diff --git a/.github/workflows/react_release.yml b/.github/workflows/react_release.yml index db5ea57..45edf53 100644 --- a/.github/workflows/react_release.yml +++ b/.github/workflows/react_release.yml @@ -3,14 +3,19 @@ name: Publish React SDK on: release: types: [ published ] + workflow_dispatch: {} + +permissions: + id-token: write + contents: read + packages: write jobs: publish: name: Publish to NPM and GitHub Package Registry runs-on: ubuntu-latest - permissions: - contents: read - packages: write + if: ${{ github.event_name == 'workflow_dispatch' || !github.event.release.draft }} + environment: npm steps: - name: Checkout branch uses: actions/checkout@v4 @@ -20,11 +25,12 @@ jobs: - name: Set up Node uses: actions/setup-node@v4 with: - node-version: 18 - registry-url: "https://registry.npmjs.org/" - always-auth: "true" + node-version: 22 cache: 'npm' + - name: Upgrade npm for OIDC support + run: npm install -g npm@latest + - name: Configure GHR auth run: echo "//npm.pkg.github.com/:_authToken=\${NODE_AUTH_TOKEN}" >> ~/.npmrc @@ -39,14 +45,17 @@ jobs: tarball=$(npm pack --ignore-scripts | tail -n1) echo "tarball=$tarball" >> "$GITHUB_OUTPUT" + # npm publish uses OIDC trusted publishing (no token needed). + # publish.sh adds --provenance for supply-chain attestations. - name: Publish to NPM env: - NODE_AUTH_TOKEN: ${{ secrets.PUBLISH_REACT_TO_NPM_FROM_GITHUB }} TARBALL: ${{ steps.pack.outputs.tarball }} + DRY_RUN: ${{ github.event_name == 'workflow_dispatch' }} run: scripts/publish.sh "https://registry.npmjs.org" "$TARBALL" - name: Publish to GitHub Package Registry env: NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} TARBALL: ${{ steps.pack.outputs.tarball }} + DRY_RUN: ${{ github.event_name == 'workflow_dispatch' }} run: scripts/publish.sh "https://npm.pkg.github.com" "$TARBALL" diff --git a/scripts/publish.sh b/scripts/publish.sh index 1241c86..3429105 100755 --- a/scripts/publish.sh +++ b/scripts/publish.sh @@ -88,8 +88,12 @@ trap 'rm -f "$body_file"' EXIT # curl -sS (no --fail) returns 0 for any HTTP response, non-zero only on # network/protocol errors — so we can cleanly separate "server answered" from # "could not reach server". +auth_header="" +if [[ -n "${NODE_AUTH_TOKEN:-}" ]]; then + auth_header="Authorization: Bearer ${NODE_AUTH_TOKEN}" +fi if ! http_code=$(curl -sS -m 30 -o "$body_file" -w '%{http_code}' \ - -H "Authorization: Bearer ${NODE_AUTH_TOKEN:-}" "$packument_url"); then + ${auth_header:+-H "$auth_header"} "$packument_url"); then echo "ERROR: request to ${packument_url} failed (network/timeout); refusing to publish on an ambiguous result." >&2 exit 1 fi @@ -135,11 +139,16 @@ if [[ "$dry_run" == "true" ]]; then exit 0 fi +provenance_flag="" +if [[ "$registry" == *"registry.npmjs.org"* ]]; then + provenance_flag="--provenance" +fi + echo "Publishing ${pkg}@${version} (tag: ${tag}) to ${registry}" if [[ -n "$tarball" ]]; then # The tarball is a prebuilt artifact; skip lifecycle scripts (prepublishOnly # = test + build) that would otherwise run from the current package.json. - npm publish "$tarball" --registry "$registry" --tag "$tag" --ignore-scripts + npm publish "$tarball" --registry "$registry" --tag "$tag" --ignore-scripts $provenance_flag else - npm publish --registry "$registry" --tag "$tag" + npm publish --registry "$registry" --tag "$tag" $provenance_flag fi