@zeropress/build-pages 0.5.6 → 0.6.1
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/README.md +65 -11
- package/dist/action.js +1561 -299
- package/dist/prebuild.js +135 -23
- package/package.json +2 -2
- package/schemas/zeropress-build-pages.config.v0.1.schema.json +26 -12
- package/src/action.js +1 -1
- package/src/prebuild.js +149 -22
- package/themes/docs/layout.html +1 -1
- package/themes/docs/theme.json +4 -4
package/dist/prebuild.js
CHANGED
|
@@ -3534,7 +3534,12 @@ var skipUntitledMarkdown = readBooleanEnv("ZEROPRESS_SKIP_UNTITLED_MARKDOWN");
|
|
|
3534
3534
|
var copyMarkdownSource = readBooleanEnv("ZEROPRESS_COPY_MARKDOWN_SOURCE", true);
|
|
3535
3535
|
var FRONT_PAGE_TYPES = /* @__PURE__ */ new Set(["theme_index", "markdown", "html"]);
|
|
3536
3536
|
var BUILD_PAGES_CONFIG_SCHEMA_URL = "https://zeropress.dev/schemas/zeropress-build-pages.config.v0.1.schema.json";
|
|
3537
|
-
var PREVIEW_DATA_SCHEMA_URL = "https://zeropress.dev/schemas/preview-data.v0.
|
|
3537
|
+
var PREVIEW_DATA_SCHEMA_URL = "https://zeropress.dev/schemas/preview-data.v0.6.schema.json";
|
|
3538
|
+
var FRONT_MATTER_DATA_KEY_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*(?:-[a-zA-Z0-9_]+)*$/;
|
|
3539
|
+
var FRONT_MATTER_DATA_MAX_DEPTH = 4;
|
|
3540
|
+
var FRONT_MATTER_DATA_MAX_KEYS = 64;
|
|
3541
|
+
var FRONT_MATTER_DATA_MAX_ARRAY_LENGTH = 256;
|
|
3542
|
+
var FRONT_MATTER_DISCOVERABILITY_VALUES = /* @__PURE__ */ new Set(["default", "noindex", "delist"]);
|
|
3538
3543
|
var PrebuildMarkdownError = class extends Error {
|
|
3539
3544
|
constructor(sourcePath, reason, expected = "", code = "invalid_markdown") {
|
|
3540
3545
|
super(reason);
|
|
@@ -3608,6 +3613,8 @@ async function main() {
|
|
|
3608
3613
|
...frontMatter.meta,
|
|
3609
3614
|
...copyMarkdownSource ? { source_markdown_url: buildSourceMarkdownUrl(sourcePath) } : {}
|
|
3610
3615
|
},
|
|
3616
|
+
...frontMatter.data !== void 0 ? { data: frontMatter.data } : {},
|
|
3617
|
+
...frontMatter.discoverability !== "default" ? { discoverability: frontMatter.discoverability } : {},
|
|
3611
3618
|
content: rewriteMarkdownLinks(bodyMarkdown, sourcePath, routeBySourcePath),
|
|
3612
3619
|
document_type: "markdown",
|
|
3613
3620
|
excerpt: frontMatter.description || extractExcerpt(bodyMarkdown, title),
|
|
@@ -3621,7 +3628,7 @@ async function main() {
|
|
|
3621
3628
|
const customHtml = await buildCustomHtmlData(customHtmlConfig);
|
|
3622
3629
|
const previewData = {
|
|
3623
3630
|
$schema: PREVIEW_DATA_SCHEMA_URL,
|
|
3624
|
-
version: "0.
|
|
3631
|
+
version: "0.6",
|
|
3625
3632
|
generator: "zeropress-build-pages",
|
|
3626
3633
|
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3627
3634
|
site,
|
|
@@ -3719,18 +3726,20 @@ function buildSiteData(config, frontPage) {
|
|
|
3719
3726
|
title: configuredSite.title,
|
|
3720
3727
|
description: configuredSite.description,
|
|
3721
3728
|
url: configuredSite.url,
|
|
3722
|
-
|
|
3729
|
+
media_base_url: "",
|
|
3723
3730
|
locale: "en-US",
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3731
|
+
posts_per_page: 10,
|
|
3732
|
+
datetime_display: "static",
|
|
3733
|
+
date_style: "medium",
|
|
3734
|
+
time_style: "none",
|
|
3727
3735
|
timezone: "UTC",
|
|
3728
3736
|
permalinks: defaultPermalinks(),
|
|
3729
3737
|
front_page: frontPage,
|
|
3730
3738
|
post_index: {
|
|
3731
3739
|
enabled: false
|
|
3732
3740
|
},
|
|
3733
|
-
|
|
3741
|
+
disallow_comments: true,
|
|
3742
|
+
expose_generator: configuredSite.expose_generator !== false,
|
|
3734
3743
|
indexing: configuredSite.indexing !== false
|
|
3735
3744
|
};
|
|
3736
3745
|
if (configuredSite.footer) {
|
|
@@ -3759,11 +3768,12 @@ function normalizeSiteConfig(value) {
|
|
|
3759
3768
|
);
|
|
3760
3769
|
}
|
|
3761
3770
|
const configuredSite = isPlainObject(value) ? value : {};
|
|
3762
|
-
assertKnownConfigKeys(configuredSite, ["title", "description", "url", "indexing", "footer"], "site");
|
|
3771
|
+
assertKnownConfigKeys(configuredSite, ["title", "description", "url", "expose_generator", "indexing", "footer"], "site");
|
|
3763
3772
|
const site = {
|
|
3764
3773
|
title: readConfigString(configuredSite.title, "Documentation"),
|
|
3765
3774
|
description: readConfigString(configuredSite.description, "A documentation site."),
|
|
3766
3775
|
url: readEnv("ZEROPRESS_SITE_URL", readConfigString(configuredSite.url, "")),
|
|
3776
|
+
expose_generator: readConfigBoolean(configuredSite.expose_generator, true, "site.expose_generator"),
|
|
3767
3777
|
indexing: readConfigBoolean(configuredSite.indexing, true, "site.indexing")
|
|
3768
3778
|
};
|
|
3769
3779
|
const footer = normalizeFooter(configuredSite.footer);
|
|
@@ -3785,19 +3795,11 @@ function normalizeFooter(value) {
|
|
|
3785
3795
|
if (copyrightText) {
|
|
3786
3796
|
footer.copyright_text = copyrightText;
|
|
3787
3797
|
}
|
|
3788
|
-
if (value.attribution !== void 0
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
if (isPlainObject(value.attribution)) {
|
|
3792
|
-
assertKnownConfigKeys(value.attribution, ["enabled"], "site.footer.attribution");
|
|
3793
|
-
if (value.attribution.enabled !== void 0 && typeof value.attribution.enabled !== "boolean") {
|
|
3794
|
-
throw new PrebuildConfigError("site.footer.attribution.enabled must be a boolean when provided.");
|
|
3798
|
+
if (value.attribution !== void 0) {
|
|
3799
|
+
if (typeof value.attribution !== "boolean") {
|
|
3800
|
+
throw new PrebuildConfigError("site.footer.attribution must be a boolean when provided.");
|
|
3795
3801
|
}
|
|
3796
|
-
|
|
3797
|
-
if (isPlainObject(value.attribution) && typeof value.attribution.enabled === "boolean") {
|
|
3798
|
-
footer.attribution = {
|
|
3799
|
-
enabled: value.attribution.enabled
|
|
3800
|
-
};
|
|
3802
|
+
footer.attribution = value.attribution;
|
|
3801
3803
|
}
|
|
3802
3804
|
return Object.keys(footer).length ? footer : void 0;
|
|
3803
3805
|
}
|
|
@@ -4143,9 +4145,23 @@ function normalizeMenuItem(item, pathLabel) {
|
|
|
4143
4145
|
url,
|
|
4144
4146
|
type: readConfigString(item.type, "custom"),
|
|
4145
4147
|
target: readConfigString(item.target, "_self"),
|
|
4148
|
+
...item.meta !== void 0 ? { meta: normalizeMenuItemMeta(item.meta, `${pathLabel}.meta`) } : {},
|
|
4146
4149
|
children: Array.isArray(item.children) ? item.children.map((child, index) => normalizeMenuItem(child, `${pathLabel}.children[${index}]`)) : []
|
|
4147
4150
|
};
|
|
4148
4151
|
}
|
|
4152
|
+
function normalizeMenuItemMeta(value, pathLabel) {
|
|
4153
|
+
if (!isPlainObject(value)) {
|
|
4154
|
+
throw new PrebuildConfigError(`${pathLabel} must be an object when provided.`);
|
|
4155
|
+
}
|
|
4156
|
+
const meta = {};
|
|
4157
|
+
for (const [key, metaValue] of Object.entries(value)) {
|
|
4158
|
+
if (!isPreviewMetaValue(metaValue)) {
|
|
4159
|
+
throw new PrebuildConfigError(`${pathLabel}.${key} must be a string, number, boolean, or null.`);
|
|
4160
|
+
}
|
|
4161
|
+
meta[key] = metaValue;
|
|
4162
|
+
}
|
|
4163
|
+
return meta;
|
|
4164
|
+
}
|
|
4149
4165
|
function defaultMenus() {
|
|
4150
4166
|
return {
|
|
4151
4167
|
primary: {
|
|
@@ -4262,7 +4278,9 @@ function normalizePublishedFrontMatter(frontMatter, sourcePath) {
|
|
|
4262
4278
|
title: normalizeFrontMatterTitle(frontMatter.title, sourcePath),
|
|
4263
4279
|
description: normalizeFrontMatterDescription(frontMatter.description, sourcePath),
|
|
4264
4280
|
path: normalizeFrontMatterRoutePath(frontMatter.path, sourcePath),
|
|
4265
|
-
|
|
4281
|
+
discoverability: normalizeFrontMatterDiscoverability(frontMatter.discoverability, sourcePath),
|
|
4282
|
+
meta: normalizeFrontMatterMeta(frontMatter.meta, sourcePath),
|
|
4283
|
+
data: normalizeFrontMatterData(frontMatter.data, sourcePath)
|
|
4266
4284
|
};
|
|
4267
4285
|
}
|
|
4268
4286
|
function normalizeFrontMatterTitle(value, sourcePath) {
|
|
@@ -4305,11 +4323,24 @@ function normalizeFrontMatterRoutePath(value, sourcePath) {
|
|
|
4305
4323
|
throw new PrebuildMarkdownError(
|
|
4306
4324
|
sourcePath,
|
|
4307
4325
|
"front matter path must be a safe generated route path.",
|
|
4308
|
-
" path: guides/install\n path: spec/preview-data-v0.
|
|
4326
|
+
" path: guides/install\n path: spec/preview-data-v0.6"
|
|
4309
4327
|
);
|
|
4310
4328
|
}
|
|
4311
4329
|
return routePath;
|
|
4312
4330
|
}
|
|
4331
|
+
function normalizeFrontMatterDiscoverability(value, sourcePath) {
|
|
4332
|
+
if (value === void 0) {
|
|
4333
|
+
return "default";
|
|
4334
|
+
}
|
|
4335
|
+
if (typeof value === "string" && FRONT_MATTER_DISCOVERABILITY_VALUES.has(value)) {
|
|
4336
|
+
return value;
|
|
4337
|
+
}
|
|
4338
|
+
throw new PrebuildMarkdownError(
|
|
4339
|
+
sourcePath,
|
|
4340
|
+
`front matter discoverability must be one of: ${Array.from(FRONT_MATTER_DISCOVERABILITY_VALUES).join(", ")}.`,
|
|
4341
|
+
" discoverability: default\n discoverability: noindex\n discoverability: delist"
|
|
4342
|
+
);
|
|
4343
|
+
}
|
|
4313
4344
|
function isSafeRoutePathSegment(segment) {
|
|
4314
4345
|
return /^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/.test(segment) && !segment.includes("..");
|
|
4315
4346
|
}
|
|
@@ -4336,7 +4367,88 @@ function normalizeFrontMatterMeta(value, sourcePath) {
|
|
|
4336
4367
|
return meta;
|
|
4337
4368
|
}
|
|
4338
4369
|
function isPreviewMetaValue(value) {
|
|
4339
|
-
return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
4370
|
+
return value === null || typeof value === "string" || typeof value === "number" && Number.isFinite(value) || typeof value === "boolean";
|
|
4371
|
+
}
|
|
4372
|
+
function normalizeFrontMatterData(value, sourcePath) {
|
|
4373
|
+
if (value === void 0) {
|
|
4374
|
+
return void 0;
|
|
4375
|
+
}
|
|
4376
|
+
if (!isPlainObject(value)) {
|
|
4377
|
+
throw new PrebuildMarkdownError(
|
|
4378
|
+
sourcePath,
|
|
4379
|
+
"front matter data must be an object when provided."
|
|
4380
|
+
);
|
|
4381
|
+
}
|
|
4382
|
+
validateFrontMatterDataObject(value, sourcePath, "data", 0);
|
|
4383
|
+
return value;
|
|
4384
|
+
}
|
|
4385
|
+
function validateFrontMatterDataValue(value, sourcePath, pathLabel, depth) {
|
|
4386
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") {
|
|
4387
|
+
return;
|
|
4388
|
+
}
|
|
4389
|
+
if (typeof value === "number") {
|
|
4390
|
+
if (!Number.isFinite(value)) {
|
|
4391
|
+
throw new PrebuildMarkdownError(
|
|
4392
|
+
sourcePath,
|
|
4393
|
+
`front matter ${pathLabel} must be a finite number.`
|
|
4394
|
+
);
|
|
4395
|
+
}
|
|
4396
|
+
return;
|
|
4397
|
+
}
|
|
4398
|
+
if (Array.isArray(value)) {
|
|
4399
|
+
validateFrontMatterDataArray(value, sourcePath, pathLabel, depth);
|
|
4400
|
+
return;
|
|
4401
|
+
}
|
|
4402
|
+
if (isPlainObject(value)) {
|
|
4403
|
+
validateFrontMatterDataObject(value, sourcePath, pathLabel, depth);
|
|
4404
|
+
return;
|
|
4405
|
+
}
|
|
4406
|
+
throw new PrebuildMarkdownError(
|
|
4407
|
+
sourcePath,
|
|
4408
|
+
`front matter ${pathLabel} must be JSON-safe structured data.`
|
|
4409
|
+
);
|
|
4410
|
+
}
|
|
4411
|
+
function validateFrontMatterDataObject(object, sourcePath, pathLabel, depth) {
|
|
4412
|
+
if (depth > FRONT_MATTER_DATA_MAX_DEPTH) {
|
|
4413
|
+
throw new PrebuildMarkdownError(
|
|
4414
|
+
sourcePath,
|
|
4415
|
+
`front matter ${pathLabel} nesting must not exceed ${FRONT_MATTER_DATA_MAX_DEPTH} container levels.`
|
|
4416
|
+
);
|
|
4417
|
+
}
|
|
4418
|
+
const entries = Object.entries(object);
|
|
4419
|
+
if (entries.length > FRONT_MATTER_DATA_MAX_KEYS) {
|
|
4420
|
+
throw new PrebuildMarkdownError(
|
|
4421
|
+
sourcePath,
|
|
4422
|
+
`front matter ${pathLabel} must not contain more than ${FRONT_MATTER_DATA_MAX_KEYS} keys.`
|
|
4423
|
+
);
|
|
4424
|
+
}
|
|
4425
|
+
for (const [key, dataValue] of entries) {
|
|
4426
|
+
const childLabel = `${pathLabel}.${key}`;
|
|
4427
|
+
if (!FRONT_MATTER_DATA_KEY_PATTERN.test(key)) {
|
|
4428
|
+
throw new PrebuildMarkdownError(
|
|
4429
|
+
sourcePath,
|
|
4430
|
+
`front matter ${childLabel} uses an invalid key.`
|
|
4431
|
+
);
|
|
4432
|
+
}
|
|
4433
|
+
validateFrontMatterDataValue(dataValue, sourcePath, childLabel, depth + 1);
|
|
4434
|
+
}
|
|
4435
|
+
}
|
|
4436
|
+
function validateFrontMatterDataArray(array, sourcePath, pathLabel, depth) {
|
|
4437
|
+
if (depth > FRONT_MATTER_DATA_MAX_DEPTH) {
|
|
4438
|
+
throw new PrebuildMarkdownError(
|
|
4439
|
+
sourcePath,
|
|
4440
|
+
`front matter ${pathLabel} nesting must not exceed ${FRONT_MATTER_DATA_MAX_DEPTH} container levels.`
|
|
4441
|
+
);
|
|
4442
|
+
}
|
|
4443
|
+
if (array.length > FRONT_MATTER_DATA_MAX_ARRAY_LENGTH) {
|
|
4444
|
+
throw new PrebuildMarkdownError(
|
|
4445
|
+
sourcePath,
|
|
4446
|
+
`front matter ${pathLabel} must not contain more than ${FRONT_MATTER_DATA_MAX_ARRAY_LENGTH} items.`
|
|
4447
|
+
);
|
|
4448
|
+
}
|
|
4449
|
+
array.forEach((dataValue, index) => {
|
|
4450
|
+
validateFrontMatterDataValue(dataValue, sourcePath, `${pathLabel}[${index}]`, depth + 1);
|
|
4451
|
+
});
|
|
4340
4452
|
}
|
|
4341
4453
|
function formatFrontMatterValue(value) {
|
|
4342
4454
|
if (typeof value === "string") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeropress/build-pages",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "ZeroPress Markdown build action and CLI for static hosting",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"node": ">=18.18.0"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@zeropress/build": "0.
|
|
43
|
+
"@zeropress/build": "0.6.1",
|
|
44
44
|
"gray-matter": "4.0.3"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
@@ -50,6 +50,12 @@
|
|
|
50
50
|
"description": "Canonical site URL. Use an empty string or omit this field for local builds without canonical output.",
|
|
51
51
|
"markdownDescription": "Canonical site URL. Use an empty string or omit this field for local builds without canonical output."
|
|
52
52
|
},
|
|
53
|
+
"expose_generator": {
|
|
54
|
+
"type": "boolean",
|
|
55
|
+
"default": true,
|
|
56
|
+
"description": "Whether generated HTML should expose the ZeroPress generator meta tag. Set false for white-label sites.",
|
|
57
|
+
"markdownDescription": "Whether generated HTML should expose `<meta name=\"generator\" content=\"ZeroPress\">`. Set `false` for white-label sites."
|
|
58
|
+
},
|
|
53
59
|
"indexing": {
|
|
54
60
|
"type": "boolean",
|
|
55
61
|
"default": true,
|
|
@@ -74,17 +80,6 @@
|
|
|
74
80
|
"markdownDescription": "Footer copyright or legal text. ZeroPress does not add a copyright symbol automatically."
|
|
75
81
|
},
|
|
76
82
|
"attribution": {
|
|
77
|
-
"$ref": "#/$defs/siteFooterAttribution"
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
},
|
|
81
|
-
"siteFooterAttribution": {
|
|
82
|
-
"type": "object",
|
|
83
|
-
"additionalProperties": false,
|
|
84
|
-
"description": "Optional ZeroPress attribution display policy for themes.",
|
|
85
|
-
"markdownDescription": "Optional ZeroPress attribution display policy for themes.",
|
|
86
|
-
"properties": {
|
|
87
|
-
"enabled": {
|
|
88
83
|
"type": "boolean",
|
|
89
84
|
"description": "When false, bundled themes hide the Published with ZeroPress attribution. Missing or true means attribution may be shown.",
|
|
90
85
|
"markdownDescription": "When `false`, bundled themes hide the Published with ZeroPress attribution. Missing or `true` means attribution may be shown."
|
|
@@ -227,6 +222,12 @@
|
|
|
227
222
|
"$ref": "#/$defs/menu"
|
|
228
223
|
}
|
|
229
224
|
},
|
|
225
|
+
"previewMeta": {
|
|
226
|
+
"type": "object",
|
|
227
|
+
"additionalProperties": {
|
|
228
|
+
"type": ["string", "number", "boolean", "null"]
|
|
229
|
+
}
|
|
230
|
+
},
|
|
230
231
|
"menu": {
|
|
231
232
|
"type": "object",
|
|
232
233
|
"additionalProperties": false,
|
|
@@ -248,6 +249,8 @@
|
|
|
248
249
|
"type": "object",
|
|
249
250
|
"additionalProperties": false,
|
|
250
251
|
"required": ["title", "url"],
|
|
252
|
+
"description": "Menu item copied into generated preview-data.",
|
|
253
|
+
"markdownDescription": "Menu item copied into generated preview-data.",
|
|
251
254
|
"properties": {
|
|
252
255
|
"title": {
|
|
253
256
|
"type": "string",
|
|
@@ -267,6 +270,9 @@
|
|
|
267
270
|
"enum": ["_self", "_blank"],
|
|
268
271
|
"default": "_self"
|
|
269
272
|
},
|
|
273
|
+
"meta": {
|
|
274
|
+
"$ref": "#/$defs/previewMeta"
|
|
275
|
+
},
|
|
270
276
|
"children": {
|
|
271
277
|
"type": "array",
|
|
272
278
|
"items": {
|
|
@@ -285,6 +291,7 @@
|
|
|
285
291
|
"title": "ZeroPress Public Docs",
|
|
286
292
|
"description": "Public documentation.",
|
|
287
293
|
"url": "https://zeropress.dev",
|
|
294
|
+
"expose_generator": true,
|
|
288
295
|
"indexing": true
|
|
289
296
|
},
|
|
290
297
|
"front_page": {
|
|
@@ -303,7 +310,14 @@
|
|
|
303
310
|
"name": "Primary Menu",
|
|
304
311
|
"items": [
|
|
305
312
|
{ "title": "Home", "url": "/" },
|
|
306
|
-
{
|
|
313
|
+
{
|
|
314
|
+
"title": "Docs",
|
|
315
|
+
"url": "/docs/",
|
|
316
|
+
"meta": {
|
|
317
|
+
"icon": "book-open",
|
|
318
|
+
"badge": "New"
|
|
319
|
+
}
|
|
320
|
+
}
|
|
307
321
|
]
|
|
308
322
|
}
|
|
309
323
|
}
|
package/src/action.js
CHANGED
package/src/prebuild.js
CHANGED
|
@@ -16,7 +16,12 @@ const skipUntitledMarkdown = readBooleanEnv('ZEROPRESS_SKIP_UNTITLED_MARKDOWN');
|
|
|
16
16
|
const copyMarkdownSource = readBooleanEnv('ZEROPRESS_COPY_MARKDOWN_SOURCE', true);
|
|
17
17
|
const FRONT_PAGE_TYPES = new Set(['theme_index', 'markdown', 'html']);
|
|
18
18
|
const BUILD_PAGES_CONFIG_SCHEMA_URL = 'https://zeropress.dev/schemas/zeropress-build-pages.config.v0.1.schema.json';
|
|
19
|
-
const PREVIEW_DATA_SCHEMA_URL = 'https://zeropress.dev/schemas/preview-data.v0.
|
|
19
|
+
const PREVIEW_DATA_SCHEMA_URL = 'https://zeropress.dev/schemas/preview-data.v0.6.schema.json';
|
|
20
|
+
const FRONT_MATTER_DATA_KEY_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*(?:-[a-zA-Z0-9_]+)*$/;
|
|
21
|
+
const FRONT_MATTER_DATA_MAX_DEPTH = 4;
|
|
22
|
+
const FRONT_MATTER_DATA_MAX_KEYS = 64;
|
|
23
|
+
const FRONT_MATTER_DATA_MAX_ARRAY_LENGTH = 256;
|
|
24
|
+
const FRONT_MATTER_DISCOVERABILITY_VALUES = new Set(['default', 'noindex', 'delist']);
|
|
20
25
|
|
|
21
26
|
class PrebuildMarkdownError extends Error {
|
|
22
27
|
constructor(sourcePath, reason, expected = '', code = 'invalid_markdown') {
|
|
@@ -99,6 +104,8 @@ async function main() {
|
|
|
99
104
|
...frontMatter.meta,
|
|
100
105
|
...(copyMarkdownSource ? { source_markdown_url: buildSourceMarkdownUrl(sourcePath) } : {}),
|
|
101
106
|
},
|
|
107
|
+
...(frontMatter.data !== undefined ? { data: frontMatter.data } : {}),
|
|
108
|
+
...(frontMatter.discoverability !== 'default' ? { discoverability: frontMatter.discoverability } : {}),
|
|
102
109
|
content: rewriteMarkdownLinks(bodyMarkdown, sourcePath, routeBySourcePath),
|
|
103
110
|
document_type: 'markdown',
|
|
104
111
|
excerpt: frontMatter.description || extractExcerpt(bodyMarkdown, title),
|
|
@@ -115,7 +122,7 @@ async function main() {
|
|
|
115
122
|
|
|
116
123
|
const previewData = {
|
|
117
124
|
$schema: PREVIEW_DATA_SCHEMA_URL,
|
|
118
|
-
version: '0.
|
|
125
|
+
version: '0.6',
|
|
119
126
|
generator: 'zeropress-build-pages',
|
|
120
127
|
generated_at: new Date().toISOString(),
|
|
121
128
|
site,
|
|
@@ -222,18 +229,20 @@ function buildSiteData(config, frontPage) {
|
|
|
222
229
|
title: configuredSite.title,
|
|
223
230
|
description: configuredSite.description,
|
|
224
231
|
url: configuredSite.url,
|
|
225
|
-
|
|
232
|
+
media_base_url: '',
|
|
226
233
|
locale: 'en-US',
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
234
|
+
posts_per_page: 10,
|
|
235
|
+
datetime_display: 'static',
|
|
236
|
+
date_style: 'medium',
|
|
237
|
+
time_style: 'none',
|
|
230
238
|
timezone: 'UTC',
|
|
231
239
|
permalinks: defaultPermalinks(),
|
|
232
240
|
front_page: frontPage,
|
|
233
241
|
post_index: {
|
|
234
242
|
enabled: false,
|
|
235
243
|
},
|
|
236
|
-
|
|
244
|
+
disallow_comments: true,
|
|
245
|
+
expose_generator: configuredSite.expose_generator !== false,
|
|
237
246
|
indexing: configuredSite.indexing !== false,
|
|
238
247
|
};
|
|
239
248
|
|
|
@@ -269,11 +278,12 @@ function normalizeSiteConfig(value) {
|
|
|
269
278
|
}
|
|
270
279
|
|
|
271
280
|
const configuredSite = isPlainObject(value) ? value : {};
|
|
272
|
-
assertKnownConfigKeys(configuredSite, ['title', 'description', 'url', 'indexing', 'footer'], 'site');
|
|
281
|
+
assertKnownConfigKeys(configuredSite, ['title', 'description', 'url', 'expose_generator', 'indexing', 'footer'], 'site');
|
|
273
282
|
const site = {
|
|
274
283
|
title: readConfigString(configuredSite.title, 'Documentation'),
|
|
275
284
|
description: readConfigString(configuredSite.description, 'A documentation site.'),
|
|
276
285
|
url: readEnv('ZEROPRESS_SITE_URL', readConfigString(configuredSite.url, '')),
|
|
286
|
+
expose_generator: readConfigBoolean(configuredSite.expose_generator, true, 'site.expose_generator'),
|
|
277
287
|
indexing: readConfigBoolean(configuredSite.indexing, true, 'site.indexing'),
|
|
278
288
|
};
|
|
279
289
|
|
|
@@ -300,19 +310,11 @@ function normalizeFooter(value) {
|
|
|
300
310
|
footer.copyright_text = copyrightText;
|
|
301
311
|
}
|
|
302
312
|
|
|
303
|
-
if (value.attribution !== undefined
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
if (isPlainObject(value.attribution)) {
|
|
307
|
-
assertKnownConfigKeys(value.attribution, ['enabled'], 'site.footer.attribution');
|
|
308
|
-
if (value.attribution.enabled !== undefined && typeof value.attribution.enabled !== 'boolean') {
|
|
309
|
-
throw new PrebuildConfigError('site.footer.attribution.enabled must be a boolean when provided.');
|
|
313
|
+
if (value.attribution !== undefined) {
|
|
314
|
+
if (typeof value.attribution !== 'boolean') {
|
|
315
|
+
throw new PrebuildConfigError('site.footer.attribution must be a boolean when provided.');
|
|
310
316
|
}
|
|
311
|
-
|
|
312
|
-
if (isPlainObject(value.attribution) && typeof value.attribution.enabled === 'boolean') {
|
|
313
|
-
footer.attribution = {
|
|
314
|
-
enabled: value.attribution.enabled,
|
|
315
|
-
};
|
|
317
|
+
footer.attribution = value.attribution;
|
|
316
318
|
}
|
|
317
319
|
|
|
318
320
|
return Object.keys(footer).length ? footer : undefined;
|
|
@@ -712,12 +714,29 @@ function normalizeMenuItem(item, pathLabel) {
|
|
|
712
714
|
url,
|
|
713
715
|
type: readConfigString(item.type, 'custom'),
|
|
714
716
|
target: readConfigString(item.target, '_self'),
|
|
717
|
+
...(item.meta !== undefined ? { meta: normalizeMenuItemMeta(item.meta, `${pathLabel}.meta`) } : {}),
|
|
715
718
|
children: Array.isArray(item.children)
|
|
716
719
|
? item.children.map((child, index) => normalizeMenuItem(child, `${pathLabel}.children[${index}]`))
|
|
717
720
|
: [],
|
|
718
721
|
};
|
|
719
722
|
}
|
|
720
723
|
|
|
724
|
+
function normalizeMenuItemMeta(value, pathLabel) {
|
|
725
|
+
if (!isPlainObject(value)) {
|
|
726
|
+
throw new PrebuildConfigError(`${pathLabel} must be an object when provided.`);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
const meta = {};
|
|
730
|
+
for (const [key, metaValue] of Object.entries(value)) {
|
|
731
|
+
if (!isPreviewMetaValue(metaValue)) {
|
|
732
|
+
throw new PrebuildConfigError(`${pathLabel}.${key} must be a string, number, boolean, or null.`);
|
|
733
|
+
}
|
|
734
|
+
meta[key] = metaValue;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
return meta;
|
|
738
|
+
}
|
|
739
|
+
|
|
721
740
|
function defaultMenus() {
|
|
722
741
|
return {
|
|
723
742
|
primary: {
|
|
@@ -844,7 +863,9 @@ function normalizePublishedFrontMatter(frontMatter, sourcePath) {
|
|
|
844
863
|
title: normalizeFrontMatterTitle(frontMatter.title, sourcePath),
|
|
845
864
|
description: normalizeFrontMatterDescription(frontMatter.description, sourcePath),
|
|
846
865
|
path: normalizeFrontMatterRoutePath(frontMatter.path, sourcePath),
|
|
866
|
+
discoverability: normalizeFrontMatterDiscoverability(frontMatter.discoverability, sourcePath),
|
|
847
867
|
meta: normalizeFrontMatterMeta(frontMatter.meta, sourcePath),
|
|
868
|
+
data: normalizeFrontMatterData(frontMatter.data, sourcePath),
|
|
848
869
|
};
|
|
849
870
|
}
|
|
850
871
|
|
|
@@ -900,13 +921,28 @@ function normalizeFrontMatterRoutePath(value, sourcePath) {
|
|
|
900
921
|
throw new PrebuildMarkdownError(
|
|
901
922
|
sourcePath,
|
|
902
923
|
'front matter path must be a safe generated route path.',
|
|
903
|
-
' path: guides/install\n path: spec/preview-data-v0.
|
|
924
|
+
' path: guides/install\n path: spec/preview-data-v0.6',
|
|
904
925
|
);
|
|
905
926
|
}
|
|
906
927
|
|
|
907
928
|
return routePath;
|
|
908
929
|
}
|
|
909
930
|
|
|
931
|
+
function normalizeFrontMatterDiscoverability(value, sourcePath) {
|
|
932
|
+
if (value === undefined) {
|
|
933
|
+
return 'default';
|
|
934
|
+
}
|
|
935
|
+
if (typeof value === 'string' && FRONT_MATTER_DISCOVERABILITY_VALUES.has(value)) {
|
|
936
|
+
return value;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
throw new PrebuildMarkdownError(
|
|
940
|
+
sourcePath,
|
|
941
|
+
`front matter discoverability must be one of: ${Array.from(FRONT_MATTER_DISCOVERABILITY_VALUES).join(', ')}.`,
|
|
942
|
+
' discoverability: default\n discoverability: noindex\n discoverability: delist',
|
|
943
|
+
);
|
|
944
|
+
}
|
|
945
|
+
|
|
910
946
|
function isSafeRoutePathSegment(segment) {
|
|
911
947
|
return (
|
|
912
948
|
/^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/.test(segment)
|
|
@@ -943,11 +979,102 @@ function isPreviewMetaValue(value) {
|
|
|
943
979
|
return (
|
|
944
980
|
value === null
|
|
945
981
|
|| typeof value === 'string'
|
|
946
|
-
|| typeof value === 'number'
|
|
982
|
+
|| (typeof value === 'number' && Number.isFinite(value))
|
|
947
983
|
|| typeof value === 'boolean'
|
|
948
984
|
);
|
|
949
985
|
}
|
|
950
986
|
|
|
987
|
+
function normalizeFrontMatterData(value, sourcePath) {
|
|
988
|
+
if (value === undefined) {
|
|
989
|
+
return undefined;
|
|
990
|
+
}
|
|
991
|
+
if (!isPlainObject(value)) {
|
|
992
|
+
throw new PrebuildMarkdownError(
|
|
993
|
+
sourcePath,
|
|
994
|
+
'front matter data must be an object when provided.',
|
|
995
|
+
);
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
validateFrontMatterDataObject(value, sourcePath, 'data', 0);
|
|
999
|
+
return value;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
function validateFrontMatterDataValue(value, sourcePath, pathLabel, depth) {
|
|
1003
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean') {
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
if (typeof value === 'number') {
|
|
1007
|
+
if (!Number.isFinite(value)) {
|
|
1008
|
+
throw new PrebuildMarkdownError(
|
|
1009
|
+
sourcePath,
|
|
1010
|
+
`front matter ${pathLabel} must be a finite number.`,
|
|
1011
|
+
);
|
|
1012
|
+
}
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
if (Array.isArray(value)) {
|
|
1016
|
+
validateFrontMatterDataArray(value, sourcePath, pathLabel, depth);
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
if (isPlainObject(value)) {
|
|
1020
|
+
validateFrontMatterDataObject(value, sourcePath, pathLabel, depth);
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
throw new PrebuildMarkdownError(
|
|
1025
|
+
sourcePath,
|
|
1026
|
+
`front matter ${pathLabel} must be JSON-safe structured data.`,
|
|
1027
|
+
);
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
function validateFrontMatterDataObject(object, sourcePath, pathLabel, depth) {
|
|
1031
|
+
if (depth > FRONT_MATTER_DATA_MAX_DEPTH) {
|
|
1032
|
+
throw new PrebuildMarkdownError(
|
|
1033
|
+
sourcePath,
|
|
1034
|
+
`front matter ${pathLabel} nesting must not exceed ${FRONT_MATTER_DATA_MAX_DEPTH} container levels.`,
|
|
1035
|
+
);
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
const entries = Object.entries(object);
|
|
1039
|
+
if (entries.length > FRONT_MATTER_DATA_MAX_KEYS) {
|
|
1040
|
+
throw new PrebuildMarkdownError(
|
|
1041
|
+
sourcePath,
|
|
1042
|
+
`front matter ${pathLabel} must not contain more than ${FRONT_MATTER_DATA_MAX_KEYS} keys.`,
|
|
1043
|
+
);
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
for (const [key, dataValue] of entries) {
|
|
1047
|
+
const childLabel = `${pathLabel}.${key}`;
|
|
1048
|
+
if (!FRONT_MATTER_DATA_KEY_PATTERN.test(key)) {
|
|
1049
|
+
throw new PrebuildMarkdownError(
|
|
1050
|
+
sourcePath,
|
|
1051
|
+
`front matter ${childLabel} uses an invalid key.`,
|
|
1052
|
+
);
|
|
1053
|
+
}
|
|
1054
|
+
validateFrontMatterDataValue(dataValue, sourcePath, childLabel, depth + 1);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
function validateFrontMatterDataArray(array, sourcePath, pathLabel, depth) {
|
|
1059
|
+
if (depth > FRONT_MATTER_DATA_MAX_DEPTH) {
|
|
1060
|
+
throw new PrebuildMarkdownError(
|
|
1061
|
+
sourcePath,
|
|
1062
|
+
`front matter ${pathLabel} nesting must not exceed ${FRONT_MATTER_DATA_MAX_DEPTH} container levels.`,
|
|
1063
|
+
);
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
if (array.length > FRONT_MATTER_DATA_MAX_ARRAY_LENGTH) {
|
|
1067
|
+
throw new PrebuildMarkdownError(
|
|
1068
|
+
sourcePath,
|
|
1069
|
+
`front matter ${pathLabel} must not contain more than ${FRONT_MATTER_DATA_MAX_ARRAY_LENGTH} items.`,
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
array.forEach((dataValue, index) => {
|
|
1074
|
+
validateFrontMatterDataValue(dataValue, sourcePath, `${pathLabel}[${index}]`, depth + 1);
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
|
|
951
1078
|
function formatFrontMatterValue(value) {
|
|
952
1079
|
if (typeof value === 'string') {
|
|
953
1080
|
return `"${value}"`;
|
package/themes/docs/layout.html
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
{{#else_if site.title}}
|
|
29
29
|
<p>{{site.title}}</p>
|
|
30
30
|
{{/if}}
|
|
31
|
-
{{#if site.footer.attribution
|
|
31
|
+
{{#if site.footer.attribution}}
|
|
32
32
|
<p>Published with <a href="https://zeropress.app" target="_blank" rel="noreferrer noopener">ZeroPress</a>.</p>
|
|
33
33
|
{{/if}}
|
|
34
34
|
</div>
|
package/themes/docs/theme.json
CHANGED
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
"name": "ZeroPress Build Pages Docs",
|
|
3
3
|
"namespace": "zeropress",
|
|
4
4
|
"slug": "zeropress-docs",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.6.0",
|
|
6
6
|
"license": "MIT",
|
|
7
|
-
"runtime": "0.
|
|
7
|
+
"runtime": "0.6",
|
|
8
8
|
"description": "Bundled documentation theme for @zeropress/build-pages",
|
|
9
9
|
"features": {
|
|
10
10
|
"comments": false,
|
|
11
11
|
"newsletter": false,
|
|
12
|
-
"
|
|
12
|
+
"post_index": false
|
|
13
13
|
},
|
|
14
|
-
"
|
|
14
|
+
"menu_slots": {
|
|
15
15
|
"primary": {
|
|
16
16
|
"title": "Primary navigation",
|
|
17
17
|
"description": "Top-level documentation navigation"
|