@bagelink/blox 1.12.22 → 1.12.25
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/CmsPageView.vue.d.ts.map +1 -1
- package/dist/PreviewApp-C2T03Jm9.cjs +4 -0
- package/dist/PreviewApp-CmThrLvv.js +4 -0
- package/dist/PreviewApp.vue.d.ts.map +1 -1
- package/dist/PreviewApp.vue_vue_type_style_index_0_lang-B0N5QbfS.js +157 -0
- package/dist/PreviewApp.vue_vue_type_style_index_0_lang-BTuE4GmT.cjs +156 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/bridge.d.ts +1 -0
- package/dist/bridge.d.ts.map +1 -1
- package/dist/core-C3Iu5qa2.js +460 -0
- package/dist/core-_fnHoEZN.cjs +459 -0
- package/dist/core.d.ts.map +1 -1
- package/dist/createBloxApp.d.ts +107 -0
- package/dist/createBloxApp.d.ts.map +1 -0
- package/dist/defineBlock.d.ts +2 -0
- package/dist/defineBlock.d.ts.map +1 -1
- package/dist/index.cjs +79 -585
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +78 -583
- package/dist/{prerender-6jE_obPj.cjs → prerender-Bi7YtzSp.cjs} +246 -6
- package/dist/prerender-D3Q4jKXm.js +522 -0
- package/dist/schema.d.ts +2 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/ssg/cli.cjs +48 -5
- package/dist/ssg/cli.mjs +48 -5
- package/dist/ssg/client.cjs +50 -3
- package/dist/ssg/client.d.ts +2 -1
- package/dist/ssg/client.d.ts.map +1 -1
- package/dist/ssg/client.mjs +49 -2
- package/dist/ssg/cms-routes.d.ts +21 -4
- package/dist/ssg/cms-routes.d.ts.map +1 -1
- package/dist/ssg/collection-cache.d.ts +53 -0
- package/dist/ssg/collection-cache.d.ts.map +1 -0
- package/dist/ssg/constants.d.ts +4 -0
- package/dist/ssg/constants.d.ts.map +1 -1
- package/dist/ssg/createSSREntry.d.ts +73 -0
- package/dist/ssg/createSSREntry.d.ts.map +1 -0
- package/dist/ssg/index.cjs +138 -6
- package/dist/ssg/index.d.ts +10 -3
- package/dist/ssg/index.d.ts.map +1 -1
- package/dist/ssg/index.mjs +124 -12
- package/dist/ssg/prerender.d.ts +19 -1
- package/dist/ssg/prerender.d.ts.map +1 -1
- package/dist/ssg/render-resolved-page.d.ts +13 -3
- package/dist/ssg/render-resolved-page.d.ts.map +1 -1
- package/dist/ssg/seo.d.ts +66 -0
- package/dist/ssg/seo.d.ts.map +1 -0
- package/dist/style.css +20 -0
- package/dist/vite-plugin.cjs +142 -3
- package/dist/vite-plugin.d.ts +22 -21
- package/dist/vite-plugin.d.ts.map +1 -1
- package/dist/vite-plugin.mjs +142 -3
- package/package.json +4 -1
- package/dist/PreviewApp-BZNzZkit.js +0 -4
- package/dist/PreviewApp-C1WvJWI4.cjs +0 -4
- package/dist/constants-BIbQhd3z.js +0 -4
- package/dist/constants-fZvybj0k.cjs +0 -3
- 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, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
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;
|
package/dist/schema.d.ts.map
CHANGED
|
@@ -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;
|
|
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-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
159
|
+
if (result.rendered.length === 0) {
|
|
160
|
+
process.exitCode = 1;
|
|
161
|
+
}
|
|
119
162
|
}
|
|
120
163
|
} catch (err) {
|
|
121
164
|
console.error("Fatal error:", err);
|