@vibe-agent-toolkit/resources 0.1.11 → 0.1.13
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/dist/external-link-cache.d.ts +96 -0
- package/dist/external-link-cache.d.ts.map +1 -0
- package/dist/external-link-cache.js +183 -0
- package/dist/external-link-cache.js.map +1 -0
- package/dist/external-link-validator.d.ts +88 -0
- package/dist/external-link-validator.d.ts.map +1 -0
- package/dist/external-link-validator.js +194 -0
- package/dist/external-link-validator.js.map +1 -0
- package/dist/link-parser.js +11 -0
- package/dist/link-parser.js.map +1 -1
- package/dist/resource-registry.d.ts +37 -0
- package/dist/resource-registry.d.ts.map +1 -1
- package/dist/resource-registry.js +107 -1
- package/dist/resource-registry.js.map +1 -1
- package/dist/schemas/project-config.d.ts +210 -0
- package/dist/schemas/project-config.d.ts.map +1 -1
- package/dist/schemas/project-config.js +21 -0
- package/dist/schemas/project-config.js.map +1 -1
- package/dist/schemas/validation-result.d.ts +3 -0
- package/dist/schemas/validation-result.d.ts.map +1 -1
- package/dist/schemas/validation-result.js +3 -0
- package/dist/schemas/validation-result.js.map +1 -1
- package/package.json +3 -2
- package/src/external-link-cache.ts +215 -0
- package/src/external-link-validator.ts +250 -0
- package/src/link-parser.ts +13 -1
- package/src/resource-registry.ts +131 -1
- package/src/schemas/project-config.ts +24 -0
- package/src/schemas/validation-result.ts +3 -0
- package/src/types/markdown-link-check.d.ts +33 -0
package/src/resource-registry.ts
CHANGED
|
@@ -11,10 +11,11 @@
|
|
|
11
11
|
import type fs from 'node:fs/promises';
|
|
12
12
|
import path from 'node:path';
|
|
13
13
|
|
|
14
|
-
import { crawlDirectory, type CrawlOptions as UtilsCrawlOptions, type GitTracker } from '@vibe-agent-toolkit/utils';
|
|
14
|
+
import { crawlDirectory, type CrawlOptions as UtilsCrawlOptions, type GitTracker, normalizedTmpdir } from '@vibe-agent-toolkit/utils';
|
|
15
15
|
|
|
16
16
|
import { calculateChecksum } from './checksum.js';
|
|
17
17
|
import { getCollectionsForFile } from './collection-matcher.js';
|
|
18
|
+
import { ExternalLinkValidator } from './external-link-validator.js';
|
|
18
19
|
import { validateFrontmatter } from './frontmatter-validator.js';
|
|
19
20
|
import { parseMarkdown } from './link-parser.js';
|
|
20
21
|
import { validateLink } from './link-validator.js';
|
|
@@ -61,6 +62,10 @@ export interface ValidateOptions {
|
|
|
61
62
|
skipGitIgnoreCheck?: boolean;
|
|
62
63
|
/** Validation mode for schemas: strict (default) or permissive */
|
|
63
64
|
validationMode?: 'strict' | 'permissive';
|
|
65
|
+
/** Check external URLs for validity (default: false) */
|
|
66
|
+
checkExternalUrls?: boolean;
|
|
67
|
+
/** Disable cache for external URL checks (default: false) */
|
|
68
|
+
noCache?: boolean;
|
|
64
69
|
}
|
|
65
70
|
|
|
66
71
|
/**
|
|
@@ -602,6 +607,12 @@ export class ResourceRegistry implements ResourceCollectionInterface {
|
|
|
602
607
|
issues.push(...this.validateAllFrontmatter(options.frontmatterSchema, mode));
|
|
603
608
|
}
|
|
604
609
|
|
|
610
|
+
// External URL validation (if enabled)
|
|
611
|
+
if (options?.checkExternalUrls) {
|
|
612
|
+
const externalUrlIssues = await this.validateExternalUrls(options.noCache ?? false);
|
|
613
|
+
issues.push(...externalUrlIssues);
|
|
614
|
+
}
|
|
615
|
+
|
|
605
616
|
// Count issues (all are errors now)
|
|
606
617
|
const errorCount = issues.length;
|
|
607
618
|
|
|
@@ -630,6 +641,125 @@ export class ResourceRegistry implements ResourceCollectionInterface {
|
|
|
630
641
|
};
|
|
631
642
|
}
|
|
632
643
|
|
|
644
|
+
/**
|
|
645
|
+
* Validate external URLs in all resources.
|
|
646
|
+
* @private
|
|
647
|
+
*/
|
|
648
|
+
private async validateExternalUrls(noCache: boolean): Promise<ValidationIssue[]> {
|
|
649
|
+
// Determine cache directory
|
|
650
|
+
const cacheDir = this.getCacheDirectory();
|
|
651
|
+
|
|
652
|
+
// Create validator
|
|
653
|
+
const validator = new ExternalLinkValidator(cacheDir, {
|
|
654
|
+
timeout: 15000,
|
|
655
|
+
cacheTtlHours: noCache ? 0 : 24,
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
// Collect all external URLs from all resources
|
|
659
|
+
const urlsToValidate = this.collectExternalUrls();
|
|
660
|
+
|
|
661
|
+
// Validate all unique URLs
|
|
662
|
+
const uniqueUrls = [...urlsToValidate.keys()];
|
|
663
|
+
const results = await validator.validateLinks(uniqueUrls);
|
|
664
|
+
|
|
665
|
+
// Convert validation results to issues
|
|
666
|
+
return this.convertValidationResultsToIssues(results, urlsToValidate);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* Get cache directory for external URL validation.
|
|
671
|
+
*
|
|
672
|
+
* Always uses system temp directory (not project directory) because:
|
|
673
|
+
* - URL validation results are universal (not project-specific)
|
|
674
|
+
* - Avoids polluting project directories
|
|
675
|
+
* - No .gitignore entry needed
|
|
676
|
+
* - OS handles cleanup automatically
|
|
677
|
+
* - Cache shared across all projects (more efficient)
|
|
678
|
+
*
|
|
679
|
+
* @private
|
|
680
|
+
*/
|
|
681
|
+
private getCacheDirectory(): string {
|
|
682
|
+
return path.join(normalizedTmpdir(), '.vat-cache');
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* Collect all external URLs from all resources.
|
|
687
|
+
* @private
|
|
688
|
+
*/
|
|
689
|
+
private collectExternalUrls(): Map<string, Array<{ resourcePath: string; line?: number }>> {
|
|
690
|
+
const urlsToValidate = new Map<string, Array<{ resourcePath: string; line?: number }>>();
|
|
691
|
+
|
|
692
|
+
for (const resource of this.resourcesByPath.values()) {
|
|
693
|
+
for (const link of resource.links) {
|
|
694
|
+
if (link.type === 'external') {
|
|
695
|
+
const locations = urlsToValidate.get(link.href) ?? [];
|
|
696
|
+
const location: { resourcePath: string; line?: number } = {
|
|
697
|
+
resourcePath: resource.filePath,
|
|
698
|
+
};
|
|
699
|
+
if (link.line !== undefined) {
|
|
700
|
+
location.line = link.line;
|
|
701
|
+
}
|
|
702
|
+
locations.push(location);
|
|
703
|
+
urlsToValidate.set(link.href, locations);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
return urlsToValidate;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* Convert validation results to validation issues.
|
|
713
|
+
* @private
|
|
714
|
+
*/
|
|
715
|
+
private convertValidationResultsToIssues(
|
|
716
|
+
results: Array<{ url: string; status: 'ok' | 'error'; statusCode: number; error?: string }>,
|
|
717
|
+
urlsToValidate: Map<string, Array<{ resourcePath: string; line?: number }>>,
|
|
718
|
+
): ValidationIssue[] {
|
|
719
|
+
const issues: ValidationIssue[] = [];
|
|
720
|
+
|
|
721
|
+
for (const result of results) {
|
|
722
|
+
if (result.status !== 'error') {
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
const locations = urlsToValidate.get(result.url);
|
|
727
|
+
if (!locations) {
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
const issueType = this.determineExternalUrlIssueType(result.statusCode, result.error);
|
|
732
|
+
const errorMessage = result.error ?? `HTTP ${result.statusCode}`;
|
|
733
|
+
|
|
734
|
+
for (const location of locations) {
|
|
735
|
+
issues.push({
|
|
736
|
+
resourcePath: location.resourcePath,
|
|
737
|
+
line: location.line,
|
|
738
|
+
type: issueType,
|
|
739
|
+
link: result.url,
|
|
740
|
+
message: `External URL failed: ${errorMessage}`,
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
return issues;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* Determine issue type based on validation error.
|
|
750
|
+
* @private
|
|
751
|
+
*/
|
|
752
|
+
private determineExternalUrlIssueType(statusCode: number, error?: string): string {
|
|
753
|
+
if (statusCode === 0) {
|
|
754
|
+
const errorLower = error?.toString().toLowerCase();
|
|
755
|
+
if (errorLower?.includes('timeout')) {
|
|
756
|
+
return 'external_url_timeout';
|
|
757
|
+
}
|
|
758
|
+
return 'external_url_error';
|
|
759
|
+
}
|
|
760
|
+
return 'external_url_dead';
|
|
761
|
+
}
|
|
762
|
+
|
|
633
763
|
/**
|
|
634
764
|
* Resolve links between resources in the registry.
|
|
635
765
|
*
|
|
@@ -11,6 +11,28 @@ export const ValidationModeSchema = z.enum(['strict', 'permissive'])
|
|
|
11
11
|
|
|
12
12
|
export type ValidationMode = z.infer<typeof ValidationModeSchema>;
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* External URL validation configuration.
|
|
16
|
+
*
|
|
17
|
+
* Controls how external URLs are validated:
|
|
18
|
+
* - enabled: Whether to check external URLs
|
|
19
|
+
* - timeout: Request timeout in milliseconds (default: 15000)
|
|
20
|
+
* - retryOn429: Whether to retry on rate limit (default: true)
|
|
21
|
+
* - ignorePatterns: Regex patterns for URLs to skip (e.g., '^https://localhost')
|
|
22
|
+
*/
|
|
23
|
+
export const ExternalUrlValidationSchema = z.object({
|
|
24
|
+
enabled: z.boolean().optional()
|
|
25
|
+
.describe('Whether to validate external URLs (default: false)'),
|
|
26
|
+
timeout: z.number().int().positive().optional()
|
|
27
|
+
.describe('Request timeout in milliseconds (default: 15000)'),
|
|
28
|
+
retryOn429: z.boolean().optional()
|
|
29
|
+
.describe('Whether to retry on rate limit (429) (default: true)'),
|
|
30
|
+
ignorePatterns: z.array(z.string()).optional()
|
|
31
|
+
.describe('Regex patterns for URLs to skip validation (e.g., "^https://localhost")'),
|
|
32
|
+
}).describe('External URL validation configuration');
|
|
33
|
+
|
|
34
|
+
export type ExternalUrlValidation = z.infer<typeof ExternalUrlValidationSchema>;
|
|
35
|
+
|
|
14
36
|
/**
|
|
15
37
|
* Validation configuration for a collection.
|
|
16
38
|
*/
|
|
@@ -23,6 +45,8 @@ export const CollectionValidationSchema = z.object({
|
|
|
23
45
|
.describe('Whether to validate external URL links (default: false)'),
|
|
24
46
|
checkGitIgnored: z.boolean().optional()
|
|
25
47
|
.describe('Whether to check if non-ignored files link to git-ignored files (default: true)'),
|
|
48
|
+
externalUrls: ExternalUrlValidationSchema.optional()
|
|
49
|
+
.describe('External URL validation configuration'),
|
|
26
50
|
}).describe('Validation configuration for a collection');
|
|
27
51
|
|
|
28
52
|
export type CollectionValidation = z.infer<typeof CollectionValidationSchema>;
|
|
@@ -9,6 +9,9 @@ import { z } from 'zod';
|
|
|
9
9
|
* - frontmatter_missing: Schema requires frontmatter, file has none
|
|
10
10
|
* - frontmatter_invalid_yaml: YAML syntax error in frontmatter
|
|
11
11
|
* - frontmatter_schema_error: Frontmatter fails JSON Schema validation
|
|
12
|
+
* - external_url_dead: External URL returned error status (4xx, 5xx)
|
|
13
|
+
* - external_url_timeout: External URL request timed out
|
|
14
|
+
* - external_url_error: External URL validation failed (DNS, network, etc.)
|
|
12
15
|
* - unknown_link: Unknown link type
|
|
13
16
|
*
|
|
14
17
|
* Includes details about what went wrong, where it occurred, and optionally
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
declare module 'markdown-link-check' {
|
|
2
|
+
interface LinkCheckOptions {
|
|
3
|
+
timeout?: string;
|
|
4
|
+
retryOn429?: boolean;
|
|
5
|
+
retryCount?: number;
|
|
6
|
+
fallbackRetryDelay?: string;
|
|
7
|
+
aliveStatusCodes?: number[];
|
|
8
|
+
ignorePatterns?: Array<{ pattern: RegExp }>;
|
|
9
|
+
httpHeaders?: Array<{
|
|
10
|
+
urls: string[];
|
|
11
|
+
headers: Record<string, string>;
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface LinkCheckResult {
|
|
16
|
+
link: string;
|
|
17
|
+
status: string;
|
|
18
|
+
statusCode: number;
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Library returns various error types
|
|
20
|
+
err?: string | Error | any;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type LinkCheckCallback = (error: Error | null, results: LinkCheckResult[]) => void;
|
|
24
|
+
|
|
25
|
+
function markdownLinkCheck(
|
|
26
|
+
markdown: string,
|
|
27
|
+
options: LinkCheckOptions,
|
|
28
|
+
callback: LinkCheckCallback,
|
|
29
|
+
): void;
|
|
30
|
+
function markdownLinkCheck(markdown: string, callback: LinkCheckCallback): void;
|
|
31
|
+
|
|
32
|
+
export = markdownLinkCheck;
|
|
33
|
+
}
|