mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-06-17 15:53:20 +03:00
Compare commits
39 Commits
address_wa
...
refactor_s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
72033472f3 | ||
|
|
93dc03e86f | ||
|
|
dae8443fe8 | ||
|
|
88a8bcbd5a | ||
|
|
4a5283543d | ||
|
|
7895336189 | ||
|
|
2d5cdccf7d | ||
|
|
497fd04081 | ||
|
|
ae9c46f8f0 | ||
|
|
dcd10cd690 | ||
|
|
5a35b71339 | ||
|
|
18a59219f7 | ||
|
|
c9095738e6 | ||
|
|
bed415fc6b | ||
|
|
4eeb93b9e4 | ||
|
|
0fc233686b | ||
|
|
def1d297f5 | ||
|
|
e7cf2a6fba | ||
|
|
2da2fd7671 | ||
|
|
d5175969e7 | ||
|
|
46f9630999 | ||
|
|
75adcf0ff0 | ||
|
|
866dd49ba3 | ||
|
|
d0a84d07aa | ||
|
|
089a4c0f8b | ||
|
|
1a1f816872 | ||
|
|
9d86c2828b | ||
|
|
3b6d3beaa7 | ||
|
|
bb75b6ead8 | ||
|
|
fccb2304f6 | ||
|
|
f00ef5eaae | ||
|
|
4e7ee760de | ||
|
|
c4faade30c | ||
|
|
295dc1392a | ||
|
|
445a8c747c | ||
|
|
292a6b9e1e | ||
|
|
0e04e7d31d | ||
|
|
4cf4acf7e9 | ||
|
|
c78e583399 |
52
.github/workflows/cli-docker.yml
vendored
52
.github/workflows/cli-docker.yml
vendored
@@ -8,8 +8,21 @@ name: Build and Push CLI Docker Image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- "*.*.*-cli"
|
||||
paths-ignore:
|
||||
- "docs/**"
|
||||
- "*.md"
|
||||
- "images/**"
|
||||
- "assets/**"
|
||||
- "instruction_images/**"
|
||||
- "src/apps/webapp/**"
|
||||
- "src/apps/webpeer/**"
|
||||
- ".github/workflows/release.yml"
|
||||
- ".github/workflows/unit-ci.yml"
|
||||
- ".github/workflows/harness-ci.yml"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
dry_run:
|
||||
@@ -41,14 +54,32 @@ jobs:
|
||||
id: meta
|
||||
run: |
|
||||
VERSION=$(jq -r '.version' manifest.json)
|
||||
EPOCH=$(date +%s)
|
||||
TAG="${VERSION}-${EPOCH}-cli"
|
||||
SHORT_SHA=$(git rev-parse --short HEAD)
|
||||
IMAGE="ghcr.io/${{ github.repository_owner }}/livesync-cli"
|
||||
echo "tag=${TAG}" >> $GITHUB_OUTPUT
|
||||
echo "image=${IMAGE}" >> $GITHUB_OUTPUT
|
||||
echo "full=${IMAGE}:${TAG}" >> $GITHUB_OUTPUT
|
||||
echo "version=${IMAGE}:${VERSION}-cli" >> $GITHUB_OUTPUT
|
||||
echo "latest=${IMAGE}:latest" >> $GITHUB_OUTPUT
|
||||
|
||||
# Build tag list based on the event and git ref
|
||||
TAGS=""
|
||||
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
|
||||
# Stable release builds
|
||||
TAGS="${IMAGE}:${VERSION}-cli,${IMAGE}:latest,${IMAGE}:${VERSION}-sha-${SHORT_SHA}-cli"
|
||||
elif [[ "${{ github.ref }}" == refs/heads/main ]]; then
|
||||
# Bleeding-edge / nightly builds
|
||||
TAGS="${IMAGE}:edge,${IMAGE}:${VERSION}-dev-sha-${SHORT_SHA}-cli"
|
||||
else
|
||||
# Other branches / manual run fallback
|
||||
TAGS="${IMAGE}:${VERSION}-dev-sha-${SHORT_SHA}-cli"
|
||||
fi
|
||||
|
||||
# Determine if the image should be pushed
|
||||
PUSH="true"
|
||||
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||
if [[ "${{ inputs.dry_run }}" == "true" ]]; then
|
||||
PUSH="false"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "tags=${TAGS}" >> $GITHUB_OUTPUT
|
||||
echo "push=${PUSH}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
@@ -92,10 +123,7 @@ jobs:
|
||||
with:
|
||||
context: .
|
||||
file: src/apps/cli/Dockerfile
|
||||
push: ${{ !(github.event_name == 'workflow_dispatch' && inputs.dry_run) }}
|
||||
tags: |
|
||||
${{ steps.meta.outputs.full }}
|
||||
${{ steps.meta.outputs.version }}
|
||||
${{ steps.meta.outputs.latest }}
|
||||
push: ${{ steps.meta.outputs.push }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
1
.github/workflows/release.yml
vendored
1
.github/workflows/release.yml
vendored
@@ -4,6 +4,7 @@ on:
|
||||
# Sequence of patterns matched against refs/tags
|
||||
tags:
|
||||
- '*' # Push events to matching any tag format, i.e. 1.0, 20.15.10
|
||||
- '!*-cli' # Exclude command-line interface tags
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
|
||||
179
.github/workflows/unit-ci.yml
vendored
179
.github/workflows/unit-ci.yml
vendored
@@ -1,68 +1,111 @@
|
||||
# Run Unit test without Harnesses
|
||||
name: unit-ci
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- beta
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'test/**'
|
||||
- 'package.json'
|
||||
- 'package-lock.json'
|
||||
- 'tsconfig.json'
|
||||
- 'vite.config.ts'
|
||||
- 'vitest.config*.ts'
|
||||
- 'esbuild.config.mjs'
|
||||
- 'eslint.config.mjs'
|
||||
- '.github/workflows/unit-ci.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'test/**'
|
||||
- 'package.json'
|
||||
- 'package-lock.json'
|
||||
- 'tsconfig.json'
|
||||
- 'vite.config.ts'
|
||||
- 'vitest.config*.ts'
|
||||
- 'esbuild.config.mjs'
|
||||
- 'eslint.config.mjs'
|
||||
- '.github/workflows/unit-ci.yml'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '24.x'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
# unit tests do not require Playwright, so we can skip installing its dependencies to save time
|
||||
# - name: Install test dependencies (Playwright Chromium)
|
||||
# run: npm run test:install-dependencies
|
||||
|
||||
- name: Run unit tests suite with coverage
|
||||
run: npm run test:unit:coverage
|
||||
|
||||
- name: Upload coverage report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coverage-report
|
||||
path: coverage/**
|
||||
# Run Unit test without Harnesses
|
||||
name: unit-ci
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- beta
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'test/**'
|
||||
- 'package.json'
|
||||
- 'package-lock.json'
|
||||
- 'tsconfig.json'
|
||||
- 'vite.config.ts'
|
||||
- 'vitest.config*.ts'
|
||||
- 'esbuild.config.mjs'
|
||||
- 'eslint.config.mjs'
|
||||
- '.github/workflows/unit-ci.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'test/**'
|
||||
- 'package.json'
|
||||
- 'package-lock.json'
|
||||
- 'tsconfig.json'
|
||||
- 'vite.config.ts'
|
||||
- 'vitest.config*.ts'
|
||||
- 'esbuild.config.mjs'
|
||||
- 'eslint.config.mjs'
|
||||
- '.github/workflows/unit-ci.yml'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
unit-test:
|
||||
name: Unit Tests
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '24.x'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run unit tests suite with coverage
|
||||
run: npm run test:unit:coverage
|
||||
|
||||
- name: Upload coverage report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: unit-coverage-report
|
||||
path: coverage/**
|
||||
|
||||
integration-test:
|
||||
name: Integration Tests
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '24.x'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Create environment configuration files
|
||||
run: |
|
||||
cat <<EOF > .env
|
||||
BUILD_MODE=dev
|
||||
PATHS_TEST_INSTALL=
|
||||
EOF
|
||||
cat <<EOF > .test.env
|
||||
hostname=http://127.0.0.1:5989/
|
||||
dbname=livesync-test-db2
|
||||
username=admin
|
||||
password=testpassword
|
||||
minioEndpoint=http://127.0.0.1:9000
|
||||
accessKey=minioadmin
|
||||
secretKey=minioadmin
|
||||
bucketName=livesync-test-bucket
|
||||
EOF
|
||||
|
||||
- name: Start CouchDB container
|
||||
run: npm run test:docker-couchdb:start
|
||||
|
||||
- name: Run integration tests
|
||||
run: npm run test:integration
|
||||
|
||||
- name: Stop CouchDB container
|
||||
if: always()
|
||||
run: npm run test:docker-couchdb:stop || true
|
||||
16
devs.md
16
devs.md
@@ -54,8 +54,13 @@ To facilitate development and testing, the build process can automatically copy
|
||||
|
||||
- ~~**Deno Tests**: Unit tests for platform-independent code (e.g., `HashManager.test.ts`)~~
|
||||
- This is now obsolete, migrated to vitest.
|
||||
- **Vitest** (`vitest.config.ts`): E2E test by Browser-based-harness using Playwright, unit tests.
|
||||
- Unit tests should be `*.unit.spec.ts` and placed alongside the implementation file (e.g., `ChunkFetcher.unit.spec.ts`).
|
||||
- **Vitest**:
|
||||
- **Unit Tests** (`vitest.config.unit.ts`): Unit tests run in Node.js (excluding harnesses and integration tests). Unit tests should be `*.unit.spec.ts` and placed alongside the implementation file (e.g., `ChunkFetcher.unit.spec.ts`). Executed via `npm run test:unit`.
|
||||
- **Integration Tests** (`vitest.config.integration.ts`): Tests run in Node.js against a real CouchDB instance. Integration tests should be `*.integration.spec.ts` or `*.integration.test.ts` and placed alongside the implementation file (e.g., `StreamingFetch.integration.spec.ts`). Executed via `npm run test:integration`.
|
||||
- If you add a feature that interacts with the remote database (e.g., replication changes, custom changes feed parameters, or custom HTTP queries), you strongly expected to write an integration test to verify the behaviour against a real CouchDB server.
|
||||
- **E2E Tests** (`vitest.config.ts`): End-to-end tests run in a browser-based harness using Playwright/Chromium to test full synchronisation scenarios. Executed via `npm run test`.
|
||||
- **P2P Tests** (`vitest.config.p2p.ts`): Browser-based Peer-to-Peer replication tests. Executed via `npm run test:p2p`.
|
||||
- **RPC Unit Tests** (`vitest.config.rpc-unit.ts`): RPC-specific unit tests with coverage thresholds.
|
||||
|
||||
- **Docker Services**: Tests require CouchDB, MinIO (S3), and P2P services:
|
||||
```bash
|
||||
@@ -63,11 +68,11 @@ To facilitate development and testing, the build process can automatically copy
|
||||
npm run test:full # Run tests with coverage
|
||||
npm run test:docker-all:stop # Stop services
|
||||
```
|
||||
If some services are not needed, start only required ones (e.g., `test:docker-couchdb:start`)
|
||||
If some services are not needed, start only required ones (e.g., `test:docker-couchdb:start`).
|
||||
Note that if services are already running, starting script will fail. Please stop them first.
|
||||
|
||||
- **Test Structure**:
|
||||
- `test/suite/` - Integration tests for sync operations
|
||||
- `test/suite/` - E2E tests for sync operations (running in browser)
|
||||
- `test/unit/` - Unit tests (via vitest, as harness is browser-based)
|
||||
- `test/harness/` - Mock implementations (e.g., `obsidian-mock.ts`)
|
||||
|
||||
@@ -151,7 +156,7 @@ Hence, the new feature should be implemented as follows:
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Module Implementation
|
||||
### Module Implementation (Now not recommended for new features, use services instead)
|
||||
|
||||
```typescript
|
||||
export class ModuleExample extends AbstractObsidianModule {
|
||||
@@ -204,6 +209,7 @@ In short, the situation remains unchanged for me, but it means you all become a
|
||||
## Contribution Guidelines
|
||||
|
||||
- Follow existing code style and conventions
|
||||
- Write integration tests (`*.integration.spec.ts` or `*.integration.test.ts`) when adding or modifying features that interact with the remote database, and ensure that they pass in the CI workflow.
|
||||
- Please bump dependencies with care, check artifacts after updates, with diff-tools and only expected changes in the build output (to avoid unexpected vulnerabilities).
|
||||
- When adding new features, please consider it has an OSS implementation, and avoid using proprietary services or APIs that may limit usage.
|
||||
- For example, any functionality to connect to a new type of server is expected to either have an OSS implementation available for that server, or to be managed under some responsibilities and/or limitations without disrupting existing functionality, and scope for surveillance reduced by some means (e.g., by client-side encryption, auditing the server ourselves).
|
||||
|
||||
@@ -488,6 +488,11 @@ Automatically Sync all files when opening Obsidian.
|
||||
Setting key: syncAfterMerge
|
||||
Sync automatically after merging files
|
||||
|
||||
#### Keep replication active in the background
|
||||
|
||||
Setting key: keepReplicationActiveInBackground
|
||||
Desktop only; uses more battery and network.
|
||||
|
||||
### 3. Update thinning
|
||||
|
||||
#### Batch database update
|
||||
|
||||
@@ -19,11 +19,15 @@ const packageJson = JSON.parse(fs.readFileSync("./package.json") + "");
|
||||
const updateInfo = JSON.stringify(fs.readFileSync("./updates.md") + "");
|
||||
|
||||
const PATHS_TEST_INSTALL = process.env?.PATHS_TEST_INSTALL || "";
|
||||
const PATH_TEST_INSTALL = PATHS_TEST_INSTALL.split(path.delimiter).map(p => p.trim()).filter(p => p.length);
|
||||
const PATH_TEST_INSTALL = PATHS_TEST_INSTALL.split(path.delimiter)
|
||||
.map((p) => p.trim())
|
||||
.filter((p) => p.length);
|
||||
if (PATH_TEST_INSTALL) {
|
||||
console.log(`Built files will be copied to ${PATH_TEST_INSTALL}`);
|
||||
} else {
|
||||
console.log("Development build: You can install the plug-in to Obsidian for testing by exporting the PATHS_TEST_INSTALL environment variable with the paths to your vault plugins directories separated by your system path delimiter (':' on Unix, ';' on Windows).");
|
||||
console.log(
|
||||
"Development build: You can install the plug-in to Obsidian for testing by exporting the PATHS_TEST_INSTALL environment variable with the paths to your vault plugins directories separated by your system path delimiter (':' on Unix, ';' on Windows)."
|
||||
);
|
||||
}
|
||||
|
||||
const moduleAliasPlugin = {
|
||||
@@ -66,6 +70,34 @@ const moduleAliasPlugin = {
|
||||
},
|
||||
};
|
||||
|
||||
const removePragmaCommentsPlugin = {
|
||||
name: "remove-pragma-comments",
|
||||
setup(build) {
|
||||
// Filter target extensions (e.g., JavaScript and TypeScript)
|
||||
build.onLoad({ filter: /\.[jt]s?$/ }, async (args) => {
|
||||
const source = await fs.promises.readFile(args.path, "utf8");
|
||||
|
||||
// Regex targeting both single-line and multi-line comments
|
||||
// This regex looks for:
|
||||
// - /* eslint ... */ (multi-line)
|
||||
// const esLintPragmaRegexBlock = /\/\*[\s\S]*?eslint[\s\S]*?\*\/|([^\\:]|^)\/\/.*eslint.*$/gm;
|
||||
// - // eslint-disable-next-line
|
||||
let cleanedSource = source;
|
||||
const tsIgnoreRegex = /\/\*\s*@ts-ignore\s*\*\/|([^\\:]|^)\/\/.*?@ts-ignore.*$/gm;
|
||||
const esLintPragmaRegexLine = /([^\\:]|^)\/\/.*?eslint-.*$/gm;
|
||||
const exps = [tsIgnoreRegex, esLintPragmaRegexLine];
|
||||
for (const exp of exps) {
|
||||
cleanedSource = cleanedSource.replace(exp, "$1");
|
||||
}
|
||||
|
||||
return {
|
||||
contents: cleanedSource,
|
||||
loader: args.path.endsWith("ts") ? "ts" : "js",
|
||||
};
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
/** @type esbuild.Plugin[] */
|
||||
const plugins = [
|
||||
{
|
||||
@@ -177,6 +209,7 @@ const context = await esbuild.context({
|
||||
preprocess: sveltePreprocess(),
|
||||
compilerOptions: { css: "injected", preserveComments: false },
|
||||
}),
|
||||
removePragmaCommentsPlugin,
|
||||
...plugins,
|
||||
],
|
||||
});
|
||||
|
||||
143
eslint.config.common.mjs
Normal file
143
eslint.config.common.mjs
Normal file
@@ -0,0 +1,143 @@
|
||||
const restrictedGlobalsOptions = [
|
||||
{
|
||||
name: "app",
|
||||
message: "Avoid using the global app object. Instead use the reference provided by your plugin instance.",
|
||||
},
|
||||
"warn",
|
||||
{
|
||||
name: "fetch",
|
||||
message: "Use the built-in `requestUrl` function instead of `fetch` for network requests in Obsidian.",
|
||||
},
|
||||
{
|
||||
name: "localStorage",
|
||||
message:
|
||||
"Prefer `App#saveLocalStorage` / `App#loadLocalStorage` functions to write / read localStorage data that's unique to a vault.",
|
||||
},
|
||||
];
|
||||
const restrictedImportsOptions = [
|
||||
{
|
||||
name: "axios",
|
||||
message: "Use the built-in `requestUrl` function instead of `axios`.",
|
||||
},
|
||||
{
|
||||
name: "superagent",
|
||||
message: "Use the built-in `requestUrl` function instead of `superagent`.",
|
||||
},
|
||||
{
|
||||
name: "got",
|
||||
message: "Use the built-in `requestUrl` function instead of `got`.",
|
||||
},
|
||||
{
|
||||
name: "ofetch",
|
||||
message: "Use the built-in `requestUrl` function instead of `ofetch`.",
|
||||
},
|
||||
{
|
||||
name: "ky",
|
||||
message: "Use the built-in `requestUrl` function instead of `ky`.",
|
||||
},
|
||||
{
|
||||
name: "node-fetch",
|
||||
message: "Use the built-in `requestUrl` function instead of `node-fetch`.",
|
||||
},
|
||||
{
|
||||
name: "moment",
|
||||
message: "The 'moment' package is bundled with Obsidian. Please import it from 'obsidian' instead.",
|
||||
},
|
||||
];
|
||||
|
||||
const warnWhileDev = "off";
|
||||
|
||||
/**
|
||||
* @type {import("eslint").Linter.RulesRecord}
|
||||
*/
|
||||
export const baseRules = {
|
||||
// -- Base rules (turned off in favour of TS specific versions or explicitly disabled).
|
||||
"no-unused-vars": "off",
|
||||
"no-unused-labels": "off",
|
||||
"no-prototype-builtins": "off",
|
||||
"require-await": "off",
|
||||
// -- TypeScript specific rules (Gradual adoption of stricter rules, currently set to 'warn' for a while).
|
||||
"@typescript-eslint/no-explicit-any": "warn",
|
||||
"@typescript-eslint/no-redundant-type-constituents": "warn",
|
||||
// -- TypeScript specific rules
|
||||
// @typescript-eslint/no-unsafe-* rules and @typescript-eslint/no-explicit-any:
|
||||
// This project contains a lot of library-sh code where the use of `any` is often necessary and justified.
|
||||
// Rules is now set to 'off' for a while.
|
||||
"@typescript-eslint/no-unsafe-argument": "off",
|
||||
"@typescript-eslint/no-unsafe-call": "off",
|
||||
"@typescript-eslint/no-unsafe-member-access": "off",
|
||||
"@typescript-eslint/no-unsafe-return": "off",
|
||||
"@typescript-eslint/no-unsafe-assignment": "off",
|
||||
// -- Reasonable rules.
|
||||
"@typescript-eslint/no-deprecated": warnWhileDev,
|
||||
"@typescript-eslint/no-unused-vars": ["error", { args: "none" }],
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"@typescript-eslint/require-await": "error",
|
||||
"@typescript-eslint/no-misused-promises": "error",
|
||||
"@typescript-eslint/no-floating-promises": "error",
|
||||
"@typescript-eslint/no-unnecessary-type-assertion": "error",
|
||||
|
||||
// -- General rules
|
||||
"no-async-promise-executor": warnWhileDev,
|
||||
"no-constant-condition": ["error", { checkLoops: false }],
|
||||
// -- Disabled rules
|
||||
// no-undef: This option breaks the global declarations for the library files and is not worth the effort to fix at this time.
|
||||
"no-undef": "off",
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {import("eslint").Linter.RulesRecord}
|
||||
*/
|
||||
export const obsidianRules = {
|
||||
// -- Obsidian rules
|
||||
// obsidianmd/no-unsupported-api: usually this project checks for API support at runtime, so this rule is not critical but can be helpful to catch potential issues.
|
||||
"obsidianmd/no-unsupported-api": warnWhileDev,
|
||||
|
||||
// -- Plugin specific overrides
|
||||
"obsidianmd/rule-custom-message": "off",
|
||||
"obsidianmd/ui/sentence-case": "off",
|
||||
"obsidianmd/no-plugin-as-component": "off",
|
||||
|
||||
// -- Temporary overrides for migration
|
||||
"obsidianmd/no-static-styles-assignment": "off",
|
||||
};
|
||||
/**
|
||||
* @type {(base:string) => import("eslint").Linter.RulesRecord}
|
||||
*/
|
||||
export const ImportAliasRules = (base) => ({
|
||||
"@dword-design/import-alias/prefer-alias": [
|
||||
"error",
|
||||
{
|
||||
aliasForSubpaths: true,
|
||||
alias: {
|
||||
"@": `${base}/src`,
|
||||
"@lib": `${base}/src/lib/src`,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
/**
|
||||
* @type {import("eslint").Linter.RulesRecord}
|
||||
*/
|
||||
export const CommunityReviewRecommendedRules = {
|
||||
"no-unused-vars": "off",
|
||||
"no-prototype-bultins": "off",
|
||||
"no-self-compare": "warn",
|
||||
"no-eval": "error",
|
||||
"no-implied-eval": "error",
|
||||
"prefer-const": "off",
|
||||
"no-implicit-globals": "error",
|
||||
"no-console": "off", // overridden by obsidianmd/rule-custom-message
|
||||
"no-restricted-globals": ["error", ...restrictedGlobalsOptions],
|
||||
"no-restricted-imports": ["error", ...restrictedImportsOptions],
|
||||
"no-alert": "error",
|
||||
"no-undef": "error",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/no-deprecated": "error",
|
||||
"@typescript-eslint/no-unused-vars": ["warn", { args: "none" }],
|
||||
"@typescript-eslint/require-await": "off",
|
||||
"@typescript-eslint/no-explicit-any": ["error", { fixToUnknown: true }],
|
||||
// "import/no-nodejs-modules": "off",
|
||||
// "import/no-extraneous-dependencies": "error",
|
||||
};
|
||||
@@ -4,6 +4,8 @@ import globals from "globals";
|
||||
import { defineConfig, globalIgnores } from "eslint/config";
|
||||
import * as sveltePlugin from "eslint-plugin-svelte";
|
||||
import svelteParser from "svelte-eslint-parser";
|
||||
import importAlias from "@dword-design/eslint-plugin-import-alias";
|
||||
import { baseRules, ImportAliasRules, obsidianRules } from "./eslint.config.common.mjs";
|
||||
const warnWhileDev = "off"; // Change to "warn" to enable warnings for rules that are currently disabled.
|
||||
export default defineConfig([
|
||||
globalIgnores([
|
||||
@@ -18,11 +20,11 @@ export default defineConfig([
|
||||
"**/*.json",
|
||||
"**/.eslintrc.js.bak",
|
||||
// Files from linked dependencies (those files should not exist for most people).
|
||||
"modules/octagonal-wheels/dist/**/*",
|
||||
"modules/octagonal-wheels/dist",
|
||||
|
||||
// Sub-projects (Exclude from root linting as they have different environments)
|
||||
"src/apps/**/*",
|
||||
"utils/**/*",
|
||||
"src/apps",
|
||||
"utils",
|
||||
|
||||
// Specific exclusions from common library (src/lib)
|
||||
"src/lib/coverage",
|
||||
@@ -54,6 +56,7 @@ export default defineConfig([
|
||||
]),
|
||||
...sveltePlugin.configs["flat/base"],
|
||||
...obsidianmd.configs.recommended,
|
||||
importAlias.configs.recommended,
|
||||
{
|
||||
files: ["**/*.ts"],
|
||||
// ignores:["src/lib/**/*.ts"], // Exclude library files from root linting (they have different environments and rules).
|
||||
@@ -62,64 +65,29 @@ export default defineConfig([
|
||||
parser: tsParser,
|
||||
parserOptions: {
|
||||
project: "./tsconfig.json",
|
||||
rootDir: "./",
|
||||
},
|
||||
},
|
||||
linterOptions:{
|
||||
linterOptions: {
|
||||
reportUnusedDisableDirectives: false,
|
||||
},
|
||||
rules: {
|
||||
// -- Base rules (turned off in favour of TS specific versions or explicitly disabled).
|
||||
"no-unused-vars": "off",
|
||||
"no-unused-labels": "off",
|
||||
"no-prototype-builtins": "off",
|
||||
"require-await": "off",
|
||||
// -- TypeScript specific rules
|
||||
// @typescript-eslint/no-unsafe-* rules and @typescript-eslint/no-explicit-any:
|
||||
// This project contains a lot of library-sh code where the use of `any` is often necessary and justified.
|
||||
// Rules is now set to 'off' for a while.
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-unsafe-argument": "off",
|
||||
"@typescript-eslint/no-unsafe-call": "off",
|
||||
"@typescript-eslint/no-unsafe-member-access": "off",
|
||||
"@typescript-eslint/no-unsafe-return": "off",
|
||||
"@typescript-eslint/no-unsafe-assignment": "off",
|
||||
// -- Reasonable rules.
|
||||
"@typescript-eslint/no-deprecated": warnWhileDev,
|
||||
"@typescript-eslint/no-unused-vars": ["error", { args: "none" }],
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"@typescript-eslint/require-await": "error",
|
||||
"@typescript-eslint/no-misused-promises": "error",
|
||||
"@typescript-eslint/no-floating-promises": "error",
|
||||
"@typescript-eslint/no-unnecessary-type-assertion": "error",
|
||||
|
||||
// -- Obsidian rules
|
||||
// obsidianmd/no-unsupported-api: usually this project checks for API support at runtime, so this rule is not critical but can be helpful to catch potential issues.
|
||||
"obsidianmd/no-unsupported-api": warnWhileDev,
|
||||
|
||||
// -- General rules
|
||||
"no-async-promise-executor": warnWhileDev,
|
||||
"no-constant-condition": ["error", { checkLoops: false }],
|
||||
// -- Disabled rules
|
||||
// no-undef: This option breaks the global declarations for the library files and is not worth the effort to fix at this time.
|
||||
"no-undef": "off",
|
||||
|
||||
// -- Plugin specific overrides
|
||||
"obsidianmd/rule-custom-message": "off",
|
||||
"obsidianmd/ui/sentence-case": "off",
|
||||
"obsidianmd/no-plugin-as-component": "off",
|
||||
|
||||
// -- Temporary overrides for migration
|
||||
"obsidianmd/no-static-styles-assignment": "off",
|
||||
...baseRules,
|
||||
...obsidianRules,
|
||||
// -- Project specific rules
|
||||
...ImportAliasRules("."),
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["**/*.svelte"],
|
||||
languageOptions: {
|
||||
globals: { ...globals.browser, PouchDB: "readonly" },
|
||||
parser: svelteParser,
|
||||
parserOptions: {
|
||||
parser: tsParser,
|
||||
extraFileExtensions: [".svelte"],
|
||||
project: "./tsconfig.json",
|
||||
rootDir: "./",
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
@@ -127,8 +95,8 @@ export default defineConfig([
|
||||
// Svelte template's declarations have a lot of false positives and the rule is not worth the effort to fix at this time.
|
||||
// it may improve in the future with some options as like ["error", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }],]
|
||||
"no-unused-vars": "off",
|
||||
"obsidianmd/no-plugin-as-component": "off",
|
||||
"obsidianmd/ui/sentence-case": "off",
|
||||
...obsidianRules,
|
||||
...ImportAliasRules("."),
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-livesync",
|
||||
"name": "Self-hosted LiveSync",
|
||||
"version": "0.25.74",
|
||||
"version": "0.25.76",
|
||||
"minAppVersion": "1.7.2",
|
||||
"description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
|
||||
"author": "vorotamoroz",
|
||||
|
||||
3941
package-lock.json
generated
3941
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
53
package.json
53
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "obsidian-livesync",
|
||||
"version": "0.25.74",
|
||||
"version": "0.25.76",
|
||||
"description": "Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
|
||||
"main": "main.js",
|
||||
"type": "module",
|
||||
@@ -25,10 +25,12 @@
|
||||
"pretty": "npm run prettyNoWrite -- --write --log-level error",
|
||||
"prettyCheck": "npm run prettyNoWrite -- --check",
|
||||
"prettyNoWrite": "prettier --config ./.prettierrc.mjs \"**/*.js\" \"**/*.ts\" \"**/*.json\" ",
|
||||
"check": "npm run tsc-check && npm run lint && npm run svelte-check",
|
||||
"check:compatibility": "node utils/check-compatibility.js --file main.js --ios 15",
|
||||
"check": "npm run tsc-check && npm run lint && npm run svelte-check && npm run check:compatibility",
|
||||
"unittest": "deno test -A --no-check --coverage=cov_profile --v8-flags=--expose-gc --trace-leaks ./src/",
|
||||
"test": "vitest run",
|
||||
"test:unit": "vitest run --config vitest.config.unit.ts",
|
||||
"test:integration": "vitest run --config vitest.config.integration.ts",
|
||||
"test:unit:coverage": "vitest run --config vitest.config.unit.ts --coverage",
|
||||
"test:install-playwright": "npx playwright install chromium",
|
||||
"test:install-dependencies": "npm run test:install-playwright",
|
||||
@@ -55,15 +57,17 @@
|
||||
"test:docker-all:stop": "npm run test:docker-all:down",
|
||||
"test:full": "npm run test:docker-all:start && vitest run --coverage && npm run test:docker-all:stop",
|
||||
"test:p2p": "bash test/suitep2p/run-p2p-tests.sh",
|
||||
"version": "node version-bump.mjs && git add manifest.json versions.json"
|
||||
"update-workspaces": "node update-workspaces.mjs",
|
||||
"version": "node version-bump.mjs && node update-workspaces.mjs && git add manifest.json versions.json src/apps/cli/package.json src/apps/webpeer/package.json src/apps/webapp/package.json"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "vorotamoroz",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@chialab/esbuild-plugin-worker": "^0.19.0",
|
||||
"@dword-design/eslint-plugin-import-alias": "^8.1.8",
|
||||
"@eslint/js": "^9.39.3",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
||||
"@playwright/test": "^1.58.2",
|
||||
"@sveltejs/vite-plugin-svelte": "^7.1.2",
|
||||
"@tsconfig/svelte": "^5.0.8",
|
||||
"@types/deno": "^2.5.0",
|
||||
"@types/diff-match-patch": "^1.0.36",
|
||||
@@ -78,23 +82,21 @@
|
||||
"@types/pouchdb-mapreduce": "^6.1.10",
|
||||
"@types/pouchdb-replication": "^6.4.7",
|
||||
"@types/transform-pouch": "^1.0.6",
|
||||
"@typescript-eslint/eslint-plugin": "8.56.1",
|
||||
"@typescript-eslint/parser": "8.56.1",
|
||||
"@vitest/browser": "^4.1.8",
|
||||
"@vitest/browser-playwright": "^4.1.8",
|
||||
"@vitest/coverage-v8": "^4.1.8",
|
||||
"dotenv-cli": "^11.0.0",
|
||||
"esbuild": "0.25.0",
|
||||
"esbuild": "0.28.1",
|
||||
"esbuild-plugin-inline-worker": "^0.1.1",
|
||||
"esbuild-svelte": "^0.9.4",
|
||||
"eslint": "^9.39.3",
|
||||
"eslint-plugin-obsidianmd": "^0.3.0",
|
||||
"eslint-plugin-svelte": "^3.15.0",
|
||||
"eslint-plugin-svelte": "^3.19.0",
|
||||
"events": "^3.3.0",
|
||||
"globals": "^14.0.0",
|
||||
"playwright": "^1.58.2",
|
||||
"postcss": "^8.5.6",
|
||||
"postcss-load-config": "^6.0.1",
|
||||
"pouchdb-adapter-http": "^9.0.0",
|
||||
"pouchdb-adapter-idb": "^9.0.0",
|
||||
"pouchdb-adapter-indexeddb": "^9.0.0",
|
||||
@@ -108,20 +110,22 @@
|
||||
"pouchdb-utils": "^9.0.0",
|
||||
"prettier": "3.8.1",
|
||||
"rollup-plugin-copy": "^3.5.0",
|
||||
"svelte": "5.41.1",
|
||||
"svelte-check": "^4.4.3",
|
||||
"svelte": "5.56.3",
|
||||
"svelte-check": "^4.6.0",
|
||||
"svelte-eslint-parser": "^1.8.0",
|
||||
"svelte-preprocess": "^6.0.3",
|
||||
"terser": "^5.39.0",
|
||||
"tinyglobby": "^0.2.15",
|
||||
"transform-pouch": "^2.0.0",
|
||||
"tslib": "^2.8.1",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "5.9.3",
|
||||
"vite": "^7.3.1",
|
||||
"vite-plugin-istanbul": "^8.0.0",
|
||||
"typescript-eslint": "^8.61.0",
|
||||
"vite": "^8.0.16",
|
||||
"vitest": "^4.1.8",
|
||||
"webdriverio": "^9.27.0",
|
||||
"yaml": "^2.8.2"
|
||||
"yaml": "^2.8.2",
|
||||
"@emnapi/core": "1.11.1",
|
||||
"@emnapi/runtime": "1.11.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.808.0",
|
||||
@@ -130,21 +134,22 @@
|
||||
"@smithy/middleware-apply-body-checksum": "^4.3.9",
|
||||
"@smithy/protocol-http": "^5.3.9",
|
||||
"@smithy/querystring-builder": "^4.2.9",
|
||||
"@smithy/types": "^4.14.3",
|
||||
"@smithy/util-retry": "^4.4.5",
|
||||
"@trystero-p2p/nostr": "^0.24.0",
|
||||
"chokidar": "^4.0.0",
|
||||
"commander": "^14.0.3",
|
||||
"diff-match-patch": "^1.0.5",
|
||||
"fflate": "^0.8.2",
|
||||
"idb": "^8.0.3",
|
||||
"markdown-it": "^14.1.1",
|
||||
"micromatch": "^4.0.0",
|
||||
"minimatch": "^10.2.2",
|
||||
"obsidian": "^1.12.3",
|
||||
"markdown-it": "^14.2.0",
|
||||
"minimatch": "^10.2.5",
|
||||
"obsidian": "^1.13.1",
|
||||
"octagonal-wheels": "^0.1.46",
|
||||
"pouchdb-adapter-leveldb": "^9.0.0",
|
||||
"qrcode-generator": "^1.4.4",
|
||||
"werift": "^0.23.0",
|
||||
"xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2"
|
||||
}
|
||||
},
|
||||
"workspaces": [
|
||||
"src/apps/cli",
|
||||
"src/apps/webpeer",
|
||||
"src/apps/webapp"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -82,8 +82,8 @@ RUN apt-get update \
|
||||
|
||||
WORKDIR /deps
|
||||
|
||||
# runtime-package.json lists only the packages that Vite leaves external
|
||||
COPY src/apps/cli/runtime-package.json ./package.json
|
||||
# package.json lists only the packages that the CLI requires
|
||||
COPY src/apps/cli/package.json ./package.json
|
||||
RUN npm install --omit=dev
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -118,19 +118,26 @@ git submodule update --init --recursive
|
||||
# Install dependencies from the repository root
|
||||
npm install
|
||||
|
||||
# Build the CLI from its package directory
|
||||
# Build the CLI from the repository root
|
||||
npm run build -w self-hosted-livesync-cli
|
||||
|
||||
# Or from the package directory
|
||||
cd src/apps/cli
|
||||
npm run build
|
||||
```
|
||||
|
||||
If `src/lib` is missing, `npm run build` now stops early with a targeted message
|
||||
instead of a low-level Vite `ENOENT` error.
|
||||
If `src/lib` is missing, the build process stops early with a targeted message instead of a low-level Vite `ENOENT` error.
|
||||
|
||||
Run the CLI:
|
||||
|
||||
```bash
|
||||
# Run with npm script (from repository root)
|
||||
npm run --silent cli -- [database-path] [command] [args...]
|
||||
# Run with npm workspace script (from repository root)
|
||||
npm run cli -w self-hosted-livesync-cli -- [database-path] [command] [args...]
|
||||
|
||||
# Or from the package directory
|
||||
cd src/apps/cli
|
||||
npm run cli -- [database-path] [command] [args...]
|
||||
|
||||
# Run the built executable directly
|
||||
node src/apps/cli/dist/index.cjs [database-path] [command] [args...]
|
||||
```
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as path from "path";
|
||||
import type { UXFileInfoStub, UXFolderInfo } from "@lib/common/types";
|
||||
import type { IConversionAdapter } from "@lib/serviceModules/adapters";
|
||||
import type { NodeFile, NodeFolder } from "./NodeTypes";
|
||||
import { path } from "../node-compat";
|
||||
|
||||
/**
|
||||
* Conversion adapter implementation for Node.js
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import * as fs from "fs/promises";
|
||||
import * as path from "path";
|
||||
import type { FilePath, UXStat } from "@lib/common/types";
|
||||
import type { IFileSystemAdapter } from "@lib/serviceModules/adapters";
|
||||
import { NodePathAdapter } from "./NodePathAdapter";
|
||||
@@ -8,6 +6,7 @@ import { NodeConversionAdapter } from "./NodeConversionAdapter";
|
||||
import { NodeStorageAdapter } from "./NodeStorageAdapter";
|
||||
import { NodeVaultAdapter } from "./NodeVaultAdapter";
|
||||
import type { NodeFile, NodeFolder, NodeStat } from "./NodeTypes";
|
||||
import { fsPromises as fs, path } from "../node-compat";
|
||||
|
||||
/**
|
||||
* Complete file system adapter implementation for Node.js
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as path from "path";
|
||||
import type { FilePath } from "@lib/common/types";
|
||||
import type { IPathAdapter } from "@lib/serviceModules/adapters";
|
||||
import type { NodeFile } from "./NodeTypes";
|
||||
import { path } from "../node-compat";
|
||||
|
||||
/**
|
||||
* Path adapter implementation for Node.js
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import * as fs from "fs/promises";
|
||||
import * as path from "path";
|
||||
import type { UXDataWriteOptions } from "@lib/common/types";
|
||||
import type { IStorageAdapter } from "@lib/serviceModules/adapters";
|
||||
import type { NodeStat } from "./NodeTypes";
|
||||
import { fsPromises as fs, path } from "../node-compat";
|
||||
|
||||
/**
|
||||
* Storage adapter implementation for Node.js
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import * as fs from "fs/promises";
|
||||
import * as path from "path";
|
||||
import type { UXDataWriteOptions } from "@lib/common/types";
|
||||
import type { IVaultAdapter } from "@lib/serviceModules/adapters";
|
||||
import type { NodeFile, NodeFolder, NodeStat } from "./NodeTypes";
|
||||
import type { NodeFile, NodeFolder } from "./NodeTypes";
|
||||
import { fsPromises as fs, path } from "../node-compat";
|
||||
|
||||
/**
|
||||
* Vault adapter implementation for Node.js
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import type { LiveSyncBaseCore } from "../../../LiveSyncBaseCore";
|
||||
import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore";
|
||||
import { P2P_DEFAULT_SETTINGS } from "@lib/common/types";
|
||||
import type { ServiceContext } from "@lib/services/base/ServiceBase";
|
||||
import { LiveSyncTrysteroReplicator } from "@lib/replication/trystero/LiveSyncTrysteroReplicator";
|
||||
import { addP2PEventHandlers } from "@lib/replication/trystero/addP2PEventHandlers";
|
||||
import { compatGlobal } from "@lib/common/coreEnvFunctions.ts";
|
||||
|
||||
type CLIP2PPeer = {
|
||||
peerId: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
function delay(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
return new Promise((resolve) => compatGlobal.setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
export function parseTimeoutSeconds(value: string, commandName: string): number {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import * as fs from "fs/promises";
|
||||
import * as path from "path";
|
||||
import { decodeSettingsFromSetupURI } from "@lib/API/processSetting";
|
||||
import { configURIBase } from "@lib/common/models/shared.const";
|
||||
import {
|
||||
@@ -18,6 +16,8 @@ import { promptForPassphrase, readStdinAsUtf8, toArrayBuffer, toDatabaseRelative
|
||||
import { collectPeers, openP2PHost, parseTimeoutSeconds, syncWithPeer } from "./p2p";
|
||||
import { performFullScan } from "@lib/serviceFeatures/offlineScanner";
|
||||
import { UnresolvedErrorManager } from "@lib/services/base/UnresolvedErrorManager";
|
||||
import { compatGlobal } from "@lib/common/coreEnvFunctions.ts";
|
||||
import { fsPromises as fs, path } from "../node-compat";
|
||||
|
||||
function redactConnectionString(uri: string): string {
|
||||
return uri.replace(/\/\/([^@/]+)@/u, "//***@");
|
||||
@@ -150,11 +150,11 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext
|
||||
);
|
||||
}
|
||||
}
|
||||
pollTimer = setTimeout(poll, currentIntervalMs);
|
||||
pollTimer = compatGlobal.setTimeout(poll, currentIntervalMs);
|
||||
};
|
||||
let pollTimer: ReturnType<typeof setTimeout> = setTimeout(poll, currentIntervalMs);
|
||||
let pollTimer = compatGlobal.setTimeout(poll, currentIntervalMs);
|
||||
core.services.appLifecycle.onUnload.addHandler(async () => {
|
||||
clearTimeout(pollTimer);
|
||||
compatGlobal.clearTimeout(pollTimer);
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { LiveSyncBaseCore } from "../../../LiveSyncBaseCore";
|
||||
import { LiveSyncBaseCore } from "@/LiveSyncBaseCore";
|
||||
import { ServiceContext } from "@lib/services/base/ServiceBase";
|
||||
import type { ObsidianLiveSyncSettings } from "@lib/common/types";
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as path from "path";
|
||||
import * as readline from "node:readline/promises";
|
||||
import { path, readline } from "../node-compat";
|
||||
|
||||
export function toArrayBuffer(data: Buffer): ArrayBuffer {
|
||||
return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength) as ArrayBuffer;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
// eslint-disable -- This is the entry point for the CLI application.
|
||||
import * as polyfill from "werift";
|
||||
import { main } from "./main";
|
||||
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
/**
|
||||
* Self-hosted LiveSync CLI
|
||||
* Command-line version of Self-hosted LiveSync plugin for syncing vaults without Obsidian
|
||||
*/
|
||||
|
||||
import * as fs from "fs/promises";
|
||||
import * as path from "path";
|
||||
import { NodeServiceContext, NodeServiceHub } from "./services/NodeServiceHub";
|
||||
import { configureNodeLocalStorage, ensureGlobalNodeLocalStorage } from "./services/NodeLocalStorage";
|
||||
import { LiveSyncBaseCore } from "../../LiveSyncBaseCore";
|
||||
import { LiveSyncBaseCore } from "@/LiveSyncBaseCore";
|
||||
import { initialiseServiceModulesCLI } from "./serviceModules/CLIServiceModules";
|
||||
import { DEFAULT_SETTINGS, LOG_LEVEL_VERBOSE, type LOG_LEVEL, type ObsidianLiveSyncSettings } from "@lib/common/types";
|
||||
import type { InjectableServiceHub } from "@lib/services/implements/injectable/InjectableServiceHub";
|
||||
import type { InjectableSettingService } from "@/lib/src/services/implements/injectable/InjectableSettingService";
|
||||
import type { InjectableSettingService } from "@lib/services/implements/injectable/InjectableSettingService";
|
||||
import {
|
||||
LOG_LEVEL_DEBUG,
|
||||
setGlobalLogFunction,
|
||||
@@ -26,7 +19,8 @@ import type { CLICommand, CLIOptions } from "./commands/types";
|
||||
import { getPathFromUXFileInfo } from "@lib/common/typeUtils";
|
||||
import { stripAllPrefixes } from "@lib/string_and_binary/path";
|
||||
import { IgnoreRules } from "./serviceModules/IgnoreRules";
|
||||
import { useP2PReplicatorFeature } from "@/lib/src/replication/trystero/useP2PReplicatorFeature";
|
||||
import { useP2PReplicatorFeature } from "@lib/replication/trystero/useP2PReplicatorFeature";
|
||||
import { fsPromises as fs, path, fs as fsSync } from "./node-compat";
|
||||
|
||||
const SETTINGS_FILE = ".livesync/settings.json";
|
||||
ensureGlobalNodeLocalStorage();
|
||||
@@ -238,8 +232,8 @@ async function createDefaultSettingsFile(options: CLIOptions) {
|
||||
const targetPath = options.settingsPath
|
||||
? path.resolve(options.settingsPath)
|
||||
: options.commandArgs[0]
|
||||
? path.resolve(options.commandArgs[0])
|
||||
: path.resolve(process.cwd(), "data.json");
|
||||
? path.resolve(options.commandArgs[0])
|
||||
: path.resolve(process.cwd(), "data.json");
|
||||
|
||||
if (!options.force) {
|
||||
try {
|
||||
@@ -329,8 +323,8 @@ export async function main() {
|
||||
options.command === "mirror" && options.commandArgs[0]
|
||||
? path.resolve(options.commandArgs[0])
|
||||
: options.vaultPath
|
||||
? path.resolve(options.vaultPath)
|
||||
: databasePath!;
|
||||
? path.resolve(options.vaultPath)
|
||||
: databasePath!;
|
||||
|
||||
// Check if vault directory exists
|
||||
try {
|
||||
@@ -485,8 +479,8 @@ export async function main() {
|
||||
}
|
||||
};
|
||||
|
||||
process.on("SIGINT", () => shutdown("SIGINT"));
|
||||
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
||||
process.on("SIGINT", () => void shutdown("SIGINT"));
|
||||
process.on("SIGTERM", () => void shutdown("SIGTERM"));
|
||||
|
||||
// Save the settings file before any lifecycle events can mutate and persist them.
|
||||
// suspendAllSync and other lifecycle hooks clobber sync settings in memory, and
|
||||
@@ -499,8 +493,8 @@ export async function main() {
|
||||
if (settingsBackup) {
|
||||
const tmpPath = settingsPath + ".tmp";
|
||||
try {
|
||||
require("fs").writeFileSync(tmpPath, settingsBackup, "utf-8");
|
||||
require("fs").renameSync(tmpPath, settingsPath);
|
||||
fsSync.writeFileSync(tmpPath, settingsBackup, "utf-8");
|
||||
fsSync.renameSync(tmpPath, settingsPath);
|
||||
} catch (err) {
|
||||
console.error("[Settings] Failed to restore settings on exit:", err);
|
||||
}
|
||||
@@ -563,7 +557,7 @@ export async function main() {
|
||||
|
||||
if (options.command === "daemon" && result) {
|
||||
// Keep the process running
|
||||
await new Promise(() => {});
|
||||
await new Promise(() => { });
|
||||
} else {
|
||||
await core.services.control.onUnload();
|
||||
}
|
||||
|
||||
@@ -10,12 +10,10 @@ import type {
|
||||
IStorageEventWatchHandlers,
|
||||
} from "@lib/managers/adapters";
|
||||
import type { FileEventItemSentinel } from "@lib/managers/StorageEventManager";
|
||||
import type { NodeFile, NodeFolder } from "../adapters/NodeTypes";
|
||||
import type { Stats } from "fs";
|
||||
import * as fs from "fs/promises";
|
||||
import * as path from "path";
|
||||
import type { NodeFile, NodeFolder } from "@/apps/cli/adapters/NodeTypes";
|
||||
import { watch as chokidarWatch, type FSWatcher } from "chokidar";
|
||||
import type { IgnoreRules } from "../serviceModules/IgnoreRules";
|
||||
import type { IgnoreRules } from "@/apps/cli/serviceModules/IgnoreRules";
|
||||
import { fsPromises as fs, path, type Stats } from "../node-compat";
|
||||
|
||||
/**
|
||||
* CLI-specific type guard adapter
|
||||
@@ -101,7 +99,7 @@ class CLIWatchAdapter implements IStorageEventWatchAdapter {
|
||||
private basePath: string,
|
||||
private ignoreRules?: IgnoreRules,
|
||||
private watchEnabled: boolean = false
|
||||
) {}
|
||||
) { }
|
||||
|
||||
private _toNodeFile(filePath: string, stats: Stats | undefined): NodeFile {
|
||||
return {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it, vi, beforeEach } from "vitest";
|
||||
import type { IStorageEventWatchHandlers } from "@lib/managers/adapters";
|
||||
import type { NodeFile } from "../adapters/NodeTypes";
|
||||
import type { NodeFile } from "@/apps/cli/adapters/NodeTypes";
|
||||
|
||||
// ── chokidar mock ──────────────────────────────────────────────────────────────
|
||||
// Must be hoisted before imports that pull in chokidar.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { StorageEventManagerBase, type StorageEventManagerBaseDependencies } from "@lib/managers/StorageEventManager";
|
||||
import { CLIStorageEventManagerAdapter } from "./CLIStorageEventManagerAdapter";
|
||||
import type { IMinimumLiveSyncCommands, LiveSyncBaseCore } from "../../../LiveSyncBaseCore";
|
||||
import type { IMinimumLiveSyncCommands, LiveSyncBaseCore } from "@/LiveSyncBaseCore";
|
||||
import type { ServiceContext } from "@lib/services/base/ServiceBase";
|
||||
import type { IgnoreRules } from "../serviceModules/IgnoreRules";
|
||||
import type { IgnoreRules } from "@/apps/cli/serviceModules/IgnoreRules";
|
||||
// import type { IMinimumLiveSyncCommands } from "@lib/services/base/IService";
|
||||
|
||||
export class StorageEventManagerCLI extends StorageEventManagerBase<CLIStorageEventManagerAdapter> {
|
||||
|
||||
13
src/apps/cli/node-compat.ts
Normal file
13
src/apps/cli/node-compat.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
/* eslint-disable obsidianmd/no-nodejs-builtins */
|
||||
import * as nodeFs from "node:fs";
|
||||
import * as nodeFsPromises from "node:fs/promises";
|
||||
import * as nodePath from "node:path";
|
||||
import * as nodeReadlinePromises from "node:readline/promises";
|
||||
import type { Stats } from "node:fs";
|
||||
export {
|
||||
nodeFs as fs,
|
||||
nodeFsPromises as fsPromises,
|
||||
nodePath as path,
|
||||
nodeReadlinePromises as readline,
|
||||
type Stats,
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "self-hosted-livesync-cli",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"version": "0.25.76-cli",
|
||||
"main": "dist/index.cjs",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@@ -38,6 +38,26 @@
|
||||
"test:e2e:docker:p2p-sync": "RUN_BUILD=0 LIVESYNC_TEST_DOCKER=1 bash test/test-p2p-sync-linux.sh",
|
||||
"test:e2e:docker:all": "export RUN_BUILD=0 && npm run test:e2e:docker:setup-put-cat && npm run test:e2e:docker:push-pull && npm run test:e2e:docker:sync-two-local && npm run test:e2e:docker:mirror && npm run test:e2e:docker:remote-commands"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {}
|
||||
"dependencies": {
|
||||
"chokidar": "^4.0.0",
|
||||
"minimatch": "^10.2.5",
|
||||
"octagonal-wheels": "^0.1.46",
|
||||
"pouchdb-adapter-http": "^9.0.0",
|
||||
"pouchdb-adapter-leveldb": "^9.0.0",
|
||||
"pouchdb-core": "^9.0.0",
|
||||
"pouchdb-errors": "^9.0.0",
|
||||
"pouchdb-find": "^9.0.0",
|
||||
"pouchdb-mapreduce": "^9.0.0",
|
||||
"pouchdb-merge": "^9.0.0",
|
||||
"pouchdb-replication": "^9.0.0",
|
||||
"pouchdb-utils": "^9.0.0",
|
||||
"transform-pouch": "^2.0.0",
|
||||
"werift": "^0.23.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^7.1.2",
|
||||
"typescript": "5.9.3",
|
||||
"vite": "^8.0.16",
|
||||
"vitest": "^4.1.8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"name": "livesync-cli-runtime",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"description": "Runtime dependencies for Self-hosted LiveSync CLI Docker image",
|
||||
"dependencies": {
|
||||
"chokidar": "^4.0.0",
|
||||
"commander": "^14.0.3",
|
||||
"werift": "^0.22.9",
|
||||
"pouchdb-adapter-http": "^9.0.0",
|
||||
"pouchdb-adapter-idb": "^9.0.0",
|
||||
"pouchdb-adapter-indexeddb": "^9.0.0",
|
||||
"pouchdb-adapter-leveldb": "^9.0.0",
|
||||
"pouchdb-adapter-memory": "^9.0.0",
|
||||
"pouchdb-core": "^9.0.0",
|
||||
"pouchdb-errors": "^9.0.0",
|
||||
"pouchdb-find": "^9.0.0",
|
||||
"pouchdb-mapreduce": "^9.0.0",
|
||||
"pouchdb-merge": "^9.0.0",
|
||||
"pouchdb-replication": "^9.0.0",
|
||||
"pouchdb-utils": "^9.0.0",
|
||||
"pouchdb-wrappers": "*",
|
||||
"transform-pouch": "^2.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
import type { InjectableServiceHub } from "@lib/services/implements/injectable/InjectableServiceHub";
|
||||
import { ServiceRebuilder } from "@lib/serviceModules/Rebuilder";
|
||||
import { ServiceFileHandler } from "../../../serviceModules/FileHandler";
|
||||
import { ServiceFileHandler } from "@/serviceModules/FileHandler";
|
||||
import { StorageAccessManager } from "@lib/managers/StorageProcessingManager";
|
||||
import type { LiveSyncBaseCore } from "../../../LiveSyncBaseCore";
|
||||
import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore";
|
||||
import type { ServiceContext } from "@lib/services/base/ServiceBase";
|
||||
import { FileAccessCLI } from "./FileAccessCLI";
|
||||
import { ServiceFileAccessCLI } from "./ServiceFileAccessImpl";
|
||||
import { ServiceDatabaseFileAccessCLI } from "./DatabaseFileAccess";
|
||||
import { StorageEventManagerCLI } from "../managers/StorageEventManagerCLI";
|
||||
import { StorageEventManagerCLI } from "@/apps/cli/managers/StorageEventManagerCLI";
|
||||
import type { ServiceModules } from "@lib/interfaces/ServiceModule";
|
||||
import type { IgnoreRules } from "./IgnoreRules";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { FileAccessBase, type FileAccessBaseDependencies } from "@lib/serviceModules/FileAccessBase";
|
||||
import { NodeFileSystemAdapter } from "../adapters/NodeFileSystemAdapter";
|
||||
import { NodeFileSystemAdapter } from "@/apps/cli/adapters/NodeFileSystemAdapter";
|
||||
|
||||
/**
|
||||
* CLI-specific implementation of FileAccessBase
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import * as fs from "fs/promises";
|
||||
import * as path from "path";
|
||||
|
||||
import { minimatch } from "minimatch";
|
||||
import { fsPromises as fs, path } from "../node-compat";
|
||||
|
||||
/**
|
||||
* Loads and evaluates ignore rules from `.livesync/ignore` inside the vault.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ServiceFileAccessBase, type StorageAccessBaseDependencies } from "@lib/serviceModules/ServiceFileAccessBase";
|
||||
import { NodeFileSystemAdapter } from "../adapters/NodeFileSystemAdapter";
|
||||
import { NodeFileSystemAdapter } from "@/apps/cli/adapters/NodeFileSystemAdapter";
|
||||
|
||||
/**
|
||||
* CLI-specific implementation of ServiceFileAccess
|
||||
|
||||
@@ -7,8 +7,7 @@ import type { InjectableDatabaseEventService } from "@lib/services/implements/in
|
||||
import type { IVaultService } from "@lib/services/base/IService";
|
||||
import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase";
|
||||
import { createInstanceLogFunction } from "@lib/services/lib/logUtils";
|
||||
import * as nodeFs from "node:fs";
|
||||
import * as nodePath from "node:path";
|
||||
import { fs as nodeFs, path as nodePath } from "../node-compat";
|
||||
|
||||
const NODE_KV_TYPED_KEY = "__nodeKvType";
|
||||
const NODE_KV_VALUES_KEY = "values";
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as nodeFs from "node:fs";
|
||||
import * as nodePath from "node:path";
|
||||
import { fs as nodeFs, path as nodePath } from "../node-compat";
|
||||
|
||||
type LocalStorageShape = {
|
||||
getItem(key: string): string | null;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { AppLifecycleService, AppLifecycleServiceDependencies } from "@lib/services/base/AppLifecycleService";
|
||||
import { ServiceContext } from "@lib/services/base/ServiceBase";
|
||||
import * as nodePath from "node:path";
|
||||
import { ConfigServiceBrowserCompat } from "@lib/services/implements/browser/ConfigServiceBrowserCompat";
|
||||
import { SvelteDialogManagerBase, type ComponentHasResult } from "@lib/services/implements/base/SvelteDialog";
|
||||
import { UIService } from "@lib/services/implements/base/UIService";
|
||||
@@ -24,7 +23,8 @@ import type { ServiceInstances } from "@lib/services/ServiceHub";
|
||||
import { NodeKeyValueDBService } from "./NodeKeyValueDBService";
|
||||
import { NodeSettingService } from "./NodeSettingService";
|
||||
import { DatabaseService } from "@lib/services/base/DatabaseService";
|
||||
import type { ObsidianLiveSyncSettings } from "@/lib/src/common/types";
|
||||
import type { ObsidianLiveSyncSettings } from "@lib/common/types";
|
||||
import { path as nodePath } from "../node-compat";
|
||||
|
||||
export class NodeServiceContext extends ServiceContext {
|
||||
databasePath: string;
|
||||
|
||||
@@ -77,7 +77,9 @@ export class BackgroundCliProcess {
|
||||
if (this.combined.includes(needle)) return;
|
||||
const status = await Promise.race([
|
||||
this.child.status.then((s) => ({ type: "status" as const, status: s })),
|
||||
new Promise<{ type: "tick" }>((resolve) => setTimeout(() => resolve({ type: "tick" }), 100)),
|
||||
new Promise<{ type: "tick" }>((resolve) =>
|
||||
setTimeout(() => resolve({ type: "tick" }), 100)
|
||||
),
|
||||
]);
|
||||
if (status.type === "status") {
|
||||
throw new Error(
|
||||
|
||||
@@ -109,7 +109,11 @@ Deno.test("daemon: ignore rules behaviour", async (t) => {
|
||||
await runCliOrFail(vaultDir, "--settings", settingsFile, "mirror");
|
||||
|
||||
const dbList = await runCliOrFail(vaultDir, "--settings", settingsFile, "ls");
|
||||
assertNotContains(dbList, "debug.log", "debug.log (ignored via .gitignore import) was unexpectedly synced to database");
|
||||
assertNotContains(
|
||||
dbList,
|
||||
"debug.log",
|
||||
"debug.log (ignored via .gitignore import) was unexpectedly synced to database"
|
||||
);
|
||||
assertContains(dbList, "regular.md", "regular.md was not synced normally alongside .gitignore import rules");
|
||||
console.log("[PASS] Case 3 verified successfully");
|
||||
});
|
||||
|
||||
@@ -46,9 +46,7 @@ Deno.test("decoupled database and vault", async () => {
|
||||
console.log("[INFO] applying CouchDB environment variables to settings");
|
||||
await applyCouchdbSettings(settingsFile, uri, user, password, dbname);
|
||||
} else {
|
||||
console.warn(
|
||||
"[WARN] CouchDB environment variables are not fully set. Push and pull operations may fail."
|
||||
);
|
||||
console.warn("[WARN] CouchDB environment variables are not fully set. Push and pull operations may fail.");
|
||||
await markSettingsConfigured(settingsFile);
|
||||
}
|
||||
|
||||
@@ -59,29 +57,11 @@ Deno.test("decoupled database and vault", async () => {
|
||||
|
||||
// 1. Test push command with decoupled vault directory
|
||||
console.log(`[INFO] push with decoupled vault -> ${REMOTE_PATH}`);
|
||||
await runCliOrFail(
|
||||
dbDir,
|
||||
"--vault",
|
||||
vaultDir,
|
||||
"--settings",
|
||||
settingsFile,
|
||||
"push",
|
||||
srcFile,
|
||||
REMOTE_PATH
|
||||
);
|
||||
await runCliOrFail(dbDir, "--vault", vaultDir, "--settings", settingsFile, "push", srcFile, REMOTE_PATH);
|
||||
|
||||
// 2. Test pull command with decoupled vault directory
|
||||
console.log(`[INFO] pull with decoupled vault <- ${REMOTE_PATH}`);
|
||||
await runCliOrFail(
|
||||
dbDir,
|
||||
"--vault",
|
||||
vaultDir,
|
||||
"--settings",
|
||||
settingsFile,
|
||||
"pull",
|
||||
REMOTE_PATH,
|
||||
pulledFile
|
||||
);
|
||||
await runCliOrFail(dbDir, "--vault", vaultDir, "--settings", settingsFile, "pull", REMOTE_PATH, pulledFile);
|
||||
|
||||
const pulled = await Deno.readTextFile(pulledFile);
|
||||
assertEquals(pulled, content, "push/pull roundtrip with decoupled vault content mismatch");
|
||||
@@ -93,14 +73,7 @@ Deno.test("decoupled database and vault", async () => {
|
||||
|
||||
// 4. Test mirror command with decoupled vault directory
|
||||
console.log("[INFO] mirror with decoupled vault");
|
||||
await runCliOrFail(
|
||||
dbDir,
|
||||
"--vault",
|
||||
vaultDir,
|
||||
"--settings",
|
||||
settingsFile,
|
||||
"mirror"
|
||||
);
|
||||
await runCliOrFail(dbDir, "--vault", vaultDir, "--settings", settingsFile, "mirror");
|
||||
|
||||
const restoredFile = join(vaultDir, REMOTE_PATH);
|
||||
const restored = await Deno.readTextFile(restoredFile);
|
||||
|
||||
@@ -14,7 +14,7 @@ import { getOptimalLoopbackIp } from "./helpers/net.ts";
|
||||
Deno.test("p2p-peers: discovers host through local relay", async () => {
|
||||
const loopbackIp = await getOptimalLoopbackIp();
|
||||
const loopbackHost = loopbackIp === "::1" ? "[::1]" : loopbackIp;
|
||||
|
||||
|
||||
const relay = Deno.env.get("RELAY") ?? `ws://${loopbackHost}:4000/`;
|
||||
const roomId = Deno.env.get("ROOM_ID") ?? `room-${Date.now()}`;
|
||||
const passphrase = Deno.env.get("PASSPHRASE") ?? "test";
|
||||
|
||||
@@ -15,7 +15,7 @@ import { getOptimalLoopbackIp } from "./helpers/net.ts";
|
||||
Deno.test("p2p-sync: discovers peer and completes sync", async () => {
|
||||
const loopbackIp = await getOptimalLoopbackIp();
|
||||
const loopbackHost = loopbackIp === "::1" ? "[::1]" : loopbackIp;
|
||||
|
||||
|
||||
const relay = Deno.env.get("RELAY") ?? `ws://${loopbackHost}:4000/`;
|
||||
const roomId = Deno.env.get("ROOM_ID") ?? `room-${Date.now()}`;
|
||||
const passphrase = Deno.env.get("PASSPHRASE") ?? "test";
|
||||
|
||||
@@ -15,7 +15,7 @@ import { getOptimalLoopbackIp } from "./helpers/net.ts";
|
||||
Deno.test("p2p: three nodes detect and resolve conflicts", async () => {
|
||||
const loopbackIp = await getOptimalLoopbackIp();
|
||||
const loopbackHost = loopbackIp === "::1" ? "[::1]" : loopbackIp;
|
||||
|
||||
|
||||
const relay = Deno.env.get("RELAY") ?? `ws://${loopbackHost}:4000/`;
|
||||
const roomId = Deno.env.get("ROOM_ID") ?? `room-${Date.now()}`;
|
||||
const passphrase = Deno.env.get("PASSPHRASE") ?? "test";
|
||||
|
||||
@@ -61,11 +61,7 @@ Deno.test("remote management commands", async () => {
|
||||
// 1. remote-status outputs valid JSON with CouchDB details
|
||||
console.log("[CASE] remote-status outputs valid JSON with CouchDB details");
|
||||
const statusOutput = await runCliCombinedOrFail(vaultDir, "--settings", settingsFile, "remote-status");
|
||||
assertContains(
|
||||
statusOutput,
|
||||
`"db_name": "${dbname}"`,
|
||||
"remote-status should return JSON containing db_name"
|
||||
);
|
||||
assertContains(statusOutput, `"db_name": "${dbname}"`, "remote-status should return JSON containing db_name");
|
||||
console.log("[PASS] remote-status verified");
|
||||
|
||||
// 2. lock-remote locks and verifies state
|
||||
|
||||
@@ -132,7 +132,7 @@ Deno.test("CLI file operations: push / cat / ls / info / rm / resolve / cat-rev
|
||||
assertEquals(data.path, REMOTE_PATH, "info .path mismatch");
|
||||
assertEquals(data.filename, REMOTE_PATH.split("/").at(-1), "info .filename mismatch");
|
||||
assert(typeof data.size === "number" && data.size >= 0, `info .size invalid: ${data.size}`);
|
||||
assert(typeof data.chunks === "number" && (data.chunks as number) >= 1, `info .chunks invalid: ${data.chunks}`);
|
||||
assert(typeof data.chunks === "number" && (data.chunks) >= 1, `info .chunks invalid: ${data.chunks}`);
|
||||
assertEquals(data.conflicts, "N/A", "info .conflicts should be N/A");
|
||||
console.log("[PASS] info output format matched");
|
||||
});
|
||||
|
||||
@@ -15,18 +15,18 @@
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": false,
|
||||
"strict": true,
|
||||
// "noImplicitAny": false,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
|
||||
// "rootDir": "../../../",
|
||||
/* Path mapping */
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["../../*"],
|
||||
"@lib/*": ["../../lib/src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["*.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
"exclude": ["node_modules", "dist", "test", "testdeno"]
|
||||
}
|
||||
|
||||
@@ -35,8 +35,15 @@ npm install
|
||||
|
||||
### Development
|
||||
|
||||
From the repository root:
|
||||
|
||||
```bash
|
||||
npm run dev -w livesync-webapp
|
||||
```
|
||||
|
||||
Or from the package directory:
|
||||
|
||||
```bash
|
||||
# Build the project (ensure you are in `src/apps/webapp` directory)
|
||||
cd src/apps/webapp
|
||||
npm run dev
|
||||
```
|
||||
@@ -45,8 +52,15 @@ This will start a development server at `http://localhost:3000`.
|
||||
|
||||
### Build
|
||||
|
||||
From the repository root:
|
||||
|
||||
```bash
|
||||
npm run build -w livesync-webapp
|
||||
```
|
||||
|
||||
Or from the package directory:
|
||||
|
||||
```bash
|
||||
# Build the project (ensure you are in `src/apps/webapp` directory)
|
||||
cd src/apps/webapp
|
||||
npm run build
|
||||
```
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { LiveSyncWebApp } from "./main";
|
||||
import { VaultHistoryStore, type VaultHistoryItem } from "./vaultSelector";
|
||||
import { compatGlobal, _activeDocument } from "@lib/common/coreEnvFunctions.ts";
|
||||
|
||||
const historyStore = new VaultHistoryStore();
|
||||
let app: LiveSyncWebApp | null = null;
|
||||
|
||||
function getRequiredElement<T extends HTMLElement>(id: string): T {
|
||||
const element = document.getElementById(id);
|
||||
const element = _activeDocument.getElementById(id);
|
||||
if (!element) {
|
||||
throw new Error(`Missing element: #${id}`);
|
||||
}
|
||||
@@ -22,7 +23,7 @@ function setBusyState(isBusy: boolean): void {
|
||||
const pickNewBtn = getRequiredElement<HTMLButtonElement>("pick-new-vault");
|
||||
pickNewBtn.disabled = isBusy;
|
||||
|
||||
const historyButtons = document.querySelectorAll<HTMLButtonElement>(".vault-item button");
|
||||
const historyButtons = _activeDocument.querySelectorAll<HTMLButtonElement>(".vault-item button");
|
||||
historyButtons.forEach((button) => {
|
||||
button.disabled = isBusy;
|
||||
});
|
||||
@@ -45,24 +46,24 @@ async function renderHistoryList(): Promise<VaultHistoryItem[]> {
|
||||
emptyEl.classList.toggle("is-hidden", items.length > 0);
|
||||
|
||||
for (const item of items) {
|
||||
const row = document.createElement("div");
|
||||
const row = _activeDocument.createElement("div");
|
||||
row.className = "vault-item";
|
||||
|
||||
const info = document.createElement("div");
|
||||
const info = _activeDocument.createElement("div");
|
||||
info.className = "vault-item-info";
|
||||
|
||||
const name = document.createElement("div");
|
||||
const name = _activeDocument.createElement("div");
|
||||
name.className = "vault-item-name";
|
||||
name.textContent = item.name;
|
||||
|
||||
const meta = document.createElement("div");
|
||||
const meta = _activeDocument.createElement("div");
|
||||
meta.className = "vault-item-meta";
|
||||
const label = item.id === lastUsedId ? "Last used" : "Used";
|
||||
meta.textContent = `${label}: ${formatLastUsed(item.lastUsedAt)}`;
|
||||
|
||||
info.append(name, meta);
|
||||
|
||||
const useButton = document.createElement("button");
|
||||
const useButton = _activeDocument.createElement("button");
|
||||
useButton.type = "button";
|
||||
useButton.textContent = "Use this vault";
|
||||
useButton.addEventListener("click", () => {
|
||||
@@ -120,7 +121,7 @@ async function initializeVaultSelector(): Promise<void> {
|
||||
await renderHistoryList();
|
||||
}
|
||||
|
||||
window.addEventListener("load", async () => {
|
||||
compatGlobal.addEventListener("load", async () => {
|
||||
try {
|
||||
await initializeVaultSelector();
|
||||
} catch (error) {
|
||||
@@ -129,11 +130,11 @@ window.addEventListener("load", async () => {
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener("beforeunload", () => {
|
||||
compatGlobal.addEventListener("beforeunload", () => {
|
||||
void app?.shutdown();
|
||||
});
|
||||
|
||||
(window as any).livesyncApp = {
|
||||
(compatGlobal as any).livesyncApp = {
|
||||
getApp: () => app,
|
||||
historyStore,
|
||||
};
|
||||
|
||||
@@ -17,8 +17,9 @@ import { useSetupURIFeature } from "@lib/serviceFeatures/setupObsidian/setupUri"
|
||||
import { useRemoteConfiguration } from "@lib/serviceFeatures/remoteConfig";
|
||||
import { SetupManager } from "@/modules/features/SetupManager";
|
||||
import { useSetupManagerHandlersFeature } from "@/serviceFeatures/setupObsidian/setupManagerHandlers";
|
||||
import { useP2PReplicatorCommands } from "@/lib/src/replication/trystero/useP2PReplicatorCommands";
|
||||
import { useP2PReplicatorFeature } from "@/lib/src/replication/trystero/useP2PReplicatorFeature";
|
||||
import { useP2PReplicatorCommands } from "@lib/replication/trystero/useP2PReplicatorCommands";
|
||||
import { useP2PReplicatorFeature } from "@lib/replication/trystero/useP2PReplicatorFeature";
|
||||
import { compatGlobal, _activeDocument } from "@lib/common/coreEnvFunctions.ts";
|
||||
|
||||
const SETTINGS_DIR = ".livesync";
|
||||
const SETTINGS_FILE = "settings.json";
|
||||
@@ -91,7 +92,7 @@ class LiveSyncWebApp {
|
||||
console.log("[Settings] Loaded from .livesync/settings.json");
|
||||
return { ...DEFAULT_SETTINGS, ...data } as ObsidianLiveSyncSettings;
|
||||
}
|
||||
} catch (error) {
|
||||
} catch {
|
||||
console.log("[Settings] Failed to load, using defaults");
|
||||
}
|
||||
return DEFAULT_SETTINGS as ObsidianLiveSyncSettings;
|
||||
@@ -102,8 +103,8 @@ class LiveSyncWebApp {
|
||||
console.log("[AppLifecycle] Restart requested");
|
||||
await this.shutdown();
|
||||
await this.initialize();
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
compatGlobal.setTimeout(() => {
|
||||
compatGlobal.location.reload();
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
@@ -169,7 +170,7 @@ class LiveSyncWebApp {
|
||||
const file = await fileHandle.getFile();
|
||||
const text = await file.text();
|
||||
return JSON.parse(text);
|
||||
} catch (error) {
|
||||
} catch {
|
||||
// File doesn't exist yet
|
||||
return null;
|
||||
}
|
||||
@@ -235,7 +236,7 @@ class LiveSyncWebApp {
|
||||
}
|
||||
|
||||
private showError(message: string) {
|
||||
const statusEl = document.getElementById("status");
|
||||
const statusEl = _activeDocument.getElementById("status");
|
||||
if (statusEl) {
|
||||
statusEl.className = "error";
|
||||
statusEl.textContent = `Error: ${message}`;
|
||||
@@ -243,7 +244,7 @@ class LiveSyncWebApp {
|
||||
}
|
||||
|
||||
private showWarning(message: string) {
|
||||
const statusEl = document.getElementById("status");
|
||||
const statusEl = _activeDocument.getElementById("status");
|
||||
if (statusEl) {
|
||||
statusEl.className = "warning";
|
||||
statusEl.textContent = `Warning: ${message}`;
|
||||
@@ -251,7 +252,7 @@ class LiveSyncWebApp {
|
||||
}
|
||||
|
||||
private showSuccess(message: string) {
|
||||
const statusEl = document.getElementById("status");
|
||||
const statusEl = _activeDocument.getElementById("status");
|
||||
if (statusEl) {
|
||||
statusEl.className = "success";
|
||||
statusEl.textContent = message;
|
||||
|
||||
@@ -10,7 +10,8 @@ import type {
|
||||
IStorageEventWatchHandlers,
|
||||
} from "@lib/managers/adapters";
|
||||
import type { FileEventItemSentinel } from "@lib/managers/StorageEventManager";
|
||||
import type { FSAPIFile, FSAPIFolder } from "../adapters/FSAPITypes";
|
||||
import type { FSAPIFile, FSAPIFolder } from "@/apps/webapp/adapters/FSAPITypes";
|
||||
import { compatGlobal } from "@lib/common/coreEnvFunctions.ts";
|
||||
|
||||
/**
|
||||
* FileSystem API-specific type guard adapter
|
||||
@@ -149,14 +150,14 @@ class FSAPIWatchAdapter implements IStorageEventWatchAdapter {
|
||||
|
||||
async beginWatch(handlers: IStorageEventWatchHandlers): Promise<void> {
|
||||
// Use FileSystemObserver if available (Chrome 124+)
|
||||
if (typeof (window as any).FileSystemObserver === "undefined") {
|
||||
if (typeof (compatGlobal as any).FileSystemObserver === "undefined") {
|
||||
console.log("[FSAPIWatchAdapter] FileSystemObserver not available, file watching disabled");
|
||||
console.log("[FSAPIWatchAdapter] Consider using Chrome 124+ for real-time file watching");
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
try {
|
||||
const FileSystemObserver = (window as any).FileSystemObserver;
|
||||
const FileSystemObserver = (compatGlobal as any).FileSystemObserver;
|
||||
|
||||
this.observer = new FileSystemObserver(async (records: any[]) => {
|
||||
for (const record of records) {
|
||||
@@ -181,7 +182,7 @@ class FSAPIWatchAdapter implements IStorageEventWatchAdapter {
|
||||
if (changedHandle && changedHandle.kind === "file") {
|
||||
const file = await changedHandle.getFile();
|
||||
const fileInfo = {
|
||||
path: relativePath as any,
|
||||
path: relativePath,
|
||||
stat: {
|
||||
size: file.size,
|
||||
mtime: file.lastModified,
|
||||
@@ -199,7 +200,7 @@ class FSAPIWatchAdapter implements IStorageEventWatchAdapter {
|
||||
}
|
||||
} else if (type === "disappeared") {
|
||||
const fileInfo = {
|
||||
path: relativePath as any,
|
||||
path: relativePath,
|
||||
stat: {
|
||||
size: 0,
|
||||
mtime: Date.now(),
|
||||
@@ -216,7 +217,7 @@ class FSAPIWatchAdapter implements IStorageEventWatchAdapter {
|
||||
if (changedHandle && changedHandle.kind === "file") {
|
||||
const file = await changedHandle.getFile();
|
||||
const fileInfo = {
|
||||
path: relativePath as any,
|
||||
path: relativePath,
|
||||
stat: {
|
||||
size: file.size,
|
||||
mtime: file.lastModified,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "livesync-webapp",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"version": "0.25.76-webapp",
|
||||
"type": "module",
|
||||
"description": "Browser-based Self-hosted LiveSync using FileSystem API",
|
||||
"scripts": {
|
||||
@@ -11,9 +11,16 @@
|
||||
"run:docker": "docker run -p 8002:80 livesync-webapp",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {},
|
||||
"dependencies": {
|
||||
"octagonal-wheels": "^0.1.46"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.58.2",
|
||||
"@sveltejs/vite-plugin-svelte": "^7.1.2",
|
||||
"playwright": "^1.58.2",
|
||||
"svelte": "5.56.3",
|
||||
"typescript": "5.9.3",
|
||||
"vite": "^7.3.1"
|
||||
"vite": "^8.0.16",
|
||||
"vite-plugin-istanbul": "^9.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { ServiceContext } from "@lib/services/base/ServiceBase";
|
||||
import { FileAccessFSAPI } from "./FileAccessFSAPI";
|
||||
import { ServiceFileAccessFSAPI } from "./ServiceFileAccessImpl";
|
||||
import { ServiceDatabaseFileAccessFSAPI } from "./DatabaseFileAccess";
|
||||
import { StorageEventManagerFSAPI } from "../managers/StorageEventManagerFSAPI";
|
||||
import { StorageEventManagerFSAPI } from "@/apps/webapp/managers/StorageEventManagerFSAPI";
|
||||
import type { ServiceModules } from "@lib/interfaces/ServiceModule";
|
||||
import { ServiceFileHandler } from "@/serviceModules/FileHandler";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { FileAccessBase, type FileAccessBaseDependencies } from "@lib/serviceModules/FileAccessBase";
|
||||
import { FSAPIFileSystemAdapter } from "../adapters/FSAPIFileSystemAdapter";
|
||||
import { FSAPIFileSystemAdapter } from "@/apps/webapp/adapters/FSAPIFileSystemAdapter";
|
||||
|
||||
/**
|
||||
* FileSystem API-specific implementation of FileAccessBase
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ServiceFileAccessBase, type StorageAccessBaseDependencies } from "@lib/serviceModules/ServiceFileAccessBase";
|
||||
import { FSAPIFileSystemAdapter } from "../adapters/FSAPIFileSystemAdapter";
|
||||
import { FSAPIFileSystemAdapter } from "@/apps/webapp/adapters/FSAPIFileSystemAdapter";
|
||||
|
||||
/**
|
||||
* FileSystem API-specific implementation of ServiceFileAccess
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
import { LiveSyncWebApp } from "./main";
|
||||
import type { ObsidianLiveSyncSettings } from "@lib/common/types";
|
||||
import type { FilePathWithPrefix } from "@lib/common/types";
|
||||
import { compatGlobal } from "@lib/common/coreEnvFunctions.ts";
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Internal state – one app instance per page / browser context
|
||||
@@ -41,7 +42,7 @@ async function waitForIdle(core: any, timeoutMs = 60_000): Promise<void> {
|
||||
(core.services?.fileProcessing?.processing?.value ?? 0) +
|
||||
(core.services?.replication?.storageApplyingCount?.value ?? 0);
|
||||
if (q === 0) return;
|
||||
await new Promise<void>((r) => setTimeout(r, 300));
|
||||
await new Promise<void>((r) => compatGlobal.setTimeout(r, 300));
|
||||
}
|
||||
throw new Error(`waitForIdle timed out after ${timeoutMs} ms`);
|
||||
}
|
||||
@@ -116,7 +117,7 @@ export interface LiveSyncTestAPI {
|
||||
const livesyncTest: LiveSyncTestAPI = {
|
||||
async init(vaultName: string, settings: Partial<ObsidianLiveSyncSettings>): Promise<void> {
|
||||
// Clean up any stale OPFS data from previous runs.
|
||||
const opfsRoot = await navigator.storage.getDirectory();
|
||||
const opfsRoot = await compatGlobal.navigator.storage.getDirectory();
|
||||
try {
|
||||
await opfsRoot.removeEntry(vaultName, { recursive: true });
|
||||
} catch {
|
||||
@@ -200,4 +201,4 @@ const livesyncTest: LiveSyncTestAPI = {
|
||||
};
|
||||
|
||||
// Expose on window for Playwright page.evaluate() calls.
|
||||
(window as any).livesyncTest = livesyncTest;
|
||||
(compatGlobal as any).livesyncTest = livesyncTest;
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*/
|
||||
|
||||
import { test, expect, type BrowserContext, type Page, type TestInfo } from "@playwright/test";
|
||||
import type { LiveSyncTestAPI } from "../test-entry";
|
||||
import type { LiveSyncTestAPI } from "@/apps/webapp/test-entry";
|
||||
import { mkdirSync, writeFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
|
||||
/* Path mapping */
|
||||
"baseUrl": ".",
|
||||
// "baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["../../*"],
|
||||
"@lib/*": ["../../lib/src/*"]
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { compatGlobal } from "@lib/common/coreEnvFunctions.ts";
|
||||
|
||||
const HANDLE_DB_NAME = "livesync-webapp-handles";
|
||||
const HANDLE_STORE_NAME = "handles";
|
||||
const LAST_USED_KEY = "meta:lastUsedVaultId";
|
||||
@@ -89,7 +91,7 @@ export class VaultHistoryStore {
|
||||
|
||||
async getVaultHistory(): Promise<VaultHistoryItem[]> {
|
||||
return this.withStore("readonly", async (store) => {
|
||||
const keys = (await this.requestAsPromise(store.getAllKeys())) as IDBValidKey[];
|
||||
const keys = (await this.requestAsPromise(store.getAllKeys()));
|
||||
const values = (await this.requestAsPromise(store.getAll())) as unknown[];
|
||||
const items: VaultHistoryItem[] = [];
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
@@ -170,7 +172,7 @@ export class VaultHistoryStore {
|
||||
}
|
||||
|
||||
async pickNewVault(): Promise<FileSystemDirectoryHandle> {
|
||||
const picker = (window as any).showDirectoryPicker;
|
||||
const picker = (compatGlobal as any).showDirectoryPicker;
|
||||
if (typeof picker !== "function") {
|
||||
throw new Error("FileSystem API showDirectoryPicker is not supported in this browser");
|
||||
}
|
||||
|
||||
@@ -13,13 +13,20 @@ This pseudo client actually receives the data from other devices, and sends if s
|
||||
|
||||
## How to use it?
|
||||
|
||||
We can build the application by running the following command:
|
||||
We can build the application from the repository root by running the following command:
|
||||
|
||||
```bash
|
||||
$ deno task build
|
||||
npm run build -w webpeer
|
||||
```
|
||||
|
||||
Then, open the `dist/index.html` in the browser. It can be configured as the same as the Self-hosted LiveSync (Same components are used[^1]).
|
||||
Or from the package directory:
|
||||
|
||||
```bash
|
||||
cd src/apps/webpeer
|
||||
npm run build
|
||||
```
|
||||
|
||||
Then, open `dist/index.html` in the browser. It can be configured in the same way as Self-hosted LiveSync (the same components are used[^1]).
|
||||
|
||||
## Some notes
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "webpeer",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"version": "0.25.76-webpeer",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -11,15 +11,17 @@
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json"
|
||||
},
|
||||
"dependencies": {},
|
||||
"dependencies": {
|
||||
"octagonal-wheels": "^0.1.46"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint-plugin-svelte": "^3.15.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
||||
"eslint-plugin-svelte": "^3.19.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^7.1.2",
|
||||
"@tsconfig/svelte": "^5.0.8",
|
||||
"svelte": "5.41.1",
|
||||
"svelte-check": "^443.3",
|
||||
"svelte": "5.56.3",
|
||||
"svelte-check": "^4.6.0",
|
||||
"typescript": "5.9.3",
|
||||
"vite": "^7.3.1"
|
||||
"vite": "^8.0.16"
|
||||
},
|
||||
"imports": {
|
||||
"../../src/worker/bgWorker.ts": "../../src/worker/bgWorker.mock.ts",
|
||||
|
||||
@@ -17,7 +17,9 @@ import {
|
||||
type PeerStatus,
|
||||
type PluginShim,
|
||||
} from "@lib/replication/trystero/P2PReplicatorPaneCommon";
|
||||
import { P2PLogCollector, type P2PReplicatorBase, useP2PReplicator } from "@lib/replication/trystero/P2PReplicatorCore";
|
||||
import { useP2PReplicator } from "@lib/replication/trystero/P2PReplicatorCore";
|
||||
import { P2PLogCollector } from "@lib/replication/trystero/P2PLogCollector";
|
||||
import type { P2PReplicatorBase } from "@lib/replication/trystero/P2PReplicatorBase.ts";
|
||||
import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase";
|
||||
import { reactiveSource } from "octagonal-wheels/dataobject/reactive_v2";
|
||||
import { EVENT_SETTING_SAVED } from "@lib/events/coreEvents";
|
||||
@@ -28,9 +30,10 @@ import { ServiceContext } from "@lib/services/base/ServiceBase";
|
||||
import type { InjectableServiceHub } from "@lib/services/InjectableServices";
|
||||
import { Menu } from "@lib/services/implements/browser/Menu";
|
||||
import { SimpleStoreIDBv2 } from "octagonal-wheels/databases/SimpleStoreIDBv2";
|
||||
import type { BrowserAPIService } from "@/lib/src/services/implements/browser/BrowserAPIService";
|
||||
import type { InjectableSettingService } from "@/lib/src/services/implements/injectable/InjectableSettingService";
|
||||
import type { BrowserAPIService } from "@lib/services/implements/browser/BrowserAPIService";
|
||||
import type { InjectableSettingService } from "@lib/services/implements/injectable/InjectableSettingService";
|
||||
import { LiveSyncTrysteroReplicator } from "@lib/replication/trystero/LiveSyncTrysteroReplicator";
|
||||
import { compatGlobal } from "@lib/common/coreEnvFunctions.ts";
|
||||
|
||||
function addToList(item: string, list: string) {
|
||||
return unique(
|
||||
@@ -137,7 +140,7 @@ export class P2PReplicatorShim implements P2PReplicatorBase {
|
||||
|
||||
this._initP2PReplicator();
|
||||
|
||||
setTimeout(() => {
|
||||
compatGlobal.setTimeout(() => {
|
||||
if (this.settings.P2P_AutoStart && this.settings.P2P_Enabled) {
|
||||
void this.open();
|
||||
}
|
||||
@@ -164,12 +167,12 @@ export class P2PReplicatorShim implements P2PReplicatorBase {
|
||||
getConfig(key: string) {
|
||||
const vaultName = this.services.vault.getVaultName();
|
||||
const dbKey = `${vaultName}-${key}`;
|
||||
return localStorage.getItem(dbKey);
|
||||
return compatGlobal.localStorage.getItem(dbKey);
|
||||
}
|
||||
setConfig(key: string, value: string) {
|
||||
const vaultName = this.services.vault.getVaultName();
|
||||
const dbKey = `${vaultName}-${key}`;
|
||||
localStorage.setItem(dbKey, value);
|
||||
compatGlobal.localStorage.setItem(dbKey, value);
|
||||
}
|
||||
|
||||
getDeviceName(): string {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Menu } from "@/lib/src/services/implements/browser/Menu";
|
||||
import { Menu } from "@lib/services/implements/browser/Menu";
|
||||
import { getDialogContext } from "@lib/services/implements/base/SvelteDialog";
|
||||
let result = $state<string | boolean>("");
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { mount } from "svelte";
|
||||
import "./app.css";
|
||||
import App from "./App.svelte";
|
||||
import { _activeDocument } from "@lib/common/coreEnvFunctions.ts";
|
||||
|
||||
const app = mount(App, {
|
||||
target: document.getElementById("app")!,
|
||||
target: _activeDocument.getElementById("app")!,
|
||||
});
|
||||
|
||||
export default app;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { mount } from "svelte";
|
||||
import "./app.css";
|
||||
import App from "./UITest.svelte";
|
||||
import { _activeDocument } from "@lib/common/coreEnvFunctions.ts";
|
||||
|
||||
const app = mount(App, {
|
||||
target: document.getElementById("app")!,
|
||||
target: _activeDocument.getElementById("app")!,
|
||||
});
|
||||
|
||||
export default app;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"sourceRoot": "../",
|
||||
// "sourceRoot": "../",
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
@@ -15,11 +15,13 @@
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"isolatedModules": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"moduleDetection": "force",
|
||||
"paths": {
|
||||
"@/*": ["../../*"],
|
||||
"@lib/*": ["../../lib/src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"]
|
||||
"include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { deleteDB, type IDBPDatabase, openDB } from "idb";
|
||||
import type { KeyValueDatabase } from "../lib/src/interfaces/KeyValueDatabase.ts";
|
||||
import type { KeyValueDatabase } from "@lib/interfaces/KeyValueDatabase.ts";
|
||||
import { serialized } from "octagonal-wheels/concurrency/lock";
|
||||
import { Logger } from "octagonal-wheels/common/logger";
|
||||
const databaseCache: { [key: string]: IDBPDatabase<any> } = {};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { LOG_LEVEL_VERBOSE, Logger } from "@/lib/src/common/logger";
|
||||
import type { KeyValueDatabase } from "@/lib/src/interfaces/KeyValueDatabase";
|
||||
import { LOG_LEVEL_VERBOSE, Logger } from "@lib/common/logger";
|
||||
import type { KeyValueDatabase } from "@lib/interfaces/KeyValueDatabase";
|
||||
import { deleteDB, openDB, type IDBPDatabase } from "idb";
|
||||
import { serialized } from "octagonal-wheels/concurrency/lock";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { eventHub } from "../lib/src/hub/hub";
|
||||
import { eventHub } from "@lib/hub/hub";
|
||||
// import type ObsidianLiveSyncPlugin from "../main";
|
||||
|
||||
export const EVENT_PLUGIN_LOADED = "plugin-loaded";
|
||||
@@ -43,5 +43,5 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
export * from "../lib/src/events/coreEvents.ts";
|
||||
export * from "@lib/events/coreEvents.ts";
|
||||
export { eventHub };
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { TFile } from "../deps";
|
||||
import type { FilePathWithPrefix, LoadedEntry } from "../lib/src/common/types";
|
||||
import type { TFile } from "@/deps";
|
||||
import type { FilePathWithPrefix, LoadedEntry } from "@lib/common/types";
|
||||
|
||||
export const EVENT_REQUEST_SHOW_HISTORY = "show-history";
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ export async function generateReport(settings: ObsidianLiveSyncSettings, core: L
|
||||
const r = await requestToCouchDBWithCredentials(
|
||||
settings.couchDB_URI,
|
||||
credential,
|
||||
window.origin,
|
||||
compatGlobal.origin,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { type PluginManifest, TFile } from "../deps.ts";
|
||||
import { type DatabaseEntry, type EntryBody, type FilePath } from "../lib/src/common/types.ts";
|
||||
export type { CacheData, FileEventItem } from "../lib/src/common/types.ts";
|
||||
import { type PluginManifest, TFile } from "@/deps.ts";
|
||||
import { type DatabaseEntry, type EntryBody, type FilePath } from "@lib/common/types.ts";
|
||||
export type { CacheData, FileEventItem } from "@lib/common/types.ts";
|
||||
|
||||
export interface PluginDataEntry extends DatabaseEntry {
|
||||
deviceVaultName: string;
|
||||
@@ -51,7 +51,7 @@ export type queueItem = {
|
||||
|
||||
export const FileWatchEventQueueMax = 10;
|
||||
|
||||
export { configURIBase, configURIBaseQR } from "../lib/src/common/types.ts";
|
||||
export { configURIBase, configURIBaseQR } from "@lib/common/types.ts";
|
||||
|
||||
export {
|
||||
CHeader,
|
||||
@@ -61,4 +61,4 @@ export {
|
||||
ICHeaderEnd,
|
||||
ICHeaderLength,
|
||||
ICXHeader,
|
||||
} from "../lib/src/common/models/fileaccess.const.ts";
|
||||
} from "@lib/common/models/fileaccess.const.ts";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { normalizePath, Platform, TAbstractFile, type RequestUrlParam, requestUrl } from "../deps.ts";
|
||||
import { normalizePath, Platform, TAbstractFile, type RequestUrlParam, requestUrl } from "@/deps.ts";
|
||||
import {
|
||||
path2id_base,
|
||||
id2path_base,
|
||||
@@ -7,9 +7,9 @@ import {
|
||||
isValidFilenameInWidows,
|
||||
isValidFilenameInAndroid,
|
||||
stripAllPrefixes,
|
||||
} from "../lib/src/string_and_binary/path.ts";
|
||||
} from "@lib/string_and_binary/path.ts";
|
||||
|
||||
import { Logger } from "../lib/src/common/logger.ts";
|
||||
import { Logger } from "@lib/common/logger.ts";
|
||||
import {
|
||||
LOG_LEVEL_INFO,
|
||||
LOG_LEVEL_NOTICE,
|
||||
@@ -22,14 +22,14 @@ import {
|
||||
type FilePathWithPrefix,
|
||||
type UXFileInfo,
|
||||
type UXFileInfoStub,
|
||||
} from "../lib/src/common/types.ts";
|
||||
} from "@lib/common/types.ts";
|
||||
export { ICHeader, ICXHeader } from "./types.ts";
|
||||
import { writeString } from "../lib/src/string_and_binary/convert.ts";
|
||||
import { writeString } from "@lib/string_and_binary/convert.ts";
|
||||
import { sameChangePairs } from "./stores.ts";
|
||||
|
||||
import { scheduleTask } from "octagonal-wheels/concurrency/task";
|
||||
import { AuthorizationHeaderGenerator } from "../lib/src/replication/httplib.ts";
|
||||
import type { KeyValueDatabase } from "../lib/src/interfaces/KeyValueDatabase.ts";
|
||||
import { AuthorizationHeaderGenerator } from "@lib/replication/httplib.ts";
|
||||
import type { KeyValueDatabase } from "@lib/interfaces/KeyValueDatabase.ts";
|
||||
|
||||
export { scheduleTask, cancelTask, cancelAllTasks } from "octagonal-wheels/concurrency/task";
|
||||
|
||||
@@ -132,7 +132,7 @@ export const _requestToCouchDBFetch = async (
|
||||
method?: string
|
||||
) => {
|
||||
const utf8str = String.fromCharCode.apply(null, [...writeString(`${username}:${password}`)]);
|
||||
const encoded = window.btoa(utf8str);
|
||||
const encoded = compatGlobal.btoa(utf8str);
|
||||
const authHeader = "Basic " + encoded;
|
||||
const transformedHeaders: Record<string, string> = {
|
||||
authorization: authHeader,
|
||||
@@ -214,7 +214,7 @@ import { BASE_IS_NEW, EVEN, TARGET_IS_NEW } from "@lib/common/models/shared.cons
|
||||
export { BASE_IS_NEW, EVEN, TARGET_IS_NEW };
|
||||
// Why 2000? : ZIP FILE Does not have enough resolution.
|
||||
import { compareMTime } from "@lib/common/utils.ts";
|
||||
import { _fetch } from "@/lib/src/common/coreEnvFunctions.ts";
|
||||
import { _fetch, compatGlobal } from "@lib/common/coreEnvFunctions.ts";
|
||||
export { compareMTime };
|
||||
function getKey(file: AnyEntry | string | UXFileInfoStub) {
|
||||
const key = typeof file == "string" ? file : stripAllPrefixes(file.path);
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
diff_match_patch,
|
||||
Platform,
|
||||
addIcon,
|
||||
} from "../../deps.ts";
|
||||
} from "@/deps.ts";
|
||||
|
||||
import type {
|
||||
EntryDoc,
|
||||
@@ -19,7 +19,7 @@ import type {
|
||||
AnyEntry,
|
||||
SavingEntry,
|
||||
diff_result,
|
||||
} from "../../lib/src/common/types.ts";
|
||||
} from "@lib/common/types.ts";
|
||||
import {
|
||||
CANCELLED,
|
||||
LEAVE_TO_SUBSEQUENT,
|
||||
@@ -29,8 +29,8 @@ import {
|
||||
LOG_LEVEL_VERBOSE,
|
||||
MODE_SELECTIVE,
|
||||
MODE_SHINY,
|
||||
} from "../../lib/src/common/types.ts";
|
||||
import { ICXHeader, PERIODIC_PLUGIN_SWEEP } from "../../common/types.ts";
|
||||
} from "@lib/common/types.ts";
|
||||
import { ICXHeader, PERIODIC_PLUGIN_SWEEP } from "@/common/types.ts";
|
||||
import {
|
||||
createBlob,
|
||||
createSavingEntryFromLoadedEntry,
|
||||
@@ -42,12 +42,12 @@ import {
|
||||
isDocContentSame,
|
||||
isLoadedEntry,
|
||||
isObjectDifferent,
|
||||
} from "../../lib/src/common/utils.ts";
|
||||
import { digestHash } from "../../lib/src/string_and_binary/hash.ts";
|
||||
import { arrayBufferToBase64, decodeBinary, readString } from "../../lib/src/string_and_binary/convert.ts";
|
||||
} from "@lib/common/utils.ts";
|
||||
import { digestHash } from "@lib/string_and_binary/hash.ts";
|
||||
import { arrayBufferToBase64, decodeBinary, readString } from "@lib/string_and_binary/convert.ts";
|
||||
import { serialized, shareRunningResult } from "octagonal-wheels/concurrency/lock";
|
||||
import { LiveSyncCommands } from "../LiveSyncCommands.ts";
|
||||
import { stripAllPrefixes } from "../../lib/src/string_and_binary/path.ts";
|
||||
import { LiveSyncCommands } from "@/features/LiveSyncCommands.ts";
|
||||
import { stripAllPrefixes } from "@lib/string_and_binary/path.ts";
|
||||
import {
|
||||
EVEN,
|
||||
disposeMemoObject,
|
||||
@@ -57,20 +57,20 @@ import {
|
||||
memoObject,
|
||||
retrieveMemoObject,
|
||||
scheduleTask,
|
||||
} from "../../common/utils.ts";
|
||||
} from "@/common/utils.ts";
|
||||
import { PeriodicProcessor } from "@/common/PeriodicProcessor.ts";
|
||||
import { JsonResolveModal } from "../HiddenFileCommon/JsonResolveModal.ts";
|
||||
import { JsonResolveModal } from "@/features/HiddenFileCommon/JsonResolveModal.ts";
|
||||
import { QueueProcessor } from "octagonal-wheels/concurrency/processor";
|
||||
import { pluginScanningCount } from "../../lib/src/mock_and_interop/stores.ts";
|
||||
import type ObsidianLiveSyncPlugin from "../../main.ts";
|
||||
import { pluginScanningCount } from "@lib/mock_and_interop/stores.ts";
|
||||
import type ObsidianLiveSyncPlugin from "@/main.ts";
|
||||
import { base64ToArrayBuffer, base64ToString } from "octagonal-wheels/binary/base64";
|
||||
import { ConflictResolveModal } from "../../modules/features/InteractiveConflictResolving/ConflictResolveModal.ts";
|
||||
import { ConflictResolveModal } from "@/modules/features/InteractiveConflictResolving/ConflictResolveModal.ts";
|
||||
import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
|
||||
import { EVENT_REQUEST_OPEN_PLUGIN_SYNC_DIALOG, eventHub } from "../../common/events.ts";
|
||||
import { EVENT_REQUEST_OPEN_PLUGIN_SYNC_DIALOG, eventHub } from "@/common/events.ts";
|
||||
import { PluginDialogModal } from "./PluginDialogModal.ts";
|
||||
import { $msg } from "@/lib/src/common/i18n.ts";
|
||||
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
|
||||
import type { LiveSyncCore } from "../../main.ts";
|
||||
import { $msg } from "@lib/common/i18n.ts";
|
||||
import type { InjectableServiceHub } from "@lib/services/InjectableServices.ts";
|
||||
import type { LiveSyncCore } from "@/main.ts";
|
||||
import { LiveSyncError } from "@lib/common/LSError.ts";
|
||||
|
||||
const d = "\u200b";
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
type IPluginDataExDisplay,
|
||||
type PluginDataExFile,
|
||||
} from "./CmdConfigSync.ts";
|
||||
import { Logger } from "../../lib/src/common/logger";
|
||||
import { type FilePath, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "../../lib/src/common/types";
|
||||
import { getDocData, timeDeltaToHumanReadable, unique } from "../../lib/src/common/utils";
|
||||
import type ObsidianLiveSyncPlugin from "../../main";
|
||||
import { Logger } from "@lib/common/logger";
|
||||
import { type FilePath, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "@lib/common/types";
|
||||
import { getDocData, timeDeltaToHumanReadable, unique } from "@lib/common/utils";
|
||||
import type ObsidianLiveSyncPlugin from "@/main";
|
||||
// import { askString } from "../../common/utils";
|
||||
import { Menu } from "@/deps.ts";
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { mount, unmount } from "svelte";
|
||||
import { App, Modal } from "../../deps.ts";
|
||||
import ObsidianLiveSyncPlugin from "../../main.ts";
|
||||
import { App, Modal } from "@/deps.ts";
|
||||
import ObsidianLiveSyncPlugin from "@/main.ts";
|
||||
import PluginPane from "./PluginPane.svelte";
|
||||
export class PluginDialogModal extends Modal {
|
||||
plugin: ObsidianLiveSyncPlugin;
|
||||
@@ -16,9 +16,11 @@ export class PluginDialogModal extends Modal {
|
||||
|
||||
override onOpen() {
|
||||
const { contentEl } = this;
|
||||
this.contentEl.style.overflow = "auto";
|
||||
this.contentEl.style.display = "flex";
|
||||
this.contentEl.style.flexDirection = "column";
|
||||
this.contentEl.setCssStyles({
|
||||
overflow: "auto",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
});
|
||||
this.titleEl.setText("Customization Sync (Beta3)");
|
||||
if (!this.component) {
|
||||
this.component = mount(PluginPane, {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import ObsidianLiveSyncPlugin from "../../main";
|
||||
import ObsidianLiveSyncPlugin from "@/main";
|
||||
import {
|
||||
ConfigSync,
|
||||
type IPluginDataExDisplay,
|
||||
@@ -11,16 +11,16 @@
|
||||
} from "./CmdConfigSync.ts";
|
||||
import PluginCombo from "./PluginCombo.svelte";
|
||||
import { Menu, type PluginManifest } from "@/deps.ts";
|
||||
import { unique } from "../../lib/src/common/utils";
|
||||
import { unique } from "@lib/common/utils";
|
||||
import {
|
||||
MODE_SELECTIVE,
|
||||
MODE_AUTOMATIC,
|
||||
MODE_PAUSED,
|
||||
type SYNC_MODE,
|
||||
MODE_SHINY,
|
||||
} from "../../lib/src/common/types";
|
||||
import { normalizePath } from "../../deps";
|
||||
import { HiddenFileSync } from "../HiddenFileSync/CmdHiddenFileSync.ts";
|
||||
} from "@lib/common/types";
|
||||
import { normalizePath } from "@/deps";
|
||||
import { HiddenFileSync } from "@/features/HiddenFileSync/CmdHiddenFileSync.ts";
|
||||
import { LOG_LEVEL_NOTICE, Logger } from "octagonal-wheels/common/logger";
|
||||
import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore.ts";
|
||||
export let plugin: ObsidianLiveSyncPlugin;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { App, Modal } from "../../deps.ts";
|
||||
import { type FilePath, type LoadedEntry } from "../../lib/src/common/types.ts";
|
||||
import { App, Modal } from "@/deps.ts";
|
||||
import { type FilePath, type LoadedEntry } from "@lib/common/types.ts";
|
||||
import JsonResolvePane from "./JsonResolvePane.svelte";
|
||||
import { waitForSignal } from "../../lib/src/common/utils.ts";
|
||||
import { waitForSignal } from "@lib/common/utils.ts";
|
||||
import { mount, unmount } from "svelte";
|
||||
|
||||
export class JsonResolveModal extends Modal {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { type Diff, DIFF_DELETE, DIFF_INSERT, diff_match_patch } from "../../deps.ts";
|
||||
import type { FilePath, LoadedEntry } from "../../lib/src/common/types.ts";
|
||||
import { decodeBinary, readString } from "../../lib/src/string_and_binary/convert.ts";
|
||||
import { getDocData, isObjectDifferent, mergeObject } from "../../lib/src/common/utils.ts";
|
||||
import { type Diff, DIFF_DELETE, DIFF_INSERT, diff_match_patch } from "@/deps.ts";
|
||||
import type { FilePath, LoadedEntry } from "@lib/common/types.ts";
|
||||
import { decodeBinary, readString } from "@lib/string_and_binary/convert.ts";
|
||||
import { getDocData, isObjectDifferent, mergeObject } from "@lib/common/utils.ts";
|
||||
|
||||
interface Props {
|
||||
docs?: LoadedEntry[];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type PluginManifest, type ListedFiles } from "../../deps.ts";
|
||||
import { type PluginManifest, type ListedFiles } from "@/deps.ts";
|
||||
import {
|
||||
type LoadedEntry,
|
||||
type FilePathWithPrefix,
|
||||
@@ -15,8 +15,8 @@ import {
|
||||
LOG_LEVEL_DEBUG,
|
||||
type MetaEntry,
|
||||
type UXDataWriteOptions,
|
||||
} from "../../lib/src/common/types.ts";
|
||||
import { type InternalFileInfo, ICHeader, ICHeaderEnd } from "../../common/types.ts";
|
||||
} from "@lib/common/types.ts";
|
||||
import { type InternalFileInfo, ICHeader, ICHeaderEnd } from "@/common/types.ts";
|
||||
import {
|
||||
readAsBlob,
|
||||
isDocContentSame,
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
fireAndForget,
|
||||
type CustomRegExp,
|
||||
getFileRegExp,
|
||||
} from "../../lib/src/common/utils.ts";
|
||||
} from "@lib/common/utils.ts";
|
||||
import {
|
||||
compareMTime,
|
||||
isInternalMetadata,
|
||||
@@ -39,17 +39,17 @@ import {
|
||||
BASE_IS_NEW,
|
||||
EVEN,
|
||||
displayRev,
|
||||
} from "../../common/utils.ts";
|
||||
} from "@/common/utils.ts";
|
||||
import { PeriodicProcessor } from "@/common/PeriodicProcessor.ts";
|
||||
import { serialized, skipIfDuplicated } from "octagonal-wheels/concurrency/lock";
|
||||
import { JsonResolveModal } from "../HiddenFileCommon/JsonResolveModal.ts";
|
||||
import { LiveSyncCommands } from "../LiveSyncCommands.ts";
|
||||
import { addPrefix, stripAllPrefixes } from "../../lib/src/string_and_binary/path.ts";
|
||||
import { JsonResolveModal } from "@/features/HiddenFileCommon/JsonResolveModal.ts";
|
||||
import { LiveSyncCommands } from "@/features/LiveSyncCommands.ts";
|
||||
import { addPrefix, stripAllPrefixes } from "@lib/string_and_binary/path.ts";
|
||||
import { QueueProcessor } from "octagonal-wheels/concurrency/processor";
|
||||
import { hiddenFilesEventCount, hiddenFilesProcessingCount } from "../../lib/src/mock_and_interop/stores.ts";
|
||||
import { EVENT_SETTING_SAVED, eventHub } from "../../common/events.ts";
|
||||
import { hiddenFilesEventCount, hiddenFilesProcessingCount } from "@lib/mock_and_interop/stores.ts";
|
||||
import { EVENT_SETTING_SAVED, eventHub } from "@/common/events.ts";
|
||||
import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
|
||||
import type { LiveSyncCore } from "../../main.ts";
|
||||
import type { LiveSyncCore } from "@/main.ts";
|
||||
import { tryGetFilePath } from "@lib/common/utils.doc.ts";
|
||||
type SyncDirection = "push" | "pull" | "safe" | "pullForce" | "pushForce";
|
||||
|
||||
|
||||
@@ -7,12 +7,12 @@ import {
|
||||
type FilePath,
|
||||
type FilePathWithPrefix,
|
||||
type LOG_LEVEL,
|
||||
} from "../lib/src/common/types.ts";
|
||||
import type ObsidianLiveSyncPlugin from "../main.ts";
|
||||
import { MARK_DONE } from "../modules/features/ModuleLog.ts";
|
||||
import type { LiveSyncCore } from "../main.ts";
|
||||
import { __$checkInstanceBinding } from "../lib/src/dev/checks.ts";
|
||||
import { createInstanceLogFunction } from "@/lib/src/services/lib/logUtils.ts";
|
||||
} from "@lib/common/types.ts";
|
||||
import type ObsidianLiveSyncPlugin from "@/main.ts";
|
||||
import { MARK_DONE } from "@/modules/features/ModuleLog.ts";
|
||||
import type { LiveSyncCore } from "@/main.ts";
|
||||
import { __$checkInstanceBinding } from "@lib/dev/checks.ts";
|
||||
import { createInstanceLogFunction } from "@lib/services/lib/logUtils.ts";
|
||||
|
||||
let noticeIndex = 0;
|
||||
export abstract class LiveSyncCommands {
|
||||
|
||||
@@ -9,14 +9,14 @@ import {
|
||||
type EntryLeaf,
|
||||
type FilePathWithPrefix,
|
||||
type MetaEntry,
|
||||
} from "../../lib/src/common/types";
|
||||
import { getNoFromRev } from "../../lib/src/pouchdb/LiveSyncLocalDB";
|
||||
import { LiveSyncCommands } from "../LiveSyncCommands";
|
||||
} from "@lib/common/types";
|
||||
import { getNoFromRev } from "@lib/pouchdb/LiveSyncLocalDB";
|
||||
import { LiveSyncCommands } from "@/features/LiveSyncCommands";
|
||||
import { serialized } from "octagonal-wheels/concurrency/lock_v2";
|
||||
import { arrayToChunkedArray } from "octagonal-wheels/collection";
|
||||
import { EVENT_ANALYSE_DB_USAGE, EVENT_REQUEST_PERFORM_GC_V3, eventHub } from "@/common/events";
|
||||
import type { LiveSyncCouchDBReplicator } from "@/lib/src/replication/couchdb/LiveSyncReplicator";
|
||||
import { delay } from "@/lib/src/common/utils";
|
||||
import type { LiveSyncCouchDBReplicator } from "@lib/replication/couchdb/LiveSyncReplicator";
|
||||
import { delay } from "@lib/common/utils";
|
||||
// import { _requestToCouchDB } from "@/common/utils";
|
||||
const DB_KEY_SEQ = "gc-seq";
|
||||
const DB_KEY_CHUNK_SET = "chunk-set";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { App, Modal } from "@/deps.ts";
|
||||
import P2POpenReplicationPane from "./P2POpenReplicationPane.svelte";
|
||||
import { mount, unmount } from "svelte";
|
||||
import type { LiveSyncTrysteroReplicator } from "@/lib/src/replication/trystero/LiveSyncTrysteroReplicator";
|
||||
import type { LiveSyncTrysteroReplicator } from "@lib/replication/trystero/LiveSyncTrysteroReplicator";
|
||||
|
||||
export type P2POpenReplicationModalCallback = {
|
||||
onSync: (peerId: string) => Promise<void>;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
// import type { TrysteroReplicator } from "@lib/replication/trystero/TrysteroReplicator";
|
||||
import { LOG_LEVEL_NOTICE, LOG_LEVEL_INFO } from "@lib/common/types";
|
||||
import { Logger } from "@lib/common/logger";
|
||||
import type { LiveSyncTrysteroReplicator } from "@/lib/src/replication/trystero/LiveSyncTrysteroReplicator";
|
||||
import type { LiveSyncTrysteroReplicator } from "@lib/replication/trystero/LiveSyncTrysteroReplicator";
|
||||
import { delay, fireAndForget } from "octagonal-wheels/promises";
|
||||
import P2PServerStatusCard from "./P2PServerStatusCard.svelte";
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { onMount, setContext } from "svelte";
|
||||
import { AutoAccepting, DEFAULT_SETTINGS, type P2PSyncSetting } from "../../../lib/src/common/types";
|
||||
import { AutoAccepting, DEFAULT_SETTINGS, type P2PSyncSetting } from "@lib/common/types";
|
||||
import {
|
||||
AcceptedStatus,
|
||||
ConnectionStatus,
|
||||
type PeerStatus,
|
||||
} from "@lib/replication/trystero/P2PReplicatorPaneCommon";
|
||||
import type { LiveSyncTrysteroReplicator } from "@lib/replication/trystero/LiveSyncTrysteroReplicator";
|
||||
import PeerStatusRow from "../P2PReplicator/PeerStatusRow.svelte";
|
||||
import PeerStatusRow from "@/features/P2PSync/P2PReplicator/PeerStatusRow.svelte";
|
||||
import { EVENT_LAYOUT_READY, eventHub } from "@/common/events";
|
||||
import {
|
||||
type PeerInfo,
|
||||
|
||||
@@ -9,7 +9,7 @@ import { LOG_LEVEL_NOTICE, REMOTE_P2P } from "@lib/common/types.ts";
|
||||
import { Logger } from "@lib/common/logger.ts";
|
||||
import { EVENT_P2P_PEER_SHOW_EXTRA_MENU, type PeerStatus } from "@lib/replication/trystero/P2PReplicatorPaneCommon.ts";
|
||||
import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore.ts";
|
||||
import type { P2PPaneParams } from "@/lib/src/replication/trystero/UseP2PReplicatorResult";
|
||||
import type { P2PPaneParams } from "@lib/replication/trystero/UseP2PReplicatorResult";
|
||||
export const VIEW_TYPE_P2P = "p2p-replicator";
|
||||
|
||||
function addToList(item: string, list: string) {
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
EVENT_P2P_REPLICATOR_STATUS,
|
||||
} from "@lib/replication/trystero/TrysteroReplicatorP2PServer";
|
||||
import { EVENT_SETTING_SAVED } from "@lib/events/coreEvents";
|
||||
import type { LiveSyncTrysteroReplicator } from "@/lib/src/replication/trystero/LiveSyncTrysteroReplicator";
|
||||
import type { P2PReplicatorStatus } from "@/lib/src/replication/trystero/TrysteroReplicator";
|
||||
import { extractP2PRoomSuffix } from "@/lib/src/common/utils";
|
||||
import type { LiveSyncTrysteroReplicator } from "@lib/replication/trystero/LiveSyncTrysteroReplicator";
|
||||
import type { P2PReplicatorStatus } from "@lib/replication/trystero/TrysteroReplicator";
|
||||
import { extractP2PRoomSuffix } from "@lib/common/utils";
|
||||
import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore";
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { WorkspaceLeaf } from "@/deps.ts";
|
||||
import { mount } from "svelte";
|
||||
import { SvelteItemView } from "@/common/SvelteItemView.ts";
|
||||
import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore.ts";
|
||||
import type { P2PPaneParams } from "@/lib/src/replication/trystero/UseP2PReplicatorResult";
|
||||
import type { P2PPaneParams } from "@lib/replication/trystero/UseP2PReplicatorResult";
|
||||
import P2PServerStatusPane from "./P2PServerStatusPane.svelte";
|
||||
|
||||
export const VIEW_TYPE_P2P_SERVER_STATUS = "p2p-server-status";
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from "svelte";
|
||||
import { AcceptedStatus, type PeerStatus } from "../../../lib/src/replication/trystero/P2PReplicatorPaneCommon";
|
||||
import type { LiveSyncTrysteroReplicator } from "../../../lib/src/replication/trystero/LiveSyncTrysteroReplicator";
|
||||
import { eventHub } from "../../../common/events";
|
||||
import { EVENT_P2P_PEER_SHOW_EXTRA_MENU } from "../../../lib/src/replication/trystero/P2PReplicatorPaneCommon";
|
||||
import { AcceptedStatus, type PeerStatus } from "@lib/replication/trystero/P2PReplicatorPaneCommon";
|
||||
import type { LiveSyncTrysteroReplicator } from "@lib/replication/trystero/LiveSyncTrysteroReplicator";
|
||||
import { eventHub } from "@/common/events";
|
||||
import { EVENT_P2P_PEER_SHOW_EXTRA_MENU } from "@lib/replication/trystero/P2PReplicatorPaneCommon";
|
||||
|
||||
interface Props {
|
||||
peerStatus: PeerStatus;
|
||||
|
||||
2
src/lib
2
src/lib
Submodule src/lib updated: 53804cbaec...c926417f82
@@ -3,7 +3,7 @@ import type { AnyEntry, FilePathWithPrefix } from "@lib/common/types";
|
||||
import type { IMinimumLiveSyncCommands, LiveSyncBaseCore } from "@/LiveSyncBaseCore";
|
||||
import { stripAllPrefixes } from "@lib/string_and_binary/path";
|
||||
import { createInstanceLogFunction } from "@lib/services/lib/logUtils";
|
||||
import type { ServiceContext } from "@/lib/src/services/base/ServiceBase";
|
||||
import type { ServiceContext } from "@lib/services/base/ServiceBase";
|
||||
|
||||
export abstract class AbstractModule<
|
||||
T extends LiveSyncBaseCore<ServiceContext, IMinimumLiveSyncCommands> = LiveSyncBaseCore<
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { type Prettify } from "../lib/src/common/types";
|
||||
import type { LiveSyncCore } from "../main";
|
||||
import type ObsidianLiveSyncPlugin from "../main";
|
||||
import { type Prettify } from "@lib/common/types";
|
||||
import type { LiveSyncCore } from "@/main";
|
||||
import type ObsidianLiveSyncPlugin from "@/main";
|
||||
import { AbstractModule } from "./AbstractModule.ts";
|
||||
import type { ChainableExecuteFunction, OverridableFunctionsKeys } from "./ModuleTypes";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Prettify } from "../lib/src/common/types";
|
||||
import type { LiveSyncCore } from "../main";
|
||||
import type { Prettify } from "@lib/common/types";
|
||||
import type { LiveSyncCore } from "@/main";
|
||||
|
||||
export type OverridableFunctionsKeys<T> = {
|
||||
[K in keyof T as K extends `$${string}` ? K : never]: T[K];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { PeriodicProcessor } from "@/common/PeriodicProcessor";
|
||||
import type { LiveSyncCore } from "../../main";
|
||||
import { AbstractModule } from "../AbstractModule";
|
||||
import type { LiveSyncCore } from "@/main";
|
||||
import { AbstractModule } from "@/modules/AbstractModule";
|
||||
|
||||
export class ModulePeriodicProcess extends AbstractModule {
|
||||
periodicSyncProcessor = new PeriodicProcessor(this.core, async () => await this.services.replication.replicate());
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import type PouchDB from "pouchdb-core";
|
||||
import { fireAndForget } from "octagonal-wheels/promises";
|
||||
import { AbstractModule } from "../AbstractModule";
|
||||
import { AbstractModule } from "@/modules/AbstractModule";
|
||||
import { Logger, LOG_LEVEL_NOTICE, LOG_LEVEL_INFO } from "octagonal-wheels/common/logger";
|
||||
import { skipIfDuplicated } from "octagonal-wheels/concurrency/lock";
|
||||
import { balanceChunkPurgedDBs } from "@lib/pouchdb/chunks";
|
||||
import { purgeUnreferencedChunks } from "@lib/pouchdb/chunks";
|
||||
import { LiveSyncCouchDBReplicator } from "../../lib/src/replication/couchdb/LiveSyncReplicator";
|
||||
import { type EntryDoc, type RemoteType } from "../../lib/src/common/types";
|
||||
import { LiveSyncCouchDBReplicator } from "@lib/replication/couchdb/LiveSyncReplicator";
|
||||
import { type EntryDoc, type RemoteType } from "@lib/common/types";
|
||||
|
||||
import { scheduleTask } from "octagonal-wheels/concurrency/task";
|
||||
import { EVENT_FILE_SAVED, EVENT_SETTING_SAVED, eventHub } from "../../common/events";
|
||||
import { EVENT_FILE_SAVED, EVENT_SETTING_SAVED, eventHub } from "@/common/events";
|
||||
|
||||
import { $msg } from "../../lib/src/common/i18n";
|
||||
import type { LiveSyncCore } from "../../main";
|
||||
import { $msg } from "@lib/common/i18n";
|
||||
import type { LiveSyncCore } from "@/main";
|
||||
import { ReplicateResultProcessor } from "./ReplicateResultProcessor";
|
||||
import { UnresolvedErrorManager } from "@lib/services/base/UnresolvedErrorManager";
|
||||
import { clearHandlers } from "@lib/replication/SyncParamsHandler";
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { fireAndForget } from "octagonal-wheels/promises";
|
||||
import { REMOTE_MINIO, REMOTE_P2P, type RemoteDBSettings } from "../../lib/src/common/types";
|
||||
import { LiveSyncCouchDBReplicator } from "../../lib/src/replication/couchdb/LiveSyncReplicator";
|
||||
import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator";
|
||||
import { AbstractModule } from "../AbstractModule";
|
||||
import type { LiveSyncCore } from "../../main";
|
||||
import { REMOTE_MINIO, REMOTE_P2P, type RemoteDBSettings } from "@lib/common/types";
|
||||
import { LiveSyncCouchDBReplicator } from "@lib/replication/couchdb/LiveSyncReplicator";
|
||||
import type { LiveSyncAbstractReplicator } from "@lib/replication/LiveSyncAbstractReplicator";
|
||||
import { AbstractModule } from "@/modules/AbstractModule";
|
||||
import type { LiveSyncCore } from "@/main";
|
||||
|
||||
export class ModuleReplicatorCouchDB extends AbstractModule {
|
||||
_anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator | false> {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { REMOTE_MINIO, type RemoteDBSettings } from "../../lib/src/common/types";
|
||||
import { LiveSyncJournalReplicator } from "../../lib/src/replication/journal/LiveSyncJournalReplicator";
|
||||
import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator";
|
||||
import type { LiveSyncCore } from "../../main";
|
||||
import { AbstractModule } from "../AbstractModule";
|
||||
import { REMOTE_MINIO, type RemoteDBSettings } from "@lib/common/types";
|
||||
import { LiveSyncJournalReplicator } from "@lib/replication/journal/LiveSyncJournalReplicator";
|
||||
import type { LiveSyncAbstractReplicator } from "@lib/replication/LiveSyncAbstractReplicator";
|
||||
import type { LiveSyncCore } from "@/main";
|
||||
import { AbstractModule } from "@/modules/AbstractModule";
|
||||
|
||||
export class ModuleReplicatorMinIO extends AbstractModule {
|
||||
_anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator | false> {
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
type MetaEntry,
|
||||
} from "@lib/common/types";
|
||||
import type { ModuleReplicator } from "./ModuleReplicator";
|
||||
import { isChunk } from "@/lib/src/common/typeUtils";
|
||||
import { isChunk } from "@lib/common/typeUtils";
|
||||
import {
|
||||
LOG_LEVEL_DEBUG,
|
||||
LOG_LEVEL_INFO,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { AbstractModule } from "../AbstractModule.ts";
|
||||
import { LOG_LEVEL_NOTICE, type FilePathWithPrefix } from "../../lib/src/common/types";
|
||||
import { AbstractModule } from "@/modules/AbstractModule.ts";
|
||||
import { LOG_LEVEL_NOTICE, type FilePathWithPrefix } from "@lib/common/types";
|
||||
import { QueueProcessor } from "octagonal-wheels/concurrency/processor";
|
||||
import { sendValue } from "octagonal-wheels/messagepassing/signal";
|
||||
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
|
||||
import type { LiveSyncCore } from "../../main.ts";
|
||||
import type { InjectableServiceHub } from "@lib/services/InjectableServices.ts";
|
||||
import type { LiveSyncCore } from "@/main.ts";
|
||||
|
||||
export class ModuleConflictChecker extends AbstractModule {
|
||||
async _queueConflictCheckIfOpen(file: FilePathWithPrefix): Promise<void> {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { serialized } from "octagonal-wheels/concurrency/lock";
|
||||
import { AbstractModule } from "../AbstractModule.ts";
|
||||
import { AbstractModule } from "@/modules/AbstractModule.ts";
|
||||
import {
|
||||
AUTO_MERGED,
|
||||
CANCELLED,
|
||||
@@ -10,15 +10,15 @@ import {
|
||||
NOT_CONFLICTED,
|
||||
type diff_check_result,
|
||||
type FilePathWithPrefix,
|
||||
} from "../../lib/src/common/types";
|
||||
} from "@lib/common/types";
|
||||
import { isCustomisationSyncMetadata, isPluginMetadata } from "@lib/common/typeUtils.ts";
|
||||
import { TARGET_IS_NEW } from "@lib/common/models/shared.const.symbols.ts";
|
||||
import { compareMTime, displayRev } from "@lib/common/utils.ts";
|
||||
import diff_match_patch from "diff-match-patch";
|
||||
import { stripAllPrefixes, isPlainText } from "../../lib/src/string_and_binary/path";
|
||||
import { eventHub } from "../../common/events.ts";
|
||||
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
|
||||
import type { LiveSyncCore } from "../../main.ts";
|
||||
import { stripAllPrefixes, isPlainText } from "@lib/string_and_binary/path";
|
||||
import { eventHub } from "@/common/events.ts";
|
||||
import type { InjectableServiceHub } from "@lib/services/InjectableServices.ts";
|
||||
import type { LiveSyncCore } from "@/main.ts";
|
||||
|
||||
declare global {
|
||||
interface LSEvents {
|
||||
@@ -182,9 +182,9 @@ export class ModuleConflictResolver extends AbstractModule {
|
||||
revs.map(async (rev) => {
|
||||
const leaf = await this.core.databaseFileAccess.fetchEntryMeta(filename, rev);
|
||||
if (leaf == false) {
|
||||
return [0, rev] as [number, string];
|
||||
return [0, rev];
|
||||
}
|
||||
return [leaf.mtime, rev] as [number, string];
|
||||
return [leaf.mtime, rev];
|
||||
})
|
||||
)),
|
||||
] as [number, string][]
|
||||
|
||||
@@ -10,12 +10,12 @@ import {
|
||||
type RemoteDBSettings,
|
||||
IncompatibleChangesInSpecificPattern,
|
||||
CompatibleButLossyChanges,
|
||||
} from "../../lib/src/common/types.ts";
|
||||
import { escapeMarkdownValue } from "../../lib/src/common/utils.ts";
|
||||
import { AbstractModule } from "../AbstractModule.ts";
|
||||
import { $msg } from "../../lib/src/common/i18n.ts";
|
||||
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
|
||||
import type { LiveSyncCore } from "../../main.ts";
|
||||
} from "@lib/common/types.ts";
|
||||
import { escapeMarkdownValue } from "@lib/common/utils.ts";
|
||||
import { AbstractModule } from "@/modules/AbstractModule.ts";
|
||||
import { $msg } from "@lib/common/i18n.ts";
|
||||
import type { InjectableServiceHub } from "@lib/services/InjectableServices.ts";
|
||||
import type { LiveSyncCore } from "@/main.ts";
|
||||
import { REMOTE_P2P } from "@lib/common/models/setting.const.ts";
|
||||
|
||||
function valueToString(value: any) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user