@nitpicker/crawler 0.6.3 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/crawler/fetch-destination.js +11 -5
- package/package.json +2 -2
- package/lib/archive/common-queries.d.ts +0 -14
- package/lib/archive/common-queries.js +0 -40
- package/lib/archive/filesystem/index.d.ts +0 -17
- package/lib/archive/filesystem/index.js +0 -17
- package/lib/archive/filesystem/utils.d.ts +0 -109
- package/lib/archive/filesystem/utils.js +0 -185
- package/lib/archive/filesystem/zip.d.ts +0 -29
- package/lib/archive/filesystem/zip.js +0 -53
- package/lib/archive/index.d.ts +0 -6
- package/lib/archive/index.js +0 -11
- package/lib/crawler/index.d.ts +0 -2
- package/lib/crawler/index.js +0 -2
- package/lib/crawler/network.d.ts +0 -30
- package/lib/crawler/network.js +0 -226
- package/lib/crawler/result-handler.d.ts +0 -118
- package/lib/crawler/result-handler.js +0 -153
- package/lib/crawler/speculative-pagination.d.ts +0 -52
- package/lib/crawler/speculative-pagination.js +0 -215
- package/lib/crawler/url-filter.d.ts +0 -56
- package/lib/crawler/url-filter.js +0 -110
- package/lib/index.d.ts +0 -16
- package/lib/index.js +0 -18
- package/lib/qzilla.d.ts +0 -136
- package/lib/qzilla.js +0 -292
- package/lib/utils/array/index.d.ts +0 -1
- package/lib/utils/array/index.js +0 -1
- package/lib/utils/async/index.d.ts +0 -1
- package/lib/utils/async/index.js +0 -1
- package/lib/utils/error/index.d.ts +0 -3
- package/lib/utils/error/index.js +0 -2
- package/lib/utils/event-emitter/index.d.ts +0 -6
- package/lib/utils/event-emitter/index.js +0 -6
- package/lib/utils/index.d.ts +0 -5
- package/lib/utils/index.js +0 -5
- package/lib/utils/network/index.d.ts +0 -1
- package/lib/utils/network/index.js +0 -1
- package/lib/utils/object/index.d.ts +0 -1
- package/lib/utils/object/index.js +0 -1
- package/lib/utils/path/index.d.ts +0 -1
- package/lib/utils/path/index.js +0 -1
- package/lib/utils/path/safe-filepath.d.ts +0 -7
- package/lib/utils/path/safe-filepath.js +0 -12
- package/lib/utils/regexp/index.d.ts +0 -1
- package/lib/utils/regexp/index.js +0 -1
- package/lib/utils/retryable/index.d.ts +0 -2
- package/lib/utils/retryable/index.js +0 -1
- package/lib/utils/sort/index.d.ts +0 -14
- package/lib/utils/sort/index.js +0 -61
- package/lib/utils/sort/remove-matches.d.ts +0 -9
- package/lib/utils/sort/remove-matches.js +0 -23
- package/lib/utils/types/index.d.ts +0 -1
- package/lib/utils/types/index.js +0 -1
- package/lib/utils/url/index.d.ts +0 -5
- package/lib/utils/url/index.js +0 -5
- package/lib/utils/url/is-lower-layer.d.ts +0 -15
- package/lib/utils/url/is-lower-layer.js +0 -55
- package/lib/utils/url/parse-url.d.ts +0 -11
- package/lib/utils/url/parse-url.js +0 -20
- package/lib/utils/url/path-match.d.ts +0 -11
- package/lib/utils/url/path-match.js +0 -18
- package/lib/utils/url/sort-url.d.ts +0 -10
- package/lib/utils/url/sort-url.js +0 -24
- package/lib/utils/url/url-partial-match.d.ts +0 -11
- package/lib/utils/url/url-partial-match.js +0 -32
package/lib/qzilla.js
DELETED
|
@@ -1,292 +0,0 @@
|
|
|
1
|
-
import Archive from './archive/archive.js';
|
|
2
|
-
import { cleanObject, EventEmitter, parseUrl, sortUrl } from './utils/index.js';
|
|
3
|
-
import pkg from '../package.json' with { type: 'json' };
|
|
4
|
-
import { Crawler } from './crawler/index.js';
|
|
5
|
-
import { crawlerLog, log } from './debug.js';
|
|
6
|
-
/**
|
|
7
|
-
* Default list of external URL prefixes excluded from crawling.
|
|
8
|
-
* Includes social media sharing endpoints that are commonly linked
|
|
9
|
-
* but provide no useful crawl data.
|
|
10
|
-
*/
|
|
11
|
-
export const DEFAULT_EXCLUDED_EXTERNAL_URLS = [
|
|
12
|
-
'https://social-plugins.line.me',
|
|
13
|
-
'https://access.line.me',
|
|
14
|
-
'https://lineit.line.me',
|
|
15
|
-
'https://line.me',
|
|
16
|
-
'https://plus.google.com',
|
|
17
|
-
'https://twitter.com',
|
|
18
|
-
'https://x.com',
|
|
19
|
-
'https://www.facebook.com/share.php',
|
|
20
|
-
'https://www.facebook.com/share/',
|
|
21
|
-
'https://www.facebook.com/sharer/',
|
|
22
|
-
'https://www.facebook.com/share_channel/',
|
|
23
|
-
'https://www.google.com',
|
|
24
|
-
];
|
|
25
|
-
/**
|
|
26
|
-
* The main entry point for Qzilla web crawling and archiving.
|
|
27
|
-
*
|
|
28
|
-
* Qzilla orchestrates the full lifecycle of a crawl session: it creates an archive,
|
|
29
|
-
* configures a {@link Crawler}, processes discovered pages and resources, and
|
|
30
|
-
* writes the final archive file. It emits events defined by {@link QzillaEvent}.
|
|
31
|
-
*
|
|
32
|
-
* Instances are created via the static factory methods {@link Qzilla.crawling}
|
|
33
|
-
* or {@link Qzilla.resume}; the constructor is private.
|
|
34
|
-
* @example
|
|
35
|
-
* ```ts
|
|
36
|
-
* const qzilla = await Qzilla.crawling(['https://example.com'], { recursive: true });
|
|
37
|
-
* await qzilla.write();
|
|
38
|
-
* ```
|
|
39
|
-
*/
|
|
40
|
-
export class Qzilla extends EventEmitter {
|
|
41
|
-
#archive;
|
|
42
|
-
#crawler;
|
|
43
|
-
#fromList;
|
|
44
|
-
/**
|
|
45
|
-
* The underlying archive instance used for storing crawl results.
|
|
46
|
-
*/
|
|
47
|
-
get archive() {
|
|
48
|
-
return this.#archive;
|
|
49
|
-
}
|
|
50
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
51
|
-
constructor(archive, options) {
|
|
52
|
-
super();
|
|
53
|
-
this.#fromList = !!options?.list;
|
|
54
|
-
this.#archive = archive;
|
|
55
|
-
this.#archive.on('error', (e) => {
|
|
56
|
-
this.#crawler.abort();
|
|
57
|
-
throw e;
|
|
58
|
-
});
|
|
59
|
-
this.#crawler = new Crawler({
|
|
60
|
-
interval: options?.interval || 0,
|
|
61
|
-
parallels: options?.parallels || 0,
|
|
62
|
-
isGettingImages: options?.image,
|
|
63
|
-
executablePath: options?.executablePath || null,
|
|
64
|
-
fetchExternal: options?.fetchExternal ?? true,
|
|
65
|
-
recursive: options?.recursive ?? true,
|
|
66
|
-
scope: options?.scope ?? [],
|
|
67
|
-
excludes: optMultiParam(options?.excludes),
|
|
68
|
-
excludeKeywords: optMultiParam(options?.excludeKeywords),
|
|
69
|
-
excludeUrls: [
|
|
70
|
-
...DEFAULT_EXCLUDED_EXTERNAL_URLS,
|
|
71
|
-
...optMultiParam(options?.excludeUrls),
|
|
72
|
-
],
|
|
73
|
-
depthOnAvoid: options?.depthOnAvoid || 10,
|
|
74
|
-
disableQueries: options?.disableQueries,
|
|
75
|
-
screenshot: Archive.joinPath(archive.tmpDir, 'screenshots'),
|
|
76
|
-
verbose: options?.verbose ?? false,
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Abort the current crawl and archive operations.
|
|
81
|
-
*
|
|
82
|
-
* Delegates to the archive's abort method, which stops all in-progress
|
|
83
|
-
* database writes and cleans up temporary resources.
|
|
84
|
-
* @returns The result of the archive abort operation.
|
|
85
|
-
*/
|
|
86
|
-
abort() {
|
|
87
|
-
return this.#archive.abort();
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Execute the crawl for the given list of URLs.
|
|
91
|
-
*
|
|
92
|
-
* Sets up event listeners on the crawler, starts crawling, and resolves
|
|
93
|
-
* when the crawl completes. Discovered pages, external pages, skipped pages,
|
|
94
|
-
* and resources are forwarded to the archive for storage.
|
|
95
|
-
* @param list - The list of parsed URLs to crawl. The first URL is used as the root.
|
|
96
|
-
* @returns A promise that resolves when crawling is complete.
|
|
97
|
-
* @throws {Error} If the URL list is empty.
|
|
98
|
-
*/
|
|
99
|
-
async crawling(list) {
|
|
100
|
-
const root = list[0];
|
|
101
|
-
if (!root) {
|
|
102
|
-
throw new Error('URL is empty');
|
|
103
|
-
}
|
|
104
|
-
return new Promise((resolve, reject) => {
|
|
105
|
-
this.#crawler.on('error', (error) => {
|
|
106
|
-
crawlerLog('On error: %O', error);
|
|
107
|
-
void this.#archive.addError(error);
|
|
108
|
-
void this.emit('error', error);
|
|
109
|
-
});
|
|
110
|
-
this.#crawler.on('page', async ({ result }) => {
|
|
111
|
-
// const pageId =
|
|
112
|
-
await this.#archive.setPage(result).catch((error) => reject(error));
|
|
113
|
-
// await this.#crawler.screenshot(pageId, Archive.joinPath(this.#archive.tmpDir, 'screenshots'));
|
|
114
|
-
});
|
|
115
|
-
this.#crawler.on('externalPage', ({ result }) => {
|
|
116
|
-
this.#archive.setExternalPage(result).catch((error) => reject(error));
|
|
117
|
-
});
|
|
118
|
-
this.#crawler.on('skip', ({ url, reason, isExternal }) => {
|
|
119
|
-
this.#archive
|
|
120
|
-
.setSkippedPage(url, reason, isExternal)
|
|
121
|
-
.catch((error) => reject(error));
|
|
122
|
-
});
|
|
123
|
-
this.#crawler.on('response', ({ resource }) => {
|
|
124
|
-
this.#archive.setResources(resource).catch((error) => reject(error));
|
|
125
|
-
});
|
|
126
|
-
this.#crawler.on('responseReferrers', (resource) => {
|
|
127
|
-
this.#archive.setResourcesReferrers(resource).catch((error) => reject(error));
|
|
128
|
-
});
|
|
129
|
-
this.#crawler.on('crawlEnd', () => {
|
|
130
|
-
resolve();
|
|
131
|
-
});
|
|
132
|
-
if (this.#fromList) {
|
|
133
|
-
this.#crawler.startMultiple(list);
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
136
|
-
this.#crawler.start(root);
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* Kill any zombie Chromium processes that were not properly cleaned up.
|
|
142
|
-
*
|
|
143
|
-
* Retrieves the list of undead process IDs from the crawler and sends
|
|
144
|
-
* a SIGTERM signal to each one. Chromium is intentionally sent SIGTERM
|
|
145
|
-
* (not SIGKILL) to avoid leaving zombie processes.
|
|
146
|
-
*/
|
|
147
|
-
garbageCollect() {
|
|
148
|
-
const pidList = this.getUndeadPid();
|
|
149
|
-
log('Undead PIDs: %O', pidList);
|
|
150
|
-
for (const pid of pidList) {
|
|
151
|
-
try {
|
|
152
|
-
log('Garbage collect: kill PID:%d', pid);
|
|
153
|
-
// Chromium becomes a zombie process if SIGKILL signal.
|
|
154
|
-
process.kill(pid);
|
|
155
|
-
}
|
|
156
|
-
catch (error) {
|
|
157
|
-
log('Garbage collect: Failed killing PID:%d %O', pid, error);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* Retrieve the list of process IDs for Chromium instances that are
|
|
163
|
-
* still running after crawling has ended.
|
|
164
|
-
* @returns An array of process IDs that should be terminated.
|
|
165
|
-
*/
|
|
166
|
-
getUndeadPid() {
|
|
167
|
-
return this.#crawler.getUndeadPid();
|
|
168
|
-
}
|
|
169
|
-
/**
|
|
170
|
-
* Write the archive to its configured file path.
|
|
171
|
-
*
|
|
172
|
-
* Emits `writeFileStart` before writing and `writeFileEnd` after
|
|
173
|
-
* the write completes successfully.
|
|
174
|
-
*/
|
|
175
|
-
async write() {
|
|
176
|
-
void this.emit('writeFileStart', { filePath: this.#archive.filePath });
|
|
177
|
-
await this.#archive.write();
|
|
178
|
-
void this.emit('writeFileEnd', { filePath: this.#archive.filePath });
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* Create a new Qzilla instance and start crawling the given URLs.
|
|
182
|
-
*
|
|
183
|
-
* This is the primary factory method for starting a fresh crawl. It:
|
|
184
|
-
* 1. Parses and sorts the input URLs
|
|
185
|
-
* 2. Creates an archive file
|
|
186
|
-
* 3. Saves the crawl configuration
|
|
187
|
-
* 4. Runs the optional initialized callback
|
|
188
|
-
* 5. Executes the crawl
|
|
189
|
-
* 6. Sorts the archived URLs in natural order
|
|
190
|
-
* @param url - One or more URL strings to crawl.
|
|
191
|
-
* @param options - Optional configuration overrides for the crawl session.
|
|
192
|
-
* @param initializedCallback - Optional callback invoked after initialization but before crawling starts.
|
|
193
|
-
* @returns A promise that resolves to the Qzilla instance after crawling completes.
|
|
194
|
-
* @throws {Error} If the URL list is empty or contains no valid URLs.
|
|
195
|
-
*/
|
|
196
|
-
static async crawling(url, options, initializedCallback) {
|
|
197
|
-
const list = sortUrl(url, options);
|
|
198
|
-
const urlParsed = list[0];
|
|
199
|
-
if (!urlParsed) {
|
|
200
|
-
throw new Error('URL is empty');
|
|
201
|
-
}
|
|
202
|
-
const fileName = `${urlParsed.hostname}-${Archive.timestamp()}`;
|
|
203
|
-
const cwd = options?.cwd ?? process.cwd();
|
|
204
|
-
const filePath = Archive.joinPath(cwd, `${fileName}.${Archive.FILE_EXTENSION}`);
|
|
205
|
-
const disableQueries = options?.disableQueries || false;
|
|
206
|
-
const archive = await Archive.create({ filePath, cwd, disableQueries });
|
|
207
|
-
await archive.setConfig({
|
|
208
|
-
version: pkg.version,
|
|
209
|
-
name: fileName,
|
|
210
|
-
baseUrl: urlParsed.withoutHash,
|
|
211
|
-
recursive: options?.recursive ?? true,
|
|
212
|
-
fetchExternal: options?.fetchExternal ?? true,
|
|
213
|
-
image: options?.image ?? true,
|
|
214
|
-
interval: options?.interval || 0,
|
|
215
|
-
parallels: options?.parallels || 0,
|
|
216
|
-
scope: options?.scope ?? [],
|
|
217
|
-
// @ts-ignore TODO: Fix CLI arguments
|
|
218
|
-
excludes: optMultiParam(options?.exclude),
|
|
219
|
-
// @ts-ignore TODO: Fix CLI arguments
|
|
220
|
-
excludeKeywords: optMultiParam(options?.excludeKeyword),
|
|
221
|
-
excludeUrls: [
|
|
222
|
-
...DEFAULT_EXCLUDED_EXTERNAL_URLS,
|
|
223
|
-
// @ts-ignore TODO: Fix CLI arguments
|
|
224
|
-
...optMultiParam(options?.excludeUrl),
|
|
225
|
-
],
|
|
226
|
-
depthOnAvoid: options?.depthOnAvoid || 10,
|
|
227
|
-
fromList: !!options?.list,
|
|
228
|
-
disableQueries,
|
|
229
|
-
});
|
|
230
|
-
const qzilla = new Qzilla(archive, options);
|
|
231
|
-
const config = await archive.getConfig();
|
|
232
|
-
if (initializedCallback) {
|
|
233
|
-
await initializedCallback(qzilla, config);
|
|
234
|
-
}
|
|
235
|
-
log('Start crawling');
|
|
236
|
-
log('URL %O', list.map((url) => url.href));
|
|
237
|
-
log('Config %O', config);
|
|
238
|
-
await qzilla.crawling(list);
|
|
239
|
-
log('Crawling complated');
|
|
240
|
-
log('Set order natural URL sort');
|
|
241
|
-
await archive.setUrlOrder();
|
|
242
|
-
log('Sorting done');
|
|
243
|
-
return qzilla;
|
|
244
|
-
}
|
|
245
|
-
/**
|
|
246
|
-
* Resume a previously interrupted crawl from an existing archive file.
|
|
247
|
-
*
|
|
248
|
-
* Restores the crawl state (pending URLs, scraped URLs, and resources)
|
|
249
|
-
* from the archive, merges any option overrides, and continues crawling
|
|
250
|
-
* from where it left off.
|
|
251
|
-
* @param stubPath - Path to the existing archive file to resume from.
|
|
252
|
-
* @param options - Optional configuration overrides to apply on top of the archived config.
|
|
253
|
-
* @param initializedCallback - Optional callback invoked after initialization but before crawling resumes.
|
|
254
|
-
* @returns A promise that resolves to the Qzilla instance after crawling completes.
|
|
255
|
-
* @throws {Error} If the archived URL is invalid.
|
|
256
|
-
*/
|
|
257
|
-
static async resume(stubPath, options, initializedCallback) {
|
|
258
|
-
const archive = await Archive.resume(stubPath);
|
|
259
|
-
const archivedConfig = await archive.getConfig();
|
|
260
|
-
const config = {
|
|
261
|
-
...archivedConfig,
|
|
262
|
-
...cleanObject(options),
|
|
263
|
-
};
|
|
264
|
-
const qzilla = new Qzilla(archive, config);
|
|
265
|
-
const _url = await archive.getUrl();
|
|
266
|
-
const url = parseUrl(_url, config);
|
|
267
|
-
if (!url) {
|
|
268
|
-
throw new Error(`URL (${_url}) is invalid`);
|
|
269
|
-
}
|
|
270
|
-
const { scraped, pending } = await archive.getCrawlingState();
|
|
271
|
-
const resources = await archive.getResourceUrlList();
|
|
272
|
-
qzilla.#crawler.resume(pending, scraped, resources);
|
|
273
|
-
if (initializedCallback) {
|
|
274
|
-
await initializedCallback(qzilla, config);
|
|
275
|
-
}
|
|
276
|
-
log('Start resuming');
|
|
277
|
-
log('Data %s', stubPath);
|
|
278
|
-
log('URL %s', url.href);
|
|
279
|
-
log('Config %O', config);
|
|
280
|
-
await qzilla.crawling([url]);
|
|
281
|
-
return qzilla;
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
/**
|
|
285
|
-
* Normalize an optional parameter that may be a single value, an array,
|
|
286
|
-
* null, or undefined into a guaranteed array.
|
|
287
|
-
* @param param - The parameter to normalize.
|
|
288
|
-
* @returns An array containing the parameter value(s), or an empty array if absent.
|
|
289
|
-
*/
|
|
290
|
-
function optMultiParam(param) {
|
|
291
|
-
return Array.isArray(param) ? param : param ? [param] : [];
|
|
292
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { eachSplitted } from './each-splitted.js';
|
package/lib/utils/array/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { eachSplitted } from './each-splitted.js';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { delay } from '@d-zero/shared/delay';
|
package/lib/utils/async/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { delay } from '@d-zero/shared/delay';
|
package/lib/utils/error/index.js
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A typed, async-capable event emitter re-exported from `@d-zero/shared`.
|
|
3
|
-
* Provides type-safe `emit`, `on`, and `off` methods where event names
|
|
4
|
-
* and their payload types are enforced at compile time.
|
|
5
|
-
*/
|
|
6
|
-
export { TypedAwaitEventEmitter as EventEmitter } from '@d-zero/shared/typed-await-event-emitter';
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A typed, async-capable event emitter re-exported from `@d-zero/shared`.
|
|
3
|
-
* Provides type-safe `emit`, `on`, and `off` methods where event names
|
|
4
|
-
* and their payload types are enforced at compile time.
|
|
5
|
-
*/
|
|
6
|
-
export { TypedAwaitEventEmitter as EventEmitter } from '@d-zero/shared/typed-await-event-emitter';
|
package/lib/utils/index.d.ts
DELETED
package/lib/utils/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { isError } from '@nitpicker/beholder';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { isError } from '@nitpicker/beholder';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './clean-object.js';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './clean-object.js';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { safeFilePath } from '@d-zero/shared/safe-filepath';
|
package/lib/utils/path/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { safeFilePath } from '@d-zero/shared/safe-filepath';
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sanitizes a file path by decoding URI-encoded characters and replacing
|
|
3
|
-
* any characters that are unsafe for use in filenames with underscores.
|
|
4
|
-
* @param filePath - The raw file path string (possibly URI-encoded) to sanitize.
|
|
5
|
-
* @returns A sanitized file path string that is safe for use as a filename.
|
|
6
|
-
*/
|
|
7
|
-
export declare function safeFilePath(filePath: string): string;
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import sanitize from 'sanitize-filename';
|
|
2
|
-
/**
|
|
3
|
-
* Sanitizes a file path by decoding URI-encoded characters and replacing
|
|
4
|
-
* any characters that are unsafe for use in filenames with underscores.
|
|
5
|
-
* @param filePath - The raw file path string (possibly URI-encoded) to sanitize.
|
|
6
|
-
* @returns A sanitized file path string that is safe for use as a filename.
|
|
7
|
-
*/
|
|
8
|
-
export function safeFilePath(filePath) {
|
|
9
|
-
return sanitize(decodeURI(filePath), {
|
|
10
|
-
replacement: '_',
|
|
11
|
-
});
|
|
12
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { strToRegex } from '@d-zero/shared/str-to-regex';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { strToRegex } from '@d-zero/shared/str-to-regex';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { retry as retryable } from '@d-zero/shared/retry';
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { ExURL, ParseURLOptions } from '@d-zero/shared/parse-url';
|
|
2
|
-
/**
|
|
3
|
-
* Compares two URLs using a natural sorting algorithm. The comparison order is:
|
|
4
|
-
* hostname, path directories, basename (with index files prioritized), file extension,
|
|
5
|
-
* query string, hash, protocol, and finally the original URL string as a tiebreaker.
|
|
6
|
-
* Numeric segments within path components are compared numerically rather than
|
|
7
|
-
* lexicographically.
|
|
8
|
-
* @param url1 - The first URL string or ExURL to compare.
|
|
9
|
-
* @param url2 - The second URL string or ExURL to compare.
|
|
10
|
-
* @param options - Optional URL parsing options.
|
|
11
|
-
* @returns `0` if the URLs are equal, `-1` if url1 should come before url2,
|
|
12
|
-
* or `1` if url1 should come after url2.
|
|
13
|
-
*/
|
|
14
|
-
export declare function naturalURLSort(url1: string | ExURL, url2: string | ExURL, options?: ParseURLOptions): 0 | -1 | 1;
|
package/lib/utils/sort/index.js
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { alphabeticalComparator } from '@d-zero/shared/sort/alphabetical';
|
|
2
|
-
import { dirComparator } from '@d-zero/shared/sort/dir';
|
|
3
|
-
import { numericalComparator } from '@d-zero/shared/sort/numerical';
|
|
4
|
-
import { parseUrl } from '../url/index.js';
|
|
5
|
-
/**
|
|
6
|
-
* Compares two URLs using a natural sorting algorithm. The comparison order is:
|
|
7
|
-
* hostname, path directories, basename (with index files prioritized), file extension,
|
|
8
|
-
* query string, hash, protocol, and finally the original URL string as a tiebreaker.
|
|
9
|
-
* Numeric segments within path components are compared numerically rather than
|
|
10
|
-
* lexicographically.
|
|
11
|
-
* @param url1 - The first URL string or ExURL to compare.
|
|
12
|
-
* @param url2 - The second URL string or ExURL to compare.
|
|
13
|
-
* @param options - Optional URL parsing options.
|
|
14
|
-
* @returns `0` if the URLs are equal, `-1` if url1 should come before url2,
|
|
15
|
-
* or `1` if url1 should come after url2.
|
|
16
|
-
*/
|
|
17
|
-
export function naturalURLSort(url1, url2, options) {
|
|
18
|
-
const u1 = typeof url1 === 'string' ? parseUrl(url1, options) : url1;
|
|
19
|
-
const u2 = typeof url2 === 'string' ? parseUrl(url2, options) : url2;
|
|
20
|
-
if (!u1 || !u2) {
|
|
21
|
-
return 0;
|
|
22
|
-
}
|
|
23
|
-
if (u1.href === u2.href) {
|
|
24
|
-
return alphabeticalComparator(u1._originUrlString, u2._originUrlString);
|
|
25
|
-
}
|
|
26
|
-
const rHost = alphabeticalComparator(u1.hostname, u2.hostname);
|
|
27
|
-
if (rHost) {
|
|
28
|
-
return rHost;
|
|
29
|
-
}
|
|
30
|
-
const rPaths = dirComparator(u1.paths, u2.paths);
|
|
31
|
-
if (rPaths) {
|
|
32
|
-
return rPaths;
|
|
33
|
-
}
|
|
34
|
-
if (u1.basename !== u2.basename) {
|
|
35
|
-
if (u1.isIndex)
|
|
36
|
-
return -1;
|
|
37
|
-
if (u2.isIndex)
|
|
38
|
-
return 1;
|
|
39
|
-
const rBasename = numericalComparator(u1.basename, u2.basename);
|
|
40
|
-
if (rBasename) {
|
|
41
|
-
return rBasename;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
const rExtname = numericalComparator(u1.extname, u2.extname);
|
|
45
|
-
if (rExtname) {
|
|
46
|
-
return rExtname;
|
|
47
|
-
}
|
|
48
|
-
const rSearch = numericalComparator(u1.query, u2.query);
|
|
49
|
-
if (rSearch) {
|
|
50
|
-
return rSearch;
|
|
51
|
-
}
|
|
52
|
-
const rHash = numericalComparator(u1.hash, u2.hash);
|
|
53
|
-
if (rHash) {
|
|
54
|
-
return rHash;
|
|
55
|
-
}
|
|
56
|
-
const rProtocol = alphabeticalComparator(u1.protocol, u2.protocol);
|
|
57
|
-
if (rProtocol) {
|
|
58
|
-
return rProtocol;
|
|
59
|
-
}
|
|
60
|
-
return numericalComparator(u1._originUrlString, u2._originUrlString);
|
|
61
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Strips the common leading prefix from two strings (case-insensitive comparison).
|
|
3
|
-
* Returns a tuple of the remaining suffixes after the shared prefix is removed.
|
|
4
|
-
* If the strings are identical (ignoring case), returns `['', '']`.
|
|
5
|
-
* @param t1 - The first string.
|
|
6
|
-
* @param t2 - The second string.
|
|
7
|
-
* @returns A tuple of the two strings with their common leading characters removed.
|
|
8
|
-
*/
|
|
9
|
-
export declare function removeMatches(t1: string, t2: string): [string, string];
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Strips the common leading prefix from two strings (case-insensitive comparison).
|
|
3
|
-
* Returns a tuple of the remaining suffixes after the shared prefix is removed.
|
|
4
|
-
* If the strings are identical (ignoring case), returns `['', '']`.
|
|
5
|
-
* @param t1 - The first string.
|
|
6
|
-
* @param t2 - The second string.
|
|
7
|
-
* @returns A tuple of the two strings with their common leading characters removed.
|
|
8
|
-
*/
|
|
9
|
-
export function removeMatches(t1, t2) {
|
|
10
|
-
let loopCount = Math.max(t1.length, t2.length);
|
|
11
|
-
t1 = t1.toLowerCase();
|
|
12
|
-
t2 = t2.toLowerCase();
|
|
13
|
-
const a1 = [...t1];
|
|
14
|
-
const a2 = [...t2];
|
|
15
|
-
while (loopCount--) {
|
|
16
|
-
if (a1[0] !== a2[0]) {
|
|
17
|
-
return [a1.join(''), a2.join('')];
|
|
18
|
-
}
|
|
19
|
-
a1.shift();
|
|
20
|
-
a2.shift();
|
|
21
|
-
}
|
|
22
|
-
return ['', ''];
|
|
23
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './types.js';
|
package/lib/utils/types/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './types.js';
|
package/lib/utils/url/index.d.ts
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export { tryParseUrl as parseUrl } from '@d-zero/shared/parse-url';
|
|
2
|
-
export { isLowerLayer } from '@d-zero/shared/is-lower-layer';
|
|
3
|
-
export { pathMatch } from '@d-zero/shared/path-match';
|
|
4
|
-
export { urlPartialMatch } from '@d-zero/shared/url-partial-match';
|
|
5
|
-
export { sortUrl } from '@d-zero/shared/sort-url';
|
package/lib/utils/url/index.js
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export { tryParseUrl as parseUrl } from '@d-zero/shared/parse-url';
|
|
2
|
-
export { isLowerLayer } from '@d-zero/shared/is-lower-layer';
|
|
3
|
-
export { pathMatch } from '@d-zero/shared/path-match';
|
|
4
|
-
export { urlPartialMatch } from '@d-zero/shared/url-partial-match';
|
|
5
|
-
export { sortUrl } from '@d-zero/shared/sort-url';
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { ExURL, ParseURLOptions } from '@d-zero/shared/parse-url';
|
|
2
|
-
/**
|
|
3
|
-
* Determines whether the target URL is at the same level or a deeper (lower) layer
|
|
4
|
-
* in the path hierarchy relative to the base URL. Both URLs must share the same hostname.
|
|
5
|
-
*
|
|
6
|
-
* For example, if the base is `https://example.com/docs/`, then
|
|
7
|
-
* `https://example.com/docs/getting-started` is considered a lower layer,
|
|
8
|
-
* while `https://example.com/about` is not.
|
|
9
|
-
* @param target - The target URL string or ExURL to check.
|
|
10
|
-
* @param base - The base URL string or ExURL to compare against.
|
|
11
|
-
* @param options - Optional URL parsing options.
|
|
12
|
-
* @returns `true` if the target URL is at the same level or deeper than the base URL
|
|
13
|
-
* within the same hostname; `false` otherwise.
|
|
14
|
-
*/
|
|
15
|
-
export declare function isLowerLayer(target: string | ExURL, base: string | ExURL, options?: ParseURLOptions): boolean;
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { parseUrl } from './parse-url.js';
|
|
2
|
-
/**
|
|
3
|
-
* Determines whether the target URL is at the same level or a deeper (lower) layer
|
|
4
|
-
* in the path hierarchy relative to the base URL. Both URLs must share the same hostname.
|
|
5
|
-
*
|
|
6
|
-
* For example, if the base is `https://example.com/docs/`, then
|
|
7
|
-
* `https://example.com/docs/getting-started` is considered a lower layer,
|
|
8
|
-
* while `https://example.com/about` is not.
|
|
9
|
-
* @param target - The target URL string or ExURL to check.
|
|
10
|
-
* @param base - The base URL string or ExURL to compare against.
|
|
11
|
-
* @param options - Optional URL parsing options.
|
|
12
|
-
* @returns `true` if the target URL is at the same level or deeper than the base URL
|
|
13
|
-
* within the same hostname; `false` otherwise.
|
|
14
|
-
*/
|
|
15
|
-
export function isLowerLayer(target, base, options) {
|
|
16
|
-
const a = typeof target === 'string' ? parseUrl(target, options) : target;
|
|
17
|
-
const b = typeof base === 'string' ? parseUrl(base, options) : base;
|
|
18
|
-
if (!a || !b) {
|
|
19
|
-
return false;
|
|
20
|
-
}
|
|
21
|
-
if (a.href === b.href) {
|
|
22
|
-
return true;
|
|
23
|
-
}
|
|
24
|
-
const aPathIsEmpty = a.paths.length === 1 && a.paths[0] === '';
|
|
25
|
-
const bPathIsEmpty = b.paths.length === 1 && b.paths[0] === '';
|
|
26
|
-
if (a.hostname !== b.hostname) {
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
if (aPathIsEmpty && bPathIsEmpty) {
|
|
30
|
-
return true;
|
|
31
|
-
}
|
|
32
|
-
if (a.paths == null && b.paths == null) {
|
|
33
|
-
return true;
|
|
34
|
-
}
|
|
35
|
-
if (a.paths && b.paths == null) {
|
|
36
|
-
return true;
|
|
37
|
-
}
|
|
38
|
-
if (!a.paths || !b.paths) {
|
|
39
|
-
return false;
|
|
40
|
-
}
|
|
41
|
-
if (a.paths.length < b.paths.length) {
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
while (Math.max(a.paths.length, b.paths.length)) {
|
|
45
|
-
const i1 = a.paths.shift();
|
|
46
|
-
const i2 = b.paths.shift();
|
|
47
|
-
if (i1 && !i2) {
|
|
48
|
-
return true;
|
|
49
|
-
}
|
|
50
|
-
if (i1 !== i2) {
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { ExURL, ParseURLOptions } from '@d-zero/shared/parse-url';
|
|
2
|
-
/**
|
|
3
|
-
* Parses a URL string into an ExURL object, filtering out non-HTTP URLs
|
|
4
|
-
* that lack a hostname and protocol. If the input is already an ExURL object,
|
|
5
|
-
* it is returned as-is without re-parsing.
|
|
6
|
-
* @param url - The URL string to parse, or an already-parsed ExURL object.
|
|
7
|
-
* @param options - Optional parsing options forwarded to the underlying parser.
|
|
8
|
-
* @returns The parsed ExURL object, or `null` if the URL is not a valid HTTP URL
|
|
9
|
-
* and has no hostname or protocol.
|
|
10
|
-
*/
|
|
11
|
-
export declare function parseUrl(url: string | ExURL, options?: ParseURLOptions): ExURL | null;
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { parseUrl as sharedParseUrl } from '@d-zero/shared/parse-url';
|
|
2
|
-
/**
|
|
3
|
-
* Parses a URL string into an ExURL object, filtering out non-HTTP URLs
|
|
4
|
-
* that lack a hostname and protocol. If the input is already an ExURL object,
|
|
5
|
-
* it is returned as-is without re-parsing.
|
|
6
|
-
* @param url - The URL string to parse, or an already-parsed ExURL object.
|
|
7
|
-
* @param options - Optional parsing options forwarded to the underlying parser.
|
|
8
|
-
* @returns The parsed ExURL object, or `null` if the URL is not a valid HTTP URL
|
|
9
|
-
* and has no hostname or protocol.
|
|
10
|
-
*/
|
|
11
|
-
export function parseUrl(url, options) {
|
|
12
|
-
if (typeof url !== 'string') {
|
|
13
|
-
return url;
|
|
14
|
-
}
|
|
15
|
-
const result = sharedParseUrl(url, options);
|
|
16
|
-
if (!result.isHTTP && !result.hostname && !result.protocol) {
|
|
17
|
-
return null;
|
|
18
|
-
}
|
|
19
|
-
return result;
|
|
20
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { ExURL, ParseURLOptions } from '@d-zero/shared/parse-url';
|
|
2
|
-
/**
|
|
3
|
-
* Tests whether the pathname portion of a URL matches a given glob pattern.
|
|
4
|
-
* Uses micromatch for glob matching.
|
|
5
|
-
* @param targetPath - The URL string or ExURL whose pathname will be tested.
|
|
6
|
-
* @param pattern - The glob pattern to match against the pathname (e.g., `"/docs/**"`).
|
|
7
|
-
* @param options - Optional URL parsing options.
|
|
8
|
-
* @returns `true` if the URL's pathname matches the pattern; `false` otherwise
|
|
9
|
-
* (also returns `false` if the URL cannot be parsed).
|
|
10
|
-
*/
|
|
11
|
-
export declare function pathMatch(targetPath: string | ExURL, pattern: string, options?: ParseURLOptions): boolean;
|