singularity-forge/scripts/generate-release-manifest.mjs
Mikael Hugo c26de39afa
Some checks are pending
sf self-deploy / build, test, and publish server image (push) Waiting to run
sf self-deploy / deploy test and probe (push) Blocked by required conditions
sf self-deploy / promote prod (push) Blocked by required conditions
feat: add source-mounted sf server self-deploy
2026-05-17 22:00:01 +02:00

112 lines
3 KiB
JavaScript

#!/usr/bin/env node
/**
* generate-release-manifest.mjs — emit the immutable build contract consumed by
* SF server health checks and deployment automation.
*
* Purpose: give Forgejo, Kubernetes, and the running web surface one small JSON
* artifact that proves which source revision, image, and verification gates are
* being promoted.
*
* Consumer: Forgejo self-deploy workflow, Docker image build, and web
* /api/version readiness surfaces.
*/
import { execFileSync } from "node:child_process";
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const args = process.argv.slice(2);
const outIndex = args.indexOf("--out");
const outPath =
outIndex >= 0 && args[outIndex + 1]
? resolve(process.cwd(), args[outIndex + 1])
: resolve(root, "dist", "sf-release-manifest.json");
const packageJson = JSON.parse(
readFileSync(resolve(root, "package.json"), "utf8"),
);
const manifest = {
schemaVersion: 1,
name: packageJson.name,
version: packageJson.version,
git: {
sha:
env("SF_GIT_SHA") ??
env("GITHUB_SHA") ??
env("FORGEJO_SHA") ??
git(["rev-parse", "HEAD"]),
ref:
env("SF_GIT_REF") ??
env("GITHUB_REF_NAME") ??
env("FORGEJO_REF_NAME") ??
env("CI_COMMIT_REF_NAME") ??
git(["rev-parse", "--abbrev-ref", "HEAD"]),
},
image: {
repository:
env("SF_IMAGE_REPOSITORY") ??
env("SF_RELEASE_IMAGE")?.replace(/@sha256:.+$/, "") ??
null,
ref: env("SF_RELEASE_IMAGE") ?? null,
digest: env("SF_RELEASE_IMAGE_DIGEST") ?? null,
registry: env("SF_RELEASE_REGISTRY") ?? null,
},
build: {
source: env("CI_SERVER_NAME") ?? env("GITHUB_SERVER_URL") ?? "local",
workflow: env("GITHUB_WORKFLOW") ?? env("CI_WORKFLOW") ?? null,
runId: env("GITHUB_RUN_ID") ?? env("CI_PIPELINE_ID") ?? null,
runNumber: env("GITHUB_RUN_NUMBER") ?? env("CI_PIPELINE_NUMBER") ?? null,
builtAt: new Date().toISOString(),
node: process.version,
npm: command(["npm", "--version"]),
},
server: {
defaultHost: "0.0.0.0",
defaultPort: 4000,
probes: {
healthz: "/api/healthz",
ready: "/api/ready",
version: "/api/version",
},
},
gates: [
"npm ci",
"npm --prefix web ci",
"npm run build:core",
"npm run build:web-host",
"npm run typecheck:extensions",
"npm run test:unit",
"server:/api/healthz",
"server:/api/ready",
"server:/api/version",
],
};
mkdirSync(dirname(outPath), { recursive: true });
writeFileSync(`${outPath}.tmp`, `${JSON.stringify(manifest, null, 2)}\n`);
renameSync(`${outPath}.tmp`, outPath);
process.stdout.write(`${outPath}\n`);
function env(name) {
const value = process.env[name]?.trim();
return value ? value : null;
}
function git(args) {
return command(["git", ...args]);
}
function command(parts) {
try {
return execFileSync(parts[0], parts.slice(1), {
cwd: root,
encoding: "utf8",
stdio: ["ignore", "pipe", "ignore"],
}).trim();
} catch {
return null;
}
}