@zeropress/build-core 0.2.2 → 0.5.0
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
|
@@ -55,7 +55,6 @@ import {
|
|
|
55
55
|
- `sitemap.xml`
|
|
56
56
|
- `feed.xml`
|
|
57
57
|
- `robots.txt`
|
|
58
|
-
- `meta.json`
|
|
59
58
|
- writing outputs through a pluggable writer
|
|
60
59
|
|
|
61
60
|
It does not:
|
|
@@ -120,7 +119,7 @@ Notes:
|
|
|
120
119
|
- `previewData` must already satisfy the canonical preview-data contract
|
|
121
120
|
- `themePackage` must already be a validated in-memory theme package
|
|
122
121
|
- `sitemap.xml` and `feed.xml` are emitted only when `site.url` is a non-empty canonical URL
|
|
123
|
-
- `robots.txt`
|
|
122
|
+
- `robots.txt` is still emitted when `generateSpecialFiles` is enabled
|
|
124
123
|
|
|
125
124
|
### `buildSiteFromThemeDir(input)`
|
|
126
125
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeropress/build-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Shared ZeroPress build core",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@zeropress/slug-policy": "^0.1.0",
|
|
37
|
-
"@zeropress/preview-data-validator": "
|
|
38
|
-
"@zeropress/theme-validator": "
|
|
37
|
+
"@zeropress/preview-data-validator": "0.5.0",
|
|
38
|
+
"@zeropress/theme-validator": "0.5.1",
|
|
39
39
|
"highlight.js": "^11.11.1",
|
|
40
40
|
"markdown-it": "^14.1.0",
|
|
41
41
|
"markdown-it-anchor": "^9.2.0"
|
package/src/api/build-site.js
CHANGED
|
@@ -83,7 +83,6 @@ export async function buildSite(input) {
|
|
|
83
83
|
await writeOutput(state.writer, state.summaries, 'feed.xml', buildFeedXml(state.previewData.site, state.emitted, state.generatedAt), 'application/rss+xml');
|
|
84
84
|
}
|
|
85
85
|
await writeOutput(state.writer, state.summaries, 'robots.txt', buildRobotsTxt(state.previewData.site), 'text/plain');
|
|
86
|
-
await writeOutput(state.writer, state.summaries, 'meta.json', buildMetaJson(state.previewData.site, state.emitted, state.generatedAt), 'application/json');
|
|
87
86
|
}
|
|
88
87
|
|
|
89
88
|
return finalizeBuildResult(state.writer, state.summaries, options);
|
|
@@ -94,7 +93,7 @@ async function createBuildState(input, options) {
|
|
|
94
93
|
throw new Error('buildSite requires a writer with an async write(file) method');
|
|
95
94
|
}
|
|
96
95
|
|
|
97
|
-
|
|
96
|
+
assertBuildPreviewData(input.previewData);
|
|
98
97
|
const themePackage = await normalizeAndValidateThemePackage(input.themePackage);
|
|
99
98
|
|
|
100
99
|
const engine = new ZeroPressEngine();
|
|
@@ -125,6 +124,7 @@ async function createBuildState(input, options) {
|
|
|
125
124
|
assetOutputs,
|
|
126
125
|
assetMap,
|
|
127
126
|
customCssHref: customCssAsset ? `/${customCssAsset.path}` : '',
|
|
127
|
+
customHtml: previewData.custom_html,
|
|
128
128
|
commentPolicyContent: buildCommentPolicyManifest(renderData.posts),
|
|
129
129
|
options,
|
|
130
130
|
generatedAt: new Date(),
|
|
@@ -139,6 +139,18 @@ async function createBuildState(input, options) {
|
|
|
139
139
|
};
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
function assertBuildPreviewData(previewData) {
|
|
143
|
+
try {
|
|
144
|
+
assertPreviewData(previewData);
|
|
145
|
+
} catch (error) {
|
|
146
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
147
|
+
if (message.startsWith('Invalid preview-data')) {
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
throw new Error(`Invalid preview-data: ${message}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
142
154
|
async function finalizeBuildResult(writer, summaries, options) {
|
|
143
155
|
let manifest;
|
|
144
156
|
if (options.writeManifest) {
|
|
@@ -168,6 +180,7 @@ async function renderRoute(state, templateName, route) {
|
|
|
168
180
|
{
|
|
169
181
|
menus: state.previewData.menus,
|
|
170
182
|
widgets: state.widgets,
|
|
183
|
+
taxonomies: state.renderData.taxonomies,
|
|
171
184
|
...route,
|
|
172
185
|
meta: buildPageMeta(state.previewData.site, {
|
|
173
186
|
currentUrl,
|
|
@@ -179,7 +192,7 @@ async function renderRoute(state, templateName, route) {
|
|
|
179
192
|
createRenderContext(state.previewData.site, currentUrl),
|
|
180
193
|
);
|
|
181
194
|
html = state.assetProcessor.updateAssetReferences(html, state.assetMap);
|
|
182
|
-
html =
|
|
195
|
+
html = injectSiteCustomizations(html, state);
|
|
183
196
|
await writeOutput(state.writer, state.summaries, routePathToOutputPath(route.path, state.previewData.site.permalinks.output_style), html, 'text/html');
|
|
184
197
|
recordRouteEmission(state, templateName, route, currentUrl);
|
|
185
198
|
}
|
|
@@ -191,6 +204,7 @@ async function renderPost(state, post) {
|
|
|
191
204
|
{
|
|
192
205
|
menus: state.previewData.menus,
|
|
193
206
|
widgets: state.widgets,
|
|
207
|
+
taxonomies: state.renderData.taxonomies,
|
|
194
208
|
post,
|
|
195
209
|
meta: buildPageMeta(state.previewData.site, {
|
|
196
210
|
currentUrl,
|
|
@@ -205,7 +219,7 @@ async function renderPost(state, post) {
|
|
|
205
219
|
createRenderContext(state.previewData.site, currentUrl),
|
|
206
220
|
);
|
|
207
221
|
html = state.assetProcessor.updateAssetReferences(html, state.assetMap);
|
|
208
|
-
html =
|
|
222
|
+
html = injectSiteCustomizations(html, state);
|
|
209
223
|
await writeOutput(state.writer, state.summaries, routePathToOutputPath(post.url, state.previewData.site.permalinks.output_style), html, 'text/html');
|
|
210
224
|
state.emitted.posts.push({
|
|
211
225
|
url: currentUrl,
|
|
@@ -224,6 +238,7 @@ async function renderPage(state, page) {
|
|
|
224
238
|
{
|
|
225
239
|
menus: state.previewData.menus,
|
|
226
240
|
widgets: state.widgets,
|
|
241
|
+
taxonomies: state.renderData.taxonomies,
|
|
227
242
|
page,
|
|
228
243
|
meta: buildPageMeta(state.previewData.site, {
|
|
229
244
|
currentUrl,
|
|
@@ -236,7 +251,7 @@ async function renderPage(state, page) {
|
|
|
236
251
|
createRenderContext(state.previewData.site, currentUrl),
|
|
237
252
|
);
|
|
238
253
|
html = state.assetProcessor.updateAssetReferences(html, state.assetMap);
|
|
239
|
-
html =
|
|
254
|
+
html = injectSiteCustomizations(html, state);
|
|
240
255
|
await writeOutput(state.writer, state.summaries, routePathToOutputPath(page.url, state.previewData.site.permalinks.output_style), html, 'text/html');
|
|
241
256
|
state.emitted.pages.push({
|
|
242
257
|
url: currentUrl,
|
|
@@ -256,6 +271,7 @@ async function maybeRenderNotFoundPage(state) {
|
|
|
256
271
|
{
|
|
257
272
|
menus: state.previewData.menus,
|
|
258
273
|
widgets: state.widgets,
|
|
274
|
+
taxonomies: state.renderData.taxonomies,
|
|
259
275
|
meta: buildPageMeta(state.previewData.site, {
|
|
260
276
|
currentUrl: '/404.html',
|
|
261
277
|
title: state.previewData.site.title,
|
|
@@ -266,7 +282,7 @@ async function maybeRenderNotFoundPage(state) {
|
|
|
266
282
|
createRenderContext(state.previewData.site, '/404.html'),
|
|
267
283
|
);
|
|
268
284
|
html = state.assetProcessor.updateAssetReferences(html, state.assetMap);
|
|
269
|
-
html =
|
|
285
|
+
html = injectSiteCustomizations(html, state);
|
|
270
286
|
await writeOutput(state.writer, state.summaries, '404.html', html, 'text/html');
|
|
271
287
|
}
|
|
272
288
|
|
|
@@ -290,6 +306,7 @@ function normalizePreviewData(previewData) {
|
|
|
290
306
|
site: normalizedSite,
|
|
291
307
|
widgets: normalizeWidgetAreas(previewData.widgets, normalizedSite.mediaBaseUrl),
|
|
292
308
|
custom_css: normalizeCustomCss(previewData.custom_css),
|
|
309
|
+
custom_html: normalizeCustomHtml(previewData.custom_html),
|
|
293
310
|
content: {
|
|
294
311
|
...previewData.content,
|
|
295
312
|
authors: previewData.content.authors.map((author) => ({
|
|
@@ -357,6 +374,23 @@ function normalizeCustomCss(customCss) {
|
|
|
357
374
|
return content ? { content } : undefined;
|
|
358
375
|
}
|
|
359
376
|
|
|
377
|
+
function normalizeCustomHtml(customHtml) {
|
|
378
|
+
if (!customHtml || typeof customHtml !== 'object') {
|
|
379
|
+
return undefined;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const headEnd = normalizeOptionalRawString(customHtml.head_end?.content);
|
|
383
|
+
const bodyEnd = normalizeOptionalRawString(customHtml.body_end?.content);
|
|
384
|
+
if (!headEnd && !bodyEnd) {
|
|
385
|
+
return undefined;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return {
|
|
389
|
+
...(headEnd ? { head_end: { content: headEnd } } : {}),
|
|
390
|
+
...(bodyEnd ? { body_end: { content: bodyEnd } } : {}),
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
|
|
360
394
|
function normalizePermalinks(permalinks) {
|
|
361
395
|
const source = permalinks && typeof permalinks === 'object' ? permalinks : {};
|
|
362
396
|
const outputStyle = typeof source.output_style === 'string' && PERMALINK_OUTPUT_STYLES.has(source.output_style)
|
|
@@ -407,6 +441,7 @@ function createRenderData(previewData, themeMetadata = {}) {
|
|
|
407
441
|
posts,
|
|
408
442
|
pages,
|
|
409
443
|
postBySlug,
|
|
444
|
+
taxonomies: buildGlobalTaxonomies(previewData, categoryCountBySlug, tagCountBySlug),
|
|
410
445
|
indexRoutes: buildPaginatedCollection({
|
|
411
446
|
items: previewData.content.posts,
|
|
412
447
|
postsPerPage: previewData.site.postsPerPage,
|
|
@@ -458,6 +493,23 @@ function createRenderData(previewData, themeMetadata = {}) {
|
|
|
458
493
|
};
|
|
459
494
|
}
|
|
460
495
|
|
|
496
|
+
function buildGlobalTaxonomies(previewData, categoryCountBySlug, tagCountBySlug) {
|
|
497
|
+
return {
|
|
498
|
+
categories: buildGlobalTaxonomyItems(previewData.site, 'categories', previewData.content.categories, categoryCountBySlug),
|
|
499
|
+
tags: buildGlobalTaxonomyItems(previewData.site, 'tags', previewData.content.tags, tagCountBySlug),
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function buildGlobalTaxonomyItems(site, permalinkKind, items, countBySlug) {
|
|
504
|
+
return items.map((item) => ({
|
|
505
|
+
name: item.name,
|
|
506
|
+
slug: item.slug,
|
|
507
|
+
url: resolvePermalink(site, permalinkKind, item).url,
|
|
508
|
+
count: countBySlug.get(item.slug) || 0,
|
|
509
|
+
description: typeof item.description === 'string' ? item.description : '',
|
|
510
|
+
}));
|
|
511
|
+
}
|
|
512
|
+
|
|
461
513
|
function resolveWidgetAreas(previewData, renderData) {
|
|
462
514
|
if (!previewData.widgets || typeof previewData.widgets !== 'object') {
|
|
463
515
|
return {};
|
|
@@ -1144,6 +1196,10 @@ function normalizeOptionalString(value) {
|
|
|
1144
1196
|
return typeof value === 'string' && value.trim() ? value.trim() : '';
|
|
1145
1197
|
}
|
|
1146
1198
|
|
|
1199
|
+
function normalizeOptionalRawString(value) {
|
|
1200
|
+
return typeof value === 'string' && value.trim() ? value : '';
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1147
1203
|
function normalizeLocale(value) {
|
|
1148
1204
|
if (typeof value !== 'string' || !value.trim()) {
|
|
1149
1205
|
return DEFAULT_LOCALE;
|
|
@@ -1625,7 +1681,7 @@ function assertPlannedOutputPathsSafe(state) {
|
|
|
1625
1681
|
];
|
|
1626
1682
|
|
|
1627
1683
|
if (state.options.generateSpecialFiles) {
|
|
1628
|
-
plannedPaths.push('404.html', 'robots.txt'
|
|
1684
|
+
plannedPaths.push('404.html', 'robots.txt');
|
|
1629
1685
|
if (hasCanonicalSiteUrl(state.previewData.site.url)) {
|
|
1630
1686
|
plannedPaths.push('sitemap.xml', 'feed.xml');
|
|
1631
1687
|
}
|
|
@@ -1724,6 +1780,12 @@ function sha256(content) {
|
|
|
1724
1780
|
return hash.digest('hex');
|
|
1725
1781
|
}
|
|
1726
1782
|
|
|
1783
|
+
function injectSiteCustomizations(html, state) {
|
|
1784
|
+
let next = injectCustomCssAssetLink(html, state.customCssHref);
|
|
1785
|
+
next = injectCustomHtml(next, state.customHtml);
|
|
1786
|
+
return next;
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1727
1789
|
function injectCustomCssAssetLink(html, href) {
|
|
1728
1790
|
if (!normalizeOptionalString(href)) {
|
|
1729
1791
|
return html;
|
|
@@ -1732,6 +1794,21 @@ function injectCustomCssAssetLink(html, href) {
|
|
|
1732
1794
|
return html.replace('</head>', ` <link rel="stylesheet" href="${escapeHtml(href)}">\n</head>`);
|
|
1733
1795
|
}
|
|
1734
1796
|
|
|
1797
|
+
function injectCustomHtml(html, customHtml) {
|
|
1798
|
+
let next = html;
|
|
1799
|
+
const headEnd = normalizeOptionalRawString(customHtml?.head_end?.content);
|
|
1800
|
+
const bodyEnd = normalizeOptionalRawString(customHtml?.body_end?.content);
|
|
1801
|
+
|
|
1802
|
+
if (headEnd) {
|
|
1803
|
+
next = next.replace('</head>', `${headEnd}\n</head>`);
|
|
1804
|
+
}
|
|
1805
|
+
if (bodyEnd) {
|
|
1806
|
+
next = next.replace('</body>', `${bodyEnd}\n</body>`);
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
return next;
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1735
1812
|
function buildSitemapXml(site, emitted, generatedAt) {
|
|
1736
1813
|
const entries = [
|
|
1737
1814
|
...emitted.indexRoutes
|
|
@@ -1788,38 +1865,6 @@ function buildRobotsTxt(site) {
|
|
|
1788
1865
|
return `${lines.join('\n')}\n`;
|
|
1789
1866
|
}
|
|
1790
1867
|
|
|
1791
|
-
function buildMetaJson(site, emitted, generatedAt) {
|
|
1792
|
-
const pages = [
|
|
1793
|
-
...emitted.posts.map((post) => ({
|
|
1794
|
-
url: post.url,
|
|
1795
|
-
title: post.title,
|
|
1796
|
-
description: post.description,
|
|
1797
|
-
publishedAt: post.publishedAt,
|
|
1798
|
-
updatedAt: post.updatedAt,
|
|
1799
|
-
type: 'post',
|
|
1800
|
-
metadata: { status: post.status },
|
|
1801
|
-
})),
|
|
1802
|
-
...emitted.pages.map((page) => ({
|
|
1803
|
-
url: page.url,
|
|
1804
|
-
title: page.title,
|
|
1805
|
-
description: page.description,
|
|
1806
|
-
type: 'page',
|
|
1807
|
-
metadata: { status: page.status },
|
|
1808
|
-
})),
|
|
1809
|
-
];
|
|
1810
|
-
|
|
1811
|
-
return JSON.stringify({
|
|
1812
|
-
generated: formatUtcIsoSeconds(generatedAt),
|
|
1813
|
-
site: {
|
|
1814
|
-
title: site.title,
|
|
1815
|
-
description: site.description,
|
|
1816
|
-
url: site.url,
|
|
1817
|
-
locale: site.locale,
|
|
1818
|
-
},
|
|
1819
|
-
pages,
|
|
1820
|
-
}, null, 2);
|
|
1821
|
-
}
|
|
1822
|
-
|
|
1823
1868
|
function getContentType(assetPath) {
|
|
1824
1869
|
const ext = assetPath.split('.').pop()?.toLowerCase();
|
|
1825
1870
|
const contentTypes = {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { parsePartialToken } from './partial-resolver.js';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
3
|
+
const PATH_SEGMENT_SOURCE = '[a-zA-Z_][a-zA-Z0-9_]*(?:-[a-zA-Z0-9_]+)*';
|
|
4
|
+
const PATH_REGEX = new RegExp(`^${PATH_SEGMENT_SOURCE}(?:\\.${PATH_SEGMENT_SOURCE})*$`);
|
|
5
|
+
const FOR_EXPRESSION_REGEX = new RegExp(`^([a-zA-Z_][a-zA-Z0-9_]*)\\s+in\\s+(${PATH_SEGMENT_SOURCE}(?:\\.${PATH_SEGMENT_SOURCE})*)$`);
|
|
6
|
+
const IF_EQ_EXPRESSION_REGEX = new RegExp(`^(${PATH_SEGMENT_SOURCE}(?:\\.${PATH_SEGMENT_SOURCE})*)\\s+("(?:[^"\\\\]|\\\\.)*")$`);
|
|
6
7
|
|
|
7
8
|
export class ControlFlowRenderer {
|
|
8
9
|
constructor(options = {}) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export class VariableResolver {
|
|
2
2
|
static MENU_PATTERN = /\{\{menu:([a-z][a-z0-9_-]{0,63})\}\}/g;
|
|
3
|
-
static VARIABLE_PATTERN = /\{\{([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)+)\}\}/g;
|
|
4
|
-
static STANDALONE_PATTERN = /\{\{([a-zA-Z_][a-zA-Z0-9_]*)\}\}/g;
|
|
3
|
+
static VARIABLE_PATTERN = /\{\{([a-zA-Z_][a-zA-Z0-9_]*(?:-[a-zA-Z0-9_]+)*(?:\.[a-zA-Z_][a-zA-Z0-9_]*(?:-[a-zA-Z0-9_]+)*)+)\}\}/g;
|
|
4
|
+
static STANDALONE_PATTERN = /\{\{([a-zA-Z_][a-zA-Z0-9_]*(?:-[a-zA-Z0-9_]+)*)\}\}/g;
|
|
5
5
|
|
|
6
6
|
resolve(template, data, options = {}) {
|
|
7
7
|
let result = template.replace(VariableResolver.MENU_PATTERN, (_, menuId) => {
|