mdx-linklist 0.1.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.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +211 -0
  3. package/dist/cache/url-cache.d.ts +13 -0
  4. package/dist/cache/url-cache.d.ts.map +1 -0
  5. package/dist/cache/url-cache.js +48 -0
  6. package/dist/cache/url-cache.js.map +1 -0
  7. package/dist/cli.d.ts +3 -0
  8. package/dist/cli.d.ts.map +1 -0
  9. package/dist/cli.js +160 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/config.d.ts +4 -0
  12. package/dist/config.d.ts.map +1 -0
  13. package/dist/config.js +42 -0
  14. package/dist/config.js.map +1 -0
  15. package/dist/index.d.ts +3 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +5 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/parsers/heading-parser.d.ts +8 -0
  20. package/dist/parsers/heading-parser.d.ts.map +1 -0
  21. package/dist/parsers/heading-parser.js +63 -0
  22. package/dist/parsers/heading-parser.js.map +1 -0
  23. package/dist/parsers/index.d.ts +2 -0
  24. package/dist/parsers/index.d.ts.map +1 -0
  25. package/dist/parsers/index.js +2 -0
  26. package/dist/parsers/index.js.map +1 -0
  27. package/dist/reporters/console.d.ts +3 -0
  28. package/dist/reporters/console.d.ts.map +1 -0
  29. package/dist/reporters/console.js +86 -0
  30. package/dist/reporters/console.js.map +1 -0
  31. package/dist/reporters/index.d.ts +4 -0
  32. package/dist/reporters/index.d.ts.map +1 -0
  33. package/dist/reporters/index.js +4 -0
  34. package/dist/reporters/index.js.map +1 -0
  35. package/dist/reporters/json.d.ts +3 -0
  36. package/dist/reporters/json.d.ts.map +1 -0
  37. package/dist/reporters/json.js +31 -0
  38. package/dist/reporters/json.js.map +1 -0
  39. package/dist/reporters/markdown.d.ts +3 -0
  40. package/dist/reporters/markdown.d.ts.map +1 -0
  41. package/dist/reporters/markdown.js +77 -0
  42. package/dist/reporters/markdown.js.map +1 -0
  43. package/dist/scanner/file-scanner.d.ts +3 -0
  44. package/dist/scanner/file-scanner.d.ts.map +1 -0
  45. package/dist/scanner/file-scanner.js +20 -0
  46. package/dist/scanner/file-scanner.js.map +1 -0
  47. package/dist/scanner/index.d.ts +3 -0
  48. package/dist/scanner/index.d.ts.map +1 -0
  49. package/dist/scanner/index.js +3 -0
  50. package/dist/scanner/index.js.map +1 -0
  51. package/dist/scanner/link-extractor.d.ts +3 -0
  52. package/dist/scanner/link-extractor.d.ts.map +1 -0
  53. package/dist/scanner/link-extractor.js +101 -0
  54. package/dist/scanner/link-extractor.js.map +1 -0
  55. package/dist/types.d.ts +46 -0
  56. package/dist/types.d.ts.map +1 -0
  57. package/dist/types.js +14 -0
  58. package/dist/types.js.map +1 -0
  59. package/dist/utils.d.ts +8 -0
  60. package/dist/utils.d.ts.map +1 -0
  61. package/dist/utils.js +115 -0
  62. package/dist/utils.js.map +1 -0
  63. package/dist/validators/external.d.ts +4 -0
  64. package/dist/validators/external.d.ts.map +1 -0
  65. package/dist/validators/external.js +111 -0
  66. package/dist/validators/external.js.map +1 -0
  67. package/dist/validators/index.d.ts +3 -0
  68. package/dist/validators/index.d.ts.map +1 -0
  69. package/dist/validators/index.js +3 -0
  70. package/dist/validators/index.js.map +1 -0
  71. package/dist/validators/internal.d.ts +3 -0
  72. package/dist/validators/internal.d.ts.map +1 -0
  73. package/dist/validators/internal.js +55 -0
  74. package/dist/validators/internal.js.map +1 -0
  75. package/package.json +55 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Aman Mittal (https://amanhimself.dev)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,211 @@
1
+ # mdx-linklist
2
+
3
+ A CLI tool to extract and validate links in MDX files. Check for broken internal links and external URLs in your documentation.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g mdx-linklist
9
+ ```
10
+
11
+ Or run directly with npx:
12
+
13
+ ```bash
14
+ npx mdx-linklist check ./docs
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ### Basic Check
20
+
21
+ ```bash
22
+ mdx-linklist check ./docs
23
+ ```
24
+
25
+ ### Options
26
+
27
+ ```bash
28
+ mdx-linklist check <directory> [options]
29
+
30
+ Options:
31
+ -c, --config <path> Path to config file
32
+ -i, --internal-only Only check internal links
33
+ -e, --external-only Only check external links
34
+ --ignore <pattern> Ignore URL pattern (can be repeated)
35
+ --ignore-domain <domain> Ignore domain (can be repeated)
36
+ --route-prefix <prefix> Route prefix for absolute paths (can be repeated)
37
+ --component <name> Custom JSX component with href prop (can be repeated)
38
+ -t, --timeout <ms> External request timeout (default: 10000)
39
+ --concurrency <n> Parallel requests (default: 10)
40
+ -f, --format <type> Output format: console|json|markdown
41
+ -o, --output <file> Write report to file
42
+ --no-progress Hide progress bar
43
+ --no-fail Do not exit with code 1 on broken links
44
+ -v, --verbose Show all links, not just broken
45
+ ```
46
+
47
+ ### Examples
48
+
49
+ ```bash
50
+ # Check only internal links (faster, no network)
51
+ mdx-linklist check ./docs --internal-only
52
+
53
+ # Output as JSON
54
+ mdx-linklist check ./docs --format json --output report.json
55
+
56
+ # Ignore localhost and specific domains
57
+ mdx-linklist check ./docs --ignore "localhost:*" --ignore-domain "twitter.com"
58
+
59
+ # Check with custom timeout
60
+ mdx-linklist check ./docs --timeout 5000
61
+
62
+ # Framework docs with route prefixes (e.g., Expo, Next.js, Docusaurus)
63
+ mdx-linklist check ./docs \
64
+ --internal-only \
65
+ --route-prefix pages \
66
+ --component Link \
67
+ --component APIBox
68
+ ```
69
+
70
+ ### Route Prefixes
71
+
72
+ Many documentation frameworks use route-style paths that map to files in a subdirectory:
73
+
74
+ ```markdown
75
+ <!-- Link in MDX -->
76
+ [Getting Started](/guides/intro)
77
+
78
+ <!-- Actual file location -->
79
+ pages/guides/intro.mdx
80
+ ```
81
+
82
+ Use `--route-prefix` to tell mdx-linklist where to find files for absolute paths:
83
+
84
+ ```bash
85
+ mdx-linklist check ./docs --route-prefix pages
86
+ ```
87
+
88
+ ## Configuration
89
+
90
+ You can use CLI flags (recommended) or create a `mdx-linklist.config.json` file:
91
+
92
+ ```json
93
+ {
94
+ "include": ["./docs/**/*.mdx", "./docs/**/*.md"],
95
+ "exclude": ["./docs/archive/**"],
96
+ "ignorePatterns": ["localhost:*", "127.0.0.1:*"],
97
+ "ignoreDomains": ["twitter.com", "x.com"],
98
+ "timeout": 10000,
99
+ "retries": 2,
100
+ "concurrency": 10,
101
+ "routePrefixes": ["pages"],
102
+ "customComponents": ["Link", "A", "CustomLink"]
103
+ }
104
+ ```
105
+
106
+ ### Config Options
107
+
108
+ | Option | Type | Default | Description |
109
+ |--------|------|---------|-------------|
110
+ | `include` | `string[]` | `["./**/*.mdx", "./**/*.md"]` | Glob patterns for files to scan |
111
+ | `exclude` | `string[]` | `["**/node_modules/**", "**/dist/**", "**/.git/**"]` | Glob patterns to exclude |
112
+ | `ignorePatterns` | `string[]` | `["localhost:*", "127.0.0.1:*", "*.local"]` | URL patterns to skip |
113
+ | `ignoreDomains` | `string[]` | `[]` | Domains to skip |
114
+ | `timeout` | `number` | `10000` | External request timeout (ms) |
115
+ | `retries` | `number` | `2` | Retry count for failed requests |
116
+ | `concurrency` | `number` | `10` | Parallel external requests |
117
+ | `routePrefixes` | `string[]` | `[]` | Directory prefixes for absolute paths |
118
+ | `customComponents` | `string[]` | `["Link", "A"]` | JSX components with href props |
119
+
120
+ ## What It Checks
121
+
122
+ ### Internal Links
123
+ - Relative paths (`./page.mdx`, `../other/page.mdx`)
124
+ - Absolute paths (`/docs/guide`)
125
+ - Combined with anchors (`./page.mdx#section`) - validates file exists
126
+
127
+ ### External Links
128
+ - HTTP/HTTPS URLs
129
+ - Follows redirects
130
+ - Reports status codes
131
+
132
+ ### JSX Components
133
+ - `<Link href="...">`
134
+ - `<A href="...">`
135
+ - Custom components (configurable via `--component` flag)
136
+
137
+ ## Output Formats
138
+
139
+ ### Console (default)
140
+
141
+ ```
142
+ BROKEN INTERNAL LINKS (2)
143
+
144
+ docs/guide.mdx:45:12
145
+ │ [Missing Page](/does-not-exist)
146
+ └─ File not found
147
+ Suggestions: /docs/guide, /docs/getting-started
148
+
149
+ BROKEN EXTERNAL LINKS (1)
150
+
151
+ docs/resources.mdx:23:5
152
+ │ https://example.com/old-page
153
+ └─ 404 Not Found
154
+
155
+ ──────────────────────────────────────────
156
+ SUMMARY
157
+ ──────────────────────────────────────────
158
+ Files scanned 50
159
+ Total links 234
160
+ Broken 3
161
+ Duration 12.4s
162
+ ──────────────────────────────────────────
163
+ ```
164
+
165
+ ### JSON
166
+
167
+ ```bash
168
+ mdx-linklist check ./docs --format json
169
+ ```
170
+
171
+ ### Markdown
172
+
173
+ ```bash
174
+ mdx-linklist check ./docs --format markdown --output report.md
175
+ ```
176
+
177
+ ## CI Integration
178
+
179
+ The CLI exits with code 1 when broken links are found:
180
+
181
+ ```yaml
182
+ # GitHub Actions
183
+ - name: Check links
184
+ run: npx mdx-linklist check ./docs --internal-only
185
+ ```
186
+
187
+ Use `--no-fail` to always exit with code 0:
188
+
189
+ ```bash
190
+ mdx-linklist check ./docs --no-fail
191
+ ```
192
+
193
+ ## Development
194
+
195
+ ```bash
196
+ # Install dependencies
197
+ bun install
198
+
199
+ # Run in development
200
+ bun run dev check ./tests/fixtures/valid-docs
201
+
202
+ # Build
203
+ bun run build
204
+
205
+ # Run tests
206
+ bun test
207
+ ```
208
+
209
+ ## License
210
+
211
+ MIT
@@ -0,0 +1,13 @@
1
+ export declare class Cache<T> {
2
+ private store;
3
+ private ttl;
4
+ constructor(ttlMs?: number);
5
+ get(key: string): T | undefined;
6
+ set(key: string, value: T): void;
7
+ has(key: string): boolean;
8
+ delete(key: string): boolean;
9
+ clear(): void;
10
+ size(): number;
11
+ prune(): number;
12
+ }
13
+ //# sourceMappingURL=url-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url-cache.d.ts","sourceRoot":"","sources":["../../src/cache/url-cache.ts"],"names":[],"mappings":"AAKA,qBAAa,KAAK,CAAC,CAAC;IAClB,OAAO,CAAC,KAAK,CAAoC;IACjD,OAAO,CAAC,GAAG,CAAS;gBAER,KAAK,GAAE,MAAsB;IAIzC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IAe/B,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI;IAOhC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAIzB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAI5B,KAAK,IAAI,IAAI;IAIb,IAAI,IAAI,MAAM;IAId,KAAK,IAAI,MAAM;CAahB"}
@@ -0,0 +1,48 @@
1
+ export class Cache {
2
+ store = new Map();
3
+ ttl;
4
+ constructor(ttlMs = 5 * 60 * 1000) {
5
+ this.ttl = ttlMs;
6
+ }
7
+ get(key) {
8
+ const entry = this.store.get(key);
9
+ if (!entry) {
10
+ return undefined;
11
+ }
12
+ if (Date.now() - entry.timestamp > this.ttl) {
13
+ this.store.delete(key);
14
+ return undefined;
15
+ }
16
+ return entry.value;
17
+ }
18
+ set(key, value) {
19
+ this.store.set(key, {
20
+ value,
21
+ timestamp: Date.now(),
22
+ });
23
+ }
24
+ has(key) {
25
+ return this.get(key) !== undefined;
26
+ }
27
+ delete(key) {
28
+ return this.store.delete(key);
29
+ }
30
+ clear() {
31
+ this.store.clear();
32
+ }
33
+ size() {
34
+ return this.store.size;
35
+ }
36
+ prune() {
37
+ const now = Date.now();
38
+ let pruned = 0;
39
+ for (const [key, entry] of this.store) {
40
+ if (now - entry.timestamp > this.ttl) {
41
+ this.store.delete(key);
42
+ pruned++;
43
+ }
44
+ }
45
+ return pruned;
46
+ }
47
+ }
48
+ //# sourceMappingURL=url-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url-cache.js","sourceRoot":"","sources":["../../src/cache/url-cache.ts"],"names":[],"mappings":"AAKA,MAAM,OAAO,KAAK;IACR,KAAK,GAAG,IAAI,GAAG,EAAyB,CAAC;IACzC,GAAG,CAAS;IAEpB,YAAY,QAAgB,CAAC,GAAG,EAAE,GAAG,IAAI;QACvC,IAAI,CAAC,GAAG,GAAG,KAAK,CAAC;IACnB,CAAC;IAED,GAAG,CAAC,GAAW;QACb,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAElC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC5C,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACvB,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,OAAO,KAAK,CAAC,KAAK,CAAC;IACrB,CAAC;IAED,GAAG,CAAC,GAAW,EAAE,KAAQ;QACvB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE;YAClB,KAAK;YACL,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;SACtB,CAAC,CAAC;IACL,CAAC;IAED,GAAG,CAAC,GAAW;QACb,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC;IACrC,CAAC;IAED,MAAM,CAAC,GAAW;QAChB,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAChC,CAAC;IAED,KAAK;QACH,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;IAED,IAAI;QACF,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;IACzB,CAAC;IAED,KAAK;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,MAAM,GAAG,CAAC,CAAC;QAEf,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACtC,IAAI,GAAG,GAAG,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;gBACrC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBACvB,MAAM,EAAE,CAAC;YACX,CAAC;QACH,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;CACF"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function createCli(): Command;
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAiBpC,wBAAgB,SAAS,IAAI,OAAO,CA+BnC"}
package/dist/cli.js ADDED
@@ -0,0 +1,160 @@
1
+ import { Command } from 'commander';
2
+ import { resolve } from 'node:path';
3
+ import { writeFileSync } from 'node:fs';
4
+ import ora from 'ora';
5
+ import chalk from 'chalk';
6
+ import { loadConfig, mergeConfig } from './config.js';
7
+ import { scanFiles, extractLinksFromFile } from './scanner/index.js';
8
+ import { validateInternalLink, validateExternalLinks } from './validators/index.js';
9
+ import { reportConsole, reportJson, reportMarkdown } from './reporters/index.js';
10
+ export function createCli() {
11
+ const program = new Command();
12
+ program
13
+ .name('mdx-linklist')
14
+ .description('Extract and validate links in MDX files')
15
+ .version('0.1.0');
16
+ program
17
+ .command('check')
18
+ .description('Check links in MDX files')
19
+ .argument('<directory>', 'Directory to scan for MDX files')
20
+ .option('-c, --config <path>', 'Path to config file')
21
+ .option('-i, --internal-only', 'Only check internal links')
22
+ .option('-e, --external-only', 'Only check external links')
23
+ .option('--ignore <pattern>', 'Ignore URL pattern (can be repeated)', collect, [])
24
+ .option('--ignore-domain <domain>', 'Ignore domain (can be repeated)', collect, [])
25
+ .option('--route-prefix <prefix>', 'Route prefix for absolute paths (can be repeated)', collect, [])
26
+ .option('--component <name>', 'Custom JSX component with href prop (can be repeated)', collect, [])
27
+ .option('-t, --timeout <ms>', 'External request timeout', '10000')
28
+ .option('--concurrency <n>', 'Parallel requests', '10')
29
+ .option('-f, --format <type>', 'Output format: console|json|markdown', 'console')
30
+ .option('-o, --output <file>', 'Write report to file')
31
+ .option('--no-progress', 'Hide progress bar')
32
+ .option('--no-fail', 'Do not exit with code 1 on broken links')
33
+ .option('-v, --verbose', 'Show all links, not just broken')
34
+ .action(async (directory, options) => {
35
+ await runCheck(directory, options);
36
+ });
37
+ return program;
38
+ }
39
+ function collect(value, previous) {
40
+ return previous.concat([value]);
41
+ }
42
+ async function runCheck(directory, options) {
43
+ const startTime = Date.now();
44
+ const baseDir = resolve(process.cwd(), directory);
45
+ // Load and merge config
46
+ let config = loadConfig(options.config);
47
+ // Apply CLI overrides
48
+ const overrides = {};
49
+ if (options.internalOnly)
50
+ overrides.internalOnly = true;
51
+ if (options.externalOnly)
52
+ overrides.externalOnly = true;
53
+ if (options.ignore.length > 0) {
54
+ overrides.ignorePatterns = options.ignore;
55
+ }
56
+ if (options.ignoreDomain.length > 0) {
57
+ overrides.ignoreDomains = options.ignoreDomain;
58
+ }
59
+ if (options.routePrefix.length > 0) {
60
+ overrides.routePrefixes = options.routePrefix;
61
+ }
62
+ if (options.component.length > 0) {
63
+ overrides.customComponents = options.component;
64
+ }
65
+ if (options.timeout)
66
+ overrides.timeout = parseInt(options.timeout, 10);
67
+ if (options.concurrency)
68
+ overrides.concurrency = parseInt(options.concurrency, 10);
69
+ config = mergeConfig(config, overrides);
70
+ const showProgress = options.progress !== false;
71
+ const format = options.format;
72
+ const verbose = options.verbose;
73
+ // Scan for files
74
+ let spinner = showProgress ? ora('Scanning for MDX files...').start() : null;
75
+ const files = await scanFiles(directory, config);
76
+ if (files.length === 0) {
77
+ spinner?.fail('No MDX files found');
78
+ process.exit(0);
79
+ }
80
+ spinner?.succeed(`Found ${files.length} files`);
81
+ // Extract links from all files
82
+ spinner = showProgress ? ora('Extracting links...').start() : null;
83
+ const allLinks = [];
84
+ for (const file of files) {
85
+ const links = extractLinksFromFile(file, config);
86
+ allLinks.push(...links);
87
+ }
88
+ spinner?.succeed(`Found ${allLinks.length} links`);
89
+ // Filter links based on config
90
+ let linksToCheck = allLinks;
91
+ if (config.internalOnly) {
92
+ linksToCheck = allLinks.filter((l) => l.type === 'internal' || l.type === 'anchor' || l.type === 'asset');
93
+ }
94
+ else if (config.externalOnly) {
95
+ linksToCheck = allLinks.filter((l) => l.type === 'external');
96
+ }
97
+ // Validate links
98
+ const results = [];
99
+ // Check internal links
100
+ const internalLinks = linksToCheck.filter((l) => l.type === 'internal' || l.type === 'anchor' || l.type === 'asset');
101
+ const externalLinks = linksToCheck.filter((l) => l.type === 'external');
102
+ if (internalLinks.length > 0) {
103
+ spinner = showProgress
104
+ ? ora(`Checking ${internalLinks.length} internal links...`).start()
105
+ : null;
106
+ for (const link of internalLinks) {
107
+ const result = await validateInternalLink(link, baseDir, config, files);
108
+ results.push(result);
109
+ }
110
+ spinner?.succeed(`Checked ${internalLinks.length} internal links`);
111
+ }
112
+ if (externalLinks.length > 0 && !config.internalOnly) {
113
+ spinner = showProgress
114
+ ? ora(`Checking ${externalLinks.length} external links...`).start()
115
+ : null;
116
+ const externalResults = await validateExternalLinks(externalLinks, config);
117
+ results.push(...externalResults);
118
+ spinner?.succeed(`Checked ${externalLinks.length} external links`);
119
+ }
120
+ // Calculate summary
121
+ const summary = {
122
+ filesScanned: files.length,
123
+ totalLinks: allLinks.length,
124
+ internalLinks: allLinks.filter((l) => l.type === 'internal' || l.type === 'anchor' || l.type === 'asset').length,
125
+ externalLinks: allLinks.filter((l) => l.type === 'external').length,
126
+ brokenInternal: results.filter((r) => r.status === 'broken' &&
127
+ (r.link.type === 'internal' || r.link.type === 'anchor' || r.link.type === 'asset')).length,
128
+ brokenExternal: results.filter((r) => r.status === 'broken' && r.link.type === 'external').length,
129
+ skipped: results.filter((r) => r.status === 'skipped').length,
130
+ timeouts: results.filter((r) => r.status === 'timeout').length,
131
+ duration: Date.now() - startTime,
132
+ };
133
+ // Generate report
134
+ let output;
135
+ switch (format) {
136
+ case 'json':
137
+ output = reportJson(results, summary, baseDir);
138
+ break;
139
+ case 'markdown':
140
+ output = reportMarkdown(results, summary, baseDir);
141
+ break;
142
+ default:
143
+ reportConsole(results, summary, baseDir, verbose);
144
+ output = '';
145
+ }
146
+ // Write to file if specified
147
+ if (options.output && output) {
148
+ writeFileSync(options.output, output, 'utf-8');
149
+ console.log(chalk.green(`Report written to ${options.output}`));
150
+ }
151
+ else if (output) {
152
+ console.log(output);
153
+ }
154
+ // Exit with error code if broken links found
155
+ const brokenCount = summary.brokenInternal + summary.brokenExternal + summary.timeouts;
156
+ if (brokenCount > 0 && options.fail !== false) {
157
+ process.exit(1);
158
+ }
159
+ }
160
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import { Config } from './types.js';
2
+ export declare function loadConfig(configPath?: string): Config;
3
+ export declare function mergeConfig(base: Config, override: Partial<Config>): Config;
4
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAkB,MAAM,YAAY,CAAC;AAEpD,wBAAgB,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAuBtD;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAc3E"}
package/dist/config.js ADDED
@@ -0,0 +1,42 @@
1
+ import { readFileSync, existsSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { DEFAULT_CONFIG } from './types.js';
4
+ export function loadConfig(configPath) {
5
+ const paths = configPath
6
+ ? [configPath]
7
+ : [
8
+ './mdx-linklist.config.json',
9
+ './linklist.config.json',
10
+ './.mdx-linklistrc.json',
11
+ ];
12
+ for (const p of paths) {
13
+ const fullPath = resolve(process.cwd(), p);
14
+ if (existsSync(fullPath)) {
15
+ try {
16
+ const content = readFileSync(fullPath, 'utf-8');
17
+ const userConfig = JSON.parse(content);
18
+ return mergeConfig(DEFAULT_CONFIG, userConfig);
19
+ }
20
+ catch {
21
+ console.warn(`Warning: Could not parse config file: ${fullPath}`);
22
+ }
23
+ }
24
+ }
25
+ return DEFAULT_CONFIG;
26
+ }
27
+ export function mergeConfig(base, override) {
28
+ return {
29
+ ...base,
30
+ ...override,
31
+ include: override.include ?? base.include,
32
+ exclude: override.exclude ?? base.exclude,
33
+ ignorePatterns: [
34
+ ...base.ignorePatterns,
35
+ ...(override.ignorePatterns ?? []),
36
+ ],
37
+ ignoreDomains: [...base.ignoreDomains, ...(override.ignoreDomains ?? [])],
38
+ customComponents: override.customComponents ?? base.customComponents,
39
+ routePrefixes: override.routePrefixes ?? base.routePrefixes,
40
+ };
41
+ }
42
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAU,cAAc,EAAE,MAAM,YAAY,CAAC;AAEpD,MAAM,UAAU,UAAU,CAAC,UAAmB;IAC5C,MAAM,KAAK,GAAG,UAAU;QACtB,CAAC,CAAC,CAAC,UAAU,CAAC;QACd,CAAC,CAAC;YACE,4BAA4B;YAC5B,wBAAwB;YACxB,wBAAwB;SACzB,CAAC;IAEN,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC;QAC3C,IAAI,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YACzB,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;gBAChD,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAoB,CAAC;gBAC1D,OAAO,WAAW,CAAC,cAAc,EAAE,UAAU,CAAC,CAAC;YACjD,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,CAAC,IAAI,CAAC,yCAAyC,QAAQ,EAAE,CAAC,CAAC;YACpE,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,cAAc,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,QAAyB;IACjE,OAAO;QACL,GAAG,IAAI;QACP,GAAG,QAAQ;QACX,OAAO,EAAE,QAAQ,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO;QACzC,OAAO,EAAE,QAAQ,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO;QACzC,cAAc,EAAE;YACd,GAAG,IAAI,CAAC,cAAc;YACtB,GAAG,CAAC,QAAQ,CAAC,cAAc,IAAI,EAAE,CAAC;SACnC;QACD,aAAa,EAAE,CAAC,GAAG,IAAI,CAAC,aAAa,EAAE,GAAG,CAAC,QAAQ,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC;QACzE,gBAAgB,EAAE,QAAQ,CAAC,gBAAgB,IAAI,IAAI,CAAC,gBAAgB;QACpE,aAAa,EAAE,QAAQ,CAAC,aAAa,IAAI,IAAI,CAAC,aAAa;KAC5D,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import { createCli } from './cli.js';
3
+ const program = createCli();
4
+ program.parse();
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAErC,MAAM,OAAO,GAAG,SAAS,EAAE,CAAC;AAC5B,OAAO,CAAC,KAAK,EAAE,CAAC"}
@@ -0,0 +1,8 @@
1
+ export interface ParsedAnchor {
2
+ id: string;
3
+ heading: string;
4
+ line: number;
5
+ }
6
+ export declare function extractAnchorsFromFile(filePath: string): ParsedAnchor[];
7
+ export declare function findSimilarAnchors(target: string, anchors: ParsedAnchor[], maxSuggestions?: number): string[];
8
+ //# sourceMappingURL=heading-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"heading-parser.d.ts","sourceRoot":"","sources":["../../src/parsers/heading-parser.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,EAAE,CA4CvE;AAED,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,YAAY,EAAE,EACvB,cAAc,SAAI,GACjB,MAAM,EAAE,CA0BV"}
@@ -0,0 +1,63 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { headingToAnchor } from '../utils.js';
3
+ export function extractAnchorsFromFile(filePath) {
4
+ const content = readFileSync(filePath, 'utf-8');
5
+ const lines = content.split('\n');
6
+ const anchors = [];
7
+ for (let i = 0; i < lines.length; i++) {
8
+ const line = lines[i];
9
+ const lineNumber = i + 1;
10
+ // Match markdown headings: ## Heading
11
+ const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
12
+ if (headingMatch) {
13
+ const headingText = headingMatch[2];
14
+ // Check for explicit ID: ## Heading {#custom-id}
15
+ const explicitIdMatch = headingText.match(/^(.+?)\s*\{#([^}]+)\}\s*$/);
16
+ if (explicitIdMatch) {
17
+ anchors.push({
18
+ id: explicitIdMatch[2],
19
+ heading: explicitIdMatch[1].trim(),
20
+ line: lineNumber,
21
+ });
22
+ }
23
+ else {
24
+ anchors.push({
25
+ id: headingToAnchor(headingText),
26
+ heading: headingText.trim(),
27
+ line: lineNumber,
28
+ });
29
+ }
30
+ }
31
+ // Match HTML/JSX headings with id: <h2 id="custom-id">
32
+ const htmlHeadingMatch = line.match(/<h[1-6][^>]*id=["']([^"']+)["'][^>]*>/i);
33
+ if (htmlHeadingMatch) {
34
+ anchors.push({
35
+ id: htmlHeadingMatch[1],
36
+ heading: '',
37
+ line: lineNumber,
38
+ });
39
+ }
40
+ }
41
+ return anchors;
42
+ }
43
+ export function findSimilarAnchors(target, anchors, maxSuggestions = 3) {
44
+ const targetLower = target.toLowerCase();
45
+ const scored = anchors.map((anchor) => {
46
+ const idLower = anchor.id.toLowerCase();
47
+ let score = 0;
48
+ // Exact substring match
49
+ if (idLower.includes(targetLower) || targetLower.includes(idLower)) {
50
+ score += 10;
51
+ }
52
+ // Similar length and characters
53
+ const commonChars = [...targetLower].filter((c) => idLower.includes(c)).length;
54
+ score += commonChars / Math.max(target.length, anchor.id.length) * 5;
55
+ return { id: anchor.id, score };
56
+ });
57
+ return scored
58
+ .filter((s) => s.score > 2)
59
+ .sort((a, b) => b.score - a.score)
60
+ .slice(0, maxSuggestions)
61
+ .map((s) => s.id);
62
+ }
63
+ //# sourceMappingURL=heading-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"heading-parser.js","sourceRoot":"","sources":["../../src/parsers/heading-parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export { extractAnchorsFromFile, findSimilarAnchors, type ParsedAnchor, } from './heading-parser.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/parsers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,kBAAkB,EAClB,KAAK,YAAY,GAClB,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { extractAnchorsFromFile, findSimilarAnchors, } from './heading-parser.js';
2
+ //# sourceMappingURL=index.js.map