@@ -2,9 +2,24 @@ import fs from 'node:fs';
22import path from 'node:path' ;
33
44const ROOT = process . cwd ( ) ;
5+ const META_DIR = process . env . UPGRADE_DEPS_META_DIR ;
6+
7+ /** @type {Map<string, { old: string | null, new: string, tag?: string }> } */
8+ const changes = new Map ( ) ;
9+
10+ function recordChange ( name , oldValue , newValue , tag ) {
11+ const entry = { old : oldValue ?? null , new : newValue } ;
12+ if ( tag ) entry . tag = tag ;
13+ changes . set ( name , entry ) ;
14+ if ( oldValue !== newValue ) {
15+ console . log ( ` ${ name } : ${ oldValue ?? '(unset)' } -> ${ newValue } ` ) ;
16+ } else {
17+ console . log ( ` ${ name } : ${ newValue } (unchanged)` ) ;
18+ }
19+ }
520
621// ============ GitHub API ============
7- async function getLatestTagCommit ( owner , repo ) {
22+ async function getLatestTag ( owner , repo ) {
823 const res = await fetch ( `https://api.github.com/repos/${ owner } /${ repo } /tags` , {
924 headers : {
1025 Authorization : `token ${ process . env . GITHUB_TOKEN } ` ,
@@ -18,11 +33,11 @@ async function getLatestTagCommit(owner, repo) {
1833 if ( ! Array . isArray ( tags ) || ! tags . length ) {
1934 throw new Error ( `No tags found for ${ owner } /${ repo } ` ) ;
2035 }
21- if ( ! tags [ 0 ] ?. commit ?. sha ) {
22- throw new Error ( `Invalid tag structure for ${ owner } /${ repo } : missing commit SHA` ) ;
36+ if ( ! tags [ 0 ] ?. commit ?. sha || ! tags [ 0 ] ?. name ) {
37+ throw new Error ( `Invalid tag structure for ${ owner } /${ repo } : missing SHA or name ` ) ;
2338 }
24- console . log ( `${ repo } -> ${ tags [ 0 ] . name } ` ) ;
25- return tags [ 0 ] . commit . sha ;
39+ console . log ( `${ repo } -> ${ tags [ 0 ] . name } ( ${ tags [ 0 ] . commit . sha . slice ( 0 , 7 ) } ) ` ) ;
40+ return { sha : tags [ 0 ] . commit . sha , tag : tags [ 0 ] . name } ;
2641}
2742
2843// ============ npm Registry ============
@@ -45,11 +60,16 @@ async function updateUpstreamVersions() {
4560 const filePath = path . join ( ROOT , 'packages/tools/.upstream-versions.json' ) ;
4661 const data = JSON . parse ( fs . readFileSync ( filePath , 'utf8' ) ) ;
4762
48- // rolldown -> rolldown/rolldown
49- data . rolldown . hash = await getLatestTagCommit ( 'rolldown' , 'rolldown' ) ;
50-
51- // vite -> vitejs/vite
52- data [ 'vite' ] . hash = await getLatestTagCommit ( 'vitejs' , 'vite' ) ;
63+ const oldRolldownHash = data . rolldown . hash ;
64+ const oldViteHash = data [ 'vite' ] . hash ;
65+ const [ rolldown , vite ] = await Promise . all ( [
66+ getLatestTag ( 'rolldown' , 'rolldown' ) ,
67+ getLatestTag ( 'vitejs' , 'vite' ) ,
68+ ] ) ;
69+ data . rolldown . hash = rolldown . sha ;
70+ data [ 'vite' ] . hash = vite . sha ;
71+ recordChange ( 'rolldown' , oldRolldownHash , rolldown . sha , rolldown . tag ) ;
72+ recordChange ( 'vite' , oldViteHash , vite . sha , vite . tag ) ;
5373
5474 fs . writeFileSync ( filePath , JSON . stringify ( data , null , 2 ) + '\n' ) ;
5575 console . log ( 'Updated .upstream-versions.json' ) ;
@@ -60,38 +80,59 @@ async function updatePnpmWorkspace(versions) {
6080 const filePath = path . join ( ROOT , 'pnpm-workspace.yaml' ) ;
6181 let content = fs . readFileSync ( filePath , 'utf8' ) ;
6282
63- // Update vitest-dev override (handle pre-release versions like -beta.1, -rc.0)
64- content = content . replace (
65- / v i t e s t - d e v : n p m : v i t e s t @ \^ [ \d . ] + ( - [ \w . ] + ) ? / ,
66- `vitest-dev: npm:vitest@^${ versions . vitest } ` ,
67- ) ;
68-
69- // Update tsdown in catalog (handle pre-release versions)
70- content = content . replace ( / t s d o w n : \^ [ \d . ] + ( - [ \w . ] + ) ? / , `tsdown: ^${ versions . tsdown } ` ) ;
71-
72- // Update @oxc -node/cli in catalog
73- content = content . replace (
74- / ' @ o x c - n o d e \/ c l i ' : \^ [ \d . ] + ( - [ \w . ] + ) ? / ,
75- `'@oxc-node/cli': ^${ versions . oxcNodeCli } ` ,
76- ) ;
77-
78- // Update @oxc -node/core in catalog
79- content = content . replace (
80- / ' @ o x c - n o d e \/ c o r e ' : \^ [ \d . ] + ( - [ \w . ] + ) ? / ,
81- `'@oxc-node/core': ^${ versions . oxcNodeCore } ` ,
82- ) ;
83-
84- // Update oxfmt in catalog
85- content = content . replace ( / o x f m t : = [ \d . ] + ( - [ \w . ] + ) ? / , `oxfmt: =${ versions . oxfmt } ` ) ;
86-
87- // Update oxlint in catalog (but not oxlint-tsgolint)
88- content = content . replace ( / o x l i n t : = [ \d . ] + ( - [ \w . ] + ) ? \n / , `oxlint: =${ versions . oxlint } \n` ) ;
83+ // The capture regex returns the current version in $1; the replacement string
84+ // substitutes the new version into the same anchor text.
85+ // oxlint's trailing \n disambiguates from oxlint-tsgolint.
86+ const entries = [
87+ {
88+ name : 'vitest' ,
89+ pattern : / v i t e s t - d e v : n p m : v i t e s t @ \^ ( [ \d . ] + (?: - [ \w . ] + ) ? ) / ,
90+ replacement : `vitest-dev: npm:vitest@^${ versions . vitest } ` ,
91+ newVersion : versions . vitest ,
92+ } ,
93+ {
94+ name : 'tsdown' ,
95+ pattern : / t s d o w n : \^ ( [ \d . ] + (?: - [ \w . ] + ) ? ) / ,
96+ replacement : `tsdown: ^${ versions . tsdown } ` ,
97+ newVersion : versions . tsdown ,
98+ } ,
99+ {
100+ name : '@oxc-node/cli' ,
101+ pattern : / ' @ o x c - n o d e \/ c l i ' : \^ ( [ \d . ] + (?: - [ \w . ] + ) ? ) / ,
102+ replacement : `'@oxc-node/cli': ^${ versions . oxcNodeCli } ` ,
103+ newVersion : versions . oxcNodeCli ,
104+ } ,
105+ {
106+ name : '@oxc-node/core' ,
107+ pattern : / ' @ o x c - n o d e \/ c o r e ' : \^ ( [ \d . ] + (?: - [ \w . ] + ) ? ) / ,
108+ replacement : `'@oxc-node/core': ^${ versions . oxcNodeCore } ` ,
109+ newVersion : versions . oxcNodeCore ,
110+ } ,
111+ {
112+ name : 'oxfmt' ,
113+ pattern : / o x f m t : = ( [ \d . ] + (?: - [ \w . ] + ) ? ) / ,
114+ replacement : `oxfmt: =${ versions . oxfmt } ` ,
115+ newVersion : versions . oxfmt ,
116+ } ,
117+ {
118+ name : 'oxlint' ,
119+ pattern : / o x l i n t : = ( [ \d . ] + (?: - [ \w . ] + ) ? ) \n / ,
120+ replacement : `oxlint: =${ versions . oxlint } \n` ,
121+ newVersion : versions . oxlint ,
122+ } ,
123+ {
124+ name : 'oxlint-tsgolint' ,
125+ pattern : / o x l i n t - t s g o l i n t : = ( [ \d . ] + (?: - [ \w . ] + ) ? ) / ,
126+ replacement : `oxlint-tsgolint: =${ versions . oxlintTsgolint } ` ,
127+ newVersion : versions . oxlintTsgolint ,
128+ } ,
129+ ] ;
89130
90- // Update oxlint-tsgolint in catalog
91- content = content . replace (
92- / o x l i n t - t s g o l i n t : = [ \d . ] + ( - [ \w . ] + ) ? / ,
93- `oxlint-tsgolint: = ${ versions . oxlintTsgolint } ` ,
94- ) ;
131+ for ( const { name , pattern , replacement , newVersion } of entries ) {
132+ const oldVersion = content . match ( pattern ) ?. [ 1 ] ;
133+ content = content . replace ( pattern , replacement ) ;
134+ recordChange ( name , oldVersion , newVersion ) ;
135+ }
95136
96137 fs . writeFileSync ( filePath , content ) ;
97138 console . log ( 'Updated pnpm-workspace.yaml' ) ;
@@ -128,15 +169,83 @@ async function updateCorePackage(devtoolsVersion) {
128169 const filePath = path . join ( ROOT , 'packages/core/package.json' ) ;
129170 const pkg = JSON . parse ( fs . readFileSync ( filePath , 'utf8' ) ) ;
130171
131- // Update @vitejs /devtools in devDependencies
132- if ( pkg . devDependencies ?. [ '@vitejs/devtools' ] ) {
172+ const currentDevtools = pkg . devDependencies ?. [ ' @vitejs/devtools' ] ;
173+ if ( currentDevtools ) {
133174 pkg . devDependencies [ '@vitejs/devtools' ] = `^${ devtoolsVersion } ` ;
175+ recordChange ( '@vitejs/devtools' , currentDevtools . replace ( / ^ [ \^ ~ ] / , '' ) , devtoolsVersion ) ;
134176 }
135177
136178 fs . writeFileSync ( filePath , JSON . stringify ( pkg , null , 2 ) + '\n' ) ;
137179 console . log ( 'Updated packages/core/package.json' ) ;
138180}
139181
182+ // ============ Write metadata files for PR description ============
183+ function writeMetaFiles ( ) {
184+ if ( ! META_DIR ) return ;
185+
186+ fs . mkdirSync ( META_DIR , { recursive : true } ) ;
187+
188+ const versionsObj = Object . fromEntries ( changes ) ;
189+ fs . writeFileSync (
190+ path . join ( META_DIR , 'versions.json' ) ,
191+ JSON . stringify ( versionsObj , null , 2 ) + '\n' ,
192+ ) ;
193+
194+ const changed = [ ...changes . entries ( ) ] . filter ( ( [ , v ] ) => v . old !== v . new ) ;
195+ const unchanged = [ ...changes . entries ( ) ] . filter ( ( [ , v ] ) => v . old === v . new ) ;
196+
197+ const isFullSha = ( s ) => / ^ [ 0 - 9 a - f ] { 40 } $ / . test ( s ) ;
198+ const formatVersion = ( v ) => {
199+ if ( v . tag ) return `${ v . tag } (${ v . new . slice ( 0 , 7 ) } )` ;
200+ if ( isFullSha ( v . new ) ) return v . new . slice ( 0 , 7 ) ;
201+ return v . new ;
202+ } ;
203+ const formatOld = ( v ) => {
204+ if ( ! v . old ) return '(unset)' ;
205+ if ( isFullSha ( v . old ) ) return v . old . slice ( 0 , 7 ) ;
206+ return v . old ;
207+ } ;
208+
209+ const commitLines = [ 'feat(deps): upgrade upstream dependencies' , '' ] ;
210+ if ( changed . length ) {
211+ for ( const [ name , v ] of changed ) {
212+ commitLines . push ( `- ${ name } : ${ formatOld ( v ) } -> ${ formatVersion ( v ) } ` ) ;
213+ }
214+ } else {
215+ commitLines . push ( '- no version changes detected' ) ;
216+ }
217+ commitLines . push ( '' ) ;
218+ fs . writeFileSync ( path . join ( META_DIR , 'commit-message.txt' ) , commitLines . join ( '\n' ) ) ;
219+
220+ const bodyLines = [ '## Summary' , '' ] ;
221+ if ( changed . length ) {
222+ bodyLines . push ( 'Automated daily upgrade of upstream dependencies.' ) ;
223+ } else {
224+ bodyLines . push ( 'Automated daily upgrade run — no upstream version changes detected.' ) ;
225+ }
226+ bodyLines . push ( '' , '## Dependency updates' , '' ) ;
227+ if ( changed . length ) {
228+ bodyLines . push ( '| Package | From | To |' ) ;
229+ bodyLines . push ( '| --- | --- | --- |' ) ;
230+ for ( const [ name , v ] of changed ) {
231+ bodyLines . push ( `| \`${ name } \` | \`${ formatOld ( v ) } \` | \`${ formatVersion ( v ) } \` |` ) ;
232+ }
233+ } else {
234+ bodyLines . push ( '_No version changes._' ) ;
235+ }
236+ if ( unchanged . length ) {
237+ bodyLines . push ( '' , '<details><summary>Unchanged dependencies</summary>' , '' ) ;
238+ for ( const [ name , v ] of unchanged ) {
239+ bodyLines . push ( `- \`${ name } \`: \`${ formatVersion ( v ) } \`` ) ;
240+ }
241+ bodyLines . push ( '' , '</details>' ) ;
242+ }
243+ bodyLines . push ( '' , '## Code changes' , '' , '_No additional code changes recorded._' , '' ) ;
244+ fs . writeFileSync ( path . join ( META_DIR , 'pr-body.md' ) , bodyLines . join ( '\n' ) ) ;
245+
246+ console . log ( `Wrote metadata files to ${ META_DIR } ` ) ;
247+ }
248+
140249console . log ( 'Fetching latest versions…' ) ;
141250
142251const [
@@ -181,4 +290,6 @@ await updatePnpmWorkspace({
181290await updateTestPackage ( vitestVersion ) ;
182291await updateCorePackage ( devtoolsVersion ) ;
183292
293+ writeMetaFiles ( ) ;
294+
184295console . log ( 'Done!' ) ;
0 commit comments