@zeropress/build-pages 0.6.1 → 0.6.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 +42 -15
- package/action.yml +3 -0
- package/dist/action.js +225 -7
- package/dist/prebuild.js +23 -2
- package/package.json +2 -2
- package/schemas/zeropress-build-pages.config.v0.1.schema.json +7 -0
- package/src/action.js +1 -0
- package/src/index.js +154 -3
- package/src/prebuild.js +30 -2
- package/themes/docs/assets/style.css +212 -0
- package/themes/docs/assets/theme.js +121 -0
- package/themes/docs/layout.html +19 -3
- package/themes/docs/page.html +2 -2
- package/themes/docs/partials/theme-scripts.html +1 -0
- package/themes/docs/post.html +1 -1
- package/themes/docs/theme.json +2 -3
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
Build ZeroPress static output for modern hosting platforms.
|
|
7
7
|
|
|
8
|
-
`@zeropress/build-pages` turns
|
|
8
|
+
`@zeropress/build-pages` turns Markdown files and public assets into a static ZeroPress site. It discovers Markdown pages, prepares the site data, stages public files, and runs [`@zeropress/build`](https://github.com/zeropress-app/zeropress-build).
|
|
9
9
|
|
|
10
10
|
The generated output is plain static files that can be deployed to GitHub Pages, Cloudflare Pages, Netlify, Vercel, or any static hosting provider.
|
|
11
11
|
|
|
@@ -13,7 +13,9 @@ The generated output is plain static files that can be deployed to GitHub Pages,
|
|
|
13
13
|
|
|
14
14
|
```txt
|
|
15
15
|
source directory
|
|
16
|
-
Markdown pages + .zeropress/config.json
|
|
16
|
+
Markdown pages + .zeropress/config.json
|
|
17
|
+
public directory
|
|
18
|
+
public files (defaults to source)
|
|
17
19
|
|
|
|
18
20
|
v
|
|
19
21
|
@zeropress/build-pages
|
|
@@ -32,7 +34,7 @@ static output directory
|
|
|
32
34
|
flowchart TD
|
|
33
35
|
source["Source directory"] --> markdown["Markdown pages (*.md)"]
|
|
34
36
|
source --> config[".zeropress/config.json"]
|
|
35
|
-
source --> publicFiles["Public files<br/>images, CSS, JS, PDF, JSON, Markdown"]
|
|
37
|
+
publicRoot["Public directory<br/>defaults to source"] --> publicFiles["Public files<br/>images, CSS, JS, PDF, JSON, Markdown"]
|
|
36
38
|
|
|
37
39
|
markdown --> buildPages["@zeropress/build-pages"]
|
|
38
40
|
config --> buildPages
|
|
@@ -83,6 +85,7 @@ jobs:
|
|
|
83
85
|
uses: zeropress-app/zeropress-build-pages@v0
|
|
84
86
|
with:
|
|
85
87
|
source: ./docs
|
|
88
|
+
public-dir: ./public
|
|
86
89
|
destination: ./_site
|
|
87
90
|
- name: Upload artifact
|
|
88
91
|
uses: actions/upload-pages-artifact@v5
|
|
@@ -114,6 +117,7 @@ That is equivalent to:
|
|
|
114
117
|
uses: zeropress-app/zeropress-build-pages@v0
|
|
115
118
|
with:
|
|
116
119
|
source: ./docs
|
|
120
|
+
public-dir: ./docs
|
|
117
121
|
destination: ./_site
|
|
118
122
|
theme: docs
|
|
119
123
|
skip-untitled-markdown: false
|
|
@@ -128,6 +132,7 @@ Custom input example:
|
|
|
128
132
|
uses: zeropress-app/zeropress-build-pages@v0
|
|
129
133
|
with:
|
|
130
134
|
source: ./documents
|
|
135
|
+
public-dir: ./public
|
|
131
136
|
destination: ./_site
|
|
132
137
|
theme-path: ./theme-docs
|
|
133
138
|
config: ./documents/.zeropress/config.json
|
|
@@ -137,7 +142,8 @@ Custom input example:
|
|
|
137
142
|
|
|
138
143
|
In the action inputs:
|
|
139
144
|
|
|
140
|
-
- `source` is the directory that contains your Markdown pages
|
|
145
|
+
- `source` is the directory that contains your Markdown pages and optional `.zeropress/config.json`. The default is `./docs`.
|
|
146
|
+
- `public-dir` is the directory copied as public passthrough files. The default is `source`.
|
|
141
147
|
- `destination` is the directory where the generated static site is written. The default is `./_site`.
|
|
142
148
|
- `theme` is the bundled theme name. The default is `docs`.
|
|
143
149
|
- `theme-path` is a custom local ZeroPress theme directory. It takes precedence over `theme`.
|
|
@@ -158,6 +164,7 @@ npx @zeropress/create-theme --name my-docs-theme --template docs
|
|
|
158
164
|
```yaml
|
|
159
165
|
with:
|
|
160
166
|
source: ./docs
|
|
167
|
+
public-dir: ./public
|
|
161
168
|
destination: ./_site
|
|
162
169
|
theme-path: ./my-docs-theme/theme
|
|
163
170
|
```
|
|
@@ -196,7 +203,8 @@ The CLI requires explicit input and output paths. The GitHub Action keeps safe d
|
|
|
196
203
|
|
|
197
204
|
| Option | Default | Purpose |
|
|
198
205
|
| --- | --- | --- |
|
|
199
|
-
| `--source <dir>` | required | Dedicated source directory containing Markdown and
|
|
206
|
+
| `--source <dir>` | required | Dedicated source directory containing Markdown and optional config |
|
|
207
|
+
| `--public-dir <dir>` | source | Public passthrough directory |
|
|
200
208
|
| `--destination <dir>` | required | Output directory |
|
|
201
209
|
| `--theme docs` | `docs` | Bundled docs theme |
|
|
202
210
|
| `--theme-path <dir>` | none | Custom ZeroPress theme directory |
|
|
@@ -208,7 +216,7 @@ The CLI requires explicit input and output paths. The GitHub Action keeps safe d
|
|
|
208
216
|
|
|
209
217
|
## Source Tree
|
|
210
218
|
|
|
211
|
-
The source directory is the folder that Build Pages reads.
|
|
219
|
+
The source directory is the folder that Build Pages reads for Markdown pages and optional `.zeropress/config.json`. By default, the source directory is also the public passthrough root. Use `public-dir` when you want to keep Markdown source and public assets separate.
|
|
212
220
|
|
|
213
221
|
Use a dedicated content directory such as `docs/` or `documents/`. Repository root source (`--source ./`) is not supported.
|
|
214
222
|
|
|
@@ -217,20 +225,25 @@ my-site/
|
|
|
217
225
|
docs/ # source
|
|
218
226
|
index.md
|
|
219
227
|
guide.md
|
|
220
|
-
assets/
|
|
221
|
-
logo.png
|
|
222
228
|
.zeropress/
|
|
223
229
|
config.json
|
|
230
|
+
public/ # public-dir, optional
|
|
231
|
+
assets/
|
|
232
|
+
logo.png
|
|
233
|
+
favicon.svg
|
|
234
|
+
robots.txt
|
|
224
235
|
_site/ # destination, generated
|
|
225
236
|
```
|
|
226
237
|
|
|
227
|
-
Build Pages stages the
|
|
238
|
+
Build Pages stages the public directory before calling [`@zeropress/build`](https://github.com/zeropress-app/zeropress-build). Generated ZeroPress output wins over staged public files.
|
|
239
|
+
|
|
240
|
+
Root-level public files named `favicon.ico`, `favicon.svg`, `favicon.png`, and `apple-touch-icon.png` are copied to the destination and auto-injected into generated HTML `<head>` output.
|
|
228
241
|
|
|
229
|
-
|
|
242
|
+
A root-level public `sitemap.xsl` is copied to the destination. When ZeroPress generates `sitemap.xml`, it auto-discovers that file and adds an XML stylesheet processing instruction for `/sitemap.xsl`.
|
|
230
243
|
|
|
231
|
-
|
|
244
|
+
The source directory must not overlap the destination directory, the selected theme directory, or the internal `.zeropress/` working directory. An explicit public directory must be an existing dedicated directory and must not be a file, symlink, repository root, destination directory, selected theme directory, or internal `.zeropress/` working directory.
|
|
232
245
|
|
|
233
|
-
|
|
246
|
+
If `public-dir` is inside `source`, Build Pages excludes that public subtree from Markdown page discovery.
|
|
234
247
|
|
|
235
248
|
Ignored while staging and Markdown discovery:
|
|
236
249
|
|
|
@@ -334,6 +347,7 @@ See the public config reference at [zeropress.dev/build-pages-config](https://ze
|
|
|
334
347
|
"description": "Project documentation",
|
|
335
348
|
"url": "https://example.github.io/project",
|
|
336
349
|
"expose_generator": true,
|
|
350
|
+
"search": true,
|
|
337
351
|
"indexing": true,
|
|
338
352
|
"footer": {
|
|
339
353
|
"copyright_text": "Copyright 2026 Example Corp.",
|
|
@@ -383,7 +397,20 @@ The bundled docs theme shows `Published with ZeroPress.` by default. Set `site.f
|
|
|
383
397
|
|
|
384
398
|
`site.expose_generator` controls the HTML generator meta tag. Missing or `true` emits `<meta name="generator" content="ZeroPress">`; set it to `false` for white-label sites.
|
|
385
399
|
|
|
386
|
-
`site.
|
|
400
|
+
`site.search` controls native ZeroPress search when the selected theme supports search UI. Missing or `true` enables native search for the bundled docs theme; `false` omits `/_zeropress/search.json`, `/_zeropress/search.js`, and `/_zeropress/search_pagefind.js` and hides the bundled search form.
|
|
401
|
+
|
|
402
|
+
The bundled docs theme marks post/page body content with `data-pagefind-body`. If you run Pagefind after the ZeroPress build, keep the theme UI pointed at `/_zeropress/search.js` and replace the native adapter:
|
|
403
|
+
|
|
404
|
+
```bash
|
|
405
|
+
npx pagefind@latest \
|
|
406
|
+
--site ./_site \
|
|
407
|
+
--output-subdir _zeropress/pagefind
|
|
408
|
+
|
|
409
|
+
cp ./_site/_zeropress/search_pagefind.js ./_site/_zeropress/search.js
|
|
410
|
+
rm ./_site/_zeropress/search.json
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
`site.indexing` controls only the generated fallback `robots.txt`. Missing or `true` allows indexing; `false` writes `User-agent: *` / `Disallow: /`. If the public directory contains `robots.txt`, that file is copied as-is and takes priority over `site.indexing`. ZeroPress does not append a `Sitemap` directive to a public `robots.txt`; add `Sitemap: https://example.com/sitemap.xml` manually when needed.
|
|
387
414
|
|
|
388
415
|
Schemas:
|
|
389
416
|
|
|
@@ -405,13 +432,13 @@ Build Pages reads optional site config from `<source>/.zeropress/config.json`. S
|
|
|
405
432
|
|
|
406
433
|
`preview-data.json` is an internal generated build input for the ZeroPress renderer. Most users do not need to edit or understand this file.
|
|
407
434
|
|
|
408
|
-
`build-report.json` records discovered Markdown counts, skipped Markdown files, front page resolution, source Markdown copy policy, and custom HTML slots.
|
|
435
|
+
`build-report.json` records source/public roots, discovered Markdown counts, skipped Markdown files, front page resolution, source Markdown copy policy, and custom HTML slots.
|
|
409
436
|
|
|
410
437
|
`public-assets/` is a temporary staged public root used before the final ZeroPress render.
|
|
411
438
|
|
|
412
439
|
## Destination Output
|
|
413
440
|
|
|
414
|
-
The `destination` directory contains the deployable static site. It includes generated ZeroPress HTML, copied public files, and original Markdown files unless Markdown source copy is disabled or files are excluded by the public passthrough rules. A
|
|
441
|
+
The `destination` directory contains the deployable static site. It includes generated ZeroPress HTML, copied public files, and original Markdown files unless Markdown source copy is disabled or files are excluded by the public passthrough rules. A public `robots.txt` is copied as a site-owned policy file; otherwise ZeroPress writes a fallback `robots.txt` with a sitemap directive when `site.url` is available. Root-level public favicon files are copied and represented as generated HTML head links. A root-level public `sitemap.xsl` is copied and linked from generated `sitemap.xml`.
|
|
415
442
|
|
|
416
443
|
## Demo
|
|
417
444
|
|
package/action.yml
CHANGED
|
@@ -9,6 +9,9 @@ inputs:
|
|
|
9
9
|
description: Dedicated source directory containing Markdown files and optional .zeropress/config.json. Repository root source is not supported.
|
|
10
10
|
required: false
|
|
11
11
|
default: ./docs
|
|
12
|
+
public-dir:
|
|
13
|
+
description: Public passthrough directory for site-owned assets. Defaults to source.
|
|
14
|
+
required: false
|
|
12
15
|
destination:
|
|
13
16
|
description: Output directory for the generated static site.
|
|
14
17
|
required: false
|
package/dist/action.js
CHANGED
|
@@ -52318,6 +52318,7 @@ function validateSite(site, path4, errors) {
|
|
|
52318
52318
|
"media_delivery_mode",
|
|
52319
52319
|
"favicon",
|
|
52320
52320
|
"expose_generator",
|
|
52321
|
+
"search",
|
|
52321
52322
|
"locale",
|
|
52322
52323
|
"posts_per_page",
|
|
52323
52324
|
"datetime_display",
|
|
@@ -52348,6 +52349,9 @@ function validateSite(site, path4, errors) {
|
|
|
52348
52349
|
if (site.expose_generator !== void 0) {
|
|
52349
52350
|
validateBoolean(site.expose_generator, `${path4}.expose_generator`, "INVALID_SITE_EXPOSE_GENERATOR", errors);
|
|
52350
52351
|
}
|
|
52352
|
+
if (site.search !== void 0) {
|
|
52353
|
+
validateBoolean(site.search, `${path4}.search`, "INVALID_SITE_SEARCH", errors);
|
|
52354
|
+
}
|
|
52351
52355
|
validateNonEmptyString(site.locale, `${path4}.locale`, "INVALID_SITE_LOCALE", errors);
|
|
52352
52356
|
validateInteger(site.posts_per_page, `${path4}.posts_per_page`, "INVALID_SITE_POSTS_PER_PAGE", errors, { minimum: 1 });
|
|
52353
52357
|
validateEnum(site.datetime_display, `${path4}.datetime_display`, "INVALID_SITE_DATETIME_DISPLAY", errors, PREVIEW_DATETIME_DISPLAY_MODES);
|
|
@@ -53010,7 +53014,7 @@ function isOptionalKey(path4, key) {
|
|
|
53010
53014
|
return key === "head_end" || key === "body_end";
|
|
53011
53015
|
}
|
|
53012
53016
|
if (path4 === "site") {
|
|
53013
|
-
return key === "media_delivery_mode" || key === "favicon" || key === "expose_generator" || key === "indexing" || key === "permalinks" || key === "front_page" || key === "post_index" || key === "footer" || key === "meta";
|
|
53017
|
+
return key === "media_delivery_mode" || key === "favicon" || key === "expose_generator" || key === "search" || key === "indexing" || key === "permalinks" || key === "front_page" || key === "post_index" || key === "footer" || key === "meta";
|
|
53014
53018
|
}
|
|
53015
53019
|
if (path4 === "site.favicon") {
|
|
53016
53020
|
return key === "icon" || key === "svg" || key === "png" || key === "apple_touch_icon";
|
|
@@ -53272,7 +53276,7 @@ var MENU_SLOT_ID_MAX_LENGTH = 32;
|
|
|
53272
53276
|
var MENU_SLOT_COUNT_MAX = 12;
|
|
53273
53277
|
var MENU_SLOT_TITLE_MAX_LENGTH = 80;
|
|
53274
53278
|
var MENU_SLOT_DESCRIPTION_MAX_LENGTH = 160;
|
|
53275
|
-
var SUPPORTED_THEME_FEATURES = /* @__PURE__ */ new Set(["comments", "newsletter", "post_index"]);
|
|
53279
|
+
var SUPPORTED_THEME_FEATURES = /* @__PURE__ */ new Set(["comments", "newsletter", "post_index", "search"]);
|
|
53276
53280
|
var THEME_MANIFEST_KEYS = /* @__PURE__ */ new Set([
|
|
53277
53281
|
"$schema",
|
|
53278
53282
|
"name",
|
|
@@ -60907,6 +60911,7 @@ var PERMALINK_OUTPUT_STYLES = /* @__PURE__ */ new Set(["directory", "html-extens
|
|
|
60907
60911
|
var COMMENT_POLICY_OUTPUT_PATH = "_zeropress/comment-policy.json";
|
|
60908
60912
|
var SEARCH_INDEX_OUTPUT_PATH = "_zeropress/search.json";
|
|
60909
60913
|
var SEARCH_ADAPTER_OUTPUT_PATH = "_zeropress/search.js";
|
|
60914
|
+
var SEARCH_PAGEFIND_ADAPTER_OUTPUT_PATH = "_zeropress/search_pagefind.js";
|
|
60910
60915
|
var OUTPUT_PATH_CONTROL_CHAR_PATTERN = /[\u0000-\u001F\u007F]/;
|
|
60911
60916
|
var SAFE_MEDIA_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
|
|
60912
60917
|
var SAFE_LINK_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]);
|
|
@@ -60964,9 +60969,12 @@ async function buildSite(input2) {
|
|
|
60964
60969
|
state.commentPolicyContent,
|
|
60965
60970
|
"application/json"
|
|
60966
60971
|
);
|
|
60967
|
-
if (options2
|
|
60972
|
+
if (shouldGenerateSearchArtifacts(state, options2)) {
|
|
60968
60973
|
await writeOutput(state.writer, state.summaries, SEARCH_INDEX_OUTPUT_PATH, buildSearchIndexJson(state), "application/json");
|
|
60969
60974
|
await writeOutput(state.writer, state.summaries, SEARCH_ADAPTER_OUTPUT_PATH, buildSearchAdapterJs(), "application/javascript");
|
|
60975
|
+
await writeOutput(state.writer, state.summaries, SEARCH_PAGEFIND_ADAPTER_OUTPUT_PATH, buildSearchPagefindAdapterJs(), "application/javascript");
|
|
60976
|
+
}
|
|
60977
|
+
if (options2.generateSpecialFiles) {
|
|
60970
60978
|
await maybeRenderNotFoundPage(state);
|
|
60971
60979
|
if (hasCanonicalSiteUrl(state.previewData.site.url)) {
|
|
60972
60980
|
await writeOutput(
|
|
@@ -61286,6 +61294,7 @@ function normalizePreviewData(previewData, options2 = {}) {
|
|
|
61286
61294
|
locale: normalizeLocale(previewData.site.locale || DEFAULT_LOCALE),
|
|
61287
61295
|
disallow_comments: previewData.site.disallow_comments === true,
|
|
61288
61296
|
expose_generator: previewData.site.expose_generator !== false,
|
|
61297
|
+
search: previewData.site.search !== false,
|
|
61289
61298
|
indexing: previewData.site.indexing !== false,
|
|
61290
61299
|
permalinks: normalizePermalinks(previewData.site.permalinks),
|
|
61291
61300
|
front_page: normalizeFrontPage(previewData.site.front_page),
|
|
@@ -61567,6 +61576,8 @@ function normalizePostIndex(post_index) {
|
|
|
61567
61576
|
function createRenderData(previewData, themeMetadata = {}) {
|
|
61568
61577
|
const themeSupportsComments = themeMetadata?.features?.comments === true;
|
|
61569
61578
|
const themeSupportsPostIndex = themeMetadata?.features?.post_index !== false;
|
|
61579
|
+
const themeSupportsSearch = themeMetadata?.features?.search === true;
|
|
61580
|
+
previewData.site.search = previewData.site.search !== false && themeSupportsSearch;
|
|
61570
61581
|
const authorsById = new Map(previewData.content.authors.map((author) => [author.id, author]));
|
|
61571
61582
|
const categoriesBySlug = new Map(previewData.content.categories.map((category) => [category.slug, category]));
|
|
61572
61583
|
const tagsBySlug = new Map(previewData.content.tags.map((tag) => [tag.slug, tag]));
|
|
@@ -62922,8 +62933,10 @@ function assertPlannedOutputPathsSafe(state) {
|
|
|
62922
62933
|
...state.assetOutputs.map((assetOutput) => assetOutput.path),
|
|
62923
62934
|
COMMENT_POLICY_OUTPUT_PATH
|
|
62924
62935
|
];
|
|
62936
|
+
if (shouldGenerateSearchArtifacts(state, state.options)) {
|
|
62937
|
+
plannedPaths.push(SEARCH_INDEX_OUTPUT_PATH, SEARCH_ADAPTER_OUTPUT_PATH, SEARCH_PAGEFIND_ADAPTER_OUTPUT_PATH);
|
|
62938
|
+
}
|
|
62925
62939
|
if (state.options.generateSpecialFiles) {
|
|
62926
|
-
plannedPaths.push(SEARCH_INDEX_OUTPUT_PATH, SEARCH_ADAPTER_OUTPUT_PATH);
|
|
62927
62940
|
plannedPaths.push("404.html");
|
|
62928
62941
|
if (shouldGenerateRobotsTxt(state.options)) {
|
|
62929
62942
|
plannedPaths.push("robots.txt");
|
|
@@ -63436,6 +63449,87 @@ function normalizeLimit(value) {
|
|
|
63436
63449
|
}
|
|
63437
63450
|
`;
|
|
63438
63451
|
}
|
|
63452
|
+
function buildSearchPagefindAdapterJs() {
|
|
63453
|
+
return `let pagefindPromise;
|
|
63454
|
+
|
|
63455
|
+
export async function preload() {
|
|
63456
|
+
if (!pagefindPromise) {
|
|
63457
|
+
pagefindPromise = import(new URL('./pagefind/pagefind.js', import.meta.url).href).then(async (pagefind) => {
|
|
63458
|
+
if (typeof pagefind.options === 'function') {
|
|
63459
|
+
await pagefind.options({ baseUrl: '/' });
|
|
63460
|
+
}
|
|
63461
|
+
return pagefind;
|
|
63462
|
+
});
|
|
63463
|
+
}
|
|
63464
|
+
|
|
63465
|
+
return pagefindPromise;
|
|
63466
|
+
}
|
|
63467
|
+
|
|
63468
|
+
export async function search(query, options = {}) {
|
|
63469
|
+
const pagefind = await preload();
|
|
63470
|
+
const result = await pagefind.search(query, options);
|
|
63471
|
+
const limit = normalizeLimit(options.limit);
|
|
63472
|
+
if (!Array.isArray(result?.results)) {
|
|
63473
|
+
return result;
|
|
63474
|
+
}
|
|
63475
|
+
|
|
63476
|
+
const results = result.results.map(normalizeResult);
|
|
63477
|
+
return {
|
|
63478
|
+
...result,
|
|
63479
|
+
results: limit ? results.slice(0, limit) : results,
|
|
63480
|
+
};
|
|
63481
|
+
}
|
|
63482
|
+
|
|
63483
|
+
function normalizeResult(result) {
|
|
63484
|
+
if (!result || typeof result.data !== 'function') {
|
|
63485
|
+
return result;
|
|
63486
|
+
}
|
|
63487
|
+
|
|
63488
|
+
return {
|
|
63489
|
+
...result,
|
|
63490
|
+
data: async () => normalizeResultData(await result.data()),
|
|
63491
|
+
};
|
|
63492
|
+
}
|
|
63493
|
+
|
|
63494
|
+
function normalizeResultData(data) {
|
|
63495
|
+
if (!data || typeof data !== 'object') {
|
|
63496
|
+
return data;
|
|
63497
|
+
}
|
|
63498
|
+
|
|
63499
|
+
return {
|
|
63500
|
+
...data,
|
|
63501
|
+
url: normalizeUrl(data.url),
|
|
63502
|
+
sub_results: Array.isArray(data.sub_results)
|
|
63503
|
+
? data.sub_results.map((item) => ({ ...item, url: normalizeUrl(item.url) }))
|
|
63504
|
+
: data.sub_results,
|
|
63505
|
+
};
|
|
63506
|
+
}
|
|
63507
|
+
|
|
63508
|
+
function normalizeUrl(value) {
|
|
63509
|
+
const url = String(value || '');
|
|
63510
|
+
if (url.startsWith('/_zeropress/') && !url.startsWith('/_zeropress/pagefind/')) {
|
|
63511
|
+
return url.replace(/^\\/_zeropress/, '') || '/';
|
|
63512
|
+
}
|
|
63513
|
+
if (url.startsWith('_zeropress/') && !url.startsWith('_zeropress/pagefind/')) {
|
|
63514
|
+
return url.replace(/^_zeropress/, '') || '/';
|
|
63515
|
+
}
|
|
63516
|
+
return url;
|
|
63517
|
+
}
|
|
63518
|
+
|
|
63519
|
+
function normalizeLimit(value) {
|
|
63520
|
+
if (value === undefined || value === null) {
|
|
63521
|
+
return null;
|
|
63522
|
+
}
|
|
63523
|
+
|
|
63524
|
+
const limit = Number(value);
|
|
63525
|
+
if (!Number.isFinite(limit) || limit <= 0) {
|
|
63526
|
+
return null;
|
|
63527
|
+
}
|
|
63528
|
+
|
|
63529
|
+
return Math.floor(limit);
|
|
63530
|
+
}
|
|
63531
|
+
`;
|
|
63532
|
+
}
|
|
63439
63533
|
function buildSitemapXml(site, emitted, generatedAt, stylesheetHref = "") {
|
|
63440
63534
|
const entries = [
|
|
63441
63535
|
...emitted.frontPage && emitted.frontPage.includeInSitemap !== false ? [{
|
|
@@ -63521,6 +63615,9 @@ function buildRobotsTxt(site) {
|
|
|
63521
63615
|
function shouldGenerateRobotsTxt(options2) {
|
|
63522
63616
|
return options2.generateSpecialFiles && options2.generateRobotsTxt !== false;
|
|
63523
63617
|
}
|
|
63618
|
+
function shouldGenerateSearchArtifacts(state, options2) {
|
|
63619
|
+
return options2.generateSpecialFiles && state.previewData.site.search === true;
|
|
63620
|
+
}
|
|
63524
63621
|
function getContentType(assetPath) {
|
|
63525
63622
|
const ext = assetPath.split(".").pop()?.toLowerCase();
|
|
63526
63623
|
const contentTypes = {
|
|
@@ -64017,6 +64114,8 @@ async function runBuildPages(options2) {
|
|
|
64017
64114
|
const cwd = path3.resolve(options2.cwd || process.cwd());
|
|
64018
64115
|
const copyMarkdownSource = options2.copyMarkdownSource !== false;
|
|
64019
64116
|
const sourceDir = path3.resolve(cwd, options2.source);
|
|
64117
|
+
const publicDirExplicit = hasExplicitPublicDir(options2);
|
|
64118
|
+
const publicDir = publicDirExplicit ? path3.resolve(cwd, options2.publicDir) : sourceDir;
|
|
64020
64119
|
const destinationDir = path3.resolve(cwd, options2.destination);
|
|
64021
64120
|
const generatedDir = path3.join(cwd, ".zeropress");
|
|
64022
64121
|
const stagingDir = path3.join(cwd, STAGING_DIR);
|
|
@@ -64025,17 +64124,22 @@ async function runBuildPages(options2) {
|
|
|
64025
64124
|
assertBuildPagesPathLayout({
|
|
64026
64125
|
cwd,
|
|
64027
64126
|
sourceDir,
|
|
64127
|
+
publicDir,
|
|
64128
|
+
publicDirExplicit,
|
|
64028
64129
|
destinationDir,
|
|
64029
64130
|
themeDir,
|
|
64030
64131
|
generatedDir
|
|
64031
64132
|
});
|
|
64032
64133
|
await assertDirectory(sourceDir, "Source directory");
|
|
64134
|
+
await assertPublicDirectory(publicDir, publicDirExplicit);
|
|
64135
|
+
await assertDestinationPath(destinationDir);
|
|
64033
64136
|
await fs3.rm(generatedDir, { recursive: true, force: true });
|
|
64034
64137
|
await fs3.mkdir(generatedDir, { recursive: true });
|
|
64035
64138
|
const env = {
|
|
64036
64139
|
...process.env,
|
|
64037
64140
|
ZEROPRESS_BUILD_PAGES_SOURCE: sourceDir,
|
|
64038
|
-
|
|
64141
|
+
ZEROPRESS_BUILD_PAGES_PUBLIC_DIR: publicDir,
|
|
64142
|
+
ZEROPRESS_PUBLIC_DIR: publicDir,
|
|
64039
64143
|
ZEROPRESS_SKIP_UNTITLED_MARKDOWN: String(Boolean(options2.skipUntitledMarkdown)),
|
|
64040
64144
|
ZEROPRESS_COPY_MARKDOWN_SOURCE: String(copyMarkdownSource)
|
|
64041
64145
|
};
|
|
@@ -64059,10 +64163,13 @@ async function runBuildPages(options2) {
|
|
|
64059
64163
|
await fs3.rm(destinationDir, { recursive: true, force: true });
|
|
64060
64164
|
await fs3.rm(stagingDir, { recursive: true, force: true });
|
|
64061
64165
|
await fs3.mkdir(stagingDir, { recursive: true });
|
|
64062
|
-
await copyPublicStaging(
|
|
64166
|
+
await copyPublicStaging(publicDir, stagingDir, {
|
|
64063
64167
|
excludePaths: [destinationDir, themeDir, generatedDir],
|
|
64064
64168
|
copyMarkdownSource
|
|
64065
64169
|
});
|
|
64170
|
+
if (copyMarkdownSource) {
|
|
64171
|
+
await copySourceMarkdownFiles(sourceDir, stagingDir, previewData);
|
|
64172
|
+
}
|
|
64066
64173
|
const previousPublicDir = process.env.ZEROPRESS_PUBLIC_DIR;
|
|
64067
64174
|
process.env.ZEROPRESS_PUBLIC_DIR = stagingDir;
|
|
64068
64175
|
try {
|
|
@@ -64097,6 +64204,9 @@ function resolveThemeDir(cwd, options2) {
|
|
|
64097
64204
|
}
|
|
64098
64205
|
throw new Error(`Unknown bundled theme: ${options2.theme}`);
|
|
64099
64206
|
}
|
|
64207
|
+
function hasExplicitPublicDir(options2) {
|
|
64208
|
+
return typeof options2.publicDir === "string" && Boolean(options2.publicDir.trim());
|
|
64209
|
+
}
|
|
64100
64210
|
async function assertDirectory(dir, label) {
|
|
64101
64211
|
let stat;
|
|
64102
64212
|
try {
|
|
@@ -64111,17 +64221,78 @@ async function assertDirectory(dir, label) {
|
|
|
64111
64221
|
throw new Error(`${label} is not a directory: ${dir}`);
|
|
64112
64222
|
}
|
|
64113
64223
|
}
|
|
64114
|
-
function
|
|
64224
|
+
async function assertPublicDirectory(publicDir, explicit) {
|
|
64225
|
+
if (!explicit) {
|
|
64226
|
+
return;
|
|
64227
|
+
}
|
|
64228
|
+
let stat;
|
|
64229
|
+
try {
|
|
64230
|
+
stat = await fs3.lstat(publicDir);
|
|
64231
|
+
} catch (error) {
|
|
64232
|
+
if (error?.code === "ENOENT") {
|
|
64233
|
+
throw new Error(`Public directory not found: ${publicDir}`);
|
|
64234
|
+
}
|
|
64235
|
+
throw error;
|
|
64236
|
+
}
|
|
64237
|
+
if (stat.isSymbolicLink()) {
|
|
64238
|
+
throw new Error(`Public directory must not be a symbolic link: ${publicDir}`);
|
|
64239
|
+
}
|
|
64240
|
+
if (!stat.isDirectory()) {
|
|
64241
|
+
throw new Error(`Public path is not a directory: ${publicDir}`);
|
|
64242
|
+
}
|
|
64243
|
+
}
|
|
64244
|
+
async function assertDestinationPath(destinationDir) {
|
|
64245
|
+
let stat;
|
|
64246
|
+
try {
|
|
64247
|
+
stat = await fs3.lstat(destinationDir);
|
|
64248
|
+
} catch (error) {
|
|
64249
|
+
if (error?.code === "ENOENT") {
|
|
64250
|
+
return;
|
|
64251
|
+
}
|
|
64252
|
+
throw error;
|
|
64253
|
+
}
|
|
64254
|
+
if (!stat.isDirectory()) {
|
|
64255
|
+
throw new Error(`Destination path is not a directory: ${destinationDir}`);
|
|
64256
|
+
}
|
|
64257
|
+
}
|
|
64258
|
+
function assertBuildPagesPathLayout({
|
|
64259
|
+
cwd,
|
|
64260
|
+
sourceDir,
|
|
64261
|
+
publicDir,
|
|
64262
|
+
publicDirExplicit,
|
|
64263
|
+
destinationDir,
|
|
64264
|
+
themeDir,
|
|
64265
|
+
generatedDir
|
|
64266
|
+
}) {
|
|
64115
64267
|
if (samePath(sourceDir, cwd)) {
|
|
64116
64268
|
throw new Error(
|
|
64117
64269
|
`Source directory must be a dedicated content directory, not the current working directory. Received: ${formatPath(cwd, sourceDir)}`
|
|
64118
64270
|
);
|
|
64119
64271
|
}
|
|
64272
|
+
if (publicDirExplicit && samePath(publicDir, cwd)) {
|
|
64273
|
+
throw new Error(
|
|
64274
|
+
`Public directory must be a dedicated asset directory, not the current working directory. Received: ${formatPath(cwd, publicDir)}`
|
|
64275
|
+
);
|
|
64276
|
+
}
|
|
64120
64277
|
assertNoPathOverlap(cwd, "Source directory", sourceDir, "internal .zeropress working directory", generatedDir);
|
|
64121
64278
|
assertNoPathOverlap(cwd, "Destination directory", destinationDir, "internal .zeropress working directory", generatedDir);
|
|
64122
64279
|
assertNoPathOverlap(cwd, "Theme directory", themeDir, "internal .zeropress working directory", generatedDir);
|
|
64280
|
+
if (!samePath(publicDir, sourceDir)) {
|
|
64281
|
+
assertNoPathOverlap(cwd, "Public directory", publicDir, "internal .zeropress working directory", generatedDir);
|
|
64282
|
+
assertNoPathOverlap(cwd, "Public directory", publicDir, "destination directory", destinationDir);
|
|
64283
|
+
assertNoPathOverlap(cwd, "Public directory", publicDir, "theme directory", themeDir);
|
|
64284
|
+
}
|
|
64123
64285
|
assertNoPathOverlap(cwd, "Source directory", sourceDir, "destination directory", destinationDir);
|
|
64124
64286
|
assertNoPathOverlap(cwd, "Source directory", sourceDir, "theme directory", themeDir);
|
|
64287
|
+
assertSourceIsNotInsidePublicDirectory(cwd, sourceDir, publicDir);
|
|
64288
|
+
}
|
|
64289
|
+
function assertSourceIsNotInsidePublicDirectory(cwd, sourceDir, publicDir) {
|
|
64290
|
+
if (samePath(sourceDir, publicDir) || !isPathInside2(publicDir, sourceDir)) {
|
|
64291
|
+
return;
|
|
64292
|
+
}
|
|
64293
|
+
throw new Error(
|
|
64294
|
+
`Source directory must not be inside the public directory. Source directory: ${formatPath(cwd, sourceDir)}; Public directory: ${formatPath(cwd, publicDir)}`
|
|
64295
|
+
);
|
|
64125
64296
|
}
|
|
64126
64297
|
function assertNoPathOverlap(cwd, firstLabel, firstPath, secondLabel, secondPath) {
|
|
64127
64298
|
if (!pathsOverlap2(firstPath, secondPath)) {
|
|
@@ -64157,6 +64328,52 @@ async function copyPublicStaging(sourceDir, targetDir, options2) {
|
|
|
64157
64328
|
await fs3.copyFile(sourcePath, targetPath);
|
|
64158
64329
|
}
|
|
64159
64330
|
}
|
|
64331
|
+
async function copySourceMarkdownFiles(sourceDir, targetDir, previewData) {
|
|
64332
|
+
const markdownUrls = /* @__PURE__ */ new Set();
|
|
64333
|
+
for (const page of previewData?.content?.pages || []) {
|
|
64334
|
+
const sourceMarkdownUrl = page?.meta?.source_markdown_url;
|
|
64335
|
+
if (typeof sourceMarkdownUrl === "string" && sourceMarkdownUrl) {
|
|
64336
|
+
markdownUrls.add(sourceMarkdownUrl);
|
|
64337
|
+
}
|
|
64338
|
+
}
|
|
64339
|
+
for (const sourceMarkdownUrl of markdownUrls) {
|
|
64340
|
+
const relativePath = sourceMarkdownUrlToRelativePath(sourceMarkdownUrl);
|
|
64341
|
+
if (!relativePath) {
|
|
64342
|
+
continue;
|
|
64343
|
+
}
|
|
64344
|
+
const sourcePath = path3.join(sourceDir, relativePath);
|
|
64345
|
+
if (!isPathInside2(sourceDir, sourcePath)) {
|
|
64346
|
+
continue;
|
|
64347
|
+
}
|
|
64348
|
+
const targetPath = path3.join(targetDir, relativePath);
|
|
64349
|
+
await fs3.mkdir(path3.dirname(targetPath), { recursive: true });
|
|
64350
|
+
await fs3.copyFile(sourcePath, targetPath);
|
|
64351
|
+
}
|
|
64352
|
+
}
|
|
64353
|
+
function sourceMarkdownUrlToRelativePath(sourceMarkdownUrl) {
|
|
64354
|
+
if (!sourceMarkdownUrl.startsWith("/") || sourceMarkdownUrl.includes("?") || sourceMarkdownUrl.includes("#")) {
|
|
64355
|
+
return "";
|
|
64356
|
+
}
|
|
64357
|
+
const rawSegments = sourceMarkdownUrl.slice(1).split("/");
|
|
64358
|
+
const segments = [];
|
|
64359
|
+
for (const rawSegment of rawSegments) {
|
|
64360
|
+
if (!rawSegment) {
|
|
64361
|
+
return "";
|
|
64362
|
+
}
|
|
64363
|
+
let segment;
|
|
64364
|
+
try {
|
|
64365
|
+
segment = decodeURIComponent(rawSegment);
|
|
64366
|
+
} catch {
|
|
64367
|
+
return "";
|
|
64368
|
+
}
|
|
64369
|
+
if (!segment || segment === "." || segment === ".." || segment.includes("/") || segment.includes("\\")) {
|
|
64370
|
+
return "";
|
|
64371
|
+
}
|
|
64372
|
+
segments.push(segment);
|
|
64373
|
+
}
|
|
64374
|
+
const relativePath = segments.join("/");
|
|
64375
|
+
return relativePath.toLowerCase().endsWith(".md") ? relativePath : "";
|
|
64376
|
+
}
|
|
64160
64377
|
function shouldIgnorePublicEntry2(name) {
|
|
64161
64378
|
const basename = String(name || "");
|
|
64162
64379
|
const lowerName = basename.toLowerCase();
|
|
@@ -64185,6 +64402,7 @@ function formatPath(cwd, targetPath) {
|
|
|
64185
64402
|
// src/action.js
|
|
64186
64403
|
var options = {
|
|
64187
64404
|
source: input("source") || "./docs",
|
|
64405
|
+
publicDir: input("public-dir"),
|
|
64188
64406
|
destination: input("destination") || "./_site",
|
|
64189
64407
|
theme: input("theme") || "docs",
|
|
64190
64408
|
themePath: input("theme-path"),
|
package/dist/prebuild.js
CHANGED
|
@@ -3524,6 +3524,7 @@ import { fileURLToPath } from "node:url";
|
|
|
3524
3524
|
var __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
3525
3525
|
var rootDir = process.cwd();
|
|
3526
3526
|
var sourceDir = resolveEnvPath(["ZEROPRESS_BUILD_PAGES_SOURCE"], "docs");
|
|
3527
|
+
var publicDir = resolveEnvPath(["ZEROPRESS_BUILD_PAGES_PUBLIC_DIR"], sourceDir);
|
|
3527
3528
|
var defaultConfigPath = path.join(sourceDir, ".zeropress", "config.json");
|
|
3528
3529
|
var configPath = resolveOptionalEnvPath(["ZEROPRESS_BUILD_PAGES_CONFIG"], defaultConfigPath);
|
|
3529
3530
|
var outDir = path.join(rootDir, ".zeropress");
|
|
@@ -3540,6 +3541,7 @@ var FRONT_MATTER_DATA_MAX_DEPTH = 4;
|
|
|
3540
3541
|
var FRONT_MATTER_DATA_MAX_KEYS = 64;
|
|
3541
3542
|
var FRONT_MATTER_DATA_MAX_ARRAY_LENGTH = 256;
|
|
3542
3543
|
var FRONT_MATTER_DISCOVERABILITY_VALUES = /* @__PURE__ */ new Set(["default", "noindex", "delist"]);
|
|
3544
|
+
var markdownDiscoverExcludeRoots = buildMarkdownDiscoverExcludeRoots();
|
|
3543
3545
|
var PrebuildMarkdownError = class extends Error {
|
|
3544
3546
|
constructor(sourcePath, reason, expected = "", code = "invalid_markdown") {
|
|
3545
3547
|
super(reason);
|
|
@@ -3740,6 +3742,7 @@ function buildSiteData(config, frontPage) {
|
|
|
3740
3742
|
},
|
|
3741
3743
|
disallow_comments: true,
|
|
3742
3744
|
expose_generator: configuredSite.expose_generator !== false,
|
|
3745
|
+
search: configuredSite.search !== false,
|
|
3743
3746
|
indexing: configuredSite.indexing !== false
|
|
3744
3747
|
};
|
|
3745
3748
|
if (configuredSite.footer) {
|
|
@@ -3768,12 +3771,13 @@ function normalizeSiteConfig(value) {
|
|
|
3768
3771
|
);
|
|
3769
3772
|
}
|
|
3770
3773
|
const configuredSite = isPlainObject(value) ? value : {};
|
|
3771
|
-
assertKnownConfigKeys(configuredSite, ["title", "description", "url", "expose_generator", "indexing", "footer"], "site");
|
|
3774
|
+
assertKnownConfigKeys(configuredSite, ["title", "description", "url", "expose_generator", "search", "indexing", "footer"], "site");
|
|
3772
3775
|
const site = {
|
|
3773
3776
|
title: readConfigString(configuredSite.title, "Documentation"),
|
|
3774
3777
|
description: readConfigString(configuredSite.description, "A documentation site."),
|
|
3775
3778
|
url: readEnv("ZEROPRESS_SITE_URL", readConfigString(configuredSite.url, "")),
|
|
3776
3779
|
expose_generator: readConfigBoolean(configuredSite.expose_generator, true, "site.expose_generator"),
|
|
3780
|
+
search: readConfigBoolean(configuredSite.search, true, "site.search"),
|
|
3777
3781
|
indexing: readConfigBoolean(configuredSite.indexing, true, "site.indexing")
|
|
3778
3782
|
};
|
|
3779
3783
|
const footer = normalizeFooter(configuredSite.footer);
|
|
@@ -4184,6 +4188,7 @@ function buildPrebuildReport({
|
|
|
4184
4188
|
return {
|
|
4185
4189
|
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4186
4190
|
source_dir: formatSourcePath(sourceDir),
|
|
4191
|
+
public_dir: formatSourcePath(publicDir),
|
|
4187
4192
|
config_path: formatSourcePath(configPath),
|
|
4188
4193
|
build_pages_config_path: formatSourcePath(buildPagesConfigPath),
|
|
4189
4194
|
preview_data_path: formatSourcePath(previewDataPath),
|
|
@@ -4209,7 +4214,8 @@ function buildPrebuildReport({
|
|
|
4209
4214
|
function printPrebuildSummary(report) {
|
|
4210
4215
|
const lines = [
|
|
4211
4216
|
"ZeroPress build report",
|
|
4212
|
-
`-
|
|
4217
|
+
`- Source root: ${report.source_dir}`,
|
|
4218
|
+
`- Public root: ${report.public_dir}`,
|
|
4213
4219
|
`- Markdown discovered: ${report.markdown.discovered}`,
|
|
4214
4220
|
`- Markdown pages generated: ${report.markdown.generated_pages}`,
|
|
4215
4221
|
`- Markdown skipped: ${report.markdown.skipped}`,
|
|
@@ -4497,6 +4503,9 @@ async function listMarkdownFiles(dir) {
|
|
|
4497
4503
|
continue;
|
|
4498
4504
|
}
|
|
4499
4505
|
const entryPath = path.join(dir, entry.name);
|
|
4506
|
+
if (isMarkdownDiscoverExcluded(entryPath)) {
|
|
4507
|
+
continue;
|
|
4508
|
+
}
|
|
4500
4509
|
if (entry.isDirectory()) {
|
|
4501
4510
|
files.push(...await listMarkdownFiles(entryPath));
|
|
4502
4511
|
continue;
|
|
@@ -4507,6 +4516,18 @@ async function listMarkdownFiles(dir) {
|
|
|
4507
4516
|
}
|
|
4508
4517
|
return files.sort((left, right) => left.localeCompare(right));
|
|
4509
4518
|
}
|
|
4519
|
+
function buildMarkdownDiscoverExcludeRoots() {
|
|
4520
|
+
if (samePath(sourceDir, publicDir) || !isPathInside(sourceDir, publicDir)) {
|
|
4521
|
+
return [];
|
|
4522
|
+
}
|
|
4523
|
+
return [publicDir];
|
|
4524
|
+
}
|
|
4525
|
+
function isMarkdownDiscoverExcluded(entryPath) {
|
|
4526
|
+
return markdownDiscoverExcludeRoots.some((excludeRoot) => samePath(entryPath, excludeRoot) || isPathInside(excludeRoot, entryPath));
|
|
4527
|
+
}
|
|
4528
|
+
function samePath(firstPath, secondPath) {
|
|
4529
|
+
return path.resolve(firstPath) === path.resolve(secondPath);
|
|
4530
|
+
}
|
|
4510
4531
|
function shouldIgnoreMarkdownDiscoverEntry(name) {
|
|
4511
4532
|
const basename = String(name || "");
|
|
4512
4533
|
const lowerName = basename.toLowerCase();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeropress/build-pages",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "ZeroPress Markdown build action and CLI for static hosting",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"node": ">=18.18.0"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@zeropress/build": "0.6.
|
|
43
|
+
"@zeropress/build": "0.6.2",
|
|
44
44
|
"gray-matter": "4.0.3"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|