📦 Publish CLIs #18
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: 📦 Publish @socketbin packages to npm registry | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: 'Version to publish (semver format: 0.0.0-20250122.143052, auto-generated if omitted)' | |
| required: false | |
| type: string | |
| method: | |
| description: 'Build method to use' | |
| required: false | |
| type: choice | |
| options: | |
| - sea | |
| - smol | |
| - smol-sea | |
| default: sea | |
| dry-run: | |
| description: 'Dry run (build but do not publish) - primes cache for CI e2e tests' | |
| required: false | |
| type: boolean | |
| default: false | |
| jobs: | |
| build-binaries: | |
| name: Build ${{ matrix.platform }}-${{ matrix.arch }} | |
| runs-on: ${{ matrix.runner }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| # Linux builds | |
| - runner: ubuntu-latest | |
| os: linux | |
| platform: linux | |
| arch: x64 | |
| - runner: ubuntu-latest | |
| os: linux | |
| platform: linux | |
| arch: arm64 | |
| # Alpine Linux builds | |
| - runner: ubuntu-latest | |
| os: linux | |
| platform: alpine | |
| arch: x64 | |
| - runner: ubuntu-latest | |
| 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: Checkout | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | |
| with: | |
| autocrlf: false | |
| - name: Setup Node.js | |
| uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 | |
| with: | |
| node-version: 22 | |
| registry-url: 'https://registry.npmjs.org' | |
| - 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: Setup ccache (Linux/macOS) | |
| if: matrix.os != 'windows' | |
| uses: hendrikmuhs/ccache-action@ed74d11c0b343532753ecead8a951bb09bb34bc9 # v1.2.14 | |
| with: | |
| key: build-${{ matrix.platform }}-${{ matrix.arch }} | |
| max-size: 2G | |
| - name: Generate build-deps cache key for smol | |
| if: inputs.method == 'smol' || inputs.method == 'smol-sea' | |
| id: smol-deps-cache-key | |
| shell: bash | |
| run: | | |
| # Generate hash from bootstrap/socket packages (matches build-smol.yml). | |
| if [ "${{ matrix.os }}" = "windows" ]; then | |
| 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) | |
| else | |
| 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 shasum -a 256 | shasum -a 256 | cut -d' ' -f1) | |
| fi | |
| echo "deps-hash=$HASH" >> $GITHUB_OUTPUT | |
| - name: Restore build-deps cache for smol | |
| if: inputs.method == 'smol' || inputs.method == 'smol-sea' | |
| id: smol-deps-cache | |
| uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 | |
| with: | |
| path: | | |
| packages/bootstrap/dist/ | |
| packages/socket/dist/ | |
| key: build-deps-smol-${{ steps.smol-deps-cache-key.outputs.deps-hash }} | |
| restore-keys: build-deps-smol- | |
| - name: Build bootstrap package for smol | |
| if: (inputs.method == 'smol' || inputs.method == 'smol-sea') && steps.smol-deps-cache.outputs.cache-hit != 'true' | |
| run: pnpm --filter @socketsecurity/bootstrap run build | |
| - name: Build socket package bootstrap for smol | |
| if: (inputs.method == 'smol' || inputs.method == 'smol-sea') && steps.smol-deps-cache.outputs.cache-hit != 'true' | |
| run: pnpm --filter socket run build | |
| - name: Generate smol build cache key | |
| if: inputs.method == 'smol' || inputs.method == 'smol-sea' | |
| id: smol-cache-key | |
| shell: bash | |
| run: | | |
| # Generate hash from patches and build scripts (matches build-smol.yml). | |
| if [ "${{ matrix.os }}" = "windows" ]; then | |
| PATCHES_HASH=$(find patches packages/node-smol-builder/patches packages/node-smol-builder/additions scripts -type f \( -name "*.patch" -o -name "*.mjs" -o -name "*.h" -o -name "*.c" -o -name "*.cc" \) | sort | xargs sha256sum | sha256sum | cut -d' ' -f1) | |
| COMBINED_HASH=$(echo "$PATCHES_HASH-${{ steps.smol-deps-cache-key.outputs.deps-hash }}" | sha256sum | cut -d' ' -f1) | |
| else | |
| PATCHES_HASH=$(find patches packages/node-smol-builder/patches packages/node-smol-builder/additions scripts -type f \( -name "*.patch" -o -name "*.mjs" -o -name "*.h" -o -name "*.c" -o -name "*.cc" \) | sort | xargs shasum -a 256 | shasum -a 256 | cut -d' ' -f1) | |
| COMBINED_HASH=$(echo "$PATCHES_HASH-${{ steps.smol-deps-cache-key.outputs.deps-hash }}" | shasum -a 256 | cut -d' ' -f1) | |
| fi | |
| echo "hash=$COMBINED_HASH" >> $GITHUB_OUTPUT | |
| - name: Restore smol binary cache | |
| if: inputs.method == 'smol' || inputs.method == 'smol-sea' | |
| id: smol-cache | |
| uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 | |
| with: | |
| path: packages/node-smol-builder/dist/socket-smol-${{ matrix.platform }}-${{ matrix.arch }}${{ matrix.os == 'windows' && '.exe' || '' }} | |
| key: node-smol-${{ matrix.platform }}-${{ matrix.arch }}-${{ steps.smol-cache-key.outputs.hash }} | |
| restore-keys: node-smol-${{ matrix.platform }}-${{ matrix.arch }}- | |
| - name: Build smol Node.js binary | |
| id: build-smol | |
| if: (inputs.method == 'smol' || inputs.method == 'smol-sea') && steps.smol-cache.outputs.cache-hit != 'true' | |
| continue-on-error: true | |
| shell: bash | |
| run: | | |
| echo "Building smol Node.js binary..." | |
| pnpm --filter @socketsecurity/node-smol-builder run build -- \ | |
| --platform=${{ matrix.platform }} \ | |
| --arch=${{ matrix.arch }} | |
| - name: Build smol binary (fallback with clean rebuild) | |
| if: (inputs.method == 'smol' || inputs.method == 'smol-sea') && steps.smol-cache.outputs.cache-hit != 'true' && steps.build-smol.outcome == 'failure' | |
| shell: bash | |
| run: | | |
| echo "Initial smol build failed, attempting clean rebuild..." | |
| pnpm --filter @socketsecurity/node-smol-builder run build -- \ | |
| --platform=${{ matrix.platform }} \ | |
| --arch=${{ matrix.arch }} \ | |
| --clean | |
| - name: Generate build-deps cache key | |
| if: inputs.method == 'sea' || inputs.method == 'smol-sea' | |
| id: deps-cache-key | |
| shell: bash | |
| run: | | |
| # Include pnpm-lock.yaml to detect dependency changes (matches build-sea.yml). | |
| if [ "${{ matrix.os }}" = "windows" ]; then | |
| 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) | |
| else | |
| 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 shasum -a 256 | shasum -a 256 | cut -d' ' -f1) | |
| LOCK_HASH=$(shasum -a 256 pnpm-lock.yaml | cut -d' ' -f1) | |
| fi | |
| COMBINED_HASH=$(echo "$HASH-$LOCK_HASH" | sha256sum | cut -d' ' -f1 || echo "$HASH-$LOCK_HASH" | shasum -a 256 | cut -d' ' -f1) | |
| echo "deps-hash=$COMBINED_HASH" >> $GITHUB_OUTPUT | |
| - name: Restore build-deps cache | |
| if: inputs.method == 'sea' || inputs.method == 'smol-sea' | |
| id: deps-cache | |
| uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 | |
| with: | |
| path: | | |
| packages/bootstrap/dist/ | |
| packages/socket/dist/ | |
| key: build-deps-sea-${{ steps.deps-cache-key.outputs.deps-hash }} | |
| restore-keys: build-deps-sea- | |
| - name: Build bootstrap package | |
| if: (inputs.method == 'sea' || inputs.method == 'smol-sea') && steps.deps-cache.outputs.cache-hit != 'true' | |
| run: pnpm --filter @socketsecurity/bootstrap run build | |
| - name: Build socket package bootstrap | |
| if: (inputs.method == 'sea' || inputs.method == 'smol-sea') && steps.deps-cache.outputs.cache-hit != 'true' | |
| run: pnpm --filter socket run build | |
| - name: Generate SEA build cache key | |
| if: inputs.method == 'sea' || inputs.method == 'smol-sea' | |
| id: sea-cache-key | |
| shell: bash | |
| run: | | |
| if [ "${{ matrix.os }}" = "windows" ]; then | |
| SEA_HASH=$(find packages/node-sea-builder packages/cli/src -type f \( -name "*.mts" -o -name "*.ts" -o -name "*.mjs" -o -name "*.js" \) | sort | xargs sha256sum | sha256sum | cut -d' ' -f1) | |
| else | |
| SEA_HASH=$(find packages/node-sea-builder packages/cli/src -type f \( -name "*.mts" -o -name "*.ts" -o -name "*.mjs" -o -name "*.js" \) | sort | xargs shasum -a 256 | shasum -a 256 | cut -d' ' -f1) | |
| fi | |
| # Include bootstrap/socket/cli dependencies in cache key (matches build-sea.yml). | |
| COMBINED_HASH=$(echo "$SEA_HASH-${{ steps.deps-cache-key.outputs.deps-hash }}" | sha256sum | cut -d' ' -f1 || echo "$SEA_HASH-${{ steps.deps-cache-key.outputs.deps-hash }}" | shasum -a 256 | cut -d' ' -f1) | |
| echo "hash=$COMBINED_HASH" >> $GITHUB_OUTPUT | |
| - name: Restore SEA binary cache | |
| if: inputs.method == 'sea' || inputs.method == 'smol-sea' | |
| id: sea-cache | |
| uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 | |
| with: | |
| path: packages/node-sea-builder/dist/sea/ | |
| key: node-sea-${{ matrix.platform }}-${{ matrix.arch }}-${{ steps.sea-cache-key.outputs.hash }} | |
| restore-keys: node-sea-${{ matrix.platform }}-${{ matrix.arch }}- | |
| - name: Build CLI (required for SEA) | |
| if: (inputs.method == 'sea' || inputs.method == 'smol-sea') && steps.sea-cache.outputs.cache-hit != 'true' | |
| shell: bash | |
| run: pnpm --filter @socketsecurity/cli run build | |
| - name: Build SEA binary | |
| id: build-sea | |
| if: (inputs.method == 'sea' || inputs.method == 'smol-sea') && steps.sea-cache.outputs.cache-hit != 'true' | |
| shell: bash | |
| run: | | |
| echo "Building SEA binary..." | |
| pnpm --filter @socketbin/node-sea-builder run build -- \ | |
| --platform=${{ matrix.platform }} \ | |
| --arch=${{ matrix.arch }} | |
| - name: Copy binary to dist/sea for prepublish script | |
| shell: bash | |
| run: | | |
| mkdir -p dist/sea | |
| # Determine source binary name (from build-sea.yml naming). | |
| if [ "${{ matrix.platform }}" = "win32" ]; then | |
| SOURCE_BINARY="packages/node-sea-builder/dist/sea/socket-win-${{ matrix.arch }}.exe" | |
| TARGET_BINARY="dist/sea/socket-${{ matrix.platform }}-${{ matrix.arch }}.exe" | |
| elif [ "${{ matrix.platform }}" = "darwin" ]; then | |
| SOURCE_BINARY="packages/node-sea-builder/dist/sea/socket-macos-${{ matrix.arch }}" | |
| TARGET_BINARY="dist/sea/socket-${{ matrix.platform }}-${{ matrix.arch }}" | |
| else | |
| SOURCE_BINARY="packages/node-sea-builder/dist/sea/socket-${{ matrix.platform }}-${{ matrix.arch }}" | |
| TARGET_BINARY="dist/sea/socket-${{ matrix.platform }}-${{ matrix.arch }}" | |
| fi | |
| cp "$SOURCE_BINARY" "$TARGET_BINARY" | |
| echo "Copied $SOURCE_BINARY -> $TARGET_BINARY" | |
| - name: Verify binary | |
| shell: bash | |
| run: | | |
| ls -la dist/sea/socket-* | |
| file dist/sea/socket-* || true | |
| - name: Upload binary artifact | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 | |
| with: | |
| name: binary-${{ matrix.platform }}-${{ matrix.arch }} | |
| path: dist/sea/socket-* | |
| retention-days: 1 | |
| publish-packages: | |
| name: Publish to npm | |
| needs: build-binaries | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| id-token: write # For provenance | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | |
| with: | |
| autocrlf: false | |
| - name: Setup Node.js | |
| uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 | |
| with: | |
| node-version: 22 | |
| registry-url: 'https://registry.npmjs.org' | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@9fd676a19091d4595eefd76e4bd31c97133911f1 # v4.2.0 | |
| with: | |
| version: ^10.20.0 | |
| - name: Install latest npm | |
| run: npm install -g npm@latest | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Determine version | |
| id: version | |
| run: | | |
| VERSION="${{ inputs.version }}" | |
| if [ -z "$VERSION" ]; then | |
| # Read base version from socketbin package and extract X.Y.Z using semver | |
| PKG_VERSION=$(jq -r '.version' packages/socketbin-cli-linux-x64/package.json) | |
| BASE_VERSION=$(node -e "const semver = require('semver'); const v = semver.parse('$PKG_VERSION'); console.log(v ? \`\${v.major}.\${v.minor}.\${v.patch}\` : '0.0.0');") | |
| # Auto-generate version in semver format: X.Y.Z-YYYYMMDD.HHmmss | |
| VERSION="${BASE_VERSION}-$(date -u +'%Y%m%d.%H%M%S')" | |
| echo "Generated version: $VERSION" | |
| else | |
| # Remove 'v' prefix if present | |
| VERSION="${VERSION#v}" | |
| echo "Using provided version: $VERSION" | |
| fi | |
| echo "version=${VERSION}" >> $GITHUB_OUTPUT | |
| - name: Check version consistency | |
| run: | | |
| echo "🔍 Checking version consistency for v${{ steps.version.outputs.version }}..." | |
| node scripts/check-version-consistency.mjs ${{ steps.version.outputs.version }} | |
| - name: Download all binaries | |
| uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 | |
| with: | |
| path: dist/sea | |
| pattern: binary-* | |
| merge-multiple: true | |
| - name: Verify downloaded binaries | |
| run: | | |
| echo "Downloaded binaries:" | |
| ls -la dist/sea/ | |
| - name: Prepare and validate Linux x64 | |
| run: | | |
| node scripts/prepublish-socketbin.mjs \ | |
| --platform=linux --arch=x64 \ | |
| --version=${{ steps.version.outputs.version }} \ | |
| --method=${{ inputs.method }} | |
| - name: Publish Linux x64 | |
| if: ${{ !inputs.dry-run }} | |
| run: | | |
| cd packages/socketbin-cli-linux-x64 | |
| npm publish --provenance --access public --tag latest --tag latest | |
| - name: Prepare and publish Linux ARM64 | |
| if: ${{ !inputs.dry-run }} | |
| run: | | |
| node scripts/prepublish-socketbin.mjs \ | |
| --platform=linux --arch=arm64 \ | |
| --version=${{ steps.version.outputs.version }} \ | |
| --method=${{ inputs.method }} | |
| cd packages/socketbin-cli-linux-arm64 | |
| npm publish --provenance --access public --tag latest --tag latest | |
| - name: Prepare and publish Alpine x64 | |
| if: ${{ !inputs.dry-run }} | |
| run: | | |
| node scripts/prepublish-socketbin.mjs \ | |
| --platform=alpine --arch=x64 \ | |
| --version=${{ steps.version.outputs.version }} \ | |
| --method=${{ inputs.method }} | |
| cd packages/socketbin-cli-alpine-x64 | |
| npm publish --provenance --access public --tag latest --tag latest | |
| - name: Prepare and publish Alpine ARM64 | |
| if: ${{ !inputs.dry-run }} | |
| run: | | |
| node scripts/prepublish-socketbin.mjs \ | |
| --platform=alpine --arch=arm64 \ | |
| --version=${{ steps.version.outputs.version }} \ | |
| --method=${{ inputs.method }} | |
| cd packages/socketbin-cli-alpine-arm64 | |
| npm publish --provenance --access public --tag latest | |
| - name: Prepare and publish macOS x64 | |
| if: ${{ !inputs.dry-run }} | |
| run: | | |
| node scripts/prepublish-socketbin.mjs \ | |
| --platform=darwin --arch=x64 \ | |
| --version=${{ steps.version.outputs.version }} \ | |
| --method=${{ inputs.method }} | |
| cd packages/socketbin-cli-darwin-x64 | |
| npm publish --provenance --access public --tag latest | |
| - name: Prepare and publish macOS ARM64 | |
| if: ${{ !inputs.dry-run }} | |
| run: | | |
| node scripts/prepublish-socketbin.mjs \ | |
| --platform=darwin --arch=arm64 \ | |
| --version=${{ steps.version.outputs.version }} \ | |
| --method=${{ inputs.method }} | |
| cd packages/socketbin-cli-darwin-arm64 | |
| npm publish --provenance --access public --tag latest | |
| - name: Prepare and publish Windows x64 | |
| if: ${{ !inputs.dry-run }} | |
| run: | | |
| node scripts/prepublish-socketbin.mjs \ | |
| --platform=win32 --arch=x64 \ | |
| --version=${{ steps.version.outputs.version }} \ | |
| --method=${{ inputs.method }} | |
| cd packages/socketbin-cli-win32-x64 | |
| npm publish --provenance --access public --tag latest | |
| - name: Prepare and publish Windows ARM64 | |
| if: ${{ !inputs.dry-run }} | |
| run: | | |
| node scripts/prepublish-socketbin.mjs \ | |
| --platform=win32 --arch=arm64 \ | |
| --version=${{ steps.version.outputs.version }} \ | |
| --method=${{ inputs.method }} | |
| cd packages/socketbin-cli-win32-arm64 | |
| npm publish --provenance --access public --tag latest | |
| - name: Prime npm cache for CI (dry-run) | |
| if: ${{ inputs.dry-run }} | |
| run: | | |
| echo "🔄 Priming npm cache for CI e2e tests..." | |
| # Prime cache for @coana-tech/cli | |
| if [ -n "$INLINED_SOCKET_CLI_COANA_VERSION" ]; then | |
| echo "Priming @coana-tech/cli@~$INLINED_SOCKET_CLI_COANA_VERSION..." | |
| npx --yes @coana-tech/cli@~$INLINED_SOCKET_CLI_COANA_VERSION --help || true | |
| fi | |
| echo "✓ Cache priming complete" | |
| - name: Dry run summary | |
| if: ${{ inputs.dry-run }} | |
| run: | | |
| echo "🚫 Dry run mode - packages were NOT published" | |
| echo "" | |
| echo "Generated packages:" | |
| find packages/binaries -name package.json -exec echo {} \; -exec jq -r '.name + "@" + .version' {} \; | |
| echo "" | |
| echo "💾 Cache primed for CI e2e tests" |