Skip to content

Commit 496090c

Browse files
authored
fix(ci): prevent bad NPM releases and promote job crashes (google-gemini#28147)
1 parent 26769a5 commit 496090c

5 files changed

Lines changed: 65 additions & 19 deletions

File tree

.github/actions/publish-release/action.yml

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,29 @@ runs:
197197
run: |
198198
node ${{ github.workspace }}/scripts/prepare-npm-release.js
199199
200+
- name: '📦 Pack CLI for verification'
201+
if: "inputs.dry-run != 'true' && inputs.force-skip-tests != 'true'"
202+
working-directory: '${{ inputs.working-directory }}'
203+
shell: 'bash'
204+
run: |
205+
npm pack --workspace="${INPUTS_CLI_PACKAGE_NAME}"
206+
# We restore the package.json so that `npm ci` in verify-release doesn't fail due to deleted dependencies
207+
git checkout packages/cli/package.json
208+
env:
209+
INPUTS_CLI_PACKAGE_NAME: '${{ inputs.cli-package-name }}'
210+
211+
- name: '🔬 Verify NPM release by version'
212+
uses: './.github/actions/verify-release'
213+
if: "${{ inputs.dry-run != 'true' && inputs.force-skip-tests != 'true' }}"
214+
with:
215+
npm-package: './google-gemini-cli-${{ inputs.release-version }}.tgz'
216+
expected-version: '${{ inputs.release-version }}'
217+
working-directory: '${{ inputs.working-directory }}'
218+
gemini_api_key: '${{ inputs.gemini_api_key }}'
219+
github-token: '${{ inputs.github-token }}'
220+
npm-registry-url: '${{ inputs.npm-registry-url }}'
221+
npm-registry-scope: '${{ inputs.npm-registry-scope }}'
222+
200223
- name: 'Get CLI Token'
201224
uses: './.github/actions/npm-auth-token'
202225
id: 'cli-token'
@@ -213,12 +236,19 @@ runs:
213236
NODE_AUTH_TOKEN: '${{ steps.cli-token.outputs.auth-token }}'
214237
INPUTS_DRY_RUN: '${{ inputs.dry-run }}'
215238
INPUTS_CLI_PACKAGE_NAME: '${{ inputs.cli-package-name }}'
239+
INPUTS_RELEASE_VERSION: '${{ inputs.release-version }}'
216240
shell: 'bash'
217241
run: |
242+
if [ -f "google-gemini-cli-${INPUTS_RELEASE_VERSION}.tgz" ]; then
243+
PUBLISH_TARGET="google-gemini-cli-${INPUTS_RELEASE_VERSION}.tgz"
244+
else
245+
PUBLISH_TARGET="--workspace=${INPUTS_CLI_PACKAGE_NAME}"
246+
fi
247+
218248
npm publish \
219249
--ignore-scripts \
220250
--dry-run="${INPUTS_DRY_RUN}" \
221-
--workspace="${INPUTS_CLI_PACKAGE_NAME}" \
251+
${PUBLISH_TARGET} \
222252
--tag staging-tmp
223253
if [[ "${INPUTS_DRY_RUN}" == "false" ]]; then
224254
npm dist-tag rm ${INPUTS_CLI_PACKAGE_NAME} staging-tmp
@@ -252,18 +282,6 @@ runs:
252282
npm dist-tag rm ${INPUTS_A2A_PACKAGE_NAME} staging-tmp
253283
fi
254284
255-
- name: '🔬 Verify NPM release by version'
256-
uses: './.github/actions/verify-release'
257-
if: "${{ inputs.dry-run != 'true' && inputs.force-skip-tests != 'true' }}"
258-
with:
259-
npm-package: '${{ inputs.cli-package-name }}@${{ inputs.release-version }}'
260-
expected-version: '${{ inputs.release-version }}'
261-
working-directory: '${{ inputs.working-directory }}'
262-
gemini_api_key: '${{ inputs.gemini_api_key }}'
263-
github-token: '${{ inputs.github-token }}'
264-
npm-registry-url: '${{ inputs.npm-registry-url }}'
265-
npm-registry-scope: '${{ inputs.npm-registry-scope }}'
266-
267285
- name: '🏷️ Tag release'
268286
uses: './.github/actions/tag-npm-release'
269287
with:

.github/actions/verify-release/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ runs:
7474
shell: 'bash'
7575
working-directory: '${{ inputs.working-directory }}'
7676
run: |-
77-
gemini_version=$(npx --prefer-online "${INPUTS_NPM_PACKAGE}" --version)
77+
gemini_version=$(npx --yes --prefer-online "${INPUTS_NPM_PACKAGE}" --version)
7878
if [ "$gemini_version" != "${INPUTS_EXPECTED_VERSION}" ]; then
7979
echo "❌ NPX Run Version mismatch: Got $gemini_version from ${INPUTS_NPM_PACKAGE}, expected ${INPUTS_EXPECTED_VERSION}"
8080
exit 1

.github/workflows/release-promote.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,9 @@ jobs:
106106
echo "NIGHTLY_JSON: ${NIGHTLY_JSON}"
107107
echo "STABLE_VERSION=${STABLE_VERSION}" >> "${GITHUB_OUTPUT}"
108108
# shellcheck disable=SC1083
109-
echo "STABLE_SHA=$(git rev-parse "$(echo "${PREVIEW_JSON}" | jq -r .previousReleaseTag)"^{commit})" >> "${GITHUB_OUTPUT}"
109+
PREVIOUS_PREVIEW_TAG=$(echo "${PREVIEW_JSON}" | jq -r .previousReleaseTag)
110+
STABLE_SHA=$(git rev-parse "${PREVIOUS_PREVIEW_TAG}^{commit}")
111+
echo "STABLE_SHA=${STABLE_SHA}" >> "${GITHUB_OUTPUT}"
110112
echo "PREVIOUS_STABLE_TAG=$(echo "${STABLE_JSON}" | jq -r .previousReleaseTag)" >> "${GITHUB_OUTPUT}"
111113
echo "PREVIEW_VERSION=$(echo "${PREVIEW_JSON}" | jq -r .releaseVersion)" >> "${GITHUB_OUTPUT}"
112114
# shellcheck disable=SC1083

.github/workflows/release-rollback.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ jobs:
8282
ORIGIN_TAG: '${{ steps.origin_tag.outputs.ORIGIN_TAG }}'
8383
shell: 'bash'
8484
run: |
85-
echo "ORIGIN_HASH=$(git rev-parse "${ORIGIN_TAG}")" >> "$GITHUB_OUTPUT"
85+
ORIGIN_HASH=$(git rev-parse "${ORIGIN_TAG}")
86+
echo "ORIGIN_HASH=${ORIGIN_HASH}" >> "$GITHUB_OUTPUT"
8687
8788
- name: 'Change tag'
8889
if: "${{ github.event.inputs.rollback_destination != '' }}"

scripts/get-release-version.js

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,12 +159,37 @@ function detectRollbackAndGetBaseline({ args, npmDistTag } = {}) {
159159
// Sort by semver to get a list from highest to lowest
160160
matchingVersions.sort((a, b) => semver.rcompare(a, b));
161161

162-
// Find the highest non-deprecated version
162+
// Find the highest non-deprecated version with a git tag
163163
let highestExistingVersion = '';
164164
for (const version of matchingVersions) {
165165
if (!isVersionDeprecated({ version, args })) {
166-
highestExistingVersion = version;
167-
break; // Found the one we want
166+
try {
167+
// Only consider versions that have a corresponding git tag.
168+
// This prevents picking up versions that were published to NPM but failed before the github release/tag.
169+
let tagExists = false;
170+
try {
171+
execSync(`git rev-parse v${version}^{commit} 2>/dev/null`);
172+
tagExists = true;
173+
} catch {
174+
const remoteTag = execSync(
175+
`git ls-remote --tags origin refs/tags/v${version} 2>/dev/null`,
176+
)
177+
.toString()
178+
.trim();
179+
if (remoteTag) {
180+
tagExists = true;
181+
}
182+
}
183+
if (!tagExists) {
184+
throw new Error(`Tag v${version} not found`);
185+
}
186+
highestExistingVersion = version;
187+
break; // Found the one we want
188+
} catch {
189+
console.error(
190+
`Ignoring version ${version} because it lacks a git tag (likely a failed release).`,
191+
);
192+
}
168193
} else {
169194
console.error(`Ignoring deprecated version: ${version}`);
170195
}

0 commit comments

Comments
 (0)