Files
yaak-mountain-loop/scripts/bundle-sandbox-plugins.mjs
T
Gregory Schier 85ba3e6852 Cut comments back to the non-obvious
Rationale that explains a decision rather than the code below it belongs in
the sandbox README or the PR, not in a header paragraph on every file.
2026-08-18 15:23:10 -07:00

97 lines
3.3 KiB
JavaScript

/**
* A stand-in for `yaakcli build --target sandbox`, which does not exist yet.
* What the CLI would need instead is at the bottom of this file.
*/
import { build } from "esbuild";
import { mkdirSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
/** Three, not the corpus: one template function, one importer, one auth method. */
const PLUGINS = ["template-function-timestamp", "importer-curl", "auth-bearer"];
const noNodeBuiltins = {
name: "no-node-builtins",
setup(build) {
build.onResolve({ filter: /^(node:|fs$|path$|crypto$|buffer$|process$|os$|util$|stream$)/ }, (args) => ({
errors: [
{
text:
`\`${args.path}\` is not available in the sandbox runtime. ` +
`Replace it with a pure-JavaScript equivalent.`,
},
],
}));
},
};
export async function bundlePlugin(name, { dir = join(root, "plugins", name) } = {}) {
const result = await build({
entryPoints: [join(dir, "src", "index.ts")],
bundle: true,
write: false,
format: "cjs",
platform: "browser",
target: "es2022",
minify: false,
legalComments: "none",
plugins: [noNodeBuiltins],
});
return result.outputFiles[0].text;
}
async function main() {
const bundles = [];
for (const name of PLUGINS) {
const source = await bundlePlugin(name);
bundles.push({ name, source });
console.log(`${name}: ${(source.length / 1024).toFixed(1)} KB`);
}
const outFile = join(root, "packages", "platform", "src", "web", "sandboxPlugins.generated.ts");
mkdirSync(dirname(outFile), { recursive: true });
writeFileSync(
outFile,
[
"// Generated by scripts/bundle-sandbox-plugins.mjs. Do not edit.",
"//",
"// The plugins the browser tier loads into its sandbox, bundled for that",
"// target and inlined as source text.",
"",
"export interface SandboxPluginBundle {",
" name: string;",
" source: string;",
"}",
"",
"export const SANDBOX_PLUGINS: SandboxPluginBundle[] = [",
...bundles.map((b) => ` { name: ${JSON.stringify(b.name)}, source: ${JSON.stringify(b.source)} },`),
"];",
"",
].join("\n"),
);
console.log(`Wrote ${outFile}`);
}
if (import.meta.url === `file://${process.argv[1]}`) await main();
/*
* What `yaakcli build --target sandbox` would need, beyond this:
*
* 1. `Platform::Browser` in the rolldown options (crates-cli/yaak-cli/src/
* commands/plugin.rs `bundler_options`), plus a resolver that fails on a
* Node built-in instead of shimming it — a silent shim turns a missing
* capability into a runtime error inside someone else's plugin.
* 2. A `runtime` field in the plugin manifest, so a plugin declares which
* target it is for and the registry can refuse to install a `node` plugin
* on a host that has no Node.
* 3. Both targets emitted for the same source where they both work, since a
* desktop with a sandbox and a desktop with Node are the same install.
* 4. Distribution as files, not as inlined strings. Inlining is what this
* script does because three small bundles cost less than an asset pipeline;
* the corpus does not, and a plugin the user installs at runtime cannot be
* inlined at build time at all.
*/