Skip to content

transfer option for invoke and capabilities #470

transfer option for invoke and capabilities

transfer option for invoke and capabilities #470

name: Build, Test, Release
on: [ push ]
defaults:
run:
shell: sh
env:
NODE_HEADERS: 24.16.0
V8_REF: 14.8.178.28
jobs:
# First, check to see if v8 has been built for each target. If not, then a more powerful `runs-on`
# builder is used on EC2.
# nb: Windows support w/ runs-on is beta and doesn't include the same tooling as GitHub.
# https://runs-on.com
# https://github.com/runs-on/runs-on
configure:
strategy:
matrix:
host:
- runs-on: ubuntu-24.04-arm
triplet: aarch64-alpine-linux-musl
- runs-on: ubuntu-24.04-arm
triplet: aarch64-unknown-linux-gnu
- runs-on: ubuntu-24.04
triplet: x86_64-alpine-linux-musl
- runs-on: ubuntu-24.04
triplet: x86_64-unknown-linux-gnu
name: Configure [${{ matrix.host.triplet }}]
runs-on: ${{ matrix.host.runs-on }}
outputs:
aarch64-alpine-linux-musl: ${{ steps.output.outputs.aarch64-alpine-linux-musl }}
aarch64-unknown-linux-gnu: ${{ steps.output.outputs.aarch64-unknown-linux-gnu }}
x86_64-alpine-linux-musl: ${{ steps.output.outputs.x86_64-alpine-linux-musl }}
# nb: Does not come with /c/msys64, so it doesn't work for me
x86_64-pc-windows: ${{ steps.output.outputs.x86_64-pc-windows }}
x86_64-unknown-linux-gnu: ${{ steps.output.outputs.x86_64-unknown-linux-gnu }}
steps:
- uses: actions/cache/restore@v5
id: cache
with:
key: v8/${{ env.V8_REF }}/${{ matrix.host.triplet }}
lookup-only: true
path: deps/v8/out
- id: output
env:
CACHE_HIT: ${{ steps.cache.outputs.cache-hit }}
TRIPLET: ${{ matrix.host.triplet }}
RUNS_ON: ${{ matrix.host.runs-on }}
run: |
set -ux
if [ "$CACHE_HIT" = true ]; then
echo "$TRIPLET=$RUNS_ON" >> "$GITHUB_OUTPUT"
else
case "$RUNS_ON" in
ubuntu-24.04)
# Default disk is 28G, which is not enough to build v8 on Linux (sysroot required).
echo "$TRIPLET=runs-on=$GITHUB_RUN_ID/family=m7a+m7i/cpu=32+48/image=ubuntu24-full-x64,volume=64g" >> "$GITHUB_OUTPUT"
;;
ubuntu-24.04-arm)
echo "$TRIPLET=runs-on=$GITHUB_RUN_ID/family=m7g/cpu=32+48/image=ubuntu24-full-arm64" >> "$GITHUB_OUTPUT"
;;
windows-2025-vs2026)
echo "$TRIPLET=runs-on=$GITHUB_RUN_ID/family=m7a+m7i/cpu=32+48/image=windows25-full-x64" >> "$GITHUB_OUTPUT"
;;
esac
fi
# Build isolated-vm
build:
name: Build [${{ matrix.host.triplet }}]
needs: configure
strategy:
fail-fast: false
matrix:
host:
- runs-on: ${{ needs.configure.outputs.aarch64-alpine-linux-musl }}
container: alpine:edge
triplet: aarch64-alpine-linux-musl
- runs-on: macos-15
triplet: aarch64-apple-darwin
- runs-on: ${{ needs.configure.outputs.aarch64-unknown-linux-gnu }}
container: debian:sid
triplet: aarch64-unknown-linux-gnu
- runs-on: ${{ needs.configure.outputs.x86_64-alpine-linux-musl }}
container: alpine:edge
triplet: x86_64-alpine-linux-musl
- runs-on: macos-15-intel
triplet: x86_64-apple-darwin
- runs-on: windows-2025-vs2026
triplet: x86_64-pc-windows
- runs-on: ${{ needs.configure.outputs.x86_64-unknown-linux-gnu }}
container: debian:sid
triplet: x86_64-unknown-linux-gnu
runs-on: ${{ matrix.host.runs-on }}
container: ${{ matrix.host.container }}
concurrency: ${{ matrix.host.triplet }}
env:
TRIPLET: ${{ matrix.host.triplet }}
steps:
- if: matrix.host.triplet == 'aarch64-alpine-linux-musl'
uses: laverdet/alpine-arm64@v1
# Setup
- uses: actions/checkout@v5
- run: |
echo "$GITHUB_WORKSPACE/packages/auto_js/js/cmake" >> "$GITHUB_PATH"
echo "$GITHUB_WORKSPACE/scripts" >> "$GITHUB_PATH"
# Install toolchain
- if: matrix.host.container == 'debian:sid'
name: Sources [Debian]
uses: laverdet/install/apt/sources@v1
with:
name: nodesource24
source: |
Types: deb
URIs: https://deb.nodesource.com/node_24.x
Suites: nodistro
Components: main
Signed-By: https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key
- if: matrix.host.container == 'alpine:edge'
name: Toolchain [Alpine]
uses: laverdet/install@v1
with:
packages: |
bash
binutils
boost-dev
build-base
clang22
clang22-extra-tools
cmake
curl
lld22
llvm22
ninja-is-really-ninja
nodejs
npm
tar
xxd
zstd
export: |
CC=clang-22
CXX=clang++-22
- if: matrix.host.container == 'debian:sid'
name: Toolchain [Debian]
uses: laverdet/install@v1
with:
packages: |
binutils
build-essential
cmake
curl
g++-16
gcc-16
libboost-all-dev
lld-22
ninja-build
nodejs
npm
xxd
zstd
export: |
CC=gcc-16
CXX=g++-16
LLD=lld-22
- if: matrix.host.container == 'debian:sid'
name: Toolchain [Debian]
run: ln -s "$(which "$LLD")" /usr/bin/ld.lld
- name: Toolchain [macOS]
if: runner.os == 'macOS'
run: |
set -ux
# cmake v4.4.0 is broken, v4.4.2 is needed.
brew update
# https://github.com/orgs/Homebrew/discussions/5602
brew install --quiet cmake ninja
brew install boost lld llvm node
# https://github.com/Homebrew/homebrew-core/issues/221782#issuecomment-3245658786
ln -s $(brew --prefix)/etc/clang ~/.config/clang
LLVM=$(brew --prefix llvm)
echo "CC=$LLVM/bin/clang" >> "$GITHUB_ENV"
echo "CXX=$LLVM/bin/clang++" >> "$GITHUB_ENV"
echo "ECO_STD=$LLVM/lib/c++/libc++.modules.json" >> "$GITHUB_ENV"
# On Windows, the POSIX shell in GitHub actions is Git Bash. This runs in an isolated MSYS2
# installation under `C:/Program Files/Git`. The runner comes with another MSYS2 installation
# in `C:/msys64`. This action adds the root MSYS2 installation higher up in $PATH so we can
# use `pacman`, and consume binaries installed by `pacman`.
# nb: Do not be confused by the `/msys64` directory which exists in the default GitHub shell!
# It is not the same as `C:/msys64`.
# nb: LLVM uses MSVC stdlib, so incomplete c++23 support. Unofficial LLVM uses libc/UCRT but
# is not supported by v8. Cross-build via Linux may be the only way.
# nb: windows-11-arm does *not* have Pacman!
# https://github.com/actions/partner-runner-images/blob/main/images/arm-windows-11-image.md
- if: runner.os == 'Windows'
name: Toolchain [Windows]
uses: laverdet/install@v1
with:
tool: /c/msys64/usr/bin/pacman
packages: |
mingw-w64-%a-boost
mingw64/mingw-w64-%a-cmake>=4.3.0
mingw64/mingw-w64-%a-ninja
export: |
CMAKE_MAKE_PROGRAM=ninja
CMAKE_PREFIX_PATH=C:/msys64/mingw64/lib/cmake
path: |
/c/msys64/usr/bin
/c/msys64/mingw64/bin
- if: runner.os == 'Windows'
name: Toolchain [Windows]
run: |
set -ux
echo ::group::Toolchain
# Look for existing version (diagnostics)
clang++ --version || true
# Install updated LLVM (and hide spammy progress bar)
winget install --accept-source-agreements --silent -e --id=LLVM.LLVM --version=22.1.7 | tr '\r' '\n' | grep -Ev --line-buffered '^[ -\/|]+$'
echo "CC=clang.exe" >> "$GITHUB_ENV"
echo "CXX=clang++.exe" >> "$GITHUB_ENV"
echo ::endgroup::
echo ::group::import std hack
# /c/Program Files/Microsoft Visual Studio/18/Enterprise
VISUAL_STUDIO_PATH=$(cygpath "$(vswhere | grep installationPath: | cut -c19-)")
# /c/Program Files/Microsoft Visual Studio/18/Enterprise/VC/Tools/MSVC/14.50.35717
MSVC_PATH="$VISUAL_STUDIO_PATH/VC/Tools/MSVC/$(ls "$VISUAL_STUDIO_PATH/VC/Tools/MSVC" | sort -V | tail -1)"
# Make the `import std` ecostd manifest
internal_escape() { cat | sed 's \\ \\\\ g'; }
json_string() { echo "\"$(cat | internal_escape)\""; }
cat <<EOF > "$RUNNER_TOOL_CACHE/stl.modules.json"
{
"version": 1,
"revision": 1,
"local-arguments": {
"system-include-directories": [ $(echo "$MSVC_PATH/modules" | json_string) ]
},
"modules": [
{ "logical-name": "std", "is-std-library": true, "source-path": $(cygpath -w "$MSVC_PATH/modules/std.ixx" | internal_escape | json_string) },
{ "logical-name": "std", "is-std-library": true, "source-path": $(cygpath -w "$MSVC_PATH/modules/std.compat.ixx" | internal_escape | json_string) }
]
}
EOF
echo "ECO_STD=/$(cygpath.exe -m "$RUNNER_TOOL_CACHE/stl.modules.json")" >> "$GITHUB_ENV"
echo ::endgroup::
echo ::group::Dependency Walker
mkdir -p "$RUNNER_TOOL_CACHE/depwalker"
cd "$RUNNER_TOOL_CACHE/depwalker"
# `--ssl-no-revoke`: curl: (35) schannel: next InitializeSecurityContext failed:
# CRYPT_E_REVOCATION_OFFLINE (0x80092013) - The revocation function was unable to check
# revocation because the revocation server was offline.
curl -fsSL --ssl-no-revoke -o depwalker.zip https://github.com/lucasg/Dependencies/releases/download/v1.11.1/Dependencies_x64_Release.zip
unzip depwalker.zip
rm depwalker.zip
echo "$PWD" >> "$GITHUB_PATH"
echo ::endgroup::
# Restore previously built v8
- uses: actions/cache/restore@v5
id: v8-cache
with:
key: v8/${{ env.V8_REF }}/${{ matrix.host.triplet }}
path: deps/v8/out
# Build v8 on cache miss
- if: steps.v8-cache.outputs.cache-hit != 'true'
name: Build v8
uses: ./.github/actions/v8
with:
container: ${{ matrix.host.container }}
ref: ${{ env.V8_REF }}
triplet: ${{ matrix.host.triplet }}
# Cache v8
- if: steps.v8-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@v5
with:
key: ${{ steps.v8-cache.outputs.cache-primary-key }}
path: deps/v8/out
# Download nodejs headers
- uses: actions/cache/restore@v5
id: nodejs-headers-cache
with:
enableCrossOsArchive: true
key: nodejs-headers-${{ env.NODE_HEADERS }}
path: deps/nodejs
- if: steps.nodejs-headers-cache.outputs.cache-hit != 'true'
name: node headers
id: nodejs-headers
env:
NODE_HEADERS: ${{ env.NODE_HEADERS }}
run: set -u; nodejs_select "deps/nodejs/$NODE_HEADERS"
- if: steps.nodejs-headers.outcome == 'success'
uses: actions/cache/save@v5
with:
enableCrossOsArchive: true
key: ${{ steps.nodejs-headers-cache.outputs.cache-primary-key }}
path: deps/nodejs
# Build isolated-vm
- uses: pnpm/action-setup@v5
- name: pnpm install
run: |
set -ux
pnpm install --frozen-lockfile
npx tsc -b
- name: Configure
env:
NODE_DIST_DIR: deps/nodejs/${{ env.NODE_HEADERS }}
V8_REF: ${{ env.V8_REF }}
run: |
set -ux
V8_REF_PATH=$PWD/deps/v8/out/$V8_REF
cmake \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_MODULE_PATH="$(pnpm exec auto_js_cmake_include)" \
-DIVM_INSTALL=artifacts \
-DV8_INCLUDE_DIR="$V8_REF_PATH/include" \
-DV8_INCLUDE_GN_FILE="$V8_REF_PATH/$TRIPLET.release/include/v8-gn.h" \
-DV8_LIBRARY_PATH="$V8_REF_PATH/$TRIPLET.release" \
-DV8_LIBRARY_TYPE=static \
${ECO_STD:+"-DCMAKE_CXX_STDLIB_MODULES_JSON=${ECO_STD}"} \
-B build \
-G Ninja \
;
- name: Build
run: ninja -C build backend_napi_v8
- name: Sanity Check
run: |
PLATFORM=$(triplet_util -t "$TRIPLET" %p)
# `actions/upload-artifact` uses the 'build/[...]' location, but we copy the module
# into place for the sanity check. It's a copy and not a link for the NativeModule rpath
# test below.
cp -a "$PWD/build/packages/backend_napi_v8/artifacts/$PLATFORM" "packages/isolated-vm/node_modules/@isolated-vm/experimental-$PLATFORM"
node -e "require('@isolated-vm/experimental')"
- name: NativeModule test
working-directory: packages/addon_example
run: |
cmake \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_MODULE_PATH="$(pnpm exec auto_js_cmake_include)" \
${ECO_STD:+"-DCMAKE_CXX_STDLIB_MODULES_JSON=${ECO_STD}"} \
-G Ninja -B build \
;
ninja -C build
node addon.js
# Ensure that the library wasn't linked with a full path
mv ../isolated-vm/node_modules ../
node addon.js
# Diagnostics
- if: runner.os == 'Linux'
name: Symbols [Linux]
run: |
set -ux
SO_PATH="build/packages/backend_napi_v8/backend_napi_v8.$(triplet_util -t "$TRIPLET" %p).node"
ls -la "$SO_PATH"
ls -lah "$SO_PATH"
ldd "$SO_PATH" 2>&1 | grep -v 'Error relocating'
nm -guC "$SO_PATH"
- if: runner.os == 'Windows'
name: Symbols [Windows]
run: |
set -ux
DLL_PATH="build/packages/backend_napi_v8/backend_napi_v8.$(triplet_util -t "$TRIPLET" %p).node"
ls -la "$DLL_PATH"
ls -lah "$DLL_PATH"
dependencies.exe -imports "$DLL_PATH"
# Upload build artifacts
- uses: actions/upload-artifact@v6
with:
name: ${{ matrix.host.triplet }}
path: build/packages/backend_napi_v8/artifacts
if-no-files-found: error
# Console on failure
- if: failure()
uses: laverdet/console@v1
# Run tests
test:
name:
Test [${{ matrix.host.container || matrix.host.runs-on }}
${{
(endsWith(matrix.host.runs-on, '-arm') && 'arm64') ||
(endsWith(matrix.host.runs-on, '-intel') && 'x64') ||
(startsWith(matrix.host.runs-on, 'macos') && 'arm64') || 'x64'
}}
${{ matrix.runtime.runtime }}${{ (matrix.runtime.runtime == 'node' && format(':{0}', matrix.runtime.node)) || '' }}]
needs: build
strategy:
fail-fast: false
matrix:
host:
- runs-on: ubuntu-24.04-arm
container: alpine:edge
- runs-on: macos-15
- runs-on: ubuntu-24.04-arm
container: debian:forky
- runs-on: ubuntu-24.04
container: alpine:edge
- runs-on: macos-15-intel
- runs-on: windows-2025
- runs-on: ubuntu-24.04
container: debian:forky
runtime:
- runtime: node
node: 24
- runtime: node
node: 26
- runtime: bun
node: 24
- runtime: deno
node: 24
exclude:
# Bun & Deno don't support musl. Actually, the gcompat hack from `laverdet/alpine-arm64`
# lets the tests run but this is probably not a platform you would find in the wild.
# https://github.com/oven-sh/bun/issues/14292
- host:
container: alpine:edge
runtime:
runtime: bun
- host:
container: alpine:edge
runtime:
runtime: deno
# Failure on Windows with Deno..
# Check failed: old_protection == PAGE_READWRITE || old_protection == PAGE_WRITECOPY.
- host:
runs-on: windows-2025
runtime:
runtime: deno
runs-on: ${{ matrix.host.runs-on }}
container: ${{ matrix.host.container }}
steps:
- if: matrix.host.container == 'alpine:edge'
uses: laverdet/alpine-arm64@v1
- if: matrix.host.container == 'alpine:edge'
uses: laverdet/install@v1
with:
packages: bash
# TODO(?): Remove this. The requirement on libatomic is in the distributed nodejs binaries.
# https://github.com/nodejs/node/issues/60790
- if: matrix.host.container == 'debian:forky' && matrix.runtime.node == '26'
uses: laverdet/install@v1
with:
packages: libatomic1
# Setup
- uses: actions/checkout@v5
- run: |
echo "$GITHUB_WORKSPACE/packages/auto_js/js/cmake" >> "$GITHUB_PATH"
echo "$GITHUB_WORKSPACE/scripts" >> "$GITHUB_PATH"
# Get triplet information and make package.json for module
- id: platform
run: |
echo "platform=$(triplet_util %p)" >> "$GITHUB_OUTPUT"
echo "triplet=$(triplet_util %t)" >> "$GITHUB_OUTPUT"
# Receive artifacts
- uses: actions/download-artifact@v8
with:
name: ${{ steps.platform.outputs.triplet }}
path: build/packages/backend_napi_v8/artifacts
# Install nodejs [musl]
- if: endsWith(steps.platform.outputs.triplet, '-linux-musl')
id: musl
env:
NODE_VERSION: ${{ matrix.runtime.node }}
run: |
set -ux
echo https://dl-cdn.alpinelinux.org/alpine/edge/community/ >> /etc/apk/repositories
echo https://dl-cdn.alpinelinux.org/alpine/edge/testing/ >> /etc/apk/repositories
case "$NODE_VERSION" in
26) echo "package=nodejs-current" >> "$GITHUB_OUTPUT" ;;
24) echo "package=nodejs" >> "$GITHUB_OUTPUT" ;;
*)
echo "Unknown node version: $NODE_VERSION" 1>&2
exit 1
;;
esac
- if: steps.musl.outcome == 'success'
uses: laverdet/install@v1
with:
packages: ${{ steps.musl.outputs.package }} npm
# Install nodejs [others]
- if: steps.musl.outcome == 'skipped'
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.runtime.node }}
# Install unzip in containers for setup-bun / setup-deno actions
- if: matrix.runtime.runtime != 'node' && (matrix.host.container == 'alpine:edge' || matrix.host.container == 'debian:forky')
uses: laverdet/install@v1
with:
packages: unzip
# Install Bun (maybe)
- if: matrix.runtime.runtime == 'bun'
uses: oven-sh/setup-bun@v2
with:
# https://github.com/oven-sh/bun/issues/37201
bun-version: canary
# Install Deno (maybe)
- if: matrix.runtime.runtime == 'deno'
uses: denoland/setup-deno@v2
with:
# TODO: Back to canary
deno-version: 2.9.4
# Install npm dependencies, build TypeScript, check module sanity
- uses: pnpm/action-setup@v5
- name: Install
env:
PLATFORM: ${{ steps.platform.outputs.platform }}
run: |
set -ux
pnpm install --frozen-lockfile
npx tsc -b
mkdir -p node_modules/@isolated-vm
mv build/packages/backend_napi_v8/artifacts/$PLATFORM node_modules/@isolated-vm/experimental-$PLATFORM
node -e "require('@isolated-vm/experimental-$PLATFORM')"
# Run tests
- name: Test
env:
TEST_SCOPE: ${{ matrix.runtime.runtime }}
timeout-minutes: 1
run: |
pnpm config set script-shell /bin/sh
pnpm run -s test
# Console on failure
- uses: laverdet/console@v1
if: failure()
release:
name: Release
needs: test
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
# Setup
- uses: actions/checkout@v5
- uses: actions/setup-node@v6
with:
node-version: 'lts/*'
- uses: pnpm/action-setup@v5
- name: Setup
id: setup
run: |
set -ux
npm config set --location user access=public
pnpm install --frozen-lockfile
npx tsc -b
# Receive artifacts
- uses: actions/download-artifact@v8
id: artifacts
with:
path: packages/isolated-vm/artifacts
# Assemble @isolated-vm/experimental
- name: Assemble
working-directory: packages/isolated-vm
run: |
set -ux
npx napi create-npm-dirs
npx napi artifacts
set -x
# copy .lib for NativeModule consumers on win32
for PLATFORM in win32-x64; do
echo backend_napi_v8.$PLATFORM.lib
cp artifacts/*/$PLATFORM/backend_napi_v8.$PLATFORM.lib npm/$PLATFORM/
PACKAGE=$(jq '.files += [ "backend_napi_v8.'$PLATFORM'.lib" ]' npm/$PLATFORM/package.json)
echo "$PACKAGE" > npm/$PLATFORM/package.json
done
# Publish
- if: github.repository == 'laverdet/isolated-vm' && github.ref_name == 'experimental' && github.event_name == 'push'
id: publish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# https://github.com/pnpm/pnpm/issues/5894
if [ "$(git status --porcelain)" ]; then
git status && false
fi
pnpm publish --no-git-checks --recursive --publish-branch experimental --provenance