[READ-ONLY] Mirror of https://github.com/vitest-dev/vitest. Next generation testing framework powered by Vite. vitest.dev
test testing-tools vite
12

Configure Feed

Select the types of activity you want to include in your feed.

chore: setup PR-driven release pipeline (#10550)

Co-authored-by: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

authored by

Hiroshi Ogawa
Hiroshi Ogawa
Codex
Claude Opus 4.8 (1M context)
Copilot Autofix powered by AI
and committed by
GitHub
(Jun 12, 2026, 1:25 PM +0200) 3d421d4e 8f85d20d

+365 -89
+17
CONTRIBUTING.md
··· 161 161 - `release` points to the latest stable release line used for <https://vitest.dev/>. Release managers update it manually for non-beta releases from `main`; it is not moved for older-line backports. 162 162 - `vN` branches are used for old major documentation sites. For example, <https://v3.vitest.dev/> uses `v3`. 163 163 164 + ### Release process 165 + 166 + Releases — publishing the npm packages, creating the git release tag, and generating the associated GitHub release — are driven by a pull request and carried out by GitHub Actions, not from a maintainer's machine. The release PR holds the version bump, and merging it kicks off the actual publish. 167 + 168 + 1. **Prepare the release PR.** Run the `Prepare Publish` workflow with two inputs: 169 + 170 + - `target_branch` — the branch matching the [release branch](#release-branches) convention for the release line. The Actions menu's separate "Use workflow from" selector should point at the same branch, to keep the workflow definition and release target aligned. 171 + - `release` or `version` — the version bump. The default `release: next` bumps to the next patch for stable releases (`4.1.2 -> 4.1.3`), or the next prerelease when already on one (`4.2.0-beta.2 -> 4.2.0-beta.3`). Otherwise set `release` to a specific bump type, or pass an exact `version` for pre-releases. 172 + 173 + The workflow pushes the bump to a branch and opens the release PR. To preview what a `release` input resolves to, run `pnpm release` locally first — it lets you browse release types and versions interactively (cancel before confirming so nothing is committed). 174 + 175 + 2. **Review and merge the PR.** Check the version bump, then merge so the `chore: release v*` commit lands on the release branch — that commit is what triggers publishing. 176 + 177 + 3. **Approve the publish workflow.** Merging triggers the `Publish Package` workflow, which builds the packages and then pauses for `Release` environment approval. Open the workflow run in GitHub Actions and approve the `Release` environment deployment; the workflow then stages the packages on npm, pushes the release tag, and generates the GitHub release. 178 + 179 + 4. **Approve the npm staged publish.** Review the staged packages on npm, then approve them with 2FA so the release becomes installable. Afterwards, confirm npm, the tag, and the GitHub release all look right. 180 + 164 181 ### Issue Triaging Workflow 165 182 166 183 ```mermaid
+3 -1
package.json
··· 3 3 "type": "module", 4 4 "version": "5.0.0-beta.4", 5 5 "private": true, 6 - "packageManager": "pnpm@11.1.2", 6 + "packageManager": "pnpm@11.6.0", 7 7 "description": "Next generation testing framework powered by Vite", 8 8 "engines": { 9 9 "node": "^22.12.0 || ^24.0.0 || >=26.0.0" ··· 47 47 "@rollup/plugin-json": "^6.1.0", 48 48 "@rollup/plugin-node-resolve": "^16.0.3", 49 49 "@types/node": "24.12.0", 50 + "@types/semver": "catalog:", 50 51 "@types/ws": "catalog:", 51 52 "@vitest/browser": "workspace:*", 52 53 "@vitest/coverage-istanbul": "workspace:*", ··· 62 63 "rollup": "^4.59.0", 63 64 "rollup-plugin-dts": "^6.3.0", 64 65 "rollup-plugin-license": "^3.7.0", 66 + "semver": "catalog:", 65 67 "tinyglobby": "catalog:", 66 68 "tsx": "^4.21.0", 67 69 "typescript": "^5.9.3",
+88 -36
pnpm-lock.yaml
··· 51 51 '@types/istanbul-reports': 52 52 specifier: ^3.0.4 53 53 version: 3.0.4 54 + '@types/semver': 55 + specifier: ^7.7.1 56 + version: 7.7.1 54 57 '@types/ws': 55 58 specifier: ^8.18.1 56 59 version: 8.18.1 ··· 117 120 playwright: 118 121 specifier: ^1.60.0 119 122 version: 1.60.0 123 + semver: 124 + specifier: ^7.8.3 125 + version: 7.8.3 120 126 sinon: 121 127 specifier: ^21.0.3 122 128 version: 21.0.3 ··· 215 221 '@types/node': 216 222 specifier: 24.12.0 217 223 version: 24.12.0 224 + '@types/semver': 225 + specifier: 'catalog:' 226 + version: 7.7.1 218 227 '@types/ws': 219 228 specifier: 'catalog:' 220 229 version: 8.18.1 ··· 260 269 rollup-plugin-license: 261 270 specifier: ^3.7.0 262 271 version: 3.7.0(picomatch@4.0.4)(rollup@4.59.0) 272 + semver: 273 + specifier: 'catalog:' 274 + version: 7.8.3 263 275 tinyglobby: 264 276 specifier: 'catalog:' 265 277 version: 0.2.15 ··· 1272 1284 version: link:../../packages/web-worker 1273 1285 '@vue/test-utils': 1274 1286 specifier: latest 1275 - version: 2.4.10(@vue/compiler-dom@3.5.29)(@vue/server-renderer@3.5.29(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3)) 1287 + version: 2.4.11(@vue/compiler-dom@3.5.29)(@vue/server-renderer@3.5.29(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3)) 1276 1288 happy-dom: 1277 1289 specifier: latest 1278 - version: 20.9.0 1290 + version: 20.10.2 1279 1291 istanbul-lib-coverage: 1280 1292 specifier: 'catalog:' 1281 1293 version: 3.2.2 ··· 1512 1524 version: link:../../packages/browser-preview 1513 1525 happy-dom: 1514 1526 specifier: latest 1515 - version: 20.9.0 1527 + version: 20.10.2 1516 1528 vitest: 1517 1529 specifier: workspace:* 1518 1530 version: link:../../packages/vitest ··· 5160 5172 '@types/resolve@1.20.2': 5161 5173 resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} 5162 5174 5175 + '@types/semver@7.7.1': 5176 + resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} 5177 + 5163 5178 '@types/sinonjs__fake-timers@8.1.5': 5164 5179 resolution: {integrity: sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==} 5165 5180 ··· 5516 5531 5517 5532 '@vue/test-utils@2.4.10': 5518 5533 resolution: {integrity: sha512-SmoZ5EA1kYiAFs9NkYdiFFQF+cSnUwnvlYEbY+DogWQZUiqOm/Y29eSbc5T6yi75SgSF9863SBeXniIEoPajCA==} 5534 + peerDependencies: 5535 + '@vue/compiler-dom': 3.x 5536 + '@vue/server-renderer': 3.x 5537 + vue: 3.x 5538 + peerDependenciesMeta: 5539 + '@vue/server-renderer': 5540 + optional: true 5541 + 5542 + '@vue/test-utils@2.4.11': 5543 + resolution: {integrity: sha512-GDqaqZsA6m2E5vNzej0aYiIb6BX8xV9pNSbbbXKOfEYwg7ZNblVX8suyqmUBThq8VIrgAJNxn+z72hVtUeiWHA==} 5519 5544 peerDependencies: 5520 5545 '@vue/compiler-dom': 3.x 5521 5546 '@vue/server-renderer': 3.x ··· 5962 5987 5963 5988 buffer-from@1.1.2: 5964 5989 resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} 5990 + 5991 + buffer-image-size@0.6.4: 5992 + resolution: {integrity: sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ==} 5993 + engines: {node: '>=4.0'} 5965 5994 5966 5995 builtin-modules@5.0.0: 5967 5996 resolution: {integrity: sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg==} ··· 7251 7280 handle-thing@2.0.1: 7252 7281 resolution: {integrity: sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==} 7253 7282 7254 - happy-dom@20.8.3: 7255 - resolution: {integrity: sha512-lMHQRRwIPyJ70HV0kkFT7jH/gXzSI7yDkQFe07E2flwmNDFoWUTRMKpW2sglsnpeA7b6S2TJPp98EbQxai8eaQ==} 7283 + happy-dom@20.10.2: 7284 + resolution: {integrity: sha512-5p9Sxis3eowDJKqx90QCsgbNA02XXqJ59NOHvD4V6cxp+rP4d/xOyVx7uY3hS8hiUbY1VeiFH8lbJ81AyuDVLQ==} 7256 7285 engines: {node: '>=20.0.0'} 7257 7286 7258 - happy-dom@20.9.0: 7259 - resolution: {integrity: sha512-GZZ9mKe8r646NUAf/zemnGbjYh4Bt8/MqASJY+pSm5ZDtc3YQox+4gsLI7yi1hba6o+eCsGxpHn5+iEVn31/FQ==} 7287 + happy-dom@20.8.3: 7288 + resolution: {integrity: sha512-lMHQRRwIPyJ70HV0kkFT7jH/gXzSI7yDkQFe07E2flwmNDFoWUTRMKpW2sglsnpeA7b6S2TJPp98EbQxai8eaQ==} 7260 7289 engines: {node: '>=20.0.0'} 7261 7290 7262 7291 has-bigints@1.1.0: ··· 9258 9287 resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} 9259 9288 hasBin: true 9260 9289 9261 - semver@7.7.2: 9262 - resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} 9263 - engines: {node: '>=10'} 9264 - hasBin: true 9265 - 9266 - semver@7.7.3: 9267 - resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} 9268 - engines: {node: '>=10'} 9269 - hasBin: true 9270 - 9271 9290 semver@7.7.4: 9272 9291 resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} 9292 + engines: {node: '>=10'} 9293 + hasBin: true 9294 + 9295 + semver@7.8.3: 9296 + resolution: {integrity: sha512-wnilbGyMxzbY7dNOl7jpKbLSjcfeweJWU5j4+u5qW+6/wuGD9KzIGOyZnQVSBM9E7DtWaaH3CyHkppYrKYoxwg==} 9273 9297 engines: {node: '>=10'} 9274 9298 hasBin: true 9275 9299 ··· 10442 10466 10443 10467 ws@8.19.0: 10444 10468 resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} 10469 + engines: {node: '>=10.0.0'} 10470 + peerDependencies: 10471 + bufferutil: ^4.0.1 10472 + utf-8-validate: '>=5.0.2' 10473 + peerDependenciesMeta: 10474 + bufferutil: 10475 + optional: true 10476 + utf-8-validate: 10477 + optional: true 10478 + 10479 + ws@8.21.0: 10480 + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} 10445 10481 engines: {node: '>=10.0.0'} 10446 10482 peerDependencies: 10447 10483 bufferutil: ^4.0.1 ··· 12874 12910 extract-zip: 2.0.1 12875 12911 progress: 2.0.3 12876 12912 proxy-agent: 6.5.0 12877 - semver: 7.7.3 12913 + semver: 7.7.4 12878 12914 tar-fs: 3.0.8 12879 12915 yargs: 17.7.2 12880 12916 transitivePeerDependencies: ··· 13563 13599 13564 13600 '@types/resolve@1.20.2': {} 13565 13601 13602 + '@types/semver@7.7.1': {} 13603 + 13566 13604 '@types/sinonjs__fake-timers@8.1.5': {} 13567 13605 13568 13606 '@types/statuses@2.0.6': {} ··· 14113 14151 optionalDependencies: 14114 14152 '@vue/server-renderer': 3.5.29(vue@3.5.29(typescript@5.9.3)) 14115 14153 14154 + '@vue/test-utils@2.4.11(@vue/compiler-dom@3.5.29)(@vue/server-renderer@3.5.29(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3))': 14155 + dependencies: 14156 + '@vue/compiler-dom': 3.5.29 14157 + js-beautify: 1.15.1 14158 + vue: 3.5.29(typescript@5.9.3) 14159 + vue-component-type-helpers: 3.2.8 14160 + optionalDependencies: 14161 + '@vue/server-renderer': 3.5.29(vue@3.5.29(typescript@5.9.3)) 14162 + 14116 14163 '@vueuse/core@12.8.2(typescript@5.9.3)': 14117 14164 dependencies: 14118 14165 '@types/web-bluetooth': 0.0.21 ··· 14536 14583 14537 14584 buffer-from@1.1.2: {} 14538 14585 14586 + buffer-image-size@0.6.4: 14587 + dependencies: 14588 + '@types/node': 24.12.0 14589 + 14539 14590 builtin-modules@5.0.0: {} 14540 14591 14541 14592 bumpp@10.4.1: ··· 14547 14598 escalade: 3.2.0 14548 14599 jsonc-parser: 3.3.1 14549 14600 package-manager-detector: 1.6.0 14550 - semver: 7.7.3 14601 + semver: 7.7.4 14551 14602 tinyexec: 1.0.2 14552 14603 tinyglobby: 0.2.15 14553 14604 yaml: 2.8.2 ··· 14654 14705 pathe: 1.1.2 14655 14706 pkg-types: 1.3.1 14656 14707 scule: 1.3.0 14657 - semver: 7.7.3 14708 + semver: 7.7.4 14658 14709 std-env: 3.10.0 14659 14710 yaml: 2.8.2 14660 14711 transitivePeerDependencies: ··· 14669 14720 convert-gitmoji: 0.1.5 14670 14721 execa: 9.6.0 14671 14722 ofetch: 1.5.1 14672 - semver: 7.7.3 14723 + semver: 7.7.4 14673 14724 tinyglobby: 0.2.15 14674 14725 transitivePeerDependencies: 14675 14726 - magicast ··· 15445 15496 globals: 15.15.0 15446 15497 globrex: 0.1.2 15447 15498 ignore: 5.3.2 15448 - semver: 7.7.3 15499 + semver: 7.7.4 15449 15500 ts-declaration-location: 1.0.7(typescript@5.9.3) 15450 15501 transitivePeerDependencies: 15451 15502 - typescript ··· 15511 15562 pluralize: 8.0.0 15512 15563 regexp-tree: 0.1.27 15513 15564 regjsparser: 0.13.0 15514 - semver: 7.7.3 15565 + semver: 7.7.4 15515 15566 strip-indent: 4.1.1 15516 15567 15517 15568 eslint-plugin-unused-imports@4.4.1(@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.3(jiti@2.6.1)): ··· 15527 15578 natural-compare: 1.4.0 15528 15579 nth-check: 2.1.1 15529 15580 postcss-selector-parser: 7.1.1 15530 - semver: 7.7.3 15581 + semver: 7.7.4 15531 15582 vue-eslint-parser: 10.4.0(eslint@10.0.3(jiti@2.6.1)) 15532 15583 xml-name-validator: 4.0.0 15533 15584 optionalDependencies: ··· 15787 15838 process-warning: 5.0.0 15788 15839 rfdc: 1.4.1 15789 15840 secure-json-parse: 4.0.0 15790 - semver: 7.7.3 15841 + semver: 7.7.4 15791 15842 toad-cache: 3.7.0 15792 15843 15793 15844 fastq@1.17.1: ··· 16114 16165 16115 16166 handle-thing@2.0.1: {} 16116 16167 16117 - happy-dom@20.8.3: 16168 + happy-dom@20.10.2: 16118 16169 dependencies: 16119 16170 '@types/node': 24.12.0 16120 16171 '@types/whatwg-mimetype': 3.0.2 16121 16172 '@types/ws': 8.18.1 16173 + buffer-image-size: 0.6.4 16122 16174 entities: 7.0.1 16123 16175 whatwg-mimetype: 3.0.0 16124 - ws: 8.19.0 16176 + ws: 8.21.0 16125 16177 transitivePeerDependencies: 16126 16178 - bufferutil 16127 16179 - utf-8-validate 16128 16180 16129 - happy-dom@20.9.0: 16181 + happy-dom@20.8.3: 16130 16182 dependencies: 16131 16183 '@types/node': 24.12.0 16132 16184 '@types/whatwg-mimetype': 3.0.2 ··· 16486 16538 '@babel/parser': 7.27.2 16487 16539 '@istanbuljs/schema': 0.1.3 16488 16540 istanbul-lib-coverage: 3.2.2 16489 - semver: 7.7.2 16541 + semver: 7.7.4 16490 16542 transitivePeerDependencies: 16491 16543 - supports-color 16492 16544 ··· 16905 16957 16906 16958 make-dir@4.0.0: 16907 16959 dependencies: 16908 - semver: 7.7.3 16960 + semver: 7.7.4 16909 16961 16910 16962 mark.js@8.11.1: {} 16911 16963 ··· 18478 18530 18479 18531 semver@6.3.1: {} 18480 18532 18481 - semver@7.7.2: {} 18482 - 18483 - semver@7.7.3: {} 18484 - 18485 18533 semver@7.7.4: {} 18534 + 18535 + semver@7.8.3: {} 18486 18536 18487 18537 send@0.18.0: 18488 18538 dependencies: ··· 18557 18607 dependencies: 18558 18608 color: 4.2.3 18559 18609 detect-libc: 2.0.4 18560 - semver: 7.7.3 18610 + semver: 7.7.4 18561 18611 optionalDependencies: 18562 18612 '@img/sharp-darwin-arm64': 0.33.5 18563 18613 '@img/sharp-darwin-x64': 0.33.5 ··· 19568 19618 eslint-visitor-keys: 5.0.0 19569 19619 espree: 11.1.0 19570 19620 esquery: 1.7.0 19571 - semver: 7.7.3 19621 + semver: 7.7.4 19572 19622 transitivePeerDependencies: 19573 19623 - supports-color 19574 19624 ··· 19928 19978 ws@8.18.3: {} 19929 19979 19930 19980 ws@8.19.0: {} 19981 + 19982 + ws@8.21.0: {} 19931 19983 19932 19984 xml-name-validator@4.0.0: {} 19933 19985
+2
pnpm-workspace.yaml
··· 62 62 '@types/istanbul-lib-source-maps': ^4.0.4 63 63 '@types/istanbul-reports': ^3.0.4 64 64 '@types/node': 24.12.0 65 + '@types/semver': ^7.7.1 65 66 '@types/ws': ^8.18.1 66 67 '@types/yauzl': ^2.10.3 67 68 '@unocss/reset': ^66.6.6 ··· 84 85 obug: ^2.1.1 85 86 pathe: ^2.0.3 86 87 playwright: ^1.60.0 88 + semver: ^7.8.3 87 89 sinon: ^21.0.3 88 90 sinon-chai: ^4.0.1 89 91 sirv: ^3.0.2
+64 -34
scripts/publish-ci.ts
··· 1 - #!/usr/bin/env zx 2 - 3 1 import { readFileSync } from 'node:fs' 4 2 import { fileURLToPath } from 'node:url' 3 + import * as semver from 'semver' 5 4 import { $ } from 'zx' 6 5 7 - if (process.env.VITE_TEST_WATCHER_DEBUG !== 'false') { 8 - throw new Error(`Cannot release Vitest without VITE_TEST_WATCHER_DEBUG=${process.env.VITE_TEST_WATCHER_DEBUG} environment variable. `) 6 + // How to test release script locally: 7 + // RELEASE_VERSION=5.0.0-beta.5 pnpm release 8 + // VITE_TEST_WATCHER_DEBUG=false PUBLISH_DRY_RUN=true PUBLISH_BRANCH=main pnpm publish-ci 5.0.0-beta.5 9 + 10 + const $$ = $({ stdio: 'inherit' }) 11 + 12 + async function main() { 13 + if (process.env.VITE_TEST_WATCHER_DEBUG !== 'false') { 14 + throw new Error(`Cannot release Vitest without VITE_TEST_WATCHER_DEBUG=${process.env.VITE_TEST_WATCHER_DEBUG} environment variable. `) 15 + } 16 + 17 + const version = process.argv[2] 18 + if (!version) { 19 + throw new Error('Missing argument to specify version') 20 + } 21 + 22 + const pkgPath = fileURLToPath(new URL('../package.json', import.meta.url)) 23 + const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) 24 + if (pkg.version !== version) { 25 + throw new Error( 26 + `Input version "${version}" does not match package.json version "${pkg.version}"`, 27 + ) 28 + } 29 + 30 + const publishBranch = process.env.PUBLISH_BRANCH 31 + if (!publishBranch) { 32 + throw new Error('Missing PUBLISH_BRANCH environment variable') 33 + } 34 + const releaseTag = await getReleaseTag(version, publishBranch) 35 + 36 + const dryRun = process.env.PUBLISH_DRY_RUN === 'true' 37 + if (dryRun) { 38 + console.log('== DRY RUN ==') 39 + } 40 + console.log(`Staging version '${version}' with tag '${releaseTag}'`) 41 + await $$`pnpm -r stage publish --access public --no-git-checks --tag ${releaseTag} ${dryRun ? ['--dry-run'] : []}` 9 42 } 10 43 11 - let version = process.argv[2] 44 + async function getReleaseTag(version: string, publishBranch: string) { 45 + // Always specify the dist-tag explicitly since otherwise `latest` would be overwritten. 46 + // Note that `main` branch doesn't always mean `latest` tag because of pre-release phase. 12 47 13 - if (!version) { 14 - throw new Error('No tag specified') 48 + // check prerelease e.g. beta, alpha, rc 49 + const parsed = semver.parse(version, {}, true) 50 + if (parsed.prerelease.length > 0) { 51 + return parsed.prerelease[0] 52 + } 53 + 54 + // If the version is not a pre-release and is greater than the latest version on npm, 55 + // then that should become the new latest version. 56 + const npmView = await $`npm view vitest dist-tags --json` 57 + const latestVersion = JSON.parse(npmView.stdout).latest 58 + if (semver.gt(version, latestVersion)) { 59 + return 'latest' 60 + } 61 + 62 + // Otherwise this is a backport release. 63 + // Use the uppercase of the branch name to avoid npm dist-tag caveats 64 + // https://docs.npmjs.com/cli/v11/commands/npm-dist-tag#caveats 65 + // - v4 branch -> V4 dist tag 66 + // - v4.1 branch -> V4.1 dist tag 67 + return publishBranch.toUpperCase() 15 68 } 16 69 17 - if (version.startsWith('v')) { 18 - version = version.slice(1) 19 - } 20 - 21 - const pkgPath = fileURLToPath(new URL('../package.json', import.meta.url)) 22 - const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) 23 - 24 - if (pkg.version !== version) { 25 - throw new Error( 26 - `Package version from tag "${version}" mismatches with the current version "${pkg.version}"`, 27 - ) 28 - } 29 - 30 - const releaseTag = version.includes('beta') 31 - ? 'beta' 32 - : version.includes('alpha') 33 - ? 'alpha' 34 - : undefined 35 - 36 - console.log('Publishing version', version, 'with tag', releaseTag || 'latest') 37 - 38 - if (releaseTag) { 39 - await $`pnpm -r publish --access public --no-git-checks --tag ${releaseTag}` 40 - } 41 - else { 42 - await $`pnpm -r publish --access public --no-git-checks` 43 - } 70 + main().catch((error) => { 71 + console.error('Error during publishing:', error) 72 + process.exit(1) 73 + })
+16 -11
scripts/release.ts
··· 1 - #!/usr/bin/env zx 2 - 3 1 import { versionBump } from 'bumpp' 4 2 import { glob } from 'tinyglobby' 5 3 6 - try { 7 - const packages = await glob(['package.json', './packages/*/package.json'], { expandDirectories: false }) 4 + async function main() { 5 + const packages = await glob(['package.json', './packages/*/package.json'], { 6 + expandDirectories: false, 7 + }) 8 8 9 9 console.log('Bumping versions in packages:', packages.join(', '), '\n') 10 10 11 + const release = process.env.RELEASE_VERSION || process.env.RELEASE_TYPE 12 + 11 13 await versionBump({ 12 14 files: packages, 15 + release, 13 16 commit: true, 14 - push: true, 15 - tag: true, 17 + tag: false, 18 + push: false, 19 + printCommits: false, 20 + confirm: !release, 16 21 }) 22 + } 17 23 18 - console.log('New release is ready, waiting for conformation at https://github.com/vitest-dev/vitest/actions') 19 - } 20 - catch (err) { 21 - console.error(err) 22 - } 24 + main().catch((error) => { 25 + console.error('Error during version bump:', error) 26 + process.exit(1) 27 + })
+99
.github/workflows/prepare-publish.yml
··· 1 + name: Prepare Publish 2 + 3 + on: 4 + workflow_dispatch: 5 + inputs: 6 + target_branch: 7 + description: Release target branch. 8 + required: true 9 + default: main 10 + type: string 11 + release: 12 + description: Bumpp release type. 13 + required: true 14 + default: next 15 + type: choice 16 + options: 17 + - next 18 + - patch 19 + - minor 20 + - major 21 + - prepatch 22 + - preminor 23 + - premajor 24 + version: 25 + description: Specify exact version instead of bumpp release type 26 + required: false 27 + type: string 28 + 29 + concurrency: 30 + group: ${{ github.workflow }}-${{ inputs.target_branch }}-${{ inputs.version || inputs.release }} 31 + cancel-in-progress: false 32 + 33 + permissions: {} 34 + 35 + jobs: 36 + prepare: 37 + if: github.repository == 'vitest-dev/vitest' 38 + name: Prepare release PR 39 + runs-on: ubuntu-latest 40 + permissions: 41 + contents: read # checkout target branch 42 + steps: 43 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 44 + with: 45 + ref: ${{ inputs.target_branch }} 46 + fetch-depth: 0 47 + persist-credentials: false 48 + 49 + - name: Install pnpm 50 + uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 51 + 52 + - name: Set node version to 24 53 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 54 + with: 55 + node-version: 24 56 + package-manager-cache: false 57 + 58 + - name: Install 59 + run: pnpm install --frozen-lockfile --prefer-offline 60 + env: 61 + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1' 62 + 63 + - name: Create branch and update version 64 + env: 65 + TARGET_BRANCH: ${{ inputs.target_branch }} 66 + RELEASE_TYPE: ${{ inputs.release }} 67 + RELEASE_VERSION: ${{ inputs.version }} 68 + RUN_ID: ${{ github.run_id }} 69 + run: | 70 + RELEASE_INPUT="${RELEASE_VERSION:-$RELEASE_TYPE}" 71 + PREPARE_BRANCH="prepare-$TARGET_BRANCH-$RELEASE_INPUT-$RUN_ID" 72 + git switch -c "$PREPARE_BRANCH" 73 + # The numeric prefix comes from `gh api /users/vitest-release-bot%5Bbot%5D --jq .id`. 74 + # This is just to make the avatar in the commit look pretty. 75 + git config user.name "vitest-release-bot[bot]" 76 + git config user.email "292707936+vitest-release-bot[bot]@users.noreply.github.com" 77 + pnpm run release 78 + 79 + - id: generate-token 80 + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 81 + with: 82 + app-id: ${{ secrets.RELEASE_GITHUB_APP_ID }} 83 + private-key: ${{ secrets.RELEASE_GITHUB_APP_PRIVATE_KEY }} 84 + permission-contents: write 85 + permission-pull-requests: write 86 + 87 + - name: Open release PR 88 + env: 89 + TARGET_BRANCH: ${{ inputs.target_branch }} 90 + GH_TOKEN: ${{ steps.generate-token.outputs.token }} 91 + run: | 92 + PREPARE_BRANCH="$(git branch --show-current)" 93 + VERSION="$(jq -r .version package.json)" 94 + git push -u "https://x-access-token:$GH_TOKEN@github.com/$GITHUB_REPOSITORY.git" HEAD 95 + gh pr create \ 96 + --base "$TARGET_BRANCH" \ 97 + --head "$PREPARE_BRANCH" \ 98 + --title "chore: release v$VERSION" \ 99 + --body "Release PR generated by the Prepare Publish workflow."
+74 -7
.github/workflows/publish.yml
··· 2 2 3 3 on: 4 4 push: 5 - tags: 6 - - 'v*' 5 + branches: 6 + - main 7 + - 'v[0-9]*' 7 8 8 9 permissions: {} 9 10 ··· 11 12 VITE_TEST_WATCHER_DEBUG: 'false' 12 13 13 14 jobs: 14 - publish: 15 - # only run on main, don't trigger in forks 15 + # Keep detection outside the Release environment. Environment approval is job-level, 16 + # so the publish job is only created after a release commit is detected. 17 + detect: 16 18 if: github.repository == 'vitest-dev/vitest' 19 + name: Detect release commit 20 + runs-on: ubuntu-slim 21 + outputs: 22 + release: ${{ steps.detect.outputs.release }} 23 + version: ${{ steps.detect.outputs.version }} 24 + steps: 25 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 26 + with: 27 + fetch-depth: 0 28 + persist-credentials: false 29 + 30 + - name: Detect release 31 + id: detect 32 + run: | 33 + SUBJECT="$(git log -1 --format=%s)" 34 + VERSION="$(jq -r .version package.json)" 35 + EXPECTED="chore: release v$VERSION" 36 + # use prefix match since commit has PR number trailer. 37 + if [[ "$SUBJECT" == "$EXPECTED"* ]]; then 38 + echo "release=true" >> "$GITHUB_OUTPUT" 39 + echo "version=$VERSION" >> "$GITHUB_OUTPUT" 40 + echo "Detected release v$VERSION" 41 + else 42 + echo "release=false" >> "$GITHUB_OUTPUT" 43 + echo "No release commit found at HEAD" 44 + fi 45 + 46 + publish: 47 + if: needs.detect.outputs.release == 'true' 48 + needs: detect 17 49 name: Publish Vitest 18 50 runs-on: ubuntu-latest 19 51 permissions: ··· 25 57 with: 26 58 fetch-depth: 0 27 59 persist-credentials: false 60 + 61 + - name: Check release tag 62 + env: 63 + VERSION: ${{ needs.detect.outputs.version }} 64 + run: | 65 + TAG="v$VERSION" 66 + 67 + if git rev-parse --verify --quiet "refs/tags/$TAG"; then 68 + echo "Tag $TAG already exists" 69 + exit 1 70 + fi 28 71 29 72 - name: Install pnpm 30 73 uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 ··· 45 88 - name: Build 46 89 run: pnpm build 47 90 48 - - name: Publish to npm 49 - run: npm i -g npm@^11.5.2 && pnpm run publish-ci "${GITHUB_REF_NAME}" 91 + - name: Stage publish to npm (dry run) 92 + env: 93 + VERSION: ${{ needs.detect.outputs.version }} 94 + PUBLISH_BRANCH: ${{ github.ref_name }} 95 + PUBLISH_DRY_RUN: 'true' 96 + run: pnpm run publish-ci "$VERSION" 97 + 98 + - name: Stage publish to npm 99 + env: 100 + VERSION: ${{ needs.detect.outputs.version }} 101 + PUBLISH_BRANCH: ${{ github.ref_name }} 102 + run: pnpm run publish-ci "$VERSION" 103 + 104 + - name: Push release tag 105 + env: 106 + VERSION: ${{ needs.detect.outputs.version }} 107 + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 108 + run: | 109 + TAG="v$VERSION" 110 + git config user.name "vitest-release-bot" 111 + git config user.email "actions@github.com" 112 + git tag "$TAG" "$GITHUB_SHA" 113 + git push "https://x-access-token:$GITHUB_TOKEN@github.com/$GITHUB_REPOSITORY.git" "$TAG" 50 114 51 115 - name: Generate Changelog 52 - run: npx changelogithub 53 116 env: 117 + VERSION: ${{ needs.detect.outputs.version }} 54 118 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 119 + run: | 120 + TAG="v$VERSION" 121 + npx changelogithub --to "$TAG" --name "$TAG"
+2
.github/workflows/zizmor.yml
··· 26 26 with: 27 27 persist-credentials: false 28 28 29 + # test locally by 30 + # zizmor .github/workflows --pedantic 29 31 - name: Run zizmor 🌈 30 32 uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6 31 33 with: