refactor: consume Commonlib as a package

This commit is contained in:
vorotamoroz
2026-07-17 11:13:16 +00:00
parent 6475d32769
commit a721d3b602
665 changed files with 3438 additions and 31592 deletions
+13 -3
View File
@@ -4,7 +4,7 @@ This directory contains the experimental real Obsidian end-to-end runner.
The generic application discovery, isolated-vault, plug-in installation, process lifecycle, CLI, CDP, and readiness implementation comes from `@vrtmrz/obsidian-test-session`. The small modules under `runner/` preserve LiveSync's existing imports and supply its plug-in ID and artefact location. LiveSync-specific fixtures, services, settings, workflows, and assertions remain in this repository.
The current smoke runner verifies only the launch path:
The current smoke runner verifies the launch path and the loaded plug-in's Service Context composition:
1. create a temporary vault,
2. install the built Self-hosted LiveSync plug-in artifacts,
@@ -13,8 +13,10 @@ The current smoke runner verifies only the launch path:
5. enable Obsidian community plug-ins for the temporary app profile,
6. reload Self-hosted LiveSync through `obsidian-cli`,
7. verify through `obsidian-cli eval` that the plug-in is loaded,
8. optionally drive a real vault or CouchDB workflow through Obsidian's own API,
9. terminate Obsidian and remove the temporary vault.
8. observe event and translation results from the actual `ObsidianServiceContext`,
9. verify that the Service Hub and every exposed service retain that exact Context,
10. optionally drive a real vault or CouchDB workflow through Obsidian's own API, and
11. terminate Obsidian and remove the temporary vault.
The runner does not require Self-hosted LiveSync to expose an E2E-only bridge. Readiness is checked from outside the plug-in through Obsidian's own CLI.
@@ -45,6 +47,10 @@ These tests are intended for local verification, not the default CI gate. Reuse
## Commands
```bash
npm run test:contract:contexts
npm run test:contract:context:webapp
npm run test:contract:context:cli
npm run test:contract:context:obsidian
npm run test:e2e:obsidian:install-appimage
npm run test:e2e:obsidian:discover
npm run test:e2e:obsidian:cli-help -- vaults verbose
@@ -62,6 +68,10 @@ npm run test:e2e:obsidian:local-suite
npm run test:e2e:obsidian:local-suite:services
```
`test:contract:contexts` runs the directly observable host contract against the Obsidian, CLI, and Webapp compositions. It verifies event and translation results, host-specific capabilities, and that the CLI and Webapp Service Hubs pass one exact Context to all exposed services. `test:contract:context:webapp` runs only the Webapp part.
`test:contract:context:cli` builds the Node CLI and runs its existing Deno setup, put, read, list, information, remove, conflict-resolution, and revision workflow. `test:contract:context:obsidian` builds the plug-in and runs the real-Obsidian smoke test, including the Context inspection. These runtime scripts are local validation entry points and are not added to the default CI gate by this change.
`test:e2e:obsidian:local-suite` builds the plug-in and, unless `LIVESYNC_CLI_COMMAND` selects an external CLI, the local LiveSync CLI. It then runs discovery, smoke, vault reflection, CouchDB upload, CLI-to-Obsidian synchronisation, Object Storage upload, startup scan, two-vault synchronisation, Hidden File Sync, Customisation Sync, and setting Markdown export in sequence. Start the local CouchDB and MinIO fixtures before running it, or use `test:e2e:obsidian:local-suite:services` to let the wrapper stop leftover fixtures, start fresh fixtures, and stop them again after the run.
`test:e2e:obsidian:couchdb-upload` reuses the CouchDB variables from `.test.env` or the process environment. It expects a reachable CouchDB service, creates a unique database, configures Self-hosted LiveSync through `obsidian-cli eval`, creates a note in real Obsidian, commits the note into the local database, runs one-shot synchronisation, and verifies that the remote database contains both the metadata document and its chunk documents.
@@ -1,4 +1,5 @@
import { evalObsidianJson } from "./cli.ts";
import { SERVICE_CONTEXT_MEMBERS } from "../../contracts/serviceContext.ts";
import type { CouchDbConfig } from "./couchdb.ts";
import type { ObjectStorageConfig } from "./objectStorage.ts";
@@ -20,6 +21,17 @@ export type CoreReadiness = {
appReady: boolean;
};
export type ObsidianServiceContextContractResult = {
contextType: string;
eventResult: string[];
translationResult: string;
hubUsesContext: boolean;
serviceContextMismatches: string[];
appCapabilityMatches: boolean;
pluginCapabilityMatches: boolean;
liveSyncPluginCapabilityMatches: boolean;
};
export type LocalDatabaseEntry = {
id: string;
rev: string;
@@ -172,6 +184,68 @@ export async function waitForLiveSyncCoreReady(
throw new Error(`Timed out waiting for Self-hosted LiveSync core readiness: ${JSON.stringify(lastReadiness)}`);
}
/**
* Inspect the actual Obsidian composition through Obsidian's CLI.
*
* This observes public Context results and verifies that the Hub and every
* exposed service retain the exact Context created by the plug-in host.
*/
export async function inspectObsidianServiceContextContract(
cliBinary: string,
env: NodeJS.ProcessEnv
): Promise<ObsidianServiceContextContractResult> {
return await evalObsidianJson<ObsidianServiceContextContractResult>(
cliBinary,
[
"(async()=>{",
"const plugin=app.plugins.plugins['obsidian-livesync'];",
"const services=plugin.core.services;",
"const context=services.context;",
`const serviceNames=${JSON.stringify(SERVICE_CONTEXT_MEMBERS)};`,
"const eventResult=[];",
"const unsubscribe=context.events.onEvent('hello',(value)=>eventResult.push(value));",
"try{context.events.emitEvent('hello','context-contract-event');}finally{unsubscribe();}",
"return JSON.stringify({",
"contextType:context.constructor.name,",
"eventResult,",
"translationResult:context.translate('Replicator.Message.InitialiseFatalError'),",
"hubUsesContext:services.context===context,",
"serviceContextMismatches:serviceNames.filter((name)=>services[name].context!==context),",
"appCapabilityMatches:context.app===app,",
"pluginCapabilityMatches:context.plugin===plugin,",
"liveSyncPluginCapabilityMatches:context.liveSyncPlugin===plugin,",
"});",
"})()",
].join(""),
env
);
}
export function assertObsidianServiceContextContract(result: ObsidianServiceContextContractResult): void {
assertEqual(result.contextType, "ObsidianServiceContext", "Unexpected Obsidian service Context type.");
assertEqual(result.hubUsesContext, true, "The Obsidian Service Hub substituted its host Context.");
assertEqual(
result.serviceContextMismatches.length,
0,
`Services used a different Context: ${result.serviceContextMismatches.join(", ")}`
);
assertEqual(
JSON.stringify(result.eventResult),
JSON.stringify(["context-contract-event"]),
"The Obsidian Context event API returned an unexpected result."
);
if (result.translationResult.length === 0) {
throw new Error("The Obsidian Context translator returned an empty result.");
}
assertEqual(result.appCapabilityMatches, true, "The Obsidian Context lost its App capability.");
assertEqual(result.pluginCapabilityMatches, true, "The Obsidian Context lost its Plugin capability.");
assertEqual(
result.liveSyncPluginCapabilityMatches,
true,
"The Obsidian Context lost its Self-hosted LiveSync plug-in capability."
);
}
export async function prepareRemote(cliBinary: string, env: NodeJS.ProcessEnv): Promise<void> {
await evalObsidianJson<unknown>(
cliBinary,
+9
View File
@@ -1,4 +1,8 @@
import { discoverObsidianCli, requireObsidianBinary } from "../runner/environment.ts";
import {
assertObsidianServiceContextContract,
inspectObsidianServiceContextContract,
} from "../runner/liveSyncWorkflow.ts";
import { startObsidianLiveSyncSession, type ObsidianLiveSyncSession } from "../runner/session.ts";
import { createTemporaryVault } from "../runner/vault.ts";
@@ -25,6 +29,11 @@ async function main(): Promise<void> {
console.log(
`Obsidian plug-in ready: ${readiness.pluginId}@${readiness.pluginVersion} in ${readiness.vaultName}`
);
const contextContract = await inspectObsidianServiceContextContract(cli.binary, session.cliEnv);
assertObsidianServiceContextContract(contextContract);
console.log(
`Obsidian service Context contract passed: ${contextContract.contextType}, ${contextContract.serviceContextMismatches.length} mismatches.`
);
await new Promise((resolve) => setTimeout(resolve, Number(process.env.E2E_OBSIDIAN_SMOKE_TIMEOUT_MS ?? 1000)));
console.log("Obsidian stayed alive after the plug-in readiness check.");
} finally {