Releasing: Patching and Rollback (#8673)

This commit is contained in:
matt korwel
2025-09-17 21:16:08 -07:00
committed by GitHub
parent 8f0306f499
commit 930f39a0cd
14 changed files with 379 additions and 475 deletions

View File

@@ -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 }}'

View File

@@ -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."

80
.github/workflows/release-manual.yml vendored Normal file
View File

@@ -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 }}'

View File

@@ -1,4 +1,4 @@
name: 'Nightly Release'
name: 'Release: Nightly'
on:
schedule:

View File

@@ -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'

View File

@@ -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
}
})

View File

@@ -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'

View File

@@ -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 }}',

View File

@@ -1,4 +1,4 @@
name: 'Promote Release'
name: 'Release: Promote'
on:
workflow_dispatch:

View File

@@ -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"

View File

@@ -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()
}
})

View File

@@ -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.

View File

@@ -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 <channel> [--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
<table>
<tr>
<td>Date
</td>
<td>Stable UTC 2000
</td>
<td>Preview UTC 2359
</td>
</tr>
<tr>
<td>Aug 19th, 2025
</td>
<td>N/A
</td>
<td>0.2.0-preview.0
</td>
</tr>
<tr>
<td>Aug 26th, 2025
</td>
<td>0.2.0
</td>
<td>0.3.0-preview.0
</td>
</tr>
<tr>
<td>Sep 2nd, 2025
</td>
<td>0.3.0
</td>
<td>0.4.0-preview.0
</td>
</tr>
<tr>
<td>Sep 9th, 2025
</td>
<td>0.4.0
</td>
<td>0.5.0-preview.0
</td>
</tr>
<tr>
<td>Sep 16th, 2025
</td>
<td>0.5.0
</td>
<td>0.6.0-preview.0
</td>
</tr>
<tr>
<td>Sep 23rd, 2025
</td>
<td>0.6.0
</td>
<td>0.7.0-preview.0
</td>
</tr>
</table>
## 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.

View File

@@ -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();