@zeropress/build-core 0.7.5 → 0.7.9

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 CHANGED
@@ -1,8 +1,10 @@
1
1
  # @zeropress/build-core
2
2
 
3
- ![npm](https://img.shields.io/npm/v/%40zeropress%2Fbuild-core)
4
- ![license](https://img.shields.io/npm/l/%40zeropress%2Fbuild-core)
5
- ![node](https://img.shields.io/node/v/%40zeropress%2Fbuild-core)
3
+ [![Package Tests](https://github.com/zeropress-app/zeropress-build-core/actions/workflows/100-test.yml/badge.svg?branch=main)](https://github.com/zeropress-app/zeropress-build-core/actions/workflows/100-test.yml)
4
+ [![CodeQL](https://github.com/zeropress-app/zeropress-build-core/actions/workflows/github-code-scanning/codeql/badge.svg?branch=main)](https://github.com/zeropress-app/zeropress-build-core/actions/workflows/github-code-scanning/codeql)
5
+ [![npm](https://img.shields.io/npm/v/%40zeropress%2Fbuild-core)](https://www.npmjs.com/package/@zeropress/build-core)
6
+ [![Socket Badge](https://badge.socket.dev/npm/package/@zeropress/build-core)](https://socket.dev/npm/package/@zeropress/build-core)
7
+ <picture><img alt="license" src="https://img.shields.io/npm/l/%40zeropress%2Fbuild-core"></picture>
6
8
 
7
9
  Shared deterministic rendering core for Preview Data v0.7 and Theme Runtime v0.7.
8
10
 
@@ -10,13 +12,14 @@ This package is the canonical rendering core for preview-data and theme packages
10
12
 
11
13
  - [@zeropress/build](https://www.npmjs.com/package/@zeropress/build)
12
14
  - [@zeropress/theme](https://www.npmjs.com/package/@zeropress/theme)
15
+ - [zeropress-studio](https://github.com/zeropress-app/zeropress-studio)
13
16
 
14
17
  Public contract references:
15
18
 
16
19
  - [Preview Data v0.7 Spec](https://zeropress.dev/reference/preview-data/specs/v0.7/)
17
- - [Preview Data v0.7 Schema](https://schemas.zeropress.dev/preview-data/v0.7/schema.json)
20
+ - [Preview Data v0.7 Schema](https://www.schemastore.org/zeropress-preview-data-0.7.json)
18
21
  - [Theme Runtime v0.7 Spec](https://zeropress.dev/reference/theme-runtime/specs/v0.7/)
19
- - [Theme Runtime v0.7 Schema](https://schemas.zeropress.dev/theme-runtime/v0.7/schema.json)
22
+ - [Theme Runtime v0.7 Schema](https://www.schemastore.org/zeropress-theme-runtime-0.7.json)
20
23
 
21
24
  It accepts canonical preview-data plus a validated theme package and produces static HTML artifacts through a writer interface.
22
25
 
@@ -118,7 +121,8 @@ Returns:
118
121
  size: 1234,
119
122
  sha256: '...'
120
123
  }
121
- ]
124
+ ],
125
+ warnings: []
122
126
  }
123
127
  ```
124
128
 
@@ -145,6 +149,20 @@ This is useful for local tooling that wants filesystem theme loading but still u
145
149
  Theme validation failures from either build entry point carry `error.code === 'THEME_VALIDATION_FAILED'`.
146
150
  Their messages preserve report and hint line breaks while escaping terminal control characters in diagnostic values such as paths, messages, and source snippets. CLI consumers can preserve the layout of these reports while escaping arbitrary errors as single-line text.
147
151
 
152
+ ## Menu depth
153
+
154
+ `theme.json.menu_slots.<menu_id>.max_depth` optionally limits rendered depth
155
+ (top-level items are depth 1). Omission leaves that menu unchanged. Items at the
156
+ limit remain, with empty `children`; deeper descendants are excluded, without
157
+ changing Preview Data. Direct `menus.<id>.items` loops and `{{menu:<id>}}` use
158
+ the same tree.
159
+
160
+ Both build APIs always return `warnings`. A depth overrun produces one
161
+ `MENU_MAX_DEPTH_EXCEEDED` warning per menu per build, containing `message`,
162
+ `menuId`, `maxDepth`, `actualDepth`, and `omittedItems` (all excluded
163
+ descendants). It does not fail the build. The library does not print warnings;
164
+ callers decide how to display them.
165
+
148
166
  ## Writers
149
167
 
150
168
  ### `MemoryWriter`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeropress/build-core",
3
- "version": "0.7.5",
3
+ "version": "0.7.9",
4
4
  "description": "Shared ZeroPress build core",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -37,13 +37,13 @@
37
37
  "node": ">=22.22.0"
38
38
  },
39
39
  "dependencies": {
40
- "@zeropress/slug-policy": "0.2.2",
41
- "@zeropress/preview-data-validator": "0.7.4",
42
- "@zeropress/theme-validator": "0.7.3",
40
+ "@zeropress/slug-policy": "^0.2.2",
41
+ "@zeropress/preview-data-validator": "^0.7.7",
42
+ "@zeropress/theme-validator": "^0.7.7",
43
43
  "highlight.js": "11.12.0",
44
44
  "htmlparser2": "12.0.0",
45
- "markdown-it": "15.0.2",
45
+ "markdown-it": "15.0.3",
46
46
  "markdown-it-anchor": "10.0.0",
47
- "sanitize-html": "2.17.7"
47
+ "sanitize-html": "2.18.1"
48
48
  }
49
49
  }
@@ -7,6 +7,7 @@ import { renderDocument, renderDocumentContent } from '../render/content-rendere
7
7
  import { extractHtmlText } from '../render/html-text.js';
8
8
  import { createThemeValidationError } from '../theme/format-theme-validation.js';
9
9
  import { ZeroPressEngine } from '../render/zeropress-engine.js';
10
+ import { prepareRenderMenus } from '../render/menus.js';
10
11
 
11
12
  const DEFAULT_OPTIONS = {
12
13
  assetHashing: true,
@@ -136,6 +137,7 @@ export async function buildSite(input) {
136
137
 
137
138
  return {
138
139
  files: state.summaries,
140
+ warnings: state.warnings,
139
141
  };
140
142
  }
141
143
 
@@ -151,6 +153,7 @@ async function createBuildState(input, options) {
151
153
  const assetProcessor = new AssetProcessor();
152
154
  const summaries = [];
153
155
  const previewData = normalizePreviewData(input.previewData, options);
156
+ const { menus, warnings } = prepareRenderMenus(previewData.menus, themePackage.metadata.menu_slots);
154
157
  const renderData = createRenderData(previewData, themePackage, options);
155
158
 
156
159
  engine.initialize(themePackage);
@@ -167,6 +170,8 @@ async function createBuildState(input, options) {
167
170
  return {
168
171
  writer: input.writer,
169
172
  previewData,
173
+ menus,
174
+ warnings,
170
175
  renderData,
171
176
  widgets: resolveWidgetAreas(previewData, renderData),
172
177
  engine,
@@ -214,7 +219,7 @@ async function renderRoute(state, templateName, route) {
214
219
  let html = await state.engine.render(
215
220
  templateName,
216
221
  {
217
- menus: state.previewData.menus,
222
+ menus: state.menus,
218
223
  widgets: state.widgets,
219
224
  collections: state.renderData.collections,
220
225
  taxonomies: state.renderData.taxonomies,
@@ -267,7 +272,7 @@ async function renderFrontPage(state, route) {
267
272
  let html = await state.engine.render(
268
273
  'page',
269
274
  {
270
- menus: state.previewData.menus,
275
+ menus: state.menus,
271
276
  widgets: state.widgets,
272
277
  collections: state.renderData.collections,
273
278
  taxonomies: state.renderData.taxonomies,
@@ -308,7 +313,7 @@ async function renderFrontPage(state, route) {
308
313
  let html = await state.engine.render(
309
314
  'index',
310
315
  {
311
- menus: state.previewData.menus,
316
+ menus: state.menus,
312
317
  widgets: state.widgets,
313
318
  collections: state.renderData.collections,
314
319
  taxonomies: state.renderData.taxonomies,
@@ -343,7 +348,7 @@ async function renderPost(state, post) {
343
348
  let html = await state.engine.render(
344
349
  'post',
345
350
  {
346
- menus: state.previewData.menus,
351
+ menus: state.menus,
347
352
  widgets: state.widgets,
348
353
  collections: state.renderData.collections,
349
354
  taxonomies: state.renderData.taxonomies,
@@ -391,7 +396,7 @@ async function renderPage(state, page) {
391
396
  let html = await state.engine.render(
392
397
  'page',
393
398
  {
394
- menus: state.previewData.menus,
399
+ menus: state.menus,
395
400
  widgets: state.widgets,
396
401
  collections: state.renderData.collections,
397
402
  taxonomies: state.renderData.taxonomies,
@@ -437,7 +442,7 @@ async function maybeRenderNotFoundPage(state) {
437
442
  let html = await state.engine.render(
438
443
  '404',
439
444
  {
440
- menus: state.previewData.menus,
445
+ menus: state.menus,
441
446
  widgets: state.widgets,
442
447
  collections: state.renderData.collections,
443
448
  taxonomies: state.renderData.taxonomies,
package/src/index.d.ts CHANGED
@@ -80,8 +80,18 @@ export interface BuildSummaryFile {
80
80
  sha256: string;
81
81
  }
82
82
 
83
+ export interface BuildWarning {
84
+ code: 'MENU_MAX_DEPTH_EXCEEDED';
85
+ message: string;
86
+ menuId: string;
87
+ maxDepth: number;
88
+ actualDepth: number;
89
+ omittedItems: number;
90
+ }
91
+
83
92
  export interface BuildSiteResult {
84
93
  files: BuildSummaryFile[];
94
+ warnings: BuildWarning[];
85
95
  }
86
96
 
87
97
  export function buildSite(input: {
@@ -0,0 +1,44 @@
1
+ // Work on rendering copies only; validation and exported Preview Data keep the full tree.
2
+ export function prepareRenderMenus(menus, slots = {}) {
3
+ const rendered = { ...menus };
4
+ const warnings = [];
5
+
6
+ for (const [menuId, slot] of Object.entries(slots)) {
7
+ if (slot.max_depth === undefined || !Object.hasOwn(menus, menuId)) continue;
8
+ const maxDepth = slot.max_depth;
9
+ const items = [];
10
+ const stack = [{ source: menus[menuId].items, output: items, depth: 1 }];
11
+ let actualDepth = 0;
12
+ let omittedItems = 0;
13
+
14
+ while (stack.length) {
15
+ const { source, output, depth } = stack.pop();
16
+ for (const item of source) {
17
+ actualDepth = Math.max(actualDepth, depth);
18
+ const children = [];
19
+ if (depth <= maxDepth) {
20
+ output.push({ ...item, children });
21
+ } else {
22
+ omittedItems += 1;
23
+ }
24
+ if (item.children.length) {
25
+ stack.push({ source: item.children, output: children, depth: depth + 1 });
26
+ }
27
+ }
28
+ }
29
+
30
+ rendered[menuId] = { ...menus[menuId], items };
31
+ if (omittedItems > 0) {
32
+ warnings.push({
33
+ code: 'MENU_MAX_DEPTH_EXCEEDED',
34
+ message: `Menu "${menuId}" reaches depth ${actualDepth}; the theme supports ${maxDepth}. ${omittedItems} deeper item(s) were omitted from rendering.`,
35
+ menuId,
36
+ maxDepth,
37
+ actualDepth,
38
+ omittedItems,
39
+ });
40
+ }
41
+ }
42
+
43
+ return { menus: rendered, warnings };
44
+ }