Files
scriptc/.github/workflows/release.yml
T
Chris TateandCruel f5ed014cc7 Preserve glibc 2.36 runtime compatibility (#365)
- Pin GNU runtime packs to glibc 2.36 Zig targets and reject incompatible imports.

- Provision verified Zig builds and exercise published artifacts on Debian Bookworm.

Fixes #349

Co-authored-by: Cruel <383999+Cruel@users.noreply.github.com>
2026-09-20 22:36:10 -05:00

404 lines
16 KiB
YAML

name: Release
on:
push:
branches:
- main
workflow_dispatch:
concurrency: ${{ github.workflow }}-${{ github.ref }}
jobs:
check-release:
name: Check for new version
# The private mirror carries this file too; only the public repo
# publishes (the trusted publisher is pinned to it).
if: github.repository == 'vercel-labs/scriptc'
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
outputs:
should_release: ${{ steps.check.outputs.should_release }}
version: ${{ steps.check.outputs.version }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "24"
registry-url: "https://registry.npmjs.org"
- name: Compare package.json version to npm
id: check
run: |
LOCAL_VERSION=$(node -p "require('./packages/cli/package.json').version")
echo "Local version: $LOCAL_VERSION"
NPM_VERSION=$(npm view scriptc version 2>/dev/null || echo "0.0.0")
echo "npm version: $NPM_VERSION"
if [ "$LOCAL_VERSION" != "$NPM_VERSION" ]; then
echo "Version changed: $NPM_VERSION -> $LOCAL_VERSION"
echo "should_release=true" >> "$GITHUB_OUTPUT"
else
echo "Version unchanged on npm, skipping publish"
echo "should_release=false" >> "$GITHUB_OUTPUT"
fi
echo "version=$LOCAL_VERSION" >> "$GITHUB_OUTPUT"
build-native-packages:
name: Build native package (${{ matrix.platform }})
needs: check-release
if: needs.check-release.outputs.should_release == 'true'
strategy:
fail-fast: false
matrix:
include:
- platform: darwin-arm64
runner: macos-15
helper: llvm-darwin-arm64
runtime: runtime-darwin-arm64
- platform: darwin-x64
runner: macos-15-intel
helper: llvm-darwin-x64
runtime: runtime-darwin-x64
- platform: linux-x64
runner: ubuntu-24.04
helper: llvm-linux-x64-gnu
runtime: runtime-linux-x64-gnu
llvm_asset: LLVM-22.1.8-Linux-X64
use_zig: true
- platform: linux-arm64
runner: ubuntu-24.04-arm
helper: llvm-linux-arm64-gnu
runtime: runtime-linux-arm64-gnu
llvm_asset: LLVM-22.1.8-Linux-ARM64
use_zig: true
- platform: windows-x64
runner: windows-2022
helper: llvm-win32-x64-msvc
runtime: runtime-win32-x64-msvc
use_zig: true
- platform: linux-x64-musl
runner: ubuntu-24.04
helper: llvm-linux-x64-musl
runtime: runtime-linux-x64-musl
llvm_asset: LLVM-22.1.8-Linux-X64
use_zig: true
- platform: linux-arm64-musl
runner: ubuntu-24.04-arm
helper: llvm-linux-arm64-musl
runtime: runtime-linux-arm64-musl
llvm_asset: LLVM-22.1.8-Linux-ARM64
use_zig: true
- platform: wasm32-wasi
runner: ubuntu-24.04
helper: llvm-linux-x64-gnu
runtime: runtime-wasm32-wasi
llvm_asset: LLVM-22.1.8-Linux-X64
use_zig: true
runs-on: ${{ matrix.runner }}
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 11.1.3
- uses: actions/setup-node@v4
with:
node-version: "24"
cache: pnpm
- name: Install toolchain (macOS)
if: startsWith(matrix.platform, 'darwin')
run: |
brew install llvm@22
llvm_prefix=$(brew --prefix llvm@22)
echo "$llvm_prefix/bin" >> "$GITHUB_PATH"
echo "LLVM_DIR=$llvm_prefix/lib/cmake/llvm" >> "$GITHUB_ENV"
- name: Install pinned LLVM development distribution (Linux)
if: startsWith(matrix.platform, 'linux') || matrix.platform == 'wasm32-wasi'
run: |
sudo apt-get update
sudo apt-get install --yes g++ zlib1g-dev libzstd-dev
curl -fL --retry 3 -o "$RUNNER_TEMP/llvm.tar.xz" "https://github.com/llvm/llvm-project/releases/download/llvmorg-22.1.8/${{ matrix.llvm_asset }}.tar.xz"
tar -xJf "$RUNNER_TEMP/llvm.tar.xz" -C "$RUNNER_TEMP"
mv "$RUNNER_TEMP/${{ matrix.llvm_asset }}" "$RUNNER_TEMP/llvm-22.1.8"
echo "LLVM_DIR=$RUNNER_TEMP/llvm-22.1.8/lib/cmake/llvm" >> "$GITHUB_ENV"
echo "$RUNNER_TEMP/llvm-22.1.8/bin" >> "$GITHUB_PATH"
- uses: vercel-labs/setup-zig@v1
if: matrix.use_zig == true
with:
version: 0.16.0
- name: Install pinned LLVM development distribution (Windows)
if: matrix.platform == 'windows-x64'
shell: pwsh
run: |
# The complete archive supplies LLVM's exported CMake targets and
# static libraries. windows-2022 includes CMake, Ninja, VS 2022,
# and 7-Zip; unlike tar.exe, 7-Zip expands this archive quickly.
$archive = "$env:RUNNER_TEMP\clang+llvm-22.1.8-x86_64-pc-windows-msvc.tar.xz"
$llvm = "$env:RUNNER_TEMP\llvm-22.1.8"
$expanded = "$env:RUNNER_TEMP\llvm-expanded"
$sevenZip = "$env:ProgramFiles\7-Zip\7z.exe"
if (-not (Test-Path $sevenZip)) {
choco install 7zip --no-progress -y
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
}
if (-not (Test-Path $sevenZip)) { throw "7-Zip is unavailable" }
curl.exe -fL --retry 3 -o $archive https://github.com/llvm/llvm-project/releases/download/llvmorg-22.1.8/clang%2Bllvm-22.1.8-x86_64-pc-windows-msvc.tar.xz
& $sevenZip x -y "-o$expanded" $archive
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
& $sevenZip x -y "-o$expanded" "$expanded\clang+llvm-22.1.8-x86_64-pc-windows-msvc.tar"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
Move-Item "$expanded\clang+llvm-22.1.8-x86_64-pc-windows-msvc" $llvm
echo "LLVM_DIR=$llvm\lib\cmake\llvm" >> $env:GITHUB_ENV
echo "$llvm\bin" >> $env:GITHUB_PATH
- run: pnpm install --frozen-lockfile
- name: Build native packages (POSIX)
if: matrix.platform != 'windows-x64'
run: |
pnpm --filter @scriptc/${{ matrix.helper }} build:native
if [ "${{ matrix.use_zig }}" = true ]; then
CC=zig AR=zig pnpm --filter @scriptc/${{ matrix.runtime }} build:native
else
CC=clang AR=llvm-ar pnpm --filter @scriptc/${{ matrix.runtime }} build:native
fi
- name: Build native packages (Windows)
if: matrix.platform == 'windows-x64'
run: |
pnpm --filter @scriptc/${{ matrix.helper }} build:native
pnpm --filter @scriptc/${{ matrix.runtime }} build:native
- uses: actions/upload-artifact@v4
with:
name: native-${{ matrix.platform }}
path: |
packages/${{ matrix.helper }}
packages/${{ matrix.runtime }}
retention-days: 1
publish:
name: Publish to npm
needs: [check-release, build-native-packages]
if: needs.check-release.outputs.should_release == 'true'
runs-on: macos-15
timeout-minutes: 15
environment: Release
permissions:
contents: read
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Download native packages
uses: actions/download-artifact@v4
with:
pattern: native-*
path: native-artifacts
merge-multiple: true
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 11.1.3
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "24"
registry-url: "https://registry.npmjs.org"
# Publishing uses npm trusted publishing (OIDC): the job's id-token
# permission lets npm mint short-lived credentials, so no npm token
# secret exists anywhere in this repo. The runtime, every shipped
# platform runtime/helper package, compiler, and CLI must each be
# configured on npmjs.com with a GitHub Actions trusted publisher
# pointing at repository vercel-labs/scriptc, workflow release.yml,
# environment Release. A package missing that configuration fails
# with an OIDC authentication error before anything uploads.
# Trusted publishing requires npm >= 11.5.1 (bundled with Node 24).
- name: Install and build
run: |
pnpm install --frozen-lockfile
cp -R native-artifacts/. packages/
# upload-artifact does not preserve executable bits. Restore the
# helper mode before pnpm runs its prepack checks.
find packages -type f -path '*/bin/scriptc-llvm-codegen' -exec chmod 755 {} +
pnpm -r build
- name: Check version sync
run: |
VERSION="${{ needs.check-release.outputs.version }}"
for pkg in packages/runtime packages/runtime-darwin-arm64 packages/llvm-darwin-arm64 packages/runtime-linux-x64-gnu packages/llvm-linux-x64-gnu packages/runtime-linux-arm64-gnu packages/llvm-linux-arm64-gnu packages/runtime-linux-x64-musl packages/llvm-linux-x64-musl packages/runtime-linux-arm64-musl packages/llvm-linux-arm64-musl packages/runtime-wasm32-wasi packages/runtime-win32-x64-msvc packages/llvm-win32-x64-msvc packages/compiler packages/cli; do
V=$(node -p "require('./$pkg/package.json').version")
if [ "$V" != "$VERSION" ]; then
echo "Version mismatch: $pkg is $V, expected $VERSION"
echo "Run 'node scripts/sync-versions.mjs' to stamp runtime and compiler from the CLI version, then commit"
exit 1
fi
done
- name: Package and verify LLVM helper
run: |
TARBALL=$(pnpm --dir packages/llvm-darwin-arm64 pack --pack-destination "$RUNNER_TEMP" --silent)
HELPER_TARBALL="$RUNNER_TEMP/$(basename "$TARBALL")"
node scripts/verify-llvm-package.mjs "$HELPER_TARBALL"
echo "HELPER_TARBALL=$HELPER_TARBALL" >> "$GITHUB_ENV"
- name: Publish to npm
run: |
VERSION="${{ needs.check-release.outputs.version }}"
# npm accepts --provenance only from PUBLIC source repositories;
# while this repo is internal the flag is dropped, and the same
# step starts attaching provenance the moment the repo goes
# public — no workflow edit.
VISIBILITY=$(gh api "repos/${{ github.repository }}" --jq .visibility)
if [ "$VISIBILITY" = "public" ]; then
PROVENANCE="--provenance"
else
PROVENANCE=""
echo "repository visibility is '$VISIBILITY': publishing without provenance"
fi
# Dependency order, so each package's deps are resolvable the
# moment it lands. pnpm pack rewrites workspace:* to the real
# version; npm publish on the tarball handles OIDC.
# Re-runs skip anything already on the registry at this version.
# npm may finish a trusted publication asynchronously. In that
# window a retry can report "previously staged" even though the
# same package/version is about to become publicly visible.
wait_for_published_version() {
name="$1"
for attempt in $(seq 1 12); do
if npm view "$name@$VERSION" version >/dev/null 2>&1; then
return 0
fi
if [ "$attempt" -lt 12 ]; then
sleep 10
fi
done
return 1
}
publish_dir() {
dir="$1"
packed="${2:-}"
name=$(node -p "require('./$dir/package.json').name")
if npm view "$name@$VERSION" version >/dev/null 2>&1; then
echo "$name@$VERSION already published, skipping"
return 0
fi
if [ -z "$packed" ]; then
tarball=$(cd "$dir" && pnpm pack --silent | tail -1)
packed="$dir/$tarball"
fi
if npm publish "$packed" $PROVENANCE --access public; then
return 0
else
publish_status=$?
fi
echo "$name@$VERSION publish failed; waiting for registry visibility before retrying"
if wait_for_published_version "$name"; then
echo "$name@$VERSION is now published; treating the publish as successful"
return 0
fi
return "$publish_status"
}
publish_dir packages/runtime
publish_dir packages/runtime-darwin-arm64
publish_dir packages/llvm-darwin-arm64 "$HELPER_TARBALL"
publish_dir packages/runtime-linux-x64-gnu
publish_dir packages/llvm-linux-x64-gnu
publish_dir packages/runtime-linux-arm64-gnu
publish_dir packages/llvm-linux-arm64-gnu
publish_dir packages/runtime-linux-x64-musl
publish_dir packages/llvm-linux-x64-musl
publish_dir packages/runtime-linux-arm64-musl
publish_dir packages/llvm-linux-arm64-musl
publish_dir packages/runtime-wasm32-wasi
publish_dir packages/runtime-win32-x64-msvc
publish_dir packages/llvm-win32-x64-msvc
publish_dir packages/compiler
publish_dir packages/cli
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# The GitHub release is a tag, notes, and one asset: the surface
# manifest (packages/compiler/surface-manifest.json — the machine-
# readable listing of the surface the static tier compiles at this
# version, regenerated here and verified against the committed file).
# The platform helper ships through its npm package rather than as a GitHub
# release asset, so this job runs AFTER a successful npm publish and never
# gates it. The body is the CHANGELOG.md block between the
# release:start/release:end markers, which RELEASING.md keeps on the
# latest entry only.
github-release:
name: Create GitHub Release
needs: [check-release, publish]
if: needs.check-release.outputs.should_release == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 11.1.3
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "24"
# Regenerate the surface manifest from this tree and require it to
# match the committed file byte-for-byte — the same staleness guard
# the test suite runs — so the attached asset is provably the
# manifest of the code being released.
- name: Generate surface manifest
run: |
pnpm install --frozen-lockfile
pnpm manifest --check
- name: Extract changelog entry
run: |
VERSION="${{ needs.check-release.outputs.version }}"
awk '/<!-- release:start -->/{found=1; next} /<!-- release:end -->/{exit} found{print}' CHANGELOG.md > /tmp/release-notes.md
LINES=$(wc -l < /tmp/release-notes.md | tr -d ' ')
if [ "$LINES" -lt 2 ]; then
echo "Error: No release notes found between <!-- release:start --> and <!-- release:end --> markers in CHANGELOG.md"
exit 1
fi
echo "Extracted release notes for $VERSION ($LINES lines)"
- name: Create GitHub Release
run: |
VERSION="${{ needs.check-release.outputs.version }}"
TAG="v$VERSION"
if gh release view "$TAG" &>/dev/null; then
echo "Release $TAG already exists, skipping creation"
else
echo "Creating release $TAG..."
gh release create "$TAG" \
--target "$GITHUB_SHA" \
--title "$TAG" \
--notes-file /tmp/release-notes.md
fi
# Attach the surface manifest (idempotent: --clobber makes
# re-runs replace the asset instead of failing).
gh release upload "$TAG" packages/compiler/surface-manifest.json --clobber
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}