Skip to content

v0.4.0

v0.4.0 #14

Workflow file for this run

# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: Publish
on:
release:
types: [published]
workflow_dispatch:
inputs:
version:
description: "Version to publish (e.g., 0.1.0)"
required: true
type: string
# Workflow-level permissions use least privilege (read-only).
# Jobs that need elevated permissions (npm OIDC, GHCR push) declare them
# individually on the job — see publish-npm and publish-docker.
permissions:
contents: read
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
# Build native addons on each platform and upload as artifacts.
# These are combined in the publish-npm job to create a cross-platform package.
#
# gnu and win32 builds run tests natively on their platform.
# musl is cross-compiled from the glibc runner (can't run tests on glibc host).
build-native:
name: Build (${{ matrix.build }})
strategy:
fail-fast: true
matrix:
build: [linux-kvm, linux-musl, windows-whp]
include:
- build: linux-kvm
hypervisor: kvm
run_tests: true
- build: linux-musl
hypervisor: kvm
run_tests: false # musl .node can't run on glibc host
- build: windows-whp
hypervisor: whp
run_tests: true
runs-on: ${{ fromJson(
format('["self-hosted", "{0}", "X64", "1ES.Pool=hld-{1}-amd", "JobId={2}-{3}-{4}-{5}"]',
matrix.hypervisor == 'whp' && 'Windows' || 'Linux',
matrix.hypervisor == 'whp' && 'win2025' || 'kvm',
matrix.build,
github.run_id,
github.run_number,
github.run_attempt)) }}
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: "22"
- uses: hyperlight-dev/ci-setup-workflow@v1.9.0
with:
rust-toolchain: "1.89"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Setup debug build for tests
run: just setup
- name: Install musl tools and rebuild for musl target
if: matrix.build == 'linux-musl'
run: |
sudo apt-get update && sudo apt-get install -y musl-tools
rustup target add x86_64-unknown-linux-musl
# Rebuild hyperlight-js NAPI addon targeting musl in release mode.
hl_dir=$(just resolve-hyperlight-dir)
cd "${hl_dir}/src/js-host-api"
npx napi build --platform --release --strip --target x86_64-unknown-linux-musl
# Rebuild hyperlight-analysis NAPI addon targeting musl in release mode.
cd "$GITHUB_WORKSPACE/src/code-validator/guest"
npx napi build --platform --release --strip --target x86_64-unknown-linux-musl --manifest-path host/Cargo.toml
node -e "require('fs').readdirSync('host').filter(f=>f.endsWith('.node')).forEach(f=>require('fs').copyFileSync('host/'+f,f))"
# This matrix leg must upload only musl artifacts; the setup step also
# creates host-platform glibc addons, which are supplied by linux-kvm.
rm -f "${hl_dir}/src/js-host-api/"*.linux-x64-gnu.node
rm -f "$GITHUB_WORKSPACE/src/code-validator/guest/host/"*.linux-x64-gnu.node
rm -f "$GITHUB_WORKSPACE/src/code-validator/guest/"*.linux-x64-gnu.node
# Verify musl .node files were actually produced
ls -la "${hl_dir}/src/js-host-api/"*.linux-x64-musl.node
ls -la "$GITHUB_WORKSPACE/src/code-validator/guest/"*linux-x64-musl* || ls -la "$GITHUB_WORKSPACE/src/code-validator/guest/host/"*linux-x64-musl*
- name: Build release binary
if: matrix.run_tests
run: node scripts/build-binary.js --release
env:
VERSION: ${{ github.event.release.tag_name || inputs.version }}
- name: Run tests
if: matrix.run_tests
run: just test
- name: Rebuild native addons for release packaging
if: matrix.run_tests
run: just build-release
# Upload the native .node addons so the publish job can combine them
- name: Upload native addons
uses: actions/upload-artifact@v7
with:
name: native-addons-${{ matrix.build }}
path: |
deps/js-host-api/js-host-api.*.node
src/code-validator/guest/host/hyperlight-analysis.*.node
src/code-validator/guest/hyperlight-analysis.*.node
if-no-files-found: error
retention-days: 1
# Assemble the final npm package tarball on a self-hosted Linux runner
# (needs `just setup` for the hyperlight toolchain to build the binary).
# The resulting tarball is uploaded as an artifact, then published from a
# github-hosted runner — npm sigstore provenance *requires* github-hosted.
pack-npm:
name: Pack npm tarball
needs: [build-native]
permissions:
contents: read
runs-on: [self-hosted, Linux, X64, "1ES.Pool=hld-kvm-amd","JobId=hyperagent-pack-npm-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}"]
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: "22"
- uses: hyperlight-dev/ci-setup-workflow@v1.9.0
with:
rust-toolchain: "1.89"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Setup
run: just setup
# Download AFTER setup so artifacts land in the symlink/junction target
# that build-hyperlight creates (deps/js-host-api → Cargo checkout).
# Downloading before setup would be clobbered when setup re-creates the link.
- name: Download all native addons
uses: actions/download-artifact@v8
with:
pattern: native-addons-*
merge-multiple: true
- name: Build binary (with all platform addons present)
run: VERSION="${{ github.event.release.tag_name || inputs.version }}" node scripts/build-binary.js --release
- name: Set version from release tag
if: github.event_name == 'release'
run: npm version ${{ github.event.release.tag_name }} --no-git-tag-version --allow-same-version
- name: Set version from input
if: github.event_name == 'workflow_dispatch'
run: npm version ${{ inputs.version }} --no-git-tag-version --allow-same-version
- name: Pack npm tarball
run: npm pack
- name: Check npm package size
run: |
TARBALL=$(ls *.tgz)
UNPACKED=$(npm pack --dry-run --json | node -e 'let data="";process.stdin.on("data",c=>data+=c);process.stdin.on("end",()=>console.log(JSON.parse(data)[0].unpackedSize))')
MAX=$((300 * 1024 * 1024))
echo "Packed tarball: $TARBALL"
echo "Unpacked size: $UNPACKED bytes (limit: $MAX)"
if [ "$UNPACKED" -gt "$MAX" ]; then
echo "❌ npm package is too large"
exit 1
fi
- name: Upload npm tarball
uses: actions/upload-artifact@v7
with:
name: npm-tarball
path: "*.tgz"
if-no-files-found: error
retention-days: 1
# Publish the prebuilt tarball from a github-hosted runner.
# npm sigstore provenance (--provenance) only accepts github-hosted runners;
# self-hosted is rejected with:
# E422 Unsupported GitHub Actions runner environment: "self-hosted"
# This job does no building — it just takes the tarball and pushes it.
publish-npm:
name: Publish to npmjs.org
needs: [pack-npm]
# id-token: write is required for npm OIDC trusted publishing.
# Scoped to this job only (least privilege).
permissions:
id-token: write
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v6
with:
node-version: "22"
registry-url: "https://registry.npmjs.org"
# Trusted publishing requires npm >=11.5.1 for OIDC token exchange.
# Pin to ^11.5.1 so we don't silently get an older 11.x that lacks OIDC.
#
# Bootstrap via `npx` rather than `npm install -g npm@...` — the latter
# hits a long-standing npm self-upgrade bug (reproduces on github-hosted
# runners too) where mid-reify npm unlinks its own `promise-retry` dep
# and dies with MODULE_NOT_FOUND. Using a fresh npx-fetched npm to
# install itself globally sidesteps the half-upgraded state entirely.
- name: Upgrade npm for trusted publishing
run: |
npx --yes npm@^11.5.1 install -g --force npm@^11.5.1
npm --version
- name: Download npm tarball
uses: actions/download-artifact@v8
with:
name: npm-tarball
# OIDC trusted publishing for release events; NPM_TOKEN fallback for workflow_dispatch
- name: Set publish flags
id: publish-flags
run: |
if [ "${{ github.event_name }}" != "workflow_dispatch" ]; then
echo "provenance=--provenance" >> "$GITHUB_OUTPUT"
else
echo "provenance=" >> "$GITHUB_OUTPUT"
fi
- name: Publish to npmjs.org
run: npm publish *.tgz --access public ${{ steps.publish-flags.outputs.provenance }}
env:
NODE_AUTH_TOKEN: ${{ github.event_name == 'workflow_dispatch' && secrets.NPM_TOKEN || '' }}
# Post-publish smoke test: install the *just-published* tarball from the npm
# registry into a clean container and verify the bundled launcher resolves
# the correct platform binary.
#
# This is the missing gate that catches:
# - tarball missing native binaries (the Friday-2026-04-23 false-alarm)
# - launcher musl-vs-glibc detection broken
# - file: deps in `dependencies` actually breaking npm install
# - registry replication / CDN propagation lag
# - shasum mismatch between published metadata and tarball blob
#
# We test linux-x64-gnu (debian-slim) and linux-x64-musl (alpine) — the two
# Linux triples we ship. Windows smoke is left out for now (the launcher's
# win32-x64-msvc path is exercised via `just test` pre-publish).
smoke-test:
name: Smoke test (${{ matrix.label }})
needs: [publish-npm]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- label: linux-x64-gnu
image: node:22-bookworm-slim
- label: linux-x64-musl
image: node:22-alpine
steps:
- name: Resolve published version
id: ver
run: |
# Release tags arrive as `v0.2.3`; npm strips the leading `v` at
# publish time. Match that here so `npm view <pkg>@<ver>` resolves.
RAW="${{ github.event.release.tag_name || inputs.version }}"
VERSION="${RAW#v}"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "Smoke-testing @hyperlight-dev/hyperagent@$VERSION"
- name: Wait for registry propagation
env:
VERSION: ${{ steps.ver.outputs.version }}
run: |
# Poll registry.npmjs.org for up to 5 minutes for the version metadata
# AND tarball blob to become fetchable. npm can expose dist metadata
# before the tarball URL has propagated through the CDN, so metadata
# visibility alone is not enough for installability.
for i in $(seq 1 30); do
ACTUAL=$(npm view "@hyperlight-dev/hyperagent@$VERSION" version 2>/dev/null || true)
if [ "$ACTUAL" = "$VERSION" ]; then
TARBALL=$(npm view "@hyperlight-dev/hyperagent@$VERSION" dist.tarball 2>/dev/null || true)
EXPECTED=$(npm view "@hyperlight-dev/hyperagent@$VERSION" dist.shasum 2>/dev/null || true)
TMP=$(mktemp)
if [ -n "$TARBALL" ] && [ -n "$EXPECTED" ] && curl -fsSL "$TARBALL" -o "$TMP"; then
FOUND=$(sha1sum "$TMP" | awk '{print $1}')
rm -f "$TMP"
if [ "$FOUND" = "$EXPECTED" ]; then
echo "✅ metadata and tarball visible after $((i * 10))s ($EXPECTED)"
exit 0
fi
echo "⏳ tarball shasum mismatch on attempt $i — expected $EXPECTED got $FOUND"
else
rm -f "$TMP"
echo "⏳ metadata visible, tarball not fetchable yet (attempt $i)"
fi
fi
sleep 10
done
echo "❌ @hyperlight-dev/hyperagent@$VERSION metadata/tarball not installable after 300s"
exit 1
- name: Install + run in ${{ matrix.image }}
env:
VERSION: ${{ steps.ver.outputs.version }}
run: |
docker run --rm \
-e VERSION="$VERSION" \
${{ matrix.image }} \
sh -c '
set -e
echo "=== platform ==="
uname -a
cat /etc/os-release 2>/dev/null | head -3 || true
echo "=== install ==="
npm install -g --no-audit --no-fund "@hyperlight-dev/hyperagent@${VERSION}"
echo "=== version check ==="
ACTUAL=$(hyperagent --version 2>&1)
echo "got: $ACTUAL"
echo "$ACTUAL" | grep -q "${VERSION}" \
|| { echo "❌ --version did not match ${VERSION}"; exit 1; }
echo "=== help check ==="
hyperagent --help 2>&1 | grep -q "Usage:" \
|| { echo "❌ --help did not produce expected output"; exit 1; }
echo "✅ smoke passed on ${VERSION}"
'
# Build and publish Docker image (after tests pass)
publish-docker:
name: Publish to GitHub Container Registry
needs: [build-native]
# packages: write for pushing the image to GHCR.
# Scoped to this job only (least privilege).
permissions:
contents: read
packages: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: "22"
- uses: hyperlight-dev/ci-setup-workflow@v1.9.0
with:
rust-toolchain: "1.89"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Setup
run: just setup
- name: Resolve symlinks for Docker context
run: |
if [ -L deps/js-host-api ]; then
target=$(readlink -f deps/js-host-api)
rm deps/js-host-api
cp -r "$target" deps/js-host-api
fi
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Log in to GitHub Container Registry
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata for Docker
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push Docker image
uses: docker/build-push-action@v7
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
VERSION=${{ github.event.release.tag_name || inputs.version }}
cache-from: type=gha
cache-to: type=gha,mode=max