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.
- package/dist/adapters/astro/index.cjs +9 -15
- package/dist/adapters/astro/index.cjs.map +1 -1
- package/dist/adapters/astro/index.d.cts +4 -3
- package/dist/adapters/astro/index.d.ts +4 -3
- package/dist/adapters/astro/index.js +6 -12
- package/dist/adapters/astro/index.js.map +1 -1
- package/dist/adapters/next/index.cjs +11 -12
- package/dist/adapters/next/index.cjs.map +1 -1
- package/dist/adapters/next/index.d.cts +5 -3
- package/dist/adapters/next/index.d.ts +5 -3
- package/dist/adapters/next/index.js +8 -9
- package/dist/adapters/next/index.js.map +1 -1
- package/dist/adapters/nuxt/index.cjs +22 -22
- package/dist/adapters/nuxt/index.cjs.map +1 -1
- package/dist/adapters/nuxt/index.d.cts +11 -7
- package/dist/adapters/nuxt/index.d.ts +11 -7
- package/dist/adapters/nuxt/index.js +13 -13
- package/dist/adapters/nuxt/index.js.map +1 -1
- package/dist/adapters/sveltekit/index.cjs +9 -15
- package/dist/adapters/sveltekit/index.cjs.map +1 -1
- package/dist/adapters/sveltekit/index.d.cts +3 -2
- package/dist/adapters/sveltekit/index.d.ts +3 -2
- package/dist/adapters/sveltekit/index.js +5 -11
- package/dist/adapters/sveltekit/index.js.map +1 -1
- package/dist/{blog-7EEJJG26.js → blog-L7HRY3QC.js} +2 -4
- package/dist/{blog-7EEJJG26.js.map → blog-L7HRY3QC.js.map} +1 -1
- package/dist/{blog-Z3R5GOMP.cjs → blog-SEXXJJSV.cjs} +3 -5
- package/dist/blog-SEXXJJSV.cjs.map +1 -0
- package/dist/{chunk-YV2ZYIAD.cjs → chunk-H3D7F4TA.cjs} +544 -503
- package/dist/chunk-H3D7F4TA.cjs.map +1 -0
- package/dist/{chunk-FALSVGPG.js → chunk-OKXBDPYF.js} +511 -470
- package/dist/chunk-OKXBDPYF.js.map +1 -0
- package/dist/cli/index.cjs +30 -24
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +29 -23
- package/dist/cli/index.js.map +1 -1
- package/dist/{dark-EX2GRAYK.cjs → dark-66ZWYLT7.cjs} +2 -4
- package/dist/dark-66ZWYLT7.cjs.map +1 -0
- package/dist/{dark-6E36AKLN.js → dark-SZOURAMM.js} +1 -3
- package/dist/{dark-6E36AKLN.js.map → dark-SZOURAMM.js.map} +1 -1
- package/dist/index.cjs +8 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -29
- package/dist/index.d.ts +29 -29
- package/dist/index.js +3 -6
- package/dist/index.js.map +1 -1
- package/dist/{minimal-D2PRAVG6.js → minimal-CGXF737F.js} +1 -3
- package/dist/{minimal-D2PRAVG6.js.map → minimal-CGXF737F.js.map} +1 -1
- package/dist/{minimal-RHOK4XEZ.cjs → minimal-XTTHXE3T.cjs} +2 -4
- package/dist/minimal-XTTHXE3T.cjs.map +1 -0
- package/dist/process-VXDWM664.cjs +7 -0
- package/dist/process-VXDWM664.cjs.map +1 -0
- package/dist/process-ZQV5M2TB.js +7 -0
- package/dist/{product-OT3XYMWD.cjs → product-BY3GVQGV.cjs} +2 -4
- package/dist/product-BY3GVQGV.cjs.map +1 -0
- package/dist/{product-NCUW3U72.js → product-UQXUI5YL.js} +1 -3
- package/dist/{product-NCUW3U72.js.map → product-UQXUI5YL.js.map} +1 -1
- package/dist/{types-CgaidvaB.d.cts → types-DAfWIHiD.d.cts} +1 -1
- package/dist/{types-CgaidvaB.d.ts → types-DAfWIHiD.d.ts} +1 -1
- package/package.json +6 -4
- package/dist/blog-Z3R5GOMP.cjs.map +0 -1
- package/dist/chunk-DGUM43GV.js +0 -11
- package/dist/chunk-FALSVGPG.js.map +0 -1
- package/dist/chunk-JEQ2X3Z6.cjs +0 -11
- package/dist/chunk-JEQ2X3Z6.cjs.map +0 -1
- package/dist/chunk-PCRQY47G.js +0 -35
- package/dist/chunk-PCRQY47G.js.map +0 -1
- package/dist/chunk-WOCXEBQC.cjs +0 -35
- package/dist/chunk-WOCXEBQC.cjs.map +0 -1
- package/dist/chunk-YV2ZYIAD.cjs.map +0 -1
- package/dist/dark-EX2GRAYK.cjs.map +0 -1
- package/dist/logger-7WBTEDED.cjs +0 -10
- package/dist/logger-7WBTEDED.cjs.map +0 -1
- package/dist/logger-BGP7C274.js +0 -10
- package/dist/logger-BGP7C274.js.map +0 -1
- package/dist/minimal-RHOK4XEZ.cjs.map +0 -1
- package/dist/process-B4PJ6CWC.cjs +0 -9
- package/dist/process-B4PJ6CWC.cjs.map +0 -1
- package/dist/process-KSSXQJE6.js +0 -9
- package/dist/process-KSSXQJE6.js.map +0 -1
- package/dist/product-OT3XYMWD.cjs.map +0 -1
- /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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
299
|
-
}
|
|
300
|
-
|
|
301
1
|
// src/generate/json-ld/article.ts
|
|
302
2
|
function generateArticleSchema(entry, site) {
|
|
303
|
-
if (!entry.title || !entry.publishDate
|
|
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(
|
|
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,
|
|
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 {
|
|
548
|
-
|
|
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(
|
|
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 =
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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 {
|
|
728
|
-
import { join as join2
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
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
|
-
|
|
832
|
-
logger.debug("Git not available or not a git repo, skipping git rename detection");
|
|
755
|
+
lines.push(" </url>");
|
|
833
756
|
}
|
|
834
|
-
|
|
757
|
+
lines.push("</urlset>");
|
|
758
|
+
return lines.join("\n");
|
|
835
759
|
}
|
|
836
|
-
function
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
const
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
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
|
-
}
|
|
875
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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-
|
|
1266
|
+
//# sourceMappingURL=chunk-OKXBDPYF.js.map
|