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.
@@ -1,216 +1,23 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ estimateTokens,
4
+ formatTokens,
5
+ scanDocs
6
+ } from "./chunk-4NYEWEU2.js";
7
+ import {
8
+ loadConfig
9
+ } from "./chunk-QI2AROM3.js";
2
10
  import {
3
11
  log,
4
12
  spinner
5
13
  } from "./chunk-7YN54Y4Y.js";
6
14
 
7
15
  // src/cli/commands/generate.ts
8
- import fs3 from "fs/promises";
9
- import path5 from "path";
10
-
11
- // src/core/config.ts
12
16
  import fs from "fs/promises";
13
- import path from "path";
14
- import YAML from "yaml";
15
- var DEFAULTS = {
16
- docs: {
17
- dir: "./docs",
18
- include: ["**/*.md", "**/*.mdx"],
19
- exclude: ["**/node_modules/**", "**/_*"]
20
- },
21
- generate: {
22
- llms_txt: true,
23
- llms_full_txt: true,
24
- ai_context: true,
25
- output_dir: "./build"
26
- },
27
- guard: {
28
- npm_packages: [],
29
- github_releases: [],
30
- endpoints: [],
31
- readme_scans: [],
32
- workflow: {
33
- enabled: true,
34
- schedule: "0 9 */3 * *",
35
- create_issues: true,
36
- labels: ["ai-context-review", "documentation"]
37
- }
38
- },
39
- deploy: {
40
- platform: "none",
41
- cors: { enabled: true, origins: ["*"] }
42
- },
43
- validate: {
44
- max_tokens: 15e4,
45
- check_links: true,
46
- check_coverage: true,
47
- coverage_threshold: 0.95
48
- }
49
- };
50
- var CONFIG_FILES = [
51
- { name: ".docs-ready.yaml", parser: "yaml" },
52
- { name: ".docs-ready.yml", parser: "yaml" },
53
- { name: ".docs-ready.json", parser: "json" },
54
- { name: ".docs-ready.toml", parser: "toml" }
55
- ];
56
- async function loadConfig(dir, configPath) {
57
- let raw;
58
- if (configPath) {
59
- const content = await fs.readFile(configPath, "utf-8");
60
- raw = parseConfig(content, configPath);
61
- } else {
62
- for (const { name, parser } of CONFIG_FILES) {
63
- const fullPath = path.join(dir, name);
64
- try {
65
- const content = await fs.readFile(fullPath, "utf-8");
66
- if (parser === "toml") {
67
- throw new Error("TOML support requires a TOML parser. Use .yaml or .json instead.");
68
- }
69
- raw = parseConfig(content, fullPath);
70
- break;
71
- } catch (err) {
72
- if (err.code === "ENOENT") continue;
73
- throw err;
74
- }
75
- }
76
- }
77
- if (!raw) {
78
- throw new Error(
79
- "No config file found. Run `docs-ready init` to create one, or create .docs-ready.yaml manually."
80
- );
81
- }
82
- validate(raw);
83
- return applyDefaults(raw);
84
- }
85
- function parseConfig(content, filePath) {
86
- if (filePath.endsWith(".json")) {
87
- return JSON.parse(content);
88
- }
89
- const parsed = YAML.parse(content);
90
- if (parsed === null || typeof parsed !== "object") {
91
- throw new Error(`Failed to parse config file: ${filePath}`);
92
- }
93
- return parsed;
94
- }
95
- function validate(raw) {
96
- const missing = [];
97
- if (!raw.title || typeof raw.title !== "string") missing.push("title");
98
- if (!raw.description || typeof raw.description !== "string") missing.push("description");
99
- if (!raw.url || typeof raw.url !== "string") missing.push("url");
100
- if (missing.length > 0) {
101
- throw new Error(
102
- `Missing required config fields: ${missing.join(", ")}. Add them to your .docs-ready.yaml file.`
103
- );
104
- }
105
- }
106
- function applyDefaults(raw) {
107
- const docs = raw.docs;
108
- const generate = raw.generate;
109
- const guard = raw.guard;
110
- const deploy = raw.deploy;
111
- const validateConf = raw.validate;
112
- const guardWorkflow = guard?.workflow ?? {};
113
- return {
114
- title: raw.title,
115
- description: raw.description,
116
- url: raw.url,
117
- docs: {
118
- dir: docs?.dir ?? DEFAULTS.docs.dir,
119
- include: docs?.include ?? DEFAULTS.docs.include,
120
- exclude: docs?.exclude ?? DEFAULTS.docs.exclude
121
- },
122
- generate: {
123
- llms_txt: generate?.llms_txt ?? DEFAULTS.generate.llms_txt,
124
- llms_full_txt: generate?.llms_full_txt ?? DEFAULTS.generate.llms_full_txt,
125
- ai_context: generate?.ai_context ?? DEFAULTS.generate.ai_context,
126
- output_dir: generate?.output_dir ?? DEFAULTS.generate.output_dir,
127
- sections: generate?.sections,
128
- ai_context_config: generate?.ai_context_config
129
- },
130
- guard: {
131
- npm_packages: guard?.npm_packages ?? DEFAULTS.guard.npm_packages,
132
- github_releases: guard?.github_releases ?? DEFAULTS.guard.github_releases,
133
- endpoints: guard?.endpoints ?? DEFAULTS.guard.endpoints,
134
- readme_scans: guard?.readme_scans ?? DEFAULTS.guard.readme_scans,
135
- workflow: {
136
- enabled: guardWorkflow.enabled ?? DEFAULTS.guard.workflow.enabled,
137
- schedule: guardWorkflow.schedule ?? DEFAULTS.guard.workflow.schedule,
138
- create_issues: guardWorkflow.create_issues ?? DEFAULTS.guard.workflow.create_issues,
139
- labels: guardWorkflow.labels ?? DEFAULTS.guard.workflow.labels
140
- }
141
- },
142
- deploy: {
143
- platform: deploy?.platform ?? DEFAULTS.deploy.platform,
144
- cors: {
145
- enabled: deploy?.cors?.enabled ?? DEFAULTS.deploy.cors.enabled,
146
- origins: deploy?.cors?.origins ?? DEFAULTS.deploy.cors.origins
147
- }
148
- },
149
- validate: {
150
- max_tokens: validateConf?.max_tokens ?? DEFAULTS.validate.max_tokens,
151
- check_links: validateConf?.check_links ?? DEFAULTS.validate.check_links,
152
- check_coverage: validateConf?.check_coverage ?? DEFAULTS.validate.check_coverage,
153
- coverage_threshold: validateConf?.coverage_threshold ?? DEFAULTS.validate.coverage_threshold
154
- }
155
- };
156
- }
157
-
158
- // src/core/scanner.ts
159
- import fs2 from "fs/promises";
160
- import path2 from "path";
161
- import matter from "gray-matter";
162
- import { glob } from "glob";
163
- async function scanDocs(docsDir, options) {
164
- const resolvedDir = path2.resolve(docsDir);
165
- const allFiles = [];
166
- for (const pattern of options.include) {
167
- const matches = await glob(pattern, {
168
- cwd: resolvedDir,
169
- ignore: options.exclude,
170
- nodir: true,
171
- absolute: false
172
- });
173
- allFiles.push(...matches);
174
- }
175
- const uniqueFiles = [...new Set(allFiles)];
176
- const pages = [];
177
- for (const relativePath of uniqueFiles) {
178
- const filePath = path2.join(resolvedDir, relativePath);
179
- const raw = await fs2.readFile(filePath, "utf-8");
180
- const { data: frontmatter, content } = matter(raw);
181
- const title = resolveTitle(frontmatter, content, relativePath);
182
- const description = frontmatter.description ?? null;
183
- pages.push({
184
- filePath,
185
- relativePath: relativePath.replace(/\\/g, "/"),
186
- title,
187
- description,
188
- frontmatter,
189
- content
190
- });
191
- }
192
- pages.sort((a, b) => {
193
- const posA = a.frontmatter.sidebar_position ?? Infinity;
194
- const posB = b.frontmatter.sidebar_position ?? Infinity;
195
- if (posA !== posB) return posA - posB;
196
- return a.relativePath.localeCompare(b.relativePath);
197
- });
198
- return pages;
199
- }
200
- function resolveTitle(frontmatter, content, relativePath) {
201
- if (typeof frontmatter.title === "string" && frontmatter.title.trim()) {
202
- return frontmatter.title.trim();
203
- }
204
- const h1Match = content.match(/^#\s+(.+)$/m);
205
- if (h1Match) {
206
- return h1Match[1].trim();
207
- }
208
- const basename = path2.basename(relativePath, path2.extname(relativePath));
209
- return basename.charAt(0).toUpperCase() + basename.slice(1).replace(/[-_]/g, " ");
210
- }
17
+ import path3 from "path";
211
18
 
212
19
  // src/generate/llms-txt.ts
213
- import path4 from "path";
20
+ import path2 from "path";
214
21
 
215
22
  // node_modules/balanced-match/dist/esm/index.js
216
23
  var balanced = (a, b, str) => {
@@ -456,9 +263,9 @@ var posixClasses = {
456
263
  var braceEscape = (s) => s.replace(/[[\]\\-]/g, "\\$&");
457
264
  var regexpEscape = (s) => s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
458
265
  var rangesToString = (ranges) => ranges.join("");
459
- var parseClass = (glob2, position) => {
266
+ var parseClass = (glob, position) => {
460
267
  const pos = position;
461
- if (glob2.charAt(pos) !== "[") {
268
+ if (glob.charAt(pos) !== "[") {
462
269
  throw new Error("not in a brace expression");
463
270
  }
464
271
  const ranges = [];
@@ -470,8 +277,8 @@ var parseClass = (glob2, position) => {
470
277
  let negate = false;
471
278
  let endPos = pos;
472
279
  let rangeStart = "";
473
- WHILE: while (i < glob2.length) {
474
- const c = glob2.charAt(i);
280
+ WHILE: while (i < glob.length) {
281
+ const c = glob.charAt(i);
475
282
  if ((c === "!" || c === "^") && i === pos + 1) {
476
283
  negate = true;
477
284
  i++;
@@ -491,9 +298,9 @@ var parseClass = (glob2, position) => {
491
298
  }
492
299
  if (c === "[" && !escaping) {
493
300
  for (const [cls, [unip, u, neg]] of Object.entries(posixClasses)) {
494
- if (glob2.startsWith(cls, i)) {
301
+ if (glob.startsWith(cls, i)) {
495
302
  if (rangeStart) {
496
- return ["$.", false, glob2.length - pos, true];
303
+ return ["$.", false, glob.length - pos, true];
497
304
  }
498
305
  i += cls.length;
499
306
  if (neg)
@@ -516,12 +323,12 @@ var parseClass = (glob2, position) => {
516
323
  i++;
517
324
  continue;
518
325
  }
519
- if (glob2.startsWith("-]", i + 1)) {
326
+ if (glob.startsWith("-]", i + 1)) {
520
327
  ranges.push(braceEscape(c + "-"));
521
328
  i += 2;
522
329
  continue;
523
330
  }
524
- if (glob2.startsWith("-", i + 1)) {
331
+ if (glob.startsWith("-", i + 1)) {
525
332
  rangeStart = c;
526
333
  i += 2;
527
334
  continue;
@@ -533,7 +340,7 @@ var parseClass = (glob2, position) => {
533
340
  return ["", false, 0, false];
534
341
  }
535
342
  if (!ranges.length && !negs.length) {
536
- return ["$.", false, glob2.length - pos, true];
343
+ return ["$.", false, glob.length - pos, true];
537
344
  }
538
345
  if (negs.length === 0 && ranges.length === 1 && /^\\?.$/.test(ranges[0]) && !negate) {
539
346
  const r = ranges[0].length === 2 ? ranges[0].slice(-1) : ranges[0];
@@ -941,16 +748,16 @@ var AST = class {
941
748
  toMMPattern() {
942
749
  if (this !== this.#root)
943
750
  return this.#root.toMMPattern();
944
- const glob2 = this.toString();
751
+ const glob = this.toString();
945
752
  const [re, body, hasMagic, uflag] = this.toRegExpSource();
946
- const anyMagic = hasMagic || this.#hasMagic || this.#options.nocase && !this.#options.nocaseMagicOnly && glob2.toUpperCase() !== glob2.toLowerCase();
753
+ const anyMagic = hasMagic || this.#hasMagic || this.#options.nocase && !this.#options.nocaseMagicOnly && glob.toUpperCase() !== glob.toLowerCase();
947
754
  if (!anyMagic) {
948
755
  return body;
949
756
  }
950
757
  const flags = (this.#options.nocase ? "i" : "") + (uflag ? "u" : "");
951
758
  return Object.assign(new RegExp(`^${re}$`, flags), {
952
759
  _src: re,
953
- _glob: glob2
760
+ _glob: glob
954
761
  });
955
762
  }
956
763
  get options() {
@@ -1145,13 +952,13 @@ var AST = class {
1145
952
  return re;
1146
953
  }).filter((p) => !(this.isStart() && this.isEnd()) || !!p).join("|");
1147
954
  }
1148
- static #parseGlob(glob2, hasMagic, noEmpty = false) {
955
+ static #parseGlob(glob, hasMagic, noEmpty = false) {
1149
956
  let escaping = false;
1150
957
  let re = "";
1151
958
  let uflag = false;
1152
959
  let inStar = false;
1153
- for (let i = 0; i < glob2.length; i++) {
1154
- const c = glob2.charAt(i);
960
+ for (let i = 0; i < glob.length; i++) {
961
+ const c = glob.charAt(i);
1155
962
  if (escaping) {
1156
963
  escaping = false;
1157
964
  re += (reSpecials.has(c) ? "\\" : "") + c;
@@ -1161,14 +968,14 @@ var AST = class {
1161
968
  if (inStar)
1162
969
  continue;
1163
970
  inStar = true;
1164
- re += noEmpty && /^[*]+$/.test(glob2) ? starNoEmpty : star;
971
+ re += noEmpty && /^[*]+$/.test(glob) ? starNoEmpty : star;
1165
972
  hasMagic = true;
1166
973
  continue;
1167
974
  } else {
1168
975
  inStar = false;
1169
976
  }
1170
977
  if (c === "\\") {
1171
- if (i === glob2.length - 1) {
978
+ if (i === glob.length - 1) {
1172
979
  re += "\\\\";
1173
980
  } else {
1174
981
  escaping = true;
@@ -1176,7 +983,7 @@ var AST = class {
1176
983
  continue;
1177
984
  }
1178
985
  if (c === "[") {
1179
- const [src, needUflag, consumed, magic] = parseClass(glob2, i);
986
+ const [src, needUflag, consumed, magic] = parseClass(glob, i);
1180
987
  if (consumed) {
1181
988
  re += src;
1182
989
  uflag = uflag || needUflag;
@@ -1192,7 +999,7 @@ var AST = class {
1192
999
  }
1193
1000
  re += regExpEscape(c);
1194
1001
  }
1195
- return [re, unescape(glob2), !!hasMagic, uflag];
1002
+ return [re, unescape(glob), !!hasMagic, uflag];
1196
1003
  }
1197
1004
  };
1198
1005
  _a = AST;
@@ -1264,11 +1071,11 @@ var qmarksTestNoExtDot = ([$0]) => {
1264
1071
  return (f) => f.length === len && f !== "." && f !== "..";
1265
1072
  };
1266
1073
  var defaultPlatform = typeof process === "object" && process ? typeof process.env === "object" && process.env && process.env.__MINIMATCH_TESTING_PLATFORM__ || process.platform : "posix";
1267
- var path3 = {
1074
+ var path = {
1268
1075
  win32: { sep: "\\" },
1269
1076
  posix: { sep: "/" }
1270
1077
  };
1271
- var sep = defaultPlatform === "win32" ? path3.win32.sep : path3.posix.sep;
1078
+ var sep = defaultPlatform === "win32" ? path.win32.sep : path.posix.sep;
1272
1079
  minimatch.sep = sep;
1273
1080
  var GLOBSTAR = /* @__PURE__ */ Symbol("globstar **");
1274
1081
  minimatch.GLOBSTAR = GLOBSTAR;
@@ -2089,7 +1896,7 @@ function extractFirstParagraph(content) {
2089
1896
  function groupByDirectory(pages) {
2090
1897
  const groups = /* @__PURE__ */ new Map();
2091
1898
  for (const page of pages) {
2092
- const dir = path4.dirname(page.relativePath);
1899
+ const dir = path2.dirname(page.relativePath);
2093
1900
  const topDir = dir === "." ? "." : dir.split("/")[0];
2094
1901
  if (!groups.has(topDir)) {
2095
1902
  groups.set(topDir, []);
@@ -2130,24 +1937,13 @@ ${cleaned}`);
2130
1937
  return sections.join("\n\n").trimEnd() + "\n";
2131
1938
  }
2132
1939
 
2133
- // src/utils/tokens.ts
2134
- function estimateTokens(content) {
2135
- return Math.ceil(content.length / 4);
2136
- }
2137
- function formatTokens(count) {
2138
- if (count >= 1e3) {
2139
- return `~${(count / 1e3).toFixed(1)}K tokens`;
2140
- }
2141
- return `~${count} tokens`;
2142
- }
2143
-
2144
1940
  // src/cli/commands/generate.ts
2145
1941
  async function generateCommand(options = {}) {
2146
1942
  const cwd = process.cwd();
2147
1943
  const config = await loadConfig(cwd);
2148
1944
  const spin = spinner("Scanning documentation...");
2149
1945
  spin.start();
2150
- const docsDir = path5.resolve(cwd, config.docs.dir);
1946
+ const docsDir = path3.resolve(cwd, config.docs.dir);
2151
1947
  const pages = await scanDocs(docsDir, {
2152
1948
  include: config.docs.include,
2153
1949
  exclude: config.docs.exclude
@@ -2158,7 +1954,7 @@ async function generateCommand(options = {}) {
2158
1954
  return;
2159
1955
  }
2160
1956
  log.info(`Found ${pages.length} documentation pages`);
2161
- const outputDir = path5.resolve(cwd, config.generate.output_dir);
1957
+ const outputDir = path3.resolve(cwd, config.generate.output_dir);
2162
1958
  if (config.generate.llms_txt && options.only !== "llms-full") {
2163
1959
  const llmsTxt = generateLlmsTxt(pages, {
2164
1960
  title: config.title,
@@ -2170,8 +1966,8 @@ async function generateCommand(options = {}) {
2170
1966
  log.info("[dry-run] Would write llms.txt");
2171
1967
  log.dim(` ${llmsTxt.length} chars, ${formatTokens(estimateTokens(llmsTxt))}`);
2172
1968
  } else {
2173
- await fs3.mkdir(outputDir, { recursive: true });
2174
- await fs3.writeFile(path5.join(outputDir, "llms.txt"), llmsTxt, "utf-8");
1969
+ await fs.mkdir(outputDir, { recursive: true });
1970
+ await fs.writeFile(path3.join(outputDir, "llms.txt"), llmsTxt, "utf-8");
2175
1971
  log.success(`Generated llms.txt (${formatTokens(estimateTokens(llmsTxt))})`);
2176
1972
  }
2177
1973
  }
@@ -2181,8 +1977,8 @@ async function generateCommand(options = {}) {
2181
1977
  log.info("[dry-run] Would write llms-full.txt");
2182
1978
  log.dim(` ${llmsFullTxt.length} chars, ${formatTokens(estimateTokens(llmsFullTxt))}`);
2183
1979
  } else {
2184
- await fs3.mkdir(outputDir, { recursive: true });
2185
- await fs3.writeFile(path5.join(outputDir, "llms-full.txt"), llmsFullTxt, "utf-8");
1980
+ await fs.mkdir(outputDir, { recursive: true });
1981
+ await fs.writeFile(path3.join(outputDir, "llms-full.txt"), llmsFullTxt, "utf-8");
2186
1982
  log.success(`Generated llms-full.txt (${formatTokens(estimateTokens(llmsFullTxt))})`);
2187
1983
  }
2188
1984
  }
@@ -2203,8 +1999,8 @@ async function generateCommand(options = {}) {
2203
1999
  log.info("[dry-run] Would write ai-context.md");
2204
2000
  log.dim(` ${aiContext.length} chars, ${formatTokens(estimateTokens(aiContext))}`);
2205
2001
  } else {
2206
- await fs3.mkdir(outputDir, { recursive: true });
2207
- await fs3.writeFile(path5.join(outputDir, "ai-context.md"), aiContext, "utf-8");
2002
+ await fs.mkdir(outputDir, { recursive: true });
2003
+ await fs.writeFile(path3.join(outputDir, "ai-context.md"), aiContext, "utf-8");
2208
2004
  log.success(`Generated ai-context.md (${formatTokens(estimateTokens(aiContext))})`);
2209
2005
  }
2210
2006
  }
@@ -2212,4 +2008,4 @@ async function generateCommand(options = {}) {
2212
2008
  export {
2213
2009
  generateCommand
2214
2010
  };
2215
- //# sourceMappingURL=generate-LDVEG2WV.js.map
2011
+ //# sourceMappingURL=generate-4EHDLBJX.js.map