mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-06-04 17:42:32 +03:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4581db45c4 | ||
|
|
05cab8ec66 | ||
|
|
b005625ef3 | ||
|
|
e5408b4dd7 | ||
|
|
95a9b1b41c | ||
|
|
2aa8bc1165 | ||
|
|
f8998d5441 | ||
|
|
26f5f54f24 | ||
|
|
99f3aca024 | ||
|
|
194397dd94 | ||
|
|
afa79d78dc | ||
|
|
9a9440a768 | ||
|
|
e375860af8 | ||
|
|
b57f34c15b | ||
|
|
b82fd9f04b | ||
|
|
bb77426b7b | ||
|
|
1bef5fbef3 | ||
|
|
7d2ba1b0b9 | ||
|
|
ac6b9a4dad | ||
|
|
225e2c5096 | ||
|
|
0e6dd300ef | ||
|
|
5a280c7919 |
122
devs.md
122
devs.md
@@ -3,6 +3,76 @@
|
||||
|
||||
Self-hosted LiveSync is an Obsidian plugin for synchronising vaults across devices using CouchDB, MinIO/S3, or peer-to-peer WebRTC. The codebase uses a modular architecture with TypeScript, Svelte, and PouchDB.
|
||||
|
||||
## Build & Development Workflow
|
||||
|
||||
### Environment Setup
|
||||
|
||||
#### First-time Setup
|
||||
|
||||
This repository uses submodules by convention. Therefore, you must use the `--recursive` flag when cloning it.
|
||||
```bash
|
||||
git clone --recursive https://github.com/vrtmrz/obsidian-livesync
|
||||
npm ci
|
||||
npm run build
|
||||
```
|
||||
|
||||
Note: if you already cloned without submodules, run: `git submodule update --init --recursive`
|
||||
|
||||
#### Branch switching
|
||||
When switching branches, please make sure to update submodules as well, since they may be updated in the new branch.
|
||||
```bash
|
||||
git checkout --recurse-submodules 0.25.70-patch1 # tag or branch name
|
||||
npm ci
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Commands
|
||||
|
||||
```bash
|
||||
npm run test:unit # Run unit tests with vitest (or `npm run test:unit:coverage` for coverage)
|
||||
npm run check # TypeScript and svelte type checking
|
||||
npm run dev # Development build with auto-rebuild (uses .env for test vault paths)
|
||||
npm run build # Production build
|
||||
npm run buildDev # Development build (one-time)
|
||||
npm run bakei18n # Pre-build step: compile i18n resources (YAML → JSON → TS)
|
||||
npm run test:unit # Run unit tests only (no Docker services required)
|
||||
npm test # Run Harness based vitest tests (requires Docker services), not recommended, unstable.
|
||||
```
|
||||
|
||||
### Tips
|
||||
|
||||
We can use CLI's E2E test command instead of `npm test`.
|
||||
|
||||
### Auto-copy to test vaults
|
||||
|
||||
To facilitate development and testing, the build process can automatically copy the built plugin to specified test vault
|
||||
|
||||
- Create `.env` file with `PATHS_TEST_INSTALL` pointing to test vault plug-in directories (`:` separated on Unix, `;` on Windows)
|
||||
- Development builds auto-copy to these paths on build whilst `npm run dev` is running (watch mode)
|
||||
|
||||
### Testing Infrastructure
|
||||
|
||||
- ~~**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`).
|
||||
|
||||
- **Docker Services**: Tests require CouchDB, MinIO (S3), and P2P services:
|
||||
```bash
|
||||
npm run test:docker-all:start # Start all test services
|
||||
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`)
|
||||
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/unit/` - Unit tests (via vitest, as harness is browser-based)
|
||||
- `test/harness/` - Mock implementations (e.g., `obsidian-mock.ts`)
|
||||
|
||||
|
||||
|
||||
## Architecture
|
||||
|
||||
### Module System
|
||||
@@ -47,48 +117,6 @@ Hence, the new feature should be implemented as follows:
|
||||
- **Development code**: Use `.dev.ts` suffix (replaced with `.prod.ts` in production)
|
||||
- **Path aliases**: `@/*` maps to `src/*`, `@lib/*` maps to `src/lib/src/*`
|
||||
|
||||
## Build & Development Workflow
|
||||
|
||||
### Commands
|
||||
|
||||
```bash
|
||||
npm run test:unit # Run unit tests with vitest (or `npm run test:unit:coverage` for coverage)
|
||||
npm run check # TypeScript and svelte type checking
|
||||
npm run dev # Development build with auto-rebuild (uses .env for test vault paths)
|
||||
npm run build # Production build
|
||||
npm run buildDev # Development build (one-time)
|
||||
npm run bakei18n # Pre-build step: compile i18n resources (YAML → JSON → TS)
|
||||
npm test # Run vitest tests (requires Docker services)
|
||||
```
|
||||
|
||||
### Environment Setup
|
||||
|
||||
- Clone with submodules: `git clone --recurse-submodules <repository-url>`
|
||||
- If you already cloned without them, run: `git submodule update --init --recursive`
|
||||
- The shared common library is provided by the `src/lib` submodule, and builds will fail if it is missing
|
||||
- Create `.env` file with `PATHS_TEST_INSTALL` pointing to test vault plug-in directories (`:` separated on Unix, `;` on Windows)
|
||||
- Development builds auto-copy to these paths on build
|
||||
|
||||
### Testing Infrastructure
|
||||
|
||||
- ~~**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`).
|
||||
|
||||
- **Docker Services**: Tests require CouchDB, MinIO (S3), and P2P services:
|
||||
```bash
|
||||
npm run test:docker-all:start # Start all test services
|
||||
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`)
|
||||
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/unit/` - Unit tests (via vitest, as harness is browser-based)
|
||||
- `test/harness/` - Mock implementations (e.g., `obsidian-mock.ts`)
|
||||
|
||||
## Code Conventions
|
||||
|
||||
### Internationalisation (i18n)
|
||||
@@ -156,17 +184,17 @@ export class ModuleExample extends AbstractObsidianModule {
|
||||
|
||||
## Beta Policy
|
||||
|
||||
- Beta versions are denoted by appending `+patchedN` to the base version number.
|
||||
- Beta versions are denoted by appending `-patchedN` to the base version number.
|
||||
- `The base version` mostly corresponds to the stable release version.
|
||||
- e.g., v0.25.41+patched1 is equivalent to v0.25.42-beta1.
|
||||
- e.g., v0.25.41-patched1 is equivalent to v0.25.42-beta1.
|
||||
- This notation is due to SemVer incompatibility of Obsidian's plugin system.
|
||||
- Hence, this release is `0.25.41+patched1`.
|
||||
- Hence, this release is `0.25.41-patched1`.
|
||||
- Each beta version may include larger changes, but bug fixes will often not be included.
|
||||
- I think that in most cases, bug fixes will cause the stable releases.
|
||||
- They will not be released per branch or backported; they will simply be released.
|
||||
- Bug fixes for previous versions will be applied to the latest beta version.
|
||||
This means, if xx.yy.02+patched1 exists and there is a defect in xx.yy.01, a fix is applied to xx.yy.02+patched1 and yields xx.yy.02+patched2.
|
||||
If the fix is required immediately, it is released as xx.yy.02 (with xx.yy.01+patched1).
|
||||
This means, if xx.yy.02-patched1 exists and there is a defect in xx.yy.01, a fix is applied to xx.yy.02-patched1 and yields xx.yy.02-patched2.
|
||||
If the fix is required immediately, it is released as xx.yy.02 (with xx.yy.01-patched1).
|
||||
- This procedure remains unchanged from the current one.
|
||||
- At the very least, I am using the latest beta.
|
||||
- However, I will not be using a beta continuously for a week after it has been released. It is probably closer to an RC in nature.
|
||||
|
||||
@@ -382,11 +382,11 @@ This file is in Markdown format so that it can be placed in the Vault externally
|
||||
|
||||
There are some options to use `redflag.md`.
|
||||
|
||||
| Filename | Human-Friendly Name | Description |
|
||||
| ------------- | ------------------- | ------------------------------------------------------------------------------------ |
|
||||
| `redflag.md` | - | Suspends all processes. |
|
||||
| `redflag2.md` | `flag_rebuild.md` | Suspends all processes, and rebuild both local and remote databases by local files. |
|
||||
| `redflag3.md` | `flag_fetch.md` | Suspends all processes, discard the local database, and fetch from the remote again. |
|
||||
| Filename | Human-Friendly Name | Description |
|
||||
| ------------- | ------------------- | --------------------------------------------------------------------------------------- |
|
||||
| `redflag.md` | - | Suspends all processes. |
|
||||
| `redflag2.md` | `flag_rebuild.md` | Suspends all processes, and rebuilds both local and remote databases from local files. |
|
||||
| `redflag3.md` | `flag_fetch.md` | Suspends all processes, discards the local database, and fetches from the remote again. |
|
||||
|
||||
When fetching everything remotely or performing a rebuild, restarting Obsidian
|
||||
is performed once for safety reasons. At that time, Self-hosted LiveSync uses
|
||||
@@ -399,6 +399,16 @@ these files are also not subject to synchronisation.
|
||||
However, occasionally the deletion of files may fail. This should generally work
|
||||
normally after restarting Obsidian. (As far as I can observe).
|
||||
|
||||
>[!IMPORTANT]
|
||||
> When a flag file is detected, all synchronisation is disabled, and `Suspend file watching` and
|
||||
> `Suspend database reflecting` are enabled automatically. Therefore, please follow the steps below to
|
||||
> resolve the issue.
|
||||
> 1. Delete the flag file.
|
||||
> 2. Shutdown Obsidian.
|
||||
> 3. Check your vault folder and ensure it is no longer there.
|
||||
> 4. Launch Obsidian.
|
||||
> 5. Disable `Suspend file watching` and `Suspend database reflecting` in the settings dialogue (if you have not been asked).
|
||||
|
||||
### Old tips
|
||||
|
||||
- Rarely, a file in the database could be corrupted. The plugin will not write
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-livesync",
|
||||
"name": "Self-hosted LiveSync",
|
||||
"version": "0.25.70-patch1",
|
||||
"version": "0.25.73",
|
||||
"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",
|
||||
|
||||
321
package-lock.json
generated
321
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "obsidian-livesync",
|
||||
"version": "0.25.70-patch1",
|
||||
"version": "0.25.73",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "obsidian-livesync",
|
||||
"version": "0.25.70-patch1",
|
||||
"version": "0.25.73",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.808.0",
|
||||
@@ -52,9 +52,9 @@
|
||||
"@types/transform-pouch": "^1.0.6",
|
||||
"@typescript-eslint/eslint-plugin": "8.56.1",
|
||||
"@typescript-eslint/parser": "8.56.1",
|
||||
"@vitest/browser": "^4.1.1",
|
||||
"@vitest/browser-playwright": "^4.1.1",
|
||||
"@vitest/coverage-v8": "^4.1.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-plugin-inline-worker": "^0.1.1",
|
||||
@@ -91,7 +91,7 @@
|
||||
"typescript": "5.9.3",
|
||||
"vite": "^7.3.1",
|
||||
"vite-plugin-istanbul": "^8.0.0",
|
||||
"vitest": "^4.1.1",
|
||||
"vitest": "^4.1.8",
|
||||
"webdriverio": "^9.27.0",
|
||||
"yaml": "^2.8.2"
|
||||
}
|
||||
@@ -1852,9 +1852,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@eslint/config-array/node_modules/brace-expansion": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
||||
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
|
||||
"version": "1.1.15",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
|
||||
"integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1933,9 +1933,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
||||
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
|
||||
"version": "1.1.15",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
|
||||
"integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -3547,13 +3547,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@smithy/core": {
|
||||
"version": "3.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.24.5.tgz",
|
||||
"integrity": "sha512-Kt8phUg45M15EjhYAbZ+fFikYneijLu9Liugz8ZsYz2i8j0hzGv27LWKpEHYRfvj+LyCOSijpcR/2i8RouV+cA==",
|
||||
"version": "3.24.6",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.24.6.tgz",
|
||||
"integrity": "sha512-wBXDRup6UU97VKyaiRo8AssnfStPtG0oAAfpq/bC0a1YYau8pM86YB4kM6ccoVi1mS8l/UHbn9oDM+7uozr/ug==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-crypto/crc32": "5.2.0",
|
||||
"@smithy/types": "^4.14.2",
|
||||
"@smithy/types": "^4.14.3",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
@@ -3720,12 +3720,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@smithy/is-array-buffer": {
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.3.3.tgz",
|
||||
"integrity": "sha512-RRxYqjUa/n8dRVkbhyuiRarppLzt4H/AtMUEFmiHlDy8o4wrgqAdzxsk9naemzu6iX67ZV375fNmX7Q8dynGKw==",
|
||||
"version": "4.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.3.6.tgz",
|
||||
"integrity": "sha512-/cSYHP8jPffkhBClQzH9fAJujIh8dwMwg2swrVF4stXQsUWO5Oi2bwyaMUcBPIyulUI5IxaJFxd9C8UQX+YZsQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@smithy/core": "^3.24.3",
|
||||
"@smithy/core": "^3.24.6",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
@@ -3989,9 +3989,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@smithy/types": {
|
||||
"version": "4.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.2.tgz",
|
||||
"integrity": "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw==",
|
||||
"version": "4.14.3",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.3.tgz",
|
||||
"integrity": "sha512-YupL0ZWmFtJexUN2cHzkvvF/b9pKrtAIfT1o7/oY/Ppu8IYeZ+lDPM5vZdQJaSeA132dJCqojjGC9NhXeF71VQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.6.2"
|
||||
@@ -4053,12 +4053,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@smithy/util-buffer-from": {
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.3.3.tgz",
|
||||
"integrity": "sha512-5xlgilVaX96HdVlLZymKUa7vOTZtisOTxBJloM2J4PeRqyAWBeFIq0DnIxQISvwxT4rgJAvk7rHhB+GlCCKe8g==",
|
||||
"version": "4.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.3.6.tgz",
|
||||
"integrity": "sha512-sms/ty2CJwHOiGzEaAVWizTVK5KusXpAYqCUeXIa+hWtNKLwjimH4z11mc07d0Fe3DT3lmZJIZWOMcVQ/N4hBQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@smithy/core": "^3.24.3",
|
||||
"@smithy/core": "^3.24.6",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
@@ -4194,12 +4194,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@smithy/util-utf8": {
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.3.3.tgz",
|
||||
"integrity": "sha512-c1QpRBn3aMsoqE64dd4Imgjy8Pynfw+eR7GkjElquxUFSnezwYVaOFm8JcYa+Bo/5ssbEyPKcT3+4bmrWYh6eQ==",
|
||||
"version": "4.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.3.6.tgz",
|
||||
"integrity": "sha512-tAa4sePYB7mlJzdYbdBqdv37KwFKWixmM/r3ihcI0HFOVjf+a5oGvtcLXcGm4S1bY4DFsLAIOHgjubtp+oRufw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@smithy/core": "^3.24.3",
|
||||
"@smithy/core": "^3.24.6",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
@@ -4994,47 +4994,46 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/browser": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.1.1.tgz",
|
||||
"integrity": "sha512-gjjrFC4+kPVK/fN9URDJWrssU5Gqh8Az8pKG/NSfQ2V+ky8b/y1BgBg0Ug13+hOGp5pzInonmGRPn7vOgSLgzA==",
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.1.8.tgz",
|
||||
"integrity": "sha512-u21VzX07HzlJYpFgkxmjEXar/tG2UqWGgyGG/46SrrPc7rSdCTPw5vuowopO9CIqF8UCUQzDFdbVnNpw6N0BfQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@blazediff/core": "1.9.1",
|
||||
"@vitest/mocker": "4.1.1",
|
||||
"@vitest/utils": "4.1.1",
|
||||
"@vitest/mocker": "4.1.8",
|
||||
"@vitest/utils": "4.1.8",
|
||||
"magic-string": "^0.30.21",
|
||||
"pngjs": "^7.0.0",
|
||||
"sirv": "^3.0.2",
|
||||
"tinyrainbow": "^3.0.3",
|
||||
"tinyrainbow": "^3.1.0",
|
||||
"ws": "^8.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vitest": "4.1.1"
|
||||
"vitest": "4.1.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/browser-playwright": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.1.1.tgz",
|
||||
"integrity": "sha512-dtVSBZZha2k/7P7EAXXrEAoxuIKl8Yv9f2Dk4GN/DGfmhf4DQvkvu+57okR2wq/gan1xppKjL/aBxK/kbYrbGw==",
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.1.8.tgz",
|
||||
"integrity": "sha512-SR7FqgegaexEg73xvf3ArtygXegagMdXnL0EZMpxrWvvhQxvicD/E8p0ib0J91riPRtQUViyh67Xjw3NqvyhVg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vitest/browser": "4.1.1",
|
||||
"@vitest/mocker": "4.1.1",
|
||||
"tinyrainbow": "^3.0.3"
|
||||
"@vitest/browser": "4.1.8",
|
||||
"@vitest/mocker": "4.1.8",
|
||||
"tinyrainbow": "^3.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"playwright": "*",
|
||||
"vitest": "4.1.1"
|
||||
"vitest": "4.1.8"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"playwright": {
|
||||
@@ -5043,14 +5042,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/coverage-v8": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.1.tgz",
|
||||
"integrity": "sha512-nZ4RWwGCoGOQRMmU/Q9wlUY540RVRxJZ9lxFsFfy0QV7Zmo5VVBhB6Sl9Xa0KIp2iIs3zWfPlo9LcY1iqbpzCw==",
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.8.tgz",
|
||||
"integrity": "sha512-lt3kovsyHwYe00wq4D1ti0Z974fWj4NLp6siqiyEufUpyFwK9Yhi7rBhac9JL5aA0zoMrJqc4vYPZRUnI7l7nw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@bcoe/v8-coverage": "^1.0.2",
|
||||
"@vitest/utils": "4.1.1",
|
||||
"@vitest/utils": "4.1.8",
|
||||
"ast-v8-to-istanbul": "^1.0.0",
|
||||
"istanbul-lib-coverage": "^3.2.2",
|
||||
"istanbul-lib-report": "^3.0.1",
|
||||
@@ -5058,14 +5058,14 @@
|
||||
"magicast": "^0.5.2",
|
||||
"obug": "^2.1.1",
|
||||
"std-env": "^4.0.0-rc.1",
|
||||
"tinyrainbow": "^3.0.3"
|
||||
"tinyrainbow": "^3.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vitest/browser": "4.1.1",
|
||||
"vitest": "4.1.1"
|
||||
"@vitest/browser": "4.1.8",
|
||||
"vitest": "4.1.8"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vitest/browser": {
|
||||
@@ -5074,41 +5074,31 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/expect": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.1.tgz",
|
||||
"integrity": "sha512-xAV0fqBTk44Rn6SjJReEQkHP3RrqbJo6JQ4zZ7/uVOiJZRarBtblzrOfFIZeYUrukp2YD6snZG6IBqhOoHTm+A==",
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.8.tgz",
|
||||
"integrity": "sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@standard-schema/spec": "^1.1.0",
|
||||
"@types/chai": "^5.2.2",
|
||||
"@vitest/spy": "4.1.1",
|
||||
"@vitest/utils": "4.1.1",
|
||||
"@vitest/spy": "4.1.8",
|
||||
"@vitest/utils": "4.1.8",
|
||||
"chai": "^6.2.2",
|
||||
"tinyrainbow": "^3.0.3"
|
||||
"tinyrainbow": "^3.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/expect/node_modules/chai": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
|
||||
"integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/mocker": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.1.tgz",
|
||||
"integrity": "sha512-h3BOylsfsCLPeceuCPAAJ+BvNwSENgJa4hXoXu4im0bs9Lyp4URc4JYK4pWLZ4pG/UQn7AT92K6IByi6rE6g3A==",
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.8.tgz",
|
||||
"integrity": "sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/spy": "4.1.1",
|
||||
"@vitest/spy": "4.1.8",
|
||||
"estree-walker": "^3.0.3",
|
||||
"magic-string": "^0.30.21"
|
||||
},
|
||||
@@ -5129,26 +5119,26 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/pretty-format": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.1.tgz",
|
||||
"integrity": "sha512-GM+TEQN5WhOygr1lp7skeVjdLPqqWMHsfzXrcHAqZJi/lIVh63H0kaRCY8MDhNWikx19zBUK8ceaLB7X5AH9NQ==",
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.8.tgz",
|
||||
"integrity": "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tinyrainbow": "^3.0.3"
|
||||
"tinyrainbow": "^3.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/runner": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.1.tgz",
|
||||
"integrity": "sha512-f7+FPy75vN91QGWsITueq0gedwUZy1fLtHOCMeQpjs8jTekAHeKP80zfDEnhrleviLHzVSDXIWuCIOFn3D3f8A==",
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.8.tgz",
|
||||
"integrity": "sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/utils": "4.1.1",
|
||||
"@vitest/utils": "4.1.8",
|
||||
"pathe": "^2.0.3"
|
||||
},
|
||||
"funding": {
|
||||
@@ -5156,14 +5146,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/snapshot": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.1.tgz",
|
||||
"integrity": "sha512-kMVSgcegWV2FibXEx9p9WIKgje58lcTbXgnJixfcg15iK8nzCXhmalL0ZLtTWLW9PH1+1NEDShiFFedB3tEgWg==",
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.8.tgz",
|
||||
"integrity": "sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "4.1.1",
|
||||
"@vitest/utils": "4.1.1",
|
||||
"@vitest/pretty-format": "4.1.8",
|
||||
"@vitest/utils": "4.1.8",
|
||||
"magic-string": "^0.30.21",
|
||||
"pathe": "^2.0.3"
|
||||
},
|
||||
@@ -5172,9 +5162,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/spy": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.1.tgz",
|
||||
"integrity": "sha512-6Ti/KT5OVaiupdIZEuZN7l3CZcR0cxnxt70Z0//3CtwgObwA6jZhmVBA3yrXSVN3gmwjgd7oDNLlsXz526gpRA==",
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.8.tgz",
|
||||
"integrity": "sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
@@ -5182,15 +5172,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/utils": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.1.tgz",
|
||||
"integrity": "sha512-cNxAlaB3sHoCdL6pj6yyUXv9Gry1NHNg0kFTXdvSIZXLHsqKH7chiWOkwJ5s5+d/oMwcoG9T0bKU38JZWKusrQ==",
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.8.tgz",
|
||||
"integrity": "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "4.1.1",
|
||||
"@vitest/pretty-format": "4.1.8",
|
||||
"convert-source-map": "^2.0.0",
|
||||
"tinyrainbow": "^3.0.3"
|
||||
"tinyrainbow": "^3.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
@@ -5223,9 +5213,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@wdio/config/node_modules/brace-expansion": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
|
||||
"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
|
||||
"integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -5591,9 +5581,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/archiver-utils/node_modules/brace-expansion": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
|
||||
"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
|
||||
"integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -6400,6 +6390,16 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/chai": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
|
||||
"integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
@@ -7882,9 +7882,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/eslint-plugin-import/node_modules/brace-expansion": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
||||
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
|
||||
"version": "1.1.15",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
|
||||
"integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -7979,9 +7979,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/eslint-plugin-json-schema-validator/node_modules/brace-expansion": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
|
||||
"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
|
||||
"integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -8045,9 +8045,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/eslint-plugin-n/node_modules/brace-expansion": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
|
||||
"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
|
||||
"integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -8206,9 +8206,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/eslint-plugin-react/node_modules/brace-expansion": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
||||
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
|
||||
"version": "1.1.15",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
|
||||
"integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -8408,9 +8408,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/eslint/node_modules/brace-expansion": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
||||
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
|
||||
"version": "1.1.15",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
|
||||
"integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -9244,9 +9244,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/globby/node_modules/brace-expansion": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
||||
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
|
||||
"version": "1.1.15",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
|
||||
"integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -10257,10 +10257,20 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz",
|
||||
"integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/puzrin"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/nodeca"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
@@ -10928,9 +10938,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "11.4.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.4.0.tgz",
|
||||
"integrity": "sha512-W+R+kFL4HgVxONq2bhXPi3bGpzGe/yEhVOp233qw9wCRtgncJ15P3bC+e4zZMu4Cq7d+WAJjXGW0uUkifhcatA==",
|
||||
"version": "11.5.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz",
|
||||
"integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"engines": {
|
||||
@@ -12650,9 +12660,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/readdir-glob/node_modules/brace-expansion": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
|
||||
"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
|
||||
"integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -13075,9 +13085,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz",
|
||||
"integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==",
|
||||
"version": "7.8.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz",
|
||||
"integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
@@ -15249,9 +15259,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "7.25.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz",
|
||||
"integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==",
|
||||
"version": "7.27.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-7.27.0.tgz",
|
||||
"integrity": "sha512-+t2Z/GwkZQDtu00813aP66ygViGtPHKhhoFZpQKpKrE+9jIgES+Zw+mFNaDWOVRKiuJjuqKHzD3B1sfGg8+ZOQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -16030,20 +16040,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vitest": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.1.tgz",
|
||||
"integrity": "sha512-yF+o4POL41rpAzj5KVILUxm1GCjKnELvaqmU9TLLUbMfDzuN0UpUR9uaDs+mCtjPe+uYPksXDRLQGGPvj1cTmA==",
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.8.tgz",
|
||||
"integrity": "sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vitest/expect": "4.1.1",
|
||||
"@vitest/mocker": "4.1.1",
|
||||
"@vitest/pretty-format": "4.1.1",
|
||||
"@vitest/runner": "4.1.1",
|
||||
"@vitest/snapshot": "4.1.1",
|
||||
"@vitest/spy": "4.1.1",
|
||||
"@vitest/utils": "4.1.1",
|
||||
"@vitest/expect": "4.1.8",
|
||||
"@vitest/mocker": "4.1.8",
|
||||
"@vitest/pretty-format": "4.1.8",
|
||||
"@vitest/runner": "4.1.8",
|
||||
"@vitest/snapshot": "4.1.8",
|
||||
"@vitest/spy": "4.1.8",
|
||||
"@vitest/utils": "4.1.8",
|
||||
"es-module-lexer": "^2.0.0",
|
||||
"expect-type": "^1.3.0",
|
||||
"magic-string": "^0.30.21",
|
||||
@@ -16054,7 +16063,7 @@
|
||||
"tinybench": "^2.9.0",
|
||||
"tinyexec": "^1.0.2",
|
||||
"tinyglobby": "^0.2.15",
|
||||
"tinyrainbow": "^3.0.3",
|
||||
"tinyrainbow": "^3.1.0",
|
||||
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0",
|
||||
"why-is-node-running": "^2.3.0"
|
||||
},
|
||||
@@ -16071,10 +16080,12 @@
|
||||
"@edge-runtime/vm": "*",
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
|
||||
"@vitest/browser-playwright": "4.1.1",
|
||||
"@vitest/browser-preview": "4.1.1",
|
||||
"@vitest/browser-webdriverio": "4.1.1",
|
||||
"@vitest/ui": "4.1.1",
|
||||
"@vitest/browser-playwright": "4.1.8",
|
||||
"@vitest/browser-preview": "4.1.8",
|
||||
"@vitest/browser-webdriverio": "4.1.8",
|
||||
"@vitest/coverage-istanbul": "4.1.8",
|
||||
"@vitest/coverage-v8": "4.1.8",
|
||||
"@vitest/ui": "4.1.8",
|
||||
"happy-dom": "*",
|
||||
"jsdom": "*",
|
||||
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||
@@ -16098,6 +16109,12 @@
|
||||
"@vitest/browser-webdriverio": {
|
||||
"optional": true
|
||||
},
|
||||
"@vitest/coverage-istanbul": {
|
||||
"optional": true
|
||||
},
|
||||
"@vitest/coverage-v8": {
|
||||
"optional": true
|
||||
},
|
||||
"@vitest/ui": {
|
||||
"optional": true
|
||||
},
|
||||
@@ -16206,9 +16223,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/webdriver/node_modules/undici": {
|
||||
"version": "6.25.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.25.0.tgz",
|
||||
"integrity": "sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==",
|
||||
"version": "6.26.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.26.0.tgz",
|
||||
"integrity": "sha512-4yqz8a3n5HmGTlsbADNtr/dJlhkh/55Rq798G6ibiULcXbDtaLpTl1pvdqcbFfeoj3iSi52lePFM7h9H21cw/A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
||||
10
package.json
10
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "obsidian-livesync",
|
||||
"version": "0.25.70-patch1",
|
||||
"version": "0.25.73",
|
||||
"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",
|
||||
@@ -80,9 +80,9 @@
|
||||
"@types/transform-pouch": "^1.0.6",
|
||||
"@typescript-eslint/eslint-plugin": "8.56.1",
|
||||
"@typescript-eslint/parser": "8.56.1",
|
||||
"@vitest/browser": "^4.1.1",
|
||||
"@vitest/browser-playwright": "^4.1.1",
|
||||
"@vitest/coverage-v8": "^4.1.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-plugin-inline-worker": "^0.1.1",
|
||||
@@ -119,7 +119,7 @@
|
||||
"typescript": "5.9.3",
|
||||
"vite": "^7.3.1",
|
||||
"vite-plugin-istanbul": "^8.0.0",
|
||||
"vitest": "^4.1.1",
|
||||
"vitest": "^4.1.8",
|
||||
"webdriverio": "^9.27.0",
|
||||
"yaml": "^2.8.2"
|
||||
},
|
||||
|
||||
@@ -105,7 +105,7 @@ class CLIWatchAdapter implements IStorageEventWatchAdapter {
|
||||
|
||||
private _toNodeFile(filePath: string, stats: Stats | undefined): NodeFile {
|
||||
return {
|
||||
path: path.relative(this.basePath, filePath) as FilePath,
|
||||
path: path.relative(this.basePath, filePath).replace(/\\/g, "/") as FilePath,
|
||||
stat: {
|
||||
ctime: stats?.ctimeMs ?? Date.now(),
|
||||
mtime: stats?.mtimeMs ?? Date.now(),
|
||||
@@ -117,7 +117,7 @@ class CLIWatchAdapter implements IStorageEventWatchAdapter {
|
||||
|
||||
private _toNodeFolder(dirPath: string): NodeFolder {
|
||||
return {
|
||||
path: path.relative(this.basePath, dirPath) as FilePath,
|
||||
path: path.relative(this.basePath, dirPath).replace(/\\/g, "/") as FilePath,
|
||||
isFolder: true,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { defineConfig } from "vite";
|
||||
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
||||
import path from "node:path";
|
||||
import { readFileSync } from "node:fs";
|
||||
const resolve = (...args: string[]) => path.resolve(...args).replace(/\\/g, "/");
|
||||
const packageJson = JSON.parse(readFileSync("../../../package.json", "utf-8"));
|
||||
const manifestJson = JSON.parse(readFileSync("../../../manifest.json", "utf-8"));
|
||||
// https://vite.dev/config/
|
||||
@@ -63,17 +64,14 @@ export default defineConfig({
|
||||
resolve: {
|
||||
alias: {
|
||||
"@lib/worker/bgWorker.ts": "../../lib/src/worker/bgWorker.mock.ts",
|
||||
"@lib/pouchdb/pouchdb-browser.ts": path.resolve(__dirname, "lib/pouchdb-node.ts"),
|
||||
"@lib/pouchdb/pouchdb-browser.ts": resolve(__dirname, "lib/pouchdb-node.ts"),
|
||||
// The CLI runs on Node.js; force AWS XML builder to its CJS Node entry
|
||||
// so Vite does not resolve the browser DOMParser-based XML parser.
|
||||
"@aws-sdk/xml-builder": path.resolve(
|
||||
__dirname,
|
||||
"../../../node_modules/@aws-sdk/xml-builder/dist-cjs/index.js"
|
||||
),
|
||||
"@aws-sdk/xml-builder": resolve(__dirname, "../../../node_modules/@aws-sdk/xml-builder/dist-cjs/index.js"),
|
||||
// Force fflate to the Node CJS entry; browser entry expects Web Worker globals.
|
||||
fflate: path.resolve(__dirname, "../../../node_modules/fflate/lib/node.cjs"),
|
||||
"@": path.resolve(__dirname, "../../"),
|
||||
"@lib": path.resolve(__dirname, "../../lib/src"),
|
||||
fflate: resolve(__dirname, "../../../node_modules/fflate/lib/node.cjs"),
|
||||
"@": resolve(__dirname, "../../"),
|
||||
"@lib": resolve(__dirname, "../../lib/src"),
|
||||
"../../src/worker/bgWorker.ts": "../../src/worker/bgWorker.mock.ts",
|
||||
},
|
||||
},
|
||||
@@ -85,7 +83,7 @@ export default defineConfig({
|
||||
minify: false,
|
||||
rollupOptions: {
|
||||
input: {
|
||||
index: path.resolve(__dirname, "entrypoint.ts"),
|
||||
index: resolve(__dirname, "entrypoint.ts"),
|
||||
},
|
||||
external: (id) => {
|
||||
if (defaultExternal.includes(id)) return true;
|
||||
@@ -101,7 +99,7 @@ export default defineConfig({
|
||||
},
|
||||
},
|
||||
lib: {
|
||||
entry: path.resolve(__dirname, "entrypoint.ts"),
|
||||
entry: resolve(__dirname, "entrypoint.ts"),
|
||||
formats: ["cjs"],
|
||||
fileName: "index",
|
||||
},
|
||||
|
||||
2
src/lib
2
src/lib
Submodule src/lib updated: 6f977537f4...76d91674c2
@@ -8,7 +8,6 @@ import { TestPaneView, VIEW_TYPE_TEST } from "./devUtil/TestPaneView.ts";
|
||||
import { writable } from "svelte/store";
|
||||
import type { FilePathWithPrefix } from "../../lib/src/common/types.ts";
|
||||
import type { LiveSyncCore } from "../../main.ts";
|
||||
|
||||
export class ModuleDev extends AbstractObsidianModule {
|
||||
_everyOnloadStart(): Promise<boolean> {
|
||||
__onMissingTranslation(() => {});
|
||||
@@ -98,6 +97,7 @@ export class ModuleDev extends AbstractObsidianModule {
|
||||
});
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
async _everyOnLayoutReady(): Promise<boolean> {
|
||||
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
||||
// if (await this.core.storageAccess.isExistsIncludeHidden("_SHOWDIALOGAUTO.md")) {
|
||||
|
||||
@@ -180,7 +180,7 @@
|
||||
autocapitalize="off"
|
||||
spellcheck="false"
|
||||
required
|
||||
pattern="^[a-z0-9][a-z0-9_]*$"
|
||||
pattern="^[a-z][a-z0-9_$()+/-]*$"
|
||||
bind:value={syncSetting.couchDB_DBNAME}
|
||||
/>
|
||||
</InputRow>
|
||||
|
||||
197
src/serviceFeatures/redFlag.simpleFetch.ts
Normal file
197
src/serviceFeatures/redFlag.simpleFetch.ts
Normal file
@@ -0,0 +1,197 @@
|
||||
import { LOG_LEVEL_NOTICE } from "octagonal-wheels/common/logger";
|
||||
import type { NecessaryServices } from "@lib/interfaces/ServiceModule";
|
||||
import { type LogFunction } from "@lib/services/lib/logUtils";
|
||||
import { UnresolvedErrorManager } from "@lib/services/base/UnresolvedErrorManager";
|
||||
import {
|
||||
ExtraOnLocal,
|
||||
ExtraOnRemote,
|
||||
FullScanModes,
|
||||
normaliseFullScanOptions,
|
||||
synchroniseAllFilesBetweenDBandStorage,
|
||||
type FullScanOptions,
|
||||
} from "@lib/serviceFeatures/offlineScanner";
|
||||
import { adjustSettingToRemoteIfNeeded, processVaultInitialisation } from "./redFlag";
|
||||
|
||||
export const SIMPLE_FETCH_STAGE1_REMOTE_WINS = "Overwrite all with remote files";
|
||||
export const SIMPLE_FETCH_STAGE1_NEWER_WINS = "Compare time and take newer";
|
||||
export const SIMPLE_FETCH_STAGE1_LEGACY = "Use the detailed flow";
|
||||
export const SIMPLE_FETCH_STAGE1_CANCEL = "Cancel";
|
||||
|
||||
export const SIMPLE_FETCH_STAGE2_REMOTE_DELETE_NONE = "Keep local files even if not on remote";
|
||||
export const SIMPLE_FETCH_STAGE2_REMOTE_DELETE_ALL = "Delete local files if not on remote";
|
||||
|
||||
export const SIMPLE_FETCH_STAGE2_NEWER_CLEANUP = "Delete local files if deleted on remote";
|
||||
export const SIMPLE_FETCH_STAGE2_NEWER_SYNC_ALL = "Keep local files even if deleted on remote";
|
||||
export const STAGE2_ABORT = "Cancel all and reboot";
|
||||
|
||||
export async function askSimpleFetchMode(
|
||||
host: NecessaryServices<"UI" | "vault", "storageAccess">
|
||||
): Promise<{ mode: string; options: Partial<FullScanOptions> } | "cancelled" | "aborted"> {
|
||||
const msg = `We are about to retrieve the remote data.
|
||||
|
||||
Firstly, how shall we handle the data retrieved from this remote server?
|
||||
|
||||
- **${SIMPLE_FETCH_STAGE1_NEWER_WINS}**: Compares the modified time of files and takes the newer one.
|
||||
If you have been using Self-hosted LiveSync and have made changes on multiple devices, this option may be suitable for you as it tries to merge changes based on modified time.
|
||||
- **${SIMPLE_FETCH_STAGE1_REMOTE_WINS}**: Remote data is the source of truth.
|
||||
If you are new to using Self-hosted LiveSync. This option may be easiest to understand and get started with.
|
||||
It will overwrite all your local files with the remote data, so please make sure you have a backup if there is any important data in your vault.
|
||||
- **${SIMPLE_FETCH_STAGE1_LEGACY}**: Opens the detailed setup wizard.
|
||||
If you want to have more control over the synchronisation process, or want to review the changes before applying, you can choose this option to use the detailed flow.
|
||||
`;
|
||||
const stage1 = await host.services.UI.confirm.confirmWithMessage(
|
||||
"Data retrieval scheduled",
|
||||
msg,
|
||||
[
|
||||
SIMPLE_FETCH_STAGE1_NEWER_WINS,
|
||||
SIMPLE_FETCH_STAGE1_REMOTE_WINS,
|
||||
SIMPLE_FETCH_STAGE1_LEGACY,
|
||||
SIMPLE_FETCH_STAGE1_CANCEL,
|
||||
],
|
||||
SIMPLE_FETCH_STAGE1_NEWER_WINS,
|
||||
0
|
||||
);
|
||||
|
||||
if (!stage1 || stage1 === SIMPLE_FETCH_STAGE1_CANCEL) return "cancelled";
|
||||
|
||||
if (stage1 === SIMPLE_FETCH_STAGE1_LEGACY) {
|
||||
return { mode: "legacy", options: {} };
|
||||
}
|
||||
|
||||
if (stage1 === SIMPLE_FETCH_STAGE1_REMOTE_WINS) {
|
||||
const msg = `Since you have chosen to overwrite all local files with remote data, **how would you like to handle local files that are not present in the remote database?**
|
||||
|
||||
- **${SIMPLE_FETCH_STAGE2_REMOTE_DELETE_ALL}**: Local-only files and remote-deleted files will be removed.
|
||||
This option will make your local vault exactly the same as the remote database, but please make sure you have a backup if there is any important data in your vault.
|
||||
- **${SIMPLE_FETCH_STAGE2_REMOTE_DELETE_NONE}**: All existing local files will be preserved.
|
||||
This option will keep all your local files, but it may cause duplicates if there are files that exist on local but not on remote. You can clean up these duplicates manually after the synchronisation.`;
|
||||
|
||||
const stage2 = await host.services.UI.confirm.confirmWithMessage(
|
||||
"How to handle extra existing local files?",
|
||||
msg,
|
||||
[SIMPLE_FETCH_STAGE2_REMOTE_DELETE_ALL, SIMPLE_FETCH_STAGE2_REMOTE_DELETE_NONE, STAGE2_ABORT],
|
||||
SIMPLE_FETCH_STAGE2_REMOTE_DELETE_NONE,
|
||||
0
|
||||
);
|
||||
if (!stage2) return "cancelled";
|
||||
if (stage2 === STAGE2_ABORT) {
|
||||
return "aborted";
|
||||
}
|
||||
return {
|
||||
mode: "remote-only",
|
||||
options: {
|
||||
mode: FullScanModes.DB_APPLY,
|
||||
extraOnRemote:
|
||||
stage2 === SIMPLE_FETCH_STAGE2_REMOTE_DELETE_ALL ? ExtraOnRemote.DELETE_LOCAL_MISSING : undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (stage1 === SIMPLE_FETCH_STAGE1_NEWER_WINS) {
|
||||
const msg = `How should files that were deleted on other devices be handled?
|
||||
|
||||
- **${SIMPLE_FETCH_STAGE2_NEWER_CLEANUP}**: Delete local files if they were deleted on remote.
|
||||
This is useful if you want to keep your vault clean and consistent across devices, but please make sure you have a backup if there is already any important data in your vault.
|
||||
- **${SIMPLE_FETCH_STAGE2_NEWER_SYNC_ALL}**: Recreate remote files even if they were deleted on remote.
|
||||
This option will keep all your local files, but it may cause duplicates if there are files that exist on local but not on remote. You can clean up these duplicates manually after the synchronisation.
|
||||
`;
|
||||
|
||||
const stage2 = await host.services.UI.confirm.confirmWithMessage(
|
||||
"Conflict & Deletion Options",
|
||||
msg,
|
||||
[SIMPLE_FETCH_STAGE2_NEWER_CLEANUP, SIMPLE_FETCH_STAGE2_NEWER_SYNC_ALL, STAGE2_ABORT],
|
||||
SIMPLE_FETCH_STAGE2_NEWER_SYNC_ALL,
|
||||
0
|
||||
);
|
||||
if (!stage2) return "cancelled";
|
||||
if (stage2 === STAGE2_ABORT) {
|
||||
return "aborted";
|
||||
}
|
||||
return {
|
||||
mode: "newer-wins",
|
||||
options: {
|
||||
mode: FullScanModes.NEWER_WINS,
|
||||
extraOnLocal:
|
||||
stage2 === SIMPLE_FETCH_STAGE2_NEWER_CLEANUP
|
||||
? ExtraOnLocal.DELETE_DB_DELETED
|
||||
: ExtraOnLocal.APPEND_STORAGE_ONLY,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return "cancelled";
|
||||
}
|
||||
|
||||
const RERUN_PROCESS = "Reboot to re-run the process";
|
||||
const RELEASE_FLAG_PROCESS = "Finalise the process and resume normal operation";
|
||||
export async function askAndPerformFastSetupOnScheduledFetchAll(
|
||||
host: NecessaryServices<
|
||||
| "vault"
|
||||
| "fileProcessing"
|
||||
| "tweakValue"
|
||||
| "UI"
|
||||
| "setting"
|
||||
| "appLifecycle"
|
||||
| "path"
|
||||
| "keyValueDB"
|
||||
| "database",
|
||||
"storageAccess" | "rebuilder" | "fileHandler"
|
||||
>,
|
||||
log: LogFunction,
|
||||
cleanupFlag: () => Promise<void>
|
||||
): Promise<boolean | undefined> {
|
||||
const result = await askSimpleFetchMode(host);
|
||||
if (result === "cancelled") {
|
||||
log("Fetch cancelled by user.", LOG_LEVEL_NOTICE);
|
||||
await cleanupFlag();
|
||||
host.services.appLifecycle.performRestart();
|
||||
return false;
|
||||
}
|
||||
if (result === "aborted") {
|
||||
log("Fetch exited by user.", LOG_LEVEL_NOTICE);
|
||||
host.services.appLifecycle.performRestart();
|
||||
return false;
|
||||
}
|
||||
if (result.mode === "legacy") {
|
||||
return undefined; // Let the legacy flow handle it.
|
||||
}
|
||||
|
||||
return await processVaultInitialisation(host, log, async () => {
|
||||
const settings = host.services.setting.currentSettings();
|
||||
await adjustSettingToRemoteIfNeeded(host, log, { preventFetchingConfig: false }, settings);
|
||||
// 1. Perform fast DB fetch (download remote DB content to local DB)
|
||||
await host.serviceModules.rebuilder.$fetchLocalDBFast(false);
|
||||
|
||||
// 2. Call the extended synchroniseAllFilesBetweenDBandStorage to reflect changes in storage
|
||||
const errorManager = new UnresolvedErrorManager(host.services.appLifecycle);
|
||||
const syncResult = await synchroniseAllFilesBetweenDBandStorage(
|
||||
host,
|
||||
log,
|
||||
errorManager,
|
||||
normaliseFullScanOptions({
|
||||
...result.options,
|
||||
showingNotice: true,
|
||||
omitEvents: true,
|
||||
ignoreSuspending: true,
|
||||
})
|
||||
);
|
||||
if (!syncResult) {
|
||||
const canRelease = await host.services.UI.confirm.askSelectStringDialogue(
|
||||
"Some files failed to synchronise. What would you like to do?",
|
||||
[RERUN_PROCESS, RELEASE_FLAG_PROCESS],
|
||||
{ defaultAction: RELEASE_FLAG_PROCESS, title: "Synchronisation Issues Detected" }
|
||||
);
|
||||
if (canRelease === RERUN_PROCESS) {
|
||||
log("User chose to reboot and re-run the process.", LOG_LEVEL_NOTICE);
|
||||
// Prevent to delete the flag, so that the process will be re-run after reboot.
|
||||
// await cleanupFlag();
|
||||
host.services.appLifecycle.performRestart();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
await host.serviceModules.rebuilder.finishRebuild();
|
||||
await cleanupFlag();
|
||||
log("Simple fetch and scan operation completed.", LOG_LEVEL_NOTICE);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
@@ -12,6 +12,9 @@ import type {
|
||||
FetchEverythingResult,
|
||||
RebuildEverythingResult,
|
||||
} from "@/modules/features/SetupWizard/dialogs/setupDialogTypes";
|
||||
import { askAndPerformFastSetupOnScheduledFetchAll } from "./redFlag.simpleFetch";
|
||||
import { ConnectionStringParser } from "@lib/common/ConnectionString";
|
||||
import { activateRemoteConfiguration } from "@lib/serviceFeatures/remoteConfig";
|
||||
|
||||
/**
|
||||
* Flag file handler interface, similar to target filter pattern.
|
||||
@@ -45,14 +48,79 @@ export async function deleteFlagFile(host: NecessaryServices<never, "storageAcce
|
||||
log(ex, LOG_LEVEL_VERBOSE);
|
||||
}
|
||||
}
|
||||
const REMOTE_KEEP_CURRENT = "Use active remote";
|
||||
const REMOTE_CANCEL = "Cancel";
|
||||
async function askAndActivateRemoteDatabase(host: NecessaryServices<"UI" | "setting", any>, log: LogFunction) {
|
||||
const settings = host.services.setting.currentSettings();
|
||||
if (settings.remoteConfigurations && Object.keys(settings.remoteConfigurations).length > 1) {
|
||||
const message =
|
||||
"Multiple remote configurations detected. Please select the remote configuration you want to fetch from.";
|
||||
const options = Object.entries(settings.remoteConfigurations).map(([id, config]) => {
|
||||
const parsed = ConnectionStringParser.parse(config.uri);
|
||||
const displayURI = (config.uri.split("@").pop() || "").substring(0, 20) + "..."; // Show only the last part of URI for better readability and privacy.
|
||||
return {
|
||||
name: `${config.name} - ${parsed.type} (${displayURI})`,
|
||||
id: id,
|
||||
};
|
||||
});
|
||||
options.push({
|
||||
name: REMOTE_KEEP_CURRENT,
|
||||
id: "keep_current",
|
||||
});
|
||||
options.push({
|
||||
name: REMOTE_CANCEL,
|
||||
id: "cancel",
|
||||
});
|
||||
|
||||
const selections = options.map((option) => option.name);
|
||||
// const defaultAction =
|
||||
// options.find((option) => option.id === settings.activeConfigurationId)?.name || selections[0];
|
||||
const selectedId = await host.services.UI.confirm.askSelectStringDialogue(message, selections, {
|
||||
title: "Select Remote Configuration",
|
||||
defaultAction: REMOTE_KEEP_CURRENT,
|
||||
});
|
||||
const selectedConfig = options.find((option) => option.name === selectedId);
|
||||
if (selectedConfig) {
|
||||
if (selectedConfig.id === "keep_current") {
|
||||
log(`Keeping current remote configuration.`, LOG_LEVEL_INFO);
|
||||
return true;
|
||||
}
|
||||
if (selectedConfig.id === "cancel") {
|
||||
log(`Remote configuration selection cancelled.`, LOG_LEVEL_NOTICE);
|
||||
return false;
|
||||
}
|
||||
const activated = activateRemoteConfiguration(settings, selectedConfig.id);
|
||||
if (activated) {
|
||||
await host.services.setting.applyPartial(activated);
|
||||
log(`Activated remote configuration: ${selectedConfig.name}`, LOG_LEVEL_INFO);
|
||||
return true;
|
||||
} else {
|
||||
log(`Failed to activate remote configuration: ${selectedConfig.name}`, LOG_LEVEL_NOTICE);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
log(`No remote configuration selected.`, LOG_LEVEL_NOTICE);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true; // If there is only one or no remote configuration, proceed without asking.
|
||||
}
|
||||
/**
|
||||
* Factory function to create a fetch all flag handler.
|
||||
* All logic related to fetch all flag is encapsulated here.
|
||||
*/
|
||||
export function createFetchAllFlagHandler(
|
||||
host: NecessaryServices<
|
||||
"vault" | "fileProcessing" | "tweakValue" | "UI" | "setting" | "appLifecycle",
|
||||
"storageAccess" | "rebuilder"
|
||||
| "vault"
|
||||
| "fileProcessing"
|
||||
| "tweakValue"
|
||||
| "UI"
|
||||
| "setting"
|
||||
| "appLifecycle"
|
||||
| "path"
|
||||
| "keyValueDB"
|
||||
| "database",
|
||||
"storageAccess" | "rebuilder" | "fileHandler"
|
||||
>,
|
||||
log: LogFunction
|
||||
): FlagFileHandler {
|
||||
@@ -69,6 +137,19 @@ export function createFetchAllFlagHandler(
|
||||
|
||||
// Handle the fetch all scheduled operation
|
||||
const onScheduled = async () => {
|
||||
// Select the remote database if there are multiple remotes configured.
|
||||
const isRemoteActivated = await askAndActivateRemoteDatabase(host, log);
|
||||
if (!isRemoteActivated) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ask user for use Fast Setup
|
||||
const useFastSetup = await askAndPerformFastSetupOnScheduledFetchAll(host, log, cleanupFlag);
|
||||
if (useFastSetup !== undefined) {
|
||||
return useFastSetup;
|
||||
}
|
||||
// if useFastSetup is undefined, it means user choose to proceed with normal fetch process, so continue to ask for fetch method.
|
||||
|
||||
const method =
|
||||
await host.services.UI.dialogManager.openWithExplicitCancel<FetchEverythingResult>(FetchEverything);
|
||||
if (method === "cancelled") {
|
||||
@@ -380,8 +461,17 @@ export function flagHandlerToEventHandler(flagHandler: FlagFileHandler) {
|
||||
|
||||
export function useRedFlagFeatures(
|
||||
host: NecessaryServices<
|
||||
"API" | "appLifecycle" | "UI" | "setting" | "tweakValue" | "fileProcessing" | "vault",
|
||||
"storageAccess" | "rebuilder"
|
||||
| "API"
|
||||
| "appLifecycle"
|
||||
| "UI"
|
||||
| "setting"
|
||||
| "tweakValue"
|
||||
| "fileProcessing"
|
||||
| "vault"
|
||||
| "path"
|
||||
| "keyValueDB"
|
||||
| "database",
|
||||
"storageAccess" | "rebuilder" | "fileHandler"
|
||||
>
|
||||
) {
|
||||
const log = createInstanceLogFunction("SF:RedFlag", host.services.API);
|
||||
|
||||
@@ -19,6 +19,45 @@ import {
|
||||
TweakValuesShouldMatchedTemplate,
|
||||
TweakValuesTemplate,
|
||||
} from "@/lib/src/common/types";
|
||||
import {
|
||||
ExtraOnLocal,
|
||||
FullScanModes,
|
||||
synchroniseAllFilesBetweenDBandStorage,
|
||||
} from "@/lib/src/serviceFeatures/offlineScanner";
|
||||
import {
|
||||
SIMPLE_FETCH_STAGE1_LEGACY,
|
||||
SIMPLE_FETCH_STAGE1_NEWER_WINS,
|
||||
SIMPLE_FETCH_STAGE1_REMOTE_WINS,
|
||||
SIMPLE_FETCH_STAGE2_NEWER_CLEANUP,
|
||||
SIMPLE_FETCH_STAGE2_NEWER_SYNC_ALL,
|
||||
SIMPLE_FETCH_STAGE2_REMOTE_DELETE_NONE,
|
||||
SIMPLE_FETCH_STAGE2_REMOTE_DELETE_ALL,
|
||||
STAGE2_ABORT,
|
||||
askAndPerformFastSetupOnScheduledFetchAll,
|
||||
askSimpleFetchMode,
|
||||
} from "./redFlag.simpleFetch";
|
||||
import { activateRemoteConfiguration } from "@lib/serviceFeatures/remoteConfig";
|
||||
//Mock synchroniseAllFilesBetweenDBandStorage
|
||||
vi.mock("@/lib/src/serviceFeatures/offlineScanner", async (importOriginal) => {
|
||||
const originalModule = (await importOriginal()) as any;
|
||||
return {
|
||||
...originalModule,
|
||||
synchroniseAllFilesBetweenDBandStorage: vi.fn(() => Promise.resolve(true)),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@lib/serviceFeatures/remoteConfig", () => {
|
||||
return {
|
||||
activateRemoteConfiguration: vi.fn((settings: any, configurationId: string) => {
|
||||
if (!settings?.remoteConfigurations?.[configurationId]) return false;
|
||||
return {
|
||||
activeConfigurationId: configurationId,
|
||||
remoteType: settings.remoteConfigurations[configurationId].remoteType ?? settings.remoteType,
|
||||
remoteURI: settings.remoteConfigurations[configurationId].uri,
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
// Mock types and functions
|
||||
const createLoggerMock = (): LogFunction => {
|
||||
@@ -68,6 +107,9 @@ const createAppLifecycleMock = () => {
|
||||
onLayoutReady: {
|
||||
addHandler: vi.fn(),
|
||||
},
|
||||
getUnresolvedMessages: {
|
||||
addHandler: vi.fn(),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -79,6 +121,7 @@ const createUIServiceMock = () => {
|
||||
confirm: {
|
||||
askSelectStringDialogue: vi.fn(),
|
||||
askYesNoDialog: vi.fn(),
|
||||
confirmWithMessage: vi.fn(),
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -86,7 +129,9 @@ const createUIServiceMock = () => {
|
||||
const createRebuilderMock = () => {
|
||||
return {
|
||||
$fetchLocal: vi.fn(async () => {}),
|
||||
$fetchLocalDBFast: vi.fn(async () => {}),
|
||||
$rebuildEverything: vi.fn(async () => {}),
|
||||
finishRebuild: vi.fn(async () => {}),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -389,6 +434,394 @@ describe("Red Flag Feature", () => {
|
||||
const handler = createFetchAllFlagHandler(host as any, log);
|
||||
expect(handler.priority).toBe(10);
|
||||
});
|
||||
|
||||
it("should use simplified remote-only mode and call performFullScan", async () => {
|
||||
const host = createHostMock();
|
||||
const log = createLoggerMock();
|
||||
|
||||
host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL);
|
||||
// Stage 1: Overwrite all with remote files
|
||||
// Stage 2: Delete local files if not on remote (Clean overwrite)
|
||||
host.mocks.ui.confirm.confirmWithMessage
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_REMOTE_WINS)
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE2_REMOTE_DELETE_ALL);
|
||||
|
||||
host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({
|
||||
batchSave: false,
|
||||
} as any);
|
||||
|
||||
const handler = createFetchAllFlagHandler(host as any, log);
|
||||
const result = await handler.handle();
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(host.mocks.rebuilder.$fetchLocalDBFast).toHaveBeenCalled();
|
||||
expect(synchroniseAllFilesBetweenDBandStorage).toHaveBeenCalled();
|
||||
// We can't easily check performFullScan call here because it's imported,
|
||||
// but we can verify rebuilder was called.
|
||||
});
|
||||
|
||||
it("should restore legacy fetch flow when requested", async () => {
|
||||
const host = createHostMock();
|
||||
const log = createLoggerMock();
|
||||
|
||||
host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL);
|
||||
host.mocks.ui.confirm.confirmWithMessage.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_LEGACY);
|
||||
host.mocks.ui.dialogManager.openWithExplicitCancel.mockResolvedValueOnce({
|
||||
vault: "identical",
|
||||
backup: "backup_skipped",
|
||||
extra: { preventFetchingConfig: false },
|
||||
});
|
||||
host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({
|
||||
batchSave: false,
|
||||
} as any);
|
||||
const handler = createFetchAllFlagHandler(host as any, log);
|
||||
const result = await handler.handle();
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(host.mocks.ui.dialogManager.openWithExplicitCancel).toHaveBeenCalled();
|
||||
expect(host.mocks.rebuilder.$fetchLocal).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should cancel fetch flow when first quick step is cancelled", async () => {
|
||||
const host = createHostMock();
|
||||
const log = createLoggerMock();
|
||||
|
||||
host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL);
|
||||
host.mocks.ui.confirm.confirmWithMessage.mockResolvedValueOnce(false);
|
||||
|
||||
const handler = createFetchAllFlagHandler(host as any, log);
|
||||
host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({
|
||||
batchSave: false,
|
||||
} as any);
|
||||
const result = await handler.handle();
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(host.mocks.rebuilder.$fetchLocal).not.toHaveBeenCalled();
|
||||
expect(host.mocks.appLifecycle.performRestart).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should use remote-authoritative quick mode for empty vault", async () => {
|
||||
const host = createHostMock();
|
||||
const log = createLoggerMock();
|
||||
|
||||
host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL);
|
||||
host.mocks.ui.confirm.confirmWithMessage
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_REMOTE_WINS)
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE2_REMOTE_DELETE_ALL);
|
||||
|
||||
host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({
|
||||
batchSave: false,
|
||||
} as any);
|
||||
const handler = createFetchAllFlagHandler(host as any, log);
|
||||
const result = await handler.handle();
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(host.mocks.rebuilder.$fetchLocalDBFast).toHaveBeenCalled();
|
||||
expect(host.mocks.rebuilder.$fetchLocal).not.toHaveBeenCalledWith(false, true);
|
||||
});
|
||||
|
||||
it("should keep current remote configuration when selected", async () => {
|
||||
const host = createHostMock();
|
||||
const log = createLoggerMock();
|
||||
|
||||
host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL);
|
||||
Object.assign(host.mocks.setting.settings, {
|
||||
remoteConfigurations: {
|
||||
alpha: { name: "Alpha", uri: "sls+https://user:pass@example.com/db1" },
|
||||
beta: { name: "Beta", uri: "sls+https://user:pass@example.com/db2" },
|
||||
},
|
||||
});
|
||||
host.mocks.ui.confirm.askSelectStringDialogue.mockResolvedValueOnce("Use active remote");
|
||||
host.mocks.ui.confirm.confirmWithMessage.mockResolvedValueOnce(false);
|
||||
|
||||
const handler = createFetchAllFlagHandler(host as any, log);
|
||||
const result = await handler.handle();
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(host.mocks.setting.applyPartial).not.toHaveBeenCalledWith(
|
||||
expect.objectContaining({ activeConfigurationId: expect.any(String) })
|
||||
);
|
||||
});
|
||||
|
||||
it("should stop when remote selection is cancelled", async () => {
|
||||
const host = createHostMock();
|
||||
const log = createLoggerMock();
|
||||
|
||||
host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL);
|
||||
Object.assign(host.mocks.setting.settings, {
|
||||
remoteConfigurations: {
|
||||
alpha: { name: "Alpha", uri: "sls+https://user:pass@example.com/db1" },
|
||||
beta: { name: "Beta", uri: "sls+https://user:pass@example.com/db2" },
|
||||
},
|
||||
});
|
||||
host.mocks.ui.confirm.askSelectStringDialogue.mockResolvedValueOnce("Cancel");
|
||||
|
||||
const handler = createFetchAllFlagHandler(host as any, log);
|
||||
const result = await handler.handle();
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(host.mocks.ui.confirm.confirmWithMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should activate selected remote configuration", async () => {
|
||||
const host = createHostMock();
|
||||
const log = createLoggerMock();
|
||||
|
||||
host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL);
|
||||
Object.assign(host.mocks.setting.settings, {
|
||||
remoteConfigurations: {
|
||||
alpha: {
|
||||
name: "Alpha",
|
||||
uri: "sls+https://user:pass@example.com/db1",
|
||||
remoteType: "CouchDB",
|
||||
},
|
||||
beta: {
|
||||
name: "Beta",
|
||||
uri: "sls+https://user:pass@example.com/db2",
|
||||
remoteType: "CouchDB",
|
||||
},
|
||||
},
|
||||
});
|
||||
host.mocks.ui.confirm.askSelectStringDialogue.mockImplementationOnce(
|
||||
async (_message: string, selections: string[]) => selections.find((e) => e.startsWith("Beta -"))
|
||||
);
|
||||
host.mocks.ui.confirm.confirmWithMessage.mockResolvedValueOnce(false);
|
||||
|
||||
const handler = createFetchAllFlagHandler(host as any, log);
|
||||
const result = await handler.handle();
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(activateRemoteConfiguration).toHaveBeenCalledWith(host.mocks.setting.settings, "beta");
|
||||
expect(host.mocks.setting.applyPartial).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ activeConfigurationId: "beta" })
|
||||
);
|
||||
});
|
||||
|
||||
it("should stop when selected remote name is unknown", async () => {
|
||||
const host = createHostMock();
|
||||
const log = createLoggerMock();
|
||||
|
||||
host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL);
|
||||
Object.assign(host.mocks.setting.settings, {
|
||||
remoteConfigurations: {
|
||||
alpha: { name: "Alpha", uri: "sls+https://user:pass@example.com/db1" },
|
||||
beta: { name: "Beta", uri: "sls+https://user:pass@example.com/db2" },
|
||||
},
|
||||
});
|
||||
host.mocks.ui.confirm.askSelectStringDialogue.mockResolvedValueOnce("Unknown option");
|
||||
|
||||
const handler = createFetchAllFlagHandler(host as any, log);
|
||||
const result = await handler.handle();
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(host.mocks.ui.confirm.confirmWithMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should stop when remote activation fails", async () => {
|
||||
const host = createHostMock();
|
||||
const log = createLoggerMock();
|
||||
|
||||
host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL);
|
||||
Object.assign(host.mocks.setting.settings, {
|
||||
remoteConfigurations: {
|
||||
alpha: { name: "Alpha", uri: "sls+https://user:pass@example.com/db1" },
|
||||
beta: { name: "Beta", uri: "sls+https://user:pass@example.com/db2" },
|
||||
},
|
||||
});
|
||||
host.mocks.ui.confirm.askSelectStringDialogue.mockImplementationOnce(
|
||||
async (_message: string, selections: string[]) => selections.find((e) => e.startsWith("Beta -"))
|
||||
);
|
||||
(activateRemoteConfiguration as any).mockReturnValueOnce(false);
|
||||
|
||||
const handler = createFetchAllFlagHandler(host as any, log);
|
||||
const result = await handler.handle();
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(host.mocks.ui.confirm.confirmWithMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("askSimpleFetchMode", () => {
|
||||
it("should return cancelled when stage1 is cancelled", async () => {
|
||||
const host = createHostMock();
|
||||
host.mocks.ui.confirm.confirmWithMessage.mockResolvedValueOnce(false);
|
||||
|
||||
await expect(askSimpleFetchMode(host as any)).resolves.toBe("cancelled");
|
||||
});
|
||||
|
||||
it("should return legacy mode when selected", async () => {
|
||||
const host = createHostMock();
|
||||
host.mocks.ui.confirm.confirmWithMessage.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_LEGACY);
|
||||
|
||||
await expect(askSimpleFetchMode(host as any)).resolves.toEqual({ mode: "legacy", options: {} });
|
||||
});
|
||||
|
||||
it("should return remote-only with keep-local option", async () => {
|
||||
const host = createHostMock();
|
||||
host.mocks.ui.confirm.confirmWithMessage
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_REMOTE_WINS)
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE2_REMOTE_DELETE_NONE);
|
||||
|
||||
await expect(askSimpleFetchMode(host as any)).resolves.toEqual({
|
||||
mode: "remote-only",
|
||||
options: {
|
||||
mode: FullScanModes.DB_APPLY,
|
||||
extraOnRemote: undefined,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should return cancelled when remote-only stage2 is cancelled", async () => {
|
||||
const host = createHostMock();
|
||||
host.mocks.ui.confirm.confirmWithMessage
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_REMOTE_WINS)
|
||||
.mockResolvedValueOnce(false);
|
||||
|
||||
await expect(askSimpleFetchMode(host as any)).resolves.toBe("cancelled");
|
||||
});
|
||||
|
||||
it("should return aborted when remote-only stage2 aborts", async () => {
|
||||
const host = createHostMock();
|
||||
host.mocks.ui.confirm.confirmWithMessage
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_REMOTE_WINS)
|
||||
.mockResolvedValueOnce(STAGE2_ABORT);
|
||||
|
||||
await expect(askSimpleFetchMode(host as any)).resolves.toBe("aborted");
|
||||
});
|
||||
|
||||
it("should return newer-wins cleanup option", async () => {
|
||||
const host = createHostMock();
|
||||
host.mocks.ui.confirm.confirmWithMessage
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_NEWER_WINS)
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE2_NEWER_CLEANUP);
|
||||
|
||||
await expect(askSimpleFetchMode(host as any)).resolves.toEqual({
|
||||
mode: "newer-wins",
|
||||
options: {
|
||||
mode: FullScanModes.NEWER_WINS,
|
||||
extraOnLocal: ExtraOnLocal.DELETE_DB_DELETED,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should return newer-wins keep-all option", async () => {
|
||||
const host = createHostMock();
|
||||
host.mocks.ui.confirm.confirmWithMessage
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_NEWER_WINS)
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE2_NEWER_SYNC_ALL);
|
||||
|
||||
await expect(askSimpleFetchMode(host as any)).resolves.toEqual({
|
||||
mode: "newer-wins",
|
||||
options: {
|
||||
mode: FullScanModes.NEWER_WINS,
|
||||
extraOnLocal: ExtraOnLocal.APPEND_STORAGE_ONLY,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should return cancelled when newer-wins stage2 is cancelled", async () => {
|
||||
const host = createHostMock();
|
||||
host.mocks.ui.confirm.confirmWithMessage
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_NEWER_WINS)
|
||||
.mockResolvedValueOnce(false);
|
||||
|
||||
await expect(askSimpleFetchMode(host as any)).resolves.toBe("cancelled");
|
||||
});
|
||||
|
||||
it("should return aborted when newer-wins stage2 aborts", async () => {
|
||||
const host = createHostMock();
|
||||
host.mocks.ui.confirm.confirmWithMessage
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_NEWER_WINS)
|
||||
.mockResolvedValueOnce(STAGE2_ABORT);
|
||||
|
||||
await expect(askSimpleFetchMode(host as any)).resolves.toBe("aborted");
|
||||
});
|
||||
});
|
||||
|
||||
describe("askAndPerformFastSetupOnScheduledFetchAll", () => {
|
||||
it("should return false and cleanup when quick flow is cancelled", async () => {
|
||||
const host = createHostMock();
|
||||
const log = createLoggerMock();
|
||||
const cleanupFlag = vi.fn().mockResolvedValue(undefined);
|
||||
|
||||
host.mocks.ui.confirm.confirmWithMessage.mockResolvedValueOnce(false);
|
||||
|
||||
const result = await askAndPerformFastSetupOnScheduledFetchAll(host as any, log, cleanupFlag);
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(cleanupFlag).toHaveBeenCalled();
|
||||
expect(host.mocks.appLifecycle.performRestart).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should return false without cleanup when quick flow is aborted", async () => {
|
||||
const host = createHostMock();
|
||||
const log = createLoggerMock();
|
||||
const cleanupFlag = vi.fn().mockResolvedValue(undefined);
|
||||
|
||||
host.mocks.ui.confirm.confirmWithMessage
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_REMOTE_WINS)
|
||||
.mockResolvedValueOnce(STAGE2_ABORT);
|
||||
|
||||
const result = await askAndPerformFastSetupOnScheduledFetchAll(host as any, log, cleanupFlag);
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(cleanupFlag).not.toHaveBeenCalled();
|
||||
expect(host.mocks.appLifecycle.performRestart).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should return undefined when legacy mode is selected", async () => {
|
||||
const host = createHostMock();
|
||||
const log = createLoggerMock();
|
||||
const cleanupFlag = vi.fn().mockResolvedValue(undefined);
|
||||
|
||||
host.mocks.ui.confirm.confirmWithMessage.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_LEGACY);
|
||||
|
||||
const result = await askAndPerformFastSetupOnScheduledFetchAll(host as any, log, cleanupFlag);
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
expect(host.mocks.rebuilder.$fetchLocalDBFast).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should reboot and return false when sync has failures and user chooses rerun", async () => {
|
||||
const host = createHostMock();
|
||||
const log = createLoggerMock();
|
||||
const cleanupFlag = vi.fn().mockResolvedValue(undefined);
|
||||
|
||||
host.mocks.ui.confirm.confirmWithMessage
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_REMOTE_WINS)
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE2_REMOTE_DELETE_ALL);
|
||||
host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({ batchSave: false } as any);
|
||||
(synchroniseAllFilesBetweenDBandStorage as any).mockResolvedValueOnce(false);
|
||||
host.mocks.ui.confirm.askSelectStringDialogue.mockResolvedValueOnce("Reboot to re-run the process");
|
||||
|
||||
const result = await askAndPerformFastSetupOnScheduledFetchAll(host as any, log, cleanupFlag);
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(host.mocks.appLifecycle.performRestart).toHaveBeenCalled();
|
||||
expect(cleanupFlag).not.toHaveBeenCalled();
|
||||
expect(host.mocks.rebuilder.finishRebuild).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should continue and finalise when sync has failures but user releases flag", async () => {
|
||||
const host = createHostMock();
|
||||
const log = createLoggerMock();
|
||||
const cleanupFlag = vi.fn().mockResolvedValue(undefined);
|
||||
|
||||
host.mocks.ui.confirm.confirmWithMessage
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_REMOTE_WINS)
|
||||
.mockResolvedValueOnce(SIMPLE_FETCH_STAGE2_REMOTE_DELETE_ALL);
|
||||
host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({ batchSave: false } as any);
|
||||
(synchroniseAllFilesBetweenDBandStorage as any).mockResolvedValueOnce(false);
|
||||
host.mocks.ui.confirm.askSelectStringDialogue.mockResolvedValueOnce(
|
||||
"Finalise the process and resume normal operation"
|
||||
);
|
||||
|
||||
const result = await askAndPerformFastSetupOnScheduledFetchAll(host as any, log, cleanupFlag);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(host.mocks.rebuilder.finishRebuild).toHaveBeenCalled();
|
||||
expect(cleanupFlag).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Rebuild All Flag Handler", () => {
|
||||
@@ -980,6 +1413,8 @@ describe("Red Flag Feature", () => {
|
||||
const log = createLoggerMock();
|
||||
|
||||
host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL);
|
||||
host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({});
|
||||
host.mocks.ui.confirm.confirmWithMessage.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_LEGACY);
|
||||
host.mocks.ui.dialogManager.openWithExplicitCancel.mockResolvedValueOnce("cancelled");
|
||||
|
||||
const handler = createFetchAllFlagHandler(host as any, log);
|
||||
@@ -1056,11 +1491,12 @@ describe("Red Flag Feature", () => {
|
||||
it("should handle fetchAll flag with flagHandlerToEventHandler identical", async () => {
|
||||
const host = createHostMock();
|
||||
const log = createLoggerMock();
|
||||
host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({
|
||||
host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValue({
|
||||
customChunkSize: 1,
|
||||
} as any);
|
||||
|
||||
host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL);
|
||||
host.mocks.ui.confirm.confirmWithMessage.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_LEGACY);
|
||||
host.mocks.ui.dialogManager.openWithExplicitCancel.mockResolvedValueOnce({ vault: "identical", extra: {} });
|
||||
host.mocks.rebuilder.$fetchLocal.mockResolvedValueOnce();
|
||||
const handler = createFetchAllFlagHandler(host as any, log);
|
||||
|
||||
239
updates.md
239
updates.md
@@ -3,27 +3,38 @@ Since 19th July, 2025 (beta1 in 0.25.0-beta1, 13th July, 2025)
|
||||
|
||||
The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsidian-livesync/blob/main/updates_old.md). Because 0.25 got a lot of updates, thankfully, compatibility is kept and we do not need breaking changes! In other words, when get enough stabled. The next version will be v1.0.0. Even though it my hope.
|
||||
|
||||
## Unreleased
|
||||
## 0.25.73
|
||||
|
||||
1st June, 2026 (draft)
|
||||
4th June, 2026
|
||||
|
||||
### Fixed
|
||||
- No longer the status element breaks other plugins' interaction (#930).
|
||||
|
||||
## 0.25.70-patch1
|
||||
- Adjust CouchDB's database name checking to its specification (#926).
|
||||
- `Reset Syncronisation on This Device` for minio and P2P is now working properly.
|
||||
|
||||
1st June, 2026
|
||||
## ~~0.25.71~~ 0.25.72
|
||||
|
||||
This release does not include any changes to behaviour (if everything is as intended).
|
||||
However, this release had addressed a large number of errors and potential issues caused by the switch to a modern ESLint configuration, as well as unintended log output.
|
||||
I have also separated out some parts where the type definitions were a bit loose.
|
||||
0.25.71 was cancelled due to the fixes needed (Object Storage related)
|
||||
|
||||
As the diff has become too large, I am releasing it as a beta.
|
||||
To anyone who has submitted a pull request, please bear with me for a little while.
|
||||
3rd June, 2026
|
||||
|
||||
### Improved
|
||||
|
||||
- Database fetching (a.k.a. Reset Synchronisation on This Device) on the initialisation now supports streaming and is faster (CouchDB only)
|
||||
- The database fetching process has been streamlined, and database operations are now suspended until it has been completed
|
||||
- The initial synchronisation process has been simplified, making it easier to synchronise files with the remote server
|
||||
- We can select the remote database to fetch from during the initialisation, when there are multiple remote databases configured (e.g. multiple CouchDBs or S3 remotes)
|
||||
- Hebrew (he) Translation has been added (Thank you so much, @MusiCode1)!
|
||||
- Translation loading time has been reduced (Thank you so much, @bmcyver)!
|
||||
|
||||
### Fixed
|
||||
|
||||
- No longer does the status element break other plugins' interaction (#930).
|
||||
- No longer does file events occured during initial database fetching using Object Storage.
|
||||
|
||||
### Refactored
|
||||
|
||||
- Many
|
||||
To support the new Community automated tests, we fixed numerous lint warnings. This may have also resolved potential issues.
|
||||
|
||||
## 0.25.70
|
||||
|
||||
@@ -94,211 +105,5 @@ Thank you so much to @SeleiXi for implementing these features!
|
||||
- Added a `Generate full report for opening the issue with debug info` command to the command palette, which generates a report without opening the settings dialogue.
|
||||
|
||||
|
||||
## 0.25.64
|
||||
|
||||
17th May, 2026
|
||||
|
||||
### P2P Status Pane
|
||||
|
||||
- Added active P2P remote selector (combo box) and `+` action to create/select a P2P remote from the P2P setup dialogue.
|
||||
- Added per-peer immediate replication action on accepted peers.
|
||||
- Updated status control icons for clarity:
|
||||
- Replicate now: `🔄` (`⏳` while running)
|
||||
- Watch: `🔔` / `🔕`
|
||||
- Sync target: `🔗` / `⛓️💥`
|
||||
- Added warning state when no active P2P remote is selected.
|
||||
|
||||
### P2P Status Card
|
||||
|
||||
- Added stable Room ID suffix display and placed it above Peer ID for better identification.
|
||||
|
||||
### Non behavioural internal changes
|
||||
|
||||
#### P2P
|
||||
|
||||
- Added `P2P_ActiveRemoteConfigurationId` as a dedicated active remote selection for P2P features, separate from the normal active remote.
|
||||
- Added activation logic for P2P dedicated remote configuration that reflects P2P settings while keeping `remoteType` unchanged.
|
||||
- Added migration support to carry over P2P active remote selection when appropriate.
|
||||
- Added shared Room ID utility functions and applied them across P2P setup and P2P panes.
|
||||
|
||||
#### Tests
|
||||
|
||||
- Added/updated unit test coverage around settings load behaviour for P2P active remote application.
|
||||
|
||||
## 0.25.63
|
||||
|
||||
17th May, 2026
|
||||
|
||||
### Fixed
|
||||
- The issue which cannot synchronise in Only-P2P mode has been fixed.
|
||||
- Fixed an issue where "Failed to connect to the remote server" was shown during the redFlag rebuild flow when P2P was the primary remote type. Remote configuration fetch is now skipped for P2P.
|
||||
|
||||
### P2P Replication UI Improvements
|
||||
- Brand-new P2P Server Status pane has been added to provide real-time visibility into your connection status and peer network.
|
||||
- For detailed instructions on using the new P2P features, please refer to the updated [User Guide: Peer-to-Peer Synchronisation (2026 Edition)](./docs/p2p_sync_updates_2026.md).
|
||||
- Now `Replicate` button or ribbon icon opens a redesigned interactive replication dialogue that performs smart bidirectional sync with a single click.
|
||||
- The vault rebuild flow (`replicateAllFromServer`) now opens the redesigned P2P Replication modal instead of a plain text selection dialogue, providing a consistent UI experience.
|
||||
|
||||
## 0.25.62
|
||||
|
||||
14th May, 2026
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed an issue where a connection could not be established when attempting to connect to a brand-new remote database without going through the set-up wizard or configuration checking (#660).
|
||||
|
||||
## 0.25.61
|
||||
|
||||
13th May, 2026
|
||||
|
||||
Reviews have started on the Obsidian Community, haven't they? It was quite a struggle, what with having to fix the outdated ESLint.
|
||||
I am a bit nervous, but it is far better than just plodding along aimlessly, so let us get on with it. If you spot any issues, please let me know straight away.
|
||||
|
||||
From now on, I am avoiding committing directly to the main branch. This is because you lots have all been sending so much PRs. I wanted to keep things harmonious.
|
||||
That said, I am still not used to rebasing, so there are some parts where the commit history is a right mess. I will work on improving that.
|
||||
|
||||
### Improved
|
||||
|
||||
- P2P synchronisation has been made more robust
|
||||
Now the foundation for P2P synchronisation has been rewritten, and the unit tests have been added. The foundation has been separated into the transport layer, signalling-and-connection layer, and, an RPC layers. And each layer has been unit-tested. As the result, the P2P synchronisation now uses the robust shim that uses RPC-ed PouchDB synchronisation in contrast to previous implementation.
|
||||
This P2P synchronisation is not compatible with previous versions in terms of connectivity. All devices must be updated.
|
||||
|
||||
### Fixed
|
||||
|
||||
- No longer baffling errors occur when setting-update is triggered during the early stage of initialisation.
|
||||
- Network error notice pop-ups are now suppressed when 'NetworkWarningStyle' is set to 'Hidden'. (Thank you so much @SeleiXi!)
|
||||
|
||||
### New features
|
||||
|
||||
- Diff navigation buttons have been added to the diff view, making it easier to move between differences. (Thank you so much @SeleiXi! #871)
|
||||
|
||||
### Translations
|
||||
|
||||
- Chinese (Simplified) translations for settings and the Setup Wizard have been added. (Thank you so much @zombiek731!)
|
||||
- Common UI controls and signal words are now localised into Chinese (Simplified). (Thank you so much @zombiek731!)
|
||||
- i18n runtime behaviour and locale coverage have been improved. (Thank you so much @52sanmao!)
|
||||
|
||||
### CLI
|
||||
|
||||
#### New features
|
||||
|
||||
- Daemon synchronisation is now supported. (Thank you so much @andrewleech! #843)
|
||||
- `HeadlessConfirm` has been implemented with sensible defaults, enabling unattended operation in headless environments. (Thank you so much @andrewleech!)
|
||||
- The CLI onboarding experience has been improved. (Thank you so much @OriBoharon! #872)
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Sub-millisecond CLI mtimes are now truncated to prevent mobile crash. (Thank you so much @brian-spackman! #893)
|
||||
|
||||
## 0.25.60
|
||||
|
||||
29th April, 2026
|
||||
|
||||
### Fixed
|
||||
|
||||
- Now larger settings can be exported and imported via QR code without issues. (#595)
|
||||
- When the settings data exceeds the QR code capacity, it is now split into multiple QR codes.
|
||||
- These QR codes are reassembled by the aggregator page, which collects the split data and reconstructs the original settings.
|
||||
- Aggregator page is available at `https://vrtmrz.github.io/obsidian-livesync/aggregator.html`, and this file is also included in the repository.
|
||||
- We will not send the settings data to any server. The QR code data is generated and processed entirely on the client side, ensuring that your settings remain private and secure. HOWEVER, please be careful your network environment.
|
||||
- Fixed some errors during serialisation and deserialisation of the settings, which caused issues in some cases when importing/exporting settings via QR code.
|
||||
|
||||
### Fixed (CLI)
|
||||
|
||||
- `ls` and `mirror` commands now provide informative feedback when no documents are found or filters skip all files, resolving the issue where they would exit silently (#860).
|
||||
- Improved the clarity of CLI command logs by including the total count of processed items.
|
||||
- The command-line argument `vault` has been renamed to a more appropriate name, `databaseDir`.
|
||||
- The `mirror` command now accepts a `vault` directory, which specifies the location where the actual files are stored. For compatibility reasons, the previous behaviour is still supported.
|
||||
|
||||
## 0.25.59
|
||||
|
||||
### Fixed
|
||||
|
||||
- No longer Setup-wizard drops username and password silently. (#865)
|
||||
- Thank you so much for @koteitan !
|
||||
- Setup URI is now correctly imported (#859).
|
||||
- Also thank you so much for @koteitan !
|
||||
|
||||
### Improved
|
||||
|
||||
- now French translation is added by @foXaCe ! Thank you so much!
|
||||
|
||||
## 0.25.58
|
||||
|
||||
### Fixed
|
||||
|
||||
- No longer credentials are broken during object storage configuration (related: #852).
|
||||
- Fixed a worker-side recursion issue that could raise `Maximum call stack size exceeded` during chunk splitting (related: #855).
|
||||
- Improved background worker crash cleanup so pending split/encryption tasks are released cleanly instead of being left in a waiting state (related: #855).
|
||||
- On start-up, the selected remote configuration is now applied to runtime connection fields as well, reducing intermittent authentication failures caused by stale runtime settings (related: #855).
|
||||
- Issue report generation now redacts `remoteConfigurations` connection strings and keeps only the scheme (e.g. `sls+https://`), so credentials are not exposed in reports.
|
||||
- Hidden file JSON conflicts no longer keep re-opening and dismissing the merge dialogue before we can act, which fixes persistent unresolvable `data.json` conflicts in plug-in settings sync (related: #850).
|
||||
|
||||
## 0.25.57
|
||||
|
||||
9th April, 2026
|
||||
|
||||
- Packing a batch during the journal sync now continues even if the batch contains no items to upload.
|
||||
- No unexpected error (about a replicator) during the early stage of initialisation.
|
||||
- Now error messages are kept hidden if the show status inside the editor is disabled (related: #829).
|
||||
- Fixed an issue where devices could no longer upload after another device performed 'Fresh Start Wipe' and 'Overwrite remote' in Object Storage mode (#848).
|
||||
- Each device's local deduplication caches (`knownIDs`, `sentIDs`, `receivedFiles`, `sentFiles`) now track the remote journal epoch (derived from the encryption parameters stored on the remote).
|
||||
- When the epoch changes, the plugin verifies whether the device's last uploaded file still exists on the remote. If the file is gone, it confirms a remote wipe and automatically clears the stale caches. If the file is still present (e.g. a protocol upgrade without a wipe), the caches are preserved, and only the epoch is updated. This means normal upgrades never cause unnecessary re-processing.
|
||||
|
||||
### Translations
|
||||
|
||||
- Russian translation has been added! Thank you so much for the contribution, @vipka1n! (#845)
|
||||
|
||||
### New features
|
||||
|
||||
- Now we can configure multiple Remote Databases of the same type, e.g, multiple CouchDBs or S3 remotes.
|
||||
- A user interface for managing multiple remote databases has been added to the settings dialogue. I think no explanation is needed for the UI, but please let me know if you have any questions.
|
||||
- We can switch between multiple Remote Databases in the settings dialogue.
|
||||
|
||||
### CLI
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Replication progress is now correctly saved and restored in the CLI (related: #846).
|
||||
|
||||
## ~~0.25.55~~ 0.25.56
|
||||
|
||||
30th March, 2026
|
||||
|
||||
### Fixed
|
||||
|
||||
- No longer `Peer-to-Peer Sync is not enabled. We cannot open a new connection.` error occurs when we have not enabled P2P sync and are not expected to use it (#830).
|
||||
|
||||
### CLI
|
||||
|
||||
- Fixed incomplete localStorage support in the CLI (#831). Thank you so much @rewse !
|
||||
- Fixed the issue where the CLI could not be connected to the remote which had been locked once (#833), also thanks to @rewse !
|
||||
|
||||
## 0.25.54
|
||||
|
||||
18th March, 2026
|
||||
|
||||
### Fixed
|
||||
|
||||
- Remote storage size check now works correctly again (#818).
|
||||
- Some buttons on the settings dialogue now respond correctly again (#827).
|
||||
|
||||
### Refactored
|
||||
|
||||
- P2P replicator has been refactored to be a little more robust and easier to understand.
|
||||
- Delete items which are no longer used that might cause potential problems
|
||||
|
||||
### CLI
|
||||
|
||||
- Fixed the corrupted display of the help message.
|
||||
- Remove some unnecessary code.
|
||||
|
||||
### WebApp
|
||||
|
||||
- Fixed the issue where the detail level was not being applied in the log pane.
|
||||
- Pop-ups are now shown.
|
||||
- Add coverage for the test.
|
||||
- Pop-ups are now shown in the web app as well.
|
||||
|
||||
Full notes are in
|
||||
[updates_old.md](https://github.com/vrtmrz/obsidian-livesync/blob/main/updates_old.md).
|
||||
|
||||
100
updates_old.md
100
updates_old.md
@@ -3,6 +3,106 @@ Since 19th July, 2025 (beta1 in 0.25.0-beta1, 13th July, 2025)
|
||||
|
||||
The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsidian-livesync/blob/main/updates_old.md). Because 0.25 got a lot of updates, thankfully, compatibility is kept and we do not need breaking changes! In other words, when get enough stabled. The next version will be v1.0.0. Even though it my hope.
|
||||
|
||||
## 0.25.73
|
||||
|
||||
4th June, 2026
|
||||
|
||||
### Fixed
|
||||
|
||||
- Adjust CouchDB's database name checking to its specification (#926).
|
||||
- `Reset Syncronisation on This Device` for minio and P2P is now working properly.
|
||||
|
||||
## ~~0.25.71~~ 0.25.72
|
||||
|
||||
0.25.71 was cancelled due to the fixes needed (Object Storage related)
|
||||
|
||||
3rd June, 2026
|
||||
|
||||
### Improved
|
||||
|
||||
- Database fetching (a.k.a. Reset Synchronisation on This Device) on the initialisation now supports streaming and is faster (CouchDB only)
|
||||
- The database fetching process has been streamlined, and database operations are now suspended until it has been completed
|
||||
- The initial synchronisation process has been simplified, making it easier to synchronise files with the remote server
|
||||
- We can select the remote database to fetch from during the initialisation, when there are multiple remote databases configured (e.g. multiple CouchDBs or S3 remotes)
|
||||
- Hebrew (he) Translation has been added (Thank you so much, @MusiCode1)!
|
||||
- Translation loading time has been reduced (Thank you so much, @bmcyver)!
|
||||
|
||||
### Fixed
|
||||
|
||||
- No longer does the status element break other plugins' interaction (#930).
|
||||
- No longer does file events occured during initial database fetching using Object Storage.
|
||||
|
||||
### Refactored
|
||||
|
||||
To support the new Community automated tests, we fixed numerous lint warnings. This may have also resolved potential issues.
|
||||
|
||||
## 0.25.70
|
||||
|
||||
25th May, 2026
|
||||
|
||||
### New features
|
||||
- Diff dialogue now has great tools to navigate and understand the differences, including:
|
||||
- A checkbox to toggle the visibility of collapsed identical sections, making it easier to focus on the actual differences (PR #889).
|
||||
- A search feature to find specific text in past revisions, and navigate revisions with search results highlighted in the dialogue (PR #890).
|
||||
|
||||
- Conflict resolution dialogue now has a navigation feature to jump between conflicts (PR #891).
|
||||
|
||||
Thank you so much to @SeleiXi for implementing these features!
|
||||
|
||||
### Improved
|
||||
|
||||
- More diagnostic information for P2P connections is now shown, including why a connection failure occurred and the current connection status.
|
||||
|
||||
## 0.25.69
|
||||
|
||||
22nd May, 2026
|
||||
|
||||
### Fixed
|
||||
- No longer does the P2P passphrase mismatch cause a server shutdown.
|
||||
- Settings related to P2P synchronisation are now correctly applied on start-up and no longer reverted.
|
||||
|
||||
### New features
|
||||
- Diagnostic P2P connection stats are now available.
|
||||
- These stats indicate the number of connection trials, successes, and failures.
|
||||
|
||||
## 0.25.68
|
||||
|
||||
22nd May, 2026
|
||||
|
||||
### Improved
|
||||
|
||||
- P2P connections have improved slightly
|
||||
- Upgrade to `trystero` v0.24.0, and fixes event handler assignment. This should fix some edge cases where P2P connections fail to establish or messages are not properly handled.
|
||||
- Weaken terser options to avoid potential issues with minification that could cause runtime errors in some environments.
|
||||
|
||||
## ~~0.25.66~~ 0.25.67
|
||||
|
||||
20th May, 2026
|
||||
|
||||
0.25.66 had a bug that the auto-accept logic for compatible but lossy mismatches was not working as intended.
|
||||
|
||||
### New features
|
||||
- Implement an auto-accept compatible tweak setting and enhance the mismatch resolution logic.
|
||||
|
||||
### Improved
|
||||
- Many messages related to tweak mismatch resolution have been updated for clarity.
|
||||
|
||||
## 0.25.65
|
||||
|
||||
19th May, 2026
|
||||
|
||||
### Fixed
|
||||
- Fix an issue about resuming from background on iOS (#888).
|
||||
- Now Chunk Splitter: `V3: Fine Deduplication` is working fine again (#866).
|
||||
- It has some drawbacks, such as fewer chunks are generated. However, it makes less transfer and storage when the files are modified but not completely changed.
|
||||
- Unsynchronised local changes (which means changes that have not been sent) are now correctly preserved as a conflict (Thank you so much for @SeleiXi!).
|
||||
- Avoid creating a new revision when the current and conflicted revisions have identical content (Thank you so much for @daichi-629).
|
||||
|
||||
### Improved
|
||||
- Improved the error verbosity on concurrent processing during the start-up process.
|
||||
- Now the `report` includes recent logs (of verbosity `verbose` even settings is not set to `verbose`).
|
||||
- Updating logs is now debounced to avoid excessive updates during rapid log generation.
|
||||
- Added a `Generate full report for opening the issue with debug info` command to the command palette, which generates a report without opening the settings dialogue.
|
||||
|
||||
## 0.25.64
|
||||
|
||||
|
||||
@@ -11,17 +11,24 @@ export default mergeConfig(
|
||||
},
|
||||
},
|
||||
test: {
|
||||
logHeapUsage: true,
|
||||
// maxConcurrency: 2,
|
||||
name: "unit-tests",
|
||||
include: ["**/*unit.test.ts", "**/*.unit.spec.ts"],
|
||||
exclude: ["test/**"],
|
||||
exclude: ["test/**", "src/apps/**/testdeno/**"],
|
||||
coverage: {
|
||||
include: ["src/**/*.ts"],
|
||||
exclude: [
|
||||
"**/*.test.ts",
|
||||
"**/*unit.test.ts",
|
||||
"**/*.unit.spec.ts",
|
||||
"test/**",
|
||||
"src/lib/**/*.test.ts",
|
||||
"**/_*",
|
||||
"src/lib/apps",
|
||||
"src/lib/src/cli",
|
||||
"src/apps/**/testdeno/**",
|
||||
// "src/apps/**",
|
||||
// "src/cli/**",
|
||||
"src/lib/src/cli/**",
|
||||
"**/*_obsolete.ts",
|
||||
...importOnlyFiles,
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user