From 619a5cd838e5bda5b92350fe6b0fe0a8bee31f76 Mon Sep 17 00:00:00 2001 From: sapphi-red Date: Thu, 30 Jul 2026 15:17:57 +0900 Subject: [PATCH 1/3] ci: setup PR-driven release --- .github/RELEASING.md | 45 ++++++ ...re-rsc-release.yml => prepare-release.yml} | 34 +++-- .github/workflows/publish.yml | 136 ++++++------------ CONTRIBUTING.md | 4 + package.json | 2 - packages/plugin-rsc/CONTRIBUTING.md | 4 - packages/plugin-rsc/RELEASING.md | 40 ------ pnpm-lock.yaml | 3 - scripts/extract-changelog.ts | 9 +- scripts/prepare-release.ts | 68 +++++++++ scripts/prepare-rsc-release.ts | 27 ---- scripts/publishCI.ts | 27 +++- scripts/release-utils.ts | 14 ++ scripts/release.ts | 57 -------- 14 files changed, 219 insertions(+), 251 deletions(-) create mode 100644 .github/RELEASING.md rename .github/workflows/{prepare-rsc-release.yml => prepare-release.yml} (69%) delete mode 100644 packages/plugin-rsc/RELEASING.md create mode 100644 scripts/prepare-release.ts delete mode 100644 scripts/prepare-rsc-release.ts create mode 100644 scripts/release-utils.ts delete mode 100644 scripts/release.ts diff --git a/.github/RELEASING.md b/.github/RELEASING.md new file mode 100644 index 000000000..b359e60b9 --- /dev/null +++ b/.github/RELEASING.md @@ -0,0 +1,45 @@ +# Releasing Packages + +All publishable packages in this repository use a pull-request-driven release flow. GitHub Actions prepares a release PR containing the version bump and changelog, and merging that PR triggers publishing. + +## Prepare a Release + +1. Run the [Prepare Release](workflows/prepare-release.yml) workflow from the `main` branch. +2. Select the package to release. +3. Select a `release` type. The default `next` creates the next patch for a stable version or advances an existing prerelease. Alternatively, select another supported release type or enter an exact `version`, which takes precedence over `release`. Prereleases must use an `alpha` or `beta` identifier so npm assigns the correct distribution tag. +4. Wait for `vite-release-bot` to open the release PR. The PR contains the selected package's version bump and changelog entry. + +The `plugin-react` and `plugin-react-swc` releases move their manually maintained `Unreleased` notes under the new version. The `plugin-rsc` release generates its changelog from conventional commits. + +To preview the available release types and versions locally, run the following command from the repository root and cancel before confirming so no files are changed: + +```sh +node scripts/prepare-release.ts plugin-react +``` + +Replace `plugin-react` with `plugin-react-swc` or `plugin-rsc` as needed. + +## Review and Publish + +1. Review the generated version and changelog, and wait for the release PR checks to pass. +2. Merge the PR while retaining the `release: @` commit subject. A matching commit at the tip of `main` triggers the publish job. +3. Open the [Publish Package](workflows/publish.yml) run and approve its `Release` environment deployment. +4. Wait for the workflow to publish the package, create the `@` tag, and create the corresponding GitHub release. +5. Verify the new version on npm and check that the tag and GitHub release point to the merged release commit. + +## Repository Configuration + +The release flow depends on configuration outside this repository: + +- The `vite-release-bot` GitHub App must be installed only on `vitejs/vite-plugin-react` with Contents and Pull requests read/write permissions. Webhooks must be disabled. +- `RELEASE_GITHUB_APP_CLIENT_ID` must be a repository variable containing the app client ID. +- `RELEASE_GITHUB_APP_PRIVATE_KEY` must be a repository secret containing the app private key. +- The `Release` environment must require maintainer approval before publishing. +- The npm trusted publishers for `@vitejs/plugin-react`, `@vitejs/plugin-react-swc`, and `@vitejs/plugin-rsc` must be restricted to `vitejs/vite-plugin-react`, `.github/workflows/publish.yml`, and the `Release` environment. + +## Recovery + +- If release preparation fails, fix the cause and rerun the preparation workflow. Each run creates a uniquely named branch. +- If publishing fails before npm accepts the package, fix the cause and rerun the failed workflow. +- If npm publishing succeeds but tag or GitHub release creation fails, do not rerun the entire publish job blindly because npm versions are immutable. Confirm the package state first, then manually create and push the `@` tag at the original release commit and create the GitHub release from the corresponding changelog entry. +- If the release tag already exists, investigate whether the package was previously published before retrying or changing any release state. diff --git a/.github/workflows/prepare-rsc-release.yml b/.github/workflows/prepare-release.yml similarity index 69% rename from .github/workflows/prepare-rsc-release.yml rename to .github/workflows/prepare-release.yml index c264a4c6d..311412180 100644 --- a/.github/workflows/prepare-rsc-release.yml +++ b/.github/workflows/prepare-release.yml @@ -1,8 +1,17 @@ -name: Prepare RSC Release +name: Prepare Release on: workflow_dispatch: inputs: + package: + description: Package to release + required: true + default: plugin-react + type: choice + options: + - plugin-react + - plugin-react-swc + - plugin-rsc release: description: Bumpp release type required: true @@ -22,7 +31,7 @@ on: type: string concurrency: - group: ${{ github.workflow }}-${{ inputs.version || inputs.release }} + group: ${{ github.workflow }}-${{ inputs.package }}-${{ inputs.version || inputs.release }} cancel-in-progress: false permissions: {} @@ -30,7 +39,7 @@ permissions: {} jobs: prepare: if: github.repository == 'vitejs/vite-plugin-react' - name: Prepare RSC release PR + name: Prepare release PR runs-on: ubuntu-latest permissions: contents: read @@ -59,11 +68,11 @@ jobs: - name: Prepare release id: prepare env: - RELEASE_TYPE: ${{ inputs.release }} - RELEASE_VERSION: ${{ inputs.version }} + RELEASE: ${{ inputs.version || inputs.release }} + RELEASE_PACKAGE: ${{ inputs.package }} run: | - node scripts/prepare-rsc-release.ts - echo "version=$(jq -r .version packages/plugin-rsc/package.json)" >> "$GITHUB_OUTPUT" + node scripts/prepare-release.ts "$RELEASE_PACKAGE" "$RELEASE" + echo "version=$(jq -r .version packages/$RELEASE_PACKAGE/package.json)" >> "$GITHUB_OUTPUT" - id: generate-token uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 @@ -79,18 +88,19 @@ jobs: env: APP_SLUG: ${{ steps.generate-token.outputs.app-slug }} GH_TOKEN: ${{ steps.generate-token.outputs.token }} + PACKAGE: ${{ inputs.package }} VERSION: ${{ steps.prepare.outputs.version }} run: | - BRANCH="release-plugin-rsc-$VERSION-$GITHUB_RUN_ID" + BRANCH="release-$PACKAGE-$VERSION-$GITHUB_RUN_ID" BOT_ID="$(gh api "/users/$APP_SLUG%5Bbot%5D" --jq .id)" git config user.name "$APP_SLUG[bot]" git config user.email "$BOT_ID+$APP_SLUG[bot]@users.noreply.github.com" git switch -c "$BRANCH" - git add packages/plugin-rsc/package.json packages/plugin-rsc/CHANGELOG.md - git commit -m "release: plugin-rsc@$VERSION" + git add "packages/$PACKAGE/package.json" "packages/$PACKAGE/CHANGELOG.md" + git commit -m "release: $PACKAGE@$VERSION" git push -u "https://x-access-token:$GH_TOKEN@github.com/$GITHUB_REPOSITORY.git" HEAD gh pr create \ --base main \ --head "$BRANCH" \ - --title "release: plugin-rsc@$VERSION" \ - --body "RSC release PR generated by the Prepare RSC Release workflow: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" + --title "release: $PACKAGE@$VERSION" \ + --body "$PACKAGE release PR generated by the Prepare Release workflow: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index e2ed4ed6a..92fa295a2 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -2,113 +2,48 @@ name: Publish Package on: push: - # RSC releases start when a generated release PR is merged. branches: - main - # Other packages retain the existing tag-driven release flow. - tags: - - 'plugin-*' permissions: {} jobs: - # Publish tag-driven releases, excluding RSC's commit-driven flow. - publish: - if: github.repository == 'vitejs/vite-plugin-react' && github.ref_type == 'tag' && !startsWith(github.ref, 'refs/tags/plugin-rsc@') - runs-on: ubuntu-latest - permissions: - contents: write # for ArnaudBarre/github-release to create a release - id-token: write # for provenance generation - environment: Release - steps: - - name: Checkout - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - persist-credentials: false - - - name: Install pnpm - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 - - - name: Set node version - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: 24 - registry-url: https://registry.npmjs.org/ - # disable cache, to avoid cache poisoning (https://docs.zizmor.sh/audits/#cache-poisoning) - package-manager-cache: false - - - name: Disallow installation scripts - run: yq '.onlyBuiltDependencies = []' -i pnpm-workspace.yaml - - - name: Install deps - run: pnpm install - env: - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1' - - - name: Get pkgName for tag - id: tag - run: | - # Check if the tag contains "alpha" - if [[ $GITHUB_REF_NAME =~ alpha ]]; then - echo "isAlpha=true" >> $GITHUB_OUTPUT - else - echo "isAlpha=false" >> $GITHUB_OUTPUT - fi - - # `%@*` truncates @ and version number from the right side. - # https://stackoverflow.com/questions/9532654/expression-after-last-specific-character - pkgName=${GITHUB_REF_NAME%@*} - echo "pkgName=$pkgName" >> $GITHUB_OUTPUT - - - if: steps.tag.outputs.pkgName == 'plugin-react-swc' - working-directory: packages/plugin-react-swc - run: pnpm build - - - name: Publish package - # npm 11.5.2+ is required for trusted publishing. - # zizmor: ignore[adhoc-packages] - run: npm i -g npm@^11.5.2 && pnpm run ci-publish "$GITHUB_REF_NAME" - - - if: steps.tag.outputs.isAlpha == 'false' - uses: ArnaudBarre/github-release@4fa6eafe8e2449c7c1c5a91ae50de4ee34db0b40 # v1.5.0 - with: - path: packages/${{ steps.tag.outputs.pkgName }}/CHANGELOG.md - tag-name: ${{ github.ref_name }} - - # Only main pushes can contain an RSC release commit from a merged release PR. - detect-rsc-release: - if: github.repository == 'vitejs/vite-plugin-react' && github.ref == 'refs/heads/main' - name: Detect RSC release commit + detect-release: + if: github.repository == 'vitejs/vite-plugin-react' + name: Detect release commit runs-on: ubuntu-slim outputs: + package: ${{ steps.detect.outputs.package }} release: ${{ steps.detect.outputs.release }} version: ${{ steps.detect.outputs.version }} steps: - name: Checkout uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: - fetch-depth: 0 persist-credentials: false - name: Detect release id: detect run: | SUBJECT="$(git log -1 --format=%s)" - VERSION="$(jq -r .version packages/plugin-rsc/package.json)" - EXPECTED="release: plugin-rsc@$VERSION" - if [[ "$SUBJECT" == "$EXPECTED"* ]]; then - echo "release=true" >> "$GITHUB_OUTPUT" - echo "version=$VERSION" >> "$GITHUB_OUTPUT" - echo "Detected RSC release $VERSION" - else - echo "release=false" >> "$GITHUB_OUTPUT" - echo "No RSC release commit found at HEAD" - fi - - publish-rsc: - if: needs.detect-rsc-release.outputs.release == 'true' - needs: detect-rsc-release - name: Publish RSC + for PACKAGE in plugin-react plugin-react-swc plugin-rsc; do + VERSION="$(jq -r .version "packages/$PACKAGE/package.json")" + EXPECTED="release: $PACKAGE@$VERSION" + if [[ "$SUBJECT" == "$EXPECTED"* ]]; then + echo "package=$PACKAGE" >> "$GITHUB_OUTPUT" + echo "release=true" >> "$GITHUB_OUTPUT" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "Detected $PACKAGE release $VERSION" + exit 0 + fi + done + echo "release=false" >> "$GITHUB_OUTPUT" + echo "No release commit found at HEAD" + + publish-release: + if: needs.detect-release.outputs.release == 'true' + needs: detect-release + name: Publish ${{ needs.detect-release.outputs.package }} runs-on: ubuntu-latest permissions: contents: write @@ -123,9 +58,10 @@ jobs: - name: Check release tag env: - VERSION: ${{ needs.detect-rsc-release.outputs.version }} + PACKAGE: ${{ needs.detect-release.outputs.package }} + VERSION: ${{ needs.detect-release.outputs.version }} run: | - TAG="plugin-rsc@$VERSION" + TAG="$PACKAGE@$VERSION" if git rev-parse --verify --quiet "refs/tags/$TAG"; then echo "Tag $TAG already exists" exit 1 @@ -149,10 +85,17 @@ jobs: env: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1' + - name: Build plugin-react-swc package + if: needs.detect-release.outputs.package == 'plugin-react-swc' + run: pnpm --filter @vitejs/plugin-react-swc build + - name: Publish package - # TODO: plugin-rsc doesn't currently publish alpha/beta prereleases. If it does, - # specify an npm dist-tag with `publish --tag ...`. - run: pnpm --filter @vitejs/plugin-rsc --fail-if-no-match publish --access public --no-git-checks + env: + PACKAGE: ${{ needs.detect-release.outputs.package }} + VERSION: ${{ needs.detect-release.outputs.version }} + # npm 11.5.2+ is required for trusted publishing. + # zizmor: ignore[adhoc-packages] + run: npm i -g npm@^11.5.2 && pnpm run ci-publish "$PACKAGE@$VERSION" - id: generate-token uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 @@ -165,8 +108,8 @@ jobs: - name: Create GitHub release run: | - TAG="plugin-rsc@$VERSION" - node scripts/extract-changelog.ts packages/plugin-rsc/CHANGELOG.md "$VERSION" > "$NOTES_FILE" + TAG="$PACKAGE@$VERSION" + node scripts/extract-changelog.ts "packages/$PACKAGE/CHANGELOG.md" "$VERSION" > "$NOTES_FILE" prerelease=() if [[ "${VERSION%%+*}" == *-* ]]; then @@ -180,5 +123,6 @@ jobs: "${prerelease[@]}" env: GH_TOKEN: ${{ steps.generate-token.outputs.token }} - NOTES_FILE: ${{ runner.temp }}/plugin-rsc-release-notes.md - VERSION: ${{ needs.detect-rsc-release.outputs.version }} + NOTES_FILE: ${{ runner.temp }}/release-notes.md + PACKAGE: ${{ needs.detect-release.outputs.package }} + VERSION: ${{ needs.detect-release.outputs.version }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 94862594c..abc52a8b2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -133,6 +133,10 @@ In many test cases, we need to mock dependencies using `link:` and `file:` proto For a mock dependency, make sure you add a `@vitejs/test-` prefix to the package name. This will avoid possible issues like false-positive alerts. +## Releasing + +See [RELEASING.md](./.github/RELEASING.md) for the maintainer release process. + ## Contributing to `@vitejs/plugin-rsc` See [CONTRIBUTING.md](packages/plugin-rsc/CONTRIBUTING.md) in the `@vitejs/plugin-rsc` package for specific guidelines on contributing to the React Server Components plugin. diff --git a/package.json b/package.json index c0d7f1d09..4d85b7f2a 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,6 @@ "debug-build": "VITE_TEST_BUILD=1 VITE_PRESERVE_BUILD_ARTIFACTS=1 vitest run -c playground/vitest.config.e2e.ts", "build": "pnpm -r --filter='./packages/*' run build", "dev": "pnpm -r --parallel --filter='./packages/*' run dev", - "release": "node scripts/release.ts", "ci-publish": "node scripts/publishCI.ts", "override-react": "bash scripts/override-react.sh", "override-vite7": "yq -i '.overrides.vite = \"^7\"' pnpm-workspace.yaml && yq -i '.overrides.\"@vitejs/plugin-react\" = \"^5\"' pnpm-workspace.yaml && yq -i '.packages += [\"!packages/plugin-react\"]' pnpm-workspace.yaml" @@ -44,7 +43,6 @@ "globals": "^17.8.0", "lint-staged": "^17.2.0", "oxfmt": "^0.60.0", - "picocolors": "^1.1.1", "playwright-chromium": "^1.62.0", "simple-git-hooks": "^2.13.1", "typescript": "^6.0.3", diff --git a/packages/plugin-rsc/CONTRIBUTING.md b/packages/plugin-rsc/CONTRIBUTING.md index 7cf2383e4..3102292e6 100644 --- a/packages/plugin-rsc/CONTRIBUTING.md +++ b/packages/plugin-rsc/CONTRIBUTING.md @@ -69,7 +69,3 @@ pnpm -C packages/plugin-rsc test-e2e -g "hmr" # Test projects created with `setupInlineFixture` are locally runnable. For example: pnpm -C packages/plugin-rsc/examples/e2e/temp/react-compiler dev ``` - -## Releasing - -See [RELEASING.md](RELEASING.md) for the maintainer release process. diff --git a/packages/plugin-rsc/RELEASING.md b/packages/plugin-rsc/RELEASING.md deleted file mode 100644 index 4c51b24bf..000000000 --- a/packages/plugin-rsc/RELEASING.md +++ /dev/null @@ -1,40 +0,0 @@ -# Releasing @vitejs/plugin-rsc - -`@vitejs/plugin-rsc` uses a pull-request-driven release flow. GitHub Actions prepares a release PR containing the version bump and changelog, and merging that PR triggers publishing. The other packages in this repository continue to use the existing tag-driven release flow. - -## Prepare a Release - -1. Run the [Prepare RSC Release](../../.github/workflows/prepare-rsc-release.yml) workflow from the `main` branch. -2. Select a `release` type. The default `next` creates the next patch for a stable version or advances an existing prerelease. Alternatively, select another supported release type or enter an exact `version`, which takes precedence over `release`. -3. Wait for `vite-release-bot` to open the release PR. The PR contains the `packages/plugin-rsc/package.json` version bump and generated `packages/plugin-rsc/CHANGELOG.md` entry. - -To preview the available release types and versions locally, run the following command from the repository root and cancel before confirming so no files are changed: - -```sh -node scripts/prepare-rsc-release.ts -``` - -## Review and Publish - -1. Review the generated version and changelog, and wait for the release PR checks to pass. -2. Merge the PR while retaining the `release: plugin-rsc@` commit subject. A matching commit at the tip of `main` triggers the RSC publish job. -3. Open the [Publish Package](../../.github/workflows/publish.yml) run and approve its `Release` environment deployment. -4. Wait for the workflow to publish the package, create the `plugin-rsc@` tag, and create the corresponding GitHub release. -5. Verify the new version on npm and check that the tag and GitHub release point to the merged release commit. - -## Repository Configuration - -The release flow depends on configuration outside this repository: - -- The `vite-release-bot` GitHub App must be installed only on `vitejs/vite-plugin-react` with Contents and Pull requests read/write permissions. Webhooks must be disabled. -- `RELEASE_GITHUB_APP_CLIENT_ID` must be a repository variable containing the app client ID. -- `RELEASE_GITHUB_APP_PRIVATE_KEY` must be a repository secret containing the app private key. -- The `Release` environment must require maintainer approval before publishing. -- The npm trusted publisher for `@vitejs/plugin-rsc` must be restricted to `vitejs/vite-plugin-react`, `.github/workflows/publish.yml`, and the `Release` environment. - -## Recovery - -- If release preparation fails, fix the cause and rerun the preparation workflow. Each run creates a uniquely named branch. -- If publishing fails before npm accepts the package, fix the cause and rerun the failed workflow. -- If npm publishing succeeds but tag or GitHub release creation fails, do not rerun the entire publish job blindly because npm versions are immutable. Confirm the package state first, then manually create and push the `plugin-rsc@` tag at the original release commit and create the GitHub release from the corresponding changelog entry. -- If the release tag already exists, investigate whether the package was previously published before retrying or changing any release state. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8cafbaf59..da2a39b18 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -47,9 +47,6 @@ importers: oxfmt: specifier: ^0.60.0 version: 0.60.0 - picocolors: - specifier: ^1.1.1 - version: 1.1.1 playwright-chromium: specifier: ^1.62.0 version: 1.62.0 diff --git a/scripts/extract-changelog.ts b/scripts/extract-changelog.ts index f1729bc9d..3f3ced11b 100644 --- a/scripts/extract-changelog.ts +++ b/scripts/extract-changelog.ts @@ -7,9 +7,12 @@ function main() { } const sections = readFileSync(path, 'utf-8').split(/^## /m).slice(1) - const section = sections.find((section) => - section.split('\n', 1)[0].includes(`[${version}](`), - ) + const section = sections.find((section) => { + const heading = section.split('\n', 1)[0] + return ( + heading.includes(`[${version}](`) || heading.startsWith(`${version} (`) + ) + }) if (!section) throw new Error(`Missing changelog entry for ${version}`) const notes = section.split('\n').slice(1).join('\n').trim() diff --git a/scripts/prepare-release.ts b/scripts/prepare-release.ts new file mode 100644 index 000000000..4a7f7dbfc --- /dev/null +++ b/scripts/prepare-release.ts @@ -0,0 +1,68 @@ +import { readFileSync, writeFileSync } from 'node:fs' +import { generateChangelog } from '@vitejs/release-scripts' +import { versionBump } from 'bumpp' +import { validatePublishVersion } from './release-utils.ts' + +const releasePackages = [ + 'plugin-react', + 'plugin-react-swc', + 'plugin-rsc', +] as const + +type ReleasePackage = (typeof releasePackages)[number] + +function parseReleasePackage(value: string | undefined): ReleasePackage { + if ((releasePackages as readonly (string | undefined)[]).includes(value)) { + return value as ReleasePackage + } + throw new Error( + `Invalid release package ${JSON.stringify(value)}. Expected one of: ${releasePackages.join(', ')}`, + ) +} + +async function main() { + const releasePackage = parseReleasePackage(process.argv[2]) + const release = process.argv[3] + + const pkgDir = `packages/${releasePackage}` + const pkgPath = `${pkgDir}/package.json` + + await versionBump({ + files: [pkgPath], + release, + commit: false, + tag: false, + push: false, + printCommits: false, + confirm: !release, + }) + + const { version } = JSON.parse(readFileSync(pkgPath, 'utf-8')) as { + version: string + } + validatePublishVersion(version) + + if (releasePackage === 'plugin-rsc') { + await generateChangelog({ + getPkgDir: () => pkgDir, + tagPrefix: `${releasePackage}@`, + }) + return + } + + const changelogPath = `${pkgDir}/CHANGELOG.md` + const changelog = readFileSync(changelogPath, 'utf-8') + const date = new Date().toISOString().slice(0, 10) + writeFileSync( + changelogPath, + changelog.replace( + '## Unreleased', + `## Unreleased\n\n## ${version} (${date})`, + ), + ) +} + +main().catch((error) => { + console.error('Error preparing release:', error) + process.exit(1) +}) diff --git a/scripts/prepare-rsc-release.ts b/scripts/prepare-rsc-release.ts deleted file mode 100644 index 183581e37..000000000 --- a/scripts/prepare-rsc-release.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { generateChangelog } from '@vitejs/release-scripts' -import { versionBump } from 'bumpp' - -async function main() { - const release = process.env.RELEASE_VERSION || process.env.RELEASE_TYPE - const pkgPath = 'packages/plugin-rsc/package.json' - - await versionBump({ - files: [pkgPath], - release, - commit: false, - tag: false, - push: false, - printCommits: false, - confirm: !release, - }) - - await generateChangelog({ - getPkgDir: () => 'packages/plugin-rsc', - tagPrefix: 'plugin-rsc@', - }) -} - -main().catch((error) => { - console.error('Error preparing RSC release:', error) - process.exit(1) -}) diff --git a/scripts/publishCI.ts b/scripts/publishCI.ts index d792b0712..6a8973ec1 100644 --- a/scripts/publishCI.ts +++ b/scripts/publishCI.ts @@ -1,10 +1,23 @@ import { publish } from '@vitejs/release-scripts' +import { validatePublishVersion } from './release-utils.ts' -publish({ - getPkgDir(pkg) { - if (pkg === 'plugin-react-swc') { - return `packages/${pkg}/dist` - } - return `packages/${pkg}` - }, +async function main() { + const tag = process.argv[2] + const version = tag?.split('@')[1] + if (!version) throw new Error(`Invalid package tag ${JSON.stringify(tag)}`) + validatePublishVersion(version) + + await publish({ + getPkgDir(pkg) { + if (pkg === 'plugin-react-swc') { + return `packages/${pkg}/dist` + } + return `packages/${pkg}` + }, + }) +} + +main().catch((error) => { + console.error('Error publishing package:', error) + process.exit(1) }) diff --git a/scripts/release-utils.ts b/scripts/release-utils.ts new file mode 100644 index 000000000..4e2ee1abc --- /dev/null +++ b/scripts/release-utils.ts @@ -0,0 +1,14 @@ +export function validatePublishVersion(version: string): void { + const prerelease = version.match(/^\d+\.\d+\.\d+-([^+]+)/)?.[1] + // only allow alpha and beta prereleases + // as @vitejs/release-scripts only supports these two types + if ( + prerelease && + !prerelease.includes('alpha') && + !prerelease.includes('beta') + ) { + throw new Error( + `Only alpha and beta prereleases are supported, received ${version}`, + ) + } +} diff --git a/scripts/release.ts b/scripts/release.ts deleted file mode 100644 index 67b1f4de6..000000000 --- a/scripts/release.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { readFileSync, writeFileSync } from 'node:fs' -import { generateChangelog, release } from '@vitejs/release-scripts' -import colors from 'picocolors' - -const nextH2RE = /^## /gm - -release({ - repo: 'vite-plugin-react', - packages: ['plugin-react', 'plugin-react-swc', 'plugin-rsc'], - getPkgDir(pkg) { - if (pkg === 'plugin-react-swc') { - return `packages/${pkg}/dist` - } - return `packages/${pkg}` - }, - toTag: (pkg, version) => `${pkg}@${version}`, - logChangelog: async (pkgName) => { - if (pkgName === 'plugin-rsc') return - - const changelog = readFileSync(`packages/${pkgName}/CHANGELOG.md`, 'utf-8') - if (!changelog.includes('## Unreleased')) { - throw new Error("Can't find '## Unreleased' section in CHANGELOG.md") - } - const index = changelog.indexOf('## Unreleased') + 13 - nextH2RE.lastIndex = index - const nextH2Pos = nextH2RE.exec(changelog)?.index - console.log(colors.dim(changelog.slice(index, nextH2Pos).trim())) - }, - generateChangelog: async (pkgName, version) => { - if (pkgName === 'plugin-rsc') { - await generateChangelog({ - getPkgDir: () => `packages/${pkgName}`, - tagPrefix: `${pkgName}@`, - }) - return - } - - if (pkgName === 'plugin-react-swc') { - console.log(colors.cyan('\nUpdating package.json version...')) - const pkgJsonPath = `packages/${pkgName}/package.json` - const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8')) - pkg.version = version - writeFileSync(pkgJsonPath, `${JSON.stringify(pkg, null, 2)}\n`) - } - - const changelog = readFileSync(`packages/${pkgName}/CHANGELOG.md`, 'utf-8') - console.log(colors.cyan('\nUpdating CHANGELOG.md...')) - const date = new Date().toISOString().slice(0, 10) - writeFileSync( - `packages/${pkgName}/CHANGELOG.md`, - changelog.replace( - '## Unreleased', - `## Unreleased\n\n## ${version} (${date})`, - ), - ) - }, -}) From 0ff5d5e2bbbf3f1e27ae0b502d526efda9ce55dd Mon Sep 17 00:00:00 2001 From: sapphi-red Date: Thu, 30 Jul 2026 15:48:00 +0900 Subject: [PATCH 2/3] ci: show changelog in PR --- .github/workflows/prepare-release.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index 311412180..f86598e14 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -89,10 +89,18 @@ jobs: APP_SLUG: ${{ steps.generate-token.outputs.app-slug }} GH_TOKEN: ${{ steps.generate-token.outputs.token }} PACKAGE: ${{ inputs.package }} + PR_BODY_FILE: ${{ runner.temp }}/release-pr-body.md VERSION: ${{ steps.prepare.outputs.version }} run: | BRANCH="release-$PACKAGE-$VERSION-$GITHUB_RUN_ID" BOT_ID="$(gh api "/users/$APP_SLUG%5Bbot%5D" --jq .id)" + { + echo "$PACKAGE release PR generated by the Prepare Release workflow: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" + echo + echo "## Changelog" + echo + node scripts/extract-changelog.ts "packages/$PACKAGE/CHANGELOG.md" "$VERSION" + } > "$PR_BODY_FILE" git config user.name "$APP_SLUG[bot]" git config user.email "$BOT_ID+$APP_SLUG[bot]@users.noreply.github.com" git switch -c "$BRANCH" @@ -103,4 +111,4 @@ jobs: --base main \ --head "$BRANCH" \ --title "release: $PACKAGE@$VERSION" \ - --body "$PACKAGE release PR generated by the Prepare Release workflow: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" + --body-file "$PR_BODY_FILE" From 223ffddbf2fcbb6f53734f6b7064587ff17df1ae Mon Sep 17 00:00:00 2001 From: sapphi-red Date: Thu, 30 Jul 2026 15:50:27 +0900 Subject: [PATCH 3/3] ci: assign release PR to the one triggered the release CI --- .github/workflows/prepare-release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index f86598e14..5a99e2cbd 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -108,6 +108,7 @@ jobs: git commit -m "release: $PACKAGE@$VERSION" git push -u "https://x-access-token:$GH_TOKEN@github.com/$GITHUB_REPOSITORY.git" HEAD gh pr create \ + --assignee "$GITHUB_ACTOR" \ --base main \ --head "$BRANCH" \ --title "release: $PACKAGE@$VERSION" \