From ecfab7ec376be8697fb0d8aefe8ff2c46b8c4f05 Mon Sep 17 00:00:00 2001 From: David Elner Date: Tue, 23 Jun 2026 19:52:06 +0000 Subject: [PATCH 1/3] Changed: Simplify the JavaScript release action inputs --- .github/workflows/release-js.yml | 6 +- actions/release/lang/js/publish/action.yml | 85 +++++++++---------- actions/release/lang/js/validate/action.yml | 74 ++++++++-------- .../actions/release/lang/js/publish.yml.erb | 27 ++---- .../actions/release/lang/js/validate.yml.erb | 21 ++--- templates/steps/lang/js/build.yml.erb | 10 +-- templates/steps/lang/js/env-dump.yml.erb | 13 ++- templates/steps/lang/js/setup.yml.erb | 62 +++++++------- .../steps/release/lang/js/npm-publish.yml.erb | 12 +-- test/release/js/package.json | 1 + 10 files changed, 140 insertions(+), 171 deletions(-) diff --git a/.github/workflows/release-js.yml b/.github/workflows/release-js.yml index 49aec90..5dae509 100644 --- a/.github/workflows/release-js.yml +++ b/.github/workflows/release-js.yml @@ -132,8 +132,7 @@ jobs: sha: ${{ inputs.sha || github.event.pull_request.head.sha }} dry_run: ${{ inputs.dry_run || github.event_name == 'pull_request' }} working_directory: test/release/js - package_manager: pnpm - node_version_file: test/release/js/.tool-versions # exercises node-version-file sourcing + node_version: test/release/js/.tool-versions # exercises version-file sourcing (vs explicit version) package_name: '@braintrust/bt-publishing-test' # exercises the npm-availability fail-fast check channel: ${{ inputs.channel || 'latest' }} allowed_channels: 'latest,rc,next,beta' @@ -231,8 +230,7 @@ jobs: sha: ${{ inputs.sha || github.event.pull_request.head.sha }} checkout: 'false' # bt-publishing-test: skip internal checkout — version patching above must survive into the build working_directory: test/release/js - package_manager: pnpm - node_version_file: test/release/js/.tool-versions + node_version: test/release/js/.tool-versions dry_run: ${{ inputs.dry_run || github.event_name == 'pull_request' }} release_tag: ${{ needs.validate.outputs.release_tag }} github_release: 'false' # bt-publishing-test: omit GitHub release to avoid tag/release pollution from repeated test runs diff --git a/actions/release/lang/js/publish/action.yml b/actions/release/lang/js/publish/action.yml index 68e5582..83b95dd 100644 --- a/actions/release/lang/js/publish/action.yml +++ b/actions/release/lang/js/publish/action.yml @@ -1,8 +1,8 @@ # GENERATED by scripts/generate.rb — edit templates/, not this file. name: Publish JavaScript Package description: > - Full JavaScript package release: checks out the SHA, sets up Node and the - package manager, builds, publishes to npm (OIDC trusted publishing), creates a + Full JavaScript package release: checks out the SHA, sets up Node and pnpm, + builds, publishes to npm (OIDC trusted publishing), creates a GitHub release, and sends a Slack completion notification. Covers the standard publish sequence for Braintrust npm packages. @@ -18,22 +18,15 @@ inputs: description: "Path to the package root (where package.json lives)" required: false default: '.' - package_manager: - description: "Package manager: pnpm | npm | yarn" - required: false - default: 'pnpm' node_version: - description: "Node version to use" - required: false - default: '' - node_version_file: - description: "Path to a node version file (.tool-versions, .nvmrc, .node-version), relative to the repo root" - required: false - default: '' + description: > + Node version (e.g. 24), or a path to a version file + (.tool-versions, .nvmrc, .node-version) relative to the repo root. + required: true pnpm_version: - description: "pnpm version to install (when package_manager is pnpm)" + description: "pnpm version to install. When empty, read from package.json packageManager." required: false - default: '10.33.0' + default: '' build_command: description: "npm script to run for the build" required: false @@ -65,10 +58,6 @@ inputs: description: "npm registry URL" required: false default: 'https://registry.npmjs.org' - npm_version: - description: "npm version to install before publishing (pinned; >= 11.5.1 for tokenless OIDC trusted publishing)" - required: false - default: '11.6.2' access: description: "npm publish access: public | restricted (provenance requires public)" required: false @@ -141,46 +130,51 @@ runs: fetch-depth: 0 - name: Set up pnpm - if: ${{ inputs.package_manager == 'pnpm' }} uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 with: + # Empty version → action-setup reads the pnpm version from this package.json's + # "packageManager" field (one source of truth). A non-empty value overrides it. version: ${{ inputs.pnpm_version }} + package_json_file: ${{ inputs.working_directory }}/package.json + + - name: Resolve Node version source + id: node-src + shell: bash + env: + NODE_VERSION: ${{ inputs.node_version }} + run: | + # node_version is either an explicit version (e.g. 24) or a path to a version + # file (.tool-versions / .nvmrc / .node-version). Route by file existence so a + # single input covers both; paths are relative to the repo root (as setup-node expects). + if [ -n "$NODE_VERSION" ] && [ -f "$NODE_VERSION" ]; then + echo "file=$NODE_VERSION" >> "$GITHUB_OUTPUT" + echo "spec=" >> "$GITHUB_OUTPUT" + else + echo "file=" >> "$GITHUB_OUTPUT" + echo "spec=$NODE_VERSION" >> "$GITHUB_OUTPUT" + fi - name: Set up Node uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - # Explicit node_version wins (setup-node prefers node-version when set); node_version_file - # (.tool-versions / .nvmrc / .node-version) is the fallback. Provide one of the two. - node-version: ${{ inputs.node_version }} - node-version-file: ${{ inputs.node_version_file }} + node-version: ${{ steps.node-src.outputs.spec }} + node-version-file: ${{ steps.node-src.outputs.file }} registry-url: ${{ inputs.registry }} - cache: ${{ inputs.package_manager }} - cache-dependency-path: | - ${{ inputs.working_directory }}/pnpm-lock.yaml - ${{ inputs.working_directory }}/package-lock.json - ${{ inputs.working_directory }}/yarn.lock + cache: pnpm + cache-dependency-path: ${{ inputs.working_directory }}/pnpm-lock.yaml - name: Install dependencies shell: bash working-directory: ${{ inputs.working_directory }} - env: - PM: ${{ inputs.package_manager }} - run: | - case "$PM" in - pnpm) pnpm install --frozen-lockfile ;; - yarn) yarn install --frozen-lockfile ;; - npm) npm ci ;; - *) echo "Unknown package_manager: $PM"; exit 1 ;; - esac + run: pnpm install --frozen-lockfile - name: Build shell: bash working-directory: ${{ inputs.working_directory }} env: - PM: ${{ inputs.package_manager }} BUILD_COMMAND: ${{ inputs.build_command }} run: | - "$PM" run "$BUILD_COMMAND" + pnpm run "$BUILD_COMMAND" - name: Publish to npm shell: bash @@ -192,7 +186,6 @@ runs: ACCESS: ${{ inputs.access }} PROVENANCE: ${{ inputs.provenance }} REGISTRY: ${{ inputs.registry }} - NPM_VERSION: ${{ inputs.npm_version }} # Force OIDC trusted publishing: clear any inherited token so npm can't fall # back to token auth. NODE_AUTH_TOKEN: '' @@ -202,7 +195,9 @@ runs: echo "npm publishing disabled; skipping." exit 0 fi - npm install -g "npm@$NPM_VERSION" + # Pinned publish CLI: OIDC trusted publishing needs npm >= 11.5.1, newer than + # the npm bundled with setup-node. Bump deliberately, not via @latest. + npm install -g "npm@11.6.2" ARGS=(--tag "$CHANNEL" --access "$ACCESS" --registry "$REGISTRY") [ "$PROVENANCE" = "true" ] && ARGS+=(--provenance) if [ "$DRY_RUN" = "true" ]; then @@ -413,14 +408,12 @@ runs: if: ${{ failure() }} shell: bash working-directory: ${{ inputs.working_directory }} - env: - PM: ${{ inputs.package_manager }} run: | echo "=== Node environment ===" node --version npm --version - echo "=== Package manager ($PM) ===" - "$PM" --version || echo "(unavailable)" + echo "=== pnpm ===" + pnpm --version || echo "(unavailable)" echo "=== Registry ===" npm config get registry echo "=== package.json version ===" diff --git a/actions/release/lang/js/validate/action.yml b/actions/release/lang/js/validate/action.yml index 77ca0d6..67f5efe 100644 --- a/actions/release/lang/js/validate/action.yml +++ b/actions/release/lang/js/validate/action.yml @@ -1,7 +1,7 @@ # GENERATED by scripts/generate.rb — edit templates/, not this file. name: Validate JavaScript Release description: > - Checks out the release SHA, sets up Node and the package manager, reads the + Checks out the release SHA, sets up Node and pnpm, reads the package version, validates the release channel and metadata (tag existence, branch, commit metadata), and builds to confirm the package is publishable before the approval gate. @@ -25,22 +25,15 @@ inputs: description: "Path to the package root (where package.json lives)" required: false default: '.' - package_manager: - description: "Package manager: pnpm | npm | yarn" - required: false - default: 'pnpm' node_version: - description: "Node version to use" - required: false - default: '' - node_version_file: - description: "Path to a node version file (.tool-versions, .nvmrc, .node-version), relative to the repo root" - required: false - default: '' + description: > + Node version (e.g. 24), or a path to a version file + (.tool-versions, .nvmrc, .node-version) relative to the repo root. + required: true pnpm_version: - description: "pnpm version to install (when package_manager is pnpm)" + description: "pnpm version to install. When empty, read from package.json packageManager." required: false - default: '10.33.0' + default: '' registry: description: "npm registry URL" required: false @@ -97,37 +90,43 @@ runs: fetch-depth: 0 - name: Set up pnpm - if: ${{ inputs.package_manager == 'pnpm' }} uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 with: + # Empty version → action-setup reads the pnpm version from this package.json's + # "packageManager" field (one source of truth). A non-empty value overrides it. version: ${{ inputs.pnpm_version }} + package_json_file: ${{ inputs.working_directory }}/package.json + + - name: Resolve Node version source + id: node-src + shell: bash + env: + NODE_VERSION: ${{ inputs.node_version }} + run: | + # node_version is either an explicit version (e.g. 24) or a path to a version + # file (.tool-versions / .nvmrc / .node-version). Route by file existence so a + # single input covers both; paths are relative to the repo root (as setup-node expects). + if [ -n "$NODE_VERSION" ] && [ -f "$NODE_VERSION" ]; then + echo "file=$NODE_VERSION" >> "$GITHUB_OUTPUT" + echo "spec=" >> "$GITHUB_OUTPUT" + else + echo "file=" >> "$GITHUB_OUTPUT" + echo "spec=$NODE_VERSION" >> "$GITHUB_OUTPUT" + fi - name: Set up Node uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - # Explicit node_version wins (setup-node prefers node-version when set); node_version_file - # (.tool-versions / .nvmrc / .node-version) is the fallback. Provide one of the two. - node-version: ${{ inputs.node_version }} - node-version-file: ${{ inputs.node_version_file }} + node-version: ${{ steps.node-src.outputs.spec }} + node-version-file: ${{ steps.node-src.outputs.file }} registry-url: ${{ inputs.registry }} - cache: ${{ inputs.package_manager }} - cache-dependency-path: | - ${{ inputs.working_directory }}/pnpm-lock.yaml - ${{ inputs.working_directory }}/package-lock.json - ${{ inputs.working_directory }}/yarn.lock + cache: pnpm + cache-dependency-path: ${{ inputs.working_directory }}/pnpm-lock.yaml - name: Install dependencies shell: bash working-directory: ${{ inputs.working_directory }} - env: - PM: ${{ inputs.package_manager }} - run: | - case "$PM" in - pnpm) pnpm install --frozen-lockfile ;; - yarn) yarn install --frozen-lockfile ;; - npm) npm ci ;; - *) echo "Unknown package_manager: $PM"; exit 1 ;; - esac + run: pnpm install --frozen-lockfile - name: Read version id: read-version @@ -230,23 +229,20 @@ runs: shell: bash working-directory: ${{ inputs.working_directory }} env: - PM: ${{ inputs.package_manager }} BUILD_COMMAND: ${{ inputs.build_command }} run: | - "$PM" run "$BUILD_COMMAND" + pnpm run "$BUILD_COMMAND" - name: Dump JavaScript environment if: ${{ failure() }} shell: bash working-directory: ${{ inputs.working_directory }} - env: - PM: ${{ inputs.package_manager }} run: | echo "=== Node environment ===" node --version npm --version - echo "=== Package manager ($PM) ===" - "$PM" --version || echo "(unavailable)" + echo "=== pnpm ===" + pnpm --version || echo "(unavailable)" echo "=== Registry ===" npm config get registry echo "=== package.json version ===" diff --git a/templates/actions/release/lang/js/publish.yml.erb b/templates/actions/release/lang/js/publish.yml.erb index 0f707cb..582a291 100644 --- a/templates/actions/release/lang/js/publish.yml.erb +++ b/templates/actions/release/lang/js/publish.yml.erb @@ -1,7 +1,7 @@ name: Publish JavaScript Package description: > - Full JavaScript package release: checks out the SHA, sets up Node and the - package manager, builds, publishes to npm (OIDC trusted publishing), creates a + Full JavaScript package release: checks out the SHA, sets up Node and pnpm, + builds, publishes to npm (OIDC trusted publishing), creates a GitHub release, and sends a Slack completion notification. Covers the standard publish sequence for Braintrust npm packages. @@ -17,22 +17,15 @@ inputs: description: "Path to the package root (where package.json lives)" required: false default: '.' - package_manager: - description: "Package manager: pnpm | npm | yarn" - required: false - default: 'pnpm' node_version: - description: "Node version to use" - required: false - default: '' - node_version_file: - description: "Path to a node version file (.tool-versions, .nvmrc, .node-version), relative to the repo root" - required: false - default: '' + description: > + Node version (e.g. 24), or a path to a version file + (.tool-versions, .nvmrc, .node-version) relative to the repo root. + required: true pnpm_version: - description: "pnpm version to install (when package_manager is pnpm)" + description: "pnpm version to install. When empty, read from package.json packageManager." required: false - default: '10.33.0' + default: '' build_command: description: "npm script to run for the build" required: false @@ -64,10 +57,6 @@ inputs: description: "npm registry URL" required: false default: 'https://registry.npmjs.org' - npm_version: - description: "npm version to install before publishing (pinned; >= 11.5.1 for tokenless OIDC trusted publishing)" - required: false - default: '11.6.2' access: description: "npm publish access: public | restricted (provenance requires public)" required: false diff --git a/templates/actions/release/lang/js/validate.yml.erb b/templates/actions/release/lang/js/validate.yml.erb index 6288a0e..2fffcbc 100644 --- a/templates/actions/release/lang/js/validate.yml.erb +++ b/templates/actions/release/lang/js/validate.yml.erb @@ -1,6 +1,6 @@ name: Validate JavaScript Release description: > - Checks out the release SHA, sets up Node and the package manager, reads the + Checks out the release SHA, sets up Node and pnpm, reads the package version, validates the release channel and metadata (tag existence, branch, commit metadata), and builds to confirm the package is publishable before the approval gate. @@ -24,22 +24,15 @@ inputs: description: "Path to the package root (where package.json lives)" required: false default: '.' - package_manager: - description: "Package manager: pnpm | npm | yarn" - required: false - default: 'pnpm' node_version: - description: "Node version to use" - required: false - default: '' - node_version_file: - description: "Path to a node version file (.tool-versions, .nvmrc, .node-version), relative to the repo root" - required: false - default: '' + description: > + Node version (e.g. 24), or a path to a version file + (.tool-versions, .nvmrc, .node-version) relative to the repo root. + required: true pnpm_version: - description: "pnpm version to install (when package_manager is pnpm)" + description: "pnpm version to install. When empty, read from package.json packageManager." required: false - default: '10.33.0' + default: '' registry: description: "npm registry URL" required: false diff --git a/templates/steps/lang/js/build.yml.erb b/templates/steps/lang/js/build.yml.erb index 69b8ac1..00a0f04 100644 --- a/templates/steps/lang/js/build.yml.erb +++ b/templates/steps/lang/js/build.yml.erb @@ -1,9 +1,8 @@ <%# - Runs the package's build script with the selected package manager. - Composed into both validate (to confirm the package builds before the - approval gate) and publish (to produce the artifact npm publishes). + Runs the package's build script with pnpm. Composed into both validate (to + confirm the package builds before the approval gate) and publish (to produce + the artifact npm publishes). - @param package_manager [String] expr for pnpm | npm | yarn. Default: ${{ inputs.package_manager }} @param build_command [String] expr for the npm script name. Default: ${{ inputs.build_command }} @param working_directory [String] expr for the package root. Default: ${{ inputs.working_directory }} -%> @@ -11,7 +10,6 @@ shell: bash working-directory: <%= locals.fetch(:working_directory) { "${{ inputs.working_directory }}" } %> env: - PM: <%= locals.fetch(:package_manager) { "${{ inputs.package_manager }}" } %> BUILD_COMMAND: <%= locals.fetch(:build_command) { "${{ inputs.build_command }}" } %> run: | - "$PM" run "$BUILD_COMMAND" + pnpm run "$BUILD_COMMAND" diff --git a/templates/steps/lang/js/env-dump.yml.erb b/templates/steps/lang/js/env-dump.yml.erb index 07dffa0..3319b4c 100644 --- a/templates/steps/lang/js/env-dump.yml.erb +++ b/templates/steps/lang/js/env-dump.yml.erb @@ -1,22 +1,19 @@ <%# - Prints environment diagnostics (Node/npm/package-manager versions, - the configured registry, the package.json version) to help debug a failed - release. Run from the package root. + Prints environment diagnostics (Node/npm/pnpm versions, the configured + registry, the package.json version) to help debug a failed release. Run from + the package root. - @param package_manager [String] expr for pnpm | npm | yarn. Default: ${{ inputs.package_manager }} @param working_directory [String] expr for the package root. Default: ${{ inputs.working_directory }} -%> - name: Dump JavaScript environment shell: bash working-directory: <%= locals.fetch(:working_directory) { "${{ inputs.working_directory }}" } %> - env: - PM: <%= locals.fetch(:package_manager) { "${{ inputs.package_manager }}" } %> run: | echo "=== Node environment ===" node --version npm --version - echo "=== Package manager ($PM) ===" - "$PM" --version || echo "(unavailable)" + echo "=== pnpm ===" + pnpm --version || echo "(unavailable)" echo "=== Registry ===" npm config get registry echo "=== package.json version ===" diff --git a/templates/steps/lang/js/setup.yml.erb b/templates/steps/lang/js/setup.yml.erb index 1e6b1ed..053e336 100644 --- a/templates/steps/lang/js/setup.yml.erb +++ b/templates/steps/lang/js/setup.yml.erb @@ -1,46 +1,48 @@ <%# - Sets up Node and the selected package manager, then installs the - dependencies (frozen). The package-manager `if:` selects which runtime to - install — genuine control flow that can't be a shell self-guard because the - pnpm install is a `uses:` action, not a `run:` step. + Sets up Node + pnpm and installs the dependencies (frozen). - @param node_version [String] expr for an explicit Node version. Wins when set. Default: ${{ inputs.node_version }} - @param node_version_file [String] expr for a node version FILE (.tool-versions / .nvmrc / - .node-version), used when node_version is empty. Default: ${{ inputs.node_version_file }} - @param package_manager [String] expr for pnpm | npm | yarn. Default: ${{ inputs.package_manager }} - @param pnpm_version [String] expr for the pnpm version (pnpm). Default: ${{ inputs.pnpm_version }} - @param registry [String] expr for the npm registry URL. Default: ${{ inputs.registry }} - @param working_directory [String] expr for the package root. Default: ${{ inputs.working_directory }} + @param node_version [String] expr for a Node version (e.g. 24) OR a path to a + version file (.tool-versions / .nvmrc / .node-version). Required. Default: ${{ inputs.node_version }} + @param pnpm_version [String] expr for the pnpm version. When empty, pnpm is read + from the package.json "packageManager" field in working_directory. Default: ${{ inputs.pnpm_version }} + @param registry [String] expr for the npm registry URL. Default: ${{ inputs.registry }} + @param working_directory [String] expr for the package root. Default: ${{ inputs.working_directory }} -%> - name: Set up pnpm - if: ${{ <%= locals.fetch(:package_manager) { "inputs.package_manager" } %> == 'pnpm' }} uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 with: + # Empty version → action-setup reads the pnpm version from this package.json's + # "packageManager" field (one source of truth). A non-empty value overrides it. version: <%= locals.fetch(:pnpm_version) { "${{ inputs.pnpm_version }}" } %> + package_json_file: <%= locals.fetch(:working_directory) { "${{ inputs.working_directory }}" } %>/package.json + +- name: Resolve Node version source + id: node-src + shell: bash + env: + NODE_VERSION: <%= locals.fetch(:node_version) { "${{ inputs.node_version }}" } %> + run: | + # node_version is either an explicit version (e.g. 24) or a path to a version + # file (.tool-versions / .nvmrc / .node-version). Route by file existence so a + # single input covers both; paths are relative to the repo root (as setup-node expects). + if [ -n "$NODE_VERSION" ] && [ -f "$NODE_VERSION" ]; then + echo "file=$NODE_VERSION" >> "$GITHUB_OUTPUT" + echo "spec=" >> "$GITHUB_OUTPUT" + else + echo "file=" >> "$GITHUB_OUTPUT" + echo "spec=$NODE_VERSION" >> "$GITHUB_OUTPUT" + fi - name: Set up Node uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - # Explicit node_version wins (setup-node prefers node-version when set); node_version_file - # (.tool-versions / .nvmrc / .node-version) is the fallback. Provide one of the two. - node-version: <%= locals.fetch(:node_version) { "${{ inputs.node_version }}" } %> - node-version-file: <%= locals.fetch(:node_version_file) { "${{ inputs.node_version_file }}" } %> + node-version: ${{ steps.node-src.outputs.spec }} + node-version-file: ${{ steps.node-src.outputs.file }} registry-url: <%= locals.fetch(:registry) { "${{ inputs.registry }}" } %> - cache: <%= locals.fetch(:package_manager) { "${{ inputs.package_manager }}" } %> - cache-dependency-path: | - <%= locals.fetch(:working_directory) { "${{ inputs.working_directory }}" } %>/pnpm-lock.yaml - <%= locals.fetch(:working_directory) { "${{ inputs.working_directory }}" } %>/package-lock.json - <%= locals.fetch(:working_directory) { "${{ inputs.working_directory }}" } %>/yarn.lock + cache: pnpm + cache-dependency-path: <%= locals.fetch(:working_directory) { "${{ inputs.working_directory }}" } %>/pnpm-lock.yaml - name: Install dependencies shell: bash working-directory: <%= locals.fetch(:working_directory) { "${{ inputs.working_directory }}" } %> - env: - PM: <%= locals.fetch(:package_manager) { "${{ inputs.package_manager }}" } %> - run: | - case "$PM" in - pnpm) pnpm install --frozen-lockfile ;; - yarn) yarn install --frozen-lockfile ;; - npm) npm ci ;; - *) echo "Unknown package_manager: $PM"; exit 1 ;; - esac + run: pnpm install --frozen-lockfile diff --git a/templates/steps/release/lang/js/npm-publish.yml.erb b/templates/steps/release/lang/js/npm-publish.yml.erb index f0e3515..40a0200 100644 --- a/templates/steps/release/lang/js/npm-publish.yml.erb +++ b/templates/steps/release/lang/js/npm-publish.yml.erb @@ -5,9 +5,10 @@ so the composing action needs no `if:` on it. Assumes Node + the package manager are already set up and the package is built. - npm is pinned to inputs.npm_version (not @latest): tokenless OIDC trusted - publishing requires npm >= 11.5.1, which is newer than the npm bundled with - setup-node. Provenance requires a public package and `id-token: write`. + npm is pinned to a known-good version internally (not @latest, not a consumer + input): tokenless OIDC trusted publishing requires npm >= 11.5.1, newer than the + npm bundled with setup-node. This is the publish CLI only — unrelated to the app's + own npm. Provenance requires a public package and `id-token: write`. -%> - name: Publish to npm shell: bash @@ -19,7 +20,6 @@ ACCESS: ${{ inputs.access }} PROVENANCE: ${{ inputs.provenance }} REGISTRY: ${{ inputs.registry }} - NPM_VERSION: ${{ inputs.npm_version }} # Force OIDC trusted publishing: clear any inherited token so npm can't fall # back to token auth. NODE_AUTH_TOKEN: '' @@ -29,7 +29,9 @@ echo "npm publishing disabled; skipping." exit 0 fi - npm install -g "npm@$NPM_VERSION" + # Pinned publish CLI: OIDC trusted publishing needs npm >= 11.5.1, newer than + # the npm bundled with setup-node. Bump deliberately, not via @latest. + npm install -g "npm@11.6.2" ARGS=(--tag "$CHANNEL" --access "$ACCESS" --registry "$REGISTRY") [ "$PROVENANCE" = "true" ] && ARGS+=(--provenance) if [ "$DRY_RUN" = "true" ]; then diff --git a/test/release/js/package.json b/test/release/js/package.json index 2eb3a08..0974497 100644 --- a/test/release/js/package.json +++ b/test/release/js/package.json @@ -7,6 +7,7 @@ "type": "git", "url": "git+https://github.com/braintrustdata/sdk-actions.git" }, + "packageManager": "pnpm@10.33.0", "main": "dist/index.js", "types": "dist/index.d.ts", "files": [ From 8c46586dbc881cb9ac19118b879bd229775fa926 Mon Sep 17 00:00:00 2001 From: David Elner Date: Fri, 26 Jun 2026 18:34:29 +0000 Subject: [PATCH 2/3] Added: build flag to JS release action to allow skipping --- actions/release/lang/js/publish/action.yml | 9 ++++++++- actions/release/lang/js/validate/action.yml | 9 ++++++++- templates/actions/release/lang/js/publish.yml.erb | 10 ++++++++-- templates/actions/release/lang/js/validate.yml.erb | 10 ++++++++-- 4 files changed, 32 insertions(+), 6 deletions(-) diff --git a/actions/release/lang/js/publish/action.yml b/actions/release/lang/js/publish/action.yml index 83b95dd..88cf87c 100644 --- a/actions/release/lang/js/publish/action.yml +++ b/actions/release/lang/js/publish/action.yml @@ -27,8 +27,14 @@ inputs: description: "pnpm version to install. When empty, read from package.json packageManager." required: false default: '' + build: + description: > + Whether to run the build step. Set to 'false' for packages that publish + their sources directly (no build) — the build step then no-ops. + required: false + default: 'true' build_command: - description: "npm script to run for the build" + description: "npm script to run for the build (when build is 'true')" required: false default: 'build' dry_run: @@ -169,6 +175,7 @@ runs: run: pnpm install --frozen-lockfile - name: Build + if: ${{ inputs.build == 'true' }} shell: bash working-directory: ${{ inputs.working_directory }} env: diff --git a/actions/release/lang/js/validate/action.yml b/actions/release/lang/js/validate/action.yml index 67f5efe..c76c4a2 100644 --- a/actions/release/lang/js/validate/action.yml +++ b/actions/release/lang/js/validate/action.yml @@ -57,8 +57,14 @@ inputs: description: "Comma-separated allowlist of accepted release channels" required: false default: 'latest' + build: + description: > + Whether to run the build step. Set to 'false' for packages that publish + their sources directly (no build) — the build step then no-ops. + required: false + default: 'true' build_command: - description: "npm script to run for the build" + description: "npm script to run for the build (when build is 'true')" required: false default: 'build' outputs: @@ -226,6 +232,7 @@ runs: echo "Validated $TAG @ $SHA ($COMMIT_MSG)" - name: Build + if: ${{ inputs.build == 'true' }} shell: bash working-directory: ${{ inputs.working_directory }} env: diff --git a/templates/actions/release/lang/js/publish.yml.erb b/templates/actions/release/lang/js/publish.yml.erb index 582a291..4fcb656 100644 --- a/templates/actions/release/lang/js/publish.yml.erb +++ b/templates/actions/release/lang/js/publish.yml.erb @@ -26,8 +26,14 @@ inputs: description: "pnpm version to install. When empty, read from package.json packageManager." required: false default: '' + build: + description: > + Whether to run the build step. Set to 'false' for packages that publish + their sources directly (no build) — the build step then no-ops. + required: false + default: 'true' build_command: - description: "npm script to run for the build" + description: "npm script to run for the build (when build is 'true')" required: false default: 'build' dry_run: @@ -130,7 +136,7 @@ runs: <%= render_step('lang/js/setup') %> -<%= render_step('lang/js/build') %> +<%= render_step('lang/js/build', if: "${{ inputs.build == 'true' }}") %> <%= render_step('release/lang/js/npm-publish') %> diff --git a/templates/actions/release/lang/js/validate.yml.erb b/templates/actions/release/lang/js/validate.yml.erb index 2fffcbc..af25bb2 100644 --- a/templates/actions/release/lang/js/validate.yml.erb +++ b/templates/actions/release/lang/js/validate.yml.erb @@ -56,8 +56,14 @@ inputs: description: "Comma-separated allowlist of accepted release channels" required: false default: 'latest' + build: + description: > + Whether to run the build step. Set to 'false' for packages that publish + their sources directly (no build) — the build step then no-ops. + required: false + default: 'true' build_command: - description: "npm script to run for the build" + description: "npm script to run for the build (when build is 'true')" required: false default: 'build' outputs: @@ -98,6 +104,6 @@ runs: <%= render_step('release/validate', version: "${{ steps.read-version.outputs.version }}", tag_format: "${{ inputs.tag_format }}") %> -<%= render_step('lang/js/build') %> +<%= render_step('lang/js/build', if: "${{ inputs.build == 'true' }}") %> <%= render_step('lang/js/env-dump', if: "${{ failure() }}") %> From c322dade683ced4cadba1ee76c2af20cf6da9a33 Mon Sep 17 00:00:00 2001 From: David Elner Date: Tue, 30 Jun 2026 02:25:27 +0000 Subject: [PATCH 3/3] Changed: use pnpm for publishing --- actions/release/lang/js/publish/action.yml | 17 +++++----- .../steps/release/lang/js/npm-publish.yml.erb | 32 ++++++++----------- 2 files changed, 22 insertions(+), 27 deletions(-) diff --git a/actions/release/lang/js/publish/action.yml b/actions/release/lang/js/publish/action.yml index 88cf87c..c0ab986 100644 --- a/actions/release/lang/js/publish/action.yml +++ b/actions/release/lang/js/publish/action.yml @@ -193,25 +193,24 @@ runs: ACCESS: ${{ inputs.access }} PROVENANCE: ${{ inputs.provenance }} REGISTRY: ${{ inputs.registry }} - # Force OIDC trusted publishing: clear any inherited token so npm can't fall - # back to token auth. + # Force OIDC: clear any inherited token so the publish can't fall back to token auth. NODE_AUTH_TOKEN: '' NPM_TOKEN: '' run: | if [ "$NPM_ENABLED" != "true" ]; then - echo "npm publishing disabled; skipping." + echo "npm registry publishing disabled; skipping." exit 0 fi - # Pinned publish CLI: OIDC trusted publishing needs npm >= 11.5.1, newer than - # the npm bundled with setup-node. Bump deliberately, not via @latest. + # pnpm delegates the upload to this npm CLI; pin >= 11.5.1 for OIDC (newer than setup-node's). npm install -g "npm@11.6.2" - ARGS=(--tag "$CHANNEL" --access "$ACCESS" --registry "$REGISTRY") + # --no-git-checks: the publish tree may be patched/detached (e.g. an rc version in package.json). + ARGS=(--tag "$CHANNEL" --access "$ACCESS" --registry "$REGISTRY" --no-git-checks) [ "$PROVENANCE" = "true" ] && ARGS+=(--provenance) if [ "$DRY_RUN" = "true" ]; then - echo "DRY RUN: npm publish ${ARGS[*]} --dry-run" - npm publish "${ARGS[@]}" --dry-run + echo "DRY RUN: pnpm publish ${ARGS[*]} --dry-run" + pnpm publish "${ARGS[@]}" --dry-run else - npm publish "${ARGS[@]}" + pnpm publish "${ARGS[@]}" fi - name: Create GitHub release diff --git a/templates/steps/release/lang/js/npm-publish.yml.erb b/templates/steps/release/lang/js/npm-publish.yml.erb index 40a0200..cfeca53 100644 --- a/templates/steps/release/lang/js/npm-publish.yml.erb +++ b/templates/steps/release/lang/js/npm-publish.yml.erb @@ -1,14 +1,11 @@ <%# - Publishes the package to the npm registry with OIDC trusted - publishing (no token) and, optionally, provenance/attestation. On a dry run, - packs without pushing. Self-guards on the `npm` toggle and `dry_run` in shell, - so the composing action needs no `if:` on it. Assumes Node + the package - manager are already set up and the package is built. + Publishes to the npm registry via `pnpm publish` (the tool — not the `npm` CLI) + with OIDC trusted publishing (no token) and optional provenance. Assumes pnpm + + Node are already set up (lang/js/setup) and the package is built. Self-guards on + the `npm` toggle (publish to the npm registry or not) and `dry_run`. - npm is pinned to a known-good version internally (not @latest, not a consumer - input): tokenless OIDC trusted publishing requires npm >= 11.5.1, newer than the - npm bundled with setup-node. This is the publish CLI only — unrelated to the app's - own npm. Provenance requires a public package and `id-token: write`. + Uses pnpm, not npm, so pnpm-strict consumers (package.json devEngines/engines, + onFail: error) publish unchanged — a direct `npm publish` trips EBADDEVENGINES. -%> - name: Publish to npm shell: bash @@ -20,23 +17,22 @@ ACCESS: ${{ inputs.access }} PROVENANCE: ${{ inputs.provenance }} REGISTRY: ${{ inputs.registry }} - # Force OIDC trusted publishing: clear any inherited token so npm can't fall - # back to token auth. + # Force OIDC: clear any inherited token so the publish can't fall back to token auth. NODE_AUTH_TOKEN: '' NPM_TOKEN: '' run: | if [ "$NPM_ENABLED" != "true" ]; then - echo "npm publishing disabled; skipping." + echo "npm registry publishing disabled; skipping." exit 0 fi - # Pinned publish CLI: OIDC trusted publishing needs npm >= 11.5.1, newer than - # the npm bundled with setup-node. Bump deliberately, not via @latest. + # pnpm delegates the upload to this npm CLI; pin >= 11.5.1 for OIDC (newer than setup-node's). npm install -g "npm@11.6.2" - ARGS=(--tag "$CHANNEL" --access "$ACCESS" --registry "$REGISTRY") + # --no-git-checks: the publish tree may be patched/detached (e.g. an rc version in package.json). + ARGS=(--tag "$CHANNEL" --access "$ACCESS" --registry "$REGISTRY" --no-git-checks) [ "$PROVENANCE" = "true" ] && ARGS+=(--provenance) if [ "$DRY_RUN" = "true" ]; then - echo "DRY RUN: npm publish ${ARGS[*]} --dry-run" - npm publish "${ARGS[@]}" --dry-run + echo "DRY RUN: pnpm publish ${ARGS[*]} --dry-run" + pnpm publish "${ARGS[@]}" --dry-run else - npm publish "${ARGS[@]}" + pnpm publish "${ARGS[@]}" fi