@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,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses a PAX extended header record block for the `path` key.
|
|
3
|
+
*
|
|
4
|
+
* Format: a sequence of `"<record-length> <key>=<value>\n"` records, where
|
|
5
|
+
* `<record-length>` is the decimal byte length of the ENTIRE record
|
|
6
|
+
* (including the length prefix itself and the trailing newline). `tar`
|
|
7
|
+
* (node-tar) emits one of these ahead of any entry whose name doesn't fit
|
|
8
|
+
* the ustar format's 100-byte field (UTF-8 multi-byte names, or names
|
|
9
|
+
* longer than 100 bytes) — verified empirically against this package's own
|
|
10
|
+
* `tar()`/`create()` output.
|
|
11
|
+
* @param data - The PAX header entry's raw data payload.
|
|
12
|
+
* @returns The `path` value if present, otherwise `null`.
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* parsePaxPath(Buffer.from('20 path=日本語/\n', 'utf8')); // '日本語/'
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export function parsePaxPath(data) {
|
|
19
|
+
let offset = 0;
|
|
20
|
+
while (offset < data.length) {
|
|
21
|
+
const spaceIndex = data.indexOf(0x20 /* ' ' */, offset);
|
|
22
|
+
if (spaceIndex === -1) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
const lengthText = data.subarray(offset, spaceIndex).toString('latin1');
|
|
26
|
+
const recordLength = Number.parseInt(lengthText, 10);
|
|
27
|
+
if (!Number.isFinite(recordLength) || recordLength <= 0) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
const recordEnd = offset + recordLength;
|
|
31
|
+
if (recordEnd > data.length) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
// Decimal (not `0x3D`) to sidestep the Prettier/eslint hex-case
|
|
35
|
+
// conflict — see `parse-tar-size-field.ts` for the full explanation.
|
|
36
|
+
const equalsIndex = data.indexOf(61 /* '=' */, spaceIndex);
|
|
37
|
+
if (equalsIndex !== -1 && equalsIndex < recordEnd) {
|
|
38
|
+
const key = data.subarray(spaceIndex + 1, equalsIndex).toString('utf8');
|
|
39
|
+
if (key === 'path') {
|
|
40
|
+
// Value runs from after '=' to just before the trailing '\n'.
|
|
41
|
+
return data.subarray(equalsIndex + 1, recordEnd - 1).toString('utf8');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
offset = recordEnd;
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses a tar header's size field (offset 124, 12 bytes), which is either
|
|
3
|
+
* a NUL/space-terminated octal ASCII string (standard) or, when the
|
|
4
|
+
* high bit of the first byte is set, a GNU base-256 big-endian binary
|
|
5
|
+
* encoding (used for sizes too large for the 11-digit octal field).
|
|
6
|
+
* @param field - The 12-byte size field.
|
|
7
|
+
* @returns The size in bytes, or `null` if the field cannot be parsed.
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* parseTarSizeField(Buffer.from('00000000004\0', 'latin1')); // 4
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export declare function parseTarSizeField(field: Buffer): number | null;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses a tar header's size field (offset 124, 12 bytes), which is either
|
|
3
|
+
* a NUL/space-terminated octal ASCII string (standard) or, when the
|
|
4
|
+
* high bit of the first byte is set, a GNU base-256 big-endian binary
|
|
5
|
+
* encoding (used for sizes too large for the 11-digit octal field).
|
|
6
|
+
* @param field - The 12-byte size field.
|
|
7
|
+
* @returns The size in bytes, or `null` if the field cannot be parsed.
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* parseTarSizeField(Buffer.from('00000000004\0', 'latin1')); // 4
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export function parseTarSizeField(field) {
|
|
14
|
+
const first = field[0];
|
|
15
|
+
if (first === undefined) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
if ((first & 0x80) !== 0) {
|
|
19
|
+
// GNU base-256: the remaining bits of the first byte plus all
|
|
20
|
+
// following bytes form a big-endian unsigned integer. `.nitpicker`
|
|
21
|
+
// tars never approach sizes needing this (single-digit-terabyte
|
|
22
|
+
// range before it would even matter), but a correct read here still
|
|
23
|
+
// costs nothing.
|
|
24
|
+
// `0b0111_1111` (not `0x7F`) to sidestep the Prettier/eslint hex-case
|
|
25
|
+
// conflict (Prettier always lowercases hex digits; this repo's
|
|
26
|
+
// `unicorn/number-literal-case` requires uppercase) — a binary literal
|
|
27
|
+
// has no letter digits to disagree about.
|
|
28
|
+
let value = BigInt(first & 0b0111_1111);
|
|
29
|
+
for (let i = 1; i < field.length; i++) {
|
|
30
|
+
value = (value << 8n) | BigInt(field[i] ?? 0);
|
|
31
|
+
}
|
|
32
|
+
return Number(value);
|
|
33
|
+
}
|
|
34
|
+
const text = field.toString('latin1').replaceAll('\0', ' ').trim();
|
|
35
|
+
if (text === '') {
|
|
36
|
+
return 0;
|
|
37
|
+
}
|
|
38
|
+
const parsed = Number.parseInt(text, 8);
|
|
39
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
40
|
+
}
|
|
@@ -12,9 +12,16 @@
|
|
|
12
12
|
* directory it sees, so callers (`Archive.open`, the migration script) can
|
|
13
13
|
* use the actual inner name regardless of what the outer file is called.
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* Parses raw tar headers directly (ustar fixed fields, PAX extended headers,
|
|
16
|
+
* GNU longname/longlink) instead of using the `tar` package's `list()` —
|
|
17
|
+
* `list()` reads the entire archive stream to completion even after
|
|
18
|
+
* `onReadEntry` has found what it needs (no internal early-abort), so on a
|
|
19
|
+
* 15 GB+ archive it was a second full read on top of `untar()`'s own read
|
|
20
|
+
* (issue #294). The fast path here stops at the first top-level directory
|
|
21
|
+
* entry, typically reading well under 1 KB. Falls back to the original
|
|
22
|
+
* `list()`-based scan on anything it doesn't recognize (unknown typeflag
|
|
23
|
+
* sequence, truncated read, non-tar content) — safety over speed for
|
|
24
|
+
* archives this function has never been exercised against.
|
|
18
25
|
* @param tarFilePath - Path to the `.nitpicker` tar file.
|
|
19
26
|
* @returns The first top-level directory name found in the archive.
|
|
20
27
|
* @throws {Error} When the tar contains no top-level directory entry
|
|
@@ -1,4 +1,13 @@
|
|
|
1
|
+
import { open } from 'node:fs/promises';
|
|
1
2
|
import { list } from 'tar';
|
|
3
|
+
import { parsePaxPath } from './parse-pax-path.js';
|
|
4
|
+
import { parseTarSizeField } from './parse-tar-size-field.js';
|
|
5
|
+
/** Bytes per tar header/data block (fixed by the tar format). */
|
|
6
|
+
const BLOCK_SIZE = 512;
|
|
7
|
+
/** Defensive cap on entries scanned before giving up and falling back to `list()`. */
|
|
8
|
+
const MAX_ENTRIES = 100_000;
|
|
9
|
+
/** Cap on PAX/GNU-longname payload size read into memory; larger is treated as malformed. */
|
|
10
|
+
const MAX_EXTENDED_PAYLOAD_SIZE = 1_000_000;
|
|
2
11
|
/**
|
|
3
12
|
* Reads the top-level directory name from a `.nitpicker` tar archive.
|
|
4
13
|
*
|
|
@@ -13,9 +22,16 @@ import { list } from 'tar';
|
|
|
13
22
|
* directory it sees, so callers (`Archive.open`, the migration script) can
|
|
14
23
|
* use the actual inner name regardless of what the outer file is called.
|
|
15
24
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
25
|
+
* Parses raw tar headers directly (ustar fixed fields, PAX extended headers,
|
|
26
|
+
* GNU longname/longlink) instead of using the `tar` package's `list()` —
|
|
27
|
+
* `list()` reads the entire archive stream to completion even after
|
|
28
|
+
* `onReadEntry` has found what it needs (no internal early-abort), so on a
|
|
29
|
+
* 15 GB+ archive it was a second full read on top of `untar()`'s own read
|
|
30
|
+
* (issue #294). The fast path here stops at the first top-level directory
|
|
31
|
+
* entry, typically reading well under 1 KB. Falls back to the original
|
|
32
|
+
* `list()`-based scan on anything it doesn't recognize (unknown typeflag
|
|
33
|
+
* sequence, truncated read, non-tar content) — safety over speed for
|
|
34
|
+
* archives this function has never been exercised against.
|
|
19
35
|
* @param tarFilePath - Path to the `.nitpicker` tar file.
|
|
20
36
|
* @returns The first top-level directory name found in the archive.
|
|
21
37
|
* @throws {Error} When the tar contains no top-level directory entry
|
|
@@ -27,6 +43,151 @@ import { list } from 'tar';
|
|
|
27
43
|
* await peekTarTopDir('renamed.nitpicker'); // → 'original'
|
|
28
44
|
*/
|
|
29
45
|
export async function peekTarTopDir(tarFilePath) {
|
|
46
|
+
const fast = await peekTarTopDirFast(tarFilePath);
|
|
47
|
+
if (fast !== null) {
|
|
48
|
+
return fast;
|
|
49
|
+
}
|
|
50
|
+
return await peekTarTopDirViaList(tarFilePath);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Returns `true` for a top-level directory name that should be skipped: an
|
|
54
|
+
* AppleDouble resource-fork sidecar (`._foo`, from macOS BSD tar) or a PAX
|
|
55
|
+
* header directory (`PaxHeaders.NNN`/`@PaxHeader`). Real `.nitpicker`
|
|
56
|
+
* archives never name their actual top dir with these prefixes. Shared by
|
|
57
|
+
* both the fast path and the `list()` fallback so the two can't drift into
|
|
58
|
+
* accepting different names for the same archive.
|
|
59
|
+
* @param name - Candidate top-level directory name.
|
|
60
|
+
*/
|
|
61
|
+
function isSkippableTopDirName(name) {
|
|
62
|
+
return (name.startsWith('._') ||
|
|
63
|
+
name.startsWith('PaxHeaders') ||
|
|
64
|
+
name.startsWith('@PaxHeader'));
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Extracts the first path segment from a tar entry path, after stripping a
|
|
68
|
+
* leading `./` some tar producers prepend.
|
|
69
|
+
* @param entryPath - The raw path recorded in (or resolved for) a tar entry.
|
|
70
|
+
*/
|
|
71
|
+
function topSegmentOf(entryPath) {
|
|
72
|
+
return entryPath.replace(/^\.\//, '').split('/')[0] ?? '';
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Reads a NUL-terminated (or full-width, if no NUL) string field out of a
|
|
76
|
+
* tar header block.
|
|
77
|
+
* @param header - The 512-byte header block.
|
|
78
|
+
* @param start - Field start offset.
|
|
79
|
+
* @param length - Field length in bytes.
|
|
80
|
+
*/
|
|
81
|
+
function readHeaderString(header, start, length) {
|
|
82
|
+
const field = header.subarray(start, start + length);
|
|
83
|
+
const nulIndex = field.indexOf(0);
|
|
84
|
+
const raw = nulIndex === -1 ? field : field.subarray(0, nulIndex);
|
|
85
|
+
return raw.toString('utf8');
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Fast path for {@link peekTarTopDir}: reads raw 512-byte tar header blocks
|
|
89
|
+
* directly from the file, stopping at the first top-level directory entry.
|
|
90
|
+
* Understands ustar fixed headers, PAX extended headers (`path=`, the form
|
|
91
|
+
* `tar`/node-tar emits for UTF-8 or >100-byte names — verified empirically:
|
|
92
|
+
* see this function's test fixtures), and GNU longname (`typeflag 'L'`,
|
|
93
|
+
* treated the same as a PAX path override for the next entry — this
|
|
94
|
+
* codebase's own `tar()` never emits it, but a `.nitpicker` produced by a
|
|
95
|
+
* different tar implementation should still resolve correctly). GNU
|
|
96
|
+
* longlink (`typeflag 'K'`, a symlink target — irrelevant to a directory
|
|
97
|
+
* name lookup) is skipped without being applied to anything.
|
|
98
|
+
*
|
|
99
|
+
* Returns `null` — never throws — on anything unrecognized: truncated read,
|
|
100
|
+
* unparseable size field, entry count past {@link MAX_ENTRIES}, or reaching
|
|
101
|
+
* the end-of-archive marker without finding a directory. The caller falls
|
|
102
|
+
* back to the exhaustive `list()`-based scan in every `null` case.
|
|
103
|
+
* @param tarFilePath - Path to the `.nitpicker` tar file.
|
|
104
|
+
* @returns The first top-level directory name, or `null` to fall back.
|
|
105
|
+
*/
|
|
106
|
+
async function peekTarTopDirFast(tarFilePath) {
|
|
107
|
+
let fileHandle;
|
|
108
|
+
try {
|
|
109
|
+
fileHandle = await open(tarFilePath, 'r');
|
|
110
|
+
const header = Buffer.alloc(BLOCK_SIZE);
|
|
111
|
+
let position = 0;
|
|
112
|
+
// Set by a preceding PAX ('x') or GNU longname ('L') entry; applies
|
|
113
|
+
// to the very next entry only, then is cleared.
|
|
114
|
+
let pendingPath = null;
|
|
115
|
+
for (let entryIndex = 0; entryIndex < MAX_ENTRIES; entryIndex++) {
|
|
116
|
+
const { bytesRead } = await fileHandle.read(header, 0, BLOCK_SIZE, position);
|
|
117
|
+
if (bytesRead < BLOCK_SIZE) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
position += BLOCK_SIZE;
|
|
121
|
+
if (header.every((byte) => byte === 0)) {
|
|
122
|
+
// End-of-archive marker reached with no directory found —
|
|
123
|
+
// genuinely absent, not a parse failure, but the caller's
|
|
124
|
+
// error message is clearer coming from the exhaustive path.
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
const typeflag = String.fromCodePoint(header[156] ?? 0);
|
|
128
|
+
const size = parseTarSizeField(header.subarray(124, 136));
|
|
129
|
+
if (size === null) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
const dataBlockCount = Math.ceil(size / BLOCK_SIZE);
|
|
133
|
+
const dataStart = position;
|
|
134
|
+
position += dataBlockCount * BLOCK_SIZE;
|
|
135
|
+
if (typeflag === 'x' || typeflag === 'L') {
|
|
136
|
+
if (size <= 0 || size > MAX_EXTENDED_PAYLOAD_SIZE) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
const payload = Buffer.alloc(size);
|
|
140
|
+
const { bytesRead: payloadBytesRead } = await fileHandle.read(payload, 0, size, dataStart);
|
|
141
|
+
if (payloadBytesRead !== size) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
pendingPath =
|
|
145
|
+
typeflag === 'x'
|
|
146
|
+
? parsePaxPath(payload)
|
|
147
|
+
: payload.toString('utf8').replace(/\0.*$/, '');
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (typeflag === 'g' || typeflag === 'K') {
|
|
151
|
+
// PAX global header (applies archive-wide, never carries a
|
|
152
|
+
// per-entry path) / GNU longlink (symlink target, irrelevant
|
|
153
|
+
// to a directory name lookup) — skip without consuming
|
|
154
|
+
// `pendingPath`.
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
const magic = header.toString('latin1', 257, 263);
|
|
158
|
+
const nameField = readHeaderString(header, 0, 100);
|
|
159
|
+
const prefixField = magic.startsWith('ustar')
|
|
160
|
+
? readHeaderString(header, 345, 155)
|
|
161
|
+
: '';
|
|
162
|
+
const entryPath = pendingPath ?? (prefixField ? `${prefixField}/${nameField}` : nameField);
|
|
163
|
+
pendingPath = null;
|
|
164
|
+
if (typeflag === '5') {
|
|
165
|
+
const top = topSegmentOf(entryPath);
|
|
166
|
+
if (top && !isSkippableTopDirName(top)) {
|
|
167
|
+
return top;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
finally {
|
|
177
|
+
await fileHandle?.close();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Exhaustive fallback for {@link peekTarTopDir}: scans the tar's entry list
|
|
182
|
+
* via the `tar` package. Reads the archive to completion regardless of when
|
|
183
|
+
* a match is found (no early-abort in the `tar` package's `list()` API),
|
|
184
|
+
* which is exactly the cost {@link peekTarTopDirFast} exists to avoid — this
|
|
185
|
+
* path only runs when the fast path can't make sense of the header stream.
|
|
186
|
+
* @param tarFilePath - Path to the `.nitpicker` tar file.
|
|
187
|
+
* @returns The first top-level directory name found in the archive.
|
|
188
|
+
* @throws {Error} When the tar contains no top-level directory entry.
|
|
189
|
+
*/
|
|
190
|
+
async function peekTarTopDirViaList(tarFilePath) {
|
|
30
191
|
let found = null;
|
|
31
192
|
await list({
|
|
32
193
|
file: tarFilePath,
|
|
@@ -41,19 +202,8 @@ export async function peekTarTopDir(tarFilePath) {
|
|
|
41
202
|
// of the real top-level dir would win instead of the dir itself.
|
|
42
203
|
if (entry.type !== 'Directory')
|
|
43
204
|
return;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const cleaned = entry.path.replace(/^\.\//, '');
|
|
47
|
-
const top = cleaned.split('/')[0];
|
|
48
|
-
if (!top || top === '')
|
|
49
|
-
return;
|
|
50
|
-
// Defense in depth: skip AppleDouble directory entries
|
|
51
|
-
// (`._foo/`) and pax extended header entries
|
|
52
|
-
// (`PaxHeaders.NNN/...` / `@PaxHeader`). Real `.nitpicker`
|
|
53
|
-
// archives never name their top dir with these prefixes.
|
|
54
|
-
if (top.startsWith('._'))
|
|
55
|
-
return;
|
|
56
|
-
if (top.startsWith('PaxHeaders') || top.startsWith('@PaxHeader'))
|
|
205
|
+
const top = topSegmentOf(entry.path);
|
|
206
|
+
if (!top || isSkippableTopDirName(top))
|
|
57
207
|
return;
|
|
58
208
|
found = top;
|
|
59
209
|
},
|
|
@@ -4,8 +4,20 @@
|
|
|
4
4
|
* The archive preserves the relative directory structure.
|
|
5
5
|
* The `dir` parameter is resolved relative to its parent directory
|
|
6
6
|
* so only the target directory name appears in the archive.
|
|
7
|
+
*
|
|
8
|
+
* When `onProgress` is given, the tar is produced through a manual stream
|
|
9
|
+
* pipe instead of tar's own `file` mode so the written byte count can be
|
|
10
|
+
* observed — a `.nitpicker` tar is dominated by the single giant `db.sqlite`
|
|
11
|
+
* entry (15 GB+ on large crawls), so per-entry callbacks would fire once and
|
|
12
|
+
* report nothing for minutes (issue #294). `totalBytes` is an estimate (the
|
|
13
|
+
* sum of the directory's file sizes; tar adds per-entry headers and padding
|
|
14
|
+
* on top), so `writtenBytes` is clamped to it and a final
|
|
15
|
+
* `(totalBytes, totalBytes)` call marks completion.
|
|
7
16
|
* @param dir - The absolute path of the directory to archive.
|
|
8
17
|
* @param outputPath - The file path where the TAR archive will be written.
|
|
18
|
+
* @param onProgress - Called as archive bytes are written, with the bytes
|
|
19
|
+
* written so far (clamped to the estimate) and the estimated total. Omit
|
|
20
|
+
* for the original silent, tar-managed-file creation.
|
|
9
21
|
* @returns A promise that resolves when the TAR archive has been created.
|
|
10
22
|
*/
|
|
11
|
-
export declare function tar(dir: string, outputPath: string): Promise<void>;
|
|
23
|
+
export declare function tar(dir: string, outputPath: string, onProgress?: (writtenBytes: number, totalBytes: number) => void): Promise<void>;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createWriteStream } from 'node:fs';
|
|
2
|
+
import { readdir, stat } from 'node:fs/promises';
|
|
1
3
|
import path from 'node:path';
|
|
2
4
|
import { create } from 'tar';
|
|
3
5
|
/**
|
|
@@ -6,17 +8,71 @@ import { create } from 'tar';
|
|
|
6
8
|
* The archive preserves the relative directory structure.
|
|
7
9
|
* The `dir` parameter is resolved relative to its parent directory
|
|
8
10
|
* so only the target directory name appears in the archive.
|
|
11
|
+
*
|
|
12
|
+
* When `onProgress` is given, the tar is produced through a manual stream
|
|
13
|
+
* pipe instead of tar's own `file` mode so the written byte count can be
|
|
14
|
+
* observed — a `.nitpicker` tar is dominated by the single giant `db.sqlite`
|
|
15
|
+
* entry (15 GB+ on large crawls), so per-entry callbacks would fire once and
|
|
16
|
+
* report nothing for minutes (issue #294). `totalBytes` is an estimate (the
|
|
17
|
+
* sum of the directory's file sizes; tar adds per-entry headers and padding
|
|
18
|
+
* on top), so `writtenBytes` is clamped to it and a final
|
|
19
|
+
* `(totalBytes, totalBytes)` call marks completion.
|
|
9
20
|
* @param dir - The absolute path of the directory to archive.
|
|
10
21
|
* @param outputPath - The file path where the TAR archive will be written.
|
|
22
|
+
* @param onProgress - Called as archive bytes are written, with the bytes
|
|
23
|
+
* written so far (clamped to the estimate) and the estimated total. Omit
|
|
24
|
+
* for the original silent, tar-managed-file creation.
|
|
11
25
|
* @returns A promise that resolves when the TAR archive has been created.
|
|
12
26
|
*/
|
|
13
|
-
export function tar(dir, outputPath) {
|
|
27
|
+
export async function tar(dir, outputPath, onProgress) {
|
|
14
28
|
const baseDir = path.dirname(dir);
|
|
15
29
|
const targetDir = path.relative(baseDir, dir);
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
30
|
+
if (!onProgress) {
|
|
31
|
+
await create({
|
|
32
|
+
gzip: false,
|
|
33
|
+
cwd: baseDir,
|
|
34
|
+
file: outputPath,
|
|
35
|
+
preservePaths: false,
|
|
36
|
+
}, [targetDir]);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const entries = await readdir(dir, { recursive: true, withFileTypes: true });
|
|
40
|
+
let totalBytes = 0;
|
|
41
|
+
for (const entry of entries) {
|
|
42
|
+
if (!entry.isFile()) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const { size } = await stat(path.join(entry.parentPath, entry.name));
|
|
46
|
+
totalBytes += size;
|
|
47
|
+
}
|
|
48
|
+
await new Promise((resolve, reject) => {
|
|
49
|
+
const source = create({
|
|
50
|
+
gzip: false,
|
|
51
|
+
cwd: baseDir,
|
|
52
|
+
preservePaths: false,
|
|
53
|
+
}, [targetDir]);
|
|
54
|
+
const sink = createWriteStream(outputPath);
|
|
55
|
+
let writtenBytes = 0;
|
|
56
|
+
source.on('data', (chunk) => {
|
|
57
|
+
writtenBytes += chunk.length;
|
|
58
|
+
onProgress(Math.min(writtenBytes, totalBytes), totalBytes);
|
|
59
|
+
});
|
|
60
|
+
// .pipe() does not destroy the other side on error, so an error on
|
|
61
|
+
// either stream would otherwise leave the write side dangling open.
|
|
62
|
+
source.on('error', (error) => {
|
|
63
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
64
|
+
sink.destroy(err);
|
|
65
|
+
reject(err);
|
|
66
|
+
});
|
|
67
|
+
sink.on('error', (error) => {
|
|
68
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
69
|
+
source.destroy(err);
|
|
70
|
+
reject(err);
|
|
71
|
+
});
|
|
72
|
+
sink.on('close', () => {
|
|
73
|
+
onProgress(totalBytes, totalBytes);
|
|
74
|
+
resolve();
|
|
75
|
+
});
|
|
76
|
+
source.pipe(sink);
|
|
77
|
+
});
|
|
22
78
|
}
|
|
@@ -4,12 +4,23 @@
|
|
|
4
4
|
* Only files newer than existing files in the target directory are extracted
|
|
5
5
|
* (uses the `newer` option). Optionally restricts extraction to a specific
|
|
6
6
|
* working directory and/or a subset of files.
|
|
7
|
+
*
|
|
8
|
+
* When `onProgress` is given, the tar is read through a manual
|
|
9
|
+
* `createReadStream` pipe instead of tar's own `file` mode so the consumed
|
|
10
|
+
* byte count can be observed — a `.nitpicker` tar is dominated by the single
|
|
11
|
+
* giant `db.sqlite` entry (15 GB+ on large crawls), so per-entry callbacks
|
|
12
|
+
* would fire once and report nothing for minutes (issue #294). Byte
|
|
13
|
+
* granularity is the read-stream chunk size (~64 KB); callers wanting
|
|
14
|
+
* coarser updates (e.g. one per percent) throttle in their own callback.
|
|
7
15
|
* @param tarFilePath - The path to the TAR archive to extract.
|
|
8
16
|
* @param options - Optional extraction settings.
|
|
9
17
|
* @param options.cwd - The working directory to extract files into.
|
|
10
18
|
* If omitted, the current working directory is used.
|
|
11
19
|
* @param options.fileList - An array of specific file paths within the archive
|
|
12
20
|
* to extract. If omitted, all files in the archive are extracted.
|
|
21
|
+
* @param options.onProgress - Called as archive bytes are consumed, with the
|
|
22
|
+
* bytes read so far and the archive's total size. Omit for the original
|
|
23
|
+
* silent, tar-managed-file extraction.
|
|
13
24
|
* @returns A promise that resolves when extraction is complete.
|
|
14
25
|
*/
|
|
15
26
|
export declare function untar(tarFilePath: string, options?: {
|
|
@@ -17,4 +28,6 @@ export declare function untar(tarFilePath: string, options?: {
|
|
|
17
28
|
cwd?: string;
|
|
18
29
|
/** An array of specific file paths within the archive to extract. */
|
|
19
30
|
fileList?: string[];
|
|
31
|
+
/** Byte-level progress callback — see the function docs. */
|
|
32
|
+
onProgress?: (readBytes: number, totalBytes: number) => void;
|
|
20
33
|
}): Promise<void>;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createReadStream } from 'node:fs';
|
|
2
|
+
import { stat } from 'node:fs/promises';
|
|
1
3
|
import { extract } from 'tar';
|
|
2
4
|
/**
|
|
3
5
|
* Extracts files from a TAR archive.
|
|
@@ -5,20 +7,78 @@ import { extract } from 'tar';
|
|
|
5
7
|
* Only files newer than existing files in the target directory are extracted
|
|
6
8
|
* (uses the `newer` option). Optionally restricts extraction to a specific
|
|
7
9
|
* working directory and/or a subset of files.
|
|
10
|
+
*
|
|
11
|
+
* When `onProgress` is given, the tar is read through a manual
|
|
12
|
+
* `createReadStream` pipe instead of tar's own `file` mode so the consumed
|
|
13
|
+
* byte count can be observed — a `.nitpicker` tar is dominated by the single
|
|
14
|
+
* giant `db.sqlite` entry (15 GB+ on large crawls), so per-entry callbacks
|
|
15
|
+
* would fire once and report nothing for minutes (issue #294). Byte
|
|
16
|
+
* granularity is the read-stream chunk size (~64 KB); callers wanting
|
|
17
|
+
* coarser updates (e.g. one per percent) throttle in their own callback.
|
|
8
18
|
* @param tarFilePath - The path to the TAR archive to extract.
|
|
9
19
|
* @param options - Optional extraction settings.
|
|
10
20
|
* @param options.cwd - The working directory to extract files into.
|
|
11
21
|
* If omitted, the current working directory is used.
|
|
12
22
|
* @param options.fileList - An array of specific file paths within the archive
|
|
13
23
|
* to extract. If omitted, all files in the archive are extracted.
|
|
24
|
+
* @param options.onProgress - Called as archive bytes are consumed, with the
|
|
25
|
+
* bytes read so far and the archive's total size. Omit for the original
|
|
26
|
+
* silent, tar-managed-file extraction.
|
|
14
27
|
* @returns A promise that resolves when extraction is complete.
|
|
15
28
|
*/
|
|
16
|
-
export function untar(tarFilePath, options) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
29
|
+
export async function untar(tarFilePath, options) {
|
|
30
|
+
const { cwd, fileList, onProgress } = options ?? {};
|
|
31
|
+
if (!onProgress) {
|
|
32
|
+
await extract({
|
|
33
|
+
file: tarFilePath,
|
|
34
|
+
newer: true,
|
|
35
|
+
cwd,
|
|
36
|
+
preservePaths: false,
|
|
37
|
+
noMtime: true,
|
|
38
|
+
}, fileList ?? []);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const { size: totalBytes } = await stat(tarFilePath);
|
|
42
|
+
await new Promise((resolve, reject) => {
|
|
43
|
+
const source = createReadStream(tarFilePath);
|
|
44
|
+
let readBytes = 0;
|
|
45
|
+
source.on('data', (chunk) => {
|
|
46
|
+
readBytes += chunk.length;
|
|
47
|
+
onProgress(readBytes, totalBytes);
|
|
48
|
+
});
|
|
49
|
+
const sink = extract({
|
|
50
|
+
newer: true,
|
|
51
|
+
cwd,
|
|
52
|
+
preservePaths: false,
|
|
53
|
+
noMtime: true,
|
|
54
|
+
}, fileList ?? []);
|
|
55
|
+
// .pipe() does not destroy the other side on error, so an error on
|
|
56
|
+
// either stream would otherwise leave the write side dangling open.
|
|
57
|
+
// `Unpack` (`extends Parser extends EE`, a plain `EventEmitter`, NOT
|
|
58
|
+
// a Minipass/stream base class despite behaving like a writable
|
|
59
|
+
// stream) has no `destroy()` — `abort()` is its own equivalent:
|
|
60
|
+
// idempotent (guarded internally), and it emits its own 'error'
|
|
61
|
+
// (confirmed via `tar`'s `warnMethod` with `recoverable: false`),
|
|
62
|
+
// which re-enters the `sink.on('error', ...)` handler below —
|
|
63
|
+
// harmless, since `source.destroy()`/`reject()` are already
|
|
64
|
+
// idempotent no-ops on a second call.
|
|
65
|
+
source.on('error', (error) => {
|
|
66
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
67
|
+
sink.abort(err);
|
|
68
|
+
reject(err);
|
|
69
|
+
});
|
|
70
|
+
sink.on('error', (error) => {
|
|
71
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
72
|
+
source.destroy(err);
|
|
73
|
+
reject(err);
|
|
74
|
+
});
|
|
75
|
+
// node-tar's Unpack stream signals completion via 'close' (all entry
|
|
76
|
+
// writes flushed), not 'finish' (input fully consumed) — resolving on
|
|
77
|
+
// 'finish' could hand the caller a tmpDir whose db.sqlite is still
|
|
78
|
+
// being written.
|
|
79
|
+
sink.on('close', () => {
|
|
80
|
+
resolve();
|
|
81
|
+
});
|
|
82
|
+
source.pipe(sink);
|
|
83
|
+
});
|
|
24
84
|
}
|
|
@@ -30,7 +30,7 @@ export declare function applyConnectionPragmas(instance: Knex): Promise<void>;
|
|
|
30
30
|
* writes during a crawl and every reader queries. Must run AFTER
|
|
31
31
|
* `createRefTables` because most entity tables reference ref-table PKs.
|
|
32
32
|
* - **Adjunct tables** ({@link createAdjunctTables}): `page_errors`,
|
|
33
|
-
* `crawl_errors`, `
|
|
33
|
+
* `crawl_errors`, `technology_signals`, `page_technologies`, `page_jsonld`, `inventory_runs`,
|
|
34
34
|
* `analysis_text_refs` + `analysis_violations`, `page_html_blobs` +
|
|
35
35
|
* `page_html_ref`. Must run AFTER `createEntityTables` because the
|
|
36
36
|
* page-scoped tables FK into `content_items(id)`.
|
|
@@ -14,7 +14,8 @@ import { createRefTables } from './create-ref-tables.js';
|
|
|
14
14
|
export async function applyConnectionPragmas(instance) {
|
|
15
15
|
// Foreign-key enforcement defaults to OFF on every new SQLite
|
|
16
16
|
// connection. Required for ON DELETE CASCADE on `page_html_ref`,
|
|
17
|
-
// `
|
|
17
|
+
// `technology_signals`, `page_technologies`, `page_jsonld`, and the
|
|
18
|
+
// `page_main_content_*` tables to fire.
|
|
18
19
|
await instance.raw('PRAGMA foreign_keys = ON');
|
|
19
20
|
await instance.raw('PRAGMA wal_autocheckpoint = 1000');
|
|
20
21
|
// Negative value = KiB of memory (64 MiB). Helps large BLOB scans.
|
|
@@ -54,7 +55,7 @@ export async function applyConnectionPragmas(instance) {
|
|
|
54
55
|
* writes during a crawl and every reader queries. Must run AFTER
|
|
55
56
|
* `createRefTables` because most entity tables reference ref-table PKs.
|
|
56
57
|
* - **Adjunct tables** ({@link createAdjunctTables}): `page_errors`,
|
|
57
|
-
* `crawl_errors`, `
|
|
58
|
+
* `crawl_errors`, `technology_signals`, `page_technologies`, `page_jsonld`, `inventory_runs`,
|
|
58
59
|
* `analysis_text_refs` + `analysis_violations`, `page_html_blobs` +
|
|
59
60
|
* `page_html_ref`. Must run AFTER `createEntityTables` because the
|
|
60
61
|
* page-scoped tables FK into `content_items(id)`.
|
|
@@ -131,8 +132,9 @@ export async function initSchema(instance) {
|
|
|
131
132
|
// the ref tables (`url_refs`, `content_type_refs`, `text_refs`,
|
|
132
133
|
// `json_refs`, `blob_refs`, `header_sets`) via FK clauses.
|
|
133
134
|
await createEntityTables(instance);
|
|
134
|
-
// Adjunct tables that FK into `content_items` (page_errors /
|
|
135
|
-
//
|
|
135
|
+
// Adjunct tables that FK into `content_items` (page_errors /
|
|
136
|
+
// technology_signals / page_technologies / page_jsonld / analysis_* /
|
|
137
|
+
// page_html_*) plus the standalone log tables
|
|
136
138
|
// (crawl_errors / inventory_runs). MUST run after
|
|
137
139
|
// {@link createEntityTables} so the FK targets exist. DDL +
|
|
138
140
|
// column-level rationale lives in {@link createAdjunctTables}, which is
|
|
@@ -15,8 +15,19 @@ import type { MainContentsData, ScrollHeightData } from '@d-zero/beholder';
|
|
|
15
15
|
* omit it (`.spec.ts` files are excluded from the `tsc` build, so this goes
|
|
16
16
|
* uncaught at compile time) — `== null` tolerates both without forcing every
|
|
17
17
|
* fixture to be updated.
|
|
18
|
+
*
|
|
19
|
+
* `customElementCount` is a separate parameter, not read off `mainContents`,
|
|
20
|
+
* because it is not one of beholder's `MainContentsData` categories —
|
|
21
|
+
* nitpicker captures Web Components itself (`crawler/capture-custom-elements.ts`)
|
|
22
|
+
* after `scrapeStart` returns. It carries three distinct states:
|
|
23
|
+
* `undefined`/capture-not-attempted and capture-failure both collapse to
|
|
24
|
+
* `null` (unknown — NOT the same as "captured, zero found"), while any
|
|
25
|
+
* number (including `0`) means capture succeeded.
|
|
18
26
|
* @param mainContents - Beholder's per-page main-content metrics, or `null`/`undefined`.
|
|
19
27
|
* @param scrollHeight - Beholder's per-page scroll-height measurements, or `null`/`undefined`.
|
|
20
|
-
* @
|
|
28
|
+
* @param customElementCount - Count of Web Components nitpicker captured in
|
|
29
|
+
* the main-content region, or `null`/`undefined` when capture was not
|
|
30
|
+
* attempted or failed.
|
|
31
|
+
* @returns The eighteen denormalised columns.
|
|
21
32
|
*/
|
|
22
|
-
export declare function computeMainContentsDenormalized(mainContents: MainContentsData | null | undefined, scrollHeight: ScrollHeightData | null | undefined): MainContentsDenormalizedColumns;
|
|
33
|
+
export declare function computeMainContentsDenormalized(mainContents: MainContentsData | null | undefined, scrollHeight: ScrollHeightData | null | undefined, customElementCount?: number | null): MainContentsDenormalizedColumns;
|