Ratatoskr/scripts/buildSha.js
Void c45cdc7bd7
All checks were successful
Build pull request / Build pull request (pull_request) Successful in 3m24s
buildSha: BUILD_SHA no longer masks a dirty tree; the vite temp-file exclusion is untracked-only
The pathspec exclude hid a tracked file of that name; the status lines are filtered instead.
Tests against a real repo: explicit sha plus stray file, stray file beside the temp file in root and
subfolder, tracked file of the temp name, new folder. Six mutants of the logic each fail a test.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-13 06:52:32 +00:00

43 lines
2 KiB
JavaScript

// The commit a bundle was built from, stamped into its index.html as <meta name="build-sha">, so a built
// folder says what it is without a build log: `grep -o 'build-sha" content="[^"]*' dist/index.html`.
// BUILD_SHA names the commit when it is a full sha (a build from a source archive has no git), otherwise git's
// HEAD does; either way -dirty is appended when git can see uncommitted changes, so an explicit sha never
// vouches for a tree that differs from it. No git and no BUILD_SHA: 'unknown'.
import { execFileSync } from 'child_process';
import { dirname, resolve } from 'path';
import { fileURLToPath } from 'url';
const REPO_DIR = resolve(dirname(fileURLToPath(import.meta.url)), '..');
// Vite writes vite.config.js.timestamp-*.mjs next to the config while it loads it; that file is the
// build's own, not source, so an UNTRACKED one must not mark the tree dirty. A tracked file of that name,
// or any other change, still does. Filtering status lines (not a pathspec) is what keeps it untracked-only.
const VITE_TEMP = /^\?\? (?:.*\/)?vite\.config\.[^/]*\.timestamp-[^/]*\.mjs$/;
export const gitIn =
(cwd) =>
(...args) =>
execFileSync('git', args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
export const readBuildSha = (env = process.env, run = gitIn(REPO_DIR)) => {
const fromEnv = (env.BUILD_SHA ?? '').trim();
const explicit = /^[0-9a-f]{40}$/.test(fromEnv) ? fromEnv : null;
try {
const head = run('rev-parse', 'HEAD').trim();
const sha = explicit ?? head;
if (!/^[0-9a-f]{40}$/.test(sha)) return 'unknown';
const dirty = run('status', '--porcelain', '-uall', '--', '.')
.split('\n')
.some((l) => l.trim() !== '' && !VITE_TEMP.test(l));
return dirty ? `${sha}-dirty` : sha;
} catch {
return explicit ?? 'unknown';
}
};
export const buildShaPlugin = (sha = readBuildSha()) => ({
name: 'ratatoskr-build-sha',
transformIndexHtml: () => [
{ tag: 'meta', attrs: { name: 'build-sha', content: sha }, injectTo: 'head-prepend' },
],
});