@cosmocoder/mcp-web-docs 2.0.20 → 2.0.22
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/build/config.d.ts +0 -19
- package/build/config.js +0 -50
- package/build/config.js.map +1 -1
- package/build/config.test.js +1 -31
- package/build/config.test.js.map +1 -1
- package/build/crawler/base.d.ts +1 -16
- package/build/crawler/base.js +4 -120
- package/build/crawler/base.js.map +1 -1
- package/build/crawler/base.test.js +2 -184
- package/build/crawler/base.test.js.map +1 -1
- package/build/crawler/crawlee-crawler.js +0 -1
- package/build/crawler/crawlee-crawler.js.map +1 -1
- package/build/crawler/crawlee-crawler.test.js +13 -0
- package/build/crawler/crawlee-crawler.test.js.map +1 -1
- package/build/crawler/docs-crawler.d.ts +4 -7
- package/build/crawler/docs-crawler.js +5 -12
- package/build/crawler/docs-crawler.js.map +1 -1
- package/build/crawler/docs-crawler.test.js +13 -51
- package/build/crawler/docs-crawler.test.js.map +1 -1
- package/build/crawler/github.d.ts +1 -1
- package/build/crawler/github.js +7 -7
- package/build/crawler/github.js.map +1 -1
- package/build/crawler/github.test.js +9 -39
- package/build/crawler/github.test.js.map +1 -1
- package/build/crawler/llms-txt.js +5 -3
- package/build/crawler/llms-txt.js.map +1 -1
- package/build/crawler/llms-txt.test.js +2 -0
- package/build/crawler/llms-txt.test.js.map +1 -1
- package/build/index.js +26 -1664
- package/build/index.js.map +1 -1
- package/build/index.test.js +267 -433
- package/build/index.test.js.map +1 -1
- package/build/indexing/queue-manager.d.ts +2 -0
- package/build/indexing/queue-manager.js +13 -18
- package/build/indexing/queue-manager.js.map +1 -1
- package/build/indexing/queue-manager.test.js +5 -12
- package/build/indexing/queue-manager.test.js.map +1 -1
- package/build/indexing/workflow.d.ts +38 -0
- package/build/indexing/workflow.js +223 -0
- package/build/indexing/workflow.js.map +1 -0
- package/build/indexing/workflow.test.d.ts +1 -0
- package/build/indexing/workflow.test.js +218 -0
- package/build/indexing/workflow.test.js.map +1 -0
- package/build/server.d.ts +88 -0
- package/build/server.js +1460 -0
- package/build/server.js.map +1 -0
- package/build/server.test.d.ts +1 -0
- package/build/server.test.js +27 -0
- package/build/server.test.js.map +1 -0
- package/build/storage/storage.js +1 -0
- package/build/storage/storage.js.map +1 -1
- package/build/storage/storage.test.js +25 -8
- package/build/storage/storage.test.js.map +1 -1
- package/build/types.d.ts +0 -15
- package/build/util/docs.js +1 -2
- package/build/util/docs.js.map +1 -1
- package/build/util/docs.test.js +8 -2
- package/build/util/docs.test.js.map +1 -1
- package/build/util/security.d.ts +1 -0
- package/build/util/security.js +6 -5
- package/build/util/security.js.map +1 -1
- package/build/util/security.test.js +7 -1
- package/build/util/security.test.js.map +1 -1
- package/package.json +1 -1
package/build/config.d.ts
CHANGED
|
@@ -1,36 +1,17 @@
|
|
|
1
1
|
export interface DocsConfig {
|
|
2
2
|
githubToken?: string;
|
|
3
|
-
maxDepth: number;
|
|
4
|
-
maxRequestsPerCrawl: number;
|
|
5
3
|
maxChunkSize: number;
|
|
6
|
-
maxConcurrentRequests: number;
|
|
7
4
|
cacheSize: number;
|
|
8
5
|
dataDir: string;
|
|
9
6
|
dbPath: string;
|
|
10
7
|
vectorDbPath: string;
|
|
11
8
|
}
|
|
12
9
|
export declare function loadConfig(): Promise<DocsConfig>;
|
|
13
|
-
export declare const IGNORED_PATHS: string[];
|
|
14
10
|
export declare const RATE_LIMIT: {
|
|
15
11
|
maxRequests: number;
|
|
16
12
|
timeWindow: number;
|
|
17
13
|
minDelay: number;
|
|
18
14
|
};
|
|
19
|
-
export declare const QUEUE_OPTIONS: {
|
|
20
|
-
maxRequestRetries: number;
|
|
21
|
-
retryDelay: number;
|
|
22
|
-
maxRequestsPerCrawl: number;
|
|
23
|
-
};
|
|
24
|
-
export declare const GITHUB_RATE_LIMIT: {
|
|
25
|
-
unauthenticated: {
|
|
26
|
-
maxRequests: number;
|
|
27
|
-
timeWindow: number;
|
|
28
|
-
};
|
|
29
|
-
authenticated: {
|
|
30
|
-
maxRequests: number;
|
|
31
|
-
timeWindow: number;
|
|
32
|
-
};
|
|
33
|
-
};
|
|
34
15
|
export declare function isValidUrl(urlString: string): boolean;
|
|
35
16
|
/**
|
|
36
17
|
* Validate URL and check for SSRF attacks (blocks private/internal networks)
|
package/build/config.js
CHANGED
|
@@ -5,10 +5,7 @@ import { logger } from './util/logger.js';
|
|
|
5
5
|
import { validatePublicUrl } from './util/security.js';
|
|
6
6
|
const DATA_DIR = join(homedir(), '.mcp-web-docs');
|
|
7
7
|
const DEFAULT_CONFIG = {
|
|
8
|
-
maxDepth: 4,
|
|
9
|
-
maxRequestsPerCrawl: 1000, // Match DocsCrawler default for better coverage
|
|
10
8
|
maxChunkSize: 1000,
|
|
11
|
-
maxConcurrentRequests: 3, // Allow concurrent requests for better performance while maintaining stability
|
|
12
9
|
cacheSize: 1000,
|
|
13
10
|
dataDir: DATA_DIR,
|
|
14
11
|
dbPath: join(DATA_DIR, 'docs.db'),
|
|
@@ -44,59 +41,12 @@ export async function loadConfig() {
|
|
|
44
41
|
});
|
|
45
42
|
return config;
|
|
46
43
|
}
|
|
47
|
-
// Constants for indexing
|
|
48
|
-
export const IGNORED_PATHS = [
|
|
49
|
-
'favicon.ico',
|
|
50
|
-
'robots.txt',
|
|
51
|
-
'.rst.txt',
|
|
52
|
-
'genindex',
|
|
53
|
-
'py-modindex',
|
|
54
|
-
'search.html',
|
|
55
|
-
'search',
|
|
56
|
-
'genindex.html',
|
|
57
|
-
'changelog',
|
|
58
|
-
'changelog.html',
|
|
59
|
-
'assets/',
|
|
60
|
-
'static/',
|
|
61
|
-
'images/',
|
|
62
|
-
'img/',
|
|
63
|
-
'css/',
|
|
64
|
-
'js/',
|
|
65
|
-
'fonts/',
|
|
66
|
-
// Common repository paths to ignore
|
|
67
|
-
'node_modules/',
|
|
68
|
-
'vendor/',
|
|
69
|
-
'test/',
|
|
70
|
-
'tests/',
|
|
71
|
-
'example/',
|
|
72
|
-
'examples/',
|
|
73
|
-
'build/',
|
|
74
|
-
'dist/',
|
|
75
|
-
'.git/',
|
|
76
|
-
];
|
|
77
44
|
// Rate limiting constants
|
|
78
45
|
export const RATE_LIMIT = {
|
|
79
46
|
maxRequests: 60, // Increased for better throughput
|
|
80
47
|
timeWindow: 60 * 1000, // 1 minute
|
|
81
48
|
minDelay: 250, // Reduced delay between requests
|
|
82
49
|
};
|
|
83
|
-
// Queue configuration
|
|
84
|
-
export const QUEUE_OPTIONS = {
|
|
85
|
-
maxRequestRetries: 2,
|
|
86
|
-
retryDelay: 1000,
|
|
87
|
-
maxRequestsPerCrawl: 2000, // Increased from default 1000
|
|
88
|
-
};
|
|
89
|
-
// GitHub API rate limits
|
|
90
|
-
export const GITHUB_RATE_LIMIT = {
|
|
91
|
-
unauthenticated: {
|
|
92
|
-
maxRequests: 60,
|
|
93
|
-
timeWindow: 60 * 60 * 1000, // 1 hour
|
|
94
|
-
},
|
|
95
|
-
authenticated: {
|
|
96
|
-
maxRequests: 5000,
|
|
97
|
-
timeWindow: 60 * 60 * 1000, // 1 hour
|
|
98
|
-
},
|
|
99
|
-
};
|
|
100
50
|
// Utility function to validate URLs
|
|
101
51
|
export function isValidUrl(urlString) {
|
|
102
52
|
try {
|
package/build/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/build/config.test.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isValidUrl, isValidPublicUrl, normalizeUrl, isGitHubUrl,
|
|
1
|
+
import { isValidUrl, isValidPublicUrl, normalizeUrl, isGitHubUrl, RATE_LIMIT } from './config.js';
|
|
2
2
|
describe('Configuration Utilities', () => {
|
|
3
3
|
describe('isValidUrl', () => {
|
|
4
4
|
it('should accept valid HTTP URLs', () => {
|
|
@@ -99,20 +99,6 @@ describe('Configuration Utilities', () => {
|
|
|
99
99
|
});
|
|
100
100
|
});
|
|
101
101
|
describe('Constants', () => {
|
|
102
|
-
describe('IGNORED_PATHS', () => {
|
|
103
|
-
it('should contain common non-content paths', () => {
|
|
104
|
-
expect(IGNORED_PATHS).toContain('favicon.ico');
|
|
105
|
-
expect(IGNORED_PATHS).toContain('robots.txt');
|
|
106
|
-
expect(IGNORED_PATHS).toContain('assets/');
|
|
107
|
-
expect(IGNORED_PATHS).toContain('node_modules/');
|
|
108
|
-
});
|
|
109
|
-
it('should be an array of strings', () => {
|
|
110
|
-
expect(Array.isArray(IGNORED_PATHS)).toBe(true);
|
|
111
|
-
IGNORED_PATHS.forEach((path) => {
|
|
112
|
-
expect(typeof path).toBe('string');
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
102
|
describe('RATE_LIMIT', () => {
|
|
117
103
|
it('should have reasonable default values', () => {
|
|
118
104
|
expect(RATE_LIMIT.maxRequests).toBeGreaterThan(0);
|
|
@@ -120,22 +106,6 @@ describe('Configuration Utilities', () => {
|
|
|
120
106
|
expect(RATE_LIMIT.minDelay).toBeGreaterThanOrEqual(0);
|
|
121
107
|
});
|
|
122
108
|
});
|
|
123
|
-
describe('QUEUE_OPTIONS', () => {
|
|
124
|
-
it('should have retry configuration', () => {
|
|
125
|
-
expect(QUEUE_OPTIONS.maxRequestRetries).toBeGreaterThanOrEqual(0);
|
|
126
|
-
expect(QUEUE_OPTIONS.retryDelay).toBeGreaterThan(0);
|
|
127
|
-
expect(QUEUE_OPTIONS.maxRequestsPerCrawl).toBeGreaterThan(0);
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
describe('GITHUB_RATE_LIMIT', () => {
|
|
131
|
-
it('should have different limits for authenticated vs unauthenticated', () => {
|
|
132
|
-
expect(GITHUB_RATE_LIMIT.authenticated.maxRequests).toBeGreaterThan(GITHUB_RATE_LIMIT.unauthenticated.maxRequests);
|
|
133
|
-
});
|
|
134
|
-
it('should have time windows defined', () => {
|
|
135
|
-
expect(GITHUB_RATE_LIMIT.authenticated.timeWindow).toBeGreaterThan(0);
|
|
136
|
-
expect(GITHUB_RATE_LIMIT.unauthenticated.timeWindow).toBeGreaterThan(0);
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
109
|
});
|
|
140
110
|
});
|
|
141
111
|
//# sourceMappingURL=config.test.js.map
|
package/build/config.test.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.test.js","sourceRoot":"","sources":["../src/config.test.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"config.test.js","sourceRoot":"","sources":["../src/config.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAElG,QAAQ,CAAC,yBAAyB,EAAE,GAAG,EAAE;IACvC,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;QAC1B,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;YACvC,MAAM,CAAC,UAAU,CAAC,oBAAoB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACpD,MAAM,CAAC,UAAU,CAAC,yBAAyB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACzD,MAAM,CAAC,UAAU,CAAC,iCAAiC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACjE,MAAM,CAAC,UAAU,CAAC,oCAAoC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;YACxC,MAAM,CAAC,UAAU,CAAC,qBAAqB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACrD,MAAM,CAAC,UAAU,CAAC,+BAA+B,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC/D,MAAM,CAAC,UAAU,CAAC,0BAA0B,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;YAC1C,MAAM,CAAC,UAAU,CAAC,mBAAmB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACpD,MAAM,CAAC,UAAU,CAAC,oBAAoB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACrD,MAAM,CAAC,UAAU,CAAC,qBAAqB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACtD,MAAM,CAAC,UAAU,CAAC,8BAA8B,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;YACpC,MAAM,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC5C,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACnC,MAAM,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC9C,MAAM,CAAC,UAAU,CAAC,yBAAyB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;YAClC,MAAM,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAClD,MAAM,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAClD,MAAM,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;QAChC,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;YACzC,MAAM,CAAC,gBAAgB,CAAC,qBAAqB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC3D,MAAM,CAAC,gBAAgB,CAAC,4BAA4B,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAClE,MAAM,CAAC,gBAAgB,CAAC,+BAA+B,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;YACvD,MAAM,CAAC,gBAAgB,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACzD,MAAM,CAAC,gBAAgB,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACzD,MAAM,CAAC,gBAAgB,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACxD,MAAM,CAAC,gBAAgB,CAAC,oBAAoB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC3D,MAAM,CAAC,gBAAgB,CAAC,mBAAmB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;YAC7D,MAAM,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC9D,MAAM,CAAC,gBAAgB,CAAC,oBAAoB,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;YAC/D,MAAM,CAAC,gBAAgB,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACxD,MAAM,CAAC,gBAAgB,CAAC,mBAAmB,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAClE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;YAC/C,MAAM,CAAC,gBAAgB,CAAC,wBAAwB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC/D,MAAM,CAAC,gBAAgB,CAAC,iCAAiC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1E,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;QAC5B,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;YACxC,MAAM,CAAC,YAAY,CAAC,sBAAsB,CAAC,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;YACzE,MAAM,CAAC,YAAY,CAAC,2BAA2B,CAAC,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;QACrF,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;YACxC,MAAM,CAAC,YAAY,CAAC,kCAAkC,CAAC,CAAC,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAC;YAClG,MAAM,CAAC,YAAY,CAAC,+BAA+B,CAAC,CAAC,CAAC,IAAI,CAAC,+BAA+B,CAAC,CAAC;QAC9F,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;YACnD,MAAM,CAAC,YAAY,CAAC,qBAAqB,CAAC,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;YACxE,MAAM,CAAC,YAAY,CAAC,0BAA0B,CAAC,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;QACpF,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;YACvC,MAAM,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;YAC/D,MAAM,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;YAC3C,MAAM,CAAC,YAAY,CAAC,kCAAkC,CAAC,CAAC,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAC;QACpG,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;QAC3B,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;YACrC,MAAM,CAAC,WAAW,CAAC,8BAA8B,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC/D,MAAM,CAAC,WAAW,CAAC,kDAAkD,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnF,MAAM,CAAC,WAAW,CAAC,oBAAoB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;YAC7C,MAAM,CAAC,WAAW,CAAC,8BAA8B,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAChE,MAAM,CAAC,WAAW,CAAC,iCAAiC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACnE,MAAM,CAAC,WAAW,CAAC,qBAAqB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;YAC1D,MAAM,CAAC,WAAW,CAAC,mBAAmB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACrD,MAAM,CAAC,WAAW,CAAC,wBAAwB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC1D,MAAM,CAAC,WAAW,CAAC,uDAAuD,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC3F,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;YAC9C,MAAM,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC7C,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACtC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,WAAW,EAAE,GAAG,EAAE;QACzB,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;YAC1B,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;gBAC/C,MAAM,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;gBAClD,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;gBACjD,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC;YACxD,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/build/crawler/base.d.ts
CHANGED
|
@@ -1,26 +1,11 @@
|
|
|
1
1
|
import { CrawlResult } from '../types.js';
|
|
2
2
|
export declare abstract class BaseCrawler {
|
|
3
|
-
protected readonly maxDepth: number;
|
|
4
|
-
protected readonly maxRequestsPerCrawl: number;
|
|
5
|
-
protected seenUrls: Set<string>;
|
|
6
3
|
protected isAborting: boolean;
|
|
7
4
|
private readonly abortController;
|
|
8
5
|
protected readonly abortSignal: AbortSignal;
|
|
9
6
|
private requestCount;
|
|
10
7
|
private lastRequestTime;
|
|
11
|
-
|
|
12
|
-
protected processedUrls: number;
|
|
13
|
-
protected onProgress?: (progress: number, description: string) => void;
|
|
14
|
-
constructor(maxDepth?: number, maxRequestsPerCrawl?: number, onProgress?: (progress: number, description: string) => void);
|
|
15
|
-
protected updateProgress(description: string): void;
|
|
16
|
-
protected addDiscoveredUrls(count: number): void;
|
|
17
|
-
protected markUrlProcessed(url: string): void;
|
|
18
|
-
abstract crawl(url: string, maxDepth?: number): AsyncGenerator<CrawlResult, void, unknown>;
|
|
19
|
-
protected shouldCrawl(urlString: string): boolean;
|
|
20
|
-
protected markUrlAsSeen(url: string): void;
|
|
21
|
-
protected getPathFromUrl(urlString: string): string;
|
|
22
|
-
protected normalizeUrl(urlString: string): string;
|
|
8
|
+
abstract crawl(url: string): AsyncGenerator<CrawlResult, void, unknown>;
|
|
23
9
|
protected rateLimit(): Promise<void>;
|
|
24
|
-
protected retryWithBackoff<T>(operation: () => Promise<T>, maxRetries?: number, baseDelay?: number): Promise<T>;
|
|
25
10
|
abort(): void;
|
|
26
11
|
}
|
package/build/crawler/base.js
CHANGED
|
@@ -1,109 +1,11 @@
|
|
|
1
|
-
import { URL } from 'url';
|
|
2
1
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
3
|
-
import {
|
|
4
|
-
import { logger } from '../util/logger.js';
|
|
2
|
+
import { RATE_LIMIT } from '../config.js';
|
|
5
3
|
export class BaseCrawler {
|
|
6
|
-
|
|
7
|
-
maxRequestsPerCrawl;
|
|
8
|
-
seenUrls;
|
|
9
|
-
isAborting;
|
|
4
|
+
isAborting = false;
|
|
10
5
|
abortController = new AbortController();
|
|
11
6
|
abortSignal = this.abortController.signal;
|
|
12
|
-
requestCount;
|
|
13
|
-
lastRequestTime;
|
|
14
|
-
totalUrls;
|
|
15
|
-
processedUrls;
|
|
16
|
-
onProgress;
|
|
17
|
-
constructor(maxDepth = 4, maxRequestsPerCrawl = 1000, onProgress) {
|
|
18
|
-
this.maxDepth = maxDepth;
|
|
19
|
-
this.maxRequestsPerCrawl = maxRequestsPerCrawl;
|
|
20
|
-
this.seenUrls = new Set();
|
|
21
|
-
this.isAborting = false;
|
|
22
|
-
this.requestCount = 0;
|
|
23
|
-
this.lastRequestTime = 0;
|
|
24
|
-
this.totalUrls = 1; // Start with 1 for the initial URL
|
|
25
|
-
this.processedUrls = 0;
|
|
26
|
-
this.onProgress = onProgress;
|
|
27
|
-
}
|
|
28
|
-
updateProgress(description) {
|
|
29
|
-
if (this.onProgress) {
|
|
30
|
-
// Calculate progress as percentage (0-100)
|
|
31
|
-
const progress = Math.min(Math.round((this.processedUrls / this.totalUrls) * 100), 100);
|
|
32
|
-
this.onProgress(progress, description);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
addDiscoveredUrls(count) {
|
|
36
|
-
this.totalUrls += count;
|
|
37
|
-
this.updateProgress('Discovering pages...');
|
|
38
|
-
}
|
|
39
|
-
markUrlProcessed(url) {
|
|
40
|
-
this.processedUrls++;
|
|
41
|
-
this.markUrlAsSeen(url);
|
|
42
|
-
this.updateProgress(`Processing page ${this.processedUrls} of ${this.totalUrls}`);
|
|
43
|
-
}
|
|
44
|
-
shouldCrawl(urlString) {
|
|
45
|
-
try {
|
|
46
|
-
const url = new URL(urlString);
|
|
47
|
-
// Skip if already seen (using full URL including query params)
|
|
48
|
-
if (this.seenUrls.has(urlString)) {
|
|
49
|
-
logger.debug(`[${this.constructor.name}] Skipping already seen URL: ${urlString}`);
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
// Skip fragments only
|
|
53
|
-
if (url.hash) {
|
|
54
|
-
logger.debug(`[${this.constructor.name}] Skipping URL with hash: ${urlString}`);
|
|
55
|
-
return false;
|
|
56
|
-
}
|
|
57
|
-
// Skip non-HTML files only if they have a file extension
|
|
58
|
-
const ext = url.pathname.split('.').pop()?.toLowerCase();
|
|
59
|
-
if (ext && ext !== 'html' && ext !== 'htm') {
|
|
60
|
-
logger.debug(`[${this.constructor.name}] Skipping non-HTML file: ${urlString}`);
|
|
61
|
-
return false;
|
|
62
|
-
}
|
|
63
|
-
// Skip ignored paths only if they match exactly
|
|
64
|
-
const path = url.pathname.toLowerCase();
|
|
65
|
-
const isIgnored = IGNORED_PATHS.some((ignored) => {
|
|
66
|
-
// If ignored path ends with /, treat it as a directory
|
|
67
|
-
if (ignored.endsWith('/')) {
|
|
68
|
-
return path.startsWith(ignored);
|
|
69
|
-
}
|
|
70
|
-
// Otherwise match exactly
|
|
71
|
-
return path === `/${ignored}`;
|
|
72
|
-
});
|
|
73
|
-
if (isIgnored) {
|
|
74
|
-
logger.debug(`[${this.constructor.name}] Skipping ignored path: ${urlString}`);
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
return true;
|
|
78
|
-
}
|
|
79
|
-
catch (error) {
|
|
80
|
-
logger.debug(`[${this.constructor.name}] Error checking URL: ${urlString}`, error);
|
|
81
|
-
return false;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
markUrlAsSeen(url) {
|
|
85
|
-
this.seenUrls.add(url);
|
|
86
|
-
}
|
|
87
|
-
getPathFromUrl(urlString) {
|
|
88
|
-
try {
|
|
89
|
-
const url = new URL(urlString);
|
|
90
|
-
return url.pathname + url.search; // Include query params in path
|
|
91
|
-
}
|
|
92
|
-
catch {
|
|
93
|
-
return urlString;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
normalizeUrl(urlString) {
|
|
97
|
-
try {
|
|
98
|
-
const url = new URL(urlString);
|
|
99
|
-
// Remove hash fragment but keep query params
|
|
100
|
-
url.hash = '';
|
|
101
|
-
return url.toString().replace(/\/$/, '');
|
|
102
|
-
}
|
|
103
|
-
catch {
|
|
104
|
-
return urlString;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
7
|
+
requestCount = 0;
|
|
8
|
+
lastRequestTime = 0;
|
|
107
9
|
async rateLimit() {
|
|
108
10
|
const now = Date.now();
|
|
109
11
|
this.requestCount++;
|
|
@@ -128,24 +30,6 @@ export class BaseCrawler {
|
|
|
128
30
|
}
|
|
129
31
|
this.lastRequestTime = Date.now();
|
|
130
32
|
}
|
|
131
|
-
async retryWithBackoff(operation, maxRetries = 3, baseDelay = 1000) {
|
|
132
|
-
let lastError;
|
|
133
|
-
for (let i = 0; i < maxRetries; i++) {
|
|
134
|
-
try {
|
|
135
|
-
return await operation();
|
|
136
|
-
}
|
|
137
|
-
catch (error) {
|
|
138
|
-
lastError = error;
|
|
139
|
-
if (i === maxRetries - 1) {
|
|
140
|
-
break;
|
|
141
|
-
}
|
|
142
|
-
// Exponential backoff
|
|
143
|
-
const delay = baseDelay * Math.pow(2, i);
|
|
144
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
throw lastError;
|
|
148
|
-
}
|
|
149
33
|
abort() {
|
|
150
34
|
this.isAborting = true;
|
|
151
35
|
this.abortController.abort();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.js","sourceRoot":"","sources":["../../src/crawler/base.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"base.js","sourceRoot":"","sources":["../../src/crawler/base.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,IAAI,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG1C,MAAM,OAAgB,WAAW;IACrB,UAAU,GAAG,KAAK,CAAC;IACZ,eAAe,GAAG,IAAI,eAAe,EAAE,CAAC;IACtC,WAAW,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC;IACrD,YAAY,GAAG,CAAC,CAAC;IACjB,eAAe,GAAG,CAAC,CAAC;IAIlB,KAAK,CAAC,SAAS;QACvB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,CAAC,YAAY,EAAE,CAAC;QAEpB,0CAA0C;QAC1C,IAAI,GAAG,GAAG,IAAI,CAAC,eAAe,GAAG,UAAU,CAAC,UAAU,EAAE,CAAC;YACvD,IAAI,CAAC,YAAY,GAAG,CAAC,CAAC;YACtB,IAAI,CAAC,eAAe,GAAG,GAAG,CAAC;YAC3B,OAAO;QACT,CAAC;QAED,0DAA0D;QAC1D,IAAI,IAAI,CAAC,YAAY,GAAG,UAAU,CAAC,WAAW,EAAE,CAAC;YAC/C,MAAM,QAAQ,GAAG,UAAU,CAAC,UAAU,GAAG,CAAC,GAAG,GAAG,IAAI,CAAC,eAAe,CAAC,CAAC;YACtE,MAAM,KAAK,CAAC,QAAQ,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;YAC/D,IAAI,CAAC,YAAY,GAAG,CAAC,CAAC;YACtB,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAClC,OAAO;QACT,CAAC;QAED,wCAAwC;QACxC,MAAM,oBAAoB,GAAG,GAAG,GAAG,IAAI,CAAC,eAAe,CAAC;QACxD,IAAI,oBAAoB,GAAG,UAAU,CAAC,QAAQ,EAAE,CAAC;YAC/C,MAAM,KAAK,CAAC,UAAU,CAAC,QAAQ,GAAG,oBAAoB,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;QACnG,CAAC;QAED,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACpC,CAAC;IAED,KAAK;QACH,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QACvB,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;IAC/B,CAAC;CACF"}
|
|
@@ -2,13 +2,8 @@ import { BaseCrawler } from './base.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Concrete implementation of BaseCrawler for testing purposes.
|
|
4
4
|
*
|
|
5
|
-
* Since BaseCrawler is abstract, we create this minimal subclass that
|
|
6
|
-
*
|
|
7
|
-
* 2. Exposes protected BaseCrawler methods via public wrappers
|
|
8
|
-
*
|
|
9
|
-
* The wrapper methods (testShouldCrawl, testMarkUrlAsSeen, etc.) simply call
|
|
10
|
-
* the inherited BaseCrawler methods - we're testing the REAL BaseCrawler
|
|
11
|
-
* implementation, not overridden methods.
|
|
5
|
+
* Since BaseCrawler is abstract, we create this minimal subclass that
|
|
6
|
+
* implements the required abstract `crawl` method.
|
|
12
7
|
*/
|
|
13
8
|
class TestCrawler extends BaseCrawler {
|
|
14
9
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
@@ -21,143 +16,21 @@ class TestCrawler extends BaseCrawler {
|
|
|
21
16
|
title: 'Test',
|
|
22
17
|
};
|
|
23
18
|
}
|
|
24
|
-
// Expose protected BaseCrawler methods for testing (these call the real implementations)
|
|
25
|
-
testShouldCrawl(url) {
|
|
26
|
-
return this.shouldCrawl(url);
|
|
27
|
-
}
|
|
28
|
-
testMarkUrlAsSeen(url) {
|
|
29
|
-
this.markUrlAsSeen(url);
|
|
30
|
-
}
|
|
31
|
-
testGetPathFromUrl(url) {
|
|
32
|
-
return this.getPathFromUrl(url);
|
|
33
|
-
}
|
|
34
|
-
testNormalizeUrl(url) {
|
|
35
|
-
return this.normalizeUrl(url);
|
|
36
|
-
}
|
|
37
19
|
async testRateLimit() {
|
|
38
20
|
return this.rateLimit();
|
|
39
21
|
}
|
|
40
|
-
async testRetryWithBackoff(operation, maxRetries) {
|
|
41
|
-
return this.retryWithBackoff(operation, maxRetries);
|
|
42
|
-
}
|
|
43
22
|
testAbort() {
|
|
44
23
|
this.abort();
|
|
45
24
|
}
|
|
46
25
|
get isAbortingFlag() {
|
|
47
26
|
return this.isAborting;
|
|
48
27
|
}
|
|
49
|
-
testUpdateProgress(description) {
|
|
50
|
-
this.updateProgress(description);
|
|
51
|
-
}
|
|
52
|
-
testAddDiscoveredUrls(count) {
|
|
53
|
-
this.addDiscoveredUrls(count);
|
|
54
|
-
}
|
|
55
|
-
testMarkUrlProcessed(url) {
|
|
56
|
-
this.markUrlProcessed(url);
|
|
57
|
-
}
|
|
58
28
|
}
|
|
59
29
|
describe('BaseCrawler', () => {
|
|
60
30
|
let crawler;
|
|
61
31
|
beforeEach(() => {
|
|
62
32
|
crawler = new TestCrawler();
|
|
63
33
|
});
|
|
64
|
-
describe('constructor', () => {
|
|
65
|
-
it('should initialize with default values', () => {
|
|
66
|
-
const defaultCrawler = new TestCrawler();
|
|
67
|
-
expect(defaultCrawler).toBeDefined();
|
|
68
|
-
});
|
|
69
|
-
it('should accept custom maxDepth', () => {
|
|
70
|
-
const customCrawler = new TestCrawler(10);
|
|
71
|
-
expect(customCrawler).toBeDefined();
|
|
72
|
-
});
|
|
73
|
-
it('should accept custom maxRequestsPerCrawl', () => {
|
|
74
|
-
const customCrawler = new TestCrawler(4, 500);
|
|
75
|
-
expect(customCrawler).toBeDefined();
|
|
76
|
-
});
|
|
77
|
-
it('should accept progress callback', () => {
|
|
78
|
-
const progressFn = vi.fn();
|
|
79
|
-
const customCrawler = new TestCrawler(4, 1000, progressFn);
|
|
80
|
-
customCrawler.testUpdateProgress('Testing');
|
|
81
|
-
expect(progressFn).toHaveBeenCalled();
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
describe('shouldCrawl', () => {
|
|
85
|
-
it('should return true for valid URLs', () => {
|
|
86
|
-
// URLs with .html extension pass the file extension check
|
|
87
|
-
expect(crawler.testShouldCrawl('https://example.com/docs.html')).toBe(true);
|
|
88
|
-
expect(crawler.testShouldCrawl('https://example.com/api.html')).toBe(true);
|
|
89
|
-
expect(crawler.testShouldCrawl('https://example.com/page.htm')).toBe(true);
|
|
90
|
-
});
|
|
91
|
-
it('should return false for already seen URLs', () => {
|
|
92
|
-
crawler.testMarkUrlAsSeen('https://example.com/seen');
|
|
93
|
-
expect(crawler.testShouldCrawl('https://example.com/seen')).toBe(false);
|
|
94
|
-
});
|
|
95
|
-
it('should return false for URLs with hash fragments', () => {
|
|
96
|
-
expect(crawler.testShouldCrawl('https://example.com/page#section')).toBe(false);
|
|
97
|
-
});
|
|
98
|
-
it('should return false for non-HTML files', () => {
|
|
99
|
-
expect(crawler.testShouldCrawl('https://example.com/image.png')).toBe(false);
|
|
100
|
-
expect(crawler.testShouldCrawl('https://example.com/doc.pdf')).toBe(false);
|
|
101
|
-
expect(crawler.testShouldCrawl('https://example.com/script.js')).toBe(false);
|
|
102
|
-
expect(crawler.testShouldCrawl('https://example.com/style.css')).toBe(false);
|
|
103
|
-
});
|
|
104
|
-
it('should return true for HTML files', () => {
|
|
105
|
-
expect(crawler.testShouldCrawl('https://example.com/page.html')).toBe(true);
|
|
106
|
-
expect(crawler.testShouldCrawl('https://example.com/page.htm')).toBe(true);
|
|
107
|
-
});
|
|
108
|
-
it('should return true for HTML file extensions', () => {
|
|
109
|
-
expect(crawler.testShouldCrawl('https://example.com/docs.html')).toBe(true);
|
|
110
|
-
expect(crawler.testShouldCrawl('https://example.com/page.htm')).toBe(true);
|
|
111
|
-
expect(crawler.testShouldCrawl('https://example.com/index.HTML')).toBe(true);
|
|
112
|
-
});
|
|
113
|
-
it('should return false for paths without HTML extension', () => {
|
|
114
|
-
// The implementation's file extension check treats all paths without
|
|
115
|
-
// .html/.htm extension as non-HTML files
|
|
116
|
-
expect(crawler.testShouldCrawl('https://example.com/docs')).toBe(false);
|
|
117
|
-
expect(crawler.testShouldCrawl('https://example.com/')).toBe(false);
|
|
118
|
-
});
|
|
119
|
-
it('should return false for invalid URLs', () => {
|
|
120
|
-
expect(crawler.testShouldCrawl('not-a-url')).toBe(false);
|
|
121
|
-
expect(crawler.testShouldCrawl('')).toBe(false);
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
describe('markUrlAsSeen', () => {
|
|
125
|
-
it('should mark URL as seen', () => {
|
|
126
|
-
const url = 'https://example.com/test.html';
|
|
127
|
-
expect(crawler.testShouldCrawl(url)).toBe(true);
|
|
128
|
-
crawler.testMarkUrlAsSeen(url);
|
|
129
|
-
expect(crawler.testShouldCrawl(url)).toBe(false);
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
describe('getPathFromUrl', () => {
|
|
133
|
-
it('should extract pathname from URL', () => {
|
|
134
|
-
expect(crawler.testGetPathFromUrl('https://example.com/docs/page')).toBe('/docs/page');
|
|
135
|
-
});
|
|
136
|
-
it('should include query params in path', () => {
|
|
137
|
-
expect(crawler.testGetPathFromUrl('https://example.com/search?q=test')).toBe('/search?q=test');
|
|
138
|
-
});
|
|
139
|
-
it('should return original string for invalid URL', () => {
|
|
140
|
-
expect(crawler.testGetPathFromUrl('not-a-url')).toBe('not-a-url');
|
|
141
|
-
});
|
|
142
|
-
it('should handle root path', () => {
|
|
143
|
-
expect(crawler.testGetPathFromUrl('https://example.com')).toBe('/');
|
|
144
|
-
expect(crawler.testGetPathFromUrl('https://example.com/')).toBe('/');
|
|
145
|
-
});
|
|
146
|
-
});
|
|
147
|
-
describe('normalizeUrl', () => {
|
|
148
|
-
it('should remove hash fragments', () => {
|
|
149
|
-
expect(crawler.testNormalizeUrl('https://example.com/page#section')).toBe('https://example.com/page');
|
|
150
|
-
});
|
|
151
|
-
it('should remove trailing slash', () => {
|
|
152
|
-
expect(crawler.testNormalizeUrl('https://example.com/docs/')).toBe('https://example.com/docs');
|
|
153
|
-
});
|
|
154
|
-
it('should preserve query params', () => {
|
|
155
|
-
expect(crawler.testNormalizeUrl('https://example.com/search?q=test')).toBe('https://example.com/search?q=test');
|
|
156
|
-
});
|
|
157
|
-
it('should return original string for invalid URL', () => {
|
|
158
|
-
expect(crawler.testNormalizeUrl('not-a-url')).toBe('not-a-url');
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
34
|
describe('abort', () => {
|
|
162
35
|
it('should set isAborting flag to true', () => {
|
|
163
36
|
expect(crawler.isAbortingFlag).toBe(false);
|
|
@@ -165,61 +38,6 @@ describe('BaseCrawler', () => {
|
|
|
165
38
|
expect(crawler.isAbortingFlag).toBe(true);
|
|
166
39
|
});
|
|
167
40
|
});
|
|
168
|
-
describe('retryWithBackoff', () => {
|
|
169
|
-
it('should return result on successful operation', async () => {
|
|
170
|
-
const operation = vi.fn().mockResolvedValue('success');
|
|
171
|
-
const result = await crawler.testRetryWithBackoff(operation);
|
|
172
|
-
expect(result).toBe('success');
|
|
173
|
-
expect(operation).toHaveBeenCalledTimes(1);
|
|
174
|
-
});
|
|
175
|
-
it('should retry on failure', async () => {
|
|
176
|
-
const operation = vi.fn().mockRejectedValueOnce(new Error('fail')).mockResolvedValue('success');
|
|
177
|
-
vi.spyOn(global, 'setTimeout').mockImplementation((callback) => {
|
|
178
|
-
callback();
|
|
179
|
-
return 0;
|
|
180
|
-
});
|
|
181
|
-
const result = await crawler.testRetryWithBackoff(operation);
|
|
182
|
-
expect(result).toBe('success');
|
|
183
|
-
expect(operation).toHaveBeenCalledTimes(2);
|
|
184
|
-
vi.restoreAllMocks();
|
|
185
|
-
});
|
|
186
|
-
it('should throw after max retries', async () => {
|
|
187
|
-
const operation = vi.fn().mockRejectedValue(new Error('persistent error'));
|
|
188
|
-
vi.spyOn(global, 'setTimeout').mockImplementation((callback) => {
|
|
189
|
-
callback();
|
|
190
|
-
return 0;
|
|
191
|
-
});
|
|
192
|
-
await expect(crawler.testRetryWithBackoff(operation, 3)).rejects.toThrow('persistent error');
|
|
193
|
-
expect(operation).toHaveBeenCalledTimes(3);
|
|
194
|
-
vi.restoreAllMocks();
|
|
195
|
-
});
|
|
196
|
-
});
|
|
197
|
-
describe('progress tracking', () => {
|
|
198
|
-
it('should call progress callback with updateProgress', () => {
|
|
199
|
-
const progressFn = vi.fn();
|
|
200
|
-
const progressCrawler = new TestCrawler(4, 1000, progressFn);
|
|
201
|
-
progressCrawler.testUpdateProgress('Processing...');
|
|
202
|
-
expect(progressFn).toHaveBeenCalledWith(expect.any(Number), 'Processing...');
|
|
203
|
-
});
|
|
204
|
-
it('should track discovered URLs', () => {
|
|
205
|
-
const progressFn = vi.fn();
|
|
206
|
-
const progressCrawler = new TestCrawler(4, 1000, progressFn);
|
|
207
|
-
progressCrawler.testAddDiscoveredUrls(5);
|
|
208
|
-
expect(progressFn).toHaveBeenCalled();
|
|
209
|
-
});
|
|
210
|
-
it('should track processed URLs', () => {
|
|
211
|
-
const progressFn = vi.fn();
|
|
212
|
-
const progressCrawler = new TestCrawler(4, 1000, progressFn);
|
|
213
|
-
progressCrawler.testMarkUrlProcessed('https://example.com/page1');
|
|
214
|
-
expect(progressFn).toHaveBeenCalled();
|
|
215
|
-
});
|
|
216
|
-
it('should not throw when progress callback not provided', () => {
|
|
217
|
-
const noCrawler = new TestCrawler();
|
|
218
|
-
expect(() => noCrawler.testUpdateProgress('Test')).not.toThrow();
|
|
219
|
-
expect(() => noCrawler.testAddDiscoveredUrls(5)).not.toThrow();
|
|
220
|
-
expect(() => noCrawler.testMarkUrlProcessed('https://example.com')).not.toThrow();
|
|
221
|
-
});
|
|
222
|
-
});
|
|
223
41
|
describe('crawl', () => {
|
|
224
42
|
it('should yield CrawlResult', async () => {
|
|
225
43
|
const results = [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.test.js","sourceRoot":"","sources":["../../src/crawler/base.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAGxC
|
|
1
|
+
{"version":3,"file":"base.test.js","sourceRoot":"","sources":["../../src/crawler/base.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAGxC;;;;;GAKG;AACH,MAAM,WAAY,SAAQ,WAAW;IACnC,6DAA6D;IAC7D,KAAK,CAAC,CAAC,KAAK,CAAC,IAAY;QACvB,MAAM;YACJ,GAAG,EAAE,qBAAqB;YAC1B,IAAI,EAAE,GAAG;YACT,OAAO,EAAE,eAAe;YACxB,aAAa,EAAE,MAAM;YACrB,KAAK,EAAE,MAAM;SACd,CAAC;IACJ,CAAC;IAEM,KAAK,CAAC,aAAa;QACxB,OAAO,IAAI,CAAC,SAAS,EAAE,CAAC;IAC1B,CAAC;IAEM,SAAS;QACd,IAAI,CAAC,KAAK,EAAE,CAAC;IACf,CAAC;IAED,IAAW,cAAc;QACvB,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;CACF;AAED,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,IAAI,OAAoB,CAAC;IAEzB,UAAU,CAAC,GAAG,EAAE;QACd,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;IAC9B,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,OAAO,EAAE,GAAG,EAAE;QACrB,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;YAC5C,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC3C,OAAO,CAAC,SAAS,EAAE,CAAC;YACpB,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,OAAO,EAAE,GAAG,EAAE;QACrB,EAAE,CAAC,0BAA0B,EAAE,KAAK,IAAI,EAAE;YACxC,MAAM,OAAO,GAAkB,EAAE,CAAC;YAClC,IAAI,KAAK,EAAE,MAAM,MAAM,IAAI,OAAO,CAAC,KAAK,CAAC,qBAAqB,CAAC,EAAE,CAAC;gBAChE,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACvB,CAAC;YAED,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC/B,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;YACnD,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QACnD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|