@zeropress/build-core 0.1.7 → 0.1.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 CHANGED
@@ -9,7 +9,7 @@ Shared build and partial-render core for ZeroPress.
9
9
  This package is the deterministic rendering engine used by:
10
10
 
11
11
  - `zeropress-admin-api-v2`
12
- - future `@zeropress/build` CLI flows
12
+ - [`@zeropress/build`](https://www.npmjs.com/package/@zeropress/build) package
13
13
 
14
14
  It accepts canonical preview-data plus a validated theme package and produces static HTML artifacts through a writer interface.
15
15
 
@@ -59,7 +59,12 @@ import {
59
59
  - `meta.json`
60
60
  - writing outputs through a pluggable writer
61
61
 
62
- It does not talk to databases, queues, KV, Durable Objects, R2, or deployment infrastructure directly.
62
+ It does not:
63
+
64
+ - fetch content from databases or APIs
65
+ - package or validate theme directories on its own unless the caller uses `buildSiteFromThemeDir`
66
+ - watch files, run a dev server, or perform deployment
67
+ - talk to queues, KV, Durable Objects, R2, GitHub, or other infrastructure directly
63
68
 
64
69
  ---
65
70
 
@@ -97,7 +102,7 @@ Returns:
97
102
  }
98
103
  ],
99
104
  manifest: {
100
- generatedAt: '2026-04-02T00:00:00.000Z',
105
+ generatedAt: '2026-04-02T00:00:00Z',
101
106
  files: [
102
107
  {
103
108
  path: 'index.html',
@@ -110,11 +115,29 @@ Returns:
110
115
  }
111
116
  ```
112
117
 
118
+ Notes:
119
+
120
+ - `writer` is required
121
+ - `previewData` must already satisfy the canonical preview-data contract
122
+ - `themePackage` must already be a validated in-memory theme package
123
+ - `sitemap.xml` and `feed.xml` are emitted only when `site.url` is a non-empty canonical URL
124
+ - `robots.txt` and `meta.json` are still emitted when `generateSpecialFiles` is enabled
125
+
113
126
  ### `buildSelectedRoutes(input)`
114
127
 
115
128
  Renders only selected post and list routes while preserving full-build parity for those outputs.
116
129
 
117
- This is intended for selective publish and other partial-render workflows.
130
+ This is intended for targeted rebuilds, output parity checks, and other partial-render workflows.
131
+
132
+ Current selection scope is limited to:
133
+
134
+ - post detail routes
135
+ - index pagination routes
136
+ - archive routes
137
+ - category routes
138
+ - tag routes
139
+
140
+ It does not currently support selecting individual page routes.
118
141
 
119
142
  Optional route templates are respected during both full and partial renders:
120
143
 
@@ -145,7 +168,7 @@ await buildSelectedRoutes({
145
168
 
146
169
  Loads a theme directory from disk and renders it using the same core pipeline.
147
170
 
148
- This is useful for local tooling and CLI flows.
171
+ This is useful for local tooling that wants filesystem theme loading but still uses the same deterministic core renderer.
149
172
 
150
173
  ---
151
174
 
@@ -193,6 +216,8 @@ Theme validation is enforced through:
193
216
 
194
217
  - [`@zeropress/theme-validator`](https://www.npmjs.com/package/@zeropress/theme-validator)
195
218
 
219
+ `buildSiteFromThemeDir()` is the convenience entry point that loads a theme directory and converts it into the required in-memory `themePackage`.
220
+
196
221
  As of `preview-data v0.4`, the payload no longer carries `routes` arrays or raw HTML fragments such as `categories_html`.
197
222
 
198
223
  Build-core now derives:
@@ -238,6 +263,13 @@ Supported options:
238
263
 
239
264
  These options apply to both full builds and partial renders where relevant.
240
265
 
266
+ Defaults:
267
+
268
+ - `assetHashing: true`
269
+ - `generateSpecialFiles: true`
270
+ - `injectHtmx: true`
271
+ - `writeManifest: false`
272
+
241
273
  ---
242
274
 
243
275
  ## Requirements
@@ -251,7 +283,7 @@ These options apply to both full builds and partial renders where relevant.
251
283
 
252
284
  - [@zeropress/preview-data-validator](https://www.npmjs.com/package/@zeropress/preview-data-validator)
253
285
  - [@zeropress/theme-validator](https://www.npmjs.com/package/@zeropress/theme-validator)
254
- - [zeropress-theme](https://www.npmjs.com/package/zeropress-theme)
286
+ - [@zeropress/theme](https://www.npmjs.com/package/@zeropress/theme)
255
287
 
256
288
  ---
257
289
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeropress/build-core",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Shared ZeroPress build and partial-render core",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -170,7 +170,7 @@ async function finalizeBuildResult(writer, summaries, options) {
170
170
  let manifest;
171
171
  if (options.writeManifest) {
172
172
  manifest = {
173
- generatedAt: new Date().toISOString(),
173
+ generatedAt: formatUtcIsoSeconds(new Date()),
174
174
  files: summaries.map((file) => ({ ...file })),
175
175
  };
176
176
  await writeOutput(
@@ -327,6 +327,8 @@ function normalizePreviewData(previewData) {
327
327
  posts: previewData.content.posts
328
328
  .map((post) => ({
329
329
  ...post,
330
+ published_at_iso: normalizeIsoTimestamp(post.published_at_iso),
331
+ updated_at_iso: normalizeIsoTimestamp(post.updated_at_iso),
330
332
  author_avatar: normalizeMediaField(post.author_avatar, normalizedSite.mediaBaseUrl),
331
333
  featured_image: normalizeMediaField(post.featured_image, normalizedSite.mediaBaseUrl),
332
334
  }))
@@ -1047,7 +1049,6 @@ function buildSitemapXml(site, emitted, generatedAt) {
1047
1049
  .filter((route) => route.url === '/')
1048
1050
  .map((route) => ({
1049
1051
  url: route.url,
1050
- lastmod: generatedAt,
1051
1052
  changefreq: 'daily',
1052
1053
  priority: 1.0,
1053
1054
  })),
@@ -1059,28 +1060,17 @@ function buildSitemapXml(site, emitted, generatedAt) {
1059
1060
  })),
1060
1061
  ...emitted.pages.map((page) => ({
1061
1062
  url: page.url,
1062
- lastmod: generatedAt,
1063
1063
  changefreq: 'monthly',
1064
1064
  priority: 0.7,
1065
1065
  })),
1066
- ...emitted.categoryRoutes.filter((route) => route.page === 1).map((route) => ({
1067
- url: route.url,
1068
- lastmod: generatedAt,
1069
- changefreq: 'weekly',
1070
- priority: 0.6,
1071
- })),
1072
- ...emitted.tagRoutes.filter((route) => route.page === 1).map((route) => ({
1073
- url: route.url,
1074
- lastmod: generatedAt,
1075
- changefreq: 'weekly',
1076
- priority: 0.5,
1077
- })),
1078
1066
  ];
1079
1067
 
1080
1068
  const body = entries.map((entry) => {
1081
1069
  const loc = escapeXml(resolveSiteUrl(site.url, entry.url));
1082
- const lastmod = entry.lastmod.toISOString().split('T')[0];
1083
- return ` <url>\n <loc>${loc}</loc>\n <lastmod>${lastmod}</lastmod>\n <changefreq>${entry.changefreq}</changefreq>\n <priority>${entry.priority.toFixed(1)}</priority>\n </url>`;
1070
+ const lastmod = entry.lastmod
1071
+ ? `\n <lastmod>${formatUtcIsoSeconds(entry.lastmod)}</lastmod>`
1072
+ : '';
1073
+ return ` <url>\n <loc>${loc}</loc>${lastmod}\n <changefreq>${entry.changefreq}</changefreq>\n <priority>${entry.priority.toFixed(1)}</priority>\n </url>`;
1084
1074
  }).join('\n');
1085
1075
 
1086
1076
  return `<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n${body}\n</urlset>`;
@@ -1130,7 +1120,7 @@ function buildMetaJson(site, emitted, generatedAt) {
1130
1120
  ];
1131
1121
 
1132
1122
  return JSON.stringify({
1133
- generated: generatedAt.toISOString(),
1123
+ generated: formatUtcIsoSeconds(generatedAt),
1134
1124
  site: {
1135
1125
  title: site.title,
1136
1126
  description: site.description,
@@ -1211,6 +1201,14 @@ function toDate(value) {
1211
1201
  return value ? new Date(value) : new Date();
1212
1202
  }
1213
1203
 
1204
+ function formatUtcIsoSeconds(value) {
1205
+ return toDate(value).toISOString().replace(/\.\d{3}Z$/, 'Z');
1206
+ }
1207
+
1208
+ function normalizeIsoTimestamp(value) {
1209
+ return value ? formatUtcIsoSeconds(value) : '';
1210
+ }
1211
+
1214
1212
  function escapeXml(str) {
1215
1213
  return String(str || '')
1216
1214
  .replace(/&/g, '&amp;')