From b2046efe8587f9ca37dd11686ebfcf57be0bcb5e Mon Sep 17 00:00:00 2001 From: tequ Date: Wed, 13 May 2026 12:01:45 +0900 Subject: [PATCH] Add npm trusted publishing workflow Add GitHub Actions workflow for npm trusted publishing via OIDC. The workflow validates package version bumps on PRs with npm publish dry-runs, publishes changed workspace packages from main-xahau, and creates matching GitHub releases. Shared npm publish logic is implemented as a local composite action used by both dry-run and publish jobs. --- .../actions/npm-publish-package/action.yml | 79 +++++++++++++++++ .github/workflows/npm-publish.yml | 87 +++++++++++++++++++ .../scripts/check-npm-version-unpublished.sh | 18 ++++ .../scripts/check-package-version-changed.sh | 23 +++++ .../scripts/read-package-metadata.sh | 11 +++ 5 files changed, 218 insertions(+) create mode 100644 .github/actions/npm-publish-package/action.yml create mode 100644 .github/workflows/npm-publish.yml create mode 100644 .github/workflows/scripts/check-npm-version-unpublished.sh create mode 100644 .github/workflows/scripts/check-package-version-changed.sh create mode 100644 .github/workflows/scripts/read-package-metadata.sh diff --git a/.github/actions/npm-publish-package/action.yml b/.github/actions/npm-publish-package/action.yml new file mode 100644 index 00000000..13c9d8be --- /dev/null +++ b/.github/actions/npm-publish-package/action.yml @@ -0,0 +1,79 @@ +name: Publish npm package +description: Publish one workspace package to npm and create the matching GitHub release. + +inputs: + package-path: + description: Workspace package path. + required: true + node-version: + description: Node.js version used for npm trusted publishing. + required: false + default: "24" + registry-url: + description: npm registry URL. + required: false + default: https://registry.npmjs.org + target-commitish: + description: Commit SHA used for the GitHub release tag. + required: false + default: "" + dry-run: + description: Run npm publish as a dry-run and skip GitHub release creation. + required: false + default: "false" + base-ref: + description: Git ref used to detect whether package.json version changed. + required: false + default: "" + +runs: + using: composite + steps: + - id: version + name: Check package version change + shell: bash + run: bash .github/workflows/scripts/check-package-version-changed.sh "${{ inputs.package-path }}" + env: + BASE_REF: ${{ inputs.base-ref }} + + - uses: actions/setup-node@v6 + if: steps.version.outputs.changed == 'true' + with: + node-version: ${{ inputs.node-version }} + registry-url: ${{ inputs.registry-url }} + package-manager-cache: false + + - name: Install dependencies + if: steps.version.outputs.changed == 'true' + shell: bash + run: npm ci + + - id: package + name: Read package metadata + if: steps.version.outputs.changed == 'true' + shell: bash + run: bash .github/workflows/scripts/read-package-metadata.sh "${{ inputs.package-path }}" + + - name: Check package version is unpublished + if: steps.version.outputs.changed == 'true' + shell: bash + run: bash .github/workflows/scripts/check-npm-version-unpublished.sh "${{ steps.package.outputs.tag }}" + + - name: Publish to npm (dry-run) + if: steps.version.outputs.changed == 'true' && inputs.dry-run == 'true' + shell: bash + run: npm publish --workspace "${{ inputs.package-path }}" --registry "${{ inputs.registry-url }}" --dry-run + + - name: Publish to npm + if: steps.version.outputs.changed == 'true' && inputs.dry-run != 'true' + shell: bash + run: npm publish --workspace "${{ inputs.package-path }}" --registry "${{ inputs.registry-url }}" + + - name: Create GitHub release + if: steps.version.outputs.changed == 'true' && inputs.dry-run != 'true' + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ steps.package.outputs.tag }} + target_commitish: ${{ inputs.target-commitish }} + name: ${{ steps.package.outputs.tag }} + body: Published ${{ steps.package.outputs.tag }} to npm. diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml new file mode 100644 index 00000000..f05a0049 --- /dev/null +++ b/.github/workflows/npm-publish.yml @@ -0,0 +1,87 @@ +name: Publish npm packages + +on: + pull_request: + branches: [main-xahau] + paths: + - packages/xahau/package.json + - packages/xahau-address-codec/package.json + - packages/xahau-binary-codec/package.json + - packages/xahau-keypairs/package.json + push: + branches: [main-xahau] + paths: + - packages/xahau/package.json + - packages/xahau-address-codec/package.json + - packages/xahau-binary-codec/package.json + - packages/xahau-keypairs/package.json + +concurrency: + group: npm-publish-${{ github.ref }} + cancel-in-progress: false + +jobs: + dry-run: + name: Dry-run ${{ matrix.package.name }} + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + permissions: + contents: read + strategy: + fail-fast: false + matrix: + package: + - name: xahau-address-codec + path: packages/xahau-address-codec + - name: xahau-binary-codec + path: packages/xahau-binary-codec + - name: xahau-keypairs + path: packages/xahau-keypairs + - name: xahau + path: packages/xahau + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Fetch pull request base + env: + BASE_REF: ${{ github.base_ref }} + run: git fetch --no-tags --depth=1 origin "$BASE_REF" + + - uses: ./.github/actions/npm-publish-package + with: + package-path: ${{ matrix.package.path }} + base-ref: origin/${{ github.base_ref }} + dry-run: "true" + + publish: + name: Publish ${{ matrix.package.name }} + if: github.event_name == 'push' && github.ref == 'refs/heads/main-xahau' + runs-on: ubuntu-latest + environment: npm + permissions: + contents: write + id-token: write + strategy: + fail-fast: false + matrix: + package: + - name: xahau-address-codec + path: packages/xahau-address-codec + - name: xahau-binary-codec + path: packages/xahau-binary-codec + - name: xahau-keypairs + path: packages/xahau-keypairs + - name: xahau + path: packages/xahau + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - uses: ./.github/actions/npm-publish-package + with: + package-path: ${{ matrix.package.path }} + base-ref: ${{ github.event.before }} + target-commitish: ${{ github.sha }} diff --git a/.github/workflows/scripts/check-npm-version-unpublished.sh b/.github/workflows/scripts/check-npm-version-unpublished.sh new file mode 100644 index 00000000..1ce47fea --- /dev/null +++ b/.github/workflows/scripts/check-npm-version-unpublished.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -euo pipefail + +package_spec="$1" +stderr_file="$(mktemp)" + +if npm view "$package_spec" version --registry "https://registry.npmjs.org" 2>"$stderr_file"; then + echo "$package_spec is already published." >&2 + exit 1 +fi + +if grep -Eq "E404|404 Not Found|is not in this registry" "$stderr_file"; then + echo "$package_spec is not published yet." + exit 0 +fi + +cat "$stderr_file" >&2 +exit 1 diff --git a/.github/workflows/scripts/check-package-version-changed.sh b/.github/workflows/scripts/check-package-version-changed.sh new file mode 100644 index 00000000..4b944cca --- /dev/null +++ b/.github/workflows/scripts/check-package-version-changed.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail + +package_path="$1" +package_file="$package_path/package.json" +current_version="$(jq -r .version "$package_file")" +changed="false" + +if [[ -z "${BASE_REF:-}" || "${BASE_REF:-}" =~ ^0+$ ]]; then + BASE_REF="HEAD^" +fi + +if previous_package="$(git show "$BASE_REF:$package_file" 2>/dev/null)"; then + previous_version="$(jq -r .version <<<"$previous_package")" + if [[ "$current_version" != "$previous_version" ]]; then + changed="true" + fi +else + changed="true" +fi + +echo "changed=$changed" >> "$GITHUB_OUTPUT" +echo "$package_path version changed: $changed" diff --git a/.github/workflows/scripts/read-package-metadata.sh b/.github/workflows/scripts/read-package-metadata.sh new file mode 100644 index 00000000..5b7ec04f --- /dev/null +++ b/.github/workflows/scripts/read-package-metadata.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -euo pipefail + +package_path="$1" +package_file="$package_path/package.json" +name="$(jq -r .name "$package_file")" +version="$(jq -r .version "$package_file")" + +echo "name=$name" >> "$GITHUB_OUTPUT" +echo "version=$version" >> "$GITHUB_OUTPUT" +echo "tag=$name@$version" >> "$GITHUB_OUTPUT"