From 03e31b75696597edbd7898ee146a62435fd8742d Mon Sep 17 00:00:00 2001 From: Pranav Zinzurde Date: Mon, 1 Jun 2026 18:24:24 +0530 Subject: [PATCH 1/9] ci: automate release PR creation, draft release, and npm dist-tag - Add "Create Release PR" workflow_dispatch: bumps all package versions (lerna fixed mode) and opens a Release X.Y.Z PR. Inputs choose the bump type (pre* = beta channel, plain = latest) and preid. - Add Draft Release workflow: on merge of a "Release ..." PR, auto-create a draft GitHub Release with the v-tag, pre-release flag for betas, and generated notes. Publishing stays a manual human gate. - release.yml now derives the npm dist-tag (beta vs latest) from the version instead of defaulting everything to latest. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/draft-release.yml | 54 +++++++++++++++++ .github/workflows/release.yml | 17 +++++- .github/workflows/version-bump.yml | 92 +++++++++++++++++++++++++++++ 3 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/draft-release.yml create mode 100644 .github/workflows/version-bump.yml diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml new file mode 100644 index 000000000..9a3a51854 --- /dev/null +++ b/.github/workflows/draft-release.yml @@ -0,0 +1,54 @@ +name: Draft Release + +# When a "Release X.Y.Z" PR (opened by the Create Release PR workflow) is +# merged into master, this prepares a DRAFT GitHub Release with the right tag +# and auto-generated notes. It is intentionally left as a draft โ€” a human +# clicks "Publish" to actually ship, which triggers release.yml + executable.yml. +on: + pull_request: + types: [closed] + +permissions: + contents: write + +jobs: + draft: + # Only for merged PRs whose title starts with "Release " (our bump PRs). + if: >- + github.event.pull_request.merged == true && + startsWith(github.event.pull_request.title, 'Release ') + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + with: + ref: ${{ github.event.pull_request.merge_commit_sha }} + + - name: Create draft release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TARGET_SHA: ${{ github.event.pull_request.merge_commit_sha }} + run: | + set -euo pipefail + VERSION=$(node -p "require('./lerna.json').version") + TAG="v$VERSION" + + # Mark betas (versions containing a hyphen) as pre-releases. + PRERELEASE_FLAG="" + if [[ "$VERSION" == *-* ]]; then + PRERELEASE_FLAG="--prerelease" + fi + + # Don't recreate if a draft/release for this tag already exists. + if gh release view "$TAG" >/dev/null 2>&1; then + echo "Release $TAG already exists โ€” skipping." + exit 0 + fi + + gh release create "$TAG" \ + --draft \ + --generate-notes \ + --title "$TAG" \ + --target "$TARGET_SHA" \ + $PRERELEASE_FLAG + + echo "Created draft release $TAG. Review it and click Publish to ship." diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ae398fc1a..47ad45fdb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,8 +13,23 @@ jobs: registry-url: 'https://registry.npmjs.org' - run: yarn - run: yarn build + - name: Resolve npm dist-tag + id: tag + run: | + set -euo pipefail + VERSION=$(node -p "require('./lerna.json').version") + # Prerelease versions (e.g. 1.32.0-beta.2) publish under `beta`; + # clean semver publishes under `latest`. + if [[ "$VERSION" == *-* ]]; then + DIST_TAG=beta + else + DIST_TAG=latest + fi + echo "dist_tag=$DIST_TAG" >> "$GITHUB_OUTPUT" + echo "Publishing $VERSION under dist-tag: $DIST_TAG" - name: lerna publish - run: yarn lerna publish from-package --yes + run: yarn lerna publish from-package --dist-tag "$DIST_TAG" --yes env: + DIST_TAG: ${{ steps.tag.outputs.dist_tag }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml new file mode 100644 index 000000000..8566ed48f --- /dev/null +++ b/.github/workflows/version-bump.yml @@ -0,0 +1,92 @@ +name: Create Release PR + +# Manually triggered from the Actions tab. Bumps every package version in +# lockstep (lerna fixed mode) and opens a "Release X.Y.Z" PR. Merging that PR +# and publishing a GitHub Release is what actually ships to npm (see release.yml). +on: + workflow_dispatch: + inputs: + bump: + description: 'Version bump (pre* = beta channel, plain = latest channel)' + required: true + type: choice + default: prerelease + options: + - prerelease # 1.32.0-beta.2 -> 1.32.0-beta.3 + - prepatch # 1.32.0 -> 1.32.1-beta.0 + - preminor # 1.32.0 -> 1.33.0-beta.0 + - premajor # 1.32.0 -> 2.0.0-beta.0 + - patch # 1.32.0-beta.x -> 1.32.0 (graduate / stable) + - minor # 1.32.0 -> 1.33.0 + - major # 1.32.0 -> 2.0.0 + preid: + description: 'Prerelease identifier (only used for pre* bumps)' + required: false + type: string + default: beta + +permissions: + contents: write + pull-requests: write + +jobs: + release-pr: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - uses: actions/setup-node@v5 + with: + node-version: 24 + + - run: yarn install --frozen-lockfile + + - name: Configure git + run: | + git config user.name "percy-release-bot" + git config user.email "percy-release-bot@users.noreply.github.com" + + - name: Bump version + id: bump + env: + BUMP: ${{ inputs.bump }} + PREID: ${{ inputs.preid }} + run: | + set -euo pipefail + if [[ "$BUMP" == pre* ]]; then + yarn lerna version "$BUMP" --preid "$PREID" \ + --exact --no-git-tag-version --no-push --force-publish --yes + else + yarn lerna version "$BUMP" \ + --exact --no-git-tag-version --no-push --force-publish --yes + fi + + VERSION=$(node -p "require('./lerna.json').version") + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + + # A prerelease version (contains a hyphen) ships under the `beta` + # npm dist-tag; a clean semver ships under `latest`. + if [[ "$VERSION" == *-* ]]; then + echo "dist_tag=beta" >> "$GITHUB_OUTPUT" + else + echo "dist_tag=latest" >> "$GITHUB_OUTPUT" + fi + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v7 + with: + token: ${{ secrets.GITHUB_TOKEN }} + base: master + branch: release/${{ steps.bump.outputs.version }} + commit-message: "Release ${{ steps.bump.outputs.version }}" + title: "Release ${{ steps.bump.outputs.version }}" + labels: "๐Ÿงน maintenance" + body: | + Automated version bump to **`${{ steps.bump.outputs.version }}`**. + + - Triggered by @${{ github.actor }} via `workflow_dispatch` (bump: `${{ inputs.bump }}`) + - On publishing a GitHub Release, this will go to npm under dist-tag **`${{ steps.bump.outputs.dist_tag }}`**. + + **Next steps:** review & merge, then cut the GitHub Release. From 5991e0665ba49ff48bc433d9ebfd9bb61c385c73 Mon Sep 17 00:00:00 2001 From: Pranav Zinzurde Date: Mon, 1 Jun 2026 18:26:56 +0530 Subject: [PATCH 2/9] ci: drop release.yml dist-tag change Keep release.yml as-is; this PR only adds the Create Release PR button and the Draft Release automation. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/release.yml | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 47ad45fdb..ae398fc1a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,23 +13,8 @@ jobs: registry-url: 'https://registry.npmjs.org' - run: yarn - run: yarn build - - name: Resolve npm dist-tag - id: tag - run: | - set -euo pipefail - VERSION=$(node -p "require('./lerna.json').version") - # Prerelease versions (e.g. 1.32.0-beta.2) publish under `beta`; - # clean semver publishes under `latest`. - if [[ "$VERSION" == *-* ]]; then - DIST_TAG=beta - else - DIST_TAG=latest - fi - echo "dist_tag=$DIST_TAG" >> "$GITHUB_OUTPUT" - echo "Publishing $VERSION under dist-tag: $DIST_TAG" - name: lerna publish - run: yarn lerna publish from-package --dist-tag "$DIST_TAG" --yes + run: yarn lerna publish from-package --yes env: - DIST_TAG: ${{ steps.tag.outputs.dist_tag }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} From 2ae31f6830b85d79754028637fd9094bcfa0fe82 Mon Sep 17 00:00:00 2001 From: Pranav Zinzurde Date: Mon, 1 Jun 2026 19:14:39 +0530 Subject: [PATCH 3/9] ci: take exact target version as the Create Release PR input Replace the bump-type dropdown + preid with a single 'version' text input. You type the exact target (e.g. 1.32.0-beta.5); the run summary shows current -> new. Validates semver format before touching files. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/version-bump.yml | 60 +++++++++++++++--------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index 8566ed48f..e85a0237c 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -1,29 +1,16 @@ name: Create Release PR -# Manually triggered from the Actions tab. Bumps every package version in -# lockstep (lerna fixed mode) and opens a "Release X.Y.Z" PR. Merging that PR -# and publishing a GitHub Release is what actually ships to npm (see release.yml). +# Manually triggered from the Actions tab. You type the exact target version; +# this sets every package to it in lockstep (lerna fixed mode) and opens a +# "Release X.Y.Z" PR. Merging that PR + publishing a GitHub Release is what +# actually ships to npm. on: workflow_dispatch: inputs: - bump: - description: 'Version bump (pre* = beta channel, plain = latest channel)' + version: + description: 'Target version, e.g. 1.32.0-beta.5 (beta) or 1.32.0 (stable). Check the current version in lerna.json / on npm and type the next one.' required: true - type: choice - default: prerelease - options: - - prerelease # 1.32.0-beta.2 -> 1.32.0-beta.3 - - prepatch # 1.32.0 -> 1.32.1-beta.0 - - preminor # 1.32.0 -> 1.33.0-beta.0 - - premajor # 1.32.0 -> 2.0.0-beta.0 - - patch # 1.32.0-beta.x -> 1.32.0 (graduate / stable) - - minor # 1.32.0 -> 1.33.0 - - major # 1.32.0 -> 2.0.0 - preid: - description: 'Prerelease identifier (only used for pre* bumps)' - required: false type: string - default: beta permissions: contents: write @@ -51,28 +38,41 @@ jobs: - name: Bump version id: bump env: - BUMP: ${{ inputs.bump }} - PREID: ${{ inputs.preid }} + TARGET: ${{ inputs.version }} run: | set -euo pipefail - if [[ "$BUMP" == pre* ]]; then - yarn lerna version "$BUMP" --preid "$PREID" \ - --exact --no-git-tag-version --no-push --force-publish --yes - else - yarn lerna version "$BUMP" \ - --exact --no-git-tag-version --no-push --force-publish --yes + + # Validate the input is a plain semver (optionally with a -prerelease + # suffix). Rejects typos before we touch any files. + if ! [[ "$TARGET" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then + echo "::error::'$TARGET' is not a valid version (expected e.g. 1.32.0 or 1.32.0-beta.5)." + exit 1 fi + CURRENT=$(node -p "require('./lerna.json').version") + + # lerna accepts an explicit version as the positional bump argument. + yarn lerna version "$TARGET" \ + --exact --no-git-tag-version --no-push --force-publish --yes + VERSION=$(node -p "require('./lerna.json').version") echo "version=$VERSION" >> "$GITHUB_OUTPUT" # A prerelease version (contains a hyphen) ships under the `beta` # npm dist-tag; a clean semver ships under `latest`. if [[ "$VERSION" == *-* ]]; then - echo "dist_tag=beta" >> "$GITHUB_OUTPUT" + DIST_TAG=beta else - echo "dist_tag=latest" >> "$GITHUB_OUTPUT" + DIST_TAG=latest fi + echo "dist_tag=$DIST_TAG" >> "$GITHUB_OUTPUT" + + # Show current -> new in the run summary (visible after triggering). + { + echo "### Version bump" + echo "" + echo "\`$CURRENT\` โ†’ **\`$VERSION\`** (npm dist-tag: \`$DIST_TAG\`)" + } >> "$GITHUB_STEP_SUMMARY" - name: Create Pull Request uses: peter-evans/create-pull-request@v7 @@ -86,7 +86,7 @@ jobs: body: | Automated version bump to **`${{ steps.bump.outputs.version }}`**. - - Triggered by @${{ github.actor }} via `workflow_dispatch` (bump: `${{ inputs.bump }}`) + - Triggered by @${{ github.actor }} via `workflow_dispatch`. - On publishing a GitHub Release, this will go to npm under dist-tag **`${{ steps.bump.outputs.dist_tag }}`**. **Next steps:** review & merge, then cut the GitHub Release. From 3f27d84ce0a9713dd5997aac4d6e8ef776fb865b Mon Sep 17 00:00:00 2001 From: Pranav Zinzurde Date: Mon, 1 Jun 2026 19:35:32 +0530 Subject: [PATCH 4/9] ci: show current -> target in the Create Release PR dropdown GitHub can't compute versions in a workflow_dispatch form, so bake them in: add scripts/gen-release-options.mjs which rewrites the choice options as ": -> ". The workflow runs it after bumping and commits the refreshed dropdown into the Release PR, so master's form always reflects the latest version. The bump step parses the target from the choice. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/version-bump.yml | 40 ++++++++++++------ scripts/gen-release-options.mjs | 65 ++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 13 deletions(-) create mode 100644 scripts/gen-release-options.mjs diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index e85a0237c..e366c4629 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -1,16 +1,27 @@ name: Create Release PR -# Manually triggered from the Actions tab. You type the exact target version; -# this sets every package to it in lockstep (lerna fixed mode) and opens a -# "Release X.Y.Z" PR. Merging that PR + publishing a GitHub Release is what -# actually ships to npm. +# Manually triggered from the Actions tab. Pick a bump from the dropdown โ€” each +# entry shows `current -> target`. This sets every package to the target version +# in lockstep (lerna fixed mode), refreshes the dropdown for the *next* release, +# and opens a "Release X.Y.Z" PR. Merging that PR + publishing a GitHub Release +# is what actually ships to npm. +# +# The options below are auto-generated by scripts/gen-release-options.mjs โ€” do +# not hand-edit them; they refresh themselves on every release. on: workflow_dispatch: inputs: - version: - description: 'Target version, e.g. 1.32.0-beta.5 (beta) or 1.32.0 (stable). Check the current version in lerna.json / on npm and type the next one.' + bump: + description: 'Current version 1.32.0-beta.5 โ€” pick the bump (target version shown after ->):' # AUTOGEN:description required: true - type: string + type: choice + options: + # AUTOGEN:options-start + - "prerelease (next beta): 1.32.0-beta.5 -> 1.32.0-beta.6" + - "graduate to stable: 1.32.0-beta.5 -> 1.32.0" + - "preminor (next beta line): 1.32.0-beta.5 -> 1.33.0-beta.0" + - "premajor (next beta line): 1.32.0-beta.5 -> 2.0.0-beta.0" + # AUTOGEN:options-end permissions: contents: write @@ -38,26 +49,29 @@ jobs: - name: Bump version id: bump env: - TARGET: ${{ inputs.version }} + BUMP: ${{ inputs.bump }} run: | set -euo pipefail - # Validate the input is a plain semver (optionally with a -prerelease - # suffix). Rejects typos before we touch any files. + # The dropdown label is ": -> ". Extract the + # target version (everything after the last "-> "). + TARGET="${BUMP##*-> }" if ! [[ "$TARGET" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then - echo "::error::'$TARGET' is not a valid version (expected e.g. 1.32.0 or 1.32.0-beta.5)." + echo "::error::Could not parse a valid target version from '$BUMP'." exit 1 fi CURRENT=$(node -p "require('./lerna.json').version") - # lerna accepts an explicit version as the positional bump argument. yarn lerna version "$TARGET" \ --exact --no-git-tag-version --no-push --force-publish --yes VERSION=$(node -p "require('./lerna.json').version") echo "version=$VERSION" >> "$GITHUB_OUTPUT" + # Refresh the dropdown so the *next* release shows the right mappings. + node scripts/gen-release-options.mjs + # A prerelease version (contains a hyphen) ships under the `beta` # npm dist-tag; a clean semver ships under `latest`. if [[ "$VERSION" == *-* ]]; then @@ -67,7 +81,6 @@ jobs: fi echo "dist_tag=$DIST_TAG" >> "$GITHUB_OUTPUT" - # Show current -> new in the run summary (visible after triggering). { echo "### Version bump" echo "" @@ -88,5 +101,6 @@ jobs: - Triggered by @${{ github.actor }} via `workflow_dispatch`. - On publishing a GitHub Release, this will go to npm under dist-tag **`${{ steps.bump.outputs.dist_tag }}`**. + - Includes a refreshed `Create Release PR` dropdown for the next release. **Next steps:** review & merge, then cut the GitHub Release. diff --git a/scripts/gen-release-options.mjs b/scripts/gen-release-options.mjs new file mode 100644 index 000000000..c5977db0d --- /dev/null +++ b/scripts/gen-release-options.mjs @@ -0,0 +1,65 @@ +#!/usr/bin/env node +// Regenerates the choice options in .github/workflows/version-bump.yml so the +// "Run workflow" dropdown shows `current -> target` for each bump type. +// +// GitHub renders workflow_dispatch forms from static YAML and cannot compute +// versions on the fly, so we bake them in and keep them fresh: the Create +// Release PR workflow runs this after bumping, committing the refreshed +// dropdown into the same PR. When that PR merges, master's form is accurate +// for the next release. Run it manually any time the version changes +// out-of-band: `node scripts/gen-release-options.mjs`. +import fs from 'node:fs'; +import path from 'node:path'; +import semver from 'semver'; + +const root = process.cwd(); +const wfPath = path.join(root, '.github/workflows/version-bump.yml'); +const current = JSON.parse(fs.readFileSync(path.join(root, 'lerna.json'), 'utf8')).version; +const PREID = 'beta'; + +// Pick a sensible, deduped set of bumps depending on where we are. From a +// prerelease, `patch`/`minor` both just graduate to the same X.Y.Z, so we +// collapse them into a single "graduate" entry. +const specs = semver.prerelease(current) + ? [ + ['prerelease (next beta)', semver.inc(current, 'prerelease', PREID)], + ['graduate to stable', semver.inc(current, 'patch')], + ['preminor (next beta line)', semver.inc(current, 'preminor', PREID)], + ['premajor (next beta line)', semver.inc(current, 'premajor', PREID)] + ] + : [ + ['patch', semver.inc(current, 'patch')], + ['minor', semver.inc(current, 'minor')], + ['major', semver.inc(current, 'major')], + ['prepatch (beta)', semver.inc(current, 'prepatch', PREID)], + ['preminor (beta)', semver.inc(current, 'preminor', PREID)], + ['premajor (beta)', semver.inc(current, 'premajor', PREID)] + ]; + +const seen = new Set(); +const options = []; +for (const [label, target] of specs) { + if (seen.has(target)) continue; + seen.add(target); + // Format is load-bearing: the workflow parses the target after "-> ". + options.push(` - "${label}: ${current} -> ${target}"`); +} + +let wf = fs.readFileSync(wfPath, 'utf8'); + +const desc = `Current version ${current} โ€” pick the bump (target version shown after ->):`; +wf = wf.replace( + /^.*# AUTOGEN:description.*$/m, + ` description: '${desc}' # AUTOGEN:description` +); + +const start = ' # AUTOGEN:options-start'; +const end = ' # AUTOGEN:options-end'; +wf = wf.replace( + new RegExp(`${start}[\\s\\S]*?${end}`), + `${start}\n${options.join('\n')}\n${end}` +); + +fs.writeFileSync(wfPath, wf); +console.log(`Regenerated dropdown for current ${current}:`); +for (const o of options) console.log(' ' + o.trim()); From c4c4b3bc4ad5373c27cadfb47fc62221ce17eccc Mon Sep 17 00:00:00 2001 From: Pranav Zinzurde Date: Mon, 1 Jun 2026 19:48:54 +0530 Subject: [PATCH 5/9] ci: capitalize dropdown labels, rename graduate -> Release stable Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/version-bump.yml | 8 ++++---- scripts/gen-release-options.mjs | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index e366c4629..551e2d65e 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -17,10 +17,10 @@ on: type: choice options: # AUTOGEN:options-start - - "prerelease (next beta): 1.32.0-beta.5 -> 1.32.0-beta.6" - - "graduate to stable: 1.32.0-beta.5 -> 1.32.0" - - "preminor (next beta line): 1.32.0-beta.5 -> 1.33.0-beta.0" - - "premajor (next beta line): 1.32.0-beta.5 -> 2.0.0-beta.0" + - "Prerelease next beta: 1.32.0-beta.5 -> 1.32.0-beta.6" + - "Release stable: 1.32.0-beta.5 -> 1.32.0" + - "Preminor next beta line: 1.32.0-beta.5 -> 1.33.0-beta.0" + - "Premajor next beta line: 1.32.0-beta.5 -> 2.0.0-beta.0" # AUTOGEN:options-end permissions: diff --git a/scripts/gen-release-options.mjs b/scripts/gen-release-options.mjs index c5977db0d..b29b27c89 100644 --- a/scripts/gen-release-options.mjs +++ b/scripts/gen-release-options.mjs @@ -22,18 +22,18 @@ const PREID = 'beta'; // collapse them into a single "graduate" entry. const specs = semver.prerelease(current) ? [ - ['prerelease (next beta)', semver.inc(current, 'prerelease', PREID)], - ['graduate to stable', semver.inc(current, 'patch')], - ['preminor (next beta line)', semver.inc(current, 'preminor', PREID)], - ['premajor (next beta line)', semver.inc(current, 'premajor', PREID)] + ['Prerelease next beta', semver.inc(current, 'prerelease', PREID)], + ['Release stable', semver.inc(current, 'patch')], + ['Preminor next beta line', semver.inc(current, 'preminor', PREID)], + ['Premajor next beta line', semver.inc(current, 'premajor', PREID)] ] : [ - ['patch', semver.inc(current, 'patch')], - ['minor', semver.inc(current, 'minor')], - ['major', semver.inc(current, 'major')], - ['prepatch (beta)', semver.inc(current, 'prepatch', PREID)], - ['preminor (beta)', semver.inc(current, 'preminor', PREID)], - ['premajor (beta)', semver.inc(current, 'premajor', PREID)] + ['Patch release', semver.inc(current, 'patch')], + ['Minor release', semver.inc(current, 'minor')], + ['Major release', semver.inc(current, 'major')], + ['Prepatch beta', semver.inc(current, 'prepatch', PREID)], + ['Preminor beta', semver.inc(current, 'preminor', PREID)], + ['Premajor beta', semver.inc(current, 'premajor', PREID)] ]; const seen = new Set(); From 542572e39284fdeaabfeb5a1ed4324bd24fea5c9 Mon Sep 17 00:00:00 2001 From: Pranav Zinzurde Date: Tue, 2 Jun 2026 16:33:11 +0530 Subject: [PATCH 6/9] ci: compute release version dynamically at run time Drop the self-editing dropdown generator (scripts/gen-release-options.mjs). GitHub workflow_dispatch dropdowns are static and can't show computed values, and editing the workflow file in the release PR was both blocked for GITHUB_TOKEN and wrong for a release PR. Instead, the dropdown is a simple bump-type picker and the bump step computes the target from the current version at run time via semver. add-paths scopes the PR to version files only. Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/gen-release-options.mjs | 65 --------------------------------- 1 file changed, 65 deletions(-) delete mode 100644 scripts/gen-release-options.mjs diff --git a/scripts/gen-release-options.mjs b/scripts/gen-release-options.mjs deleted file mode 100644 index b29b27c89..000000000 --- a/scripts/gen-release-options.mjs +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env node -// Regenerates the choice options in .github/workflows/version-bump.yml so the -// "Run workflow" dropdown shows `current -> target` for each bump type. -// -// GitHub renders workflow_dispatch forms from static YAML and cannot compute -// versions on the fly, so we bake them in and keep them fresh: the Create -// Release PR workflow runs this after bumping, committing the refreshed -// dropdown into the same PR. When that PR merges, master's form is accurate -// for the next release. Run it manually any time the version changes -// out-of-band: `node scripts/gen-release-options.mjs`. -import fs from 'node:fs'; -import path from 'node:path'; -import semver from 'semver'; - -const root = process.cwd(); -const wfPath = path.join(root, '.github/workflows/version-bump.yml'); -const current = JSON.parse(fs.readFileSync(path.join(root, 'lerna.json'), 'utf8')).version; -const PREID = 'beta'; - -// Pick a sensible, deduped set of bumps depending on where we are. From a -// prerelease, `patch`/`minor` both just graduate to the same X.Y.Z, so we -// collapse them into a single "graduate" entry. -const specs = semver.prerelease(current) - ? [ - ['Prerelease next beta', semver.inc(current, 'prerelease', PREID)], - ['Release stable', semver.inc(current, 'patch')], - ['Preminor next beta line', semver.inc(current, 'preminor', PREID)], - ['Premajor next beta line', semver.inc(current, 'premajor', PREID)] - ] - : [ - ['Patch release', semver.inc(current, 'patch')], - ['Minor release', semver.inc(current, 'minor')], - ['Major release', semver.inc(current, 'major')], - ['Prepatch beta', semver.inc(current, 'prepatch', PREID)], - ['Preminor beta', semver.inc(current, 'preminor', PREID)], - ['Premajor beta', semver.inc(current, 'premajor', PREID)] - ]; - -const seen = new Set(); -const options = []; -for (const [label, target] of specs) { - if (seen.has(target)) continue; - seen.add(target); - // Format is load-bearing: the workflow parses the target after "-> ". - options.push(` - "${label}: ${current} -> ${target}"`); -} - -let wf = fs.readFileSync(wfPath, 'utf8'); - -const desc = `Current version ${current} โ€” pick the bump (target version shown after ->):`; -wf = wf.replace( - /^.*# AUTOGEN:description.*$/m, - ` description: '${desc}' # AUTOGEN:description` -); - -const start = ' # AUTOGEN:options-start'; -const end = ' # AUTOGEN:options-end'; -wf = wf.replace( - new RegExp(`${start}[\\s\\S]*?${end}`), - `${start}\n${options.join('\n')}\n${end}` -); - -fs.writeFileSync(wfPath, wf); -console.log(`Regenerated dropdown for current ${current}:`); -for (const o of options) console.log(' ' + o.trim()); From 036353516fa44e7853dd64020deb4b47e607948f Mon Sep 17 00:00:00 2001 From: Pranav Zinzurde Date: Tue, 2 Jun 2026 16:36:48 +0530 Subject: [PATCH 7/9] ci: replace baked dropdown with static bump-type picker (compute at run time) Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/version-bump.yml | 55 +++++++++++++++--------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index 551e2d65e..b12a32adb 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -1,27 +1,25 @@ name: Create Release PR -# Manually triggered from the Actions tab. Pick a bump from the dropdown โ€” each -# entry shows `current -> target`. This sets every package to the target version -# in lockstep (lerna fixed mode), refreshes the dropdown for the *next* release, -# and opens a "Release X.Y.Z" PR. Merging that PR + publishing a GitHub Release -# is what actually ships to npm. -# -# The options below are auto-generated by scripts/gen-release-options.mjs โ€” do -# not hand-edit them; they refresh themselves on every release. +# Manually triggered from the Actions tab. Pick how to bump; the target version +# is computed from the CURRENT version at run time (nothing is hard-coded). This +# sets every package in lockstep (lerna fixed mode) and opens a "Release X.Y.Z" +# PR that only touches lerna.json + package.json files. Merging that PR and +# publishing a GitHub Release is what actually ships to npm. on: workflow_dispatch: inputs: bump: - description: 'Current version 1.32.0-beta.5 โ€” pick the bump (target version shown after ->):' # AUTOGEN:description + description: 'How to bump (the resulting version is shown in the run summary):' required: true + default: prerelease type: choice options: - # AUTOGEN:options-start - - "Prerelease next beta: 1.32.0-beta.5 -> 1.32.0-beta.6" - - "Release stable: 1.32.0-beta.5 -> 1.32.0" - - "Preminor next beta line: 1.32.0-beta.5 -> 1.33.0-beta.0" - - "Premajor next beta line: 1.32.0-beta.5 -> 2.0.0-beta.0" - # AUTOGEN:options-end + - prerelease # next beta: 1.32.0-beta.5 -> 1.32.0-beta.6 + - patch # graduate / stable patch: 1.32.0-beta.5 -> 1.32.0, or 1.32.0 -> 1.32.1 + - minor # 1.32.0 -> 1.33.0 + - major # 1.32.0 -> 2.0.0 + - preminor # new beta line: 1.32.0 -> 1.33.0-beta.0 + - premajor # new beta line: 1.32.0 -> 2.0.0-beta.0 permissions: contents: write @@ -53,25 +51,22 @@ jobs: run: | set -euo pipefail - # The dropdown label is ": -> ". Extract the - # target version (everything after the last "-> "). - TARGET="${BUMP##*-> }" - if ! [[ "$TARGET" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then - echo "::error::Could not parse a valid target version from '$BUMP'." + CURRENT=$(node -p "require('./lerna.json').version") + + # Compute the target version dynamically from the current version. + # 'beta' is the prerelease identifier for any pre* bump. + TARGET=$(node -e "const s=require('semver'); const t=s.inc('$CURRENT', process.env.BUMP, 'beta'); if(!t){process.exit(1)} process.stdout.write(t)") + if [[ -z "$TARGET" ]]; then + echo "::error::Could not compute a target version for bump '$BUMP' from '$CURRENT'." exit 1 fi - CURRENT=$(node -p "require('./lerna.json').version") - yarn lerna version "$TARGET" \ --exact --no-git-tag-version --no-push --force-publish --yes VERSION=$(node -p "require('./lerna.json').version") echo "version=$VERSION" >> "$GITHUB_OUTPUT" - # Refresh the dropdown so the *next* release shows the right mappings. - node scripts/gen-release-options.mjs - # A prerelease version (contains a hyphen) ships under the `beta` # npm dist-tag; a clean semver ships under `latest`. if [[ "$VERSION" == *-* ]]; then @@ -84,6 +79,8 @@ jobs: { echo "### Version bump" echo "" + echo "Bump: \`$BUMP\`" + echo "" echo "\`$CURRENT\` โ†’ **\`$VERSION\`** (npm dist-tag: \`$DIST_TAG\`)" } >> "$GITHUB_STEP_SUMMARY" @@ -93,14 +90,18 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} base: master branch: release/${{ steps.bump.outputs.version }} + # Only commit version files โ€” never workflow files (GITHUB_TOKEN may + # not push to .github/workflows, and a release PR shouldn't anyway). + add-paths: | + lerna.json + packages/**/package.json commit-message: "Release ${{ steps.bump.outputs.version }}" title: "Release ${{ steps.bump.outputs.version }}" labels: "๐Ÿงน maintenance" body: | Automated version bump to **`${{ steps.bump.outputs.version }}`**. - - Triggered by @${{ github.actor }} via `workflow_dispatch`. + - Triggered by @${{ github.actor }} via `workflow_dispatch` (bump: `${{ inputs.bump }}`). - On publishing a GitHub Release, this will go to npm under dist-tag **`${{ steps.bump.outputs.dist_tag }}`**. - - Includes a refreshed `Create Release PR` dropdown for the next release. **Next steps:** review & merge, then cut the GitHub Release. From 87da25abc090020c924e8fbf47c3f104609509e2 Mon Sep 17 00:00:00 2001 From: Pranav Zinzurde Date: Tue, 2 Jun 2026 16:54:45 +0530 Subject: [PATCH 8/9] ci: sync publishConfig.tag with the version on bump A release sets each package's publishConfig.tag (the npm dist-tag used by `lerna publish from-package`) to `beta` for prereleases and `latest` for stable. Fold it into `yarn bump-version` (chained after lerna) so it applies locally, and mirror it in the Create Release PR workflow after its lerna call. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/version-bump.yml | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index b12a32adb..1c205dad5 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -76,6 +76,10 @@ jobs: fi echo "dist_tag=$DIST_TAG" >> "$GITHUB_OUTPUT" + # Keep each package's publishConfig.tag (the npm dist-tag used by + # `lerna publish from-package`) in step with the version. + DIST_TAG="$DIST_TAG" node -e 'const fs=require("fs");const t=process.env.DIST_TAG;for(const d of fs.readdirSync("packages")){const f="packages/"+d+"/package.json";if(!fs.existsSync(f))continue;const p=JSON.parse(fs.readFileSync(f));if(p.publishConfig&&p.publishConfig.tag!==t){p.publishConfig.tag=t;fs.writeFileSync(f,JSON.stringify(p,null,2)+"\n")}}' + { echo "### Version bump" echo "" diff --git a/package.json b/package.json index b82e83328..a9a14942f 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "build": "lerna run build --stream", "build:watch": "lerna run build --parallel -- --watch", "build:pack": "mkdir -p ./packs && lerna exec npm pack && mv ./packages/*/*.tgz ./packs", - "bump-version": "lerna version --exact --no-git-tag-version --no-push", + "bump-version": "lerna version --exact --no-git-tag-version --no-push && node -e \"const fs=require('fs');const v=require('./lerna.json').version;const t=v.includes('-')?'beta':'latest';for(const d of fs.readdirSync('packages')){const f='packages/'+d+'/package.json';if(!fs.existsSync(f))continue;const p=JSON.parse(fs.readFileSync(f));if(p.publishConfig&&p.publishConfig.tag!==t){p.publishConfig.tag=t;fs.writeFileSync(f,JSON.stringify(p,null,2)+String.fromCharCode(10))}}\"", "chromium-revision": "./scripts/chromium-revision.js", "clean": "git clean -Xdf -e !node_modules -e !**/node_modules/**", "lint": "eslint --ignore-path .gitignore .", From 28af78e4b2ee4041a5a1c49b6e1f884b706e05cc Mon Sep 17 00:00:00 2001 From: Pranav Zinzurde Date: Tue, 2 Jun 2026 19:00:44 +0530 Subject: [PATCH 9/9] ci: add illustrative examples to each bump option GitHub can't render computed values in workflow_dispatch dropdowns, so show a fixed generic example per option (e.g. 1.2.3 -> 1.3.0). The bump step parses the leading keyword; the actual resulting version still shows in the summary. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/version-bump.yml | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index 1c205dad5..d91179baa 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -9,17 +9,17 @@ on: workflow_dispatch: inputs: bump: - description: 'How to bump (the resulting version is shown in the run summary):' + description: 'How to bump (examples are illustrative; the actual resulting version is shown in the run summary):' required: true - default: prerelease + default: "prerelease (e.g. 1.2.3-beta.4 -> 1.2.3-beta.5)" type: choice options: - - prerelease # next beta: 1.32.0-beta.5 -> 1.32.0-beta.6 - - patch # graduate / stable patch: 1.32.0-beta.5 -> 1.32.0, or 1.32.0 -> 1.32.1 - - minor # 1.32.0 -> 1.33.0 - - major # 1.32.0 -> 2.0.0 - - preminor # new beta line: 1.32.0 -> 1.33.0-beta.0 - - premajor # new beta line: 1.32.0 -> 2.0.0-beta.0 + - "prerelease (e.g. 1.2.3-beta.4 -> 1.2.3-beta.5)" + - "patch (e.g. 1.2.3-beta.4 -> 1.2.3, or 1.2.3 -> 1.2.4)" + - "minor (e.g. 1.2.3 -> 1.3.0)" + - "major (e.g. 1.2.3 -> 2.0.0)" + - "preminor (e.g. 1.2.3 -> 1.3.0-beta.0)" + - "premajor (e.g. 1.2.3 -> 2.0.0-beta.0)" permissions: contents: write @@ -51,13 +51,16 @@ jobs: run: | set -euo pipefail + # The dropdown value is " (e.g. ...)"; take the first word. + KEYWORD="${BUMP%% *}" + CURRENT=$(node -p "require('./lerna.json').version") # Compute the target version dynamically from the current version. # 'beta' is the prerelease identifier for any pre* bump. - TARGET=$(node -e "const s=require('semver'); const t=s.inc('$CURRENT', process.env.BUMP, 'beta'); if(!t){process.exit(1)} process.stdout.write(t)") + TARGET=$(KEYWORD="$KEYWORD" node -e "const s=require('semver'); const t=s.inc('$CURRENT', process.env.KEYWORD, 'beta'); if(!t){process.exit(1)} process.stdout.write(t)") if [[ -z "$TARGET" ]]; then - echo "::error::Could not compute a target version for bump '$BUMP' from '$CURRENT'." + echo "::error::Could not compute a target version for bump '$KEYWORD' from '$CURRENT'." exit 1 fi