feat(release): route dsh prerelease dist-tags

This commit is contained in:
imccyu 2026-08-24 11:48:56 +08:00
parent f46e4a8ada
commit 45455aae77
3 changed files with 60 additions and 4 deletions

View file

@ -67,6 +67,22 @@ describe('release families', () => {
])
})
it.each(['0.0.2-alpha.1', '0.0.2-canary.1', '0.0.2-rc.1'])(
'accepts the explicit dsh prerelease version %s',
(version) => {
const root = mkdtempSync(join(tmpdir(), 'dsh-release-prerelease-'))
roots.push(root)
write(join(root, 'package.json'), '{"version":"0.0.1"}\n')
const dsh = releaseFamily('dsh')
const published = member('packages/core/published', '@deepseek-ai/dsh-published')
const plan = planShared(dsh, root, [published], version)
expect(plan.version).toBe(version)
expect(plan.planned[1]?.tag).toBe(`dsh-v${version}`)
},
)
it('names one tag for the whole dsh family and one per vendored package', () => {
const dsh = releaseFamily('dsh')
const vendor = releaseFamily('vendor')
@ -81,6 +97,18 @@ describe('release families', () => {
expect(vendor.tagFor({ ...cordis, version: '4.0.0-rc.7' })).toBe('vendor-cordis-v4.0.0-rc.7')
})
it('assigns alpha and canary dist-tags only to dsh releases', () => {
const dsh = releaseFamily('dsh')
const vendor = releaseFamily('vendor')
expect(dsh.distTagForVersion('0.0.2-alpha.1')).toBe('alpha')
expect(dsh.distTagForVersion('0.0.2-canary.1')).toBe('canary')
expect(dsh.distTagForVersion('0.0.2-rc.1')).toBe('next')
expect(dsh.distTagForVersion('0.0.2')).toBeUndefined()
expect(vendor.distTagForVersion('4.0.1-alpha.1')).toBe('next')
expect(vendor.distTagForVersion('4.0.1-canary.1')).toBe('next')
})
it('rejects a family whose members disagree on the shared version', () => {
const dsh = releaseFamily('dsh')
const members = [member('apps/cli', '@deepseek-ai/dsh'), { ...member('apps/web', '@deepseek-ai/dsh-web-frontend'), version: '0.0.2' }]
@ -277,6 +305,12 @@ describe('vendored version baseline', () => {
})
describe('version precedence', () => {
it('orders alpha, canary, and release-candidate versions by semver precedence', () => {
expect(compareVersions('4.0.1-alpha.1', '4.0.1-canary.1')).toBeLessThan(0)
expect(compareVersions('4.0.1-canary.1', '4.0.1-rc.1')).toBeLessThan(0)
expect(compareVersions('4.0.1-rc.1', '4.0.1')).toBeLessThan(0)
})
it('ranks a release above the prerelease it follows', () => {
// git --sort=v:refname disagrees, placing 4.0.1-rc.1 above 4.0.1, which is
// why the newest published version is chosen here rather than by git.

View file

@ -285,6 +285,15 @@ export abstract class ReleaseFamily {
*/
abstract tagPrefixFor(member: ReleaseMember): string
/**
* The npm dist-tag assigned while publishing a version.
* @param version - package version from the packed manifest.
* @returns `next` for a prerelease, or undefined so npm uses `latest`.
*/
distTagForVersion(version: string): string | undefined {
return version.includes('-') ? 'next' : undefined
}
/**
* The tag a member publishes from.
* @param member - the member being published.
@ -339,6 +348,14 @@ class DshFamily extends ReleaseFamily {
return this.tagPrefix
}
override distTagForVersion(version: string): string | undefined {
const separator = version.indexOf('-')
if (separator === -1) return undefined
const [channel] = version.slice(separator + 1).split('.')
if (channel === 'alpha' || channel === 'canary') return channel
return 'next'
}
/**
* Reject source and declaration-map members, the repository's publication policy.
* @param member - the packed member.

View file

@ -93,10 +93,15 @@ function registryState(name: string, version: string): RegistryState {
* @param tarball - absolute tarball path.
* @param name - package name the tarball declares.
* @param version - package version the tarball declares.
* @param distTag - explicit npm dist-tag, or undefined for npm's `latest` default.
*/
async function publishTarball(tarball: string, name: string, version: string): Promise<void> {
// A prerelease version never takes the latest dist-tag.
const tagArgs = version.includes('-') ? ['--tag', 'next'] : []
async function publishTarball(
tarball: string,
name: string,
version: string,
distTag: string | undefined,
): Promise<void> {
const tagArgs = distTag === undefined ? [] : ['--tag', distTag]
for (let tries = 1; tries <= PUBLISH_ATTEMPTS; tries += 1) {
// No --access: the sequences do not share one access level, so a
// command-line flag could not serve both and would override the manifest
@ -164,7 +169,7 @@ async function main(): Promise<void> {
// Space out the writes: the gap belongs between publishes, so a run that
// only skips does not wait at all.
if (published > 0) await sleep(PUBLISH_SPACING_MS)
await publishTarball(tarball, name, version)
await publishTarball(tarball, name, version, family.distTagForVersion(version))
console.log(`release publish: ${progress} ${name}@${version} published`)
published += 1
}