@fulldotdev/scan 0.1.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.
Files changed (55) hide show
  1. package/README.md +79 -0
  2. package/bin/fullscan.js +2 -0
  3. package/dist/cli.d.ts +1 -0
  4. package/dist/cli.js +239 -0
  5. package/dist/engine/analysis.d.ts +2 -0
  6. package/dist/engine/analysis.js +747 -0
  7. package/dist/engine/browser-inspection.d.ts +143 -0
  8. package/dist/engine/browser-inspection.js +567 -0
  9. package/dist/engine/browser.d.ts +22 -0
  10. package/dist/engine/browser.js +629 -0
  11. package/dist/engine/collect.d.ts +6 -0
  12. package/dist/engine/collect.js +359 -0
  13. package/dist/engine/crawl-scope.d.ts +22 -0
  14. package/dist/engine/crawl-scope.js +145 -0
  15. package/dist/engine/env.d.ts +1 -0
  16. package/dist/engine/env.js +3 -0
  17. package/dist/engine/html.d.ts +307 -0
  18. package/dist/engine/html.js +645 -0
  19. package/dist/engine/language.d.ts +13 -0
  20. package/dist/engine/language.js +75 -0
  21. package/dist/engine/lighthouse-evidence.d.ts +36 -0
  22. package/dist/engine/lighthouse-evidence.js +69 -0
  23. package/dist/engine/lighthouse.d.ts +4 -0
  24. package/dist/engine/lighthouse.js +284 -0
  25. package/dist/engine/log.d.ts +1 -0
  26. package/dist/engine/log.js +4 -0
  27. package/dist/engine/network.d.ts +53 -0
  28. package/dist/engine/network.js +296 -0
  29. package/dist/engine/proxy.d.ts +8 -0
  30. package/dist/engine/proxy.js +95 -0
  31. package/dist/engine/run.d.ts +38 -0
  32. package/dist/engine/run.js +202 -0
  33. package/dist/engine/select.d.ts +6 -0
  34. package/dist/engine/select.js +38 -0
  35. package/dist/engine/site.d.ts +186 -0
  36. package/dist/engine/site.js +758 -0
  37. package/dist/engine/srcset.d.ts +1 -0
  38. package/dist/engine/srcset.js +31 -0
  39. package/dist/engine/state.d.ts +30 -0
  40. package/dist/engine/state.js +198 -0
  41. package/dist/engine/structured-data.d.ts +83 -0
  42. package/dist/engine/structured-data.js +331 -0
  43. package/dist/engine/types.d.ts +128 -0
  44. package/dist/engine/types.js +63 -0
  45. package/dist/engine.d.ts +1 -0
  46. package/dist/engine.js +1 -0
  47. package/dist/index.d.ts +8 -0
  48. package/dist/index.js +7 -0
  49. package/dist/report/build.d.ts +377 -0
  50. package/dist/report/build.js +2263 -0
  51. package/dist/report/evidence.d.ts +58 -0
  52. package/dist/report/evidence.js +192 -0
  53. package/dist/report/rules.d.ts +41 -0
  54. package/dist/report/rules.js +301 -0
  55. package/package.json +52 -0
@@ -0,0 +1,128 @@
1
+ import { z } from "zod";
2
+ export declare const version: string;
3
+ export declare const optionsSchema: z.ZodObject<{
4
+ deepScan: z.ZodDefault<z.ZodBoolean>;
5
+ lighthouseRepeatBelow: z.ZodOptional<z.ZodNumber>;
6
+ lighthouseMaxRuns: z.ZodDefault<z.ZodNumber>;
7
+ maxPages: z.ZodOptional<z.ZodNumber>;
8
+ maxDiscoveredUrls: z.ZodOptional<z.ZodNumber>;
9
+ maxResources: z.ZodOptional<z.ZodNumber>;
10
+ maxBrowserPages: z.ZodOptional<z.ZodNumber>;
11
+ maxSitemaps: z.ZodOptional<z.ZodNumber>;
12
+ maxQueryVariantsPerPath: z.ZodOptional<z.ZodNumber>;
13
+ requestTimeoutMs: z.ZodDefault<z.ZodNumber>;
14
+ maxResponseBytes: z.ZodDefault<z.ZodNumber>;
15
+ maxResourceBytes: z.ZodDefault<z.ZodNumber>;
16
+ maxSitemapBytes: z.ZodDefault<z.ZodNumber>;
17
+ requestDelayMs: z.ZodDefault<z.ZodNumber>;
18
+ browser: z.ZodDefault<z.ZodBoolean>;
19
+ lighthouse: z.ZodDefault<z.ZodEnum<{
20
+ all: "all";
21
+ off: "off";
22
+ templates: "templates";
23
+ }>>;
24
+ lighthousePerTemplate: z.ZodDefault<z.ZodNumber>;
25
+ external: z.ZodDefault<z.ZodBoolean>;
26
+ allowLocal: z.ZodDefault<z.ZodBoolean>;
27
+ }, z.core.$strict>;
28
+ export type Options = z.infer<typeof optionsSchema>;
29
+ export type JobKind = "discover" | "sitemap" | "page" | "resource" | "browser" | "lighthouse" | "analysis";
30
+ export type JobStatus = "queued" | "running" | "completed" | "failed" | "cancelled";
31
+ export interface Job {
32
+ kind: JobKind;
33
+ key: string;
34
+ status: JobStatus;
35
+ attempts: number;
36
+ error?: string;
37
+ durationMs?: number;
38
+ }
39
+ export interface ScanCoverage {
40
+ omittedCandidates: number;
41
+ templates?: number;
42
+ lighthouseEligible?: number;
43
+ lighthouseSelected?: string[];
44
+ lighthouseSelection?: string;
45
+ jobs?: {
46
+ kind: string;
47
+ status: string;
48
+ count: number;
49
+ }[];
50
+ expired?: boolean;
51
+ }
52
+ export interface Scan {
53
+ id: string;
54
+ url: string;
55
+ status: "running" | "completed" | "partial";
56
+ options: Options;
57
+ createdAt: string;
58
+ finishedAt?: string | null;
59
+ rotation: number;
60
+ coverage: ScanCoverage;
61
+ }
62
+ export interface Observation {
63
+ kind: string;
64
+ key: string;
65
+ data: any;
66
+ }
67
+ export interface Artifact {
68
+ kind: string;
69
+ key: string;
70
+ contentType: string;
71
+ body: string | Buffer;
72
+ }
73
+ export interface Candidate {
74
+ url: string;
75
+ source: string;
76
+ kind: "page" | "resource" | "sitemap";
77
+ }
78
+ export interface JobResult {
79
+ observations: Observation[];
80
+ artifacts?: Artifact[];
81
+ candidates?: Candidate[];
82
+ }
83
+ export interface UrlRow {
84
+ url: string;
85
+ kind: Candidate["kind"];
86
+ sources: string[];
87
+ scheduled: boolean;
88
+ skipReason: string | null;
89
+ }
90
+ export interface HttpResult {
91
+ url: string;
92
+ finalUrl: string;
93
+ status: number | null;
94
+ headers: Record<string, string>;
95
+ redirects: {
96
+ url: string;
97
+ status: number;
98
+ location: string;
99
+ }[];
100
+ body: string;
101
+ raw?: Buffer;
102
+ bytes: number;
103
+ durationMs: number;
104
+ fetchedAt: string;
105
+ outcome: "ok" | "http-error" | "inconclusive" | "blocked" | "error";
106
+ error?: string;
107
+ truncated?: boolean;
108
+ attempts: number;
109
+ }
110
+ export declare function httpEvidence(result: HttpResult): {
111
+ url: string;
112
+ finalUrl: string;
113
+ status: number | null;
114
+ headers: Record<string, string>;
115
+ redirects: {
116
+ url: string;
117
+ status: number;
118
+ location: string;
119
+ }[];
120
+ bytes: number;
121
+ durationMs: number;
122
+ fetchedAt: string;
123
+ outcome: "ok" | "http-error" | "inconclusive" | "blocked" | "error";
124
+ error?: string;
125
+ truncated?: boolean;
126
+ attempts: number;
127
+ };
128
+ export declare function errorMessage(error: unknown): string;
@@ -0,0 +1,63 @@
1
+ import { z } from "zod";
2
+ import pkg from "../../package.json" with { type: "json" };
3
+ export const version = pkg.version;
4
+ // Count limits are optional: a scan follows every discovered page, sitemap
5
+ // and resource unless a limit is supplied. Size and time limits always apply.
6
+ export const optionsSchema = z
7
+ .object({
8
+ // Deeper measurement: rendering observation, Lighthouse repeats, more
9
+ // Lighthouse detail. Slower; meant for manual scans.
10
+ deepScan: z.boolean().default(false),
11
+ lighthouseRepeatBelow: z.number().int().min(0).max(100).optional(),
12
+ lighthouseMaxRuns: z.number().int().min(1).max(3).default(3),
13
+ maxPages: z.number().int().min(1).optional(),
14
+ maxDiscoveredUrls: z.number().int().min(1).optional(),
15
+ maxResources: z.number().int().min(0).optional(),
16
+ // The per-page browser pass runs on every HTML page unless limited.
17
+ maxBrowserPages: z.number().int().min(0).optional(),
18
+ maxSitemaps: z.number().int().min(1).optional(),
19
+ // Query-string variants of one path that are not pagination are followed
20
+ // up to this many; the rest are recorded as not followed (facet traps).
21
+ maxQueryVariantsPerPath: z.number().int().min(1).optional(),
22
+ requestTimeoutMs: z.number().int().min(1000).max(30000).default(12000),
23
+ maxResponseBytes: z
24
+ .number()
25
+ .int()
26
+ .min(1024)
27
+ .max(8_000_000)
28
+ .default(2_000_000),
29
+ // Resources are hashed and type-sniffed, not read, so a prefix is enough.
30
+ maxResourceBytes: z
31
+ .number()
32
+ .int()
33
+ .min(1024)
34
+ .max(8_000_000)
35
+ .default(500_000),
36
+ // Large sites ship sitemaps far bigger than a page.
37
+ maxSitemapBytes: z
38
+ .number()
39
+ .int()
40
+ .min(1024)
41
+ .max(50_000_000)
42
+ .default(10_000_000),
43
+ requestDelayMs: z.number().int().min(100).max(5000).default(200),
44
+ // The browser pass (rendered comparison, axe, overflow, keyboard).
45
+ browser: z.boolean().default(true),
46
+ // Lighthouse on one page per template (rotating), on every page, or off.
47
+ lighthouse: z.enum(["templates", "all", "off"]).default("templates"),
48
+ lighthousePerTemplate: z.number().int().min(1).max(10).default(1),
49
+ // Free external sources: RDAP, Chrome UX Report, Safe Browsing,
50
+ // SSL Labs, HSTS preload, certificate logs, blocklists.
51
+ external: z.boolean().default(true),
52
+ // Loopback and private targets, for a local development server. Every
53
+ // external source is then reported as not applicable.
54
+ allowLocal: z.boolean().default(false),
55
+ })
56
+ .strict();
57
+ export function httpEvidence(result) {
58
+ const { body, raw, ...evidence } = result;
59
+ return evidence;
60
+ }
61
+ export function errorMessage(error) {
62
+ return error instanceof Error ? error.message : String(error);
63
+ }
@@ -0,0 +1 @@
1
+ export { runScan, type RunInput, type RunOutput, type ScanEvent } from "./engine/run.js";
package/dist/engine.js ADDED
@@ -0,0 +1 @@
1
+ export { runScan } from "./engine/run.js";
@@ -0,0 +1,8 @@
1
+ export type { RunInput, RunOutput, ScanEvent } from "./engine/run.js";
2
+ export { optionsSchema, version, type Options, type Scan, type Observation, type UrlRow, type Job } from "./engine/types.js";
3
+ export { ScanState, type StoredArtifact } from "./engine/state.js";
4
+ export { normalizeUrl } from "./engine/network.js";
5
+ export { templateKey } from "./engine/select.js";
6
+ export { buildReport, formatReport, diffFindings, type Report, type Finding, type StoredFinding, type Check, type Changes, type PreviousScan, } from "./report/build.js";
7
+ export { rules, topicLabels, topicOrder, publicReport, publicFinding, type Rule, type Topic, type Severity, type Confidence } from "./report/rules.js";
8
+ export { assetInventory, pageRows, incomingLinks, type AssetRow, type PageRow } from "./report/evidence.js";
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export { optionsSchema, version } from "./engine/types.js";
2
+ export { ScanState } from "./engine/state.js";
3
+ export { normalizeUrl } from "./engine/network.js";
4
+ export { templateKey } from "./engine/select.js";
5
+ export { buildReport, formatReport, diffFindings, } from "./report/build.js";
6
+ export { rules, topicLabels, topicOrder, publicReport, publicFinding } from "./report/rules.js";
7
+ export { assetInventory, pageRows, incomingLinks } from "./report/evidence.js";
@@ -0,0 +1,377 @@
1
+ import { type Confidence, type Severity, type Topic } from "./rules.js";
2
+ import type { Observation, Scan, UrlRow } from "../engine/types.js";
3
+ import type { StoredArtifact } from "../engine/state.js";
4
+ export type { Topic, Severity, Confidence } from "./rules.js";
5
+ export type Area = "availability" | "search" | "performance" | "accessibility" | "quality";
6
+ export interface FindingItem {
7
+ url: string | null;
8
+ text: string;
9
+ }
10
+ export interface Finding {
11
+ id: string;
12
+ code: string;
13
+ group?: string;
14
+ topic: Topic;
15
+ area?: Area;
16
+ severity: Severity;
17
+ confidence: Confidence;
18
+ title: string;
19
+ problem: string;
20
+ impact: string;
21
+ count: number;
22
+ pages: number;
23
+ items: string[];
24
+ urls: string[];
25
+ description?: string;
26
+ action?: string;
27
+ }
28
+ export interface StoredFinding extends Omit<Finding, "items"> {
29
+ items: FindingItem[];
30
+ }
31
+ export type CheckOutcome = "passed" | "issue" | "review" | "not-applicable" | "unavailable" | "not-checked";
32
+ export interface Check {
33
+ code: string;
34
+ topic: Topic;
35
+ title: string;
36
+ outcome: CheckOutcome;
37
+ detail?: string;
38
+ }
39
+ export interface Scores {
40
+ performance: number | null;
41
+ accessibility: number | null;
42
+ bestPractices: number | null;
43
+ seo: number | null;
44
+ }
45
+ export interface FieldData {
46
+ available: boolean;
47
+ level?: "origin" | "url";
48
+ lcp: number | null;
49
+ inp: number | null;
50
+ cls: number | null;
51
+ verdict: "good" | "needs-improvement" | "poor" | null;
52
+ period: {
53
+ from: string;
54
+ to: string;
55
+ } | null;
56
+ note: string;
57
+ history?: {
58
+ to: string | null;
59
+ lcp: number | null;
60
+ inp: number | null;
61
+ cls: number | null;
62
+ }[];
63
+ }
64
+ export interface Security {
65
+ safeBrowsing: {
66
+ flagged: boolean;
67
+ threats: string[];
68
+ } | null;
69
+ tlsGrade: string | null;
70
+ hstsPreload: string | null;
71
+ blocklists: {
72
+ listed: string[];
73
+ clean: string[];
74
+ unavailable: string[];
75
+ } | null;
76
+ certificates: {
77
+ hostnames: number;
78
+ issuedLast30Days: number;
79
+ sample: string[];
80
+ } | null;
81
+ }
82
+ export interface DomainInfo {
83
+ name: string;
84
+ expiresAt: string | null;
85
+ daysRemaining: number | null;
86
+ registrar: string | null;
87
+ }
88
+ export interface Indexability {
89
+ indexable: number;
90
+ noindex: number;
91
+ canonicalElsewhere: number;
92
+ broken: number;
93
+ blocked: number;
94
+ }
95
+ export interface VisualChange {
96
+ url: string;
97
+ changed: number;
98
+ screenshotId: string | null;
99
+ previousScreenshotId: string | null;
100
+ }
101
+ export interface FindingChange {
102
+ id: string;
103
+ code: string;
104
+ title: string;
105
+ severity: Severity;
106
+ count: number;
107
+ from?: number;
108
+ to?: number;
109
+ }
110
+ export interface Changes {
111
+ previousScanId: string;
112
+ previousCreatedAt: string;
113
+ newFindings: Finding[];
114
+ resolvedFindings: FindingChange[];
115
+ unverifiedFindings: FindingChange[];
116
+ recurringFindings: FindingChange[];
117
+ worsenedFindings: FindingChange[];
118
+ fixedFindings: FindingChange[];
119
+ pagesAdded: string[];
120
+ pagesRemoved: string[];
121
+ titleChanges: {
122
+ url: string;
123
+ from: string;
124
+ to: string;
125
+ }[];
126
+ changedPages: {
127
+ url: string;
128
+ changes: string[];
129
+ }[];
130
+ infrastructure: string[];
131
+ scoreDrops: {
132
+ category: keyof Scores;
133
+ from: number;
134
+ to: number;
135
+ }[];
136
+ performance: {
137
+ scores: {
138
+ category: keyof Scores;
139
+ from: number;
140
+ to: number;
141
+ }[];
142
+ metrics: {
143
+ metric: string;
144
+ from: number;
145
+ to: number;
146
+ }[];
147
+ field: {
148
+ metric: string;
149
+ from: number;
150
+ to: number;
151
+ }[];
152
+ };
153
+ visual: VisualChange[];
154
+ indexability: {
155
+ from: number;
156
+ to: number;
157
+ } | null;
158
+ }
159
+ export interface TopicSummary {
160
+ status: "issues" | "review" | "passed" | "unavailable" | "not-checked" | "not-applicable";
161
+ findings: number;
162
+ critical: number;
163
+ warning: number;
164
+ hint: number;
165
+ facts: string[];
166
+ }
167
+ export interface Coverage {
168
+ discovered: number;
169
+ crawled: number;
170
+ omitted: number;
171
+ sampled: boolean;
172
+ complete: boolean;
173
+ pages: {
174
+ discovered: number;
175
+ followed: number;
176
+ notFollowed: Record<string, number>;
177
+ observed: number;
178
+ ok: number;
179
+ broken: number;
180
+ blocked: number;
181
+ inconclusive: number;
182
+ notReached: number;
183
+ };
184
+ resources: {
185
+ discovered: number;
186
+ checked: number;
187
+ ok: number;
188
+ broken: number;
189
+ inconclusive: number;
190
+ unverifiable: number;
191
+ notChecked: number;
192
+ };
193
+ sitemaps: {
194
+ discovered: number;
195
+ fetched: number;
196
+ valid: number;
197
+ invalid: number;
198
+ entries: number;
199
+ notFetched: number;
200
+ };
201
+ browser: {
202
+ enabled: boolean;
203
+ eligible: number;
204
+ completed: number;
205
+ partial: number;
206
+ skipped: number;
207
+ notRun: number;
208
+ };
209
+ lighthouse: {
210
+ enabled: boolean;
211
+ eligible: number;
212
+ selected: number;
213
+ omitted: number;
214
+ completed: number;
215
+ partial: number;
216
+ notRun: number;
217
+ devices: string[];
218
+ };
219
+ interactions: {
220
+ pagesScrolled: number;
221
+ loadMoreFound: number;
222
+ loadMoreClicked: number;
223
+ notPerformed: string[];
224
+ };
225
+ slashVariants?: {
226
+ expected: number;
227
+ verified: number;
228
+ unknown: string[];
229
+ };
230
+ remainingWork: {
231
+ kind: string;
232
+ status: string;
233
+ count: number;
234
+ }[];
235
+ expired: boolean;
236
+ external: Record<string, string>;
237
+ limits: Record<string, number>;
238
+ notes: string[];
239
+ }
240
+ export interface Report {
241
+ version?: 2;
242
+ scanId: string;
243
+ siteUrl: string;
244
+ status: string;
245
+ createdAt: string;
246
+ finishedAt: string | null;
247
+ overview?: {
248
+ characteristics: {
249
+ technologies: string[];
250
+ languages: string[];
251
+ pageTypes: Record<string, number>;
252
+ ecommerce: boolean;
253
+ structuredDataTypes: string[];
254
+ sitemap: boolean;
255
+ llms: boolean;
256
+ markdown: boolean;
257
+ multilingual: boolean;
258
+ };
259
+ summary: string;
260
+ priorities: string[];
261
+ };
262
+ health: "healthy" | "attention" | "critical";
263
+ pages: {
264
+ observed: number;
265
+ ok: number;
266
+ broken: number;
267
+ tested: number;
268
+ lighthouse?: number;
269
+ };
270
+ coverage: Coverage;
271
+ indexability: Indexability;
272
+ security: Security;
273
+ weight: {
274
+ averageBytes: number | null;
275
+ heaviest: {
276
+ url: string;
277
+ bytes: number;
278
+ } | null;
279
+ carbonGrams: number | null;
280
+ };
281
+ scores: {
282
+ mobile: Scores | null;
283
+ desktop: Scores | null;
284
+ measuredAt?: string | null;
285
+ carried?: boolean;
286
+ };
287
+ metrics: {
288
+ pages: number;
289
+ ttfb: number | null;
290
+ fcp: number | null;
291
+ lcp: number | null;
292
+ cls: number | null;
293
+ bytes: number | null;
294
+ thirdPartyShare: number | null;
295
+ } | null;
296
+ field: FieldData | null;
297
+ fieldPages?: ({
298
+ url: string;
299
+ } & FieldData)[];
300
+ domain: DomainInfo | null;
301
+ topics?: Record<Topic, TopicSummary>;
302
+ checks?: Check[];
303
+ methodology?: {
304
+ versions: Record<string, string | null>;
305
+ measuredAt: Record<string, string | null>;
306
+ notes: string[];
307
+ };
308
+ counts: {
309
+ critical: number;
310
+ warning: number;
311
+ hint?: number;
312
+ };
313
+ findings: Finding[];
314
+ changes: Changes | null;
315
+ }
316
+ export interface PreviousPage {
317
+ key: string;
318
+ title: string | null;
319
+ outcome: string | null;
320
+ status: number | null;
321
+ words: number | null;
322
+ canonical: string | null;
323
+ language: string | null;
324
+ }
325
+ export interface PreviousScan {
326
+ id: string;
327
+ createdAt: string;
328
+ report: Report;
329
+ findings: StoredFinding[];
330
+ pages: PreviousPage[];
331
+ dns?: any;
332
+ tls?: any;
333
+ home?: {
334
+ technologies?: {
335
+ name: string;
336
+ }[];
337
+ } | null;
338
+ screenshots?: {
339
+ url: string;
340
+ id: string;
341
+ body: Buffer;
342
+ }[];
343
+ lastScores?: {
344
+ mobile: Scores | null;
345
+ desktop: Scores | null;
346
+ measuredAt?: string | null;
347
+ } | null;
348
+ }
349
+ export interface ReportInput {
350
+ records: Observation[];
351
+ urls: UrlRow[];
352
+ artifacts: StoredArtifact[];
353
+ previous?: PreviousScan;
354
+ }
355
+ export declare function fieldVerdict(d: {
356
+ lcp?: number | null;
357
+ inp?: number | null;
358
+ cls?: number | null;
359
+ }): FieldData["verdict"];
360
+ export declare function diffFindings(before: StoredFinding[] | Finding[], after: StoredFinding[], verified?: (item: FindingItem | string, finding: {
361
+ code: string;
362
+ id?: string;
363
+ group?: string;
364
+ }) => boolean): {
365
+ newFindings: StoredFinding[];
366
+ resolvedFindings: FindingChange[];
367
+ unverifiedFindings: FindingChange[];
368
+ recurringFindings: FindingChange[];
369
+ worsenedFindings: FindingChange[];
370
+ };
371
+ export declare function screenshotDifference(a: Buffer, b: Buffer): number | null;
372
+ export declare function buildReport(scan: Scan, input: ReportInput): Promise<{
373
+ report: Report;
374
+ findings: StoredFinding[];
375
+ }>;
376
+ export declare const areaLabels: Record<Area, string>;
377
+ export declare function formatReport(report: Report): string;