Skip to content

Commit dc9811b

Browse files
ataibarkaiclaude
andcommitted
feat(showcase/scripts): port probe-docs.ts for live docs-status checks
Ports probe-docs.ts from 4084. For each entry in shared/feature-registry.json it HEADs the feature's og_docs_url and checks whether a matching .mdx exists under shell/src/content/docs/<shell_docs_url>, then writes the results to shell/src/data/docs-status.json. shell-internal's DocsRow falls back to this probed state for (integration, feature) cells that don't have a per-column docs-links.json override. Exposes the script as `probe-docs` in showcase/scripts/package.json. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 0df1d23 commit dc9811b

2 files changed

Lines changed: 117 additions & 0 deletions

File tree

showcase/scripts/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"type": "module",
55
"scripts": {
66
"generate-registry": "tsx generate-registry.ts",
7+
"probe-docs": "tsx probe-docs.ts",
78
"validate-manifests": "tsx generate-registry.ts --validate-only",
89
"showcase:audit": "tsx audit.ts",
910
"create-integration": "tsx create-integration/index.ts",

showcase/scripts/probe-docs.ts

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// Docs-link probe.
2+
//
3+
// Reads shared/feature-registry.json; for each feature:
4+
// - og_docs_url → HTTP HEAD. 2xx = "ok", else "notfound" / "error".
5+
// - shell_docs_url (relative path like "/docs/features/agentic-chat")
6+
// → check shell/src/content/docs/<path>.mdx (or index.mdx).
7+
// file exists = "ok", else "notfound". No network.
8+
//
9+
// Writes shell/src/data/docs-status.json. The shell-internal UI reads it
10+
// so green ✓ / red ✗ reflect actual reachability, not just "field present."
11+
//
12+
// Intended to run on `pnpm dev` (via predev hook) and CI. Safe to run
13+
// frequently — HEAD requests are cheap and the file list is ~50.
14+
15+
import fs from "fs";
16+
import path from "path";
17+
import { fileURLToPath } from "url";
18+
19+
const __filename = fileURLToPath(import.meta.url);
20+
const __dirname = path.dirname(__filename);
21+
22+
const ROOT = path.resolve(__dirname, "..");
23+
const REGISTRY_PATH = path.join(ROOT, "shared", "feature-registry.json");
24+
const SHELL_DOCS_ROOT = path.join(ROOT, "shell", "src", "content", "docs");
25+
const OUTPUT_PATH = path.join(ROOT, "shell", "src", "data", "docs-status.json");
26+
27+
type DocState = "ok" | "missing" | "notfound" | "error";
28+
29+
interface Feature {
30+
id: string;
31+
og_docs_url?: string;
32+
shell_docs_url?: string;
33+
}
34+
35+
interface FeatureDocStatus {
36+
og: DocState;
37+
shell: DocState;
38+
}
39+
40+
// Soft-404 detection. docs.copilotkit.ai returns HTTP 200 with a
41+
// client-rendered "Page Not Found" view for missing docs. Two signals:
42+
// (a) Next.js header "x-matched-path: /[[...slug]]" → catch-all fallback
43+
// (b) `<meta name="robots" content="noindex">` in body → page asks not to
44+
// be indexed, which docs sites only do for 404s and draft content.
45+
// Both are robust across Next.js-hosted docs; we treat either as notfound.
46+
const NOINDEX_PATTERN =
47+
/<meta\s+name=["']robots["']\s+content=["'][^"']*noindex[^"']*["']/i;
48+
49+
async function probeOg(url: string | undefined): Promise<DocState> {
50+
if (!url) return "missing";
51+
try {
52+
const res = await fetch(url, { method: "GET", redirect: "follow" });
53+
if (res.status === 404) return "notfound";
54+
if (res.status < 200 || res.status >= 400) return "error";
55+
const matched = res.headers.get("x-matched-path") ?? "";
56+
if (matched.includes("[[...") || matched.includes("[..."))
57+
return "notfound";
58+
const body = await res.text();
59+
if (NOINDEX_PATTERN.test(body)) return "notfound";
60+
return "ok";
61+
} catch {
62+
return "error";
63+
}
64+
}
65+
66+
function probeShell(docsPath: string | undefined): DocState {
67+
if (!docsPath) return "missing";
68+
// Strip leading /docs/ prefix to map to content root.
69+
const rel = docsPath.replace(/^\/docs\/?/, "").replace(/\/$/, "");
70+
const candidates = [
71+
path.join(SHELL_DOCS_ROOT, `${rel}.mdx`),
72+
path.join(SHELL_DOCS_ROOT, rel, "index.mdx"),
73+
];
74+
return candidates.some((p) => fs.existsSync(p)) ? "ok" : "notfound";
75+
}
76+
77+
async function main() {
78+
const raw = fs.readFileSync(REGISTRY_PATH, "utf-8");
79+
const registry = JSON.parse(raw) as { features: Feature[] };
80+
81+
const results: Record<string, FeatureDocStatus> = {};
82+
83+
// Probe OG URLs in parallel; shell check is sync filesystem.
84+
const entries = await Promise.all(
85+
registry.features.map(async (f) => {
86+
const og = await probeOg(f.og_docs_url);
87+
const shell = probeShell(f.shell_docs_url);
88+
return [f.id, { og, shell }] as const;
89+
}),
90+
);
91+
for (const [id, status] of entries) results[id] = status;
92+
93+
const generatedAt = new Date().toISOString();
94+
fs.writeFileSync(
95+
OUTPUT_PATH,
96+
JSON.stringify({ generated_at: generatedAt, features: results }, null, 2),
97+
);
98+
99+
// Per-feature summary is noisy; print aggregate counts.
100+
const counts = { ok: 0, missing: 0, notfound: 0, error: 0 };
101+
for (const s of Object.values(results)) {
102+
counts[s.og]++;
103+
counts[s.shell]++;
104+
}
105+
console.log(
106+
`Wrote ${OUTPUT_PATH} (${registry.features.length} features × 2 links)`,
107+
);
108+
console.log(
109+
` ok=${counts.ok} notfound=${counts.notfound} error=${counts.error} missing=${counts.missing}`,
110+
);
111+
}
112+
113+
main().catch((err) => {
114+
console.error("[probe-docs] fatal:", err);
115+
process.exit(1);
116+
});

0 commit comments

Comments
 (0)