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/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,3 @@
1
+ export { validateInternalLink } from './internal.js';
2
+ export { validateExternalLink, validateExternalLinks } from './external.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -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,3 @@
1
+ export { validateInternalLink } from './internal.js';
2
+ export { validateExternalLink, validateExternalLinks } from './external.js';
3
+ //# sourceMappingURL=index.js.map
@@ -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,3 @@
1
+ import { ExtractedLink, LinkCheckResult, Config } from '../types.js';
2
+ export declare function validateInternalLink(link: ExtractedLink, baseDir: string, config: Config, allFiles: string[]): Promise<LinkCheckResult>;
3
+ //# sourceMappingURL=internal.d.ts.map
@@ -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
+ }