@forkpoint/agent-lighthouse-core 0.3.0 → 0.4.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/dist/index.d.mts +111 -5
- package/dist/index.d.ts +111 -5
- package/dist/index.js +174 -30
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +171 -30
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -198,8 +198,89 @@ interface ReadinessVitals {
|
|
|
198
198
|
technical: number;
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
-
type
|
|
202
|
-
|
|
201
|
+
type PhaseId = 'fetch-root' | 'fetch-pages' | 'analyze' | 'audits' | 'report';
|
|
202
|
+
type ScanEvent = {
|
|
203
|
+
type: 'scan:start';
|
|
204
|
+
url: string;
|
|
205
|
+
fraction: number;
|
|
206
|
+
elapsedMs: number;
|
|
207
|
+
} | {
|
|
208
|
+
type: 'phase:start';
|
|
209
|
+
phase: PhaseId;
|
|
210
|
+
totalUnits: number;
|
|
211
|
+
fraction: number;
|
|
212
|
+
elapsedMs: number;
|
|
213
|
+
} | {
|
|
214
|
+
type: 'unit:done';
|
|
215
|
+
phase: PhaseId;
|
|
216
|
+
completed: number;
|
|
217
|
+
total: number;
|
|
218
|
+
label?: string;
|
|
219
|
+
fraction: number;
|
|
220
|
+
elapsedMs: number;
|
|
221
|
+
} | {
|
|
222
|
+
type: 'unit:fail';
|
|
223
|
+
phase: PhaseId;
|
|
224
|
+
label: string;
|
|
225
|
+
error: string;
|
|
226
|
+
fraction: number;
|
|
227
|
+
elapsedMs: number;
|
|
228
|
+
} | {
|
|
229
|
+
type: 'phase:done';
|
|
230
|
+
phase: PhaseId;
|
|
231
|
+
durationMs: number;
|
|
232
|
+
fraction: number;
|
|
233
|
+
elapsedMs: number;
|
|
234
|
+
} | {
|
|
235
|
+
type: 'scan:done';
|
|
236
|
+
durationMs: number;
|
|
237
|
+
score: number;
|
|
238
|
+
fraction: number;
|
|
239
|
+
elapsedMs: number;
|
|
240
|
+
};
|
|
241
|
+
/**
|
|
242
|
+
* How much of the overall scan fraction each phase owns. Sums to 1 so the
|
|
243
|
+
* fraction of a finished scan is exactly 1.
|
|
244
|
+
*/
|
|
245
|
+
declare const PHASE_WEIGHTS: Record<PhaseId, number>;
|
|
246
|
+
/**
|
|
247
|
+
* Turns scan milestones into {@link ScanEvent}s. Owns the fraction math
|
|
248
|
+
* (`Σ weights of done phases + current phase weight × completed/total`),
|
|
249
|
+
* clamps it to be monotonic non-decreasing, and stamps `fraction`/`elapsedMs`
|
|
250
|
+
* onto every event. Emission is synchronous — throttling is a consumer concern.
|
|
251
|
+
*/
|
|
252
|
+
declare class ProgressTracker {
|
|
253
|
+
private readonly onEvent;
|
|
254
|
+
private readonly startMs;
|
|
255
|
+
private doneWeight;
|
|
256
|
+
private phase;
|
|
257
|
+
private phaseStartMs;
|
|
258
|
+
private totalUnits;
|
|
259
|
+
private completedUnits;
|
|
260
|
+
private lastFraction;
|
|
261
|
+
constructor(onEvent: (event: ScanEvent) => void);
|
|
262
|
+
/** Fraction of the whole scan that is complete, in [0, 1]. Never decreases. */
|
|
263
|
+
get fraction(): number;
|
|
264
|
+
/** Stamp an event with the current fraction/elapsed and advance the floor. */
|
|
265
|
+
private stamp;
|
|
266
|
+
private elapsedMs;
|
|
267
|
+
scanStart(url: string): void;
|
|
268
|
+
phaseStart(phase: PhaseId, totalUnits: number): void;
|
|
269
|
+
/** Correct the current phase's unit total (e.g. discovery finds pages mid-phase). */
|
|
270
|
+
setPhaseTotal(totalUnits: number): void;
|
|
271
|
+
unitDone(label?: string): void;
|
|
272
|
+
/** A failed unit still counts as settled work so the phase can complete. */
|
|
273
|
+
unitFail(label: string, error: string): void;
|
|
274
|
+
phaseDone(): void;
|
|
275
|
+
scanDone(score: number): void;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
interface ScanOptions {
|
|
279
|
+
onEvent?: (event: ScanEvent) => void;
|
|
280
|
+
pages?: PageOverride[] | null;
|
|
281
|
+
signal?: AbortSignal;
|
|
282
|
+
}
|
|
283
|
+
declare function runScan(url: string, options?: ScanOptions): Promise<ScanReport>;
|
|
203
284
|
|
|
204
285
|
interface A11yNodeFinding {
|
|
205
286
|
target: string;
|
|
@@ -310,12 +391,37 @@ interface AuditRunResult {
|
|
|
310
391
|
categories: CategoryResult[];
|
|
311
392
|
overallScore: number;
|
|
312
393
|
}
|
|
313
|
-
|
|
394
|
+
/**
|
|
395
|
+
* Progress emitted per settled audit. The orchestrator forwards these into the
|
|
396
|
+
* ProgressTracker, which owns counting and fraction/elapsed stamping.
|
|
397
|
+
*/
|
|
398
|
+
type AuditProgressEvent = {
|
|
399
|
+
type: 'unit:done';
|
|
400
|
+
label: string;
|
|
401
|
+
} | {
|
|
402
|
+
type: 'unit:fail';
|
|
403
|
+
label: string;
|
|
404
|
+
error: string;
|
|
405
|
+
};
|
|
406
|
+
interface AuditPlan {
|
|
407
|
+
runnable: Array<{
|
|
408
|
+
reg: AuditRegistration;
|
|
409
|
+
categoryId: string;
|
|
410
|
+
}>;
|
|
411
|
+
skipped: CheckResult[];
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Split a scan config into the audits that will actually execute and the
|
|
415
|
+
* page-type-skipped `na` stubs. Exported so the orchestrator can size the
|
|
416
|
+
* audits progress phase before running it.
|
|
417
|
+
*/
|
|
418
|
+
declare function planAudits(ctx: CheckContext, config: ScanConfig): AuditPlan;
|
|
314
419
|
/**
|
|
315
420
|
* Execute all audits registered in the config against the scan context.
|
|
316
421
|
* Returns check results grouped into categories with weighted scores.
|
|
422
|
+
* Pass a precomputed `plan` (from {@link planAudits}) to avoid recomputing it.
|
|
317
423
|
*/
|
|
318
|
-
declare function runAudits(ctx: CheckContext, config: ScanConfig,
|
|
424
|
+
declare function runAudits(ctx: CheckContext, config: ScanConfig, onEvent?: (event: AuditProgressEvent) => void, plan?: AuditPlan): Promise<AuditRunResult>;
|
|
319
425
|
|
|
320
426
|
declare function parseHtml(html: string): CheerioAPI;
|
|
321
427
|
declare function extractJsonLd($: CheerioAPI): object[];
|
|
@@ -711,4 +817,4 @@ declare class Logger {
|
|
|
711
817
|
}
|
|
712
818
|
declare const logger: Logger;
|
|
713
819
|
|
|
714
|
-
export { type AgentLighthouseConfig, Audit, type AuditGuidance, AuditGuidanceSchema, type AuditMeta, AuditMetaSchema, type AuditRegistration, type AuditResult, AuditResultSchema, type AuditRunResult, CATEGORY_NAMES, CATEGORY_WEIGHTS, type CategoryConfig, type CategoryResult, type CheckContext, type CheckFn, type CheckPriority, CheckPrioritySchema, type CheckRecommendation, type CheckResult, CheckResultSchema, type CheckStatus, CheckStatusSchema, DEFAULT_SCAN_LIMIT, type FetchOptions, type FetchResult, type FieldStatus, type FixEffort, FixEffortSchema, MAX_CONCURRENT_REQUESTS, MAX_PAGES_PER_SCAN, MAX_RESPONSE_BODY_BYTES, PAGE_TYPE_LABELS, PRESETS, type PageContext, type PageOverride, type PageType, type PresetName, type PresetOptions, type ProductFieldVerification, READINESS_WEIGHTS, REQUEST_TIMEOUT_MS, type ReadinessVitals, SCANNER_USER_AGENT, SCAN_TIMEOUT_MS, SCORE_TIER_LABELS, type ScanConfig, type ScanReport, type ScoreDisplayMode, ScoreDisplayModeSchema, type ScoreTier, TAG_SCAN_ERROR, TAG_SKIPPED_PAGE_TYPE, type WafProtection, buildCategoryResult, calculateCategoryScore, calculateOverallScore, createFetcher, defaultConfig, defineConfig, detectPageType, detectWafProtection, extractDomain, extractForms, extractHeadLinks, extractHeadings, extractImages, extractInternalLinks, extractJsonLd, extractMarkdownLinks, extractMetaTags, extractNavLinks, extractProductFieldVerification, extractScripts, extractStylesheetUrls, flattenJsonLd, getMainContentText, getPreset, getScoreTier, getTierColor, getTierLabel, getWordCount, isPrivateIp, isSafeUrl, joinUrl, loadConfigFile, logger, normalizeUrl, parseHtml, runAudits, runScan };
|
|
820
|
+
export { type AgentLighthouseConfig, Audit, type AuditGuidance, AuditGuidanceSchema, type AuditMeta, AuditMetaSchema, type AuditPlan, type AuditProgressEvent, type AuditRegistration, type AuditResult, AuditResultSchema, type AuditRunResult, CATEGORY_NAMES, CATEGORY_WEIGHTS, type CategoryConfig, type CategoryResult, type CheckContext, type CheckFn, type CheckPriority, CheckPrioritySchema, type CheckRecommendation, type CheckResult, CheckResultSchema, type CheckStatus, CheckStatusSchema, DEFAULT_SCAN_LIMIT, type FetchOptions, type FetchResult, type FieldStatus, type FixEffort, FixEffortSchema, MAX_CONCURRENT_REQUESTS, MAX_PAGES_PER_SCAN, MAX_RESPONSE_BODY_BYTES, PAGE_TYPE_LABELS, PHASE_WEIGHTS, PRESETS, type PageContext, type PageOverride, type PageType, type PhaseId, type PresetName, type PresetOptions, type ProductFieldVerification, ProgressTracker, READINESS_WEIGHTS, REQUEST_TIMEOUT_MS, type ReadinessVitals, SCANNER_USER_AGENT, SCAN_TIMEOUT_MS, SCORE_TIER_LABELS, type ScanConfig, type ScanEvent, type ScanOptions, type ScanReport, type ScoreDisplayMode, ScoreDisplayModeSchema, type ScoreTier, TAG_SCAN_ERROR, TAG_SKIPPED_PAGE_TYPE, type WafProtection, buildCategoryResult, calculateCategoryScore, calculateOverallScore, createFetcher, defaultConfig, defineConfig, detectPageType, detectWafProtection, extractDomain, extractForms, extractHeadLinks, extractHeadings, extractImages, extractInternalLinks, extractJsonLd, extractMarkdownLinks, extractMetaTags, extractNavLinks, extractProductFieldVerification, extractScripts, extractStylesheetUrls, flattenJsonLd, getMainContentText, getPreset, getScoreTier, getTierColor, getTierLabel, getWordCount, isPrivateIp, isSafeUrl, joinUrl, loadConfigFile, logger, normalizeUrl, parseHtml, planAudits, runAudits, runScan };
|
package/dist/index.d.ts
CHANGED
|
@@ -198,8 +198,89 @@ interface ReadinessVitals {
|
|
|
198
198
|
technical: number;
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
-
type
|
|
202
|
-
|
|
201
|
+
type PhaseId = 'fetch-root' | 'fetch-pages' | 'analyze' | 'audits' | 'report';
|
|
202
|
+
type ScanEvent = {
|
|
203
|
+
type: 'scan:start';
|
|
204
|
+
url: string;
|
|
205
|
+
fraction: number;
|
|
206
|
+
elapsedMs: number;
|
|
207
|
+
} | {
|
|
208
|
+
type: 'phase:start';
|
|
209
|
+
phase: PhaseId;
|
|
210
|
+
totalUnits: number;
|
|
211
|
+
fraction: number;
|
|
212
|
+
elapsedMs: number;
|
|
213
|
+
} | {
|
|
214
|
+
type: 'unit:done';
|
|
215
|
+
phase: PhaseId;
|
|
216
|
+
completed: number;
|
|
217
|
+
total: number;
|
|
218
|
+
label?: string;
|
|
219
|
+
fraction: number;
|
|
220
|
+
elapsedMs: number;
|
|
221
|
+
} | {
|
|
222
|
+
type: 'unit:fail';
|
|
223
|
+
phase: PhaseId;
|
|
224
|
+
label: string;
|
|
225
|
+
error: string;
|
|
226
|
+
fraction: number;
|
|
227
|
+
elapsedMs: number;
|
|
228
|
+
} | {
|
|
229
|
+
type: 'phase:done';
|
|
230
|
+
phase: PhaseId;
|
|
231
|
+
durationMs: number;
|
|
232
|
+
fraction: number;
|
|
233
|
+
elapsedMs: number;
|
|
234
|
+
} | {
|
|
235
|
+
type: 'scan:done';
|
|
236
|
+
durationMs: number;
|
|
237
|
+
score: number;
|
|
238
|
+
fraction: number;
|
|
239
|
+
elapsedMs: number;
|
|
240
|
+
};
|
|
241
|
+
/**
|
|
242
|
+
* How much of the overall scan fraction each phase owns. Sums to 1 so the
|
|
243
|
+
* fraction of a finished scan is exactly 1.
|
|
244
|
+
*/
|
|
245
|
+
declare const PHASE_WEIGHTS: Record<PhaseId, number>;
|
|
246
|
+
/**
|
|
247
|
+
* Turns scan milestones into {@link ScanEvent}s. Owns the fraction math
|
|
248
|
+
* (`Σ weights of done phases + current phase weight × completed/total`),
|
|
249
|
+
* clamps it to be monotonic non-decreasing, and stamps `fraction`/`elapsedMs`
|
|
250
|
+
* onto every event. Emission is synchronous — throttling is a consumer concern.
|
|
251
|
+
*/
|
|
252
|
+
declare class ProgressTracker {
|
|
253
|
+
private readonly onEvent;
|
|
254
|
+
private readonly startMs;
|
|
255
|
+
private doneWeight;
|
|
256
|
+
private phase;
|
|
257
|
+
private phaseStartMs;
|
|
258
|
+
private totalUnits;
|
|
259
|
+
private completedUnits;
|
|
260
|
+
private lastFraction;
|
|
261
|
+
constructor(onEvent: (event: ScanEvent) => void);
|
|
262
|
+
/** Fraction of the whole scan that is complete, in [0, 1]. Never decreases. */
|
|
263
|
+
get fraction(): number;
|
|
264
|
+
/** Stamp an event with the current fraction/elapsed and advance the floor. */
|
|
265
|
+
private stamp;
|
|
266
|
+
private elapsedMs;
|
|
267
|
+
scanStart(url: string): void;
|
|
268
|
+
phaseStart(phase: PhaseId, totalUnits: number): void;
|
|
269
|
+
/** Correct the current phase's unit total (e.g. discovery finds pages mid-phase). */
|
|
270
|
+
setPhaseTotal(totalUnits: number): void;
|
|
271
|
+
unitDone(label?: string): void;
|
|
272
|
+
/** A failed unit still counts as settled work so the phase can complete. */
|
|
273
|
+
unitFail(label: string, error: string): void;
|
|
274
|
+
phaseDone(): void;
|
|
275
|
+
scanDone(score: number): void;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
interface ScanOptions {
|
|
279
|
+
onEvent?: (event: ScanEvent) => void;
|
|
280
|
+
pages?: PageOverride[] | null;
|
|
281
|
+
signal?: AbortSignal;
|
|
282
|
+
}
|
|
283
|
+
declare function runScan(url: string, options?: ScanOptions): Promise<ScanReport>;
|
|
203
284
|
|
|
204
285
|
interface A11yNodeFinding {
|
|
205
286
|
target: string;
|
|
@@ -310,12 +391,37 @@ interface AuditRunResult {
|
|
|
310
391
|
categories: CategoryResult[];
|
|
311
392
|
overallScore: number;
|
|
312
393
|
}
|
|
313
|
-
|
|
394
|
+
/**
|
|
395
|
+
* Progress emitted per settled audit. The orchestrator forwards these into the
|
|
396
|
+
* ProgressTracker, which owns counting and fraction/elapsed stamping.
|
|
397
|
+
*/
|
|
398
|
+
type AuditProgressEvent = {
|
|
399
|
+
type: 'unit:done';
|
|
400
|
+
label: string;
|
|
401
|
+
} | {
|
|
402
|
+
type: 'unit:fail';
|
|
403
|
+
label: string;
|
|
404
|
+
error: string;
|
|
405
|
+
};
|
|
406
|
+
interface AuditPlan {
|
|
407
|
+
runnable: Array<{
|
|
408
|
+
reg: AuditRegistration;
|
|
409
|
+
categoryId: string;
|
|
410
|
+
}>;
|
|
411
|
+
skipped: CheckResult[];
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Split a scan config into the audits that will actually execute and the
|
|
415
|
+
* page-type-skipped `na` stubs. Exported so the orchestrator can size the
|
|
416
|
+
* audits progress phase before running it.
|
|
417
|
+
*/
|
|
418
|
+
declare function planAudits(ctx: CheckContext, config: ScanConfig): AuditPlan;
|
|
314
419
|
/**
|
|
315
420
|
* Execute all audits registered in the config against the scan context.
|
|
316
421
|
* Returns check results grouped into categories with weighted scores.
|
|
422
|
+
* Pass a precomputed `plan` (from {@link planAudits}) to avoid recomputing it.
|
|
317
423
|
*/
|
|
318
|
-
declare function runAudits(ctx: CheckContext, config: ScanConfig,
|
|
424
|
+
declare function runAudits(ctx: CheckContext, config: ScanConfig, onEvent?: (event: AuditProgressEvent) => void, plan?: AuditPlan): Promise<AuditRunResult>;
|
|
319
425
|
|
|
320
426
|
declare function parseHtml(html: string): CheerioAPI;
|
|
321
427
|
declare function extractJsonLd($: CheerioAPI): object[];
|
|
@@ -711,4 +817,4 @@ declare class Logger {
|
|
|
711
817
|
}
|
|
712
818
|
declare const logger: Logger;
|
|
713
819
|
|
|
714
|
-
export { type AgentLighthouseConfig, Audit, type AuditGuidance, AuditGuidanceSchema, type AuditMeta, AuditMetaSchema, type AuditRegistration, type AuditResult, AuditResultSchema, type AuditRunResult, CATEGORY_NAMES, CATEGORY_WEIGHTS, type CategoryConfig, type CategoryResult, type CheckContext, type CheckFn, type CheckPriority, CheckPrioritySchema, type CheckRecommendation, type CheckResult, CheckResultSchema, type CheckStatus, CheckStatusSchema, DEFAULT_SCAN_LIMIT, type FetchOptions, type FetchResult, type FieldStatus, type FixEffort, FixEffortSchema, MAX_CONCURRENT_REQUESTS, MAX_PAGES_PER_SCAN, MAX_RESPONSE_BODY_BYTES, PAGE_TYPE_LABELS, PRESETS, type PageContext, type PageOverride, type PageType, type PresetName, type PresetOptions, type ProductFieldVerification, READINESS_WEIGHTS, REQUEST_TIMEOUT_MS, type ReadinessVitals, SCANNER_USER_AGENT, SCAN_TIMEOUT_MS, SCORE_TIER_LABELS, type ScanConfig, type ScanReport, type ScoreDisplayMode, ScoreDisplayModeSchema, type ScoreTier, TAG_SCAN_ERROR, TAG_SKIPPED_PAGE_TYPE, type WafProtection, buildCategoryResult, calculateCategoryScore, calculateOverallScore, createFetcher, defaultConfig, defineConfig, detectPageType, detectWafProtection, extractDomain, extractForms, extractHeadLinks, extractHeadings, extractImages, extractInternalLinks, extractJsonLd, extractMarkdownLinks, extractMetaTags, extractNavLinks, extractProductFieldVerification, extractScripts, extractStylesheetUrls, flattenJsonLd, getMainContentText, getPreset, getScoreTier, getTierColor, getTierLabel, getWordCount, isPrivateIp, isSafeUrl, joinUrl, loadConfigFile, logger, normalizeUrl, parseHtml, runAudits, runScan };
|
|
820
|
+
export { type AgentLighthouseConfig, Audit, type AuditGuidance, AuditGuidanceSchema, type AuditMeta, AuditMetaSchema, type AuditPlan, type AuditProgressEvent, type AuditRegistration, type AuditResult, AuditResultSchema, type AuditRunResult, CATEGORY_NAMES, CATEGORY_WEIGHTS, type CategoryConfig, type CategoryResult, type CheckContext, type CheckFn, type CheckPriority, CheckPrioritySchema, type CheckRecommendation, type CheckResult, CheckResultSchema, type CheckStatus, CheckStatusSchema, DEFAULT_SCAN_LIMIT, type FetchOptions, type FetchResult, type FieldStatus, type FixEffort, FixEffortSchema, MAX_CONCURRENT_REQUESTS, MAX_PAGES_PER_SCAN, MAX_RESPONSE_BODY_BYTES, PAGE_TYPE_LABELS, PHASE_WEIGHTS, PRESETS, type PageContext, type PageOverride, type PageType, type PhaseId, type PresetName, type PresetOptions, type ProductFieldVerification, ProgressTracker, READINESS_WEIGHTS, REQUEST_TIMEOUT_MS, type ReadinessVitals, SCANNER_USER_AGENT, SCAN_TIMEOUT_MS, SCORE_TIER_LABELS, type ScanConfig, type ScanEvent, type ScanOptions, type ScanReport, type ScoreDisplayMode, ScoreDisplayModeSchema, type ScoreTier, TAG_SCAN_ERROR, TAG_SKIPPED_PAGE_TYPE, type WafProtection, buildCategoryResult, calculateCategoryScore, calculateOverallScore, createFetcher, defaultConfig, defineConfig, detectPageType, detectWafProtection, extractDomain, extractForms, extractHeadLinks, extractHeadings, extractImages, extractInternalLinks, extractJsonLd, extractMarkdownLinks, extractMetaTags, extractNavLinks, extractProductFieldVerification, extractScripts, extractStylesheetUrls, flattenJsonLd, getMainContentText, getPreset, getScoreTier, getTierColor, getTierLabel, getWordCount, isPrivateIp, isSafeUrl, joinUrl, loadConfigFile, logger, normalizeUrl, parseHtml, planAudits, runAudits, runScan };
|
package/dist/index.js
CHANGED
|
@@ -45,7 +45,9 @@ __export(index_exports, {
|
|
|
45
45
|
MAX_PAGES_PER_SCAN: () => MAX_PAGES_PER_SCAN,
|
|
46
46
|
MAX_RESPONSE_BODY_BYTES: () => MAX_RESPONSE_BODY_BYTES,
|
|
47
47
|
PAGE_TYPE_LABELS: () => PAGE_TYPE_LABELS,
|
|
48
|
+
PHASE_WEIGHTS: () => PHASE_WEIGHTS,
|
|
48
49
|
PRESETS: () => PRESETS,
|
|
50
|
+
ProgressTracker: () => ProgressTracker,
|
|
49
51
|
READINESS_WEIGHTS: () => READINESS_WEIGHTS,
|
|
50
52
|
REQUEST_TIMEOUT_MS: () => REQUEST_TIMEOUT_MS,
|
|
51
53
|
SCANNER_USER_AGENT: () => SCANNER_USER_AGENT,
|
|
@@ -89,6 +91,7 @@ __export(index_exports, {
|
|
|
89
91
|
logger: () => logger,
|
|
90
92
|
normalizeUrl: () => normalizeUrl,
|
|
91
93
|
parseHtml: () => parseHtml,
|
|
94
|
+
planAudits: () => planAudits,
|
|
92
95
|
runAudits: () => runAudits,
|
|
93
96
|
runScan: () => runScan
|
|
94
97
|
});
|
|
@@ -17492,17 +17495,17 @@ function stubCheck(meta, tag, explanation) {
|
|
|
17492
17495
|
tags: [tag]
|
|
17493
17496
|
};
|
|
17494
17497
|
}
|
|
17495
|
-
|
|
17498
|
+
function planAudits(ctx, config) {
|
|
17496
17499
|
const scannedPageTypes = new Set(ctx.pages.map((p) => p.pageType));
|
|
17497
|
-
const
|
|
17498
|
-
const
|
|
17500
|
+
const runnable = [];
|
|
17501
|
+
const skipped = [];
|
|
17499
17502
|
for (const cat of config.categories) {
|
|
17500
17503
|
const regs = config.audits[cat.id] ?? [];
|
|
17501
17504
|
for (const reg2 of regs) {
|
|
17502
17505
|
const applicable = reg2.meta.applicablePageTypes;
|
|
17503
17506
|
if (applicable && applicable.length > 0) {
|
|
17504
17507
|
if (!applicable.some((pt) => scannedPageTypes.has(pt))) {
|
|
17505
|
-
|
|
17508
|
+
skipped.push(
|
|
17506
17509
|
stubCheck(
|
|
17507
17510
|
reg2.meta,
|
|
17508
17511
|
TAG_SKIPPED_PAGE_TYPE,
|
|
@@ -17512,30 +17515,35 @@ async function runAudits(ctx, config, onProgress) {
|
|
|
17512
17515
|
continue;
|
|
17513
17516
|
}
|
|
17514
17517
|
}
|
|
17515
|
-
|
|
17518
|
+
runnable.push({ reg: reg2, categoryId: cat.id });
|
|
17516
17519
|
}
|
|
17517
17520
|
}
|
|
17518
|
-
|
|
17519
|
-
|
|
17521
|
+
return { runnable, skipped };
|
|
17522
|
+
}
|
|
17523
|
+
async function runAudits(ctx, config, onEvent, plan) {
|
|
17524
|
+
const { runnable, skipped } = plan ?? planAudits(ctx, config);
|
|
17525
|
+
const allChecks = [...skipped];
|
|
17520
17526
|
const batchSize = 20;
|
|
17521
|
-
for (let i = 0; i <
|
|
17522
|
-
const batch =
|
|
17527
|
+
for (let i = 0; i < runnable.length; i += batchSize) {
|
|
17528
|
+
const batch = runnable.slice(i, i + batchSize);
|
|
17523
17529
|
const batchResults = await Promise.all(
|
|
17524
17530
|
batch.map(async ({ reg: reg2 }) => {
|
|
17531
|
+
const label2 = `${reg2.meta.id} ${reg2.meta.title}`;
|
|
17525
17532
|
try {
|
|
17526
17533
|
const instance = reg2.create();
|
|
17527
17534
|
const result = await instance.audit(ctx);
|
|
17528
|
-
|
|
17535
|
+
const check = instance.toCheckResult(result);
|
|
17536
|
+
onEvent?.({ type: "unit:done", label: label2 });
|
|
17537
|
+
return check;
|
|
17529
17538
|
} catch (err) {
|
|
17530
17539
|
logger.error({ err, auditId: reg2.meta.id }, "[scanner] Audit error");
|
|
17531
17540
|
const message = err instanceof Error ? err.message : String(err);
|
|
17541
|
+
onEvent?.({ type: "unit:fail", label: label2, error: message });
|
|
17532
17542
|
return stubCheck(reg2.meta, TAG_SCAN_ERROR, `Audit failed to run: ${message}`);
|
|
17533
17543
|
}
|
|
17534
17544
|
})
|
|
17535
17545
|
);
|
|
17536
17546
|
allChecks.push(...batchResults);
|
|
17537
|
-
completed += batchResults.length;
|
|
17538
|
-
onProgress?.(completed, totalAudits);
|
|
17539
17547
|
}
|
|
17540
17548
|
const categories = config.categories.map((cat) => {
|
|
17541
17549
|
const catChecks = allChecks.filter((c) => c.category === cat.id);
|
|
@@ -17583,6 +17591,110 @@ function buildWeightedCategoryResult(cat, checks2, registrations) {
|
|
|
17583
17591
|
};
|
|
17584
17592
|
}
|
|
17585
17593
|
|
|
17594
|
+
// src/progress.ts
|
|
17595
|
+
var PHASE_WEIGHTS = {
|
|
17596
|
+
"fetch-root": 0.35,
|
|
17597
|
+
"fetch-pages": 0.2,
|
|
17598
|
+
analyze: 0.1,
|
|
17599
|
+
audits: 0.3,
|
|
17600
|
+
report: 0.05
|
|
17601
|
+
};
|
|
17602
|
+
var ProgressTracker = class {
|
|
17603
|
+
onEvent;
|
|
17604
|
+
startMs;
|
|
17605
|
+
doneWeight = 0;
|
|
17606
|
+
phase = null;
|
|
17607
|
+
phaseStartMs = 0;
|
|
17608
|
+
totalUnits = 0;
|
|
17609
|
+
completedUnits = 0;
|
|
17610
|
+
lastFraction = 0;
|
|
17611
|
+
constructor(onEvent) {
|
|
17612
|
+
this.onEvent = onEvent;
|
|
17613
|
+
this.startMs = performance.now();
|
|
17614
|
+
}
|
|
17615
|
+
/** Fraction of the whole scan that is complete, in [0, 1]. Never decreases. */
|
|
17616
|
+
get fraction() {
|
|
17617
|
+
let f = this.doneWeight;
|
|
17618
|
+
if (this.phase !== null) {
|
|
17619
|
+
const ratio = this.totalUnits > 0 ? Math.min(1, this.completedUnits / this.totalUnits) : 0;
|
|
17620
|
+
f += PHASE_WEIGHTS[this.phase] * ratio;
|
|
17621
|
+
}
|
|
17622
|
+
return Math.min(1, Math.max(f, this.lastFraction));
|
|
17623
|
+
}
|
|
17624
|
+
/** Stamp an event with the current fraction/elapsed and advance the floor. */
|
|
17625
|
+
stamp() {
|
|
17626
|
+
const fraction = this.fraction;
|
|
17627
|
+
this.lastFraction = Math.max(this.lastFraction, fraction);
|
|
17628
|
+
return { fraction, elapsedMs: this.elapsedMs() };
|
|
17629
|
+
}
|
|
17630
|
+
elapsedMs() {
|
|
17631
|
+
return Math.max(0, Math.round(performance.now() - this.startMs));
|
|
17632
|
+
}
|
|
17633
|
+
scanStart(url) {
|
|
17634
|
+
this.onEvent({ type: "scan:start", url, ...this.stamp() });
|
|
17635
|
+
}
|
|
17636
|
+
phaseStart(phase, totalUnits) {
|
|
17637
|
+
this.phase = phase;
|
|
17638
|
+
this.phaseStartMs = performance.now();
|
|
17639
|
+
this.totalUnits = Math.max(0, totalUnits);
|
|
17640
|
+
this.completedUnits = 0;
|
|
17641
|
+
this.onEvent({
|
|
17642
|
+
type: "phase:start",
|
|
17643
|
+
phase,
|
|
17644
|
+
totalUnits: this.totalUnits,
|
|
17645
|
+
...this.stamp()
|
|
17646
|
+
});
|
|
17647
|
+
}
|
|
17648
|
+
/** Correct the current phase's unit total (e.g. discovery finds pages mid-phase). */
|
|
17649
|
+
setPhaseTotal(totalUnits) {
|
|
17650
|
+
this.totalUnits = Math.max(this.completedUnits, totalUnits);
|
|
17651
|
+
}
|
|
17652
|
+
unitDone(label2) {
|
|
17653
|
+
const phase = this.phase;
|
|
17654
|
+
if (phase === null) return;
|
|
17655
|
+
this.completedUnits += 1;
|
|
17656
|
+
this.onEvent({
|
|
17657
|
+
type: "unit:done",
|
|
17658
|
+
phase,
|
|
17659
|
+
completed: this.completedUnits,
|
|
17660
|
+
total: this.totalUnits,
|
|
17661
|
+
label: label2,
|
|
17662
|
+
...this.stamp()
|
|
17663
|
+
});
|
|
17664
|
+
}
|
|
17665
|
+
/** A failed unit still counts as settled work so the phase can complete. */
|
|
17666
|
+
unitFail(label2, error) {
|
|
17667
|
+
const phase = this.phase;
|
|
17668
|
+
if (phase === null) return;
|
|
17669
|
+
this.completedUnits += 1;
|
|
17670
|
+
this.onEvent({
|
|
17671
|
+
type: "unit:fail",
|
|
17672
|
+
phase,
|
|
17673
|
+
label: label2,
|
|
17674
|
+
error,
|
|
17675
|
+
...this.stamp()
|
|
17676
|
+
});
|
|
17677
|
+
}
|
|
17678
|
+
phaseDone() {
|
|
17679
|
+
const phase = this.phase;
|
|
17680
|
+
if (phase === null) return;
|
|
17681
|
+
this.phase = null;
|
|
17682
|
+
this.doneWeight += PHASE_WEIGHTS[phase];
|
|
17683
|
+
const durationMs = Math.max(0, Math.round(performance.now() - this.phaseStartMs));
|
|
17684
|
+
this.totalUnits = 0;
|
|
17685
|
+
this.completedUnits = 0;
|
|
17686
|
+
this.onEvent({
|
|
17687
|
+
type: "phase:done",
|
|
17688
|
+
phase,
|
|
17689
|
+
durationMs,
|
|
17690
|
+
...this.stamp()
|
|
17691
|
+
});
|
|
17692
|
+
}
|
|
17693
|
+
scanDone(score) {
|
|
17694
|
+
this.onEvent({ type: "scan:done", durationMs: this.elapsedMs(), score, ...this.stamp() });
|
|
17695
|
+
}
|
|
17696
|
+
};
|
|
17697
|
+
|
|
17586
17698
|
// src/audits/accessibility/runner.ts
|
|
17587
17699
|
var import_jsdom = require("jsdom");
|
|
17588
17700
|
|
|
@@ -23094,9 +23206,11 @@ function discoverPages(homepageUrl, domain, rootFiles, homepage$, exclude, maxAd
|
|
|
23094
23206
|
}
|
|
23095
23207
|
return selected;
|
|
23096
23208
|
}
|
|
23097
|
-
async function runScan(url,
|
|
23098
|
-
const
|
|
23099
|
-
|
|
23209
|
+
async function runScan(url, options) {
|
|
23210
|
+
const onEvent = options?.onEvent;
|
|
23211
|
+
const pageOverrides = options?.pages;
|
|
23212
|
+
const signal = options?.signal;
|
|
23213
|
+
const tracker = new ProgressTracker((event) => onEvent?.(event));
|
|
23100
23214
|
const start = performance.now();
|
|
23101
23215
|
const fetcher = createFetcher();
|
|
23102
23216
|
const baseUrl = new URL(url).origin;
|
|
@@ -23119,7 +23233,7 @@ async function runScan(url, onProgress, pageOverrides, signal) {
|
|
|
23119
23233
|
}
|
|
23120
23234
|
logger.debug({ url, domain }, "[orchestrator] Starting runScan");
|
|
23121
23235
|
signal?.throwIfAborted();
|
|
23122
|
-
|
|
23236
|
+
tracker.scanStart(displayUrl);
|
|
23123
23237
|
const rootFilePaths = [
|
|
23124
23238
|
"/robots.txt",
|
|
23125
23239
|
"/llms.txt",
|
|
@@ -23157,19 +23271,26 @@ async function runScan(url, onProgress, pageOverrides, signal) {
|
|
|
23157
23271
|
"/our-story"
|
|
23158
23272
|
];
|
|
23159
23273
|
logger.debug({ count: rootFilePaths.length }, "[orchestrator] Phase 1: Fetching root files");
|
|
23274
|
+
tracker.phaseStart("fetch-root", rootFilePaths.length);
|
|
23160
23275
|
const rootResults = await Promise.all(
|
|
23161
|
-
rootFilePaths.map(
|
|
23276
|
+
rootFilePaths.map(
|
|
23277
|
+
(path) => fetcher.fetch({ url: `${baseUrl}${path}`, signal }).then((result) => {
|
|
23278
|
+
tracker.unitDone(path);
|
|
23279
|
+
return result;
|
|
23280
|
+
})
|
|
23281
|
+
)
|
|
23162
23282
|
);
|
|
23163
23283
|
const rootFiles = {};
|
|
23164
23284
|
rootFilePaths.forEach((path, i) => {
|
|
23165
23285
|
rootFiles[path] = rootResults[i];
|
|
23166
23286
|
});
|
|
23167
|
-
|
|
23287
|
+
tracker.phaseDone();
|
|
23168
23288
|
logger.debug("[orchestrator] Phase 1 complete: Root files fetched");
|
|
23169
23289
|
signal?.throwIfAborted();
|
|
23170
|
-
await progress(30, "Fetching pages");
|
|
23171
23290
|
logger.debug("[orchestrator] Phase 2: Fetching pages");
|
|
23291
|
+
tracker.phaseStart("fetch-pages", 1);
|
|
23172
23292
|
const homepageResult = await fetcher.fetch({ url, signal });
|
|
23293
|
+
tracker.unitDone(displayUrl);
|
|
23173
23294
|
const homepage$ = homepageResult.status === 200 && homepageResult.body ? parseHtml(homepageResult.body) : null;
|
|
23174
23295
|
const discoverLimit = Math.max(0, MAX_PAGES_PER_SCAN - 1 - overrideUrls.length);
|
|
23175
23296
|
const discoveredUrls = homepage$ ? discoverPages(url, domain, rootFiles, homepage$, new Set(overrideTypeByKey.keys()), discoverLimit) : [];
|
|
@@ -23178,12 +23299,22 @@ async function runScan(url, onProgress, pageOverrides, signal) {
|
|
|
23178
23299
|
"[orchestrator] Page set: overrides + discovered URLs"
|
|
23179
23300
|
);
|
|
23180
23301
|
const extraUrls = [...overrideUrls, ...discoveredUrls];
|
|
23181
|
-
|
|
23302
|
+
tracker.setPhaseTotal(1 + extraUrls.length);
|
|
23182
23303
|
const extraResults = await Promise.all(
|
|
23183
|
-
extraUrls.map(
|
|
23304
|
+
extraUrls.map(
|
|
23305
|
+
(pageUrl) => fetcher.fetch({ url: pageUrl, signal }).then((result) => {
|
|
23306
|
+
tracker.unitDone(pageUrl);
|
|
23307
|
+
return result;
|
|
23308
|
+
})
|
|
23309
|
+
)
|
|
23184
23310
|
);
|
|
23311
|
+
tracker.phaseDone();
|
|
23185
23312
|
const allPageResults = [homepageResult, ...extraResults];
|
|
23186
23313
|
const allPageUrls = [displayUrl, ...extraUrls];
|
|
23314
|
+
tracker.phaseStart(
|
|
23315
|
+
"analyze",
|
|
23316
|
+
allPageResults.filter((r) => r.status === 200 && r.body).length
|
|
23317
|
+
);
|
|
23187
23318
|
const pages = allPageResults.map((r, i) => ({ result: r, url: allPageUrls[i], index: i })).filter((p) => p.result.status === 200 && p.result.body).map((p) => {
|
|
23188
23319
|
const $ = parseHtml(p.result.body);
|
|
23189
23320
|
const jsonLd = extractJsonLd($);
|
|
@@ -23191,6 +23322,7 @@ async function runScan(url, onProgress, pageOverrides, signal) {
|
|
|
23191
23322
|
const meta = extractMetaTags($);
|
|
23192
23323
|
const isFirstPage = p.index === 0;
|
|
23193
23324
|
const forcedType = overrideTypeByKey.get(p.url.replace(/\/$/, ""));
|
|
23325
|
+
tracker.unitDone(p.url);
|
|
23194
23326
|
return {
|
|
23195
23327
|
url: p.url,
|
|
23196
23328
|
pageType: forcedType ?? detectPageType(p.url, $, structuredData, meta, isFirstPage),
|
|
@@ -23208,13 +23340,12 @@ async function runScan(url, onProgress, pageOverrides, signal) {
|
|
|
23208
23340
|
p.a11yResults = await runA11yForHtml(p.fetchResult.body, p.url, A11Y_RULES);
|
|
23209
23341
|
})
|
|
23210
23342
|
);
|
|
23211
|
-
|
|
23343
|
+
tracker.phaseDone();
|
|
23212
23344
|
logger.debug(
|
|
23213
23345
|
{ pagesAnalyzed: pages.length },
|
|
23214
23346
|
"[orchestrator] Phase 2 complete: Page analysis complete"
|
|
23215
23347
|
);
|
|
23216
23348
|
signal?.throwIfAborted();
|
|
23217
|
-
await progress(60, "Running audits");
|
|
23218
23349
|
logger.debug("[orchestrator] Phase 3: Running audits");
|
|
23219
23350
|
const wafProtection = detectWafProtection(url, homepageResult, rootFiles, pages.length);
|
|
23220
23351
|
const ctx = {
|
|
@@ -23222,19 +23353,27 @@ async function runScan(url, onProgress, pageOverrides, signal) {
|
|
|
23222
23353
|
pages,
|
|
23223
23354
|
domain,
|
|
23224
23355
|
baseUrl,
|
|
23225
|
-
fetch: (
|
|
23356
|
+
fetch: (options2) => fetcher.fetch({ ...options2, signal }),
|
|
23226
23357
|
wafProtection: wafProtection ?? void 0
|
|
23227
23358
|
};
|
|
23359
|
+
const auditPlan = planAudits(ctx, defaultConfig);
|
|
23360
|
+
tracker.phaseStart("audits", auditPlan.runnable.length);
|
|
23228
23361
|
const {
|
|
23229
23362
|
checks: allChecks,
|
|
23230
23363
|
categories,
|
|
23231
23364
|
overallScore
|
|
23232
|
-
} = await runAudits(
|
|
23233
|
-
|
|
23234
|
-
|
|
23235
|
-
|
|
23365
|
+
} = await runAudits(
|
|
23366
|
+
ctx,
|
|
23367
|
+
defaultConfig,
|
|
23368
|
+
(event) => {
|
|
23369
|
+
if (event.type === "unit:done") tracker.unitDone(event.label);
|
|
23370
|
+
else tracker.unitFail(event.label, event.error);
|
|
23371
|
+
},
|
|
23372
|
+
auditPlan
|
|
23373
|
+
);
|
|
23374
|
+
tracker.phaseDone();
|
|
23236
23375
|
logger.debug("[orchestrator] Phase 3 complete: Audits finished");
|
|
23237
|
-
|
|
23376
|
+
tracker.phaseStart("report", 1);
|
|
23238
23377
|
logger.debug("[orchestrator] Phase 4: Building final report");
|
|
23239
23378
|
const durationMs = Math.round(performance.now() - start);
|
|
23240
23379
|
const recommendations = allChecks.filter((c) => c.status !== "pass").slice().sort((a, b) => {
|
|
@@ -23251,7 +23390,6 @@ async function runScan(url, onProgress, pageOverrides, signal) {
|
|
|
23251
23390
|
const topPasses = allChecks.filter((c) => c.status === "pass").slice().sort(
|
|
23252
23391
|
(a, b) => (weightMap.get(b.id) ?? 1) - (weightMap.get(a.id) ?? 1)
|
|
23253
23392
|
).slice(0, 10);
|
|
23254
|
-
await progress(100, "Complete");
|
|
23255
23393
|
const readinessVitals = calculateReadinessVitals(allChecks);
|
|
23256
23394
|
const readinessScore = Math.round(
|
|
23257
23395
|
readinessVitals.commerce * READINESS_WEIGHTS.commerce + readinessVitals.content * READINESS_WEIGHTS.content + readinessVitals.botAccessibility * READINESS_WEIGHTS.botAccessibility + readinessVitals.technical * READINESS_WEIGHTS.technical
|
|
@@ -23281,6 +23419,9 @@ async function runScan(url, onProgress, pageOverrides, signal) {
|
|
|
23281
23419
|
productFields: [...overrideTypeByKey.values()].includes("product") ? extractProductFieldVerification(pages) : void 0
|
|
23282
23420
|
};
|
|
23283
23421
|
report.summary = generateScanSummary(report);
|
|
23422
|
+
tracker.unitDone();
|
|
23423
|
+
tracker.phaseDone();
|
|
23424
|
+
tracker.scanDone(overallScore);
|
|
23284
23425
|
logger.debug({ durationMs, score: overallScore }, "[orchestrator] runScan complete");
|
|
23285
23426
|
return report;
|
|
23286
23427
|
}
|
|
@@ -23466,7 +23607,9 @@ function loadConfigFile(customPath) {
|
|
|
23466
23607
|
MAX_PAGES_PER_SCAN,
|
|
23467
23608
|
MAX_RESPONSE_BODY_BYTES,
|
|
23468
23609
|
PAGE_TYPE_LABELS,
|
|
23610
|
+
PHASE_WEIGHTS,
|
|
23469
23611
|
PRESETS,
|
|
23612
|
+
ProgressTracker,
|
|
23470
23613
|
READINESS_WEIGHTS,
|
|
23471
23614
|
REQUEST_TIMEOUT_MS,
|
|
23472
23615
|
SCANNER_USER_AGENT,
|
|
@@ -23510,6 +23653,7 @@ function loadConfigFile(customPath) {
|
|
|
23510
23653
|
logger,
|
|
23511
23654
|
normalizeUrl,
|
|
23512
23655
|
parseHtml,
|
|
23656
|
+
planAudits,
|
|
23513
23657
|
runAudits,
|
|
23514
23658
|
runScan
|
|
23515
23659
|
});
|