@goobits/docs-engine-cli 2.0.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 ADDED
@@ -0,0 +1,324 @@
1
+ # @goobits/docs-engine-cli
2
+
3
+ Command-line tools for managing and validating documentation built with @goobits/docs-engine.
4
+
5
+ ## Features
6
+
7
+ - āœ… **Link validation** - Check internal and external links
8
+ - šŸ” **Anchor checking** - Validate anchor links (#section)
9
+ - šŸ“ **File existence** - Ensure linked files exist
10
+ - 🌐 **External validation** - HTTP requests to validate external URLs
11
+ - šŸ“¦ **Version management** - Manage multiple documentation versions
12
+ - ⚔ **Performance** - Concurrent checking with configurable limits
13
+ - šŸŽØ **Beautiful output** - Color-coded results with chalk
14
+ - āš™ļø **Configurable** - Support for config files
15
+ - šŸ”„ **CI-friendly** - JSON output and non-zero exit codes
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ pnpm add -D @goobits/docs-engine-cli
21
+ # or
22
+ npm install --save-dev @goobits/docs-engine-cli
23
+ ```
24
+
25
+ ## Commands
26
+
27
+ ### Link Checker
28
+
29
+ Check all links in your markdown documentation for broken internal references and optionally validate external URLs.
30
+
31
+ ```bash
32
+ # Basic usage - check internal links
33
+ docs-engine check-links
34
+
35
+ # Check with external link validation
36
+ docs-engine check-links --external
37
+
38
+ # Custom base directory
39
+ docs-engine check-links --base-dir ./documentation
40
+
41
+ # Quiet mode (only show errors)
42
+ docs-engine check-links --quiet
43
+
44
+ # JSON output for CI integration
45
+ docs-engine check-links --json
46
+ ```
47
+
48
+ **Options:**
49
+
50
+ - `-b, --base-dir <path>` - Base directory for documentation (default: current directory)
51
+ - `-p, --pattern <glob>` - Glob pattern for files to check (default: `**/*.{md,mdx}`)
52
+ - `-e, --external` - Validate external links (slower, default: `false`)
53
+ - `-t, --timeout <ms>` - External link timeout in milliseconds (default: `5000`)
54
+ - `-c, --concurrency <number>` - Max concurrent external requests (default: `10`)
55
+ - `-q, --quiet` - Only show errors, hide valid links
56
+ - `--json` - Output results as JSON
57
+ - `--config <path>` - Path to config file
58
+
59
+ **Exit Codes:**
60
+ - `0` - All links valid
61
+ - `1` - Broken links found or error occurred
62
+
63
+ ### Version Management
64
+
65
+ Manage multiple versions of your documentation (similar to Docusaurus versioning).
66
+
67
+ #### Create Version
68
+
69
+ Create a new documentation version from current docs:
70
+
71
+ ```bash
72
+ # Create version 2.0
73
+ docs-engine version create 2.0
74
+
75
+ # Create with custom docs directory
76
+ docs-engine version create 2.0 --docs-dir ./documentation
77
+ ```
78
+
79
+ This will:
80
+ 1. Copy current docs to `docs/versioned_docs/version-2.0/`
81
+ 2. Update `docs/versions.json` with the new version
82
+ 3. Mark the new version as "latest"
83
+
84
+ #### List Versions
85
+
86
+ List all available documentation versions:
87
+
88
+ ```bash
89
+ docs-engine version list
90
+
91
+ # Output:
92
+ # 2.0 [latest]
93
+ # 1.5 [stable]
94
+ # 1.0 [legacy]
95
+ ```
96
+
97
+ #### Delete Version
98
+
99
+ Delete a documentation version:
100
+
101
+ ```bash
102
+ docs-engine version delete 1.0
103
+
104
+ # Skip confirmation
105
+ docs-engine version delete 1.0 --force
106
+ ```
107
+
108
+ ## Configuration
109
+
110
+ ### Config File
111
+
112
+ Create `.docs-engine.json` in your project root:
113
+
114
+ ```json
115
+ {
116
+ "baseDir": ".",
117
+ "pattern": "docs/**/*.{md,mdx}",
118
+ "checkExternal": false,
119
+ "timeout": 5000,
120
+ "concurrency": 10,
121
+ "exclude": [
122
+ "**/node_modules/**",
123
+ "**/dist/**",
124
+ "**/.git/**"
125
+ ],
126
+ "skipDomains": [
127
+ "localhost",
128
+ "127.0.0.1",
129
+ "example.com"
130
+ ],
131
+ "validExtensions": [".md", ".mdx"]
132
+ }
133
+ ```
134
+
135
+ **Configuration Options:**
136
+
137
+ - **`baseDir`** (string): Base directory for documentation
138
+ - **`pattern`** (string): Glob pattern for files to check
139
+ - **`checkExternal`** (boolean): Whether to validate external links
140
+ - **`exclude`** (string[]): Patterns to exclude from checking
141
+ - **`timeout`** (number): Timeout for external requests in milliseconds
142
+ - **`concurrency`** (number): Maximum concurrent external requests
143
+ - **`skipDomains`** (string[]): Domains to skip validation
144
+ - **`validExtensions`** (string[]): File extensions to treat as valid
145
+
146
+ ## CI Integration
147
+
148
+ ### GitHub Actions
149
+
150
+ ```yaml
151
+ name: Check Links
152
+
153
+ on: [push, pull_request]
154
+
155
+ jobs:
156
+ link-check:
157
+ runs-on: ubuntu-latest
158
+ steps:
159
+ - uses: actions/checkout@v3
160
+ - uses: pnpm/action-setup@v2
161
+ - uses: actions/setup-node@v3
162
+ with:
163
+ node-version: 20
164
+ cache: 'pnpm'
165
+
166
+ - run: pnpm install
167
+ - run: pnpm docs-engine check-links
168
+
169
+ # Optional: Check external links on schedule
170
+ - name: Check external links
171
+ if: github.event_name == 'schedule'
172
+ run: pnpm docs-engine check-links --external
173
+ ```
174
+
175
+ ### Pre-commit Hook
176
+
177
+ ```bash
178
+ #!/bin/sh
179
+ docs-engine check-links --quiet || exit 1
180
+ ```
181
+
182
+ ## Versioning Workflow
183
+
184
+ ### 1. Initial Setup
185
+
186
+ Start with your documentation in `docs/`:
187
+
188
+ ```
189
+ docs/
190
+ getting-started.md
191
+ api-reference.md
192
+ guides/
193
+ ...
194
+ ```
195
+
196
+ ### 2. Create First Version
197
+
198
+ When releasing v1.0:
199
+
200
+ ```bash
201
+ docs-engine version create 1.0
202
+ ```
203
+
204
+ This creates:
205
+
206
+ ```
207
+ docs/
208
+ current/ # Development version
209
+ versioned_docs/
210
+ version-1.0/ # v1.0 release
211
+ versions.json # Version metadata
212
+ ```
213
+
214
+ ### 3. Continue Development
215
+
216
+ Keep editing files in `docs/current/` for the next release.
217
+
218
+ ### 4. Release Next Version
219
+
220
+ When releasing v2.0:
221
+
222
+ ```bash
223
+ docs-engine version create 2.0
224
+ ```
225
+
226
+ Users can now view:
227
+ - `/docs/` - Latest (v2.0)
228
+ - `/docs/v2.0/` - v2.0 documentation
229
+ - `/docs/v1.0/` - v1.0 documentation
230
+
231
+ ## Link Validation Details
232
+
233
+ ### Internal Links
234
+
235
+ The link checker validates:
236
+
237
+ - **Relative links**: `./page.md`, `../other.md`
238
+ - **Absolute links**: `/docs/page.md`
239
+ - **Anchor links**: `#section`, `page.md#section`
240
+ - **Markdown links**: `[text](url)`
241
+ - **HTML links**: `<a href="url">`
242
+ - **Image links**: `![alt](image.png)`
243
+
244
+ ### External Links
245
+
246
+ When `--external` is enabled:
247
+
248
+ - āœ… HTTP/HTTPS URLs validated with HEAD/GET requests
249
+ - āœ… Follow redirects (up to 5 hops)
250
+ - āœ… Configurable timeout and concurrency
251
+ - āœ… Result caching to avoid duplicate requests
252
+ - āœ… Domain skipping for local/test URLs
253
+ - āœ… Rate limiting to avoid overwhelming servers
254
+
255
+ ### Error Types
256
+
257
+ - **File not found** - Internal link points to non-existent file
258
+ - **Anchor not found** - Section anchor doesn't exist in target file
259
+ - **External error** - HTTP error (404, 500, etc.)
260
+ - **Timeout** - External URL didn't respond in time
261
+ - **Network error** - Connection refused or DNS failure
262
+
263
+ ## Output Examples
264
+
265
+ ### Success Output
266
+
267
+ ```
268
+ āœ” Link checker initialized
269
+
270
+ šŸ“Š Checked 127 links across 45 files
271
+
272
+ āœ“ Valid: 125
273
+ ⚠ Warnings: 2
274
+
275
+ All critical links are valid!
276
+ ```
277
+
278
+ ### Error Output
279
+
280
+ ```
281
+ āœ– Link checker found errors
282
+
283
+ šŸ“Š Checked 127 links across 45 files
284
+
285
+ āœ“ Valid: 123
286
+ āœ— Broken: 4
287
+
288
+ Broken Links:
289
+
290
+ src/content/docs/api.md:45
291
+ → /docs/missing-page.md
292
+ āœ— File not found
293
+
294
+ src/content/docs/guide.md:12
295
+ → ./tutorial.md#non-existent
296
+ āœ— Anchor #non-existent not found
297
+
298
+ src/content/docs/external.md:8
299
+ → https://example.com/broken
300
+ āœ— 404 Not Found
301
+ ```
302
+
303
+ ## Development
304
+
305
+ ```bash
306
+ # Install dependencies
307
+ pnpm install
308
+
309
+ # Build the CLI
310
+ pnpm build
311
+
312
+ # Test locally
313
+ node dist/index.js check-links
314
+
315
+ # Watch mode during development
316
+ pnpm dev
317
+
318
+ # Test the CLI
319
+ docs-engine check-links
320
+ ```
321
+
322
+ ## License
323
+
324
+ MIT Ā© GooBits
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/index.js ADDED
@@ -0,0 +1,796 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+ import { glob } from "glob";
6
+ import ora from "ora";
7
+ import chalk2 from "chalk";
8
+ import path3 from "path";
9
+ import { readFileSync as readFileSync4 } from "fs";
10
+ import { fileURLToPath } from "url";
11
+ import { dirname as dirname2, join as join2 } from "path";
12
+
13
+ // src/link-extractor.ts
14
+ import { unified } from "unified";
15
+ import remarkParse from "remark-parse";
16
+ import remarkMdx from "remark-mdx";
17
+ import { visit } from "unist-util-visit";
18
+ import { readFileSync } from "fs";
19
+ function isExternalUrl(url) {
20
+ return /^https?:\/\//i.test(url);
21
+ }
22
+ function isAnchorOnly(url) {
23
+ return url.startsWith("#");
24
+ }
25
+ function extractLinksFromFile(filePath) {
26
+ const content = readFileSync(filePath, "utf-8");
27
+ const links = [];
28
+ const tree = unified().use(remarkParse).use(remarkMdx).parse(content);
29
+ visit(tree, ["link", "image"], (node, _index, _parent) => {
30
+ const url = node.url;
31
+ if (!url) return;
32
+ const position = node.position;
33
+ const line = position?.start.line || 0;
34
+ let text = "";
35
+ if (node.type === "link") {
36
+ const linkNode = node;
37
+ if (linkNode.children && linkNode.children.length > 0) {
38
+ const firstChild = linkNode.children[0];
39
+ if ("value" in firstChild) {
40
+ text = firstChild.value;
41
+ }
42
+ }
43
+ } else if (node.type === "image") {
44
+ const imageNode = node;
45
+ text = imageNode.alt || "";
46
+ }
47
+ links.push({
48
+ url,
49
+ text,
50
+ file: filePath,
51
+ line,
52
+ type: node.type === "image" ? "image" : "link",
53
+ isExternal: isExternalUrl(url),
54
+ isAnchor: isAnchorOnly(url)
55
+ });
56
+ });
57
+ const htmlLinkRegex = /<a\s+(?:[^>]*?\s+)?href=["']([^"']+)["']/gi;
58
+ const lines = content.split("\n");
59
+ lines.forEach((lineContent, index) => {
60
+ let match;
61
+ while ((match = htmlLinkRegex.exec(lineContent)) !== null) {
62
+ const url = match[1];
63
+ links.push({
64
+ url,
65
+ text: "",
66
+ // Would need HTML parsing to extract text
67
+ file: filePath,
68
+ line: index + 1,
69
+ type: "html",
70
+ isExternal: isExternalUrl(url),
71
+ isAnchor: isAnchorOnly(url)
72
+ });
73
+ }
74
+ });
75
+ return links;
76
+ }
77
+ function extractLinksFromFiles(filePaths) {
78
+ const allLinks = [];
79
+ for (const file of filePaths) {
80
+ try {
81
+ const links = extractLinksFromFile(file);
82
+ allLinks.push(...links);
83
+ } catch (error) {
84
+ console.error(`Error extracting links from ${file}:`, error);
85
+ }
86
+ }
87
+ return allLinks;
88
+ }
89
+
90
+ // src/link-validator.ts
91
+ import { existsSync, readFileSync as readFileSync2, statSync } from "fs";
92
+ import { resolve, dirname, join } from "path";
93
+ import pLimit from "p-limit";
94
+ function resolveLinkPath(link, sourceFile, baseDir) {
95
+ const [pathPart] = link.split("#");
96
+ if (pathPart.startsWith("/")) {
97
+ return resolve(baseDir, pathPart.slice(1));
98
+ }
99
+ const sourceDir = dirname(sourceFile);
100
+ return resolve(sourceDir, pathPart);
101
+ }
102
+ function extractAnchor(url) {
103
+ const parts = url.split("#");
104
+ return parts.length > 1 ? parts[1] : void 0;
105
+ }
106
+ function anchorExistsInFile(filePath, anchor) {
107
+ try {
108
+ const content = readFileSync2(filePath, "utf-8");
109
+ const normalizedAnchor = anchor.toLowerCase().replace(/\s+/g, "-");
110
+ const headerRegex = /^#+\s+(.+)$/gm;
111
+ let match;
112
+ while ((match = headerRegex.exec(content)) !== null) {
113
+ const headerText = match[1];
114
+ const headerId = headerText.toLowerCase().replace(/[^\w\s-]/g, "").replace(/\s+/g, "-");
115
+ if (headerId === normalizedAnchor) {
116
+ return true;
117
+ }
118
+ }
119
+ const htmlAnchorRegex = /<a\s+(?:name|id)=["']([^"']+)["']/gi;
120
+ while ((match = htmlAnchorRegex.exec(content)) !== null) {
121
+ if (match[1] === anchor) {
122
+ return true;
123
+ }
124
+ }
125
+ return false;
126
+ } catch {
127
+ return false;
128
+ }
129
+ }
130
+ function validateInternalLink(link, options) {
131
+ const { baseDir, validExtensions = [".md", ".mdx"] } = options;
132
+ try {
133
+ let targetPath = resolveLinkPath(link.url, link.file, baseDir);
134
+ let fileExists = existsSync(targetPath);
135
+ if (!fileExists) {
136
+ for (const ext of validExtensions) {
137
+ const pathWithExt = targetPath + ext;
138
+ if (existsSync(pathWithExt)) {
139
+ targetPath = pathWithExt;
140
+ fileExists = true;
141
+ break;
142
+ }
143
+ }
144
+ }
145
+ if (!fileExists && existsSync(targetPath) && statSync(targetPath).isDirectory()) {
146
+ for (const ext of validExtensions) {
147
+ const indexPath = join(targetPath, `index${ext}`);
148
+ if (existsSync(indexPath)) {
149
+ targetPath = indexPath;
150
+ fileExists = true;
151
+ break;
152
+ }
153
+ }
154
+ }
155
+ if (!fileExists) {
156
+ return {
157
+ link,
158
+ isValid: false,
159
+ error: `File not found: ${targetPath}`
160
+ };
161
+ }
162
+ const anchor = extractAnchor(link.url);
163
+ if (anchor) {
164
+ const anchorExists = anchorExistsInFile(targetPath, anchor);
165
+ if (!anchorExists) {
166
+ return {
167
+ link,
168
+ isValid: false,
169
+ error: `Anchor #${anchor} not found in ${targetPath}`
170
+ };
171
+ }
172
+ }
173
+ return {
174
+ link,
175
+ isValid: true
176
+ };
177
+ } catch (error) {
178
+ return {
179
+ link,
180
+ isValid: false,
181
+ error: error instanceof Error ? error.message : "Unknown error"
182
+ };
183
+ }
184
+ }
185
+ async function validateExternalLink(link, options) {
186
+ const { timeout = 5e3, skipDomains = [] } = options;
187
+ try {
188
+ const url = new URL(link.url);
189
+ if (skipDomains.some((domain) => url.hostname.includes(domain))) {
190
+ return {
191
+ link,
192
+ isValid: true,
193
+ statusCode: 0
194
+ // Skipped
195
+ };
196
+ }
197
+ const controller = new AbortController();
198
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
199
+ const response = await fetch(link.url, {
200
+ method: "HEAD",
201
+ signal: controller.signal,
202
+ redirect: "follow"
203
+ });
204
+ clearTimeout(timeoutId);
205
+ const isValid = response.ok;
206
+ return {
207
+ link,
208
+ isValid,
209
+ statusCode: response.status,
210
+ redirectUrl: response.redirected ? response.url : void 0,
211
+ error: isValid ? void 0 : `HTTP ${response.status} ${response.statusText}`
212
+ };
213
+ } catch (error) {
214
+ return {
215
+ link,
216
+ isValid: false,
217
+ statusCode: 0,
218
+ error: error instanceof Error ? error.message : "Request failed"
219
+ };
220
+ }
221
+ }
222
+ async function validateLinks(links, options) {
223
+ const { concurrency = 10, checkExternal = false } = options;
224
+ const results = [];
225
+ const internalLinks = links.filter((l) => !l.isExternal);
226
+ const externalLinks = links.filter((l) => l.isExternal);
227
+ for (const link of internalLinks) {
228
+ results.push(validateInternalLink(link, options));
229
+ }
230
+ if (checkExternal && externalLinks.length > 0) {
231
+ const limit = pLimit(concurrency);
232
+ const externalResults = await Promise.all(
233
+ externalLinks.map((link) => limit(() => validateExternalLink(link, options)))
234
+ );
235
+ results.push(...externalResults);
236
+ }
237
+ return results;
238
+ }
239
+
240
+ // src/reporter.ts
241
+ import chalk from "chalk";
242
+ function calculateStats(results) {
243
+ return {
244
+ total: results.length,
245
+ valid: results.filter((r) => r.isValid).length,
246
+ broken: results.filter((r) => !r.isValid).length,
247
+ external: results.filter((r) => r.link.isExternal).length,
248
+ internal: results.filter((r) => !r.link.isExternal).length
249
+ };
250
+ }
251
+ function groupByErrorType(results) {
252
+ const broken = results.filter((r) => !r.isValid);
253
+ return {
254
+ notFound: broken.filter((r) => r.error?.includes("not found")),
255
+ brokenAnchor: broken.filter((r) => r.error?.includes("Anchor")),
256
+ externalError: broken.filter((r) => r.link.isExternal && r.statusCode && r.statusCode >= 400),
257
+ timeout: broken.filter((r) => r.error?.includes("timeout") || r.error?.includes("aborted"))
258
+ };
259
+ }
260
+ function formatLinkLocation(link) {
261
+ return `${link.file}:${link.line}`;
262
+ }
263
+ function printResults(results, options = {}) {
264
+ const { quiet = false, verbose = false, json = false } = options;
265
+ if (json) {
266
+ console.log(JSON.stringify(results, null, 2));
267
+ return;
268
+ }
269
+ const stats = calculateStats(results);
270
+ const grouped = groupByErrorType(results);
271
+ if (quiet) {
272
+ const broken = results.filter((r) => !r.isValid);
273
+ broken.forEach((result) => {
274
+ console.error(`${formatLinkLocation(result.link)} - ${result.link.url} - ${result.error}`);
275
+ });
276
+ return;
277
+ }
278
+ console.log(chalk.bold("\n\u{1F50D} Link Validation Results\n"));
279
+ if (grouped.notFound.length > 0) {
280
+ console.log(chalk.red.bold(`
281
+ \u274C Files Not Found (${grouped.notFound.length}):
282
+ `));
283
+ grouped.notFound.forEach((result) => {
284
+ console.log(
285
+ ` ${chalk.gray(formatLinkLocation(result.link))} ${chalk.yellow(result.link.url)}`
286
+ );
287
+ console.log(` ${chalk.red(result.error)}`);
288
+ });
289
+ }
290
+ if (grouped.brokenAnchor.length > 0) {
291
+ console.log(chalk.red.bold(`
292
+ \u274C Broken Anchors (${grouped.brokenAnchor.length}):
293
+ `));
294
+ grouped.brokenAnchor.forEach((result) => {
295
+ console.log(
296
+ ` ${chalk.gray(formatLinkLocation(result.link))} ${chalk.yellow(result.link.url)}`
297
+ );
298
+ console.log(` ${chalk.red(result.error)}`);
299
+ });
300
+ }
301
+ if (grouped.externalError.length > 0) {
302
+ console.log(chalk.red.bold(`
303
+ \u274C External Link Errors (${grouped.externalError.length}):
304
+ `));
305
+ grouped.externalError.forEach((result) => {
306
+ console.log(
307
+ ` ${chalk.gray(formatLinkLocation(result.link))} ${chalk.yellow(result.link.url)}`
308
+ );
309
+ console.log(
310
+ ` ${chalk.red(`HTTP ${result.statusCode}`)} ${result.error ? `- ${result.error}` : ""}`
311
+ );
312
+ });
313
+ }
314
+ if (grouped.timeout.length > 0) {
315
+ console.log(chalk.yellow.bold(`
316
+ \u26A0\uFE0F Timeouts (${grouped.timeout.length}):
317
+ `));
318
+ grouped.timeout.forEach((result) => {
319
+ console.log(
320
+ ` ${chalk.gray(formatLinkLocation(result.link))} ${chalk.yellow(result.link.url)}`
321
+ );
322
+ console.log(` ${chalk.yellow(result.error)}`);
323
+ });
324
+ }
325
+ if (verbose) {
326
+ console.log(chalk.green.bold(`
327
+ \u2713 Valid Links (${stats.valid}):
328
+ `));
329
+ results.filter((r) => r.isValid).forEach((result) => {
330
+ console.log(
331
+ ` ${chalk.gray(formatLinkLocation(result.link))} ${chalk.green("\u2713")} ${result.link.url}`
332
+ );
333
+ });
334
+ }
335
+ console.log(chalk.bold("\n\u{1F4CA} Summary:\n"));
336
+ console.log(` Total links: ${chalk.cyan(stats.total.toString())}`);
337
+ console.log(` Valid: ${chalk.green(stats.valid.toString())}`);
338
+ console.log(
339
+ ` Broken: ${stats.broken > 0 ? chalk.red(stats.broken.toString()) : chalk.green("0")}`
340
+ );
341
+ console.log(` Internal: ${chalk.cyan(stats.internal.toString())}`);
342
+ console.log(` External: ${chalk.cyan(stats.external.toString())}`);
343
+ if (stats.broken === 0) {
344
+ console.log(chalk.green.bold("\n\u2728 All links are valid!\n"));
345
+ } else {
346
+ console.log(chalk.red.bold(`
347
+ \u{1F494} Found ${stats.broken} broken link(s)
348
+ `));
349
+ }
350
+ }
351
+
352
+ // src/config.ts
353
+ import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
354
+ import { resolve as resolve2 } from "path";
355
+ import { createLogger } from "@goobits/docs-engine/utils";
356
+ var logger = createLogger("cli-config");
357
+ var defaultConfig = {
358
+ baseDir: process.cwd(),
359
+ include: ["**/*.md", "**/*.mdx"],
360
+ exclude: ["**/node_modules/**", "**/dist/**", "**/.git/**"],
361
+ checkExternal: false,
362
+ timeout: 5e3,
363
+ concurrency: 10,
364
+ skipDomains: ["localhost", "127.0.0.1", "example.com"],
365
+ validExtensions: [".md", ".mdx"]
366
+ };
367
+ function loadConfig(cwd = process.cwd()) {
368
+ const configFiles = [
369
+ ".linkcheckerrc.json",
370
+ ".linkcheckerrc",
371
+ "linkchecker.config.json",
372
+ "linkchecker.config.js"
373
+ ];
374
+ for (const configFile of configFiles) {
375
+ const configPath = resolve2(cwd, configFile);
376
+ if (existsSync2(configPath)) {
377
+ try {
378
+ const content = readFileSync3(configPath, "utf-8");
379
+ return JSON.parse(content);
380
+ } catch (error) {
381
+ logger.error({ error, configPath }, "Error loading config file");
382
+ }
383
+ }
384
+ }
385
+ return void 0;
386
+ }
387
+ function mergeConfig(config = {}) {
388
+ return {
389
+ ...defaultConfig,
390
+ ...config,
391
+ // Deep merge arrays
392
+ include: config.include || defaultConfig.include,
393
+ exclude: config.exclude || defaultConfig.exclude,
394
+ skipDomains: [...defaultConfig.skipDomains, ...config.skipDomains || []],
395
+ validExtensions: [...defaultConfig.validExtensions, ...config.validExtensions || []]
396
+ };
397
+ }
398
+
399
+ // src/versioning.ts
400
+ import { promises as fs } from "fs";
401
+ import path from "path";
402
+ async function createVersion(version, docsDir) {
403
+ const versionedDocsDir = path.join(docsDir, "versioned_docs");
404
+ const currentDocsDir = path.join(docsDir, "current");
405
+ const versionDir = path.join(versionedDocsDir, `version-${version}`);
406
+ await fs.mkdir(versionedDocsDir, { recursive: true });
407
+ const sourceDir = await fs.access(currentDocsDir).then(() => currentDocsDir).catch(() => docsDir);
408
+ await copyDirectory(sourceDir, versionDir);
409
+ const versionsFile = path.join(docsDir, "versions.json");
410
+ let config;
411
+ try {
412
+ const content = await fs.readFile(versionsFile, "utf-8");
413
+ config = JSON.parse(content);
414
+ } catch {
415
+ config = {
416
+ currentVersion: version,
417
+ versions: []
418
+ };
419
+ }
420
+ config.versions.unshift({
421
+ version,
422
+ label: "latest",
423
+ releaseDate: (/* @__PURE__ */ new Date()).toISOString()
424
+ });
425
+ if (config.versions.length > 1) {
426
+ const previousLatest = config.versions[1];
427
+ if (previousLatest.label === "latest") {
428
+ previousLatest.label = "stable";
429
+ }
430
+ }
431
+ config.currentVersion = version;
432
+ await fs.writeFile(versionsFile, JSON.stringify(config, null, 2));
433
+ }
434
+ async function listVersions(docsDir) {
435
+ const versionsFile = path.join(docsDir, "versions.json");
436
+ try {
437
+ const content = await fs.readFile(versionsFile, "utf-8");
438
+ const config = JSON.parse(content);
439
+ return config.versions;
440
+ } catch {
441
+ return [];
442
+ }
443
+ }
444
+ async function deleteVersion(version, docsDir) {
445
+ const versionedDocsDir = path.join(docsDir, "versioned_docs");
446
+ const versionDir = path.join(versionedDocsDir, `version-${version}`);
447
+ await fs.rm(versionDir, { recursive: true, force: true });
448
+ const versionsFile = path.join(docsDir, "versions.json");
449
+ const content = await fs.readFile(versionsFile, "utf-8");
450
+ const config = JSON.parse(content);
451
+ config.versions = config.versions.filter((v) => v.version !== version);
452
+ if (config.currentVersion === version && config.versions.length > 0) {
453
+ config.currentVersion = config.versions[0].version;
454
+ }
455
+ await fs.writeFile(versionsFile, JSON.stringify(config, null, 2));
456
+ }
457
+ async function copyDirectory(src, dest) {
458
+ await fs.mkdir(dest, { recursive: true });
459
+ const entries = await fs.readdir(src, { withFileTypes: true });
460
+ for (const entry of entries) {
461
+ const srcPath = path.join(src, entry.name);
462
+ const destPath = path.join(dest, entry.name);
463
+ if (entry.isDirectory()) {
464
+ await copyDirectory(srcPath, destPath);
465
+ } else {
466
+ await fs.copyFile(srcPath, destPath);
467
+ }
468
+ }
469
+ }
470
+
471
+ // src/api-generator.ts
472
+ import { writeFileSync, mkdirSync, existsSync as existsSync3 } from "fs";
473
+ import path2 from "path";
474
+ import { parseApi, generateApiDocFile, generateIndexFile } from "@goobits/docs-engine/server";
475
+ async function generateApiDocs(config) {
476
+ const parsedFiles = parseApi({
477
+ entryPoints: config.entryPoints,
478
+ tsConfigPath: config.tsConfigPath,
479
+ exclude: config.exclude
480
+ });
481
+ if (parsedFiles.length === 0) {
482
+ throw new Error("No API items found in the specified entry points");
483
+ }
484
+ if (!existsSync3(config.outputDir)) {
485
+ mkdirSync(config.outputDir, { recursive: true });
486
+ }
487
+ const generatedFiles = [];
488
+ for (const parsedFile of parsedFiles) {
489
+ const { content, fileName } = generateApiDocFile(parsedFile, config.markdownConfig);
490
+ const outputPath = path2.join(config.outputDir, fileName);
491
+ writeFileSync(outputPath, content, "utf-8");
492
+ generatedFiles.push({
493
+ fileName,
494
+ items: parsedFile.items
495
+ });
496
+ }
497
+ if (config.generateIndex !== false) {
498
+ const indexContent = generateIndexFile(generatedFiles);
499
+ const indexPath = path2.join(config.outputDir, "index.md");
500
+ writeFileSync(indexPath, indexContent, "utf-8");
501
+ }
502
+ }
503
+
504
+ // src/symbol-watcher.ts
505
+ import { createSymbolMapGenerator } from "@goobits/docs-engine/server";
506
+ async function watchSymbols(config, options = {}) {
507
+ const { debounce = 500, onChange, verbose = false } = options;
508
+ if (verbose) {
509
+ console.log("\u{1F680} Starting TypeScript file watcher...");
510
+ console.log(` Source patterns: ${config.sourcePatterns.join(", ")}`);
511
+ console.log(` Output: ${config.outputPath}`);
512
+ }
513
+ const generator = createSymbolMapGenerator(config);
514
+ const watcher = await generator.watch({
515
+ debounce,
516
+ onChange: (stats) => {
517
+ if (onChange) {
518
+ onChange(stats);
519
+ } else if (verbose) {
520
+ console.log(
521
+ `\u2705 Symbol map updated (${(stats.duration / 1e3).toFixed(1)}s, ${stats.symbolCount} symbols)`
522
+ );
523
+ }
524
+ }
525
+ });
526
+ if (verbose) {
527
+ console.log("\n\u{1F440} Watching TypeScript files for changes...");
528
+ console.log(" Press Ctrl+C to stop\n");
529
+ }
530
+ return watcher;
531
+ }
532
+
533
+ // src/symbol-benchmarker.ts
534
+ import { createSymbolMapGenerator as createSymbolMapGenerator2 } from "@goobits/docs-engine/server";
535
+ async function benchmarkSymbols(config) {
536
+ const generator = createSymbolMapGenerator2(config);
537
+ return await generator.benchmark();
538
+ }
539
+ function printBenchmarkResults(results) {
540
+ console.log("\n" + "=".repeat(50));
541
+ console.log("\u{1F4C8} Benchmark Results:\n");
542
+ console.log(`Cold run: ${results.cold}ms (${(results.cold / 1e3).toFixed(2)}s)`);
543
+ console.log(`Warm run (no change): ${results.warm}ms (${(results.warm / 1e3).toFixed(2)}s)`);
544
+ console.log(
545
+ `Warm run (1 change): ${results.warmWithChange}ms (${(results.warmWithChange / 1e3).toFixed(2)}s)`
546
+ );
547
+ console.log(`
548
+ Improvement: ${results.improvement.toFixed(1)}% faster`);
549
+ console.log(
550
+ `Target achieved: ${results.warm < 1e3 ? "\u2705 Yes" : "\u274C No"} (target: <1000ms)`
551
+ );
552
+ console.log(`
553
+ \u{1F4BE} Cache file size: ${(results.cacheSize / 1024).toFixed(2)} KB`);
554
+ console.log(`\u{1F524} Symbol count: ${results.symbolCount}`);
555
+ console.log("=".repeat(50) + "\n");
556
+ }
557
+
558
+ // src/index.ts
559
+ function getVersion() {
560
+ try {
561
+ const __filename2 = fileURLToPath(import.meta.url);
562
+ const __dirname2 = dirname2(__filename2);
563
+ const packageJsonPath = join2(__dirname2, "../package.json");
564
+ const packageJson = JSON.parse(readFileSync4(packageJsonPath, "utf-8"));
565
+ return packageJson.version || "1.0.0";
566
+ } catch {
567
+ return "1.0.0";
568
+ }
569
+ }
570
+ var program = new Command();
571
+ program.name("docs-engine").description("CLI tools for docs-engine - link checking, versioning, and validation").version(getVersion());
572
+ program.command("check-links").description("Check all links in markdown documentation").option("-b, --base-dir <path>", "Base directory for docs (default: current directory)").option("-p, --pattern <glob>", "Glob pattern for markdown files (default: **/*.md)").option("-e, --external", "Validate external links (slower)").option("-t, --timeout <ms>", "Timeout for external requests in ms (default: 5000)", "5000").option("-c, --concurrency <num>", "Max concurrent external requests (default: 10)", "10").option("-q, --quiet", "Only show errors").option("-v, --verbose", "Show all links including valid ones").option("--json", "Output results as JSON").option("--config <path>", "Path to config file").action(async (options) => {
573
+ const spinner = ora("Initializing link checker...").start();
574
+ try {
575
+ let fileConfig;
576
+ if (options.config) {
577
+ try {
578
+ const { readFileSync: readFileSync5 } = await import("fs");
579
+ fileConfig = JSON.parse(readFileSync5(options.config, "utf-8"));
580
+ } catch {
581
+ fileConfig = void 0;
582
+ }
583
+ } else {
584
+ fileConfig = loadConfig();
585
+ }
586
+ const config = mergeConfig({
587
+ ...fileConfig,
588
+ baseDir: options.baseDir || fileConfig?.baseDir,
589
+ checkExternal: options.external || fileConfig?.checkExternal,
590
+ timeout: parseInt(options.timeout, 10),
591
+ concurrency: parseInt(options.concurrency, 10)
592
+ });
593
+ spinner.text = "Finding markdown files...";
594
+ const pattern = options.pattern || config.include.join(",");
595
+ const files = await glob(pattern, {
596
+ cwd: config.baseDir,
597
+ absolute: true,
598
+ ignore: config.exclude
599
+ });
600
+ if (files.length === 0) {
601
+ spinner.fail("No markdown files found");
602
+ process.exit(1);
603
+ }
604
+ spinner.succeed(`Found ${files.length} markdown file(s)`);
605
+ spinner.start("Extracting links...");
606
+ const links = extractLinksFromFiles(files);
607
+ spinner.succeed(`Extracted ${links.length} link(s)`);
608
+ spinner.start(
609
+ config.checkExternal ? "Validating links (including external)..." : "Validating internal links..."
610
+ );
611
+ const results = await validateLinks(links, {
612
+ baseDir: config.baseDir,
613
+ checkExternal: config.checkExternal,
614
+ timeout: config.timeout,
615
+ concurrency: config.concurrency,
616
+ skipDomains: config.skipDomains,
617
+ validExtensions: config.validExtensions
618
+ });
619
+ spinner.succeed("Validation complete");
620
+ printResults(results, {
621
+ quiet: options.quiet,
622
+ verbose: options.verbose,
623
+ json: options.json
624
+ });
625
+ const brokenCount = results.filter((r) => !r.isValid).length;
626
+ if (brokenCount > 0) {
627
+ process.exit(1);
628
+ }
629
+ } catch (error) {
630
+ spinner.fail("Link checking failed");
631
+ console.error(error);
632
+ process.exit(1);
633
+ }
634
+ });
635
+ var versionCmd = program.command("version").description("Manage documentation versions");
636
+ versionCmd.command("create <version>").description("Create new documentation version from current docs").option("-d, --docs-dir <path>", "Documentation directory", "docs").action(async (version, options) => {
637
+ const spinner = ora(`Creating version ${version}...`).start();
638
+ try {
639
+ const docsDir = path3.resolve(process.cwd(), options.docsDir);
640
+ await createVersion(version, docsDir);
641
+ spinner.succeed(`Version ${version} created successfully!`);
642
+ console.log(chalk2.cyan("\nNext steps:"));
643
+ console.log(
644
+ chalk2.gray(
645
+ ` - Version created in ${path3.join(docsDir, "versioned_docs", `version-${version}`)}`
646
+ )
647
+ );
648
+ console.log(chalk2.gray(` - Updated versions.json`));
649
+ console.log(chalk2.gray(` - Deploy your updated documentation`));
650
+ } catch (error) {
651
+ spinner.fail("Failed to create version");
652
+ console.error(chalk2.red(error instanceof Error ? error.message : "Unknown error"));
653
+ process.exit(1);
654
+ }
655
+ });
656
+ versionCmd.command("list").description("List all documentation versions").option("-d, --docs-dir <path>", "Documentation directory", "docs").action(async (options) => {
657
+ try {
658
+ const docsDir = path3.resolve(process.cwd(), options.docsDir);
659
+ const versions = await listVersions(docsDir);
660
+ if (versions.length === 0) {
661
+ console.log(chalk2.yellow("No versions found"));
662
+ return;
663
+ }
664
+ console.log(chalk2.bold("\nDocumentation Versions:"));
665
+ versions.forEach((version) => {
666
+ const label = version.label ? chalk2.cyan(` [${version.label}]`) : "";
667
+ console.log(` ${version.version}${label}`);
668
+ });
669
+ console.log();
670
+ } catch (error) {
671
+ console.error(chalk2.red(error instanceof Error ? error.message : "Unknown error"));
672
+ process.exit(1);
673
+ }
674
+ });
675
+ versionCmd.command("delete <version>").description("Delete a documentation version").option("-d, --docs-dir <path>", "Documentation directory", "docs").option("-f, --force", "Skip confirmation", false).action(async (version, options) => {
676
+ const spinner = ora(`Deleting version ${version}...`).start();
677
+ try {
678
+ const docsDir = path3.resolve(process.cwd(), options.docsDir);
679
+ await deleteVersion(version, docsDir);
680
+ spinner.succeed(`Version ${version} deleted successfully!`);
681
+ } catch (error) {
682
+ spinner.fail("Failed to delete version");
683
+ console.error(chalk2.red(error instanceof Error ? error.message : "Unknown error"));
684
+ process.exit(1);
685
+ }
686
+ });
687
+ program.command("generate-api").description("Generate API documentation from TypeScript source files").option("-e, --entry-points <paths...>", "TypeScript files or glob patterns to parse", [
688
+ "src/**/*.ts"
689
+ ]).option("-o, --output-dir <path>", "Output directory for generated markdown", "docs/api").option("-t, --tsconfig <path>", "Path to tsconfig.json").option("--exclude <patterns...>", "Patterns to exclude from parsing").option("--base-url <url>", "Base URL for type links (e.g., /api)").option("--repo-url <url>", "Repository URL for source links").option("--repo-branch <branch>", "Repository branch for source links", "main").option("--no-index", "Skip generating index file").option("--source-links", "Include links to source code").action(async (options) => {
690
+ const spinner = ora("Generating API documentation...").start();
691
+ try {
692
+ await generateApiDocs({
693
+ entryPoints: options.entryPoints,
694
+ outputDir: path3.resolve(process.cwd(), options.outputDir),
695
+ tsConfigPath: options.tsconfig ? path3.resolve(process.cwd(), options.tsconfig) : void 0,
696
+ exclude: options.exclude,
697
+ generateIndex: options.index,
698
+ markdownConfig: {
699
+ baseUrl: options.baseUrl,
700
+ repoUrl: options.repoUrl,
701
+ repoBranch: options.repoBranch,
702
+ includeSourceLinks: options.sourceLinks
703
+ }
704
+ });
705
+ const filesCount = options.entryPoints.length;
706
+ spinner.succeed(`API documentation generated successfully!`);
707
+ console.log(chalk2.cyan("\nGenerated files:"));
708
+ console.log(chalk2.gray(` Output directory: ${options.outputDir}`));
709
+ console.log(chalk2.gray(` Entry points: ${filesCount} pattern(s)`));
710
+ if (options.index) {
711
+ console.log(chalk2.gray(` Index file: ${path3.join(options.outputDir, "index.md")}`));
712
+ }
713
+ } catch (error) {
714
+ spinner.fail("API documentation generation failed");
715
+ console.error(chalk2.red(error instanceof Error ? error.message : "Unknown error"));
716
+ if (error instanceof Error && error.stack) {
717
+ console.error(chalk2.gray(error.stack));
718
+ }
719
+ process.exit(1);
720
+ }
721
+ });
722
+ program.command("symbols").description("Generate, watch, or benchmark symbol map from TypeScript sources").option("-s, --source <patterns...>", "Source patterns to scan", ["src/**/*.ts"]).option("-o, --output <path>", "Output path for symbol map", "docs/symbol-map.json").option("-e, --exclude <patterns...>", "Patterns to exclude").option("--cache-dir <path>", "Cache directory", ".cache").option("--cache-version <version>", "Cache version", "1.0").option("-w, --watch", "Watch files and regenerate on changes").option("-b, --benchmark", "Run performance benchmark").option("--debounce <ms>", "Debounce delay for watch mode in ms", "500").option("-v, --verbose", "Show verbose output").action(async (options) => {
723
+ try {
724
+ const config = {
725
+ sourcePatterns: options.source,
726
+ excludePatterns: options.exclude || [
727
+ "**/*.test.ts",
728
+ "**/*.spec.ts",
729
+ "**/node_modules/**",
730
+ "**/dist/**"
731
+ ],
732
+ outputPath: path3.resolve(process.cwd(), options.output),
733
+ cacheDir: options.cacheDir,
734
+ cacheVersion: options.cacheVersion,
735
+ baseDir: process.cwd()
736
+ };
737
+ if (options.benchmark) {
738
+ const spinner2 = ora("Running benchmark...").start();
739
+ spinner2.text = "\u{1F52C} Symbol Map Generation Benchmark";
740
+ spinner2.stop();
741
+ console.log("=".repeat(50));
742
+ console.log("Running performance tests...\n");
743
+ const results = await benchmarkSymbols(config);
744
+ printBenchmarkResults(results);
745
+ return;
746
+ }
747
+ if (options.watch) {
748
+ console.log("\u{1F680} Starting TypeScript file watcher...");
749
+ console.log(` Source: ${options.source.join(", ")}`);
750
+ console.log(` Output: ${options.output}
751
+ `);
752
+ const watcher = await watchSymbols(config, {
753
+ debounce: parseInt(options.debounce, 10),
754
+ verbose: options.verbose,
755
+ onChange: (stats) => {
756
+ console.log(
757
+ `\u2705 Symbol map updated (${(stats.duration / 1e3).toFixed(1)}s, ${stats.symbolCount} symbols)`
758
+ );
759
+ console.log("\n\u{1F440} Watching TypeScript files for changes...");
760
+ }
761
+ });
762
+ console.log("\u{1F440} Watching TypeScript files for changes...");
763
+ console.log(" Press Ctrl+C to stop\n");
764
+ const shutdown = async () => {
765
+ console.log("\n\n\u{1F44B} Stopping file watcher...");
766
+ await watcher.close();
767
+ console.log("\u2705 File watcher stopped");
768
+ process.exit(0);
769
+ };
770
+ process.on("SIGINT", shutdown);
771
+ process.on("SIGTERM", shutdown);
772
+ return;
773
+ }
774
+ const spinner = ora("Generating symbol map...").start();
775
+ const startTime = Date.now();
776
+ const { createSymbolMapGenerator: createSymbolMapGenerator3 } = await import("@goobits/docs-engine/server");
777
+ const generator = createSymbolMapGenerator3(config);
778
+ const symbolMap = await generator.generate();
779
+ const duration = Date.now() - startTime;
780
+ const symbolCount = Object.values(symbolMap).flat().length;
781
+ spinner.succeed("Symbol map generated successfully!");
782
+ console.log(chalk2.cyan("\nGenerated:"));
783
+ console.log(chalk2.gray(` Output: ${options.output}`));
784
+ console.log(chalk2.gray(` Symbols: ${symbolCount}`));
785
+ console.log(chalk2.gray(` Duration: ${(duration / 1e3).toFixed(2)}s`));
786
+ } catch (error) {
787
+ console.error(chalk2.red("Symbol generation failed"));
788
+ console.error(chalk2.red(error instanceof Error ? error.message : "Unknown error"));
789
+ if (error instanceof Error && error.stack && options.verbose) {
790
+ console.error(chalk2.gray(error.stack));
791
+ }
792
+ process.exit(1);
793
+ }
794
+ });
795
+ program.parse();
796
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/link-extractor.ts","../src/link-validator.ts","../src/reporter.ts","../src/config.ts","../src/versioning.ts","../src/api-generator.ts","../src/symbol-watcher.ts","../src/symbol-benchmarker.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { Command } from 'commander';\nimport { glob } from 'glob';\nimport ora from 'ora';\nimport chalk from 'chalk';\nimport path from 'path';\nimport { readFileSync } from 'fs';\nimport { fileURLToPath } from 'url';\nimport { dirname, join } from 'path';\nimport { extractLinksFromFiles } from './link-extractor.js';\nimport { validateLinks } from './link-validator.js';\nimport { printResults } from './reporter.js';\nimport { loadConfig, mergeConfig } from './config.js';\nimport type { LinkCheckerConfig } from './config.js';\nimport { createVersion, listVersions, deleteVersion } from './versioning.js';\nimport { generateApiDocs } from './api-generator.js';\nimport { watchSymbols } from './symbol-watcher.js';\nimport { benchmarkSymbols, printBenchmarkResults } from './symbol-benchmarker.js';\n\n/**\n * Get the CLI version from package.json\n */\nfunction getVersion(): string {\n try {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n const packageJsonPath = join(__dirname, '../package.json');\n const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\n return packageJson.version || '1.0.0';\n } catch {\n return '1.0.0';\n }\n}\n\n/**\n * Main CLI program\n *\n * Provides commands for:\n * - Link checking and validation\n * - Documentation versioning\n * - Symbol map generation and watching\n * - Documentation maintenance\n *\n * @public\n */\nconst program = new Command();\n\nprogram\n .name('docs-engine')\n .description('CLI tools for docs-engine - link checking, versioning, and validation')\n .version(getVersion());\n\n/**\n * Check links command\n *\n * Usage:\n * ```bash\n * docs-engine check-links\n * docs-engine check-links --external\n * docs-engine check-links --quiet\n * docs-engine check-links --json > results.json\n * ```\n */\nprogram\n .command('check-links')\n .description('Check all links in markdown documentation')\n .option('-b, --base-dir <path>', 'Base directory for docs (default: current directory)')\n .option('-p, --pattern <glob>', 'Glob pattern for markdown files (default: **/*.md)')\n .option('-e, --external', 'Validate external links (slower)')\n .option('-t, --timeout <ms>', 'Timeout for external requests in ms (default: 5000)', '5000')\n .option('-c, --concurrency <num>', 'Max concurrent external requests (default: 10)', '10')\n .option('-q, --quiet', 'Only show errors')\n .option('-v, --verbose', 'Show all links including valid ones')\n .option('--json', 'Output results as JSON')\n .option('--config <path>', 'Path to config file')\n .action(async (options) => {\n const spinner = ora('Initializing link checker...').start();\n\n try {\n // Load configuration\n let fileConfig: LinkCheckerConfig | undefined;\n if (options.config) {\n try {\n const { readFileSync } = await import('fs');\n fileConfig = JSON.parse(readFileSync(options.config, 'utf-8')) as LinkCheckerConfig;\n } catch {\n fileConfig = undefined;\n }\n } else {\n fileConfig = loadConfig();\n }\n\n const config = mergeConfig({\n ...fileConfig,\n baseDir: options.baseDir || fileConfig?.baseDir,\n checkExternal: options.external || fileConfig?.checkExternal,\n timeout: parseInt(options.timeout, 10),\n concurrency: parseInt(options.concurrency, 10),\n });\n\n spinner.text = 'Finding markdown files...';\n\n // Find all markdown files\n const pattern = options.pattern || config.include.join(',');\n const files = await glob(pattern, {\n cwd: config.baseDir,\n absolute: true,\n ignore: config.exclude,\n });\n\n if (files.length === 0) {\n spinner.fail('No markdown files found');\n process.exit(1);\n }\n\n spinner.succeed(`Found ${files.length} markdown file(s)`);\n\n // Extract links\n spinner.start('Extracting links...');\n const links = extractLinksFromFiles(files);\n spinner.succeed(`Extracted ${links.length} link(s)`);\n\n // Validate links\n spinner.start(\n config.checkExternal\n ? 'Validating links (including external)...'\n : 'Validating internal links...'\n );\n\n const results = await validateLinks(links, {\n baseDir: config.baseDir,\n checkExternal: config.checkExternal,\n timeout: config.timeout,\n concurrency: config.concurrency,\n skipDomains: config.skipDomains,\n validExtensions: config.validExtensions,\n });\n\n spinner.succeed('Validation complete');\n\n // Print results\n printResults(results, {\n quiet: options.quiet,\n verbose: options.verbose,\n json: options.json,\n });\n\n // Exit with error code if broken links found\n const brokenCount = results.filter((r) => !r.isValid).length;\n if (brokenCount > 0) {\n process.exit(1);\n }\n } catch (error) {\n spinner.fail('Link checking failed');\n console.error(error);\n process.exit(1);\n }\n });\n\n/**\n * Version management commands\n */\nconst versionCmd = program.command('version').description('Manage documentation versions');\n\nversionCmd\n .command('create <version>')\n .description('Create new documentation version from current docs')\n .option('-d, --docs-dir <path>', 'Documentation directory', 'docs')\n .action(async (version: string, options) => {\n const spinner = ora(`Creating version ${version}...`).start();\n\n try {\n const docsDir = path.resolve(process.cwd(), options.docsDir);\n await createVersion(version, docsDir);\n spinner.succeed(`Version ${version} created successfully!`);\n\n console.log(chalk.cyan('\\nNext steps:'));\n console.log(\n chalk.gray(\n ` - Version created in ${path.join(docsDir, 'versioned_docs', `version-${version}`)}`\n )\n );\n console.log(chalk.gray(` - Updated versions.json`));\n console.log(chalk.gray(` - Deploy your updated documentation`));\n } catch (error) {\n spinner.fail('Failed to create version');\n console.error(chalk.red(error instanceof Error ? error.message : 'Unknown error'));\n process.exit(1);\n }\n });\n\nversionCmd\n .command('list')\n .description('List all documentation versions')\n .option('-d, --docs-dir <path>', 'Documentation directory', 'docs')\n .action(async (options) => {\n try {\n const docsDir = path.resolve(process.cwd(), options.docsDir);\n const versions = await listVersions(docsDir);\n\n if (versions.length === 0) {\n console.log(chalk.yellow('No versions found'));\n return;\n }\n\n console.log(chalk.bold('\\nDocumentation Versions:'));\n versions.forEach((version) => {\n const label = version.label ? chalk.cyan(` [${version.label}]`) : '';\n console.log(` ${version.version}${label}`);\n });\n console.log();\n } catch (error) {\n console.error(chalk.red(error instanceof Error ? error.message : 'Unknown error'));\n process.exit(1);\n }\n });\n\nversionCmd\n .command('delete <version>')\n .description('Delete a documentation version')\n .option('-d, --docs-dir <path>', 'Documentation directory', 'docs')\n .option('-f, --force', 'Skip confirmation', false)\n .action(async (version: string, options) => {\n const spinner = ora(`Deleting version ${version}...`).start();\n\n try {\n const docsDir = path.resolve(process.cwd(), options.docsDir);\n await deleteVersion(version, docsDir);\n spinner.succeed(`Version ${version} deleted successfully!`);\n } catch (error) {\n spinner.fail('Failed to delete version');\n console.error(chalk.red(error instanceof Error ? error.message : 'Unknown error'));\n process.exit(1);\n }\n });\n\n/**\n * API documentation generation command\n */\nprogram\n .command('generate-api')\n .description('Generate API documentation from TypeScript source files')\n .option('-e, --entry-points <paths...>', 'TypeScript files or glob patterns to parse', [\n 'src/**/*.ts',\n ])\n .option('-o, --output-dir <path>', 'Output directory for generated markdown', 'docs/api')\n .option('-t, --tsconfig <path>', 'Path to tsconfig.json')\n .option('--exclude <patterns...>', 'Patterns to exclude from parsing')\n .option('--base-url <url>', 'Base URL for type links (e.g., /api)')\n .option('--repo-url <url>', 'Repository URL for source links')\n .option('--repo-branch <branch>', 'Repository branch for source links', 'main')\n .option('--no-index', 'Skip generating index file')\n .option('--source-links', 'Include links to source code')\n .action(async (options) => {\n const spinner = ora('Generating API documentation...').start();\n\n try {\n await generateApiDocs({\n entryPoints: options.entryPoints,\n outputDir: path.resolve(process.cwd(), options.outputDir),\n tsConfigPath: options.tsconfig ? path.resolve(process.cwd(), options.tsconfig) : undefined,\n exclude: options.exclude,\n generateIndex: options.index,\n markdownConfig: {\n baseUrl: options.baseUrl,\n repoUrl: options.repoUrl,\n repoBranch: options.repoBranch,\n includeSourceLinks: options.sourceLinks,\n },\n });\n\n const filesCount = options.entryPoints.length;\n spinner.succeed(`API documentation generated successfully!`);\n\n console.log(chalk.cyan('\\nGenerated files:'));\n console.log(chalk.gray(` Output directory: ${options.outputDir}`));\n console.log(chalk.gray(` Entry points: ${filesCount} pattern(s)`));\n\n if (options.index) {\n console.log(chalk.gray(` Index file: ${path.join(options.outputDir, 'index.md')}`));\n }\n } catch (error) {\n spinner.fail('API documentation generation failed');\n console.error(chalk.red(error instanceof Error ? error.message : 'Unknown error'));\n if (error instanceof Error && error.stack) {\n console.error(chalk.gray(error.stack));\n }\n process.exit(1);\n }\n });\n\n/**\n * Symbol map generation command\n */\nprogram\n .command('symbols')\n .description('Generate, watch, or benchmark symbol map from TypeScript sources')\n .option('-s, --source <patterns...>', 'Source patterns to scan', ['src/**/*.ts'])\n .option('-o, --output <path>', 'Output path for symbol map', 'docs/symbol-map.json')\n .option('-e, --exclude <patterns...>', 'Patterns to exclude')\n .option('--cache-dir <path>', 'Cache directory', '.cache')\n .option('--cache-version <version>', 'Cache version', '1.0')\n .option('-w, --watch', 'Watch files and regenerate on changes')\n .option('-b, --benchmark', 'Run performance benchmark')\n .option('--debounce <ms>', 'Debounce delay for watch mode in ms', '500')\n .option('-v, --verbose', 'Show verbose output')\n .action(async (options) => {\n try {\n const config = {\n sourcePatterns: options.source,\n excludePatterns: options.exclude || [\n '**/*.test.ts',\n '**/*.spec.ts',\n '**/node_modules/**',\n '**/dist/**',\n ],\n outputPath: path.resolve(process.cwd(), options.output),\n cacheDir: options.cacheDir,\n cacheVersion: options.cacheVersion,\n baseDir: process.cwd(),\n };\n\n // Benchmark mode\n if (options.benchmark) {\n const spinner = ora('Running benchmark...').start();\n spinner.text = 'šŸ”¬ Symbol Map Generation Benchmark';\n spinner.stop();\n\n console.log('='.repeat(50));\n console.log('Running performance tests...\\n');\n\n const results = await benchmarkSymbols(config);\n printBenchmarkResults(results);\n return;\n }\n\n // Watch mode\n if (options.watch) {\n console.log('šŸš€ Starting TypeScript file watcher...');\n console.log(` Source: ${options.source.join(', ')}`);\n console.log(` Output: ${options.output}\\n`);\n\n const watcher = await watchSymbols(config, {\n debounce: parseInt(options.debounce, 10),\n verbose: options.verbose,\n onChange: (stats) => {\n console.log(\n `āœ… Symbol map updated (${(stats.duration / 1000).toFixed(1)}s, ${stats.symbolCount} symbols)`\n );\n console.log('\\nšŸ‘€ Watching TypeScript files for changes...');\n },\n });\n\n console.log('šŸ‘€ Watching TypeScript files for changes...');\n console.log(' Press Ctrl+C to stop\\n');\n\n // Handle graceful shutdown\n const shutdown = async (): Promise<void> => {\n console.log('\\n\\nšŸ‘‹ Stopping file watcher...');\n await watcher.close();\n console.log('āœ… File watcher stopped');\n process.exit(0);\n };\n\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n return;\n }\n\n // Normal generation mode\n const spinner = ora('Generating symbol map...').start();\n const startTime = Date.now();\n const { createSymbolMapGenerator } = await import('@goobits/docs-engine/server');\n\n const generator = createSymbolMapGenerator(config);\n const symbolMap = await generator.generate();\n const duration = Date.now() - startTime;\n const symbolCount = Object.values(symbolMap).flat().length;\n\n spinner.succeed('Symbol map generated successfully!');\n console.log(chalk.cyan('\\nGenerated:'));\n console.log(chalk.gray(` Output: ${options.output}`));\n console.log(chalk.gray(` Symbols: ${symbolCount}`));\n console.log(chalk.gray(` Duration: ${(duration / 1000).toFixed(2)}s`));\n } catch (error) {\n console.error(chalk.red('Symbol generation failed'));\n console.error(chalk.red(error instanceof Error ? error.message : 'Unknown error'));\n if (error instanceof Error && error.stack && options.verbose) {\n console.error(chalk.gray(error.stack));\n }\n process.exit(1);\n }\n });\n\n// Parse CLI arguments\nprogram.parse();\n","import { unified } from 'unified';\nimport remarkParse from 'remark-parse';\nimport remarkMdx from 'remark-mdx';\nimport { visit } from 'unist-util-visit';\nimport type { Link, Image } from 'mdast';\nimport { readFileSync } from 'fs';\n\n/**\n * Represents a link found in a markdown file\n *\n * @public\n */\nexport interface ExtractedLink {\n /** Link URL or path */\n url: string;\n /** Link text or alt text */\n text: string;\n /** Source file path */\n file: string;\n /** Line number in source file */\n line: number;\n /** Link type */\n type: 'link' | 'image' | 'html';\n /** Whether this is an external URL */\n isExternal: boolean;\n /** Whether this is an anchor link */\n isAnchor: boolean;\n}\n\n// ============================================================================\n// Module-Private Helpers (True Privacy via ESM)\n// ============================================================================\n\n/**\n * Check if a URL is external (http/https)\n * Module-private helper - not exported, not accessible outside this module\n */\nfunction isExternalUrl(url: string): boolean {\n return /^https?:\\/\\//i.test(url);\n}\n\n/**\n * Check if a URL is an anchor-only link\n * Module-private helper - not exported, not accessible outside this module\n */\nfunction isAnchorOnly(url: string): boolean {\n return url.startsWith('#');\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * Extract all links from a markdown file\n *\n * Extracts:\n * - Markdown links: `[text](url)`\n * - Images: `![alt](url)`\n * - HTML links: `<a href=\"url\">`\n *\n * @param filePath - Path to markdown file\n * @returns Array of extracted links\n *\n * @example\n * ```typescript\n * const links = extractLinksFromFile('./docs/guide.md');\n * links.forEach(link => {\n * console.log(`${link.file}:${link.line} - ${link.url}`);\n * });\n * ```\n *\n * @public\n */\nexport function extractLinksFromFile(filePath: string): ExtractedLink[] {\n const content = readFileSync(filePath, 'utf-8');\n const links: ExtractedLink[] = [];\n\n // Parse markdown with MDX support\n const tree = unified().use(remarkParse).use(remarkMdx).parse(content);\n\n // Extract markdown links and images\n visit(tree, ['link', 'image'], (node: Link | Image, _index, _parent) => {\n const url = node.url;\n if (!url) return;\n\n const position = node.position;\n const line = position?.start.line || 0;\n\n // Get link text\n let text = '';\n if (node.type === 'link') {\n const linkNode = node as Link;\n if (linkNode.children && linkNode.children.length > 0) {\n const firstChild = linkNode.children[0];\n if ('value' in firstChild) {\n text = firstChild.value as string;\n }\n }\n } else if (node.type === 'image') {\n const imageNode = node as Image;\n text = imageNode.alt || '';\n }\n\n links.push({\n url,\n text,\n file: filePath,\n line,\n type: node.type === 'image' ? 'image' : 'link',\n isExternal: isExternalUrl(url),\n isAnchor: isAnchorOnly(url),\n });\n });\n\n // Extract HTML links (basic regex for <a href=\"\">)\n const htmlLinkRegex = /<a\\s+(?:[^>]*?\\s+)?href=[\"']([^\"']+)[\"']/gi;\n const lines = content.split('\\n');\n\n lines.forEach((lineContent, index) => {\n let match;\n while ((match = htmlLinkRegex.exec(lineContent)) !== null) {\n const url = match[1];\n links.push({\n url,\n text: '', // Would need HTML parsing to extract text\n file: filePath,\n line: index + 1,\n type: 'html',\n isExternal: isExternalUrl(url),\n isAnchor: isAnchorOnly(url),\n });\n }\n });\n\n return links;\n}\n\n/**\n * Extract links from multiple markdown files\n *\n * @param filePaths - Array of file paths\n * @returns Array of all extracted links\n *\n * @example\n * ```typescript\n * const files = ['./docs/guide.md', './docs/api.md'];\n * const allLinks = extractLinksFromFiles(files);\n * console.log(`Found ${allLinks.length} links across ${files.length} files`);\n * ```\n *\n * @public\n */\nexport function extractLinksFromFiles(filePaths: string[]): ExtractedLink[] {\n const allLinks: ExtractedLink[] = [];\n\n for (const file of filePaths) {\n try {\n const links = extractLinksFromFile(file);\n allLinks.push(...links);\n } catch (error) {\n console.error(`Error extracting links from ${file}:`, error);\n }\n }\n\n return allLinks;\n}\n\n/**\n * Group links by type (internal, external, anchor)\n *\n * @param links - Array of links to group\n * @returns Grouped links object\n *\n * @example\n * ```typescript\n * const grouped = groupLinksByType(links);\n * console.log(`Internal: ${grouped.internal.length}`);\n * console.log(`External: ${grouped.external.length}`);\n * console.log(`Anchors: ${grouped.anchor.length}`);\n * ```\n *\n * @public\n */\nexport function groupLinksByType(links: ExtractedLink[]): {\n internal: ExtractedLink[];\n external: ExtractedLink[];\n anchor: ExtractedLink[];\n} {\n return {\n internal: links.filter((l) => !l.isExternal && !l.isAnchor),\n external: links.filter((l) => l.isExternal),\n anchor: links.filter((l) => l.isAnchor),\n };\n}\n","import { existsSync, readFileSync, statSync } from 'fs';\nimport { resolve, dirname, join } from 'path';\nimport type { ExtractedLink } from './link-extractor.js';\nimport pLimit from 'p-limit';\n\n/**\n * Link validation result\n *\n * @public\n */\nexport interface ValidationResult {\n /** The link that was validated */\n link: ExtractedLink;\n /** Whether the link is valid */\n isValid: boolean;\n /** Error message if invalid */\n error?: string;\n /** HTTP status code (for external links) */\n statusCode?: number;\n /** Redirect URL (if redirected) */\n redirectUrl?: string;\n}\n\n/**\n * Configuration options for link validation\n *\n * @public\n */\nexport interface ValidationOptions {\n /** Base directory for resolving relative paths */\n baseDir: string;\n /** Validate external links (requires HTTP requests) */\n checkExternal?: boolean;\n /** Timeout for external link requests (ms) */\n timeout?: number;\n /** Maximum concurrent external requests */\n concurrency?: number;\n /** Domains to skip validation */\n skipDomains?: string[];\n /** File extensions to treat as valid */\n validExtensions?: string[];\n}\n\n// ============================================================================\n// Module-Private Helpers (True Privacy via ESM)\n// ============================================================================\n\n/**\n * Resolve a relative link to an absolute file path\n * Module-private helper - not exported, not accessible outside this module\n *\n * Handles:\n * - Relative paths: `./file.md`, `../file.md`\n * - Absolute paths: `/docs/file.md`\n * - Removes `.md` extension for route matching\n * - Supports anchor links: `file.md#section`\n */\nfunction resolveLinkPath(link: string, sourceFile: string, baseDir: string): string {\n // Remove anchor\n const [pathPart] = link.split('#');\n\n // Handle absolute paths\n if (pathPart.startsWith('/')) {\n return resolve(baseDir, pathPart.slice(1));\n }\n\n // Handle relative paths\n const sourceDir = dirname(sourceFile);\n return resolve(sourceDir, pathPart);\n}\n\n/**\n * Extract anchor from URL\n * Module-private helper - not exported, not accessible outside this module\n */\nfunction extractAnchor(url: string): string | undefined {\n const parts = url.split('#');\n return parts.length > 1 ? parts[1] : undefined;\n}\n\n/**\n * Check if an anchor exists in a markdown file\n * Module-private helper - not exported, not accessible outside this module\n *\n * Checks for:\n * - Headers: `## Section Name` → `#section-name`\n * - HTML anchors: `<a name=\"anchor\">` or `<a id=\"anchor\">`\n */\nfunction anchorExistsInFile(filePath: string, anchor: string): boolean {\n try {\n const content = readFileSync(filePath, 'utf-8');\n\n // Convert anchor to expected format (lowercase, spaces to hyphens)\n const normalizedAnchor = anchor.toLowerCase().replace(/\\s+/g, '-');\n\n // Check for markdown headers\n // Match: ## Header, ### Header, etc.\n const headerRegex = /^#+\\s+(.+)$/gm;\n let match;\n\n while ((match = headerRegex.exec(content)) !== null) {\n const headerText = match[1];\n const headerId = headerText\n .toLowerCase()\n .replace(/[^\\w\\s-]/g, '') // Remove special chars\n .replace(/\\s+/g, '-'); // Spaces to hyphens\n\n if (headerId === normalizedAnchor) {\n return true;\n }\n }\n\n // Check for HTML anchors\n const htmlAnchorRegex = /<a\\s+(?:name|id)=[\"']([^\"']+)[\"']/gi;\n while ((match = htmlAnchorRegex.exec(content)) !== null) {\n if (match[1] === anchor) {\n return true;\n }\n }\n\n return false;\n } catch {\n return false;\n }\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * Validate an internal link\n *\n * Checks:\n * - File exists\n * - Anchor exists (if present)\n * - Handles .md extension conversion\n *\n * @param link - Link to validate\n * @param options - Validation options\n * @returns Validation result\n *\n * @example\n * ```typescript\n * const result = validateInternalLink(link, { baseDir: '/project' });\n * if (!result.isValid) {\n * console.error(`Broken link: ${result.error}`);\n * }\n * ```\n *\n * @public\n */\nexport function validateInternalLink(\n link: ExtractedLink,\n options: ValidationOptions\n): ValidationResult {\n const { baseDir, validExtensions = ['.md', '.mdx'] } = options;\n\n try {\n // Resolve the link path\n let targetPath = resolveLinkPath(link.url, link.file, baseDir);\n\n // Check if file exists as-is\n let fileExists = existsSync(targetPath);\n\n // If not, try adding valid extensions\n if (!fileExists) {\n for (const ext of validExtensions) {\n const pathWithExt = targetPath + ext;\n if (existsSync(pathWithExt)) {\n targetPath = pathWithExt;\n fileExists = true;\n break;\n }\n }\n }\n\n // Check if it's a directory with index file\n if (!fileExists && existsSync(targetPath) && statSync(targetPath).isDirectory()) {\n for (const ext of validExtensions) {\n const indexPath = join(targetPath, `index${ext}`);\n if (existsSync(indexPath)) {\n targetPath = indexPath;\n fileExists = true;\n break;\n }\n }\n }\n\n if (!fileExists) {\n return {\n link,\n isValid: false,\n error: `File not found: ${targetPath}`,\n };\n }\n\n // Check anchor if present\n const anchor = extractAnchor(link.url);\n if (anchor) {\n const anchorExists = anchorExistsInFile(targetPath, anchor);\n if (!anchorExists) {\n return {\n link,\n isValid: false,\n error: `Anchor #${anchor} not found in ${targetPath}`,\n };\n }\n }\n\n return {\n link,\n isValid: true,\n };\n } catch (error) {\n return {\n link,\n isValid: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n };\n }\n}\n\n/**\n * Validate an external link with HTTP request\n *\n * @param link - Link to validate\n * @param options - Validation options\n * @returns Validation result\n *\n * @example\n * ```typescript\n * const result = await validateExternalLink(link, { timeout: 5000 });\n * console.log(`Status: ${result.statusCode}`);\n * ```\n *\n * @public\n */\nexport async function validateExternalLink(\n link: ExtractedLink,\n options: ValidationOptions\n): Promise<ValidationResult> {\n const { timeout = 5000, skipDomains = [] } = options;\n\n try {\n // Check if domain should be skipped\n const url = new URL(link.url);\n if (skipDomains.some((domain) => url.hostname.includes(domain))) {\n return {\n link,\n isValid: true,\n statusCode: 0, // Skipped\n };\n }\n\n // Make HEAD request with timeout\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n const response = await fetch(link.url, {\n method: 'HEAD',\n signal: controller.signal,\n redirect: 'follow',\n });\n\n clearTimeout(timeoutId);\n\n const isValid = response.ok; // 200-299\n\n return {\n link,\n isValid,\n statusCode: response.status,\n redirectUrl: response.redirected ? response.url : undefined,\n error: isValid ? undefined : `HTTP ${response.status} ${response.statusText}`,\n };\n } catch (error) {\n return {\n link,\n isValid: false,\n statusCode: 0,\n error: error instanceof Error ? error.message : 'Request failed',\n };\n }\n}\n\n/**\n * Validate multiple links with concurrency control\n *\n * @param links - Links to validate\n * @param options - Validation options\n * @returns Array of validation results\n *\n * @example\n * ```typescript\n * const results = await validateLinks(allLinks, {\n * baseDir: '/project',\n * checkExternal: true,\n * concurrency: 10\n * });\n *\n * const broken = results.filter(r => !r.isValid);\n * console.log(`Found ${broken.length} broken links`);\n * ```\n *\n * @public\n */\nexport async function validateLinks(\n links: ExtractedLink[],\n options: ValidationOptions\n): Promise<ValidationResult[]> {\n const { concurrency = 10, checkExternal = false } = options;\n const results: ValidationResult[] = [];\n\n // Separate internal and external links\n const internalLinks = links.filter((l) => !l.isExternal);\n const externalLinks = links.filter((l) => l.isExternal);\n\n // Validate internal links (synchronous)\n for (const link of internalLinks) {\n results.push(validateInternalLink(link, options));\n }\n\n // Validate external links (asynchronous with concurrency)\n if (checkExternal && externalLinks.length > 0) {\n const limit = pLimit(concurrency);\n const externalResults = await Promise.all(\n externalLinks.map((link) => limit(() => validateExternalLink(link, options)))\n );\n results.push(...externalResults);\n }\n\n return results;\n}\n","import chalk from 'chalk';\nimport type { ValidationResult } from './link-validator.js';\nimport type { ExtractedLink } from './link-extractor.js';\n\n/**\n * Report statistics\n *\n * @public\n */\nexport interface ReportStats {\n /** Total links checked */\n total: number;\n /** Valid links */\n valid: number;\n /** Broken links */\n broken: number;\n /** External links checked */\n external: number;\n /** Internal links checked */\n internal: number;\n}\n\n/**\n * Calculate statistics from validation results\n *\n * @param results - Validation results\n * @returns Report statistics\n *\n * @internal\n */\nexport function calculateStats(results: ValidationResult[]): ReportStats {\n return {\n total: results.length,\n valid: results.filter((r) => r.isValid).length,\n broken: results.filter((r) => !r.isValid).length,\n external: results.filter((r) => r.link.isExternal).length,\n internal: results.filter((r) => !r.link.isExternal).length,\n };\n}\n\n/**\n * Group validation results by error type\n *\n * @param results - Validation results\n * @returns Grouped results\n *\n * @internal\n */\nexport function groupByErrorType(results: ValidationResult[]): {\n notFound: ValidationResult[];\n brokenAnchor: ValidationResult[];\n externalError: ValidationResult[];\n timeout: ValidationResult[];\n} {\n const broken = results.filter((r) => !r.isValid);\n\n return {\n notFound: broken.filter((r) => r.error?.includes('not found')),\n brokenAnchor: broken.filter((r) => r.error?.includes('Anchor')),\n externalError: broken.filter((r) => r.link.isExternal && r.statusCode && r.statusCode >= 400),\n timeout: broken.filter((r) => r.error?.includes('timeout') || r.error?.includes('aborted')),\n };\n}\n\n/**\n * Format a link location for display\n *\n * @param link - Link to format\n * @returns Formatted string\n *\n * @example\n * ```typescript\n * formatLinkLocation(link);\n * // Returns: \"docs/guide.md:42\"\n * ```\n *\n * @internal\n */\nexport function formatLinkLocation(link: ExtractedLink): string {\n return `${link.file}:${link.line}`;\n}\n\n/**\n * Print validation results to console\n *\n * @param results - Validation results\n * @param options - Output options\n *\n * @example\n * ```typescript\n * printResults(results, { quiet: false, verbose: true });\n * ```\n *\n * @public\n */\nexport function printResults(\n results: ValidationResult[],\n options: {\n quiet?: boolean;\n verbose?: boolean;\n json?: boolean;\n } = {}\n): void {\n const { quiet = false, verbose = false, json = false } = options;\n\n // JSON output\n if (json) {\n console.log(JSON.stringify(results, null, 2));\n return;\n }\n\n const stats = calculateStats(results);\n const grouped = groupByErrorType(results);\n\n // Quiet mode - only show errors\n if (quiet) {\n const broken = results.filter((r) => !r.isValid);\n broken.forEach((result) => {\n console.error(`${formatLinkLocation(result.link)} - ${result.link.url} - ${result.error}`);\n });\n return;\n }\n\n // Print header\n console.log(chalk.bold('\\nšŸ” Link Validation Results\\n'));\n\n // Print broken links by category\n if (grouped.notFound.length > 0) {\n console.log(chalk.red.bold(`\\nāŒ Files Not Found (${grouped.notFound.length}):\\n`));\n grouped.notFound.forEach((result) => {\n console.log(\n ` ${chalk.gray(formatLinkLocation(result.link))} ${chalk.yellow(result.link.url)}`\n );\n console.log(` ${chalk.red(result.error)}`);\n });\n }\n\n if (grouped.brokenAnchor.length > 0) {\n console.log(chalk.red.bold(`\\nāŒ Broken Anchors (${grouped.brokenAnchor.length}):\\n`));\n grouped.brokenAnchor.forEach((result) => {\n console.log(\n ` ${chalk.gray(formatLinkLocation(result.link))} ${chalk.yellow(result.link.url)}`\n );\n console.log(` ${chalk.red(result.error)}`);\n });\n }\n\n if (grouped.externalError.length > 0) {\n console.log(chalk.red.bold(`\\nāŒ External Link Errors (${grouped.externalError.length}):\\n`));\n grouped.externalError.forEach((result) => {\n console.log(\n ` ${chalk.gray(formatLinkLocation(result.link))} ${chalk.yellow(result.link.url)}`\n );\n console.log(\n ` ${chalk.red(`HTTP ${result.statusCode}`)} ${result.error ? `- ${result.error}` : ''}`\n );\n });\n }\n\n if (grouped.timeout.length > 0) {\n console.log(chalk.yellow.bold(`\\nāš ļø Timeouts (${grouped.timeout.length}):\\n`));\n grouped.timeout.forEach((result) => {\n console.log(\n ` ${chalk.gray(formatLinkLocation(result.link))} ${chalk.yellow(result.link.url)}`\n );\n console.log(` ${chalk.yellow(result.error)}`);\n });\n }\n\n // Print verbose output (all links)\n if (verbose) {\n console.log(chalk.green.bold(`\\nāœ“ Valid Links (${stats.valid}):\\n`));\n results\n .filter((r) => r.isValid)\n .forEach((result) => {\n console.log(\n ` ${chalk.gray(formatLinkLocation(result.link))} ${chalk.green('āœ“')} ${result.link.url}`\n );\n });\n }\n\n // Print summary\n console.log(chalk.bold('\\nšŸ“Š Summary:\\n'));\n console.log(` Total links: ${chalk.cyan(stats.total.toString())}`);\n console.log(` Valid: ${chalk.green(stats.valid.toString())}`);\n console.log(\n ` Broken: ${stats.broken > 0 ? chalk.red(stats.broken.toString()) : chalk.green('0')}`\n );\n console.log(` Internal: ${chalk.cyan(stats.internal.toString())}`);\n console.log(` External: ${chalk.cyan(stats.external.toString())}`);\n\n // Success or failure message\n if (stats.broken === 0) {\n console.log(chalk.green.bold('\\n✨ All links are valid!\\n'));\n } else {\n console.log(chalk.red.bold(`\\nšŸ’” Found ${stats.broken} broken link(s)\\n`));\n }\n}\n\n/**\n * Print progress update\n *\n * @param message - Progress message\n * @param current - Current item number\n * @param total - Total items\n *\n * @public\n */\nexport function printProgress(message: string, current: number, total: number): void {\n const percentage = Math.round((current / total) * 100);\n const bar = 'ā–ˆ'.repeat(Math.floor(percentage / 5));\n const empty = 'ā–‘'.repeat(20 - bar.length);\n\n process.stdout.write(`\\r${message} [${bar}${empty}] ${percentage}% (${current}/${total})`);\n\n if (current === total) {\n process.stdout.write('\\n');\n }\n}\n","import { existsSync, readFileSync } from 'fs';\nimport { resolve } from 'path';\nimport { createLogger } from '@goobits/docs-engine/utils';\n\nconst logger = createLogger('cli-config');\n\n/**\n * Link checker configuration\n *\n * @public\n */\nexport interface LinkCheckerConfig {\n /** Base directory for resolving links */\n baseDir?: string;\n /** Glob patterns to include */\n include?: string[];\n /** Glob patterns to exclude */\n exclude?: string[];\n /** Validate external links */\n checkExternal?: boolean;\n /** External link timeout (ms) */\n timeout?: number;\n /** Maximum concurrent requests */\n concurrency?: number;\n /** Domains to skip validation */\n skipDomains?: string[];\n /** Valid file extensions */\n validExtensions?: string[];\n}\n\n/**\n * Default configuration\n *\n * @public\n */\nexport const defaultConfig: Required<LinkCheckerConfig> = {\n baseDir: process.cwd(),\n include: ['**/*.md', '**/*.mdx'],\n exclude: ['**/node_modules/**', '**/dist/**', '**/.git/**'],\n checkExternal: false,\n timeout: 5000,\n concurrency: 10,\n skipDomains: ['localhost', '127.0.0.1', 'example.com'],\n validExtensions: ['.md', '.mdx'],\n};\n\n/**\n * Load configuration from file\n *\n * Supports:\n * - `.linkcheckerrc.json`\n * - `.linkcheckerrc`\n * - `linkchecker.config.json`\n *\n * @param cwd - Current working directory\n * @returns Loaded configuration or undefined\n *\n * @example\n * ```typescript\n * const config = loadConfig('/project');\n * if (config) {\n * console.log('Using config:', config);\n * }\n * ```\n *\n * @public\n */\nexport function loadConfig(cwd: string = process.cwd()): LinkCheckerConfig | undefined {\n const configFiles = [\n '.linkcheckerrc.json',\n '.linkcheckerrc',\n 'linkchecker.config.json',\n 'linkchecker.config.js',\n ];\n\n for (const configFile of configFiles) {\n const configPath = resolve(cwd, configFile);\n\n if (existsSync(configPath)) {\n try {\n const content = readFileSync(configPath, 'utf-8');\n return JSON.parse(content) as LinkCheckerConfig;\n } catch (error) {\n logger.error({ error, configPath }, 'Error loading config file');\n }\n }\n }\n\n return undefined;\n}\n\n/**\n * Merge configuration with defaults\n *\n * @param config - User configuration\n * @returns Merged configuration\n *\n * @example\n * ```typescript\n * const config = mergeConfig({ checkExternal: true });\n * // Returns: { ...defaults, checkExternal: true }\n * ```\n *\n * @public\n */\nexport function mergeConfig(config: LinkCheckerConfig = {}): Required<LinkCheckerConfig> {\n return {\n ...defaultConfig,\n ...config,\n // Deep merge arrays\n include: config.include || defaultConfig.include,\n exclude: config.exclude || defaultConfig.exclude,\n skipDomains: [...defaultConfig.skipDomains, ...(config.skipDomains || [])],\n validExtensions: [...defaultConfig.validExtensions, ...(config.validExtensions || [])],\n };\n}\n","/**\n * Documentation Versioning Utilities\n *\n * Manages multiple versions of documentation with version-specific\n * navigation, search, and routing.\n */\n\nimport { promises as fs } from 'fs';\nimport path from 'path';\n\n/**\n * Version metadata\n */\nexport interface VersionMetadata {\n version: string;\n label?: 'latest' | 'stable' | 'legacy' | 'deprecated';\n releaseDate?: string;\n deprecationDate?: string;\n message?: string;\n}\n\n/**\n * Versions configuration file\n */\nexport interface VersionsConfig {\n currentVersion: string;\n versions: VersionMetadata[];\n}\n\n/**\n * Create a new documentation version\n *\n * Copies current docs to versioned_docs/version-X and updates versions.json\n *\n * @public\n */\nexport async function createVersion(version: string, docsDir: string): Promise<void> {\n const versionedDocsDir = path.join(docsDir, 'versioned_docs');\n const currentDocsDir = path.join(docsDir, 'current');\n const versionDir = path.join(versionedDocsDir, `version-${version}`);\n\n // Ensure versioned_docs directory exists\n await fs.mkdir(versionedDocsDir, { recursive: true });\n\n // Copy current docs to versioned directory\n const sourceDir = await fs\n .access(currentDocsDir)\n .then(() => currentDocsDir)\n .catch(() => docsDir);\n await copyDirectory(sourceDir, versionDir);\n\n // Update versions.json\n const versionsFile = path.join(docsDir, 'versions.json');\n let config: VersionsConfig;\n\n try {\n const content = await fs.readFile(versionsFile, 'utf-8');\n config = JSON.parse(content);\n } catch {\n // Create new versions config\n config = {\n currentVersion: version,\n versions: [],\n };\n }\n\n // Add new version\n config.versions.unshift({\n version,\n label: 'latest',\n releaseDate: new Date().toISOString(),\n });\n\n // Update previous latest\n if (config.versions.length > 1) {\n const previousLatest = config.versions[1];\n if (previousLatest.label === 'latest') {\n previousLatest.label = 'stable';\n }\n }\n\n config.currentVersion = version;\n\n // Save versions.json\n await fs.writeFile(versionsFile, JSON.stringify(config, null, 2));\n}\n\n/**\n * List all documentation versions\n *\n * @public\n */\nexport async function listVersions(docsDir: string): Promise<VersionMetadata[]> {\n const versionsFile = path.join(docsDir, 'versions.json');\n\n try {\n const content = await fs.readFile(versionsFile, 'utf-8');\n const config: VersionsConfig = JSON.parse(content);\n return config.versions;\n } catch {\n return [];\n }\n}\n\n/**\n * Delete a documentation version\n *\n * @public\n */\nexport async function deleteVersion(version: string, docsDir: string): Promise<void> {\n const versionedDocsDir = path.join(docsDir, 'versioned_docs');\n const versionDir = path.join(versionedDocsDir, `version-${version}`);\n\n // Remove version directory\n await fs.rm(versionDir, { recursive: true, force: true });\n\n // Update versions.json\n const versionsFile = path.join(docsDir, 'versions.json');\n const content = await fs.readFile(versionsFile, 'utf-8');\n const config: VersionsConfig = JSON.parse(content);\n\n config.versions = config.versions.filter((v) => v.version !== version);\n\n // Update current version if deleted\n if (config.currentVersion === version && config.versions.length > 0) {\n config.currentVersion = config.versions[0].version;\n }\n\n await fs.writeFile(versionsFile, JSON.stringify(config, null, 2));\n}\n\n/**\n * Copy directory recursively\n */\nasync function copyDirectory(src: string, dest: string): Promise<void> {\n await fs.mkdir(dest, { recursive: true });\n\n const entries = await fs.readdir(src, { withFileTypes: true });\n\n for (const entry of entries) {\n const srcPath = path.join(src, entry.name);\n const destPath = path.join(dest, entry.name);\n\n if (entry.isDirectory()) {\n await copyDirectory(srcPath, destPath);\n } else {\n await fs.copyFile(srcPath, destPath);\n }\n }\n}\n\n/**\n * Get version configuration\n *\n * @public\n */\nexport async function getVersionConfig(docsDir: string): Promise<VersionsConfig | null> {\n const versionsFile = path.join(docsDir, 'versions.json');\n\n try {\n const content = await fs.readFile(versionsFile, 'utf-8');\n return JSON.parse(content);\n } catch {\n return null;\n }\n}\n","/**\n * API documentation generator CLI\n */\n\nimport { writeFileSync, mkdirSync, existsSync } from 'fs';\nimport path from 'path';\nimport { parseApi, generateApiDocFile, generateIndexFile } from '@goobits/docs-engine/server';\nimport type { MarkdownGeneratorConfig } from '@goobits/docs-engine/server';\n\n/**\n * Configuration for API documentation generation\n */\nexport interface ApiGeneratorConfig {\n entryPoints: string[];\n outputDir: string;\n tsConfigPath?: string;\n exclude?: string[];\n markdownConfig?: MarkdownGeneratorConfig;\n generateIndex?: boolean;\n}\n\n/**\n * Generate API documentation from TypeScript files\n */\nexport async function generateApiDocs(config: ApiGeneratorConfig): Promise<void> {\n // Parse TypeScript files\n const parsedFiles = parseApi({\n entryPoints: config.entryPoints,\n tsConfigPath: config.tsConfigPath,\n exclude: config.exclude,\n });\n\n if (parsedFiles.length === 0) {\n throw new Error('No API items found in the specified entry points');\n }\n\n // Ensure output directory exists\n if (!existsSync(config.outputDir)) {\n mkdirSync(config.outputDir, { recursive: true });\n }\n\n const generatedFiles: Array<{ fileName: string; items: unknown[] }> = [];\n\n // Generate markdown for each file\n for (const parsedFile of parsedFiles) {\n const { content, fileName } = generateApiDocFile(parsedFile, config.markdownConfig);\n\n const outputPath = path.join(config.outputDir, fileName);\n writeFileSync(outputPath, content, 'utf-8');\n\n generatedFiles.push({\n fileName,\n items: parsedFile.items,\n });\n }\n\n // Generate index file\n if (config.generateIndex !== false) {\n const indexContent = generateIndexFile(generatedFiles);\n const indexPath = path.join(config.outputDir, 'index.md');\n writeFileSync(indexPath, indexContent, 'utf-8');\n }\n}\n","/**\n * Symbol Map Watcher\n *\n * Watches TypeScript files and automatically regenerates symbol map\n * when changes are detected.\n *\n * @module\n */\n\nimport { createSymbolMapGenerator } from '@goobits/docs-engine/server';\nimport type { SymbolMapGeneratorConfig } from '@goobits/docs-engine/server';\n\nexport interface WatchOptions {\n /** Debounce delay in milliseconds (default: 500) */\n debounce?: number;\n /** Callback when symbol map is updated */\n onChange?: (stats: { duration: number; symbolCount: number }) => void;\n /** Show verbose output */\n verbose?: boolean;\n}\n\n/**\n * Watch TypeScript files and regenerate symbol map on changes\n *\n * @param config - Symbol map generator configuration\n * @param options - Watch options\n * @returns Promise that resolves with watcher instance\n *\n * @example\n * ```typescript\n * const watcher = await watchSymbols({\n * sourcePatterns: ['src/**\\/*.ts'],\n * outputPath: 'docs/symbol-map.json'\n * }, {\n * debounce: 500,\n * onChange: (stats) => console.log(`Updated: ${stats.symbolCount} symbols`)\n * });\n *\n * // Stop watching\n * await watcher.close();\n * ```\n *\n * @public\n */\nexport async function watchSymbols(\n config: SymbolMapGeneratorConfig,\n options: WatchOptions = {}\n): Promise<{ close: () => Promise<void> }> {\n const { debounce = 500, onChange, verbose = false } = options;\n\n if (verbose) {\n console.log('šŸš€ Starting TypeScript file watcher...');\n console.log(` Source patterns: ${config.sourcePatterns.join(', ')}`);\n console.log(` Output: ${config.outputPath}`);\n }\n\n const generator = createSymbolMapGenerator(config);\n\n // Start watching\n const watcher = await generator.watch({\n debounce,\n onChange: (stats) => {\n if (onChange) {\n onChange(stats);\n } else if (verbose) {\n console.log(\n `āœ… Symbol map updated (${(stats.duration / 1000).toFixed(1)}s, ${stats.symbolCount} symbols)`\n );\n }\n },\n });\n\n if (verbose) {\n console.log('\\nšŸ‘€ Watching TypeScript files for changes...');\n console.log(' Press Ctrl+C to stop\\n');\n }\n\n return watcher;\n}\n","/**\n * Symbol Map Benchmarker\n *\n * Benchmarks symbol map generation performance with different cache scenarios.\n *\n * @module\n */\n\nimport { createSymbolMapGenerator } from '@goobits/docs-engine/server';\nimport type { SymbolMapGeneratorConfig } from '@goobits/docs-engine/server';\n\nexport interface BenchmarkResults {\n /** Cold run time (no cache) in milliseconds */\n cold: number;\n /** Warm run time (with cache, no changes) in milliseconds */\n warm: number;\n /** Warm run time (with cache, 1 file changed) in milliseconds */\n warmWithChange: number;\n /** Performance improvement percentage */\n improvement: number;\n /** Cache file size in bytes */\n cacheSize: number;\n /** Number of symbols generated */\n symbolCount: number;\n}\n\n/**\n * Run benchmark for symbol map generation\n *\n * Tests performance in three scenarios:\n * 1. Cold run (no cache)\n * 2. Warm run (with cache, no changes)\n * 3. Warm run (with cache, 1 file changed)\n *\n * @param config - Symbol map generator configuration\n * @returns Promise that resolves with benchmark results\n *\n * @example\n * ```typescript\n * const results = await benchmarkSymbols({\n * sourcePatterns: ['src/**\\/*.ts'],\n * outputPath: 'docs/symbol-map.json'\n * });\n *\n * console.log(`Cold: ${results.cold}ms`);\n * console.log(`Warm: ${results.warm}ms`);\n * console.log(`Improvement: ${results.improvement}%`);\n * ```\n *\n * @public\n */\nexport async function benchmarkSymbols(\n config: SymbolMapGeneratorConfig\n): Promise<BenchmarkResults> {\n const generator = createSymbolMapGenerator(config);\n return await generator.benchmark();\n}\n\n/**\n * Format and print benchmark results\n *\n * @param results - Benchmark results\n *\n * @public\n */\nexport function printBenchmarkResults(results: BenchmarkResults): void {\n console.log('\\n' + '='.repeat(50));\n console.log('šŸ“ˆ Benchmark Results:\\n');\n console.log(`Cold run: ${results.cold}ms (${(results.cold / 1000).toFixed(2)}s)`);\n console.log(`Warm run (no change): ${results.warm}ms (${(results.warm / 1000).toFixed(2)}s)`);\n console.log(\n `Warm run (1 change): ${results.warmWithChange}ms (${(results.warmWithChange / 1000).toFixed(2)}s)`\n );\n console.log(`\\nImprovement: ${results.improvement.toFixed(1)}% faster`);\n console.log(\n `Target achieved: ${results.warm < 1000 ? 'āœ… Yes' : 'āŒ No'} (target: <1000ms)`\n );\n console.log(`\\nšŸ’¾ Cache file size: ${(results.cacheSize / 1024).toFixed(2)} KB`);\n console.log(`šŸ”¤ Symbol count: ${results.symbolCount}`);\n console.log('='.repeat(50) + '\\n');\n}\n"],"mappings":"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","names":["chalk","path","readFileSync","dirname","join","readFileSync","existsSync","readFileSync","resolve","existsSync","path","createSymbolMapGenerator","__filename","__dirname","dirname","join","readFileSync","path","chalk","spinner","createSymbolMapGenerator"]}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@goobits/docs-engine-cli",
3
+ "version": "2.0.0",
4
+ "description": "CLI tools for docs-engine - link checking, validation, versioning, and maintenance",
5
+ "type": "module",
6
+ "bin": {
7
+ "docs-engine": "./dist/index.js"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "scripts": {
15
+ "build": "tsup",
16
+ "dev": "tsup --watch",
17
+ "prepublishOnly": "pnpm run build"
18
+ },
19
+ "dependencies": {
20
+ "@goobits/docs-engine": "workspace:*",
21
+ "commander": "^14.0.2",
22
+ "chalk": "^5.6.2",
23
+ "ora": "^9.1.0",
24
+ "glob": "^13.0.0",
25
+ "unified": "^11.0.5",
26
+ "remark-parse": "^11.0.0",
27
+ "remark-mdx": "^3.1.1",
28
+ "unist-util-visit": "^5.1.0",
29
+ "p-limit": "^7.2.0"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^25.0.10",
33
+ "tsup": "^8.5.1",
34
+ "typescript": "^5.9.3"
35
+ },
36
+ "keywords": [
37
+ "documentation",
38
+ "link-checker",
39
+ "versioning",
40
+ "markdown",
41
+ "cli",
42
+ "validation"
43
+ ],
44
+ "license": "MIT",
45
+ "author": "GooBits"
46
+ }