mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-11-07 06:19:43 -10:00
fix(release): derive the next patch when the preview baseline lags latest (#16470)
* fix(release): derive the next patch when the preview baseline lags latest (#16466) Co-authored-by: Qwen-Coder <[email protected]> * fix(release): derive the stable bump from the rollback-aware latest (#16466) Co-authored-by: Qwen-Coder <[email protected]> * refactor(release): share the published-latest bump helper (#16466) Co-authored-by: Qwen-Coder <[email protected]> * fix(release): refuse cross-line stable versions from older refs (#16466) Co-authored-by: Qwen-Coder <[email protected]> * fix(release): refuse stable releases from refs behind latest (#16466) Co-authored-by: Qwen-Coder <[email protected]> Co-authored-by: Qwen-Coder <[email protected]> * fix(release): refuse cross-line and stale stable derivations (#16466) Co-authored-by: Qwen-Coder <[email protected]> * test(release): pin the cross-line guard's strict boundary (#16466) Co-authored-by: Qwen-Coder <[email protected]> * fix(release): refuse a derived base above the checked-out ref (#16466) Co-authored-by: Qwen-Coder <[email protected]> * fix(release): keep same-line next-patch promotions allowed (#16466) Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: qwen-code-dev-bot <[email protected]> Co-authored-by: Qwen-Coder <[email protected]>
This commit is contained in:
2 files changed
+506
-29
No files matched your search
@@ -327,6 +327,60 @@ function getNightlyVersion() {
|
||||
};
|
||||
}
|
||||
|
||||
// A channel base at or below the published stable would repeat a shipped
|
||||
// version or move the `latest` tag backwards, so target the next patch after
|
||||
// `latest` instead. Returns the base unchanged when no bump is needed.
|
||||
// Read through the rollback-aware lookup so a rolled-back dist-tag can never
|
||||
// become the base.
|
||||
function bumpPastPublishedLatest(baseVersion, label, target, refBase) {
|
||||
const latestStable =
|
||||
getAndVerifyTags('latest', 'v[0-9].[0-9].[0-9]')?.latestVersion ?? '';
|
||||
// A ref behind the published stable would publish its own content
|
||||
// under a version it never contained, whether the preview base is ahead of
|
||||
// latest (a release/0.25 dispatch mid-cycle) or behind it. Check before the
|
||||
// no-bump early return so both states are caught.
|
||||
if (
|
||||
refBase &&
|
||||
semver.valid(latestStable) &&
|
||||
semver.lt(refBase, latestStable)
|
||||
) {
|
||||
throw new Error(
|
||||
`Published latest ${latestStable} is ahead of the checked-out package.json version ${refBase}. Refusing a stale or cross-line stable release.`,
|
||||
);
|
||||
}
|
||||
// When the preview base already leads `latest`, no bump is computed below,
|
||||
// so the bumped-version check cannot fire. Only a base on another release
|
||||
// line is refused here: a patch ahead on the ref's own line is the ordinary
|
||||
// next release.
|
||||
if (
|
||||
refBase &&
|
||||
semver.valid(baseVersion) &&
|
||||
semver.lt(refBase, baseVersion) &&
|
||||
semver.diff(refBase, baseVersion) !== 'patch'
|
||||
) {
|
||||
throw new Error(
|
||||
`Derived base ${baseVersion} is on a different release line than the checked-out package.json version ${refBase}. Refusing a cross-line stable release.`,
|
||||
);
|
||||
}
|
||||
if (
|
||||
!semver.valid(latestStable) ||
|
||||
!semver.valid(baseVersion) ||
|
||||
semver.lt(latestStable, baseVersion)
|
||||
) {
|
||||
return baseVersion;
|
||||
}
|
||||
const bumped = semver.inc(latestStable, 'patch');
|
||||
if (refBase && semver.lt(bumped, refBase)) {
|
||||
throw new Error(
|
||||
`Bumping to ${bumped} would publish the checked-out ${refBase} content on an older release line. Refusing a cross-line stable release.`,
|
||||
);
|
||||
}
|
||||
console.error(
|
||||
`${label} ${baseVersion} is at or below published latest ${latestStable}; bumping ${target} ${bumped}.`,
|
||||
);
|
||||
return bumped;
|
||||
}
|
||||
|
||||
function getStableVersion(args) {
|
||||
const tagResult = getAndVerifyTags('preview', 'v*-preview*');
|
||||
let releaseVersion;
|
||||
@@ -337,20 +391,23 @@ function getStableVersion(args) {
|
||||
} else if (tagResult) {
|
||||
releaseVersion = tagResult.latestVersion.replace(/-preview.*/, '');
|
||||
validateVersion(releaseVersion, 'X.Y.Z', 'derived from preview dist-tag');
|
||||
const latestStable = getVersionFromNPM('latest');
|
||||
if (
|
||||
latestStable &&
|
||||
semver.valid(latestStable) &&
|
||||
semver.gt(latestStable, releaseVersion)
|
||||
) {
|
||||
throw new Error(
|
||||
`Derived stable version ${releaseVersion} is lower than published latest ${latestStable}. Refusing retrograde baseline.`,
|
||||
);
|
||||
}
|
||||
const refBase = readJson('package.json').version.split('-')[0];
|
||||
releaseVersion = bumpPastPublishedLatest(
|
||||
releaseVersion,
|
||||
'Preview baseline',
|
||||
'the stable base to at least',
|
||||
refBase,
|
||||
);
|
||||
} else {
|
||||
const packageJson = readJson('package.json');
|
||||
releaseVersion = packageJson.version.split('-')[0];
|
||||
validateVersion(releaseVersion, 'X.Y.Z', 'package.json version');
|
||||
releaseVersion = bumpPastPublishedLatest(
|
||||
releaseVersion,
|
||||
'Package version',
|
||||
'the stable base to at least',
|
||||
releaseVersion,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -375,23 +432,11 @@ function getPreviewVersion(args) {
|
||||
// When the nightly base is already published as stable, the preview must
|
||||
// target the next patch — otherwise the scheduled Tuesday release derives
|
||||
// a version whose channel packages already exist on npm (E403).
|
||||
// Use the rollback-aware lookup so a rolled-back dist-tag doesn't produce
|
||||
// a retrograde preview base.
|
||||
const latestTagResult = getAndVerifyTags('latest', 'v[0-9].[0-9].[0-9]');
|
||||
const latestStable = latestTagResult?.latestVersion ?? '';
|
||||
if (
|
||||
latestStable &&
|
||||
semver.valid(latestStable) &&
|
||||
semver.valid(baseVersion)
|
||||
) {
|
||||
if (semver.gte(latestStable, baseVersion)) {
|
||||
const bumped = semver.inc(latestStable, 'patch');
|
||||
console.error(
|
||||
`Nightly base ${baseVersion} is at or below published latest ${latestStable}; bumping preview base to ${bumped}.`,
|
||||
);
|
||||
baseVersion = bumped;
|
||||
}
|
||||
}
|
||||
baseVersion = bumpPastPublishedLatest(
|
||||
baseVersion,
|
||||
'Nightly base',
|
||||
'preview base to',
|
||||
);
|
||||
releaseVersion = baseVersion + '-preview.0';
|
||||
validateVersion(
|
||||
releaseVersion,
|
||||
|
||||
@@ -664,7 +664,7 @@ describe('getVersion', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should reject a stable release derived below the published latest version', () => {
|
||||
it('should bump the stable base past the published latest when the preview lags it', () => {
|
||||
const mockWithOlderPreviewVersions = (command) => {
|
||||
if (
|
||||
command.includes('npm view') &&
|
||||
@@ -685,11 +685,443 @@ describe('getVersion', () => {
|
||||
return mockExecSync(command);
|
||||
};
|
||||
vi.mocked(execSync).mockImplementation(mockWithOlderPreviewVersions);
|
||||
// The checked-out ref is on the same release line as published latest.
|
||||
vi.mocked(readFileSync).mockReturnValue(
|
||||
JSON.stringify({ version: '0.9.0' }),
|
||||
);
|
||||
|
||||
const result = getVersion({ type: 'stable' });
|
||||
expect(result.releaseVersion).toBe('0.9.1');
|
||||
expect(result.npmTag).toBe('latest');
|
||||
expect(result.previousReleaseTag).toBe('v0.6.1');
|
||||
});
|
||||
|
||||
it('should keep the preview-derived base when the latest dist-tag is missing', () => {
|
||||
// First stable release: previews exist but nothing has shipped stable,
|
||||
// so the rollback-aware latest lookup returns null.
|
||||
const mockWithMissingLatest = (command) => {
|
||||
if (
|
||||
command.includes('npm view') &&
|
||||
command.includes('--tag=preview') &&
|
||||
!command.includes('versions --json')
|
||||
)
|
||||
return '0.7.0-preview.1';
|
||||
if (
|
||||
command.includes('npm view') &&
|
||||
command.includes('--tag=latest') &&
|
||||
!command.includes('versions --json')
|
||||
)
|
||||
throw new Error('npm error code E404');
|
||||
if (command.includes('npm view') && command.includes('versions --json'))
|
||||
return JSON.stringify(['0.7.0-preview.0', '0.7.0-preview.1']);
|
||||
return mockExecSync(command);
|
||||
};
|
||||
vi.mocked(execSync).mockImplementation(mockWithMissingLatest);
|
||||
|
||||
const result = getVersion({ type: 'stable' });
|
||||
expect(result.releaseVersion).toBe('0.7.0');
|
||||
expect(result.npmTag).toBe('latest');
|
||||
});
|
||||
|
||||
it('should ignore an invalid latest dist-tag when deriving the stable base', () => {
|
||||
const mockWithInvalidLatest = (command) => {
|
||||
if (
|
||||
command.includes('npm view') &&
|
||||
command.includes('--tag=preview') &&
|
||||
!command.includes('versions --json')
|
||||
)
|
||||
return '0.7.0-preview.1';
|
||||
if (
|
||||
command.includes('npm view') &&
|
||||
command.includes('--tag=latest') &&
|
||||
!command.includes('versions --json')
|
||||
)
|
||||
return 'banana';
|
||||
if (command.includes('npm view') && command.includes('versions --json'))
|
||||
return JSON.stringify(['0.7.0-preview.0', '0.7.0-preview.1']);
|
||||
return mockExecSync(command);
|
||||
};
|
||||
vi.mocked(execSync).mockImplementation(mockWithInvalidLatest);
|
||||
|
||||
const result = getVersion({ type: 'stable' });
|
||||
expect(result.releaseVersion).toBe('0.7.0');
|
||||
expect(result.npmTag).toBe('latest');
|
||||
});
|
||||
|
||||
it('should read the published latest through the rollback-aware lookup', () => {
|
||||
// A rolled-back `latest` dist-tag must not become the stable base:
|
||||
// 0.11.0 is the highest published stable even though the tag says
|
||||
// 0.9.2, and publishing 0.9.3 would move the tag backwards.
|
||||
const mockWithRolledBackLatest = (command) => {
|
||||
if (
|
||||
command.includes('npm view') &&
|
||||
command.includes('--tag=preview') &&
|
||||
!command.includes('versions --json')
|
||||
)
|
||||
return '0.9.0-preview.1';
|
||||
if (
|
||||
command.includes('npm view') &&
|
||||
command.includes('--tag=latest') &&
|
||||
!command.includes('versions --json')
|
||||
)
|
||||
return '0.9.2';
|
||||
if (command.includes('npm view') && command.includes('versions --json'))
|
||||
return JSON.stringify(['0.9.0-preview.1', '0.9.2', '0.11.0']);
|
||||
return mockExecSync(command);
|
||||
};
|
||||
vi.mocked(execSync).mockImplementation(mockWithRolledBackLatest);
|
||||
// The ref is on the 0.11 line; only the dist-tag was rolled back.
|
||||
vi.mocked(readFileSync).mockReturnValue(
|
||||
JSON.stringify({ version: '0.11.0' }),
|
||||
);
|
||||
|
||||
const result = getVersion({ type: 'stable' });
|
||||
expect(result.releaseVersion).toBe('0.11.1');
|
||||
expect(result.npmTag).toBe('latest');
|
||||
});
|
||||
|
||||
it('should refuse a versionless stable release dispatched from a ref behind latest', () => {
|
||||
// A release/0.25 dispatch resolves against the global dist-tags, which
|
||||
// have moved on to 0.26. Bumping would publish 0.25 branch content as
|
||||
// 0.26.9 under `latest`, so the stale dispatch must abort.
|
||||
vi.mocked(readFileSync).mockReturnValue(
|
||||
JSON.stringify({ version: '0.25.9' }),
|
||||
);
|
||||
const mockWithOlderReleaseLine = (command) => {
|
||||
if (
|
||||
command.includes('npm view') &&
|
||||
command.includes('--tag=preview') &&
|
||||
!command.includes('versions --json')
|
||||
)
|
||||
return '0.26.3-preview.0';
|
||||
if (
|
||||
command.includes('npm view') &&
|
||||
command.includes('--tag=latest') &&
|
||||
!command.includes('versions --json')
|
||||
)
|
||||
return '0.26.8';
|
||||
if (command.includes('npm view') && command.includes('versions --json'))
|
||||
return JSON.stringify(['0.26.3-preview.0', '0.26.8']);
|
||||
return mockExecSync(command);
|
||||
};
|
||||
vi.mocked(execSync).mockImplementation(mockWithOlderReleaseLine);
|
||||
|
||||
expect(() => getVersion({ type: 'stable' })).toThrow(
|
||||
'Derived stable version 0.7.0 is lower than published latest 0.9.0',
|
||||
'is ahead of the checked-out package.json version',
|
||||
);
|
||||
});
|
||||
|
||||
it('should refuse an older ref even when the preview base is ahead of latest', () => {
|
||||
// Mid-cycle the preview tag runs ahead of the published stable. A
|
||||
// release/0.25 dispatch must still abort instead of publishing its
|
||||
// content as the future minor.
|
||||
vi.mocked(readFileSync).mockReturnValue(
|
||||
JSON.stringify({ version: '0.25.9' }),
|
||||
);
|
||||
const mockWithAheadPreview = (command) => {
|
||||
if (
|
||||
command.includes('npm view') &&
|
||||
command.includes('--tag=preview') &&
|
||||
!command.includes('versions --json')
|
||||
)
|
||||
return '0.27.0-preview.0';
|
||||
if (
|
||||
command.includes('npm view') &&
|
||||
command.includes('--tag=latest') &&
|
||||
!command.includes('versions --json')
|
||||
)
|
||||
return '0.26.9';
|
||||
if (command.includes('npm view') && command.includes('versions --json'))
|
||||
return JSON.stringify(['0.26.9', '0.27.0-preview.0']);
|
||||
return mockExecSync(command);
|
||||
};
|
||||
vi.mocked(execSync).mockImplementation(mockWithAheadPreview);
|
||||
|
||||
expect(() => getVersion({ type: 'stable' })).toThrow(
|
||||
'is ahead of the checked-out package.json version',
|
||||
);
|
||||
});
|
||||
|
||||
it('should refuse a stale ref when the preview base is ahead and no bump happens', () => {
|
||||
// release/v0.26 at 0.26.8 with `latest` also 0.26.8: the preview tag
|
||||
// already carries 0.27.0, so no bump is computed and only a direct
|
||||
// base-vs-ref comparison can refuse publishing 0.26 content as 0.27.0.
|
||||
vi.mocked(readFileSync).mockReturnValue(
|
||||
JSON.stringify({ version: '0.26.8' }),
|
||||
);
|
||||
const mockWithAheadPreviewAtLatest = (command) => {
|
||||
if (
|
||||
command.includes('npm view') &&
|
||||
command.includes('--tag=preview') &&
|
||||
!command.includes('versions --json')
|
||||
)
|
||||
return '0.27.0-preview.0';
|
||||
if (
|
||||
command.includes('npm view') &&
|
||||
command.includes('--tag=latest') &&
|
||||
!command.includes('versions --json')
|
||||
)
|
||||
return '0.26.8';
|
||||
if (command.includes('npm view') && command.includes('versions --json'))
|
||||
return JSON.stringify(['0.26.8', '0.27.0-preview.0']);
|
||||
return mockExecSync(command);
|
||||
};
|
||||
vi.mocked(execSync).mockImplementation(mockWithAheadPreviewAtLatest);
|
||||
|
||||
expect(() => getVersion({ type: 'stable' })).toThrow(
|
||||
'Derived base 0.27.0 is on a different release line than the checked-out package.json version 0.26.8',
|
||||
);
|
||||
});
|
||||
|
||||
it('should allow the ordinary next patch when the checked-out ref matches latest', () => {
|
||||
// main after v0.26.8: package.json 0.26.8, `latest` 0.26.8, and the
|
||||
// Tuesday preview already targets 0.26.9. A versionless stable dispatch
|
||||
// promotes that preview; it is not a cross-line release.
|
||||
vi.mocked(readFileSync).mockReturnValue(
|
||||
JSON.stringify({ version: '0.26.8' }),
|
||||
);
|
||||
const mockWithNextPatchPreview = (command) => {
|
||||
if (
|
||||
command.includes('npm view') &&
|
||||
command.includes('--tag=preview') &&
|
||||
!command.includes('versions --json')
|
||||
)
|
||||
return '0.26.9-preview.0';
|
||||
if (
|
||||
command.includes('npm view') &&
|
||||
command.includes('--tag=latest') &&
|
||||
!command.includes('versions --json')
|
||||
)
|
||||
return '0.26.8';
|
||||
if (command.includes('npm view') && command.includes('versions --json'))
|
||||
return JSON.stringify(['0.26.8', '0.26.9-preview.0']);
|
||||
return mockExecSync(command);
|
||||
};
|
||||
vi.mocked(execSync).mockImplementation(mockWithNextPatchPreview);
|
||||
|
||||
const result = getVersion({ type: 'stable' });
|
||||
expect(result.releaseVersion).toBe('0.26.9');
|
||||
expect(result.npmTag).toBe('latest');
|
||||
});
|
||||
|
||||
it('should refuse a stable release when the ref is behind latest on the same line', () => {
|
||||
// release/0.26.5 predates the 0.26.8 stable. Bumping it to 0.26.9
|
||||
// would publish older content over a newer stable.
|
||||
vi.mocked(readFileSync).mockReturnValue(
|
||||
JSON.stringify({ version: '0.26.5' }),
|
||||
);
|
||||
const mockWithStaleRef = (command) => {
|
||||
if (
|
||||
command.includes('npm view') &&
|
||||
command.includes('--tag=preview') &&
|
||||
!command.includes('versions --json')
|
||||
)
|
||||
return '0.26.3-preview.0';
|
||||
if (
|
||||
command.includes('npm view') &&
|
||||
command.includes('--tag=latest') &&
|
||||
!command.includes('versions --json')
|
||||
)
|
||||
return '0.26.8';
|
||||
if (command.includes('npm view') && command.includes('versions --json'))
|
||||
return JSON.stringify(['0.26.3-preview.0', '0.26.8']);
|
||||
return mockExecSync(command);
|
||||
};
|
||||
vi.mocked(execSync).mockImplementation(mockWithStaleRef);
|
||||
|
||||
expect(() => getVersion({ type: 'stable' })).toThrow(
|
||||
'is ahead of the checked-out package.json version',
|
||||
);
|
||||
});
|
||||
|
||||
it('should release a new minor when the checked-out ref is ahead of latest', () => {
|
||||
// The ordinary new-minor dispatch: the preview tag already carries the
|
||||
// next minor while the ref's base is ahead of `latest`.
|
||||
vi.mocked(readFileSync).mockReturnValue(
|
||||
JSON.stringify({ version: '0.27.0' }),
|
||||
);
|
||||
const mockWithNextMinorPreview = (command) => {
|
||||
if (
|
||||
command.includes('npm view') &&
|
||||
command.includes('--tag=preview') &&
|
||||
!command.includes('versions --json')
|
||||
)
|
||||
return '0.27.0-preview.0';
|
||||
if (
|
||||
command.includes('npm view') &&
|
||||
command.includes('--tag=latest') &&
|
||||
!command.includes('versions --json')
|
||||
)
|
||||
return '0.26.9';
|
||||
if (command.includes('npm view') && command.includes('versions --json'))
|
||||
return JSON.stringify(['0.26.9', '0.27.0-preview.0']);
|
||||
return mockExecSync(command);
|
||||
};
|
||||
vi.mocked(execSync).mockImplementation(mockWithNextMinorPreview);
|
||||
|
||||
const result = getVersion({ type: 'stable' });
|
||||
expect(result.releaseVersion).toBe('0.27.0');
|
||||
expect(result.npmTag).toBe('latest');
|
||||
});
|
||||
|
||||
it('should refuse a ref ahead of latest when the preview base lags it', () => {
|
||||
// The preview tag still carries 0.26 while the ref is already 0.27.0.
|
||||
// Bumping the lagging preview base to 0.26.10 would publish the 0.27.0
|
||||
// content as a 0.26 patch on `latest`.
|
||||
vi.mocked(readFileSync).mockReturnValue(
|
||||
JSON.stringify({ version: '0.27.0' }),
|
||||
);
|
||||
const mockWithLaggingPreview = (command) => {
|
||||
if (
|
||||
command.includes('npm view') &&
|
||||
command.includes('--tag=preview') &&
|
||||
!command.includes('versions --json')
|
||||
)
|
||||
return '0.26.0-preview.1';
|
||||
if (
|
||||
command.includes('npm view') &&
|
||||
command.includes('--tag=latest') &&
|
||||
!command.includes('versions --json')
|
||||
)
|
||||
return '0.26.9';
|
||||
if (command.includes('npm view') && command.includes('versions --json'))
|
||||
return JSON.stringify(['0.26.0-preview.1', '0.26.9']);
|
||||
return mockExecSync(command);
|
||||
};
|
||||
vi.mocked(execSync).mockImplementation(mockWithLaggingPreview);
|
||||
|
||||
expect(() => getVersion({ type: 'stable' })).toThrow(
|
||||
'would publish the checked-out 0.27.0 content on an older release line',
|
||||
);
|
||||
});
|
||||
|
||||
it('should allow a bump that lands exactly on the checked-out ref version', () => {
|
||||
// A ref at 0.9.1 publishing the next patch after latest 0.9.0 is the
|
||||
// ordinary release; the cross-line guard must stay strictly-below.
|
||||
vi.mocked(readFileSync).mockReturnValue(
|
||||
JSON.stringify({ version: '0.9.1' }),
|
||||
);
|
||||
const mockWithExactRefBump = (command) => {
|
||||
if (
|
||||
command.includes('npm view') &&
|
||||
command.includes('--tag=preview') &&
|
||||
!command.includes('versions --json')
|
||||
)
|
||||
return '0.7.0-preview.1';
|
||||
if (
|
||||
command.includes('npm view') &&
|
||||
command.includes('--tag=latest') &&
|
||||
!command.includes('versions --json')
|
||||
)
|
||||
return '0.9.0';
|
||||
if (command.includes('npm view') && command.includes('versions --json'))
|
||||
return JSON.stringify([
|
||||
'0.7.0-preview.0',
|
||||
'0.7.0-preview.1',
|
||||
'0.9.0',
|
||||
]);
|
||||
return mockExecSync(command);
|
||||
};
|
||||
vi.mocked(execSync).mockImplementation(mockWithExactRefBump);
|
||||
|
||||
const result = getVersion({ type: 'stable' });
|
||||
expect(result.releaseVersion).toBe('0.9.1');
|
||||
expect(result.npmTag).toBe('latest');
|
||||
});
|
||||
|
||||
it('should refuse a stale ref when no preview has ever been published', () => {
|
||||
// The package.json fallback skipped the stale-ref check, so a ref at
|
||||
// 0.8.0 could select 0.8.x while latest had already moved to 0.9.0.
|
||||
vi.mocked(readFileSync).mockReturnValue(
|
||||
JSON.stringify({ version: '0.8.0' }),
|
||||
);
|
||||
const mockWithNoPreviewRoute = (command) => {
|
||||
if (
|
||||
command.includes('npm view') &&
|
||||
command.includes('--tag=preview') &&
|
||||
!command.includes('versions --json')
|
||||
)
|
||||
throw new Error('npm error code E404');
|
||||
if (command.includes('npm view') && command.includes('--tag=latest'))
|
||||
return '0.9.0';
|
||||
if (command.includes('npm view') && command.includes('versions --json'))
|
||||
return JSON.stringify(['0.8.0', '0.9.0']);
|
||||
return mockExecSync(command);
|
||||
};
|
||||
vi.mocked(execSync).mockImplementation(mockWithNoPreviewRoute);
|
||||
|
||||
expect(() => getVersion({ type: 'stable' })).toThrow(
|
||||
'is ahead of the checked-out package.json version',
|
||||
);
|
||||
});
|
||||
|
||||
it('should bump the stable base when the preview base equals the published latest', () => {
|
||||
// The common ordering: previews for X.Y.Z exist, then X.Y.Z ships
|
||||
// stable. The equality arm keeps the derived version unpublished
|
||||
// even when the existence probes cannot confirm it.
|
||||
const mockWithEqualLatest = (command) => {
|
||||
if (
|
||||
command.includes('npm view') &&
|
||||
command.includes('--tag=preview') &&
|
||||
!command.includes('versions --json')
|
||||
)
|
||||
return '0.9.0-preview.1';
|
||||
if (
|
||||
command.includes('npm view') &&
|
||||
command.includes('--tag=latest') &&
|
||||
!command.includes('versions --json')
|
||||
)
|
||||
return '0.9.0';
|
||||
if (command.includes('npm view') && command.includes('versions --json'))
|
||||
return JSON.stringify(['0.9.0-preview.1', '0.9.0']);
|
||||
return mockExecSync(command);
|
||||
};
|
||||
vi.mocked(execSync).mockImplementation(mockWithEqualLatest);
|
||||
vi.mocked(readFileSync).mockReturnValue(
|
||||
JSON.stringify({ version: '0.9.0' }),
|
||||
);
|
||||
|
||||
const result = getVersion({ type: 'stable' });
|
||||
expect(result.releaseVersion).toBe('0.9.1');
|
||||
expect(result.npmTag).toBe('latest');
|
||||
});
|
||||
|
||||
it('should describe the bumped stable base as a candidate, not the released version', () => {
|
||||
const mockWithTaggedCandidate = (command) => {
|
||||
if (
|
||||
command.includes('npm view') &&
|
||||
command.includes('--tag=preview') &&
|
||||
!command.includes('versions --json')
|
||||
)
|
||||
throw new Error('npm error code E404');
|
||||
if (command.includes('npm view') && command.includes('--tag=latest'))
|
||||
return '0.9.0';
|
||||
if (command.includes('npm view') && command.includes('versions --json'))
|
||||
return JSON.stringify([
|
||||
'0.7.0-preview.0',
|
||||
'0.7.0-preview.1',
|
||||
'0.9.0',
|
||||
]);
|
||||
if (
|
||||
command.includes('npm view @qwen-code/[email protected] version')
|
||||
)
|
||||
return '0.9.1';
|
||||
return mockExecSync(command);
|
||||
};
|
||||
vi.mocked(execSync).mockImplementation(mockWithTaggedCandidate);
|
||||
vi.mocked(readFileSync).mockReturnValue(
|
||||
JSON.stringify({ version: '0.9.0' }),
|
||||
);
|
||||
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
|
||||
const result = getVersion({ type: 'stable' });
|
||||
|
||||
expect(result.releaseVersion).toBe('0.9.2');
|
||||
expect(errorSpy.mock.calls.flat().join('\n')).toContain(
|
||||
'bumping the stable base to at least 0.9.1.',
|
||||
);
|
||||
errorSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
it('runCli default dispatch prints the version JSON and exits 0', () => {
|
||||
|
||||
Reference in new issue
Block a user