docs-ready 0.2.0 → 0.4.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,167 +1,23 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ loadConfig
4
+ } from "./chunk-QI2AROM3.js";
2
5
  import {
3
6
  log,
4
7
  spinner
5
8
  } from "./chunk-7YN54Y4Y.js";
6
9
 
7
10
  // src/cli/commands/generate.ts
8
- import fs3 from "fs/promises";
9
- import path5 from "path";
11
+ import fs2 from "fs/promises";
12
+ import path4 from "path";
10
13
 
11
- // src/core/config.ts
14
+ // src/core/scanner.ts
12
15
  import fs from "fs/promises";
13
16
  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
17
  import matter from "gray-matter";
162
18
  import { glob } from "glob";
163
19
  async function scanDocs(docsDir, options) {
164
- const resolvedDir = path2.resolve(docsDir);
20
+ const resolvedDir = path.resolve(docsDir);
165
21
  const allFiles = [];
166
22
  for (const pattern of options.include) {
167
23
  const matches = await glob(pattern, {
@@ -175,8 +31,8 @@ async function scanDocs(docsDir, options) {
175
31
  const uniqueFiles = [...new Set(allFiles)];
176
32
  const pages = [];
177
33
  for (const relativePath of uniqueFiles) {
178
- const filePath = path2.join(resolvedDir, relativePath);
179
- const raw = await fs2.readFile(filePath, "utf-8");
34
+ const filePath = path.join(resolvedDir, relativePath);
35
+ const raw = await fs.readFile(filePath, "utf-8");
180
36
  const { data: frontmatter, content } = matter(raw);
181
37
  const title = resolveTitle(frontmatter, content, relativePath);
182
38
  const description = frontmatter.description ?? null;
@@ -205,12 +61,12 @@ function resolveTitle(frontmatter, content, relativePath) {
205
61
  if (h1Match) {
206
62
  return h1Match[1].trim();
207
63
  }
208
- const basename = path2.basename(relativePath, path2.extname(relativePath));
64
+ const basename = path.basename(relativePath, path.extname(relativePath));
209
65
  return basename.charAt(0).toUpperCase() + basename.slice(1).replace(/[-_]/g, " ");
210
66
  }
211
67
 
212
68
  // src/generate/llms-txt.ts
213
- import path4 from "path";
69
+ import path3 from "path";
214
70
 
215
71
  // node_modules/balanced-match/dist/esm/index.js
216
72
  var balanced = (a, b, str) => {
@@ -1264,11 +1120,11 @@ var qmarksTestNoExtDot = ([$0]) => {
1264
1120
  return (f) => f.length === len && f !== "." && f !== "..";
1265
1121
  };
1266
1122
  var defaultPlatform = typeof process === "object" && process ? typeof process.env === "object" && process.env && process.env.__MINIMATCH_TESTING_PLATFORM__ || process.platform : "posix";
1267
- var path3 = {
1123
+ var path2 = {
1268
1124
  win32: { sep: "\\" },
1269
1125
  posix: { sep: "/" }
1270
1126
  };
1271
- var sep = defaultPlatform === "win32" ? path3.win32.sep : path3.posix.sep;
1127
+ var sep = defaultPlatform === "win32" ? path2.win32.sep : path2.posix.sep;
1272
1128
  minimatch.sep = sep;
1273
1129
  var GLOBSTAR = /* @__PURE__ */ Symbol("globstar **");
1274
1130
  minimatch.GLOBSTAR = GLOBSTAR;
@@ -2089,7 +1945,7 @@ function extractFirstParagraph(content) {
2089
1945
  function groupByDirectory(pages) {
2090
1946
  const groups = /* @__PURE__ */ new Map();
2091
1947
  for (const page of pages) {
2092
- const dir = path4.dirname(page.relativePath);
1948
+ const dir = path3.dirname(page.relativePath);
2093
1949
  const topDir = dir === "." ? "." : dir.split("/")[0];
2094
1950
  if (!groups.has(topDir)) {
2095
1951
  groups.set(topDir, []);
@@ -2147,7 +2003,7 @@ async function generateCommand(options = {}) {
2147
2003
  const config = await loadConfig(cwd);
2148
2004
  const spin = spinner("Scanning documentation...");
2149
2005
  spin.start();
2150
- const docsDir = path5.resolve(cwd, config.docs.dir);
2006
+ const docsDir = path4.resolve(cwd, config.docs.dir);
2151
2007
  const pages = await scanDocs(docsDir, {
2152
2008
  include: config.docs.include,
2153
2009
  exclude: config.docs.exclude
@@ -2158,7 +2014,7 @@ async function generateCommand(options = {}) {
2158
2014
  return;
2159
2015
  }
2160
2016
  log.info(`Found ${pages.length} documentation pages`);
2161
- const outputDir = path5.resolve(cwd, config.generate.output_dir);
2017
+ const outputDir = path4.resolve(cwd, config.generate.output_dir);
2162
2018
  if (config.generate.llms_txt && options.only !== "llms-full") {
2163
2019
  const llmsTxt = generateLlmsTxt(pages, {
2164
2020
  title: config.title,
@@ -2170,8 +2026,8 @@ async function generateCommand(options = {}) {
2170
2026
  log.info("[dry-run] Would write llms.txt");
2171
2027
  log.dim(` ${llmsTxt.length} chars, ${formatTokens(estimateTokens(llmsTxt))}`);
2172
2028
  } else {
2173
- await fs3.mkdir(outputDir, { recursive: true });
2174
- await fs3.writeFile(path5.join(outputDir, "llms.txt"), llmsTxt, "utf-8");
2029
+ await fs2.mkdir(outputDir, { recursive: true });
2030
+ await fs2.writeFile(path4.join(outputDir, "llms.txt"), llmsTxt, "utf-8");
2175
2031
  log.success(`Generated llms.txt (${formatTokens(estimateTokens(llmsTxt))})`);
2176
2032
  }
2177
2033
  }
@@ -2181,13 +2037,35 @@ async function generateCommand(options = {}) {
2181
2037
  log.info("[dry-run] Would write llms-full.txt");
2182
2038
  log.dim(` ${llmsFullTxt.length} chars, ${formatTokens(estimateTokens(llmsFullTxt))}`);
2183
2039
  } else {
2184
- await fs3.mkdir(outputDir, { recursive: true });
2185
- await fs3.writeFile(path5.join(outputDir, "llms-full.txt"), llmsFullTxt, "utf-8");
2040
+ await fs2.mkdir(outputDir, { recursive: true });
2041
+ await fs2.writeFile(path4.join(outputDir, "llms-full.txt"), llmsFullTxt, "utf-8");
2186
2042
  log.success(`Generated llms-full.txt (${formatTokens(estimateTokens(llmsFullTxt))})`);
2187
2043
  }
2188
2044
  }
2045
+ if (config.generate.ai_context && options.only !== "llms-txt" && options.only !== "llms-full") {
2046
+ const { generateAiContext } = await import("./ai-context-QVGQK3GD.js");
2047
+ const aiContext = generateAiContext(pages, {
2048
+ title: config.title,
2049
+ description: config.description,
2050
+ aiContextConfig: config.generate.ai_context_config ? {
2051
+ key_pages: config.generate.ai_context_config.key_pages,
2052
+ extra_sections: config.generate.ai_context_config.extra_sections?.map((s) => ({
2053
+ title: s.title,
2054
+ content: s.source ?? ""
2055
+ }))
2056
+ } : void 0
2057
+ });
2058
+ if (options.dryRun) {
2059
+ log.info("[dry-run] Would write ai-context.md");
2060
+ log.dim(` ${aiContext.length} chars, ${formatTokens(estimateTokens(aiContext))}`);
2061
+ } else {
2062
+ await fs2.mkdir(outputDir, { recursive: true });
2063
+ await fs2.writeFile(path4.join(outputDir, "ai-context.md"), aiContext, "utf-8");
2064
+ log.success(`Generated ai-context.md (${formatTokens(estimateTokens(aiContext))})`);
2065
+ }
2066
+ }
2189
2067
  }
2190
2068
  export {
2191
2069
  generateCommand
2192
2070
  };
2193
- //# sourceMappingURL=generate-56HFRN5I.js.map
2071
+ //# sourceMappingURL=generate-N54SEQ7E.js.map