mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-11-06 00:08:23 +00:00
Default new CLI and Obsidian environments to NFC path identity
This commit is contained in:
1 parent
ac502b0261
commit
39cc31c2e8
26 files changed
+1466
-61
No files matched your search
@@ -1,5 +1,5 @@
|
||||
import type { ObsidianLiveSyncSettings } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import { createNewVaultSettings } from "@vrtmrz/livesync-commonlib/settings";
|
||||
import { createNewVaultSettings } from "@/common/newVaultSettings.ts";
|
||||
|
||||
export function createDefaultCliSettings(): ObsidianLiveSyncSettings {
|
||||
return {
|
||||
|
||||
@@ -7,10 +7,14 @@ describe("createDefaultCliSettings", () => {
|
||||
const settings = createDefaultCliSettings();
|
||||
const recommended = createNewVaultSettings();
|
||||
|
||||
expect(settings.pathNormalizationVersion).toBe(1);
|
||||
expect(settings).toEqual({
|
||||
...recommended,
|
||||
pathNormalizationVersion: 1,
|
||||
useIndexedDBAdapter: false,
|
||||
isConfigured: false,
|
||||
});
|
||||
expect(recommended.pathNormalizationVersion).toBe(0);
|
||||
expect(settings.remoteConfigurations).not.toBe(recommended.remoteConfigurations);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
import {
|
||||
REMOTE_COUCHDB,
|
||||
REMOTE_MINIO,
|
||||
type ObsidianLiveSyncSettings,
|
||||
} from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import type { RemoteConnectionProbe } from "@vrtmrz/livesync-commonlib/replication";
|
||||
import { resolveInitialPathIdentity } from "@/common/initialPathIdentity";
|
||||
|
||||
/** Adopt an existing central remote only for the first explicit Fetch. */
|
||||
export async function resolveInitialFetchPathIdentity(
|
||||
settings: ObsidianLiveSyncSettings,
|
||||
createProbe: () => Promise<RemoteConnectionProbe | undefined>
|
||||
): Promise<0 | 1 | undefined> {
|
||||
if (settings.isConfigured) return undefined;
|
||||
const hasRemote =
|
||||
(settings.remoteType === REMOTE_COUCHDB && !!settings.couchDB_URI && !!settings.couchDB_DBNAME) ||
|
||||
(settings.remoteType === REMOTE_MINIO && !!settings.endpoint && !!settings.bucket);
|
||||
if (!hasRemote) return undefined;
|
||||
return await resolveInitialPathIdentity(await createProbe(), settings.pathNormalizationVersion);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { REMOTE_COUCHDB, REMOTE_MINIO, REMOTE_P2P } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import { createDefaultCliSettings } from "./cliSettingsDefaults";
|
||||
import { resolveInitialFetchPathIdentity } from "./initialFetchPathIdentity";
|
||||
|
||||
function fixture() {
|
||||
const settings = {
|
||||
...createDefaultCliSettings(),
|
||||
couchDB_URI: "https://db.test",
|
||||
couchDB_DBNAME: "vault",
|
||||
endpoint: "https://objects.test",
|
||||
bucket: "vault",
|
||||
};
|
||||
const probe = {
|
||||
check: vi.fn(),
|
||||
getStatus: vi.fn(),
|
||||
inspectPathIdentity: vi.fn(async () => ({
|
||||
status: "configured" as const,
|
||||
pathNormalizationVersion: 0 as const,
|
||||
})),
|
||||
dispose: vi.fn(async () => undefined),
|
||||
};
|
||||
return { settings, probe, createProbe: vi.fn(async () => probe) };
|
||||
}
|
||||
|
||||
describe("initial CLI Fetch path identity", () => {
|
||||
it.each([REMOTE_COUCHDB, REMOTE_MINIO])("adopts a legacy %s remote before the first Fetch", async (remoteType) => {
|
||||
const { settings, probe, createProbe } = fixture();
|
||||
settings.remoteType = remoteType;
|
||||
await expect(resolveInitialFetchPathIdentity(settings, createProbe)).resolves.toBe(0);
|
||||
expect(settings.pathNormalizationVersion).toBe(1);
|
||||
expect(probe.dispose).toHaveBeenCalledOnce();
|
||||
});
|
||||
it("leaves an already configured device unchanged", async () => {
|
||||
const { settings, createProbe } = fixture();
|
||||
settings.isConfigured = true;
|
||||
await expect(resolveInitialFetchPathIdentity(settings, createProbe)).resolves.toBeUndefined();
|
||||
expect(createProbe).not.toHaveBeenCalled();
|
||||
});
|
||||
it.each(["unconfigured", "p2p"])("does not infer identity for %s settings", async (kind) => {
|
||||
const { settings, createProbe } = fixture();
|
||||
if (kind === "p2p") settings.remoteType = REMOTE_P2P;
|
||||
else settings.couchDB_URI = "";
|
||||
await expect(resolveInitialFetchPathIdentity(settings, createProbe)).resolves.toBeUndefined();
|
||||
expect(createProbe).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -5,6 +5,7 @@ import { join } from "node:path";
|
||||
import * as chokidar from "chokidar";
|
||||
import { path } from "@vrtmrz/livesync-commonlib/node";
|
||||
import * as offlineScanner from "@vrtmrz/livesync-commonlib/compat/serviceFeatures/offlineScanner";
|
||||
import { ReplicatorService } from "@vrtmrz/livesync-commonlib/compat/services/base/ReplicatorService";
|
||||
import { ControlService } from "@vrtmrz/livesync-commonlib/compat/services/base/ControlService";
|
||||
import {
|
||||
AutoAccepting,
|
||||
@@ -333,6 +334,45 @@ describe("CLI database preparation", () => {
|
||||
expect(watch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("retains the adopted initial Fetch identity without --write-settings", async () => {
|
||||
await writeFile(
|
||||
settingsPath,
|
||||
JSON.stringify({
|
||||
...createDefaultCliSettings(),
|
||||
isConfigured: false,
|
||||
couchDB_URI: "https://example.test",
|
||||
couchDB_DBNAME: "vault",
|
||||
couchDB_USER: "user",
|
||||
couchDB_PASSWORD: "pass",
|
||||
})
|
||||
);
|
||||
const dispose = vi.fn(async () => undefined);
|
||||
const inspect = vi.spyOn(ReplicatorService.prototype, "createRemoteResource").mockResolvedValue({
|
||||
check: vi.fn(),
|
||||
getStatus: vi.fn(),
|
||||
inspectPathIdentity: vi.fn(async () => ({ status: "configured", pathNormalizationVersion: 0 })),
|
||||
dispose,
|
||||
});
|
||||
await start(
|
||||
"fetch",
|
||||
async (_options, { core }) => {
|
||||
expect(core.services.setting.currentSettings().pathNormalizationVersion).toBe(0);
|
||||
return true;
|
||||
},
|
||||
0,
|
||||
true
|
||||
);
|
||||
const persisted = JSON.parse(await readFile(settingsPath, "utf-8"));
|
||||
expect(persisted.pathNormalizationVersion).toBe(0);
|
||||
expect(dispose).toHaveBeenCalledOnce();
|
||||
expect(inspect).toHaveBeenCalledOnce();
|
||||
await start("ls", async (_options, { core }) => {
|
||||
expect(core.services.setting.currentSettings().pathNormalizationVersion).toBe(0);
|
||||
return false;
|
||||
});
|
||||
expect(inspect).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it.each(["rebuild", "fetch"] as const)("does not scan or mirror legacy Vault files before %s", async (command) => {
|
||||
const storedPaths: string[] = [];
|
||||
const runner = vi.fn<CliCommandRunner>(async (_options, { core }) => {
|
||||
|
||||
@@ -30,6 +30,8 @@ import type { ReplicationSchedulingControl } from "@/serviceFeatures/replication
|
||||
import { createNodeStandardIo, fsPromises as fs, path } from "@vrtmrz/livesync-commonlib/node";
|
||||
import type { StandardIo } from "@vrtmrz/livesync-commonlib/context";
|
||||
import { writeStderrLine, writeStdoutLine } from "./cliOutput";
|
||||
import { REMOTE_RESOURCE_KINDS } from "@vrtmrz/livesync-commonlib/replication";
|
||||
import { resolveInitialFetchPathIdentity } from "./initialFetchPathIdentity";
|
||||
import { createDefaultCliSettings } from "./cliSettingsDefaults";
|
||||
import {
|
||||
applyStoredSetting,
|
||||
@@ -601,6 +603,22 @@ export async function main(
|
||||
throw new Error("Replication scheduling was not provided during core feature composition.");
|
||||
}
|
||||
|
||||
let initialFetchPathIdentity: 0 | 1 | undefined;
|
||||
if (options.command === "fetch") {
|
||||
core.services.appLifecycle.onSettingLoaded.addHandler(async () => {
|
||||
const settings = core.services.setting.currentSettings();
|
||||
const version = await resolveInitialFetchPathIdentity(settings, () =>
|
||||
core.services.replicator.createRemoteResource(REMOTE_RESOURCE_KINDS.CONNECTION, settings)
|
||||
);
|
||||
if (version !== undefined) {
|
||||
initialFetchPathIdentity = version;
|
||||
await core.services.setting.applyPartial({ pathNormalizationVersion: version });
|
||||
await core.services.setting.saveSettingData();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// Setup signal handlers for graceful shutdown
|
||||
const shutdown = async (signal: string) => {
|
||||
writeStdoutLine(standardIo);
|
||||
@@ -802,6 +820,7 @@ export async function main(
|
||||
: (JSON.parse(originalSettingsText) as ObsidianLiveSyncSettings);
|
||||
durable.additionalSuffixOfDatabaseName =
|
||||
core.services.setting.currentSettings().additionalSuffixOfDatabaseName;
|
||||
if (initialFetchPathIdentity !== undefined) durable.pathNormalizationVersion = initialFetchPathIdentity;
|
||||
settingsTextToCommit = JSON.stringify(durable, null, 2);
|
||||
}
|
||||
settingsTextToCommit ??= originalSettingsText;
|
||||
|
||||
Reference in new issue
Block a user