Skip to content

🧩 WASM Assets #75

🧩 WASM Assets

🧩 WASM Assets #75

Workflow file for this run

name: 🧱 Build WASM Assets
on:
workflow_call:
inputs:
force:
description: 'Force rebuild (ignore cache)'
required: false
type: boolean
default: false
build-yoga:
description: 'Build Yoga Layout WASM'
required: false
type: boolean
default: true
build-models:
description: 'Build AI Models'
required: false
type: boolean
default: true
models-quant-level:
description: 'AI Models quantization level'
required: false
type: string
default: 'INT4'
build-onnx:
description: 'Build ONNX Runtime WASM'
required: false
type: boolean
default: true
workflow_dispatch:
inputs:
force:
description: 'Force rebuild (ignore cache)'
required: false
type: boolean
default: false
build-yoga:
description: 'Build Yoga Layout WASM'
required: false
type: boolean
default: true
build-models:
description: 'Build AI Models'
required: false
type: boolean
default: true
models-quant-level:
description: 'AI Models quantization level'
required: false
type: choice
options:
- INT4
- INT8
default: INT4
build-onnx:
description: 'Build ONNX Runtime WASM'
required: false
type: boolean
default: true
# Removed push/pull_request triggers to avoid runner contention.
# WASM assets are cached and used by socketbin builds.
# Run manually via workflow_dispatch when WASM sources change.
permissions:
contents: read
concurrency:
group: build-wasm-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
build-yoga-layout:
name: 🧘 Build Yoga Layout WASM
if: ${{ inputs.build-yoga != false }}
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Setup Node.js
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with:
node-version: 22
- name: Setup pnpm
uses: pnpm/action-setup@9fd676a19091d4595eefd76e4bd31c97133911f1 # v4.2.0
with:
version: ^10.16.0
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Generate yoga build cache key
id: yoga-cache-key
run: |
# Extract Yoga version from package.json (package version matches Yoga Layout release).
YOGA_VERSION=$(node -p "require('./packages/yoga-layout/package.json').version")
# Hash includes source files and package.json.
HASH=$(find packages/yoga-layout -type f \( -name "*.cpp" -o -name "*.h" -o -name "*.mjs" -o -name "CMakeLists.txt" -o -name "package.json" \) | sort | xargs sha256sum | sha256sum | cut -d' ' -f1)
FULL_HASH="${HASH}-${YOGA_VERSION}"
echo "hash=$FULL_HASH" >> $GITHUB_OUTPUT
echo "Yoga Layout version: v$YOGA_VERSION"
- name: Restore yoga output cache
id: yoga-cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: packages/yoga-layout/build/wasm
key: yoga-wasm-${{ steps.yoga-cache-key.outputs.hash }}
restore-keys: yoga-wasm-
enableCrossOsArchive: true
- name: Restore yoga build cache
id: yoga-build-cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: packages/yoga-layout/build
key: yoga-build-${{ steps.yoga-cache-key.outputs.hash }}
restore-keys: |
yoga-build-
- name: Verify cached artifacts
id: yoga-cache-valid
run: |
if [ -f "packages/yoga-layout/build/wasm/yoga.wasm" ] && [ -f "packages/yoga-layout/build/wasm/yoga.js" ]; then
echo "valid=true" >> $GITHUB_OUTPUT
echo "Cache hit: artifacts found"
ls -lh packages/yoga-layout/build/wasm/
else
echo "valid=false" >> $GITHUB_OUTPUT
echo "Cache miss or incomplete: forcing rebuild"
ls -lh packages/yoga-layout/build/wasm/ 2>/dev/null || echo "Directory does not exist"
fi
- name: Install Emscripten
if: steps.yoga-cache-valid.outputs.valid != 'true' || inputs.force
run: |
echo "::group::Installing Emscripten"
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
echo "::endgroup::"
- name: Build Yoga Layout WASM
if: steps.yoga-cache-valid.outputs.valid != 'true' || inputs.force
run: |
echo "::group::Building Yoga Layout WASM"
source emsdk/emsdk_env.sh
if [ "${{ inputs.force }}" = "true" ]; then
pnpm --filter @socketsecurity/yoga-layout run build -- --force
else
pnpm --filter @socketsecurity/yoga-layout run build
fi
echo "Build exit code: $?"
echo "Checking for build artifacts..."
ls -lh packages/yoga-layout/build/wasm/ || echo "wasm directory not found"
ls -lh packages/yoga-layout/build/cmake/ || echo "cmake directory not found"
echo "::endgroup::"
- name: Verify build artifacts
run: |
echo "=== Yoga Layout Build Artifacts ==="
if [ ! -f "packages/yoga-layout/build/wasm/yoga.wasm" ] || [ ! -f "packages/yoga-layout/build/wasm/yoga.js" ]; then
echo "ERROR: Required WASM artifacts not found!"
ls -lh packages/yoga-layout/build/wasm/ || echo "Directory does not exist"
exit 1
fi
ls -lh packages/yoga-layout/build/wasm/
echo ""
echo "yoga.wasm size: $(du -h packages/yoga-layout/build/wasm/yoga.wasm | cut -f1)"
echo "yoga.js size: $(du -h packages/yoga-layout/build/wasm/yoga.js | cut -f1)"
- name: Upload yoga artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: yoga-wasm
path: packages/yoga-layout/build/wasm/
retention-days: 7
build-models:
name: πŸ€– Build AI Models
if: ${{ inputs.build-models != false }}
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Setup Node.js
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with:
node-version: 22
- name: Setup pnpm
uses: pnpm/action-setup@9fd676a19091d4595eefd76e4bd31c97133911f1 # v4.2.0
with:
version: ^10.16.0
- name: Setup Python
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
with:
python-version: '3.11'
- name: Cache pip packages
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: ~/.cache/pip
key: pip-${{ runner.os }}-py3.11-${{ hashFiles('**/requirements*.txt') }}-onnx-torch
restore-keys: |
pip-${{ runner.os }}-py3.11-
pip-${{ runner.os }}-
- name: Install Python dependencies
run: |
echo "::group::Installing Python ML dependencies"
pip install torch transformers
pip install "onnx>=1.15.0" "onnxruntime>=1.20.0"
pip install "optimum[onnxruntime]"
echo "Installed packages:"
pip list | grep -E "(onnx|optimum|torch)"
echo ""
python3 -c "import onnxruntime; print(f'ONNX Runtime version: {onnxruntime.__version__}')"
python3 -c "from onnxruntime.quantization.matmul_nbits_quantizer import MatMulNBitsQuantizer, RTNWeightOnlyQuantConfig; print('βœ“ INT4 quantization available')"
echo "::endgroup::"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Generate models cache key
id: models-cache-key
run: |
# Extract models version from package.json.
MODELS_VERSION=$(node -p "require('./packages/models/package.json').version")
# Hash includes script files and package.json.
HASH=$(find packages/models -type f \( -name "*.mjs" -o -name "package.json" \) | sort | xargs sha256sum | sha256sum | cut -d' ' -f1)
# Include quantization level in cache key.
QUANT_LEVEL="${{ inputs.models-quant-level || 'INT4' }}"
FULL_HASH="${HASH}-${MODELS_VERSION}-${QUANT_LEVEL}"
echo "hash=$FULL_HASH" >> $GITHUB_OUTPUT
echo "quant-level=${QUANT_LEVEL}" >> $GITHUB_OUTPUT
echo "suffix=$(echo ${QUANT_LEVEL} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT
echo "Models version: v$MODELS_VERSION"
echo "Quantization level: $QUANT_LEVEL"
- name: Restore models cache
id: models-cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: packages/models/dist
key: models-${{ steps.models-cache-key.outputs.hash }}
restore-keys: models-
enableCrossOsArchive: true
- name: Verify cached artifacts
id: models-cache-valid
run: |
SUFFIX="${{ steps.models-cache-key.outputs.suffix }}"
# Check for both MiniLM and CodeT5 models.
if [ -f "packages/models/dist/minilm-l6-${SUFFIX}.onnx" ] && \
[ -f "packages/models/dist/codet5-encoder-${SUFFIX}.onnx" ] && \
[ -f "packages/models/dist/codet5-decoder-${SUFFIX}.onnx" ]; then
echo "valid=true" >> $GITHUB_OUTPUT
echo "Cache hit: all artifacts found"
ls -lh packages/models/dist/
else
echo "valid=false" >> $GITHUB_OUTPUT
echo "Cache miss or incomplete: forcing rebuild"
echo "Expected files:"
echo " - minilm-l6-${SUFFIX}.onnx"
echo " - codet5-encoder-${SUFFIX}.onnx"
echo " - codet5-decoder-${SUFFIX}.onnx"
ls -lh packages/models/dist/ 2>/dev/null || echo "Directory does not exist"
fi
- name: Check workflow status before build
if: steps.models-cache-valid.outputs.valid != 'true' || inputs.force
run: |
# Fail fast: check if workflow already failed before starting long build.
STATUS=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }} --jq '.status' 2>/dev/null || echo "in_progress")
if [ "$STATUS" = "cancelled" ]; then
echo "Workflow was cancelled - exiting early"
exit 1
fi
env:
GH_TOKEN: ${{ github.token }}
- name: Build AI models
if: steps.models-cache-valid.outputs.valid != 'true' || inputs.force
run: |
QUANT_LEVEL="${{ steps.models-cache-key.outputs.quant-level }}"
echo "::group::Building ${QUANT_LEVEL}-quantized AI models"
# Build command with quantization flag and --all for both models.
BUILD_CMD="pnpm --filter @socketsecurity/models run build -- --all"
if [ "$QUANT_LEVEL" = "INT8" ]; then
BUILD_CMD="$BUILD_CMD --int8"
fi
if [ "${{ inputs.force }}" = "true" ]; then
BUILD_CMD="$BUILD_CMD --force"
fi
echo "Running: $BUILD_CMD"
eval $BUILD_CMD
echo "Build exit code: $?"
echo "Checking for build artifacts..."
ls -lh packages/models/dist/ || echo "dist directory not found"
echo "::endgroup::"
- name: Verify build artifacts
run: |
SUFFIX="${{ steps.models-cache-key.outputs.suffix }}"
echo "=== AI Models Build Artifacts ==="
if [ ! -f "packages/models/dist/minilm-l6-${SUFFIX}.onnx" ]; then
echo "ERROR: minilm-l6-${SUFFIX}.onnx not found!"
ls -lh packages/models/dist/ || echo "Directory does not exist"
exit 1
fi
if [ ! -f "packages/models/dist/codet5-encoder-${SUFFIX}.onnx" ]; then
echo "ERROR: codet5-encoder-${SUFFIX}.onnx not found!"
ls -lh packages/models/dist/ || echo "Directory does not exist"
exit 1
fi
if [ ! -f "packages/models/dist/codet5-decoder-${SUFFIX}.onnx" ]; then
echo "ERROR: codet5-decoder-${SUFFIX}.onnx not found!"
ls -lh packages/models/dist/ || echo "Directory does not exist"
exit 1
fi
ls -lh packages/models/dist/
echo ""
echo "minilm-l6-${SUFFIX}.onnx size: $(du -h packages/models/dist/minilm-l6-${SUFFIX}.onnx | cut -f1)"
echo "codet5-encoder-${SUFFIX}.onnx size: $(du -h packages/models/dist/codet5-encoder-${SUFFIX}.onnx | cut -f1)"
echo "codet5-decoder-${SUFFIX}.onnx size: $(du -h packages/models/dist/codet5-decoder-${SUFFIX}.onnx | cut -f1)"
- name: Upload models artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: ai-models
path: packages/models/dist/
retention-days: 7
build-onnx-runtime:
name: 🌐 Build ONNX Runtime WASM
if: ${{ inputs.build-onnx != false }}
runs-on: ubuntu-latest
timeout-minutes: 90
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Setup Node.js
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with:
node-version: 22
- name: Setup pnpm
uses: pnpm/action-setup@9fd676a19091d4595eefd76e4bd31c97133911f1 # v4.2.0
with:
version: ^10.16.0
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Generate ONNX Runtime cache key
id: onnx-cache-key
run: |
# Extract ONNX Runtime version from package.json (package version matches ONNX Runtime release).
ONNX_VERSION=$(node -p "require('./packages/onnxruntime/package.json').version")
# Hash includes script files and package.json.
HASH=$(find packages/onnxruntime -type f \( -name "*.mjs" -o -name "package.json" \) | sort | xargs sha256sum | sha256sum | cut -d' ' -f1)
FULL_HASH="${HASH}-${ONNX_VERSION}"
echo "hash=$FULL_HASH" >> $GITHUB_OUTPUT
echo "ONNX Runtime version: v$ONNX_VERSION"
- name: Restore ONNX Runtime output cache
id: onnx-cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: packages/onnxruntime/build/wasm
key: onnx-runtime-${{ steps.onnx-cache-key.outputs.hash }}
restore-keys: onnx-runtime-
enableCrossOsArchive: true
- name: Restore ONNX Runtime build cache
id: onnx-build-cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: packages/onnxruntime/build
key: onnx-runtime-build-${{ steps.onnx-cache-key.outputs.hash }}
restore-keys: |
onnx-runtime-build-
- name: Verify cached artifacts
id: onnx-cache-valid
run: |
if [ -f "packages/onnxruntime/build/wasm/ort-wasm-simd-threaded.wasm" ] && [ -f "packages/onnxruntime/build/wasm/ort-wasm-simd-threaded.js" ]; then
echo "valid=true" >> $GITHUB_OUTPUT
echo "Cache hit: artifacts found"
ls -lh packages/onnxruntime/build/wasm/
else
echo "valid=false" >> $GITHUB_OUTPUT
echo "Cache miss or incomplete: forcing rebuild"
ls -lh packages/onnxruntime/build/wasm/ 2>/dev/null || echo "Directory does not exist"
fi
- name: Cache Emscripten SDK
id: emsdk-cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: emsdk
key: emsdk-${{ runner.os }}-3.1.69
restore-keys: emsdk-${{ runner.os }}-
- name: Install Emscripten
if: (steps.onnx-cache-valid.outputs.valid != 'true' || inputs.force) && steps.emsdk-cache.outputs.cache-hit != 'true'
run: |
echo "::group::Installing Emscripten"
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
echo "::endgroup::"
- name: Activate Emscripten (from cache)
if: (steps.onnx-cache-valid.outputs.valid != 'true' || inputs.force) && steps.emsdk-cache.outputs.cache-hit == 'true'
run: |
cd emsdk
./emsdk activate latest
- name: Check workflow status before build
if: steps.onnx-cache-valid.outputs.valid != 'true' || inputs.force
run: |
# Fail fast: check if workflow already failed before starting long build.
STATUS=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }} --jq '.status' 2>/dev/null || echo "in_progress")
if [ "$STATUS" = "cancelled" ]; then
echo "Workflow was cancelled - exiting early"
exit 1
fi
env:
GH_TOKEN: ${{ github.token }}
- name: Build ONNX Runtime WASM
if: steps.onnx-cache-valid.outputs.valid != 'true' || inputs.force
run: |
echo "::group::Building ONNX Runtime WASM (this will take 30-60 minutes)"
source emsdk/emsdk_env.sh
if [ "${{ inputs.force }}" = "true" ]; then
pnpm --filter @socketsecurity/onnxruntime run build -- --force
else
pnpm --filter @socketsecurity/onnxruntime run build
fi
echo "Build exit code: $?"
echo "Checking for build artifacts..."
ls -lh packages/onnxruntime/build/wasm/ || echo "wasm directory not found"
echo "::endgroup::"
- name: Save ONNX Runtime build cache
if: always() && (steps.onnx-cache-valid.outputs.valid != 'true' || inputs.force)
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: packages/onnxruntime/build
key: onnx-runtime-build-${{ steps.onnx-cache-key.outputs.hash }}
- name: Verify build artifacts
run: |
echo "=== ONNX Runtime Build Artifacts ==="
if [ ! -f "packages/onnxruntime/build/wasm/ort-wasm-simd-threaded.wasm" ] || [ ! -f "packages/onnxruntime/build/wasm/ort-wasm-simd-threaded.js" ]; then
echo "ERROR: Required ONNX Runtime WASM artifacts not found!"
ls -lh packages/onnxruntime/build/wasm/ || echo "Directory does not exist"
exit 1
fi
ls -lh packages/onnxruntime/build/wasm/
echo ""
echo "ort-wasm-simd-threaded.wasm size: $(du -h packages/onnxruntime/build/wasm/ort-wasm-simd-threaded.wasm | cut -f1)"
echo "ort-wasm-simd-threaded.js size: $(du -h packages/onnxruntime/build/wasm/ort-wasm-simd-threaded.js | cut -f1)"
- name: Upload ONNX Runtime artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: onnx-runtime
path: packages/onnxruntime/build/wasm/
retention-days: 7
summary:
name: πŸ“Š 🧱 WASM Build Summary
if: always()
needs: [build-yoga-layout, build-models, build-onnx-runtime]
runs-on: ubuntu-latest
steps:
- name: Download all artifacts
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
continue-on-error: true
with:
path: artifacts
- name: Generate summary
run: |
echo "# 🧱 WASM Build Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "## βœ… Build Complete" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Selected WASM assets built successfully and cached." >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### πŸ“¦ Artifacts" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Asset | Files |" >> $GITHUB_STEP_SUMMARY
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
if [ -d "artifacts/yoga-wasm" ]; then
echo "| 🧘 Yoga Layout | \`yoga.wasm\`, \`yoga.js\` |" >> $GITHUB_STEP_SUMMARY
fi
if [ -d "artifacts/ai-models" ]; then
# Detect quantization level from file names.
if [ -f "artifacts/ai-models/minilm-l6-int8.onnx" ]; then
echo "| πŸ€– AI Models | \`minilm-l6-int8.onnx\` (INT8), \`codet5-encoder-int8.onnx\` (INT8), \`codet5-decoder-int8.onnx\` (INT8) |" >> $GITHUB_STEP_SUMMARY
elif [ -f "artifacts/ai-models/minilm-l6-int4.onnx" ]; then
echo "| πŸ€– AI Models | \`minilm-l6-int4.onnx\` (INT4), \`codet5-encoder-int4.onnx\` (INT4), \`codet5-decoder-int4.onnx\` (INT4) |" >> $GITHUB_STEP_SUMMARY
else
echo "| πŸ€– AI Models | $(ls artifacts/ai-models/*.onnx 2>/dev/null | xargs -n1 basename | sed 's/^/`/;s/$/`/' | tr '\n' ',' | sed 's/,$//' || echo "No ONNX files found") |" >> $GITHUB_STEP_SUMMARY
fi
fi
if [ -d "artifacts/onnx-runtime" ]; then
echo "| 🌐 ONNX Runtime | \`ort-wasm-simd-threaded.wasm\`, \`ort-wasm-simd-threaded.mjs\` |" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 🎯 Next Steps" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- These artifacts are now cached for CI builds" >> $GITHUB_STEP_SUMMARY
echo "- CLI builds will use these cached WASM assets" >> $GITHUB_STEP_SUMMARY
echo "- Cache is invalidated when source files change" >> $GITHUB_STEP_SUMMARY