📦 Publish CLIs #14
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 cache key | |
| id: build-cache-key | |
| shell: bash | |
| run: | | |
| # Generate hash from build-affecting files (matches build-binaries.yml). | |
| if [ "${{ matrix.os }}" = "windows" ]; then | |
| 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) | |
| else | |
| 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) | |
| fi | |
| echo "hash=$HASH" >> $GITHUB_OUTPUT | |
| echo "Build hash: $HASH" | |
| - name: Restore smol binary from build-socketbin.yml cache | |
| uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 | |
| id: smol-cache | |
| 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.build-cache-key.outputs.hash }} | |
| restore-keys: node-smol-${{ matrix.platform }}-${{ matrix.arch }}- | |
| - name: Cache compiled Node.js binary | |
| uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 | |
| id: binary-cache | |
| with: | |
| path: | | |
| packages/node-smol-builder/build/out/Release/node | |
| packages/node-smol-builder/build/out/Release/node.exe | |
| key: node-binary-${{ matrix.platform }}-${{ matrix.arch }}-${{ steps.build-cache-key.outputs.hash }} | |
| restore-keys: | | |
| node-binary-${{ matrix.platform }}-${{ matrix.arch }}- | |
| - name: Build smol Node.js binary | |
| id: build-smol | |
| if: inputs.method == 'smol' || inputs.method == 'smol-sea' | |
| 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.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: Build CLI (required for SEA) | |
| if: inputs.method == 'sea' || inputs.method == 'smol-sea' | |
| shell: bash | |
| run: pnpm --filter @socketsecurity/cli run build | |
| - name: Build SEA binary | |
| id: build-sea | |
| if: inputs.method == 'sea' || inputs.method == 'smol-sea' | |
| 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" | |
| publish-main: | |
| name: Publish main socket package | |
| needs: publish-packages | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| id-token: write | |
| 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: Update package.json for @socketbin | |
| run: | | |
| cd src/sea/npm-package | |
| # Update version | |
| npm version ${{ steps.version.outputs.version }} --no-git-tag-version | |
| # Update package.json to use optionalDependencies | |
| node -e " | |
| const fs = require('fs'); | |
| const { execSync } = require('child_process'); | |
| const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')); | |
| // Remove old postinstall script | |
| delete pkg.scripts?.postinstall; | |
| // Add dispatcher script to bin | |
| pkg.bin = { socket: 'bin/socket.js' }; | |
| // Get latest version from registry for each platform package | |
| const platforms = [ | |
| 'alpine-arm64', 'alpine-x64', | |
| 'darwin-arm64', 'darwin-x64', | |
| 'linux-arm64', 'linux-x64', | |
| 'win32-arm64', 'win32-x64' | |
| ]; | |
| pkg.optionalDependencies = {}; | |
| for (const platform of platforms) { | |
| const pkgName = \`@socketbin/cli-\${platform}\`; | |
| try { | |
| // Get latest published version from registry | |
| const version = execSync(\`npm view \${pkgName} version\`, { encoding: 'utf8' }).trim(); | |
| pkg.optionalDependencies[pkgName] = version; | |
| console.log(\`Using published version for \${pkgName}: \${version}\`); | |
| } catch (e) { | |
| // Package doesn't exist yet - skip it | |
| console.log(\`Package \${pkgName} not found in registry, skipping\`); | |
| } | |
| } | |
| // Update files list | |
| pkg.files = ['bin', 'README.md']; | |
| fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n'); | |
| " | |
| # Show the updated package.json | |
| cat package.json | |
| - name: Validate main package | |
| run: | | |
| cd src/sea/npm-package | |
| # Basic validation | |
| test -f package.json || exit 1 | |
| test -f bin/socket.js || exit 1 | |
| test -f README.md || exit 1 | |
| echo "✓ Package structure validated" | |
| - name: Publish main package | |
| if: ${{ !inputs.dry-run }} | |
| working-directory: src/sea/npm-package | |
| run: npm publish --provenance --access public --tag latest | |
| - name: Dry run summary | |
| if: ${{ inputs.dry-run }} | |
| run: | | |
| echo "🚫 Dry run mode - main package was NOT published" | |
| echo "" | |
| echo "Would have published:" | |
| cd src/sea/npm-package | |
| echo "socket@$(jq -r .version package.json)" |