vaza-content 0.2.0 → 0.2.2

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 (82) hide show
  1. package/dist/adapters/astro/index.cjs +9 -15
  2. package/dist/adapters/astro/index.cjs.map +1 -1
  3. package/dist/adapters/astro/index.d.cts +4 -3
  4. package/dist/adapters/astro/index.d.ts +4 -3
  5. package/dist/adapters/astro/index.js +6 -12
  6. package/dist/adapters/astro/index.js.map +1 -1
  7. package/dist/adapters/next/index.cjs +11 -12
  8. package/dist/adapters/next/index.cjs.map +1 -1
  9. package/dist/adapters/next/index.d.cts +5 -3
  10. package/dist/adapters/next/index.d.ts +5 -3
  11. package/dist/adapters/next/index.js +8 -9
  12. package/dist/adapters/next/index.js.map +1 -1
  13. package/dist/adapters/nuxt/index.cjs +22 -22
  14. package/dist/adapters/nuxt/index.cjs.map +1 -1
  15. package/dist/adapters/nuxt/index.d.cts +11 -7
  16. package/dist/adapters/nuxt/index.d.ts +11 -7
  17. package/dist/adapters/nuxt/index.js +13 -13
  18. package/dist/adapters/nuxt/index.js.map +1 -1
  19. package/dist/adapters/sveltekit/index.cjs +9 -15
  20. package/dist/adapters/sveltekit/index.cjs.map +1 -1
  21. package/dist/adapters/sveltekit/index.d.cts +3 -2
  22. package/dist/adapters/sveltekit/index.d.ts +3 -2
  23. package/dist/adapters/sveltekit/index.js +5 -11
  24. package/dist/adapters/sveltekit/index.js.map +1 -1
  25. package/dist/{blog-7EEJJG26.js → blog-L7HRY3QC.js} +2 -4
  26. package/dist/{blog-7EEJJG26.js.map → blog-L7HRY3QC.js.map} +1 -1
  27. package/dist/{blog-Z3R5GOMP.cjs → blog-SEXXJJSV.cjs} +3 -5
  28. package/dist/blog-SEXXJJSV.cjs.map +1 -0
  29. package/dist/{chunk-YV2ZYIAD.cjs → chunk-H3D7F4TA.cjs} +544 -503
  30. package/dist/chunk-H3D7F4TA.cjs.map +1 -0
  31. package/dist/{chunk-FALSVGPG.js → chunk-OKXBDPYF.js} +511 -470
  32. package/dist/chunk-OKXBDPYF.js.map +1 -0
  33. package/dist/cli/index.cjs +30 -24
  34. package/dist/cli/index.cjs.map +1 -1
  35. package/dist/cli/index.js +29 -23
  36. package/dist/cli/index.js.map +1 -1
  37. package/dist/{dark-EX2GRAYK.cjs → dark-66ZWYLT7.cjs} +2 -4
  38. package/dist/dark-66ZWYLT7.cjs.map +1 -0
  39. package/dist/{dark-6E36AKLN.js → dark-SZOURAMM.js} +1 -3
  40. package/dist/{dark-6E36AKLN.js.map → dark-SZOURAMM.js.map} +1 -1
  41. package/dist/index.cjs +8 -11
  42. package/dist/index.cjs.map +1 -1
  43. package/dist/index.d.cts +29 -29
  44. package/dist/index.d.ts +29 -29
  45. package/dist/index.js +3 -6
  46. package/dist/index.js.map +1 -1
  47. package/dist/{minimal-D2PRAVG6.js → minimal-CGXF737F.js} +1 -3
  48. package/dist/{minimal-D2PRAVG6.js.map → minimal-CGXF737F.js.map} +1 -1
  49. package/dist/{minimal-RHOK4XEZ.cjs → minimal-XTTHXE3T.cjs} +2 -4
  50. package/dist/minimal-XTTHXE3T.cjs.map +1 -0
  51. package/dist/process-VXDWM664.cjs +7 -0
  52. package/dist/process-VXDWM664.cjs.map +1 -0
  53. package/dist/process-ZQV5M2TB.js +7 -0
  54. package/dist/{product-OT3XYMWD.cjs → product-BY3GVQGV.cjs} +2 -4
  55. package/dist/product-BY3GVQGV.cjs.map +1 -0
  56. package/dist/{product-NCUW3U72.js → product-UQXUI5YL.js} +1 -3
  57. package/dist/{product-NCUW3U72.js.map → product-UQXUI5YL.js.map} +1 -1
  58. package/dist/{types-CgaidvaB.d.cts → types-DAfWIHiD.d.cts} +1 -1
  59. package/dist/{types-CgaidvaB.d.ts → types-DAfWIHiD.d.ts} +1 -1
  60. package/package.json +6 -4
  61. package/dist/blog-Z3R5GOMP.cjs.map +0 -1
  62. package/dist/chunk-DGUM43GV.js +0 -11
  63. package/dist/chunk-FALSVGPG.js.map +0 -1
  64. package/dist/chunk-JEQ2X3Z6.cjs +0 -11
  65. package/dist/chunk-JEQ2X3Z6.cjs.map +0 -1
  66. package/dist/chunk-PCRQY47G.js +0 -35
  67. package/dist/chunk-PCRQY47G.js.map +0 -1
  68. package/dist/chunk-WOCXEBQC.cjs +0 -35
  69. package/dist/chunk-WOCXEBQC.cjs.map +0 -1
  70. package/dist/chunk-YV2ZYIAD.cjs.map +0 -1
  71. package/dist/dark-EX2GRAYK.cjs.map +0 -1
  72. package/dist/logger-7WBTEDED.cjs +0 -10
  73. package/dist/logger-7WBTEDED.cjs.map +0 -1
  74. package/dist/logger-BGP7C274.js +0 -10
  75. package/dist/logger-BGP7C274.js.map +0 -1
  76. package/dist/minimal-RHOK4XEZ.cjs.map +0 -1
  77. package/dist/process-B4PJ6CWC.cjs +0 -9
  78. package/dist/process-B4PJ6CWC.cjs.map +0 -1
  79. package/dist/process-KSSXQJE6.js +0 -9
  80. package/dist/process-KSSXQJE6.js.map +0 -1
  81. package/dist/product-OT3XYMWD.cjs.map +0 -1
  82. /package/dist/{chunk-DGUM43GV.js.map → process-ZQV5M2TB.js.map} +0 -0
@@ -1,306 +1,6 @@
1
- import {
2
- logger,
3
- setLogLevel
4
- } from "./chunk-PCRQY47G.js";
5
-
6
- // src/normalize/blur-placeholder.ts
7
- import sharp from "sharp";
8
- async function generateBlurPlaceholder(imagePath) {
9
- try {
10
- const buffer = await sharp(imagePath).resize(8, 8, { fit: "inside" }).blur().png().toBuffer();
11
- return `data:image/png;base64,${buffer.toString("base64")}`;
12
- } catch {
13
- return void 0;
14
- }
15
- }
16
-
17
- // src/constants.ts
18
- var WORDS_PER_MINUTE = 200;
19
- var EXCERPT_MAX_LENGTH = 160;
20
- var DEFAULT_META_TITLE_MAX = 60;
21
- var DEFAULT_META_DESC_MAX = 120;
22
- var DEFAULT_FRESHNESS_MAX_AGE = "6m";
23
- var DEFAULT_RSS_LIMIT = 50;
24
- var DEFAULT_SITEMAP_PRIORITY = 0.7;
25
- var DEFAULT_SITEMAP_CHANGE_FREQ = "weekly";
26
- var OG_IMAGE_WIDTH = 1200;
27
- var OG_IMAGE_HEIGHT = 630;
28
- var OG_IMAGE_CONCURRENCY = 5;
29
-
30
- // src/normalize/excerpt.ts
31
- function stripMarkdown(text) {
32
- return text.replace(/```[\s\S]*?```/g, "").replace(/!\[.*?\]\(.*?\)/g, "").replace(/\[([^\]]*)\]\(.*?\)/g, "$1").replace(/^#{1,6}\s+/gm, "").replace(/\*{1,3}(.*?)\*{1,3}/g, "$1").replace(/_{1,3}(.*?)_{1,3}/g, "$1").replace(/`([^`]*)`/g, "$1").replace(/^>\s+/gm, "").replace(/^[-*_]{3,}\s*$/gm, "").replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim();
33
- }
34
- function generateExcerpt(body, maxLength = EXCERPT_MAX_LENGTH) {
35
- const clean = stripMarkdown(body);
36
- if (clean.length <= maxLength) {
37
- return clean;
38
- }
39
- const truncated = clean.slice(0, maxLength);
40
- const lastSpace = truncated.lastIndexOf(" ");
41
- if (lastSpace === -1) {
42
- return truncated.slice(0, maxLength);
43
- }
44
- return truncated.slice(0, lastSpace);
45
- }
46
-
47
- // src/normalize/description.ts
48
- function generateDescription(body, excerpt) {
49
- return excerpt ?? generateExcerpt(body);
50
- }
51
-
52
- // src/normalize/image-dimensions.ts
53
- import sharp2 from "sharp";
54
- async function getImageDimensions(imagePath) {
55
- try {
56
- const metadata = await sharp2(imagePath).metadata();
57
- if (metadata.width && metadata.height) {
58
- return { width: metadata.width, height: metadata.height };
59
- }
60
- return void 0;
61
- } catch {
62
- return void 0;
63
- }
64
- }
65
-
66
- // src/normalize/word-count.ts
67
- function calcWordCount(body) {
68
- return body.trim().split(/\s+/).filter(Boolean).length;
69
- }
70
-
71
- // src/normalize/reading-time.ts
72
- function calcReadingTime(body) {
73
- const words = calcWordCount(body);
74
- return Math.max(1, Math.ceil(words / WORDS_PER_MINUTE));
75
- }
76
-
77
- // src/normalize/slug.ts
78
- function generateSlug(title) {
79
- return title.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
80
- }
81
-
82
- // src/normalize/toc.ts
83
- function toAnchorSlug(text) {
84
- return text.toLowerCase().replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
85
- }
86
- function extractToc(body) {
87
- const headingRegex = /^(#{2,3})\s+(.+)$/gm;
88
- const items = [];
89
- let match;
90
- while ((match = headingRegex.exec(body)) !== null) {
91
- const depth = match[1].length;
92
- const text = match[2].trim();
93
- items.push({
94
- depth,
95
- text,
96
- slug: toAnchorSlug(text)
97
- });
98
- }
99
- return items;
100
- }
101
-
102
- // src/normalize/index.ts
103
- function toDate(value) {
104
- if (value === void 0) return void 0;
105
- if (value instanceof Date) return value;
106
- return new Date(value);
107
- }
108
- async function normalize(partial) {
109
- const { body, title } = partial;
110
- const slug = partial.slug || generateSlug(title);
111
- const wordCount = partial.wordCount ?? calcWordCount(body);
112
- const readingTime = partial.readingTime ?? calcReadingTime(body);
113
- const excerpt = partial.excerpt ?? generateExcerpt(body);
114
- const toc = partial.toc ?? extractToc(body);
115
- const description = partial.description ?? generateDescription(body, excerpt);
116
- const publishDate = toDate(partial.publishDate) ?? /* @__PURE__ */ new Date();
117
- const updateDate = toDate(partial.updateDate);
118
- const eventDate = toDate(partial.eventDate);
119
- let image = partial.image ? { ...partial.image } : void 0;
120
- if (image?.src) {
121
- if (!image.blurDataURL) {
122
- image.blurDataURL = await generateBlurPlaceholder(image.src);
123
- }
124
- if (!image.width || !image.height) {
125
- const dims = await getImageDimensions(image.src);
126
- if (dims) {
127
- image.width = dims.width;
128
- image.height = dims.height;
129
- }
130
- }
131
- }
132
- const entry = {
133
- slug,
134
- title,
135
- body,
136
- description,
137
- publishDate,
138
- readingTime,
139
- wordCount,
140
- excerpt,
141
- toc,
142
- ...updateDate && { updateDate },
143
- ...image && { image },
144
- ...partial.tags && { tags: partial.tags },
145
- ...partial.category && { category: partial.category },
146
- ...partial.author && { author: partial.author },
147
- ...partial.relatedEntries && { relatedEntries: partial.relatedEntries },
148
- ...partial.canonical && { canonical: partial.canonical },
149
- ...partial.noindex !== void 0 && { noindex: partial.noindex },
150
- ...partial.jsonLdType && { jsonLdType: partial.jsonLdType },
151
- ...partial.faqs && { faqs: partial.faqs },
152
- ...partial.steps && { steps: partial.steps },
153
- ...partial.price && { price: partial.price },
154
- ...partial.ingredients && { ingredients: partial.ingredients },
155
- ...partial.cookTime && { cookTime: partial.cookTime },
156
- ...eventDate && { eventDate },
157
- ...partial.eventLocation && { eventLocation: partial.eventLocation }
158
- };
159
- return entry;
160
- }
161
-
162
- // src/generate/sitemap.ts
163
- function generateSitemap(entries, config) {
164
- const siteUrl = config.site.url.replace(/\/$/, "");
165
- const defaultChangeFreq = config.sitemap?.changeFrequency ?? DEFAULT_SITEMAP_CHANGE_FREQ;
166
- const defaultPriority = config.sitemap?.priority ?? DEFAULT_SITEMAP_PRIORITY;
167
- const collectionKeys = Object.keys(config.collections);
168
- const fallbackBasePath = collectionKeys.length > 0 ? config.collections[collectionKeys[0]].basePath : "";
169
- const sitemapEntries = [];
170
- for (const entry of entries) {
171
- if (entry.noindex) continue;
172
- const basePath = (entry._basePath ?? fallbackBasePath).replace(/\/$/, "");
173
- const loc = `${siteUrl}${basePath}/${entry.slug}`;
174
- const lastmod = (entry.updateDate ?? entry.publishDate).toISOString();
175
- const sitemapEntry = {
176
- loc,
177
- lastmod,
178
- changefreq: defaultChangeFreq,
179
- priority: defaultPriority
180
- };
181
- if (entry.image) {
182
- sitemapEntry.images = [
183
- {
184
- loc: entry.image.src.startsWith("http") ? entry.image.src : `${siteUrl}${entry.image.src}`,
185
- title: entry.image.alt
186
- }
187
- ];
188
- }
189
- sitemapEntries.push(sitemapEntry);
190
- }
191
- if (config.sitemap?.additionalPaths) {
192
- for (const path of config.sitemap.additionalPaths) {
193
- sitemapEntries.push({
194
- loc: `${siteUrl}${path}`,
195
- changefreq: defaultChangeFreq,
196
- priority: defaultPriority
197
- });
198
- }
199
- }
200
- return sitemapEntries;
201
- }
202
- function renderSitemapXml(entries) {
203
- const hasImages = entries.some((e) => e.images && e.images.length > 0);
204
- const lines = [
205
- '<?xml version="1.0" encoding="UTF-8"?>',
206
- `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"${hasImages ? ' xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"' : ""}>`
207
- ];
208
- for (const entry of entries) {
209
- lines.push(" <url>");
210
- lines.push(` <loc>${escapeXml(entry.loc)}</loc>`);
211
- if (entry.lastmod) {
212
- lines.push(` <lastmod>${entry.lastmod}</lastmod>`);
213
- }
214
- if (entry.changefreq) {
215
- lines.push(` <changefreq>${entry.changefreq}</changefreq>`);
216
- }
217
- if (entry.priority !== void 0) {
218
- lines.push(` <priority>${entry.priority}</priority>`);
219
- }
220
- if (entry.images) {
221
- for (const img of entry.images) {
222
- lines.push(" <image:image>");
223
- lines.push(` <image:loc>${escapeXml(img.loc)}</image:loc>`);
224
- if (img.title) {
225
- lines.push(
226
- ` <image:title>${escapeXml(img.title)}</image:title>`
227
- );
228
- }
229
- lines.push(" </image:image>");
230
- }
231
- }
232
- lines.push(" </url>");
233
- }
234
- lines.push("</urlset>");
235
- return lines.join("\n");
236
- }
237
- function escapeXml(str) {
238
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
239
- }
240
-
241
- // src/generate/rss.ts
242
- function generateRss(entries, config) {
243
- const siteUrl = config.site.url.replace(/\/$/, "");
244
- const limit = config.rss?.limit ?? DEFAULT_RSS_LIMIT;
245
- const collectionKeys = Object.keys(config.collections);
246
- const fallbackBasePath = collectionKeys.length > 0 ? config.collections[collectionKeys[0]].basePath : "";
247
- const sorted = [...entries].filter((e) => !e.noindex).sort((a, b) => b.publishDate.getTime() - a.publishDate.getTime());
248
- const limited = sorted.slice(0, limit);
249
- return limited.map((entry) => {
250
- const basePath = (entry._basePath ?? fallbackBasePath).replace(/\/$/, "");
251
- const link = `${siteUrl}${basePath}/${entry.slug}`;
252
- return {
253
- title: entry.title,
254
- link,
255
- description: entry.description || entry.excerpt,
256
- pubDate: entry.publishDate.toUTCString(),
257
- guid: link,
258
- author: entry.author?.name
259
- };
260
- });
261
- }
262
- function renderRssXml(items, config) {
263
- const siteUrl = config.site.url.replace(/\/$/, "");
264
- const lines = [
265
- '<?xml version="1.0" encoding="UTF-8"?>',
266
- '<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">',
267
- " <channel>",
268
- ` <title>${escapeXml2(config.site.name)}</title>`,
269
- ` <link>${escapeXml2(siteUrl)}</link>`,
270
- ` <description>${escapeXml2(config.site.description ?? "")}</description>`,
271
- ` <language>${config.site.language ?? "en"}</language>`,
272
- ` <lastBuildDate>${(/* @__PURE__ */ new Date()).toUTCString()}</lastBuildDate>`
273
- ];
274
- if (config.rss?.path) {
275
- lines.push(
276
- ` <atom:link href="${escapeXml2(siteUrl + config.rss.path)}" rel="self" type="application/rss+xml" />`
277
- );
278
- }
279
- for (const item of items) {
280
- lines.push(" <item>");
281
- lines.push(` <title>${escapeXml2(item.title)}</title>`);
282
- lines.push(` <link>${escapeXml2(item.link)}</link>`);
283
- lines.push(
284
- ` <description>${escapeXml2(item.description)}</description>`
285
- );
286
- lines.push(` <pubDate>${item.pubDate}</pubDate>`);
287
- lines.push(` <guid isPermaLink="true">${escapeXml2(item.guid)}</guid>`);
288
- if (item.author) {
289
- lines.push(` <author>${escapeXml2(item.author)}</author>`);
290
- }
291
- lines.push(" </item>");
292
- }
293
- lines.push(" </channel>");
294
- lines.push("</rss>");
295
- return lines.join("\n");
296
- }
297
- function escapeXml2(str) {
298
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
299
- }
300
-
301
1
  // src/generate/json-ld/article.ts
302
2
  function generateArticleSchema(entry, site) {
303
- if (!entry.title || !entry.publishDate || !entry.author) {
3
+ if (!entry.title || !entry.publishDate) {
304
4
  return null;
305
5
  }
306
6
  const schema = {
@@ -309,17 +9,19 @@ function generateArticleSchema(entry, site) {
309
9
  headline: entry.title,
310
10
  description: entry.description || entry.excerpt,
311
11
  datePublished: entry.publishDate.toISOString(),
312
- author: {
313
- "@type": "Person",
314
- name: entry.author.name,
315
- ...entry.author.url && { url: entry.author.url }
316
- },
317
12
  publisher: {
318
13
  "@type": "Organization",
319
14
  name: site.name,
320
15
  ...site.url && { url: site.url }
321
16
  }
322
17
  };
18
+ if (entry.author) {
19
+ schema.author = {
20
+ "@type": "Person",
21
+ name: entry.author.name,
22
+ ...entry.author.url && { url: entry.author.url }
23
+ };
24
+ }
323
25
  if (entry.updateDate) {
324
26
  schema.dateModified = entry.updateDate.toISOString();
325
27
  }
@@ -337,25 +39,6 @@ function generateArticleSchema(entry, site) {
337
39
  return schema;
338
40
  }
339
41
 
340
- // src/generate/json-ld/faq.ts
341
- function generateFaqSchema(entry, _site) {
342
- if (!entry.faqs || entry.faqs.length === 0) {
343
- return null;
344
- }
345
- return {
346
- "@context": "https://schema.org",
347
- "@type": "FAQPage",
348
- mainEntity: entry.faqs.map((faq) => ({
349
- "@type": "Question",
350
- name: faq.question,
351
- acceptedAnswer: {
352
- "@type": "Answer",
353
- text: faq.answer
354
- }
355
- }))
356
- };
357
- }
358
-
359
42
  // src/generate/breadcrumbs.ts
360
43
  function generateBreadcrumbs(slug, title, siteUrl, basePath) {
361
44
  const url = siteUrl.replace(/\/$/, "");
@@ -394,7 +77,12 @@ function generateBreadcrumbs(slug, title, siteUrl, basePath) {
394
77
  // src/generate/json-ld/breadcrumb.ts
395
78
  function generateBreadcrumbSchema(entry, site, basePath) {
396
79
  const siteUrl = site.url.replace(/\/$/, "");
397
- const crumbs = generateBreadcrumbs(entry.slug, entry.title, siteUrl, basePath);
80
+ const crumbs = generateBreadcrumbs(
81
+ entry.slug,
82
+ entry.title,
83
+ siteUrl,
84
+ basePath
85
+ );
398
86
  return {
399
87
  "@context": "https://schema.org",
400
88
  "@type": "BreadcrumbList",
@@ -407,6 +95,54 @@ function generateBreadcrumbSchema(entry, site, basePath) {
407
95
  };
408
96
  }
409
97
 
98
+ // src/generate/json-ld/event.ts
99
+ function generateEventSchema(entry, site) {
100
+ if (!entry.eventDate) {
101
+ return null;
102
+ }
103
+ const schema = {
104
+ "@context": "https://schema.org",
105
+ "@type": "Event",
106
+ name: entry.title,
107
+ description: entry.description || entry.excerpt,
108
+ startDate: entry.eventDate.toISOString(),
109
+ organizer: {
110
+ "@type": "Organization",
111
+ name: site.name,
112
+ url: site.url
113
+ }
114
+ };
115
+ if (entry.eventLocation) {
116
+ schema.location = {
117
+ "@type": "Place",
118
+ name: entry.eventLocation
119
+ };
120
+ }
121
+ if (entry.image) {
122
+ schema.image = entry.image.src.startsWith("http") ? entry.image.src : `${site.url.replace(/\/$/, "")}${entry.image.src}`;
123
+ }
124
+ return schema;
125
+ }
126
+
127
+ // src/generate/json-ld/faq.ts
128
+ function generateFaqSchema(entry, _site) {
129
+ if (!entry.faqs || entry.faqs.length === 0) {
130
+ return null;
131
+ }
132
+ return {
133
+ "@context": "https://schema.org",
134
+ "@type": "FAQPage",
135
+ mainEntity: entry.faqs.map((faq) => ({
136
+ "@type": "Question",
137
+ name: faq.question,
138
+ acceptedAnswer: {
139
+ "@type": "Answer",
140
+ text: faq.answer
141
+ }
142
+ }))
143
+ };
144
+ }
145
+
410
146
  // src/generate/json-ld/how-to.ts
411
147
  function generateHowToSchema(entry, _site) {
412
148
  if (!entry.steps || entry.steps.length === 0) {
@@ -450,7 +186,7 @@ function generateProductSchema(entry, site) {
450
186
  }
451
187
 
452
188
  // src/generate/json-ld/recipe.ts
453
- function generateRecipeSchema(entry, _site) {
189
+ function generateRecipeSchema(entry, site) {
454
190
  if (!entry.ingredients || entry.ingredients.length === 0) {
455
191
  return null;
456
192
  }
@@ -470,35 +206,6 @@ function generateRecipeSchema(entry, _site) {
470
206
  name: entry.author.name
471
207
  };
472
208
  }
473
- if (entry.image) {
474
- schema.image = entry.image.src;
475
- }
476
- return schema;
477
- }
478
-
479
- // src/generate/json-ld/event.ts
480
- function generateEventSchema(entry, site) {
481
- if (!entry.eventDate) {
482
- return null;
483
- }
484
- const schema = {
485
- "@context": "https://schema.org",
486
- "@type": "Event",
487
- name: entry.title,
488
- description: entry.description || entry.excerpt,
489
- startDate: entry.eventDate.toISOString(),
490
- organizer: {
491
- "@type": "Organization",
492
- name: site.name,
493
- url: site.url
494
- }
495
- };
496
- if (entry.eventLocation) {
497
- schema.location = {
498
- "@type": "Place",
499
- name: entry.eventLocation
500
- };
501
- }
502
209
  if (entry.image) {
503
210
  schema.image = entry.image.src.startsWith("http") ? entry.image.src : `${site.url.replace(/\/$/, "")}${entry.image.src}`;
504
211
  }
@@ -544,8 +251,57 @@ function generateJsonLd(entries, config) {
544
251
  }
545
252
 
546
253
  // src/generate/og-images.ts
547
- import { writeFileSync, readFileSync, existsSync, mkdirSync, statSync } from "fs";
548
- import { join } from "path";
254
+ import {
255
+ existsSync,
256
+ mkdirSync,
257
+ readFileSync,
258
+ statSync,
259
+ writeFileSync
260
+ } from "fs";
261
+ import { dirname, join } from "path";
262
+
263
+ // src/constants.ts
264
+ var WORDS_PER_MINUTE = 200;
265
+ var EXCERPT_MAX_LENGTH = 160;
266
+ var DEFAULT_META_TITLE_MAX = 60;
267
+ var DEFAULT_META_DESC_MAX = 120;
268
+ var DEFAULT_FRESHNESS_MAX_AGE = "6m";
269
+ var DEFAULT_RSS_LIMIT = 50;
270
+ var DEFAULT_SITEMAP_PRIORITY = 0.7;
271
+ var DEFAULT_SITEMAP_CHANGE_FREQ = "weekly";
272
+ var OG_IMAGE_WIDTH = 1200;
273
+ var OG_IMAGE_HEIGHT = 630;
274
+ var OG_IMAGE_CONCURRENCY = 5;
275
+
276
+ // src/logger.ts
277
+ var LEVELS = {
278
+ silent: 0,
279
+ error: 1,
280
+ warn: 2,
281
+ info: 3,
282
+ debug: 4
283
+ };
284
+ var currentLevel = "info";
285
+ function setLogLevel(level) {
286
+ currentLevel = level;
287
+ }
288
+ function shouldLog(level) {
289
+ return LEVELS[level] <= LEVELS[currentLevel];
290
+ }
291
+ var logger = {
292
+ error(...args) {
293
+ if (shouldLog("error")) console.error("[vaza-content]", ...args);
294
+ },
295
+ warn(...args) {
296
+ if (shouldLog("warn")) console.warn("[vaza-content]", ...args);
297
+ },
298
+ info(...args) {
299
+ if (shouldLog("info")) console.log("[vaza-content]", ...args);
300
+ },
301
+ debug(...args) {
302
+ if (shouldLog("debug")) console.log("[vaza-content] [debug]", ...args);
303
+ }
304
+ };
549
305
 
550
306
  // src/utils/concurrency.ts
551
307
  async function pMap(items, fn, concurrency) {
@@ -599,7 +355,9 @@ async function generateOgImages(entries, config) {
599
355
  logger.debug("OG images: all cached, nothing to generate");
600
356
  return results;
601
357
  }
602
- logger.info(`Generating ${toGenerate.length} OG images (concurrency: ${concurrency})`);
358
+ logger.info(
359
+ `Generating ${toGenerate.length} OG images (concurrency: ${concurrency})`
360
+ );
603
361
  await pMap(
604
362
  toGenerate,
605
363
  async (entry) => {
@@ -623,7 +381,7 @@ async function generateOgImages(entries, config) {
623
381
  });
624
382
  const pngData = resvg.render();
625
383
  const pngBuffer = pngData.asPng();
626
- const parentDir = join(outputPath, "..");
384
+ const parentDir = dirname(outputPath);
627
385
  if (!existsSync(parentDir)) {
628
386
  mkdirSync(parentDir, { recursive: true });
629
387
  }
@@ -678,54 +436,28 @@ async function loadFont(config) {
678
436
  async function loadTemplate(template) {
679
437
  switch (template) {
680
438
  case "blog": {
681
- const mod = await import("./blog-7EEJJG26.js");
439
+ const mod = await import("./blog-L7HRY3QC.js");
682
440
  return mod.blogTemplate;
683
441
  }
684
442
  case "product": {
685
- const mod = await import("./product-NCUW3U72.js");
443
+ const mod = await import("./product-UQXUI5YL.js");
686
444
  return mod.productTemplate;
687
445
  }
688
446
  case "dark": {
689
- const mod = await import("./dark-6E36AKLN.js");
447
+ const mod = await import("./dark-SZOURAMM.js");
690
448
  return mod.darkTemplate;
691
449
  }
692
- case "minimal":
693
450
  default: {
694
- const mod = await import("./minimal-D2PRAVG6.js");
451
+ const mod = await import("./minimal-CGXF737F.js");
695
452
  return mod.minimalTemplate;
696
453
  }
697
454
  }
698
455
  }
699
456
 
700
- // src/generate/taxonomy.ts
701
- function generateTaxonomy(entries, _config) {
702
- const tags = {};
703
- const categories = {};
704
- for (const entry of entries) {
705
- if (entry.tags) {
706
- for (const tag of entry.tags) {
707
- const normalized = tag.toLowerCase().trim();
708
- if (!tags[normalized]) {
709
- tags[normalized] = [];
710
- }
711
- tags[normalized].push(entry.slug);
712
- }
713
- }
714
- if (entry.category) {
715
- const normalized = entry.category.toLowerCase().trim();
716
- if (!categories[normalized]) {
717
- categories[normalized] = [];
718
- }
719
- categories[normalized].push(entry.slug);
720
- }
721
- }
722
- return { tags, categories };
723
- }
724
-
725
457
  // src/generate/redirects.ts
726
458
  import { execSync } from "child_process";
727
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2 } from "fs";
728
- import { join as join2, dirname } from "path";
459
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
460
+ import { basename, dirname as dirname2, join as join2 } from "path";
729
461
 
730
462
  // src/utils/levenshtein.ts
731
463
  function levenshtein(a, b) {
@@ -813,79 +545,245 @@ function detectGitRenames() {
813
545
  }
814
546
  match = renameRegex.exec(output);
815
547
  }
816
- match = simpleRenameRegex.exec(output);
817
- while (match) {
818
- const oldPath = match[1].trim();
819
- const newPath = match[2].trim();
820
- const oldSlug = extractSlug(oldPath);
821
- const newSlug = extractSlug(newPath);
822
- if (oldSlug && newSlug && oldSlug !== newSlug) {
823
- redirects.push({
824
- from: oldSlug,
825
- to: newSlug,
826
- status: 301
827
- });
548
+ match = simpleRenameRegex.exec(output);
549
+ while (match) {
550
+ const oldPath = match[1].trim();
551
+ const newPath = match[2].trim();
552
+ const oldSlug = extractSlug(oldPath);
553
+ const newSlug = extractSlug(newPath);
554
+ if (oldSlug && newSlug && oldSlug !== newSlug) {
555
+ redirects.push({
556
+ from: oldSlug,
557
+ to: newSlug,
558
+ status: 301
559
+ });
560
+ }
561
+ match = simpleRenameRegex.exec(output);
562
+ }
563
+ } catch {
564
+ logger.debug(
565
+ "Git not available or not a git repo, skipping git rename detection"
566
+ );
567
+ }
568
+ return redirects;
569
+ }
570
+ function detectManifestChanges(entries) {
571
+ const redirects = [];
572
+ const manifestPath = join2(process.cwd(), MANIFEST_DIR, MANIFEST_FILE);
573
+ const currentSlugs = entries.map((e) => e.slug).sort();
574
+ if (existsSync2(manifestPath)) {
575
+ try {
576
+ const raw = readFileSync2(manifestPath, "utf-8");
577
+ const previousSlugs = JSON.parse(raw);
578
+ const currentSet = new Set(currentSlugs);
579
+ const previousSet = new Set(previousSlugs);
580
+ const removedSlugs = previousSlugs.filter((s) => !currentSet.has(s));
581
+ const addedSlugs = currentSlugs.filter((s) => !previousSet.has(s));
582
+ if (removedSlugs.length > 0 && addedSlugs.length > 0) {
583
+ const usedAdded = /* @__PURE__ */ new Set();
584
+ for (const removed of removedSlugs) {
585
+ let bestMatch = "";
586
+ let bestScore = 0;
587
+ for (const added of addedSlugs) {
588
+ if (usedAdded.has(added)) continue;
589
+ const score = similarity(removed, added);
590
+ if (score > bestScore) {
591
+ bestScore = score;
592
+ bestMatch = added;
593
+ }
594
+ }
595
+ if (bestMatch && bestScore >= MIN_SIMILARITY) {
596
+ usedAdded.add(bestMatch);
597
+ redirects.push({
598
+ from: removed,
599
+ to: bestMatch,
600
+ status: 301
601
+ });
602
+ logger.debug(
603
+ `Redirect: ${removed} -> ${bestMatch} (similarity: ${bestScore.toFixed(2)})`
604
+ );
605
+ }
606
+ }
607
+ }
608
+ } catch {
609
+ logger.warn("Corrupted slug manifest, will be overwritten");
610
+ }
611
+ }
612
+ const dir = dirname2(manifestPath);
613
+ if (!existsSync2(dir)) {
614
+ mkdirSync2(dir, { recursive: true });
615
+ }
616
+ writeFileSync2(manifestPath, JSON.stringify(currentSlugs, null, 2));
617
+ return redirects;
618
+ }
619
+ function extractSlug(filePath) {
620
+ const name = basename(filePath);
621
+ if (!name) return null;
622
+ return name.replace(/\.(mdx?|tsx?|jsx?)$/, "");
623
+ }
624
+
625
+ // src/generate/rss.ts
626
+ function generateRss(entries, config) {
627
+ const siteUrl = config.site.url.replace(/\/$/, "");
628
+ const limit = config.rss?.limit ?? DEFAULT_RSS_LIMIT;
629
+ const collectionKeys = Object.keys(config.collections);
630
+ const fallbackBasePath = collectionKeys.length > 0 ? config.collections[collectionKeys[0]].basePath : "";
631
+ const sorted = [...entries].filter((e) => !e.noindex).sort((a, b) => b.publishDate.getTime() - a.publishDate.getTime());
632
+ const limited = sorted.slice(0, limit);
633
+ return limited.map((entry) => {
634
+ const basePath = (entry._basePath ?? fallbackBasePath).replace(/\/$/, "");
635
+ const link = `${siteUrl}${basePath}/${entry.slug}`;
636
+ return {
637
+ title: entry.title,
638
+ link,
639
+ description: entry.description || entry.excerpt,
640
+ pubDate: entry.publishDate.toUTCString(),
641
+ guid: link,
642
+ author: entry.author?.name
643
+ };
644
+ });
645
+ }
646
+ function renderRssXml(items, config) {
647
+ const siteUrl = config.site.url.replace(/\/$/, "");
648
+ const lines = [
649
+ '<?xml version="1.0" encoding="UTF-8"?>',
650
+ '<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">',
651
+ " <channel>",
652
+ ` <title>${escapeXml(config.site.name)}</title>`,
653
+ ` <link>${escapeXml(siteUrl)}</link>`,
654
+ ` <description>${escapeXml(config.site.description ?? "")}</description>`,
655
+ ` <language>${config.site.language ?? "en"}</language>`,
656
+ ` <lastBuildDate>${(/* @__PURE__ */ new Date()).toUTCString()}</lastBuildDate>`
657
+ ];
658
+ if (config.rss?.path) {
659
+ lines.push(
660
+ ` <atom:link href="${escapeXml(siteUrl + config.rss.path)}" rel="self" type="application/rss+xml" />`
661
+ );
662
+ }
663
+ for (const item of items) {
664
+ lines.push(" <item>");
665
+ lines.push(` <title>${escapeXml(item.title)}</title>`);
666
+ lines.push(` <link>${escapeXml(item.link)}</link>`);
667
+ lines.push(
668
+ ` <description>${escapeXml(item.description)}</description>`
669
+ );
670
+ lines.push(` <pubDate>${item.pubDate}</pubDate>`);
671
+ lines.push(` <guid isPermaLink="true">${escapeXml(item.guid)}</guid>`);
672
+ if (item.author) {
673
+ lines.push(` <author>${escapeXml(item.author)}</author>`);
674
+ }
675
+ lines.push(" </item>");
676
+ }
677
+ lines.push(" </channel>");
678
+ lines.push("</rss>");
679
+ return lines.join("\n");
680
+ }
681
+ function escapeXml(str) {
682
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
683
+ }
684
+
685
+ // src/generate/sitemap.ts
686
+ function generateSitemap(entries, config) {
687
+ const siteUrl = config.site.url.replace(/\/$/, "");
688
+ const defaultChangeFreq = config.sitemap?.changeFrequency ?? DEFAULT_SITEMAP_CHANGE_FREQ;
689
+ const defaultPriority = config.sitemap?.priority ?? DEFAULT_SITEMAP_PRIORITY;
690
+ const collectionKeys = Object.keys(config.collections);
691
+ const fallbackBasePath = collectionKeys.length > 0 ? config.collections[collectionKeys[0]].basePath : "";
692
+ const sitemapEntries = [];
693
+ for (const entry of entries) {
694
+ if (entry.noindex) continue;
695
+ const basePath = (entry._basePath ?? fallbackBasePath).replace(/\/$/, "");
696
+ const loc = `${siteUrl}${basePath}/${entry.slug}`;
697
+ const lastmod = (entry.updateDate ?? entry.publishDate).toISOString();
698
+ const sitemapEntry = {
699
+ loc,
700
+ lastmod,
701
+ changefreq: defaultChangeFreq,
702
+ priority: defaultPriority
703
+ };
704
+ if (entry.image) {
705
+ sitemapEntry.images = [
706
+ {
707
+ loc: entry.image.src.startsWith("http") ? entry.image.src : `${siteUrl}${entry.image.src}`,
708
+ title: entry.image.alt
709
+ }
710
+ ];
711
+ }
712
+ sitemapEntries.push(sitemapEntry);
713
+ }
714
+ if (config.sitemap?.additionalPaths) {
715
+ for (const path of config.sitemap.additionalPaths) {
716
+ sitemapEntries.push({
717
+ loc: `${siteUrl}${path}`,
718
+ changefreq: defaultChangeFreq,
719
+ priority: defaultPriority
720
+ });
721
+ }
722
+ }
723
+ return sitemapEntries;
724
+ }
725
+ function renderSitemapXml(entries) {
726
+ const hasImages = entries.some((e) => e.images && e.images.length > 0);
727
+ const lines = [
728
+ '<?xml version="1.0" encoding="UTF-8"?>',
729
+ `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"${hasImages ? ' xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"' : ""}>`
730
+ ];
731
+ for (const entry of entries) {
732
+ lines.push(" <url>");
733
+ lines.push(` <loc>${escapeXml2(entry.loc)}</loc>`);
734
+ if (entry.lastmod) {
735
+ lines.push(` <lastmod>${entry.lastmod}</lastmod>`);
736
+ }
737
+ if (entry.changefreq) {
738
+ lines.push(` <changefreq>${entry.changefreq}</changefreq>`);
739
+ }
740
+ if (entry.priority !== void 0) {
741
+ lines.push(` <priority>${entry.priority}</priority>`);
742
+ }
743
+ if (entry.images) {
744
+ for (const img of entry.images) {
745
+ lines.push(" <image:image>");
746
+ lines.push(` <image:loc>${escapeXml2(img.loc)}</image:loc>`);
747
+ if (img.title) {
748
+ lines.push(
749
+ ` <image:title>${escapeXml2(img.title)}</image:title>`
750
+ );
751
+ }
752
+ lines.push(" </image:image>");
828
753
  }
829
- match = simpleRenameRegex.exec(output);
830
754
  }
831
- } catch {
832
- logger.debug("Git not available or not a git repo, skipping git rename detection");
755
+ lines.push(" </url>");
833
756
  }
834
- return redirects;
757
+ lines.push("</urlset>");
758
+ return lines.join("\n");
835
759
  }
836
- function detectManifestChanges(entries) {
837
- const redirects = [];
838
- const manifestPath = join2(process.cwd(), MANIFEST_DIR, MANIFEST_FILE);
839
- const currentSlugs = entries.map((e) => e.slug).sort();
840
- if (existsSync2(manifestPath)) {
841
- try {
842
- const raw = readFileSync2(manifestPath, "utf-8");
843
- const previousSlugs = JSON.parse(raw);
844
- const currentSet = new Set(currentSlugs);
845
- const previousSet = new Set(previousSlugs);
846
- const removedSlugs = previousSlugs.filter((s) => !currentSet.has(s));
847
- const addedSlugs = currentSlugs.filter((s) => !previousSet.has(s));
848
- if (removedSlugs.length > 0 && addedSlugs.length > 0) {
849
- const usedAdded = /* @__PURE__ */ new Set();
850
- for (const removed of removedSlugs) {
851
- let bestMatch = "";
852
- let bestScore = 0;
853
- for (const added of addedSlugs) {
854
- if (usedAdded.has(added)) continue;
855
- const score = similarity(removed, added);
856
- if (score > bestScore) {
857
- bestScore = score;
858
- bestMatch = added;
859
- }
860
- }
861
- if (bestMatch && bestScore >= MIN_SIMILARITY) {
862
- usedAdded.add(bestMatch);
863
- redirects.push({
864
- from: removed,
865
- to: bestMatch,
866
- status: 301
867
- });
868
- logger.debug(
869
- `Redirect: ${removed} -> ${bestMatch} (similarity: ${bestScore.toFixed(2)})`
870
- );
871
- }
760
+ function escapeXml2(str) {
761
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
762
+ }
763
+
764
+ // src/generate/taxonomy.ts
765
+ function generateTaxonomy(entries, _config) {
766
+ const tags = {};
767
+ const categories = {};
768
+ for (const entry of entries) {
769
+ if (entry.tags) {
770
+ for (const tag of entry.tags) {
771
+ const normalized = tag.toLowerCase().trim();
772
+ if (!tags[normalized]) {
773
+ tags[normalized] = [];
872
774
  }
775
+ tags[normalized].push(entry.slug);
873
776
  }
874
- } catch {
875
- logger.warn("Corrupted slug manifest, will be overwritten");
777
+ }
778
+ if (entry.category) {
779
+ const normalized = entry.category.toLowerCase().trim();
780
+ if (!categories[normalized]) {
781
+ categories[normalized] = [];
782
+ }
783
+ categories[normalized].push(entry.slug);
876
784
  }
877
785
  }
878
- const dir = dirname(manifestPath);
879
- if (!existsSync2(dir)) {
880
- mkdirSync2(dir, { recursive: true });
881
- }
882
- writeFileSync2(manifestPath, JSON.stringify(currentSlugs, null, 2));
883
- return redirects;
884
- }
885
- function extractSlug(filePath) {
886
- const name = filePath.split("/").pop();
887
- if (!name) return null;
888
- return name.replace(/\.(mdx?|tsx?|jsx?)$/, "");
786
+ return { tags, categories };
889
787
  }
890
788
 
891
789
  // src/integrity/alt-text.ts
@@ -943,14 +841,14 @@ function checkBrokenLinks(entries, severity = "warn") {
943
841
  }
944
842
 
945
843
  // src/integrity/duplicate-content.ts
946
- function normalize2(title) {
844
+ function normalize(title) {
947
845
  return title.toLowerCase().replace(/[^\w\s]/g, "").replace(/\s+/g, " ").trim();
948
846
  }
949
847
  function checkDuplicateContent(entries, severity = "warn") {
950
848
  const issues = [];
951
849
  const seen = /* @__PURE__ */ new Map();
952
850
  for (const entry of entries) {
953
- const key = normalize2(entry.title);
851
+ const key = normalize(entry.title);
954
852
  if (!key) continue;
955
853
  const existing = seen.get(key);
956
854
  if (existing) {
@@ -1136,6 +1034,149 @@ function checkIntegrity(entries, config) {
1136
1034
  return { issues, summary: { errors, warnings } };
1137
1035
  }
1138
1036
 
1037
+ // src/normalize/blur-placeholder.ts
1038
+ import sharp from "sharp";
1039
+ async function generateBlurPlaceholder(imagePath) {
1040
+ try {
1041
+ const buffer = await sharp(imagePath).resize(8, 8, { fit: "inside" }).blur().png().toBuffer();
1042
+ return `data:image/png;base64,${buffer.toString("base64")}`;
1043
+ } catch {
1044
+ return void 0;
1045
+ }
1046
+ }
1047
+
1048
+ // src/normalize/excerpt.ts
1049
+ function stripMarkdown(text) {
1050
+ return text.replace(/```[\s\S]*?```/g, "").replace(/!\[.*?\]\(.*?\)/g, "").replace(/\[([^\]]*)\]\(.*?\)/g, "$1").replace(/^#{1,6}\s+/gm, "").replace(/\*{1,3}(.*?)\*{1,3}/g, "$1").replace(/_{1,3}(.*?)_{1,3}/g, "$1").replace(/`([^`]*)`/g, "$1").replace(/^>\s+/gm, "").replace(/^[-*_]{3,}\s*$/gm, "").replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim();
1051
+ }
1052
+ function generateExcerpt(body, maxLength = EXCERPT_MAX_LENGTH) {
1053
+ const clean = stripMarkdown(body);
1054
+ if (clean.length <= maxLength) {
1055
+ return clean;
1056
+ }
1057
+ const truncated = clean.slice(0, maxLength);
1058
+ const lastSpace = truncated.lastIndexOf(" ");
1059
+ if (lastSpace === -1) {
1060
+ return truncated.slice(0, maxLength);
1061
+ }
1062
+ return truncated.slice(0, lastSpace);
1063
+ }
1064
+
1065
+ // src/normalize/description.ts
1066
+ function generateDescription(body, excerpt) {
1067
+ return excerpt ?? generateExcerpt(body);
1068
+ }
1069
+
1070
+ // src/normalize/image-dimensions.ts
1071
+ import sharp2 from "sharp";
1072
+ async function getImageDimensions(imagePath) {
1073
+ try {
1074
+ const metadata = await sharp2(imagePath).metadata();
1075
+ if (metadata.width && metadata.height) {
1076
+ return { width: metadata.width, height: metadata.height };
1077
+ }
1078
+ return void 0;
1079
+ } catch {
1080
+ return void 0;
1081
+ }
1082
+ }
1083
+
1084
+ // src/normalize/word-count.ts
1085
+ function calcWordCount(body) {
1086
+ return body.trim().split(/\s+/).filter(Boolean).length;
1087
+ }
1088
+
1089
+ // src/normalize/reading-time.ts
1090
+ function calcReadingTime(body) {
1091
+ const words = calcWordCount(body);
1092
+ return Math.max(1, Math.ceil(words / WORDS_PER_MINUTE));
1093
+ }
1094
+
1095
+ // src/normalize/slug.ts
1096
+ function generateSlug(title) {
1097
+ return title.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
1098
+ }
1099
+
1100
+ // src/normalize/toc.ts
1101
+ function toAnchorSlug(text) {
1102
+ return text.toLowerCase().replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
1103
+ }
1104
+ function extractToc(body) {
1105
+ const headingRegex = /^(#{2,3})\s+(.+)$/gm;
1106
+ const items = [];
1107
+ let match;
1108
+ while ((match = headingRegex.exec(body)) !== null) {
1109
+ const depth = match[1].length;
1110
+ const text = match[2].trim();
1111
+ items.push({
1112
+ depth,
1113
+ text,
1114
+ slug: toAnchorSlug(text)
1115
+ });
1116
+ }
1117
+ return items;
1118
+ }
1119
+
1120
+ // src/normalize/index.ts
1121
+ function toDate(value) {
1122
+ if (value === void 0) return void 0;
1123
+ if (value instanceof Date) return value;
1124
+ return new Date(value);
1125
+ }
1126
+ async function normalize2(partial) {
1127
+ const { body, title } = partial;
1128
+ const slug = partial.slug || generateSlug(title);
1129
+ const wordCount = partial.wordCount ?? calcWordCount(body);
1130
+ const readingTime = partial.readingTime ?? calcReadingTime(body);
1131
+ const excerpt = partial.excerpt ?? generateExcerpt(body);
1132
+ const toc = partial.toc ?? extractToc(body);
1133
+ const description = partial.description ?? generateDescription(body, excerpt);
1134
+ const publishDate = toDate(partial.publishDate) ?? /* @__PURE__ */ new Date();
1135
+ const updateDate = toDate(partial.updateDate);
1136
+ const eventDate = toDate(partial.eventDate);
1137
+ const image = partial.image ? { ...partial.image } : void 0;
1138
+ if (image?.src) {
1139
+ if (!image.blurDataURL) {
1140
+ image.blurDataURL = await generateBlurPlaceholder(image.src);
1141
+ }
1142
+ if (!image.width || !image.height) {
1143
+ const dims = await getImageDimensions(image.src);
1144
+ if (dims) {
1145
+ image.width = dims.width;
1146
+ image.height = dims.height;
1147
+ }
1148
+ }
1149
+ }
1150
+ const entry = {
1151
+ slug,
1152
+ title,
1153
+ body,
1154
+ description,
1155
+ publishDate,
1156
+ readingTime,
1157
+ wordCount,
1158
+ excerpt,
1159
+ toc,
1160
+ ...updateDate && { updateDate },
1161
+ ...image && { image },
1162
+ ...partial.tags && { tags: partial.tags },
1163
+ ...partial.category && { category: partial.category },
1164
+ ...partial.author && { author: partial.author },
1165
+ ...partial.relatedEntries && { relatedEntries: partial.relatedEntries },
1166
+ ...partial.canonical && { canonical: partial.canonical },
1167
+ ...partial.noindex !== void 0 && { noindex: partial.noindex },
1168
+ ...partial.jsonLdType && { jsonLdType: partial.jsonLdType },
1169
+ ...partial.faqs && { faqs: partial.faqs },
1170
+ ...partial.steps && { steps: partial.steps },
1171
+ ...partial.price && { price: partial.price },
1172
+ ...partial.ingredients && { ingredients: partial.ingredients },
1173
+ ...partial.cookTime && { cookTime: partial.cookTime },
1174
+ ...eventDate && { eventDate },
1175
+ ...partial.eventLocation && { eventLocation: partial.eventLocation }
1176
+ };
1177
+ return entry;
1178
+ }
1179
+
1139
1180
  // src/process.ts
1140
1181
  async function processCollections(config) {
1141
1182
  if (config.logLevel) {
@@ -1147,7 +1188,7 @@ async function processCollections(config) {
1147
1188
  const rawEntries = await collection.entries();
1148
1189
  const partialEntries = rawEntries.map(collection.map);
1149
1190
  for (const partial of partialEntries) {
1150
- const normalized = await normalize(partial);
1191
+ const normalized = await normalize2(partial);
1151
1192
  normalized._collection = name;
1152
1193
  normalized._basePath = collection.basePath;
1153
1194
  allEntries.push(normalized);
@@ -1192,9 +1233,7 @@ function printIntegrityReport(report) {
1192
1233
  }
1193
1234
  }
1194
1235
  logger.info("\u2500".repeat(50));
1195
- logger.info(
1196
- `${summary.errors} error(s), ${summary.warnings} warning(s)`
1197
- );
1236
+ logger.info(`${summary.errors} error(s), ${summary.warnings} warning(s)`);
1198
1237
  if (summary.errors > 0) {
1199
1238
  throw new Error(
1200
1239
  `[vaza-content] Build failed: ${summary.errors} integrity error(s) found.`
@@ -1209,17 +1248,19 @@ export {
1209
1248
  DEFAULT_RSS_LIMIT,
1210
1249
  DEFAULT_SITEMAP_PRIORITY,
1211
1250
  DEFAULT_SITEMAP_CHANGE_FREQ,
1212
- normalize,
1213
- generateSitemap,
1214
- renderSitemapXml,
1215
- generateRss,
1216
- renderRssXml,
1217
1251
  generateBreadcrumbs,
1218
1252
  generateJsonLd,
1253
+ setLogLevel,
1254
+ logger,
1219
1255
  generateOgImages,
1220
- generateTaxonomy,
1221
1256
  detectRedirects,
1257
+ generateRss,
1258
+ renderRssXml,
1259
+ generateSitemap,
1260
+ renderSitemapXml,
1261
+ generateTaxonomy,
1222
1262
  checkIntegrity,
1263
+ normalize2 as normalize,
1223
1264
  processCollections
1224
1265
  };
1225
- //# sourceMappingURL=chunk-FALSVGPG.js.map
1266
+ //# sourceMappingURL=chunk-OKXBDPYF.js.map