Two builds of the same source tree could publish different artifacts because buildEmbeddedPatterns.ts and buildTypeEmbeddings.ts stamped their generated output with new Date().toISOString(). Route both generators' "Generated:" header and the generatedAt runtime field through a shared resolver: newest git commit time among the generator's inputs (script + source data), falling back to the stamp already present in the previous output when git is unavailable (e.g. a published tarball build), and finally to a fixed epoch value. Every fallback logs to stderr so degradation is never silent. Regenerated both committed output files once so the tree carries deterministic stamps; no other content changed.
118 lines
3.6 KiB
TypeScript
118 lines
3.6 KiB
TypeScript
/**
|
|
* Deterministic generation-stamp resolution for Brainy's build-time code
|
|
* generators.
|
|
*
|
|
* Two builds of the same source tree must produce byte-identical output.
|
|
* A wall-clock stamp (`new Date()`) breaks that guarantee, so every
|
|
* generator that writes a "Generated:" header or a `generatedAt` field
|
|
* into its output must resolve the stamp through this module instead.
|
|
*
|
|
* Resolution order:
|
|
* 1. The newest git commit timestamp among the generator's input files
|
|
* (the generator script itself always counts as an input).
|
|
* 2. If git metadata is unavailable (for example, building from a
|
|
* published npm tarball with no `.git` directory), the stamp already
|
|
* recorded in the previously generated output file.
|
|
* 3. If neither is available, the fixed epoch string
|
|
* `1970-01-01T00:00:00.000Z`.
|
|
*
|
|
* Every fallback logs a line to stderr — deterministic degradation is
|
|
* loud, never a silent divergence.
|
|
*/
|
|
|
|
import { execFileSync } from 'child_process'
|
|
import * as fs from 'fs'
|
|
|
|
const EPOCH_STAMP = '1970-01-01T00:00:00.000Z'
|
|
const STAMP_PATTERN = /\*\s*Generated:\s*(\S+)/
|
|
|
|
/**
|
|
* Resolve the deterministic stamp for a generator run.
|
|
*
|
|
* @param inputPaths Absolute paths to every file whose content determines
|
|
* the generator's output, including the generator script itself.
|
|
* @param previousOutputPath Absolute path to the previously generated
|
|
* file, used for the existing-stamp fallback when git is unavailable.
|
|
* @returns An ISO-8601 timestamp string that is deterministic for a given
|
|
* source tree.
|
|
*/
|
|
export function resolveDeterministicStamp(
|
|
inputPaths: string[],
|
|
previousOutputPath: string
|
|
): string {
|
|
const gitStamp = newestGitCommitTimestamp(inputPaths)
|
|
if (gitStamp) {
|
|
return gitStamp
|
|
}
|
|
|
|
const existingStamp = readExistingStamp(previousOutputPath)
|
|
if (existingStamp) {
|
|
process.stderr.write(
|
|
`[deterministic-stamp] no git commit history found for generator inputs; ` +
|
|
`reusing existing stamp from ${previousOutputPath}: ${existingStamp}\n`
|
|
)
|
|
return existingStamp
|
|
}
|
|
|
|
process.stderr.write(
|
|
`[deterministic-stamp] no git commit history and no previous output at ` +
|
|
`${previousOutputPath}; falling back to fixed epoch stamp ${EPOCH_STAMP}\n`
|
|
)
|
|
return EPOCH_STAMP
|
|
}
|
|
|
|
/**
|
|
* Find the newest git commit timestamp among the given input paths.
|
|
* Returns null if git is unavailable, the tree is not a git repository,
|
|
* or none of the inputs have any commit history yet.
|
|
*/
|
|
function newestGitCommitTimestamp(inputPaths: string[]): string | null {
|
|
let newest: string | null = null
|
|
|
|
for (const inputPath of inputPaths) {
|
|
if (!fs.existsSync(inputPath)) {
|
|
continue
|
|
}
|
|
|
|
let out: string
|
|
try {
|
|
out = execFileSync(
|
|
'git',
|
|
['log', '-1', '--format=%cI', '--', inputPath],
|
|
{ stdio: ['ignore', 'pipe', 'ignore'] }
|
|
)
|
|
.toString()
|
|
.trim()
|
|
} catch {
|
|
// git missing, not a repository, or no permissions — handled by the
|
|
// caller's fallback chain.
|
|
continue
|
|
}
|
|
|
|
if (!out) {
|
|
// Path exists but has no commit history yet (e.g. newly created,
|
|
// uncommitted file).
|
|
continue
|
|
}
|
|
|
|
if (!newest || new Date(out).getTime() > new Date(newest).getTime()) {
|
|
newest = out
|
|
}
|
|
}
|
|
|
|
return newest
|
|
}
|
|
|
|
/**
|
|
* Parse the `* Generated: <ISO timestamp>` header out of a previously
|
|
* generated file, if one exists.
|
|
*/
|
|
function readExistingStamp(outputPath: string): string | null {
|
|
if (!fs.existsSync(outputPath)) {
|
|
return null
|
|
}
|
|
|
|
const content = fs.readFileSync(outputPath, 'utf-8')
|
|
const match = content.match(STAMP_PATTERN)
|
|
return match ? match[1] : null
|
|
}
|