#!/usr/bin/env node import { execFileSync } from 'node:child_process' import { mkdir, readFile, readdir, rename, rm, writeFile } from 'node:fs/promises' import { dirname, join, resolve } from 'node:path' import { pathToFileURL } from 'node:url' const COMMIT_PATTERN = /^[0-9a-f]{40}$/u const VERSION_PATTERN = /^\d+\.\d+\.\d+$/u const ARCHITECTURE_VERSION_PATTERN = /^\d{4}-\d{2}-\d{2}\.\d+$/u const DEVELOPMENT_ID_PATTERN = /^DEV-\d{8}-\d{3}$/u const CANONICAL_REPOSITORY = 'repo://guanghulab.com/code/bingshuo/hololake-system-architecture' const CANONICAL_REPOSITORY_ID = 'REPO-014' const CANONICAL_SOURCE_PATH = 'product-source/hololake-platform' const PROVENANCE_FILE_NAME = 'build-provenance.json' function requireExactString(value, label, pattern) { if (typeof value !== 'string' || !pattern.test(value)) { throw new Error(`invalid ${label}`) } return value } export function buildInternalReleaseProvenance(input) { if (input.worktreeClean !== true) { throw new Error('internal release provenance requires a clean source worktree') } if (input.sourceRepository !== CANONICAL_REPOSITORY) { throw new Error('invalid source repository') } if (input.sourceRepositoryId !== CANONICAL_REPOSITORY_ID) { throw new Error('invalid source repository id') } if (input.sourcePath !== CANONICAL_SOURCE_PATH) { throw new Error('invalid source path') } return { schema: 'hololake.internal-release-provenance/v1', architecture: { id: requireExactString(input.architectureId, 'architecture id', /^HLP-[A-Z0-9-]+$/u), version: requireExactString(input.architectureVersion, 'architecture version', ARCHITECTURE_VERSION_PATTERN), }, build: { application_version: requireExactString(input.applicationVersion, 'application version', VERSION_PATTERN), development_id: requireExactString(input.developmentId, 'development id', DEVELOPMENT_ID_PATTERN), distribution: requireExactString(input.distribution, 'distribution', /^[a-z][a-z0-9-]+$/u), recorded_at: requireExactString(input.recordedAt, 'recorded at', /^\d{4}-\d{2}-\d{2}T/u), target_triple: requireExactString(input.targetTriple, 'target triple', /^[a-z0-9-]+$/u), }, source: { branch: requireExactString(input.sourceBranch, 'source branch', /^[A-Za-z0-9._/-]+$/u), commit: requireExactString(input.sourceCommit, 'source commit', COMMIT_PATTERN), path: input.sourcePath, repository: input.sourceRepository, repository_id: input.sourceRepositoryId, tree: requireExactString(input.sourceTree, 'source tree', COMMIT_PATTERN), worktree_clean: true, }, } } function expectEqual(actual, expected, label) { if (actual !== expected) throw new Error(`${label} mismatch`) } export function validateInternalReleaseProvenance(provenance, expected) { if (!provenance || provenance.schema !== 'hololake.internal-release-provenance/v1') { throw new Error('provenance schema mismatch') } expectEqual(provenance.source?.commit, expected.sourceCommit, 'source commit') expectEqual(provenance.source?.tree, expected.sourceTree, 'source tree') expectEqual(provenance.source?.branch, expected.sourceBranch, 'source branch') expectEqual(provenance.source?.repository, expected.sourceRepository, 'source repository') expectEqual(provenance.source?.repository_id, expected.sourceRepositoryId, 'source repository id') expectEqual(provenance.source?.path, expected.sourcePath, 'source path') expectEqual(provenance.source?.worktree_clean, expected.worktreeClean, 'source worktree state') expectEqual(provenance.architecture?.id, expected.architectureId, 'architecture id') expectEqual(provenance.architecture?.version, expected.architectureVersion, 'architecture version') expectEqual(provenance.build?.application_version, expected.applicationVersion, 'application version') expectEqual(provenance.build?.development_id, expected.developmentId, 'development id') expectEqual(provenance.build?.distribution, expected.distribution, 'distribution') expectEqual(provenance.build?.target_triple, expected.targetTriple, 'target triple') return true } function git(repositoryRoot, ...args) { return execFileSync('git', ['-C', repositoryRoot, ...args], { encoding: 'utf8' }).trim() } async function repositoryExpectation({ repositoryRoot, developmentId, distribution, targetTriple }) { const architecture = JSON.parse(await readFile(join(repositoryRoot, 'routing/hololake-current-architecture.json'), 'utf8')) const application = JSON.parse(await readFile(join(repositoryRoot, CANONICAL_SOURCE_PATH, 'src-tauri/tauri.conf.json'), 'utf8')) return { sourceCommit: git(repositoryRoot, 'rev-parse', 'HEAD'), sourceTree: git(repositoryRoot, 'rev-parse', 'HEAD^{tree}'), sourceBranch: git(repositoryRoot, 'branch', '--show-current'), sourceRepository: CANONICAL_REPOSITORY, sourceRepositoryId: CANONICAL_REPOSITORY_ID, sourcePath: CANONICAL_SOURCE_PATH, architectureId: architecture.architecture_id, architectureVersion: architecture.version, applicationVersion: application.version, developmentId, distribution, targetTriple, worktreeClean: git(repositoryRoot, 'status', '--porcelain=v1', '--untracked-files=all') === '', } } async function writeJsonAtomic(outputPath, value) { await mkdir(dirname(outputPath), { recursive: true }) const temporary = `${outputPath}.${process.pid}.tmp` await writeFile(temporary, `${JSON.stringify(value, null, 2)}\n`, { flag: 'wx' }) try { await rename(temporary, outputPath) } catch (error) { await rm(temporary, { force: true }) throw error } } async function findProvenanceFiles(directory) { const found = [] for (const entry of await readdir(directory, { withFileTypes: true })) { const entryPath = join(directory, entry.name) if (entry.isDirectory()) found.push(...await findProvenanceFiles(entryPath)) else if (entry.isFile() && entry.name === PROVENANCE_FILE_NAME) found.push(entryPath) } return found } function option(args, name) { const index = args.indexOf(name) if (index === -1 || !args[index + 1]) throw new Error(`${name} is required`) return args[index + 1] } async function main() { const [command, ...args] = process.argv.slice(2) if (!['generate', 'verify'].includes(command)) { throw new Error('Usage: internal-release-provenance.mjs [options]') } const repositoryRoot = git(process.cwd(), 'rev-parse', '--show-toplevel') const developmentId = option(args, '--development-id') const distribution = option(args, '--distribution') const targetTriple = option(args, '--target-triple') const expected = await repositoryExpectation({ repositoryRoot, developmentId, distribution, targetTriple }) if (command === 'generate') { const outputPath = resolve(option(args, '--output')) const provenance = buildInternalReleaseProvenance({ ...expected, recordedAt: new Date().toISOString(), }) await writeJsonAtomic(outputPath, provenance) process.stdout.write(`${outputPath}\n`) return } const bundlePath = resolve(option(args, '--bundle')) const provenanceFiles = await findProvenanceFiles(bundlePath) if (provenanceFiles.length !== 1) { throw new Error(`expected exactly one packaged ${PROVENANCE_FILE_NAME}, found ${provenanceFiles.length}`) } const provenance = JSON.parse(await readFile(provenanceFiles[0], 'utf8')) validateInternalReleaseProvenance(provenance, expected) process.stdout.write(`${provenanceFiles[0]}\n`) } if (import.meta.url === pathToFileURL(process.argv[1]).href) { await main() }