ReviewOS

also looking at this

craft-native/craft

fix: validate native SDK release prerequisites before tagging

#364
Merged chris wants to merge codex/native-sdk-release-preflight into main
7 files +153 -15

Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.

scripts/release-version.test.tsmodified+15-1
Changes to scripts/release-version.test.ts
@@ -1,5 +1,5 @@
11import { afterAll, expect, test } from 'bun:test'
2import { mkdirSync, rmSync, writeFileSync } from 'node:fs'
2import { mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
33import { join } from 'node:path'
44import { validateReleaseCheckout, checkReleaseVersion, validateReleaseVersion } from './release-version'
55import { createTempDir, removeAllTempDirs } from '../packages/typescript/src/temp-dir'
@@ -60,3 +60,17 @@ test('refuses an incomplete sparse release checkout before a version bump', () =
6060 rmSync(manifest)
6161 expect(() => validateReleaseCheckout(root)).toThrow('packages/react/package.json')
6262})
63
64test('release preflight rejects missing tools before changing package versions or tags', () => {
65 const root = createTempDir('craft-release-prerequisites-')
66 mkdirSync(join(root, 'scripts'))
67 const manifest = '{"version":"1.2.3"}\n'
68 writeFileSync(join(root, 'package.json'), manifest)
69 writeFileSync(join(root, 'scripts/release-version.ts'), readFileSync(join(import.meta.dir, 'release-version.ts')))
70 expect(Bun.spawnSync(['git', 'init', '-q'], { cwd: root }).exitCode).toBe(0)
71 const result = Bun.spawnSync([process.execPath, 'scripts/release-version.ts', '--preflight'], { cwd: root, stdout: 'pipe', stderr: 'pipe' })
72 expect(result.exitCode).not.toBe(0)
73 expect(result.stderr.toString()).toContain('bun install --frozen-lockfile')
74 expect(readFileSync(join(root, 'package.json'), 'utf8')).toBe(manifest)
75 expect(Bun.spawnSync(['git', 'tag', '--list'], { cwd: root, stdout: 'pipe' }).stdout.toString()).toBe('')
76})
scripts/release-version.tsmodified+11-0
Changes to scripts/release-version.ts
@@ -10,6 +10,16 @@ export function validateReleaseCheckout(root: string): void {
1010 throw new Error(`Release checkout omits package manifests: ${missing.join(', ')}. Expand the sparse checkout before releasing.`)
1111}
1212
13/** Refuse missing release tools before a version or immutable tag can change. */
14export function validateReleaseDependencies(root: string): void {
15 for (const cli of ['@stacksjs/bumpx/bin/cli.js', '@stacksjs/logsmith/bin/cli.js']) {
16 try { Bun.resolveSync(cli, root) }
17 catch (cause) {
18 throw new Error(`Cannot resolve release dependency ${cli}. Run bun install --frozen-lockfile before releasing.`, { cause })
19 }
20 }
21}
22
1323export function validateReleaseVersion(ref: string, version: string, manifests: Record<string, unknown>): void {
1424 const errors: string[] = []
1525 const identifier = '(?:0|[1-9]\\d*|[\\da-z-]*[a-z-][\\da-z-]*)'
@@ -47,6 +57,7 @@ if (import.meta.main) {
4757 const root = resolve(import.meta.dir, '..')
4858 if (process.argv.includes('--preflight')) {
4959 validateReleaseCheckout(root)
60 validateReleaseDependencies(root)
5061 console.log('Release checkout contains every tracked package manifest')
5162 process.exit(0)
5263 }