@stati/core 1.6.4 → 1.7.1

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 (81) hide show
  1. package/README.md +616 -101
  2. package/dist/core/build.d.ts.map +1 -1
  3. package/dist/core/build.js +42 -6
  4. package/dist/core/content.d.ts.map +1 -1
  5. package/dist/core/content.js +1 -2
  6. package/dist/core/dev.d.ts.map +1 -1
  7. package/dist/core/dev.js +2 -5
  8. package/dist/core/index.d.ts +13 -0
  9. package/dist/core/index.d.ts.map +1 -0
  10. package/dist/core/index.js +12 -0
  11. package/dist/core/invalidate.js +2 -2
  12. package/dist/core/isg/build-lock.js +1 -1
  13. package/dist/core/isg/deps.d.ts.map +1 -1
  14. package/dist/core/isg/deps.js +1 -3
  15. package/dist/core/isg/hash.js +1 -1
  16. package/dist/core/isg/index.d.ts +16 -0
  17. package/dist/core/isg/index.d.ts.map +1 -0
  18. package/dist/core/isg/index.js +22 -0
  19. package/dist/core/isg/manifest.js +1 -1
  20. package/dist/core/preview.d.ts.map +1 -1
  21. package/dist/core/preview.js +1 -2
  22. package/dist/core/templates.d.ts.map +1 -1
  23. package/dist/core/templates.js +4 -7
  24. package/dist/core/utils/index.d.ts +16 -0
  25. package/dist/core/utils/index.d.ts.map +1 -0
  26. package/dist/core/utils/index.js +22 -0
  27. package/dist/core/utils/partial-validation.d.ts.map +1 -1
  28. package/dist/core/utils/partial-validation.js +2 -1
  29. package/dist/index.d.ts +6 -8
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +3 -4
  32. package/dist/seo/auto-inject.d.ts +48 -0
  33. package/dist/seo/auto-inject.d.ts.map +1 -0
  34. package/dist/seo/auto-inject.js +108 -0
  35. package/dist/seo/generator.d.ts +77 -0
  36. package/dist/seo/generator.d.ts.map +1 -0
  37. package/dist/seo/generator.js +320 -0
  38. package/dist/seo/index.d.ts +12 -0
  39. package/dist/seo/index.d.ts.map +1 -0
  40. package/dist/seo/index.js +15 -0
  41. package/dist/seo/robots.d.ts +84 -0
  42. package/dist/seo/robots.d.ts.map +1 -0
  43. package/dist/seo/robots.js +165 -0
  44. package/dist/seo/sitemap.d.ts +37 -0
  45. package/dist/seo/sitemap.d.ts.map +1 -0
  46. package/dist/seo/sitemap.js +320 -0
  47. package/dist/seo/utils/escape-and-validation.d.ts +99 -0
  48. package/dist/seo/utils/escape-and-validation.d.ts.map +1 -0
  49. package/dist/seo/utils/escape-and-validation.js +319 -0
  50. package/dist/seo/utils/index.d.ts +7 -0
  51. package/dist/seo/utils/index.d.ts.map +1 -0
  52. package/dist/seo/utils/index.js +8 -0
  53. package/dist/seo/utils/url.d.ts +46 -0
  54. package/dist/seo/utils/url.d.ts.map +1 -0
  55. package/dist/seo/utils/url.js +66 -0
  56. package/dist/seo/utils.d.ts +94 -0
  57. package/dist/seo/utils.d.ts.map +1 -0
  58. package/dist/seo/utils.js +304 -0
  59. package/dist/types/config.d.ts +58 -0
  60. package/dist/types/config.d.ts.map +1 -1
  61. package/dist/types/content.d.ts +181 -0
  62. package/dist/types/content.d.ts.map +1 -1
  63. package/dist/types/index.d.ts +5 -2
  64. package/dist/types/index.d.ts.map +1 -1
  65. package/dist/types/index.js +1 -1
  66. package/dist/types/seo.d.ts +69 -0
  67. package/dist/types/seo.d.ts.map +1 -0
  68. package/dist/types/seo.js +36 -0
  69. package/dist/types/sitemap.d.ts +94 -0
  70. package/dist/types/sitemap.d.ts.map +1 -0
  71. package/dist/types/sitemap.js +4 -0
  72. package/package.json +1 -1
  73. package/dist/core/utils/partials.d.ts +0 -24
  74. package/dist/core/utils/partials.d.ts.map +0 -1
  75. package/dist/core/utils/partials.js +0 -85
  76. package/dist/tests/utils/test-mocks.d.ts +0 -69
  77. package/dist/tests/utils/test-mocks.d.ts.map +0 -1
  78. package/dist/tests/utils/test-mocks.js +0 -125
  79. package/dist/types.d.ts +0 -543
  80. package/dist/types.d.ts.map +0 -1
  81. package/dist/types.js +0 -1
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Sitemap generation utilities for Stati
3
+ * @module seo/sitemap
4
+ */
5
+ import type { SitemapEntry, SitemapConfig, SitemapGenerationResult } from '../types/sitemap.js';
6
+ import type { PageModel } from '../types/content.js';
7
+ import type { StatiConfig } from '../types/config.js';
8
+ /**
9
+ * Generates a sitemap entry from a page model
10
+ * @param page - Page to generate entry for
11
+ * @param config - Stati configuration
12
+ * @param sitemapConfig - Sitemap configuration
13
+ * @returns Sitemap entry or null if page should be excluded
14
+ */
15
+ export declare function generateSitemapEntry(page: PageModel, config: StatiConfig, sitemapConfig: SitemapConfig): SitemapEntry | null;
16
+ /**
17
+ * Generates sitemap XML from entries
18
+ * @param entries - Sitemap entries
19
+ * @returns Complete sitemap XML
20
+ */
21
+ export declare function generateSitemapXml(entries: SitemapEntry[]): string;
22
+ /**
23
+ * Generates sitemap index XML for multiple sitemaps
24
+ * @param sitemapUrls - Array of sitemap URLs
25
+ * @param siteUrl - Base site URL
26
+ * @returns Sitemap index XML
27
+ */
28
+ export declare function generateSitemapIndexXml(sitemapUrls: string[], siteUrl: string): string;
29
+ /**
30
+ * Generates sitemap(s) from pages
31
+ * @param pages - All pages to include in sitemap
32
+ * @param config - Stati configuration
33
+ * @param sitemapConfig - Sitemap configuration
34
+ * @returns Sitemap generation result with XML content
35
+ */
36
+ export declare function generateSitemap(pages: PageModel[], config: StatiConfig, sitemapConfig: SitemapConfig): SitemapGenerationResult;
37
+ //# sourceMappingURL=sitemap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sitemap.d.ts","sourceRoot":"","sources":["../../src/seo/sitemap.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,aAAa,EAEb,uBAAuB,EACxB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AA6ItD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,WAAW,EACnB,aAAa,EAAE,aAAa,GAC3B,YAAY,GAAG,IAAI,CA4ErB;AA2BD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,CAUlE;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAatF;AAoBD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,SAAS,EAAE,EAClB,MAAM,EAAE,WAAW,EACnB,aAAa,EAAE,aAAa,GAC3B,uBAAuB,CA8CzB"}
@@ -0,0 +1,320 @@
1
+ /**
2
+ * Sitemap generation utilities for Stati
3
+ * @module seo/sitemap
4
+ */
5
+ import { escapeHtml, resolveAbsoluteUrl } from './utils/index.js';
6
+ /**
7
+ * Maximum entries per sitemap file (per sitemap.org spec)
8
+ */
9
+ const MAX_SITEMAP_ENTRIES = 50000;
10
+ /**
11
+ * Formats a date for sitemap lastmod field (W3C Datetime / ISO 8601)
12
+ * @param date - Date to format
13
+ * @returns ISO 8601 formatted date string (YYYY-MM-DD)
14
+ */
15
+ function formatSitemapDate(date) {
16
+ const parts = date.toISOString().split('T');
17
+ if (parts[0]) {
18
+ return parts[0];
19
+ }
20
+ return date.toISOString().substring(0, 10);
21
+ }
22
+ /**
23
+ * Validates and clamps priority value to 0.0-1.0 range
24
+ * @param priority - Priority value to validate
25
+ * @returns Clamped priority value
26
+ */
27
+ function validatePriority(priority) {
28
+ if (isNaN(priority)) {
29
+ return 0.5;
30
+ }
31
+ return Math.max(0.0, Math.min(1.0, priority));
32
+ }
33
+ /**
34
+ * Validates change frequency value
35
+ * @param changefreq - Change frequency to validate
36
+ * @returns Valid change frequency or undefined
37
+ */
38
+ function validateChangeFreq(changefreq) {
39
+ const validFreqs = ['always', 'hourly', 'daily', 'weekly', 'monthly', 'yearly', 'never'];
40
+ if (changefreq && validFreqs.includes(changefreq)) {
41
+ return changefreq;
42
+ }
43
+ return undefined;
44
+ }
45
+ /**
46
+ * Escapes regex special characters in a string, except glob wildcards
47
+ * @param str - String to escape
48
+ * @returns String with regex special characters escaped
49
+ */
50
+ function escapeRegexExceptGlob(str) {
51
+ // Escape all regex special characters except * and ?
52
+ return str.replace(/[.+^${}()|[\]\\]/g, '\\$&');
53
+ }
54
+ /**
55
+ * Checks if a page URL matches a list of patterns.
56
+ * @param url - The URL to check.
57
+ * @param patterns - An array of glob-style patterns.
58
+ * @returns `true` if the URL matches any pattern, `false` otherwise.
59
+ */
60
+ function urlMatchesAnyPattern(url, patterns) {
61
+ for (const pattern of patterns) {
62
+ // Simple glob patterns
63
+ if (pattern.includes('*') || pattern.includes('?')) {
64
+ // Escape regex special characters first, then convert glob wildcards
65
+ const escapedPattern = escapeRegexExceptGlob(pattern);
66
+ const regexPattern = escapedPattern.replace(/\*/g, '.*').replace(/\?/g, '.');
67
+ const regex = new RegExp('^' + regexPattern + '$');
68
+ if (regex.test(url)) {
69
+ return true;
70
+ }
71
+ }
72
+ else if (url === pattern || url.startsWith(pattern)) {
73
+ return true;
74
+ }
75
+ }
76
+ return false;
77
+ }
78
+ /**
79
+ * Checks if a page matches exclude patterns
80
+ * @param page - Page to check
81
+ * @param patterns - Exclude patterns (glob or regex)
82
+ * @returns true if page should be excluded
83
+ */
84
+ function matchesExcludePattern(page, patterns) {
85
+ if (!patterns || patterns.length === 0) {
86
+ return false;
87
+ }
88
+ return urlMatchesAnyPattern(page.url, patterns);
89
+ }
90
+ /**
91
+ * Checks if a page matches include patterns
92
+ * @param page - Page to check
93
+ * @param patterns - Include patterns (glob or regex)
94
+ * @returns true if page should be included
95
+ */
96
+ function matchesIncludePattern(page, patterns) {
97
+ if (!patterns || patterns.length === 0) {
98
+ return true; // Include all by default
99
+ }
100
+ return urlMatchesAnyPattern(page.url, patterns);
101
+ }
102
+ /**
103
+ * Determines priority for a page based on priority rules
104
+ * @param page - Page to evaluate
105
+ * @param rules - Priority rules from config
106
+ * @param defaultPriority - Default priority value
107
+ * @returns Calculated priority
108
+ */
109
+ function determinePriority(page, rules, defaultPriority = 0.5) {
110
+ if (!rules || rules.length === 0) {
111
+ return defaultPriority;
112
+ }
113
+ // Check each rule in order (first match wins)
114
+ for (const rule of rules) {
115
+ const pattern = rule.pattern;
116
+ if (pattern.includes('*') || pattern.includes('?')) {
117
+ // Escape regex special characters first, then convert glob wildcards
118
+ const escapedPattern = escapeRegexExceptGlob(pattern);
119
+ const regexPattern = escapedPattern.replace(/\*/g, '.*').replace(/\?/g, '.');
120
+ const regex = new RegExp('^' + regexPattern + '$');
121
+ if (regex.test(page.url)) {
122
+ return validatePriority(rule.priority);
123
+ }
124
+ }
125
+ else if (page.url === pattern || page.url.startsWith(pattern)) {
126
+ return validatePriority(rule.priority);
127
+ }
128
+ }
129
+ return defaultPriority;
130
+ }
131
+ /**
132
+ * Generates a sitemap entry from a page model
133
+ * @param page - Page to generate entry for
134
+ * @param config - Stati configuration
135
+ * @param sitemapConfig - Sitemap configuration
136
+ * @returns Sitemap entry or null if page should be excluded
137
+ */
138
+ export function generateSitemapEntry(page, config, sitemapConfig) {
139
+ // Check frontmatter exclude flag
140
+ if (page.frontMatter.sitemap?.exclude === true) {
141
+ return null;
142
+ }
143
+ // Check exclude patterns
144
+ if (matchesExcludePattern(page, sitemapConfig.excludePatterns)) {
145
+ return null;
146
+ }
147
+ // Check include patterns
148
+ if (!matchesIncludePattern(page, sitemapConfig.includePatterns)) {
149
+ return null;
150
+ }
151
+ // Apply filter function if provided
152
+ if (sitemapConfig.filter && !sitemapConfig.filter(page)) {
153
+ return null;
154
+ }
155
+ const siteUrl = config.site.baseUrl;
156
+ // Determine lastmod
157
+ let lastmod;
158
+ if (page.frontMatter.sitemap?.lastmod) {
159
+ lastmod = formatSitemapDate(new Date(page.frontMatter.sitemap.lastmod));
160
+ }
161
+ else if (page.frontMatter.updated) {
162
+ lastmod = formatSitemapDate(new Date(page.frontMatter.updated));
163
+ }
164
+ else if (page.frontMatter.date) {
165
+ lastmod = formatSitemapDate(new Date(page.frontMatter.date));
166
+ }
167
+ // Determine changefreq
168
+ let changefreq;
169
+ if (page.frontMatter.sitemap?.changefreq) {
170
+ changefreq = validateChangeFreq(page.frontMatter.sitemap.changefreq);
171
+ }
172
+ if (!changefreq && sitemapConfig.defaultChangeFreq) {
173
+ changefreq = sitemapConfig.defaultChangeFreq;
174
+ }
175
+ // Determine priority
176
+ let priority;
177
+ if (page.frontMatter.sitemap?.priority !== undefined) {
178
+ priority = validatePriority(page.frontMatter.sitemap.priority);
179
+ }
180
+ else {
181
+ priority = determinePriority(page, sitemapConfig.priorityRules, sitemapConfig.defaultPriority);
182
+ }
183
+ // Resolve absolute URL
184
+ let url = resolveAbsoluteUrl(page.url, siteUrl);
185
+ // Apply transformUrl if provided
186
+ if (sitemapConfig.transformUrl) {
187
+ url = sitemapConfig.transformUrl(url, page, config);
188
+ }
189
+ // Build entry
190
+ const entry = { url };
191
+ if (lastmod !== undefined) {
192
+ entry.lastmod = lastmod;
193
+ }
194
+ if (changefreq !== undefined) {
195
+ entry.changefreq = changefreq;
196
+ }
197
+ if (priority !== undefined) {
198
+ entry.priority = priority;
199
+ }
200
+ // Apply transformEntry if provided
201
+ if (sitemapConfig.transformEntry) {
202
+ return sitemapConfig.transformEntry(entry, page);
203
+ }
204
+ return entry;
205
+ }
206
+ /**
207
+ * Generates XML for a single sitemap entry
208
+ * @param entry - Sitemap entry
209
+ * @returns XML string for entry
210
+ */
211
+ function generateEntryXml(entry) {
212
+ let xml = ' <url>\n';
213
+ xml += ` <loc>${escapeHtml(entry.url)}</loc>\n`;
214
+ if (entry.lastmod) {
215
+ xml += ` <lastmod>${escapeHtml(entry.lastmod)}</lastmod>\n`;
216
+ }
217
+ if (entry.changefreq) {
218
+ xml += ` <changefreq>${escapeHtml(entry.changefreq)}</changefreq>\n`;
219
+ }
220
+ if (entry.priority !== undefined) {
221
+ xml += ` <priority>${entry.priority.toFixed(1)}</priority>\n`;
222
+ }
223
+ xml += ' </url>\n';
224
+ return xml;
225
+ }
226
+ /**
227
+ * Generates sitemap XML from entries
228
+ * @param entries - Sitemap entries
229
+ * @returns Complete sitemap XML
230
+ */
231
+ export function generateSitemapXml(entries) {
232
+ let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
233
+ xml += '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n';
234
+ for (const entry of entries) {
235
+ xml += generateEntryXml(entry);
236
+ }
237
+ xml += '</urlset>\n';
238
+ return xml;
239
+ }
240
+ /**
241
+ * Generates sitemap index XML for multiple sitemaps
242
+ * @param sitemapUrls - Array of sitemap URLs
243
+ * @param siteUrl - Base site URL
244
+ * @returns Sitemap index XML
245
+ */
246
+ export function generateSitemapIndexXml(sitemapUrls, siteUrl) {
247
+ let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
248
+ xml += '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n';
249
+ for (const url of sitemapUrls) {
250
+ const absoluteUrl = resolveAbsoluteUrl(url, siteUrl);
251
+ xml += ' <sitemap>\n';
252
+ xml += ` <loc>${escapeHtml(absoluteUrl)}</loc>\n`;
253
+ xml += ' </sitemap>\n';
254
+ }
255
+ xml += '</sitemapindex>\n';
256
+ return xml;
257
+ }
258
+ /**
259
+ * Splits entries into multiple sitemaps if needed
260
+ * @param entries - All sitemap entries
261
+ * @returns Array of entry arrays (one per sitemap file)
262
+ */
263
+ function splitEntriesIntoSitemaps(entries) {
264
+ if (entries.length <= MAX_SITEMAP_ENTRIES) {
265
+ return [entries];
266
+ }
267
+ const sitemaps = [];
268
+ for (let i = 0; i < entries.length; i += MAX_SITEMAP_ENTRIES) {
269
+ sitemaps.push(entries.slice(i, i + MAX_SITEMAP_ENTRIES));
270
+ }
271
+ return sitemaps;
272
+ }
273
+ /**
274
+ * Generates sitemap(s) from pages
275
+ * @param pages - All pages to include in sitemap
276
+ * @param config - Stati configuration
277
+ * @param sitemapConfig - Sitemap configuration
278
+ * @returns Sitemap generation result with XML content
279
+ */
280
+ export function generateSitemap(pages, config, sitemapConfig) {
281
+ // Generate entries for all pages
282
+ const entries = [];
283
+ for (const page of pages) {
284
+ const entry = generateSitemapEntry(page, config, sitemapConfig);
285
+ if (entry) {
286
+ entries.push(entry);
287
+ }
288
+ }
289
+ // Check if we need multiple sitemaps
290
+ const sitemapGroups = splitEntriesIntoSitemaps(entries);
291
+ if (sitemapGroups.length === 1 && sitemapGroups[0]) {
292
+ // Single sitemap
293
+ const xml = generateSitemapXml(sitemapGroups[0]);
294
+ return {
295
+ xml,
296
+ entryCount: entries.length,
297
+ sizeInBytes: Buffer.byteLength(xml, 'utf8'),
298
+ };
299
+ }
300
+ // Multiple sitemaps - generate index
301
+ const sitemapUrls = [];
302
+ const sitemapFiles = [];
303
+ for (let i = 0; i < sitemapGroups.length; i++) {
304
+ const group = sitemapGroups[i];
305
+ if (!group)
306
+ continue;
307
+ const filename = `sitemap-${i + 1}.xml`;
308
+ const xml = generateSitemapXml(group);
309
+ sitemapUrls.push(`/${filename}`);
310
+ sitemapFiles.push({ filename, xml });
311
+ }
312
+ // Generate index
313
+ const indexXml = generateSitemapIndexXml(sitemapUrls, config.site.baseUrl);
314
+ return {
315
+ xml: indexXml,
316
+ entryCount: entries.length,
317
+ sizeInBytes: Buffer.byteLength(indexXml, 'utf8'),
318
+ sitemaps: sitemapFiles,
319
+ };
320
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * SEO utility functions for HTML escaping, validation, and tag detection
3
+ */
4
+ import type { SEOMetadata } from '../../types/content.js';
5
+ import type { SEOValidationResult, SEOTagType } from '../../types/seo.js';
6
+ import type { Logger } from '../../types/logging.js';
7
+ /**
8
+ * Escape HTML entities to prevent XSS attacks.
9
+ * Uses memoization for performance with frequently repeated strings.
10
+ *
11
+ * Implements LRU-style cache eviction: when the cache is full, it's cleared
12
+ * and the new entry is added. This prevents unbounded memory growth while
13
+ * still providing caching benefits for repeated strings.
14
+ *
15
+ * @param text - The text to escape
16
+ * @returns HTML-safe string with special characters escaped
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * escapeHtml('<script>alert("xss")</script>');
21
+ * // Returns: '&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;'
22
+ * ```
23
+ */
24
+ export declare function escapeHtml(text: string): string;
25
+ /**
26
+ * Sanitize structured data to prevent XSS attacks and ensure safe JSON-LD output.
27
+ * Recursively processes objects and arrays, escaping string values and enforcing depth limits.
28
+ *
29
+ * Security: Objects exceeding max depth are completely removed rather than replaced with
30
+ * a string placeholder to prevent potential XSS vectors.
31
+ *
32
+ * @param data - The data to sanitize
33
+ * @param logger - Logger instance for warnings
34
+ * @param depth - Current recursion depth (internal use)
35
+ * @param maxDepth - Maximum allowed recursion depth (default: 50)
36
+ * @returns Sanitized data safe for JSON-LD output, or undefined if max depth exceeded
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * const data = {
41
+ * name: '<script>alert("xss")</script>',
42
+ * nested: { value: 'test' }
43
+ * };
44
+ * sanitizeStructuredData(data, logger);
45
+ * // Returns: { name: '&lt;script&gt;...', nested: { value: 'test' } }
46
+ * ```
47
+ */
48
+ export declare function sanitizeStructuredData(data: unknown, logger: Logger, depth?: number, maxDepth?: number): unknown;
49
+ /**
50
+ * Generate robots meta tag content from SEO metadata and robots configuration.
51
+ * Combines noindex flag and robots directives into a comma-separated string.
52
+ *
53
+ * @param seo - SEO metadata containing robots configuration
54
+ * @returns Comma-separated robots directives, or empty string if none
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * generateRobotsContent({ noindex: true, robots: { follow: false } });
59
+ * // Returns: 'noindex, nofollow'
60
+ * ```
61
+ */
62
+ export declare function generateRobotsContent(seo: SEOMetadata): string;
63
+ /**
64
+ * Validate SEO metadata before processing.
65
+ * Checks for common issues like invalid URLs, improper lengths, and malformed data.
66
+ *
67
+ * @param seo - SEO metadata to validate
68
+ * @param _pageUrl - URL of the page being validated (for context in error messages)
69
+ * @returns Validation result with valid flag, errors, and warnings
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * const result = validateSEOMetadata({
74
+ * title: 'My Page',
75
+ * canonical: 'invalid-url'
76
+ * }, '/my-page');
77
+ * // Returns: { valid: false, errors: ['Invalid canonical URL...'], warnings: [] }
78
+ * ```
79
+ */
80
+ export declare function validateSEOMetadata(seo: SEOMetadata, _pageUrl: string): SEOValidationResult;
81
+ /**
82
+ * Detect existing SEO tags in HTML to avoid duplication during auto-injection.
83
+ * Uses enhanced regex patterns to handle multi-line attributes and edge cases.
84
+ *
85
+ * Returns a Set of SEOTagType enum values indicating which tag types are already present.
86
+ * This allows for granular control: only missing tags will be generated.
87
+ *
88
+ * @param html - The HTML content to scan
89
+ * @returns Set of SEOTagType enum values for existing tags
90
+ *
91
+ * @example
92
+ * ```typescript
93
+ * const html = '<head><title>My Page</title><meta name="description" content="..."></head>';
94
+ * const existing = detectExistingSEOTags(html);
95
+ * // Returns: Set { SEOTagType.Title, SEOTagType.Description }
96
+ * ```
97
+ */
98
+ export declare function detectExistingSEOTags(html: string): Set<SEOTagType>;
99
+ //# sourceMappingURL=escape-and-validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"escape-and-validation.d.ts","sourceRoot":"","sources":["../../../src/seo/utils/escape-and-validation.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAgB,MAAM,wBAAwB,CAAC;AACxE,OAAO,KAAK,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAE1E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AASrD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA4B/C;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,OAAO,EACb,MAAM,EAAE,MAAM,EACd,KAAK,GAAE,MAAU,EACjB,QAAQ,GAAE,MAAW,GACpB,OAAO,CA0CT;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,CAiD9D;AAiBD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,GAAG,mBAAmB,CAwE3F;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,UAAU,CAAC,CAuCnE"}