From a9ee483efe08f93e998c17811384207de6f47b91 Mon Sep 17 00:00:00 2001 From: satsukies Date: Wed, 7 Oct 2026 14:42:12 +0900 Subject: [PATCH 1/2] ci: allow retrying npm publish for an existing release tag --- .github/workflows/release.yml | 64 +++++++++++++++++++++++++++++------ README.md | 4 +++ 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1a56efa..18c3c86 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,13 +3,24 @@ name: Release on: push: branches: [main] + # Retries npm publish for a release whose tag already exists. Keep this in + # release.yml: npm trusted publishing is bound to this workflow file. + workflow_dispatch: + inputs: + tag: + description: 'Existing release tag to publish, e.g. deploygate--v1.5.2' + required: true + type: string jobs: release-please: + if: ${{ github.event_name == 'push' }} runs-on: ubuntu-latest permissions: contents: write - id-token: write + outputs: + release_created: ${{ steps.release.outputs.release_created }} + tag_name: ${{ steps.release.outputs.tag_name }} steps: - uses: actions/create-github-app-token@v3 id: app-token @@ -55,24 +66,57 @@ jobs: git push origin "HEAD:${BRANCH}" fi - - if: ${{ steps.release.outputs.release_created == 'true' }} - uses: actions/checkout@v7 + publish: + needs: release-please + if: ${{ !cancelled() && (github.event_name == 'workflow_dispatch' || (needs.release-please.result == 'success' && needs.release-please.outputs.release_created == 'true')) }} + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + env: + TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || needs.release-please.outputs.tag_name }} + steps: + - run: | + if ! printf '%s' "$TAG" | grep -Eq '^deploygate--v[0-9]+\.[0-9]+\.[0-9]+$'; then + echo "::error::Invalid release tag (expected deploygate--vX.Y.Z)" + exit 1 + fi - - if: ${{ steps.release.outputs.release_created == 'true' }} + - uses: actions/checkout@v7 + with: + ref: refs/tags/${{ env.TAG }} + + - id: check + run: | + VERSION="${TAG#deploygate--v}" + PKG_VERSION=$(node -p "require('./package.json').version") + if [ "$PKG_VERSION" != "$VERSION" ]; then + echo "::error::package.json version ${PKG_VERSION} does not match tag ${TAG}" + exit 1 + fi + # npm view exits non-zero (E404) or prints nothing when the version is absent. + PUBLISHED=$(npm view "@deploygate/mcp@${VERSION}" version 2>/dev/null || true) + if [ "$PUBLISHED" = "$VERSION" ]; then + echo "::notice::@deploygate/mcp@${VERSION} is already on npm; skipping publish" + echo "skip=true" >> "$GITHUB_OUTPUT" + else + echo "skip=false" >> "$GITHUB_OUTPUT" + fi + + - if: ${{ steps.check.outputs.skip == 'false' }} uses: actions/setup-node@v7 with: node-version: 24 cache: npm registry-url: 'https://registry.npmjs.org' - - if: ${{ steps.release.outputs.release_created == 'true' }} + - if: ${{ steps.check.outputs.skip == 'false' }} run: npm install -g npm@latest - - if: ${{ steps.release.outputs.release_created == 'true' }} + - if: ${{ steps.check.outputs.skip == 'false' }} run: npm ci - - if: ${{ steps.release.outputs.release_created == 'true' }} - run: npm run bundle - - - if: ${{ steps.release.outputs.release_created == 'true' }} + # No `npm run bundle` here: the plugin/scripts/bundle.js committed at the + # tag is published as-is, so npm and git ship the same file. + - if: ${{ steps.check.outputs.skip == 'false' }} run: npm publish diff --git a/README.md b/README.md index 52607ac..78f5602 100644 --- a/README.md +++ b/README.md @@ -225,6 +225,10 @@ End users can pin to a tag with: Without a pin, `claude plugin install` follows `main`. Users can fetch the latest published tag with `claude plugin update deploygate`. +### Retrying a failed npm publish + +If the tag `deploygate--vX.Y.Z` and its GitHub Release exist but `npm publish` failed, do not delete the tag: that breaks release-please's baseline. Fix the cause, then go to Actions → Release → Run workflow and enter the tag. The workflow checks out the tag and publishes the files committed there, including `plugin/scripts/bundle.js`. If that version is already on npm, the run does nothing. + ## Support & Project Status This plugin is open-source software provided **as-is on a best-effort From 84c4a2b76a99a604784fa21f38abcfaf0a4803c2 Mon Sep 17 00:00:00 2001 From: satsukies Date: Wed, 7 Oct 2026 14:50:29 +0900 Subject: [PATCH 2/2] ci: only publish tags on main and stop on npm lookup errors --- .github/workflows/release.yml | 29 ++++++++++++++++++++++------- README.md | 5 ++++- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 18c3c86..d4c7d80 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -85,6 +85,15 @@ jobs: - uses: actions/checkout@v7 with: ref: refs/tags/${{ env.TAG }} + fetch-depth: 0 + + # Only publish commits that went through main, so a manual run cannot + # publish a tag pointing at unreviewed code. + - run: | + if ! git merge-base --is-ancestor HEAD origin/main; then + echo "::error::${TAG} is not reachable from main; refusing to publish" + exit 1 + fi - id: check run: | @@ -94,14 +103,20 @@ jobs: echo "::error::package.json version ${PKG_VERSION} does not match tag ${TAG}" exit 1 fi - # npm view exits non-zero (E404) or prints nothing when the version is absent. - PUBLISHED=$(npm view "@deploygate/mcp@${VERSION}" version 2>/dev/null || true) - if [ "$PUBLISHED" = "$VERSION" ]; then - echo "::notice::@deploygate/mcp@${VERSION} is already on npm; skipping publish" - echo "skip=true" >> "$GITHUB_OUTPUT" - else - echo "skip=false" >> "$GITHUB_OUTPUT" + # Only an explicit E404 means "not published yet". Any other registry + # error stops the run instead of attempting a publish. + if PUBLISHED=$(npm view "@deploygate/mcp@${VERSION}" version 2>"$RUNNER_TEMP/npm-view.err"); then + if [ "$PUBLISHED" = "$VERSION" ]; then + echo "::notice::@deploygate/mcp@${VERSION} is already on npm; skipping publish" + echo "skip=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + elif ! grep -q E404 "$RUNNER_TEMP/npm-view.err"; then + cat "$RUNNER_TEMP/npm-view.err" + echo "::error::Could not check npm for @deploygate/mcp@${VERSION}" + exit 1 fi + echo "skip=false" >> "$GITHUB_OUTPUT" - if: ${{ steps.check.outputs.skip == 'false' }} uses: actions/setup-node@v7 diff --git a/README.md b/README.md index 78f5602..17a7687 100644 --- a/README.md +++ b/README.md @@ -227,7 +227,10 @@ Without a pin, `claude plugin install` follows `main`. Users can fetch the lates ### Retrying a failed npm publish -If the tag `deploygate--vX.Y.Z` and its GitHub Release exist but `npm publish` failed, do not delete the tag: that breaks release-please's baseline. Fix the cause, then go to Actions → Release → Run workflow and enter the tag. The workflow checks out the tag and publishes the files committed there, including `plugin/scripts/bundle.js`. If that version is already on npm, the run does nothing. +If the tag `deploygate--vX.Y.Z` and its GitHub Release exist but `npm publish` failed, do not delete the tag: that breaks release-please's baseline. + +- If the failure was transient or outside the repository (an npm outage, a registry or trusted-publisher setting), fix it and go to Actions → Release → Run workflow, then enter the tag. The workflow checks out the tag and publishes the files committed there, including `plugin/scripts/bundle.js`. It refuses tags that are not reachable from `main`, and does nothing if that version is already on npm. +- If the failure was caused by the tagged files themselves (for example a broken `package-lock.json`), a retry fails again because the tag cannot change. Merge a `fix:` PR to `main` and ship it as the next release instead. ## Support & Project Status