89 lines
3.2 KiB
JavaScript
89 lines
3.2 KiB
JavaScript
import fs from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { execFileSync } from "node:child_process";
|
|
|
|
export async function buildService({
|
|
config,
|
|
compiler,
|
|
pkg,
|
|
output,
|
|
coreServer,
|
|
sharedFiles,
|
|
implementations,
|
|
recordFile,
|
|
}) {
|
|
const relative = compiler.artifactPath;
|
|
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) });
|
|
}
|
|
}
|