Skip to content

ci(release): build win32-x64 with llvm-mingw (ucrt), stage the load p… #13

ci(release): build win32-x64 with llvm-mingw (ucrt), stage the load p…

ci(release): build win32-x64 with llvm-mingw (ucrt), stage the load p… #13

Workflow file for this run

name: release
on:
push:
tags:
- "v*"
permissions:
contents: write # GitHub Release
id-token: write # npm provenance / trusted publishing
jobs:
build-lib:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 1
- uses: actions/setup-node@v4
with:
node-version: 24
- uses: actions/setup-go@v5
with:
go-version: "1.26" # typescript-go/go.mod
- name: Install dependencies
run: npm ci
- name: Install typescript submodule dependencies
run: npm ci
working-directory: typescript
- name: Install typescript-go submodule dependencies
run: npm ci
working-directory: typescript-go
- name: Build js (tsgo native-preview -> vendor)
run: npm run build:js
- name: Build lib (patched TypeScript fork)
run: npm run build:lib
- name: Gates
run: |
npm run check:lib
npm run check:enums
- name: Upload main package payload
uses: actions/upload-artifact@v4
with:
name: lib-dist
path: |
package.json
README.md
LICENSE
NOTICE
bin/
lib/
vendor/
retention-days: 1
build-bridge:
strategy:
fail-fast: false
matrix:
include:
- { target: darwin-arm64, runner: macos-latest }
# darwin-x64 cross-compiles on the arm64 runner (clang -arch x86_64):
# GitHub's Intel pool (macos-13 retired 2025; macos-15-intel) is tiny
# and routinely congested — depending on it makes releases hang on
# queue capacity. Verified locally: Mach-O x86_64 dylib out of an
# arm64 Mac with CGO_ENABLED=1 GOOS=darwin GOARCH=amd64.
- { target: darwin-x64, runner: macos-latest, goos: darwin, goarch: amd64, cc: clang, cgo_cflags: "-arch x86_64", cgo_ldflags: "-arch x86_64" }
- { target: win32-x64, runner: ubuntu-latest, goos: windows, goarch: amd64, cc: x86_64-w64-mingw32-gcc, llvm_mingw: "1", node_lib: "https://nodejs.org/dist/v24.15.0/win-x64/node.lib" }
- { target: win32-arm64, runner: ubuntu-latest, goos: windows, goarch: arm64, cc: aarch64-w64-mingw32-gcc, llvm_mingw: "1", node_lib: "https://nodejs.org/dist/v24.15.0/win-arm64/node.lib" }
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Checkout typescript-go submodule
run: git submodule update --init --depth 1 typescript-go
- uses: actions/setup-node@v4
with:
node-version: 24
- uses: actions/setup-go@v5
with:
go-version: "1.26" # typescript-go/go.mod
- name: Apply tsgo patches
run: node tools/patch-tsgo.js
- name: Install cross toolchain
if: matrix.apt
run: sudo apt-get update && sudo apt-get install -y ${{ matrix.apt }}
- name: Install llvm-mingw
if: matrix.llvm_mingw
run: |
curl -sSL https://github.com/mstorsjo/llvm-mingw/releases/download/20250417/llvm-mingw-20250417-ucrt-ubuntu-22.04-x86_64.tar.xz | sudo tar -xJ -C /opt
echo "/opt/llvm-mingw-20250417-ucrt-ubuntu-22.04-x86_64/bin" >> "$GITHUB_PATH"
- name: Download node.lib (windows targets)
if: matrix.node_lib
run: curl -sSL "${{ matrix.node_lib }}" -o /tmp/node.lib
- name: Build bridge (stripped)
run: node tools/build-bridge.js
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
GOARM: ${{ matrix.goarm }}
CC: ${{ matrix.cc }}
CGO_CFLAGS: ${{ matrix.cgo_cflags }}
CGO_LDFLAGS: ${{ matrix.cgo_ldflags }}
TNB_NODE_LIB: ${{ matrix.node_lib != null && '/tmp/node.lib' || '' }}
CGO_ENABLED: 1
TNB_STRIP: 1
- name: Upload bridge binary
uses: actions/upload-artifact@v4
with:
name: bridge-${{ matrix.target }}
path: native/bridge.*
retention-days: 1
build-bridge-linux:
strategy:
fail-fast: false
matrix:
include:
- { target: linux-x64, runner: ubuntu-latest, load: true }
- { target: linux-arm64, runner: ubuntu-24.04-arm, load: true }
- { target: linux-arm, runner: ubuntu-latest, goos: linux, goarch: arm, goarm: "7", cc: arm-linux-gnueabihf-gcc, apt: gcc-arm-linux-gnueabihf }
runs-on: ${{ matrix.runner }}
container: debian:bullseye-slim
steps:
- name: Install build dependencies
run: apt-get update && apt-get install -y binutils ca-certificates gcc git ${{ matrix.apt }}
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Checkout typescript-go submodule
# Container runs as root; the mounted workspace is owned by the
# runner user, which trips git's dubious-ownership check.
run: |
git config --global --add safe.directory "$GITHUB_WORKSPACE"
git submodule update --init --depth 1 typescript-go
- uses: actions/setup-node@v4
with:
node-version: 24
- uses: actions/setup-go@v5
with:
go-version: "1.26" # typescript-go/go.mod
- name: Apply tsgo patches
run: node tools/patch-tsgo.js
- name: Build bridge against glibc 2.31
run: node tools/build-bridge.js
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
GOARM: ${{ matrix.goarm }}
CC: ${{ matrix.cc }}
CGO_ENABLED: 1
TNB_STRIP: 1
- name: Verify glibc floor
run: |
max_glibc="$(readelf --version-info native/bridge.node \
| grep -oE 'GLIBC_[0-9]+(\.[0-9]+)*' \
| sort -Vu \
| tail -1)"
if [ -z "$max_glibc" ]; then
echo "bridge has no readable GLIBC version requirements"
exit 1
fi
echo "maximum required glibc: $max_glibc"
if [ "$(printf '%s\n' GLIBC_2.31 "$max_glibc" | sort -V | tail -1)" != GLIBC_2.31 ]; then
echo "bridge requires $max_glibc, newer than the GLIBC_2.31 release floor"
exit 1
fi
- name: Load bridge on glibc 2.31
if: matrix.load
run: >
node -e "try { const b = require('./native/bridge.node'); const keys = Object.keys(b); console.log('loaded, exports:', keys.join(',')); if (typeof b.newSession !== 'function') { console.error('FAIL: newSession missing'); process.exit(1); } } catch (e) { console.error('FAIL: require threw:', e && e.stack || e); process.exit(1); }"
- name: Upload bridge binary
uses: actions/upload-artifact@v4
with:
name: bridge-${{ matrix.target }}
path: native/bridge.*
retention-days: 1
test-bridge-win32-x64:
needs: build-bridge
runs-on: windows-latest
steps:
- uses: actions/setup-node@v4
with:
node-version: 24
- uses: actions/download-artifact@v4
with:
name: bridge-win32-x64
path: native
- name: Load bridge on Windows
# Staged probe: each line narrows where death happens. The final
# errorlevel distinguishes a hard crash (0xC0000005) from a
# deliberate exit(1) — WER logs nothing on the runner image.
run: |
node -v
node -e "console.log('node ok')"
cmd /c "node -e \"console.log('pre-load'); try { const b = require('./native/bridge.node'); console.log('loaded, exports:', Object.keys(b).join(',')); if (typeof b.newSession !== 'function') { console.error('FAIL: newSession missing'); process.exit(1); } } catch (e) { console.error('FAIL: require threw:', e && e.stack || e); process.exit(1); }\" & echo EXIT=%errorlevel%"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
- name: Collect crash evidence
# A silent load failure is a hard crash before V8 can report; Windows
# Error Reporting still records the exception code and faulting module.
if: failure()
run: |
Get-WinEvent -FilterHashtable @{LogName='Application'; ProviderName='Application Error'} -MaxEvents 3 -ErrorAction SilentlyContinue | Format-List TimeCreated, Message
Get-WinEvent -FilterHashtable @{LogName='Application'; ProviderName='Windows Error Reporting'} -MaxEvents 3 -ErrorAction SilentlyContinue | Format-List TimeCreated, Message
publish:
needs: [build-lib, build-bridge, build-bridge-linux, test-bridge-win32-x64]
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1 # tools/pack-platform-packages.mjs
- uses: actions/setup-node@v4
with:
node-version: 24
registry-url: https://registry.npmjs.org
- name: Ensure npm >= 11.5.1 (trusted publishing)
run: npm install -g npm@latest
- name: Download artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Stage main package
run: |
mkdir staging
cp -R artifacts/lib-dist/. staging/
mkdir bridges
for d in artifacts/bridge-*; do cp -R "$d" "bridges/$(basename "$d")"; done
- name: Verify tag matches package.json version
run: |
TAG="${GITHUB_REF_NAME#v}"
PKG="$(node -p "require('./staging/package.json').version")"
if [ "$TAG" != "$PKG" ]; then
echo "tag $TAG != package.json version $PKG"
exit 1
fi
- name: Assemble platform sub-packages
run: node tools/pack-platform-packages.mjs staging bridges subpkgs
- name: Publish platform sub-packages
run: |
set -euo pipefail
for d in subpkgs/*; do
echo "== publishing $(basename "$d") =="
(cd "$d" && npm publish --provenance --tag latest)
done
env:
# Empty when the secret is unset: npm then falls back to OIDC trusted publishing.
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Publish main package
# --tag latest: the version is a semver prerelease (x.y.z-bridge.N…),
# so npm requires an EXPLICIT tag (default would be rejected), but an
# explicit `latest` is accepted. latest is the only dist-tag; no
# per-line alias tags are kept.
run: npm publish --provenance --tag latest
working-directory: staging
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Create GitHub Release
run: gh release create "${GITHUB_REF_NAME}" --generate-notes --verify-tag
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}