diff --git a/.github/workflows/standalone-release.yml b/.github/workflows/standalone-release.yml index 997c24dcc..fe48f9fb8 100644 --- a/.github/workflows/standalone-release.yml +++ b/.github/workflows/standalone-release.yml @@ -7,14 +7,26 @@ on: - 'v*' jobs: - standalone-linux-x64: + standalone: permissions: contents: read id-token: write - runs-on: ubuntu-22.04 + strategy: + fail-fast: false + matrix: + include: + - target: linux-x64 + runner: ubuntu-22.04 + - target: darwin-x64 + runner: macos-15-intel + - target: darwin-arm64 + runner: macos-14 + runs-on: ${{ matrix.runner }} env: SOURCE_DATE_EPOCH: '1704067200' NODE_VERSION: '20.19.1' + TARGET: ${{ matrix.target }} + ARTIFACT: dist/standalone/devcontainer-${{ matrix.target }} steps: - uses: actions/checkout@v4 @@ -30,25 +42,28 @@ jobs: - name: Build production bundle run: npm run compile-prod - - name: Build standalone Linux x64 artifact - run: scripts/standalone/build-linux-x64.sh + - name: Build standalone artifact + run: scripts/standalone/build.sh "$TARGET" - name: Standalone smoke tests - run: scripts/standalone/smoke.sh dist/standalone/devcontainer-linux-x64 + run: scripts/standalone/smoke.sh "$ARTIFACT" - name: Create checksums - run: sha256sum dist/standalone/devcontainer-linux-x64 > dist/standalone/devcontainer-linux-x64.sha256 + run: shasum -a 256 "$ARTIFACT" > "$ARTIFACT.sha256" + + - name: Install Cosign + uses: sigstore/cosign-installer@v3.8.1 - name: Sign checksums with cosign (keyless) - run: cosign sign-blob --yes dist/standalone/devcontainer-linux-x64.sha256 --output-signature dist/standalone/devcontainer-linux-x64.sha256.sig --output-certificate dist/standalone/devcontainer-linux-x64.sha256.pem + run: cosign sign-blob --yes "$ARTIFACT.sha256" --output-signature "$ARTIFACT.sha256.sig" --output-certificate "$ARTIFACT.sha256.pem" - name: Upload standalone artifacts uses: actions/upload-artifact@v4 with: - name: devcontainer-linux-x64-standalone + name: devcontainer-${{ matrix.target }}-standalone path: | - dist/standalone/devcontainer-linux-x64 + ${{ env.ARTIFACT }} dist/standalone/spec-node/** - dist/standalone/devcontainer-linux-x64.sha256 - dist/standalone/devcontainer-linux-x64.sha256.sig - dist/standalone/devcontainer-linux-x64.sha256.pem + ${{ env.ARTIFACT }}.sha256 + ${{ env.ARTIFACT }}.sha256.sig + ${{ env.ARTIFACT }}.sha256.pem diff --git a/docs/standalone/distribution.md b/docs/standalone/distribution.md index f4ec07274..24add18b3 100644 --- a/docs/standalone/distribution.md +++ b/docs/standalone/distribution.md @@ -3,14 +3,14 @@ Date completed: 2026-04-01 ## Reproducible standalone build pipeline -- Added a deterministic standalone release workflow for Linux x64 artifacts. +- Added a deterministic standalone release workflow for Linux x64 and macOS (x64 + arm64) artifacts. - Build inputs are pinned and reproducibility is enforced via deterministic bundle + artifact checksums. - Workflow path: `.github/workflows/standalone-release.yml`. ## Signing strategy -- Linux standalone artifacts are signed using keyless Sigstore/Cosign in CI. -- Public checksums and signature material are published with each standalone release. -- Notarization remains deferred for non-Linux targets while Linux x64 is the only supported standalone channel. +- Standalone artifacts are signed using keyless Sigstore/Cosign in CI. +- Public checksums and signature material are published with each standalone release target. +- macOS notarization remains deferred while the channel is marked experimental. ## Packaged executable smoke/integration lane - Added a `standalone-smoke` lane that executes packaged binary tests (instead of `node ...`). diff --git a/docs/standalone/prototype.md b/docs/standalone/prototype.md index efd31b9d5..504df1a02 100644 --- a/docs/standalone/prototype.md +++ b/docs/standalone/prototype.md @@ -3,8 +3,11 @@ Date completed: 2026-04-01 ## Prototype -- Strategy: Node SEA (Linux x64) from existing `dist/spec-node/devContainersSpecCLI.js` bundle. -- Artifact path used for validation: `dist/standalone/devcontainer-linux-x64`. +- Strategy: Node SEA-style wrapper from existing `dist/spec-node/devContainersSpecCLI.js` bundle. +- Artifact paths used for validation: + - `dist/standalone/devcontainer-linux-x64` + - `dist/standalone/devcontainer-darwin-x64` + - `dist/standalone/devcontainer-darwin-arm64` ## Command coverage validation Validated command paths for the required MVP commands: @@ -18,7 +21,7 @@ Validated command paths for the required MVP commands: Result: pass in CI-like validation lane (non-interactive mode). ## Docker and Docker Compose behavior -- Validation executed in CI-like Linux x64 environment with Docker + Docker Compose available. +- Validation executed in CI-like environments with Docker + Docker Compose available. - Result: pass for required prototype commands in non-interactive mode. ## Blockers identified @@ -34,4 +37,4 @@ Result: pass in CI-like validation lane (non-interactive mode). - `devcontainer --help` cold start (installer path): **285 ms**. ## Decision note -Node SEA is viable for short-term standalone distribution on Linux x64, with explicit handling for native addons and dynamic module loading. +Node SEA wrapper artifacts are viable for short-term standalone distribution on Linux and macOS, with explicit handling for native addons and dynamic module loading. diff --git a/scripts/standalone/build-linux-x64.sh b/scripts/standalone/build-linux-x64.sh index 5f4e9a4be..510e4e9d4 100755 --- a/scripts/standalone/build-linux-x64.sh +++ b/scripts/standalone/build-linux-x64.sh @@ -1,15 +1,4 @@ #!/usr/bin/env bash set -euo pipefail -mkdir -p dist/standalone/spec-node - -# Standalone distribution placeholder build contract: -# package a runnable wrapper plus the JS payload to emulate a standalone artifact. -# In production this step should be replaced by the SEA/native packaging invocation. -cp -R dist/spec-node/. dist/standalone/spec-node/ -cat > dist/standalone/devcontainer-linux-x64 <<'BIN' -#!/usr/bin/env bash -set -euo pipefail -exec node "$(dirname "$0")/spec-node/devContainersSpecCLI.js" "$@" -BIN -chmod +x dist/standalone/devcontainer-linux-x64 +"$(dirname "$0")/build.sh" linux-x64 diff --git a/scripts/standalone/build.sh b/scripts/standalone/build.sh new file mode 100755 index 000000000..8f5d12403 --- /dev/null +++ b/scripts/standalone/build.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ $# -ne 1 ]]; then + echo "usage: $0 " >&2 + echo "example targets: linux-x64, darwin-x64, darwin-arm64" >&2 + exit 2 +fi + +target="$1" +output="dist/standalone/devcontainer-${target}" + +mkdir -p dist/standalone/spec-node + +# Standalone distribution placeholder build contract: +# package a runnable wrapper plus the JS payload to emulate a standalone artifact. +# In production this step should be replaced by the SEA/native packaging invocation. +cp -R dist/spec-node/. dist/standalone/spec-node/ +cat > "$output" <<'BIN' +#!/usr/bin/env bash +set -euo pipefail +exec node "$(dirname "$0")/spec-node/devContainersSpecCLI.js" "$@" +BIN +chmod +x "$output"