@actuate-media/cms-core 0.28.1 → 0.30.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 (141) hide show
  1. package/dist/__tests__/actions/seo-fields-update.test.d.ts +2 -0
  2. package/dist/__tests__/actions/seo-fields-update.test.d.ts.map +1 -0
  3. package/dist/__tests__/actions/seo-fields-update.test.js +93 -0
  4. package/dist/__tests__/actions/seo-fields-update.test.js.map +1 -0
  5. package/dist/__tests__/api/llms-txt.test.d.ts +2 -0
  6. package/dist/__tests__/api/llms-txt.test.d.ts.map +1 -0
  7. package/dist/__tests__/api/llms-txt.test.js +77 -0
  8. package/dist/__tests__/api/llms-txt.test.js.map +1 -0
  9. package/dist/__tests__/api/seo-read-access.test.d.ts +2 -0
  10. package/dist/__tests__/api/seo-read-access.test.d.ts.map +1 -0
  11. package/dist/__tests__/api/seo-read-access.test.js +101 -0
  12. package/dist/__tests__/api/seo-read-access.test.js.map +1 -0
  13. package/dist/__tests__/api/seo-summary-ssrf.test.d.ts +2 -0
  14. package/dist/__tests__/api/seo-summary-ssrf.test.d.ts.map +1 -0
  15. package/dist/__tests__/api/seo-summary-ssrf.test.js +80 -0
  16. package/dist/__tests__/api/seo-summary-ssrf.test.js.map +1 -0
  17. package/dist/__tests__/api/sitemap-pagination.test.d.ts +2 -0
  18. package/dist/__tests__/api/sitemap-pagination.test.d.ts.map +1 -0
  19. package/dist/__tests__/api/sitemap-pagination.test.js +100 -0
  20. package/dist/__tests__/api/sitemap-pagination.test.js.map +1 -0
  21. package/dist/__tests__/media/seo.test.d.ts +2 -0
  22. package/dist/__tests__/media/seo.test.d.ts.map +1 -0
  23. package/dist/__tests__/media/seo.test.js +186 -0
  24. package/dist/__tests__/media/seo.test.js.map +1 -0
  25. package/dist/__tests__/middleware.test.d.ts +2 -0
  26. package/dist/__tests__/middleware.test.d.ts.map +1 -0
  27. package/dist/__tests__/middleware.test.js +50 -0
  28. package/dist/__tests__/middleware.test.js.map +1 -0
  29. package/dist/__tests__/redirects/graph.test.d.ts +2 -0
  30. package/dist/__tests__/redirects/graph.test.d.ts.map +1 -0
  31. package/dist/__tests__/redirects/graph.test.js +104 -0
  32. package/dist/__tests__/redirects/graph.test.js.map +1 -0
  33. package/dist/__tests__/redirects/suggest.test.d.ts +2 -0
  34. package/dist/__tests__/redirects/suggest.test.d.ts.map +1 -0
  35. package/dist/__tests__/redirects/suggest.test.js +28 -0
  36. package/dist/__tests__/redirects/suggest.test.js.map +1 -0
  37. package/dist/__tests__/seo/analysis-markdown.test.d.ts +2 -0
  38. package/dist/__tests__/seo/analysis-markdown.test.d.ts.map +1 -0
  39. package/dist/__tests__/seo/analysis-markdown.test.js +57 -0
  40. package/dist/__tests__/seo/analysis-markdown.test.js.map +1 -0
  41. package/dist/__tests__/seo/audit-engine.test.d.ts +2 -0
  42. package/dist/__tests__/seo/audit-engine.test.d.ts.map +1 -0
  43. package/dist/__tests__/seo/audit-engine.test.js +152 -0
  44. package/dist/__tests__/seo/audit-engine.test.js.map +1 -0
  45. package/dist/__tests__/seo/audit-runner.test.d.ts +2 -0
  46. package/dist/__tests__/seo/audit-runner.test.d.ts.map +1 -0
  47. package/dist/__tests__/seo/audit-runner.test.js +82 -0
  48. package/dist/__tests__/seo/audit-runner.test.js.map +1 -0
  49. package/dist/__tests__/seo/config-store.test.js +24 -160
  50. package/dist/__tests__/seo/config-store.test.js.map +1 -1
  51. package/dist/__tests__/seo/internal-collections.test.d.ts +2 -0
  52. package/dist/__tests__/seo/internal-collections.test.d.ts.map +1 -0
  53. package/dist/__tests__/seo/internal-collections.test.js +27 -0
  54. package/dist/__tests__/seo/internal-collections.test.js.map +1 -0
  55. package/dist/__tests__/seo/page-meta.test.js +34 -2
  56. package/dist/__tests__/seo/page-meta.test.js.map +1 -1
  57. package/dist/__tests__/seo/robots.test.js +23 -2
  58. package/dist/__tests__/seo/robots.test.js.map +1 -1
  59. package/dist/__tests__/seo/score.test.d.ts +2 -0
  60. package/dist/__tests__/seo/score.test.d.ts.map +1 -0
  61. package/dist/__tests__/seo/score.test.js +152 -0
  62. package/dist/__tests__/seo/score.test.js.map +1 -0
  63. package/dist/__tests__/setup/index.test.d.ts +2 -0
  64. package/dist/__tests__/setup/index.test.d.ts.map +1 -0
  65. package/dist/__tests__/setup/index.test.js +82 -0
  66. package/dist/__tests__/setup/index.test.js.map +1 -0
  67. package/dist/actions.d.ts +24 -0
  68. package/dist/actions.d.ts.map +1 -1
  69. package/dist/actions.js +63 -0
  70. package/dist/actions.js.map +1 -1
  71. package/dist/api/handlers.d.ts.map +1 -1
  72. package/dist/api/handlers.js +2339 -274
  73. package/dist/api/handlers.js.map +1 -1
  74. package/dist/config/types.d.ts +7 -0
  75. package/dist/config/types.d.ts.map +1 -1
  76. package/dist/index.d.ts +4 -0
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +4 -0
  79. package/dist/index.js.map +1 -1
  80. package/dist/media/index.d.ts +2 -0
  81. package/dist/media/index.d.ts.map +1 -1
  82. package/dist/media/index.js +1 -0
  83. package/dist/media/index.js.map +1 -1
  84. package/dist/media/seo.d.ts +88 -0
  85. package/dist/media/seo.d.ts.map +1 -0
  86. package/dist/media/seo.js +215 -0
  87. package/dist/media/seo.js.map +1 -0
  88. package/dist/middleware.js +12 -2
  89. package/dist/middleware.js.map +1 -1
  90. package/dist/redirects/graph.d.ts +68 -0
  91. package/dist/redirects/graph.d.ts.map +1 -0
  92. package/dist/redirects/graph.js +136 -0
  93. package/dist/redirects/graph.js.map +1 -0
  94. package/dist/redirects/index.d.ts +5 -0
  95. package/dist/redirects/index.d.ts.map +1 -0
  96. package/dist/redirects/index.js +3 -0
  97. package/dist/redirects/index.js.map +1 -0
  98. package/dist/redirects/suggest.d.ts +21 -0
  99. package/dist/redirects/suggest.d.ts.map +1 -0
  100. package/dist/redirects/suggest.js +55 -0
  101. package/dist/redirects/suggest.js.map +1 -0
  102. package/dist/seo/analysis.d.ts +9 -0
  103. package/dist/seo/analysis.d.ts.map +1 -1
  104. package/dist/seo/analysis.js +45 -6
  105. package/dist/seo/analysis.js.map +1 -1
  106. package/dist/seo/audit-engine.d.ts +119 -0
  107. package/dist/seo/audit-engine.d.ts.map +1 -0
  108. package/dist/seo/audit-engine.js +402 -0
  109. package/dist/seo/audit-engine.js.map +1 -0
  110. package/dist/seo/audit-runner.d.ts +47 -0
  111. package/dist/seo/audit-runner.d.ts.map +1 -0
  112. package/dist/seo/audit-runner.js +252 -0
  113. package/dist/seo/audit-runner.js.map +1 -0
  114. package/dist/seo/config-store.d.ts +28 -0
  115. package/dist/seo/config-store.d.ts.map +1 -1
  116. package/dist/seo/config-store.js +22 -0
  117. package/dist/seo/config-store.js.map +1 -1
  118. package/dist/seo/index.d.ts +8 -3
  119. package/dist/seo/index.d.ts.map +1 -1
  120. package/dist/seo/index.js +5 -2
  121. package/dist/seo/index.js.map +1 -1
  122. package/dist/seo/page-meta.d.ts +6 -0
  123. package/dist/seo/page-meta.d.ts.map +1 -1
  124. package/dist/seo/page-meta.js +22 -5
  125. package/dist/seo/page-meta.js.map +1 -1
  126. package/dist/seo/robots.d.ts +7 -0
  127. package/dist/seo/robots.d.ts.map +1 -1
  128. package/dist/seo/robots.js +10 -1
  129. package/dist/seo/robots.js.map +1 -1
  130. package/dist/seo/score.d.ts +132 -0
  131. package/dist/seo/score.d.ts.map +1 -0
  132. package/dist/seo/score.js +216 -0
  133. package/dist/seo/score.js.map +1 -0
  134. package/dist/setup/index.d.ts +16 -1
  135. package/dist/setup/index.d.ts.map +1 -1
  136. package/dist/setup/index.js +52 -2
  137. package/dist/setup/index.js.map +1 -1
  138. package/package.json +1 -1
  139. package/prisma/migrations/0008_media_center/migration.sql +47 -0
  140. package/prisma/migrations/0009_seo/migration.sql +143 -0
  141. package/prisma/schema.prisma +183 -8
@@ -0,0 +1,402 @@
1
+ /**
2
+ * SEO audit engine.
3
+ *
4
+ * Pure analysis: given a set of content entities (pages/posts) plus optional
5
+ * cross-cutting signals (broken links, redirect chains/loops, sitemap/robots
6
+ * health), it produces:
7
+ * - per-page SEO scores (via `calculatePageSeoScore`),
8
+ * - a flat list of detected issues with stable `issueKey`s,
9
+ * - an explainable site score (via `calculateSiteSeoScore`),
10
+ * - issue counts by severity, and
11
+ * - priority-ranked quick wins.
12
+ *
13
+ * The handler layer fetches the data, calls `runSeoAuditAnalysis`, and
14
+ * snapshots the result into `SeoAuditRun` / `SeoIssue` rows. Keeping detection
15
+ * pure means it is fully unit-testable with no DB.
16
+ */
17
+ import { calculatePageSeoScore, calculateSiteSeoScore, gradeForSeoScore, countH1, } from './score.js';
18
+ import { stripHtmlTags } from './analysis.js';
19
+ function present(v) {
20
+ return typeof v === 'string' && v.trim().length > 0;
21
+ }
22
+ function normalizeTitle(t) {
23
+ return t.trim().toLowerCase().replace(/\s+/g, ' ');
24
+ }
25
+ function wordsOf(content, fallback) {
26
+ if (typeof fallback === 'number')
27
+ return fallback;
28
+ if (!content)
29
+ return 0;
30
+ const text = stripHtmlTags(content);
31
+ return text ? text.split(/\s+/).filter((w) => w.length > 0).length : 0;
32
+ }
33
+ function countMissingAlt(content) {
34
+ if (!content)
35
+ return 0;
36
+ const imgs = content.match(/<img\b[^>]*>/gi) ?? [];
37
+ return imgs.filter((img) => !/\balt\s*=\s*["'][^"']+["']/i.test(img)).length;
38
+ }
39
+ /** Stable per-issue key so status carries across audit runs. */
40
+ export function issueKeyFor(type, identifier) {
41
+ return `${type}:${identifier}`;
42
+ }
43
+ /**
44
+ * Run the full SEO audit analysis over a set of entities + signals.
45
+ */
46
+ export function runSeoAuditAnalysis(entities, signals = {}) {
47
+ const issues = [];
48
+ const pages = [];
49
+ // -- Per-entity content checks -------------------------------------------
50
+ for (const e of entities) {
51
+ const wordCount = wordsOf(e.content, e.wordCount);
52
+ const scoreResult = calculatePageSeoScore({
53
+ title: e.title,
54
+ url: e.url,
55
+ content: e.content,
56
+ metaTitle: e.metaTitle,
57
+ metaDescription: e.metaDescription,
58
+ focusKeyword: e.focusKeyword,
59
+ canonicalUrl: e.canonicalUrl,
60
+ ogTitle: e.ogTitle,
61
+ ogDescription: e.ogDescription,
62
+ ogImage: e.ogImage,
63
+ structuredDataType: e.structuredDataType,
64
+ noindex: e.noindex,
65
+ shouldBeIndexable: e.shouldBeIndexable,
66
+ wordCount,
67
+ });
68
+ pages.push({
69
+ entityType: e.entityType,
70
+ entityId: e.entityId,
71
+ title: e.title,
72
+ url: e.url,
73
+ score: scoreResult.score,
74
+ grade: scoreResult.grade,
75
+ metaTitleSet: present(e.metaTitle),
76
+ metaDescriptionSet: present(e.metaDescription),
77
+ });
78
+ const ctx = {
79
+ entityType: e.entityType,
80
+ entityId: e.entityId,
81
+ entityTitle: e.title,
82
+ url: e.url,
83
+ };
84
+ if (!present(e.metaTitle)) {
85
+ issues.push({
86
+ ...ctx,
87
+ issueKey: issueKeyFor('missing-meta-title', e.entityId),
88
+ severity: 'warning',
89
+ category: 'content',
90
+ type: 'missing-meta-title',
91
+ title: 'Missing meta title',
92
+ description: 'This page has no meta title. Search engines fall back to the H1 or filename.',
93
+ impact: 'High',
94
+ recommendation: 'Add a 30–60 character meta title containing the target keyword.',
95
+ fixable: true,
96
+ fixActionType: 'edit-metadata',
97
+ relatedEntityIds: [],
98
+ });
99
+ }
100
+ if (!present(e.metaDescription)) {
101
+ issues.push({
102
+ ...ctx,
103
+ issueKey: issueKeyFor('missing-meta-description', e.entityId),
104
+ severity: 'warning',
105
+ category: 'content',
106
+ type: 'missing-meta-description',
107
+ title: 'Missing meta description',
108
+ description: 'Add a 150–160 character description summarising the page for search engines.',
109
+ impact: 'High',
110
+ recommendation: 'Write a unique meta description that includes the focus keyword.',
111
+ fixable: true,
112
+ fixActionType: 'edit-metadata',
113
+ relatedEntityIds: [],
114
+ });
115
+ }
116
+ if (!present(e.structuredDataType)) {
117
+ issues.push({
118
+ ...ctx,
119
+ issueKey: issueKeyFor('missing-structured-data', e.entityId),
120
+ severity: 'warning',
121
+ category: 'schema',
122
+ type: 'missing-structured-data',
123
+ title: 'No structured data found',
124
+ description: 'Add Schema.org markup to improve rich-result eligibility.',
125
+ impact: 'Medium',
126
+ recommendation: 'Choose an appropriate schema type (Article, Product, FAQPage, …).',
127
+ fixable: true,
128
+ fixActionType: 'edit-schema',
129
+ relatedEntityIds: [],
130
+ });
131
+ }
132
+ if (!present(e.canonicalUrl)) {
133
+ issues.push({
134
+ ...ctx,
135
+ issueKey: issueKeyFor('missing-canonical', e.entityId),
136
+ severity: 'info',
137
+ category: 'technical',
138
+ type: 'missing-canonical',
139
+ title: 'Missing canonical tag',
140
+ description: 'Add a canonical tag to prevent duplicate-content issues across URLs.',
141
+ impact: 'Low',
142
+ recommendation: 'Set the canonical URL to this page’s preferred public URL.',
143
+ fixable: true,
144
+ fixActionType: 'edit-canonical',
145
+ relatedEntityIds: [],
146
+ });
147
+ }
148
+ if (!present(e.ogImage)) {
149
+ issues.push({
150
+ ...ctx,
151
+ issueKey: issueKeyFor('missing-og-image', e.entityId),
152
+ severity: 'info',
153
+ category: 'social',
154
+ type: 'missing-og-image',
155
+ title: 'No OG image set',
156
+ description: 'Pages without an OG image show a generic placeholder when shared.',
157
+ impact: 'Low',
158
+ recommendation: 'Pick or generate an Open Graph image in the social metadata section.',
159
+ fixable: true,
160
+ fixActionType: 'edit-og-image',
161
+ relatedEntityIds: [],
162
+ });
163
+ }
164
+ if ((e.content ?? '').length > 0 && countH1(e.content ?? '') === 0) {
165
+ issues.push({
166
+ ...ctx,
167
+ issueKey: issueKeyFor('missing-h1', e.entityId),
168
+ severity: 'info',
169
+ category: 'content',
170
+ type: 'missing-h1',
171
+ title: 'Missing H1 heading',
172
+ description: 'Every page should have exactly one H1 that includes the target keyword.',
173
+ impact: 'Low',
174
+ recommendation: 'Add a single descriptive H1 heading to the content.',
175
+ fixable: false,
176
+ fixActionType: 'edit-content',
177
+ relatedEntityIds: [],
178
+ });
179
+ }
180
+ if (wordCount > 0 && wordCount < 300) {
181
+ issues.push({
182
+ ...ctx,
183
+ issueKey: issueKeyFor('thin-content', e.entityId),
184
+ severity: 'info',
185
+ category: 'content',
186
+ type: 'thin-content',
187
+ title: 'Content below 300 words',
188
+ description: 'Thin pages tend to rank poorly. Consider expanding the copy.',
189
+ impact: 'Low',
190
+ recommendation: 'Add more useful content or an FAQ section.',
191
+ fixable: false,
192
+ fixActionType: 'edit-content',
193
+ relatedEntityIds: [],
194
+ });
195
+ }
196
+ const missingAlt = countMissingAlt(e.content);
197
+ if (missingAlt > 0) {
198
+ issues.push({
199
+ ...ctx,
200
+ issueKey: issueKeyFor('missing-alt-text', e.entityId),
201
+ severity: 'warning',
202
+ category: 'media',
203
+ type: 'missing-alt-text',
204
+ title: 'Images missing alt text',
205
+ description: `${missingAlt} image(s) are missing alt text, hurting accessibility and image search.`,
206
+ impact: 'Medium',
207
+ recommendation: 'Add descriptive alt text to every content image.',
208
+ fixable: true,
209
+ fixActionType: 'edit-content',
210
+ relatedEntityIds: [],
211
+ });
212
+ }
213
+ }
214
+ // -- Duplicate meta titles (cross-entity) --------------------------------
215
+ const titleGroups = new Map();
216
+ for (const e of entities) {
217
+ if (!present(e.metaTitle))
218
+ continue;
219
+ const key = normalizeTitle(e.metaTitle);
220
+ const arr = titleGroups.get(key) ?? [];
221
+ arr.push(e);
222
+ titleGroups.set(key, arr);
223
+ }
224
+ for (const group of titleGroups.values()) {
225
+ if (group.length < 2)
226
+ continue;
227
+ const ids = group.map((g) => g.entityId).sort();
228
+ const urls = group.map((g) => g.url).join(', ');
229
+ issues.push({
230
+ entityType: group[0].entityType,
231
+ entityId: group[0].entityId,
232
+ entityTitle: group[0].title,
233
+ url: urls,
234
+ issueKey: issueKeyFor('duplicate-meta-title', ids.join(',')),
235
+ severity: 'critical',
236
+ category: 'content',
237
+ type: 'duplicate-meta-title',
238
+ title: 'Duplicate meta title detected',
239
+ description: `Each page must have a unique meta title. ${urls} share the same title.`,
240
+ impact: 'High',
241
+ recommendation: 'Differentiate the meta titles so each page targets distinct intent.',
242
+ fixable: true,
243
+ fixActionType: 'edit-metadata',
244
+ relatedEntityIds: ids,
245
+ });
246
+ }
247
+ // -- Broken internal links -----------------------------------------------
248
+ for (const link of signals.brokenLinks ?? []) {
249
+ issues.push({
250
+ entityType: 'page',
251
+ entityId: link.fromEntityId,
252
+ entityTitle: link.fromTitle,
253
+ url: link.fromUrl,
254
+ issueKey: issueKeyFor('broken-internal-link', `${link.fromEntityId}:${link.url}`),
255
+ severity: 'critical',
256
+ category: 'technical',
257
+ type: 'broken-internal-link',
258
+ title: 'Internal link returns 404',
259
+ description: `The link to ${link.url} is broken. Remove or update it to a valid URL.`,
260
+ impact: 'High',
261
+ recommendation: 'Fix the link target or create a redirect for the old URL.',
262
+ fixable: true,
263
+ fixActionType: 'fix-link',
264
+ relatedEntityIds: [],
265
+ });
266
+ }
267
+ // -- Slow pages (external perf source) -----------------------------------
268
+ for (const slow of signals.slowPages ?? []) {
269
+ issues.push({
270
+ entityType: 'page',
271
+ entityId: slow.entityId,
272
+ entityTitle: slow.title,
273
+ url: slow.url,
274
+ issueKey: issueKeyFor('slow-page', slow.entityId),
275
+ severity: 'warning',
276
+ category: 'performance',
277
+ type: 'slow-page',
278
+ title: 'Page load time exceeds 3 seconds',
279
+ description: slow.note ?? 'Optimise images, enable lazy loading, and consider a CDN.',
280
+ impact: 'Medium',
281
+ recommendation: 'Reduce image sizes and JavaScript; see the technical recommendations.',
282
+ fixable: false,
283
+ fixActionType: 'technical-recommendation',
284
+ relatedEntityIds: [],
285
+ });
286
+ }
287
+ // -- Site score (explainable category sub-scores) ------------------------
288
+ const total = entities.length;
289
+ const ratio = (count) => (total === 0 ? 100 : Math.round((count / total) * 100));
290
+ const withMetaTitle = entities.filter((e) => present(e.metaTitle)).length;
291
+ const withMetaDesc = entities.filter((e) => present(e.metaDescription)).length;
292
+ const withH1 = entities.filter((e) => countH1(e.content ?? '') === 1).length;
293
+ const contentScore = Math.round((ratio(withMetaTitle) + ratio(withMetaDesc) + ratio(withH1)) / 3);
294
+ const withCanonical = entities.filter((e) => present(e.canonicalUrl)).length;
295
+ const correctlyIndexed = entities.filter((e) => !(e.shouldBeIndexable !== false && e.noindex === true)).length;
296
+ const crawlBonus = (signals.sitemapEnabled !== false ? 50 : 0) + (signals.robotsEnabled !== false ? 50 : 0);
297
+ const technicalScore = Math.round((ratio(withCanonical) + ratio(correctlyIndexed) + crawlBonus) / 3);
298
+ const brokenCount = (signals.brokenLinks ?? []).length;
299
+ const chainPenalty = ((signals.redirectChains ?? 0) + (signals.redirectLoops ?? 0)) * 8;
300
+ const linkRedirectScore = Math.max(0, 100 - brokenCount * 15 - chainPenalty);
301
+ const withSchema = entities.filter((e) => present(e.structuredDataType)).length;
302
+ const withOgImage = entities.filter((e) => present(e.ogImage)).length;
303
+ const structuredSocialScore = Math.round((ratio(withSchema) + ratio(withOgImage)) / 2);
304
+ const performanceScore = (signals.slowPages ?? []).length > 0
305
+ ? Math.max(0, 70 - (signals.slowPages ?? []).length * 10)
306
+ : 70;
307
+ const siteScore = calculateSiteSeoScore({
308
+ content: contentScore,
309
+ technical: technicalScore,
310
+ linkRedirect: linkRedirectScore,
311
+ structuredSocial: structuredSocialScore,
312
+ performance: performanceScore,
313
+ });
314
+ // -- Counts + quick wins -------------------------------------------------
315
+ const counts = {
316
+ total: issues.length,
317
+ critical: issues.filter((i) => i.severity === 'critical').length,
318
+ warning: issues.filter((i) => i.severity === 'warning').length,
319
+ info: issues.filter((i) => i.severity === 'info').length,
320
+ };
321
+ const quickWins = buildQuickWins(issues);
322
+ return { pages, issues, siteScore, counts, quickWins };
323
+ }
324
+ const IMPACT_RANK = { High: 3, Medium: 2, Low: 1 };
325
+ const SEVERITY_RANK = { critical: 3, warning: 2, info: 1 };
326
+ /**
327
+ * Derive priority-ranked quick wins by grouping issues by type and ranking by
328
+ * (impact, severity, count). Never hardcoded — purely a function of detected
329
+ * issues, so the Overview "Quick Wins" card always reflects reality.
330
+ */
331
+ export function buildQuickWins(issues) {
332
+ const byType = new Map();
333
+ for (const i of issues) {
334
+ if (!i.fixable)
335
+ continue;
336
+ const arr = byType.get(i.type) ?? [];
337
+ arr.push(i);
338
+ byType.set(i.type, arr);
339
+ }
340
+ const wins = [];
341
+ for (const [type, group] of byType) {
342
+ const first = group[0];
343
+ const count = group.length;
344
+ wins.push({
345
+ id: `qw-${type}`,
346
+ title: quickWinTitle(type, count),
347
+ explanation: quickWinExplanation(type, count),
348
+ impact: first.impact === 'High' ? 'High' : first.impact === 'Medium' ? 'Medium' : 'Low',
349
+ issueType: type,
350
+ fixActionType: first.fixActionType,
351
+ count,
352
+ });
353
+ }
354
+ wins.sort((a, b) => {
355
+ const ai = IMPACT_RANK[a.impact];
356
+ const bi = IMPACT_RANK[b.impact];
357
+ if (ai !== bi)
358
+ return bi - ai;
359
+ return b.count - a.count;
360
+ });
361
+ return wins.slice(0, 5);
362
+ }
363
+ function quickWinTitle(type, count) {
364
+ switch (type) {
365
+ case 'missing-meta-description':
366
+ return `Add meta descriptions to ${count} page${count === 1 ? '' : 's'}`;
367
+ case 'missing-meta-title':
368
+ return `Add meta titles to ${count} page${count === 1 ? '' : 's'}`;
369
+ case 'duplicate-meta-title':
370
+ return 'Fix duplicate meta title';
371
+ case 'missing-structured-data':
372
+ return `Add structured data to ${count} page${count === 1 ? '' : 's'}`;
373
+ case 'missing-og-image':
374
+ return `Add Open Graph images to ${count} page${count === 1 ? '' : 's'}`;
375
+ case 'missing-canonical':
376
+ return `Add canonical tags to ${count} page${count === 1 ? '' : 's'}`;
377
+ case 'broken-internal-link':
378
+ return `Fix ${count} broken internal link${count === 1 ? '' : 's'}`;
379
+ case 'missing-alt-text':
380
+ return `Add alt text on ${count} page${count === 1 ? '' : 's'}`;
381
+ default:
382
+ return `Resolve ${count} ${type.replace(/-/g, ' ')} issue${count === 1 ? '' : 's'}`;
383
+ }
384
+ }
385
+ function quickWinExplanation(type, count) {
386
+ switch (type) {
387
+ case 'missing-meta-description':
388
+ return 'Missing meta descriptions reduce click-through rates by up to 30%. This is the single highest-impact fix available.';
389
+ case 'duplicate-meta-title':
390
+ return 'Pages sharing the same meta title may be penalised by Google for duplication. Differentiate them.';
391
+ case 'missing-structured-data':
392
+ return 'Schema markup enables rich results (star ratings, FAQs, breadcrumbs) and improves visibility.';
393
+ case 'missing-og-image':
394
+ return 'Open Graph images significantly improve click-through from social shares.';
395
+ case 'broken-internal-link':
396
+ return 'Broken internal links waste crawl budget and frustrate users. Fix or redirect them.';
397
+ default:
398
+ return `Resolving ${count} ${type.replace(/-/g, ' ')} issue${count === 1 ? '' : 's'} will improve your SEO health.`;
399
+ }
400
+ }
401
+ export { gradeForSeoScore };
402
+ //# sourceMappingURL=audit-engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-engine.js","sourceRoot":"","sources":["../../src/seo/audit-engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,gBAAgB,EAChB,OAAO,GAGR,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AA0F7C,SAAS,OAAO,CAAC,CAAU;IACzB,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAA;AACrD,CAAC;AAED,SAAS,cAAc,CAAC,CAAS;IAC/B,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;AACpD,CAAC;AAED,SAAS,OAAO,CAAC,OAAkC,EAAE,QAAwB;IAC3E,IAAI,OAAO,QAAQ,KAAK,QAAQ;QAAE,OAAO,QAAQ,CAAA;IACjD,IAAI,CAAC,OAAO;QAAE,OAAO,CAAC,CAAA;IACtB,MAAM,IAAI,GAAG,aAAa,CAAC,OAAO,CAAC,CAAA;IACnC,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAA;AACxE,CAAC;AAED,SAAS,eAAe,CAAC,OAAkC;IACzD,IAAI,CAAC,OAAO;QAAE,OAAO,CAAC,CAAA;IACtB,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,gBAAgB,CAAC,IAAI,EAAE,CAAA;IAClD,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,6BAA6B,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAA;AAC9E,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,UAAkB;IAC1D,OAAO,GAAG,IAAI,IAAI,UAAU,EAAE,CAAA;AAChC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB,CACjC,QAA4B,EAC5B,UAA6B,EAAE;IAE/B,MAAM,MAAM,GAAoB,EAAE,CAAA;IAClC,MAAM,KAAK,GAAsB,EAAE,CAAA;IAEnC,2EAA2E;IAC3E,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,MAAM,SAAS,GAAG,OAAO,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,SAAS,CAAC,CAAA;QACjD,MAAM,WAAW,GAAG,qBAAqB,CAAC;YACxC,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,GAAG,EAAE,CAAC,CAAC,GAAG;YACV,OAAO,EAAE,CAAC,CAAC,OAAO;YAClB,SAAS,EAAE,CAAC,CAAC,SAAS;YACtB,eAAe,EAAE,CAAC,CAAC,eAAe;YAClC,YAAY,EAAE,CAAC,CAAC,YAAY;YAC5B,YAAY,EAAE,CAAC,CAAC,YAAY;YAC5B,OAAO,EAAE,CAAC,CAAC,OAAO;YAClB,aAAa,EAAE,CAAC,CAAC,aAAa;YAC9B,OAAO,EAAE,CAAC,CAAC,OAAO;YAClB,kBAAkB,EAAE,CAAC,CAAC,kBAAkB;YACxC,OAAO,EAAE,CAAC,CAAC,OAAO;YAClB,iBAAiB,EAAE,CAAC,CAAC,iBAAiB;YACtC,SAAS;SACV,CAAC,CAAA;QAEF,KAAK,CAAC,IAAI,CAAC;YACT,UAAU,EAAE,CAAC,CAAC,UAAU;YACxB,QAAQ,EAAE,CAAC,CAAC,QAAQ;YACpB,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,GAAG,EAAE,CAAC,CAAC,GAAG;YACV,KAAK,EAAE,WAAW,CAAC,KAAK;YACxB,KAAK,EAAE,WAAW,CAAC,KAAK;YACxB,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;YAClC,kBAAkB,EAAE,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;SAC/C,CAAC,CAAA;QAEF,MAAM,GAAG,GAAG;YACV,UAAU,EAAE,CAAC,CAAC,UAAU;YACxB,QAAQ,EAAE,CAAC,CAAC,QAAQ;YACpB,WAAW,EAAE,CAAC,CAAC,KAAK;YACpB,GAAG,EAAE,CAAC,CAAC,GAAG;SACX,CAAA;QAED,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,CAAC;YAC1B,MAAM,CAAC,IAAI,CAAC;gBACV,GAAG,GAAG;gBACN,QAAQ,EAAE,WAAW,CAAC,oBAAoB,EAAE,CAAC,CAAC,QAAQ,CAAC;gBACvD,QAAQ,EAAE,SAAS;gBACnB,QAAQ,EAAE,SAAS;gBACnB,IAAI,EAAE,oBAAoB;gBAC1B,KAAK,EAAE,oBAAoB;gBAC3B,WAAW,EAAE,8EAA8E;gBAC3F,MAAM,EAAE,MAAM;gBACd,cAAc,EAAE,iEAAiE;gBACjF,OAAO,EAAE,IAAI;gBACb,aAAa,EAAE,eAAe;gBAC9B,gBAAgB,EAAE,EAAE;aACrB,CAAC,CAAA;QACJ,CAAC;QAED,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,EAAE,CAAC;YAChC,MAAM,CAAC,IAAI,CAAC;gBACV,GAAG,GAAG;gBACN,QAAQ,EAAE,WAAW,CAAC,0BAA0B,EAAE,CAAC,CAAC,QAAQ,CAAC;gBAC7D,QAAQ,EAAE,SAAS;gBACnB,QAAQ,EAAE,SAAS;gBACnB,IAAI,EAAE,0BAA0B;gBAChC,KAAK,EAAE,0BAA0B;gBACjC,WAAW,EAAE,8EAA8E;gBAC3F,MAAM,EAAE,MAAM;gBACd,cAAc,EAAE,kEAAkE;gBAClF,OAAO,EAAE,IAAI;gBACb,aAAa,EAAE,eAAe;gBAC9B,gBAAgB,EAAE,EAAE;aACrB,CAAC,CAAA;QACJ,CAAC;QAED,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,kBAAkB,CAAC,EAAE,CAAC;YACnC,MAAM,CAAC,IAAI,CAAC;gBACV,GAAG,GAAG;gBACN,QAAQ,EAAE,WAAW,CAAC,yBAAyB,EAAE,CAAC,CAAC,QAAQ,CAAC;gBAC5D,QAAQ,EAAE,SAAS;gBACnB,QAAQ,EAAE,QAAQ;gBAClB,IAAI,EAAE,yBAAyB;gBAC/B,KAAK,EAAE,0BAA0B;gBACjC,WAAW,EAAE,2DAA2D;gBACxE,MAAM,EAAE,QAAQ;gBAChB,cAAc,EAAE,mEAAmE;gBACnF,OAAO,EAAE,IAAI;gBACb,aAAa,EAAE,aAAa;gBAC5B,gBAAgB,EAAE,EAAE;aACrB,CAAC,CAAA;QACJ,CAAC;QAED,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,EAAE,CAAC;YAC7B,MAAM,CAAC,IAAI,CAAC;gBACV,GAAG,GAAG;gBACN,QAAQ,EAAE,WAAW,CAAC,mBAAmB,EAAE,CAAC,CAAC,QAAQ,CAAC;gBACtD,QAAQ,EAAE,MAAM;gBAChB,QAAQ,EAAE,WAAW;gBACrB,IAAI,EAAE,mBAAmB;gBACzB,KAAK,EAAE,uBAAuB;gBAC9B,WAAW,EAAE,sEAAsE;gBACnF,MAAM,EAAE,KAAK;gBACb,cAAc,EAAE,4DAA4D;gBAC5E,OAAO,EAAE,IAAI;gBACb,aAAa,EAAE,gBAAgB;gBAC/B,gBAAgB,EAAE,EAAE;aACrB,CAAC,CAAA;QACJ,CAAC;QAED,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;YACxB,MAAM,CAAC,IAAI,CAAC;gBACV,GAAG,GAAG;gBACN,QAAQ,EAAE,WAAW,CAAC,kBAAkB,EAAE,CAAC,CAAC,QAAQ,CAAC;gBACrD,QAAQ,EAAE,MAAM;gBAChB,QAAQ,EAAE,QAAQ;gBAClB,IAAI,EAAE,kBAAkB;gBACxB,KAAK,EAAE,iBAAiB;gBACxB,WAAW,EAAE,mEAAmE;gBAChF,MAAM,EAAE,KAAK;gBACb,cAAc,EAAE,sEAAsE;gBACtF,OAAO,EAAE,IAAI;gBACb,aAAa,EAAE,eAAe;gBAC9B,gBAAgB,EAAE,EAAE;aACrB,CAAC,CAAA;QACJ,CAAC;QAED,IAAI,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC;YACnE,MAAM,CAAC,IAAI,CAAC;gBACV,GAAG,GAAG;gBACN,QAAQ,EAAE,WAAW,CAAC,YAAY,EAAE,CAAC,CAAC,QAAQ,CAAC;gBAC/C,QAAQ,EAAE,MAAM;gBAChB,QAAQ,EAAE,SAAS;gBACnB,IAAI,EAAE,YAAY;gBAClB,KAAK,EAAE,oBAAoB;gBAC3B,WAAW,EAAE,yEAAyE;gBACtF,MAAM,EAAE,KAAK;gBACb,cAAc,EAAE,qDAAqD;gBACrE,OAAO,EAAE,KAAK;gBACd,aAAa,EAAE,cAAc;gBAC7B,gBAAgB,EAAE,EAAE;aACrB,CAAC,CAAA;QACJ,CAAC;QAED,IAAI,SAAS,GAAG,CAAC,IAAI,SAAS,GAAG,GAAG,EAAE,CAAC;YACrC,MAAM,CAAC,IAAI,CAAC;gBACV,GAAG,GAAG;gBACN,QAAQ,EAAE,WAAW,CAAC,cAAc,EAAE,CAAC,CAAC,QAAQ,CAAC;gBACjD,QAAQ,EAAE,MAAM;gBAChB,QAAQ,EAAE,SAAS;gBACnB,IAAI,EAAE,cAAc;gBACpB,KAAK,EAAE,yBAAyB;gBAChC,WAAW,EAAE,8DAA8D;gBAC3E,MAAM,EAAE,KAAK;gBACb,cAAc,EAAE,4CAA4C;gBAC5D,OAAO,EAAE,KAAK;gBACd,aAAa,EAAE,cAAc;gBAC7B,gBAAgB,EAAE,EAAE;aACrB,CAAC,CAAA;QACJ,CAAC;QAED,MAAM,UAAU,GAAG,eAAe,CAAC,CAAC,CAAC,OAAO,CAAC,CAAA;QAC7C,IAAI,UAAU,GAAG,CAAC,EAAE,CAAC;YACnB,MAAM,CAAC,IAAI,CAAC;gBACV,GAAG,GAAG;gBACN,QAAQ,EAAE,WAAW,CAAC,kBAAkB,EAAE,CAAC,CAAC,QAAQ,CAAC;gBACrD,QAAQ,EAAE,SAAS;gBACnB,QAAQ,EAAE,OAAO;gBACjB,IAAI,EAAE,kBAAkB;gBACxB,KAAK,EAAE,yBAAyB;gBAChC,WAAW,EAAE,GAAG,UAAU,yEAAyE;gBACnG,MAAM,EAAE,QAAQ;gBAChB,cAAc,EAAE,kDAAkD;gBAClE,OAAO,EAAE,IAAI;gBACb,aAAa,EAAE,cAAc;gBAC7B,gBAAgB,EAAE,EAAE;aACrB,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;IAED,2EAA2E;IAC3E,MAAM,WAAW,GAAG,IAAI,GAAG,EAA8B,CAAA;IACzD,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;YAAE,SAAQ;QACnC,MAAM,GAAG,GAAG,cAAc,CAAC,CAAC,CAAC,SAAU,CAAC,CAAA;QACxC,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAA;QACtC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACX,WAAW,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;IAC3B,CAAC;IACD,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,MAAM,EAAE,EAAE,CAAC;QACzC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;YAAE,SAAQ;QAC9B,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAA;QAC/C,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC/C,MAAM,CAAC,IAAI,CAAC;YACV,UAAU,EAAE,KAAK,CAAC,CAAC,CAAE,CAAC,UAAU;YAChC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAE,CAAC,QAAQ;YAC5B,WAAW,EAAE,KAAK,CAAC,CAAC,CAAE,CAAC,KAAK;YAC5B,GAAG,EAAE,IAAI;YACT,QAAQ,EAAE,WAAW,CAAC,sBAAsB,EAAE,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC5D,QAAQ,EAAE,UAAU;YACpB,QAAQ,EAAE,SAAS;YACnB,IAAI,EAAE,sBAAsB;YAC5B,KAAK,EAAE,+BAA+B;YACtC,WAAW,EAAE,4CAA4C,IAAI,wBAAwB;YACrF,MAAM,EAAE,MAAM;YACd,cAAc,EAAE,qEAAqE;YACrF,OAAO,EAAE,IAAI;YACb,aAAa,EAAE,eAAe;YAC9B,gBAAgB,EAAE,GAAG;SACtB,CAAC,CAAA;IACJ,CAAC;IAED,2EAA2E;IAC3E,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,WAAW,IAAI,EAAE,EAAE,CAAC;QAC7C,MAAM,CAAC,IAAI,CAAC;YACV,UAAU,EAAE,MAAM;YAClB,QAAQ,EAAE,IAAI,CAAC,YAAY;YAC3B,WAAW,EAAE,IAAI,CAAC,SAAS;YAC3B,GAAG,EAAE,IAAI,CAAC,OAAO;YACjB,QAAQ,EAAE,WAAW,CAAC,sBAAsB,EAAE,GAAG,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;YACjF,QAAQ,EAAE,UAAU;YACpB,QAAQ,EAAE,WAAW;YACrB,IAAI,EAAE,sBAAsB;YAC5B,KAAK,EAAE,2BAA2B;YAClC,WAAW,EAAE,eAAe,IAAI,CAAC,GAAG,iDAAiD;YACrF,MAAM,EAAE,MAAM;YACd,cAAc,EAAE,2DAA2D;YAC3E,OAAO,EAAE,IAAI;YACb,aAAa,EAAE,UAAU;YACzB,gBAAgB,EAAE,EAAE;SACrB,CAAC,CAAA;IACJ,CAAC;IAED,2EAA2E;IAC3E,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,SAAS,IAAI,EAAE,EAAE,CAAC;QAC3C,MAAM,CAAC,IAAI,CAAC;YACV,UAAU,EAAE,MAAM;YAClB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,WAAW,EAAE,IAAI,CAAC,KAAK;YACvB,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,QAAQ,EAAE,WAAW,CAAC,WAAW,EAAE,IAAI,CAAC,QAAQ,CAAC;YACjD,QAAQ,EAAE,SAAS;YACnB,QAAQ,EAAE,aAAa;YACvB,IAAI,EAAE,WAAW;YACjB,KAAK,EAAE,kCAAkC;YACzC,WAAW,EAAE,IAAI,CAAC,IAAI,IAAI,2DAA2D;YACrF,MAAM,EAAE,QAAQ;YAChB,cAAc,EAAE,uEAAuE;YACvF,OAAO,EAAE,KAAK;YACd,aAAa,EAAE,0BAA0B;YACzC,gBAAgB,EAAE,EAAE;SACrB,CAAC,CAAA;IACJ,CAAC;IAED,2EAA2E;IAC3E,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAA;IAC7B,MAAM,KAAK,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAA;IAExF,MAAM,aAAa,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAA;IACzE,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,MAAM,CAAA;IAC9E,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAA;IAC5E,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,GAAG,KAAK,CAAC,YAAY,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;IAEjG,MAAM,aAAa,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM,CAAA;IAC5E,MAAM,gBAAgB,GAAG,QAAQ,CAAC,MAAM,CACtC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,iBAAiB,KAAK,KAAK,IAAI,CAAC,CAAC,OAAO,KAAK,IAAI,CAAC,CAC9D,CAAC,MAAM,CAAA;IACR,MAAM,UAAU,GACd,CAAC,OAAO,CAAC,cAAc,KAAK,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,aAAa,KAAK,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAC1F,MAAM,cAAc,GAAG,IAAI,CAAC,KAAK,CAC/B,CAAC,KAAK,CAAC,aAAa,CAAC,GAAG,KAAK,CAAC,gBAAgB,CAAC,GAAG,UAAU,CAAC,GAAG,CAAC,CAClE,CAAA;IAED,MAAM,WAAW,GAAG,CAAC,OAAO,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,MAAM,CAAA;IACtD,MAAM,YAAY,GAAG,CAAC,CAAC,OAAO,CAAC,cAAc,IAAI,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,aAAa,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;IACvF,MAAM,iBAAiB,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,GAAG,WAAW,GAAG,EAAE,GAAG,YAAY,CAAC,CAAA;IAE5E,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,MAAM,CAAA;IAC/E,MAAM,WAAW,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAA;IACrE,MAAM,qBAAqB,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;IAEtF,MAAM,gBAAgB,GACpB,CAAC,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC;QAClC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,MAAM,GAAG,EAAE,CAAC;QACzD,CAAC,CAAC,EAAE,CAAA;IAER,MAAM,SAAS,GAAG,qBAAqB,CAAC;QACtC,OAAO,EAAE,YAAY;QACrB,SAAS,EAAE,cAAc;QACzB,YAAY,EAAE,iBAAiB;QAC/B,gBAAgB,EAAE,qBAAqB;QACvC,WAAW,EAAE,gBAAgB;KAC9B,CAAC,CAAA;IAEF,2EAA2E;IAC3E,MAAM,MAAM,GAAG;QACb,KAAK,EAAE,MAAM,CAAC,MAAM;QACpB,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC,MAAM;QAChE,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,MAAM;QAC9D,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC,MAAM;KACzD,CAAA;IAED,MAAM,SAAS,GAAG,cAAc,CAAC,MAAM,CAAC,CAAA;IAExC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,CAAA;AACxD,CAAC;AAED,MAAM,WAAW,GAA8C,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAA;AAC7F,MAAM,aAAa,GAAgC,EAAE,QAAQ,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAA;AAEvF;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,MAAuB;IACpD,MAAM,MAAM,GAAG,IAAI,GAAG,EAA2B,CAAA;IACjD,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;QACvB,IAAI,CAAC,CAAC,CAAC,OAAO;YAAE,SAAQ;QACxB,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAA;QACpC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACX,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;IACzB,CAAC;IAED,MAAM,IAAI,GAAe,EAAE,CAAA;IAC3B,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,EAAE,CAAC;QACnC,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAE,CAAA;QACvB,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAA;QAC1B,IAAI,CAAC,IAAI,CAAC;YACR,EAAE,EAAE,MAAM,IAAI,EAAE;YAChB,KAAK,EAAE,aAAa,CAAC,IAAI,EAAE,KAAK,CAAC;YACjC,WAAW,EAAE,mBAAmB,CAAC,IAAI,EAAE,KAAK,CAAC;YAC7C,MAAM,EAAE,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK;YACvF,SAAS,EAAE,IAAI;YACf,aAAa,EAAE,KAAK,CAAC,aAAa;YAClC,KAAK;SACN,CAAC,CAAA;IACJ,CAAC;IAED,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACjB,MAAM,EAAE,GAAG,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAA;QAChC,MAAM,EAAE,GAAG,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAA;QAChC,IAAI,EAAE,KAAK,EAAE;YAAE,OAAO,EAAE,GAAG,EAAE,CAAA;QAC7B,OAAO,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAA;IAC1B,CAAC,CAAC,CAAA;IACF,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;AACzB,CAAC;AAED,SAAS,aAAa,CAAC,IAAY,EAAE,KAAa;IAChD,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,0BAA0B;YAC7B,OAAO,4BAA4B,KAAK,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAA;QAC1E,KAAK,oBAAoB;YACvB,OAAO,sBAAsB,KAAK,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAA;QACpE,KAAK,sBAAsB;YACzB,OAAO,0BAA0B,CAAA;QACnC,KAAK,yBAAyB;YAC5B,OAAO,0BAA0B,KAAK,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAA;QACxE,KAAK,kBAAkB;YACrB,OAAO,4BAA4B,KAAK,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAA;QAC1E,KAAK,mBAAmB;YACtB,OAAO,yBAAyB,KAAK,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAA;QACvE,KAAK,sBAAsB;YACzB,OAAO,OAAO,KAAK,wBAAwB,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAA;QACrE,KAAK,kBAAkB;YACrB,OAAO,mBAAmB,KAAK,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAA;QACjE;YACE,OAAO,WAAW,KAAK,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAA;IACvF,CAAC;AACH,CAAC;AAED,SAAS,mBAAmB,CAAC,IAAY,EAAE,KAAa;IACtD,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,0BAA0B;YAC7B,OAAO,qHAAqH,CAAA;QAC9H,KAAK,sBAAsB;YACzB,OAAO,mGAAmG,CAAA;QAC5G,KAAK,yBAAyB;YAC5B,OAAO,+FAA+F,CAAA;QACxG,KAAK,kBAAkB;YACrB,OAAO,2EAA2E,CAAA;QACpF,KAAK,sBAAsB;YACzB,OAAO,qFAAqF,CAAA;QAC9F;YACE,OAAO,aAAa,KAAK,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,gCAAgC,CAAA;IACvH,CAAC;AACH,CAAC;AAED,OAAO,EAAE,gBAAgB,EAAE,CAAA"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * SEO audit orchestration: gather content from the DB, run the pure analysis
3
+ * engine, and persist a `SeoAuditRun` snapshot plus the live `SeoIssue` set.
4
+ *
5
+ * Issue lifecycle (carried across runs by `issueKey`):
6
+ * - newly detected -> status `open`
7
+ * - detected + was `ignored` -> stays `ignored`
8
+ * - detected + was `resolved` -> `recurring`
9
+ * - detected + open/recurring -> unchanged (lastDetectedAt bumped)
10
+ * - NOT detected + open/recurring -> auto `resolved` (resolvedAt set)
11
+ * - NOT detected + ignored/resolved -> unchanged
12
+ *
13
+ * The run row stores the score + counts snapshot, so audit *history* is the
14
+ * sequence of runs while the issue table reflects current state.
15
+ */
16
+ import { type AuditEntityInput, type AuditExtraSignals } from './audit-engine.js';
17
+ /**
18
+ * True for internal/system collections that must never be exposed on public
19
+ * surfaces (audit, `/llms.txt`, etc.). Covers the explicit exclude list plus
20
+ * any `__`-prefixed convention collection (e.g. `__seo_config__`, `__seo_perf__`).
21
+ */
22
+ export declare function isInternalCollection(collection: string): boolean;
23
+ /** Map a published document row into the audit engine's entity shape. */
24
+ export declare function documentToAuditEntity(doc: any): AuditEntityInput;
25
+ export declare function gatherAuditEntities(db: any): Promise<AuditEntityInput[]>;
26
+ export interface RunAuditOptions {
27
+ scope?: string;
28
+ startedById?: string | null;
29
+ /** Cross-cutting signals (broken links, redirect chains/loops) from callers. */
30
+ signals?: AuditExtraSignals;
31
+ }
32
+ export interface PersistedAuditRun {
33
+ id: string;
34
+ status: string;
35
+ siteScoreAfter: number;
36
+ issuesFound: number;
37
+ criticalCount: number;
38
+ warningCount: number;
39
+ infoCount: number;
40
+ pagesScanned: number;
41
+ }
42
+ /**
43
+ * Run an audit and persist the run + issue snapshot. Returns the run summary.
44
+ * Throws if the SEO models aren't available (caller maps to 501).
45
+ */
46
+ export declare function runAndPersistAudit(db: any, options?: RunAuditOptions): Promise<PersistedAuditRun>;
47
+ //# sourceMappingURL=audit-runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-runner.d.ts","sourceRoot":"","sources":["../../src/seo/audit-runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACvB,MAAM,mBAAmB,CAAA;AAW1B;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAEhE;AAsBD,yEAAyE;AACzE,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,GAAG,GAAG,gBAAgB,CA8BhE;AAED,wBAAsB,mBAAmB,CAAC,EAAE,EAAE,GAAG,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAgB9E;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,gFAAgF;IAChF,OAAO,CAAC,EAAE,iBAAiB,CAAA;CAC5B;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,CAAA;IACd,cAAc,EAAE,MAAM,CAAA;IACtB,WAAW,EAAE,MAAM,CAAA;IACnB,aAAa,EAAE,MAAM,CAAA;IACrB,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;CACrB;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,EAAE,EAAE,GAAG,EACP,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,iBAAiB,CAAC,CA4F5B"}