mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-08-20 10:24:01 +02:00
Adds packages/plugin-sandbox: QuickJS-ng compiled to wasm, running in a dedicated worker, with a runtime shell inside it that loads a plugin bundle and answers the same InternalEventPayload events the Node runtime answers. Plugins are unmodified. Wires the browser host's template function, authentication, cURL import and template render commands to it, and relaxes TemplateCallback's Send bound on wasm32 so the engine's renderer can call back out to a plugin.
116 lines
4.3 KiB
JavaScript
116 lines
4.3 KiB
JavaScript
/**
|
|
* Bundle plugins for the sandbox runtime.
|
|
*
|
|
* A stand-in for `yaakcli build --target sandbox`, which does not exist yet.
|
|
* The difference from the Node target is small and entirely in the resolver:
|
|
* nothing may resolve to a Node built-in, because the sandbox has none — see
|
|
* `packages/plugin-sandbox/README.md` for the full contract. Bundling here
|
|
* rather than in the CLI keeps the CLI out of this slice; what the CLI would
|
|
* need is written down at the bottom of this file.
|
|
*
|
|
* Output is a generated TypeScript module holding each bundle as a string,
|
|
* which is how the browser host ships them today. That is the part most
|
|
* obviously temporary: see the note at the bottom.
|
|
*/
|
|
|
|
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)), "..");
|
|
|
|
/**
|
|
* The plugins the browser tier ships.
|
|
*
|
|
* Three, not the whole corpus: this slice is about the runtime existing and
|
|
* being proven, and each of these proves a different path through it — a
|
|
* template function, an importer, an authentication method.
|
|
*/
|
|
const PLUGINS = ["template-function-timestamp", "importer-curl", "auth-bearer"];
|
|
|
|
/** Refuse Node built-ins loudly at build time rather than at first call. */
|
|
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,
|
|
// CommonJS because that is what the shell evaluates: a `new Function` with
|
|
// `module`, `exports` and a `require` that only throws.
|
|
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.
|
|
*/
|