@d-zero/beholder 3.1.5 → 4.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 +14 -0
- package/README.md +38 -3
- package/dist/get-main-contents.d.ts +48 -0
- package/dist/get-main-contents.js +270 -0
- package/dist/index.d.ts +1 -1
- package/dist/is-html-content-type.d.ts +18 -0
- package/dist/is-html-content-type.js +20 -0
- package/dist/measure-scroll-height.d.ts +23 -0
- package/dist/measure-scroll-height.js +44 -0
- package/dist/scraper.js +53 -23
- package/dist/types.d.ts +183 -0
- package/package.json +3 -3
- package/src/get-main-contents.spec.ts +282 -0
- package/src/get-main-contents.ts +306 -0
- package/src/index.ts +11 -0
- package/src/is-html-content-type.spec.ts +17 -0
- package/src/is-html-content-type.ts +20 -0
- package/src/measure-scroll-height.spec.ts +47 -0
- package/src/measure-scroll-height.ts +52 -0
- package/src/scraper.ts +67 -31
- package/src/types.ts +196 -0
- package/tsconfig.tsbuildinfo +1 -1
package/dist/scraper.js
CHANGED
|
@@ -43,8 +43,11 @@ import { retry as retryable } from '@d-zero/shared/retry';
|
|
|
43
43
|
import { TypedAwaitEventEmitter as EventEmitter } from '@d-zero/shared/typed-await-event-emitter';
|
|
44
44
|
import { resourceLog, scraperLog } from './debug.js';
|
|
45
45
|
import { DEFAULT_DOM_EVALUATION_TIMEOUT, getAnchorList, getImageList, getMeta, } from './dom-evaluation.js';
|
|
46
|
+
import { getMainContents } from './get-main-contents.js';
|
|
46
47
|
import { isError } from './is-error.js';
|
|
48
|
+
import { isHtmlContentType } from './is-html-content-type.js';
|
|
47
49
|
import { keywordCheck } from './keyword-check.js';
|
|
50
|
+
import { measureScrollHeight } from './measure-scroll-height.js';
|
|
48
51
|
import { emptyMeta } from './meta/classify.js';
|
|
49
52
|
import { findDisconnectionFailures } from './network-disconnection.js';
|
|
50
53
|
import { parseUrl } from './parse-url.js';
|
|
@@ -95,7 +98,10 @@ let Scraper = (() => {
|
|
|
95
98
|
})];
|
|
96
99
|
_private_fetchImages_decorators = [retryable({
|
|
97
100
|
timeout: 20 * 60 * 1000,
|
|
98
|
-
fallback:
|
|
101
|
+
fallback: {
|
|
102
|
+
imageList: [],
|
|
103
|
+
scrollHeight: { desktop: null, mobile: null },
|
|
104
|
+
},
|
|
99
105
|
onWait(determinedInterval, retryCount, methodName, error) {
|
|
100
106
|
void this.emit('changePhase', {
|
|
101
107
|
pid: process.pid,
|
|
@@ -267,7 +273,7 @@ let Scraper = (() => {
|
|
|
267
273
|
const contentType = responseHeaders['content-type']?.split(';')[0] || null;
|
|
268
274
|
const _contentLength = Number.parseInt(responseHeaders['content-length'] ?? '');
|
|
269
275
|
const contentLength = Number.isFinite(_contentLength) ? _contentLength : null;
|
|
270
|
-
if (contentType
|
|
276
|
+
if (!isHtmlContentType(contentType)) {
|
|
271
277
|
return {
|
|
272
278
|
url,
|
|
273
279
|
isTarget: false,
|
|
@@ -282,6 +288,8 @@ let Scraper = (() => {
|
|
|
282
288
|
imageList: [],
|
|
283
289
|
anchorList: [],
|
|
284
290
|
html: '',
|
|
291
|
+
mainContents: null,
|
|
292
|
+
scrollHeight: null,
|
|
285
293
|
isSkipped: false,
|
|
286
294
|
};
|
|
287
295
|
}
|
|
@@ -326,6 +334,8 @@ let Scraper = (() => {
|
|
|
326
334
|
imageList: [],
|
|
327
335
|
anchorList: [],
|
|
328
336
|
html,
|
|
337
|
+
mainContents: null,
|
|
338
|
+
scrollHeight: null,
|
|
329
339
|
isSkipped: false,
|
|
330
340
|
};
|
|
331
341
|
}
|
|
@@ -347,6 +357,9 @@ let Scraper = (() => {
|
|
|
347
357
|
.join(', ');
|
|
348
358
|
throw new Error(`Network disconnection detected during page load: ${errorSummary}`);
|
|
349
359
|
}
|
|
360
|
+
const mainContents = await getMainContents(page, {
|
|
361
|
+
mainContentSelector: options?.mainContentSelector,
|
|
362
|
+
});
|
|
350
363
|
void this.emit('changePhase', {
|
|
351
364
|
pid: process.pid,
|
|
352
365
|
name: 'getAnchors',
|
|
@@ -368,18 +381,23 @@ let Scraper = (() => {
|
|
|
368
381
|
statusCode: status,
|
|
369
382
|
headers: responseHeaders ?? undefined,
|
|
370
383
|
}, domEvaluationTimeout);
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
})
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
384
|
+
let imageList = [];
|
|
385
|
+
let scrollHeight = null;
|
|
386
|
+
if (captureImages) {
|
|
387
|
+
void this.emit('changePhase', {
|
|
388
|
+
pid: process.pid,
|
|
389
|
+
name: 'extractImages',
|
|
390
|
+
url,
|
|
391
|
+
isExternal,
|
|
392
|
+
message: `%countdown(${domEvaluationTimeout},extractImages_${url.withoutHash},s)%s`,
|
|
393
|
+
});
|
|
394
|
+
const fetched = await this.#fetchImages(page, url.withoutHashAndAuth, isExternal, imageLoadTimeout, domEvaluationTimeout);
|
|
395
|
+
imageList = fetched.imageList;
|
|
396
|
+
scrollHeight = fetched.scrollHeight;
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
scrollHeight = await measureScrollHeight(page);
|
|
400
|
+
}
|
|
383
401
|
return {
|
|
384
402
|
url,
|
|
385
403
|
isTarget: true,
|
|
@@ -394,6 +412,8 @@ let Scraper = (() => {
|
|
|
394
412
|
anchorList,
|
|
395
413
|
imageList,
|
|
396
414
|
html,
|
|
415
|
+
mainContents,
|
|
416
|
+
scrollHeight,
|
|
397
417
|
isSkipped: false,
|
|
398
418
|
};
|
|
399
419
|
}, "#fetchData") }, _private_fetchData_decorators, { kind: "method", name: "#fetchData", static: false, private: true, access: { has: obj => #fetchData in obj, get: obj => obj.#fetchData }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
@@ -404,7 +424,9 @@ let Scraper = (() => {
|
|
|
404
424
|
{ key: 'mobile-small', preset: devicePresets['mobile-small'] },
|
|
405
425
|
];
|
|
406
426
|
const imageList = [];
|
|
427
|
+
const scrollHeight = { desktop: null, mobile: null };
|
|
407
428
|
for (const { key, preset } of devices) {
|
|
429
|
+
const scrollKey = key === 'desktop-compact' ? 'desktop' : 'mobile';
|
|
408
430
|
try {
|
|
409
431
|
void this.emit('changePhase', {
|
|
410
432
|
pid: process.pid,
|
|
@@ -421,6 +443,7 @@ let Scraper = (() => {
|
|
|
421
443
|
timeout: 5000,
|
|
422
444
|
maxScrollHeight: MAX_SCROLL_HEIGHT,
|
|
423
445
|
});
|
|
446
|
+
scrollHeight[scrollKey] = scanResult.scrollHeight;
|
|
424
447
|
if (!scanResult.scrolled) {
|
|
425
448
|
void this.emit('changePhase', {
|
|
426
449
|
pid: process.pid,
|
|
@@ -465,7 +488,7 @@ let Scraper = (() => {
|
|
|
465
488
|
});
|
|
466
489
|
}
|
|
467
490
|
}
|
|
468
|
-
return imageList;
|
|
491
|
+
return { imageList, scrollHeight };
|
|
469
492
|
}, "#fetchImages") }, _private_fetchImages_decorators, { kind: "method", name: "#fetchImages", static: false, private: true, access: { has: obj => #fetchImages in obj, get: obj => obj.#fetchImages }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
470
493
|
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
471
494
|
}
|
|
@@ -540,6 +563,8 @@ let Scraper = (() => {
|
|
|
540
563
|
imageList: [],
|
|
541
564
|
anchorList: [],
|
|
542
565
|
html: '',
|
|
566
|
+
mainContents: null,
|
|
567
|
+
scrollHeight: null,
|
|
543
568
|
isSkipped: false,
|
|
544
569
|
};
|
|
545
570
|
void this.emit('changePhase', {
|
|
@@ -565,11 +590,13 @@ let Scraper = (() => {
|
|
|
565
590
|
pageData: {
|
|
566
591
|
...headResult,
|
|
567
592
|
isTarget: false,
|
|
593
|
+
mainContents: headResult.mainContents ?? null,
|
|
594
|
+
scrollHeight: headResult.scrollHeight ?? null,
|
|
568
595
|
},
|
|
569
596
|
resources,
|
|
570
597
|
};
|
|
571
598
|
}
|
|
572
|
-
if (headResult === null || headResult.contentType
|
|
599
|
+
if (headResult === null || isHtmlContentType(headResult.contentType)) {
|
|
573
600
|
const fetchResult = await this.#fetchData(page, url, isExternal, captureImages, imageLoadTimeout, resources, failedRequests, options).catch((error) => {
|
|
574
601
|
if (error instanceof Error) {
|
|
575
602
|
return error;
|
|
@@ -732,12 +759,12 @@ let Scraper = (() => {
|
|
|
732
759
|
* changes and triggers a reload. Isolating each device preset allows partial
|
|
733
760
|
* results — if one viewport fails, the other can still succeed.
|
|
734
761
|
*
|
|
735
|
-
* WHY retryable with 20-min timeout and
|
|
736
|
-
* best-effort. If all retries fail,
|
|
737
|
-
* failing the entire page scrape. The 20-min wall clock
|
|
738
|
-
* whose mobile-small `scrollHeight` reaches ~300k px
|
|
739
|
-
* responsive data tables, which take ~5 min to scroll). A shorter
|
|
740
|
-
* causes a second retry to start while the previous attempt's
|
|
762
|
+
* WHY retryable with 20-min timeout and empty fallback: Image extraction is
|
|
763
|
+
* best-effort. If all retries fail, empty images and null scroll heights are
|
|
764
|
+
* returned rather than failing the entire page scrape. The 20-min wall clock
|
|
765
|
+
* accommodates pages whose mobile-small `scrollHeight` reaches ~300k px
|
|
766
|
+
* (observed on responsive data tables, which take ~5 min to scroll). A shorter
|
|
767
|
+
* timeout causes a second retry to start while the previous attempt's
|
|
741
768
|
* `scrollAllOver` is still running its `page.evaluate` calls in the
|
|
742
769
|
* background — `Promise.race` in `retry.ts` does not cancel `fn()`. The
|
|
743
770
|
* collision then surfaces as "Attempted to use detached Frame" or
|
|
@@ -749,12 +776,15 @@ let Scraper = (() => {
|
|
|
749
776
|
* preset entirely keeps the timeout-vs-background-evaluate collision from
|
|
750
777
|
* ever being triggered, at the cost of losing that viewport's image data
|
|
751
778
|
* for those pages. See {@link MAX_SCROLL_HEIGHT} for the chosen threshold.
|
|
779
|
+
*
|
|
780
|
+
* Scroll heights are still recorded when scrolling is skipped for the
|
|
781
|
+
* height limit — the measurement from `beforePageScan` is kept.
|
|
752
782
|
* @param page - Puppeteer page instance
|
|
753
783
|
* @param url - The page URL string (without hash and auth)
|
|
754
784
|
* @param isExternal - Whether the page is external
|
|
755
785
|
* @param imageLoadTimeout - Timeout (ms) for waiting images to complete loading
|
|
756
786
|
* @param domEvaluationTimeout - Timeout (ms) for the in-page image extraction `page.evaluate`
|
|
757
|
-
* @returns
|
|
787
|
+
* @returns Image elements plus desktop/mobile scroll heights from the scan path
|
|
758
788
|
*/
|
|
759
789
|
get #fetchImages() { return _private_fetchImages_descriptor.value; }
|
|
760
790
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -42,9 +42,187 @@ export type PageData = {
|
|
|
42
42
|
imageList: ImageElement[];
|
|
43
43
|
/** HTML snapshot of the rendered DOM. */
|
|
44
44
|
html: string;
|
|
45
|
+
/**
|
|
46
|
+
* Quantitative main-content metrics extracted in the browser after load.
|
|
47
|
+
* `null` when the page is non-HTML, external, non-HTTP, or otherwise not fully scraped.
|
|
48
|
+
* When HTML is scraped but no main region is found, this is still an object with empty arrays and `wordCount: 0`.
|
|
49
|
+
*/
|
|
50
|
+
mainContents: MainContentsData | null;
|
|
51
|
+
/**
|
|
52
|
+
* `document.body.scrollHeight` at desktop-compact and mobile-small viewports.
|
|
53
|
+
* `null` when not measured (non-HTML, external, non-HTTP, or skipped).
|
|
54
|
+
*/
|
|
55
|
+
scrollHeight: ScrollHeightData | null;
|
|
45
56
|
/** Always `false` for successfully scraped pages. See {@link SkippedPageData} for skipped pages. */
|
|
46
57
|
isSkipped: false;
|
|
47
58
|
};
|
|
59
|
+
/**
|
|
60
|
+
* `document.body.scrollHeight` measured at the scraper's desktop and mobile device presets.
|
|
61
|
+
*/
|
|
62
|
+
export type ScrollHeightData = {
|
|
63
|
+
/** Height at `desktop-compact` (width 1280), or `null` if that preset failed. */
|
|
64
|
+
desktop: number | null;
|
|
65
|
+
/** Height at `mobile-small` (width 320 @ 2x), or `null` if that preset failed. */
|
|
66
|
+
mobile: number | null;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Quantitative metrics for the detected main content region of a page.
|
|
70
|
+
* @example
|
|
71
|
+
* ```ts
|
|
72
|
+
* const { mainContents } = pageData;
|
|
73
|
+
* if (mainContents) {
|
|
74
|
+
* console.log(mainContents.wordCount, mainContents.headings.length);
|
|
75
|
+
* }
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export type MainContentsData = {
|
|
79
|
+
/** `document.title` trimmed. Empty string when the title is empty. */
|
|
80
|
+
title: string;
|
|
81
|
+
/**
|
|
82
|
+
* Identifying info for the detected main region element.
|
|
83
|
+
* `null` when no selector matched (arrays are empty and `wordCount` is `0`).
|
|
84
|
+
*/
|
|
85
|
+
main: MainContentsMainTag | null;
|
|
86
|
+
/**
|
|
87
|
+
* Character count of the main region's `textContent` after whitespace removal.
|
|
88
|
+
* `0` when no main region was found or the text is whitespace-only.
|
|
89
|
+
*/
|
|
90
|
+
wordCount: number;
|
|
91
|
+
/**
|
|
92
|
+
* Character count of `document.body` `textContent` after whitespace removal.
|
|
93
|
+
* Measured even when no main region is found (`0` if `body` is missing).
|
|
94
|
+
*/
|
|
95
|
+
bodyWordCount: number;
|
|
96
|
+
/** Headings (`h1`–`h6`) inside the main region, in DOM order. */
|
|
97
|
+
headings: MainContentsHeading[];
|
|
98
|
+
/** Images (`img`, `input[type=image]`) inside the main region, in DOM order. */
|
|
99
|
+
images: MainContentsImage[];
|
|
100
|
+
/** Tables inside the main region, in DOM order. */
|
|
101
|
+
tables: MainContentsTable[];
|
|
102
|
+
/** Button-like elements inside the main region, in DOM order. */
|
|
103
|
+
buttons: MainContentsButton[];
|
|
104
|
+
/** Iframes inside the main region, in DOM order. */
|
|
105
|
+
iframes: MainContentsIframe[];
|
|
106
|
+
/** Videos inside the main region, in DOM order. */
|
|
107
|
+
videos: MainContentsVideo[];
|
|
108
|
+
/** Audios inside the main region, in DOM order. */
|
|
109
|
+
audios: MainContentsAudio[];
|
|
110
|
+
/** Canvases inside the main region, in DOM order. */
|
|
111
|
+
canvases: MainContentsCanvas[];
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Identifying information about the DOM element used as the main content area.
|
|
115
|
+
*/
|
|
116
|
+
export type MainContentsMainTag = {
|
|
117
|
+
/** Element tag name (e.g. `"MAIN"`, `"DIV"`), as reported by `nodeName`. */
|
|
118
|
+
nodeName: string;
|
|
119
|
+
/** The element's `id`, or `null` when empty/absent. */
|
|
120
|
+
id: string | null;
|
|
121
|
+
/** CSS class names on the element. */
|
|
122
|
+
classList: string[];
|
|
123
|
+
/** The WAI-ARIA `role` attribute value, or `null` when absent. */
|
|
124
|
+
role: string | null;
|
|
125
|
+
/**
|
|
126
|
+
* A simple diagnostic CSS selector built from tag + id + classes
|
|
127
|
+
* (not `@medv/finder`). // cspell:disable-line
|
|
128
|
+
*/
|
|
129
|
+
selector: string;
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* A heading element (`h1`–`h6`) found within the main content area.
|
|
133
|
+
*/
|
|
134
|
+
export type MainContentsHeading = {
|
|
135
|
+
/** Heading text after whitespace removal, or `null` when empty. */
|
|
136
|
+
text: string | null;
|
|
137
|
+
/** Heading level from the tag name. */
|
|
138
|
+
level: 1 | 2 | 3 | 4 | 5 | 6;
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* An `<img>` or `<input type="image">` element found in the main content.
|
|
142
|
+
*/
|
|
143
|
+
export type MainContentsImage = {
|
|
144
|
+
/** Resolved absolute `src` URL. */
|
|
145
|
+
src: string;
|
|
146
|
+
/** `alt` attribute value (may be an empty string). */
|
|
147
|
+
alt: string;
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* Structural summary of a `<table>` found within the main content.
|
|
151
|
+
*/
|
|
152
|
+
export type MainContentsTable = {
|
|
153
|
+
/** Number of `<tr>` elements. */
|
|
154
|
+
rows: number;
|
|
155
|
+
/** Number of `th`/`td` cells in the first row, or `0` when there is no row. */
|
|
156
|
+
cols: number;
|
|
157
|
+
/** Whether the table contains a `<thead>`. */
|
|
158
|
+
hasHeader: boolean;
|
|
159
|
+
/** Whether the table contains a `<tfoot>`. */
|
|
160
|
+
hasFooter: boolean;
|
|
161
|
+
/** Whether any cell uses `colspan` or `rowspan`. */
|
|
162
|
+
hasMergedCell: boolean;
|
|
163
|
+
};
|
|
164
|
+
/**
|
|
165
|
+
* A button-like element found in the main content
|
|
166
|
+
* (`button`, `[role=button]`, `[class*=button]`, `[class*=btn]`).
|
|
167
|
+
*/
|
|
168
|
+
export type MainContentsButton = {
|
|
169
|
+
/** Element tag name (e.g. `"BUTTON"`, `"A"`, `"DIV"`). */
|
|
170
|
+
nodeName: string;
|
|
171
|
+
/** `role` attribute, or `null` when absent. */
|
|
172
|
+
role: string | null;
|
|
173
|
+
/** `type` for `<button>` / `<input>`, otherwise `null`. */
|
|
174
|
+
type: string | null;
|
|
175
|
+
/** Label text after whitespace removal, or `null` when empty. */
|
|
176
|
+
text: string | null;
|
|
177
|
+
/** `true` when `disabled` or `aria-disabled="true"`. */
|
|
178
|
+
disabled: boolean;
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* An `<iframe>` found in the main content.
|
|
182
|
+
*/
|
|
183
|
+
export type MainContentsIframe = {
|
|
184
|
+
/** Resolved absolute `src` URL (empty string when unset). */
|
|
185
|
+
src: string;
|
|
186
|
+
/** `title` attribute, or `null` when the attribute is absent. */
|
|
187
|
+
title: string | null;
|
|
188
|
+
/** Raw `width` attribute string, or `null` when absent. */
|
|
189
|
+
width: string | null;
|
|
190
|
+
/** Raw `height` attribute string, or `null` when absent. */
|
|
191
|
+
height: string | null;
|
|
192
|
+
};
|
|
193
|
+
/**
|
|
194
|
+
* A `<video>` found in the main content.
|
|
195
|
+
*/
|
|
196
|
+
export type MainContentsVideo = {
|
|
197
|
+
/**
|
|
198
|
+
* Resolved media URL: `currentSrc`, else `src` attribute, else first `source[src]`, else `""`.
|
|
199
|
+
*/
|
|
200
|
+
src: string;
|
|
201
|
+
/** Resolved `poster` URL, or `null` when unset. */
|
|
202
|
+
poster: string | null;
|
|
203
|
+
/** IDL `width` in pixels. */
|
|
204
|
+
width: number;
|
|
205
|
+
/** IDL `height` in pixels. */
|
|
206
|
+
height: number;
|
|
207
|
+
};
|
|
208
|
+
/**
|
|
209
|
+
* An `<audio>` found in the main content.
|
|
210
|
+
*/
|
|
211
|
+
export type MainContentsAudio = {
|
|
212
|
+
/**
|
|
213
|
+
* Resolved media URL: `currentSrc`, else `src` attribute, else first `source[src]`, else `""`.
|
|
214
|
+
*/
|
|
215
|
+
src: string;
|
|
216
|
+
};
|
|
217
|
+
/**
|
|
218
|
+
* A `<canvas>` found in the main content.
|
|
219
|
+
*/
|
|
220
|
+
export type MainContentsCanvas = {
|
|
221
|
+
/** IDL bitmap width. */
|
|
222
|
+
width: number;
|
|
223
|
+
/** IDL bitmap height. */
|
|
224
|
+
height: number;
|
|
225
|
+
};
|
|
48
226
|
/**
|
|
49
227
|
* Information about an image element found on a page.
|
|
50
228
|
*/
|
|
@@ -316,4 +494,9 @@ export type ScraperOptions = {
|
|
|
316
494
|
* Default: 180_000 (180s, aligned with the upstream retryable timeout).
|
|
317
495
|
*/
|
|
318
496
|
domEvaluationTimeout?: number;
|
|
497
|
+
/**
|
|
498
|
+
* CSS selector prepended to the default main-content detection list
|
|
499
|
+
* (highest priority). When omitted or `null`, only the built-in selectors are used.
|
|
500
|
+
*/
|
|
501
|
+
mainContentSelector?: string | null;
|
|
319
502
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@d-zero/beholder",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Page-level scraper for web crawling and auditing",
|
|
5
5
|
"author": "D-ZERO",
|
|
6
6
|
"license": "MIT",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"clean": "tsc --build --clean"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@d-zero/puppeteer-page-scan": "4.6.
|
|
23
|
+
"@d-zero/puppeteer-page-scan": "4.6.5",
|
|
24
24
|
"@d-zero/shared": "0.22.2",
|
|
25
25
|
"debug": "4.4.3",
|
|
26
26
|
"puppeteer": "25.3.0",
|
|
@@ -36,5 +36,5 @@
|
|
|
36
36
|
"url": "https://github.com/d-zero-dev/tools.git",
|
|
37
37
|
"directory": "packages/@d-zero/beholder"
|
|
38
38
|
},
|
|
39
|
-
"gitHead": "
|
|
39
|
+
"gitHead": "581df4a1796bd864f30980c759e6ada99914ffb7"
|
|
40
40
|
}
|