@bagelink/blox 1.15.531 → 1.15.533
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/{shard-runner-JAI_Tb4S.js → shard-runner-DFZAHrwJ.js} +2 -2
- package/dist/{shard-runner-Bn4JyeQC.cjs → shard-runner-DTkniXr1.cjs} +2 -2
- package/dist/ssg/cli.cjs +13 -2
- package/dist/ssg/cli.mjs +13 -2
- package/dist/ssg/index.cjs +2 -1
- package/dist/ssg/index.d.ts +2 -0
- package/dist/ssg/index.d.ts.map +1 -1
- package/dist/ssg/index.mjs +7 -6
- package/dist/ssg/validate-output.d.ts +6 -0
- package/dist/ssg/validate-output.d.ts.map +1 -0
- package/dist/{prerender-VtzdjvzI.js → validate-output-C-RpYkvf.js} +94 -4
- package/dist/{prerender-Dc2Zl2PC.cjs → validate-output-C3NhyFsU.cjs} +90 -0
- package/package.json +1 -1
|
@@ -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, s as splitSitemapPathsByLocale, l as localeSitemapPath, g as generateSitemapXml, b as generateSitemapIndexXml } from "./
|
|
5
|
+
import { f as fetchCmsSiteData, s as splitSitemapPathsByLocale, l as localeSitemapPath, g as generateSitemapXml, b as generateSitemapIndexXml } from "./validate-output-C-RpYkvf.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
|
function resolveShardCount(shards) {
|
|
@@ -109,7 +109,7 @@ async function runSharded(opts) {
|
|
|
109
109
|
}
|
|
110
110
|
if (process.env.BLOX_SSG_LLM !== "0") {
|
|
111
111
|
try {
|
|
112
|
-
const { mergeLlmParts } = await import("./
|
|
112
|
+
const { mergeLlmParts } = await import("./validate-output-C-RpYkvf.js").then((n) => n.z);
|
|
113
113
|
const mergeWebsite = website;
|
|
114
114
|
const partFiles = Array.from({ length: total }, (_, i) => path.join(llmPartsDir, `part.${i}.json`));
|
|
115
115
|
const { pages } = await mergeLlmParts({
|
|
@@ -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("./
|
|
29
|
+
const llmArtifacts = require("./validate-output-C3NhyFsU.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
|
function resolveShardCount(shards) {
|
|
@@ -133,7 +133,7 @@ async function runSharded(opts) {
|
|
|
133
133
|
}
|
|
134
134
|
if (process.env.BLOX_SSG_LLM !== "0") {
|
|
135
135
|
try {
|
|
136
|
-
const { mergeLlmParts } = await Promise.resolve().then(() => require("./
|
|
136
|
+
const { mergeLlmParts } = await Promise.resolve().then(() => require("./validate-output-C3NhyFsU.cjs")).then((n) => n.llmArtifacts);
|
|
137
137
|
const mergeWebsite = website;
|
|
138
138
|
const partFiles = Array.from({ length: total }, (_, i) => path.join(llmPartsDir, `part.${i}.json`));
|
|
139
139
|
const { pages } = await mergeLlmParts({
|
package/dist/ssg/cli.cjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
const process = require("node:process");
|
|
4
4
|
const fs = require("node:fs/promises");
|
|
5
|
-
const llmArtifacts = require("../
|
|
5
|
+
const llmArtifacts = require("../validate-output-C3NhyFsU.cjs");
|
|
6
6
|
function installFetchTimeout() {
|
|
7
7
|
const ms = Number(process.env.BLOX_SSG_FETCH_TIMEOUT_MS) || 2e4;
|
|
8
8
|
const original = globalThis.fetch;
|
|
@@ -56,6 +56,7 @@ async function main() {
|
|
|
56
56
|
let shardsOpt = null;
|
|
57
57
|
let routesCmd = "";
|
|
58
58
|
let siteDataFile = "";
|
|
59
|
+
let validateOutDir = "";
|
|
59
60
|
const passthroughArgs = [];
|
|
60
61
|
const excludePaths = ["/_blox_preview"];
|
|
61
62
|
for (const a of argv) {
|
|
@@ -71,6 +72,10 @@ async function main() {
|
|
|
71
72
|
routesCmd = a.slice("--routes-cmd=".length);
|
|
72
73
|
} else if (a.startsWith("--site-data=")) {
|
|
73
74
|
siteDataFile = a.slice("--site-data=".length);
|
|
75
|
+
} else if (a === "--validate") {
|
|
76
|
+
validateOutDir = "dist";
|
|
77
|
+
} else if (a.startsWith("--validate=")) {
|
|
78
|
+
validateOutDir = a.slice("--validate=".length);
|
|
74
79
|
} else if (a.startsWith("--shard=")) {
|
|
75
80
|
const [i, n] = a.slice("--shard=".length).split("/").map(Number);
|
|
76
81
|
if (Number.isInteger(i) && Number.isInteger(n) && n > 0 && i >= 0 && i < n) {
|
|
@@ -95,6 +100,7 @@ Options:
|
|
|
95
100
|
--shard=<i/n> (internal) Render only this shard's slice — set by --shards.
|
|
96
101
|
--routes-cmd=<cmd> Shell command whose stdout (whitespace-separated) is the
|
|
97
102
|
route list. For router-driven sites with no CMS pages.
|
|
103
|
+
--validate[=dir] Validate generated SSG assets (default directory: dist).
|
|
98
104
|
--help, -h Show this help
|
|
99
105
|
|
|
100
106
|
Environment:
|
|
@@ -120,10 +126,15 @@ Environment:
|
|
|
120
126
|
extraPaths.push(a);
|
|
121
127
|
}
|
|
122
128
|
}
|
|
129
|
+
if (validateOutDir) {
|
|
130
|
+
await llmArtifacts.validateSsgOutput({ outDir: validateOutDir });
|
|
131
|
+
console.log(`[blox-ssg] validated output in ${validateOutDir}`);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
123
134
|
if (shardsOpt !== null || routesCmd) {
|
|
124
135
|
const { apiBase: apiBase2, websiteName: configWebsiteName2 } = await resolveApiBase(mode);
|
|
125
136
|
const websiteName2 = process.env.WEBSITE_NAME ?? configWebsiteName2 ?? "default";
|
|
126
|
-
const { runSharded } = await Promise.resolve().then(() => require("../shard-runner-
|
|
137
|
+
const { runSharded } = await Promise.resolve().then(() => require("../shard-runner-DTkniXr1.cjs"));
|
|
127
138
|
const code = await runSharded({
|
|
128
139
|
shards: shardsOpt ?? "auto",
|
|
129
140
|
mode,
|
package/dist/ssg/cli.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import process from "node:process";
|
|
3
3
|
import fs from "node:fs/promises";
|
|
4
|
-
import { u as uploadBloxManifest, p as polyfillBloxSsgGlobals, f as fetchCmsSiteData, a as prerender } from "../
|
|
4
|
+
import { v as validateSsgOutput, u as uploadBloxManifest, p as polyfillBloxSsgGlobals, f as fetchCmsSiteData, a as prerender } from "../validate-output-C-RpYkvf.js";
|
|
5
5
|
function installFetchTimeout() {
|
|
6
6
|
const ms = Number(process.env.BLOX_SSG_FETCH_TIMEOUT_MS) || 2e4;
|
|
7
7
|
const original = globalThis.fetch;
|
|
@@ -55,6 +55,7 @@ async function main() {
|
|
|
55
55
|
let shardsOpt = null;
|
|
56
56
|
let routesCmd = "";
|
|
57
57
|
let siteDataFile = "";
|
|
58
|
+
let validateOutDir = "";
|
|
58
59
|
const passthroughArgs = [];
|
|
59
60
|
const excludePaths = ["/_blox_preview"];
|
|
60
61
|
for (const a of argv) {
|
|
@@ -70,6 +71,10 @@ async function main() {
|
|
|
70
71
|
routesCmd = a.slice("--routes-cmd=".length);
|
|
71
72
|
} else if (a.startsWith("--site-data=")) {
|
|
72
73
|
siteDataFile = a.slice("--site-data=".length);
|
|
74
|
+
} else if (a === "--validate") {
|
|
75
|
+
validateOutDir = "dist";
|
|
76
|
+
} else if (a.startsWith("--validate=")) {
|
|
77
|
+
validateOutDir = a.slice("--validate=".length);
|
|
73
78
|
} else if (a.startsWith("--shard=")) {
|
|
74
79
|
const [i, n] = a.slice("--shard=".length).split("/").map(Number);
|
|
75
80
|
if (Number.isInteger(i) && Number.isInteger(n) && n > 0 && i >= 0 && i < n) {
|
|
@@ -94,6 +99,7 @@ Options:
|
|
|
94
99
|
--shard=<i/n> (internal) Render only this shard's slice — set by --shards.
|
|
95
100
|
--routes-cmd=<cmd> Shell command whose stdout (whitespace-separated) is the
|
|
96
101
|
route list. For router-driven sites with no CMS pages.
|
|
102
|
+
--validate[=dir] Validate generated SSG assets (default directory: dist).
|
|
97
103
|
--help, -h Show this help
|
|
98
104
|
|
|
99
105
|
Environment:
|
|
@@ -119,10 +125,15 @@ Environment:
|
|
|
119
125
|
extraPaths.push(a);
|
|
120
126
|
}
|
|
121
127
|
}
|
|
128
|
+
if (validateOutDir) {
|
|
129
|
+
await validateSsgOutput({ outDir: validateOutDir });
|
|
130
|
+
console.log(`[blox-ssg] validated output in ${validateOutDir}`);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
122
133
|
if (shardsOpt !== null || routesCmd) {
|
|
123
134
|
const { apiBase: apiBase2, websiteName: configWebsiteName2 } = await resolveApiBase(mode);
|
|
124
135
|
const websiteName2 = process.env.WEBSITE_NAME ?? configWebsiteName2 ?? "default";
|
|
125
|
-
const { runSharded } = await import("../shard-runner-
|
|
136
|
+
const { runSharded } = await import("../shard-runner-DFZAHrwJ.js");
|
|
126
137
|
const code = await runSharded({
|
|
127
138
|
shards: shardsOpt ?? "auto",
|
|
128
139
|
mode,
|
package/dist/ssg/index.cjs
CHANGED
|
@@ -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("../
|
|
25
|
+
const llmArtifacts = require("../validate-output-C3NhyFsU.cjs");
|
|
26
26
|
const ssg_client = require("./client.cjs");
|
|
27
27
|
const pinia = require("pinia");
|
|
28
28
|
const vue = require("vue");
|
|
@@ -279,6 +279,7 @@ exports.prerender = llmArtifacts.prerender;
|
|
|
279
279
|
exports.resolveManifestProvenance = llmArtifacts.resolveManifestProvenance;
|
|
280
280
|
exports.splitSitemapPathsByLocale = llmArtifacts.splitSitemapPathsByLocale;
|
|
281
281
|
exports.uploadBloxManifest = llmArtifacts.uploadBloxManifest;
|
|
282
|
+
exports.validateSsgOutput = llmArtifacts.validateSsgOutput;
|
|
282
283
|
exports.BLOX_COLLECTIONS_WINDOW_KEY = ssg_client.BLOX_COLLECTIONS_WINDOW_KEY;
|
|
283
284
|
exports.BLOX_DATA_WINDOW_KEY = ssg_client.BLOX_DATA_WINDOW_KEY;
|
|
284
285
|
exports.BLOX_PAGE_SEO_WINDOW_KEY = ssg_client.BLOX_PAGE_SEO_WINDOW_KEY;
|
package/dist/ssg/index.d.ts
CHANGED
|
@@ -29,4 +29,6 @@ export type { LlmArtifactsConfig, LlmPageEntry } from './llm-artifacts';
|
|
|
29
29
|
export { installBloxStateCache } from './state-cache';
|
|
30
30
|
export { resolveManifestProvenance, uploadBloxManifest } from './manifest-upload';
|
|
31
31
|
export type { ManifestProvenance, ManifestUpload } from './manifest-upload';
|
|
32
|
+
export { validateSsgOutput } from './validate-output';
|
|
33
|
+
export type { ValidateSsgOutputOptions } from './validate-output';
|
|
32
34
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/ssg/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ssg/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AACvE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAC9D,OAAO,EAAE,sBAAsB,EAAE,aAAa,EAAE,gBAAgB,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAA;AACrH,YAAY,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAA;AAC9D,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AACrG;;;;;;;;;;GAUG;AACH,OAAO,EAAE,2BAA2B,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAA;AAC5J,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACrD,YAAY,EAAE,YAAY,EAAE,yBAAyB,EAAE,MAAM,kBAAkB,CAAA;AAC/E,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAA;AACxD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AACjG,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAC1D,YAAY,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAC/D,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,OAAO,CAAA;AAC9M,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AACvD,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,mBAAmB,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AACnH,YAAY,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AACvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AACrD,OAAO,EAAE,yBAAyB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AACjF,YAAY,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ssg/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AACvE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAC9D,OAAO,EAAE,sBAAsB,EAAE,aAAa,EAAE,gBAAgB,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAA;AACrH,YAAY,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAA;AAC9D,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AACrG;;;;;;;;;;GAUG;AACH,OAAO,EAAE,2BAA2B,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAA;AAC5J,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACrD,YAAY,EAAE,YAAY,EAAE,yBAAyB,EAAE,MAAM,kBAAkB,CAAA;AAC/E,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAA;AACxD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AACjG,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAC1D,YAAY,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAC/D,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,OAAO,CAAA;AAC9M,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AACvD,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,mBAAmB,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AACnH,YAAY,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AACvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AACrD,OAAO,EAAE,yBAAyB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AACjF,YAAY,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAC3E,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AACrD,YAAY,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAA"}
|
package/dist/ssg/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { c as buildPageHead, d as primaryContextEntry, e as buildJsonLd, h as extractPageText, B as BloxBuildResponseError, i as bloxBuildJson } from "../
|
|
2
|
-
import { j, k, m, f, n, o, q, r, b, g, t,
|
|
1
|
+
import { c as buildPageHead, d as primaryContextEntry, e as buildJsonLd, h as extractPageText, B as BloxBuildResponseError, i as bloxBuildJson } from "../validate-output-C-RpYkvf.js";
|
|
2
|
+
import { j, k, m, f, n, o, q, r, b, g, t, w, l, x, p, a, y, s, u, v } from "../validate-output-C-RpYkvf.js";
|
|
3
3
|
import { BLOX_STATE_WINDOW_KEY, BLOX_WEBSITE_ID_WINDOW_KEY } from "./client.mjs";
|
|
4
4
|
import { BLOX_COLLECTIONS_WINDOW_KEY, BLOX_DATA_WINDOW_KEY, BLOX_PAGE_SEO_WINDOW_KEY, getCollectionCache, getEmbeddedWebsiteId, installBloxStateCache, installCollectionCache } from "./client.mjs";
|
|
5
5
|
import { createPinia } from "pinia";
|
|
@@ -258,15 +258,16 @@ export {
|
|
|
258
258
|
t as getBloxBuildRequestOptions,
|
|
259
259
|
getCollectionCache,
|
|
260
260
|
getEmbeddedWebsiteId,
|
|
261
|
-
|
|
261
|
+
w as getSiteIconFlags,
|
|
262
262
|
installBloxStateCache,
|
|
263
263
|
installCollectionCache,
|
|
264
264
|
l as localeSitemapPath,
|
|
265
|
-
|
|
265
|
+
x as mergeLlmParts,
|
|
266
266
|
p as polyfillBloxSsgGlobals,
|
|
267
267
|
a as prerender,
|
|
268
268
|
renderBloxSsgPage,
|
|
269
|
-
|
|
269
|
+
y as resolveManifestProvenance,
|
|
270
270
|
s as splitSitemapPathsByLocale,
|
|
271
|
-
u as uploadBloxManifest
|
|
271
|
+
u as uploadBloxManifest,
|
|
272
|
+
v as validateSsgOutput
|
|
272
273
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-output.d.ts","sourceRoot":"","sources":["../../src/ssg/validate-output.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,wBAAwB;IACxC,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;CACvB;AAED,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiDxF"}
|
|
@@ -1171,6 +1171,95 @@ function discoverInternalLinks(html) {
|
|
|
1171
1171
|
}
|
|
1172
1172
|
return [...out];
|
|
1173
1173
|
}
|
|
1174
|
+
const sitemapUrl = /<url>\s*<loc>[^<]+<\/loc>/m;
|
|
1175
|
+
const sitemapIndexEntry = /<sitemap>\s*<loc>[^<]+<\/loc>/gm;
|
|
1176
|
+
const sitemapLoc = /<loc>([^<]+)<\/loc>/;
|
|
1177
|
+
const loadingShell = /class=["'][^"']*\bblox-initial-loading\b/;
|
|
1178
|
+
const embeddedCmsState = /\b__BLOX_(?:STATE|DATA)__\b/;
|
|
1179
|
+
async function validateSsgOutput(options) {
|
|
1180
|
+
const outDir = path.resolve(options.outDir);
|
|
1181
|
+
const failures = [];
|
|
1182
|
+
const requiredFiles = options.requireFiles ?? [
|
|
1183
|
+
"robots.txt",
|
|
1184
|
+
"_redirects",
|
|
1185
|
+
"sitemap.xml",
|
|
1186
|
+
"llms.txt",
|
|
1187
|
+
"llms-full.txt"
|
|
1188
|
+
];
|
|
1189
|
+
const htmlFiles = await collectHtmlFiles(outDir);
|
|
1190
|
+
let hydratedFiles = 0;
|
|
1191
|
+
for (const file of htmlFiles) {
|
|
1192
|
+
const name = path.relative(outDir, file);
|
|
1193
|
+
const html = await fs.readFile(file, "utf8");
|
|
1194
|
+
if (embeddedCmsState.test(html)) hydratedFiles += 1;
|
|
1195
|
+
if (!/<title>[^<]+<\/title>/i.test(html)) failures.push(`${name}: missing page title`);
|
|
1196
|
+
if (!/<link\s[^>]*rel=["']canonical["'][^>]*>/i.test(html)) {
|
|
1197
|
+
failures.push(`${name}: missing canonical URL`);
|
|
1198
|
+
}
|
|
1199
|
+
if (!/<html[^>]*\slang=["'][^"']+["']/i.test(html)) failures.push(`${name}: missing html lang`);
|
|
1200
|
+
if (loadingShell.test(html) && !embeddedCmsState.test(html)) {
|
|
1201
|
+
failures.push(`${name}: contains only the Blox loading shell`);
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
if (!htmlFiles.length) failures.push("output contains no HTML files");
|
|
1205
|
+
if (!hydratedFiles) failures.push("output contains no prerendered Blox hydration state");
|
|
1206
|
+
for (const required of requiredFiles) {
|
|
1207
|
+
try {
|
|
1208
|
+
const content = await fs.readFile(path.join(outDir, required), "utf8");
|
|
1209
|
+
if (required === "robots.txt" && /^Disallow:\s*\/\s*$/im.test(content)) {
|
|
1210
|
+
failures.push("robots.txt blocks the entire site");
|
|
1211
|
+
}
|
|
1212
|
+
if (required === "llms.txt" && !/^- \[[^\]]+\]\([^)]+\)/m.test(content)) {
|
|
1213
|
+
failures.push("llms.txt contains no page links");
|
|
1214
|
+
}
|
|
1215
|
+
if (required === "sitemap.xml") {
|
|
1216
|
+
failures.push(...await validateSitemap(outDir, content));
|
|
1217
|
+
}
|
|
1218
|
+
} catch {
|
|
1219
|
+
failures.push(`missing ${required}`);
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
if (failures.length) {
|
|
1223
|
+
throw new Error(`Invalid Blox SSG output:
|
|
1224
|
+
${failures.map((item) => ` - ${item}`).join("\n")}`);
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
async function collectHtmlFiles(directory) {
|
|
1228
|
+
const entries = await fs.readdir(directory, { withFileTypes: true });
|
|
1229
|
+
const files = await Promise.all(entries.map(async (entry) => {
|
|
1230
|
+
const child = path.join(directory, entry.name);
|
|
1231
|
+
if (entry.isDirectory() && entry.name !== "server") return collectHtmlFiles(child);
|
|
1232
|
+
return entry.isFile() && entry.name.endsWith(".html") ? [child] : [];
|
|
1233
|
+
}));
|
|
1234
|
+
return files.flat();
|
|
1235
|
+
}
|
|
1236
|
+
async function validateSitemap(outDir, content) {
|
|
1237
|
+
var _a;
|
|
1238
|
+
if (sitemapUrl.test(content)) return [];
|
|
1239
|
+
const failures = [];
|
|
1240
|
+
const entries = content.match(sitemapIndexEntry) ?? [];
|
|
1241
|
+
if (!entries.length) return ["sitemap.xml contains neither page URLs nor child sitemaps"];
|
|
1242
|
+
let childUrlCount = 0;
|
|
1243
|
+
for (const entry of entries) {
|
|
1244
|
+
const loc = (_a = entry.match(sitemapLoc)) == null ? void 0 : _a[1];
|
|
1245
|
+
if (!loc) continue;
|
|
1246
|
+
const pathname = new URL(loc).pathname.replace(/^\/+/, "");
|
|
1247
|
+
const childPath = path.resolve(outDir, pathname);
|
|
1248
|
+
if (!childPath.startsWith(`${outDir}${path.sep}`)) {
|
|
1249
|
+
failures.push(`child sitemap escapes output directory: ${pathname}`);
|
|
1250
|
+
continue;
|
|
1251
|
+
}
|
|
1252
|
+
try {
|
|
1253
|
+
const child = await fs.readFile(childPath, "utf8");
|
|
1254
|
+
if (!sitemapUrl.test(child)) failures.push(`${pathname} contains no page URLs`);
|
|
1255
|
+
else childUrlCount += (child.match(/<url>/g) ?? []).length;
|
|
1256
|
+
} catch {
|
|
1257
|
+
failures.push(`missing child sitemap ${pathname}`);
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
if (!childUrlCount) failures.push("sitemap index child files contain no page URLs");
|
|
1261
|
+
return failures;
|
|
1262
|
+
}
|
|
1174
1263
|
export {
|
|
1175
1264
|
BloxBuildResponseError as B,
|
|
1176
1265
|
prerender as a,
|
|
@@ -1194,8 +1283,9 @@ export {
|
|
|
1194
1283
|
splitSitemapPathsByLocale as s,
|
|
1195
1284
|
getBloxBuildRequestOptions as t,
|
|
1196
1285
|
uploadBloxManifest as u,
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1286
|
+
validateSsgOutput as v,
|
|
1287
|
+
getSiteIconFlags as w,
|
|
1288
|
+
mergeLlmParts as x,
|
|
1289
|
+
resolveManifestProvenance as y,
|
|
1290
|
+
llmArtifacts as z
|
|
1201
1291
|
};
|
|
@@ -1194,6 +1194,95 @@ function discoverInternalLinks(html) {
|
|
|
1194
1194
|
}
|
|
1195
1195
|
return [...out];
|
|
1196
1196
|
}
|
|
1197
|
+
const sitemapUrl = /<url>\s*<loc>[^<]+<\/loc>/m;
|
|
1198
|
+
const sitemapIndexEntry = /<sitemap>\s*<loc>[^<]+<\/loc>/gm;
|
|
1199
|
+
const sitemapLoc = /<loc>([^<]+)<\/loc>/;
|
|
1200
|
+
const loadingShell = /class=["'][^"']*\bblox-initial-loading\b/;
|
|
1201
|
+
const embeddedCmsState = /\b__BLOX_(?:STATE|DATA)__\b/;
|
|
1202
|
+
async function validateSsgOutput(options) {
|
|
1203
|
+
const outDir = path.resolve(options.outDir);
|
|
1204
|
+
const failures = [];
|
|
1205
|
+
const requiredFiles = options.requireFiles ?? [
|
|
1206
|
+
"robots.txt",
|
|
1207
|
+
"_redirects",
|
|
1208
|
+
"sitemap.xml",
|
|
1209
|
+
"llms.txt",
|
|
1210
|
+
"llms-full.txt"
|
|
1211
|
+
];
|
|
1212
|
+
const htmlFiles = await collectHtmlFiles(outDir);
|
|
1213
|
+
let hydratedFiles = 0;
|
|
1214
|
+
for (const file of htmlFiles) {
|
|
1215
|
+
const name = path.relative(outDir, file);
|
|
1216
|
+
const html = await fs.readFile(file, "utf8");
|
|
1217
|
+
if (embeddedCmsState.test(html)) hydratedFiles += 1;
|
|
1218
|
+
if (!/<title>[^<]+<\/title>/i.test(html)) failures.push(`${name}: missing page title`);
|
|
1219
|
+
if (!/<link\s[^>]*rel=["']canonical["'][^>]*>/i.test(html)) {
|
|
1220
|
+
failures.push(`${name}: missing canonical URL`);
|
|
1221
|
+
}
|
|
1222
|
+
if (!/<html[^>]*\slang=["'][^"']+["']/i.test(html)) failures.push(`${name}: missing html lang`);
|
|
1223
|
+
if (loadingShell.test(html) && !embeddedCmsState.test(html)) {
|
|
1224
|
+
failures.push(`${name}: contains only the Blox loading shell`);
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
if (!htmlFiles.length) failures.push("output contains no HTML files");
|
|
1228
|
+
if (!hydratedFiles) failures.push("output contains no prerendered Blox hydration state");
|
|
1229
|
+
for (const required of requiredFiles) {
|
|
1230
|
+
try {
|
|
1231
|
+
const content = await fs.readFile(path.join(outDir, required), "utf8");
|
|
1232
|
+
if (required === "robots.txt" && /^Disallow:\s*\/\s*$/im.test(content)) {
|
|
1233
|
+
failures.push("robots.txt blocks the entire site");
|
|
1234
|
+
}
|
|
1235
|
+
if (required === "llms.txt" && !/^- \[[^\]]+\]\([^)]+\)/m.test(content)) {
|
|
1236
|
+
failures.push("llms.txt contains no page links");
|
|
1237
|
+
}
|
|
1238
|
+
if (required === "sitemap.xml") {
|
|
1239
|
+
failures.push(...await validateSitemap(outDir, content));
|
|
1240
|
+
}
|
|
1241
|
+
} catch {
|
|
1242
|
+
failures.push(`missing ${required}`);
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
if (failures.length) {
|
|
1246
|
+
throw new Error(`Invalid Blox SSG output:
|
|
1247
|
+
${failures.map((item) => ` - ${item}`).join("\n")}`);
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
async function collectHtmlFiles(directory) {
|
|
1251
|
+
const entries = await fs.readdir(directory, { withFileTypes: true });
|
|
1252
|
+
const files = await Promise.all(entries.map(async (entry) => {
|
|
1253
|
+
const child = path.join(directory, entry.name);
|
|
1254
|
+
if (entry.isDirectory() && entry.name !== "server") return collectHtmlFiles(child);
|
|
1255
|
+
return entry.isFile() && entry.name.endsWith(".html") ? [child] : [];
|
|
1256
|
+
}));
|
|
1257
|
+
return files.flat();
|
|
1258
|
+
}
|
|
1259
|
+
async function validateSitemap(outDir, content) {
|
|
1260
|
+
var _a;
|
|
1261
|
+
if (sitemapUrl.test(content)) return [];
|
|
1262
|
+
const failures = [];
|
|
1263
|
+
const entries = content.match(sitemapIndexEntry) ?? [];
|
|
1264
|
+
if (!entries.length) return ["sitemap.xml contains neither page URLs nor child sitemaps"];
|
|
1265
|
+
let childUrlCount = 0;
|
|
1266
|
+
for (const entry of entries) {
|
|
1267
|
+
const loc = (_a = entry.match(sitemapLoc)) == null ? void 0 : _a[1];
|
|
1268
|
+
if (!loc) continue;
|
|
1269
|
+
const pathname = new URL(loc).pathname.replace(/^\/+/, "");
|
|
1270
|
+
const childPath = path.resolve(outDir, pathname);
|
|
1271
|
+
if (!childPath.startsWith(`${outDir}${path.sep}`)) {
|
|
1272
|
+
failures.push(`child sitemap escapes output directory: ${pathname}`);
|
|
1273
|
+
continue;
|
|
1274
|
+
}
|
|
1275
|
+
try {
|
|
1276
|
+
const child = await fs.readFile(childPath, "utf8");
|
|
1277
|
+
if (!sitemapUrl.test(child)) failures.push(`${pathname} contains no page URLs`);
|
|
1278
|
+
else childUrlCount += (child.match(/<url>/g) ?? []).length;
|
|
1279
|
+
} catch {
|
|
1280
|
+
failures.push(`missing child sitemap ${pathname}`);
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
if (!childUrlCount) failures.push("sitemap index child files contain no page URLs");
|
|
1284
|
+
return failures;
|
|
1285
|
+
}
|
|
1197
1286
|
exports.BloxBuildResponseError = BloxBuildResponseError;
|
|
1198
1287
|
exports.bloxBuildJson = bloxBuildJson;
|
|
1199
1288
|
exports.bloxBuildRequest = bloxBuildRequest;
|
|
@@ -1220,3 +1309,4 @@ exports.primaryContextEntry = primaryContextEntry;
|
|
|
1220
1309
|
exports.resolveManifestProvenance = resolveManifestProvenance;
|
|
1221
1310
|
exports.splitSitemapPathsByLocale = splitSitemapPathsByLocale;
|
|
1222
1311
|
exports.uploadBloxManifest = uploadBloxManifest;
|
|
1312
|
+
exports.validateSsgOutput = validateSsgOutput;
|
package/package.json
CHANGED