Skip to content

Commit 1e546a5

Browse files
SisyphusZhengDevBot
andcommitted
fix(tools): make check-release-truth prerelease- and prepare-window-aware (#1150) (#1284)
The checker hard-coded stable-release expectations: maturity 'stable', 'published as stable X' anchors and publishedVersion == PACKAGE_VERSION. That contradicts the release state machine for a prerelease in the prepare window: advancePublishedReleaseStateText is finalize-owned and prepare intentionally leaves the published line on the prior release. - Derive the npm dist-tag from the version itself, mirroring npmPublishTag semantics (alpha|beta|rc, else latest). - Accept the prerelease-shaped published-line anchors PR #1283 wrote (dist-tag beta; npm latest stays on the stable line). - Tolerate the finalize-owned lag of docs/release/release-state.json during the prepare window: accept exactly the two consistent windows (finalized on the current line, or internally consistent lag on the prior published line) and reject every mixed state. - Keep every stable-release expectation intact via an injectable ReleaseTruthContext, regression-covered by tests. Refs #1150 #1155; unblocks the v0.44.0-beta.1 publish finalize (#1282). Co-authored-by: DevBot <devbot@openelement.dev>
1 parent eeb2311 commit 1e546a5

2 files changed

Lines changed: 290 additions & 55 deletions

File tree

tools/check-release-truth.test.ts

Lines changed: 176 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,205 @@
11
import { assert, assertEquals } from '@std/assert';
2-
import { findReleaseTruthFailures, type ReleaseState } from './check-release-truth.ts';
32
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',
1823
};
1924

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;
2157
return {
2258
'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.`,
2477
'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 线。`,
2679
'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\``,
2881
'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}"`,
3286
};
3387
}
3488

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', () => {
37104
assertEquals(
38-
findReleaseTruthFailures(state, (path) => fixture[path as keyof typeof fixture]),
105+
findReleaseTruthFailures(finalizedState(STABLE, 'stable'), reader(stableFiles(STABLE)), STABLE),
39106
[],
40107
);
41108
});
42109

43110
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');
46113
fixture['docs/roadmap/ROADMAP.md'] = fixture['docs/roadmap/ROADMAP.md'].replace(
47114
'| `0.43.0` | shipped |',
48115
'| `0.43.0` | streaming SSR |',
49116
);
50117
fixture['examples/supabase-cloudflare-starter/deno.json'] = '"version": "0.43.0-alpha.1"';
51118
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+
);
56124
assert(failures.some((failure) => failure.includes('README.md')));
57125
assert(failures.some((failure) => failure.includes('deferred capability')));
58126
assert(failures.some((failure) => failure.includes('deno.json')));
59127
assert(failures.some((failure) => failure.includes('missing release-truth scan target')));
60128
});
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

Comments
 (0)