mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-03 20:13:02 +00:00
125 lines
4.3 KiB
JavaScript
125 lines
4.3 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const root = path.resolve(__dirname, '../..');
|
|
const failures = [];
|
|
|
|
function read(relativePath) {
|
|
return fs.readFileSync(path.join(root, relativePath), 'utf8');
|
|
}
|
|
|
|
function requireText(relativePath, expected) {
|
|
if (!read(relativePath).includes(expected)) {
|
|
failures.push(`${relativePath} is missing: ${expected}`);
|
|
}
|
|
}
|
|
|
|
function collectFiles(directory, filePattern) {
|
|
const files = [];
|
|
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
const absolutePath = path.join(directory, entry.name);
|
|
if (entry.isDirectory()) {
|
|
files.push(...collectFiles(absolutePath, filePattern));
|
|
} else if (entry.isFile() && filePattern.test(entry.name)) {
|
|
files.push(absolutePath);
|
|
}
|
|
}
|
|
return files;
|
|
}
|
|
|
|
const removedGuides = [
|
|
'docs/ccs-bar.md',
|
|
'docs/cursor-integration.md',
|
|
'docs/dashboard-auth-cli.md',
|
|
'docs/session-sharing-technical-analysis.md',
|
|
];
|
|
|
|
for (const relativePath of removedGuides) {
|
|
if (fs.existsSync(path.join(root, relativePath))) {
|
|
failures.push(`${relativePath} should use its canonical public-doc replacement`);
|
|
}
|
|
}
|
|
|
|
requireText('README.md', 'https://docs.ccs.kaitran.ca/features/proxy/openai-compatible-providers');
|
|
requireText('README.md', 'https://docs.ccs.kaitran.ca/features/workflow/browser-automation');
|
|
requireText('docs/browser-automation.md', 'CCS_BROWSER_INTERCEPT_FULFILL_MODE=enabled');
|
|
requireText('docs/browser-automation.md', 'CCS_BROWSER_UPLOAD_ROOTS');
|
|
requireText('docs/browser-automation.md', 'CCS_BROWSER_DOWNLOAD_ROOTS');
|
|
requireText('docs/browser-automation.md', 'browser_wait_for_event');
|
|
requireText('docs/browser-automation.md', 'path-scoped bearer values');
|
|
requireText('docs/codex-auth.md', 'src/codex-auth/codex-auth-help.ts');
|
|
requireText('docs/codex-auth.md', 'CCSXP_CODEX_HOME');
|
|
requireText('docs/image-analysis.md', 'https://docs.ccs.kaitran.ca/features/ai/image-analysis');
|
|
requireText('docs/openai-compatible-providers.md', 'CCS_OPENAI_PROXY_INSECURE');
|
|
requireText('docs/openai-compatible-providers.md', 'CCS_OPENAI_PROXY_REQUEST_TIMEOUT_MS');
|
|
requireText('macos-bar/README.md', 'macos-bar/VERSION');
|
|
requireText('macos-bar/README.md', '.github/workflows/bar-release.yml');
|
|
requireText(
|
|
'.github/ISSUE_TEMPLATE/documentation.yml',
|
|
'https://docs.ccs.kaitran.ca/providers/oauth/cursor'
|
|
);
|
|
|
|
const practicalGuidanceFiles = [
|
|
path.join(root, 'README.md'),
|
|
path.join(root, 'CLAUDE.md'),
|
|
path.join(root, 'CONTRIBUTING.md'),
|
|
path.join(root, 'SECURITY.md'),
|
|
path.join(root, 'docker', 'README.md'),
|
|
path.join(root, 'macos-bar', 'README.md'),
|
|
...collectFiles(path.join(root, 'docs'), /\.mdx?$/),
|
|
...collectFiles(path.join(root, '.github', 'ISSUE_TEMPLATE'), /\.(md|ya?ml)$/),
|
|
];
|
|
|
|
for (const staleGuidePath of removedGuides) {
|
|
for (const guidancePath of practicalGuidanceFiles) {
|
|
if (read(path.relative(root, guidancePath)).includes(staleGuidePath)) {
|
|
failures.push(
|
|
`${path.relative(root, guidancePath)} references deleted guide: ${staleGuidePath}`
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
const markdownFiles = [
|
|
path.join(root, 'README.md'),
|
|
path.join(root, 'docker', 'README.md'),
|
|
path.join(root, 'macos-bar', 'README.md'),
|
|
...collectFiles(path.join(root, 'docs'), /\.mdx?$/),
|
|
];
|
|
const linkPattern = /!?\[[^\]]*]\(([^)]+)\)/g;
|
|
|
|
for (const markdownPath of markdownFiles) {
|
|
const source = fs.readFileSync(markdownPath, 'utf8');
|
|
for (const match of source.matchAll(linkPattern)) {
|
|
let target = match[1].trim().replace(/^<|>$/g, '');
|
|
if (!target || target.startsWith('#') || /^(https?:|mailto:|tel:)/i.test(target)) {
|
|
continue;
|
|
}
|
|
|
|
target = target.split('#', 1)[0].split('?', 1)[0];
|
|
try {
|
|
target = decodeURIComponent(target);
|
|
} catch {
|
|
failures.push(`${path.relative(root, markdownPath)} has invalid link encoding: ${match[1]}`);
|
|
continue;
|
|
}
|
|
|
|
const resolved = path.resolve(path.dirname(markdownPath), target);
|
|
if (!fs.existsSync(resolved)) {
|
|
failures.push(`${path.relative(root, markdownPath)} has missing relative link: ${match[1]}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (failures.length > 0) {
|
|
console.error('[X] Documentation freshness checks failed:');
|
|
for (const failure of failures) {
|
|
console.error(` ${failure}`);
|
|
}
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log('[OK] Documentation pointers, retained contracts, and relative links are current.');
|