jsdoc-scribe 1.0.1 → 1.11.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,57 @@
1
+ "use strict";
2
+
3
+ const { collectFiles, DEFAULT_EXTENSIONS, DEFAULT_IGNORE_DIRS } = require("./index.js");
4
+ const { extractModule } = require("./extractor.js");
5
+ const { buildSite, moduleLabel, moduleHtmlPath } = require("./renderer.js");
6
+
7
+ /**
8
+ * Extract documentation models from an array of file paths.
9
+ * All files are attempted; failures are logged to stderr and omitted from results.
10
+ * Uses Promise.allSettled so a single bad file never aborts the batch.
11
+ *
12
+ * @param {string[]} files
13
+ * @returns {Promise<object[]>}
14
+ */
15
+ async function extractModules(files) {
16
+ const results = await Promise.allSettled(
17
+ files.map(f => Promise.resolve().then(() => extractModule(f)))
18
+ );
19
+ const modules = [];
20
+ for (let i = 0; i < results.length; i++) {
21
+ const r = results[i];
22
+ if (r.status === "fulfilled") {
23
+ modules.push(r.value);
24
+ } else {
25
+ process.stderr.write("jsdoc-scribe/docs: skipped " + files[i] + ": " + r.reason.message + "\n");
26
+ }
27
+ }
28
+ return modules;
29
+ }
30
+
31
+ /**
32
+ * One-shot convenience: collect files, extract docs, build HTML site.
33
+ *
34
+ * @param {string|string[]} inputPaths Source file or directory paths
35
+ * @param {object} [options] Same options as buildSite()
36
+ * @returns {Promise<Array<{path:string,html:string}>>}
37
+ */
38
+ async function generateSite(inputPaths, options) {
39
+ const paths = Array.isArray(inputPaths) ? inputPaths : [inputPaths];
40
+ const opts = options || {};
41
+ const files = [].concat(...paths.map(p => collectFiles(p, opts.extensions, opts.ignoreDirs)));
42
+ const unique = [...new Set(files)];
43
+ const modules = await extractModules(unique);
44
+ return buildSite(modules, { projectName: opts.projectName, version: opts.version });
45
+ }
46
+
47
+ module.exports = {
48
+ collectFiles,
49
+ extractModule,
50
+ extractModules,
51
+ buildSite,
52
+ generateSite,
53
+ moduleLabel,
54
+ moduleHtmlPath,
55
+ DEFAULT_EXTENSIONS,
56
+ DEFAULT_IGNORE_DIRS,
57
+ };