diff --git a/packages/extension-base/README.md b/packages/extension-base/README.md index 844c708..b195c92 100644 --- a/packages/extension-base/README.md +++ b/packages/extension-base/README.md @@ -18,4 +18,10 @@ The shared CLI is modeled on `apps/roam` in the Discourse Graphs monorepo. It bu `roam-prototype dev` watches the source and serves `dist/` from a local URL. `roam-prototype build` creates a minified production bundle without source maps. README and CHANGELOG files are copied into `dist/`; imported CSS is emitted as `extension.css`. -The starter still uses `roamjs-components/util/runExtension`. Its production error reporting to SamePage is behavior inside `roamjs-components`, independent of which bundler produced `extension.js`; reports include the graph name and extension settings. Never store credentials or sensitive data in extension settings. +The starter imports the lifecycle wrapper as a named export: + +```ts +import { runExtension } from "roamjs-components/util"; +``` + +Its production error reporting to SamePage is behavior inside `roamjs-components`, independent of which bundler produced `extension.js`; reports include the graph name and extension settings. Never store credentials or sensitive data in extension settings. diff --git a/packages/extension-base/scripts/cli.mjs b/packages/extension-base/scripts/cli.mjs index fc88469..d675220 100644 --- a/packages/extension-base/scripts/cli.mjs +++ b/packages/extension-base/scripts/cli.mjs @@ -1,6 +1,7 @@ #!/usr/bin/env node import * as esbuild from "esbuild"; +import { realpathSync } from "node:fs"; import { copyFile, mkdir, readFile, rm, stat } from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; @@ -181,7 +182,24 @@ const run = async () => { throw new Error("Usage: roam-prototype [--port ]"); }; -const isCli = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url); +const normalizePath = (value) => { + const normalized = path.normalize(value); + return process.platform === "win32" ? normalized.toLowerCase() : normalized; +}; + +export const pathsReferToSameFile = (left, right) => { + if (!left || !right) return false; + const resolvedLeft = path.resolve(left); + const resolvedRight = path.resolve(right); + try { + return normalizePath(realpathSync.native(resolvedLeft)) === + normalizePath(realpathSync.native(resolvedRight)); + } catch { + return normalizePath(resolvedLeft) === normalizePath(resolvedRight); + } +}; + +const isCli = pathsReferToSameFile(process.argv[1], fileURLToPath(import.meta.url)); if (isCli) { run().catch((error) => { console.error(error instanceof Error ? error.message : error); diff --git a/packages/extension-base/template/src/index.ts b/packages/extension-base/template/src/index.ts index addfa1f..bcbd195 100644 --- a/packages/extension-base/template/src/index.ts +++ b/packages/extension-base/template/src/index.ts @@ -1,5 +1,5 @@ import { render as renderToast } from "roamjs-components/components/Toast"; -import runExtension from "roamjs-components/util/runExtension"; +import { runExtension } from "roamjs-components/util"; import "./styles.css"; export default runExtension(async () => { diff --git a/test/create-prototype.test.mjs b/test/create-prototype.test.mjs index d4080db..d8a7ad7 100644 --- a/test/create-prototype.test.mjs +++ b/test/create-prototype.test.mjs @@ -52,9 +52,17 @@ test("creates a complete prototype with catalog dependencies", async () => { assert.equal(manifest.devDependencies["@samepage/scripts"], undefined); assert.equal(manifest.dependencies["roamjs-components"], "catalog:"); assert.equal(manifest.dependencies["use-sync-external-store"], "catalog:"); + const entry = await readFile( + path.join(result.destination, "src", "index.ts"), + "utf8", + ); assert.match( - await readFile(path.join(result.destination, "src", "index.ts"), "utf8"), - /runExtension/, + entry, + /import \{ runExtension \} from "roamjs-components\/util";/, + ); + assert.doesNotMatch( + entry, + /import runExtension from "roamjs-components\/util\/runExtension";/, ); }); }); diff --git a/test/extension-cli.test.mjs b/test/extension-cli.test.mjs new file mode 100644 index 0000000..84237f1 --- /dev/null +++ b/test/extension-cli.test.mjs @@ -0,0 +1,33 @@ +import assert from "node:assert/strict"; +import { mkdir, mkdtemp, rm, symlink, writeFile } from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; +import { pathsReferToSameFile } from "../packages/extension-base/scripts/cli.mjs"; + +test("recognizes a CLI invoked through a workspace directory link", async () => { + const root = await mkdtemp(path.join(os.tmpdir(), "roam-extension-cli-")); + const realDirectory = path.join(root, "real"); + const linkedDirectory = path.join(root, "linked"); + const filename = "cli.mjs"; + + try { + await mkdir(realDirectory); + await writeFile(path.join(realDirectory, filename), "", "utf8"); + await symlink( + realDirectory, + linkedDirectory, + process.platform === "win32" ? "junction" : "dir", + ); + + assert.equal( + pathsReferToSameFile( + path.join(linkedDirectory, filename), + path.join(realDirectory, filename), + ), + true, + ); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); diff --git a/test/starter-integration.test.mjs b/test/starter-integration.test.mjs index 757368a..cbe5ce8 100644 --- a/test/starter-integration.test.mjs +++ b/test/starter-integration.test.mjs @@ -75,7 +75,7 @@ test( "--lockfile=false", "--force", ]; - if (!process.env.CI) installArguments.push("--offline"); + if (!process.env.CI) installArguments.push("--prefer-offline"); run(installArguments, repoRoot); run(["--dir", destination, "test"], repoRoot); const recursiveBuild = run( @@ -83,6 +83,7 @@ test( repoRoot, ); assert.match(recursiveBuild, new RegExp(name)); + assert.match(recursiveBuild, /Built dist/); assert.doesNotMatch(recursiveBuild, /readme-only-placeholder@/); for (const file of [