also looking at this
fix(release): gate registry notification on final artifact checks
#305
6 files
+104
-14
| @@ -0,0 +1,77 @@ | ||
| 1 | import { expect, test } from 'bun:test' | |
| 2 | import { chmodSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs' | |
| 3 | import { tmpdir } from 'node:os' | |
| 4 | import { join } from 'node:path' | |
| 5 | ||
| 6 | type 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 | } | |
| 13 | const release = Bun.YAML.parse(readFileSync(join(import.meta.dir, '../.github/workflows/release.yml'), 'utf8')) as { jobs: Record<string, Job> } | |
| 14 | const needs = (job: Job) => typeof job.needs === 'string' ? [job.needs] : job.needs ?? [] | |
| 15 | const steps = (job: Job) => job.steps?.map(step => step.run ?? '').join('\n') ?? '' | |
| 16 | ||
| 17 | test('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 | ||
| 24 | test('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 | ||
| 33 | test('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 | ||
| 48 | test('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 | ||
| 66 | test('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 | }) | |