Files
gstack/.github/workflows/release-artifacts.yml
T
Sinabina dbf94804ad fix: make runtime release six-platform safe
Use an allowed local file URL for the browser smoke test, centralize keyless archive signing after native builds so Windows ARM64 does not require an unavailable Cosign binary, and advance the immutable bootstrap channel to RC2.
2026-07-20 16:58:31 -07:00

202 lines
8.8 KiB
YAML

name: Release runtime artifacts
on:
push:
tags:
- v2.0.0
- v2.0.0-rc.*
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
- 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
smoke_url=$(node -e 'const fs=require("fs"),p=require("path").join(process.env.RUNNER_TEMP,"gstack-runtime-smoke.html");fs.writeFileSync(p,"<!doctype html><title>GStack runtime smoke</title>\n");process.stdout.write(require("url").pathToFileURL(p).href)')
PATH="$clean_path" GSTACK_NODE="$node_command" BROWSE_PARENT_PID=0 \
"$GSTACK_HOME/bin/browse" goto "$smoke_url"
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: 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: 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: Create strict six-target manifest
run: node .github/scripts/create-runtime-release-manifest.mjs release-output "$GITHUB_REPOSITORY" 2.0.0 "$GITHUB_REF_NAME"
- 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 }}
PRERELEASE_FLAG: ${{ contains(github.ref_name, '-rc.') && '--prerelease' || '' }}
run: |
set -euo pipefail
gh release create "$GITHUB_REF_NAME" \
--verify-tag \
$PRERELEASE_FLAG \
--title "GStack runtime $GITHUB_REF_NAME" \
--notes "Signed optional runtime artifacts for the six portable GStack skills." \
release-output/*
shell: bash