docs-ready 0.3.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-4NYEWEU2.js +73 -0
- package/dist/chunk-4NYEWEU2.js.map +1 -0
- package/dist/chunk-5XFC7QYB.js +33 -0
- package/dist/chunk-5XFC7QYB.js.map +1 -0
- package/dist/chunk-QI2AROM3.js +153 -0
- package/dist/chunk-QI2AROM3.js.map +1 -0
- package/dist/{generate-LDVEG2WV.js → generate-4EHDLBJX.js} +41 -245
- package/dist/generate-4EHDLBJX.js.map +1 -0
- package/dist/guard-VC7ENYOC.js +359 -0
- package/dist/guard-VC7ENYOC.js.map +1 -0
- package/dist/index.js +7 -5
- package/dist/index.js.map +1 -1
- package/dist/validate-ITHI7RFF.js +219 -0
- package/dist/validate-ITHI7RFF.js.map +1 -0
- package/dist/workflow-PTPU42JU.js +75 -0
- package/dist/workflow-PTPU42JU.js.map +1 -0
- package/package.json +2 -1
- package/dist/generate-LDVEG2WV.js.map +0 -1
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/core/scanner.ts
|
|
4
|
+
import fs from "fs/promises";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import matter from "gray-matter";
|
|
7
|
+
import { glob } from "glob";
|
|
8
|
+
async function scanDocs(docsDir, options) {
|
|
9
|
+
const resolvedDir = path.resolve(docsDir);
|
|
10
|
+
const allFiles = [];
|
|
11
|
+
for (const pattern of options.include) {
|
|
12
|
+
const matches = await glob(pattern, {
|
|
13
|
+
cwd: resolvedDir,
|
|
14
|
+
ignore: options.exclude,
|
|
15
|
+
nodir: true,
|
|
16
|
+
absolute: false
|
|
17
|
+
});
|
|
18
|
+
allFiles.push(...matches);
|
|
19
|
+
}
|
|
20
|
+
const uniqueFiles = [...new Set(allFiles)];
|
|
21
|
+
const pages = [];
|
|
22
|
+
for (const relativePath of uniqueFiles) {
|
|
23
|
+
const filePath = path.join(resolvedDir, relativePath);
|
|
24
|
+
const raw = await fs.readFile(filePath, "utf-8");
|
|
25
|
+
const { data: frontmatter, content } = matter(raw);
|
|
26
|
+
const title = resolveTitle(frontmatter, content, relativePath);
|
|
27
|
+
const description = frontmatter.description ?? null;
|
|
28
|
+
pages.push({
|
|
29
|
+
filePath,
|
|
30
|
+
relativePath: relativePath.replace(/\\/g, "/"),
|
|
31
|
+
title,
|
|
32
|
+
description,
|
|
33
|
+
frontmatter,
|
|
34
|
+
content
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
pages.sort((a, b) => {
|
|
38
|
+
const posA = a.frontmatter.sidebar_position ?? Infinity;
|
|
39
|
+
const posB = b.frontmatter.sidebar_position ?? Infinity;
|
|
40
|
+
if (posA !== posB) return posA - posB;
|
|
41
|
+
return a.relativePath.localeCompare(b.relativePath);
|
|
42
|
+
});
|
|
43
|
+
return pages;
|
|
44
|
+
}
|
|
45
|
+
function resolveTitle(frontmatter, content, relativePath) {
|
|
46
|
+
if (typeof frontmatter.title === "string" && frontmatter.title.trim()) {
|
|
47
|
+
return frontmatter.title.trim();
|
|
48
|
+
}
|
|
49
|
+
const h1Match = content.match(/^#\s+(.+)$/m);
|
|
50
|
+
if (h1Match) {
|
|
51
|
+
return h1Match[1].trim();
|
|
52
|
+
}
|
|
53
|
+
const basename = path.basename(relativePath, path.extname(relativePath));
|
|
54
|
+
return basename.charAt(0).toUpperCase() + basename.slice(1).replace(/[-_]/g, " ");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/utils/tokens.ts
|
|
58
|
+
function estimateTokens(content) {
|
|
59
|
+
return Math.ceil(content.length / 4);
|
|
60
|
+
}
|
|
61
|
+
function formatTokens(count) {
|
|
62
|
+
if (count >= 1e3) {
|
|
63
|
+
return `~${(count / 1e3).toFixed(1)}K tokens`;
|
|
64
|
+
}
|
|
65
|
+
return `~${count} tokens`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export {
|
|
69
|
+
scanDocs,
|
|
70
|
+
estimateTokens,
|
|
71
|
+
formatTokens
|
|
72
|
+
};
|
|
73
|
+
//# sourceMappingURL=chunk-4NYEWEU2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/scanner.ts","../src/utils/tokens.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport matter from \"gray-matter\";\nimport { glob } from \"glob\";\n\nexport interface ScannedPage {\n filePath: string;\n relativePath: string;\n title: string;\n description: string | null;\n frontmatter: Record<string, unknown>;\n content: string;\n}\n\nexport interface ScanOptions {\n include: string[];\n exclude: string[];\n}\n\nexport async function scanDocs(docsDir: string, options: ScanOptions): Promise<ScannedPage[]> {\n const resolvedDir = path.resolve(docsDir);\n const allFiles: string[] = [];\n\n for (const pattern of options.include) {\n const matches = await glob(pattern, {\n cwd: resolvedDir,\n ignore: options.exclude,\n nodir: true,\n absolute: false,\n });\n allFiles.push(...matches);\n }\n\n const uniqueFiles = [...new Set(allFiles)];\n const pages: ScannedPage[] = [];\n\n for (const relativePath of uniqueFiles) {\n const filePath = path.join(resolvedDir, relativePath);\n const raw = await fs.readFile(filePath, \"utf-8\");\n const { data: frontmatter, content } = matter(raw);\n\n const title = resolveTitle(frontmatter, content, relativePath);\n const description = (frontmatter.description as string) ?? null;\n\n pages.push({\n filePath,\n relativePath: relativePath.replace(/\\\\/g, \"/\"),\n title,\n description,\n frontmatter,\n content,\n });\n }\n\n pages.sort((a, b) => {\n const posA = (a.frontmatter.sidebar_position as number) ?? Infinity;\n const posB = (b.frontmatter.sidebar_position as number) ?? Infinity;\n if (posA !== posB) return posA - posB;\n return a.relativePath.localeCompare(b.relativePath);\n });\n\n return pages;\n}\n\nfunction resolveTitle(\n frontmatter: Record<string, unknown>,\n content: string,\n relativePath: string\n): string {\n if (typeof frontmatter.title === \"string\" && frontmatter.title.trim()) {\n return frontmatter.title.trim();\n }\n\n const h1Match = content.match(/^#\\s+(.+)$/m);\n if (h1Match) {\n return h1Match[1].trim();\n }\n\n const basename = path.basename(relativePath, path.extname(relativePath));\n return basename.charAt(0).toUpperCase() + basename.slice(1).replace(/[-_]/g, \" \");\n}\n","/**\n * Estimate token count using chars/4 heuristic.\n * Accurate within ~15% for English technical documentation.\n */\nexport function estimateTokens(content: string): number {\n return Math.ceil(content.length / 4);\n}\n\n/**\n * Format token count for display (e.g. \"~12.5K tokens\")\n */\nexport function formatTokens(count: number): string {\n if (count >= 1000) {\n return `~${(count / 1000).toFixed(1)}K tokens`;\n }\n return `~${count} tokens`;\n}\n"],"mappings":";;;AAAA,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,OAAO,YAAY;AACnB,SAAS,YAAY;AAgBrB,eAAsB,SAAS,SAAiB,SAA8C;AAC5F,QAAM,cAAc,KAAK,QAAQ,OAAO;AACxC,QAAM,WAAqB,CAAC;AAE5B,aAAW,WAAW,QAAQ,SAAS;AACrC,UAAM,UAAU,MAAM,KAAK,SAAS;AAAA,MAClC,KAAK;AAAA,MACL,QAAQ,QAAQ;AAAA,MAChB,OAAO;AAAA,MACP,UAAU;AAAA,IACZ,CAAC;AACD,aAAS,KAAK,GAAG,OAAO;AAAA,EAC1B;AAEA,QAAM,cAAc,CAAC,GAAG,IAAI,IAAI,QAAQ,CAAC;AACzC,QAAM,QAAuB,CAAC;AAE9B,aAAW,gBAAgB,aAAa;AACtC,UAAM,WAAW,KAAK,KAAK,aAAa,YAAY;AACpD,UAAM,MAAM,MAAM,GAAG,SAAS,UAAU,OAAO;AAC/C,UAAM,EAAE,MAAM,aAAa,QAAQ,IAAI,OAAO,GAAG;AAEjD,UAAM,QAAQ,aAAa,aAAa,SAAS,YAAY;AAC7D,UAAM,cAAe,YAAY,eAA0B;AAE3D,UAAM,KAAK;AAAA,MACT;AAAA,MACA,cAAc,aAAa,QAAQ,OAAO,GAAG;AAAA,MAC7C;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAEA,QAAM,KAAK,CAAC,GAAG,MAAM;AACnB,UAAM,OAAQ,EAAE,YAAY,oBAA+B;AAC3D,UAAM,OAAQ,EAAE,YAAY,oBAA+B;AAC3D,QAAI,SAAS,KAAM,QAAO,OAAO;AACjC,WAAO,EAAE,aAAa,cAAc,EAAE,YAAY;AAAA,EACpD,CAAC;AAED,SAAO;AACT;AAEA,SAAS,aACP,aACA,SACA,cACQ;AACR,MAAI,OAAO,YAAY,UAAU,YAAY,YAAY,MAAM,KAAK,GAAG;AACrE,WAAO,YAAY,MAAM,KAAK;AAAA,EAChC;AAEA,QAAM,UAAU,QAAQ,MAAM,aAAa;AAC3C,MAAI,SAAS;AACX,WAAO,QAAQ,CAAC,EAAE,KAAK;AAAA,EACzB;AAEA,QAAM,WAAW,KAAK,SAAS,cAAc,KAAK,QAAQ,YAAY,CAAC;AACvE,SAAO,SAAS,OAAO,CAAC,EAAE,YAAY,IAAI,SAAS,MAAM,CAAC,EAAE,QAAQ,SAAS,GAAG;AAClF;;;AC5EO,SAAS,eAAe,SAAyB;AACtD,SAAO,KAAK,KAAK,QAAQ,SAAS,CAAC;AACrC;AAKO,SAAS,aAAa,OAAuB;AAClD,MAAI,SAAS,KAAM;AACjB,WAAO,KAAK,QAAQ,KAAM,QAAQ,CAAC,CAAC;AAAA,EACtC;AACA,SAAO,IAAI,KAAK;AAClB;","names":[]}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/utils/http.ts
|
|
4
|
+
async function fetchWithTimeout(url, options = {}) {
|
|
5
|
+
const { timeout = 1e4, headers = {} } = options;
|
|
6
|
+
const controller = new AbortController();
|
|
7
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
8
|
+
try {
|
|
9
|
+
const response = await fetch(url, {
|
|
10
|
+
signal: controller.signal,
|
|
11
|
+
headers
|
|
12
|
+
});
|
|
13
|
+
return response;
|
|
14
|
+
} finally {
|
|
15
|
+
clearTimeout(timeoutId);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function githubHeaders() {
|
|
19
|
+
const headers = {
|
|
20
|
+
Accept: "application/vnd.github.v3+json"
|
|
21
|
+
};
|
|
22
|
+
const token = process.env.GITHUB_TOKEN;
|
|
23
|
+
if (token) {
|
|
24
|
+
headers.Authorization = `Bearer ${token}`;
|
|
25
|
+
}
|
|
26
|
+
return headers;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export {
|
|
30
|
+
fetchWithTimeout,
|
|
31
|
+
githubHeaders
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=chunk-5XFC7QYB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/http.ts"],"sourcesContent":["export interface FetchOptions {\n timeout?: number;\n headers?: Record<string, string>;\n}\n\nexport async function fetchWithTimeout(\n url: string,\n options: FetchOptions = {}\n): Promise<Response> {\n const { timeout = 10000, headers = {} } = options;\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try {\n const response = await fetch(url, {\n signal: controller.signal,\n headers,\n });\n return response;\n } finally {\n clearTimeout(timeoutId);\n }\n}\n\nexport function githubHeaders(): Record<string, string> {\n const headers: Record<string, string> = {\n Accept: \"application/vnd.github.v3+json\",\n };\n const token = process.env.GITHUB_TOKEN;\n if (token) {\n headers.Authorization = `Bearer ${token}`;\n }\n return headers;\n}\n"],"mappings":";;;AAKA,eAAsB,iBACpB,KACA,UAAwB,CAAC,GACN;AACnB,QAAM,EAAE,UAAU,KAAO,UAAU,CAAC,EAAE,IAAI;AAC1C,QAAM,aAAa,IAAI,gBAAgB;AACvC,QAAM,YAAY,WAAW,MAAM,WAAW,MAAM,GAAG,OAAO;AAE9D,MAAI;AACF,UAAM,WAAW,MAAM,MAAM,KAAK;AAAA,MAChC,QAAQ,WAAW;AAAA,MACnB;AAAA,IACF,CAAC;AACD,WAAO;AAAA,EACT,UAAE;AACA,iBAAa,SAAS;AAAA,EACxB;AACF;AAEO,SAAS,gBAAwC;AACtD,QAAM,UAAkC;AAAA,IACtC,QAAQ;AAAA,EACV;AACA,QAAM,QAAQ,QAAQ,IAAI;AAC1B,MAAI,OAAO;AACT,YAAQ,gBAAgB,UAAU,KAAK;AAAA,EACzC;AACA,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/core/config.ts
|
|
4
|
+
import fs from "fs/promises";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import YAML from "yaml";
|
|
7
|
+
var DEFAULTS = {
|
|
8
|
+
docs: {
|
|
9
|
+
dir: "./docs",
|
|
10
|
+
include: ["**/*.md", "**/*.mdx"],
|
|
11
|
+
exclude: ["**/node_modules/**", "**/_*"]
|
|
12
|
+
},
|
|
13
|
+
generate: {
|
|
14
|
+
llms_txt: true,
|
|
15
|
+
llms_full_txt: true,
|
|
16
|
+
ai_context: true,
|
|
17
|
+
output_dir: "./build"
|
|
18
|
+
},
|
|
19
|
+
guard: {
|
|
20
|
+
npm_packages: [],
|
|
21
|
+
github_releases: [],
|
|
22
|
+
endpoints: [],
|
|
23
|
+
readme_scans: [],
|
|
24
|
+
workflow: {
|
|
25
|
+
enabled: true,
|
|
26
|
+
schedule: "0 9 */3 * *",
|
|
27
|
+
create_issues: true,
|
|
28
|
+
labels: ["ai-context-review", "documentation"]
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
deploy: {
|
|
32
|
+
platform: "none",
|
|
33
|
+
cors: { enabled: true, origins: ["*"] }
|
|
34
|
+
},
|
|
35
|
+
validate: {
|
|
36
|
+
max_tokens: 15e4,
|
|
37
|
+
check_links: true,
|
|
38
|
+
check_coverage: true,
|
|
39
|
+
coverage_threshold: 0.95
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
var CONFIG_FILES = [
|
|
43
|
+
{ name: ".docs-ready.yaml", parser: "yaml" },
|
|
44
|
+
{ name: ".docs-ready.yml", parser: "yaml" },
|
|
45
|
+
{ name: ".docs-ready.json", parser: "json" },
|
|
46
|
+
{ name: ".docs-ready.toml", parser: "toml" }
|
|
47
|
+
];
|
|
48
|
+
async function loadConfig(dir, configPath) {
|
|
49
|
+
let raw;
|
|
50
|
+
if (configPath) {
|
|
51
|
+
const content = await fs.readFile(configPath, "utf-8");
|
|
52
|
+
raw = parseConfig(content, configPath);
|
|
53
|
+
} else {
|
|
54
|
+
for (const { name, parser } of CONFIG_FILES) {
|
|
55
|
+
const fullPath = path.join(dir, name);
|
|
56
|
+
try {
|
|
57
|
+
const content = await fs.readFile(fullPath, "utf-8");
|
|
58
|
+
if (parser === "toml") {
|
|
59
|
+
throw new Error("TOML support requires a TOML parser. Use .yaml or .json instead.");
|
|
60
|
+
}
|
|
61
|
+
raw = parseConfig(content, fullPath);
|
|
62
|
+
break;
|
|
63
|
+
} catch (err) {
|
|
64
|
+
if (err.code === "ENOENT") continue;
|
|
65
|
+
throw err;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (!raw) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
"No config file found. Run `docs-ready init` to create one, or create .docs-ready.yaml manually."
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
validate(raw);
|
|
75
|
+
return applyDefaults(raw);
|
|
76
|
+
}
|
|
77
|
+
function parseConfig(content, filePath) {
|
|
78
|
+
if (filePath.endsWith(".json")) {
|
|
79
|
+
return JSON.parse(content);
|
|
80
|
+
}
|
|
81
|
+
const parsed = YAML.parse(content);
|
|
82
|
+
if (parsed === null || typeof parsed !== "object") {
|
|
83
|
+
throw new Error(`Failed to parse config file: ${filePath}`);
|
|
84
|
+
}
|
|
85
|
+
return parsed;
|
|
86
|
+
}
|
|
87
|
+
function validate(raw) {
|
|
88
|
+
const missing = [];
|
|
89
|
+
if (!raw.title || typeof raw.title !== "string") missing.push("title");
|
|
90
|
+
if (!raw.description || typeof raw.description !== "string") missing.push("description");
|
|
91
|
+
if (!raw.url || typeof raw.url !== "string") missing.push("url");
|
|
92
|
+
if (missing.length > 0) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
`Missing required config fields: ${missing.join(", ")}. Add them to your .docs-ready.yaml file.`
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function applyDefaults(raw) {
|
|
99
|
+
const docs = raw.docs;
|
|
100
|
+
const generate = raw.generate;
|
|
101
|
+
const guard = raw.guard;
|
|
102
|
+
const deploy = raw.deploy;
|
|
103
|
+
const validateConf = raw.validate;
|
|
104
|
+
const guardWorkflow = guard?.workflow ?? {};
|
|
105
|
+
return {
|
|
106
|
+
title: raw.title,
|
|
107
|
+
description: raw.description,
|
|
108
|
+
url: raw.url,
|
|
109
|
+
docs: {
|
|
110
|
+
dir: docs?.dir ?? DEFAULTS.docs.dir,
|
|
111
|
+
include: docs?.include ?? DEFAULTS.docs.include,
|
|
112
|
+
exclude: docs?.exclude ?? DEFAULTS.docs.exclude
|
|
113
|
+
},
|
|
114
|
+
generate: {
|
|
115
|
+
llms_txt: generate?.llms_txt ?? DEFAULTS.generate.llms_txt,
|
|
116
|
+
llms_full_txt: generate?.llms_full_txt ?? DEFAULTS.generate.llms_full_txt,
|
|
117
|
+
ai_context: generate?.ai_context ?? DEFAULTS.generate.ai_context,
|
|
118
|
+
output_dir: generate?.output_dir ?? DEFAULTS.generate.output_dir,
|
|
119
|
+
sections: generate?.sections,
|
|
120
|
+
ai_context_config: generate?.ai_context_config
|
|
121
|
+
},
|
|
122
|
+
guard: {
|
|
123
|
+
npm_packages: guard?.npm_packages ?? DEFAULTS.guard.npm_packages,
|
|
124
|
+
github_releases: guard?.github_releases ?? DEFAULTS.guard.github_releases,
|
|
125
|
+
endpoints: guard?.endpoints ?? DEFAULTS.guard.endpoints,
|
|
126
|
+
readme_scans: guard?.readme_scans ?? DEFAULTS.guard.readme_scans,
|
|
127
|
+
workflow: {
|
|
128
|
+
enabled: guardWorkflow.enabled ?? DEFAULTS.guard.workflow.enabled,
|
|
129
|
+
schedule: guardWorkflow.schedule ?? DEFAULTS.guard.workflow.schedule,
|
|
130
|
+
create_issues: guardWorkflow.create_issues ?? DEFAULTS.guard.workflow.create_issues,
|
|
131
|
+
labels: guardWorkflow.labels ?? DEFAULTS.guard.workflow.labels
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
deploy: {
|
|
135
|
+
platform: deploy?.platform ?? DEFAULTS.deploy.platform,
|
|
136
|
+
cors: {
|
|
137
|
+
enabled: deploy?.cors?.enabled ?? DEFAULTS.deploy.cors.enabled,
|
|
138
|
+
origins: deploy?.cors?.origins ?? DEFAULTS.deploy.cors.origins
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
validate: {
|
|
142
|
+
max_tokens: validateConf?.max_tokens ?? DEFAULTS.validate.max_tokens,
|
|
143
|
+
check_links: validateConf?.check_links ?? DEFAULTS.validate.check_links,
|
|
144
|
+
check_coverage: validateConf?.check_coverage ?? DEFAULTS.validate.check_coverage,
|
|
145
|
+
coverage_threshold: validateConf?.coverage_threshold ?? DEFAULTS.validate.coverage_threshold
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export {
|
|
151
|
+
loadConfig
|
|
152
|
+
};
|
|
153
|
+
//# sourceMappingURL=chunk-QI2AROM3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/config.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport YAML from \"yaml\";\n\nexport interface DocsReadyConfig {\n title: string;\n description: string;\n url: string;\n docs: {\n dir: string;\n include: string[];\n exclude: string[];\n };\n generate: {\n llms_txt: boolean;\n llms_full_txt: boolean;\n ai_context: boolean;\n output_dir: string;\n sections?: Array<{ title: string; patterns: string[] }>;\n ai_context_config?: {\n key_pages?: Array<{ path: string; section: string }>;\n extra_sections?: Array<{ title: string; source?: string; auto_extract?: boolean }>;\n };\n };\n guard: {\n npm_packages: Array<{ name: string; label: string }>;\n github_releases: Array<{ repo: string; label: string }>;\n endpoints: Array<{ url: string; label: string; expected_status?: number[] }>;\n readme_scans: Array<{ repo: string; keywords: string[] }>;\n workflow: {\n enabled: boolean;\n schedule: string;\n create_issues: boolean;\n labels: string[];\n };\n };\n deploy: {\n platform: \"vercel\" | \"netlify\" | \"cloudflare\" | \"none\";\n cors: { enabled: boolean; origins: string[] };\n };\n validate: {\n max_tokens: number;\n check_links: boolean;\n check_coverage: boolean;\n coverage_threshold: number;\n };\n}\n\nconst DEFAULTS: Omit<DocsReadyConfig, \"title\" | \"description\" | \"url\"> = {\n docs: {\n dir: \"./docs\",\n include: [\"**/*.md\", \"**/*.mdx\"],\n exclude: [\"**/node_modules/**\", \"**/_*\"],\n },\n generate: {\n llms_txt: true,\n llms_full_txt: true,\n ai_context: true,\n output_dir: \"./build\",\n },\n guard: {\n npm_packages: [],\n github_releases: [],\n endpoints: [],\n readme_scans: [],\n workflow: {\n enabled: true,\n schedule: \"0 9 */3 * *\",\n create_issues: true,\n labels: [\"ai-context-review\", \"documentation\"],\n },\n },\n deploy: {\n platform: \"none\",\n cors: { enabled: true, origins: [\"*\"] },\n },\n validate: {\n max_tokens: 150000,\n check_links: true,\n check_coverage: true,\n coverage_threshold: 0.95,\n },\n};\n\nconst CONFIG_FILES = [\n { name: \".docs-ready.yaml\", parser: \"yaml\" as const },\n { name: \".docs-ready.yml\", parser: \"yaml\" as const },\n { name: \".docs-ready.json\", parser: \"json\" as const },\n { name: \".docs-ready.toml\", parser: \"toml\" as const },\n];\n\nexport async function loadConfig(dir: string, configPath?: string): Promise<DocsReadyConfig> {\n let raw: Record<string, unknown> | undefined;\n\n if (configPath) {\n const content = await fs.readFile(configPath, \"utf-8\");\n raw = parseConfig(content, configPath);\n } else {\n for (const { name, parser } of CONFIG_FILES) {\n const fullPath = path.join(dir, name);\n try {\n const content = await fs.readFile(fullPath, \"utf-8\");\n if (parser === \"toml\") {\n throw new Error(\"TOML support requires a TOML parser. Use .yaml or .json instead.\");\n }\n raw = parseConfig(content, fullPath);\n break;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") continue;\n throw err;\n }\n }\n }\n\n if (!raw) {\n throw new Error(\n \"No config file found. Run `docs-ready init` to create one, or create .docs-ready.yaml manually.\"\n );\n }\n\n validate(raw);\n return applyDefaults(raw);\n}\n\nfunction parseConfig(content: string, filePath: string): Record<string, unknown> {\n if (filePath.endsWith(\".json\")) {\n return JSON.parse(content);\n }\n const parsed = YAML.parse(content);\n if (parsed === null || typeof parsed !== \"object\") {\n throw new Error(`Failed to parse config file: ${filePath}`);\n }\n return parsed;\n}\n\nfunction validate(raw: Record<string, unknown>): void {\n const missing: string[] = [];\n if (!raw.title || typeof raw.title !== \"string\") missing.push(\"title\");\n if (!raw.description || typeof raw.description !== \"string\") missing.push(\"description\");\n if (!raw.url || typeof raw.url !== \"string\") missing.push(\"url\");\n\n if (missing.length > 0) {\n throw new Error(\n `Missing required config fields: ${missing.join(\", \")}. ` +\n `Add them to your .docs-ready.yaml file.`\n );\n }\n}\n\nfunction applyDefaults(raw: Record<string, unknown>): DocsReadyConfig {\n const docs = raw.docs as Record<string, unknown> | undefined;\n const generate = raw.generate as Record<string, unknown> | undefined;\n const guard = raw.guard as Record<string, unknown> | undefined;\n const deploy = raw.deploy as Record<string, unknown> | undefined;\n const validateConf = raw.validate as Record<string, unknown> | undefined;\n const guardWorkflow = (guard?.workflow as Record<string, unknown>) ?? {};\n\n return {\n title: raw.title as string,\n description: raw.description as string,\n url: raw.url as string,\n docs: {\n dir: (docs?.dir as string) ?? DEFAULTS.docs.dir,\n include: (docs?.include as string[]) ?? DEFAULTS.docs.include,\n exclude: (docs?.exclude as string[]) ?? DEFAULTS.docs.exclude,\n },\n generate: {\n llms_txt: (generate?.llms_txt as boolean) ?? DEFAULTS.generate.llms_txt,\n llms_full_txt: (generate?.llms_full_txt as boolean) ?? DEFAULTS.generate.llms_full_txt,\n ai_context: (generate?.ai_context as boolean) ?? DEFAULTS.generate.ai_context,\n output_dir: (generate?.output_dir as string) ?? DEFAULTS.generate.output_dir,\n sections: generate?.sections as DocsReadyConfig[\"generate\"][\"sections\"],\n ai_context_config: generate?.ai_context_config as DocsReadyConfig[\"generate\"][\"ai_context_config\"],\n },\n guard: {\n npm_packages: (guard?.npm_packages as DocsReadyConfig[\"guard\"][\"npm_packages\"]) ?? DEFAULTS.guard.npm_packages,\n github_releases: (guard?.github_releases as DocsReadyConfig[\"guard\"][\"github_releases\"]) ?? DEFAULTS.guard.github_releases,\n endpoints: (guard?.endpoints as DocsReadyConfig[\"guard\"][\"endpoints\"]) ?? DEFAULTS.guard.endpoints,\n readme_scans: (guard?.readme_scans as DocsReadyConfig[\"guard\"][\"readme_scans\"]) ?? DEFAULTS.guard.readme_scans,\n workflow: {\n enabled: (guardWorkflow.enabled as boolean) ?? DEFAULTS.guard.workflow.enabled,\n schedule: (guardWorkflow.schedule as string) ?? DEFAULTS.guard.workflow.schedule,\n create_issues: (guardWorkflow.create_issues as boolean) ?? DEFAULTS.guard.workflow.create_issues,\n labels: (guardWorkflow.labels as string[]) ?? DEFAULTS.guard.workflow.labels,\n },\n },\n deploy: {\n platform: (deploy?.platform as DocsReadyConfig[\"deploy\"][\"platform\"]) ?? DEFAULTS.deploy.platform,\n cors: {\n enabled: ((deploy?.cors as Record<string, unknown>)?.enabled as boolean) ?? DEFAULTS.deploy.cors.enabled,\n origins: ((deploy?.cors as Record<string, unknown>)?.origins as string[]) ?? DEFAULTS.deploy.cors.origins,\n },\n },\n validate: {\n max_tokens: (validateConf?.max_tokens as number) ?? DEFAULTS.validate.max_tokens,\n check_links: (validateConf?.check_links as boolean) ?? DEFAULTS.validate.check_links,\n check_coverage: (validateConf?.check_coverage as boolean) ?? DEFAULTS.validate.check_coverage,\n coverage_threshold: (validateConf?.coverage_threshold as number) ?? DEFAULTS.validate.coverage_threshold,\n },\n };\n}\n"],"mappings":"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","names":[]}
|