Skip to content

ci: publish to npm via trusted publishing instead of a stored token #1190

ci: publish to npm via trusted publishing instead of a stored token

ci: publish to npm via trusted publishing instead of a stored token #1190

Workflow file for this run

name: Nightly release
on:
push:
branches:
- "**"
tags:
- "!**"
pull_request:
branches:
- main
workflow_dispatch:
inputs:
skip_build:
description: "Skip the build-napi job. Requires napi_run_id."
type: boolean
default: false
skip_release:
description: "Skip the release (publish) job; only build NAPI artifacts."
type: boolean
default: false
napi_run_id:
description: "Existing run ID to download NAPI artifacts from (only used when skip_build is true)."
type: string
default: ""
permissions:
contents: read
actions: read
env:
CARGO_TERM_COLOR: always
LINUX_GLIBC_VERSION: "2.17"
jobs:
build-napi:
name: Build NAPI - ${{ matrix.target }}
if: github.event_name != 'workflow_dispatch' || inputs.skip_build != true
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: macos-latest
target: x86_64-apple-darwin
platform: darwin-x64
- os: macos-latest
target: aarch64-apple-darwin
platform: darwin-arm64
- os: ubuntu-22.04
target: x86_64-unknown-linux-gnu
platform: linux-x64-gnu
- os: ubuntu-22.04
target: x86_64-unknown-linux-musl
platform: linux-x64-musl
- os: blacksmith-32vcpu-ubuntu-2404
target: aarch64-unknown-linux-gnu
platform: linux-arm64-gnu
- os: blacksmith-32vcpu-ubuntu-2404
target: aarch64-unknown-linux-musl
platform: linux-arm64-musl
- os: windows-latest
target: x86_64-pc-windows-msvc
platform: win32-x64-msvc
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup Vite+
uses: voidzero-dev/setup-vp@250f29ce396baf5e8f24498e17c0dfdebabc26eb # v1.15.0
with:
node-version-file: ".node-version"
cache: true
run-install: true
- name: Setup Rust
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with:
targets: ${{ matrix.target }}
- name: Mount sticky cache
if: startsWith(matrix.os, 'blacksmith-')
uses: ./.github/actions/mount-sticky-cache
with:
key-prefix: ${{ github.repository }}-nightly-${{ github.job }}-${{ matrix.target }}
cargo: "true"
target: target
- name: Cache cargo build
if: ${{ !startsWith(matrix.os, 'blacksmith-') }}
# zizmor: ignore[cache-poisoning] save-if restricts writes to main pushes, so PR runs cannot poison release builds.
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2.9.1
with:
shared-key: nightly-${{ matrix.target }}
# Only save cache on main pushes to prevent PR runs from poisoning the cache used by release builds.
save-if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
- name: Install zig (Linux)
if: contains(matrix.target, 'unknown-linux')
run: |
curl -fsSL https://ziglang.org/download/0.13.0/zig-linux-x86_64-0.13.0.tar.xz -o zig.tar.xz
tar -xf zig.tar.xz
echo "$PWD/zig-linux-x86_64-0.13.0" >> "$GITHUB_PATH"
- name: Install cargo-zigbuild (Linux)
if: contains(matrix.target, 'unknown-linux')
run: cargo install cargo-zigbuild
- name: Build NAPI (Linux)
if: contains(matrix.target, 'unknown-linux')
working-directory: crates/ox_content_napi
shell: bash
run: |
set -euo pipefail
build_target="${{ matrix.target }}"
if [[ "${{ matrix.target }}" == *-unknown-linux-gnu ]]; then
build_target="${build_target}.${LINUX_GLIBC_VERSION}"
else
export RUSTFLAGS="${RUSTFLAGS:-} -C target-feature=-crt-static"
fi
cargo zigbuild --release --target "$build_target"
src="../../target/${{ matrix.target }}/release/libox_content_napi.so"
if [[ ! -f "$src" ]]; then
src="../../target/${build_target}/release/libox_content_napi.so"
fi
cp "$src" "ox-content.${{ matrix.platform }}.node"
- name: Verify Linux native dependencies
if: contains(matrix.target, 'unknown-linux')
shell: bash
run: |
set -euo pipefail
node_file="crates/ox_content_napi/ox-content.${{ matrix.platform }}.node"
if [[ "${{ matrix.target }}" == *-unknown-linux-gnu ]]; then
max_glibc="$(
readelf --version-info "$node_file" |
sed -n 's/.*Name: GLIBC_\([0-9.]*\).*/\1/p' |
sort -V |
tail -1
)"
echo "max GLIBC version: ${max_glibc:-none}"
if [[ -n "$max_glibc" ]] &&
[[ "$(printf '%s\n' "$LINUX_GLIBC_VERSION" "$max_glibc" | sort -V | tail -1)" != "$LINUX_GLIBC_VERSION" ]]; then
echo "::error::${node_file} requires GLIBC_${max_glibc}, expected <= GLIBC_${LINUX_GLIBC_VERSION}"
exit 1
fi
else
if readelf --version-info "$node_file" | grep -q 'GLIBC_'; then
echo "::error::${node_file} unexpectedly references GLIBC symbols"
exit 1
fi
fi
- name: Build NAPI
if: ${{ !contains(matrix.target, 'unknown-linux') }}
working-directory: crates/ox_content_napi
run: vp exec -- napi build --release --target ${{ matrix.target }}
- name: Upload artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: napi-${{ matrix.target }}
path: crates/ox_content_napi/*.node
if-no-files-found: error
release:
name: Publish preview via pkg-pr-new
runs-on: blacksmith-32vcpu-ubuntu-2404
needs: build-napi
# Skip when explicitly disabled via workflow_dispatch.
# Use always() so this still runs when build-napi was skipped (skip_build mode).
if: |
always() &&
(github.event_name != 'workflow_dispatch' || inputs.skip_release != true) &&
(needs.build-napi.result == 'success' ||
(github.event_name == 'workflow_dispatch' && inputs.skip_build == true))
steps:
- name: Validate napi_run_id when skip_build is true
if: github.event_name == 'workflow_dispatch' && inputs.skip_build == true
env:
NAPI_RUN_ID: ${{ inputs.napi_run_id }}
run: |
if [ -z "$NAPI_RUN_ID" ]; then
echo "::error::napi_run_id input is required when skip_build is true."
exit 1
fi
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup Vite+
uses: voidzero-dev/setup-vp@250f29ce396baf5e8f24498e17c0dfdebabc26eb # v1.15.0
with:
node-version-file: ".node-version"
cache: true
run-install: true
- name: Download artifacts (same run)
if: github.event_name != 'workflow_dispatch' || inputs.skip_build != true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: napi-*
path: crates/ox_content_napi/artifacts
- name: Download artifacts (from previous run)
if: github.event_name == 'workflow_dispatch' && inputs.skip_build == true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: napi-*
path: crates/ox_content_napi/artifacts
run-id: ${{ inputs.napi_run_id }}
github-token: ${{ github.token }}
# napi-cli 3.8 rejects managed paths that escape the boundary it discovers
# from its cwd, so point --cwd at the workspace root and keep every path
# relative to it. `binding-packages` then resolves inside the pnpm
# workspace instead of escaping the crate directory.
- name: Create npm sub-package directories
working-directory: crates/ox_content_napi
run: >-
vp exec -- napi create-npm-dirs --cwd ../..
--package-json-path crates/ox_content_napi/package.json
--npm-dir binding-packages
- name: Move artifacts to sub-packages
working-directory: crates/ox_content_napi
run: |
declare -A target_map=(
["x86_64-apple-darwin"]="darwin-x64"
["aarch64-apple-darwin"]="darwin-arm64"
["x86_64-unknown-linux-gnu"]="linux-x64-gnu"
["x86_64-unknown-linux-musl"]="linux-x64-musl"
["aarch64-unknown-linux-gnu"]="linux-arm64-gnu"
["aarch64-unknown-linux-musl"]="linux-arm64-musl"
["x86_64-pc-windows-msvc"]="win32-x64-msvc"
)
for dir in artifacts/napi-*; do
target="${dir##*/napi-}"
platform="${target_map[$target]}"
if [ -n "$platform" ]; then
for f in "$dir"/*.node; do
cp "$f" "../../binding-packages/${platform}/ox-content.${platform}.node"
done
fi
done
echo "=== Verify binding packages ==="
for pkg in ../../binding-packages/*/; do
ls -la "$pkg"*.node 2>/dev/null || echo "WARNING: No .node file in $pkg"
done
# Same step as publish.yml: rewrite the main package's
# optionalDependencies to reference the binding sub-packages.
# Without this, consumers installing @ox-content/napi cannot
# resolve the matching binding. --skip-optional-publish tells
# napi-cli not to publish the optional sub-packages itself
# (pkg-pr-new handles that downstream).
- name: Update optionalDependencies (napi pre-publish)
working-directory: crates/ox_content_napi
run: >-
vp exec -- napi pre-publish -t npm --cwd ../..
--package-json-path crates/ox_content_napi/package.json
--npm-dir binding-packages --no-gh-release --skip-optional-publish
# `pnpm build` recompiles @ox-content/napi via `napi build --release`,
# so the job needs a Rust toolchain. Pin stable instead of relying on the
# runner image's preinstalled rustc: the Blacksmith image ships an older
# rustc than the oxc dependencies' MSRV (1.93).
- name: Setup Rust
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
- name: Mount sticky cache
uses: ./.github/actions/mount-sticky-cache
with:
key-prefix: ${{ github.repository }}-nightly-${{ github.job }}
cargo: "true"
target: target
- name: Build TS packages
run: vp exec -- pnpm --filter "@ox-content/*" build
- name: Publish to pkg-pr-new
id: publish-pkg-pr-new
continue-on-error: ${{ github.event_name == 'pull_request' }}
run: |
vp exec -- pnpx pkg-pr-new publish --compact --pnpm \
./crates/ox_content_napi \
./binding-packages/darwin-x64 \
./binding-packages/darwin-arm64 \
./binding-packages/linux-x64-gnu \
./binding-packages/linux-x64-musl \
./binding-packages/linux-arm64-gnu \
./binding-packages/linux-arm64-musl \
./binding-packages/win32-x64-msvc \
./npm/ox-content-islands \
./npm/unplugin-ox-content \
./npm/vite-plugin-ox-content \
./npm/vite-plugin-ox-content-react \
./npm/vite-plugin-ox-content-solid \
./npm/vite-plugin-ox-content-svelte \
./npm/vite-plugin-ox-content-vue
- name: Warn when preview publish is unavailable
if: github.event_name == 'pull_request' && steps.publish-pkg-pr-new.outcome == 'failure'
run: echo "::warning::pkg-pr-new preview publish failed; keeping pull request CI non-blocking for the external preview service."