Initial import of LogDarioSeq baseline
Establishes version control for the personal Logseq DB fork before starting the server-client architecture migration and domain features described in docs/agent-guide and the approved implementation plan. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
450
scripts/test-cli-release-config.mjs
Normal file
450
scripts/test-cli-release-config.mjs
Normal file
@@ -0,0 +1,450 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { mkdtempSync } from "node:fs";
|
||||
import os from "node:os";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
|
||||
const readText = (relativePath) =>
|
||||
fs.readFileSync(path.join(repoRoot, relativePath), "utf8");
|
||||
|
||||
const readJson = (relativePath) =>
|
||||
JSON.parse(readText(relativePath));
|
||||
|
||||
const rootPackage = readJson("package.json");
|
||||
const desktopPackage = readJson("resources/package.json");
|
||||
|
||||
const zvecOptionalRuntimeDependencies = [
|
||||
"@zvec/bindings-darwin-arm64",
|
||||
"@zvec/bindings-linux-arm64",
|
||||
"@zvec/bindings-linux-x64",
|
||||
"@zvec/bindings-win32-x64",
|
||||
"@zvec/zvec",
|
||||
];
|
||||
|
||||
const assertNotContains = (text, needle, label) => {
|
||||
assert.equal(
|
||||
text.includes(needle),
|
||||
false,
|
||||
`${label} should not contain ${needle}`,
|
||||
);
|
||||
};
|
||||
|
||||
const assertNoShadowRuntime = (text, label) => {
|
||||
for (const needle of ["SHADOW_IMPORT", ".shadow-cljs", "cljs-runtime"]) {
|
||||
assertNotContains(text, needle, label);
|
||||
}
|
||||
};
|
||||
|
||||
const assertRootScriptDoesNotBuildShadowCli = (scriptName, command) => {
|
||||
assert.ok(command, `package.json should define ${scriptName}`);
|
||||
assert.doesNotMatch(
|
||||
command,
|
||||
/clojure -[MA]:cljs (?:watch|compile|release)[^"]*\blogseq-cli\b/,
|
||||
`${scriptName} should not build the old Shadow CLI`,
|
||||
);
|
||||
};
|
||||
|
||||
const assertCliReleaseCommand = (command, label) => {
|
||||
assert.match(command, /pnpm --dir cli bundle/, `${label} should bundle cli/`);
|
||||
assert.match(
|
||||
command,
|
||||
/node \.\/scripts\/stage-cli-runtime\.mjs/,
|
||||
`${label} should stage the cli/ bundle`,
|
||||
);
|
||||
};
|
||||
|
||||
const filesUnder = (relativePath) => {
|
||||
const absolutePath = path.join(repoRoot, relativePath);
|
||||
if (!fs.existsSync(absolutePath)) {
|
||||
return [];
|
||||
}
|
||||
const stat = fs.statSync(absolutePath);
|
||||
if (stat.isFile()) {
|
||||
return [relativePath];
|
||||
}
|
||||
return fs
|
||||
.readdirSync(absolutePath, { withFileTypes: true })
|
||||
.flatMap((entry) => {
|
||||
const entryPath = path.join(relativePath, entry.name);
|
||||
return entry.isDirectory() ? filesUnder(entryPath) : [entryPath];
|
||||
});
|
||||
};
|
||||
|
||||
const assertFilesDoNotMatch = (relativePaths, pattern, label) => {
|
||||
const matches = relativePaths.flatMap((relativePath) => {
|
||||
const text = readText(relativePath);
|
||||
return pattern.test(text) ? [relativePath] : [];
|
||||
});
|
||||
assert.deepEqual(matches, [], `${label} should not contain ${pattern}`);
|
||||
};
|
||||
|
||||
const workflow = readText(".github/workflows/deps-cli.yml");
|
||||
assertNotContains(workflow, "deps/cli", "deps-cli workflow");
|
||||
assert.match(workflow, /cli\/\*\*/, "workflow should watch cli/ sources");
|
||||
assert.match(workflow, /pnpm cli:release/, "workflow should build new CLI");
|
||||
assertNotContains(workflow, "clojure -M:cljs release logseq-cli", "deps-cli workflow");
|
||||
assert.match(workflow, /pnpm db-worker-node:release:bundle/, "workflow should build bundled db-worker-node runtime");
|
||||
assert.match(workflow, /node scripts\/prepare-cli-package\.mjs/, "workflow should prepare publish package");
|
||||
assert.match(workflow, /working-directory: dist\/cli-package/, "workflow should publish prepared package");
|
||||
|
||||
const buildWorkflow = readText(".github/workflows/build.yml");
|
||||
assert.match(buildWorkflow, /pnpm cli:release/, "db graph workflow should build release CLI");
|
||||
assertNotContains(buildWorkflow, "clojure -M:cljs release logseq-cli", "db graph workflow");
|
||||
assert.match(buildWorkflow, /pnpm db-worker-node:release:bundle/, "db graph workflow should build release db-worker-node runtime");
|
||||
assert.match(buildWorkflow, /pnpm --dir dist\/cli-package install --prod --ignore-workspace/, "db graph workflow should install prepared CLI package dependencies");
|
||||
assert.match(buildWorkflow, /libsecret-1-0/, "db graph workflow should install keytar's Linux runtime dependency");
|
||||
assert.match(buildWorkflow, /pnpm --dir dist\/cli-package pack --pack-destination \.\.\//, "db graph workflow should verify the prepared CLI package with pnpm pack");
|
||||
assertNotContains(buildWorkflow, "create_graph_with_schema_org.cljs ./cli-root/graphs/schema-graph --subset", "db graph workflow");
|
||||
assert.match(buildWorkflow, /node dist\/cli-package\/dist\/logseq\.js --root-dir scripts\/cli-root/, "db graph workflow should test packaged CLI");
|
||||
assert.match(buildWorkflow, /node dist\/cli-package\/dist\/logseq\.js --root-dir scripts\/cli-root.+--timeout-ms 3000/, "db graph workflow should use a 3s CLI request timeout for packaged CLI graph commands");
|
||||
assert.match(buildWorkflow, /--graph schema-graph --timeout-ms 120000/, "db graph workflow should allow the full schema graph validation to finish in CI");
|
||||
assertNotContains(buildWorkflow, "clojure -M:cljs compile logseq-cli", "db graph workflow");
|
||||
assertNotContains(buildWorkflow, "pnpm db-worker-node:compile:bundle", "db graph workflow");
|
||||
|
||||
const desktopReleaseWorkflow = readText(".github/workflows/build-desktop-release.yml");
|
||||
assert.match(
|
||||
desktopReleaseWorkflow,
|
||||
/OCAML_VERSION: '5\.1\.1'/,
|
||||
"desktop release workflow should define the OCaml version used by cli/",
|
||||
);
|
||||
assert.match(
|
||||
desktopReleaseWorkflow,
|
||||
/uses: ocaml\/setup-ocaml@v3/,
|
||||
"desktop release workflow should set up OCaml before building cli/",
|
||||
);
|
||||
assert.match(
|
||||
desktopReleaseWorkflow,
|
||||
/working-directory: cli\s+run: opam install \. --deps-only --with-test --yes/,
|
||||
"desktop release workflow should install cli/ OCaml deps",
|
||||
);
|
||||
assert.match(
|
||||
desktopReleaseWorkflow,
|
||||
/pnpm --dir cli install --frozen-lockfile --ignore-workspace/,
|
||||
"desktop release workflow should install cli/ pnpm deps",
|
||||
);
|
||||
assert.match(
|
||||
desktopReleaseWorkflow,
|
||||
/opam exec -- pnpm cli:release/,
|
||||
"desktop release workflow should build and stage the OCaml CLI",
|
||||
);
|
||||
assert.ok(
|
||||
desktopReleaseWorkflow.indexOf("opam exec -- pnpm cli:release") <
|
||||
desktopReleaseWorkflow.indexOf("pnpm desktop:prepare-runtime-js"),
|
||||
"desktop release workflow should stage the CLI before preparing desktop runtime scripts",
|
||||
);
|
||||
assertNotContains(
|
||||
desktopReleaseWorkflow,
|
||||
"clojure -M:cljs release logseq-cli",
|
||||
"desktop release workflow",
|
||||
);
|
||||
|
||||
const shadowCljs = readText("shadow-cljs.edn");
|
||||
assertNotContains(shadowCljs, ":logseq-cli", "shadow-cljs.edn");
|
||||
|
||||
assertCliReleaseCommand(rootPackage.scripts?.["cli:release"], "cli:release");
|
||||
for (const [scriptName, command] of Object.entries(rootPackage.scripts ?? {})) {
|
||||
assertRootScriptDoesNotBuildShadowCli(scriptName, command);
|
||||
}
|
||||
|
||||
const gulpfile = readText("gulpfile.js");
|
||||
assert.match(
|
||||
gulpfile,
|
||||
/pnpm cli:release/,
|
||||
"Electron maker preparation should stage the CLI runtime",
|
||||
);
|
||||
assert.ok(
|
||||
gulpfile.indexOf("pnpm cli:release") <
|
||||
gulpfile.indexOf("pnpm desktop:prepare-runtime-js"),
|
||||
"Electron maker preparation should stage the CLI before preparing desktop runtime scripts",
|
||||
);
|
||||
|
||||
const stageScriptPath = path.join(repoRoot, "scripts", "stage-cli-runtime.mjs");
|
||||
assert.equal(
|
||||
fs.existsSync(stageScriptPath),
|
||||
true,
|
||||
"scripts/stage-cli-runtime.mjs should exist",
|
||||
);
|
||||
const stageScript = readText("scripts/stage-cli-runtime.mjs");
|
||||
assert.match(
|
||||
stageScript,
|
||||
/cli[\\/]_build[\\/]default[\\/]dist[\\/]logseq-cli\.js/,
|
||||
"stage script should read cli/_build/default/dist/logseq-cli.js",
|
||||
);
|
||||
assert.match(
|
||||
stageScript,
|
||||
/static[\\/]logseq-cli\.js/,
|
||||
"stage script should write static/logseq-cli.js",
|
||||
);
|
||||
for (const needle of ["SHADOW_IMPORT", ".shadow-cljs", "cljs-runtime"]) {
|
||||
assert.match(
|
||||
stageScript,
|
||||
new RegExp(needle.replace(".", "\\.")),
|
||||
`stage script should reject ${needle}`,
|
||||
);
|
||||
}
|
||||
|
||||
const runStageCliRuntime = (fixtureRoot) =>
|
||||
execFileSync(process.execPath, ["scripts/stage-cli-runtime.mjs"], {
|
||||
cwd: repoRoot,
|
||||
env: {
|
||||
...process.env,
|
||||
LOGSEQ_STAGE_CLI_RUNTIME_REPO_ROOT: fixtureRoot,
|
||||
},
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
|
||||
const makeStageFixture = () =>
|
||||
mkdtempSync(path.join(os.tmpdir(), "logseq-stage-cli-"));
|
||||
|
||||
{
|
||||
const fixtureRoot = makeStageFixture();
|
||||
assert.throws(
|
||||
() => runStageCliRuntime(fixtureRoot),
|
||||
/cli\/_build\/default\/dist\/logseq-cli\.js/,
|
||||
"stage script should fail when the cli/ bundle is missing",
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
const fixtureRoot = makeStageFixture();
|
||||
const sourcePath = path.join(
|
||||
fixtureRoot,
|
||||
"cli",
|
||||
"_build",
|
||||
"default",
|
||||
"dist",
|
||||
"logseq-cli.js",
|
||||
);
|
||||
fs.mkdirSync(path.dirname(sourcePath), { recursive: true });
|
||||
fs.writeFileSync(sourcePath, "console.log('SHADOW_IMPORT');\n");
|
||||
assert.throws(
|
||||
() => runStageCliRuntime(fixtureRoot),
|
||||
/SHADOW_IMPORT/,
|
||||
"stage script should reject Shadow runtime markers",
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
const fixtureRoot = makeStageFixture();
|
||||
const sourcePath = path.join(
|
||||
fixtureRoot,
|
||||
"cli",
|
||||
"_build",
|
||||
"default",
|
||||
"dist",
|
||||
"logseq-cli.js",
|
||||
);
|
||||
const stagedPath = path.join(fixtureRoot, "static", "logseq-cli.js");
|
||||
const staleMapPath = path.join(fixtureRoot, "static", "logseq-cli.js.map");
|
||||
fs.mkdirSync(path.dirname(sourcePath), { recursive: true });
|
||||
fs.mkdirSync(path.dirname(staleMapPath), { recursive: true });
|
||||
fs.writeFileSync(sourcePath, "console.log('new cli');\n");
|
||||
fs.writeFileSync(staleMapPath, "{}\n");
|
||||
const output = runStageCliRuntime(fixtureRoot);
|
||||
assert.equal(
|
||||
fs.readFileSync(stagedPath, "utf8"),
|
||||
"console.log('new cli');\n",
|
||||
);
|
||||
assert.equal(
|
||||
fs.existsSync(staleMapPath),
|
||||
false,
|
||||
"stage script should remove stale root sourcemap",
|
||||
);
|
||||
assert.match(output, /cli\/_build\/default\/dist\/logseq-cli\.js/);
|
||||
assert.match(output, /static\/logseq-cli\.js/);
|
||||
}
|
||||
|
||||
{
|
||||
const fixtureRoot = makeStageFixture();
|
||||
const sourcePath = path.join(
|
||||
fixtureRoot,
|
||||
"cli",
|
||||
"_build",
|
||||
"default",
|
||||
"dist",
|
||||
"logseq-cli.js",
|
||||
);
|
||||
const stagedPath = path.join(fixtureRoot, "static", "logseq-cli.js");
|
||||
fs.mkdirSync(path.dirname(sourcePath), { recursive: true });
|
||||
fs.mkdirSync(path.dirname(stagedPath), { recursive: true });
|
||||
fs.writeFileSync(sourcePath, "console.log('first');\n");
|
||||
fs.writeFileSync(stagedPath, "console.log('old');\n");
|
||||
fs.chmodSync(stagedPath, 0o444);
|
||||
runStageCliRuntime(fixtureRoot);
|
||||
assert.equal(
|
||||
fs.readFileSync(stagedPath, "utf8"),
|
||||
"console.log('first');\n",
|
||||
"stage script should overwrite an existing read-only staged file",
|
||||
);
|
||||
}
|
||||
|
||||
assert.equal(
|
||||
fs.existsSync(path.join(repoRoot, "deps/cli/package.json")),
|
||||
false,
|
||||
"old deps/cli package should be removed",
|
||||
);
|
||||
|
||||
const depsEdn = readText("deps.edn");
|
||||
assertNotContains(depsEdn, 'logseq/cli', "deps.edn");
|
||||
assertNotContains(depsEdn, '"deps/cli"', "deps.edn");
|
||||
|
||||
const bbEdn = readText("bb.edn");
|
||||
assertNotContains(bbEdn, "legacy cli", "bb.edn");
|
||||
assertNotContains(bbEdn, "../cli/src", "bb.edn");
|
||||
|
||||
const lintTask = readText("scripts/src/logseq/tasks/dev/lint.clj");
|
||||
assertNotContains(lintTask, '"deps/cli"', "lint task");
|
||||
|
||||
const lintDepsTask = readText("scripts/src/logseq/tasks/dev/lint_test_deps.clj");
|
||||
assertNotContains(lintDepsTask, '"deps/cli"', "lint/test deps task");
|
||||
|
||||
for (const dependencyName of zvecOptionalRuntimeDependencies) {
|
||||
assert.equal(
|
||||
rootPackage.dependencies?.[dependencyName],
|
||||
undefined,
|
||||
`${dependencyName} should not be a hard root runtime dependency`,
|
||||
);
|
||||
assert.ok(
|
||||
rootPackage.optionalDependencies?.[dependencyName],
|
||||
`${dependencyName} should be an optional root runtime dependency`,
|
||||
);
|
||||
assert.equal(
|
||||
desktopPackage.dependencies?.[dependencyName],
|
||||
undefined,
|
||||
`${dependencyName} should not be a hard desktop runtime dependency`,
|
||||
);
|
||||
assert.ok(
|
||||
desktopPackage.optionalDependencies?.[dependencyName],
|
||||
`${dependencyName} should be an optional desktop runtime dependency`,
|
||||
);
|
||||
}
|
||||
|
||||
assert.equal(
|
||||
fs.existsSync(path.join(repoRoot, "src/main/logseq/cli/common/mcp/server.cljs")),
|
||||
false,
|
||||
"CLI sources should not include an MCP server",
|
||||
);
|
||||
|
||||
assertFilesDoNotMatch(
|
||||
[
|
||||
...filesUnder("src/main/logseq/cli"),
|
||||
...filesUnder("src/test/logseq/cli"),
|
||||
"scripts/prepare-cli-package.mjs",
|
||||
],
|
||||
/logseq\.cli\.common\.mcp|modelcontextprotocol|zod\/v3|mcp-server/,
|
||||
"CLI sources and package preparation",
|
||||
);
|
||||
|
||||
const prepareCliPackageScript = readText("scripts/prepare-cli-package.mjs");
|
||||
assert.match(
|
||||
prepareCliPackageScript,
|
||||
/static[\\/]logseq-cli\.js/,
|
||||
"package preparation should package the staged CLI",
|
||||
);
|
||||
assert.match(
|
||||
prepareCliPackageScript,
|
||||
/cli[\\/]_build[\\/]default[\\/]dist[\\/]logseq-cli\.js/,
|
||||
"package preparation should compare the staged CLI against the cli/ bundle",
|
||||
);
|
||||
for (const needle of ["SHADOW_IMPORT", ".shadow-cljs", "cljs-runtime"]) {
|
||||
assert.match(
|
||||
prepareCliPackageScript,
|
||||
new RegExp(needle.replace(".", "\\.")),
|
||||
`package preparation should reject ${needle}`,
|
||||
);
|
||||
}
|
||||
|
||||
const prepareDesktopRuntimeScript = readText(
|
||||
"scripts/prepare-desktop-runtime-js.mjs",
|
||||
);
|
||||
assert.match(
|
||||
prepareDesktopRuntimeScript,
|
||||
/static[\\/]logseq-cli\.js/,
|
||||
"desktop runtime preparation should read the staged CLI",
|
||||
);
|
||||
assert.match(
|
||||
prepareDesktopRuntimeScript,
|
||||
/static[\\/]js[\\/]logseq-cli\.js/,
|
||||
"desktop runtime preparation should write static/js/logseq-cli.js",
|
||||
);
|
||||
assert.match(
|
||||
prepareDesktopRuntimeScript,
|
||||
/cli[\\/]_build[\\/]default[\\/]dist[\\/]logseq-cli\.js/,
|
||||
"desktop runtime preparation should compare the staged CLI against the cli/ bundle",
|
||||
);
|
||||
assert.match(
|
||||
prepareDesktopRuntimeScript,
|
||||
/fs\.chmod\(to, stats\.mode \| 0o200\)/,
|
||||
"desktop runtime preparation should overwrite read-only staged runtime files",
|
||||
);
|
||||
assertNotContains(
|
||||
prepareDesktopRuntimeScript,
|
||||
'fs.rm(path.join(staticDir, "logseq-cli.js")',
|
||||
"desktop runtime preparation should keep root staged CLI available",
|
||||
);
|
||||
assertNotContains(
|
||||
prepareDesktopRuntimeScript,
|
||||
'fs.rm(path.join(staticDir, "db-worker-node.js")',
|
||||
"desktop runtime preparation should keep root staged db-worker-node available",
|
||||
);
|
||||
|
||||
execFileSync(process.execPath, ["scripts/prepare-cli-package.mjs"], {
|
||||
cwd: repoRoot,
|
||||
stdio: "pipe",
|
||||
});
|
||||
|
||||
const packageRoot = path.join(repoRoot, "dist/cli-package");
|
||||
const packageJson = JSON.parse(
|
||||
fs.readFileSync(path.join(packageRoot, "package.json"), "utf8"),
|
||||
);
|
||||
|
||||
assert.equal(packageJson.name, "@logseq/cli");
|
||||
assert.equal(packageJson.bin.logseq, "dist/logseq.js");
|
||||
assert.equal(packageJson.private, undefined);
|
||||
assert.equal(packageJson.dependencies?.["@modelcontextprotocol/sdk"], undefined);
|
||||
assert.equal(packageJson.dependencies?.zod, undefined);
|
||||
assert.ok(packageJson.dependencies?.["@js-joda/core"], "publish package should include @js-joda/core for release artifacts");
|
||||
assert.ok(packageJson.dependencies?.keytar, "publish package should include keytar for db-worker-node");
|
||||
assert.ok(packageJson.dependencies?.["string-width"], "publish package should include string-width for CLI rendering");
|
||||
for (const dependencyName of zvecOptionalRuntimeDependencies) {
|
||||
assert.equal(
|
||||
packageJson.dependencies?.[dependencyName],
|
||||
undefined,
|
||||
`${dependencyName} should not be a hard publish package dependency`,
|
||||
);
|
||||
assert.ok(
|
||||
packageJson.optionalDependencies?.[dependencyName],
|
||||
`${dependencyName} should be an optional publish package dependency`,
|
||||
);
|
||||
}
|
||||
assert.deepEqual(packageJson.pnpm?.onlyBuiltDependencies, [
|
||||
"@zvec/zvec",
|
||||
"better-sqlite3",
|
||||
"keytar",
|
||||
]);
|
||||
assert.deepEqual(packageJson.files, [
|
||||
"dist/logseq.js",
|
||||
"static/logseq-cli.js",
|
||||
"static/js/db-worker-node.js",
|
||||
"static/js/db-worker-node-assets.json",
|
||||
".agents/skills/logseq-cli/SKILL.md",
|
||||
]);
|
||||
|
||||
for (const relativePath of packageJson.files) {
|
||||
assert.equal(
|
||||
fs.existsSync(path.join(packageRoot, relativePath)),
|
||||
true,
|
||||
`publish package should include ${relativePath}`,
|
||||
);
|
||||
}
|
||||
|
||||
assertNoShadowRuntime(readText("static/logseq-cli.js"), "root CLI release artifact");
|
||||
assertNoShadowRuntime(
|
||||
fs.readFileSync(path.join(packageRoot, "static/logseq-cli.js"), "utf8"),
|
||||
"publish package CLI release artifact",
|
||||
);
|
||||
Reference in New Issue
Block a user