From 930f39a0cdcc3e1b4a61ffdc68f7f0c8f9d5e125 Mon Sep 17 00:00:00 2001 From: matt korwel Date: Wed, 17 Sep 2025 21:16:08 -0700 Subject: [PATCH] Releasing: Patching and Rollback (#8673) --- .github/workflows/patch-release.yml | 94 -------- .github/workflows/release-change-tags.yml | 55 +++++ .github/workflows/release-manual.yml | 80 +++++++ ...ightly-release.yml => release-nightly.yml} | 2 +- ...h-pr.yml => release-patch-1-create-pr.yml} | 8 +- .github/workflows/release-patch-2-trigger.yml | 65 ++++++ .github/workflows/release-patch-3-release.yml | 89 ++++++++ ...ent.yml => release-patch-from-comment.yml} | 4 +- ...romote-release.yml => release-promote.yml} | 2 +- .github/workflows/release.yml | 202 ----------------- .github/workflows/trigger-patch-release.yml | 30 --- docs/issue-and-pr-automation.md | 2 +- docs/releases.md | 203 ++++++------------ scripts/create-patch-pr.js | 18 +- 14 files changed, 379 insertions(+), 475 deletions(-) delete mode 100644 .github/workflows/patch-release.yml create mode 100644 .github/workflows/release-change-tags.yml create mode 100644 .github/workflows/release-manual.yml rename .github/workflows/{nightly-release.yml => release-nightly.yml} (99%) rename .github/workflows/{create-patch-pr.yml => release-patch-1-create-pr.yml} (90%) create mode 100644 .github/workflows/release-patch-2-trigger.yml create mode 100644 .github/workflows/release-patch-3-release.yml rename .github/workflows/{patch-from-comment.yml => release-patch-from-comment.yml} (95%) rename .github/workflows/{promote-release.yml => release-promote.yml} (99%) delete mode 100644 .github/workflows/release.yml delete mode 100644 .github/workflows/trigger-patch-release.yml diff --git a/.github/workflows/patch-release.yml b/.github/workflows/patch-release.yml deleted file mode 100644 index 9a05bfad76..0000000000 --- a/.github/workflows/patch-release.yml +++ /dev/null @@ -1,94 +0,0 @@ -name: 'Patch Release' - -on: - workflow_dispatch: - inputs: - type: - description: 'The type of release to patch from.' - 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 - force_skip_tests: - description: 'Select to skip the "Run Tests" step in testing. Prod releases should run tests' - required: false - type: 'boolean' - default: false - -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 }}' - - steps: - - name: 'Checkout' - uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5 - with: - ref: '${{ github.event.inputs.ref || github.sha }}' - fetch-depth: 0 - - - name: 'Setup Node.js' - uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions/setup-node@v4 - with: - node-version-file: '.nvmrc' - cache: 'npm' - - - name: 'Install Dependencies' - 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: 'Print Calculated Version' - run: |- - echo "Calculated version: ${{ steps.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 }}' - 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 }}' diff --git a/.github/workflows/release-change-tags.yml b/.github/workflows/release-change-tags.yml new file mode 100644 index 0000000000..b594d99d17 --- /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/.github/workflows/release-manual.yml b/.github/workflows/release-manual.yml new file mode 100644 index 0000000000..b0abd5012c --- /dev/null +++ b/.github/workflows/release-manual.yml @@ -0,0 +1,80 @@ +name: 'Release: Manual' + +on: + workflow_dispatch: + inputs: + version: + 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, tag, or SHA to release from.' + required: true + type: 'string' + 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 + type: 'boolean' + default: true + force_skip_tests: + description: 'Select to skip the "Run Tests" step in testing. Prod releases should run tests' + required: false + type: 'boolean' + default: false + +jobs: + release: + runs-on: 'ubuntu-latest' + permissions: + contents: 'write' + packages: 'write' + issues: 'write' + steps: + - name: 'Checkout' + uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' + with: + ref: '${{ github.event.inputs.ref }}' + fetch-depth: 0 + + - name: 'Setup Node.js' + uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: 'Install Dependencies' + run: 'npm ci' + + - 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 }} + uses: './.github/actions/run-tests' + with: + gemini_api_key: '${{ secrets.GEMINI_API_KEY }}' + + - 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/.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 494a1cc621..65e75007a3 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 90% rename from .github/workflows/create-patch-pr.yml rename to .github/workflows/release-patch-1-create-pr.yml index d8ba42cec4..e8743063b3 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: @@ -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/.github/workflows/release-patch-2-trigger.yml b/.github/workflows/release-patch-2-trigger.yml new file mode 100644 index 0000000000..8ced44f311 --- /dev/null +++ b/.github/workflows/release-patch-2-trigger.yml @@ -0,0 +1,65 @@ +name: 'Release: Patch (2) Trigger' + +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: 'release-patch-3-release.yml' + dry_run: + description: 'Whether this is a dry run.' + required: false + type: 'boolean' + default: false + +jobs: + trigger-patch-release: + 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' + steps: + - name: 'Trigger Patch Release' + uses: 'actions/github-script@00f12e3e20659f42342b1c0226afda7f7c042325' + with: + script: | + 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 version = headRef.split('/')[1]; + const channel = version.includes('preview') ? 'preview' : 'stable'; + const ref = `release/${version}`; + const workflow_id = context.eventName === 'pull_request' + ? 'release-patch-3-release.yml' + : '${{ github.event.inputs.workflow_id }}'; + + github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: workflow_id, + ref: 'mk-patch-releases', + inputs: { + type: channel, + dry_run: isDryRun.toString(), + version: version, + release_ref: ref + } + }) diff --git a/.github/workflows/release-patch-3-release.yml b/.github/workflows/release-patch-3-release.yml new file mode 100644 index 0000000000..6fd76c04f3 --- /dev/null +++ b/.github/workflows/release-patch-3-release.yml @@ -0,0 +1,89 @@ +name: 'Release: Patch (3) Release' + +on: + workflow_dispatch: + inputs: + type: + description: 'The type of release to perform.' + required: true + type: 'choice' + options: + - 'stable' + - 'preview' + 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' + release_ref: + description: 'The branch, tag, or SHA to release from.' + required: true + type: 'string' + +jobs: + release: + runs-on: 'ubuntu-latest' + permissions: + contents: 'write' + packages: 'write' + issues: 'write' + steps: + - name: 'Checkout' + uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' + with: + ref: '${{ github.event.inputs.release_ref }}' + fetch-depth: 0 + + - name: 'Setup Node.js' + uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: 'Install Dependencies' + run: |- + npm ci + + - 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: |- + echo "Calculated version: ${{ steps.patch_version.outputs.RELEASE_VERSION }}" + + - name: 'Run Tests' + uses: './.github/actions/run-tests' + with: + gemini_api_key: '${{ secrets.GEMINI_API_KEY }}' + + - name: 'Publish Release' + uses: './.github/actions/publish-release' + with: + 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.patch_version.outputs.RELEASE_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' 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 55065b5b1c..bfc51da8f9 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 1911d3dc6a..ef8e3f680f 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/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 4b29a925ae..0000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,202 +0,0 @@ -name: 'Release' - -on: - workflow_dispatch: - inputs: - version: - description: 'The version to release (e.g., v0.1.11).' - required: true - type: 'string' - 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 - force_skip_tests: - description: 'Select to skip the "Run Tests" step in testing. Prod releases should run tests' - required: false - type: 'boolean' - default: false - -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 }}' - - steps: - - name: 'Checkout' - uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5 - with: - ref: '${{ github.event.inputs.ref || github.sha }}' - 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 - with: - node-version-file: '.nvmrc' - cache: 'npm' - - - name: 'Install Dependencies' - 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: '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 - 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}" - - - 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" diff --git a/.github/workflows/trigger-patch-release.yml b/.github/workflows/trigger-patch-release.yml deleted file mode 100644 index 4270111bbd..0000000000 --- a/.github/workflows/trigger-patch-release.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: 'Trigger Patch Release' - -on: - pull_request: - types: - - 'closed' - -jobs: - trigger-patch-release: - if: "github.event.pull_request.merged == true && startsWith(github.head_ref, 'hotfix/')" - runs-on: 'ubuntu-latest' - steps: - - name: 'Trigger Patch Release' - uses: 'actions/github-script@00f12e3e20659f42342b1c0226afda7f7c042325' - with: - script: | - const body = context.payload.pull_request.body; - const isDryRun = body.includes('[DRY RUN]'); - const ref = context.payload.pull_request.base.ref; - const channel = ref.includes('preview') ? 'preview' : 'stable'; - github.rest.actions.createWorkflowDispatch({ - owner: context.repo.owner, - repo: context.repo.repo, - workflow_id: 'patch-release.yml', - ref: ref, - inputs: { - type: channel, - dry_run: isDryRun.toString() - } - }) diff --git a/docs/issue-and-pr-automation.md b/docs/issue-and-pr-automation.md index 45a4bdfd0e..bff71e0582 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 6425fae616..25235ab467 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: @@ -59,17 +58,57 @@ 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 + +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 + +1. Navigate to the **Actions** tab of the repository. +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. +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. -### 1. Create the Patch Pull Request +### 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 has been merged, a maintainer can add a comment on that same PR with the following format: +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]` @@ -78,151 +117,41 @@ After a pull request has been merged, a maintainer can add a comment on that sam 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. +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** -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. +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. +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 +#### 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 +#### 3. Automatic Release -Upon merging the pull request, a final workflow is automatically triggered. It will: +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. 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. +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. -## 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`: - -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 - -To perform a manual release for a patch or hotfix: - -1. Navigate to the **Actions** tab of the repository. -2. Select the **Patch Release** 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. -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 - -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 We also run a Google cloud build called [release-docker.yml](../.gcp/release-docker.yml). Which publishes the sandbox docker to match your release. This will also be moved to GH and combined with the main release file once service account permissions are sorted out. @@ -240,7 +169,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. diff --git a/scripts/create-patch-pr.js b/scripts/create-patch-pr.js index 7804c2a9f0..7b6ff57043 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) { @@ -116,8 +122,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();