From 18a518257aeaf877249e4fa0577c466243726de3 Mon Sep 17 00:00:00 2001 From: trick77 Date: Mon, 27 Jul 2026 13:05:09 +0200 Subject: [PATCH] ci: guard releases against duplicate versions and dead tags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ported from opencode-litellm-pricing, where both earned their place while debugging a trusted-publisher misconfiguration that failed four releases. Reject a version already on npm before packing and signing, rather than after — npm refuses duplicates and the failure is otherwise late and noisy. Delete the tag when nothing was published. Such a tag cannot be re-pushed and blocks the retry. Guarded on the publish step's own outcome, so a tag whose package did go out is left alone: npm will not take the same version twice, and that release has to be finished by hand. Also align ci.yaml on npm ci --ignore-scripts, matching release.yaml. --- .github/workflows/ci.yaml | 2 +- .github/workflows/release.yaml | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e2b0a29..98481e2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -20,6 +20,6 @@ jobs: with: node-version: ${{ matrix.node }} cache: npm - - run: npm ci + - run: npm ci --ignore-scripts - run: npm run build - run: npm test diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 5103afa..dece5ba 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -34,10 +34,27 @@ jobs: echo "tag $tag does not match package.json $pkg" >&2 exit 1 fi + - name: Verify this version is not already on npm + run: | + name=$(node -p "require('./package.json').name") + pkg=$(node -p "require('./package.json').version") + if npm view "$name@$pkg" version --silent >/dev/null 2>&1; then + echo "::error::$name@$pkg is already published. npm rejects duplicate versions — bump and tag again." >&2 + exit 1 + fi # OIDC trusted publishing requires npm >= 11.5.1. Invoke via npx # so we always get a recent enough CLI without mutating the # global npm install on the runner. - - run: npx -y npm@latest publish --access public --provenance + - id: publish + run: npx -y npm@latest publish --access public --provenance - uses: softprops/action-gh-release@v3 with: generate_release_notes: true + # A tag that produced no published version is a trap: it cannot be + # re-pushed, so every retry needs manual tag surgery. Drop it — but only + # when the publish itself did not succeed. If the package went out and a + # later step failed, the tag must stay, because npm will not accept the + # same version twice and that release has to be finished by hand. + - name: Drop the tag if nothing was published + if: failure() && steps.publish.outcome != 'success' + run: git push --delete origin "$GITHUB_REF_NAME"