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..c0ab986 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,24 +18,23 @@ 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" + 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 empty, read from package.json packageManager." required: false default: '' - pnpm_version: - description: "pnpm version to install (when package_manager is pnpm)" + 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: '10.33.0' + 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: @@ -65,10 +64,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 +136,52 @@ 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 + if: ${{ inputs.build == 'true' }} 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,24 +193,24 @@ 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. + # 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 - npm install -g "npm@$NPM_VERSION" - ARGS=(--tag "$CHANNEL" --access "$ACCESS" --registry "$REGISTRY") + # 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" + # --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 @@ -413,14 +414,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..c76c4a2 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 @@ -64,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: @@ -97,37 +96,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 @@ -227,26 +232,24 @@ runs: echo "Validated $TAG @ $SHA ($COMMIT_MSG)" - name: Build + if: ${{ inputs.build == 'true' }} 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..4fcb656 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,24 +17,23 @@ 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" + 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 empty, read from package.json packageManager." required: false default: '' - pnpm_version: - description: "pnpm version to install (when package_manager is pnpm)" + 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: '10.33.0' + 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: @@ -64,10 +63,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,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 6288a0e..af25bb2 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 @@ -63,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: @@ -105,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() }}") %> 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..cfeca53 100644 --- a/templates/steps/release/lang/js/npm-publish.yml.erb +++ b/templates/steps/release/lang/js/npm-publish.yml.erb @@ -1,13 +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 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`. + 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 @@ -19,22 +17,22 @@ 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. + # 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 - npm install -g "npm@$NPM_VERSION" - ARGS=(--tag "$CHANNEL" --access "$ACCESS" --registry "$REGISTRY") + # 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" + # --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 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": [