@zeropress/build-core 0.6.13 → 0.7.1
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 +145 -43
- package/package.json +8 -7
- package/src/api/build-site.js +969 -405
- package/src/assets/asset-processor.js +0 -13
- package/src/index.d.ts +43 -11
- package/src/render/content-renderer.js +98 -168
- package/src/render/control-flow-renderer.js +5 -2
- package/src/render/variable-resolver.js +6 -1
- package/src/render/zeropress-engine.js +3 -1
- package/src/theme/format-theme-validation.js +54 -0
- package/src/theme/load-theme-dir.js +134 -68
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|

|
|
5
5
|

|
|
6
6
|
|
|
7
|
-
Shared deterministic rendering core for
|
|
7
|
+
Shared deterministic rendering core for Preview Data v0.7 and Theme Runtime v0.7.
|
|
8
8
|
|
|
9
9
|
This package is the canonical rendering core for preview-data and theme packages consumed directly by:
|
|
10
10
|
|
|
@@ -13,10 +13,10 @@ This package is the canonical rendering core for preview-data and theme packages
|
|
|
13
13
|
|
|
14
14
|
Public contract references:
|
|
15
15
|
|
|
16
|
-
- [Preview Data v0.
|
|
17
|
-
- [Preview Data v0.
|
|
18
|
-
- [Theme Runtime v0.
|
|
19
|
-
- [Theme Runtime v0.
|
|
16
|
+
- [Preview Data v0.7 Spec](https://zeropress.dev/reference/preview-data/specs/v0.7/)
|
|
17
|
+
- [Preview Data v0.7 Schema](https://schemas.zeropress.dev/preview-data/v0.7/schema.json)
|
|
18
|
+
- [Theme Runtime v0.7 Spec](https://zeropress.dev/reference/theme-runtime/specs/v0.7/)
|
|
19
|
+
- [Theme Runtime v0.7 Schema](https://schemas.zeropress.dev/theme-runtime/v0.7/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
|
|
|
@@ -83,9 +83,6 @@ const result = await buildSite({
|
|
|
83
83
|
previewData,
|
|
84
84
|
themePackage,
|
|
85
85
|
writer,
|
|
86
|
-
options: {
|
|
87
|
-
writeManifest: true,
|
|
88
|
-
},
|
|
89
86
|
});
|
|
90
87
|
```
|
|
91
88
|
|
|
@@ -100,18 +97,7 @@ Returns:
|
|
|
100
97
|
size: 1234,
|
|
101
98
|
sha256: '...'
|
|
102
99
|
}
|
|
103
|
-
]
|
|
104
|
-
manifest: {
|
|
105
|
-
generatedAt: '2026-04-02T00:00:00Z',
|
|
106
|
-
files: [
|
|
107
|
-
{
|
|
108
|
-
path: 'index.html',
|
|
109
|
-
contentType: 'text/html',
|
|
110
|
-
size: 1234,
|
|
111
|
-
sha256: '...'
|
|
112
|
-
}
|
|
113
|
-
]
|
|
114
|
-
}
|
|
100
|
+
]
|
|
115
101
|
}
|
|
116
102
|
```
|
|
117
103
|
|
|
@@ -121,11 +107,13 @@ Notes:
|
|
|
121
107
|
- `previewData` must already satisfy the canonical preview-data contract
|
|
122
108
|
- `themePackage` must already be a validated in-memory theme package
|
|
123
109
|
- `sitemap.xml` is emitted only when `site.url` is a non-empty canonical URL
|
|
124
|
-
- `feed.xml` is emitted only when `site.url` is a non-empty canonical URL and `generateFeed` is not `false`
|
|
110
|
+
- `feed.xml` is emitted only when `site.feed.enabled` is not `false`, `site.url` is a non-empty canonical URL, and `generateFeed` is not `false`; its `lastBuildDate` is derived from required `previewData.generated_at`, never the build machine clock
|
|
125
111
|
- callers may pass `sitemapStylesheetHref` to add an XML stylesheet processing instruction to generated `sitemap.xml`
|
|
126
|
-
- fallback `robots.txt` is emitted when `
|
|
127
|
-
- fallback `robots.txt` uses `site.
|
|
112
|
+
- fallback `robots.txt` is emitted when `generateRobotsTxt` is not `false`
|
|
113
|
+
- fallback `robots.txt` uses the effective `site.robots.allow_indexing` policy; omitted Preview Data defaults to `true`, `false` emits `Disallow: /`, and `true` emits `Allow: /`
|
|
128
114
|
- 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
|
|
115
|
+
- dotted slug segments such as `v0.6` remain literal in route URLs, canonical URLs, and output filenames
|
|
116
|
+
- output planning rejects duplicate public URLs, including clean-host aliases (`page.html` → `/page` and `page/index.html` → `/page/`), content routes shadowed by generated special files, and file/directory path hierarchy conflicts before writing
|
|
129
117
|
|
|
130
118
|
### `buildSiteFromThemeDir(input)`
|
|
131
119
|
|
|
@@ -177,9 +165,13 @@ Theme validation is enforced through:
|
|
|
177
165
|
|
|
178
166
|
`buildSiteFromThemeDir()` is the convenience entry point that loads a theme directory and converts it into the required in-memory `themePackage`.
|
|
179
167
|
|
|
180
|
-
|
|
168
|
+
Theme packages use fixed safety limits shared with `@zeropress/theme-validator`: at most 128 entries, 1 MiB per file, and 4 MiB in total expanded content. `buildSiteFromThemeDir()` normalizes equivalent spellings such as `theme`, `theme/`, and `theme/.` before checking the final entry, requires that final entry to be real rather than a symbolic link, pins an accepted root to its canonical path, checks filesystem sizes before reading file bodies, and rejects every symbolic link inside the root, including links that stay inside the theme directory and dangling links. Symbolic links in ancestor path components are allowed. Package paths reject literal backslashes and exact empty, `.` or `..` segments while ordinary filenames such as `name..txt` remain valid. Directory entries that collide after NFC and case normalization are also rejected so directory and ZIP adapters use the same cross-platform identity. `buildSite()` validates the same limits for an already-loaded in-memory package. Packaged ZIP input is handled by `@zeropress/theme`, which additionally limits the archive itself to 2 MiB.
|
|
169
|
+
|
|
170
|
+
Theme CSS and JavaScript assets are emitted byte-for-byte as provided. `custom_css.content` is checked for nonblank content but otherwise retains its original whitespace, comments, strings, and syntax. Build Core may hash output filenames; every hash is computed from the bytes actually emitted, and Build Core does not rewrite or minify CSS or JavaScript. Both `.js` and `.mjs` outputs use a JavaScript MIME type.
|
|
181
171
|
|
|
182
|
-
|
|
172
|
+
User-defined scalar metadata under `site.meta`, `post.meta`, `page.meta`, and item `meta` is always HTML-escaped during template interpolation. Names ending in `_html` or `_url` do not opt metadata into raw rendering; use the explicit content and custom HTML contracts for trusted raw markup.
|
|
173
|
+
|
|
174
|
+
Post/Page HTML, Markdown raw HTML, and text-widget HTML pass through a parser-based explicit allowlist. Safe structural, media, table, task-list, and alert markup is retained; event handlers and unsafe URL schemes are removed. Links permit relative URLs, HTTP(S), `mailto:`, and `tel:`. Media and iframe URLs permit relative URLs or HTTP(S), and protocol-relative URLs are rejected. Each `srcset` candidate is checked independently. This sanitizer boundary does not include trusted `custom_html` slots or `standalone_html`, which remain intentionally unchanged.
|
|
183
175
|
|
|
184
176
|
Build-core derives:
|
|
185
177
|
|
|
@@ -187,31 +179,142 @@ Build-core derives:
|
|
|
187
179
|
- post list HTML blocks
|
|
188
180
|
- pagination HTML
|
|
189
181
|
- taxonomy link HTML
|
|
190
|
-
-
|
|
182
|
+
- localized fallback `published_at` / `updated_at` values plus unconditional `published_at_iso` / `updated_at_iso` values
|
|
191
183
|
- datetime formatting from `site.locale`, `site.timezone`, `site.date_style`, and `site.time_style`
|
|
192
184
|
- `reading_time`
|
|
193
|
-
- `
|
|
185
|
+
- a route-root `comments` discriminated context
|
|
186
|
+
|
|
187
|
+
Every ordinary rendered route receives effective feature state on `site`, regardless of whether the corresponding Preview Data preference was omitted:
|
|
188
|
+
|
|
189
|
+
```js
|
|
190
|
+
{
|
|
191
|
+
search: { enabled: true },
|
|
192
|
+
feed: { enabled: true, url: '/feed.xml' },
|
|
193
|
+
archive: { enabled: true, url: '/archive/' },
|
|
194
|
+
comments: {
|
|
195
|
+
enabled: true,
|
|
196
|
+
provider: 'zeropress',
|
|
197
|
+
api_base_url: 'https://comments.example.com',
|
|
198
|
+
per_page: 50,
|
|
199
|
+
order: 'desc',
|
|
200
|
+
threading: { enabled: true, max_depth: 2 }
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Disabled states are exactly `{ enabled: false }`. Search combines the Preview Data request with `theme.json.features.search`. Feed additionally requires a canonical `site.url` and is hard-disabled by `generateFeed: false`. Archive additionally requires `archive.html`; its URL is `/archive/` for directory output and `/archive` for html-extension output. Comments additionally require `theme.json.features.comments`. `site.post_index.enabled` and `site.post_index.paginate` are also effective values after applying `theme.json.features.post_index` and route generation.
|
|
206
|
+
|
|
207
|
+
Build Core always emits localized fallback datetime strings together with canonical ISO timestamps. Themes may progressively enhance explicitly marked `<time datetime="...">` elements for the visitor's browser locale and timezone, but must preserve the fallback when JavaScript, `Intl`, or ISO parsing is unavailable. Themes that want canonical site-local display should use the fallback without client enhancement.
|
|
208
|
+
|
|
209
|
+
When `site.comments` is present, Build Core materializes the following defaults before rendering:
|
|
210
|
+
|
|
211
|
+
- `provider: "zeropress"`
|
|
212
|
+
- `per_page: 50`
|
|
213
|
+
- `order: "desc"`
|
|
214
|
+
- `threading.enabled: true`
|
|
215
|
+
- `threading.max_depth: 2`
|
|
216
|
+
|
|
217
|
+
Trailing slashes are removed from `site.comments.api_base_url`, except when the value is the same-origin root `/`. The normalized configuration remains available as `site.comments`.
|
|
218
|
+
|
|
219
|
+
Every rendered route receives a top-level `comments` object. Inactive and non-detail routes receive exactly:
|
|
220
|
+
|
|
221
|
+
```js
|
|
222
|
+
{
|
|
223
|
+
comments: {
|
|
224
|
+
enabled: false
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
An active post or page detail route receives:
|
|
230
|
+
|
|
231
|
+
```js
|
|
232
|
+
{
|
|
233
|
+
comments: {
|
|
234
|
+
enabled: true,
|
|
235
|
+
target_type: 'post', // or 'page'
|
|
236
|
+
target_public_id: 101,
|
|
237
|
+
provider: 'zeropress', // or 'wordpress'
|
|
238
|
+
api_base_url: 'https://comments.example.com',
|
|
239
|
+
per_page: 50,
|
|
240
|
+
order: 'desc',
|
|
241
|
+
threading: {
|
|
242
|
+
enabled: true,
|
|
243
|
+
max_depth: 2
|
|
244
|
+
},
|
|
245
|
+
request_token: '...'
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
The active state requires all of the following:
|
|
194
251
|
|
|
195
|
-
|
|
252
|
+
- the theme declares `features.comments: true`
|
|
253
|
+
- `site.comments` is configured with `enabled: true`
|
|
254
|
+
- the post or page has `allow_comments: true` and a positive `public_id`
|
|
255
|
+
- the ZeroPress provider has a non-empty item `comments.request_token`
|
|
196
256
|
|
|
197
|
-
|
|
198
|
-
|
|
257
|
+
Post and Page `allow_comments` are optional Preview Data fields. Omission is
|
|
258
|
+
normalized to `false`; producers should emit `true` only for items that opt in
|
|
259
|
+
to comments.
|
|
199
260
|
|
|
200
|
-
The
|
|
261
|
+
The theme-facing `request_token` key exists only for the `zeropress` provider. Preview-data items may retain ignored token metadata for `wordpress` or inactive comment states, but Build Core drops it. An active `wordpress` context omits that key entirely. Post/page objects, structured list items, collection items and cursors, adjacent-item summaries, search data, feeds, and non-detail route roots do not receive a copy of the item token. Themes must use the route-root `comments.enabled` discriminator.
|
|
201
262
|
|
|
202
|
-
- `site
|
|
263
|
+
The canonical `preview-data v0.7` site contract uses:
|
|
264
|
+
|
|
265
|
+
- `site.media_origin`
|
|
203
266
|
- `site.media_delivery_mode`
|
|
204
267
|
- `site.locale`
|
|
205
268
|
- `site.timezone`
|
|
206
|
-
- `site.datetime_display`
|
|
207
269
|
- `site.date_style`
|
|
208
270
|
- `site.time_style`
|
|
209
|
-
- `site.
|
|
210
|
-
- `site.
|
|
271
|
+
- `site.feed`
|
|
272
|
+
- `site.archive`
|
|
273
|
+
- `site.comments`
|
|
274
|
+
- `site.robots`
|
|
211
275
|
- `site.expose_generator`
|
|
212
276
|
- `site.search`
|
|
213
277
|
|
|
214
|
-
|
|
278
|
+
Post objects use `public_id` as their public numeric identity. The removed internal `content.posts[].id` field is neither required nor exposed to the theme runtime.
|
|
279
|
+
|
|
280
|
+
`site.url` is either an empty string or a credential-free HTTP(S) origin. A trailing root slash is accepted and normalized to `URL.origin`; credentials, paths, queries, and fragments are invalid. Menu and newsletter navigation URLs must be safe single-slash root-relative paths or credential-free HTTP(S) URLs. Query and fragment are allowed, while bare/dot-relative paths, protocol-relative URLs, path dot segments, backslashes, whitespace/control characters, and malformed percent escapes are invalid.
|
|
281
|
+
|
|
282
|
+
`site.media_origin` is either an empty string or an absolute HTTP(S) origin. Media fields accept only safe single-slash root-relative paths with a real path or credential-free HTTP(S) URLs with a path; `/`, bare paths, and dot-relative paths are invalid. Build Core resolves root-relative site favicon/logo, profile-widget avatar, author avatar, Post/Page featured-image, and `content.media[].src` values against that origin, while preserving already-absolute external URLs. SEO image metadata receives the normalized featured image. Responsive `srcset` variants require `media_delivery_mode: "media_domain"`, a non-empty origin, and an exact origin match; paths, credentials, query strings, and fragments are not part of the media-origin contract.
|
|
283
|
+
|
|
284
|
+
Locale and time zone are canonicalized once at build start. Locale uses canonical BCP 47. Time zone accepts `UTC`, canonical IANA identifiers, or canonical fixed offsets within `±14:00`; zero offset becomes `UTC`. The same values drive fallback date strings, archives, date-based permalinks, feeds, and rendered metadata.
|
|
285
|
+
|
|
286
|
+
When routing fields are omitted, Build Core uses directory output, `/posts/:slug/`, `/:slug/`, `/categories/:slug/`, and `/tags/:slug/`; `front_page` defaults to `theme_index`; and `post_index` defaults to `{ enabled: true, path: "/", paginate: true }`. Page objects retain a leaf `slug`, but external identity uses the NFC-normalized effective route path. Page front-page references use `page_path`, Page collection references use `path`, and different Page paths may share the same leaf slug.
|
|
287
|
+
|
|
288
|
+
`site.favicon.icon`, `svg`, and `png` form the default favicon set. `icon_dark` is the optional dark color-scheme icon. When both sets exist, Build Core emits the dark icon first with `media="(prefers-color-scheme: dark)"` and marks every default icon link with the light media query. A single available set is emitted without a media condition, and `apple_touch_icon` is always unconditional. An explicit preview-data `site.favicon` object replaces the complete auto-discovered build option instead of merging field by field.
|
|
289
|
+
|
|
290
|
+
### Custom HTML
|
|
291
|
+
|
|
292
|
+
Preview Data v0.7 carries optional trusted site-level HTML as flat raw strings:
|
|
293
|
+
|
|
294
|
+
```json
|
|
295
|
+
{
|
|
296
|
+
"custom_html": {
|
|
297
|
+
"head_end": "<meta name=\"example\" content=\"value\">",
|
|
298
|
+
"body_end": "<script src=\"/assets/example.js\"></script>"
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Each configured slot must be nonblank and is limited to 65,536 Unicode code points. Build Core preserves the string without trimming, escaping, sanitizing, or interpreting it.
|
|
304
|
+
|
|
305
|
+
For ordinary theme-rendered HTML routes, site customization order is deterministic:
|
|
306
|
+
|
|
307
|
+
1. favicon links
|
|
308
|
+
2. ZeroPress generator metadata, unless disabled
|
|
309
|
+
3. the generated custom CSS link
|
|
310
|
+
4. `custom_html.head_end`
|
|
311
|
+
5. `custom_html.body_end`
|
|
312
|
+
|
|
313
|
+
Closing `</head>` and `</body>` tags are matched case-insensitively. If a configured custom HTML slot has no matching closing tag, the build fails with the route, output path, missing tag, and remediation guidance instead of silently dropping the configured content. Themes that consume these slots must render the corresponding closing tag. A `site.front_page.type: "standalone_html"` document remains byte-for-byte independent and does not receive favicon, generator, custom CSS, or custom HTML injection.
|
|
314
|
+
|
|
315
|
+
Native search artifacts are emitted only when preview-data does not set `site.search.enabled: false` and the active theme declares `features.search: true`. When that effective search state is disabled, search widgets are omitted from resolved widget items while their widget areas and non-search siblings remain available. The native adapter tokenizes non-CJK text with `Intl.Segmenter(site.locale)` and falls back to Unicode word matching only when Segmenter is unavailable or fails. CJK runs contribute their full token and unique bigrams while repeated occurrences still contribute term frequency. `search_pagefind.js` is a Pagefind adapter that can replace `search.js` after a post-build Pagefind step.
|
|
316
|
+
|
|
317
|
+
When archive is disabled or `archive.html` is missing, archive routes and archive widget items are omitted while authored menu items, widget areas, and non-archive siblings remain unchanged. No archive output path is claimed in that state, so content may use `/archive`. Feed behaves the same way for `feed.xml`: a disabled feed neither emits nor claims the file. Enabled feeds add one RSS autodiscovery link to `meta.head_tags` on ordinary theme-rendered routes; `404.html` and standalone front-page HTML do not receive it.
|
|
215
318
|
|
|
216
319
|
Optional route templates behave as rendering capabilities, not guaranteed outputs:
|
|
217
320
|
|
|
@@ -220,7 +323,9 @@ Optional route templates behave as rendering capabilities, not guaranteed output
|
|
|
220
323
|
- `tag.html`
|
|
221
324
|
- `404.html`
|
|
222
325
|
|
|
223
|
-
If preview-data includes content that could produce archive/category/tag pages but the theme omits the matching optional template, build-core skips those outputs. Special files are derived from emitted outputs rather than raw preview-data alone.
|
|
326
|
+
If preview-data includes content that could produce archive/category/tag pages but the theme omits the matching optional template, build-core skips those outputs. `404.html` is emitted only when the theme provides its matching template. Special files are derived from emitted outputs rather than raw preview-data alone.
|
|
327
|
+
|
|
328
|
+
Generated `404.html` uses the document title `Page Not Found - <site title>` and always includes `robots: noindex`. It omits description, canonical, Open Graph, and article metadata because a static error document is not a canonical or shareable content route. Build-core writes the artifact only; the development server or deployment host remains responsible for serving it with HTTP status `404` for missing URLs.
|
|
224
329
|
|
|
225
330
|
## Build Options
|
|
226
331
|
|
|
@@ -229,20 +334,17 @@ Supported options:
|
|
|
229
334
|
- `assetHashing`
|
|
230
335
|
- `favicon`
|
|
231
336
|
- `sitemapStylesheetHref`
|
|
232
|
-
- `generateSpecialFiles`
|
|
233
337
|
- `generateFeed`
|
|
234
338
|
- `generateRobotsTxt`
|
|
235
|
-
- `
|
|
236
|
-
|
|
237
|
-
These options apply to both full builds and partial renders where relevant.
|
|
339
|
+
- `reservedOutputPaths`
|
|
238
340
|
|
|
239
341
|
Defaults:
|
|
240
342
|
|
|
241
343
|
- `assetHashing: true`
|
|
242
|
-
- `generateSpecialFiles: true`
|
|
243
344
|
- `generateFeed: true`
|
|
244
345
|
- `generateRobotsTxt: true`
|
|
245
|
-
|
|
346
|
+
|
|
347
|
+
`reservedOutputPaths` is an internal orchestration boundary for callers that already own files in the final output tree, such as a copied public directory. Reserved files are safety-validated and participate in clean-URL alias, exact-path, and file/directory hierarchy collision checks, but Build Core does not write them or include them in the returned file summaries.
|
|
246
348
|
|
|
247
349
|
## License
|
|
248
350
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeropress/build-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Shared ZeroPress build core",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,14 +30,15 @@
|
|
|
30
30
|
"test": "node --test"
|
|
31
31
|
},
|
|
32
32
|
"engines": {
|
|
33
|
-
"node": ">=
|
|
33
|
+
"node": ">=22.12.0"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@zeropress/slug-policy": "0.1
|
|
37
|
-
"@zeropress/preview-data-validator": "0.
|
|
38
|
-
"@zeropress/theme-validator": "0.
|
|
36
|
+
"@zeropress/slug-policy": "0.2.1",
|
|
37
|
+
"@zeropress/preview-data-validator": "0.7.1",
|
|
38
|
+
"@zeropress/theme-validator": "0.7.1",
|
|
39
39
|
"highlight.js": "11.11.1",
|
|
40
|
-
"markdown-it": "14.
|
|
41
|
-
"markdown-it-anchor": "9.2.
|
|
40
|
+
"markdown-it": "14.3.0",
|
|
41
|
+
"markdown-it-anchor": "9.2.1",
|
|
42
|
+
"sanitize-html": "2.17.6"
|
|
42
43
|
}
|
|
43
44
|
}
|