@bagelink/blox 1.12.21 → 1.12.23

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 (59) hide show
  1. package/dist/CmsPageView.vue.d.ts.map +1 -1
  2. package/dist/PreviewApp-C2T03Jm9.cjs +4 -0
  3. package/dist/PreviewApp-CmThrLvv.js +4 -0
  4. package/dist/PreviewApp.vue.d.ts.map +1 -1
  5. package/dist/PreviewApp.vue_vue_type_style_index_0_lang-B0N5QbfS.js +157 -0
  6. package/dist/PreviewApp.vue_vue_type_style_index_0_lang-BTuE4GmT.cjs +156 -0
  7. package/dist/api/index.d.ts.map +1 -1
  8. package/dist/bridge.d.ts +1 -0
  9. package/dist/bridge.d.ts.map +1 -1
  10. package/dist/core-C3Iu5qa2.js +460 -0
  11. package/dist/core-_fnHoEZN.cjs +459 -0
  12. package/dist/core.d.ts.map +1 -1
  13. package/dist/createBloxApp.d.ts +107 -0
  14. package/dist/createBloxApp.d.ts.map +1 -0
  15. package/dist/defineBlock.d.ts +2 -0
  16. package/dist/defineBlock.d.ts.map +1 -1
  17. package/dist/index.cjs +79 -572
  18. package/dist/index.d.ts +2 -0
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.mjs +78 -570
  21. package/dist/{prerender-6jE_obPj.cjs → prerender-Bi7YtzSp.cjs} +246 -6
  22. package/dist/prerender-D3Q4jKXm.js +522 -0
  23. package/dist/schema.d.ts +2 -0
  24. package/dist/schema.d.ts.map +1 -1
  25. package/dist/ssg/cli.cjs +48 -5
  26. package/dist/ssg/cli.mjs +48 -5
  27. package/dist/ssg/client.cjs +50 -3
  28. package/dist/ssg/client.d.ts +2 -1
  29. package/dist/ssg/client.d.ts.map +1 -1
  30. package/dist/ssg/client.mjs +49 -2
  31. package/dist/ssg/cms-routes.d.ts +21 -4
  32. package/dist/ssg/cms-routes.d.ts.map +1 -1
  33. package/dist/ssg/collection-cache.d.ts +53 -0
  34. package/dist/ssg/collection-cache.d.ts.map +1 -0
  35. package/dist/ssg/constants.d.ts +4 -0
  36. package/dist/ssg/constants.d.ts.map +1 -1
  37. package/dist/ssg/createSSREntry.d.ts +73 -0
  38. package/dist/ssg/createSSREntry.d.ts.map +1 -0
  39. package/dist/ssg/index.cjs +138 -6
  40. package/dist/ssg/index.d.ts +10 -3
  41. package/dist/ssg/index.d.ts.map +1 -1
  42. package/dist/ssg/index.mjs +124 -12
  43. package/dist/ssg/prerender.d.ts +19 -1
  44. package/dist/ssg/prerender.d.ts.map +1 -1
  45. package/dist/ssg/render-resolved-page.d.ts +13 -3
  46. package/dist/ssg/render-resolved-page.d.ts.map +1 -1
  47. package/dist/ssg/seo.d.ts +66 -0
  48. package/dist/ssg/seo.d.ts.map +1 -0
  49. package/dist/style.css +20 -0
  50. package/dist/vite-plugin.cjs +142 -3
  51. package/dist/vite-plugin.d.ts +22 -21
  52. package/dist/vite-plugin.d.ts.map +1 -1
  53. package/dist/vite-plugin.mjs +142 -3
  54. package/package.json +4 -1
  55. package/dist/PreviewApp-BZNzZkit.js +0 -4
  56. package/dist/PreviewApp-C1WvJWI4.cjs +0 -4
  57. package/dist/constants-BIbQhd3z.js +0 -4
  58. package/dist/constants-fZvybj0k.cjs +0 -3
  59. package/dist/prerender-DYmDaqcz.js +0 -282
@@ -0,0 +1,522 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import process from "node:process";
4
+ import { pathToFileURL } from "node:url";
5
+ async function fetchCmsSiteData(apiBase, websiteName) {
6
+ const sitesRes = await fetch(`${apiBase}/cms/websites`);
7
+ const sitesData = await sitesRes.json();
8
+ const sites = Array.isArray(sitesData) ? sitesData : sitesData.data ?? [];
9
+ const site = sites.find((s) => s.name === websiteName);
10
+ if ((site == null ? void 0 : site.id) == null || site.id === "") throw new Error(`Website "${websiteName}" not found at ${apiBase}`);
11
+ let website = site;
12
+ try {
13
+ const wRes = await fetch(`${apiBase}/cms/websites/${site.id}`);
14
+ if (wRes.ok) website = await wRes.json();
15
+ } catch {
16
+ }
17
+ const pagesRes = await fetch(`${apiBase}/cms/websites/${site.id}/pages?locale=${website.default_locale || "en"}`);
18
+ const pagesData = await pagesRes.json();
19
+ const pages = Array.isArray(pagesData) ? pagesData : pagesData.data ?? [];
20
+ const paths = await expandRoutes(apiBase, pages);
21
+ let redirects = [];
22
+ try {
23
+ const rRes = await fetch(`${apiBase}/cms/websites/${site.id}/redirects`);
24
+ if (rRes.ok) {
25
+ const rData = await rRes.json();
26
+ const items = Array.isArray(rData) ? rData : rData.data ?? [];
27
+ redirects = items.filter((r) => r.from_path && r.to_path);
28
+ }
29
+ } catch {
30
+ }
31
+ const collections = extractCollectionRefs(pages);
32
+ return { websiteId: site.id, website, paths, redirects, collections };
33
+ }
34
+ async function fetchCmsPrerenderPaths(apiBase, websiteName) {
35
+ const { paths } = await fetchCmsSiteData(apiBase, websiteName);
36
+ return paths;
37
+ }
38
+ async function expandRoutes(apiBase, pages) {
39
+ const routes = [];
40
+ for (const page of pages) {
41
+ const slug = page.slug ?? "/";
42
+ if (page.status && page.status !== "published") continue;
43
+ if (slug.includes(":") && page.data_bindings) {
44
+ for (const [, binding] of Object.entries(page.data_bindings)) {
45
+ const b = binding;
46
+ if (b.adapter === "datastore" && b.collection != null && b.collection !== "" && b.store != null && b.store !== "" && b.bind_by != null && b.bind_by !== "") {
47
+ try {
48
+ const itemsRes = await fetch(
49
+ `${apiBase}/datastore/${b.store}/collections/${b.collection}?limit=200`
50
+ );
51
+ const itemsData = await itemsRes.json();
52
+ const items = Array.isArray(itemsData) ? itemsData : itemsData.data ?? [];
53
+ for (const item of items) {
54
+ const bindValue = item[b.bind_by];
55
+ if (bindValue != null && bindValue !== "") {
56
+ const concrete = slug.replace(
57
+ /:([a-z_]+)/g,
58
+ (_m, p) => p === b.bind_by ? String(bindValue) : String(item[p] ?? "")
59
+ );
60
+ routes.push(concrete);
61
+ }
62
+ }
63
+ } catch (e) {
64
+ console.warn(` [blox-ssg] Could not expand ${slug}: ${e.message}`);
65
+ }
66
+ }
67
+ }
68
+ } else if (!slug.includes(":")) {
69
+ routes.push(slug === "" ? "/" : slug);
70
+ }
71
+ }
72
+ return [...new Set(routes)];
73
+ }
74
+ function extractCollectionRefs(pages) {
75
+ const seen = /* @__PURE__ */ new Set();
76
+ const refs = [];
77
+ for (const page of pages) {
78
+ if (!page.data_bindings) continue;
79
+ for (const binding of Object.values(page.data_bindings)) {
80
+ const b = binding;
81
+ if (b.adapter === "datastore" && b.collection != null && b.collection !== "" && b.store != null && b.store !== "") {
82
+ const key = `${b.store}/${b.collection}`;
83
+ if (!seen.has(key)) {
84
+ seen.add(key);
85
+ refs.push({ store: b.store, collection: b.collection });
86
+ }
87
+ }
88
+ }
89
+ }
90
+ return refs;
91
+ }
92
+ function buildPageHead(options) {
93
+ const { url, resolvedData, website, stateScript } = options;
94
+ const parts = [];
95
+ const meta = (website == null ? void 0 : website.meta) ?? {};
96
+ const page = resolvedData == null ? void 0 : resolvedData.page;
97
+ const ctx = extractPrimaryContext(resolvedData == null ? void 0 : resolvedData.contexts);
98
+ const title = (page == null ? void 0 : page.meta_title) || ctx.title || ((page == null ? void 0 : page.title) && meta.og_site_name ? `${page.title} · ${meta.og_site_name}` : null) || (page == null ? void 0 : page.title) || meta.default_meta_title;
99
+ if (title) {
100
+ const fullTitle = ctx.title && meta.og_site_name && !ctx.title.includes(meta.og_site_name) ? `${esc(ctx.title)} · ${esc(meta.og_site_name)}` : esc(title);
101
+ parts.push(`<title>${fullTitle}</title>`);
102
+ }
103
+ const description = (page == null ? void 0 : page.meta_description) || ctx.description || meta.default_meta_description;
104
+ if (description) {
105
+ parts.push(`<meta name="description" content="${esc(description)}">`);
106
+ }
107
+ const canonicalBase = (meta.canonical_base_url || (website == null ? void 0 : website.domain) || "").replace(/\/$/, "");
108
+ if (canonicalBase) {
109
+ parts.push(`<link rel="canonical" href="${esc(canonicalBase + url)}">`);
110
+ }
111
+ const ogTitle = (page == null ? void 0 : page.meta_title) || ctx.title || (page == null ? void 0 : page.title) || meta.og_site_name || meta.default_meta_title;
112
+ const ogDesc = (page == null ? void 0 : page.meta_description) || ctx.description || meta.og_description || meta.default_meta_description;
113
+ const ogImage = (page == null ? void 0 : page.og_image) || ctx.image || meta.default_og_image;
114
+ const ogType = meta.og_type || "website";
115
+ if (ogTitle) parts.push(`<meta property="og:title" content="${esc(ogTitle)}">`);
116
+ if (ogDesc) parts.push(`<meta property="og:description" content="${esc(ogDesc)}">`);
117
+ if (ogImage) parts.push(`<meta property="og:image" content="${esc(ogImage)}">`);
118
+ parts.push(`<meta property="og:type" content="${esc(ogType)}">`);
119
+ if (canonicalBase) parts.push(`<meta property="og:url" content="${esc(canonicalBase + url)}">`);
120
+ if (meta.og_site_name) parts.push(`<meta property="og:site_name" content="${esc(meta.og_site_name)}">`);
121
+ const twitterCard = meta.twitter_card || (ogImage ? "summary_large_image" : "summary");
122
+ parts.push(`<meta name="twitter:card" content="${esc(twitterCard)}">`);
123
+ if (ogTitle) parts.push(`<meta name="twitter:title" content="${esc(ogTitle)}">`);
124
+ if (ogDesc) parts.push(`<meta name="twitter:description" content="${esc(ogDesc)}">`);
125
+ if (ogImage) parts.push(`<meta name="twitter:image" content="${esc(ogImage)}">`);
126
+ if (meta.twitter_site) parts.push(`<meta name="twitter:site" content="${esc(meta.twitter_site)}">`);
127
+ const alternates = resolvedData == null ? void 0 : resolvedData.alternates;
128
+ if (alternates && canonicalBase) {
129
+ for (const [locale, slug] of Object.entries(alternates)) {
130
+ parts.push(`<link rel="alternate" hreflang="${esc(locale)}" href="${esc(canonicalBase + slug)}">`);
131
+ }
132
+ const defaultSlug = alternates[(website == null ? void 0 : website.default_locale) ?? "en"] ?? url;
133
+ parts.push(`<link rel="alternate" hreflang="x-default" href="${esc(canonicalBase + defaultSlug)}">`);
134
+ }
135
+ if (meta.noindex) {
136
+ parts.push('<meta name="robots" content="noindex, nofollow">');
137
+ }
138
+ if (stateScript) {
139
+ parts.push(stateScript);
140
+ }
141
+ return parts.join("\n");
142
+ }
143
+ function buildSiteHead(website) {
144
+ if (!website) return "";
145
+ const parts = [];
146
+ const meta = website.meta ?? {};
147
+ if (website.favicon_url) {
148
+ parts.push(`<link rel="icon" href="${esc(website.favicon_url)}">`);
149
+ }
150
+ if (meta.webclip) {
151
+ parts.push(`<link rel="apple-touch-icon" sizes="180x180" href="${esc(meta.webclip)}">`);
152
+ }
153
+ if (meta.verification_google) {
154
+ parts.push(`<meta name="google-site-verification" content="${esc(meta.verification_google)}">`);
155
+ }
156
+ if (meta.verification_bing) {
157
+ parts.push(`<meta name="msvalidate.01" content="${esc(meta.verification_bing)}">`);
158
+ }
159
+ if (meta.structured_data_org) {
160
+ try {
161
+ const json = typeof meta.structured_data_org === "string" ? meta.structured_data_org : JSON.stringify(meta.structured_data_org);
162
+ parts.push(`<script type="application/ld+json">${json}${"<"}/script>`);
163
+ } catch {
164
+ }
165
+ }
166
+ const plausibleId = website.plausible_site_id;
167
+ if (plausibleId) {
168
+ parts.push(`<script defer data-domain="${esc(plausibleId)}" src="https://plausible.io/js/script.js"><${"/"}script>`);
169
+ }
170
+ return parts.join("\n");
171
+ }
172
+ function generateSitemapXml(options) {
173
+ const { renderedPaths, canonicalBase, defaultChangefreq = "weekly", defaultPriority = "0.7" } = options;
174
+ const base = canonicalBase.replace(/\/$/, "");
175
+ const urls = renderedPaths.map((p) => {
176
+ const priority = p === "/" ? "1.0" : defaultPriority;
177
+ const changefreq = p === "/" ? "daily" : defaultChangefreq;
178
+ return ` <url>
179
+ <loc>${esc(base + p)}</loc>
180
+ <changefreq>${changefreq}</changefreq>
181
+ <priority>${priority}</priority>
182
+ </url>`;
183
+ });
184
+ return `<?xml version="1.0" encoding="UTF-8"?>
185
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
186
+ ${urls.join("\n")}
187
+ </urlset>
188
+ `;
189
+ }
190
+ function generateRobotsTxt(options) {
191
+ const { robotsTxt, canonicalBase, noindex } = options;
192
+ if (noindex) {
193
+ return `User-agent: *
194
+ Disallow: /
195
+ `;
196
+ }
197
+ const base = (canonicalBase || "").replace(/\/$/, "");
198
+ const sitemapLine = base ? `
199
+ Sitemap: ${base}/sitemap.xml
200
+ ` : "";
201
+ if (robotsTxt) {
202
+ if (sitemapLine && !robotsTxt.toLowerCase().includes("sitemap:")) {
203
+ return `${robotsTxt.trimEnd()}
204
+ ${sitemapLine}`;
205
+ }
206
+ return robotsTxt;
207
+ }
208
+ return `User-agent: *
209
+ Allow: /
210
+ ${sitemapLine}`;
211
+ }
212
+ function generateNetlifyRedirects(redirects) {
213
+ if (redirects.length === 0) return "";
214
+ return `${redirects.map((r) => `${r.from_path} ${r.to_path} ${r.status_code}`).join("\n")}
215
+ `;
216
+ }
217
+ function stripTemplateSeoTags(template) {
218
+ return template.replace(/<title>[^<]*<\/title>/gi, "<!--ssg:title-->").replace(/<meta\s[^>]*name\s*=\s*["']description["'][^>]*>/gi, "").replace(/<meta\s[^>]*property\s*=\s*["']og:[^"']*["'][^>]*>/gi, "").replace(/<meta\s[^>]*(?:property|name)\s*=\s*["']twitter:[^"']*["'][^>]*>/gi, "").replace(/<link\s[^>]*rel\s*=\s*["']apple-touch-icon["'][^>]*>/gi, "").replace(/<link\s[^>]*rel\s*=\s*["']icon["'][^>]*>/gi, "").replace(/\n\s*\n\s*\n/g, "\n\n");
219
+ }
220
+ function extractPrimaryContext(contexts) {
221
+ const empty = { title: null, description: null, image: null };
222
+ if (!contexts || typeof contexts !== "object") return empty;
223
+ const ctx = Object.values(contexts).find((c) => c != null);
224
+ if (!ctx) return empty;
225
+ const str = (key) => {
226
+ const v = ctx[key];
227
+ return typeof v === "string" && v.trim() ? v.trim() : null;
228
+ };
229
+ return {
230
+ title: str("meta_title") || str("title"),
231
+ description: str("meta_description") || str("excerpt") || str("description") || str("blurb") || str("summary"),
232
+ image: str("og_image") || str("cover_image_url") || str("image_url") || str("image")
233
+ };
234
+ }
235
+ function esc(s) {
236
+ return s.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
237
+ }
238
+ async function polyfillBloxSsgGlobals(options) {
239
+ const { env } = await import("node:process");
240
+ const pageUrl = (options == null ? void 0 : options.pageUrl) ?? env.BAGELINK_API_URL ?? "https://example.com/";
241
+ const { Window: HappyWindow } = await import("happy-dom");
242
+ const _win = new HappyWindow({ url: pageUrl });
243
+ const g = globalThis;
244
+ if (g.window == null) g.window = g;
245
+ for (const key of Object.getOwnPropertyNames(_win)) {
246
+ if (key === "window" || key === "global" || key === "globalThis") continue;
247
+ try {
248
+ if (!(key in g)) g[key] = _win[key];
249
+ } catch {
250
+ }
251
+ }
252
+ for (const key of [
253
+ "document",
254
+ "location",
255
+ "localStorage",
256
+ "sessionStorage",
257
+ "navigator",
258
+ "Element",
259
+ "Document",
260
+ "HTMLElement",
261
+ "Event",
262
+ "CustomEvent",
263
+ "MouseEvent",
264
+ "KeyboardEvent",
265
+ "MutationObserver",
266
+ "IntersectionObserver",
267
+ "ResizeObserver",
268
+ "getComputedStyle",
269
+ "matchMedia",
270
+ "requestAnimationFrame",
271
+ "cancelAnimationFrame"
272
+ ]) {
273
+ try {
274
+ g[key] = _win[key];
275
+ } catch {
276
+ }
277
+ }
278
+ g.window = g;
279
+ g.window.location = _win.location;
280
+ g.window.document = _win.document;
281
+ }
282
+ async function prerender({
283
+ root = process.cwd(),
284
+ clientOutDir = "dist/client",
285
+ serverEntry = "dist/server/main.server.js",
286
+ paths = [],
287
+ crawl = true,
288
+ excludePaths = [],
289
+ failFast = false,
290
+ maxPages = 5e3,
291
+ mode = "dir",
292
+ website = null,
293
+ websiteId = "",
294
+ redirects = [],
295
+ collections = {}
296
+ } = {}) {
297
+ const absRoot = path.resolve(root);
298
+ const absClient = path.resolve(absRoot, clientOutDir);
299
+ const absServerEntry = path.resolve(absRoot, serverEntry);
300
+ const templatePath = path.join(absClient, "index.html");
301
+ const rawTemplate = await fs.readFile(templatePath, "utf8");
302
+ let template = stripTemplateSeoTags(rawTemplate);
303
+ const siteHead = buildSiteHead(website);
304
+ if (siteHead) {
305
+ template = template.replace("</head>", `${siteHead}
306
+ </head>`);
307
+ }
308
+ const serverMod = await import(pathToFileURL(absServerEntry).href);
309
+ if (typeof serverMod.render !== "function") {
310
+ throw new TypeError(
311
+ `SSR entry must export async function render(url, ctx). Missing "render" in: ${absServerEntry}`
312
+ );
313
+ }
314
+ let manifest = null;
315
+ try {
316
+ const manifestPath = path.join(absClient, "ssr-manifest.json");
317
+ manifest = JSON.parse(await fs.readFile(manifestPath, "utf8"));
318
+ } catch {
319
+ }
320
+ const isExcluded = makeExcludeChecker(excludePaths);
321
+ const queue = [];
322
+ const seen = /* @__PURE__ */ new Set();
323
+ enqueue("/", queue, seen, isExcluded);
324
+ for (const p of paths) enqueue(p, queue, seen, isExcluded);
325
+ const fontPreloads = await discoverFontPreloads(absClient);
326
+ const rendered = [];
327
+ const failures = [];
328
+ while (queue.length) {
329
+ if (rendered.length >= maxPages) break;
330
+ const urlPath = queue.shift();
331
+ if (urlPath == null || urlPath === "") continue;
332
+ if (isExcluded(urlPath)) continue;
333
+ try {
334
+ const { html, head = "", htmlAttrs = "" } = await serverMod.render(urlPath, {
335
+ manifest,
336
+ template,
337
+ website,
338
+ websiteId,
339
+ collections
340
+ });
341
+ const outHtml = injectIntoTemplate(template, head, html, fontPreloads, htmlAttrs);
342
+ const outfile = outFilePath(absClient, urlPath, mode);
343
+ await fs.mkdir(path.dirname(outfile), { recursive: true });
344
+ await fs.writeFile(outfile, outHtml, "utf8");
345
+ rendered.push(urlPath);
346
+ if (crawl) {
347
+ const discovered = discoverInternalLinks(outHtml);
348
+ for (const p of discovered) enqueue(p, queue, seen, isExcluded);
349
+ }
350
+ } catch (err) {
351
+ failures.push({ path: urlPath, error: err });
352
+ if (failFast) throw err;
353
+ console.warn(`[prerender] failed: ${urlPath}
354
+ `, err.stack ?? err);
355
+ }
356
+ }
357
+ const websiteMeta = (website == null ? void 0 : website.meta) ?? {};
358
+ const canonicalBase = (websiteMeta.canonical_base_url || (website == null ? void 0 : website.domain) || "").replace(/\/$/, "");
359
+ if (canonicalBase && rendered.length > 0) {
360
+ const sitemap = generateSitemapXml({ renderedPaths: rendered, canonicalBase });
361
+ await fs.writeFile(path.join(absClient, "sitemap.xml"), sitemap, "utf8");
362
+ console.log(` Generated sitemap.xml (${rendered.length} URLs)`);
363
+ }
364
+ const robotsTxt = generateRobotsTxt({
365
+ robotsTxt: website == null ? void 0 : website.robots_txt,
366
+ canonicalBase,
367
+ noindex: websiteMeta.noindex
368
+ });
369
+ await fs.writeFile(path.join(absClient, "robots.txt"), robotsTxt, "utf8");
370
+ console.log(" Generated robots.txt");
371
+ if (redirects.length > 0) {
372
+ const redirectsContent = generateNetlifyRedirects(redirects);
373
+ const redirectsPath = path.join(absClient, "_redirects");
374
+ let existing = "";
375
+ try {
376
+ existing = await fs.readFile(redirectsPath, "utf8");
377
+ } catch {
378
+ }
379
+ await fs.writeFile(redirectsPath, redirectsContent + existing, "utf8");
380
+ console.log(` Generated _redirects (${redirects.length} rules)`);
381
+ }
382
+ return {
383
+ rendered,
384
+ failures,
385
+ queuedRemaining: queue.length,
386
+ totalDiscovered: seen.size
387
+ };
388
+ }
389
+ function normalizePath(p) {
390
+ if (!p) return "/";
391
+ try {
392
+ if (p.startsWith("http://") || p.startsWith("https://")) {
393
+ const u = new URL(p);
394
+ p = u.pathname + (u.search || "");
395
+ }
396
+ } catch {
397
+ }
398
+ if (!p.startsWith("/")) p = `/${p}`;
399
+ const hashIdx = p.indexOf("#");
400
+ if (hashIdx !== -1) p = p.slice(0, hashIdx);
401
+ p = p.replace(/\/{2,}/g, "/");
402
+ if (p.length > 1 && p.endsWith("/")) p = p.slice(0, -1);
403
+ return p || "/";
404
+ }
405
+ function enqueue(p, queue, seen, isExcluded) {
406
+ const np = normalizePath(p);
407
+ if (seen.has(np)) return;
408
+ if (isExcluded(np)) return;
409
+ seen.add(np);
410
+ queue.push(np);
411
+ }
412
+ function makeExcludeChecker(excludePaths) {
413
+ const rules = Array.isArray(excludePaths) ? excludePaths : [excludePaths];
414
+ return (p) => {
415
+ const np = normalizePath(p);
416
+ for (const r of rules) {
417
+ if (typeof r === "string" && r === "") continue;
418
+ if (typeof r === "function") {
419
+ if (r(np)) return true;
420
+ } else if (r instanceof RegExp) {
421
+ if (r.test(np)) return true;
422
+ } else if (typeof r === "string") {
423
+ const prefix = normalizePath(r);
424
+ if (np === prefix || np.startsWith(`${prefix}/`)) return true;
425
+ }
426
+ }
427
+ return false;
428
+ };
429
+ }
430
+ async function discoverFontPreloads(clientOutDir) {
431
+ const assetsDir = path.join(clientOutDir, "assets");
432
+ let cssFiles = [];
433
+ try {
434
+ const entries = await fs.readdir(assetsDir);
435
+ cssFiles = entries.filter((f) => f.endsWith(".css")).map((f) => path.join(assetsDir, f));
436
+ } catch {
437
+ return "";
438
+ }
439
+ const seen = /* @__PURE__ */ new Set();
440
+ const woff2Re = /url\((["']?)(https?:\/\/[^"')]+\.woff2)\1\)/g;
441
+ for (const file of cssFiles) {
442
+ const css = await fs.readFile(file, "utf8");
443
+ for (const [, , url] of css.matchAll(woff2Re)) {
444
+ if (url !== "") seen.add(url);
445
+ }
446
+ }
447
+ return [...seen].map((href) => `<link rel="preload" as="font" type="font/woff2" crossorigin href="${href}">`).join("\n");
448
+ }
449
+ function injectIntoTemplate(template, head, appHtml, fontPreloads, htmlAttrs = "") {
450
+ let out = template;
451
+ if (template.includes("<!--app-html-->")) {
452
+ out = template.replace("<!--app-html-->", appHtml);
453
+ } else if (template.includes('<div id="app"></div>')) {
454
+ out = template.replace('<div id="app"></div>', `<div id="app">${appHtml}</div>`);
455
+ }
456
+ if (htmlAttrs) {
457
+ const langMatch = htmlAttrs.match(/lang="([^"]*)"/);
458
+ if (langMatch) {
459
+ out = out.replace(/(<html[^>]*\s)lang="[^"]*"/, `$1lang="${langMatch[1]}"`);
460
+ }
461
+ }
462
+ if (fontPreloads) {
463
+ out = out.replace("</head>", `${fontPreloads}
464
+ </head>`);
465
+ }
466
+ if (head) {
467
+ if (/<title>/.test(head)) {
468
+ out = out.replace("<!--ssg:title-->", "");
469
+ }
470
+ const descTagRe = /<meta\s[^>]*name\s*=\s*["']description["'][^>]*>/gi;
471
+ const descMatches = [...head.matchAll(descTagRe)];
472
+ if (descMatches.length > 1) {
473
+ for (let i = 0; i < descMatches.length - 1; i++) {
474
+ head = head.replace(descMatches[i][0], "");
475
+ }
476
+ }
477
+ if (/<meta\s[^>]*name\s*=\s*["']description["']/i.test(head)) {
478
+ out = out.replace(/<meta\s[^>]*name\s*=\s*["']description["'][^>]*>/gi, "");
479
+ }
480
+ out = out.replace("</head>", `${head}
481
+ </head>`);
482
+ }
483
+ return out;
484
+ }
485
+ function outFilePath(absClientDir, urlPath, mode) {
486
+ const [pathname] = urlPath.split("?");
487
+ const safe = pathname === "/" ? "/" : pathname;
488
+ if (mode === "file") {
489
+ if (safe === "/") return path.join(absClientDir, "index.html");
490
+ return path.join(absClientDir, `${safe.slice(1)}.html`);
491
+ }
492
+ if (safe === "/") return path.join(absClientDir, "index.html");
493
+ return path.join(absClientDir, safe.slice(1), "index.html");
494
+ }
495
+ function discoverInternalLinks(html) {
496
+ const out = /* @__PURE__ */ new Set();
497
+ const hrefRe = /\bhref\s*=\s*(?:"([^"]*)"|'([^']*)')/gi;
498
+ let m;
499
+ while ((m = hrefRe.exec(html)) !== null) {
500
+ const raw = (m[1] ?? m[2] ?? "").trim();
501
+ if (!raw) continue;
502
+ if (raw.startsWith("mailto:") || raw.startsWith("tel:") || raw.startsWith("javascript:") || raw.startsWith("#")) {
503
+ continue;
504
+ }
505
+ if (raw.startsWith("http://") || raw.startsWith("https://")) continue;
506
+ if (/\.(?:pdf|png|jpe?g|gif|svg|webp|css|js|map|ico)(?:\?|$)/i.test(raw)) continue;
507
+ const candidate = raw.startsWith("/") ? raw : `/${raw}`;
508
+ out.add(candidate);
509
+ }
510
+ return [...out];
511
+ }
512
+ export {
513
+ prerender as a,
514
+ buildPageHead as b,
515
+ fetchCmsPrerenderPaths as c,
516
+ buildSiteHead as d,
517
+ generateRobotsTxt as e,
518
+ fetchCmsSiteData as f,
519
+ generateNetlifyRedirects as g,
520
+ generateSitemapXml as h,
521
+ polyfillBloxSsgGlobals as p
522
+ };
package/dist/schema.d.ts CHANGED
@@ -14,6 +14,8 @@ export interface BlockSchema {
14
14
  fields?: SchemaDefinition;
15
15
  /** Default prop values. Merged with block props at render time. */
16
16
  defaults?: Record<string, unknown>;
17
+ /** True when this block fetches its own data from a datastore collection. */
18
+ dataSource?: boolean;
17
19
  }
18
20
  export interface BlockDefinition {
19
21
  component: Component;
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,KAAK,CAAA;AACpC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAE9C,MAAM,WAAW,WAAW;IAC3B,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,sDAAsD;IACtD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,MAAM,CAAC,EAAE,gBAAgB,CAAA;IACzB,mEAAmE;IACnE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAClC;AAED,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,SAAS,CAAA;IACpB,MAAM,EAAE,WAAW,CAAA;CACnB;AAED,+EAA+E;AAC/E,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,CAAA"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,KAAK,CAAA;AACpC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAE9C,MAAM,WAAW,WAAW;IAC3B,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,sDAAsD;IACtD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,MAAM,CAAC,EAAE,gBAAgB,CAAA;IACzB,mEAAmE;IACnE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAClC,6EAA6E;IAC7E,UAAU,CAAC,EAAE,OAAO,CAAA;CACpB;AAED,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,SAAS,CAAA;IACpB,MAAM,EAAE,WAAW,CAAA;CACnB;AAED,+EAA+E;AAC/E,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,CAAA"}
package/dist/ssg/cli.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bun
2
2
  "use strict";
3
3
  const process = require("node:process");
4
- const prerender = require("../prerender-6jE_obPj.cjs");
4
+ const prerender = require("../prerender-Bi7YtzSp.cjs");
5
5
  async function resolveApiBase(mode) {
6
6
  if (process.env.BAGELINK_API_URL != null && process.env.BAGELINK_API_URL !== "") {
7
7
  return { apiBase: process.env.BAGELINK_API_URL };
@@ -30,6 +30,7 @@ async function resolveApiBase(mode) {
30
30
  return { apiBase: "https://localhost:8000" };
31
31
  }
32
32
  async function main() {
33
+ var _a;
33
34
  const argv = process.argv.slice(2);
34
35
  const extraPaths = [];
35
36
  let crawl = false;
@@ -72,11 +73,47 @@ Environment:
72
73
  const websiteName = process.env.WEBSITE_NAME ?? configWebsiteName ?? "default";
73
74
  console.log(`Fetching routes from ${apiBase} (mode: ${mode}, site: ${websiteName})…`);
74
75
  let paths = [];
76
+ let website = null;
77
+ let websiteId = "";
78
+ let redirects = [];
79
+ const collections = {};
75
80
  try {
76
- paths = await prerender.fetchCmsPrerenderPaths(apiBase, websiteName);
81
+ const siteData = await prerender.fetchCmsSiteData(apiBase, websiteName);
82
+ paths = siteData.paths;
83
+ website = siteData.website;
84
+ websiteId = siteData.websiteId;
85
+ redirects = siteData.redirects;
77
86
  console.log(` Found ${paths.length} CMS routes`);
87
+ if (redirects.length > 0) console.log(` Found ${redirects.length} redirects`);
88
+ const allCollectionRefs = [...siteData.collections];
89
+ const extraCollections = (_a = process.env.BLOX_SSG_COLLECTIONS) == null ? void 0 : _a.trim();
90
+ if (extraCollections) {
91
+ for (const entry of extraCollections.split(",")) {
92
+ const parts = entry.trim().split("/");
93
+ if (parts.length === 2 && parts[0] && parts[1]) {
94
+ const already = allCollectionRefs.some((r) => r.store === parts[0] && r.collection === parts[1]);
95
+ if (!already) allCollectionRefs.push({ store: parts[0], collection: parts[1] });
96
+ }
97
+ }
98
+ }
99
+ if (allCollectionRefs.length > 0) {
100
+ console.log(` Pre-fetching ${allCollectionRefs.length} datastore collection(s)…`);
101
+ for (const ref of allCollectionRefs) {
102
+ try {
103
+ const url = `${apiBase}/datastore/${ref.store}/collections/${ref.collection}?limit=500`;
104
+ const res = await fetch(url);
105
+ const data = await res.json();
106
+ const items = Array.isArray(data) ? data : data.data ?? [];
107
+ const key = `${ref.store}/${ref.collection}`;
108
+ collections[key] = items;
109
+ console.log(` ${key}: ${items.length} items`);
110
+ } catch (e) {
111
+ console.warn(` Failed to fetch ${ref.store}/${ref.collection}: ${e.message}`);
112
+ }
113
+ }
114
+ }
78
115
  } catch (err) {
79
- console.error(" Failed to fetch CMS routes:", err.message);
116
+ console.error(" Failed to fetch CMS data:", err.message);
80
117
  paths = ["/"];
81
118
  }
82
119
  for (const p of extraPaths) {
@@ -92,7 +129,11 @@ Environment:
92
129
  crawl,
93
130
  excludePaths,
94
131
  maxPages: 5e3,
95
- mode: "file"
132
+ mode: "file",
133
+ website,
134
+ websiteId,
135
+ redirects,
136
+ collections
96
137
  });
97
138
  const elapsed = ((Date.now() - startTime) / 1e3).toFixed(2);
98
139
  console.log(`
@@ -115,7 +156,9 @@ ${"=".repeat(60)}`);
115
156
  if (result.failures.length > 10) {
116
157
  console.log(` ... and ${result.failures.length - 10} more`);
117
158
  }
118
- process.exitCode = 1;
159
+ if (result.rendered.length === 0) {
160
+ process.exitCode = 1;
161
+ }
119
162
  }
120
163
  } catch (err) {
121
164
  console.error("Fatal error:", err);