Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
363 changes: 363 additions & 0 deletions .github/workflows/publish-npm-dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,363 @@
name: Publish npm dev packages

on:
workflow_call:
inputs:
source_ref:
description: Exact 40-character lowercase commit SHA to package
required: true
type: string
dry_run:
description: Build, pack, and smoke-test without publishing
default: true
type: boolean

permissions:
contents: read
id-token: write

concurrency:
group: npm-dev-${{ inputs.source_ref }}
cancel-in-progress: false

jobs:
prepare:
runs-on: depot-ubuntu-24.04-arm-4
timeout-minutes: 30
outputs:
git_sha: ${{ steps.identity.outputs.git_sha }}
short_sha: ${{ steps.identity.outputs.short_sha }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.source_ref }}

- uses: actions/checkout@v4
with:
ref: ${{ github.ref }}
path: .npm-dev-tools

- id: identity
name: Record immutable source identity
shell: bash
run: |
set -euo pipefail
source_ref="${{ inputs.source_ref }}"
[[ "$source_ref" =~ ^[0-9a-f]{40}$ ]] || {
echo "source_ref must be an exact 40-character lowercase commit SHA" >&2
exit 1
}
git_sha="$(git rev-parse HEAD)"
test "$git_sha" = "$source_ref"
echo "git_sha=$git_sha" >> "$GITHUB_OUTPUT"
echo "short_sha=${git_sha:0:12}" >> "$GITHUB_OUTPUT"

- uses: pnpm/action-setup@v4
with:
version: 10.11.0

- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm

- name: Test version and dependency rewriting
env:
NPM_DEV_SOURCE_ROOT: ${{ github.workspace }}
run: node --test .npm-dev-tools/scripts/npm-dev-release.test.mjs

build-addon:
needs: prepare
strategy:
fail-fast: false
matrix:
include:
- runner: depot-macos-15
os: macos
target: aarch64-apple-darwin
triple: darwin-arm64
expect_arch: arm64
- runner: depot-macos-15
os: macos
target: x86_64-apple-darwin
triple: darwin-x64
expect_arch: x86_64
- runner: depot-ubuntu-24.04-16
os: linux
target: x86_64-unknown-linux-gnu
triple: linux-x64-gnu
expect_arch: x86-64
- runner: depot-ubuntu-24.04-arm-16
os: linux
target: aarch64-unknown-linux-gnu
triple: linux-arm64-gnu
expect_arch: aarch64
runs-on: ${{ matrix.runner }}
timeout-minutes: 60
env:
CARGO_INCREMENTAL: "0"
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.prepare.outputs.git_sha }}

- name: Setup Rust (linux)
if: matrix.os == 'linux'
uses: ./.github/actions/setup-rust
with:
depot-project-id: ${{ vars.DEPOT_PROJECT_ID }}
repo-access-token: ${{ secrets.REPO_ACCESS_TOKEN }}
install-protoc: "true"

- uses: dtolnay/rust-toolchain@nightly
if: matrix.os == 'macos'

- name: Configure git credentials for cargo (macos)
if: matrix.os == 'macos'
run: git config --global url."https://x-access-token:${{ secrets.REPO_ACCESS_TOKEN }}@github.com/".insteadOf "https://github.com/"

- name: Install protoc (macos)
if: matrix.os == 'macos'
run: brew install protobuf

- name: Add cross target
if: matrix.target == 'x86_64-apple-darwin'
run: rustup target add x86_64-apple-darwin

- uses: pnpm/action-setup@v4
with:
version: 10.11.0

- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Stamp native addon version
shell: bash
run: |
set -euo pipefail
npm_version="$(node -p "require('./packages/bindings/package.json').version")-dev.${{ needs.prepare.outputs.git_sha }}"
node --input-type=module - "$npm_version" <<'EOF'
import { readFileSync, writeFileSync } from "node:fs"
const [version] = process.argv.slice(2)
const path = "crates/alien-bindings-node/Cargo.toml"
const source = readFileSync(path, "utf8")
const updated = source.replace(/^version\.workspace = true$/m, `version = "${version}"`)
if (updated === source) throw new Error("native addon Cargo version was not updated")
writeFileSync(path, updated)
EOF
test "$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[] | select(.name == "alien-bindings-node") | .version')" = "$npm_version"

- name: Build addon
env:
CARGO_NET_GIT_FETCH_WITH_CLI: "true"
run: |
pnpm --filter @alienplatform/bindings exec napi build \
--platform --release --target ${{ matrix.target }} \
--cwd "$GITHUB_WORKSPACE/crates/alien-bindings-node"
cp "crates/alien-bindings-node/alien-bindings-node.${{ matrix.triple }}.node" \
"packages/bindings/npm/${{ matrix.triple }}/alien-bindings-node.${{ matrix.triple }}.node"
file "packages/bindings/npm/${{ matrix.triple }}/alien-bindings-node.${{ matrix.triple }}.node" \
| grep -q "${{ matrix.expect_arch }}"

- uses: actions/upload-artifact@v4
with:
name: npm-dev-addon-${{ matrix.triple }}
retention-days: 1
path: packages/bindings/npm/${{ matrix.triple }}/alien-bindings-node.${{ matrix.triple }}.node

pack-and-publish:
needs: [prepare, build-addon]
runs-on: depot-ubuntu-24.04-arm-4
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.prepare.outputs.git_sha }}

- uses: actions/checkout@v4
with:
ref: ${{ github.ref }}
path: .npm-dev-tools

- uses: pnpm/action-setup@v4
with:
version: 10.11.0

- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
registry-url: https://registry.npmjs.org

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build JavaScript packages
run: pnpm build
env:
NODE_OPTIONS: --max-old-space-size=8192

- name: Restore native addons
uses: actions/download-artifact@v4
with:
pattern: npm-dev-addon-*
path: addons

- name: Stage native addons
shell: bash
run: |
set -euo pipefail
for triple in darwin-arm64 darwin-x64 linux-x64-gnu linux-arm64-gnu; do
cp "addons/npm-dev-addon-${triple}/alien-bindings-node.${triple}.node" \
"packages/bindings/npm/${triple}/alien-bindings-node.${triple}.node"
done

- name: Rewrite package graph to immutable dev versions
run: |
node .npm-dev-tools/scripts/npm-dev-release.mjs rewrite "${{ needs.prepare.outputs.git_sha }}" | tee npm-dev-versions.json
node packages/bindings/scripts/inject-optional-deps.mjs
node .npm-dev-tools/scripts/npm-dev-release.mjs validate "${{ needs.prepare.outputs.git_sha }}"
{
echo '## npm dev package versions'
echo '```json'
cat npm-dev-versions.json
echo '```'
} >> "$GITHUB_STEP_SUMMARY"

- name: Pack every publishable package
shell: bash
run: |
set -euo pipefail
mkdir -p npm-dev-tarballs npm-dev-pack-results
while IFS= read -r package_dir; do
safe_name="$(basename "$package_dir")-$(printf '%s' "$package_dir" | sha256sum | cut -c1-8)"
(cd "$package_dir" && npm pack --json --pack-destination "$GITHUB_WORKSPACE/npm-dev-tarballs") \
> "npm-dev-pack-results/${safe_name}.json"
done <<'EOF'
packages/core
packages/commands
packages/bindings
packages/sdk
packages/testing
client-sdks/platform/typescript
client-sdks/manager/typescript
packages/bindings/npm/darwin-arm64
packages/bindings/npm/darwin-x64
packages/bindings/npm/linux-x64-gnu
packages/bindings/npm/linux-arm64-gnu
EOF
node .npm-dev-tools/scripts/npm-dev-release.mjs digest npm-dev-tarballs/*.tgz | tee npm-dev-digests.json
{
echo '## npm tarball SHA-256 digests'
echo '```json'
cat npm-dev-digests.json
echo '```'
} >> "$GITHUB_STEP_SUMMARY"

- name: Smoke install packed dependency graph
shell: bash
run: |
set -euo pipefail
consumer="$(mktemp -d)"
npm init --yes --prefix "$consumer" >/dev/null
case "$(uname -s)-$(uname -m)" in
Linux-x86_64) native_package_prefix=alienplatform-bindings-linux-x64-gnu ;;
Linux-aarch64) native_package_prefix=alienplatform-bindings-linux-arm64-gnu ;;
Darwin-x86_64) native_package_prefix=alienplatform-bindings-darwin-x64 ;;
Darwin-arm64) native_package_prefix=alienplatform-bindings-darwin-arm64 ;;
*) echo "Unsupported smoke-test platform: $(uname -s)-$(uname -m)" >&2; exit 1 ;;
esac
tarballs=("$GITHUB_WORKSPACE"/npm-dev-tarballs/*.tgz)
install_tarballs=()
for tarball in "${tarballs[@]}"; do
filename="$(basename "$tarball")"
case "$filename" in
alienplatform-bindings-darwin-arm64-*.tgz|alienplatform-bindings-darwin-x64-*.tgz|alienplatform-bindings-linux-x64-gnu-*.tgz|alienplatform-bindings-linux-arm64-gnu-*.tgz)
[[ "$filename" == "$native_package_prefix"-*.tgz ]] && install_tarballs+=("$tarball")
;;
*) install_tarballs+=("$tarball") ;;
esac
done
npm install --prefix "$consumer" "${install_tarballs[@]}"
cd "$consumer"
node --input-type=module <<'EOF'
import { Container } from "@alienplatform/core"
import { AlienError, BindingNotConfiguredError, storage } from "@alienplatform/bindings"
const resource = new Container("dev-package-smoke")
.code({ type: "image", image: "busybox:stable" })
.cpu(0.25)
.memory("256Mi")
.permissions("dev-package-smoke")
.commandsEnabled(true)
.build()
if (resource.config.commandsEnabled !== true) throw new Error("packed core lacks commandsEnabled")
await import("@alienplatform/commands")
await import("@alienplatform/sdk")
try {
await storage("dev-package-smoke").list()
throw new Error("unconfigured binding unexpectedly succeeded")
} catch (error) {
if (!(error instanceof AlienError) || error.code !== BindingNotConfiguredError.metadata.code) throw error
}
EOF

- name: Validate npm authentication
if: ${{ !inputs.dry_run }}
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npm whoami

- name: Publish immutable packages under dev tag
if: ${{ !inputs.dry_run }}
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
shell: bash
run: |
set -euo pipefail
publish_tarball() {
local tarball
tarball="$(realpath "$1")"
local manifest name version local_integrity remote_integrity
manifest="$(tar -xOzf "$tarball" package/package.json)"
name="$(jq -er '.name' <<< "$manifest")"
version="$(jq -er '.version' <<< "$manifest")"
local_integrity="sha512-$(openssl dgst -sha512 -binary "$tarball" | openssl base64 -A)"
[[ "$version" =~ -dev\.[0-9a-f]{40}$ ]] || { echo "Refusing stable version $name@$version" >&2; exit 1; }
remote_integrity="$(npm view "$name@$version" dist.integrity 2>/dev/null || true)"
if [ -n "$remote_integrity" ]; then
test "$remote_integrity" = "$local_integrity" || {
echo "Published $name@$version has different bytes" >&2
exit 1
}
echo "$name@$version already exists with matching integrity"
else
npm publish "$tarball" --access public --tag dev
fi
}

# Native packages and leaf dependencies first; public entry points last.
for pattern in \
'*bindings-darwin-arm64*' '*bindings-darwin-x64*' \
'*bindings-linux-x64-gnu*' '*bindings-linux-arm64-gnu*' \
'*core*' '*bindings-[0-9]*' '*commands*' '*sdk*' '*testing*' \
'*platform-api*' '*manager-api*'; do
mapfile -t matches < <(compgen -G "npm-dev-tarballs/${pattern}.tgz" || true)
test "${#matches[@]}" -gt 0 || continue
for tarball in "${matches[@]}"; do publish_tarball "$tarball"; done
done

- name: Upload package evidence
uses: actions/upload-artifact@v4
with:
name: npm-dev-packages-${{ needs.prepare.outputs.short_sha }}
retention-days: 7
path: |
npm-dev-versions.json
npm-dev-digests.json
npm-dev-pack-results
npm-dev-tarballs
Loading
Loading