@zeropress/build-pages 0.6.2 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/prebuild.js CHANGED
@@ -3534,8 +3534,8 @@ var buildReportPath = path.join(outDir, "build-report.json");
3534
3534
  var skipUntitledMarkdown = readBooleanEnv("ZEROPRESS_SKIP_UNTITLED_MARKDOWN");
3535
3535
  var copyMarkdownSource = readBooleanEnv("ZEROPRESS_COPY_MARKDOWN_SOURCE", true);
3536
3536
  var FRONT_PAGE_TYPES = /* @__PURE__ */ new Set(["theme_index", "markdown", "html"]);
3537
- var BUILD_PAGES_CONFIG_SCHEMA_URL = "https://zeropress.dev/schemas/zeropress-build-pages.config.v0.1.schema.json";
3538
- var PREVIEW_DATA_SCHEMA_URL = "https://zeropress.dev/schemas/preview-data.v0.6.schema.json";
3537
+ var BUILD_PAGES_CONFIG_SCHEMA_URL = "https://schemas.zeropress.dev/build-pages-config/v0.1/schema.json";
3538
+ var PREVIEW_DATA_SCHEMA_URL = "https://schemas.zeropress.dev/preview-data/v0.6/schema.json";
3539
3539
  var FRONT_MATTER_DATA_KEY_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*(?:-[a-zA-Z0-9_]+)*$/;
3540
3540
  var FRONT_MATTER_DATA_MAX_DEPTH = 4;
3541
3541
  var FRONT_MATTER_DATA_MAX_KEYS = 64;
@@ -3729,7 +3729,7 @@ function buildSiteData(config, frontPage) {
3729
3729
  description: configuredSite.description,
3730
3730
  url: configuredSite.url,
3731
3731
  media_base_url: "",
3732
- locale: "en-US",
3732
+ locale: configuredSite.locale,
3733
3733
  posts_per_page: 10,
3734
3734
  datetime_display: "static",
3735
3735
  date_style: "medium",
@@ -3745,6 +3745,12 @@ function buildSiteData(config, frontPage) {
3745
3745
  search: configuredSite.search !== false,
3746
3746
  indexing: configuredSite.indexing !== false
3747
3747
  };
3748
+ if (configuredSite.logo) {
3749
+ site.logo = configuredSite.logo;
3750
+ }
3751
+ if (configuredSite.meta !== void 0) {
3752
+ site.meta = configuredSite.meta;
3753
+ }
3748
3754
  if (configuredSite.footer) {
3749
3755
  site.footer = configuredSite.footer;
3750
3756
  }
@@ -3771,21 +3777,83 @@ function normalizeSiteConfig(value) {
3771
3777
  );
3772
3778
  }
3773
3779
  const configuredSite = isPlainObject(value) ? value : {};
3774
- assertKnownConfigKeys(configuredSite, ["title", "description", "url", "expose_generator", "search", "indexing", "footer"], "site");
3780
+ assertKnownConfigKeys(configuredSite, ["title", "description", "url", "logo", "locale", "expose_generator", "search", "indexing", "footer", "meta"], "site");
3775
3781
  const site = {
3776
3782
  title: readConfigString(configuredSite.title, "Documentation"),
3777
3783
  description: readConfigString(configuredSite.description, "A documentation site."),
3778
3784
  url: readEnv("ZEROPRESS_SITE_URL", readConfigString(configuredSite.url, "")),
3785
+ locale: normalizeSiteLocale(configuredSite.locale),
3779
3786
  expose_generator: readConfigBoolean(configuredSite.expose_generator, true, "site.expose_generator"),
3780
3787
  search: readConfigBoolean(configuredSite.search, true, "site.search"),
3781
3788
  indexing: readConfigBoolean(configuredSite.indexing, true, "site.indexing")
3782
3789
  };
3790
+ const logo = normalizeSiteLogo(configuredSite.logo);
3791
+ if (logo) {
3792
+ site.logo = logo;
3793
+ }
3783
3794
  const footer = normalizeFooter(configuredSite.footer);
3784
3795
  if (footer) {
3785
3796
  site.footer = footer;
3786
3797
  }
3798
+ if (configuredSite.meta !== void 0) {
3799
+ site.meta = normalizeSiteMeta(configuredSite.meta, "site.meta");
3800
+ }
3787
3801
  return site;
3788
3802
  }
3803
+ function normalizeSiteLocale(value) {
3804
+ if (value === void 0) {
3805
+ return "en-US";
3806
+ }
3807
+ if (typeof value !== "string") {
3808
+ throw new PrebuildConfigError("site.locale must be a string when provided.");
3809
+ }
3810
+ const locale = value.trim();
3811
+ if (locale.length < 2) {
3812
+ throw new PrebuildConfigError('site.locale must be a non-empty locale string such as "en-US" or "ko-KR".');
3813
+ }
3814
+ return locale;
3815
+ }
3816
+ function normalizeSiteLogo(value) {
3817
+ if (value === void 0) {
3818
+ return void 0;
3819
+ }
3820
+ if (!isPlainObject(value)) {
3821
+ throw new PrebuildConfigError("site.logo must be an object when provided.");
3822
+ }
3823
+ assertKnownConfigKeys(value, ["src", "alt"], "site.logo");
3824
+ const src = readConfigString(value.src, "");
3825
+ if (!src) {
3826
+ throw new PrebuildConfigError(
3827
+ "site.logo.src must be a non-empty URL-like string.",
3828
+ ' "logo": { "src": "/logo.svg", "alt": "My Site" }'
3829
+ );
3830
+ }
3831
+ validateUrlLikeString(src, "site.logo.src");
3832
+ const logo = { src };
3833
+ if (value.alt !== void 0) {
3834
+ if (typeof value.alt !== "string") {
3835
+ throw new PrebuildConfigError("site.logo.alt must be a string when provided.");
3836
+ }
3837
+ const alt = value.alt.trim();
3838
+ if (alt) {
3839
+ logo.alt = alt;
3840
+ }
3841
+ }
3842
+ return logo;
3843
+ }
3844
+ function normalizeSiteMeta(value, pathLabel) {
3845
+ if (!isPlainObject(value)) {
3846
+ throw new PrebuildConfigError(`${pathLabel} must be an object when provided.`);
3847
+ }
3848
+ const meta = {};
3849
+ for (const [key, metaValue] of Object.entries(value)) {
3850
+ if (!isPreviewMetaValue(metaValue)) {
3851
+ throw new PrebuildConfigError(`${pathLabel}.${key} must be a string, number, boolean, or null.`);
3852
+ }
3853
+ meta[key] = metaValue;
3854
+ }
3855
+ return meta;
3856
+ }
3789
3857
  function normalizeFooter(value) {
3790
3858
  if (value === void 0) {
3791
3859
  return void 0;
@@ -3807,6 +3875,22 @@ function normalizeFooter(value) {
3807
3875
  }
3808
3876
  return Object.keys(footer).length ? footer : void 0;
3809
3877
  }
3878
+ function validateUrlLikeString(value, pathLabel) {
3879
+ if (value.startsWith("//")) {
3880
+ throw new PrebuildConfigError(`${pathLabel} must be an absolute URL or a safe relative path.`);
3881
+ }
3882
+ if (value.startsWith("/") || value.startsWith("./") || value.startsWith("../")) {
3883
+ return;
3884
+ }
3885
+ try {
3886
+ const url = new URL(value);
3887
+ if (!url.protocol || !url.hostname) {
3888
+ throw new Error("missing host");
3889
+ }
3890
+ } catch {
3891
+ throw new PrebuildConfigError(`${pathLabel} must be an absolute URL or a safe relative path.`);
3892
+ }
3893
+ }
3810
3894
  function readConfigBoolean(value, fallback, pathName) {
3811
3895
  if (value === void 0) {
3812
3896
  return fallback;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeropress/build-pages",
3
- "version": "0.6.2",
3
+ "version": "0.6.3",
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.6.2",
43
+ "@zeropress/build": "0.6.3",
44
44
  "gray-matter": "4.0.3"
45
45
  },
46
46
  "devDependencies": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://zeropress.dev/schemas/zeropress-build-pages.config.v0.1.schema.json",
3
+ "$id": "https://schemas.zeropress.dev/build-pages-config/v0.1/schema.json",
4
4
  "title": "ZeroPress Build Pages Config v0.1",
5
5
  "description": "Optional source-directory configuration for @zeropress/build-pages workflows.",
6
6
  "markdownDescription": "Optional source-directory configuration for `@zeropress/build-pages` workflows.",
@@ -50,6 +50,15 @@
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
+ "logo": {
54
+ "$ref": "#/$defs/siteLogo"
55
+ },
56
+ "locale": {
57
+ "type": "string",
58
+ "minLength": 2,
59
+ "description": "Site locale used for generated preview-data language metadata, such as html lang and feed language.",
60
+ "markdownDescription": "Site locale used for generated preview-data language metadata, such as HTML `lang` and feed language."
61
+ },
53
62
  "expose_generator": {
54
63
  "type": "boolean",
55
64
  "default": true,
@@ -70,6 +79,29 @@
70
79
  },
71
80
  "footer": {
72
81
  "$ref": "#/$defs/siteFooter"
82
+ },
83
+ "meta": {
84
+ "$ref": "#/$defs/previewMeta"
85
+ }
86
+ }
87
+ },
88
+ "siteLogo": {
89
+ "type": "object",
90
+ "additionalProperties": false,
91
+ "required": ["src"],
92
+ "description": "Optional site logo data copied into generated preview-data.",
93
+ "markdownDescription": "Optional site logo data copied into generated preview-data.",
94
+ "properties": {
95
+ "src": {
96
+ "type": "string",
97
+ "minLength": 1,
98
+ "description": "Logo URL or safe relative path.",
99
+ "markdownDescription": "Logo URL or safe relative path."
100
+ },
101
+ "alt": {
102
+ "type": "string",
103
+ "description": "Optional logo alternative text. Themes may fall back to site.title when omitted.",
104
+ "markdownDescription": "Optional logo alternative text. Themes may fall back to `site.title` when omitted."
73
105
  }
74
106
  }
75
107
  },
@@ -291,15 +323,23 @@
291
323
  },
292
324
  "examples": [
293
325
  {
294
- "$schema": "https://zeropress.dev/schemas/zeropress-build-pages.config.v0.1.schema.json",
326
+ "$schema": "https://schemas.zeropress.dev/build-pages-config/v0.1/schema.json",
295
327
  "version": "0.1",
296
328
  "site": {
297
329
  "title": "ZeroPress Public Docs",
298
330
  "description": "Public documentation.",
299
331
  "url": "https://zeropress.dev",
332
+ "logo": {
333
+ "src": "/logo.svg",
334
+ "alt": "ZeroPress Public Docs"
335
+ },
336
+ "locale": "en-US",
300
337
  "expose_generator": true,
301
338
  "search": true,
302
- "indexing": true
339
+ "indexing": true,
340
+ "meta": {
341
+ "issue": "Spring 2026"
342
+ }
303
343
  },
304
344
  "front_page": {
305
345
  "type": "markdown"
package/src/prebuild.js CHANGED
@@ -16,8 +16,8 @@ const buildReportPath = path.join(outDir, 'build-report.json');
16
16
  const skipUntitledMarkdown = readBooleanEnv('ZEROPRESS_SKIP_UNTITLED_MARKDOWN');
17
17
  const copyMarkdownSource = readBooleanEnv('ZEROPRESS_COPY_MARKDOWN_SOURCE', true);
18
18
  const FRONT_PAGE_TYPES = new Set(['theme_index', 'markdown', 'html']);
19
- const BUILD_PAGES_CONFIG_SCHEMA_URL = 'https://zeropress.dev/schemas/zeropress-build-pages.config.v0.1.schema.json';
20
- const PREVIEW_DATA_SCHEMA_URL = 'https://zeropress.dev/schemas/preview-data.v0.6.schema.json';
19
+ const BUILD_PAGES_CONFIG_SCHEMA_URL = 'https://schemas.zeropress.dev/build-pages-config/v0.1/schema.json';
20
+ const PREVIEW_DATA_SCHEMA_URL = 'https://schemas.zeropress.dev/preview-data/v0.6/schema.json';
21
21
  const FRONT_MATTER_DATA_KEY_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*(?:-[a-zA-Z0-9_]+)*$/;
22
22
  const FRONT_MATTER_DATA_MAX_DEPTH = 4;
23
23
  const FRONT_MATTER_DATA_MAX_KEYS = 64;
@@ -232,7 +232,7 @@ function buildSiteData(config, frontPage) {
232
232
  description: configuredSite.description,
233
233
  url: configuredSite.url,
234
234
  media_base_url: '',
235
- locale: 'en-US',
235
+ locale: configuredSite.locale,
236
236
  posts_per_page: 10,
237
237
  datetime_display: 'static',
238
238
  date_style: 'medium',
@@ -249,6 +249,14 @@ function buildSiteData(config, frontPage) {
249
249
  indexing: configuredSite.indexing !== false,
250
250
  };
251
251
 
252
+ if (configuredSite.logo) {
253
+ site.logo = configuredSite.logo;
254
+ }
255
+
256
+ if (configuredSite.meta !== undefined) {
257
+ site.meta = configuredSite.meta;
258
+ }
259
+
252
260
  if (configuredSite.footer) {
253
261
  site.footer = configuredSite.footer;
254
262
  }
@@ -281,24 +289,98 @@ function normalizeSiteConfig(value) {
281
289
  }
282
290
 
283
291
  const configuredSite = isPlainObject(value) ? value : {};
284
- assertKnownConfigKeys(configuredSite, ['title', 'description', 'url', 'expose_generator', 'search', 'indexing', 'footer'], 'site');
292
+ assertKnownConfigKeys(configuredSite, ['title', 'description', 'url', 'logo', 'locale', 'expose_generator', 'search', 'indexing', 'footer', 'meta'], 'site');
285
293
  const site = {
286
294
  title: readConfigString(configuredSite.title, 'Documentation'),
287
295
  description: readConfigString(configuredSite.description, 'A documentation site.'),
288
296
  url: readEnv('ZEROPRESS_SITE_URL', readConfigString(configuredSite.url, '')),
297
+ locale: normalizeSiteLocale(configuredSite.locale),
289
298
  expose_generator: readConfigBoolean(configuredSite.expose_generator, true, 'site.expose_generator'),
290
299
  search: readConfigBoolean(configuredSite.search, true, 'site.search'),
291
300
  indexing: readConfigBoolean(configuredSite.indexing, true, 'site.indexing'),
292
301
  };
293
302
 
303
+ const logo = normalizeSiteLogo(configuredSite.logo);
304
+ if (logo) {
305
+ site.logo = logo;
306
+ }
307
+
294
308
  const footer = normalizeFooter(configuredSite.footer);
295
309
  if (footer) {
296
310
  site.footer = footer;
297
311
  }
298
312
 
313
+ if (configuredSite.meta !== undefined) {
314
+ site.meta = normalizeSiteMeta(configuredSite.meta, 'site.meta');
315
+ }
316
+
299
317
  return site;
300
318
  }
301
319
 
320
+ function normalizeSiteLocale(value) {
321
+ if (value === undefined) {
322
+ return 'en-US';
323
+ }
324
+ if (typeof value !== 'string') {
325
+ throw new PrebuildConfigError('site.locale must be a string when provided.');
326
+ }
327
+
328
+ const locale = value.trim();
329
+ if (locale.length < 2) {
330
+ throw new PrebuildConfigError('site.locale must be a non-empty locale string such as "en-US" or "ko-KR".');
331
+ }
332
+
333
+ return locale;
334
+ }
335
+
336
+ function normalizeSiteLogo(value) {
337
+ if (value === undefined) {
338
+ return undefined;
339
+ }
340
+ if (!isPlainObject(value)) {
341
+ throw new PrebuildConfigError('site.logo must be an object when provided.');
342
+ }
343
+ assertKnownConfigKeys(value, ['src', 'alt'], 'site.logo');
344
+
345
+ const src = readConfigString(value.src, '');
346
+ if (!src) {
347
+ throw new PrebuildConfigError(
348
+ 'site.logo.src must be a non-empty URL-like string.',
349
+ ' "logo": { "src": "/logo.svg", "alt": "My Site" }',
350
+ );
351
+ }
352
+ validateUrlLikeString(src, 'site.logo.src');
353
+
354
+ const logo = { src };
355
+ if (value.alt !== undefined) {
356
+ if (typeof value.alt !== 'string') {
357
+ throw new PrebuildConfigError('site.logo.alt must be a string when provided.');
358
+ }
359
+ const alt = value.alt.trim();
360
+ if (alt) {
361
+ logo.alt = alt;
362
+ }
363
+ }
364
+
365
+ return logo;
366
+ }
367
+
368
+ function normalizeSiteMeta(value, pathLabel) {
369
+ if (!isPlainObject(value)) {
370
+ throw new PrebuildConfigError(`${pathLabel} must be an object when provided.`);
371
+ }
372
+
373
+ const meta = {};
374
+ for (const [key, metaValue] of Object.entries(value)) {
375
+ if (!isPreviewMetaValue(metaValue)) {
376
+ throw new PrebuildConfigError(`${pathLabel}.${key} must be a string, number, boolean, or null.`);
377
+ }
378
+ meta[key] = metaValue;
379
+ }
380
+
381
+ return meta;
382
+ }
383
+
302
384
  function normalizeFooter(value) {
303
385
  if (value === undefined) {
304
386
  return undefined;
@@ -324,6 +406,25 @@ function normalizeFooter(value) {
324
406
  return Object.keys(footer).length ? footer : undefined;
325
407
  }
326
408
 
409
+ function validateUrlLikeString(value, pathLabel) {
410
+ if (value.startsWith('//')) {
411
+ throw new PrebuildConfigError(`${pathLabel} must be an absolute URL or a safe relative path.`);
412
+ }
413
+
414
+ if (value.startsWith('/') || value.startsWith('./') || value.startsWith('../')) {
415
+ return;
416
+ }
417
+
418
+ try {
419
+ const url = new URL(value);
420
+ if (!url.protocol || !url.hostname) {
421
+ throw new Error('missing host');
422
+ }
423
+ } catch {
424
+ throw new PrebuildConfigError(`${pathLabel} must be an absolute URL or a safe relative path.`);
425
+ }
426
+ }
427
+
327
428
  function readConfigBoolean(value, fallback, pathName) {
328
429
  if (value === undefined) {
329
430
  return fallback;
@@ -1,6 +1,17 @@
1
1
  <section class="shell not-found">
2
- <p class="eyebrow">404</p>
2
+ <p class="eyebrow eyebrow--error">
3
+ <span class="eyebrow__dot" aria-hidden="true"></span>
4
+ 404
5
+ </p>
3
6
  <h1>Page not found</h1>
4
7
  <p class="lede">The page you are looking for is not part of the current documentation build.</p>
5
- <a class="button-link" href="/">Return home</a>
8
+ <div class="hero__actions">
9
+ <a class="button-link" href="/">
10
+ Return home
11
+ <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
12
+ <line x1="5" y1="12" x2="19" y2="12"></line>
13
+ <polyline points="12 5 19 12 12 19"></polyline>
14
+ </svg>
15
+ </a>
16
+ </div>
6
17
  </section>