recker 1.0.26 → 1.0.27-next.8396df6

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 (181) hide show
  1. package/dist/browser/browser/cache.d.ts +40 -0
  2. package/dist/browser/browser/cache.js +199 -0
  3. package/dist/browser/browser/crypto.d.ts +24 -0
  4. package/dist/browser/browser/crypto.js +80 -0
  5. package/dist/browser/browser/index.d.ts +31 -0
  6. package/dist/browser/browser/index.js +31 -0
  7. package/dist/browser/browser/recker.d.ts +26 -0
  8. package/dist/browser/browser/recker.js +61 -0
  9. package/dist/browser/cache/basic-file-storage.d.ts +12 -0
  10. package/dist/browser/cache/basic-file-storage.js +50 -0
  11. package/dist/browser/cache/memory-limits.d.ts +20 -0
  12. package/dist/browser/cache/memory-limits.js +96 -0
  13. package/dist/browser/cache/memory-storage.d.ts +132 -0
  14. package/dist/browser/cache/memory-storage.js +454 -0
  15. package/dist/browser/cache.d.ts +40 -0
  16. package/dist/browser/cache.js +199 -0
  17. package/dist/browser/constants/http-status.d.ts +73 -0
  18. package/dist/browser/constants/http-status.js +156 -0
  19. package/dist/browser/cookies/memory-cookie-jar.d.ts +30 -0
  20. package/dist/browser/cookies/memory-cookie-jar.js +210 -0
  21. package/dist/browser/core/client.d.ts +118 -0
  22. package/dist/browser/core/client.js +667 -0
  23. package/dist/browser/core/errors.d.ts +142 -0
  24. package/dist/browser/core/errors.js +308 -0
  25. package/dist/browser/core/index.d.ts +5 -0
  26. package/dist/browser/core/index.js +5 -0
  27. package/dist/browser/core/request-promise.d.ts +23 -0
  28. package/dist/browser/core/request-promise.js +82 -0
  29. package/dist/browser/core/request.d.ts +20 -0
  30. package/dist/browser/core/request.js +76 -0
  31. package/dist/browser/core/response.d.ts +34 -0
  32. package/dist/browser/core/response.js +178 -0
  33. package/dist/browser/crypto.d.ts +24 -0
  34. package/dist/browser/crypto.js +80 -0
  35. package/dist/browser/index.d.ts +31 -0
  36. package/dist/browser/index.js +31 -0
  37. package/dist/browser/plugins/auth/api-key.d.ts +8 -0
  38. package/dist/browser/plugins/auth/api-key.js +27 -0
  39. package/dist/browser/plugins/auth/auth0.d.ts +33 -0
  40. package/dist/browser/plugins/auth/auth0.js +94 -0
  41. package/dist/browser/plugins/auth/aws-sigv4.d.ts +10 -0
  42. package/dist/browser/plugins/auth/aws-sigv4.js +88 -0
  43. package/dist/browser/plugins/auth/azure-ad.d.ts +48 -0
  44. package/dist/browser/plugins/auth/azure-ad.js +152 -0
  45. package/dist/browser/plugins/auth/basic.d.ts +7 -0
  46. package/dist/browser/plugins/auth/basic.js +13 -0
  47. package/dist/browser/plugins/auth/bearer.d.ts +8 -0
  48. package/dist/browser/plugins/auth/bearer.js +17 -0
  49. package/dist/browser/plugins/auth/cognito.d.ts +45 -0
  50. package/dist/browser/plugins/auth/cognito.js +208 -0
  51. package/dist/browser/plugins/auth/digest.d.ts +8 -0
  52. package/dist/browser/plugins/auth/digest.js +100 -0
  53. package/dist/browser/plugins/auth/firebase.d.ts +32 -0
  54. package/dist/browser/plugins/auth/firebase.js +195 -0
  55. package/dist/browser/plugins/auth/github-app.d.ts +36 -0
  56. package/dist/browser/plugins/auth/github-app.js +170 -0
  57. package/dist/browser/plugins/auth/google-service-account.d.ts +49 -0
  58. package/dist/browser/plugins/auth/google-service-account.js +172 -0
  59. package/dist/browser/plugins/auth/index.d.ts +15 -0
  60. package/dist/browser/plugins/auth/index.js +15 -0
  61. package/dist/browser/plugins/auth/mtls.d.ts +37 -0
  62. package/dist/browser/plugins/auth/mtls.js +140 -0
  63. package/dist/browser/plugins/auth/oauth2.d.ts +8 -0
  64. package/dist/browser/plugins/auth/oauth2.js +26 -0
  65. package/dist/browser/plugins/auth/oidc.d.ts +55 -0
  66. package/dist/browser/plugins/auth/oidc.js +222 -0
  67. package/dist/browser/plugins/auth/okta.d.ts +47 -0
  68. package/dist/browser/plugins/auth/okta.js +157 -0
  69. package/dist/browser/plugins/auth.d.ts +1 -0
  70. package/dist/browser/plugins/auth.js +1 -0
  71. package/dist/browser/plugins/cache.d.ts +15 -0
  72. package/dist/browser/plugins/cache.js +486 -0
  73. package/dist/browser/plugins/circuit-breaker.d.ts +13 -0
  74. package/dist/browser/plugins/circuit-breaker.js +100 -0
  75. package/dist/browser/plugins/compression.d.ts +4 -0
  76. package/dist/browser/plugins/compression.js +130 -0
  77. package/dist/browser/plugins/cookie-jar.d.ts +5 -0
  78. package/dist/browser/plugins/cookie-jar.js +72 -0
  79. package/dist/browser/plugins/dedup.d.ts +5 -0
  80. package/dist/browser/plugins/dedup.js +35 -0
  81. package/dist/browser/plugins/graphql.d.ts +13 -0
  82. package/dist/browser/plugins/graphql.js +58 -0
  83. package/dist/browser/plugins/grpc-web.d.ts +79 -0
  84. package/dist/browser/plugins/grpc-web.js +261 -0
  85. package/dist/browser/plugins/hls.d.ts +105 -0
  86. package/dist/browser/plugins/hls.js +395 -0
  87. package/dist/browser/plugins/jsonrpc.d.ts +75 -0
  88. package/dist/browser/plugins/jsonrpc.js +143 -0
  89. package/dist/browser/plugins/logger.d.ts +13 -0
  90. package/dist/browser/plugins/logger.js +108 -0
  91. package/dist/browser/plugins/odata.d.ts +181 -0
  92. package/dist/browser/plugins/odata.js +564 -0
  93. package/dist/browser/plugins/pagination.d.ts +16 -0
  94. package/dist/browser/plugins/pagination.js +105 -0
  95. package/dist/browser/plugins/rate-limit.d.ts +15 -0
  96. package/dist/browser/plugins/rate-limit.js +162 -0
  97. package/dist/browser/plugins/retry.d.ts +14 -0
  98. package/dist/browser/plugins/retry.js +116 -0
  99. package/dist/browser/plugins/scrape.d.ts +21 -0
  100. package/dist/browser/plugins/scrape.js +82 -0
  101. package/dist/browser/plugins/server-timing.d.ts +7 -0
  102. package/dist/browser/plugins/server-timing.js +24 -0
  103. package/dist/browser/plugins/soap.d.ts +72 -0
  104. package/dist/browser/plugins/soap.js +347 -0
  105. package/dist/browser/plugins/xml.d.ts +9 -0
  106. package/dist/browser/plugins/xml.js +194 -0
  107. package/dist/browser/plugins/xsrf.d.ts +9 -0
  108. package/dist/browser/plugins/xsrf.js +48 -0
  109. package/dist/browser/recker.d.ts +26 -0
  110. package/dist/browser/recker.js +61 -0
  111. package/dist/browser/runner/request-runner.d.ts +46 -0
  112. package/dist/browser/runner/request-runner.js +89 -0
  113. package/dist/browser/scrape/document.d.ts +44 -0
  114. package/dist/browser/scrape/document.js +210 -0
  115. package/dist/browser/scrape/element.d.ts +49 -0
  116. package/dist/browser/scrape/element.js +176 -0
  117. package/dist/browser/scrape/extractors.d.ts +16 -0
  118. package/dist/browser/scrape/extractors.js +356 -0
  119. package/dist/browser/scrape/types.d.ts +107 -0
  120. package/dist/browser/scrape/types.js +1 -0
  121. package/dist/browser/transport/fetch.d.ts +11 -0
  122. package/dist/browser/transport/fetch.js +143 -0
  123. package/dist/browser/transport/undici.d.ts +38 -0
  124. package/dist/browser/transport/undici.js +897 -0
  125. package/dist/browser/types/ai.d.ts +267 -0
  126. package/dist/browser/types/ai.js +1 -0
  127. package/dist/browser/types/index.d.ts +351 -0
  128. package/dist/browser/types/index.js +1 -0
  129. package/dist/browser/types/logger.d.ts +16 -0
  130. package/dist/browser/types/logger.js +66 -0
  131. package/dist/browser/types/udp.d.ts +138 -0
  132. package/dist/browser/types/udp.js +1 -0
  133. package/dist/browser/utils/agent-manager.d.ts +29 -0
  134. package/dist/browser/utils/agent-manager.js +160 -0
  135. package/dist/browser/utils/body.d.ts +10 -0
  136. package/dist/browser/utils/body.js +148 -0
  137. package/dist/browser/utils/charset.d.ts +15 -0
  138. package/dist/browser/utils/charset.js +169 -0
  139. package/dist/browser/utils/concurrency.d.ts +20 -0
  140. package/dist/browser/utils/concurrency.js +120 -0
  141. package/dist/browser/utils/dns.d.ts +6 -0
  142. package/dist/browser/utils/dns.js +26 -0
  143. package/dist/browser/utils/header-parser.d.ts +94 -0
  144. package/dist/browser/utils/header-parser.js +617 -0
  145. package/dist/browser/utils/html-cleaner.d.ts +1 -0
  146. package/dist/browser/utils/html-cleaner.js +21 -0
  147. package/dist/browser/utils/link-header.d.ts +69 -0
  148. package/dist/browser/utils/link-header.js +190 -0
  149. package/dist/browser/utils/optional-require.d.ts +19 -0
  150. package/dist/browser/utils/optional-require.js +105 -0
  151. package/dist/browser/utils/progress.d.ts +8 -0
  152. package/dist/browser/utils/progress.js +82 -0
  153. package/dist/browser/utils/request-pool.d.ts +22 -0
  154. package/dist/browser/utils/request-pool.js +101 -0
  155. package/dist/browser/utils/sse.d.ts +7 -0
  156. package/dist/browser/utils/sse.js +67 -0
  157. package/dist/browser/utils/streaming.d.ts +17 -0
  158. package/dist/browser/utils/streaming.js +84 -0
  159. package/dist/browser/utils/try-fn.d.ts +3 -0
  160. package/dist/browser/utils/try-fn.js +59 -0
  161. package/dist/browser/utils/user-agent.d.ts +44 -0
  162. package/dist/browser/utils/user-agent.js +100 -0
  163. package/dist/browser/utils/whois.d.ts +32 -0
  164. package/dist/browser/utils/whois.js +246 -0
  165. package/dist/browser/websocket/client.d.ts +65 -0
  166. package/dist/browser/websocket/client.js +313 -0
  167. package/dist/cli/index.d.ts +1 -0
  168. package/dist/cli/index.js +99 -3
  169. package/dist/index.d.ts +1 -0
  170. package/dist/index.js +1 -0
  171. package/dist/seo/analyzer.d.ts +20 -0
  172. package/dist/seo/analyzer.js +544 -0
  173. package/dist/seo/index.d.ts +2 -0
  174. package/dist/seo/index.js +1 -0
  175. package/dist/seo/types.d.ts +100 -0
  176. package/dist/seo/types.js +1 -0
  177. package/dist/transport/fetch.d.ts +7 -1
  178. package/dist/transport/fetch.js +58 -76
  179. package/dist/utils/columns.d.ts +14 -0
  180. package/dist/utils/columns.js +69 -0
  181. package/package.json +34 -2
@@ -0,0 +1,544 @@
1
+ import { extractMeta, extractOpenGraph, extractTwitterCard, extractJsonLd, extractLinks, extractImages, } from '../scrape/extractors.js';
2
+ import { requireOptional } from '../utils/optional-require.js';
3
+ let cheerioModule = null;
4
+ async function loadCheerio() {
5
+ if (cheerioModule)
6
+ return cheerioModule;
7
+ cheerioModule = await requireOptional('cheerio', 'recker/seo');
8
+ return cheerioModule;
9
+ }
10
+ export class SeoAnalyzer {
11
+ $;
12
+ options;
13
+ constructor($, options = {}) {
14
+ this.$ = $;
15
+ this.options = options;
16
+ }
17
+ static async fromHtml(html, options = {}) {
18
+ const { load } = await loadCheerio();
19
+ return new SeoAnalyzer(load(html), options);
20
+ }
21
+ analyze() {
22
+ const checks = [];
23
+ const url = this.options.baseUrl || '';
24
+ const meta = extractMeta(this.$);
25
+ const og = extractOpenGraph(this.$);
26
+ const twitter = extractTwitterCard(this.$);
27
+ const jsonLd = extractJsonLd(this.$);
28
+ const links = extractLinks(this.$, { baseUrl: this.options.baseUrl });
29
+ const images = extractImages(this.$, { baseUrl: this.options.baseUrl });
30
+ const titleAnalysis = this.analyzeTitle(meta.title);
31
+ const descriptionAnalysis = this.analyzeDescription(meta.description);
32
+ const headings = this.analyzeHeadings();
33
+ const content = this.analyzeContent();
34
+ const linkAnalysis = this.analyzeLinks(links);
35
+ const imageAnalysis = this.analyzeImages(images);
36
+ const social = this.analyzeSocialMeta(og, twitter);
37
+ const technical = this.analyzeTechnical(meta);
38
+ const jsonLdAnalysis = this.analyzeJsonLd(jsonLd);
39
+ checks.push(...titleAnalysis.checks);
40
+ checks.push(...descriptionAnalysis.checks);
41
+ checks.push(...headings.checks);
42
+ checks.push(...linkAnalysis.checks);
43
+ checks.push(...imageAnalysis.checks);
44
+ checks.push(...social.checks);
45
+ checks.push(...technical.checks);
46
+ checks.push(...jsonLdAnalysis.checks);
47
+ const { score, grade } = this.calculateScore(checks);
48
+ return {
49
+ url,
50
+ timestamp: new Date(),
51
+ grade,
52
+ score,
53
+ checks,
54
+ title: meta.title ? { text: meta.title, length: meta.title.length } : undefined,
55
+ metaDescription: meta.description ? { text: meta.description, length: meta.description.length } : undefined,
56
+ headings: headings.analysis,
57
+ content,
58
+ links: linkAnalysis.analysis,
59
+ images: imageAnalysis.analysis,
60
+ social: social.analysis,
61
+ technical: technical.analysis,
62
+ jsonLd: {
63
+ count: jsonLd.length,
64
+ types: jsonLd.map(j => j['@type']).filter(Boolean),
65
+ },
66
+ };
67
+ }
68
+ analyzeTitle(title) {
69
+ const checks = [];
70
+ if (!title) {
71
+ checks.push({
72
+ name: 'Title Tag',
73
+ status: 'fail',
74
+ message: 'Missing title tag',
75
+ recommendation: 'Add a unique, descriptive title tag (50-60 characters)',
76
+ });
77
+ return { checks };
78
+ }
79
+ const length = title.length;
80
+ if (length < 30) {
81
+ checks.push({
82
+ name: 'Title Tag',
83
+ status: 'warn',
84
+ message: `Title too short (${length} chars)`,
85
+ value: title,
86
+ recommendation: 'Expand title to 50-60 characters for better visibility',
87
+ });
88
+ }
89
+ else if (length > 60) {
90
+ checks.push({
91
+ name: 'Title Tag',
92
+ status: 'warn',
93
+ message: `Title may be truncated (${length} chars)`,
94
+ value: title,
95
+ recommendation: 'Shorten title to under 60 characters to prevent truncation',
96
+ });
97
+ }
98
+ else {
99
+ checks.push({
100
+ name: 'Title Tag',
101
+ status: 'pass',
102
+ message: `Title length OK (${length} chars)`,
103
+ value: title,
104
+ });
105
+ }
106
+ return { checks };
107
+ }
108
+ analyzeDescription(description) {
109
+ const checks = [];
110
+ if (!description) {
111
+ checks.push({
112
+ name: 'Meta Description',
113
+ status: 'fail',
114
+ message: 'Missing meta description',
115
+ recommendation: 'Add a compelling meta description (150-160 characters)',
116
+ });
117
+ return { checks };
118
+ }
119
+ const length = description.length;
120
+ if (length < 120) {
121
+ checks.push({
122
+ name: 'Meta Description',
123
+ status: 'warn',
124
+ message: `Description too short (${length} chars)`,
125
+ value: description.slice(0, 100) + (description.length > 100 ? '...' : ''),
126
+ recommendation: 'Expand to 150-160 characters for better CTR',
127
+ });
128
+ }
129
+ else if (length > 160) {
130
+ checks.push({
131
+ name: 'Meta Description',
132
+ status: 'warn',
133
+ message: `Description may be truncated (${length} chars)`,
134
+ value: description.slice(0, 100) + '...',
135
+ recommendation: 'Shorten to under 160 characters',
136
+ });
137
+ }
138
+ else {
139
+ checks.push({
140
+ name: 'Meta Description',
141
+ status: 'pass',
142
+ message: `Description length OK (${length} chars)`,
143
+ value: description.slice(0, 100) + (description.length > 100 ? '...' : ''),
144
+ });
145
+ }
146
+ return { checks };
147
+ }
148
+ analyzeHeadings() {
149
+ const checks = [];
150
+ const issues = [];
151
+ const structure = [];
152
+ const counts = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0 };
153
+ for (let level = 1; level <= 6; level++) {
154
+ const headings = this.$(`h${level}`);
155
+ counts[level] = headings.length;
156
+ headings.each((_, el) => {
157
+ const text = this.$(el).text().trim();
158
+ structure.push({ level, text: text.slice(0, 80), count: 1 });
159
+ });
160
+ }
161
+ if (counts[1] === 0) {
162
+ checks.push({
163
+ name: 'H1 Tag',
164
+ status: 'fail',
165
+ message: 'Missing H1 heading',
166
+ recommendation: 'Add a single H1 heading that describes the page content',
167
+ });
168
+ issues.push('No H1 tag found');
169
+ }
170
+ else if (counts[1] > 1) {
171
+ checks.push({
172
+ name: 'H1 Tag',
173
+ status: 'warn',
174
+ message: `Multiple H1 tags (${counts[1]} found)`,
175
+ recommendation: 'Use only one H1 per page',
176
+ });
177
+ issues.push('Multiple H1 tags');
178
+ }
179
+ else {
180
+ checks.push({
181
+ name: 'H1 Tag',
182
+ status: 'pass',
183
+ message: 'Single H1 tag present',
184
+ });
185
+ }
186
+ let hasProperHierarchy = true;
187
+ let prevLevel = 0;
188
+ for (const heading of structure) {
189
+ if (heading.level > prevLevel + 1 && prevLevel !== 0) {
190
+ hasProperHierarchy = false;
191
+ issues.push(`Skipped heading level: H${prevLevel} to H${heading.level}`);
192
+ }
193
+ prevLevel = heading.level;
194
+ }
195
+ if (!hasProperHierarchy) {
196
+ checks.push({
197
+ name: 'Heading Hierarchy',
198
+ status: 'warn',
199
+ message: 'Heading levels are skipped',
200
+ recommendation: 'Use sequential heading levels (H1 → H2 → H3)',
201
+ });
202
+ }
203
+ else if (structure.length > 0) {
204
+ checks.push({
205
+ name: 'Heading Hierarchy',
206
+ status: 'pass',
207
+ message: 'Heading structure is correct',
208
+ });
209
+ }
210
+ return {
211
+ checks,
212
+ analysis: {
213
+ structure,
214
+ h1Count: counts[1],
215
+ hasProperHierarchy,
216
+ issues,
217
+ },
218
+ };
219
+ }
220
+ analyzeContent() {
221
+ const bodyText = this.$('body')
222
+ .clone()
223
+ .find('script, style, noscript')
224
+ .remove()
225
+ .end()
226
+ .text()
227
+ .replace(/\s+/g, ' ')
228
+ .trim();
229
+ const words = bodyText.split(/\s+/).filter(w => w.length > 0);
230
+ const sentences = bodyText.split(/[.!?]+/).filter(s => s.trim().length > 0);
231
+ const paragraphs = this.$('p').length;
232
+ const wordCount = words.length;
233
+ const readingTimeMinutes = Math.ceil(wordCount / 200);
234
+ return {
235
+ wordCount,
236
+ characterCount: bodyText.length,
237
+ sentenceCount: sentences.length,
238
+ paragraphCount: paragraphs,
239
+ readingTimeMinutes,
240
+ avgWordsPerSentence: sentences.length > 0 ? Math.round(wordCount / sentences.length) : 0,
241
+ };
242
+ }
243
+ analyzeLinks(links) {
244
+ const checks = [];
245
+ const analysis = {
246
+ total: links.length,
247
+ internal: links.filter(l => l.type === 'internal').length,
248
+ external: links.filter(l => l.type === 'external').length,
249
+ nofollow: links.filter(l => l.rel?.includes('nofollow')).length,
250
+ broken: 0,
251
+ withoutText: links.filter(l => !l.text?.trim()).length,
252
+ };
253
+ if (analysis.withoutText > 0) {
254
+ checks.push({
255
+ name: 'Link Text',
256
+ status: 'warn',
257
+ message: `${analysis.withoutText} links without descriptive text`,
258
+ recommendation: 'Add descriptive anchor text to all links',
259
+ });
260
+ }
261
+ else if (links.length > 0) {
262
+ checks.push({
263
+ name: 'Link Text',
264
+ status: 'pass',
265
+ message: 'All links have descriptive text',
266
+ });
267
+ }
268
+ checks.push({
269
+ name: 'Links',
270
+ status: 'info',
271
+ message: `${analysis.total} total (${analysis.internal} internal, ${analysis.external} external)`,
272
+ });
273
+ return { checks, analysis };
274
+ }
275
+ analyzeImages(images) {
276
+ const checks = [];
277
+ const analysis = {
278
+ total: images.length,
279
+ withAlt: images.filter(i => i.alt && i.alt.trim().length > 0).length,
280
+ withoutAlt: images.filter(i => !i.alt || i.alt.trim().length === 0).length,
281
+ lazy: images.filter(i => i.loading === 'lazy').length,
282
+ missingDimensions: images.filter(i => !i.width || !i.height).length,
283
+ };
284
+ if (images.length === 0) {
285
+ checks.push({
286
+ name: 'Images',
287
+ status: 'info',
288
+ message: 'No images found on page',
289
+ });
290
+ return { checks, analysis };
291
+ }
292
+ const altPercentage = Math.round((analysis.withAlt / analysis.total) * 100);
293
+ if (analysis.withoutAlt > 0) {
294
+ checks.push({
295
+ name: 'Image Alt Text',
296
+ status: analysis.withoutAlt > analysis.total / 2 ? 'fail' : 'warn',
297
+ message: `${analysis.withoutAlt} of ${analysis.total} images missing alt text (${100 - altPercentage}%)`,
298
+ recommendation: 'Add descriptive alt text to all images for accessibility and SEO',
299
+ });
300
+ }
301
+ else {
302
+ checks.push({
303
+ name: 'Image Alt Text',
304
+ status: 'pass',
305
+ message: 'All images have alt text',
306
+ });
307
+ }
308
+ if (analysis.lazy === 0 && analysis.total > 3) {
309
+ checks.push({
310
+ name: 'Lazy Loading',
311
+ status: 'info',
312
+ message: 'No images use lazy loading',
313
+ recommendation: 'Consider adding loading="lazy" to below-the-fold images',
314
+ });
315
+ }
316
+ return { checks, analysis };
317
+ }
318
+ analyzeSocialMeta(og, twitter) {
319
+ const checks = [];
320
+ const ogIssues = [];
321
+ const twitterIssues = [];
322
+ const hasOg = !!(og.title || og.description || og.image);
323
+ if (!hasOg) {
324
+ ogIssues.push('No OpenGraph meta tags found');
325
+ }
326
+ else {
327
+ if (!og.title)
328
+ ogIssues.push('Missing og:title');
329
+ if (!og.description)
330
+ ogIssues.push('Missing og:description');
331
+ if (!og.image)
332
+ ogIssues.push('Missing og:image');
333
+ if (!og.url)
334
+ ogIssues.push('Missing og:url');
335
+ }
336
+ if (ogIssues.length === 0) {
337
+ checks.push({
338
+ name: 'OpenGraph',
339
+ status: 'pass',
340
+ message: 'OpenGraph meta tags are complete',
341
+ });
342
+ }
343
+ else if (hasOg) {
344
+ checks.push({
345
+ name: 'OpenGraph',
346
+ status: 'warn',
347
+ message: `OpenGraph incomplete: ${ogIssues.join(', ')}`,
348
+ recommendation: 'Add all essential OpenGraph tags for better social sharing',
349
+ });
350
+ }
351
+ else {
352
+ checks.push({
353
+ name: 'OpenGraph',
354
+ status: 'fail',
355
+ message: 'No OpenGraph meta tags',
356
+ recommendation: 'Add og:title, og:description, og:image, and og:url for social sharing',
357
+ });
358
+ }
359
+ const hasTwitter = !!(twitter.card || twitter.title || twitter.description);
360
+ if (!hasTwitter) {
361
+ twitterIssues.push('No Twitter Card meta tags found');
362
+ }
363
+ else {
364
+ if (!twitter.card)
365
+ twitterIssues.push('Missing twitter:card');
366
+ if (!twitter.title)
367
+ twitterIssues.push('Missing twitter:title');
368
+ if (!twitter.description)
369
+ twitterIssues.push('Missing twitter:description');
370
+ }
371
+ if (twitterIssues.length === 0) {
372
+ checks.push({
373
+ name: 'Twitter Card',
374
+ status: 'pass',
375
+ message: 'Twitter Card meta tags are complete',
376
+ });
377
+ }
378
+ else if (hasTwitter) {
379
+ checks.push({
380
+ name: 'Twitter Card',
381
+ status: 'warn',
382
+ message: `Twitter Card incomplete: ${twitterIssues.join(', ')}`,
383
+ });
384
+ }
385
+ else {
386
+ checks.push({
387
+ name: 'Twitter Card',
388
+ status: 'warn',
389
+ message: 'No Twitter Card meta tags',
390
+ recommendation: 'Add twitter:card, twitter:title, and twitter:description',
391
+ });
392
+ }
393
+ return {
394
+ checks,
395
+ analysis: {
396
+ openGraph: {
397
+ present: hasOg,
398
+ hasTitle: !!og.title,
399
+ hasDescription: !!og.description,
400
+ hasImage: !!og.image,
401
+ hasUrl: !!og.url,
402
+ issues: ogIssues,
403
+ },
404
+ twitterCard: {
405
+ present: hasTwitter,
406
+ hasCard: !!twitter.card,
407
+ hasTitle: !!twitter.title,
408
+ hasDescription: !!twitter.description,
409
+ hasImage: !!twitter.image,
410
+ issues: twitterIssues,
411
+ },
412
+ },
413
+ };
414
+ }
415
+ analyzeTechnical(meta) {
416
+ const checks = [];
417
+ const htmlLang = this.$('html').attr('lang');
418
+ const analysis = {
419
+ hasCanonical: !!meta.canonical,
420
+ canonicalUrl: meta.canonical,
421
+ hasRobotsMeta: !!meta.robots,
422
+ robotsContent: meta.robots,
423
+ hasViewport: !!meta.viewport,
424
+ hasCharset: !!meta.charset,
425
+ hasLang: !!htmlLang,
426
+ langValue: htmlLang,
427
+ };
428
+ if (analysis.hasCanonical) {
429
+ checks.push({
430
+ name: 'Canonical URL',
431
+ status: 'pass',
432
+ message: 'Canonical URL is defined',
433
+ value: meta.canonical,
434
+ });
435
+ }
436
+ else {
437
+ checks.push({
438
+ name: 'Canonical URL',
439
+ status: 'warn',
440
+ message: 'No canonical URL defined',
441
+ recommendation: 'Add <link rel="canonical" href="..."> to prevent duplicate content issues',
442
+ });
443
+ }
444
+ if (analysis.hasViewport) {
445
+ checks.push({
446
+ name: 'Viewport',
447
+ status: 'pass',
448
+ message: 'Viewport meta tag is set',
449
+ });
450
+ }
451
+ else {
452
+ checks.push({
453
+ name: 'Viewport',
454
+ status: 'fail',
455
+ message: 'Missing viewport meta tag',
456
+ recommendation: 'Add <meta name="viewport" content="width=device-width, initial-scale=1">',
457
+ });
458
+ }
459
+ if (analysis.hasLang) {
460
+ checks.push({
461
+ name: 'Language',
462
+ status: 'pass',
463
+ message: `Language attribute set (${htmlLang})`,
464
+ });
465
+ }
466
+ else {
467
+ checks.push({
468
+ name: 'Language',
469
+ status: 'warn',
470
+ message: 'Missing lang attribute on <html>',
471
+ recommendation: 'Add lang attribute: <html lang="en">',
472
+ });
473
+ }
474
+ if (meta.robots) {
475
+ const robotsLower = meta.robots.toLowerCase();
476
+ if (robotsLower.includes('noindex')) {
477
+ checks.push({
478
+ name: 'Robots',
479
+ status: 'warn',
480
+ message: 'Page is set to noindex',
481
+ value: meta.robots,
482
+ });
483
+ }
484
+ else {
485
+ checks.push({
486
+ name: 'Robots',
487
+ status: 'info',
488
+ message: `Robots meta: ${meta.robots}`,
489
+ });
490
+ }
491
+ }
492
+ return { checks, analysis };
493
+ }
494
+ analyzeJsonLd(jsonLd) {
495
+ const checks = [];
496
+ if (jsonLd.length === 0) {
497
+ checks.push({
498
+ name: 'Structured Data',
499
+ status: 'info',
500
+ message: 'No JSON-LD structured data found',
501
+ recommendation: 'Consider adding Schema.org structured data for rich snippets',
502
+ });
503
+ }
504
+ else {
505
+ const types = jsonLd.map(j => j['@type']).filter(Boolean);
506
+ checks.push({
507
+ name: 'Structured Data',
508
+ status: 'pass',
509
+ message: `${jsonLd.length} JSON-LD block(s) found`,
510
+ value: types.length > 0 ? `Types: ${types.join(', ')}` : undefined,
511
+ });
512
+ }
513
+ return { checks };
514
+ }
515
+ calculateScore(checks) {
516
+ const weights = {
517
+ pass: 100,
518
+ warn: 50,
519
+ fail: 0,
520
+ info: 100,
521
+ };
522
+ const scoringChecks = checks.filter(c => c.status !== 'info');
523
+ if (scoringChecks.length === 0)
524
+ return { score: 100, grade: 'A' };
525
+ const totalWeight = scoringChecks.reduce((sum, check) => sum + weights[check.status], 0);
526
+ const score = Math.round(totalWeight / scoringChecks.length);
527
+ let grade;
528
+ if (score >= 90)
529
+ grade = 'A';
530
+ else if (score >= 80)
531
+ grade = 'B';
532
+ else if (score >= 70)
533
+ grade = 'C';
534
+ else if (score >= 60)
535
+ grade = 'D';
536
+ else
537
+ grade = 'F';
538
+ return { score, grade };
539
+ }
540
+ }
541
+ export async function analyzeSeo(html, options = {}) {
542
+ const analyzer = await SeoAnalyzer.fromHtml(html, options);
543
+ return analyzer.analyze();
544
+ }
@@ -0,0 +1,2 @@
1
+ export { SeoAnalyzer, analyzeSeo } from './analyzer.js';
2
+ export type { SeoReport, SeoCheckResult, SeoStatus, HeadingInfo, HeadingAnalysis, ContentMetrics, LinkAnalysis, ImageAnalysis, SocialMetaAnalysis, TechnicalSeo, SeoAnalyzerOptions, } from './types.js';
@@ -0,0 +1 @@
1
+ export { SeoAnalyzer, analyzeSeo } from './analyzer.js';
@@ -0,0 +1,100 @@
1
+ export type SeoStatus = 'pass' | 'warn' | 'fail' | 'info';
2
+ export interface SeoCheckResult {
3
+ name: string;
4
+ status: SeoStatus;
5
+ message: string;
6
+ value?: string | number;
7
+ recommendation?: string;
8
+ }
9
+ export interface HeadingInfo {
10
+ level: number;
11
+ text: string;
12
+ count: number;
13
+ }
14
+ export interface HeadingAnalysis {
15
+ structure: HeadingInfo[];
16
+ h1Count: number;
17
+ hasProperHierarchy: boolean;
18
+ issues: string[];
19
+ }
20
+ export interface ContentMetrics {
21
+ wordCount: number;
22
+ characterCount: number;
23
+ sentenceCount: number;
24
+ paragraphCount: number;
25
+ readingTimeMinutes: number;
26
+ avgWordsPerSentence: number;
27
+ }
28
+ export interface LinkAnalysis {
29
+ total: number;
30
+ internal: number;
31
+ external: number;
32
+ nofollow: number;
33
+ broken: number;
34
+ withoutText: number;
35
+ }
36
+ export interface ImageAnalysis {
37
+ total: number;
38
+ withAlt: number;
39
+ withoutAlt: number;
40
+ lazy: number;
41
+ missingDimensions: number;
42
+ }
43
+ export interface SocialMetaAnalysis {
44
+ openGraph: {
45
+ present: boolean;
46
+ hasTitle: boolean;
47
+ hasDescription: boolean;
48
+ hasImage: boolean;
49
+ hasUrl: boolean;
50
+ issues: string[];
51
+ };
52
+ twitterCard: {
53
+ present: boolean;
54
+ hasCard: boolean;
55
+ hasTitle: boolean;
56
+ hasDescription: boolean;
57
+ hasImage: boolean;
58
+ issues: string[];
59
+ };
60
+ }
61
+ export interface TechnicalSeo {
62
+ hasCanonical: boolean;
63
+ canonicalUrl?: string;
64
+ hasRobotsMeta: boolean;
65
+ robotsContent?: string;
66
+ hasViewport: boolean;
67
+ hasCharset: boolean;
68
+ hasLang: boolean;
69
+ langValue?: string;
70
+ }
71
+ export interface SeoReport {
72
+ url: string;
73
+ timestamp: Date;
74
+ grade: string;
75
+ score: number;
76
+ checks: SeoCheckResult[];
77
+ title?: {
78
+ text: string;
79
+ length: number;
80
+ };
81
+ metaDescription?: {
82
+ text: string;
83
+ length: number;
84
+ };
85
+ headings: HeadingAnalysis;
86
+ content: ContentMetrics;
87
+ links: LinkAnalysis;
88
+ images: ImageAnalysis;
89
+ social: SocialMetaAnalysis;
90
+ technical: TechnicalSeo;
91
+ jsonLd: {
92
+ count: number;
93
+ types: string[];
94
+ };
95
+ }
96
+ export interface SeoAnalyzerOptions {
97
+ baseUrl?: string;
98
+ analyzeContent?: boolean;
99
+ checkBrokenLinks?: boolean;
100
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,11 @@
1
1
  import { ReckerRequest, ReckerResponse, Transport } from '../types/index.js';
2
+ export interface FetchTransportOptions {
3
+ credentials?: RequestCredentials;
4
+ cache?: RequestCache;
5
+ keepalive?: boolean;
6
+ }
2
7
  export declare class FetchTransport implements Transport {
3
- constructor();
8
+ private options;
9
+ constructor(options?: FetchTransportOptions);
4
10
  dispatch(req: ReckerRequest): Promise<ReckerResponse>;
5
11
  }