Skip to content

feat: Turbopack plugin + Next16 (#75) #314

feat: Turbopack plugin + Next16 (#75)

feat: Turbopack plugin + Next16 (#75) #314

Workflow file for this run

name: CI
on:
push:
branches: [main, next]
tags:
- 'v*'
pull_request:
branches: [main, next]
schedule:
- cron: '17 6 * * *'
workflow_dispatch:
inputs:
deploy_workers:
description: Deploy all supported Workers after verification
required: false
type: boolean
default: false
publish_packages:
description: Publish npm packages after verification
required: false
type: boolean
default: false
jobs:
# ─── Fast lint (parallel with build-extract) ──────────
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: .tool-versions
- uses: oven-sh/setup-bun@v2
with:
bun-version-file: .tool-versions
- run: bun install
- name: Lint
run: bunx vp run verify:lint
- name: NAPI loading contract check
run: |
if grep -rn "require('@animus-ui/extract')" packages/_integration/__tests__/; then
echo "ERROR: require('@animus-ui/extract') in _integration tests violates the NAPI loading contract."
echo "Use require('../../extract/index-v2.js') instead. See packages/_integration/CLAUDE.md."
exit 1
fi
# ─── Rust unit tests (debug profile, no NAPI binary) ─────
test-rust:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
with:
workspaces: |
packages/extract/crates/system-loader
packages/extract/crates/extract-v2
- uses: oven-sh/setup-bun@v2
with:
bun-version-file: .tool-versions
- run: bun install
- name: Rust unit tests
run: bunx vp run verify:unit:rust
# ─── Strict Rust lint (parallel with test-rust) ────────────
clippy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@1.97.0
with:
components: clippy
- uses: Swatinem/rust-cache@v2
with:
workspaces: |
packages/extract/crates/system-loader
packages/extract/crates/extract-v2
- uses: oven-sh/setup-bun@v2
with:
bun-version-file: .tool-versions
- run: bun install
- name: Clippy
run: bunx vp run verify:clippy
# ─── Rust dependency hygiene (parallel with test-rust) ───
hygiene-rust:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
with:
workspaces: |
packages/extract/crates/system-loader
packages/extract/crates/extract-v2
- uses: oven-sh/setup-bun@v2
with:
bun-version-file: .tool-versions
- run: bun install
- name: Install cargo-machete
uses: taiki-e/install-action@v2
with:
tool: cargo-machete@0.9.2
- name: Rust dependency hygiene
run: bunx vp run verify:hygiene:rust
# ─── Rust NAPI build matrix (release profile, no tests) ──
build-extract:
strategy:
fail-fast: false
matrix:
include:
- target: aarch64-apple-darwin
runner: macos-14
- target: x86_64-unknown-linux-gnu
runner: ubuntu-latest
- target: aarch64-unknown-linux-gnu
runner: ubuntu-24.04-arm
optional: true
runs-on: ${{ matrix.runner }}
continue-on-error: ${{ matrix.optional || false }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
with:
workspaces: |
packages/extract/crates/system-loader
packages/extract/crates/extract-v2
- uses: oven-sh/setup-bun@v2
with:
bun-version-file: .tool-versions
- run: bun install
- name: Build v2 NAPI binary
working-directory: packages/extract/crates/extract-v2
run: |
unset RUSTUP_TOOLCHAIN
bunx @napi-rs/cli build --platform --release --target ${{ matrix.target }}
- name: Upload v2 binary
uses: actions/upload-artifact@v4
with:
name: napi-v2-${{ matrix.target }}
path: packages/extract/crates/extract-v2/*.node
if-no-files-found: error
# ─── Full pipeline verification ─────────────────────────
verify:
needs: [build-extract, test-rust]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: .tool-versions
- uses: oven-sh/setup-bun@v2
with:
bun-version-file: .tool-versions
- name: Download v2 linux binary
# @animus-ui/showcase#verify:build runs on engine v2 (default since the
# 2026-07-13 flip) — the v2 binary is a hard precondition.
uses: actions/download-artifact@v4
with:
name: napi-v2-x86_64-unknown-linux-gnu
path: packages/extract/crates/extract-v2/
- run: bun install
- name: Verify v2 NAPI binary (repo root context)
run: |
ls -la packages/extract/crates/extract-v2/*.node
bun -e "const m = require('./packages/extract/index-v2.js'); if (typeof m.ExtractEngine !== 'function') { throw new Error('ExtractEngine missing — v2 NAPI load failed'); } console.log('v2 NAPI exports:', Object.keys(m));"
- name: Verify v2 NAPI binary (_integration context)
working-directory: packages/_integration/__tests__
run: |
bun -e "const m = require('../../extract/index-v2.js'); if (typeof m.ExtractEngine !== 'function') { throw new Error('ExtractEngine not a function — NAPI loading contract violated'); } console.log('OK: ExtractEngine is a function from _integration context');"
- name: Build TS
run: bunx vp run build:ts
- name: Compile
run: bunx vp run verify:compile
- name: Type Contract Tests
run: bunx vp run verify:types
- name: Unit Tests (TS)
run: bunx vp run verify:unit:ts
- name: Canary Tests
run: bunx vp run verify:canary
- name: Parity Oracle
run: bunx vp run verify:parity
- name: Integration Tests
run: bunx vp run verify:integration
- name: Showcase Build + Assert
run: |
bunx vp run @animus-ui/showcase#verify:build
bunx vp run @animus-ui/showcase#verify:assert
- name: Upload lane receipts
uses: actions/upload-artifact@v4
with:
name: receipts-showcase
path: packages/showcase/.receipts/
# ─── Next consumer lane (build + assert on every push) ──
verify-next:
needs: [build-extract]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: .tool-versions
- uses: oven-sh/setup-bun@v2
with:
bun-version-file: .tool-versions
- name: Download v2 linux binary
uses: actions/download-artifact@v4
with:
name: napi-v2-x86_64-unknown-linux-gnu
path: packages/extract/crates/extract-v2/
- run: bun install
- name: Build TS
run: bunx vp run build:ts
- name: Next consumer lane
run: |
bunx vp run @animus-ui/next-app#verify:build
bunx vp run @animus-ui/next-app#verify:assert
- name: Upload lane receipts
uses: actions/upload-artifact@v4
with:
name: receipts-next
path: e2e/next-app/.receipts/
# ─── Vite consumer lane (build + assert on every push) ──
verify-vite:
needs: [build-extract]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: .tool-versions
- uses: oven-sh/setup-bun@v2
with:
bun-version-file: .tool-versions
- name: Download v2 linux binary
uses: actions/download-artifact@v4
with:
name: napi-v2-x86_64-unknown-linux-gnu
path: packages/extract/crates/extract-v2/
- run: bun install
- name: Build TS
run: bunx vp run build:ts
- name: Vite consumer lane
run: |
bunx vp run @animus-ui/vite-app#verify:build
bunx vp run @animus-ui/vite-app#verify:assert
- name: Upload lane receipts
uses: actions/upload-artifact@v4
with:
name: receipts-vite
path: e2e/vite-app/.receipts/
# ─── Supported Worker consumer release gate ────────────
verify-workers:
needs: [build-extract]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: .tool-versions
- uses: oven-sh/setup-bun@v2
with:
bun-version-file: .tool-versions
- name: Download v2 linux binary
uses: actions/download-artifact@v4
with:
name: napi-v2-x86_64-unknown-linux-gnu
path: packages/extract/crates/extract-v2/
- run: bun install
- name: Build TS
run: bunx vp run build:ts
- name: Verify Worker contracts
run: bunx vp run verify:workers:contracts
- name: Verify supported Workers
run: bunx vp run --fail-if-no-match -F './e2e/*' -F '!@animus-ui/next-app' -F '!animus-packed-app' -F './packages/showcase' verify
# ─── Packed consumer lane (npm staging install on every push) ──
verify-packed:
needs: [build-extract]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: .tool-versions
- uses: oven-sh/setup-bun@v2
with:
bun-version-file: .tool-versions
- name: Download v2 linux binary
uses: actions/download-artifact@v4
with:
name: napi-v2-x86_64-unknown-linux-gnu
path: packages/extract/crates/extract-v2/
- run: bun install
- name: Build TS
run: bunx vp run build:ts
- name: Packed consumer lane
run: bunx vp run verify:packed
- name: Upload lane receipts
uses: actions/upload-artifact@v4
with:
name: receipts-packed
path: e2e/packed-app/.staging/receipts/
# ─── Worker deployment (main push, schedule, or explicit dispatch) ──
deploy-workers:
needs:
[
lint,
clippy,
hygiene-rust,
verify,
verify-next,
verify-vite,
verify-workers,
verify-packed,
]
if: >-
github.ref == 'refs/heads/main' &&
(github.event_name == 'push' ||
github.event_name == 'schedule' ||
(github.event_name == 'workflow_dispatch' && inputs.deploy_workers == true))
permissions:
contents: read
concurrency:
group: deploy-workers
cancel-in-progress: false
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: .tool-versions
- uses: oven-sh/setup-bun@v2
with:
bun-version-file: .tool-versions
- name: Download v2 linux binary
uses: actions/download-artifact@v4
with:
name: napi-v2-x86_64-unknown-linux-gnu
path: packages/extract/crates/extract-v2/
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Validate Workers
run: bash scripts/deploy/workers.sh validate
- name: Deploy Workers
env:
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
run: bash scripts/deploy/workers.sh deploy
# ─── Release (tag push or manual dispatch only) ─────────
release:
needs:
[
lint,
clippy,
hygiene-rust,
verify,
verify-next,
verify-vite,
verify-workers,
verify-packed,
]
if: >-
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) ||
(github.event_name == 'workflow_dispatch' && inputs.publish_packages == true)
runs-on: ubuntu-latest
env:
NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version-file: .tool-versions
- uses: actions/setup-node@v4
with:
node-version-file: .tool-versions
registry-url: 'https://registry.npmjs.org'
scope: 'animus-ui'
env:
NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
- run: bun install
- name: Verify npm auth
run: npm whoami
# ── Parse version from tag ──
- name: Set version from tag
id: version
run: |
if [[ "$GITHUB_REF" == refs/tags/v* ]]; then
VERSION="${GITHUB_REF#refs/tags/v}"
else
VERSION="0.0.0-dev.$(date +%s)"
fi
if [[ "$VERSION" == *-* ]]; then
TAG="next"
else
TAG="latest"
fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "Publishing version: $VERSION (tag: $TAG)"
# ── Set version in all packages ──
- name: Bump versions and resolve workspace deps
run: |
VERSION="${{ steps.version.outputs.version }}"
for pkg in properties system extract vite-plugin next-plugin; do
jq --arg v "$VERSION" '
.version = $v |
if .dependencies then .dependencies |= with_entries(if .value == "workspace:*" then .value = $v else . end) else . end |
if .optionalDependencies then .optionalDependencies |= with_entries(if (.value | test("^[0-9]")) then .value = $v else . end) else . end
' packages/$pkg/package.json > tmp.json && mv tmp.json packages/$pkg/package.json
echo "$pkg → $VERSION"
done
# ── Build TS packages ──
- name: Build
run: bunx vp run build:ts
# ── Download all NAPI binaries ──
# A target missing its v2 binary fails the release job —
# never publish a partial matrix (engine-release-packaging).
- name: Download darwin-arm64 v2 binary
uses: actions/download-artifact@v4
with:
name: napi-v2-aarch64-apple-darwin
path: packages/extract/crates/extract-v2/
- name: Download linux-x64 v2 binary
uses: actions/download-artifact@v4
with:
name: napi-v2-x86_64-unknown-linux-gnu
path: packages/extract/crates/extract-v2/
- name: Download linux-arm64 v2 binary
uses: actions/download-artifact@v4
with:
name: napi-v2-aarch64-unknown-linux-gnu
path: packages/extract/crates/extract-v2/
# ── Pack and verify one immutable release bundle ──
- name: Pack immutable release bundle
env:
RELEASE_BUNDLE: ${{ runner.temp }}/animus-release-bundle
run: |
VERSION="${{ steps.version.outputs.version }}"
mkdir -p "$RELEASE_BUNDLE"
if compgen -G "$RELEASE_BUNDLE/*.tgz" > /dev/null; then
echo "ERROR: release bundle is not empty: $RELEASE_BUNDLE" >&2
exit 1
fi
for pkg in properties system extract vite-plugin next-plugin; do
npm pack "packages/$pkg" --pack-destination "$RELEASE_BUNDLE" --ignore-scripts
done
for artifact in \
"animus-ui-extract-${VERSION}.tgz" \
"animus-ui-properties-${VERSION}.tgz" \
"animus-ui-system-${VERSION}.tgz" \
"animus-ui-vite-plugin-${VERSION}.tgz" \
"animus-ui-next-plugin-${VERSION}.tgz"; do
if [ ! -f "$RELEASE_BUNDLE/$artifact" ]; then
echo "ERROR: release artifact missing: $RELEASE_BUNDLE/$artifact" >&2
exit 1
fi
done
ls -la "$RELEASE_BUNDLE"
- name: Verify immutable release bundle
env:
RELEASE_BUNDLE: ${{ runner.temp }}/animus-release-bundle
run: |
VERSION="${{ steps.version.outputs.version }}"
bash scripts/verify/postpack-smoke.sh \
--tarball "$RELEASE_BUNDLE/animus-ui-extract-${VERSION}.tgz" \
--expect-full-matrix
bash scripts/verify/packed.sh --tarballs-dir "$RELEASE_BUNDLE"
# ── Publish the verified bytes in dependency order ──
- name: Publish immutable release bundle
env:
RELEASE_BUNDLE: ${{ runner.temp }}/animus-release-bundle
run: |
VERSION="${{ steps.version.outputs.version }}"
NPM_TAG="${{ steps.version.outputs.tag }}"
npm publish "$RELEASE_BUNDLE/animus-ui-extract-${VERSION}.tgz" --access public --tag "$NPM_TAG" --ignore-scripts
npm publish "$RELEASE_BUNDLE/animus-ui-properties-${VERSION}.tgz" --access public --tag "$NPM_TAG" --ignore-scripts
npm publish "$RELEASE_BUNDLE/animus-ui-system-${VERSION}.tgz" --access public --tag "$NPM_TAG" --ignore-scripts
npm publish "$RELEASE_BUNDLE/animus-ui-vite-plugin-${VERSION}.tgz" --access public --tag "$NPM_TAG" --ignore-scripts
npm publish "$RELEASE_BUNDLE/animus-ui-next-plugin-${VERSION}.tgz" --access public --tag "$NPM_TAG" --ignore-scripts