Fix CLI internal path filtering on Windows

Split logical Vault paths on '/' on every platform so that internal database
files and hidden path segments remain excluded when the database and Vault
share a directory.

Cover POSIX and Windows host separators through the real CLI bootstrap
filter, preserving the omitted-Vault-path E2E scenario.
This commit is contained in:
vorotamoroz committed 2026-10-31 06:19:11 +00:00
1 parent 2433d117c2
commit 5e3e7e1330
3 files changed
+39 -1

No files matched your search

+36
View File
@@ -3,6 +3,7 @@ import { appendFile, mkdtemp, mkdir, readFile, rm, stat, writeFile } from "node:
import { tmpdir } from "node:os"; import { tmpdir } from "node:os";
import { join } from "node:path"; import { join } from "node:path";
import * as chokidar from "chokidar"; import * as chokidar from "chokidar";
import { path } from "@vrtmrz/livesync-commonlib/node";
import * as offlineScanner from "@vrtmrz/livesync-commonlib/compat/serviceFeatures/offlineScanner"; import * as offlineScanner from "@vrtmrz/livesync-commonlib/compat/serviceFeatures/offlineScanner";
import { ControlService } from "@vrtmrz/livesync-commonlib/compat/services/base/ControlService"; import { ControlService } from "@vrtmrz/livesync-commonlib/compat/services/base/ControlService";
import { import {
@@ -20,6 +21,11 @@ import { main, type CliCommandRunner } from "./main";
import { cloneSettings } from "./settingsPersistence"; import { cloneSettings } from "./settingsPersistence";
import type { CLICommand } from "./commands/types"; import type { CLICommand } from "./commands/types";
vi.mock("@vrtmrz/livesync-commonlib/node", async (importOriginal) => {
const actual = await importOriginal<typeof import("@vrtmrz/livesync-commonlib/node")>();
return { ...actual, path: { ...actual.path } };
});
vi.mock("chokidar", { spy: true }); vi.mock("chokidar", { spy: true });
vi.mock("@vrtmrz/livesync-commonlib/compat/serviceFeatures/offlineScanner", { spy: true }); vi.mock("@vrtmrz/livesync-commonlib/compat/serviceFeatures/offlineScanner", { spy: true });
@@ -88,6 +94,36 @@ describe("CLI database preparation", () => {
await expect(main(standardIo, runner)).rejects.toThrow(`__EXIT__:${exitCode}`); await expect(main(standardIo, runner)).rejects.toThrow(`__EXIT__:${exitCode}`);
} }
it.each([
{ separator: "/", platform: "POSIX" },
{ separator: "\\", platform: "Windows" },
])("excludes internal Vault paths with the $platform host separator", async ({ separator }) => {
const targets = [
"headless-vault-livesync-v2/LOCK",
".livesync/settings.json",
"notes/.private/secret.md",
"notes/local.md",
];
const observed: boolean[] = [];
const runner = vi.fn<CliCommandRunner>(async (_options, { core }) => {
const originalSeparator = path.sep;
try {
Object.defineProperty(path, "sep", { value: separator });
for (const target of targets) {
observed.push(await core.services.vault.isTargetFile(target as FilePathWithPrefix));
}
} finally {
Object.defineProperty(path, "sep", { value: originalSeparator });
}
return false;
});
await start("ls", runner);
expect(runner).toHaveBeenCalledOnce();
expect(observed).toEqual([false, false, false, true]);
});
it("loads legacy device values and persists configuration through the service-owned store", async () => { it("loads legacy device values and persists configuration through the service-owned store", async () => {
const localStoragePath = join(directory, ".livesync", "runtime", "local-storage.json"); const localStoragePath = join(directory, ".livesync", "runtime", "local-storage.json");
await mkdir(join(directory, ".livesync", "runtime"), { recursive: true }); await mkdir(join(directory, ".livesync", "runtime"), { recursive: true });
+1 -1
View File
@@ -550,7 +550,7 @@ export async function main(
// Add target filter to prevent internal files are handled // Add target filter to prevent internal files are handled
core.services.vault.isTargetFile.addHandler(async (target) => { core.services.vault.isTargetFile.addHandler(async (target) => {
const targetPath = stripAllPrefixes(getPathFromUXFileInfo(target)); const targetPath = stripAllPrefixes(getPathFromUXFileInfo(target));
const parts = targetPath.split(path.sep); const parts = targetPath.split("/");
// if some part of the path starts with dot, treat it as internal file and ignore. // if some part of the path starts with dot, treat it as internal file and ignore.
if (parts.some((part) => part.startsWith("."))) { if (parts.some((part) => part.startsWith("."))) {
return await Promise.resolve(false); return await Promise.resolve(false);
+2
View File
@@ -12,6 +12,8 @@ Earlier releases remain available in the 1.0 release history, the 1.0 preview hi
## Unreleased ## Unreleased
- The CLI mirror now starts correctly on Windows when the Vault path is omitted and the database directory is also used as the Vault. It checks logical Vault paths using `/` separators, correctly excluding internal database files and nested hidden directories even when the host uses Windows path separators.
## 1.0.43 ## 1.0.43
30th October, 2026 30th October, 2026