| 1 | import { spawnSync } from "node:child_process"; |
| 2 | import { copyFileSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs"; |
| 3 | import { tmpdir } from "node:os"; |
| 4 | import { join } from "node:path"; |
| 5 | import { fileURLToPath } from "node:url"; |
| 6 | import { afterEach, describe, expect, it } from "vitest"; |
| 7 | |
| 8 | const script = fileURLToPath(new URL("../scripts/sync-latest-release.mjs", import.meta.url)); |
| 9 | const roots: string[] = []; |
| 10 | afterEach(() => { for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }); }); |
| 11 | |
| 12 | const OLD = { tag: "v0.10.0", version: "0.10.0", publishedAt: "2026-09-01T00:00:00Z", url: "https://github.com/codewhale-hq/CodeWhale/releases/tag/v0.10.0" }; |
| 13 | |
| 14 | /** The script in a throwaway web/ + docs/ tree, with GitHub stubbed. */ |
| 15 | function run(args: string[], { status = 200, cloud = { release: { latest: "0.10.0" } } }: { status?: number; cloud?: unknown } = {}) { |
| 16 | const root = mkdtempSync(join(tmpdir(), "cw-sync-release-")); |
| 17 | roots.push(root); |
| 18 | mkdirSync(join(root, "web", "scripts"), { recursive: true }); |
| 19 | mkdirSync(join(root, "web", "data"), { recursive: true }); |
| 20 | mkdirSync(join(root, "docs", "cloud-facts"), { recursive: true }); |
| 21 | copyFileSync(script, join(root, "web", "scripts", "sync-latest-release.mjs")); |
| 22 | writeFileSync(join(root, "web", "data", "latest-published-release.json"), `${JSON.stringify(OLD, null, 2)}\n`); |
| 23 | writeFileSync(join(root, "docs", "public-surface-facts.json"), `${JSON.stringify({ latestPublishedRelease: { ...OLD, sources: ["x"] } }, null, 2)}\n`); |
| 24 | writeFileSync(join(root, "docs", "cloud-facts", "stable.json"), `${JSON.stringify(cloud, null, 2)}\n`); |
| 25 | const release = JSON.stringify({ tag_name: "v0.10.1", published_at: "2026-09-02T00:00:00Z" }); |
| 26 | const stub = `globalThis.fetch = async () => new Response(${JSON.stringify(release)}, { status: ${status} });`; |
| 27 | const result = spawnSync(process.execPath, ["--import", `data:text/javascript,${encodeURIComponent(stub)}`, join(root, "web", "scripts", "sync-latest-release.mjs"), ...args], { encoding: "utf8", env: { ...process.env, GITHUB_TOKEN: "" } }); |
| 28 | const read = (...parts: string[]) => JSON.parse(readFileSync(join(root, ...parts), "utf8")); |
| 29 | return { result, root, target: () => read("web", "data", "latest-published-release.json"), mirror: () => read("docs", "public-surface-facts.json") }; |
| 30 | } |
| 31 | |
| 32 | describe("sync-latest-release", () => { |
| 33 | it("fails --check when GitHub cannot be read instead of reporting current", () => { |
| 34 | const { result } = run(["--check"], { status: 503 }); |
| 35 | expect(result.status).toBe(1); |
| 36 | expect(result.stderr).toContain("could not read the latest release"); |
| 37 | }); |
| 38 | |
| 39 | it("writes nothing when any mirror is unusable, then moves all three together", () => { |
| 40 | const broken = run([], { cloud: { notRelease: true } }); |
| 41 | expect(broken.result.status).toBe(1); |
| 42 | expect(broken.target()).toEqual(OLD); |
| 43 | expect(broken.mirror().latestPublishedRelease.tag).toBe("v0.10.0"); |
| 44 | expect(readdirSync(join(broken.root, "web", "data"))).toEqual(["latest-published-release.json"]); |
| 45 | |
| 46 | const ok = run([]); |
| 47 | expect(ok.result.status, ok.result.stderr).toBe(0); |
| 48 | expect(ok.target().tag).toBe("v0.10.1"); |
| 49 | expect(ok.mirror().latestPublishedRelease).toMatchObject({ tag: "v0.10.1", sources: ["x"] }); |
| 50 | expect(JSON.parse(readFileSync(join(ok.root, "docs", "cloud-facts", "stable.json"), "utf8")).release).toMatchObject({ latest: "0.10.1" }); |
| 51 | }); |
| 52 | }); |
| 53 |