@cosmocoder/mcp-web-docs 2.0.21 → 2.0.23
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/__mocks__/embeddings.d.ts +0 -4
- package/build/__mocks__/embeddings.js +0 -9
- package/build/__mocks__/embeddings.js.map +1 -1
- package/build/config.d.ts +0 -2
- package/build/config.js +0 -40
- package/build/config.js.map +1 -1
- package/build/config.test.js +1 -36
- package/build/config.test.js.map +1 -1
- package/build/crawler/auth.d.ts +0 -17
- package/build/crawler/auth.js +1 -40
- package/build/crawler/auth.js.map +1 -1
- package/build/crawler/auth.test.js +4 -22
- package/build/crawler/auth.test.js.map +1 -1
- package/build/crawler/base.d.ts +0 -13
- package/build/crawler/base.js +4 -116
- package/build/crawler/base.js.map +1 -1
- package/build/crawler/base.test.js +2 -172
- package/build/crawler/base.test.js.map +1 -1
- package/build/crawler/content-utils.d.ts +0 -1
- package/build/crawler/content-utils.js +0 -8
- package/build/crawler/content-utils.js.map +1 -1
- package/build/crawler/content-utils.test.js +1 -41
- package/build/crawler/content-utils.test.js.map +1 -1
- package/build/crawler/crawlee-crawler.d.ts +2 -21
- 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 +3 -4
- package/build/crawler/docs-crawler.js +3 -5
- package/build/crawler/docs-crawler.js.map +1 -1
- package/build/crawler/docs-crawler.test.js +3 -9
- 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 +8 -19
- 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/crawler/site-rules.js +6 -4
- package/build/crawler/site-rules.js.map +1 -1
- package/build/index.js +7 -1687
- package/build/index.js.map +1 -1
- package/build/index.test.js +124 -422
- package/build/index.test.js.map +1 -1
- package/build/indexing/queue-manager.js +3 -2
- package/build/indexing/queue-manager.js.map +1 -1
- package/build/indexing/status.d.ts +0 -5
- package/build/indexing/status.js +1 -26
- package/build/indexing/status.js.map +1 -1
- package/build/indexing/status.test.js +0 -24
- package/build/indexing/status.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/processor/processor.d.ts +2 -2
- package/build/processor/processor.test.js +3 -3
- package/build/processor/processor.test.js.map +1 -1
- 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.d.ts +2 -7
- package/build/storage/storage.js +1 -55
- package/build/storage/storage.js.map +1 -1
- package/build/storage/storage.test.js +25 -19
- package/build/storage/storage.test.js.map +1 -1
- package/build/types.d.ts +1 -29
- 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 -6
- package/build/util/security.js +7 -14
- package/build/util/security.js.map +1 -1
- package/build/util/security.test.js +7 -14
- package/build/util/security.test.js.map +1 -1
- package/package.json +2 -6
- package/build/crawler/content-extractors.d.ts +0 -9
- package/build/crawler/content-extractors.js +0 -9
- package/build/crawler/content-extractors.js.map +0 -1
package/build/crawler/base.d.ts
CHANGED
|
@@ -1,24 +1,11 @@
|
|
|
1
1
|
import { CrawlResult } from '../types.js';
|
|
2
2
|
export declare abstract class BaseCrawler {
|
|
3
|
-
protected seenUrls: Set<string>;
|
|
4
3
|
protected isAborting: boolean;
|
|
5
4
|
private readonly abortController;
|
|
6
5
|
protected readonly abortSignal: AbortSignal;
|
|
7
6
|
private requestCount;
|
|
8
7
|
private lastRequestTime;
|
|
9
|
-
protected totalUrls: number;
|
|
10
|
-
protected processedUrls: number;
|
|
11
|
-
protected onProgress?: (progress: number, description: string) => void;
|
|
12
|
-
constructor(onProgress?: (progress: number, description: string) => void);
|
|
13
|
-
protected updateProgress(description: string): void;
|
|
14
|
-
protected addDiscoveredUrls(count: number): void;
|
|
15
|
-
protected markUrlProcessed(url: string): void;
|
|
16
8
|
abstract crawl(url: string): AsyncGenerator<CrawlResult, void, unknown>;
|
|
17
|
-
protected shouldCrawl(urlString: string): boolean;
|
|
18
|
-
protected markUrlAsSeen(url: string): void;
|
|
19
|
-
protected getPathFromUrl(urlString: string): string;
|
|
20
|
-
protected normalizeUrl(urlString: string): string;
|
|
21
9
|
protected rateLimit(): Promise<void>;
|
|
22
|
-
protected retryWithBackoff<T>(operation: () => Promise<T>, maxRetries?: number, baseDelay?: number): Promise<T>;
|
|
23
10
|
abort(): void;
|
|
24
11
|
}
|
package/build/crawler/base.js
CHANGED
|
@@ -1,105 +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
|
-
isAborting;
|
|
4
|
+
isAborting = false;
|
|
8
5
|
abortController = new AbortController();
|
|
9
6
|
abortSignal = this.abortController.signal;
|
|
10
|
-
requestCount;
|
|
11
|
-
lastRequestTime;
|
|
12
|
-
totalUrls;
|
|
13
|
-
processedUrls;
|
|
14
|
-
onProgress;
|
|
15
|
-
constructor(onProgress) {
|
|
16
|
-
this.seenUrls = new Set();
|
|
17
|
-
this.isAborting = false;
|
|
18
|
-
this.requestCount = 0;
|
|
19
|
-
this.lastRequestTime = 0;
|
|
20
|
-
this.totalUrls = 1; // Start with 1 for the initial URL
|
|
21
|
-
this.processedUrls = 0;
|
|
22
|
-
this.onProgress = onProgress;
|
|
23
|
-
}
|
|
24
|
-
updateProgress(description) {
|
|
25
|
-
if (this.onProgress) {
|
|
26
|
-
// Calculate progress as percentage (0-100)
|
|
27
|
-
const progress = Math.min(Math.round((this.processedUrls / this.totalUrls) * 100), 100);
|
|
28
|
-
this.onProgress(progress, description);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
addDiscoveredUrls(count) {
|
|
32
|
-
this.totalUrls += count;
|
|
33
|
-
this.updateProgress('Discovering pages...');
|
|
34
|
-
}
|
|
35
|
-
markUrlProcessed(url) {
|
|
36
|
-
this.processedUrls++;
|
|
37
|
-
this.markUrlAsSeen(url);
|
|
38
|
-
this.updateProgress(`Processing page ${this.processedUrls} of ${this.totalUrls}`);
|
|
39
|
-
}
|
|
40
|
-
shouldCrawl(urlString) {
|
|
41
|
-
try {
|
|
42
|
-
const url = new URL(urlString);
|
|
43
|
-
// Skip if already seen (using full URL including query params)
|
|
44
|
-
if (this.seenUrls.has(urlString)) {
|
|
45
|
-
logger.debug(`[${this.constructor.name}] Skipping already seen URL: ${urlString}`);
|
|
46
|
-
return false;
|
|
47
|
-
}
|
|
48
|
-
// Skip fragments only
|
|
49
|
-
if (url.hash) {
|
|
50
|
-
logger.debug(`[${this.constructor.name}] Skipping URL with hash: ${urlString}`);
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
53
|
-
// Skip non-HTML files only if they have a file extension
|
|
54
|
-
const ext = url.pathname.split('.').pop()?.toLowerCase();
|
|
55
|
-
if (ext && ext !== 'html' && ext !== 'htm') {
|
|
56
|
-
logger.debug(`[${this.constructor.name}] Skipping non-HTML file: ${urlString}`);
|
|
57
|
-
return false;
|
|
58
|
-
}
|
|
59
|
-
// Skip ignored paths only if they match exactly
|
|
60
|
-
const path = url.pathname.toLowerCase();
|
|
61
|
-
const isIgnored = IGNORED_PATHS.some((ignored) => {
|
|
62
|
-
// If ignored path ends with /, treat it as a directory
|
|
63
|
-
if (ignored.endsWith('/')) {
|
|
64
|
-
return path.startsWith(ignored);
|
|
65
|
-
}
|
|
66
|
-
// Otherwise match exactly
|
|
67
|
-
return path === `/${ignored}`;
|
|
68
|
-
});
|
|
69
|
-
if (isIgnored) {
|
|
70
|
-
logger.debug(`[${this.constructor.name}] Skipping ignored path: ${urlString}`);
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
return true;
|
|
74
|
-
}
|
|
75
|
-
catch (error) {
|
|
76
|
-
logger.debug(`[${this.constructor.name}] Error checking URL: ${urlString}`, error);
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
markUrlAsSeen(url) {
|
|
81
|
-
this.seenUrls.add(url);
|
|
82
|
-
}
|
|
83
|
-
getPathFromUrl(urlString) {
|
|
84
|
-
try {
|
|
85
|
-
const url = new URL(urlString);
|
|
86
|
-
return url.pathname + url.search; // Include query params in path
|
|
87
|
-
}
|
|
88
|
-
catch {
|
|
89
|
-
return urlString;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
normalizeUrl(urlString) {
|
|
93
|
-
try {
|
|
94
|
-
const url = new URL(urlString);
|
|
95
|
-
// Remove hash fragment but keep query params
|
|
96
|
-
url.hash = '';
|
|
97
|
-
return url.toString().replace(/\/$/, '');
|
|
98
|
-
}
|
|
99
|
-
catch {
|
|
100
|
-
return urlString;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
7
|
+
requestCount = 0;
|
|
8
|
+
lastRequestTime = 0;
|
|
103
9
|
async rateLimit() {
|
|
104
10
|
const now = Date.now();
|
|
105
11
|
this.requestCount++;
|
|
@@ -124,24 +30,6 @@ export class BaseCrawler {
|
|
|
124
30
|
}
|
|
125
31
|
this.lastRequestTime = Date.now();
|
|
126
32
|
}
|
|
127
|
-
async retryWithBackoff(operation, maxRetries = 3, baseDelay = 1000) {
|
|
128
|
-
let lastError;
|
|
129
|
-
for (let i = 0; i < maxRetries; i++) {
|
|
130
|
-
try {
|
|
131
|
-
return await operation();
|
|
132
|
-
}
|
|
133
|
-
catch (error) {
|
|
134
|
-
lastError = error;
|
|
135
|
-
if (i === maxRetries - 1) {
|
|
136
|
-
break;
|
|
137
|
-
}
|
|
138
|
-
// Exponential backoff
|
|
139
|
-
const delay = baseDelay * Math.pow(2, i);
|
|
140
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
throw lastError;
|
|
144
|
-
}
|
|
145
33
|
abort() {
|
|
146
34
|
this.isAborting = true;
|
|
147
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,131 +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 accept progress callback', () => {
|
|
66
|
-
const progressFn = vi.fn();
|
|
67
|
-
const customCrawler = new TestCrawler(progressFn);
|
|
68
|
-
customCrawler.testUpdateProgress('Testing');
|
|
69
|
-
expect(progressFn).toHaveBeenCalled();
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
describe('shouldCrawl', () => {
|
|
73
|
-
it('should return true for valid URLs', () => {
|
|
74
|
-
// URLs with .html extension pass the file extension check
|
|
75
|
-
expect(crawler.testShouldCrawl('https://example.com/docs.html')).toBe(true);
|
|
76
|
-
expect(crawler.testShouldCrawl('https://example.com/api.html')).toBe(true);
|
|
77
|
-
expect(crawler.testShouldCrawl('https://example.com/page.htm')).toBe(true);
|
|
78
|
-
});
|
|
79
|
-
it('should return false for already seen URLs', () => {
|
|
80
|
-
crawler.testMarkUrlAsSeen('https://example.com/seen');
|
|
81
|
-
expect(crawler.testShouldCrawl('https://example.com/seen')).toBe(false);
|
|
82
|
-
});
|
|
83
|
-
it('should return false for URLs with hash fragments', () => {
|
|
84
|
-
expect(crawler.testShouldCrawl('https://example.com/page#section')).toBe(false);
|
|
85
|
-
});
|
|
86
|
-
it('should return false for non-HTML files', () => {
|
|
87
|
-
expect(crawler.testShouldCrawl('https://example.com/image.png')).toBe(false);
|
|
88
|
-
expect(crawler.testShouldCrawl('https://example.com/doc.pdf')).toBe(false);
|
|
89
|
-
expect(crawler.testShouldCrawl('https://example.com/script.js')).toBe(false);
|
|
90
|
-
expect(crawler.testShouldCrawl('https://example.com/style.css')).toBe(false);
|
|
91
|
-
});
|
|
92
|
-
it('should return true for HTML files', () => {
|
|
93
|
-
expect(crawler.testShouldCrawl('https://example.com/page.html')).toBe(true);
|
|
94
|
-
expect(crawler.testShouldCrawl('https://example.com/page.htm')).toBe(true);
|
|
95
|
-
});
|
|
96
|
-
it('should return true for HTML file extensions', () => {
|
|
97
|
-
expect(crawler.testShouldCrawl('https://example.com/docs.html')).toBe(true);
|
|
98
|
-
expect(crawler.testShouldCrawl('https://example.com/page.htm')).toBe(true);
|
|
99
|
-
expect(crawler.testShouldCrawl('https://example.com/index.HTML')).toBe(true);
|
|
100
|
-
});
|
|
101
|
-
it('should return false for paths without HTML extension', () => {
|
|
102
|
-
// The implementation's file extension check treats all paths without
|
|
103
|
-
// .html/.htm extension as non-HTML files
|
|
104
|
-
expect(crawler.testShouldCrawl('https://example.com/docs')).toBe(false);
|
|
105
|
-
expect(crawler.testShouldCrawl('https://example.com/')).toBe(false);
|
|
106
|
-
});
|
|
107
|
-
it('should return false for invalid URLs', () => {
|
|
108
|
-
expect(crawler.testShouldCrawl('not-a-url')).toBe(false);
|
|
109
|
-
expect(crawler.testShouldCrawl('')).toBe(false);
|
|
110
|
-
});
|
|
111
|
-
});
|
|
112
|
-
describe('markUrlAsSeen', () => {
|
|
113
|
-
it('should mark URL as seen', () => {
|
|
114
|
-
const url = 'https://example.com/test.html';
|
|
115
|
-
expect(crawler.testShouldCrawl(url)).toBe(true);
|
|
116
|
-
crawler.testMarkUrlAsSeen(url);
|
|
117
|
-
expect(crawler.testShouldCrawl(url)).toBe(false);
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
describe('getPathFromUrl', () => {
|
|
121
|
-
it('should extract pathname from URL', () => {
|
|
122
|
-
expect(crawler.testGetPathFromUrl('https://example.com/docs/page')).toBe('/docs/page');
|
|
123
|
-
});
|
|
124
|
-
it('should include query params in path', () => {
|
|
125
|
-
expect(crawler.testGetPathFromUrl('https://example.com/search?q=test')).toBe('/search?q=test');
|
|
126
|
-
});
|
|
127
|
-
it('should return original string for invalid URL', () => {
|
|
128
|
-
expect(crawler.testGetPathFromUrl('not-a-url')).toBe('not-a-url');
|
|
129
|
-
});
|
|
130
|
-
it('should handle root path', () => {
|
|
131
|
-
expect(crawler.testGetPathFromUrl('https://example.com')).toBe('/');
|
|
132
|
-
expect(crawler.testGetPathFromUrl('https://example.com/')).toBe('/');
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
describe('normalizeUrl', () => {
|
|
136
|
-
it('should remove hash fragments', () => {
|
|
137
|
-
expect(crawler.testNormalizeUrl('https://example.com/page#section')).toBe('https://example.com/page');
|
|
138
|
-
});
|
|
139
|
-
it('should remove trailing slash', () => {
|
|
140
|
-
expect(crawler.testNormalizeUrl('https://example.com/docs/')).toBe('https://example.com/docs');
|
|
141
|
-
});
|
|
142
|
-
it('should preserve query params', () => {
|
|
143
|
-
expect(crawler.testNormalizeUrl('https://example.com/search?q=test')).toBe('https://example.com/search?q=test');
|
|
144
|
-
});
|
|
145
|
-
it('should return original string for invalid URL', () => {
|
|
146
|
-
expect(crawler.testNormalizeUrl('not-a-url')).toBe('not-a-url');
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
34
|
describe('abort', () => {
|
|
150
35
|
it('should set isAborting flag to true', () => {
|
|
151
36
|
expect(crawler.isAbortingFlag).toBe(false);
|
|
@@ -153,61 +38,6 @@ describe('BaseCrawler', () => {
|
|
|
153
38
|
expect(crawler.isAbortingFlag).toBe(true);
|
|
154
39
|
});
|
|
155
40
|
});
|
|
156
|
-
describe('retryWithBackoff', () => {
|
|
157
|
-
it('should return result on successful operation', async () => {
|
|
158
|
-
const operation = vi.fn().mockResolvedValue('success');
|
|
159
|
-
const result = await crawler.testRetryWithBackoff(operation);
|
|
160
|
-
expect(result).toBe('success');
|
|
161
|
-
expect(operation).toHaveBeenCalledTimes(1);
|
|
162
|
-
});
|
|
163
|
-
it('should retry on failure', async () => {
|
|
164
|
-
const operation = vi.fn().mockRejectedValueOnce(new Error('fail')).mockResolvedValue('success');
|
|
165
|
-
vi.spyOn(global, 'setTimeout').mockImplementation((callback) => {
|
|
166
|
-
callback();
|
|
167
|
-
return 0;
|
|
168
|
-
});
|
|
169
|
-
const result = await crawler.testRetryWithBackoff(operation);
|
|
170
|
-
expect(result).toBe('success');
|
|
171
|
-
expect(operation).toHaveBeenCalledTimes(2);
|
|
172
|
-
vi.restoreAllMocks();
|
|
173
|
-
});
|
|
174
|
-
it('should throw after max retries', async () => {
|
|
175
|
-
const operation = vi.fn().mockRejectedValue(new Error('persistent error'));
|
|
176
|
-
vi.spyOn(global, 'setTimeout').mockImplementation((callback) => {
|
|
177
|
-
callback();
|
|
178
|
-
return 0;
|
|
179
|
-
});
|
|
180
|
-
await expect(crawler.testRetryWithBackoff(operation, 3)).rejects.toThrow('persistent error');
|
|
181
|
-
expect(operation).toHaveBeenCalledTimes(3);
|
|
182
|
-
vi.restoreAllMocks();
|
|
183
|
-
});
|
|
184
|
-
});
|
|
185
|
-
describe('progress tracking', () => {
|
|
186
|
-
it('should call progress callback with updateProgress', () => {
|
|
187
|
-
const progressFn = vi.fn();
|
|
188
|
-
const progressCrawler = new TestCrawler(progressFn);
|
|
189
|
-
progressCrawler.testUpdateProgress('Processing...');
|
|
190
|
-
expect(progressFn).toHaveBeenCalledWith(expect.any(Number), 'Processing...');
|
|
191
|
-
});
|
|
192
|
-
it('should track discovered URLs', () => {
|
|
193
|
-
const progressFn = vi.fn();
|
|
194
|
-
const progressCrawler = new TestCrawler(progressFn);
|
|
195
|
-
progressCrawler.testAddDiscoveredUrls(5);
|
|
196
|
-
expect(progressFn).toHaveBeenCalled();
|
|
197
|
-
});
|
|
198
|
-
it('should track processed URLs', () => {
|
|
199
|
-
const progressFn = vi.fn();
|
|
200
|
-
const progressCrawler = new TestCrawler(progressFn);
|
|
201
|
-
progressCrawler.testMarkUrlProcessed('https://example.com/page1');
|
|
202
|
-
expect(progressFn).toHaveBeenCalled();
|
|
203
|
-
});
|
|
204
|
-
it('should not throw when progress callback not provided', () => {
|
|
205
|
-
const noCrawler = new TestCrawler();
|
|
206
|
-
expect(() => noCrawler.testUpdateProgress('Test')).not.toThrow();
|
|
207
|
-
expect(() => noCrawler.testAddDiscoveredUrls(5)).not.toThrow();
|
|
208
|
-
expect(() => noCrawler.testMarkUrlProcessed('https://example.com')).not.toThrow();
|
|
209
|
-
});
|
|
210
|
-
});
|
|
211
41
|
describe('crawl', () => {
|
|
212
42
|
it('should yield CrawlResult', async () => {
|
|
213
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"}
|
|
@@ -11,12 +11,4 @@ export function cleanContent(text) {
|
|
|
11
11
|
.replace(/\n{3,}/g, '\n\n') // Max 2 consecutive newlines
|
|
12
12
|
.trim();
|
|
13
13
|
}
|
|
14
|
-
// Utility function specifically for cleaning code blocks
|
|
15
|
-
export function cleanCodeBlock(code) {
|
|
16
|
-
return code
|
|
17
|
-
.replace(/^\s+|\s+$/g, '') // Trim whitespace
|
|
18
|
-
.replace(/\t/g, ' ') // Convert tabs to spaces
|
|
19
|
-
.replace(/\n{3,}/g, '\n\n') // Reduce multiple blank lines
|
|
20
|
-
.replace(/\u00A0/g, ' '); // Replace non-breaking spaces
|
|
21
|
-
}
|
|
22
14
|
//# sourceMappingURL=content-utils.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content-utils.js","sourceRoot":"","sources":["../../src/crawler/content-utils.ts"],"names":[],"mappings":"AAAA,gDAAgD;AAChD,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO,IAAI;SACR,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,2BAA2B;SACjD,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,yBAAyB;SAChD,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,yBAAyB;SAC9C,OAAO,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,8DAA8D;SACxF,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,sDAAsD;SACpF,IAAI,CAAC,IAAI,CAAC;SACV,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,6BAA6B;SACxD,IAAI,EAAE,CAAC;AACZ,CAAC
|
|
1
|
+
{"version":3,"file":"content-utils.js","sourceRoot":"","sources":["../../src/crawler/content-utils.ts"],"names":[],"mappings":"AAAA,gDAAgD;AAChD,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO,IAAI;SACR,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,2BAA2B;SACjD,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,yBAAyB;SAChD,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,yBAAyB;SAC9C,OAAO,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,8DAA8D;SACxF,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,sDAAsD;SACpF,IAAI,CAAC,IAAI,CAAC;SACV,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,6BAA6B;SACxD,IAAI,EAAE,CAAC;AACZ,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { cleanContent
|
|
1
|
+
import { cleanContent } from './content-utils.js';
|
|
2
2
|
describe('Content Utilities', () => {
|
|
3
3
|
describe('cleanContent', () => {
|
|
4
4
|
it('should convert escaped newlines to actual newlines', () => {
|
|
@@ -55,45 +55,5 @@ describe('Content Utilities', () => {
|
|
|
55
55
|
expect(result).toBe('Title\n\n Content with multiple spaces\n Tabbed');
|
|
56
56
|
});
|
|
57
57
|
});
|
|
58
|
-
describe('cleanCodeBlock', () => {
|
|
59
|
-
it('should trim leading and trailing whitespace', () => {
|
|
60
|
-
const input = ' \nconst x = 1;\n ';
|
|
61
|
-
const result = cleanCodeBlock(input);
|
|
62
|
-
expect(result).toBe('const x = 1;');
|
|
63
|
-
});
|
|
64
|
-
it('should convert tabs to spaces', () => {
|
|
65
|
-
const input = 'function test() {\n\treturn true;\n}';
|
|
66
|
-
const result = cleanCodeBlock(input);
|
|
67
|
-
expect(result).toBe('function test() {\n return true;\n}');
|
|
68
|
-
});
|
|
69
|
-
it('should reduce multiple blank lines', () => {
|
|
70
|
-
const input = 'line 1\n\n\n\nline 2';
|
|
71
|
-
const result = cleanCodeBlock(input);
|
|
72
|
-
expect(result).toBe('line 1\n\nline 2');
|
|
73
|
-
});
|
|
74
|
-
it('should replace non-breaking spaces with regular spaces', () => {
|
|
75
|
-
const input = 'const\u00A0x\u00A0=\u00A01;';
|
|
76
|
-
const result = cleanCodeBlock(input);
|
|
77
|
-
expect(result).toBe('const x = 1;');
|
|
78
|
-
});
|
|
79
|
-
it('should handle empty string', () => {
|
|
80
|
-
expect(cleanCodeBlock('')).toBe('');
|
|
81
|
-
});
|
|
82
|
-
it('should handle code with multiple formatting issues', () => {
|
|
83
|
-
const input = '\n\t\tconst x\u00A0= 1;\n\n\n\n\treturn x;\n';
|
|
84
|
-
const result = cleanCodeBlock(input);
|
|
85
|
-
expect(result).toBe('const x = 1;\n\n return x;');
|
|
86
|
-
});
|
|
87
|
-
it('should preserve single blank lines', () => {
|
|
88
|
-
const input = 'line 1\n\nline 2';
|
|
89
|
-
const result = cleanCodeBlock(input);
|
|
90
|
-
expect(result).toBe('line 1\n\nline 2');
|
|
91
|
-
});
|
|
92
|
-
it('should handle code with mixed indentation', () => {
|
|
93
|
-
const input = 'function test() {\n\tif (true) {\n\t\treturn;\n\t}\n}';
|
|
94
|
-
const result = cleanCodeBlock(input);
|
|
95
|
-
expect(result).toBe('function test() {\n if (true) {\n return;\n }\n}');
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
58
|
});
|
|
99
59
|
//# sourceMappingURL=content-utils.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content-utils.test.js","sourceRoot":"","sources":["../../src/crawler/content-utils.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,
|
|
1
|
+
{"version":3,"file":"content-utils.test.js","sourceRoot":"","sources":["../../src/crawler/content-utils.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,25 +1,6 @@
|
|
|
1
1
|
import { CrawlResult } from '../types.js';
|
|
2
2
|
import { BaseCrawler } from './base.js';
|
|
3
|
-
|
|
4
|
-
export interface StorageState {
|
|
5
|
-
cookies: Array<{
|
|
6
|
-
name: string;
|
|
7
|
-
value: string;
|
|
8
|
-
domain: string;
|
|
9
|
-
path: string;
|
|
10
|
-
expires?: number;
|
|
11
|
-
httpOnly?: boolean;
|
|
12
|
-
secure?: boolean;
|
|
13
|
-
sameSite?: 'Strict' | 'Lax' | 'None';
|
|
14
|
-
}>;
|
|
15
|
-
origins?: Array<{
|
|
16
|
-
origin: string;
|
|
17
|
-
localStorage: Array<{
|
|
18
|
-
name: string;
|
|
19
|
-
value: string;
|
|
20
|
-
}>;
|
|
21
|
-
}>;
|
|
22
|
-
}
|
|
3
|
+
import { type ValidatedStorageState } from '../util/security.js';
|
|
23
4
|
export declare class CrawleeCrawler extends BaseCrawler {
|
|
24
5
|
private crawler;
|
|
25
6
|
private queueManager;
|
|
@@ -39,7 +20,7 @@ export declare class CrawleeCrawler extends BaseCrawler {
|
|
|
39
20
|
/**
|
|
40
21
|
* Set authentication cookies/localStorage to use when crawling
|
|
41
22
|
*/
|
|
42
|
-
setStorageState(state:
|
|
23
|
+
setStorageState(state: ValidatedStorageState): void;
|
|
43
24
|
/**
|
|
44
25
|
* Check if a URL is within the allowed domain for this crawl.
|
|
45
26
|
* This prevents following redirects or links to external domains.
|