From 9f6ed0a634174c93ea707f6b407240e5981556dd Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Mon, 17 Aug 2026 06:16:33 +0900 Subject: [PATCH] =?UTF-8?q?fix(ci):=20publish=20=E5=BE=8C=E3=81=AB?= =?UTF-8?q?=E5=8C=BF=E5=90=8D=E3=81=A7=E8=AA=AD=E3=82=81=E3=82=8B=E3=81=8B?= =?UTF-8?q?=E3=82=92=E6=A4=9C=E8=A8=BC=E3=81=97=E3=80=81restricted=20?= =?UTF-8?q?=E3=81=AA=E3=82=89=20public=20=E3=81=AB=E5=BC=B7=E5=88=B6?= =?UTF-8?q?=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit npm publish は名前が新規の 3 本(react-geojson / react-kml / react-for-mappls)に対して "+ @mapconductor/react-geojson@0.2.0" を出して exit 0 したが、packument への 匿名 GET は 404 を返す。バージョンだけ新規の 17 本は問題なかった。 scoped パッケージが restricted になると匿名からは 404 に見え、 「作られていない」と区別がつかない。認証付きで access を確認し、 public に強制したうえで、最後に匿名 GET が 200 になることまで検証する。 匿名で 200 になることだけが「利用者が install できる」ことの証明になる。 --- .github/workflows/npm-publish.yml | 53 +++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index bd27ee5..e97a683 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -162,3 +162,56 @@ jobs: env: # npm checks authentication even for a publish. NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + # `npm publish` printed "+ @mapconductor/react-geojson@0.2.0" and exited 0 + # for the three packages whose NAME was new (react-geojson, react-kml, + # react-for-mappls), yet an anonymous GET of the packument returned 404 + # while the 17 packages that were only new VERSIONS were fine. + # + # A scoped package that ends up "restricted" is invisible to anonymous + # requests, which is indistinguishable from "never created" unless you + # ask with the token. So: report the authenticated visibility, force + # public, then verify anonymously. Anonymous 404 is the only check that + # actually proves a user can install it. + - name: Force public access and verify anonymous visibility + shell: bash + run: | + set -uo pipefail + + mapfile -t packages < <( + npm query .workspace --json | node -e ' + let input = ""; + process.stdin.on("data", chunk => input += chunk); + process.stdin.on("end", () => { + for (const workspace of JSON.parse(input)) { + if (!workspace.private) console.log(workspace.name); + } + }); + ' + ) + + failed=() + for package in "${packages[@]}"; do + status="$(npm access get status "${package}" 2>&1 || echo 'unknown')" + if [ "${status}" != "public" ]; then + echo "${package}: access=${status}, forcing public" + npm access set status=public "${package}" || echo "::warning::npm access set failed for ${package}" + fi + + # Anonymous, unauthenticated read - what an installing user sees. + code="$(curl -s -o /dev/null -w '%{http_code}' \ + "https://registry.npmjs.org/$(printf '%s' "${package}" | sed 's|/|%2F|')")" + if [ "${code}" = "200" ]; then + echo "ok ${package} (anonymous ${code}, access=${status})" + else + echo "::error::${package} is not anonymously readable (HTTP ${code}, access=${status})" + failed+=("${package}") + fi + done + + if [ ${#failed[@]} -gt 0 ]; then + echo "::error::${#failed[@]} package(s) not publicly installable: ${failed[*]}" + exit 1 + fi + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}