Files
open-design/tools/release/tests/catalog-export.test.ts
Marc ChanandGitHub 241f520037 feat(release): publish versioned catalog and DSH snapshots (#7617)
* feat(release): publish versioned catalog and DSH snapshots

Add tools-release catalog export/pack/publish and a canonical DSH
bootstrap source so landing can later consume immutable R2 snapshots
without globbing the monorepo tree.

* fix(release): harden catalog snapshot publishing

* fix(release): use portable tar ownership flags

* fix(release): resolve catalog root from git checkout

* fix(release): stabilize catalog preview assets

* fix(release): close catalog publish determinism gaps

* fix(release): remove mutable catalog runtime metadata

* fix(release): prevent stale catalog pointer updates

* fix(release): fail all-fallback catalog previews

* fix(release): fail previews with blocked dependencies

* fix(release): export localized design system bodies

* fix(release): pin catalog preview environment

* fix(release): close catalog and bootstrap rollback gaps

* fix(release): fail partial catalog preview renders

* fix(release): render remote catalog entries locally

* fix(release): keep DSH bootstrap publishes fresh

* fix(release): parallelize catalog snapshot uploads

* fix(release): preserve hidden catalog assets

* fix(release): trigger catalog publish on storage helpers

Include common.ts and s3-upload.ts in catalog-publish and catalog-validate
path filters so publisher CAS, signing, and retry changes republish.

* fix(release): gate catalog provenance and DSH helper triggers

Validate complete provenance against the packed bundle before any
immutable upload. Include common.ts and s3-upload.ts in the standalone
DSH publisher path filter.

* fix(release): match provenance recordCounts to catalog

Derive counts from catalog.records and require an exact match before
any immutable upload so empty or stale provenance cannot become latest.
2026-09-01 03:25:37 +00:00

149 lines
5.2 KiB
TypeScript

import { mkdtemp, mkdir, writeFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { describe, expect, it } from "vitest";
import { resolveRepoRoot } from "../src/catalog/export-catalog.ts";
import { exportCatalog } from "../src/catalog/export.ts";
import { exporterVersion } from "../src/catalog/pack-catalog.ts";
import { assertValidCatalog, validateCatalog } from "../src/catalog/validate.ts";
const FIXTURE_ROOT = resolve(import.meta.dirname, "fixtures/catalog");
const WORKSPACE_ROOT = resolve(import.meta.dirname, "../../..");
const SOURCE_COMMIT = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
describe("catalog export", () => {
it("resolves the repository root from package and bundled CLI layouts", () => {
expect(resolveRepoRoot()).toBe(WORKSPACE_ROOT);
});
it("derives immutable exporter identity from package version and source commit", () => {
expect(exporterVersion(SOURCE_COMMIT)).toMatch(
new RegExp(`^tools-release@[^+]+\\+${SOURCE_COMMIT}$`),
);
});
it("exports a tiny fixture tree into a valid catalog.json", () => {
const { catalog } = exportCatalog({
repoRoot: FIXTURE_ROOT,
sourceCommit: SOURCE_COMMIT,
generatedAt: "2026-08-29T00:00:00.000Z",
});
assertValidCatalog(catalog);
expect(catalog.schemaVersion).toBe(1);
expect(catalog.sourceCommit).toBe(SOURCE_COMMIT);
const byType = Object.fromEntries(
["skill", "system", "craft", "template", "plugin"].map((type) => [
type,
catalog.records.filter((r) => r.type === type).map((r) => r.id),
]),
);
expect(byType.skill).toEqual(["alpha", "beta"]);
expect(byType.system).toEqual(["brand-x"]);
expect(byType.craft).toEqual(["spacing-rules"]);
expect(byType.template).toEqual(["deck-one"]);
expect(byType.plugin).toEqual(["demo-atom", "example-demo-plugin"]);
const alpha = catalog.records.find((r) => r.id === "alpha");
expect(alpha).toMatchObject({
type: "skill",
kind: "template",
name: "Alpha Skill",
mode: "prototype",
});
expect(alpha && "preview" in alpha ? alpha.preview?.path : null).toBe("previews/skills/alpha.webp");
const beta = catalog.records.find((r) => r.id === "beta");
expect(beta).toMatchObject({ type: "skill", kind: "instruction" });
const system = catalog.records.find((r) => r.id === "brand-x");
expect(system).toMatchObject({
type: "system",
category: "Product",
palette: expect.arrayContaining(["#1a1817", "#d44b1e", "#efe7d2"]),
});
if (system?.type === "system") {
expect(system.bodiesI18n?.["zh-CN"]).toContain("中文正文");
expect(system.tokens).toMatchObject({
total: 4,
grade: "excellent",
theme: { bg: "#1a1817", accent: "#d44b1e" },
});
}
const plugin = catalog.records.find((r) => r.id === "example-demo-plugin");
expect(plugin?.type).toBe("plugin");
if (plugin?.type === "plugin") {
// Authored poster wins over baked video poster.
expect(plugin.preview?.remotePoster).toBe("https://cdn.example.test/demo-poster.webp");
expect(plugin.preview?.entryPath).toBe(
"entries/plugins/examples/demo-plugin/example.html",
);
expect(plugin.titleI18n?.["zh-CN"]).toBe("演示插件");
}
const atom = catalog.records.find((record) => record.id === "demo-atom");
expect(atom).toMatchObject({ type: "plugin", kind: "atom", discoverable: false });
// Snapshot records must not embed raw example.html contents as files —
// only markdown bodies. The export walk never copies html into catalog.json.
const serialized = JSON.stringify(catalog);
expect(serialized).not.toContain("<!doctype html>");
});
it("fails closed on unsupported/corrupt skill frontmatter", async () => {
const root = await mkdtemp(join(tmpdir(), "od-catalog-corrupt-"));
try {
await mkdir(join(root, "skills/bad"), { recursive: true });
await writeFile(
join(root, "skills/bad/SKILL.md"),
"---\nname: [unterminated\n---\n\n# Bad\n",
"utf8",
);
expect(() =>
exportCatalog({
repoRoot: root,
sourceCommit: SOURCE_COMMIT,
generatedAt: "2026-08-29T00:00:00.000Z",
}),
).toThrow(/invalid YAML frontmatter/);
} finally {
await rm(root, { force: true, recursive: true });
}
});
});
describe("catalog validate", () => {
it("rejects duplicate stable ids", () => {
const { catalog } = exportCatalog({
repoRoot: FIXTURE_ROOT,
sourceCommit: SOURCE_COMMIT,
generatedAt: "2026-08-29T00:00:00.000Z",
});
catalog.records.push({ ...catalog.records[0]! });
const result = validateCatalog(catalog);
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.errors.some((e) => e.includes("duplicate identity"))).toBe(true);
}
});
it("rejects invalid schemaVersion and short commits", () => {
const result = validateCatalog({
schemaVersion: 99,
sourceCommit: "abc",
generatedAt: "",
records: [],
});
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.errors.join("\n")).toMatch(/schemaVersion/);
expect(result.errors.join("\n")).toMatch(/sourceCommit/);
}
});
});