@nitpicker/crawler 0.18.0 → 0.19.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/append-retry-failed-common-setup-phases.d.ts +15 -0
- package/lib/append-retry-failed-common-setup-phases.js +21 -0
- package/lib/append-setup-phases.d.ts +17 -0
- package/lib/append-setup-phases.js +22 -0
- package/lib/archive/archive-accessor.d.ts +31 -5
- package/lib/archive/archive-accessor.js +46 -24
- package/lib/archive/archive.d.ts +115 -37
- package/lib/archive/archive.js +118 -52
- package/lib/archive/cache/extract-archive-to-cache.d.ts +10 -1
- package/lib/archive/cache/extract-archive-to-cache.js +14 -4
- package/lib/archive/create-adjunct-tables.d.ts +12 -2
- package/lib/archive/create-adjunct-tables.js +91 -29
- package/lib/archive/create-entity-tables.d.ts +5 -1
- package/lib/archive/create-entity-tables.js +6 -1
- package/lib/archive/database.d.ts +39 -9
- package/lib/archive/database.js +63 -18
- package/lib/archive/db-ops/lifecycle/init.d.ts +14 -2
- package/lib/archive/db-ops/lifecycle/init.js +29 -11
- package/lib/archive/db-ops/meta/get-custom-elements-of-page.d.ts +11 -0
- package/lib/archive/db-ops/meta/get-custom-elements-of-page.js +15 -0
- package/lib/archive/db-ops/meta/get-page-technologies-of-page.d.ts +11 -0
- package/lib/archive/db-ops/meta/get-page-technologies-of-page.js +15 -0
- package/lib/archive/db-ops/meta/get-technology-signals-of-page.d.ts +11 -0
- package/lib/archive/db-ops/meta/get-technology-signals-of-page.js +15 -0
- package/lib/archive/db-ops/pages/order/set-url-order.d.ts +6 -1
- package/lib/archive/db-ops/pages/order/set-url-order.js +7 -1
- package/lib/archive/db-ops/pages/read/build-page-query.js +1 -1
- package/lib/archive/db-ops/pages/reset/repromote-external-pages.d.ts +6 -1
- package/lib/archive/db-ops/pages/reset/repromote-external-pages.js +12 -3
- package/lib/archive/db-ops/pages/reset/reset-failed-pages.d.ts +6 -1
- package/lib/archive/db-ops/pages/reset/reset-failed-pages.js +10 -2
- package/lib/archive/db-ops/pages/write/insert-custom-elements.d.ts +27 -0
- package/lib/archive/db-ops/pages/write/insert-custom-elements.js +40 -0
- package/lib/archive/db-ops/pages/write/insert-page.d.ts +24 -4
- package/lib/archive/db-ops/pages/write/insert-page.js +40 -4
- package/lib/archive/db-ops/pages/write/insert-technologies.d.ts +25 -0
- package/lib/archive/db-ops/pages/write/insert-technologies.js +45 -0
- package/lib/archive/db-ops/pages/write/link-redirect-sources.d.ts +7 -1
- package/lib/archive/db-ops/pages/write/link-redirect-sources.js +8 -2
- package/lib/archive/db-ops/pages/write/update-page.d.ts +4 -1
- package/lib/archive/db-ops/pages/write/update-page.js +38 -16
- package/lib/archive/db-ops/resources/get-resource-url-list.d.ts +12 -1
- package/lib/archive/db-ops/resources/get-resource-url-list.js +43 -5
- package/lib/archive/filesystem/copy-file-with-progress.d.ts +20 -0
- package/lib/archive/filesystem/copy-file-with-progress.js +34 -0
- package/lib/archive/filesystem/parse-pax-path.d.ts +18 -0
- package/lib/archive/filesystem/parse-pax-path.js +47 -0
- package/lib/archive/filesystem/parse-tar-size-field.d.ts +13 -0
- package/lib/archive/filesystem/parse-tar-size-field.js +40 -0
- package/lib/archive/filesystem/peek-tar-top-dir.d.ts +10 -3
- package/lib/archive/filesystem/peek-tar-top-dir.js +166 -16
- package/lib/archive/filesystem/tar.d.ts +13 -1
- package/lib/archive/filesystem/tar.js +63 -7
- package/lib/archive/filesystem/untar.d.ts +13 -0
- package/lib/archive/filesystem/untar.js +68 -8
- package/lib/archive/init-schema.d.ts +1 -1
- package/lib/archive/init-schema.js +6 -4
- package/lib/archive/meta/compute-main-contents-denormalized.d.ts +13 -2
- package/lib/archive/meta/compute-main-contents-denormalized.js +15 -2
- package/lib/archive/meta/technologies/combine-technology-confidence.d.ts +46 -0
- package/lib/archive/meta/technologies/combine-technology-confidence.js +93 -0
- package/lib/archive/meta/technologies/convert-legacy-page-tags-to-inserts.d.ts +45 -0
- package/lib/archive/meta/technologies/convert-legacy-page-tags-to-inserts.js +53 -0
- package/lib/archive/meta/technologies/convert-tag-row-to-wappalyzer-signal.d.ts +33 -0
- package/lib/archive/meta/technologies/convert-tag-row-to-wappalyzer-signal.js +46 -0
- package/lib/archive/meta/technologies/extract-technologies-for-archive.d.ts +30 -0
- package/lib/archive/meta/technologies/extract-technologies-for-archive.js +27 -0
- package/lib/archive/meta/technologies/match-structural-technology-signals.d.ts +13 -0
- package/lib/archive/meta/technologies/match-structural-technology-signals.js +44 -0
- package/lib/archive/meta/technologies/normalize-wappalyzer-entries.d.ts +25 -0
- package/lib/archive/meta/technologies/normalize-wappalyzer-entries.js +35 -0
- package/lib/archive/meta/technologies/technology-signal-definitions.d.ts +43 -0
- package/lib/archive/meta/technologies/technology-signal-definitions.js +193 -0
- package/lib/archive/meta/technologies/types.d.ts +68 -0
- package/lib/archive/meta/technologies/types.js +13 -0
- package/lib/archive/meta/types.d.ts +97 -30
- package/lib/archive/migrate-content-items-alias-of-id.d.ts +6 -1
- package/lib/archive/migrate-content-items-alias-of-id.js +14 -3
- package/lib/archive/migrate-content-items-dedupe-cap-event-id.d.ts +6 -1
- package/lib/archive/migrate-content-items-dedupe-cap-event-id.js +14 -3
- package/lib/archive/migrate-info-main-content-selector.d.ts +6 -1
- package/lib/archive/migrate-info-main-content-selector.js +14 -3
- package/lib/archive/migrate-info-roots.d.ts +6 -1
- package/lib/archive/migrate-info-roots.js +14 -3
- package/lib/archive/migrate-inventory-runs-exclude-skipped.d.ts +6 -1
- package/lib/archive/migrate-inventory-runs-exclude-skipped.js +14 -3
- package/lib/archive/migrate-inventory-runs-invalid-skipped.d.ts +6 -1
- package/lib/archive/migrate-inventory-runs-invalid-skipped.js +14 -3
- package/lib/archive/migrate-main-contents-columns.d.ts +6 -1
- package/lib/archive/migrate-main-contents-columns.js +14 -3
- package/lib/archive/migrate-page-meta-body-hash.d.ts +6 -1
- package/lib/archive/migrate-page-meta-body-hash.js +14 -3
- package/lib/archive/migrate-page-meta-console-error-count.d.ts +6 -1
- package/lib/archive/migrate-page-meta-console-error-count.js +14 -3
- package/lib/archive/migrate-page-meta-custom-element-count.d.ts +33 -0
- package/lib/archive/migrate-page-meta-custom-element-count.js +51 -0
- package/lib/archive/migrate-page-tags-to-page-technologies.d.ts +39 -0
- package/lib/archive/migrate-page-tags-to-page-technologies.js +79 -0
- package/lib/archive/page.d.ts +37 -13
- package/lib/archive/page.js +45 -17
- package/lib/archive/retarget-legacy-fk-tables.d.ts +27 -14
- package/lib/archive/retarget-legacy-fk-tables.js +61 -15
- package/lib/archive/types.d.ts +18 -0
- package/lib/crawler/assert-puppeteer-shared-with-beholder.d.ts +24 -0
- package/lib/crawler/assert-puppeteer-shared-with-beholder.js +36 -0
- package/lib/crawler/capture-custom-elements.d.ts +33 -0
- package/lib/crawler/capture-custom-elements.js +39 -0
- package/lib/crawler/close-browser-safely.d.ts +3 -3
- package/lib/crawler/close-browser-safely.js +8 -17
- package/lib/crawler/collect-custom-elements.d.ts +33 -0
- package/lib/crawler/collect-custom-elements.js +110 -0
- package/lib/crawler/crawler.js +101 -46
- package/lib/crawler/fetch-destination.js +6 -13
- package/lib/crawler/find-package-dir.d.ts +24 -0
- package/lib/crawler/find-package-dir.js +39 -0
- package/lib/crawler/scan-js-resource-for-license-comment.d.ts +32 -0
- package/lib/crawler/scan-js-resource-for-license-comment.js +140 -0
- package/lib/crawler/scan-js-resources-for-technology-signals.d.ts +58 -0
- package/lib/crawler/scan-js-resources-for-technology-signals.js +196 -0
- package/lib/crawler/types.d.ts +23 -0
- package/lib/crawler-orchestrator.d.ts +53 -9
- package/lib/crawler-orchestrator.js +223 -46
- package/lib/crawler.d.ts +11 -1
- package/lib/crawler.js +9 -0
- package/lib/inventory-setup-phases.d.ts +32 -0
- package/lib/inventory-setup-phases.js +45 -0
- package/lib/resume-setup-phases.d.ts +16 -0
- package/lib/resume-setup-phases.js +24 -0
- package/lib/retry-failed-setup-phases.d.ts +19 -0
- package/lib/retry-failed-setup-phases.js +24 -0
- package/lib/setup-recovery-phase-labels.d.ts +21 -0
- package/lib/setup-recovery-phase-labels.js +24 -0
- package/lib/types.d.ts +145 -0
- package/package.json +12 -9
- package/lib/archive/db-ops/meta/get-tags-of-page.d.ts +0 -12
- package/lib/archive/db-ops/meta/get-tags-of-page.js +0 -28
- package/lib/archive/db-ops/pages/write/insert-tags.d.ts +0 -16
- package/lib/archive/db-ops/pages/write/insert-tags.js +0 -34
- package/lib/archive/meta/summarize-tags.d.ts +0 -16
- package/lib/archive/meta/summarize-tags.js +0 -33
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { combineTechnologyConfidence } from '../archive/meta/technologies/combine-technology-confidence.js';
|
|
2
|
+
import { scanJsResourceForLicenseComment } from './scan-js-resource-for-license-comment.js';
|
|
3
|
+
/**
|
|
4
|
+
* Content-Type strings this scan treats as JavaScript. Deliberately
|
|
5
|
+
* duplicated (not imported) from `@nitpicker/query`'s `content-type-rules.ts`
|
|
6
|
+
* `'javascript'` category rule: the crawler package must not depend back on
|
|
7
|
+
* query (see `ensure-viewer-read-model-quietly.ts`'s docs for the same
|
|
8
|
+
* boundary), and this list is small and stable enough that duplication is
|
|
9
|
+
* cheaper than introducing a shared third package for it.
|
|
10
|
+
*/
|
|
11
|
+
const JS_CONTENT_TYPES = [
|
|
12
|
+
'text/javascript',
|
|
13
|
+
'application/javascript',
|
|
14
|
+
'application/x-javascript',
|
|
15
|
+
'application/ecmascript',
|
|
16
|
+
];
|
|
17
|
+
const DEFAULT_CONCURRENCY = 4;
|
|
18
|
+
/**
|
|
19
|
+
* Runs `worker` over `items` with at most `concurrency` calls in flight at
|
|
20
|
+
* once. A minimal worker-pool, not a chunk-then-parallelize helper like
|
|
21
|
+
* `eachSplitted` (which parallelizes across chunks, not within one) — this
|
|
22
|
+
* module needs a true cap on simultaneous outbound HTTP connections.
|
|
23
|
+
* @param items - The items to process.
|
|
24
|
+
* @param concurrency - Maximum simultaneous `worker` calls.
|
|
25
|
+
* @param worker - Called once per item; errors propagate to the caller.
|
|
26
|
+
*/
|
|
27
|
+
async function runWithConcurrency(items, concurrency, worker) {
|
|
28
|
+
let nextIndex = 0;
|
|
29
|
+
/**
|
|
30
|
+
*
|
|
31
|
+
*/
|
|
32
|
+
async function runNext() {
|
|
33
|
+
const index = nextIndex++;
|
|
34
|
+
if (index >= items.length)
|
|
35
|
+
return;
|
|
36
|
+
await worker(items[index]);
|
|
37
|
+
await runNext();
|
|
38
|
+
}
|
|
39
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => runNext()));
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Re-derives one page's `technology_signals` + `page_technologies` rows from
|
|
43
|
+
* its existing persisted signals plus newly-discovered `js-license-comment`
|
|
44
|
+
* signals, then scoped-replaces both tables — the same full-per-page-replace
|
|
45
|
+
* invariant `insertTechnologies` follows (see its docs: the two tables are
|
|
46
|
+
* never updated independently).
|
|
47
|
+
*
|
|
48
|
+
* `technology_signals` does not persist `category`/`version` (only
|
|
49
|
+
* `page_technologies` does — see `create-adjunct-tables.ts`), so a technology
|
|
50
|
+
* whose category/version came from a signal not re-derivable from the
|
|
51
|
+
* persisted rows (a `wappalyzer` or `meta-generator` signal, both computed at
|
|
52
|
+
* crawl time from data this function does not have) would otherwise regress
|
|
53
|
+
* to `null` here. The existing `page_technologies` row is read first and its
|
|
54
|
+
* `category`/`version` fall back in wherever the freshly combined result has
|
|
55
|
+
* none.
|
|
56
|
+
* @param knex - The archive's Knex instance.
|
|
57
|
+
* @param pageId - The page to update.
|
|
58
|
+
* @param newSignals - Newly-discovered signals for this page (from JS
|
|
59
|
+
* resources it references).
|
|
60
|
+
*/
|
|
61
|
+
async function applyNewSignalsToPage(knex, pageId, newSignals) {
|
|
62
|
+
await knex.transaction(async (trx) => {
|
|
63
|
+
const existingSignalRows = await trx('technology_signals')
|
|
64
|
+
.where('pageId', pageId)
|
|
65
|
+
.select('technology', 'signalType', 'evidence', 'weight');
|
|
66
|
+
const existingTechnologyRows = await trx('page_technologies')
|
|
67
|
+
.where('pageId', pageId)
|
|
68
|
+
.select('technology', 'category', 'version');
|
|
69
|
+
const existingMetaByTechnology = new Map(existingTechnologyRows.map((row) => [row.technology, row]));
|
|
70
|
+
const allSignals = [...existingSignalRows, ...newSignals];
|
|
71
|
+
const technologies = combineTechnologyConfidence(allSignals).map((t) => {
|
|
72
|
+
const previous = existingMetaByTechnology.get(t.technology);
|
|
73
|
+
return {
|
|
74
|
+
...t,
|
|
75
|
+
category: t.category ?? previous?.category ?? null,
|
|
76
|
+
version: t.version ?? previous?.version ?? null,
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
await trx('technology_signals').where('pageId', pageId).delete();
|
|
80
|
+
await trx('page_technologies').where('pageId', pageId).delete();
|
|
81
|
+
await trx('technology_signals').insert(allSignals.map((s) => ({
|
|
82
|
+
pageId,
|
|
83
|
+
technology: s.technology,
|
|
84
|
+
signalType: s.signalType,
|
|
85
|
+
evidence: s.evidence,
|
|
86
|
+
weight: s.weight,
|
|
87
|
+
})));
|
|
88
|
+
if (technologies.length > 0) {
|
|
89
|
+
await trx('page_technologies').insert(technologies.map((t) => ({ pageId, ...t })));
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Post-crawl network enrichment (distinct from crawl-time extraction and
|
|
95
|
+
* from read-model-time backfill — see ARCHITECTURE.md): re-fetches the
|
|
96
|
+
* leading bytes of every not-yet-scanned internal JS resource, tests them
|
|
97
|
+
* for a known technology's license comment, and folds any match into the
|
|
98
|
+
* referencing pages' `technology_signals` / `page_technologies`.
|
|
99
|
+
*
|
|
100
|
+
* Each resource is scanned at most once ever, across the archive's whole
|
|
101
|
+
* lifetime — outcomes (including non-matches) are recorded in
|
|
102
|
+
* `technology_js_scan_cache` keyed by `resourceId`, so a later
|
|
103
|
+
* `--append`/`--retry-failed` run only pays the network cost for resources
|
|
104
|
+
* discovered since the last run.
|
|
105
|
+
*
|
|
106
|
+
* A single resource can be referenced by many pages (a shared bundle); a
|
|
107
|
+
* match is applied to every one of them independently. Network scanning
|
|
108
|
+
* runs at bounded concurrency; the per-page DB recombination that follows
|
|
109
|
+
* runs after every scan has settled, never concurrently for the same page,
|
|
110
|
+
* so two resources that both resolve to the same page cannot race each
|
|
111
|
+
* other's read-modify-write.
|
|
112
|
+
*
|
|
113
|
+
* Best-effort like `scanJsResourceForLicenseComment`: an unreachable
|
|
114
|
+
* resource is recorded as a non-match (cached as scanned, `technology:
|
|
115
|
+
* null`) rather than retried or surfaced as an error — a flaky CDN must not
|
|
116
|
+
* block the rest of the archive's enrichment, and the resource will not be
|
|
117
|
+
* retried until `technology_js_scan_cache` itself is cleared.
|
|
118
|
+
* @param accessor - The archive to enrich.
|
|
119
|
+
* @param options - Concurrency, byte-cap, timeout, and progress overrides.
|
|
120
|
+
* @returns Counters describing what was scanned, matched, and updated.
|
|
121
|
+
* @example
|
|
122
|
+
* const result = await scanJsResourcesForTechnologySignals(archive);
|
|
123
|
+
* // { candidateCount: 42, scannedCount: 42, matchedCount: 3, pagesUpdatedCount: 57 }
|
|
124
|
+
*/
|
|
125
|
+
export async function scanJsResourcesForTechnologySignals(accessor, options = {}) {
|
|
126
|
+
const knex = accessor.getKnex();
|
|
127
|
+
const concurrency = Math.max(options.concurrency ?? DEFAULT_CONCURRENCY, 1);
|
|
128
|
+
const candidates = await knex('resource_items as ri')
|
|
129
|
+
.join('url_refs as ur', 'ur.id', 'ri.url_id')
|
|
130
|
+
.leftJoin('content_type_refs as ctr', 'ctr.id', 'ri.content_type_id')
|
|
131
|
+
.leftJoin('technology_js_scan_cache as cache', 'cache.resourceId', 'ri.id')
|
|
132
|
+
.where('ri.is_external', 0)
|
|
133
|
+
.whereNull('cache.resourceId')
|
|
134
|
+
.where((qb) => {
|
|
135
|
+
qb.whereIn('ctr.raw', JS_CONTENT_TYPES)
|
|
136
|
+
.orWhere('ur.url', 'like', '%.js')
|
|
137
|
+
.orWhere('ur.url', 'like', '%.js?%')
|
|
138
|
+
.orWhere('ur.url', 'like', '%.mjs')
|
|
139
|
+
.orWhere('ur.url', 'like', '%.mjs?%');
|
|
140
|
+
})
|
|
141
|
+
.select('ri.id as resourceId', 'ur.url as url');
|
|
142
|
+
let scannedCount = 0;
|
|
143
|
+
const matchesByResourceId = new Map();
|
|
144
|
+
await runWithConcurrency(candidates, concurrency, async (candidate) => {
|
|
145
|
+
const signal = await scanJsResourceForLicenseComment(candidate.url, {
|
|
146
|
+
byteLimit: options.byteLimit,
|
|
147
|
+
timeout: options.timeout,
|
|
148
|
+
userAgent: options.userAgent,
|
|
149
|
+
});
|
|
150
|
+
scannedCount++;
|
|
151
|
+
options.onProgress?.(scannedCount, candidates.length);
|
|
152
|
+
await knex('technology_js_scan_cache').insert({
|
|
153
|
+
resourceId: candidate.resourceId,
|
|
154
|
+
scannedAt: Date.now(),
|
|
155
|
+
technology: signal?.technology ?? null,
|
|
156
|
+
evidence: signal?.evidence ?? null,
|
|
157
|
+
});
|
|
158
|
+
if (signal) {
|
|
159
|
+
matchesByResourceId.set(candidate.resourceId, signal);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
if (matchesByResourceId.size === 0) {
|
|
163
|
+
return {
|
|
164
|
+
candidateCount: candidates.length,
|
|
165
|
+
scannedCount,
|
|
166
|
+
matchedCount: 0,
|
|
167
|
+
pagesUpdatedCount: 0,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
const edges = await knex('resource_ref_edges')
|
|
171
|
+
.whereIn('resource_id', [...matchesByResourceId.keys()])
|
|
172
|
+
.select('resource_id', 'page_id');
|
|
173
|
+
const newSignalsByPageId = new Map();
|
|
174
|
+
for (const edge of edges) {
|
|
175
|
+
const signal = matchesByResourceId.get(edge.resource_id);
|
|
176
|
+
if (!signal)
|
|
177
|
+
continue;
|
|
178
|
+
const list = newSignalsByPageId.get(edge.page_id);
|
|
179
|
+
if (list) {
|
|
180
|
+
list.push(signal);
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
newSignalsByPageId.set(edge.page_id, [signal]);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const affectedPageIds = [...newSignalsByPageId.keys()];
|
|
187
|
+
await runWithConcurrency(affectedPageIds, concurrency, async (pageId) => {
|
|
188
|
+
await applyNewSignalsToPage(knex, pageId, newSignalsByPageId.get(pageId));
|
|
189
|
+
});
|
|
190
|
+
return {
|
|
191
|
+
candidateCount: candidates.length,
|
|
192
|
+
scannedCount,
|
|
193
|
+
matchedCount: matchesByResourceId.size,
|
|
194
|
+
pagesUpdatedCount: affectedPageIds.length,
|
|
195
|
+
};
|
|
196
|
+
}
|
package/lib/crawler/types.d.ts
CHANGED
|
@@ -301,6 +301,14 @@ export interface CrawlerEventTypes {
|
|
|
301
301
|
* common case outside `crawl --inventory`. See {@link PageSource}.
|
|
302
302
|
*/
|
|
303
303
|
source?: PageSource;
|
|
304
|
+
/**
|
|
305
|
+
* This page's body hash, precomputed by the crawler from `result.html`
|
|
306
|
+
* (non-null whenever `result.html.length > 0`, since this event is only
|
|
307
|
+
* emitted for internal pages). Forwarded through `Archive.setPage` to
|
|
308
|
+
* `update-page.ts` so `page_meta.body_hash` is written from this value
|
|
309
|
+
* instead of hashing the same html a second time.
|
|
310
|
+
*/
|
|
311
|
+
bodyHash?: Buffer | null;
|
|
304
312
|
};
|
|
305
313
|
/**
|
|
306
314
|
* Emitted when an external page (outside the crawl scope) has been scraped.
|
|
@@ -475,6 +483,21 @@ export interface CrawlerEventTypes {
|
|
|
475
483
|
observedCount: number;
|
|
476
484
|
};
|
|
477
485
|
}
|
|
486
|
+
/**
|
|
487
|
+
* One Web Component (custom element) found inside a page's main-content
|
|
488
|
+
* region by {@link ./collect-custom-elements.ts}. Unlike beholder's eight
|
|
489
|
+
* `MainContentsData` categories (headings/images/tables/buttons/iframes/
|
|
490
|
+
* videos/audios/canvases), this is captured independently by nitpicker
|
|
491
|
+
* itself — see {@link ./capture-custom-elements.ts} for why.
|
|
492
|
+
*/
|
|
493
|
+
export interface MainContentCustomElementCandidate {
|
|
494
|
+
/** The element's `nodeName` (always upper-cased in an HTML document, e.g. `MY-WIDGET`). */
|
|
495
|
+
nodeName: string;
|
|
496
|
+
/** The element's `id` attribute, or `null` when absent. */
|
|
497
|
+
elementId: string | null;
|
|
498
|
+
/** The element's class list, in DOM order. */
|
|
499
|
+
classList: string[];
|
|
500
|
+
}
|
|
478
501
|
/**
|
|
479
502
|
* Tunables for `NetworkOutageDetector`.
|
|
480
503
|
*/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Config } from './archive/types.js';
|
|
2
2
|
import type { NetworkProbe } from './crawler/probe-network.js';
|
|
3
3
|
import type { InventoryMode } from './crawler/types.js';
|
|
4
|
-
import type { CrawlEvent } from './types.js';
|
|
4
|
+
import type { CrawlEvent, SetupProgressCallbacks } from './types.js';
|
|
5
5
|
import type { ExURL } from '@d-zero/shared/parse-url';
|
|
6
6
|
import { TypedAwaitEventEmitter as EventEmitter } from '@d-zero/shared/typed-await-event-emitter';
|
|
7
7
|
import Archive from './archive/archive.js';
|
|
@@ -111,10 +111,13 @@ interface InventorySource {
|
|
|
111
111
|
* writes the final archive file. It emits events defined by {@link CrawlEvent}.
|
|
112
112
|
*
|
|
113
113
|
* Instances are created via the static factory methods {@link CrawlerOrchestrator.crawling}
|
|
114
|
-
* or {@link CrawlerOrchestrator.resume}; the constructor is private.
|
|
114
|
+
* or {@link CrawlerOrchestrator.resume}; the constructor is private. Implements
|
|
115
|
+
* `Symbol.asyncDispose` so callers can use `await using` to close the archive
|
|
116
|
+
* and reap zombie Chromium processes on scope exit instead of a manual
|
|
117
|
+
* `try`/`finally` around `archive.close()` + `garbageCollect()`.
|
|
115
118
|
* @example
|
|
116
119
|
* ```ts
|
|
117
|
-
*
|
|
120
|
+
* await using orchestrator = await CrawlerOrchestrator.crawling(['https://example.com'], { recursive: true });
|
|
118
121
|
* await orchestrator.write();
|
|
119
122
|
* ```
|
|
120
123
|
*/
|
|
@@ -125,6 +128,23 @@ export declare class CrawlerOrchestrator extends EventEmitter<CrawlEvent> {
|
|
|
125
128
|
*/
|
|
126
129
|
get archive(): Archive;
|
|
127
130
|
private constructor();
|
|
131
|
+
/**
|
|
132
|
+
* Enables `await using orchestrator = ...`. Closes the archive (write
|
|
133
|
+
* or remove tmpDir + release the lock, per {@link Archive.close}) and
|
|
134
|
+
* then reaps any zombie Chromium processes via {@link garbageCollect} —
|
|
135
|
+
* the same two-step teardown every CLI crawl command previously
|
|
136
|
+
* repeated by hand in a `finally` block.
|
|
137
|
+
*
|
|
138
|
+
* Relays `Archive.close()`'s recovery-write progress (issue #294) as
|
|
139
|
+
* `recoveringArchiveWrite`/`writeStep`/`writeTarProgress` — the same
|
|
140
|
+
* events `write()` emits — for the rare case where the file doesn't
|
|
141
|
+
* exist on disk yet at dispose time (e.g. an explicit `write()` call
|
|
142
|
+
* threw partway through). A CLI listener whose display is still open at
|
|
143
|
+
* that point (it hadn't yet seen `writeFileEnd`) picks these up for
|
|
144
|
+
* free; one that already tore down after the earlier failure silently
|
|
145
|
+
* drops them, same as any other post-close display update.
|
|
146
|
+
*/
|
|
147
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
128
148
|
/**
|
|
129
149
|
* Abort the current crawl operation.
|
|
130
150
|
*
|
|
@@ -173,8 +193,11 @@ export declare class CrawlerOrchestrator extends EventEmitter<CrawlEvent> {
|
|
|
173
193
|
* the time `write()` is called those tables are already populated.
|
|
174
194
|
* This method just tars.
|
|
175
195
|
*
|
|
176
|
-
* Emits `writeFileStart` before writing and `writeFileEnd` after
|
|
177
|
-
*
|
|
196
|
+
* Emits `writeFileStart` before writing and `writeFileEnd` after the
|
|
197
|
+
* write completes successfully. Also relays `Archive.write()`'s
|
|
198
|
+
* per-step (`writeStep`) and tar-byte (`writeTarProgress`) progress
|
|
199
|
+
* (issue #294) — tarring a 15 GB+ archive can take minutes, and without
|
|
200
|
+
* these events a CLI listener has no way to show it isn't hung.
|
|
178
201
|
*/
|
|
179
202
|
write(): Promise<void>;
|
|
180
203
|
/**
|
|
@@ -211,10 +234,14 @@ export declare class CrawlerOrchestrator extends EventEmitter<CrawlEvent> {
|
|
|
211
234
|
* @param newUrls - New root URLs to add and crawl.
|
|
212
235
|
* @param options - Optional config overrides applied on top of the archived config.
|
|
213
236
|
* @param initializedCallback - Optional callback invoked after initialization but before crawling resumes.
|
|
237
|
+
* @param setupProgress - Optional progress callbacks for the setup phase
|
|
238
|
+
* (untar, `.bak` copy, repromote, state rebuild) that runs before
|
|
239
|
+
* `initializedCallback` — see {@link SetupProgressCallbacks} for why
|
|
240
|
+
* this can't go through the orchestrator's event emitter (issue #294).
|
|
214
241
|
* @returns The orchestrator instance after the append crawl completes.
|
|
215
242
|
* @throws {Error} When `newUrls` is empty, the archive is in list mode, or it cannot be parsed.
|
|
216
243
|
*/
|
|
217
|
-
static append(archivePath: string, newUrls: string[], options?: Partial<CrawlConfig>, initializedCallback?: CrawlInitializedCallback): Promise<CrawlerOrchestrator>;
|
|
244
|
+
static append(archivePath: string, newUrls: string[], options?: Partial<CrawlConfig>, initializedCallback?: CrawlInitializedCallback, setupProgress?: SetupProgressCallbacks): Promise<CrawlerOrchestrator>;
|
|
218
245
|
/**
|
|
219
246
|
* Inventory mode: cross-reference a user-supplied URL list against an
|
|
220
247
|
* existing `.nitpicker` archive and import ONLY the URLs that are not yet
|
|
@@ -292,10 +319,15 @@ export declare class CrawlerOrchestrator extends EventEmitter<CrawlEvent> {
|
|
|
292
319
|
* URLs. Pass `null` for programmatic callers that built
|
|
293
320
|
* `inventoryUrls` in-memory; the audit row's `source_file_sha256`
|
|
294
321
|
* column will be `NULL` and no source list is archived.
|
|
322
|
+
* @param setupProgress - Optional progress callbacks for the setup phase
|
|
323
|
+
* (untar, scope classification, bulk inserts, state rebuild) that runs
|
|
324
|
+
* before `initializedCallback` — see {@link SetupProgressCallbacks} for
|
|
325
|
+
* why this can't go through the orchestrator's event emitter (issue
|
|
326
|
+
* #294).
|
|
295
327
|
* @returns The orchestrator instance after a successful inventory pass.
|
|
296
328
|
* @throws {Error} When `inventoryUrls` is empty or the archive is in list mode. Unresolved pending URLs from a previous crawl do NOT throw — see step 3.
|
|
297
329
|
*/
|
|
298
|
-
static inventory(archivePath: string, inventoryUrls: string[], options?: Partial<CrawlConfig>, initializedCallback?: CrawlInitializedCallback, source?: InventorySource | null): Promise<CrawlerOrchestrator>;
|
|
330
|
+
static inventory(archivePath: string, inventoryUrls: string[], options?: Partial<CrawlConfig>, initializedCallback?: CrawlInitializedCallback, source?: InventorySource | null, setupProgress?: SetupProgressCallbacks): Promise<CrawlerOrchestrator>;
|
|
299
331
|
/**
|
|
300
332
|
* Re-fetch previously-failed pages in an existing `.nitpicker` archive.
|
|
301
333
|
*
|
|
@@ -326,10 +358,14 @@ export declare class CrawlerOrchestrator extends EventEmitter<CrawlEvent> {
|
|
|
326
358
|
* @param archivePath - Absolute or relative path to the existing `.nitpicker`.
|
|
327
359
|
* @param options - Optional config overrides applied on top of the archived config.
|
|
328
360
|
* @param initializedCallback - Optional callback invoked after initialization but before crawling resumes.
|
|
361
|
+
* @param setupProgress - Optional progress callbacks for the setup phase
|
|
362
|
+
* (untar, `.bak` copy, reset, state rebuild) that runs before
|
|
363
|
+
* `initializedCallback` — see {@link SetupProgressCallbacks} for why
|
|
364
|
+
* this can't go through the orchestrator's event emitter (issue #294).
|
|
329
365
|
* @returns The orchestrator instance after the retry crawl completes.
|
|
330
366
|
* @throws {Error} When the archive is in list mode or has no parseable roots.
|
|
331
367
|
*/
|
|
332
|
-
static retryFailed(archivePath: string, options?: Partial<CrawlConfig>, initializedCallback?: CrawlInitializedCallback): Promise<CrawlerOrchestrator>;
|
|
368
|
+
static retryFailed(archivePath: string, options?: Partial<CrawlConfig>, initializedCallback?: CrawlInitializedCallback, setupProgress?: SetupProgressCallbacks): Promise<CrawlerOrchestrator>;
|
|
333
369
|
/**
|
|
334
370
|
* Resume a previously interrupted crawl from an existing archive file.
|
|
335
371
|
*
|
|
@@ -339,9 +375,17 @@ export declare class CrawlerOrchestrator extends EventEmitter<CrawlEvent> {
|
|
|
339
375
|
* @param stubPath - Path to the existing archive file to resume from.
|
|
340
376
|
* @param options - Optional configuration overrides to apply on top of the archived config.
|
|
341
377
|
* @param initializedCallback - Optional callback invoked after initialization but before crawling resumes.
|
|
378
|
+
* @param setupProgress - Optional progress callbacks for the setup phase
|
|
379
|
+
* (self-healing migrations, state rebuild) that runs before
|
|
380
|
+
* `initializedCallback` — see {@link SetupProgressCallbacks} for why
|
|
381
|
+
* this can't go through the orchestrator's event emitter (issue #294).
|
|
382
|
+
* No `onExtractProgress`/`onCopyProgress`: unlike `append`/`inventory`/
|
|
383
|
+
* `retryFailed`, `resume` reconnects to an existing tmpDir (no untar)
|
|
384
|
+
* and takes no `.bak` (nothing to restore — the interrupted crawl's
|
|
385
|
+
* tmpDir IS the source of truth).
|
|
342
386
|
* @returns A promise that resolves to the CrawlerOrchestrator instance after crawling completes.
|
|
343
387
|
* @throws {Error} If the archived URL is invalid.
|
|
344
388
|
*/
|
|
345
|
-
static resume(stubPath: string, options?: Partial<CrawlConfig>, initializedCallback?: CrawlInitializedCallback): Promise<CrawlerOrchestrator>;
|
|
389
|
+
static resume(stubPath: string, options?: Partial<CrawlConfig>, initializedCallback?: CrawlInitializedCallback, setupProgress?: SetupProgressCallbacks): Promise<CrawlerOrchestrator>;
|
|
346
390
|
}
|
|
347
391
|
export {};
|