feat: add plugin packing, signing, pinned trust and tmxplug bundling

This commit is contained in:
LukeGus committed 2026-09-25 09:21:28 -05:00
1 parent c6a44b7ff3
commit 2ee9b3592c
25 files changed
+1764 -56

No files matched your search

+49 -23
View File
@@ -1,39 +1,39 @@
/**
* Builds every plugin workspace, then stages the result in dist/plugins.
* Stages the plugins listed in docker/bundled-plugins.json in dist/plugins.
*
* getBundledPluginsDir() in src/backend/plugins/paths.ts resolves
* dist/backend/backend/plugins -> dist/plugins, which is where this writes.
* Only what a server needs at runtime is copied: the manifest, the built
* bundles, locales and migrations. Source and tests stay out of the image.
* A workspace plugin is built from plugins/<id>; a tmxplug plugin is
* downloaded (or read from a path), checked against its pinned sha256 and
* unpacked. Either way only what a server needs at runtime lands there.
*/
const fs = require("node:fs");
const path = require("node:path");
const { execFileSync } = require("node:child_process");
const {
loadBundledPlugins,
fetchArtifact,
extractArtifact,
} = require("./lib/bundled-plugins.cjs");
const root = path.resolve(__dirname, "..");
const source = path.join(root, "plugins");
const destination = path.join(root, "dist", "plugins");
const cli = path.join(root, "packages", "plugin-sdk", "cli", "index.mjs");
const SHIPPED = ["manifest.json", "dist", "locales", "migrations", "README.md"];
// Same list as a .tmxplug, see packages/plugin-sdk/cli/lib/tmxplug.mjs.
const SHIPPED = [
"manifest.json",
"dist",
"locales",
"migrations",
"README.md",
"CHANGELOG.md",
"icon.svg",
];
if (!fs.existsSync(source)) {
console.log("No plugins/ directory, nothing to bundle.");
process.exit(0);
}
const pluginIds = fs
.readdirSync(source, { withFileTypes: true })
.filter((entry) => entry.isDirectory())
.filter((entry) =>
fs.existsSync(path.join(source, entry.name, "manifest.json")),
)
.map((entry) => entry.name);
fs.rmSync(destination, { recursive: true, force: true });
for (const id of pluginIds) {
function stageWorkspace(id) {
const pluginDir = path.join(source, id);
execFileSync(process.execPath, [cli, "build"], {
@@ -41,8 +41,7 @@ for (const id of pluginIds) {
stdio: "inherit",
});
const built = path.join(pluginDir, "dist", "backend.js");
if (!fs.existsSync(built)) {
if (!fs.existsSync(path.join(pluginDir, "dist", "backend.js"))) {
throw new Error(`${id} produced no dist/backend.js`);
}
@@ -55,4 +54,31 @@ for (const id of pluginIds) {
}
}
console.log(`Bundled ${pluginIds.length} plugin(s): ${pluginIds.join(", ")}`);
async function main() {
const plugins = loadBundledPlugins(root);
fs.rmSync(destination, { recursive: true, force: true });
for (const plugin of plugins) {
if (plugin.source === "workspace") {
stageWorkspace(plugin.id);
} else {
const buffer = await fetchArtifact(plugin, root);
await extractArtifact(
buffer,
path.join(destination, plugin.id),
plugin.id,
);
console.log(`unpacked ${plugin.id}`);
}
}
console.log(
`Bundled ${plugins.length} plugin(s): ${plugins.map((p) => p.id).join(", ")}`,
);
}
main().catch((error) => {
console.error(error?.message ?? error);
process.exit(1);
});