@seostudio/audit-engine 0.0.0-stage → 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.
@@ -0,0 +1,197 @@
1
+ // src/config.ts
2
+ var SEO_CRAWLER_ROBOTS_AGENT = "SiteSnapshot";
3
+ var SEO_CRAWLER_USER_AGENT_TOKEN = `${SEO_CRAWLER_ROBOTS_AGENT}/1.0`;
4
+ var SEO_CRAWLER_DOCS_PATH = "/docs/site-snapshot";
5
+ function seoCrawlerUserAgent(baseUrl) {
6
+ return `${SEO_CRAWLER_USER_AGENT_TOKEN} (+${baseUrl.replace(/\/+$/, "")}${SEO_CRAWLER_DOCS_PATH})`;
7
+ }
8
+ function compatiblePart(crawlerUserAgent) {
9
+ const match = /^(\S+) \(\+([^)]+)\)$/.exec(crawlerUserAgent.trim());
10
+ return match === null ? `${crawlerUserAgent.trim()}; bot` : `${match[1] ?? ""}; bot; +${match[2] ?? ""}`;
11
+ }
12
+ function seoRenderUserAgent(crawlerUserAgent, chromeMajor) {
13
+ if (seoIsBrowserUserAgent(crawlerUserAgent)) return crawlerUserAgent.trim();
14
+ const major = /^\d+$/.test(chromeMajor) ? chromeMajor : "0";
15
+ return `Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${major}.0.0.0 Mobile Safari/537.36 (compatible; ${compatiblePart(crawlerUserAgent)})`;
16
+ }
17
+ function seoDesktopRenderUserAgent(crawlerUserAgent, chromeMajor) {
18
+ if (seoIsBrowserUserAgent(crawlerUserAgent)) return crawlerUserAgent.trim();
19
+ const major = /^\d+$/.test(chromeMajor) ? chromeMajor : "0";
20
+ return `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${major}.0.0.0 Safari/537.36 (compatible; ${compatiblePart(crawlerUserAgent)})`;
21
+ }
22
+ var SEO_STATED_CHROME_MAJOR = "141";
23
+ function seoIsBrowserUserAgent(userAgent) {
24
+ return userAgent.trim().startsWith("Mozilla/");
25
+ }
26
+ var seoCrawlerLimits = {
27
+ minDelayMs: 1e3,
28
+ maxCrawlDelayMs: 1e4,
29
+ timeoutMs: 15e3,
30
+ maxPageBytes: 5 * 1024 * 1024,
31
+ maxSitemapBytes: 50 * 1024 * 1024,
32
+ maxSitemapFiles: 50,
33
+ maxSitemapBytesTotal: 100 * 1024 * 1024,
34
+ maxSitemapUrls: 5e4,
35
+ maxRedirects: 5,
36
+ trapGuards: { maxQueryParams: 3, maxPathSegments: 12, maxUrlChars: 1024, maxSegmentRepeats: 2 },
37
+ maxLinksPerPage: 500,
38
+ renderTimeoutMs: 15e3,
39
+ renderQuietMs: 500,
40
+ renderResourceTimeoutMs: 1e4,
41
+ renderMaxRequests: 300,
42
+ renderMaxResourceBytes: 8 * 1024 * 1024,
43
+ renderMaxBytes: 32 * 1024 * 1024,
44
+ renderMaxInflight: 8,
45
+ renderResourceIntervalMs: 250,
46
+ renderMaxPostBytes: 64 * 1024,
47
+ renderMaxElements: 15e4,
48
+ renderGraceMs: 5e3,
49
+ renderProbeMs: 3e3,
50
+ renderAxeMs: 6e3,
51
+ renderScriptsMs: 4e3,
52
+ renderMaxWaitMs: 1e4,
53
+ renderDesktopViewport: { width: 1350, height: 940 },
54
+ renderViewport: { width: 412, height: 915 },
55
+ noindexFamilySample: 10
56
+ };
57
+ var SEO_SHARED_CDN_HOSTS = [
58
+ "cdn.shopify.com",
59
+ "amplience.net",
60
+ "res.cloudinary.com",
61
+ "imgix.net",
62
+ "imagedelivery.net",
63
+ "cdnjs.cloudflare.com",
64
+ "cdn.jsdelivr.net",
65
+ "unpkg.com",
66
+ "code.jquery.com",
67
+ "global.ssl.fastly.net",
68
+ "static.wixstatic.com",
69
+ "static.parastorage.com",
70
+ "images.squarespace-cdn.com",
71
+ "website-files.com",
72
+ "ctfassets.net",
73
+ "cdn.sanity.io",
74
+ "a.storyblok.com",
75
+ "wp.com",
76
+ "fonts.googleapis.com",
77
+ "ajax.googleapis.com",
78
+ "gstatic.com",
79
+ "googletagmanager.com",
80
+ "use.typekit.net",
81
+ "ytimg.com"
82
+ ];
83
+ var seoResourceCheckLimits = {
84
+ resourcesPerPage: 200,
85
+ outboundLinksPerPage: 100,
86
+ checksPerStep: 100,
87
+ sharedCdnDelayMs: 125,
88
+ maxBodyBytes: 16 * 1024 * 1024,
89
+ documentsPerPage: 50,
90
+ documentTitleBytes: 64 * 1024,
91
+ documentLargeBytes: 10 * 1024 * 1024
92
+ };
93
+ var SEO_JSON_LD_CHARS_PER_PAGE = 16e3;
94
+
95
+ // src/scope.ts
96
+ function bareHost(host) {
97
+ return host.toLowerCase().replace(/\.$/, "").replace(/^www\./, "");
98
+ }
99
+ function siteScope(url) {
100
+ try {
101
+ const parsed = new URL(url);
102
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
103
+ const host = parsed.hostname.toLowerCase();
104
+ if (host === "") return null;
105
+ return { origin: `https://${parsed.host.toLowerCase()}`, host };
106
+ } catch {
107
+ return null;
108
+ }
109
+ }
110
+ function isInternalHost(scope, host) {
111
+ const bare = bareHost(host);
112
+ const site = bareHost(scope.host);
113
+ if (bare === site) return true;
114
+ return scope.subdomains === true && site !== "" && bare.endsWith(`.${site}`);
115
+ }
116
+ function scopePort(scope) {
117
+ try {
118
+ return new URL(scope.origin).port;
119
+ } catch {
120
+ return "";
121
+ }
122
+ }
123
+ function isInternalUrl(scope, url) {
124
+ let parsed;
125
+ try {
126
+ parsed = new URL(url);
127
+ } catch {
128
+ return false;
129
+ }
130
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return false;
131
+ if (parsed.username !== "" || parsed.password !== "") return false;
132
+ if (parsed.port !== scopePort(scope)) return false;
133
+ return isInternalHost(scope, parsed.hostname);
134
+ }
135
+ function keyInScope(scope, key) {
136
+ return isInternalUrl(scope, `https://${key}`);
137
+ }
138
+ function hostOfKey(key) {
139
+ return (key.split("/")[0] ?? "").replace(/:\d+$/, "");
140
+ }
141
+ function bareHostOfKey(key) {
142
+ return bareHost(hostOfKey(key));
143
+ }
144
+ function onSiteOrBelow(url, siteHost) {
145
+ let host;
146
+ try {
147
+ host = new URL(url).hostname.toLowerCase().replace(/^www\./, "");
148
+ } catch {
149
+ return false;
150
+ }
151
+ return host === siteHost || host.endsWith(`.${siteHost}`);
152
+ }
153
+ var PLATFORM_PATH = /^\/cdn-cgi(?:\/|$)/i;
154
+ function isPlatformPath(keyOrUrl) {
155
+ if (/^https?:\/\//i.test(keyOrUrl)) {
156
+ try {
157
+ return PLATFORM_PATH.test(new URL(keyOrUrl).pathname);
158
+ } catch {
159
+ return false;
160
+ }
161
+ }
162
+ const slash = keyOrUrl.indexOf("/");
163
+ return slash !== -1 && PLATFORM_PATH.test(keyOrUrl.slice(slash).split(/[?#]/)[0] ?? "");
164
+ }
165
+
166
+ // src/host-gate.ts
167
+ function hostGateKey(host) {
168
+ return bareHost(host);
169
+ }
170
+ function isSharedCdnHost(host) {
171
+ const name = hostGateKey(host.replace(/:\d+$/, ""));
172
+ return SEO_SHARED_CDN_HOSTS.some((listed) => name === listed || name.endsWith(`.${listed}`));
173
+ }
174
+ function memoryHostGate() {
175
+ const next = /* @__PURE__ */ new Map();
176
+ return {
177
+ reserve: async (host, intervalMs, now) => {
178
+ const key = hostGateKey(host);
179
+ const at = Math.max(now, next.get(key) ?? 0);
180
+ next.set(key, at + intervalMs);
181
+ return at - now;
182
+ },
183
+ backOff: async (host, untilMs) => {
184
+ const key = hostGateKey(host);
185
+ next.set(key, Math.max(next.get(key) ?? 0, untilMs));
186
+ }
187
+ };
188
+ }
189
+ function retryAfterMs(retryAfter, now, defaults) {
190
+ const value = retryAfter?.trim() ?? "";
191
+ let asked = defaults.defaultMs;
192
+ if (/^\d+$/.test(value)) asked = Number(value) * 1e3;
193
+ else if (value !== "" && Number.isFinite(Date.parse(value))) asked = Date.parse(value) - now;
194
+ return Math.min(defaults.maxMs, Math.max(0, asked));
195
+ }
196
+
197
+ export { SEO_CRAWLER_ROBOTS_AGENT, SEO_CRAWLER_USER_AGENT_TOKEN, SEO_JSON_LD_CHARS_PER_PAGE, SEO_STATED_CHROME_MAJOR, bareHost, bareHostOfKey, hostGateKey, hostOfKey, isInternalHost, isInternalUrl, isPlatformPath, isSharedCdnHost, keyInScope, memoryHostGate, onSiteOrBelow, retryAfterMs, seoCrawlerLimits, seoCrawlerUserAgent, seoDesktopRenderUserAgent, seoRenderUserAgent, seoResourceCheckLimits, siteScope };