Relase: Clean up and condensing (#3321)

This commit is contained in:
matt korwel 2025-07-05 13:58:59 -07:00 committed by GitHub
parent 4be32d1f73
commit a7256f630c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 349 additions and 442 deletions

View File

@ -1,31 +0,0 @@
steps:
- name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder'
entrypoint: 'npm'
args: ['install']
- name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder'
entrypoint: 'npm'
args: ['run', 'auth']
- name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder'
entrypoint: 'npm'
args:
[
'run',
'prerelease:version',
'--workspaces',
'--',
'--suffix="$SHORT_SHA.$_REVISION"',
]
- name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder'
entrypoint: 'npm'
args: ['run', 'prerelease:deps', '--workspaces']
- name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder'
entrypoint: 'npm'
args:
['publish', '--tag=head', '--dry-run', '--workspace=@google/gemini-cli']
options:
defaultLogsBucketBehavior: REGIONAL_USER_OWNED_BUCKET

View File

@ -1,150 +0,0 @@
steps:
# Step 1: Install root dependencies (includes workspaces)
- name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder'
id: 'Install Dependencies'
entrypoint: 'npm'
args: ['install']
# Step 2: Update version in root package.json
- name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder'
id: 'Set version in workspace root'
entrypoint: 'bash'
args:
- -c # Use bash -c to allow for command substitution and string manipulation
- |
current_version=$(npm pkg get version | sed 's/"//g')
if [ "$_OFFICIAL_RELEASE" = "true" ]; then
new_version="$current_version"
else
new_version="${current_version}-rc.$_REVISION"
fi
npm pkg set "version=${new_version}"
echo "Set root package.json version to: ${new_version}"
# Step 3: Binds the package versions to the version in the repo root's package.json
- name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder'
id: 'Bind package versions to workspace root'
entrypoint: 'npm'
args: ['run', 'prerelease:dev'] # This will run prerelease:version and prerelease:deps
# Step 4: Authenticate for Docker (so we can push images to the artifact registry)
- name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder'
id: 'Authenticate docker'
entrypoint: 'npm'
args: ['run', 'auth']
# Step 5: Build workspace packages
- name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder'
id: 'Build packages'
entrypoint: 'npm'
args: ['run', 'build:packages']
# Step 6: Prepare CLI package.json for publishing
- name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder'
id: 'Prepare @google/gemini-cli and @google/gemini-cli-core packages'
entrypoint: 'npm'
args: ['run', 'prepare:packages']
env:
- 'GEMINI_SANDBOX=$_CONTAINER_TOOL'
- 'SANDBOX_IMAGE_REGISTRY=$_SANDBOX_IMAGE_REGISTRY'
- 'SANDBOX_IMAGE_NAME=$_SANDBOX_IMAGE_NAME'
# Step 7: Build sandbox container image
- name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder'
id: 'Build sandbox Docker image'
entrypoint: 'npm'
args: ['run', 'build:sandbox:fast']
env:
- 'GEMINI_SANDBOX=$_CONTAINER_TOOL'
- 'SANDBOX_IMAGE_REGISTRY=$_SANDBOX_IMAGE_REGISTRY'
- 'SANDBOX_IMAGE_NAME=$_SANDBOX_IMAGE_NAME'
# Step 8: Publish sandbox container image
- name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder'
id: 'Publish sandbox Docker image'
entrypoint: 'npm'
args: ['run', 'publish:sandbox']
env:
- 'GEMINI_SANDBOX=$_CONTAINER_TOOL'
- 'SANDBOX_IMAGE_REGISTRY=$_SANDBOX_IMAGE_REGISTRY'
- 'SANDBOX_IMAGE_NAME=$_SANDBOX_IMAGE_NAME'
# Pre-Step 9: authenticate to our intermediate npm registry
# NOTE: when running locally, run this instead (from the `packages/core` directory):
# - `npm login --registry https://wombat-dressing-room.appspot.com`
# - use a 24hr token
- name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder'
id: 'Setup @google/gemini-cli-core auth token for publishing'
entrypoint: 'bash'
args:
- -c
- |
echo "//wombat-dressing-room.appspot.com/:_authToken=$$CORE_PACKAGE_PUBLISH_TOKEN" > $$HOME/.npmrc
secretEnv: ['CORE_PACKAGE_PUBLISH_TOKEN']
# Step 9: Publish @google/gemini-cli-core to NPM
- name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder'
id: 'Publish @google/gemini-cli-core package'
entrypoint: 'bash'
args:
- -c
- |
if [ "$_OFFICIAL_RELEASE" = "true" ]; then
npm publish --workspace=@google/gemini-cli-core --tag=latest
else
npm publish --workspace=@google/gemini-cli-core --tag=rc
fi
env:
- 'GEMINI_SANDBOX=$_CONTAINER_TOOL'
- 'SANDBOX_IMAGE_REGISTRY=$_SANDBOX_IMAGE_REGISTRY'
- 'SANDBOX_IMAGE_NAME=$_SANDBOX_IMAGE_NAME'
# Pre-Step 10: authenticate to our intermediate npm registry
# NOTE: when running locally, run this instead (from the `packages/cli` directory)
# - `npm login --registry https://wombat-dressing-room.appspot.com`
# - use a 24hr token
- name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder'
id: 'Setup @google/gemini-cli auth token for publishing'
entrypoint: 'bash'
args:
- -c
- |
echo "//wombat-dressing-room.appspot.com/:_authToken=$$CLI_PACKAGE_PUBLISH_TOKEN" > $$HOME/.npmrc
secretEnv: ['CLI_PACKAGE_PUBLISH_TOKEN']
# Step 10: Publish @google/gemini-cli to NPM
- name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder'
id: 'Publish @google/gemini-cli package'
entrypoint: 'bash'
args:
- -c
- |
if [ "$_OFFICIAL_RELEASE" = "true" ]; then
npm publish --workspace=@google/gemini-cli --tag=latest
else
npm publish --workspace=@google/gemini-cli --tag=rc
fi
env:
- 'GEMINI_SANDBOX=$_CONTAINER_TOOL'
- 'SANDBOX_IMAGE_REGISTRY=$_SANDBOX_IMAGE_REGISTRY'
- 'SANDBOX_IMAGE_NAME=$_SANDBOX_IMAGE_NAME'
options:
defaultLogsBucketBehavior: REGIONAL_USER_OWNED_BUCKET
dynamicSubstitutions: true
availableSecrets:
secretManager:
- versionName: ${_CLI_PACKAGE_WOMBAT_TOKEN_RESOURCE_NAME}
env: 'CLI_PACKAGE_PUBLISH_TOKEN'
- versionName: ${_CORE_PACKAGE_WOMBAT_TOKEN_RESOURCE_NAME}
env: 'CORE_PACKAGE_PUBLISH_TOKEN'
substitutions:
_REVISION: '0'
_OFFICIAL_RELEASE: 'false'
_CONTAINER_TOOL: 'docker'
_SANDBOX_IMAGE_REGISTRY: ''
_SANDBOX_IMAGE_NAME: ''
_CLI_PACKAGE_WOMBAT_TOKEN_RESOURCE_NAME: ''
_CORE_PACKAGE_WOMBAT_TOKEN_RESOURCE_NAME: ''

View File

@ -1,14 +1,14 @@
name: Release
on:
push:
tags:
- 'v*.*.*'
schedule:
# Runs every day at midnight UTC for the nightly release.
- cron: '0 0 * * *'
workflow_dispatch:
inputs:
version:
description: 'The version to release (e.g., v0.1.11). Required for manual patch releases.'
required: true
required: false # Not required for scheduled runs
type: string
ref:
description: 'The branch or ref to release from.'
@ -20,24 +20,54 @@ on:
required: true
type: boolean
default: true
create_nightly_release:
description: 'Simulate a scheduled nightly release. If true, the version input is ignored.'
required: false
type: boolean
default: false
force_skip_tests:
description: 'If true, skip the "Run Tests" step. This is only applicable for nightly releases.'
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 code
uses: actions/checkout@v4
with:
# For manual runs, checkout the specified ref (e.g., main). For tag pushes, checkout the tag itself.
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.ref }}
ref: ${{ github.sha }}
fetch-depth: 0
- name: Set booleans for simplified logic
id: vars
run: |
is_nightly="false"
if [[ "${{ github.event_name }}" == "schedule" || "${{ github.event.inputs.create_nightly_release }}" == "true" ]]; then
is_nightly="true"
fi
echo "is_nightly=${is_nightly}" >> $GITHUB_OUTPUT
is_dry_run="false"
if [[ "${{ github.event.inputs.dry_run }}" == "true" ]]; then
is_dry_run="true"
fi
echo "is_dry_run=${is_dry_run}" >> $GITHUB_OUTPUT
- name: Setup Node.js
uses: actions/setup-node@v4
with:
@ -50,39 +80,27 @@ jobs:
- name: Get the version
id: version
run: |
echo "Workflow triggered by: ${{ github.event_name }}"
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
echo "Input ref: ${{ inputs.ref }}"
echo "Input version: ${{ inputs.version }}"
RELEASE_TAG=${{ inputs.version }}
else
echo "Triggering ref: ${{ github.ref }}"
RELEASE_TAG=${GITHUB_REF_NAME}
fi
VERSION_JSON=$(node scripts/get-release-version.js)
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
env:
IS_NIGHTLY: ${{ steps.vars.outputs.is_nightly }}
MANUAL_VERSION: ${{ inputs.version }}
echo "---"
echo "Initial RELEASE_TAG: ${RELEASE_TAG}"
- name: Run Tests
if: github.event.inputs.force_skip_tests != 'true'
run: |
npm run preflight
npm run test:integration:sandbox:none
npm run test:integration:sandbox:docker
env:
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
# Validate that the tag starts with 'v' and follows semver
if [[ ! "$RELEASE_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$ ]]; then
echo "Error: Version must be in the format vX.Y.Z, vX.Y.Z-prerelease, or vX.Y.Z+buildmeta"
exit 1
fi
RELEASE_VERSION="${RELEASE_TAG#v}"
if [[ $RELEASE_VERSION == *-* ]]; then
NPM_TAG=$(echo $RELEASE_VERSION | cut -d'-' -f2 | cut -d'.' -f1)
else
NPM_TAG="latest"
fi
echo "Finalized RELEASE_VERSION: ${RELEASE_VERSION}"
echo "Finalized NPM_TAG: ${NPM_TAG}"
echo "---"
echo "RELEASE_TAG=${RELEASE_TAG}" >> $GITHUB_OUTPUT
echo "RELEASE_VERSION=${RELEASE_VERSION}" >> $GITHUB_OUTPUT
echo "NPM_TAG=${NPM_TAG}" >> $GITHUB_OUTPUT
- name: Configure Git User
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Create and switch to a release branch
id: release_branch
@ -95,13 +113,16 @@ jobs:
run: |
npm run release:version ${{ steps.version.outputs.RELEASE_VERSION }}
- name: Commit package versions
- name: Commit and Conditionally Push package versions
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add package.json package-lock.json packages/*/package.json
git commit -m "chore(release): ${{ steps.version.outputs.RELEASE_TAG }}"
git push --set-upstream origin ${{ steps.release_branch.outputs.BRANCH_NAME }} --follow-tags
if [[ "${{ steps.vars.outputs.is_dry_run }}" == "false" ]]; then
echo "Pushing release branch to remote..."
git push --set-upstream origin ${{ steps.release_branch.outputs.BRANCH_NAME }} --follow-tags
else
echo "Dry run enabled. Skipping push."
fi
- name: Build and Prepare Packages
run: |
@ -116,20 +137,21 @@ jobs:
scope: '@google'
- name: Publish @google/gemini-cli-core
run: npm publish --workspace=@google/gemini-cli-core --tag=${{ steps.version.outputs.NPM_TAG }} ${{ inputs.dry_run && '--dry-run' || '' }}
run: npm publish --workspace=@google/gemini-cli-core --tag=${{ steps.version.outputs.NPM_TAG }} ${{ steps.vars.outputs.is_dry_run == 'true' && '--dry-run' || '' }}
env:
NODE_AUTH_TOKEN: ${{ secrets.WOMBAT_TOKEN_CORE }}
- name: Install latest core package
if: steps.vars.outputs.is_dry_run == 'false'
run: npm install @google/gemini-cli-core@${{ steps.version.outputs.NPM_TAG }} --workspace=@google/gemini-cli --save-exact
- name: Publish @google/gemini-cli
run: npm publish --workspace=@google/gemini-cli --tag=${{ steps.version.outputs.NPM_TAG }} ${{ inputs.dry_run && '--dry-run' || '' }}
run: npm publish --workspace=@google/gemini-cli --tag=${{ steps.version.outputs.NPM_TAG }} ${{ steps.vars.outputs.is_dry_run == 'true' && '--dry-run' || '' }}
env:
NODE_AUTH_TOKEN: ${{ secrets.WOMBAT_TOKEN_CLI }}
- name: Create GitHub Release and Tag
if: '!inputs.dry_run'
if: ${{ steps.vars.outputs.is_dry_run == 'false' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_BRANCH: ${{ steps.release_branch.outputs.BRANCH_NAME }}
@ -139,3 +161,13 @@ jobs:
--target "$RELEASE_BRANCH" \
--title "Release ${{ steps.version.outputs.RELEASE_TAG }}" \
--generate-notes
- name: Create Issue on Failure
if: failure()
run: |
gh issue create \
--title "Release Failed for ${{ steps.version.outputs.RELEASE_TAG || 'N/A' }} on $(date +'%Y-%m-%d')" \
--body "The release workflow failed. See the full run for details: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
--label "type: bug,release-failure"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -1,74 +0,0 @@
name: Scheduled Nightly Release
on:
schedule:
# Runs every day at midnight UTC.
- cron: '0 0 * * *'
workflow_dispatch:
jobs:
nightly-release:
runs-on: ubuntu-latest
permissions:
contents: write
issues: write
steps:
- name: Checkout main branch
uses: actions/checkout@v4
with:
ref: 'main'
fetch-depth: 0 # Fetch all history for git tags
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install Dependencies
run: npm ci
- name: Run Preflight Checks
run: npm run preflight
- name: Run Integration Tests (without Docker)
uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08
env:
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
with:
timeout_minutes: 10
max_attempts: 3
retry_wait_seconds: 30
command: npm run test:integration:sandbox:none
- name: Run Integration Tests (with Docker)
uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08
env:
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
with:
timeout_minutes: 10
max_attempts: 3
retry_wait_seconds: 30
command: npm run test:integration:sandbox:docker
- name: Configure Git User
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Create and Push Nightly Tag
if: success()
run: npm run tag:release:nightly
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create Issue on Failure
if: failure()
run: |
gh issue create \
--title "Nightly Release Failed on $(date +'%Y-%m-%d')" \
--body "The scheduled nightly release workflow failed. See the full run for details: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
--label "type: bug,nightly-failure"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -18,21 +18,18 @@ This package is not bundled. When it is published, it is published as a standard
This project follows a structured release process to ensure that all packages are versioned and published correctly. The process is designed to be as automated as possible.
## Current Theory
For most all changes, simply patching the minor version is acceptable. We can and should release frequently; the more often we release the easier it is to tell what change broke something. Developers are encouraged to push a release as described below after their branch merges. I also think I'm open to doing the release publishing steps as a part of an existing PR, though this could have more churn if others are also releasing and version numbers change frequently.
## How To Release
Releasing a new version is as simple as creating and pushing a new Git tag. The tag must follow semantic versioning and be prefixed with `v`, for example `v0.2.0` or `v1.0.0-alpha.1`. From the branch you want to release from, run the following commands:
Releases are managed through the [release.yml](https://github.com/google-gemini/gemini-cli/actions/workflows/release.yml) GitHub Actions workflow. To perform a manual release for a patch or hotfix:
```bash
# Create the new tag (e.g., v0.2.0)
# Optional use git log to find an older commit sha to tag
git tag v0.2.0 <optional sha>
# Push the tag to the remote repository to trigger the release
git push origin v0.2.0
```
1. Navigate to the **Actions** tab of the repository.
2. Select the **Release** 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.2.1`).
- **Ref**: The branch or commit SHA to release from (defaults to `main`).
- **Dry Run**: Leave as `true` to test the workflow without publishing, or set to `false` to perform a live release.
5. Click **Run workflow**.
## Nightly Releases
@ -40,14 +37,14 @@ In addition to manual releases, this project has an automated nightly release pr
### Process
Every night at midnight UTC, the [Scheduled Nightly Release workflow](https://github.com/google-gemini/gemini-cli/actions/workflows/scheduled-nightly-release.yml) runs automatically. It performs the following steps:
Every night at midnight UTC, the [Release workflow](https://github.com/google-gemini/gemini-cli/actions/workflows/release.yml) runs automatically on a schedule. It performs the following steps:
1. Checks out the latest code from the `main` branch.
2. Installs all dependencies.
3. Runs the full suite of `preflight` checks (linting, type-checking, etc.).
4. Runs the integration tests, both with and without Docker. The tests are automatically retried up to three times to handle any flakiness.
5. If all checks and tests succeed, it runs the `npm run tag:release:nightly` script. This script creates and pushes a new annotated Git tag with the format `v<version>+nightly.<ddmmyy>.<sha>`.
6. Pushing this tag triggers the main [release workflow](https://github.com/google-gemini/gemini-cli/actions/workflows/release.yml), which publishes the package to npm with the `nightly` tag.
3. Runs the full suite of `preflight` checks and integration tests.
4. If all tests succeed, it calculates the next nightly version number (e.g., `v0.2.1-nightly.20230101`).
5. It then builds and publishes the packages to npm with the `nightly` dist-tag.
6. Finally, it creates a GitHub Release for the nightly version.
### Failure Handling
@ -61,32 +58,11 @@ To install the latest nightly build, use the `@nightly` tag:
npm install -g @google/gemini-cli@nightly
```
The high-level process is:
1. Ensure your local branch `main` or `release-xxx` if hotfixing a previous release is up-to-date with the remote repository.
1. Decide on the new version number based on the changes since the last release.
1. _Optionally_ `git log` to find the sha of the commit you want to push if not latest
1. _Optionally_ run [integration tests](integration-tests.md) locally to increase confidence in the release.
1. Create a new Git tag with the desired version number.
1. Push the tag to the `google-gemini/gemini-cli` repository.
1. The push will trigger the release workflow, which automates the rest of the process.
1. Once the workflow is complete, it will have created a `release/vX.Y.Z` branch with the version bumps. Create a pull request from this branch to merge the version changes back into `main`.
Pushing a new tag will trigger the [release workflow](https://github.com/google-gemini/gemini-cli/actions/workflows/release.yml), which will automatically:
- Build and publish the packages to the npm registry.
- Create a new GitHub release with generated release notes.
- Create a new branch `release/vX.Y.Z` containing the version bump in the `package.json` files.
We also run a Google cloud build called [release-docker.yml](../.gcp/release-docker.yaml). 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.
### 2. Monitor the Release Workflow
You can monitor the progress of the release workflow in the [GitHub Actions tab](https://github.com/google-gemini/gemini-cli/actions/workflows/release.yml). If the workflow fails, you will need to investigate the cause of the failure, fix the issue, and then create a new tag to trigger a new release.
### After the Release
After the workflow has successfully completed, you should:
After the workflow has successfully completed, you can monitor its progress in the [GitHub Actions tab](https://github.com/google-gemini/gemini-cli/actions/workflows/release.yml). Once complete, you should:
1. Go to the [pull requests page](https://github.com/google-gemini/gemini-cli/pulls) of the repository.
2. Create a new pull request from the `release/vX.Y.Z` branch to `main`.

View File

@ -18,6 +18,8 @@ test('should be able to list a directory', async (t) => {
const prompt = `Can you list the files in the current directory`;
const result = await rig.run(prompt);
assert.ok(result.includes('file1.txt'));
assert.ok(result.includes('subdir'));
const lines = result.split('\n').filter((line) => line.trim() !== '');
assert.equal(lines.length, 2);
assert.ok(lines.includes('file1.txt'));
assert.ok(lines.includes('subdir'));
});

62
package-lock.json generated
View File

@ -35,6 +35,7 @@
"prettier": "^3.5.3",
"react-devtools-core": "^4.28.5",
"typescript-eslint": "^8.30.1",
"vitest": "^3.2.4",
"yargs": "^17.7.2"
},
"engines": {
@ -1879,8 +1880,7 @@
"optional": true,
"os": [
"android"
],
"peer": true
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.44.0",
@ -1894,8 +1894,7 @@
"optional": true,
"os": [
"android"
],
"peer": true
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.44.0",
@ -1909,8 +1908,7 @@
"optional": true,
"os": [
"darwin"
],
"peer": true
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.44.0",
@ -1924,8 +1922,7 @@
"optional": true,
"os": [
"darwin"
],
"peer": true
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.44.0",
@ -1939,8 +1936,7 @@
"optional": true,
"os": [
"freebsd"
],
"peer": true
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.44.0",
@ -1954,8 +1950,7 @@
"optional": true,
"os": [
"freebsd"
],
"peer": true
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.44.0",
@ -1969,8 +1964,7 @@
"optional": true,
"os": [
"linux"
],
"peer": true
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.44.0",
@ -1984,8 +1978,7 @@
"optional": true,
"os": [
"linux"
],
"peer": true
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.44.0",
@ -1999,8 +1992,7 @@
"optional": true,
"os": [
"linux"
],
"peer": true
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.44.0",
@ -2014,8 +2006,7 @@
"optional": true,
"os": [
"linux"
],
"peer": true
]
},
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
"version": "4.44.0",
@ -2029,8 +2020,7 @@
"optional": true,
"os": [
"linux"
],
"peer": true
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.44.0",
@ -2044,8 +2034,7 @@
"optional": true,
"os": [
"linux"
],
"peer": true
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.44.0",
@ -2059,8 +2048,7 @@
"optional": true,
"os": [
"linux"
],
"peer": true
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.44.0",
@ -2074,8 +2062,7 @@
"optional": true,
"os": [
"linux"
],
"peer": true
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.44.0",
@ -2089,8 +2076,7 @@
"optional": true,
"os": [
"linux"
],
"peer": true
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.44.0",
@ -2104,8 +2090,7 @@
"optional": true,
"os": [
"linux"
],
"peer": true
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.44.0",
@ -2119,8 +2104,7 @@
"optional": true,
"os": [
"linux"
],
"peer": true
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.44.0",
@ -2134,8 +2118,7 @@
"optional": true,
"os": [
"win32"
],
"peer": true
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.44.0",
@ -2149,8 +2132,7 @@
"optional": true,
"os": [
"win32"
],
"peer": true
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.44.0",
@ -2164,8 +2146,7 @@
"optional": true,
"os": [
"win32"
],
"peer": true
]
},
"node_modules/@rtsao/scc": {
"version": "1.1.0",
@ -5398,7 +5379,6 @@
"os": [
"darwin"
],
"peer": true,
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}

View File

@ -24,12 +24,13 @@
"clean": "node scripts/clean.js",
"prepare": "npm run bundle",
"test": "npm run test --workspaces",
"test:ci": "npm run test:ci --workspaces --if-present",
"test:ci": "npm run test:ci --workspaces --if-present && npm run test:scripts",
"test:e2e": "npm run test:integration:sandbox:none -- --verbose --keep-output",
"test:integration:all": "npm run test:integration:sandbox:none && npm run test:integration:sandbox:docker && npm run test:integration:sandbox:podman",
"test:integration:sandbox:none": "GEMINI_SANDBOX=false node integration-tests/run-tests.js",
"test:integration:sandbox:docker": "GEMINI_SANDBOX=docker node integration-tests/run-tests.js",
"test:integration:sandbox:podman": "GEMINI_SANDBOX=podman node integration-tests/run-tests.js",
"test:scripts": "vitest --config ./scripts/tests/vitest.config.ts",
"start": "node scripts/start.js",
"debug": "cross-env DEBUG=1 node --inspect-brk scripts/start.js",
"lint:fix": "eslint . --fix && eslint integration-tests --fix",
@ -85,7 +86,7 @@
"prettier": "^3.5.3",
"react-devtools-core": "^4.28.5",
"typescript-eslint": "^8.30.1",
"vitest": "^3.2.4",
"yargs": "^17.7.2"
},
"dependencies": {}
}
}

View File

@ -0,0 +1,89 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { execSync } from 'child_process';
import fs from 'fs';
import path from 'path';
function getPackageVersion() {
const packageJsonPath = path.resolve(process.cwd(), 'package.json');
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
return packageJson.version;
}
function getShortSha() {
return execSync('git rev-parse --short HEAD').toString().trim();
}
export function getNightlyTagName() {
const version = getPackageVersion();
const now = new Date();
const year = now.getUTCFullYear().toString().slice(-2);
const month = (now.getUTCMonth() + 1).toString().padStart(2, '0');
const day = now.getUTCDate().toString().padStart(2, '0');
const date = `${year}${month}${day}`;
const sha = getShortSha();
return `v${version}-nightly.${date}.${sha}`;
}
export function getReleaseVersion() {
const isNightly = process.env.IS_NIGHTLY === 'true';
const manualVersion = process.env.MANUAL_VERSION;
let releaseTag;
if (isNightly) {
console.error('Calculating next nightly version...');
releaseTag = getNightlyTagName();
} else if (manualVersion) {
console.error(`Using manual version: ${manualVersion}`);
releaseTag = manualVersion;
} else {
throw new Error(
'Error: No version specified and this is not a nightly release.',
);
}
if (!releaseTag) {
throw new Error('Error: Version could not be determined.');
}
if (!releaseTag.startsWith('v')) {
console.error("Version is missing 'v' prefix. Prepending it.");
releaseTag = `v${releaseTag}`;
}
if (releaseTag.includes('+')) {
throw new Error(
'Error: Versions with build metadata (+) are not supported for releases. Please use a pre-release version (e.g., v1.2.3-alpha.4) instead.',
);
}
if (!releaseTag.match(/^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?$/)) {
throw new Error(
'Error: Version must be in the format vX.Y.Z or vX.Y.Z-prerelease',
);
}
const releaseVersion = releaseTag.substring(1);
let npmTag = 'latest';
if (releaseVersion.includes('-')) {
npmTag = releaseVersion.split('-')[1].split('.')[0];
}
return { releaseTag, releaseVersion, npmTag };
}
if (process.argv[1] === new URL(import.meta.url).pathname) {
try {
const versions = getReleaseVersion();
console.log(JSON.stringify(versions));
} catch (error) {
console.error(error.message);
process.exit(1);
}
}

View File

@ -1,58 +0,0 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { execSync } from 'child_process';
import { readFileSync } from 'fs';
import path from 'path';
function getVersion() {
const packageJsonPath = path.resolve(process.cwd(), 'package.json');
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
return packageJson.version;
}
function getShortSha() {
return execSync('git rev-parse --short HEAD').toString().trim();
}
function getNightlyTagName() {
const version = getVersion();
const now = new Date();
const year = now.getUTCFullYear().toString().slice(-2);
const month = (now.getUTCMonth() + 1).toString().padStart(2, '0');
const day = now.getUTCDate().toString().padStart(2, '0');
const date = `${year}${month}${day}`;
const sha = getShortSha();
return `v${version}-nightly.${date}.${sha}`;
}
function createAndPushTag(tagName, isSigned) {
const command = isSigned
? `git tag -s -a ${tagName} -m ''`
: `git tag ${tagName}`;
try {
console.log(`Executing: ${command}`);
execSync(command, { stdio: 'inherit' });
console.log(`Successfully created tag: ${tagName}`);
console.log(`Pushing tag to origin...`);
execSync(`git push origin ${tagName}`, { stdio: 'inherit' });
console.log(`Successfully pushed tag: ${tagName}`);
} catch (error) {
console.error(`Failed to create or push tag: ${tagName}`);
console.error(error);
process.exit(1);
}
}
const tagName = getNightlyTagName();
// In GitHub Actions, the CI variable is set to true.
// We will create a signed commit if not in a CI environment.
const shouldSign = !process.env.CI;
createAndPushTag(tagName, shouldSign);

View File

@ -0,0 +1,108 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
import { getReleaseVersion } from '../get-release-version';
import { execSync } from 'child_process';
import * as fs from 'fs';
vi.mock('child_process', () => ({
execSync: vi.fn(),
}));
vi.mock('fs', async (importOriginal) => {
const mod = await importOriginal();
return {
...mod,
readFileSync: vi.fn(),
};
});
describe('getReleaseVersion', () => {
const originalEnv = { ...process.env };
beforeEach(() => {
vi.resetModules();
process.env = { ...originalEnv };
vi.useFakeTimers();
});
afterEach(() => {
process.env = originalEnv;
vi.clearAllMocks();
vi.useRealTimers();
});
it('should calculate nightly version when IS_NIGHTLY is true', () => {
process.env.IS_NIGHTLY = 'true';
const knownDate = new Date('2025-07-20T10:00:00.000Z');
vi.setSystemTime(knownDate);
vi.mocked(fs.readFileSync).mockReturnValue(
JSON.stringify({ version: '0.1.0' }),
);
vi.mocked(execSync).mockReturnValue('abcdef');
const { releaseTag, releaseVersion, npmTag } = getReleaseVersion();
expect(releaseTag).toBe('v0.1.9-nightly.250720.abcdef');
expect(releaseVersion).toBe('0.1.9-nightly.250720.abcdef');
expect(npmTag).toBe('nightly');
});
it('should use manual version when provided', () => {
process.env.MANUAL_VERSION = '1.2.3';
const { releaseTag, releaseVersion, npmTag } = getReleaseVersion();
expect(releaseTag).toBe('v1.2.3');
expect(releaseVersion).toBe('1.2.3');
expect(npmTag).toBe('latest');
});
it('should prepend v to manual version if missing', () => {
process.env.MANUAL_VERSION = '1.2.3';
const { releaseTag } = getReleaseVersion();
expect(releaseTag).toBe('v1.2.3');
});
it('should handle pre-release versions correctly', () => {
process.env.MANUAL_VERSION = 'v1.2.3-beta.1';
const { releaseTag, releaseVersion, npmTag } = getReleaseVersion();
expect(releaseTag).toBe('v1.2.3-beta.1');
expect(releaseVersion).toBe('1.2.3-beta.1');
expect(npmTag).toBe('beta');
});
it('should throw an error for invalid version format', () => {
process.env.MANUAL_VERSION = '1.2';
expect(() => getReleaseVersion()).toThrow(
'Error: Version must be in the format vX.Y.Z or vX.Y.Z-prerelease',
);
});
it('should throw an error if no version is provided for non-nightly release', () => {
expect(() => getReleaseVersion()).toThrow(
'Error: No version specified and this is not a nightly release.',
);
});
it('should throw an error for versions with build metadata', () => {
process.env.MANUAL_VERSION = 'v1.2.3+build456';
expect(() => getReleaseVersion()).toThrow(
'Error: Versions with build metadata (+) are not supported for releases.',
);
});
});
describe('get-release-version script', () => {
it('should print version JSON to stdout when executed directly', () => {
const expectedJson = {
releaseTag: 'v0.1.0-nightly.20250705',
releaseVersion: '0.1.0-nightly.20250705',
npmTag: 'nightly',
};
execSync.mockReturnValue(JSON.stringify(expectedJson));
const result = execSync('node scripts/get-release-version.js').toString();
expect(JSON.parse(result)).toEqual(expectedJson);
});
});

View File

@ -0,0 +1,12 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { vi } from 'vitest';
vi.mock('fs', () => ({
...vi.importActual('fs'),
appendFileSync: vi.fn(),
}));

View File

@ -0,0 +1,20 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'node',
include: ['scripts/tests/**/*.test.js'],
setupFiles: ['scripts/tests/test-setup.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'lcov'],
},
},
});