diff --git a/scripts/release/families.spec.ts b/scripts/release/families.spec.ts index c3ae3c8c2f..8d9fbbe3e8 100644 --- a/scripts/release/families.spec.ts +++ b/scripts/release/families.spec.ts @@ -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. diff --git a/scripts/release/families.ts b/scripts/release/families.ts index 7a73323244..6a888c9879 100644 --- a/scripts/release/families.ts +++ b/scripts/release/families.ts @@ -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. diff --git a/scripts/release/publish.ts b/scripts/release/publish.ts index 2590e96e52..36498b4392 100644 --- a/scripts/release/publish.ts +++ b/scripts/release/publish.ts @@ -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 { - // 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 { + 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 { // 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 }