@zeropress/build-core 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 CHANGED
@@ -57,8 +57,9 @@ import {
57
57
  - `feed.xml`
58
58
  - fallback `robots.txt`
59
59
  - `/_zeropress/comment-policy.json`
60
- - `/_zeropress/search.json`
61
- - `/_zeropress/search.js`
60
+ - `/_zeropress/search.json` when native search is enabled
61
+ - `/_zeropress/search.js` when native search is enabled
62
+ - `/_zeropress/search_pagefind.js` when native search is enabled
62
63
  - writing outputs through a pluggable writer
63
64
 
64
65
  It does not:
@@ -212,6 +213,9 @@ The canonical `preview-data v0.6` site contract uses:
212
213
  - `site.disallow_comments`
213
214
  - `site.indexing`
214
215
  - `site.expose_generator`
216
+ - `site.search`
217
+
218
+ Native search artifacts are emitted only when preview-data does not set `site.search: false` and the active theme declares `features.search: true`. `search_pagefind.js` is a Pagefind adapter that can replace `search.js` after a post-build Pagefind step.
215
219
 
216
220
  Optional route templates behave as rendering capabilities, not guaranteed outputs:
217
221
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeropress/build-core",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "description": "Shared ZeroPress build core",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -34,8 +34,8 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "@zeropress/slug-policy": "0.1.0",
37
- "@zeropress/preview-data-validator": "0.6.1",
38
- "@zeropress/theme-validator": "0.6.1",
37
+ "@zeropress/preview-data-validator": "0.6.2",
38
+ "@zeropress/theme-validator": "0.6.2",
39
39
  "highlight.js": "^11.11.1",
40
40
  "markdown-it": "^14.1.0",
41
41
  "markdown-it-anchor": "^9.2.0"
@@ -40,6 +40,7 @@ const PERMALINK_OUTPUT_STYLES = new Set(['directory', 'html-extension']);
40
40
  const COMMENT_POLICY_OUTPUT_PATH = '_zeropress/comment-policy.json';
41
41
  const SEARCH_INDEX_OUTPUT_PATH = '_zeropress/search.json';
42
42
  const SEARCH_ADAPTER_OUTPUT_PATH = '_zeropress/search.js';
43
+ const SEARCH_PAGEFIND_ADAPTER_OUTPUT_PATH = '_zeropress/search_pagefind.js';
43
44
  const OUTPUT_PATH_CONTROL_CHAR_PATTERN = /[\u0000-\u001F\u007F]/;
44
45
  const SAFE_MEDIA_PROTOCOLS = new Set(['http:', 'https:']);
45
46
  const SAFE_LINK_PROTOCOLS = new Set(['http:', 'https:', 'mailto:', 'tel:']);
@@ -107,9 +108,13 @@ export async function buildSite(input) {
107
108
  'application/json',
108
109
  );
109
110
 
110
- if (options.generateSpecialFiles) {
111
+ if (shouldGenerateSearchArtifacts(state, options)) {
111
112
  await writeOutput(state.writer, state.summaries, SEARCH_INDEX_OUTPUT_PATH, buildSearchIndexJson(state), 'application/json');
112
113
  await writeOutput(state.writer, state.summaries, SEARCH_ADAPTER_OUTPUT_PATH, buildSearchAdapterJs(), 'application/javascript');
114
+ await writeOutput(state.writer, state.summaries, SEARCH_PAGEFIND_ADAPTER_OUTPUT_PATH, buildSearchPagefindAdapterJs(), 'application/javascript');
115
+ }
116
+
117
+ if (options.generateSpecialFiles) {
113
118
  await maybeRenderNotFoundPage(state);
114
119
  if (hasCanonicalSiteUrl(state.previewData.site.url)) {
115
120
  await writeOutput(
@@ -459,6 +464,7 @@ function normalizePreviewData(previewData, options = {}) {
459
464
  locale: normalizeLocale(previewData.site.locale || DEFAULT_LOCALE),
460
465
  disallow_comments: previewData.site.disallow_comments === true,
461
466
  expose_generator: previewData.site.expose_generator !== false,
467
+ search: previewData.site.search !== false,
462
468
  indexing: previewData.site.indexing !== false,
463
469
  permalinks: normalizePermalinks(previewData.site.permalinks),
464
470
  front_page: normalizeFrontPage(previewData.site.front_page),
@@ -794,6 +800,8 @@ function normalizePostIndex(post_index) {
794
800
  function createRenderData(previewData, themeMetadata = {}) {
795
801
  const themeSupportsComments = themeMetadata?.features?.comments === true;
796
802
  const themeSupportsPostIndex = themeMetadata?.features?.post_index !== false;
803
+ const themeSupportsSearch = themeMetadata?.features?.search === true;
804
+ previewData.site.search = previewData.site.search !== false && themeSupportsSearch;
797
805
  const authorsById = new Map(previewData.content.authors.map((author) => [author.id, author]));
798
806
  const categoriesBySlug = new Map(previewData.content.categories.map((category) => [category.slug, category]));
799
807
  const tagsBySlug = new Map(previewData.content.tags.map((tag) => [tag.slug, tag]));
@@ -2393,8 +2401,11 @@ function assertPlannedOutputPathsSafe(state) {
2393
2401
  COMMENT_POLICY_OUTPUT_PATH,
2394
2402
  ];
2395
2403
 
2404
+ if (shouldGenerateSearchArtifacts(state, state.options)) {
2405
+ plannedPaths.push(SEARCH_INDEX_OUTPUT_PATH, SEARCH_ADAPTER_OUTPUT_PATH, SEARCH_PAGEFIND_ADAPTER_OUTPUT_PATH);
2406
+ }
2407
+
2396
2408
  if (state.options.generateSpecialFiles) {
2397
- plannedPaths.push(SEARCH_INDEX_OUTPUT_PATH, SEARCH_ADAPTER_OUTPUT_PATH);
2398
2409
  plannedPaths.push('404.html');
2399
2410
  if (shouldGenerateRobotsTxt(state.options)) {
2400
2411
  plannedPaths.push('robots.txt');
@@ -2965,6 +2976,88 @@ function normalizeLimit(value) {
2965
2976
  `;
2966
2977
  }
2967
2978
 
2979
+ function buildSearchPagefindAdapterJs() {
2980
+ return `let pagefindPromise;
2981
+
2982
+ export async function preload() {
2983
+ if (!pagefindPromise) {
2984
+ pagefindPromise = import(new URL('./pagefind/pagefind.js', import.meta.url).href).then(async (pagefind) => {
2985
+ if (typeof pagefind.options === 'function') {
2986
+ await pagefind.options({ baseUrl: '/' });
2987
+ }
2988
+ return pagefind;
2989
+ });
2990
+ }
2991
+
2992
+ return pagefindPromise;
2993
+ }
2994
+
2995
+ export async function search(query, options = {}) {
2996
+ const pagefind = await preload();
2997
+ const result = await pagefind.search(query, options);
2998
+ const limit = normalizeLimit(options.limit);
2999
+ if (!Array.isArray(result?.results)) {
3000
+ return result;
3001
+ }
3002
+
3003
+ const results = result.results.map(normalizeResult);
3004
+ return {
3005
+ ...result,
3006
+ results: limit ? results.slice(0, limit) : results,
3007
+ };
3008
+ }
3009
+
3010
+ function normalizeResult(result) {
3011
+ if (!result || typeof result.data !== 'function') {
3012
+ return result;
3013
+ }
3014
+
3015
+ return {
3016
+ ...result,
3017
+ data: async () => normalizeResultData(await result.data()),
3018
+ };
3019
+ }
3020
+
3021
+ function normalizeResultData(data) {
3022
+ if (!data || typeof data !== 'object') {
3023
+ return data;
3024
+ }
3025
+
3026
+ return {
3027
+ ...data,
3028
+ url: normalizeUrl(data.url),
3029
+ sub_results: Array.isArray(data.sub_results)
3030
+ ? data.sub_results.map((item) => ({ ...item, url: normalizeUrl(item.url) }))
3031
+ : data.sub_results,
3032
+ };
3033
+ }
3034
+
3035
+ function normalizeUrl(value) {
3036
+ const url = String(value || '');
3037
+ if (url.startsWith('/_zeropress/') && !url.startsWith('/_zeropress/pagefind/')) {
3038
+ return url.replace(/^\\/_zeropress/, '') || '/';
3039
+ }
3040
+ if (url.startsWith('_zeropress/') && !url.startsWith('_zeropress/pagefind/')) {
3041
+ return url.replace(/^_zeropress/, '') || '/';
3042
+ }
3043
+ return url;
3044
+ }
3045
+
3046
+ function normalizeLimit(value) {
3047
+ if (value === undefined || value === null) {
3048
+ return null;
3049
+ }
3050
+
3051
+ const limit = Number(value);
3052
+ if (!Number.isFinite(limit) || limit <= 0) {
3053
+ return null;
3054
+ }
3055
+
3056
+ return Math.floor(limit);
3057
+ }
3058
+ `;
3059
+ }
3060
+
2968
3061
  function buildSitemapXml(site, emitted, generatedAt, stylesheetHref = '') {
2969
3062
  const entries = [
2970
3063
  ...(emitted.frontPage && emitted.frontPage.includeInSitemap !== false
@@ -3045,6 +3138,10 @@ function shouldGenerateRobotsTxt(options) {
3045
3138
  return options.generateSpecialFiles && options.generateRobotsTxt !== false;
3046
3139
  }
3047
3140
 
3141
+ function shouldGenerateSearchArtifacts(state, options) {
3142
+ return options.generateSpecialFiles && state.previewData.site.search === true;
3143
+ }
3144
+
3048
3145
  function getContentType(assetPath) {
3049
3146
  const ext = assetPath.split('.').pop()?.toLowerCase();
3050
3147
  const contentTypes = {