Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 169 additions & 0 deletions .github/workflows/sea-build-sign.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
name: Build SEA Artifacts

on:
workflow_dispatch:
inputs:
sign_artifacts:
description: 'Sign artifacts when signing secrets are configured.'
required: false
default: false
type: boolean

permissions:
contents: read

env:
NODE_OPTIONS: --unhandled-rejections=warn
DO_NOT_TRACK: '1'

jobs:
build-sea-native:
name: SEA Native (${{ matrix.target.name }})
runs-on: ${{ matrix.target.runs_on }}
env:
SIGN_ARTIFACTS: ${{ inputs.sign_artifacts }}
MACOS_CERTIFICATE_P12_BASE64: ${{ secrets.MACOS_CERTIFICATE_P12_BASE64 }}
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
MACOS_SIGNING_IDENTITY: ${{ secrets.MACOS_SIGNING_IDENTITY }}
WINDOWS_CERTIFICATE_PFX_BASE64: ${{ secrets.WINDOWS_CERTIFICATE_PFX_BASE64 }}
WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
WINDOWS_TIMESTAMP_URL: ${{ vars.WINDOWS_TIMESTAMP_URL }}
strategy:
fail-fast: false
matrix:
target:
- name: macOS
runs_on: macos-latest
binary_path: dist/sea/vip
artifact_name: vip-sea-macos
- name: Linux
runs_on: ubuntu-latest
binary_path: dist/sea/vip
artifact_name: vip-sea-linux
- name: Windows
runs_on: windows-latest
binary_path: dist/sea/vip.exe
artifact_name: vip-sea-windows

steps:
- name: Check out the source code
uses: actions/checkout@v6

- name: Set git to use LF
if: runner.os == 'Windows'
run: |
git config --global core.autocrlf false
git config --global core.eol lf

- name: Set up Node.js 22
uses: actions/setup-node@v6
with:
node-version: '22.x'
cache: npm
cache-dependency-path: npm-shrinkwrap.json

- name: Install dependencies
run: npm ci

- name: Build dist
run: npm run build

- name: Build SEA artifact
run: npm run build:sea

- name: Smoke test executable (Unix)
if: runner.os != 'Windows'
run: |
${{ matrix.target.binary_path }} --version
${{ matrix.target.binary_path }} whoami --help

- name: Smoke test executable (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
.\${{ matrix.target.binary_path }} --version
.\${{ matrix.target.binary_path }} whoami --help

- name: Import macOS signing certificate
if: runner.os == 'macOS' && env.SIGN_ARTIFACTS == 'true' && env.MACOS_CERTIFICATE_P12_BASE64 != '' && env.MACOS_CERTIFICATE_PASSWORD != '' && env.MACOS_SIGNING_IDENTITY != ''
run: |
KEYCHAIN_PATH="$RUNNER_TEMP/build.keychain-db"
CERT_PATH="$RUNNER_TEMP/macos-cert.p12"

echo "$MACOS_CERTIFICATE_P12_BASE64" | base64 --decode > "$CERT_PATH"
security create-keychain -p "" "$KEYCHAIN_PATH"
security unlock-keychain -p "" "$KEYCHAIN_PATH"
security list-keychains -s "$KEYCHAIN_PATH"
security default-keychain -s "$KEYCHAIN_PATH"
security import "$CERT_PATH" -k "$KEYCHAIN_PATH" -P "$MACOS_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple: -s -k "" "$KEYCHAIN_PATH"

codesign --remove-signature ${{ matrix.target.binary_path }}
codesign --sign "$MACOS_SIGNING_IDENTITY" --force --options runtime ${{ matrix.target.binary_path }}
codesign --verify --strict --verbose=2 ${{ matrix.target.binary_path }}

- name: Sign Windows executable
if: runner.os == 'Windows' && env.SIGN_ARTIFACTS == 'true' && env.WINDOWS_CERTIFICATE_PFX_BASE64 != '' && env.WINDOWS_CERTIFICATE_PASSWORD != ''
shell: pwsh
run: |
$certPath = Join-Path $env:RUNNER_TEMP 'codesign.pfx'
[System.IO.File]::WriteAllBytes($certPath, [System.Convert]::FromBase64String($env:WINDOWS_CERTIFICATE_PFX_BASE64))
$timestampUrl = if ($env:WINDOWS_TIMESTAMP_URL) { $env:WINDOWS_TIMESTAMP_URL } else { 'http://timestamp.digicert.com' }

signtool sign /fd SHA256 /td SHA256 /tr $timestampUrl /f $certPath /p $env:WINDOWS_CERTIFICATE_PASSWORD .\${{ matrix.target.binary_path }}
signtool verify /pa /v .\${{ matrix.target.binary_path }}

- name: Generate checksum (Unix)
if: runner.os != 'Windows'
run: |
shasum -a 256 ${{ matrix.target.binary_path }} > ${{ matrix.target.binary_path }}.sha256

- name: Generate checksum (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
$hash = (Get-FileHash -Algorithm SHA256 .\${{ matrix.target.binary_path }}).Hash.ToLower()
"${hash} *${{ matrix.target.binary_path }}" | Set-Content .\${{ matrix.target.binary_path }}.sha256 -NoNewline

- name: Upload SEA artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.target.artifact_name }}
path: |
${{ matrix.target.binary_path }}
${{ matrix.target.binary_path }}.sha256
if-no-files-found: error

build-sea-windows-wsl:
name: SEA (Windows WSL)
runs-on: windows-latest

steps:
- name: Check out the source code
uses: actions/checkout@v6

- name: Set git to use LF
run: |
git config --global core.autocrlf false
git config --global core.eol lf

- name: Build SEA inside WSL
shell: pwsh
run: |
$workspaceWsl = (wsl.exe wslpath -a "$env:GITHUB_WORKSPACE").Trim()
if (-not $workspaceWsl) {
throw 'Failed to resolve WSL workspace path.'
}

wsl.exe bash -lc "set -euo pipefail; export NVM_DIR=\$HOME/.nvm; if [ ! -s \"\$NVM_DIR/nvm.sh\" ]; then curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash; fi"
wsl.exe bash -lc "set -euo pipefail; export NVM_DIR=\$HOME/.nvm; . \"\$NVM_DIR/nvm.sh\"; nvm install 22; nvm use 22; cd \"$workspaceWsl\"; npm ci; npm run build; npm run build:sea; ./dist/sea/vip --version; ./dist/sea/vip whoami --help"
wsl.exe bash -lc "set -euo pipefail; cd \"$workspaceWsl\"; sha256sum dist/sea/vip > dist/sea/vip.sha256"

- name: Upload WSL SEA artifact
uses: actions/upload-artifact@v4
with:
name: vip-sea-windows-wsl
path: |
dist/sea/vip
dist/sea/vip.sha256
if-no-files-found: error
6 changes: 6 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ Guide for future agents working on this codebase. Focus on traps, cross-cutting
- `prepare` runs `npm run clean && npm run build`; npm package bins point to `dist/**`. Always rebuild before publishing so dist matches src.
- `helpers/prepublishOnly.js` enforces branch `trunk` for `npm publish --tag latest` and optionally reruns `npm test`. Release flows expect a clean node version that satisfies `engines.node`.

## Standalone SEA Packaging

- Canonical runbook for standalone executable build/signing is in `docs/SEA-BUILD-SIGNING.md`. Use it for macOS, Linux, Windows native, and WSL-mediated Windows builds.
- SEA builds are Node 22 only (enforced in `helpers/build-sea.js`); always verify `node -v` before `npm run build:sea`.
- The executable is self-contained for Node runtime + JS deps, but `dev-env` commands still require host Docker/Compose availability.

## Common Pitfalls Checklist

- Running CLI without a token opens a browser (`open`) and waits for interactive input—pass `--help` or set `WPVIP_DEPLOY_TOKEN` in automation.
Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# CLAUDE

For guidance on working in this repo, traps, and migration notes, see `AGENTS.md`.
For standalone SEA build/signing by platform, see `docs/SEA-BUILD-SIGNING.md`.
171 changes: 171 additions & 0 deletions docs/SEA-BUILD-SIGNING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
# SEA Build and Signing Runbook

Purpose: build and sign the standalone VIP CLI executable (`dist/sea/vip` or `dist/sea/vip.exe`) for each supported platform.

This repo uses `helpers/build-sea.js` and the `npm run build:sea` script. SEA build is pinned to Node 22.

## Shared Prerequisites

- Use Node 22.x exactly for SEA builds.
- Install dependencies before building: `npm ci`.
- Build from repo root.
- The build script embeds:
- Node runtime
- bundled CLI code (`src/bin/vip-sea.js`)
- SEA assets and runtime dependency archive (`sea.node_modules.tgz`)
- Output paths:
- Unix: `dist/sea/vip`
- Windows: `dist/sea/vip.exe`

## Shared Build Steps

```bash
npm ci
npm run build
npm run build:sea
```

Quick smoke checks after every build:

```bash
dist/sea/vip --version
dist/sea/vip whoami --help
dist/sea/vip dev-env info --help
```

## macOS (native)

Node/tool setup:

```bash
export NVM_DIR="$HOME/.nvm"
. "$NVM_DIR/nvm.sh"
nvm use 22
node -v
```

Build:

```bash
npm ci
npm run build
npm run build:sea
```

Notes:

- `helpers/build-sea.js` already does ad-hoc signing (`codesign --sign -`) after blob injection so local execution works.
- For distribution, replace ad-hoc signature with a real Developer ID certificate.

Distribution signing:

```bash
codesign --remove-signature dist/sea/vip
codesign --sign "Developer ID Application: <TEAM/ORG>" --force --options runtime dist/sea/vip
codesign --verify --strict --verbose=2 dist/sea/vip
spctl -a -t exec -vv dist/sea/vip
```

## Linux (native)

Node/tool setup:

```bash
node -v
```

(Use Node 22 before build.)

Build:

```bash
npm ci
npm run build
npm run build:sea
chmod +x dist/sea/vip
```

Signing guidance:

- Linux does not have a universal OS-enforced Authenticode-style executable signature.
- Recommended: publish checksums and detached signatures.

Checksum + GPG example:

```bash
sha256sum dist/sea/vip > dist/sea/vip.sha256
gpg --armor --detach-sign dist/sea/vip
```

Cosign blob example:

```bash
cosign sign-blob --yes --output-signature dist/sea/vip.sig dist/sea/vip
cosign verify-blob --signature dist/sea/vip.sig dist/sea/vip
```

## Windows (native)

Use PowerShell or `cmd.exe` on Windows (not WSL) when producing Windows artifacts.

Build:

```powershell
npm ci
npm run build
npm run build:sea
.\dist\sea\vip.exe --version
```

Authenticode signing (SignTool):

```powershell
signtool sign /fd SHA256 /td SHA256 /tr http://timestamp.digicert.com /a .\dist\sea\vip.exe
signtool verify /pa /v .\dist\sea\vip.exe
```

If your cert is in a PFX file:

```powershell
signtool sign /f C:\path\cert.pfx /p <PFX_PASSWORD> /fd SHA256 /td SHA256 /tr http://timestamp.digicert.com .\dist\sea\vip.exe
```

## Windows from WSL

Important: WSL builds Linux binaries by default.

- If target is Linux binary: build/sign inside WSL using the Linux flow.
- If target is Windows `.exe`: run the build and signing commands in Windows context.

From WSL, invoke Windows PowerShell for a Windows-target build:

```bash
WIN_REPO_PATH="$(wslpath -w "$PWD")"
powershell.exe -NoProfile -Command "Set-Location '$WIN_REPO_PATH'; npm ci; npm run build; npm run build:sea"
```

Then sign in Windows context:

```bash
powershell.exe -NoProfile -Command "Set-Location '$WIN_REPO_PATH'; signtool sign /fd SHA256 /td SHA256 /tr http://timestamp.digicert.com /a .\\dist\\sea\\vip.exe; signtool verify /pa /v .\\dist\\sea\\vip.exe"
```

## Release Checklist for Agents

- Confirm Node 22 before SEA build.
- Confirm artifact type matches target OS (`vip` vs `vip.exe`).
- Run smoke checks on the produced executable.
- Apply platform-appropriate signature method.
- Verify signature/checksum before publishing.
- Record signing method and timestamp authority in release notes.

## GitHub Actions Automation

- Workflow: `.github/workflows/sea-build-sign.yml`
- Trigger: manual `workflow_dispatch`
- Jobs: native macOS/Linux/Windows SEA builds plus a Windows WSL SEA build
- Optional signing: set `sign_artifacts=true` and provide signing secrets/vars:
- macOS: `MACOS_CERTIFICATE_P12_BASE64`, `MACOS_CERTIFICATE_PASSWORD`, `MACOS_SIGNING_IDENTITY`
- Windows: `WINDOWS_CERTIFICATE_PFX_BASE64`, `WINDOWS_CERTIFICATE_PASSWORD`
- optional variable: `WINDOWS_TIMESTAMP_URL`
- Output: uploaded SEA binary + SHA256 artifact per job
Loading
Loading