mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-21 04:10:47 +02:00
fix: add production runtime RC release channel
Publish signed prerelease artifacts from v2.0.0-rc.* tags, bind bootstrap trust to the immutable RC tag, make missing-release errors actionable, and install the six public skills from the canonical subpath.
This commit is contained in:
@@ -29,6 +29,13 @@ afterEach(() => {
|
||||
});
|
||||
|
||||
describe('GStack 2 standard installer surface', () => {
|
||||
test('documents the canonical public subpath instead of the legacy-bearing repository root', () => {
|
||||
for (const file of ['AGENTS.md', 'CLAUDE.md', 'README.md']) {
|
||||
const content = fs.readFileSync(path.join(DEFAULT_REPO_ROOT, file), 'utf8');
|
||||
expect(content, file).toContain('npx skills add time-attack/gstack/skills');
|
||||
}
|
||||
});
|
||||
|
||||
test('publishes exactly six uniquely named canonical skills', () => {
|
||||
const result = inspectRepository(DEFAULT_REPO_ROOT);
|
||||
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { spawnSync } from "node:child_process";
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, "..");
|
||||
const SCRIPT = path.join(ROOT, ".github", "scripts", "create-runtime-release-manifest.mjs");
|
||||
const WORKFLOW = path.join(ROOT, ".github", "workflows", "release-artifacts.yml");
|
||||
const TARGETS = ["darwin-arm64", "darwin-x64", "linux-arm64", "linux-x64", "windows-arm64", "windows-x64"];
|
||||
const COMMON = ["core", "browser-code", "browser-headless", "browser-visible", "design", "diagram", "pdf"];
|
||||
|
||||
async function stageFixture(directory: string) {
|
||||
for (const target of TARGETS) {
|
||||
const components = [...COMMON, ...(target.startsWith("darwin-") ? ["ios"] : [])];
|
||||
for (const component of components) {
|
||||
const name = `gstack-runtime-2.0.0-${target}-${component}.tar.gz`;
|
||||
await fs.writeFile(path.join(directory, name), "fixture\n");
|
||||
await fs.writeFile(path.join(directory, `${name}.sha256`), `${"a".repeat(64)} ${name}\n`);
|
||||
await fs.writeFile(path.join(directory, `${name}.sigstore.json`), "{}\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe("GStack runtime release channel", () => {
|
||||
test("release candidates retain runtime compatibility while binding URLs and signatures to the RC tag", async () => {
|
||||
const directory = await fs.mkdtemp(path.join(os.tmpdir(), "gstack-runtime-release-channel-"));
|
||||
try {
|
||||
await stageFixture(directory);
|
||||
const result = spawnSync(process.execPath, [SCRIPT, directory, "time-attack/gstack", "2.0.0", "v2.0.0-rc.1"], {
|
||||
encoding: "utf8",
|
||||
});
|
||||
expect(result.status).toBe(0);
|
||||
const manifest = JSON.parse(await fs.readFile(path.join(directory, "gstack-runtime-manifest.json"), "utf8"));
|
||||
expect(manifest.version).toBe("2.0.0");
|
||||
expect(manifest.targets["darwin-arm64"].components["browser-visible"]).toMatchObject({
|
||||
url: "https://github.com/time-attack/gstack/releases/download/v2.0.0-rc.1/gstack-runtime-2.0.0-darwin-arm64-browser-visible.tar.gz",
|
||||
certificateIdentity: "https://github.com/time-attack/gstack/.github/workflows/release-artifacts.yml@refs/tags/v2.0.0-rc.1",
|
||||
});
|
||||
} finally {
|
||||
await fs.rm(directory, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("release manifest generation rejects non-runtime tags before reading artifacts", async () => {
|
||||
const directory = await fs.mkdtemp(path.join(os.tmpdir(), "gstack-runtime-invalid-tag-"));
|
||||
try {
|
||||
const result = spawnSync(process.execPath, [SCRIPT, directory, "time-attack/gstack", "2.0.0", "main"], {
|
||||
encoding: "utf8",
|
||||
});
|
||||
expect(result.status).not.toBe(0);
|
||||
expect(`${result.stdout}${result.stderr}`).toContain("Invalid runtime release tag");
|
||||
} finally {
|
||||
await fs.rm(directory, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("release workflow publishes both RC and stable tags through the same signed manifest path", async () => {
|
||||
const workflow = await fs.readFile(WORKFLOW, "utf8");
|
||||
expect(workflow).toContain("v2.0.0-rc.*");
|
||||
expect(workflow).toContain('2.0.0 "$GITHUB_REF_NAME"');
|
||||
expect(workflow).toContain("PRERELEASE_FLAG:");
|
||||
expect(workflow).toContain("--prerelease");
|
||||
expect(workflow).toContain('gh release create "$GITHUB_REF_NAME"');
|
||||
});
|
||||
});
|
||||
@@ -10,6 +10,7 @@ import { setupRuntime } from "../runtime/setup.js";
|
||||
import { bashCandidates, resolveBashCommand } from "../runtime/tooling.js";
|
||||
import {
|
||||
BOOTSTRAP_SCHEMA_VERSION,
|
||||
BOOTSTRAP_RELEASE_TAG,
|
||||
BOOTSTRAP_RUNTIME_VERSION,
|
||||
CAPABILITY_COMPONENTS,
|
||||
COMPONENT_DEPENDENCIES,
|
||||
@@ -27,7 +28,7 @@ function officialManifestFixture(target: string, customize?: (component: string,
|
||||
.filter((component) => component !== "ios" || target.startsWith("darwin-"))
|
||||
.map((component) => {
|
||||
const artifact: Record<string, unknown> = {
|
||||
url: `https://github.com/time-attack/gstack/releases/download/v${BOOTSTRAP_RUNTIME_VERSION}/${component}.tar.gz`,
|
||||
url: `https://github.com/time-attack/gstack/releases/download/${BOOTSTRAP_RELEASE_TAG}/${component}.tar.gz`,
|
||||
sha256: "0".repeat(64),
|
||||
bytes: 8,
|
||||
format: "tar.gz",
|
||||
@@ -296,6 +297,31 @@ describe("GStack runtime setup UX", () => {
|
||||
expect(fetches).toBe(0);
|
||||
});
|
||||
|
||||
test("missing official release stops before install with an actionable immutable-tag error", async () => {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), "gstack-bootstrap-missing-release-"));
|
||||
const output = capture();
|
||||
let calls = 0;
|
||||
try {
|
||||
expect(await bootstrapMain([
|
||||
"preview", "--capability", "browser-visible", "--home", path.join(root, "home"),
|
||||
], {
|
||||
stdout: output.stream,
|
||||
stderr: output.stream,
|
||||
fetch: async (url: string) => {
|
||||
calls += 1;
|
||||
return { ok: false, status: 404, url };
|
||||
},
|
||||
})).toBe(1);
|
||||
expect(calls).toBe(1);
|
||||
expect(output.value()).toContain(`Official runtime release ${BOOTSTRAP_RELEASE_TAG} is not published`);
|
||||
expect(output.value()).toContain(OFFICIAL_MANIFEST_URL);
|
||||
expect(output.value()).toContain("No files were downloaded or installed");
|
||||
expect(await fs.readdir(root)).toEqual([]);
|
||||
} finally {
|
||||
await fs.rm(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("bootstrap executes through a symlinked or aliased filesystem path", async () => {
|
||||
if (process.platform === "win32") return;
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), "gstack-bootstrap-link-"));
|
||||
|
||||
Reference in New Issue
Block a user