From d4cb610c3251a389059b45360c2eb7a1f8562c32 Mon Sep 17 00:00:00 2001 From: mkorwel Date: Wed, 17 Sep 2025 15:00:35 -0700 Subject: [PATCH 01/13] permissiong for nightly job --- .github/workflows/nightly-release.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/nightly-release.yml b/.github/workflows/nightly-release.yml index f0ca3716712..5b1dc65efef 100644 --- a/.github/workflows/nightly-release.yml +++ b/.github/workflows/nightly-release.yml @@ -25,6 +25,8 @@ jobs: release: runs-on: 'ubuntu-latest' permissions: + contents: 'write' + packages: 'write' issues: 'write' steps: - name: 'Checkout' From 2722473a928e1b6ac376c26a7529a4915d250784 Mon Sep 17 00:00:00 2001 From: mkorwel Date: Wed, 17 Sep 2025 15:20:13 -0700 Subject: [PATCH 02/13] permission --- .../actions/create-pull-request/action.yml | 54 +++++++++++++++++++ .github/workflows/create-patch-pr.yml | 13 ++++- .github/workflows/nightly-release.yml | 11 ++++ .github/workflows/promote-release.yml | 23 ++++---- 4 files changed, 85 insertions(+), 16 deletions(-) create mode 100644 .github/actions/create-pull-request/action.yml diff --git a/.github/actions/create-pull-request/action.yml b/.github/actions/create-pull-request/action.yml new file mode 100644 index 00000000000..815d67682aa --- /dev/null +++ b/.github/actions/create-pull-request/action.yml @@ -0,0 +1,54 @@ +name: 'Create and Merge Pull Request' +description: 'Creates a pull request and merges it automatically.' + +inputs: + branch-name: + description: 'The name of the branch to create the PR from.' + required: true + pr-title: + description: 'The title of the pull request.' + required: true + pr-body: + description: 'The body of the pull request.' + required: true + base-branch: + description: 'The branch to merge into.' + required: true + default: 'main' + app-id: + description: 'The ID of the GitHub App.' + required: true + private-key: + description: 'The private key of the GitHub App.' + required: true + dry-run: + description: 'Whether to run in dry-run mode.' + required: false + default: 'false' + +runs: + using: 'composite' + steps: + - name: 'Generate GitHub App Token' + id: 'generate_token' + if: "inputs.dry-run == 'false'" + uses: 'actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b' + with: + app-id: '${{ inputs.app-id }}' + private-key: '${{ inputs.private-key }}' + permission-pull-requests: 'write' + permission-contents: 'write' + + - name: 'Create and Approve Pull Request' + if: "inputs.dry-run == 'false'" + env: + GH_TOKEN: '${{ steps.generate_token.outputs.token }}' + shell: 'bash' + run: | + gh pr create \ + --title "${{ inputs.pr-title }}" \ + --body "${{ inputs.pr-body }}" \ + --base "${{ inputs.base-branch }}" \ + --head "${{ inputs.branch-name }}" \ + --fill + gh pr merge --auto --squash diff --git a/.github/workflows/create-patch-pr.yml b/.github/workflows/create-patch-pr.yml index 2ec6aed3eb1..d8ba42cec41 100644 --- a/.github/workflows/create-patch-pr.yml +++ b/.github/workflows/create-patch-pr.yml @@ -46,13 +46,22 @@ jobs: git config user.name "gemini-cli-robot" git config user.email "gemini-cli-robot@google.com" + - name: 'Generate GitHub App Token' + id: 'generate_token' + uses: 'actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b' + with: + app-id: '${{ secrets.APP_ID }}' + private-key: '${{ secrets.PRIVATE_KEY }}' + permission-pull-requests: 'write' + permission-contents: 'write' + - name: 'Create Patch for Stable' if: "github.event.inputs.channel == 'stable'" env: - GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}' + GH_TOKEN: '${{ steps.generate_token.outputs.token }}' run: 'node scripts/create-patch-pr.js --commit=${{ github.event.inputs.commit }} --channel=stable --dry-run=${{ github.event.inputs.dry_run }}' - name: 'Create Patch for Preview' env: - GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}' + GH_TOKEN: '${{ steps.generate_token.outputs.token }}' run: 'node scripts/create-patch-pr.js --commit=${{ github.event.inputs.commit }} --channel=${{ github.event.inputs.channel }} --dry-run=${{ github.event.inputs.dry_run }}' diff --git a/.github/workflows/nightly-release.yml b/.github/workflows/nightly-release.yml index 5b1dc65efef..494a1cc6215 100644 --- a/.github/workflows/nightly-release.yml +++ b/.github/workflows/nightly-release.yml @@ -28,6 +28,7 @@ jobs: contents: 'write' packages: 'write' issues: 'write' + pull-requests: 'write' steps: - name: 'Checkout' uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' @@ -73,6 +74,16 @@ jobs: dry-run: '${{ github.event.inputs.dry_run }}' previous-tag: '${{ steps.nightly_version.outputs.PREVIOUS_TAG }}' + - name: 'Create and Merge Pull Request' + uses: './.github/actions/create-pull-request' + with: + branch-name: 'release/${{ steps.nightly_version.outputs.RELEASE_TAG }}' + pr-title: 'chore(release): bump version to ${{ steps.nightly_version.outputs.RELEASE_VERSION }}' + pr-body: 'Automated version bump for nightly release.' + app-id: '${{ secrets.APP_ID }}' + private-key: '${{ secrets.PRIVATE_KEY }}' + dry-run: '${{ github.event.inputs.dry_run }}' + - name: 'Create Issue on Failure' if: '${{ failure() && github.event.inputs.dry_run == false }}' env: diff --git a/.github/workflows/promote-release.yml b/.github/workflows/promote-release.yml index e778bc047fa..1911d3dc6a3 100644 --- a/.github/workflows/promote-release.yml +++ b/.github/workflows/promote-release.yml @@ -317,20 +317,15 @@ jobs: echo "Dry run enabled. Skipping push." fi - - name: 'Create and Approve Pull Request' - if: |- - ${{ github.event.inputs.dry_run == 'false' }} - env: - GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}' - BRANCH_NAME: '${{ steps.release_branch.outputs.BRANCH_NAME }}' - run: | - gh pr create \ - --title "chore(release): bump version to ${{ needs.calculate-versions.outputs.NEXT_NIGHTLY_VERSION }}" \ - --body "Automated version bump to prepare for the next nightly release." \ - --base "main" \ - --head "${BRANCH_NAME}" \ - --fill - gh pr merge --auto --squash + - name: 'Create and Merge Pull Request' + uses: './.github/actions/create-pull-request' + with: + branch-name: '${{ steps.release_branch.outputs.BRANCH_NAME }}' + pr-title: 'chore(release): bump version to ${{ needs.calculate-versions.outputs.NEXT_NIGHTLY_VERSION }}' + pr-body: 'Automated version bump to prepare for the next nightly release.' + app-id: '${{ secrets.APP_ID }}' + private-key: '${{ secrets.PRIVATE_KEY }}' + dry-run: '${{ github.event.inputs.dry_run }}' - name: 'Create Issue on Failure' if: '${{ failure() && github.event.inputs.dry_run == false }}' From 21f795281fd11c20e260cc43a1ecdabe02a120b9 Mon Sep 17 00:00:00 2001 From: mkorwel Date: Wed, 17 Sep 2025 18:09:56 -0700 Subject: [PATCH 03/13] add ref to patching --- .github/workflows/create-patch-pr.yml | 6 ++++++ scripts/create-patch-pr.js | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/create-patch-pr.yml b/.github/workflows/create-patch-pr.yml index d8ba42cec41..aecb8e01fa5 100644 --- a/.github/workflows/create-patch-pr.yml +++ b/.github/workflows/create-patch-pr.yml @@ -19,6 +19,11 @@ on: required: false type: 'boolean' default: false + ref: + description: 'The branch, tag, or SHA to test from.' + required: false + type: 'string' + default: 'main' jobs: create-patch: @@ -30,6 +35,7 @@ jobs: - name: 'Checkout' uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5 with: + ref: '${{ github.event.inputs.ref }}' fetch-depth: 0 - name: 'Setup Node.js' diff --git a/scripts/create-patch-pr.js b/scripts/create-patch-pr.js index 7804c2a9f0a..97283f6eb6d 100644 --- a/scripts/create-patch-pr.js +++ b/scripts/create-patch-pr.js @@ -116,8 +116,8 @@ function getLatestTag(channel) { console.log(`Fetching latest tag for channel: ${channel}...`); const pattern = channel === 'stable' - ? '\'(contains("nightly") or contains("preview")) | not\'' - : '\'(contains("preview"))\''; + ? '(contains("nightly") or contains("preview")) | not' + : '(contains("preview"))'; const command = `gh release list --limit 30 --json tagName | jq -r '[.[] | select(.tagName | ${pattern})] | .[0].tagName'`; try { return execSync(command).toString().trim(); From b81f2426028d6ddeefde26e2bff95a0cea268d54 Mon Sep 17 00:00:00 2001 From: mkorwel Date: Wed, 17 Sep 2025 18:20:40 -0700 Subject: [PATCH 04/13] logging --- scripts/create-patch-pr.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/scripts/create-patch-pr.js b/scripts/create-patch-pr.js index 97283f6eb6d..7b6ff570435 100644 --- a/scripts/create-patch-pr.js +++ b/scripts/create-patch-pr.js @@ -82,12 +82,18 @@ async function main() { if (dryRun) { prBody += '\n\n**[DRY RUN]**'; } - run( - `gh pr create --base ${releaseBranch} --head ${hotfixBranch} --title "${prTitle}" --body "${prBody}"`, - dryRun, - ); + const prCommand = `gh pr create --base ${releaseBranch} --head ${hotfixBranch} --title "${prTitle}" --body "${prBody}"`; + run(prCommand, dryRun); console.log('Patch process completed successfully!'); + + if (dryRun) { + console.log('\n--- Dry Run Summary ---'); + console.log(`Release Branch: ${releaseBranch}`); + console.log(`Hotfix Branch: ${hotfixBranch}`); + console.log(`Pull Request Command: ${prCommand}`); + console.log('---------------------'); + } } function run(command, dryRun = false) { From a5a6863176c60a262f9fb419e78e8df030598c1e Mon Sep 17 00:00:00 2001 From: mkorwel Date: Wed, 17 Sep 2025 19:05:42 -0700 Subject: [PATCH 05/13] fix(workflows): add permissions to trigger patch release The `trigger-patch-release` workflow was failing with a `HttpError: Resource not accessible by integration` error. This was because the workflow did not have the necessary permissions to trigger another workflow. This change adds the `actions: write` permission to the `trigger-patch-release` job, which allows it to create a workflow dispatch event. --- .github/workflows/trigger-patch-release.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/trigger-patch-release.yml b/.github/workflows/trigger-patch-release.yml index 4270111bbd9..5684330a873 100644 --- a/.github/workflows/trigger-patch-release.yml +++ b/.github/workflows/trigger-patch-release.yml @@ -9,6 +9,8 @@ jobs: trigger-patch-release: if: "github.event.pull_request.merged == true && startsWith(github.head_ref, 'hotfix/')" runs-on: 'ubuntu-latest' + permissions: + actions: 'write' steps: - name: 'Trigger Patch Release' uses: 'actions/github-script@00f12e3e20659f42342b1c0226afda7f7c042325' From 6b8bc640e41321326a1b518aa0c1d18630239b81 Mon Sep 17 00:00:00 2001 From: mkorwel Date: Wed, 17 Sep 2025 19:10:31 -0700 Subject: [PATCH 06/13] feat(workflows): allow manual trigger for patch release --- .github/workflows/trigger-patch-release.yml | 44 ++++++++++++++++++--- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/.github/workflows/trigger-patch-release.yml b/.github/workflows/trigger-patch-release.yml index 5684330a873..347896679b2 100644 --- a/.github/workflows/trigger-patch-release.yml +++ b/.github/workflows/trigger-patch-release.yml @@ -4,10 +4,26 @@ on: pull_request: types: - 'closed' + workflow_dispatch: + inputs: + ref: + description: 'The head ref of the merged hotfix PR to trigger the release for (e.g. hotfix/v1.2.3/cherry-pick-abc).' + required: true + type: 'string' + workflow_id: + description: 'The workflow to trigger. Defaults to patch-release.yml' + required: false + type: 'string' + default: 'patch-release.yml' + dry_run: + description: 'Whether this is a dry run.' + required: false + type: 'boolean' + default: false jobs: trigger-patch-release: - if: "github.event.pull_request.merged == true && startsWith(github.head_ref, 'hotfix/')" + if: "(github.event_name == 'pull_request' && github.event.pull_request.merged == true && startsWith(github.head_ref, 'hotfix/')) || github.event_name == 'workflow_dispatch'" runs-on: 'ubuntu-latest' permissions: actions: 'write' @@ -16,17 +32,33 @@ jobs: uses: 'actions/github-script@00f12e3e20659f42342b1c0226afda7f7c042325' with: script: | - const body = context.payload.pull_request.body; + let body = ''; + let headRef = ''; + + if (context.eventName === 'pull_request') { + body = context.payload.pull_request.body; + headRef = context.payload.pull_request.head.ref; + } else { // workflow_dispatch + body = '${{ github.event.inputs.dry_run }}' ? '[DRY RUN]' : ''; + headRef = '${{ github.event.inputs.ref }}'; + } + const isDryRun = body.includes('[DRY RUN]'); - const ref = context.payload.pull_request.base.ref; - const channel = ref.includes('preview') ? 'preview' : 'stable'; + const version = headRef.split('/')[1]; + const channel = version.includes('preview') ? 'preview' : 'stable'; + const ref = `release/${version}`; + const workflow_id = context.eventName === 'pull_request' + ? 'patch-release.yml' + : '${{ github.event.inputs.workflow_id }}'; + github.rest.actions.createWorkflowDispatch({ owner: context.repo.owner, repo: context.repo.repo, - workflow_id: 'patch-release.yml', + workflow_id: workflow_id, ref: ref, inputs: { type: channel, - dry_run: isDryRun.toString() + dry_run: isDryRun.toString(), + version: version } }) From 8fe4b133aa776c9c90ecf7dc7eefcaf459524c43 Mon Sep 17 00:00:00 2001 From: mkorwel Date: Wed, 17 Sep 2025 19:16:55 -0700 Subject: [PATCH 07/13] fix(workflows): add version input to patch release workflow --- .github/workflows/patch-release.yml | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/.github/workflows/patch-release.yml b/.github/workflows/patch-release.yml index 9a05bfad76f..eee5a745bee 100644 --- a/.github/workflows/patch-release.yml +++ b/.github/workflows/patch-release.yml @@ -4,22 +4,21 @@ on: workflow_dispatch: inputs: type: - description: 'The type of release to patch from.' + description: 'The type of release to perform.' required: true type: 'choice' options: - 'stable' - 'preview' - ref: - description: 'The branch or ref (full git sha) to release from.' - required: true - type: 'string' - default: 'main' dry_run: description: 'Run a dry-run of the release process; no branches, npm packages or GitHub releases will be created.' required: true type: 'boolean' default: true + version: + description: 'The version to release.' + required: true + type: 'string' force_skip_tests: description: 'Select to skip the "Run Tests" step in testing. Prod releases should run tests' required: false @@ -59,17 +58,12 @@ jobs: run: |- npm ci - - name: 'Get the version' - id: 'version' - env: - GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' - run: |- - VERSION_JSON="$(node scripts/get-release-version.js --type=patch --patch-from=${{ github.event.inputs.type }})" - echo "${VERSION_JSON}" - echo "RELEASE_TAG=$(echo "${VERSION_JSON}" | jq -r .releaseTag)" >> "${GITHUB_OUTPUT}" - echo "RELEASE_VERSION=$(echo "${VERSION_JSON}" | jq -r .releaseVersion)" >> "${GITHUB_OUTPUT}" - echo "NPM_TAG=$(echo "${VERSION_JSON}" | jq -r .npmTag)" >> "${GITHUB_OUTPUT}" - echo "PREVIOUS_TAG=$(echo "${VERSION_JSON}" | jq -r .previousReleaseTag)" >> "${GITHUB_OUTPUT}" + - name: 'Get Patch Version' + id: 'patch_version' + run: | + echo "RELEASE_TAG=${{ github.event.inputs.version }}" >> "${GITHUB_OUTPUT}" + echo "RELEASE_VERSION=${{ github.event.inputs.version }}" >> "${GITHUB_OUTPUT}" + echo "NPM_TAG=${{ github.event.inputs.type }}" >> "${GITHUB_OUTPUT}" - name: 'Print Calculated Version' run: |- From 16ff2914f3ca07860e308d64028e32a7029aaaf7 Mon Sep 17 00:00:00 2001 From: mkorwel Date: Wed, 17 Sep 2025 19:18:19 -0700 Subject: [PATCH 08/13] fix(workflows): correct dry_run logic in trigger-patch-release --- .github/workflows/trigger-patch-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/trigger-patch-release.yml b/.github/workflows/trigger-patch-release.yml index 347896679b2..187a5c7e6a6 100644 --- a/.github/workflows/trigger-patch-release.yml +++ b/.github/workflows/trigger-patch-release.yml @@ -39,7 +39,7 @@ jobs: body = context.payload.pull_request.body; headRef = context.payload.pull_request.head.ref; } else { // workflow_dispatch - body = '${{ github.event.inputs.dry_run }}' ? '[DRY RUN]' : ''; + body = ${{ github.event.inputs.dry_run }} ? '[DRY RUN]' : ''; headRef = '${{ github.event.inputs.ref }}'; } From 9ce96fa91302f91740de3776fe3fbb8a56bfa229 Mon Sep 17 00:00:00 2001 From: mkorwel Date: Wed, 17 Sep 2025 19:20:46 -0700 Subject: [PATCH 09/13] fix(workflows): dispatch patch release from current branch --- .github/workflows/patch-release.yml | 24 ++++++--------------- .github/workflows/trigger-patch-release.yml | 5 +++-- 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/.github/workflows/patch-release.yml b/.github/workflows/patch-release.yml index eee5a745bee..86ab4fb8306 100644 --- a/.github/workflows/patch-release.yml +++ b/.github/workflows/patch-release.yml @@ -19,33 +19,23 @@ on: description: 'The version to release.' required: true type: 'string' - force_skip_tests: - description: 'Select to skip the "Run Tests" step in testing. Prod releases should run tests' - required: false - type: 'boolean' - default: false + release_ref: + description: 'The branch, tag, or SHA to release from.' + required: true + type: 'string' jobs: release: runs-on: 'ubuntu-latest' - environment: - name: 'production-release' - url: '${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ steps.version.outputs.RELEASE_TAG }}' - if: |- - ${{ github.repository == 'google-gemini/gemini-cli' }} permissions: contents: 'write' packages: 'write' - id-token: 'write' - issues: 'write' # For creating issues on failure - outputs: - RELEASE_TAG: '${{ steps.version.outputs.RELEASE_TAG }}' - + issues: 'write' steps: - name: 'Checkout' - uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5 + uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' with: - ref: '${{ github.event.inputs.ref || github.sha }}' + ref: '${{ github.event.inputs.release_ref }}' fetch-depth: 0 - name: 'Setup Node.js' diff --git a/.github/workflows/trigger-patch-release.yml b/.github/workflows/trigger-patch-release.yml index 187a5c7e6a6..7692c87ad4d 100644 --- a/.github/workflows/trigger-patch-release.yml +++ b/.github/workflows/trigger-patch-release.yml @@ -55,10 +55,11 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, workflow_id: workflow_id, - ref: ref, + ref: 'mk-patch-releases', inputs: { type: channel, dry_run: isDryRun.toString(), - version: version + version: version, + release_ref: ref } }) From b3a5f59918585377abcd4bb36706fa4745458408 Mon Sep 17 00:00:00 2001 From: mkorwel Date: Wed, 17 Sep 2025 19:25:23 -0700 Subject: [PATCH 10/13] feat(workflows): create issue on patch release failure --- .github/workflows/patch-release.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/patch-release.yml b/.github/workflows/patch-release.yml index 86ab4fb8306..0d8c99d8aec 100644 --- a/.github/workflows/patch-release.yml +++ b/.github/workflows/patch-release.yml @@ -76,3 +76,15 @@ jobs: github-token: '${{ secrets.GITHUB_TOKEN }}' dry-run: '${{ github.event.inputs.dry_run }}' previous-tag: '${{ steps.version.outputs.PREVIOUS_TAG }}' + + - name: 'Create Issue on Failure' + if: '${{ failure() && github.event.inputs.dry_run == false }}' + env: + GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' + RELEASE_TAG: '${{ steps.patch_version.outputs.RELEASE_TAG }}' + DETAILS_URL: '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}' + run: | + gh issue create \ + --title 'Patch Release Failed for ${RELEASE_TAG} on $(date +'%Y-%m-%d')' \ + --body 'The patch-release workflow failed. See the full run for details: ${DETAILS_URL}' \ + --label 'kind/bug,release-failure,priority/p0' From dec519794e062ef727babadef8b837bedad99dad Mon Sep 17 00:00:00 2001 From: mkorwel Date: Wed, 17 Sep 2025 19:51:02 -0700 Subject: [PATCH 11/13] refactor(workflows): standardize release workflow naming --- .../workflows/{release.yml => release-manual.yml} | 2 +- .../{nightly-release.yml => release-nightly.yml} | 2 +- ...e-patch-pr.yml => release-patch-1-create-pr.yml} | 2 +- ...atch-release.yml => release-patch-2-trigger.yml} | 6 +++--- ...atch-release.yml => release-patch-3-release.yml} | 13 ++++++------- ...m-comment.yml => release-patch-from-comment.yml} | 4 ++-- .../{promote-release.yml => release-promote.yml} | 2 +- docs/issue-and-pr-automation.md | 2 +- docs/releases.md | 2 +- 9 files changed, 17 insertions(+), 18 deletions(-) rename .github/workflows/{release.yml => release-manual.yml} (99%) rename .github/workflows/{nightly-release.yml => release-nightly.yml} (99%) rename .github/workflows/{create-patch-pr.yml => release-patch-1-create-pr.yml} (98%) rename .github/workflows/{trigger-patch-release.yml => release-patch-2-trigger.yml} (94%) rename .github/workflows/{patch-release.yml => release-patch-3-release.yml} (86%) rename .github/workflows/{patch-from-comment.yml => release-patch-from-comment.yml} (95%) rename .github/workflows/{promote-release.yml => release-promote.yml} (99%) diff --git a/.github/workflows/release.yml b/.github/workflows/release-manual.yml similarity index 99% rename from .github/workflows/release.yml rename to .github/workflows/release-manual.yml index 4b29a925aee..5e42bfd0a9a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release-manual.yml @@ -1,4 +1,4 @@ -name: 'Release' +name: 'Release: Manual' on: workflow_dispatch: diff --git a/.github/workflows/nightly-release.yml b/.github/workflows/release-nightly.yml similarity index 99% rename from .github/workflows/nightly-release.yml rename to .github/workflows/release-nightly.yml index 494a1cc6215..65e75007a36 100644 --- a/.github/workflows/nightly-release.yml +++ b/.github/workflows/release-nightly.yml @@ -1,4 +1,4 @@ -name: 'Nightly Release' +name: 'Release: Nightly' on: schedule: diff --git a/.github/workflows/create-patch-pr.yml b/.github/workflows/release-patch-1-create-pr.yml similarity index 98% rename from .github/workflows/create-patch-pr.yml rename to .github/workflows/release-patch-1-create-pr.yml index aecb8e01fa5..e8743063b3a 100644 --- a/.github/workflows/create-patch-pr.yml +++ b/.github/workflows/release-patch-1-create-pr.yml @@ -1,4 +1,4 @@ -name: 'Create Patch PR' +name: 'Release: Patch (1) Create PR' on: workflow_dispatch: diff --git a/.github/workflows/trigger-patch-release.yml b/.github/workflows/release-patch-2-trigger.yml similarity index 94% rename from .github/workflows/trigger-patch-release.yml rename to .github/workflows/release-patch-2-trigger.yml index 7692c87ad4d..8ced44f311f 100644 --- a/.github/workflows/trigger-patch-release.yml +++ b/.github/workflows/release-patch-2-trigger.yml @@ -1,4 +1,4 @@ -name: 'Trigger Patch Release' +name: 'Release: Patch (2) Trigger' on: pull_request: @@ -14,7 +14,7 @@ on: description: 'The workflow to trigger. Defaults to patch-release.yml' required: false type: 'string' - default: 'patch-release.yml' + default: 'release-patch-3-release.yml' dry_run: description: 'Whether this is a dry run.' required: false @@ -48,7 +48,7 @@ jobs: const channel = version.includes('preview') ? 'preview' : 'stable'; const ref = `release/${version}`; const workflow_id = context.eventName === 'pull_request' - ? 'patch-release.yml' + ? 'release-patch-3-release.yml' : '${{ github.event.inputs.workflow_id }}'; github.rest.actions.createWorkflowDispatch({ diff --git a/.github/workflows/patch-release.yml b/.github/workflows/release-patch-3-release.yml similarity index 86% rename from .github/workflows/patch-release.yml rename to .github/workflows/release-patch-3-release.yml index 0d8c99d8aec..6fd76c04f37 100644 --- a/.github/workflows/patch-release.yml +++ b/.github/workflows/release-patch-3-release.yml @@ -1,4 +1,4 @@ -name: 'Patch Release' +name: 'Release: Patch (3) Release' on: workflow_dispatch: @@ -57,25 +57,24 @@ jobs: - name: 'Print Calculated Version' run: |- - echo "Calculated version: ${{ steps.version.outputs.RELEASE_VERSION }}" + echo "Calculated version: ${{ steps.patch_version.outputs.RELEASE_VERSION }}" - name: 'Run Tests' uses: './.github/actions/run-tests' with: - force_skip_tests: '${{ github.event.inputs.force_skip_tests }}' gemini_api_key: '${{ secrets.GEMINI_API_KEY }}' - name: 'Publish Release' uses: './.github/actions/publish-release' with: - release-version: '${{ steps.version.outputs.RELEASE_VERSION }}' - release-tag: '${{ steps.version.outputs.RELEASE_TAG }}' - npm-tag: '${{ steps.version.outputs.NPM_TAG }}' + release-version: '${{ steps.patch_version.outputs.RELEASE_VERSION }}' + release-tag: '${{ steps.patch_version.outputs.RELEASE_TAG }}' + npm-tag: '${{ steps.patch_version.outputs.NPM_TAG }}' wombat-token-core: '${{ secrets.WOMBAT_TOKEN_CORE }}' wombat-token-cli: '${{ secrets.WOMBAT_TOKEN_CLI }}' github-token: '${{ secrets.GITHUB_TOKEN }}' dry-run: '${{ github.event.inputs.dry_run }}' - previous-tag: '${{ steps.version.outputs.PREVIOUS_TAG }}' + previous-tag: '${{ steps.patch_version.outputs.RELEASE_TAG }}' - name: 'Create Issue on Failure' if: '${{ failure() && github.event.inputs.dry_run == false }}' diff --git a/.github/workflows/patch-from-comment.yml b/.github/workflows/release-patch-from-comment.yml similarity index 95% rename from .github/workflows/patch-from-comment.yml rename to .github/workflows/release-patch-from-comment.yml index 55065b5b1cd..bfc51da8f9b 100644 --- a/.github/workflows/patch-from-comment.yml +++ b/.github/workflows/release-patch-from-comment.yml @@ -1,4 +1,4 @@ -name: 'Patch from Comment' +name: 'Release: Patch from Comment' on: issue_comment: @@ -38,7 +38,7 @@ jobs: github.rest.actions.createWorkflowDispatch({ owner: context.repo.owner, repo: context.repo.repo, - workflow_id: 'create-patch-pr.yml', + workflow_id: 'release-patch-1-create-pr.yml', ref: 'main', inputs: { commit: '${{ steps.pr_status.outputs.MERGE_COMMIT_SHA }}', diff --git a/.github/workflows/promote-release.yml b/.github/workflows/release-promote.yml similarity index 99% rename from .github/workflows/promote-release.yml rename to .github/workflows/release-promote.yml index 1911d3dc6a3..ef8e3f680f7 100644 --- a/.github/workflows/promote-release.yml +++ b/.github/workflows/release-promote.yml @@ -1,4 +1,4 @@ -name: 'Promote Release' +name: 'Release: Promote' on: workflow_dispatch: diff --git a/docs/issue-and-pr-automation.md b/docs/issue-and-pr-automation.md index 45a4bdfd0ef..bff71e05828 100644 --- a/docs/issue-and-pr-automation.md +++ b/docs/issue-and-pr-automation.md @@ -73,7 +73,7 @@ This is a fallback workflow to ensure that no issue gets missed by the triage pr This workflow handles the process of packaging and publishing new versions of the Gemini CLI. -- **Workflow File**: `.github/workflows/release.yml` +- **Workflow File**: `.github/workflows/release-manual.yml` - **When it runs**: On a daily schedule for "nightly" releases, and manually for official patch/minor releases. - **What it does**: - Automatically builds the project, bumps the version numbers, and publishes the packages to npm. diff --git a/docs/releases.md b/docs/releases.md index 6425fae6162..86d50e419e6 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -240,7 +240,7 @@ After pushing a new release smoke testing should be performed to ensure that the If you need to test the release process without actually publishing to NPM or creating a public GitHub release, you can trigger the workflow manually from the GitHub UI. -1. Go to the [Actions tab](https://github.com/google-gemini/gemini-cli/actions/workflows/release.yml) of the repository. +1. Go to the [Actions tab](https://github.com/google-gemini/gemini-cli/actions/workflows/release-manual.yml) of the repository. 2. Click on the "Run workflow" dropdown. 3. Leave the `dry_run` option checked (`true`). 4. Click the "Run workflow" button. From e8dc74854cd31981bf9a13ea67662ac309cc7fdf Mon Sep 17 00:00:00 2001 From: mkorwel Date: Wed, 17 Sep 2025 20:23:41 -0700 Subject: [PATCH 12/13] refactor(workflows): simplify and standardize manual release process --- .github/workflows/release-manual.yml | 192 +++++---------------------- docs/releases.md | 164 ++--------------------- 2 files changed, 46 insertions(+), 310 deletions(-) diff --git a/.github/workflows/release-manual.yml b/.github/workflows/release-manual.yml index 5e42bfd0a9a..b0abd5012c1 100644 --- a/.github/workflows/release-manual.yml +++ b/.github/workflows/release-manual.yml @@ -4,14 +4,21 @@ on: workflow_dispatch: inputs: version: - description: 'The version to release (e.g., v0.1.11).' + description: 'The version to release (e.g., v0.1.11). Must be a valid semver string with a "v" prefix.' required: true type: 'string' ref: - description: 'The branch or ref (full git sha) to release from.' + description: 'The branch, tag, or SHA to release from.' required: true type: 'string' - default: 'main' + npm_channel: + description: 'The npm channel to publish to.' + required: true + type: 'choice' + options: + - 'stable' + - 'preview' + - 'dev' dry_run: description: 'Run a dry-run of the release process; no branches, npm packages or GitHub releases will be created.' required: true @@ -26,177 +33,48 @@ on: jobs: release: runs-on: 'ubuntu-latest' - environment: - name: 'production-release' - url: '${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ steps.version.outputs.RELEASE_TAG }}' - if: |- - ${{ github.repository == 'google-gemini/gemini-cli' }} permissions: contents: 'write' packages: 'write' - id-token: 'write' - issues: 'write' # For creating issues on failure - outputs: - RELEASE_TAG: '${{ steps.version.outputs.RELEASE_TAG }}' - + issues: 'write' steps: - name: 'Checkout' - uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5 + uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' with: - ref: '${{ github.event.inputs.ref || github.sha }}' + ref: '${{ github.event.inputs.ref }}' fetch-depth: 0 - - name: 'Set booleans for simplified logic' - env: - DRY_RUN_INPUT: '${{ github.event.inputs.dry_run }}' - id: 'vars' - run: |- - is_dry_run="false" - if [[ "${DRY_RUN_INPUT}" == "true" ]]; then - is_dry_run="true" - fi - echo "is_dry_run=${is_dry_run}" >> "${GITHUB_OUTPUT}" - - name: 'Setup Node.js' - uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions/setup-node@v4 + uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' with: node-version-file: '.nvmrc' cache: 'npm' - name: 'Install Dependencies' - run: |- - npm ci + run: 'npm ci' - - name: 'Get the version' - id: 'version' - run: |- - RELEASE_TAG="${{ inputs.version }}" - # The version for npm should not have the 'v' prefix. - RELEASE_VERSION="${RELEASE_TAG#v}" - NPM_TAG="latest" - if [[ "${RELEASE_TAG}" == *"preview"* ]]; then - NPM_TAG="preview" - fi - PREVIOUS_TAG=$(git describe --tags --abbrev=0) - echo "RELEASE_TAG=${RELEASE_TAG}" >> "${GITHUB_OUTPUT}" - echo "RELEASE_VERSION=${RELEASE_VERSION}" >> "${GITHUB_OUTPUT}" - echo "NPM_TAG=${NPM_TAG}" >> "${GITHUB_OUTPUT}" - echo "PREVIOUS_TAG=${PREVIOUS_TAG}" >> "${GITHUB_OUTPUT}" + - name: 'Prepare Release Info' + id: 'release_info' + run: | + RELEASE_VERSION="${{ github.event.inputs.version }}" + echo "RELEASE_VERSION=${RELEASE_VERSION#v}" >> "${GITHUB_OUTPUT}" + echo "PREVIOUS_TAG=$(git describe --tags --abbrev=0)" >> "${GITHUB_OUTPUT}" - name: 'Run Tests' if: |- - ${{ github.event.inputs.force_skip_tests != 'true' }} - env: - GEMINI_API_KEY: '${{ secrets.GEMINI_API_KEY }}' - run: |- - npm run preflight - npm run test:integration:sandbox:none - npm run test:integration:sandbox:docker - - - name: 'Configure Git User' - run: |- - git config user.name "gemini-cli-robot" - git config user.email "gemini-cli-robot@google.com" - - - name: 'Create and switch to a release branch' - id: 'release_branch' - env: - RELEASE_TAG: '${{ steps.version.outputs.RELEASE_TAG }}' - run: |- - BRANCH_NAME="release/${RELEASE_TAG}" - git switch -c "${BRANCH_NAME}" - echo "BRANCH_NAME=${BRANCH_NAME}" >> "${GITHUB_OUTPUT}" - - - name: 'Update package versions' - env: - RELEASE_VERSION: '${{ steps.version.outputs.RELEASE_VERSION }}' - run: |- - npm run release:version "${RELEASE_VERSION}" - - - name: 'Commit and Conditionally Push package versions' - env: - BRANCH_NAME: '${{ steps.release_branch.outputs.BRANCH_NAME }}' - IS_DRY_RUN: '${{ steps.vars.outputs.is_dry_run }}' - RELEASE_TAG: '${{ steps.version.outputs.RELEASE_TAG }}' - run: |- - git add package.json npm-shrinkwrap.json packages/*/package.json - git commit -m "chore(release): ${RELEASE_TAG}" - if [[ "${IS_DRY_RUN}" == "false" ]]; then - echo "Pushing release branch to remote..." - git push --set-upstream origin "${BRANCH_NAME}" --follow-tags - else - echo "Dry run enabled. Skipping push." - fi - - - name: 'Build and Prepare Packages' - run: |- - npm run build:packages - npm run prepare:package - - - name: 'Configure npm for publishing' - uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions/setup-node@v4 + ${{ github.event.inputs.force_skip_tests != true }} + uses: './.github/actions/run-tests' with: - node-version-file: '.nvmrc' - registry-url: 'https://wombat-dressing-room.appspot.com' - scope: '@google' - - - name: 'Publish @google/gemini-cli-core' - env: - IS_DRY_RUN: '${{ steps.vars.outputs.is_dry_run }}' - NODE_AUTH_TOKEN: '${{ secrets.WOMBAT_TOKEN_CORE }}' - NPM_TAG: '${{ steps.version.outputs.NPM_TAG }}' - run: |- - npm publish \ - --dry-run="${IS_DRY_RUN}" \ - --workspace="@google/gemini-cli-core" \ - --tag="${NPM_TAG}" - - - name: 'Install latest core package' - if: |- - ${{ steps.vars.outputs.is_dry_run == 'false' }} - env: - RELEASE_VERSION: '${{ steps.version.outputs.RELEASE_VERSION }}' - run: |- - npm install "@google/gemini-cli-core@${RELEASE_VERSION}" \ - --workspace="@google/gemini-cli" \ - --save-exact - - - name: 'Publish @google/gemini-cli' - env: - IS_DRY_RUN: '${{ steps.vars.outputs.is_dry_run }}' - NODE_AUTH_TOKEN: '${{ secrets.WOMBAT_TOKEN_CLI }}' - NPM_TAG: '${{ steps.version.outputs.NPM_TAG }}' - run: |- - npm publish \ - --dry-run="${IS_DRY_RUN}" \ - --workspace="@google/gemini-cli" \ - --tag="${NPM_TAG}" + gemini_api_key: '${{ secrets.GEMINI_API_KEY }}' - - name: 'Create GitHub Release and Tag' - if: |- - ${{ steps.vars.outputs.is_dry_run == 'false' }} - env: - GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' - RELEASE_BRANCH: '${{ steps.release_branch.outputs.BRANCH_NAME }}' - RELEASE_TAG: '${{ steps.version.outputs.RELEASE_TAG }}' - PREVIOUS_TAG: '${{ steps.version.outputs.PREVIOUS_TAG }}' - run: |- - gh release create "${RELEASE_TAG}" \ - bundle/gemini.js \ - --target "$RELEASE_BRANCH" \ - --title "Release ${RELEASE_TAG}" \ - --notes-start-tag "$PREVIOUS_TAG" \ - --generate-notes - - - name: 'Create Issue on Failure' - if: |- - ${{ failure() }} - env: - GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' - RELEASE_TAG: '${{ steps.version.outputs.RELEASE_TAG }} || "N/A"' - DETAILS_URL: '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}' - run: |- - gh issue create \ - --title "Release Failed for ${RELEASE_TAG} on $(date +'%Y-%m-%d')" \ - --body "The release workflow failed. See the full run for details: ${DETAILS_URL}" \ - --label "kind/bug,release-failure,priority/p0" + - name: 'Publish Release' + uses: './.github/actions/publish-release' + with: + release-version: '${{ steps.release_info.outputs.RELEASE_VERSION }}' + release-tag: '${{ github.event.inputs.version }}' + npm-tag: '${{ github.event.inputs.npm_channel }}' + wombat-token-core: '${{ secrets.WOMBAT_TOKEN_CORE }}' + wombat-token-cli: '${{ secrets.WOMBAT_TOKEN_CLI }}' + github-token: '${{ secrets.GITHUB_TOKEN }}' + dry-run: '${{ github.event.inputs.dry_run }}' + previous-tag: '${{ steps.release_info.outputs.PREVIOUS_TAG }}' diff --git a/docs/releases.md b/docs/releases.md index 86d50e419e6..a6ebc5ee5ab 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -59,169 +59,27 @@ To ensure the highest reliability, the release promotion process uses the **NPM This NPM-first approach, backed by integrity checks, makes the release process highly robust and prevents the kinds of versioning discrepancies that can arise from relying solely on git history or API outputs. -## Patching Releases +## Manual Releases -If a critical bug that is already fixed on `main` needs to be patched on a `stable` or `preview` release, the process is now highly automated. - -### 1. Create the Patch Pull Request - -There are two ways to create a patch pull request: - -**Option A: From a GitHub Comment (Recommended)** - -After a pull request has been merged, a maintainer can add a comment on that same PR with the following format: - -`/patch [--dry-run]` - -- **channel**: `stable` or `preview` -- **--dry-run** (optional): If included, the workflow will run in dry-run mode. This will create the PR with "[DRY RUN]" in the title, and merging it will trigger a dry run of the final release, so nothing is actually published. - -Example: `/patch stable --dry-run` - -The workflow will automatically find the merge commit SHA and begin the patch process. If the PR is not yet merged, it will post a comment indicating the failure. - -**Option B: Manually Triggering the Workflow** - -Follow the manual release process using the "Patch Release" GitHub Actions workflow. - -- **Type**: Select whether you are patching a `stable` or `preview` release. The workflow will automatically calculate the next patch version. -- **Ref**: Use your source branch as the reference (ex. `release/v0.2.0-preview.0`) - Navigate to the **Actions** tab and run the **Create Patch PR** workflow. - -- **Commit**: The full SHA of the commit on `main` that you want to cherry-pick. -- **Channel**: The channel you want to patch (`stable` or `preview`). - -This workflow will automatically: - -1. Find the latest release tag for the channel. -2. Create a release branch from that tag if one doesn't exist (e.g., `release/v0.5.1`). -3. Create a new hotfix branch from the release branch. -4. Cherry-pick your specified commit into the hotfix branch. -5. Create a pull request from the hotfix branch back to the release branch. - -**Important:** If you select `stable`, the workflow will run twice, creating one PR for the `stable` channel and a second PR for the `preview` channel. - -### 2. Review and Merge - -Review the automatically created pull request(s) to ensure the cherry-pick was successful and the changes are correct. Once approved, merge the pull request. - -**Security Note:** The `release/*` branches are protected by branch protection rules. A pull request to one of these branches requires at least one review from a code owner before it can be merged. This ensures that no unauthorized code is released. - -### 3. Automatic Release - -Upon merging the pull request, a final workflow is automatically triggered. It will: - -1. Run the `patch-release` workflow. -2. Build and test the patched code. -3. Publish the new patch version to npm. -4. Create a new GitHub release with the patch notes. - -This fully automated process ensures that patches are created and released consistently and reliably. - -## Release Schedule - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Date - Stable UTC 2000 - Preview UTC 2359 -
Aug 19th, 2025 - N/A - 0.2.0-preview.0 -
Aug 26th, 2025 - 0.2.0 - 0.3.0-preview.0 -
Sep 2nd, 2025 - 0.3.0 - 0.4.0-preview.0 -
Sep 9th, 2025 - 0.4.0 - 0.5.0-preview.0 -
Sep 16th, 2025 - 0.5.0 - 0.6.0-preview.0 -
Sep 23rd, 2025 - 0.6.0 - 0.7.0-preview.0 -
- -## How To Release - -Releases are managed through GitHub Actions workflows. - -### Weekly Promotions - -To perform the weekly promotion of `preview` to `stable` and `nightly` to `preview`: +For situations requiring a release outside of the regular nightly and weekly promotion schedule (e.g., a critical hotfix), you can use the `Release: Manual` workflow. This workflow provides a direct way to publish a specific version from any branch, tag, or commit SHA. -1. Navigate to the **Actions** tab of the repository. -2. Select the **Promote Release** workflow from the list. -3. Click the **Run workflow** dropdown button. -4. Leave **Dry Run** as `true` to test the workflow without publishing, or set to `false` to perform a live release. -5. Click **Run workflow**. - -### Patching a Release +This process replaces the previous multi-step patching procedure, offering a single, streamlined workflow for all manual release needs. -To perform a manual release for a patch or hotfix: +### How to Create a Manual Release 1. Navigate to the **Actions** tab of the repository. -2. Select the **Patch Release** workflow from the list. +2. Select the **Release: Manual** workflow from the list. 3. Click the **Run workflow** dropdown button. 4. Fill in the required inputs: - - **Type**: Select whether you are patching a `stable` or `preview` release. - - **Ref**: The branch or commit SHA to release from. - - **Dry Run**: Leave as `true` to test the workflow without publishing, or set to `false` to perform a live release. + - **Version**: The exact version to release (e.g., `v0.6.1`). This must be a valid semantic version with a `v` prefix. + - **Ref**: The branch, tag, or full commit SHA to release from. + - **NPM Channel**: The npm tag to publish with. Select `stable` for a general release, `preview` for a pre-release, or `none` to skip publishing to npm entirely. + - **Dry Run**: Leave as `true` to run all steps without publishing, or set to `false` to perform a live release. + - **Force Skip Tests**: Set to `true` to skip the test suite. This is not recommended for production releases. 5. Click **Run workflow**. -### TLDR - -Each release, wether automated or manual performs the following steps: - -1. Checks out the latest code from the `main` branch. -1. Installs all dependencies. -1. Runs the full suite of `preflight` checks and integration tests. -1. If all tests succeed, it calculates the next version number based on the inputs. -1. It creates a branch name `release/${VERSION}`. -1. It creates a tag name `v${VERSION}`. -1. It then builds and publishes the packages to npm with the provided version number. -1. Finally, it creates a GitHub Release for the version. - -### Failure Handling +The workflow will then proceed to test (if not skipped), build, and publish the release. If the workflow fails during a non-dry run, it will automatically create a GitHub issue with the failure details. -If any step in the workflow fails, it will automatically create a new issue in the repository with the labels `bug` and `release-failure`. The issue will contain a link to the failed workflow run for easy debugging. ### Docker From fd53b8ef02192bb10d79be5e5b900acae62508d3 Mon Sep 17 00:00:00 2001 From: mkorwel Date: Wed, 17 Sep 2025 20:44:08 -0700 Subject: [PATCH 13/13] feat(workflows): add release-change-tags workflow and update docs --- .github/workflows/release-change-tags.yml | 55 +++++++++++ docs/releases.md | 107 ++++++++++++++++++---- 2 files changed, 144 insertions(+), 18 deletions(-) create mode 100644 .github/workflows/release-change-tags.yml diff --git a/.github/workflows/release-change-tags.yml b/.github/workflows/release-change-tags.yml new file mode 100644 index 00000000000..b594d99d173 --- /dev/null +++ b/.github/workflows/release-change-tags.yml @@ -0,0 +1,55 @@ +name: 'Release: Change Tags' + +on: + workflow_dispatch: + inputs: + version: + description: 'The package version to tag (e.g., 0.5.0-preview-2). This version must already exist on the npm registry.' + required: true + type: 'string' + channel: + description: 'The npm dist-tag to apply (e.g., preview, stable).' + required: true + type: 'choice' + options: + - 'stable' + - 'preview' + - 'nightly' + dry_run: + description: 'Whether to run in dry-run mode.' + required: false + type: 'boolean' + default: true + +jobs: + change-tags: + runs-on: 'ubuntu-latest' + permissions: + packages: 'write' + issues: 'write' + steps: + - name: 'Setup Node.js' + uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' + with: + node-version-file: '.nvmrc' + registry-url: 'https://wombat-dressing-room.appspot.com' + scope: '@google' + + - name: 'Change tag for @google/gemini-cli-core' + if: 'github.event.inputs.dry_run == false' + env: + NODE_AUTH_TOKEN: '${{ secrets.WOMBAT_TOKEN_CORE }}' + run: | + npm dist-tag add @google/gemini-cli-core@${{ github.event.inputs.version }} ${{ github.event.inputs.channel }} + + - name: 'Change tag for @google/gemini-cli' + if: 'github.event.inputs.dry_run == false' + env: + NODE_AUTH_TOKEN: '${{ secrets.WOMBAT_TOKEN_CLI }}' + run: | + npm dist-tag add @google/gemini-cli@${{ github.event.inputs.version }} ${{ github.event.inputs.channel }} + + - name: 'Log dry run' + if: 'github.event.inputs.dry_run == true' + run: | + echo "Dry run: Would have added tag '${{ github.event.inputs.channel }}' to version '${{ github.event.inputs.version }}' for @google/gemini-cli and @google/gemini-cli-core." diff --git a/docs/releases.md b/docs/releases.md index a6ebc5ee5ab..25235ab467d 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -4,9 +4,16 @@ We will follow https://semver.org/ as closely as possible but will call out when or if we have to deviate from it. Our weekly releases will be minor version increments and any bug or hotfixes between releases will go out as patch versions on the most recent release. +Each Tuesaday ~2000 UTC new Stable and Preview releases will be cut. The promotion flow is: + +- Code is commited to main and pushed each night to nightly +- After no more than 1 week on main, code is promoted to the `preview` channel +- After 1 week the most recent `preview` channel is promoted to `stable` cannel +- Patch fixes will be produced against both `preview` and `stable` as needed, with the final 'patch' version number incrementing each time. + ### Preview -New preview releases will be published each week at UTC 2359 on Tuesdays. These releases will not have been fully vetted and may contain regressions or other outstanding issues. Please help us test and install with `preview` tag. +These releases will not have been fully vetted and may contain regressions or other outstanding issues. Please help us test and install with `preview` tag. ```bash npm install -g @google/gemini-cli@preview @@ -14,7 +21,7 @@ npm install -g @google/gemini-cli@preview ### Stable -- New stable releases will be published each week at UTC 2000 on Tuesdays, this will be the full promotion of last week's release + any bug fixes and validations. Use `latest` tag. +This will be the full promotion of last week's release + any bug fixes and validations. Use `latest` tag. ```bash npm install -g @google/gemini-cli@latest @@ -28,14 +35,6 @@ npm install -g @google/gemini-cli@latest npm install -g @google/gemini-cli@nightly ``` -# Release Process - -Our release cadence is new releases are sent to a preview channel for a week and then promoted to stable after a week. Version numbers will follow SemVer with weekly releases incrementing the minor version. Patches and bug fixes to both preview and stable releases will increment the patch version. - -## Nightly Release - -Each night at UTC 0000 we will auto deploy a nightly release from `main`. This will be a version of the next production release, x.y.z, with the nightly tag. - ## Weekly Release Promotion Each Tuesday, the on-call engineer will trigger the "Promote Release" workflow. This single action automates the entire weekly release process: @@ -61,9 +60,7 @@ This NPM-first approach, backed by integrity checks, makes the release process h ## Manual Releases -For situations requiring a release outside of the regular nightly and weekly promotion schedule (e.g., a critical hotfix), you can use the `Release: Manual` workflow. This workflow provides a direct way to publish a specific version from any branch, tag, or commit SHA. - -This process replaces the previous multi-step patching procedure, offering a single, streamlined workflow for all manual release needs. +For situations requiring a release outside of the regular nightly and weekly promotion schedule, and NOT already covered by patching process, you can use the `Release: Manual` workflow. This workflow provides a direct way to publish a specific version from any branch, tag, or commit SHA. ### How to Create a Manual Release @@ -71,15 +68,89 @@ This process replaces the previous multi-step patching procedure, offering a sin 2. Select the **Release: Manual** workflow from the list. 3. Click the **Run workflow** dropdown button. 4. Fill in the required inputs: - - **Version**: The exact version to release (e.g., `v0.6.1`). This must be a valid semantic version with a `v` prefix. - - **Ref**: The branch, tag, or full commit SHA to release from. - - **NPM Channel**: The npm tag to publish with. Select `stable` for a general release, `preview` for a pre-release, or `none` to skip publishing to npm entirely. - - **Dry Run**: Leave as `true` to run all steps without publishing, or set to `false` to perform a live release. - - **Force Skip Tests**: Set to `true` to skip the test suite. This is not recommended for production releases. + - **Version**: The exact version to release (e.g., `v0.6.1`). This must be a valid semantic version with a `v` prefix. + - **Ref**: The branch, tag, or full commit SHA to release from. + - **NPM Channel**: The npm tag to publish with. Select `stable` for a general release, `preview` for a pre-release, or `none` to skip publishing to npm entirely. + - **Dry Run**: Leave as `true` to run all steps without publishing, or set to `false` to perform a live release. + - **Force Skip Tests**: Set to `true` to skip the test suite. This is not recommended for production releases. 5. Click **Run workflow**. The workflow will then proceed to test (if not skipped), build, and publish the release. If the workflow fails during a non-dry run, it will automatically create a GitHub issue with the failure details. +## Rollback/Rollforward + +In the event that a release has a critical regression, you can quickly roll back to a previous stable version or roll forward to a new patch by changing the npm `dist-tag`. The `Release: Change Tags` workflow provides a safe and controlled way to do this. + +This is the preferred method for both rollbacks and rollforwards, as it does not require a full release cycle. + +### How to Change a Release Tag + +1. Navigate to the **Actions** tab of the repository. +2. Select the **Release: Change Tags** workflow from the list. +3. Click the **Run workflow** dropdown button. +4. Fill in the required inputs: + - **Version**: The existing package version that you want to point the tag to (e.g., `0.5.0-preview-2`). This version **must** already be published to the npm registry. + - **Channel**: The npm `dist-tag` to apply (e.g., `preview`, `stable`). + - **Dry Run**: Leave as `true` to log the action without making changes, or set to `false` to perform the live tag change. +5. Click **Run workflow**. + +The workflow will then run `npm dist-tag add` for both the `@google/gemini-cli` and `@google/gemini-cli-core` packages, pointing the specified channel to the specified version. + +## Patching + +If a critical bug that is already fixed on `main` needs to be patched on a `stable` or `preview` release, the process is now highly automated. + +### How to Patch + +#### 1. Create the Patch Pull Request + +There are two ways to create a patch pull request: + +**Option A: From a GitHub Comment (Recommended)** + +After a pull request containing the fix has been merged, a maintainer can add a comment on that same PR with the following format: + +`/patch [--dry-run]` + +- **channel**: `stable` or `preview` +- **--dry-run** (optional): If included, the workflow will run in dry-run mode. This will create the PR with "[DRY RUN]" in the title, and merging it will trigger a dry run of the final release, so nothing is actually published. + +Example: `/patch stable --dry-run` + +The `Release: Patch from Comment` workflow will automatically find the merge commit SHA and trigger the `Release: Patch (1) Create PR` workflow. If the PR is not yet merged, it will post a comment indicating the failure. + +**Option B: Manually Triggering the Workflow** + +Navigate to the **Actions** tab and run the **Release: Patch (1) Create PR** workflow. + +- **Commit**: The full SHA of the commit on `main` that you want to cherry-pick. +- **Channel**: The channel you want to patch (`stable` or `preview`). + +This workflow will automatically: + +1. Find the latest release tag for the channel. +2. Create a release branch from that tag if one doesn't exist (e.g., `release/v0.5.1`). +3. Create a new hotfix branch from the release branch. +4. Cherry-pick your specified commit into the hotfix branch. +5. Create a pull request from the hotfix branch back to the release branch. + +**Important:** If you select `stable`, the workflow will run twice, creating one PR for the `stable` channel and a second PR for the `preview` channel. + +#### 2. Review and Merge + +Review the automatically created pull request(s) to ensure the cherry-pick was successful and the changes are correct. Once approved, merge the pull request. + +**Security Note:** The `release/*` branches are protected by branch protection rules. A pull request to one of these branches requires at least one review from a code owner before it can be merged. This ensures that no unauthorized code is released. + +#### 3. Automatic Release + +Upon merging the pull request, the `Release: Patch (2) Trigger` workflow is automatically triggered. It will then start the `Release: Patch (3) Release` workflow, which will: + +1. Build and test the patched code. +2. Publish the new patch version to npm. +3. Create a new GitHub release with the patch notes. + +This fully automated process ensures that patches are created and released consistently and reliably. ### Docker