Skip to content
Open
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
49 changes: 46 additions & 3 deletions .github/blocks/publish-npm-packages/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ inputs:
required: false
default: 'false'
npm-token:
description: '[secret] NPM authentication token'
required: true
description: 'NPM authentication token. Used only as a fallback when npm OIDC Trusted Publishing is unavailable or fails. Not required if OIDC Trusted Publishing is fully configured for every package being released.'
required: false

runs:
using: 'composite'
Expand Down Expand Up @@ -81,11 +81,54 @@ runs:
(cd "$PKG_PATH" && $BUILD_CMD)
fi

# Dry-run only validates packaging, so do it before any auth checks.
# `bun publish --dry-run` is fine here because it doesn't hit the
# registry; we keep it on bun so workspace:* deps still resolve.
if [ "$DRY_RUN" = "true" ]; then
echo "Dry run: would publish $NAME with tag $TAG"
(cd "$PKG_PATH" && bun publish --tag "$TAG" --access public --dry-run)
echo ""
continue
fi

# Step 1: try npm OIDC Trusted Publishing (tokenless, with
# provenance). bun publish doesn't yet support OIDC
# (oven-sh/bun#15601), so we use the documented workaround: pack
# with bun (preserves workspace:* rewriting that npm pack would
# leave broken) and publish the tarball with npm. The runner only
# sets ACTIONS_ID_TOKEN_REQUEST_TOKEN when the job has
# 'id-token: write', so when that permission is absent we skip OIDC
# entirely rather than run a publish that is guaranteed to fail on
# auth.
PUBLISHED=false
if [ -n "${ACTIONS_ID_TOKEN_REQUEST_TOKEN:-}" ]; then
echo "🔒 Attempting publish of $NAME via npm Trusted Publishing (OIDC)..."
if (
cd "$PKG_PATH"
rm -f -- *.tgz 2>/dev/null || true
bun pm pack
TARBALL=$(ls -t -- *.tgz | head -n 1)
[ -n "$TARBALL" ]
npm publish "$TARBALL" --tag "$TAG" --access public --provenance
); then
echo "✅ Published $NAME via OIDC Trusted Publishing."
PUBLISHED=true
else
echo "⚠️ OIDC publish of $NAME failed. Falling back to NPM token."
Comment on lines +94 to +117

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE=".github/blocks/publish-npm-packages/action.yaml"
if [ ! -f "$FILE" ]; then
  echo "Missing file: $FILE" >&2
  exit 1
fi

echo "== Relevant inputs defaults =="
sed -n '1,180p' "$FILE" | nl -ba | sed -n '1,180p'

echo
echo "== Locate node-version default explicitly =="
rg -n "node-version" "$FILE" || true

echo
echo "== Locate OIDC/publish logic =="
rg -n "Trusted Publishing|ACTIONS_ID_TOKEN_REQUEST_TOKEN|--provenance|npm publish" "$FILE" || true

Repository: photon-hq/buildspace

Length of output: 157


🌐 Web query:

npm trusted publishing minimum node version 22.14.0 npm 11.5.1

💡 Result:

To utilize npm trusted publishing, you must use npm CLI version 11.5.1 or later and Node.js version 22.14.0 or higher [1][2][3][4]. These minimum version requirements ensure compatibility with the OIDC-based authentication mechanism used to establish trust relationships between your package and CI/CD providers [1][3]. If your environment is running an older version of Node.js or npm, you can satisfy the requirement by manually updating npm within your CI/CD workflow before performing the publish operation, for example by running: npm install -g npm@latest [2][4][5] Alternatively, you can ensure your CI/CD runner is using an environment that includes these versions by default (e.g., Node.js 24+) [6][5].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE=".github/blocks/publish-npm-packages/action.yaml"
test -f "$FILE" || { echo "Missing file: $FILE" >&2; exit 1; }

echo "== File head =="
sed -n '1,120p' "$FILE"

echo
echo "== Inputs section (best-effort) =="
rg -n "inputs:|node-version|npm-token|provenance|dry-run" "$FILE" || true

echo
echo "== Around node-version definition =="
# print a small window around the first node-version match
LINE="$(rg -n "node-version" "$FILE" | head -n 1 | cut -d: -f1)"
START=$((LINE-20)); END=$((LINE+20))
sed -n "${START},${END}p" "$FILE"

echo
echo "== Around OIDC publish branch =="
rg -n "Trusted Publishing|ACTIONS_ID_TOKEN_REQUEST_TOKEN|bun pm pack|npm publish.*--provenance" "$FILE" || true
OIDC_LINE="$(rg -n "ACTIONS_ID_TOKEN_REQUEST_TOKEN" "$FILE" | head -n 1 | cut -d: -f1)"
START=$((OIDC_LINE-25)); END=$((OIDC_LINE+70))
sed -n "${START},${END}p" "$FILE"

Repository: photon-hq/buildspace

Length of output: 7272


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE=".github/blocks/publish-npm-packages/action.yaml"
test -f "$FILE"

echo "== OIDC block + fallback (lines 80-180) =="
awk 'NR>=80 && NR<=180 {printf "%5d:%s\n", NR, $0}' "$FILE"

Repository: photon-hq/buildspace

Length of output: 3145


🌐 Web query:

Node.js 24 bundled npm version (npm CLI version) trusted publishing npm 11.5.1

💡 Result:

Node.js 24 was released on May 6, 2025 [1][2], and shipped with npm 11.0.0 [3][2]. Node.js versions are bundled with specific minor versions of npm that evolve over the lifecycle of the release line through subsequent updates [4]. Regarding trusted publishing: - Trusted publishing is a security feature that allows developers to publish packages from CI/CD environments using OpenID Connect (OIDC) rather than long-lived authentication tokens [5]. - The official npm documentation explicitly states that trusted publishing requires npm CLI version 11.5.1 or later [5][6]. - Because Node.js 24 receives regular updates, the bundled npm version has been incremented beyond 11.5.1 in later releases [4]. For example, Node.js 24.14.0 included npm 11.9.0 [4], and Node.js 24.15.0 included npm 11.12.1 [4]. Therefore, users running a recent version of Node.js 24 will satisfy the npm version requirement for trusted publishing by default [5][7]. If you are using an earlier release of Node.js 24 that includes an npm version older than 11.5.1, you can update npm globally or within your CI/CD environment using the command: npm install -g npm@latest [8][7]. Users can also use the npm trust command, which was introduced to manage these relationships directly from the CLI [8][9].

Citations:


Fix OIDC publish default Node runtime to meet npm trusted publishing requirements

The OIDC path runs npm publish ... --provenance, but the action defaults inputs.node-version to 20. npm trusted publishing requires Node >= 22.14.0 and npm CLI >= 11.5.1, so callers enabling OIDC without overriding node-version can hit the “OIDC failed → fallback” path (or hard-fail if npm-token wasn’t provided).

Suggested fix
  node-version:
    description: '[string] Node.js version'
    required: false
-    default: '20'
+    default: '24'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/blocks/publish-npm-packages/action.yaml around lines 94 - 117, The
OIDC publish path requires Node >=22.14.0 and npm CLI >=11.5.1 but the action
defaults inputs.node-version to 20, causing OIDC publishes to fail; update the
action default for inputs.node-version to at least 22.14.0 (or a newer LTS like
22.x/24.x) and ensure the workflow's setup steps use that input before the OIDC
publish block, and either add a step to upgrade npm to >=11.5.1 (e.g., npm
install -g npm@11.5.1+) or document/validate the npm version before running the
OIDC publish; specifically change the default inputs.node-version and verify the
OIDC publish section (the code around PUBLISHED and the npm publish --provenance
invocation) runs under that Node/npm version.

fi
else
echo "Publishing $NAME with tag $TAG"
echo "ℹ️ No OIDC id-token available (job lacks 'id-token: write'). Skipping OIDC; will use NPM token."
fi

# Step 2: fall back to the traditional token-based publish. Same
# bun publish flow as before, so workspace:* deps continue to
# resolve. Only reached when OIDC was skipped or failed.
if [ "$PUBLISHED" != "true" ]; then
if [ -z "$NODE_AUTH_TOKEN" ]; then
echo "❌ OIDC was unavailable or failed, and no NPM token was provided. Cannot publish $NAME."
exit 1
fi
echo "🔑 Publishing $NAME via NPM token with tag $TAG"
(cd "$PKG_PATH" && bun publish --tag "$TAG" --access public)
fi

Expand Down
40 changes: 38 additions & 2 deletions .github/workflows/typescript-monorepo-release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,18 @@ on:
required: false
default: false
description: "Opt in to Blacksmith Linux runners (blacksmith-4vcpu-ubuntu-2404). Requires the Blacksmith GitHub App installed on the caller org. Defaults to ubuntu-latest."
use-oidc:
type: boolean
required: false
default: false
description: "Opt in to npm OIDC Trusted Publishing. When true, the publish job inherits the caller's GITHUB_TOKEN so it can use 'id-token: write' (the caller must grant it) and a trusted publisher must be configured on npmjs.com for every package being released. Falls back to NPM_TOKEN if OIDC is unavailable. When false (default), publishing uses NPM_TOKEN and the job keeps least-privilege 'contents: read'."
secrets:
OPENAI_API_KEY:
required: true
description: "OpenAI API key for version and notes"
NPM_TOKEN:
required: true
description: "NPM token for publishing"
required: false
description: "NPM token (required for npm publish when not using OIDC Trusted Publishing, or as a fallback when OIDC isn't configured for some packages)"
Comment on lines 73 to +75

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Restore an early guard for the non-OIDC publish path.

Making NPM_TOKEN optional here removes the only fail-fast check for the default use-oidc: false path. If a caller forgets the secret, this workflow now gets through version bumping, and github-release can run in parallel with npm-publish, so you can create a GitHub release before publish finally dies inside the block. Please add a preflight validation that fails before bumping/releasing when dry-run is false, use-oidc is false, and NPM_TOKEN is empty.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/typescript-monorepo-release.yaml around lines 73 - 75, Add
a preflight validation step before any version bump/release steps that checks
the workflow inputs: if inputs.dry-run is "false" AND inputs.use-oidc is "false"
AND the NPM_TOKEN secret is empty, fail the run immediately; implement this as a
dedicated job/step (e.g., id "validate-publish-params" or a top-of-job step
before "bump-version"/"github-release"/"npm-publish") that uses an if-condition
and a small shell script to echo a clear error and exit 1 when the three
conditions are met so the workflow fails fast instead of proceeding to version
bumping or parallel release steps.

APP_ID:
required: false
description: "GitHub App ID for pushing to protected branches and triggering downstream workflows"
Expand Down Expand Up @@ -175,8 +180,12 @@ jobs:
app-id: ${{ secrets.APP_ID }}
app-private-key: ${{ secrets.APP_PRIVATE_KEY }}

# Default publish path (use-oidc: false). Keeps least-privilege
# `contents: read` exactly as before and publishes via NPM_TOKEN. Existing
# callers are unaffected — no permission changes required on their side.
npm-publish:
needs: [check-labels, detect-changes, bump-versions]
if: inputs.use-oidc != true
runs-on: ${{ inputs.use-blacksmith && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }}
permissions:
contents: read
Expand All @@ -196,3 +205,30 @@ jobs:
root-build-command: ${{ inputs.root-build-command }}
dry-run: ${{ inputs.dry-run }}
npm-token: ${{ secrets.NPM_TOKEN }}

# OIDC publish path (use-oidc: true). Opt-in only. It omits an explicit
# `permissions:` block so it inherits the caller's GITHUB_TOKEN — meaning
# `id-token: write` flows through when the caller grants it. Because it never
# *requests* id-token, callers that haven't granted it can't be hard-failed;
# the publish step just falls back to NPM_TOKEN. Default callers never reach
# this job (the `if` below is false), so their permissions are untouched.
npm-publish-oidc:
needs: [check-labels, detect-changes, bump-versions]
if: inputs.use-oidc == true
runs-on: ${{ inputs.use-blacksmith && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }}
steps:
- uses: actions/checkout@v5
with:
ref: ${{ github.ref_name }}
fetch-depth: 0
Comment on lines +220 to +223

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Disable checkout credential persistence in the OIDC publish job (npm-publish-oidc).

The actions/checkout@v5 step at ref/fetch-depth omits persist-credentials: false; actions/checkout defaults it to true, leaving authenticated credentials available to later steps in the same job. Set persist-credentials: false on this checkout step.

Suggested fix
      - uses: actions/checkout@v5
        with:
          ref: ${{ github.ref_name }}
          fetch-depth: 0
+         persist-credentials: false
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- uses: actions/checkout@v5
with:
ref: ${{ github.ref_name }}
fetch-depth: 0
- uses: actions/checkout@v5
with:
ref: ${{ github.ref_name }}
fetch-depth: 0
persist-credentials: false
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 220-223: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 220-220: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/typescript-monorepo-release.yaml around lines 220 - 223,
The checkout step using actions/checkout@v5 in the npm-publish-oidc job leaves
authenticated credentials persisted; update that checkout invocation to set
persist-credentials: false so credentials are not kept for later steps. Locate
the actions/checkout@v5 step (the checkout step in the npm-publish-oidc job) and
add the persist-credentials: false key alongside ref and fetch-depth.


- name: Publish NPM Packages
uses: photon-hq/buildspace/.github/blocks/publish-npm-packages@main
with:
changed-packages: ${{ needs.detect-changes.outputs.changed }}
bun-version: ${{ inputs.bun-version }}
tag: ${{ (inputs.prerelease || fromJSON(needs.check-labels.outputs.labels).prerelease) && 'beta' || inputs.npm-tag }}
build-command: ${{ inputs.build-command }}
root-build-command: ${{ inputs.root-build-command }}
dry-run: ${{ inputs.dry-run }}
npm-token: ${{ secrets.NPM_TOKEN }}
18 changes: 14 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -341,13 +341,14 @@ Complete release pipeline for TypeScript/JavaScript monorepos with independently
| `prerelease` | boolean | No | `false` | Force prerelease |
| `release` | boolean | No | `false` | Force release (bypasses label check) |
| `dry-run` | boolean | No | `false` | Test without actually publishing |
| `use-oidc` | boolean | No | `false` | Opt in to npm OIDC Trusted Publishing (requires caller `id-token: write` + a trusted publisher configured on npmjs.com for every package being released). Falls back to `NPM_TOKEN`. Default keeps least-privilege token publishing |

#### Secrets

| Secret | Required | Description |
|--------|----------|-------------|
| `OPENAI_API_KEY` | Yes | For AI-powered versioning and release notes |
| `NPM_TOKEN` | Yes | npm authentication token |
| `NPM_TOKEN` | No | npm auth token; used as the fallback when OIDC Trusted Publishing isn't configured for a package |
| `APP_ID` | No | GitHub App ID (for pushing to protected branches) |
| `APP_PRIVATE_KEY` | No | GitHub App private key (for pushing to protected branches) |

Expand All @@ -360,6 +361,7 @@ jobs:
permissions:
contents: write
pull-requests: read
# id-token: write # only needed when use-oidc: true (npm Trusted Publishing)
with:
service-name: photon-ts
packages: |
Expand All @@ -370,11 +372,14 @@ jobs:
]
root-build-command: "turbo build"
include-dependents: true
# use-oidc: true # opt in to npm OIDC Trusted Publishing
secrets:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
```

> **Enabling OIDC Trusted Publishing (opt-in):** set `use-oidc: true`, add `id-token: write` to the caller's `permissions`, and configure a [trusted publisher](https://docs.npmjs.com/trusted-publishers) **for every package** in the monorepo on npmjs.com. The runner needs npm ≥ 11.5.1. Since `bun publish` doesn't speak OIDC yet ([oven-sh/bun#15601](https://github.com/oven-sh/bun/issues/15601)), the OIDC path packs each package with `bun pm pack` (preserving `workspace:*` rewriting) and publishes the tarball with `npm publish --provenance`; the token fallback continues to use `bun publish` exactly as before. Callers that don't set `use-oidc` are completely unaffected — they keep `contents: read` least-privilege and publish via `NPM_TOKEN` exactly as before.

#### How It Works

```
Expand Down Expand Up @@ -1076,7 +1081,7 @@ Determines versions for all changed monorepo packages using a single AI call, bu

**Path:** `.github/blocks/publish-npm-packages/action.yaml`

Builds and publishes multiple monorepo packages to npm in dependency order. Supports both per-package builds and a single root build command.
Builds and publishes multiple monorepo packages to npm in dependency order. Supports both per-package builds and a single root build command. Tries npm OIDC Trusted Publishing first (per package, when the calling job has `id-token: write`), then falls back to token-based `bun publish` with `npm-token`.

#### Inputs

Expand All @@ -1088,8 +1093,8 @@ Builds and publishes multiple monorepo packages to npm in dependency order. Supp
| `tag` | string | No | `latest` | npm dist-tag |
| `build-command` | string | No | `bun run build` | Per-package build command (ignored if `root-build-command` is set) |
| `root-build-command` | string | No | `""` | Build once at repo root (e.g., `turbo build`) |
| `dry-run` | boolean | No | `false` | Run `npm publish --dry-run` |
| `npm-token` | secret | Yes | — | npm authentication token |
| `dry-run` | boolean | No | `false` | Run `bun publish --dry-run` |
| `npm-token` | secret | No | — | npm token; used only as a fallback when OIDC Trusted Publishing is unavailable or fails for a package |

#### Usage

Expand All @@ -1101,6 +1106,11 @@ Builds and publishes multiple monorepo packages to npm in dependency order. Supp
npm-token: ${{ secrets.NPM_TOKEN }}
```

#### Publishing modes

1. **OIDC Trusted Publishing (preferred):** when the calling job has `id-token: write`, each package is packed with `bun pm pack` (preserving `workspace:*` rewriting) and published tokenlessly with `npm publish <tarball> --provenance`. Requires a configured [trusted publisher](https://docs.npmjs.com/trusted-publishers) for **every** package on npmjs.com and npm ≥ 11.5.1 on the runner. Done per-package, so partial OIDC coverage falls back per-package.
2. **Token fallback:** if OIDC is unavailable (no `id-token: write`) or fails for a package, that package falls back to `bun publish` with `npm-token` (same behavior as before). This is the default today — keep providing `NPM_TOKEN` until every package has a trusted publisher configured.

---

### rust-build
Expand Down
Loading