WASM 3. Yoga Layout #249
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: WASM 3. Yoga Layout | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| dry_run: | |
| description: 'Dry run (build only, no release)' | |
| type: boolean | |
| default: true | |
| force: | |
| description: 'Force rebuild (ignore cache)' | |
| type: boolean | |
| default: false | |
| build_mode: | |
| description: 'Build mode' | |
| type: choice | |
| options: | |
| - prod | |
| - dev | |
| default: prod | |
| debug: | |
| description: 'Debug (0|1|namespace[,...])' | |
| type: string | |
| default: '0' | |
| workflow_call: | |
| inputs: | |
| dry_run: | |
| type: boolean | |
| default: true | |
| force: | |
| type: boolean | |
| default: false | |
| build_mode: | |
| type: string | |
| default: prod | |
| debug: | |
| type: string | |
| default: '0' | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| jobs: | |
| build: | |
| name: Build Yoga Layout WASM | |
| permissions: | |
| contents: read | |
| id-token: write # OIDC authentication for Depot builds | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 90 | |
| env: | |
| # Yoga Layout builds WASM on Linux only; derive platform-arch from the | |
| # Node.js platform naming that the source code uses for layout paths. | |
| PLATFORM_ARCH: linux-x64 | |
| steps: | |
| - uses: SocketDev/socket-registry/.github/actions/setup-and-install@ea1986b8019fedee5fb38b485690b13ad8e0217f # main | |
| - name: Enable debug logging | |
| uses: ./.github/actions/enable-debug-logging | |
| with: | |
| debug: ${{ inputs.debug }} | |
| - name: Load tool versions | |
| id: tool-versions | |
| run: | | |
| NODE_VERSION=$(cat .node-version | tr -d '\n') | |
| YOGA_VERSION=$(jq -r '.sources.yoga.version' packages/yoga-layout-builder/package.json) | |
| if [ -z "$YOGA_VERSION" ] || [ "$YOGA_VERSION" = "null" ]; then | |
| echo "error: packages/yoga-layout-builder/package.json missing .sources.yoga.version" >&2 | |
| exit 1 | |
| fi | |
| echo "node-version=$NODE_VERSION" >> $GITHUB_OUTPUT | |
| echo "yoga-version=$YOGA_VERSION" >> $GITHUB_OUTPUT | |
| echo "Loaded Node.js: $NODE_VERSION, Yoga Layout: $YOGA_VERSION" | |
| - name: Set build mode | |
| id: build-mode | |
| uses: ./.github/actions/set-build-mode | |
| with: | |
| build-mode: ${{ inputs.build_mode }} | |
| - name: Load cache version from centralized config | |
| id: cache-version | |
| uses: ./.github/actions/load-cache-version | |
| with: | |
| package-name: yoga-layout | |
| - name: Generate Yoga cache key | |
| id: cache-key | |
| env: | |
| CACHE_VERSION: ${{ steps.cache-version.outputs.version }} | |
| run: | | |
| # Cross-platform hash function. | |
| if command -v shasum &> /dev/null; then | |
| hash_cmd="shasum -a 256" | |
| elif command -v sha256sum &> /dev/null; then | |
| hash_cmd="sha256sum" | |
| else | |
| echo "Error: No SHA-256 command found" | |
| exit 1 | |
| fi | |
| # Per-checkpoint cumulative hashing (like node-smol) | |
| # Each checkpoint includes its dependencies (cumulative) | |
| # Helper to hash directory contents. | |
| # Hashes ALL files, not just *.mts — external-tools.json and other | |
| # non-script configs affect the build too. | |
| hash_dir() { | |
| local dir=$1 | |
| if [ -d "$dir" ]; then | |
| # LC_COLLATE=C: keep ordering locale-independent so cache | |
| # keys don't diverge across runner locales. | |
| find "$dir" -type f 2>/dev/null | LC_COLLATE=C sort | xargs $hash_cmd 2>/dev/null | $hash_cmd | cut -d' ' -f1 || echo "" | |
| else | |
| echo "" | |
| fi | |
| } | |
| # Common scripts (used by all checkpoints) | |
| COMMON=$(hash_dir packages/yoga-layout-builder/scripts/common) | |
| PACKAGE_JSON=$($hash_cmd packages/yoga-layout-builder/package.json | cut -d' ' -f1) | |
| BUILD_MJS=$($hash_cmd packages/yoga-layout-builder/scripts/build.mts | cut -d' ' -f1) | |
| # Shared TS helpers used by all phases + pnpm-lock + .node-version. | |
| BUILD_INFRA_LIB=$(hash_dir packages/build-infra/lib) | |
| BIN_INFRA_LIB=$(hash_dir packages/bin-infra/lib) | |
| PNPM_LOCK_HASH=$($hash_cmd pnpm-lock.yaml 2>/dev/null | cut -d' ' -f1) | |
| NODE_VERSION_HASH=$($hash_cmd .node-version 2>/dev/null | cut -d' ' -f1) | |
| # external-tools.json pins cmake / emscripten / python3 versions | |
| # consumed by pinned-versions.mts — bumps there produce different | |
| # WASM output. package-root metadata (package.json) pins sources | |
| # and devDependencies. Top-level scripts/paths.mts is re-exported | |
| # by every per-phase shared/paths.mts, and scripts/lib/ | |
| # (optimization-flags, etc.) is imported cross-phase. None were | |
| # previously hashed. | |
| EXTERNAL_TOOLS_ROOT=$($hash_cmd packages/yoga-layout-builder/external-tools.json 2>/dev/null | cut -d' ' -f1) | |
| SCRIPTS_TOP=$($hash_cmd packages/yoga-layout-builder/scripts/paths.mts 2>/dev/null | cut -d' ' -f1) | |
| # get-checkpoint-chain.mts drives the cache-key plan later in | |
| # this workflow (used at "Set checkpoint chain for build mode"). | |
| # Edit the chain-producing script without this hash and the | |
| # cache key stays the same while the chain output differs — | |
| # a stale cache survives what should be an invalidation. | |
| GET_CHECKPOINT_CHAIN=$($hash_cmd packages/yoga-layout-builder/scripts/get-checkpoint-chain.mts 2>/dev/null | cut -d' ' -f1) | |
| SCRIPTS_LIB_DIR=$(if [ -d packages/yoga-layout-builder/scripts/lib ]; then hash_dir packages/yoga-layout-builder/scripts/lib; else echo ""; fi) | |
| # build-infra/external-tools.json pins the foundational | |
| # toolchain (cmake/emscripten/python via install-tools.mts); | |
| # build-infra/scripts/ holds get-checkpoint-chain + | |
| # smoke-test-binary used across all builders. Both unhashed | |
| # before R20 — bumps there silently hit stale cache. | |
| BUILD_INFRA_EXTERNAL_TOOLS=$($hash_cmd packages/build-infra/external-tools.json 2>/dev/null | cut -d' ' -f1) | |
| BUILD_INFRA_SCRIPTS=$(hash_dir packages/build-infra/scripts) | |
| # Root package.json + pnpm-workspace.yaml hold catalog versions, | |
| # overrides, trustPolicy, minimumReleaseAge. Unhashed before R24. | |
| ROOT_PACKAGE_JSON_HASH=$($hash_cmd package.json 2>/dev/null | cut -d' ' -f1) | |
| PNPM_WORKSPACE_HASH=$($hash_cmd pnpm-workspace.yaml 2>/dev/null | cut -d' ' -f1) | |
| # Socket-registry propagation SHA — extracted from THIS | |
| # workflow's own YAML at runtime, not via a catalog lookup. | |
| # A registry cascade bump (which edits the @<sha> pin in | |
| # this file) flows through to a cache-key change here, so | |
| # the checkpoint cache invalidates whenever the registry | |
| # provisions a new pnpm version / zizmor build / etc. That | |
| # makes .build-context/registry-tools.json — whose contents | |
| # are materialized per-run from SOCKET_TOOL_CHECKSUMS_FILE | |
| # — implicitly part of the cache key. | |
| WORKFLOW_FILE="${GITHUB_WORKFLOW_REF%@*}" | |
| WORKFLOW_FILE="${WORKFLOW_FILE#${GITHUB_REPOSITORY}/}" | |
| SOCKET_REGISTRY_SHA=$(grep -ohE 'SocketDev/socket-registry/[^@]+@[0-9a-f]{40}' "$WORKFLOW_FILE" 2>/dev/null | head -1 | grep -oE '[0-9a-f]{40}$' || echo "") | |
| SHARED_DEPS="${BUILD_INFRA_LIB}${BIN_INFRA_LIB}${PNPM_LOCK_HASH}${NODE_VERSION_HASH}${EXTERNAL_TOOLS_ROOT}${SCRIPTS_TOP}${GET_CHECKPOINT_CHAIN}${SCRIPTS_LIB_DIR}${BUILD_INFRA_EXTERNAL_TOOLS}${BUILD_INFRA_SCRIPTS}${ROOT_PACKAGE_JSON_HASH}${PNPM_WORKSPACE_HASH}${SOCKET_REGISTRY_SHA}" | |
| # source-cloned: cache-version + common + source-cloned + build.mjs + package.json + shared deps | |
| SOURCE_CLONED_DIR=$(hash_dir packages/yoga-layout-builder/scripts/source-cloned) | |
| SOURCE_CLONED_HASH=$(echo "${CACHE_VERSION}${COMMON}${SOURCE_CLONED_DIR}${BUILD_MJS}${PACKAGE_JSON}${SHARED_DEPS}" | $hash_cmd | cut -d' ' -f1) | |
| # source-configured: source-cloned + source-configured | |
| SOURCE_CONFIGURED_DIR=$(hash_dir packages/yoga-layout-builder/scripts/source-configured) | |
| SOURCE_CONFIGURED_HASH=$(echo "${SOURCE_CLONED_HASH}${SOURCE_CONFIGURED_DIR}" | $hash_cmd | cut -d' ' -f1) | |
| # wasm-compiled: source-configured + wasm-compiled | |
| WASM_COMPILED_DIR=$(hash_dir packages/yoga-layout-builder/scripts/wasm-compiled) | |
| WASM_COMPILED_HASH=$(echo "${SOURCE_CONFIGURED_HASH}${WASM_COMPILED_DIR}" | $hash_cmd | cut -d' ' -f1) | |
| # wasm-released: wasm-compiled + wasm-released | |
| WASM_RELEASE_DIR=$(hash_dir packages/yoga-layout-builder/scripts/wasm-released) | |
| WASM_RELEASE_HASH=$(echo "${WASM_COMPILED_HASH}${WASM_RELEASE_DIR}" | $hash_cmd | cut -d' ' -f1) | |
| # wasm-optimized: wasm-released + wasm-optimized | |
| WASM_OPTIMIZED_DIR=$(hash_dir packages/yoga-layout-builder/scripts/wasm-optimized) | |
| WASM_OPTIMIZED_HASH=$(echo "${WASM_RELEASE_HASH}${WASM_OPTIMIZED_DIR}" | $hash_cmd | cut -d' ' -f1) | |
| # wasm-synced: wasm-optimized + wasm-synced + shared wasm-sync | |
| # helpers + src/wrapper/. generate-sync.mts reads src/wrapper/ | |
| # YGEnums.mts + wrapAssembly.mts and delegates to shared helpers | |
| # in build-infra/wasm-synced/ (wasm-sync-wrapper, transform, | |
| # generate-sync-cjs/esm). Hash the whole shared directory so | |
| # helper edits invalidate this checkpoint too. | |
| WASM_SYNC_DIR=$(hash_dir packages/yoga-layout-builder/scripts/wasm-synced) | |
| WASM_SYNC_SHARED=$(hash_dir packages/build-infra/wasm-synced) | |
| SRC_WRAPPER=$(hash_dir packages/yoga-layout-builder/src/wrapper) | |
| WASM_SYNC_HASH=$(echo "${WASM_OPTIMIZED_HASH}${WASM_SYNC_DIR}${WASM_SYNC_SHARED}${SRC_WRAPPER}" | $hash_cmd | cut -d' ' -f1) | |
| # finalized: wasm-synced + finalized (most complete hash) | |
| # Also includes Dockerfile (build recipe) and yoga upstream submodule SHA. | |
| FINAL_DIR=$(hash_dir packages/yoga-layout-builder/scripts/finalized) | |
| DOCKERFILE_HASH=$(if [ -f packages/yoga-layout-builder/docker/Dockerfile.linux ]; then $hash_cmd packages/yoga-layout-builder/docker/Dockerfile.linux | cut -d' ' -f1; else echo ""; fi) | |
| YOGA_SHA=$(git ls-tree HEAD packages/yoga-layout-builder/upstream/yoga 2>/dev/null | awk '{print $3}' || echo "none") | |
| FINAL_HASH=$(echo "${WASM_SYNC_HASH}${FINAL_DIR}${DOCKERFILE_HASH}${YOGA_SHA}" | $hash_cmd | cut -d' ' -f1) | |
| echo "cache_version=${CACHE_VERSION}" >> $GITHUB_OUTPUT | |
| echo "source_cloned_hash=${SOURCE_CLONED_HASH}" >> $GITHUB_OUTPUT | |
| echo "final_hash=${FINAL_HASH}" >> $GITHUB_OUTPUT | |
| - name: Restore Yoga checkpoint cache | |
| uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 | |
| id: yoga-checkpoint-cache | |
| if: ${{ !inputs.force }} | |
| with: | |
| path: | | |
| packages/yoga-layout-builder/build/shared/checkpoints | |
| packages/yoga-layout-builder/build/${{ steps.build-mode.outputs.mode }}/${{ env.PLATFORM_ARCH }}/checkpoints | |
| key: yoga-checkpoints-${{ steps.cache-key.outputs.cache_version }}-v${{ steps.tool-versions.outputs.yoga-version }}-${{ runner.os }}-${{ steps.build-mode.outputs.mode }}-${{ steps.cache-key.outputs.final_hash }} | |
| # NOTE: No restore-keys - we require exact match to prevent stale cache restoration. | |
| # Partial matches with restore-keys cause builds to use outdated artifacts when | |
| # source changes bump the final_hash but the prefix still matches. | |
| - name: Set checkpoint chain for build mode | |
| id: checkpoint-chain | |
| env: | |
| BUILD_MODE: ${{ steps.build-mode.outputs.mode }} | |
| run: | | |
| # Use get-checkpoint-chain.mts to ensure consistency with local builds | |
| # Dev mode: skip wasm-optimized (optimization is disabled) | |
| # Prod mode: include wasm-optimized | |
| CHAIN=$(node packages/yoga-layout-builder/scripts/get-checkpoint-chain.mts --$BUILD_MODE) | |
| echo "checkpoint_chain=$CHAIN" >> $GITHUB_OUTPUT | |
| # Convert comma-separated to space-separated for validation | |
| CHECKPOINTS=$(echo "$CHAIN" | tr ',' ' ') | |
| echo "checkpoints=$CHECKPOINTS" >> $GITHUB_OUTPUT | |
| echo "Checkpoint chain for $BUILD_MODE mode: $CHAIN" | |
| - name: Validate checkpoint cache integrity | |
| id: validate-cache | |
| if: steps.yoga-checkpoint-cache.outputs.cache-hit == 'true' | |
| uses: ./.github/actions/validate-checkpoints | |
| with: | |
| checkpoint-dirs: packages/yoga-layout-builder/build/shared/checkpoints:packages/yoga-layout-builder/build/${{ steps.build-mode.outputs.mode }}/${{ env.PLATFORM_ARCH }}/checkpoints | |
| checkpoints: ${{ steps.checkpoint-chain.outputs.checkpoints }} | |
| package-name: yoga-layout-builder | |
| - name: Restore build output from checkpoint chain | |
| id: restore-checkpoint | |
| uses: ./.github/actions/restore-checkpoint | |
| with: | |
| package-name: 'yoga-layout-builder' | |
| build-mode: ${{ steps.build-mode.outputs.mode }} | |
| checkpoint-chain: ${{ steps.checkpoint-chain.outputs.checkpoint_chain }} | |
| cache-hit: ${{ steps.yoga-checkpoint-cache.outputs.cache-hit }} | |
| cache-valid: ${{ steps.validate-cache.outputs.valid }} | |
| platform-arch: ${{ env.PLATFORM_ARCH }} | |
| - name: Setup Depot CLI | |
| if: (steps.yoga-checkpoint-cache.outputs.cache-hit != 'true' || steps.validate-cache.outputs.valid == 'false') || steps.restore-checkpoint.outputs.build-required == 'true' | |
| uses: depot/setup-action@15c09a5f77a0840ad4bce955686522a257853461 # v1.7.1 | |
| with: | |
| oidc: true | |
| - name: Initialize Yoga submodule | |
| # Cache-miss only: the cache-key uses `git ls-tree HEAD` so the | |
| # submodule does not need to be initialized to compute it. | |
| if: (steps.yoga-checkpoint-cache.outputs.cache-hit != 'true' || steps.validate-cache.outputs.valid == 'false') || steps.restore-checkpoint.outputs.build-required == 'true' | |
| uses: ./.github/actions/init-submodules | |
| with: | |
| submodules: packages/yoga-layout-builder/upstream/yoga | |
| - name: Stage registry tool-checksums for Docker build context | |
| if: | | |
| (steps.yoga-checkpoint-cache.outputs.cache-hit != 'true' || steps.validate-cache.outputs.valid == 'false') || | |
| steps.restore-checkpoint.outputs.build-required == 'true' | |
| shell: bash | |
| run: | | |
| # Socket-registry's setup action exports SOCKET_TOOL_CHECKSUMS_FILE | |
| # pointing at a stable on-runner copy of external-tools.json. | |
| # Dockerfiles `COPY .build-context/registry-tools.json …` to | |
| # resolve pnpm version + per-platform asset + sha256 at build | |
| # time without duplicating per-Dockerfile constants. | |
| mkdir -p .build-context | |
| cp "$SOCKET_TOOL_CHECKSUMS_FILE" .build-context/registry-tools.json | |
| - name: Build Yoga Layout WASM with Depot (offloads compute) | |
| id: build | |
| if: | | |
| (steps.yoga-checkpoint-cache.outputs.cache-hit != 'true' || steps.validate-cache.outputs.valid == 'false') || | |
| steps.restore-checkpoint.outputs.build-required == 'true' | |
| uses: depot/build-push-action@5f3b3c2e5a00f0093de47f657aeaefcedff27d18 # v1.17.0 | |
| with: | |
| project: 8fpj9495vw | |
| platforms: linux/amd64 | |
| build-args: | | |
| BUILD_MODE=${{ steps.build-mode.outputs.mode }} | |
| PLATFORM_ARCH=${{ env.PLATFORM_ARCH }} | |
| CACHE_VERSION=${{ steps.cache-version.outputs.version }} | |
| no-cache: ${{ inputs.force == true }} | |
| file: packages/yoga-layout-builder/docker/Dockerfile.linux | |
| target: export | |
| outputs: type=local,dest=packages/yoga-layout-builder/build | |
| context: . | |
| - name: Verify Depot build output | |
| if: | | |
| (steps.yoga-checkpoint-cache.outputs.cache-hit != 'true' || steps.validate-cache.outputs.valid == 'false') || | |
| steps.restore-checkpoint.outputs.build-required == 'true' | |
| env: | |
| BUILD_MODE: ${{ steps.build-mode.outputs.mode }} | |
| run: | | |
| echo "Verifying Depot build output structure..." | |
| ls -lah packages/yoga-layout-builder/build/ | |
| # Check if build artifacts exist. | |
| if [ ! -d "packages/yoga-layout-builder/build/$BUILD_MODE" ]; then | |
| echo "× Build directory not found!" | |
| echo "Contents of output directory:" | |
| find packages/yoga-layout-builder/build/ -type f | |
| exit 1 | |
| fi | |
| echo "✅ Build artifacts found" | |
| ls -lh packages/yoga-layout-builder/build/$BUILD_MODE/ | |
| - name: Validate Depot checkpoints | |
| if: | | |
| (steps.yoga-checkpoint-cache.outputs.cache-hit != 'true' || steps.validate-cache.outputs.valid == 'false') || | |
| steps.restore-checkpoint.outputs.build-required == 'true' | |
| uses: ./.github/actions/validate-depot-checkpoints | |
| with: | |
| package-path: packages/yoga-layout-builder | |
| build-mode: ${{ steps.build-mode.outputs.mode }} | |
| package-name: yoga-layout-builder | |
| platform-arch: ${{ env.PLATFORM_ARCH }} | |
| # Debug output only needed on cache-miss / invalid / build-required | |
| # runs. Full cache-hit rounds have a known-good layout from the | |
| # restored checkpoint tarball — skip the full tree walk to save I/O. | |
| - name: Debug build output structure | |
| if: (steps.yoga-checkpoint-cache.outputs.cache-hit != 'true' || steps.validate-cache.outputs.valid == 'false') || steps.restore-checkpoint.outputs.build-required == 'true' | |
| env: | |
| BUILD_MODE: ${{ steps.build-mode.outputs.mode }} | |
| run: | | |
| echo "=== Debugging build output structure ===" | |
| echo "Looking for files in: packages/yoga-layout-builder/build/${BUILD_MODE}/" | |
| if [ -d "packages/yoga-layout-builder/build/${BUILD_MODE}" ]; then | |
| echo "Build directory exists, showing full structure:" | |
| find packages/yoga-layout-builder/build/${BUILD_MODE} -type f -o -type d | sort | |
| else | |
| echo "× Build directory not found!" | |
| echo "Available directories in packages/yoga-layout-builder/build/:" | |
| ls -la packages/yoga-layout-builder/build/ 2>/dev/null || echo "build/ directory doesn't exist" | |
| fi | |
| echo "=== End debug output ===" | |
| # Skip on full cache-hit — files came from the restored checkpoint | |
| # tarball and were already integrity-checked by validate-cache. | |
| - name: Validate build output | |
| if: (steps.yoga-checkpoint-cache.outputs.cache-hit != 'true' || steps.validate-cache.outputs.valid == 'false') || steps.restore-checkpoint.outputs.build-required == 'true' | |
| env: | |
| BUILD_MODE: ${{ steps.build-mode.outputs.mode }} | |
| run: | | |
| echo "Validating Yoga Layout build output..." | |
| if [ ! -f "packages/yoga-layout-builder/build/${BUILD_MODE}/${PLATFORM_ARCH}/wasm/out/Final/yoga.wasm" ]; then | |
| echo "× Build failed: WASM file missing" | |
| echo "Expected path: packages/yoga-layout-builder/build/${BUILD_MODE}/${PLATFORM_ARCH}/wasm/out/Final/yoga.wasm" | |
| exit 1 | |
| fi | |
| if [ ! -f "packages/yoga-layout-builder/build/${BUILD_MODE}/${PLATFORM_ARCH}/wasm/out/Final/yoga.mjs" ]; then | |
| echo "× Build failed: ES module file missing" | |
| exit 1 | |
| fi | |
| if [ ! -f "packages/yoga-layout-builder/build/${BUILD_MODE}/${PLATFORM_ARCH}/wasm/out/Final/yoga-sync.cjs" ]; then | |
| echo "× Build failed: Sync CJS file missing" | |
| exit 1 | |
| fi | |
| if [ ! -f "packages/yoga-layout-builder/build/${BUILD_MODE}/${PLATFORM_ARCH}/wasm/out/Final/yoga-sync.mjs" ]; then | |
| echo "× Build failed: Sync ESM file missing" | |
| exit 1 | |
| fi | |
| WASM_SIZE=$(stat -c%s "packages/yoga-layout-builder/build/${BUILD_MODE}/${PLATFORM_ARCH}/wasm/out/Final/yoga.wasm" 2>/dev/null || stat -f%z "packages/yoga-layout-builder/build/${BUILD_MODE}/${PLATFORM_ARCH}/wasm/out/Final/yoga.wasm") | |
| if [ "$WASM_SIZE" -lt 100000 ]; then | |
| echo "× Build failed: WASM file too small ($WASM_SIZE bytes)" | |
| exit 1 | |
| fi | |
| echo "✅ Build validation passed" | |
| ls -lh "packages/yoga-layout-builder/build/${BUILD_MODE}/${PLATFORM_ARCH}/wasm/out/Final/" | |
| - name: Upload Yoga Layout artifacts | |
| uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 | |
| with: | |
| name: yoga-layout-wasm | |
| path: packages/yoga-layout-builder/build/${{ steps.build-mode.outputs.mode }}/${{ env.PLATFORM_ARCH }}/wasm/out/Final/ | |
| retention-days: 30 | |
| compression-level: 0 # Already-compressed binary artifact | |
| if-no-files-found: error | |
| - name: Cleanup before cache save | |
| if: always() | |
| env: | |
| BUILD_MODE: ${{ steps.build-mode.outputs.mode }} | |
| run: | | |
| echo "Cleaning up temporary files before cache save..." | |
| # Remove temporary build files that shouldn't be cached | |
| TEMP_DIRS=( | |
| "packages/yoga-layout-builder/build/${BUILD_MODE}/${PLATFORM_ARCH}/temp" | |
| "packages/yoga-layout-builder/build/${BUILD_MODE}/${PLATFORM_ARCH}/wasm/out" | |
| ) | |
| for DIR in "${TEMP_DIRS[@]}"; do | |
| if [ -d "$DIR" ]; then | |
| echo "Removing: $DIR" | |
| rm -rf "$DIR" | |
| fi | |
| done | |
| echo "✅ Cleanup complete" | |
| release: | |
| name: Release Yoga Layout | |
| needs: build | |
| if: github.event_name == 'workflow_dispatch' && !inputs.dry_run | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 10 | |
| environment: release # Dedicated environment for secret access with approval gates | |
| permissions: | |
| contents: write # Required to create GitHub releases | |
| env: | |
| # Release job downloads the Linux x64 build artifact into this layout | |
| # to match the build job's output path. | |
| PLATFORM_ARCH: linux-x64 | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| sparse-checkout: | | |
| packages/yoga-layout-builder/package.json | |
| .github/scripts/generate-version.sh | |
| sparse-checkout-cone-mode: false | |
| - name: Load Yoga version from package.json | |
| id: tool-versions | |
| run: | | |
| YOGA_VERSION=$(jq -r '.sources.yoga.version' packages/yoga-layout-builder/package.json) | |
| if [ -z "$YOGA_VERSION" ] || [ "$YOGA_VERSION" = "null" ]; then | |
| echo "error: packages/yoga-layout-builder/package.json missing .sources.yoga.version" >&2 | |
| exit 1 | |
| fi | |
| echo "yoga-version=$YOGA_VERSION" >> $GITHUB_OUTPUT | |
| echo "Loaded Yoga Layout: $YOGA_VERSION" | |
| - name: Download Yoga Layout artifacts | |
| uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 | |
| with: | |
| name: yoga-layout-wasm | |
| path: packages/yoga-layout-builder/build/prod/${{ env.PLATFORM_ARCH }}/wasm/out/Final/ | |
| - name: Generate version | |
| id: version | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| source .github/scripts/generate-version.sh | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "Version: $VERSION" | |
| TAG="yoga-layout-${VERSION}" | |
| if gh release view "$TAG" &>/dev/null; then | |
| echo "Release $TAG already exists, skipping." | |
| echo "skip=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "skip=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Rename assets with version | |
| if: steps.version.outputs.skip != 'true' | |
| env: | |
| VERSION: ${{ steps.version.outputs.version }} | |
| run: | | |
| cd packages/yoga-layout-builder/build/prod/${PLATFORM_ARCH}/wasm/out/Final | |
| mv yoga.wasm yoga-${VERSION}.wasm | |
| mv yoga.mjs yoga-${VERSION}.mjs | |
| mv yoga-sync.cjs yoga-sync-${VERSION}.cjs | |
| mv yoga-sync.mjs yoga-sync-${VERSION}.mjs | |
| - name: Generate checksums | |
| if: steps.version.outputs.skip != 'true' | |
| run: | | |
| cd packages/yoga-layout-builder/build/prod/${PLATFORM_ARCH}/wasm/out/Final | |
| $(command -v shasum &> /dev/null && echo "shasum -a 256" || echo "sha256sum") yoga-*.wasm yoga-*.mjs yoga-sync-*.cjs yoga-sync-*.mjs > checksums.txt | |
| cat checksums.txt | |
| - name: Import GPG key | |
| if: steps.version.outputs.skip != 'true' | |
| env: | |
| GPG_PRIVATE_KEY: ${{ secrets.BOT_GPG_PRIVATE_KEY }} | |
| GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} | |
| run: | | |
| if [ -n "$GPG_PRIVATE_KEY" ]; then | |
| echo "$GPG_PRIVATE_KEY" | gpg --batch --import | |
| echo "GPG key imported successfully" | |
| else | |
| echo "GPG_PRIVATE_KEY secret not set, skipping signature" | |
| fi | |
| - name: Sign checksums | |
| if: steps.version.outputs.skip != 'true' | |
| env: | |
| GPG_PRIVATE_KEY: ${{ secrets.BOT_GPG_PRIVATE_KEY }} | |
| GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} | |
| run: | | |
| if [ -n "$GPG_PRIVATE_KEY" ]; then | |
| cd packages/yoga-layout-builder/build/prod/${PLATFORM_ARCH}/wasm/out/Final | |
| if [ -n "$GPG_PASSPHRASE" ]; then | |
| echo "$GPG_PASSPHRASE" | gpg --batch --yes --passphrase-fd 0 --detach-sign --armor checksums.txt | |
| else | |
| gpg --batch --yes --detach-sign --armor checksums.txt | |
| fi | |
| echo "[OK] Created checksums.txt.asc" | |
| ls -lh checksums.txt.asc | |
| fi | |
| - name: Create GitHub Release | |
| if: steps.version.outputs.skip != 'true' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| STEPS_VERSION_OUTPUTS_VERSION: ${{ steps.version.outputs.version }} | |
| YOGA_VERSION: ${{ steps.tool-versions.outputs.yoga-version }} | |
| run: | | |
| VERSION="${STEPS_VERSION_OUTPUTS_VERSION}" | |
| RELEASE_NAME="yoga-layout" | |
| TAG="${RELEASE_NAME}-${VERSION}" | |
| echo "Creating new release $TAG..." | |
| # Extract date-hash from tag for title | |
| TITLE_SUFFIX="${TAG#yoga-layout-}" | |
| gh release create "$TAG" \ | |
| --title "yoga-layout ${TITLE_SUFFIX}" \ | |
| --notes "Yoga Layout v${YOGA_VERSION} compiled to WebAssembly. | |
| ## Files | |
| - \`yoga-${VERSION}.wasm\` - WASM binary | |
| - \`yoga-${VERSION}.mjs\` - ES module loader | |
| - \`yoga-sync-${VERSION}.cjs\` - Synchronous CommonJS wrapper | |
| - \`yoga-sync-${VERSION}.mjs\` - Synchronous ES module wrapper | |
| - \`checksums.txt\` - SHA256 checksums | |
| ## Usage | |
| Import the module in your project: | |
| \`\`\`javascript | |
| import yoga from './yoga-${VERSION}.mjs'; | |
| \`\`\`" \ | |
| packages/yoga-layout-builder/build/prod/${PLATFORM_ARCH}/wasm/out/Final/yoga-${VERSION}.wasm \ | |
| packages/yoga-layout-builder/build/prod/${PLATFORM_ARCH}/wasm/out/Final/yoga-${VERSION}.mjs \ | |
| packages/yoga-layout-builder/build/prod/${PLATFORM_ARCH}/wasm/out/Final/yoga-sync-${VERSION}.cjs \ | |
| packages/yoga-layout-builder/build/prod/${PLATFORM_ARCH}/wasm/out/Final/yoga-sync-${VERSION}.mjs \ | |
| packages/yoga-layout-builder/build/prod/${PLATFORM_ARCH}/wasm/out/Final/checksums.txt \ | |
| $([ -f packages/yoga-layout-builder/build/prod/${PLATFORM_ARCH}/wasm/out/Final/checksums.txt.asc ] && echo "packages/yoga-layout-builder/build/prod/${PLATFORM_ARCH}/wasm/out/Final/checksums.txt.asc" || echo "") | |
| update-checksums: | |
| needs: release | |
| if: github.event_name == 'workflow_dispatch' && !inputs.dry_run | |
| permissions: | |
| contents: write # release-assets.json commit after publishing GitHub release | |
| uses: ./.github/workflows/update-checksums.yml | |
| secrets: | |
| BOT_GPG_PRIVATE_KEY: ${{ secrets.BOT_GPG_PRIVATE_KEY }} |