@d-zero/beholder 2.1.6 → 3.0.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/CHANGELOG.md +38 -0
- package/dist/dom-evaluation.d.ts +72 -24
- package/dist/dom-evaluation.js +442 -84
- package/dist/index.d.ts +1 -1
- package/dist/meta/classify.d.ts +52 -0
- package/dist/meta/classify.js +731 -0
- package/dist/meta/id-extractors.d.ts +40 -0
- package/dist/meta/id-extractors.js +196 -0
- package/dist/meta/keys.d.ts +41 -0
- package/dist/meta/keys.js +507 -0
- package/dist/meta/parsers.d.ts +74 -0
- package/dist/meta/parsers.js +293 -0
- package/dist/meta/tag-detection.d.ts +59 -0
- package/dist/meta/tag-detection.js +120 -0
- package/dist/meta/types.d.ts +874 -0
- package/dist/meta/types.js +12 -0
- package/dist/scraper.js +15 -13
- package/dist/types.d.ts +3 -38
- package/package.json +5 -4
- package/src/dom-evaluation.spec.ts +301 -73
- package/src/dom-evaluation.ts +558 -88
- package/src/index.ts +43 -0
- package/src/meta/classify.spec.ts +281 -0
- package/src/meta/classify.ts +810 -0
- package/src/meta/id-extractors.spec.ts +69 -0
- package/src/meta/id-extractors.ts +206 -0
- package/src/meta/keys.ts +568 -0
- package/src/meta/parsers.spec.ts +178 -0
- package/src/meta/parsers.ts +304 -0
- package/src/meta/simple-wappalyzer.d.ts +37 -0
- package/src/meta/tag-detection.spec.ts +134 -0
- package/src/meta/tag-detection.ts +161 -0
- package/src/meta/types.ts +949 -0
- package/src/scraper.ts +19 -13
- package/src/types.ts +49 -55
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,44 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [3.0.0](https://github.com/d-zero-dev/tools/compare/@d-zero/beholder@2.1.6...@d-zero/beholder@3.0.0) (2026-06-16)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
- **beholder:** warn loudly and tripwire-test puppeteer Page.\_client() coverage ([97a07ea](https://github.com/d-zero-dev/tools/commit/97a07ea273e90d50bfede1d68f594ddee9c33268))
|
|
11
|
+
|
|
12
|
+
- feat(beholder)!: expand meta extraction with frontmatter-keys schema and Wappalyzer tag detection ([6ee7861](https://github.com/d-zero-dev/tools/commit/6ee78617aac3fe3d5c022ccfd0df265de0c5310b))
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
|
|
16
|
+
- **beholder:** rewrite getAnchorList with single AX tree + parallel describeNode ([#876](https://github.com/d-zero-dev/tools/issues/876)) ([7e5b089](https://github.com/d-zero-dev/tools/commit/7e5b089695bd1e605d63c6faef2e8bf927bd861f))
|
|
17
|
+
|
|
18
|
+
### BREAKING CHANGES
|
|
19
|
+
|
|
20
|
+
- `Meta` is restructured from flat keys (`noindex`, `canonical`,
|
|
21
|
+
`'og:type'`, `'twitter:card'`, ...) into a nested shape backed by
|
|
22
|
+
`frontmatter-keys.md`. New required fields: `title`, `jsonLd`,
|
|
23
|
+
`speculationRules`, `originTrial`, `tags`, `others`. `getMeta(page)` now takes
|
|
24
|
+
a context object `getMeta(page, { url, html?, statusCode?, headers? }, timeout?)`.
|
|
25
|
+
Old top-level shortcuts (`canonical`, `alternate`, `noindex`, `nofollow`,
|
|
26
|
+
`noarchive`, `'og:*'`, `'twitter:card'`) are removed; values move to
|
|
27
|
+
`meta.link.canonical`, `meta.robots.*`, `meta.og.*`, `meta.twitter.*` etc.
|
|
28
|
+
|
|
29
|
+
Changes:
|
|
30
|
+
|
|
31
|
+
- New `src/meta/` module: `types.ts`, `keys.ts`, `parsers.ts`, `classify.ts`,
|
|
32
|
+
`id-extractors.ts`, `tag-detection.ts`, plus ambient `simple-wappalyzer.d.ts`
|
|
33
|
+
- Browser-side `collectHead()` serializes every `<meta>`, `<link>`, structured-data
|
|
34
|
+
`<script>`, `<base>`, `<iframe>` plus a curated set of `window` globals into
|
|
35
|
+
`RawHeadEntry[]`; Node-side `classify()` maps these to typed Meta fields
|
|
36
|
+
- `simple-wappalyzer` (MIT) added as a dependency for technology detection;
|
|
37
|
+
detected providers run through `id-extractors.ts` for real ID extraction
|
|
38
|
+
(GA4, GTM, UA, FB Pixel, Hotjar, Clarity, ...)
|
|
39
|
+
- Unknown markup is preserved under `Meta.others` (meta/property/httpEquiv/
|
|
40
|
+
itemprop/link/script/iframe buckets) so nothing is silently dropped
|
|
41
|
+
- Tests: parsers/classify/id-extractors/tag-detection units + getMeta
|
|
42
|
+
error/timeout fallback
|
|
43
|
+
|
|
6
44
|
## [2.1.6](https://github.com/d-zero-dev/tools/compare/@d-zero/beholder@2.1.5...@d-zero/beholder@2.1.6) (2026-06-15)
|
|
7
45
|
|
|
8
46
|
### Bug Fixes
|
package/dist/dom-evaluation.d.ts
CHANGED
|
@@ -19,8 +19,12 @@ import type { ElementHandle, Page } from 'puppeteer';
|
|
|
19
19
|
* Default timeout (ms) applied to DOM evaluation operations when the caller does not
|
|
20
20
|
* specify one. Bounds how long a single `page.evaluate` / property read may hang on a
|
|
21
21
|
* page whose main thread is unresponsive.
|
|
22
|
+
*
|
|
23
|
+
* WHY 180s: Aligned with the upstream `Scraper#fetchData` retryable timeout (3 min) so
|
|
24
|
+
* a single phase does not exceed the retry budget while still tolerating large pages
|
|
25
|
+
* (e.g., 1000+ anchors) and slow main threads.
|
|
22
26
|
*/
|
|
23
|
-
export declare const DEFAULT_DOM_EVALUATION_TIMEOUT =
|
|
27
|
+
export declare const DEFAULT_DOM_EVALUATION_TIMEOUT = 180000;
|
|
24
28
|
/**
|
|
25
29
|
* Parameters for {@link getProp}.
|
|
26
30
|
* @template T - The expected type of the property value.
|
|
@@ -65,35 +69,79 @@ export declare function getImageList(page: Page, viewportWidth: number, timeout?
|
|
|
65
69
|
* the accessible name (from the accessibility tree, falling back to `textContent`),
|
|
66
70
|
* and filters out non-HTTP links.
|
|
67
71
|
*
|
|
68
|
-
* WHY
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
+
* WHY Strategy F (single AX-tree fetch + parallel `DOM.describeNode`): the old
|
|
73
|
+
* implementation called `page.accessibility.snapshot({ root })` per anchor, which
|
|
74
|
+
* triggers a CDP round-trip *and* a Chrome-side AX subtree computation (~42ms
|
|
75
|
+
* each). On a page with 1181 anchors that compounded to ~53s. By fetching the
|
|
76
|
+
* full AX tree once and using `DOM.describeNode` in parallel to map element
|
|
77
|
+
* handles back to AX nodes by `backendDOMNodeId`, the same data is collected in
|
|
78
|
+
* ~150ms on the same page — a ~350× speed-up while preserving the original
|
|
79
|
+
* accessible-name semantics. See issue #876 for measurements.
|
|
80
|
+
*
|
|
81
|
+
* WHY the whole operation is wrapped in `raceWithTimeout`: even with bounded
|
|
82
|
+
* per-CDP-call timeouts, a degenerate page (blocked main thread, thousands of
|
|
83
|
+
* anchors, runaway describeNode latency) could chain enough sub-timeouts to
|
|
84
|
+
* exceed the caller's `timeout` budget. The outer race guarantees the function
|
|
85
|
+
* returns within `timeout`, surfacing whatever anchors were collected so far so
|
|
86
|
+
* the upstream scrape phase can continue rather than tripping a retryable retry.
|
|
72
87
|
* @param page - The Puppeteer page to extract anchors from.
|
|
73
88
|
* @param options - Optional URL parsing options (e.g., `disableQueries`).
|
|
74
|
-
* @param timeout -
|
|
89
|
+
* @param timeout - Total time budget in ms for the whole extraction. Defaults to {@link DEFAULT_DOM_EVALUATION_TIMEOUT}.
|
|
75
90
|
* @returns An array of {@link AnchorData} objects for all HTTP(S) links found on the page.
|
|
76
91
|
*/
|
|
77
92
|
export declare function getAnchorList(page: Page, options?: ParseURLOptions, timeout?: number): Promise<AnchorData[]>;
|
|
78
93
|
/**
|
|
79
|
-
*
|
|
94
|
+
* Required context for {@link getMeta}. Provided by the scraper from data it
|
|
95
|
+
* already has on hand (URL it navigated to, response status/headers it received).
|
|
96
|
+
*
|
|
97
|
+
* `html` is optional: when omitted, `getMeta` falls back to `page.content()`
|
|
98
|
+
* to obtain the rendered HTML for the third-party tag detection pass.
|
|
99
|
+
*/
|
|
100
|
+
export type GetMetaContext = {
|
|
101
|
+
/** The fully resolved URL of the page (after redirects). */
|
|
102
|
+
readonly url: string;
|
|
103
|
+
/** Rendered HTML. Falls back to `page.content()` when omitted. */
|
|
104
|
+
readonly html?: string;
|
|
105
|
+
/** Response status code, surfaced to the Wappalyzer driver. */
|
|
106
|
+
readonly statusCode?: number;
|
|
107
|
+
/** Response headers; case is preserved by the caller, lowercased internally. */
|
|
108
|
+
readonly headers?: Record<string, string | string[] | undefined>;
|
|
109
|
+
/**
|
|
110
|
+
* When `true`, the returned `Meta` includes `_raw: RawHeadEntry[]` for
|
|
111
|
+
* debugging. Default `false` to keep the serialized payload small.
|
|
112
|
+
*/
|
|
113
|
+
readonly includeRaw?: boolean;
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* Extracts comprehensive metadata from the page.
|
|
80
117
|
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
118
|
+
* Two passes happen in parallel:
|
|
119
|
+
* 1. Browser-side `collectHead()` serializes every `<meta>`, `<link>`,
|
|
120
|
+
* relevant `<script>`, `<base>`, `<noscript>`/`<iframe>` and a curated
|
|
121
|
+
* set of `window` globals into a `RawHeadEntry[]`. Node-side `classify()`
|
|
122
|
+
* then maps those entries to typed `Meta` fields using the lookup tables
|
|
123
|
+
* in `./meta/keys.ts`, with unknown entries preserved in `Meta.others`.
|
|
124
|
+
* 2. `detectTags()` runs `simple-wappalyzer` over the page HTML to produce
|
|
125
|
+
* `Meta.tags` (technology detection + real-ID extraction).
|
|
84
126
|
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
127
|
+
* The whole call is wrapped in `raceWithTimeout`. On timeout an empty `Meta`
|
|
128
|
+
* (with `title: ''` and empty required arrays/objects) is returned.
|
|
129
|
+
* @param page
|
|
130
|
+
* @param context
|
|
131
|
+
* @param timeout
|
|
132
|
+
* @example
|
|
133
|
+
* ```ts
|
|
134
|
+
* const meta = await getMeta(page, {
|
|
135
|
+
* url: 'https://example.com/',
|
|
136
|
+
* html: await page.content(),
|
|
137
|
+
* statusCode: response.status,
|
|
138
|
+
* headers: response.headers,
|
|
139
|
+
* });
|
|
140
|
+
* console.log(meta.title); // <title> text
|
|
141
|
+
* console.log(meta.og?.image); // og:image[] array
|
|
142
|
+
* console.log(meta.robots?.noindex); // parsed robots
|
|
143
|
+
* console.log(meta.tags.detected.Analytics); // Wappalyzer hits
|
|
144
|
+
* console.log(meta.tags.entries.find(e => e.provider === 'Google Analytics')?.id);
|
|
145
|
+
* ```
|
|
98
146
|
*/
|
|
99
|
-
export declare function getMeta(page: Page, timeout?: number): Promise<Meta>;
|
|
147
|
+
export declare function getMeta(page: Page, context: GetMetaContext, timeout?: number): Promise<Meta>;
|