jsdoc-scribe 1.0.1 → 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/README.md CHANGED
@@ -1,110 +1,500 @@
1
- # jsdoc-scribe
2
-
3
- Pure, deterministic, **AST-based** JSDoc comment generator for JavaScript & TypeScript.
4
- **No AI / LLM is used anywhere** — every line of every comment is derived
5
- mechanically from the syntax tree (names, modifiers, type annotations,
6
- parameter lists, heritage clauses, enum members, etc). Same input always
7
- produces the same output.
8
-
9
- ## Install
10
-
11
- ```bash
12
- # run once without installing anything
13
- npx jsdoc-scribe . --write
14
-
15
- # or add it to your project
16
- npm install --save-dev jsdoc-scribe
17
-
18
- # or install it globally
19
- npm install -g jsdoc-scribe
20
- ```
21
-
22
- Once installed, the command is **`gen-comments`**.
23
-
24
- ## Usage
25
-
26
- ```bash
27
- gen-comments <path> [path2 ...] [options]
28
- ```
29
-
30
- `<path>` can be a single file **or a directory** — directories are scanned
31
- recursively for `.js` / `.jsx` / `.ts` / `.tsx` files. `node_modules`, `.git`,
32
- `dist`, `build`, `out`, `coverage`, `.next`, `.turbo`, `.cache`, and any other
33
- dotfolder are skipped automatically.
34
-
35
- | Flag | Description |
36
- |---|---|
37
- | `--write`, `-w` | Edit files **in place**. Without this flag, output goes to a sibling `<name>.commented.<ext>` file next to each original, so you can review a diff before committing to it. |
38
- | `--force`, `-f` | Re-insert comment blocks even on nodes that already have a leading `/** */`. Off by default, to stay idempotent. |
39
- | `--help`, `-h` | Show usage. |
40
- | `--version`, `-v` | Show the installed version. |
41
-
42
- ```bash
43
- gen-comments src/utils.ts # preview only -> utils.commented.ts
44
- gen-comments . # scan whole project, preview only
45
- gen-comments . --write # scan whole project, edit in place
46
- gen-comments src --write --force # also re-document already-commented files
47
- ```
48
-
49
- If you run `--write` outside a git repo, the CLI prints a one-line warning
50
- (not a blocker) recommending you commit first so you have something to diff
51
- or revert against.
52
-
53
- ## The algorithm
54
-
55
- 1. **Parse** each file into a `ts.SourceFile` AST using the TypeScript
56
- compiler's parser (`typescript` npm package). That parser is syntax-only —
57
- it never type-checks — and it's a superset parser for JavaScript, which is
58
- why this works on plain `.js`/`.jsx` just as well as `.ts`/`.tsx`.
59
- 2. **Walk** the AST recursively. Tracked node kinds:
60
- - `FunctionDeclaration` (top-level or nested)
61
- - `ClassDeclaration` + its `constructor` / methods / properties / get-set
62
- - `VariableStatement` (`const`/`let`/`var`, including arrow/function inits)
63
- - `PropertyAssignment` with a function/arrow value inside an object literal
64
- - `InterfaceDeclaration`, `TypeAliasDeclaration`, `EnumDeclaration`
65
- 3. **Skip** any node that already has a leading `/** ... */` block — unless
66
- `--force` is passed. This keeps the tool idempotent: running it twice
67
- never duplicates comments.
68
- 4. **Build** the comment block from pure syntax only:
69
- - An explicit type annotation is always used as-is.
70
- - With no annotation, it falls back to a syntactic guess: literal kind for
71
- variables (`'x'` → `string`, `[1,2]` → `Array`, …), and for function
72
- return types, a scan for a top-level `return <value>;` (so a function
73
- that clearly returns something is never mislabeled `void` just because
74
- it lacks a type annotation).
75
- - Modifiers (`async`, `static`, `private`, `readonly`, `abstract`,
76
- `export`, generator `*`) are read directly off the AST node.
77
- 5. **Insert** the comment as plain text at the exact byte offset where the
78
- node's own line indentation begins. All edits are collected first, then
79
- applied bottom-to-top so earlier offsets never shift — the rest of the
80
- file is never re-printed or reformatted, only insertions happen.
81
- 6. **Write** the result — to `<file>.commented.<ext>` by default, or back to
82
- the original file with `--write`.
83
-
84
- ## Using it as a library
85
-
86
- ```js
87
- const { processFile, collectFiles } = require('jsdoc-scribe');
88
-
89
- // process one file, return number of comment blocks added
90
- processFile('src/utils.ts', { write: true });
91
-
92
- // recursively find every matching source file under a directory
93
- const files = collectFiles('src');
94
- ```
95
-
96
- ## Known scope / limitations (by design)
97
-
98
- - Inline anonymous callbacks passed directly as call arguments
99
- (`arr.map(x => x * 2)`) are **not** commented — inserting a multi-line block
100
- there would mangle the call expression. Anything with its own declaration
101
- (function decl, class member, variable, named object property) is covered.
102
- - Type/return inference is 100% syntactic. It is never "smart" about what
103
- your code *means* — only what it *looks like* structurally.
104
- - Multi-declarator statements (`const a = 1, b = 2;`) get one combined block
105
- rather than a per-declarator function-style doc.
106
- - `.d.ts` files are skipped (no implementation to document).
107
-
108
- ## License
109
-
110
- MIT
1
+ # jsdoc-scribe
2
+
3
+ [![npm version](https://img.shields.io/npm/v/jsdoc-scribe.svg)](https://www.npmjs.com/package/jsdoc-scribe)
4
+ [![npm downloads](https://img.shields.io/npm/dm/jsdoc-scribe.svg)](https://www.npmjs.com/package/jsdoc-scribe)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
6
+ [![Node.js](https://img.shields.io/node/v/jsdoc-scribe.svg)](https://nodejs.org)
7
+
8
+ > Pure, deterministic, **AST-based** JSDoc comment generator and multi-page documentation site builder for JavaScript & TypeScript.
9
+ > **No AI. No LLM. No surprises.** Same input always produces the same output.
10
+
11
+ ---
12
+
13
+ ## What it does
14
+
15
+ `jsdoc-scribe` ships two independent CLI tools:
16
+
17
+ | Tool | What it does |
18
+ |---|---|
19
+ | `gen-comments` | Inserts `/** */` JSDoc blocks into your source files by reading the AST — no AI, no guessing |
20
+ | `gen-docs` | Generates a multi-page HTML documentation site from your already-documented source |
21
+
22
+ ---
23
+
24
+ ## Table of contents
25
+
26
+ - [Install](#install)
27
+ - [gen-comments Add JSDoc to source files](#gen-comments--add-jsdoc-to-source-files)
28
+ - [Before / after example](#before--after-example)
29
+ - [CLI flags](#cli-flags)
30
+ - [How the algorithm works](#how-the-algorithm-works)
31
+ - [gen-docs Build a documentation site](#gen-docs--build-a-documentation-site)
32
+ - [Quick start](#quick-start)
33
+ - [CLI flags](#cli-flags-1)
34
+ - [Config file](#config-file)
35
+ - [Themes](#themes)
36
+ - [Source links](#source-links)
37
+ - [Ignore patterns](#ignore-patterns)
38
+ - [Watch mode](#watch-mode)
39
+ - [Programmatic API](#programmatic-api)
40
+ - [GitHub Actions auto-deploy docs](#github-actions--auto-deploy-docs)
41
+ - [Known limitations](#known-limitations)
42
+ - [License](#license)
43
+
44
+ ---
45
+
46
+ ## Install
47
+
48
+ ```bash
49
+ # run once without installing
50
+ npx jsdoc-scribe . --write
51
+
52
+ # add to your project
53
+ npm install --save-dev jsdoc-scribe
54
+
55
+ # or install globally
56
+ npm install -g jsdoc-scribe
57
+ ```
58
+
59
+ ---
60
+
61
+ ## gen-comments Add JSDoc to source files
62
+
63
+ `gen-comments` walks your source tree, parses every `.js` / `.jsx` / `.ts` / `.tsx` file with the TypeScript compiler's AST parser, and inserts `/** */` blocks for every undocumented function, class, method, interface, enum, type alias, and variable.
64
+
65
+ ### Before / after example
66
+
67
+ **Before** a plain TypeScript file with no documentation:
68
+
69
+ ```ts
70
+ // src/auth.ts
71
+
72
+ export async function login(username: string, password: string): Promise<User> {
73
+ const user = await db.findByUsername(username);
74
+ if (!user || !verify(password, user.passwordHash)) {
75
+ throw new AuthError("Invalid credentials");
76
+ }
77
+ return user;
78
+ }
79
+
80
+ export class TokenService {
81
+ private readonly secret: string;
82
+
83
+ constructor(secret: string) {
84
+ this.secret = secret;
85
+ }
86
+
87
+ sign(payload: Record<string, unknown>, expiresIn = "1h"): string {
88
+ return jwt.sign(payload, this.secret, { expiresIn });
89
+ }
90
+
91
+ verify(token: string): Record<string, unknown> | null {
92
+ try {
93
+ return jwt.verify(token, this.secret) as Record<string, unknown>;
94
+ } catch {
95
+ return null;
96
+ }
97
+ }
98
+ }
99
+
100
+ export type AuthRole = "admin" | "editor" | "viewer";
101
+
102
+ export enum Permission {
103
+ Read = "read",
104
+ Write = "write",
105
+ Delete = "delete",
106
+ }
107
+ ```
108
+
109
+ **After** — run `gen-comments src/auth.ts --write`:
110
+
111
+ ```ts
112
+ // src/auth.ts
113
+
114
+ /**
115
+ * @async
116
+ * @param {string} username
117
+ * @param {string} password
118
+ * @returns {Promise<User>}
119
+ */
120
+ export async function login(username: string, password: string): Promise<User> {
121
+ const user = await db.findByUsername(username);
122
+ if (!user || !verify(password, user.passwordHash)) {
123
+ throw new AuthError("Invalid credentials");
124
+ }
125
+ return user;
126
+ }
127
+
128
+ /**
129
+ * @class TokenService
130
+ */
131
+ export class TokenService {
132
+ /** @type {string} */
133
+ private readonly secret: string;
134
+
135
+ /**
136
+ * @constructor
137
+ * @param {string} secret
138
+ */
139
+ constructor(secret: string) {
140
+ this.secret = secret;
141
+ }
142
+
143
+ /**
144
+ * @param {Record<string, unknown>} payload
145
+ * @param {string} [expiresIn]
146
+ * @returns {string}
147
+ */
148
+ sign(payload: Record<string, unknown>, expiresIn = "1h"): string {
149
+ return jwt.sign(payload, this.secret, { expiresIn });
150
+ }
151
+
152
+ /**
153
+ * @param {string} token
154
+ * @returns {Record<string, unknown> | null}
155
+ */
156
+ verify(token: string): Record<string, unknown> | null {
157
+ try {
158
+ return jwt.verify(token, this.secret) as Record<string, unknown>;
159
+ } catch {
160
+ return null;
161
+ }
162
+ }
163
+ }
164
+
165
+ /**
166
+ * @typedef {"admin" | "editor" | "viewer"} AuthRole
167
+ */
168
+ export type AuthRole = "admin" | "editor" | "viewer";
169
+
170
+ /**
171
+ * @enum {string}
172
+ */
173
+ export enum Permission {
174
+ Read = "read",
175
+ Write = "write",
176
+ Delete = "delete",
177
+ }
178
+ ```
179
+
180
+ > The tool is **idempotent** — running it a second time adds zero blocks.
181
+ > Nodes that already have a `/** */` block are never touched unless you pass `--force`.
182
+
183
+ ### CLI flags
184
+
185
+ ```bash
186
+ gen-comments <path> [path2 ...] [options]
187
+ ```
188
+
189
+ `<path>` can be a file or directory. Directories are recursively scanned. `node_modules`, `dist`, `build`, `.git`, and all dotfolders are skipped automatically.
190
+
191
+ | Flag | Short | Description |
192
+ |---|---|---|
193
+ | `--write` | `-w` | Edit files **in place**. Without this flag, output goes to a sibling `<name>.out.<ext>` file so you can review the diff first. |
194
+ | `--force` | `-f` | Re-insert blocks even on nodes that already have `/** */`. Useful for regenerating stale docs. |
195
+ | `--help` | `-h` | Show usage. |
196
+ | `--version` | `-v` | Show installed version. |
197
+
198
+ ```bash
199
+ gen-comments src/utils.ts # preview → writes utils.out.ts
200
+ gen-comments . # scan whole project, preview only
201
+ gen-comments . --write # scan and edit in place
202
+ gen-comments src --write --force # also re-document already-commented files
203
+ ```
204
+
205
+ > **Tip:** Commit your changes before running `--write`. The CLI reminds you if you're outside a git repo.
206
+
207
+ ### How the algorithm works
208
+
209
+ 1. **Parse** — uses `typescript` (the npm package) purely as a syntax parser. Works on `.js`/`.jsx` as well as `.ts`/`.tsx`. No type-checking, no compilation.
210
+ 2. **Walk** — visits `FunctionDeclaration`, `ClassDeclaration` (+ constructor / methods / properties / accessors), `VariableStatement` (including arrow functions and const objects), `InterfaceDeclaration`, `TypeAliasDeclaration`, `EnumDeclaration`.
211
+ 3. **Skip** — any node with an existing leading `/** */` block is left untouched (unless `--force`).
212
+ 4. **Build** — the comment block is built purely from syntax:
213
+ - Explicit type annotations are used verbatim.
214
+ - Without annotations, the tool infers from syntax: literal kind for variables (`'x'` → `string`, `[1,2]` → `Array`), and for function returns it scans for a top-level `return <value>` statement.
215
+ - Modifiers (`async`, `static`, `private`, `readonly`, `abstract`, `export`, generator `*`) are read from the AST.
216
+ 5. **Insert** — edits are collected and applied bottom-to-top so no byte offset shifts, and the rest of the file is never reprinted.
217
+
218
+ ---
219
+
220
+ ## gen-docs — Build a documentation site
221
+
222
+ `gen-docs` reads your source files (or an entire directory), extracts the public API using the same AST engine, and outputs a multi-page HTML documentation site with search, themes, source links, and cross-references.
223
+
224
+ ### Quick start
225
+
226
+ ```bash
227
+ # Generate docs for an entire directory
228
+ gen-docs src --out docs --title "My Project"
229
+
230
+ # Open docs/index.html in your browser
231
+ ```
232
+
233
+ The output is a self-contained static site — no server required.
234
+
235
+ ### CLI flags
236
+
237
+ ```bash
238
+ gen-docs <path> [path2 ...] [options]
239
+ ```
240
+
241
+ | Flag | Short | Description |
242
+ |---|---|---|
243
+ | `--out <dir>` | `-o` | Output directory (default: `docs`). |
244
+ | `--title <name>` | `-t` | Site title shown in the header and `<title>` tag. |
245
+ | `--theme <name>` | `-T` | Visual theme: `default`, `minimal`, or `dark` (default: `default`). |
246
+ | `--json` | `-j` | Also write a `docs.json` machine-readable export. |
247
+ | `--readme` | `-r` | Also write a `README.md` with markdown tables of every module. |
248
+ | `--source-url <url>` | `-s` | GitHub base URL for per-card source links (e.g. `https://github.com/org/repo/blob/main`). |
249
+ | `--ignore <glob>` | `-I` | Glob pattern to exclude files. Repeatable. |
250
+ | `--config <file>` | `-c` | Path to a `.jsdoc-scribe.json` config file. |
251
+ | `--watch` | `-W` | Rebuild on file changes (150 ms debounce). |
252
+ | `--help` | `-h` | Show usage. |
253
+ | `--version` | `-v` | Show installed version. |
254
+
255
+ ```bash
256
+ gen-docs src --out docs --title "My API"
257
+ gen-docs src --theme dark --source-url https://github.com/org/repo/blob/main
258
+ gen-docs src --json --readme --out _site
259
+ gen-docs src --ignore "**/internal/**" --ignore "**/*.test.ts"
260
+ gen-docs src --watch
261
+ ```
262
+
263
+ ### Config file
264
+
265
+ Create `.jsdoc-scribe.json` in your project root to avoid repeating flags:
266
+
267
+ ```json
268
+ {
269
+ "out": "docs",
270
+ "title": "My Project",
271
+ "theme": "default",
272
+ "json": true,
273
+ "readme": false,
274
+ "sourceUrl": "https://github.com/org/repo/blob/main",
275
+ "ignore": [
276
+ "**/internal/**",
277
+ "**/*.test.ts",
278
+ "**/*.spec.ts"
279
+ ]
280
+ }
281
+ ```
282
+
283
+ CLI flags always override config file values. Use `--config path/to/other.json` to point to a custom config path.
284
+
285
+ ### Themes
286
+
287
+ | Theme | Description |
288
+ |---|---|
289
+ | `default` | Blue-accented light sidebar, light/dark toggle stored in `localStorage`. |
290
+ | `minimal` | Clean light-only layout, no toggle. |
291
+ | `dark` | Forced dark mode, no toggle. |
292
+
293
+ ```bash
294
+ gen-docs src --theme minimal
295
+ gen-docs src --theme dark
296
+ ```
297
+
298
+ ### Source links
299
+
300
+ Pass a GitHub blob base URL and every documented symbol card gets a clickable "line N" link straight to the source:
301
+
302
+ ```bash
303
+ gen-docs src \
304
+ --source-url https://github.com/org/repo/blob/main \
305
+ --out docs
306
+ ```
307
+
308
+ Each card then shows `line 42 ↗` linking to `https://github.com/org/repo/blob/main/src/utils.ts#L42`.
309
+
310
+ ### Ignore patterns
311
+
312
+ ```bash
313
+ # Exclude test files and an entire folder
314
+ gen-docs src \
315
+ --ignore "**/*.test.ts" \
316
+ --ignore "**/*.spec.ts" \
317
+ --ignore "**/internal/**"
318
+ ```
319
+
320
+ Patterns support `**/` prefix and `*` wildcard. You can also specify them in `.jsdoc-scribe.json`.
321
+
322
+ ### Watch mode
323
+
324
+ ```bash
325
+ gen-docs src --out docs --watch
326
+ ```
327
+
328
+ The site rebuilds whenever any matching source file changes. Useful during active development — just refresh the browser.
329
+
330
+ ---
331
+
332
+ ## Programmatic API
333
+
334
+ ```js
335
+ const {
336
+ collectFiles,
337
+ extractModule,
338
+ extractModules,
339
+ buildSite,
340
+ generateSite,
341
+ moduleLabel,
342
+ moduleHtmlPath,
343
+ DEFAULT_EXTENSIONS,
344
+ DEFAULT_IGNORE_DIRS,
345
+ } = require('jsdoc-scribe/docs');
346
+ ```
347
+
348
+ ### One-shot generation
349
+
350
+ ```js
351
+ const { generateSite } = require('jsdoc-scribe/docs');
352
+
353
+ generateSite(['src'], {
354
+ out: 'docs',
355
+ title: 'My Project',
356
+ theme: 'default',
357
+ sourceUrl: 'https://github.com/org/repo/blob/main',
358
+ });
359
+ ```
360
+
361
+ ### Step-by-step
362
+
363
+ ```js
364
+ const { collectFiles, extractModules, buildSite } = require('jsdoc-scribe/docs');
365
+ const fs = require('fs');
366
+ const path = require('path');
367
+
368
+ // 1. Collect source files
369
+ const files = collectFiles(['src'], {
370
+ extensions: ['.ts', '.tsx'],
371
+ ignore: ['**/*.test.ts'],
372
+ });
373
+
374
+ // 2. Extract the API model
375
+ const modules = extractModules(files);
376
+
377
+ // 3. Inspect the model
378
+ modules.forEach(mod => {
379
+ console.log(mod.filePath, {
380
+ functions: mod.functions.length,
381
+ classes: mod.classes.length,
382
+ description: mod.description,
383
+ });
384
+ });
385
+
386
+ // 4. Build the HTML site
387
+ const pages = buildSite(modules, {
388
+ title: 'My Project',
389
+ theme: 'dark',
390
+ sourceUrl: 'https://github.com/org/repo/blob/main',
391
+ });
392
+
393
+ // 5. Write pages yourself
394
+ const outDir = 'docs';
395
+ fs.mkdirSync(outDir, { recursive: true });
396
+ for (const [filePath, html] of Object.entries(pages)) {
397
+ const dest = path.join(outDir, filePath);
398
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
399
+ fs.writeFileSync(dest, html);
400
+ }
401
+ ```
402
+
403
+ ### Extracted module shape
404
+
405
+ Each module object returned by `extractModule(filePath)` has this structure:
406
+
407
+ ```ts
408
+ {
409
+ filePath: string;
410
+ description: string | null; // top-of-file @module description
411
+ since: string | null; // @since from top-of-file block
412
+ functions: Array<{
413
+ name: string;
414
+ params: Array<{ name: string; type: string; optional: boolean }>;
415
+ returnType: string;
416
+ isAsync: boolean;
417
+ isExported: boolean;
418
+ line: number; // 1-based line number
419
+ description: string | null;
420
+ jsdocParams: Array<{ name: string; type: string; description: string }>;
421
+ returns: { type: string; description: string } | null;
422
+ throws: Array<{ type: string; description: string }>;
423
+ since: string | null;
424
+ deprecated: string | null;
425
+ }>;
426
+ classes: Array<{ name: string; methods: [...]; properties: [...]; ... }>;
427
+ interfaces: Array<{ ... }>;
428
+ typeAliases: Array<{ ... }>;
429
+ enums: Array<{ ... }>;
430
+ variables: Array<{ ... }>;
431
+ }
432
+ ```
433
+
434
+ ---
435
+
436
+ ## GitHub Actions — auto-deploy docs
437
+
438
+ Add this workflow to automatically publish your docs to GitHub Pages on every push to `main`:
439
+
440
+ ```yaml
441
+ # .github/workflows/docs.yml
442
+ name: Deploy docs
443
+
444
+ on:
445
+ push:
446
+ branches: [main]
447
+
448
+ permissions:
449
+ contents: read
450
+ pages: write
451
+ id-token: write
452
+
453
+ jobs:
454
+ deploy:
455
+ runs-on: ubuntu-latest
456
+ environment:
457
+ name: github-pages
458
+ url: ${{ steps.deployment.outputs.page_url }}
459
+ steps:
460
+ - uses: actions/checkout@v4
461
+
462
+ - uses: actions/setup-node@v4
463
+ with:
464
+ node-version: 20
465
+
466
+ - run: npm ci
467
+
468
+ - name: Generate docs
469
+ run: |
470
+ npx gen-docs src \
471
+ --out _site \
472
+ --title "${{ github.event.repository.name }}" \
473
+ --source-url "https://github.com/${{ github.repository }}/blob/main" \
474
+ --json
475
+
476
+ - uses: actions/upload-pages-artifact@v3
477
+ with:
478
+ path: _site
479
+
480
+ - id: deployment
481
+ uses: actions/deploy-pages@v4
482
+ ```
483
+
484
+ Enable GitHub Pages in your repo settings (Settings → Pages → Source: GitHub Actions) and your docs will be live at `https://<org>.github.io/<repo>` after every push.
485
+
486
+ ---
487
+
488
+ ## Known limitations
489
+
490
+ - **Inline anonymous callbacks** passed directly as call arguments (`arr.map(x => x * 2)`) are not documented — inserting a multi-line block there would mangle the expression. Named declarations (functions, class members, variables, object properties) are all covered.
491
+ - **Type inference is 100% syntactic.** The tool reads what your code *looks like*, not what it *means*. It never evaluates, imports, or type-checks.
492
+ - **Multi-declarator statements** (`const a = 1, b = 2;`) get one combined block rather than per-declarator docs.
493
+ - **`.d.ts` files** are skipped — no implementation to document.
494
+ - **`gen-docs` does not serve** — the output is a static site. Use any static file server (`npx serve docs`) or deploy to GitHub Pages, Netlify, Vercel, etc.
495
+
496
+ ---
497
+
498
+ ## License
499
+
500
+ MIT © [Chintan](https://github.com/imchintoo)