@nitpicker/crawler 0.4.2 → 0.4.3
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/package.json +5 -2
- package/CHANGELOG.md +0 -16
- package/src/archive/__mock__/.gitignore +0 -3
- package/src/archive/__mock__/mock.sqlite +0 -0
- package/src/archive/archive-accessor.ts +0 -337
- package/src/archive/archive.ts +0 -408
- package/src/archive/database.spec.ts +0 -469
- package/src/archive/database.ts +0 -1059
- package/src/archive/debug.ts +0 -10
- package/src/archive/filesystem/append-text.spec.ts +0 -26
- package/src/archive/filesystem/append-text.ts +0 -16
- package/src/archive/filesystem/copy-dir-sync.spec.ts +0 -27
- package/src/archive/filesystem/copy-dir-sync.ts +0 -10
- package/src/archive/filesystem/copy-dir.spec.ts +0 -33
- package/src/archive/filesystem/copy-dir.ts +0 -14
- package/src/archive/filesystem/exists.spec.ts +0 -33
- package/src/archive/filesystem/exists.ts +0 -10
- package/src/archive/filesystem/get-file-list.spec.ts +0 -37
- package/src/archive/filesystem/get-file-list.ts +0 -13
- package/src/archive/filesystem/index.ts +0 -17
- package/src/archive/filesystem/is-dir.spec.ts +0 -29
- package/src/archive/filesystem/is-dir.ts +0 -11
- package/src/archive/filesystem/mkdir.spec.ts +0 -37
- package/src/archive/filesystem/mkdir.ts +0 -16
- package/src/archive/filesystem/output-json.spec.ts +0 -34
- package/src/archive/filesystem/output-json.ts +0 -16
- package/src/archive/filesystem/output-text.spec.ts +0 -31
- package/src/archive/filesystem/output-text.ts +0 -35
- package/src/archive/filesystem/read-json.spec.ts +0 -26
- package/src/archive/filesystem/read-json.ts +0 -12
- package/src/archive/filesystem/read-text.spec.ts +0 -25
- package/src/archive/filesystem/read-text.ts +0 -11
- package/src/archive/filesystem/readline.spec.ts +0 -29
- package/src/archive/filesystem/readline.ts +0 -30
- package/src/archive/filesystem/remove.spec.ts +0 -34
- package/src/archive/filesystem/remove.ts +0 -11
- package/src/archive/filesystem/rename.spec.ts +0 -46
- package/src/archive/filesystem/rename.ts +0 -21
- package/src/archive/filesystem/tar.spec.ts +0 -33
- package/src/archive/filesystem/tar.ts +0 -27
- package/src/archive/filesystem/untar.spec.ts +0 -34
- package/src/archive/filesystem/untar.ts +0 -36
- package/src/archive/index.ts +0 -13
- package/src/archive/page.spec.ts +0 -368
- package/src/archive/page.ts +0 -420
- package/src/archive/resource.spec.ts +0 -101
- package/src/archive/resource.ts +0 -73
- package/src/archive/safe-path.spec.ts +0 -44
- package/src/archive/safe-path.ts +0 -18
- package/src/archive/types.ts +0 -227
- package/src/crawler/clear-destination-cache.spec.ts +0 -20
- package/src/crawler/clear-destination-cache.ts +0 -9
- package/src/crawler/crawler.ts +0 -873
- package/src/crawler/decompose-url.spec.ts +0 -48
- package/src/crawler/decompose-url.ts +0 -90
- package/src/crawler/destination-cache.spec.ts +0 -23
- package/src/crawler/destination-cache.ts +0 -8
- package/src/crawler/detect-pagination-pattern.spec.ts +0 -169
- package/src/crawler/detect-pagination-pattern.ts +0 -66
- package/src/crawler/fetch-destination.ts +0 -257
- package/src/crawler/fetch-robots-txt.spec.ts +0 -83
- package/src/crawler/fetch-robots-txt.ts +0 -91
- package/src/crawler/find-best-matching-scope.spec.ts +0 -39
- package/src/crawler/find-best-matching-scope.ts +0 -57
- package/src/crawler/generate-predicted-urls.spec.ts +0 -42
- package/src/crawler/generate-predicted-urls.ts +0 -34
- package/src/crawler/handle-ignore-and-skip.spec.ts +0 -66
- package/src/crawler/handle-ignore-and-skip.ts +0 -30
- package/src/crawler/handle-resource-response.spec.ts +0 -45
- package/src/crawler/handle-resource-response.ts +0 -21
- package/src/crawler/handle-scrape-end.spec.ts +0 -109
- package/src/crawler/handle-scrape-end.ts +0 -115
- package/src/crawler/handle-scrape-error.spec.ts +0 -105
- package/src/crawler/handle-scrape-error.ts +0 -58
- package/src/crawler/index.ts +0 -2
- package/src/crawler/inject-scope-auth.spec.ts +0 -36
- package/src/crawler/inject-scope-auth.ts +0 -27
- package/src/crawler/is-external-url.spec.ts +0 -31
- package/src/crawler/is-external-url.ts +0 -17
- package/src/crawler/is-in-any-lower-layer.spec.ts +0 -31
- package/src/crawler/is-in-any-lower-layer.ts +0 -22
- package/src/crawler/link-list.spec.ts +0 -355
- package/src/crawler/link-list.ts +0 -275
- package/src/crawler/link-to-page-data.spec.ts +0 -133
- package/src/crawler/link-to-page-data.ts +0 -34
- package/src/crawler/net-timeout-error.spec.ts +0 -25
- package/src/crawler/net-timeout-error.ts +0 -11
- package/src/crawler/protocol-agnostic-key.spec.ts +0 -40
- package/src/crawler/protocol-agnostic-key.ts +0 -11
- package/src/crawler/reconstruct-url.spec.ts +0 -37
- package/src/crawler/reconstruct-url.ts +0 -37
- package/src/crawler/robots-checker.spec.ts +0 -104
- package/src/crawler/robots-checker.ts +0 -73
- package/src/crawler/should-discard-predicted.spec.ts +0 -125
- package/src/crawler/should-discard-predicted.ts +0 -33
- package/src/crawler/should-skip-url.spec.ts +0 -77
- package/src/crawler/should-skip-url.ts +0 -37
- package/src/crawler/types.ts +0 -146
- package/src/crawler-orchestrator.ts +0 -401
- package/src/debug.ts +0 -10
- package/src/index.ts +0 -25
- package/src/types.ts +0 -30
- package/src/utils/array/each-splitted.spec.ts +0 -38
- package/src/utils/array/each-splitted.ts +0 -19
- package/src/utils/array/index.ts +0 -1
- package/src/utils/debug.ts +0 -6
- package/src/utils/error/dom-evaluation-error.spec.ts +0 -20
- package/src/utils/error/dom-evaluation-error.ts +0 -6
- package/src/utils/error/error-emitter.spec.ts +0 -78
- package/src/utils/error/error-emitter.ts +0 -44
- package/src/utils/error/index.ts +0 -3
- package/src/utils/index.ts +0 -5
- package/src/utils/object/clean-object.spec.ts +0 -24
- package/src/utils/object/clean-object.ts +0 -13
- package/src/utils/object/index.ts +0 -1
- package/src/utils/types/index.ts +0 -1
- package/src/utils/types/types.ts +0 -65
- package/tsconfig.json +0 -11
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import type LinkList from './link-list.js';
|
|
2
|
-
import type { CrawlerOptions } from './types.js';
|
|
3
|
-
import type { AnchorData, Link, PageData } from '../utils/index.js';
|
|
4
|
-
import type { ExURL } from '@d-zero/shared/parse-url';
|
|
5
|
-
|
|
6
|
-
import { crawlerLog } from '../debug.js';
|
|
7
|
-
|
|
8
|
-
import { injectScopeAuth } from './inject-scope-auth.js';
|
|
9
|
-
import { isExternalUrl } from './is-external-url.js';
|
|
10
|
-
import { isInAnyLowerLayer } from './is-in-any-lower-layer.js';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Process the result of a successful page scrape.
|
|
14
|
-
*
|
|
15
|
-
* Extracts anchors from the page (unless in metadata-only mode), enqueues
|
|
16
|
-
* newly discovered URLs via the `addUrl` callback, and marks the URL
|
|
17
|
-
* as done in the link list.
|
|
18
|
-
* @param result - The scraped page data.
|
|
19
|
-
* @param linkList - The link list managing the crawl queue.
|
|
20
|
-
* @param scope - Map of hostnames to their scope URLs.
|
|
21
|
-
* @param options - Crawler configuration options.
|
|
22
|
-
* @param addUrl - Callback to enqueue a newly discovered URL. Accepts optional
|
|
23
|
-
* `{ metadataOnly: true }` to request metadata-only scraping.
|
|
24
|
-
* @returns An object containing the constructed link and whether the page is external.
|
|
25
|
-
*/
|
|
26
|
-
export function handleScrapeEnd(
|
|
27
|
-
result: PageData,
|
|
28
|
-
linkList: LinkList,
|
|
29
|
-
scope: ReadonlyMap<string, readonly ExURL[]>,
|
|
30
|
-
options: CrawlerOptions,
|
|
31
|
-
addUrl: (url: ExURL, opts?: { metadataOnly?: true }) => void,
|
|
32
|
-
): { link: Link | null; isExternal: boolean } {
|
|
33
|
-
const isMetadataOnly = linkList.isMetadataOnly(result.url.withoutHash);
|
|
34
|
-
if (!isMetadataOnly) {
|
|
35
|
-
processAnchors(result.anchorList, scope, options, addUrl);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const link = linkList.done(
|
|
39
|
-
result.url,
|
|
40
|
-
scope,
|
|
41
|
-
{
|
|
42
|
-
page: result,
|
|
43
|
-
},
|
|
44
|
-
options,
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
crawlerLog('Scrape end URL: %s', result.url.href);
|
|
48
|
-
crawlerLog('Scrape end Status: %d', result.status);
|
|
49
|
-
crawlerLog('Scrape end Type: %s', result.contentType);
|
|
50
|
-
if (!result.isExternal) {
|
|
51
|
-
crawlerLog('Scrape end Anchors: %d URLs', result.anchorList.length);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return { link, isExternal: result.isExternal };
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Process anchor elements extracted from a scraped page and enqueue new URLs.
|
|
59
|
-
*
|
|
60
|
-
* For each anchor:
|
|
61
|
-
* 1. Determines if it is external (outside the crawl scope)
|
|
62
|
-
* 2. Injects authentication credentials from matching scope URLs
|
|
63
|
-
* 3. Reconstructs the `withoutHash` URL with injected auth
|
|
64
|
-
* 4. In recursive mode: enqueues internal lower-layer URLs for full scraping,
|
|
65
|
-
* and external URLs for metadata-only scraping (if `fetchExternal` is enabled)
|
|
66
|
-
* 5. In non-recursive mode: enqueues all URLs for metadata-only scraping
|
|
67
|
-
* @param anchors - The list of anchor data extracted from the page.
|
|
68
|
-
* @param scope - Map of hostnames to their scope URLs.
|
|
69
|
-
* @param options - Crawler configuration options.
|
|
70
|
-
* @param addUrl - Callback to enqueue a newly discovered URL. Accepts optional
|
|
71
|
-
* `{ metadataOnly: true }` to request metadata-only scraping.
|
|
72
|
-
*/
|
|
73
|
-
function processAnchors(
|
|
74
|
-
anchors: AnchorData[],
|
|
75
|
-
scope: ReadonlyMap<string, readonly ExURL[]>,
|
|
76
|
-
options: CrawlerOptions,
|
|
77
|
-
addUrl: (url: ExURL, opts?: { metadataOnly?: true }) => void,
|
|
78
|
-
): void {
|
|
79
|
-
for (const anchor of anchors) {
|
|
80
|
-
const isExternal = isExternalUrl(anchor.href, scope);
|
|
81
|
-
anchor.isExternal = isExternal;
|
|
82
|
-
|
|
83
|
-
if (!isExternal && (!anchor.href.username || !anchor.href.password)) {
|
|
84
|
-
injectScopeAuth(anchor.href, scope);
|
|
85
|
-
|
|
86
|
-
const auth =
|
|
87
|
-
anchor.href.username && anchor.href.password
|
|
88
|
-
? `${anchor.href.username}:${anchor.href.password}@`
|
|
89
|
-
: '';
|
|
90
|
-
const host =
|
|
91
|
-
anchor.href.hostname + (anchor.href.port ? `:${anchor.href.port}` : '');
|
|
92
|
-
const newSearch = anchor.href.query ? `?${anchor.href.query}` : '';
|
|
93
|
-
const body = anchor.href.dirname
|
|
94
|
-
? `${anchor.href.paths.join('/')}${newSearch}`
|
|
95
|
-
: newSearch
|
|
96
|
-
? `${newSearch}`
|
|
97
|
-
: '';
|
|
98
|
-
const withoutHash = `${anchor.href.protocol}//${auth}${host}${body ? `/${body}` : ''}`;
|
|
99
|
-
|
|
100
|
-
anchor.href.withoutHash = withoutHash;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (options.recursive) {
|
|
104
|
-
const scopes = scope.get(anchor.href.hostname);
|
|
105
|
-
if (scopes && isInAnyLowerLayer(anchor.href, scopes, options)) {
|
|
106
|
-
addUrl(anchor.href);
|
|
107
|
-
} else if (isExternal && options.fetchExternal) {
|
|
108
|
-
addUrl(anchor.href, { metadataOnly: true });
|
|
109
|
-
}
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
addUrl(anchor.href, { metadataOnly: true });
|
|
114
|
-
}
|
|
115
|
-
}
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import type { CrawlerOptions } from './types.js';
|
|
2
|
-
|
|
3
|
-
import { tryParseUrl as parseUrl } from '@d-zero/shared/parse-url';
|
|
4
|
-
import { describe, it, expect } from 'vitest';
|
|
5
|
-
|
|
6
|
-
import { handleScrapeError } from './handle-scrape-error.js';
|
|
7
|
-
import LinkList from './link-list.js';
|
|
8
|
-
|
|
9
|
-
const defaultOptions: CrawlerOptions = {
|
|
10
|
-
interval: 0,
|
|
11
|
-
parallels: 1,
|
|
12
|
-
recursive: true,
|
|
13
|
-
fromList: false,
|
|
14
|
-
captureImages: false,
|
|
15
|
-
executablePath: null,
|
|
16
|
-
fetchExternal: false,
|
|
17
|
-
scope: ['https://example.com/'],
|
|
18
|
-
excludes: [],
|
|
19
|
-
excludeKeywords: [],
|
|
20
|
-
excludeUrls: [],
|
|
21
|
-
maxExcludedDepth: 10,
|
|
22
|
-
retry: 3,
|
|
23
|
-
verbose: false,
|
|
24
|
-
disableQueries: false,
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Create a scope map for testing.
|
|
29
|
-
* @returns A scope map with example.com.
|
|
30
|
-
*/
|
|
31
|
-
function createScope() {
|
|
32
|
-
return new Map([['example.com', [parseUrl('https://example.com/')!]]]);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
describe('handleScrapeError', () => {
|
|
36
|
-
it('marks the URL as done when shutdown is true', () => {
|
|
37
|
-
const linkList = new LinkList();
|
|
38
|
-
const url = parseUrl('https://example.com/page')!;
|
|
39
|
-
const scope = createScope();
|
|
40
|
-
linkList.add(url);
|
|
41
|
-
linkList.progress(url);
|
|
42
|
-
|
|
43
|
-
const { link, result } = handleScrapeError(
|
|
44
|
-
{
|
|
45
|
-
url,
|
|
46
|
-
error: { name: 'Error', message: 'Browser crashed' },
|
|
47
|
-
shutdown: true,
|
|
48
|
-
pid: 1234,
|
|
49
|
-
},
|
|
50
|
-
linkList,
|
|
51
|
-
scope,
|
|
52
|
-
defaultOptions,
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
expect(link).not.toBeNull();
|
|
56
|
-
expect(link!.url.href).toBe(url.href);
|
|
57
|
-
expect(result).toBeDefined();
|
|
58
|
-
expect(result!.status).toBe(-1);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('marks the URL as done when shutdown is false', () => {
|
|
62
|
-
const linkList = new LinkList();
|
|
63
|
-
const url = parseUrl('https://example.com/page')!;
|
|
64
|
-
const scope = createScope();
|
|
65
|
-
linkList.add(url);
|
|
66
|
-
linkList.progress(url);
|
|
67
|
-
|
|
68
|
-
const { link, result } = handleScrapeError(
|
|
69
|
-
{
|
|
70
|
-
url,
|
|
71
|
-
error: { name: 'Error', message: 'ERR_NAME_NOT_RESOLVED' },
|
|
72
|
-
shutdown: false,
|
|
73
|
-
pid: 5678,
|
|
74
|
-
},
|
|
75
|
-
linkList,
|
|
76
|
-
scope,
|
|
77
|
-
defaultOptions,
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
expect(link).not.toBeNull();
|
|
81
|
-
expect(link!.url.href).toBe(url.href);
|
|
82
|
-
expect(result).toBeDefined();
|
|
83
|
-
expect(result!.status).toBe(-1);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it('returns null link when url is null', () => {
|
|
87
|
-
const linkList = new LinkList();
|
|
88
|
-
const scope = createScope();
|
|
89
|
-
|
|
90
|
-
const { link, result } = handleScrapeError(
|
|
91
|
-
{
|
|
92
|
-
url: null,
|
|
93
|
-
error: { name: 'Error', message: 'Unknown error' },
|
|
94
|
-
shutdown: true,
|
|
95
|
-
pid: undefined,
|
|
96
|
-
},
|
|
97
|
-
linkList,
|
|
98
|
-
scope,
|
|
99
|
-
defaultOptions,
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
expect(link).toBeNull();
|
|
103
|
-
expect(result).toBeUndefined();
|
|
104
|
-
});
|
|
105
|
-
});
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import type LinkList from './link-list.js';
|
|
2
|
-
import type { CrawlerOptions } from './types.js';
|
|
3
|
-
import type { Link, PageData } from '../utils/index.js';
|
|
4
|
-
import type { ExURL } from '@d-zero/shared/parse-url';
|
|
5
|
-
|
|
6
|
-
import { crawlerErrorLog } from '../debug.js';
|
|
7
|
-
|
|
8
|
-
import { linkToPageData } from './link-to-page-data.js';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Handle an error that occurred during page scraping.
|
|
12
|
-
*
|
|
13
|
-
* Marks the URL as done and creates a fallback {@link PageData} from the
|
|
14
|
-
* link, regardless of whether the error caused a shutdown. This ensures
|
|
15
|
-
* that errored URLs are recorded in the DB (`status = -1, scraped = 1`)
|
|
16
|
-
* and not re-queued on resume.
|
|
17
|
-
* @param payload - The error payload from the scraper.
|
|
18
|
-
* @param payload.url - The URL being scraped when the error occurred, or `null`.
|
|
19
|
-
* @param payload.error - The error details including name, message, and optional stack.
|
|
20
|
-
* @param payload.error.name
|
|
21
|
-
* @param payload.error.message
|
|
22
|
-
* @param payload.error.stack
|
|
23
|
-
* @param payload.shutdown - Whether the error caused the scraper process to shut down.
|
|
24
|
-
* @param payload.pid - The process ID of the scraper, or `undefined`.
|
|
25
|
-
* @param linkList - The link list managing the crawl queue.
|
|
26
|
-
* @param scope - Map of hostnames to their scope URLs.
|
|
27
|
-
* @param options - Crawler configuration options.
|
|
28
|
-
* @returns An object with the link and an optional fallback PageData result.
|
|
29
|
-
*/
|
|
30
|
-
export function handleScrapeError(
|
|
31
|
-
payload: {
|
|
32
|
-
url: ExURL | null;
|
|
33
|
-
error: { name: string; message: string; stack?: string };
|
|
34
|
-
shutdown: boolean;
|
|
35
|
-
pid: number | undefined;
|
|
36
|
-
},
|
|
37
|
-
linkList: LinkList,
|
|
38
|
-
scope: ReadonlyMap<string, readonly ExURL[]>,
|
|
39
|
-
options: CrawlerOptions,
|
|
40
|
-
): { link: Link | null; result?: PageData } {
|
|
41
|
-
const { url, error, shutdown, pid } = payload;
|
|
42
|
-
let link: Link | null = null;
|
|
43
|
-
let result: PageData | undefined;
|
|
44
|
-
|
|
45
|
-
if (url) {
|
|
46
|
-
const updated = linkList.done(url, scope, { error }, options);
|
|
47
|
-
if (updated) {
|
|
48
|
-
link = updated;
|
|
49
|
-
result = linkToPageData(updated);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
crawlerErrorLog('From %d(%s)', pid, url?.href ?? 'UNKNOWN_URL');
|
|
54
|
-
crawlerErrorLog('Then shutdown?: %s', shutdown ? 'Yes' : 'No');
|
|
55
|
-
crawlerErrorLog('%O', error);
|
|
56
|
-
|
|
57
|
-
return { link, result };
|
|
58
|
-
}
|
package/src/crawler/index.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import type { ExURL } from '@d-zero/shared/parse-url';
|
|
2
|
-
|
|
3
|
-
import { tryParseUrl as parseUrl } from '@d-zero/shared/parse-url';
|
|
4
|
-
import { describe, it, expect } from 'vitest';
|
|
5
|
-
|
|
6
|
-
import { injectScopeAuth } from './inject-scope-auth.js';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Create a scope map from hostname-URL pairs for testing.
|
|
10
|
-
* @param entries - Array of [hostname, urls] tuples.
|
|
11
|
-
* @returns A map from hostname to parsed ExURL arrays.
|
|
12
|
-
*/
|
|
13
|
-
function createScope(entries: [string, string[]][]): Map<string, ExURL[]> {
|
|
14
|
-
return new Map(
|
|
15
|
-
entries.map(([h, urls]) => [h, urls.map((u) => parseUrl(u)!).filter(Boolean)]),
|
|
16
|
-
);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
describe('injectScopeAuth', () => {
|
|
20
|
-
it('injects auth from matching scope URL', () => {
|
|
21
|
-
const url = parseUrl('https://example.com/blog/post')!;
|
|
22
|
-
const scope = createScope([['example.com', ['https://user:pass@example.com/blog']]]);
|
|
23
|
-
injectScopeAuth(url, scope);
|
|
24
|
-
expect(url.username).toBe('user');
|
|
25
|
-
expect(url.password).toBe('pass');
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('does not inject auth when hostname does not match', () => {
|
|
29
|
-
const url = parseUrl('https://other.com/page')!;
|
|
30
|
-
const scope = createScope([['example.com', ['https://user:pass@example.com/']]]);
|
|
31
|
-
injectScopeAuth(url, scope);
|
|
32
|
-
// username/password are null for URLs parsed without auth
|
|
33
|
-
expect(url.username).toBeNull();
|
|
34
|
-
expect(url.password).toBeNull();
|
|
35
|
-
});
|
|
36
|
-
});
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import type { ExURL } from '@d-zero/shared/parse-url';
|
|
2
|
-
|
|
3
|
-
import { findBestMatchingScope } from './find-best-matching-scope.js';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Inject authentication credentials from a matching scope URL into the target URL.
|
|
7
|
-
*
|
|
8
|
-
* Finds the best-matching scope URL (deepest path match) for the given URL's
|
|
9
|
-
* hostname and copies its `username` and `password` properties. This mutates
|
|
10
|
-
* the `url` parameter in place.
|
|
11
|
-
* @param url - The parsed URL to receive authentication credentials (mutated in place).
|
|
12
|
-
* @param scope - Map of hostnames to their scope URLs.
|
|
13
|
-
*/
|
|
14
|
-
export function injectScopeAuth(
|
|
15
|
-
url: ExURL,
|
|
16
|
-
scope: ReadonlyMap<string, readonly ExURL[]>,
|
|
17
|
-
): void {
|
|
18
|
-
const scopes = scope.get(url.hostname);
|
|
19
|
-
if (!scopes) {
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
const matchedScope = findBestMatchingScope(url, scopes);
|
|
23
|
-
if (matchedScope) {
|
|
24
|
-
url.username = matchedScope.username;
|
|
25
|
-
url.password = matchedScope.password;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import type { ExURL } from '@d-zero/shared/parse-url';
|
|
2
|
-
|
|
3
|
-
import { tryParseUrl as parseUrl } from '@d-zero/shared/parse-url';
|
|
4
|
-
import { describe, it, expect } from 'vitest';
|
|
5
|
-
|
|
6
|
-
import { isExternalUrl } from './is-external-url.js';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Create a scope map from hostname-URL pairs for testing.
|
|
10
|
-
* @param entries - Array of [hostname, urls] tuples.
|
|
11
|
-
* @returns A map from hostname to parsed ExURL arrays.
|
|
12
|
-
*/
|
|
13
|
-
function createScope(entries: [string, string[]][]): Map<string, ExURL[]> {
|
|
14
|
-
return new Map(
|
|
15
|
-
entries.map(([h, urls]) => [h, urls.map((u) => parseUrl(u)!).filter(Boolean)]),
|
|
16
|
-
);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
describe('isExternalUrl', () => {
|
|
20
|
-
it('returns false when hostname is in scope', () => {
|
|
21
|
-
const url = parseUrl('https://example.com/page')!;
|
|
22
|
-
const scope = createScope([['example.com', ['https://example.com/']]]);
|
|
23
|
-
expect(isExternalUrl(url, scope)).toBe(false);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('returns true when hostname is not in scope', () => {
|
|
27
|
-
const url = parseUrl('https://other.com/page')!;
|
|
28
|
-
const scope = createScope([['example.com', ['https://example.com/']]]);
|
|
29
|
-
expect(isExternalUrl(url, scope)).toBe(true);
|
|
30
|
-
});
|
|
31
|
-
});
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { ExURL } from '@d-zero/shared/parse-url';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Determine whether a URL is external to the crawl scope.
|
|
5
|
-
*
|
|
6
|
-
* A URL is considered external if its hostname does not appear
|
|
7
|
-
* as a key in the scope map.
|
|
8
|
-
* @param url - The parsed URL to check.
|
|
9
|
-
* @param scope - Map of hostnames to their scope URLs.
|
|
10
|
-
* @returns `true` if the URL is outside the crawl scope.
|
|
11
|
-
*/
|
|
12
|
-
export function isExternalUrl(
|
|
13
|
-
url: ExURL,
|
|
14
|
-
scope: ReadonlyMap<string, readonly ExURL[]>,
|
|
15
|
-
): boolean {
|
|
16
|
-
return !scope.has(url.hostname);
|
|
17
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import type { ParseURLOptions } from '@d-zero/shared/parse-url';
|
|
2
|
-
|
|
3
|
-
import { tryParseUrl as parseUrl } from '@d-zero/shared/parse-url';
|
|
4
|
-
import { describe, it, expect } from 'vitest';
|
|
5
|
-
|
|
6
|
-
import { isInAnyLowerLayer } from './is-in-any-lower-layer.js';
|
|
7
|
-
|
|
8
|
-
const defaultOptions: ParseURLOptions = {};
|
|
9
|
-
|
|
10
|
-
describe('isInAnyLowerLayer', () => {
|
|
11
|
-
it('returns true when URL is in a lower layer of a scope', () => {
|
|
12
|
-
const url = parseUrl('https://example.com/blog/post')!;
|
|
13
|
-
const scopes = [parseUrl('https://example.com/blog/')!];
|
|
14
|
-
expect(isInAnyLowerLayer(url, scopes, defaultOptions)).toBe(true);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('returns false when URL is not in a lower layer', () => {
|
|
18
|
-
const url = parseUrl('https://example.com/about')!;
|
|
19
|
-
const scopes = [parseUrl('https://example.com/blog/')!];
|
|
20
|
-
expect(isInAnyLowerLayer(url, scopes, defaultOptions)).toBe(false);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('returns true when URL matches one of multiple scopes', () => {
|
|
24
|
-
const url = parseUrl('https://example.com/docs/api')!;
|
|
25
|
-
const scopes = [
|
|
26
|
-
parseUrl('https://example.com/blog/')!,
|
|
27
|
-
parseUrl('https://example.com/docs/')!,
|
|
28
|
-
];
|
|
29
|
-
expect(isInAnyLowerLayer(url, scopes, defaultOptions)).toBe(true);
|
|
30
|
-
});
|
|
31
|
-
});
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import type { ExURL, ParseURLOptions } from '@d-zero/shared/parse-url';
|
|
2
|
-
|
|
3
|
-
import { isLowerLayer } from '@d-zero/shared/is-lower-layer';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Check whether a URL is in a lower layer (subdirectory) of any scope URL.
|
|
7
|
-
*
|
|
8
|
-
* Tests the URL against each scope URL using the `isLowerLayer` utility,
|
|
9
|
-
* which checks if the URL's path is at the same level or deeper than
|
|
10
|
-
* the scope URL's path.
|
|
11
|
-
* @param url - The parsed URL to check.
|
|
12
|
-
* @param scopes - The list of scope URLs to test against.
|
|
13
|
-
* @param options - URL parsing options used for layer comparison.
|
|
14
|
-
* @returns `true` if the URL is in a lower layer of at least one scope URL.
|
|
15
|
-
*/
|
|
16
|
-
export function isInAnyLowerLayer(
|
|
17
|
-
url: ExURL,
|
|
18
|
-
scopes: readonly ExURL[],
|
|
19
|
-
options: ParseURLOptions,
|
|
20
|
-
): boolean {
|
|
21
|
-
return scopes.some((scope) => isLowerLayer(url.href, scope.href, options));
|
|
22
|
-
}
|