@@ -152,13 +152,41 @@ async function updatePnpmWorkspace(versions: PnpmWorkspaceVersions): Promise<voi
152152 let content = fs . readFileSync ( filePath , 'utf8' ) ;
153153
154154 // oxlint's trailing \n in the pattern disambiguates from oxlint-tsgolint.
155+ // All @vitest /* catalog entries (browser + core direct deps) must stay pinned
156+ // to the same exact version as `vitest` itself, otherwise the catalog drifts
157+ // from VITEST_VERSION.
158+ const vitestExactVersionPackages = [
159+ '@vitest/browser' ,
160+ '@vitest/browser-playwright' ,
161+ '@vitest/browser-preview' ,
162+ '@vitest/browser-webdriverio' ,
163+ '@vitest/expect' ,
164+ '@vitest/mocker' ,
165+ '@vitest/pretty-format' ,
166+ '@vitest/runner' ,
167+ '@vitest/snapshot' ,
168+ '@vitest/spy' ,
169+ '@vitest/utils' ,
170+ ] ;
171+ const vitestExactVersionEntries : PnpmWorkspaceEntry [ ] = vitestExactVersionPackages . map ( ( pkg ) => ( {
172+ name : pkg ,
173+ pattern : new RegExp ( `'${ pkg . replaceAll ( '/' , '\\/' ) } ': ([\\d.]+(?:-[\\w.]+)?)` ) ,
174+ replacement : `'${ pkg } ': ${ versions . vitest } ` ,
175+ newVersion : versions . vitest ,
176+ } ) ) ;
155177 const entries : PnpmWorkspaceEntry [ ] = [
156178 {
157179 name : 'vitest' ,
158- pattern : / v i t e s t - d e v : n p m : v i t e s t @ \^ ( [ \d . ] + (?: - [ \w . ] + ) ? ) / ,
159- replacement : `vitest-dev: npm:vitest@^${ versions . vitest } ` ,
180+ // The `@voidzero-dev/vite-plus-test` wrapper (which used to be aliased
181+ // here via `vitest-dev: npm:vitest@^…`) has been removed. Vitest is now
182+ // a plain catalog entry pinned to an exact version (`vitest: x.y.z`),
183+ // so match that shape directly. The leading newline anchor disambiguates
184+ // from neighbouring keys like `vitepress-*` and `@vitest/browser`.
185+ pattern : / \n { 2 } v i t e s t : ( [ \d . ] + (?: - [ \w . ] + ) ? ) \n / ,
186+ replacement : `\n vitest: ${ versions . vitest } \n` ,
160187 newVersion : versions . vitest ,
161188 } ,
189+ ...vitestExactVersionEntries ,
162190 {
163191 name : 'tsdown' ,
164192 pattern : / t s d o w n : \^ ( [ \d . ] + (?: - [ \w . ] + ) ? ) / ,
@@ -246,34 +274,129 @@ async function updatePnpmWorkspace(versions: PnpmWorkspaceVersions): Promise<voi
246274 console . log ( 'Updated pnpm-workspace.yaml' ) ;
247275}
248276
249- // ============ Update packages/test/package.json ============
250- async function updateTestPackage ( vitestVersion : string ) : Promise < void > {
251- const filePath = path . join ( ROOT , 'packages/test/package.json' ) ;
252- const pkg : PackageJson = readJsonFile ( filePath ) ;
253- const devDependencies = pkg . devDependencies ;
254- if ( ! devDependencies ) {
255- throw new Error ( 'packages/test/package.json is missing devDependencies' ) ;
277+ // ============ Update VITEST_VERSION constant ============
278+ // Keeps the TypeScript source-of-truth (`packages/cli/src/utils/constants.ts`)
279+ // in sync with the `vitest:` catalog entry in pnpm-workspace.yaml. The
280+ // constant is consumed by both `packages/cli` and `ecosystem-ci/patch-project.ts`
281+ // (which re-imports it), so daily upstream bumps must update it here too.
282+ async function updateVitestVersionConstant ( vitestVersion : string ) : Promise < void > {
283+ const filePath = path . join ( ROOT , 'packages/cli/src/utils/constants.ts' ) ;
284+ const content = fs . readFileSync ( filePath , 'utf8' ) ;
285+ const pattern = / e x p o r t c o n s t V I T E S T _ V E R S I O N = ' ( [ \d . ] + (?: - [ \w . ] + ) ? ) ' ; / ;
286+ let oldVersion : string | undefined ;
287+ const updated = content . replace ( pattern , ( _match : string , captured : string ) => {
288+ oldVersion = captured ;
289+ return `export const VITEST_VERSION = '${ vitestVersion } ';` ;
290+ } ) ;
291+ if ( oldVersion === undefined ) {
292+ throw new Error (
293+ `Failed to match VITEST_VERSION in ${ filePath } — the pattern ${ pattern } is stale, ` +
294+ `please update it in .github/scripts/upgrade-deps.ts` ,
295+ ) ;
256296 }
297+ fs . writeFileSync ( filePath , updated ) ;
298+ recordChange ( 'VITEST_VERSION constant' , oldVersion , vitestVersion ) ;
299+ console . log ( 'Updated packages/cli/src/utils/constants.ts' ) ;
300+ }
257301
258- // Update all @vitest /* devDependencies
259- for ( const dep of Object . keys ( devDependencies ) ) {
260- if ( dep . startsWith ( '@vitest/' ) ) {
261- devDependencies [ dep ] = vitestVersion ;
302+ // ============ Update .github/workflows/test-vp-create.yml ============
303+ // The `vp create` smoke-test workflow pins every vitest-family package via the
304+ // `VP_OVERRIDE_PACKAGES` env var so that template installs use the bundled
305+ // version. Daily upstream bumps must rewrite those pins so the workflow does
306+ // not drift behind the rest of the repo.
307+ async function updateTestVpCreateWorkflow ( vitestVersion : string ) : Promise < void > {
308+ const filePath = path . join ( ROOT , '.github/workflows/test-vp-create.yml' ) ;
309+ const content = fs . readFileSync ( filePath , 'utf8' ) ;
310+ const vitestKeys = [
311+ 'vitest' ,
312+ '@vitest/expect' ,
313+ '@vitest/runner' ,
314+ '@vitest/snapshot' ,
315+ '@vitest/spy' ,
316+ '@vitest/utils' ,
317+ '@vitest/mocker' ,
318+ '@vitest/pretty-format' ,
319+ '@vitest/coverage-v8' ,
320+ '@vitest/coverage-istanbul' ,
321+ ] ;
322+ let updated = content ;
323+ let oldVersion : string | undefined ;
324+ for ( const key of vitestKeys ) {
325+ const pattern = new RegExp ( `"${ key . replaceAll ( '/' , '\\/' ) } ":"([\\d.]+(?:-[\\w.]+)?)"` ) ;
326+ let matched = false ;
327+ updated = updated . replace ( pattern , ( _match : string , captured : string ) => {
328+ matched = true ;
329+ oldVersion ??= captured ;
330+ return `"${ key } ":"${ vitestVersion } "` ;
331+ } ) ;
332+ if ( ! matched ) {
333+ throw new Error (
334+ `Failed to match "${ key } " in ${ filePath } — the pattern ${ pattern } is stale, ` +
335+ `please update it in .github/scripts/upgrade-deps.ts` ,
336+ ) ;
262337 }
263338 }
339+ fs . writeFileSync ( filePath , updated ) ;
340+ recordChange ( 'test-vp-create workflow' , oldVersion ?? null , vitestVersion ) ;
341+ console . log ( 'Updated .github/workflows/test-vp-create.yml' ) ;
342+ }
264343
265- // Update vitest-dev devDependency
266- if ( devDependencies [ 'vitest-dev' ] ) {
267- devDependencies [ 'vitest-dev' ] = `^${ vitestVersion } ` ;
268- }
269-
270- // Update @vitest /ui peerDependency if present
271- if ( pkg . peerDependencies ?. [ '@vitest/ui' ] ) {
272- pkg . peerDependencies [ '@vitest/ui' ] = vitestVersion ;
344+ // ============ Update README.md manual-migration vitest pins ============
345+ // The manual-migration guide pins `vitest` to an exact version in three places —
346+ // the npm/Bun `overrides` block, the pnpm-workspace `overrides` block, and the
347+ // Yarn `resolutions` block — so a hand-migrated project shares one Vitest copy
348+ // with the bundled `vp test`. Those literals are NOT interpolated from
349+ // VITEST_VERSION, so a daily bump must rewrite them or the guide drifts behind the
350+ // bundled version. The packages/cli/README.md mirror (refreshed from the root
351+ // README's suffix at build time) is kept in sync here too so the daily PR stays
352+ // self-consistent without depending on a build step running first.
353+ async function updateReadmeVitestPins ( vitestVersion : string ) : Promise < void > {
354+ const readmePaths = [ path . join ( ROOT , 'README.md' ) , path . join ( ROOT , 'packages/cli/README.md' ) ] ;
355+ // JSON form: `"vitest": "4.1.9"` — npm/Bun `overrides` + Yarn `resolutions` (2 blocks)
356+ const jsonPattern = / ( " v i t e s t " : " ) [ \d . ] + (?: - [ \w . ] + ) ? ( " ) / g;
357+ // YAML form: ` vitest: 4.1.9` — pnpm-workspace `overrides` (1 block)
358+ const yamlPattern = / ( \n \s * v i t e s t : ) [ \d . ] + (?: - [ \w . ] + ) ? ( \n ) / g;
359+ // Both READMEs carry the same three manual-migration pins (the cli copy mirrors the
360+ // root's suffix). Assert the exact shape so a daily run fails loudly — like every
361+ // other updater here — if a block is reworded or removed, instead of silently
362+ // shipping a README where only some pins were bumped.
363+ const EXPECTED_JSON = 2 ;
364+ const EXPECTED_YAML = 1 ;
365+ let oldVersion : string | undefined ;
366+ const capture = ( match : string ) : void => {
367+ const found = / ( \d [ \d . ] * (?: - [ \w . ] + ) ? ) / . exec ( match ) ?. [ 1 ] ;
368+ if ( found && oldVersion === undefined ) {
369+ oldVersion = found ;
370+ }
371+ } ;
372+ for ( const filePath of readmePaths ) {
373+ if ( ! fs . existsSync ( filePath ) ) {
374+ continue ;
375+ }
376+ const content = fs . readFileSync ( filePath , 'utf8' ) ;
377+ let jsonMatched = 0 ;
378+ let yamlMatched = 0 ;
379+ let updated = content . replace ( jsonPattern , ( match : string , pre : string , post : string ) => {
380+ jsonMatched ++ ;
381+ capture ( match ) ;
382+ return `${ pre } ${ vitestVersion } ${ post } ` ;
383+ } ) ;
384+ updated = updated . replace ( yamlPattern , ( match : string , pre : string , post : string ) => {
385+ yamlMatched ++ ;
386+ capture ( match ) ;
387+ return `${ pre } ${ vitestVersion } ${ post } ` ;
388+ } ) ;
389+ if ( jsonMatched !== EXPECTED_JSON || yamlMatched !== EXPECTED_YAML ) {
390+ throw new Error (
391+ `Expected ${ EXPECTED_JSON } JSON + ${ EXPECTED_YAML } YAML vitest pins in ${ filePath } , ` +
392+ `found ${ jsonMatched } + ${ yamlMatched } — the manual-migration section changed, ` +
393+ `please update updateReadmeVitestPins in .github/scripts/upgrade-deps.ts` ,
394+ ) ;
395+ }
396+ fs . writeFileSync ( filePath , updated ) ;
397+ console . log ( `Updated ${ path . relative ( ROOT , filePath ) } ` ) ;
273398 }
274-
275- fs . writeFileSync ( filePath , JSON . stringify ( pkg , null , 2 ) + '\n' ) ;
276- console . log ( 'Updated packages/test/package.json' ) ;
399+ recordChange ( 'README vitest pins' , oldVersion ?? null , vitestVersion ) ;
277400}
278401
279402// ============ Update packages/core/package.json ============
@@ -430,7 +553,9 @@ await updatePnpmWorkspace({
430553 oxcParser : oxcParserVersion ,
431554 oxcTransform : oxcTransformVersion ,
432555} ) ;
433- await updateTestPackage ( vitestVersion ) ;
556+ await updateVitestVersionConstant ( vitestVersion ) ;
557+ await updateTestVpCreateWorkflow ( vitestVersion ) ;
558+ await updateReadmeVitestPins ( vitestVersion ) ;
434559await updateCorePackage ( devtoolsVersion ) ;
435560
436561writeMetaFiles ( ) ;
0 commit comments