ReviewOS

also looking at this

craft-native/craft

fix(release): gate registry notification on final artifact checks

#305
Merged glennmichael123 wants to merge codex/release-integration-checks into main
6 files +104 -14
scripts/release-workflow.test.tsadded+77-0
Changes to scripts/release-workflow.test.ts
@@ -0,0 +1,77 @@
1import { expect, test } from 'bun:test'
2import { chmodSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
3import { tmpdir } from 'node:os'
4import { join } from 'node:path'
5
6type Job = {
7 needs?: string | string[]
8 if?: string
9 uses?: string
10 steps?: { name?: string, run?: string, uses?: string }[]
11 strategy?: { matrix: { platform: { name: string, os: string }[] } }
12}
13const release = Bun.YAML.parse(readFileSync(join(import.meta.dir, '../.github/workflows/release.yml'), 'utf8')) as { jobs: Record<string, Job> }
14const needs = (job: Job) => typeof job.needs === 'string' ? [job.needs] : job.needs ?? []
15const steps = (job: Job) => job.steps?.map(step => step.run ?? '').join('\n') ?? ''
16
17test('every publishing path depends on release identity validation', () => {
18 for (const name of ['pantry', 'npm', 'release-sbom', 'verify-release', 'verify-macos-downloads'])
19 expect(needs(release.jobs[name])).toContain('validate-release')
20 for (const name of ['npm', 'verify-release', 'verify-macos-downloads'])
21 expect(release.jobs[name].if).toBe("$")
22})
23
24test('registry indexing waits for the final manifest, macOS downloads and attached SBOMs', () => {
25 const notify = release.jobs['notify-registry']
26 expect(notify).toBeDefined()
27 expect(needs(notify).sort()).toEqual(['attach-release-sbom', 'verify-macos-downloads', 'verify-release'])
28 expect(notify.if).toBeUndefined() // Default success gating: never bypass failed prerequisites.
29 expect(steps(notify)).toContain('https://registry.pantry.dev/api/rebuild')
30 expect(steps(release.jobs['verify-release'])).not.toContain('/api/rebuild')
31})
32
33test('npm publication follows artifact validation, and macOS downloads run on both matching architectures', () => {
34 const npm = release.jobs.npm.steps!.map(step => step.run ?? '')
35 const verify = npm.indexOf('bun run verify:npm')
36 expect(verify).toBeGreaterThan(-1)
37 expect(npm.indexOf('pantry publish --npm --access public')).toBeGreaterThan(verify)
38 expect(release.jobs['verify-macos-downloads'].strategy?.matrix.platform).toEqual([
39 { os: 'macos-15', name: 'darwin-arm64' },
40 { os: 'macos-15-intel', name: 'darwin-x64' },
41 ])
42 expect(steps(release.jobs['verify-macos-downloads'])).toContain('scripts/verify-macos-release.ts')
43 expect(release.jobs['release-sbom'].uses).toBe('./.github/workflows/sbom.yml')
44 expect(needs(release.jobs['attach-release-sbom'])).toContain('release-sbom')
45 expect(steps(release.jobs['attach-release-sbom'])).toContain('cmp "sbom/$document" "$VERIFY_DIR/$document"')
46})
47
48test('a registry network failure remains a visible best-effort warning', () => {
49 const command = steps(release.jobs['notify-registry'])
50 expect(command).not.toBe('')
51 const root = mkdtempSync(join(tmpdir(), 'craft-registry-notify-'))
52 try {
53 const curl = join(root, 'curl')
54 writeFileSync(curl, '#!/bin/sh\nexit 28\n')
55 chmodSync(curl, 0o755)
56 const result = Bun.spawnSync(['/bin/bash', '-e', '-o', 'pipefail', '-c', command], {
57 env: { ...process.env, PATH: root, PANTRY_TOKEN: 'fixture-not-a-token' },
58 stdout: 'pipe', stderr: 'pipe',
59 })
60 expect(result.exitCode).toBe(0)
61 expect(result.stdout.toString()).toContain('::warning::Registry request failed')
62 }
63 finally { rmSync(root, { recursive: true, force: true }) }
64})
65
66test('SBOM generation validates required documents before uploading artifacts', () => {
67 const workflow = Bun.YAML.parse(readFileSync(join(import.meta.dir, '../.github/workflows/sbom.yml'), 'utf8')) as { jobs: Record<string, Job> }
68 const generate = workflow.jobs.generate.steps!
69 const validation = generate.findIndex(step => step.name === 'Validate SBOMs')
70 const upload = generate.findIndex(step => step.uses?.startsWith('actions/upload-artifact@'))
71 expect(validation).toBeGreaterThan(-1)
72 expect(upload).toBeGreaterThan(validation)
73 const command = generate[validation]!.run!
74 expect(command).toContain('bun scripts/verify-sbom.ts sbom "v$VERSION"')
75 expect(command).toContain("require('./package.json').version")
76 expect(command).not.toContain('||')
77})