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.
- package/LICENSE +21 -0
- package/README.md +211 -0
- package/dist/cache/url-cache.d.ts +13 -0
- package/dist/cache/url-cache.d.ts.map +1 -0
- package/dist/cache/url-cache.js +48 -0
- package/dist/cache/url-cache.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +160 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +42 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/parsers/heading-parser.d.ts +8 -0
- package/dist/parsers/heading-parser.d.ts.map +1 -0
- package/dist/parsers/heading-parser.js +63 -0
- package/dist/parsers/heading-parser.js.map +1 -0
- package/dist/parsers/index.d.ts +2 -0
- package/dist/parsers/index.d.ts.map +1 -0
- package/dist/parsers/index.js +2 -0
- package/dist/parsers/index.js.map +1 -0
- package/dist/reporters/console.d.ts +3 -0
- package/dist/reporters/console.d.ts.map +1 -0
- package/dist/reporters/console.js +86 -0
- package/dist/reporters/console.js.map +1 -0
- package/dist/reporters/index.d.ts +4 -0
- package/dist/reporters/index.d.ts.map +1 -0
- package/dist/reporters/index.js +4 -0
- package/dist/reporters/index.js.map +1 -0
- package/dist/reporters/json.d.ts +3 -0
- package/dist/reporters/json.d.ts.map +1 -0
- package/dist/reporters/json.js +31 -0
- package/dist/reporters/json.js.map +1 -0
- package/dist/reporters/markdown.d.ts +3 -0
- package/dist/reporters/markdown.d.ts.map +1 -0
- package/dist/reporters/markdown.js +77 -0
- package/dist/reporters/markdown.js.map +1 -0
- package/dist/scanner/file-scanner.d.ts +3 -0
- package/dist/scanner/file-scanner.d.ts.map +1 -0
- package/dist/scanner/file-scanner.js +20 -0
- package/dist/scanner/file-scanner.js.map +1 -0
- package/dist/scanner/index.d.ts +3 -0
- package/dist/scanner/index.d.ts.map +1 -0
- package/dist/scanner/index.js +3 -0
- package/dist/scanner/index.js.map +1 -0
- package/dist/scanner/link-extractor.d.ts +3 -0
- package/dist/scanner/link-extractor.d.ts.map +1 -0
- package/dist/scanner/link-extractor.js +101 -0
- package/dist/scanner/link-extractor.js.map +1 -0
- package/dist/types.d.ts +46 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +14 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +8 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +115 -0
- package/dist/utils.js.map +1 -0
- package/dist/validators/external.d.ts +4 -0
- package/dist/validators/external.d.ts.map +1 -0
- package/dist/validators/external.js +111 -0
- package/dist/validators/external.js.map +1 -0
- package/dist/validators/index.d.ts +3 -0
- package/dist/validators/index.d.ts.map +1 -0
- package/dist/validators/index.js +3 -0
- package/dist/validators/index.js.map +1 -0
- package/dist/validators/internal.d.ts +3 -0
- package/dist/validators/internal.d.ts.map +1 -0
- package/dist/validators/internal.js +55 -0
- package/dist/validators/internal.js.map +1 -0
- 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 @@
|
|
|
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
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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"}
|