|
1 | 1 | import { assert, assertEquals } from '@std/assert'; |
2 | | -import { findReleaseTruthFailures, type ReleaseState } from './check-release-truth.ts'; |
3 | 2 | import { |
4 | | - ACTIVE_EXECUTION_VERSION, |
5 | | - LATEST_LANDED_TRAIN, |
6 | | - NEXT_EXECUTION_VERSION, |
7 | | - PACKAGE_VERSION, |
8 | | -} from './project-constants.ts'; |
9 | | - |
10 | | -const state: ReleaseState = { |
11 | | - schemaVersion: 1, |
12 | | - sourceVersion: PACKAGE_VERSION, |
13 | | - publishedVersion: PACKAGE_VERSION, |
14 | | - latestLandedTrain: LATEST_LANDED_TRAIN, |
15 | | - activeTarget: ACTIVE_EXECUTION_VERSION, |
16 | | - nextPlannedTrain: NEXT_EXECUTION_VERSION, |
17 | | - maturity: 'stable', |
| 3 | + findReleaseTruthFailures, |
| 4 | + npmDistTag, |
| 5 | + type ReleaseState, |
| 6 | + type ReleaseTruthContext, |
| 7 | +} from './check-release-truth.ts'; |
| 8 | + |
| 9 | +// Injected contexts keep the stable-line expectations regression-covered even |
| 10 | +// while the repository itself sits on a prerelease line. |
| 11 | +const STABLE: ReleaseTruthContext = { |
| 12 | + packageVersion: '0.43.3', |
| 13 | + packageVersionTag: 'v0.43.3', |
| 14 | + activeTarget: 'v0.43.3', |
| 15 | + nextPlannedTrain: 'v0.44.0-alpha.0', |
| 16 | +}; |
| 17 | + |
| 18 | +const PRERELEASE: ReleaseTruthContext = { |
| 19 | + packageVersion: '0.44.0-beta.1', |
| 20 | + packageVersionTag: 'v0.44.0-beta.1', |
| 21 | + activeTarget: 'v0.44.0-beta.1', |
| 22 | + nextPlannedTrain: 'v0.44.0-beta.2', |
18 | 23 | }; |
19 | 24 |
|
20 | | -function files() { |
| 25 | +function finalizedState( |
| 26 | + context: ReleaseTruthContext, |
| 27 | + maturity: ReleaseState['maturity'], |
| 28 | +): ReleaseState { |
| 29 | + return { |
| 30 | + schemaVersion: 1, |
| 31 | + sourceVersion: context.packageVersion, |
| 32 | + publishedVersion: context.packageVersion, |
| 33 | + latestLandedTrain: context.packageVersionTag, |
| 34 | + activeTarget: context.activeTarget, |
| 35 | + nextPlannedTrain: context.nextPlannedTrain, |
| 36 | + maturity, |
| 37 | + }; |
| 38 | +} |
| 39 | + |
| 40 | +// Prepare window: the docs and the package line already advanced, but the |
| 41 | +// finalize-owned fields of release-state.json still describe the previously |
| 42 | +// published line. |
| 43 | +function prepareWindowState(context: ReleaseTruthContext, priorLine: string): ReleaseState { |
| 44 | + return { |
| 45 | + schemaVersion: 1, |
| 46 | + sourceVersion: priorLine, |
| 47 | + publishedVersion: priorLine, |
| 48 | + latestLandedTrain: `v${priorLine}`, |
| 49 | + activeTarget: context.activeTarget, |
| 50 | + nextPlannedTrain: context.nextPlannedTrain, |
| 51 | + maturity: 'stable', |
| 52 | + }; |
| 53 | +} |
| 54 | + |
| 55 | +function stableFiles(context: ReleaseTruthContext): Record<string, string> { |
| 56 | + const v = context.packageVersion; |
21 | 57 | return { |
22 | 58 | 'README.md': |
23 | | - `Source package line: \`${state.sourceVersion}\`\nnpm registry line: \`v${state.publishedVersion}\`\npublished as stable \`${state.publishedVersion}\``, |
| 59 | + `Source package line: \`${v}\`\nnpm registry line: \`v${v}\`\npublished as stable \`${v}\``, |
| 60 | + 'README.zh.md': `源码包行为 \`${v}\`\nnpm registry 行为 \`v${v}\`\nstable \`${v}\` 发布`, |
| 61 | + 'docs/status/STATUS.md': |
| 62 | + `Active release target: \`${context.activeTarget}\`\nstable at\n\`${v}\``, |
| 63 | + 'docs/roadmap/ROADMAP.md': |
| 64 | + `Active execution target: \`${context.activeTarget}\`.\n\`${v}\` is both the current source package line\n| \`0.43.0\` | shipped |`, |
| 65 | + 'docs/current/VERSION_PLAN.md': `Active release target: \`${context.activeTarget}\``, |
| 66 | + 'examples/supabase-cloudflare-starter/deno.json': `"version": "${v}"`, |
| 67 | + }; |
| 68 | +} |
| 69 | + |
| 70 | +// The anchor shape PR #1283 wrote for the Beta.1 prerelease line. |
| 71 | +function prereleaseFiles(context: ReleaseTruthContext): Record<string, string> { |
| 72 | + const v = context.packageVersion; |
| 73 | + const tag = npmDistTag(v); |
| 74 | + return { |
| 75 | + 'README.md': |
| 76 | + `Source package line: \`${v}\` (\`${context.packageVersionTag}\`).\nnpm registry line: \`v${v}\` (prerelease, dist-tag \`${tag}\`); npm \`latest\` remains the stable \`0.43.3\` line.`, |
24 | 77 | 'README.zh.md': |
25 | | - `源码包行为 \`${state.sourceVersion}\`\nnpm registry 行为 \`v${state.publishedVersion}\`\nstable \`${state.publishedVersion}\` 发布`, |
| 78 | + `源码包行为 \`${v}\`(\`${context.packageVersionTag}\`)\nnpm registry 行为 \`v${v}\`——预发布版本(dist-tag \`${tag}\`);npm \`latest\` 仍为\n已发布的稳定 0.43 线。`, |
26 | 79 | 'docs/status/STATUS.md': |
27 | | - `Active release target: \`${state.activeTarget}\`\nstable at\n\`${state.publishedVersion}\``, |
| 80 | + `Active release target: \`${context.activeTarget}\`\nnpm registry line: \`v${v}\` (prerelease, dist-tag \`${tag}\`)\ndist-tag stays stable at\n\`0.43.3\``, |
28 | 81 | 'docs/roadmap/ROADMAP.md': |
29 | | - `Active execution target: \`${state.activeTarget}\`.\n\`${state.publishedVersion}\` is both the current source package line\n| \`0.43.0\` | shipped |`, |
30 | | - 'docs/current/VERSION_PLAN.md': `Active release target: \`${state.activeTarget}\``, |
31 | | - 'examples/supabase-cloudflare-starter/deno.json': `"version": "${state.sourceVersion}"`, |
| 82 | + `Active execution target: \`${context.activeTarget}\`.\nnpm registry line: \`v${v}\` (prerelease, dist-tag \`${tag}\`).\nThe npm \`latest\` dist-tag remains on the published stable 0.43 line.\n| \`0.43.0\` | shipped |`, |
| 83 | + 'docs/current/VERSION_PLAN.md': |
| 84 | + `Active release target: \`${context.activeTarget}\`\nnpm registry line: \`v${v}\` (prerelease, dist-tag \`${tag}\`; npm \`latest\` remains the stable 0.43 line)`, |
| 85 | + 'examples/supabase-cloudflare-starter/deno.json': `"version": "${v}"`, |
32 | 86 | }; |
33 | 87 | } |
34 | 88 |
|
35 | | -Deno.test('release truth accepts the converged state', () => { |
36 | | - const fixture = files(); |
| 89 | +function reader(fixture: Record<string, string>) { |
| 90 | + return (path: string): string => { |
| 91 | + if (!(path in fixture)) throw new Deno.errors.NotFound(); |
| 92 | + return fixture[path]; |
| 93 | + }; |
| 94 | +} |
| 95 | + |
| 96 | +Deno.test('npmDistTag mirrors npmPublishTag semantics', () => { |
| 97 | + assertEquals(npmDistTag('0.44.0-alpha.3'), 'alpha'); |
| 98 | + assertEquals(npmDistTag('0.44.0-beta.1'), 'beta'); |
| 99 | + assertEquals(npmDistTag('0.44.0-rc.1'), 'rc'); |
| 100 | + assertEquals(npmDistTag('0.43.3'), 'latest'); |
| 101 | +}); |
| 102 | + |
| 103 | +Deno.test('release truth accepts the converged stable state', () => { |
37 | 104 | assertEquals( |
38 | | - findReleaseTruthFailures(state, (path) => fixture[path as keyof typeof fixture]), |
| 105 | + findReleaseTruthFailures(finalizedState(STABLE, 'stable'), reader(stableFiles(STABLE)), STABLE), |
39 | 106 | [], |
40 | 107 | ); |
41 | 108 | }); |
42 | 109 |
|
43 | 110 | Deno.test('release truth rejects stale copy, deferred shipped claims, alpha starter, and missing roots', () => { |
44 | | - const fixture: Record<string, string> = files(); |
45 | | - fixture['README.md'] = fixture['README.md'].replace(state.sourceVersion, '0.42.0'); |
| 111 | + const fixture = stableFiles(STABLE); |
| 112 | + fixture['README.md'] = fixture['README.md'].replace(STABLE.packageVersion, '0.42.0'); |
46 | 113 | fixture['docs/roadmap/ROADMAP.md'] = fixture['docs/roadmap/ROADMAP.md'].replace( |
47 | 114 | '| `0.43.0` | shipped |', |
48 | 115 | '| `0.43.0` | streaming SSR |', |
49 | 116 | ); |
50 | 117 | fixture['examples/supabase-cloudflare-starter/deno.json'] = '"version": "0.43.0-alpha.1"'; |
51 | 118 | delete fixture['docs/status/STATUS.md']; |
52 | | - const failures = findReleaseTruthFailures(state, (path) => { |
53 | | - if (!(path in fixture)) throw new Deno.errors.NotFound(); |
54 | | - return fixture[path]; |
55 | | - }); |
| 119 | + const failures = findReleaseTruthFailures( |
| 120 | + finalizedState(STABLE, 'stable'), |
| 121 | + reader(fixture), |
| 122 | + STABLE, |
| 123 | + ); |
56 | 124 | assert(failures.some((failure) => failure.includes('README.md'))); |
57 | 125 | assert(failures.some((failure) => failure.includes('deferred capability'))); |
58 | 126 | assert(failures.some((failure) => failure.includes('deno.json'))); |
59 | 127 | assert(failures.some((failure) => failure.includes('missing release-truth scan target'))); |
60 | 128 | }); |
| 129 | + |
| 130 | +Deno.test('release truth accepts the prerelease prepare window (state lags on the prior stable line)', () => { |
| 131 | + assertEquals( |
| 132 | + findReleaseTruthFailures( |
| 133 | + prepareWindowState(PRERELEASE, '0.43.3'), |
| 134 | + reader(prereleaseFiles(PRERELEASE)), |
| 135 | + PRERELEASE, |
| 136 | + ), |
| 137 | + [], |
| 138 | + ); |
| 139 | +}); |
| 140 | + |
| 141 | +Deno.test('release truth accepts the finalized prerelease state', () => { |
| 142 | + assertEquals( |
| 143 | + findReleaseTruthFailures( |
| 144 | + finalizedState(PRERELEASE, 'beta'), |
| 145 | + reader(prereleaseFiles(PRERELEASE)), |
| 146 | + PRERELEASE, |
| 147 | + ), |
| 148 | + [], |
| 149 | + ); |
| 150 | +}); |
| 151 | + |
| 152 | +Deno.test('release truth rejects a mixed prepare-window state', () => { |
| 153 | + const earlyPublish = { |
| 154 | + ...prepareWindowState(PRERELEASE, '0.43.3'), |
| 155 | + publishedVersion: PRERELEASE.packageVersion, |
| 156 | + }; |
| 157 | + const earlyFailures = findReleaseTruthFailures( |
| 158 | + earlyPublish, |
| 159 | + reader(prereleaseFiles(PRERELEASE)), |
| 160 | + PRERELEASE, |
| 161 | + ); |
| 162 | + assert(earlyFailures.some((failure) => failure.includes('publishedVersion'))); |
| 163 | + |
| 164 | + const earlyMaturity = { ...prepareWindowState(PRERELEASE, '0.43.3'), maturity: 'beta' as const }; |
| 165 | + const maturityFailures = findReleaseTruthFailures( |
| 166 | + earlyMaturity, |
| 167 | + reader(prereleaseFiles(PRERELEASE)), |
| 168 | + PRERELEASE, |
| 169 | + ); |
| 170 | + assert(maturityFailures.some((failure) => failure.includes('maturity'))); |
| 171 | + |
| 172 | + const earlyTrain = { |
| 173 | + ...prepareWindowState(PRERELEASE, '0.43.3'), |
| 174 | + latestLandedTrain: PRERELEASE.packageVersionTag, |
| 175 | + }; |
| 176 | + const trainFailures = findReleaseTruthFailures( |
| 177 | + earlyTrain, |
| 178 | + reader(prereleaseFiles(PRERELEASE)), |
| 179 | + PRERELEASE, |
| 180 | + ); |
| 181 | + assert(trainFailures.some((failure) => failure.includes('latestLandedTrain'))); |
| 182 | +}); |
| 183 | + |
| 184 | +Deno.test('release truth rejects stable-shaped published-line anchors for a prerelease', () => { |
| 185 | + const failures = findReleaseTruthFailures( |
| 186 | + prepareWindowState(PRERELEASE, '0.43.3'), |
| 187 | + reader(stableFiles(PRERELEASE)), |
| 188 | + PRERELEASE, |
| 189 | + ); |
| 190 | + assert(failures.some((failure) => failure.includes('README.md') && failure.includes('dist-tag'))); |
| 191 | + assert( |
| 192 | + failures.some((failure) => failure.includes('README.zh.md') && failure.includes('预发布版本')), |
| 193 | + ); |
| 194 | + assert(failures.some((failure) => failure.includes('docs/status/STATUS.md'))); |
| 195 | + assert(failures.some((failure) => failure.includes('docs/roadmap/ROADMAP.md'))); |
| 196 | +}); |
| 197 | + |
| 198 | +Deno.test('release truth rejects stable maturity once a prerelease is finalized', () => { |
| 199 | + const failures = findReleaseTruthFailures( |
| 200 | + finalizedState(PRERELEASE, 'stable'), |
| 201 | + reader(prereleaseFiles(PRERELEASE)), |
| 202 | + PRERELEASE, |
| 203 | + ); |
| 204 | + assert(failures.some((failure) => failure.includes('maturity'))); |
| 205 | +}); |
0 commit comments