@nitpicker/core 0.23.1 → 0.25.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/README.md +165 -35
- package/lib/assert-u32-id.d.ts +15 -0
- package/lib/assert-u32-id.js +22 -0
- package/lib/check-native-executor-config.d.ts +21 -0
- package/lib/check-native-executor-config.js +24 -0
- package/lib/compute-alias-keys.d.ts +28 -0
- package/lib/compute-alias-keys.js +30 -0
- package/lib/compute-body-hash.d.ts +22 -0
- package/lib/compute-body-hash.js +25 -0
- package/lib/compute-url-sort-ranks.d.ts +28 -0
- package/lib/compute-url-sort-ranks.js +49 -0
- package/lib/create-isolated-cluster-run.d.ts +35 -0
- package/lib/create-isolated-cluster-run.js +52 -0
- package/lib/create-template-classification-run.d.ts +49 -0
- package/lib/create-template-classification-run.js +386 -0
- package/lib/group-alias-candidates.d.ts +40 -0
- package/lib/group-alias-candidates.js +104 -0
- package/lib/load-native-binding.d.ts +26 -0
- package/lib/load-native-binding.js +152 -0
- package/lib/match-js-license-comment-signal.d.ts +19 -0
- package/lib/match-js-license-comment-signal.js +21 -0
- package/lib/match-selector-html.d.ts +41 -0
- package/lib/match-selector-html.js +42 -0
- package/lib/match-selector-snapshots.d.ts +40 -0
- package/lib/match-selector-snapshots.js +57 -0
- package/lib/match-structural-technology-signals.d.ts +23 -0
- package/lib/match-structural-technology-signals.js +25 -0
- package/lib/max-decoded-snapshot-bytes.d.ts +22 -0
- package/lib/max-decoded-snapshot-bytes.js +35 -0
- package/lib/native-binding.d.ts +9 -0
- package/lib/native-binding.js +9 -0
- package/lib/pack-stored-snapshots.d.ts +23 -0
- package/lib/pack-stored-snapshots.js +53 -0
- package/lib/run-with-signal.d.ts +27 -0
- package/lib/run-with-signal.js +54 -0
- package/lib/search-html-document.d.ts +32 -0
- package/lib/search-html-document.js +31 -0
- package/lib/search-html-snapshots.d.ts +46 -0
- package/lib/search-html-snapshots.js +54 -0
- package/lib/template-block-sample-size.d.ts +13 -0
- package/lib/template-block-sample-size.js +14 -0
- package/lib/template-exact-page-limit.d.ts +18 -0
- package/lib/template-exact-page-limit.js +19 -0
- package/lib/template-memory-budget.d.ts +16 -0
- package/lib/template-memory-budget.js +17 -0
- package/lib/to-native-selector-plan.d.ts +16 -0
- package/lib/to-native-selector-plan.js +40 -0
- package/lib/to-u32-ids.d.ts +14 -0
- package/lib/to-u32-ids.js +23 -0
- package/lib/types.d.ts +736 -248
- package/lib/validate-search-pattern.d.ts +25 -0
- package/lib/validate-search-pattern.js +27 -0
- package/package.json +83 -16
- package/lib/core.d.ts +0 -4
- package/lib/core.js +0 -4
- package/lib/discover-analyze-plugins.d.ts +0 -14
- package/lib/discover-analyze-plugins.js +0 -32
- package/lib/get-table-cache-root.d.ts +0 -18
- package/lib/get-table-cache-root.js +0 -22
- package/lib/hooks/define-plugin.d.ts +0 -68
- package/lib/hooks/define-plugin.js +0 -69
- package/lib/import-modules.d.ts +0 -24
- package/lib/import-modules.js +0 -38
- package/lib/load-plugin-settings.d.ts +0 -44
- package/lib/load-plugin-settings.js +0 -125
- package/lib/nitpicker.d.ts +0 -150
- package/lib/nitpicker.js +0 -437
- package/lib/page-analysis-worker.d.ts +0 -58
- package/lib/page-analysis-worker.js +0 -68
- package/lib/read-plugin-labels.d.ts +0 -15
- package/lib/read-plugin-labels.js +0 -30
- package/lib/table.d.ts +0 -75
- package/lib/table.js +0 -132
- package/lib/url-event-bus.d.ts +0 -33
- package/lib/url-event-bus.js +0 -21
- package/lib/worker/runner.d.ts +0 -25
- package/lib/worker/runner.js +0 -31
- package/lib/worker/types.d.ts +0 -23
- package/lib/worker/types.js +0 -1
- package/lib/worker/worker-pool.d.ts +0 -82
- package/lib/worker/worker-pool.js +0 -278
- package/lib/worker/worker.d.ts +0 -34
- package/lib/worker/worker.js +0 -108
package/lib/types.d.ts
CHANGED
|
@@ -1,306 +1,794 @@
|
|
|
1
|
-
import type { Lanes } from '@d-zero/dealer';
|
|
2
|
-
import type { ExURL as URL } from '@d-zero/shared/parse-url';
|
|
3
|
-
import type { TableValue, Violation } from '@nitpicker/types';
|
|
4
|
-
import type { DOMWindow } from 'jsdom';
|
|
5
1
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
2
|
+
* The functions the native addon (`crates/nitpicker_napi`) exports, as seen
|
|
3
|
+
* from JavaScript. Kept in sync with the `#[napi]` functions by hand — the
|
|
4
|
+
* addon's own generated typings are not used, so every export goes through a
|
|
5
|
+
* documented one-function-per-file wrapper instead.
|
|
6
|
+
*/
|
|
7
|
+
export interface NativeBinding {
|
|
8
|
+
/** See `compute-body-hash.ts`. */
|
|
9
|
+
computeBodyHash(html: Uint8Array): Buffer;
|
|
10
|
+
/** See `match-structural-technology-signals.ts`. */
|
|
11
|
+
matchStructuralTechnologySignals(html: Uint8Array): StructuralTechnologySignal[];
|
|
12
|
+
/** See `match-js-license-comment-signal.ts`. */
|
|
13
|
+
matchJsLicenseCommentSignal(script: Uint8Array): StructuralTechnologySignal | null;
|
|
14
|
+
/** See `match-selector-html.ts`. */
|
|
15
|
+
matchSelectorHtml(plan: NativeSelectorPlan, html: Uint8Array): HtmlMatchOutcome;
|
|
16
|
+
/** See `match-selector-snapshots.ts`. */
|
|
17
|
+
matchSelectorSnapshots(plan: NativeSelectorPlan, bodies: Uint8Array, offsets: Uint32Array, codecs: Uint8Array, maxDecodedBytes: number, cancelToken?: NativeCancelToken): Promise<Uint8Array>;
|
|
18
|
+
/** See `validate-search-pattern.ts`. */
|
|
19
|
+
validateSearchPattern(pattern: NativeSearchPattern): boolean;
|
|
20
|
+
/** See `search-html-document.ts`. */
|
|
21
|
+
searchHtmlDocument(pattern: NativeSearchPattern, html: Uint8Array, snippetLength: number): SnapshotSearchOutcome;
|
|
22
|
+
/** See `search-html-snapshots.ts`. */
|
|
23
|
+
searchHtmlSnapshots(pattern: NativeSearchPattern, snippetLength: number, bodies: Uint8Array, offsets: Uint32Array, codecs: Uint8Array, maxDecodedBytes: number, cancelToken?: NativeCancelToken): Promise<{
|
|
24
|
+
matchCounts: number[];
|
|
25
|
+
snippets: string[];
|
|
26
|
+
}>;
|
|
27
|
+
/** See `check-native-executor-config.ts`. */
|
|
28
|
+
checkNativeExecutorConfig(): void;
|
|
29
|
+
/** See `run-with-signal.ts`. */
|
|
30
|
+
NativeCancelToken: new () => NativeCancelToken;
|
|
31
|
+
/** See `compute-url-sort-ranks.ts`. */
|
|
32
|
+
computeUrlSortRanks(urls: Uint8Array, offsets: Uint32Array): Uint32Array;
|
|
33
|
+
/** See `compute-alias-keys.ts`. */
|
|
34
|
+
computeAliasKeys(url: string): {
|
|
35
|
+
tierA: string;
|
|
36
|
+
tierB: string;
|
|
37
|
+
} | null;
|
|
38
|
+
/** See `group-alias-candidates.ts`. */
|
|
39
|
+
groupAliasCandidates(input: NativeAliasCandidates): Uint32Array;
|
|
40
|
+
/** See `create-isolated-cluster-run.ts`. */
|
|
41
|
+
IsolatedClusterRun: new (candidateIds: Uint32Array, redirects: Uint32Array) => NativeIsolatedClusterRun;
|
|
42
|
+
/** See `create-template-classification-run.ts`. */
|
|
43
|
+
TemplateClassificationRun: new (onProgress?: (event: NativeTemplateClassificationProgress) => void, memoryBudget?: number) => NativeTemplateClassificationRun;
|
|
44
|
+
/** See `template-block-sample-size.ts`. */
|
|
45
|
+
TEMPLATE_BLOCK_SAMPLE_SIZE: number;
|
|
46
|
+
/** See `template-memory-budget.ts`. */
|
|
47
|
+
TEMPLATE_MEMORY_BUDGET: number;
|
|
48
|
+
/** See `template-exact-page-limit.ts`. */
|
|
49
|
+
TEMPLATE_EXACT_PAGE_LIMIT: number;
|
|
50
|
+
/** See `max-decoded-snapshot-bytes.ts`. */
|
|
51
|
+
MAX_DECODED_SNAPSHOT_BYTES: number;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Stops the asynchronous native calls it was passed to. Raising it is one-way;
|
|
55
|
+
* a call that sees it raised rejects with code `Cancelled`.
|
|
13
56
|
*/
|
|
14
|
-
export interface
|
|
57
|
+
export interface NativeCancelToken {
|
|
58
|
+
/** Raises the token. */
|
|
59
|
+
cancel(): void;
|
|
60
|
+
}
|
|
61
|
+
/** How two adjacent compounds of a complex selector are joined. */
|
|
62
|
+
export type SelectorCombinator = 'descendant' | 'child';
|
|
63
|
+
/** The attribute selector operators the matcher evaluates. */
|
|
64
|
+
export type SelectorAttributeOperator = 'exists' | 'equals' | 'includes' | 'dash' | 'prefix' | 'suffix' | 'substring';
|
|
65
|
+
/** One attribute condition of a compound (`[a]`, `[a^=v]`, `.cls`, `#id`, ...). */
|
|
66
|
+
export interface SelectorAttributeTest {
|
|
67
|
+
/** Lower-cased attribute name. */
|
|
68
|
+
readonly name: string;
|
|
69
|
+
readonly operator: SelectorAttributeOperator;
|
|
70
|
+
/** The selector's literal value (empty for `exists`). */
|
|
71
|
+
readonly value: string;
|
|
72
|
+
/** Whether values compare ignoring ASCII case. */
|
|
73
|
+
readonly ignoreCase: boolean;
|
|
74
|
+
}
|
|
75
|
+
/** An `An+B` position test (`:nth-child`, `:nth-of-type`, `:first-*`). */
|
|
76
|
+
export interface SelectorNthTest {
|
|
77
|
+
readonly kind: 'child' | 'of-type';
|
|
78
|
+
readonly a: number;
|
|
79
|
+
readonly b: number;
|
|
80
|
+
}
|
|
81
|
+
/** The compound inside `:not(...)` — a compound without negations of its own. */
|
|
82
|
+
export interface SelectorNegatedCompound {
|
|
83
|
+
/** Lower-cased tag name, or `null` for any element. */
|
|
84
|
+
readonly tag: string | null;
|
|
85
|
+
readonly attributes: readonly SelectorAttributeTest[];
|
|
86
|
+
readonly nth: readonly SelectorNthTest[];
|
|
87
|
+
}
|
|
88
|
+
/** A compound selector: every condition must hold on one element. */
|
|
89
|
+
export interface SelectorCompound extends SelectorNegatedCompound {
|
|
90
|
+
readonly negations: readonly SelectorNegatedCompound[];
|
|
91
|
+
}
|
|
92
|
+
/** One comma alternative of a selector list. */
|
|
93
|
+
export interface SelectorAlternative {
|
|
94
|
+
/** Compounds in source (ancestor-first) order; between 1 and 30. */
|
|
95
|
+
readonly compounds: readonly SelectorCompound[];
|
|
96
|
+
/** `combinators[j - 1]` joins `compounds[j - 1]` and `compounds[j]`. */
|
|
97
|
+
readonly combinators: readonly SelectorCombinator[];
|
|
15
98
|
/**
|
|
16
|
-
*
|
|
17
|
-
* for
|
|
18
|
-
* @deprecated Use `module` to identify plugins instead.
|
|
99
|
+
* Lower-cased printable-ASCII literals that must occur in the document in
|
|
100
|
+
* this order for the alternative to match, or `null` when none can be derived.
|
|
19
101
|
*/
|
|
20
|
-
|
|
102
|
+
readonly prefilterLiterals: readonly string[] | null;
|
|
21
103
|
/**
|
|
22
|
-
*
|
|
104
|
+
* Whether one start tag decides this alternative: a single compound with
|
|
105
|
+
* no position test whose attribute values have a single stored form.
|
|
23
106
|
*/
|
|
24
|
-
|
|
107
|
+
readonly direct: boolean;
|
|
108
|
+
}
|
|
109
|
+
/** A validated selector list as the matcher evaluates it. */
|
|
110
|
+
export interface SelectorPlan {
|
|
111
|
+
readonly alternatives: readonly SelectorAlternative[];
|
|
112
|
+
/** Whether any alternative tests `:nth-child` / `:first-child`. */
|
|
113
|
+
readonly needsChildIndex: boolean;
|
|
114
|
+
/** Whether any alternative tests `:nth-of-type` / `:first-of-type`. */
|
|
115
|
+
readonly needsTypeIndex: boolean;
|
|
116
|
+
}
|
|
117
|
+
/** {@link SelectorNegatedCompound} as the native conversion reads it: no tag is absent, not `null`. */
|
|
118
|
+
export interface NativeSelectorConditions {
|
|
119
|
+
readonly tag?: string;
|
|
120
|
+
readonly attributes: readonly SelectorAttributeTest[];
|
|
121
|
+
readonly nth: readonly SelectorNthTest[];
|
|
122
|
+
}
|
|
123
|
+
/** {@link SelectorPlan} as the native conversion reads it (see `to-native-selector-plan.ts`). */
|
|
124
|
+
export interface NativeSelectorPlan {
|
|
125
|
+
readonly alternatives: readonly {
|
|
126
|
+
readonly compounds: readonly (NativeSelectorConditions & {
|
|
127
|
+
readonly negations: readonly NativeSelectorConditions[];
|
|
128
|
+
})[];
|
|
129
|
+
readonly combinators: readonly SelectorCombinator[];
|
|
130
|
+
readonly prefilterLiterals?: readonly string[];
|
|
131
|
+
readonly direct: boolean;
|
|
132
|
+
}[];
|
|
133
|
+
readonly needsChildIndex: boolean;
|
|
134
|
+
readonly needsTypeIndex: boolean;
|
|
135
|
+
}
|
|
136
|
+
/** The verdict on one document and which stages produced it. */
|
|
137
|
+
export interface HtmlMatchOutcome {
|
|
138
|
+
/** Whether an element of the document matches the selector list. */
|
|
139
|
+
readonly matched: boolean;
|
|
140
|
+
/** The ordered-literal prefilter rejected the document; its markup was never read. */
|
|
141
|
+
readonly prefiltered: boolean;
|
|
142
|
+
/** The open-element stack ran over the document. */
|
|
143
|
+
readonly tokenized: boolean;
|
|
144
|
+
/** Elements the stack tested (zero when it did not run). */
|
|
145
|
+
readonly elementsVisited: number;
|
|
146
|
+
}
|
|
147
|
+
/** One stored HTML snapshot as it sits in `page_html_blobs`. */
|
|
148
|
+
export interface StoredHtmlSnapshot {
|
|
149
|
+
/** The stored bytes. */
|
|
150
|
+
readonly body: Uint8Array;
|
|
151
|
+
/** `page_html_blobs.codec`: `zstd` or `none`; anything else is rejected. */
|
|
152
|
+
readonly codec: string;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* What `query search-html` looks for: a literal substring or a regular
|
|
156
|
+
* expression (Rust `regex` syntax) with its flags.
|
|
157
|
+
*/
|
|
158
|
+
export interface NativeSearchPattern {
|
|
159
|
+
/** The literal text, or the regular expression's source. */
|
|
160
|
+
readonly source: string;
|
|
161
|
+
/** Match `source` as plain text; the flags are then ignored. */
|
|
162
|
+
readonly literal: boolean;
|
|
163
|
+
/** `i`. */
|
|
164
|
+
readonly ignoreCase: boolean;
|
|
165
|
+
/** `m`: `^` and `$` match at every `\n`. */
|
|
166
|
+
readonly multiLine: boolean;
|
|
167
|
+
/** `s`: `.` matches `\n` too. */
|
|
168
|
+
readonly dotAll: boolean;
|
|
169
|
+
}
|
|
170
|
+
/** What a search found in one document. */
|
|
171
|
+
export interface SnapshotSearchOutcome {
|
|
172
|
+
/** Non-overlapping matches; 0 when the document does not match. */
|
|
173
|
+
readonly matchCount: number;
|
|
174
|
+
/** Whitespace-collapsed window around the first match; empty when there is none. */
|
|
175
|
+
readonly snippet: string;
|
|
176
|
+
}
|
|
177
|
+
/** The verdict on one snapshot of a batch. */
|
|
178
|
+
export interface SnapshotMatchOutcome {
|
|
179
|
+
readonly matched: boolean;
|
|
180
|
+
/** The ordered-literal prefilter rejected the document; its markup was never read. */
|
|
181
|
+
readonly prefiltered: boolean;
|
|
182
|
+
/** The open-element stack ran over the document. */
|
|
183
|
+
readonly tokenized: boolean;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* How a structural technology signal was found: a marker element or
|
|
187
|
+
* attribute in the HTML, a framework's URL path fragment, a scoped-style
|
|
188
|
+
* attribute convention, a marker too generic to count for much on its own,
|
|
189
|
+
* or a license banner at the top of a JS file.
|
|
190
|
+
*/
|
|
191
|
+
export type StructuralTechnologySignalType = 'html-marker' | 'url-pattern' | 'scoped-attr' | 'weak-marker' | 'js-license-comment';
|
|
192
|
+
/**
|
|
193
|
+
* One structural fingerprint definition (in `crates/nitpicker_tech_detect`)
|
|
194
|
+
* that matched, with the evidence cut from its first match.
|
|
195
|
+
*/
|
|
196
|
+
export interface StructuralTechnologySignal {
|
|
197
|
+
/** Normalized technology name (`'Next.js'`, `'Vue'`, ...). */
|
|
198
|
+
readonly technology: string;
|
|
199
|
+
readonly signalType: StructuralTechnologySignalType;
|
|
200
|
+
/** The first match, cut to 200 UTF-16 code units. */
|
|
201
|
+
readonly evidence: string;
|
|
202
|
+
/** The definition's confidence in isolation, 0-100. */
|
|
203
|
+
readonly weight: number;
|
|
204
|
+
/** The category attached to the technology when only this signal fired. */
|
|
205
|
+
readonly category: string;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* The addon's run, which `createTemplateClassificationRun` wraps into a
|
|
209
|
+
* {@link TemplateClassificationRun}; use that instead. Each computing call
|
|
210
|
+
* resolves with the number of progress events it sent, which may still be on
|
|
211
|
+
* their way to the callback (the wrapper waits for them). A panic in the
|
|
212
|
+
* addon or a poisoned lock rejects the call with a generic failure; neither
|
|
213
|
+
* can be provoked from JavaScript, so no test exercises them.
|
|
214
|
+
* @example
|
|
215
|
+
* ```ts
|
|
216
|
+
* const native = new nativeBinding.TemplateClassificationRun();
|
|
217
|
+
* native.pushSignals([{ paths: ['a'], stylesheetHrefs: [], stylesheetHosts: [] }]);
|
|
218
|
+
* const sent = await native.block(); // 0
|
|
219
|
+
* ```
|
|
220
|
+
*/
|
|
221
|
+
export interface NativeTemplateClassificationRun {
|
|
25
222
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
223
|
+
* {@link TemplateClassificationRun.pushSignals}, each stylesheet href's
|
|
224
|
+
* `new URL(href).host` (`null` where it throws) given alongside.
|
|
28
225
|
*/
|
|
29
|
-
|
|
226
|
+
pushSignals(signals: readonly NativeTemplatePageSignals[]): void;
|
|
227
|
+
/** {@link TemplateClassificationRun.block}, resolving with the events sent. */
|
|
228
|
+
block(): Promise<number>;
|
|
229
|
+
/** {@link TemplateClassificationRun.pushPages}, resolving with the events sent. */
|
|
230
|
+
pushPages(pages: readonly TemplatePageHtml[]): Promise<number>;
|
|
231
|
+
/** {@link TemplateClassificationRun.finishPages}, resolving with the events sent. */
|
|
232
|
+
finishPages(): Promise<number>;
|
|
233
|
+
/** {@link TemplateClassificationRun.pageMask}; throws while a call runs. */
|
|
234
|
+
pageMask(): Uint8Array;
|
|
235
|
+
/** {@link TemplateClassificationRun.needsAssignment}; throws while a call runs. */
|
|
236
|
+
needsAssignment(): boolean;
|
|
237
|
+
/** {@link TemplateClassificationRun.pushAssignmentPages}, resolving with the events sent. */
|
|
238
|
+
pushAssignmentPages(pages: readonly TemplatePageHtml[]): Promise<number>;
|
|
239
|
+
/** {@link TemplateClassificationRun.takeOutput}, with the events sent. */
|
|
240
|
+
takeOutput(): Promise<{
|
|
241
|
+
readonly output: NativeTemplateClassificationOutput;
|
|
242
|
+
readonly sent: number;
|
|
243
|
+
}>;
|
|
244
|
+
/** {@link TemplateClassificationRun.cancel}. */
|
|
245
|
+
cancel(): void;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* {@link TemplatePageSignals} as the addon takes it.
|
|
249
|
+
* @example
|
|
250
|
+
* ```ts
|
|
251
|
+
* const signals: NativeTemplatePageSignals = {
|
|
252
|
+
* paths: ['news', '1'],
|
|
253
|
+
* stylesheetHrefs: ['https://example.com/a.css', 'not a url'],
|
|
254
|
+
* stylesheetHosts: ['example.com', null],
|
|
255
|
+
* };
|
|
256
|
+
* ```
|
|
257
|
+
*/
|
|
258
|
+
export interface NativeTemplatePageSignals {
|
|
259
|
+
readonly paths: readonly string[];
|
|
260
|
+
readonly stylesheetHrefs: readonly string[];
|
|
261
|
+
/** `new URL(href).host` of each of `stylesheetHrefs`, `null` where it throws. */
|
|
262
|
+
readonly stylesheetHosts: readonly (string | null)[];
|
|
263
|
+
readonly host?: string;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* {@link TemplateClassificationProgress} as the addon reports it, every
|
|
267
|
+
* field optional; `createTemplateClassificationRun` narrows it.
|
|
268
|
+
* @example
|
|
269
|
+
* ```ts
|
|
270
|
+
* const event: NativeTemplateClassificationProgress = { phase: 'stage-b-start', unitCount: 42 };
|
|
271
|
+
* ```
|
|
272
|
+
*/
|
|
273
|
+
export interface NativeTemplateClassificationProgress {
|
|
274
|
+
readonly phase: string;
|
|
275
|
+
readonly blockKey?: string;
|
|
276
|
+
readonly blocksProcessed?: number;
|
|
277
|
+
readonly totalBlocks?: number;
|
|
278
|
+
readonly pagesAssigned?: number;
|
|
279
|
+
readonly pagesToAssign?: number;
|
|
280
|
+
readonly unitCount?: number;
|
|
281
|
+
readonly pageCount?: number;
|
|
282
|
+
readonly pairBytes?: number;
|
|
283
|
+
readonly budget?: number;
|
|
284
|
+
readonly reason?: string;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* {@link TemplateClassificationOutput} as the addon returns it: the reasons
|
|
288
|
+
* flattened, which `createTemplateClassificationRun` turns into
|
|
289
|
+
* {@link TemplateClusterReason}s.
|
|
290
|
+
* @example
|
|
291
|
+
* ```ts
|
|
292
|
+
* const { keys, keyOfPage, reasons } = (await native.takeOutput()).output;
|
|
293
|
+
* ```
|
|
294
|
+
*/
|
|
295
|
+
export interface NativeTemplateClassificationOutput {
|
|
296
|
+
readonly keys: string[];
|
|
297
|
+
readonly keyOfPage: Uint32Array;
|
|
298
|
+
readonly reasons: readonly {
|
|
299
|
+
readonly clusterKey: string;
|
|
300
|
+
readonly memberCount: number;
|
|
301
|
+
readonly blocking: readonly {
|
|
302
|
+
readonly blockKey: string;
|
|
303
|
+
readonly kind: string;
|
|
304
|
+
readonly distinctiveStylesheetHrefs?: readonly string[];
|
|
305
|
+
readonly pathKey?: string;
|
|
306
|
+
}[];
|
|
307
|
+
readonly structuralCoreTokens: readonly string[];
|
|
308
|
+
readonly landmarks: readonly ({
|
|
309
|
+
readonly type: string;
|
|
310
|
+
} & TemplateLandmarkProfile)[];
|
|
311
|
+
readonly siblingClusterKeys: readonly string[];
|
|
312
|
+
}[];
|
|
313
|
+
readonly sampledBlocks: readonly {
|
|
314
|
+
readonly blockKey: string;
|
|
315
|
+
readonly pageCount: number;
|
|
316
|
+
readonly pairBytes: number;
|
|
317
|
+
readonly reason: string;
|
|
318
|
+
}[];
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* The element a crawler reported as a page's main content: the element whose
|
|
322
|
+
* subtree holds the page-specific content and under which the classifier
|
|
323
|
+
* caps the nesting depth it compares. Every given field must match the same
|
|
324
|
+
* element; empty strings and lists count as not given, and a hint with
|
|
325
|
+
* nothing given matches nothing (otherwise the shallowest element of the
|
|
326
|
+
* page would win and the cap would silently apply to the wrong node).
|
|
327
|
+
*
|
|
328
|
+
* A structured object rather than a CSS selector string: nothing has to be
|
|
329
|
+
* parsed (the HTML parser already exposes `id`/`class`/`role`), it maps 1:1
|
|
330
|
+
* onto the columns a crawler records for its detected main-content element,
|
|
331
|
+
* and a crawler's own selector string is diagnostic (tag + id + classes) and
|
|
332
|
+
* not guaranteed to be unique, so it is the wrong thing to re-parse.
|
|
333
|
+
* @example
|
|
334
|
+
* ```ts
|
|
335
|
+
* const hint: ContentRootHint = { tagName: 'DIV', id: 'content', classList: ['l-main'] };
|
|
336
|
+
* ```
|
|
337
|
+
*/
|
|
338
|
+
export interface ContentRootHint {
|
|
339
|
+
/** Compared case-insensitively. */
|
|
340
|
+
readonly tagName?: string;
|
|
341
|
+
readonly id?: string;
|
|
342
|
+
readonly role?: string;
|
|
343
|
+
/** Every listed class must be on the element. */
|
|
344
|
+
readonly classList?: readonly string[];
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* What Pass 0 reads of one page handed to a
|
|
348
|
+
* {@link TemplateClassificationRun}: no HTML, a few hundred bytes.
|
|
349
|
+
* @example
|
|
350
|
+
* ```ts
|
|
351
|
+
* const signals: TemplatePageSignals = {
|
|
352
|
+
* paths: ['news', '1'],
|
|
353
|
+
* stylesheetHrefs: ['https://example.com/a.css'],
|
|
354
|
+
* host: 'example.com',
|
|
355
|
+
* };
|
|
356
|
+
* ```
|
|
357
|
+
*/
|
|
358
|
+
export interface TemplatePageSignals {
|
|
359
|
+
/** The page URL's path segments; a directory-style URL ends in `''`. */
|
|
360
|
+
readonly paths: readonly string[];
|
|
361
|
+
/** Absolute hrefs of the page's stylesheets, in document order. */
|
|
362
|
+
readonly stylesheetHrefs: readonly string[];
|
|
30
363
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
364
|
+
* The page URL's host (`new URL(pageUrl).host`). Stylesheets on other
|
|
365
|
+
* hosts are left out of blocking; a page without it is judged against
|
|
366
|
+
* the host most pages load stylesheets from.
|
|
33
367
|
*/
|
|
34
|
-
|
|
368
|
+
readonly host?: string;
|
|
35
369
|
}
|
|
36
370
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
371
|
+
* One page's HTML handed to a {@link TemplateClassificationRun}.
|
|
372
|
+
* @example
|
|
373
|
+
* ```ts
|
|
374
|
+
* const page: TemplatePageHtml = { html: '<body><main>…</main></body>', contentRoot: { tagName: 'main' } };
|
|
375
|
+
* ```
|
|
41
376
|
*/
|
|
42
|
-
export interface
|
|
43
|
-
|
|
44
|
-
readonly
|
|
45
|
-
/** When `true`, outputs plain-text progress lines instead of animated Lanes. */
|
|
46
|
-
readonly verbose?: boolean;
|
|
377
|
+
export interface TemplatePageHtml {
|
|
378
|
+
readonly html: string;
|
|
379
|
+
readonly contentRoot?: ContentRootHint;
|
|
47
380
|
}
|
|
48
381
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* 4. Update `README.md` analyze options table
|
|
63
|
-
* @see {@link ./load-plugin-settings.ts!loadPluginSettings} for the merge logic
|
|
382
|
+
* Progress of a {@link TemplateClassificationRun}: a block will be clustered
|
|
383
|
+
* from a sample (over the memory budget, or among the largest when the
|
|
384
|
+
* blocks clustered whole would hold too many pages), a block's clusters are done,
|
|
385
|
+
* another thousand pages outside the samples were assigned, or the merge
|
|
386
|
+
* across blocks began.
|
|
387
|
+
* @example
|
|
388
|
+
* ```ts
|
|
389
|
+
* const onProgress = (event: TemplateClassificationProgress) => {
|
|
390
|
+
* if (event.phase === 'pass1-block-complete') {
|
|
391
|
+
* console.log(`${event.blocksProcessed}/${event.totalBlocks} blocks`);
|
|
392
|
+
* }
|
|
393
|
+
* };
|
|
394
|
+
* ```
|
|
64
395
|
*/
|
|
65
|
-
export
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
396
|
+
export type TemplateClassificationProgress = {
|
|
397
|
+
readonly phase: 'block-sampled';
|
|
398
|
+
readonly blockKey: string;
|
|
399
|
+
readonly pageCount: number;
|
|
400
|
+
readonly pairBytes: number;
|
|
401
|
+
readonly budget: number;
|
|
402
|
+
readonly reason: TemplateSampleReason;
|
|
403
|
+
} | {
|
|
404
|
+
readonly phase: 'pass1-block-complete';
|
|
405
|
+
readonly blockKey: string;
|
|
406
|
+
readonly blocksProcessed: number;
|
|
407
|
+
readonly totalBlocks: number;
|
|
408
|
+
} | {
|
|
409
|
+
readonly phase: 'pass1b-assign';
|
|
410
|
+
readonly pagesAssigned: number;
|
|
411
|
+
readonly pagesToAssign: number;
|
|
412
|
+
} | {
|
|
413
|
+
readonly phase: 'stage-b-start';
|
|
414
|
+
readonly unitCount: number;
|
|
415
|
+
};
|
|
416
|
+
/**
|
|
417
|
+
* Why Pass 0 formed a block: its pages share a distinctive set of
|
|
418
|
+
* stylesheets (`css`), or fall back to their URL section (`path`), or are
|
|
419
|
+
* pages without stylesheets gathered into their section's stylesheet block
|
|
420
|
+
* (`orphanMerge`).
|
|
421
|
+
* @example
|
|
422
|
+
* ```ts
|
|
423
|
+
* const reason: TemplateBlockingReason = { kind: 'path', pathKey: 'news' };
|
|
424
|
+
* ```
|
|
425
|
+
*/
|
|
426
|
+
export type TemplateBlockingReason = {
|
|
427
|
+
readonly kind: 'css';
|
|
428
|
+
readonly distinctiveStylesheetHrefs: readonly string[];
|
|
429
|
+
} | {
|
|
430
|
+
readonly kind: 'path';
|
|
431
|
+
readonly pathKey: string;
|
|
432
|
+
} | {
|
|
433
|
+
readonly kind: 'orphanMerge';
|
|
434
|
+
readonly pathKey: string;
|
|
435
|
+
};
|
|
436
|
+
/**
|
|
437
|
+
* A page region the classifier treats as chrome: HTML5 sectioning elements
|
|
438
|
+
* and their ARIA landmark roles (`<header>` / `banner`, `<footer>` /
|
|
439
|
+
* `contentinfo`, `<nav>` / `navigation`, `<aside>` / `complementary`,
|
|
440
|
+
* `role="form"`, `<search>` / `search`). `main` is content and never one.
|
|
441
|
+
* @example
|
|
442
|
+
* ```ts
|
|
443
|
+
* const type: TemplateLandmarkType = 'footer';
|
|
444
|
+
* ```
|
|
445
|
+
*/
|
|
446
|
+
export type TemplateLandmarkType = 'header' | 'footer' | 'nav' | 'aside' | 'form' | 'search';
|
|
447
|
+
/**
|
|
448
|
+
* What `createTemplateClassificationRun`'s progress callback shares with the
|
|
449
|
+
* run: the delivery counts, the waiting call, the run's failure, and the
|
|
450
|
+
* addon's run only through a `WeakRef`. Kept apart from the run's methods so
|
|
451
|
+
* the callback, which the addon holds while the run lives, does not hold the
|
|
452
|
+
* run strongly.
|
|
453
|
+
* @example
|
|
454
|
+
* ```ts
|
|
455
|
+
* const state: TemplateClassificationProgressState = {
|
|
456
|
+
* delivered: 0,
|
|
457
|
+
* expected: 0,
|
|
458
|
+
* onDelivered: undefined,
|
|
459
|
+
* failure: undefined,
|
|
460
|
+
* nativeRef: undefined,
|
|
461
|
+
* };
|
|
462
|
+
* ```
|
|
463
|
+
*/
|
|
464
|
+
export interface TemplateClassificationProgressState {
|
|
465
|
+
/** Events the callback received. */
|
|
466
|
+
delivered: number;
|
|
467
|
+
/** Events the finished calls said they sent. */
|
|
468
|
+
expected: number;
|
|
469
|
+
/** Wakes the call waiting for its events, if one is. */
|
|
470
|
+
onDelivered: (() => void) | undefined;
|
|
471
|
+
/** The error that failed the run, once one did. */
|
|
472
|
+
failure: {
|
|
473
|
+
readonly error: unknown;
|
|
474
|
+
} | undefined;
|
|
475
|
+
/** The addon's run, to cancel when the caller's callback throws. */
|
|
476
|
+
nativeRef: WeakRef<NativeTemplateClassificationRun> | undefined;
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* How common one landmark type is across a cluster's member pages.
|
|
480
|
+
* @example
|
|
481
|
+
* ```ts
|
|
482
|
+
* const footer: TemplateLandmarkProfile = {
|
|
483
|
+
* presenceRate: 1,
|
|
484
|
+
* chromeRate: 1,
|
|
485
|
+
* shellTokens: ['footer>p'],
|
|
486
|
+
* memberCountWithInstance: 12,
|
|
487
|
+
* };
|
|
488
|
+
* ```
|
|
489
|
+
*/
|
|
490
|
+
export interface TemplateLandmarkProfile {
|
|
491
|
+
/** Fraction of the member pages carrying the type. */
|
|
492
|
+
readonly presenceRate: number;
|
|
493
|
+
/** Fraction of the type's instances that match the cluster's shell for it. */
|
|
494
|
+
readonly chromeRate: number;
|
|
495
|
+
/** The cluster's shell tokens for the type, in UTF-16 order. */
|
|
496
|
+
readonly shellTokens: readonly string[];
|
|
497
|
+
/** Member pages carrying the type. */
|
|
498
|
+
readonly memberCountWithInstance: number;
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Why a final cluster's pages sit together: evidence, no verdicts. Its
|
|
502
|
+
* shape — key order included — is what `@nitpicker/archive` stores as JSON,
|
|
503
|
+
* so the stored bytes follow from it.
|
|
504
|
+
* @example
|
|
505
|
+
* ```ts
|
|
506
|
+
* const reason: TemplateClusterReason = {
|
|
507
|
+
* memberCount: 2,
|
|
508
|
+
* blocking: [{ blockKey: 'path:news', reason: { kind: 'path', pathKey: 'news' } }],
|
|
509
|
+
* structuralCoreTokens: ['article', 'body'],
|
|
510
|
+
* landmarks: {},
|
|
511
|
+
* siblingClusterKeys: [],
|
|
512
|
+
* };
|
|
513
|
+
* ```
|
|
514
|
+
*/
|
|
515
|
+
export interface TemplateClusterReason {
|
|
516
|
+
/** Its member pages (the sampled ones, for a block clustered from a sample). */
|
|
517
|
+
readonly memberCount: number;
|
|
518
|
+
/** Each Pass-0 block it came from, with the reason the block was formed. */
|
|
519
|
+
readonly blocking: readonly {
|
|
520
|
+
readonly blockKey: string;
|
|
521
|
+
readonly reason: TemplateBlockingReason;
|
|
522
|
+
}[];
|
|
523
|
+
/** The structure most of its pages share, in UTF-16 order. */
|
|
524
|
+
readonly structuralCoreTokens: readonly string[];
|
|
525
|
+
/** Per landmark type its members carry; absent types are left out. */
|
|
526
|
+
readonly landmarks: {
|
|
527
|
+
readonly [Type in TemplateLandmarkType]?: TemplateLandmarkProfile;
|
|
77
528
|
};
|
|
529
|
+
/** The other final clusters of its blocks, in UTF-16 order. */
|
|
530
|
+
readonly siblingClusterKeys: readonly string[];
|
|
78
531
|
}
|
|
79
532
|
/**
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
533
|
+
* Why a block is clustered from a sample of its pages: its Stage A
|
|
534
|
+
* similarity table would exceed the memory budget (`table-over-budget`), or
|
|
535
|
+
* the blocks clustered whole would hold more than
|
|
536
|
+
* `TEMPLATE_EXACT_PAGE_LIMIT` pages and this one is among the largest
|
|
537
|
+
* (`pages-over-limit`).
|
|
538
|
+
* @example
|
|
539
|
+
* ```ts
|
|
540
|
+
* const reason: TemplateSampleReason = 'pages-over-limit';
|
|
541
|
+
* ```
|
|
542
|
+
*/
|
|
543
|
+
export type TemplateSampleReason = 'table-over-budget' | 'pages-over-limit';
|
|
544
|
+
/**
|
|
545
|
+
* A block clustered from a sample of its pages; see
|
|
546
|
+
* {@link TemplateSampleReason} for why.
|
|
547
|
+
* @example
|
|
548
|
+
* ```ts
|
|
549
|
+
* const block: TemplateSampledBlock = {
|
|
550
|
+
* blockKey: 'path:news',
|
|
551
|
+
* pageCount: 30_000,
|
|
552
|
+
* pairBytes: 3_599_880_000,
|
|
553
|
+
* reason: 'table-over-budget',
|
|
554
|
+
* };
|
|
555
|
+
* ```
|
|
87
556
|
*/
|
|
88
|
-
export interface
|
|
89
|
-
/**
|
|
90
|
-
|
|
557
|
+
export interface TemplateSampledBlock {
|
|
558
|
+
/** The block's key. */
|
|
559
|
+
readonly blockKey: string;
|
|
560
|
+
/** Its pages. */
|
|
561
|
+
readonly pageCount: number;
|
|
562
|
+
/** Bytes its similarity table would have taken. */
|
|
563
|
+
readonly pairBytes: number;
|
|
564
|
+
/** Why it was sampled. */
|
|
565
|
+
readonly reason: TemplateSampleReason;
|
|
91
566
|
}
|
|
92
567
|
/**
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
|
|
101
|
-
|
|
568
|
+
* What a {@link TemplateClassificationRun} produces.
|
|
569
|
+
* @example
|
|
570
|
+
* ```ts
|
|
571
|
+
* const { keys, keyOfPage, reasons } = await run.takeOutput();
|
|
572
|
+
* const keyOfFirstPage = keys[keyOfPage[0]!]!; // '["path:news","cluster:0"]'
|
|
573
|
+
* reasons.get(keyOfFirstPage)?.memberCount;
|
|
574
|
+
* ```
|
|
575
|
+
*/
|
|
576
|
+
export interface TemplateClassificationOutput {
|
|
577
|
+
/** Every final cluster key (`JSON.stringify([blockKey, "cluster:N"])`). */
|
|
578
|
+
readonly keys: string[];
|
|
579
|
+
/** Each page's key, as an index into `keys`. */
|
|
580
|
+
readonly keyOfPage: Uint32Array;
|
|
581
|
+
/** Each final cluster's reason, by key. */
|
|
582
|
+
readonly reasons: ReadonlyMap<string, TemplateClusterReason>;
|
|
583
|
+
/** The blocks clustered from a sample, in block order; empty when none was. */
|
|
584
|
+
readonly sampledBlocks: readonly TemplateSampledBlock[];
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* One template classification run, executing on the addon's thread pool.
|
|
588
|
+
* The caller reads the corpus: every page's signals (`pushSignals`), then
|
|
589
|
+
* `block()`, then every page's HTML in the same order (`pushPages`,
|
|
590
|
+
* `finishPages`), and, when `needsAssignment()`, once more
|
|
591
|
+
* (`pushAssignmentPages`); `takeOutput()` merges and returns the result.
|
|
102
592
|
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
* @
|
|
593
|
+
* Calls must not overlap: await each before the next (an overlapping call
|
|
594
|
+
* rejects and leaves the running one alone). A promise resolves only after
|
|
595
|
+
* every progress event of its call was delivered. A call that rejects —
|
|
596
|
+
* cancelled, a protocol error, or `onProgress` threw (which also cancels the
|
|
597
|
+
* run) — fails the run: every later call rejects with the same error, and no
|
|
598
|
+
* further progress is reported.
|
|
599
|
+
* @example
|
|
600
|
+
* ```ts
|
|
601
|
+
* const run = createTemplateClassificationRun();
|
|
602
|
+
* run.pushSignals([{ paths: ['a'], stylesheetHrefs: [] }]);
|
|
603
|
+
* await run.block();
|
|
604
|
+
* await run.pushPages([{ html: '<body></body>' }]);
|
|
605
|
+
* await run.finishPages();
|
|
606
|
+
* const output = await run.takeOutput();
|
|
607
|
+
* ```
|
|
110
608
|
*/
|
|
111
|
-
export interface
|
|
112
|
-
/**
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
609
|
+
export interface TemplateClassificationRun {
|
|
610
|
+
/** Adds the next pages' signals, in corpus order. */
|
|
611
|
+
pushSignals(signals: readonly TemplatePageSignals[]): void;
|
|
612
|
+
/** Splits the pages into blocks (Pass 0); the HTML reads follow. */
|
|
613
|
+
block(): Promise<void>;
|
|
614
|
+
/** Reads the next pages' HTML, in corpus order. */
|
|
615
|
+
pushPages(pages: readonly TemplatePageHtml[]): Promise<void>;
|
|
616
|
+
/** Ends the first read; the blocks clustered whole are clustered here. */
|
|
617
|
+
finishPages(): Promise<void>;
|
|
117
618
|
/**
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
* full Chrome instance per page) should set a small number; lightweight
|
|
123
|
-
* DOM-only plugins can leave it unset.
|
|
124
|
-
*
|
|
125
|
-
* When omitted, the orchestrator falls back to `os.cpus().length`.
|
|
619
|
+
* Which pages the current read uses (`1`): on the first read every page of
|
|
620
|
+
* a block clustered whole and the sampled pages of a sampled block, on the
|
|
621
|
+
* assignment read the other pages of the sampled blocks. Every page must
|
|
622
|
+
* still be pushed, in order; the others may carry empty HTML.
|
|
126
623
|
*/
|
|
127
|
-
|
|
624
|
+
pageMask(): Uint8Array;
|
|
128
625
|
/**
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
626
|
+
* Whether the run expects the assignment read: true once the first
|
|
627
|
+
* read has ended with pages outside the samples of a sampled block, until
|
|
628
|
+
* the assignment read has ended; false otherwise.
|
|
132
629
|
*/
|
|
133
|
-
|
|
630
|
+
needsAssignment(): boolean;
|
|
631
|
+
/** Assigns the next pages of the second read, in corpus order. */
|
|
632
|
+
pushAssignmentPages(pages: readonly TemplatePageHtml[]): Promise<void>;
|
|
633
|
+
/** Merges the clusters across blocks; the run cannot be used afterwards. */
|
|
634
|
+
takeOutput(): Promise<TemplateClassificationOutput>;
|
|
134
635
|
/**
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
* @param page.url - Parsed URL of the page being analyzed.
|
|
139
|
-
* @param page.html - Raw HTML string of the page.
|
|
140
|
-
* @param page.window - JSDOM window with the page's DOM tree. Closed after the callback returns.
|
|
141
|
-
* @param page.num - Zero-based index of the current page in the batch.
|
|
142
|
-
* @param page.total - Total number of pages in the batch.
|
|
143
|
-
* @returns Report data for this page, or `null` to skip.
|
|
636
|
+
* Makes the running call, and every later one, reject at its next page or
|
|
637
|
+
* block. A call already finished but waiting on its progress events
|
|
638
|
+
* resolves without them.
|
|
144
639
|
*/
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
640
|
+
cancel(): void;
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Inputs to `createTemplateClassificationRun`.
|
|
644
|
+
* @example
|
|
645
|
+
* ```ts
|
|
646
|
+
* const options: CreateTemplateClassificationRunOptions = {
|
|
647
|
+
* onProgress: (event) => console.log(event.phase),
|
|
648
|
+
* };
|
|
649
|
+
* ```
|
|
650
|
+
*/
|
|
651
|
+
export interface CreateTemplateClassificationRunOptions {
|
|
157
652
|
/**
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
* window. It is designed for checks that depend only on URL metadata
|
|
162
|
-
* (e.g. checking URL patterns, external link policies).
|
|
163
|
-
* @param page - URL context including external/internal classification.
|
|
164
|
-
* @param page.url - Parsed URL being analyzed.
|
|
165
|
-
* @param page.isExternal - Whether this URL is external to the crawled site.
|
|
166
|
-
* @returns Report data for this URL, or `null` to skip.
|
|
653
|
+
* Bytes a block's Stage A similarity table (`n * (n - 1) / 2 * 8` for `n`
|
|
654
|
+
* pages) may take before the block is clustered from a sample instead.
|
|
655
|
+
* Defaults to `TEMPLATE_MEMORY_BUDGET`; `0` samples every block.
|
|
167
656
|
*/
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
/** Whether this URL is external to the crawled site. */
|
|
172
|
-
isExternal: boolean;
|
|
173
|
-
}): Promise<ReportPage<T> | null> | ReportPage<T> | null;
|
|
657
|
+
readonly memoryBudget?: number;
|
|
658
|
+
/** Receives progress events. */
|
|
659
|
+
readonly onProgress?: (event: TemplateClassificationProgress) => void;
|
|
174
660
|
}
|
|
175
661
|
/**
|
|
176
|
-
* The
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
662
|
+
* The addon's isolated-cluster run, which `createIsolatedClusterRun` wraps
|
|
663
|
+
* into an {@link IsolatedClusterRun}; use that instead.
|
|
664
|
+
* @example
|
|
665
|
+
* ```ts
|
|
666
|
+
* const native = new nativeBinding.IsolatedClusterRun(Uint32Array.of(1, 2), new Uint32Array());
|
|
667
|
+
* native.unionEdges(Uint32Array.of(1, 2));
|
|
668
|
+
* native.labels(); // Uint32Array [0, 0]
|
|
669
|
+
* ```
|
|
182
670
|
*/
|
|
183
|
-
export interface
|
|
184
|
-
/**
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
|
|
671
|
+
export interface NativeIsolatedClusterRun {
|
|
672
|
+
/** {@link IsolatedClusterRun.unionEdges}, the pairs flattened. */
|
|
673
|
+
unionEdges(edges: Uint32Array): void;
|
|
674
|
+
/** {@link IsolatedClusterRun.labels}. */
|
|
675
|
+
labels(): Uint32Array;
|
|
188
676
|
}
|
|
189
677
|
/**
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
678
|
+
* The candidates {@link groupAliasCandidates} hands the addon, column by
|
|
679
|
+
* column.
|
|
680
|
+
* @example
|
|
681
|
+
* ```ts
|
|
682
|
+
* const columns: NativeAliasCandidates = {
|
|
683
|
+
* urls: Buffer.from('https://example.com/'),
|
|
684
|
+
* urlOffsets: Uint32Array.of(0, 20),
|
|
685
|
+
* urlIds: Uint32Array.of(1),
|
|
686
|
+
* titleTextIds: Uint32Array.of(1),
|
|
687
|
+
* canonicalUrlIds: Uint32Array.of(4_294_967_295),
|
|
688
|
+
* bodyHashes: new Uint8Array(),
|
|
689
|
+
* hashOffsets: Uint32Array.of(0, 0),
|
|
690
|
+
* hashPresent: Uint8Array.of(0),
|
|
691
|
+
* };
|
|
692
|
+
* ```
|
|
198
693
|
*/
|
|
199
|
-
export interface
|
|
200
|
-
/**
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
|
|
694
|
+
export interface NativeAliasCandidates {
|
|
695
|
+
/** The URLs back to back as UTF-8. */
|
|
696
|
+
readonly urls: Uint8Array;
|
|
697
|
+
/** `n + 1` positions cutting `urls` into the `n` URLs. */
|
|
698
|
+
readonly urlOffsets: Uint32Array;
|
|
699
|
+
readonly urlIds: Uint32Array;
|
|
700
|
+
readonly titleTextIds: Uint32Array;
|
|
701
|
+
/** `4294967295` for a page without a canonical. */
|
|
702
|
+
readonly canonicalUrlIds: Uint32Array;
|
|
703
|
+
/** The body hashes back to back. */
|
|
704
|
+
readonly bodyHashes: Uint8Array;
|
|
705
|
+
/** `n + 1` positions cutting `bodyHashes` into `n` pieces. */
|
|
706
|
+
readonly hashOffsets: Uint32Array;
|
|
707
|
+
/** `0` for a page without a body hash (its piece is empty). */
|
|
708
|
+
readonly hashPresent: Uint8Array;
|
|
204
709
|
}
|
|
205
710
|
/**
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
* The factory receives the user's settings object (`O`) and returns
|
|
210
|
-
* an {@link AnalyzePlugin} instance (or a Promise thereof). This two-phase
|
|
211
|
-
* pattern allows plugins to perform async initialization (e.g.
|
|
212
|
-
* loading locale files, compiling lint configs) once, then reuse
|
|
213
|
-
* the resulting plugin for every page.
|
|
214
|
-
*
|
|
215
|
-
* Use {@link ./hooks/define-plugin.ts!definePlugin} to define a
|
|
216
|
-
* plugin with full type inference.
|
|
217
|
-
* @template O - Shape of the plugin's settings from the config file.
|
|
218
|
-
* @template T - String literal union of column keys the plugin contributes.
|
|
711
|
+
* The two keys under which pages are grouped as the same page under another
|
|
712
|
+
* URL; see `computeAliasKeys`.
|
|
219
713
|
* @example
|
|
220
714
|
* ```ts
|
|
221
|
-
*
|
|
222
|
-
* import { definePlugin } from '@nitpicker/core';
|
|
223
|
-
*
|
|
224
|
-
* type Options = { keywords: string[] };
|
|
225
|
-
*
|
|
226
|
-
* export default definePlugin(async (options: Options) => {
|
|
227
|
-
* return {
|
|
228
|
-
* headers: { found: 'Keywords Found' },
|
|
229
|
-
* async eachPage({ html }) {
|
|
230
|
-
* const count = options.keywords.filter(k => html.includes(k)).length;
|
|
231
|
-
* return { page: { found: { value: count } } };
|
|
232
|
-
* },
|
|
233
|
-
* };
|
|
234
|
-
* });
|
|
715
|
+
* const keys: AliasKeys = { tierA: 'example.com/about/', tierB: 'example.com/about' };
|
|
235
716
|
* ```
|
|
236
|
-
* @see {@link AnalyzePlugin} for the runtime interface
|
|
237
|
-
* @see {@link ./hooks/define-plugin.ts!definePlugin} for the type-safe wrapper
|
|
238
717
|
*/
|
|
239
|
-
export
|
|
718
|
+
export interface AliasKeys {
|
|
719
|
+
/** Folds the scheme, the host's case and a trailing `/index.{ext}`. */
|
|
720
|
+
readonly tierA: string;
|
|
721
|
+
/** Tier A with one trailing `/` of the path dropped. */
|
|
722
|
+
readonly tierB: string;
|
|
723
|
+
}
|
|
240
724
|
/**
|
|
241
|
-
*
|
|
725
|
+
* One page that may be the same page as another under a different URL.
|
|
242
726
|
* @example
|
|
243
727
|
* ```ts
|
|
244
|
-
* const
|
|
245
|
-
*
|
|
246
|
-
*
|
|
728
|
+
* const page: AliasCandidate = {
|
|
729
|
+
* url: 'https://example.com/about/',
|
|
730
|
+
* urlId: 7,
|
|
731
|
+
* titleTextId: 3,
|
|
732
|
+
* canonicalUrlId: null,
|
|
733
|
+
* bodyHash: null,
|
|
247
734
|
* };
|
|
248
735
|
* ```
|
|
249
736
|
*/
|
|
250
|
-
export
|
|
251
|
-
/**
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
* Internal Map representation of page-keyed table data.
|
|
262
|
-
* Used by {@link ../table.ts!Table} for efficient merge operations.
|
|
263
|
-
*/
|
|
264
|
-
export type TableRow<K extends string> = Map<string, TableData<K>>;
|
|
265
|
-
/**
|
|
266
|
-
* Payload for starting an analyze action.
|
|
267
|
-
* @internal
|
|
268
|
-
*/
|
|
269
|
-
export interface PluginExecutionContext {
|
|
270
|
-
/** Human-readable name of the plugin module. */
|
|
271
|
-
pluginModuleName: string;
|
|
272
|
-
/** Resolved file system path to the plugin module. */
|
|
273
|
-
pluginModulePath: string;
|
|
274
|
-
/** Plugin-specific settings to pass to the hook factory. */
|
|
275
|
-
settings: unknown;
|
|
276
|
-
/** Path to the config file where this action was declared. */
|
|
277
|
-
configFilePath: string;
|
|
278
|
-
/** Temporary directory where the archive is extracted. */
|
|
279
|
-
archiveTempDir: string;
|
|
280
|
-
/** Full resolved configuration. */
|
|
281
|
-
config: Config;
|
|
737
|
+
export interface AliasCandidate {
|
|
738
|
+
/** The page's URL. */
|
|
739
|
+
readonly url: string;
|
|
740
|
+
/** `url_refs.id` of that URL. */
|
|
741
|
+
readonly urlId: number;
|
|
742
|
+
/** The id of the page's title text; pages with different titles never group. */
|
|
743
|
+
readonly titleTextId: number;
|
|
744
|
+
/** `url_refs.id` of the URL the page's `<link rel="canonical">` points at. */
|
|
745
|
+
readonly canonicalUrlId: number | null;
|
|
746
|
+
/** The page's body hash; without one the page groups by URL form only. */
|
|
747
|
+
readonly bodyHash: Uint8Array | null;
|
|
282
748
|
}
|
|
283
749
|
/**
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
*
|
|
750
|
+
* The connected components of the isolated pages, built from the pages' link
|
|
751
|
+
* edges as they are read. Created by `createIsolatedClusterRun`.
|
|
752
|
+
* @example
|
|
753
|
+
* ```ts
|
|
754
|
+
* const run = createIsolatedClusterRun([10, 20, 30], []);
|
|
755
|
+
* run.unionEdges([10, 20]);
|
|
756
|
+
* run.labels(); // Uint32Array [0, 0, 1]
|
|
757
|
+
* ```
|
|
288
758
|
*/
|
|
289
|
-
export interface
|
|
759
|
+
export interface IsolatedClusterRun {
|
|
290
760
|
/**
|
|
291
|
-
*
|
|
761
|
+
* Joins the pages of a batch of link edges, `[page, href, page, href, ...]`
|
|
762
|
+
* by page id, in any order and batch size. An edge counts when `page` is a
|
|
763
|
+
* candidate and `href`, once redirects and aliases are followed, is a
|
|
764
|
+
* different one.
|
|
292
765
|
*/
|
|
293
|
-
|
|
294
|
-
/** Absolute path to the written `.nitpicker` archive file. */
|
|
295
|
-
filePath: string;
|
|
296
|
-
};
|
|
766
|
+
unionEdges(edges: ArrayLike<number>): void;
|
|
297
767
|
/**
|
|
298
|
-
*
|
|
768
|
+
* For each candidate, in the order given, the number of its component,
|
|
769
|
+
* counted in order of first appearance.
|
|
299
770
|
*/
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
771
|
+
labels(): Uint32Array;
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Inputs to `loadNativeBinding`. Every field defaults to the running
|
|
775
|
+
* process; tests override them to exercise other platforms.
|
|
776
|
+
*/
|
|
777
|
+
export interface LoadNativeBindingOptions {
|
|
778
|
+
/** `process.platform` of the host. */
|
|
779
|
+
readonly platform: NodeJS.Platform;
|
|
780
|
+
/** `process.arch` of the host. */
|
|
781
|
+
readonly arch: NodeJS.Architecture;
|
|
782
|
+
/**
|
|
783
|
+
* glibc version of the running process (e.g. `"2.35"`), or `null` when the
|
|
784
|
+
* C library is not glibc (musl). Consulted only when `platform` is
|
|
785
|
+
* `linux`; when omitted there, it is read from Node's diagnostic report.
|
|
786
|
+
*/
|
|
787
|
+
readonly glibcVersion: string | null;
|
|
788
|
+
/** Absolute path of the `@nitpicker/core` package directory. */
|
|
789
|
+
readonly packageDir: string;
|
|
790
|
+
/** Whether a file exists (`fs.existsSync`). */
|
|
791
|
+
readonly fileExists: (filePath: string) => boolean;
|
|
792
|
+
/** Loads a `.node` file or a package by specifier (`require`). */
|
|
793
|
+
readonly requireModule: (specifier: string) => unknown;
|
|
306
794
|
}
|