88 lines
3.1 KiB
JavaScript
88 lines
3.1 KiB
JavaScript
// Write the build stamp for the server.
|
|
//
|
|
// The server `build` script runs this after `tsc`. It writes the commit SHA
|
|
// into `dist/build-info.json`. The instrumentation module reads that stamp to
|
|
// report `service.version`, so the value tracks the true built commit.
|
|
//
|
|
// The build resolves the commit in two steps:
|
|
// 1. `git rev-parse --short HEAD` in the server directory.
|
|
// 2. The `PAPERCLIP_BUILD_COMMIT` environment variable.
|
|
// A Docker image build excludes `.git`, so the git lookup fails there. The
|
|
// image build passes the commit in `PAPERCLIP_BUILD_COMMIT` instead, so the
|
|
// stamp still records the true built commit.
|
|
//
|
|
// The build must not fail when no commit is available. A missing `git`, a
|
|
// checkout with no `.git`, and an unset `PAPERCLIP_BUILD_COMMIT` together write
|
|
// no stamp and exit 0.
|
|
|
|
import { execFileSync } from "node:child_process";
|
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
import { dirname, join } from "node:path";
|
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
|
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
const serverDir = join(scriptDir, "..");
|
|
const distDir = join(serverDir, "dist");
|
|
const outFile = join(distDir, "build-info.json");
|
|
|
|
/**
|
|
* Resolve the commit for the build stamp. Prefer the git commit. Fall back to
|
|
* the supplied commit — the value a Docker image build passes in
|
|
* `PAPERCLIP_BUILD_COMMIT` when `.git` is absent. Return null when neither
|
|
* source gives a non-empty value.
|
|
*
|
|
* @param {unknown} gitCommit The `git rev-parse` result, or null on failure.
|
|
* @param {unknown} suppliedCommit The `PAPERCLIP_BUILD_COMMIT` value.
|
|
* @returns {string | null}
|
|
*/
|
|
export function resolveBuildCommit(gitCommit, suppliedCommit) {
|
|
const git = typeof gitCommit === "string" ? gitCommit.trim() : "";
|
|
if (git) return git;
|
|
const supplied = typeof suppliedCommit === "string" ? suppliedCommit.trim() : "";
|
|
if (supplied) return supplied;
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Read the short commit SHA with `git rev-parse --short HEAD` in the server
|
|
* directory. Return the SHA, or null on any failure.
|
|
*
|
|
* @returns {string | null}
|
|
*/
|
|
function readGitCommit() {
|
|
try {
|
|
const out = execFileSync("git", ["rev-parse", "--short", "HEAD"], {
|
|
cwd: serverDir,
|
|
stdio: ["ignore", "pipe", "ignore"],
|
|
})
|
|
.toString()
|
|
.trim();
|
|
return out.length > 0 ? out : null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resolve the commit and write the build stamp. Write no stamp and return when
|
|
* no commit is available, so the build continues.
|
|
*/
|
|
function main() {
|
|
const commit = resolveBuildCommit(readGitCommit(), process.env.PAPERCLIP_BUILD_COMMIT);
|
|
|
|
if (!commit) {
|
|
console.log("[build-stamp] no commit available; wrote no build stamp");
|
|
return;
|
|
}
|
|
|
|
mkdirSync(distDir, { recursive: true });
|
|
writeFileSync(outFile, `${JSON.stringify({ commit }, null, 2)}\n`);
|
|
console.log(`[build-stamp] wrote ${outFile} commit=${commit}`);
|
|
}
|
|
|
|
// Run only when node invokes this file directly (the `build` script). A test
|
|
// that imports `resolveBuildCommit` does not run `main`.
|
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
main();
|
|
}
|