🤏 Smol Binaries #113
Workflow file for this run
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: 🤏 Smol Binaries | |
| on: | |
| workflow_call: | |
| inputs: | |
| force: | |
| description: 'Force rebuild (ignore cache)' | |
| required: false | |
| type: boolean | |
| default: false | |
| build-linux: | |
| description: 'Build Linux smol binaries' | |
| required: false | |
| type: boolean | |
| default: true | |
| build-macos: | |
| description: 'Build macOS smol binaries' | |
| required: false | |
| type: boolean | |
| default: true | |
| build-windows: | |
| description: 'Build Windows smol binaries' | |
| required: false | |
| type: boolean | |
| default: true | |
| workflow_dispatch: | |
| inputs: | |
| force: | |
| description: 'Force rebuild (ignore cache)' | |
| required: false | |
| type: boolean | |
| default: false | |
| build-linux: | |
| description: 'Build Linux smol binaries' | |
| required: false | |
| type: boolean | |
| default: true | |
| build-macos: | |
| description: 'Build macOS smol binaries' | |
| required: false | |
| type: boolean | |
| default: true | |
| build-windows: | |
| description: 'Build Windows smol binaries' | |
| required: false | |
| type: boolean | |
| default: true | |
| # Removed push/pull_request triggers to prevent automatic builds. | |
| # Run manually via workflow_dispatch or via workflow_call from build-socketbin.yml. | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: build-smol-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| build-deps: | |
| name: ⚡ Dependencies | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| outputs: | |
| deps-hash: ${{ steps.deps-cache-key.outputs.hash }} | |
| 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.11.0 | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@9fd676a19091d4595eefd76e4bd31c97133911f1 # v4.2.0 | |
| with: | |
| version: 10.20.0 | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Generate build-deps cache key | |
| id: deps-cache-key | |
| shell: bash | |
| run: | | |
| # Include pnpm-lock.yaml to detect dependency changes (e.g., @socketsecurity/lib updates). | |
| HASH=$(find packages/bootstrap packages/socket -type f \( -name "*.mts" -o -name "*.ts" -o -name "*.mjs" -o -name "*.js" -o -name "*.json" \) ! -path "*/node_modules/*" ! -path "*/dist/*" ! -path "*/build/*" | sort | xargs sha256sum | sha256sum | cut -d' ' -f1) | |
| LOCK_HASH=$(sha256sum pnpm-lock.yaml | cut -d' ' -f1) | |
| COMBINED_HASH=$(echo "$HASH-$LOCK_HASH" | sha256sum | cut -d' ' -f1) | |
| echo "hash=$COMBINED_HASH" >> $GITHUB_OUTPUT | |
| - name: Restore build-deps cache | |
| id: deps-cache | |
| uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 | |
| with: | |
| path: | | |
| packages/bootstrap/dist/ | |
| packages/socket/dist/ | |
| key: build-deps-smol-${{ steps.deps-cache-key.outputs.hash }} | |
| restore-keys: build-deps-smol- | |
| - name: Build bootstrap package | |
| if: steps.deps-cache.outputs.cache-hit != 'true' | |
| run: pnpm --filter @socketsecurity/bootstrap run build | |
| - name: Build socket package bootstrap | |
| if: steps.deps-cache.outputs.cache-hit != 'true' | |
| run: pnpm --filter socket run build | |
| - name: Upload build artifacts | |
| uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 | |
| with: | |
| name: build-deps-smol-${{ github.sha }} | |
| path: packages/ | |
| retention-days: 1 | |
| build-smol: | |
| needs: build-deps | |
| name: ⚡ Smol - ${{ matrix.platform }}-${{ matrix.arch }} | |
| runs-on: ${{ matrix.runner }} | |
| timeout-minutes: ${{ inputs.force && 180 || 150 }} | |
| strategy: | |
| fail-fast: true | |
| max-parallel: 8 | |
| matrix: | |
| include: | |
| # Linux builds. | |
| - runner: ubuntu-latest | |
| os: linux | |
| platform: linux | |
| arch: x64 | |
| - runner: ubuntu-24.04-arm | |
| os: linux | |
| platform: linux | |
| arch: arm64 | |
| # Alpine Linux builds - use musl binaries from unofficial-builds.nodejs.org. | |
| - runner: ubuntu-latest | |
| os: linux | |
| platform: alpine | |
| arch: x64 | |
| - runner: ubuntu-24.04-arm | |
| os: linux | |
| platform: alpine | |
| arch: arm64 | |
| # macOS builds. | |
| - runner: macos-latest | |
| os: darwin | |
| platform: darwin | |
| arch: x64 | |
| - runner: macos-latest | |
| os: darwin | |
| platform: darwin | |
| arch: arm64 | |
| # Windows builds. | |
| - runner: windows-latest | |
| os: windows | |
| platform: win32 | |
| arch: x64 | |
| - runner: windows-latest | |
| os: windows | |
| platform: win32 | |
| arch: arm64 | |
| steps: | |
| - name: Check if platform is enabled | |
| id: check-platform | |
| shell: bash | |
| run: | | |
| SHOULD_RUN="false" | |
| if [ "${{ matrix.platform }}" = "linux" ] || [ "${{ matrix.platform }}" = "alpine" ]; then | |
| if [ "${{ inputs.build-linux }}" != "false" ]; then | |
| SHOULD_RUN="true" | |
| fi | |
| elif [ "${{ matrix.platform }}" = "darwin" ]; then | |
| if [ "${{ inputs.build-macos }}" != "false" ]; then | |
| SHOULD_RUN="true" | |
| fi | |
| elif [ "${{ matrix.platform }}" = "win32" ]; then | |
| if [ "${{ inputs.build-windows }}" != "false" ]; then | |
| SHOULD_RUN="true" | |
| fi | |
| fi | |
| echo "should-run=$SHOULD_RUN" >> $GITHUB_OUTPUT | |
| if [ "$SHOULD_RUN" = "true" ]; then | |
| echo -e "\033[35m⚡\033[0m Socket building ${{ matrix.platform }}-${{ matrix.arch }}" | |
| else | |
| echo "⊘ Skipping ${{ matrix.platform }}-${{ matrix.arch }} (disabled by inputs)" | |
| fi | |
| - name: Checkout | |
| if: steps.check-platform.outputs.should-run == 'true' | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | |
| - name: Download build artifacts | |
| if: steps.check-platform.outputs.should-run == 'true' | |
| uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 | |
| with: | |
| name: build-deps-smol-${{ github.sha }} | |
| path: packages/ | |
| - name: Verify bootstrap artifacts | |
| if: steps.check-platform.outputs.should-run == 'true' | |
| shell: bash | |
| run: | | |
| echo -e "\033[35m⚡\033[0m Verifying Socket bootstrap artifacts" | |
| echo "" | |
| ls -lh packages/bootstrap/dist/bootstrap-smol.js && echo -e "\033[32m✓\033[0m bootstrap-smol.js present" || (echo -e "\033[31m✗\033[0m bootstrap-smol.js missing" && exit 1) | |
| ls -lh packages/socket/dist/bootstrap.js && echo -e "\033[32m✓\033[0m socket bootstrap.js present" || (echo -e "\033[31m✗\033[0m socket bootstrap.js missing" && exit 1) | |
| echo "" | |
| echo -e "\033[35m⚡\033[0m Bootstrap artifacts verified" | |
| - name: Setup Node.js | |
| if: steps.check-platform.outputs.should-run == 'true' | |
| uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 | |
| with: | |
| node-version: 22.11.0 | |
| - name: Setup pnpm | |
| if: steps.check-platform.outputs.should-run == 'true' | |
| uses: pnpm/action-setup@9fd676a19091d4595eefd76e4bd31c97133911f1 # v4.2.0 | |
| with: | |
| version: 10.20.0 | |
| - name: Install dependencies | |
| if: steps.check-platform.outputs.should-run == 'true' | |
| run: pnpm install --frozen-lockfile | |
| - name: Record build start time | |
| if: steps.check-platform.outputs.should-run == 'true' | |
| id: build-start | |
| shell: bash | |
| run: echo "timestamp=$(date +%s)" >> $GITHUB_OUTPUT | |
| - name: Generate smol build cache key | |
| if: steps.check-platform.outputs.should-run == 'true' | |
| id: smol-cache-key | |
| shell: bash | |
| run: | | |
| PATCHES_HASH=$(find patches packages/node-smol-builder/patches packages/node-smol-builder/additions scripts -type f \( -name "*.patch" -o -name "*.template.patch" -o -name "*.mjs" -o -name "*.template.mjs" -o -name "*.h" -o -name "*.c" -o -name "*.cc" \) | sort | xargs sha256sum | sha256sum | cut -d' ' -f1) | |
| # Include bootstrap/socket dependencies in cache key to invalidate when they change. | |
| COMBINED_HASH=$(echo "$PATCHES_HASH-${{ needs.build-deps.outputs.deps-hash }}" | sha256sum | cut -d' ' -f1) | |
| echo "hash=$COMBINED_HASH" >> $GITHUB_OUTPUT | |
| - name: Setup ccache (Linux/macOS) | |
| if: matrix.os != 'windows' | |
| uses: hendrikmuhs/ccache-action@ed74d11c0b343532753ecead8a951bb09bb34bc9 # v1.2.14 | |
| with: | |
| key: build-${{ matrix.platform }}-${{ matrix.arch }}-${{ steps.smol-cache-key.outputs.hash }} | |
| max-size: 2G | |
| - name: Restore smol build cache | |
| if: inputs.force != true | |
| id: smol-build-cache | |
| uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 | |
| with: | |
| path: packages/node-smol-builder/build | |
| key: node-smol-build-${{ matrix.platform }}-${{ matrix.arch }}-${{ steps.smol-cache-key.outputs.hash }} | |
| restore-keys: | | |
| node-smol-build-${{ matrix.platform }}-${{ matrix.arch }}- | |
| - name: Restore smol stripped binary cache | |
| if: inputs.force != true | |
| id: smol-stripped-cache | |
| uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 | |
| with: | |
| path: packages/node-smol-builder/build/out/Stripped | |
| key: node-smol-stripped-${{ matrix.platform }}-${{ matrix.arch }}-${{ steps.smol-cache-key.outputs.hash }} | |
| restore-keys: node-smol-stripped-${{ matrix.platform }}-${{ matrix.arch }}- | |
| - name: Restore smol binary cache | |
| if: inputs.force != true | |
| id: smol-cache | |
| uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 | |
| with: | |
| path: packages/node-smol-builder/dist/socket-smol-${{ matrix.platform }}-${{ matrix.arch }} | |
| key: node-smol-${{ matrix.platform }}-${{ matrix.arch }}-${{ steps.smol-cache-key.outputs.hash }} | |
| restore-keys: node-smol-${{ matrix.platform }}-${{ matrix.arch }}- | |
| - name: Validate build cache integrity | |
| if: steps.check-platform.outputs.should-run == 'true' && steps.smol-build-cache.outputs.cache-hit == 'true' | |
| shell: bash | |
| run: | | |
| CACHE_VALID="true" | |
| # Check checkpoint files exist. | |
| if [ ! -f "packages/node-smol-builder/build/.checkpoints/cloned" ]; then | |
| echo -e "\033[33m⚠\033[0m Missing 'cloned' checkpoint" | |
| CACHE_VALID="false" | |
| fi | |
| # If final binary exists, all checkpoints should exist. | |
| BINARY_PATH="packages/node-smol-builder/dist/socket-smol-${{ matrix.platform }}-${{ matrix.arch }}" | |
| if [ "${{ matrix.os }}" = "windows" ]; then | |
| BINARY_PATH="${BINARY_PATH}.exe" | |
| fi | |
| if [ -f "$BINARY_PATH" ]; then | |
| for checkpoint in cloned built complete; do | |
| if [ ! -f "packages/node-smol-builder/build/.checkpoints/$checkpoint" ]; then | |
| echo -e "\033[31m✗\033[0m Incomplete cache: missing $checkpoint checkpoint but binary exists" | |
| CACHE_VALID="false" | |
| fi | |
| done | |
| fi | |
| # Invalidate corrupted cache. | |
| if [ "$CACHE_VALID" = "false" ]; then | |
| echo "::warning::Invalidating corrupted build cache..." | |
| rm -rf packages/node-smol-builder/build | |
| else | |
| echo -e "\033[32m✓\033[0m Build cache integrity validated" | |
| fi | |
| - name: Verify smol binary cache | |
| if: steps.check-platform.outputs.should-run == 'true' | |
| id: smol-cache-valid | |
| shell: bash | |
| run: | | |
| BINARY_PATH="packages/node-smol-builder/dist/socket-smol-${{ matrix.platform }}-${{ matrix.arch }}" | |
| if [ "${{ matrix.os }}" = "windows" ]; then | |
| BINARY_PATH="${BINARY_PATH}.exe" | |
| fi | |
| if [ -f "$BINARY_PATH" ]; then | |
| echo "valid=true" >> $GITHUB_OUTPUT | |
| echo -e "\033[35m⚡\033[0m Socket smol binary cached: $BINARY_PATH" | |
| # Smoke test: verify binary can execute --version. | |
| if "$BINARY_PATH" --version >/dev/null 2>&1; then | |
| echo -e "\033[32m✓\033[0m Smoke test passed (--version)" | |
| else | |
| echo "::warning::Binary exists but failed smoke test, rebuilding" | |
| echo "valid=false" >> $GITHUB_OUTPUT | |
| rm -f "$BINARY_PATH" | |
| fi | |
| else | |
| echo "valid=false" >> $GITHUB_OUTPUT | |
| echo -e "\033[31m✗\033[0m Socket smol binary cache miss: $BINARY_PATH" | |
| fi | |
| - name: Setup Python | |
| if: steps.smol-cache-valid.outputs.valid != 'true' || inputs.force | |
| uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 | |
| with: | |
| # Use 3.11 to match Alpine 3.19 (Docker builds) which ships Python 3.11.14. | |
| # Python 3.13 breaks gyp with: TypeError: Strings must be encoded before hashing | |
| # At: tools/gyp/pylib/gyp/generator/ninja.py:813 hashlib.md5(outputs[0]) | |
| # Python 3.13 requires .encode() for hashlib, but gyp doesn't support it yet. | |
| # Using 3.11 ensures consistency across standard and Alpine builds. | |
| python-version: '3.11' | |
| - name: Verify Python installation | |
| if: steps.smol-cache-valid.outputs.valid != 'true' || inputs.force | |
| shell: bash | |
| run: | | |
| echo "Python version:" | |
| python3 --version | |
| which python3 | |
| echo "PATH=$PATH" | |
| - name: Install build dependencies (Ubuntu) | |
| if: (steps.smol-cache-valid.outputs.valid != 'true' || inputs.force) && matrix.platform == 'linux' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y build-essential gcc-aarch64-linux-gnu g++-aarch64-linux-gnu | |
| - name: Cache Ninja (Ubuntu) | |
| if: (steps.smol-cache-valid.outputs.valid != 'true' || inputs.force) && matrix.platform == 'linux' | |
| id: ninja-cache-ubuntu | |
| uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 | |
| with: | |
| path: /usr/bin/ninja | |
| key: ninja-ubuntu-${{ runner.os }}-${{ runner.arch }} | |
| - name: Install Ninja (Ubuntu) | |
| if: (steps.smol-cache-valid.outputs.valid != 'true' || inputs.force) && matrix.platform == 'linux' && steps.ninja-cache-ubuntu.outputs.cache-hit != 'true' | |
| run: sudo apt-get install -y ninja-build | |
| - name: Cache Ninja (macOS) | |
| if: (steps.smol-cache-valid.outputs.valid != 'true' || inputs.force) && matrix.platform == 'darwin' | |
| id: ninja-cache-macos | |
| uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 | |
| with: | |
| path: | | |
| /usr/local/bin/ninja | |
| /opt/homebrew/bin/ninja | |
| key: ninja-macos-${{ runner.os }}-${{ runner.arch }} | |
| - name: Install Ninja (macOS) | |
| if: (steps.smol-cache-valid.outputs.valid != 'true' || inputs.force) && matrix.platform == 'darwin' && steps.ninja-cache-macos.outputs.cache-hit != 'true' | |
| run: brew install ninja | |
| - name: Cache Ninja (Windows) | |
| if: (steps.smol-cache-valid.outputs.valid != 'true' || inputs.force) && matrix.platform == 'win32' | |
| id: ninja-cache-windows | |
| uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 | |
| with: | |
| path: C:\ProgramData\chocolatey\bin\ninja.exe | |
| key: ninja-windows-${{ runner.os }}-${{ runner.arch }} | |
| - name: Install Ninja (Windows) | |
| if: (steps.smol-cache-valid.outputs.valid != 'true' || inputs.force) && matrix.platform == 'win32' && steps.ninja-cache-windows.outputs.cache-hit != 'true' | |
| run: choco install ninja | |
| - name: Ensure Git for Windows is installed | |
| if: (steps.smol-cache-valid.outputs.valid != 'true' || inputs.force) && matrix.platform == 'win32' | |
| shell: pwsh | |
| run: | | |
| # ═══════════════════════════════════════════════════════════════════ | |
| # CRITICAL: Verify Git for Windows Installation | |
| # ═══════════════════════════════════════════════════════════════════ | |
| # WHY: Git for Windows provides Unix tools needed for patching Node.js source. | |
| # Specifically, we need the 'patch' command to apply Socket patches. | |
| # | |
| # WHAT: Check if Git for Windows is installed. If missing, install it. | |
| # | |
| # NOTE: GitHub Actions runners have Git pre-installed, but this ensures | |
| # compatibility with custom runners or local testing. | |
| # ═══════════════════════════════════════════════════════════════════ | |
| $gitPath = "C:\Program Files\Git" | |
| if (-not (Test-Path $gitPath)) { | |
| Write-Host "⚠ Git for Windows not found at $gitPath" | |
| Write-Host "→ Installing Git for Windows via Chocolatey..." | |
| choco install git -y --no-progress | |
| refreshenv | |
| } else { | |
| Write-Host "✓ Git for Windows found: $gitPath" | |
| } | |
| - name: Add Git Unix tools to PATH (Windows) | |
| if: (steps.smol-cache-valid.outputs.valid != 'true' || inputs.force) && matrix.platform == 'win32' | |
| shell: pwsh | |
| run: | | |
| # ═══════════════════════════════════════════════════════════════════ | |
| # CRITICAL: Add Unix Tools to PATH | |
| # ═══════════════════════════════════════════════════════════════════ | |
| # WHY: Socket patches are in unified diff format and require the 'patch' | |
| # command to apply. Git for Windows bundles Unix tools (patch, diff, | |
| # etc.) in usr/bin but doesn't add them to PATH by default. | |
| # | |
| # WHAT: Add Git's usr/bin directory to PATH so 'patch' is available. | |
| # | |
| # LOCATION: C:\Program Files\Git\usr\bin\patch.exe | |
| # ═══════════════════════════════════════════════════════════════════ | |
| $gitUnixPath = "C:\Program Files\Git\usr\bin" | |
| # Add to GITHUB_PATH so subsequent steps inherit this PATH change. | |
| Write-Host "→ Adding Unix tools to PATH: $gitUnixPath" | |
| Add-Content -Path $env:GITHUB_PATH -Value $gitUnixPath | |
| # Verify patch is now accessible (update PATH in current shell). | |
| $env:PATH = "$gitUnixPath;$env:PATH" | |
| Write-Host "" | |
| Write-Host "→ Verifying patch command:" | |
| Get-Command patch -ErrorAction SilentlyContinue | |
| & patch --version | |
| - name: Ensure Visual Studio is installed | |
| if: (steps.smol-cache-valid.outputs.valid != 'true' || inputs.force) && matrix.platform == 'win32' | |
| shell: pwsh | |
| run: | | |
| # ═══════════════════════════════════════════════════════════════════ | |
| # CRITICAL: Verify Visual Studio Installation | |
| # ═══════════════════════════════════════════════════════════════════ | |
| # WHY: Node.js builds require MSVC compiler (cl.exe), linker (link.exe), | |
| # and Windows SDK. These come with Visual Studio or Build Tools. | |
| # | |
| # WHAT: Check if VS is installed. If missing, install Build Tools 2022. | |
| # | |
| # NOTE: GitHub Actions runners have VS pre-installed, but this ensures | |
| # compatibility with custom runners or local testing. | |
| # ═══════════════════════════════════════════════════════════════════ | |
| $vswherePath = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" | |
| if (Test-Path $vswherePath) { | |
| # vswhere found, check if VS is installed. | |
| $vsPath = & $vswherePath -latest -property installationPath | |
| if ($vsPath) { | |
| Write-Host "✓ Visual Studio found: $vsPath" | |
| } else { | |
| # vswhere exists but found no VS installation. | |
| Write-Host "⚠ vswhere.exe found but no VS installation detected" | |
| Write-Host "→ Installing Visual Studio 2022 Build Tools..." | |
| choco install visualstudio2022buildtools --package-parameters "--add Microsoft.VisualStudio.Workload.VCTools --includeRecommended --passive" -y --no-progress | |
| refreshenv | |
| } | |
| } else { | |
| # No vswhere means no VS installer. | |
| Write-Host "⚠ vswhere.exe not found (Visual Studio not installed)" | |
| Write-Host "→ Installing Visual Studio 2022 Build Tools..." | |
| choco install visualstudio2022buildtools --package-parameters "--add Microsoft.VisualStudio.Workload.VCTools --includeRecommended --passive" -y --no-progress | |
| refreshenv | |
| } | |
| - name: Setup Visual Studio environment (Windows) | |
| if: (steps.smol-cache-valid.outputs.valid != 'true' || inputs.force) && matrix.platform == 'win32' | |
| shell: pwsh | |
| run: | | |
| # ═══════════════════════════════════════════════════════════════════ | |
| # CRITICAL: Configure MSVC Build Environment | |
| # ═══════════════════════════════════════════════════════════════════ | |
| # WHY: Node.js configure.py requires MSVC compiler environment variables | |
| # (PATH, INCLUDE, LIB, etc.) to locate the C++ compiler and linker. | |
| # | |
| # WHAT: Run vcvarsall.bat to set up the Visual Studio build environment, | |
| # then export all changed environment variables to GITHUB_ENV so | |
| # subsequent build steps can use the MSVC toolchain. | |
| # | |
| # FLOW: | |
| # 1. Find VS installation → vswhere.exe | |
| # 2. Locate vcvarsall.bat → VS\VC\Auxiliary\Build\ | |
| # 3. Capture env BEFORE → cmd.exe: set | |
| # 4. Run vcvarsall.bat → Sets up MSVC environment | |
| # 5. Capture env AFTER → cmd.exe: set | |
| # 6. Export changes → GITHUB_ENV (for next steps) | |
| # | |
| # REFERENCE: Same technique as ilammy/msvc-dev-cmd action | |
| # ═══════════════════════════════════════════════════════════════════ | |
| # ─────────────────────────────────────────────────────────────────── | |
| # STEP 1: Find Visual Studio installation using vswhere | |
| # ─────────────────────────────────────────────────────────────────── | |
| # vswhere.exe is Microsoft's official tool for locating VS installations. | |
| # It's included with VS 2017+ and GitHub Actions runners. | |
| # | |
| $vswherePath = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" | |
| if (-not (Test-Path $vswherePath)) { | |
| Write-Error "vswhere.exe not found at $vswherePath" | |
| exit 1 | |
| } | |
| # Query vswhere for the latest VS installation path. | |
| # -products * : Include all VS products (Enterprise, Professional, Community, BuildTools) | |
| # -latest : Get the newest version | |
| # -prerelease : Include preview/RC versions | |
| # | |
| $vsPath = & $vswherePath -products * -latest -prerelease -property installationPath | |
| if (-not $vsPath) { | |
| Write-Error "Visual Studio installation not found" | |
| exit 1 | |
| } | |
| # ─────────────────────────────────────────────────────────────────── | |
| # STEP 2: Locate vcvarsall.bat (MSVC environment setup script) | |
| # ─────────────────────────────────────────────────────────────────── | |
| # vcvarsall.bat is the master script that sets up the MSVC build environment. | |
| # It sets PATH, INCLUDE, LIB, and other variables needed by the compiler. | |
| # | |
| # Location: <VS Install>\VC\Auxiliary\Build\vcvarsall.bat | |
| # | |
| $vcvarsallPath = Join-Path $vsPath "VC\Auxiliary\Build\vcvarsall.bat" | |
| if (-not (Test-Path $vcvarsallPath)) { | |
| Write-Error "vcvarsall.bat not found at $vcvarsallPath" | |
| exit 1 | |
| } | |
| Write-Host "Found vcvarsall.bat: $vcvarsallPath" | |
| # ─────────────────────────────────────────────────────────────────── | |
| # STEP 3-5: Capture environment before/after running vcvarsall.bat | |
| # ─────────────────────────────────────────────────────────────────── | |
| # TECHNIQUE: Run three commands in sequence, separated by cls (clear screen): | |
| # | |
| # ┌──────────────────────────────────────────────────────────────┐ | |
| # │ cmd.exe /c "set && cls && vcvarsall.bat arch && cls && set" │ | |
| # │ │ | |
| # │ Output sections (split by cls): │ | |
| # │ ┌─────────────┐ ┌──────────────┐ ┌────────────────┐│ | |
| # │ │ Section [0] │ cls │ Section [1] │ cls │ Section [2] ││ | |
| # │ │ (env BEFORE)│ │ (vcvars msgs)│ │ (env AFTER) ││ | |
| # │ └─────────────┘ └──────────────┘ └────────────────┘│ | |
| # └──────────────────────────────────────────────────────────────┘ | |
| # | |
| # cls (clear screen) outputs form feed (0x0C), making it a reliable delimiter. | |
| # | |
| $arch = "${{ matrix.arch }}" | |
| Write-Host "Setting up MSVC environment for: $arch" | |
| # Build the command for cmd.exe with proper nested quoting. | |
| # PowerShell -> cmd.exe requires careful quote escaping: | |
| # - Outer quotes for cmd.exe /c argument | |
| # - Inner quotes around vcvarsall.bat path (has spaces) | |
| # - Escape inner quotes with backtick for PowerShell | |
| # | |
| # Result: cmd.exe receives -> set && cls && "C:\...\vcvarsall.bat" x64 && cls && set | |
| # | |
| $cmdArgs = @( | |
| '/c' | |
| "set && cls && `"$vcvarsallPath`" $arch && cls && set" | |
| ) | |
| # Invoke cmd.exe with properly structured arguments. | |
| $output = & cmd.exe $cmdArgs 2>&1 | |
| # Split output by form feed character (0x0C produced by cls). | |
| $sections = ($output -join "`n") -split "`f" | |
| if ($sections.Count -lt 3) { | |
| Write-Host "ERROR: Failed to parse vcvarsall.bat output" | |
| Write-Host "Expected 3 sections (before, messages, after), got $($sections.Count)" | |
| Write-Host "" | |
| Write-Host "Raw output:" | |
| Write-Host $output | |
| exit 1 | |
| } | |
| # ─────────────────────────────────────────────────────────────────── | |
| # STEP 6: Parse environment variables (before and after) | |
| # ─────────────────────────────────────────────────────────────────── | |
| # Convert "NAME=VALUE" lines into PowerShell hashtables for comparison. | |
| # | |
| # Parse BEFORE environment (section 0). | |
| $oldEnv = @{} | |
| foreach ($line in ($sections[0] -split "`n")) { | |
| if ($line -match '^([^=]+)=(.*)$') { | |
| $oldEnv[$matches[1]] = $matches[2] | |
| } | |
| } | |
| # Parse AFTER environment (section 2). | |
| $newEnv = @{} | |
| foreach ($line in ($sections[2] -split "`n")) { | |
| if ($line -match '^([^=]+)=(.*)$') { | |
| $newEnv[$matches[1]] = $matches[2] | |
| } | |
| } | |
| # ─────────────────────────────────────────────────────────────────── | |
| # STEP 7: Export only CHANGED variables to GITHUB_ENV | |
| # ─────────────────────────────────────────────────────────────────── | |
| # GITHUB_ENV format (heredoc for multiline values): | |
| # VARNAME<<EOF_12345 | |
| # value with | |
| # multiple lines | |
| # EOF_12345 | |
| # | |
| # This makes variables available to ALL subsequent steps in the job. | |
| # | |
| Write-Host "Exporting changed environment variables:" | |
| $exportCount = 0 | |
| foreach ($name in $newEnv.Keys) { | |
| $newValue = $newEnv[$name] | |
| $oldValue = $oldEnv[$name] | |
| # Export only variables that changed or are new. | |
| if ($newValue -ne $oldValue) { | |
| Write-Host " $name" | |
| $exportCount++ | |
| # Use heredoc format with random delimiter to safely handle special characters. | |
| $delimiter = "EOF_$(Get-Random)" | |
| Add-Content -Path $env:GITHUB_ENV -Value "$name<<$delimiter" | |
| Add-Content -Path $env:GITHUB_ENV -Value $newValue | |
| Add-Content -Path $env:GITHUB_ENV -Value $delimiter | |
| } | |
| } | |
| Write-Host "" | |
| Write-Host "Visual Studio environment configured ($exportCount variables exported)" | |
| # ─────────────────────────────────────────────────────────────────── | |
| # STEP 7.5: Export gyp-specific environment variables | |
| # ─────────────────────────────────────────────────────────────────── | |
| # gyp (used by configure.py) needs these to bypass registry detection. | |
| # GYP_MSVS_VERSION: Tells gyp which VS version to use. | |
| # GYP_MSVS_OVERRIDE_PATH: Bypasses registry check, uses provided path. | |
| # | |
| Write-Host "" | |
| Write-Host "Exporting gyp environment variables:" | |
| # GYP_MSVS_VERSION | |
| $gypVersion = "2022" | |
| Add-Content -Path $env:GITHUB_ENV -Value "GYP_MSVS_VERSION=$gypVersion" | |
| Write-Host " GYP_MSVS_VERSION = $gypVersion" | |
| # GYP_MSVS_OVERRIDE_PATH | |
| Add-Content -Path $env:GITHUB_ENV -Value "GYP_MSVS_OVERRIDE_PATH=$vsPath" | |
| Write-Host " GYP_MSVS_OVERRIDE_PATH = $vsPath" | |
| # ─────────────────────────────────────────────────────────────────── | |
| # STEP 8: Verify critical VS environment variables were set | |
| # ─────────────────────────────────────────────────────────────────── | |
| # CRITICAL: Ensure vcvarsall.bat actually configured the environment. | |
| # configure.py needs these variables to find the MSVC compiler. | |
| # | |
| Write-Host "" | |
| Write-Host "Verifying critical MSVC environment variables:" | |
| $criticalVars = @( | |
| 'VCINSTALLDIR' | |
| 'WindowsSDKVersion' | |
| 'INCLUDE' | |
| 'LIB' | |
| 'PATH' | |
| ) | |
| $allSet = $true | |
| foreach ($var in $criticalVars) { | |
| # Check if variable was exported to newEnv (will be in GITHUB_ENV). | |
| $value = $newEnv[$var] | |
| if ($value) { | |
| $preview = $value.Substring(0, [Math]::Min(60, $value.Length)) | |
| if ($value.Length -gt 60) { $preview += "..." } | |
| Write-Host " ✓ $var = $preview" | |
| } else { | |
| Write-Host " ✗ $var is NOT SET" | |
| $allSet = $false | |
| } | |
| } | |
| Write-Host "" | |
| if (-not $allSet) { | |
| Write-Error "vcvarsall.bat did not set required environment variables!" | |
| Write-Host "" | |
| Write-Host "This means:" | |
| Write-Host " 1. vcvarsall.bat failed to run, OR" | |
| Write-Host " 2. vcvarsall.bat ran but didn't configure the environment" | |
| Write-Host "" | |
| Write-Host "Debug: Showing vcvarsall.bat messages (section 1):" | |
| Write-Host $sections[1] | |
| exit 1 | |
| } | |
| Write-Host "✓ MSVC environment verified" | |
| Write-Host "Next build step will have access to MSVC compiler toolchain" | |
| - name: Verify MSVC environment persisted (Windows) | |
| if: (steps.smol-cache-valid.outputs.valid != 'true' || inputs.force) && matrix.platform == 'win32' | |
| shell: pwsh | |
| run: | | |
| # ═══════════════════════════════════════════════════════════════════ | |
| # SECONDARY CHECK: Verify GITHUB_ENV worked | |
| # ═══════════════════════════════════════════════════════════════════ | |
| # This verifies that environment variables exported via GITHUB_ENV | |
| # in the previous step are actually available in this new step. | |
| # | |
| # If this fails, it means GITHUB_ENV heredoc format has issues. | |
| # ═══════════════════════════════════════════════════════════════════ | |
| Write-Host "Verifying MSVC environment persisted to this step:" | |
| Write-Host "" | |
| $criticalVars = @('VCINSTALLDIR', 'WindowsSDKVersion', 'INCLUDE', 'LIB') | |
| $allSet = $true | |
| foreach ($var in $criticalVars) { | |
| $value = [Environment]::GetEnvironmentVariable($var) | |
| if ($value) { | |
| Write-Host " ✓ $var is set" | |
| } else { | |
| Write-Host " ✗ $var is NOT SET" | |
| $allSet = $false | |
| } | |
| } | |
| Write-Host "" | |
| if (-not $allSet) { | |
| Write-Error "GITHUB_ENV failed to persist environment variables!" | |
| Write-Host "Variables were set in previous step but didn't carry over." | |
| Write-Host "This is likely an issue with heredoc formatting in GITHUB_ENV." | |
| exit 1 | |
| } | |
| Write-Host "✓ GITHUB_ENV worked - variables persisted correctly" | |
| - name: Build smol binary (Standard platforms) | |
| if: steps.check-platform.outputs.should-run == 'true' && (steps.smol-cache-valid.outputs.valid != 'true' || inputs.force) && matrix.platform != 'alpine' | |
| run: pnpm --filter @socketbin/node-smol-builder run build | |
| - name: Set up Docker Buildx (Alpine) | |
| if: steps.check-platform.outputs.should-run == 'true' && (steps.smol-cache-valid.outputs.valid != 'true' || inputs.force) && matrix.platform == 'alpine' | |
| uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 | |
| - name: Build Alpine builder image with caching | |
| if: steps.check-platform.outputs.should-run == 'true' && (steps.smol-cache-valid.outputs.valid != 'true' || inputs.force) && matrix.platform == 'alpine' | |
| uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0 | |
| with: | |
| context: . | |
| file: ./packages/node-smol-builder/docker/Dockerfile.alpine | |
| platforms: linux/${{ matrix.arch == 'x64' && 'amd64' || 'arm64' }} | |
| push: false | |
| load: true | |
| tags: smol-alpine-builder:${{ matrix.arch }} | |
| cache-from: type=gha,scope=alpine-builder-${{ matrix.arch }} | |
| cache-to: type=gha,mode=max,scope=alpine-builder-${{ matrix.arch }} | |
| - name: Build smol binary (Alpine - Docker) | |
| if: steps.check-platform.outputs.should-run == 'true' && (steps.smol-cache-valid.outputs.valid != 'true' || inputs.force) && matrix.platform == 'alpine' | |
| run: | | |
| docker run --rm \ | |
| --platform linux/${{ matrix.arch == 'x64' && 'amd64' || 'arm64' }} \ | |
| -v $PWD:/workspace \ | |
| -w /workspace \ | |
| -e PNPM_HOME=/usr/local/bin \ | |
| smol-alpine-builder:${{ matrix.arch }} \ | |
| sh -c 'pnpm --filter @socketbin/node-smol-builder run build' | |
| - name: Verify smol binary | |
| if: steps.check-platform.outputs.should-run == 'true' | |
| shell: bash | |
| run: | | |
| echo -e "\033[35m⚡\033[0m Socket smol binary verification" | |
| mkdir -p packages/node-smol-builder/dist | |
| ls -lh packages/node-smol-builder/dist/ | |
| echo "" | |
| BINARY_PATH="packages/node-smol-builder/dist/socket-smol-${{ matrix.platform }}-${{ matrix.arch }}" | |
| if [ -f "$BINARY_PATH" ] || [ -f "${BINARY_PATH}.exe" ]; then | |
| if [ "${{ matrix.os }}" = "windows" ]; then | |
| echo -e "\033[32m✓\033[0m socket-smol-${{ matrix.platform }}-${{ matrix.arch }}.exe: $(du -h ${BINARY_PATH}.exe | cut -f1)" | |
| else | |
| echo -e "\033[32m✓\033[0m socket-smol-${{ matrix.platform }}-${{ matrix.arch }}: $(du -h $BINARY_PATH | cut -f1)" | |
| fi | |
| else | |
| echo -e "\033[33m⚠\033[0m Binary not found at expected path" | |
| fi | |
| - name: Upload smol binary | |
| if: steps.check-platform.outputs.should-run == 'true' | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 | |
| with: | |
| name: socket-smol-${{ matrix.platform }}-${{ matrix.arch }} | |
| path: | | |
| packages/node-smol-builder/dist/socket-smol-${{ matrix.platform }}-${{ matrix.arch }} | |
| packages/node-smol-builder/dist/socket-smol-${{ matrix.platform }}-${{ matrix.arch }}.exe | |
| retention-days: 7 | |
| if-no-files-found: warn | |
| - name: Calculate and report build metrics | |
| if: always() && steps.check-platform.outputs.should-run == 'true' && steps.build-start.outputs.timestamp | |
| shell: bash | |
| run: | | |
| # Calculate build duration. | |
| END_TIME=$(date +%s) | |
| DURATION=$((END_TIME - ${{ steps.build-start.outputs.timestamp }})) | |
| DURATION_MIN=$((DURATION / 60)) | |
| DURATION_SEC=$((DURATION % 60)) | |
| # Determine if cache was used. | |
| CACHE_STATUS="${{ steps.smol-cache-valid.outputs.valid == 'true' && 'Hit' || 'Miss' }}" | |
| BUILD_STATUS="${{ job.status }}" | |
| # Create metrics JSON. | |
| cat > build-metrics.json <<EOF | |
| { | |
| "platform": "${{ matrix.platform }}", | |
| "arch": "${{ matrix.arch }}", | |
| "duration_seconds": $DURATION, | |
| "duration_formatted": "${DURATION_MIN}m ${DURATION_SEC}s", | |
| "cache_status": "$CACHE_STATUS", | |
| "build_status": "$BUILD_STATUS", | |
| "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)" | |
| } | |
| EOF | |
| # Report to step summary. | |
| echo "## Build Metrics: ${{ matrix.platform }}-${{ matrix.arch }}" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "- Duration: **${DURATION_MIN}m ${DURATION_SEC}s**" >> $GITHUB_STEP_SUMMARY | |
| echo "- Cache: **$CACHE_STATUS**" >> $GITHUB_STEP_SUMMARY | |
| echo "- Status: **$BUILD_STATUS**" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| - name: Upload build metrics | |
| if: always() && steps.check-platform.outputs.should-run == 'true' | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 | |
| with: | |
| name: build-metrics-${{ matrix.platform }}-${{ matrix.arch }} | |
| path: build-metrics.json | |
| retention-days: 30 | |
| if-no-files-found: ignore | |
| summary: | |
| name: ⚡ Build Summary | |
| needs: [build-deps, build-smol] | |
| if: always() && !cancelled() | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Generate summary | |
| run: | | |
| echo "# ⚡ Socket Smol Build" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| # Check status of dependencies. | |
| DEPS_STATUS="${{ needs.build-deps.result }}" | |
| BUILD_STATUS="${{ needs.build-smol.result }}" | |
| if [ "$DEPS_STATUS" = "failure" ] || [ "$BUILD_STATUS" = "failure" ]; then | |
| echo "## ✗ Build Failed" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "One or more builds failed. Check the logs above for details." >> $GITHUB_STEP_SUMMARY | |
| elif [ "$DEPS_STATUS" = "skipped" ] && [ "$BUILD_STATUS" = "skipped" ]; then | |
| echo "## ⊘ Builds Skipped" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "All builds were skipped (likely due to workflow inputs)." >> $GITHUB_STEP_SUMMARY | |
| elif [ "$BUILD_STATUS" = "skipped" ]; then | |
| echo "## ⊘ Builds Skipped" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "Builds were skipped (platform builds disabled by workflow inputs)." >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "## ✓ Build Complete" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "Socket smol binaries (compressed Node.js + CLI) built successfully." >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### ⚡ Socket Smol Method" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "| Method | Description | Size |" >> $GITHUB_STEP_SUMMARY | |
| echo "|--------|-------------|------|" >> $GITHUB_STEP_SUMMARY | |
| echo "| ⚡ Socket Smol | Compressed Node.js + Socket CLI | ~18 MB |" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### 🎯 Platforms" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "- Linux (x64, arm64)" >> $GITHUB_STEP_SUMMARY | |
| echo "- macOS (x64, arm64)" >> $GITHUB_STEP_SUMMARY | |
| echo "- Windows (x64, arm64)" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### → Next Steps" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| if [ "$BUILD_STATUS" = "success" ]; then | |
| echo "- Binaries cached for E2E tests" >> $GITHUB_STEP_SUMMARY | |
| echo "- Use \`publish-socketbin.yml\` to publish" >> $GITHUB_STEP_SUMMARY | |
| echo "- Cache invalidated on patch/script changes" >> $GITHUB_STEP_SUMMARY | |
| elif [ "$BUILD_STATUS" = "skipped" ]; then | |
| echo "- Re-run workflow with platform builds enabled" >> $GITHUB_STEP_SUMMARY | |
| echo "- Or use \`workflow_dispatch\` to trigger specific platforms" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "- Check build logs for error details" >> $GITHUB_STEP_SUMMARY | |
| echo "- Fix issues and re-run workflow" >> $GITHUB_STEP_SUMMARY | |
| fi | |