pattern-dump 0.3.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 +61 -0
- package/README.md +7 -2
- package/dist/chunks/{chunk-5WFBNPXB.js → chunk-DIDJPCLS.js} +163 -15
- package/dist/index.js +1 -1
- package/dist/pd.js +1439 -725
- package/dist/types/config/index.d.ts +14 -5
- package/dist/types/config/own-manifest.d.ts +9 -0
- package/dist/types/config/validate.d.ts +8 -1
- 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 = {
|
|
@@ -899,17 +901,78 @@ 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
977
|
for (const source2 of sources) {
|
|
915
978
|
if (!source2) continue;
|
|
@@ -956,7 +1019,7 @@ async function readDataFile(path, display) {
|
|
|
956
1019
|
}
|
|
957
1020
|
}
|
|
958
1021
|
function imageTag(source2, o) {
|
|
959
|
-
const esc =
|
|
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
1024
|
if (source2.kind === "single") return `<img src="${esc(source2.url)}" width="${source2.width}" height="${source2.height}" ${rest}>`;
|
|
962
1025
|
const widths = o.widths.filter((w) => w <= source2.width);
|
|
@@ -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
|
};
|
|
@@ -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;
|
|
@@ -3086,6 +3167,7 @@ var page_rule_tiers_generated_default = {
|
|
|
3086
3167
|
var require4 = createRequire3(import.meta.url);
|
|
3087
3168
|
var axeSource;
|
|
3088
3169
|
var source = () => axeSource ??= readFileSync2(require4.resolve("axe-core/axe.min.js"), "utf8");
|
|
3170
|
+
var axeScript = () => source();
|
|
3089
3171
|
var PAGE_TAGS = ["wcag2a", "wcag2aa", "wcag21a", "wcag21aa", "wcag22aa", "best-practice"];
|
|
3090
3172
|
async function runAxeOnPage(html, rules) {
|
|
3091
3173
|
const dom = new JSDOM2(html, { url: "http://site.invalid/", runScripts: "outside-only", pretendToBeVisual: true });
|
|
@@ -3116,6 +3198,7 @@ async function runPageA11y(html) {
|
|
|
3116
3198
|
nodes: v.nodes.map((n) => ({ target: n.target.map(String).join(" "), html: n.html }))
|
|
3117
3199
|
}));
|
|
3118
3200
|
}
|
|
3201
|
+
var pageRuleTiers = page_rule_tiers_generated_default;
|
|
3119
3202
|
|
|
3120
3203
|
// packages/dev-server/src/diagnostics.ts
|
|
3121
3204
|
async function checkComponent(componentId, variant, render, quarantineEntry) {
|
|
@@ -3137,7 +3220,7 @@ async function checkComponent(componentId, variant, render, quarantineEntry) {
|
|
|
3137
3220
|
const a11yDiags = await runA11yProducer(previewHtml);
|
|
3138
3221
|
return { previewHtml, diagnostics: [...renderDiags, ...a11yDiags] };
|
|
3139
3222
|
} catch (e) {
|
|
3140
|
-
const
|
|
3223
|
+
const location2 = e instanceof Error && "location" in e ? e.location : void 0;
|
|
3141
3224
|
return {
|
|
3142
3225
|
previewHtml: "",
|
|
3143
3226
|
diagnostics: [{
|
|
@@ -3145,7 +3228,7 @@ async function checkComponent(componentId, variant, render, quarantineEntry) {
|
|
|
3145
3228
|
severity: "error",
|
|
3146
3229
|
source: "render",
|
|
3147
3230
|
message: e instanceof Error ? e.message : String(e),
|
|
3148
|
-
...
|
|
3231
|
+
...location2 && Object.keys(location2).length ? { location: location2 } : {}
|
|
3149
3232
|
}]
|
|
3150
3233
|
};
|
|
3151
3234
|
}
|
|
@@ -3232,7 +3315,8 @@ async function componentContentHash(id, discoveryMap, globalDataDigest2, checkCo
|
|
|
3232
3315
|
const entry = discoveryMap.get(id);
|
|
3233
3316
|
const hbs = entry?.hbsPath ? await readFile8(entry.hbsPath, "utf-8").catch(() => "") : "";
|
|
3234
3317
|
const json2 = entry?.jsonPath ? await readFile8(entry.jsonPath, "utf-8").catch(() => "") : "";
|
|
3235
|
-
|
|
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");
|
|
3236
3320
|
}
|
|
3237
3321
|
function globalDataDigest(globalData) {
|
|
3238
3322
|
return createHash4("sha1").update(JSON.stringify(globalData ?? {})).digest("hex");
|
|
@@ -3446,8 +3530,8 @@ async function runDiagnosticsAudit(config, cwd) {
|
|
|
3446
3530
|
}
|
|
3447
3531
|
|
|
3448
3532
|
// packages/dev-server/src/static-build.ts
|
|
3449
|
-
import { cp, mkdir as mkdir5, readdir as
|
|
3450
|
-
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";
|
|
3451
3535
|
|
|
3452
3536
|
// packages/dev-server/src/http-utils.ts
|
|
3453
3537
|
import { readFile as readFile9 } from "node:fs/promises";
|
|
@@ -3568,12 +3652,12 @@ function imageCacheDir(cwd) {
|
|
|
3568
3652
|
}
|
|
3569
3653
|
var caches = /* @__PURE__ */ new WeakMap();
|
|
3570
3654
|
var cacheFor = (cfg) => {
|
|
3571
|
-
let
|
|
3572
|
-
if (!
|
|
3573
|
-
|
|
3574
|
-
caches.set(cfg,
|
|
3655
|
+
let cache2 = caches.get(cfg);
|
|
3656
|
+
if (!cache2) {
|
|
3657
|
+
cache2 = createImageCache(imageCacheDir(cfg.cwd));
|
|
3658
|
+
caches.set(cfg, cache2);
|
|
3575
3659
|
}
|
|
3576
|
-
return
|
|
3660
|
+
return cache2;
|
|
3577
3661
|
};
|
|
3578
3662
|
async function variantSource(name, hash, width, encoding, cfg) {
|
|
3579
3663
|
const base2 = PD_ROUTE_PREFIX.assets + name.split("/").map(encodeURIComponent).join("/");
|
|
@@ -3611,11 +3695,11 @@ async function serveImageVariant(pathname, host, res, cfg) {
|
|
|
3611
3695
|
return;
|
|
3612
3696
|
}
|
|
3613
3697
|
if (variant.width > (await imageSize(sharp, source2)).width) return notFound();
|
|
3614
|
-
const
|
|
3615
|
-
let bytes = await
|
|
3698
|
+
const cache2 = cacheFor(cfg);
|
|
3699
|
+
let bytes = await cache2.read(variant.hash);
|
|
3616
3700
|
if (!bytes) {
|
|
3617
3701
|
bytes = await encodeWebp(sharp, source2, variant.width, encoding);
|
|
3618
|
-
await
|
|
3702
|
+
await cache2.write(variant.hash, bytes);
|
|
3619
3703
|
}
|
|
3620
3704
|
res.writeHead(200, { "Content-Type": "image/webp", "Cache-Control": "public, max-age=31536000, immutable" });
|
|
3621
3705
|
res.end(bytes);
|
|
@@ -3681,172 +3765,618 @@ function createSnapshotWriter({ outDir, basePath, origin, report, fetch: get = (
|
|
|
3681
3765
|
return { capture, store, failures };
|
|
3682
3766
|
}
|
|
3683
3767
|
|
|
3684
|
-
// packages/dev-server/src/storefront/
|
|
3685
|
-
import {
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
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() === "---") };
|
|
3700
3787
|
}
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
const problems = [];
|
|
3711
|
-
const siteProblems = /* @__PURE__ */ new Set();
|
|
3712
|
-
const list = await get(PD_ROUTES.pages);
|
|
3713
|
-
if (list.status !== 200) {
|
|
3714
|
-
const body = await list.text();
|
|
3715
|
-
let error = body;
|
|
3716
|
-
try {
|
|
3717
|
-
error = JSON.parse(body).error ?? body;
|
|
3718
|
-
} catch {
|
|
3719
|
-
}
|
|
3720
|
-
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: {} };
|
|
3721
3797
|
}
|
|
3722
|
-
const
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
const
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
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;
|
|
3731
3819
|
}
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
const res = await get(url);
|
|
3739
|
-
const type = res.headers.get("content-type") ?? "";
|
|
3740
|
-
if (res.status !== (url === PD_ROUTES.notFound ? 404 : 200)) {
|
|
3741
|
-
const body = await res.text();
|
|
3742
|
-
const items = [...body.matchAll(/<li( data-pd-site)?>([\s\S]*?)<\/li>/g)].map((m) => ({ site: !!m[1], text: unescapeHtml(m[2]) }));
|
|
3743
|
-
for (const i of items) {
|
|
3744
|
-
if (!i.site) problems.push(`${url}: ${i.text}`);
|
|
3745
|
-
else if (!siteProblems.has(i.text)) {
|
|
3746
|
-
siteProblems.add(i.text);
|
|
3747
|
-
problems.push(i.text);
|
|
3748
|
-
}
|
|
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()}`);
|
|
3749
3826
|
}
|
|
3750
|
-
|
|
3827
|
+
listKey = null;
|
|
3751
3828
|
continue;
|
|
3752
3829
|
}
|
|
3753
|
-
|
|
3754
|
-
if (
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
const lower = html.toLowerCase();
|
|
3771
|
-
for (let m = markup.exec(html); m; m = markup.exec(html)) {
|
|
3772
|
-
const name = m[1];
|
|
3773
|
-
if (name === void 0) continue;
|
|
3774
|
-
const tag = { name, start: m.index, end: markup.lastIndex, attrs: m[2] ?? "" };
|
|
3775
|
-
const key = name.toLowerCase();
|
|
3776
|
-
if (RAW_TEXT.has(key)) {
|
|
3777
|
-
const close = lower.indexOf(`</${key}`, tag.end);
|
|
3778
|
-
tag.content = { start: tag.end, end: close < 0 ? html.length : close };
|
|
3779
|
-
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;
|
|
3780
3847
|
}
|
|
3781
|
-
tags.push(tag);
|
|
3782
3848
|
}
|
|
3783
|
-
return
|
|
3849
|
+
return { values: out, errors, lines: keyLines };
|
|
3784
3850
|
}
|
|
3785
|
-
function
|
|
3786
|
-
|
|
3787
|
-
return q.replace(/&(#x[0-9a-f]+|#[0-9]+|amp|lt|gt|quot|apos|#39);/gi, (_, e) => {
|
|
3788
|
-
const k = e.toLowerCase();
|
|
3789
|
-
if (k.startsWith("#x")) return String.fromCodePoint(parseInt(k.slice(2), 16));
|
|
3790
|
-
if (k.startsWith("#")) return String.fromCodePoint(parseInt(k.slice(1), 10));
|
|
3791
|
-
return { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'" }[k] ?? `&${e};`;
|
|
3792
|
-
});
|
|
3851
|
+
function readFrontmatter(md2) {
|
|
3852
|
+
return scan(md2).values;
|
|
3793
3853
|
}
|
|
3794
|
-
function
|
|
3795
|
-
return
|
|
3854
|
+
function readFrontmatterStrict(md2) {
|
|
3855
|
+
return scan(md2);
|
|
3796
3856
|
}
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
var OTHER_HOST = /^(?:[a-z][a-z0-9+.-]*:)?\/\//i;
|
|
3801
|
-
function idsOf(html) {
|
|
3802
|
-
const ids = /* @__PURE__ */ new Set();
|
|
3803
|
-
for (const tag of startTags(html)) {
|
|
3804
|
-
for (const a of attributesOf(tag)) {
|
|
3805
|
-
if (a.value === void 0) continue;
|
|
3806
|
-
if (a.name === "id" || a.name === "name" && tag.name.toLowerCase() === "a") ids.add(a.value);
|
|
3807
|
-
}
|
|
3808
|
-
}
|
|
3809
|
-
return ids;
|
|
3857
|
+
function frontmatterBodyOffset(md2) {
|
|
3858
|
+
const { end } = block(md2);
|
|
3859
|
+
return end === null || end < 0 ? 0 : end + 1;
|
|
3810
3860
|
}
|
|
3811
|
-
function
|
|
3812
|
-
const
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
if (a.name === "href" || a.name === "src") refs.push([tag.name.toLowerCase(), a.name, a.value.trim()]);
|
|
3817
|
-
else if (a.name === "srcset") {
|
|
3818
|
-
for (const c3 of a.value.split(",")) {
|
|
3819
|
-
const url = c3.trim().split(/\s+/)[0];
|
|
3820
|
-
if (url) refs.push([tag.name.toLowerCase(), a.name, url]);
|
|
3821
|
-
}
|
|
3822
|
-
}
|
|
3823
|
-
}
|
|
3824
|
-
}
|
|
3825
|
-
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");
|
|
3826
3866
|
}
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
const path = new URL(ref, `http://site.invalid${page}`).pathname;
|
|
3836
|
-
if (path.startsWith(workbenchUrl)) rest = path.slice(workbenchUrl.length);
|
|
3837
|
-
}
|
|
3838
|
-
if (rest !== void 0) routes.add(decodeURIComponent(rest.split(/[?#]/)[0].replace(/\/$/, "")));
|
|
3839
|
-
}
|
|
3840
|
-
}
|
|
3841
|
-
return routes;
|
|
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;
|
|
3842
3875
|
}
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
const
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
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);
|
|
4364
|
+
} else if (!OTHER_HOST.test(ref)) {
|
|
4365
|
+
const path = new URL(ref, `http://site.invalid${page}`).pathname;
|
|
4366
|
+
if (path.startsWith(workbenchUrl)) rest = path.slice(workbenchUrl.length);
|
|
4367
|
+
}
|
|
4368
|
+
if (rest !== void 0) routes.add(decodeURIComponent(rest.split(/[?#]/)[0].replace(/\/$/, "")));
|
|
4369
|
+
}
|
|
4370
|
+
}
|
|
4371
|
+
return routes;
|
|
4372
|
+
}
|
|
4373
|
+
function checkLinks(site) {
|
|
4374
|
+
const problems = [];
|
|
4375
|
+
const external = /* @__PURE__ */ new Map();
|
|
4376
|
+
const idCache = /* @__PURE__ */ new Map();
|
|
4377
|
+
const ids = (url) => {
|
|
4378
|
+
const html = site.documents.get(url);
|
|
4379
|
+
if (html === void 0) return void 0;
|
|
3850
4380
|
let found = idCache.get(url);
|
|
3851
4381
|
if (!found) idCache.set(url, found = idsOf(html));
|
|
3852
4382
|
return found;
|
|
@@ -3867,6 +4397,8 @@ function checkLinks(site) {
|
|
|
3867
4397
|
if (hash && !(wb.variants.get(route) ?? []).includes(decodeURIComponent(hash))) return `${route} has no variant "${hash}" in the workbench`;
|
|
3868
4398
|
return void 0;
|
|
3869
4399
|
};
|
|
4400
|
+
const base2 = site.basePath ?? "/";
|
|
4401
|
+
const hint = site.pathHint ?? base2;
|
|
3870
4402
|
let references = 0;
|
|
3871
4403
|
for (const page of site.pages) {
|
|
3872
4404
|
const html = site.documents.get(page);
|
|
@@ -3882,7 +4414,7 @@ function checkLinks(site) {
|
|
|
3882
4414
|
continue;
|
|
3883
4415
|
}
|
|
3884
4416
|
let href = ref;
|
|
3885
|
-
if (site.siteUrl && ref.startsWith(site.siteUrl)) href =
|
|
4417
|
+
if (site.siteUrl && ref.startsWith(site.siteUrl)) href = `${base2}${ref.slice(site.siteUrl.length)}`;
|
|
3886
4418
|
else if (OTHER_HOST.test(ref)) {
|
|
3887
4419
|
if (site.pagesOnly) continue;
|
|
3888
4420
|
const list = external.get(ref) ?? [];
|
|
@@ -3890,18 +4422,24 @@ function checkLinks(site) {
|
|
|
3890
4422
|
external.set(ref, list);
|
|
3891
4423
|
continue;
|
|
3892
4424
|
}
|
|
3893
|
-
const url = new URL(href, `http://site.invalid${page}`);
|
|
3894
|
-
const
|
|
4425
|
+
const url = new URL(href, `http://site.invalid${base2}${page.slice(1)}`);
|
|
4426
|
+
const served = decodeURIComponent(url.pathname);
|
|
3895
4427
|
const hash = url.hash.slice(1);
|
|
3896
|
-
if (wb && wb.url.startsWith("/") &&
|
|
3897
|
-
const why = workbenchProblem(
|
|
4428
|
+
if (wb && wb.url.startsWith("/") && served.startsWith(wb.url)) {
|
|
4429
|
+
const why = workbenchProblem(served.slice(wb.url.length), hash);
|
|
3898
4430
|
if (why) say(why);
|
|
3899
4431
|
continue;
|
|
3900
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)}`;
|
|
3901
4438
|
if (site.pagesOnly && /^\/(?:_pd|assets|preview)\//.test(path)) continue;
|
|
3902
4439
|
const target = builtAs(path);
|
|
3903
4440
|
if (target === void 0) {
|
|
3904
|
-
|
|
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}`);
|
|
3905
4443
|
continue;
|
|
3906
4444
|
}
|
|
3907
4445
|
if (!hash) continue;
|
|
@@ -3928,6 +4466,135 @@ function checkPageBasics(html) {
|
|
|
3928
4466
|
return problems;
|
|
3929
4467
|
}
|
|
3930
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
|
+
|
|
3931
4598
|
// packages/dev-server/src/storefront/site-files.ts
|
|
3932
4599
|
var escapeXml = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
3933
4600
|
function sitemapXml(entries) {
|
|
@@ -3954,6 +4621,68 @@ Sitemap: ${p.siteUrl}sitemap.xml
|
|
|
3954
4621
|
` : ""}`;
|
|
3955
4622
|
}
|
|
3956
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
|
+
|
|
3957
4686
|
// packages/dev-server/src/static-build.ts
|
|
3958
4687
|
var notABarrier = (path) => basename4(path) !== ".__testOnly_barrier";
|
|
3959
4688
|
async function buildStaticSite(config, root, opts) {
|
|
@@ -3966,8 +4695,8 @@ async function buildStaticSite(config, root, opts) {
|
|
|
3966
4695
|
const outDir = resolve7(opts.outDir);
|
|
3967
4696
|
const cfg = resolveServerConfig(config, root, {}, loadPatternPackages(config.packages, root));
|
|
3968
4697
|
const inside2 = (child, parent) => {
|
|
3969
|
-
const rel =
|
|
3970
|
-
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);
|
|
3971
4700
|
};
|
|
3972
4701
|
const project = resolve7(root);
|
|
3973
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`);
|
|
@@ -3996,6 +4725,7 @@ async function buildStaticSite(config, root, opts) {
|
|
|
3996
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}`);
|
|
3997
4726
|
}
|
|
3998
4727
|
}
|
|
4728
|
+
const playwright = opts.browser && storefront ? await (opts.loadPlaywright ?? loadProjectPlaywright)(project) : void 0;
|
|
3999
4729
|
const fixed = opts.now;
|
|
4000
4730
|
const now = fixed ? () => new Date(fixed.getTime()) : () => /* @__PURE__ */ new Date();
|
|
4001
4731
|
const storefrontNotes = /* @__PURE__ */ new Set();
|
|
@@ -4009,9 +4739,14 @@ async function buildStaticSite(config, root, opts) {
|
|
|
4009
4739
|
}
|
|
4010
4740
|
});
|
|
4011
4741
|
log("\u2026 server ready");
|
|
4012
|
-
const
|
|
4013
|
-
const
|
|
4014
|
-
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;
|
|
4015
4750
|
if (sfCrawl && sfCrawl.problems.length) {
|
|
4016
4751
|
await server.close();
|
|
4017
4752
|
throw new Error(`pd build: the storefront has problems, so nothing was written:
|
|
@@ -4021,6 +4756,7 @@ ${sfCrawl.problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
4021
4756
|
const origin = `http://127.0.0.1:${server.port}`;
|
|
4022
4757
|
let linkSummary;
|
|
4023
4758
|
let a11ySummary;
|
|
4759
|
+
let imageSummary;
|
|
4024
4760
|
const externalNotes = [];
|
|
4025
4761
|
if (storefront && sfGet && sfCrawl) {
|
|
4026
4762
|
const { pages: list } = await (await sfGet(PD_ROUTES.pages)).json();
|
|
@@ -4048,6 +4784,7 @@ ${sfCrawl.problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
4048
4784
|
documents: sfCrawl.documents,
|
|
4049
4785
|
pages,
|
|
4050
4786
|
...storefront.url !== void 0 ? { siteUrl: storefront.url.endsWith("/") ? storefront.url : `${storefront.url}/` } : {},
|
|
4787
|
+
basePath: sfBase,
|
|
4051
4788
|
workbench: { url: wbUrl, routes, variants }
|
|
4052
4789
|
});
|
|
4053
4790
|
if (links.problems.length) {
|
|
@@ -4081,15 +4818,34 @@ ${links.problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
4081
4818
|
throw new Error(`pd build: the storefront has accessibility problems, so nothing was written:
|
|
4082
4819
|
${errors.map((e) => ` - ${e}`).join("\n")}`);
|
|
4083
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;
|
|
4084
4838
|
a11ySummary = {
|
|
4085
4839
|
pages: pages.length,
|
|
4086
|
-
|
|
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]
|
|
4087
4842
|
};
|
|
4088
4843
|
log(`\u2026 accessibility checked on ${pages.length} pages`);
|
|
4089
4844
|
}
|
|
4090
4845
|
await rm3(outDir, { recursive: true, force: true });
|
|
4091
4846
|
await mkdir5(outDir, { recursive: true });
|
|
4092
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");
|
|
4093
4849
|
const { capture, store } = createSnapshotWriter({ outDir, basePath, origin, report });
|
|
4094
4850
|
const referenced = (html) => [...html.matchAll(/(?:href|src)="(\/_pd\/(?:components|themes|scripts)\/[^"]+)"/g)].map((m) => m[1].replace(/&/g, "&"));
|
|
4095
4851
|
try {
|
|
@@ -4128,9 +4884,9 @@ ${errors.map((e) => ` - ${e}`).join("\n")}`);
|
|
|
4128
4884
|
for (const variant of [void 0, ...base2.variants.map((v) => v.name)]) {
|
|
4129
4885
|
const detailText = variant ? await capture(componentUrl(id, variant)) : baseText;
|
|
4130
4886
|
if (detailText) {
|
|
4131
|
-
const { document:
|
|
4132
|
-
await store(previewUrl(id, variant),
|
|
4133
|
-
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);
|
|
4134
4890
|
}
|
|
4135
4891
|
await capture(renderedSourceUrl(id, variant), { optional: true });
|
|
4136
4892
|
await capture(dataSourceUrl(id, variant), { optional: true });
|
|
@@ -4152,13 +4908,13 @@ ${errors.map((e) => ` - ${e}`).join("\n")}`);
|
|
|
4152
4908
|
for (const [i, rootDir] of cfg.assetRoots.entries()) {
|
|
4153
4909
|
const by = shadowed.get(rootDir);
|
|
4154
4910
|
if (by !== void 0) {
|
|
4155
|
-
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`);
|
|
4156
4912
|
continue;
|
|
4157
4913
|
}
|
|
4158
|
-
const exists = await
|
|
4914
|
+
const exists = await stat5(rootDir).then((s) => s.isDirectory(), () => false);
|
|
4159
4915
|
if (!exists) continue;
|
|
4160
|
-
await cp(rootDir,
|
|
4161
|
-
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 });
|
|
4162
4918
|
}
|
|
4163
4919
|
const runtime = {
|
|
4164
4920
|
mode: "static",
|
|
@@ -4174,12 +4930,12 @@ ${errors.map((e) => ` - ${e}`).join("\n")}`);
|
|
|
4174
4930
|
report.warnings.push(`shell page for route ${route} not written: ${staticPathFor(route)} already holds the server's response to ${holder}`);
|
|
4175
4931
|
}
|
|
4176
4932
|
}
|
|
4177
|
-
await writeFile5(
|
|
4933
|
+
await writeFile5(join19(outDir, ".nojekyll"), "");
|
|
4178
4934
|
report.files++;
|
|
4179
|
-
await writeFile5(
|
|
4935
|
+
await writeFile5(join19(outDir, "robots.txt"), workbenchRobotsTxt(basePath));
|
|
4180
4936
|
report.files++;
|
|
4181
|
-
if (basePath !== "/" && sameHostWorkbenchPath(storefront ?? void 0) !== basePath) {
|
|
4182
|
-
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 /`);
|
|
4183
4939
|
}
|
|
4184
4940
|
if (storefront && sfGet && sfCrawl) {
|
|
4185
4941
|
const sfOut = storefront.outDir;
|
|
@@ -4192,22 +4948,34 @@ ${errors.map((e) => ` - ${e}`).join("\n")}`);
|
|
|
4192
4948
|
throw new Error(`pd build: the storefront changed while it was being written:
|
|
4193
4949
|
${sf.failures.map((f) => ` - ${f}`).join("\n")}`);
|
|
4194
4950
|
}
|
|
4195
|
-
await writeFile5(
|
|
4951
|
+
await writeFile5(join19(sfOut, ".nojekyll"), "");
|
|
4196
4952
|
counts.files++;
|
|
4197
4953
|
const stubs = new Set((await (await sfGet(PD_ROUTES.pages)).json()).pages.flatMap((p) => p.redirectFrom ?? []));
|
|
4198
4954
|
const pages = sfCrawl.urls.filter((u) => !/^\/(?:_pd|assets|preview)\//.test(u) && !sfFiles.includes(u) && !stubs.has(u)).length;
|
|
4199
|
-
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
|
+
}
|
|
4200
4965
|
if (storefront.url === void 0) {
|
|
4201
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/"`);
|
|
4202
4967
|
}
|
|
4203
4968
|
report.notes.push(...storefrontNotes, ...externalNotes);
|
|
4204
4969
|
if (linkSummary) report.links = linkSummary;
|
|
4205
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;
|
|
4206
4974
|
log(`\u2026 storefront: ${pages} pages, ${counts.files} files`);
|
|
4207
4975
|
}
|
|
4208
4976
|
const used = /* @__PURE__ */ new Set();
|
|
4209
4977
|
for (const root2 of [outDir, ...storefront ? [storefront.outDir] : []]) {
|
|
4210
|
-
const files = await
|
|
4978
|
+
const files = await readdir8(join19(root2, "_pd", "images"), { recursive: true }).catch(() => []);
|
|
4211
4979
|
for (const f of files) {
|
|
4212
4980
|
const m = /\.([0-9a-f]{10})\.[1-9][0-9]*\.webp$/.exec(String(f));
|
|
4213
4981
|
if (m) used.add(m[1]);
|
|
@@ -4222,19 +4990,19 @@ ${sf.failures.map((f) => ` - ${f}`).join("\n")}`);
|
|
|
4222
4990
|
}
|
|
4223
4991
|
|
|
4224
4992
|
// packages/dev-server/src/requires.ts
|
|
4225
|
-
import { readFile as
|
|
4993
|
+
import { readFile as readFile12 } from "node:fs/promises";
|
|
4226
4994
|
import { existsSync as existsSync4 } from "node:fs";
|
|
4227
|
-
import { basename as basename5, join as
|
|
4995
|
+
import { basename as basename5, join as join20, relative as relative11 } from "node:path";
|
|
4228
4996
|
function normalizeScriptId(raw) {
|
|
4229
4997
|
let s = raw.trim().replace(/^\[(.+)\]$/, "$1").trim();
|
|
4230
4998
|
s = basename5(s);
|
|
4231
4999
|
s = s.replace(/\.(ts|js)$/, "");
|
|
4232
5000
|
return s;
|
|
4233
5001
|
}
|
|
4234
|
-
function parseFrontmatterList(
|
|
5002
|
+
function parseFrontmatterList(md2, key) {
|
|
4235
5003
|
const values = [];
|
|
4236
5004
|
const re = new RegExp(`^${key}:\\s*(.+)$`, "mg");
|
|
4237
|
-
for (const [, val] of
|
|
5005
|
+
for (const [, val] of md2.matchAll(re)) {
|
|
4238
5006
|
values.push(val.trim());
|
|
4239
5007
|
}
|
|
4240
5008
|
return values;
|
|
@@ -4247,9 +5015,9 @@ async function aggregateRequires(componentId, patternsDir, flatNameToPath, disco
|
|
|
4247
5015
|
let ownAssets = [];
|
|
4248
5016
|
if (entry?.mdPath) {
|
|
4249
5017
|
try {
|
|
4250
|
-
const
|
|
4251
|
-
ownScripts = parseFrontmatterList(
|
|
4252
|
-
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());
|
|
4253
5021
|
} catch {
|
|
4254
5022
|
}
|
|
4255
5023
|
}
|
|
@@ -4258,7 +5026,7 @@ async function aggregateRequires(componentId, patternsDir, flatNameToPath, disco
|
|
|
4258
5026
|
const hbsPath = entry?.hbsPath ?? "";
|
|
4259
5027
|
let hbs = "";
|
|
4260
5028
|
try {
|
|
4261
|
-
hbs = hbsPath ? await
|
|
5029
|
+
hbs = hbsPath ? await readFile12(hbsPath, "utf-8") : "";
|
|
4262
5030
|
} catch {
|
|
4263
5031
|
}
|
|
4264
5032
|
for (const flatName of parsePartialsFromHbs(hbs)) {
|
|
@@ -4279,7 +5047,7 @@ async function loadSvgAssets(ids, assetsDir) {
|
|
|
4279
5047
|
for (const id of ids) {
|
|
4280
5048
|
for (const candidate of [`${id}.svg`, `images/${id}.svg`]) {
|
|
4281
5049
|
try {
|
|
4282
|
-
const raw = await
|
|
5050
|
+
const raw = await readFile12(join20(assetsDir, candidate), "utf-8");
|
|
4283
5051
|
contents.push(hideSvg(raw));
|
|
4284
5052
|
break;
|
|
4285
5053
|
} catch {
|
|
@@ -4292,21 +5060,21 @@ function hideSvg(svg) {
|
|
|
4292
5060
|
return svg.replace(/<svg(\s|>)/, '<svg$1style="display:none;position:absolute" aria-hidden="true" ');
|
|
4293
5061
|
}
|
|
4294
5062
|
async function reportUnresolvableScripts(discoveryMap, scriptsDirs, globalScripts, cwd) {
|
|
4295
|
-
const resolves = (id) => scriptsDirs.some((d) => existsSync4(
|
|
4296
|
-
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";
|
|
4297
5065
|
for (const id of globalScripts) {
|
|
4298
5066
|
if (id.includes("://") || resolves(normalizeScriptId(id))) continue;
|
|
4299
5067
|
console.warn(`[dev-server] globalScripts "${id}": no ${normalizeScriptId(id)}.ts in ${where}`);
|
|
4300
5068
|
}
|
|
4301
5069
|
for (const [componentId, entry] of discoveryMap) {
|
|
4302
5070
|
if (!entry.mdPath) continue;
|
|
4303
|
-
let
|
|
5071
|
+
let md2;
|
|
4304
5072
|
try {
|
|
4305
|
-
|
|
5073
|
+
md2 = await readFile12(entry.mdPath, "utf-8");
|
|
4306
5074
|
} catch {
|
|
4307
5075
|
continue;
|
|
4308
5076
|
}
|
|
4309
|
-
for (const raw of parseFrontmatterList(
|
|
5077
|
+
for (const raw of parseFrontmatterList(md2, "requireScript")) {
|
|
4310
5078
|
const id = normalizeScriptId(raw);
|
|
4311
5079
|
if (resolves(id)) continue;
|
|
4312
5080
|
console.warn(`[dev-server] requireScript "${raw}" on ${componentId}: no ${id}.ts in ${where}`);
|
|
@@ -4423,6 +5191,7 @@ function createServerState() {
|
|
|
4423
5191
|
baseProvenance: null,
|
|
4424
5192
|
perComponentCss: /* @__PURE__ */ new Map(),
|
|
4425
5193
|
baseCss: "",
|
|
5194
|
+
entryBaseRules: /* @__PURE__ */ new Map(),
|
|
4426
5195
|
baseCssVersion: 0,
|
|
4427
5196
|
discovery: createLazy("discovery", { slow: { afterMs: DISCOVERY_SLOW_MS, what: "the component index" } }),
|
|
4428
5197
|
audit: createLazy("audit"),
|
|
@@ -4453,6 +5222,7 @@ function applyStyleData(state, data) {
|
|
|
4453
5222
|
state.baseProvenance = data.baseProvenance;
|
|
4454
5223
|
state.perComponentCss = data.componentCssMap;
|
|
4455
5224
|
state.baseCss = data.baseCss;
|
|
5225
|
+
state.entryBaseRules = new Map(data.baseRulesByEntry);
|
|
4456
5226
|
return { changedComponents: changedComponents.sort(), baseChanged };
|
|
4457
5227
|
}
|
|
4458
5228
|
function scriptFor(state, scriptId) {
|
|
@@ -4498,24 +5268,24 @@ function cachedDiscovery(state) {
|
|
|
4498
5268
|
}
|
|
4499
5269
|
|
|
4500
5270
|
// packages/dev-server/src/routes.ts
|
|
4501
|
-
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";
|
|
4502
5272
|
import { readFile as readFile24 } from "node:fs/promises";
|
|
4503
5273
|
|
|
4504
5274
|
// packages/dev-server/src/component-tree.ts
|
|
4505
|
-
import { readFile as
|
|
4506
|
-
import { join as
|
|
5275
|
+
import { readFile as readFile15 } from "node:fs/promises";
|
|
5276
|
+
import { join as join23 } from "node:path";
|
|
4507
5277
|
|
|
4508
5278
|
// packages/dev-server/src/layout-mode.ts
|
|
4509
|
-
import { readFile as
|
|
4510
|
-
import { join as
|
|
5279
|
+
import { readFile as readFile14 } from "node:fs/promises";
|
|
5280
|
+
import { join as join22 } from "node:path";
|
|
4511
5281
|
|
|
4512
5282
|
// packages/dev-server/src/pattern-dirs.ts
|
|
4513
|
-
import { readFile as
|
|
4514
|
-
import { join as
|
|
5283
|
+
import { readFile as readFile13 } from "node:fs/promises";
|
|
5284
|
+
import { join as join21 } from "node:path";
|
|
4515
5285
|
async function readFolderFile(dirs, rel) {
|
|
4516
5286
|
for (const dir of typeof dirs === "string" ? [dirs] : dirs) {
|
|
4517
5287
|
try {
|
|
4518
|
-
return await
|
|
5288
|
+
return await readFile13(join21(dir, rel), "utf-8");
|
|
4519
5289
|
} catch {
|
|
4520
5290
|
}
|
|
4521
5291
|
}
|
|
@@ -4528,7 +5298,7 @@ async function resolveLayoutMode(componentId, patternsDir, discoveryMap) {
|
|
|
4528
5298
|
const parts = componentId.split("/");
|
|
4529
5299
|
const category = parts[0];
|
|
4530
5300
|
let layout = FULL_CATEGORIES.has(stripOrderPrefixes(category)) ? "full" : "centered";
|
|
4531
|
-
const categoryIndex = await readFolderFile(patternsDir,
|
|
5301
|
+
const categoryIndex = await readFolderFile(patternsDir, join22(category, "index.md"));
|
|
4532
5302
|
if (categoryIndex !== null) {
|
|
4533
5303
|
const m = categoryIndex.match(/^preview:\s*(centered|full)$/m);
|
|
4534
5304
|
if (m) layout = m[1];
|
|
@@ -4536,8 +5306,8 @@ async function resolveLayoutMode(componentId, patternsDir, discoveryMap) {
|
|
|
4536
5306
|
const mdPath = discoveryMap?.get(componentId)?.mdPath;
|
|
4537
5307
|
if (mdPath) {
|
|
4538
5308
|
try {
|
|
4539
|
-
const
|
|
4540
|
-
const m =
|
|
5309
|
+
const md2 = await readFile14(mdPath, "utf-8");
|
|
5310
|
+
const m = md2.match(/^preview:\s*(centered|full)$/m);
|
|
4541
5311
|
if (m) layout = m[1];
|
|
4542
5312
|
} catch {
|
|
4543
5313
|
}
|
|
@@ -4545,130 +5315,6 @@ async function resolveLayoutMode(componentId, patternsDir, discoveryMap) {
|
|
|
4545
5315
|
return layout;
|
|
4546
5316
|
}
|
|
4547
5317
|
|
|
4548
|
-
// packages/dev-server/src/frontmatter.ts
|
|
4549
|
-
var BOM = "\uFEFF";
|
|
4550
|
-
var unquote = (s) => {
|
|
4551
|
-
const t = s.trim();
|
|
4552
|
-
return t.length >= 2 && (t[0] === '"' || t[0] === "'") && t.at(-1) === t[0] ? t.slice(1, -1) : t;
|
|
4553
|
-
};
|
|
4554
|
-
var unterminatedQuote = (s) => {
|
|
4555
|
-
const t = s.trim();
|
|
4556
|
-
return (t[0] === '"' || t[0] === "'") && (t.length < 2 || t.at(-1) !== t[0]);
|
|
4557
|
-
};
|
|
4558
|
-
function block(md) {
|
|
4559
|
-
const text = md.startsWith(BOM) ? md.slice(1) : md;
|
|
4560
|
-
const lines = text.split(/\r?\n/);
|
|
4561
|
-
if (lines[0]?.trim() !== "---") return { text, lines, end: null };
|
|
4562
|
-
return { text, lines, end: lines.findIndex((l, i) => i > 0 && l.trim() === "---") };
|
|
4563
|
-
}
|
|
4564
|
-
function scan(md) {
|
|
4565
|
-
const errors = [];
|
|
4566
|
-
const err = (line, message) => {
|
|
4567
|
-
errors.push({ line, message });
|
|
4568
|
-
};
|
|
4569
|
-
const { text, lines, end: blockEnd } = block(md);
|
|
4570
|
-
if (blockEnd === null) {
|
|
4571
|
-
if (/^---\r(?!\n)/.test(text)) err(1, "lines end in a bare CR \u2014 use LF or CRLF line endings");
|
|
4572
|
-
return { values: {}, errors, lines: {} };
|
|
4573
|
-
}
|
|
4574
|
-
const end = blockEnd;
|
|
4575
|
-
if (end < 0) {
|
|
4576
|
-
err(1, "the frontmatter block opened here is never closed \u2014 end it with a line holding only ---");
|
|
4577
|
-
return { values: {}, errors, lines: {} };
|
|
4578
|
-
}
|
|
4579
|
-
const out = {};
|
|
4580
|
-
const keyLines = {};
|
|
4581
|
-
let listKey = null;
|
|
4582
|
-
for (let i = 1; i < end; i++) {
|
|
4583
|
-
const line = lines[i];
|
|
4584
|
-
const n = i + 1;
|
|
4585
|
-
const bareCr = line.includes("\r");
|
|
4586
|
-
if (bareCr) err(n, "line ends in a bare CR \u2014 use LF or CRLF line endings");
|
|
4587
|
-
const lineErr = (message) => {
|
|
4588
|
-
if (!bareCr) err(n, message);
|
|
4589
|
-
};
|
|
4590
|
-
const item = line.match(/^\s*-\s+(.*)$/);
|
|
4591
|
-
if (item && listKey) {
|
|
4592
|
-
if (unterminatedQuote(item[1])) lineErr(`list item ${item[1].trim()} has an opening quote with no closing one`);
|
|
4593
|
-
out[listKey].push(unquote(item[1]));
|
|
4594
|
-
continue;
|
|
4595
|
-
}
|
|
4596
|
-
const kv = line.match(/^([A-Za-z_][\w-]*):\s*(.*)$/);
|
|
4597
|
-
if (!kv) {
|
|
4598
|
-
if (line.trim() !== "") {
|
|
4599
|
-
if (item) lineErr("list item with no key above it \u2014 a list follows a line `key:`");
|
|
4600
|
-
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");
|
|
4601
|
-
else lineErr(`cannot read this line: ${line.trim()}`);
|
|
4602
|
-
}
|
|
4603
|
-
listKey = null;
|
|
4604
|
-
continue;
|
|
4605
|
-
}
|
|
4606
|
-
const [, key, raw] = kv;
|
|
4607
|
-
if (key in out) lineErr(`duplicate key "${key}"`);
|
|
4608
|
-
keyLines[key] = n;
|
|
4609
|
-
const value = raw.trim();
|
|
4610
|
-
if (value === "") {
|
|
4611
|
-
out[key] = [];
|
|
4612
|
-
listKey = key;
|
|
4613
|
-
} else if (value.startsWith("[") && value.endsWith("]")) {
|
|
4614
|
-
const items = value.slice(1, -1).split(",");
|
|
4615
|
-
for (const it of items) if (unterminatedQuote(it)) lineErr(`list item ${it.trim()} has an opening quote with no closing one`);
|
|
4616
|
-
out[key] = items.map(unquote).filter(Boolean);
|
|
4617
|
-
listKey = null;
|
|
4618
|
-
} else {
|
|
4619
|
-
if (value.startsWith("[")) lineErr(`"${key}": the list opened with [ is never closed with ]`);
|
|
4620
|
-
else if (unterminatedQuote(value)) lineErr(`"${key}": the value has an opening quote with no closing one`);
|
|
4621
|
-
out[key] = unquote(value);
|
|
4622
|
-
listKey = null;
|
|
4623
|
-
}
|
|
4624
|
-
}
|
|
4625
|
-
return { values: out, errors, lines: keyLines };
|
|
4626
|
-
}
|
|
4627
|
-
function readFrontmatter(md) {
|
|
4628
|
-
return scan(md).values;
|
|
4629
|
-
}
|
|
4630
|
-
function readFrontmatterStrict(md) {
|
|
4631
|
-
return scan(md);
|
|
4632
|
-
}
|
|
4633
|
-
function frontmatterBodyOffset(md) {
|
|
4634
|
-
const { end } = block(md);
|
|
4635
|
-
return end === null || end < 0 ? 0 : end + 1;
|
|
4636
|
-
}
|
|
4637
|
-
function frontmatterBody(md) {
|
|
4638
|
-
const { text, lines, end } = block(md);
|
|
4639
|
-
if (end === null) return text;
|
|
4640
|
-
if (end < 0) return "";
|
|
4641
|
-
return lines.slice(end + 1).join("\n");
|
|
4642
|
-
}
|
|
4643
|
-
|
|
4644
|
-
// packages/dev-server/src/tree-order.ts
|
|
4645
|
-
function parseOrder(value) {
|
|
4646
|
-
if (value === void 0) return void 0;
|
|
4647
|
-
if (Array.isArray(value)) return { list: value };
|
|
4648
|
-
if (/^-?\d+(\.\d+)?$/.test(value)) return { weight: Number(value) };
|
|
4649
|
-
const list = value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
4650
|
-
return list.length ? { list } : void 0;
|
|
4651
|
-
}
|
|
4652
|
-
var collator = new Intl.Collator("en", { numeric: true });
|
|
4653
|
-
var REST = "...";
|
|
4654
|
-
function orderSiblings(items, list) {
|
|
4655
|
-
const position = /* @__PURE__ */ new Map();
|
|
4656
|
-
list?.forEach((name, i) => {
|
|
4657
|
-
if (!position.has(name)) position.set(name, i);
|
|
4658
|
-
});
|
|
4659
|
-
const restPosition = position.get(REST) ?? Infinity;
|
|
4660
|
-
const positionOf2 = (item) => list ? position.get(item.segment) ?? restPosition : 0;
|
|
4661
|
-
return [...items].sort((a, b) => {
|
|
4662
|
-
const ra = a.weight ?? 0;
|
|
4663
|
-
const rb = b.weight ?? 0;
|
|
4664
|
-
if (ra !== rb) return ra - rb;
|
|
4665
|
-
const pa = positionOf2(a);
|
|
4666
|
-
const pb = positionOf2(b);
|
|
4667
|
-
if (pa !== pb) return pa - pb;
|
|
4668
|
-
return collator.compare(a.segment, b.segment) || (a.segment < b.segment ? -1 : a.segment > b.segment ? 1 : 0);
|
|
4669
|
-
});
|
|
4670
|
-
}
|
|
4671
|
-
|
|
4672
5318
|
// packages/dev-server/src/component-tree.ts
|
|
4673
5319
|
function directChildPaths(parentPath, allIds) {
|
|
4674
5320
|
const prefix = `${parentPath}/`;
|
|
@@ -4686,9 +5332,9 @@ function folderTitle(segment) {
|
|
|
4686
5332
|
const text = words.join(" ");
|
|
4687
5333
|
return text.charAt(0).toUpperCase() + text.slice(1);
|
|
4688
5334
|
}
|
|
4689
|
-
var readText = (path) =>
|
|
4690
|
-
function orderOf(
|
|
4691
|
-
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"]);
|
|
4692
5338
|
}
|
|
4693
5339
|
function hasPublicComponent(node) {
|
|
4694
5340
|
if (node.kind === "component" && node.visibility !== "private") return true;
|
|
@@ -4722,22 +5368,22 @@ async function buildTreeNodeFromMap(nodePath, idsUnderPath, patternsDir, overvie
|
|
|
4722
5368
|
let visibility;
|
|
4723
5369
|
let weight;
|
|
4724
5370
|
if (isComponent && entry.mdPath) {
|
|
4725
|
-
const
|
|
4726
|
-
if (
|
|
4727
|
-
const t =
|
|
4728
|
-
const s =
|
|
4729
|
-
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);
|
|
4730
5376
|
if (t) title = t[1].trim();
|
|
4731
5377
|
if (s) {
|
|
4732
5378
|
const sv = s[1].trim();
|
|
4733
5379
|
if (sv === "stable" || sv === "deprecated" || sv === "draft") status = sv;
|
|
4734
5380
|
}
|
|
4735
5381
|
if (v) visibility = v[1];
|
|
4736
|
-
const o = orderOf(
|
|
5382
|
+
const o = orderOf(md2);
|
|
4737
5383
|
if (o && "weight" in o) weight = o.weight;
|
|
4738
5384
|
}
|
|
4739
5385
|
}
|
|
4740
|
-
const indexMd = await readFolderFile(patternsDir,
|
|
5386
|
+
const indexMd = await readFolderFile(patternsDir, join23(nodePath, "index.md"));
|
|
4741
5387
|
const folderOrder = orderOf(indexMd);
|
|
4742
5388
|
const built = [];
|
|
4743
5389
|
for (const childPath of directChildPaths(nodePath, idsUnderPath)) {
|
|
@@ -4787,10 +5433,10 @@ import { readFile as readFile21 } from "node:fs/promises";
|
|
|
4787
5433
|
// packages/dev-server/src/markdown.ts
|
|
4788
5434
|
import { readFile as readFile19 } from "node:fs/promises";
|
|
4789
5435
|
import { basename as basename8 } from "node:path";
|
|
4790
|
-
import
|
|
5436
|
+
import MarkdownIt4 from "markdown-it";
|
|
4791
5437
|
|
|
4792
5438
|
// packages/dev-server/src/placed-tags.ts
|
|
4793
|
-
import
|
|
5439
|
+
import MarkdownIt2 from "markdown-it";
|
|
4794
5440
|
var OPEN = /^<(pd-[a-z0-9-]+(?:~[a-z0-9-]+)?)/;
|
|
4795
5441
|
var CLOSE = /^<\/(pd-[a-z0-9-~]+)\s*>/;
|
|
4796
5442
|
var ATTR = /^\s+([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/;
|
|
@@ -4874,9 +5520,9 @@ function placedTagRule(state, startLine, endLine, silent) {
|
|
|
4874
5520
|
state.line = Math.min(line + 1, endLine);
|
|
4875
5521
|
return true;
|
|
4876
5522
|
}
|
|
4877
|
-
function usePlacedTags(
|
|
4878
|
-
|
|
4879
|
-
return
|
|
5523
|
+
function usePlacedTags(md2) {
|
|
5524
|
+
md2.block.ruler.before("paragraph", "pd_component", placedTagRule, { alt: ["paragraph", "blockquote", "list"] });
|
|
5525
|
+
return md2;
|
|
4880
5526
|
}
|
|
4881
5527
|
var camel = (s) => s.replace(/-([a-z0-9])/g, (_, c3) => c3.toUpperCase());
|
|
4882
5528
|
function dedent(s) {
|
|
@@ -4942,10 +5588,10 @@ async function toPlaced(tag, resolver, problems) {
|
|
|
4942
5588
|
// packages/dev-server/src/storefront/placed.ts
|
|
4943
5589
|
import { readFile as readFile18 } from "node:fs/promises";
|
|
4944
5590
|
import { basename as basename7 } from "node:path";
|
|
4945
|
-
import
|
|
5591
|
+
import MarkdownIt3 from "markdown-it";
|
|
4946
5592
|
|
|
4947
5593
|
// packages/dev-server/src/storefront/declared-values.ts
|
|
4948
|
-
import { readFile as
|
|
5594
|
+
import { readFile as readFile16 } from "node:fs/promises";
|
|
4949
5595
|
var NUMERIC = /^-?\d+(\.\d+)?$/;
|
|
4950
5596
|
var quote = (v) => Array.isArray(v) ? `[${v.join(", ")}]` : `"${v}"`;
|
|
4951
5597
|
var describe = (v, mode) => mode === "frontmatter" ? quote(v) : JSON.stringify(v);
|
|
@@ -5039,7 +5685,7 @@ function jsonValue(prop, value, path, owner, problems) {
|
|
|
5039
5685
|
async function readSchema(schemaPath) {
|
|
5040
5686
|
if (!schemaPath) return {};
|
|
5041
5687
|
try {
|
|
5042
|
-
return { schema: JSON.parse(await
|
|
5688
|
+
return { schema: JSON.parse(await readFile16(schemaPath, "utf8")) };
|
|
5043
5689
|
} catch (e) {
|
|
5044
5690
|
return { error: e instanceof Error ? e.message : String(e) };
|
|
5045
5691
|
}
|
|
@@ -5130,209 +5776,12 @@ async function placedValue(prop, node, where, ctx, problems) {
|
|
|
5130
5776
|
import { readFile as readFile17 } from "node:fs/promises";
|
|
5131
5777
|
import { basename as basename6 } from "node:path";
|
|
5132
5778
|
|
|
5133
|
-
// packages/dev-server/src/storefront/pages.ts
|
|
5134
|
-
import { readdir as readdir8, readFile as readFile16, stat as stat5 } from "node:fs/promises";
|
|
5135
|
-
import { join as join22, relative as relative11, sep as sep7 } from "node:path";
|
|
5136
|
-
|
|
5137
|
-
// packages/dev-server/src/storefront/redirects.ts
|
|
5138
|
-
function redirectsOf(f) {
|
|
5139
|
-
const v = f.frontmatter.values["redirectFrom"];
|
|
5140
|
-
return Array.isArray(v) ? v : typeof v === "string" ? [v] : [];
|
|
5141
|
-
}
|
|
5142
|
-
var RESERVED = [PD_ROUTE_PREFIX.preview, "/_pd/", PD_ROUTE_PREFIX.assets, "/api/", PD_ROUTE_PREFIX.source];
|
|
5143
|
-
function redirectProblems(files, workbenchPath) {
|
|
5144
|
-
const problems = [];
|
|
5145
|
-
const claimed = /* @__PURE__ */ new Map();
|
|
5146
|
-
for (const f of files.values()) {
|
|
5147
|
-
const at = `${f.path}${f.frontmatter.lines["redirectFrom"] !== void 0 ? `:${f.frontmatter.lines["redirectFrom"]}` : ""}`;
|
|
5148
|
-
for (const old of redirectsOf(f)) {
|
|
5149
|
-
if (typeof old !== "string" || !old.startsWith("/") || !old.endsWith("/")) continue;
|
|
5150
|
-
const page = files.get(old);
|
|
5151
|
-
if (page) {
|
|
5152
|
-
problems.push(`${at}: redirectFrom "${old}" is the page ${page.path}: a route can't both be a page and forward to one`);
|
|
5153
|
-
continue;
|
|
5154
|
-
}
|
|
5155
|
-
const reserved = RESERVED.find((r) => old.startsWith(r));
|
|
5156
|
-
if (reserved) {
|
|
5157
|
-
problems.push(`${at}: redirectFrom "${old}" is under ${reserved}, which Pattern Dump serves itself`);
|
|
5158
|
-
continue;
|
|
5159
|
-
}
|
|
5160
|
-
if (workbenchPath !== void 0 && old.startsWith(workbenchPath)) {
|
|
5161
|
-
problems.push(`${at}: redirectFrom "${old}" is ${workbenchPlace(old, workbenchPath)}`);
|
|
5162
|
-
continue;
|
|
5163
|
-
}
|
|
5164
|
-
const other = claimed.get(old);
|
|
5165
|
-
if (other && other !== f) {
|
|
5166
|
-
problems.push(`${at}: redirectFrom "${old}" is also claimed by ${other.path}: one old route forwards to one page`);
|
|
5167
|
-
continue;
|
|
5168
|
-
}
|
|
5169
|
-
claimed.set(old, f);
|
|
5170
|
-
}
|
|
5171
|
-
}
|
|
5172
|
-
return problems;
|
|
5173
|
-
}
|
|
5174
|
-
function workbenchPlace(route, workbenchPath) {
|
|
5175
|
-
return `${route === workbenchPath ? "at" : "under"} ${workbenchPath}, where the workbench is published on this host (storefront.workbenchUrl), so the workbench would overwrite it there`;
|
|
5176
|
-
}
|
|
5177
|
-
function redirectStub(p) {
|
|
5178
|
-
const target = escapeAttr(p.route);
|
|
5179
|
-
const canonical2 = p.siteUrl !== void 0 ? `
|
|
5180
|
-
<link rel="canonical" href="${escapeAttr(new URL(p.route.slice(1), p.siteUrl).href)}">` : "";
|
|
5181
|
-
return `<!DOCTYPE html>
|
|
5182
|
-
<html lang="${escapeAttr(p.lang)}">
|
|
5183
|
-
<head>
|
|
5184
|
-
<meta charset="UTF-8">
|
|
5185
|
-
<title>Moved to ${escapeHtml(p.title)}</title>${canonical2}
|
|
5186
|
-
<meta name="robots" content="noindex">
|
|
5187
|
-
<meta http-equiv="refresh" content="0; url=${target}">
|
|
5188
|
-
</head>
|
|
5189
|
-
<body>
|
|
5190
|
-
<p>This page has moved to <a href="${target}">${escapeHtml(p.title)}</a>.</p>
|
|
5191
|
-
</body>
|
|
5192
|
-
</html>
|
|
5193
|
-
`;
|
|
5194
|
-
}
|
|
5195
|
-
|
|
5196
|
-
// packages/dev-server/src/storefront/pages.ts
|
|
5197
|
-
var DEFAULT_LAYOUT = "page";
|
|
5198
|
-
var NOT_FOUND_FILE = "404.md";
|
|
5199
|
-
function routeFor(path) {
|
|
5200
|
-
const parts = path.replace(/\.md$/, "").split("/");
|
|
5201
|
-
if (parts.at(-1) === "index") parts.pop();
|
|
5202
|
-
return parts.length ? `/${parts.join("/")}/` : "/";
|
|
5203
|
-
}
|
|
5204
|
-
var NAME = /^[a-z0-9-]+$/;
|
|
5205
|
-
async function markdownFiles(dir, root, problems) {
|
|
5206
|
-
const out = [];
|
|
5207
|
-
const entries = (await readdir8(dir, { withFileTypes: true }).catch(() => [])).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
5208
|
-
for (const e of entries) {
|
|
5209
|
-
if (e.name.startsWith(".")) continue;
|
|
5210
|
-
const p = join22(dir, e.name);
|
|
5211
|
-
const rel = relative11(root, p).split(sep7).join("/");
|
|
5212
|
-
if (e.isDirectory()) {
|
|
5213
|
-
if (!NAME.test(e.name)) {
|
|
5214
|
-
problems.push(`${rel}/: a folder name is part of the URL, so it is lowercase letters, digits and hyphens only`);
|
|
5215
|
-
continue;
|
|
5216
|
-
}
|
|
5217
|
-
out.push(...await markdownFiles(p, root, problems));
|
|
5218
|
-
} else if (!e.name.endsWith(".md")) {
|
|
5219
|
-
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)`);
|
|
5220
|
-
} else if (!NAME.test(e.name.slice(0, -".md".length))) {
|
|
5221
|
-
problems.push(`${rel}: a page's file name is part of the URL, so it is lowercase letters, digits and hyphens only, then .md`);
|
|
5222
|
-
} else {
|
|
5223
|
-
out.push(rel);
|
|
5224
|
-
}
|
|
5225
|
-
}
|
|
5226
|
-
return out;
|
|
5227
|
-
}
|
|
5228
|
-
var scalar = (v) => typeof v === "string" ? v : void 0;
|
|
5229
|
-
async function readPages(pagesDir, opts = {}) {
|
|
5230
|
-
const problems = [];
|
|
5231
|
-
const files = /* @__PURE__ */ new Map();
|
|
5232
|
-
let notFound;
|
|
5233
|
-
if (!await stat5(pagesDir).then((s) => s.isDirectory(), () => false)) {
|
|
5234
|
-
return { pages: [], nav: [], files, problems: [`the pages folder ${pagesDir} does not exist`] };
|
|
5235
|
-
}
|
|
5236
|
-
for (const path of (await markdownFiles(pagesDir, pagesDir, problems)).sort()) {
|
|
5237
|
-
const route = routeFor(path);
|
|
5238
|
-
const other = files.get(route);
|
|
5239
|
-
if (other) {
|
|
5240
|
-
problems.push(`${other.path} and ${path} are both the page at ${route}`);
|
|
5241
|
-
continue;
|
|
5242
|
-
}
|
|
5243
|
-
const file = join22(pagesDir, ...path.split("/"));
|
|
5244
|
-
const text = await readFile16(file, "utf8").catch((e) => {
|
|
5245
|
-
if (e.code === "ENOENT") return null;
|
|
5246
|
-
throw e;
|
|
5247
|
-
});
|
|
5248
|
-
if (text === null) continue;
|
|
5249
|
-
if (path === NOT_FOUND_FILE) {
|
|
5250
|
-
notFound = { route: PD_ROUTES.notFound, file, path, frontmatter: readFrontmatterStrict(text) };
|
|
5251
|
-
continue;
|
|
5252
|
-
}
|
|
5253
|
-
files.set(route, { route, file, path, frontmatter: readFrontmatterStrict(text) });
|
|
5254
|
-
}
|
|
5255
|
-
const pageOf = (f) => {
|
|
5256
|
-
const redirectFrom = redirectsOf(f);
|
|
5257
|
-
return {
|
|
5258
|
-
route: f.route,
|
|
5259
|
-
title: scalar(f.frontmatter.values["title"]) ?? f.route,
|
|
5260
|
-
layout: scalar(f.frontmatter.values["layout"]) ?? DEFAULT_LAYOUT,
|
|
5261
|
-
...redirectFrom.length ? { redirectFrom } : {}
|
|
5262
|
-
};
|
|
5263
|
-
};
|
|
5264
|
-
const entryOf = (f) => {
|
|
5265
|
-
const description = scalar(f.frontmatter.values["description"]);
|
|
5266
|
-
return { route: f.route, title: pageOf(f).title, ...description !== void 0 ? { description } : {} };
|
|
5267
|
-
};
|
|
5268
|
-
const weightOf = (f) => {
|
|
5269
|
-
const o = parseOrder(f?.frontmatter.values["order"]);
|
|
5270
|
-
return o && "weight" in o ? o.weight : void 0;
|
|
5271
|
-
};
|
|
5272
|
-
const listOf = (f) => {
|
|
5273
|
-
const o = parseOrder(f?.frontmatter.values["order"]);
|
|
5274
|
-
return o && "list" in o ? o.list : void 0;
|
|
5275
|
-
};
|
|
5276
|
-
const folders = /* @__PURE__ */ new Set();
|
|
5277
|
-
for (const f of files.values()) {
|
|
5278
|
-
const parts = f.path.split("/").slice(0, -1);
|
|
5279
|
-
for (let i = 1; i <= parts.length; i++) folders.add(`/${parts.slice(0, i).join("/")}/`);
|
|
5280
|
-
}
|
|
5281
|
-
if (!files.has("/")) problems.push("there is no home page: the pages folder needs an index.md at its top, the page at /");
|
|
5282
|
-
for (const folder of [...folders].sort()) {
|
|
5283
|
-
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`);
|
|
5284
|
-
}
|
|
5285
|
-
const childrenOf = (folder) => {
|
|
5286
|
-
const depth = folder === "/" ? 0 : folder.split("/").length - 2;
|
|
5287
|
-
const out = /* @__PURE__ */ new Map();
|
|
5288
|
-
for (const route of [...files.keys(), ...folders]) {
|
|
5289
|
-
if (route === folder || !route.startsWith(folder)) continue;
|
|
5290
|
-
const segs = route.split("/").filter(Boolean);
|
|
5291
|
-
if (segs.length !== depth + 1) continue;
|
|
5292
|
-
out.set(route, segs[depth]);
|
|
5293
|
-
}
|
|
5294
|
-
return [...out].map(([route, segment]) => ({ route, segment }));
|
|
5295
|
-
};
|
|
5296
|
-
const ordered = (folder, extra = []) => {
|
|
5297
|
-
const items = childrenOf(folder).map((c3) => {
|
|
5298
|
-
const w = weightOf(files.get(c3.route));
|
|
5299
|
-
return { route: c3.route, segment: c3.segment, ...w !== void 0 ? { weight: w } : {} };
|
|
5300
|
-
});
|
|
5301
|
-
return orderSiblings([...extra, ...items], listOf(files.get(folder)));
|
|
5302
|
-
};
|
|
5303
|
-
const home = files.get("/");
|
|
5304
|
-
const homeWeight = weightOf(home);
|
|
5305
|
-
const top = ordered("/", home ? [{ route: "/", segment: "index", ...homeWeight !== void 0 ? { weight: homeWeight } : {} }] : []);
|
|
5306
|
-
const nav = [];
|
|
5307
|
-
for (const item of top) {
|
|
5308
|
-
const page = files.get(item.route);
|
|
5309
|
-
if (!page) continue;
|
|
5310
|
-
const children = folders.has(item.route) && item.route !== "/" ? ordered(item.route).flatMap((c3) => {
|
|
5311
|
-
const p = files.get(c3.route);
|
|
5312
|
-
return p ? [entryOf(p)] : [];
|
|
5313
|
-
}) : [];
|
|
5314
|
-
nav.push({ ...entryOf(page), ...children.length ? { children } : {} });
|
|
5315
|
-
}
|
|
5316
|
-
const inNav = nav.flatMap((n) => [n.route, ...(n.children ?? []).map((c3) => c3.route)]);
|
|
5317
|
-
const rest = [...files.keys()].filter((r) => !inNav.includes(r)).sort();
|
|
5318
|
-
const routes = [...inNav, ...rest];
|
|
5319
|
-
const pages = routes.map((r) => pageOf(files.get(r)));
|
|
5320
|
-
const { workbenchPath } = opts;
|
|
5321
|
-
if (workbenchPath !== void 0) {
|
|
5322
|
-
for (const f of files.values()) {
|
|
5323
|
-
if (f.route.startsWith(workbenchPath)) problems.push(`${f.path}: the page ${f.route} is ${workbenchPlace(f.route, workbenchPath)}`);
|
|
5324
|
-
}
|
|
5325
|
-
}
|
|
5326
|
-
problems.push(...redirectProblems(files, workbenchPath));
|
|
5327
|
-
return { pages, nav, files: new Map(routes.map((r) => [r, files.get(r)])), problems, ...notFound ? { notFound } : {} };
|
|
5328
|
-
}
|
|
5329
|
-
|
|
5330
5779
|
// packages/dev-server/src/storefront/frontmatter-contract.ts
|
|
5331
|
-
var RESERVED_KEYS = ["layout", "order", "redirectFrom", "index", "image"];
|
|
5780
|
+
var RESERVED_KEYS = ["layout", "order", "redirectFrom", "index", "image", "date", "tags", "draft", "sort"];
|
|
5332
5781
|
var ASSEMBLY_KEYS = ["body", "nav", "site", "page"];
|
|
5333
5782
|
var OLD_ROUTE = /^\/(?:(?!\.\.?\/)[^/?#\s]+\/)+$/;
|
|
5334
5783
|
var ASSET_PATH = /^\/assets\/[^?#]+[^/?#]$/;
|
|
5335
|
-
function checkBuildKeys(values, at) {
|
|
5784
|
+
function checkBuildKeys(values, path, at) {
|
|
5336
5785
|
const out = {};
|
|
5337
5786
|
const redirect = values["redirectFrom"];
|
|
5338
5787
|
if (redirect !== void 0) {
|
|
@@ -5352,6 +5801,16 @@ function checkBuildKeys(values, at) {
|
|
|
5352
5801
|
if (typeof image === "string" && ASSET_PATH.test(image)) out.image = image;
|
|
5353
5802
|
else at("image", `image must be a path to a file in the assets, such as "/assets/images/share.png"`);
|
|
5354
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));
|
|
5355
5814
|
return out;
|
|
5356
5815
|
}
|
|
5357
5816
|
function layoutNames(discoveryMap) {
|
|
@@ -5374,12 +5833,12 @@ async function checkPageFrontmatter(page, discoveryMap) {
|
|
|
5374
5833
|
if (!Array.isArray(rawLayout)) {
|
|
5375
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)`);
|
|
5376
5835
|
}
|
|
5377
|
-
return { layoutId, values: {}, build: checkBuildKeys(values, at), problems };
|
|
5836
|
+
return { layoutId, values: {}, build: checkBuildKeys(values, page.path, at), problems };
|
|
5378
5837
|
}
|
|
5379
5838
|
const read = await readSchema(entry.schemaPath);
|
|
5380
5839
|
if (read.error !== void 0) {
|
|
5381
5840
|
problems.push({ file: page.path, message: `the schema of layout "${layout}" cannot be read: ${read.error}` });
|
|
5382
|
-
return { layoutId, values: {}, build: checkBuildKeys(values, at), problems };
|
|
5841
|
+
return { layoutId, values: {}, build: checkBuildKeys(values, page.path, at), problems };
|
|
5383
5842
|
}
|
|
5384
5843
|
const schema = read.schema ?? {};
|
|
5385
5844
|
const hasSchema = read.schema !== void 0;
|
|
@@ -5389,7 +5848,7 @@ async function checkPageFrontmatter(page, discoveryMap) {
|
|
|
5389
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` });
|
|
5390
5849
|
}
|
|
5391
5850
|
}
|
|
5392
|
-
const build = checkBuildKeys(values, at);
|
|
5851
|
+
const build = checkBuildKeys(values, page.path, at);
|
|
5393
5852
|
const out = {};
|
|
5394
5853
|
for (const [key, value] of Object.entries(values)) {
|
|
5395
5854
|
if (RESERVED_KEYS.includes(key)) continue;
|
|
@@ -5420,6 +5879,9 @@ async function checkPageFrontmatter(page, discoveryMap) {
|
|
|
5420
5879
|
|
|
5421
5880
|
// packages/dev-server/src/storefront/derived.ts
|
|
5422
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
|
+
};
|
|
5423
5885
|
var PAGE_SHAPE = {
|
|
5424
5886
|
fields: {
|
|
5425
5887
|
headings: { items: { fields: { level: "value", text: "value", anchor: "value" } } },
|
|
@@ -5428,10 +5890,13 @@ var PAGE_SHAPE = {
|
|
|
5428
5890
|
prev: LINK,
|
|
5429
5891
|
next: LINK,
|
|
5430
5892
|
created: "value",
|
|
5431
|
-
modified: "value"
|
|
5893
|
+
modified: "value",
|
|
5894
|
+
children: { items: ENTRY },
|
|
5895
|
+
date: "value",
|
|
5896
|
+
tags: "value"
|
|
5432
5897
|
}
|
|
5433
5898
|
};
|
|
5434
|
-
var FILLED = ["headings", "section", "breadcrumbs", "prev", "next", "created", "modified"];
|
|
5899
|
+
var FILLED = ["headings", "section", "breadcrumbs", "prev", "next", "created", "modified", "children", "date", "tags"];
|
|
5435
5900
|
function positionOf(route, site) {
|
|
5436
5901
|
const titles = new Map(site.pages.map((p) => [p.route, p.title]));
|
|
5437
5902
|
const link = (r) => titles.has(r) ? { route: r, title: titles.get(r) } : null;
|
|
@@ -5479,7 +5944,7 @@ var NAVIGATIONS_KEY = "navigations";
|
|
|
5479
5944
|
var AT = "storefront.site.navigations";
|
|
5480
5945
|
var KEYS = ["page", "href", "label", "children"];
|
|
5481
5946
|
var isObject5 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
5482
|
-
var
|
|
5947
|
+
var show2 = (v) => typeof v === "string" ? `"${v}"` : JSON.stringify(v) ?? String(v);
|
|
5483
5948
|
var isAllowedHref = (href) => /^(https?:\/\/|mailto:)/i.test(href) || /^\/(?!\/)/.test(href);
|
|
5484
5949
|
function listedNavigations(declared, owner) {
|
|
5485
5950
|
if (!declared) return { listed: [], problems: [] };
|
|
@@ -5502,7 +5967,7 @@ function checkNavigations(value, declared, owner, pagePaths) {
|
|
|
5502
5967
|
const problems = [...names.problems];
|
|
5503
5968
|
const navigations = {};
|
|
5504
5969
|
if (value !== void 0 && !isObject5(value)) {
|
|
5505
|
-
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)})`);
|
|
5506
5971
|
return { navigations, listed: names.listed, problems };
|
|
5507
5972
|
}
|
|
5508
5973
|
const given = value ?? {};
|
|
@@ -5513,7 +5978,7 @@ function checkNavigations(value, declared, owner, pagePaths) {
|
|
|
5513
5978
|
continue;
|
|
5514
5979
|
}
|
|
5515
5980
|
if (!Array.isArray(entries)) {
|
|
5516
|
-
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)})`);
|
|
5517
5982
|
continue;
|
|
5518
5983
|
}
|
|
5519
5984
|
problems.push(...checkEntries(entries, `${AT}.${name}`, pagePaths));
|
|
@@ -5529,7 +5994,7 @@ function checkEntries(entries, at, pagePaths) {
|
|
|
5529
5994
|
}
|
|
5530
5995
|
function checkEntry(entry, at, pagePaths) {
|
|
5531
5996
|
const FORMS = `{ page }, { href, label } or { label, children }`;
|
|
5532
|
-
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)})`];
|
|
5533
5998
|
const problems = [];
|
|
5534
5999
|
for (const key of Object.keys(entry)) {
|
|
5535
6000
|
if (KEYS.includes(key)) continue;
|
|
@@ -5542,11 +6007,11 @@ function checkEntry(entry, at, pagePaths) {
|
|
|
5542
6007
|
return problems;
|
|
5543
6008
|
}
|
|
5544
6009
|
const label = entry["label"];
|
|
5545
|
-
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)})`);
|
|
5546
6011
|
const form = forms[0];
|
|
5547
6012
|
if (form === "page") {
|
|
5548
6013
|
const page = entry["page"];
|
|
5549
|
-
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)})`);
|
|
5550
6015
|
else if (!pagePaths.has(page)) {
|
|
5551
6016
|
const near = suggest(page, [...pagePaths]);
|
|
5552
6017
|
problems.push(`${at}: "${page}" is not a page in the pages folder${near ? ` \u2014 did you mean "${near}"?` : ""}`);
|
|
@@ -5555,11 +6020,11 @@ function checkEntry(entry, at, pagePaths) {
|
|
|
5555
6020
|
if (label === void 0) problems.push(`${at} needs a "label"`);
|
|
5556
6021
|
if (form === "href") {
|
|
5557
6022
|
const href = entry["href"];
|
|
5558
|
-
if (typeof href !== "string") problems.push(`${at}.href must be text (got ${
|
|
5559
|
-
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)`);
|
|
5560
6025
|
} else {
|
|
5561
6026
|
const children = entry["children"];
|
|
5562
|
-
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)})`);
|
|
5563
6028
|
else problems.push(...checkEntries(children, `${at}.children`, pagePaths));
|
|
5564
6029
|
}
|
|
5565
6030
|
}
|
|
@@ -5580,10 +6045,86 @@ function resolveNavigations(navigations, listed, model, route) {
|
|
|
5580
6045
|
return Object.fromEntries(listed.map((name) => [name, Object.hasOwn(navigations, name) ? resolve15(navigations[name]) : null]));
|
|
5581
6046
|
}
|
|
5582
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
|
+
|
|
5583
6124
|
// packages/dev-server/src/storefront/site.ts
|
|
5584
6125
|
var SITE_SCHEMA_OWNER = "partials/base";
|
|
5585
|
-
var none = (problems = []) => ({ values: {}, navigations: {}, listed: [], problems });
|
|
5586
|
-
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: [] }) {
|
|
5587
6128
|
const problems = [];
|
|
5588
6129
|
const entry = discoveryMap.get(SITE_SCHEMA_OWNER);
|
|
5589
6130
|
if (!entry) {
|
|
@@ -5600,24 +6141,32 @@ async function checkSite(site, discoveryMap, pagePaths = /* @__PURE__ */ new Set
|
|
|
5600
6141
|
}
|
|
5601
6142
|
const navDeclared = declared.properties?.[NAVIGATIONS_KEY];
|
|
5602
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
|
+
);
|
|
5603
6151
|
if (site === void 0) {
|
|
5604
6152
|
if (schema.required?.includes("site")) problems.push(`storefront.site is required by ${SITE_SCHEMA_OWNER}`);
|
|
5605
|
-
problems.push(...navs.problems);
|
|
5606
|
-
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 };
|
|
5607
6155
|
}
|
|
5608
|
-
const
|
|
6156
|
+
const own = (k) => k !== NAVIGATIONS_KEY && k !== COLLECTIONS_KEY;
|
|
6157
|
+
const rest = Object.fromEntries(Object.entries(site).filter(([k]) => own(k)));
|
|
5609
6158
|
const restDeclared = {
|
|
5610
6159
|
...declared,
|
|
5611
|
-
properties: Object.fromEntries(Object.entries(declared.properties ?? {}).filter(([k]) => k
|
|
5612
|
-
...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) } : {}
|
|
5613
6162
|
};
|
|
5614
6163
|
const checked = checkJsonKeys(restDeclared, rest, "storefront.site", SITE_SCHEMA_OWNER);
|
|
5615
|
-
problems.push(...checked.problems, ...navs.problems);
|
|
6164
|
+
problems.push(...checked.problems, ...navs.problems, ...cols.problems);
|
|
5616
6165
|
if (declared.required?.includes(NAVIGATIONS_KEY) && !Object.hasOwn(site, NAVIGATIONS_KEY)) {
|
|
5617
6166
|
problems.push(`storefront.site.${NAVIGATIONS_KEY} is required by ${SITE_SCHEMA_OWNER}`);
|
|
5618
6167
|
}
|
|
5619
|
-
if (problems.length) return { ...none(problems), listed: navs.listed };
|
|
5620
|
-
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 };
|
|
5621
6170
|
}
|
|
5622
6171
|
|
|
5623
6172
|
// packages/dev-server/src/storefront/sample-data.ts
|
|
@@ -5635,7 +6184,7 @@ function includeTree(layoutId, composition) {
|
|
|
5635
6184
|
}
|
|
5636
6185
|
return [layoutId, ...[...seen].filter((id) => id !== layoutId).sort()];
|
|
5637
6186
|
}
|
|
5638
|
-
async function checkSampleData(layoutId, discoveryMap, composition, layoutDeclared, siteDeclared, pageDeclared, navigationNames = []) {
|
|
6187
|
+
async function checkSampleData(layoutId, discoveryMap, composition, layoutDeclared, siteDeclared, pageDeclared, navigationNames = [], collectionNames = []) {
|
|
5639
6188
|
const problems = [];
|
|
5640
6189
|
const allowed = new Set([...layoutDeclared, ...ASSEMBLY_KEYS].filter((k) => k !== "page" || pageDeclared !== void 0));
|
|
5641
6190
|
const shapeFields = PAGE_SHAPE.fields;
|
|
@@ -5672,10 +6221,11 @@ async function checkSampleData(layoutId, discoveryMap, composition, layoutDeclar
|
|
|
5672
6221
|
for (const [sub, v] of Object.entries(value)) {
|
|
5673
6222
|
if (!siteDeclared.includes(sub)) {
|
|
5674
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}`);
|
|
5675
|
-
} else if (sub === "navigations" && isPlainObject2(v)) {
|
|
6224
|
+
} else if ((sub === "navigations" || sub === "collections") && isPlainObject2(v)) {
|
|
6225
|
+
const names = sub === "navigations" ? navigationNames : collectionNames;
|
|
5676
6226
|
for (const name of Object.keys(v)) {
|
|
5677
|
-
if (!
|
|
5678
|
-
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}`);
|
|
5679
6229
|
}
|
|
5680
6230
|
}
|
|
5681
6231
|
}
|
|
@@ -5687,7 +6237,7 @@ async function checkSampleData(layoutId, discoveryMap, composition, layoutDeclar
|
|
|
5687
6237
|
}
|
|
5688
6238
|
|
|
5689
6239
|
// packages/dev-server/src/storefront/placed.ts
|
|
5690
|
-
var contentMd = new
|
|
6240
|
+
var contentMd = new MarkdownIt3({ html: false, linkify: false });
|
|
5691
6241
|
var kebab = (k) => k.replace(/[A-Z]/g, (c3) => `-${c3.toLowerCase()}`);
|
|
5692
6242
|
async function placementComponents(id, deps) {
|
|
5693
6243
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -5736,7 +6286,7 @@ async function unsafeOutputs(id, at, deps) {
|
|
|
5736
6286
|
}
|
|
5737
6287
|
return problems;
|
|
5738
6288
|
}
|
|
5739
|
-
var
|
|
6289
|
+
var isObject7 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
5740
6290
|
function undeclaredSampleKeys(sample, props, prefix) {
|
|
5741
6291
|
const out = [];
|
|
5742
6292
|
for (const [key, value] of Object.entries(sample)) {
|
|
@@ -5745,7 +6295,7 @@ function undeclaredSampleKeys(sample, props, prefix) {
|
|
|
5745
6295
|
out.push(`${prefix}${key}`);
|
|
5746
6296
|
continue;
|
|
5747
6297
|
}
|
|
5748
|
-
if (
|
|
6298
|
+
if (isObject7(value) && prop.properties) out.push(...undeclaredSampleKeys(value, prop.properties, `${prefix}${key}.`));
|
|
5749
6299
|
}
|
|
5750
6300
|
return out;
|
|
5751
6301
|
}
|
|
@@ -5764,7 +6314,7 @@ async function sampleDataProblems(id, at, deps) {
|
|
|
5764
6314
|
} catch {
|
|
5765
6315
|
continue;
|
|
5766
6316
|
}
|
|
5767
|
-
if (!
|
|
6317
|
+
if (!isObject7(sample)) continue;
|
|
5768
6318
|
for (const key of undeclaredSampleKeys(sample, declared, "")) {
|
|
5769
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` });
|
|
5770
6320
|
}
|
|
@@ -5784,6 +6334,11 @@ async function schemaProblems(id, at, deps) {
|
|
|
5784
6334
|
})) {
|
|
5785
6335
|
problems.push({ at, message: `${component} cannot be placed: its schema (${deps.shown(entry.schemaPath)}, ${p.path}): ${p.message}` });
|
|
5786
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
|
+
}
|
|
5787
6342
|
}
|
|
5788
6343
|
return problems;
|
|
5789
6344
|
}
|
|
@@ -5839,23 +6394,23 @@ var Slugger = class {
|
|
|
5839
6394
|
// packages/dev-server/src/markdown.ts
|
|
5840
6395
|
var INLINE = { embed: "inline" };
|
|
5841
6396
|
function createDocMarkdown(placing) {
|
|
5842
|
-
const
|
|
5843
|
-
|
|
6397
|
+
const md2 = new MarkdownIt4({ html: false, linkify: false });
|
|
6398
|
+
md2.validateLink = (url) => {
|
|
5844
6399
|
const t = url.trim();
|
|
5845
6400
|
return /^(https?|mailto|tel):/i.test(t) || /^[.#/]/.test(t) || t.startsWith("pattern:");
|
|
5846
6401
|
};
|
|
5847
|
-
|
|
6402
|
+
md2.renderer.rules["fence"] = (tokens, idx, _opts, env) => {
|
|
5848
6403
|
const map = env.resolvedFences;
|
|
5849
6404
|
return map?.get(idx) ?? _mdPlainPre(tokens[idx]?.content ?? "");
|
|
5850
6405
|
};
|
|
5851
|
-
const image =
|
|
5852
|
-
|
|
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);
|
|
5853
6408
|
if (placing) {
|
|
5854
|
-
usePlacedTags(
|
|
5855
|
-
|
|
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>
|
|
5856
6411
|
`;
|
|
5857
6412
|
}
|
|
5858
|
-
return
|
|
6413
|
+
return md2;
|
|
5859
6414
|
}
|
|
5860
6415
|
var _md = createDocMarkdown(false);
|
|
5861
6416
|
var _mdPlaced = createDocMarkdown(true);
|
|
@@ -5909,10 +6464,10 @@ function _resolvePatternLinks(tokens, discoveryMap, embed) {
|
|
|
5909
6464
|
}
|
|
5910
6465
|
async function renderDoc(body, renderComponent, discoveryMap, embed = INLINE) {
|
|
5911
6466
|
const placing = embed.embed === "frame" ? embed.placed : void 0;
|
|
5912
|
-
const
|
|
5913
|
-
const tokens =
|
|
6467
|
+
const md2 = placing ? _mdPlaced : _md;
|
|
6468
|
+
const tokens = md2.parse(body, { pdLineOffset: placing?.lineOffset ?? 0 });
|
|
5914
6469
|
_resolvePatternLinks(tokens, discoveryMap, embed);
|
|
5915
|
-
if (embed.embed === "frame" && embed.prose) proseImages(
|
|
6470
|
+
if (embed.embed === "frame" && embed.prose) proseImages(md2, tokens, embed.prose);
|
|
5916
6471
|
if (embed.embed === "frame") {
|
|
5917
6472
|
const headings = headingAnchors(tokens, embed.takenIds);
|
|
5918
6473
|
embed.headings?.push(...headings);
|
|
@@ -5933,9 +6488,9 @@ async function renderDoc(body, renderComponent, discoveryMap, embed = INLINE) {
|
|
|
5933
6488
|
`);
|
|
5934
6489
|
}
|
|
5935
6490
|
}
|
|
5936
|
-
return
|
|
6491
|
+
return md2.renderer.render(tokens, md2.options, { resolvedFences, resolvedPlaced });
|
|
5937
6492
|
}
|
|
5938
|
-
function proseImages(
|
|
6493
|
+
function proseImages(md2, tokens, prose) {
|
|
5939
6494
|
let eager = prose.slot?.eager ?? 0;
|
|
5940
6495
|
for (const block2 of tokens) {
|
|
5941
6496
|
if (block2.type !== "inline" || !block2.children) continue;
|
|
@@ -5948,7 +6503,7 @@ function proseImages(md, tokens, prose) {
|
|
|
5948
6503
|
if (child.type !== "image") continue;
|
|
5949
6504
|
const src = child.attrGet("src") ?? "";
|
|
5950
6505
|
const problem = (message) => prose.problems.push({ line, message: `the image ${src}${message}` });
|
|
5951
|
-
const alt =
|
|
6506
|
+
const alt = md2.renderer.renderInlineAsText(child.children ?? [], md2.options, {});
|
|
5952
6507
|
if (alt.trim() === "") {
|
|
5953
6508
|
problem(` has no alt text: write what it shows between the brackets; a decorative image goes in through a component, with alt=""`);
|
|
5954
6509
|
continue;
|
|
@@ -6135,7 +6690,7 @@ import postcss4 from "postcss";
|
|
|
6135
6690
|
import * as sass3 from "sass";
|
|
6136
6691
|
import { SourceMapConsumer as SourceMapConsumer2 } from "source-map";
|
|
6137
6692
|
import { parse as parseHtml2 } from "node-html-parser";
|
|
6138
|
-
async function recomputeComponentCss(entryFile, compId, stylesDir, componentIds, inBase = () => false) {
|
|
6693
|
+
async function recomputeComponentCss(entryFile, compId, stylesDir, componentIds, inBase = () => false, out) {
|
|
6139
6694
|
try {
|
|
6140
6695
|
const compiled = sass3.compile(entryFile, { sourceMap: true, ...sassPackageOptions(entryFile) });
|
|
6141
6696
|
const rawMap = compiled.sourceMap;
|
|
@@ -6143,16 +6698,22 @@ async function recomputeComponentCss(entryFile, compId, stylesDir, componentIds,
|
|
|
6143
6698
|
const consumer = new SourceMapConsumer2(rawMap);
|
|
6144
6699
|
const root = postcss4.parse(compiled.css);
|
|
6145
6700
|
const bucket = /* @__PURE__ */ new Set();
|
|
6701
|
+
const base2 = /* @__PURE__ */ new Set();
|
|
6146
6702
|
root.each((node) => {
|
|
6147
6703
|
const pos = node.source?.start;
|
|
6148
6704
|
if (!pos) return;
|
|
6149
6705
|
const original = consumer.originalPositionFor({ line: pos.line, column: pos.column - 1 });
|
|
6150
6706
|
if (!original.source) return;
|
|
6151
6707
|
const originPath = original.source.startsWith("file://") ? fileURLToPath4(original.source) : resolve8(dirname8(entryFile), original.source);
|
|
6152
|
-
if (inBase(originPath))
|
|
6708
|
+
if (inBase(originPath)) {
|
|
6709
|
+
addLastOccurrence(base2, cssNodeText(node));
|
|
6710
|
+
return;
|
|
6711
|
+
}
|
|
6153
6712
|
const originComp = resolveScssToComponent(originPath, stylesDir, componentIds);
|
|
6154
6713
|
if (originComp === compId) addLastOccurrence(bucket, cssNodeText(node));
|
|
6714
|
+
else if (originComp === null) addLastOccurrence(base2, cssNodeText(node));
|
|
6155
6715
|
});
|
|
6716
|
+
if (out) out.baseRules = joinCssChunks(base2);
|
|
6156
6717
|
return joinCssChunks(bucket);
|
|
6157
6718
|
} catch {
|
|
6158
6719
|
return null;
|
|
@@ -6345,13 +6906,13 @@ async function buildComponentDetail(componentId, patternsDir, renderFn, tracker,
|
|
|
6345
6906
|
}
|
|
6346
6907
|
|
|
6347
6908
|
// packages/dev-server/src/overview.ts
|
|
6348
|
-
import { join as
|
|
6909
|
+
import { join as join24 } from "node:path";
|
|
6349
6910
|
async function buildFolderOverview(folderPath, patternsDir, renderFn, discoveryMap) {
|
|
6350
6911
|
const category = folderPath.split("/")[0];
|
|
6351
6912
|
let folderLayout = FULL_CATEGORIES.has(stripOrderPrefixes(category)) ? "full" : "centered";
|
|
6352
6913
|
let doc;
|
|
6353
6914
|
try {
|
|
6354
|
-
const rawIndex = await readFolderFile(patternsDir,
|
|
6915
|
+
const rawIndex = await readFolderFile(patternsDir, join24(folderPath, "index.md"));
|
|
6355
6916
|
if (rawIndex === null) throw new Error("no index.md");
|
|
6356
6917
|
const m = rawIndex.match(/^preview:\s*(centered|full)$/m);
|
|
6357
6918
|
if (m) folderLayout = m[1];
|
|
@@ -6388,9 +6949,9 @@ async function collectPublicLeaves(folderPath, patternsDir, discoveryMap) {
|
|
|
6388
6949
|
}
|
|
6389
6950
|
|
|
6390
6951
|
// packages/dev-server/src/content-hash.ts
|
|
6391
|
-
import { createHash as
|
|
6952
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
6392
6953
|
function contentHash(bytes) {
|
|
6393
|
-
return
|
|
6954
|
+
return createHash6("sha256").update(bytes).digest("hex").slice(0, 10);
|
|
6394
6955
|
}
|
|
6395
6956
|
|
|
6396
6957
|
// packages/dev-server/src/scripts.ts
|
|
@@ -6560,7 +7121,11 @@ async function stampSrcset(value, hashOf) {
|
|
|
6560
7121
|
}));
|
|
6561
7122
|
return entries.join(",");
|
|
6562
7123
|
}
|
|
6563
|
-
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) {
|
|
6564
7129
|
const isPreview = name.toLowerCase() === "meta" && /\s(?:property="og:image"|name="twitter:image")/.test(attrs);
|
|
6565
7130
|
return replaceAsync(attrs, ATTRIBUTE, async (m) => {
|
|
6566
7131
|
const [whole, lead, attr2, eq, quoted] = m;
|
|
@@ -6573,22 +7138,22 @@ async function stampTag(name, attrs, hashOf) {
|
|
|
6573
7138
|
else if (key === "srcset") stamped = await stampSrcset(value, hashOf);
|
|
6574
7139
|
else if (key === "content" && isPreview) {
|
|
6575
7140
|
const at = /^(https?:\/\/[^/"']+)(.*)$/.exec(value);
|
|
6576
|
-
if (at) stamped = `${at[1]}${await
|
|
7141
|
+
if (at) stamped = `${at[1]}${await stampAbsolute(at[2], hashOf, base2)}`;
|
|
6577
7142
|
}
|
|
6578
7143
|
return `${lead}${attr2}${eq}${q}${stamped}${q}`;
|
|
6579
7144
|
});
|
|
6580
7145
|
}
|
|
6581
|
-
async function stampHtml(html, hashOf) {
|
|
7146
|
+
async function stampHtml(html, hashOf, base2 = "/") {
|
|
6582
7147
|
const parts = [];
|
|
6583
7148
|
let last = 0;
|
|
6584
7149
|
for (const tag of startTags(html)) {
|
|
6585
7150
|
parts.push(html.slice(last, tag.start));
|
|
6586
7151
|
const attrsAt = tag.start + 1 + tag.name.length;
|
|
6587
|
-
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)}`);
|
|
6588
7153
|
last = tag.end;
|
|
6589
7154
|
if (!tag.content) continue;
|
|
6590
7155
|
const content = html.slice(tag.content.start, tag.content.end);
|
|
6591
|
-
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);
|
|
6592
7157
|
last = tag.content.end;
|
|
6593
7158
|
}
|
|
6594
7159
|
parts.push(html.slice(last));
|
|
@@ -6605,7 +7170,7 @@ async function stampCss(css, hashOf) {
|
|
|
6605
7170
|
|
|
6606
7171
|
// packages/dev-server/src/assets.ts
|
|
6607
7172
|
import { existsSync as existsSync5 } from "node:fs";
|
|
6608
|
-
import { createRequire as
|
|
7173
|
+
import { createRequire as createRequire5 } from "node:module";
|
|
6609
7174
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
6610
7175
|
function resolveDevUiDist(moduleUrl, exists = existsSync5) {
|
|
6611
7176
|
const packaged = fileURLToPath5(new URL("./dev-ui/", moduleUrl));
|
|
@@ -6613,7 +7178,7 @@ function resolveDevUiDist(moduleUrl, exists = existsSync5) {
|
|
|
6613
7178
|
return fileURLToPath5(new URL("../../../apps/dev-ui/dist/", moduleUrl));
|
|
6614
7179
|
}
|
|
6615
7180
|
function resolveAxeCore(moduleUrl) {
|
|
6616
|
-
return
|
|
7181
|
+
return createRequire5(moduleUrl).resolve("axe-core/axe.min.js");
|
|
6617
7182
|
}
|
|
6618
7183
|
|
|
6619
7184
|
// packages/dev-server/src/storefront/page-image.ts
|
|
@@ -6674,6 +7239,7 @@ function jsonLdScript(p) {
|
|
|
6674
7239
|
name: p.title,
|
|
6675
7240
|
...p.description ? { description: p.description } : {},
|
|
6676
7241
|
isPartOf: { "@id": site },
|
|
7242
|
+
...p.published ? { datePublished: p.published } : {},
|
|
6677
7243
|
...p.modified ? { dateModified: p.modified } : {},
|
|
6678
7244
|
...p.breadcrumbs.length ? {
|
|
6679
7245
|
breadcrumb: {
|
|
@@ -6705,9 +7271,9 @@ function writtenByPatternDump(tag, attrs) {
|
|
|
6705
7271
|
if (tag === "script" && attr(attrs, "type") === "application/ld+json") return `<script type="application/ld+json">`;
|
|
6706
7272
|
return void 0;
|
|
6707
7273
|
}
|
|
6708
|
-
function checkHeadMarkup(html) {
|
|
7274
|
+
function checkHeadMarkup(html, source2 = { label: HEAD_PARTIAL, subject: "a head partial" }) {
|
|
6709
7275
|
const problems = [];
|
|
6710
|
-
const say = (what) => problems.push(`${
|
|
7276
|
+
const say = (what) => problems.push(`${source2.label}: ${what.replaceAll("a head partial", source2.subject)}`);
|
|
6711
7277
|
let rest = html.replace(/<!--[\s\S]*?-->/g, "");
|
|
6712
7278
|
rest = rest.replace(/<(script|style|noscript|title)\b([^>]*)>[\s\S]*?<\/\1\s*>/gi, (_m, rawTag, attrs) => {
|
|
6713
7279
|
const tag = rawTag.toLowerCase();
|
|
@@ -6781,13 +7347,14 @@ var LIVE_NOTICE = `function(t){var n=document.querySelector("[data-pd-live-notic
|
|
|
6781
7347
|
function exampleHeightCheck() {
|
|
6782
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();});})();`;
|
|
6783
7349
|
}
|
|
6784
|
-
function
|
|
7350
|
+
function document2(head, body, live, lang = "en", base2) {
|
|
6785
7351
|
return `<!DOCTYPE html>
|
|
6786
7352
|
<html lang="${escapeAttr(lang)}">
|
|
6787
7353
|
<head>
|
|
6788
7354
|
<meta charset="UTF-8">
|
|
6789
7355
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6790
|
-
${
|
|
7356
|
+
${base2 !== void 0 ? `<meta name="pd-base-path" content="${escapeAttr(base2)}">
|
|
7357
|
+
` : ""}${head}
|
|
6791
7358
|
</head>
|
|
6792
7359
|
<body>
|
|
6793
7360
|
${body}
|
|
@@ -6844,7 +7411,7 @@ var BUILT_IN_NOT_FOUND_DOCUMENT = `<main>
|
|
|
6844
7411
|
</main>`;
|
|
6845
7412
|
function errorDocument(route, problems, live, siteWide = []) {
|
|
6846
7413
|
const items = problems.map((p) => `<li${siteWide.includes(p) ? " data-pd-site" : ""}>${escapeHtml(p)}</li>`).join("\n");
|
|
6847
|
-
return
|
|
7414
|
+
return document2(
|
|
6848
7415
|
`<title>Problems \u2014 ${escapeHtml(route)}</title>`,
|
|
6849
7416
|
`<main>
|
|
6850
7417
|
<h1>The page ${escapeHtml(route)} cannot be built</h1>
|
|
@@ -6878,7 +7445,7 @@ async function assemblePage(route, model, a) {
|
|
|
6878
7445
|
if (!a.discoveryMap.has(layoutId)) {
|
|
6879
7446
|
return {
|
|
6880
7447
|
status: 404,
|
|
6881
|
-
html:
|
|
7448
|
+
html: document2(`<title>Page not found</title>`, BUILT_IN_NOT_FOUND_DOCUMENT, a.live, a.lang, a.basePath ?? "/"),
|
|
6882
7449
|
problems: [],
|
|
6883
7450
|
notes: [`there is no ${NOT_FOUND_FILE} and no ${layoutId}, so the 404 page is a plain one of Pattern Dump's`]
|
|
6884
7451
|
};
|
|
@@ -6903,7 +7470,10 @@ async function assemblePage(route, model, a) {
|
|
|
6903
7470
|
const line = page.frontmatter.lines["redirectFrom"];
|
|
6904
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`);
|
|
6905
7472
|
}
|
|
6906
|
-
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
|
+
});
|
|
6907
7477
|
siteWide = site.problems;
|
|
6908
7478
|
problems.push(...site.problems);
|
|
6909
7479
|
const imageLine = page.frontmatter.lines["image"];
|
|
@@ -6919,6 +7489,7 @@ async function assemblePage(route, model, a) {
|
|
|
6919
7489
|
const layoutSchema = (await readSchema(a.discoveryMap.get(checked.layoutId)?.schemaPath)).schema;
|
|
6920
7490
|
const proseSlot = readProseSlot(layoutSchema);
|
|
6921
7491
|
const imageLookup = a.images ? await a.images() : void 0;
|
|
7492
|
+
const placedIds = /* @__PURE__ */ new Set();
|
|
6922
7493
|
const renderBody = async (takenIds = []) => {
|
|
6923
7494
|
const prose = imageLookup ? {
|
|
6924
7495
|
layoutId: checked.layoutId,
|
|
@@ -6929,6 +7500,7 @@ async function assemblePage(route, model, a) {
|
|
|
6929
7500
|
...proseSlot.problem ? { slotProblem: proseSlot.problem } : {}
|
|
6930
7501
|
} : void 0;
|
|
6931
7502
|
const docProblems = [];
|
|
7503
|
+
placedIds.clear();
|
|
6932
7504
|
const placedProblems = [];
|
|
6933
7505
|
const headings = [];
|
|
6934
7506
|
const body = await renderDoc(
|
|
@@ -6950,7 +7522,11 @@ async function assemblePage(route, model, a) {
|
|
|
6950
7522
|
discoveryMap: a.discoveryMap,
|
|
6951
7523
|
handleMap: a.handleMap,
|
|
6952
7524
|
composition: a.composition,
|
|
6953
|
-
|
|
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
|
+
},
|
|
6954
7530
|
schemaOf: async (id) => (await readSchema(a.discoveryMap.get(id)?.schemaPath)).schema,
|
|
6955
7531
|
shown: a.shown
|
|
6956
7532
|
}
|
|
@@ -6971,37 +7547,65 @@ async function assemblePage(route, model, a) {
|
|
|
6971
7547
|
const notes = [];
|
|
6972
7548
|
let dates = {};
|
|
6973
7549
|
let modified = null;
|
|
6974
|
-
|
|
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");
|
|
6975
7554
|
if (a.dates && !isNotFound && (showsDates || a.siteUrl !== void 0)) {
|
|
6976
|
-
const dated = await a.dates.of(page.path,
|
|
7555
|
+
const dated = await a.dates.of(page.path, mode);
|
|
6977
7556
|
if (showsDates) {
|
|
6978
7557
|
dates = dated.dates;
|
|
6979
7558
|
notes.push(...dated.notes);
|
|
6980
7559
|
problems.push(...dated.problems);
|
|
6981
7560
|
}
|
|
6982
7561
|
if (dated.shallow) notes.push(SHALLOW_SEO_NOTE);
|
|
6983
|
-
else
|
|
6984
|
-
|
|
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 ?? []);
|
|
6985
7587
|
if (a.discoveryMap.has(checked.layoutId)) {
|
|
6986
|
-
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));
|
|
6987
7589
|
}
|
|
6988
7590
|
const hasHead = a.discoveryMap.has(HEAD_PARTIAL);
|
|
6989
7591
|
if (hasHead) {
|
|
6990
|
-
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);
|
|
6991
7593
|
siteWide = [...siteWide, ...headSample];
|
|
6992
7594
|
problems.push(...headSample);
|
|
6993
7595
|
}
|
|
6994
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`));
|
|
6995
7598
|
const siteData = {
|
|
6996
7599
|
...withDeclaredAbsentAsNull(site.values, siteDeclared),
|
|
6997
|
-
...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 } : {}
|
|
6998
7602
|
};
|
|
6999
7603
|
const dataFor = (body, headings) => ({
|
|
7000
7604
|
...withDeclaredAbsentAsNull(checked.values, layoutDeclared, ["body", "nav", "site", "page"]),
|
|
7001
7605
|
body,
|
|
7002
7606
|
nav: markCurrent(model.nav, route),
|
|
7003
7607
|
site: siteData,
|
|
7004
|
-
...pageDeclaration.declared ? { page: pageValue(pageDeclaration.declared, { headings, ...positionOf(route, model), ...dates }) } : {}
|
|
7608
|
+
...pageDeclaration.declared ? { page: pageValue(pageDeclaration.declared, { headings, ...positionOf(route, model), ...dates, ...listing }) } : {}
|
|
7005
7609
|
});
|
|
7006
7610
|
const renderLayout = async (body, headings) => {
|
|
7007
7611
|
try {
|
|
@@ -7051,6 +7655,30 @@ async function assemblePage(route, model, a) {
|
|
|
7051
7655
|
return fail(markup);
|
|
7052
7656
|
}
|
|
7053
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
|
+
}
|
|
7054
7682
|
const title = typeof checked.values["title"] === "string" ? checked.values["title"] : isNotFound ? "Page not found" : route;
|
|
7055
7683
|
const siteTitle = typeof site.values["title"] === "string" ? site.values["title"] : void 0;
|
|
7056
7684
|
const description = typeof checked.values["description"] === "string" ? checked.values["description"] : void 0;
|
|
@@ -7067,9 +7695,10 @@ async function assemblePage(route, model, a) {
|
|
|
7067
7695
|
image: imageCheck?.ok ? imageCheck.image : void 0,
|
|
7068
7696
|
index: !isNotFound && a.siteIndex !== false && checked.build.index !== false
|
|
7069
7697
|
}),
|
|
7070
|
-
...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 })] : [],
|
|
7071
7699
|
...headExtra ? [headExtra] : [],
|
|
7072
|
-
|
|
7700
|
+
...siteHead ? [siteHead] : [],
|
|
7701
|
+
await a.head(checked.layoutId, html, [...placedIds])
|
|
7073
7702
|
].join("\n");
|
|
7074
7703
|
if (isNotFound) {
|
|
7075
7704
|
const relative19 = relativeUrls(`${head}
|
|
@@ -7106,21 +7735,33 @@ ${html}`);
|
|
|
7106
7735
|
const self = isNotFound ? PD_ROUTES.notFound : route;
|
|
7107
7736
|
const routes = /* @__PURE__ */ new Set([...model.files.keys(), ...[...model.files.values()].flatMap(redirectsOf), PD_ROUTES.notFound]);
|
|
7108
7737
|
routes.add(self);
|
|
7109
|
-
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
|
+
});
|
|
7110
7745
|
notes.push(...links.problems.map((p) => `warning: ${page.path}${p.slice(self.length)}`));
|
|
7111
7746
|
notes.push(...checkPageBasics(html).map((p) => `warning: ${page.path}: ${p}`));
|
|
7112
7747
|
}
|
|
7113
|
-
return { status: isNotFound ? 404 : 200, html:
|
|
7114
|
-
${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 };
|
|
7115
7750
|
}
|
|
7116
7751
|
|
|
7117
7752
|
// packages/dev-server/src/storefront/page-head.ts
|
|
7118
|
-
async function pageStylesheets(cfg, state, discovery, layoutId, html) {
|
|
7119
|
-
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
|
+
}
|
|
7120
7761
|
const assets = await resolveDocumentAssets(cfg, state, {
|
|
7121
7762
|
componentId: layoutId,
|
|
7122
7763
|
html,
|
|
7123
|
-
requireScripts
|
|
7764
|
+
requireScripts,
|
|
7124
7765
|
requireAssets: void 0,
|
|
7125
7766
|
compositionRegistry: await compositionFor(state, discovery, cfg.patternDirs),
|
|
7126
7767
|
discoveryMap
|
|
@@ -7133,7 +7774,8 @@ async function pageStylesheets(cfg, state, discovery, layoutId, html) {
|
|
|
7133
7774
|
themeSwitch: cfg.themeSwitch,
|
|
7134
7775
|
baseCssVersion: state.baseCssVersion
|
|
7135
7776
|
});
|
|
7136
|
-
|
|
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");
|
|
7137
7779
|
}
|
|
7138
7780
|
|
|
7139
7781
|
// packages/dev-server/src/routes.ts
|
|
@@ -7147,9 +7789,31 @@ async function handleRequest(req, res, ctx) {
|
|
|
7147
7789
|
return;
|
|
7148
7790
|
}
|
|
7149
7791
|
const host = (req.headers.host ?? "").replace(/:\d+$/, "").toLowerCase();
|
|
7150
|
-
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
|
+
}
|
|
7151
7796
|
return routeRequest(req, res, ctx);
|
|
7152
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
|
+
}
|
|
7153
7817
|
function holdResponse() {
|
|
7154
7818
|
let status = 200;
|
|
7155
7819
|
const headers = {};
|
|
@@ -7184,12 +7848,12 @@ function holdResponse() {
|
|
|
7184
7848
|
var stampCaches = /* @__PURE__ */ new WeakMap();
|
|
7185
7849
|
function hashOfFor(req, ctx) {
|
|
7186
7850
|
const generation = ctx.generation?.() ?? "";
|
|
7187
|
-
let
|
|
7188
|
-
if (!
|
|
7189
|
-
|
|
7190
|
-
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);
|
|
7191
7855
|
}
|
|
7192
|
-
const { hashes } =
|
|
7856
|
+
const { hashes } = cache2;
|
|
7193
7857
|
return (path) => {
|
|
7194
7858
|
let hash = hashes.get(path);
|
|
7195
7859
|
if (!hash) {
|
|
@@ -7215,8 +7879,13 @@ async function stampedResponse(req, res, ctx) {
|
|
|
7215
7879
|
await routeRequest(req, held.res, ctx);
|
|
7216
7880
|
const type = String(held.headers["content-type"] ?? "");
|
|
7217
7881
|
let body = held.body();
|
|
7218
|
-
|
|
7219
|
-
|
|
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);
|
|
7220
7889
|
res.writeHead(held.status(), held.headers);
|
|
7221
7890
|
res.end(body);
|
|
7222
7891
|
}
|
|
@@ -7265,10 +7934,10 @@ async function routeRequest(req, res, ctx) {
|
|
|
7265
7934
|
return;
|
|
7266
7935
|
}
|
|
7267
7936
|
if (pathname === PD_ROUTES.mainJs) {
|
|
7268
|
-
return await serveFile(res,
|
|
7937
|
+
return await serveFile(res, join25(devUiDist2, "main.js"), "application/javascript");
|
|
7269
7938
|
}
|
|
7270
7939
|
if (pathname === PD_ROUTES.mainCss) {
|
|
7271
|
-
return await serveFile(res,
|
|
7940
|
+
return await serveFile(res, join25(devUiDist2, "main.css"), "text/css");
|
|
7272
7941
|
}
|
|
7273
7942
|
if (pathname === PD_ROUTES.axeCore) {
|
|
7274
7943
|
return await serveFile(res, resolveAxeCore(import.meta.url), "application/javascript");
|
|
@@ -7586,7 +8255,7 @@ async function routeRequest(req, res, ctx) {
|
|
|
7586
8255
|
if (cfg.storefront && host === cfg.storefront.host) {
|
|
7587
8256
|
const discovery = await currentDiscovery(state, patternRoots);
|
|
7588
8257
|
const discoveryMap = discovery.discoveryMap;
|
|
7589
|
-
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" });
|
|
7590
8259
|
if (pathname === PD_ROUTES.pages) {
|
|
7591
8260
|
if (model.problems.length) {
|
|
7592
8261
|
json(res, 500, { error: model.problems.join("\n") });
|
|
@@ -7595,8 +8264,8 @@ async function routeRequest(req, res, ctx) {
|
|
|
7595
8264
|
json(res, 200, { pages: model.pages, nav: model.nav });
|
|
7596
8265
|
return;
|
|
7597
8266
|
}
|
|
7598
|
-
const siteUrl = cfg.storefront.url === void 0 ? void 0 : ctx.live ? `http://${req.headers.host ?? cfg.storefront.host}
|
|
7599
|
-
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 === "/") {
|
|
7600
8269
|
res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
|
|
7601
8270
|
res.end(storefrontRobotsTxt({ siteUrl, index: cfg.storefront.index, workbenchPath: sameHostWorkbenchPath(cfg.storefront) }));
|
|
7602
8271
|
return;
|
|
@@ -7635,13 +8304,15 @@ async function routeRequest(req, res, ctx) {
|
|
|
7635
8304
|
render,
|
|
7636
8305
|
site: ctx.site.get(),
|
|
7637
8306
|
workbenchUrl: ctx.storefrontWorkbenchUrl ?? `http://${PD_HOSTS.workbench}${port2 ? `:${port2}` : ""}/`,
|
|
7638
|
-
head: (layoutId, html) => pageStylesheets(cfg, state, discovery, layoutId, html),
|
|
8307
|
+
head: (layoutId, html, placed) => pageStylesheets(cfg, state, discovery, layoutId, html, placed),
|
|
7639
8308
|
...ctx.dates ? { dates: ctx.dates } : {},
|
|
7640
8309
|
...ctx.live ? { live: { wsPort, generation: generation ?? "" } } : {},
|
|
7641
8310
|
image: (path) => checkPageImage(path, assetRoots, assetRootNames),
|
|
7642
8311
|
...siteUrl !== void 0 ? { siteUrl } : {},
|
|
7643
8312
|
lang: cfg.storefront.lang,
|
|
7644
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 } } : {},
|
|
7645
8316
|
...cfg.storefront.image !== void 0 ? { siteImage: cfg.storefront.image } : {},
|
|
7646
8317
|
...ctx.images ? { images: ctx.images } : {}
|
|
7647
8318
|
});
|
|
@@ -7734,7 +8405,7 @@ function createRenderingHandle(build, registrars, globalData) {
|
|
|
7734
8405
|
|
|
7735
8406
|
// packages/dev-server/src/watch-roots.ts
|
|
7736
8407
|
import { statSync as statSync3 } from "node:fs";
|
|
7737
|
-
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";
|
|
7738
8409
|
var MODULE = /\.[mc]?js$/;
|
|
7739
8410
|
function helperScope(path) {
|
|
7740
8411
|
let isDir = false;
|
|
@@ -7753,23 +8424,23 @@ function watchedRoots(cfg) {
|
|
|
7753
8424
|
styles: [...projectStyles, ...packageStyles].map((r) => ({ dir: r.dir, inBase: documentBaseMatcher(r.dir, r.base) })),
|
|
7754
8425
|
themes: [
|
|
7755
8426
|
...cfg.config.themes?.dir && cfg.themesDir ? [cfg.themesDir] : [],
|
|
7756
|
-
...[...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))
|
|
7757
8428
|
],
|
|
7758
8429
|
scripts: [
|
|
7759
8430
|
...cfg.config.scriptsDir && cfg.scriptsDirs[0] ? [cfg.scriptsDirs[0]] : [],
|
|
7760
|
-
...[...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))
|
|
7761
8432
|
],
|
|
7762
8433
|
data: [
|
|
7763
8434
|
...cfg.config.dataDir ? [resolve9(cfg.cwd, cfg.config.dataDir)] : [],
|
|
7764
|
-
...[...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))
|
|
7765
8436
|
],
|
|
7766
8437
|
assets: [
|
|
7767
8438
|
...[cfg.config.assets?.dir ?? []].flat().map((d) => resolve9(cfg.cwd, d)),
|
|
7768
|
-
...[...linked.values()].flatMap((p) => (p.manifest.assets ?? []).map((a) =>
|
|
8439
|
+
...[...linked.values()].flatMap((p) => (p.manifest.assets ?? []).map((a) => join26(p.dir, a)))
|
|
7769
8440
|
],
|
|
7770
8441
|
helpers: [
|
|
7771
8442
|
...cfg.config.helpers ? [resolve9(cfg.cwd, cfg.config.helpers)] : [],
|
|
7772
|
-
...[...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))
|
|
7773
8444
|
].map(helperScope)
|
|
7774
8445
|
};
|
|
7775
8446
|
}
|
|
@@ -7800,10 +8471,10 @@ function rootMatchers(watched) {
|
|
|
7800
8471
|
}
|
|
7801
8472
|
|
|
7802
8473
|
// packages/dev-server/src/watch.ts
|
|
7803
|
-
import { createHash as
|
|
8474
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
7804
8475
|
import { existsSync as existsSync6, readFileSync as readFileSync4, statSync as statSync4 } from "node:fs";
|
|
7805
8476
|
import { appendFile, readdir as readdir9 } from "node:fs/promises";
|
|
7806
|
-
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";
|
|
7807
8478
|
import chokidar from "chokidar";
|
|
7808
8479
|
|
|
7809
8480
|
// packages/dev-server/src/config-change.ts
|
|
@@ -7862,7 +8533,7 @@ function seenNow(path) {
|
|
|
7862
8533
|
const s = statSync4(path);
|
|
7863
8534
|
const stamp2 = `${s.ino}:${s.ctimeMs}:${s.mtimeMs}:${s.size}`;
|
|
7864
8535
|
const forced = globalThis.__testOnly_watchStamp;
|
|
7865
|
-
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") };
|
|
7866
8537
|
} catch {
|
|
7867
8538
|
return null;
|
|
7868
8539
|
}
|
|
@@ -7893,7 +8564,7 @@ async function startWatching(ctx) {
|
|
|
7893
8564
|
const barrierWaiters = /* @__PURE__ */ new Set();
|
|
7894
8565
|
const SETTLE_MS = 1e4;
|
|
7895
8566
|
async function settle() {
|
|
7896
|
-
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));
|
|
7897
8568
|
const waiting = new Set(files);
|
|
7898
8569
|
const handled = new Promise((resolve15, reject) => {
|
|
7899
8570
|
if (!waiting.size) {
|
|
@@ -8151,7 +8822,7 @@ async function startWatching(ctx) {
|
|
|
8151
8822
|
}
|
|
8152
8823
|
for (const entry of entries) {
|
|
8153
8824
|
if (closed) return true;
|
|
8154
|
-
const path =
|
|
8825
|
+
const path = join27(dir, entry.name);
|
|
8155
8826
|
if (entry.isDirectory()) {
|
|
8156
8827
|
if (firstReport(path)) dispatchers.addDir?.(path);
|
|
8157
8828
|
await scanFolder(path);
|
|
@@ -8261,7 +8932,11 @@ async function startWatching(ctx) {
|
|
|
8261
8932
|
const compId = componentOfScss(resolve10(recompiledFile), allIds);
|
|
8262
8933
|
const root = styleRootOf(resolve10(recompiledFile));
|
|
8263
8934
|
if (compId && root) {
|
|
8264
|
-
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
|
+
}
|
|
8265
8940
|
if (newCss !== null) {
|
|
8266
8941
|
const wasEmpty = (state.perComponentCss.get(compId) ?? "") === "";
|
|
8267
8942
|
state.perComponentCss.set(compId, newCss);
|
|
@@ -8463,7 +9138,7 @@ async function startWatching(ctx) {
|
|
|
8463
9138
|
async function pagesFingerprint() {
|
|
8464
9139
|
if (!pagesDir) return null;
|
|
8465
9140
|
const model = await readPages(pagesDir, { workbenchPath });
|
|
8466
|
-
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] });
|
|
8467
9142
|
}
|
|
8468
9143
|
async function handlePages(filePath, event) {
|
|
8469
9144
|
if (!inPages(filePath)) return false;
|
|
@@ -8768,7 +9443,7 @@ function createGitDates(pagesDir, listPages, git = runGit) {
|
|
|
8768
9443
|
|
|
8769
9444
|
// packages/dev-server/src/export-io.ts
|
|
8770
9445
|
import { readFile as readFile25, writeFile as writeFile6, mkdir as mkdir6 } from "node:fs/promises";
|
|
8771
|
-
import { dirname as dirname10, join as
|
|
9446
|
+
import { dirname as dirname10, join as join28, relative as relative15, resolve as resolve11 } from "node:path";
|
|
8772
9447
|
|
|
8773
9448
|
// packages/dev-server/src/export.ts
|
|
8774
9449
|
var escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -8961,20 +9636,20 @@ async function writeExport(families, sink) {
|
|
|
8961
9636
|
const files = [];
|
|
8962
9637
|
for (const family of families) {
|
|
8963
9638
|
const baseRel = `${family.baseId}.hbs`;
|
|
8964
|
-
const basePath =
|
|
9639
|
+
const basePath = join28(sink, baseRel);
|
|
8965
9640
|
await mkdir6(dirname10(basePath), { recursive: true });
|
|
8966
9641
|
await writeFile6(basePath, family.baseHbs, "utf-8");
|
|
8967
9642
|
files.push(baseRel);
|
|
8968
9643
|
if (family.baseData) {
|
|
8969
9644
|
const dataRel = `${family.baseId}.json`;
|
|
8970
|
-
await writeFile6(
|
|
9645
|
+
await writeFile6(join28(sink, dataRel), JSON.stringify(family.baseData, null, 2) + "\n", "utf-8");
|
|
8971
9646
|
files.push(dataRel);
|
|
8972
9647
|
}
|
|
8973
9648
|
for (const v of family.variants) {
|
|
8974
9649
|
if (v.token === "") continue;
|
|
8975
9650
|
const overlayRel = `${family.baseId}~${v.token}.json`;
|
|
8976
9651
|
const overlay = { modifier: v.modifier, ...v.data ?? {} };
|
|
8977
|
-
await writeFile6(
|
|
9652
|
+
await writeFile6(join28(sink, overlayRel), JSON.stringify(overlay, null, 2) + "\n", "utf-8");
|
|
8978
9653
|
files.push(overlayRel);
|
|
8979
9654
|
}
|
|
8980
9655
|
}
|
|
@@ -9006,7 +9681,7 @@ async function writeThreadedIncluders(threaded, patternsDir, sink) {
|
|
|
9006
9681
|
if (res.rewrites === 0) continue;
|
|
9007
9682
|
rewrites += res.rewrites;
|
|
9008
9683
|
const rel = relative15(patternsDir, hbsPath);
|
|
9009
|
-
const outPath =
|
|
9684
|
+
const outPath = join28(sink, rel);
|
|
9010
9685
|
await mkdir6(dirname10(outPath), { recursive: true });
|
|
9011
9686
|
await writeFile6(outPath, res.hbs, "utf-8");
|
|
9012
9687
|
files.push(rel);
|
|
@@ -9162,6 +9837,16 @@ async function startDevServer(config, cwd, opts = {}) {
|
|
|
9162
9837
|
build: opts.live === false,
|
|
9163
9838
|
warn: (message) => console.warn(`[dev-server] ${message}`)
|
|
9164
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
|
+
};
|
|
9165
9850
|
const buildRenderer = (registrars, data) => {
|
|
9166
9851
|
const renderWith = createRenderer({
|
|
9167
9852
|
patternsDir,
|
|
@@ -9175,7 +9860,9 @@ async function startDevServer(config, cwd, opts = {}) {
|
|
|
9175
9860
|
});
|
|
9176
9861
|
return async (input) => {
|
|
9177
9862
|
await images.ready(generationNow());
|
|
9178
|
-
|
|
9863
|
+
const result = await renderWith(input);
|
|
9864
|
+
reportHelperNames(result.meta?.diagnostics);
|
|
9865
|
+
return result;
|
|
9179
9866
|
};
|
|
9180
9867
|
};
|
|
9181
9868
|
const rendering = createRenderingHandle(buildRenderer, helperRegistrars, globalData);
|
|
@@ -9199,7 +9886,7 @@ async function startDevServer(config, cwd, opts = {}) {
|
|
|
9199
9886
|
if (cfg.themeDirs.length) {
|
|
9200
9887
|
for (const { name, dir } of await discoverThemeFiles(cfg.themeDirs)) {
|
|
9201
9888
|
try {
|
|
9202
|
-
const css = await compileSassFile(
|
|
9889
|
+
const css = await compileSassFile(join29(dir, `${name}.theme.scss`));
|
|
9203
9890
|
state.themeCss.set(name, css);
|
|
9204
9891
|
const slots = config.themes?.dir && dir === cfg.themeDirs[0] ? swatchSlots : cfg.packageSwatchSlots;
|
|
9205
9892
|
state.themeTokens.set(name, await extractThemeTokens(name, css, slots, dir));
|
|
@@ -9275,7 +9962,7 @@ async function startDevServer(config, cwd, opts = {}) {
|
|
|
9275
9962
|
httpPort = actualPort;
|
|
9276
9963
|
console.log(`[dev-server] Listening at http://127.0.0.1:${actualPort}`);
|
|
9277
9964
|
if (live) {
|
|
9278
|
-
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}`);
|
|
9279
9966
|
console.log(`[dev-server] Workbench: http://${cfg.storefront ? PD_HOSTS.workbench : "localhost"}:${actualPort}/`);
|
|
9280
9967
|
}
|
|
9281
9968
|
const startupDiscovery = await cachedDiscovery(state);
|
|
@@ -9320,10 +10007,10 @@ async function startDevServer(config, cwd, opts = {}) {
|
|
|
9320
10007
|
import { build as esbuild } from "esbuild";
|
|
9321
10008
|
import { existsSync as existsSync7 } from "node:fs";
|
|
9322
10009
|
import { readFile as readFile26, writeFile as writeFile7, unlink, mkdir as mkdir7 } from "node:fs/promises";
|
|
9323
|
-
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";
|
|
9324
10011
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
9325
|
-
import { createHash as
|
|
9326
|
-
import { pathToFileURL as
|
|
10012
|
+
import { createHash as createHash8, randomUUID as randomUUID3 } from "node:crypto";
|
|
10013
|
+
import { pathToFileURL as pathToFileURL3 } from "node:url";
|
|
9327
10014
|
async function runDev(root, opts, configPath) {
|
|
9328
10015
|
const config = await loadConfig(root, configPath);
|
|
9329
10016
|
const file = configFileOf(root, configPath);
|
|
@@ -9336,6 +10023,7 @@ async function runBuild(root, configPath, options) {
|
|
|
9336
10023
|
outDir,
|
|
9337
10024
|
...options.base !== void 0 ? { basePath: options.base } : {},
|
|
9338
10025
|
...options.audit !== void 0 ? { audit: options.audit } : {},
|
|
10026
|
+
...options.browser ? { browser: true } : {},
|
|
9339
10027
|
pdVersion: options.pdVersion,
|
|
9340
10028
|
log: (line) => console.log(`[pd build] ${line}`)
|
|
9341
10029
|
});
|
|
@@ -9343,16 +10031,24 @@ async function runBuild(root, configPath, options) {
|
|
|
9343
10031
|
console.log(`[pd build] ${report.components} components \u2192 ${report.files} files, ${mb} MB in ${report.ms} ms`);
|
|
9344
10032
|
console.log(`[pd build] ${report.outDir} (served from ${report.basePath})`);
|
|
9345
10033
|
if (report.storefront) {
|
|
9346
|
-
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})`);
|
|
9347
10035
|
}
|
|
9348
10036
|
if (report.links) {
|
|
9349
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)` : ""));
|
|
9350
10038
|
}
|
|
9351
10039
|
if (report.accessibility) {
|
|
9352
10040
|
const n = report.accessibility.warnings.length;
|
|
9353
|
-
console.log(`[pd build] accessibility: ${report.accessibility.pages} pages, no errors` + (n ? `; ${n} rule${n === 1 ? "" : "s"} with warnings:` : ""));
|
|
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:` : ""));
|
|
9354
10042
|
for (const w of report.accessibility.warnings) console.log(`[pd build] warning: ${w}`);
|
|
9355
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
|
+
}
|
|
9356
10052
|
for (const w of report.warnings) console.log(`[pd build] not built: ${w}`);
|
|
9357
10053
|
for (const n of report.notes) console.log(`[pd build] note: ${n}`);
|
|
9358
10054
|
}
|
|
@@ -9361,7 +10057,7 @@ async function runAudit(root, configPath, format = "md") {
|
|
|
9361
10057
|
const { graph, reaudited, reused, artifactPath, imageWarning } = await runDiagnosticsAudit(config, root);
|
|
9362
10058
|
const report = formatAudit(graph, format);
|
|
9363
10059
|
const ext = format === "json" ? "json" : format === "text" ? "txt" : "md";
|
|
9364
|
-
const reportPath =
|
|
10060
|
+
const reportPath = join30(dirname11(artifactPath), `diagnostics-report.${ext}`);
|
|
9365
10061
|
await writeFile7(reportPath, report, "utf-8");
|
|
9366
10062
|
console.log(report);
|
|
9367
10063
|
const meta = format === "json" ? console.error : console.log;
|
|
@@ -9379,8 +10075,8 @@ function auditSummaryLines(r) {
|
|
|
9379
10075
|
];
|
|
9380
10076
|
}
|
|
9381
10077
|
function defaultExportDir(root) {
|
|
9382
|
-
const key =
|
|
9383
|
-
return
|
|
10078
|
+
const key = createHash8("sha1").update(resolve12(root)).digest("hex").slice(0, 16);
|
|
10079
|
+
return join30(tmpdir2(), "pattern-dump", key, "export");
|
|
9384
10080
|
}
|
|
9385
10081
|
function assertOutsideRoot(outDir, root) {
|
|
9386
10082
|
const rel = relative16(resolve12(root), resolve12(outDir));
|
|
@@ -9457,18 +10153,30 @@ async function loadConfig(root, configPath) {
|
|
|
9457
10153
|
if (!existsSync7(configPath)) {
|
|
9458
10154
|
throw new ConfigError(`--config file not found: ${configPath}`);
|
|
9459
10155
|
}
|
|
9460
|
-
return validateConfig(await readConfigFile(configPath), configPath);
|
|
10156
|
+
return validateConfig(await withRootManifest(root, await readConfigFile(configPath), configPath), configPath);
|
|
9461
10157
|
}
|
|
9462
10158
|
const path = configFileOf(root);
|
|
9463
|
-
if (path) return validateConfig(await readConfigFile(path), basename11(path));
|
|
10159
|
+
if (path) return validateConfig(await withRootManifest(root, await readConfigFile(path), basename11(path)), basename11(path));
|
|
9464
10160
|
throw new ConfigError(
|
|
9465
10161
|
`No pattern-dump.config.ts or pattern-dump.config.json found in ${root} (and no --config given)`
|
|
9466
10162
|
);
|
|
9467
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
|
+
}
|
|
9468
10176
|
function configFileOf(root, configPath) {
|
|
9469
10177
|
if (configPath) return configPath;
|
|
9470
10178
|
for (const name of CONFIG_NAMES) {
|
|
9471
|
-
const path =
|
|
10179
|
+
const path = join30(root, name);
|
|
9472
10180
|
if (existsSync7(path)) return path;
|
|
9473
10181
|
}
|
|
9474
10182
|
return void 0;
|
|
@@ -9478,7 +10186,7 @@ async function readConfigFile(path) {
|
|
|
9478
10186
|
return path.endsWith(".json") ? JSON.parse(await readFile26(path, "utf-8")) : loadTsConfig(path);
|
|
9479
10187
|
}
|
|
9480
10188
|
async function loadTsConfig(tsPath) {
|
|
9481
|
-
const tmp =
|
|
10189
|
+
const tmp = join30(tmpdir2(), `pd-config-${process.pid}-${randomUUID3()}.mjs`);
|
|
9482
10190
|
try {
|
|
9483
10191
|
await esbuild({
|
|
9484
10192
|
entryPoints: [tsPath],
|
|
@@ -9488,7 +10196,7 @@ async function loadTsConfig(tsPath) {
|
|
|
9488
10196
|
// No packages:"external" — the temp file runs from /tmp so we must inline deps.
|
|
9489
10197
|
outfile: tmp
|
|
9490
10198
|
});
|
|
9491
|
-
const { default: cfg } = await import(
|
|
10199
|
+
const { default: cfg } = await import(pathToFileURL3(tmp).href);
|
|
9492
10200
|
return cfg;
|
|
9493
10201
|
} finally {
|
|
9494
10202
|
await unlink(tmp).catch(() => {
|
|
@@ -9568,7 +10276,8 @@ function parseCommand(argv, cwd) {
|
|
|
9568
10276
|
out: { type: "string" },
|
|
9569
10277
|
base: { type: "string" },
|
|
9570
10278
|
audit: { type: "boolean" },
|
|
9571
|
-
"no-audit": { type: "boolean" }
|
|
10279
|
+
"no-audit": { type: "boolean" },
|
|
10280
|
+
browser: { type: "boolean" }
|
|
9572
10281
|
}
|
|
9573
10282
|
});
|
|
9574
10283
|
if (values2.help) return { kind: "help", topic: cmd };
|
|
@@ -9579,7 +10288,8 @@ function parseCommand(argv, cwd) {
|
|
|
9579
10288
|
...base(values2, cwd),
|
|
9580
10289
|
...values2.out !== void 0 ? { out: values2.out } : {},
|
|
9581
10290
|
...values2.base !== void 0 ? { base: values2.base } : {},
|
|
9582
|
-
...audit !== void 0 ? { audit } : {}
|
|
10291
|
+
...audit !== void 0 ? { audit } : {},
|
|
10292
|
+
...values2.browser ? { browser: true } : {}
|
|
9583
10293
|
};
|
|
9584
10294
|
}
|
|
9585
10295
|
const { values } = parseArgs({
|
|
@@ -9676,6 +10386,9 @@ var USAGE = `Usage: pd <command> [options]
|
|
|
9676
10386
|
--config <path> Use a config file outside the root
|
|
9677
10387
|
--out <dir> Output folder, relative to the root (default: build.outDir, outDir, or dist)
|
|
9678
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)
|
|
9679
10392
|
--no-audit Leave the corpus audit out: it is not run, and the site has no Audit tab
|
|
9680
10393
|
(default: build.audit in the config; --audit forces it back in)
|
|
9681
10394
|
|
|
@@ -9689,12 +10402,12 @@ var USAGE = `Usage: pd <command> [options]
|
|
|
9689
10402
|
// packages/cli/src/init/run.ts
|
|
9690
10403
|
import { existsSync as existsSync8 } from "node:fs";
|
|
9691
10404
|
import { readFile as readFile28 } from "node:fs/promises";
|
|
9692
|
-
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";
|
|
9693
10406
|
|
|
9694
10407
|
// packages/cli/package.json
|
|
9695
10408
|
var package_default = {
|
|
9696
10409
|
name: "@pattern-dump/cli",
|
|
9697
|
-
version: "0.
|
|
10410
|
+
version: "0.4.0",
|
|
9698
10411
|
private: true,
|
|
9699
10412
|
description: "The pd command and the published pattern-dump package.",
|
|
9700
10413
|
type: "module",
|
|
@@ -9730,12 +10443,12 @@ var PD_VERSION = package_default.version;
|
|
|
9730
10443
|
|
|
9731
10444
|
// packages/cli/src/init/files.ts
|
|
9732
10445
|
import { mkdir as mkdir8, readdir as readdir10, readFile as readFile27, writeFile as writeFile8 } from "node:fs/promises";
|
|
9733
|
-
import { dirname as dirname12, join as
|
|
10446
|
+
import { dirname as dirname12, join as join31, relative as relative17, sep as sep12 } from "node:path";
|
|
9734
10447
|
async function readTemplateDir(dir) {
|
|
9735
10448
|
const out = [];
|
|
9736
10449
|
async function walk(current) {
|
|
9737
10450
|
for (const entry of await readdir10(current, { withFileTypes: true })) {
|
|
9738
|
-
const full =
|
|
10451
|
+
const full = join31(current, entry.name);
|
|
9739
10452
|
if (entry.isDirectory()) await walk(full);
|
|
9740
10453
|
else out.push({ path: relative17(dir, full).split(sep12).join("/"), content: await readFile27(full, "utf8") });
|
|
9741
10454
|
}
|
|
@@ -9744,11 +10457,11 @@ async function readTemplateDir(dir) {
|
|
|
9744
10457
|
return out.sort((a, b) => a.path.localeCompare(b.path));
|
|
9745
10458
|
}
|
|
9746
10459
|
async function readTemplates(templatesDir) {
|
|
9747
|
-
const fill = (name) => readFile27(
|
|
10460
|
+
const fill = (name) => readFile27(join31(templatesDir, "storefront-fills", name), "utf8");
|
|
9748
10461
|
const [base2, examples, storefront, config, readme, gitignore] = await Promise.all([
|
|
9749
|
-
readTemplateDir(
|
|
9750
|
-
readTemplateDir(
|
|
9751
|
-
readTemplateDir(
|
|
10462
|
+
readTemplateDir(join31(templatesDir, "base")),
|
|
10463
|
+
readTemplateDir(join31(templatesDir, "examples")),
|
|
10464
|
+
readTemplateDir(join31(templatesDir, "storefront")),
|
|
9752
10465
|
fill("config.txt"),
|
|
9753
10466
|
fill("readme.md"),
|
|
9754
10467
|
fill("gitignore.txt")
|
|
@@ -9758,12 +10471,12 @@ async function readTemplates(templatesDir) {
|
|
|
9758
10471
|
async function applyPlan(target, plan) {
|
|
9759
10472
|
await mkdir8(target, { recursive: true });
|
|
9760
10473
|
for (const file of plan.writes) {
|
|
9761
|
-
const abs =
|
|
10474
|
+
const abs = join31(target, file.path);
|
|
9762
10475
|
await mkdir8(dirname12(abs), { recursive: true });
|
|
9763
10476
|
await writeFile8(abs, file.content, { flag: "wx" });
|
|
9764
10477
|
}
|
|
9765
|
-
await writeFile8(
|
|
9766
|
-
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 });
|
|
9767
10480
|
}
|
|
9768
10481
|
|
|
9769
10482
|
// packages/cli/src/init/plan.ts
|
|
@@ -9885,7 +10598,7 @@ async function runInit(cmd, io) {
|
|
|
9885
10598
|
} else {
|
|
9886
10599
|
dependency = dependencySpec(void 0, io.cwd);
|
|
9887
10600
|
}
|
|
9888
|
-
const pkgPath =
|
|
10601
|
+
const pkgPath = join32(target, "package.json");
|
|
9889
10602
|
let packageJson = null;
|
|
9890
10603
|
if (existsSync8(pkgPath)) {
|
|
9891
10604
|
try {
|
|
@@ -9906,7 +10619,7 @@ async function runInit(cmd, io) {
|
|
|
9906
10619
|
const install = cmd.install ?? (prompt ? await io.confirm("Run npm install", true) : true);
|
|
9907
10620
|
const answers = { name, brand, examples, storefront, dependency };
|
|
9908
10621
|
const templates = await readTemplates(io.templatesDir);
|
|
9909
|
-
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);
|
|
9910
10623
|
if (!result.ok) {
|
|
9911
10624
|
io.log(`\u2717 ${result.reason}`);
|
|
9912
10625
|
return 1;
|
|
@@ -9955,7 +10668,7 @@ async function runInit(cmd, io) {
|
|
|
9955
10668
|
|
|
9956
10669
|
// packages/cli/src/init/local-build.ts
|
|
9957
10670
|
import { existsSync as existsSync9, statSync as statSync5 } from "node:fs";
|
|
9958
|
-
import { join as
|
|
10671
|
+
import { join as join33 } from "node:path";
|
|
9959
10672
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
9960
10673
|
var BUILT = [
|
|
9961
10674
|
"packages/cli/dist/index.js",
|
|
@@ -9973,10 +10686,10 @@ var mtime = (path) => {
|
|
|
9973
10686
|
};
|
|
9974
10687
|
function detectLocalBuild(moduleUrl, packageName, version) {
|
|
9975
10688
|
const repo = fileURLToPath7(new URL("../../../", moduleUrl)).replace(/[\\/]$/, "");
|
|
9976
|
-
if (!existsSync9(
|
|
9977
|
-
const tarball =
|
|
10689
|
+
if (!existsSync9(join33(repo, "packages/cli/src/distribution.ts"))) return null;
|
|
10690
|
+
const tarball = join33(repo, ".pd-pack", `${packageName}-${version}.tgz`);
|
|
9978
10691
|
if (!existsSync9(tarball)) return { repo, tarball: null, stale: false };
|
|
9979
|
-
const newestBuild = Math.max(...BUILT.map((p) => mtime(
|
|
10692
|
+
const newestBuild = Math.max(...BUILT.map((p) => mtime(join33(repo, p))));
|
|
9980
10693
|
return { repo, tarball, stale: newestBuild > mtime(tarball) };
|
|
9981
10694
|
}
|
|
9982
10695
|
|
|
@@ -10043,6 +10756,7 @@ switch (command.kind) {
|
|
|
10043
10756
|
...command.out !== void 0 ? { out: command.out } : {},
|
|
10044
10757
|
...command.base !== void 0 ? { base: command.base } : {},
|
|
10045
10758
|
...command.audit !== void 0 ? { audit: command.audit } : {},
|
|
10759
|
+
...command.browser ? { browser: true } : {},
|
|
10046
10760
|
pdVersion: PD_VERSION
|
|
10047
10761
|
}).then(() => process.stdout.write("", () => process.exit(0))).catch((e) => {
|
|
10048
10762
|
console.error("[pd] Error:", e instanceof Error ? e.message : e);
|