ReviewOS

also looking at this

craft-native/craft

fix(dev): validate the pinned Zig toolchain before builds

#299
Merged glennmichael123 wants to merge codex/pinned-zig-toolchain into main
5 files +137 -14
scripts/zig.tsadded+61-0
Changes to scripts/zig.ts
@@ -0,0 +1,61 @@
1import { readFileSync } from 'node:fs'
2import { join, resolve } from 'node:path'
3
4export function pinnedZigVersion(root: string): string {
5 const lock = JSON.parse(readFileSync(join(root, 'pantry.lock'), 'utf8'))
6 const version = lock.workspaces?.['']?.system?.['ziglang.org']
7 if (typeof version !== 'string' || !/^\d+\.\d+\.\d+(?:-dev\.\d+_[a-f0-9]+)?$/.test(version))
8 throw new Error('pantry.lock must contain an exact Zig version in the root workspace system dependencies')
9 return version.replace('_', '+')
10}
11
12export function resolveZig(root: string, env: NodeJS.ProcessEnv = process.env): { path: string, version: string, overridden: boolean } {
13 const expected = pinnedZigVersion(root)
14 const executable = env.CRAFT_ZIG || 'zig'
15 const path = Bun.which(executable, { PATH: env.PATH, cwd: root })
16 const remedy = 'Activate the pinned Pantry toolchain, or set CRAFT_ZIG to its executable. To intentionally test a different snapshot, also set CRAFT_ALLOW_UNPINNED_ZIG=1.'
17 if (!path)
18 throw new Error(`Zig ${expected} was not found (${executable}). ${remedy}`)
19 const result = Bun.spawnSync([path, 'version'], { cwd: root, env, stdout: 'pipe', stderr: 'pipe' })
20 if (result.exitCode !== 0)
21 throw new Error(`Cannot read Zig version from ${path}: ${result.stderr.toString().trim()}. ${remedy}`)
22 const version = result.stdout.toString().trim()
23 if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.+-]+)?$/.test(version))
24 throw new Error(`Invalid Zig version from ${path}: ${JSON.stringify(version)}`)
25 const overridden = version !== expected
26 if (overridden && env.CRAFT_ALLOW_UNPINNED_ZIG !== '1')
27 throw new Error(`Zig version mismatch: ${path} reports ${version}; pantry.lock requires ${expected}. ${remedy}`)
28 return { path, version, overridden }
29}
30
31export function runZig(root: string, args: string[], env: NodeJS.ProcessEnv = process.env): number {
32 const command = [...args]
33 const core = command[0] === '--core'
34 if (core) command.shift()
35 const versioned = command[0] === '--versioned'
36 if (versioned) command.shift()
37 if (versioned) {
38 const { version } = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'))
39 command.push(`-Dversion=${version}`)
40 }
41 const zig = resolveZig(root, env)
42 console.error(`Zig ${zig.version}: ${zig.path}${zig.overridden ? ' (explicit unpinned override)' : ''}`)
43 const result = Bun.spawnSync([zig.path, ...command], {
44 cwd: core ? join(root, 'packages/zig') : root,
45 env,
46 stdin: 'inherit',
47 stdout: 'inherit',
48 stderr: 'inherit',
49 })
50 return result.exitCode
51}
52
53if (import.meta.main) {
54 try {
55 process.exitCode = runZig(resolve(import.meta.dir, '..'), process.argv.slice(2))
56 }
57 catch (error) {
58 console.error(error instanceof Error ? error.message : String(error))
59 process.exitCode = 1
60 }
61}