Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions .github/workflows/release-js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I almost feel like we can hardcode the version

package_name: '@braintrust/bt-publishing-test' # exercises the npm-availability fail-fast check
channel: ${{ inputs.channel || 'latest' }}
allowed_channels: 'latest,rc,next,beta'
Expand Down Expand Up @@ -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
Expand Down
105 changes: 52 additions & 53 deletions actions/release/lang/js/publish/action.yml
Original file line number Diff line number Diff line change
@@ -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.

Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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 ==="
Expand Down
83 changes: 43 additions & 40 deletions actions/release/lang/js/validate/action.yml
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love that we build as part of a shared action. Could we point the action to build artifacts, like npm pack outputs (which are .tgz files)?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We had a conversation about this: what was highlighted is that what we have right now is an "all-inclusive, it just works" workflow that offers coverage for most required activities out of the box, hence why building is a part of it.

However, this is not to preclude flexibility its just a question of timing: in the future, the same building blocks that compose these high-level shared actions can also be provided directly to consumers so they can re-combine them as needed (a.k.a. omit the standard build, implement their own.)

This is a deliberate and an important element of the overall shared action design: not all consumers needs will be met by one action. Perfect is the enemy of the good, and its impractical to try to have one action do everything in all cases. In the face of a mismatch in constraints and capabilities, its better to have consumers use some parts than none.


For the purposes of this PR, it sounds like the feedback isn't blocking, but we should enable custom builds later when we roll out to the SDK repo.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exactly right, thanks!


- 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 ==="
Expand Down
Loading