jsdoc-scribe 1.0.0 → 1.7.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/bin/cli.js CHANGED
@@ -1,115 +1,115 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
-
4
- const fs = require("fs");
5
- const path = require("path");
6
- const { processFile, collectFiles } = require("../lib/index.js");
7
- const pkg = require("../package.json");
8
-
9
- function printHelp() {
10
- console.log(`
11
- ${pkg.name} v${pkg.version}
12
- Pure, AST-based JSDoc comment generator for JavaScript & TypeScript. No AI involved.
13
-
14
- Usage:
15
- gen-comments <path> [path2 ...] [options]
16
-
17
- <path> can be a single file OR a directory (scanned recursively for
18
- .js/.jsx/.ts/.tsx files; node_modules, .git, dist, build, etc. are skipped).
19
-
20
- Options:
21
- --write, -w Overwrite files in place.
22
- (default: writes a sibling "<name>.commented.<ext>" file
23
- next to each original, so you can review a diff first)
24
- --force, -f Add comment blocks even on nodes that already have one.
25
- --help, -h Show this help.
26
- --version, -v Show the installed version.
27
-
28
- Examples:
29
- gen-comments src/utils.ts # preview only, writes utils.commented.ts
30
- gen-comments . # scan whole project, preview only
31
- gen-comments . --write # scan whole project, edit files in place
32
- gen-comments src --write --force # re-document files that already have JSDoc
33
- `);
34
- }
35
-
36
- function parseArgs(argv) {
37
- const args = { inputs: [], write: false, force: false, help: false, version: false };
38
- for (const a of argv) {
39
- if (a === "--write" || a === "-w") args.write = true;
40
- else if (a === "--force" || a === "-f") args.force = true;
41
- else if (a === "--help" || a === "-h") args.help = true;
42
- else if (a === "--version" || a === "-v") args.version = true;
43
- else args.inputs.push(a);
44
- }
45
- return args;
46
- }
47
-
48
- function isInsideGitRepo(startDir) {
49
- let dir = path.resolve(startDir);
50
- while (true) {
51
- if (fs.existsSync(path.join(dir, ".git"))) return true;
52
- const parent = path.dirname(dir);
53
- if (parent === dir) return false;
54
- dir = parent;
55
- }
56
- }
57
-
58
- function main() {
59
- const argv = process.argv.slice(2);
60
- const { inputs, write, force, help, version } = parseArgs(argv);
61
-
62
- if (version) {
63
- console.log(pkg.version);
64
- return;
65
- }
66
- if (help || inputs.length === 0) {
67
- printHelp();
68
- process.exitCode = inputs.length === 0 && !help ? 1 : 0;
69
- return;
70
- }
71
-
72
- if (write && !isInsideGitRepo(process.cwd())) {
73
- console.warn(
74
- "⚠ --write will edit files in place and this folder is not (or you are not inside) a git repo.\n" +
75
- " Consider committing your work first so you have something to diff/revert against.\n",
76
- );
77
- }
78
-
79
- let files = [];
80
- for (const input of inputs) {
81
- if (!fs.existsSync(input)) {
82
- console.error(`skip: path not found - ${input}`);
83
- continue;
84
- }
85
- files.push(...collectFiles(input));
86
- }
87
- files = [...new Set(files)];
88
-
89
- if (files.length === 0) {
90
- console.log("No matching .js/.jsx/.ts/.tsx files found.");
91
- return;
92
- }
93
-
94
- console.log(`Scanning ${files.length} file(s)...`);
95
- let totalBlocks = 0;
96
- let touchedFiles = 0;
97
- for (const file of files) {
98
- try {
99
- const count = processFile(file, { write, force });
100
- totalBlocks += count;
101
- if (count > 0) touchedFiles += 1;
102
- } catch (err) {
103
- console.error(` ${file} -> FAILED: ${err.message}`);
104
- }
105
- }
106
-
107
- console.log(
108
- `\nDone. ${totalBlocks} comment block(s) added across ${touchedFiles} file(s) ` + `(${files.length} scanned).`,
109
- );
110
- if (!write) {
111
- console.log("This was a preview run — pass --write to edit the original files in place.");
112
- }
113
- }
114
-
115
- main();
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const fs = require("fs");
5
+ const path = require("path");
6
+ const { processFile, collectFiles } = require("../lib/index.js");
7
+ const pkg = require("../package.json");
8
+
9
+ function printHelp() {
10
+ console.log(`
11
+ ${pkg.name} v${pkg.version}
12
+ Pure, AST-based JSDoc comment generator for JavaScript & TypeScript. No AI involved.
13
+
14
+ Usage:
15
+ gen-comments <path> [path2 ...] [options]
16
+
17
+ <path> can be a single file OR a directory (scanned recursively for
18
+ .js/.jsx/.ts/.tsx files; node_modules, .git, dist, build, etc. are skipped).
19
+
20
+ Options:
21
+ --write, -w Overwrite files in place.
22
+ (default: writes a sibling "<name>.<ext>" file
23
+ next to each original, so you can review a diff first)
24
+ --force, -f Add comment blocks even on nodes that already have one.
25
+ --help, -h Show this help.
26
+ --version, -v Show the installed version.
27
+
28
+ Examples:
29
+ gen-comments src/utils.ts # preview only, writes utils.ts
30
+ gen-comments . # scan whole project, preview only
31
+ gen-comments . --write # scan whole project, edit files in place
32
+ gen-comments src --write --force # re-document files that already have JSDoc
33
+ `);
34
+ }
35
+
36
+ function parseArgs(argv) {
37
+ const args = { inputs: [], write: false, force: false, help: false, version: false };
38
+ for (const a of argv) {
39
+ if (a === "--write" || a === "-w") args.write = true;
40
+ else if (a === "--force" || a === "-f") args.force = true;
41
+ else if (a === "--help" || a === "-h") args.help = true;
42
+ else if (a === "--version" || a === "-v") args.version = true;
43
+ else args.inputs.push(a);
44
+ }
45
+ return args;
46
+ }
47
+
48
+ function isInsideGitRepo(startDir) {
49
+ let dir = path.resolve(startDir);
50
+ while (true) {
51
+ if (fs.existsSync(path.join(dir, ".git"))) return true;
52
+ const parent = path.dirname(dir);
53
+ if (parent === dir) return false;
54
+ dir = parent;
55
+ }
56
+ }
57
+
58
+ function main() {
59
+ const argv = process.argv.slice(2);
60
+ const { inputs, write, force, help, version } = parseArgs(argv);
61
+
62
+ if (version) {
63
+ console.log(pkg.version);
64
+ return;
65
+ }
66
+ if (help || inputs.length === 0) {
67
+ printHelp();
68
+ process.exitCode = inputs.length === 0 && !help ? 1 : 0;
69
+ return;
70
+ }
71
+
72
+ if (write && !isInsideGitRepo(process.cwd())) {
73
+ console.warn(
74
+ "⚠ --write will edit files in place and this folder is not (or you are not inside) a git repo.\n" +
75
+ " Consider committing your work first so you have something to diff/revert against.\n",
76
+ );
77
+ }
78
+
79
+ let files = [];
80
+ for (const input of inputs) {
81
+ if (!fs.existsSync(input)) {
82
+ console.error(`skip: path not found - ${input}`);
83
+ continue;
84
+ }
85
+ files.push(...collectFiles(input));
86
+ }
87
+ files = [...new Set(files)];
88
+
89
+ if (files.length === 0) {
90
+ console.log("No matching .js/.jsx/.ts/.tsx files found.");
91
+ return;
92
+ }
93
+
94
+ console.log(`Scanning ${files.length} file(s)...`);
95
+ let totalBlocks = 0;
96
+ let touchedFiles = 0;
97
+ for (const file of files) {
98
+ try {
99
+ const count = processFile(file, { write, force });
100
+ totalBlocks += count;
101
+ if (count > 0) touchedFiles += 1;
102
+ } catch (err) {
103
+ console.error(` ${file} -> FAILED: ${err.message}`);
104
+ }
105
+ }
106
+
107
+ console.log(
108
+ `\nDone. ${totalBlocks} comment block(s) added across ${touchedFiles} file(s) ` + `(${files.length} scanned).`,
109
+ );
110
+ if (!write) {
111
+ console.log("This was a preview run — pass --write to edit the original files in place.");
112
+ }
113
+ }
114
+
115
+ main();
@@ -0,0 +1,299 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const fs = require("fs");
5
+ const path = require("path");
6
+ const { collectFiles } = require("../lib/index.js");
7
+ const { extractModule } = require("../lib/extractor.js");
8
+ const { buildSite, moduleLabel, moduleHtmlPath } = require("../lib/renderer.js");
9
+ const { loadConfig, mergeConfig } = require("../lib/config.js");
10
+ const pkg = require("../package.json");
11
+
12
+ const VALID_THEMES = ["default", "minimal", "dark"];
13
+
14
+ function printHelp() {
15
+ console.log(`
16
+ ${pkg.name} v${pkg.version} -- gen-docs
17
+ Generate a multi-page HTML documentation site from JS/TS source files.
18
+
19
+ Usage:
20
+ gen-docs <path> [path2 ...] [options]
21
+
22
+ Options:
23
+ --out <dir>, -o <dir> Output directory (default: ./docs)
24
+ --title <name>, -t <name> Project title (default: package name)
25
+ --theme <name>, -T <name> Theme: default|minimal|dark (default: default)
26
+ --json, -j Also write docs.json
27
+ --readme, -r Also write README.md
28
+ --ignore <glob>, -I <glob> Exclude files matching glob (repeatable)
29
+ --source-url <url>, -s <url> GitHub base URL for source links
30
+ --config <file>, -c <file> Path to config file (default: .jsdoc-scribe.json)
31
+ --watch, -W Watch for changes and rebuild automatically
32
+ --help, -h Show this help.
33
+ --version, -v Show version.
34
+
35
+ Config file (.jsdoc-scribe.json):
36
+ { "out": "docs", "title": "My API", "theme": "minimal", "json": true,
37
+ "readme": true, "sourceUrl": "https://github.com/user/repo/blob/main",
38
+ "ignore": ["**/*.test.ts", "src/generated/"] }
39
+
40
+ Examples:
41
+ gen-docs src
42
+ gen-docs . --out site --json --readme --theme minimal
43
+ gen-docs src --ignore "**/*.test.ts" --ignore "dist/"
44
+ gen-docs src --source-url https://github.com/user/repo/blob/main
45
+ gen-docs src --watch
46
+ `);
47
+ }
48
+
49
+ function parseArgs(argv) {
50
+ const args = {
51
+ inputs: [], out: undefined, title: undefined, theme: undefined,
52
+ json: undefined, readme: undefined, watch: false,
53
+ ignore: [], sourceUrl: undefined, configPath: undefined,
54
+ help: false, version: false,
55
+ };
56
+ let i = 0;
57
+ while (i < argv.length) {
58
+ const a = argv[i];
59
+ if (a === "--help" || a === "-h") { args.help = true; }
60
+ else if (a === "--version" || a === "-v") { args.version = true; }
61
+ else if (a === "--watch" || a === "-W") { args.watch = true; }
62
+ else if (a === "--json" || a === "-j") { args.json = true; }
63
+ else if (a === "--readme" || a === "-r") { args.readme = true; }
64
+ else if ((a === "--out" || a === "-o") && argv[i+1]) { args.out = argv[++i]; }
65
+ else if ((a === "--title" || a === "-t") && argv[i+1]) { args.title = argv[++i]; }
66
+ else if ((a === "--config" || a === "-c") && argv[i+1]) { args.configPath= argv[++i]; }
67
+ else if ((a === "--source-url"|| a === "-s") && argv[i+1]) { args.sourceUrl = argv[++i]; }
68
+ else if ((a === "--ignore" || a === "-I") && argv[i+1]) { args.ignore.push(argv[++i]); }
69
+ else if ((a === "--theme" || a === "-T") && argv[i+1]) {
70
+ const t = argv[++i];
71
+ if (!VALID_THEMES.includes(t)) { console.error(`Unknown theme "${t}". Valid: ${VALID_THEMES.join(", ")}`); process.exit(1); }
72
+ args.theme = t;
73
+ }
74
+ else if (!a.startsWith("-")) args.inputs.push(a);
75
+ i++;
76
+ }
77
+ return args;
78
+ }
79
+
80
+ function resolveTitle(titleArg) {
81
+ if (titleArg) return titleArg;
82
+ try { return JSON.parse(fs.readFileSync(path.resolve("package.json"), "utf8")).name || "Documentation"; }
83
+ catch (_) { return "Documentation"; }
84
+ }
85
+
86
+ function resolveVersion() {
87
+ try { return JSON.parse(fs.readFileSync(path.resolve("package.json"), "utf8")).version || ""; }
88
+ catch (_) { return ""; }
89
+ }
90
+
91
+ function collectAllFiles(inputs, ignorePatterns) {
92
+ let files = [];
93
+ for (const input of inputs) {
94
+ if (!fs.existsSync(input)) { console.error(`skip: path not found -- ${input}`); continue; }
95
+ files.push(...collectFiles(input, undefined, undefined, ignorePatterns));
96
+ }
97
+ return [...new Set(files)];
98
+ }
99
+
100
+ // ---------------------------------------------------------------------------
101
+ // README generator
102
+ // ---------------------------------------------------------------------------
103
+
104
+ function generateReadme(modules, projectName, version, outDir) {
105
+ let md = `# ${projectName}${version ? ` v${version}` : ""}\n\n`;
106
+ md += `> Auto-generated API reference. ${modules.length} module(s).\n\n`;
107
+ md += `## Table of Contents\n\n`;
108
+ modules.forEach(mod => {
109
+ const label = moduleLabel(mod.filePath, modules);
110
+ const anchor = label.toLowerCase().replace(/[^a-z0-9]+/g, "-");
111
+ md += `- [${label}](#${anchor})\n`;
112
+ });
113
+ md += "\n---\n\n";
114
+
115
+ modules.forEach(mod => {
116
+ const label = moduleLabel(mod.filePath, modules);
117
+ md += `## ${label}\n\n\`${mod.filePath}\`\n\n`;
118
+
119
+ function row(kind, item, sig) {
120
+ const dep = item.deprecated != null ? " ⚠️ *deprecated*" : "";
121
+ const snc = item.since ? ` *(since v${item.since})*` : "";
122
+ const desc = item.description ? ` — ${item.description}` : "";
123
+ return `| \`${sig}\` | ${kind}${dep}${snc}${desc} |\n`;
124
+ }
125
+
126
+ if (mod.functions.length) {
127
+ md += `### Functions\n\n| Signature | Description |\n|-----------|-------------|\n`;
128
+ mod.functions.forEach(f => {
129
+ const ps = (f.params||[]).map(p => p.name+": "+p.type).join(", ");
130
+ md += row("function", f, `${f.name}(${ps}): ${f.returnType}`);
131
+ });
132
+ md += "\n";
133
+ }
134
+ if (mod.classes.length) {
135
+ md += `### Classes\n\n| Name | Description |\n|------|-------------|\n`;
136
+ mod.classes.forEach(c => md += row("class", c, c.name));
137
+ md += "\n";
138
+ }
139
+ if (mod.interfaces.length) {
140
+ md += `### Interfaces\n\n| Name | Description |\n|------|-------------|\n`;
141
+ mod.interfaces.forEach(i => md += row("interface", i, i.name));
142
+ md += "\n";
143
+ }
144
+ if (mod.typeAliases.length) {
145
+ md += `### Types\n\n| Name | Definition |\n|------|------------|\n`;
146
+ mod.typeAliases.forEach(t => md += `| \`${t.name}\` | \`${t.type}\`${t.description?" — "+t.description:""} |\n`);
147
+ md += "\n";
148
+ }
149
+ if (mod.enums.length) {
150
+ md += `### Enums\n\n| Name | Description |\n|------|-------------|\n`;
151
+ mod.enums.forEach(e => md += row("enum", e, e.name));
152
+ md += "\n";
153
+ }
154
+ if (mod.variables.length) {
155
+ md += `### Variables & Constants\n\n| Name | Type | Description |\n|------|------|-------------|\n`;
156
+ mod.variables.forEach(v => {
157
+ const dep = v.deprecated != null ? " ⚠️ *deprecated*" : "";
158
+ md += `| \`${v.name}\` | \`${v.type}\` | ${dep}${v.description||""} |\n`;
159
+ });
160
+ md += "\n";
161
+ }
162
+ md += "---\n\n";
163
+ });
164
+
165
+ md += `*Generated by [jsdoc-scribe](https://www.npmjs.com/package/jsdoc-scribe) v${version}*\n`;
166
+ const dest = path.join(outDir, "README.md");
167
+ fs.writeFileSync(dest, md, "utf8");
168
+ return dest;
169
+ }
170
+
171
+ // ---------------------------------------------------------------------------
172
+ // Build
173
+ // ---------------------------------------------------------------------------
174
+
175
+ function build(files, outDir, projectName, projectVersion, opts, silent) {
176
+ const modules = [];
177
+ for (const file of files) {
178
+ try { modules.push(extractModule(file)); }
179
+ catch (err) { console.error(` ${file} -> FAILED: ${err.message}`); }
180
+ }
181
+
182
+ const pages = buildSite(modules, {
183
+ projectName,
184
+ version: projectVersion,
185
+ theme: opts.theme,
186
+ sourceUrl: opts.sourceUrl,
187
+ });
188
+ fs.mkdirSync(path.join(outDir, "modules"), { recursive: true });
189
+
190
+ for (const p of pages) {
191
+ const dest = path.join(outDir, p.path);
192
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
193
+ fs.writeFileSync(dest, p.html, "utf8");
194
+ if (!silent) console.log(` wrote ${path.relative(process.cwd(), dest)}`);
195
+ }
196
+
197
+ if (opts.json) {
198
+ const jsonOut = path.join(outDir, "docs.json");
199
+ const payload = {
200
+ version: projectVersion, title: projectName,
201
+ generatedAt: new Date().toISOString(),
202
+ modules: modules.map(m => ({
203
+ filePath: m.filePath,
204
+ functions: m.functions, classes: m.classes,
205
+ interfaces: m.interfaces, typeAliases: m.typeAliases,
206
+ enums: m.enums, variables: m.variables,
207
+ })),
208
+ };
209
+ fs.writeFileSync(jsonOut, JSON.stringify(payload, null, 2), "utf8");
210
+ if (!silent) console.log(` wrote ${path.relative(process.cwd(), jsonOut)}`);
211
+ }
212
+
213
+ if (opts.readme) {
214
+ const dest = generateReadme(modules, projectName, projectVersion, outDir);
215
+ if (!silent) console.log(` wrote ${path.relative(process.cwd(), dest)}`);
216
+ }
217
+
218
+ return pages.length;
219
+ }
220
+
221
+ // ---------------------------------------------------------------------------
222
+ // Main
223
+ // ---------------------------------------------------------------------------
224
+
225
+ function main() {
226
+ const argv = process.argv.slice(2);
227
+ const cliArgs = parseArgs(argv);
228
+
229
+ if (cliArgs.version) { console.log(pkg.version); return; }
230
+ if (cliArgs.help || cliArgs.inputs.length === 0) {
231
+ printHelp();
232
+ process.exitCode = cliArgs.inputs.length === 0 && !cliArgs.help ? 1 : 0;
233
+ return;
234
+ }
235
+
236
+ // Load config file and merge with CLI (CLI wins)
237
+ const fileConfig = loadConfig(cliArgs.configPath);
238
+ const opts = mergeConfig(fileConfig, cliArgs);
239
+
240
+ const files = collectAllFiles(cliArgs.inputs, opts.ignore);
241
+ if (files.length === 0) { console.log("No matching .js/.jsx/.ts/.tsx files found."); return; }
242
+
243
+ const outDir = path.resolve(opts.out || "docs");
244
+ const projectName = resolveTitle(opts.title);
245
+ const projectVersion = resolveVersion();
246
+ const extras = [opts.json && "docs.json", opts.readme && "README.md"].filter(Boolean);
247
+ const extraStr = extras.length ? " + " + extras.join(" + ") : "";
248
+
249
+ if (!cliArgs.watch) {
250
+ console.log(`Extracting ${files.length} file(s)...`);
251
+ if (opts.ignore.length) console.log(` ignoring: ${opts.ignore.join(", ")}`);
252
+ const n = build(files, outDir, projectName, projectVersion, opts, false);
253
+ console.log(`\nDone. ${n} page(s)${extraStr} written to ${path.relative(process.cwd(), outDir) || outDir}`);
254
+ console.log(`Open ${path.join(path.relative(process.cwd(), outDir) || outDir, "index.html")} in a browser.`);
255
+ return;
256
+ }
257
+
258
+ // ── Watch mode ────────────────────────────────────────────────────────────
259
+ function stamp() { return new Date().toLocaleTimeString(); }
260
+ console.log(`[watch] Watching ${files.length} file(s). Output: ${path.relative(process.cwd(), outDir) || outDir}`);
261
+ if (opts.ignore.length) console.log(`[watch] ignoring: ${opts.ignore.join(", ")}`);
262
+ console.log(`[watch] Press Ctrl+C to stop.\n`);
263
+
264
+ try {
265
+ const n = build(files, outDir, projectName, projectVersion, opts, true);
266
+ console.log(`[${stamp()}] Built ${n} page(s)${extraStr}`);
267
+ } catch (err) { console.error(`[${stamp()}] Build failed: ${err.message}`); }
268
+
269
+ let timer = null;
270
+ function scheduleBuild() {
271
+ if (timer) clearTimeout(timer);
272
+ timer = setTimeout(function () {
273
+ timer = null;
274
+ try {
275
+ const freshFiles = collectAllFiles(cliArgs.inputs, opts.ignore);
276
+ const n = build(freshFiles, outDir, projectName, projectVersion, opts, true);
277
+ console.log(`[${stamp()}] Rebuilt ${n} page(s)${extraStr}`);
278
+ } catch (err) { console.error(`[${stamp()}] Rebuild failed: ${err.message}`); }
279
+ }, 150);
280
+ }
281
+
282
+ const watched = new Set();
283
+ for (const input of cliArgs.inputs) {
284
+ if (!fs.existsSync(input)) continue;
285
+ const absInput = path.resolve(input);
286
+ if (watched.has(absInput)) continue;
287
+ watched.add(absInput);
288
+ fs.watch(absInput, { recursive: true }, function (event, filename) {
289
+ if (!filename) return;
290
+ const ext = path.extname(filename).toLowerCase();
291
+ if (![".js", ".jsx", ".ts", ".tsx"].includes(ext)) return;
292
+ if (/\.commented\.[jt]sx?$/.test(filename)) return;
293
+ console.log(`[${stamp()}] Changed: ${filename}`);
294
+ scheduleBuild();
295
+ });
296
+ }
297
+ }
298
+
299
+ main();
package/lib/config.js ADDED
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+
6
+ const CONFIG_FILENAME = ".jsdoc-scribe.json";
7
+
8
+ /**
9
+ * Load and return config from `configPath` (default: cwd/.jsdoc-scribe.json).
10
+ * Returns an empty object if the file doesn't exist.
11
+ *
12
+ * @param {string} [configPath] - Explicit path to config file.
13
+ * @returns {{ out?, title?, theme?, json?, readme?, ignore?, sourceUrl? }}
14
+ */
15
+ function loadConfig(configPath) {
16
+ const p = configPath || path.resolve(process.cwd(), CONFIG_FILENAME);
17
+ if (!fs.existsSync(p)) return {};
18
+ try {
19
+ const raw = fs.readFileSync(p, "utf8");
20
+ return JSON.parse(raw);
21
+ } catch (err) {
22
+ process.stderr.write(`jsdoc-scribe: failed to parse config at ${p}: ${err.message}\n`);
23
+ return {};
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Merge config file values with CLI args.
29
+ * CLI args take precedence over config file (explicit undefined values are skipped).
30
+ *
31
+ * @param {object} fileConfig - Values from .jsdoc-scribe.json
32
+ * @param {object} cliArgs - Values parsed from argv (undefined = not set)
33
+ * @returns {object} Merged options object
34
+ */
35
+ function mergeConfig(fileConfig, cliArgs) {
36
+ return {
37
+ out: cliArgs.out ?? fileConfig.out ?? null,
38
+ title: cliArgs.title ?? fileConfig.title ?? null,
39
+ theme: cliArgs.theme ?? fileConfig.theme ?? "default",
40
+ json: cliArgs.json ?? fileConfig.json ?? false,
41
+ readme: cliArgs.readme ?? fileConfig.readme ?? false,
42
+ sourceUrl: cliArgs.sourceUrl ?? fileConfig.sourceUrl ?? null,
43
+ ignore: [...(fileConfig.ignore || []), ...(cliArgs.ignore || [])],
44
+ };
45
+ }
46
+
47
+ module.exports = { loadConfig, mergeConfig, CONFIG_FILENAME };
package/lib/docs.js ADDED
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * jsdoc-scribe/docs — programmatic API
5
+ *
6
+ * Use this when you want to drive jsdoc-scribe from Node code rather than
7
+ * the CLI. All lower-level pieces are also exported so you can build a
8
+ * custom renderer on top of the same extraction pipeline.
9
+ *
10
+ * @example
11
+ * const { extractModule, buildSite, collectFiles } = require('jsdoc-scribe/docs');
12
+ *
13
+ * const files = collectFiles('./src');
14
+ * const modules = files.map(f => extractModule(f));
15
+ * const pages = buildSite(modules, { projectName: 'My API', version: '1.0.0' });
16
+ *
17
+ * // pages is [{ path: 'index.html', html: '...' }, { path: 'modules/...html', html: '...' }]
18
+ * pages.forEach(p => require('fs').writeFileSync(require('path').join('./docs', p.path), p.html));
19
+ */
20
+
21
+ const { collectFiles, DEFAULT_EXTENSIONS, DEFAULT_IGNORE_DIRS } = require("./index.js");
22
+ const { extractModule } = require("./extractor.js");
23
+ const { buildSite, moduleLabel, moduleHtmlPath } = require("./renderer.js");
24
+
25
+ /**
26
+ * Extract structured documentation from every file in an array.
27
+ * Skips files that fail to parse and logs the error to stderr.
28
+ * @param {string[]} files - absolute or relative file paths
29
+ * @returns {object[]} array of module models (same shape as extractModule())
30
+ */
31
+ function extractModules(files) {
32
+ const modules = [];
33
+ for (const file of files) {
34
+ try {
35
+ modules.push(extractModule(file));
36
+ } catch (err) {
37
+ process.stderr.write(`jsdoc-scribe/docs: skipped ${file} — ${err.message}\n`);
38
+ }
39
+ }
40
+ return modules;
41
+ }
42
+
43
+ /**
44
+ * One-shot: collect files, extract docs, build site, return pages.
45
+ * @param {string|string[]} inputPaths - file or directory paths to scan
46
+ * @param {{ projectName?: string, version?: string, extensions?: string[], ignoreDirs?: Set<string> }} [options]
47
+ * @returns {{ path: string, html: string }[]}
48
+ */
49
+ function generateSite(inputPaths, options) {
50
+ const paths = Array.isArray(inputPaths) ? inputPaths : [inputPaths];
51
+ const opts = options || {};
52
+ const files = [].concat(...paths.map(p => collectFiles(p, opts.extensions, opts.ignoreDirs)));
53
+ const unique = [...new Set(files)];
54
+ const modules = extractModules(unique);
55
+ return buildSite(modules, { projectName: opts.projectName, version: opts.version });
56
+ }
57
+
58
+ module.exports = {
59
+ // Core pipeline
60
+ collectFiles,
61
+ extractModule,
62
+ extractModules,
63
+ buildSite,
64
+ generateSite,
65
+ // Helpers
66
+ moduleLabel,
67
+ moduleHtmlPath,
68
+ // Constants
69
+ DEFAULT_EXTENSIONS,
70
+ DEFAULT_IGNORE_DIRS,
71
+ };