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.
This commit is contained in:
Sinabina
2026-07-20 16:58:31 -07:00
parent b0047cc525
commit dbf94804ad
10 changed files with 35 additions and 21 deletions
+10 -11
View File
@@ -53,8 +53,6 @@ jobs:
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with: with:
node-version: 22.23.1 node-version: 22.23.1
- uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # v3.10.0
- name: Install frozen dependencies - name: Install frozen dependencies
run: bun install --frozen-lockfile --ignore-scripts run: bun install --frozen-lockfile --ignore-scripts
shell: bash shell: bash
@@ -114,8 +112,9 @@ jobs:
"$GSTACK_HOME/bin/browse" stop >/dev/null 2>&1 || true "$GSTACK_HOME/bin/browse" stop >/dev/null 2>&1 || true
} }
trap browser_cleanup EXIT 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 \ PATH="$clean_path" GSTACK_NODE="$node_command" BROWSE_PARENT_PID=0 \
"$GSTACK_HOME/bin/browse" goto about:blank "$GSTACK_HOME/bin/browse" goto "$smoke_url"
PATH="$clean_path" GSTACK_NODE="$node_command" BROWSE_PARENT_PID=0 \ PATH="$clean_path" GSTACK_NODE="$node_command" BROWSE_PARENT_PID=0 \
"$GSTACK_HOME/bin/browse" status "$GSTACK_HOME/bin/browse" status
browser_cleanup browser_cleanup
@@ -132,14 +131,6 @@ jobs:
done done
shell: bash 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 - name: Attest component archive provenance
uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2 uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2
with: with:
@@ -171,6 +162,14 @@ jobs:
merge-multiple: true merge-multiple: true
- uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # v3.10.0 - 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 - 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" run: node .github/scripts/create-runtime-release-manifest.mjs release-output "$GITHUB_REPOSITORY" 2.0.0 "$GITHUB_REF_NAME"
+1 -1
View File
@@ -16,7 +16,7 @@ export const BOOTSTRAP_RUNTIME_VERSION = "2.0.0";
// Keep the runtime compatibility version separate from the immutable release // Keep the runtime compatibility version separate from the immutable release
// channel. Release candidates carry the 2.0.0 runtime contract while letting // channel. Release candidates carry the 2.0.0 runtime contract while letting
// fresh-machine production journeys run before the stable v2.0.0 tag exists. // fresh-machine production journeys run before the stable v2.0.0 tag exists.
export const BOOTSTRAP_RELEASE_TAG = "v2.0.0-rc.1"; export const BOOTSTRAP_RELEASE_TAG = "v2.0.0-rc.2";
export const OFFICIAL_MANIFEST_URL = export const OFFICIAL_MANIFEST_URL =
`https://github.com/time-attack/gstack/releases/download/${BOOTSTRAP_RELEASE_TAG}/gstack-runtime-manifest.json`; `https://github.com/time-attack/gstack/releases/download/${BOOTSTRAP_RELEASE_TAG}/gstack-runtime-manifest.json`;
const CAPABILITIES = new Set(["browser", "browser-visible", "design", "pdf", "diagram", "ios"]); const CAPABILITIES = new Set(["browser", "browser-visible", "design", "pdf", "diagram", "ios"]);
@@ -16,7 +16,7 @@ export const BOOTSTRAP_RUNTIME_VERSION = "2.0.0";
// Keep the runtime compatibility version separate from the immutable release // Keep the runtime compatibility version separate from the immutable release
// channel. Release candidates carry the 2.0.0 runtime contract while letting // channel. Release candidates carry the 2.0.0 runtime contract while letting
// fresh-machine production journeys run before the stable v2.0.0 tag exists. // fresh-machine production journeys run before the stable v2.0.0 tag exists.
export const BOOTSTRAP_RELEASE_TAG = "v2.0.0-rc.1"; export const BOOTSTRAP_RELEASE_TAG = "v2.0.0-rc.2";
export const OFFICIAL_MANIFEST_URL = export const OFFICIAL_MANIFEST_URL =
`https://github.com/time-attack/gstack/releases/download/${BOOTSTRAP_RELEASE_TAG}/gstack-runtime-manifest.json`; `https://github.com/time-attack/gstack/releases/download/${BOOTSTRAP_RELEASE_TAG}/gstack-runtime-manifest.json`;
const CAPABILITIES = new Set(["browser", "browser-visible", "design", "pdf", "diagram", "ios"]); const CAPABILITIES = new Set(["browser", "browser-visible", "design", "pdf", "diagram", "ios"]);
@@ -16,7 +16,7 @@ export const BOOTSTRAP_RUNTIME_VERSION = "2.0.0";
// Keep the runtime compatibility version separate from the immutable release // Keep the runtime compatibility version separate from the immutable release
// channel. Release candidates carry the 2.0.0 runtime contract while letting // channel. Release candidates carry the 2.0.0 runtime contract while letting
// fresh-machine production journeys run before the stable v2.0.0 tag exists. // fresh-machine production journeys run before the stable v2.0.0 tag exists.
export const BOOTSTRAP_RELEASE_TAG = "v2.0.0-rc.1"; export const BOOTSTRAP_RELEASE_TAG = "v2.0.0-rc.2";
export const OFFICIAL_MANIFEST_URL = export const OFFICIAL_MANIFEST_URL =
`https://github.com/time-attack/gstack/releases/download/${BOOTSTRAP_RELEASE_TAG}/gstack-runtime-manifest.json`; `https://github.com/time-attack/gstack/releases/download/${BOOTSTRAP_RELEASE_TAG}/gstack-runtime-manifest.json`;
const CAPABILITIES = new Set(["browser", "browser-visible", "design", "pdf", "diagram", "ios"]); const CAPABILITIES = new Set(["browser", "browser-visible", "design", "pdf", "diagram", "ios"]);
@@ -16,7 +16,7 @@ export const BOOTSTRAP_RUNTIME_VERSION = "2.0.0";
// Keep the runtime compatibility version separate from the immutable release // Keep the runtime compatibility version separate from the immutable release
// channel. Release candidates carry the 2.0.0 runtime contract while letting // channel. Release candidates carry the 2.0.0 runtime contract while letting
// fresh-machine production journeys run before the stable v2.0.0 tag exists. // fresh-machine production journeys run before the stable v2.0.0 tag exists.
export const BOOTSTRAP_RELEASE_TAG = "v2.0.0-rc.1"; export const BOOTSTRAP_RELEASE_TAG = "v2.0.0-rc.2";
export const OFFICIAL_MANIFEST_URL = export const OFFICIAL_MANIFEST_URL =
`https://github.com/time-attack/gstack/releases/download/${BOOTSTRAP_RELEASE_TAG}/gstack-runtime-manifest.json`; `https://github.com/time-attack/gstack/releases/download/${BOOTSTRAP_RELEASE_TAG}/gstack-runtime-manifest.json`;
const CAPABILITIES = new Set(["browser", "browser-visible", "design", "pdf", "diagram", "ios"]); const CAPABILITIES = new Set(["browser", "browser-visible", "design", "pdf", "diagram", "ios"]);
@@ -16,7 +16,7 @@ export const BOOTSTRAP_RUNTIME_VERSION = "2.0.0";
// Keep the runtime compatibility version separate from the immutable release // Keep the runtime compatibility version separate from the immutable release
// channel. Release candidates carry the 2.0.0 runtime contract while letting // channel. Release candidates carry the 2.0.0 runtime contract while letting
// fresh-machine production journeys run before the stable v2.0.0 tag exists. // fresh-machine production journeys run before the stable v2.0.0 tag exists.
export const BOOTSTRAP_RELEASE_TAG = "v2.0.0-rc.1"; export const BOOTSTRAP_RELEASE_TAG = "v2.0.0-rc.2";
export const OFFICIAL_MANIFEST_URL = export const OFFICIAL_MANIFEST_URL =
`https://github.com/time-attack/gstack/releases/download/${BOOTSTRAP_RELEASE_TAG}/gstack-runtime-manifest.json`; `https://github.com/time-attack/gstack/releases/download/${BOOTSTRAP_RELEASE_TAG}/gstack-runtime-manifest.json`;
const CAPABILITIES = new Set(["browser", "browser-visible", "design", "pdf", "diagram", "ios"]); const CAPABILITIES = new Set(["browser", "browser-visible", "design", "pdf", "diagram", "ios"]);
@@ -16,7 +16,7 @@ export const BOOTSTRAP_RUNTIME_VERSION = "2.0.0";
// Keep the runtime compatibility version separate from the immutable release // Keep the runtime compatibility version separate from the immutable release
// channel. Release candidates carry the 2.0.0 runtime contract while letting // channel. Release candidates carry the 2.0.0 runtime contract while letting
// fresh-machine production journeys run before the stable v2.0.0 tag exists. // fresh-machine production journeys run before the stable v2.0.0 tag exists.
export const BOOTSTRAP_RELEASE_TAG = "v2.0.0-rc.1"; export const BOOTSTRAP_RELEASE_TAG = "v2.0.0-rc.2";
export const OFFICIAL_MANIFEST_URL = export const OFFICIAL_MANIFEST_URL =
`https://github.com/time-attack/gstack/releases/download/${BOOTSTRAP_RELEASE_TAG}/gstack-runtime-manifest.json`; `https://github.com/time-attack/gstack/releases/download/${BOOTSTRAP_RELEASE_TAG}/gstack-runtime-manifest.json`;
const CAPABILITIES = new Set(["browser", "browser-visible", "design", "pdf", "diagram", "ios"]); const CAPABILITIES = new Set(["browser", "browser-visible", "design", "pdf", "diagram", "ios"]);
@@ -16,7 +16,7 @@ export const BOOTSTRAP_RUNTIME_VERSION = "2.0.0";
// Keep the runtime compatibility version separate from the immutable release // Keep the runtime compatibility version separate from the immutable release
// channel. Release candidates carry the 2.0.0 runtime contract while letting // channel. Release candidates carry the 2.0.0 runtime contract while letting
// fresh-machine production journeys run before the stable v2.0.0 tag exists. // fresh-machine production journeys run before the stable v2.0.0 tag exists.
export const BOOTSTRAP_RELEASE_TAG = "v2.0.0-rc.1"; export const BOOTSTRAP_RELEASE_TAG = "v2.0.0-rc.2";
export const OFFICIAL_MANIFEST_URL = export const OFFICIAL_MANIFEST_URL =
`https://github.com/time-attack/gstack/releases/download/${BOOTSTRAP_RELEASE_TAG}/gstack-runtime-manifest.json`; `https://github.com/time-attack/gstack/releases/download/${BOOTSTRAP_RELEASE_TAG}/gstack-runtime-manifest.json`;
const CAPABILITIES = new Set(["browser", "browser-visible", "design", "pdf", "diagram", "ios"]); const CAPABILITIES = new Set(["browser", "browser-visible", "design", "pdf", "diagram", "ios"]);
@@ -57,10 +57,18 @@ describe("GStack runtime release channel", () => {
test("release workflow publishes both RC and stable tags through the same signed manifest path", async () => { test("release workflow publishes both RC and stable tags through the same signed manifest path", async () => {
const workflow = await fs.readFile(WORKFLOW, "utf8"); const workflow = await fs.readFile(WORKFLOW, "utf8");
const buildSection = workflow.slice(workflow.indexOf(" build:"), workflow.indexOf("\n manifest:"));
const manifestSection = workflow.slice(workflow.indexOf("\n manifest:"));
expect(workflow).toContain("v2.0.0-rc.*"); expect(workflow).toContain("v2.0.0-rc.*");
expect(workflow).toContain('2.0.0 "$GITHUB_REF_NAME"'); expect(workflow).toContain('2.0.0 "$GITHUB_REF_NAME"');
expect(workflow).toContain("PRERELEASE_FLAG:"); expect(workflow).toContain("PRERELEASE_FLAG:");
expect(workflow).toContain("--prerelease"); expect(workflow).toContain("--prerelease");
expect(workflow).toContain('gh release create "$GITHUB_REF_NAME"'); expect(workflow).toContain('gh release create "$GITHUB_REF_NAME"');
expect(workflow).toContain("pathToFileURL(p).href");
expect(workflow).not.toContain("goto about:blank");
expect(buildSection).not.toContain("sigstore/cosign-installer");
expect(manifestSection).toContain("sigstore/cosign-installer");
expect(manifestSection.indexOf("Keyless-sign component archives"))
.toBeLessThan(manifestSection.indexOf("Create strict six-target manifest"));
}); });
}); });
+10 -3
View File
@@ -53,14 +53,20 @@ describe("release and CI hardening", () => {
expect(workflow).toContain("versions/current.json"); expect(workflow).toContain("versions/current.json");
expect(workflow).not.toContain('active="$GSTACK_HOME/versions/2.0.0"'); expect(workflow).not.toContain('active="$GSTACK_HOME/versions/2.0.0"');
expect(workflow).toContain(".gstack-runtime-browsers"); expect(workflow).toContain(".gstack-runtime-browsers");
expect(workflow).toContain('chromium.launch({ headless: true, channel: "chromium" })'); // Exercise both the bundled browser and the explicit Chromium channel. Keep
// this semantic: the workflow intentionally loops over launch options so a
// harmless refactor does not invalidate release hardening.
expect(workflow).toMatch(/for \(const options of \[\{ headless: true \}, \{ headless: true, channel: ["']chromium["'] \}\]\)/);
expect(workflow).toContain("chromium.launch(options)");
expect(workflow).toContain("await browser.close()");
expect(workflow).not.toContain("--with-deps"); expect(workflow).not.toContain("--with-deps");
expect(workflow).toContain(".gstack-runtime-tools/bun"); expect(workflow).toContain(".gstack-runtime-tools/bun");
expect(workflow).toContain('"$GSTACK_HOME/bin/bun" --version'); expect(workflow).toContain('"$GSTACK_HOME/bin/bun" --version');
expect(workflow).toContain("BUN-LICENSE-1.3.14.md"); expect(workflow).toContain("BUN-LICENSE-1.3.14.md");
expect(workflow).toContain("command -v bun"); expect(workflow).toContain("command -v bun");
expect(workflow).toContain("GSTACK_NODE=\"$node_command\""); expect(workflow).toContain("GSTACK_NODE=\"$node_command\"");
expect(workflow).toContain("goto about:blank"); expect(workflow).toContain("pathToFileURL(p).href");
expect(workflow).not.toContain("goto about:blank");
const manifest = read(".github/scripts/create-runtime-release-manifest.mjs"); const manifest = read(".github/scripts/create-runtime-release-manifest.mjs");
expect(manifest).toContain("bytes: stat.size"); expect(manifest).toContain("bytes: stat.size");
expect(manifest).toContain('certificateOidcIssuer: "https://token.actions.githubusercontent.com"'); expect(manifest).toContain('certificateOidcIssuer: "https://token.actions.githubusercontent.com"');
@@ -80,7 +86,8 @@ describe("release and CI hardening", () => {
expect(installer).toContain('entry(managedBunRelativePath(), "managed-bun", true)'); expect(installer).toContain('entry(managedBunRelativePath(), "managed-bun", true)');
const browser = read("browse/src/cli.ts"); const browser = read("browse/src/cli.ts");
expect(browser).toContain("Every installed/compiled client must use the adjacent Node-compatible daemon"); expect(browser).toContain("Every installed/compiled client must use the adjacent Node-compatible daemon");
expect(browser).toContain("if (IS_COMPILED && !NODE_SERVER_SCRIPT)"); expect(browser).toContain("export function resolveServerLaunchTarget(");
expect(browser).toContain("server-node.mjs not found. Rebuild the managed browser runtime");
}); });
test("Windows setup lane installs, doctors, and uninstalls rather than only building", () => { test("Windows setup lane installs, doctors, and uninstalls rather than only building", () => {