Build composable Camino packages with explicit type and source dependencies

This commit is contained in:
Timothy J. Aveni
2026-09-22 13:51:32 -07:00
parent 85734e3003
commit 0da022e216
12 changed files with 508 additions and 78 deletions
+8 -73
View File
@@ -1,3 +1,4 @@
import { buildService } from "./build-service.mjs";
import { buildComponents } from "./build-components.mjs";
/** Trusted Nix build driver. No package-defined build hooks run between generation and witnessing. */
import fs from "node:fs/promises";
@@ -161,79 +162,13 @@ for (const { target, mode, settings } of builds) {
};
implementations.push({ ...body, contentDigest: compiler.implementationDigest(body) });
}
const components = await buildComponents({ config, compiler, plan, pkg, output, recordFile, generated });
if (pkg.exports.length || config.service.entry) {
await fs.mkdir(path.join(output, "bin"), { recursive: true });
await fs.mkdir(path.join(output, "share/quixos/service"), { recursive: true });
let serviceEntry;
if (config.service.entry) {
if (!/\.m?js$/.test(config.service.entry))
throw Error("Custom service entry must be JavaScript; use a separate typed/language build before packaging");
serviceEntry = path.join(config.source, relative(config.service.entry));
} else {
const modules = implementations.filter((implementation) => implementation.target === "server");
if (pkg.exports.some((entry) => !modules.some((module) => module.exports.includes(entry.id))))
throw Error("Generated service does not cover every package export; provide a custom service");
if (!modules.length) throw Error("Generated service requires a checked registry");
serviceEntry = path.resolve("service-entry.mjs");
const imports = modules
.map(
(module, index) =>
`import { ${module.registryExport} as registry${index} } from ${JSON.stringify(path.join(output, module.path))};`,
)
.join("\n");
await fs.writeFile(
serviceEntry,
imports +
`\nimport {servePortableRegistry} from ${JSON.stringify(config.serviceAdapter)};
const registries = [${modules.map((_, index) => "registry" + index).join(",")}];
const owners = new Map();
for (const registry of registries) for (const id of registry.exports) {
if (owners.has(id)) throw Error("Duplicate registry export " + id);
owners.set(id, registry);
}
servePortableRegistry({exports: [...owners.keys()], invoke(request, channel, context) {
const registry = owners.get(request.exportId);
if (!registry) throw Error("Unknown export");
return registry.invoke(request, channel, context);
}});\n`,
);
}
const serviceModule = "share/quixos/service/main.mjs";
execFileSync(
config.esbuild,
[
serviceEntry,
"--bundle",
"--platform=node",
"--format=esm",
"--minify-whitespace",
"--target=es2024",
`--external:${coreServer}`,
`--outfile=${path.join(output, serviceModule)}`,
],
{ stdio: "inherit" },
);
await fs.writeFile(
path.join(output, "bin/service"),
`#!${config.shell}\nservice_root=\${0%/*}/..\nexec ${config.node} "$service_root/${serviceModule}" "$@"\n`,
{ mode: 0o755 },
);
const serviceBody = {
id: "service",
target: "server",
compatibility: "quixos-service-v1",
path: "bin/service",
ports: "quixos-transaction-ports-v1",
exports: pkg.exports.map((e) => e.id),
files: [
await recordFile("bin/service", "text/x-shellscript"),
await recordFile(serviceModule, "text/javascript"),
...sharedFiles,
],
};
implementations.unshift({ ...serviceBody, contentDigest: compiler.implementationDigest(serviceBody) });
}
const components =
config.group && !config.components
? []
: await buildComponents({ config, compiler, plan, pkg, output, recordFile, generated });
if (!config.group || config.service.entry)
await buildService({ config, compiler, pkg, output, coreServer, sharedFiles, implementations, recordFile });
const manifest = compiler.parsePackageArtifacts({
schemaVersion: 1,
packageRevisionId: pkg.revision,