From add9a735ea8983b9882aa345d45150a61695333b Mon Sep 17 00:00:00 2001 From: pallyoung Date: Tue, 25 Aug 2026 14:04:52 +0800 Subject: [PATCH] fix(release): make product promotion finalization resilient --- .github/workflows/product-release.yml | 33 ++++++++++++++++++--------- scripts/github-workflows.test.ts | 5 ++++ 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/.github/workflows/product-release.yml b/.github/workflows/product-release.yml index 8a58a8f3..917896c0 100644 --- a/.github/workflows/product-release.yml +++ b/.github/workflows/product-release.yml @@ -848,15 +848,23 @@ jobs: --latest=false fi - - name: Verify promoted Product - run: | - selected_version=$(npm view "${PACKAGE_NAME}@${FINAL_DIST_TAG}" version --json | node -p "JSON.parse(require('fs').readFileSync(0, 'utf8'))") - if [[ "${selected_version}" != "${PRODUCT_VERSION}" ]]; then - echo "Final npm dist-tag does not resolve to the accepted Product" >&2 - exit 1 - fi - mkdir -p release/product-promotion/final - gh release download product-stable --pattern product-channel.json --dir release/product-promotion/final + - name: Verify promoted Product + run: | + selected_version="" + for attempt in 1 2 3 4 5 6; do + selected_version=$(npm view "${PACKAGE_NAME}@${FINAL_DIST_TAG}" version --json 2>/dev/null | node -p "JSON.parse(require('fs').readFileSync(0, 'utf8'))") || true + if [[ "${selected_version}" == "${PRODUCT_VERSION}" ]]; then + break + fi + if [[ "${attempt}" == "6" ]]; then + echo "Final npm dist-tag does not resolve to the accepted Product" >&2 + exit 1 + fi + echo "npm dist-tag has not propagated yet (attempt ${attempt}/6); retrying in 10 seconds" + sleep 10 + done + mkdir -p release/product-promotion/final + gh release download product-stable --pattern product-channel.json --dir release/product-promotion/final final_digest=$(sha256sum release/product-promotion/final/product-channel.json | cut -d ' ' -f 1) final_version=$(node -p "require('./release/product-promotion/final/product-channel.json').version") if [[ "${final_digest}" != "${PRODUCT_CHANNEL_SHA256}" || "${final_version}" != "${PRODUCT_VERSION}" ]]; then @@ -864,8 +872,11 @@ jobs: exit 1 fi - - name: Remove temporary npm dist-tag - run: npm dist-tag rm "${PACKAGE_NAME}" "${ACCEPTANCE_DIST_TAG}" + - name: Remove temporary npm dist-tag + run: | + if ! npm dist-tag rm "${PACKAGE_NAME}" "${ACCEPTANCE_DIST_TAG}"; then + echo "::warning::Unable to remove temporary npm dist-tag ${ACCEPTANCE_DIST_TAG}; promotion is already complete" + fi - name: Record Product promotion run: | diff --git a/scripts/github-workflows.test.ts b/scripts/github-workflows.test.ts index 3441ce8d..3c74b9da 100644 --- a/scripts/github-workflows.test.ts +++ b/scripts/github-workflows.test.ts @@ -268,7 +268,12 @@ describe("Product publication workflow", () => { expect(steps[releaseIndex]?.run).toContain("--prerelease=false --latest=false"); expect(steps[pointerIndex]?.run).toContain("product-stable"); expect(steps[pointerIndex]?.run).toContain("product-channel.json"); + expect(steps[verifyIndex]?.run).toContain("for attempt in 1 2 3 4 5 6"); + expect(steps[verifyIndex]?.run).toContain("sleep 10"); expect(steps[cleanupIndex]?.run).toContain("npm dist-tag rm"); + expect(steps[cleanupIndex]?.run).toContain( + "::warning::Unable to remove temporary npm dist-tag" + ); expect(steps[recordIndex]?.run).toContain("promotion.json"); expect(steps[recordIndex]?.run).toContain( "release-workflow-helpers.ts verify-existing-promotion-record"