Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion packages/extension-base/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
20 changes: 19 additions & 1 deletion packages/extension-base/scripts/cli.mjs
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -181,7 +182,24 @@ const run = async () => {
throw new Error("Usage: roam-prototype <build|dev> [--port <number>]");
};

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);
Expand Down
2 changes: 1 addition & 1 deletion packages/extension-base/template/src/index.ts
Original file line number Diff line number Diff line change
@@ -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";
Comment thread
mdroidian marked this conversation as resolved.
import "./styles.css";

export default runExtension(async () => {
Expand Down
12 changes: 10 additions & 2 deletions test/create-prototype.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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";/,
);
});
});
Expand Down
33 changes: 33 additions & 0 deletions test/extension-cli.test.mjs
Original file line number Diff line number Diff line change
@@ -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 });
}
});
3 changes: 2 additions & 1 deletion test/starter-integration.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,15 @@ 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(
["--recursive", "--if-present", "--filter", "./prototypes/**", "build"],
repoRoot,
);
assert.match(recursiveBuild, new RegExp(name));
assert.match(recursiveBuild, /Built dist/);
assert.doesNotMatch(recursiveBuild, /readme-only-placeholder@/);

for (const file of [
Expand Down