@agentsean/crawler 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/LICENSE +661 -0
  2. package/dist/adaptive.d.ts +9 -0
  3. package/dist/adaptive.d.ts.map +1 -0
  4. package/dist/adaptive.js +43 -0
  5. package/dist/adaptive.js.map +1 -0
  6. package/dist/crawl.d.ts +4 -0
  7. package/dist/crawl.d.ts.map +1 -0
  8. package/dist/crawl.js +348 -0
  9. package/dist/crawl.js.map +1 -0
  10. package/dist/decode.d.ts +7 -0
  11. package/dist/decode.d.ts.map +1 -0
  12. package/dist/decode.js +53 -0
  13. package/dist/decode.js.map +1 -0
  14. package/dist/extract.d.ts +4 -0
  15. package/dist/extract.d.ts.map +1 -0
  16. package/dist/extract.js +268 -0
  17. package/dist/extract.js.map +1 -0
  18. package/dist/hash.d.ts +4 -0
  19. package/dist/hash.d.ts.map +1 -0
  20. package/dist/hash.js +11 -0
  21. package/dist/hash.js.map +1 -0
  22. package/dist/http.d.ts +38 -0
  23. package/dist/http.d.ts.map +1 -0
  24. package/dist/http.js +207 -0
  25. package/dist/http.js.map +1 -0
  26. package/dist/index.d.ts +14 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +13 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/persist.d.ts +20 -0
  31. package/dist/persist.d.ts.map +1 -0
  32. package/dist/persist.js +186 -0
  33. package/dist/persist.js.map +1 -0
  34. package/dist/probe.d.ts +4 -0
  35. package/dist/probe.d.ts.map +1 -0
  36. package/dist/probe.js +132 -0
  37. package/dist/probe.js.map +1 -0
  38. package/dist/readability.d.ts +9 -0
  39. package/dist/readability.d.ts.map +1 -0
  40. package/dist/readability.js +60 -0
  41. package/dist/readability.js.map +1 -0
  42. package/dist/render.d.ts +15 -0
  43. package/dist/render.d.ts.map +1 -0
  44. package/dist/render.js +87 -0
  45. package/dist/render.js.map +1 -0
  46. package/dist/robots.d.ts +13 -0
  47. package/dist/robots.d.ts.map +1 -0
  48. package/dist/robots.js +187 -0
  49. package/dist/robots.js.map +1 -0
  50. package/dist/simhash.d.ts +9 -0
  51. package/dist/simhash.d.ts.map +1 -0
  52. package/dist/simhash.js +58 -0
  53. package/dist/simhash.js.map +1 -0
  54. package/dist/sitemap.d.ts +3 -0
  55. package/dist/sitemap.d.ts.map +1 -0
  56. package/dist/sitemap.js +165 -0
  57. package/dist/sitemap.js.map +1 -0
  58. package/dist/types.d.ts +251 -0
  59. package/dist/types.d.ts.map +1 -0
  60. package/dist/types.js +2 -0
  61. package/dist/types.js.map +1 -0
  62. package/dist/ua.d.ts +3 -0
  63. package/dist/ua.d.ts.map +1 -0
  64. package/dist/ua.js +3 -0
  65. package/dist/ua.js.map +1 -0
  66. package/dist/url.d.ts +11 -0
  67. package/dist/url.d.ts.map +1 -0
  68. package/dist/url.js +114 -0
  69. package/dist/url.js.map +1 -0
  70. package/package.json +60 -0
@@ -0,0 +1,186 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { crawls, entitySightings, findings, pages, pageSnapshots, sites, } from "@agentsean/db";
3
+ import { eq } from "drizzle-orm";
4
+ import { urlHash } from "./hash.js";
5
+ export async function persistCrawl(db, result, siteName, opts) {
6
+ const now = new Date().toISOString();
7
+ const existing = db.select().from(sites).where(eq(sites.origin, result.origin)).all();
8
+ let siteId = existing[0]?.id;
9
+ if (!siteId) {
10
+ siteId = randomUUID();
11
+ const observeUntil = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString();
12
+ db.insert(sites)
13
+ .values({
14
+ id: siteId,
15
+ origin: result.origin,
16
+ name: siteName ?? result.origin,
17
+ observeUntil,
18
+ createdAt: now,
19
+ updatedAt: now,
20
+ })
21
+ .run();
22
+ }
23
+ recordCrawlEntity(db, siteId, result.origin + "/", now);
24
+ const status = opts?.status ??
25
+ (result.aborted ? "running" : result.truncated ? "running" : "complete");
26
+ let crawlId = opts?.crawlId;
27
+ if (crawlId) {
28
+ const row = db.select().from(crawls).where(eq(crawls.id, crawlId)).get();
29
+ if (row) {
30
+ db.update(crawls)
31
+ .set({
32
+ finishedAt: status === "complete" ? result.finishedAt : null,
33
+ status,
34
+ pagesSeen: (row.pagesSeen ?? 0) + result.pagesSeen,
35
+ pagesChanged: (row.pagesChanged ?? 0) + result.pagesChanged,
36
+ error: result.aborted ? "aborted" : null,
37
+ })
38
+ .where(eq(crawls.id, crawlId))
39
+ .run();
40
+ }
41
+ else {
42
+ crawlId = undefined;
43
+ }
44
+ }
45
+ if (!crawlId) {
46
+ crawlId = randomUUID();
47
+ db.insert(crawls)
48
+ .values({
49
+ id: crawlId,
50
+ siteId,
51
+ startedAt: result.startedAt,
52
+ finishedAt: status === "complete" ? result.finishedAt : null,
53
+ status,
54
+ pagesSeen: result.pagesSeen,
55
+ pagesChanged: result.pagesChanged,
56
+ error: result.aborted ? "aborted" : null,
57
+ })
58
+ .run();
59
+ }
60
+ for (const page of result.pages) {
61
+ persistPage(db, siteId, crawlId, page, now);
62
+ }
63
+ return { siteId, crawlId };
64
+ }
65
+ function persistPage(db, siteId, crawlId, page, now) {
66
+ const hash = urlHash(page.url);
67
+ const found = db
68
+ .select()
69
+ .from(pages)
70
+ .where(eq(pages.siteId, siteId))
71
+ .all()
72
+ .find((row) => row.urlHash === hash);
73
+ const extract = page.extract;
74
+ const values = {
75
+ url: page.url,
76
+ urlHash: hash,
77
+ statusCode: page.statusCode,
78
+ contentHash: page.contentHash,
79
+ title: extract?.title ?? null,
80
+ metaDescription: extract?.metaDescription ?? null,
81
+ canonical: extract?.canonicalHtml[0] ?? null,
82
+ h1: extract?.h1[0] ?? null,
83
+ wordCount: extract?.mainWordCount ?? extract?.wordCount ?? null,
84
+ lang: extract?.lang ?? null,
85
+ jsonld: extract?.jsonLd.length
86
+ ? JSON.stringify(extract.jsonLd.map((j) => j.parsed))
87
+ : null,
88
+ lastCrawledAt: now,
89
+ lastChangedAt: page.notModified ? (found?.lastChangedAt ?? now) : now,
90
+ inlinkCount: page.inlinkCount,
91
+ outlinkCount: page.outlinkCount,
92
+ };
93
+ let pageId;
94
+ if (found) {
95
+ pageId = found.id;
96
+ db.update(pages).set(values).where(eq(pages.id, pageId)).run();
97
+ }
98
+ else {
99
+ pageId = randomUUID();
100
+ db.insert(pages)
101
+ .values({
102
+ id: pageId,
103
+ siteId,
104
+ firstSeenAt: now,
105
+ lastAuditedAt: null,
106
+ ...values,
107
+ })
108
+ .run();
109
+ }
110
+ db.insert(pageSnapshots)
111
+ .values({
112
+ id: randomUUID(),
113
+ pageId,
114
+ crawlId,
115
+ fetchedAt: page.fetchedAt,
116
+ statusCode: page.statusCode,
117
+ contentHash: page.contentHash,
118
+ body: page.html ? page.html.slice(0, 2_000_000) : null,
119
+ headers: JSON.stringify(page.headers),
120
+ })
121
+ .run();
122
+ recordCrawlEntity(db, siteId, page.url, now);
123
+ }
124
+ function recordCrawlEntity(db, siteId, url, now) {
125
+ const existing = db
126
+ .select()
127
+ .from(entitySightings)
128
+ .where(eq(entitySightings.siteId, siteId))
129
+ .all()
130
+ .find((row) => row.entity === url);
131
+ if (existing)
132
+ return;
133
+ db.insert(entitySightings)
134
+ .values({
135
+ id: randomUUID(),
136
+ siteId,
137
+ entity: url,
138
+ entityKind: "url",
139
+ source: "crawl",
140
+ firstSeenAt: now,
141
+ })
142
+ .run();
143
+ }
144
+ export function persistFindings(db, siteId, rows) {
145
+ const now = new Date().toISOString();
146
+ const pageRows = db.select().from(pages).where(eq(pages.siteId, siteId)).all();
147
+ const byUrl = new Map(pageRows.map((p) => [p.url, p.id]));
148
+ for (const row of rows) {
149
+ const existing = db
150
+ .select()
151
+ .from(findings)
152
+ .where(eq(findings.fingerprint, row.fingerprint))
153
+ .all()[0];
154
+ if (existing) {
155
+ db.update(findings)
156
+ .set({
157
+ severity: row.severity,
158
+ title: row.title,
159
+ explanation: row.explanation,
160
+ evidence: JSON.stringify(row.evidence),
161
+ status: "open",
162
+ })
163
+ .where(eq(findings.id, existing.id))
164
+ .run();
165
+ continue;
166
+ }
167
+ db.insert(findings)
168
+ .values({
169
+ id: randomUUID(),
170
+ siteId,
171
+ pageId: row.pageUrl ? (byUrl.get(row.pageUrl) ?? null) : null,
172
+ ruleId: row.ruleId,
173
+ severity: row.severity,
174
+ autonomyTier: row.autonomyTier,
175
+ title: row.title,
176
+ explanation: row.explanation,
177
+ evidence: JSON.stringify(row.evidence),
178
+ status: "open",
179
+ fingerprint: row.fingerprint,
180
+ firstDetectedAt: now,
181
+ resolvedAt: null,
182
+ })
183
+ .run();
184
+ }
185
+ }
186
+ //# sourceMappingURL=persist.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persist.js","sourceRoot":"","sources":["../src/persist.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { Agent } from "undici";
2
+ import type { OriginProbe } from "./types.js";
3
+ export declare function probeOrigin(startUrl: string, agent: Agent): Promise<OriginProbe>;
4
+ //# sourceMappingURL=probe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"probe.d.ts","sourceRoot":"","sources":["../src/probe.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAKpC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,KAAK,GACX,OAAO,CAAC,WAAW,CAAC,CA2FtB"}
package/dist/probe.js ADDED
@@ -0,0 +1,132 @@
1
+ import tls from "node:tls";
2
+ import { fetchUrl } from "./http.js";
3
+ import { contentHash } from "./hash.js";
4
+ import { originOf } from "./url.js";
5
+ import { randomBytes } from "node:crypto";
6
+ export async function probeOrigin(startUrl, agent) {
7
+ const origin = originOf(startUrl);
8
+ const u = new URL(origin);
9
+ const https = u.protocol === "https:";
10
+ let httpRedirectsToHttps = null;
11
+ let wwwSplit = null;
12
+ let wwwPreferred = null;
13
+ let trailingSlashSplit = null;
14
+ let randomSoft404 = false;
15
+ let randomSoft404Url = null;
16
+ let randomSoft404Hash = null;
17
+ let hsts = null;
18
+ if (https) {
19
+ try {
20
+ const httpUrl = `http://${u.host}${u.pathname}`;
21
+ const res = await fetchUrl(httpUrl, { agent, timeoutMs: 8_000, maxRedirects: 5 });
22
+ httpRedirectsToHttps = res.finalUrl.startsWith("https:");
23
+ }
24
+ catch {
25
+ httpRedirectsToHttps = false;
26
+ }
27
+ }
28
+ try {
29
+ const host = u.hostname;
30
+ const alt = host.startsWith("www.") ? host.slice(4) : `www.${host}`;
31
+ const altUrl = `${u.protocol}//${alt}/`;
32
+ const a = await fetchUrl(`${origin}/`, {
33
+ agent,
34
+ timeoutMs: 8_000,
35
+ maxRedirects: 0,
36
+ });
37
+ const b = await fetchUrl(altUrl, { agent, timeoutMs: 8_000, maxRedirects: 0 });
38
+ const a200 = a.statusCode === 200;
39
+ const b200 = b.statusCode === 200;
40
+ wwwSplit = a200 && b200;
41
+ if (!wwwSplit) {
42
+ wwwPreferred = a200 ? origin : b200 ? `${u.protocol}//${alt}` : origin;
43
+ }
44
+ hsts = a.headers["strict-transport-security"] ?? null;
45
+ }
46
+ catch {
47
+ wwwSplit = null;
48
+ }
49
+ try {
50
+ const withSlash = `${origin}/probe-slash-test/`;
51
+ const noSlash = `${origin}/probe-slash-test`;
52
+ const a = await fetchUrl(withSlash, { agent, timeoutMs: 8_000, maxRedirects: 0 });
53
+ const b = await fetchUrl(noSlash, { agent, timeoutMs: 8_000, maxRedirects: 0 });
54
+ trailingSlashSplit = a.statusCode === 200 && b.statusCode === 200;
55
+ }
56
+ catch {
57
+ trailingSlashSplit = null;
58
+ }
59
+ try {
60
+ const slug = randomBytes(16).toString("hex");
61
+ randomSoft404Url = `${origin}/${slug}`;
62
+ const res = await fetchUrl(randomSoft404Url, {
63
+ agent,
64
+ timeoutMs: 8_000,
65
+ maxRedirects: 0,
66
+ });
67
+ randomSoft404 = res.statusCode === 200;
68
+ if (randomSoft404 && res.decoded.length) {
69
+ randomSoft404Hash = contentHash(res.decoded);
70
+ }
71
+ }
72
+ catch {
73
+ randomSoft404 = false;
74
+ }
75
+ const cert = https
76
+ ? await readCert(u.hostname, Number(u.port || 443))
77
+ : {
78
+ certValidTo: null,
79
+ certDaysRemaining: null,
80
+ certError: null,
81
+ alpn: null,
82
+ };
83
+ return {
84
+ https,
85
+ httpRedirectsToHttps,
86
+ wwwSplit,
87
+ wwwPreferred,
88
+ trailingSlashSplit,
89
+ randomSoft404,
90
+ randomSoft404Url,
91
+ randomSoft404Hash,
92
+ hsts,
93
+ ...cert,
94
+ };
95
+ }
96
+ function readCert(host, port) {
97
+ return new Promise((resolve) => {
98
+ const sock = tls.connect({ host, port, servername: host, rejectUnauthorized: true }, () => {
99
+ const cert = sock.getPeerCertificate();
100
+ const validTo = cert.valid_to ? new Date(cert.valid_to) : null;
101
+ const days = validTo
102
+ ? Math.floor((validTo.getTime() - Date.now()) / 86_400_000)
103
+ : null;
104
+ const alpn = sock.alpnProtocol || sock.getProtocol() || null;
105
+ sock.end();
106
+ resolve({
107
+ certValidTo: validTo?.toISOString() ?? null,
108
+ certDaysRemaining: days,
109
+ certError: null,
110
+ alpn: typeof alpn === "string" ? alpn : null,
111
+ });
112
+ });
113
+ sock.setTimeout(5000, () => {
114
+ sock.destroy();
115
+ resolve({
116
+ certValidTo: null,
117
+ certDaysRemaining: null,
118
+ certError: "timeout",
119
+ alpn: null,
120
+ });
121
+ });
122
+ sock.on("error", (e) => {
123
+ resolve({
124
+ certValidTo: null,
125
+ certDaysRemaining: null,
126
+ certError: e.message,
127
+ alpn: null,
128
+ });
129
+ });
130
+ });
131
+ }
132
+ //# sourceMappingURL=probe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"probe.js","sourceRoot":"","sources":["../src/probe.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export type ReadabilityResult = {
2
+ title: string;
3
+ text: string;
4
+ wordCount: number;
5
+ };
6
+ export declare function extractMainContent(html: string, documentTitle: string): ReadabilityResult;
7
+ export declare function words(text: string): number;
8
+ export declare function loremIpsumIn(text: string): boolean;
9
+ //# sourceMappingURL=readability.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readability.d.ts","sourceRoot":"","sources":["../src/readability.ts"],"names":[],"mappings":"AA0BA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,aAAa,EAAE,MAAM,GACpB,iBAAiB,CA0BnB;AAED,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG1C;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAIlD"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Vendored main-content extractor.
3
+ *
4
+ * @mozilla/readability 0.6.0 last published 2025-03-03 (stale). We vendor a
5
+ * focused extractor rather than taking the unmaintained runtime dep.
6
+ *
7
+ * IMPORTANT: Mozilla Readability sources `title` from attacker-controlled
8
+ * JSON-LD `headline` and `excerpt` from `og:description`. Both MUST be
9
+ * treated as untrusted. This implementation never reads JSON-LD or Open
10
+ * Graph for title/excerpt.
11
+ */
12
+ import { parseHTML } from "linkedom";
13
+ const STRIP = new Set([
14
+ "SCRIPT",
15
+ "STYLE",
16
+ "NOSCRIPT",
17
+ "NAV",
18
+ "FOOTER",
19
+ "ASIDE",
20
+ "FORM",
21
+ "IFRAME",
22
+ "SVG",
23
+ "HEADER",
24
+ ]);
25
+ export function extractMainContent(html, documentTitle) {
26
+ let text = "";
27
+ try {
28
+ const { document } = parseHTML(html);
29
+ for (const el of document.querySelectorAll("*")) {
30
+ if (STRIP.has(el.tagName))
31
+ el.remove();
32
+ }
33
+ const main = document.querySelector("article") ??
34
+ document.querySelector("main") ??
35
+ document.querySelector("[role='main']") ??
36
+ document.querySelector("body");
37
+ text = (main?.textContent ?? "").replace(/\s+/g, " ").trim();
38
+ }
39
+ catch {
40
+ text = html
41
+ .replace(/<script[\s\S]*?<\/script>/gi, " ")
42
+ .replace(/<style[\s\S]*?<\/style>/gi, " ")
43
+ .replace(/<[^>]+>/g, " ")
44
+ .replace(/\s+/g, " ")
45
+ .trim();
46
+ }
47
+ return {
48
+ title: documentTitle,
49
+ text,
50
+ wordCount: words(text),
51
+ };
52
+ }
53
+ export function words(text) {
54
+ const m = text.trim().match(/\S+/g);
55
+ return m ? m.length : 0;
56
+ }
57
+ export function loremIpsumIn(text) {
58
+ return /lorem ipsum|dolor sit amet|placeholder text|\bTODO\b|\bFIXME\b|\[insert/i.test(text);
59
+ }
60
+ //# sourceMappingURL=readability.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readability.js","sourceRoot":"","sources":["../src/readability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAErC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC;IACpB,QAAQ;IACR,OAAO;IACP,UAAU;IACV,KAAK;IACL,QAAQ;IACR,OAAO;IACP,MAAM;IACN,QAAQ;IACR,KAAK;IACL,QAAQ;CACT,CAAC,CAAC;AAQH,MAAM,UAAU,kBAAkB,CAChC,IAAY,EACZ,aAAqB;IAErB,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,IAAI,CAAC;QACH,MAAM,EAAE,QAAQ,EAAE,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;QACrC,KAAK,MAAM,EAAE,IAAI,QAAQ,CAAC,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC;YAChD,IAAI,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,OAAO,CAAC;gBAAE,EAAE,CAAC,MAAM,EAAE,CAAC;QACzC,CAAC;QACD,MAAM,IAAI,GACR,QAAQ,CAAC,aAAa,CAAC,SAAS,CAAC;YACjC,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC;YAC9B,QAAQ,CAAC,aAAa,CAAC,eAAe,CAAC;YACvC,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;QACjC,IAAI,GAAG,CAAC,IAAI,EAAE,WAAW,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAC/D,CAAC;IAAC,MAAM,CAAC;QACP,IAAI,GAAG,IAAI;aACR,OAAO,CAAC,6BAA6B,EAAE,GAAG,CAAC;aAC3C,OAAO,CAAC,2BAA2B,EAAE,GAAG,CAAC;aACzC,OAAO,CAAC,UAAU,EAAE,GAAG,CAAC;aACxB,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;aACpB,IAAI,EAAE,CAAC;IACZ,CAAC;IACD,OAAO;QACL,KAAK,EAAE,aAAa;QACpB,IAAI;QACJ,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC;KACvB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,IAAY;IAChC,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACpC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AAC1B,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO,0EAA0E,CAAC,IAAI,CACpF,IAAI,CACL,CAAC;AACJ,CAAC"}
@@ -0,0 +1,15 @@
1
+ import type { PageExtract } from "./types.js";
2
+ export type RenderResult = {
3
+ html: string;
4
+ extract: PageExtract;
5
+ finalUrl: string;
6
+ errors: string[];
7
+ timedOut: boolean;
8
+ };
9
+ /**
10
+ * Adaptive Playwright renderer. Playwright is optional — if Chromium is not
11
+ * installed we skip JS rendering rather than failing the audit.
12
+ */
13
+ export declare function renderPage(url: string, rawHtml: string): Promise<RenderResult | null>;
14
+ export declare function jsDependencyScore(html: string, extract: PageExtract): number;
15
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAG9C,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,WAAW,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF;;;GAGG;AACH,wBAAsB,UAAU,CAC9B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CA6D9B;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,MAAM,CAQ5E"}
package/dist/render.js ADDED
@@ -0,0 +1,87 @@
1
+ import { extractPage } from "./extract.js";
2
+ /**
3
+ * Adaptive Playwright renderer. Playwright is optional — if Chromium is not
4
+ * installed we skip JS rendering rather than failing the audit.
5
+ */
6
+ export async function renderPage(url, rawHtml) {
7
+ let playwright;
8
+ try {
9
+ playwright = await import("playwright");
10
+ }
11
+ catch {
12
+ return null;
13
+ }
14
+ const browserType = playwright.chromium;
15
+ let browser;
16
+ try {
17
+ browser = await browserType.launch({
18
+ headless: true,
19
+ args: [
20
+ "--disable-dev-shm-usage",
21
+ "--disable-gpu",
22
+ "--disable-extensions",
23
+ "--mute-audio",
24
+ ],
25
+ });
26
+ const context = await browser.newContext({
27
+ viewport: { width: 412, height: 823 },
28
+ deviceScaleFactor: 2.625,
29
+ isMobile: true,
30
+ });
31
+ const page = await context.newPage();
32
+ await page.route("**/*", (route) => {
33
+ const type = route.request().resourceType();
34
+ if (type === "image" || type === "media" || type === "font") {
35
+ return route.abort();
36
+ }
37
+ return route.continue();
38
+ });
39
+ const errors = [];
40
+ page.on("pageerror", (e) => errors.push(e.message));
41
+ let timedOut = false;
42
+ try {
43
+ await page.goto(url, { waitUntil: "domcontentloaded", timeout: 15_000 });
44
+ }
45
+ catch {
46
+ timedOut = true;
47
+ }
48
+ const html = await page.content();
49
+ const finalUrl = page.url();
50
+ await context.close();
51
+ return {
52
+ html,
53
+ extract: extractPage(html, finalUrl),
54
+ finalUrl,
55
+ errors,
56
+ timedOut,
57
+ };
58
+ }
59
+ catch {
60
+ return {
61
+ html: rawHtml,
62
+ extract: extractPage(rawHtml, url),
63
+ finalUrl: url,
64
+ errors: ["playwright_unavailable"],
65
+ timedOut: false,
66
+ };
67
+ }
68
+ finally {
69
+ if (browser)
70
+ await browser.close().catch(() => undefined);
71
+ }
72
+ }
73
+ export function jsDependencyScore(html, extract) {
74
+ let score = 0;
75
+ if (extract.spaRootEmpty)
76
+ score += 0.5;
77
+ if (extract.hasNoscriptJsWarning)
78
+ score += 0.3;
79
+ if (extract.mainWordCount < 50)
80
+ score += 0.2;
81
+ if (extract.scripts.filter((s) => s.src).length >= 8)
82
+ score += 0.1;
83
+ if (/#\/|#!/.test(html))
84
+ score += 0.2;
85
+ return Math.min(1, score);
86
+ }
87
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { RobotsOutcome } from "./types.js";
2
+ export declare function parseRobotsTxt(raw: string): Omit<RobotsOutcome, "statusCode" | "contentType" | "bytes" | "redirectHops" | "error" | "mode">;
3
+ export declare function robotsFromFetch(opts: {
4
+ statusCode: number | null;
5
+ raw: string;
6
+ contentType: string;
7
+ bytes: number;
8
+ redirectHops: number;
9
+ error: string | null;
10
+ }): RobotsOutcome;
11
+ export declare function isAllowedByRobots(robots: RobotsOutcome, url: string, userAgent?: string): boolean;
12
+ export declare function matchRule(pattern: string, path: string): boolean;
13
+ //# sourceMappingURL=robots.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"robots.d.ts","sourceRoot":"","sources":["../src/robots.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAe,aAAa,EAAE,MAAM,YAAY,CAAC;AAI7D,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,GACV,IAAI,CACL,aAAa,EACb,YAAY,GAAG,aAAa,GAAG,OAAO,GAAG,cAAc,GAAG,OAAO,GAAG,MAAM,CAC3E,CA8FA;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE;IACpC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,GAAG,aAAa,CAoBhB;AAED,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,aAAa,EACrB,GAAG,EAAE,MAAM,EACX,SAAS,SAAY,GACpB,OAAO,CAOT;AAoCD,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAchE"}