@zeropress/build-core 0.7.5 → 0.7.6

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
@@ -118,7 +118,8 @@ Returns:
118
118
  size: 1234,
119
119
  sha256: '...'
120
120
  }
121
- ]
121
+ ],
122
+ warnings: []
122
123
  }
123
124
  ```
124
125
 
@@ -145,6 +146,20 @@ This is useful for local tooling that wants filesystem theme loading but still u
145
146
  Theme validation failures from either build entry point carry `error.code === 'THEME_VALIDATION_FAILED'`.
146
147
  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
148
 
149
+ ## Menu depth
150
+
151
+ `theme.json.menu_slots.<menu_id>.max_depth` optionally limits rendered depth
152
+ (top-level items are depth 1). Omission leaves that menu unchanged. Items at the
153
+ limit remain, with empty `children`; deeper descendants are excluded, without
154
+ changing Preview Data. Direct `menus.<id>.items` loops and `{{menu:<id>}}` use
155
+ the same tree.
156
+
157
+ Both build APIs always return `warnings`. A depth overrun produces one
158
+ `MENU_MAX_DEPTH_EXCEEDED` warning per menu per build, containing `message`,
159
+ `menuId`, `maxDepth`, `actualDepth`, and `omittedItems` (all excluded
160
+ descendants). It does not fail the build. The library does not print warnings;
161
+ callers decide how to display them.
162
+
148
163
  ## Writers
149
164
 
150
165
  ### `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.6",
4
4
  "description": "Shared ZeroPress build core",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -39,7 +39,7 @@
39
39
  "dependencies": {
40
40
  "@zeropress/slug-policy": "0.2.2",
41
41
  "@zeropress/preview-data-validator": "0.7.4",
42
- "@zeropress/theme-validator": "0.7.3",
42
+ "@zeropress/theme-validator": "0.7.4",
43
43
  "highlight.js": "11.12.0",
44
44
  "htmlparser2": "12.0.0",
45
45
  "markdown-it": "15.0.2",
@@ -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
+ }