@zeropress/build-core 0.5.1 → 0.5.2
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 -2
- package/package.json +4 -4
- package/src/api/build-site.js +323 -23
- package/src/assets/asset-processor.js +1 -6
- package/src/index.d.ts +7 -1
- package/src/theme/load-theme-dir.js +0 -1
package/README.md
CHANGED
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
- generating special files such as:
|
|
55
55
|
- `sitemap.xml`
|
|
56
56
|
- `feed.xml`
|
|
57
|
-
- `robots.txt`
|
|
57
|
+
- fallback `robots.txt`
|
|
58
58
|
- writing outputs through a pluggable writer
|
|
59
59
|
|
|
60
60
|
It does not:
|
|
@@ -119,7 +119,9 @@ Notes:
|
|
|
119
119
|
- `previewData` must already satisfy the canonical preview-data contract
|
|
120
120
|
- `themePackage` must already be a validated in-memory theme package
|
|
121
121
|
- `sitemap.xml` and `feed.xml` are emitted only when `site.url` is a non-empty canonical URL
|
|
122
|
-
- `robots.txt` is
|
|
122
|
+
- fallback `robots.txt` is emitted when `generateSpecialFiles` is enabled and `generateRobotsTxt` is not `false`
|
|
123
|
+
- fallback `robots.txt` uses `site.indexing`; `false` emits `Disallow: /`, while missing or `true` emits `Allow: /`
|
|
124
|
+
- callers that disable fallback robots because a public `robots.txt` exists should copy that file as-is; sitemap directives in custom robots files are caller/user responsibility
|
|
123
125
|
|
|
124
126
|
### `buildSiteFromThemeDir(input)`
|
|
125
127
|
|
|
@@ -175,6 +177,8 @@ Theme validation is enforced through:
|
|
|
175
177
|
|
|
176
178
|
`buildSiteFromThemeDir()` is the convenience entry point that loads a theme directory and converts it into the required in-memory `themePackage`.
|
|
177
179
|
|
|
180
|
+
JavaScript theme assets are emitted as provided. Build-core may hash output filenames, but it does not rewrite or minify JavaScript content.
|
|
181
|
+
|
|
178
182
|
As of `preview-data v0.4`, the payload no longer carries `routes` arrays or raw HTML fragments such as `categories_html`.
|
|
179
183
|
|
|
180
184
|
Build-core now derives:
|
|
@@ -219,6 +223,7 @@ Supported options:
|
|
|
219
223
|
|
|
220
224
|
- `assetHashing`
|
|
221
225
|
- `generateSpecialFiles`
|
|
226
|
+
- `generateRobotsTxt`
|
|
222
227
|
- `writeManifest`
|
|
223
228
|
|
|
224
229
|
These options apply to both full builds and partial renders where relevant.
|
|
@@ -227,6 +232,7 @@ Defaults:
|
|
|
227
232
|
|
|
228
233
|
- `assetHashing: true`
|
|
229
234
|
- `generateSpecialFiles: true`
|
|
235
|
+
- `generateRobotsTxt: true`
|
|
230
236
|
- `writeManifest: false`
|
|
231
237
|
|
|
232
238
|
---
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeropress/build-core",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "Shared ZeroPress build core",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,9 +33,9 @@
|
|
|
33
33
|
"node": ">=18.18.0"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@zeropress/slug-policy": "
|
|
37
|
-
"@zeropress/preview-data-validator": "0.5.
|
|
38
|
-
"@zeropress/theme-validator": "0.5.
|
|
36
|
+
"@zeropress/slug-policy": "0.1.0",
|
|
37
|
+
"@zeropress/preview-data-validator": "0.5.2",
|
|
38
|
+
"@zeropress/theme-validator": "0.5.3",
|
|
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
|
@@ -9,6 +9,7 @@ import { ZeroPressEngine } from '../render/zeropress-engine.js';
|
|
|
9
9
|
const DEFAULT_OPTIONS = {
|
|
10
10
|
assetHashing: true,
|
|
11
11
|
generateSpecialFiles: true,
|
|
12
|
+
generateRobotsTxt: true,
|
|
12
13
|
writeManifest: false,
|
|
13
14
|
};
|
|
14
15
|
|
|
@@ -37,6 +38,9 @@ const COMMENT_POLICY_OUTPUT_PATH = '_zeropress/comment-policy.json';
|
|
|
37
38
|
const OUTPUT_PATH_CONTROL_CHAR_PATTERN = /[\u0000-\u001F\u007F]/;
|
|
38
39
|
const SAFE_MEDIA_PROTOCOLS = new Set(['http:', 'https:']);
|
|
39
40
|
const SAFE_LINK_PROTOCOLS = new Set(['http:', 'https:', 'mailto:', 'tel:']);
|
|
41
|
+
const MEDIA_DELIVERY_MODES = new Set(['none', 'media_domain']);
|
|
42
|
+
const RESPONSIVE_IMAGE_WIDTHS = [320, 480, 768, 1024, 1280, 1600, 1920];
|
|
43
|
+
const RESPONSIVE_IMAGE_EXTENSIONS = new Set(['jpg', 'jpeg', 'png', 'webp', 'avif']);
|
|
40
44
|
export async function buildSite(input) {
|
|
41
45
|
const options = { ...DEFAULT_OPTIONS, ...(input.options || {}) };
|
|
42
46
|
const state = await createBuildState(input, options);
|
|
@@ -94,7 +98,9 @@ export async function buildSite(input) {
|
|
|
94
98
|
await writeOutput(state.writer, state.summaries, 'sitemap.xml', buildSitemapXml(state.previewData.site, state.emitted, state.generatedAt), 'application/xml');
|
|
95
99
|
await writeOutput(state.writer, state.summaries, 'feed.xml', buildFeedXml(state.previewData.site, state.emitted, state.generatedAt), 'application/rss+xml');
|
|
96
100
|
}
|
|
97
|
-
|
|
101
|
+
if (shouldGenerateRobotsTxt(options)) {
|
|
102
|
+
await writeOutput(state.writer, state.summaries, 'robots.txt', buildRobotsTxt(state.previewData.site), 'text/plain');
|
|
103
|
+
}
|
|
98
104
|
}
|
|
99
105
|
|
|
100
106
|
return finalizeBuildResult(state.writer, state.summaries, options);
|
|
@@ -111,7 +117,7 @@ async function createBuildState(input, options) {
|
|
|
111
117
|
const engine = new ZeroPressEngine();
|
|
112
118
|
const assetProcessor = new AssetProcessor();
|
|
113
119
|
const summaries = [];
|
|
114
|
-
const previewData = normalizePreviewData(input.previewData);
|
|
120
|
+
const previewData = normalizePreviewData(input.previewData, options);
|
|
115
121
|
const renderData = createRenderData(previewData, themePackage.metadata);
|
|
116
122
|
|
|
117
123
|
engine.initialize(themePackage);
|
|
@@ -137,6 +143,7 @@ async function createBuildState(input, options) {
|
|
|
137
143
|
assetMap,
|
|
138
144
|
customCssHref: customCssAsset ? `/${customCssAsset.path}` : '',
|
|
139
145
|
customHtml: previewData.custom_html,
|
|
146
|
+
favicon: previewData.site.favicon,
|
|
140
147
|
commentPolicyContent: buildCommentPolicyManifest(renderData.posts),
|
|
141
148
|
options,
|
|
142
149
|
generatedAt: new Date(),
|
|
@@ -197,6 +204,7 @@ async function renderRoute(state, templateName, route) {
|
|
|
197
204
|
{
|
|
198
205
|
menus: state.previewData.menus,
|
|
199
206
|
widgets: state.widgets,
|
|
207
|
+
collections: state.renderData.collections,
|
|
200
208
|
taxonomies: state.renderData.taxonomies,
|
|
201
209
|
...route,
|
|
202
210
|
route: routeContext,
|
|
@@ -243,6 +251,7 @@ async function renderFrontPage(state, route) {
|
|
|
243
251
|
{
|
|
244
252
|
menus: state.previewData.menus,
|
|
245
253
|
widgets: state.widgets,
|
|
254
|
+
collections: state.renderData.collections,
|
|
246
255
|
taxonomies: state.renderData.taxonomies,
|
|
247
256
|
page,
|
|
248
257
|
route: routeContext,
|
|
@@ -273,6 +282,7 @@ async function renderFrontPage(state, route) {
|
|
|
273
282
|
{
|
|
274
283
|
menus: state.previewData.menus,
|
|
275
284
|
widgets: state.widgets,
|
|
285
|
+
collections: state.renderData.collections,
|
|
276
286
|
taxonomies: state.renderData.taxonomies,
|
|
277
287
|
...route,
|
|
278
288
|
route: routeContext,
|
|
@@ -303,6 +313,7 @@ async function renderPost(state, post) {
|
|
|
303
313
|
{
|
|
304
314
|
menus: state.previewData.menus,
|
|
305
315
|
widgets: state.widgets,
|
|
316
|
+
collections: state.renderData.collections,
|
|
306
317
|
taxonomies: state.renderData.taxonomies,
|
|
307
318
|
post,
|
|
308
319
|
route: buildRouteContext('post', currentUrl),
|
|
@@ -340,6 +351,7 @@ async function renderPage(state, page) {
|
|
|
340
351
|
{
|
|
341
352
|
menus: state.previewData.menus,
|
|
342
353
|
widgets: state.widgets,
|
|
354
|
+
collections: state.renderData.collections,
|
|
343
355
|
taxonomies: state.renderData.taxonomies,
|
|
344
356
|
page,
|
|
345
357
|
route: buildRouteContext('page', currentUrl),
|
|
@@ -376,6 +388,7 @@ async function maybeRenderNotFoundPage(state) {
|
|
|
376
388
|
{
|
|
377
389
|
menus: state.previewData.menus,
|
|
378
390
|
widgets: state.widgets,
|
|
391
|
+
collections: state.renderData.collections,
|
|
379
392
|
taxonomies: state.renderData.taxonomies,
|
|
380
393
|
route: buildRouteContext('not_found', '/404.html'),
|
|
381
394
|
meta: buildPageMeta(state.previewData.site, {
|
|
@@ -392,10 +405,14 @@ async function maybeRenderNotFoundPage(state) {
|
|
|
392
405
|
await writeOutput(state.writer, state.summaries, '404.html', html, 'text/html');
|
|
393
406
|
}
|
|
394
407
|
|
|
395
|
-
function normalizePreviewData(previewData) {
|
|
408
|
+
function normalizePreviewData(previewData, options = {}) {
|
|
396
409
|
const normalizedSite = {
|
|
397
410
|
...previewData.site,
|
|
398
411
|
mediaBaseUrl: normalizeOptionalString(previewData.site.mediaBaseUrl),
|
|
412
|
+
mediaDeliveryMode: MEDIA_DELIVERY_MODES.has(previewData.site.mediaDeliveryMode)
|
|
413
|
+
? previewData.site.mediaDeliveryMode
|
|
414
|
+
: 'none',
|
|
415
|
+
favicon: normalizeSiteFavicon(previewData.site.favicon || options.favicon),
|
|
399
416
|
postsPerPage: Number.isInteger(previewData.site.postsPerPage) && previewData.site.postsPerPage > 0
|
|
400
417
|
? previewData.site.postsPerPage
|
|
401
418
|
: DEFAULT_POSTS_PER_PAGE,
|
|
@@ -404,42 +421,195 @@ function normalizePreviewData(previewData) {
|
|
|
404
421
|
timezone: normalizeNonEmptyString(previewData.site.timezone, DEFAULT_TIMEZONE),
|
|
405
422
|
locale: normalizeLocale(previewData.site.locale || DEFAULT_LOCALE),
|
|
406
423
|
disallowComments: previewData.site.disallowComments === true,
|
|
424
|
+
indexing: previewData.site.indexing !== false,
|
|
407
425
|
permalinks: normalizePermalinks(previewData.site.permalinks),
|
|
408
426
|
front_page: normalizeFrontPage(previewData.site.front_page),
|
|
409
427
|
post_index: normalizePostIndex(previewData.site.post_index),
|
|
410
428
|
footer: normalizeSiteFooter(previewData.site.footer),
|
|
411
429
|
};
|
|
430
|
+
const media = normalizeContentMedia(previewData.content.media, normalizedSite);
|
|
431
|
+
const mediaRegistry = buildMediaRegistry(media);
|
|
412
432
|
|
|
413
433
|
return {
|
|
414
434
|
...previewData,
|
|
415
435
|
site: normalizedSite,
|
|
436
|
+
menus: normalizeRecordMap(previewData.menus),
|
|
437
|
+
collections: normalizeCollections(previewData.collections),
|
|
416
438
|
widgets: normalizeWidgetAreas(previewData.widgets, normalizedSite.mediaBaseUrl),
|
|
417
439
|
custom_css: normalizeCustomCss(previewData.custom_css),
|
|
418
440
|
custom_html: normalizeCustomHtml(previewData.custom_html),
|
|
419
441
|
content: {
|
|
420
442
|
...previewData.content,
|
|
421
|
-
authors: previewData.content.authors.map((author) =>
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
443
|
+
authors: previewData.content.authors.map((author) => {
|
|
444
|
+
const avatar = normalizeMediaField(author.avatar, normalizedSite.mediaBaseUrl);
|
|
445
|
+
const avatarMedia = deriveManagedMedia(avatar, mediaRegistry, normalizedSite);
|
|
446
|
+
return {
|
|
447
|
+
...author,
|
|
448
|
+
avatar,
|
|
449
|
+
...(avatarMedia ? { avatar_media: avatarMedia } : {}),
|
|
450
|
+
};
|
|
451
|
+
}),
|
|
425
452
|
posts: previewData.content.posts
|
|
426
|
-
.map((post) =>
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
453
|
+
.map((post) => {
|
|
454
|
+
const featuredImage = normalizeMediaField(post.featured_image, normalizedSite.mediaBaseUrl);
|
|
455
|
+
const featuredMedia = deriveManagedMedia(featuredImage, mediaRegistry, normalizedSite);
|
|
456
|
+
return {
|
|
457
|
+
...post,
|
|
458
|
+
published_at_iso: normalizeIsoTimestamp(post.published_at_iso),
|
|
459
|
+
updated_at_iso: normalizeIsoTimestamp(post.updated_at_iso),
|
|
460
|
+
featured_image: featuredImage,
|
|
461
|
+
...(featuredMedia ? { featured_media: featuredMedia } : {}),
|
|
462
|
+
};
|
|
463
|
+
})
|
|
432
464
|
.sort((left, right) => toDate(right.published_at_iso).getTime() - toDate(left.published_at_iso).getTime()),
|
|
433
|
-
pages: previewData.content.pages.map((page) =>
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
465
|
+
pages: previewData.content.pages.map((page) => {
|
|
466
|
+
const featuredImage = normalizeMediaField(page.featured_image, normalizedSite.mediaBaseUrl);
|
|
467
|
+
const featuredMedia = deriveManagedMedia(featuredImage, mediaRegistry, normalizedSite);
|
|
468
|
+
return {
|
|
469
|
+
...page,
|
|
470
|
+
featured_image: featuredImage,
|
|
471
|
+
...(featuredMedia ? { featured_media: featuredMedia } : {}),
|
|
472
|
+
};
|
|
473
|
+
}),
|
|
437
474
|
categories: [...previewData.content.categories],
|
|
438
475
|
tags: [...previewData.content.tags],
|
|
476
|
+
media,
|
|
439
477
|
},
|
|
440
478
|
};
|
|
441
479
|
}
|
|
442
480
|
|
|
481
|
+
function normalizeRecordMap(value) {
|
|
482
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
483
|
+
return {};
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return { ...value };
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function normalizeContentMedia(mediaItems, site) {
|
|
490
|
+
if (!Array.isArray(mediaItems)) {
|
|
491
|
+
return [];
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
return mediaItems
|
|
495
|
+
.map((item) => {
|
|
496
|
+
if (!item || typeof item !== 'object') {
|
|
497
|
+
return null;
|
|
498
|
+
}
|
|
499
|
+
const src = normalizeMediaField(item.src, site.mediaBaseUrl);
|
|
500
|
+
const width = Number.isInteger(item.width) && item.width > 0 ? item.width : 0;
|
|
501
|
+
const height = Number.isInteger(item.height) && item.height > 0 ? item.height : 0;
|
|
502
|
+
if (!src || !width || !height) {
|
|
503
|
+
return null;
|
|
504
|
+
}
|
|
505
|
+
return {
|
|
506
|
+
src,
|
|
507
|
+
width,
|
|
508
|
+
height,
|
|
509
|
+
alt: typeof item.alt === 'string' ? item.alt : '',
|
|
510
|
+
};
|
|
511
|
+
})
|
|
512
|
+
.filter(Boolean);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function buildMediaRegistry(mediaItems) {
|
|
516
|
+
const registry = new Map();
|
|
517
|
+
for (const item of mediaItems) {
|
|
518
|
+
if (!registry.has(item.src)) {
|
|
519
|
+
registry.set(item.src, item);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return registry;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function deriveManagedMedia(src, mediaRegistry, site) {
|
|
526
|
+
const normalizedSrc = normalizeOptionalString(src);
|
|
527
|
+
if (!normalizedSrc) {
|
|
528
|
+
return null;
|
|
529
|
+
}
|
|
530
|
+
const media = mediaRegistry.get(normalizedSrc);
|
|
531
|
+
if (!media) {
|
|
532
|
+
return null;
|
|
533
|
+
}
|
|
534
|
+
return {
|
|
535
|
+
...media,
|
|
536
|
+
srcset: buildResponsiveImageSrcset(media, site),
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function buildResponsiveImageSrcset(media, site) {
|
|
541
|
+
if (site.mediaDeliveryMode !== 'media_domain') {
|
|
542
|
+
return '';
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const mediaBaseUrl = normalizeOptionalString(site.mediaBaseUrl);
|
|
546
|
+
if (!mediaBaseUrl || !isUrlUnderMediaBase(media.src, mediaBaseUrl) || !isResponsiveRasterImage(media.src)) {
|
|
547
|
+
return '';
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const widths = RESPONSIVE_IMAGE_WIDTHS.filter((width) => width <= media.width);
|
|
551
|
+
if (!widths.includes(media.width)) {
|
|
552
|
+
widths.push(media.width);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
return widths
|
|
556
|
+
.filter((width, index, values) => width > 0 && values.indexOf(width) === index)
|
|
557
|
+
.map((width) => `${buildResponsiveImageVariantUrl(media.src, width)} ${width}w`)
|
|
558
|
+
.join(', ');
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function buildResponsiveImageVariantUrl(src, width) {
|
|
562
|
+
try {
|
|
563
|
+
const url = new URL(src);
|
|
564
|
+
url.searchParams.set('w', String(width));
|
|
565
|
+
url.searchParams.set('fit', 'scale-down');
|
|
566
|
+
url.searchParams.set('format', 'auto');
|
|
567
|
+
return decodeURI(url.toString());
|
|
568
|
+
} catch {
|
|
569
|
+
return src;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function isUrlUnderMediaBase(src, mediaBaseUrl) {
|
|
574
|
+
try {
|
|
575
|
+
const sourceUrl = new URL(src);
|
|
576
|
+
const baseUrl = new URL(mediaBaseUrl);
|
|
577
|
+
const basePath = baseUrl.pathname.endsWith('/') ? baseUrl.pathname : `${baseUrl.pathname}/`;
|
|
578
|
+
return sourceUrl.origin === baseUrl.origin && sourceUrl.pathname.startsWith(basePath);
|
|
579
|
+
} catch {
|
|
580
|
+
return false;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function isResponsiveRasterImage(src) {
|
|
585
|
+
try {
|
|
586
|
+
const url = new URL(src);
|
|
587
|
+
const lastSegment = url.pathname.split('/').pop() || '';
|
|
588
|
+
const extension = lastSegment.includes('.') ? lastSegment.split('.').pop().toLowerCase() : '';
|
|
589
|
+
return RESPONSIVE_IMAGE_EXTENSIONS.has(extension);
|
|
590
|
+
} catch {
|
|
591
|
+
return false;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function normalizeCollections(collections) {
|
|
596
|
+
if (!collections || typeof collections !== 'object' || Array.isArray(collections)) {
|
|
597
|
+
return {};
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
return Object.fromEntries(
|
|
601
|
+
Object.entries(collections).map(([collectionId, collection]) => [
|
|
602
|
+
collectionId,
|
|
603
|
+
{
|
|
604
|
+
...collection,
|
|
605
|
+
title: normalizeOptionalString(collection?.title),
|
|
606
|
+
description: normalizeOptionalString(collection?.description),
|
|
607
|
+
items: Array.isArray(collection?.items) ? collection.items.map((item) => ({ ...item })) : [],
|
|
608
|
+
},
|
|
609
|
+
]),
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
|
|
443
613
|
function normalizeWidgetAreas(widgetAreas, mediaBaseUrl) {
|
|
444
614
|
if (!widgetAreas || typeof widgetAreas !== 'object') {
|
|
445
615
|
return {};
|
|
@@ -516,6 +686,22 @@ function normalizeCustomHtml(customHtml) {
|
|
|
516
686
|
};
|
|
517
687
|
}
|
|
518
688
|
|
|
689
|
+
function normalizeSiteFavicon(favicon) {
|
|
690
|
+
if (!favicon || typeof favicon !== 'object') {
|
|
691
|
+
return undefined;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
const normalized = {};
|
|
695
|
+
for (const key of ['icon', 'svg', 'png', 'apple_touch_icon']) {
|
|
696
|
+
const value = normalizeOptionalString(favicon[key]);
|
|
697
|
+
if (value) {
|
|
698
|
+
normalized[key] = value;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
return Object.keys(normalized).length ? normalized : undefined;
|
|
703
|
+
}
|
|
704
|
+
|
|
519
705
|
function normalizePermalinks(permalinks) {
|
|
520
706
|
const source = permalinks && typeof permalinks === 'object' ? permalinks : {};
|
|
521
707
|
const outputStyle = typeof source.output_style === 'string' && PERMALINK_OUTPUT_STYLES.has(source.output_style)
|
|
@@ -590,6 +776,7 @@ function createRenderData(previewData, themeMetadata = {}) {
|
|
|
590
776
|
}));
|
|
591
777
|
const pages = previewData.content.pages.map((page) => preparePage(page, previewData.site));
|
|
592
778
|
const postBySlug = new Map(posts.map((post) => [post.slug, post]));
|
|
779
|
+
const pageBySlug = new Map(pages.map((page) => [page.slug, page]));
|
|
593
780
|
const frontPage = previewData.site.front_page;
|
|
594
781
|
const postIndex = previewData.site.post_index;
|
|
595
782
|
const effectivePostIndexEnabled = postIndex.enabled !== false && themeSupportsPostIndex;
|
|
@@ -610,6 +797,7 @@ function createRenderData(previewData, themeMetadata = {}) {
|
|
|
610
797
|
posts,
|
|
611
798
|
pages: preparedPages,
|
|
612
799
|
postBySlug,
|
|
800
|
+
collections: resolveCollections(previewData.collections, postBySlug, pageBySlug, frontPage),
|
|
613
801
|
taxonomies: buildGlobalTaxonomies(previewData, categoryCountBySlug, tagCountBySlug),
|
|
614
802
|
frontPageRoute,
|
|
615
803
|
indexRoutes: buildPostIndexRoutes({
|
|
@@ -668,6 +856,69 @@ function buildGlobalTaxonomies(previewData, categoryCountBySlug, tagCountBySlug)
|
|
|
668
856
|
};
|
|
669
857
|
}
|
|
670
858
|
|
|
859
|
+
function resolveCollections(collections, postBySlug, pageBySlug, frontPage) {
|
|
860
|
+
if (!collections || typeof collections !== 'object') {
|
|
861
|
+
return {};
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
return Object.fromEntries(
|
|
865
|
+
Object.entries(collections).map(([collectionId, collection]) => [
|
|
866
|
+
collectionId,
|
|
867
|
+
{
|
|
868
|
+
id: collectionId,
|
|
869
|
+
title: normalizeOptionalString(collection?.title),
|
|
870
|
+
description: normalizeOptionalString(collection?.description),
|
|
871
|
+
items: resolveCollectionItems(collectionId, collection?.items, postBySlug, pageBySlug, frontPage),
|
|
872
|
+
},
|
|
873
|
+
]),
|
|
874
|
+
);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
function resolveCollectionItems(collectionId, items, postBySlug, pageBySlug, frontPage) {
|
|
878
|
+
if (!Array.isArray(items)) {
|
|
879
|
+
return [];
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
return items.map((item, index) => resolveCollectionItem(collectionId, item, index, postBySlug, pageBySlug, frontPage));
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
function resolveCollectionItem(collectionId, item, index, postBySlug, pageBySlug, frontPage) {
|
|
886
|
+
if (item?.type === 'post') {
|
|
887
|
+
const post = postBySlug.get(item.slug);
|
|
888
|
+
if (!post) {
|
|
889
|
+
throw new Error(`Invalid collection "${collectionId}": item ${index + 1} references missing post slug "${item.slug}".`);
|
|
890
|
+
}
|
|
891
|
+
return {
|
|
892
|
+
type: 'post',
|
|
893
|
+
meta: post.meta,
|
|
894
|
+
...buildStructuredPostSummary(post),
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
if (item?.type === 'page') {
|
|
899
|
+
const page = pageBySlug.get(item.slug);
|
|
900
|
+
if (!page) {
|
|
901
|
+
throw new Error(`Invalid collection "${collectionId}": item ${index + 1} references missing page slug "${item.slug}".`);
|
|
902
|
+
}
|
|
903
|
+
return buildCollectionPageSummary(page, frontPage);
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
throw new Error(`Invalid collection "${collectionId}": item ${index + 1} has unsupported type "${item?.type}".`);
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
function buildCollectionPageSummary(page, frontPage) {
|
|
910
|
+
return {
|
|
911
|
+
type: 'page',
|
|
912
|
+
title: page.title,
|
|
913
|
+
slug: page.slug,
|
|
914
|
+
url: frontPage?.type === 'page' && frontPage.page_slug === page.slug ? '/' : page.url,
|
|
915
|
+
excerpt: page.excerpt || '',
|
|
916
|
+
featured_image: page.featured_image || '',
|
|
917
|
+
...(page.featured_media ? { featured_media: { ...page.featured_media } } : {}),
|
|
918
|
+
meta: page.meta,
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
|
|
671
922
|
function buildFrontPageRoute(frontPage, pages, effectivePostIndexEnabled, postIndexBasePath) {
|
|
672
923
|
if (frontPage.type === 'theme_index') {
|
|
673
924
|
if (effectivePostIndexEnabled && postIndexBasePath === '/') {
|
|
@@ -1031,6 +1282,7 @@ function preparePost(post, site, authorsById, categoriesBySlug, tagsBySlug, them
|
|
|
1031
1282
|
updated_at_iso: post.updated_at_iso,
|
|
1032
1283
|
author_id: post.author_id,
|
|
1033
1284
|
featured_image: post.featured_image,
|
|
1285
|
+
...(post.featured_media ? { featured_media: { ...post.featured_media } } : {}),
|
|
1034
1286
|
meta: post.meta,
|
|
1035
1287
|
status: post.status,
|
|
1036
1288
|
allow_comments: post.allow_comments,
|
|
@@ -1040,6 +1292,7 @@ function preparePost(post, site, authorsById, categoriesBySlug, tagsBySlug, them
|
|
|
1040
1292
|
id: post.author_id,
|
|
1041
1293
|
display_name: normalizeNonEmptyString(author?.display_name, post.author_id),
|
|
1042
1294
|
avatar: author?.avatar || '',
|
|
1295
|
+
...(author?.avatar_media ? { avatar_media: { ...author.avatar_media } } : {}),
|
|
1043
1296
|
},
|
|
1044
1297
|
categories,
|
|
1045
1298
|
tags,
|
|
@@ -1281,9 +1534,11 @@ function buildStructuredPostSummary(post) {
|
|
|
1281
1534
|
published_at_iso: post.published_at_iso,
|
|
1282
1535
|
reading_time: post.reading_time,
|
|
1283
1536
|
featured_image: post.featured_image,
|
|
1537
|
+
...(post.featured_media ? { featured_media: { ...post.featured_media } } : {}),
|
|
1284
1538
|
author: {
|
|
1285
1539
|
display_name: post.author?.display_name || '',
|
|
1286
1540
|
avatar: post.author?.avatar || '',
|
|
1541
|
+
...(post.author?.avatar_media ? { avatar_media: { ...post.author.avatar_media } } : {}),
|
|
1287
1542
|
},
|
|
1288
1543
|
categories: Array.isArray(post.categories) ? post.categories.map((category) => ({ ...category })) : [],
|
|
1289
1544
|
tags: Array.isArray(post.tags) ? post.tags.map((tag) => ({ ...tag })) : [],
|
|
@@ -1533,7 +1788,8 @@ async function normalizeAndValidateThemePackage(themePackage) {
|
|
|
1533
1788
|
...(themePackage.metadata.features ? { features: themePackage.metadata.features } : {}),
|
|
1534
1789
|
...(themePackage.metadata.menuSlots ? { menuSlots: themePackage.metadata.menuSlots } : {}),
|
|
1535
1790
|
...(themePackage.metadata.widgetAreas ? { widgetAreas: themePackage.metadata.widgetAreas } : {}),
|
|
1536
|
-
|
|
1791
|
+
...(themePackage.metadata.siteMeta ? { siteMeta: themePackage.metadata.siteMeta } : {}),
|
|
1792
|
+
...(themePackage.metadata.collectionSlots ? { collectionSlots: themePackage.metadata.collectionSlots } : {}),
|
|
1537
1793
|
}));
|
|
1538
1794
|
|
|
1539
1795
|
for (const [templateName, templateContent] of themePackage.templates.entries()) {
|
|
@@ -1569,7 +1825,6 @@ function normalizeThemePackageMetadata(sourceMetadata, manifest) {
|
|
|
1569
1825
|
return {
|
|
1570
1826
|
...manifest,
|
|
1571
1827
|
...(sourceMetadata?.thumbnail ? { thumbnail: sourceMetadata.thumbnail } : {}),
|
|
1572
|
-
settings: sourceMetadata?.settings || {},
|
|
1573
1828
|
};
|
|
1574
1829
|
}
|
|
1575
1830
|
|
|
@@ -1743,7 +1998,7 @@ function normalizeMediaField(value, mediaBaseUrl) {
|
|
|
1743
1998
|
|
|
1744
1999
|
const normalizedBaseUrl = normalizeOptionalString(mediaBaseUrl);
|
|
1745
2000
|
if (!normalizedBaseUrl) {
|
|
1746
|
-
return
|
|
2001
|
+
return normalizedValue;
|
|
1747
2002
|
}
|
|
1748
2003
|
|
|
1749
2004
|
try {
|
|
@@ -1979,7 +2234,10 @@ function assertPlannedOutputPathsSafe(state) {
|
|
|
1979
2234
|
];
|
|
1980
2235
|
|
|
1981
2236
|
if (state.options.generateSpecialFiles) {
|
|
1982
|
-
plannedPaths.push('404.html'
|
|
2237
|
+
plannedPaths.push('404.html');
|
|
2238
|
+
if (shouldGenerateRobotsTxt(state.options)) {
|
|
2239
|
+
plannedPaths.push('robots.txt');
|
|
2240
|
+
}
|
|
1983
2241
|
if (hasCanonicalSiteUrl(state.previewData.site.url)) {
|
|
1984
2242
|
plannedPaths.push('sitemap.xml', 'feed.xml');
|
|
1985
2243
|
}
|
|
@@ -2079,11 +2337,43 @@ function sha256(content) {
|
|
|
2079
2337
|
}
|
|
2080
2338
|
|
|
2081
2339
|
function injectSiteCustomizations(html, state) {
|
|
2082
|
-
let next =
|
|
2340
|
+
let next = injectFaviconLinks(html, state.favicon);
|
|
2341
|
+
next = injectCustomCssAssetLink(next, state.customCssHref);
|
|
2083
2342
|
next = injectCustomHtml(next, state.customHtml);
|
|
2084
2343
|
return next;
|
|
2085
2344
|
}
|
|
2086
2345
|
|
|
2346
|
+
function injectFaviconLinks(html, favicon) {
|
|
2347
|
+
const links = buildFaviconLinks(favicon);
|
|
2348
|
+
if (!links) {
|
|
2349
|
+
return html;
|
|
2350
|
+
}
|
|
2351
|
+
|
|
2352
|
+
return html.replace('</head>', `${links}\n</head>`);
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2355
|
+
function buildFaviconLinks(favicon) {
|
|
2356
|
+
if (!favicon || typeof favicon !== 'object') {
|
|
2357
|
+
return '';
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2360
|
+
const lines = [];
|
|
2361
|
+
if (normalizeOptionalString(favicon.icon)) {
|
|
2362
|
+
lines.push(` <link rel="icon" href="${escapeHtml(favicon.icon)}" sizes="any">`);
|
|
2363
|
+
}
|
|
2364
|
+
if (normalizeOptionalString(favicon.svg)) {
|
|
2365
|
+
lines.push(` <link rel="icon" href="${escapeHtml(favicon.svg)}" type="image/svg+xml">`);
|
|
2366
|
+
}
|
|
2367
|
+
if (normalizeOptionalString(favicon.png)) {
|
|
2368
|
+
lines.push(` <link rel="icon" href="${escapeHtml(favicon.png)}" type="image/png">`);
|
|
2369
|
+
}
|
|
2370
|
+
if (normalizeOptionalString(favicon.apple_touch_icon)) {
|
|
2371
|
+
lines.push(` <link rel="apple-touch-icon" href="${escapeHtml(favicon.apple_touch_icon)}">`);
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2374
|
+
return lines.join('\n');
|
|
2375
|
+
}
|
|
2376
|
+
|
|
2087
2377
|
function injectCustomCssAssetLink(html, href) {
|
|
2088
2378
|
if (!normalizeOptionalString(href)) {
|
|
2089
2379
|
return html;
|
|
@@ -2165,13 +2455,23 @@ function buildFeedXml(site, emitted, generatedAt) {
|
|
|
2165
2455
|
}
|
|
2166
2456
|
|
|
2167
2457
|
function buildRobotsTxt(site) {
|
|
2168
|
-
const lines = ['User-agent: *'
|
|
2458
|
+
const lines = ['User-agent: *'];
|
|
2459
|
+
if (site.indexing === false) {
|
|
2460
|
+
lines.push('Disallow: /');
|
|
2461
|
+
return `${lines.join('\n')}\n`;
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2464
|
+
lines.push('Allow: /');
|
|
2169
2465
|
if (site.url) {
|
|
2170
2466
|
lines.push('', `Sitemap: ${resolveSiteUrl(site.url, '/sitemap.xml')}`);
|
|
2171
2467
|
}
|
|
2172
2468
|
return `${lines.join('\n')}\n`;
|
|
2173
2469
|
}
|
|
2174
2470
|
|
|
2471
|
+
function shouldGenerateRobotsTxt(options) {
|
|
2472
|
+
return options.generateSpecialFiles && options.generateRobotsTxt !== false;
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2175
2475
|
function getContentType(assetPath) {
|
|
2176
2476
|
const ext = assetPath.split('.').pop()?.toLowerCase();
|
|
2177
2477
|
const contentTypes = {
|
|
@@ -11,12 +11,7 @@ export class AssetProcessor {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
async processJavaScript(js) {
|
|
14
|
-
return js
|
|
15
|
-
.replace(/\/\/(?![^\r\n]*https?:)[^\r\n]*/g, '')
|
|
16
|
-
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
17
|
-
.replace(/\s+/g, ' ')
|
|
18
|
-
.replace(/\s*([{}();,=+\-*/<>!&|])\s*/g, '$1')
|
|
19
|
-
.trim();
|
|
14
|
+
return js;
|
|
20
15
|
}
|
|
21
16
|
|
|
22
17
|
generateAssetHash(content) {
|
package/src/index.d.ts
CHANGED
|
@@ -4,7 +4,6 @@ import type { ThemeManifest } from '@zeropress/theme-validator';
|
|
|
4
4
|
export interface ThemePackage {
|
|
5
5
|
metadata: ThemeManifest & {
|
|
6
6
|
thumbnail?: string;
|
|
7
|
-
settings?: Record<string, unknown>;
|
|
8
7
|
};
|
|
9
8
|
templates: Map<string, string>;
|
|
10
9
|
partials: Map<string, string>;
|
|
@@ -23,7 +22,14 @@ export interface BuildWriter {
|
|
|
23
22
|
|
|
24
23
|
export interface BuildOptions {
|
|
25
24
|
assetHashing?: boolean;
|
|
25
|
+
favicon?: {
|
|
26
|
+
icon?: string;
|
|
27
|
+
svg?: string;
|
|
28
|
+
png?: string;
|
|
29
|
+
apple_touch_icon?: string;
|
|
30
|
+
};
|
|
26
31
|
generateSpecialFiles?: boolean;
|
|
32
|
+
generateRobotsTxt?: boolean;
|
|
27
33
|
writeManifest?: boolean;
|
|
28
34
|
}
|
|
29
35
|
|