From 9efd68ee4b756af3a7d37a91e0ec2536122b8e94 Mon Sep 17 00:00:00 2001 From: Rob Reed Date: Thu, 27 Aug 2026 22:00:38 -0700 Subject: [PATCH] Fail early on a tag collision, and drop the unused token fallback. The publish runs before the tag is pushed, and a published version cannot be replaced, so a tag that already exists would be discovered only after the package had shipped, leaving a release that no re-run can tag. The shadow release used this same version scheme, so its tags are real candidates. Check for the tag in the same preflight that checks the published version. Reading a dependency from another repository's package is granted through that package's Manage Actions access, which makes GITHUB_TOKEN sufficient. No organization secret reaches these forks, so the GH_TOKEN fallback could never have resolved to anything, and its presence only suggested otherwise. Also state explicitly that CI needs packages: read, rather than depending on a repository default that an organization can tighten, and stop describing the dry run as packing when it only reports what would be packed. --- .github/workflows/build.yml | 5 +++++ .github/workflows/xelp_npm_release.yml | 30 ++++++++++++++++++-------- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ea0a90d..6abd426 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,6 +16,11 @@ concurrency: jobs: ci: runs-on: ${{ matrix.os }} + # npm ci reads @plexinc packages, so do not rely on the repository + # default, which an organization can tighten without warning. + permissions: + contents: read + packages: read env: NODE_OPTIONS: "--max-old-space-size=4096" strategy: diff --git a/.github/workflows/xelp_npm_release.yml b/.github/workflows/xelp_npm_release.yml index 6e91c6f..03f8cf9 100644 --- a/.github/workflows/xelp_npm_release.yml +++ b/.github/workflows/xelp_npm_release.yml @@ -4,8 +4,10 @@ name: Xelp npm Release # @plexinc/, then tags the commit and creates a GitHub release. # # Authentication is the workflow's own GITHUB_TOKEN, so there is no secret to -# provision or rotate. Consumers authenticate the way every other Plex client -# repo does, with a personal access token carrying read:packages. +# provision or rotate. Reading a dependency that lives in another repository's +# package needs that repository granted under the package's Manage Actions +# access, not a different token. Consumers authenticate the way every other Plex +# client repo does, with a personal access token carrying read:packages. # # This replaces xelp_shadow_release.yml, which committed dist/ to the xelp/dist # branch and served the package to consumers through a git tag. Run one or the @@ -20,7 +22,7 @@ on: workflow_dispatch: inputs: dryRun: - description: Build and pack, but do not publish, tag, or release. + description: Build and report what would be packed, without publishing, tagging or releasing. type: boolean default: false @@ -107,6 +109,16 @@ jobs: exit 1 fi + # The publish happens before the tag, and a published version cannot be + # replaced, so a tag collision found at the tagging step would leave a + # published package that no re-run can tag. The shadow release used this + # same version scheme, so its tags are real candidates for a collision. + # + if git ls-remote --exit-code --tags origin "refs/tags/$VERSION" >/dev/null 2>&1; then + echo "::error::Tag $VERSION already exists. Land another commit, or wait for tomorrow's date stamp." + exit 1 + fi + # The upstream lockfile is what keeps the transitive dependencies on # working versions, so install from it rather than re-resolving. A clean # re-resolve floats vscode-languageserver-protocol onto an exports only @@ -114,12 +126,12 @@ jobs: # - name: Install dependencies env: - # Dependencies can live in another repo's package, and a repository's - # own GITHUB_TOKEN cannot read those, so prefer the organization token. - # The fallback keeps this working in a fork that has no @plexinc - # dependencies, where the repo token is enough. + # Reading a dependency published from another repository requires that + # repository to be listed under the package's Manage Actions access. + # The grant is per package and is not inherited, so a newly published + # @plexinc package needs its consumers added before this will resolve. # - NODE_AUTH_TOKEN: ${{ secrets.GH_TOKEN || secrets.GITHUB_TOKEN }} + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: npm ci - name: Build @@ -156,7 +168,7 @@ jobs: NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: npm publish - - name: Pack without publishing + - name: Report what would be packed if: ${{ inputs.dryRun }} run: npm pack --dry-run