From ffeee75ab78b9a159ba651ea5abca954724a6334 Mon Sep 17 00:00:00 2001 From: Barney Huang Date: Mon, 17 Aug 2026 21:49:24 +0800 Subject: [PATCH] fix(ci): publish workspaces one at a time, skipping what is already out `npm publish --workspaces` stops at the first package it cannot publish, so a token whose granted packages do not cover the unscoped `dorval` took the whole release down with it: v0.10.3, v0.10.4 and v0.10.5 each got a tag, a GitHub release and their @dorval/* packages, while the CLI stayed at 0.10.2 on npm and the workflow reported failure with no indication of which half had landed. Publish each package on its own, skip versions already on the registry, and report published/skipped/failed at the end. A real problem still fails the build, but a re-run now picks up whatever was missed instead of tripping over the packages that did go out. Also points manual-publish at the same script - its per-package steps had drifted, tolerating a failure from dio and custom but not from core or the CLI. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/manual-publish.yml | 24 +--------- .releaserc.json | 2 +- scripts/publish-workspaces.sh | 66 ++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 24 deletions(-) create mode 100755 scripts/publish-workspaces.sh diff --git a/.github/workflows/manual-publish.yml b/.github/workflows/manual-publish.yml index cddc86f..c2a0433 100644 --- a/.github/workflows/manual-publish.yml +++ b/.github/workflows/manual-publish.yml @@ -82,29 +82,7 @@ jobs: npm whoami - name: Publish packages to npm - run: | - # Publish core first (dependency for others) - echo "Publishing @dorval/core..." - cd packages/core - npm publish --access public --tag ${{ inputs.tag }} - cd ../.. - - # Publish other packages - echo "Publishing @dorval/dio..." - cd packages/dio - npm publish --access public --tag ${{ inputs.tag }} || echo "Failed to publish @dorval/dio (may not exist)" - cd ../.. - - echo "Publishing @dorval/custom..." - cd packages/custom - npm publish --access public --tag ${{ inputs.tag }} || echo "Failed to publish @dorval/custom (may not exist)" - cd ../.. - - # Publish CLI last - echo "Publishing dorval CLI..." - cd packages/dorval - npm publish --access public --tag ${{ inputs.tag }} - cd ../.. + run: bash scripts/publish-workspaces.sh ${{ inputs.tag }} env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.releaserc.json b/.releaserc.json index 29e42cf..2af51d0 100644 --- a/.releaserc.json +++ b/.releaserc.json @@ -33,7 +33,7 @@ "@semantic-release/exec", { "prepareCmd": "for pkg in packages/*; do if [ -f \"$pkg/package.json\" ]; then (cd \"$pkg\" && npm version ${nextRelease.version} --no-git-tag-version); fi; done && sed -i.bak 's/\"@dorval\\/core\": \"[^\"]*\"/\"@dorval\\/core\": \"^'${nextRelease.version}'\"/' packages/dorval/package.json && rm packages/dorval/package.json.bak && yarn install --mode=update-lockfile", - "publishCmd": "npm publish --workspaces --access public" + "publishCmd": "bash scripts/publish-workspaces.sh" } ], [ diff --git a/scripts/publish-workspaces.sh b/scripts/publish-workspaces.sh new file mode 100755 index 0000000..a570892 --- /dev/null +++ b/scripts/publish-workspaces.sh @@ -0,0 +1,66 @@ +#!/bin/bash + +# Publish every workspace package, skipping versions already on the registry. +# +# `npm publish --workspaces` stops at the first package it cannot publish, so one +# package the token has no rights to takes the rest down with it and leaves the +# release half done - a tag and a GitHub release for a version npm never got. +# Here each package is published on its own, failures are collected, and the +# script exits non-zero at the end so a real problem still fails the build. +# +# Skipping versions already on the registry makes a re-run pick up whatever was +# missed, rather than failing on the packages that did go out. +# +# Usage: scripts/publish-workspaces.sh [npm-tag] +# DRY_RUN=1 pass --dry-run to npm publish + +set -uo pipefail + +TAG=${1:-latest} +DRY_RUN=${DRY_RUN:-} + +published=() +skipped=() +failed=() + +# core goes first, the CLI package depends on it. Listing it twice and dropping +# the repeat keeps the order explicit rather than leaning on how the glob sorts. +for pkg in $(ls -d packages/core packages/* 2>/dev/null | awk '!seen[$0]++'); do + [ -f "$pkg/package.json" ] || continue + + name=$(node -p "require('./$pkg/package.json').name") + version=$(node -p "require('./$pkg/package.json').version") + private=$(node -p "require('./$pkg/package.json').private === true") + + if [ "$private" = "true" ]; then + echo "-- $name is private, skipping" + continue + fi + + if npm view "$name@$version" version >/dev/null 2>&1; then + echo "-- $name@$version is already on the registry, skipping" + skipped+=("$name@$version") + continue + fi + + echo "-- publishing $name@$version (tag: $TAG)" + if (cd "$pkg" && npm publish --access public --tag "$TAG" ${DRY_RUN:+--dry-run}); then + published+=("$name@$version") + else + echo "!! failed to publish $name@$version" + failed+=("$name@$version") + fi +done + +echo +echo "published: ${published[*]:-none}" +echo "skipped: ${skipped[*]:-none}" +echo "failed: ${failed[*]:-none}" + +if [ ${#failed[@]} -gt 0 ]; then + echo + echo "A 403 on a single package usually means the npm token's granted packages" + echo "do not cover it - a token scoped to @dorval does not carry the unscoped" + echo "dorval package." + exit 1 +fi