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.

docs/RELEASING.mdmodified+3-1
Changes to docs/RELEASING.md
@@ -11,7 +11,9 @@ They also read addressed native state from the main, child, and adopted windows.
1111Each installed-app smoke also checks creator teardown and adoption of a
1212surviving, unparented grandchild. The PKG case explicitly destroys the retained
1313macOS creator after close; DEB and MSI close already releases it.
14The release hook generates the next changelog section before that commit,
14The release command registers the workspace and Zig release manifests with
15Bumpx so their versions update and roll back together. The release hook
16generates the next changelog section before that commit,
1517deduplicates repeated issue references, and links the previous and new tags.
1618Do not run the hook again after it has written the section.
1719
package.jsonmodified+2-2
Changes to package.json
@@ -61,8 +61,8 @@
6161 "changelog:generate": "bun scripts/generate-release-changelog.ts",
6262 "test:release": "bun test ./scripts/changelog-preview.test.ts ./scripts/release-workflow.test.ts ./scripts/release-manifest.test.ts ./scripts/verify-sbom.test.ts",
6363 "release": "bun scripts/release-version.ts --preflight && bun x --bun @stacksjs/bumpx prompt --recursive",
64 "release:patch": "bun scripts/release-version.ts --preflight && bun x --bun @stacksjs/bumpx patch --recursive --commit --tag --push --yes --no-changelog --execute \"bun scripts/sync-release-version.ts && bun scripts/generate-release-changelog.ts && git add packages/zig/build.zig.zon packages/zig/pantry.json CHANGELOG.md\"",
65 "release:version": "bun scripts/release-version.ts --preflight && bun x --bun @stacksjs/bumpx --recursive --commit --tag --push --yes --no-changelog --execute \"bun scripts/sync-release-version.ts && bun scripts/generate-release-changelog.ts && git add packages/zig/build.zig.zon packages/zig/pantry.json CHANGELOG.md\""
64 "release:patch": "bun scripts/release-version.ts --preflight && bun x --bun @stacksjs/bumpx patch --recursive --files package.json,packages/zig/build.zig.zon,packages/zig/pantry.json --commit --tag --push --yes --no-changelog --execute \"bun scripts/generate-release-changelog.ts && git add CHANGELOG.md\"",
65 "release:version": "bun scripts/release-version.ts --preflight && bun x --bun @stacksjs/bumpx --recursive --files package.json,packages/zig/build.zig.zon,packages/zig/pantry.json --commit --tag --push --yes --no-changelog --execute \"bun scripts/generate-release-changelog.ts && git add CHANGELOG.md\""
6666 },
6767 "devDependencies": {
6868 "@stacksjs/buddy": "0.72.56",
scripts/changelog-preview.test.tsmodified+35-4
Changes to scripts/changelog-preview.test.ts
@@ -1,5 +1,5 @@
11import { afterAll, expect, test } from 'bun:test'
2import { readFileSync, rmSync, writeFileSync } from 'node:fs'
2import { mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
33import { join } from 'node:path'
44import { dedupeChangelogReferences, previewChangelog } from './changelog-preview'
55import { generateReleaseChangelog } from './generate-release-changelog'
@@ -9,6 +9,7 @@ import { createTempDir, removeAllTempDirs } from '../packages/typescript/src/tem
99afterAll(removeAllTempDirs)
1010
1111const cli = join(import.meta.dir, 'changelog-preview.ts')
12const shellQuote = (value: string): string => `'${value.replaceAll("'", "'\\''")}'`
1213function fixture(run: (directory: string, git: (...args: string[]) => string) => void) {
1314 const directory = createTempDir('craft-changelog-fixture-')
1415 const git = (...args: string[]) => {
@@ -127,17 +128,22 @@ test('patch release uses the guarded changelog generator before committing', ()
127128 expect(project.scripts['changelog:generate']).toBe('bun scripts/generate-release-changelog.ts')
128129 expect(command).toContain('--no-changelog')
129130 expect(command).toContain('bun scripts/generate-release-changelog.ts')
130 expect(command).toContain('git add packages/zig/build.zig.zon packages/zig/pantry.json CHANGELOG.md')
131 expect(command).toContain('--files package.json,packages/zig/build.zig.zon,packages/zig/pantry.json')
131132})
132133
133134test('an explicit version commits and tags the guarded changelog without its duplicate generator', () => {
134135 fixture((directory, git) => {
136 mkdirSync(join(directory, 'packages/zig'), { recursive: true })
137 writeFileSync(join(directory, 'packages/zig/build.zig.zon'), '.{ .version = "1.0.1", }')
138 writeFileSync(join(directory, 'packages/zig/pantry.json'), '{"version":"1.0.1"}')
139 git('add', 'packages')
140 git('commit', '-qm', 'chore: register native release metadata')
135141 git('commit', '--allow-empty', '-qm', 'Merge pull request #326 from example/feature')
136142 const bumpx = Bun.resolveSync('@stacksjs/bumpx/bin/cli.js', import.meta.dir)
137143 const generator = join(import.meta.dir, 'generate-release-changelog.ts')
138144 const result = Bun.spawnSync([
139 process.execPath, bumpx, '--recursive', '--commit', '--tag', '--no-push', '--yes', '--no-changelog',
140 '--execute', `bun ${JSON.stringify(generator)} && git add CHANGELOG.md`,
145 process.execPath, bumpx, '--recursive', '--files', 'package.json,packages/zig/build.zig.zon,packages/zig/pantry.json', '--commit', '--tag', '--no-push', '--yes', '--no-changelog',
146 '--execute', `${shellQuote(process.execPath)} ${shellQuote(generator)} && git add CHANGELOG.md`,
141147 '1.0.2',
142148 ], { cwd: directory, stdout: 'pipe', stderr: 'pipe', timeout: 30_000 })
143149 expect(result.exitCode, result.stderr.toString()).toBe(0)
@@ -145,6 +151,31 @@ test('an explicit version commits and tags the guarded changelog without its dup
145151 const notes = git('show', 'HEAD:CHANGELOG.md')
146152 expect(notes).toContain('[Compare changes](https://github.com/example/fixture/compare/v1.0.0...v1.0.2)')
147153 expect(notes.match(/\[#326\]\(https:\/\/github\.com\/example\/fixture\/issues\/326\)/g)).toHaveLength(1)
154 expect(JSON.parse(git('show', 'HEAD:packages/zig/pantry.json')).version).toBe('1.0.2')
155 expect(git('show', 'HEAD:packages/zig/build.zig.zon')).toContain('"1.0.2"')
156 expect(git('status', '--porcelain')).toBe('')
157 })
158})
159
160test('the published release tool restores workspace and native versions when a required command fails', () => {
161 fixture((directory, git) => {
162 mkdirSync(join(directory, 'packages/zig'), { recursive: true })
163 writeFileSync(join(directory, 'packages/zig/build.zig.zon'), '.{ .version = "1.0.1", }')
164 writeFileSync(join(directory, 'packages/zig/pantry.json'), '{"version":"1.0.1"}')
165 git('add', 'packages')
166 git('commit', '-qm', 'chore: register native release metadata')
167 const head = git('rev-parse', 'HEAD')
168 const paths = ['package.json', 'packages/zig/build.zig.zon', 'packages/zig/pantry.json']
169 const before = paths.map(path => readFileSync(join(directory, path), 'utf8'))
170 const bumpx = Bun.resolveSync('@stacksjs/bumpx/bin/cli.js', import.meta.dir)
171 const result = Bun.spawnSync([
172 process.execPath, bumpx, 'patch', '--recursive', '--files', paths.join(','), '--commit', '--tag', '--no-push', '--yes', '--no-changelog',
173 '--execute', 'bun -e "process.exit(23)"',
174 ], { cwd: directory, stdout: 'pipe', stderr: 'pipe', timeout: 30_000 })
175 expect(result.exitCode).not.toBe(0)
176 expect(paths.map(path => readFileSync(join(directory, path), 'utf8'))).toEqual(before)
177 expect(git('rev-parse', 'HEAD')).toBe(head)
178 expect(git('tag', '--list', 'v1.0.2')).toBe('')
148179 expect(git('status', '--porcelain')).toBe('')
149180 })
150181})