@zeropress/build-core 0.6.1 → 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/README.md +8 -9
- package/package.json +4 -4
- package/src/api/build-site.js +135 -32
package/README.md
CHANGED
|
@@ -14,9 +14,9 @@ This package is the canonical rendering core for preview-data and theme packages
|
|
|
14
14
|
Public contract references:
|
|
15
15
|
|
|
16
16
|
- [Preview Data v0.6 Spec](https://zeropress.dev/spec/preview-data-v0.6.html)
|
|
17
|
-
- [Preview Data v0.6 Schema](https://zeropress.dev/
|
|
17
|
+
- [Preview Data v0.6 Schema](https://schemas.zeropress.dev/preview-data/v0.6/schema.json)
|
|
18
18
|
- [Theme Runtime v0.6 Spec](https://zeropress.dev/spec/theme-runtime-v0.6.html)
|
|
19
|
-
- [Theme Runtime v0.6 Schema](https://zeropress.dev/
|
|
19
|
+
- [Theme Runtime v0.6 Schema](https://schemas.zeropress.dev/theme-runtime/v0.6/schema.json)
|
|
20
20
|
|
|
21
21
|
It accepts canonical preview-data plus a validated theme package and produces static HTML artifacts through a writer interface.
|
|
22
22
|
|
|
@@ -56,9 +56,9 @@ import {
|
|
|
56
56
|
- `sitemap.xml`
|
|
57
57
|
- `feed.xml`
|
|
58
58
|
- fallback `robots.txt`
|
|
59
|
-
- `/_zeropress/
|
|
60
|
-
- `/_zeropress/search.
|
|
61
|
-
- `/_zeropress/
|
|
59
|
+
- `/_zeropress/search.json` when native search is enabled
|
|
60
|
+
- `/_zeropress/search.js` when native search is enabled
|
|
61
|
+
- `/_zeropress/search_pagefind.js` when native search is enabled
|
|
62
62
|
- writing outputs through a pluggable writer
|
|
63
63
|
|
|
64
64
|
It does not:
|
|
@@ -191,10 +191,6 @@ Build-core derives:
|
|
|
191
191
|
- `reading_time`
|
|
192
192
|
- `comments_enabled`
|
|
193
193
|
|
|
194
|
-
It also emits a comments allowlist artifact:
|
|
195
|
-
|
|
196
|
-
- `/_zeropress/comment-policy.json`
|
|
197
|
-
|
|
198
194
|
Comment availability is derived from preview-data policy:
|
|
199
195
|
|
|
200
196
|
- `site.disallow_comments`
|
|
@@ -212,6 +208,9 @@ The canonical `preview-data v0.6` site contract uses:
|
|
|
212
208
|
- `site.disallow_comments`
|
|
213
209
|
- `site.indexing`
|
|
214
210
|
- `site.expose_generator`
|
|
211
|
+
- `site.search`
|
|
212
|
+
|
|
213
|
+
Native search artifacts are emitted only when preview-data does not set `site.search: false` and the active theme declares `features.search: true`. `search_pagefind.js` is a Pagefind adapter that can replace `search.js` after a post-build Pagefind step.
|
|
215
214
|
|
|
216
215
|
Optional route templates behave as rendering capabilities, not guaranteed outputs:
|
|
217
216
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeropress/build-core",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
4
4
|
"description": "Shared ZeroPress build core",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -34,9 +34,9 @@
|
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@zeropress/slug-policy": "0.1.0",
|
|
37
|
-
"@zeropress/preview-data-validator": "0.6.
|
|
38
|
-
"@zeropress/theme-validator": "0.6.
|
|
39
|
-
"highlight.js": "
|
|
37
|
+
"@zeropress/preview-data-validator": "0.6.3",
|
|
38
|
+
"@zeropress/theme-validator": "0.6.3",
|
|
39
|
+
"highlight.js": "11.11.1",
|
|
40
40
|
"markdown-it": "^14.1.0",
|
|
41
41
|
"markdown-it-anchor": "^9.2.0"
|
|
42
42
|
}
|
package/src/api/build-site.js
CHANGED
|
@@ -37,9 +37,9 @@ const DEFAULT_POST_INDEX = Object.freeze({
|
|
|
37
37
|
paginate: true,
|
|
38
38
|
});
|
|
39
39
|
const PERMALINK_OUTPUT_STYLES = new Set(['directory', 'html-extension']);
|
|
40
|
-
const COMMENT_POLICY_OUTPUT_PATH = '_zeropress/comment-policy.json';
|
|
41
40
|
const SEARCH_INDEX_OUTPUT_PATH = '_zeropress/search.json';
|
|
42
41
|
const SEARCH_ADAPTER_OUTPUT_PATH = '_zeropress/search.js';
|
|
42
|
+
const SEARCH_PAGEFIND_ADAPTER_OUTPUT_PATH = '_zeropress/search_pagefind.js';
|
|
43
43
|
const OUTPUT_PATH_CONTROL_CHAR_PATTERN = /[\u0000-\u001F\u007F]/;
|
|
44
44
|
const SAFE_MEDIA_PROTOCOLS = new Set(['http:', 'https:']);
|
|
45
45
|
const SAFE_LINK_PROTOCOLS = new Set(['http:', 'https:', 'mailto:', 'tel:']);
|
|
@@ -99,17 +99,13 @@ export async function buildSite(input) {
|
|
|
99
99
|
await writeOutput(state.writer, state.summaries, assetOutput.path, assetOutput.content, assetOutput.contentType);
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
state.writer,
|
|
104
|
-
state.summaries,
|
|
105
|
-
COMMENT_POLICY_OUTPUT_PATH,
|
|
106
|
-
state.commentPolicyContent,
|
|
107
|
-
'application/json',
|
|
108
|
-
);
|
|
109
|
-
|
|
110
|
-
if (options.generateSpecialFiles) {
|
|
102
|
+
if (shouldGenerateSearchArtifacts(state, options)) {
|
|
111
103
|
await writeOutput(state.writer, state.summaries, SEARCH_INDEX_OUTPUT_PATH, buildSearchIndexJson(state), 'application/json');
|
|
112
104
|
await writeOutput(state.writer, state.summaries, SEARCH_ADAPTER_OUTPUT_PATH, buildSearchAdapterJs(), 'application/javascript');
|
|
105
|
+
await writeOutput(state.writer, state.summaries, SEARCH_PAGEFIND_ADAPTER_OUTPUT_PATH, buildSearchPagefindAdapterJs(), 'application/javascript');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (options.generateSpecialFiles) {
|
|
113
109
|
await maybeRenderNotFoundPage(state);
|
|
114
110
|
if (hasCanonicalSiteUrl(state.previewData.site.url)) {
|
|
115
111
|
await writeOutput(
|
|
@@ -168,7 +164,6 @@ async function createBuildState(input, options) {
|
|
|
168
164
|
customHtml: previewData.custom_html,
|
|
169
165
|
favicon: previewData.site.favicon,
|
|
170
166
|
exposeGenerator: previewData.site.expose_generator !== false,
|
|
171
|
-
commentPolicyContent: buildCommentPolicyManifest(renderData.posts),
|
|
172
167
|
options,
|
|
173
168
|
generatedAt: new Date(),
|
|
174
169
|
emitted: {
|
|
@@ -234,7 +229,9 @@ async function renderRoute(state, templateName, route) {
|
|
|
234
229
|
route: routeContext,
|
|
235
230
|
meta: buildPageMeta(state.previewData.site, {
|
|
236
231
|
currentUrl,
|
|
237
|
-
title:
|
|
232
|
+
title: route.is_front_page === true
|
|
233
|
+
? buildFrontPageTitle(state.previewData.site)
|
|
234
|
+
: state.previewData.site.title,
|
|
238
235
|
description: state.previewData.site.description,
|
|
239
236
|
ogType: 'website',
|
|
240
237
|
}),
|
|
@@ -281,7 +278,7 @@ async function renderFrontPage(state, route) {
|
|
|
281
278
|
route: routeContext,
|
|
282
279
|
meta: buildPageMeta(state.previewData.site, {
|
|
283
280
|
currentUrl,
|
|
284
|
-
title:
|
|
281
|
+
title: buildFrontPageTitle(state.previewData.site),
|
|
285
282
|
description: page.excerpt,
|
|
286
283
|
ogType: 'website',
|
|
287
284
|
image: page.featured_image,
|
|
@@ -314,7 +311,7 @@ async function renderFrontPage(state, route) {
|
|
|
314
311
|
route: routeContext,
|
|
315
312
|
meta: buildPageMeta(state.previewData.site, {
|
|
316
313
|
currentUrl,
|
|
317
|
-
title: state.previewData.site
|
|
314
|
+
title: buildFrontPageTitle(state.previewData.site),
|
|
318
315
|
description: state.previewData.site.description,
|
|
319
316
|
ogType: 'website',
|
|
320
317
|
}),
|
|
@@ -436,13 +433,17 @@ async function maybeRenderNotFoundPage(state) {
|
|
|
436
433
|
}
|
|
437
434
|
|
|
438
435
|
function normalizePreviewData(previewData, options = {}) {
|
|
436
|
+
const media_base_url = normalizeOptionalString(previewData.site.media_base_url);
|
|
439
437
|
const normalizedSite = {
|
|
440
438
|
...previewData.site,
|
|
441
|
-
media_base_url
|
|
439
|
+
media_base_url,
|
|
442
440
|
media_delivery_mode: MEDIA_DELIVERY_MODES.has(previewData.site.media_delivery_mode)
|
|
443
441
|
? previewData.site.media_delivery_mode
|
|
444
442
|
: 'none',
|
|
445
|
-
favicon:
|
|
443
|
+
favicon: previewData.site.favicon
|
|
444
|
+
? normalizeSiteFavicon(previewData.site.favicon, media_base_url)
|
|
445
|
+
: normalizeSiteFavicon(options.favicon, ''),
|
|
446
|
+
logo: normalizeSiteLogo(previewData.site.logo, media_base_url),
|
|
446
447
|
posts_per_page: Number.isInteger(previewData.site.posts_per_page) && previewData.site.posts_per_page > 0
|
|
447
448
|
? previewData.site.posts_per_page
|
|
448
449
|
: DEFAULT_POSTS_PER_PAGE,
|
|
@@ -459,6 +460,7 @@ function normalizePreviewData(previewData, options = {}) {
|
|
|
459
460
|
locale: normalizeLocale(previewData.site.locale || DEFAULT_LOCALE),
|
|
460
461
|
disallow_comments: previewData.site.disallow_comments === true,
|
|
461
462
|
expose_generator: previewData.site.expose_generator !== false,
|
|
463
|
+
search: previewData.site.search !== false,
|
|
462
464
|
indexing: previewData.site.indexing !== false,
|
|
463
465
|
permalinks: normalizePermalinks(previewData.site.permalinks),
|
|
464
466
|
front_page: normalizeFrontPage(previewData.site.front_page),
|
|
@@ -732,7 +734,7 @@ function normalizeCustomHtml(customHtml) {
|
|
|
732
734
|
};
|
|
733
735
|
}
|
|
734
736
|
|
|
735
|
-
function normalizeSiteFavicon(favicon) {
|
|
737
|
+
function normalizeSiteFavicon(favicon, media_base_url) {
|
|
736
738
|
if (!favicon || typeof favicon !== 'object') {
|
|
737
739
|
return undefined;
|
|
738
740
|
}
|
|
@@ -741,13 +743,30 @@ function normalizeSiteFavicon(favicon) {
|
|
|
741
743
|
for (const key of ['icon', 'svg', 'png', 'apple_touch_icon']) {
|
|
742
744
|
const value = normalizeOptionalString(favicon[key]);
|
|
743
745
|
if (value) {
|
|
744
|
-
normalized[key] = value;
|
|
746
|
+
normalized[key] = normalizeMediaField(value, media_base_url);
|
|
745
747
|
}
|
|
746
748
|
}
|
|
747
749
|
|
|
748
750
|
return Object.keys(normalized).length ? normalized : undefined;
|
|
749
751
|
}
|
|
750
752
|
|
|
753
|
+
function normalizeSiteLogo(logo, media_base_url) {
|
|
754
|
+
if (!logo || typeof logo !== 'object') {
|
|
755
|
+
return undefined;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
const src = normalizeMediaField(logo.src, media_base_url);
|
|
759
|
+
if (!src) {
|
|
760
|
+
return undefined;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
const alt = normalizeOptionalString(logo.alt);
|
|
764
|
+
return {
|
|
765
|
+
src,
|
|
766
|
+
...(alt ? { alt } : {}),
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
|
|
751
770
|
function normalizePermalinks(permalinks) {
|
|
752
771
|
const source = permalinks && typeof permalinks === 'object' ? permalinks : {};
|
|
753
772
|
const outputStyle = typeof source.output_style === 'string' && PERMALINK_OUTPUT_STYLES.has(source.output_style)
|
|
@@ -794,6 +813,8 @@ function normalizePostIndex(post_index) {
|
|
|
794
813
|
function createRenderData(previewData, themeMetadata = {}) {
|
|
795
814
|
const themeSupportsComments = themeMetadata?.features?.comments === true;
|
|
796
815
|
const themeSupportsPostIndex = themeMetadata?.features?.post_index !== false;
|
|
816
|
+
const themeSupportsSearch = themeMetadata?.features?.search === true;
|
|
817
|
+
previewData.site.search = previewData.site.search !== false && themeSupportsSearch;
|
|
797
818
|
const authorsById = new Map(previewData.content.authors.map((author) => [author.id, author]));
|
|
798
819
|
const categoriesBySlug = new Map(previewData.content.categories.map((category) => [category.slug, category]));
|
|
799
820
|
const tagsBySlug = new Map(previewData.content.tags.map((tag) => [tag.slug, tag]));
|
|
@@ -1426,18 +1447,6 @@ function preparePost(post, site, authorsById, categoriesBySlug, tagsBySlug, them
|
|
|
1426
1447
|
};
|
|
1427
1448
|
}
|
|
1428
1449
|
|
|
1429
|
-
function buildCommentPolicyManifest(posts) {
|
|
1430
|
-
const commentablePosts = posts
|
|
1431
|
-
.filter((post) => post.status === 'published' && post.comments_enabled === true)
|
|
1432
|
-
.map((post) => post.public_id)
|
|
1433
|
-
.filter((value) => Number.isInteger(value) && value > 0);
|
|
1434
|
-
|
|
1435
|
-
return JSON.stringify({
|
|
1436
|
-
version: 1,
|
|
1437
|
-
commentable_posts: commentablePosts,
|
|
1438
|
-
}, null, 2);
|
|
1439
|
-
}
|
|
1440
|
-
|
|
1441
1450
|
function buildTaxonomyRoutes(options) {
|
|
1442
1451
|
const routes = [];
|
|
1443
1452
|
|
|
@@ -2086,6 +2095,12 @@ function buildDocumentTitle(contentTitle, siteTitle) {
|
|
|
2086
2095
|
return `${resolvedContentTitle} - ${resolvedSiteTitle}`;
|
|
2087
2096
|
}
|
|
2088
2097
|
|
|
2098
|
+
function buildFrontPageTitle(site) {
|
|
2099
|
+
const resolvedSiteTitle = normalizeNonEmptyString(site.title, '');
|
|
2100
|
+
const resolvedDescription = normalizeOptionalString(site.description);
|
|
2101
|
+
return resolvedDescription ? `${resolvedSiteTitle} - ${resolvedDescription}` : resolvedSiteTitle;
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2089
2104
|
function buildMetaHeadTags(meta) {
|
|
2090
2105
|
const tags = [];
|
|
2091
2106
|
|
|
@@ -2390,11 +2405,13 @@ function assertPlannedOutputPathsSafe(state) {
|
|
|
2390
2405
|
const plannedPaths = [
|
|
2391
2406
|
...routeEntries.map((entry) => entry.outputPath),
|
|
2392
2407
|
...state.assetOutputs.map((assetOutput) => assetOutput.path),
|
|
2393
|
-
COMMENT_POLICY_OUTPUT_PATH,
|
|
2394
2408
|
];
|
|
2395
2409
|
|
|
2410
|
+
if (shouldGenerateSearchArtifacts(state, state.options)) {
|
|
2411
|
+
plannedPaths.push(SEARCH_INDEX_OUTPUT_PATH, SEARCH_ADAPTER_OUTPUT_PATH, SEARCH_PAGEFIND_ADAPTER_OUTPUT_PATH);
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2396
2414
|
if (state.options.generateSpecialFiles) {
|
|
2397
|
-
plannedPaths.push(SEARCH_INDEX_OUTPUT_PATH, SEARCH_ADAPTER_OUTPUT_PATH);
|
|
2398
2415
|
plannedPaths.push('404.html');
|
|
2399
2416
|
if (shouldGenerateRobotsTxt(state.options)) {
|
|
2400
2417
|
plannedPaths.push('robots.txt');
|
|
@@ -2965,6 +2982,88 @@ function normalizeLimit(value) {
|
|
|
2965
2982
|
`;
|
|
2966
2983
|
}
|
|
2967
2984
|
|
|
2985
|
+
function buildSearchPagefindAdapterJs() {
|
|
2986
|
+
return `let pagefindPromise;
|
|
2987
|
+
|
|
2988
|
+
export async function preload() {
|
|
2989
|
+
if (!pagefindPromise) {
|
|
2990
|
+
pagefindPromise = import(new URL('./pagefind/pagefind.js', import.meta.url).href).then(async (pagefind) => {
|
|
2991
|
+
if (typeof pagefind.options === 'function') {
|
|
2992
|
+
await pagefind.options({ baseUrl: '/' });
|
|
2993
|
+
}
|
|
2994
|
+
return pagefind;
|
|
2995
|
+
});
|
|
2996
|
+
}
|
|
2997
|
+
|
|
2998
|
+
return pagefindPromise;
|
|
2999
|
+
}
|
|
3000
|
+
|
|
3001
|
+
export async function search(query, options = {}) {
|
|
3002
|
+
const pagefind = await preload();
|
|
3003
|
+
const result = await pagefind.search(query, options);
|
|
3004
|
+
const limit = normalizeLimit(options.limit);
|
|
3005
|
+
if (!Array.isArray(result?.results)) {
|
|
3006
|
+
return result;
|
|
3007
|
+
}
|
|
3008
|
+
|
|
3009
|
+
const results = result.results.map(normalizeResult);
|
|
3010
|
+
return {
|
|
3011
|
+
...result,
|
|
3012
|
+
results: limit ? results.slice(0, limit) : results,
|
|
3013
|
+
};
|
|
3014
|
+
}
|
|
3015
|
+
|
|
3016
|
+
function normalizeResult(result) {
|
|
3017
|
+
if (!result || typeof result.data !== 'function') {
|
|
3018
|
+
return result;
|
|
3019
|
+
}
|
|
3020
|
+
|
|
3021
|
+
return {
|
|
3022
|
+
...result,
|
|
3023
|
+
data: async () => normalizeResultData(await result.data()),
|
|
3024
|
+
};
|
|
3025
|
+
}
|
|
3026
|
+
|
|
3027
|
+
function normalizeResultData(data) {
|
|
3028
|
+
if (!data || typeof data !== 'object') {
|
|
3029
|
+
return data;
|
|
3030
|
+
}
|
|
3031
|
+
|
|
3032
|
+
return {
|
|
3033
|
+
...data,
|
|
3034
|
+
url: normalizeUrl(data.url),
|
|
3035
|
+
sub_results: Array.isArray(data.sub_results)
|
|
3036
|
+
? data.sub_results.map((item) => ({ ...item, url: normalizeUrl(item.url) }))
|
|
3037
|
+
: data.sub_results,
|
|
3038
|
+
};
|
|
3039
|
+
}
|
|
3040
|
+
|
|
3041
|
+
function normalizeUrl(value) {
|
|
3042
|
+
const url = String(value || '');
|
|
3043
|
+
if (url.startsWith('/_zeropress/') && !url.startsWith('/_zeropress/pagefind/')) {
|
|
3044
|
+
return url.replace(/^\\/_zeropress/, '') || '/';
|
|
3045
|
+
}
|
|
3046
|
+
if (url.startsWith('_zeropress/') && !url.startsWith('_zeropress/pagefind/')) {
|
|
3047
|
+
return url.replace(/^_zeropress/, '') || '/';
|
|
3048
|
+
}
|
|
3049
|
+
return url;
|
|
3050
|
+
}
|
|
3051
|
+
|
|
3052
|
+
function normalizeLimit(value) {
|
|
3053
|
+
if (value === undefined || value === null) {
|
|
3054
|
+
return null;
|
|
3055
|
+
}
|
|
3056
|
+
|
|
3057
|
+
const limit = Number(value);
|
|
3058
|
+
if (!Number.isFinite(limit) || limit <= 0) {
|
|
3059
|
+
return null;
|
|
3060
|
+
}
|
|
3061
|
+
|
|
3062
|
+
return Math.floor(limit);
|
|
3063
|
+
}
|
|
3064
|
+
`;
|
|
3065
|
+
}
|
|
3066
|
+
|
|
2968
3067
|
function buildSitemapXml(site, emitted, generatedAt, stylesheetHref = '') {
|
|
2969
3068
|
const entries = [
|
|
2970
3069
|
...(emitted.frontPage && emitted.frontPage.includeInSitemap !== false
|
|
@@ -3045,6 +3144,10 @@ function shouldGenerateRobotsTxt(options) {
|
|
|
3045
3144
|
return options.generateSpecialFiles && options.generateRobotsTxt !== false;
|
|
3046
3145
|
}
|
|
3047
3146
|
|
|
3147
|
+
function shouldGenerateSearchArtifacts(state, options) {
|
|
3148
|
+
return options.generateSpecialFiles && state.previewData.site.search === true;
|
|
3149
|
+
}
|
|
3150
|
+
|
|
3048
3151
|
function getContentType(assetPath) {
|
|
3049
3152
|
const ext = assetPath.split('.').pop()?.toLowerCase();
|
|
3050
3153
|
const contentTypes = {
|