From c93aca99319ec80fb9b2451b884e95352c0bd109 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dai=20L=C3=B3pez?= Date: Thu, 23 Jul 2026 14:38:58 -0600 Subject: [PATCH] ci(release): publish to npm and crates.io via OIDC trusted publishing Extend OIDC trusted publishing to the npm and crates.io release jobs, removing the long-lived NPM_TOKEN and CARGO_REGISTRY_TOKEN from the release path (the PyPI job already moved to OIDC). As with PyPI, tree-sitter/workflows' package-npm.yml and package-crates.yml require their token secrets and expose no id-token permission, and the upstream fix (tree-sitter/workflows#54) is an unmerged draft, so both are vendored here with that patch applied and adapted per registry: - npm: the auth token is optional and the publish job carries id-token: write. npm is upgraded to a trusted-publishing-capable release (>= 11.5.1) before publish, since the bundled npm predates OIDC support. When NODE_AUTH_TOKEN is unset, npm publish authenticates through the OIDC id-token. - crates.io: the registry token is optional and the publish job carries id-token: write. When no token is set, rust-lang/crates-io-auth-action mints a short-lived token via OIDC, which cargo publish then uses. release.yml calls the local copies, grants contents: read + id-token: write to each reusable workflow, and no longer passes the token secrets. Both require a GitHub trusted publisher configured on the respective registry (workflow release.yml; environments npm and crates). Co-authored-by: Claude Opus 4.8 --- .github/workflows/package-crates.yml | 90 ++++++++++++++++ .github/workflows/package-npm.yml | 156 +++++++++++++++++++++++++++ .github/workflows/release.yml | 19 ++-- 3 files changed, 259 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/package-crates.yml create mode 100644 .github/workflows/package-npm.yml diff --git a/.github/workflows/package-crates.yml b/.github/workflows/package-crates.yml new file mode 100644 index 0000000..852cbf1 --- /dev/null +++ b/.github/workflows/package-crates.yml @@ -0,0 +1,90 @@ +# Vendored from tree-sitter/workflows/.github/workflows/package-crates.yml, with +# OIDC trusted publishing enabled (upstream PR #54, unmerged): CARGO_REGISTRY_TOKEN +# is optional and the publish job carries `id-token: write`. When no token is set, +# rust-lang/crates-io-auth-action mints a short-lived one via OIDC. `workflow_call` +# only, so it never runs on its own. Re-sync from upstream if the build drifts. +name: Publish package (crates.io) + +on: + workflow_call: + inputs: + package-name: + description: The name of the package + default: ${{github.event.repository.name}} + type: string + environment-name: + description: The name of the environment + default: crates + type: string + rust-toolchain: + description: The Rust toolchain + default: ${{vars.RUST_TOOLCHAIN || 'stable'}} + type: string + generate: + description: Generate the parser artifacts + default: false + type: boolean + abi-version: + description: The tree-sitter ABI version + default: 15 + type: number + secrets: + # Optional: unset -> OIDC trusted publishing; set -> classic token (rollback) + CARGO_REGISTRY_TOKEN: + description: An authentication token for crates.io + required: false + +jobs: + package: + name: Publish Rust package + runs-on: ubuntu-latest + environment: + name: ${{inputs.environment-name}} + url: https://crates.io/crates/${{inputs.package-name}} + permissions: + contents: read + id-token: write + # Surface the token as env so the OIDC step can be skipped when a classic + # token is provided (secrets are not readable in a step `if`, env is). + env: + CARGO_REGISTRY_TOKEN: ${{secrets.CARGO_REGISTRY_TOKEN}} + steps: + - name: Checkout repository + uses: actions/checkout@v7 + - name: Set up Rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: ${{inputs.rust-toolchain}} + - name: Set up tree-sitter CLI + if: ${{inputs.generate}} + uses: tree-sitter/setup-action/cli@v2 + - name: Install dependencies + run: |- + if jq -e "$JQ_SCRIPT" package.json >/dev/null; then + npm i --ignore-scripts --omit peer --omit optional + fi + env: + JQ_SCRIPT: >- + .dependencies + .devDependencies | + with_entries(select( + (.key | startswith("tree-sitter-")) + and (.key != "tree-sitter-cli") + )) | length > 0 + - name: Regenerate parser + if: ${{inputs.generate}} + run: | + while read -r grammar_dir; do + pushd "$grammar_dir" + tree-sitter generate + popd > /dev/null + done < <(jq -r '.grammars[].path // "."' tree-sitter.json) + env: + TREE_SITTER_ABI_VERSION: ${{inputs.abi-version}} + - name: Mint crates.io OIDC token + id: auth + if: env.CARGO_REGISTRY_TOKEN == '' + uses: rust-lang/crates-io-auth-action@v1 + - name: Publish to crates.io + run: cargo publish + env: + CARGO_REGISTRY_TOKEN: ${{env.CARGO_REGISTRY_TOKEN || steps.auth.outputs.token}} diff --git a/.github/workflows/package-npm.yml b/.github/workflows/package-npm.yml new file mode 100644 index 0000000..cfc0961 --- /dev/null +++ b/.github/workflows/package-npm.yml @@ -0,0 +1,156 @@ +# Vendored from tree-sitter/workflows/.github/workflows/package-npm.yml, with +# OIDC trusted publishing enabled (upstream PR #54, unmerged): NODE_AUTH_TOKEN is +# optional and the publish job carries `id-token: write`. npm is upgraded to a +# release that supports trusted publishing (>= 11.5.1; the default Node ships an +# older npm). `workflow_call` only, so it never runs on its own. Re-sync from +# upstream if the build matrix drifts. +name: Publish package (npm) + +on: + workflow_call: + inputs: + package-name: + description: The name of the package + default: ${{github.event.repository.name}} + type: string + environment-name: + description: The name of the environment + default: npm + type: string + node-version: + description: The NodeJS version + default: ${{vars.NODE_VERSION || '22'}} + type: string + generate: + description: Generate the parser artifacts + default: false + type: boolean + abi-version: + description: The tree-sitter ABI version + default: 15 + type: number + secrets: + # Optional: unset -> OIDC trusted publishing; set -> classic token (rollback) + NODE_AUTH_TOKEN: + description: An authentication token for npm + required: false + +defaults: + run: + shell: bash + +jobs: + build_wasm: + name: Build Wasm binaries + runs-on: ubuntu-24.04 + continue-on-error: true + steps: + - name: Checkout repository + uses: actions/checkout@v7 + - name: Set up NodeJS + uses: actions/setup-node@v6 + with: + cache: npm + node-version: ${{inputs.node-version}} + - name: Install dependencies + run: npm i --omit peer --omit optional + - name: Regenerate parser + if: ${{inputs.generate}} + run: | + while read -r grammar_dir; do + pushd "$grammar_dir" + npm x -- tree-sitter generate + popd > /dev/null + done < <(jq -r '.grammars[].path // "."' tree-sitter.json) + env: + TREE_SITTER_ABI_VERSION: ${{inputs.abi-version}} + - name: Build Wasm binaries + run: |- + while read -r grammar_dir; do + npm x -- tree-sitter build --wasm "$grammar_dir" + done < <(jq -r '.grammars[].path // "."' tree-sitter.json) + - name: Upload binaries + uses: actions/upload-artifact@v7 + with: + path: "*.wasm" + name: prebuilds-wasm + retention-days: 2 + + build_node: + name: Build NodeJS binaries on ${{matrix.os}} + runs-on: ${{matrix.os}} + strategy: + matrix: + os: + - windows-2022 + - windows-11-arm + - ubuntu-24.04 + - ubuntu-24.04-arm + - macos-15 + - macos-15-intel + steps: + - name: Checkout repository + uses: actions/checkout@v7 + - name: Set up NodeJS + uses: actions/setup-node@v6 + with: + cache: npm + node-version: ${{inputs.node-version}} + - name: Install dependencies + run: npm i --omit peer --omit optional + - name: Regenerate parser + if: ${{inputs.generate}} + shell: bash + run: | + while read -r grammar_dir; do + grammar_dir="${grammar_dir%$'\r'}" + pushd "$grammar_dir" + npm x -- tree-sitter generate + popd > /dev/null + done < <(jq -r '.grammars[].path // "."' tree-sitter.json) + - name: Build binary + run: npm x -- prebuildify -t 20.19.5 + - name: Upload binaries + uses: actions/upload-artifact@v7 + with: + path: prebuilds/** + name: prebuilds-${{matrix.os}} + retention-days: 2 + + package: + name: Publish NodeJS package + needs: [build_wasm, build_node] + runs-on: ubuntu-24.04 + environment: + name: ${{inputs.environment-name}} + url: https://www.npmjs.com/package/${{inputs.package-name}} + permissions: + contents: read + id-token: write + steps: + - name: Checkout repository + uses: actions/checkout@v7 + - name: Set up NodeJS + uses: actions/setup-node@v6 + with: + cache: npm + node-version: ${{inputs.node-version}} + registry-url: https://registry.npmjs.org/ + - name: Download binaries + uses: actions/download-artifact@v8 + with: + path: prebuilds + pattern: prebuilds-* + merge-multiple: true + - name: Check binaries + run: tree prebuilds + - name: Move Wasm binaries to root + continue-on-error: true + run: mv -v prebuilds/*.wasm . + # Trusted publishing requires npm >= 11.5.1; the bundled npm is older. + - name: Upgrade npm + run: npm install -g npm@latest + - name: Publish to npm + run: npm publish + env: + NODE_AUTH_TOKEN: ${{secrets.NODE_AUTH_TOKEN}} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b124360..1763dfe 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -40,27 +40,34 @@ jobs: # Registry publish is gated in THIS run, not a separate tag-triggered workflow: # a tag pushed by the default GITHUB_TOKEN cannot trigger another workflow. + # Local OIDC-enabled copy of package-npm.yml (see the pypi job below and + # tree-sitter/workflows#54). No NODE_AUTH_TOKEN passed -> npm publishes via + # the OIDC id-token. npm: needs: release-please if: needs.release-please.outputs.release_created == 'true' - uses: tree-sitter/workflows/.github/workflows/package-npm.yml@main + permissions: + contents: read + id-token: write + uses: ./.github/workflows/package-npm.yml with: generate: true - secrets: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + # Local OIDC-enabled copy of package-crates.yml. No CARGO_REGISTRY_TOKEN + # passed -> crates-io-auth-action mints a short-lived token via OIDC. crates: needs: release-please if: needs.release-please.outputs.release_created == 'true' - uses: tree-sitter/workflows/.github/workflows/package-crates.yml@main + permissions: + contents: read + id-token: write + uses: ./.github/workflows/package-crates.yml # generate:false — the committed src/ is complete and tracked, so cargo # publishes from it directly. generate:true made the job cargo-install the # CLI into an in-workspace CARGO_HOME, whose .crates.toml/.crates2.json then # tripped `cargo publish`'s uncommitted-changes check. with: generate: false - secrets: - CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} # Local copy of tree-sitter/workflows' package-pypi.yml, patched for OIDC # trusted publishing (upstream PR tree-sitter/workflows#54, unmerged). No