pattern-dump 0.2.0-alpha.0 → 0.4.0
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/CHANGELOG.md +83 -0
- package/README.md +12 -7
- package/dist/chunks/{chunk-ESMZAOP2.js → chunk-DIDJPCLS.js} +177 -18
- package/dist/index.js +1 -1
- package/dist/pd.js +1689 -766
- package/dist/types/config/index.d.ts +21 -6
- package/dist/types/config/own-manifest.d.ts +9 -0
- package/dist/types/config/validate.d.ts +15 -2
- package/dist/types/contracts/dev-protocol.d.ts +1 -1
- package/package.json +1 -1
- package/templates/storefront/.github/workflows/pages.yml +3 -2
- package/templates/storefront/pages/articles/hello.md +10 -0
- package/templates/storefront/pages/articles/index.md +10 -0
- package/templates/storefront/src/patterns/layouts/listing/listing.hbs +16 -0
- package/templates/storefront/src/patterns/layouts/listing/listing.json +13 -0
- package/templates/storefront/src/patterns/layouts/listing/listing.md +7 -0
- package/templates/storefront/src/patterns/layouts/listing/listing.schema.json +18 -0
- package/templates/storefront/src/patterns/layouts/page/page.hbs +1 -0
- package/templates/storefront/src/patterns/layouts/page/page.json +1 -1
- package/templates/storefront/src/patterns/layouts/page/page.md +2 -1
- package/templates/storefront/src/patterns/layouts/page/page.schema.json +3 -1
- package/templates/storefront/src/styles/layouts/listing.scss +15 -0
- package/templates/storefront/src/styles/layouts/page.scss +3 -0
- package/templates/storefront-fills/config.txt +4 -1
- package/templates/storefront-fills/readme.md +11 -3
package/dist/pd.js
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
import {
|
|
3
3
|
ConfigError,
|
|
4
4
|
sameHostWorkbenchPath,
|
|
5
|
+
storefrontBasePath,
|
|
5
6
|
suggest,
|
|
6
|
-
validateConfig
|
|
7
|
-
|
|
7
|
+
validateConfig,
|
|
8
|
+
withOwnManifest
|
|
9
|
+
} from "./chunks/chunk-DIDJPCLS.js";
|
|
8
10
|
|
|
9
11
|
// packages/contracts/src/dev-protocol.ts
|
|
10
12
|
var AUDIT_SCOPE = {
|
|
@@ -335,9 +337,9 @@ async function buildDiscoveryMap(roots) {
|
|
|
335
337
|
const dir = dirname(absPath);
|
|
336
338
|
const stem = basename(absPath, ".hbs");
|
|
337
339
|
const fileSet = dirFileSets.get(dir) ?? /* @__PURE__ */ new Set();
|
|
338
|
-
const
|
|
340
|
+
const source2 = rootOf.get(absPath)?.root.source;
|
|
339
341
|
return {
|
|
340
|
-
...
|
|
342
|
+
...source2 ? { source: source2 } : {},
|
|
341
343
|
hbsPath: absPath,
|
|
342
344
|
jsonPath: fileSet.has(`${stem}.json`) ? join(dir, `${stem}.json`) : void 0,
|
|
343
345
|
mdPath: fileSet.has(`${stem}.md`) ? join(dir, `${stem}.md`) : void 0,
|
|
@@ -395,8 +397,8 @@ async function buildDiscoveryMap(roots) {
|
|
|
395
397
|
const baseName = basename(varPaths[0], ".hbs").split("~")[0];
|
|
396
398
|
const fileSet = dirFileSets.get(varDir) ?? /* @__PURE__ */ new Set();
|
|
397
399
|
const mdPath = fileSet.has(`${baseName}.md`) ? join(varDir, `${baseName}.md`) : void 0;
|
|
398
|
-
const
|
|
399
|
-
discoveryMap.set(baseId, { ...
|
|
400
|
+
const source2 = rootOf.get(varPaths[0])?.root.source;
|
|
401
|
+
discoveryMap.set(baseId, { ...source2 ? { source: source2 } : {}, hbsPath: "", ownMarkup: false, variants: variantTokens, mdPath });
|
|
400
402
|
handleMap.set(handle, baseId);
|
|
401
403
|
}
|
|
402
404
|
}
|
|
@@ -426,8 +428,8 @@ async function buildDiscoveryMap(roots) {
|
|
|
426
428
|
if (!baseEntry?.hbsPath) continue;
|
|
427
429
|
const variantHandle = `${deriveHandle(baseId)}~${variantToken}`;
|
|
428
430
|
if (!handleMap.has(variantHandle)) {
|
|
429
|
-
const
|
|
430
|
-
discoveryMap.set(variantId, { ...
|
|
431
|
+
const source2 = rootOf.get(jsonPath)?.root.source;
|
|
432
|
+
discoveryMap.set(variantId, { ...source2 ? { source: source2 } : {}, hbsPath: baseEntry.hbsPath, jsonPath, ownMarkup: false });
|
|
431
433
|
handleMap.set(variantHandle, variantId);
|
|
432
434
|
}
|
|
433
435
|
}
|
|
@@ -899,21 +901,82 @@ function checkSchemaHelperNames(schema, helpers) {
|
|
|
899
901
|
}
|
|
900
902
|
var isHelperNameProblem = (message) => message.includes(`" ${HELPER_NAME}, so {{`);
|
|
901
903
|
|
|
904
|
+
// packages/renderer/src/helper-shadow.ts
|
|
905
|
+
import Handlebars3 from "handlebars";
|
|
906
|
+
function keysOf(data) {
|
|
907
|
+
const found = /* @__PURE__ */ new Map();
|
|
908
|
+
const walk = (v, path) => {
|
|
909
|
+
if (Array.isArray(v)) {
|
|
910
|
+
for (const item of v) walk(item, `${path}[]`);
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
if (typeof v !== "object" || v === null) return;
|
|
914
|
+
for (const [k, child] of Object.entries(v)) {
|
|
915
|
+
const here = path ? `${path}.${k}` : k;
|
|
916
|
+
if (!found.has(k)) found.set(k, here);
|
|
917
|
+
walk(child, here);
|
|
918
|
+
}
|
|
919
|
+
};
|
|
920
|
+
walk(data, "");
|
|
921
|
+
return found;
|
|
922
|
+
}
|
|
923
|
+
function helperShadows(template, data, helpers, skip = /* @__PURE__ */ new Set()) {
|
|
924
|
+
let ast;
|
|
925
|
+
try {
|
|
926
|
+
ast = Handlebars3.parse(template);
|
|
927
|
+
} catch {
|
|
928
|
+
return [];
|
|
929
|
+
}
|
|
930
|
+
const helperNames = new Set(helpers);
|
|
931
|
+
const keys = keysOf(data);
|
|
932
|
+
const shadows = [];
|
|
933
|
+
const seen = /* @__PURE__ */ new Set();
|
|
934
|
+
const visit2 = (node) => {
|
|
935
|
+
if (Array.isArray(node)) {
|
|
936
|
+
node.forEach(visit2);
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
if (typeof node !== "object" || node === null) return;
|
|
940
|
+
const n = node;
|
|
941
|
+
if (n.type === "MustacheStatement") {
|
|
942
|
+
const path = n["path"];
|
|
943
|
+
const params = n["params"];
|
|
944
|
+
const hash = n["hash"];
|
|
945
|
+
const name = path?.parts?.[0];
|
|
946
|
+
if (path?.type === "PathExpression" && !path.data && path.parts?.length === 1 && path.original === name && !params?.length && !hash?.pairs?.length && name && helperNames.has(name) && keys.has(name) && !skip.has(name)) {
|
|
947
|
+
const loc = n["loc"];
|
|
948
|
+
const key = `${name}@${loc?.start?.line}:${loc?.start?.column}`;
|
|
949
|
+
if (!seen.has(key)) {
|
|
950
|
+
seen.add(key);
|
|
951
|
+
shadows.push({ name, at: keys.get(name), line: loc?.start?.line ?? 1, column: (loc?.start?.column ?? 0) + 1 });
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
for (const [k, v] of Object.entries(n)) if (k !== "loc" && typeof v === "object") visit2(v);
|
|
956
|
+
};
|
|
957
|
+
visit2(ast);
|
|
958
|
+
return shadows;
|
|
959
|
+
}
|
|
960
|
+
|
|
902
961
|
// packages/renderer/src/index.ts
|
|
903
962
|
import { HtmlValidate } from "html-validate";
|
|
904
|
-
import
|
|
963
|
+
import Handlebars4 from "handlebars";
|
|
905
964
|
import { readFile } from "node:fs/promises";
|
|
906
965
|
import { basename as basename2, isAbsolute, relative as relative2, sep } from "node:path";
|
|
966
|
+
var COMPILE = { preventIndent: true };
|
|
907
967
|
var htmlValidator = new HtmlValidate({
|
|
908
968
|
rules: {
|
|
909
969
|
"element-permitted-content": "error",
|
|
910
970
|
"element-permitted-parent": "error"
|
|
911
971
|
}
|
|
912
972
|
});
|
|
973
|
+
function renderPlainTemplate(source2, data) {
|
|
974
|
+
return Handlebars4.create().compile(source2, COMPILE)(data);
|
|
975
|
+
}
|
|
913
976
|
function deepMerge(target, ...sources) {
|
|
914
|
-
for (const
|
|
915
|
-
if (!
|
|
916
|
-
for (const [key, value] of Object.entries(
|
|
977
|
+
for (const source2 of sources) {
|
|
978
|
+
if (!source2) continue;
|
|
979
|
+
for (const [key, value] of Object.entries(source2)) {
|
|
917
980
|
const targetVal = target[key];
|
|
918
981
|
if (value !== null && typeof value === "object" && !Array.isArray(value) && targetVal !== null && typeof targetVal === "object" && !Array.isArray(targetVal)) {
|
|
919
982
|
deepMerge(targetVal, value);
|
|
@@ -955,16 +1018,16 @@ async function readDataFile(path, display) {
|
|
|
955
1018
|
};
|
|
956
1019
|
}
|
|
957
1020
|
}
|
|
958
|
-
function imageTag(
|
|
959
|
-
const esc =
|
|
1021
|
+
function imageTag(source2, o) {
|
|
1022
|
+
const esc = Handlebars4.escapeExpression;
|
|
960
1023
|
const rest = `alt="${esc(o.alt)}"${o.title !== void 0 ? ` title="${esc(o.title)}"` : ""} loading="${o.eager ? "eager" : "lazy"}" decoding="async"`;
|
|
961
|
-
if (
|
|
962
|
-
const widths = o.widths.filter((w) => w <=
|
|
963
|
-
if (widths.length === 0) widths.push(
|
|
964
|
-
const urls = widths.map((w) =>
|
|
1024
|
+
if (source2.kind === "single") return `<img src="${esc(source2.url)}" width="${source2.width}" height="${source2.height}" ${rest}>`;
|
|
1025
|
+
const widths = o.widths.filter((w) => w <= source2.width);
|
|
1026
|
+
if (widths.length === 0) widths.push(source2.width);
|
|
1027
|
+
const urls = widths.map((w) => source2.variantUrl(w, o.settings));
|
|
965
1028
|
const top = widths[widths.length - 1];
|
|
966
1029
|
const srcset = urls.map((u, i) => `${esc(u)} ${widths[i]}w`).join(", ");
|
|
967
|
-
return `<img src="${esc(urls[urls.length - 1])}" srcset="${srcset}" sizes="${esc(o.sizes)}" width="${top}" height="${Math.round(top *
|
|
1030
|
+
return `<img src="${esc(urls[urls.length - 1])}" srcset="${srcset}" sizes="${esc(o.sizes)}" width="${top}" height="${Math.round(top * source2.height / source2.width)}" ${rest}>`;
|
|
968
1031
|
}
|
|
969
1032
|
function registerImageHelper(env, images) {
|
|
970
1033
|
env.registerHelper("pd-image", function(value, options) {
|
|
@@ -979,11 +1042,11 @@ function registerImageHelper(env, images) {
|
|
|
979
1042
|
}
|
|
980
1043
|
if (typeof value !== "string" || value === "") throw new Error(`{{pd-image}}: the image field is empty`);
|
|
981
1044
|
if (!images) throw new Error(`{{pd-image}}: this renderer was given no image index`);
|
|
982
|
-
const
|
|
983
|
-
if (!
|
|
984
|
-
if (
|
|
1045
|
+
const source2 = images(value);
|
|
1046
|
+
if (!source2) throw new Error(`{{pd-image}}: "${value}" is not an image in the assets`);
|
|
1047
|
+
if (source2.kind === "unavailable") throw new Error(source2.reason);
|
|
985
1048
|
const settings = options.data?.pdImages;
|
|
986
|
-
return new env.SafeString(imageTag(
|
|
1049
|
+
return new env.SafeString(imageTag(source2, {
|
|
987
1050
|
widths: slot.widths,
|
|
988
1051
|
sizes: slot.sizes,
|
|
989
1052
|
alt: String(alt),
|
|
@@ -1102,7 +1165,7 @@ function makeDisplayPath(roots, displayPath) {
|
|
|
1102
1165
|
}
|
|
1103
1166
|
function dryRunHelperRegistrar(register) {
|
|
1104
1167
|
try {
|
|
1105
|
-
register(
|
|
1168
|
+
register(Handlebars4.create());
|
|
1106
1169
|
return null;
|
|
1107
1170
|
} catch (e) {
|
|
1108
1171
|
return e instanceof Error ? e.message : String(e);
|
|
@@ -1153,7 +1216,7 @@ var createRenderer = (options) => {
|
|
|
1153
1216
|
variantData,
|
|
1154
1217
|
input.data
|
|
1155
1218
|
);
|
|
1156
|
-
const env =
|
|
1219
|
+
const env = Handlebars4.create();
|
|
1157
1220
|
const componentSlug = safePart(componentId.replace(/[\\/]/g, "-"));
|
|
1158
1221
|
let currentBase = `pd-${componentSlug}`;
|
|
1159
1222
|
const idCache = /* @__PURE__ */ new Map();
|
|
@@ -1197,7 +1260,7 @@ var createRenderer = (options) => {
|
|
|
1197
1260
|
const baseJsonPath = variantTilde > 0 ? discoveryMap.get(compId.slice(0, variantTilde))?.jsonPath : void 0;
|
|
1198
1261
|
const baseData = baseJsonPath ? await readDataFile(baseJsonPath, display) : {};
|
|
1199
1262
|
const childDefaults = deepMerge(structuredClone(baseData.data ?? {}), childData.data);
|
|
1200
|
-
const childTemplate = env.compile(await slotted(partialSrc, compId));
|
|
1263
|
+
const childTemplate = env.compile(await slotted(partialSrc, compId), COMPILE);
|
|
1201
1264
|
const capturedDefaults = childDefaults;
|
|
1202
1265
|
const partialSlug = safePart(compId.replace(/[\\/]/g, "-"));
|
|
1203
1266
|
env.registerPartial(
|
|
@@ -1240,7 +1303,7 @@ var createRenderer = (options) => {
|
|
|
1240
1303
|
}
|
|
1241
1304
|
let html;
|
|
1242
1305
|
try {
|
|
1243
|
-
html = env.compile(await slotted(templateSrc, componentId))(data, {
|
|
1306
|
+
html = env.compile(await slotted(templateSrc, componentId), COMPILE)(data, {
|
|
1244
1307
|
partials: { ...env.partials, "@partial-block": () => "" },
|
|
1245
1308
|
// The render's image settings, for {{pd-image}} here and in every partial (data frames pass on).
|
|
1246
1309
|
data: { pdImages: input.images }
|
|
@@ -1321,6 +1384,16 @@ var createRenderer = (options) => {
|
|
|
1321
1384
|
}
|
|
1322
1385
|
} catch {
|
|
1323
1386
|
}
|
|
1387
|
+
const schemaNamed = new Set(diagnostics.filter((d) => d.kind === "SCHEMA_INVALID" && d.path).map((d) => /\/properties\/([^/]+)$/.exec(d.path)?.[1]).filter((n) => !!n));
|
|
1388
|
+
for (const s of helperShadows(templateSrc, data, Object.keys(env.helpers), schemaNamed)) {
|
|
1389
|
+
diagnostics.push({
|
|
1390
|
+
kind: "HELPER_SHADOW",
|
|
1391
|
+
severity: "warning",
|
|
1392
|
+
source: "render",
|
|
1393
|
+
message: `{{${s.name}}} calls the helper "${s.name}", so the data's "${s.at}" is never read here: write {{this.${s.name}}} to read the field, or give the field another name`,
|
|
1394
|
+
location: { file: display(entry.hbsPath), line: s.line, column: s.column }
|
|
1395
|
+
});
|
|
1396
|
+
}
|
|
1324
1397
|
const meta = diagnostics.length > 0 ? { componentId, variant, diagnostics } : void 0;
|
|
1325
1398
|
return { html, document: buildDocument(html, options), ...meta ? { meta } : {} };
|
|
1326
1399
|
};
|
|
@@ -1373,21 +1446,21 @@ function loadSharp(importer) {
|
|
|
1373
1446
|
if (!importer) loaded = attempt;
|
|
1374
1447
|
return attempt;
|
|
1375
1448
|
}
|
|
1376
|
-
function imageVariantKey(
|
|
1377
|
-
return imageVariantKeyFromSourceHash(imageSourceHash(
|
|
1449
|
+
function imageVariantKey(source2, width, encoding) {
|
|
1450
|
+
return imageVariantKeyFromSourceHash(imageSourceHash(source2), width, encoding);
|
|
1378
1451
|
}
|
|
1379
|
-
function imageSourceHash(
|
|
1380
|
-
return createHash("sha256").update(
|
|
1452
|
+
function imageSourceHash(source2) {
|
|
1453
|
+
return createHash("sha256").update(source2).digest("hex");
|
|
1381
1454
|
}
|
|
1382
1455
|
function imageVariantKeyFromSourceHash(sourceHash, width, encoding) {
|
|
1383
1456
|
return createHash("sha256").update(`${sourceHash}|webp|orient=auto|w=${width}|q=${encoding.quality}|e=${encoding.effort}|sharp=${SHARP_VERSION}`).digest("hex").slice(0, 10);
|
|
1384
1457
|
}
|
|
1385
|
-
async function imageSize(sharp,
|
|
1386
|
-
const meta = await sharp(
|
|
1458
|
+
async function imageSize(sharp, source2) {
|
|
1459
|
+
const meta = await sharp(source2).metadata();
|
|
1387
1460
|
return { width: meta.autoOrient?.width ?? meta.width ?? 0, height: meta.autoOrient?.height ?? meta.height ?? 0 };
|
|
1388
1461
|
}
|
|
1389
|
-
function encodeWebp(sharp,
|
|
1390
|
-
return sharp(
|
|
1462
|
+
function encodeWebp(sharp, source2, width, encoding) {
|
|
1463
|
+
return sharp(source2, { autoOrient: true }).resize({ width, withoutEnlargement: true }).webp({ quality: encoding.quality, effort: encoding.effort }).toBuffer();
|
|
1391
1464
|
}
|
|
1392
1465
|
function createImageCache(dir) {
|
|
1393
1466
|
const file = (key) => join2(dir, `${key}.webp`);
|
|
@@ -1807,7 +1880,7 @@ function isOwnOrigin(origin, hosts, port2) {
|
|
|
1807
1880
|
// packages/dev-server/src/index.ts
|
|
1808
1881
|
import { createServer } from "node:http";
|
|
1809
1882
|
import { stat as stat6 } from "node:fs/promises";
|
|
1810
|
-
import { join as
|
|
1883
|
+
import { join as join29, sep as sep11 } from "node:path";
|
|
1811
1884
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
1812
1885
|
|
|
1813
1886
|
// packages/dev-server/src/themes.ts
|
|
@@ -2313,9 +2386,12 @@ function resolveServerConfig(config, cwd, opts = {}, packages = []) {
|
|
|
2313
2386
|
workbenchUrl: config.storefront.workbenchUrl,
|
|
2314
2387
|
image: config.storefront.image,
|
|
2315
2388
|
url: config.storefront.url,
|
|
2389
|
+
basePath: storefrontBasePath(config.storefront),
|
|
2316
2390
|
lang: config.storefront.lang ?? "en",
|
|
2317
2391
|
index: config.storefront.index !== false,
|
|
2318
|
-
images: { ...images, ...config.storefront.images }
|
|
2392
|
+
images: { ...images, ...config.storefront.images },
|
|
2393
|
+
head: config.storefront.head !== void 0 ? resolve4(cwd, config.storefront.head) : void 0,
|
|
2394
|
+
headName: config.storefront.head
|
|
2319
2395
|
} : null,
|
|
2320
2396
|
images,
|
|
2321
2397
|
overviewsEnabled: config.overviews !== false,
|
|
@@ -2516,6 +2592,7 @@ async function buildAllStyleData(roots, componentIds, entryOverride) {
|
|
|
2516
2592
|
const componentCssBuckets = /* @__PURE__ */ new Map();
|
|
2517
2593
|
const baseCssByRoot = [];
|
|
2518
2594
|
const outsideRules = /* @__PURE__ */ new Map();
|
|
2595
|
+
const baseRulesByEntry = /* @__PURE__ */ new Map();
|
|
2519
2596
|
for (const id of componentIds) styleUsesModules.set(id, []);
|
|
2520
2597
|
const rootList = typeof roots === "string" ? [{ dir: roots, ...entryOverride ? { entries: entryOverride } : {} }] : roots;
|
|
2521
2598
|
for (const styleRoot of rootList) {
|
|
@@ -2583,6 +2660,7 @@ async function buildAllStyleData(roots, componentIds, entryOverride) {
|
|
|
2583
2660
|
if (!rawMap) continue;
|
|
2584
2661
|
const consumer = new SourceMapConsumer(rawMap);
|
|
2585
2662
|
const root = postcss3.parse(compiled.css);
|
|
2663
|
+
const entryBase = /* @__PURE__ */ new Set();
|
|
2586
2664
|
root.each((node) => {
|
|
2587
2665
|
const originPath = originPathFor2(node);
|
|
2588
2666
|
if (originPath === null) return;
|
|
@@ -2590,6 +2668,7 @@ async function buildAllStyleData(roots, componentIds, entryOverride) {
|
|
|
2590
2668
|
const owner = inBase(originPath) ? "__base__" : resolveScssToComponent(originPath, stylesDir, componentIds) ?? "__base__";
|
|
2591
2669
|
if (owner === "__base__") {
|
|
2592
2670
|
addLastOccurrence(baseCssBucket, nodeText);
|
|
2671
|
+
addLastOccurrence(entryBase, nodeText);
|
|
2593
2672
|
if (reportOutside && !inBase(originPath)) {
|
|
2594
2673
|
const seen = outsideRules.get(originPath) ?? /* @__PURE__ */ new Set();
|
|
2595
2674
|
seen.add(nodeText);
|
|
@@ -2601,6 +2680,7 @@ async function buildAllStyleData(roots, componentIds, entryOverride) {
|
|
|
2601
2680
|
componentCssBuckets.set(owner, bucket);
|
|
2602
2681
|
}
|
|
2603
2682
|
});
|
|
2683
|
+
baseRulesByEntry.set(resolve5(entryFile), joinCssChunks(entryBase));
|
|
2604
2684
|
const { classMap: perFileClasses, baseModules: perFileBaseModules, ranks: perFileRanks } = extractClassesFromCompiled(consumer, root, entryFile, stylesDir, componentIds);
|
|
2605
2685
|
for (const [name, owner] of perFileClasses) {
|
|
2606
2686
|
const rank = perFileRanks.get(name) ?? 0;
|
|
@@ -2631,7 +2711,8 @@ async function buildAllStyleData(roots, componentIds, entryOverride) {
|
|
|
2631
2711
|
componentCssMap,
|
|
2632
2712
|
// Packages' base rules first (later roots), the project's last — its own base wins the cascade.
|
|
2633
2713
|
baseCss: joinCssChunks([...baseCssByRoot].reverse().flat()),
|
|
2634
|
-
baseOutsideList: new Map([...outsideRules].map(([path, rules]) => [path, rules.size]))
|
|
2714
|
+
baseOutsideList: new Map([...outsideRules].map(([path, rules]) => [path, rules.size])),
|
|
2715
|
+
baseRulesByEntry
|
|
2635
2716
|
};
|
|
2636
2717
|
}
|
|
2637
2718
|
|
|
@@ -2644,7 +2725,7 @@ async function surfaceCollisions(collisions, cwd, hotreloader) {
|
|
|
2644
2725
|
console.warn(`[dev-server] HANDLE_COLLISION: "${col.handle}" \u2014 incumbent: ${col.incumbentPath} \u2014 quarantined: ${col.quarantinedPath}`);
|
|
2645
2726
|
}
|
|
2646
2727
|
if (collisions.length > 0) console.warn(`[dev-server] collisions listed in ${join9(pdScratchDir(cwd), "CONFLICTS.generated.md")}`);
|
|
2647
|
-
const
|
|
2728
|
+
const md2 = collisions.length === 0 ? "# Conflicts\n\nNo handle collisions detected.\n" : [
|
|
2648
2729
|
"# Conflicts",
|
|
2649
2730
|
"",
|
|
2650
2731
|
`${collisions.length} handle collision${collisions.length > 1 ? "s" : ""} detected. Quarantined components are excluded from the tree until the collision is resolved.`,
|
|
@@ -2659,7 +2740,7 @@ async function surfaceCollisions(collisions, cwd, hotreloader) {
|
|
|
2659
2740
|
try {
|
|
2660
2741
|
const dir = pdScratchDir(cwd);
|
|
2661
2742
|
await mkdir2(dir, { recursive: true });
|
|
2662
|
-
await writeFile2(join9(dir, "CONFLICTS.generated.md"),
|
|
2743
|
+
await writeFile2(join9(dir, "CONFLICTS.generated.md"), md2, "utf-8");
|
|
2663
2744
|
} catch (e) {
|
|
2664
2745
|
console.warn("[dev-server] Could not write CONFLICTS.generated.md to scratch dir:", e);
|
|
2665
2746
|
}
|
|
@@ -2677,7 +2758,7 @@ async function surfaceFlattening(discoveryMap, registry, cwd) {
|
|
|
2677
2758
|
}
|
|
2678
2759
|
const hasEdge = (a, b) => (registry.uses.get(a)?.includes(b) ?? false) || (registry.uses.get(b)?.includes(a) ?? false);
|
|
2679
2760
|
const clusters = detectFlattening(inputs, { hasEdge });
|
|
2680
|
-
const
|
|
2761
|
+
const md2 = renderFlatteningReport(clusters);
|
|
2681
2762
|
if (clusters.length) {
|
|
2682
2763
|
const members = clusters.reduce((n, c3) => n + c3.members.length, 0);
|
|
2683
2764
|
console.warn(`[dev-server] FLATTENED: ${clusters.length} cluster(s) across ${members} components \u2014 see ${join9(pdScratchDir(cwd), "FLATTENED.generated.md")}`);
|
|
@@ -2685,7 +2766,7 @@ async function surfaceFlattening(discoveryMap, registry, cwd) {
|
|
|
2685
2766
|
try {
|
|
2686
2767
|
const dir = pdScratchDir(cwd);
|
|
2687
2768
|
await mkdir2(dir, { recursive: true });
|
|
2688
|
-
await writeFile2(join9(dir, "FLATTENED.generated.md"),
|
|
2769
|
+
await writeFile2(join9(dir, "FLATTENED.generated.md"), md2, "utf-8");
|
|
2689
2770
|
} catch (e) {
|
|
2690
2771
|
console.warn("[dev-server] Could not write FLATTENED.generated.md to scratch dir:", e);
|
|
2691
2772
|
}
|
|
@@ -2747,8 +2828,8 @@ async function assetFiles(roots, names) {
|
|
|
2747
2828
|
if (!e.isFile()) continue;
|
|
2748
2829
|
const path = join10(e.parentPath, e.name);
|
|
2749
2830
|
const rel = relative5(root, path).split(sep2).join("/");
|
|
2750
|
-
|
|
2751
|
-
if (i === 0) files.push({ url: `${PD_ROUTE_PREFIX.assets}${rel}`, path });
|
|
2831
|
+
files.push({ url: `${PD_ROUTE_PREFIX.assets}${names[i]}/${rel}`, path });
|
|
2832
|
+
if (i === 0 && !names.includes(rel.split("/")[0])) files.push({ url: `${PD_ROUTE_PREFIX.assets}${rel}`, path });
|
|
2752
2833
|
}
|
|
2753
2834
|
}
|
|
2754
2835
|
return files;
|
|
@@ -2960,6 +3041,165 @@ async function runA11yProducer(html) {
|
|
|
2960
3041
|
return diagnostics;
|
|
2961
3042
|
}
|
|
2962
3043
|
|
|
3044
|
+
// packages/a11y/src/page.ts
|
|
3045
|
+
import { readFileSync as readFileSync2 } from "node:fs";
|
|
3046
|
+
import { createRequire as createRequire3 } from "node:module";
|
|
3047
|
+
import { JSDOM as JSDOM2 } from "jsdom";
|
|
3048
|
+
|
|
3049
|
+
// packages/a11y/src/page-rule-tiers.generated.json
|
|
3050
|
+
var page_rule_tiers_generated_default = {
|
|
3051
|
+
_note: "Generated by packages/a11y/src/page-classifier.ts \u2014 do not hand-edit. Re-run: npm run classify-pages --workspace @pattern-dump/a11y",
|
|
3052
|
+
axeVersion: "4.11.4",
|
|
3053
|
+
tags: [
|
|
3054
|
+
"wcag2a",
|
|
3055
|
+
"wcag2aa",
|
|
3056
|
+
"wcag21a",
|
|
3057
|
+
"wcag21aa",
|
|
3058
|
+
"wcag22aa",
|
|
3059
|
+
"best-practice"
|
|
3060
|
+
],
|
|
3061
|
+
jsdom: [
|
|
3062
|
+
"accesskeys",
|
|
3063
|
+
"area-alt",
|
|
3064
|
+
"aria-allowed-attr",
|
|
3065
|
+
"aria-allowed-role",
|
|
3066
|
+
"aria-command-name",
|
|
3067
|
+
"aria-conditional-attr",
|
|
3068
|
+
"aria-deprecated-role",
|
|
3069
|
+
"aria-dialog-name",
|
|
3070
|
+
"aria-hidden-body",
|
|
3071
|
+
"aria-input-field-name",
|
|
3072
|
+
"aria-meter-name",
|
|
3073
|
+
"aria-progressbar-name",
|
|
3074
|
+
"aria-required-attr",
|
|
3075
|
+
"aria-required-children",
|
|
3076
|
+
"aria-required-parent",
|
|
3077
|
+
"aria-roledescription",
|
|
3078
|
+
"aria-roles",
|
|
3079
|
+
"aria-text",
|
|
3080
|
+
"aria-toggle-field-name",
|
|
3081
|
+
"aria-tooltip-name",
|
|
3082
|
+
"aria-treeitem-name",
|
|
3083
|
+
"aria-valid-attr",
|
|
3084
|
+
"aria-valid-attr-value",
|
|
3085
|
+
"autocomplete-valid",
|
|
3086
|
+
"blink",
|
|
3087
|
+
"button-name",
|
|
3088
|
+
"definition-list",
|
|
3089
|
+
"dlitem",
|
|
3090
|
+
"document-title",
|
|
3091
|
+
"empty-heading",
|
|
3092
|
+
"empty-table-header",
|
|
3093
|
+
"frame-title",
|
|
3094
|
+
"heading-order",
|
|
3095
|
+
"html-has-lang",
|
|
3096
|
+
"html-lang-valid",
|
|
3097
|
+
"html-xml-lang-mismatch",
|
|
3098
|
+
"image-alt",
|
|
3099
|
+
"image-redundant-alt",
|
|
3100
|
+
"input-button-name",
|
|
3101
|
+
"input-image-alt",
|
|
3102
|
+
"label",
|
|
3103
|
+
"label-title-only",
|
|
3104
|
+
"landmark-banner-is-top-level",
|
|
3105
|
+
"landmark-contentinfo-is-top-level",
|
|
3106
|
+
"landmark-main-is-top-level",
|
|
3107
|
+
"landmark-no-duplicate-banner",
|
|
3108
|
+
"landmark-no-duplicate-contentinfo",
|
|
3109
|
+
"landmark-no-duplicate-main",
|
|
3110
|
+
"landmark-unique",
|
|
3111
|
+
"link-name",
|
|
3112
|
+
"list",
|
|
3113
|
+
"listitem",
|
|
3114
|
+
"meta-refresh",
|
|
3115
|
+
"meta-viewport",
|
|
3116
|
+
"nested-interactive",
|
|
3117
|
+
"presentation-role-conflict",
|
|
3118
|
+
"region",
|
|
3119
|
+
"role-img-alt",
|
|
3120
|
+
"scope-attr-valid",
|
|
3121
|
+
"select-name",
|
|
3122
|
+
"summary-name",
|
|
3123
|
+
"svg-img-alt",
|
|
3124
|
+
"tabindex",
|
|
3125
|
+
"table-duplicate-name",
|
|
3126
|
+
"td-headers-attr",
|
|
3127
|
+
"valid-lang"
|
|
3128
|
+
],
|
|
3129
|
+
browser: [
|
|
3130
|
+
"aria-braille-equivalent",
|
|
3131
|
+
"aria-hidden-focus",
|
|
3132
|
+
"aria-prohibited-attr",
|
|
3133
|
+
"audio-caption",
|
|
3134
|
+
"avoid-inline-spacing",
|
|
3135
|
+
"bypass",
|
|
3136
|
+
"color-contrast",
|
|
3137
|
+
"css-orientation-lock",
|
|
3138
|
+
"duplicate-id-aria",
|
|
3139
|
+
"focus-order-semantics",
|
|
3140
|
+
"form-field-multiple-labels",
|
|
3141
|
+
"frame-focusable-content",
|
|
3142
|
+
"frame-tested",
|
|
3143
|
+
"frame-title-unique",
|
|
3144
|
+
"hidden-content",
|
|
3145
|
+
"label-content-name-mismatch",
|
|
3146
|
+
"landmark-complementary-is-top-level",
|
|
3147
|
+
"landmark-one-main",
|
|
3148
|
+
"link-in-text-block",
|
|
3149
|
+
"marquee",
|
|
3150
|
+
"meta-viewport-large",
|
|
3151
|
+
"no-autoplay-audio",
|
|
3152
|
+
"object-alt",
|
|
3153
|
+
"p-as-heading",
|
|
3154
|
+
"page-has-heading-one",
|
|
3155
|
+
"scrollable-region-focusable",
|
|
3156
|
+
"server-side-image-map",
|
|
3157
|
+
"skip-link",
|
|
3158
|
+
"table-fake-caption",
|
|
3159
|
+
"target-size",
|
|
3160
|
+
"td-has-header",
|
|
3161
|
+
"th-has-data-cells",
|
|
3162
|
+
"video-caption"
|
|
3163
|
+
]
|
|
3164
|
+
};
|
|
3165
|
+
|
|
3166
|
+
// packages/a11y/src/page.ts
|
|
3167
|
+
var require4 = createRequire3(import.meta.url);
|
|
3168
|
+
var axeSource;
|
|
3169
|
+
var source = () => axeSource ??= readFileSync2(require4.resolve("axe-core/axe.min.js"), "utf8");
|
|
3170
|
+
var axeScript = () => source();
|
|
3171
|
+
var PAGE_TAGS = ["wcag2a", "wcag2aa", "wcag21a", "wcag21aa", "wcag22aa", "best-practice"];
|
|
3172
|
+
async function runAxeOnPage(html, rules) {
|
|
3173
|
+
const dom = new JSDOM2(html, { url: "http://site.invalid/", runScripts: "outside-only", pretendToBeVisual: true });
|
|
3174
|
+
dom.virtualConsole.removeAllListeners();
|
|
3175
|
+
try {
|
|
3176
|
+
dom.window.eval(source());
|
|
3177
|
+
const axe2 = dom.window.axe;
|
|
3178
|
+
const ruleIds = axe2.getRules(PAGE_TAGS).map((r2) => r2.ruleId);
|
|
3179
|
+
const options = rules.length ? { runOnly: { type: "rule", values: rules } } : { runOnly: { type: "tag", values: PAGE_TAGS } };
|
|
3180
|
+
const r = await axe2.run(dom.window.document, { ...options, resultTypes: ["violations", "incomplete"] });
|
|
3181
|
+
return { violations: r.violations, incomplete: r.incomplete, axeVersion: axe2.version, ruleIds };
|
|
3182
|
+
} finally {
|
|
3183
|
+
dom.window.close();
|
|
3184
|
+
}
|
|
3185
|
+
}
|
|
3186
|
+
function severityOf(impact, tags) {
|
|
3187
|
+
const wcag = tags.some((t) => /^wcag\d/.test(t));
|
|
3188
|
+
return wcag && (impact === "critical" || impact === "serious") ? "error" : "warning";
|
|
3189
|
+
}
|
|
3190
|
+
async function runPageA11y(html) {
|
|
3191
|
+
const { violations } = await runAxeOnPage(html, page_rule_tiers_generated_default.jsdom);
|
|
3192
|
+
return violations.map((v) => ({
|
|
3193
|
+
ruleId: v.id,
|
|
3194
|
+
severity: severityOf(v.impact, v.tags),
|
|
3195
|
+
impact: v.impact ?? "minor",
|
|
3196
|
+
help: v.help,
|
|
3197
|
+
helpUrl: v.helpUrl,
|
|
3198
|
+
nodes: v.nodes.map((n) => ({ target: n.target.map(String).join(" "), html: n.html }))
|
|
3199
|
+
}));
|
|
3200
|
+
}
|
|
3201
|
+
var pageRuleTiers = page_rule_tiers_generated_default;
|
|
3202
|
+
|
|
2963
3203
|
// packages/dev-server/src/diagnostics.ts
|
|
2964
3204
|
async function checkComponent(componentId, variant, render, quarantineEntry) {
|
|
2965
3205
|
if (quarantineEntry) {
|
|
@@ -2980,7 +3220,7 @@ async function checkComponent(componentId, variant, render, quarantineEntry) {
|
|
|
2980
3220
|
const a11yDiags = await runA11yProducer(previewHtml);
|
|
2981
3221
|
return { previewHtml, diagnostics: [...renderDiags, ...a11yDiags] };
|
|
2982
3222
|
} catch (e) {
|
|
2983
|
-
const
|
|
3223
|
+
const location2 = e instanceof Error && "location" in e ? e.location : void 0;
|
|
2984
3224
|
return {
|
|
2985
3225
|
previewHtml: "",
|
|
2986
3226
|
diagnostics: [{
|
|
@@ -2988,7 +3228,7 @@ async function checkComponent(componentId, variant, render, quarantineEntry) {
|
|
|
2988
3228
|
severity: "error",
|
|
2989
3229
|
source: "render",
|
|
2990
3230
|
message: e instanceof Error ? e.message : String(e),
|
|
2991
|
-
...
|
|
3231
|
+
...location2 && Object.keys(location2).length ? { location: location2 } : {}
|
|
2992
3232
|
}]
|
|
2993
3233
|
};
|
|
2994
3234
|
}
|
|
@@ -3075,7 +3315,8 @@ async function componentContentHash(id, discoveryMap, globalDataDigest2, checkCo
|
|
|
3075
3315
|
const entry = discoveryMap.get(id);
|
|
3076
3316
|
const hbs = entry?.hbsPath ? await readFile8(entry.hbsPath, "utf-8").catch(() => "") : "";
|
|
3077
3317
|
const json2 = entry?.jsonPath ? await readFile8(entry.jsonPath, "utf-8").catch(() => "") : "";
|
|
3078
|
-
|
|
3318
|
+
const schema = entry?.schemaPath ? await readFile8(entry.schemaPath, "utf-8").catch(() => "") : "";
|
|
3319
|
+
return createHash4("sha1").update(id).update(" ").update(hbs).update(" ").update(json2).update(" ").update(schema).update(" ").update(globalDataDigest2).update(" ").update(checkCode).digest("hex");
|
|
3079
3320
|
}
|
|
3080
3321
|
function globalDataDigest(globalData) {
|
|
3081
3322
|
return createHash4("sha1").update(JSON.stringify(globalData ?? {})).digest("hex");
|
|
@@ -3119,7 +3360,7 @@ async function persistSlices(root, slices) {
|
|
|
3119
3360
|
}
|
|
3120
3361
|
|
|
3121
3362
|
// packages/dev-server/src/packages.ts
|
|
3122
|
-
import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as
|
|
3363
|
+
import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync3, realpathSync as realpathSync2, statSync as statSync2 } from "node:fs";
|
|
3123
3364
|
import { dirname as dirname5, join as join13, sep as sep3 } from "node:path";
|
|
3124
3365
|
function findPackageDir(name, root) {
|
|
3125
3366
|
let dir = root;
|
|
@@ -3164,7 +3405,7 @@ function loadPatternPackages(references, root) {
|
|
|
3164
3405
|
throw new Error(`pattern package "${name}" is not installed \u2014 run: npm install --save-dev ${name}`);
|
|
3165
3406
|
}
|
|
3166
3407
|
const dir = realpathSync2(via);
|
|
3167
|
-
const pkg = JSON.parse(
|
|
3408
|
+
const pkg = JSON.parse(readFileSync3(join13(dir, "package.json"), "utf8"));
|
|
3168
3409
|
if (!pkg.patternDump) {
|
|
3169
3410
|
if (!direct) return;
|
|
3170
3411
|
throw new Error(
|
|
@@ -3289,8 +3530,8 @@ async function runDiagnosticsAudit(config, cwd) {
|
|
|
3289
3530
|
}
|
|
3290
3531
|
|
|
3291
3532
|
// packages/dev-server/src/static-build.ts
|
|
3292
|
-
import { cp, mkdir as mkdir5, readdir as
|
|
3293
|
-
import { basename as basename4, join as
|
|
3533
|
+
import { cp, mkdir as mkdir5, readdir as readdir8, rm as rm3, stat as stat5, writeFile as writeFile5 } from "node:fs/promises";
|
|
3534
|
+
import { basename as basename4, join as join19, relative as relative10, resolve as resolve7, sep as sep7 } from "node:path";
|
|
3294
3535
|
|
|
3295
3536
|
// packages/dev-server/src/http-utils.ts
|
|
3296
3537
|
import { readFile as readFile9 } from "node:fs/promises";
|
|
@@ -3411,12 +3652,12 @@ function imageCacheDir(cwd) {
|
|
|
3411
3652
|
}
|
|
3412
3653
|
var caches = /* @__PURE__ */ new WeakMap();
|
|
3413
3654
|
var cacheFor = (cfg) => {
|
|
3414
|
-
let
|
|
3415
|
-
if (!
|
|
3416
|
-
|
|
3417
|
-
caches.set(cfg,
|
|
3655
|
+
let cache2 = caches.get(cfg);
|
|
3656
|
+
if (!cache2) {
|
|
3657
|
+
cache2 = createImageCache(imageCacheDir(cfg.cwd));
|
|
3658
|
+
caches.set(cfg, cache2);
|
|
3418
3659
|
}
|
|
3419
|
-
return
|
|
3660
|
+
return cache2;
|
|
3420
3661
|
};
|
|
3421
3662
|
async function variantSource(name, hash, width, encoding, cfg) {
|
|
3422
3663
|
const base2 = PD_ROUTE_PREFIX.assets + name.split("/").map(encodeURIComponent).join("/");
|
|
@@ -3441,8 +3682,8 @@ async function serveImageVariant(pathname, host, res, cfg) {
|
|
|
3441
3682
|
const variant = parseImageUrl(pathname);
|
|
3442
3683
|
if (!variant) return notFound();
|
|
3443
3684
|
const encoding = cfg.storefront && host === cfg.storefront.host ? cfg.storefront.images : cfg.images;
|
|
3444
|
-
const
|
|
3445
|
-
if (!
|
|
3685
|
+
const source2 = await variantSource(variant.name, variant.hash, variant.width, encoding, cfg);
|
|
3686
|
+
if (!source2) return notFound();
|
|
3446
3687
|
let sharp;
|
|
3447
3688
|
try {
|
|
3448
3689
|
sharp = await loadSharp();
|
|
@@ -3453,12 +3694,12 @@ async function serveImageVariant(pathname, host, res, cfg) {
|
|
|
3453
3694
|
res.end(e.message);
|
|
3454
3695
|
return;
|
|
3455
3696
|
}
|
|
3456
|
-
if (variant.width > (await imageSize(sharp,
|
|
3457
|
-
const
|
|
3458
|
-
let bytes = await
|
|
3697
|
+
if (variant.width > (await imageSize(sharp, source2)).width) return notFound();
|
|
3698
|
+
const cache2 = cacheFor(cfg);
|
|
3699
|
+
let bytes = await cache2.read(variant.hash);
|
|
3459
3700
|
if (!bytes) {
|
|
3460
|
-
bytes = await encodeWebp(sharp,
|
|
3461
|
-
await
|
|
3701
|
+
bytes = await encodeWebp(sharp, source2, variant.width, encoding);
|
|
3702
|
+
await cache2.write(variant.hash, bytes);
|
|
3462
3703
|
}
|
|
3463
3704
|
res.writeHead(200, { "Content-Type": "image/webp", "Cache-Control": "public, max-age=31536000, immutable" });
|
|
3464
3705
|
res.end(bytes);
|
|
@@ -3524,156 +3765,602 @@ function createSnapshotWriter({ outDir, basePath, origin, report, fetch: get = (
|
|
|
3524
3765
|
return { capture, store, failures };
|
|
3525
3766
|
}
|
|
3526
3767
|
|
|
3527
|
-
// packages/dev-server/src/storefront/
|
|
3528
|
-
import {
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3768
|
+
// packages/dev-server/src/storefront/pages.ts
|
|
3769
|
+
import { readdir as readdir7, readFile as readFile11, stat as stat4 } from "node:fs/promises";
|
|
3770
|
+
import { join as join17, relative as relative9, sep as sep6 } from "node:path";
|
|
3771
|
+
|
|
3772
|
+
// packages/dev-server/src/frontmatter.ts
|
|
3773
|
+
var BOM = "\uFEFF";
|
|
3774
|
+
var unquote = (s) => {
|
|
3775
|
+
const t = s.trim();
|
|
3776
|
+
return t.length >= 2 && (t[0] === '"' || t[0] === "'") && t.at(-1) === t[0] ? t.slice(1, -1) : t;
|
|
3777
|
+
};
|
|
3778
|
+
var unterminatedQuote = (s) => {
|
|
3779
|
+
const t = s.trim();
|
|
3780
|
+
return (t[0] === '"' || t[0] === "'") && (t.length < 2 || t.at(-1) !== t[0]);
|
|
3781
|
+
};
|
|
3782
|
+
function block(md2) {
|
|
3783
|
+
const text = md2.startsWith(BOM) ? md2.slice(1) : md2;
|
|
3784
|
+
const lines = text.split(/\r?\n/);
|
|
3785
|
+
if (lines[0]?.trim() !== "---") return { text, lines, end: null };
|
|
3786
|
+
return { text, lines, end: lines.findIndex((l, i) => i > 0 && l.trim() === "---") };
|
|
3543
3787
|
}
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
const problems = [];
|
|
3554
|
-
const siteProblems = /* @__PURE__ */ new Set();
|
|
3555
|
-
const list = await get(PD_ROUTES.pages);
|
|
3556
|
-
if (list.status !== 200) {
|
|
3557
|
-
const body = await list.text();
|
|
3558
|
-
let error = body;
|
|
3559
|
-
try {
|
|
3560
|
-
error = JSON.parse(body).error ?? body;
|
|
3561
|
-
} catch {
|
|
3562
|
-
}
|
|
3563
|
-
return { urls: [], problems: error.split("\n").filter(Boolean), documents: /* @__PURE__ */ new Map() };
|
|
3788
|
+
function scan(md2) {
|
|
3789
|
+
const errors = [];
|
|
3790
|
+
const err = (line, message) => {
|
|
3791
|
+
errors.push({ line, message });
|
|
3792
|
+
};
|
|
3793
|
+
const { text, lines, end: blockEnd } = block(md2);
|
|
3794
|
+
if (blockEnd === null) {
|
|
3795
|
+
if (/^---\r(?!\n)/.test(text)) err(1, "lines end in a bare CR \u2014 use LF or CRLF line endings");
|
|
3796
|
+
return { values: {}, errors, lines: {} };
|
|
3564
3797
|
}
|
|
3565
|
-
const
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
const
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3798
|
+
const end = blockEnd;
|
|
3799
|
+
if (end < 0) {
|
|
3800
|
+
err(1, "the frontmatter block opened here is never closed \u2014 end it with a line holding only ---");
|
|
3801
|
+
return { values: {}, errors, lines: {} };
|
|
3802
|
+
}
|
|
3803
|
+
const out = {};
|
|
3804
|
+
const keyLines = {};
|
|
3805
|
+
let listKey = null;
|
|
3806
|
+
for (let i = 1; i < end; i++) {
|
|
3807
|
+
const line = lines[i];
|
|
3808
|
+
const n = i + 1;
|
|
3809
|
+
const bareCr = line.includes("\r");
|
|
3810
|
+
if (bareCr) err(n, "line ends in a bare CR \u2014 use LF or CRLF line endings");
|
|
3811
|
+
const lineErr = (message) => {
|
|
3812
|
+
if (!bareCr) err(n, message);
|
|
3813
|
+
};
|
|
3814
|
+
const item = line.match(/^\s*-\s+(.*)$/);
|
|
3815
|
+
if (item && listKey) {
|
|
3816
|
+
if (unterminatedQuote(item[1])) lineErr(`list item ${item[1].trim()} has an opening quote with no closing one`);
|
|
3817
|
+
out[listKey].push(unquote(item[1]));
|
|
3818
|
+
continue;
|
|
3574
3819
|
}
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
const res = await get(url);
|
|
3582
|
-
const type = res.headers.get("content-type") ?? "";
|
|
3583
|
-
if (res.status !== (url === PD_ROUTES.notFound ? 404 : 200)) {
|
|
3584
|
-
const body = await res.text();
|
|
3585
|
-
const items = [...body.matchAll(/<li( data-pd-site)?>([\s\S]*?)<\/li>/g)].map((m) => ({ site: !!m[1], text: unescapeHtml(m[2]) }));
|
|
3586
|
-
for (const i of items) {
|
|
3587
|
-
if (!i.site) problems.push(`${url}: ${i.text}`);
|
|
3588
|
-
else if (!siteProblems.has(i.text)) {
|
|
3589
|
-
siteProblems.add(i.text);
|
|
3590
|
-
problems.push(i.text);
|
|
3591
|
-
}
|
|
3820
|
+
const kv = line.match(/^([A-Za-z_][\w-]*):\s*(.*)$/);
|
|
3821
|
+
if (!kv) {
|
|
3822
|
+
if (line.trim() !== "") {
|
|
3823
|
+
if (item) lineErr("list item with no key above it \u2014 a list follows a line `key:`");
|
|
3824
|
+
else if (/^\s+[A-Za-z_][\w-]*:/.test(line)) lineErr("nested value \u2014 only top-level keys are supported, written at the start of the line");
|
|
3825
|
+
else lineErr(`cannot read this line: ${line.trim()}`);
|
|
3592
3826
|
}
|
|
3593
|
-
|
|
3827
|
+
listKey = null;
|
|
3594
3828
|
continue;
|
|
3595
3829
|
}
|
|
3596
|
-
|
|
3597
|
-
if (
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
const lower = html.toLowerCase();
|
|
3614
|
-
for (let m = markup.exec(html); m; m = markup.exec(html)) {
|
|
3615
|
-
const name = m[1];
|
|
3616
|
-
if (name === void 0) continue;
|
|
3617
|
-
const tag = { name, start: m.index, end: markup.lastIndex, attrs: m[2] ?? "" };
|
|
3618
|
-
const key = name.toLowerCase();
|
|
3619
|
-
if (RAW_TEXT.has(key)) {
|
|
3620
|
-
const close = lower.indexOf(`</${key}`, tag.end);
|
|
3621
|
-
tag.content = { start: tag.end, end: close < 0 ? html.length : close };
|
|
3622
|
-
markup.lastIndex = tag.content.end;
|
|
3830
|
+
const [, key, raw] = kv;
|
|
3831
|
+
if (key in out) lineErr(`duplicate key "${key}"`);
|
|
3832
|
+
keyLines[key] = n;
|
|
3833
|
+
const value = raw.trim();
|
|
3834
|
+
if (value === "") {
|
|
3835
|
+
out[key] = [];
|
|
3836
|
+
listKey = key;
|
|
3837
|
+
} else if (value.startsWith("[") && value.endsWith("]")) {
|
|
3838
|
+
const items = value.slice(1, -1).split(",");
|
|
3839
|
+
for (const it of items) if (unterminatedQuote(it)) lineErr(`list item ${it.trim()} has an opening quote with no closing one`);
|
|
3840
|
+
out[key] = items.map(unquote).filter(Boolean);
|
|
3841
|
+
listKey = null;
|
|
3842
|
+
} else {
|
|
3843
|
+
if (value.startsWith("[")) lineErr(`"${key}": the list opened with [ is never closed with ]`);
|
|
3844
|
+
else if (unterminatedQuote(value)) lineErr(`"${key}": the value has an opening quote with no closing one`);
|
|
3845
|
+
out[key] = unquote(value);
|
|
3846
|
+
listKey = null;
|
|
3623
3847
|
}
|
|
3624
|
-
tags.push(tag);
|
|
3625
3848
|
}
|
|
3626
|
-
return
|
|
3849
|
+
return { values: out, errors, lines: keyLines };
|
|
3627
3850
|
}
|
|
3628
|
-
function
|
|
3629
|
-
|
|
3630
|
-
return q.replace(/&(#x[0-9a-f]+|#[0-9]+|amp|lt|gt|quot|apos|#39);/gi, (_, e) => {
|
|
3631
|
-
const k = e.toLowerCase();
|
|
3632
|
-
if (k.startsWith("#x")) return String.fromCodePoint(parseInt(k.slice(2), 16));
|
|
3633
|
-
if (k.startsWith("#")) return String.fromCodePoint(parseInt(k.slice(1), 10));
|
|
3634
|
-
return { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'" }[k] ?? `&${e};`;
|
|
3635
|
-
});
|
|
3851
|
+
function readFrontmatter(md2) {
|
|
3852
|
+
return scan(md2).values;
|
|
3636
3853
|
}
|
|
3637
|
-
function
|
|
3638
|
-
return
|
|
3854
|
+
function readFrontmatterStrict(md2) {
|
|
3855
|
+
return scan(md2);
|
|
3639
3856
|
}
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
var OTHER_HOST = /^(?:[a-z][a-z0-9+.-]*:)?\/\//i;
|
|
3644
|
-
function idsOf(html) {
|
|
3645
|
-
const ids = /* @__PURE__ */ new Set();
|
|
3646
|
-
for (const tag of startTags(html)) {
|
|
3647
|
-
for (const a of attributesOf(tag)) {
|
|
3648
|
-
if (a.value === void 0) continue;
|
|
3649
|
-
if (a.name === "id" || a.name === "name" && tag.name.toLowerCase() === "a") ids.add(a.value);
|
|
3650
|
-
}
|
|
3651
|
-
}
|
|
3652
|
-
return ids;
|
|
3857
|
+
function frontmatterBodyOffset(md2) {
|
|
3858
|
+
const { end } = block(md2);
|
|
3859
|
+
return end === null || end < 0 ? 0 : end + 1;
|
|
3653
3860
|
}
|
|
3654
|
-
function
|
|
3655
|
-
const
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
if (a.name === "href" || a.name === "src") refs.push([tag.name.toLowerCase(), a.name, a.value.trim()]);
|
|
3660
|
-
else if (a.name === "srcset") {
|
|
3661
|
-
for (const c3 of a.value.split(",")) {
|
|
3662
|
-
const url = c3.trim().split(/\s+/)[0];
|
|
3663
|
-
if (url) refs.push([tag.name.toLowerCase(), a.name, url]);
|
|
3664
|
-
}
|
|
3665
|
-
}
|
|
3666
|
-
}
|
|
3667
|
-
}
|
|
3668
|
-
return refs;
|
|
3861
|
+
function frontmatterBody(md2) {
|
|
3862
|
+
const { text, lines, end } = block(md2);
|
|
3863
|
+
if (end === null) return text;
|
|
3864
|
+
if (end < 0) return "";
|
|
3865
|
+
return lines.slice(end + 1).join("\n");
|
|
3669
3866
|
}
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3867
|
+
|
|
3868
|
+
// packages/dev-server/src/tree-order.ts
|
|
3869
|
+
function parseOrder(value) {
|
|
3870
|
+
if (value === void 0) return void 0;
|
|
3871
|
+
if (Array.isArray(value)) return { list: value };
|
|
3872
|
+
if (/^-?\d+(\.\d+)?$/.test(value)) return { weight: Number(value) };
|
|
3873
|
+
const list = value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
3874
|
+
return list.length ? { list } : void 0;
|
|
3875
|
+
}
|
|
3876
|
+
var collator = new Intl.Collator("en", { numeric: true });
|
|
3877
|
+
var REST = "...";
|
|
3878
|
+
function orderSiblings(items, list) {
|
|
3879
|
+
const position = /* @__PURE__ */ new Map();
|
|
3880
|
+
list?.forEach((name, i) => {
|
|
3881
|
+
if (!position.has(name)) position.set(name, i);
|
|
3882
|
+
});
|
|
3883
|
+
const restPosition = position.get(REST) ?? Infinity;
|
|
3884
|
+
const positionOf2 = (item) => list ? position.get(item.segment) ?? restPosition : 0;
|
|
3885
|
+
return [...items].sort((a, b) => {
|
|
3886
|
+
const ra = a.weight ?? 0;
|
|
3887
|
+
const rb = b.weight ?? 0;
|
|
3888
|
+
if (ra !== rb) return ra - rb;
|
|
3889
|
+
const pa = positionOf2(a);
|
|
3890
|
+
const pb = positionOf2(b);
|
|
3891
|
+
if (pa !== pb) return pa - pb;
|
|
3892
|
+
return collator.compare(a.segment, b.segment) || (a.segment < b.segment ? -1 : a.segment > b.segment ? 1 : 0);
|
|
3893
|
+
});
|
|
3894
|
+
}
|
|
3895
|
+
|
|
3896
|
+
// packages/dev-server/src/storefront/redirects.ts
|
|
3897
|
+
function redirectsOf(f) {
|
|
3898
|
+
const v = f.frontmatter.values["redirectFrom"];
|
|
3899
|
+
return Array.isArray(v) ? v : typeof v === "string" ? [v] : [];
|
|
3900
|
+
}
|
|
3901
|
+
var RESERVED = [PD_ROUTE_PREFIX.preview, "/_pd/", PD_ROUTE_PREFIX.assets, "/api/", PD_ROUTE_PREFIX.source];
|
|
3902
|
+
function redirectProblems(files, workbenchPath) {
|
|
3903
|
+
const problems = [];
|
|
3904
|
+
const claimed = /* @__PURE__ */ new Map();
|
|
3905
|
+
for (const f of files.values()) {
|
|
3906
|
+
const at = `${f.path}${f.frontmatter.lines["redirectFrom"] !== void 0 ? `:${f.frontmatter.lines["redirectFrom"]}` : ""}`;
|
|
3907
|
+
for (const old of redirectsOf(f)) {
|
|
3908
|
+
if (typeof old !== "string" || !old.startsWith("/") || !old.endsWith("/")) continue;
|
|
3909
|
+
const page = files.get(old);
|
|
3910
|
+
if (page) {
|
|
3911
|
+
problems.push(`${at}: redirectFrom "${old}" is the page ${page.path}: a route can't both be a page and forward to one`);
|
|
3912
|
+
continue;
|
|
3913
|
+
}
|
|
3914
|
+
const reserved = RESERVED.find((r) => old.startsWith(r));
|
|
3915
|
+
if (reserved) {
|
|
3916
|
+
problems.push(`${at}: redirectFrom "${old}" is under ${reserved}, which Pattern Dump serves itself`);
|
|
3917
|
+
continue;
|
|
3918
|
+
}
|
|
3919
|
+
if (workbenchPath !== void 0 && old.startsWith(workbenchPath)) {
|
|
3920
|
+
problems.push(`${at}: redirectFrom "${old}" is ${workbenchPlace(old, workbenchPath)}`);
|
|
3921
|
+
continue;
|
|
3922
|
+
}
|
|
3923
|
+
const other = claimed.get(old);
|
|
3924
|
+
if (other && other !== f) {
|
|
3925
|
+
problems.push(`${at}: redirectFrom "${old}" is also claimed by ${other.path}: one old route forwards to one page`);
|
|
3926
|
+
continue;
|
|
3927
|
+
}
|
|
3928
|
+
claimed.set(old, f);
|
|
3929
|
+
}
|
|
3930
|
+
}
|
|
3931
|
+
return problems;
|
|
3932
|
+
}
|
|
3933
|
+
function workbenchPlace(route, workbenchPath) {
|
|
3934
|
+
return `${route === workbenchPath ? "at" : "under"} ${workbenchPath}, where the workbench is published on this host (storefront.workbenchUrl), so the workbench would overwrite it there`;
|
|
3935
|
+
}
|
|
3936
|
+
function redirectStub(p) {
|
|
3937
|
+
const target = escapeAttr(p.route);
|
|
3938
|
+
const canonical2 = p.siteUrl !== void 0 ? `
|
|
3939
|
+
<link rel="canonical" href="${escapeAttr(new URL(p.route.slice(1), p.siteUrl).href)}">` : "";
|
|
3940
|
+
return `<!DOCTYPE html>
|
|
3941
|
+
<html lang="${escapeAttr(p.lang)}">
|
|
3942
|
+
<head>
|
|
3943
|
+
<meta charset="UTF-8">
|
|
3944
|
+
<title>Moved to ${escapeHtml(p.title)}</title>${canonical2}
|
|
3945
|
+
<meta name="robots" content="noindex">
|
|
3946
|
+
<meta http-equiv="refresh" content="0; url=${target}">
|
|
3947
|
+
</head>
|
|
3948
|
+
<body>
|
|
3949
|
+
<p>This page has moved to <a href="${target}">${escapeHtml(p.title)}</a>.</p>
|
|
3950
|
+
</body>
|
|
3951
|
+
</html>
|
|
3952
|
+
`;
|
|
3953
|
+
}
|
|
3954
|
+
|
|
3955
|
+
// packages/dev-server/src/storefront/listing-keys.ts
|
|
3956
|
+
var DATE = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
3957
|
+
var TAG = /^[a-z0-9-]+$/;
|
|
3958
|
+
var show = (v) => Array.isArray(v) ? `[${v.join(", ")}]` : `"${v}"`;
|
|
3959
|
+
function readDate(v) {
|
|
3960
|
+
const m = typeof v === "string" ? DATE.exec(v) : null;
|
|
3961
|
+
if (m) {
|
|
3962
|
+
const [y, mo, d] = [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
3963
|
+
const t = new Date(Date.UTC(y, mo - 1, d));
|
|
3964
|
+
if (t.getUTCFullYear() === y && t.getUTCMonth() === mo - 1 && t.getUTCDate() === d) return { ok: true, value: v };
|
|
3965
|
+
}
|
|
3966
|
+
return { ok: false, message: `date ${show(v)} is not a date: write it YYYY-MM-DD, such as "2026-11-14"` };
|
|
3967
|
+
}
|
|
3968
|
+
function readTags(v) {
|
|
3969
|
+
const list = Array.isArray(v) ? v : [v];
|
|
3970
|
+
const bad = list.filter((t) => !TAG.test(t));
|
|
3971
|
+
if (bad.length) {
|
|
3972
|
+
return { ok: false, message: `tags: ${bad.map((t) => `"${t}"`).join(", ")} ${bad.length === 1 ? "is" : "are"} not a tag: a tag is lowercase letters, digits and hyphens, such as "design-systems"` };
|
|
3973
|
+
}
|
|
3974
|
+
const twice = [...new Set(list.filter((t, i) => list.indexOf(t) !== i))];
|
|
3975
|
+
if (twice.length) return { ok: false, message: `tags: ${twice.map((t) => `"${t}"`).join(", ")} ${twice.length === 1 ? "is" : "are"} listed twice` };
|
|
3976
|
+
return { ok: true, value: list };
|
|
3977
|
+
}
|
|
3978
|
+
function readDraft(v) {
|
|
3979
|
+
return v === "true" || v === "false" ? { ok: true, value: v === "true" } : { ok: false, message: "draft must be true or false" };
|
|
3980
|
+
}
|
|
3981
|
+
function readSort(v, path) {
|
|
3982
|
+
if (path !== "index.md" && !path.endsWith("/index.md")) {
|
|
3983
|
+
return { ok: false, message: `sort orders a folder's pages, so it belongs on the folder's index.md, not on a page` };
|
|
3984
|
+
}
|
|
3985
|
+
return v === "date" || v === "order" ? { ok: true, value: v } : { ok: false, message: `sort must be "date" (newest first) or "order" (the folder's order, the default)` };
|
|
3986
|
+
}
|
|
3987
|
+
|
|
3988
|
+
// packages/dev-server/src/storefront/teaser.ts
|
|
3989
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
3990
|
+
import MarkdownIt from "markdown-it";
|
|
3991
|
+
var md = new MarkdownIt({ html: true, linkify: false });
|
|
3992
|
+
function plainText(children) {
|
|
3993
|
+
let depth = 0;
|
|
3994
|
+
let out = "";
|
|
3995
|
+
for (const t of children) {
|
|
3996
|
+
if (t.type === "html_inline") {
|
|
3997
|
+
if (/^<pd-[a-z0-9-]+(?:\s[^>]*)?(?<!\/)>$/i.test(t.content.trim())) depth++;
|
|
3998
|
+
else if (/^<\/pd-/i.test(t.content.trim()) && depth > 0) depth--;
|
|
3999
|
+
continue;
|
|
4000
|
+
}
|
|
4001
|
+
if (depth > 0) continue;
|
|
4002
|
+
if (t.type === "text" || t.type === "code_inline") out += t.content;
|
|
4003
|
+
else if (t.type === "softbreak" || t.type === "hardbreak") out += " ";
|
|
4004
|
+
}
|
|
4005
|
+
return out.replace(/\s+/g, " ").trim();
|
|
4006
|
+
}
|
|
4007
|
+
function firstParagraph(body) {
|
|
4008
|
+
const tokens = md.parse(body, {});
|
|
4009
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
4010
|
+
if (tokens[i].type !== "paragraph_open") continue;
|
|
4011
|
+
const text = plainText(tokens[i + 1]?.children ?? []);
|
|
4012
|
+
if (text !== "") return text;
|
|
4013
|
+
}
|
|
4014
|
+
return null;
|
|
4015
|
+
}
|
|
4016
|
+
var cache = /* @__PURE__ */ new Map();
|
|
4017
|
+
var LIMIT = 1e4;
|
|
4018
|
+
var teaserStats = { parses: 0 };
|
|
4019
|
+
function teaserOf(body) {
|
|
4020
|
+
const key = createHash5("sha256").update(body).digest("hex");
|
|
4021
|
+
if (cache.has(key)) return cache.get(key);
|
|
4022
|
+
teaserStats.parses++;
|
|
4023
|
+
const value = firstParagraph(body);
|
|
4024
|
+
if (cache.size >= LIMIT) cache.delete(cache.keys().next().value);
|
|
4025
|
+
cache.set(key, value);
|
|
4026
|
+
return value;
|
|
4027
|
+
}
|
|
4028
|
+
|
|
4029
|
+
// packages/dev-server/src/storefront/pages.ts
|
|
4030
|
+
var DEFAULT_LAYOUT = "page";
|
|
4031
|
+
var NOT_FOUND_FILE = "404.md";
|
|
4032
|
+
function routeFor(path) {
|
|
4033
|
+
const parts = path.replace(/\.md$/, "").split("/");
|
|
4034
|
+
if (parts.at(-1) === "index") parts.pop();
|
|
4035
|
+
return parts.length ? `/${parts.join("/")}/` : "/";
|
|
4036
|
+
}
|
|
4037
|
+
var NAME = /^[a-z0-9-]+$/;
|
|
4038
|
+
async function markdownFiles(dir, root, problems) {
|
|
4039
|
+
const out = [];
|
|
4040
|
+
const entries = (await readdir7(dir, { withFileTypes: true }).catch(() => [])).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
4041
|
+
for (const e of entries) {
|
|
4042
|
+
if (e.name.startsWith(".")) continue;
|
|
4043
|
+
const p = join17(dir, e.name);
|
|
4044
|
+
const rel = relative9(root, p).split(sep6).join("/");
|
|
4045
|
+
if (e.isDirectory()) {
|
|
4046
|
+
if (!NAME.test(e.name)) {
|
|
4047
|
+
problems.push(`${rel}/: a folder name is part of the URL, so it is lowercase letters, digits and hyphens only`);
|
|
4048
|
+
continue;
|
|
4049
|
+
}
|
|
4050
|
+
out.push(...await markdownFiles(p, root, problems));
|
|
4051
|
+
} else if (!e.name.endsWith(".md")) {
|
|
4052
|
+
problems.push(`${rel} is not a page: only .md files belong in the pages folder (images and other files go in the project's assets)`);
|
|
4053
|
+
} else if (!NAME.test(e.name.slice(0, -".md".length))) {
|
|
4054
|
+
problems.push(`${rel}: a page's file name is part of the URL, so it is lowercase letters, digits and hyphens only, then .md`);
|
|
4055
|
+
} else {
|
|
4056
|
+
out.push(rel);
|
|
4057
|
+
}
|
|
4058
|
+
}
|
|
4059
|
+
return out;
|
|
4060
|
+
}
|
|
4061
|
+
var scalar = (v) => typeof v === "string" ? v : void 0;
|
|
4062
|
+
async function readPages(pagesDir, opts = {}) {
|
|
4063
|
+
const problems = [];
|
|
4064
|
+
const files = /* @__PURE__ */ new Map();
|
|
4065
|
+
let notFound;
|
|
4066
|
+
if (!await stat4(pagesDir).then((s) => s.isDirectory(), () => false)) {
|
|
4067
|
+
return { pages: [], nav: [], files, entries: /* @__PURE__ */ new Map(), folders: /* @__PURE__ */ new Map(), drafts: [], problems: [`the pages folder ${pagesDir} does not exist`] };
|
|
4068
|
+
}
|
|
4069
|
+
const bodies = /* @__PURE__ */ new Map();
|
|
4070
|
+
for (const path of (await markdownFiles(pagesDir, pagesDir, problems)).sort()) {
|
|
4071
|
+
const route = routeFor(path);
|
|
4072
|
+
const other = files.get(route);
|
|
4073
|
+
if (other) {
|
|
4074
|
+
problems.push(`${other.path} and ${path} are both the page at ${route}`);
|
|
4075
|
+
continue;
|
|
4076
|
+
}
|
|
4077
|
+
const file = join17(pagesDir, ...path.split("/"));
|
|
4078
|
+
const text = await readFile11(file, "utf8").catch((e) => {
|
|
4079
|
+
if (e.code === "ENOENT") return null;
|
|
4080
|
+
throw e;
|
|
4081
|
+
});
|
|
4082
|
+
if (text === null) continue;
|
|
4083
|
+
if (path === NOT_FOUND_FILE) {
|
|
4084
|
+
notFound = { route: PD_ROUTES.notFound, file, path, frontmatter: readFrontmatterStrict(text) };
|
|
4085
|
+
continue;
|
|
4086
|
+
}
|
|
4087
|
+
files.set(route, { route, file, path, frontmatter: readFrontmatterStrict(text) });
|
|
4088
|
+
bodies.set(route, text);
|
|
4089
|
+
}
|
|
4090
|
+
const isDraft = (f) => {
|
|
4091
|
+
const v = f?.frontmatter.values["draft"];
|
|
4092
|
+
return v !== void 0 && (() => {
|
|
4093
|
+
const r = readDraft(v);
|
|
4094
|
+
return r.ok && r.value;
|
|
4095
|
+
})();
|
|
4096
|
+
};
|
|
4097
|
+
const drafts = [];
|
|
4098
|
+
for (const [route, f] of [...files]) {
|
|
4099
|
+
const segments = route.split("/").filter(Boolean);
|
|
4100
|
+
const folderDraft = segments.slice(0, -1).some((_, i) => isDraft(files.get(`/${segments.slice(0, i + 1).join("/")}/`)));
|
|
4101
|
+
if (!isDraft(f) && !(opts.drafts === "leave-out" && folderDraft)) continue;
|
|
4102
|
+
drafts.push(f.path);
|
|
4103
|
+
if (opts.drafts === "leave-out") {
|
|
4104
|
+
files.delete(route);
|
|
4105
|
+
bodies.delete(route);
|
|
4106
|
+
}
|
|
4107
|
+
}
|
|
4108
|
+
drafts.sort();
|
|
4109
|
+
const pageOf = (f) => {
|
|
4110
|
+
const redirectFrom = redirectsOf(f);
|
|
4111
|
+
return {
|
|
4112
|
+
route: f.route,
|
|
4113
|
+
title: scalar(f.frontmatter.values["title"]) ?? f.route,
|
|
4114
|
+
layout: scalar(f.frontmatter.values["layout"]) ?? DEFAULT_LAYOUT,
|
|
4115
|
+
...redirectFrom.length ? { redirectFrom } : {}
|
|
4116
|
+
};
|
|
4117
|
+
};
|
|
4118
|
+
const entryOf = (f) => {
|
|
4119
|
+
const description = scalar(f.frontmatter.values["description"]);
|
|
4120
|
+
return { route: f.route, title: pageOf(f).title, ...description !== void 0 ? { description } : {} };
|
|
4121
|
+
};
|
|
4122
|
+
const weightOf = (f) => {
|
|
4123
|
+
const o = parseOrder(f?.frontmatter.values["order"]);
|
|
4124
|
+
return o && "weight" in o ? o.weight : void 0;
|
|
4125
|
+
};
|
|
4126
|
+
const listOf = (f) => {
|
|
4127
|
+
const o = parseOrder(f?.frontmatter.values["order"]);
|
|
4128
|
+
return o && "list" in o ? o.list : void 0;
|
|
4129
|
+
};
|
|
4130
|
+
const folders = /* @__PURE__ */ new Set();
|
|
4131
|
+
for (const f of files.values()) {
|
|
4132
|
+
const parts = f.path.split("/").slice(0, -1);
|
|
4133
|
+
for (let i = 1; i <= parts.length; i++) folders.add(`/${parts.slice(0, i).join("/")}/`);
|
|
4134
|
+
}
|
|
4135
|
+
if (!files.has("/")) problems.push("there is no home page: the pages folder needs an index.md at its top, the page at /");
|
|
4136
|
+
for (const folder of [...folders].sort()) {
|
|
4137
|
+
if (!files.has(folder)) problems.push(`the folder ${folder.slice(1)} holds pages but has no index.md \u2014 it needs one, as the folder's own page`);
|
|
4138
|
+
}
|
|
4139
|
+
const childrenOf = (folder) => {
|
|
4140
|
+
const depth = folder === "/" ? 0 : folder.split("/").length - 2;
|
|
4141
|
+
const out = /* @__PURE__ */ new Map();
|
|
4142
|
+
for (const route of [...files.keys(), ...folders]) {
|
|
4143
|
+
if (route === folder || !route.startsWith(folder)) continue;
|
|
4144
|
+
const segs = route.split("/").filter(Boolean);
|
|
4145
|
+
if (segs.length !== depth + 1) continue;
|
|
4146
|
+
out.set(route, segs[depth]);
|
|
4147
|
+
}
|
|
4148
|
+
return [...out].map(([route, segment]) => ({ route, segment }));
|
|
4149
|
+
};
|
|
4150
|
+
const ordered = (folder, extra = []) => {
|
|
4151
|
+
const items = childrenOf(folder).map((c3) => {
|
|
4152
|
+
const w = weightOf(files.get(c3.route));
|
|
4153
|
+
return { route: c3.route, segment: c3.segment, ...w !== void 0 ? { weight: w } : {} };
|
|
4154
|
+
});
|
|
4155
|
+
return orderSiblings([...extra, ...items], listOf(files.get(folder)));
|
|
4156
|
+
};
|
|
4157
|
+
const home = files.get("/");
|
|
4158
|
+
const homeWeight = weightOf(home);
|
|
4159
|
+
const top = ordered("/", home ? [{ route: "/", segment: "index", ...homeWeight !== void 0 ? { weight: homeWeight } : {} }] : []);
|
|
4160
|
+
const nav = [];
|
|
4161
|
+
for (const item of top) {
|
|
4162
|
+
const page = files.get(item.route);
|
|
4163
|
+
if (!page) continue;
|
|
4164
|
+
const children = folders.has(item.route) && item.route !== "/" ? ordered(item.route).flatMap((c3) => {
|
|
4165
|
+
const p = files.get(c3.route);
|
|
4166
|
+
return p ? [entryOf(p)] : [];
|
|
4167
|
+
}) : [];
|
|
4168
|
+
nav.push({ ...entryOf(page), ...children.length ? { children } : {} });
|
|
4169
|
+
}
|
|
4170
|
+
const inNav = nav.flatMap((n) => [n.route, ...(n.children ?? []).map((c3) => c3.route)]);
|
|
4171
|
+
const rest = [...files.keys()].filter((r) => !inNav.includes(r)).sort();
|
|
4172
|
+
const routes = [...inNav, ...rest];
|
|
4173
|
+
const pages = routes.map((r) => pageOf(files.get(r)));
|
|
4174
|
+
const { workbenchPath } = opts;
|
|
4175
|
+
if (workbenchPath !== void 0) {
|
|
4176
|
+
for (const f of files.values()) {
|
|
4177
|
+
if (f.route.startsWith(workbenchPath)) problems.push(`${f.path}: the page ${f.route} is ${workbenchPlace(f.route, workbenchPath)}`);
|
|
4178
|
+
}
|
|
4179
|
+
}
|
|
4180
|
+
problems.push(...redirectProblems(files, workbenchPath));
|
|
4181
|
+
const ok = (v, read) => {
|
|
4182
|
+
if (v === void 0) return void 0;
|
|
4183
|
+
const r = read(v);
|
|
4184
|
+
return r.ok ? r.value : void 0;
|
|
4185
|
+
};
|
|
4186
|
+
const entries = /* @__PURE__ */ new Map();
|
|
4187
|
+
for (const route of routes) {
|
|
4188
|
+
const f = files.get(route);
|
|
4189
|
+
const v = f.frontmatter.values;
|
|
4190
|
+
const description = scalar(v["description"]);
|
|
4191
|
+
const image = scalar(v["image"]);
|
|
4192
|
+
entries.set(route, {
|
|
4193
|
+
description: description ?? teaserOf(frontmatterBody(bodies.get(route))),
|
|
4194
|
+
date: ok(v["date"], readDate) ?? null,
|
|
4195
|
+
image: image !== void 0 && /^\/assets\/[^?#]+[^/?#]$/.test(image) ? image : null,
|
|
4196
|
+
tags: ok(v["tags"], readTags) ?? [],
|
|
4197
|
+
draft: ok(v["draft"], readDraft) ?? false
|
|
4198
|
+
});
|
|
4199
|
+
}
|
|
4200
|
+
const listings = /* @__PURE__ */ new Map();
|
|
4201
|
+
for (const folder of ["/", ...[...folders].sort()]) {
|
|
4202
|
+
const index = files.get(folder);
|
|
4203
|
+
if (!index) continue;
|
|
4204
|
+
listings.set(folder, {
|
|
4205
|
+
sort: ok(index.frontmatter.values["sort"], (v) => readSort(v, index.path)) ?? "order",
|
|
4206
|
+
children: ordered(folder).map((c3) => c3.route).filter((r) => files.has(r))
|
|
4207
|
+
});
|
|
4208
|
+
}
|
|
4209
|
+
return { pages, nav, files: new Map(routes.map((r) => [r, files.get(r)])), entries, folders: listings, drafts, problems, ...notFound ? { notFound } : {} };
|
|
4210
|
+
}
|
|
4211
|
+
|
|
4212
|
+
// packages/dev-server/src/storefront/crawl.ts
|
|
4213
|
+
import { request } from "node:http";
|
|
4214
|
+
function hostTransport(port2, host, basePath = "/") {
|
|
4215
|
+
return (liveUrl) => new Promise((resolve15, reject) => {
|
|
4216
|
+
const path = basePath === "/" || !liveUrl.startsWith("/") ? liveUrl : `${basePath}${liveUrl.slice(1)}`;
|
|
4217
|
+
const req = request({ host: "127.0.0.1", port: port2, path, headers: { host: `${host}:${port2}` } }, (res) => {
|
|
4218
|
+
const chunks = [];
|
|
4219
|
+
res.on("data", (c3) => chunks.push(c3));
|
|
4220
|
+
res.on("end", () => {
|
|
4221
|
+
const headers = new Headers();
|
|
4222
|
+
for (const [k, v] of Object.entries(res.headers)) if (typeof v === "string") headers.set(k, v);
|
|
4223
|
+
resolve15(new Response(Buffer.concat(chunks), { status: res.statusCode ?? 0, headers }));
|
|
4224
|
+
});
|
|
4225
|
+
});
|
|
4226
|
+
req.on("error", reject);
|
|
4227
|
+
req.end();
|
|
4228
|
+
});
|
|
4229
|
+
}
|
|
4230
|
+
var unescapeHtml = (s) => s.replace(/</g, "<").replace(/>/g, ">").replace(/"/g, `"`).replace(/&/g, "&");
|
|
4231
|
+
var toSite = (ref, base2) => base2 === "/" ? ref : ref.startsWith(base2) ? `/${ref.slice(base2.length)}` : null;
|
|
4232
|
+
var markupRefs = (html, base2) => [
|
|
4233
|
+
...[...html.matchAll(/(?:href|src)="(\/[^"]+)"/g)].map((m) => unescapeHtml(m[1])),
|
|
4234
|
+
// Each candidate of a srcset: its URL, before the width or density that describes it.
|
|
4235
|
+
...[...html.matchAll(/srcset="([^"]+)"/g)].flatMap((m) => unescapeHtml(m[1]).split(",").map((c3) => c3.trim().split(/\s+/)[0] ?? "")),
|
|
4236
|
+
...[...html.matchAll(/<meta property="og:image" content="https?:\/\/[^/"]+(\/[^"]+)">/g)].map((m) => unescapeHtml(m[1]))
|
|
4237
|
+
].map((u) => toSite(u, base2)).filter((u) => u !== null && /^\/(?:_pd|assets|preview)\//.test(u));
|
|
4238
|
+
var cssRefs = (css, base2) => [...css.matchAll(/url\(\s*(['"]?)(\/[^'")\s]+)\1\s*\)/g)].map((m) => toSite(m[2], base2)).filter((u) => u !== null && /^\/(?:_pd|assets)\//.test(u));
|
|
4239
|
+
async function crawlStorefront(get, files = [], basePath = "/") {
|
|
4240
|
+
const problems = [];
|
|
4241
|
+
const siteProblems = /* @__PURE__ */ new Set();
|
|
4242
|
+
const list = await get(PD_ROUTES.pages);
|
|
4243
|
+
if (list.status !== 200) {
|
|
4244
|
+
const body = await list.text();
|
|
4245
|
+
let error = body;
|
|
4246
|
+
try {
|
|
4247
|
+
error = JSON.parse(body).error ?? body;
|
|
4248
|
+
} catch {
|
|
4249
|
+
}
|
|
4250
|
+
return { urls: [], problems: error.split("\n").filter(Boolean), documents: /* @__PURE__ */ new Map() };
|
|
4251
|
+
}
|
|
4252
|
+
const { pages } = await list.json();
|
|
4253
|
+
const urls = [];
|
|
4254
|
+
const documents = /* @__PURE__ */ new Map();
|
|
4255
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4256
|
+
const queue = [];
|
|
4257
|
+
const add = (url) => {
|
|
4258
|
+
if (!seen.has(url)) {
|
|
4259
|
+
seen.add(url);
|
|
4260
|
+
queue.push(url);
|
|
4261
|
+
}
|
|
4262
|
+
};
|
|
4263
|
+
for (const p of pages) add(p.route);
|
|
4264
|
+
for (const p of pages) for (const old of p.redirectFrom ?? []) add(old);
|
|
4265
|
+
for (const f of files) add(f);
|
|
4266
|
+
while (queue.length) {
|
|
4267
|
+
const url = queue.shift();
|
|
4268
|
+
const res = await get(url);
|
|
4269
|
+
const type = res.headers.get("content-type") ?? "";
|
|
4270
|
+
if (res.status !== (url === PD_ROUTES.notFound ? 404 : 200)) {
|
|
4271
|
+
const body = await res.text();
|
|
4272
|
+
const items = [...body.matchAll(/<li( data-pd-site)?>([\s\S]*?)<\/li>/g)].map((m) => ({ site: !!m[1], text: unescapeHtml(m[2]) }));
|
|
4273
|
+
for (const i of items) {
|
|
4274
|
+
if (!i.site) problems.push(`${url}: ${i.text}`);
|
|
4275
|
+
else if (!siteProblems.has(i.text)) {
|
|
4276
|
+
siteProblems.add(i.text);
|
|
4277
|
+
problems.push(i.text);
|
|
4278
|
+
}
|
|
4279
|
+
}
|
|
4280
|
+
if (!items.length) problems.push(`${url}: ${res.status} ${body.split("\n")[0]?.slice(0, 160) ?? ""}`.trimEnd());
|
|
4281
|
+
continue;
|
|
4282
|
+
}
|
|
4283
|
+
urls.push(url);
|
|
4284
|
+
if (/text\/html/.test(type)) {
|
|
4285
|
+
const html = await res.text();
|
|
4286
|
+
documents.set(url, html);
|
|
4287
|
+
for (const ref of markupRefs(html, basePath)) add(ref);
|
|
4288
|
+
} else if (/text\/css/.test(type)) for (const ref of cssRefs(await res.text(), basePath)) add(ref);
|
|
4289
|
+
}
|
|
4290
|
+
return { urls, problems, documents };
|
|
4291
|
+
}
|
|
4292
|
+
|
|
4293
|
+
// packages/dev-server/src/storefront/html-tags.ts
|
|
4294
|
+
var MARKUP = /<!--[\s\S]*?-->|<([a-zA-Z][\w:-]*)((?:\s+[^\s"'>/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'=<>`]+))?)*)\s*\/?>/g;
|
|
4295
|
+
var ATTRIBUTE = /(\s+)([^\s"'>/=]+)(?:(\s*=\s*)("[^"]*"|'[^']*'|[^\s"'=<>`]+))?/g;
|
|
4296
|
+
var RAW_TEXT = /* @__PURE__ */ new Set(["script", "style", "textarea", "title"]);
|
|
4297
|
+
function startTags(html) {
|
|
4298
|
+
const tags = [];
|
|
4299
|
+
const markup = new RegExp(MARKUP.source, "g");
|
|
4300
|
+
const lower = html.toLowerCase();
|
|
4301
|
+
for (let m = markup.exec(html); m; m = markup.exec(html)) {
|
|
4302
|
+
const name = m[1];
|
|
4303
|
+
if (name === void 0) continue;
|
|
4304
|
+
const tag = { name, start: m.index, end: markup.lastIndex, attrs: m[2] ?? "" };
|
|
4305
|
+
const key = name.toLowerCase();
|
|
4306
|
+
if (RAW_TEXT.has(key)) {
|
|
4307
|
+
const close = lower.indexOf(`</${key}`, tag.end);
|
|
4308
|
+
tag.content = { start: tag.end, end: close < 0 ? html.length : close };
|
|
4309
|
+
markup.lastIndex = tag.content.end;
|
|
4310
|
+
}
|
|
4311
|
+
tags.push(tag);
|
|
4312
|
+
}
|
|
4313
|
+
return tags;
|
|
4314
|
+
}
|
|
4315
|
+
function decodeAttribute(written) {
|
|
4316
|
+
const q = written[0] === '"' || written[0] === "'" ? written.slice(1, -1) : written;
|
|
4317
|
+
return q.replace(/&(#x[0-9a-f]+|#[0-9]+|amp|lt|gt|quot|apos|#39);/gi, (_, e) => {
|
|
4318
|
+
const k = e.toLowerCase();
|
|
4319
|
+
if (k.startsWith("#x")) return String.fromCodePoint(parseInt(k.slice(2), 16));
|
|
4320
|
+
if (k.startsWith("#")) return String.fromCodePoint(parseInt(k.slice(1), 10));
|
|
4321
|
+
return { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'" }[k] ?? `&${e};`;
|
|
4322
|
+
});
|
|
4323
|
+
}
|
|
4324
|
+
function attributesOf(tag) {
|
|
4325
|
+
return [...tag.attrs.matchAll(ATTRIBUTE)].map((m) => ({ name: m[2].toLowerCase(), value: m[4] === void 0 ? void 0 : decodeAttribute(m[4]) }));
|
|
4326
|
+
}
|
|
4327
|
+
|
|
4328
|
+
// packages/dev-server/src/storefront/links.ts
|
|
4329
|
+
var SKIPPED = /^(?:mailto|tel|data|javascript):/i;
|
|
4330
|
+
var OTHER_HOST = /^(?:[a-z][a-z0-9+.-]*:)?\/\//i;
|
|
4331
|
+
function idsOf(html) {
|
|
4332
|
+
const ids = /* @__PURE__ */ new Set();
|
|
4333
|
+
for (const tag of startTags(html)) {
|
|
4334
|
+
for (const a of attributesOf(tag)) {
|
|
4335
|
+
if (a.value === void 0) continue;
|
|
4336
|
+
if (a.name === "id" || a.name === "name" && tag.name.toLowerCase() === "a") ids.add(a.value);
|
|
4337
|
+
}
|
|
4338
|
+
}
|
|
4339
|
+
return ids;
|
|
4340
|
+
}
|
|
4341
|
+
function referencesOf(html) {
|
|
4342
|
+
const refs = [];
|
|
4343
|
+
for (const tag of startTags(html)) {
|
|
4344
|
+
for (const a of attributesOf(tag)) {
|
|
4345
|
+
if (a.value === void 0) continue;
|
|
4346
|
+
if (a.name === "href" || a.name === "src") refs.push([tag.name.toLowerCase(), a.name, a.value.trim()]);
|
|
4347
|
+
else if (a.name === "srcset") {
|
|
4348
|
+
for (const c3 of a.value.split(",")) {
|
|
4349
|
+
const url = c3.trim().split(/\s+/)[0];
|
|
4350
|
+
if (url) refs.push([tag.name.toLowerCase(), a.name, url]);
|
|
4351
|
+
}
|
|
4352
|
+
}
|
|
4353
|
+
}
|
|
4354
|
+
}
|
|
4355
|
+
return refs;
|
|
4356
|
+
}
|
|
4357
|
+
function linkedWorkbenchRoutes(documents, pages, workbenchUrl) {
|
|
4358
|
+
const routes = /* @__PURE__ */ new Set();
|
|
4359
|
+
for (const page of pages) {
|
|
4360
|
+
for (const [, , ref] of referencesOf(documents.get(page) ?? "")) {
|
|
4361
|
+
let rest;
|
|
4362
|
+
if (OTHER_HOST.test(workbenchUrl)) {
|
|
4363
|
+
if (ref.startsWith(workbenchUrl)) rest = ref.slice(workbenchUrl.length);
|
|
3677
4364
|
} else if (!OTHER_HOST.test(ref)) {
|
|
3678
4365
|
const path = new URL(ref, `http://site.invalid${page}`).pathname;
|
|
3679
4366
|
if (path.startsWith(workbenchUrl)) rest = path.slice(workbenchUrl.length);
|
|
@@ -3710,6 +4397,8 @@ function checkLinks(site) {
|
|
|
3710
4397
|
if (hash && !(wb.variants.get(route) ?? []).includes(decodeURIComponent(hash))) return `${route} has no variant "${hash}" in the workbench`;
|
|
3711
4398
|
return void 0;
|
|
3712
4399
|
};
|
|
4400
|
+
const base2 = site.basePath ?? "/";
|
|
4401
|
+
const hint = site.pathHint ?? base2;
|
|
3713
4402
|
let references = 0;
|
|
3714
4403
|
for (const page of site.pages) {
|
|
3715
4404
|
const html = site.documents.get(page);
|
|
@@ -3725,7 +4414,7 @@ function checkLinks(site) {
|
|
|
3725
4414
|
continue;
|
|
3726
4415
|
}
|
|
3727
4416
|
let href = ref;
|
|
3728
|
-
if (site.siteUrl && ref.startsWith(site.siteUrl)) href =
|
|
4417
|
+
if (site.siteUrl && ref.startsWith(site.siteUrl)) href = `${base2}${ref.slice(site.siteUrl.length)}`;
|
|
3729
4418
|
else if (OTHER_HOST.test(ref)) {
|
|
3730
4419
|
if (site.pagesOnly) continue;
|
|
3731
4420
|
const list = external.get(ref) ?? [];
|
|
@@ -3733,18 +4422,24 @@ function checkLinks(site) {
|
|
|
3733
4422
|
external.set(ref, list);
|
|
3734
4423
|
continue;
|
|
3735
4424
|
}
|
|
3736
|
-
const url = new URL(href, `http://site.invalid${page}`);
|
|
3737
|
-
const
|
|
4425
|
+
const url = new URL(href, `http://site.invalid${base2}${page.slice(1)}`);
|
|
4426
|
+
const served = decodeURIComponent(url.pathname);
|
|
3738
4427
|
const hash = url.hash.slice(1);
|
|
3739
|
-
if (wb && wb.url.startsWith("/") &&
|
|
3740
|
-
const why = workbenchProblem(
|
|
4428
|
+
if (wb && wb.url.startsWith("/") && served.startsWith(wb.url)) {
|
|
4429
|
+
const why = workbenchProblem(served.slice(wb.url.length), hash);
|
|
3741
4430
|
if (why) say(why);
|
|
3742
4431
|
continue;
|
|
3743
4432
|
}
|
|
4433
|
+
if (!served.startsWith(base2)) {
|
|
4434
|
+
say(`${served} is outside ${base2}, the path the site is published under (storefront.url)`);
|
|
4435
|
+
continue;
|
|
4436
|
+
}
|
|
4437
|
+
const path = `/${served.slice(base2.length)}`;
|
|
3744
4438
|
if (site.pagesOnly && /^\/(?:_pd|assets|preview)\//.test(path)) continue;
|
|
3745
4439
|
const target = builtAs(path);
|
|
3746
4440
|
if (target === void 0) {
|
|
3747
|
-
|
|
4441
|
+
const doubled = hint !== "/" && path.startsWith(hint) && builtAs(`/${path.slice(hint.length)}`) !== void 0;
|
|
4442
|
+
say(doubled ? `no page or file is built at ${path}: the link is written with the site's path ${hint} already; write it from the site's root, such as /${path.slice(hint.length)}, and the path is put in front of it` : `no page or file is built at ${path}`);
|
|
3748
4443
|
continue;
|
|
3749
4444
|
}
|
|
3750
4445
|
if (!hash) continue;
|
|
@@ -3755,6 +4450,151 @@ function checkLinks(site) {
|
|
|
3755
4450
|
return { problems, external, references };
|
|
3756
4451
|
}
|
|
3757
4452
|
|
|
4453
|
+
// packages/dev-server/src/storefront/page-basics.ts
|
|
4454
|
+
function checkPageBasics(html) {
|
|
4455
|
+
let main = 0, h1 = 0;
|
|
4456
|
+
for (const tag of startTags(html)) {
|
|
4457
|
+
const name = tag.name.toLowerCase();
|
|
4458
|
+
if (name === "main" || attributesOf(tag).some((a) => a.name === "role" && a.value?.trim() === "main")) main++;
|
|
4459
|
+
if (name === "h1") h1++;
|
|
4460
|
+
}
|
|
4461
|
+
const problems = [];
|
|
4462
|
+
if (main === 0) problems.push("the page has no <main>: the page's own content goes in one, so assistive technology can go straight to it");
|
|
4463
|
+
else if (main > 1) problems.push(`the page has ${main} main landmarks (<main> or role="main"): it has exactly one, holding the page's own content`);
|
|
4464
|
+
if (h1 === 0) problems.push("the page has no <h1>: one heading names the page");
|
|
4465
|
+
else if (h1 > 1) problems.push(`the page has ${h1} <h1> headings: one names the page, the others are <h2> or lower`);
|
|
4466
|
+
return problems;
|
|
4467
|
+
}
|
|
4468
|
+
|
|
4469
|
+
// packages/dev-server/src/storefront/browser-checks.ts
|
|
4470
|
+
import { createRequire as createRequire4 } from "node:module";
|
|
4471
|
+
import { join as join18 } from "node:path";
|
|
4472
|
+
import { pathToFileURL as pathToFileURL2 } from "node:url";
|
|
4473
|
+
var BrowserUnavailableError = class extends Error {
|
|
4474
|
+
};
|
|
4475
|
+
var INSTALL = "npm install --save-dev playwright, then npx playwright install chromium";
|
|
4476
|
+
async function loadProjectPlaywright(root) {
|
|
4477
|
+
const require5 = createRequire4(join18(root, "package.json"));
|
|
4478
|
+
for (const name of ["playwright", "@playwright/test"]) {
|
|
4479
|
+
let resolved;
|
|
4480
|
+
try {
|
|
4481
|
+
resolved = require5.resolve(name);
|
|
4482
|
+
} catch {
|
|
4483
|
+
continue;
|
|
4484
|
+
}
|
|
4485
|
+
const mod = await import(pathToFileURL2(resolved).href);
|
|
4486
|
+
const chromium = mod.chromium ?? mod.default?.chromium;
|
|
4487
|
+
if (chromium) return { chromium };
|
|
4488
|
+
}
|
|
4489
|
+
throw new BrowserUnavailableError(`pd build --browser needs Playwright in this project: ${INSTALL}`);
|
|
4490
|
+
}
|
|
4491
|
+
var VIEWPORTS = [
|
|
4492
|
+
{ name: "1000\xD7720", viewport: { width: 1e3, height: 720 }, dpr: 1 },
|
|
4493
|
+
{ name: "390\xD7844", viewport: { width: 390, height: 844 }, dpr: 2 }
|
|
4494
|
+
];
|
|
4495
|
+
async function runBrowserChecks(o) {
|
|
4496
|
+
const base2 = o.basePath ?? "/";
|
|
4497
|
+
let browser;
|
|
4498
|
+
try {
|
|
4499
|
+
browser = await o.playwright.chromium.launch({ headless: true });
|
|
4500
|
+
} catch (e) {
|
|
4501
|
+
throw new BrowserUnavailableError(`pd build --browser couldn't start Chromium (${(e instanceof Error ? e.message : String(e)).split("\n")[0]}): ${INSTALL}`);
|
|
4502
|
+
}
|
|
4503
|
+
const origin = "http://site.invalid";
|
|
4504
|
+
const axe2 = axeScript();
|
|
4505
|
+
const found = /* @__PURE__ */ new Map();
|
|
4506
|
+
const note = (kind, key, text, at) => {
|
|
4507
|
+
const f = found.get(key) ?? { kind, text, at: [] };
|
|
4508
|
+
f.at.push(at);
|
|
4509
|
+
found.set(key, f);
|
|
4510
|
+
};
|
|
4511
|
+
let current = "";
|
|
4512
|
+
try {
|
|
4513
|
+
for (const vp of VIEWPORTS) {
|
|
4514
|
+
const context = await browser.newContext({ viewport: vp.viewport, deviceScaleFactor: vp.dpr });
|
|
4515
|
+
await context.route("**/*", async (route) => {
|
|
4516
|
+
const url = new URL(route.request().url());
|
|
4517
|
+
if (url.origin !== origin) {
|
|
4518
|
+
await route.abort();
|
|
4519
|
+
return;
|
|
4520
|
+
}
|
|
4521
|
+
if (!url.pathname.startsWith(base2)) {
|
|
4522
|
+
if (url.pathname !== "/favicon.ico") {
|
|
4523
|
+
const text = `${current}: a request to ${url.pathname}${url.search} is outside ${base2}, the path the site is published under (storefront.url): a script that builds this URL needs the site's path, which <meta name="pd-base-path"> names`;
|
|
4524
|
+
note("error", `outside ${text}`, text, vp.name);
|
|
4525
|
+
}
|
|
4526
|
+
await route.fulfill({ status: 404, body: Buffer.from("") });
|
|
4527
|
+
return;
|
|
4528
|
+
}
|
|
4529
|
+
const res = await o.get(`/${url.pathname.slice(base2.length)}${url.search}`);
|
|
4530
|
+
const headers = {};
|
|
4531
|
+
const type = res.headers.get("content-type");
|
|
4532
|
+
if (type) headers["content-type"] = type;
|
|
4533
|
+
await route.fulfill({ status: res.status, headers, body: Buffer.from(await res.arrayBuffer()) });
|
|
4534
|
+
});
|
|
4535
|
+
const page = await context.newPage();
|
|
4536
|
+
for (const path of o.pages) {
|
|
4537
|
+
current = path;
|
|
4538
|
+
await page.goto(`${origin}${base2}${path.slice(1)}`, { waitUntil: "load" });
|
|
4539
|
+
await page.addScriptTag({ content: axe2 });
|
|
4540
|
+
const violations = await page.evaluate(async (rules) => {
|
|
4541
|
+
const r = await window.axe.run(document, { runOnly: { type: "rule", values: rules }, resultTypes: ["violations"], iframes: false });
|
|
4542
|
+
return r.violations;
|
|
4543
|
+
}, pageRuleTiers.browser);
|
|
4544
|
+
for (const v of violations) {
|
|
4545
|
+
const where = v.nodes.map((n) => n.target.map(String).join(" ")).join(", ");
|
|
4546
|
+
const text = `${path}: ${v.id} (${v.impact ?? "minor"}): ${v.help}, on ${where} (${v.helpUrl})`;
|
|
4547
|
+
note(severityOf(v.impact, v.tags) === "error" ? "error" : "a11y", `a11y ${text}`, text, vp.name);
|
|
4548
|
+
}
|
|
4549
|
+
const images = await page.evaluate(async () => {
|
|
4550
|
+
const seen = [];
|
|
4551
|
+
for (const img of Array.from(document.querySelectorAll("img[srcset]"))) {
|
|
4552
|
+
img.scrollIntoView({ block: "center", inline: "center" });
|
|
4553
|
+
if (!img.complete || img.naturalWidth === 0) {
|
|
4554
|
+
await new Promise((resolve15) => {
|
|
4555
|
+
const done = () => resolve15();
|
|
4556
|
+
img.addEventListener("load", done, { once: true });
|
|
4557
|
+
img.addEventListener("error", done, { once: true });
|
|
4558
|
+
setTimeout(done, 5e3);
|
|
4559
|
+
});
|
|
4560
|
+
}
|
|
4561
|
+
const candidates = img.srcset.split(",").map((c3) => c3.trim().split(/\s+/)).map(([u, d]) => ({ url: new URL(u, location.href).href, w: parseInt(d ?? "", 10) }));
|
|
4562
|
+
const chosen = candidates.find((c3) => c3.url === img.currentSrc);
|
|
4563
|
+
seen.push({
|
|
4564
|
+
src: img.getAttribute("src") ?? "",
|
|
4565
|
+
sizes: img.sizes,
|
|
4566
|
+
box: img.getBoundingClientRect().width,
|
|
4567
|
+
chosen: chosen && !Number.isNaN(chosen.w) ? chosen.w : null,
|
|
4568
|
+
candidates: candidates.map((c3) => c3.w).filter((w) => !Number.isNaN(w))
|
|
4569
|
+
});
|
|
4570
|
+
}
|
|
4571
|
+
return seen;
|
|
4572
|
+
}, void 0);
|
|
4573
|
+
for (const img of images) {
|
|
4574
|
+
if (img.chosen === null || img.box === 0) continue;
|
|
4575
|
+
const need = Math.round(img.box * vp.dpr);
|
|
4576
|
+
const covering = img.candidates.filter((w) => w >= need).sort((a, b) => a - b)[0];
|
|
4577
|
+
const written = img.src.replace(/\?v=[0-9a-f]+$/, "");
|
|
4578
|
+
const src = base2 !== "/" && written.startsWith(base2) ? `/${written.slice(base2.length)}` : written;
|
|
4579
|
+
const asset = /^\/_pd\/images\/(.+)\.[0-9a-f]{10}\.\d+\.webp$/.exec(src)?.[1];
|
|
4580
|
+
const what = asset ? `the image ${asset}` : `<img src="${src}">`;
|
|
4581
|
+
const box = `is laid out ${Math.round(img.box)} px wide (${need} px at ${vp.dpr}x)`;
|
|
4582
|
+
if (covering !== void 0 && img.chosen > covering) {
|
|
4583
|
+
note("image", `image ${path} ${src} large`, `${path}: ${what} ${box} but takes the ${img.chosen}w variant where ${covering}w would do: its sizes, "${img.sizes}", says it is wider than it is`, vp.name);
|
|
4584
|
+
} else if (img.chosen < need && img.candidates.some((w) => w > img.chosen)) {
|
|
4585
|
+
note("image", `image ${path} ${src} small`, `${path}: ${what} ${box} but takes the ${img.chosen}w variant, which is too small: its sizes, "${img.sizes}", says it is narrower than it is`, vp.name);
|
|
4586
|
+
}
|
|
4587
|
+
}
|
|
4588
|
+
}
|
|
4589
|
+
await context.close();
|
|
4590
|
+
}
|
|
4591
|
+
} finally {
|
|
4592
|
+
await browser.close();
|
|
4593
|
+
}
|
|
4594
|
+
const lines = (kind) => [...found.values()].filter((f) => f.kind === kind).map((f) => `${f.text} (at ${f.at.join(" and ")})`);
|
|
4595
|
+
return { errors: lines("error"), a11yWarnings: lines("a11y"), images: lines("image") };
|
|
4596
|
+
}
|
|
4597
|
+
|
|
3758
4598
|
// packages/dev-server/src/storefront/site-files.ts
|
|
3759
4599
|
var escapeXml = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
3760
4600
|
function sitemapXml(entries) {
|
|
@@ -3781,6 +4621,68 @@ Sitemap: ${p.siteUrl}sitemap.xml
|
|
|
3781
4621
|
` : ""}`;
|
|
3782
4622
|
}
|
|
3783
4623
|
|
|
4624
|
+
// packages/dev-server/src/storefront/base-path.ts
|
|
4625
|
+
var URL_ATTRIBUTES = /* @__PURE__ */ new Set(["href", "src", "poster", "action", "formaction", "xlink:href"]);
|
|
4626
|
+
var SRCSET_ATTRIBUTES = /* @__PURE__ */ new Set(["srcset", "imagesrcset"]);
|
|
4627
|
+
function withBase(ref, p) {
|
|
4628
|
+
if (p.base === "/" || !ref.startsWith("/") || ref.startsWith("//") || ref.startsWith("/\\")) return ref;
|
|
4629
|
+
if (p.workbenchPath !== void 0 && p.workbenchPath !== "/" && ref.startsWith(p.workbenchPath)) return ref;
|
|
4630
|
+
return `${p.base}${ref.slice(1)}`;
|
|
4631
|
+
}
|
|
4632
|
+
function withBaseSrcset(value, p) {
|
|
4633
|
+
return value.split(",").map((entry) => {
|
|
4634
|
+
const m = /^(\s*)(\S+)(.*)$/s.exec(entry);
|
|
4635
|
+
return m ? `${m[1]}${withBase(m[2], p)}${m[3]}` : entry;
|
|
4636
|
+
}).join(",");
|
|
4637
|
+
}
|
|
4638
|
+
function withBaseCss(css, p) {
|
|
4639
|
+
if (p.base === "/") return css;
|
|
4640
|
+
return css.replace(/url\(\s*(['"]?)([^'")\s]+)\1\s*\)/g, (whole, q, ref) => {
|
|
4641
|
+
const out = withBase(ref, p);
|
|
4642
|
+
return out === ref ? whole : `url(${q}${out}${q})`;
|
|
4643
|
+
}).replace(/(@import\s+)(["'])([^"']+)\2/g, (_whole, at, q, ref) => `${at}${q}${withBase(ref, p)}${q}`);
|
|
4644
|
+
}
|
|
4645
|
+
function withBaseAttrs(name, attrs, p) {
|
|
4646
|
+
const tag = name.toLowerCase();
|
|
4647
|
+
const refresh = tag === "meta" && /\shttp-equiv\s*=\s*["']?refresh/i.test(attrs);
|
|
4648
|
+
return attrs.replace(ATTRIBUTE, (whole, lead, attr2, eq, quoted) => {
|
|
4649
|
+
if (quoted === void 0 || eq === void 0) return whole;
|
|
4650
|
+
const q = quoted[0] === '"' || quoted[0] === "'" ? quoted[0] : "";
|
|
4651
|
+
const value = q ? quoted.slice(1, -1) : quoted;
|
|
4652
|
+
const key = attr2.toLowerCase();
|
|
4653
|
+
let out = value;
|
|
4654
|
+
if (URL_ATTRIBUTES.has(key) || key === "data" && tag === "object") out = withBase(value, p);
|
|
4655
|
+
else if (SRCSET_ATTRIBUTES.has(key)) out = withBaseSrcset(value, p);
|
|
4656
|
+
else if (key === "style") out = withBaseCss(value, p);
|
|
4657
|
+
else if (key === "content" && refresh) {
|
|
4658
|
+
out = value.replace(/^(\s*[\d.]*\s*[;,]\s*(?:url\s*=\s*)?)(['"]?)(\S[^'"]*)/i, (_m, head, uq, ref) => `${head}${uq}${withBase(ref, p)}`);
|
|
4659
|
+
}
|
|
4660
|
+
return out === value ? whole : `${lead}${attr2}${eq}${q}${out}${q}`;
|
|
4661
|
+
});
|
|
4662
|
+
}
|
|
4663
|
+
function withBaseHtml(html, p) {
|
|
4664
|
+
if (p.base === "/") return html;
|
|
4665
|
+
const parts = [];
|
|
4666
|
+
let last = 0;
|
|
4667
|
+
for (const tag of startTags(html)) {
|
|
4668
|
+
parts.push(html.slice(last, tag.start));
|
|
4669
|
+
const attrsAt = tag.start + 1 + tag.name.length;
|
|
4670
|
+
parts.push(`<${tag.name}${withBaseAttrs(tag.name, tag.attrs, p)}${html.slice(attrsAt + tag.attrs.length, tag.end)}`);
|
|
4671
|
+
last = tag.end;
|
|
4672
|
+
if (!tag.content) continue;
|
|
4673
|
+
const content = html.slice(tag.content.start, tag.content.end);
|
|
4674
|
+
parts.push(tag.name.toLowerCase() === "style" ? withBaseCss(content, p) : content);
|
|
4675
|
+
last = tag.content.end;
|
|
4676
|
+
}
|
|
4677
|
+
parts.push(html.slice(last));
|
|
4678
|
+
return parts.join("");
|
|
4679
|
+
}
|
|
4680
|
+
function withNoindex(html) {
|
|
4681
|
+
if (/<meta\s+name=["']?robots["']?/i.test(html)) return html;
|
|
4682
|
+
return html.replace(/<head(\s[^>]*)?>/i, (head) => `${head}
|
|
4683
|
+
<meta name="robots" content="noindex">`);
|
|
4684
|
+
}
|
|
4685
|
+
|
|
3784
4686
|
// packages/dev-server/src/static-build.ts
|
|
3785
4687
|
var notABarrier = (path) => basename4(path) !== ".__testOnly_barrier";
|
|
3786
4688
|
async function buildStaticSite(config, root, opts) {
|
|
@@ -3793,23 +4695,23 @@ async function buildStaticSite(config, root, opts) {
|
|
|
3793
4695
|
const outDir = resolve7(opts.outDir);
|
|
3794
4696
|
const cfg = resolveServerConfig(config, root, {}, loadPatternPackages(config.packages, root));
|
|
3795
4697
|
const inside2 = (child, parent) => {
|
|
3796
|
-
const rel =
|
|
3797
|
-
return rel === "" || !rel.startsWith("..") && !rel.startsWith(
|
|
4698
|
+
const rel = relative10(parent, child);
|
|
4699
|
+
return rel === "" || !rel.startsWith("..") && !rel.startsWith(sep7) && !/^[a-zA-Z]:/.test(rel);
|
|
3798
4700
|
};
|
|
3799
4701
|
const project = resolve7(root);
|
|
3800
4702
|
if (inside2(project, outDir)) throw new Error(`pd build: --out ${outDir} would contain the project itself \u2014 choose a folder inside or beside it, like ./dist`);
|
|
3801
|
-
for (const
|
|
3802
|
-
if (inside2(outDir,
|
|
3803
|
-
throw new Error(`pd build: --out ${outDir} overlaps ${
|
|
4703
|
+
for (const source2 of [cfg.patternsDir, cfg.stylesDir, ...cfg.themeDirs, ...cfg.assetRoots]) {
|
|
4704
|
+
if (inside2(outDir, source2) || inside2(source2, outDir)) {
|
|
4705
|
+
throw new Error(`pd build: --out ${outDir} overlaps ${source2}, which Pattern Dump reads \u2014 choose another folder`);
|
|
3804
4706
|
}
|
|
3805
4707
|
}
|
|
3806
4708
|
const storefront = cfg.storefront;
|
|
3807
4709
|
if (storefront) {
|
|
3808
4710
|
const sfOut = storefront.outDir;
|
|
3809
4711
|
if (inside2(project, sfOut)) throw new Error(`pd build: storefront.outDir ${sfOut} would contain the project itself \u2014 choose a folder inside or beside it`);
|
|
3810
|
-
for (const
|
|
3811
|
-
if (inside2(sfOut,
|
|
3812
|
-
throw new Error(`pd build: storefront.outDir ${sfOut} overlaps ${
|
|
4712
|
+
for (const source2 of [cfg.patternsDir, cfg.stylesDir, storefront.pagesDir, ...cfg.themeDirs, ...cfg.assetRoots]) {
|
|
4713
|
+
if (inside2(sfOut, source2) || inside2(source2, sfOut)) {
|
|
4714
|
+
throw new Error(`pd build: storefront.outDir ${sfOut} overlaps ${source2}, which Pattern Dump reads \u2014 choose another folder`);
|
|
3813
4715
|
}
|
|
3814
4716
|
}
|
|
3815
4717
|
if (inside2(sfOut, outDir) || inside2(outDir, sfOut)) {
|
|
@@ -3823,6 +4725,7 @@ async function buildStaticSite(config, root, opts) {
|
|
|
3823
4725
|
throw new Error(`pd build: storefront.workbenchUrl is ${storefront.workbenchUrl}, so the workbench must be built for ${sameHost} (build.basePath or --base); it is being built for ${basePath}`);
|
|
3824
4726
|
}
|
|
3825
4727
|
}
|
|
4728
|
+
const playwright = opts.browser && storefront ? await (opts.loadPlaywright ?? loadProjectPlaywright)(project) : void 0;
|
|
3826
4729
|
const fixed = opts.now;
|
|
3827
4730
|
const now = fixed ? () => new Date(fixed.getTime()) : () => /* @__PURE__ */ new Date();
|
|
3828
4731
|
const storefrontNotes = /* @__PURE__ */ new Set();
|
|
@@ -3836,9 +4739,14 @@ async function buildStaticSite(config, root, opts) {
|
|
|
3836
4739
|
}
|
|
3837
4740
|
});
|
|
3838
4741
|
log("\u2026 server ready");
|
|
3839
|
-
const
|
|
3840
|
-
const
|
|
3841
|
-
const
|
|
4742
|
+
const sfBase = storefront?.basePath ?? "/";
|
|
4743
|
+
const sfGet = storefront ? hostTransport(server.port, storefront.host, sfBase) : null;
|
|
4744
|
+
const sfFiles = storefront ? [
|
|
4745
|
+
...sfBase === "/" ? [PD_ROUTES.robots] : [],
|
|
4746
|
+
PD_ROUTES.notFound,
|
|
4747
|
+
...storefront.url !== void 0 && storefront.index ? [PD_ROUTES.sitemap] : []
|
|
4748
|
+
] : [];
|
|
4749
|
+
const sfCrawl = sfGet ? await crawlStorefront(sfGet, sfFiles, sfBase) : null;
|
|
3842
4750
|
if (sfCrawl && sfCrawl.problems.length) {
|
|
3843
4751
|
await server.close();
|
|
3844
4752
|
throw new Error(`pd build: the storefront has problems, so nothing was written:
|
|
@@ -3847,6 +4755,8 @@ ${sfCrawl.problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
3847
4755
|
if (sfCrawl) log(`\u2026 storefront checked: ${sfCrawl.urls.length} files`);
|
|
3848
4756
|
const origin = `http://127.0.0.1:${server.port}`;
|
|
3849
4757
|
let linkSummary;
|
|
4758
|
+
let a11ySummary;
|
|
4759
|
+
let imageSummary;
|
|
3850
4760
|
const externalNotes = [];
|
|
3851
4761
|
if (storefront && sfGet && sfCrawl) {
|
|
3852
4762
|
const { pages: list } = await (await sfGet(PD_ROUTES.pages)).json();
|
|
@@ -3874,6 +4784,7 @@ ${sfCrawl.problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
3874
4784
|
documents: sfCrawl.documents,
|
|
3875
4785
|
pages,
|
|
3876
4786
|
...storefront.url !== void 0 ? { siteUrl: storefront.url.endsWith("/") ? storefront.url : `${storefront.url}/` } : {},
|
|
4787
|
+
basePath: sfBase,
|
|
3877
4788
|
workbench: { url: wbUrl, routes, variants }
|
|
3878
4789
|
});
|
|
3879
4790
|
if (links.problems.length) {
|
|
@@ -3884,10 +4795,57 @@ ${links.problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
3884
4795
|
for (const [url, on] of links.external) externalNotes.push(`an external link, not checked: ${url} (on ${on.join(", ")})`);
|
|
3885
4796
|
linkSummary = { pages: pages.length, references: links.references, external: links.external.size };
|
|
3886
4797
|
log(`\u2026 links checked: ${links.references} references on ${pages.length} pages`);
|
|
4798
|
+
const errors = [];
|
|
4799
|
+
const warned = /* @__PURE__ */ new Map();
|
|
4800
|
+
for (const page of pages) {
|
|
4801
|
+
const html = sfCrawl.documents.get(page);
|
|
4802
|
+
if (html === void 0) continue;
|
|
4803
|
+
for (const p of checkPageBasics(html)) errors.push(`${page}: ${p}`);
|
|
4804
|
+
for (const f of await runPageA11y(html)) {
|
|
4805
|
+
const where = f.nodes.map((n) => n.target).join(", ");
|
|
4806
|
+
if (f.severity === "error") {
|
|
4807
|
+
errors.push(`${page}: ${f.ruleId} (${f.impact}): ${f.help}, on ${where} (${f.helpUrl})`);
|
|
4808
|
+
continue;
|
|
4809
|
+
}
|
|
4810
|
+
const w = warned.get(f.ruleId) ?? { finding: f, pages: [], elements: 0 };
|
|
4811
|
+
w.pages.push(page);
|
|
4812
|
+
w.elements += f.nodes.length;
|
|
4813
|
+
warned.set(f.ruleId, w);
|
|
4814
|
+
}
|
|
4815
|
+
}
|
|
4816
|
+
if (errors.length) {
|
|
4817
|
+
await server.close();
|
|
4818
|
+
throw new Error(`pd build: the storefront has accessibility problems, so nothing was written:
|
|
4819
|
+
${errors.map((e) => ` - ${e}`).join("\n")}`);
|
|
4820
|
+
}
|
|
4821
|
+
let imageWarnings;
|
|
4822
|
+
const browserWarnings = [];
|
|
4823
|
+
if (playwright) {
|
|
4824
|
+
const checked = await runBrowserChecks({ pages, get: sfGet, playwright, basePath: sfBase }).catch(async (e) => {
|
|
4825
|
+
await server.close();
|
|
4826
|
+
throw e;
|
|
4827
|
+
});
|
|
4828
|
+
if (checked.errors.length) {
|
|
4829
|
+
await server.close();
|
|
4830
|
+
throw new Error(`pd build: the storefront has problems in a browser, so nothing was written:
|
|
4831
|
+
${checked.errors.map((e) => ` - ${e}`).join("\n")}`);
|
|
4832
|
+
}
|
|
4833
|
+
browserWarnings.push(...checked.a11yWarnings);
|
|
4834
|
+
imageWarnings = checked.images;
|
|
4835
|
+
log(`\u2026 checked in Chromium at ${VIEWPORTS.map((v) => `${v.name} @${v.dpr}x`).join(" and ")}`);
|
|
4836
|
+
}
|
|
4837
|
+
imageSummary = imageWarnings ? { warnings: imageWarnings } : void 0;
|
|
4838
|
+
a11ySummary = {
|
|
4839
|
+
pages: pages.length,
|
|
4840
|
+
...playwright ? { browser: true } : {},
|
|
4841
|
+
warnings: [...[...warned.values()].map((w) => `${w.finding.ruleId} (${w.finding.impact}): ${w.finding.help}, ${w.elements} element${w.elements === 1 ? "" : "s"} on ${w.pages.join(", ")} (${w.finding.helpUrl})`), ...browserWarnings]
|
|
4842
|
+
};
|
|
4843
|
+
log(`\u2026 accessibility checked on ${pages.length} pages`);
|
|
3887
4844
|
}
|
|
3888
4845
|
await rm3(outDir, { recursive: true, force: true });
|
|
3889
4846
|
await mkdir5(outDir, { recursive: true });
|
|
3890
4847
|
const report = { outDir, basePath, files: 0, bytes: 0, components: 0, warnings: [], notes: [], ms: 0 };
|
|
4848
|
+
if (opts.browser && !storefront) report.notes.push("--browser checks a storefront's pages in Chromium, and this project has no storefront: nothing was checked");
|
|
3891
4849
|
const { capture, store } = createSnapshotWriter({ outDir, basePath, origin, report });
|
|
3892
4850
|
const referenced = (html) => [...html.matchAll(/(?:href|src)="(\/_pd\/(?:components|themes|scripts)\/[^"]+)"/g)].map((m) => m[1].replace(/&/g, "&"));
|
|
3893
4851
|
try {
|
|
@@ -3926,9 +4884,9 @@ ${links.problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
3926
4884
|
for (const variant of [void 0, ...base2.variants.map((v) => v.name)]) {
|
|
3927
4885
|
const detailText = variant ? await capture(componentUrl(id, variant)) : baseText;
|
|
3928
4886
|
if (detailText) {
|
|
3929
|
-
const { document:
|
|
3930
|
-
await store(previewUrl(id, variant),
|
|
3931
|
-
for (const ref of referenced(
|
|
4887
|
+
const { document: document3 } = JSON.parse(detailText);
|
|
4888
|
+
await store(previewUrl(id, variant), basePath === "/" ? document3 : withNoindex(document3));
|
|
4889
|
+
for (const ref of referenced(document3)) await capture(ref);
|
|
3932
4890
|
}
|
|
3933
4891
|
await capture(renderedSourceUrl(id, variant), { optional: true });
|
|
3934
4892
|
await capture(dataSourceUrl(id, variant), { optional: true });
|
|
@@ -3950,13 +4908,13 @@ ${links.problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
3950
4908
|
for (const [i, rootDir] of cfg.assetRoots.entries()) {
|
|
3951
4909
|
const by = shadowed.get(rootDir);
|
|
3952
4910
|
if (by !== void 0) {
|
|
3953
|
-
report.warnings.push(`assets: ${
|
|
4911
|
+
report.warnings.push(`assets: ${relative10(root, rootDir)} is hidden by ${relative10(root, by) || "."}, which has the same folder name; left out`);
|
|
3954
4912
|
continue;
|
|
3955
4913
|
}
|
|
3956
|
-
const exists = await
|
|
4914
|
+
const exists = await stat5(rootDir).then((s) => s.isDirectory(), () => false);
|
|
3957
4915
|
if (!exists) continue;
|
|
3958
|
-
await cp(rootDir,
|
|
3959
|
-
if (cfg.assetRoots.length === 1) await cp(rootDir,
|
|
4916
|
+
await cp(rootDir, join19(outDir, "assets", cfg.assetRootNames[i]), { recursive: true, filter: notABarrier });
|
|
4917
|
+
if (cfg.assetRoots.length === 1) await cp(rootDir, join19(outDir, "assets"), { recursive: true, force: false, filter: notABarrier });
|
|
3960
4918
|
}
|
|
3961
4919
|
const runtime = {
|
|
3962
4920
|
mode: "static",
|
|
@@ -3972,12 +4930,12 @@ ${links.problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
3972
4930
|
report.warnings.push(`shell page for route ${route} not written: ${staticPathFor(route)} already holds the server's response to ${holder}`);
|
|
3973
4931
|
}
|
|
3974
4932
|
}
|
|
3975
|
-
await writeFile5(
|
|
4933
|
+
await writeFile5(join19(outDir, ".nojekyll"), "");
|
|
3976
4934
|
report.files++;
|
|
3977
|
-
await writeFile5(
|
|
4935
|
+
await writeFile5(join19(outDir, "robots.txt"), workbenchRobotsTxt(basePath));
|
|
3978
4936
|
report.files++;
|
|
3979
|
-
if (basePath !== "/" && sameHostWorkbenchPath(storefront ?? void 0) !== basePath) {
|
|
3980
|
-
report.notes.push(`robots.txt is written to the workbench's root, but crawlers read it only at a host's root: with the base path ${basePath}, add its rule (Disallow: ${basePath}preview/) to the robots.txt the host serves at /`);
|
|
4937
|
+
if (basePath !== "/" && (sameHostWorkbenchPath(storefront ?? void 0) !== basePath || sfBase !== "/")) {
|
|
4938
|
+
report.notes.push(`robots.txt is written to the workbench's root, but crawlers read it only at a host's root: with the base path ${basePath}, each preview carries a robots noindex tag instead; where you can, also add its rule (Disallow: ${basePath}preview/) to the robots.txt the host serves at /`);
|
|
3981
4939
|
}
|
|
3982
4940
|
if (storefront && sfGet && sfCrawl) {
|
|
3983
4941
|
const sfOut = storefront.outDir;
|
|
@@ -3990,21 +4948,34 @@ ${links.problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
3990
4948
|
throw new Error(`pd build: the storefront changed while it was being written:
|
|
3991
4949
|
${sf.failures.map((f) => ` - ${f}`).join("\n")}`);
|
|
3992
4950
|
}
|
|
3993
|
-
await writeFile5(
|
|
4951
|
+
await writeFile5(join19(sfOut, ".nojekyll"), "");
|
|
3994
4952
|
counts.files++;
|
|
3995
4953
|
const stubs = new Set((await (await sfGet(PD_ROUTES.pages)).json()).pages.flatMap((p) => p.redirectFrom ?? []));
|
|
3996
4954
|
const pages = sfCrawl.urls.filter((u) => !/^\/(?:_pd|assets|preview)\//.test(u) && !sfFiles.includes(u) && !stubs.has(u)).length;
|
|
3997
|
-
report.storefront = { outDir: sfOut, pages, files: counts.files, bytes: counts.bytes };
|
|
4955
|
+
report.storefront = { outDir: sfOut, basePath: sfBase, pages, files: counts.files, bytes: counts.bytes };
|
|
4956
|
+
if (sfBase !== "/") {
|
|
4957
|
+
const wbPath = sameHostWorkbenchPath(storefront);
|
|
4958
|
+
const rules = storefront.index ? [
|
|
4959
|
+
`Disallow: ${sfBase}preview/`,
|
|
4960
|
+
...wbPath !== void 0 ? [`Disallow: ${wbPath}preview/`] : [],
|
|
4961
|
+
...storefront.url !== void 0 ? [`Sitemap: ${storefront.url}sitemap.xml`] : []
|
|
4962
|
+
] : [`Disallow: ${sfBase}`];
|
|
4963
|
+
report.notes.push(`robots.txt is not written: the site is published under ${sfBase}, and crawlers read robots.txt only at a host's root. Where you can, add to the host's own robots.txt: ${rules.join("; ")}`);
|
|
4964
|
+
}
|
|
3998
4965
|
if (storefront.url === void 0) {
|
|
3999
4966
|
report.notes.push(`storefront.url is not set, so pages have no canonical URL, og:url, og:image or JSON-LD, and there is no sitemap: set storefront.url to the site's address, such as "https://example.com/"`);
|
|
4000
4967
|
}
|
|
4001
4968
|
report.notes.push(...storefrontNotes, ...externalNotes);
|
|
4002
4969
|
if (linkSummary) report.links = linkSummary;
|
|
4970
|
+
if (a11ySummary) report.accessibility = a11ySummary;
|
|
4971
|
+
if (imageSummary) report.images = imageSummary;
|
|
4972
|
+
const drafts = (await readPages(storefront.pagesDir, { drafts: "leave-out" })).drafts;
|
|
4973
|
+
if (drafts.length) report.drafts = drafts;
|
|
4003
4974
|
log(`\u2026 storefront: ${pages} pages, ${counts.files} files`);
|
|
4004
4975
|
}
|
|
4005
4976
|
const used = /* @__PURE__ */ new Set();
|
|
4006
4977
|
for (const root2 of [outDir, ...storefront ? [storefront.outDir] : []]) {
|
|
4007
|
-
const files = await
|
|
4978
|
+
const files = await readdir8(join19(root2, "_pd", "images"), { recursive: true }).catch(() => []);
|
|
4008
4979
|
for (const f of files) {
|
|
4009
4980
|
const m = /\.([0-9a-f]{10})\.[1-9][0-9]*\.webp$/.exec(String(f));
|
|
4010
4981
|
if (m) used.add(m[1]);
|
|
@@ -4019,19 +4990,19 @@ ${sf.failures.map((f) => ` - ${f}`).join("\n")}`);
|
|
|
4019
4990
|
}
|
|
4020
4991
|
|
|
4021
4992
|
// packages/dev-server/src/requires.ts
|
|
4022
|
-
import { readFile as
|
|
4993
|
+
import { readFile as readFile12 } from "node:fs/promises";
|
|
4023
4994
|
import { existsSync as existsSync4 } from "node:fs";
|
|
4024
|
-
import { basename as basename5, join as
|
|
4995
|
+
import { basename as basename5, join as join20, relative as relative11 } from "node:path";
|
|
4025
4996
|
function normalizeScriptId(raw) {
|
|
4026
4997
|
let s = raw.trim().replace(/^\[(.+)\]$/, "$1").trim();
|
|
4027
4998
|
s = basename5(s);
|
|
4028
4999
|
s = s.replace(/\.(ts|js)$/, "");
|
|
4029
5000
|
return s;
|
|
4030
5001
|
}
|
|
4031
|
-
function parseFrontmatterList(
|
|
5002
|
+
function parseFrontmatterList(md2, key) {
|
|
4032
5003
|
const values = [];
|
|
4033
5004
|
const re = new RegExp(`^${key}:\\s*(.+)$`, "mg");
|
|
4034
|
-
for (const [, val] of
|
|
5005
|
+
for (const [, val] of md2.matchAll(re)) {
|
|
4035
5006
|
values.push(val.trim());
|
|
4036
5007
|
}
|
|
4037
5008
|
return values;
|
|
@@ -4044,9 +5015,9 @@ async function aggregateRequires(componentId, patternsDir, flatNameToPath, disco
|
|
|
4044
5015
|
let ownAssets = [];
|
|
4045
5016
|
if (entry?.mdPath) {
|
|
4046
5017
|
try {
|
|
4047
|
-
const
|
|
4048
|
-
ownScripts = parseFrontmatterList(
|
|
4049
|
-
ownAssets = parseFrontmatterList(
|
|
5018
|
+
const md2 = await readFile12(entry.mdPath, "utf-8");
|
|
5019
|
+
ownScripts = parseFrontmatterList(md2, "requireScript").map(normalizeScriptId);
|
|
5020
|
+
ownAssets = parseFrontmatterList(md2, "requireAssets").map((v) => v.trim().replace(/^\[(.+)\]$/, "$1").trim());
|
|
4050
5021
|
} catch {
|
|
4051
5022
|
}
|
|
4052
5023
|
}
|
|
@@ -4055,7 +5026,7 @@ async function aggregateRequires(componentId, patternsDir, flatNameToPath, disco
|
|
|
4055
5026
|
const hbsPath = entry?.hbsPath ?? "";
|
|
4056
5027
|
let hbs = "";
|
|
4057
5028
|
try {
|
|
4058
|
-
hbs = hbsPath ? await
|
|
5029
|
+
hbs = hbsPath ? await readFile12(hbsPath, "utf-8") : "";
|
|
4059
5030
|
} catch {
|
|
4060
5031
|
}
|
|
4061
5032
|
for (const flatName of parsePartialsFromHbs(hbs)) {
|
|
@@ -4076,7 +5047,7 @@ async function loadSvgAssets(ids, assetsDir) {
|
|
|
4076
5047
|
for (const id of ids) {
|
|
4077
5048
|
for (const candidate of [`${id}.svg`, `images/${id}.svg`]) {
|
|
4078
5049
|
try {
|
|
4079
|
-
const raw = await
|
|
5050
|
+
const raw = await readFile12(join20(assetsDir, candidate), "utf-8");
|
|
4080
5051
|
contents.push(hideSvg(raw));
|
|
4081
5052
|
break;
|
|
4082
5053
|
} catch {
|
|
@@ -4089,21 +5060,21 @@ function hideSvg(svg) {
|
|
|
4089
5060
|
return svg.replace(/<svg(\s|>)/, '<svg$1style="display:none;position:absolute" aria-hidden="true" ');
|
|
4090
5061
|
}
|
|
4091
5062
|
async function reportUnresolvableScripts(discoveryMap, scriptsDirs, globalScripts, cwd) {
|
|
4092
|
-
const resolves = (id) => scriptsDirs.some((d) => existsSync4(
|
|
4093
|
-
const where = scriptsDirs.length ? scriptsDirs.map((d) =>
|
|
5063
|
+
const resolves = (id) => scriptsDirs.some((d) => existsSync4(join20(d, `${id}.ts`)));
|
|
5064
|
+
const where = scriptsDirs.length ? scriptsDirs.map((d) => relative11(cwd, d) || d).join(" or ") : "no scriptsDir configured";
|
|
4094
5065
|
for (const id of globalScripts) {
|
|
4095
5066
|
if (id.includes("://") || resolves(normalizeScriptId(id))) continue;
|
|
4096
5067
|
console.warn(`[dev-server] globalScripts "${id}": no ${normalizeScriptId(id)}.ts in ${where}`);
|
|
4097
5068
|
}
|
|
4098
5069
|
for (const [componentId, entry] of discoveryMap) {
|
|
4099
5070
|
if (!entry.mdPath) continue;
|
|
4100
|
-
let
|
|
5071
|
+
let md2;
|
|
4101
5072
|
try {
|
|
4102
|
-
|
|
5073
|
+
md2 = await readFile12(entry.mdPath, "utf-8");
|
|
4103
5074
|
} catch {
|
|
4104
5075
|
continue;
|
|
4105
5076
|
}
|
|
4106
|
-
for (const raw of parseFrontmatterList(
|
|
5077
|
+
for (const raw of parseFrontmatterList(md2, "requireScript")) {
|
|
4107
5078
|
const id = normalizeScriptId(raw);
|
|
4108
5079
|
if (resolves(id)) continue;
|
|
4109
5080
|
console.warn(`[dev-server] requireScript "${raw}" on ${componentId}: no ${id}.ts in ${where}`);
|
|
@@ -4220,6 +5191,7 @@ function createServerState() {
|
|
|
4220
5191
|
baseProvenance: null,
|
|
4221
5192
|
perComponentCss: /* @__PURE__ */ new Map(),
|
|
4222
5193
|
baseCss: "",
|
|
5194
|
+
entryBaseRules: /* @__PURE__ */ new Map(),
|
|
4223
5195
|
baseCssVersion: 0,
|
|
4224
5196
|
discovery: createLazy("discovery", { slow: { afterMs: DISCOVERY_SLOW_MS, what: "the component index" } }),
|
|
4225
5197
|
audit: createLazy("audit"),
|
|
@@ -4250,6 +5222,7 @@ function applyStyleData(state, data) {
|
|
|
4250
5222
|
state.baseProvenance = data.baseProvenance;
|
|
4251
5223
|
state.perComponentCss = data.componentCssMap;
|
|
4252
5224
|
state.baseCss = data.baseCss;
|
|
5225
|
+
state.entryBaseRules = new Map(data.baseRulesByEntry);
|
|
4253
5226
|
return { changedComponents: changedComponents.sort(), baseChanged };
|
|
4254
5227
|
}
|
|
4255
5228
|
function scriptFor(state, scriptId) {
|
|
@@ -4295,24 +5268,24 @@ function cachedDiscovery(state) {
|
|
|
4295
5268
|
}
|
|
4296
5269
|
|
|
4297
5270
|
// packages/dev-server/src/routes.ts
|
|
4298
|
-
import { basename as basename9, extname as extname2, join as
|
|
5271
|
+
import { basename as basename9, extname as extname2, join as join25, relative as relative12 } from "node:path";
|
|
4299
5272
|
import { readFile as readFile24 } from "node:fs/promises";
|
|
4300
5273
|
|
|
4301
5274
|
// packages/dev-server/src/component-tree.ts
|
|
4302
|
-
import { readFile as
|
|
4303
|
-
import { join as
|
|
5275
|
+
import { readFile as readFile15 } from "node:fs/promises";
|
|
5276
|
+
import { join as join23 } from "node:path";
|
|
4304
5277
|
|
|
4305
5278
|
// packages/dev-server/src/layout-mode.ts
|
|
4306
|
-
import { readFile as
|
|
4307
|
-
import { join as
|
|
5279
|
+
import { readFile as readFile14 } from "node:fs/promises";
|
|
5280
|
+
import { join as join22 } from "node:path";
|
|
4308
5281
|
|
|
4309
5282
|
// packages/dev-server/src/pattern-dirs.ts
|
|
4310
|
-
import { readFile as
|
|
4311
|
-
import { join as
|
|
5283
|
+
import { readFile as readFile13 } from "node:fs/promises";
|
|
5284
|
+
import { join as join21 } from "node:path";
|
|
4312
5285
|
async function readFolderFile(dirs, rel) {
|
|
4313
5286
|
for (const dir of typeof dirs === "string" ? [dirs] : dirs) {
|
|
4314
5287
|
try {
|
|
4315
|
-
return await
|
|
5288
|
+
return await readFile13(join21(dir, rel), "utf-8");
|
|
4316
5289
|
} catch {
|
|
4317
5290
|
}
|
|
4318
5291
|
}
|
|
@@ -4325,7 +5298,7 @@ async function resolveLayoutMode(componentId, patternsDir, discoveryMap) {
|
|
|
4325
5298
|
const parts = componentId.split("/");
|
|
4326
5299
|
const category = parts[0];
|
|
4327
5300
|
let layout = FULL_CATEGORIES.has(stripOrderPrefixes(category)) ? "full" : "centered";
|
|
4328
|
-
const categoryIndex = await readFolderFile(patternsDir,
|
|
5301
|
+
const categoryIndex = await readFolderFile(patternsDir, join22(category, "index.md"));
|
|
4329
5302
|
if (categoryIndex !== null) {
|
|
4330
5303
|
const m = categoryIndex.match(/^preview:\s*(centered|full)$/m);
|
|
4331
5304
|
if (m) layout = m[1];
|
|
@@ -4333,8 +5306,8 @@ async function resolveLayoutMode(componentId, patternsDir, discoveryMap) {
|
|
|
4333
5306
|
const mdPath = discoveryMap?.get(componentId)?.mdPath;
|
|
4334
5307
|
if (mdPath) {
|
|
4335
5308
|
try {
|
|
4336
|
-
const
|
|
4337
|
-
const m =
|
|
5309
|
+
const md2 = await readFile14(mdPath, "utf-8");
|
|
5310
|
+
const m = md2.match(/^preview:\s*(centered|full)$/m);
|
|
4338
5311
|
if (m) layout = m[1];
|
|
4339
5312
|
} catch {
|
|
4340
5313
|
}
|
|
@@ -4342,130 +5315,6 @@ async function resolveLayoutMode(componentId, patternsDir, discoveryMap) {
|
|
|
4342
5315
|
return layout;
|
|
4343
5316
|
}
|
|
4344
5317
|
|
|
4345
|
-
// packages/dev-server/src/frontmatter.ts
|
|
4346
|
-
var BOM = "\uFEFF";
|
|
4347
|
-
var unquote = (s) => {
|
|
4348
|
-
const t = s.trim();
|
|
4349
|
-
return t.length >= 2 && (t[0] === '"' || t[0] === "'") && t.at(-1) === t[0] ? t.slice(1, -1) : t;
|
|
4350
|
-
};
|
|
4351
|
-
var unterminatedQuote = (s) => {
|
|
4352
|
-
const t = s.trim();
|
|
4353
|
-
return (t[0] === '"' || t[0] === "'") && (t.length < 2 || t.at(-1) !== t[0]);
|
|
4354
|
-
};
|
|
4355
|
-
function block(md) {
|
|
4356
|
-
const text = md.startsWith(BOM) ? md.slice(1) : md;
|
|
4357
|
-
const lines = text.split(/\r?\n/);
|
|
4358
|
-
if (lines[0]?.trim() !== "---") return { text, lines, end: null };
|
|
4359
|
-
return { text, lines, end: lines.findIndex((l, i) => i > 0 && l.trim() === "---") };
|
|
4360
|
-
}
|
|
4361
|
-
function scan(md) {
|
|
4362
|
-
const errors = [];
|
|
4363
|
-
const err = (line, message) => {
|
|
4364
|
-
errors.push({ line, message });
|
|
4365
|
-
};
|
|
4366
|
-
const { text, lines, end: blockEnd } = block(md);
|
|
4367
|
-
if (blockEnd === null) {
|
|
4368
|
-
if (/^---\r(?!\n)/.test(text)) err(1, "lines end in a bare CR \u2014 use LF or CRLF line endings");
|
|
4369
|
-
return { values: {}, errors, lines: {} };
|
|
4370
|
-
}
|
|
4371
|
-
const end = blockEnd;
|
|
4372
|
-
if (end < 0) {
|
|
4373
|
-
err(1, "the frontmatter block opened here is never closed \u2014 end it with a line holding only ---");
|
|
4374
|
-
return { values: {}, errors, lines: {} };
|
|
4375
|
-
}
|
|
4376
|
-
const out = {};
|
|
4377
|
-
const keyLines = {};
|
|
4378
|
-
let listKey = null;
|
|
4379
|
-
for (let i = 1; i < end; i++) {
|
|
4380
|
-
const line = lines[i];
|
|
4381
|
-
const n = i + 1;
|
|
4382
|
-
const bareCr = line.includes("\r");
|
|
4383
|
-
if (bareCr) err(n, "line ends in a bare CR \u2014 use LF or CRLF line endings");
|
|
4384
|
-
const lineErr = (message) => {
|
|
4385
|
-
if (!bareCr) err(n, message);
|
|
4386
|
-
};
|
|
4387
|
-
const item = line.match(/^\s*-\s+(.*)$/);
|
|
4388
|
-
if (item && listKey) {
|
|
4389
|
-
if (unterminatedQuote(item[1])) lineErr(`list item ${item[1].trim()} has an opening quote with no closing one`);
|
|
4390
|
-
out[listKey].push(unquote(item[1]));
|
|
4391
|
-
continue;
|
|
4392
|
-
}
|
|
4393
|
-
const kv = line.match(/^([A-Za-z_][\w-]*):\s*(.*)$/);
|
|
4394
|
-
if (!kv) {
|
|
4395
|
-
if (line.trim() !== "") {
|
|
4396
|
-
if (item) lineErr("list item with no key above it \u2014 a list follows a line `key:`");
|
|
4397
|
-
else if (/^\s+[A-Za-z_][\w-]*:/.test(line)) lineErr("nested value \u2014 only top-level keys are supported, written at the start of the line");
|
|
4398
|
-
else lineErr(`cannot read this line: ${line.trim()}`);
|
|
4399
|
-
}
|
|
4400
|
-
listKey = null;
|
|
4401
|
-
continue;
|
|
4402
|
-
}
|
|
4403
|
-
const [, key, raw] = kv;
|
|
4404
|
-
if (key in out) lineErr(`duplicate key "${key}"`);
|
|
4405
|
-
keyLines[key] = n;
|
|
4406
|
-
const value = raw.trim();
|
|
4407
|
-
if (value === "") {
|
|
4408
|
-
out[key] = [];
|
|
4409
|
-
listKey = key;
|
|
4410
|
-
} else if (value.startsWith("[") && value.endsWith("]")) {
|
|
4411
|
-
const items = value.slice(1, -1).split(",");
|
|
4412
|
-
for (const it of items) if (unterminatedQuote(it)) lineErr(`list item ${it.trim()} has an opening quote with no closing one`);
|
|
4413
|
-
out[key] = items.map(unquote).filter(Boolean);
|
|
4414
|
-
listKey = null;
|
|
4415
|
-
} else {
|
|
4416
|
-
if (value.startsWith("[")) lineErr(`"${key}": the list opened with [ is never closed with ]`);
|
|
4417
|
-
else if (unterminatedQuote(value)) lineErr(`"${key}": the value has an opening quote with no closing one`);
|
|
4418
|
-
out[key] = unquote(value);
|
|
4419
|
-
listKey = null;
|
|
4420
|
-
}
|
|
4421
|
-
}
|
|
4422
|
-
return { values: out, errors, lines: keyLines };
|
|
4423
|
-
}
|
|
4424
|
-
function readFrontmatter(md) {
|
|
4425
|
-
return scan(md).values;
|
|
4426
|
-
}
|
|
4427
|
-
function readFrontmatterStrict(md) {
|
|
4428
|
-
return scan(md);
|
|
4429
|
-
}
|
|
4430
|
-
function frontmatterBodyOffset(md) {
|
|
4431
|
-
const { end } = block(md);
|
|
4432
|
-
return end === null || end < 0 ? 0 : end + 1;
|
|
4433
|
-
}
|
|
4434
|
-
function frontmatterBody(md) {
|
|
4435
|
-
const { text, lines, end } = block(md);
|
|
4436
|
-
if (end === null) return text;
|
|
4437
|
-
if (end < 0) return "";
|
|
4438
|
-
return lines.slice(end + 1).join("\n");
|
|
4439
|
-
}
|
|
4440
|
-
|
|
4441
|
-
// packages/dev-server/src/tree-order.ts
|
|
4442
|
-
function parseOrder(value) {
|
|
4443
|
-
if (value === void 0) return void 0;
|
|
4444
|
-
if (Array.isArray(value)) return { list: value };
|
|
4445
|
-
if (/^-?\d+(\.\d+)?$/.test(value)) return { weight: Number(value) };
|
|
4446
|
-
const list = value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
4447
|
-
return list.length ? { list } : void 0;
|
|
4448
|
-
}
|
|
4449
|
-
var collator = new Intl.Collator("en", { numeric: true });
|
|
4450
|
-
var REST = "...";
|
|
4451
|
-
function orderSiblings(items, list) {
|
|
4452
|
-
const position = /* @__PURE__ */ new Map();
|
|
4453
|
-
list?.forEach((name, i) => {
|
|
4454
|
-
if (!position.has(name)) position.set(name, i);
|
|
4455
|
-
});
|
|
4456
|
-
const restPosition = position.get(REST) ?? Infinity;
|
|
4457
|
-
const positionOf2 = (item) => list ? position.get(item.segment) ?? restPosition : 0;
|
|
4458
|
-
return [...items].sort((a, b) => {
|
|
4459
|
-
const ra = a.weight ?? 0;
|
|
4460
|
-
const rb = b.weight ?? 0;
|
|
4461
|
-
if (ra !== rb) return ra - rb;
|
|
4462
|
-
const pa = positionOf2(a);
|
|
4463
|
-
const pb = positionOf2(b);
|
|
4464
|
-
if (pa !== pb) return pa - pb;
|
|
4465
|
-
return collator.compare(a.segment, b.segment) || (a.segment < b.segment ? -1 : a.segment > b.segment ? 1 : 0);
|
|
4466
|
-
});
|
|
4467
|
-
}
|
|
4468
|
-
|
|
4469
5318
|
// packages/dev-server/src/component-tree.ts
|
|
4470
5319
|
function directChildPaths(parentPath, allIds) {
|
|
4471
5320
|
const prefix = `${parentPath}/`;
|
|
@@ -4483,9 +5332,9 @@ function folderTitle(segment) {
|
|
|
4483
5332
|
const text = words.join(" ");
|
|
4484
5333
|
return text.charAt(0).toUpperCase() + text.slice(1);
|
|
4485
5334
|
}
|
|
4486
|
-
var readText = (path) =>
|
|
4487
|
-
function orderOf(
|
|
4488
|
-
return
|
|
5335
|
+
var readText = (path) => readFile15(path, "utf-8").catch(() => null);
|
|
5336
|
+
function orderOf(md2) {
|
|
5337
|
+
return md2 === null ? void 0 : parseOrder(readFrontmatter(md2)["order"]);
|
|
4489
5338
|
}
|
|
4490
5339
|
function hasPublicComponent(node) {
|
|
4491
5340
|
if (node.kind === "component" && node.visibility !== "private") return true;
|
|
@@ -4519,22 +5368,22 @@ async function buildTreeNodeFromMap(nodePath, idsUnderPath, patternsDir, overvie
|
|
|
4519
5368
|
let visibility;
|
|
4520
5369
|
let weight;
|
|
4521
5370
|
if (isComponent && entry.mdPath) {
|
|
4522
|
-
const
|
|
4523
|
-
if (
|
|
4524
|
-
const t =
|
|
4525
|
-
const s =
|
|
4526
|
-
const v =
|
|
5371
|
+
const md2 = await readText(entry.mdPath);
|
|
5372
|
+
if (md2 !== null) {
|
|
5373
|
+
const t = md2.match(/^title:\s*(.+)$/m);
|
|
5374
|
+
const s = md2.match(/^status:\s*(.+)$/m);
|
|
5375
|
+
const v = md2.match(/^visibility:\s*(public|private)$/m);
|
|
4527
5376
|
if (t) title = t[1].trim();
|
|
4528
5377
|
if (s) {
|
|
4529
5378
|
const sv = s[1].trim();
|
|
4530
5379
|
if (sv === "stable" || sv === "deprecated" || sv === "draft") status = sv;
|
|
4531
5380
|
}
|
|
4532
5381
|
if (v) visibility = v[1];
|
|
4533
|
-
const o = orderOf(
|
|
5382
|
+
const o = orderOf(md2);
|
|
4534
5383
|
if (o && "weight" in o) weight = o.weight;
|
|
4535
5384
|
}
|
|
4536
5385
|
}
|
|
4537
|
-
const indexMd = await readFolderFile(patternsDir,
|
|
5386
|
+
const indexMd = await readFolderFile(patternsDir, join23(nodePath, "index.md"));
|
|
4538
5387
|
const folderOrder = orderOf(indexMd);
|
|
4539
5388
|
const built = [];
|
|
4540
5389
|
for (const childPath of directChildPaths(nodePath, idsUnderPath)) {
|
|
@@ -4584,10 +5433,10 @@ import { readFile as readFile21 } from "node:fs/promises";
|
|
|
4584
5433
|
// packages/dev-server/src/markdown.ts
|
|
4585
5434
|
import { readFile as readFile19 } from "node:fs/promises";
|
|
4586
5435
|
import { basename as basename8 } from "node:path";
|
|
4587
|
-
import
|
|
5436
|
+
import MarkdownIt4 from "markdown-it";
|
|
4588
5437
|
|
|
4589
5438
|
// packages/dev-server/src/placed-tags.ts
|
|
4590
|
-
import
|
|
5439
|
+
import MarkdownIt2 from "markdown-it";
|
|
4591
5440
|
var OPEN = /^<(pd-[a-z0-9-]+(?:~[a-z0-9-]+)?)/;
|
|
4592
5441
|
var CLOSE = /^<\/(pd-[a-z0-9-~]+)\s*>/;
|
|
4593
5442
|
var ATTR = /^\s+([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/;
|
|
@@ -4671,9 +5520,9 @@ function placedTagRule(state, startLine, endLine, silent) {
|
|
|
4671
5520
|
state.line = Math.min(line + 1, endLine);
|
|
4672
5521
|
return true;
|
|
4673
5522
|
}
|
|
4674
|
-
function usePlacedTags(
|
|
4675
|
-
|
|
4676
|
-
return
|
|
5523
|
+
function usePlacedTags(md2) {
|
|
5524
|
+
md2.block.ruler.before("paragraph", "pd_component", placedTagRule, { alt: ["paragraph", "blockquote", "list"] });
|
|
5525
|
+
return md2;
|
|
4677
5526
|
}
|
|
4678
5527
|
var camel = (s) => s.replace(/-([a-z0-9])/g, (_, c3) => c3.toUpperCase());
|
|
4679
5528
|
function dedent(s) {
|
|
@@ -4739,10 +5588,10 @@ async function toPlaced(tag, resolver, problems) {
|
|
|
4739
5588
|
// packages/dev-server/src/storefront/placed.ts
|
|
4740
5589
|
import { readFile as readFile18 } from "node:fs/promises";
|
|
4741
5590
|
import { basename as basename7 } from "node:path";
|
|
4742
|
-
import
|
|
5591
|
+
import MarkdownIt3 from "markdown-it";
|
|
4743
5592
|
|
|
4744
5593
|
// packages/dev-server/src/storefront/declared-values.ts
|
|
4745
|
-
import { readFile as
|
|
5594
|
+
import { readFile as readFile16 } from "node:fs/promises";
|
|
4746
5595
|
var NUMERIC = /^-?\d+(\.\d+)?$/;
|
|
4747
5596
|
var quote = (v) => Array.isArray(v) ? `[${v.join(", ")}]` : `"${v}"`;
|
|
4748
5597
|
var describe = (v, mode) => mode === "frontmatter" ? quote(v) : JSON.stringify(v);
|
|
@@ -4836,7 +5685,7 @@ function jsonValue(prop, value, path, owner, problems) {
|
|
|
4836
5685
|
async function readSchema(schemaPath) {
|
|
4837
5686
|
if (!schemaPath) return {};
|
|
4838
5687
|
try {
|
|
4839
|
-
return { schema: JSON.parse(await
|
|
5688
|
+
return { schema: JSON.parse(await readFile16(schemaPath, "utf8")) };
|
|
4840
5689
|
} catch (e) {
|
|
4841
5690
|
return { error: e instanceof Error ? e.message : String(e) };
|
|
4842
5691
|
}
|
|
@@ -4927,209 +5776,12 @@ async function placedValue(prop, node, where, ctx, problems) {
|
|
|
4927
5776
|
import { readFile as readFile17 } from "node:fs/promises";
|
|
4928
5777
|
import { basename as basename6 } from "node:path";
|
|
4929
5778
|
|
|
4930
|
-
// packages/dev-server/src/storefront/pages.ts
|
|
4931
|
-
import { readdir as readdir8, readFile as readFile16, stat as stat5 } from "node:fs/promises";
|
|
4932
|
-
import { join as join22, relative as relative11, sep as sep7 } from "node:path";
|
|
4933
|
-
|
|
4934
|
-
// packages/dev-server/src/storefront/redirects.ts
|
|
4935
|
-
function redirectsOf(f) {
|
|
4936
|
-
const v = f.frontmatter.values["redirectFrom"];
|
|
4937
|
-
return Array.isArray(v) ? v : typeof v === "string" ? [v] : [];
|
|
4938
|
-
}
|
|
4939
|
-
var RESERVED = [PD_ROUTE_PREFIX.preview, "/_pd/", PD_ROUTE_PREFIX.assets, "/api/", PD_ROUTE_PREFIX.source];
|
|
4940
|
-
function redirectProblems(files, workbenchPath) {
|
|
4941
|
-
const problems = [];
|
|
4942
|
-
const claimed = /* @__PURE__ */ new Map();
|
|
4943
|
-
for (const f of files.values()) {
|
|
4944
|
-
const at = `${f.path}${f.frontmatter.lines["redirectFrom"] !== void 0 ? `:${f.frontmatter.lines["redirectFrom"]}` : ""}`;
|
|
4945
|
-
for (const old of redirectsOf(f)) {
|
|
4946
|
-
if (typeof old !== "string" || !old.startsWith("/") || !old.endsWith("/")) continue;
|
|
4947
|
-
const page = files.get(old);
|
|
4948
|
-
if (page) {
|
|
4949
|
-
problems.push(`${at}: redirectFrom "${old}" is the page ${page.path}: a route can't both be a page and forward to one`);
|
|
4950
|
-
continue;
|
|
4951
|
-
}
|
|
4952
|
-
const reserved = RESERVED.find((r) => old.startsWith(r));
|
|
4953
|
-
if (reserved) {
|
|
4954
|
-
problems.push(`${at}: redirectFrom "${old}" is under ${reserved}, which Pattern Dump serves itself`);
|
|
4955
|
-
continue;
|
|
4956
|
-
}
|
|
4957
|
-
if (workbenchPath !== void 0 && old.startsWith(workbenchPath)) {
|
|
4958
|
-
problems.push(`${at}: redirectFrom "${old}" is ${workbenchPlace(old, workbenchPath)}`);
|
|
4959
|
-
continue;
|
|
4960
|
-
}
|
|
4961
|
-
const other = claimed.get(old);
|
|
4962
|
-
if (other && other !== f) {
|
|
4963
|
-
problems.push(`${at}: redirectFrom "${old}" is also claimed by ${other.path}: one old route forwards to one page`);
|
|
4964
|
-
continue;
|
|
4965
|
-
}
|
|
4966
|
-
claimed.set(old, f);
|
|
4967
|
-
}
|
|
4968
|
-
}
|
|
4969
|
-
return problems;
|
|
4970
|
-
}
|
|
4971
|
-
function workbenchPlace(route, workbenchPath) {
|
|
4972
|
-
return `${route === workbenchPath ? "at" : "under"} ${workbenchPath}, where the workbench is published on this host (storefront.workbenchUrl), so the workbench would overwrite it there`;
|
|
4973
|
-
}
|
|
4974
|
-
function redirectStub(p) {
|
|
4975
|
-
const target = escapeAttr(p.route);
|
|
4976
|
-
const canonical2 = p.siteUrl !== void 0 ? `
|
|
4977
|
-
<link rel="canonical" href="${escapeAttr(new URL(p.route.slice(1), p.siteUrl).href)}">` : "";
|
|
4978
|
-
return `<!DOCTYPE html>
|
|
4979
|
-
<html lang="${escapeAttr(p.lang)}">
|
|
4980
|
-
<head>
|
|
4981
|
-
<meta charset="UTF-8">
|
|
4982
|
-
<title>Moved to ${escapeHtml(p.title)}</title>${canonical2}
|
|
4983
|
-
<meta name="robots" content="noindex">
|
|
4984
|
-
<meta http-equiv="refresh" content="0; url=${target}">
|
|
4985
|
-
</head>
|
|
4986
|
-
<body>
|
|
4987
|
-
<p>This page has moved to <a href="${target}">${escapeHtml(p.title)}</a>.</p>
|
|
4988
|
-
</body>
|
|
4989
|
-
</html>
|
|
4990
|
-
`;
|
|
4991
|
-
}
|
|
4992
|
-
|
|
4993
|
-
// packages/dev-server/src/storefront/pages.ts
|
|
4994
|
-
var DEFAULT_LAYOUT = "page";
|
|
4995
|
-
var NOT_FOUND_FILE = "404.md";
|
|
4996
|
-
function routeFor(path) {
|
|
4997
|
-
const parts = path.replace(/\.md$/, "").split("/");
|
|
4998
|
-
if (parts.at(-1) === "index") parts.pop();
|
|
4999
|
-
return parts.length ? `/${parts.join("/")}/` : "/";
|
|
5000
|
-
}
|
|
5001
|
-
var NAME = /^[a-z0-9-]+$/;
|
|
5002
|
-
async function markdownFiles(dir, root, problems) {
|
|
5003
|
-
const out = [];
|
|
5004
|
-
const entries = (await readdir8(dir, { withFileTypes: true }).catch(() => [])).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
5005
|
-
for (const e of entries) {
|
|
5006
|
-
if (e.name.startsWith(".")) continue;
|
|
5007
|
-
const p = join22(dir, e.name);
|
|
5008
|
-
const rel = relative11(root, p).split(sep7).join("/");
|
|
5009
|
-
if (e.isDirectory()) {
|
|
5010
|
-
if (!NAME.test(e.name)) {
|
|
5011
|
-
problems.push(`${rel}/: a folder name is part of the URL, so it is lowercase letters, digits and hyphens only`);
|
|
5012
|
-
continue;
|
|
5013
|
-
}
|
|
5014
|
-
out.push(...await markdownFiles(p, root, problems));
|
|
5015
|
-
} else if (!e.name.endsWith(".md")) {
|
|
5016
|
-
problems.push(`${rel} is not a page: only .md files belong in the pages folder (images and other files go in the project's assets)`);
|
|
5017
|
-
} else if (!NAME.test(e.name.slice(0, -".md".length))) {
|
|
5018
|
-
problems.push(`${rel}: a page's file name is part of the URL, so it is lowercase letters, digits and hyphens only, then .md`);
|
|
5019
|
-
} else {
|
|
5020
|
-
out.push(rel);
|
|
5021
|
-
}
|
|
5022
|
-
}
|
|
5023
|
-
return out;
|
|
5024
|
-
}
|
|
5025
|
-
var scalar = (v) => typeof v === "string" ? v : void 0;
|
|
5026
|
-
async function readPages(pagesDir, opts = {}) {
|
|
5027
|
-
const problems = [];
|
|
5028
|
-
const files = /* @__PURE__ */ new Map();
|
|
5029
|
-
let notFound;
|
|
5030
|
-
if (!await stat5(pagesDir).then((s) => s.isDirectory(), () => false)) {
|
|
5031
|
-
return { pages: [], nav: [], files, problems: [`the pages folder ${pagesDir} does not exist`] };
|
|
5032
|
-
}
|
|
5033
|
-
for (const path of (await markdownFiles(pagesDir, pagesDir, problems)).sort()) {
|
|
5034
|
-
const route = routeFor(path);
|
|
5035
|
-
const other = files.get(route);
|
|
5036
|
-
if (other) {
|
|
5037
|
-
problems.push(`${other.path} and ${path} are both the page at ${route}`);
|
|
5038
|
-
continue;
|
|
5039
|
-
}
|
|
5040
|
-
const file = join22(pagesDir, ...path.split("/"));
|
|
5041
|
-
const text = await readFile16(file, "utf8").catch((e) => {
|
|
5042
|
-
if (e.code === "ENOENT") return null;
|
|
5043
|
-
throw e;
|
|
5044
|
-
});
|
|
5045
|
-
if (text === null) continue;
|
|
5046
|
-
if (path === NOT_FOUND_FILE) {
|
|
5047
|
-
notFound = { route: PD_ROUTES.notFound, file, path, frontmatter: readFrontmatterStrict(text) };
|
|
5048
|
-
continue;
|
|
5049
|
-
}
|
|
5050
|
-
files.set(route, { route, file, path, frontmatter: readFrontmatterStrict(text) });
|
|
5051
|
-
}
|
|
5052
|
-
const pageOf = (f) => {
|
|
5053
|
-
const redirectFrom = redirectsOf(f);
|
|
5054
|
-
return {
|
|
5055
|
-
route: f.route,
|
|
5056
|
-
title: scalar(f.frontmatter.values["title"]) ?? f.route,
|
|
5057
|
-
layout: scalar(f.frontmatter.values["layout"]) ?? DEFAULT_LAYOUT,
|
|
5058
|
-
...redirectFrom.length ? { redirectFrom } : {}
|
|
5059
|
-
};
|
|
5060
|
-
};
|
|
5061
|
-
const entryOf = (f) => {
|
|
5062
|
-
const description = scalar(f.frontmatter.values["description"]);
|
|
5063
|
-
return { route: f.route, title: pageOf(f).title, ...description !== void 0 ? { description } : {} };
|
|
5064
|
-
};
|
|
5065
|
-
const weightOf = (f) => {
|
|
5066
|
-
const o = parseOrder(f?.frontmatter.values["order"]);
|
|
5067
|
-
return o && "weight" in o ? o.weight : void 0;
|
|
5068
|
-
};
|
|
5069
|
-
const listOf = (f) => {
|
|
5070
|
-
const o = parseOrder(f?.frontmatter.values["order"]);
|
|
5071
|
-
return o && "list" in o ? o.list : void 0;
|
|
5072
|
-
};
|
|
5073
|
-
const folders = /* @__PURE__ */ new Set();
|
|
5074
|
-
for (const f of files.values()) {
|
|
5075
|
-
const parts = f.path.split("/").slice(0, -1);
|
|
5076
|
-
for (let i = 1; i <= parts.length; i++) folders.add(`/${parts.slice(0, i).join("/")}/`);
|
|
5077
|
-
}
|
|
5078
|
-
if (!files.has("/")) problems.push("there is no home page: the pages folder needs an index.md at its top, the page at /");
|
|
5079
|
-
for (const folder of [...folders].sort()) {
|
|
5080
|
-
if (!files.has(folder)) problems.push(`the folder ${folder.slice(1)} holds pages but has no index.md \u2014 it needs one, as the folder's own page`);
|
|
5081
|
-
}
|
|
5082
|
-
const childrenOf = (folder) => {
|
|
5083
|
-
const depth = folder === "/" ? 0 : folder.split("/").length - 2;
|
|
5084
|
-
const out = /* @__PURE__ */ new Map();
|
|
5085
|
-
for (const route of [...files.keys(), ...folders]) {
|
|
5086
|
-
if (route === folder || !route.startsWith(folder)) continue;
|
|
5087
|
-
const segs = route.split("/").filter(Boolean);
|
|
5088
|
-
if (segs.length !== depth + 1) continue;
|
|
5089
|
-
out.set(route, segs[depth]);
|
|
5090
|
-
}
|
|
5091
|
-
return [...out].map(([route, segment]) => ({ route, segment }));
|
|
5092
|
-
};
|
|
5093
|
-
const ordered = (folder, extra = []) => {
|
|
5094
|
-
const items = childrenOf(folder).map((c3) => {
|
|
5095
|
-
const w = weightOf(files.get(c3.route));
|
|
5096
|
-
return { route: c3.route, segment: c3.segment, ...w !== void 0 ? { weight: w } : {} };
|
|
5097
|
-
});
|
|
5098
|
-
return orderSiblings([...extra, ...items], listOf(files.get(folder)));
|
|
5099
|
-
};
|
|
5100
|
-
const home = files.get("/");
|
|
5101
|
-
const homeWeight = weightOf(home);
|
|
5102
|
-
const top = ordered("/", home ? [{ route: "/", segment: "index", ...homeWeight !== void 0 ? { weight: homeWeight } : {} }] : []);
|
|
5103
|
-
const nav = [];
|
|
5104
|
-
for (const item of top) {
|
|
5105
|
-
const page = files.get(item.route);
|
|
5106
|
-
if (!page) continue;
|
|
5107
|
-
const children = folders.has(item.route) && item.route !== "/" ? ordered(item.route).flatMap((c3) => {
|
|
5108
|
-
const p = files.get(c3.route);
|
|
5109
|
-
return p ? [entryOf(p)] : [];
|
|
5110
|
-
}) : [];
|
|
5111
|
-
nav.push({ ...entryOf(page), ...children.length ? { children } : {} });
|
|
5112
|
-
}
|
|
5113
|
-
const inNav = nav.flatMap((n) => [n.route, ...(n.children ?? []).map((c3) => c3.route)]);
|
|
5114
|
-
const rest = [...files.keys()].filter((r) => !inNav.includes(r)).sort();
|
|
5115
|
-
const routes = [...inNav, ...rest];
|
|
5116
|
-
const pages = routes.map((r) => pageOf(files.get(r)));
|
|
5117
|
-
const { workbenchPath } = opts;
|
|
5118
|
-
if (workbenchPath !== void 0) {
|
|
5119
|
-
for (const f of files.values()) {
|
|
5120
|
-
if (f.route.startsWith(workbenchPath)) problems.push(`${f.path}: the page ${f.route} is ${workbenchPlace(f.route, workbenchPath)}`);
|
|
5121
|
-
}
|
|
5122
|
-
}
|
|
5123
|
-
problems.push(...redirectProblems(files, workbenchPath));
|
|
5124
|
-
return { pages, nav, files: new Map(routes.map((r) => [r, files.get(r)])), problems, ...notFound ? { notFound } : {} };
|
|
5125
|
-
}
|
|
5126
|
-
|
|
5127
5779
|
// packages/dev-server/src/storefront/frontmatter-contract.ts
|
|
5128
|
-
var RESERVED_KEYS = ["layout", "order", "redirectFrom", "index", "image"];
|
|
5780
|
+
var RESERVED_KEYS = ["layout", "order", "redirectFrom", "index", "image", "date", "tags", "draft", "sort"];
|
|
5129
5781
|
var ASSEMBLY_KEYS = ["body", "nav", "site", "page"];
|
|
5130
5782
|
var OLD_ROUTE = /^\/(?:(?!\.\.?\/)[^/?#\s]+\/)+$/;
|
|
5131
5783
|
var ASSET_PATH = /^\/assets\/[^?#]+[^/?#]$/;
|
|
5132
|
-
function checkBuildKeys(values, at) {
|
|
5784
|
+
function checkBuildKeys(values, path, at) {
|
|
5133
5785
|
const out = {};
|
|
5134
5786
|
const redirect = values["redirectFrom"];
|
|
5135
5787
|
if (redirect !== void 0) {
|
|
@@ -5149,6 +5801,16 @@ function checkBuildKeys(values, at) {
|
|
|
5149
5801
|
if (typeof image === "string" && ASSET_PATH.test(image)) out.image = image;
|
|
5150
5802
|
else at("image", `image must be a path to a file in the assets, such as "/assets/images/share.png"`);
|
|
5151
5803
|
}
|
|
5804
|
+
const take = (key, r) => {
|
|
5805
|
+
if (r === void 0) return;
|
|
5806
|
+
if (r.ok) out[key] = r.value;
|
|
5807
|
+
else at(key, r.message);
|
|
5808
|
+
};
|
|
5809
|
+
const v = (key) => values[key];
|
|
5810
|
+
take("date", v("date") === void 0 ? void 0 : readDate(v("date")));
|
|
5811
|
+
take("tags", v("tags") === void 0 ? void 0 : readTags(v("tags")));
|
|
5812
|
+
take("draft", v("draft") === void 0 ? void 0 : readDraft(v("draft")));
|
|
5813
|
+
take("sort", v("sort") === void 0 ? void 0 : readSort(v("sort"), path));
|
|
5152
5814
|
return out;
|
|
5153
5815
|
}
|
|
5154
5816
|
function layoutNames(discoveryMap) {
|
|
@@ -5171,12 +5833,12 @@ async function checkPageFrontmatter(page, discoveryMap) {
|
|
|
5171
5833
|
if (!Array.isArray(rawLayout)) {
|
|
5172
5834
|
at("layout", known.length ? `layout "${layout}" is not a layout \u2014 the layouts are ${known.map((n) => `"${n}"`).join(", ")}` : `layout "${layout}" is not a layout \u2014 the project has no layouts (components in a layouts/ folder of its patterns)`);
|
|
5173
5835
|
}
|
|
5174
|
-
return { layoutId, values: {}, build: checkBuildKeys(values, at), problems };
|
|
5836
|
+
return { layoutId, values: {}, build: checkBuildKeys(values, page.path, at), problems };
|
|
5175
5837
|
}
|
|
5176
5838
|
const read = await readSchema(entry.schemaPath);
|
|
5177
5839
|
if (read.error !== void 0) {
|
|
5178
5840
|
problems.push({ file: page.path, message: `the schema of layout "${layout}" cannot be read: ${read.error}` });
|
|
5179
|
-
return { layoutId, values: {}, build: checkBuildKeys(values, at), problems };
|
|
5841
|
+
return { layoutId, values: {}, build: checkBuildKeys(values, page.path, at), problems };
|
|
5180
5842
|
}
|
|
5181
5843
|
const schema = read.schema ?? {};
|
|
5182
5844
|
const hasSchema = read.schema !== void 0;
|
|
@@ -5186,7 +5848,7 @@ async function checkPageFrontmatter(page, discoveryMap) {
|
|
|
5186
5848
|
problems.push({ file: page.path, message: `layout "${layout}" declares "${key}", which a page sets for the build, not for its layout: rename it in the layout's schema` });
|
|
5187
5849
|
}
|
|
5188
5850
|
}
|
|
5189
|
-
const build = checkBuildKeys(values, at);
|
|
5851
|
+
const build = checkBuildKeys(values, page.path, at);
|
|
5190
5852
|
const out = {};
|
|
5191
5853
|
for (const [key, value] of Object.entries(values)) {
|
|
5192
5854
|
if (RESERVED_KEYS.includes(key)) continue;
|
|
@@ -5217,6 +5879,9 @@ async function checkPageFrontmatter(page, discoveryMap) {
|
|
|
5217
5879
|
|
|
5218
5880
|
// packages/dev-server/src/storefront/derived.ts
|
|
5219
5881
|
var LINK = { fields: { route: "value", title: "value" } };
|
|
5882
|
+
var ENTRY = {
|
|
5883
|
+
fields: { route: "value", title: "value", description: "value", date: "value", image: "value", tags: "value", draft: "value" }
|
|
5884
|
+
};
|
|
5220
5885
|
var PAGE_SHAPE = {
|
|
5221
5886
|
fields: {
|
|
5222
5887
|
headings: { items: { fields: { level: "value", text: "value", anchor: "value" } } },
|
|
@@ -5225,10 +5890,13 @@ var PAGE_SHAPE = {
|
|
|
5225
5890
|
prev: LINK,
|
|
5226
5891
|
next: LINK,
|
|
5227
5892
|
created: "value",
|
|
5228
|
-
modified: "value"
|
|
5893
|
+
modified: "value",
|
|
5894
|
+
children: { items: ENTRY },
|
|
5895
|
+
date: "value",
|
|
5896
|
+
tags: "value"
|
|
5229
5897
|
}
|
|
5230
5898
|
};
|
|
5231
|
-
var FILLED = ["headings", "section", "breadcrumbs", "prev", "next", "created", "modified"];
|
|
5899
|
+
var FILLED = ["headings", "section", "breadcrumbs", "prev", "next", "created", "modified", "children", "date", "tags"];
|
|
5232
5900
|
function positionOf(route, site) {
|
|
5233
5901
|
const titles = new Map(site.pages.map((p) => [p.route, p.title]));
|
|
5234
5902
|
const link = (r) => titles.has(r) ? { route: r, title: titles.get(r) } : null;
|
|
@@ -5276,7 +5944,7 @@ var NAVIGATIONS_KEY = "navigations";
|
|
|
5276
5944
|
var AT = "storefront.site.navigations";
|
|
5277
5945
|
var KEYS = ["page", "href", "label", "children"];
|
|
5278
5946
|
var isObject5 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
5279
|
-
var
|
|
5947
|
+
var show2 = (v) => typeof v === "string" ? `"${v}"` : JSON.stringify(v) ?? String(v);
|
|
5280
5948
|
var isAllowedHref = (href) => /^(https?:\/\/|mailto:)/i.test(href) || /^\/(?!\/)/.test(href);
|
|
5281
5949
|
function listedNavigations(declared, owner) {
|
|
5282
5950
|
if (!declared) return { listed: [], problems: [] };
|
|
@@ -5299,7 +5967,7 @@ function checkNavigations(value, declared, owner, pagePaths) {
|
|
|
5299
5967
|
const problems = [...names.problems];
|
|
5300
5968
|
const navigations = {};
|
|
5301
5969
|
if (value !== void 0 && !isObject5(value)) {
|
|
5302
|
-
problems.push(`${AT} must be an object of named navigations, such as { footer: [{ page: "about.md" }] } (got ${
|
|
5970
|
+
problems.push(`${AT} must be an object of named navigations, such as { footer: [{ page: "about.md" }] } (got ${show2(value)})`);
|
|
5303
5971
|
return { navigations, listed: names.listed, problems };
|
|
5304
5972
|
}
|
|
5305
5973
|
const given = value ?? {};
|
|
@@ -5310,7 +5978,7 @@ function checkNavigations(value, declared, owner, pagePaths) {
|
|
|
5310
5978
|
continue;
|
|
5311
5979
|
}
|
|
5312
5980
|
if (!Array.isArray(entries)) {
|
|
5313
|
-
problems.push(`${AT}.${name} must be a list of entries (got ${
|
|
5981
|
+
problems.push(`${AT}.${name} must be a list of entries (got ${show2(entries)})`);
|
|
5314
5982
|
continue;
|
|
5315
5983
|
}
|
|
5316
5984
|
problems.push(...checkEntries(entries, `${AT}.${name}`, pagePaths));
|
|
@@ -5326,7 +5994,7 @@ function checkEntries(entries, at, pagePaths) {
|
|
|
5326
5994
|
}
|
|
5327
5995
|
function checkEntry(entry, at, pagePaths) {
|
|
5328
5996
|
const FORMS = `{ page }, { href, label } or { label, children }`;
|
|
5329
|
-
if (!isObject5(entry)) return [`${at} must be an entry, one of ${FORMS} (got ${
|
|
5997
|
+
if (!isObject5(entry)) return [`${at} must be an entry, one of ${FORMS} (got ${show2(entry)})`];
|
|
5330
5998
|
const problems = [];
|
|
5331
5999
|
for (const key of Object.keys(entry)) {
|
|
5332
6000
|
if (KEYS.includes(key)) continue;
|
|
@@ -5339,11 +6007,11 @@ function checkEntry(entry, at, pagePaths) {
|
|
|
5339
6007
|
return problems;
|
|
5340
6008
|
}
|
|
5341
6009
|
const label = entry["label"];
|
|
5342
|
-
if (label !== void 0 && typeof label !== "string") problems.push(`${at}.label must be text (got ${
|
|
6010
|
+
if (label !== void 0 && typeof label !== "string") problems.push(`${at}.label must be text (got ${show2(label)})`);
|
|
5343
6011
|
const form = forms[0];
|
|
5344
6012
|
if (form === "page") {
|
|
5345
6013
|
const page = entry["page"];
|
|
5346
|
-
if (typeof page !== "string") problems.push(`${at}.page must be the path of a page in the pages folder, such as "about.md" (got ${
|
|
6014
|
+
if (typeof page !== "string") problems.push(`${at}.page must be the path of a page in the pages folder, such as "about.md" (got ${show2(page)})`);
|
|
5347
6015
|
else if (!pagePaths.has(page)) {
|
|
5348
6016
|
const near = suggest(page, [...pagePaths]);
|
|
5349
6017
|
problems.push(`${at}: "${page}" is not a page in the pages folder${near ? ` \u2014 did you mean "${near}"?` : ""}`);
|
|
@@ -5352,11 +6020,11 @@ function checkEntry(entry, at, pagePaths) {
|
|
|
5352
6020
|
if (label === void 0) problems.push(`${at} needs a "label"`);
|
|
5353
6021
|
if (form === "href") {
|
|
5354
6022
|
const href = entry["href"];
|
|
5355
|
-
if (typeof href !== "string") problems.push(`${at}.href must be text (got ${
|
|
5356
|
-
else if (!isAllowedHref(href)) problems.push(`${at}.href ${
|
|
6023
|
+
if (typeof href !== "string") problems.push(`${at}.href must be text (got ${show2(href)})`);
|
|
6024
|
+
else if (!isAllowedHref(href)) problems.push(`${at}.href ${show2(href)} must start with https:, http: or mailto:, or be a path from the site's root (/\u2026)`);
|
|
5357
6025
|
} else {
|
|
5358
6026
|
const children = entry["children"];
|
|
5359
|
-
if (!Array.isArray(children)) problems.push(`${at}.children must be a list of entries (got ${
|
|
6027
|
+
if (!Array.isArray(children)) problems.push(`${at}.children must be a list of entries (got ${show2(children)})`);
|
|
5360
6028
|
else problems.push(...checkEntries(children, `${at}.children`, pagePaths));
|
|
5361
6029
|
}
|
|
5362
6030
|
}
|
|
@@ -5377,10 +6045,86 @@ function resolveNavigations(navigations, listed, model, route) {
|
|
|
5377
6045
|
return Object.fromEntries(listed.map((name) => [name, Object.hasOwn(navigations, name) ? resolve15(navigations[name]) : null]));
|
|
5378
6046
|
}
|
|
5379
6047
|
|
|
6048
|
+
// packages/dev-server/src/storefront/collections.ts
|
|
6049
|
+
async function dateOf(route, model, dates, mode) {
|
|
6050
|
+
const own = model.entries.get(route)?.date ?? null;
|
|
6051
|
+
const file = model.files.get(route);
|
|
6052
|
+
if (own !== null || !dates || !file) return { date: own, problems: [], notes: [] };
|
|
6053
|
+
const dated = await dates.of(file.path, mode);
|
|
6054
|
+
return { date: dated.shallow && mode === "build" ? null : dated.dates.created?.slice(0, 10) ?? null, problems: dated.problems, notes: dated.notes };
|
|
6055
|
+
}
|
|
6056
|
+
async function entriesOf(folder, model, o) {
|
|
6057
|
+
const listing = model.folders.get(folder);
|
|
6058
|
+
if (!listing) return { entries: [], problems: [], notes: [] };
|
|
6059
|
+
const titles = new Map(model.pages.map((p) => [p.route, p.title]));
|
|
6060
|
+
const problems = [], notes = [];
|
|
6061
|
+
const entries = [];
|
|
6062
|
+
for (const route of listing.children) {
|
|
6063
|
+
const fields = model.entries.get(route);
|
|
6064
|
+
if (!fields) continue;
|
|
6065
|
+
const dated = await dateOf(route, model, o.dates, o.mode);
|
|
6066
|
+
problems.push(...dated.problems);
|
|
6067
|
+
if (listing.sort === "date") notes.push(...dated.notes);
|
|
6068
|
+
entries.push({ route, title: titles.get(route) ?? route, ...fields, date: dated.date });
|
|
6069
|
+
}
|
|
6070
|
+
if (listing.sort === "date") {
|
|
6071
|
+
const index = model.files.get(folder).path;
|
|
6072
|
+
for (const e of entries.filter((e2) => e2.date === null)) {
|
|
6073
|
+
const text = `${index}: sort: date, but ${model.files.get(e.route).path} has no date: give it one (date: YYYY-MM-DD), or commit it so git dates it`;
|
|
6074
|
+
if (o.mode === "build") problems.push(text);
|
|
6075
|
+
else notes.push(`${text}; it is listed last`);
|
|
6076
|
+
}
|
|
6077
|
+
entries.sort((a, b) => a.date === b.date ? a.route < b.route ? -1 : 1 : a.date === null ? 1 : b.date === null ? -1 : a.date < b.date ? 1 : -1);
|
|
6078
|
+
}
|
|
6079
|
+
return { entries, problems: [...new Set(problems)], notes: [...new Set(notes)] };
|
|
6080
|
+
}
|
|
6081
|
+
var COLLECTIONS_KEY = "collections";
|
|
6082
|
+
var isObject6 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
6083
|
+
function checkCollections(declared, owner, folders, leftOut, configured) {
|
|
6084
|
+
const problems = [], notes = [];
|
|
6085
|
+
if (configured && Object.hasOwn(configured, COLLECTIONS_KEY)) {
|
|
6086
|
+
problems.push(`storefront.site.${COLLECTIONS_KEY} is filled by Pattern Dump from the pages folders: list the names in ${owner}'s schema, and don't set it in the config`);
|
|
6087
|
+
}
|
|
6088
|
+
if (!declared) return { listed: [], problems, notes };
|
|
6089
|
+
const names = isObject6(declared["properties"]) ? declared["properties"] : {};
|
|
6090
|
+
const extra = Object.keys(declared).filter((k) => !["type", "description", "title", "properties", "required"].includes(k));
|
|
6091
|
+
if (extra.length || declared["type"] !== void 0 && declared["type"] !== "object") {
|
|
6092
|
+
problems.push(`${owner} declares a shape for site.${COLLECTIONS_KEY}: the shape of an entry is Pattern Dump's, so list the folders' names under properties only`);
|
|
6093
|
+
}
|
|
6094
|
+
const required = Array.isArray(declared["required"]) ? declared["required"].map(String) : [];
|
|
6095
|
+
const listed = [.../* @__PURE__ */ new Set([...Object.keys(names), ...required])];
|
|
6096
|
+
for (const name of listed) {
|
|
6097
|
+
const schema = names[name];
|
|
6098
|
+
const own = Object.keys(isObject6(schema) ? schema : {}).filter((k) => k !== "description" && k !== "title");
|
|
6099
|
+
if (own.length) {
|
|
6100
|
+
problems.push(`${owner} declares ${own.map((k) => `"${k}"`).join(", ")} for site.${COLLECTIONS_KEY}.${name}: the shape of an entry is Pattern Dump's, so list the name only (a description at most)`);
|
|
6101
|
+
}
|
|
6102
|
+
if (!folders.has(name) && !leftOut.includes(`${name}/index.md`)) {
|
|
6103
|
+
const near = suggest(name, [...folders]);
|
|
6104
|
+
const text = `${owner} lists site.${COLLECTIONS_KEY}.${name}, but the pages folder has no folder "${name}" with an index.md`;
|
|
6105
|
+
const hint = near ? ` \u2014 did you mean "${near}"?` : folders.size ? ` (its folders: ${[...folders].map((f) => `"${f}"`).join(", ")})` : "";
|
|
6106
|
+
if (required.includes(name)) problems.push(`${text}, and ${owner} requires it${hint}`);
|
|
6107
|
+
else notes.push(`${text}, so it is an empty list${hint}`);
|
|
6108
|
+
}
|
|
6109
|
+
}
|
|
6110
|
+
return { listed, problems, notes };
|
|
6111
|
+
}
|
|
6112
|
+
async function resolveCollections(listed, model, o) {
|
|
6113
|
+
const collections = {};
|
|
6114
|
+
const problems = [], notes = [];
|
|
6115
|
+
for (const name of listed) {
|
|
6116
|
+
const r = await entriesOf(`/${name}/`, model, o);
|
|
6117
|
+
collections[name] = r.entries;
|
|
6118
|
+
problems.push(...r.problems);
|
|
6119
|
+
notes.push(...r.notes);
|
|
6120
|
+
}
|
|
6121
|
+
return { collections, problems, notes };
|
|
6122
|
+
}
|
|
6123
|
+
|
|
5380
6124
|
// packages/dev-server/src/storefront/site.ts
|
|
5381
6125
|
var SITE_SCHEMA_OWNER = "partials/base";
|
|
5382
|
-
var none = (problems = []) => ({ values: {}, navigations: {}, listed: [], problems });
|
|
5383
|
-
async function checkSite(site, discoveryMap, pagePaths = /* @__PURE__ */ new Set()) {
|
|
6126
|
+
var none = (problems = [], collections = []) => ({ values: {}, navigations: {}, listed: [], collections, problems });
|
|
6127
|
+
async function checkSite(site, discoveryMap, pagePaths = /* @__PURE__ */ new Set(), pages = { folders: /* @__PURE__ */ new Set(), leftOut: [] }) {
|
|
5384
6128
|
const problems = [];
|
|
5385
6129
|
const entry = discoveryMap.get(SITE_SCHEMA_OWNER);
|
|
5386
6130
|
if (!entry) {
|
|
@@ -5397,24 +6141,32 @@ async function checkSite(site, discoveryMap, pagePaths = /* @__PURE__ */ new Set
|
|
|
5397
6141
|
}
|
|
5398
6142
|
const navDeclared = declared.properties?.[NAVIGATIONS_KEY];
|
|
5399
6143
|
const navs = checkNavigations(site?.[NAVIGATIONS_KEY], navDeclared, SITE_SCHEMA_OWNER, pagePaths);
|
|
6144
|
+
const cols = checkCollections(
|
|
6145
|
+
declared.properties?.[COLLECTIONS_KEY],
|
|
6146
|
+
SITE_SCHEMA_OWNER,
|
|
6147
|
+
pages.folders,
|
|
6148
|
+
pages.leftOut,
|
|
6149
|
+
site
|
|
6150
|
+
);
|
|
5400
6151
|
if (site === void 0) {
|
|
5401
6152
|
if (schema.required?.includes("site")) problems.push(`storefront.site is required by ${SITE_SCHEMA_OWNER}`);
|
|
5402
|
-
problems.push(...navs.problems);
|
|
5403
|
-
return { ...none(problems), listed: navs.listed };
|
|
6153
|
+
problems.push(...navs.problems, ...cols.problems);
|
|
6154
|
+
return { ...none(problems, cols.listed), listed: navs.listed, notes: cols.notes };
|
|
5404
6155
|
}
|
|
5405
|
-
const
|
|
6156
|
+
const own = (k) => k !== NAVIGATIONS_KEY && k !== COLLECTIONS_KEY;
|
|
6157
|
+
const rest = Object.fromEntries(Object.entries(site).filter(([k]) => own(k)));
|
|
5406
6158
|
const restDeclared = {
|
|
5407
6159
|
...declared,
|
|
5408
|
-
properties: Object.fromEntries(Object.entries(declared.properties ?? {}).filter(([k]) => k
|
|
5409
|
-
...declared.required ? { required: declared.required.filter(
|
|
6160
|
+
properties: Object.fromEntries(Object.entries(declared.properties ?? {}).filter(([k]) => own(k))),
|
|
6161
|
+
...declared.required ? { required: declared.required.filter(own) } : {}
|
|
5410
6162
|
};
|
|
5411
6163
|
const checked = checkJsonKeys(restDeclared, rest, "storefront.site", SITE_SCHEMA_OWNER);
|
|
5412
|
-
problems.push(...checked.problems, ...navs.problems);
|
|
6164
|
+
problems.push(...checked.problems, ...navs.problems, ...cols.problems);
|
|
5413
6165
|
if (declared.required?.includes(NAVIGATIONS_KEY) && !Object.hasOwn(site, NAVIGATIONS_KEY)) {
|
|
5414
6166
|
problems.push(`storefront.site.${NAVIGATIONS_KEY} is required by ${SITE_SCHEMA_OWNER}`);
|
|
5415
6167
|
}
|
|
5416
|
-
if (problems.length) return { ...none(problems), listed: navs.listed };
|
|
5417
|
-
return { values: checked.value, navigations: navs.navigations, listed: navs.listed, problems };
|
|
6168
|
+
if (problems.length) return { ...none(problems, cols.listed), listed: navs.listed, notes: cols.notes };
|
|
6169
|
+
return { values: checked.value, navigations: navs.navigations, listed: navs.listed, collections: cols.listed, problems, notes: cols.notes };
|
|
5418
6170
|
}
|
|
5419
6171
|
|
|
5420
6172
|
// packages/dev-server/src/storefront/sample-data.ts
|
|
@@ -5432,7 +6184,7 @@ function includeTree(layoutId, composition) {
|
|
|
5432
6184
|
}
|
|
5433
6185
|
return [layoutId, ...[...seen].filter((id) => id !== layoutId).sort()];
|
|
5434
6186
|
}
|
|
5435
|
-
async function checkSampleData(layoutId, discoveryMap, composition, layoutDeclared, siteDeclared, pageDeclared, navigationNames = []) {
|
|
6187
|
+
async function checkSampleData(layoutId, discoveryMap, composition, layoutDeclared, siteDeclared, pageDeclared, navigationNames = [], collectionNames = []) {
|
|
5436
6188
|
const problems = [];
|
|
5437
6189
|
const allowed = new Set([...layoutDeclared, ...ASSEMBLY_KEYS].filter((k) => k !== "page" || pageDeclared !== void 0));
|
|
5438
6190
|
const shapeFields = PAGE_SHAPE.fields;
|
|
@@ -5469,10 +6221,11 @@ async function checkSampleData(layoutId, discoveryMap, composition, layoutDeclar
|
|
|
5469
6221
|
for (const [sub, v] of Object.entries(value)) {
|
|
5470
6222
|
if (!siteDeclared.includes(sub)) {
|
|
5471
6223
|
problems.push(`${where} sets "site.${sub}", which ${SITE_SCHEMA_OWNER} does not declare for site, so it would show on every page using ${layoutId}`);
|
|
5472
|
-
} else if (sub === "navigations" && isPlainObject2(v)) {
|
|
6224
|
+
} else if ((sub === "navigations" || sub === "collections") && isPlainObject2(v)) {
|
|
6225
|
+
const names = sub === "navigations" ? navigationNames : collectionNames;
|
|
5473
6226
|
for (const name of Object.keys(v)) {
|
|
5474
|
-
if (!
|
|
5475
|
-
problems.push(`${where} sets "site
|
|
6227
|
+
if (!names.includes(name)) {
|
|
6228
|
+
problems.push(`${where} sets "site.${sub}.${name}", which ${SITE_SCHEMA_OWNER} does not list, so it would show on every page using ${layoutId}`);
|
|
5476
6229
|
}
|
|
5477
6230
|
}
|
|
5478
6231
|
}
|
|
@@ -5484,7 +6237,7 @@ async function checkSampleData(layoutId, discoveryMap, composition, layoutDeclar
|
|
|
5484
6237
|
}
|
|
5485
6238
|
|
|
5486
6239
|
// packages/dev-server/src/storefront/placed.ts
|
|
5487
|
-
var contentMd = new
|
|
6240
|
+
var contentMd = new MarkdownIt3({ html: false, linkify: false });
|
|
5488
6241
|
var kebab = (k) => k.replace(/[A-Z]/g, (c3) => `-${c3.toLowerCase()}`);
|
|
5489
6242
|
async function placementComponents(id, deps) {
|
|
5490
6243
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -5524,8 +6277,8 @@ async function unsafeOutputs(id, at, deps) {
|
|
|
5524
6277
|
for (const componentId of [...templates].sort()) {
|
|
5525
6278
|
const file = deps.discoveryMap.get(componentId)?.hbsPath;
|
|
5526
6279
|
if (!file) continue;
|
|
5527
|
-
const
|
|
5528
|
-
for (const o of unescapedOutputs(
|
|
6280
|
+
const source2 = await readFile18(file, "utf8").catch(() => "");
|
|
6281
|
+
for (const o of unescapedOutputs(source2)) {
|
|
5529
6282
|
if (!o.helper && allowed.has(o.name)) continue;
|
|
5530
6283
|
const what = o.helper ? `a helper's result unescaped ({{{${o.name}}}})` : `"${o.name}" unescaped ({{{${o.name}}}})`;
|
|
5531
6284
|
problems.push({ at, message: `${componentId} outputs ${what} at ${deps.shown(file)}:${o.line}:${o.column}, which only a text/markdown property may be` });
|
|
@@ -5533,7 +6286,7 @@ async function unsafeOutputs(id, at, deps) {
|
|
|
5533
6286
|
}
|
|
5534
6287
|
return problems;
|
|
5535
6288
|
}
|
|
5536
|
-
var
|
|
6289
|
+
var isObject7 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
5537
6290
|
function undeclaredSampleKeys(sample, props, prefix) {
|
|
5538
6291
|
const out = [];
|
|
5539
6292
|
for (const [key, value] of Object.entries(sample)) {
|
|
@@ -5542,7 +6295,7 @@ function undeclaredSampleKeys(sample, props, prefix) {
|
|
|
5542
6295
|
out.push(`${prefix}${key}`);
|
|
5543
6296
|
continue;
|
|
5544
6297
|
}
|
|
5545
|
-
if (
|
|
6298
|
+
if (isObject7(value) && prop.properties) out.push(...undeclaredSampleKeys(value, prop.properties, `${prefix}${key}.`));
|
|
5546
6299
|
}
|
|
5547
6300
|
return out;
|
|
5548
6301
|
}
|
|
@@ -5561,7 +6314,7 @@ async function sampleDataProblems(id, at, deps) {
|
|
|
5561
6314
|
} catch {
|
|
5562
6315
|
continue;
|
|
5563
6316
|
}
|
|
5564
|
-
if (!
|
|
6317
|
+
if (!isObject7(sample)) continue;
|
|
5565
6318
|
for (const key of undeclaredSampleKeys(sample, declared, "")) {
|
|
5566
6319
|
problems.push({ at, message: `the sample data of ${template} (${basename7(jsonPath)}) sets "${key}", which ${owner} does not declare, so it would show wherever ${owner} is placed` });
|
|
5567
6320
|
}
|
|
@@ -5581,6 +6334,11 @@ async function schemaProblems(id, at, deps) {
|
|
|
5581
6334
|
})) {
|
|
5582
6335
|
problems.push({ at, message: `${component} cannot be placed: its schema (${deps.shown(entry.schemaPath)}, ${p.path}): ${p.message}` });
|
|
5583
6336
|
}
|
|
6337
|
+
const rendered = await deps.render({ component }).catch(() => void 0);
|
|
6338
|
+
for (const d of rendered?.meta?.diagnostics ?? []) {
|
|
6339
|
+
if (d.kind !== "SCHEMA_INVALID" || !isHelperNameProblem(d.message)) continue;
|
|
6340
|
+
problems.push({ at, message: `${component} cannot be placed: its schema (${deps.shown(entry.schemaPath)}, ${d.path ?? "#"}): ${d.message}` });
|
|
6341
|
+
}
|
|
5584
6342
|
}
|
|
5585
6343
|
return problems;
|
|
5586
6344
|
}
|
|
@@ -5636,23 +6394,23 @@ var Slugger = class {
|
|
|
5636
6394
|
// packages/dev-server/src/markdown.ts
|
|
5637
6395
|
var INLINE = { embed: "inline" };
|
|
5638
6396
|
function createDocMarkdown(placing) {
|
|
5639
|
-
const
|
|
5640
|
-
|
|
6397
|
+
const md2 = new MarkdownIt4({ html: false, linkify: false });
|
|
6398
|
+
md2.validateLink = (url) => {
|
|
5641
6399
|
const t = url.trim();
|
|
5642
6400
|
return /^(https?|mailto|tel):/i.test(t) || /^[.#/]/.test(t) || t.startsWith("pattern:");
|
|
5643
6401
|
};
|
|
5644
|
-
|
|
6402
|
+
md2.renderer.rules["fence"] = (tokens, idx, _opts, env) => {
|
|
5645
6403
|
const map = env.resolvedFences;
|
|
5646
6404
|
return map?.get(idx) ?? _mdPlainPre(tokens[idx]?.content ?? "");
|
|
5647
6405
|
};
|
|
5648
|
-
const image =
|
|
5649
|
-
|
|
6406
|
+
const image = md2.renderer.rules["image"];
|
|
6407
|
+
md2.renderer.rules["image"] = (tokens, idx, opts, env, self) => tokens[idx]?.meta?.pdImage ?? image(tokens, idx, opts, env, self);
|
|
5650
6408
|
if (placing) {
|
|
5651
|
-
usePlacedTags(
|
|
5652
|
-
|
|
6409
|
+
usePlacedTags(md2);
|
|
6410
|
+
md2.renderer.rules["pd_component"] = (_tokens, idx, _opts, env) => env.resolvedPlaced?.get(idx) ?? `<p class="doc-error">[component not placed: see the page's problems]</p>
|
|
5653
6411
|
`;
|
|
5654
6412
|
}
|
|
5655
|
-
return
|
|
6413
|
+
return md2;
|
|
5656
6414
|
}
|
|
5657
6415
|
var _md = createDocMarkdown(false);
|
|
5658
6416
|
var _mdPlaced = createDocMarkdown(true);
|
|
@@ -5706,10 +6464,10 @@ function _resolvePatternLinks(tokens, discoveryMap, embed) {
|
|
|
5706
6464
|
}
|
|
5707
6465
|
async function renderDoc(body, renderComponent, discoveryMap, embed = INLINE) {
|
|
5708
6466
|
const placing = embed.embed === "frame" ? embed.placed : void 0;
|
|
5709
|
-
const
|
|
5710
|
-
const tokens =
|
|
6467
|
+
const md2 = placing ? _mdPlaced : _md;
|
|
6468
|
+
const tokens = md2.parse(body, { pdLineOffset: placing?.lineOffset ?? 0 });
|
|
5711
6469
|
_resolvePatternLinks(tokens, discoveryMap, embed);
|
|
5712
|
-
if (embed.embed === "frame" && embed.prose) proseImages(
|
|
6470
|
+
if (embed.embed === "frame" && embed.prose) proseImages(md2, tokens, embed.prose);
|
|
5713
6471
|
if (embed.embed === "frame") {
|
|
5714
6472
|
const headings = headingAnchors(tokens, embed.takenIds);
|
|
5715
6473
|
embed.headings?.push(...headings);
|
|
@@ -5730,9 +6488,9 @@ async function renderDoc(body, renderComponent, discoveryMap, embed = INLINE) {
|
|
|
5730
6488
|
`);
|
|
5731
6489
|
}
|
|
5732
6490
|
}
|
|
5733
|
-
return
|
|
6491
|
+
return md2.renderer.render(tokens, md2.options, { resolvedFences, resolvedPlaced });
|
|
5734
6492
|
}
|
|
5735
|
-
function proseImages(
|
|
6493
|
+
function proseImages(md2, tokens, prose) {
|
|
5736
6494
|
let eager = prose.slot?.eager ?? 0;
|
|
5737
6495
|
for (const block2 of tokens) {
|
|
5738
6496
|
if (block2.type !== "inline" || !block2.children) continue;
|
|
@@ -5745,22 +6503,22 @@ function proseImages(md, tokens, prose) {
|
|
|
5745
6503
|
if (child.type !== "image") continue;
|
|
5746
6504
|
const src = child.attrGet("src") ?? "";
|
|
5747
6505
|
const problem = (message) => prose.problems.push({ line, message: `the image ${src}${message}` });
|
|
5748
|
-
const alt =
|
|
6506
|
+
const alt = md2.renderer.renderInlineAsText(child.children ?? [], md2.options, {});
|
|
5749
6507
|
if (alt.trim() === "") {
|
|
5750
6508
|
problem(` has no alt text: write what it shows between the brackets; a decorative image goes in through a component, with alt=""`);
|
|
5751
6509
|
continue;
|
|
5752
6510
|
}
|
|
5753
6511
|
if (!src.startsWith("/assets/")) continue;
|
|
5754
|
-
const
|
|
5755
|
-
if (!
|
|
6512
|
+
const source2 = prose.lookup(src);
|
|
6513
|
+
if (!source2) {
|
|
5756
6514
|
problem(` is not an image in the assets`);
|
|
5757
6515
|
continue;
|
|
5758
6516
|
}
|
|
5759
|
-
if (
|
|
5760
|
-
problem(`: ${
|
|
6517
|
+
if (source2.kind === "unavailable") {
|
|
6518
|
+
problem(`: ${source2.reason}`);
|
|
5761
6519
|
continue;
|
|
5762
6520
|
}
|
|
5763
|
-
if (
|
|
6521
|
+
if (source2.kind === "variants" && !prose.slot) {
|
|
5764
6522
|
problem(` needs its layout's slot for prose images: ${prose.layoutId} ` + (prose.slotProblem ? `has an invalid x-pd-prose-images (${prose.slotProblem})` : `declares no x-pd-prose-images`));
|
|
5765
6523
|
continue;
|
|
5766
6524
|
}
|
|
@@ -5768,7 +6526,7 @@ function proseImages(md, tokens, prose) {
|
|
|
5768
6526
|
const atOnce = eager > 0;
|
|
5769
6527
|
if (atOnce) eager--;
|
|
5770
6528
|
const slot = prose.slot;
|
|
5771
|
-
child.meta = { pdImage: imageTag(
|
|
6529
|
+
child.meta = { pdImage: imageTag(source2, {
|
|
5772
6530
|
widths: slot?.widths ?? [],
|
|
5773
6531
|
sizes: slot ? atOnce ? slot.sizes : `auto, ${slot.sizes}` : "",
|
|
5774
6532
|
alt,
|
|
@@ -5932,7 +6690,7 @@ import postcss4 from "postcss";
|
|
|
5932
6690
|
import * as sass3 from "sass";
|
|
5933
6691
|
import { SourceMapConsumer as SourceMapConsumer2 } from "source-map";
|
|
5934
6692
|
import { parse as parseHtml2 } from "node-html-parser";
|
|
5935
|
-
async function recomputeComponentCss(entryFile, compId, stylesDir, componentIds, inBase = () => false) {
|
|
6693
|
+
async function recomputeComponentCss(entryFile, compId, stylesDir, componentIds, inBase = () => false, out) {
|
|
5936
6694
|
try {
|
|
5937
6695
|
const compiled = sass3.compile(entryFile, { sourceMap: true, ...sassPackageOptions(entryFile) });
|
|
5938
6696
|
const rawMap = compiled.sourceMap;
|
|
@@ -5940,16 +6698,22 @@ async function recomputeComponentCss(entryFile, compId, stylesDir, componentIds,
|
|
|
5940
6698
|
const consumer = new SourceMapConsumer2(rawMap);
|
|
5941
6699
|
const root = postcss4.parse(compiled.css);
|
|
5942
6700
|
const bucket = /* @__PURE__ */ new Set();
|
|
6701
|
+
const base2 = /* @__PURE__ */ new Set();
|
|
5943
6702
|
root.each((node) => {
|
|
5944
6703
|
const pos = node.source?.start;
|
|
5945
6704
|
if (!pos) return;
|
|
5946
6705
|
const original = consumer.originalPositionFor({ line: pos.line, column: pos.column - 1 });
|
|
5947
6706
|
if (!original.source) return;
|
|
5948
6707
|
const originPath = original.source.startsWith("file://") ? fileURLToPath4(original.source) : resolve8(dirname8(entryFile), original.source);
|
|
5949
|
-
if (inBase(originPath))
|
|
6708
|
+
if (inBase(originPath)) {
|
|
6709
|
+
addLastOccurrence(base2, cssNodeText(node));
|
|
6710
|
+
return;
|
|
6711
|
+
}
|
|
5950
6712
|
const originComp = resolveScssToComponent(originPath, stylesDir, componentIds);
|
|
5951
6713
|
if (originComp === compId) addLastOccurrence(bucket, cssNodeText(node));
|
|
6714
|
+
else if (originComp === null) addLastOccurrence(base2, cssNodeText(node));
|
|
5952
6715
|
});
|
|
6716
|
+
if (out) out.baseRules = joinCssChunks(base2);
|
|
5953
6717
|
return joinCssChunks(bucket);
|
|
5954
6718
|
} catch {
|
|
5955
6719
|
return null;
|
|
@@ -6090,7 +6854,7 @@ async function buildComponentDetail(componentId, patternsDir, renderFn, tracker,
|
|
|
6090
6854
|
} catch {
|
|
6091
6855
|
}
|
|
6092
6856
|
}
|
|
6093
|
-
const
|
|
6857
|
+
const source2 = {
|
|
6094
6858
|
hbs,
|
|
6095
6859
|
hbsHighlighted,
|
|
6096
6860
|
...jsonStr !== void 0 ? { json: jsonStr, jsonHighlighted } : {},
|
|
@@ -6131,7 +6895,7 @@ async function buildComponentDetail(componentId, patternsDir, renderFn, tracker,
|
|
|
6131
6895
|
layout,
|
|
6132
6896
|
...deprecatedUse ? { deprecatedUse } : {},
|
|
6133
6897
|
variants,
|
|
6134
|
-
source,
|
|
6898
|
+
source: source2,
|
|
6135
6899
|
doc,
|
|
6136
6900
|
...requireScripts ? { requireScripts } : {},
|
|
6137
6901
|
...requireAssets ? { requireAssets } : {},
|
|
@@ -6142,13 +6906,13 @@ async function buildComponentDetail(componentId, patternsDir, renderFn, tracker,
|
|
|
6142
6906
|
}
|
|
6143
6907
|
|
|
6144
6908
|
// packages/dev-server/src/overview.ts
|
|
6145
|
-
import { join as
|
|
6909
|
+
import { join as join24 } from "node:path";
|
|
6146
6910
|
async function buildFolderOverview(folderPath, patternsDir, renderFn, discoveryMap) {
|
|
6147
6911
|
const category = folderPath.split("/")[0];
|
|
6148
6912
|
let folderLayout = FULL_CATEGORIES.has(stripOrderPrefixes(category)) ? "full" : "centered";
|
|
6149
6913
|
let doc;
|
|
6150
6914
|
try {
|
|
6151
|
-
const rawIndex = await readFolderFile(patternsDir,
|
|
6915
|
+
const rawIndex = await readFolderFile(patternsDir, join24(folderPath, "index.md"));
|
|
6152
6916
|
if (rawIndex === null) throw new Error("no index.md");
|
|
6153
6917
|
const m = rawIndex.match(/^preview:\s*(centered|full)$/m);
|
|
6154
6918
|
if (m) folderLayout = m[1];
|
|
@@ -6185,9 +6949,9 @@ async function collectPublicLeaves(folderPath, patternsDir, discoveryMap) {
|
|
|
6185
6949
|
}
|
|
6186
6950
|
|
|
6187
6951
|
// packages/dev-server/src/content-hash.ts
|
|
6188
|
-
import { createHash as
|
|
6952
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
6189
6953
|
function contentHash(bytes) {
|
|
6190
|
-
return
|
|
6954
|
+
return createHash6("sha256").update(bytes).digest("hex").slice(0, 10);
|
|
6191
6955
|
}
|
|
6192
6956
|
|
|
6193
6957
|
// packages/dev-server/src/scripts.ts
|
|
@@ -6357,7 +7121,11 @@ async function stampSrcset(value, hashOf) {
|
|
|
6357
7121
|
}));
|
|
6358
7122
|
return entries.join(",");
|
|
6359
7123
|
}
|
|
6360
|
-
async function
|
|
7124
|
+
async function stampAbsolute(path, hashOf, base2) {
|
|
7125
|
+
if (base2 === "/" || !path.startsWith(base2)) return stamp(path, hashOf);
|
|
7126
|
+
return `${base2}${(await stamp(`/${path.slice(base2.length)}`, hashOf)).slice(1)}`;
|
|
7127
|
+
}
|
|
7128
|
+
async function stampTag(name, attrs, hashOf, base2) {
|
|
6361
7129
|
const isPreview = name.toLowerCase() === "meta" && /\s(?:property="og:image"|name="twitter:image")/.test(attrs);
|
|
6362
7130
|
return replaceAsync(attrs, ATTRIBUTE, async (m) => {
|
|
6363
7131
|
const [whole, lead, attr2, eq, quoted] = m;
|
|
@@ -6370,22 +7138,22 @@ async function stampTag(name, attrs, hashOf) {
|
|
|
6370
7138
|
else if (key === "srcset") stamped = await stampSrcset(value, hashOf);
|
|
6371
7139
|
else if (key === "content" && isPreview) {
|
|
6372
7140
|
const at = /^(https?:\/\/[^/"']+)(.*)$/.exec(value);
|
|
6373
|
-
if (at) stamped = `${at[1]}${await
|
|
7141
|
+
if (at) stamped = `${at[1]}${await stampAbsolute(at[2], hashOf, base2)}`;
|
|
6374
7142
|
}
|
|
6375
7143
|
return `${lead}${attr2}${eq}${q}${stamped}${q}`;
|
|
6376
7144
|
});
|
|
6377
7145
|
}
|
|
6378
|
-
async function stampHtml(html, hashOf) {
|
|
7146
|
+
async function stampHtml(html, hashOf, base2 = "/") {
|
|
6379
7147
|
const parts = [];
|
|
6380
7148
|
let last = 0;
|
|
6381
7149
|
for (const tag of startTags(html)) {
|
|
6382
7150
|
parts.push(html.slice(last, tag.start));
|
|
6383
7151
|
const attrsAt = tag.start + 1 + tag.name.length;
|
|
6384
|
-
parts.push(`<${tag.name}${await stampTag(tag.name, tag.attrs, hashOf)}${html.slice(attrsAt + tag.attrs.length, tag.end)}`);
|
|
7152
|
+
parts.push(`<${tag.name}${await stampTag(tag.name, tag.attrs, hashOf, base2)}${html.slice(attrsAt + tag.attrs.length, tag.end)}`);
|
|
6385
7153
|
last = tag.end;
|
|
6386
7154
|
if (!tag.content) continue;
|
|
6387
7155
|
const content = html.slice(tag.content.start, tag.content.end);
|
|
6388
|
-
parts.push(tag.name.toLowerCase() === "script" && /\stype=["']?application\/ld\+json/i.test(tag.attrs) ? await replaceAsync(content, /("image":"https?:\/\/[^/"]+)([^"]*)"/g, async (j) => `${j[1]}${await
|
|
7156
|
+
parts.push(tag.name.toLowerCase() === "script" && /\stype=["']?application\/ld\+json/i.test(tag.attrs) ? await replaceAsync(content, /("image":"https?:\/\/[^/"]+)([^"]*)"/g, async (j) => `${j[1]}${await stampAbsolute(j[2], hashOf, base2)}"`) : content);
|
|
6389
7157
|
last = tag.content.end;
|
|
6390
7158
|
}
|
|
6391
7159
|
parts.push(html.slice(last));
|
|
@@ -6402,7 +7170,7 @@ async function stampCss(css, hashOf) {
|
|
|
6402
7170
|
|
|
6403
7171
|
// packages/dev-server/src/assets.ts
|
|
6404
7172
|
import { existsSync as existsSync5 } from "node:fs";
|
|
6405
|
-
import { createRequire as
|
|
7173
|
+
import { createRequire as createRequire5 } from "node:module";
|
|
6406
7174
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
6407
7175
|
function resolveDevUiDist(moduleUrl, exists = existsSync5) {
|
|
6408
7176
|
const packaged = fileURLToPath5(new URL("./dev-ui/", moduleUrl));
|
|
@@ -6410,7 +7178,7 @@ function resolveDevUiDist(moduleUrl, exists = existsSync5) {
|
|
|
6410
7178
|
return fileURLToPath5(new URL("../../../apps/dev-ui/dist/", moduleUrl));
|
|
6411
7179
|
}
|
|
6412
7180
|
function resolveAxeCore(moduleUrl) {
|
|
6413
|
-
return
|
|
7181
|
+
return createRequire5(moduleUrl).resolve("axe-core/axe.min.js");
|
|
6414
7182
|
}
|
|
6415
7183
|
|
|
6416
7184
|
// packages/dev-server/src/storefront/page-image.ts
|
|
@@ -6471,6 +7239,7 @@ function jsonLdScript(p) {
|
|
|
6471
7239
|
name: p.title,
|
|
6472
7240
|
...p.description ? { description: p.description } : {},
|
|
6473
7241
|
isPartOf: { "@id": site },
|
|
7242
|
+
...p.published ? { datePublished: p.published } : {},
|
|
6474
7243
|
...p.modified ? { dateModified: p.modified } : {},
|
|
6475
7244
|
...p.breadcrumbs.length ? {
|
|
6476
7245
|
breadcrumb: {
|
|
@@ -6502,9 +7271,9 @@ function writtenByPatternDump(tag, attrs) {
|
|
|
6502
7271
|
if (tag === "script" && attr(attrs, "type") === "application/ld+json") return `<script type="application/ld+json">`;
|
|
6503
7272
|
return void 0;
|
|
6504
7273
|
}
|
|
6505
|
-
function checkHeadMarkup(html) {
|
|
7274
|
+
function checkHeadMarkup(html, source2 = { label: HEAD_PARTIAL, subject: "a head partial" }) {
|
|
6506
7275
|
const problems = [];
|
|
6507
|
-
const say = (what) => problems.push(`${
|
|
7276
|
+
const say = (what) => problems.push(`${source2.label}: ${what.replaceAll("a head partial", source2.subject)}`);
|
|
6508
7277
|
let rest = html.replace(/<!--[\s\S]*?-->/g, "");
|
|
6509
7278
|
rest = rest.replace(/<(script|style|noscript|title)\b([^>]*)>[\s\S]*?<\/\1\s*>/gi, (_m, rawTag, attrs) => {
|
|
6510
7279
|
const tag = rawTag.toLowerCase();
|
|
@@ -6578,13 +7347,14 @@ var LIVE_NOTICE = `function(t){var n=document.querySelector("[data-pd-live-notic
|
|
|
6578
7347
|
function exampleHeightCheck() {
|
|
6579
7348
|
return `(function(){var notice=(${LIVE_NOTICE});document.querySelectorAll("figure.doc-example iframe[height]").forEach(function(f){var check=function(){var d=f.contentDocument;if(!d||d.URL==="about:blank")return;d.fonts.ready.then(function(){var declared=Number(f.getAttribute("height")),root=d.documentElement.getBoundingClientRect().height,id=f.title,outcome="fits";if(root===f.clientHeight)outcome="can't tell (the content is as tall as its frame)";else if(root<declared-1){outcome="too tall";var t=id+": its live example is declared "+declared+"px tall (x-pd-example-height), but its content is "+Math.round(root)+"px. A frame is never fitted shorter than it starts, so it stays too tall: declare at most "+Math.floor(root)+".";console.warn(t);notice(t);}console.debug("[pd] example height of "+id+": "+outcome);});};f.addEventListener("load",check);if(f.contentDocument&&f.contentDocument.readyState==="complete")check();});})();`;
|
|
6580
7349
|
}
|
|
6581
|
-
function
|
|
7350
|
+
function document2(head, body, live, lang = "en", base2) {
|
|
6582
7351
|
return `<!DOCTYPE html>
|
|
6583
7352
|
<html lang="${escapeAttr(lang)}">
|
|
6584
7353
|
<head>
|
|
6585
7354
|
<meta charset="UTF-8">
|
|
6586
7355
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6587
|
-
${
|
|
7356
|
+
${base2 !== void 0 ? `<meta name="pd-base-path" content="${escapeAttr(base2)}">
|
|
7357
|
+
` : ""}${head}
|
|
6588
7358
|
</head>
|
|
6589
7359
|
<body>
|
|
6590
7360
|
${body}
|
|
@@ -6641,7 +7411,7 @@ var BUILT_IN_NOT_FOUND_DOCUMENT = `<main>
|
|
|
6641
7411
|
</main>`;
|
|
6642
7412
|
function errorDocument(route, problems, live, siteWide = []) {
|
|
6643
7413
|
const items = problems.map((p) => `<li${siteWide.includes(p) ? " data-pd-site" : ""}>${escapeHtml(p)}</li>`).join("\n");
|
|
6644
|
-
return
|
|
7414
|
+
return document2(
|
|
6645
7415
|
`<title>Problems \u2014 ${escapeHtml(route)}</title>`,
|
|
6646
7416
|
`<main>
|
|
6647
7417
|
<h1>The page ${escapeHtml(route)} cannot be built</h1>
|
|
@@ -6669,25 +7439,25 @@ async function assemblePage(route, model, a) {
|
|
|
6669
7439
|
const notFound = () => ({ status: 404, html: errorDocument(route, [`there is no page at ${route}`], a.live), problems: [`there is no page at ${route}`], notes: [] });
|
|
6670
7440
|
const isNotFound = route === PD_ROUTES.notFound;
|
|
6671
7441
|
let page = isNotFound ? model.notFound : model.files.get(route);
|
|
6672
|
-
let
|
|
7442
|
+
let source2;
|
|
6673
7443
|
if (isNotFound && !page) {
|
|
6674
7444
|
const layoutId = `layouts/${DEFAULT_LAYOUT}`;
|
|
6675
7445
|
if (!a.discoveryMap.has(layoutId)) {
|
|
6676
7446
|
return {
|
|
6677
7447
|
status: 404,
|
|
6678
|
-
html:
|
|
7448
|
+
html: document2(`<title>Page not found</title>`, BUILT_IN_NOT_FOUND_DOCUMENT, a.live, a.lang, a.basePath ?? "/"),
|
|
6679
7449
|
problems: [],
|
|
6680
7450
|
notes: [`there is no ${NOT_FOUND_FILE} and no ${layoutId}, so the 404 page is a plain one of Pattern Dump's`]
|
|
6681
7451
|
};
|
|
6682
7452
|
}
|
|
6683
7453
|
const declared = (await readSchema(a.discoveryMap.get(layoutId)?.schemaPath)).schema?.properties ?? {};
|
|
6684
|
-
|
|
6685
|
-
page = { route, file: "", path: `${NOT_FOUND_FILE} (built in)`, frontmatter: readFrontmatterStrict(
|
|
7454
|
+
source2 = builtInNotFound(Object.hasOwn(declared, "title"));
|
|
7455
|
+
page = { route, file: "", path: `${NOT_FOUND_FILE} (built in)`, frontmatter: readFrontmatterStrict(source2) };
|
|
6686
7456
|
}
|
|
6687
7457
|
if (!page) return notFound();
|
|
6688
|
-
if (
|
|
7458
|
+
if (source2 === void 0) {
|
|
6689
7459
|
try {
|
|
6690
|
-
|
|
7460
|
+
source2 = await readFile23(page.file, "utf8");
|
|
6691
7461
|
} catch (e) {
|
|
6692
7462
|
if (e.code === "ENOENT") return notFound();
|
|
6693
7463
|
throw e;
|
|
@@ -6700,7 +7470,10 @@ async function assemblePage(route, model, a) {
|
|
|
6700
7470
|
const line = page.frontmatter.lines["redirectFrom"];
|
|
6701
7471
|
problems.push(`${page.path}${line !== void 0 ? `:${line}` : ""}: the 404 page can't have redirectFrom: it is served for every address that has no page`);
|
|
6702
7472
|
}
|
|
6703
|
-
const site = await checkSite(a.site, a.discoveryMap, new Set([...model.files.values()].map((f) => f.path))
|
|
7473
|
+
const site = await checkSite(a.site, a.discoveryMap, new Set([...model.files.values()].map((f) => f.path)), {
|
|
7474
|
+
folders: new Set([...model.folders.keys()].filter((r) => r !== "/").map((r) => r.slice(1, -1))),
|
|
7475
|
+
leftOut: model.drafts
|
|
7476
|
+
});
|
|
6704
7477
|
siteWide = site.problems;
|
|
6705
7478
|
problems.push(...site.problems);
|
|
6706
7479
|
const imageLine = page.frontmatter.lines["image"];
|
|
@@ -6716,20 +7489,22 @@ async function assemblePage(route, model, a) {
|
|
|
6716
7489
|
const layoutSchema = (await readSchema(a.discoveryMap.get(checked.layoutId)?.schemaPath)).schema;
|
|
6717
7490
|
const proseSlot = readProseSlot(layoutSchema);
|
|
6718
7491
|
const imageLookup = a.images ? await a.images() : void 0;
|
|
7492
|
+
const placedIds = /* @__PURE__ */ new Set();
|
|
6719
7493
|
const renderBody = async (takenIds = []) => {
|
|
6720
7494
|
const prose = imageLookup ? {
|
|
6721
7495
|
layoutId: checked.layoutId,
|
|
6722
7496
|
lookup: imageLookup,
|
|
6723
|
-
lineOffset: frontmatterBodyOffset(
|
|
7497
|
+
lineOffset: frontmatterBodyOffset(source2),
|
|
6724
7498
|
problems: [],
|
|
6725
7499
|
...proseSlot.slot ? { slot: proseSlot.slot } : {},
|
|
6726
7500
|
...proseSlot.problem ? { slotProblem: proseSlot.problem } : {}
|
|
6727
7501
|
} : void 0;
|
|
6728
7502
|
const docProblems = [];
|
|
7503
|
+
placedIds.clear();
|
|
6729
7504
|
const placedProblems = [];
|
|
6730
7505
|
const headings = [];
|
|
6731
7506
|
const body = await renderDoc(
|
|
6732
|
-
frontmatterBody(
|
|
7507
|
+
frontmatterBody(source2),
|
|
6733
7508
|
(id, variant) => a.render({ component: id, variant }).then((r) => r.html),
|
|
6734
7509
|
a.discoveryMap,
|
|
6735
7510
|
{
|
|
@@ -6741,13 +7516,17 @@ async function assemblePage(route, model, a) {
|
|
|
6741
7516
|
...prose ? { prose } : {},
|
|
6742
7517
|
// Components placed with values (pd- tags): checked against their schemas, rendered into the page.
|
|
6743
7518
|
placed: {
|
|
6744
|
-
lineOffset: frontmatterBodyOffset(
|
|
7519
|
+
lineOffset: frontmatterBodyOffset(source2),
|
|
6745
7520
|
problems: placedProblems,
|
|
6746
7521
|
deps: {
|
|
6747
7522
|
discoveryMap: a.discoveryMap,
|
|
6748
7523
|
handleMap: a.handleMap,
|
|
6749
7524
|
composition: a.composition,
|
|
6750
|
-
|
|
7525
|
+
// Each component rendered for a placement: the page loads its behaviour scripts (page-head.ts).
|
|
7526
|
+
render: (input) => {
|
|
7527
|
+
placedIds.add(input.component.split("~")[0]);
|
|
7528
|
+
return a.render(input);
|
|
7529
|
+
},
|
|
6751
7530
|
schemaOf: async (id) => (await readSchema(a.discoveryMap.get(id)?.schemaPath)).schema,
|
|
6752
7531
|
shown: a.shown
|
|
6753
7532
|
}
|
|
@@ -6768,37 +7547,65 @@ async function assemblePage(route, model, a) {
|
|
|
6768
7547
|
const notes = [];
|
|
6769
7548
|
let dates = {};
|
|
6770
7549
|
let modified = null;
|
|
6771
|
-
|
|
7550
|
+
let published = checked.build.date ?? null;
|
|
7551
|
+
const mode = a.live ? "dev" : "build";
|
|
7552
|
+
const declares = (k) => pageDeclaration.declared?.includes(k) ?? false;
|
|
7553
|
+
const showsDates = declares("created") || declares("modified");
|
|
6772
7554
|
if (a.dates && !isNotFound && (showsDates || a.siteUrl !== void 0)) {
|
|
6773
|
-
const dated = await a.dates.of(page.path,
|
|
7555
|
+
const dated = await a.dates.of(page.path, mode);
|
|
6774
7556
|
if (showsDates) {
|
|
6775
7557
|
dates = dated.dates;
|
|
6776
7558
|
notes.push(...dated.notes);
|
|
6777
7559
|
problems.push(...dated.problems);
|
|
6778
7560
|
}
|
|
6779
7561
|
if (dated.shallow) notes.push(SHALLOW_SEO_NOTE);
|
|
6780
|
-
else
|
|
6781
|
-
|
|
7562
|
+
else {
|
|
7563
|
+
modified = dated.dates.modified;
|
|
7564
|
+
published ??= dated.dates.created;
|
|
7565
|
+
}
|
|
7566
|
+
}
|
|
7567
|
+
const listing = {};
|
|
7568
|
+
if (declares("children")) {
|
|
7569
|
+
const listed = isNotFound ? { entries: [], problems: [], notes: [] } : await entriesOf(route, model, { dates: a.dates, mode });
|
|
7570
|
+
listing.children = listed.entries;
|
|
7571
|
+
problems.push(...listed.problems);
|
|
7572
|
+
notes.push(...listed.notes);
|
|
7573
|
+
}
|
|
7574
|
+
if (declares("date")) {
|
|
7575
|
+
const dated = isNotFound ? { date: null, problems: [], notes: [] } : await dateOf(route, model, a.dates, mode);
|
|
7576
|
+
listing.date = dated.date;
|
|
7577
|
+
problems.push(...dated.problems);
|
|
7578
|
+
notes.push(...dated.notes);
|
|
7579
|
+
}
|
|
7580
|
+
if (declares("tags")) listing.tags = model.entries.get(route)?.tags ?? [];
|
|
7581
|
+
const collections = site.collections.length ? await resolveCollections(site.collections, model, { dates: a.dates, mode }) : void 0;
|
|
7582
|
+
if (collections) {
|
|
7583
|
+
problems.push(...collections.problems);
|
|
7584
|
+
notes.push(...collections.notes);
|
|
7585
|
+
}
|
|
7586
|
+
notes.push(...site.notes ?? []);
|
|
6782
7587
|
if (a.discoveryMap.has(checked.layoutId)) {
|
|
6783
|
-
problems.push(...await checkSampleData(checked.layoutId, a.discoveryMap, a.composition, layoutDeclared, siteDeclared, pageDeclaration.declared, site.listed));
|
|
7588
|
+
problems.push(...await checkSampleData(checked.layoutId, a.discoveryMap, a.composition, layoutDeclared, siteDeclared, pageDeclaration.declared, site.listed, site.collections));
|
|
6784
7589
|
}
|
|
6785
7590
|
const hasHead = a.discoveryMap.has(HEAD_PARTIAL);
|
|
6786
7591
|
if (hasHead) {
|
|
6787
|
-
const headSample = await checkSampleData(HEAD_PARTIAL, a.discoveryMap, a.composition, [], siteDeclared, void 0, site.listed);
|
|
7592
|
+
const headSample = await checkSampleData(HEAD_PARTIAL, a.discoveryMap, a.composition, [], siteDeclared, void 0, site.listed, site.collections);
|
|
6788
7593
|
siteWide = [...siteWide, ...headSample];
|
|
6789
7594
|
problems.push(...headSample);
|
|
6790
7595
|
}
|
|
6791
7596
|
if (problems.length) return fail(problems);
|
|
7597
|
+
if (a.live) notes.push(...model.drafts.map((d) => `${d} is a draft: pd build leaves it out`));
|
|
6792
7598
|
const siteData = {
|
|
6793
7599
|
...withDeclaredAbsentAsNull(site.values, siteDeclared),
|
|
6794
|
-
...site.listed.length ? { navigations: resolveNavigations(site.navigations, site.listed, model, route) } : {}
|
|
7600
|
+
...site.listed.length ? { navigations: resolveNavigations(site.navigations, site.listed, model, route) } : {},
|
|
7601
|
+
...collections ? { collections: collections.collections } : {}
|
|
6795
7602
|
};
|
|
6796
7603
|
const dataFor = (body, headings) => ({
|
|
6797
7604
|
...withDeclaredAbsentAsNull(checked.values, layoutDeclared, ["body", "nav", "site", "page"]),
|
|
6798
7605
|
body,
|
|
6799
7606
|
nav: markCurrent(model.nav, route),
|
|
6800
7607
|
site: siteData,
|
|
6801
|
-
...pageDeclaration.declared ? { page: pageValue(pageDeclaration.declared, { headings, ...positionOf(route, model), ...dates }) } : {}
|
|
7608
|
+
...pageDeclaration.declared ? { page: pageValue(pageDeclaration.declared, { headings, ...positionOf(route, model), ...dates, ...listing }) } : {}
|
|
6802
7609
|
});
|
|
6803
7610
|
const renderLayout = async (body, headings) => {
|
|
6804
7611
|
try {
|
|
@@ -6848,6 +7655,30 @@ async function assemblePage(route, model, a) {
|
|
|
6848
7655
|
return fail(markup);
|
|
6849
7656
|
}
|
|
6850
7657
|
}
|
|
7658
|
+
let siteHead = "";
|
|
7659
|
+
if (a.siteHead) {
|
|
7660
|
+
const label = `storefront.head (${a.siteHead.name})`;
|
|
7661
|
+
let source3;
|
|
7662
|
+
try {
|
|
7663
|
+
source3 = await readFile23(a.siteHead.path, "utf8");
|
|
7664
|
+
} catch {
|
|
7665
|
+
const p = `${label}: there is no such file`;
|
|
7666
|
+
siteWide = [...siteWide, p];
|
|
7667
|
+
return fail([p]);
|
|
7668
|
+
}
|
|
7669
|
+
try {
|
|
7670
|
+
siteHead = renderPlainTemplate(source3, { site: siteData }).trim();
|
|
7671
|
+
} catch (e) {
|
|
7672
|
+
const p = `${label}: ${e instanceof Error ? e.message.split("\n")[0] : String(e)}`;
|
|
7673
|
+
siteWide = [...siteWide, p];
|
|
7674
|
+
return fail([p]);
|
|
7675
|
+
}
|
|
7676
|
+
const markup = checkHeadMarkup(siteHead, { label, subject: "the site's head" });
|
|
7677
|
+
if (markup.length) {
|
|
7678
|
+
siteWide = [...siteWide, ...markup];
|
|
7679
|
+
return fail(markup);
|
|
7680
|
+
}
|
|
7681
|
+
}
|
|
6851
7682
|
const title = typeof checked.values["title"] === "string" ? checked.values["title"] : isNotFound ? "Page not found" : route;
|
|
6852
7683
|
const siteTitle = typeof site.values["title"] === "string" ? site.values["title"] : void 0;
|
|
6853
7684
|
const description = typeof checked.values["description"] === "string" ? checked.values["description"] : void 0;
|
|
@@ -6864,9 +7695,10 @@ async function assemblePage(route, model, a) {
|
|
|
6864
7695
|
image: imageCheck?.ok ? imageCheck.image : void 0,
|
|
6865
7696
|
index: !isNotFound && a.siteIndex !== false && checked.build.index !== false
|
|
6866
7697
|
}),
|
|
6867
|
-
...a.siteUrl !== void 0 && !isNotFound ? [jsonLdScript({ siteUrl: a.siteUrl, siteTitle, route, title, description, breadcrumbs: positionOf(route, model).breadcrumbs, modified })] : [],
|
|
7698
|
+
...a.siteUrl !== void 0 && !isNotFound ? [jsonLdScript({ siteUrl: a.siteUrl, siteTitle, route, title, description, breadcrumbs: positionOf(route, model).breadcrumbs, modified, published })] : [],
|
|
6868
7699
|
...headExtra ? [headExtra] : [],
|
|
6869
|
-
|
|
7700
|
+
...siteHead ? [siteHead] : [],
|
|
7701
|
+
await a.head(checked.layoutId, html, [...placedIds])
|
|
6870
7702
|
].join("\n");
|
|
6871
7703
|
if (isNotFound) {
|
|
6872
7704
|
const relative19 = relativeUrls(`${head}
|
|
@@ -6903,20 +7735,33 @@ ${html}`);
|
|
|
6903
7735
|
const self = isNotFound ? PD_ROUTES.notFound : route;
|
|
6904
7736
|
const routes = /* @__PURE__ */ new Set([...model.files.keys(), ...[...model.files.values()].flatMap(redirectsOf), PD_ROUTES.notFound]);
|
|
6905
7737
|
routes.add(self);
|
|
6906
|
-
const links = checkLinks({
|
|
7738
|
+
const links = checkLinks({
|
|
7739
|
+
built: routes,
|
|
7740
|
+
documents: /* @__PURE__ */ new Map([[self, html]]),
|
|
7741
|
+
pages: [self],
|
|
7742
|
+
pagesOnly: true,
|
|
7743
|
+
...a.basePath !== void 0 ? { pathHint: a.basePath } : {}
|
|
7744
|
+
});
|
|
6907
7745
|
notes.push(...links.problems.map((p) => `warning: ${page.path}${p.slice(self.length)}`));
|
|
7746
|
+
notes.push(...checkPageBasics(html).map((p) => `warning: ${page.path}: ${p}`));
|
|
6908
7747
|
}
|
|
6909
|
-
return { status: isNotFound ? 404 : 200, html:
|
|
6910
|
-
${fitterScript()}`, a.live, a.lang), problems: [], notes };
|
|
7748
|
+
return { status: isNotFound ? 404 : 200, html: document2(head, `${html}
|
|
7749
|
+
${fitterScript()}`, a.live, a.lang, a.basePath ?? "/"), problems: [], notes };
|
|
6911
7750
|
}
|
|
6912
7751
|
|
|
6913
7752
|
// packages/dev-server/src/storefront/page-head.ts
|
|
6914
|
-
async function pageStylesheets(cfg, state, discovery, layoutId, html) {
|
|
6915
|
-
const { discoveryMap } = discovery;
|
|
7753
|
+
async function pageStylesheets(cfg, state, discovery, layoutId, html, placed = []) {
|
|
7754
|
+
const { discoveryMap, handleMap } = discovery;
|
|
7755
|
+
const requireScripts = [];
|
|
7756
|
+
for (const id of [layoutId, ...placed]) {
|
|
7757
|
+
for (const s of (await aggregateRequires(id, cfg.patternDirs, handleMap, discoveryMap)).scripts) {
|
|
7758
|
+
if (!requireScripts.includes(s)) requireScripts.push(s);
|
|
7759
|
+
}
|
|
7760
|
+
}
|
|
6916
7761
|
const assets = await resolveDocumentAssets(cfg, state, {
|
|
6917
7762
|
componentId: layoutId,
|
|
6918
7763
|
html,
|
|
6919
|
-
requireScripts
|
|
7764
|
+
requireScripts,
|
|
6920
7765
|
requireAssets: void 0,
|
|
6921
7766
|
compositionRegistry: await compositionFor(state, discovery, cfg.patternDirs),
|
|
6922
7767
|
discoveryMap
|
|
@@ -6929,7 +7774,8 @@ async function pageStylesheets(cfg, state, discovery, layoutId, html) {
|
|
|
6929
7774
|
themeSwitch: cfg.themeSwitch,
|
|
6930
7775
|
baseCssVersion: state.baseCssVersion
|
|
6931
7776
|
});
|
|
6932
|
-
|
|
7777
|
+
const scripts = assets.scripts.map((src) => src.includes("://") ? `<script type="module" src="${escapeAttr(src)}"></script>` : `<script type="module" src="${escapeAttr(scriptUrl(src, assets.scriptVersions?.get(src) ?? void 0))}"></script>`);
|
|
7778
|
+
return [layers.base, layers.assets, layers.themes, layers.closure, ...scripts].filter(Boolean).join("\n");
|
|
6933
7779
|
}
|
|
6934
7780
|
|
|
6935
7781
|
// packages/dev-server/src/routes.ts
|
|
@@ -6943,9 +7789,31 @@ async function handleRequest(req, res, ctx) {
|
|
|
6943
7789
|
return;
|
|
6944
7790
|
}
|
|
6945
7791
|
const host = (req.headers.host ?? "").replace(/:\d+$/, "").toLowerCase();
|
|
6946
|
-
if (cfg.storefront && host === cfg.storefront.host)
|
|
7792
|
+
if (cfg.storefront && host === cfg.storefront.host) {
|
|
7793
|
+
if (!underBasePath(req, res, cfg.storefront.basePath)) return;
|
|
7794
|
+
return stampedResponse(req, res, ctx);
|
|
7795
|
+
}
|
|
6947
7796
|
return routeRequest(req, res, ctx);
|
|
6948
7797
|
}
|
|
7798
|
+
function underBasePath(req, res, base2) {
|
|
7799
|
+
if (base2 === "/") return true;
|
|
7800
|
+
const raw = req.url ?? "/";
|
|
7801
|
+
const at = raw.search(/[?#]/);
|
|
7802
|
+
const path = at < 0 ? raw : raw.slice(0, at);
|
|
7803
|
+
const query = at < 0 ? "" : raw.slice(at);
|
|
7804
|
+
if (path === TEST_SETTLE_ROUTE) return true;
|
|
7805
|
+
if (path.startsWith(base2)) {
|
|
7806
|
+
req.url = `/${raw.slice(base2.length)}`;
|
|
7807
|
+
return true;
|
|
7808
|
+
}
|
|
7809
|
+
if (path === "/" || `${path}/` === base2) {
|
|
7810
|
+
res.writeHead(302, { Location: `${base2}${query}` });
|
|
7811
|
+
res.end();
|
|
7812
|
+
return false;
|
|
7813
|
+
}
|
|
7814
|
+
req.url = PD_ROUTES.notFound;
|
|
7815
|
+
return true;
|
|
7816
|
+
}
|
|
6949
7817
|
function holdResponse() {
|
|
6950
7818
|
let status = 200;
|
|
6951
7819
|
const headers = {};
|
|
@@ -6980,12 +7848,12 @@ function holdResponse() {
|
|
|
6980
7848
|
var stampCaches = /* @__PURE__ */ new WeakMap();
|
|
6981
7849
|
function hashOfFor(req, ctx) {
|
|
6982
7850
|
const generation = ctx.generation?.() ?? "";
|
|
6983
|
-
let
|
|
6984
|
-
if (!
|
|
6985
|
-
|
|
6986
|
-
stampCaches.set(ctx,
|
|
7851
|
+
let cache2 = stampCaches.get(ctx);
|
|
7852
|
+
if (!cache2 || cache2.generation !== generation) {
|
|
7853
|
+
cache2 = { generation, hashes: /* @__PURE__ */ new Map() };
|
|
7854
|
+
stampCaches.set(ctx, cache2);
|
|
6987
7855
|
}
|
|
6988
|
-
const { hashes } =
|
|
7856
|
+
const { hashes } = cache2;
|
|
6989
7857
|
return (path) => {
|
|
6990
7858
|
let hash = hashes.get(path);
|
|
6991
7859
|
if (!hash) {
|
|
@@ -7011,8 +7879,13 @@ async function stampedResponse(req, res, ctx) {
|
|
|
7011
7879
|
await routeRequest(req, held.res, ctx);
|
|
7012
7880
|
const type = String(held.headers["content-type"] ?? "");
|
|
7013
7881
|
let body = held.body();
|
|
7014
|
-
|
|
7015
|
-
|
|
7882
|
+
const sf = ctx.cfg.storefront;
|
|
7883
|
+
const base2 = { base: sf.basePath, workbenchPath: sameHostWorkbenchPath(sf) };
|
|
7884
|
+
const preview = sf.basePath !== "/" && (req.url ?? "").startsWith(PD_ROUTE_PREFIX.preview);
|
|
7885
|
+
if (/^text\/html\b/.test(type)) {
|
|
7886
|
+
const html = withBaseHtml(await stampHtml(body.toString("utf8"), hashOfFor(req, ctx), sf.basePath), base2);
|
|
7887
|
+
body = preview ? withNoindex(html) : html;
|
|
7888
|
+
} else if (/^text\/css\b/.test(type)) body = withBaseCss(await stampCss(body.toString("utf8"), hashOfFor(req, ctx)), base2);
|
|
7016
7889
|
res.writeHead(held.status(), held.headers);
|
|
7017
7890
|
res.end(body);
|
|
7018
7891
|
}
|
|
@@ -7061,10 +7934,10 @@ async function routeRequest(req, res, ctx) {
|
|
|
7061
7934
|
return;
|
|
7062
7935
|
}
|
|
7063
7936
|
if (pathname === PD_ROUTES.mainJs) {
|
|
7064
|
-
return await serveFile(res,
|
|
7937
|
+
return await serveFile(res, join25(devUiDist2, "main.js"), "application/javascript");
|
|
7065
7938
|
}
|
|
7066
7939
|
if (pathname === PD_ROUTES.mainCss) {
|
|
7067
|
-
return await serveFile(res,
|
|
7940
|
+
return await serveFile(res, join25(devUiDist2, "main.css"), "text/css");
|
|
7068
7941
|
}
|
|
7069
7942
|
if (pathname === PD_ROUTES.axeCore) {
|
|
7070
7943
|
return await serveFile(res, resolveAxeCore(import.meta.url), "application/javascript");
|
|
@@ -7382,7 +8255,7 @@ async function routeRequest(req, res, ctx) {
|
|
|
7382
8255
|
if (cfg.storefront && host === cfg.storefront.host) {
|
|
7383
8256
|
const discovery = await currentDiscovery(state, patternRoots);
|
|
7384
8257
|
const discoveryMap = discovery.discoveryMap;
|
|
7385
|
-
const model = await readPages(cfg.storefront.pagesDir, { workbenchPath: sameHostWorkbenchPath(cfg.storefront) });
|
|
8258
|
+
const model = await readPages(cfg.storefront.pagesDir, { workbenchPath: sameHostWorkbenchPath(cfg.storefront), drafts: ctx.live ? "show" : "leave-out" });
|
|
7386
8259
|
if (pathname === PD_ROUTES.pages) {
|
|
7387
8260
|
if (model.problems.length) {
|
|
7388
8261
|
json(res, 500, { error: model.problems.join("\n") });
|
|
@@ -7391,8 +8264,8 @@ async function routeRequest(req, res, ctx) {
|
|
|
7391
8264
|
json(res, 200, { pages: model.pages, nav: model.nav });
|
|
7392
8265
|
return;
|
|
7393
8266
|
}
|
|
7394
|
-
const siteUrl = cfg.storefront.url === void 0 ? void 0 : ctx.live ? `http://${req.headers.host ?? cfg.storefront.host}
|
|
7395
|
-
if (pathname === PD_ROUTES.robots) {
|
|
8267
|
+
const siteUrl = cfg.storefront.url === void 0 ? void 0 : ctx.live ? `http://${req.headers.host ?? cfg.storefront.host}${cfg.storefront.basePath}` : cfg.storefront.url;
|
|
8268
|
+
if (pathname === PD_ROUTES.robots && cfg.storefront.basePath === "/") {
|
|
7396
8269
|
res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
|
|
7397
8270
|
res.end(storefrontRobotsTxt({ siteUrl, index: cfg.storefront.index, workbenchPath: sameHostWorkbenchPath(cfg.storefront) }));
|
|
7398
8271
|
return;
|
|
@@ -7431,13 +8304,15 @@ async function routeRequest(req, res, ctx) {
|
|
|
7431
8304
|
render,
|
|
7432
8305
|
site: ctx.site.get(),
|
|
7433
8306
|
workbenchUrl: ctx.storefrontWorkbenchUrl ?? `http://${PD_HOSTS.workbench}${port2 ? `:${port2}` : ""}/`,
|
|
7434
|
-
head: (layoutId, html) => pageStylesheets(cfg, state, discovery, layoutId, html),
|
|
8307
|
+
head: (layoutId, html, placed) => pageStylesheets(cfg, state, discovery, layoutId, html, placed),
|
|
7435
8308
|
...ctx.dates ? { dates: ctx.dates } : {},
|
|
7436
8309
|
...ctx.live ? { live: { wsPort, generation: generation ?? "" } } : {},
|
|
7437
8310
|
image: (path) => checkPageImage(path, assetRoots, assetRootNames),
|
|
7438
8311
|
...siteUrl !== void 0 ? { siteUrl } : {},
|
|
7439
8312
|
lang: cfg.storefront.lang,
|
|
7440
8313
|
siteIndex: cfg.storefront.index,
|
|
8314
|
+
basePath: cfg.storefront.basePath,
|
|
8315
|
+
...cfg.storefront.head !== void 0 ? { siteHead: { path: cfg.storefront.head, name: cfg.storefront.headName ?? cfg.storefront.head } } : {},
|
|
7441
8316
|
...cfg.storefront.image !== void 0 ? { siteImage: cfg.storefront.image } : {},
|
|
7442
8317
|
...ctx.images ? { images: ctx.images } : {}
|
|
7443
8318
|
});
|
|
@@ -7530,7 +8405,7 @@ function createRenderingHandle(build, registrars, globalData) {
|
|
|
7530
8405
|
|
|
7531
8406
|
// packages/dev-server/src/watch-roots.ts
|
|
7532
8407
|
import { statSync as statSync3 } from "node:fs";
|
|
7533
|
-
import { dirname as dirname9, extname as extname3, join as
|
|
8408
|
+
import { dirname as dirname9, extname as extname3, join as join26, resolve as resolve9, sep as sep8 } from "node:path";
|
|
7534
8409
|
var MODULE = /\.[mc]?js$/;
|
|
7535
8410
|
function helperScope(path) {
|
|
7536
8411
|
let isDir = false;
|
|
@@ -7549,23 +8424,23 @@ function watchedRoots(cfg) {
|
|
|
7549
8424
|
styles: [...projectStyles, ...packageStyles].map((r) => ({ dir: r.dir, inBase: documentBaseMatcher(r.dir, r.base) })),
|
|
7550
8425
|
themes: [
|
|
7551
8426
|
...cfg.config.themes?.dir && cfg.themesDir ? [cfg.themesDir] : [],
|
|
7552
|
-
...[...linked.values()].filter((p) => p.manifest.themes).map((p) =>
|
|
8427
|
+
...[...linked.values()].filter((p) => p.manifest.themes).map((p) => join26(p.dir, p.manifest.themes.dir))
|
|
7553
8428
|
],
|
|
7554
8429
|
scripts: [
|
|
7555
8430
|
...cfg.config.scriptsDir && cfg.scriptsDirs[0] ? [cfg.scriptsDirs[0]] : [],
|
|
7556
|
-
...[...linked.values()].filter((p) => p.manifest.scripts).map((p) =>
|
|
8431
|
+
...[...linked.values()].filter((p) => p.manifest.scripts).map((p) => join26(p.dir, p.manifest.scripts))
|
|
7557
8432
|
],
|
|
7558
8433
|
data: [
|
|
7559
8434
|
...cfg.config.dataDir ? [resolve9(cfg.cwd, cfg.config.dataDir)] : [],
|
|
7560
|
-
...[...linked.values()].filter((p) => p.manifest.data).map((p) =>
|
|
8435
|
+
...[...linked.values()].filter((p) => p.manifest.data).map((p) => join26(p.dir, p.manifest.data))
|
|
7561
8436
|
],
|
|
7562
8437
|
assets: [
|
|
7563
8438
|
...[cfg.config.assets?.dir ?? []].flat().map((d) => resolve9(cfg.cwd, d)),
|
|
7564
|
-
...[...linked.values()].flatMap((p) => (p.manifest.assets ?? []).map((a) =>
|
|
8439
|
+
...[...linked.values()].flatMap((p) => (p.manifest.assets ?? []).map((a) => join26(p.dir, a)))
|
|
7565
8440
|
],
|
|
7566
8441
|
helpers: [
|
|
7567
8442
|
...cfg.config.helpers ? [resolve9(cfg.cwd, cfg.config.helpers)] : [],
|
|
7568
|
-
...[...linked.values()].filter((p) => p.manifest.helpers).map((p) =>
|
|
8443
|
+
...[...linked.values()].filter((p) => p.manifest.helpers).map((p) => join26(p.dir, p.manifest.helpers))
|
|
7569
8444
|
].map(helperScope)
|
|
7570
8445
|
};
|
|
7571
8446
|
}
|
|
@@ -7596,10 +8471,10 @@ function rootMatchers(watched) {
|
|
|
7596
8471
|
}
|
|
7597
8472
|
|
|
7598
8473
|
// packages/dev-server/src/watch.ts
|
|
7599
|
-
import { createHash as
|
|
7600
|
-
import { existsSync as existsSync6, readFileSync as
|
|
8474
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
8475
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4, statSync as statSync4 } from "node:fs";
|
|
7601
8476
|
import { appendFile, readdir as readdir9 } from "node:fs/promises";
|
|
7602
|
-
import { basename as basename10, extname as extname4, join as
|
|
8477
|
+
import { basename as basename10, extname as extname4, join as join27, relative as relative13, resolve as resolve10, sep as sep9 } from "node:path";
|
|
7603
8478
|
import chokidar from "chokidar";
|
|
7604
8479
|
|
|
7605
8480
|
// packages/dev-server/src/config-change.ts
|
|
@@ -7658,7 +8533,7 @@ function seenNow(path) {
|
|
|
7658
8533
|
const s = statSync4(path);
|
|
7659
8534
|
const stamp2 = `${s.ino}:${s.ctimeMs}:${s.mtimeMs}:${s.size}`;
|
|
7660
8535
|
const forced = globalThis.__testOnly_watchStamp;
|
|
7661
|
-
return { stamp: forced ? forced(path, stamp2) : stamp2, real: stamp2, hash:
|
|
8536
|
+
return { stamp: forced ? forced(path, stamp2) : stamp2, real: stamp2, hash: createHash7("sha1").update(readFileSync4(path)).digest("hex") };
|
|
7662
8537
|
} catch {
|
|
7663
8538
|
return null;
|
|
7664
8539
|
}
|
|
@@ -7689,7 +8564,7 @@ async function startWatching(ctx) {
|
|
|
7689
8564
|
const barrierWaiters = /* @__PURE__ */ new Set();
|
|
7690
8565
|
const SETTLE_MS = 1e4;
|
|
7691
8566
|
async function settle() {
|
|
7692
|
-
const files = [...new Set(watchDirs)].filter((d) => existsSync6(d)).map((d) =>
|
|
8567
|
+
const files = [...new Set(watchDirs)].filter((d) => existsSync6(d)).map((d) => join27(d, BARRIER));
|
|
7693
8568
|
const waiting = new Set(files);
|
|
7694
8569
|
const handled = new Promise((resolve15, reject) => {
|
|
7695
8570
|
if (!waiting.size) {
|
|
@@ -7947,7 +8822,7 @@ async function startWatching(ctx) {
|
|
|
7947
8822
|
}
|
|
7948
8823
|
for (const entry of entries) {
|
|
7949
8824
|
if (closed) return true;
|
|
7950
|
-
const path =
|
|
8825
|
+
const path = join27(dir, entry.name);
|
|
7951
8826
|
if (entry.isDirectory()) {
|
|
7952
8827
|
if (firstReport(path)) dispatchers.addDir?.(path);
|
|
7953
8828
|
await scanFolder(path);
|
|
@@ -8057,7 +8932,11 @@ async function startWatching(ctx) {
|
|
|
8057
8932
|
const compId = componentOfScss(resolve10(recompiledFile), allIds);
|
|
8058
8933
|
const root = styleRootOf(resolve10(recompiledFile));
|
|
8059
8934
|
if (compId && root) {
|
|
8060
|
-
const
|
|
8935
|
+
const out = {};
|
|
8936
|
+
const newCss = await recomputeComponentCss(recompiledFile, compId, root.dir, allIds, root.inBase, out);
|
|
8937
|
+
if (newCss !== null && (out.baseRules ?? "") !== (state.entryBaseRules.get(resolve10(recompiledFile)) ?? "")) {
|
|
8938
|
+
return restyleAll(discovery, timestamp, effectiveFiles, compileErrors.length > 0);
|
|
8939
|
+
}
|
|
8061
8940
|
if (newCss !== null) {
|
|
8062
8941
|
const wasEmpty = (state.perComponentCss.get(compId) ?? "") === "";
|
|
8063
8942
|
state.perComponentCss.set(compId, newCss);
|
|
@@ -8259,7 +9138,7 @@ async function startWatching(ctx) {
|
|
|
8259
9138
|
async function pagesFingerprint() {
|
|
8260
9139
|
if (!pagesDir) return null;
|
|
8261
9140
|
const model = await readPages(pagesDir, { workbenchPath });
|
|
8262
|
-
return JSON.stringify({ nav: model.nav, pages: model.pages, problems: model.problems });
|
|
9141
|
+
return JSON.stringify({ nav: model.nav, pages: model.pages, problems: model.problems, entries: [...model.entries], folders: [...model.folders] });
|
|
8263
9142
|
}
|
|
8264
9143
|
async function handlePages(filePath, event) {
|
|
8265
9144
|
if (!inPages(filePath)) return false;
|
|
@@ -8564,7 +9443,7 @@ function createGitDates(pagesDir, listPages, git = runGit) {
|
|
|
8564
9443
|
|
|
8565
9444
|
// packages/dev-server/src/export-io.ts
|
|
8566
9445
|
import { readFile as readFile25, writeFile as writeFile6, mkdir as mkdir6 } from "node:fs/promises";
|
|
8567
|
-
import { dirname as dirname10, join as
|
|
9446
|
+
import { dirname as dirname10, join as join28, relative as relative15, resolve as resolve11 } from "node:path";
|
|
8568
9447
|
|
|
8569
9448
|
// packages/dev-server/src/export.ts
|
|
8570
9449
|
var escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -8757,20 +9636,20 @@ async function writeExport(families, sink) {
|
|
|
8757
9636
|
const files = [];
|
|
8758
9637
|
for (const family of families) {
|
|
8759
9638
|
const baseRel = `${family.baseId}.hbs`;
|
|
8760
|
-
const basePath =
|
|
9639
|
+
const basePath = join28(sink, baseRel);
|
|
8761
9640
|
await mkdir6(dirname10(basePath), { recursive: true });
|
|
8762
9641
|
await writeFile6(basePath, family.baseHbs, "utf-8");
|
|
8763
9642
|
files.push(baseRel);
|
|
8764
9643
|
if (family.baseData) {
|
|
8765
9644
|
const dataRel = `${family.baseId}.json`;
|
|
8766
|
-
await writeFile6(
|
|
9645
|
+
await writeFile6(join28(sink, dataRel), JSON.stringify(family.baseData, null, 2) + "\n", "utf-8");
|
|
8767
9646
|
files.push(dataRel);
|
|
8768
9647
|
}
|
|
8769
9648
|
for (const v of family.variants) {
|
|
8770
9649
|
if (v.token === "") continue;
|
|
8771
9650
|
const overlayRel = `${family.baseId}~${v.token}.json`;
|
|
8772
9651
|
const overlay = { modifier: v.modifier, ...v.data ?? {} };
|
|
8773
|
-
await writeFile6(
|
|
9652
|
+
await writeFile6(join28(sink, overlayRel), JSON.stringify(overlay, null, 2) + "\n", "utf-8");
|
|
8774
9653
|
files.push(overlayRel);
|
|
8775
9654
|
}
|
|
8776
9655
|
}
|
|
@@ -8802,7 +9681,7 @@ async function writeThreadedIncluders(threaded, patternsDir, sink) {
|
|
|
8802
9681
|
if (res.rewrites === 0) continue;
|
|
8803
9682
|
rewrites += res.rewrites;
|
|
8804
9683
|
const rel = relative15(patternsDir, hbsPath);
|
|
8805
|
-
const outPath =
|
|
9684
|
+
const outPath = join28(sink, rel);
|
|
8806
9685
|
await mkdir6(dirname10(outPath), { recursive: true });
|
|
8807
9686
|
await writeFile6(outPath, res.hbs, "utf-8");
|
|
8808
9687
|
files.push(rel);
|
|
@@ -8958,6 +9837,16 @@ async function startDevServer(config, cwd, opts = {}) {
|
|
|
8958
9837
|
build: opts.live === false,
|
|
8959
9838
|
warn: (message) => console.warn(`[dev-server] ${message}`)
|
|
8960
9839
|
});
|
|
9840
|
+
const helperNamesSaid = /* @__PURE__ */ new Set();
|
|
9841
|
+
const reportHelperNames = (diagnostics) => {
|
|
9842
|
+
for (const d of diagnostics ?? []) {
|
|
9843
|
+
if (d.kind !== "SCHEMA_INVALID" || !isHelperNameProblem(d.message)) continue;
|
|
9844
|
+
const line = `${d.location?.file ?? "a schema"} (${d.path ?? "#"}): ${d.message}`;
|
|
9845
|
+
if (helperNamesSaid.has(line)) continue;
|
|
9846
|
+
helperNamesSaid.add(line);
|
|
9847
|
+
console.error(`[dev-server] error: ${line}`);
|
|
9848
|
+
}
|
|
9849
|
+
};
|
|
8961
9850
|
const buildRenderer = (registrars, data) => {
|
|
8962
9851
|
const renderWith = createRenderer({
|
|
8963
9852
|
patternsDir,
|
|
@@ -8971,7 +9860,9 @@ async function startDevServer(config, cwd, opts = {}) {
|
|
|
8971
9860
|
});
|
|
8972
9861
|
return async (input) => {
|
|
8973
9862
|
await images.ready(generationNow());
|
|
8974
|
-
|
|
9863
|
+
const result = await renderWith(input);
|
|
9864
|
+
reportHelperNames(result.meta?.diagnostics);
|
|
9865
|
+
return result;
|
|
8975
9866
|
};
|
|
8976
9867
|
};
|
|
8977
9868
|
const rendering = createRenderingHandle(buildRenderer, helperRegistrars, globalData);
|
|
@@ -8995,7 +9886,7 @@ async function startDevServer(config, cwd, opts = {}) {
|
|
|
8995
9886
|
if (cfg.themeDirs.length) {
|
|
8996
9887
|
for (const { name, dir } of await discoverThemeFiles(cfg.themeDirs)) {
|
|
8997
9888
|
try {
|
|
8998
|
-
const css = await compileSassFile(
|
|
9889
|
+
const css = await compileSassFile(join29(dir, `${name}.theme.scss`));
|
|
8999
9890
|
state.themeCss.set(name, css);
|
|
9000
9891
|
const slots = config.themes?.dir && dir === cfg.themeDirs[0] ? swatchSlots : cfg.packageSwatchSlots;
|
|
9001
9892
|
state.themeTokens.set(name, await extractThemeTokens(name, css, slots, dir));
|
|
@@ -9071,7 +9962,7 @@ async function startDevServer(config, cwd, opts = {}) {
|
|
|
9071
9962
|
httpPort = actualPort;
|
|
9072
9963
|
console.log(`[dev-server] Listening at http://127.0.0.1:${actualPort}`);
|
|
9073
9964
|
if (live) {
|
|
9074
|
-
if (cfg.storefront) console.log(`[dev-server] Website: http://${cfg.storefront.host}:${actualPort}
|
|
9965
|
+
if (cfg.storefront) console.log(`[dev-server] Website: http://${cfg.storefront.host}:${actualPort}${cfg.storefront.basePath}`);
|
|
9075
9966
|
console.log(`[dev-server] Workbench: http://${cfg.storefront ? PD_HOSTS.workbench : "localhost"}:${actualPort}/`);
|
|
9076
9967
|
}
|
|
9077
9968
|
const startupDiscovery = await cachedDiscovery(state);
|
|
@@ -9116,10 +10007,10 @@ async function startDevServer(config, cwd, opts = {}) {
|
|
|
9116
10007
|
import { build as esbuild } from "esbuild";
|
|
9117
10008
|
import { existsSync as existsSync7 } from "node:fs";
|
|
9118
10009
|
import { readFile as readFile26, writeFile as writeFile7, unlink, mkdir as mkdir7 } from "node:fs/promises";
|
|
9119
|
-
import { basename as basename11, join as
|
|
10010
|
+
import { basename as basename11, join as join30, dirname as dirname11, resolve as resolve12, relative as relative16, isAbsolute as isAbsolute4 } from "node:path";
|
|
9120
10011
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
9121
|
-
import { createHash as
|
|
9122
|
-
import { pathToFileURL as
|
|
10012
|
+
import { createHash as createHash8, randomUUID as randomUUID3 } from "node:crypto";
|
|
10013
|
+
import { pathToFileURL as pathToFileURL3 } from "node:url";
|
|
9123
10014
|
async function runDev(root, opts, configPath) {
|
|
9124
10015
|
const config = await loadConfig(root, configPath);
|
|
9125
10016
|
const file = configFileOf(root, configPath);
|
|
@@ -9132,6 +10023,7 @@ async function runBuild(root, configPath, options) {
|
|
|
9132
10023
|
outDir,
|
|
9133
10024
|
...options.base !== void 0 ? { basePath: options.base } : {},
|
|
9134
10025
|
...options.audit !== void 0 ? { audit: options.audit } : {},
|
|
10026
|
+
...options.browser ? { browser: true } : {},
|
|
9135
10027
|
pdVersion: options.pdVersion,
|
|
9136
10028
|
log: (line) => console.log(`[pd build] ${line}`)
|
|
9137
10029
|
});
|
|
@@ -9139,11 +10031,24 @@ async function runBuild(root, configPath, options) {
|
|
|
9139
10031
|
console.log(`[pd build] ${report.components} components \u2192 ${report.files} files, ${mb} MB in ${report.ms} ms`);
|
|
9140
10032
|
console.log(`[pd build] ${report.outDir} (served from ${report.basePath})`);
|
|
9141
10033
|
if (report.storefront) {
|
|
9142
|
-
console.log(`[pd build] storefront: ${report.storefront.pages} pages \u2192 ${report.storefront.files} files in ${report.storefront.outDir} (served from
|
|
10034
|
+
console.log(`[pd build] storefront: ${report.storefront.pages} pages \u2192 ${report.storefront.files} files in ${report.storefront.outDir} (served from ${report.storefront.basePath})`);
|
|
9143
10035
|
}
|
|
9144
10036
|
if (report.links) {
|
|
9145
10037
|
console.log(`[pd build] links: ${report.links.references} references on ${report.links.pages} pages, none broken` + (report.links.external ? `; ${report.links.external} external, not checked (notes)` : ""));
|
|
9146
10038
|
}
|
|
10039
|
+
if (report.accessibility) {
|
|
10040
|
+
const n = report.accessibility.warnings.length;
|
|
10041
|
+
console.log(`[pd build] accessibility: ${report.accessibility.pages} pages${report.accessibility.browser ? ", in jsdom and Chromium" : ""}, no errors` + (n ? `; ${n} rule${n === 1 ? "" : "s"} with warnings:` : ""));
|
|
10042
|
+
for (const w of report.accessibility.warnings) console.log(`[pd build] warning: ${w}`);
|
|
10043
|
+
}
|
|
10044
|
+
if (report.images) {
|
|
10045
|
+
const n = report.images.warnings.length;
|
|
10046
|
+
console.log(`[pd build] images: ${n ? `${n} with a variant that doesn't fit:` : "every variant fits its box"}`);
|
|
10047
|
+
for (const w of report.images.warnings) console.log(`[pd build] warning: ${w}`);
|
|
10048
|
+
}
|
|
10049
|
+
if (report.drafts) {
|
|
10050
|
+
console.log(`[pd build] drafts: ${report.drafts.length} left out (${report.drafts.join(", ")})`);
|
|
10051
|
+
}
|
|
9147
10052
|
for (const w of report.warnings) console.log(`[pd build] not built: ${w}`);
|
|
9148
10053
|
for (const n of report.notes) console.log(`[pd build] note: ${n}`);
|
|
9149
10054
|
}
|
|
@@ -9152,7 +10057,7 @@ async function runAudit(root, configPath, format = "md") {
|
|
|
9152
10057
|
const { graph, reaudited, reused, artifactPath, imageWarning } = await runDiagnosticsAudit(config, root);
|
|
9153
10058
|
const report = formatAudit(graph, format);
|
|
9154
10059
|
const ext = format === "json" ? "json" : format === "text" ? "txt" : "md";
|
|
9155
|
-
const reportPath =
|
|
10060
|
+
const reportPath = join30(dirname11(artifactPath), `diagnostics-report.${ext}`);
|
|
9156
10061
|
await writeFile7(reportPath, report, "utf-8");
|
|
9157
10062
|
console.log(report);
|
|
9158
10063
|
const meta = format === "json" ? console.error : console.log;
|
|
@@ -9170,8 +10075,8 @@ function auditSummaryLines(r) {
|
|
|
9170
10075
|
];
|
|
9171
10076
|
}
|
|
9172
10077
|
function defaultExportDir(root) {
|
|
9173
|
-
const key =
|
|
9174
|
-
return
|
|
10078
|
+
const key = createHash8("sha1").update(resolve12(root)).digest("hex").slice(0, 16);
|
|
10079
|
+
return join30(tmpdir2(), "pattern-dump", key, "export");
|
|
9175
10080
|
}
|
|
9176
10081
|
function assertOutsideRoot(outDir, root) {
|
|
9177
10082
|
const rel = relative16(resolve12(root), resolve12(outDir));
|
|
@@ -9248,18 +10153,30 @@ async function loadConfig(root, configPath) {
|
|
|
9248
10153
|
if (!existsSync7(configPath)) {
|
|
9249
10154
|
throw new ConfigError(`--config file not found: ${configPath}`);
|
|
9250
10155
|
}
|
|
9251
|
-
return validateConfig(await readConfigFile(configPath), configPath);
|
|
10156
|
+
return validateConfig(await withRootManifest(root, await readConfigFile(configPath), configPath), configPath);
|
|
9252
10157
|
}
|
|
9253
10158
|
const path = configFileOf(root);
|
|
9254
|
-
if (path) return validateConfig(await readConfigFile(path), basename11(path));
|
|
10159
|
+
if (path) return validateConfig(await withRootManifest(root, await readConfigFile(path), basename11(path)), basename11(path));
|
|
9255
10160
|
throw new ConfigError(
|
|
9256
10161
|
`No pattern-dump.config.ts or pattern-dump.config.json found in ${root} (and no --config given)`
|
|
9257
10162
|
);
|
|
9258
10163
|
}
|
|
10164
|
+
async function withRootManifest(root, raw, configName) {
|
|
10165
|
+
const pkgPath = join30(root, "package.json");
|
|
10166
|
+
if (!existsSync7(pkgPath)) return raw;
|
|
10167
|
+
let pkg;
|
|
10168
|
+
try {
|
|
10169
|
+
pkg = JSON.parse(await readFile26(pkgPath, "utf-8"));
|
|
10170
|
+
} catch {
|
|
10171
|
+
return raw;
|
|
10172
|
+
}
|
|
10173
|
+
const manifest = typeof pkg === "object" && pkg !== null ? pkg.patternDump : void 0;
|
|
10174
|
+
return manifest === void 0 ? raw : withOwnManifest(raw, manifest, { config: configName, manifest: "package.json" });
|
|
10175
|
+
}
|
|
9259
10176
|
function configFileOf(root, configPath) {
|
|
9260
10177
|
if (configPath) return configPath;
|
|
9261
10178
|
for (const name of CONFIG_NAMES) {
|
|
9262
|
-
const path =
|
|
10179
|
+
const path = join30(root, name);
|
|
9263
10180
|
if (existsSync7(path)) return path;
|
|
9264
10181
|
}
|
|
9265
10182
|
return void 0;
|
|
@@ -9269,7 +10186,7 @@ async function readConfigFile(path) {
|
|
|
9269
10186
|
return path.endsWith(".json") ? JSON.parse(await readFile26(path, "utf-8")) : loadTsConfig(path);
|
|
9270
10187
|
}
|
|
9271
10188
|
async function loadTsConfig(tsPath) {
|
|
9272
|
-
const tmp =
|
|
10189
|
+
const tmp = join30(tmpdir2(), `pd-config-${process.pid}-${randomUUID3()}.mjs`);
|
|
9273
10190
|
try {
|
|
9274
10191
|
await esbuild({
|
|
9275
10192
|
entryPoints: [tsPath],
|
|
@@ -9279,7 +10196,7 @@ async function loadTsConfig(tsPath) {
|
|
|
9279
10196
|
// No packages:"external" — the temp file runs from /tmp so we must inline deps.
|
|
9280
10197
|
outfile: tmp
|
|
9281
10198
|
});
|
|
9282
|
-
const { default: cfg } = await import(
|
|
10199
|
+
const { default: cfg } = await import(pathToFileURL3(tmp).href);
|
|
9283
10200
|
return cfg;
|
|
9284
10201
|
} finally {
|
|
9285
10202
|
await unlink(tmp).catch(() => {
|
|
@@ -9359,7 +10276,8 @@ function parseCommand(argv, cwd) {
|
|
|
9359
10276
|
out: { type: "string" },
|
|
9360
10277
|
base: { type: "string" },
|
|
9361
10278
|
audit: { type: "boolean" },
|
|
9362
|
-
"no-audit": { type: "boolean" }
|
|
10279
|
+
"no-audit": { type: "boolean" },
|
|
10280
|
+
browser: { type: "boolean" }
|
|
9363
10281
|
}
|
|
9364
10282
|
});
|
|
9365
10283
|
if (values2.help) return { kind: "help", topic: cmd };
|
|
@@ -9370,7 +10288,8 @@ function parseCommand(argv, cwd) {
|
|
|
9370
10288
|
...base(values2, cwd),
|
|
9371
10289
|
...values2.out !== void 0 ? { out: values2.out } : {},
|
|
9372
10290
|
...values2.base !== void 0 ? { base: values2.base } : {},
|
|
9373
|
-
...audit !== void 0 ? { audit } : {}
|
|
10291
|
+
...audit !== void 0 ? { audit } : {},
|
|
10292
|
+
...values2.browser ? { browser: true } : {}
|
|
9374
10293
|
};
|
|
9375
10294
|
}
|
|
9376
10295
|
const { values } = parseArgs({
|
|
@@ -9467,6 +10386,9 @@ var USAGE = `Usage: pd <command> [options]
|
|
|
9467
10386
|
--config <path> Use a config file outside the root
|
|
9468
10387
|
--out <dir> Output folder, relative to the root (default: build.outDir, outDir, or dist)
|
|
9469
10388
|
--base <path> Where it will be hosted: / (default) or a subpath such as /docs/
|
|
10389
|
+
--browser Also check the storefront's pages in Chromium: contrast and the accessibility
|
|
10390
|
+
rules that need a rendered page, and each image's variant against its size.
|
|
10391
|
+
Uses the project's own Playwright (npm install --save-dev playwright)
|
|
9470
10392
|
--no-audit Leave the corpus audit out: it is not run, and the site has no Audit tab
|
|
9471
10393
|
(default: build.audit in the config; --audit forces it back in)
|
|
9472
10394
|
|
|
@@ -9480,12 +10402,12 @@ var USAGE = `Usage: pd <command> [options]
|
|
|
9480
10402
|
// packages/cli/src/init/run.ts
|
|
9481
10403
|
import { existsSync as existsSync8 } from "node:fs";
|
|
9482
10404
|
import { readFile as readFile28 } from "node:fs/promises";
|
|
9483
|
-
import { basename as basename12, dirname as dirname13, join as
|
|
10405
|
+
import { basename as basename12, dirname as dirname13, join as join32, relative as relative18, resolve as resolve14 } from "node:path";
|
|
9484
10406
|
|
|
9485
10407
|
// packages/cli/package.json
|
|
9486
10408
|
var package_default = {
|
|
9487
10409
|
name: "@pattern-dump/cli",
|
|
9488
|
-
version: "0.
|
|
10410
|
+
version: "0.4.0",
|
|
9489
10411
|
private: true,
|
|
9490
10412
|
description: "The pd command and the published pattern-dump package.",
|
|
9491
10413
|
type: "module",
|
|
@@ -9521,12 +10443,12 @@ var PD_VERSION = package_default.version;
|
|
|
9521
10443
|
|
|
9522
10444
|
// packages/cli/src/init/files.ts
|
|
9523
10445
|
import { mkdir as mkdir8, readdir as readdir10, readFile as readFile27, writeFile as writeFile8 } from "node:fs/promises";
|
|
9524
|
-
import { dirname as dirname12, join as
|
|
10446
|
+
import { dirname as dirname12, join as join31, relative as relative17, sep as sep12 } from "node:path";
|
|
9525
10447
|
async function readTemplateDir(dir) {
|
|
9526
10448
|
const out = [];
|
|
9527
10449
|
async function walk(current) {
|
|
9528
10450
|
for (const entry of await readdir10(current, { withFileTypes: true })) {
|
|
9529
|
-
const full =
|
|
10451
|
+
const full = join31(current, entry.name);
|
|
9530
10452
|
if (entry.isDirectory()) await walk(full);
|
|
9531
10453
|
else out.push({ path: relative17(dir, full).split(sep12).join("/"), content: await readFile27(full, "utf8") });
|
|
9532
10454
|
}
|
|
@@ -9535,11 +10457,11 @@ async function readTemplateDir(dir) {
|
|
|
9535
10457
|
return out.sort((a, b) => a.path.localeCompare(b.path));
|
|
9536
10458
|
}
|
|
9537
10459
|
async function readTemplates(templatesDir) {
|
|
9538
|
-
const fill = (name) => readFile27(
|
|
10460
|
+
const fill = (name) => readFile27(join31(templatesDir, "storefront-fills", name), "utf8");
|
|
9539
10461
|
const [base2, examples, storefront, config, readme, gitignore] = await Promise.all([
|
|
9540
|
-
readTemplateDir(
|
|
9541
|
-
readTemplateDir(
|
|
9542
|
-
readTemplateDir(
|
|
10462
|
+
readTemplateDir(join31(templatesDir, "base")),
|
|
10463
|
+
readTemplateDir(join31(templatesDir, "examples")),
|
|
10464
|
+
readTemplateDir(join31(templatesDir, "storefront")),
|
|
9543
10465
|
fill("config.txt"),
|
|
9544
10466
|
fill("readme.md"),
|
|
9545
10467
|
fill("gitignore.txt")
|
|
@@ -9549,12 +10471,12 @@ async function readTemplates(templatesDir) {
|
|
|
9549
10471
|
async function applyPlan(target, plan) {
|
|
9550
10472
|
await mkdir8(target, { recursive: true });
|
|
9551
10473
|
for (const file of plan.writes) {
|
|
9552
|
-
const abs =
|
|
10474
|
+
const abs = join31(target, file.path);
|
|
9553
10475
|
await mkdir8(dirname12(abs), { recursive: true });
|
|
9554
10476
|
await writeFile8(abs, file.content, { flag: "wx" });
|
|
9555
10477
|
}
|
|
9556
|
-
await writeFile8(
|
|
9557
|
-
for (const dir of ["assets", "src/patterns"]) await mkdir8(
|
|
10478
|
+
await writeFile8(join31(target, "package.json"), plan.packageJson.content);
|
|
10479
|
+
for (const dir of ["assets", "src/patterns"]) await mkdir8(join31(target, dir), { recursive: true });
|
|
9558
10480
|
}
|
|
9559
10481
|
|
|
9560
10482
|
// packages/cli/src/init/plan.ts
|
|
@@ -9676,7 +10598,7 @@ async function runInit(cmd, io) {
|
|
|
9676
10598
|
} else {
|
|
9677
10599
|
dependency = dependencySpec(void 0, io.cwd);
|
|
9678
10600
|
}
|
|
9679
|
-
const pkgPath =
|
|
10601
|
+
const pkgPath = join32(target, "package.json");
|
|
9680
10602
|
let packageJson = null;
|
|
9681
10603
|
if (existsSync8(pkgPath)) {
|
|
9682
10604
|
try {
|
|
@@ -9697,7 +10619,7 @@ async function runInit(cmd, io) {
|
|
|
9697
10619
|
const install = cmd.install ?? (prompt ? await io.confirm("Run npm install", true) : true);
|
|
9698
10620
|
const answers = { name, brand, examples, storefront, dependency };
|
|
9699
10621
|
const templates = await readTemplates(io.templatesDir);
|
|
9700
|
-
const result = planInit(templates, answers, { packageJson, exists: (p) => existsSync8(
|
|
10622
|
+
const result = planInit(templates, answers, { packageJson, exists: (p) => existsSync8(join32(target, p)) }, PD_PACKAGE_NAME);
|
|
9701
10623
|
if (!result.ok) {
|
|
9702
10624
|
io.log(`\u2717 ${result.reason}`);
|
|
9703
10625
|
return 1;
|
|
@@ -9746,7 +10668,7 @@ async function runInit(cmd, io) {
|
|
|
9746
10668
|
|
|
9747
10669
|
// packages/cli/src/init/local-build.ts
|
|
9748
10670
|
import { existsSync as existsSync9, statSync as statSync5 } from "node:fs";
|
|
9749
|
-
import { join as
|
|
10671
|
+
import { join as join33 } from "node:path";
|
|
9750
10672
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
9751
10673
|
var BUILT = [
|
|
9752
10674
|
"packages/cli/dist/index.js",
|
|
@@ -9764,10 +10686,10 @@ var mtime = (path) => {
|
|
|
9764
10686
|
};
|
|
9765
10687
|
function detectLocalBuild(moduleUrl, packageName, version) {
|
|
9766
10688
|
const repo = fileURLToPath7(new URL("../../../", moduleUrl)).replace(/[\\/]$/, "");
|
|
9767
|
-
if (!existsSync9(
|
|
9768
|
-
const tarball =
|
|
10689
|
+
if (!existsSync9(join33(repo, "packages/cli/src/distribution.ts"))) return null;
|
|
10690
|
+
const tarball = join33(repo, ".pd-pack", `${packageName}-${version}.tgz`);
|
|
9769
10691
|
if (!existsSync9(tarball)) return { repo, tarball: null, stale: false };
|
|
9770
|
-
const newestBuild = Math.max(...BUILT.map((p) => mtime(
|
|
10692
|
+
const newestBuild = Math.max(...BUILT.map((p) => mtime(join33(repo, p))));
|
|
9771
10693
|
return { repo, tarball, stale: newestBuild > mtime(tarball) };
|
|
9772
10694
|
}
|
|
9773
10695
|
|
|
@@ -9834,6 +10756,7 @@ switch (command.kind) {
|
|
|
9834
10756
|
...command.out !== void 0 ? { out: command.out } : {},
|
|
9835
10757
|
...command.base !== void 0 ? { base: command.base } : {},
|
|
9836
10758
|
...command.audit !== void 0 ? { audit: command.audit } : {},
|
|
10759
|
+
...command.browser ? { browser: true } : {},
|
|
9837
10760
|
pdVersion: PD_VERSION
|
|
9838
10761
|
}).then(() => process.stdout.write("", () => process.exit(0))).catch((e) => {
|
|
9839
10762
|
console.error("[pd] Error:", e instanceof Error ? e.message : e);
|