@nitpicker/crawler 0.18.1 → 0.20.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 +4 -4
- package/lib/archive/db-ops/pages/write/insert-page.js +3 -2
- 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/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 +140 -56
- 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/handle-scrape-end.js +19 -10
- package/lib/crawler/resolve-result-went-off-host.d.ts +34 -0
- package/lib/crawler/resolve-result-went-off-host.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 +34 -1
- 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 +11 -8
- 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,93 @@
|
|
|
1
|
+
import { TECHNOLOGY_CONFIDENCE_THRESHOLD } from './technology-signal-definitions.js';
|
|
2
|
+
/**
|
|
3
|
+
* Priority order for picking which signal's `category`/`version` wins when
|
|
4
|
+
* more than one signal for the same technology supplies one. `wappalyzer`
|
|
5
|
+
* wins first — it is the only source with a real Wappalyzer-reported
|
|
6
|
+
* `version`; `meta-generator` second — also carries a real version
|
|
7
|
+
* (`<meta name="generator" content="Astro v4.2.0">`); the rest never
|
|
8
|
+
* carry a version and are listed only so the `.find()` below terminates
|
|
9
|
+
* deterministically regardless of definition order.
|
|
10
|
+
*/
|
|
11
|
+
const CATEGORY_VERSION_SOURCE_PRIORITY = [
|
|
12
|
+
'wappalyzer',
|
|
13
|
+
'meta-generator',
|
|
14
|
+
'html-marker',
|
|
15
|
+
'url-pattern',
|
|
16
|
+
'scoped-attr',
|
|
17
|
+
'js-license-comment',
|
|
18
|
+
'weak-marker',
|
|
19
|
+
];
|
|
20
|
+
/**
|
|
21
|
+
* Combines every signal detected for one technology on one page into a
|
|
22
|
+
* single confidence score via noisy-OR:
|
|
23
|
+
*
|
|
24
|
+
* ```
|
|
25
|
+
* confidence = round(100 * (1 - Π (1 - weight/100)))
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* over each DISTINCT `signalType` present (same signal type appearing
|
|
29
|
+
* twice — e.g. two different `url-pattern` definitions both matching —
|
|
30
|
+
* counts once, at its highest weight; compounding only happens ACROSS
|
|
31
|
+
* signal types, which is what makes this "multiple independent signals
|
|
32
|
+
* agree" rather than "one noisy signal repeated"). A single signal's
|
|
33
|
+
* result equals its own weight exactly (`1 - (1 - w/100) = w/100`).
|
|
34
|
+
*
|
|
35
|
+
* Technologies whose combined confidence falls below
|
|
36
|
+
* `TECHNOLOGY_CONFIDENCE_THRESHOLD` are dropped entirely — a lone weak
|
|
37
|
+
* signal (e.g. `id="app"`, weight 15) is noise, not a detection, and
|
|
38
|
+
* `page_technologies` has no `isDetected` column to hide it behind; the
|
|
39
|
+
* row simply is not written. Pass `options.skipThreshold` to keep every
|
|
40
|
+
* technology regardless of confidence (see its docs for why).
|
|
41
|
+
* @param signals - Every signal detected for a single page, across all
|
|
42
|
+
* technologies (this function groups by `technology` internally).
|
|
43
|
+
* @param options - See {@link CombineTechnologyConfidenceOptions}.
|
|
44
|
+
* @returns One {@link PageTechnologyPartial} per technology (filtered by
|
|
45
|
+
* the confidence threshold unless `options.skipThreshold` is set), in no
|
|
46
|
+
* particular order.
|
|
47
|
+
*/
|
|
48
|
+
export function combineTechnologyConfidence(signals, options = {}) {
|
|
49
|
+
const byTechnology = new Map();
|
|
50
|
+
for (const signal of signals) {
|
|
51
|
+
const group = byTechnology.get(signal.technology);
|
|
52
|
+
if (group) {
|
|
53
|
+
group.push(signal);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
byTechnology.set(signal.technology, [signal]);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const results = [];
|
|
60
|
+
for (const [technology, group] of byTechnology) {
|
|
61
|
+
const maxWeightByType = new Map();
|
|
62
|
+
for (const signal of group) {
|
|
63
|
+
const current = maxWeightByType.get(signal.signalType);
|
|
64
|
+
if (current === undefined || signal.weight > current) {
|
|
65
|
+
maxWeightByType.set(signal.signalType, signal.weight);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const product = [...maxWeightByType.values()].reduce((acc, weight) => acc * (1 - weight / 100), 1);
|
|
69
|
+
const confidence = Math.round(100 * (1 - product));
|
|
70
|
+
if (!options.skipThreshold && confidence < TECHNOLOGY_CONFIDENCE_THRESHOLD)
|
|
71
|
+
continue;
|
|
72
|
+
let categorySource;
|
|
73
|
+
let versionSource;
|
|
74
|
+
for (const type of CATEGORY_VERSION_SOURCE_PRIORITY) {
|
|
75
|
+
if (!categorySource) {
|
|
76
|
+
categorySource = group.find((s) => s.signalType === type && s.category != null);
|
|
77
|
+
}
|
|
78
|
+
if (!versionSource) {
|
|
79
|
+
versionSource = group.find((s) => s.signalType === type && s.version != null);
|
|
80
|
+
}
|
|
81
|
+
if (categorySource && versionSource)
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
results.push({
|
|
85
|
+
technology,
|
|
86
|
+
category: categorySource?.category ?? null,
|
|
87
|
+
version: versionSource?.version ?? null,
|
|
88
|
+
confidence,
|
|
89
|
+
signalCount: maxWeightByType.size,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return results;
|
|
93
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { LegacyTagRowLike } from './convert-tag-row-to-wappalyzer-signal.js';
|
|
2
|
+
/** One `technology_signals` row ready for insertion, `pageId` already resolved. */
|
|
3
|
+
export interface LegacyTechnologySignalInsert {
|
|
4
|
+
pageId: number;
|
|
5
|
+
technology: string;
|
|
6
|
+
signalType: string;
|
|
7
|
+
evidence: string | null;
|
|
8
|
+
weight: number;
|
|
9
|
+
}
|
|
10
|
+
/** One `page_technologies` row ready for insertion, `pageId` already resolved. */
|
|
11
|
+
export interface LegacyPageTechnologyInsert {
|
|
12
|
+
pageId: number;
|
|
13
|
+
technology: string;
|
|
14
|
+
category: string | null;
|
|
15
|
+
version: string | null;
|
|
16
|
+
confidence: number;
|
|
17
|
+
signalCount: number;
|
|
18
|
+
}
|
|
19
|
+
/** Result of {@link convertLegacyPageTagsToInserts}. */
|
|
20
|
+
export interface LegacyPageTagsConversion {
|
|
21
|
+
signalInserts: LegacyTechnologySignalInsert[];
|
|
22
|
+
technologyInserts: LegacyPageTechnologyInsert[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Converts every persisted (legacy) `page_tags` row into the `pageId`-keyed
|
|
26
|
+
* `technology_signals` + `page_technologies` insert rows for that page's
|
|
27
|
+
* whole tag set — shared by both `page_tags` migration paths
|
|
28
|
+
* (`migrate-page-tags-to-page-technologies.ts` for archives already on the
|
|
29
|
+
* current schema, `retarget-legacy-fk-tables.ts` for the 0.10→0.13 upgrade)
|
|
30
|
+
* so a `page_tags` row converts identically regardless of which path
|
|
31
|
+
* produced it, and a future fix to the conversion only needs to change one
|
|
32
|
+
* place.
|
|
33
|
+
*
|
|
34
|
+
* Uses `combineTechnologyConfidence(signals, { skipThreshold: true })` —
|
|
35
|
+
* migration preserves every historical Wappalyzer detection the old
|
|
36
|
+
* `page_tags`-based API returned unconditionally, rather than applying the
|
|
37
|
+
* confidence-noise filter meant for fresh crawl-time detections (see that
|
|
38
|
+
* option's docs).
|
|
39
|
+
* @param rows - Every `page_tags` row, in any order (grouped internally by
|
|
40
|
+
* `pageId`).
|
|
41
|
+
* @returns The two insert-ready row sets, empty arrays if `rows` is empty.
|
|
42
|
+
*/
|
|
43
|
+
export declare function convertLegacyPageTagsToInserts(rows: readonly (LegacyTagRowLike & {
|
|
44
|
+
pageId: number;
|
|
45
|
+
})[]): LegacyPageTagsConversion;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { combineTechnologyConfidence } from './combine-technology-confidence.js';
|
|
2
|
+
import { convertTagRowToWappalyzerSignal } from './convert-tag-row-to-wappalyzer-signal.js';
|
|
3
|
+
/**
|
|
4
|
+
* Converts every persisted (legacy) `page_tags` row into the `pageId`-keyed
|
|
5
|
+
* `technology_signals` + `page_technologies` insert rows for that page's
|
|
6
|
+
* whole tag set — shared by both `page_tags` migration paths
|
|
7
|
+
* (`migrate-page-tags-to-page-technologies.ts` for archives already on the
|
|
8
|
+
* current schema, `retarget-legacy-fk-tables.ts` for the 0.10→0.13 upgrade)
|
|
9
|
+
* so a `page_tags` row converts identically regardless of which path
|
|
10
|
+
* produced it, and a future fix to the conversion only needs to change one
|
|
11
|
+
* place.
|
|
12
|
+
*
|
|
13
|
+
* Uses `combineTechnologyConfidence(signals, { skipThreshold: true })` —
|
|
14
|
+
* migration preserves every historical Wappalyzer detection the old
|
|
15
|
+
* `page_tags`-based API returned unconditionally, rather than applying the
|
|
16
|
+
* confidence-noise filter meant for fresh crawl-time detections (see that
|
|
17
|
+
* option's docs).
|
|
18
|
+
* @param rows - Every `page_tags` row, in any order (grouped internally by
|
|
19
|
+
* `pageId`).
|
|
20
|
+
* @returns The two insert-ready row sets, empty arrays if `rows` is empty.
|
|
21
|
+
*/
|
|
22
|
+
export function convertLegacyPageTagsToInserts(rows) {
|
|
23
|
+
const signalsByPage = new Map();
|
|
24
|
+
for (const row of rows) {
|
|
25
|
+
const signal = convertTagRowToWappalyzerSignal(row);
|
|
26
|
+
const group = signalsByPage.get(row.pageId);
|
|
27
|
+
if (group) {
|
|
28
|
+
group.push(signal);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
signalsByPage.set(row.pageId, [signal]);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const signalInserts = [];
|
|
35
|
+
const technologyInserts = [];
|
|
36
|
+
for (const [pageId, signals] of signalsByPage) {
|
|
37
|
+
for (const signal of signals) {
|
|
38
|
+
signalInserts.push({
|
|
39
|
+
pageId,
|
|
40
|
+
technology: signal.technology,
|
|
41
|
+
signalType: signal.signalType,
|
|
42
|
+
evidence: signal.evidence,
|
|
43
|
+
weight: signal.weight,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
for (const technology of combineTechnologyConfidence(signals, {
|
|
47
|
+
skipThreshold: true,
|
|
48
|
+
})) {
|
|
49
|
+
technologyInserts.push({ pageId, ...technology });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return { signalInserts, technologyInserts };
|
|
53
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { TechnologySignalPartial } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Minimal shape this function needs from a persisted (legacy) `page_tags`
|
|
4
|
+
* row. Deliberately narrower than the old `TagRow` — only the columns a
|
|
5
|
+
* `wappalyzer` signal can use. `categories` accepts `unknown` because
|
|
6
|
+
* callers read it via two different paths that disagree on whether the
|
|
7
|
+
* driver already parsed the `json()` column: raw `SELECT` (still a JSON
|
|
8
|
+
* string) vs. knex's typed row mapping (already an array on some drivers).
|
|
9
|
+
*/
|
|
10
|
+
export interface LegacyTagRowLike {
|
|
11
|
+
provider: string;
|
|
12
|
+
version: string | null;
|
|
13
|
+
confidence: number | null;
|
|
14
|
+
categories: unknown;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Converts one persisted (legacy) `page_tags` row into a `wappalyzer`
|
|
18
|
+
* technology signal — the shared core `migrate-page-tags-to-page-technologies.ts`
|
|
19
|
+
* (read-time migration for archives already on the current schema) and
|
|
20
|
+
* `retarget-legacy-fk-tables.ts` (0.10→0.13 upgrade path) both call, so a
|
|
21
|
+
* `page_tags` row converts identically regardless of which migration path
|
|
22
|
+
* produced it.
|
|
23
|
+
*
|
|
24
|
+
* Mirrors `normalize-wappalyzer-entries.ts`'s live-beholder conversion, but
|
|
25
|
+
* reads from the OLD persisted row shape (`provider`/`version`/`confidence`/
|
|
26
|
+
* `categories`) instead of beholder's live `TagEntry` (`provider`/`version`/
|
|
27
|
+
* `confidence`/`categories`/`id`/`sources`) — structurally similar, but
|
|
28
|
+
* `page_tags` has no `id` (externalId) to use as `evidence`, so the provider
|
|
29
|
+
* name is used instead.
|
|
30
|
+
* @param row - The persisted `page_tags` row (or an equivalent shape).
|
|
31
|
+
* @returns The equivalent `wappalyzer` signal.
|
|
32
|
+
*/
|
|
33
|
+
export declare function convertTagRowToWappalyzerSignal(row: LegacyTagRowLike): TechnologySignalPartial;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { WAPPALYZER_PROVIDER_TO_TECHNOLOGY, WAPPALYZER_SIGNAL_WEIGHT, } from './technology-signal-definitions.js';
|
|
2
|
+
/**
|
|
3
|
+
* Converts one persisted (legacy) `page_tags` row into a `wappalyzer`
|
|
4
|
+
* technology signal — the shared core `migrate-page-tags-to-page-technologies.ts`
|
|
5
|
+
* (read-time migration for archives already on the current schema) and
|
|
6
|
+
* `retarget-legacy-fk-tables.ts` (0.10→0.13 upgrade path) both call, so a
|
|
7
|
+
* `page_tags` row converts identically regardless of which migration path
|
|
8
|
+
* produced it.
|
|
9
|
+
*
|
|
10
|
+
* Mirrors `normalize-wappalyzer-entries.ts`'s live-beholder conversion, but
|
|
11
|
+
* reads from the OLD persisted row shape (`provider`/`version`/`confidence`/
|
|
12
|
+
* `categories`) instead of beholder's live `TagEntry` (`provider`/`version`/
|
|
13
|
+
* `confidence`/`categories`/`id`/`sources`) — structurally similar, but
|
|
14
|
+
* `page_tags` has no `id` (externalId) to use as `evidence`, so the provider
|
|
15
|
+
* name is used instead.
|
|
16
|
+
* @param row - The persisted `page_tags` row (or an equivalent shape).
|
|
17
|
+
* @returns The equivalent `wappalyzer` signal.
|
|
18
|
+
*/
|
|
19
|
+
export function convertTagRowToWappalyzerSignal(row) {
|
|
20
|
+
const categories = parseCategories(row.categories);
|
|
21
|
+
return {
|
|
22
|
+
technology: WAPPALYZER_PROVIDER_TO_TECHNOLOGY[row.provider] ?? row.provider,
|
|
23
|
+
signalType: 'wappalyzer',
|
|
24
|
+
evidence: row.provider,
|
|
25
|
+
weight: row.confidence ?? WAPPALYZER_SIGNAL_WEIGHT,
|
|
26
|
+
category: categories[0] ?? null,
|
|
27
|
+
version: row.version ?? null,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* @param raw
|
|
32
|
+
*/
|
|
33
|
+
function parseCategories(raw) {
|
|
34
|
+
if (Array.isArray(raw))
|
|
35
|
+
return raw;
|
|
36
|
+
if (typeof raw === 'string' && raw.length > 0) {
|
|
37
|
+
try {
|
|
38
|
+
const parsed = JSON.parse(raw);
|
|
39
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { PageTechnologyPartial, TechnologySignalPartial } from './types.js';
|
|
2
|
+
import type { PageData } from '../../../utils/types/types.js';
|
|
3
|
+
/**
|
|
4
|
+
* The result of one page's technology-signal extraction: every raw signal
|
|
5
|
+
* (for `technology_signals`, MCP/query signal-level lookups, and per-page
|
|
6
|
+
* "why was this detected" drill-down) plus the confidence-combined roll-up
|
|
7
|
+
* (for `page_technologies`).
|
|
8
|
+
*/
|
|
9
|
+
export interface ExtractedTechnologies {
|
|
10
|
+
signals: TechnologySignalPartial[];
|
|
11
|
+
technologies: PageTechnologyPartial[];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Extracts every technology signal for one page — structural (HTML markers,
|
|
15
|
+
* URL path fragments, scoped attributes, `<meta name="generator">`) plus
|
|
16
|
+
* Wappalyzer (beholder's `meta.tags.entries`) — and combines them into
|
|
17
|
+
* confidence-scored technology rows.
|
|
18
|
+
*
|
|
19
|
+
* Crawl-time, synchronous, zero extra network cost — the direct replacement
|
|
20
|
+
* for `extract-tags-for-archive.ts` in the live crawl write path (that file
|
|
21
|
+
* itself is untouched; `scripts/migrate-to-0.10.mjs` still depends on it).
|
|
22
|
+
* JS-license-comment signals are NOT produced here — they require an extra
|
|
23
|
+
* network fetch per JS resource and are added afterward, once per crawl, by
|
|
24
|
+
* `scan-js-resources-for-technology-signals.ts` ("JSスキャン・エンリッチメント",
|
|
25
|
+
* a distinct post-crawl category — see ARCHITECTURE.md).
|
|
26
|
+
* @param html - The page's raw HTML string.
|
|
27
|
+
* @param meta - `PageData.meta` (for `generator` and `tags.entries`).
|
|
28
|
+
* @returns Raw signals and their confidence-combined roll-up.
|
|
29
|
+
*/
|
|
30
|
+
export declare function extractTechnologiesForArchive(html: string, meta: PageData['meta']): ExtractedTechnologies;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { combineTechnologyConfidence } from './combine-technology-confidence.js';
|
|
2
|
+
import { matchStructuralTechnologySignals } from './match-structural-technology-signals.js';
|
|
3
|
+
import { normalizeWappalyzerEntries } from './normalize-wappalyzer-entries.js';
|
|
4
|
+
/**
|
|
5
|
+
* Extracts every technology signal for one page — structural (HTML markers,
|
|
6
|
+
* URL path fragments, scoped attributes, `<meta name="generator">`) plus
|
|
7
|
+
* Wappalyzer (beholder's `meta.tags.entries`) — and combines them into
|
|
8
|
+
* confidence-scored technology rows.
|
|
9
|
+
*
|
|
10
|
+
* Crawl-time, synchronous, zero extra network cost — the direct replacement
|
|
11
|
+
* for `extract-tags-for-archive.ts` in the live crawl write path (that file
|
|
12
|
+
* itself is untouched; `scripts/migrate-to-0.10.mjs` still depends on it).
|
|
13
|
+
* JS-license-comment signals are NOT produced here — they require an extra
|
|
14
|
+
* network fetch per JS resource and are added afterward, once per crawl, by
|
|
15
|
+
* `scan-js-resources-for-technology-signals.ts` ("JSスキャン・エンリッチメント",
|
|
16
|
+
* a distinct post-crawl category — see ARCHITECTURE.md).
|
|
17
|
+
* @param html - The page's raw HTML string.
|
|
18
|
+
* @param meta - `PageData.meta` (for `generator` and `tags.entries`).
|
|
19
|
+
* @returns Raw signals and their confidence-combined roll-up.
|
|
20
|
+
*/
|
|
21
|
+
export function extractTechnologiesForArchive(html, meta) {
|
|
22
|
+
const structuralSignals = matchStructuralTechnologySignals(html, meta.generator);
|
|
23
|
+
const wappalyzerSignals = normalizeWappalyzerEntries(meta.tags?.entries);
|
|
24
|
+
const signals = [...structuralSignals, ...wappalyzerSignals];
|
|
25
|
+
const technologies = combineTechnologyConfidence(signals);
|
|
26
|
+
return { signals, technologies };
|
|
27
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { TechnologySignalPartial } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Detects every {@link TECHNOLOGY_SIGNAL_DEFINITIONS} pattern against a
|
|
4
|
+
* page's raw HTML string, plus `<meta name="generator">` prefix matches
|
|
5
|
+
* against the already-extracted `generator` field. Pure and synchronous —
|
|
6
|
+
* no network I/O, no Wappalyzer data (see `normalize-wappalyzer-entries.ts`
|
|
7
|
+
* for that source).
|
|
8
|
+
* @param html - The page's raw HTML string.
|
|
9
|
+
* @param generator - `page_meta.generator` (already extracted by
|
|
10
|
+
* `derive-flat-from-meta.ts` from beholder's `Meta`), or `null`/`undefined`.
|
|
11
|
+
* @returns Un-combined structural signals, one per matched definition.
|
|
12
|
+
*/
|
|
13
|
+
export declare function matchStructuralTechnologySignals(html: string, generator: string | null | undefined): TechnologySignalPartial[];
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { GENERATOR_TECHNOLOGY_MATCHERS, TECHNOLOGY_SIGNAL_DEFINITIONS, } from './technology-signal-definitions.js';
|
|
2
|
+
const EVIDENCE_MAX_LENGTH = 200;
|
|
3
|
+
/**
|
|
4
|
+
* Detects every {@link TECHNOLOGY_SIGNAL_DEFINITIONS} pattern against a
|
|
5
|
+
* page's raw HTML string, plus `<meta name="generator">` prefix matches
|
|
6
|
+
* against the already-extracted `generator` field. Pure and synchronous —
|
|
7
|
+
* no network I/O, no Wappalyzer data (see `normalize-wappalyzer-entries.ts`
|
|
8
|
+
* for that source).
|
|
9
|
+
* @param html - The page's raw HTML string.
|
|
10
|
+
* @param generator - `page_meta.generator` (already extracted by
|
|
11
|
+
* `derive-flat-from-meta.ts` from beholder's `Meta`), or `null`/`undefined`.
|
|
12
|
+
* @returns Un-combined structural signals, one per matched definition.
|
|
13
|
+
*/
|
|
14
|
+
export function matchStructuralTechnologySignals(html, generator) {
|
|
15
|
+
const signals = [];
|
|
16
|
+
for (const def of TECHNOLOGY_SIGNAL_DEFINITIONS) {
|
|
17
|
+
const match = def.pattern.exec(html);
|
|
18
|
+
if (match) {
|
|
19
|
+
signals.push({
|
|
20
|
+
technology: def.technology,
|
|
21
|
+
signalType: def.signalType,
|
|
22
|
+
evidence: match[0].slice(0, EVIDENCE_MAX_LENGTH),
|
|
23
|
+
weight: def.weight,
|
|
24
|
+
category: def.category ?? null,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (generator) {
|
|
29
|
+
const lowerGenerator = generator.toLowerCase();
|
|
30
|
+
for (const matcher of GENERATOR_TECHNOLOGY_MATCHERS) {
|
|
31
|
+
if (lowerGenerator.startsWith(matcher.prefix)) {
|
|
32
|
+
signals.push({
|
|
33
|
+
technology: matcher.technology,
|
|
34
|
+
signalType: 'meta-generator',
|
|
35
|
+
evidence: generator.slice(0, EVIDENCE_MAX_LENGTH),
|
|
36
|
+
weight: matcher.weight,
|
|
37
|
+
category: matcher.category,
|
|
38
|
+
version: generator.slice(matcher.prefix.length).trim() || null,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return signals;
|
|
44
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { TechnologySignalPartial } from './types.js';
|
|
2
|
+
import type { TagEntry } from '@d-zero/beholder';
|
|
3
|
+
/**
|
|
4
|
+
* Converts beholder's `TagsMeta.entries` (per-page Wappalyzer detections,
|
|
5
|
+
* one row per provider × external-id tuple) into `wappalyzer` technology
|
|
6
|
+
* signals.
|
|
7
|
+
*
|
|
8
|
+
* Every entry becomes a signal — Wappalyzer is not limited to the curated
|
|
9
|
+
* frameworks {@link TECHNOLOGY_SIGNAL_DEFINITIONS} tracks; analytics tools,
|
|
10
|
+
* CMSes, and everything else Wappalyzer's fingerprint DB knows about pass
|
|
11
|
+
* through as their own technology (provider name verbatim). Only the
|
|
12
|
+
* curated frameworks get their provider name FOLDED onto the same
|
|
13
|
+
* technology name their structural signals use, via
|
|
14
|
+
* {@link WAPPALYZER_PROVIDER_TO_TECHNOLOGY} — so e.g. Wappalyzer's `'Vue.js'`
|
|
15
|
+
* and a `data-v-*` structural match both contribute to the `'Vue'` row,
|
|
16
|
+
* not two separate rows.
|
|
17
|
+
*
|
|
18
|
+
* This is the direct replacement for the old `extract-tags-for-archive.ts`
|
|
19
|
+
* (which still exists, unmodified, for `scripts/migrate-to-0.10.mjs`'s
|
|
20
|
+
* independent use — see that file's JSDoc) in the LIVE crawl write path.
|
|
21
|
+
* @param entries - `meta.tags.entries` from beholder, or `undefined`
|
|
22
|
+
* (legacy minimal-`Meta` test fixtures predate the required field).
|
|
23
|
+
* @returns One signal per entry; empty array when no tags were detected.
|
|
24
|
+
*/
|
|
25
|
+
export declare function normalizeWappalyzerEntries(entries: readonly TagEntry[] | undefined): TechnologySignalPartial[];
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { WAPPALYZER_PROVIDER_TO_TECHNOLOGY, WAPPALYZER_SIGNAL_WEIGHT, } from './technology-signal-definitions.js';
|
|
2
|
+
/**
|
|
3
|
+
* Converts beholder's `TagsMeta.entries` (per-page Wappalyzer detections,
|
|
4
|
+
* one row per provider × external-id tuple) into `wappalyzer` technology
|
|
5
|
+
* signals.
|
|
6
|
+
*
|
|
7
|
+
* Every entry becomes a signal — Wappalyzer is not limited to the curated
|
|
8
|
+
* frameworks {@link TECHNOLOGY_SIGNAL_DEFINITIONS} tracks; analytics tools,
|
|
9
|
+
* CMSes, and everything else Wappalyzer's fingerprint DB knows about pass
|
|
10
|
+
* through as their own technology (provider name verbatim). Only the
|
|
11
|
+
* curated frameworks get their provider name FOLDED onto the same
|
|
12
|
+
* technology name their structural signals use, via
|
|
13
|
+
* {@link WAPPALYZER_PROVIDER_TO_TECHNOLOGY} — so e.g. Wappalyzer's `'Vue.js'`
|
|
14
|
+
* and a `data-v-*` structural match both contribute to the `'Vue'` row,
|
|
15
|
+
* not two separate rows.
|
|
16
|
+
*
|
|
17
|
+
* This is the direct replacement for the old `extract-tags-for-archive.ts`
|
|
18
|
+
* (which still exists, unmodified, for `scripts/migrate-to-0.10.mjs`'s
|
|
19
|
+
* independent use — see that file's JSDoc) in the LIVE crawl write path.
|
|
20
|
+
* @param entries - `meta.tags.entries` from beholder, or `undefined`
|
|
21
|
+
* (legacy minimal-`Meta` test fixtures predate the required field).
|
|
22
|
+
* @returns One signal per entry; empty array when no tags were detected.
|
|
23
|
+
*/
|
|
24
|
+
export function normalizeWappalyzerEntries(entries) {
|
|
25
|
+
if (!entries || entries.length === 0)
|
|
26
|
+
return [];
|
|
27
|
+
return entries.map((entry) => ({
|
|
28
|
+
technology: WAPPALYZER_PROVIDER_TO_TECHNOLOGY[entry.provider] ?? entry.provider,
|
|
29
|
+
signalType: 'wappalyzer',
|
|
30
|
+
evidence: entry.id ?? entry.provider,
|
|
31
|
+
weight: entry.confidence ?? WAPPALYZER_SIGNAL_WEIGHT,
|
|
32
|
+
category: entry.categories[0] ?? null,
|
|
33
|
+
version: entry.version ?? null,
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { TechnologySignalDefinition } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Fixed fingerprint list for the frontend frameworks this module detects via
|
|
4
|
+
* structural signals (URL path fragments, HTML markers, scoped-attribute
|
|
5
|
+
* conventions). Deliberately a TypeScript array, not an external config
|
|
6
|
+
* file (per the accepted design: precision/coverage is refined by editing
|
|
7
|
+
* this file, not by runtime configuration).
|
|
8
|
+
*
|
|
9
|
+
* Every framework here is ALSO detectable via {@link WAPPALYZER_PROVIDER_TO_TECHNOLOGY}
|
|
10
|
+
* when beholder's Wappalyzer pass independently reports it — the two signal
|
|
11
|
+
* sources compound in `combineTechnologyConfidence` rather than one
|
|
12
|
+
* replacing the other. Wappalyzer detects many more technologies than the
|
|
13
|
+
* frameworks listed here (analytics, CMSes, ...); those pass through with
|
|
14
|
+
* the provider name verbatim as the technology name, uncurated.
|
|
15
|
+
* @module
|
|
16
|
+
*/
|
|
17
|
+
export declare const TECHNOLOGY_SIGNAL_DEFINITIONS: readonly TechnologySignalDefinition[];
|
|
18
|
+
/**
|
|
19
|
+
* `<meta name="generator">` prefix matchers, checked case-insensitively
|
|
20
|
+
* against `page_meta.generator` (already extracted — no new HTML scan
|
|
21
|
+
* needed). Distinct from {@link TECHNOLOGY_SIGNAL_DEFINITIONS} because the
|
|
22
|
+
* match strategy is prefix-on-a-known-field, not regex-over-HTML.
|
|
23
|
+
*/
|
|
24
|
+
export declare const GENERATOR_TECHNOLOGY_MATCHERS: ReadonlyArray<{
|
|
25
|
+
technology: string;
|
|
26
|
+
prefix: string;
|
|
27
|
+
weight: number;
|
|
28
|
+
category: string;
|
|
29
|
+
}>;
|
|
30
|
+
/**
|
|
31
|
+
* Wappalyzer provider name → normalized technology name, for the curated
|
|
32
|
+
* frameworks that also have {@link TECHNOLOGY_SIGNAL_DEFINITIONS}. Providers
|
|
33
|
+
* NOT in this map still become `page_technologies` rows — the technology
|
|
34
|
+
* name is just the provider name verbatim (see `normalize-wappalyzer-entries.ts`)
|
|
35
|
+
* — this map exists only to fold curated frameworks' Wappalyzer detections
|
|
36
|
+
* onto the same technology name their structural signals use, so the two
|
|
37
|
+
* sources compound instead of appearing as two separate rows.
|
|
38
|
+
*/
|
|
39
|
+
export declare const WAPPALYZER_PROVIDER_TO_TECHNOLOGY: Readonly<Record<string, string>>;
|
|
40
|
+
/** Confidence a lone `wappalyzer` signal contributes, before compounding with any structural signal. */
|
|
41
|
+
export declare const WAPPALYZER_SIGNAL_WEIGHT = 60;
|
|
42
|
+
/** Confidence threshold (`combineTechnologyConfidence`'s output) at or above which a `page_technologies` row is written at all. Below this, a single weak/ambiguous signal is treated as noise, not a detection. */
|
|
43
|
+
export declare const TECHNOLOGY_CONFIDENCE_THRESHOLD = 30;
|