From 252ab3360aa83be39cdf042f1546612727eb0314 Mon Sep 17 00:00:00 2001 From: Simon Davies Date: Wed, 16 Sep 2026 21:35:20 +0100 Subject: [PATCH 1/2] Add linux-arm64 npm packaging Adds the `@hyperlight-dev/js-host-api-linux-arm64-gnu` platform package so Linux aarch64 consumers get a prebuilt native binary, completing the npm side of aarch64 support. Mirrors the existing `linux-x64-gnu` package conventions: `os: [linux]`, `cpu: [arm64]`, `libc: [glibc]`. There is no musl aarch64 variant. The publish matrix builds `aarch64-unknown-linux-gnu` on `[self-hosted, Linux, arm64, kvm]`, matching the Linux aarch64 runner labels already used by dep_build.yml. That target is native on those runners, so unlike the musl entry it needs no extra `rustup target add`. The new package is added to FIRST_TIME_PACKAGES because it has no npm trusted publisher until it is published once. As with darwin-arm64, its root lockfile edge has no `resolved` URL or `integrity` hash until the first release; `npm ci` skips unresolvable optional deps, and this was verified to exit 0 plainly, with --omit=optional, and with --os=linux --cpu=arm64. docs/release.md said `three platform packages` while there were already four; corrected to five along with the package count updates. Note: this has never run on real Linux aarch64 hardware. CI is the first execution. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: Simon Davies --- .github/workflows/npm-publish.yml | 27 +++++++++++++++-- Justfile | 3 +- docs/release.md | 10 ++++--- src/js-host-api/DEVELOPMENT.md | 1 + .../npm/linux-arm64-gnu/package.json | 30 +++++++++++++++++++ src/js-host-api/package-lock.json | 1 + src/js-host-api/package.json | 4 ++- 7 files changed, 68 insertions(+), 8 deletions(-) create mode 100644 src/js-host-api/npm/linux-arm64-gnu/package.json diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 510c716..7597327 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -44,7 +44,7 @@ env: # Comma-delimited npm package names that have not yet been published and # therefore cannot have a trusted publisher configured. Remove each package # in a follow-up PR after its first release and trusted-publisher setup. - FIRST_TIME_PACKAGES: ",@hyperlight-dev/js-host-api-darwin-arm64," + FIRST_TIME_PACKAGES: ",@hyperlight-dev/js-host-api-darwin-arm64,@hyperlight-dev/js-host-api-linux-arm64-gnu," jobs: build: @@ -64,6 +64,9 @@ jobs: - target: aarch64-apple-darwin os: macos-15 build_name: darwin-arm64 + - target: aarch64-unknown-linux-gnu + os: [self-hosted, Linux, arm64, kvm] + build_name: linux-arm64-gnu runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v7 @@ -213,6 +216,12 @@ jobs: name: bindings-darwin-arm64 path: ${{ env.WORKING_DIR }}/artifacts/darwin-arm64 + - name: Download Linux arm64 GNU artifact + uses: actions/download-artifact@v8 + with: + name: bindings-linux-arm64-gnu + path: ${{ env.WORKING_DIR }}/artifacts/linux-arm64-gnu + - name: Download JS bindings uses: actions/download-artifact@v8 with: @@ -230,10 +239,12 @@ jobs: mv artifacts/linux-x64-musl/*.node npm/linux-x64-musl/js-host-api.linux-x64-musl.node mv artifacts/win32-x64-msvc/*.node npm/win32-x64-msvc/js-host-api.win32-x64-msvc.node mv artifacts/darwin-arm64/*.node npm/darwin-arm64/js-host-api.darwin-arm64.node + mv artifacts/linux-arm64-gnu/*.node npm/linux-arm64-gnu/js-host-api.linux-arm64-gnu.node ls -la npm/linux-x64-gnu/ ls -la npm/linux-x64-musl/ ls -la npm/win32-x64-msvc/ ls -la npm/darwin-arm64/ + ls -la npm/linux-arm64-gnu/ - name: Set package versions working-directory: ${{ env.WORKING_DIR }} @@ -246,6 +257,7 @@ jobs: cd ../linux-x64-musl && npm version "$VERSION" --no-git-tag-version --allow-same-version cd ../win32-x64-msvc && npm version "$VERSION" --no-git-tag-version --allow-same-version cd ../darwin-arm64 && npm version "$VERSION" --no-git-tag-version --allow-same-version + cd ../linux-arm64-gnu && npm version "$VERSION" --no-git-tag-version --allow-same-version env: VERSION: ${{ inputs.version }} @@ -325,6 +337,13 @@ jobs: env: NODE_AUTH_TOKEN: ${{ (github.event_name == 'workflow_dispatch' || contains(env.FIRST_TIME_PACKAGES, ',@hyperlight-dev/js-host-api-darwin-arm64,')) && secrets.NPM_TOKEN || '' }} + - name: Publish Linux arm64 GNU package + if: ${{ !inputs.dry_run }} + working-directory: ${{ env.WORKING_DIR }}/npm/linux-arm64-gnu + run: npm publish --access public --ignore-scripts ${{ (github.event_name == 'workflow_dispatch' || contains(env.FIRST_TIME_PACKAGES, ',@hyperlight-dev/js-host-api-linux-arm64-gnu,')) && '' || '--provenance' }} + env: + NODE_AUTH_TOKEN: ${{ (github.event_name == 'workflow_dispatch' || contains(env.FIRST_TIME_PACKAGES, ',@hyperlight-dev/js-host-api-linux-arm64-gnu,')) && secrets.NPM_TOKEN || '' }} + - name: Publish main package if: ${{ !inputs.dry_run }} working-directory: ${{ env.WORKING_DIR }} @@ -342,7 +361,8 @@ jobs: "@hyperlight-dev/js-host-api-linux-x64-gnu" \ "@hyperlight-dev/js-host-api-linux-x64-musl" \ "@hyperlight-dev/js-host-api-win32-x64-msvc" \ - "@hyperlight-dev/js-host-api-darwin-arm64"; do + "@hyperlight-dev/js-host-api-darwin-arm64" \ + "@hyperlight-dev/js-host-api-linux-arm64-gnu"; do if npm view "$pkg@$VERSION" version > /dev/null 2>&1; then echo "✅ $pkg@$VERSION published" else @@ -375,5 +395,8 @@ jobs: echo "--- @hyperlight-dev/js-host-api-darwin-arm64 ---" npm pack ./npm/darwin-arm64 --dry-run echo "" + echo "--- @hyperlight-dev/js-host-api-linux-arm64-gnu ---" + npm pack ./npm/linux-arm64-gnu --dry-run + echo "" echo "--- @hyperlight-dev/js-host-api ---" npm pack --dry-run \ No newline at end of file diff --git a/Justfile b/Justfile index 29a3672..f24f275 100644 --- a/Justfile +++ b/Justfile @@ -226,12 +226,13 @@ set-version version: cargo update \ --manifest-path src/hyperlight-js-runtime/tests/fixtures/extended_runtime/Cargo.toml \ -p hyperlight-js-runtime -p hyperlight-js-common - # npm: main + the 4 platform package.json versions (--ignore-scripts avoids needing node_modules) + # npm: main + the 5 platform package.json versions (--ignore-scripts avoids needing node_modules) cd src/js-host-api && npm version {{ version }} --no-git-tag-version --allow-same-version --ignore-scripts cd src/js-host-api/npm/linux-x64-gnu && npm version {{ version }} --no-git-tag-version --allow-same-version --ignore-scripts cd src/js-host-api/npm/linux-x64-musl && npm version {{ version }} --no-git-tag-version --allow-same-version --ignore-scripts cd src/js-host-api/npm/win32-x64-msvc && npm version {{ version }} --no-git-tag-version --allow-same-version --ignore-scripts cd src/js-host-api/npm/darwin-arm64 && npm version {{ version }} --no-git-tag-version --allow-same-version --ignore-scripts + cd src/js-host-api/npm/linux-arm64-gnu && npm version {{ version }} --no-git-tag-version --allow-same-version --ignore-scripts # Verify the npm lockfile cd src/js-host-api && npm ci --dry-run --omit=optional --ignore-scripts diff --git a/docs/release.md b/docs/release.md index b2744dc..f97fc2d 100644 --- a/docs/release.md +++ b/docs/release.md @@ -10,7 +10,7 @@ Do this with the `just set-version` recipe. **Always use this instead of bumping - every workspace crate's `version` and the root `Cargo.lock`, - the excluded `extended_runtime` fixture's own `Cargo.lock` (a bare `cargo set-version` can't reach it, and a stale one fails the `native_modules --locked` build), -- the npm main package and the three platform packages, +- the npm main package and the five platform packages, - the root package version in `src/js-host-api/package-lock.json`. Keep `optionalDependencies` on the latest published version so `npm ci` can use @@ -68,12 +68,13 @@ Trusted publishing is configured on [npmjs.com](https://www.npmjs.com/) for each 3. Set **Organization**: `hyperlight-dev`, **Repository**: `hyperlight-js`, **Workflow**: `CreateRelease.yml` 4. Save -This must be done for all 5 packages: +This must be done for all 6 packages: - `@hyperlight-dev/js-host-api` - `@hyperlight-dev/js-host-api-linux-x64-gnu` - `@hyperlight-dev/js-host-api-linux-x64-musl` - `@hyperlight-dev/js-host-api-win32-x64-msvc` - `@hyperlight-dev/js-host-api-darwin-arm64` +- `@hyperlight-dev/js-host-api-linux-arm64-gnu` > **Note:** Trusted publishers are configured per package, and npm cannot configure one for a > package that does not exist yet. The publish workflow therefore has a temporary @@ -113,12 +114,13 @@ If you need to publish npm packages manually via `workflow_dispatch`, you'll nee 1. **Temporarily allow token-based publishing on npmjs.com** - Go to each package on [npmjs.com](https://www.npmjs.com/) → Settings → Publishing access - Change from "Require two-factor authentication and disallow tokens" to "Require two-factor authentication or automation tokens" - - Do this for all 5 packages: + - Do this for all 6 packages: - `@hyperlight-dev/js-host-api` - `@hyperlight-dev/js-host-api-linux-x64-gnu` - `@hyperlight-dev/js-host-api-linux-x64-musl` - `@hyperlight-dev/js-host-api-win32-x64-msvc` - `@hyperlight-dev/js-host-api-darwin-arm64` + - `@hyperlight-dev/js-host-api-linux-arm64-gnu` 2. **Create an npm automation token** - Go to [npmjs.com](https://www.npmjs.com/) → Access Tokens → Generate New Token → Granular Access Token @@ -140,5 +142,5 @@ If you need to publish npm packages manually via `workflow_dispatch`, you'll nee 5. **Clean up immediately after publishing** - Delete the `NPM_TOKEN` repo secret on GitHub → Settings → Secrets and variables → Actions - Revoke the npm token on npmjs.com → Access Tokens - - Re-enable "Require two-factor authentication and disallow tokens" on all 5 packages + - Re-enable "Require two-factor authentication and disallow tokens" on all 6 packages - Verify the packages published correctly: `npm view @hyperlight-dev/js-host-api versions` diff --git a/src/js-host-api/DEVELOPMENT.md b/src/js-host-api/DEVELOPMENT.md index 8ad5b9a..c7277cf 100644 --- a/src/js-host-api/DEVELOPMENT.md +++ b/src/js-host-api/DEVELOPMENT.md @@ -67,6 +67,7 @@ The npm release consists of the following packages: | `@hyperlight-dev/js-host-api-linux-x64-musl` | Linux x86_64 (musl/Alpine) native binary | | `@hyperlight-dev/js-host-api-win32-x64-msvc` | Windows x86_64 native binary | | `@hyperlight-dev/js-host-api-darwin-arm64` | macOS aarch64 (Apple Silicon) native binary | +| `@hyperlight-dev/js-host-api-linux-arm64-gnu` | Linux aarch64 (glibc) native binary | ### How Platform Selection Works diff --git a/src/js-host-api/npm/linux-arm64-gnu/package.json b/src/js-host-api/npm/linux-arm64-gnu/package.json new file mode 100644 index 0000000..bd01526 --- /dev/null +++ b/src/js-host-api/npm/linux-arm64-gnu/package.json @@ -0,0 +1,30 @@ +{ + "name": "@hyperlight-dev/js-host-api-linux-arm64-gnu", + "version": "0.4.0", + "os": [ + "linux" + ], + "cpu": [ + "arm64" + ], + "main": "js-host-api.linux-arm64-gnu.node", + "files": [ + "js-host-api.linux-arm64-gnu.node" + ], + "description": "Node.js API bindings for Hyperlight JS - Linux arm64 gnu", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "git+https://github.com/hyperlight-dev/hyperlight-js.git" + }, + "homepage": "https://github.com/hyperlight-dev/hyperlight-js#readme", + "bugs": { + "url": "https://github.com/hyperlight-dev/hyperlight-js/issues" + }, + "engines": { + "node": ">= 18" + }, + "libc": [ + "glibc" + ] +} diff --git a/src/js-host-api/package-lock.json b/src/js-host-api/package-lock.json index 3b2ad80..e3b3c64 100644 --- a/src/js-host-api/package-lock.json +++ b/src/js-host-api/package-lock.json @@ -20,6 +20,7 @@ }, "optionalDependencies": { "@hyperlight-dev/js-host-api-darwin-arm64": "0.4.0", + "@hyperlight-dev/js-host-api-linux-arm64-gnu": "0.4.0", "@hyperlight-dev/js-host-api-linux-x64-gnu": "0.4.0", "@hyperlight-dev/js-host-api-linux-x64-musl": "0.4.0", "@hyperlight-dev/js-host-api-win32-x64-msvc": "0.4.0" diff --git a/src/js-host-api/package.json b/src/js-host-api/package.json index c24ae37..cf6134c 100644 --- a/src/js-host-api/package.json +++ b/src/js-host-api/package.json @@ -31,12 +31,14 @@ "x86_64-unknown-linux-gnu", "x86_64-unknown-linux-musl", "x86_64-pc-windows-msvc", - "aarch64-apple-darwin" + "aarch64-apple-darwin", + "aarch64-unknown-linux-gnu" ] }, "license": "Apache-2.0", "optionalDependencies": { "@hyperlight-dev/js-host-api-darwin-arm64": "0.4.0", + "@hyperlight-dev/js-host-api-linux-arm64-gnu": "0.4.0", "@hyperlight-dev/js-host-api-linux-x64-gnu": "0.4.0", "@hyperlight-dev/js-host-api-linux-x64-musl": "0.4.0", "@hyperlight-dev/js-host-api-win32-x64-msvc": "0.4.0" From 79d6a274aebafb46d5699dc5330416bda13f9136 Mon Sep 17 00:00:00 2001 From: Simon Davies Date: Wed, 16 Sep 2026 21:50:19 +0100 Subject: [PATCH 2/2] Run PR validation on stacked pull requests `ValidatePullRequests.yml` and `PRLabelChecker.yml` both filtered on `branches`, and that filter is evaluated against the pull request's own base ref. Stacked pull requests target the layer below them rather than `main`, so neither workflow fired for them: a stacked layer got only CodeQL and DCO, with no build, benchmarks, license-header, spelling or label check. That is worse than it sounds, because it defers all breakage to merge time. A layer is only built once the layer below merges and GitHub retargets it to `main`, so a stack is validated one layer at a time, in sequence, after each merge, instead of in parallel while it is being reviewed. Drop the `branches` filter from both rather than adding a pattern for stack branch names, which would only encode one contributor's naming convention. No long-lived non-`main` branches are used as pull request bases: every branch is either `release/**`, which the filter already matched, or an ordinary feature or dependabot branch, which are pull request heads rather than bases. `PRLabelChecker.yml` is widened as well because `check-labels` is a required status check on `main`. Base retargeting raises an `edited` event, which is not in that workflow's `types`, so a layer labelled while it still targeted the layer below would never run the check, and nothing would re-fire it afterwards, leaving a required check pending and blocking the merge. Check runs attach to the head SHA, so running once while the layer is stacked keeps the result valid through retargeting. Concurrency is unaffected: the group is keyed on `github.ref`, which is `refs/pull//merge` for pull request events and therefore already unique per pull request. This does mean more concurrent matrix builds when several layers are open at once, which is the intended trade for not serialising failures to merge time. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: Simon Davies --- .github/workflows/PRLabelChecker.yml | 7 ++++++- .github/workflows/ValidatePullRequests.yml | 8 +++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/workflows/PRLabelChecker.yml b/.github/workflows/PRLabelChecker.yml index bc84ed2..2e72497 100644 --- a/.github/workflows/PRLabelChecker.yml +++ b/.github/workflows/PRLabelChecker.yml @@ -1,7 +1,12 @@ name: Label Checker +# No `branches:` filter, for the same reason as ValidatePullRequests.yml. +# `check-labels` is a required check on `main`, and base retargeting raises an +# `edited` event, which is absent from the types below. A layer labelled while +# it still targets the layer below would therefore never run this check, and +# after retargeting nothing re-fires it, leaving a required check permanently +# pending. Running on every base keeps the result attached to the head SHA. on: pull_request: - branches: main types: [opened, labeled, unlabeled, synchronize, reopened] permissions: pull-requests: read diff --git a/.github/workflows/ValidatePullRequests.yml b/.github/workflows/ValidatePullRequests.yml index b2aeedf..5238470 100644 --- a/.github/workflows/ValidatePullRequests.yml +++ b/.github/workflows/ValidatePullRequests.yml @@ -2,9 +2,15 @@ name: Validate Pull Request +# No `branches:` filter: PRs in a stack target the layer below them rather than +# `main`, and the filter is evaluated against the PR's own base ref, so a +# filtered trigger leaves every stacked layer without build or test coverage +# until the layer below merges and GitHub retargets it. That serialises all +# breakage to merge time. `report-ci-status` is a required check on `main`, and +# check runs attach to the head SHA, so running here means the result is already +# present when a layer is retargeted. on: pull_request: - branches: [ main, 'release/**' ] # Cancels old running job if a new one is triggered (e.g. by a push onto the same branch). # This will cancel dependent jobs as well, such as dep_build and dep_benchmarks