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/dist/utils.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { resolve, dirname, relative } from 'node:path';
|
|
2
|
+
export function resolveInternalPath(href, sourceFile, baseDir) {
|
|
3
|
+
// Remove anchor from path
|
|
4
|
+
const pathWithoutAnchor = href.split('#')[0];
|
|
5
|
+
if (!pathWithoutAnchor) {
|
|
6
|
+
// Anchor-only link like "#section"
|
|
7
|
+
return sourceFile;
|
|
8
|
+
}
|
|
9
|
+
if (pathWithoutAnchor.startsWith('/')) {
|
|
10
|
+
// Absolute path from base
|
|
11
|
+
return resolve(baseDir, pathWithoutAnchor.slice(1));
|
|
12
|
+
}
|
|
13
|
+
// Relative path
|
|
14
|
+
return resolve(dirname(sourceFile), pathWithoutAnchor);
|
|
15
|
+
}
|
|
16
|
+
export function shouldIgnoreUrl(href, ignorePatterns, ignoreDomains) {
|
|
17
|
+
// Check ignore patterns
|
|
18
|
+
for (const pattern of ignorePatterns) {
|
|
19
|
+
const regex = new RegExp('^' + pattern.replace(/\*/g, '.*').replace(/\?/g, '.') + '$');
|
|
20
|
+
if (regex.test(href)) {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// Check ignore domains
|
|
25
|
+
try {
|
|
26
|
+
const url = new URL(href);
|
|
27
|
+
if (ignoreDomains.includes(url.hostname)) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// Not a valid URL, check as string
|
|
33
|
+
for (const domain of ignoreDomains) {
|
|
34
|
+
if (href.includes(domain)) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
export function isExternalUrl(href) {
|
|
42
|
+
return href.startsWith('http://') || href.startsWith('https://');
|
|
43
|
+
}
|
|
44
|
+
export function isSpecialProtocol(href) {
|
|
45
|
+
const specialProtocols = [
|
|
46
|
+
'mailto:',
|
|
47
|
+
'tel:',
|
|
48
|
+
'javascript:',
|
|
49
|
+
'data:',
|
|
50
|
+
'ftp:',
|
|
51
|
+
'file:',
|
|
52
|
+
];
|
|
53
|
+
return specialProtocols.some((protocol) => href.startsWith(protocol));
|
|
54
|
+
}
|
|
55
|
+
export function formatDuration(ms) {
|
|
56
|
+
if (ms < 1000) {
|
|
57
|
+
return `${ms}ms`;
|
|
58
|
+
}
|
|
59
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
60
|
+
}
|
|
61
|
+
export function relativePath(filePath, baseDir) {
|
|
62
|
+
return relative(baseDir, filePath);
|
|
63
|
+
}
|
|
64
|
+
export function findSimilarPaths(target, availablePaths, maxSuggestions = 3) {
|
|
65
|
+
const targetLower = target.toLowerCase();
|
|
66
|
+
const targetParts = targetLower.split('/').filter(Boolean);
|
|
67
|
+
const scored = availablePaths.map((path) => {
|
|
68
|
+
const pathLower = path.toLowerCase();
|
|
69
|
+
const pathParts = pathLower.split('/').filter(Boolean);
|
|
70
|
+
let score = 0;
|
|
71
|
+
// Exact substring match
|
|
72
|
+
if (pathLower.includes(targetLower) || targetLower.includes(pathLower)) {
|
|
73
|
+
score += 10;
|
|
74
|
+
}
|
|
75
|
+
// Matching filename
|
|
76
|
+
const targetFile = targetParts[targetParts.length - 1];
|
|
77
|
+
const pathFile = pathParts[pathParts.length - 1];
|
|
78
|
+
if (targetFile && pathFile && pathFile.includes(targetFile)) {
|
|
79
|
+
score += 5;
|
|
80
|
+
}
|
|
81
|
+
// Levenshtein-ish similarity for short strings
|
|
82
|
+
if (target.length < 50 && path.length < 50) {
|
|
83
|
+
const distance = levenshteinDistance(targetLower, pathLower);
|
|
84
|
+
const maxLen = Math.max(target.length, path.length);
|
|
85
|
+
score += Math.max(0, (1 - distance / maxLen) * 5);
|
|
86
|
+
}
|
|
87
|
+
return { path, score };
|
|
88
|
+
});
|
|
89
|
+
return scored
|
|
90
|
+
.filter((s) => s.score > 2)
|
|
91
|
+
.sort((a, b) => b.score - a.score)
|
|
92
|
+
.slice(0, maxSuggestions)
|
|
93
|
+
.map((s) => s.path);
|
|
94
|
+
}
|
|
95
|
+
function levenshteinDistance(a, b) {
|
|
96
|
+
const matrix = [];
|
|
97
|
+
for (let i = 0; i <= b.length; i++) {
|
|
98
|
+
matrix[i] = [i];
|
|
99
|
+
}
|
|
100
|
+
for (let j = 0; j <= a.length; j++) {
|
|
101
|
+
matrix[0][j] = j;
|
|
102
|
+
}
|
|
103
|
+
for (let i = 1; i <= b.length; i++) {
|
|
104
|
+
for (let j = 1; j <= a.length; j++) {
|
|
105
|
+
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
106
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return matrix[b.length][a.length];
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { ExtractedLink, LinkCheckResult, Config } from '../types.js';
|
|
2
|
+
export declare function validateExternalLink(link: ExtractedLink, config: Config): Promise<LinkCheckResult>;
|
|
3
|
+
export declare function validateExternalLinks(links: ExtractedLink[], config: Config): Promise<LinkCheckResult[]>;
|
|
4
|
+
//# sourceMappingURL=external.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"external.d.ts","sourceRoot":"","sources":["../../src/validators/external.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAWrE,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,aAAa,EACnB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,eAAe,CAAC,CA2G1B;AAED,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,aAAa,EAAE,EACtB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,eAAe,EAAE,CAAC,CAiB5B"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { shouldIgnoreUrl } from '../utils.js';
|
|
2
|
+
const urlCache = new Map();
|
|
3
|
+
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
4
|
+
export async function validateExternalLink(link, config) {
|
|
5
|
+
const { href } = link;
|
|
6
|
+
// Check if should be ignored
|
|
7
|
+
if (shouldIgnoreUrl(href, config.ignorePatterns, config.ignoreDomains)) {
|
|
8
|
+
return { link, status: 'skipped' };
|
|
9
|
+
}
|
|
10
|
+
// Check cache
|
|
11
|
+
const cached = urlCache.get(href);
|
|
12
|
+
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
|
|
13
|
+
return { ...cached.result, link };
|
|
14
|
+
}
|
|
15
|
+
const startTime = Date.now();
|
|
16
|
+
for (let attempt = 0; attempt <= config.retries; attempt++) {
|
|
17
|
+
try {
|
|
18
|
+
const controller = new AbortController();
|
|
19
|
+
const timeoutId = setTimeout(() => controller.abort(), config.timeout);
|
|
20
|
+
const response = await fetch(href, {
|
|
21
|
+
method: 'HEAD',
|
|
22
|
+
signal: controller.signal,
|
|
23
|
+
redirect: 'follow',
|
|
24
|
+
headers: {
|
|
25
|
+
'User-Agent': 'mdx-linklist/1.0 (Link Checker)',
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
clearTimeout(timeoutId);
|
|
29
|
+
const responseTime = Date.now() - startTime;
|
|
30
|
+
if (response.ok) {
|
|
31
|
+
const result = {
|
|
32
|
+
link,
|
|
33
|
+
status: 'valid',
|
|
34
|
+
statusCode: response.status,
|
|
35
|
+
responseTime,
|
|
36
|
+
};
|
|
37
|
+
urlCache.set(href, { result, timestamp: Date.now() });
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
// Some servers don't support HEAD, try GET
|
|
41
|
+
if (response.status === 405) {
|
|
42
|
+
const getResponse = await fetch(href, {
|
|
43
|
+
method: 'GET',
|
|
44
|
+
signal: controller.signal,
|
|
45
|
+
redirect: 'follow',
|
|
46
|
+
headers: {
|
|
47
|
+
'User-Agent': 'mdx-linklist/1.0 (Link Checker)',
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
if (getResponse.ok) {
|
|
51
|
+
const result = {
|
|
52
|
+
link,
|
|
53
|
+
status: 'valid',
|
|
54
|
+
statusCode: getResponse.status,
|
|
55
|
+
responseTime: Date.now() - startTime,
|
|
56
|
+
};
|
|
57
|
+
urlCache.set(href, { result, timestamp: Date.now() });
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const result = {
|
|
62
|
+
link,
|
|
63
|
+
status: 'broken',
|
|
64
|
+
statusCode: response.status,
|
|
65
|
+
error: `${response.status} ${response.statusText}`,
|
|
66
|
+
responseTime,
|
|
67
|
+
};
|
|
68
|
+
urlCache.set(href, { result, timestamp: Date.now() });
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
if (attempt === config.retries) {
|
|
73
|
+
const isTimeout = error instanceof Error && error.name === 'AbortError';
|
|
74
|
+
const result = {
|
|
75
|
+
link,
|
|
76
|
+
status: isTimeout ? 'timeout' : 'broken',
|
|
77
|
+
error: isTimeout
|
|
78
|
+
? `Timeout after ${config.timeout}ms`
|
|
79
|
+
: error instanceof Error
|
|
80
|
+
? error.message
|
|
81
|
+
: 'Unknown error',
|
|
82
|
+
responseTime: Date.now() - startTime,
|
|
83
|
+
};
|
|
84
|
+
urlCache.set(href, { result, timestamp: Date.now() });
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
// Wait before retry
|
|
88
|
+
await new Promise((resolve) => setTimeout(resolve, 1000 * (attempt + 1)));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Should not reach here, but just in case
|
|
92
|
+
return {
|
|
93
|
+
link,
|
|
94
|
+
status: 'broken',
|
|
95
|
+
error: 'Max retries exceeded',
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
export async function validateExternalLinks(links, config) {
|
|
99
|
+
const results = [];
|
|
100
|
+
const chunks = [];
|
|
101
|
+
// Split into chunks for concurrency control
|
|
102
|
+
for (let i = 0; i < links.length; i += config.concurrency) {
|
|
103
|
+
chunks.push(links.slice(i, i + config.concurrency));
|
|
104
|
+
}
|
|
105
|
+
for (const chunk of chunks) {
|
|
106
|
+
const chunkResults = await Promise.all(chunk.map((link) => validateExternalLink(link, config)));
|
|
107
|
+
results.push(...chunkResults);
|
|
108
|
+
}
|
|
109
|
+
return results;
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=external.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"external.js","sourceRoot":"","sources":["../../src/validators/external.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/validators/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/validators/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../../src/validators/internal.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrE,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAAE,GACjB,OAAO,CAAC,eAAe,CAAC,CA0D1B"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { resolveInternalPath, findSimilarPaths } from '../utils.js';
|
|
4
|
+
export async function validateInternalLink(link, baseDir, config, allFiles) {
|
|
5
|
+
const pathWithoutAnchor = link.href.split('#')[0];
|
|
6
|
+
// Handle anchor-only links - skip them
|
|
7
|
+
if (!pathWithoutAnchor || pathWithoutAnchor === '') {
|
|
8
|
+
return { link, status: 'valid' };
|
|
9
|
+
}
|
|
10
|
+
// Resolve the target file path
|
|
11
|
+
const targetPath = resolveInternalPath(link.href, link.sourceFile, baseDir);
|
|
12
|
+
// Try different extensions if no extension provided
|
|
13
|
+
const extensions = ['.mdx', '.md', '/index.mdx', '/index.md', '.tsx', '.ts', '.jsx', '.js'];
|
|
14
|
+
let foundPath = null;
|
|
15
|
+
// Helper to try finding file with extensions
|
|
16
|
+
const tryFindFile = (basePath) => {
|
|
17
|
+
if (existsSync(basePath)) {
|
|
18
|
+
return basePath;
|
|
19
|
+
}
|
|
20
|
+
for (const ext of extensions) {
|
|
21
|
+
const pathWithExt = basePath + ext;
|
|
22
|
+
if (existsSync(pathWithExt)) {
|
|
23
|
+
return pathWithExt;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
};
|
|
28
|
+
// First try the direct path
|
|
29
|
+
foundPath = tryFindFile(targetPath);
|
|
30
|
+
// If not found and it's an absolute path, try with route prefixes
|
|
31
|
+
if (!foundPath && pathWithoutAnchor.startsWith('/') && config.routePrefixes.length > 0) {
|
|
32
|
+
for (const prefix of config.routePrefixes) {
|
|
33
|
+
const prefixedPath = resolve(baseDir, prefix, pathWithoutAnchor.slice(1));
|
|
34
|
+
foundPath = tryFindFile(prefixedPath);
|
|
35
|
+
if (foundPath)
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (!foundPath) {
|
|
40
|
+
// Try to find similar paths for suggestions
|
|
41
|
+
const relativePaths = allFiles.map((f) => {
|
|
42
|
+
const rel = f.replace(baseDir, '').replace(/^\//, '');
|
|
43
|
+
return '/' + rel;
|
|
44
|
+
});
|
|
45
|
+
const suggestions = findSimilarPaths(link.href, relativePaths);
|
|
46
|
+
return {
|
|
47
|
+
link,
|
|
48
|
+
status: 'broken',
|
|
49
|
+
error: 'File not found',
|
|
50
|
+
suggestions: suggestions.length > 0 ? suggestions : undefined,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return { link, status: 'valid' };
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=internal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"internal.js","sourceRoot":"","sources":["../../src/validators/internal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpE,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,IAAmB,EACnB,OAAe,EACf,MAAc,EACd,QAAkB;IAElB,MAAM,iBAAiB,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAElD,uCAAuC;IACvC,IAAI,CAAC,iBAAiB,IAAI,iBAAiB,KAAK,EAAE,EAAE,CAAC;QACnD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IACnC,CAAC;IAED,+BAA+B;IAC/B,MAAM,UAAU,GAAG,mBAAmB,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IAE5E,oDAAoD;IACpD,MAAM,UAAU,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;IAC5F,IAAI,SAAS,GAAkB,IAAI,CAAC;IAEpC,6CAA6C;IAC7C,MAAM,WAAW,GAAG,CAAC,QAAgB,EAAiB,EAAE;QACtD,IAAI,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YACzB,OAAO,QAAQ,CAAC;QAClB,CAAC;QACD,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;YAC7B,MAAM,WAAW,GAAG,QAAQ,GAAG,GAAG,CAAC;YACnC,IAAI,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;gBAC5B,OAAO,WAAW,CAAC;YACrB,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;IAEF,4BAA4B;IAC5B,SAAS,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC;IAEpC,kEAAkE;IAClE,IAAI,CAAC,SAAS,IAAI,iBAAiB,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvF,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC;YAC1C,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YAC1E,SAAS,GAAG,WAAW,CAAC,YAAY,CAAC,CAAC;YACtC,IAAI,SAAS;gBAAE,MAAM;QACvB,CAAC;IACH,CAAC;IAED,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,4CAA4C;QAC5C,MAAM,aAAa,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YACvC,MAAM,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YACtD,OAAO,GAAG,GAAG,GAAG,CAAC;QACnB,CAAC,CAAC,CAAC;QACH,MAAM,WAAW,GAAG,gBAAgB,CAAC,IAAI,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;QAE/D,OAAO;YACL,IAAI;YACJ,MAAM,EAAE,QAAQ;YAChB,KAAK,EAAE,gBAAgB;YACvB,WAAW,EAAE,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS;SAC9D,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;AACnC,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mdx-linklist",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A CLI tool to extract and validate links in MDX files",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Aman Mittal",
|
|
7
|
+
"url": "https://amanhimself.dev"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"type": "module",
|
|
11
|
+
"bin": {
|
|
12
|
+
"mdx-linklist": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"main": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"keywords": [
|
|
20
|
+
"mdx",
|
|
21
|
+
"markdown",
|
|
22
|
+
"link-checker",
|
|
23
|
+
"broken-links",
|
|
24
|
+
"documentation",
|
|
25
|
+
"cli"
|
|
26
|
+
],
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": ""
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsc",
|
|
33
|
+
"dev": "bun src/index.ts",
|
|
34
|
+
"start": "node dist/index.js",
|
|
35
|
+
"test": "bun test",
|
|
36
|
+
"test:vitest": "vitest run",
|
|
37
|
+
"test:watch": "vitest",
|
|
38
|
+
"prepublishOnly": "bun run build"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"chalk": "^5.3.0",
|
|
42
|
+
"commander": "^12.1.0",
|
|
43
|
+
"glob": "^11.0.0",
|
|
44
|
+
"ora": "^8.1.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/bun": "latest",
|
|
48
|
+
"@types/node": "^22.10.0",
|
|
49
|
+
"typescript": "^5.7.0",
|
|
50
|
+
"vitest": "^2.1.0"
|
|
51
|
+
},
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=18.0.0"
|
|
54
|
+
}
|
|
55
|
+
}
|