From dc94a03f39c712a0bb9b4ab0de7bac0e29c2b12e Mon Sep 17 00:00:00 2001 From: Brandon Keiji Date: Thu, 29 May 2025 21:01:44 +0000 Subject: [PATCH] feat: publish root Dockerfile to our image registry (#599) --- .gcp/dogfood.yaml | 25 ++++++++--- Dockerfile | 2 + package.json | 11 ++++- packages/cli/Dockerfile.sandbox | 28 ------------- packages/cli/package.json | 6 +-- scripts/build_sandbox.sh | 2 +- scripts/prepare-cli-packagejson.js | 66 ++++++++++++++++++++++++++++++ scripts/publish-sandbox.sh | 41 +++++++++++++++++++ 8 files changed, 140 insertions(+), 41 deletions(-) delete mode 100644 packages/cli/Dockerfile.sandbox create mode 100644 scripts/prepare-cli-packagejson.js create mode 100755 scripts/publish-sandbox.sh diff --git a/.gcp/dogfood.yaml b/.gcp/dogfood.yaml index 911fdf46..01eddded 100644 --- a/.gcp/dogfood.yaml +++ b/.gcp/dogfood.yaml @@ -1,8 +1,10 @@ steps: + # Step 1: Install root dependencies (includes workspaces) - name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder' entrypoint: 'npm' args: ['install'] + # Step 2: Update version with build suffix - name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder' entrypoint: 'npm' args: @@ -14,22 +16,33 @@ steps: '--suffix="$SHORT_SHA.$_REVISION"', ] + # Step 3: Bind dependencies to the new versions - name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder' entrypoint: 'npm' args: ['run', 'prerelease:deps', '--workspaces'] - # A bit of a hack to get the .npmrc into the Dockerfile.sandbox. Should probably streamline this. - - name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder' - entrypoint: 'cp' - args: ['/workspace/.npmrc', '/builder/home/.npmrc'] - + # Step 4: Authenticate for Docker and NPM - name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder' entrypoint: 'npm' args: ['run', 'auth'] + # Step 5: Run the master release script - name: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-builder' entrypoint: 'npm' - args: ['publish', '--tag=head', '--workspace=@gemini-code/cli'] + args: ['run', 'publish:release'] + env: + - 'GEMINI_SANDBOX=$_CONTAINER_TOOL' + - 'SANDBOX_IMAGE_REGISTRY=$_SANDBOX_IMAGE_REGISTRY' + - 'SANDBOX_IMAGE_NAME=$_SANDBOX_IMAGE_NAME' + - 'NPM_PUBLISH_TAG=$_NPM_PUBLISH_TAG' options: defaultLogsBucketBehavior: REGIONAL_USER_OWNED_BUCKET + dynamicSubstitutions: true + +substitutions: + _REVISION: '0' + _SANDBOX_IMAGE_REGISTRY: 'us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers' + _SANDBOX_IMAGE_NAME: 'gemini-cli-sandbox' + _NPM_PUBLISH_TAG: 'head' + _CONTAINER_TOOL: 'docker' diff --git a/Dockerfile b/Dockerfile index 3cabc24b..695e4691 100644 --- a/Dockerfile +++ b/Dockerfile @@ -36,3 +36,5 @@ COPY packages/server/dist/gemini-code-server-*.tgz /usr/local/share/npm-global/g RUN npm install -g /usr/local/share/npm-global/gemini-code-cli.tgz /usr/local/share/npm-global/gemini-code-server.tgz \ && npm cache clean --force \ && rm -f /usr/local/share/npm-global/gemini-code-{cli,server}.tgz + +ENTRYPOINT ["gemini"] diff --git a/package.json b/package.json index b84a4b77..30f6b988 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,16 @@ "auth:docker": "gcloud auth configure-docker us-west1-docker.pkg.dev", "auth": "npm run auth:npm && npm run auth:docker", "prerelease:dev": "npm run prerelease:version --workspaces && npm run prerelease:deps --workspaces", - "bundle": "npm run generate && node_modules/.bin/esbuild packages/cli/index.ts --bundle --outfile=bundle/gemini.js --platform=node --format=esm --banner:js=\"import { createRequire } from 'module'; const require = createRequire(import.meta.url); globalThis.__filename = require('url').fileURLToPath(import.meta.url); globalThis.__dirname = require('path').dirname(globalThis.__filename);\" && bash scripts/copy_bundle_assets.sh" + "bundle": "npm run generate && node_modules/.bin/esbuild packages/cli/index.ts --bundle --outfile=bundle/gemini.js --platform=node --format=esm --banner:js=\"import { createRequire } from 'module'; const require = createRequire(import.meta.url); globalThis.__filename = require('url').fileURLToPath(import.meta.url); globalThis.__dirname = require('path').dirname(globalThis.__filename);\" && bash scripts/copy_bundle_assets.sh", + "build:cli": "npm run build --workspace packages/cli", + "build:server": "npm run build --workspace packages/server", + "build:packages": "npm run build:server && npm run build:cli", + "build:docker": "scripts/build_sandbox.sh -s", + "tag:docker": "docker tag gemini-code-sandbox:latest ${SANDBOX_IMAGE_REGISTRY:?SANDBOX_IMAGE_REGISTRY not set}/${SANDBOX_IMAGE_NAME:?SANDBOX_IMAGE_NAME not set}:$npm_package_version", + "prepare:cli-packagejson": "node scripts/prepare-cli-packagejson.js", + "publish:sandbox": "scripts/publish-sandbox.sh", + "publish:npm": "npm publish --workspace @gemini-code/cli ${NPM_PUBLISH_TAG:+--tag=$NPM_PUBLISH_TAG} ${NPM_DRY_RUN:+--dry-run}", + "publish:release": "npm run build:packages && npm run build:docker && npm run tag:docker && npm run prepare:cli-packagejson && npm run publish:sandbox && npm run publish:npm" }, "bin": { "gemini": "bundle/gemini.js" diff --git a/packages/cli/Dockerfile.sandbox b/packages/cli/Dockerfile.sandbox deleted file mode 100644 index 7250bbf3..00000000 --- a/packages/cli/Dockerfile.sandbox +++ /dev/null @@ -1,28 +0,0 @@ -FROM docker.io/library/node:20-slim - -ARG CLI_VERSION -ENV SANDBOX=${CLI_VERSION} - -# install minimal set of packages, then clean up -RUN apt-get update && apt-get install -y --no-install-recommends \ - man-db \ - curl \ - dnsutils \ - less \ - jq \ - bc \ - gh \ - git \ - unzip \ - rsync \ - ripgrep \ - procps \ - psmisc \ - lsof \ - socat \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* - -RUN --mount=type=secret,id=npmrc,dst=/root/.npmrc npm install -g @gemini-code/cli@${CLI_VERSION} --verbose - -ENTRYPOINT 'gemini' diff --git a/packages/cli/package.json b/packages/cli/package.json index 6ae1bd40..486ca50e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -7,11 +7,8 @@ "bin": { "gemini": "dist/index.js" }, - "image": "us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-cli", "scripts": { - "build:sandbox": "DOCKER_BUILDKIT=1 docker build --build-arg CLI_VERSION=$npm_package_version --no-cache --secret id=npmrc,src=$HOME/.npmrc -t us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-cli:$npm_package_version -f Dockerfile.sandbox .", "build": "../../scripts/build_package.sh", - "publish:sandbox": "docker push us-west1-docker.pkg.dev/gemini-code-dev/gemini-code-containers/gemini-code-cli:$npm_package_version", "clean": "rm -rf dist", "start": "node dist/index.js", "debug": "node --inspect-brk dist/index.js", @@ -23,8 +20,7 @@ "prerelease:version": "node ../../scripts/bind_package_version.js", "prerelease:deps": "node ../../scripts/bind_package_dependencies.js", "prepublishOnly": "npm publish --workspace=@gemini-code/server", - "prepack": "npm run build", - "postpublish": "npm run build:sandbox && npm run publish:sandbox" + "prepack": "npm run build" }, "files": [ "dist" diff --git a/scripts/build_sandbox.sh b/scripts/build_sandbox.sh index 58ad6d03..5997e201 100755 --- a/scripts/build_sandbox.sh +++ b/scripts/build_sandbox.sh @@ -26,7 +26,7 @@ fi CMD=$(scripts/sandbox_command.sh) echo "using $CMD for sandboxing" -IMAGE=gemini-code-sandbox +IMAGE=gemini-code-sandbox:latest DOCKERFILE=Dockerfile SKIP_NPM_INSTALL_BUILD=false diff --git a/scripts/prepare-cli-packagejson.js b/scripts/prepare-cli-packagejson.js new file mode 100644 index 00000000..8e0efff1 --- /dev/null +++ b/scripts/prepare-cli-packagejson.js @@ -0,0 +1,66 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +// ES module equivalent of __dirname +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const cliPackageJsonPath = path.resolve( + __dirname, + '../packages/cli/package.json', +); +const cliPackageJson = JSON.parse(fs.readFileSync(cliPackageJsonPath, 'utf8')); + +// Get version from root package.json (accessible via env var in npm scripts) +const version = process.env.npm_package_version; + +// Get Docker registry and image name directly from PUBLISH_ environment variables. +// These are expected to be set by the CI/build environment. +const dockerRegistry = process.env.SANDBOX_IMAGE_REGISTRY; +const dockerImageName = process.env.SANDBOX_IMAGE_NAME; + +if (!version || !dockerRegistry || !dockerImageName) { + console.error( + 'Error: Missing required environment variables. Need: ' + + 'npm_package_version, SANDBOX_IMAGE_REGISTRY, and SANDBOX_IMAGE_NAME.', + ); + console.error( + 'These should be passed from the CI environment (e.g., Cloud Build substitutions) ' + + 'to the npm publish:release script.', + ); + process.exit(1); +} + +const dockerImageUri = `${dockerRegistry}/${dockerImageName}:${version}`; + +// Add or update fields in cliPackageJson.config to store this information +if (!cliPackageJson.config) { + cliPackageJson.config = {}; +} +cliPackageJson.config.dockerImageUri = dockerImageUri; +cliPackageJson.config.dockerRegistry = dockerRegistry; +cliPackageJson.config.dockerImageName = dockerImageName; + +// Remove 'prepublishOnly' from scripts if it exists +if (cliPackageJson.scripts && cliPackageJson.scripts.prepublishOnly) { + delete cliPackageJson.scripts.prepublishOnly; + console.log('Removed prepublishOnly script from packages/cli/package.json'); +} + +fs.writeFileSync( + cliPackageJsonPath, + JSON.stringify(cliPackageJson, null, 2) + '\n', +); +console.log( + `Updated ${path.relative(process.cwd(), cliPackageJsonPath)} with Docker image details:`, +); +console.log(` URI: ${dockerImageUri}`); +console.log(` Registry: ${dockerRegistry}`); +console.log(` Image Name: ${dockerImageName}`); diff --git a/scripts/publish-sandbox.sh b/scripts/publish-sandbox.sh new file mode 100755 index 00000000..dfc16353 --- /dev/null +++ b/scripts/publish-sandbox.sh @@ -0,0 +1,41 @@ +#!/bin/bash +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -euo pipefail + +# Ensure required environment variables are set +if [ -z "${SANDBOX_IMAGE_REGISTRY}" ]; then + echo "Error: SANDBOX_IMAGE_REGISTRY environment variable is not set." >&2 + exit 1 +fi + +if [ -z "${SANDBOX_IMAGE_NAME}" ]; then + echo "Error: SANDBOX_IMAGE_NAME environment variable is not set." >&2 + exit 1 +fi + +if [ -z "${npm_package_version}" ]; then + echo "Error: npm_package_version environment variable is not set (should be run via npm)." >&2 + exit 1 +fi + +IMAGE_URI="${SANDBOX_IMAGE_REGISTRY}/${SANDBOX_IMAGE_NAME}:${npm_package_version}" + +if [ -n "${DOCKER_DRY_RUN:-}" ]; then + echo "DRY RUN: Would execute: docker push \"${IMAGE_URI}\"" +else + echo "Executing: docker push \"${IMAGE_URI}\"" + docker push "${IMAGE_URI}" +fi