@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 +24 -6
- package/package.json +6 -6
- package/src/api/build-site.js +11 -6
- package/src/index.d.ts +10 -0
- package/src/render/menus.js +44 -0
package/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# @zeropress/build-core
|
|
2
2
|
|
|
3
|
-
](https://github.com/zeropress-app/zeropress-build-core/actions/workflows/100-test.yml)
|
|
4
|
+
[](https://github.com/zeropress-app/zeropress-build-core/actions/workflows/github-code-scanning/codeql)
|
|
5
|
+
[](https://www.npmjs.com/package/@zeropress/build-core)
|
|
6
|
+
[](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://
|
|
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://
|
|
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.
|
|
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.
|
|
42
|
-
"@zeropress/theme-validator": "0.7.
|
|
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.
|
|
45
|
+
"markdown-it": "15.0.3",
|
|
46
46
|
"markdown-it-anchor": "10.0.0",
|
|
47
|
-
"sanitize-html": "2.
|
|
47
|
+
"sanitize-html": "2.18.1"
|
|
48
48
|
}
|
|
49
49
|
}
|
package/src/api/build-site.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
+
}
|