singularity-forge/.github/workflows/build-native.yml
ace-pm 172753c3b2 refactor(forge): complete gsd → forge rebrand across native, logging, and build system
- Rename native Rust crates: gsd-engine → forge-engine, gsd-ast → forge-ast, gsd-grep → forge-grep
- Update all crate dependencies (Cargo.toml, .rs source) and N-API artifacts
- Mass rename log prefix [gsd] → [forge] across 81 files (scripts, src/, extensions, tests)
- Rename log prefix "gsd-db:" → "forge-db:" in template literals
- Update nix flake: add sf-run-native devShell with Rust toolchain for native addon builds
- Update CI workflow artifact names (build-native.yml)
- Verify only packages/native/* touched (no upstream pi-* packages renamed)

Rationale: Complete gsd-2 → singularity-forge rebrand (2026-04-15). Native addon is
sf-run-specific; all gsd-prefixed logging and crate names must align with new identity.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 14:11:45 +02:00

296 lines
11 KiB
YAML

name: Build Native Binaries
on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
publish:
description: "Publish platform packages to npm"
required: false
default: "false"
type: choice
options:
- "false"
- "true"
jobs:
build:
strategy:
fail-fast: false
matrix:
include:
- os: macos-14
target: aarch64-apple-darwin
platform: darwin-arm64
- os: macos-14
target: x86_64-apple-darwin
platform: darwin-x64
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
platform: linux-x64-gnu
- os: ubuntu-latest
target: aarch64-unknown-linux-gnu
platform: linux-arm64-gnu
cross: true
- os: windows-latest
target: x86_64-pc-windows-msvc
platform: win32-x64-msvc
runs-on: ${{ matrix.os }}
name: Build ${{ matrix.platform }}
steps:
- uses: actions/checkout@v6
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Add Rust compilation target
run: rustup target add ${{ matrix.target }}
- name: Cache Rust build artifacts
uses: Swatinem/rust-cache@v2
with:
shared-key: native-${{ matrix.platform }}
workspaces: |
native -> target
- name: Install cross-compilation tools (Linux ARM64)
if: matrix.cross
run: |
sudo apt-get update
sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
- name: Build native addon
working-directory: native/crates/engine
env:
# CARGO_ENCODED_RUSTFLAGS overrides target-specific rustflags in
# .cargo/config.toml, which sets -C target-cpu=native for dev builds.
# CI must produce portable binaries.
CARGO_ENCODED_RUSTFLAGS: ""
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: ${{ matrix.cross && 'aarch64-linux-gnu-gcc' || '' }}
run: cargo build --release --target ${{ matrix.target }}
- name: Prepare artifact (Unix)
if: runner.os != 'Windows'
run: |
mkdir -p artifacts
cp native/target/${{ matrix.target }}/release/libforge_engine.dylib artifacts/forge_engine.node 2>/dev/null || \
cp native/target/${{ matrix.target }}/release/libforge_engine.so artifacts/forge_engine.node 2>/dev/null || \
{ echo "::error::No library found for ${{ matrix.platform }}"; exit 1; }
ls -la artifacts/
- name: Prepare artifact (Windows)
if: runner.os == 'Windows'
run: |
mkdir artifacts
copy native\target\${{ matrix.target }}\release\forge_engine.dll artifacts\forge_engine.node
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: native-${{ matrix.platform }}
path: artifacts/forge_engine.node
if-no-files-found: error
publish:
needs: build
if: startsWith(github.ref, 'refs/tags/v') || github.event.inputs.publish == 'true'
runs-on: blacksmith-4vcpu-ubuntu-2404
name: Publish platform packages
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: "24"
registry-url: "https://registry.npmjs.org"
cache: "npm"
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Copy binaries to platform packages
run: |
for platform in darwin-arm64 darwin-x64 linux-x64-gnu linux-arm64-gnu win32-x64-msvc; do
cp "artifacts/native-${platform}/forge_engine.node" "native/npm/${platform}/forge_engine.node"
echo "Copied binary for ${platform}"
ls -la "native/npm/${platform}/"
done
- name: Sync platform package versions
run: node native/scripts/sync-platform-versions.cjs
- name: Detect prerelease version
id: version-check
run: |
VERSION=$(node -p "require('./package.json').version")
if echo "$VERSION" | grep -q '-next\.'; then
echo "is_prerelease=true" >> "$GITHUB_OUTPUT"
echo "tag_flag=--tag next" >> "$GITHUB_OUTPUT"
echo "Prerelease detected: ${VERSION} → publishing with --tag next"
else
echo "is_prerelease=false" >> "$GITHUB_OUTPUT"
echo "tag_flag=" >> "$GITHUB_OUTPUT"
echo "Stable release: ${VERSION} → publishing with --tag latest (default)"
fi
- name: Publish platform packages
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
for platform in darwin-arm64 darwin-x64 linux-x64-gnu linux-arm64-gnu win32-x64-msvc; do
echo "Publishing @gsd-build/engine-${platform}..."
cd "native/npm/${platform}"
OUTPUT=$(npm publish --access public ${{ steps.version-check.outputs.tag_flag }} 2>&1) && echo "$OUTPUT" || {
if echo "$OUTPUT" | grep -q "cannot publish over the previously published"; then
echo "Already published, skipping"
else
echo "::error::Failed to publish ${platform}: $OUTPUT"
exit 1
fi
}
cd "$GITHUB_WORKSPACE"
done
- name: Verify platform packages are published
run: |
VERSION=$(node -p "require('./package.json').version")
echo "Verifying platform packages at version ${VERSION}..."
# Exponential backoff: 5s, 10s, 20s, 30s, 30s (max 5 attempts, ~95s worst case vs fixed 30s + single check)
DELAY=5
for attempt in $(seq 1 5); do
FAILED=0
for platform in darwin-arm64 darwin-x64 linux-x64-gnu linux-arm64-gnu win32-x64-msvc; do
PKG="@gsd-build/engine-${platform}"
PUBLISHED=$(npm view "${PKG}@${VERSION}" version 2>/dev/null || echo "")
if [ "${PUBLISHED}" != "${VERSION}" ]; then
FAILED=1
break
fi
done
if [ "${FAILED}" = "0" ]; then
echo "All platform packages verified (attempt ${attempt})."
break
fi
if [ "$attempt" = "5" ]; then
echo "::error::One or more platform packages not found after 5 attempts. Aborting."
for platform in darwin-arm64 darwin-x64 linux-x64-gnu linux-arm64-gnu win32-x64-msvc; do
PKG="@gsd-build/engine-${platform}"
PUBLISHED=$(npm view "${PKG}@${VERSION}" version 2>/dev/null || echo "")
if [ "${PUBLISHED}" = "${VERSION}" ]; then
echo " ✓ ${PKG}@${VERSION}"
else
echo " ✗ ${PKG}@${VERSION} (got: '${PUBLISHED}')"
fi
done
exit 1
fi
echo " Attempt ${attempt}: not all packages visible yet, retrying in ${DELAY}s..."
sleep "$DELAY"
DELAY=$((DELAY * 2))
if [ "$DELAY" -gt 30 ]; then DELAY=30; fi
done
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Verify dist exists
run: test -s dist/loader.js || { echo "::error::dist/loader.js missing or empty after build"; exit 1; }
- name: Validate package is installable
run: npm run validate-pack
- name: Publish main package
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
# --ignore-scripts: skip prepublishOnly since we built explicitly above
OUTPUT=$(npm publish --ignore-scripts ${{ steps.version-check.outputs.tag_flag }} 2>&1) && echo "$OUTPUT" || {
if echo "$OUTPUT" | grep -q "cannot publish over the previously published\|You cannot publish over"; then
echo "Already published, skipping"
else
echo "$OUTPUT"
exit 1
fi
}
- name: Post-publish smoke test
run: |
VERSION=$(node -p "require('./package.json').version")
TMPDIR=$(mktemp -d)
cd "$TMPDIR"
npm init -y > /dev/null 2>&1
# Wait for npm registry with exponential backoff (5s, 10s, 20s, 30s, 30s, 30s, 30s — max ~155s vs fixed 5min)
echo "Waiting for gsd-pi@${VERSION} to appear on npm..."
DELAY=5
for attempt in $(seq 1 8); do
PUBLISHED=$(npm view "gsd-pi@${VERSION}" version 2>/dev/null || echo "")
if [ "${PUBLISHED}" = "${VERSION}" ]; then
echo " ✓ Version ${VERSION} visible on npm (attempt ${attempt})"
break
fi
if [ "$attempt" = "8" ]; then
echo "::warning::gsd-pi@${VERSION} not visible on npm after 8 attempts — skipping smoke test"
exit 0
fi
echo " Attempt ${attempt}: not yet visible, retrying in ${DELAY}s..."
sleep "$DELAY"
DELAY=$((DELAY * 2))
if [ "$DELAY" -gt 30 ]; then DELAY=30; fi
done
# Install and verify with backoff (5s, 10s, 20s)
echo "Installing gsd-pi@${VERSION}..."
DELAY=5
for attempt in 1 2 3; do
if npm install "gsd-pi@${VERSION}" 2>&1 | tee /tmp/install-output.txt; then
echo " ✓ Install succeeded"
RAW=$(node node_modules/gsd-pi/dist/loader.js --version 2>&1 || echo "FAILED")
ACTUAL=$(echo "$RAW" | sed 's/\x1b\[[0-9;]*m//g' | grep -oE "^${VERSION}$" | head -1)
if [ "$ACTUAL" = "$VERSION" ]; then
echo " ✓ gsd --version = ${VERSION}"
echo "Published package is functional"
exit 0
else
echo "::error::Version mismatch: expected ${VERSION} in output:"
echo "$RAW"
exit 1
fi
fi
echo "Install attempt ${attempt}/3 failed, retrying in ${DELAY}s..."
cat /tmp/install-output.txt
sleep "$DELAY"
DELAY=$((DELAY * 2))
done
echo "::error::Smoke test failed — gsd-pi@${VERSION} not installable"
exit 1
- name: Verify dist-tag after publish
if: steps.version-check.outputs.is_prerelease == 'false'
run: |
VERSION=$(node -p "require('./package.json').version")
echo "Verifying npm dist-tag 'latest' points to ${VERSION}..."
DELAY=5
for attempt in $(seq 1 6); do
LATEST=$(npm view gsd-pi dist-tags.latest 2>/dev/null || echo "")
if [ "${LATEST}" = "${VERSION}" ]; then
echo " ✓ npm dist-tags.latest = ${VERSION}"
exit 0
fi
echo " Attempt ${attempt}/6: latest=${LATEST}, expected=${VERSION}, retrying in ${DELAY}s..."
sleep "$DELAY"
DELAY=$((DELAY * 2))
if [ "$DELAY" -gt 30 ]; then DELAY=30; fi
done
echo "::error::dist-tags.latest is '${LATEST}' but expected '${VERSION}' — run: npm dist-tag add gsd-pi@${VERSION} latest"
exit 1