codex-toys/scripts/check-docs.ts

178 lines
7.5 KiB
TypeScript

import { spawn } from "node:child_process";
import { readFile } from "node:fs/promises";
import { fileURLToPath } from "node:url";
const checks: string[] = [];
const failures: string[] = [];
const root = new URL("..", import.meta.url);
const rootPath = fileURLToPath(root);
const textFiles = new Map<string, string>();
async function read(relativePath: string): Promise<string> {
const cached = textFiles.get(relativePath);
if (cached !== undefined) {
return cached;
}
const text = await readFile(new URL(relativePath, root), "utf8");
textFiles.set(relativePath, text);
return text;
}
async function expectIncludes(file: string, needle: string, label?: string): Promise<void> {
const text = await read(file);
checks.push(`${file}: ${label ?? needle}`);
if (!text.includes(needle)) {
failures.push(`${file} is missing ${JSON.stringify(needle)}`);
}
}
async function expectExcludes(file: string, needle: string, label?: string): Promise<void> {
const text = await read(file);
checks.push(`${file}: excludes ${label ?? needle}`);
if (text.includes(needle)) {
failures.push(`${file} should not contain ${JSON.stringify(needle)}`);
}
}
async function main(): Promise<void> {
const helpProc = spawn(process.execPath, ["--import", "tsx", "packages/codex-client/src/cli/index.ts", "--help"], {
cwd: rootPath,
});
const [help, helpError, helpExit] = await Promise.all([
collectText(helpProc.stdout),
collectText(helpProc.stderr),
exitCodeFor(helpProc),
]);
if (helpExit !== 0) {
process.stderr.write(helpError);
process.stderr.write(help);
process.exit(helpExit);
}
const cliDoc = await read("docs/pages/reference/cli.md");
const requiredCliLines = [
"codex-flows fetch [--json] [--no-color]",
"codex-flows app <method> [params-json]",
"codex-flows workspace doctor [--mode auto|local|actions] [--json]",
"codex-flows workspace tick [--mode auto|local|actions]",
"codex-flows workspace run <task-id> [--mode auto|local|actions]",
"codex-flows memories transplant global-to-workspace [--apply]",
"codex-flows memories transplant workspace-to-global [--apply]",
"codex-flows threads locate <thread-id> [--codex-home <home>]",
"codex-flows threads inspect <thread-id-or-rollout.jsonl> [--codex-home <home>]",
"codex-flows threads install-rollout <rollout.jsonl> [--codex-home <home>] [--replace]",
"codex-flows threads transplant <thread-id> --from-codex-home <src> --to-codex-home <dst> [--replace]",
"codex-flows pack inspect <source> [--json]",
"codex-flows pack add <source> [--apply] [--include <name>] [--exclude <name>]",
"codex-flows pack doctor [--json]",
"codex-flows pack list [--json]",
"codex-flows flow dispatch --event <event.json>",
"--merge codex",
];
for (const line of requiredCliLines) {
checks.push(`CLI help includes ${line}`);
if (!help.includes(line)) {
failures.push(`CLI help is missing ${JSON.stringify(line)}`);
}
checks.push(`CLI docs include ${line}`);
if (!cliDoc.includes(line)) {
failures.push(`docs/pages/reference/cli.md is missing ${JSON.stringify(line)}`);
}
}
await expectExcludes("README.md", "Codex Bare");
await expectExcludes("README.md", "DEVELOP.md");
await expectExcludes("package.json", "codex-bare");
await expectExcludes("package.json", "Thin web UI");
await expectExcludes("SECURITY.md", "codex-bare");
await expectIncludes("SECURITY.md", "Memory transplant");
await expectIncludes("README.md", "docs/pages/guides/workspace-autonomy.md");
await expectIncludes("README.md", "docs/pages/guides/memory-transplant.md");
await expectIncludes("README.md", "docs/pages/guides/thread-transplant.md");
await expectIncludes("README.md", "docs/pages/guides/install-pack-repos.md");
await expectIncludes("docs/tome.config.js", "\"guides/workspace-autonomy\"");
await expectIncludes("docs/tome.config.js", "\"guides/memory-transplant\"");
await expectIncludes("docs/tome.config.js", "\"guides/thread-transplant\"");
await expectIncludes("docs/tome.config.js", "\"guides/install-pack-repos\"");
await expectIncludes("docs/tome.config.js", "RELEASE.md");
await expectIncludes("docs/index.html", "<title>codex-flows</title>");
await expectIncludes("docs/pages/index.md", "Workspace autonomy");
await expectIncludes("docs/pages/index.md", "Memory transplant");
await expectIncludes("docs/pages/index.md", "Thread Transplant");
await expectIncludes("docs/pages/index.md", "Pack Install");
await expectIncludes("docs/pages/index.md", "@peezy.tech/codex-flows");
await expectIncludes("docs/pages/reference/packages.md", "workspace autonomy");
await expectIncludes("docs/pages/reference/packages.md", "memory transplant");
await expectIncludes("docs/pages/reference/packages.md", "@peezy.tech/codex-flows/threads");
await expectIncludes("docs/pages/guides/workspace-autonomy.md", "[workspace]");
await expectIncludes("docs/pages/guides/workspace-autonomy.md", ".codex/workspace/local");
await expectIncludes("docs/pages/guides/workspace-autonomy.md", ".codex/workspace/actions");
await expectIncludes("docs/pages/guides/workspace-autonomy.md", "CODEX_WORKSPACE_MODE=actions");
await expectIncludes("docs/pages/guides/memory-transplant.md", "MEMORY.md");
await expectIncludes("docs/pages/guides/memory-transplant.md", "memory_summary.md");
await expectIncludes("docs/pages/guides/memory-transplant.md", "raw_memories.md");
await expectIncludes("docs/pages/guides/memory-transplant.md", "rollout_summaries/*.md");
await expectIncludes("docs/pages/guides/memory-transplant.md", "sqlite");
await expectIncludes("docs/pages/guides/memory-transplant.md", "skills");
await expectIncludes("docs/pages/guides/thread-transplant.md", "install-rollout");
await expectIncludes("docs/pages/guides/thread-transplant.md", "sessions/<YYYY>/<MM>/<DD>/<rollout-file>.jsonl");
await expectIncludes("docs/pages/guides/thread-transplant.md", "--replace");
await expectIncludes("docs/pages/guides/thread-transplant.md", "not app-server-native import");
await expectIncludes("docs/pages/guides/install-pack-repos.md", "pack repo");
await expectIncludes("docs/pages/guides/install-pack-repos.md", ".codex/pack-lock.json");
await expectIncludes("docs/pages/guides/install-pack-repos.md", ".agents/plugins/marketplace.json");
await expectIncludes("docs/pages/guides/install-pack-repos.md", "[features].plugin_hooks = true");
await expectIncludes("packages/codex-client/README.md", "codex-flows workspace doctor");
await expectIncludes("packages/codex-client/README.md", "codex-flows memories transplant global-to-workspace");
await expectIncludes("packages/codex-client/README.md", "codex-flows threads transplant <thread-id>");
await expectIncludes("packages/codex-client/README.md", "codex-flows pack inspect owner/repo");
if (failures.length > 0) {
for (const failure of failures) {
console.error(`docs check failed: ${failure}`);
}
console.error(`docs check inspected ${checks.length} conditions`);
process.exit(1);
}
console.log(`docs check passed (${checks.length} conditions)`);
}
function collectText(stream: NodeJS.ReadableStream | null): Promise<string> {
return new Promise((resolve, reject) => {
let output = "";
if (!stream) {
resolve(output);
return;
}
stream.setEncoding("utf8");
stream.on("data", (chunk: string) => {
output += chunk;
});
stream.once("error", reject);
stream.once("end", () => resolve(output));
});
}
function exitCodeFor(child: ReturnType<typeof spawn>): Promise<number | null> {
return new Promise((resolve, reject) => {
child.once("error", reject);
child.once("exit", (code) => resolve(code));
});
}
void main().catch((error) => {
console.error(error instanceof Error ? error.message : String(error));
process.exit(1);
});