@@ -22,8 +22,13 @@ type LatestTag = {
2222 tag : string ;
2323} ;
2424
25+ type LatestTagOptions = {
26+ stableOnly ?: boolean ;
27+ } ;
28+
2529type NpmLatestResponse = {
2630 version ?: unknown ;
31+ dependencies ?: Record < string , string > ;
2732} ;
2833
2934type UpstreamVersions = {
@@ -38,6 +43,7 @@ type UpstreamVersions = {
3843type PnpmWorkspaceVersions = {
3944 vitest : string ;
4045 tsdown : string ;
46+ lightningcss : string ;
4147 oxcNodeCli : string ;
4248 oxcNodeCore : string ;
4349 oxfmt : string ;
@@ -62,6 +68,8 @@ type PackageJson = {
6268 peerDependencies ?: Record < string , string > ;
6369} ;
6470
71+ const STABLE_SEMVER_TAG_RE = / ^ v ? \d + \. \d + \. \d + $ / ;
72+
6573const isFullSha = ( s : string ) : boolean => / ^ [ 0 - 9 a - f ] { 40 } $ / . test ( s ) ;
6674
6775const changes = new Map < string , Change > ( ) ;
@@ -89,21 +97,34 @@ function recordChange(
8997}
9098
9199// ============ GitHub API ============
92- async function getLatestTag ( owner : string , repo : string ) : Promise < LatestTag > {
93- const res = await fetch ( `https://api.github.com/repos/${ owner } /${ repo } /tags?per_page=1` , {
94- headers : {
95- Authorization : `token ${ process . env . GITHUB_TOKEN } ` ,
96- Accept : 'application/vnd.github.v3+json' ,
100+ async function getLatestTag (
101+ owner : string ,
102+ repo : string ,
103+ options : LatestTagOptions = { } ,
104+ ) : Promise < LatestTag > {
105+ const perPage = options . stableOnly ? 100 : 1 ;
106+ const res = await fetch (
107+ `https://api.github.com/repos/${ owner } /${ repo } /tags?per_page=${ perPage } ` ,
108+ {
109+ headers : {
110+ Authorization : `token ${ process . env . GITHUB_TOKEN } ` ,
111+ Accept : 'application/vnd.github.v3+json' ,
112+ } ,
97113 } ,
98- } ) ;
114+ ) ;
99115 if ( ! res . ok ) {
100116 throw new Error ( `Failed to fetch tags for ${ owner } /${ repo } : ${ res . status } ${ res . statusText } ` ) ;
101117 }
102118 const tags = ( await res . json ( ) ) as GitHubTag [ ] ;
103119 if ( ! Array . isArray ( tags ) || ! tags . length ) {
104120 throw new Error ( `No tags found for ${ owner } /${ repo } ` ) ;
105121 }
106- const [ latest ] = tags ;
122+ const latest = options . stableOnly
123+ ? tags . find ( ( tag ) => typeof tag . name === 'string' && STABLE_SEMVER_TAG_RE . test ( tag . name ) )
124+ : tags [ 0 ] ;
125+ if ( ! latest ) {
126+ throw new Error ( `No stable semver tags found for ${ owner } /${ repo } ` ) ;
127+ }
107128 if ( typeof latest ?. commit ?. sha !== 'string' || typeof latest . name !== 'string' ) {
108129 throw new Error ( `Invalid tag structure for ${ owner } /${ repo } : missing SHA or name` ) ;
109130 }
@@ -112,20 +133,37 @@ async function getLatestTag(owner: string, repo: string): Promise<LatestTag> {
112133}
113134
114135// ============ npm Registry ============
115- async function getLatestNpmVersion ( packageName : string ) : Promise < string > {
136+ async function fetchNpmLatest ( packageName : string ) : Promise < NpmLatestResponse > {
116137 const res = await fetch ( `https://registry.npmjs.org/${ packageName } /latest` ) ;
117138 if ( ! res . ok ) {
118139 throw new Error (
119- `Failed to fetch npm version for ${ packageName } : ${ res . status } ${ res . statusText } ` ,
140+ `Failed to fetch npm metadata for ${ packageName } : ${ res . status } ${ res . statusText } ` ,
120141 ) ;
121142 }
122- const data = ( await res . json ( ) ) as NpmLatestResponse ;
143+ return ( await res . json ( ) ) as NpmLatestResponse ;
144+ }
145+
146+ async function getLatestNpmVersion ( packageName : string ) : Promise < string > {
147+ const data = await fetchNpmLatest ( packageName ) ;
123148 if ( typeof data . version !== 'string' ) {
124149 throw new Error ( `Invalid npm response for ${ packageName } : missing version field` ) ;
125150 }
126151 return data . version ;
127152}
128153
154+ // Read a dependency range from the latest published version of `packageName`,
155+ // e.g. the `lightningcss` range that the bundled `@tsdown/css` depends on.
156+ async function getNpmDependencyRange ( packageName : string , dependencyName : string ) : Promise < string > {
157+ const data = await fetchNpmLatest ( packageName ) ;
158+ const range = data . dependencies ?. [ dependencyName ] ;
159+ if ( typeof range !== 'string' ) {
160+ throw new Error (
161+ `Invalid npm response for ${ packageName } : missing dependencies.${ dependencyName } ` ,
162+ ) ;
163+ }
164+ return range ;
165+ }
166+
129167// ============ Update .upstream-versions.json ============
130168async function updateUpstreamVersions ( ) : Promise < void > {
131169 const filePath = path . join ( ROOT , 'packages/tools/.upstream-versions.json' ) ;
@@ -135,7 +173,7 @@ async function updateUpstreamVersions(): Promise<void> {
135173 const oldViteHash = data . vite . hash ;
136174 const [ rolldown , vite ] = await Promise . all ( [
137175 getLatestTag ( 'rolldown' , 'rolldown' ) ,
138- getLatestTag ( 'vitejs' , 'vite' ) ,
176+ getLatestTag ( 'vitejs' , 'vite' , { stableOnly : true } ) ,
139177 ] ) ;
140178 data . rolldown . hash = rolldown . sha ;
141179 data . vite . hash = vite . sha ;
@@ -193,6 +231,32 @@ async function updatePnpmWorkspace(versions: PnpmWorkspaceVersions): Promise<voi
193231 replacement : `tsdown: ^${ versions . tsdown } ` ,
194232 newVersion : versions . tsdown ,
195233 } ,
234+ // `@tsdown/css` and `@tsdown/exe` are bundled into core and published in
235+ // lockstep with tsdown (they exact-peer-depend on the same tsdown version),
236+ // so pin both catalog entries to the tsdown version to avoid drift.
237+ {
238+ name : '@tsdown/css' ,
239+ pattern : / ' @ t s d o w n \/ c s s ' : \^ ( [ \d . ] + (?: - [ \w . ] + ) ? ) / ,
240+ replacement : `'@tsdown/css': ^${ versions . tsdown } ` ,
241+ newVersion : versions . tsdown ,
242+ } ,
243+ {
244+ name : '@tsdown/exe' ,
245+ pattern : / ' @ t s d o w n \/ e x e ' : \^ ( [ \d . ] + (?: - [ \w . ] + ) ? ) / ,
246+ replacement : `'@tsdown/exe': ^${ versions . tsdown } ` ,
247+ newVersion : versions . tsdown ,
248+ } ,
249+ // `lightningcss` is a core dependency consumed by the bundled `@tsdown/css`.
250+ // Track exactly what `@tsdown/css` requires (already an `^x.y.z` range) so a
251+ // tsdown upgrade that bumps lightningcss is mirrored here.
252+ {
253+ name : 'lightningcss' ,
254+ // Match any range value (not just `^x.y.z`) so the pattern can re-match
255+ // whatever `@tsdown/css` declares (`~`, `>=`, compound ranges) on the next run.
256+ pattern : / \n { 2 } l i g h t n i n g c s s : ( [ ^ \n ] + ) \n / ,
257+ replacement : `\n lightningcss: ${ versions . lightningcss } \n` ,
258+ newVersion : versions . lightningcss ,
259+ } ,
196260 {
197261 name : '@oxc-node/cli' ,
198262 pattern : / ' @ o x c - n o d e \/ c l i ' : \^ ( [ \d . ] + (?: - [ \w . ] + ) ? ) / ,
@@ -497,6 +561,7 @@ console.log('Fetching latest versions…');
497561const [
498562 vitestVersion ,
499563 tsdownVersion ,
564+ lightningcssVersion ,
500565 devtoolsVersion ,
501566 oxcNodeCliVersion ,
502567 oxcNodeCoreVersion ,
@@ -511,6 +576,8 @@ const [
511576] = await Promise . all ( [
512577 getLatestNpmVersion ( 'vitest' ) ,
513578 getLatestNpmVersion ( 'tsdown' ) ,
579+ // Mirror exactly what the bundled @tsdown /css depends on.
580+ getNpmDependencyRange ( '@tsdown/css' , 'lightningcss' ) ,
514581 getLatestNpmVersion ( '@vitejs/devtools' ) ,
515582 getLatestNpmVersion ( '@oxc-node/cli' ) ,
516583 getLatestNpmVersion ( '@oxc-node/core' ) ,
@@ -526,6 +593,7 @@ const [
526593
527594console . log ( `vitest: ${ vitestVersion } ` ) ;
528595console . log ( `tsdown: ${ tsdownVersion } ` ) ;
596+ console . log ( `lightningcss (from @tsdown/css): ${ lightningcssVersion } ` ) ;
529597console . log ( `@vitejs/devtools: ${ devtoolsVersion } ` ) ;
530598console . log ( `@oxc-node/cli: ${ oxcNodeCliVersion } ` ) ;
531599console . log ( `@oxc-node/core: ${ oxcNodeCoreVersion } ` ) ;
@@ -542,6 +610,7 @@ await updateUpstreamVersions();
542610await updatePnpmWorkspace ( {
543611 vitest : vitestVersion ,
544612 tsdown : tsdownVersion ,
613+ lightningcss : lightningcssVersion ,
545614 oxcNodeCli : oxcNodeCliVersion ,
546615 oxcNodeCore : oxcNodeCoreVersion ,
547616 oxfmt : oxfmtVersion ,
0 commit comments