@bagelink/blox 1.20.5 → 1.20.6

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.
@@ -2,7 +2,7 @@ import { spawn } from "node:child_process";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
4
  import process from "node:process";
5
- import { f as fetchCmsSiteData, g as generateSitemapArtifacts, d as validateNoRedirectingLinks, i as isErrorPagePath } from "./validate-output-CJhtHWFT.js";
5
+ import { f as fetchCmsSiteData, g as generateSitemapArtifacts, c as validatePreviewOutput, e as validateNoRedirectingLinks, i as isErrorPagePath, h as isPreviewPath } from "./validate-output-BTvdILwE.js";
6
6
  const AUTO_SHARD_CAP = Number(process.env.BLOX_SSG_SHARD_CAP) || 6;
7
7
  const PREFETCH_BUDGET = Number(process.env.BLOX_SSG_PREFETCH_BUDGET) || 24;
8
8
  const SEQUENTIAL_SHARDS = process.env.BLOX_SSG_SEQUENTIAL === "1";
@@ -16,7 +16,7 @@ function matchesCmsPath(pathname, cmsPaths) {
16
16
  return false;
17
17
  }
18
18
  function sitemapRoutesForWebsite(routes, siteNoindex) {
19
- return routes.filter((route) => !route.noindex && !isErrorPagePath(route.url));
19
+ return routes.filter((route) => !route.noindex && !isErrorPagePath(route.url) && !isPreviewPath(route.url));
20
20
  }
21
21
  function resolveShardCount(shards) {
22
22
  if (shards === "auto") {
@@ -145,7 +145,7 @@ async function runSharded(opts) {
145
145
  }
146
146
  if (process.env.BLOX_SSG_LLM !== "0" && !((_a = website == null ? void 0 : website.seo) == null ? void 0 : _a.noindex)) {
147
147
  try {
148
- const { mergeLlmParts } = await import("./validate-output-CJhtHWFT.js").then((n) => n.E);
148
+ const { mergeLlmParts } = await import("./validate-output-BTvdILwE.js").then((n) => n.G);
149
149
  const mergeWebsite = website;
150
150
  const partFiles = Array.from({ length: total }, (_, i) => path.join(llmPartsDir, `part.${i}.json`));
151
151
  const { pages } = await mergeLlmParts({
@@ -191,6 +191,7 @@ async function runSharded(opts) {
191
191
  await fs.rm(routePartsDir, { recursive: true, force: true });
192
192
  await fs.rm(siteDataFile, { force: true }).catch(() => {
193
193
  });
194
+ await validatePreviewOutput(path.resolve("dist"));
194
195
  await validateNoRedirectingLinks(path.resolve("dist"));
195
196
  console.log("[blox-ssg] validated internal links (no redirecting URLs)");
196
197
  console.log(`[blox-ssg] sharded run done in ${((Date.now() - start) / 1e3).toFixed(1)}s`);
@@ -26,7 +26,7 @@ const node_child_process = require("node:child_process");
26
26
  const os = require("node:os");
27
27
  const path = require("node:path");
28
28
  const process = require("node:process");
29
- const llmArtifacts = require("./validate-output-Bwp0ediG.cjs");
29
+ const llmArtifacts = require("./validate-output-CAcHbUBv.cjs");
30
30
  const AUTO_SHARD_CAP = Number(process.env.BLOX_SSG_SHARD_CAP) || 6;
31
31
  const PREFETCH_BUDGET = Number(process.env.BLOX_SSG_PREFETCH_BUDGET) || 24;
32
32
  const SEQUENTIAL_SHARDS = process.env.BLOX_SSG_SEQUENTIAL === "1";
@@ -40,7 +40,7 @@ function matchesCmsPath(pathname, cmsPaths) {
40
40
  return false;
41
41
  }
42
42
  function sitemapRoutesForWebsite(routes, siteNoindex) {
43
- return routes.filter((route) => !route.noindex && !llmArtifacts.isErrorPagePath(route.url));
43
+ return routes.filter((route) => !route.noindex && !llmArtifacts.isErrorPagePath(route.url) && !llmArtifacts.isPreviewPath(route.url));
44
44
  }
45
45
  function resolveShardCount(shards) {
46
46
  if (shards === "auto") {
@@ -169,7 +169,7 @@ async function runSharded(opts) {
169
169
  }
170
170
  if (process.env.BLOX_SSG_LLM !== "0" && !((_a = website == null ? void 0 : website.seo) == null ? void 0 : _a.noindex)) {
171
171
  try {
172
- const { mergeLlmParts } = await Promise.resolve().then(() => require("./validate-output-Bwp0ediG.cjs")).then((n) => n.llmArtifacts);
172
+ const { mergeLlmParts } = await Promise.resolve().then(() => require("./validate-output-CAcHbUBv.cjs")).then((n) => n.llmArtifacts);
173
173
  const mergeWebsite = website;
174
174
  const partFiles = Array.from({ length: total }, (_, i) => path.join(llmPartsDir, `part.${i}.json`));
175
175
  const { pages } = await mergeLlmParts({
@@ -215,6 +215,7 @@ async function runSharded(opts) {
215
215
  await fs.rm(routePartsDir, { recursive: true, force: true });
216
216
  await fs.rm(siteDataFile, { force: true }).catch(() => {
217
217
  });
218
+ await llmArtifacts.validatePreviewOutput(path.resolve("dist"));
218
219
  await llmArtifacts.validateNoRedirectingLinks(path.resolve("dist"));
219
220
  console.log("[blox-ssg] validated internal links (no redirecting URLs)");
220
221
  console.log(`[blox-ssg] sharded run done in ${((Date.now() - start) / 1e3).toFixed(1)}s`);
package/dist/ssg/cli.cjs CHANGED
@@ -3,7 +3,7 @@
3
3
  const process = require("node:process");
4
4
  const fs = require("node:fs/promises");
5
5
  const ssg_buildGrant = require("./build-grant.cjs");
6
- const llmArtifacts = require("../validate-output-Bwp0ediG.cjs");
6
+ const llmArtifacts = require("../validate-output-CAcHbUBv.cjs");
7
7
  const localeStrategy = require("../localeStrategy-OwRIn35C.cjs");
8
8
  function installFetchTimeout() {
9
9
  const ms = Number(process.env.BLOX_SSG_FETCH_TIMEOUT_MS) || 2e4;
@@ -157,7 +157,7 @@ Environment:
157
157
  if (shardsOpt !== null || routesCmd) {
158
158
  const { apiBase: apiBase2, websiteName: configWebsiteName2 } = await resolveApiBase(mode);
159
159
  const websiteName2 = process.env.WEBSITE_NAME ?? configWebsiteName2 ?? "default";
160
- const { runSharded } = await Promise.resolve().then(() => require("../shard-runner-DsFdfDLh.cjs"));
160
+ const { runSharded } = await Promise.resolve().then(() => require("../shard-runner-C6BtYRC2.cjs"));
161
161
  const code = await runSharded({
162
162
  shards: shardsOpt ?? "auto",
163
163
  mode,
@@ -303,6 +303,7 @@ ${"=".repeat(60)}`);
303
303
  }
304
304
  }
305
305
  if (validateOutput && shardTotal === 1) {
306
+ await llmArtifacts.validatePreviewOutput("dist");
306
307
  await llmArtifacts.validateInternalLinks("dist");
307
308
  await llmArtifacts.validateNoRedirectingLinks("dist");
308
309
  console.log("[blox-ssg] validated internal links");
package/dist/ssg/cli.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  import process from "node:process";
3
3
  import fs from "node:fs/promises";
4
4
  import { resolveBloxBuildGrant } from "./build-grant.mjs";
5
- import { v as validateSsgOutput, u as uploadBloxManifest, p as polyfillBloxSsgGlobals, f as fetchCmsSiteData, b as buildCollectionResolver, a as prerender, c as validateInternalLinks, d as validateNoRedirectingLinks } from "../validate-output-CJhtHWFT.js";
5
+ import { v as validateSsgOutput, u as uploadBloxManifest, p as polyfillBloxSsgGlobals, f as fetchCmsSiteData, b as buildCollectionResolver, a as prerender, c as validatePreviewOutput, d as validateInternalLinks, e as validateNoRedirectingLinks } from "../validate-output-BTvdILwE.js";
6
6
  import { s as setPageSlugMap } from "../localeStrategy-hXlNof1E.js";
7
7
  function installFetchTimeout() {
8
8
  const ms = Number(process.env.BLOX_SSG_FETCH_TIMEOUT_MS) || 2e4;
@@ -156,7 +156,7 @@ Environment:
156
156
  if (shardsOpt !== null || routesCmd) {
157
157
  const { apiBase: apiBase2, websiteName: configWebsiteName2 } = await resolveApiBase(mode);
158
158
  const websiteName2 = process.env.WEBSITE_NAME ?? configWebsiteName2 ?? "default";
159
- const { runSharded } = await import("../shard-runner-0mHNuOav.js");
159
+ const { runSharded } = await import("../shard-runner-BGMCtBmP.js");
160
160
  const code = await runSharded({
161
161
  shards: shardsOpt ?? "auto",
162
162
  mode,
@@ -302,6 +302,7 @@ ${"=".repeat(60)}`);
302
302
  }
303
303
  }
304
304
  if (validateOutput && shardTotal === 1) {
305
+ await validatePreviewOutput("dist");
305
306
  await validateInternalLinks("dist");
306
307
  await validateNoRedirectingLinks("dist");
307
308
  console.log("[blox-ssg] validated internal links");
@@ -22,7 +22,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
22
22
  mod
23
23
  ));
24
24
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
25
- const llmArtifacts = require("../validate-output-Bwp0ediG.cjs");
25
+ const llmArtifacts = require("../validate-output-CAcHbUBv.cjs");
26
26
  const ssg_buildGrant = require("./build-grant.cjs");
27
27
  const ssg_client = require("../client-AoDTJJXJ.cjs");
28
28
  const vue = require("vue");
@@ -1,5 +1,5 @@
1
- import { e as buildPageHead, h as primaryContextEntry, j as buildJsonLd, k as extractPageText, B as BloxBuildResponseError, l as bloxBuildJson } from "../validate-output-CJhtHWFT.js";
2
- import { m, n, f, o, q, r, s, g, t, w, x, y, z, A, p, a, C, D, u, c, v } from "../validate-output-CJhtHWFT.js";
1
+ import { j as buildPageHead, k as primaryContextEntry, l as buildJsonLd, m as extractPageText, B as BloxBuildResponseError, n as bloxBuildJson } from "../validate-output-BTvdILwE.js";
2
+ import { o, q, f, r, s, t, w, g, x, y, z, A, C, D, p, a, E, F, u, d, v } from "../validate-output-BTvdILwE.js";
3
3
  import { parseBloxBuildGrantBody, resolveBloxBuildGrant } from "./build-grant.mjs";
4
4
  import { a as BLOX_HYDRATION_WINDOW_KEY } from "../client-Ce9fRCGH.js";
5
5
  import { createSSRApp, h, Suspense } from "vue";
@@ -443,35 +443,35 @@ export {
443
443
  BLOX_HYDRATION_WINDOW_KEY,
444
444
  BloxBuildResponseError,
445
445
  bloxBuildJson,
446
- m as bloxBuildRequest,
446
+ o as bloxBuildRequest,
447
447
  buildJsonLd,
448
448
  buildPageHead,
449
- n as buildSiteHead,
449
+ q as buildSiteHead,
450
450
  createBloxSSREntry,
451
451
  extractPageText,
452
452
  f as fetchCmsSiteData,
453
453
  fetchDatastoreSnapshot,
454
- o as generateLlmsFullTxt,
455
- q as generateLlmsTxt,
456
- r as generateNetlifyRedirects,
457
- s as generateRobotsTxt,
454
+ r as generateLlmsFullTxt,
455
+ s as generateLlmsTxt,
456
+ t as generateNetlifyRedirects,
457
+ w as generateRobotsTxt,
458
458
  g as generateSitemapArtifacts,
459
- t as generateSitemapIndexXml,
460
- w as generateSitemapXml,
461
- x as getBloxBuildRequestOptions,
462
- y as getSiteIconFlags,
463
- z as localeSitemapPath,
464
- A as mergeLlmParts,
459
+ x as generateSitemapIndexXml,
460
+ y as generateSitemapXml,
461
+ z as getBloxBuildRequestOptions,
462
+ A as getSiteIconFlags,
463
+ C as localeSitemapPath,
464
+ D as mergeLlmParts,
465
465
  parseBloxBuildGrantBody,
466
466
  p as polyfillBloxSsgGlobals,
467
467
  a as prerender,
468
468
  querySnapshotRows,
469
469
  renderBloxSsgPage,
470
470
  resolveBloxBuildGrant,
471
- C as resolveManifestProvenance,
471
+ E as resolveManifestProvenance,
472
472
  snapshotRows,
473
- D as splitSitemapPathsByLocale,
473
+ F as splitSitemapPathsByLocale,
474
474
  u as uploadBloxManifest,
475
- c as validateInternalLinks,
475
+ d as validateInternalLinks,
476
476
  v as validateSsgOutput
477
477
  };
@@ -1 +1 @@
1
- {"version":3,"file":"prerender.d.ts","sourceRoot":"","sources":["../../src/ssg/prerender.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"prerender.d.ts","sourceRoot":"","sources":["../../src/ssg/prerender.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export declare const PREVIEW_PATH = "/_blox_preview";
2
+ export declare const PREVIEW_FILE = "_blox_preview.html";
3
+ export declare const PREVIEW_REWRITES = "/_blox_preview /_blox_preview.html 200!\n/_blox_preview/* /_blox_preview.html 200!\n";
4
+ export declare function isPreviewPath(value: string): boolean;
5
+ /** The client shell, never the prerendered homepage, boots the editor iframe. */
6
+ export declare function writePreviewShell(outDir: string, rawTemplate: string): Promise<void>;
7
+ /** Reserved rules always precede CMS and user catchalls; stale overrides lose. */
8
+ export declare function previewRedirects(existing: string, cms: string): string;
9
+ //# sourceMappingURL=preview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../src/ssg/preview.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,YAAY,mBAAmB,CAAA;AAC5C,eAAO,MAAM,YAAY,uBAAuB,CAAA;AAChD,eAAO,MAAM,gBAAgB,yFAAqF,CAAA;AAElH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAGpD;AAED,iFAAiF;AACjF,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB1F;AAED,kFAAkF;AAClF,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAStE"}
@@ -1 +1 @@
1
- {"version":3,"file":"shard-runner.d.ts","sourceRoot":"","sources":["../../src/ssg/shard-runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAQH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAA;AAM9D,MAAM,WAAW,kBAAkB;IAClC,qDAAqD;IACrD,MAAM,EAAE,MAAM,GAAG,MAAM,CAAA;IACvB,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAA;IACZ,uDAAuD;IACvD,OAAO,EAAE,MAAM,CAAA;IACf,sDAAsD;IACtD,WAAW,EAAE,MAAM,CAAA;IACnB,yEAAyE;IACzE,YAAY,CAAC,EAAE,uBAAuB,CAAA;IACtC,iFAAiF;IACjF,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,8FAA8F;IAC9F,eAAe,EAAE,MAAM,EAAE,CAAA;IACzB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CAClB;AAWD,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,OAAO,CAQ/E;AAED,wBAAgB,uBAAuB,CAAC,CAAC,SAAS;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EAClF,MAAM,EAAE,CAAC,EAAE,EACX,WAAW,EAAE,OAAO,GAAG,SAAS,GAC9B,CAAC,EAAE,CAOL;AAsED;;GAEG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CAgK1E"}
1
+ {"version":3,"file":"shard-runner.d.ts","sourceRoot":"","sources":["../../src/ssg/shard-runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAQH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAA;AAO9D,MAAM,WAAW,kBAAkB;IAClC,qDAAqD;IACrD,MAAM,EAAE,MAAM,GAAG,MAAM,CAAA;IACvB,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAA;IACZ,uDAAuD;IACvD,OAAO,EAAE,MAAM,CAAA;IACf,sDAAsD;IACtD,WAAW,EAAE,MAAM,CAAA;IACnB,yEAAyE;IACzE,YAAY,CAAC,EAAE,uBAAuB,CAAA;IACtC,iFAAiF;IACjF,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,8FAA8F;IAC9F,eAAe,EAAE,MAAM,EAAE,CAAA;IACzB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CAClB;AAWD,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,OAAO,CAQ/E;AAED,wBAAgB,uBAAuB,CAAC,CAAC,SAAS;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EAClF,MAAM,EAAE,CAAC,EAAE,EACX,WAAW,EAAE,OAAO,GAAG,SAAS,GAC9B,CAAC,EAAE,CAOL;AAsED;;GAEG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CAiK1E"}
@@ -35,5 +35,6 @@ export interface ValidateSsgOutputOptions {
35
35
  * remain for external/legacy URLs only.
36
36
  */
37
37
  export declare function validateNoRedirectingLinks(outDir: string): Promise<void>;
38
+ export declare function validatePreviewOutput(outDir: string): Promise<void>;
38
39
  export declare function validateSsgOutput(options: ValidateSsgOutputOptions): Promise<void>;
39
40
  //# sourceMappingURL=validate-output.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validate-output.d.ts","sourceRoot":"","sources":["../../src/ssg/validate-output.ts"],"names":[],"mappings":"AAWA;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBzE;AAED,MAAM,WAAW,wBAAwB;IACxC,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;IACvB;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B;;;;;;;OAOG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAC5B;AAED;;;;;;;GAOG;AACH,wBAAsB,0BAA0B,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA4B9E;AAqED,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CA2ExF"}
1
+ {"version":3,"file":"validate-output.d.ts","sourceRoot":"","sources":["../../src/ssg/validate-output.ts"],"names":[],"mappings":"AAYA;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBzE;AAED,MAAM,WAAW,wBAAwB;IACxC,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;IACvB;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B;;;;;;;OAOG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAC5B;AAED;;;;;;;GAOG;AACH,wBAAsB,0BAA0B,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA4B9E;AAqED,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA6BzE;AAED,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6ExF"}
@@ -814,6 +814,38 @@ function absUrl(value) {
814
814
  if (/^(https?:)?\/\//i.test(v) || v.startsWith("/")) return v;
815
815
  return `${FILES_BASE_URL}/${v.replace(/^\/+/, "")}`;
816
816
  }
817
+ const PREVIEW_PATH = "/_blox_preview";
818
+ const PREVIEW_FILE = "_blox_preview.html";
819
+ const PREVIEW_REWRITES = `${PREVIEW_PATH} /${PREVIEW_FILE} 200!
820
+ ${PREVIEW_PATH}/* /${PREVIEW_FILE} 200!
821
+ `;
822
+ function isPreviewPath(value) {
823
+ const pathname = value.split(/[?#]/)[0].replace(/\/+$/, "");
824
+ return pathname === PREVIEW_PATH || pathname === `/${PREVIEW_FILE}` || pathname.startsWith(`${PREVIEW_PATH}/`);
825
+ }
826
+ async function writePreviewShell(outDir, rawTemplate) {
827
+ const target = path.join(outDir, PREVIEW_FILE);
828
+ try {
829
+ await fs.access(target);
830
+ return;
831
+ } catch {
832
+ }
833
+ if (/__BLOX_(?:HYDRATION|STATE|DATA)__|data-server-rendered/.test(rawTemplate)) {
834
+ throw new Error("Cannot create Blox preview shell from prerendered HTML; rebuild the Vite client first");
835
+ }
836
+ const shell = stripTemplateSeoTags(rawTemplate).replace(/<meta\b[^>]*\bname\s*=\s*["'](?:robots|googlebot|bingbot)["'][^>]*>/gi, "").replace(/<link\b[^>]*\bhreflang\s*=[^>]*>/gi, "").replace(/<\/head>/i, '<title>Blox editor preview</title>\n<meta name="robots" content="noindex, nofollow">\n</head>');
837
+ await fs.writeFile(target, shell, "utf8");
838
+ }
839
+ function previewRedirects(existing, cms) {
840
+ const clean = (content) => content.split("\n").filter((line) => {
841
+ const source = line.trim().split(/\s+/)[0];
842
+ return !source || source.startsWith("#") || !isPreviewPath(source.replace(/\*$/, ""));
843
+ }).join("\n");
844
+ const cmsRules = clean(cms);
845
+ const generated = new Set(cmsRules.split("\n").filter((line) => line.trim() && !line.trim().startsWith("#")));
846
+ const userRules = clean(existing).split("\n").filter((line) => !generated.has(line)).join("\n");
847
+ return PREVIEW_REWRITES + [cmsRules.trim(), userRules.trim()].filter(Boolean).join("\n") + "\n";
848
+ }
817
849
  function typeFromContextKey(key) {
818
850
  const k = key.replace(/^\$/, "").toLowerCase();
819
851
  if (/(post|article|blog|story|news)/.test(k)) return "Article";
@@ -1075,6 +1107,7 @@ async function prerender({
1075
1107
  const absServerEntry = path.resolve(absRoot, serverEntry);
1076
1108
  const templatePath = path.join(absClient, "index.html");
1077
1109
  const rawTemplate = await fs.readFile(templatePath, "utf8");
1110
+ if (writeSharedAssets) await writePreviewShell(absClient, rawTemplate);
1078
1111
  let template = stripTemplateSeoTags(rawTemplate, getSiteIconFlags(website));
1079
1112
  const siteHead = buildSiteHead(website);
1080
1113
  if (siteHead) {
@@ -1257,33 +1290,30 @@ async function prerender({
1257
1290
  console.log(" Generated robots.txt");
1258
1291
  if ((_e = website == null ? void 0 : website.seo) == null ? void 0 : _e.noindex) {
1259
1292
  const headersPath = path.join(absClient, "_headers");
1260
- let existing = "";
1293
+ let existing2 = "";
1261
1294
  try {
1262
- existing = await fs.readFile(headersPath, "utf8");
1295
+ existing2 = await fs.readFile(headersPath, "utf8");
1263
1296
  } catch {
1264
1297
  }
1265
- if (!existing.includes("X-Robots-Tag")) {
1298
+ if (!existing2.includes("X-Robots-Tag")) {
1266
1299
  await fs.writeFile(
1267
1300
  headersPath,
1268
1301
  `/*
1269
1302
  X-Robots-Tag: noindex, nofollow
1270
- ${existing}`,
1303
+ ${existing2}`,
1271
1304
  "utf8"
1272
1305
  );
1273
1306
  console.log(" Generated _headers (X-Robots-Tag: noindex)");
1274
1307
  }
1275
1308
  }
1276
- if (redirects.length > 0) {
1277
- const redirectsContent = generateNetlifyRedirects(redirects);
1278
- const redirectsPath = path.join(absClient, "_redirects");
1279
- let existing = "";
1280
- try {
1281
- existing = await fs.readFile(redirectsPath, "utf8");
1282
- } catch {
1283
- }
1284
- await fs.writeFile(redirectsPath, redirectsContent + existing, "utf8");
1285
- console.log(` Generated _redirects (${redirects.length} rules)`);
1309
+ const redirectsPath = path.join(absClient, "_redirects");
1310
+ let existing = "";
1311
+ try {
1312
+ existing = await fs.readFile(redirectsPath, "utf8");
1313
+ } catch {
1286
1314
  }
1315
+ await fs.writeFile(redirectsPath, previewRedirects(existing, generateNetlifyRedirects(redirects)), "utf8");
1316
+ console.log(` Generated _redirects (${redirects.length} CMS rules + editor preview)`);
1287
1317
  if (!((_f = website == null ? void 0 : website.seo) == null ? void 0 : _f.noindex) && llmEnabled && llm.writeIndex !== false && llmPages.length > 0) {
1288
1318
  const indexable = llmPages.filter((p) => !noindexPaths.has(p.url));
1289
1319
  const llmsTxt = generateLlmsTxt({ website, pages: indexable, config: llm });
@@ -1338,6 +1368,7 @@ function normalizePath$1(p) {
1338
1368
  }
1339
1369
  function enqueue(p, queue, seen, isExcluded) {
1340
1370
  const np = normalizePath$1(p);
1371
+ if (isPreviewPath(np)) return;
1341
1372
  if (seen.has(np)) return;
1342
1373
  if (isExcluded(np)) return;
1343
1374
  seen.add(np);
@@ -1669,8 +1700,35 @@ async function readSiteNoindex(outDir) {
1669
1700
  }
1670
1701
  return false;
1671
1702
  }
1703
+ async function validatePreviewOutput(outDir) {
1704
+ var _a, _b;
1705
+ const html = await fs.readFile(path.join(outDir, PREVIEW_FILE), "utf8");
1706
+ const redirects = await fs.readFile(path.join(outDir, "_redirects"), "utf8");
1707
+ const { Window } = await import("happy-dom");
1708
+ const window = new Window();
1709
+ try {
1710
+ const doc = window.document;
1711
+ doc.write(html);
1712
+ const robots = ((_a = doc.querySelector('meta[name="robots"]')) == null ? void 0 : _a.getAttribute("content")) ?? "";
1713
+ const app = doc.querySelector("#app");
1714
+ const scripts = [...doc.querySelectorAll('script[type="module"][src]')];
1715
+ if (!/\bnoindex\b/i.test(robots) || !/\bnofollow\b/i.test(robots) || doc.querySelector('link[rel="canonical"], link[hreflang]') || /__BLOX_(?:HYDRATION|STATE|DATA)__|data-server-rendered/.test(html) || (app ? app.children.length > 0 || !!((_b = app.textContent) == null ? void 0 : _b.trim()) : !html.includes("<!--app-html-->")) || !scripts.length || unhashedGeneratedAssets(html).length || !redirects.startsWith(PREVIEW_REWRITES)) {
1716
+ throw new Error("Invalid Blox preview output: expected a clean noindex client shell and first-priority preview rewrites");
1717
+ }
1718
+ for (const script of scripts) {
1719
+ const src = script.getAttribute("src") ?? "";
1720
+ if (!/^\/assets\/[A-Za-z0-9_./-]+$/.test(src) || src.split("/").includes("..") || !contentHash.test(src)) {
1721
+ throw new Error(`Invalid Blox preview client asset: ${src}`);
1722
+ }
1723
+ await fs.access(path.join(outDir, src));
1724
+ }
1725
+ } finally {
1726
+ await window.happyDOM.close();
1727
+ }
1728
+ }
1672
1729
  async function validateSsgOutput(options) {
1673
1730
  const outDir = path.resolve(options.outDir);
1731
+ await validatePreviewOutput(outDir);
1674
1732
  const failures = [];
1675
1733
  const checkInternalLinks = options.checkInternalLinks ?? true;
1676
1734
  const requireHashedAssets = options.requireHashedAssets ?? true;
@@ -1686,6 +1744,7 @@ async function validateSsgOutput(options) {
1686
1744
  for (const file of htmlFiles) {
1687
1745
  const name = path.relative(outDir, file);
1688
1746
  const html = await fs.readFile(file, "utf8");
1747
+ if (name === PREVIEW_FILE) continue;
1689
1748
  if (embeddedCmsState.test(html)) hydratedFiles += 1;
1690
1749
  if (!/<title>[^<]+<\/title>/i.test(html)) failures.push(`${name}: missing page title`);
1691
1750
  if (!/<link\s[^>]*rel=["']canonical["'][^>]*>/i.test(html)) {
@@ -1794,35 +1853,37 @@ async function validateSitemap(outDir, content) {
1794
1853
  return failures;
1795
1854
  }
1796
1855
  export {
1797
- mergeLlmParts as A,
1856
+ getSiteIconFlags as A,
1798
1857
  BloxBuildResponseError as B,
1799
- resolveManifestProvenance as C,
1800
- splitSitemapPathsByLocale as D,
1801
- llmArtifacts as E,
1858
+ localeSitemapPath as C,
1859
+ mergeLlmParts as D,
1860
+ resolveManifestProvenance as E,
1861
+ splitSitemapPathsByLocale as F,
1862
+ llmArtifacts as G,
1802
1863
  prerender as a,
1803
1864
  buildCollectionResolver as b,
1804
- validateInternalLinks as c,
1805
- validateNoRedirectingLinks as d,
1806
- buildPageHead as e,
1865
+ validatePreviewOutput as c,
1866
+ validateInternalLinks as d,
1867
+ validateNoRedirectingLinks as e,
1807
1868
  fetchCmsSiteData as f,
1808
1869
  generateSitemapArtifacts as g,
1809
- primaryContextEntry as h,
1870
+ isPreviewPath as h,
1810
1871
  isErrorPagePath as i,
1811
- buildJsonLd as j,
1812
- extractPageText as k,
1813
- bloxBuildJson as l,
1814
- bloxBuildRequest as m,
1815
- buildSiteHead as n,
1816
- generateLlmsFullTxt as o,
1872
+ buildPageHead as j,
1873
+ primaryContextEntry as k,
1874
+ buildJsonLd as l,
1875
+ extractPageText as m,
1876
+ bloxBuildJson as n,
1877
+ bloxBuildRequest as o,
1817
1878
  polyfillBloxSsgGlobals as p,
1818
- generateLlmsTxt as q,
1819
- generateNetlifyRedirects as r,
1820
- generateRobotsTxt as s,
1821
- generateSitemapIndexXml as t,
1879
+ buildSiteHead as q,
1880
+ generateLlmsFullTxt as r,
1881
+ generateLlmsTxt as s,
1882
+ generateNetlifyRedirects as t,
1822
1883
  uploadBloxManifest as u,
1823
1884
  validateSsgOutput as v,
1824
- generateSitemapXml as w,
1825
- getBloxBuildRequestOptions as x,
1826
- getSiteIconFlags as y,
1827
- localeSitemapPath as z
1885
+ generateRobotsTxt as w,
1886
+ generateSitemapIndexXml as x,
1887
+ generateSitemapXml as y,
1888
+ getBloxBuildRequestOptions as z
1828
1889
  };
@@ -837,6 +837,38 @@ function absUrl(value) {
837
837
  if (/^(https?:)?\/\//i.test(v) || v.startsWith("/")) return v;
838
838
  return `${FILES_BASE_URL}/${v.replace(/^\/+/, "")}`;
839
839
  }
840
+ const PREVIEW_PATH = "/_blox_preview";
841
+ const PREVIEW_FILE = "_blox_preview.html";
842
+ const PREVIEW_REWRITES = `${PREVIEW_PATH} /${PREVIEW_FILE} 200!
843
+ ${PREVIEW_PATH}/* /${PREVIEW_FILE} 200!
844
+ `;
845
+ function isPreviewPath(value) {
846
+ const pathname = value.split(/[?#]/)[0].replace(/\/+$/, "");
847
+ return pathname === PREVIEW_PATH || pathname === `/${PREVIEW_FILE}` || pathname.startsWith(`${PREVIEW_PATH}/`);
848
+ }
849
+ async function writePreviewShell(outDir, rawTemplate) {
850
+ const target = path.join(outDir, PREVIEW_FILE);
851
+ try {
852
+ await fs.access(target);
853
+ return;
854
+ } catch {
855
+ }
856
+ if (/__BLOX_(?:HYDRATION|STATE|DATA)__|data-server-rendered/.test(rawTemplate)) {
857
+ throw new Error("Cannot create Blox preview shell from prerendered HTML; rebuild the Vite client first");
858
+ }
859
+ const shell = stripTemplateSeoTags(rawTemplate).replace(/<meta\b[^>]*\bname\s*=\s*["'](?:robots|googlebot|bingbot)["'][^>]*>/gi, "").replace(/<link\b[^>]*\bhreflang\s*=[^>]*>/gi, "").replace(/<\/head>/i, '<title>Blox editor preview</title>\n<meta name="robots" content="noindex, nofollow">\n</head>');
860
+ await fs.writeFile(target, shell, "utf8");
861
+ }
862
+ function previewRedirects(existing, cms) {
863
+ const clean = (content) => content.split("\n").filter((line) => {
864
+ const source = line.trim().split(/\s+/)[0];
865
+ return !source || source.startsWith("#") || !isPreviewPath(source.replace(/\*$/, ""));
866
+ }).join("\n");
867
+ const cmsRules = clean(cms);
868
+ const generated = new Set(cmsRules.split("\n").filter((line) => line.trim() && !line.trim().startsWith("#")));
869
+ const userRules = clean(existing).split("\n").filter((line) => !generated.has(line)).join("\n");
870
+ return PREVIEW_REWRITES + [cmsRules.trim(), userRules.trim()].filter(Boolean).join("\n") + "\n";
871
+ }
840
872
  function typeFromContextKey(key) {
841
873
  const k = key.replace(/^\$/, "").toLowerCase();
842
874
  if (/(post|article|blog|story|news)/.test(k)) return "Article";
@@ -1098,6 +1130,7 @@ async function prerender({
1098
1130
  const absServerEntry = path.resolve(absRoot, serverEntry);
1099
1131
  const templatePath = path.join(absClient, "index.html");
1100
1132
  const rawTemplate = await fs.readFile(templatePath, "utf8");
1133
+ if (writeSharedAssets) await writePreviewShell(absClient, rawTemplate);
1101
1134
  let template = stripTemplateSeoTags(rawTemplate, getSiteIconFlags(website));
1102
1135
  const siteHead = buildSiteHead(website);
1103
1136
  if (siteHead) {
@@ -1280,33 +1313,30 @@ async function prerender({
1280
1313
  console.log(" Generated robots.txt");
1281
1314
  if ((_e = website == null ? void 0 : website.seo) == null ? void 0 : _e.noindex) {
1282
1315
  const headersPath = path.join(absClient, "_headers");
1283
- let existing = "";
1316
+ let existing2 = "";
1284
1317
  try {
1285
- existing = await fs.readFile(headersPath, "utf8");
1318
+ existing2 = await fs.readFile(headersPath, "utf8");
1286
1319
  } catch {
1287
1320
  }
1288
- if (!existing.includes("X-Robots-Tag")) {
1321
+ if (!existing2.includes("X-Robots-Tag")) {
1289
1322
  await fs.writeFile(
1290
1323
  headersPath,
1291
1324
  `/*
1292
1325
  X-Robots-Tag: noindex, nofollow
1293
- ${existing}`,
1326
+ ${existing2}`,
1294
1327
  "utf8"
1295
1328
  );
1296
1329
  console.log(" Generated _headers (X-Robots-Tag: noindex)");
1297
1330
  }
1298
1331
  }
1299
- if (redirects.length > 0) {
1300
- const redirectsContent = generateNetlifyRedirects(redirects);
1301
- const redirectsPath = path.join(absClient, "_redirects");
1302
- let existing = "";
1303
- try {
1304
- existing = await fs.readFile(redirectsPath, "utf8");
1305
- } catch {
1306
- }
1307
- await fs.writeFile(redirectsPath, redirectsContent + existing, "utf8");
1308
- console.log(` Generated _redirects (${redirects.length} rules)`);
1332
+ const redirectsPath = path.join(absClient, "_redirects");
1333
+ let existing = "";
1334
+ try {
1335
+ existing = await fs.readFile(redirectsPath, "utf8");
1336
+ } catch {
1309
1337
  }
1338
+ await fs.writeFile(redirectsPath, previewRedirects(existing, generateNetlifyRedirects(redirects)), "utf8");
1339
+ console.log(` Generated _redirects (${redirects.length} CMS rules + editor preview)`);
1310
1340
  if (!((_f = website == null ? void 0 : website.seo) == null ? void 0 : _f.noindex) && llmEnabled && llm.writeIndex !== false && llmPages.length > 0) {
1311
1341
  const indexable = llmPages.filter((p) => !noindexPaths.has(p.url));
1312
1342
  const llmsTxt = generateLlmsTxt({ website, pages: indexable, config: llm });
@@ -1361,6 +1391,7 @@ function normalizePath$1(p) {
1361
1391
  }
1362
1392
  function enqueue(p, queue, seen, isExcluded) {
1363
1393
  const np = normalizePath$1(p);
1394
+ if (isPreviewPath(np)) return;
1364
1395
  if (seen.has(np)) return;
1365
1396
  if (isExcluded(np)) return;
1366
1397
  seen.add(np);
@@ -1692,8 +1723,35 @@ async function readSiteNoindex(outDir) {
1692
1723
  }
1693
1724
  return false;
1694
1725
  }
1726
+ async function validatePreviewOutput(outDir) {
1727
+ var _a, _b;
1728
+ const html = await fs.readFile(path.join(outDir, PREVIEW_FILE), "utf8");
1729
+ const redirects = await fs.readFile(path.join(outDir, "_redirects"), "utf8");
1730
+ const { Window } = await import("happy-dom");
1731
+ const window = new Window();
1732
+ try {
1733
+ const doc = window.document;
1734
+ doc.write(html);
1735
+ const robots = ((_a = doc.querySelector('meta[name="robots"]')) == null ? void 0 : _a.getAttribute("content")) ?? "";
1736
+ const app = doc.querySelector("#app");
1737
+ const scripts = [...doc.querySelectorAll('script[type="module"][src]')];
1738
+ if (!/\bnoindex\b/i.test(robots) || !/\bnofollow\b/i.test(robots) || doc.querySelector('link[rel="canonical"], link[hreflang]') || /__BLOX_(?:HYDRATION|STATE|DATA)__|data-server-rendered/.test(html) || (app ? app.children.length > 0 || !!((_b = app.textContent) == null ? void 0 : _b.trim()) : !html.includes("<!--app-html-->")) || !scripts.length || unhashedGeneratedAssets(html).length || !redirects.startsWith(PREVIEW_REWRITES)) {
1739
+ throw new Error("Invalid Blox preview output: expected a clean noindex client shell and first-priority preview rewrites");
1740
+ }
1741
+ for (const script of scripts) {
1742
+ const src = script.getAttribute("src") ?? "";
1743
+ if (!/^\/assets\/[A-Za-z0-9_./-]+$/.test(src) || src.split("/").includes("..") || !contentHash.test(src)) {
1744
+ throw new Error(`Invalid Blox preview client asset: ${src}`);
1745
+ }
1746
+ await fs.access(path.join(outDir, src));
1747
+ }
1748
+ } finally {
1749
+ await window.happyDOM.close();
1750
+ }
1751
+ }
1695
1752
  async function validateSsgOutput(options) {
1696
1753
  const outDir = path.resolve(options.outDir);
1754
+ await validatePreviewOutput(outDir);
1697
1755
  const failures = [];
1698
1756
  const checkInternalLinks = options.checkInternalLinks ?? true;
1699
1757
  const requireHashedAssets = options.requireHashedAssets ?? true;
@@ -1709,6 +1767,7 @@ async function validateSsgOutput(options) {
1709
1767
  for (const file of htmlFiles) {
1710
1768
  const name = path.relative(outDir, file);
1711
1769
  const html = await fs.readFile(file, "utf8");
1770
+ if (name === PREVIEW_FILE) continue;
1712
1771
  if (embeddedCmsState.test(html)) hydratedFiles += 1;
1713
1772
  if (!/<title>[^<]+<\/title>/i.test(html)) failures.push(`${name}: missing page title`);
1714
1773
  if (!/<link\s[^>]*rel=["']canonical["'][^>]*>/i.test(html)) {
@@ -1835,6 +1894,7 @@ exports.generateSitemapXml = generateSitemapXml;
1835
1894
  exports.getBloxBuildRequestOptions = getBloxBuildRequestOptions;
1836
1895
  exports.getSiteIconFlags = getSiteIconFlags;
1837
1896
  exports.isErrorPagePath = isErrorPagePath;
1897
+ exports.isPreviewPath = isPreviewPath;
1838
1898
  exports.llmArtifacts = llmArtifacts;
1839
1899
  exports.localeSitemapPath = localeSitemapPath;
1840
1900
  exports.mergeLlmParts = mergeLlmParts;
@@ -1846,4 +1906,5 @@ exports.splitSitemapPathsByLocale = splitSitemapPathsByLocale;
1846
1906
  exports.uploadBloxManifest = uploadBloxManifest;
1847
1907
  exports.validateInternalLinks = validateInternalLinks;
1848
1908
  exports.validateNoRedirectingLinks = validateNoRedirectingLinks;
1909
+ exports.validatePreviewOutput = validatePreviewOutput;
1849
1910
  exports.validateSsgOutput = validateSsgOutput;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bagelink/blox",
3
3
  "type": "module",
4
- "version": "1.20.5",
4
+ "version": "1.20.6",
5
5
  "description": "Blox page builder library for drag-and-drop page building and static data management",
6
6
  "author": {
7
7
  "name": "Bagel Studio",