@zeropress/build-pages 0.5.6 → 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/dist/action.js CHANGED
@@ -52242,7 +52242,7 @@ function invalidSlugValidationResult(value, code2) {
52242
52242
  }
52243
52243
 
52244
52244
  // node_modules/@zeropress/preview-data-validator/src/index.js
52245
- var PREVIEW_DATA_VERSION = "0.5";
52245
+ var PREVIEW_DATA_VERSION = "0.6";
52246
52246
  var PREVIEW_DOCUMENT_TYPES = ["plaintext", "markdown", "html"];
52247
52247
  var PREVIEW_MENU_ITEM_TYPES = ["custom", "page", "post", "category"];
52248
52248
  var PREVIEW_MENU_TARGETS = ["_self", "_blank"];
@@ -52251,9 +52251,16 @@ var PREVIEW_WIDGET_AREA_ID_PATTERN = /^[a-z][a-z0-9_-]{0,63}$/;
52251
52251
  var PREVIEW_COLLECTION_ID_PATTERN = /^[a-z][a-z0-9_-]{0,63}$/;
52252
52252
  var PREVIEW_COLLECTION_ITEM_TYPES = ["post", "page"];
52253
52253
  var PREVIEW_MEDIA_DELIVERY_MODES = ["none", "media_domain"];
52254
+ var PREVIEW_DATETIME_DISPLAY_MODES = ["static", "client"];
52255
+ var PREVIEW_DATETIME_STYLES = ["none", "short", "medium", "long", "full"];
52256
+ var PREVIEW_DISCOVERABILITY_VALUES = ["default", "noindex", "delist"];
52254
52257
  var PREVIEW_PERMALINK_OUTPUT_STYLES = ["directory", "html-extension"];
52255
52258
  var PREVIEW_PERMALINK_FIELDS = ["posts", "pages", "categories", "tags"];
52256
52259
  var PREVIEW_FRONT_PAGE_TYPES = ["theme_index", "page", "standalone_html"];
52260
+ var PREVIEW_DATA_KEY_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*(?:-[a-zA-Z0-9_]+)*$/;
52261
+ var PREVIEW_DATA_MAX_DEPTH = 4;
52262
+ var PREVIEW_DATA_MAX_KEYS = 64;
52263
+ var PREVIEW_DATA_MAX_ARRAY_LENGTH = 256;
52257
52264
  var PREVIEW_PERMALINK_TOKENS = Object.freeze({
52258
52265
  posts: /* @__PURE__ */ new Set(["slug", "public_id", "year", "month", "day"]),
52259
52266
  pages: /* @__PURE__ */ new Set(["slug"]),
@@ -52307,15 +52314,18 @@ function validateSite(site, path4, errors) {
52307
52314
  "title",
52308
52315
  "description",
52309
52316
  "url",
52310
- "mediaBaseUrl",
52311
- "mediaDeliveryMode",
52317
+ "media_base_url",
52318
+ "media_delivery_mode",
52312
52319
  "favicon",
52320
+ "expose_generator",
52321
+ "search",
52313
52322
  "locale",
52314
- "postsPerPage",
52315
- "dateFormat",
52316
- "timeFormat",
52323
+ "posts_per_page",
52324
+ "datetime_display",
52325
+ "date_style",
52326
+ "time_style",
52317
52327
  "timezone",
52318
- "disallowComments",
52328
+ "disallow_comments",
52319
52329
  "indexing",
52320
52330
  "permalinks",
52321
52331
  "front_page",
@@ -52329,19 +52339,26 @@ function validateSite(site, path4, errors) {
52329
52339
  validateNonEmptyString(site.title, `${path4}.title`, "INVALID_SITE_TITLE", errors);
52330
52340
  validateString(site.description, `${path4}.description`, "INVALID_SITE_DESCRIPTION", errors);
52331
52341
  validateSiteUri(site.url, `${path4}.url`, "INVALID_SITE_URL", errors);
52332
- validateSiteUri(site.mediaBaseUrl, `${path4}.mediaBaseUrl`, "INVALID_SITE_MEDIA_BASE_URL", errors);
52333
- if (site.mediaDeliveryMode !== void 0) {
52334
- validateEnum(site.mediaDeliveryMode, `${path4}.mediaDeliveryMode`, "INVALID_SITE_MEDIA_DELIVERY_MODE", errors, PREVIEW_MEDIA_DELIVERY_MODES);
52342
+ validateSiteUri(site.media_base_url, `${path4}.media_base_url`, "INVALID_SITE_MEDIA_BASE_URL", errors);
52343
+ if (site.media_delivery_mode !== void 0) {
52344
+ validateEnum(site.media_delivery_mode, `${path4}.media_delivery_mode`, "INVALID_SITE_MEDIA_DELIVERY_MODE", errors, PREVIEW_MEDIA_DELIVERY_MODES);
52335
52345
  }
52336
52346
  if (site.favicon !== void 0) {
52337
52347
  validateSiteFavicon(site.favicon, `${path4}.favicon`, errors);
52338
52348
  }
52349
+ if (site.expose_generator !== void 0) {
52350
+ validateBoolean(site.expose_generator, `${path4}.expose_generator`, "INVALID_SITE_EXPOSE_GENERATOR", errors);
52351
+ }
52352
+ if (site.search !== void 0) {
52353
+ validateBoolean(site.search, `${path4}.search`, "INVALID_SITE_SEARCH", errors);
52354
+ }
52339
52355
  validateNonEmptyString(site.locale, `${path4}.locale`, "INVALID_SITE_LOCALE", errors);
52340
- validateInteger(site.postsPerPage, `${path4}.postsPerPage`, "INVALID_SITE_POSTS_PER_PAGE", errors, { minimum: 1 });
52341
- validateNonEmptyString(site.dateFormat, `${path4}.dateFormat`, "INVALID_SITE_DATE_FORMAT", errors);
52342
- validateString(site.timeFormat, `${path4}.timeFormat`, "INVALID_SITE_TIME_FORMAT", errors);
52356
+ validateInteger(site.posts_per_page, `${path4}.posts_per_page`, "INVALID_SITE_POSTS_PER_PAGE", errors, { minimum: 1 });
52357
+ validateEnum(site.datetime_display, `${path4}.datetime_display`, "INVALID_SITE_DATETIME_DISPLAY", errors, PREVIEW_DATETIME_DISPLAY_MODES);
52358
+ validateEnum(site.date_style, `${path4}.date_style`, "INVALID_SITE_DATE_STYLE", errors, PREVIEW_DATETIME_STYLES);
52359
+ validateEnum(site.time_style, `${path4}.time_style`, "INVALID_SITE_TIME_STYLE", errors, PREVIEW_DATETIME_STYLES);
52343
52360
  validateNonEmptyString(site.timezone, `${path4}.timezone`, "INVALID_SITE_TIMEZONE", errors);
52344
- validateBoolean(site.disallowComments, `${path4}.disallowComments`, "INVALID_SITE_DISALLOW_COMMENTS", errors);
52361
+ validateBoolean(site.disallow_comments, `${path4}.disallow_comments`, "INVALID_SITE_DISALLOW_COMMENTS", errors);
52345
52362
  if (site.indexing !== void 0) {
52346
52363
  validateBoolean(site.indexing, `${path4}.indexing`, "INVALID_SITE_INDEXING", errors);
52347
52364
  }
@@ -52357,7 +52374,6 @@ function validateSite(site, path4, errors) {
52357
52374
  "site_description",
52358
52375
  "site_url",
52359
52376
  "metadata",
52360
- "media_delivery_mode",
52361
52377
  "media_delivery_base_url",
52362
52378
  "language",
52363
52379
  "siteLocale",
@@ -52395,10 +52411,7 @@ function validateSiteFooter(footer, path4, errors) {
52395
52411
  validateNonEmptyString(footer.copyright_text, `${path4}.copyright_text`, "INVALID_SITE_FOOTER_COPYRIGHT_TEXT", errors);
52396
52412
  }
52397
52413
  if (footer.attribution !== void 0) {
52398
- validateClosedObject(footer.attribution, `${path4}.attribution`, errors, ["enabled"]);
52399
- if (isObject(footer.attribution) && footer.attribution.enabled !== void 0) {
52400
- validateBoolean(footer.attribution.enabled, `${path4}.attribution.enabled`, "INVALID_SITE_FOOTER_ATTRIBUTION_ENABLED", errors);
52401
- }
52414
+ validateBoolean(footer.attribution, `${path4}.attribution`, "INVALID_SITE_FOOTER_ATTRIBUTION", errors);
52402
52415
  }
52403
52416
  }
52404
52417
  function validateMenus(menus, path4, errors) {
@@ -52424,7 +52437,7 @@ function validatePreviewMenu(menu, path4, errors) {
52424
52437
  });
52425
52438
  }
52426
52439
  function validatePreviewMenuItem(item, path4, errors) {
52427
- validateClosedObject(item, path4, errors, ["title", "url", "type", "target", "children"]);
52440
+ validateClosedObject(item, path4, errors, ["title", "url", "type", "target", "meta", "children"]);
52428
52441
  if (!isObject(item)) {
52429
52442
  return;
52430
52443
  }
@@ -52432,6 +52445,7 @@ function validatePreviewMenuItem(item, path4, errors) {
52432
52445
  validateUrlLike(item.url, `${path4}.url`, "INVALID_MENU_ITEM_URL", errors);
52433
52446
  validateEnum(item.type, `${path4}.type`, "INVALID_MENU_ITEM_TYPE", errors, PREVIEW_MENU_ITEM_TYPES);
52434
52447
  validateEnum(item.target, `${path4}.target`, "INVALID_MENU_ITEM_TARGET", errors, PREVIEW_MENU_TARGETS);
52448
+ validatePreviewMeta(item.meta, `${path4}.meta`, errors);
52435
52449
  validateArray(item.children, `${path4}.children`, "INVALID_MENU_ITEM_CHILDREN", errors, (entry, index) => {
52436
52450
  validatePreviewMenuItem(entry, `${path4}.children[${index}]`, errors);
52437
52451
  });
@@ -52637,7 +52651,9 @@ function validatePreviewPost(post, path4, errors, authorIds) {
52637
52651
  "author_id",
52638
52652
  "featured_image",
52639
52653
  "meta",
52654
+ "data",
52640
52655
  "status",
52656
+ "discoverability",
52641
52657
  "allow_comments",
52642
52658
  "category_slugs",
52643
52659
  "tag_slugs"
@@ -52655,6 +52671,9 @@ function validatePreviewPost(post, path4, errors, authorIds) {
52655
52671
  validateDateTimeString(post.updated_at_iso, `${path4}.updated_at_iso`, "INVALID_POST_UPDATED_AT_ISO", errors);
52656
52672
  validateNonEmptyString(post.author_id, `${path4}.author_id`, "INVALID_POST_AUTHOR_ID", errors);
52657
52673
  validateEnum(post.status, `${path4}.status`, "INVALID_POST_STATUS", errors, ["published", "draft"]);
52674
+ if (post.discoverability !== void 0) {
52675
+ validateEnum(post.discoverability, `${path4}.discoverability`, "INVALID_POST_DISCOVERABILITY", errors, PREVIEW_DISCOVERABILITY_VALUES);
52676
+ }
52658
52677
  validateBoolean(post.allow_comments, `${path4}.allow_comments`, "INVALID_POST_ALLOW_COMMENTS", errors);
52659
52678
  validateSlugArray(post.category_slugs, `${path4}.category_slugs`, "INVALID_POST_CATEGORY_SLUGS", errors);
52660
52679
  validateSlugArray(post.tag_slugs, `${path4}.tag_slugs`, "INVALID_POST_TAG_SLUGS", errors);
@@ -52662,12 +52681,13 @@ function validatePreviewPost(post, path4, errors, authorIds) {
52662
52681
  validateUrlLike(post.featured_image, `${path4}.featured_image`, "INVALID_POST_FEATURED_IMAGE", errors);
52663
52682
  }
52664
52683
  validatePreviewMeta(post.meta, `${path4}.meta`, errors);
52684
+ validatePreviewStructuredData(post.data, `${path4}.data`, errors);
52665
52685
  if (typeof post.author_id === "string" && post.author_id.trim() !== "" && !authorIds.has(post.author_id)) {
52666
52686
  errors.push(issue("INVALID_POST_AUTHOR_REFERENCE", `${path4}.author_id`, "Referenced author_id does not exist"));
52667
52687
  }
52668
52688
  }
52669
52689
  function validatePreviewPage(page, path4, errors) {
52670
- validateClosedObject(page, path4, errors, ["title", "slug", "path", "content", "document_type", "excerpt", "featured_image", "meta", "status"]);
52690
+ validateClosedObject(page, path4, errors, ["title", "slug", "path", "content", "document_type", "excerpt", "featured_image", "meta", "data", "status", "discoverability"]);
52671
52691
  if (!isObject(page)) {
52672
52692
  return;
52673
52693
  }
@@ -52683,7 +52703,11 @@ function validatePreviewPage(page, path4, errors) {
52683
52703
  validateUrlLike(page.featured_image, `${path4}.featured_image`, "INVALID_PAGE_FEATURED_IMAGE", errors);
52684
52704
  }
52685
52705
  validatePreviewMeta(page.meta, `${path4}.meta`, errors);
52706
+ validatePreviewStructuredData(page.data, `${path4}.data`, errors);
52686
52707
  validateEnum(page.status, `${path4}.status`, "INVALID_PAGE_STATUS", errors, ["published", "draft"]);
52708
+ if (page.discoverability !== void 0) {
52709
+ validateEnum(page.discoverability, `${path4}.discoverability`, "INVALID_PAGE_DISCOVERABILITY", errors, PREVIEW_DISCOVERABILITY_VALUES);
52710
+ }
52687
52711
  }
52688
52712
  function validatePreviewMeta(meta, path4, errors) {
52689
52713
  if (meta === void 0) {
@@ -52700,6 +52724,66 @@ function validatePreviewMeta(meta, path4, errors) {
52700
52724
  errors.push(issue("INVALID_META_VALUE", `${path4}.${key}`, "meta values must be strings, numbers, booleans, or null"));
52701
52725
  }
52702
52726
  }
52727
+ function validatePreviewStructuredData(data, path4, errors) {
52728
+ if (data === void 0) {
52729
+ return;
52730
+ }
52731
+ if (!isObject(data)) {
52732
+ errors.push(issue("INVALID_DATA", path4, "data must be an object"));
52733
+ return;
52734
+ }
52735
+ validatePreviewDataObject(data, path4, errors, 0);
52736
+ }
52737
+ function validatePreviewDataValue(value, path4, errors, depth) {
52738
+ if (value === null || typeof value === "string" || typeof value === "boolean") {
52739
+ return;
52740
+ }
52741
+ if (typeof value === "number") {
52742
+ if (!Number.isFinite(value)) {
52743
+ errors.push(issue("INVALID_DATA_VALUE", path4, "data numbers must be finite"));
52744
+ }
52745
+ return;
52746
+ }
52747
+ if (Array.isArray(value)) {
52748
+ validatePreviewDataArray(value, path4, errors, depth);
52749
+ return;
52750
+ }
52751
+ if (isObject(value)) {
52752
+ validatePreviewDataObject(value, path4, errors, depth);
52753
+ return;
52754
+ }
52755
+ errors.push(issue("INVALID_DATA_VALUE", path4, "data values must be JSON-safe strings, numbers, booleans, null, arrays, or objects"));
52756
+ }
52757
+ function validatePreviewDataObject(object, path4, errors, depth) {
52758
+ if (depth > PREVIEW_DATA_MAX_DEPTH) {
52759
+ errors.push(issue("INVALID_DATA_DEPTH", path4, `data nesting must not exceed ${PREVIEW_DATA_MAX_DEPTH} container levels`));
52760
+ return;
52761
+ }
52762
+ const entries = Object.entries(object);
52763
+ if (entries.length > PREVIEW_DATA_MAX_KEYS) {
52764
+ errors.push(issue("INVALID_DATA_OBJECT_SIZE", path4, `data objects must not contain more than ${PREVIEW_DATA_MAX_KEYS} keys`));
52765
+ }
52766
+ for (const [key, value] of entries) {
52767
+ const childPath = `${path4}.${key}`;
52768
+ if (!PREVIEW_DATA_KEY_PATTERN.test(key)) {
52769
+ errors.push(issue("INVALID_DATA_KEY", childPath, "data keys must be valid template path segments"));
52770
+ continue;
52771
+ }
52772
+ validatePreviewDataValue(value, childPath, errors, depth + 1);
52773
+ }
52774
+ }
52775
+ function validatePreviewDataArray(array, path4, errors, depth) {
52776
+ if (depth > PREVIEW_DATA_MAX_DEPTH) {
52777
+ errors.push(issue("INVALID_DATA_DEPTH", path4, `data nesting must not exceed ${PREVIEW_DATA_MAX_DEPTH} container levels`));
52778
+ return;
52779
+ }
52780
+ if (array.length > PREVIEW_DATA_MAX_ARRAY_LENGTH) {
52781
+ errors.push(issue("INVALID_DATA_ARRAY_SIZE", path4, `data arrays must not contain more than ${PREVIEW_DATA_MAX_ARRAY_LENGTH} items`));
52782
+ }
52783
+ array.forEach((value, index) => {
52784
+ validatePreviewDataValue(value, `${path4}[${index}]`, errors, depth + 1);
52785
+ });
52786
+ }
52703
52787
  function validatePermalinks(permalinks, path4, errors) {
52704
52788
  if (permalinks === void 0) {
52705
52789
  return;
@@ -52930,7 +53014,7 @@ function isOptionalKey(path4, key) {
52930
53014
  return key === "head_end" || key === "body_end";
52931
53015
  }
52932
53016
  if (path4 === "site") {
52933
- return key === "mediaDeliveryMode" || key === "favicon" || 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";
52934
53018
  }
52935
53019
  if (path4 === "site.favicon") {
52936
53020
  return key === "icon" || key === "svg" || key === "png" || key === "apple_touch_icon";
@@ -52938,9 +53022,6 @@ function isOptionalKey(path4, key) {
52938
53022
  if (path4 === "site.footer") {
52939
53023
  return key === "copyright_text" || key === "attribution";
52940
53024
  }
52941
- if (path4 === "site.footer.attribution") {
52942
- return key === "enabled";
52943
- }
52944
53025
  if (path4 === "site.front_page") {
52945
53026
  return key === "page_slug" || key === "html";
52946
53027
  }
@@ -52959,6 +53040,9 @@ function isOptionalKey(path4, key) {
52959
53040
  if (path4.startsWith("content.media[")) {
52960
53041
  return key === "alt";
52961
53042
  }
53043
+ if (path4.startsWith("menus.") && (path4.includes(".items[") || path4.includes(".children["))) {
53044
+ return key === "meta";
53045
+ }
52962
53046
  if (path4.startsWith("widgets.") && path4.includes(".items[")) {
52963
53047
  return key === "settings";
52964
53048
  }
@@ -52966,10 +53050,10 @@ function isOptionalKey(path4, key) {
52966
53050
  return key === "title" || key === "description";
52967
53051
  }
52968
53052
  if (path4.startsWith("content.posts[")) {
52969
- return key === "id" || key === "featured_image" || key === "meta";
53053
+ return key === "id" || key === "featured_image" || key === "meta" || key === "data" || key === "discoverability";
52970
53054
  }
52971
53055
  if (path4.startsWith("content.pages[")) {
52972
- return key === "path" || key === "excerpt" || key === "featured_image" || key === "meta";
53056
+ return key === "path" || key === "excerpt" || key === "featured_image" || key === "meta" || key === "data" || key === "discoverability";
52973
53057
  }
52974
53058
  if (path4.startsWith("content.categories[") || path4.startsWith("content.tags[")) {
52975
53059
  return key === "description";
@@ -53014,7 +53098,7 @@ function mapSlugValidationMessage(issueCode) {
53014
53098
  function rejectLegacyKeys(value, path4, errors, keys, code2) {
53015
53099
  for (const key of keys) {
53016
53100
  if (key in value) {
53017
- errors.push(issue(code2, `${path4}.${key}`, "Legacy field is not allowed in preview-data v0.5"));
53101
+ errors.push(issue(code2, `${path4}.${key}`, "Legacy field is not allowed in preview-data v0.6"));
53018
53102
  }
53019
53103
  }
53020
53104
  }
@@ -53124,9 +53208,46 @@ var PARTIAL_TAG_REGEX = /\{\{(partial:[^}]+)\}\}/g;
53124
53208
  var TEMPLATE_PATH_SEGMENT_SOURCE = "[a-zA-Z_][a-zA-Z0-9_]*(?:-[a-zA-Z0-9_]+)*";
53125
53209
  var TEMPLATE_PATH_REGEX = new RegExp(`^${TEMPLATE_PATH_SEGMENT_SOURCE}(?:\\.${TEMPLATE_PATH_SEGMENT_SOURCE})*$`);
53126
53210
  var FOR_TAG_REGEX = new RegExp(`^#for ([a-zA-Z_][a-zA-Z0-9_]*) in (${TEMPLATE_PATH_SEGMENT_SOURCE}(?:\\.${TEMPLATE_PATH_SEGMENT_SOURCE})*)$`);
53127
- var IF_EQ_EXPRESSION_REGEX = new RegExp(`^(${TEMPLATE_PATH_SEGMENT_SOURCE}(?:\\.${TEMPLATE_PATH_SEGMENT_SOURCE})*)\\s+("(?:[^"\\\\]|\\\\.)*")$`);
53211
+ var NUMBER_LITERAL_REGEX = /^-?(?:0|[1-9]\d*)(?:\.\d+)?$/;
53212
+ var COMPARISON_BLOCK_TAGS = /* @__PURE__ */ new Set(["if_eq", "if_neq", "if_in", "if_starts_with"]);
53213
+ var COMPARISON_ELSE_IF_TAGS = /* @__PURE__ */ new Set(["else_if_eq", "else_if_neq", "else_if_in", "else_if_starts_with"]);
53214
+ var COMPARISON_TAG_OPERATORS = {
53215
+ if_eq: "eq",
53216
+ else_if_eq: "eq",
53217
+ if_neq: "neq",
53218
+ else_if_neq: "neq",
53219
+ if_in: "in",
53220
+ else_if_in: "in",
53221
+ if_starts_with: "starts_with",
53222
+ else_if_starts_with: "starts_with"
53223
+ };
53224
+ var PARTIAL_ARG_ROOT_PATHS = /* @__PURE__ */ new Set([
53225
+ "archive",
53226
+ "author",
53227
+ "category",
53228
+ "collection",
53229
+ "collections",
53230
+ "currentUrl",
53231
+ "language",
53232
+ "menu",
53233
+ "menus",
53234
+ "meta",
53235
+ "page",
53236
+ "pagination",
53237
+ "partial",
53238
+ "post",
53239
+ "posts",
53240
+ "route",
53241
+ "site",
53242
+ "tag",
53243
+ "taxonomies",
53244
+ "taxonomy",
53245
+ "widget",
53246
+ "widgets"
53247
+ ]);
53128
53248
  var PARTIAL_ARG_KEY_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
53129
53249
  var SEMVER_REGEX = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
53250
+ var LICENSE_REF_REGEX = /^LicenseRef-[A-Za-z0-9][A-Za-z0-9.-]*$/;
53130
53251
  var NAMESPACE_REGEX = /^[a-z0-9]+(-[a-z0-9]+)*$/;
53131
53252
  var SLUG_REGEX = /^[a-z0-9]+(-[a-z0-9]+)*$/;
53132
53253
  var ALLOWED_LICENSES = [
@@ -53137,7 +53258,10 @@ var ALLOWED_LICENSES = [
53137
53258
  "GPL-3.0-or-later"
53138
53259
  ];
53139
53260
  var LICENSES = new Set(ALLOWED_LICENSES);
53140
- var DEFAULT_RUNTIME = "0.5";
53261
+ var THEME_LINK_KEYS = /* @__PURE__ */ new Set(["homepage", "repository", "documentation", "support", "marketplace", "license"]);
53262
+ var THEME_LINK_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:"]);
53263
+ var DEFAULT_RUNTIME = "0.6";
53264
+ var THEME_RUNTIME_V0_6 = DEFAULT_RUNTIME;
53141
53265
  var SUPPORTED_RUNTIMES = [DEFAULT_RUNTIME];
53142
53266
  var SUPPORTED_RUNTIME_SET = new Set(SUPPORTED_RUNTIMES);
53143
53267
  var NAMESPACE_MIN_LENGTH = 3;
@@ -53152,7 +53276,7 @@ var MENU_SLOT_ID_MAX_LENGTH = 32;
53152
53276
  var MENU_SLOT_COUNT_MAX = 12;
53153
53277
  var MENU_SLOT_TITLE_MAX_LENGTH = 80;
53154
53278
  var MENU_SLOT_DESCRIPTION_MAX_LENGTH = 160;
53155
- var SUPPORTED_THEME_FEATURES = /* @__PURE__ */ new Set(["comments", "newsletter", "postIndex"]);
53279
+ var SUPPORTED_THEME_FEATURES = /* @__PURE__ */ new Set(["comments", "newsletter", "post_index", "search"]);
53156
53280
  var THEME_MANIFEST_KEYS = /* @__PURE__ */ new Set([
53157
53281
  "$schema",
53158
53282
  "name",
@@ -53164,11 +53288,12 @@ var THEME_MANIFEST_KEYS = /* @__PURE__ */ new Set([
53164
53288
  "author",
53165
53289
  "description",
53166
53290
  "thumbnail",
53291
+ "links",
53167
53292
  "features",
53168
- "menuSlots",
53169
- "widgetAreas",
53170
- "siteMeta",
53171
- "collectionSlots"
53293
+ "menu_slots",
53294
+ "widget_areas",
53295
+ "site_meta",
53296
+ "collection_slots"
53172
53297
  ]);
53173
53298
  var SITE_META_KEY_REGEX = /^[a-z][a-z0-9_]*(?:-[a-z0-9_]+)*$/;
53174
53299
  var SITE_META_KEY_MAX_LENGTH = 64;
@@ -53194,7 +53319,11 @@ function validateFeatureFlags(rawValue, errors) {
53194
53319
  "INVALID_THEME_FEATURE",
53195
53320
  `theme.json.features.${featureName}`,
53196
53321
  `Unknown theme feature '${featureName}'`,
53197
- "error"
53322
+ "error",
53323
+ {
53324
+ category: "theme_manifest",
53325
+ hint: themeFeatureHint(featureName)
53326
+ }
53198
53327
  ));
53199
53328
  continue;
53200
53329
  }
@@ -53211,6 +53340,80 @@ function validateFeatureFlags(rawValue, errors) {
53211
53340
  }
53212
53341
  return Object.keys(normalizedFeatures).length > 0 ? normalizedFeatures : void 0;
53213
53342
  }
53343
+ function themeFeatureHint(featureName) {
53344
+ const hints = {
53345
+ postIndex: "ZeroPress runtime v0.6 uses snake_case keys. Use 'post_index' instead of 'postIndex'."
53346
+ };
53347
+ return hints[featureName] || "";
53348
+ }
53349
+ function themeManifestFieldHint(fieldName) {
53350
+ const hints = {
53351
+ menuSlots: "ZeroPress runtime v0.6 uses snake_case keys. Use 'menu_slots' instead of 'menuSlots'.",
53352
+ widgetAreas: "ZeroPress runtime v0.6 uses snake_case keys. Use 'widget_areas' instead of 'widgetAreas'.",
53353
+ siteMeta: "ZeroPress runtime v0.6 uses snake_case keys. Use 'site_meta' instead of 'siteMeta'.",
53354
+ collectionSlots: "ZeroPress runtime v0.6 uses snake_case keys. Use 'collection_slots' instead of 'collectionSlots'.",
53355
+ settings: "theme.json.settings was removed in runtime v0.6. Use preview-data site.meta and theme.json site_meta hints for site-level values."
53356
+ };
53357
+ return hints[fieldName] || "";
53358
+ }
53359
+ function isAllowedLicense(value) {
53360
+ return LICENSES.has(value) || LICENSE_REF_REGEX.test(value);
53361
+ }
53362
+ function isValidThemeLinkUrl(value) {
53363
+ try {
53364
+ const parsed = new URL(value);
53365
+ return THEME_LINK_PROTOCOLS.has(parsed.protocol);
53366
+ } catch {
53367
+ return false;
53368
+ }
53369
+ }
53370
+ function validateThemeLinks(rawValue, errors) {
53371
+ if (rawValue === void 0) {
53372
+ return void 0;
53373
+ }
53374
+ if (!rawValue || typeof rawValue !== "object" || Array.isArray(rawValue)) {
53375
+ errors.push(issue2(
53376
+ "INVALID_LINKS",
53377
+ "theme.json.links",
53378
+ "theme.json field 'links' must be an object when present",
53379
+ "error"
53380
+ ));
53381
+ return void 0;
53382
+ }
53383
+ const normalizedLinks = {};
53384
+ for (const [linkKey, value] of Object.entries(rawValue)) {
53385
+ if (!THEME_LINK_KEYS.has(linkKey)) {
53386
+ errors.push(issue2(
53387
+ "INVALID_THEME_LINK",
53388
+ `theme.json.links.${linkKey}`,
53389
+ `Unknown theme link '${linkKey}'`,
53390
+ "error"
53391
+ ));
53392
+ continue;
53393
+ }
53394
+ if (typeof value !== "string" || value.trim() === "") {
53395
+ errors.push(issue2(
53396
+ "INVALID_THEME_LINK_VALUE",
53397
+ `theme.json.links.${linkKey}`,
53398
+ `theme link '${linkKey}' must be a non-empty URL string`,
53399
+ "error"
53400
+ ));
53401
+ continue;
53402
+ }
53403
+ const normalizedValue = value.trim();
53404
+ if (!isValidThemeLinkUrl(normalizedValue)) {
53405
+ errors.push(issue2(
53406
+ "INVALID_THEME_LINK_VALUE",
53407
+ `theme.json.links.${linkKey}`,
53408
+ `theme link '${linkKey}' must be an absolute http, https, or mailto URL`,
53409
+ "error"
53410
+ ));
53411
+ continue;
53412
+ }
53413
+ normalizedLinks[linkKey] = normalizedValue;
53414
+ }
53415
+ return Object.keys(normalizedLinks).length > 0 ? normalizedLinks : void 0;
53416
+ }
53214
53417
  function validateHelperMetadataMap(rawValue, fieldName, issueCodes, errors) {
53215
53418
  if (rawValue === void 0) {
53216
53419
  return void 0;
@@ -53312,7 +53515,7 @@ function validateSiteMetaMap(rawValue, errors) {
53312
53515
  errors.push(issue2(
53313
53516
  "INVALID_SITE_META",
53314
53517
  "theme.json",
53315
- "theme.json field 'siteMeta' must be an object when present",
53518
+ "theme.json field 'site_meta' must be an object when present",
53316
53519
  "error"
53317
53520
  ));
53318
53521
  return void 0;
@@ -53322,7 +53525,7 @@ function validateSiteMetaMap(rawValue, errors) {
53322
53525
  errors.push(issue2(
53323
53526
  "INVALID_SITE_META",
53324
53527
  "theme.json",
53325
- "theme.json field 'siteMeta' must not be empty",
53528
+ "theme.json field 'site_meta' must not be empty",
53326
53529
  "error"
53327
53530
  ));
53328
53531
  }
@@ -53330,7 +53533,7 @@ function validateSiteMetaMap(rawValue, errors) {
53330
53533
  errors.push(issue2(
53331
53534
  "INVALID_SITE_META",
53332
53535
  "theme.json",
53333
- `theme.json field 'siteMeta' must contain at most ${SITE_META_COUNT_MAX} keys`,
53536
+ `theme.json field 'site_meta' must contain at most ${SITE_META_COUNT_MAX} keys`,
53334
53537
  "error"
53335
53538
  ));
53336
53539
  }
@@ -53339,8 +53542,8 @@ function validateSiteMetaMap(rawValue, errors) {
53339
53542
  if (!SITE_META_KEY_REGEX.test(metaKey) || metaKey.length > SITE_META_KEY_MAX_LENGTH) {
53340
53543
  errors.push(issue2(
53341
53544
  "INVALID_SITE_META_KEY",
53342
- `theme.json.siteMeta.${metaKey}`,
53343
- `siteMeta key '${metaKey}' must start with a lowercase letter and use lowercase letters, digits, underscores, or internal hyphens only`,
53545
+ `theme.json.site_meta.${metaKey}`,
53546
+ `site_meta key '${metaKey}' must start with a lowercase letter and use lowercase letters, digits, underscores, or internal hyphens only`,
53344
53547
  "error"
53345
53548
  ));
53346
53549
  continue;
@@ -53348,8 +53551,8 @@ function validateSiteMetaMap(rawValue, errors) {
53348
53551
  if (!value || typeof value !== "object" || Array.isArray(value)) {
53349
53552
  errors.push(issue2(
53350
53553
  "INVALID_SITE_META_FIELD",
53351
- `theme.json.siteMeta.${metaKey}`,
53352
- `siteMeta key '${metaKey}' must be an object`,
53554
+ `theme.json.site_meta.${metaKey}`,
53555
+ `site_meta key '${metaKey}' must be an object`,
53353
53556
  "error"
53354
53557
  ));
53355
53558
  continue;
@@ -53359,8 +53562,8 @@ function validateSiteMetaMap(rawValue, errors) {
53359
53562
  if (!allowedKeys.has(key)) {
53360
53563
  errors.push(issue2(
53361
53564
  "INVALID_SITE_META_PROPERTY",
53362
- `theme.json.siteMeta.${metaKey}.${key}`,
53363
- `Unknown siteMeta property '${key}' in key '${metaKey}'`,
53565
+ `theme.json.site_meta.${metaKey}.${key}`,
53566
+ `Unknown site_meta property '${key}' in key '${metaKey}'`,
53364
53567
  "error"
53365
53568
  ));
53366
53569
  }
@@ -53368,31 +53571,31 @@ function validateSiteMetaMap(rawValue, errors) {
53368
53571
  if (typeof value.title !== "string" || value.title.trim() === "") {
53369
53572
  errors.push(issue2(
53370
53573
  "INVALID_SITE_META_TITLE",
53371
- `theme.json.siteMeta.${metaKey}.title`,
53372
- `siteMeta key '${metaKey}' must define a non-empty 'title'`,
53574
+ `theme.json.site_meta.${metaKey}.title`,
53575
+ `site_meta key '${metaKey}' must define a non-empty 'title'`,
53373
53576
  "error"
53374
53577
  ));
53375
53578
  } else if (value.title.trim().length > MENU_SLOT_TITLE_MAX_LENGTH) {
53376
53579
  errors.push(issue2(
53377
53580
  "INVALID_SITE_META_TITLE",
53378
- `theme.json.siteMeta.${metaKey}.title`,
53379
- `siteMeta key '${metaKey}' title must be at most ${MENU_SLOT_TITLE_MAX_LENGTH} characters`,
53581
+ `theme.json.site_meta.${metaKey}.title`,
53582
+ `site_meta key '${metaKey}' title must be at most ${MENU_SLOT_TITLE_MAX_LENGTH} characters`,
53380
53583
  "error"
53381
53584
  ));
53382
53585
  }
53383
53586
  if (value.description !== void 0 && (typeof value.description !== "string" || value.description.trim().length > MENU_SLOT_DESCRIPTION_MAX_LENGTH)) {
53384
53587
  errors.push(issue2(
53385
53588
  "INVALID_SITE_META_DESCRIPTION",
53386
- `theme.json.siteMeta.${metaKey}.description`,
53387
- `siteMeta key '${metaKey}' description must be a string at most ${MENU_SLOT_DESCRIPTION_MAX_LENGTH} characters`,
53589
+ `theme.json.site_meta.${metaKey}.description`,
53590
+ `site_meta key '${metaKey}' description must be a string at most ${MENU_SLOT_DESCRIPTION_MAX_LENGTH} characters`,
53388
53591
  "error"
53389
53592
  ));
53390
53593
  }
53391
53594
  if (value.type !== void 0 && !SITE_META_TYPES.has(value.type)) {
53392
53595
  errors.push(issue2(
53393
53596
  "INVALID_SITE_META_TYPE",
53394
- `theme.json.siteMeta.${metaKey}.type`,
53395
- `siteMeta key '${metaKey}' type must be one of: string, number, boolean`,
53597
+ `theme.json.site_meta.${metaKey}.type`,
53598
+ `site_meta key '${metaKey}' type must be one of: string, number, boolean`,
53396
53599
  "error"
53397
53600
  ));
53398
53601
  }
@@ -53401,15 +53604,15 @@ function validateSiteMetaMap(rawValue, errors) {
53401
53604
  if (value.default !== null && defaultType !== "string" && defaultType !== "number" && defaultType !== "boolean") {
53402
53605
  errors.push(issue2(
53403
53606
  "INVALID_SITE_META_DEFAULT",
53404
- `theme.json.siteMeta.${metaKey}.default`,
53405
- `siteMeta key '${metaKey}' default must be a string, number, boolean, or null`,
53607
+ `theme.json.site_meta.${metaKey}.default`,
53608
+ `site_meta key '${metaKey}' default must be a string, number, boolean, or null`,
53406
53609
  "error"
53407
53610
  ));
53408
53611
  } else if (typeof value.type === "string" && SITE_META_TYPES.has(value.type) && value.default !== null && defaultType !== value.type) {
53409
53612
  errors.push(issue2(
53410
53613
  "INVALID_SITE_META_DEFAULT",
53411
- `theme.json.siteMeta.${metaKey}.default`,
53412
- `siteMeta key '${metaKey}' default must match its declared type`,
53614
+ `theme.json.site_meta.${metaKey}.default`,
53615
+ `site_meta key '${metaKey}' default must match its declared type`,
53413
53616
  "error"
53414
53617
  ));
53415
53618
  }
@@ -53454,11 +53657,16 @@ async function validateThemeFiles(fileMap, options2 = {}) {
53454
53657
  manifest = manifestResult.manifest;
53455
53658
  errors.push(...manifestResult.errors);
53456
53659
  } catch (error) {
53660
+ const rawThemeJson = getText(files.get("theme.json"));
53457
53661
  errors.push(issue2(
53458
53662
  "INVALID_THEME_JSON",
53459
53663
  "theme.json",
53460
53664
  `Invalid theme.json: ${error instanceof Error ? error.message : String(error)}`,
53461
- "error"
53665
+ "error",
53666
+ {
53667
+ ...locationForJsonParseError(error, rawThemeJson),
53668
+ category: "json_syntax"
53669
+ }
53462
53670
  ));
53463
53671
  }
53464
53672
  }
@@ -53524,7 +53732,11 @@ function validateManifest(themeJson) {
53524
53732
  "UNKNOWN_THEME_MANIFEST_FIELD",
53525
53733
  `theme.json.${key}`,
53526
53734
  `Unknown theme.json field '${key}'`,
53527
- "error"
53735
+ "error",
53736
+ {
53737
+ category: "theme_manifest",
53738
+ hint: themeManifestFieldHint(key)
53739
+ }
53528
53740
  ));
53529
53741
  }
53530
53742
  }
@@ -53555,13 +53767,24 @@ function validateManifest(themeJson) {
53555
53767
  errors.push(issue2("INVALID_SEMVER", "theme.json", "Theme version must follow semantic versioning (e.g. 1.0.0)", "error"));
53556
53768
  }
53557
53769
  if (typeof themeJson.runtime === "string" && !SUPPORTED_RUNTIME_SET.has(themeJson.runtime.trim())) {
53558
- errors.push(issue2("INVALID_RUNTIME_VERSION", "theme.json", `theme.json field 'runtime' must be one of: ${SUPPORTED_RUNTIMES.join(", ")}`, "error"));
53770
+ errors.push(issue2(
53771
+ "INVALID_RUNTIME_VERSION",
53772
+ "theme.json",
53773
+ `theme.json field 'runtime' must be one of: ${SUPPORTED_RUNTIMES.join(", ")}`,
53774
+ "error",
53775
+ {
53776
+ category: "theme_manifest",
53777
+ hint: `Update theme.json:
53778
+
53779
+ "runtime": "${THEME_RUNTIME_V0_6}"`
53780
+ }
53781
+ ));
53559
53782
  }
53560
- if (typeof themeJson.license === "string" && !LICENSES.has(themeJson.license.trim())) {
53783
+ if (typeof themeJson.license === "string" && !isAllowedLicense(themeJson.license.trim())) {
53561
53784
  errors.push(issue2(
53562
53785
  "INVALID_LICENSE",
53563
53786
  "theme.json",
53564
- "theme.json field 'license' must be one of: MIT, Apache-2.0, BSD-3-Clause, GPL-3.0-only, GPL-3.0-or-later",
53787
+ "theme.json field 'license' must be one of: MIT, Apache-2.0, BSD-3-Clause, GPL-3.0-only, GPL-3.0-or-later, or a LicenseRef-* identifier",
53565
53788
  "error"
53566
53789
  ));
53567
53790
  }
@@ -53625,11 +53848,15 @@ function validateManifest(themeJson) {
53625
53848
  manifest.thumbnail = themeJson.thumbnail;
53626
53849
  }
53627
53850
  }
53851
+ const links = validateThemeLinks(themeJson.links, errors);
53852
+ if (links) {
53853
+ manifest.links = links;
53854
+ }
53628
53855
  const features = validateFeatureFlags(themeJson.features, errors);
53629
53856
  if (features) {
53630
53857
  manifest.features = features;
53631
53858
  }
53632
- const menuSlots = validateHelperMetadataMap(themeJson.menuSlots, "menuSlots", {
53859
+ const menu_slots = validateHelperMetadataMap(themeJson.menu_slots, "menu_slots", {
53633
53860
  itemLabel: "Menu slot",
53634
53861
  propertyLabel: "menu slot property",
53635
53862
  collectionLabel: "slots",
@@ -53640,10 +53867,10 @@ function validateManifest(themeJson) {
53640
53867
  invalidTitleCode: "INVALID_MENU_SLOT_TITLE",
53641
53868
  invalidDescriptionCode: "INVALID_MENU_SLOT_DESCRIPTION"
53642
53869
  }, errors);
53643
- if (menuSlots) {
53644
- manifest.menuSlots = menuSlots;
53870
+ if (menu_slots) {
53871
+ manifest.menu_slots = menu_slots;
53645
53872
  }
53646
- const widgetAreas = validateHelperMetadataMap(themeJson.widgetAreas, "widgetAreas", {
53873
+ const widget_areas = validateHelperMetadataMap(themeJson.widget_areas, "widget_areas", {
53647
53874
  itemLabel: "Widget area",
53648
53875
  propertyLabel: "widget area property",
53649
53876
  collectionLabel: "areas",
@@ -53654,14 +53881,14 @@ function validateManifest(themeJson) {
53654
53881
  invalidTitleCode: "INVALID_WIDGET_AREA_TITLE",
53655
53882
  invalidDescriptionCode: "INVALID_WIDGET_AREA_DESCRIPTION"
53656
53883
  }, errors);
53657
- if (widgetAreas) {
53658
- manifest.widgetAreas = widgetAreas;
53884
+ if (widget_areas) {
53885
+ manifest.widget_areas = widget_areas;
53659
53886
  }
53660
- const siteMeta = validateSiteMetaMap(themeJson.siteMeta, errors);
53661
- if (siteMeta) {
53662
- manifest.siteMeta = siteMeta;
53887
+ const site_meta = validateSiteMetaMap(themeJson.site_meta, errors);
53888
+ if (site_meta) {
53889
+ manifest.site_meta = site_meta;
53663
53890
  }
53664
- const collectionSlots = validateHelperMetadataMap(themeJson.collectionSlots, "collectionSlots", {
53891
+ const collection_slots = validateHelperMetadataMap(themeJson.collection_slots, "collection_slots", {
53665
53892
  itemLabel: "Collection slot",
53666
53893
  propertyLabel: "collection slot property",
53667
53894
  collectionLabel: "slots",
@@ -53672,8 +53899,8 @@ function validateManifest(themeJson) {
53672
53899
  invalidTitleCode: "INVALID_COLLECTION_SLOT_TITLE",
53673
53900
  invalidDescriptionCode: "INVALID_COLLECTION_SLOT_DESCRIPTION"
53674
53901
  }, errors);
53675
- if (collectionSlots) {
53676
- manifest.collectionSlots = collectionSlots;
53902
+ if (collection_slots) {
53903
+ manifest.collection_slots = collection_slots;
53677
53904
  }
53678
53905
  return { errors, manifest: errors.length > 0 ? void 0 : manifest };
53679
53906
  }
@@ -53685,8 +53912,20 @@ function validateTemplateSyntax(templatePath, content, context) {
53685
53912
  if (contentSlotMatches.length !== 1) {
53686
53913
  errors.push(issue2("INVALID_LAYOUT_SLOT", "layout.html", "layout.html must contain exactly one {{slot:content}}", "error"));
53687
53914
  }
53688
- if (/<script\b/i.test(content)) {
53689
- errors.push(issue2("LAYOUT_SCRIPT_NOT_ALLOWED", "layout.html", "layout.html must not contain <script> tags", "error"));
53915
+ const scriptMatch = /<script\b/i.exec(content);
53916
+ if (scriptMatch) {
53917
+ errors.push(issue2(
53918
+ "LAYOUT_SCRIPT_NOT_ALLOWED",
53919
+ "layout.html",
53920
+ "layout.html must not contain <script> tags",
53921
+ "error",
53922
+ {
53923
+ ...locationForIndex(content, scriptMatch.index),
53924
+ category: "theme_validation",
53925
+ hint: "Move shared scripts into a partial such as {{partial:content-enhancements}}, then include that partial from layout.html.",
53926
+ snippet: snippetForIndex(content, scriptMatch.index)
53927
+ }
53928
+ ));
53690
53929
  }
53691
53930
  }
53692
53931
  let match2;
@@ -53737,7 +53976,9 @@ function validateRuntimeV05TemplateSyntax(templatePath, content, errors) {
53737
53976
  index = end + 2;
53738
53977
  if (token.startsWith("partial:")) {
53739
53978
  try {
53740
- parsePartialReferenceToken(token);
53979
+ parsePartialReferenceToken(token, {
53980
+ allowedSingleSegmentPaths: getPartialArgScope(stack)
53981
+ });
53741
53982
  } catch (error) {
53742
53983
  errors.push(issue2(
53743
53984
  "INVALID_PARTIAL_REFERENCE",
@@ -53762,7 +54003,7 @@ function validateRuntimeV05TemplateSyntax(templatePath, content, errors) {
53762
54003
  }
53763
54004
  if (token === "#else") {
53764
54005
  const current = stack[stack.length - 1];
53765
- if (!current || current.tag !== "if" && current.tag !== "if_eq") {
54006
+ if (!current || current.tag !== "if" && !COMPARISON_BLOCK_TAGS.has(current.tag)) {
53766
54007
  errors.push(issue2("UNEXPECTED_TEMPLATE_ELSE", templatePath, `Unexpected {{#else}} in ${templatePath}`, "error"));
53767
54008
  return;
53768
54009
  }
@@ -53773,9 +54014,11 @@ function validateRuntimeV05TemplateSyntax(templatePath, content, errors) {
53773
54014
  current.hasElse = true;
53774
54015
  continue;
53775
54016
  }
53776
- if (token.startsWith("#else_if_eq ")) {
54017
+ const comparisonElseIfTag = getComparisonElseIfTag(token);
54018
+ if (comparisonElseIfTag) {
53777
54019
  const current = stack[stack.length - 1];
53778
- if (!current || current.tag !== "if_eq") {
54020
+ const expectedBlockTag = comparisonElseIfTag.replace(/^else_/, "");
54021
+ if (!current || current.tag !== expectedBlockTag) {
53779
54022
  errors.push(issue2("UNEXPECTED_TEMPLATE_ELSE_IF", templatePath, `Unexpected {{${token}}} in ${templatePath}`, "error"));
53780
54023
  return;
53781
54024
  }
@@ -53783,13 +54026,13 @@ function validateRuntimeV05TemplateSyntax(templatePath, content, errors) {
53783
54026
  errors.push(issue2("INVALID_TEMPLATE_BRANCH_ORDER", templatePath, `{{${token}}} cannot appear after {{#else}} in ${templatePath}`, "error"));
53784
54027
  return;
53785
54028
  }
53786
- const expression = token.slice("#else_if_eq ".length).trim();
53787
- const parsed = parseIfEqExpression(expression);
54029
+ const expression = token.slice(`#${comparisonElseIfTag} `.length).trim();
54030
+ const parsed = parseComparisonExpression(expression, comparisonElseIfTag);
53788
54031
  if (!parsed) {
53789
54032
  errors.push(issue2("UNSUPPORTED_TEMPLATE_TAG", templatePath, `Unsupported template tag '{{${token}}}' in ${templatePath}`, "error"));
53790
54033
  return;
53791
54034
  }
53792
- validateReservedPathUsage(parsed.path, templatePath, errors, stack, { isPartialFile });
54035
+ validateComparisonPathUsage(parsed, templatePath, errors, stack, { isPartialFile });
53793
54036
  continue;
53794
54037
  }
53795
54038
  if (token.startsWith("#else_if ")) {
@@ -53812,7 +54055,7 @@ function validateRuntimeV05TemplateSyntax(templatePath, content, errors) {
53812
54055
  }
53813
54056
  if (token.startsWith("/")) {
53814
54057
  const closingTag = token.slice(1).trim();
53815
- if (!["if", "if_eq", "for"].includes(closingTag)) {
54058
+ if (!["if", "for", ...COMPARISON_BLOCK_TAGS].includes(closingTag)) {
53816
54059
  errors.push(issue2("UNSUPPORTED_TEMPLATE_TAG", templatePath, `Unsupported template closing tag '{{${token}}}' in ${templatePath}`, "error"));
53817
54060
  return;
53818
54061
  }
@@ -53833,22 +54076,23 @@ function validateRuntimeV05TemplateSyntax(templatePath, content, errors) {
53833
54076
  stack.push({ tag: "if", hasElse: false });
53834
54077
  continue;
53835
54078
  }
53836
- if (token.startsWith("#if_eq ")) {
53837
- const expression = token.slice("#if_eq ".length).trim();
53838
- const parsed = parseIfEqExpression(expression);
54079
+ const comparisonBlockTag = getComparisonBlockTag(token);
54080
+ if (comparisonBlockTag) {
54081
+ const expression = token.slice(`#${comparisonBlockTag} `.length).trim();
54082
+ const parsed = parseComparisonExpression(expression, comparisonBlockTag);
53839
54083
  if (!parsed) {
53840
54084
  errors.push(issue2("UNSUPPORTED_TEMPLATE_TAG", templatePath, `Unsupported template tag '{{${token}}}' in ${templatePath}`, "error"));
53841
54085
  return;
53842
54086
  }
53843
- validateReservedPathUsage(parsed.path, templatePath, errors, stack, { isPartialFile });
53844
- stack.push({ tag: "if_eq", hasElse: false });
54087
+ validateComparisonPathUsage(parsed, templatePath, errors, stack, { isPartialFile });
54088
+ stack.push({ tag: comparisonBlockTag, hasElse: false });
53845
54089
  continue;
53846
54090
  }
53847
54091
  const forMatch = FOR_TAG_REGEX.exec(token);
53848
54092
  if (forMatch) {
53849
54093
  const path4 = forMatch[2];
53850
54094
  validateReservedPathUsage(path4, templatePath, errors, stack, { isPartialFile });
53851
- stack.push({ tag: "for", hasElse: false });
54095
+ stack.push({ tag: "for", itemName: forMatch[1], hasElse: false });
53852
54096
  continue;
53853
54097
  }
53854
54098
  errors.push(issue2("UNSUPPORTED_TEMPLATE_TAG", templatePath, `Unsupported template tag '{{${token}}}' in ${templatePath}`, "error"));
@@ -53881,16 +54125,124 @@ function validateReservedPathUsage(path4, templatePath, errors, stack, options2
53881
54125
  ));
53882
54126
  }
53883
54127
  }
53884
- function parseIfEqExpression(expression) {
53885
- const match2 = IF_EQ_EXPRESSION_REGEX.exec(expression);
53886
- if (!match2) {
54128
+ function validateComparisonPathUsage(parsed, templatePath, errors, stack, options2) {
54129
+ for (const operand of [parsed.left, ...parsed.operands]) {
54130
+ if (operand.kind === "path") {
54131
+ validateReservedPathUsage(operand.path, templatePath, errors, stack, options2);
54132
+ }
54133
+ }
54134
+ }
54135
+ function getComparisonBlockTag(token) {
54136
+ for (const tagName of COMPARISON_BLOCK_TAGS) {
54137
+ if (token.startsWith(`#${tagName} `)) {
54138
+ return tagName;
54139
+ }
54140
+ }
54141
+ return "";
54142
+ }
54143
+ function getComparisonElseIfTag(token) {
54144
+ for (const tagName of COMPARISON_ELSE_IF_TAGS) {
54145
+ if (token.startsWith(`#${tagName} `)) {
54146
+ return tagName;
54147
+ }
54148
+ }
54149
+ return "";
54150
+ }
54151
+ function parseComparisonExpression(expression, tagName) {
54152
+ const operator = COMPARISON_TAG_OPERATORS[tagName];
54153
+ let tokens;
54154
+ try {
54155
+ tokens = tokenizeExpression(expression);
54156
+ } catch {
54157
+ return null;
54158
+ }
54159
+ if (!operator || operator === "in" && tokens.length < 2 || operator !== "in" && tokens.length !== 2) {
54160
+ return null;
54161
+ }
54162
+ const left = parsePathOperand(tokens[0]);
54163
+ if (!left) {
53887
54164
  return null;
53888
54165
  }
54166
+ const operands = [];
54167
+ for (const token of tokens.slice(1)) {
54168
+ const operand = parseComparisonOperand(token);
54169
+ if (!operand) {
54170
+ return null;
54171
+ }
54172
+ operands.push(operand);
54173
+ }
53889
54174
  return {
53890
- path: match2[1],
53891
- literal: match2[2]
54175
+ operator,
54176
+ left,
54177
+ operands
53892
54178
  };
53893
54179
  }
54180
+ function tokenizeExpression(expression) {
54181
+ const tokens = [];
54182
+ let index = 0;
54183
+ while (index < expression.length) {
54184
+ while (/\s/.test(expression[index] || "")) {
54185
+ index += 1;
54186
+ }
54187
+ if (index >= expression.length) {
54188
+ break;
54189
+ }
54190
+ if (expression[index] === '"') {
54191
+ const start2 = index;
54192
+ index += 1;
54193
+ let escaped = false;
54194
+ while (index < expression.length) {
54195
+ const char = expression[index];
54196
+ if (escaped) {
54197
+ escaped = false;
54198
+ } else if (char === "\\") {
54199
+ escaped = true;
54200
+ } else if (char === '"') {
54201
+ index += 1;
54202
+ tokens.push(expression.slice(start2, index));
54203
+ break;
54204
+ }
54205
+ index += 1;
54206
+ }
54207
+ if (tokens[tokens.length - 1] !== expression.slice(start2, index)) {
54208
+ throw new Error(`Unclosed string literal in expression: ${expression}`);
54209
+ }
54210
+ continue;
54211
+ }
54212
+ const start = index;
54213
+ while (index < expression.length && !/\s/.test(expression[index])) {
54214
+ index += 1;
54215
+ }
54216
+ tokens.push(expression.slice(start, index));
54217
+ }
54218
+ return tokens;
54219
+ }
54220
+ function parsePathOperand(token) {
54221
+ return TEMPLATE_PATH_REGEX.test(token) ? { kind: "path", path: token } : null;
54222
+ }
54223
+ function parseComparisonOperand(token) {
54224
+ if (token.startsWith('"')) {
54225
+ try {
54226
+ const value = JSON.parse(token);
54227
+ return typeof value === "string" ? { kind: "literal", value } : null;
54228
+ } catch {
54229
+ return null;
54230
+ }
54231
+ }
54232
+ if (token === "true") {
54233
+ return { kind: "literal", value: true };
54234
+ }
54235
+ if (token === "false") {
54236
+ return { kind: "literal", value: false };
54237
+ }
54238
+ if (token === "null") {
54239
+ return { kind: "literal", value: null };
54240
+ }
54241
+ if (NUMBER_LITERAL_REGEX.test(token)) {
54242
+ return { kind: "literal", value: Number(token) };
54243
+ }
54244
+ return parsePathOperand(token);
54245
+ }
53894
54246
  function validatePartialReferences(templateContents, partialContents, context) {
53895
54247
  const { errors } = context;
53896
54248
  for (const [templatePath, content] of templateContents.entries()) {
@@ -53956,7 +54308,7 @@ function getReferencedPartialNames(content) {
53956
54308
  let match2;
53957
54309
  while ((match2 = PARTIAL_TAG_REGEX.exec(content)) !== null) {
53958
54310
  try {
53959
- const { name } = parsePartialReferenceToken(match2[1]);
54311
+ const { name } = parsePartialReferenceToken(match2[1], { validateArgs: false });
53960
54312
  matches.add(name);
53961
54313
  } catch {
53962
54314
  }
@@ -53964,7 +54316,7 @@ function getReferencedPartialNames(content) {
53964
54316
  PARTIAL_TAG_REGEX.lastIndex = 0;
53965
54317
  return matches;
53966
54318
  }
53967
- function parsePartialReferenceToken(token) {
54319
+ function parsePartialReferenceToken(token, options2 = {}) {
53968
54320
  const source = String(token || "").trim();
53969
54321
  if (!source.startsWith("partial:")) {
53970
54322
  throw new Error("Partial token must start with partial:");
@@ -53979,10 +54331,12 @@ function parsePartialReferenceToken(token) {
53979
54331
  throw new Error(`Invalid partial name '${name}'`);
53980
54332
  }
53981
54333
  const argsSource = expression.slice(name.length).trim();
53982
- parsePartialArgs(argsSource);
54334
+ if (options2.validateArgs !== false) {
54335
+ parsePartialArgs(argsSource, options2);
54336
+ }
53983
54337
  return { name };
53984
54338
  }
53985
- function parsePartialArgs(source) {
54339
+ function parsePartialArgs(source, options2 = {}) {
53986
54340
  if (!source) {
53987
54341
  return {};
53988
54342
  }
@@ -54034,7 +54388,8 @@ function parsePartialArgs(source) {
54034
54388
  if (cursor >= source.length || source[cursor] !== '"') {
54035
54389
  throw new Error(`Unclosed string literal for partial argument '${key}'`);
54036
54390
  }
54037
- args[key] = JSON.parse(source.slice(index, cursor + 1));
54391
+ JSON.parse(source.slice(index, cursor + 1));
54392
+ args[key] = true;
54038
54393
  index = cursor + 1;
54039
54394
  continue;
54040
54395
  }
@@ -54044,10 +54399,25 @@ function parsePartialArgs(source) {
54044
54399
  continue;
54045
54400
  }
54046
54401
  if (source.startsWith("false", index) && isValueBoundary(source, index + 5)) {
54047
- args[key] = false;
54402
+ args[key] = true;
54048
54403
  index += 5;
54049
54404
  continue;
54050
54405
  }
54406
+ if (source.startsWith("null", index) && isValueBoundary(source, index + 4)) {
54407
+ args[key] = true;
54408
+ index += 4;
54409
+ continue;
54410
+ }
54411
+ const valueMatch = /^\S+/.exec(source.slice(index));
54412
+ if (!valueMatch) {
54413
+ throw new Error(`Missing partial argument value for '${key}'`);
54414
+ }
54415
+ const valueToken = valueMatch[0];
54416
+ if (NUMBER_LITERAL_REGEX.test(valueToken) || TEMPLATE_PATH_REGEX.test(valueToken) && isAllowedPartialArgPath(valueToken, options2)) {
54417
+ args[key] = true;
54418
+ index += valueToken.length;
54419
+ continue;
54420
+ }
54051
54421
  throw new Error(`Unsupported partial argument value for '${key}'`);
54052
54422
  }
54053
54423
  return args;
@@ -54055,6 +54425,23 @@ function parsePartialArgs(source) {
54055
54425
  function isValueBoundary(source, index) {
54056
54426
  return index >= source.length || /\s/.test(source[index]);
54057
54427
  }
54428
+ function isAllowedPartialArgPath(valueToken, options2) {
54429
+ if (valueToken.includes(".")) {
54430
+ return true;
54431
+ }
54432
+ const allowedSingleSegmentPaths = options2?.allowedSingleSegmentPaths;
54433
+ return allowedSingleSegmentPaths instanceof Set && allowedSingleSegmentPaths.has(valueToken);
54434
+ }
54435
+ function getPartialArgScope(stack) {
54436
+ const scope = new Set(PARTIAL_ARG_ROOT_PATHS);
54437
+ for (const entry of stack) {
54438
+ if (entry.tag === "for" && typeof entry.itemName === "string") {
54439
+ scope.add(entry.itemName);
54440
+ scope.add("loop");
54441
+ }
54442
+ }
54443
+ return scope;
54444
+ }
54058
54445
  function validatePathSafety(pathEntries, errors) {
54059
54446
  for (const entry of pathEntries) {
54060
54447
  const entryPath = normalizePath(String(entry.path || ""));
@@ -54102,13 +54489,71 @@ function normalizeAbsolutePath(value) {
54102
54489
  function isPathInsideRoot(candidate, root) {
54103
54490
  return candidate === root || candidate.startsWith(`${root}/`);
54104
54491
  }
54105
- function issue2(code2, filePath, message, severity) {
54106
- return {
54492
+ function issue2(code2, filePath, message, severity, details = {}) {
54493
+ const nextIssue = {
54107
54494
  code: code2,
54108
54495
  path: filePath,
54109
54496
  message,
54110
54497
  severity
54111
54498
  };
54499
+ if (Number.isInteger(details.line) && details.line > 0) {
54500
+ nextIssue.line = details.line;
54501
+ }
54502
+ if (Number.isInteger(details.column) && details.column > 0) {
54503
+ nextIssue.column = details.column;
54504
+ }
54505
+ if (typeof details.hint === "string" && details.hint.trim()) {
54506
+ nextIssue.hint = details.hint;
54507
+ }
54508
+ if (typeof details.category === "string" && details.category.trim()) {
54509
+ nextIssue.category = details.category;
54510
+ }
54511
+ if (details.snippet && typeof details.snippet === "object" && typeof details.snippet.line === "string" && typeof details.snippet.pointer === "string") {
54512
+ nextIssue.snippet = {
54513
+ line: details.snippet.line,
54514
+ pointer: details.snippet.pointer
54515
+ };
54516
+ }
54517
+ return nextIssue;
54518
+ }
54519
+ function locationForJsonParseError(error, source) {
54520
+ const message = error instanceof Error ? error.message : String(error);
54521
+ const lineColumnMatch = /\bline\s+(\d+)\s+column\s+(\d+)/i.exec(message);
54522
+ if (lineColumnMatch) {
54523
+ return {
54524
+ line: Number(lineColumnMatch[1]),
54525
+ column: Number(lineColumnMatch[2])
54526
+ };
54527
+ }
54528
+ const positionMatch = /\bposition\s+(\d+)/i.exec(message);
54529
+ if (positionMatch) {
54530
+ return locationForIndex(source, Number(positionMatch[1]));
54531
+ }
54532
+ if (/Unexpected end of JSON input/i.test(message)) {
54533
+ return locationForIndex(source, source.length);
54534
+ }
54535
+ return {};
54536
+ }
54537
+ function locationForIndex(source, index) {
54538
+ const boundedIndex = Math.max(0, Math.min(Number.isInteger(index) ? index : 0, source.length));
54539
+ let line = 1;
54540
+ let column = 1;
54541
+ for (let cursor = 0; cursor < boundedIndex; cursor += 1) {
54542
+ if (source[cursor] === "\n") {
54543
+ line += 1;
54544
+ column = 1;
54545
+ } else {
54546
+ column += 1;
54547
+ }
54548
+ }
54549
+ return { line, column };
54550
+ }
54551
+ function snippetForIndex(source, index) {
54552
+ const location = locationForIndex(source, index);
54553
+ const lines = String(source).split(/\r?\n/);
54554
+ const line = lines[location.line - 1] || "";
54555
+ const pointer = `${" ".repeat(Math.max(location.column - 1, 0))}^`;
54556
+ return { line, pointer };
54112
54557
  }
54113
54558
 
54114
54559
  // node_modules/@zeropress/build-core/src/assets/asset-processor.js
@@ -59574,6 +60019,10 @@ function sanitizeHtml(html) {
59574
60019
  "li",
59575
60020
  "blockquote",
59576
60021
  "aside",
60022
+ "figure",
60023
+ "figcaption",
60024
+ "picture",
60025
+ "source",
59577
60026
  "table",
59578
60027
  "thead",
59579
60028
  "tbody",
@@ -59589,9 +60038,10 @@ function sanitizeHtml(html) {
59589
60038
  const allowedAttributes = {
59590
60039
  a: /* @__PURE__ */ new Set(["href", "title", "class", "id"]),
59591
60040
  aside: /* @__PURE__ */ new Set(["role", "class", "id"]),
59592
- img: /* @__PURE__ */ new Set(["src", "alt", "title", "class", "id", "width", "height"]),
60041
+ img: /* @__PURE__ */ new Set(["src", "srcset", "sizes", "alt", "title", "class", "id", "width", "height", "loading", "decoding"]),
59593
60042
  iframe: /* @__PURE__ */ new Set(["src", "width", "height", "frameborder", "allowfullscreen", "class"]),
59594
60043
  input: /* @__PURE__ */ new Set(["type", "checked", "disabled", "class", "id"]),
60044
+ source: /* @__PURE__ */ new Set(["src", "srcset", "sizes", "type", "media", "width", "height", "class", "id"]),
59595
60045
  "*": /* @__PURE__ */ new Set(["class", "id"])
59596
60046
  };
59597
60047
  const safeUriPattern = /^(?:(?:https?|mailto|tel):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i;
@@ -59619,13 +60069,16 @@ function sanitizeHtml(html) {
59619
60069
  if ((attributeName === "href" || attributeName === "src") && !safeUriPattern.test(attributeValue)) {
59620
60070
  continue;
59621
60071
  }
60072
+ if (attributeName === "srcset" && !isSafeSrcset(attributeValue, safeUriPattern)) {
60073
+ continue;
60074
+ }
59622
60075
  if (normalizedTag === "input" && attributeName === "type" && attributeValue !== "checkbox") {
59623
60076
  continue;
59624
60077
  }
59625
60078
  filteredAttributes.push(`${attributeName}="${attributeValue}"`);
59626
60079
  }
59627
60080
  }
59628
- const isSelfClosing = match2.endsWith("/>") || normalizedTag === "br" || normalizedTag === "hr" || normalizedTag === "img" || normalizedTag === "input";
60081
+ const isSelfClosing = match2.endsWith("/>") || normalizedTag === "br" || normalizedTag === "hr" || normalizedTag === "img" || normalizedTag === "input" || normalizedTag === "source";
59629
60082
  const attributeSuffix = filteredAttributes.length > 0 ? ` ${filteredAttributes.join(" ")}` : "";
59630
60083
  return isSelfClosing ? `<${normalizedTag}${attributeSuffix} />` : `<${normalizedTag}${attributeSuffix}>`;
59631
60084
  });
@@ -59636,6 +60089,13 @@ function sanitizeHtml(html) {
59636
60089
  return part.replace(/&(?!(?:[a-zA-Z]+|#\d+|#x[0-9a-fA-F]+);)/g, "&amp;");
59637
60090
  }).join("");
59638
60091
  }
60092
+ function isSafeSrcset(value, safeUriPattern) {
60093
+ const candidates = String(value).split(",").map((candidate) => candidate.trim()).filter(Boolean);
60094
+ return candidates.length > 0 && candidates.every((candidate) => {
60095
+ const [url] = candidate.split(/\s+/);
60096
+ return Boolean(url) && safeUriPattern.test(url);
60097
+ });
60098
+ }
59639
60099
  function slugify(value) {
59640
60100
  return String(value || "").toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
59641
60101
  }
@@ -59728,6 +60188,9 @@ var VariableResolver = class _VariableResolver {
59728
60188
  }
59729
60189
  }
59730
60190
  }
60191
+ if (variablePath === "data" || variablePath.startsWith("data.") || variablePath.includes(".data.")) {
60192
+ return false;
60193
+ }
59731
60194
  const lastSegment = variablePath.split(".").pop();
59732
60195
  if (lastSegment === "html" || lastSegment?.endsWith("_html")) {
59733
60196
  return true;
@@ -59745,7 +60208,10 @@ var VariableResolver = class _VariableResolver {
59745
60208
  // node_modules/@zeropress/build-core/src/render/partial-resolver.js
59746
60209
  var PARTIAL_NAME_REGEX2 = /^[a-zA-Z_][a-zA-Z0-9_-]*(?:\/[a-zA-Z_][a-zA-Z0-9_-]*)*$/;
59747
60210
  var IDENTIFIER_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
59748
- function parsePartialToken(token) {
60211
+ var PATH_SEGMENT_SOURCE = "[a-zA-Z_][a-zA-Z0-9_]*(?:-[a-zA-Z0-9_]+)*";
60212
+ var PATH_REGEX = new RegExp(`^${PATH_SEGMENT_SOURCE}(?:\\.${PATH_SEGMENT_SOURCE})*$`);
60213
+ var NUMBER_LITERAL_REGEX2 = /^-?(?:0|[1-9]\d*)(?:\.\d+)?$/;
60214
+ function parsePartialToken(token, options2 = {}) {
59749
60215
  const source = String(token || "").trim();
59750
60216
  if (!source.startsWith("partial:")) {
59751
60217
  throw new Error(`Invalid partial token: ${source}`);
@@ -59760,10 +60226,10 @@ function parsePartialToken(token) {
59760
60226
  throw new Error(`Invalid partial name: ${name}`);
59761
60227
  }
59762
60228
  const argsSource = expression.slice(name.length).trim();
59763
- const args = parsePartialArgs2(argsSource);
60229
+ const args = parsePartialArgs2(argsSource, options2);
59764
60230
  return { name, args };
59765
60231
  }
59766
- function parsePartialArgs2(source) {
60232
+ function parsePartialArgs2(source, options2 = {}) {
59767
60233
  if (!source) {
59768
60234
  return {};
59769
60235
  }
@@ -59815,20 +60281,40 @@ function parsePartialArgs2(source) {
59815
60281
  if (cursor >= source.length || source[cursor] !== '"') {
59816
60282
  throw new Error(`Unclosed string literal for partial argument "${key}"`);
59817
60283
  }
59818
- args[key] = JSON.parse(source.slice(index, cursor + 1));
60284
+ args[key] = { kind: "literal", value: JSON.parse(source.slice(index, cursor + 1)) };
59819
60285
  index = cursor + 1;
59820
60286
  continue;
59821
60287
  }
59822
60288
  if (source.startsWith("true", index) && isValueBoundary2(source, index + 4)) {
59823
- args[key] = true;
60289
+ args[key] = { kind: "literal", value: true };
59824
60290
  index += 4;
59825
60291
  continue;
59826
60292
  }
59827
60293
  if (source.startsWith("false", index) && isValueBoundary2(source, index + 5)) {
59828
- args[key] = false;
60294
+ args[key] = { kind: "literal", value: false };
59829
60295
  index += 5;
59830
60296
  continue;
59831
60297
  }
60298
+ if (source.startsWith("null", index) && isValueBoundary2(source, index + 4)) {
60299
+ args[key] = { kind: "literal", value: null };
60300
+ index += 4;
60301
+ continue;
60302
+ }
60303
+ const valueMatch = /^\S+/.exec(source.slice(index));
60304
+ if (!valueMatch) {
60305
+ throw new Error(`Missing partial argument value for "${key}"`);
60306
+ }
60307
+ const valueToken = valueMatch[0];
60308
+ if (NUMBER_LITERAL_REGEX2.test(valueToken)) {
60309
+ args[key] = { kind: "literal", value: Number(valueToken) };
60310
+ index += valueToken.length;
60311
+ continue;
60312
+ }
60313
+ if (PATH_REGEX.test(valueToken) && isAllowedPathArgument(valueToken, options2)) {
60314
+ args[key] = { kind: "path", path: valueToken };
60315
+ index += valueToken.length;
60316
+ continue;
60317
+ }
59832
60318
  throw new Error(`Unsupported partial argument value for "${key}"`);
59833
60319
  }
59834
60320
  return args;
@@ -59836,50 +60322,184 @@ function parsePartialArgs2(source) {
59836
60322
  function isValueBoundary2(source, index) {
59837
60323
  return index >= source.length || /\s/.test(source[index]);
59838
60324
  }
60325
+ function isAllowedPathArgument(valueToken, options2) {
60326
+ if (valueToken.includes(".")) {
60327
+ return true;
60328
+ }
60329
+ const allowedSingleSegmentPaths = options2?.allowedSingleSegmentPaths;
60330
+ return allowedSingleSegmentPaths instanceof Set && allowedSingleSegmentPaths.has(valueToken);
60331
+ }
59839
60332
 
59840
60333
  // node_modules/@zeropress/build-core/src/render/control-flow-renderer.js
59841
- var PATH_SEGMENT_SOURCE = "[a-zA-Z_][a-zA-Z0-9_]*(?:-[a-zA-Z0-9_]+)*";
59842
- var PATH_REGEX = new RegExp(`^${PATH_SEGMENT_SOURCE}(?:\\.${PATH_SEGMENT_SOURCE})*$`);
59843
- var FOR_EXPRESSION_REGEX = new RegExp(`^([a-zA-Z_][a-zA-Z0-9_]*)\\s+in\\s+(${PATH_SEGMENT_SOURCE}(?:\\.${PATH_SEGMENT_SOURCE})*)$`);
59844
- var IF_EQ_EXPRESSION_REGEX2 = new RegExp(`^(${PATH_SEGMENT_SOURCE}(?:\\.${PATH_SEGMENT_SOURCE})*)\\s+("(?:[^"\\\\]|\\\\.)*")$`);
59845
- var ControlFlowRenderer = class {
59846
- constructor(options2 = {}) {
59847
- this.resolvePath = typeof options2.resolvePath === "function" ? options2.resolvePath : ((data, path4) => path4.split(".").reduce((current, segment) => {
59848
- if (current == null || typeof current !== "object") {
59849
- return void 0;
59850
- }
59851
- return current[segment];
59852
- }, data));
59853
- this.renderText = typeof options2.renderText === "function" ? options2.renderText : ((value) => value);
59854
- }
59855
- render(template, data, renderOptions = {}) {
59856
- return this.renderTemplate(template, data, {
59857
- ...renderOptions,
59858
- partialStack: Array.isArray(renderOptions.partialStack) ? renderOptions.partialStack : []
59859
- });
60334
+ var PATH_SEGMENT_SOURCE2 = "[a-zA-Z_][a-zA-Z0-9_]*(?:-[a-zA-Z0-9_]+)*";
60335
+ var PATH_REGEX2 = new RegExp(`^${PATH_SEGMENT_SOURCE2}(?:\\.${PATH_SEGMENT_SOURCE2})*$`);
60336
+ var FOR_EXPRESSION_REGEX = new RegExp(`^([a-zA-Z_][a-zA-Z0-9_]*)\\s+in\\s+(${PATH_SEGMENT_SOURCE2}(?:\\.${PATH_SEGMENT_SOURCE2})*)$`);
60337
+ var NUMBER_LITERAL_REGEX3 = /^-?(?:0|[1-9]\d*)(?:\.\d+)?$/;
60338
+ var COMPARISON_BLOCK_TAGS2 = /* @__PURE__ */ new Set(["if_eq", "if_neq", "if_in", "if_starts_with"]);
60339
+ var COMPARISON_ELSE_IF_TAGS2 = /* @__PURE__ */ new Set(["else_if_eq", "else_if_neq", "else_if_in", "else_if_starts_with"]);
60340
+ var COMPARISON_TAG_OPERATORS2 = {
60341
+ if_eq: "eq",
60342
+ else_if_eq: "eq",
60343
+ if_neq: "neq",
60344
+ else_if_neq: "neq",
60345
+ if_in: "in",
60346
+ else_if_in: "in",
60347
+ if_starts_with: "starts_with",
60348
+ else_if_starts_with: "starts_with"
60349
+ };
60350
+ var PARTIAL_ARG_ROOT_PATHS2 = /* @__PURE__ */ new Set([
60351
+ "archive",
60352
+ "author",
60353
+ "category",
60354
+ "collection",
60355
+ "collections",
60356
+ "currentUrl",
60357
+ "language",
60358
+ "menu",
60359
+ "menus",
60360
+ "meta",
60361
+ "page",
60362
+ "pagination",
60363
+ "partial",
60364
+ "post",
60365
+ "posts",
60366
+ "route",
60367
+ "site",
60368
+ "tag",
60369
+ "taxonomies",
60370
+ "taxonomy",
60371
+ "widget",
60372
+ "widgets"
60373
+ ]);
60374
+ function getComparisonBlockTag2(token) {
60375
+ for (const tagName of COMPARISON_BLOCK_TAGS2) {
60376
+ if (token.startsWith(`#${tagName} `)) {
60377
+ return tagName;
60378
+ }
59860
60379
  }
59861
- renderTemplate(template, data, renderOptions) {
59862
- const nodes = this.parse(template);
59863
- return this.renderNodes(nodes, data, renderOptions);
60380
+ return "";
60381
+ }
60382
+ function getComparisonElseIfTag2(token) {
60383
+ for (const tagName of COMPARISON_ELSE_IF_TAGS2) {
60384
+ if (token.startsWith(`#${tagName} `)) {
60385
+ return tagName;
60386
+ }
59864
60387
  }
59865
- parse(template) {
59866
- const source = String(template || "");
59867
- const { nodes, nextIndex, stopTag } = this.parseNodes(source, 0, /* @__PURE__ */ new Set());
59868
- if (stopTag) {
59869
- throw new Error(`Unexpected closing tag ${stopTag}`);
60388
+ return "";
60389
+ }
60390
+ function tokenizeExpression2(expression) {
60391
+ const tokens = [];
60392
+ let index = 0;
60393
+ while (index < expression.length) {
60394
+ while (/\s/.test(expression[index] || "")) {
60395
+ index += 1;
59870
60396
  }
59871
- if (nextIndex !== source.length) {
59872
- throw new Error("Unexpected parser termination");
60397
+ if (index >= expression.length) {
60398
+ break;
59873
60399
  }
59874
- return nodes;
59875
- }
59876
- parseNodes(source, startIndex, stopTags) {
59877
- const nodes = [];
59878
- let index = startIndex;
59879
- while (index < source.length) {
59880
- const nextDelimiter = source.indexOf("{{", index);
59881
- if (nextDelimiter === -1) {
59882
- nodes.push({ type: "text", value: source.slice(index) });
60400
+ if (expression[index] === '"') {
60401
+ const start2 = index;
60402
+ index += 1;
60403
+ let escaped = false;
60404
+ while (index < expression.length) {
60405
+ const char = expression[index];
60406
+ if (escaped) {
60407
+ escaped = false;
60408
+ } else if (char === "\\") {
60409
+ escaped = true;
60410
+ } else if (char === '"') {
60411
+ index += 1;
60412
+ tokens.push(expression.slice(start2, index));
60413
+ break;
60414
+ }
60415
+ index += 1;
60416
+ }
60417
+ if (tokens[tokens.length - 1] !== expression.slice(start2, index)) {
60418
+ throw new Error(`Unclosed string literal in expression: ${expression}`);
60419
+ }
60420
+ continue;
60421
+ }
60422
+ const start = index;
60423
+ while (index < expression.length && !/\s/.test(expression[index])) {
60424
+ index += 1;
60425
+ }
60426
+ tokens.push(expression.slice(start, index));
60427
+ }
60428
+ return tokens;
60429
+ }
60430
+ function parsePathOperand2(token, tagName, expression) {
60431
+ if (!PATH_REGEX2.test(token)) {
60432
+ throw new Error(`Invalid ${tagName} expression: ${expression}`);
60433
+ }
60434
+ return { kind: "path", path: token };
60435
+ }
60436
+ function parseComparisonOperand2(token, tagName, expression) {
60437
+ if (token.startsWith('"')) {
60438
+ try {
60439
+ const value = JSON.parse(token);
60440
+ if (typeof value !== "string") {
60441
+ throw new Error("Expected string literal");
60442
+ }
60443
+ return { kind: "literal", value };
60444
+ } catch {
60445
+ throw new Error(`Invalid ${tagName} expression: ${expression}`);
60446
+ }
60447
+ }
60448
+ if (token === "true") {
60449
+ return { kind: "literal", value: true };
60450
+ }
60451
+ if (token === "false") {
60452
+ return { kind: "literal", value: false };
60453
+ }
60454
+ if (token === "null") {
60455
+ return { kind: "literal", value: null };
60456
+ }
60457
+ if (NUMBER_LITERAL_REGEX3.test(token)) {
60458
+ return { kind: "literal", value: Number(token) };
60459
+ }
60460
+ if (PATH_REGEX2.test(token)) {
60461
+ return { kind: "path", path: token };
60462
+ }
60463
+ throw new Error(`Invalid ${tagName} expression: ${expression}`);
60464
+ }
60465
+ var ControlFlowRenderer = class {
60466
+ constructor(options2 = {}) {
60467
+ this.resolvePath = typeof options2.resolvePath === "function" ? options2.resolvePath : ((data, path4) => path4.split(".").reduce((current, segment) => {
60468
+ if (current == null || typeof current !== "object") {
60469
+ return void 0;
60470
+ }
60471
+ return current[segment];
60472
+ }, data));
60473
+ this.renderText = typeof options2.renderText === "function" ? options2.renderText : ((value) => value);
60474
+ }
60475
+ render(template, data, renderOptions = {}) {
60476
+ return this.renderTemplate(template, data, {
60477
+ ...renderOptions,
60478
+ partialStack: Array.isArray(renderOptions.partialStack) ? renderOptions.partialStack : []
60479
+ });
60480
+ }
60481
+ renderTemplate(template, data, renderOptions) {
60482
+ const nodes = this.parse(template);
60483
+ return this.renderNodes(nodes, data, renderOptions);
60484
+ }
60485
+ parse(template) {
60486
+ const source = String(template || "");
60487
+ const { nodes, nextIndex, stopTag } = this.parseNodes(source, 0, /* @__PURE__ */ new Set(), PARTIAL_ARG_ROOT_PATHS2);
60488
+ if (stopTag) {
60489
+ throw new Error(`Unexpected closing tag ${stopTag}`);
60490
+ }
60491
+ if (nextIndex !== source.length) {
60492
+ throw new Error("Unexpected parser termination");
60493
+ }
60494
+ return nodes;
60495
+ }
60496
+ parseNodes(source, startIndex, stopTags, partialArgScope) {
60497
+ const nodes = [];
60498
+ let index = startIndex;
60499
+ while (index < source.length) {
60500
+ const nextDelimiter = source.indexOf("{{", index);
60501
+ if (nextDelimiter === -1) {
60502
+ nodes.push({ type: "text", value: source.slice(index) });
59883
60503
  return { nodes, nextIndex: source.length, stopTag: null };
59884
60504
  }
59885
60505
  if (nextDelimiter > index) {
@@ -59924,9 +60544,10 @@ var ControlFlowRenderer = class {
59924
60544
  }
59925
60545
  return { nodes, nextIndex: tokenEnd + 2, stopTag: "else" };
59926
60546
  }
59927
- if (token.startsWith("#else_if_eq ")) {
59928
- if (!stopTags.has("else_if_eq")) {
59929
- throw new Error("Unexpected else_if_eq tag");
60547
+ const comparisonElseIfTag = getComparisonElseIfTag2(token);
60548
+ if (comparisonElseIfTag) {
60549
+ if (!stopTags.has(comparisonElseIfTag)) {
60550
+ throw new Error(`Unexpected ${comparisonElseIfTag} tag`);
59930
60551
  }
59931
60552
  return { nodes, nextIndex: tokenEnd + 2, stopTag: token };
59932
60553
  }
@@ -59937,21 +60558,22 @@ var ControlFlowRenderer = class {
59937
60558
  return { nodes, nextIndex: tokenEnd + 2, stopTag: token };
59938
60559
  }
59939
60560
  if (token.startsWith("partial:")) {
59940
- const { name, args } = parsePartialToken(token);
60561
+ const { name, args } = parsePartialToken(token, { allowedSingleSegmentPaths: partialArgScope });
59941
60562
  nodes.push({ type: "partial", name, args });
59942
60563
  index = tokenEnd + 2;
59943
60564
  continue;
59944
60565
  }
59945
- if (token.startsWith("#if_eq ")) {
59946
- const expression = token.slice("#if_eq ".length).trim();
59947
- const block2 = this.parseIfEqBlock(source, tokenEnd + 2, expression);
60566
+ const comparisonBlockTag = getComparisonBlockTag2(token);
60567
+ if (comparisonBlockTag) {
60568
+ const expression = token.slice(`#${comparisonBlockTag} `.length).trim();
60569
+ const block2 = this.parseComparisonBlock(source, tokenEnd + 2, comparisonBlockTag, expression, partialArgScope);
59948
60570
  nodes.push(block2.node);
59949
60571
  index = block2.nextIndex;
59950
60572
  continue;
59951
60573
  }
59952
60574
  if (token.startsWith("#if ")) {
59953
60575
  const expression = token.slice("#if ".length).trim();
59954
- const block2 = this.parseIfBlock(source, tokenEnd + 2, expression);
60576
+ const block2 = this.parseIfBlock(source, tokenEnd + 2, expression, partialArgScope);
59955
60577
  nodes.push(block2.node);
59956
60578
  index = block2.nextIndex;
59957
60579
  continue;
@@ -59963,7 +60585,8 @@ var ControlFlowRenderer = class {
59963
60585
  throw new Error(`Invalid for expression: ${expression}`);
59964
60586
  }
59965
60587
  const [, itemName, path4] = match2;
59966
- const forResult = this.parseNodes(source, tokenEnd + 2, /* @__PURE__ */ new Set(["for"]));
60588
+ const forScope = /* @__PURE__ */ new Set([...partialArgScope, itemName, "loop"]);
60589
+ const forResult = this.parseNodes(source, tokenEnd + 2, /* @__PURE__ */ new Set(["for"]), forScope);
59967
60590
  if (forResult.stopTag !== "for") {
59968
60591
  throw new Error("Unclosed for block");
59969
60592
  }
@@ -59980,8 +60603,8 @@ var ControlFlowRenderer = class {
59980
60603
  }
59981
60604
  return { nodes, nextIndex: source.length, stopTag: null };
59982
60605
  }
59983
- parseIfBlock(source, startIndex, initialPath) {
59984
- if (!PATH_REGEX.test(initialPath)) {
60606
+ parseIfBlock(source, startIndex, initialPath, partialArgScope) {
60607
+ if (!PATH_REGEX2.test(initialPath)) {
59985
60608
  throw new Error(`Invalid if expression: ${initialPath}`);
59986
60609
  }
59987
60610
  const branches = [];
@@ -59989,13 +60612,13 @@ var ControlFlowRenderer = class {
59989
60612
  let nextIndex = startIndex;
59990
60613
  let currentPath = initialPath;
59991
60614
  while (true) {
59992
- const branchResult = this.parseNodes(source, nextIndex, /* @__PURE__ */ new Set(["else", "else_if", "if"]));
60615
+ const branchResult = this.parseNodes(source, nextIndex, /* @__PURE__ */ new Set(["else", "else_if", "if"]), partialArgScope);
59993
60616
  branches.push({
59994
60617
  path: currentPath,
59995
60618
  consequent: branchResult.nodes
59996
60619
  });
59997
60620
  if (branchResult.stopTag === "else") {
59998
- const elseResult = this.parseNodes(source, branchResult.nextIndex, /* @__PURE__ */ new Set(["if"]));
60621
+ const elseResult = this.parseNodes(source, branchResult.nextIndex, /* @__PURE__ */ new Set(["if"]), partialArgScope);
59999
60622
  if (elseResult.stopTag !== "if") {
60000
60623
  throw new Error("Unclosed if block after else");
60001
60624
  }
@@ -60005,7 +60628,7 @@ var ControlFlowRenderer = class {
60005
60628
  }
60006
60629
  if (typeof branchResult.stopTag === "string" && branchResult.stopTag.startsWith("#else_if ")) {
60007
60630
  currentPath = branchResult.stopTag.slice("#else_if ".length).trim();
60008
- if (!PATH_REGEX.test(currentPath)) {
60631
+ if (!PATH_REGEX2.test(currentPath)) {
60009
60632
  throw new Error(`Invalid else_if expression: ${currentPath}`);
60010
60633
  }
60011
60634
  nextIndex = branchResult.nextIndex;
@@ -60026,60 +60649,65 @@ var ControlFlowRenderer = class {
60026
60649
  nextIndex
60027
60650
  };
60028
60651
  }
60029
- parseIfEqBlock(source, startIndex, expression) {
60030
- const initialBranch = this.parseIfEqBranchExpression(expression, "if_eq");
60652
+ parseComparisonBlock(source, startIndex, tagName, expression, partialArgScope) {
60653
+ const initialBranch = this.parseComparisonBranchExpression(expression, tagName);
60031
60654
  const branches = [];
60032
60655
  let alternate = [];
60033
60656
  let nextIndex = startIndex;
60034
60657
  let currentBranch = initialBranch;
60658
+ const elseIfTag = `else_${tagName}`;
60035
60659
  while (true) {
60036
- const branchResult = this.parseNodes(source, nextIndex, /* @__PURE__ */ new Set(["else", "else_if_eq", "if_eq"]));
60660
+ const branchResult = this.parseNodes(source, nextIndex, /* @__PURE__ */ new Set(["else", elseIfTag, tagName]), partialArgScope);
60037
60661
  branches.push({
60038
- path: currentBranch.path,
60039
- literal: currentBranch.literal,
60662
+ operator: currentBranch.operator,
60663
+ left: currentBranch.left,
60664
+ operands: currentBranch.operands,
60040
60665
  consequent: branchResult.nodes
60041
60666
  });
60042
60667
  if (branchResult.stopTag === "else") {
60043
- const elseResult = this.parseNodes(source, branchResult.nextIndex, /* @__PURE__ */ new Set(["if_eq"]));
60044
- if (elseResult.stopTag !== "if_eq") {
60045
- throw new Error("Unclosed if_eq block after else");
60668
+ const elseResult = this.parseNodes(source, branchResult.nextIndex, /* @__PURE__ */ new Set([tagName]), partialArgScope);
60669
+ if (elseResult.stopTag !== tagName) {
60670
+ throw new Error(`Unclosed ${tagName} block after else`);
60046
60671
  }
60047
60672
  alternate = elseResult.nodes;
60048
60673
  nextIndex = elseResult.nextIndex;
60049
60674
  break;
60050
60675
  }
60051
- if (typeof branchResult.stopTag === "string" && branchResult.stopTag.startsWith("#else_if_eq ")) {
60052
- currentBranch = this.parseIfEqBranchExpression(
60053
- branchResult.stopTag.slice("#else_if_eq ".length).trim(),
60054
- "else_if_eq"
60676
+ if (typeof branchResult.stopTag === "string" && branchResult.stopTag.startsWith(`#${elseIfTag} `)) {
60677
+ currentBranch = this.parseComparisonBranchExpression(
60678
+ branchResult.stopTag.slice(`#${elseIfTag} `.length).trim(),
60679
+ elseIfTag
60055
60680
  );
60056
60681
  nextIndex = branchResult.nextIndex;
60057
60682
  continue;
60058
60683
  }
60059
- if (branchResult.stopTag === "if_eq") {
60684
+ if (branchResult.stopTag === tagName) {
60060
60685
  nextIndex = branchResult.nextIndex;
60061
60686
  break;
60062
60687
  }
60063
- throw new Error("Unclosed if_eq block");
60688
+ throw new Error(`Unclosed ${tagName} block`);
60064
60689
  }
60065
60690
  return {
60066
60691
  node: {
60067
- type: "if_eq",
60692
+ type: "comparison",
60068
60693
  branches,
60069
60694
  alternate
60070
60695
  },
60071
60696
  nextIndex
60072
60697
  };
60073
60698
  }
60074
- parseIfEqBranchExpression(expression, tagName) {
60075
- const match2 = IF_EQ_EXPRESSION_REGEX2.exec(expression);
60076
- if (!match2) {
60699
+ parseComparisonBranchExpression(expression, tagName) {
60700
+ const operator = COMPARISON_TAG_OPERATORS2[tagName];
60701
+ const tokens = tokenizeExpression2(expression);
60702
+ if (!operator || operator === "in" && tokens.length < 2 || operator !== "in" && tokens.length !== 2) {
60077
60703
  throw new Error(`Invalid ${tagName} expression: ${expression}`);
60078
60704
  }
60079
- const [, path4, literalSource] = match2;
60705
+ const left = parsePathOperand2(tokens[0], tagName, expression);
60706
+ const operands = tokens.slice(1).map((token) => parseComparisonOperand2(token, tagName, expression));
60080
60707
  return {
60081
- path: path4,
60082
- literal: JSON.parse(literalSource)
60708
+ operator,
60709
+ left,
60710
+ operands
60083
60711
  };
60084
60712
  }
60085
60713
  renderNodes(nodes, data, renderOptions) {
@@ -60093,8 +60721,8 @@ var ControlFlowRenderer = class {
60093
60721
  const branch = node.branches.find((entry) => this.isTruthy(this.resolvePath(data, entry.path)));
60094
60722
  return branch ? this.renderNodes(branch.consequent, data, renderOptions) : this.renderNodes(node.alternate, data, renderOptions);
60095
60723
  }
60096
- case "if_eq": {
60097
- const branch = node.branches.find((entry) => this.resolvePath(data, entry.path) === entry.literal);
60724
+ case "comparison": {
60725
+ const branch = node.branches.find((entry) => this.evaluateComparison(entry, data));
60098
60726
  return branch ? this.renderNodes(branch.consequent, data, renderOptions) : this.renderNodes(node.alternate, data, renderOptions);
60099
60727
  }
60100
60728
  case "for": {
@@ -60129,15 +60757,62 @@ var ControlFlowRenderer = class {
60129
60757
  throw new Error(`Circular partial reference detected: ${cycle}`);
60130
60758
  }
60131
60759
  const partialTemplate = String(partials.get(node.name) || "");
60760
+ const partialArgs = this.resolvePartialArgs(node.args, data);
60132
60761
  const partialData = {
60133
60762
  ...data,
60134
- partial: { ...node.args }
60763
+ partial: {
60764
+ ...data?.partial && typeof data.partial === "object" ? data.partial : {},
60765
+ ...partialArgs
60766
+ }
60135
60767
  };
60136
60768
  return this.renderTemplate(partialTemplate, partialData, {
60137
60769
  ...renderOptions,
60138
60770
  partialStack: [...activeStack, node.name]
60139
60771
  });
60140
60772
  }
60773
+ resolvePartialArgs(args, data) {
60774
+ const resolved = {};
60775
+ for (const [key, operand] of Object.entries(args || {})) {
60776
+ if (operand?.kind === "literal") {
60777
+ resolved[key] = operand.value;
60778
+ continue;
60779
+ }
60780
+ if (operand?.kind === "path") {
60781
+ resolved[key] = this.resolvePath(data, operand.path);
60782
+ }
60783
+ }
60784
+ return resolved;
60785
+ }
60786
+ evaluateComparison(entry, data) {
60787
+ const left = this.evaluateOperand(entry.left, data);
60788
+ const operands = entry.operands.map((operand) => this.evaluateOperand(operand, data));
60789
+ if (entry.operator === "eq") {
60790
+ const right = operands[0];
60791
+ return !left.missing && !right.missing && left.value === right.value;
60792
+ }
60793
+ if (entry.operator === "neq") {
60794
+ const right = operands[0];
60795
+ return left.missing || right.missing || left.value !== right.value;
60796
+ }
60797
+ if (entry.operator === "in") {
60798
+ if (left.missing) {
60799
+ return false;
60800
+ }
60801
+ return operands.some((operand) => !operand.missing && left.value === operand.value);
60802
+ }
60803
+ if (entry.operator === "starts_with") {
60804
+ const right = operands[0];
60805
+ return !left.missing && !right.missing && typeof left.value === "string" && typeof right.value === "string" && left.value.startsWith(right.value);
60806
+ }
60807
+ return false;
60808
+ }
60809
+ evaluateOperand(operand, data) {
60810
+ if (operand.kind === "literal") {
60811
+ return { value: operand.value, missing: false };
60812
+ }
60813
+ const value = this.resolvePath(data, operand.path);
60814
+ return { value, missing: value === void 0 };
60815
+ }
60141
60816
  isTruthy(value) {
60142
60817
  if (Array.isArray(value)) {
60143
60818
  return value.length > 0;
@@ -60179,7 +60854,7 @@ var ZeroPressEngine = class {
60179
60854
  const renderData = this.combineRenderData(data, context);
60180
60855
  const renderedContent = this.renderTemplate(template, renderData);
60181
60856
  const layoutWithSlots = this.slotResolver.resolve(layout, this.themePackage.partials, CONTENT_SLOT_PLACEHOLDER);
60182
- return this.renderTemplate(layoutWithSlots, renderData).replaceAll(CONTENT_SLOT_PLACEHOLDER, renderedContent);
60857
+ return this.renderTemplate(layoutWithSlots, renderData).replaceAll(CONTENT_SLOT_PLACEHOLDER, () => renderedContent);
60183
60858
  }
60184
60859
  combineRenderData(data, context) {
60185
60860
  return {
@@ -60190,7 +60865,7 @@ var ZeroPressEngine = class {
60190
60865
  };
60191
60866
  }
60192
60867
  renderTemplate(template, data) {
60193
- if (this.themePackage?.metadata?.runtime !== "0.5") {
60868
+ if (this.themePackage?.metadata?.runtime !== "0.6") {
60194
60869
  throw new Error(`Unsupported theme runtime: ${this.themePackage?.metadata?.runtime || "unknown"}`);
60195
60870
  }
60196
60871
  return this.controlFlowRenderer.render(template, data, {
@@ -60210,10 +60885,13 @@ var DEFAULT_OPTIONS = {
60210
60885
  writeManifest: false
60211
60886
  };
60212
60887
  var DEFAULT_POSTS_PER_PAGE = 10;
60213
- var DEFAULT_DATE_FORMAT = "YYYY-MM-DD";
60214
- var DEFAULT_TIME_FORMAT = "HH:mm";
60888
+ var DEFAULT_DATETIME_DISPLAY = "static";
60889
+ var DEFAULT_DATE_STYLE = "medium";
60890
+ var DEFAULT_TIME_STYLE = "none";
60215
60891
  var DEFAULT_TIMEZONE = "UTC";
60216
60892
  var DEFAULT_LOCALE = "en-US";
60893
+ var DATETIME_DISPLAY_MODES = /* @__PURE__ */ new Set(["static", "client"]);
60894
+ var DATETIME_STYLES = /* @__PURE__ */ new Set(["none", "short", "medium", "long", "full"]);
60217
60895
  var DEFAULT_PERMALINKS = Object.freeze({
60218
60896
  output_style: "directory",
60219
60897
  posts: "/posts/:slug/",
@@ -60231,12 +60909,25 @@ var DEFAULT_POST_INDEX = Object.freeze({
60231
60909
  });
60232
60910
  var PERMALINK_OUTPUT_STYLES = /* @__PURE__ */ new Set(["directory", "html-extension"]);
60233
60911
  var COMMENT_POLICY_OUTPUT_PATH = "_zeropress/comment-policy.json";
60912
+ var SEARCH_INDEX_OUTPUT_PATH = "_zeropress/search.json";
60913
+ var SEARCH_ADAPTER_OUTPUT_PATH = "_zeropress/search.js";
60914
+ var SEARCH_PAGEFIND_ADAPTER_OUTPUT_PATH = "_zeropress/search_pagefind.js";
60234
60915
  var OUTPUT_PATH_CONTROL_CHAR_PATTERN = /[\u0000-\u001F\u007F]/;
60235
60916
  var SAFE_MEDIA_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
60236
60917
  var SAFE_LINK_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]);
60237
60918
  var MEDIA_DELIVERY_MODES = /* @__PURE__ */ new Set(["none", "media_domain"]);
60919
+ var DISCOVERABILITY_VALUES = /* @__PURE__ */ new Set(["default", "noindex", "delist"]);
60238
60920
  var RESPONSIVE_IMAGE_WIDTHS = [320, 480, 768, 1024, 1280, 1600, 1920];
60239
60921
  var RESPONSIVE_IMAGE_EXTENSIONS = /* @__PURE__ */ new Set(["jpg", "jpeg", "png", "webp", "avif"]);
60922
+ var SEARCH_FIELD_WEIGHTS = Object.freeze({
60923
+ title: 5,
60924
+ headings: 3,
60925
+ tags: 2.5,
60926
+ categories: 2,
60927
+ excerpt: 1.5,
60928
+ content_text: 1
60929
+ });
60930
+ var SEARCH_RECENCY_BOOST_MAX = 0.15;
60240
60931
  async function buildSite(input2) {
60241
60932
  const options2 = { ...DEFAULT_OPTIONS, ...input2.options || {} };
60242
60933
  const state = await createBuildState(input2, options2);
@@ -60278,10 +60969,21 @@ async function buildSite(input2) {
60278
60969
  state.commentPolicyContent,
60279
60970
  "application/json"
60280
60971
  );
60972
+ if (shouldGenerateSearchArtifacts(state, options2)) {
60973
+ await writeOutput(state.writer, state.summaries, SEARCH_INDEX_OUTPUT_PATH, buildSearchIndexJson(state), "application/json");
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
+ }
60281
60977
  if (options2.generateSpecialFiles) {
60282
60978
  await maybeRenderNotFoundPage(state);
60283
60979
  if (hasCanonicalSiteUrl(state.previewData.site.url)) {
60284
- await writeOutput(state.writer, state.summaries, "sitemap.xml", buildSitemapXml(state.previewData.site, state.emitted, state.generatedAt), "application/xml");
60980
+ await writeOutput(
60981
+ state.writer,
60982
+ state.summaries,
60983
+ "sitemap.xml",
60984
+ buildSitemapXml(state.previewData.site, state.emitted, state.generatedAt, options2.sitemapStylesheetHref),
60985
+ "application/xml"
60986
+ );
60285
60987
  await writeOutput(state.writer, state.summaries, "feed.xml", buildFeedXml(state.previewData.site, state.emitted, state.generatedAt), "application/rss+xml");
60286
60988
  }
60287
60989
  if (shouldGenerateRobotsTxt(options2)) {
@@ -60323,6 +61025,7 @@ async function createBuildState(input2, options2) {
60323
61025
  customCssHref: customCssAsset ? `/${customCssAsset.path}` : "",
60324
61026
  customHtml: previewData.custom_html,
60325
61027
  favicon: previewData.site.favicon,
61028
+ exposeGenerator: previewData.site.expose_generator !== false,
60326
61029
  commentPolicyContent: buildCommentPolicyManifest(renderData.posts),
60327
61030
  options: options2,
60328
61031
  generatedAt: /* @__PURE__ */ new Date(),
@@ -60432,7 +61135,8 @@ async function renderFrontPage(state, route) {
60432
61135
  title: buildDocumentTitle(page.title, state.previewData.site.title),
60433
61136
  description: page.excerpt,
60434
61137
  ogType: "website",
60435
- image: page.featured_image
61138
+ image: page.featured_image,
61139
+ robotsNoindex: shouldNoindexDocument(page)
60436
61140
  })
60437
61141
  },
60438
61142
  createRenderContext(state.previewData.site, currentUrl)
@@ -60444,7 +61148,8 @@ async function renderFrontPage(state, route) {
60444
61148
  url: currentUrl,
60445
61149
  title: page.title,
60446
61150
  description: page.excerpt,
60447
- includeInFeed: false
61151
+ includeInFeed: false,
61152
+ includeInSitemap: !isDelistedDocument(page)
60448
61153
  };
60449
61154
  return;
60450
61155
  }
@@ -60494,7 +61199,8 @@ async function renderPost(state, post) {
60494
61199
  ogType: "article",
60495
61200
  image: post.featured_image,
60496
61201
  publishedTime: post.published_at_iso,
60497
- modifiedTime: post.updated_at_iso
61202
+ modifiedTime: post.updated_at_iso,
61203
+ robotsNoindex: shouldNoindexDocument(post)
60498
61204
  })
60499
61205
  },
60500
61206
  createRenderContext(state.previewData.site, currentUrl)
@@ -60502,14 +61208,16 @@ async function renderPost(state, post) {
60502
61208
  html = state.assetProcessor.updateAssetReferences(html, state.assetMap);
60503
61209
  html = injectSiteCustomizations(html, state);
60504
61210
  await writeOutput(state.writer, state.summaries, routePathToOutputPath(post.url, state.previewData.site.permalinks.output_style), html, "text/html");
60505
- state.emitted.posts.push({
60506
- url: currentUrl,
60507
- title: post.title,
60508
- description: post.excerpt,
60509
- publishedAt: post.published_at_iso,
60510
- updatedAt: post.updated_at_iso,
60511
- status: post.status
60512
- });
61211
+ if (!isDelistedDocument(post)) {
61212
+ state.emitted.posts.push({
61213
+ url: currentUrl,
61214
+ title: post.title,
61215
+ description: post.excerpt,
61216
+ publishedAt: post.published_at_iso,
61217
+ updatedAt: post.updated_at_iso,
61218
+ status: post.status
61219
+ });
61220
+ }
60513
61221
  }
60514
61222
  async function renderPage(state, page) {
60515
61223
  const currentUrl = page.url;
@@ -60530,7 +61238,8 @@ async function renderPage(state, page) {
60530
61238
  title: buildDocumentTitle(page.title, state.previewData.site.title),
60531
61239
  description: page.excerpt,
60532
61240
  ogType: "website",
60533
- image: page.featured_image
61241
+ image: page.featured_image,
61242
+ robotsNoindex: shouldNoindexDocument(page)
60534
61243
  })
60535
61244
  },
60536
61245
  createRenderContext(state.previewData.site, currentUrl)
@@ -60543,7 +61252,7 @@ async function renderPage(state, page) {
60543
61252
  title: page.title,
60544
61253
  description: page.excerpt,
60545
61254
  status: page.status,
60546
- includeInSitemap: page.omit_from_sitemap !== true
61255
+ includeInSitemap: page.omit_from_sitemap !== true && !isDelistedDocument(page)
60547
61256
  });
60548
61257
  }
60549
61258
  async function maybeRenderNotFoundPage(state) {
@@ -60574,15 +61283,18 @@ async function maybeRenderNotFoundPage(state) {
60574
61283
  function normalizePreviewData(previewData, options2 = {}) {
60575
61284
  const normalizedSite = {
60576
61285
  ...previewData.site,
60577
- mediaBaseUrl: normalizeOptionalString(previewData.site.mediaBaseUrl),
60578
- mediaDeliveryMode: MEDIA_DELIVERY_MODES.has(previewData.site.mediaDeliveryMode) ? previewData.site.mediaDeliveryMode : "none",
61286
+ media_base_url: normalizeOptionalString(previewData.site.media_base_url),
61287
+ media_delivery_mode: MEDIA_DELIVERY_MODES.has(previewData.site.media_delivery_mode) ? previewData.site.media_delivery_mode : "none",
60579
61288
  favicon: normalizeSiteFavicon(previewData.site.favicon || options2.favicon),
60580
- postsPerPage: Number.isInteger(previewData.site.postsPerPage) && previewData.site.postsPerPage > 0 ? previewData.site.postsPerPage : DEFAULT_POSTS_PER_PAGE,
60581
- dateFormat: normalizeNonEmptyString(previewData.site.dateFormat, DEFAULT_DATE_FORMAT),
60582
- timeFormat: typeof previewData.site.timeFormat === "string" ? previewData.site.timeFormat : DEFAULT_TIME_FORMAT,
61289
+ posts_per_page: Number.isInteger(previewData.site.posts_per_page) && previewData.site.posts_per_page > 0 ? previewData.site.posts_per_page : DEFAULT_POSTS_PER_PAGE,
61290
+ datetime_display: DATETIME_DISPLAY_MODES.has(previewData.site.datetime_display) ? previewData.site.datetime_display : DEFAULT_DATETIME_DISPLAY,
61291
+ date_style: DATETIME_STYLES.has(previewData.site.date_style) ? previewData.site.date_style : DEFAULT_DATE_STYLE,
61292
+ time_style: DATETIME_STYLES.has(previewData.site.time_style) ? previewData.site.time_style : DEFAULT_TIME_STYLE,
60583
61293
  timezone: normalizeNonEmptyString(previewData.site.timezone, DEFAULT_TIMEZONE),
60584
61294
  locale: normalizeLocale(previewData.site.locale || DEFAULT_LOCALE),
60585
- disallowComments: previewData.site.disallowComments === true,
61295
+ disallow_comments: previewData.site.disallow_comments === true,
61296
+ expose_generator: previewData.site.expose_generator !== false,
61297
+ search: previewData.site.search !== false,
60586
61298
  indexing: previewData.site.indexing !== false,
60587
61299
  permalinks: normalizePermalinks(previewData.site.permalinks),
60588
61300
  front_page: normalizeFrontPage(previewData.site.front_page),
@@ -60596,13 +61308,13 @@ function normalizePreviewData(previewData, options2 = {}) {
60596
61308
  site: normalizedSite,
60597
61309
  menus: normalizeRecordMap(previewData.menus),
60598
61310
  collections: normalizeCollections(previewData.collections),
60599
- widgets: normalizeWidgetAreas(previewData.widgets, normalizedSite.mediaBaseUrl),
61311
+ widgets: normalizeWidgetAreas(previewData.widgets, normalizedSite.media_base_url),
60600
61312
  custom_css: normalizeCustomCss(previewData.custom_css),
60601
61313
  custom_html: normalizeCustomHtml(previewData.custom_html),
60602
61314
  content: {
60603
61315
  ...previewData.content,
60604
61316
  authors: previewData.content.authors.map((author) => {
60605
- const avatar = normalizeMediaField(author.avatar, normalizedSite.mediaBaseUrl);
61317
+ const avatar = normalizeMediaField(author.avatar, normalizedSite.media_base_url);
60606
61318
  const avatarMedia = deriveManagedMedia(avatar, mediaRegistry, normalizedSite);
60607
61319
  return {
60608
61320
  ...author,
@@ -60611,21 +61323,23 @@ function normalizePreviewData(previewData, options2 = {}) {
60611
61323
  };
60612
61324
  }),
60613
61325
  posts: previewData.content.posts.map((post) => {
60614
- const featuredImage = normalizeMediaField(post.featured_image, normalizedSite.mediaBaseUrl);
61326
+ const featuredImage = normalizeMediaField(post.featured_image, normalizedSite.media_base_url);
60615
61327
  const featuredMedia = deriveManagedMedia(featuredImage, mediaRegistry, normalizedSite);
60616
61328
  return {
60617
61329
  ...post,
60618
61330
  published_at_iso: normalizeIsoTimestamp(post.published_at_iso),
60619
61331
  updated_at_iso: normalizeIsoTimestamp(post.updated_at_iso),
61332
+ discoverability: normalizeDiscoverability(post.discoverability),
60620
61333
  featured_image: featuredImage,
60621
61334
  ...featuredMedia ? { featured_media: featuredMedia } : {}
60622
61335
  };
60623
61336
  }).sort((left, right) => toDate(right.published_at_iso).getTime() - toDate(left.published_at_iso).getTime()),
60624
61337
  pages: previewData.content.pages.map((page) => {
60625
- const featuredImage = normalizeMediaField(page.featured_image, normalizedSite.mediaBaseUrl);
61338
+ const featuredImage = normalizeMediaField(page.featured_image, normalizedSite.media_base_url);
60626
61339
  const featuredMedia = deriveManagedMedia(featuredImage, mediaRegistry, normalizedSite);
60627
61340
  return {
60628
61341
  ...page,
61342
+ discoverability: normalizeDiscoverability(page.discoverability),
60629
61343
  featured_image: featuredImage,
60630
61344
  ...featuredMedia ? { featured_media: featuredMedia } : {}
60631
61345
  };
@@ -60642,6 +61356,15 @@ function normalizeRecordMap(value) {
60642
61356
  }
60643
61357
  return { ...value };
60644
61358
  }
61359
+ function normalizeDiscoverability(value) {
61360
+ return DISCOVERABILITY_VALUES.has(value) ? value : "default";
61361
+ }
61362
+ function isDelistedDocument(document2) {
61363
+ return document2?.discoverability === "delist";
61364
+ }
61365
+ function shouldNoindexDocument(document2) {
61366
+ return document2?.discoverability === "noindex" || document2?.discoverability === "delist";
61367
+ }
60645
61368
  function normalizeContentMedia(mediaItems, site) {
60646
61369
  if (!Array.isArray(mediaItems)) {
60647
61370
  return [];
@@ -60650,7 +61373,7 @@ function normalizeContentMedia(mediaItems, site) {
60650
61373
  if (!item || typeof item !== "object") {
60651
61374
  return null;
60652
61375
  }
60653
- const src = normalizeMediaField(item.src, site.mediaBaseUrl);
61376
+ const src = normalizeMediaField(item.src, site.media_base_url);
60654
61377
  const width = Number.isInteger(item.width) && item.width > 0 ? item.width : 0;
60655
61378
  const height = Number.isInteger(item.height) && item.height > 0 ? item.height : 0;
60656
61379
  if (!src || !width || !height) {
@@ -60688,11 +61411,11 @@ function deriveManagedMedia(src, mediaRegistry, site) {
60688
61411
  };
60689
61412
  }
60690
61413
  function buildResponsiveImageSrcset(media, site) {
60691
- if (site.mediaDeliveryMode !== "media_domain") {
61414
+ if (site.media_delivery_mode !== "media_domain") {
60692
61415
  return "";
60693
61416
  }
60694
- const mediaBaseUrl = normalizeOptionalString(site.mediaBaseUrl);
60695
- if (!mediaBaseUrl || !isUrlUnderMediaBase(media.src, mediaBaseUrl) || !isResponsiveRasterImage(media.src)) {
61417
+ const media_base_url = normalizeOptionalString(site.media_base_url);
61418
+ if (!media_base_url || !isUrlUnderMediaBase(media.src, media_base_url) || !isResponsiveRasterImage(media.src)) {
60696
61419
  return "";
60697
61420
  }
60698
61421
  const widths = RESPONSIVE_IMAGE_WIDTHS.filter((width) => width <= media.width);
@@ -60712,10 +61435,10 @@ function buildResponsiveImageVariantUrl(src, width) {
60712
61435
  return src;
60713
61436
  }
60714
61437
  }
60715
- function isUrlUnderMediaBase(src, mediaBaseUrl) {
61438
+ function isUrlUnderMediaBase(src, media_base_url) {
60716
61439
  try {
60717
61440
  const sourceUrl = new URL(src);
60718
- const baseUrl = new URL(mediaBaseUrl);
61441
+ const baseUrl = new URL(media_base_url);
60719
61442
  const basePath = baseUrl.pathname.endsWith("/") ? baseUrl.pathname : `${baseUrl.pathname}/`;
60720
61443
  return sourceUrl.origin === baseUrl.origin && sourceUrl.pathname.startsWith(basePath);
60721
61444
  } catch {
@@ -60748,34 +61471,30 @@ function normalizeCollections(collections) {
60748
61471
  ])
60749
61472
  );
60750
61473
  }
60751
- function normalizeWidgetAreas(widgetAreas, mediaBaseUrl) {
60752
- if (!widgetAreas || typeof widgetAreas !== "object") {
61474
+ function normalizeWidgetAreas(widget_areas, media_base_url) {
61475
+ if (!widget_areas || typeof widget_areas !== "object") {
60753
61476
  return {};
60754
61477
  }
60755
61478
  return Object.fromEntries(
60756
- Object.entries(widgetAreas).map(([widgetAreaId, widgetArea]) => [
61479
+ Object.entries(widget_areas).map(([widgetAreaId, widgetArea]) => [
60757
61480
  widgetAreaId,
60758
61481
  {
60759
61482
  ...widgetArea,
60760
61483
  name: normalizeNonEmptyString(widgetArea?.name, widgetAreaId),
60761
- items: Array.isArray(widgetArea?.items) ? widgetArea.items.map((item) => normalizeWidgetItem(item, mediaBaseUrl)) : []
61484
+ items: Array.isArray(widgetArea?.items) ? widgetArea.items.map((item) => normalizeWidgetItem(item, media_base_url)) : []
60762
61485
  }
60763
61486
  ])
60764
61487
  );
60765
61488
  }
60766
61489
  function normalizeSiteFooter(footer) {
60767
61490
  const source = footer && typeof footer === "object" && !Array.isArray(footer) ? footer : {};
60768
- const attribution = source.attribution && typeof source.attribution === "object" && !Array.isArray(source.attribution) ? source.attribution : {};
60769
61491
  return {
60770
61492
  ...source,
60771
61493
  copyright_text: normalizeOptionalString(source.copyright_text),
60772
- attribution: {
60773
- ...attribution,
60774
- enabled: attribution.enabled !== false
60775
- }
61494
+ attribution: source.attribution !== false
60776
61495
  };
60777
61496
  }
60778
- function normalizeWidgetItem(item, mediaBaseUrl) {
61497
+ function normalizeWidgetItem(item, media_base_url) {
60779
61498
  const normalizedItem = {
60780
61499
  ...item,
60781
61500
  title: typeof item?.title === "string" ? item.title.trim() : ""
@@ -60785,7 +61504,7 @@ function normalizeWidgetItem(item, mediaBaseUrl) {
60785
61504
  ...normalizedItem,
60786
61505
  settings: {
60787
61506
  ...item.settings,
60788
- avatar: normalizeMediaField(item.settings.avatar, mediaBaseUrl)
61507
+ avatar: normalizeMediaField(item.settings.avatar, media_base_url)
60789
61508
  }
60790
61509
  };
60791
61510
  }
@@ -60844,19 +61563,21 @@ function normalizeFrontPage(frontPage) {
60844
61563
  ...type === "standalone_html" ? { html: normalizeOptionalRawString(frontPage.html) } : {}
60845
61564
  };
60846
61565
  }
60847
- function normalizePostIndex(postIndex) {
60848
- if (!postIndex || typeof postIndex !== "object") {
61566
+ function normalizePostIndex(post_index) {
61567
+ if (!post_index || typeof post_index !== "object") {
60849
61568
  return { ...DEFAULT_POST_INDEX };
60850
61569
  }
60851
61570
  return {
60852
- enabled: postIndex.enabled !== false,
60853
- path: normalizeNonEmptyString(postIndex.path, DEFAULT_POST_INDEX.path),
60854
- paginate: postIndex.paginate !== false
61571
+ enabled: post_index.enabled !== false,
61572
+ path: normalizeNonEmptyString(post_index.path, DEFAULT_POST_INDEX.path),
61573
+ paginate: post_index.paginate !== false
60855
61574
  };
60856
61575
  }
60857
61576
  function createRenderData(previewData, themeMetadata = {}) {
60858
61577
  const themeSupportsComments = themeMetadata?.features?.comments === true;
60859
- const themeSupportsPostIndex = themeMetadata?.features?.postIndex !== false;
61578
+ const themeSupportsPostIndex = themeMetadata?.features?.post_index !== false;
61579
+ const themeSupportsSearch = themeMetadata?.features?.search === true;
61580
+ previewData.site.search = previewData.site.search !== false && themeSupportsSearch;
60860
61581
  const authorsById = new Map(previewData.content.authors.map((author) => [author.id, author]));
60861
61582
  const categoriesBySlug = new Map(previewData.content.categories.map((category) => [category.slug, category]));
60862
61583
  const tagsBySlug = new Map(previewData.content.tags.map((tag) => [tag.slug, tag]));
@@ -60864,7 +61585,8 @@ function createRenderData(previewData, themeMetadata = {}) {
60864
61585
  const tagPostsBySlug = /* @__PURE__ */ new Map();
60865
61586
  const categoryCountBySlug = /* @__PURE__ */ new Map();
60866
61587
  const tagCountBySlug = /* @__PURE__ */ new Map();
60867
- for (const post of previewData.content.posts) {
61588
+ const discoverableSourcePosts = previewData.content.posts.filter((post) => !isDelistedDocument(post));
61589
+ for (const post of discoverableSourcePosts) {
60868
61590
  for (const slug of post.category_slugs) {
60869
61591
  pushToSlugMap(categoryPostsBySlug, slug, post);
60870
61592
  categoryCountBySlug.set(slug, (categoryCountBySlug.get(slug) || 0) + 1);
@@ -60875,45 +61597,54 @@ function createRenderData(previewData, themeMetadata = {}) {
60875
61597
  }
60876
61598
  }
60877
61599
  const preparedPosts = previewData.content.posts.map((post) => preparePost(post, previewData.site, authorsById, categoriesBySlug, tagsBySlug, themeSupportsComments));
60878
- const posts = preparedPosts.map((post, index) => ({
61600
+ const discoverablePreparedPosts = preparedPosts.filter((post) => !isDelistedDocument(post));
61601
+ const adjacentPostsBySlug = new Map(
61602
+ discoverablePreparedPosts.map((post, index) => [post.slug, {
61603
+ prev: index > 0 ? buildAdjacentPostSummary(discoverablePreparedPosts[index - 1]) : null,
61604
+ next: index < discoverablePreparedPosts.length - 1 ? buildAdjacentPostSummary(discoverablePreparedPosts[index + 1]) : null
61605
+ }])
61606
+ );
61607
+ const posts = preparedPosts.map((post) => ({
60879
61608
  ...post,
60880
- prev: index > 0 ? buildAdjacentPostSummary(preparedPosts[index - 1]) : null,
60881
- next: index < preparedPosts.length - 1 ? buildAdjacentPostSummary(preparedPosts[index + 1]) : null
61609
+ prev: adjacentPostsBySlug.get(post.slug)?.prev || null,
61610
+ next: adjacentPostsBySlug.get(post.slug)?.next || null
60882
61611
  }));
60883
61612
  const pages = previewData.content.pages.map((page) => preparePage(page, previewData.site));
60884
61613
  const postBySlug = new Map(posts.map((post) => [post.slug, post]));
60885
61614
  const pageBySlug = new Map(pages.map((page) => [page.slug, page]));
60886
61615
  const frontPage = previewData.site.front_page;
60887
- const postIndex = previewData.site.post_index;
60888
- const effectivePostIndexEnabled = postIndex.enabled !== false && themeSupportsPostIndex;
60889
- const effectivePostIndexPaginate = effectivePostIndexEnabled && postIndex.paginate !== false;
60890
- const postIndexBasePath = normalizeRoutePath(postIndex.path || DEFAULT_POST_INDEX.path);
60891
- if (frontPage.type !== "theme_index" && effectivePostIndexEnabled && postIndexBasePath === "/") {
61616
+ const collections = resolveCollections(previewData.collections, postBySlug, pageBySlug, frontPage);
61617
+ attachCollectionCursors(posts, pages, collections);
61618
+ const post_index = previewData.site.post_index;
61619
+ const effectivePostIndexEnabled = post_index.enabled !== false && themeSupportsPostIndex;
61620
+ const effectivePostIndexPaginate = effectivePostIndexEnabled && post_index.paginate !== false;
61621
+ const post_indexBasePath = normalizeRoutePath(post_index.path || DEFAULT_POST_INDEX.path);
61622
+ if (frontPage.type !== "theme_index" && effectivePostIndexEnabled && post_indexBasePath === "/") {
60892
61623
  throw new Error('Invalid front page configuration: site.front_page occupies "/" so site.post_index.path must not be "/". Set site.post_index.path to a non-root path or disable site.post_index.');
60893
61624
  }
60894
- const frontPageRoute = buildFrontPageRoute(frontPage, pages, effectivePostIndexEnabled, postIndexBasePath);
61625
+ const frontPageRoute = buildFrontPageRoute(frontPage, pages, effectivePostIndexEnabled, post_indexBasePath);
60895
61626
  const pageFrontPageSlug = frontPage.type === "page" ? frontPage.page_slug : "";
60896
61627
  const preparedPages = pageFrontPageSlug ? pages.filter((page) => page.slug !== pageFrontPageSlug) : pages;
60897
61628
  return {
60898
61629
  posts,
60899
61630
  pages: preparedPages,
60900
61631
  postBySlug,
60901
- collections: resolveCollections(previewData.collections, postBySlug, pageBySlug, frontPage),
61632
+ collections,
60902
61633
  taxonomies: buildGlobalTaxonomies(previewData, categoryCountBySlug, tagCountBySlug),
60903
61634
  frontPageRoute,
60904
61635
  indexRoutes: buildPostIndexRoutes({
60905
61636
  enabled: effectivePostIndexEnabled,
60906
61637
  paginate: effectivePostIndexPaginate,
60907
- items: previewData.content.posts,
60908
- postsPerPage: previewData.site.postsPerPage,
60909
- basePath: postIndexBasePath,
61638
+ items: discoverableSourcePosts,
61639
+ posts_per_page: previewData.site.posts_per_page,
61640
+ basePath: post_indexBasePath,
60910
61641
  outputStyle: previewData.site.permalinks.output_style,
60911
61642
  postBySlug,
60912
61643
  frontPage
60913
61644
  }),
60914
61645
  archiveRoutes: buildPaginatedCollection({
60915
- items: previewData.content.posts,
60916
- postsPerPage: previewData.site.postsPerPage,
61646
+ items: discoverableSourcePosts,
61647
+ posts_per_page: previewData.site.posts_per_page,
60917
61648
  basePath: "/archive/",
60918
61649
  outputStyle: previewData.site.permalinks.output_style
60919
61650
  }).map((entry) => ({
@@ -60929,7 +61660,7 @@ function createRenderData(previewData, themeMetadata = {}) {
60929
61660
  items: previewData.content.categories,
60930
61661
  postsBySlug: categoryPostsBySlug,
60931
61662
  postBySlug,
60932
- postsPerPage: previewData.site.postsPerPage,
61663
+ posts_per_page: previewData.site.posts_per_page,
60933
61664
  outputStyle: previewData.site.permalinks.output_style,
60934
61665
  buildBasePath: (category) => resolvePermalink(previewData.site, "categories", category).path,
60935
61666
  renderExtras: (category) => ({
@@ -60940,7 +61671,7 @@ function createRenderData(previewData, themeMetadata = {}) {
60940
61671
  items: previewData.content.tags,
60941
61672
  postsBySlug: tagPostsBySlug,
60942
61673
  postBySlug,
60943
- postsPerPage: previewData.site.postsPerPage,
61674
+ posts_per_page: previewData.site.posts_per_page,
60944
61675
  outputStyle: previewData.site.permalinks.output_style,
60945
61676
  buildBasePath: (tag) => resolvePermalink(previewData.site, "tags", tag).path,
60946
61677
  renderExtras: (tag) => ({
@@ -60960,15 +61691,19 @@ function resolveCollections(collections, postBySlug, pageBySlug, frontPage) {
60960
61691
  return {};
60961
61692
  }
60962
61693
  return Object.fromEntries(
60963
- Object.entries(collections).map(([collectionId, collection]) => [
60964
- collectionId,
60965
- {
60966
- id: collectionId,
60967
- title: normalizeOptionalString(collection?.title),
60968
- description: normalizeOptionalString(collection?.description),
60969
- items: resolveCollectionItems(collectionId, collection?.items, postBySlug, pageBySlug, frontPage)
60970
- }
60971
- ])
61694
+ Object.entries(collections).map(([collectionId, collection]) => {
61695
+ const items = resolveCollectionItems(collectionId, collection?.items, postBySlug, pageBySlug, frontPage);
61696
+ return [
61697
+ collectionId,
61698
+ {
61699
+ id: collectionId,
61700
+ title: normalizeOptionalString(collection?.title),
61701
+ description: normalizeOptionalString(collection?.description),
61702
+ count: items.length,
61703
+ items
61704
+ }
61705
+ ];
61706
+ })
60972
61707
  );
60973
61708
  }
60974
61709
  function resolveCollectionItems(collectionId, items, postBySlug, pageBySlug, frontPage) {
@@ -61007,12 +61742,57 @@ function buildCollectionPageSummary(page, frontPage) {
61007
61742
  excerpt: page.excerpt || "",
61008
61743
  featured_image: page.featured_image || "",
61009
61744
  ...page.featured_media ? { featured_media: { ...page.featured_media } } : {},
61010
- meta: page.meta
61745
+ meta: page.meta,
61746
+ data: page.data
61747
+ };
61748
+ }
61749
+ function attachCollectionCursors(posts, pages, collections) {
61750
+ const postTargets = new Map(posts.map((post) => [post.slug, post]));
61751
+ const pageTargets = new Map(pages.map((page) => [page.slug, page]));
61752
+ for (const [collectionId, collection] of Object.entries(collections || {})) {
61753
+ const items = Array.isArray(collection.items) ? collection.items : [];
61754
+ items.forEach((item, index) => {
61755
+ const target = item.type === "post" ? postTargets.get(item.slug) : item.type === "page" ? pageTargets.get(item.slug) : null;
61756
+ if (!target) {
61757
+ return;
61758
+ }
61759
+ target.collection_cursors = target.collection_cursors || {};
61760
+ target.collection_cursors[collectionId] = buildCollectionCursor(collectionId, collection, items, index);
61761
+ });
61762
+ }
61763
+ }
61764
+ function buildCollectionCursor(collectionId, collection, items, index) {
61765
+ const count = items.length;
61766
+ return {
61767
+ collection_id: collectionId,
61768
+ collection_title: collection.title || "",
61769
+ index,
61770
+ position: index + 1,
61771
+ count,
61772
+ first: index === 0,
61773
+ last: index === count - 1,
61774
+ prev: index > 0 ? buildCollectionCursorItemSummary(items[index - 1]) : null,
61775
+ next: index < count - 1 ? buildCollectionCursorItemSummary(items[index + 1]) : null
61011
61776
  };
61012
61777
  }
61013
- function buildFrontPageRoute(frontPage, pages, effectivePostIndexEnabled, postIndexBasePath) {
61778
+ function buildCollectionCursorItemSummary(item) {
61779
+ if (!item) {
61780
+ return null;
61781
+ }
61782
+ return {
61783
+ type: item.type,
61784
+ title: item.title,
61785
+ slug: item.slug,
61786
+ url: item.url,
61787
+ excerpt: item.excerpt || "",
61788
+ featured_image: item.featured_image || "",
61789
+ meta: item.meta,
61790
+ data: item.data
61791
+ };
61792
+ }
61793
+ function buildFrontPageRoute(frontPage, pages, effectivePostIndexEnabled, post_indexBasePath) {
61014
61794
  if (frontPage.type === "theme_index") {
61015
- if (effectivePostIndexEnabled && postIndexBasePath === "/") {
61795
+ if (effectivePostIndexEnabled && post_indexBasePath === "/") {
61016
61796
  return null;
61017
61797
  }
61018
61798
  return {
@@ -61050,7 +61830,7 @@ function buildPostIndexRoutes(options2) {
61050
61830
  return [];
61051
61831
  }
61052
61832
  if (!options2.paginate) {
61053
- const items = options2.items.slice(0, options2.postsPerPage);
61833
+ const items = options2.items.slice(0, options2.posts_per_page);
61054
61834
  return [{
61055
61835
  path: options2.basePath,
61056
61836
  route_type: "post_index",
@@ -61064,7 +61844,7 @@ function buildPostIndexRoutes(options2) {
61064
61844
  }
61065
61845
  return buildPaginatedCollection({
61066
61846
  items: options2.items,
61067
- postsPerPage: options2.postsPerPage,
61847
+ posts_per_page: options2.posts_per_page,
61068
61848
  basePath: options2.basePath,
61069
61849
  outputStyle: options2.outputStyle
61070
61850
  }).map((entry) => ({
@@ -61143,7 +61923,7 @@ function resolveWidgetItem(item, previewData, renderData, widgetAreaId, index) {
61143
61923
  }
61144
61924
  function resolveRecentPostsWidget(baseWidget, settings, renderData) {
61145
61925
  const limit = clampInteger(settings?.limit, 5, 1, 20);
61146
- const items = renderData.posts.slice(0, limit).map((post) => ({
61926
+ const items = renderData.posts.filter((post) => !isDelistedDocument(post)).slice(0, limit).map((post) => ({
61147
61927
  title: post.title,
61148
61928
  url: `/posts/${encodeSlugSegment(post.slug)}/`,
61149
61929
  published_at: post.published_at,
@@ -61312,7 +62092,9 @@ function preparePost(post, site, authorsById, categoriesBySlug, tagsBySlug, them
61312
62092
  featured_image: post.featured_image,
61313
62093
  ...post.featured_media ? { featured_media: { ...post.featured_media } } : {},
61314
62094
  meta: post.meta,
62095
+ data: post.data,
61315
62096
  status: post.status,
62097
+ discoverability: post.discoverability,
61316
62098
  allow_comments: post.allow_comments,
61317
62099
  category_slugs: post.category_slugs,
61318
62100
  tag_slugs: post.tag_slugs,
@@ -61329,7 +62111,7 @@ function preparePost(post, site, authorsById, categoriesBySlug, tagsBySlug, them
61329
62111
  published_at: formatTimestamp(post.published_at_iso, site),
61330
62112
  updated_at: formatTimestamp(post.updated_at_iso, site),
61331
62113
  reading_time: calculateReadingTime(renderedDocument.html),
61332
- comments_enabled: themeSupportsComments && site.disallowComments !== true && post.allow_comments === true
62114
+ comments_enabled: themeSupportsComments && site.disallow_comments !== true && post.allow_comments === true
61333
62115
  };
61334
62116
  }
61335
62117
  function buildCommentPolicyManifest(posts) {
@@ -61348,7 +62130,7 @@ function buildTaxonomyRoutes(options2) {
61348
62130
  }
61349
62131
  const paginated = buildPaginatedCollection({
61350
62132
  items: matchedPosts,
61351
- postsPerPage: options2.postsPerPage,
62133
+ posts_per_page: options2.posts_per_page,
61352
62134
  basePath: options2.buildBasePath(item),
61353
62135
  outputStyle: options2.outputStyle
61354
62136
  });
@@ -61369,13 +62151,13 @@ function buildTaxonomyRoutes(options2) {
61369
62151
  function buildPaginatedCollection(options2) {
61370
62152
  const basePath = normalizePaginationBasePath(options2.basePath);
61371
62153
  const outputStyle = PERMALINK_OUTPUT_STYLES.has(options2.outputStyle) ? options2.outputStyle : DEFAULT_PERMALINKS.output_style;
61372
- const postsPerPage = Number.isInteger(options2.postsPerPage) && options2.postsPerPage > 0 ? options2.postsPerPage : DEFAULT_POSTS_PER_PAGE;
62154
+ const posts_per_page = Number.isInteger(options2.posts_per_page) && options2.posts_per_page > 0 ? options2.posts_per_page : DEFAULT_POSTS_PER_PAGE;
61373
62155
  const totalPosts = options2.items.length;
61374
- const totalPages = Math.max(1, Math.ceil(totalPosts / postsPerPage));
62156
+ const totalPages = Math.max(1, Math.ceil(totalPosts / posts_per_page));
61375
62157
  const pages = [];
61376
62158
  for (let page = 1; page <= totalPages; page += 1) {
61377
- const start = (page - 1) * postsPerPage;
61378
- const end = start + postsPerPage;
62159
+ const start = (page - 1) * posts_per_page;
62160
+ const end = start + posts_per_page;
61379
62161
  pages.push({
61380
62162
  path: buildPaginatedPath(basePath, page),
61381
62163
  page,
@@ -61523,6 +62305,8 @@ function buildStructuredPostSummary(post) {
61523
62305
  reading_time: post.reading_time,
61524
62306
  featured_image: post.featured_image,
61525
62307
  ...post.featured_media ? { featured_media: { ...post.featured_media } } : {},
62308
+ meta: post.meta,
62309
+ data: post.data,
61526
62310
  author: {
61527
62311
  display_name: post.author?.display_name || "",
61528
62312
  avatar: post.author?.avatar || "",
@@ -61542,7 +62326,8 @@ function buildAdjacentPostSummary(post) {
61542
62326
  url: post.url,
61543
62327
  excerpt: post.excerpt,
61544
62328
  published_at: post.published_at,
61545
- published_at_iso: post.published_at_iso
62329
+ published_at_iso: post.published_at_iso,
62330
+ data: post.data
61546
62331
  };
61547
62332
  }
61548
62333
  function buildArchiveGroups(posts, postBySlug) {
@@ -61615,40 +62400,22 @@ function formatArchiveLabel(date, site) {
61615
62400
  function formatTimestamp(value, site) {
61616
62401
  const date = toDate(value);
61617
62402
  const locale = normalizeLocale(site.locale || DEFAULT_LOCALE);
61618
- const dateFormat = normalizeNonEmptyString(site.dateFormat, DEFAULT_DATE_FORMAT);
61619
- const timeFormat = typeof site.timeFormat === "string" ? site.timeFormat : DEFAULT_TIME_FORMAT;
62403
+ const dateStyle = DATETIME_STYLES.has(site.date_style) ? site.date_style : DEFAULT_DATE_STYLE;
62404
+ const timeStyle = DATETIME_STYLES.has(site.time_style) ? site.time_style : DEFAULT_TIME_STYLE;
61620
62405
  const siteTimezone = normalizeNonEmptyString(site.timezone, DEFAULT_TIMEZONE);
61621
- const dateParts = new Intl.DateTimeFormat(locale, {
61622
- timeZone: siteTimezone,
61623
- year: "numeric",
61624
- month: dateFormat === "MMMM D, YYYY" ? "long" : "2-digit",
61625
- day: "2-digit"
61626
- }).formatToParts(date);
61627
- const year = dateParts.find((part) => part.type === "year")?.value || "";
61628
- const month = dateParts.find((part) => part.type === "month")?.value || "";
61629
- const day = dateParts.find((part) => part.type === "day")?.value || "";
61630
- let formattedDate = `${year}-${month}-${day}`;
61631
- if (dateFormat === "DD/MM/YYYY") {
61632
- formattedDate = `${day}/${month}/${year}`;
61633
- } else if (dateFormat === "MM/DD/YYYY") {
61634
- formattedDate = `${month}/${day}/${year}`;
61635
- } else if (dateFormat === "MMMM D, YYYY") {
61636
- formattedDate = `${month} ${String(Number(day))}, ${year}`;
61637
- }
61638
- if (!timeFormat) {
61639
- return formattedDate;
61640
- }
61641
- const timeParts = new Intl.DateTimeFormat(locale, {
61642
- timeZone: siteTimezone,
61643
- hour: "2-digit",
61644
- minute: "2-digit",
61645
- hour12: timeFormat === "hh:mm A"
61646
- }).formatToParts(date);
61647
- const hour = timeParts.find((part) => part.type === "hour")?.value || "";
61648
- const minute = timeParts.find((part) => part.type === "minute")?.value || "";
61649
- const dayPeriod = (timeParts.find((part) => part.type === "dayPeriod")?.value || "").toUpperCase();
61650
- const formattedTime = timeFormat === "hh:mm A" ? `${hour}:${minute}${dayPeriod ? ` ${dayPeriod}` : ""}` : `${hour}:${minute}`;
61651
- return `${formattedDate} ${formattedTime}`.trim();
62406
+ if (dateStyle === "none" && timeStyle === "none") {
62407
+ return "";
62408
+ }
62409
+ const options2 = {
62410
+ timeZone: siteTimezone
62411
+ };
62412
+ if (dateStyle !== "none") {
62413
+ options2.dateStyle = dateStyle;
62414
+ }
62415
+ if (timeStyle !== "none") {
62416
+ options2.timeStyle = timeStyle;
62417
+ }
62418
+ return new Intl.DateTimeFormat(locale, options2).format(date);
61652
62419
  }
61653
62420
  function calculateReadingTime(html) {
61654
62421
  const plainText = String(html || "").replace(/<[^>]*>/g, " ");
@@ -61729,11 +62496,12 @@ async function normalizeAndValidateThemePackage(themePackage) {
61729
62496
  ...themePackage.metadata.author ? { author: themePackage.metadata.author } : {},
61730
62497
  ...themePackage.metadata.description ? { description: themePackage.metadata.description } : {},
61731
62498
  ...themePackage.metadata.thumbnail ? { thumbnail: themePackage.metadata.thumbnail } : {},
62499
+ ...themePackage.metadata.links ? { links: themePackage.metadata.links } : {},
61732
62500
  ...themePackage.metadata.features ? { features: themePackage.metadata.features } : {},
61733
- ...themePackage.metadata.menuSlots ? { menuSlots: themePackage.metadata.menuSlots } : {},
61734
- ...themePackage.metadata.widgetAreas ? { widgetAreas: themePackage.metadata.widgetAreas } : {},
61735
- ...themePackage.metadata.siteMeta ? { siteMeta: themePackage.metadata.siteMeta } : {},
61736
- ...themePackage.metadata.collectionSlots ? { collectionSlots: themePackage.metadata.collectionSlots } : {}
62501
+ ...themePackage.metadata.menu_slots ? { menu_slots: themePackage.metadata.menu_slots } : {},
62502
+ ...themePackage.metadata.widget_areas ? { widget_areas: themePackage.metadata.widget_areas } : {},
62503
+ ...themePackage.metadata.site_meta ? { site_meta: themePackage.metadata.site_meta } : {},
62504
+ ...themePackage.metadata.collection_slots ? { collection_slots: themePackage.metadata.collection_slots } : {}
61737
62505
  }));
61738
62506
  for (const [templateName, templateContent] of themePackage.templates.entries()) {
61739
62507
  fileMap.set(`${templateName}.html`, templateContent);
@@ -61746,7 +62514,7 @@ async function normalizeAndValidateThemePackage(themePackage) {
61746
62514
  }
61747
62515
  const validation = await validateThemeFiles(fileMap);
61748
62516
  if (!validation.ok) {
61749
- throw new Error(`Theme validation failed: ${validation.errors[0]?.message || "Unknown error"}`);
62517
+ throw new Error(formatThemeValidationFailure(validation));
61750
62518
  }
61751
62519
  if (!validation.manifest) {
61752
62520
  throw new Error("Theme validation failed: normalized manifest not available");
@@ -61758,6 +62526,55 @@ async function normalizeAndValidateThemePackage(themePackage) {
61758
62526
  assets: themePackage.assets
61759
62527
  };
61760
62528
  }
62529
+ function formatThemeValidationFailure(validation) {
62530
+ const blocks = [
62531
+ [
62532
+ "Theme validation failed",
62533
+ `Errors: ${validation.errors.length}`,
62534
+ `Checked files: ${validation.checkedFiles}`
62535
+ ].join("\n"),
62536
+ ...validation.errors.map((issue3) => formatThemeValidationIssue(issue3))
62537
+ ];
62538
+ return blocks.join("\n\n");
62539
+ }
62540
+ function formatThemeValidationIssue(issue3) {
62541
+ if (!issue3) {
62542
+ return "Reason: Unknown error";
62543
+ }
62544
+ const lines = [`ERROR ${issue3.code || "THEME_VALIDATION_ERROR"}`];
62545
+ const location = splitIssuePath(issue3.path);
62546
+ if (location.file) {
62547
+ lines.push(`File: ${location.file}`);
62548
+ }
62549
+ if (location.path) {
62550
+ lines.push(`Path: ${location.path}`);
62551
+ }
62552
+ if (Number.isInteger(issue3.line) && Number.isInteger(issue3.column)) {
62553
+ lines.push(`Line: ${issue3.line}, Column: ${issue3.column}`);
62554
+ }
62555
+ if (issue3.category) {
62556
+ lines.push(`Category: ${issue3.category}`);
62557
+ }
62558
+ lines.push(`Reason: ${issue3.message || "Unknown error"}`);
62559
+ if (issue3.snippet) {
62560
+ const lineLabel = Number.isInteger(issue3.line) ? String(issue3.line) : "";
62561
+ lines.push("", `${lineLabel} | ${issue3.snippet.line}`, `${" ".repeat(lineLabel.length)} | ${issue3.snippet.pointer}`);
62562
+ }
62563
+ if (issue3.hint) {
62564
+ lines.push("", "Hint:", issue3.hint);
62565
+ }
62566
+ return lines.join("\n");
62567
+ }
62568
+ function splitIssuePath(issuePath) {
62569
+ const normalizedPath = String(issuePath || "");
62570
+ if (normalizedPath.startsWith("theme.json.")) {
62571
+ return {
62572
+ file: "theme.json",
62573
+ path: normalizedPath.slice("theme.json.".length)
62574
+ };
62575
+ }
62576
+ return { file: normalizedPath, path: "" };
62577
+ }
61761
62578
  function normalizeThemePackageMetadata(sourceMetadata, manifest) {
61762
62579
  return {
61763
62580
  ...manifest,
@@ -61827,6 +62644,7 @@ function buildPageMeta(site, options2 = {}) {
61827
62644
  const ogType = normalizeNonEmptyString(options2.ogType, "website");
61828
62645
  const publishedTime = normalizeOptionalString(options2.publishedTime);
61829
62646
  const modifiedTime = normalizeOptionalString(options2.modifiedTime);
62647
+ const robotsNoindex = options2.robotsNoindex === true;
61830
62648
  const meta = {
61831
62649
  title: escapeHtml2(resolvedTitle),
61832
62650
  description: resolvedDescription ? escapeHtml2(resolvedDescription) : "",
@@ -61838,7 +62656,8 @@ function buildPageMeta(site, options2 = {}) {
61838
62656
  og_site_name: escapeHtml2(site.title),
61839
62657
  og_image: ogImage ? escapeHtml2(ogImage) : "",
61840
62658
  article_published_time: publishedTime ? escapeHtml2(publishedTime) : "",
61841
- article_modified_time: modifiedTime ? escapeHtml2(modifiedTime) : ""
62659
+ article_modified_time: modifiedTime ? escapeHtml2(modifiedTime) : "",
62660
+ robots_noindex: robotsNoindex
61842
62661
  };
61843
62662
  return {
61844
62663
  ...meta,
@@ -61855,6 +62674,9 @@ function buildMetaHeadTags(meta) {
61855
62674
  if (meta.description) {
61856
62675
  tags.push(`<meta name="description" content="${meta.description}">`);
61857
62676
  }
62677
+ if (meta.robots_noindex) {
62678
+ tags.push('<meta name="robots" content="noindex">');
62679
+ }
61858
62680
  if (meta.canonical_url) {
61859
62681
  tags.push(`<link rel="canonical" href="${meta.canonical_url}">`);
61860
62682
  }
@@ -61895,7 +62717,7 @@ function resolveMetaImageUrl(image2) {
61895
62717
  }
61896
62718
  return "";
61897
62719
  }
61898
- function normalizeMediaField(value, mediaBaseUrl) {
62720
+ function normalizeMediaField(value, media_base_url) {
61899
62721
  if (value === void 0) {
61900
62722
  return void 0;
61901
62723
  }
@@ -61906,7 +62728,7 @@ function normalizeMediaField(value, mediaBaseUrl) {
61906
62728
  if (isAbsoluteUrl(normalizedValue)) {
61907
62729
  return normalizeAbsoluteUrl(normalizedValue, SAFE_MEDIA_PROTOCOLS);
61908
62730
  }
61909
- const normalizedBaseUrl = normalizeOptionalString(mediaBaseUrl);
62731
+ const normalizedBaseUrl = normalizeOptionalString(media_base_url);
61910
62732
  if (!normalizedBaseUrl) {
61911
62733
  return normalizedValue;
61912
62734
  }
@@ -62111,6 +62933,9 @@ function assertPlannedOutputPathsSafe(state) {
62111
62933
  ...state.assetOutputs.map((assetOutput) => assetOutput.path),
62112
62934
  COMMENT_POLICY_OUTPUT_PATH
62113
62935
  ];
62936
+ if (shouldGenerateSearchArtifacts(state, state.options)) {
62937
+ plannedPaths.push(SEARCH_INDEX_OUTPUT_PATH, SEARCH_ADAPTER_OUTPUT_PATH, SEARCH_PAGEFIND_ADAPTER_OUTPUT_PATH);
62938
+ }
62114
62939
  if (state.options.generateSpecialFiles) {
62115
62940
  plannedPaths.push("404.html");
62116
62941
  if (shouldGenerateRobotsTxt(state.options)) {
@@ -62191,6 +63016,7 @@ function sha256(content) {
62191
63016
  }
62192
63017
  function injectSiteCustomizations(html, state) {
62193
63018
  let next = injectFaviconLinks(html, state.favicon);
63019
+ next = injectGeneratorMeta(next, state.exposeGenerator);
62194
63020
  next = injectCustomCssAssetLink(next, state.customCssHref);
62195
63021
  next = injectCustomHtml(next, state.customHtml);
62196
63022
  return next;
@@ -62222,6 +63048,12 @@ function buildFaviconLinks(favicon) {
62222
63048
  }
62223
63049
  return lines.join("\n");
62224
63050
  }
63051
+ function injectGeneratorMeta(html, exposeGenerator) {
63052
+ if (exposeGenerator === false) {
63053
+ return html;
63054
+ }
63055
+ return html.replace("</head>", ' <meta name="generator" content="ZeroPress">\n</head>');
63056
+ }
62225
63057
  function injectCustomCssAssetLink(html, href) {
62226
63058
  if (!normalizeOptionalString(href)) {
62227
63059
  return html;
@@ -62243,9 +63075,464 @@ function injectCustomHtml(html, customHtml) {
62243
63075
  }
62244
63076
  return next;
62245
63077
  }
62246
- function buildSitemapXml(site, emitted, generatedAt) {
63078
+ function buildSearchIndexJson(state) {
63079
+ return `${JSON.stringify(buildSearchIndexItems(state), null, 2)}
63080
+ `;
63081
+ }
63082
+ function buildSearchIndexItems(state) {
63083
+ const posts = state.renderData.posts.filter((post) => post.status === "published" && !isDelistedDocument(post)).map((post) => ({
63084
+ id: `post:${post.slug}`,
63085
+ type: "post",
63086
+ title: post.title,
63087
+ url: post.url,
63088
+ excerpt: normalizeSearchText(post.excerpt),
63089
+ headings: buildSearchHeadings(post.toc),
63090
+ categories: Array.isArray(post.categories) ? post.categories.map((category) => category.name).filter(Boolean) : [],
63091
+ tags: Array.isArray(post.tags) ? post.tags.map((tag) => tag.name).filter(Boolean) : [],
63092
+ published_at_iso: normalizeIsoTimestamp(post.published_at_iso),
63093
+ updated_at_iso: normalizeIsoTimestamp(post.updated_at_iso),
63094
+ content_text: htmlToSearchText(post.html)
63095
+ }));
63096
+ const frontPagePage = state.renderData.frontPageRoute?.front_page_type === "page" ? state.renderData.frontPageRoute.page : null;
63097
+ const frontPageItems = frontPagePage && frontPagePage.status === "published" && !isDelistedDocument(frontPagePage) ? [buildSearchPageItem(frontPagePage, "/")] : [];
63098
+ const pageItems = state.renderData.pages.filter((page) => page.status === "published" && !isDelistedDocument(page)).map((page) => buildSearchPageItem(page, page.url));
63099
+ return [...posts, ...frontPageItems, ...pageItems];
63100
+ }
63101
+ function buildSearchPageItem(page, url) {
63102
+ return {
63103
+ id: `page:${page.slug}`,
63104
+ type: "page",
63105
+ title: page.title,
63106
+ url,
63107
+ excerpt: normalizeSearchText(page.excerpt),
63108
+ headings: buildSearchHeadings(page.toc),
63109
+ categories: [],
63110
+ tags: [],
63111
+ published_at_iso: "",
63112
+ updated_at_iso: "",
63113
+ content_text: htmlToSearchText(page.html)
63114
+ };
63115
+ }
63116
+ function buildSearchHeadings(toc) {
63117
+ return Array.isArray(toc) ? toc.map((item) => normalizeSearchText(item?.title)).filter(Boolean) : [];
63118
+ }
63119
+ function htmlToSearchText(html) {
63120
+ return normalizeSearchText(decodeHtmlEntities(
63121
+ String(html || "").replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, " ").replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, " ").replace(/<!--[\s\S]*?-->/g, " ").replace(/<[^>]+>/g, " ")
63122
+ ));
63123
+ }
63124
+ function decodeHtmlEntities(value) {
63125
+ const namedEntities = {
63126
+ amp: "&",
63127
+ lt: "<",
63128
+ gt: ">",
63129
+ quot: '"',
63130
+ apos: "'",
63131
+ nbsp: " "
63132
+ };
63133
+ return String(value || "").replace(/&(#x[0-9a-fA-F]+|#\d+|[a-zA-Z][a-zA-Z0-9]+);/g, (match2, entity2) => {
63134
+ if (entity2.startsWith("#x")) {
63135
+ const codePoint = Number.parseInt(entity2.slice(2), 16);
63136
+ return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : match2;
63137
+ }
63138
+ if (entity2.startsWith("#")) {
63139
+ const codePoint = Number.parseInt(entity2.slice(1), 10);
63140
+ return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : match2;
63141
+ }
63142
+ return Object.prototype.hasOwnProperty.call(namedEntities, entity2) ? namedEntities[entity2] : match2;
63143
+ });
63144
+ }
63145
+ function normalizeSearchText(value) {
63146
+ return String(value || "").replace(/\s+/g, " ").trim();
63147
+ }
63148
+ function buildSearchAdapterJs() {
63149
+ const fieldWeightsJson = JSON.stringify(SEARCH_FIELD_WEIGHTS, null, 2);
63150
+ return `const FIELD_WEIGHTS = ${fieldWeightsJson};
63151
+ const FIELD_NAMES = Object.keys(FIELD_WEIGHTS);
63152
+ const RECENCY_BOOST_MAX = ${SEARCH_RECENCY_BOOST_MAX};
63153
+ const DEFAULT_LIMIT = 20;
63154
+ const BM25_K1 = 1.2;
63155
+ const BM25_B = 0.75;
63156
+
63157
+ let preparedIndexPromise;
63158
+
63159
+ export async function preload() {
63160
+ await loadPreparedIndex();
63161
+ }
63162
+
63163
+ export async function search(query, options = {}) {
63164
+ const prepared = await loadPreparedIndex();
63165
+ const terms = tokenize(query);
63166
+ const phrase = normalizeText(query);
63167
+ if (terms.length === 0 && !phrase) {
63168
+ return { results: [] };
63169
+ }
63170
+
63171
+ const hits = [];
63172
+ for (const document of prepared.documents) {
63173
+ const score = scoreDocument(document, terms, phrase, prepared);
63174
+ if (score > 0) {
63175
+ hits.push({ document, score });
63176
+ }
63177
+ }
63178
+
63179
+ const limit = normalizeLimit(options.limit);
63180
+ hits.sort((left, right) => right.score - left.score || left.document.raw.title.localeCompare(right.document.raw.title));
63181
+
63182
+ return {
63183
+ results: hits.slice(0, limit).map((hit) => ({
63184
+ id: hit.document.raw.id,
63185
+ score: Number(hit.score.toFixed(6)),
63186
+ data: async () => buildResultData(hit.document.raw, query),
63187
+ })),
63188
+ };
63189
+ }
63190
+
63191
+ async function loadPreparedIndex() {
63192
+ if (!preparedIndexPromise) {
63193
+ preparedIndexPromise = fetch(new URL('./search.json', import.meta.url))
63194
+ .then((response) => {
63195
+ if (!response.ok) {
63196
+ throw new Error('ZeroPress search index not found');
63197
+ }
63198
+ return response.json();
63199
+ })
63200
+ .then((items) => prepareIndex(Array.isArray(items) ? items : []));
63201
+ }
63202
+
63203
+ return preparedIndexPromise;
63204
+ }
63205
+
63206
+ function prepareIndex(items) {
63207
+ const documents = items.map((item) => prepareDocument(item));
63208
+ const documentFrequencies = new Map();
63209
+ const averageLengths = Object.fromEntries(FIELD_NAMES.map((fieldName) => [fieldName, 1]));
63210
+ const newestPostTime = documents.reduce((newest, document) => {
63211
+ if (document.raw.type !== 'post') {
63212
+ return newest;
63213
+ }
63214
+ return Math.max(newest, document.publishedTime || 0);
63215
+ }, 0);
63216
+
63217
+ for (const document of documents) {
63218
+ const seenTerms = new Set();
63219
+ for (const fieldName of FIELD_NAMES) {
63220
+ for (const term of document.fieldTokens[fieldName]) {
63221
+ seenTerms.add(term);
63222
+ }
63223
+ }
63224
+ for (const term of seenTerms) {
63225
+ documentFrequencies.set(term, (documentFrequencies.get(term) || 0) + 1);
63226
+ }
63227
+ }
63228
+
63229
+ for (const fieldName of FIELD_NAMES) {
63230
+ const total = documents.reduce((sum, document) => sum + document.fieldLengths[fieldName], 0);
63231
+ averageLengths[fieldName] = documents.length > 0 ? Math.max(1, total / documents.length) : 1;
63232
+ }
63233
+
63234
+ return {
63235
+ documents,
63236
+ documentFrequencies,
63237
+ averageLengths,
63238
+ documentCount: documents.length,
63239
+ newestPostTime,
63240
+ };
63241
+ }
63242
+
63243
+ function prepareDocument(item) {
63244
+ const raw = normalizeItem(item);
63245
+ const fields = {
63246
+ title: raw.title,
63247
+ headings: raw.headings.join(' '),
63248
+ tags: raw.tags.join(' '),
63249
+ categories: raw.categories.join(' '),
63250
+ excerpt: raw.excerpt,
63251
+ content_text: raw.content_text,
63252
+ };
63253
+ const fieldTexts = {};
63254
+ const fieldTokens = {};
63255
+ const fieldTermCounts = {};
63256
+ const fieldLengths = {};
63257
+
63258
+ for (const [fieldName, value] of Object.entries(fields)) {
63259
+ const normalizedText = normalizeText(value);
63260
+ const tokens = tokenize(normalizedText);
63261
+ fieldTexts[fieldName] = normalizedText;
63262
+ fieldTokens[fieldName] = tokens;
63263
+ fieldTermCounts[fieldName] = countTerms(tokens);
63264
+ fieldLengths[fieldName] = Math.max(1, tokens.length);
63265
+ }
63266
+
63267
+ return {
63268
+ raw,
63269
+ fieldTexts,
63270
+ fieldTokens,
63271
+ fieldTermCounts,
63272
+ fieldLengths,
63273
+ publishedTime: Date.parse(raw.published_at_iso) || 0,
63274
+ };
63275
+ }
63276
+
63277
+ function normalizeItem(item) {
63278
+ return {
63279
+ id: String(item && item.id || ''),
63280
+ type: item && item.type === 'page' ? 'page' : 'post',
63281
+ title: String(item && item.title || ''),
63282
+ url: String(item && item.url || ''),
63283
+ excerpt: String(item && item.excerpt || ''),
63284
+ headings: Array.isArray(item && item.headings) ? item.headings.map(String) : [],
63285
+ categories: Array.isArray(item && item.categories) ? item.categories.map(String) : [],
63286
+ tags: Array.isArray(item && item.tags) ? item.tags.map(String) : [],
63287
+ published_at_iso: String(item && item.published_at_iso || ''),
63288
+ updated_at_iso: String(item && item.updated_at_iso || ''),
63289
+ content_text: String(item && item.content_text || ''),
63290
+ };
63291
+ }
63292
+
63293
+ function scoreDocument(document, terms, phrase, prepared) {
63294
+ let score = 0;
63295
+ const uniqueTerms = Array.from(new Set(terms));
63296
+
63297
+ for (const term of uniqueTerms) {
63298
+ const documentFrequency = prepared.documentFrequencies.get(term) || 0;
63299
+ const idf = Math.log(1 + (prepared.documentCount - documentFrequency + 0.5) / (documentFrequency + 0.5));
63300
+
63301
+ for (const fieldName of FIELD_NAMES) {
63302
+ const termFrequency = document.fieldTermCounts[fieldName].get(term) || 0;
63303
+ if (termFrequency === 0) {
63304
+ continue;
63305
+ }
63306
+
63307
+ const fieldLength = document.fieldLengths[fieldName];
63308
+ const averageLength = prepared.averageLengths[fieldName];
63309
+ const denominator = termFrequency + BM25_K1 * (1 - BM25_B + BM25_B * (fieldLength / averageLength));
63310
+ score += FIELD_WEIGHTS[fieldName] * idf * ((termFrequency * (BM25_K1 + 1)) / denominator);
63311
+ }
63312
+ }
63313
+
63314
+ if (phrase && phrase.length > 1) {
63315
+ for (const fieldName of FIELD_NAMES) {
63316
+ if (document.fieldTexts[fieldName].includes(phrase)) {
63317
+ score += FIELD_WEIGHTS[fieldName] * 0.6;
63318
+ }
63319
+ }
63320
+ }
63321
+
63322
+ if (score <= 0) {
63323
+ return 0;
63324
+ }
63325
+
63326
+ return score * (1 + recencyBoost(document, prepared.newestPostTime));
63327
+ }
63328
+
63329
+ function recencyBoost(document, newestPostTime) {
63330
+ if (document.raw.type !== 'post' || !document.publishedTime || !newestPostTime) {
63331
+ return 0;
63332
+ }
63333
+ const ageMs = Math.max(0, newestPostTime - document.publishedTime);
63334
+ const halfLifeMs = 180 * 24 * 60 * 60 * 1000;
63335
+ return RECENCY_BOOST_MAX * Math.exp(-ageMs / halfLifeMs);
63336
+ }
63337
+
63338
+ function buildResultData(item, query) {
63339
+ const excerpt = buildExcerpt(item, query);
63340
+ return {
63341
+ url: item.url,
63342
+ excerpt,
63343
+ plain_excerpt: excerpt,
63344
+ meta: {
63345
+ title: item.title,
63346
+ type: item.type,
63347
+ published_at_iso: item.published_at_iso,
63348
+ updated_at_iso: item.updated_at_iso,
63349
+ categories: item.categories,
63350
+ tags: item.tags,
63351
+ },
63352
+ sub_results: [],
63353
+ };
63354
+ }
63355
+
63356
+ function buildExcerpt(item, query) {
63357
+ const explicitExcerpt = String(item.excerpt || '').trim();
63358
+ if (explicitExcerpt) {
63359
+ return explicitExcerpt;
63360
+ }
63361
+
63362
+ const text = String(item.content_text || '').replace(/\\s+/g, ' ').trim();
63363
+ if (!text) {
63364
+ return '';
63365
+ }
63366
+
63367
+ const normalizedText = normalizeText(text);
63368
+ const terms = tokenize(query);
63369
+ const firstMatch = terms.map((term) => normalizedText.indexOf(term)).filter((index) => index >= 0).sort((a, b) => a - b)[0];
63370
+ const start = Math.max(0, (firstMatch || 0) - 80);
63371
+ const end = Math.min(text.length, start + 180);
63372
+ const prefix = start > 0 ? '...' : '';
63373
+ const suffix = end < text.length ? '...' : '';
63374
+ return prefix + text.slice(start, end).trim() + suffix;
63375
+ }
63376
+
63377
+ function countTerms(tokens) {
63378
+ const counts = new Map();
63379
+ for (const token of tokens) {
63380
+ counts.set(token, (counts.get(token) || 0) + 1);
63381
+ }
63382
+ return counts;
63383
+ }
63384
+
63385
+ function tokenize(value) {
63386
+ const text = normalizeText(value);
63387
+ if (!text) {
63388
+ return [];
63389
+ }
63390
+
63391
+ const tokens = [];
63392
+ if (typeof Intl !== 'undefined' && typeof Intl.Segmenter === 'function') {
63393
+ try {
63394
+ const segmenter = new Intl.Segmenter(undefined, { granularity: 'word' });
63395
+ for (const part of segmenter.segment(text)) {
63396
+ if (part.isWordLike && isUsefulToken(part.segment)) {
63397
+ tokens.push(part.segment);
63398
+ }
63399
+ }
63400
+ } catch {
63401
+ // Fall through to regex tokenization.
63402
+ }
63403
+ }
63404
+
63405
+ for (const match of text.matchAll(/[\\p{Letter}\\p{Number}]+/gu)) {
63406
+ if (isUsefulToken(match[0])) {
63407
+ tokens.push(match[0]);
63408
+ }
63409
+ }
63410
+
63411
+ for (const match of text.matchAll(/[\\p{Script=Han}\\p{Script=Hiragana}\\p{Script=Katakana}\\p{Script=Hangul}]+/gu)) {
63412
+ tokens.push(...buildNgrams(match[0], 2));
63413
+ }
63414
+
63415
+ return tokens;
63416
+ }
63417
+
63418
+ function buildNgrams(value, size) {
63419
+ const normalized = Array.from(value);
63420
+ if (normalized.length <= size) {
63421
+ return isUsefulToken(value) ? [value] : [];
63422
+ }
63423
+
63424
+ const tokens = [];
63425
+ for (let index = 0; index <= normalized.length - size; index += 1) {
63426
+ tokens.push(normalized.slice(index, index + size).join(''));
63427
+ }
63428
+ tokens.push(value);
63429
+ return tokens;
63430
+ }
63431
+
63432
+ function isUsefulToken(value) {
63433
+ const token = String(value || '').trim();
63434
+ return token.length > 1 || /^\\d$/.test(token);
63435
+ }
63436
+
63437
+ function normalizeText(value) {
63438
+ return String(value || '')
63439
+ .normalize('NFKC')
63440
+ .toLowerCase()
63441
+ .replace(/[\\u2018\\u2019]/g, "'")
63442
+ .replace(/[_-]+/g, ' ')
63443
+ .replace(/\\s+/g, ' ')
63444
+ .trim();
63445
+ }
63446
+
63447
+ function normalizeLimit(value) {
63448
+ return Number.isInteger(value) && value > 0 ? Math.min(value, 100) : DEFAULT_LIMIT;
63449
+ }
63450
+ `;
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
+ }
63533
+ function buildSitemapXml(site, emitted, generatedAt, stylesheetHref = "") {
62247
63534
  const entries = [
62248
- ...emitted.frontPage ? [{
63535
+ ...emitted.frontPage && emitted.frontPage.includeInSitemap !== false ? [{
62249
63536
  url: emitted.frontPage.url,
62250
63537
  changefreq: "daily",
62251
63538
  priority: 1
@@ -62277,7 +63564,10 @@ function buildSitemapXml(site, emitted, generatedAt) {
62277
63564
  <priority>${entry.priority.toFixed(1)}</priority>
62278
63565
  </url>`;
62279
63566
  }).join("\n");
62280
- return `<?xml version="1.0" encoding="UTF-8"?>
63567
+ const normalizedStylesheetHref = normalizeOptionalString(stylesheetHref);
63568
+ const stylesheet = normalizedStylesheetHref ? `
63569
+ <?xml-stylesheet type="text/xsl" href="${escapeXml(normalizedStylesheetHref)}"?>` : "";
63570
+ return `<?xml version="1.0" encoding="UTF-8"?>${stylesheet}
62281
63571
  <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
62282
63572
  ${body}
62283
63573
  </urlset>`;
@@ -62325,6 +63615,9 @@ function buildRobotsTxt(site) {
62325
63615
  function shouldGenerateRobotsTxt(options2) {
62326
63616
  return options2.generateSpecialFiles && options2.generateRobotsTxt !== false;
62327
63617
  }
63618
+ function shouldGenerateSearchArtifacts(state, options2) {
63619
+ return options2.generateSpecialFiles && state.previewData.site.search === true;
63620
+ }
62328
63621
  function getContentType(assetPath) {
62329
63622
  const ext = assetPath.split(".").pop()?.toLowerCase();
62330
63623
  const contentTypes = {
@@ -62400,7 +63693,7 @@ async function loadThemePackageFromDir(themeDir) {
62400
63693
  await readThemeDir(fs4, path4, themeDir, themeDir, fileMap);
62401
63694
  const validation = await validateThemeFiles(fileMap);
62402
63695
  if (!validation.ok) {
62403
- throw new Error(`Theme validation failed: ${validation.errors[0]?.message || "Unknown error"}`);
63696
+ throw new Error(formatThemeValidationFailure2(validation));
62404
63697
  }
62405
63698
  const rawThemeJson = String(fileMap.get("theme.json"));
62406
63699
  const themeJson = JSON.parse(rawThemeJson);
@@ -62440,6 +63733,55 @@ async function loadThemePackageFromDir(themeDir) {
62440
63733
  assets
62441
63734
  };
62442
63735
  }
63736
+ function formatThemeValidationFailure2(validation) {
63737
+ const blocks = [
63738
+ [
63739
+ "Theme validation failed",
63740
+ `Errors: ${validation.errors.length}`,
63741
+ `Checked files: ${validation.checkedFiles}`
63742
+ ].join("\n"),
63743
+ ...validation.errors.map((issue3) => formatThemeValidationIssue2(issue3))
63744
+ ];
63745
+ return blocks.join("\n\n");
63746
+ }
63747
+ function formatThemeValidationIssue2(issue3) {
63748
+ if (!issue3) {
63749
+ return "Reason: Unknown error";
63750
+ }
63751
+ const lines = [`ERROR ${issue3.code || "THEME_VALIDATION_ERROR"}`];
63752
+ const location = splitIssuePath2(issue3.path);
63753
+ if (location.file) {
63754
+ lines.push(`File: ${location.file}`);
63755
+ }
63756
+ if (location.path) {
63757
+ lines.push(`Path: ${location.path}`);
63758
+ }
63759
+ if (Number.isInteger(issue3.line) && Number.isInteger(issue3.column)) {
63760
+ lines.push(`Line: ${issue3.line}, Column: ${issue3.column}`);
63761
+ }
63762
+ if (issue3.category) {
63763
+ lines.push(`Category: ${issue3.category}`);
63764
+ }
63765
+ lines.push(`Reason: ${issue3.message || "Unknown error"}`);
63766
+ if (issue3.snippet) {
63767
+ const lineLabel = Number.isInteger(issue3.line) ? String(issue3.line) : "";
63768
+ lines.push("", `${lineLabel} | ${issue3.snippet.line}`, `${" ".repeat(lineLabel.length)} | ${issue3.snippet.pointer}`);
63769
+ }
63770
+ if (issue3.hint) {
63771
+ lines.push("", "Hint:", issue3.hint);
63772
+ }
63773
+ return lines.join("\n");
63774
+ }
63775
+ function splitIssuePath2(issuePath) {
63776
+ const normalizedPath = String(issuePath || "");
63777
+ if (normalizedPath.startsWith("theme.json.")) {
63778
+ return {
63779
+ file: "theme.json",
63780
+ path: normalizedPath.slice("theme.json.".length)
63781
+ };
63782
+ }
63783
+ return { file: normalizedPath, path: "" };
63784
+ }
62443
63785
  async function readThemeDir(fs4, path4, rootDir, currentDir, fileMap) {
62444
63786
  const entries = await fs4.readdir(currentDir, { withFileTypes: true });
62445
63787
  for (const entry of entries) {
@@ -62486,6 +63828,7 @@ var PUBLIC_FAVICON_FILES = Object.freeze({
62486
63828
  png: "favicon.png",
62487
63829
  apple_touch_icon: "apple-touch-icon.png"
62488
63830
  });
63831
+ var PUBLIC_SITEMAP_STYLESHEET_FILE = "sitemap.xsl";
62489
63832
  async function assertThemeDirectory(themeDir) {
62490
63833
  let stat;
62491
63834
  try {
@@ -62500,14 +63843,15 @@ async function assertThemeDirectory(themeDir) {
62500
63843
  throw new Error(`Theme path is not a directory: ${themeDir}`);
62501
63844
  }
62502
63845
  }
62503
- async function runBuild(themeDir, previewData, outDir) {
62504
- assertPublicPathDoesNotOverlap("Theme directory", themeDir);
62505
- assertPublicPathDoesNotOverlap("Output directory", outDir);
63846
+ async function runBuild(themeDir, previewData, outDir, options2 = {}) {
63847
+ const publicDir = resolvePublicDir(process.cwd(), options2.publicDir);
63848
+ assertPublicPathDoesNotOverlap("Theme directory", themeDir, process.cwd(), publicDir);
63849
+ assertPublicPathDoesNotOverlap("Output directory", outDir, process.cwd(), publicDir);
62506
63850
  await assertThemeDirectory(themeDir);
62507
63851
  await assertEmptyOutputDirectory(outDir);
62508
- const publicDir = resolvePublicDir();
62509
63852
  const hasPublicRobotsTxt = await publicRobotsTxtExists(publicDir);
62510
63853
  const publicFavicon = await discoverPublicFavicon(publicDir);
63854
+ const sitemapStylesheetHref = await discoverPublicSitemapStylesheet(publicDir);
62511
63855
  await copyPublicDirectory(publicDir, outDir);
62512
63856
  const writer = new GeneratedOutputWriter({ outDir });
62513
63857
  return buildSiteFromThemeDir({
@@ -62516,6 +63860,7 @@ async function runBuild(themeDir, previewData, outDir) {
62516
63860
  writer,
62517
63861
  options: {
62518
63862
  favicon: publicFavicon,
63863
+ sitemapStylesheetHref,
62519
63864
  generateRobotsTxt: !hasPublicRobotsTxt
62520
63865
  }
62521
63866
  });
@@ -62555,7 +63900,10 @@ async function assertEmptyOutputDirectory(outDir) {
62555
63900
  throw error;
62556
63901
  }
62557
63902
  }
62558
- function resolvePublicDir(cwd = process.cwd()) {
63903
+ function resolvePublicDir(cwd = process.cwd(), publicDir) {
63904
+ if (publicDir) {
63905
+ return path.resolve(cwd, publicDir);
63906
+ }
62559
63907
  const envValue = process.env[PUBLIC_DIR_ENV_NAME]?.trim();
62560
63908
  return path.resolve(cwd, envValue || DEFAULT_PUBLIC_DIR_NAME);
62561
63909
  }
@@ -62604,6 +63952,18 @@ async function discoverPublicFavicon(publicDir) {
62604
63952
  }
62605
63953
  return Object.keys(favicon).length ? favicon : void 0;
62606
63954
  }
63955
+ async function discoverPublicSitemapStylesheet(publicDir) {
63956
+ let stat;
63957
+ try {
63958
+ stat = await fs.lstat(path.join(publicDir, PUBLIC_SITEMAP_STYLESHEET_FILE));
63959
+ } catch (error) {
63960
+ if (error && (error.code === "ENOENT" || error.code === "ENOTDIR")) {
63961
+ return void 0;
63962
+ }
63963
+ throw error;
63964
+ }
63965
+ return stat.isFile() ? `/${PUBLIC_SITEMAP_STYLESHEET_FILE}` : void 0;
63966
+ }
62607
63967
  async function copyPublicEntries(sourceDir, targetDir) {
62608
63968
  const entries = await fs.readdir(sourceDir, { withFileTypes: true });
62609
63969
  for (const entry of entries) {
@@ -62628,8 +63988,7 @@ function shouldIgnorePublicEntry(name) {
62628
63988
  const lowerName = basename.toLowerCase();
62629
63989
  return basename.startsWith(".") || lowerName === "node_modules" || lowerName === "thumbs.db" || lowerName.endsWith(".key") || lowerName.endsWith(".pem");
62630
63990
  }
62631
- function assertPublicPathDoesNotOverlap(label, candidatePath, cwd = process.cwd()) {
62632
- const publicDir = resolvePublicDir(cwd);
63991
+ function assertPublicPathDoesNotOverlap(label, candidatePath, cwd = process.cwd(), publicDir = resolvePublicDir(cwd)) {
62633
63992
  const resolvedCandidate = path.resolve(cwd, candidatePath);
62634
63993
  if (!pathsOverlap(publicDir, resolvedCandidate)) {
62635
63994
  return;
@@ -62755,6 +64114,8 @@ async function runBuildPages(options2) {
62755
64114
  const cwd = path3.resolve(options2.cwd || process.cwd());
62756
64115
  const copyMarkdownSource = options2.copyMarkdownSource !== false;
62757
64116
  const sourceDir = path3.resolve(cwd, options2.source);
64117
+ const publicDirExplicit = hasExplicitPublicDir(options2);
64118
+ const publicDir = publicDirExplicit ? path3.resolve(cwd, options2.publicDir) : sourceDir;
62758
64119
  const destinationDir = path3.resolve(cwd, options2.destination);
62759
64120
  const generatedDir = path3.join(cwd, ".zeropress");
62760
64121
  const stagingDir = path3.join(cwd, STAGING_DIR);
@@ -62763,17 +64124,22 @@ async function runBuildPages(options2) {
62763
64124
  assertBuildPagesPathLayout({
62764
64125
  cwd,
62765
64126
  sourceDir,
64127
+ publicDir,
64128
+ publicDirExplicit,
62766
64129
  destinationDir,
62767
64130
  themeDir,
62768
64131
  generatedDir
62769
64132
  });
62770
64133
  await assertDirectory(sourceDir, "Source directory");
64134
+ await assertPublicDirectory(publicDir, publicDirExplicit);
64135
+ await assertDestinationPath(destinationDir);
62771
64136
  await fs3.rm(generatedDir, { recursive: true, force: true });
62772
64137
  await fs3.mkdir(generatedDir, { recursive: true });
62773
64138
  const env = {
62774
64139
  ...process.env,
62775
64140
  ZEROPRESS_BUILD_PAGES_SOURCE: sourceDir,
62776
- ZEROPRESS_PUBLIC_DIR: sourceDir,
64141
+ ZEROPRESS_BUILD_PAGES_PUBLIC_DIR: publicDir,
64142
+ ZEROPRESS_PUBLIC_DIR: publicDir,
62777
64143
  ZEROPRESS_SKIP_UNTITLED_MARKDOWN: String(Boolean(options2.skipUntitledMarkdown)),
62778
64144
  ZEROPRESS_COPY_MARKDOWN_SOURCE: String(copyMarkdownSource)
62779
64145
  };
@@ -62797,10 +64163,13 @@ async function runBuildPages(options2) {
62797
64163
  await fs3.rm(destinationDir, { recursive: true, force: true });
62798
64164
  await fs3.rm(stagingDir, { recursive: true, force: true });
62799
64165
  await fs3.mkdir(stagingDir, { recursive: true });
62800
- await copyPublicStaging(sourceDir, stagingDir, {
64166
+ await copyPublicStaging(publicDir, stagingDir, {
62801
64167
  excludePaths: [destinationDir, themeDir, generatedDir],
62802
64168
  copyMarkdownSource
62803
64169
  });
64170
+ if (copyMarkdownSource) {
64171
+ await copySourceMarkdownFiles(sourceDir, stagingDir, previewData);
64172
+ }
62804
64173
  const previousPublicDir = process.env.ZEROPRESS_PUBLIC_DIR;
62805
64174
  process.env.ZEROPRESS_PUBLIC_DIR = stagingDir;
62806
64175
  try {
@@ -62835,6 +64204,9 @@ function resolveThemeDir(cwd, options2) {
62835
64204
  }
62836
64205
  throw new Error(`Unknown bundled theme: ${options2.theme}`);
62837
64206
  }
64207
+ function hasExplicitPublicDir(options2) {
64208
+ return typeof options2.publicDir === "string" && Boolean(options2.publicDir.trim());
64209
+ }
62838
64210
  async function assertDirectory(dir, label) {
62839
64211
  let stat;
62840
64212
  try {
@@ -62849,17 +64221,78 @@ async function assertDirectory(dir, label) {
62849
64221
  throw new Error(`${label} is not a directory: ${dir}`);
62850
64222
  }
62851
64223
  }
62852
- function assertBuildPagesPathLayout({ cwd, sourceDir, destinationDir, themeDir, generatedDir }) {
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
+ }) {
62853
64267
  if (samePath(sourceDir, cwd)) {
62854
64268
  throw new Error(
62855
64269
  `Source directory must be a dedicated content directory, not the current working directory. Received: ${formatPath(cwd, sourceDir)}`
62856
64270
  );
62857
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
+ }
62858
64277
  assertNoPathOverlap(cwd, "Source directory", sourceDir, "internal .zeropress working directory", generatedDir);
62859
64278
  assertNoPathOverlap(cwd, "Destination directory", destinationDir, "internal .zeropress working directory", generatedDir);
62860
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
+ }
62861
64285
  assertNoPathOverlap(cwd, "Source directory", sourceDir, "destination directory", destinationDir);
62862
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
+ );
62863
64296
  }
62864
64297
  function assertNoPathOverlap(cwd, firstLabel, firstPath, secondLabel, secondPath) {
62865
64298
  if (!pathsOverlap2(firstPath, secondPath)) {
@@ -62895,6 +64328,52 @@ async function copyPublicStaging(sourceDir, targetDir, options2) {
62895
64328
  await fs3.copyFile(sourcePath, targetPath);
62896
64329
  }
62897
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
+ }
62898
64377
  function shouldIgnorePublicEntry2(name) {
62899
64378
  const basename = String(name || "");
62900
64379
  const lowerName = basename.toLowerCase();
@@ -62923,6 +64402,7 @@ function formatPath(cwd, targetPath) {
62923
64402
  // src/action.js
62924
64403
  var options = {
62925
64404
  source: input("source") || "./docs",
64405
+ publicDir: input("public-dir"),
62926
64406
  destination: input("destination") || "./_site",
62927
64407
  theme: input("theme") || "docs",
62928
64408
  themePath: input("theme-path"),
@@ -62940,7 +64420,7 @@ try {
62940
64420
  process.exitCode = 1;
62941
64421
  }
62942
64422
  function input(name) {
62943
- return process.env[`INPUT_${name.toUpperCase().replace(/-/g, "_")}`]?.trim() || "";
64423
+ return process.env[`INPUT_${name.toUpperCase()}`]?.trim() || "";
62944
64424
  }
62945
64425
  function booleanInput(name, fallback) {
62946
64426
  const value = input(name);