feat: componentize GStack 2 runtime and release integrity

This commit is contained in:
Sinabina
2026-07-20 14:16:23 -07:00
parent b0ea2296d1
commit f14445bb00
270 changed files with 9681 additions and 51572 deletions
+6 -2
View File
@@ -1,8 +1,12 @@
name: Workflow Lint
on: [push, pull_request]
permissions:
contents: read
jobs:
actionlint:
runs-on: ubicloud-standard-8
steps:
- uses: actions/checkout@v4
- uses: rhysd/actionlint@v1.7.11
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: rhysd/actionlint@393031adb9afb225ee52ae2ccd7a5af5525e03e8 # v1.7.11
+6 -3
View File
@@ -1,4 +1,7 @@
name: Build CI Image
permissions:
contents: read
on:
# Rebuild weekly (Monday 6am UTC) to pick up CLI updates
schedule:
@@ -20,18 +23,18 @@ jobs:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
# Copy lockfile + package.json into Docker build context
- run: cp package.json bun.lock .github/docker/
- uses: docker/login-action@v3
- uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v6
- uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: .github/docker
file: .github/docker/Dockerfile.ci
+8 -5
View File
@@ -1,4 +1,7 @@
name: Periodic Evals
permissions:
contents: read
on:
schedule:
- cron: '0 6 * * 1' # Monday 6 AM UTC
@@ -22,12 +25,12 @@ jobs:
outputs:
image-tag: ${{ steps.meta.outputs.tag }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- id: meta
run: echo "tag=${{ env.IMAGE }}:${{ hashFiles('.github/docker/Dockerfile.ci', 'package.json', 'bun.lock') }}" >> "$GITHUB_OUTPUT"
- uses: docker/login-action@v3
- uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -46,7 +49,7 @@ jobs:
run: cp package.json bun.lock .github/docker/
- if: steps.check.outputs.exists == 'false'
uses: docker/build-push-action@v6
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: .github/docker
file: .github/docker/Dockerfile.ci
@@ -88,7 +91,7 @@ jobs:
- name: e2e-gemini
file: test/gemini-e2e.test.ts
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
@@ -126,7 +129,7 @@ jobs:
- name: Upload eval results
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: eval-periodic-${{ matrix.suite.name }}
path: ~/.gstack-dev/evals/*.json
+14 -8
View File
@@ -1,4 +1,7 @@
name: E2E Evals
permissions:
contents: read
on:
pull_request:
branches: [main]
@@ -15,6 +18,8 @@ env:
jobs:
# Build Docker image with pre-baked toolchain (cached — only rebuilds on Dockerfile/lockfile change)
build-image:
# Paid secrets and package-write credentials never run on fork code.
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubicloud-standard-8
permissions:
contents: read
@@ -22,12 +27,12 @@ jobs:
outputs:
image-tag: ${{ steps.meta.outputs.tag }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- id: meta
run: echo "tag=${{ env.IMAGE }}:${{ hashFiles('.github/docker/Dockerfile.ci', 'package.json', 'bun.lock') }}" >> "$GITHUB_OUTPUT"
- uses: docker/login-action@v3
- uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -46,7 +51,7 @@ jobs:
run: cp package.json bun.lock .github/docker/
- if: steps.check.outputs.exists == 'false'
uses: docker/build-push-action@v6
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: .github/docker
file: .github/docker/Dockerfile.ci
@@ -56,6 +61,7 @@ jobs:
${{ env.IMAGE }}:latest
evals:
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ${{ matrix.suite.runner || 'ubicloud-standard-8' }}
needs: build-image
container:
@@ -105,7 +111,7 @@ jobs:
file: test/skill-e2e-office-hours-auto-mode.test.ts test/skill-e2e-plan-mode-no-op.test.ts
timeout: 35
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
@@ -254,7 +260,7 @@ jobs:
- name: Upload eval results
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: eval-${{ matrix.suite.name }}
path: ~/.gstack-dev/evals/*.json
@@ -263,7 +269,7 @@ jobs:
report:
runs-on: ubicloud-standard-8
needs: evals
if: always() && github.event_name == 'pull_request'
if: always() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
timeout-minutes: 5
permissions:
contents: read
@@ -275,12 +281,12 @@ jobs:
# early and never hit it, which is why this stayed hidden). See #1802 CI fix.
issues: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 1
- name: Download all eval artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
pattern: eval-*
path: /tmp/eval-results
+19 -2
View File
@@ -30,7 +30,7 @@ jobs:
bun-version: 1.3.14
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22
node-version: 22.23.1
- name: Configure isolated test identity
run: |
git config --global user.email "gstack2-ci@example.invalid"
@@ -56,10 +56,27 @@ jobs:
bun-version: 1.3.14
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22
node-version: 22.23.1
- run: bun install --frozen-lockfile
- run: bun run test:gstack2:install
installed-first-use:
name: Installed six-skill first use
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.3.14
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22.23.1
- run: bun install --frozen-lockfile --ignore-scripts
- run: bun run gen:gstack2
- name: Prove consent-first capability setup continues after approval
run: bun test --timeout 30000 test/gstack2-installation.test.ts test/gstack2-skill-ux.test.ts
dev-container:
runs-on: ubuntu-24.04
timeout-minutes: 25
+6 -3
View File
@@ -1,4 +1,7 @@
name: make-pdf copy-paste gate
permissions:
contents: read
on:
pull_request:
branches: [main]
@@ -36,11 +39,11 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: oven-sh/setup-bun@v2
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: latest
bun-version: 1.3.14
- name: Install dependencies
run: bun install --frozen-lockfile
+4 -1
View File
@@ -1,5 +1,8 @@
name: PR Title Sync
permissions:
contents: read
# WHY pull_request_target (not pull_request): the default GITHUB_TOKEN is
# READ-ONLY on fork PRs under `pull_request`, so the title-sync backstop could
# never `gh pr edit` a fork/agent PR. `pull_request_target` runs in the base-repo
@@ -39,7 +42,7 @@ jobs:
steps:
# Base repo only — trusted infra (the rewrite helper). No PR-head checkout.
- name: Checkout base repo (trusted)
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 1
+75
View File
@@ -0,0 +1,75 @@
name: Release quality gate
on:
pull_request:
branches: [main]
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
jobs:
quality:
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.3.14
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22.23.1
- name: Install frozen dependencies
run: bun install --frozen-lockfile --ignore-scripts
- name: Release and package contract tests
run: bun run check:release
- name: Scan changed production text for credentials
env:
BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }}
HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
run: |
set -euo pipefail
if ! git cat-file -e "${BASE_SHA}^{commit}" 2>/dev/null; then
BASE_SHA=$(git rev-parse HEAD^)
fi
git diff --unified=0 --no-color "$BASE_SHA" "$HEAD_SHA" -- \
. \
':(exclude)test/**' \
':(exclude)evals/**' \
':(exclude)outputs/**' \
':(exclude)lib/diagram-render/dist/**' \
':(exclude)skills/*/references/support/**' \
':(exclude)docs/gstack-2/BACKLOG-MAP.json' \
| node .github/scripts/gate-secret-scan.mjs
- name: Pack, install, and invoke the npm runtime-control package
run: node .github/scripts/smoke-packed-package.mjs "$GITHUB_WORKSPACE"
- name: Gate critical dependency advisories
run: bun audit --audit-level=critical
- name: Syntax-check runtime JavaScript
run: find runtime -type f \( -name '*.js' -o -name '*.mjs' \) -print0 | xargs -0 -n1 node --check
- name: Install ShellCheck
run: |
sudo apt-get update
sudo apt-get install -y shellcheck=0.9.0-1
shellcheck --version
- name: ShellCheck release and setup boundaries
run: >-
shellcheck --severity=error
setup
scripts/build.sh
scripts/write-version-files.sh
scripts/gstack2/runtime-install-smoke.sh
browse/scripts/build-node-server.sh
+198
View File
@@ -0,0 +1,198 @@
name: Release runtime artifacts
on:
push:
tags: [v2.0.0]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: runtime-release-${{ github.ref }}
cancel-in-progress: false
jobs:
build:
name: Build ${{ matrix.target }}
permissions:
contents: read
id-token: write
attestations: write
strategy:
fail-fast: false
matrix:
include:
- os: macos-15
target: darwin-arm64
capabilities: browser,browser-visible,design,pdf,diagram,ios
- os: macos-15-intel
target: darwin-x64
capabilities: browser,browser-visible,design,pdf,diagram,ios
- os: ubuntu-24.04-arm
target: linux-arm64
capabilities: browser,browser-visible,design,pdf,diagram
- os: ubuntu-24.04
target: linux-x64
capabilities: browser,browser-visible,design,pdf,diagram
- os: windows-11-arm
target: windows-arm64
capabilities: browser,browser-visible,design,pdf,diagram
- os: windows-2025
target: windows-x64
capabilities: browser,browser-visible,design,pdf,diagram
runs-on: ${{ matrix.os }}
timeout-minutes: 35
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.3.14
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22.23.1
- uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # v3.10.0
- name: Install frozen dependencies
run: bun install --frozen-lockfile --ignore-scripts
shell: bash
- name: Build and stage the complete managed runtime
env:
GSTACK_HOME: ${{ runner.temp }}/gstack-release-home
TARGET: ${{ matrix.target }}
CAPABILITIES: ${{ matrix.capabilities }}
run: |
set -euo pipefail
node runtime/install.js \
--source "$GITHUB_WORKSPACE" \
--home "$GSTACK_HOME" \
--version 2.0.0 \
--install-now \
--yes \
--capabilities "$CAPABILITIES"
active_slot=$(node -e 'const fs=require("fs"),p=process.argv[1];const v=JSON.parse(fs.readFileSync(p,"utf8")).current;if(typeof v!=="string"||!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(v))process.exit(1);process.stdout.write(v)' "$GSTACK_HOME/versions/current.json")
active="$GSTACK_HOME/versions/$active_slot"
test -f "$active/.gstack-bundle.json"
case "$TARGET" in
windows-*) managed_bun_rel=".gstack-runtime-tools/bun.exe" ;;
*) managed_bun_rel=".gstack-runtime-tools/bun" ;;
esac
managed_bun="$active/$managed_bun_rel"
test -f "$managed_bun"
test "$("$managed_bun" --version)" = "1.3.14"
test -f "$active/runtime/licenses/BUN-LICENSE-1.3.14.md"
test -f "$active/runtime/licenses/BUN-SOURCE.md"
node -e 'const fs=require("fs"),c=require("crypto"),root=process.argv[1],rel=process.argv[2];const m=JSON.parse(fs.readFileSync(root+"/.gstack-bundle.json","utf8"));if(m.tools?.bun?.path!==rel||m.tools?.bun?.version!=="1.3.14")process.exit(1);const license=fs.readFileSync(root+"/runtime/licenses/BUN-LICENSE-1.3.14.md");if(c.createHash("sha256").update(license).digest("hex")!=="2cb858b2db8fc793bca2093489c5bc8eee615d002cc4924254904044c27a0afa")process.exit(1)' "$active" "$managed_bun_rel"
test -d "$active/.gstack-runtime-browsers"
(
cd "$active"
PLAYWRIGHT_BROWSERS_PATH="$active/.gstack-runtime-browsers" \
node --input-type=module --eval \
'const { chromium } = await import("./node_modules/playwright/index.mjs"); for (const options of [{ headless: true }, { headless: true, channel: "chromium" }]) { const browser = await chromium.launch(options); await browser.close(); }'
)
node_command=$(node -p 'process.execPath')
host_bun=$(command -v bun)
host_bun_dir=$(cd "$(dirname "$host_bun")" && pwd -P)
clean_path=""
IFS=: read -r -a path_parts <<< "$PATH"
for part in "${path_parts[@]}"; do
physical=$(cd "$part" 2>/dev/null && pwd -P || printf '%s' "$part")
if [ "$physical" != "$host_bun_dir" ]; then
clean_path="${clean_path:+$clean_path:}$part"
fi
done
if (PATH="$clean_path"; command -v bun >/dev/null 2>&1); then
echo "Host-global Bun remained available after removing setup-bun from PATH" >&2
exit 1
fi
test "$(PATH="$clean_path" GSTACK_NODE="$node_command" "$GSTACK_HOME/bin/bun" --version)" = "1.3.14"
browser_cleanup() {
PATH="$clean_path" GSTACK_NODE="$node_command" BROWSE_PARENT_PID=0 \
"$GSTACK_HOME/bin/browse" stop >/dev/null 2>&1 || true
}
trap browser_cleanup EXIT
PATH="$clean_path" GSTACK_NODE="$node_command" BROWSE_PARENT_PID=0 \
"$GSTACK_HOME/bin/browse" goto about:blank
PATH="$clean_path" GSTACK_NODE="$node_command" BROWSE_PARENT_PID=0 \
"$GSTACK_HOME/bin/browse" status
browser_cleanup
trap - EXIT
stage="$RUNNER_TEMP/runtime-components"
mkdir -p "$stage" "$GITHUB_WORKSPACE/release-output"
node .github/scripts/stage-runtime-components.mjs "$active" "$stage"
for component_dir in "$stage"/*; do
test -d "$component_dir" || continue
component=$(basename "$component_dir")
archive="$GITHUB_WORKSPACE/release-output/gstack-runtime-2.0.0-$TARGET-$component.tar.gz"
tar -czf "$archive" -C "$component_dir" gstack
node -e 'const fs=require("fs"),c=require("crypto"),p=process.argv[1];const b=fs.readFileSync(p);fs.writeFileSync(p+".sha256",c.createHash("sha256").update(b).digest("hex")+" "+require("path").basename(p)+"\n")' "$archive"
done
shell: bash
- name: Keyless-sign component archives
run: |
set -euo pipefail
for archive in release-output/*.tar.gz; do
cosign sign-blob --yes --bundle "$archive.sigstore.json" "$archive"
done
shell: bash
- name: Attest component archive provenance
uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2
with:
subject-path: release-output/*.tar.gz
- name: Upload signed archive
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: runtime-${{ matrix.target }}
path: release-output/*
if-no-files-found: error
retention-days: 14
manifest:
name: Assemble manifest and GitHub Release
needs: build
runs-on: ubuntu-24.04
if: startsWith(github.ref, 'refs/tags/v')
permissions:
contents: write
id-token: write
attestations: write
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
pattern: runtime-*
path: release-output
merge-multiple: true
- uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # v3.10.0
- name: Create strict six-target manifest
run: node .github/scripts/create-runtime-release-manifest.mjs release-output "$GITHUB_REPOSITORY" 2.0.0
- name: Checksum and keyless-sign manifest
run: |
set -euo pipefail
cd release-output
sha256sum gstack-runtime-manifest.json > gstack-runtime-manifest.json.sha256
cosign sign-blob --yes --bundle gstack-runtime-manifest.json.sigstore.json gstack-runtime-manifest.json
shell: bash
- name: Attest manifest provenance
uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2
with:
subject-path: release-output/gstack-runtime-manifest.json
- name: Publish immutable release assets
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
gh release create "$GITHUB_REF_NAME" \
--verify-tag \
--title "GStack runtime 2.0.0" \
--notes "Signed optional runtime artifacts for the six portable GStack skills." \
release-output/*
shell: bash
+6 -2
View File
@@ -1,11 +1,15 @@
name: Skill Docs Freshness
on: [push, pull_request]
permissions:
contents: read
jobs:
check-freshness:
runs-on: ubicloud-standard-8
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
- run: bun install
- name: Check Claude host freshness
run: bun run gen:skill-docs
+5 -2
View File
@@ -1,5 +1,8 @@
name: Version Gate
permissions:
contents: read
on:
pull_request:
paths:
@@ -20,13 +23,13 @@ jobs:
pull-requests: read
steps:
- name: Checkout PR head
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
- name: Setup Bun
uses: oven-sh/setup-bun@v2
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
- name: Read versions
id: versions
+6 -3
View File
@@ -1,5 +1,8 @@
name: Windows Free Tests
permissions:
contents: read
# Curated subset of the free test suite that runs on a paid faster Windows runner.
#
# Codex's v1.18.0.0 review flagged that the existing evals.yml workflow uses
@@ -39,11 +42,11 @@ jobs:
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: oven-sh/setup-bun@v1
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: latest
bun-version: 1.3.14
- name: Configure git identity (required by tests that init temp repos)
run: |
+38 -39
View File
@@ -1,10 +1,12 @@
name: Windows Setup E2E
# End-to-end fresh-install gate for Windows. Runs `./setup` on a clean
# windows-latest checkout and asserts the build completes, binaries
# resolve via find-browse, and the gstack-paths state root resolves
# cleanly. Catches Bun shell-parser regressions in package.json's build
# chain (#1538, #1537, #1530, #1457, #1561) before they reach users.
permissions:
contents: read
# End-to-end optional-runtime gate for Windows. It first proves dry-run is
# non-mutating, then performs an explicit browser-capability install, checks
# doctor and the native artifact, and uninstalls. Agent Skills host placement
# is deliberately outside this workflow and is covered by gstack2-gate.yml.
#
# Separate from windows-free-tests.yml because that one runs a curated
# unit-test subset; this one exercises the install path itself.
@@ -19,6 +21,7 @@ on:
- 'scripts/build.sh'
- 'scripts/write-version-files.sh'
- 'setup'
- 'runtime/**'
- 'browse/src/cli.ts'
- 'browse/src/find-browse.ts'
- 'bin/gstack-paths'
@@ -35,11 +38,18 @@ jobs:
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: oven-sh/setup-bun@v1
- name: Configure isolated runtime home
run: echo "GSTACK_HOME=$RUNNER_TEMP/gstack-setup-e2e" >> "$GITHUB_ENV"
shell: bash
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: latest
bun-version: 1.3.14
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22.23.1
- name: Configure git identity
run: |
@@ -52,45 +62,34 @@ jobs:
run: bun install --frozen-lockfile
shell: bash
- name: Run bun run build (the previously-broken path)
# This is the regression gate. Bun's Windows shell parser rejected
# multiple constructs the old inline build chain used; the wave
# moved the build to scripts/build.sh. If this step fails on
# Windows, the build chain regressed.
run: bun run build
shell: bash
env:
GSTACK_SKIP_PLAYWRIGHT: '1'
- name: Verify binaries exist (with .exe extension on Windows)
- name: Preview without mutating state
run: |
set -e
test -f browse/dist/browse.exe || test -f browse/dist/browse || (echo "MISSING: browse" && exit 1)
test -f browse/dist/find-browse.exe || test -f browse/dist/find-browse || (echo "MISSING: find-browse" && exit 1)
test -f design/dist/design.exe || test -f design/dist/design || (echo "MISSING: design" && exit 1)
test -f bin/gstack-global-discover.exe || test -f bin/gstack-global-discover || (echo "MISSING: gstack-global-discover" && exit 1)
echo "All binaries present"
bash ./setup --dry-run --capabilities browser
test ! -e "$GSTACK_HOME" || (echo "dry-run mutated GSTACK_HOME" && exit 1)
shell: bash
- name: Verify find-browse resolves to the .exe variant
- name: Explicitly install the browser capability
run: |
set -e
OUT=$(bun browse/src/find-browse.ts 2>&1) || true
echo "find-browse output: $OUT"
# On Windows, find-browse should successfully resolve to a binary,
# whether or not it has the .exe extension on disk. Empty output
# or "not found" means the .exe extension resolver regressed.
echo "$OUT" | grep -qE '(browse\.exe|browse)$' || (echo "find-browse failed to resolve binary on Windows" && exit 1)
bash ./setup --install-now --yes --capabilities browser
test -f "$GSTACK_HOME/versions/current.json"
test -f "$GSTACK_HOME/bin/gstack.cmd"
shell: bash
- name: Verify gstack-paths state root resolves
- name: Verify doctor and installed native browser
run: |
set -e
eval "$(bash bin/gstack-paths)"
test -n "$GSTACK_STATE_ROOT" || (echo "GSTACK_STATE_ROOT empty" && exit 1)
test -n "$PLAN_ROOT" || (echo "PLAN_ROOT empty" && exit 1)
test -n "$TMP_ROOT" || (echo "TMP_ROOT empty" && exit 1)
echo "GSTACK_STATE_ROOT=$GSTACK_STATE_ROOT"
echo "PLAN_ROOT=$PLAN_ROOT"
echo "TMP_ROOT=$TMP_ROOT"
node runtime/cli.js doctor --json > doctor.json
node -e 'const r=require("./doctor.json");if(!r.checks.some(x=>x.id==="managed-runtime"&&x.status==="pass"))process.exit(1);if(!r.checks.some(x=>x.id==="capability:browser"&&x.status==="pass"))process.exit(1)'
browser=$(node runtime/cli.js runtime path browse/dist/browse.exe)
test -f "$browser"
shell: bash
- name: Uninstall the runtime and preserve user state
run: |
set -e
node runtime/cli.js uninstall
test ! -e "$GSTACK_HOME/versions/current.json"
test -d "$GSTACK_HOME/projects"
shell: bash