@zeropress/build-pages 0.5.5 → 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 +71 -12
- package/dist/action.js +2159 -289
- package/dist/prebuild.js +147 -24
- package/package.json +2 -2
- package/schemas/zeropress-build-pages.config.v0.1.schema.json +35 -14
- package/src/action.js +1 -1
- package/src/prebuild.js +161 -22
- package/themes/docs/layout.html +1 -1
- package/themes/docs/theme.json +4 -4
- package/schemas/zeropress-build-pages.config.schema.json +0 -5
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,21 @@ 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,
|
|
3743
|
+
indexing: configuredSite.indexing !== false
|
|
3734
3744
|
};
|
|
3735
3745
|
if (configuredSite.footer) {
|
|
3736
3746
|
site.footer = configuredSite.footer;
|
|
@@ -3758,11 +3768,13 @@ function normalizeSiteConfig(value) {
|
|
|
3758
3768
|
);
|
|
3759
3769
|
}
|
|
3760
3770
|
const configuredSite = isPlainObject(value) ? value : {};
|
|
3761
|
-
assertKnownConfigKeys(configuredSite, ["title", "description", "url", "footer"], "site");
|
|
3771
|
+
assertKnownConfigKeys(configuredSite, ["title", "description", "url", "expose_generator", "indexing", "footer"], "site");
|
|
3762
3772
|
const site = {
|
|
3763
3773
|
title: readConfigString(configuredSite.title, "Documentation"),
|
|
3764
3774
|
description: readConfigString(configuredSite.description, "A documentation site."),
|
|
3765
|
-
url: readEnv("ZEROPRESS_SITE_URL", readConfigString(configuredSite.url, ""))
|
|
3775
|
+
url: readEnv("ZEROPRESS_SITE_URL", readConfigString(configuredSite.url, "")),
|
|
3776
|
+
expose_generator: readConfigBoolean(configuredSite.expose_generator, true, "site.expose_generator"),
|
|
3777
|
+
indexing: readConfigBoolean(configuredSite.indexing, true, "site.indexing")
|
|
3766
3778
|
};
|
|
3767
3779
|
const footer = normalizeFooter(configuredSite.footer);
|
|
3768
3780
|
if (footer) {
|
|
@@ -3783,22 +3795,23 @@ function normalizeFooter(value) {
|
|
|
3783
3795
|
if (copyrightText) {
|
|
3784
3796
|
footer.copyright_text = copyrightText;
|
|
3785
3797
|
}
|
|
3786
|
-
if (value.attribution !== void 0
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
if (isPlainObject(value.attribution)) {
|
|
3790
|
-
assertKnownConfigKeys(value.attribution, ["enabled"], "site.footer.attribution");
|
|
3791
|
-
if (value.attribution.enabled !== void 0 && typeof value.attribution.enabled !== "boolean") {
|
|
3792
|
-
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.");
|
|
3793
3801
|
}
|
|
3794
|
-
|
|
3795
|
-
if (isPlainObject(value.attribution) && typeof value.attribution.enabled === "boolean") {
|
|
3796
|
-
footer.attribution = {
|
|
3797
|
-
enabled: value.attribution.enabled
|
|
3798
|
-
};
|
|
3802
|
+
footer.attribution = value.attribution;
|
|
3799
3803
|
}
|
|
3800
3804
|
return Object.keys(footer).length ? footer : void 0;
|
|
3801
3805
|
}
|
|
3806
|
+
function readConfigBoolean(value, fallback, pathName) {
|
|
3807
|
+
if (value === void 0) {
|
|
3808
|
+
return fallback;
|
|
3809
|
+
}
|
|
3810
|
+
if (typeof value !== "boolean") {
|
|
3811
|
+
throw new PrebuildConfigError(`${pathName} must be a boolean when provided.`);
|
|
3812
|
+
}
|
|
3813
|
+
return value;
|
|
3814
|
+
}
|
|
3802
3815
|
async function buildFrontPageData(frontPageConfig, pageInputs, config) {
|
|
3803
3816
|
if (frontPageConfig.type === "theme_index") {
|
|
3804
3817
|
return {
|
|
@@ -4132,9 +4145,23 @@ function normalizeMenuItem(item, pathLabel) {
|
|
|
4132
4145
|
url,
|
|
4133
4146
|
type: readConfigString(item.type, "custom"),
|
|
4134
4147
|
target: readConfigString(item.target, "_self"),
|
|
4148
|
+
...item.meta !== void 0 ? { meta: normalizeMenuItemMeta(item.meta, `${pathLabel}.meta`) } : {},
|
|
4135
4149
|
children: Array.isArray(item.children) ? item.children.map((child, index) => normalizeMenuItem(child, `${pathLabel}.children[${index}]`)) : []
|
|
4136
4150
|
};
|
|
4137
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
|
+
}
|
|
4138
4165
|
function defaultMenus() {
|
|
4139
4166
|
return {
|
|
4140
4167
|
primary: {
|
|
@@ -4251,7 +4278,9 @@ function normalizePublishedFrontMatter(frontMatter, sourcePath) {
|
|
|
4251
4278
|
title: normalizeFrontMatterTitle(frontMatter.title, sourcePath),
|
|
4252
4279
|
description: normalizeFrontMatterDescription(frontMatter.description, sourcePath),
|
|
4253
4280
|
path: normalizeFrontMatterRoutePath(frontMatter.path, sourcePath),
|
|
4254
|
-
|
|
4281
|
+
discoverability: normalizeFrontMatterDiscoverability(frontMatter.discoverability, sourcePath),
|
|
4282
|
+
meta: normalizeFrontMatterMeta(frontMatter.meta, sourcePath),
|
|
4283
|
+
data: normalizeFrontMatterData(frontMatter.data, sourcePath)
|
|
4255
4284
|
};
|
|
4256
4285
|
}
|
|
4257
4286
|
function normalizeFrontMatterTitle(value, sourcePath) {
|
|
@@ -4294,11 +4323,24 @@ function normalizeFrontMatterRoutePath(value, sourcePath) {
|
|
|
4294
4323
|
throw new PrebuildMarkdownError(
|
|
4295
4324
|
sourcePath,
|
|
4296
4325
|
"front matter path must be a safe generated route path.",
|
|
4297
|
-
" path: guides/install\n path: spec/preview-data-v0.
|
|
4326
|
+
" path: guides/install\n path: spec/preview-data-v0.6"
|
|
4298
4327
|
);
|
|
4299
4328
|
}
|
|
4300
4329
|
return routePath;
|
|
4301
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
|
+
}
|
|
4302
4344
|
function isSafeRoutePathSegment(segment) {
|
|
4303
4345
|
return /^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/.test(segment) && !segment.includes("..");
|
|
4304
4346
|
}
|
|
@@ -4325,7 +4367,88 @@ function normalizeFrontMatterMeta(value, sourcePath) {
|
|
|
4325
4367
|
return meta;
|
|
4326
4368
|
}
|
|
4327
4369
|
function isPreviewMetaValue(value) {
|
|
4328
|
-
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
|
+
});
|
|
4329
4452
|
}
|
|
4330
4453
|
function formatFrontMatterValue(value) {
|
|
4331
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,18 @@
|
|
|
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
|
+
},
|
|
59
|
+
"indexing": {
|
|
60
|
+
"type": "boolean",
|
|
61
|
+
"default": true,
|
|
62
|
+
"description": "Fallback robots.txt indexing policy. Missing or true allows indexing; false writes a fallback robots.txt that disallows all agents unless the source directory provides robots.txt.",
|
|
63
|
+
"markdownDescription": "Fallback `robots.txt` indexing policy. Missing or `true` allows indexing; `false` writes a fallback `robots.txt` that disallows all agents unless the source directory provides `robots.txt`."
|
|
64
|
+
},
|
|
53
65
|
"footer": {
|
|
54
66
|
"$ref": "#/$defs/siteFooter"
|
|
55
67
|
}
|
|
@@ -68,17 +80,6 @@
|
|
|
68
80
|
"markdownDescription": "Footer copyright or legal text. ZeroPress does not add a copyright symbol automatically."
|
|
69
81
|
},
|
|
70
82
|
"attribution": {
|
|
71
|
-
"$ref": "#/$defs/siteFooterAttribution"
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
},
|
|
75
|
-
"siteFooterAttribution": {
|
|
76
|
-
"type": "object",
|
|
77
|
-
"additionalProperties": false,
|
|
78
|
-
"description": "Optional ZeroPress attribution display policy for themes.",
|
|
79
|
-
"markdownDescription": "Optional ZeroPress attribution display policy for themes.",
|
|
80
|
-
"properties": {
|
|
81
|
-
"enabled": {
|
|
82
83
|
"type": "boolean",
|
|
83
84
|
"description": "When false, bundled themes hide the Published with ZeroPress attribution. Missing or true means attribution may be shown.",
|
|
84
85
|
"markdownDescription": "When `false`, bundled themes hide the Published with ZeroPress attribution. Missing or `true` means attribution may be shown."
|
|
@@ -221,6 +222,12 @@
|
|
|
221
222
|
"$ref": "#/$defs/menu"
|
|
222
223
|
}
|
|
223
224
|
},
|
|
225
|
+
"previewMeta": {
|
|
226
|
+
"type": "object",
|
|
227
|
+
"additionalProperties": {
|
|
228
|
+
"type": ["string", "number", "boolean", "null"]
|
|
229
|
+
}
|
|
230
|
+
},
|
|
224
231
|
"menu": {
|
|
225
232
|
"type": "object",
|
|
226
233
|
"additionalProperties": false,
|
|
@@ -242,6 +249,8 @@
|
|
|
242
249
|
"type": "object",
|
|
243
250
|
"additionalProperties": false,
|
|
244
251
|
"required": ["title", "url"],
|
|
252
|
+
"description": "Menu item copied into generated preview-data.",
|
|
253
|
+
"markdownDescription": "Menu item copied into generated preview-data.",
|
|
245
254
|
"properties": {
|
|
246
255
|
"title": {
|
|
247
256
|
"type": "string",
|
|
@@ -261,6 +270,9 @@
|
|
|
261
270
|
"enum": ["_self", "_blank"],
|
|
262
271
|
"default": "_self"
|
|
263
272
|
},
|
|
273
|
+
"meta": {
|
|
274
|
+
"$ref": "#/$defs/previewMeta"
|
|
275
|
+
},
|
|
264
276
|
"children": {
|
|
265
277
|
"type": "array",
|
|
266
278
|
"items": {
|
|
@@ -273,12 +285,14 @@
|
|
|
273
285
|
},
|
|
274
286
|
"examples": [
|
|
275
287
|
{
|
|
276
|
-
"$schema": "
|
|
288
|
+
"$schema": "https://zeropress.dev/schemas/zeropress-build-pages.config.v0.1.schema.json",
|
|
277
289
|
"version": "0.1",
|
|
278
290
|
"site": {
|
|
279
291
|
"title": "ZeroPress Public Docs",
|
|
280
292
|
"description": "Public documentation.",
|
|
281
|
-
"url": "https://zeropress.dev"
|
|
293
|
+
"url": "https://zeropress.dev",
|
|
294
|
+
"expose_generator": true,
|
|
295
|
+
"indexing": true
|
|
282
296
|
},
|
|
283
297
|
"front_page": {
|
|
284
298
|
"type": "markdown"
|
|
@@ -296,7 +310,14 @@
|
|
|
296
310
|
"name": "Primary Menu",
|
|
297
311
|
"items": [
|
|
298
312
|
{ "title": "Home", "url": "/" },
|
|
299
|
-
{
|
|
313
|
+
{
|
|
314
|
+
"title": "Docs",
|
|
315
|
+
"url": "/docs/",
|
|
316
|
+
"meta": {
|
|
317
|
+
"icon": "book-open",
|
|
318
|
+
"badge": "New"
|
|
319
|
+
}
|
|
320
|
+
}
|
|
300
321
|
]
|
|
301
322
|
}
|
|
302
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,21 @@ 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,
|
|
246
|
+
indexing: configuredSite.indexing !== false,
|
|
237
247
|
};
|
|
238
248
|
|
|
239
249
|
if (configuredSite.footer) {
|
|
@@ -268,11 +278,13 @@ function normalizeSiteConfig(value) {
|
|
|
268
278
|
}
|
|
269
279
|
|
|
270
280
|
const configuredSite = isPlainObject(value) ? value : {};
|
|
271
|
-
assertKnownConfigKeys(configuredSite, ['title', 'description', 'url', 'footer'], 'site');
|
|
281
|
+
assertKnownConfigKeys(configuredSite, ['title', 'description', 'url', 'expose_generator', 'indexing', 'footer'], 'site');
|
|
272
282
|
const site = {
|
|
273
283
|
title: readConfigString(configuredSite.title, 'Documentation'),
|
|
274
284
|
description: readConfigString(configuredSite.description, 'A documentation site.'),
|
|
275
285
|
url: readEnv('ZEROPRESS_SITE_URL', readConfigString(configuredSite.url, '')),
|
|
286
|
+
expose_generator: readConfigBoolean(configuredSite.expose_generator, true, 'site.expose_generator'),
|
|
287
|
+
indexing: readConfigBoolean(configuredSite.indexing, true, 'site.indexing'),
|
|
276
288
|
};
|
|
277
289
|
|
|
278
290
|
const footer = normalizeFooter(configuredSite.footer);
|
|
@@ -298,24 +310,26 @@ function normalizeFooter(value) {
|
|
|
298
310
|
footer.copyright_text = copyrightText;
|
|
299
311
|
}
|
|
300
312
|
|
|
301
|
-
if (value.attribution !== undefined
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
if (isPlainObject(value.attribution)) {
|
|
305
|
-
assertKnownConfigKeys(value.attribution, ['enabled'], 'site.footer.attribution');
|
|
306
|
-
if (value.attribution.enabled !== undefined && typeof value.attribution.enabled !== 'boolean') {
|
|
307
|
-
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.');
|
|
308
316
|
}
|
|
309
|
-
|
|
310
|
-
if (isPlainObject(value.attribution) && typeof value.attribution.enabled === 'boolean') {
|
|
311
|
-
footer.attribution = {
|
|
312
|
-
enabled: value.attribution.enabled,
|
|
313
|
-
};
|
|
317
|
+
footer.attribution = value.attribution;
|
|
314
318
|
}
|
|
315
319
|
|
|
316
320
|
return Object.keys(footer).length ? footer : undefined;
|
|
317
321
|
}
|
|
318
322
|
|
|
323
|
+
function readConfigBoolean(value, fallback, pathName) {
|
|
324
|
+
if (value === undefined) {
|
|
325
|
+
return fallback;
|
|
326
|
+
}
|
|
327
|
+
if (typeof value !== 'boolean') {
|
|
328
|
+
throw new PrebuildConfigError(`${pathName} must be a boolean when provided.`);
|
|
329
|
+
}
|
|
330
|
+
return value;
|
|
331
|
+
}
|
|
332
|
+
|
|
319
333
|
async function buildFrontPageData(frontPageConfig, pageInputs, config) {
|
|
320
334
|
if (frontPageConfig.type === 'theme_index') {
|
|
321
335
|
return {
|
|
@@ -700,12 +714,29 @@ function normalizeMenuItem(item, pathLabel) {
|
|
|
700
714
|
url,
|
|
701
715
|
type: readConfigString(item.type, 'custom'),
|
|
702
716
|
target: readConfigString(item.target, '_self'),
|
|
717
|
+
...(item.meta !== undefined ? { meta: normalizeMenuItemMeta(item.meta, `${pathLabel}.meta`) } : {}),
|
|
703
718
|
children: Array.isArray(item.children)
|
|
704
719
|
? item.children.map((child, index) => normalizeMenuItem(child, `${pathLabel}.children[${index}]`))
|
|
705
720
|
: [],
|
|
706
721
|
};
|
|
707
722
|
}
|
|
708
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
|
+
|
|
709
740
|
function defaultMenus() {
|
|
710
741
|
return {
|
|
711
742
|
primary: {
|
|
@@ -832,7 +863,9 @@ function normalizePublishedFrontMatter(frontMatter, sourcePath) {
|
|
|
832
863
|
title: normalizeFrontMatterTitle(frontMatter.title, sourcePath),
|
|
833
864
|
description: normalizeFrontMatterDescription(frontMatter.description, sourcePath),
|
|
834
865
|
path: normalizeFrontMatterRoutePath(frontMatter.path, sourcePath),
|
|
866
|
+
discoverability: normalizeFrontMatterDiscoverability(frontMatter.discoverability, sourcePath),
|
|
835
867
|
meta: normalizeFrontMatterMeta(frontMatter.meta, sourcePath),
|
|
868
|
+
data: normalizeFrontMatterData(frontMatter.data, sourcePath),
|
|
836
869
|
};
|
|
837
870
|
}
|
|
838
871
|
|
|
@@ -888,13 +921,28 @@ function normalizeFrontMatterRoutePath(value, sourcePath) {
|
|
|
888
921
|
throw new PrebuildMarkdownError(
|
|
889
922
|
sourcePath,
|
|
890
923
|
'front matter path must be a safe generated route path.',
|
|
891
|
-
' path: guides/install\n path: spec/preview-data-v0.
|
|
924
|
+
' path: guides/install\n path: spec/preview-data-v0.6',
|
|
892
925
|
);
|
|
893
926
|
}
|
|
894
927
|
|
|
895
928
|
return routePath;
|
|
896
929
|
}
|
|
897
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
|
+
|
|
898
946
|
function isSafeRoutePathSegment(segment) {
|
|
899
947
|
return (
|
|
900
948
|
/^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/.test(segment)
|
|
@@ -931,11 +979,102 @@ function isPreviewMetaValue(value) {
|
|
|
931
979
|
return (
|
|
932
980
|
value === null
|
|
933
981
|
|| typeof value === 'string'
|
|
934
|
-
|| typeof value === 'number'
|
|
982
|
+
|| (typeof value === 'number' && Number.isFinite(value))
|
|
935
983
|
|| typeof value === 'boolean'
|
|
936
984
|
);
|
|
937
985
|
}
|
|
938
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
|
+
|
|
939
1078
|
function formatFrontMatterValue(value) {
|
|
940
1079
|
if (typeof value === 'string') {
|
|
941
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>
|