Files
2026-07-13 12:58:18 +08:00

1053 lines
29 KiB
TypeScript

import { spawnSync } from "child_process";
import fs from "fs";
import os from "os";
import path from "path";
import { fileURLToPath } from "url";
import { glob } from "glob";
import yaml from "yaml";
import {
findUnexpectedMultiFileRegions,
type MultiFileRegionSource,
} from "./lib/demo-region-guard.js";
import { checkEssentialContent } from "./lib/essential-content.js";
import type { PageInput } from "./lib/essential-content.js";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
// showcase/scripts/ → showcase/ → repo root. Different from
// validate-parity.ts (which stops at showcase/) because `nx build shell-docs`
// must run from the monorepo root.
const REPO_ROOT = path.resolve(__dirname, "..", "..");
export type CheckStatus = "pass" | "fail" | "skipped";
export interface CheckResult {
name: string;
status: CheckStatus;
messages: string[];
}
export interface BuildCheckOptions {
skipExecution?: boolean;
}
export function runBuildCheck(opts: BuildCheckOptions = {}): CheckResult {
if (opts.skipExecution) {
return {
name: "nx-build-shell-docs",
status: "skipped",
messages: ["skipExecution=true; no build run"],
};
}
const out = spawnSync("npx", ["nx", "build", "shell-docs"], {
cwd: REPO_ROOT,
encoding: "utf-8",
});
if (out.status === 0) {
return { name: "nx-build-shell-docs", status: "pass", messages: [] };
}
return {
name: "nx-build-shell-docs",
status: "fail",
messages: [out.stdout || "", out.stderr || ""].filter(Boolean),
};
}
interface RegistryDemo {
id: string;
}
interface RegistryIntegrationLite {
slug: string;
demos: RegistryDemo[];
}
interface RegistryLite {
integrations: RegistryIntegrationLite[];
}
interface ManifestDemoLite {
id: string;
route?: string;
highlight?: string[];
}
interface ManifestLite {
slug: string;
demos?: ManifestDemoLite[];
}
// Strip fenced code blocks (``` ... ```) before scanning for component
// references / links / imports. Without this, every regex below false-
// positives on example code inside tutorial pages — e.g. a docs page
// that shows `<InlineDemo demo="some-example" />` in a fenced code
// sample would report "unknown demo id" even though it's literal
// documentation, not a live component reference.
const FENCED_CODE_RE = /```[\s\S]*?```/g;
function strip(body: string): string {
return body.replace(FENCED_CODE_RE, "");
}
const INLINE_DEMO_RE = /<InlineDemo\s+[^>]*demo=["']([^"']+)["']/g;
export function checkInlineDemoRefs(input: {
pages: PageInput[];
registry: RegistryLite;
}): CheckResult {
const known = new Set<string>();
for (const i of input.registry.integrations) {
for (const d of i.demos) {
known.add(d.id);
}
}
const failures: string[] = [];
for (const page of input.pages) {
const body = strip(page.body);
INLINE_DEMO_RE.lastIndex = 0;
let m: RegExpExecArray | null;
while ((m = INLINE_DEMO_RE.exec(body)) !== null) {
if (!known.has(m[1])) {
failures.push(`${page.path}: unknown demo id "${m[1]}"`);
}
}
}
return {
name: "inline-demo-refs",
status: failures.length === 0 ? "pass" : "fail",
messages: failures,
};
}
interface DemoRegion {
file: string;
startLine: number;
endLine: number;
code: string;
language: string;
}
interface DemoFile {
filename: string;
language: string;
content: string;
}
interface DemoRecord {
regions?: Record<string, DemoRegion>;
files?: DemoFile[];
}
interface DemoContent {
demos: Record<string, DemoRecord>;
}
interface SetupContentLite {
concepts: Record<string, { source?: string }>;
}
interface CodeBlock {
language: string;
meta: string;
code: string;
}
const SNIPPET_RE = /<Snippet\s+[^>]*region=["']([^"']+)["']/g;
export function checkSnippetRegions(input: {
pages: PageInput[];
demoContent: DemoContent;
}): CheckResult {
const allRegions = new Set<string>();
for (const record of Object.values(input.demoContent.demos)) {
for (const regionName of Object.keys(record.regions ?? {})) {
allRegions.add(regionName);
}
}
const failures: string[] = [];
for (const page of input.pages) {
const body = strip(page.body);
SNIPPET_RE.lastIndex = 0;
let m: RegExpExecArray | null;
while ((m = SNIPPET_RE.exec(body)) !== null) {
if (!allRegions.has(m[1])) {
failures.push(`${page.path}: unknown snippet region "${m[1]}"`);
}
}
}
return {
name: "snippet-regions",
status: failures.length === 0 ? "pass" : "fail",
messages: failures,
};
}
const REGION_START_RE = /@region\[([a-z0-9][a-z0-9-]*)\]/g;
const SKIP_EXACT = new Set([".DS_Store", "Thumbs.db"]);
const SKIP_DIRS = new Set(["__pycache__", "node_modules", ".next"]);
const SKIP_EXTENSIONS = new Set([
".pyc",
".png",
".jpg",
".jpeg",
".gif",
".webp",
".ico",
".bmp",
".avif",
".tiff",
".woff",
".woff2",
".ttf",
".otf",
".eot",
".pdf",
".zip",
".tar",
".gz",
".tgz",
".bz2",
".7z",
".rar",
".mp4",
".mp3",
".wav",
".mov",
".webm",
".ogg",
]);
function walkRegionCandidateFiles(
absDir: string,
currentRel = "",
): Array<{ abs: string; rel: string }> {
const out: Array<{ abs: string; rel: string }> = [];
for (const entry of fs.readdirSync(absDir, { withFileTypes: true })) {
if (SKIP_EXACT.has(entry.name)) continue;
const abs = path.join(absDir, entry.name);
const rel = currentRel ? `${currentRel}/${entry.name}` : entry.name;
if (entry.isDirectory()) {
if (!SKIP_DIRS.has(entry.name))
out.push(...walkRegionCandidateFiles(abs, rel));
continue;
}
if (!entry.isFile()) continue;
if (SKIP_EXTENSIONS.has(path.extname(entry.name).toLowerCase())) continue;
out.push({ abs, rel });
}
return out;
}
function collectMultiFileRegionSources(): MultiFileRegionSource[] {
const integrationsDir = path.join(REPO_ROOT, "showcase", "integrations");
const sources: MultiFileRegionSource[] = [];
const packageDirs = fs
.readdirSync(integrationsDir, { withFileTypes: true })
.filter((entry) => entry.isDirectory())
.map((entry) => entry.name);
for (const pkgDir of packageDirs) {
const pkgRoot = path.join(integrationsDir, pkgDir);
const manifestPath = path.join(pkgRoot, "manifest.yaml");
if (!fs.existsSync(manifestPath)) continue;
const manifest = yaml.parse(
fs.readFileSync(manifestPath, "utf-8"),
) as ManifestLite;
for (const demo of manifest.demos ?? []) {
if (!demo.route) continue;
const routeDir = demo.route.replace(/^\/demos\//, "");
const demoDir = path.join(pkgRoot, "src", "app", "demos", routeDir);
if (!fs.existsSync(demoDir)) continue;
const files = walkRegionCandidateFiles(demoDir).map((file) => ({
...file,
bundled: `src/app/demos/${routeDir}/${file.rel}`,
}));
const demoPathSet = new Set(files.map((file) => file.bundled));
for (const hlPath of demo.highlight ?? []) {
if (demoPathSet.has(hlPath)) continue;
const abs = path.join(pkgRoot, hlPath);
if (fs.existsSync(abs) && fs.statSync(abs).isFile()) {
files.push({ abs, rel: hlPath, bundled: hlPath });
}
}
const filesByRegion = new Map<string, Set<string>>();
for (const file of files) {
const raw = fs.readFileSync(file.abs, "utf-8");
REGION_START_RE.lastIndex = 0;
let match: RegExpExecArray | null;
while ((match = REGION_START_RE.exec(raw)) !== null) {
const regionFiles = filesByRegion.get(match[1]) ?? new Set<string>();
regionFiles.add(file.bundled);
filesByRegion.set(match[1], regionFiles);
}
}
const demoKey = `${manifest.slug}::${demo.id}`;
for (const [regionName, regionFiles] of filesByRegion.entries()) {
if (regionFiles.size > 1) {
sources.push({
demoKey,
regionName,
files: [...regionFiles],
});
}
}
}
}
return sources;
}
export function checkUnexpectedMultiFileRegionSources(input: {
sources: MultiFileRegionSource[];
}): CheckResult {
const unexpected = findUnexpectedMultiFileRegions(input.sources);
return {
name: "duplicate-region-sources",
status: unexpected.length === 0 ? "pass" : "fail",
messages: unexpected.map(
({ demoKey, regionName, files }) =>
`${demoKey}: region "${regionName}" appears in multiple files: ${files.join(", ")}`,
),
};
}
export function checkDuplicateRegionSources(): CheckResult {
return checkUnexpectedMultiFileRegionSources({
sources: collectMultiFileRegionSources(),
});
}
const MD_LINK_RE = /\[[^\]]*\]\((\/[^)\s]*)\)/g;
export function checkInternalLinks(input: {
pages: PageInput[];
knownRoutes: Set<string>;
}): CheckResult {
const failures: string[] = [];
for (const page of input.pages) {
const body = strip(page.body);
MD_LINK_RE.lastIndex = 0;
let m: RegExpExecArray | null;
while ((m = MD_LINK_RE.exec(body)) !== null) {
const raw = m[1];
const cleaned = raw.split("#")[0].split("?")[0];
if (!input.knownRoutes.has(cleaned)) {
failures.push(`${page.path}: dead link "${raw}"`);
}
}
}
return {
name: "internal-links",
status: failures.length === 0 ? "pass" : "fail",
messages: failures,
};
}
const ALIAS_IMPORT_RE =
/^\s*import\s+[^"';]+from\s+["'](@\/[^"']+)["']\s*;?\s*$/gm;
export function checkImportPaths(input: {
pages: PageInput[];
existsOnDisk: (importPath: string) => boolean;
}): CheckResult {
const failures: string[] = [];
for (const page of input.pages) {
const body = strip(page.body);
ALIAS_IMPORT_RE.lastIndex = 0;
let m: RegExpExecArray | null;
while ((m = ALIAS_IMPORT_RE.exec(body)) !== null) {
if (!input.existsOnDisk(m[1])) {
failures.push(`${page.path}: unresolved import "${m[1]}"`);
}
}
}
return {
name: "import-paths",
status: failures.length === 0 ? "pass" : "fail",
messages: failures,
};
}
export function runEssentialContentCheck(pages: PageInput[]): CheckResult {
const messages: string[] = [];
for (const page of pages) {
const r = checkEssentialContent(page);
if (r.status === "fail") messages.push(...r.messages);
}
return {
name: "essential-content",
status: messages.length === 0 ? "pass" : "fail",
messages,
};
}
function loadPages(): PageInput[] {
const docsRoot = path.join(
REPO_ROOT,
"showcase",
"shell-docs",
"src",
"content",
"docs",
);
const files = glob.sync("**/*.mdx", { cwd: docsRoot });
return files.map((rel) => ({
path: rel,
body: fs.readFileSync(path.join(docsRoot, rel), "utf-8"),
}));
}
function loadRegistry(): RegistryLite {
const p = path.join(
REPO_ROOT,
"showcase",
"shell-docs",
"src",
"data",
"registry.json",
);
return JSON.parse(fs.readFileSync(p, "utf-8")) as RegistryLite;
}
function loadDemoContent(): DemoContent {
const p = path.join(
REPO_ROOT,
"showcase",
"shell-docs",
"src",
"data",
"demo-content.json",
);
return JSON.parse(fs.readFileSync(p, "utf-8")) as DemoContent;
}
function loadKnownRoutes(): Set<string> {
const docsRoot = path.join(
REPO_ROOT,
"showcase",
"shell-docs",
"src",
"content",
"docs",
);
const files = glob.sync("**/*.mdx", { cwd: docsRoot });
const routes = new Set<string>();
for (const rel of files) {
const noExt = rel.replace(/\.mdx$/, "");
const noIndex = noExt.endsWith("/index")
? noExt.slice(0, -"/index".length)
: noExt;
// Strip route-group segments like (other), (foo) — they are removed
// from the URL by Fumadocs/Next.js. Mirrors normalizeSlugForUrl in
// showcase/shell-docs/src/lib/sitemap-helpers.ts.
const normalized = noIndex
.split("/")
.filter((seg) => !/^\(.+\)$/.test(seg))
.join("/");
routes.add(normalized === "" ? "/" : "/" + normalized);
}
routes.add("/"); // root
return routes;
}
function aliasExists(importPath: string): boolean {
const stripped = importPath.replace(/^@\//, "");
const root = path.join(REPO_ROOT, "showcase", "shell-docs", "src");
return (
fs.existsSync(path.join(root, stripped)) ||
fs.existsSync(path.join(root, "content", stripped))
);
}
export function loadSnippetComponentNames(): Set<string> {
const docsRenderPath = path.join(
REPO_ROOT,
"showcase/shell-docs/src/lib/docs-render.tsx",
);
const src = fs.readFileSync(docsRenderPath, "utf-8");
const mapMatch = src.match(/export const SNIPPET_MAP[^{]*\{([^}]+)\}/s);
if (!mapMatch)
throw new Error("Could not find SNIPPET_MAP in docs-render.tsx");
const keys = [...mapMatch[1].matchAll(/^\s*(\w+)\s*:/gm)].map((m) => m[1]);
return new Set(keys);
}
const SNIPPET_WITH_PROPS_RE = /<([A-Z]\w*)\s+[^>]*(?:>|\/>)/g;
// Matches the same shape that inlineSnippets() in docs-render.tsx resolves at
// render time. Per-match check (not per-name) so a page with both <Foo /> and
// <Foo framework="x" /> correctly flags only the latter.
const INLINE_HANDLED_RE = /^<[A-Z]\w*\s*(?:components=\{[^}]*\}\s*)?\/>/;
const MDX_IMPORT_NAME_RE = /^\s*import\s+(\w+)\s+from\s+["']@\/snippets\//gm;
export function checkComponentImports({
pages,
}: {
pages: PageInput[];
}): CheckResult {
const snippetComponents = loadSnippetComponentNames();
const failures: string[] = [];
for (const page of pages) {
const body = strip(page.body);
// Scan imports on the stripped body (same as usages below). Using the raw
// body would count an `import …` shown inside a fenced code sample as a
// real import and silently suppress a genuine missing-import failure.
const imported = new Set<string>();
MDX_IMPORT_NAME_RE.lastIndex = 0;
let im: RegExpExecArray | null;
while ((im = MDX_IMPORT_NAME_RE.exec(body)) !== null) {
imported.add(im[1]);
}
SNIPPET_WITH_PROPS_RE.lastIndex = 0;
const flagged = new Set<string>();
let m: RegExpExecArray | null;
while ((m = SNIPPET_WITH_PROPS_RE.exec(body)) !== null) {
const name = m[1];
if (
snippetComponents.has(name) &&
!imported.has(name) &&
!INLINE_HANDLED_RE.test(m[0]) &&
!flagged.has(name)
) {
flagged.add(name);
failures.push(
`${page.path}: <${name}> used with props but missing snippet import`,
);
}
}
}
return {
name: "component-imports",
status: failures.length === 0 ? "pass" : "fail",
messages: failures,
};
}
const CLAUDE_QUICKSTARTS = [
{
slug: "claude-sdk-python",
path: "integrations/claude-sdk-python/quickstart.mdx",
title: "Python",
modelEnvLine: "ANTHROPIC_MODEL=claude-sonnet-4-6",
requiredStarterFiles: [
"src/agent_server.py",
"src/agents/claude_agent_sdk_adapter.py",
"src/app/api/copilotkit/route.ts",
],
},
{
slug: "claude-sdk-typescript",
path: "integrations/claude-sdk-typescript/quickstart.mdx",
title: "TypeScript",
modelEnvLine: "CLAUDE_MODEL=claude-sonnet-4-6",
requiredStarterFiles: [
"src/agent_server.ts",
"src/app/api/copilotkit/route.ts",
"src/app/page.tsx",
],
},
] as const;
const FENCE_RX = /^[ \t]*```([^\s`]*)?([^\n]*)\n([\s\S]*?)\n[ \t]*```/gm;
function extractCodeBlocks(body: string): CodeBlock[] {
const blocks: CodeBlock[] = [];
FENCE_RX.lastIndex = 0;
let match: RegExpExecArray | null;
while ((match = FENCE_RX.exec(body)) !== null) {
blocks.push({
language: match[1] ?? "",
meta: match[2] ?? "",
code: match[3],
});
}
return blocks;
}
function escapeRegex(value: string): string {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function hasFenceTitle(block: CodeBlock, title: string): boolean {
return (
new RegExp(`\\btitle=["']${escapeRegex(title)}["']`).test(block.meta) ||
block.meta.trim() === `title=${title}`
);
}
function findTitledBlock(
blocks: CodeBlock[],
title: string,
): CodeBlock | undefined {
return blocks.find((block) => hasFenceTitle(block, title));
}
function findShellCommand(
blocks: CodeBlock[],
startsWith: string,
): string | undefined {
return blocks
.filter((block) => block.language === "bash" || block.language === "sh")
.flatMap((block) => block.code.split(/\r?\n/).map((line) => line.trim()))
.find((line) => line.startsWith(startsWith));
}
function addMissing(
failures: string[],
pagePath: string,
body: string,
needle: string,
label: string,
) {
if (!body.includes(needle)) {
failures.push(`${pagePath}: missing ${label} (${needle})`);
}
}
function addMissingRegex(
failures: string[],
pagePath: string,
body: string,
pattern: RegExp,
label: string,
) {
if (!pattern.test(body)) {
failures.push(`${pagePath}: missing ${label}`);
}
}
function checkBlockContains(
failures: string[],
pagePath: string,
block: CodeBlock | undefined,
title: string,
needles: Array<[string | RegExp, string]>,
) {
if (!block) {
failures.push(`${pagePath}: missing code block title="${title}"`);
return;
}
for (const [needle, label] of needles) {
const ok =
typeof needle === "string"
? block.code.includes(needle)
: needle.test(block.code);
if (!ok) failures.push(`${pagePath}: ${title} missing ${label}`);
}
}
function checkCommandContains(
failures: string[],
pagePath: string,
blocks: CodeBlock[],
startsWith: string,
label: string,
packages: string[],
) {
const command = findShellCommand(blocks, startsWith);
if (!command) {
failures.push(`${pagePath}: missing ${label} command (${startsWith})`);
return;
}
for (const packageName of packages) {
if (!command.includes(packageName)) {
failures.push(
`${pagePath}: ${label} command missing package ${packageName}`,
);
}
}
}
export function checkClaudeQuickstarts(input: {
pages: PageInput[];
setupSource?: (framework: string, concept: string) => string | null;
starterFileExists?: (framework: string, filePath: string) => boolean;
}): CheckResult {
const failures: string[] = [];
const pagesByPath = new Map(input.pages.map((page) => [page.path, page]));
const setupSource = input.setupSource ?? (() => null);
const starterFileExists = input.starterFileExists ?? (() => true);
for (const config of CLAUDE_QUICKSTARTS) {
const page = pagesByPath.get(config.path);
if (!page) {
failures.push(`${config.path}: quickstart page missing`);
continue;
}
const blocks = extractCodeBlocks(page.body);
addMissingRegex(
failures,
page.path,
page.body,
/<TailoredContent\b[^>]*\bid=["']agent["']/,
'TailoredContent id="agent"',
);
addMissingRegex(
failures,
page.path,
page.body,
/<TailoredContentOption\b[^>]*\bid=["']starter["']/,
"starter TailoredContentOption",
);
addMissingRegex(
failures,
page.path,
page.body,
/<TailoredContentOption\b[^>]*\bid=["']bring-your-own["']/,
"bring-your-own TailoredContentOption",
);
addMissingRegex(
failures,
page.path,
page.body,
/<FrameworkSetup\b[^>]*\bconcept=["']agent-setup["']/,
'FrameworkSetup concept="agent-setup"',
);
addMissing(
failures,
page.path,
page.body,
`npx copilotkit@latest init --framework ${config.slug}`,
"starter CLI command",
);
addMissing(
failures,
page.path,
page.body,
"ANTHROPIC_API_KEY=your_anthropic_api_key",
"Anthropic env var",
);
addMissing(
failures,
page.path,
page.body,
config.modelEnvLine,
"Claude model env var",
);
addMissing(
failures,
page.path,
page.body,
"AGENT_URL=http://localhost:8000",
"agent URL default",
);
addMissing(
failures,
page.path,
page.body,
"curl http://localhost:8000/health",
"agent health check",
);
for (const filePath of config.requiredStarterFiles) {
addMissing(
failures,
page.path,
page.body,
`\`${filePath}\``,
`starter file claim ${filePath}`,
);
if (!starterFileExists(config.slug, filePath)) {
failures.push(
`${page.path}: documented starter file not found after extraction: ${filePath}`,
);
}
}
const setup = setupSource(config.slug, "agent-setup");
if (!setup || setup.trim().length === 0) {
failures.push(`${page.path}: missing bundled agent-setup content`);
} else {
if (!/ClaudeAgentAdapter/.test(setup)) {
failures.push(`${page.path}: agent-setup missing ClaudeAgentAdapter`);
}
if (!/```|~~~/.test(setup)) {
failures.push(`${page.path}: agent-setup missing a code block`);
}
}
checkBlockContains(
failures,
page.path,
findTitledBlock(blocks, "app/api/copilotkit/route.ts"),
"app/api/copilotkit/route.ts",
[
["HttpAgent", "HttpAgent"],
["CopilotRuntime", "CopilotRuntime"],
["ExperimentalEmptyAdapter", "ExperimentalEmptyAdapter"],
[
"copilotRuntimeNextJSAppRouterEndpoint",
"copilotRuntimeNextJSAppRouterEndpoint",
],
['"http://localhost:8000"', "localhost agent URL"],
],
);
checkBlockContains(
failures,
page.path,
findTitledBlock(blocks, "app/layout.tsx"),
"app/layout.tsx",
[
['@copilotkit/react-core/v2"', "v2 React entrypoint"],
['agent="claude_agent"', "agent prop"],
],
);
checkBlockContains(
failures,
page.path,
findTitledBlock(blocks, "app/page.tsx"),
"app/page.tsx",
[["CopilotSidebar", "CopilotSidebar"]],
);
if (config.slug === "claude-sdk-python") {
checkCommandContains(
failures,
page.path,
blocks,
"uv add ",
"Python agent install",
[
"claude-agent-sdk",
"ag-ui-claude-sdk",
"ag-ui-protocol",
"anthropic",
"fastapi",
"uvicorn",
"python-dotenv",
],
);
checkCommandContains(
failures,
page.path,
blocks,
"npm install @copilotkit/runtime",
"frontend install",
["@copilotkit/runtime", "@copilotkit/react-core", "@ag-ui/client"],
);
checkBlockContains(
failures,
page.path,
findTitledBlock(blocks, "main.py"),
"main.py",
[
["RunAgentInput", "RunAgentInput"],
["await request.json()", "request JSON parsing"],
['os.getenv("ANTHROPIC_MODEL"', "Anthropic model env var"],
["RunErrorEvent", "RunErrorEvent"],
["EventType.RUN_ERROR", "RUN_ERROR event"],
["ClaudeAgentAdapter", "ClaudeAgentAdapter"],
["adapter.run(input_data)", "adapter run"],
["StreamingResponse", "StreamingResponse"],
['media_type="text/event-stream"', "SSE media type"],
['@app.get("/health")', "health route"],
['@app.post("/")', "agent POST route"],
],
);
} else {
checkCommandContains(
failures,
page.path,
blocks,
"npm install @anthropic-ai/claude-agent-sdk",
"TypeScript agent install",
[
"@anthropic-ai/claude-agent-sdk@^0.2.58",
"@anthropic-ai/sdk",
"@ag-ui/claude-agent-sdk",
"@ag-ui/core",
"@ag-ui/encoder",
"express",
"dotenv",
"zod",
],
);
checkCommandContains(
failures,
page.path,
blocks,
"npm install -D typescript",
"TypeScript dev install",
["typescript", "tsx", "@types/node", "@types/express"],
);
checkCommandContains(
failures,
page.path,
blocks,
"npm install @copilotkit/runtime",
"frontend install",
["@copilotkit/runtime", "@copilotkit/react-core", "@ag-ui/client"],
);
const agentBlock = findTitledBlock(blocks, "src/agent-server.ts");
checkBlockContains(
failures,
page.path,
agentBlock,
"src/agent-server.ts",
[
["express", "express"],
["app.use(express.json", "JSON body parser"],
["RunAgentInput", "RunAgentInput"],
["EventType.RUN_ERROR", "RUN_ERROR event"],
["EventEncoder", "EventEncoder"],
["ClaudeAgentAdapter", "ClaudeAgentAdapter"],
['app.post("/",', "agent POST route"],
['app.get("/health"', "health route"],
['"text/event-stream"', "SSE content type"],
],
);
if (agentBlock) {
const writesSse = /encodeSSE\s*\(/.test(agentBlock.code);
const negotiatesContentType =
/getContentType\s*\(\s*\)/.test(agentBlock.code) ||
/new\s+EventEncoder\s*\(\s*\{[\s\S]*?\baccept\b[\s\S]*?\}\s*\)/.test(
agentBlock.code,
);
if (writesSse && negotiatesContentType) {
failures.push(
`${page.path}: src/agent-server.ts writes SSE frames but negotiates a non-SSE content type`,
);
}
}
}
}
return {
name: "claude-quickstarts",
status: failures.length === 0 ? "pass" : "fail",
messages: failures,
};
}
function loadSetupContent(): SetupContentLite {
const p = path.join(
REPO_ROOT,
"showcase",
"shell-docs",
"src",
"data",
"setup-content.json",
);
return JSON.parse(fs.readFileSync(p, "utf-8")) as SetupContentLite;
}
function createExtractedStarterResolver(): {
starterFileExists: (framework: string, filePath: string) => boolean;
cleanup: () => void;
errors: string[];
} {
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "claude-quickstarts-"));
const cache = new Map<string, Set<string>>();
const errors: string[] = [];
function load(framework: string): Set<string> {
const cached = cache.get(framework);
if (cached) return cached;
const outDir = path.join(tmpRoot, framework);
const result = spawnSync(
"npx",
["tsx", "extract-starter.ts", framework, outDir],
{
cwd: __dirname,
encoding: "utf-8",
},
);
if (result.status !== 0) {
errors.push(
`${framework}: starter extraction failed: ${[
result.stdout,
result.stderr,
]
.filter(Boolean)
.join("\n")}`,
);
const empty = new Set<string>();
cache.set(framework, empty);
return empty;
}
const files = glob
.sync("**/*", { cwd: outDir, nodir: true, dot: true })
.map((file) => file.split(path.sep).join("/"));
const fileSet = new Set(files);
cache.set(framework, fileSet);
return fileSet;
}
return {
starterFileExists: (framework, filePath) => load(framework).has(filePath),
cleanup: () => fs.rmSync(tmpRoot, { recursive: true, force: true }),
errors,
};
}
async function main() {
const skipBuild = process.argv.includes("--skip-build");
const pages = loadPages();
const registry = loadRegistry();
const demoContent = loadDemoContent();
const setupContent = loadSetupContent();
const knownRoutes = loadKnownRoutes();
const starterResolver = createExtractedStarterResolver();
let failed = false;
// Always clean up the extraction temp dir, even if a check throws — cleanup
// runs in finally, and process.exit is deferred until after it so the exit
// can't skip the finally block.
try {
const results: CheckResult[] = [
runBuildCheck({ skipExecution: skipBuild }),
checkInlineDemoRefs({ pages, registry }),
checkSnippetRegions({ pages, demoContent }),
checkDuplicateRegionSources(),
checkInternalLinks({ pages, knownRoutes }),
checkImportPaths({ pages, existsOnDisk: aliasExists }),
checkComponentImports({ pages }),
checkClaudeQuickstarts({
pages,
setupSource: (framework, concept) =>
setupContent.concepts[`${framework}::${concept}`]?.source ?? null,
starterFileExists: starterResolver.starterFileExists,
}),
runEssentialContentCheck(pages),
];
if (starterResolver.errors.length > 0) {
results.push({
name: "starter-extraction",
status: "fail",
messages: starterResolver.errors,
});
}
for (const r of results) {
const tag =
r.status === "pass" ? "PASS" : r.status === "fail" ? "FAIL" : "SKIP";
console.log(`[${tag}] ${r.name}`);
for (const msg of r.messages) {
console.log(` ${msg}`);
}
if (r.status === "fail") failed = true;
}
} finally {
starterResolver.cleanup();
}
process.exit(failed ? 1 : 0);
}
if (fileURLToPath(import.meta.url) === path.resolve(process.argv[1] ?? "")) {
main().catch((e) => {
console.error(e);
process.exit(1);
});
}