@zeropress/build-pages 0.5.5 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/action.js CHANGED
@@ -52242,15 +52242,25 @@ 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"];
52249
52249
  var PREVIEW_MENU_ID_PATTERN = /^[a-z][a-z0-9_-]{0,63}$/;
52250
52250
  var PREVIEW_WIDGET_AREA_ID_PATTERN = /^[a-z][a-z0-9_-]{0,63}$/;
52251
+ var PREVIEW_COLLECTION_ID_PATTERN = /^[a-z][a-z0-9_-]{0,63}$/;
52252
+ var PREVIEW_COLLECTION_ITEM_TYPES = ["post", "page"];
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"];
52251
52257
  var PREVIEW_PERMALINK_OUTPUT_STYLES = ["directory", "html-extension"];
52252
52258
  var PREVIEW_PERMALINK_FIELDS = ["posts", "pages", "categories", "tags"];
52253
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;
52254
52264
  var PREVIEW_PERMALINK_TOKENS = Object.freeze({
52255
52265
  posts: /* @__PURE__ */ new Set(["slug", "public_id", "year", "month", "day"]),
52256
52266
  pages: /* @__PURE__ */ new Set(["slug"]),
@@ -52259,7 +52269,7 @@ var PREVIEW_PERMALINK_TOKENS = Object.freeze({
52259
52269
  });
52260
52270
  function validatePreviewData(data) {
52261
52271
  const errors = [];
52262
- validateClosedObject(data, "", errors, ["$schema", "version", "generator", "generated_at", "site", "content", "menus", "widgets", "custom_css", "custom_html"]);
52272
+ validateClosedObject(data, "", errors, ["$schema", "version", "generator", "generated_at", "site", "content", "menus", "widgets", "collections", "custom_css", "custom_html"]);
52263
52273
  if (isObject(data)) {
52264
52274
  if (data.$schema !== void 0) {
52265
52275
  validateString(data.$schema, "$schema", "INVALID_SCHEMA_HINT", errors);
@@ -52269,8 +52279,15 @@ function validatePreviewData(data) {
52269
52279
  validateDateTimeString(data.generated_at, "generated_at", "INVALID_GENERATED_AT", errors);
52270
52280
  validateSite(data.site, "site", errors);
52271
52281
  validateContent(data.content, "content", errors);
52272
- validateMenus(data.menus, "menus", errors);
52273
- validateWidgets(data.widgets, "widgets", errors);
52282
+ if (data.menus !== void 0) {
52283
+ validateMenus(data.menus, "menus", errors);
52284
+ }
52285
+ if (data.widgets !== void 0) {
52286
+ validateWidgets(data.widgets, "widgets", errors);
52287
+ }
52288
+ if (data.collections !== void 0) {
52289
+ validateCollections(data.collections, "collections", errors);
52290
+ }
52274
52291
  if (data.custom_css !== void 0) {
52275
52292
  validateCustomCss(data.custom_css, "custom_css", errors);
52276
52293
  }
@@ -52293,23 +52310,58 @@ function assertPreviewData(data) {
52293
52310
  return data;
52294
52311
  }
52295
52312
  function validateSite(site, path4, errors) {
52296
- validateObject(site, path4, "INVALID_SITE", errors);
52313
+ validateClosedObject(site, path4, errors, [
52314
+ "title",
52315
+ "description",
52316
+ "url",
52317
+ "media_base_url",
52318
+ "media_delivery_mode",
52319
+ "favicon",
52320
+ "expose_generator",
52321
+ "locale",
52322
+ "posts_per_page",
52323
+ "datetime_display",
52324
+ "date_style",
52325
+ "time_style",
52326
+ "timezone",
52327
+ "disallow_comments",
52328
+ "indexing",
52329
+ "permalinks",
52330
+ "front_page",
52331
+ "post_index",
52332
+ "footer",
52333
+ "meta"
52334
+ ]);
52297
52335
  if (!isObject(site)) {
52298
52336
  return;
52299
52337
  }
52300
52338
  validateNonEmptyString(site.title, `${path4}.title`, "INVALID_SITE_TITLE", errors);
52301
52339
  validateString(site.description, `${path4}.description`, "INVALID_SITE_DESCRIPTION", errors);
52302
52340
  validateSiteUri(site.url, `${path4}.url`, "INVALID_SITE_URL", errors);
52303
- validateSiteUri(site.mediaBaseUrl, `${path4}.mediaBaseUrl`, "INVALID_SITE_MEDIA_BASE_URL", errors);
52341
+ validateSiteUri(site.media_base_url, `${path4}.media_base_url`, "INVALID_SITE_MEDIA_BASE_URL", errors);
52342
+ if (site.media_delivery_mode !== void 0) {
52343
+ validateEnum(site.media_delivery_mode, `${path4}.media_delivery_mode`, "INVALID_SITE_MEDIA_DELIVERY_MODE", errors, PREVIEW_MEDIA_DELIVERY_MODES);
52344
+ }
52345
+ if (site.favicon !== void 0) {
52346
+ validateSiteFavicon(site.favicon, `${path4}.favicon`, errors);
52347
+ }
52348
+ if (site.expose_generator !== void 0) {
52349
+ validateBoolean(site.expose_generator, `${path4}.expose_generator`, "INVALID_SITE_EXPOSE_GENERATOR", errors);
52350
+ }
52304
52351
  validateNonEmptyString(site.locale, `${path4}.locale`, "INVALID_SITE_LOCALE", errors);
52305
- validateInteger(site.postsPerPage, `${path4}.postsPerPage`, "INVALID_SITE_POSTS_PER_PAGE", errors, { minimum: 1 });
52306
- validateNonEmptyString(site.dateFormat, `${path4}.dateFormat`, "INVALID_SITE_DATE_FORMAT", errors);
52307
- validateString(site.timeFormat, `${path4}.timeFormat`, "INVALID_SITE_TIME_FORMAT", errors);
52352
+ validateInteger(site.posts_per_page, `${path4}.posts_per_page`, "INVALID_SITE_POSTS_PER_PAGE", errors, { minimum: 1 });
52353
+ validateEnum(site.datetime_display, `${path4}.datetime_display`, "INVALID_SITE_DATETIME_DISPLAY", errors, PREVIEW_DATETIME_DISPLAY_MODES);
52354
+ validateEnum(site.date_style, `${path4}.date_style`, "INVALID_SITE_DATE_STYLE", errors, PREVIEW_DATETIME_STYLES);
52355
+ validateEnum(site.time_style, `${path4}.time_style`, "INVALID_SITE_TIME_STYLE", errors, PREVIEW_DATETIME_STYLES);
52308
52356
  validateNonEmptyString(site.timezone, `${path4}.timezone`, "INVALID_SITE_TIMEZONE", errors);
52309
- validateBoolean(site.disallowComments, `${path4}.disallowComments`, "INVALID_SITE_DISALLOW_COMMENTS", errors);
52357
+ validateBoolean(site.disallow_comments, `${path4}.disallow_comments`, "INVALID_SITE_DISALLOW_COMMENTS", errors);
52358
+ if (site.indexing !== void 0) {
52359
+ validateBoolean(site.indexing, `${path4}.indexing`, "INVALID_SITE_INDEXING", errors);
52360
+ }
52310
52361
  validatePermalinks(site.permalinks, `${path4}.permalinks`, errors);
52311
52362
  validateFrontPage(site.front_page, `${path4}.front_page`, errors);
52312
52363
  validatePostIndex(site.post_index, `${path4}.post_index`, errors);
52364
+ validatePreviewMeta(site.meta, `${path4}.meta`, errors);
52313
52365
  if (site.footer !== void 0) {
52314
52366
  validateSiteFooter(site.footer, `${path4}.footer`, errors);
52315
52367
  }
@@ -52318,7 +52370,6 @@ function validateSite(site, path4, errors) {
52318
52370
  "site_description",
52319
52371
  "site_url",
52320
52372
  "metadata",
52321
- "media_delivery_mode",
52322
52373
  "media_delivery_base_url",
52323
52374
  "language",
52324
52375
  "siteLocale",
@@ -52328,7 +52379,7 @@ function validateSite(site, path4, errors) {
52328
52379
  ], "INVALID_LEGACY_SITE_FIELD");
52329
52380
  }
52330
52381
  function validateContent(content, path4, errors) {
52331
- validateClosedObject(content, path4, errors, ["authors", "posts", "pages", "categories", "tags"]);
52382
+ validateClosedObject(content, path4, errors, ["authors", "posts", "pages", "categories", "tags", "media"]);
52332
52383
  if (!isObject(content)) {
52333
52384
  return;
52334
52385
  }
@@ -52343,6 +52394,9 @@ function validateContent(content, path4, errors) {
52343
52394
  validateArray(content.tags, `${path4}.tags`, "INVALID_TAGS", errors, (entry, index) => {
52344
52395
  validatePreviewTag(entry, `${path4}.tags[${index}]`, errors);
52345
52396
  });
52397
+ if (content.media !== void 0) {
52398
+ validateMediaArray(content.media, `${path4}.media`, errors);
52399
+ }
52346
52400
  }
52347
52401
  function validateSiteFooter(footer, path4, errors) {
52348
52402
  validateClosedObject(footer, path4, errors, ["copyright_text", "attribution"]);
@@ -52353,10 +52407,7 @@ function validateSiteFooter(footer, path4, errors) {
52353
52407
  validateNonEmptyString(footer.copyright_text, `${path4}.copyright_text`, "INVALID_SITE_FOOTER_COPYRIGHT_TEXT", errors);
52354
52408
  }
52355
52409
  if (footer.attribution !== void 0) {
52356
- validateClosedObject(footer.attribution, `${path4}.attribution`, errors, ["enabled"]);
52357
- if (isObject(footer.attribution) && footer.attribution.enabled !== void 0) {
52358
- validateBoolean(footer.attribution.enabled, `${path4}.attribution.enabled`, "INVALID_SITE_FOOTER_ATTRIBUTION_ENABLED", errors);
52359
- }
52410
+ validateBoolean(footer.attribution, `${path4}.attribution`, "INVALID_SITE_FOOTER_ATTRIBUTION", errors);
52360
52411
  }
52361
52412
  }
52362
52413
  function validateMenus(menus, path4, errors) {
@@ -52382,7 +52433,7 @@ function validatePreviewMenu(menu, path4, errors) {
52382
52433
  });
52383
52434
  }
52384
52435
  function validatePreviewMenuItem(item, path4, errors) {
52385
- validateClosedObject(item, path4, errors, ["title", "url", "type", "target", "children"]);
52436
+ validateClosedObject(item, path4, errors, ["title", "url", "type", "target", "meta", "children"]);
52386
52437
  if (!isObject(item)) {
52387
52438
  return;
52388
52439
  }
@@ -52390,6 +52441,7 @@ function validatePreviewMenuItem(item, path4, errors) {
52390
52441
  validateUrlLike(item.url, `${path4}.url`, "INVALID_MENU_ITEM_URL", errors);
52391
52442
  validateEnum(item.type, `${path4}.type`, "INVALID_MENU_ITEM_TYPE", errors, PREVIEW_MENU_ITEM_TYPES);
52392
52443
  validateEnum(item.target, `${path4}.target`, "INVALID_MENU_ITEM_TARGET", errors, PREVIEW_MENU_TARGETS);
52444
+ validatePreviewMeta(item.meta, `${path4}.meta`, errors);
52393
52445
  validateArray(item.children, `${path4}.children`, "INVALID_MENU_ITEM_CHILDREN", errors, (entry, index) => {
52394
52446
  validatePreviewMenuItem(entry, `${path4}.children[${index}]`, errors);
52395
52447
  });
@@ -52407,6 +52459,49 @@ function validateWidgets(widgets, path4, errors) {
52407
52459
  validatePreviewWidgetArea(widgetArea, `${path4}.${widgetAreaId}`, errors);
52408
52460
  }
52409
52461
  }
52462
+ function validateCollections(collections, path4, errors) {
52463
+ validateObject(collections, path4, "INVALID_COLLECTIONS", errors);
52464
+ if (!isObject(collections)) {
52465
+ return;
52466
+ }
52467
+ for (const [collectionId, collection] of Object.entries(collections)) {
52468
+ if (!PREVIEW_COLLECTION_ID_PATTERN.test(collectionId)) {
52469
+ errors.push(issue("INVALID_COLLECTION_ID", `${path4}.${collectionId}`, "Collection ids must match ^[a-z][a-z0-9_-]{0,63}$"));
52470
+ }
52471
+ validatePreviewCollection(collection, `${path4}.${collectionId}`, errors);
52472
+ }
52473
+ }
52474
+ function validatePreviewCollection(collection, path4, errors) {
52475
+ validateClosedObject(collection, path4, errors, ["title", "description", "items"]);
52476
+ if (!isObject(collection)) {
52477
+ return;
52478
+ }
52479
+ if (collection.title !== void 0) {
52480
+ validateNonEmptyString(collection.title, `${path4}.title`, "INVALID_COLLECTION_TITLE", errors);
52481
+ }
52482
+ if (collection.description !== void 0) {
52483
+ validateString(collection.description, `${path4}.description`, "INVALID_COLLECTION_DESCRIPTION", errors);
52484
+ }
52485
+ const seenItems = /* @__PURE__ */ new Set();
52486
+ validateArray(collection.items, `${path4}.items`, "INVALID_COLLECTION_ITEMS", errors, (entry, index) => {
52487
+ validatePreviewCollectionItem(entry, `${path4}.items[${index}]`, errors, seenItems);
52488
+ });
52489
+ }
52490
+ function validatePreviewCollectionItem(item, path4, errors, seenItems) {
52491
+ validateClosedObject(item, path4, errors, ["type", "slug"]);
52492
+ if (!isObject(item)) {
52493
+ return;
52494
+ }
52495
+ validateEnum(item.type, `${path4}.type`, "INVALID_COLLECTION_ITEM_TYPE", errors, PREVIEW_COLLECTION_ITEM_TYPES);
52496
+ validateSlugSegment2(item.slug, `${path4}.slug`, "INVALID_COLLECTION_ITEM_SLUG", errors);
52497
+ if (typeof item.type === "string" && PREVIEW_COLLECTION_ITEM_TYPES.includes(item.type) && typeof item.slug === "string") {
52498
+ const key = `${item.type}:${item.slug}`;
52499
+ if (seenItems.has(key)) {
52500
+ errors.push(issue("DUPLICATE_COLLECTION_ITEM", `${path4}.slug`, "Duplicate collection item in the same collection"));
52501
+ }
52502
+ seenItems.add(key);
52503
+ }
52504
+ }
52410
52505
  function validatePreviewWidgetArea(widgetArea, path4, errors) {
52411
52506
  validateClosedObject(widgetArea, path4, errors, ["name", "items"]);
52412
52507
  if (!isObject(widgetArea)) {
@@ -52498,6 +52593,46 @@ function validatePreviewAuthor(author, path4, errors) {
52498
52593
  validateUrlLike(author.avatar, `${path4}.avatar`, "INVALID_AUTHOR_AVATAR", errors);
52499
52594
  }
52500
52595
  }
52596
+ function validateMediaArray(value, path4, errors) {
52597
+ const sources = /* @__PURE__ */ new Set();
52598
+ validateArray(value, path4, "INVALID_MEDIA", errors, (entry, index) => {
52599
+ validatePreviewMedia(entry, `${path4}[${index}]`, errors);
52600
+ if (!isObject(entry) || typeof entry.src !== "string") {
52601
+ return;
52602
+ }
52603
+ if (sources.has(entry.src)) {
52604
+ errors.push(issue("DUPLICATE_MEDIA_SRC", `${path4}[${index}].src`, "Media src values must be unique"));
52605
+ return;
52606
+ }
52607
+ sources.add(entry.src);
52608
+ });
52609
+ }
52610
+ function validateSiteFavicon(favicon, path4, errors) {
52611
+ validateClosedObject(favicon, path4, errors, ["icon", "svg", "png", "apple_touch_icon"]);
52612
+ if (!isObject(favicon)) {
52613
+ return;
52614
+ }
52615
+ if (favicon.icon === void 0 && favicon.svg === void 0 && favicon.png === void 0 && favicon.apple_touch_icon === void 0) {
52616
+ errors.push(issue("INVALID_SITE_FAVICON", path4, "site.favicon must include at least one favicon URL"));
52617
+ }
52618
+ for (const key of ["icon", "svg", "png", "apple_touch_icon"]) {
52619
+ if (favicon[key] !== void 0) {
52620
+ validateUrlLike(favicon[key], `${path4}.${key}`, "INVALID_SITE_FAVICON_URL", errors);
52621
+ }
52622
+ }
52623
+ }
52624
+ function validatePreviewMedia(media, path4, errors) {
52625
+ validateClosedObject(media, path4, errors, ["src", "width", "height", "alt"]);
52626
+ if (!isObject(media)) {
52627
+ return;
52628
+ }
52629
+ validateUrlLike(media.src, `${path4}.src`, "INVALID_MEDIA_SRC", errors);
52630
+ validateInteger(media.width, `${path4}.width`, "INVALID_MEDIA_WIDTH", errors, { minimum: 1 });
52631
+ validateInteger(media.height, `${path4}.height`, "INVALID_MEDIA_HEIGHT", errors, { minimum: 1 });
52632
+ if (media.alt !== void 0) {
52633
+ validateString(media.alt, `${path4}.alt`, "INVALID_MEDIA_ALT", errors);
52634
+ }
52635
+ }
52501
52636
  function validatePreviewPost(post, path4, errors, authorIds) {
52502
52637
  validateClosedObject(post, path4, errors, [
52503
52638
  "id",
@@ -52512,7 +52647,9 @@ function validatePreviewPost(post, path4, errors, authorIds) {
52512
52647
  "author_id",
52513
52648
  "featured_image",
52514
52649
  "meta",
52650
+ "data",
52515
52651
  "status",
52652
+ "discoverability",
52516
52653
  "allow_comments",
52517
52654
  "category_slugs",
52518
52655
  "tag_slugs"
@@ -52530,6 +52667,9 @@ function validatePreviewPost(post, path4, errors, authorIds) {
52530
52667
  validateDateTimeString(post.updated_at_iso, `${path4}.updated_at_iso`, "INVALID_POST_UPDATED_AT_ISO", errors);
52531
52668
  validateNonEmptyString(post.author_id, `${path4}.author_id`, "INVALID_POST_AUTHOR_ID", errors);
52532
52669
  validateEnum(post.status, `${path4}.status`, "INVALID_POST_STATUS", errors, ["published", "draft"]);
52670
+ if (post.discoverability !== void 0) {
52671
+ validateEnum(post.discoverability, `${path4}.discoverability`, "INVALID_POST_DISCOVERABILITY", errors, PREVIEW_DISCOVERABILITY_VALUES);
52672
+ }
52533
52673
  validateBoolean(post.allow_comments, `${path4}.allow_comments`, "INVALID_POST_ALLOW_COMMENTS", errors);
52534
52674
  validateSlugArray(post.category_slugs, `${path4}.category_slugs`, "INVALID_POST_CATEGORY_SLUGS", errors);
52535
52675
  validateSlugArray(post.tag_slugs, `${path4}.tag_slugs`, "INVALID_POST_TAG_SLUGS", errors);
@@ -52537,12 +52677,13 @@ function validatePreviewPost(post, path4, errors, authorIds) {
52537
52677
  validateUrlLike(post.featured_image, `${path4}.featured_image`, "INVALID_POST_FEATURED_IMAGE", errors);
52538
52678
  }
52539
52679
  validatePreviewMeta(post.meta, `${path4}.meta`, errors);
52680
+ validatePreviewStructuredData(post.data, `${path4}.data`, errors);
52540
52681
  if (typeof post.author_id === "string" && post.author_id.trim() !== "" && !authorIds.has(post.author_id)) {
52541
52682
  errors.push(issue("INVALID_POST_AUTHOR_REFERENCE", `${path4}.author_id`, "Referenced author_id does not exist"));
52542
52683
  }
52543
52684
  }
52544
52685
  function validatePreviewPage(page, path4, errors) {
52545
- validateClosedObject(page, path4, errors, ["title", "slug", "path", "content", "document_type", "excerpt", "featured_image", "meta", "status"]);
52686
+ validateClosedObject(page, path4, errors, ["title", "slug", "path", "content", "document_type", "excerpt", "featured_image", "meta", "data", "status", "discoverability"]);
52546
52687
  if (!isObject(page)) {
52547
52688
  return;
52548
52689
  }
@@ -52558,7 +52699,11 @@ function validatePreviewPage(page, path4, errors) {
52558
52699
  validateUrlLike(page.featured_image, `${path4}.featured_image`, "INVALID_PAGE_FEATURED_IMAGE", errors);
52559
52700
  }
52560
52701
  validatePreviewMeta(page.meta, `${path4}.meta`, errors);
52702
+ validatePreviewStructuredData(page.data, `${path4}.data`, errors);
52561
52703
  validateEnum(page.status, `${path4}.status`, "INVALID_PAGE_STATUS", errors, ["published", "draft"]);
52704
+ if (page.discoverability !== void 0) {
52705
+ validateEnum(page.discoverability, `${path4}.discoverability`, "INVALID_PAGE_DISCOVERABILITY", errors, PREVIEW_DISCOVERABILITY_VALUES);
52706
+ }
52562
52707
  }
52563
52708
  function validatePreviewMeta(meta, path4, errors) {
52564
52709
  if (meta === void 0) {
@@ -52575,6 +52720,66 @@ function validatePreviewMeta(meta, path4, errors) {
52575
52720
  errors.push(issue("INVALID_META_VALUE", `${path4}.${key}`, "meta values must be strings, numbers, booleans, or null"));
52576
52721
  }
52577
52722
  }
52723
+ function validatePreviewStructuredData(data, path4, errors) {
52724
+ if (data === void 0) {
52725
+ return;
52726
+ }
52727
+ if (!isObject(data)) {
52728
+ errors.push(issue("INVALID_DATA", path4, "data must be an object"));
52729
+ return;
52730
+ }
52731
+ validatePreviewDataObject(data, path4, errors, 0);
52732
+ }
52733
+ function validatePreviewDataValue(value, path4, errors, depth) {
52734
+ if (value === null || typeof value === "string" || typeof value === "boolean") {
52735
+ return;
52736
+ }
52737
+ if (typeof value === "number") {
52738
+ if (!Number.isFinite(value)) {
52739
+ errors.push(issue("INVALID_DATA_VALUE", path4, "data numbers must be finite"));
52740
+ }
52741
+ return;
52742
+ }
52743
+ if (Array.isArray(value)) {
52744
+ validatePreviewDataArray(value, path4, errors, depth);
52745
+ return;
52746
+ }
52747
+ if (isObject(value)) {
52748
+ validatePreviewDataObject(value, path4, errors, depth);
52749
+ return;
52750
+ }
52751
+ errors.push(issue("INVALID_DATA_VALUE", path4, "data values must be JSON-safe strings, numbers, booleans, null, arrays, or objects"));
52752
+ }
52753
+ function validatePreviewDataObject(object, path4, errors, depth) {
52754
+ if (depth > PREVIEW_DATA_MAX_DEPTH) {
52755
+ errors.push(issue("INVALID_DATA_DEPTH", path4, `data nesting must not exceed ${PREVIEW_DATA_MAX_DEPTH} container levels`));
52756
+ return;
52757
+ }
52758
+ const entries = Object.entries(object);
52759
+ if (entries.length > PREVIEW_DATA_MAX_KEYS) {
52760
+ errors.push(issue("INVALID_DATA_OBJECT_SIZE", path4, `data objects must not contain more than ${PREVIEW_DATA_MAX_KEYS} keys`));
52761
+ }
52762
+ for (const [key, value] of entries) {
52763
+ const childPath = `${path4}.${key}`;
52764
+ if (!PREVIEW_DATA_KEY_PATTERN.test(key)) {
52765
+ errors.push(issue("INVALID_DATA_KEY", childPath, "data keys must be valid template path segments"));
52766
+ continue;
52767
+ }
52768
+ validatePreviewDataValue(value, childPath, errors, depth + 1);
52769
+ }
52770
+ }
52771
+ function validatePreviewDataArray(array, path4, errors, depth) {
52772
+ if (depth > PREVIEW_DATA_MAX_DEPTH) {
52773
+ errors.push(issue("INVALID_DATA_DEPTH", path4, `data nesting must not exceed ${PREVIEW_DATA_MAX_DEPTH} container levels`));
52774
+ return;
52775
+ }
52776
+ if (array.length > PREVIEW_DATA_MAX_ARRAY_LENGTH) {
52777
+ errors.push(issue("INVALID_DATA_ARRAY_SIZE", path4, `data arrays must not contain more than ${PREVIEW_DATA_MAX_ARRAY_LENGTH} items`));
52778
+ }
52779
+ array.forEach((value, index) => {
52780
+ validatePreviewDataValue(value, `${path4}[${index}]`, errors, depth + 1);
52781
+ });
52782
+ }
52578
52783
  function validatePermalinks(permalinks, path4, errors) {
52579
52784
  if (permalinks === void 0) {
52580
52785
  return;
@@ -52787,7 +52992,7 @@ function validateClosedObject(value, path4, errors, allowedKeys) {
52787
52992
  return;
52788
52993
  }
52789
52994
  for (const key of Object.keys(value)) {
52790
- if (!allowedKeys.includes(key) && !isSiteExtensionKey(path4, key)) {
52995
+ if (!allowedKeys.includes(key)) {
52791
52996
  errors.push(issue("UNKNOWN_PROPERTY", path4 ? `${path4}.${key}` : key, "Unexpected property"));
52792
52997
  }
52793
52998
  }
@@ -52799,20 +53004,20 @@ function validateClosedObject(value, path4, errors, allowedKeys) {
52799
53004
  }
52800
53005
  function isOptionalKey(path4, key) {
52801
53006
  if (path4 === "") {
52802
- return key === "$schema" || key === "custom_css" || key === "custom_html";
53007
+ return key === "$schema" || key === "menus" || key === "widgets" || key === "collections" || key === "custom_css" || key === "custom_html";
52803
53008
  }
52804
53009
  if (path4 === "custom_html") {
52805
53010
  return key === "head_end" || key === "body_end";
52806
53011
  }
52807
53012
  if (path4 === "site") {
52808
- return key === "permalinks" || key === "front_page" || key === "post_index" || key === "footer";
53013
+ return key === "media_delivery_mode" || key === "favicon" || key === "expose_generator" || key === "indexing" || key === "permalinks" || key === "front_page" || key === "post_index" || key === "footer" || key === "meta";
53014
+ }
53015
+ if (path4 === "site.favicon") {
53016
+ return key === "icon" || key === "svg" || key === "png" || key === "apple_touch_icon";
52809
53017
  }
52810
53018
  if (path4 === "site.footer") {
52811
53019
  return key === "copyright_text" || key === "attribution";
52812
53020
  }
52813
- if (path4 === "site.footer.attribution") {
52814
- return key === "enabled";
52815
- }
52816
53021
  if (path4 === "site.front_page") {
52817
53022
  return key === "page_slug" || key === "html";
52818
53023
  }
@@ -52822,38 +53027,35 @@ function isOptionalKey(path4, key) {
52822
53027
  if (path4 === "site.permalinks") {
52823
53028
  return key === "output_style" || PREVIEW_PERMALINK_FIELDS.includes(key);
52824
53029
  }
53030
+ if (path4 === "content") {
53031
+ return key === "media";
53032
+ }
52825
53033
  if (path4.startsWith("content.authors[")) {
52826
53034
  return key === "avatar";
52827
53035
  }
53036
+ if (path4.startsWith("content.media[")) {
53037
+ return key === "alt";
53038
+ }
53039
+ if (path4.startsWith("menus.") && (path4.includes(".items[") || path4.includes(".children["))) {
53040
+ return key === "meta";
53041
+ }
52828
53042
  if (path4.startsWith("widgets.") && path4.includes(".items[")) {
52829
53043
  return key === "settings";
52830
53044
  }
53045
+ if (path4.startsWith("collections.") && !path4.includes(".items[")) {
53046
+ return key === "title" || key === "description";
53047
+ }
52831
53048
  if (path4.startsWith("content.posts[")) {
52832
- return key === "id" || key === "featured_image" || key === "meta";
53049
+ return key === "id" || key === "featured_image" || key === "meta" || key === "data" || key === "discoverability";
52833
53050
  }
52834
53051
  if (path4.startsWith("content.pages[")) {
52835
- return key === "path" || key === "excerpt" || key === "featured_image" || key === "meta";
53052
+ return key === "path" || key === "excerpt" || key === "featured_image" || key === "meta" || key === "data" || key === "discoverability";
52836
53053
  }
52837
53054
  if (path4.startsWith("content.categories[") || path4.startsWith("content.tags[")) {
52838
53055
  return key === "description";
52839
53056
  }
52840
53057
  return false;
52841
53058
  }
52842
- function isSiteExtensionKey(path4, key) {
52843
- if (path4 !== "site") {
52844
- return false;
52845
- }
52846
- return ![
52847
- "site_name",
52848
- "site_description",
52849
- "site_url",
52850
- "metadata",
52851
- "media_delivery_mode",
52852
- "media_delivery_base_url",
52853
- "site_timezone",
52854
- "site_locale"
52855
- ].includes(key);
52856
- }
52857
53059
  function validateSlugArray(value, path4, code2, errors) {
52858
53060
  if (!Array.isArray(value)) {
52859
53061
  errors.push(issue(code2, path4, "Expected an array"));
@@ -52892,7 +53094,7 @@ function mapSlugValidationMessage(issueCode) {
52892
53094
  function rejectLegacyKeys(value, path4, errors, keys, code2) {
52893
53095
  for (const key of keys) {
52894
53096
  if (key in value) {
52895
- errors.push(issue(code2, `${path4}.${key}`, "Legacy field is not allowed in preview-data v0.5"));
53097
+ errors.push(issue(code2, `${path4}.${key}`, "Legacy field is not allowed in preview-data v0.6"));
52896
53098
  }
52897
53099
  }
52898
53100
  }
@@ -53002,9 +53204,46 @@ var PARTIAL_TAG_REGEX = /\{\{(partial:[^}]+)\}\}/g;
53002
53204
  var TEMPLATE_PATH_SEGMENT_SOURCE = "[a-zA-Z_][a-zA-Z0-9_]*(?:-[a-zA-Z0-9_]+)*";
53003
53205
  var TEMPLATE_PATH_REGEX = new RegExp(`^${TEMPLATE_PATH_SEGMENT_SOURCE}(?:\\.${TEMPLATE_PATH_SEGMENT_SOURCE})*$`);
53004
53206
  var FOR_TAG_REGEX = new RegExp(`^#for ([a-zA-Z_][a-zA-Z0-9_]*) in (${TEMPLATE_PATH_SEGMENT_SOURCE}(?:\\.${TEMPLATE_PATH_SEGMENT_SOURCE})*)$`);
53005
- var IF_EQ_EXPRESSION_REGEX = new RegExp(`^(${TEMPLATE_PATH_SEGMENT_SOURCE}(?:\\.${TEMPLATE_PATH_SEGMENT_SOURCE})*)\\s+("(?:[^"\\\\]|\\\\.)*")$`);
53207
+ var NUMBER_LITERAL_REGEX = /^-?(?:0|[1-9]\d*)(?:\.\d+)?$/;
53208
+ var COMPARISON_BLOCK_TAGS = /* @__PURE__ */ new Set(["if_eq", "if_neq", "if_in", "if_starts_with"]);
53209
+ var COMPARISON_ELSE_IF_TAGS = /* @__PURE__ */ new Set(["else_if_eq", "else_if_neq", "else_if_in", "else_if_starts_with"]);
53210
+ var COMPARISON_TAG_OPERATORS = {
53211
+ if_eq: "eq",
53212
+ else_if_eq: "eq",
53213
+ if_neq: "neq",
53214
+ else_if_neq: "neq",
53215
+ if_in: "in",
53216
+ else_if_in: "in",
53217
+ if_starts_with: "starts_with",
53218
+ else_if_starts_with: "starts_with"
53219
+ };
53220
+ var PARTIAL_ARG_ROOT_PATHS = /* @__PURE__ */ new Set([
53221
+ "archive",
53222
+ "author",
53223
+ "category",
53224
+ "collection",
53225
+ "collections",
53226
+ "currentUrl",
53227
+ "language",
53228
+ "menu",
53229
+ "menus",
53230
+ "meta",
53231
+ "page",
53232
+ "pagination",
53233
+ "partial",
53234
+ "post",
53235
+ "posts",
53236
+ "route",
53237
+ "site",
53238
+ "tag",
53239
+ "taxonomies",
53240
+ "taxonomy",
53241
+ "widget",
53242
+ "widgets"
53243
+ ]);
53006
53244
  var PARTIAL_ARG_KEY_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
53007
53245
  var SEMVER_REGEX = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
53246
+ var LICENSE_REF_REGEX = /^LicenseRef-[A-Za-z0-9][A-Za-z0-9.-]*$/;
53008
53247
  var NAMESPACE_REGEX = /^[a-z0-9]+(-[a-z0-9]+)*$/;
53009
53248
  var SLUG_REGEX = /^[a-z0-9]+(-[a-z0-9]+)*$/;
53010
53249
  var ALLOWED_LICENSES = [
@@ -53015,7 +53254,10 @@ var ALLOWED_LICENSES = [
53015
53254
  "GPL-3.0-or-later"
53016
53255
  ];
53017
53256
  var LICENSES = new Set(ALLOWED_LICENSES);
53018
- var DEFAULT_RUNTIME = "0.5";
53257
+ var THEME_LINK_KEYS = /* @__PURE__ */ new Set(["homepage", "repository", "documentation", "support", "marketplace", "license"]);
53258
+ var THEME_LINK_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:"]);
53259
+ var DEFAULT_RUNTIME = "0.6";
53260
+ var THEME_RUNTIME_V0_6 = DEFAULT_RUNTIME;
53019
53261
  var SUPPORTED_RUNTIMES = [DEFAULT_RUNTIME];
53020
53262
  var SUPPORTED_RUNTIME_SET = new Set(SUPPORTED_RUNTIMES);
53021
53263
  var NAMESPACE_MIN_LENGTH = 3;
@@ -53030,7 +53272,29 @@ var MENU_SLOT_ID_MAX_LENGTH = 32;
53030
53272
  var MENU_SLOT_COUNT_MAX = 12;
53031
53273
  var MENU_SLOT_TITLE_MAX_LENGTH = 80;
53032
53274
  var MENU_SLOT_DESCRIPTION_MAX_LENGTH = 160;
53033
- var SUPPORTED_THEME_FEATURES = /* @__PURE__ */ new Set(["comments", "newsletter", "postIndex"]);
53275
+ var SUPPORTED_THEME_FEATURES = /* @__PURE__ */ new Set(["comments", "newsletter", "post_index"]);
53276
+ var THEME_MANIFEST_KEYS = /* @__PURE__ */ new Set([
53277
+ "$schema",
53278
+ "name",
53279
+ "namespace",
53280
+ "slug",
53281
+ "version",
53282
+ "license",
53283
+ "runtime",
53284
+ "author",
53285
+ "description",
53286
+ "thumbnail",
53287
+ "links",
53288
+ "features",
53289
+ "menu_slots",
53290
+ "widget_areas",
53291
+ "site_meta",
53292
+ "collection_slots"
53293
+ ]);
53294
+ var SITE_META_KEY_REGEX = /^[a-z][a-z0-9_]*(?:-[a-z0-9_]+)*$/;
53295
+ var SITE_META_KEY_MAX_LENGTH = 64;
53296
+ var SITE_META_COUNT_MAX = 32;
53297
+ var SITE_META_TYPES = /* @__PURE__ */ new Set(["string", "number", "boolean"]);
53034
53298
  function validateFeatureFlags(rawValue, errors) {
53035
53299
  if (rawValue === void 0) {
53036
53300
  return void 0;
@@ -53051,7 +53315,11 @@ function validateFeatureFlags(rawValue, errors) {
53051
53315
  "INVALID_THEME_FEATURE",
53052
53316
  `theme.json.features.${featureName}`,
53053
53317
  `Unknown theme feature '${featureName}'`,
53054
- "error"
53318
+ "error",
53319
+ {
53320
+ category: "theme_manifest",
53321
+ hint: themeFeatureHint(featureName)
53322
+ }
53055
53323
  ));
53056
53324
  continue;
53057
53325
  }
@@ -53068,6 +53336,80 @@ function validateFeatureFlags(rawValue, errors) {
53068
53336
  }
53069
53337
  return Object.keys(normalizedFeatures).length > 0 ? normalizedFeatures : void 0;
53070
53338
  }
53339
+ function themeFeatureHint(featureName) {
53340
+ const hints = {
53341
+ postIndex: "ZeroPress runtime v0.6 uses snake_case keys. Use 'post_index' instead of 'postIndex'."
53342
+ };
53343
+ return hints[featureName] || "";
53344
+ }
53345
+ function themeManifestFieldHint(fieldName) {
53346
+ const hints = {
53347
+ menuSlots: "ZeroPress runtime v0.6 uses snake_case keys. Use 'menu_slots' instead of 'menuSlots'.",
53348
+ widgetAreas: "ZeroPress runtime v0.6 uses snake_case keys. Use 'widget_areas' instead of 'widgetAreas'.",
53349
+ siteMeta: "ZeroPress runtime v0.6 uses snake_case keys. Use 'site_meta' instead of 'siteMeta'.",
53350
+ collectionSlots: "ZeroPress runtime v0.6 uses snake_case keys. Use 'collection_slots' instead of 'collectionSlots'.",
53351
+ 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."
53352
+ };
53353
+ return hints[fieldName] || "";
53354
+ }
53355
+ function isAllowedLicense(value) {
53356
+ return LICENSES.has(value) || LICENSE_REF_REGEX.test(value);
53357
+ }
53358
+ function isValidThemeLinkUrl(value) {
53359
+ try {
53360
+ const parsed = new URL(value);
53361
+ return THEME_LINK_PROTOCOLS.has(parsed.protocol);
53362
+ } catch {
53363
+ return false;
53364
+ }
53365
+ }
53366
+ function validateThemeLinks(rawValue, errors) {
53367
+ if (rawValue === void 0) {
53368
+ return void 0;
53369
+ }
53370
+ if (!rawValue || typeof rawValue !== "object" || Array.isArray(rawValue)) {
53371
+ errors.push(issue2(
53372
+ "INVALID_LINKS",
53373
+ "theme.json.links",
53374
+ "theme.json field 'links' must be an object when present",
53375
+ "error"
53376
+ ));
53377
+ return void 0;
53378
+ }
53379
+ const normalizedLinks = {};
53380
+ for (const [linkKey, value] of Object.entries(rawValue)) {
53381
+ if (!THEME_LINK_KEYS.has(linkKey)) {
53382
+ errors.push(issue2(
53383
+ "INVALID_THEME_LINK",
53384
+ `theme.json.links.${linkKey}`,
53385
+ `Unknown theme link '${linkKey}'`,
53386
+ "error"
53387
+ ));
53388
+ continue;
53389
+ }
53390
+ if (typeof value !== "string" || value.trim() === "") {
53391
+ errors.push(issue2(
53392
+ "INVALID_THEME_LINK_VALUE",
53393
+ `theme.json.links.${linkKey}`,
53394
+ `theme link '${linkKey}' must be a non-empty URL string`,
53395
+ "error"
53396
+ ));
53397
+ continue;
53398
+ }
53399
+ const normalizedValue = value.trim();
53400
+ if (!isValidThemeLinkUrl(normalizedValue)) {
53401
+ errors.push(issue2(
53402
+ "INVALID_THEME_LINK_VALUE",
53403
+ `theme.json.links.${linkKey}`,
53404
+ `theme link '${linkKey}' must be an absolute http, https, or mailto URL`,
53405
+ "error"
53406
+ ));
53407
+ continue;
53408
+ }
53409
+ normalizedLinks[linkKey] = normalizedValue;
53410
+ }
53411
+ return Object.keys(normalizedLinks).length > 0 ? normalizedLinks : void 0;
53412
+ }
53071
53413
  function validateHelperMetadataMap(rawValue, fieldName, issueCodes, errors) {
53072
53414
  if (rawValue === void 0) {
53073
53415
  return void 0;
@@ -53161,6 +53503,127 @@ function validateHelperMetadataMap(rawValue, fieldName, issueCodes, errors) {
53161
53503
  }
53162
53504
  return Object.keys(normalizedItems).length > 0 ? normalizedItems : void 0;
53163
53505
  }
53506
+ function validateSiteMetaMap(rawValue, errors) {
53507
+ if (rawValue === void 0) {
53508
+ return void 0;
53509
+ }
53510
+ if (!rawValue || typeof rawValue !== "object" || Array.isArray(rawValue)) {
53511
+ errors.push(issue2(
53512
+ "INVALID_SITE_META",
53513
+ "theme.json",
53514
+ "theme.json field 'site_meta' must be an object when present",
53515
+ "error"
53516
+ ));
53517
+ return void 0;
53518
+ }
53519
+ const entries = Object.entries(rawValue);
53520
+ if (entries.length === 0) {
53521
+ errors.push(issue2(
53522
+ "INVALID_SITE_META",
53523
+ "theme.json",
53524
+ "theme.json field 'site_meta' must not be empty",
53525
+ "error"
53526
+ ));
53527
+ }
53528
+ if (entries.length > SITE_META_COUNT_MAX) {
53529
+ errors.push(issue2(
53530
+ "INVALID_SITE_META",
53531
+ "theme.json",
53532
+ `theme.json field 'site_meta' must contain at most ${SITE_META_COUNT_MAX} keys`,
53533
+ "error"
53534
+ ));
53535
+ }
53536
+ const normalizedItems = {};
53537
+ for (const [metaKey, value] of entries) {
53538
+ if (!SITE_META_KEY_REGEX.test(metaKey) || metaKey.length > SITE_META_KEY_MAX_LENGTH) {
53539
+ errors.push(issue2(
53540
+ "INVALID_SITE_META_KEY",
53541
+ `theme.json.site_meta.${metaKey}`,
53542
+ `site_meta key '${metaKey}' must start with a lowercase letter and use lowercase letters, digits, underscores, or internal hyphens only`,
53543
+ "error"
53544
+ ));
53545
+ continue;
53546
+ }
53547
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
53548
+ errors.push(issue2(
53549
+ "INVALID_SITE_META_FIELD",
53550
+ `theme.json.site_meta.${metaKey}`,
53551
+ `site_meta key '${metaKey}' must be an object`,
53552
+ "error"
53553
+ ));
53554
+ continue;
53555
+ }
53556
+ const allowedKeys = /* @__PURE__ */ new Set(["title", "description", "type", "default"]);
53557
+ for (const key of Object.keys(value)) {
53558
+ if (!allowedKeys.has(key)) {
53559
+ errors.push(issue2(
53560
+ "INVALID_SITE_META_PROPERTY",
53561
+ `theme.json.site_meta.${metaKey}.${key}`,
53562
+ `Unknown site_meta property '${key}' in key '${metaKey}'`,
53563
+ "error"
53564
+ ));
53565
+ }
53566
+ }
53567
+ if (typeof value.title !== "string" || value.title.trim() === "") {
53568
+ errors.push(issue2(
53569
+ "INVALID_SITE_META_TITLE",
53570
+ `theme.json.site_meta.${metaKey}.title`,
53571
+ `site_meta key '${metaKey}' must define a non-empty 'title'`,
53572
+ "error"
53573
+ ));
53574
+ } else if (value.title.trim().length > MENU_SLOT_TITLE_MAX_LENGTH) {
53575
+ errors.push(issue2(
53576
+ "INVALID_SITE_META_TITLE",
53577
+ `theme.json.site_meta.${metaKey}.title`,
53578
+ `site_meta key '${metaKey}' title must be at most ${MENU_SLOT_TITLE_MAX_LENGTH} characters`,
53579
+ "error"
53580
+ ));
53581
+ }
53582
+ if (value.description !== void 0 && (typeof value.description !== "string" || value.description.trim().length > MENU_SLOT_DESCRIPTION_MAX_LENGTH)) {
53583
+ errors.push(issue2(
53584
+ "INVALID_SITE_META_DESCRIPTION",
53585
+ `theme.json.site_meta.${metaKey}.description`,
53586
+ `site_meta key '${metaKey}' description must be a string at most ${MENU_SLOT_DESCRIPTION_MAX_LENGTH} characters`,
53587
+ "error"
53588
+ ));
53589
+ }
53590
+ if (value.type !== void 0 && !SITE_META_TYPES.has(value.type)) {
53591
+ errors.push(issue2(
53592
+ "INVALID_SITE_META_TYPE",
53593
+ `theme.json.site_meta.${metaKey}.type`,
53594
+ `site_meta key '${metaKey}' type must be one of: string, number, boolean`,
53595
+ "error"
53596
+ ));
53597
+ }
53598
+ if (value.default !== void 0) {
53599
+ const defaultType = typeof value.default;
53600
+ if (value.default !== null && defaultType !== "string" && defaultType !== "number" && defaultType !== "boolean") {
53601
+ errors.push(issue2(
53602
+ "INVALID_SITE_META_DEFAULT",
53603
+ `theme.json.site_meta.${metaKey}.default`,
53604
+ `site_meta key '${metaKey}' default must be a string, number, boolean, or null`,
53605
+ "error"
53606
+ ));
53607
+ } else if (typeof value.type === "string" && SITE_META_TYPES.has(value.type) && value.default !== null && defaultType !== value.type) {
53608
+ errors.push(issue2(
53609
+ "INVALID_SITE_META_DEFAULT",
53610
+ `theme.json.site_meta.${metaKey}.default`,
53611
+ `site_meta key '${metaKey}' default must match its declared type`,
53612
+ "error"
53613
+ ));
53614
+ }
53615
+ }
53616
+ if (typeof value.title === "string" && value.title.trim() !== "" && value.title.trim().length <= MENU_SLOT_TITLE_MAX_LENGTH && (value.description === void 0 || typeof value.description === "string" && value.description.trim().length <= MENU_SLOT_DESCRIPTION_MAX_LENGTH) && (value.type === void 0 || SITE_META_TYPES.has(value.type)) && (value.default === void 0 || value.default === null || typeof value.default === "string" || typeof value.default === "number" || typeof value.default === "boolean")) {
53617
+ normalizedItems[metaKey] = {
53618
+ title: value.title.trim(),
53619
+ ...typeof value.description === "string" && value.description.trim() !== "" ? { description: value.description.trim() } : {},
53620
+ ...typeof value.type === "string" && SITE_META_TYPES.has(value.type) ? { type: value.type } : {},
53621
+ ...value.default !== void 0 ? { default: value.default } : {}
53622
+ };
53623
+ }
53624
+ }
53625
+ return Object.keys(normalizedItems).length > 0 ? normalizedItems : void 0;
53626
+ }
53164
53627
  async function validateThemeFiles(fileMap, options2 = {}) {
53165
53628
  const files = normalizeFileMap(fileMap);
53166
53629
  const errors = [];
@@ -53190,11 +53653,16 @@ async function validateThemeFiles(fileMap, options2 = {}) {
53190
53653
  manifest = manifestResult.manifest;
53191
53654
  errors.push(...manifestResult.errors);
53192
53655
  } catch (error) {
53656
+ const rawThemeJson = getText(files.get("theme.json"));
53193
53657
  errors.push(issue2(
53194
53658
  "INVALID_THEME_JSON",
53195
53659
  "theme.json",
53196
53660
  `Invalid theme.json: ${error instanceof Error ? error.message : String(error)}`,
53197
- "error"
53661
+ "error",
53662
+ {
53663
+ ...locationForJsonParseError(error, rawThemeJson),
53664
+ category: "json_syntax"
53665
+ }
53198
53666
  ));
53199
53667
  }
53200
53668
  }
@@ -53254,6 +53722,20 @@ function validateManifest(themeJson) {
53254
53722
  license: "",
53255
53723
  runtime: ""
53256
53724
  };
53725
+ for (const key of Object.keys(themeJson)) {
53726
+ if (!THEME_MANIFEST_KEYS.has(key)) {
53727
+ errors.push(issue2(
53728
+ "UNKNOWN_THEME_MANIFEST_FIELD",
53729
+ `theme.json.${key}`,
53730
+ `Unknown theme.json field '${key}'`,
53731
+ "error",
53732
+ {
53733
+ category: "theme_manifest",
53734
+ hint: themeManifestFieldHint(key)
53735
+ }
53736
+ ));
53737
+ }
53738
+ }
53257
53739
  if (themeJson.$schema !== void 0 && typeof themeJson.$schema !== "string") {
53258
53740
  errors.push(issue2(
53259
53741
  "INVALID_SCHEMA_HINT",
@@ -53281,13 +53763,24 @@ function validateManifest(themeJson) {
53281
53763
  errors.push(issue2("INVALID_SEMVER", "theme.json", "Theme version must follow semantic versioning (e.g. 1.0.0)", "error"));
53282
53764
  }
53283
53765
  if (typeof themeJson.runtime === "string" && !SUPPORTED_RUNTIME_SET.has(themeJson.runtime.trim())) {
53284
- errors.push(issue2("INVALID_RUNTIME_VERSION", "theme.json", `theme.json field 'runtime' must be one of: ${SUPPORTED_RUNTIMES.join(", ")}`, "error"));
53766
+ errors.push(issue2(
53767
+ "INVALID_RUNTIME_VERSION",
53768
+ "theme.json",
53769
+ `theme.json field 'runtime' must be one of: ${SUPPORTED_RUNTIMES.join(", ")}`,
53770
+ "error",
53771
+ {
53772
+ category: "theme_manifest",
53773
+ hint: `Update theme.json:
53774
+
53775
+ "runtime": "${THEME_RUNTIME_V0_6}"`
53776
+ }
53777
+ ));
53285
53778
  }
53286
- if (typeof themeJson.license === "string" && !LICENSES.has(themeJson.license.trim())) {
53779
+ if (typeof themeJson.license === "string" && !isAllowedLicense(themeJson.license.trim())) {
53287
53780
  errors.push(issue2(
53288
53781
  "INVALID_LICENSE",
53289
53782
  "theme.json",
53290
- "theme.json field 'license' must be one of: MIT, Apache-2.0, BSD-3-Clause, GPL-3.0-only, GPL-3.0-or-later",
53783
+ "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",
53291
53784
  "error"
53292
53785
  ));
53293
53786
  }
@@ -53339,11 +53832,27 @@ function validateManifest(themeJson) {
53339
53832
  manifest.description = description;
53340
53833
  }
53341
53834
  }
53835
+ if (themeJson.thumbnail !== void 0) {
53836
+ if (typeof themeJson.thumbnail !== "string") {
53837
+ errors.push(issue2(
53838
+ "INVALID_THUMBNAIL",
53839
+ "theme.json.thumbnail",
53840
+ "theme.json field 'thumbnail' must be a string when present",
53841
+ "error"
53842
+ ));
53843
+ } else {
53844
+ manifest.thumbnail = themeJson.thumbnail;
53845
+ }
53846
+ }
53847
+ const links = validateThemeLinks(themeJson.links, errors);
53848
+ if (links) {
53849
+ manifest.links = links;
53850
+ }
53342
53851
  const features = validateFeatureFlags(themeJson.features, errors);
53343
53852
  if (features) {
53344
53853
  manifest.features = features;
53345
53854
  }
53346
- const menuSlots = validateHelperMetadataMap(themeJson.menuSlots, "menuSlots", {
53855
+ const menu_slots = validateHelperMetadataMap(themeJson.menu_slots, "menu_slots", {
53347
53856
  itemLabel: "Menu slot",
53348
53857
  propertyLabel: "menu slot property",
53349
53858
  collectionLabel: "slots",
@@ -53354,10 +53863,10 @@ function validateManifest(themeJson) {
53354
53863
  invalidTitleCode: "INVALID_MENU_SLOT_TITLE",
53355
53864
  invalidDescriptionCode: "INVALID_MENU_SLOT_DESCRIPTION"
53356
53865
  }, errors);
53357
- if (menuSlots) {
53358
- manifest.menuSlots = menuSlots;
53866
+ if (menu_slots) {
53867
+ manifest.menu_slots = menu_slots;
53359
53868
  }
53360
- const widgetAreas = validateHelperMetadataMap(themeJson.widgetAreas, "widgetAreas", {
53869
+ const widget_areas = validateHelperMetadataMap(themeJson.widget_areas, "widget_areas", {
53361
53870
  itemLabel: "Widget area",
53362
53871
  propertyLabel: "widget area property",
53363
53872
  collectionLabel: "areas",
@@ -53368,8 +53877,26 @@ function validateManifest(themeJson) {
53368
53877
  invalidTitleCode: "INVALID_WIDGET_AREA_TITLE",
53369
53878
  invalidDescriptionCode: "INVALID_WIDGET_AREA_DESCRIPTION"
53370
53879
  }, errors);
53371
- if (widgetAreas) {
53372
- manifest.widgetAreas = widgetAreas;
53880
+ if (widget_areas) {
53881
+ manifest.widget_areas = widget_areas;
53882
+ }
53883
+ const site_meta = validateSiteMetaMap(themeJson.site_meta, errors);
53884
+ if (site_meta) {
53885
+ manifest.site_meta = site_meta;
53886
+ }
53887
+ const collection_slots = validateHelperMetadataMap(themeJson.collection_slots, "collection_slots", {
53888
+ itemLabel: "Collection slot",
53889
+ propertyLabel: "collection slot property",
53890
+ collectionLabel: "slots",
53891
+ invalidCollectionCode: "INVALID_COLLECTION_SLOTS",
53892
+ invalidIdCode: "INVALID_COLLECTION_SLOT_ID",
53893
+ invalidItemCode: "INVALID_COLLECTION_SLOT",
53894
+ invalidPropertyCode: "INVALID_COLLECTION_SLOT_PROPERTY",
53895
+ invalidTitleCode: "INVALID_COLLECTION_SLOT_TITLE",
53896
+ invalidDescriptionCode: "INVALID_COLLECTION_SLOT_DESCRIPTION"
53897
+ }, errors);
53898
+ if (collection_slots) {
53899
+ manifest.collection_slots = collection_slots;
53373
53900
  }
53374
53901
  return { errors, manifest: errors.length > 0 ? void 0 : manifest };
53375
53902
  }
@@ -53381,8 +53908,20 @@ function validateTemplateSyntax(templatePath, content, context) {
53381
53908
  if (contentSlotMatches.length !== 1) {
53382
53909
  errors.push(issue2("INVALID_LAYOUT_SLOT", "layout.html", "layout.html must contain exactly one {{slot:content}}", "error"));
53383
53910
  }
53384
- if (/<script\b/i.test(content)) {
53385
- errors.push(issue2("LAYOUT_SCRIPT_NOT_ALLOWED", "layout.html", "layout.html must not contain <script> tags", "error"));
53911
+ const scriptMatch = /<script\b/i.exec(content);
53912
+ if (scriptMatch) {
53913
+ errors.push(issue2(
53914
+ "LAYOUT_SCRIPT_NOT_ALLOWED",
53915
+ "layout.html",
53916
+ "layout.html must not contain <script> tags",
53917
+ "error",
53918
+ {
53919
+ ...locationForIndex(content, scriptMatch.index),
53920
+ category: "theme_validation",
53921
+ hint: "Move shared scripts into a partial such as {{partial:content-enhancements}}, then include that partial from layout.html.",
53922
+ snippet: snippetForIndex(content, scriptMatch.index)
53923
+ }
53924
+ ));
53386
53925
  }
53387
53926
  }
53388
53927
  let match2;
@@ -53433,7 +53972,9 @@ function validateRuntimeV05TemplateSyntax(templatePath, content, errors) {
53433
53972
  index = end + 2;
53434
53973
  if (token.startsWith("partial:")) {
53435
53974
  try {
53436
- parsePartialReferenceToken(token);
53975
+ parsePartialReferenceToken(token, {
53976
+ allowedSingleSegmentPaths: getPartialArgScope(stack)
53977
+ });
53437
53978
  } catch (error) {
53438
53979
  errors.push(issue2(
53439
53980
  "INVALID_PARTIAL_REFERENCE",
@@ -53458,7 +53999,7 @@ function validateRuntimeV05TemplateSyntax(templatePath, content, errors) {
53458
53999
  }
53459
54000
  if (token === "#else") {
53460
54001
  const current = stack[stack.length - 1];
53461
- if (!current || current.tag !== "if" && current.tag !== "if_eq") {
54002
+ if (!current || current.tag !== "if" && !COMPARISON_BLOCK_TAGS.has(current.tag)) {
53462
54003
  errors.push(issue2("UNEXPECTED_TEMPLATE_ELSE", templatePath, `Unexpected {{#else}} in ${templatePath}`, "error"));
53463
54004
  return;
53464
54005
  }
@@ -53469,9 +54010,11 @@ function validateRuntimeV05TemplateSyntax(templatePath, content, errors) {
53469
54010
  current.hasElse = true;
53470
54011
  continue;
53471
54012
  }
53472
- if (token.startsWith("#else_if_eq ")) {
54013
+ const comparisonElseIfTag = getComparisonElseIfTag(token);
54014
+ if (comparisonElseIfTag) {
53473
54015
  const current = stack[stack.length - 1];
53474
- if (!current || current.tag !== "if_eq") {
54016
+ const expectedBlockTag = comparisonElseIfTag.replace(/^else_/, "");
54017
+ if (!current || current.tag !== expectedBlockTag) {
53475
54018
  errors.push(issue2("UNEXPECTED_TEMPLATE_ELSE_IF", templatePath, `Unexpected {{${token}}} in ${templatePath}`, "error"));
53476
54019
  return;
53477
54020
  }
@@ -53479,13 +54022,13 @@ function validateRuntimeV05TemplateSyntax(templatePath, content, errors) {
53479
54022
  errors.push(issue2("INVALID_TEMPLATE_BRANCH_ORDER", templatePath, `{{${token}}} cannot appear after {{#else}} in ${templatePath}`, "error"));
53480
54023
  return;
53481
54024
  }
53482
- const expression = token.slice("#else_if_eq ".length).trim();
53483
- const parsed = parseIfEqExpression(expression);
54025
+ const expression = token.slice(`#${comparisonElseIfTag} `.length).trim();
54026
+ const parsed = parseComparisonExpression(expression, comparisonElseIfTag);
53484
54027
  if (!parsed) {
53485
54028
  errors.push(issue2("UNSUPPORTED_TEMPLATE_TAG", templatePath, `Unsupported template tag '{{${token}}}' in ${templatePath}`, "error"));
53486
54029
  return;
53487
54030
  }
53488
- validateReservedPathUsage(parsed.path, templatePath, errors, stack, { isPartialFile });
54031
+ validateComparisonPathUsage(parsed, templatePath, errors, stack, { isPartialFile });
53489
54032
  continue;
53490
54033
  }
53491
54034
  if (token.startsWith("#else_if ")) {
@@ -53508,7 +54051,7 @@ function validateRuntimeV05TemplateSyntax(templatePath, content, errors) {
53508
54051
  }
53509
54052
  if (token.startsWith("/")) {
53510
54053
  const closingTag = token.slice(1).trim();
53511
- if (!["if", "if_eq", "for"].includes(closingTag)) {
54054
+ if (!["if", "for", ...COMPARISON_BLOCK_TAGS].includes(closingTag)) {
53512
54055
  errors.push(issue2("UNSUPPORTED_TEMPLATE_TAG", templatePath, `Unsupported template closing tag '{{${token}}}' in ${templatePath}`, "error"));
53513
54056
  return;
53514
54057
  }
@@ -53529,22 +54072,23 @@ function validateRuntimeV05TemplateSyntax(templatePath, content, errors) {
53529
54072
  stack.push({ tag: "if", hasElse: false });
53530
54073
  continue;
53531
54074
  }
53532
- if (token.startsWith("#if_eq ")) {
53533
- const expression = token.slice("#if_eq ".length).trim();
53534
- const parsed = parseIfEqExpression(expression);
54075
+ const comparisonBlockTag = getComparisonBlockTag(token);
54076
+ if (comparisonBlockTag) {
54077
+ const expression = token.slice(`#${comparisonBlockTag} `.length).trim();
54078
+ const parsed = parseComparisonExpression(expression, comparisonBlockTag);
53535
54079
  if (!parsed) {
53536
54080
  errors.push(issue2("UNSUPPORTED_TEMPLATE_TAG", templatePath, `Unsupported template tag '{{${token}}}' in ${templatePath}`, "error"));
53537
54081
  return;
53538
54082
  }
53539
- validateReservedPathUsage(parsed.path, templatePath, errors, stack, { isPartialFile });
53540
- stack.push({ tag: "if_eq", hasElse: false });
54083
+ validateComparisonPathUsage(parsed, templatePath, errors, stack, { isPartialFile });
54084
+ stack.push({ tag: comparisonBlockTag, hasElse: false });
53541
54085
  continue;
53542
54086
  }
53543
54087
  const forMatch = FOR_TAG_REGEX.exec(token);
53544
54088
  if (forMatch) {
53545
54089
  const path4 = forMatch[2];
53546
54090
  validateReservedPathUsage(path4, templatePath, errors, stack, { isPartialFile });
53547
- stack.push({ tag: "for", hasElse: false });
54091
+ stack.push({ tag: "for", itemName: forMatch[1], hasElse: false });
53548
54092
  continue;
53549
54093
  }
53550
54094
  errors.push(issue2("UNSUPPORTED_TEMPLATE_TAG", templatePath, `Unsupported template tag '{{${token}}}' in ${templatePath}`, "error"));
@@ -53577,16 +54121,124 @@ function validateReservedPathUsage(path4, templatePath, errors, stack, options2
53577
54121
  ));
53578
54122
  }
53579
54123
  }
53580
- function parseIfEqExpression(expression) {
53581
- const match2 = IF_EQ_EXPRESSION_REGEX.exec(expression);
53582
- if (!match2) {
54124
+ function validateComparisonPathUsage(parsed, templatePath, errors, stack, options2) {
54125
+ for (const operand of [parsed.left, ...parsed.operands]) {
54126
+ if (operand.kind === "path") {
54127
+ validateReservedPathUsage(operand.path, templatePath, errors, stack, options2);
54128
+ }
54129
+ }
54130
+ }
54131
+ function getComparisonBlockTag(token) {
54132
+ for (const tagName of COMPARISON_BLOCK_TAGS) {
54133
+ if (token.startsWith(`#${tagName} `)) {
54134
+ return tagName;
54135
+ }
54136
+ }
54137
+ return "";
54138
+ }
54139
+ function getComparisonElseIfTag(token) {
54140
+ for (const tagName of COMPARISON_ELSE_IF_TAGS) {
54141
+ if (token.startsWith(`#${tagName} `)) {
54142
+ return tagName;
54143
+ }
54144
+ }
54145
+ return "";
54146
+ }
54147
+ function parseComparisonExpression(expression, tagName) {
54148
+ const operator = COMPARISON_TAG_OPERATORS[tagName];
54149
+ let tokens;
54150
+ try {
54151
+ tokens = tokenizeExpression(expression);
54152
+ } catch {
54153
+ return null;
54154
+ }
54155
+ if (!operator || operator === "in" && tokens.length < 2 || operator !== "in" && tokens.length !== 2) {
53583
54156
  return null;
53584
54157
  }
54158
+ const left = parsePathOperand(tokens[0]);
54159
+ if (!left) {
54160
+ return null;
54161
+ }
54162
+ const operands = [];
54163
+ for (const token of tokens.slice(1)) {
54164
+ const operand = parseComparisonOperand(token);
54165
+ if (!operand) {
54166
+ return null;
54167
+ }
54168
+ operands.push(operand);
54169
+ }
53585
54170
  return {
53586
- path: match2[1],
53587
- literal: match2[2]
54171
+ operator,
54172
+ left,
54173
+ operands
53588
54174
  };
53589
54175
  }
54176
+ function tokenizeExpression(expression) {
54177
+ const tokens = [];
54178
+ let index = 0;
54179
+ while (index < expression.length) {
54180
+ while (/\s/.test(expression[index] || "")) {
54181
+ index += 1;
54182
+ }
54183
+ if (index >= expression.length) {
54184
+ break;
54185
+ }
54186
+ if (expression[index] === '"') {
54187
+ const start2 = index;
54188
+ index += 1;
54189
+ let escaped = false;
54190
+ while (index < expression.length) {
54191
+ const char = expression[index];
54192
+ if (escaped) {
54193
+ escaped = false;
54194
+ } else if (char === "\\") {
54195
+ escaped = true;
54196
+ } else if (char === '"') {
54197
+ index += 1;
54198
+ tokens.push(expression.slice(start2, index));
54199
+ break;
54200
+ }
54201
+ index += 1;
54202
+ }
54203
+ if (tokens[tokens.length - 1] !== expression.slice(start2, index)) {
54204
+ throw new Error(`Unclosed string literal in expression: ${expression}`);
54205
+ }
54206
+ continue;
54207
+ }
54208
+ const start = index;
54209
+ while (index < expression.length && !/\s/.test(expression[index])) {
54210
+ index += 1;
54211
+ }
54212
+ tokens.push(expression.slice(start, index));
54213
+ }
54214
+ return tokens;
54215
+ }
54216
+ function parsePathOperand(token) {
54217
+ return TEMPLATE_PATH_REGEX.test(token) ? { kind: "path", path: token } : null;
54218
+ }
54219
+ function parseComparisonOperand(token) {
54220
+ if (token.startsWith('"')) {
54221
+ try {
54222
+ const value = JSON.parse(token);
54223
+ return typeof value === "string" ? { kind: "literal", value } : null;
54224
+ } catch {
54225
+ return null;
54226
+ }
54227
+ }
54228
+ if (token === "true") {
54229
+ return { kind: "literal", value: true };
54230
+ }
54231
+ if (token === "false") {
54232
+ return { kind: "literal", value: false };
54233
+ }
54234
+ if (token === "null") {
54235
+ return { kind: "literal", value: null };
54236
+ }
54237
+ if (NUMBER_LITERAL_REGEX.test(token)) {
54238
+ return { kind: "literal", value: Number(token) };
54239
+ }
54240
+ return parsePathOperand(token);
54241
+ }
53590
54242
  function validatePartialReferences(templateContents, partialContents, context) {
53591
54243
  const { errors } = context;
53592
54244
  for (const [templatePath, content] of templateContents.entries()) {
@@ -53652,7 +54304,7 @@ function getReferencedPartialNames(content) {
53652
54304
  let match2;
53653
54305
  while ((match2 = PARTIAL_TAG_REGEX.exec(content)) !== null) {
53654
54306
  try {
53655
- const { name } = parsePartialReferenceToken(match2[1]);
54307
+ const { name } = parsePartialReferenceToken(match2[1], { validateArgs: false });
53656
54308
  matches.add(name);
53657
54309
  } catch {
53658
54310
  }
@@ -53660,7 +54312,7 @@ function getReferencedPartialNames(content) {
53660
54312
  PARTIAL_TAG_REGEX.lastIndex = 0;
53661
54313
  return matches;
53662
54314
  }
53663
- function parsePartialReferenceToken(token) {
54315
+ function parsePartialReferenceToken(token, options2 = {}) {
53664
54316
  const source = String(token || "").trim();
53665
54317
  if (!source.startsWith("partial:")) {
53666
54318
  throw new Error("Partial token must start with partial:");
@@ -53675,10 +54327,12 @@ function parsePartialReferenceToken(token) {
53675
54327
  throw new Error(`Invalid partial name '${name}'`);
53676
54328
  }
53677
54329
  const argsSource = expression.slice(name.length).trim();
53678
- parsePartialArgs(argsSource);
54330
+ if (options2.validateArgs !== false) {
54331
+ parsePartialArgs(argsSource, options2);
54332
+ }
53679
54333
  return { name };
53680
54334
  }
53681
- function parsePartialArgs(source) {
54335
+ function parsePartialArgs(source, options2 = {}) {
53682
54336
  if (!source) {
53683
54337
  return {};
53684
54338
  }
@@ -53730,7 +54384,8 @@ function parsePartialArgs(source) {
53730
54384
  if (cursor >= source.length || source[cursor] !== '"') {
53731
54385
  throw new Error(`Unclosed string literal for partial argument '${key}'`);
53732
54386
  }
53733
- args[key] = JSON.parse(source.slice(index, cursor + 1));
54387
+ JSON.parse(source.slice(index, cursor + 1));
54388
+ args[key] = true;
53734
54389
  index = cursor + 1;
53735
54390
  continue;
53736
54391
  }
@@ -53740,10 +54395,25 @@ function parsePartialArgs(source) {
53740
54395
  continue;
53741
54396
  }
53742
54397
  if (source.startsWith("false", index) && isValueBoundary(source, index + 5)) {
53743
- args[key] = false;
54398
+ args[key] = true;
53744
54399
  index += 5;
53745
54400
  continue;
53746
54401
  }
54402
+ if (source.startsWith("null", index) && isValueBoundary(source, index + 4)) {
54403
+ args[key] = true;
54404
+ index += 4;
54405
+ continue;
54406
+ }
54407
+ const valueMatch = /^\S+/.exec(source.slice(index));
54408
+ if (!valueMatch) {
54409
+ throw new Error(`Missing partial argument value for '${key}'`);
54410
+ }
54411
+ const valueToken = valueMatch[0];
54412
+ if (NUMBER_LITERAL_REGEX.test(valueToken) || TEMPLATE_PATH_REGEX.test(valueToken) && isAllowedPartialArgPath(valueToken, options2)) {
54413
+ args[key] = true;
54414
+ index += valueToken.length;
54415
+ continue;
54416
+ }
53747
54417
  throw new Error(`Unsupported partial argument value for '${key}'`);
53748
54418
  }
53749
54419
  return args;
@@ -53751,6 +54421,23 @@ function parsePartialArgs(source) {
53751
54421
  function isValueBoundary(source, index) {
53752
54422
  return index >= source.length || /\s/.test(source[index]);
53753
54423
  }
54424
+ function isAllowedPartialArgPath(valueToken, options2) {
54425
+ if (valueToken.includes(".")) {
54426
+ return true;
54427
+ }
54428
+ const allowedSingleSegmentPaths = options2?.allowedSingleSegmentPaths;
54429
+ return allowedSingleSegmentPaths instanceof Set && allowedSingleSegmentPaths.has(valueToken);
54430
+ }
54431
+ function getPartialArgScope(stack) {
54432
+ const scope = new Set(PARTIAL_ARG_ROOT_PATHS);
54433
+ for (const entry of stack) {
54434
+ if (entry.tag === "for" && typeof entry.itemName === "string") {
54435
+ scope.add(entry.itemName);
54436
+ scope.add("loop");
54437
+ }
54438
+ }
54439
+ return scope;
54440
+ }
53754
54441
  function validatePathSafety(pathEntries, errors) {
53755
54442
  for (const entry of pathEntries) {
53756
54443
  const entryPath = normalizePath(String(entry.path || ""));
@@ -53798,13 +54485,71 @@ function normalizeAbsolutePath(value) {
53798
54485
  function isPathInsideRoot(candidate, root) {
53799
54486
  return candidate === root || candidate.startsWith(`${root}/`);
53800
54487
  }
53801
- function issue2(code2, filePath, message, severity) {
53802
- return {
54488
+ function issue2(code2, filePath, message, severity, details = {}) {
54489
+ const nextIssue = {
53803
54490
  code: code2,
53804
54491
  path: filePath,
53805
54492
  message,
53806
54493
  severity
53807
54494
  };
54495
+ if (Number.isInteger(details.line) && details.line > 0) {
54496
+ nextIssue.line = details.line;
54497
+ }
54498
+ if (Number.isInteger(details.column) && details.column > 0) {
54499
+ nextIssue.column = details.column;
54500
+ }
54501
+ if (typeof details.hint === "string" && details.hint.trim()) {
54502
+ nextIssue.hint = details.hint;
54503
+ }
54504
+ if (typeof details.category === "string" && details.category.trim()) {
54505
+ nextIssue.category = details.category;
54506
+ }
54507
+ if (details.snippet && typeof details.snippet === "object" && typeof details.snippet.line === "string" && typeof details.snippet.pointer === "string") {
54508
+ nextIssue.snippet = {
54509
+ line: details.snippet.line,
54510
+ pointer: details.snippet.pointer
54511
+ };
54512
+ }
54513
+ return nextIssue;
54514
+ }
54515
+ function locationForJsonParseError(error, source) {
54516
+ const message = error instanceof Error ? error.message : String(error);
54517
+ const lineColumnMatch = /\bline\s+(\d+)\s+column\s+(\d+)/i.exec(message);
54518
+ if (lineColumnMatch) {
54519
+ return {
54520
+ line: Number(lineColumnMatch[1]),
54521
+ column: Number(lineColumnMatch[2])
54522
+ };
54523
+ }
54524
+ const positionMatch = /\bposition\s+(\d+)/i.exec(message);
54525
+ if (positionMatch) {
54526
+ return locationForIndex(source, Number(positionMatch[1]));
54527
+ }
54528
+ if (/Unexpected end of JSON input/i.test(message)) {
54529
+ return locationForIndex(source, source.length);
54530
+ }
54531
+ return {};
54532
+ }
54533
+ function locationForIndex(source, index) {
54534
+ const boundedIndex = Math.max(0, Math.min(Number.isInteger(index) ? index : 0, source.length));
54535
+ let line = 1;
54536
+ let column = 1;
54537
+ for (let cursor = 0; cursor < boundedIndex; cursor += 1) {
54538
+ if (source[cursor] === "\n") {
54539
+ line += 1;
54540
+ column = 1;
54541
+ } else {
54542
+ column += 1;
54543
+ }
54544
+ }
54545
+ return { line, column };
54546
+ }
54547
+ function snippetForIndex(source, index) {
54548
+ const location = locationForIndex(source, index);
54549
+ const lines = String(source).split(/\r?\n/);
54550
+ const line = lines[location.line - 1] || "";
54551
+ const pointer = `${" ".repeat(Math.max(location.column - 1, 0))}^`;
54552
+ return { line, pointer };
53808
54553
  }
53809
54554
 
53810
54555
  // node_modules/@zeropress/build-core/src/assets/asset-processor.js
@@ -53814,7 +54559,7 @@ var AssetProcessor = class {
53814
54559
  return css.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\s+/g, " ").replace(/\s*([{}:;,>+~])\s*/g, "$1").replace(/;}/g, "}").trim();
53815
54560
  }
53816
54561
  async processJavaScript(js) {
53817
- return js.replace(/\/\/(?![^\r\n]*https?:)[^\r\n]*/g, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/\s+/g, " ").replace(/\s*([{}();,=+\-*/<>!&|])\s*/g, "$1").trim();
54562
+ return js;
53818
54563
  }
53819
54564
  generateAssetHash(content) {
53820
54565
  const hash = createHash("sha256");
@@ -59270,6 +60015,10 @@ function sanitizeHtml(html) {
59270
60015
  "li",
59271
60016
  "blockquote",
59272
60017
  "aside",
60018
+ "figure",
60019
+ "figcaption",
60020
+ "picture",
60021
+ "source",
59273
60022
  "table",
59274
60023
  "thead",
59275
60024
  "tbody",
@@ -59285,9 +60034,10 @@ function sanitizeHtml(html) {
59285
60034
  const allowedAttributes = {
59286
60035
  a: /* @__PURE__ */ new Set(["href", "title", "class", "id"]),
59287
60036
  aside: /* @__PURE__ */ new Set(["role", "class", "id"]),
59288
- img: /* @__PURE__ */ new Set(["src", "alt", "title", "class", "id", "width", "height"]),
60037
+ img: /* @__PURE__ */ new Set(["src", "srcset", "sizes", "alt", "title", "class", "id", "width", "height", "loading", "decoding"]),
59289
60038
  iframe: /* @__PURE__ */ new Set(["src", "width", "height", "frameborder", "allowfullscreen", "class"]),
59290
60039
  input: /* @__PURE__ */ new Set(["type", "checked", "disabled", "class", "id"]),
60040
+ source: /* @__PURE__ */ new Set(["src", "srcset", "sizes", "type", "media", "width", "height", "class", "id"]),
59291
60041
  "*": /* @__PURE__ */ new Set(["class", "id"])
59292
60042
  };
59293
60043
  const safeUriPattern = /^(?:(?:https?|mailto|tel):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i;
@@ -59315,13 +60065,16 @@ function sanitizeHtml(html) {
59315
60065
  if ((attributeName === "href" || attributeName === "src") && !safeUriPattern.test(attributeValue)) {
59316
60066
  continue;
59317
60067
  }
60068
+ if (attributeName === "srcset" && !isSafeSrcset(attributeValue, safeUriPattern)) {
60069
+ continue;
60070
+ }
59318
60071
  if (normalizedTag === "input" && attributeName === "type" && attributeValue !== "checkbox") {
59319
60072
  continue;
59320
60073
  }
59321
60074
  filteredAttributes.push(`${attributeName}="${attributeValue}"`);
59322
60075
  }
59323
60076
  }
59324
- const isSelfClosing = match2.endsWith("/>") || normalizedTag === "br" || normalizedTag === "hr" || normalizedTag === "img" || normalizedTag === "input";
60077
+ const isSelfClosing = match2.endsWith("/>") || normalizedTag === "br" || normalizedTag === "hr" || normalizedTag === "img" || normalizedTag === "input" || normalizedTag === "source";
59325
60078
  const attributeSuffix = filteredAttributes.length > 0 ? ` ${filteredAttributes.join(" ")}` : "";
59326
60079
  return isSelfClosing ? `<${normalizedTag}${attributeSuffix} />` : `<${normalizedTag}${attributeSuffix}>`;
59327
60080
  });
@@ -59332,6 +60085,13 @@ function sanitizeHtml(html) {
59332
60085
  return part.replace(/&(?!(?:[a-zA-Z]+|#\d+|#x[0-9a-fA-F]+);)/g, "&amp;");
59333
60086
  }).join("");
59334
60087
  }
60088
+ function isSafeSrcset(value, safeUriPattern) {
60089
+ const candidates = String(value).split(",").map((candidate) => candidate.trim()).filter(Boolean);
60090
+ return candidates.length > 0 && candidates.every((candidate) => {
60091
+ const [url] = candidate.split(/\s+/);
60092
+ return Boolean(url) && safeUriPattern.test(url);
60093
+ });
60094
+ }
59335
60095
  function slugify(value) {
59336
60096
  return String(value || "").toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
59337
60097
  }
@@ -59424,6 +60184,9 @@ var VariableResolver = class _VariableResolver {
59424
60184
  }
59425
60185
  }
59426
60186
  }
60187
+ if (variablePath === "data" || variablePath.startsWith("data.") || variablePath.includes(".data.")) {
60188
+ return false;
60189
+ }
59427
60190
  const lastSegment = variablePath.split(".").pop();
59428
60191
  if (lastSegment === "html" || lastSegment?.endsWith("_html")) {
59429
60192
  return true;
@@ -59441,7 +60204,10 @@ var VariableResolver = class _VariableResolver {
59441
60204
  // node_modules/@zeropress/build-core/src/render/partial-resolver.js
59442
60205
  var PARTIAL_NAME_REGEX2 = /^[a-zA-Z_][a-zA-Z0-9_-]*(?:\/[a-zA-Z_][a-zA-Z0-9_-]*)*$/;
59443
60206
  var IDENTIFIER_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
59444
- function parsePartialToken(token) {
60207
+ var PATH_SEGMENT_SOURCE = "[a-zA-Z_][a-zA-Z0-9_]*(?:-[a-zA-Z0-9_]+)*";
60208
+ var PATH_REGEX = new RegExp(`^${PATH_SEGMENT_SOURCE}(?:\\.${PATH_SEGMENT_SOURCE})*$`);
60209
+ var NUMBER_LITERAL_REGEX2 = /^-?(?:0|[1-9]\d*)(?:\.\d+)?$/;
60210
+ function parsePartialToken(token, options2 = {}) {
59445
60211
  const source = String(token || "").trim();
59446
60212
  if (!source.startsWith("partial:")) {
59447
60213
  throw new Error(`Invalid partial token: ${source}`);
@@ -59456,10 +60222,10 @@ function parsePartialToken(token) {
59456
60222
  throw new Error(`Invalid partial name: ${name}`);
59457
60223
  }
59458
60224
  const argsSource = expression.slice(name.length).trim();
59459
- const args = parsePartialArgs2(argsSource);
60225
+ const args = parsePartialArgs2(argsSource, options2);
59460
60226
  return { name, args };
59461
60227
  }
59462
- function parsePartialArgs2(source) {
60228
+ function parsePartialArgs2(source, options2 = {}) {
59463
60229
  if (!source) {
59464
60230
  return {};
59465
60231
  }
@@ -59511,20 +60277,40 @@ function parsePartialArgs2(source) {
59511
60277
  if (cursor >= source.length || source[cursor] !== '"') {
59512
60278
  throw new Error(`Unclosed string literal for partial argument "${key}"`);
59513
60279
  }
59514
- args[key] = JSON.parse(source.slice(index, cursor + 1));
60280
+ args[key] = { kind: "literal", value: JSON.parse(source.slice(index, cursor + 1)) };
59515
60281
  index = cursor + 1;
59516
60282
  continue;
59517
60283
  }
59518
60284
  if (source.startsWith("true", index) && isValueBoundary2(source, index + 4)) {
59519
- args[key] = true;
60285
+ args[key] = { kind: "literal", value: true };
59520
60286
  index += 4;
59521
60287
  continue;
59522
60288
  }
59523
60289
  if (source.startsWith("false", index) && isValueBoundary2(source, index + 5)) {
59524
- args[key] = false;
60290
+ args[key] = { kind: "literal", value: false };
59525
60291
  index += 5;
59526
60292
  continue;
59527
60293
  }
60294
+ if (source.startsWith("null", index) && isValueBoundary2(source, index + 4)) {
60295
+ args[key] = { kind: "literal", value: null };
60296
+ index += 4;
60297
+ continue;
60298
+ }
60299
+ const valueMatch = /^\S+/.exec(source.slice(index));
60300
+ if (!valueMatch) {
60301
+ throw new Error(`Missing partial argument value for "${key}"`);
60302
+ }
60303
+ const valueToken = valueMatch[0];
60304
+ if (NUMBER_LITERAL_REGEX2.test(valueToken)) {
60305
+ args[key] = { kind: "literal", value: Number(valueToken) };
60306
+ index += valueToken.length;
60307
+ continue;
60308
+ }
60309
+ if (PATH_REGEX.test(valueToken) && isAllowedPathArgument(valueToken, options2)) {
60310
+ args[key] = { kind: "path", path: valueToken };
60311
+ index += valueToken.length;
60312
+ continue;
60313
+ }
59528
60314
  throw new Error(`Unsupported partial argument value for "${key}"`);
59529
60315
  }
59530
60316
  return args;
@@ -59532,12 +60318,146 @@ function parsePartialArgs2(source) {
59532
60318
  function isValueBoundary2(source, index) {
59533
60319
  return index >= source.length || /\s/.test(source[index]);
59534
60320
  }
60321
+ function isAllowedPathArgument(valueToken, options2) {
60322
+ if (valueToken.includes(".")) {
60323
+ return true;
60324
+ }
60325
+ const allowedSingleSegmentPaths = options2?.allowedSingleSegmentPaths;
60326
+ return allowedSingleSegmentPaths instanceof Set && allowedSingleSegmentPaths.has(valueToken);
60327
+ }
59535
60328
 
59536
60329
  // node_modules/@zeropress/build-core/src/render/control-flow-renderer.js
59537
- var PATH_SEGMENT_SOURCE = "[a-zA-Z_][a-zA-Z0-9_]*(?:-[a-zA-Z0-9_]+)*";
59538
- var PATH_REGEX = new RegExp(`^${PATH_SEGMENT_SOURCE}(?:\\.${PATH_SEGMENT_SOURCE})*$`);
59539
- var FOR_EXPRESSION_REGEX = new RegExp(`^([a-zA-Z_][a-zA-Z0-9_]*)\\s+in\\s+(${PATH_SEGMENT_SOURCE}(?:\\.${PATH_SEGMENT_SOURCE})*)$`);
59540
- var IF_EQ_EXPRESSION_REGEX2 = new RegExp(`^(${PATH_SEGMENT_SOURCE}(?:\\.${PATH_SEGMENT_SOURCE})*)\\s+("(?:[^"\\\\]|\\\\.)*")$`);
60330
+ var PATH_SEGMENT_SOURCE2 = "[a-zA-Z_][a-zA-Z0-9_]*(?:-[a-zA-Z0-9_]+)*";
60331
+ var PATH_REGEX2 = new RegExp(`^${PATH_SEGMENT_SOURCE2}(?:\\.${PATH_SEGMENT_SOURCE2})*$`);
60332
+ var FOR_EXPRESSION_REGEX = new RegExp(`^([a-zA-Z_][a-zA-Z0-9_]*)\\s+in\\s+(${PATH_SEGMENT_SOURCE2}(?:\\.${PATH_SEGMENT_SOURCE2})*)$`);
60333
+ var NUMBER_LITERAL_REGEX3 = /^-?(?:0|[1-9]\d*)(?:\.\d+)?$/;
60334
+ var COMPARISON_BLOCK_TAGS2 = /* @__PURE__ */ new Set(["if_eq", "if_neq", "if_in", "if_starts_with"]);
60335
+ var COMPARISON_ELSE_IF_TAGS2 = /* @__PURE__ */ new Set(["else_if_eq", "else_if_neq", "else_if_in", "else_if_starts_with"]);
60336
+ var COMPARISON_TAG_OPERATORS2 = {
60337
+ if_eq: "eq",
60338
+ else_if_eq: "eq",
60339
+ if_neq: "neq",
60340
+ else_if_neq: "neq",
60341
+ if_in: "in",
60342
+ else_if_in: "in",
60343
+ if_starts_with: "starts_with",
60344
+ else_if_starts_with: "starts_with"
60345
+ };
60346
+ var PARTIAL_ARG_ROOT_PATHS2 = /* @__PURE__ */ new Set([
60347
+ "archive",
60348
+ "author",
60349
+ "category",
60350
+ "collection",
60351
+ "collections",
60352
+ "currentUrl",
60353
+ "language",
60354
+ "menu",
60355
+ "menus",
60356
+ "meta",
60357
+ "page",
60358
+ "pagination",
60359
+ "partial",
60360
+ "post",
60361
+ "posts",
60362
+ "route",
60363
+ "site",
60364
+ "tag",
60365
+ "taxonomies",
60366
+ "taxonomy",
60367
+ "widget",
60368
+ "widgets"
60369
+ ]);
60370
+ function getComparisonBlockTag2(token) {
60371
+ for (const tagName of COMPARISON_BLOCK_TAGS2) {
60372
+ if (token.startsWith(`#${tagName} `)) {
60373
+ return tagName;
60374
+ }
60375
+ }
60376
+ return "";
60377
+ }
60378
+ function getComparisonElseIfTag2(token) {
60379
+ for (const tagName of COMPARISON_ELSE_IF_TAGS2) {
60380
+ if (token.startsWith(`#${tagName} `)) {
60381
+ return tagName;
60382
+ }
60383
+ }
60384
+ return "";
60385
+ }
60386
+ function tokenizeExpression2(expression) {
60387
+ const tokens = [];
60388
+ let index = 0;
60389
+ while (index < expression.length) {
60390
+ while (/\s/.test(expression[index] || "")) {
60391
+ index += 1;
60392
+ }
60393
+ if (index >= expression.length) {
60394
+ break;
60395
+ }
60396
+ if (expression[index] === '"') {
60397
+ const start2 = index;
60398
+ index += 1;
60399
+ let escaped = false;
60400
+ while (index < expression.length) {
60401
+ const char = expression[index];
60402
+ if (escaped) {
60403
+ escaped = false;
60404
+ } else if (char === "\\") {
60405
+ escaped = true;
60406
+ } else if (char === '"') {
60407
+ index += 1;
60408
+ tokens.push(expression.slice(start2, index));
60409
+ break;
60410
+ }
60411
+ index += 1;
60412
+ }
60413
+ if (tokens[tokens.length - 1] !== expression.slice(start2, index)) {
60414
+ throw new Error(`Unclosed string literal in expression: ${expression}`);
60415
+ }
60416
+ continue;
60417
+ }
60418
+ const start = index;
60419
+ while (index < expression.length && !/\s/.test(expression[index])) {
60420
+ index += 1;
60421
+ }
60422
+ tokens.push(expression.slice(start, index));
60423
+ }
60424
+ return tokens;
60425
+ }
60426
+ function parsePathOperand2(token, tagName, expression) {
60427
+ if (!PATH_REGEX2.test(token)) {
60428
+ throw new Error(`Invalid ${tagName} expression: ${expression}`);
60429
+ }
60430
+ return { kind: "path", path: token };
60431
+ }
60432
+ function parseComparisonOperand2(token, tagName, expression) {
60433
+ if (token.startsWith('"')) {
60434
+ try {
60435
+ const value = JSON.parse(token);
60436
+ if (typeof value !== "string") {
60437
+ throw new Error("Expected string literal");
60438
+ }
60439
+ return { kind: "literal", value };
60440
+ } catch {
60441
+ throw new Error(`Invalid ${tagName} expression: ${expression}`);
60442
+ }
60443
+ }
60444
+ if (token === "true") {
60445
+ return { kind: "literal", value: true };
60446
+ }
60447
+ if (token === "false") {
60448
+ return { kind: "literal", value: false };
60449
+ }
60450
+ if (token === "null") {
60451
+ return { kind: "literal", value: null };
60452
+ }
60453
+ if (NUMBER_LITERAL_REGEX3.test(token)) {
60454
+ return { kind: "literal", value: Number(token) };
60455
+ }
60456
+ if (PATH_REGEX2.test(token)) {
60457
+ return { kind: "path", path: token };
60458
+ }
60459
+ throw new Error(`Invalid ${tagName} expression: ${expression}`);
60460
+ }
59541
60461
  var ControlFlowRenderer = class {
59542
60462
  constructor(options2 = {}) {
59543
60463
  this.resolvePath = typeof options2.resolvePath === "function" ? options2.resolvePath : ((data, path4) => path4.split(".").reduce((current, segment) => {
@@ -59560,7 +60480,7 @@ var ControlFlowRenderer = class {
59560
60480
  }
59561
60481
  parse(template) {
59562
60482
  const source = String(template || "");
59563
- const { nodes, nextIndex, stopTag } = this.parseNodes(source, 0, /* @__PURE__ */ new Set());
60483
+ const { nodes, nextIndex, stopTag } = this.parseNodes(source, 0, /* @__PURE__ */ new Set(), PARTIAL_ARG_ROOT_PATHS2);
59564
60484
  if (stopTag) {
59565
60485
  throw new Error(`Unexpected closing tag ${stopTag}`);
59566
60486
  }
@@ -59569,7 +60489,7 @@ var ControlFlowRenderer = class {
59569
60489
  }
59570
60490
  return nodes;
59571
60491
  }
59572
- parseNodes(source, startIndex, stopTags) {
60492
+ parseNodes(source, startIndex, stopTags, partialArgScope) {
59573
60493
  const nodes = [];
59574
60494
  let index = startIndex;
59575
60495
  while (index < source.length) {
@@ -59620,9 +60540,10 @@ var ControlFlowRenderer = class {
59620
60540
  }
59621
60541
  return { nodes, nextIndex: tokenEnd + 2, stopTag: "else" };
59622
60542
  }
59623
- if (token.startsWith("#else_if_eq ")) {
59624
- if (!stopTags.has("else_if_eq")) {
59625
- throw new Error("Unexpected else_if_eq tag");
60543
+ const comparisonElseIfTag = getComparisonElseIfTag2(token);
60544
+ if (comparisonElseIfTag) {
60545
+ if (!stopTags.has(comparisonElseIfTag)) {
60546
+ throw new Error(`Unexpected ${comparisonElseIfTag} tag`);
59626
60547
  }
59627
60548
  return { nodes, nextIndex: tokenEnd + 2, stopTag: token };
59628
60549
  }
@@ -59633,21 +60554,22 @@ var ControlFlowRenderer = class {
59633
60554
  return { nodes, nextIndex: tokenEnd + 2, stopTag: token };
59634
60555
  }
59635
60556
  if (token.startsWith("partial:")) {
59636
- const { name, args } = parsePartialToken(token);
60557
+ const { name, args } = parsePartialToken(token, { allowedSingleSegmentPaths: partialArgScope });
59637
60558
  nodes.push({ type: "partial", name, args });
59638
60559
  index = tokenEnd + 2;
59639
60560
  continue;
59640
60561
  }
59641
- if (token.startsWith("#if_eq ")) {
59642
- const expression = token.slice("#if_eq ".length).trim();
59643
- const block2 = this.parseIfEqBlock(source, tokenEnd + 2, expression);
60562
+ const comparisonBlockTag = getComparisonBlockTag2(token);
60563
+ if (comparisonBlockTag) {
60564
+ const expression = token.slice(`#${comparisonBlockTag} `.length).trim();
60565
+ const block2 = this.parseComparisonBlock(source, tokenEnd + 2, comparisonBlockTag, expression, partialArgScope);
59644
60566
  nodes.push(block2.node);
59645
60567
  index = block2.nextIndex;
59646
60568
  continue;
59647
60569
  }
59648
60570
  if (token.startsWith("#if ")) {
59649
60571
  const expression = token.slice("#if ".length).trim();
59650
- const block2 = this.parseIfBlock(source, tokenEnd + 2, expression);
60572
+ const block2 = this.parseIfBlock(source, tokenEnd + 2, expression, partialArgScope);
59651
60573
  nodes.push(block2.node);
59652
60574
  index = block2.nextIndex;
59653
60575
  continue;
@@ -59659,7 +60581,8 @@ var ControlFlowRenderer = class {
59659
60581
  throw new Error(`Invalid for expression: ${expression}`);
59660
60582
  }
59661
60583
  const [, itemName, path4] = match2;
59662
- const forResult = this.parseNodes(source, tokenEnd + 2, /* @__PURE__ */ new Set(["for"]));
60584
+ const forScope = /* @__PURE__ */ new Set([...partialArgScope, itemName, "loop"]);
60585
+ const forResult = this.parseNodes(source, tokenEnd + 2, /* @__PURE__ */ new Set(["for"]), forScope);
59663
60586
  if (forResult.stopTag !== "for") {
59664
60587
  throw new Error("Unclosed for block");
59665
60588
  }
@@ -59676,8 +60599,8 @@ var ControlFlowRenderer = class {
59676
60599
  }
59677
60600
  return { nodes, nextIndex: source.length, stopTag: null };
59678
60601
  }
59679
- parseIfBlock(source, startIndex, initialPath) {
59680
- if (!PATH_REGEX.test(initialPath)) {
60602
+ parseIfBlock(source, startIndex, initialPath, partialArgScope) {
60603
+ if (!PATH_REGEX2.test(initialPath)) {
59681
60604
  throw new Error(`Invalid if expression: ${initialPath}`);
59682
60605
  }
59683
60606
  const branches = [];
@@ -59685,13 +60608,13 @@ var ControlFlowRenderer = class {
59685
60608
  let nextIndex = startIndex;
59686
60609
  let currentPath = initialPath;
59687
60610
  while (true) {
59688
- const branchResult = this.parseNodes(source, nextIndex, /* @__PURE__ */ new Set(["else", "else_if", "if"]));
60611
+ const branchResult = this.parseNodes(source, nextIndex, /* @__PURE__ */ new Set(["else", "else_if", "if"]), partialArgScope);
59689
60612
  branches.push({
59690
60613
  path: currentPath,
59691
60614
  consequent: branchResult.nodes
59692
60615
  });
59693
60616
  if (branchResult.stopTag === "else") {
59694
- const elseResult = this.parseNodes(source, branchResult.nextIndex, /* @__PURE__ */ new Set(["if"]));
60617
+ const elseResult = this.parseNodes(source, branchResult.nextIndex, /* @__PURE__ */ new Set(["if"]), partialArgScope);
59695
60618
  if (elseResult.stopTag !== "if") {
59696
60619
  throw new Error("Unclosed if block after else");
59697
60620
  }
@@ -59701,7 +60624,7 @@ var ControlFlowRenderer = class {
59701
60624
  }
59702
60625
  if (typeof branchResult.stopTag === "string" && branchResult.stopTag.startsWith("#else_if ")) {
59703
60626
  currentPath = branchResult.stopTag.slice("#else_if ".length).trim();
59704
- if (!PATH_REGEX.test(currentPath)) {
60627
+ if (!PATH_REGEX2.test(currentPath)) {
59705
60628
  throw new Error(`Invalid else_if expression: ${currentPath}`);
59706
60629
  }
59707
60630
  nextIndex = branchResult.nextIndex;
@@ -59722,60 +60645,65 @@ var ControlFlowRenderer = class {
59722
60645
  nextIndex
59723
60646
  };
59724
60647
  }
59725
- parseIfEqBlock(source, startIndex, expression) {
59726
- const initialBranch = this.parseIfEqBranchExpression(expression, "if_eq");
60648
+ parseComparisonBlock(source, startIndex, tagName, expression, partialArgScope) {
60649
+ const initialBranch = this.parseComparisonBranchExpression(expression, tagName);
59727
60650
  const branches = [];
59728
60651
  let alternate = [];
59729
60652
  let nextIndex = startIndex;
59730
60653
  let currentBranch = initialBranch;
60654
+ const elseIfTag = `else_${tagName}`;
59731
60655
  while (true) {
59732
- const branchResult = this.parseNodes(source, nextIndex, /* @__PURE__ */ new Set(["else", "else_if_eq", "if_eq"]));
60656
+ const branchResult = this.parseNodes(source, nextIndex, /* @__PURE__ */ new Set(["else", elseIfTag, tagName]), partialArgScope);
59733
60657
  branches.push({
59734
- path: currentBranch.path,
59735
- literal: currentBranch.literal,
60658
+ operator: currentBranch.operator,
60659
+ left: currentBranch.left,
60660
+ operands: currentBranch.operands,
59736
60661
  consequent: branchResult.nodes
59737
60662
  });
59738
60663
  if (branchResult.stopTag === "else") {
59739
- const elseResult = this.parseNodes(source, branchResult.nextIndex, /* @__PURE__ */ new Set(["if_eq"]));
59740
- if (elseResult.stopTag !== "if_eq") {
59741
- throw new Error("Unclosed if_eq block after else");
60664
+ const elseResult = this.parseNodes(source, branchResult.nextIndex, /* @__PURE__ */ new Set([tagName]), partialArgScope);
60665
+ if (elseResult.stopTag !== tagName) {
60666
+ throw new Error(`Unclosed ${tagName} block after else`);
59742
60667
  }
59743
60668
  alternate = elseResult.nodes;
59744
60669
  nextIndex = elseResult.nextIndex;
59745
60670
  break;
59746
60671
  }
59747
- if (typeof branchResult.stopTag === "string" && branchResult.stopTag.startsWith("#else_if_eq ")) {
59748
- currentBranch = this.parseIfEqBranchExpression(
59749
- branchResult.stopTag.slice("#else_if_eq ".length).trim(),
59750
- "else_if_eq"
60672
+ if (typeof branchResult.stopTag === "string" && branchResult.stopTag.startsWith(`#${elseIfTag} `)) {
60673
+ currentBranch = this.parseComparisonBranchExpression(
60674
+ branchResult.stopTag.slice(`#${elseIfTag} `.length).trim(),
60675
+ elseIfTag
59751
60676
  );
59752
60677
  nextIndex = branchResult.nextIndex;
59753
60678
  continue;
59754
60679
  }
59755
- if (branchResult.stopTag === "if_eq") {
60680
+ if (branchResult.stopTag === tagName) {
59756
60681
  nextIndex = branchResult.nextIndex;
59757
60682
  break;
59758
60683
  }
59759
- throw new Error("Unclosed if_eq block");
60684
+ throw new Error(`Unclosed ${tagName} block`);
59760
60685
  }
59761
60686
  return {
59762
60687
  node: {
59763
- type: "if_eq",
60688
+ type: "comparison",
59764
60689
  branches,
59765
60690
  alternate
59766
60691
  },
59767
60692
  nextIndex
59768
60693
  };
59769
60694
  }
59770
- parseIfEqBranchExpression(expression, tagName) {
59771
- const match2 = IF_EQ_EXPRESSION_REGEX2.exec(expression);
59772
- if (!match2) {
60695
+ parseComparisonBranchExpression(expression, tagName) {
60696
+ const operator = COMPARISON_TAG_OPERATORS2[tagName];
60697
+ const tokens = tokenizeExpression2(expression);
60698
+ if (!operator || operator === "in" && tokens.length < 2 || operator !== "in" && tokens.length !== 2) {
59773
60699
  throw new Error(`Invalid ${tagName} expression: ${expression}`);
59774
60700
  }
59775
- const [, path4, literalSource] = match2;
60701
+ const left = parsePathOperand2(tokens[0], tagName, expression);
60702
+ const operands = tokens.slice(1).map((token) => parseComparisonOperand2(token, tagName, expression));
59776
60703
  return {
59777
- path: path4,
59778
- literal: JSON.parse(literalSource)
60704
+ operator,
60705
+ left,
60706
+ operands
59779
60707
  };
59780
60708
  }
59781
60709
  renderNodes(nodes, data, renderOptions) {
@@ -59789,8 +60717,8 @@ var ControlFlowRenderer = class {
59789
60717
  const branch = node.branches.find((entry) => this.isTruthy(this.resolvePath(data, entry.path)));
59790
60718
  return branch ? this.renderNodes(branch.consequent, data, renderOptions) : this.renderNodes(node.alternate, data, renderOptions);
59791
60719
  }
59792
- case "if_eq": {
59793
- const branch = node.branches.find((entry) => this.resolvePath(data, entry.path) === entry.literal);
60720
+ case "comparison": {
60721
+ const branch = node.branches.find((entry) => this.evaluateComparison(entry, data));
59794
60722
  return branch ? this.renderNodes(branch.consequent, data, renderOptions) : this.renderNodes(node.alternate, data, renderOptions);
59795
60723
  }
59796
60724
  case "for": {
@@ -59825,15 +60753,62 @@ var ControlFlowRenderer = class {
59825
60753
  throw new Error(`Circular partial reference detected: ${cycle}`);
59826
60754
  }
59827
60755
  const partialTemplate = String(partials.get(node.name) || "");
60756
+ const partialArgs = this.resolvePartialArgs(node.args, data);
59828
60757
  const partialData = {
59829
60758
  ...data,
59830
- partial: { ...node.args }
60759
+ partial: {
60760
+ ...data?.partial && typeof data.partial === "object" ? data.partial : {},
60761
+ ...partialArgs
60762
+ }
59831
60763
  };
59832
60764
  return this.renderTemplate(partialTemplate, partialData, {
59833
60765
  ...renderOptions,
59834
60766
  partialStack: [...activeStack, node.name]
59835
60767
  });
59836
60768
  }
60769
+ resolvePartialArgs(args, data) {
60770
+ const resolved = {};
60771
+ for (const [key, operand] of Object.entries(args || {})) {
60772
+ if (operand?.kind === "literal") {
60773
+ resolved[key] = operand.value;
60774
+ continue;
60775
+ }
60776
+ if (operand?.kind === "path") {
60777
+ resolved[key] = this.resolvePath(data, operand.path);
60778
+ }
60779
+ }
60780
+ return resolved;
60781
+ }
60782
+ evaluateComparison(entry, data) {
60783
+ const left = this.evaluateOperand(entry.left, data);
60784
+ const operands = entry.operands.map((operand) => this.evaluateOperand(operand, data));
60785
+ if (entry.operator === "eq") {
60786
+ const right = operands[0];
60787
+ return !left.missing && !right.missing && left.value === right.value;
60788
+ }
60789
+ if (entry.operator === "neq") {
60790
+ const right = operands[0];
60791
+ return left.missing || right.missing || left.value !== right.value;
60792
+ }
60793
+ if (entry.operator === "in") {
60794
+ if (left.missing) {
60795
+ return false;
60796
+ }
60797
+ return operands.some((operand) => !operand.missing && left.value === operand.value);
60798
+ }
60799
+ if (entry.operator === "starts_with") {
60800
+ const right = operands[0];
60801
+ return !left.missing && !right.missing && typeof left.value === "string" && typeof right.value === "string" && left.value.startsWith(right.value);
60802
+ }
60803
+ return false;
60804
+ }
60805
+ evaluateOperand(operand, data) {
60806
+ if (operand.kind === "literal") {
60807
+ return { value: operand.value, missing: false };
60808
+ }
60809
+ const value = this.resolvePath(data, operand.path);
60810
+ return { value, missing: value === void 0 };
60811
+ }
59837
60812
  isTruthy(value) {
59838
60813
  if (Array.isArray(value)) {
59839
60814
  return value.length > 0;
@@ -59875,7 +60850,7 @@ var ZeroPressEngine = class {
59875
60850
  const renderData = this.combineRenderData(data, context);
59876
60851
  const renderedContent = this.renderTemplate(template, renderData);
59877
60852
  const layoutWithSlots = this.slotResolver.resolve(layout, this.themePackage.partials, CONTENT_SLOT_PLACEHOLDER);
59878
- return this.renderTemplate(layoutWithSlots, renderData).replaceAll(CONTENT_SLOT_PLACEHOLDER, renderedContent);
60853
+ return this.renderTemplate(layoutWithSlots, renderData).replaceAll(CONTENT_SLOT_PLACEHOLDER, () => renderedContent);
59879
60854
  }
59880
60855
  combineRenderData(data, context) {
59881
60856
  return {
@@ -59886,7 +60861,7 @@ var ZeroPressEngine = class {
59886
60861
  };
59887
60862
  }
59888
60863
  renderTemplate(template, data) {
59889
- if (this.themePackage?.metadata?.runtime !== "0.5") {
60864
+ if (this.themePackage?.metadata?.runtime !== "0.6") {
59890
60865
  throw new Error(`Unsupported theme runtime: ${this.themePackage?.metadata?.runtime || "unknown"}`);
59891
60866
  }
59892
60867
  return this.controlFlowRenderer.render(template, data, {
@@ -59902,13 +60877,17 @@ var ZeroPressEngine = class {
59902
60877
  var DEFAULT_OPTIONS = {
59903
60878
  assetHashing: true,
59904
60879
  generateSpecialFiles: true,
60880
+ generateRobotsTxt: true,
59905
60881
  writeManifest: false
59906
60882
  };
59907
60883
  var DEFAULT_POSTS_PER_PAGE = 10;
59908
- var DEFAULT_DATE_FORMAT = "YYYY-MM-DD";
59909
- var DEFAULT_TIME_FORMAT = "HH:mm";
60884
+ var DEFAULT_DATETIME_DISPLAY = "static";
60885
+ var DEFAULT_DATE_STYLE = "medium";
60886
+ var DEFAULT_TIME_STYLE = "none";
59910
60887
  var DEFAULT_TIMEZONE = "UTC";
59911
60888
  var DEFAULT_LOCALE = "en-US";
60889
+ var DATETIME_DISPLAY_MODES = /* @__PURE__ */ new Set(["static", "client"]);
60890
+ var DATETIME_STYLES = /* @__PURE__ */ new Set(["none", "short", "medium", "long", "full"]);
59912
60891
  var DEFAULT_PERMALINKS = Object.freeze({
59913
60892
  output_style: "directory",
59914
60893
  posts: "/posts/:slug/",
@@ -59926,9 +60905,24 @@ var DEFAULT_POST_INDEX = Object.freeze({
59926
60905
  });
59927
60906
  var PERMALINK_OUTPUT_STYLES = /* @__PURE__ */ new Set(["directory", "html-extension"]);
59928
60907
  var COMMENT_POLICY_OUTPUT_PATH = "_zeropress/comment-policy.json";
60908
+ var SEARCH_INDEX_OUTPUT_PATH = "_zeropress/search.json";
60909
+ var SEARCH_ADAPTER_OUTPUT_PATH = "_zeropress/search.js";
59929
60910
  var OUTPUT_PATH_CONTROL_CHAR_PATTERN = /[\u0000-\u001F\u007F]/;
59930
60911
  var SAFE_MEDIA_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
59931
60912
  var SAFE_LINK_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]);
60913
+ var MEDIA_DELIVERY_MODES = /* @__PURE__ */ new Set(["none", "media_domain"]);
60914
+ var DISCOVERABILITY_VALUES = /* @__PURE__ */ new Set(["default", "noindex", "delist"]);
60915
+ var RESPONSIVE_IMAGE_WIDTHS = [320, 480, 768, 1024, 1280, 1600, 1920];
60916
+ var RESPONSIVE_IMAGE_EXTENSIONS = /* @__PURE__ */ new Set(["jpg", "jpeg", "png", "webp", "avif"]);
60917
+ var SEARCH_FIELD_WEIGHTS = Object.freeze({
60918
+ title: 5,
60919
+ headings: 3,
60920
+ tags: 2.5,
60921
+ categories: 2,
60922
+ excerpt: 1.5,
60923
+ content_text: 1
60924
+ });
60925
+ var SEARCH_RECENCY_BOOST_MAX = 0.15;
59932
60926
  async function buildSite(input2) {
59933
60927
  const options2 = { ...DEFAULT_OPTIONS, ...input2.options || {} };
59934
60928
  const state = await createBuildState(input2, options2);
@@ -59971,12 +60965,22 @@ async function buildSite(input2) {
59971
60965
  "application/json"
59972
60966
  );
59973
60967
  if (options2.generateSpecialFiles) {
60968
+ await writeOutput(state.writer, state.summaries, SEARCH_INDEX_OUTPUT_PATH, buildSearchIndexJson(state), "application/json");
60969
+ await writeOutput(state.writer, state.summaries, SEARCH_ADAPTER_OUTPUT_PATH, buildSearchAdapterJs(), "application/javascript");
59974
60970
  await maybeRenderNotFoundPage(state);
59975
60971
  if (hasCanonicalSiteUrl(state.previewData.site.url)) {
59976
- await writeOutput(state.writer, state.summaries, "sitemap.xml", buildSitemapXml(state.previewData.site, state.emitted, state.generatedAt), "application/xml");
60972
+ await writeOutput(
60973
+ state.writer,
60974
+ state.summaries,
60975
+ "sitemap.xml",
60976
+ buildSitemapXml(state.previewData.site, state.emitted, state.generatedAt, options2.sitemapStylesheetHref),
60977
+ "application/xml"
60978
+ );
59977
60979
  await writeOutput(state.writer, state.summaries, "feed.xml", buildFeedXml(state.previewData.site, state.emitted, state.generatedAt), "application/rss+xml");
59978
60980
  }
59979
- await writeOutput(state.writer, state.summaries, "robots.txt", buildRobotsTxt(state.previewData.site), "text/plain");
60981
+ if (shouldGenerateRobotsTxt(options2)) {
60982
+ await writeOutput(state.writer, state.summaries, "robots.txt", buildRobotsTxt(state.previewData.site), "text/plain");
60983
+ }
59980
60984
  }
59981
60985
  return finalizeBuildResult(state.writer, state.summaries, options2);
59982
60986
  }
@@ -59989,7 +60993,7 @@ async function createBuildState(input2, options2) {
59989
60993
  const engine = new ZeroPressEngine();
59990
60994
  const assetProcessor = new AssetProcessor();
59991
60995
  const summaries = [];
59992
- const previewData = normalizePreviewData(input2.previewData);
60996
+ const previewData = normalizePreviewData(input2.previewData, options2);
59993
60997
  const renderData = createRenderData(previewData, themePackage.metadata);
59994
60998
  engine.initialize(themePackage);
59995
60999
  const assetOutputs = await buildAssetOutputs(themePackage.assets, assetProcessor, options2);
@@ -60012,6 +61016,8 @@ async function createBuildState(input2, options2) {
60012
61016
  assetMap,
60013
61017
  customCssHref: customCssAsset ? `/${customCssAsset.path}` : "",
60014
61018
  customHtml: previewData.custom_html,
61019
+ favicon: previewData.site.favicon,
61020
+ exposeGenerator: previewData.site.expose_generator !== false,
60015
61021
  commentPolicyContent: buildCommentPolicyManifest(renderData.posts),
60016
61022
  options: options2,
60017
61023
  generatedAt: /* @__PURE__ */ new Date(),
@@ -60068,6 +61074,7 @@ async function renderRoute(state, templateName, route) {
60068
61074
  {
60069
61075
  menus: state.previewData.menus,
60070
61076
  widgets: state.widgets,
61077
+ collections: state.renderData.collections,
60071
61078
  taxonomies: state.renderData.taxonomies,
60072
61079
  ...route,
60073
61080
  route: routeContext,
@@ -60111,6 +61118,7 @@ async function renderFrontPage(state, route) {
60111
61118
  {
60112
61119
  menus: state.previewData.menus,
60113
61120
  widgets: state.widgets,
61121
+ collections: state.renderData.collections,
60114
61122
  taxonomies: state.renderData.taxonomies,
60115
61123
  page,
60116
61124
  route: routeContext,
@@ -60119,7 +61127,8 @@ async function renderFrontPage(state, route) {
60119
61127
  title: buildDocumentTitle(page.title, state.previewData.site.title),
60120
61128
  description: page.excerpt,
60121
61129
  ogType: "website",
60122
- image: page.featured_image
61130
+ image: page.featured_image,
61131
+ robotsNoindex: shouldNoindexDocument(page)
60123
61132
  })
60124
61133
  },
60125
61134
  createRenderContext(state.previewData.site, currentUrl)
@@ -60131,7 +61140,8 @@ async function renderFrontPage(state, route) {
60131
61140
  url: currentUrl,
60132
61141
  title: page.title,
60133
61142
  description: page.excerpt,
60134
- includeInFeed: false
61143
+ includeInFeed: false,
61144
+ includeInSitemap: !isDelistedDocument(page)
60135
61145
  };
60136
61146
  return;
60137
61147
  }
@@ -60140,6 +61150,7 @@ async function renderFrontPage(state, route) {
60140
61150
  {
60141
61151
  menus: state.previewData.menus,
60142
61152
  widgets: state.widgets,
61153
+ collections: state.renderData.collections,
60143
61154
  taxonomies: state.renderData.taxonomies,
60144
61155
  ...route,
60145
61156
  route: routeContext,
@@ -60169,6 +61180,7 @@ async function renderPost(state, post) {
60169
61180
  {
60170
61181
  menus: state.previewData.menus,
60171
61182
  widgets: state.widgets,
61183
+ collections: state.renderData.collections,
60172
61184
  taxonomies: state.renderData.taxonomies,
60173
61185
  post,
60174
61186
  route: buildRouteContext("post", currentUrl),
@@ -60179,7 +61191,8 @@ async function renderPost(state, post) {
60179
61191
  ogType: "article",
60180
61192
  image: post.featured_image,
60181
61193
  publishedTime: post.published_at_iso,
60182
- modifiedTime: post.updated_at_iso
61194
+ modifiedTime: post.updated_at_iso,
61195
+ robotsNoindex: shouldNoindexDocument(post)
60183
61196
  })
60184
61197
  },
60185
61198
  createRenderContext(state.previewData.site, currentUrl)
@@ -60187,14 +61200,16 @@ async function renderPost(state, post) {
60187
61200
  html = state.assetProcessor.updateAssetReferences(html, state.assetMap);
60188
61201
  html = injectSiteCustomizations(html, state);
60189
61202
  await writeOutput(state.writer, state.summaries, routePathToOutputPath(post.url, state.previewData.site.permalinks.output_style), html, "text/html");
60190
- state.emitted.posts.push({
60191
- url: currentUrl,
60192
- title: post.title,
60193
- description: post.excerpt,
60194
- publishedAt: post.published_at_iso,
60195
- updatedAt: post.updated_at_iso,
60196
- status: post.status
60197
- });
61203
+ if (!isDelistedDocument(post)) {
61204
+ state.emitted.posts.push({
61205
+ url: currentUrl,
61206
+ title: post.title,
61207
+ description: post.excerpt,
61208
+ publishedAt: post.published_at_iso,
61209
+ updatedAt: post.updated_at_iso,
61210
+ status: post.status
61211
+ });
61212
+ }
60198
61213
  }
60199
61214
  async function renderPage(state, page) {
60200
61215
  const currentUrl = page.url;
@@ -60205,6 +61220,7 @@ async function renderPage(state, page) {
60205
61220
  {
60206
61221
  menus: state.previewData.menus,
60207
61222
  widgets: state.widgets,
61223
+ collections: state.renderData.collections,
60208
61224
  taxonomies: state.renderData.taxonomies,
60209
61225
  page,
60210
61226
  route: buildRouteContext("page", currentUrl),
@@ -60214,7 +61230,8 @@ async function renderPage(state, page) {
60214
61230
  title: buildDocumentTitle(page.title, state.previewData.site.title),
60215
61231
  description: page.excerpt,
60216
61232
  ogType: "website",
60217
- image: page.featured_image
61233
+ image: page.featured_image,
61234
+ robotsNoindex: shouldNoindexDocument(page)
60218
61235
  })
60219
61236
  },
60220
61237
  createRenderContext(state.previewData.site, currentUrl)
@@ -60227,7 +61244,7 @@ async function renderPage(state, page) {
60227
61244
  title: page.title,
60228
61245
  description: page.excerpt,
60229
61246
  status: page.status,
60230
- includeInSitemap: page.omit_from_sitemap !== true
61247
+ includeInSitemap: page.omit_from_sitemap !== true && !isDelistedDocument(page)
60231
61248
  });
60232
61249
  }
60233
61250
  async function maybeRenderNotFoundPage(state) {
@@ -60239,6 +61256,7 @@ async function maybeRenderNotFoundPage(state) {
60239
61256
  {
60240
61257
  menus: state.previewData.menus,
60241
61258
  widgets: state.widgets,
61259
+ collections: state.renderData.collections,
60242
61260
  taxonomies: state.renderData.taxonomies,
60243
61261
  route: buildRouteContext("not_found", "/404.html"),
60244
61262
  meta: buildPageMeta(state.previewData.site, {
@@ -60254,76 +61272,220 @@ async function maybeRenderNotFoundPage(state) {
60254
61272
  html = injectSiteCustomizations(html, state);
60255
61273
  await writeOutput(state.writer, state.summaries, "404.html", html, "text/html");
60256
61274
  }
60257
- function normalizePreviewData(previewData) {
61275
+ function normalizePreviewData(previewData, options2 = {}) {
60258
61276
  const normalizedSite = {
60259
61277
  ...previewData.site,
60260
- mediaBaseUrl: normalizeOptionalString(previewData.site.mediaBaseUrl),
60261
- postsPerPage: Number.isInteger(previewData.site.postsPerPage) && previewData.site.postsPerPage > 0 ? previewData.site.postsPerPage : DEFAULT_POSTS_PER_PAGE,
60262
- dateFormat: normalizeNonEmptyString(previewData.site.dateFormat, DEFAULT_DATE_FORMAT),
60263
- timeFormat: typeof previewData.site.timeFormat === "string" ? previewData.site.timeFormat : DEFAULT_TIME_FORMAT,
61278
+ media_base_url: normalizeOptionalString(previewData.site.media_base_url),
61279
+ media_delivery_mode: MEDIA_DELIVERY_MODES.has(previewData.site.media_delivery_mode) ? previewData.site.media_delivery_mode : "none",
61280
+ favicon: normalizeSiteFavicon(previewData.site.favicon || options2.favicon),
61281
+ 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,
61282
+ datetime_display: DATETIME_DISPLAY_MODES.has(previewData.site.datetime_display) ? previewData.site.datetime_display : DEFAULT_DATETIME_DISPLAY,
61283
+ date_style: DATETIME_STYLES.has(previewData.site.date_style) ? previewData.site.date_style : DEFAULT_DATE_STYLE,
61284
+ time_style: DATETIME_STYLES.has(previewData.site.time_style) ? previewData.site.time_style : DEFAULT_TIME_STYLE,
60264
61285
  timezone: normalizeNonEmptyString(previewData.site.timezone, DEFAULT_TIMEZONE),
60265
61286
  locale: normalizeLocale(previewData.site.locale || DEFAULT_LOCALE),
60266
- disallowComments: previewData.site.disallowComments === true,
61287
+ disallow_comments: previewData.site.disallow_comments === true,
61288
+ expose_generator: previewData.site.expose_generator !== false,
61289
+ indexing: previewData.site.indexing !== false,
60267
61290
  permalinks: normalizePermalinks(previewData.site.permalinks),
60268
61291
  front_page: normalizeFrontPage(previewData.site.front_page),
60269
61292
  post_index: normalizePostIndex(previewData.site.post_index),
60270
61293
  footer: normalizeSiteFooter(previewData.site.footer)
60271
61294
  };
61295
+ const media = normalizeContentMedia(previewData.content.media, normalizedSite);
61296
+ const mediaRegistry = buildMediaRegistry(media);
60272
61297
  return {
60273
61298
  ...previewData,
60274
61299
  site: normalizedSite,
60275
- widgets: normalizeWidgetAreas(previewData.widgets, normalizedSite.mediaBaseUrl),
61300
+ menus: normalizeRecordMap(previewData.menus),
61301
+ collections: normalizeCollections(previewData.collections),
61302
+ widgets: normalizeWidgetAreas(previewData.widgets, normalizedSite.media_base_url),
60276
61303
  custom_css: normalizeCustomCss(previewData.custom_css),
60277
61304
  custom_html: normalizeCustomHtml(previewData.custom_html),
60278
61305
  content: {
60279
61306
  ...previewData.content,
60280
- authors: previewData.content.authors.map((author) => ({
60281
- ...author,
60282
- avatar: normalizeMediaField(author.avatar, normalizedSite.mediaBaseUrl)
60283
- })),
60284
- posts: previewData.content.posts.map((post) => ({
60285
- ...post,
60286
- published_at_iso: normalizeIsoTimestamp(post.published_at_iso),
60287
- updated_at_iso: normalizeIsoTimestamp(post.updated_at_iso),
60288
- featured_image: normalizeMediaField(post.featured_image, normalizedSite.mediaBaseUrl)
60289
- })).sort((left, right) => toDate(right.published_at_iso).getTime() - toDate(left.published_at_iso).getTime()),
60290
- pages: previewData.content.pages.map((page) => ({
60291
- ...page,
60292
- featured_image: normalizeMediaField(page.featured_image, normalizedSite.mediaBaseUrl)
60293
- })),
61307
+ authors: previewData.content.authors.map((author) => {
61308
+ const avatar = normalizeMediaField(author.avatar, normalizedSite.media_base_url);
61309
+ const avatarMedia = deriveManagedMedia(avatar, mediaRegistry, normalizedSite);
61310
+ return {
61311
+ ...author,
61312
+ avatar,
61313
+ ...avatarMedia ? { avatar_media: avatarMedia } : {}
61314
+ };
61315
+ }),
61316
+ posts: previewData.content.posts.map((post) => {
61317
+ const featuredImage = normalizeMediaField(post.featured_image, normalizedSite.media_base_url);
61318
+ const featuredMedia = deriveManagedMedia(featuredImage, mediaRegistry, normalizedSite);
61319
+ return {
61320
+ ...post,
61321
+ published_at_iso: normalizeIsoTimestamp(post.published_at_iso),
61322
+ updated_at_iso: normalizeIsoTimestamp(post.updated_at_iso),
61323
+ discoverability: normalizeDiscoverability(post.discoverability),
61324
+ featured_image: featuredImage,
61325
+ ...featuredMedia ? { featured_media: featuredMedia } : {}
61326
+ };
61327
+ }).sort((left, right) => toDate(right.published_at_iso).getTime() - toDate(left.published_at_iso).getTime()),
61328
+ pages: previewData.content.pages.map((page) => {
61329
+ const featuredImage = normalizeMediaField(page.featured_image, normalizedSite.media_base_url);
61330
+ const featuredMedia = deriveManagedMedia(featuredImage, mediaRegistry, normalizedSite);
61331
+ return {
61332
+ ...page,
61333
+ discoverability: normalizeDiscoverability(page.discoverability),
61334
+ featured_image: featuredImage,
61335
+ ...featuredMedia ? { featured_media: featuredMedia } : {}
61336
+ };
61337
+ }),
60294
61338
  categories: [...previewData.content.categories],
60295
- tags: [...previewData.content.tags]
61339
+ tags: [...previewData.content.tags],
61340
+ media
60296
61341
  }
60297
61342
  };
60298
61343
  }
60299
- function normalizeWidgetAreas(widgetAreas, mediaBaseUrl) {
60300
- if (!widgetAreas || typeof widgetAreas !== "object") {
61344
+ function normalizeRecordMap(value) {
61345
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
61346
+ return {};
61347
+ }
61348
+ return { ...value };
61349
+ }
61350
+ function normalizeDiscoverability(value) {
61351
+ return DISCOVERABILITY_VALUES.has(value) ? value : "default";
61352
+ }
61353
+ function isDelistedDocument(document2) {
61354
+ return document2?.discoverability === "delist";
61355
+ }
61356
+ function shouldNoindexDocument(document2) {
61357
+ return document2?.discoverability === "noindex" || document2?.discoverability === "delist";
61358
+ }
61359
+ function normalizeContentMedia(mediaItems, site) {
61360
+ if (!Array.isArray(mediaItems)) {
61361
+ return [];
61362
+ }
61363
+ return mediaItems.map((item) => {
61364
+ if (!item || typeof item !== "object") {
61365
+ return null;
61366
+ }
61367
+ const src = normalizeMediaField(item.src, site.media_base_url);
61368
+ const width = Number.isInteger(item.width) && item.width > 0 ? item.width : 0;
61369
+ const height = Number.isInteger(item.height) && item.height > 0 ? item.height : 0;
61370
+ if (!src || !width || !height) {
61371
+ return null;
61372
+ }
61373
+ return {
61374
+ src,
61375
+ width,
61376
+ height,
61377
+ alt: typeof item.alt === "string" ? item.alt : ""
61378
+ };
61379
+ }).filter(Boolean);
61380
+ }
61381
+ function buildMediaRegistry(mediaItems) {
61382
+ const registry = /* @__PURE__ */ new Map();
61383
+ for (const item of mediaItems) {
61384
+ if (!registry.has(item.src)) {
61385
+ registry.set(item.src, item);
61386
+ }
61387
+ }
61388
+ return registry;
61389
+ }
61390
+ function deriveManagedMedia(src, mediaRegistry, site) {
61391
+ const normalizedSrc = normalizeOptionalString(src);
61392
+ if (!normalizedSrc) {
61393
+ return null;
61394
+ }
61395
+ const media = mediaRegistry.get(normalizedSrc);
61396
+ if (!media) {
61397
+ return null;
61398
+ }
61399
+ return {
61400
+ ...media,
61401
+ srcset: buildResponsiveImageSrcset(media, site)
61402
+ };
61403
+ }
61404
+ function buildResponsiveImageSrcset(media, site) {
61405
+ if (site.media_delivery_mode !== "media_domain") {
61406
+ return "";
61407
+ }
61408
+ const media_base_url = normalizeOptionalString(site.media_base_url);
61409
+ if (!media_base_url || !isUrlUnderMediaBase(media.src, media_base_url) || !isResponsiveRasterImage(media.src)) {
61410
+ return "";
61411
+ }
61412
+ const widths = RESPONSIVE_IMAGE_WIDTHS.filter((width) => width <= media.width);
61413
+ if (!widths.includes(media.width)) {
61414
+ widths.push(media.width);
61415
+ }
61416
+ return widths.filter((width, index, values) => width > 0 && values.indexOf(width) === index).map((width) => `${buildResponsiveImageVariantUrl(media.src, width)} ${width}w`).join(", ");
61417
+ }
61418
+ function buildResponsiveImageVariantUrl(src, width) {
61419
+ try {
61420
+ const url = new URL(src);
61421
+ url.searchParams.set("w", String(width));
61422
+ url.searchParams.set("fit", "scale-down");
61423
+ url.searchParams.set("format", "auto");
61424
+ return decodeURI(url.toString());
61425
+ } catch {
61426
+ return src;
61427
+ }
61428
+ }
61429
+ function isUrlUnderMediaBase(src, media_base_url) {
61430
+ try {
61431
+ const sourceUrl = new URL(src);
61432
+ const baseUrl = new URL(media_base_url);
61433
+ const basePath = baseUrl.pathname.endsWith("/") ? baseUrl.pathname : `${baseUrl.pathname}/`;
61434
+ return sourceUrl.origin === baseUrl.origin && sourceUrl.pathname.startsWith(basePath);
61435
+ } catch {
61436
+ return false;
61437
+ }
61438
+ }
61439
+ function isResponsiveRasterImage(src) {
61440
+ try {
61441
+ const url = new URL(src);
61442
+ const lastSegment = url.pathname.split("/").pop() || "";
61443
+ const extension = lastSegment.includes(".") ? lastSegment.split(".").pop().toLowerCase() : "";
61444
+ return RESPONSIVE_IMAGE_EXTENSIONS.has(extension);
61445
+ } catch {
61446
+ return false;
61447
+ }
61448
+ }
61449
+ function normalizeCollections(collections) {
61450
+ if (!collections || typeof collections !== "object" || Array.isArray(collections)) {
60301
61451
  return {};
60302
61452
  }
60303
61453
  return Object.fromEntries(
60304
- Object.entries(widgetAreas).map(([widgetAreaId, widgetArea]) => [
61454
+ Object.entries(collections).map(([collectionId, collection]) => [
61455
+ collectionId,
61456
+ {
61457
+ ...collection,
61458
+ title: normalizeOptionalString(collection?.title),
61459
+ description: normalizeOptionalString(collection?.description),
61460
+ items: Array.isArray(collection?.items) ? collection.items.map((item) => ({ ...item })) : []
61461
+ }
61462
+ ])
61463
+ );
61464
+ }
61465
+ function normalizeWidgetAreas(widget_areas, media_base_url) {
61466
+ if (!widget_areas || typeof widget_areas !== "object") {
61467
+ return {};
61468
+ }
61469
+ return Object.fromEntries(
61470
+ Object.entries(widget_areas).map(([widgetAreaId, widgetArea]) => [
60305
61471
  widgetAreaId,
60306
61472
  {
60307
61473
  ...widgetArea,
60308
61474
  name: normalizeNonEmptyString(widgetArea?.name, widgetAreaId),
60309
- items: Array.isArray(widgetArea?.items) ? widgetArea.items.map((item) => normalizeWidgetItem(item, mediaBaseUrl)) : []
61475
+ items: Array.isArray(widgetArea?.items) ? widgetArea.items.map((item) => normalizeWidgetItem(item, media_base_url)) : []
60310
61476
  }
60311
61477
  ])
60312
61478
  );
60313
61479
  }
60314
61480
  function normalizeSiteFooter(footer) {
60315
61481
  const source = footer && typeof footer === "object" && !Array.isArray(footer) ? footer : {};
60316
- const attribution = source.attribution && typeof source.attribution === "object" && !Array.isArray(source.attribution) ? source.attribution : {};
60317
61482
  return {
60318
61483
  ...source,
60319
61484
  copyright_text: normalizeOptionalString(source.copyright_text),
60320
- attribution: {
60321
- ...attribution,
60322
- enabled: attribution.enabled !== false
60323
- }
61485
+ attribution: source.attribution !== false
60324
61486
  };
60325
61487
  }
60326
- function normalizeWidgetItem(item, mediaBaseUrl) {
61488
+ function normalizeWidgetItem(item, media_base_url) {
60327
61489
  const normalizedItem = {
60328
61490
  ...item,
60329
61491
  title: typeof item?.title === "string" ? item.title.trim() : ""
@@ -60333,7 +61495,7 @@ function normalizeWidgetItem(item, mediaBaseUrl) {
60333
61495
  ...normalizedItem,
60334
61496
  settings: {
60335
61497
  ...item.settings,
60336
- avatar: normalizeMediaField(item.settings.avatar, mediaBaseUrl)
61498
+ avatar: normalizeMediaField(item.settings.avatar, media_base_url)
60337
61499
  }
60338
61500
  };
60339
61501
  }
@@ -60357,6 +61519,19 @@ function normalizeCustomHtml(customHtml) {
60357
61519
  ...bodyEnd ? { body_end: { content: bodyEnd } } : {}
60358
61520
  };
60359
61521
  }
61522
+ function normalizeSiteFavicon(favicon) {
61523
+ if (!favicon || typeof favicon !== "object") {
61524
+ return void 0;
61525
+ }
61526
+ const normalized = {};
61527
+ for (const key of ["icon", "svg", "png", "apple_touch_icon"]) {
61528
+ const value = normalizeOptionalString(favicon[key]);
61529
+ if (value) {
61530
+ normalized[key] = value;
61531
+ }
61532
+ }
61533
+ return Object.keys(normalized).length ? normalized : void 0;
61534
+ }
60360
61535
  function normalizePermalinks(permalinks) {
60361
61536
  const source = permalinks && typeof permalinks === "object" ? permalinks : {};
60362
61537
  const outputStyle = typeof source.output_style === "string" && PERMALINK_OUTPUT_STYLES.has(source.output_style) ? source.output_style : DEFAULT_PERMALINKS.output_style;
@@ -60379,19 +61554,19 @@ function normalizeFrontPage(frontPage) {
60379
61554
  ...type === "standalone_html" ? { html: normalizeOptionalRawString(frontPage.html) } : {}
60380
61555
  };
60381
61556
  }
60382
- function normalizePostIndex(postIndex) {
60383
- if (!postIndex || typeof postIndex !== "object") {
61557
+ function normalizePostIndex(post_index) {
61558
+ if (!post_index || typeof post_index !== "object") {
60384
61559
  return { ...DEFAULT_POST_INDEX };
60385
61560
  }
60386
61561
  return {
60387
- enabled: postIndex.enabled !== false,
60388
- path: normalizeNonEmptyString(postIndex.path, DEFAULT_POST_INDEX.path),
60389
- paginate: postIndex.paginate !== false
61562
+ enabled: post_index.enabled !== false,
61563
+ path: normalizeNonEmptyString(post_index.path, DEFAULT_POST_INDEX.path),
61564
+ paginate: post_index.paginate !== false
60390
61565
  };
60391
61566
  }
60392
61567
  function createRenderData(previewData, themeMetadata = {}) {
60393
61568
  const themeSupportsComments = themeMetadata?.features?.comments === true;
60394
- const themeSupportsPostIndex = themeMetadata?.features?.postIndex !== false;
61569
+ const themeSupportsPostIndex = themeMetadata?.features?.post_index !== false;
60395
61570
  const authorsById = new Map(previewData.content.authors.map((author) => [author.id, author]));
60396
61571
  const categoriesBySlug = new Map(previewData.content.categories.map((category) => [category.slug, category]));
60397
61572
  const tagsBySlug = new Map(previewData.content.tags.map((tag) => [tag.slug, tag]));
@@ -60399,7 +61574,8 @@ function createRenderData(previewData, themeMetadata = {}) {
60399
61574
  const tagPostsBySlug = /* @__PURE__ */ new Map();
60400
61575
  const categoryCountBySlug = /* @__PURE__ */ new Map();
60401
61576
  const tagCountBySlug = /* @__PURE__ */ new Map();
60402
- for (const post of previewData.content.posts) {
61577
+ const discoverableSourcePosts = previewData.content.posts.filter((post) => !isDelistedDocument(post));
61578
+ for (const post of discoverableSourcePosts) {
60403
61579
  for (const slug of post.category_slugs) {
60404
61580
  pushToSlugMap(categoryPostsBySlug, slug, post);
60405
61581
  categoryCountBySlug.set(slug, (categoryCountBySlug.get(slug) || 0) + 1);
@@ -60410,43 +61586,54 @@ function createRenderData(previewData, themeMetadata = {}) {
60410
61586
  }
60411
61587
  }
60412
61588
  const preparedPosts = previewData.content.posts.map((post) => preparePost(post, previewData.site, authorsById, categoriesBySlug, tagsBySlug, themeSupportsComments));
60413
- const posts = preparedPosts.map((post, index) => ({
61589
+ const discoverablePreparedPosts = preparedPosts.filter((post) => !isDelistedDocument(post));
61590
+ const adjacentPostsBySlug = new Map(
61591
+ discoverablePreparedPosts.map((post, index) => [post.slug, {
61592
+ prev: index > 0 ? buildAdjacentPostSummary(discoverablePreparedPosts[index - 1]) : null,
61593
+ next: index < discoverablePreparedPosts.length - 1 ? buildAdjacentPostSummary(discoverablePreparedPosts[index + 1]) : null
61594
+ }])
61595
+ );
61596
+ const posts = preparedPosts.map((post) => ({
60414
61597
  ...post,
60415
- prev: index > 0 ? buildAdjacentPostSummary(preparedPosts[index - 1]) : null,
60416
- next: index < preparedPosts.length - 1 ? buildAdjacentPostSummary(preparedPosts[index + 1]) : null
61598
+ prev: adjacentPostsBySlug.get(post.slug)?.prev || null,
61599
+ next: adjacentPostsBySlug.get(post.slug)?.next || null
60417
61600
  }));
60418
61601
  const pages = previewData.content.pages.map((page) => preparePage(page, previewData.site));
60419
61602
  const postBySlug = new Map(posts.map((post) => [post.slug, post]));
61603
+ const pageBySlug = new Map(pages.map((page) => [page.slug, page]));
60420
61604
  const frontPage = previewData.site.front_page;
60421
- const postIndex = previewData.site.post_index;
60422
- const effectivePostIndexEnabled = postIndex.enabled !== false && themeSupportsPostIndex;
60423
- const effectivePostIndexPaginate = effectivePostIndexEnabled && postIndex.paginate !== false;
60424
- const postIndexBasePath = normalizeRoutePath(postIndex.path || DEFAULT_POST_INDEX.path);
60425
- if (frontPage.type !== "theme_index" && effectivePostIndexEnabled && postIndexBasePath === "/") {
61605
+ const collections = resolveCollections(previewData.collections, postBySlug, pageBySlug, frontPage);
61606
+ attachCollectionCursors(posts, pages, collections);
61607
+ const post_index = previewData.site.post_index;
61608
+ const effectivePostIndexEnabled = post_index.enabled !== false && themeSupportsPostIndex;
61609
+ const effectivePostIndexPaginate = effectivePostIndexEnabled && post_index.paginate !== false;
61610
+ const post_indexBasePath = normalizeRoutePath(post_index.path || DEFAULT_POST_INDEX.path);
61611
+ if (frontPage.type !== "theme_index" && effectivePostIndexEnabled && post_indexBasePath === "/") {
60426
61612
  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.');
60427
61613
  }
60428
- const frontPageRoute = buildFrontPageRoute(frontPage, pages, effectivePostIndexEnabled, postIndexBasePath);
61614
+ const frontPageRoute = buildFrontPageRoute(frontPage, pages, effectivePostIndexEnabled, post_indexBasePath);
60429
61615
  const pageFrontPageSlug = frontPage.type === "page" ? frontPage.page_slug : "";
60430
61616
  const preparedPages = pageFrontPageSlug ? pages.filter((page) => page.slug !== pageFrontPageSlug) : pages;
60431
61617
  return {
60432
61618
  posts,
60433
61619
  pages: preparedPages,
60434
61620
  postBySlug,
61621
+ collections,
60435
61622
  taxonomies: buildGlobalTaxonomies(previewData, categoryCountBySlug, tagCountBySlug),
60436
61623
  frontPageRoute,
60437
61624
  indexRoutes: buildPostIndexRoutes({
60438
61625
  enabled: effectivePostIndexEnabled,
60439
61626
  paginate: effectivePostIndexPaginate,
60440
- items: previewData.content.posts,
60441
- postsPerPage: previewData.site.postsPerPage,
60442
- basePath: postIndexBasePath,
61627
+ items: discoverableSourcePosts,
61628
+ posts_per_page: previewData.site.posts_per_page,
61629
+ basePath: post_indexBasePath,
60443
61630
  outputStyle: previewData.site.permalinks.output_style,
60444
61631
  postBySlug,
60445
61632
  frontPage
60446
61633
  }),
60447
61634
  archiveRoutes: buildPaginatedCollection({
60448
- items: previewData.content.posts,
60449
- postsPerPage: previewData.site.postsPerPage,
61635
+ items: discoverableSourcePosts,
61636
+ posts_per_page: previewData.site.posts_per_page,
60450
61637
  basePath: "/archive/",
60451
61638
  outputStyle: previewData.site.permalinks.output_style
60452
61639
  }).map((entry) => ({
@@ -60462,7 +61649,7 @@ function createRenderData(previewData, themeMetadata = {}) {
60462
61649
  items: previewData.content.categories,
60463
61650
  postsBySlug: categoryPostsBySlug,
60464
61651
  postBySlug,
60465
- postsPerPage: previewData.site.postsPerPage,
61652
+ posts_per_page: previewData.site.posts_per_page,
60466
61653
  outputStyle: previewData.site.permalinks.output_style,
60467
61654
  buildBasePath: (category) => resolvePermalink(previewData.site, "categories", category).path,
60468
61655
  renderExtras: (category) => ({
@@ -60473,7 +61660,7 @@ function createRenderData(previewData, themeMetadata = {}) {
60473
61660
  items: previewData.content.tags,
60474
61661
  postsBySlug: tagPostsBySlug,
60475
61662
  postBySlug,
60476
- postsPerPage: previewData.site.postsPerPage,
61663
+ posts_per_page: previewData.site.posts_per_page,
60477
61664
  outputStyle: previewData.site.permalinks.output_style,
60478
61665
  buildBasePath: (tag) => resolvePermalink(previewData.site, "tags", tag).path,
60479
61666
  renderExtras: (tag) => ({
@@ -60488,9 +61675,113 @@ function buildGlobalTaxonomies(previewData, categoryCountBySlug, tagCountBySlug)
60488
61675
  tags: buildGlobalTaxonomyItems(previewData.site, "tags", previewData.content.tags, tagCountBySlug)
60489
61676
  };
60490
61677
  }
60491
- function buildFrontPageRoute(frontPage, pages, effectivePostIndexEnabled, postIndexBasePath) {
61678
+ function resolveCollections(collections, postBySlug, pageBySlug, frontPage) {
61679
+ if (!collections || typeof collections !== "object") {
61680
+ return {};
61681
+ }
61682
+ return Object.fromEntries(
61683
+ Object.entries(collections).map(([collectionId, collection]) => {
61684
+ const items = resolveCollectionItems(collectionId, collection?.items, postBySlug, pageBySlug, frontPage);
61685
+ return [
61686
+ collectionId,
61687
+ {
61688
+ id: collectionId,
61689
+ title: normalizeOptionalString(collection?.title),
61690
+ description: normalizeOptionalString(collection?.description),
61691
+ count: items.length,
61692
+ items
61693
+ }
61694
+ ];
61695
+ })
61696
+ );
61697
+ }
61698
+ function resolveCollectionItems(collectionId, items, postBySlug, pageBySlug, frontPage) {
61699
+ if (!Array.isArray(items)) {
61700
+ return [];
61701
+ }
61702
+ return items.map((item, index) => resolveCollectionItem(collectionId, item, index, postBySlug, pageBySlug, frontPage));
61703
+ }
61704
+ function resolveCollectionItem(collectionId, item, index, postBySlug, pageBySlug, frontPage) {
61705
+ if (item?.type === "post") {
61706
+ const post = postBySlug.get(item.slug);
61707
+ if (!post) {
61708
+ throw new Error(`Invalid collection "${collectionId}": item ${index + 1} references missing post slug "${item.slug}".`);
61709
+ }
61710
+ return {
61711
+ type: "post",
61712
+ meta: post.meta,
61713
+ ...buildStructuredPostSummary(post)
61714
+ };
61715
+ }
61716
+ if (item?.type === "page") {
61717
+ const page = pageBySlug.get(item.slug);
61718
+ if (!page) {
61719
+ throw new Error(`Invalid collection "${collectionId}": item ${index + 1} references missing page slug "${item.slug}".`);
61720
+ }
61721
+ return buildCollectionPageSummary(page, frontPage);
61722
+ }
61723
+ throw new Error(`Invalid collection "${collectionId}": item ${index + 1} has unsupported type "${item?.type}".`);
61724
+ }
61725
+ function buildCollectionPageSummary(page, frontPage) {
61726
+ return {
61727
+ type: "page",
61728
+ title: page.title,
61729
+ slug: page.slug,
61730
+ url: frontPage?.type === "page" && frontPage.page_slug === page.slug ? "/" : page.url,
61731
+ excerpt: page.excerpt || "",
61732
+ featured_image: page.featured_image || "",
61733
+ ...page.featured_media ? { featured_media: { ...page.featured_media } } : {},
61734
+ meta: page.meta,
61735
+ data: page.data
61736
+ };
61737
+ }
61738
+ function attachCollectionCursors(posts, pages, collections) {
61739
+ const postTargets = new Map(posts.map((post) => [post.slug, post]));
61740
+ const pageTargets = new Map(pages.map((page) => [page.slug, page]));
61741
+ for (const [collectionId, collection] of Object.entries(collections || {})) {
61742
+ const items = Array.isArray(collection.items) ? collection.items : [];
61743
+ items.forEach((item, index) => {
61744
+ const target = item.type === "post" ? postTargets.get(item.slug) : item.type === "page" ? pageTargets.get(item.slug) : null;
61745
+ if (!target) {
61746
+ return;
61747
+ }
61748
+ target.collection_cursors = target.collection_cursors || {};
61749
+ target.collection_cursors[collectionId] = buildCollectionCursor(collectionId, collection, items, index);
61750
+ });
61751
+ }
61752
+ }
61753
+ function buildCollectionCursor(collectionId, collection, items, index) {
61754
+ const count = items.length;
61755
+ return {
61756
+ collection_id: collectionId,
61757
+ collection_title: collection.title || "",
61758
+ index,
61759
+ position: index + 1,
61760
+ count,
61761
+ first: index === 0,
61762
+ last: index === count - 1,
61763
+ prev: index > 0 ? buildCollectionCursorItemSummary(items[index - 1]) : null,
61764
+ next: index < count - 1 ? buildCollectionCursorItemSummary(items[index + 1]) : null
61765
+ };
61766
+ }
61767
+ function buildCollectionCursorItemSummary(item) {
61768
+ if (!item) {
61769
+ return null;
61770
+ }
61771
+ return {
61772
+ type: item.type,
61773
+ title: item.title,
61774
+ slug: item.slug,
61775
+ url: item.url,
61776
+ excerpt: item.excerpt || "",
61777
+ featured_image: item.featured_image || "",
61778
+ meta: item.meta,
61779
+ data: item.data
61780
+ };
61781
+ }
61782
+ function buildFrontPageRoute(frontPage, pages, effectivePostIndexEnabled, post_indexBasePath) {
60492
61783
  if (frontPage.type === "theme_index") {
60493
- if (effectivePostIndexEnabled && postIndexBasePath === "/") {
61784
+ if (effectivePostIndexEnabled && post_indexBasePath === "/") {
60494
61785
  return null;
60495
61786
  }
60496
61787
  return {
@@ -60528,7 +61819,7 @@ function buildPostIndexRoutes(options2) {
60528
61819
  return [];
60529
61820
  }
60530
61821
  if (!options2.paginate) {
60531
- const items = options2.items.slice(0, options2.postsPerPage);
61822
+ const items = options2.items.slice(0, options2.posts_per_page);
60532
61823
  return [{
60533
61824
  path: options2.basePath,
60534
61825
  route_type: "post_index",
@@ -60542,7 +61833,7 @@ function buildPostIndexRoutes(options2) {
60542
61833
  }
60543
61834
  return buildPaginatedCollection({
60544
61835
  items: options2.items,
60545
- postsPerPage: options2.postsPerPage,
61836
+ posts_per_page: options2.posts_per_page,
60546
61837
  basePath: options2.basePath,
60547
61838
  outputStyle: options2.outputStyle
60548
61839
  }).map((entry) => ({
@@ -60621,7 +61912,7 @@ function resolveWidgetItem(item, previewData, renderData, widgetAreaId, index) {
60621
61912
  }
60622
61913
  function resolveRecentPostsWidget(baseWidget, settings, renderData) {
60623
61914
  const limit = clampInteger(settings?.limit, 5, 1, 20);
60624
- const items = renderData.posts.slice(0, limit).map((post) => ({
61915
+ const items = renderData.posts.filter((post) => !isDelistedDocument(post)).slice(0, limit).map((post) => ({
60625
61916
  title: post.title,
60626
61917
  url: `/posts/${encodeSlugSegment(post.slug)}/`,
60627
61918
  published_at: post.published_at,
@@ -60788,15 +62079,19 @@ function preparePost(post, site, authorsById, categoriesBySlug, tagsBySlug, them
60788
62079
  updated_at_iso: post.updated_at_iso,
60789
62080
  author_id: post.author_id,
60790
62081
  featured_image: post.featured_image,
62082
+ ...post.featured_media ? { featured_media: { ...post.featured_media } } : {},
60791
62083
  meta: post.meta,
62084
+ data: post.data,
60792
62085
  status: post.status,
62086
+ discoverability: post.discoverability,
60793
62087
  allow_comments: post.allow_comments,
60794
62088
  category_slugs: post.category_slugs,
60795
62089
  tag_slugs: post.tag_slugs,
60796
62090
  author: {
60797
62091
  id: post.author_id,
60798
62092
  display_name: normalizeNonEmptyString(author?.display_name, post.author_id),
60799
- avatar: author?.avatar || ""
62093
+ avatar: author?.avatar || "",
62094
+ ...author?.avatar_media ? { avatar_media: { ...author.avatar_media } } : {}
60800
62095
  },
60801
62096
  categories,
60802
62097
  tags,
@@ -60805,7 +62100,7 @@ function preparePost(post, site, authorsById, categoriesBySlug, tagsBySlug, them
60805
62100
  published_at: formatTimestamp(post.published_at_iso, site),
60806
62101
  updated_at: formatTimestamp(post.updated_at_iso, site),
60807
62102
  reading_time: calculateReadingTime(renderedDocument.html),
60808
- comments_enabled: themeSupportsComments && site.disallowComments !== true && post.allow_comments === true
62103
+ comments_enabled: themeSupportsComments && site.disallow_comments !== true && post.allow_comments === true
60809
62104
  };
60810
62105
  }
60811
62106
  function buildCommentPolicyManifest(posts) {
@@ -60824,7 +62119,7 @@ function buildTaxonomyRoutes(options2) {
60824
62119
  }
60825
62120
  const paginated = buildPaginatedCollection({
60826
62121
  items: matchedPosts,
60827
- postsPerPage: options2.postsPerPage,
62122
+ posts_per_page: options2.posts_per_page,
60828
62123
  basePath: options2.buildBasePath(item),
60829
62124
  outputStyle: options2.outputStyle
60830
62125
  });
@@ -60845,13 +62140,13 @@ function buildTaxonomyRoutes(options2) {
60845
62140
  function buildPaginatedCollection(options2) {
60846
62141
  const basePath = normalizePaginationBasePath(options2.basePath);
60847
62142
  const outputStyle = PERMALINK_OUTPUT_STYLES.has(options2.outputStyle) ? options2.outputStyle : DEFAULT_PERMALINKS.output_style;
60848
- const postsPerPage = Number.isInteger(options2.postsPerPage) && options2.postsPerPage > 0 ? options2.postsPerPage : DEFAULT_POSTS_PER_PAGE;
62143
+ const posts_per_page = Number.isInteger(options2.posts_per_page) && options2.posts_per_page > 0 ? options2.posts_per_page : DEFAULT_POSTS_PER_PAGE;
60849
62144
  const totalPosts = options2.items.length;
60850
- const totalPages = Math.max(1, Math.ceil(totalPosts / postsPerPage));
62145
+ const totalPages = Math.max(1, Math.ceil(totalPosts / posts_per_page));
60851
62146
  const pages = [];
60852
62147
  for (let page = 1; page <= totalPages; page += 1) {
60853
- const start = (page - 1) * postsPerPage;
60854
- const end = start + postsPerPage;
62148
+ const start = (page - 1) * posts_per_page;
62149
+ const end = start + posts_per_page;
60855
62150
  pages.push({
60856
62151
  path: buildPaginatedPath(basePath, page),
60857
62152
  page,
@@ -60998,9 +62293,13 @@ function buildStructuredPostSummary(post) {
60998
62293
  published_at_iso: post.published_at_iso,
60999
62294
  reading_time: post.reading_time,
61000
62295
  featured_image: post.featured_image,
62296
+ ...post.featured_media ? { featured_media: { ...post.featured_media } } : {},
62297
+ meta: post.meta,
62298
+ data: post.data,
61001
62299
  author: {
61002
62300
  display_name: post.author?.display_name || "",
61003
- avatar: post.author?.avatar || ""
62301
+ avatar: post.author?.avatar || "",
62302
+ ...post.author?.avatar_media ? { avatar_media: { ...post.author.avatar_media } } : {}
61004
62303
  },
61005
62304
  categories: Array.isArray(post.categories) ? post.categories.map((category) => ({ ...category })) : [],
61006
62305
  tags: Array.isArray(post.tags) ? post.tags.map((tag) => ({ ...tag })) : []
@@ -61016,7 +62315,8 @@ function buildAdjacentPostSummary(post) {
61016
62315
  url: post.url,
61017
62316
  excerpt: post.excerpt,
61018
62317
  published_at: post.published_at,
61019
- published_at_iso: post.published_at_iso
62318
+ published_at_iso: post.published_at_iso,
62319
+ data: post.data
61020
62320
  };
61021
62321
  }
61022
62322
  function buildArchiveGroups(posts, postBySlug) {
@@ -61089,40 +62389,22 @@ function formatArchiveLabel(date, site) {
61089
62389
  function formatTimestamp(value, site) {
61090
62390
  const date = toDate(value);
61091
62391
  const locale = normalizeLocale(site.locale || DEFAULT_LOCALE);
61092
- const dateFormat = normalizeNonEmptyString(site.dateFormat, DEFAULT_DATE_FORMAT);
61093
- const timeFormat = typeof site.timeFormat === "string" ? site.timeFormat : DEFAULT_TIME_FORMAT;
62392
+ const dateStyle = DATETIME_STYLES.has(site.date_style) ? site.date_style : DEFAULT_DATE_STYLE;
62393
+ const timeStyle = DATETIME_STYLES.has(site.time_style) ? site.time_style : DEFAULT_TIME_STYLE;
61094
62394
  const siteTimezone = normalizeNonEmptyString(site.timezone, DEFAULT_TIMEZONE);
61095
- const dateParts = new Intl.DateTimeFormat(locale, {
61096
- timeZone: siteTimezone,
61097
- year: "numeric",
61098
- month: dateFormat === "MMMM D, YYYY" ? "long" : "2-digit",
61099
- day: "2-digit"
61100
- }).formatToParts(date);
61101
- const year = dateParts.find((part) => part.type === "year")?.value || "";
61102
- const month = dateParts.find((part) => part.type === "month")?.value || "";
61103
- const day = dateParts.find((part) => part.type === "day")?.value || "";
61104
- let formattedDate = `${year}-${month}-${day}`;
61105
- if (dateFormat === "DD/MM/YYYY") {
61106
- formattedDate = `${day}/${month}/${year}`;
61107
- } else if (dateFormat === "MM/DD/YYYY") {
61108
- formattedDate = `${month}/${day}/${year}`;
61109
- } else if (dateFormat === "MMMM D, YYYY") {
61110
- formattedDate = `${month} ${String(Number(day))}, ${year}`;
61111
- }
61112
- if (!timeFormat) {
61113
- return formattedDate;
61114
- }
61115
- const timeParts = new Intl.DateTimeFormat(locale, {
61116
- timeZone: siteTimezone,
61117
- hour: "2-digit",
61118
- minute: "2-digit",
61119
- hour12: timeFormat === "hh:mm A"
61120
- }).formatToParts(date);
61121
- const hour = timeParts.find((part) => part.type === "hour")?.value || "";
61122
- const minute = timeParts.find((part) => part.type === "minute")?.value || "";
61123
- const dayPeriod = (timeParts.find((part) => part.type === "dayPeriod")?.value || "").toUpperCase();
61124
- const formattedTime = timeFormat === "hh:mm A" ? `${hour}:${minute}${dayPeriod ? ` ${dayPeriod}` : ""}` : `${hour}:${minute}`;
61125
- return `${formattedDate} ${formattedTime}`.trim();
62395
+ if (dateStyle === "none" && timeStyle === "none") {
62396
+ return "";
62397
+ }
62398
+ const options2 = {
62399
+ timeZone: siteTimezone
62400
+ };
62401
+ if (dateStyle !== "none") {
62402
+ options2.dateStyle = dateStyle;
62403
+ }
62404
+ if (timeStyle !== "none") {
62405
+ options2.timeStyle = timeStyle;
62406
+ }
62407
+ return new Intl.DateTimeFormat(locale, options2).format(date);
61126
62408
  }
61127
62409
  function calculateReadingTime(html) {
61128
62410
  const plainText = String(html || "").replace(/<[^>]*>/g, " ");
@@ -61203,10 +62485,12 @@ async function normalizeAndValidateThemePackage(themePackage) {
61203
62485
  ...themePackage.metadata.author ? { author: themePackage.metadata.author } : {},
61204
62486
  ...themePackage.metadata.description ? { description: themePackage.metadata.description } : {},
61205
62487
  ...themePackage.metadata.thumbnail ? { thumbnail: themePackage.metadata.thumbnail } : {},
62488
+ ...themePackage.metadata.links ? { links: themePackage.metadata.links } : {},
61206
62489
  ...themePackage.metadata.features ? { features: themePackage.metadata.features } : {},
61207
- ...themePackage.metadata.menuSlots ? { menuSlots: themePackage.metadata.menuSlots } : {},
61208
- ...themePackage.metadata.widgetAreas ? { widgetAreas: themePackage.metadata.widgetAreas } : {},
61209
- settings: themePackage.metadata.settings || {}
62490
+ ...themePackage.metadata.menu_slots ? { menu_slots: themePackage.metadata.menu_slots } : {},
62491
+ ...themePackage.metadata.widget_areas ? { widget_areas: themePackage.metadata.widget_areas } : {},
62492
+ ...themePackage.metadata.site_meta ? { site_meta: themePackage.metadata.site_meta } : {},
62493
+ ...themePackage.metadata.collection_slots ? { collection_slots: themePackage.metadata.collection_slots } : {}
61210
62494
  }));
61211
62495
  for (const [templateName, templateContent] of themePackage.templates.entries()) {
61212
62496
  fileMap.set(`${templateName}.html`, templateContent);
@@ -61219,7 +62503,7 @@ async function normalizeAndValidateThemePackage(themePackage) {
61219
62503
  }
61220
62504
  const validation = await validateThemeFiles(fileMap);
61221
62505
  if (!validation.ok) {
61222
- throw new Error(`Theme validation failed: ${validation.errors[0]?.message || "Unknown error"}`);
62506
+ throw new Error(formatThemeValidationFailure(validation));
61223
62507
  }
61224
62508
  if (!validation.manifest) {
61225
62509
  throw new Error("Theme validation failed: normalized manifest not available");
@@ -61231,11 +62515,59 @@ async function normalizeAndValidateThemePackage(themePackage) {
61231
62515
  assets: themePackage.assets
61232
62516
  };
61233
62517
  }
62518
+ function formatThemeValidationFailure(validation) {
62519
+ const blocks = [
62520
+ [
62521
+ "Theme validation failed",
62522
+ `Errors: ${validation.errors.length}`,
62523
+ `Checked files: ${validation.checkedFiles}`
62524
+ ].join("\n"),
62525
+ ...validation.errors.map((issue3) => formatThemeValidationIssue(issue3))
62526
+ ];
62527
+ return blocks.join("\n\n");
62528
+ }
62529
+ function formatThemeValidationIssue(issue3) {
62530
+ if (!issue3) {
62531
+ return "Reason: Unknown error";
62532
+ }
62533
+ const lines = [`ERROR ${issue3.code || "THEME_VALIDATION_ERROR"}`];
62534
+ const location = splitIssuePath(issue3.path);
62535
+ if (location.file) {
62536
+ lines.push(`File: ${location.file}`);
62537
+ }
62538
+ if (location.path) {
62539
+ lines.push(`Path: ${location.path}`);
62540
+ }
62541
+ if (Number.isInteger(issue3.line) && Number.isInteger(issue3.column)) {
62542
+ lines.push(`Line: ${issue3.line}, Column: ${issue3.column}`);
62543
+ }
62544
+ if (issue3.category) {
62545
+ lines.push(`Category: ${issue3.category}`);
62546
+ }
62547
+ lines.push(`Reason: ${issue3.message || "Unknown error"}`);
62548
+ if (issue3.snippet) {
62549
+ const lineLabel = Number.isInteger(issue3.line) ? String(issue3.line) : "";
62550
+ lines.push("", `${lineLabel} | ${issue3.snippet.line}`, `${" ".repeat(lineLabel.length)} | ${issue3.snippet.pointer}`);
62551
+ }
62552
+ if (issue3.hint) {
62553
+ lines.push("", "Hint:", issue3.hint);
62554
+ }
62555
+ return lines.join("\n");
62556
+ }
62557
+ function splitIssuePath(issuePath) {
62558
+ const normalizedPath = String(issuePath || "");
62559
+ if (normalizedPath.startsWith("theme.json.")) {
62560
+ return {
62561
+ file: "theme.json",
62562
+ path: normalizedPath.slice("theme.json.".length)
62563
+ };
62564
+ }
62565
+ return { file: normalizedPath, path: "" };
62566
+ }
61234
62567
  function normalizeThemePackageMetadata(sourceMetadata, manifest) {
61235
62568
  return {
61236
62569
  ...manifest,
61237
- ...sourceMetadata?.thumbnail ? { thumbnail: sourceMetadata.thumbnail } : {},
61238
- settings: sourceMetadata?.settings || {}
62570
+ ...sourceMetadata?.thumbnail ? { thumbnail: sourceMetadata.thumbnail } : {}
61239
62571
  };
61240
62572
  }
61241
62573
  async function buildAssetOutputs(assets, assetProcessor, options2) {
@@ -61301,6 +62633,7 @@ function buildPageMeta(site, options2 = {}) {
61301
62633
  const ogType = normalizeNonEmptyString(options2.ogType, "website");
61302
62634
  const publishedTime = normalizeOptionalString(options2.publishedTime);
61303
62635
  const modifiedTime = normalizeOptionalString(options2.modifiedTime);
62636
+ const robotsNoindex = options2.robotsNoindex === true;
61304
62637
  const meta = {
61305
62638
  title: escapeHtml2(resolvedTitle),
61306
62639
  description: resolvedDescription ? escapeHtml2(resolvedDescription) : "",
@@ -61312,7 +62645,8 @@ function buildPageMeta(site, options2 = {}) {
61312
62645
  og_site_name: escapeHtml2(site.title),
61313
62646
  og_image: ogImage ? escapeHtml2(ogImage) : "",
61314
62647
  article_published_time: publishedTime ? escapeHtml2(publishedTime) : "",
61315
- article_modified_time: modifiedTime ? escapeHtml2(modifiedTime) : ""
62648
+ article_modified_time: modifiedTime ? escapeHtml2(modifiedTime) : "",
62649
+ robots_noindex: robotsNoindex
61316
62650
  };
61317
62651
  return {
61318
62652
  ...meta,
@@ -61329,6 +62663,9 @@ function buildMetaHeadTags(meta) {
61329
62663
  if (meta.description) {
61330
62664
  tags.push(`<meta name="description" content="${meta.description}">`);
61331
62665
  }
62666
+ if (meta.robots_noindex) {
62667
+ tags.push('<meta name="robots" content="noindex">');
62668
+ }
61332
62669
  if (meta.canonical_url) {
61333
62670
  tags.push(`<link rel="canonical" href="${meta.canonical_url}">`);
61334
62671
  }
@@ -61369,7 +62706,7 @@ function resolveMetaImageUrl(image2) {
61369
62706
  }
61370
62707
  return "";
61371
62708
  }
61372
- function normalizeMediaField(value, mediaBaseUrl) {
62709
+ function normalizeMediaField(value, media_base_url) {
61373
62710
  if (value === void 0) {
61374
62711
  return void 0;
61375
62712
  }
@@ -61380,9 +62717,9 @@ function normalizeMediaField(value, mediaBaseUrl) {
61380
62717
  if (isAbsoluteUrl(normalizedValue)) {
61381
62718
  return normalizeAbsoluteUrl(normalizedValue, SAFE_MEDIA_PROTOCOLS);
61382
62719
  }
61383
- const normalizedBaseUrl = normalizeOptionalString(mediaBaseUrl);
62720
+ const normalizedBaseUrl = normalizeOptionalString(media_base_url);
61384
62721
  if (!normalizedBaseUrl) {
61385
- return "";
62722
+ return normalizedValue;
61386
62723
  }
61387
62724
  try {
61388
62725
  return decodeURI(new URL(normalizedValue, normalizedBaseUrl).toString());
@@ -61586,7 +62923,11 @@ function assertPlannedOutputPathsSafe(state) {
61586
62923
  COMMENT_POLICY_OUTPUT_PATH
61587
62924
  ];
61588
62925
  if (state.options.generateSpecialFiles) {
61589
- plannedPaths.push("404.html", "robots.txt");
62926
+ plannedPaths.push(SEARCH_INDEX_OUTPUT_PATH, SEARCH_ADAPTER_OUTPUT_PATH);
62927
+ plannedPaths.push("404.html");
62928
+ if (shouldGenerateRobotsTxt(state.options)) {
62929
+ plannedPaths.push("robots.txt");
62930
+ }
61590
62931
  if (hasCanonicalSiteUrl(state.previewData.site.url)) {
61591
62932
  plannedPaths.push("sitemap.xml", "feed.xml");
61592
62933
  }
@@ -61661,10 +63002,45 @@ function sha256(content) {
61661
63002
  return hash.digest("hex");
61662
63003
  }
61663
63004
  function injectSiteCustomizations(html, state) {
61664
- let next = injectCustomCssAssetLink(html, state.customCssHref);
63005
+ let next = injectFaviconLinks(html, state.favicon);
63006
+ next = injectGeneratorMeta(next, state.exposeGenerator);
63007
+ next = injectCustomCssAssetLink(next, state.customCssHref);
61665
63008
  next = injectCustomHtml(next, state.customHtml);
61666
63009
  return next;
61667
63010
  }
63011
+ function injectFaviconLinks(html, favicon) {
63012
+ const links = buildFaviconLinks(favicon);
63013
+ if (!links) {
63014
+ return html;
63015
+ }
63016
+ return html.replace("</head>", `${links}
63017
+ </head>`);
63018
+ }
63019
+ function buildFaviconLinks(favicon) {
63020
+ if (!favicon || typeof favicon !== "object") {
63021
+ return "";
63022
+ }
63023
+ const lines = [];
63024
+ if (normalizeOptionalString(favicon.icon)) {
63025
+ lines.push(` <link rel="icon" href="${escapeHtml2(favicon.icon)}" sizes="any">`);
63026
+ }
63027
+ if (normalizeOptionalString(favicon.svg)) {
63028
+ lines.push(` <link rel="icon" href="${escapeHtml2(favicon.svg)}" type="image/svg+xml">`);
63029
+ }
63030
+ if (normalizeOptionalString(favicon.png)) {
63031
+ lines.push(` <link rel="icon" href="${escapeHtml2(favicon.png)}" type="image/png">`);
63032
+ }
63033
+ if (normalizeOptionalString(favicon.apple_touch_icon)) {
63034
+ lines.push(` <link rel="apple-touch-icon" href="${escapeHtml2(favicon.apple_touch_icon)}">`);
63035
+ }
63036
+ return lines.join("\n");
63037
+ }
63038
+ function injectGeneratorMeta(html, exposeGenerator) {
63039
+ if (exposeGenerator === false) {
63040
+ return html;
63041
+ }
63042
+ return html.replace("</head>", ' <meta name="generator" content="ZeroPress">\n</head>');
63043
+ }
61668
63044
  function injectCustomCssAssetLink(html, href) {
61669
63045
  if (!normalizeOptionalString(href)) {
61670
63046
  return html;
@@ -61686,9 +63062,383 @@ function injectCustomHtml(html, customHtml) {
61686
63062
  }
61687
63063
  return next;
61688
63064
  }
61689
- function buildSitemapXml(site, emitted, generatedAt) {
63065
+ function buildSearchIndexJson(state) {
63066
+ return `${JSON.stringify(buildSearchIndexItems(state), null, 2)}
63067
+ `;
63068
+ }
63069
+ function buildSearchIndexItems(state) {
63070
+ const posts = state.renderData.posts.filter((post) => post.status === "published" && !isDelistedDocument(post)).map((post) => ({
63071
+ id: `post:${post.slug}`,
63072
+ type: "post",
63073
+ title: post.title,
63074
+ url: post.url,
63075
+ excerpt: normalizeSearchText(post.excerpt),
63076
+ headings: buildSearchHeadings(post.toc),
63077
+ categories: Array.isArray(post.categories) ? post.categories.map((category) => category.name).filter(Boolean) : [],
63078
+ tags: Array.isArray(post.tags) ? post.tags.map((tag) => tag.name).filter(Boolean) : [],
63079
+ published_at_iso: normalizeIsoTimestamp(post.published_at_iso),
63080
+ updated_at_iso: normalizeIsoTimestamp(post.updated_at_iso),
63081
+ content_text: htmlToSearchText(post.html)
63082
+ }));
63083
+ const frontPagePage = state.renderData.frontPageRoute?.front_page_type === "page" ? state.renderData.frontPageRoute.page : null;
63084
+ const frontPageItems = frontPagePage && frontPagePage.status === "published" && !isDelistedDocument(frontPagePage) ? [buildSearchPageItem(frontPagePage, "/")] : [];
63085
+ const pageItems = state.renderData.pages.filter((page) => page.status === "published" && !isDelistedDocument(page)).map((page) => buildSearchPageItem(page, page.url));
63086
+ return [...posts, ...frontPageItems, ...pageItems];
63087
+ }
63088
+ function buildSearchPageItem(page, url) {
63089
+ return {
63090
+ id: `page:${page.slug}`,
63091
+ type: "page",
63092
+ title: page.title,
63093
+ url,
63094
+ excerpt: normalizeSearchText(page.excerpt),
63095
+ headings: buildSearchHeadings(page.toc),
63096
+ categories: [],
63097
+ tags: [],
63098
+ published_at_iso: "",
63099
+ updated_at_iso: "",
63100
+ content_text: htmlToSearchText(page.html)
63101
+ };
63102
+ }
63103
+ function buildSearchHeadings(toc) {
63104
+ return Array.isArray(toc) ? toc.map((item) => normalizeSearchText(item?.title)).filter(Boolean) : [];
63105
+ }
63106
+ function htmlToSearchText(html) {
63107
+ return normalizeSearchText(decodeHtmlEntities(
63108
+ String(html || "").replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, " ").replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, " ").replace(/<!--[\s\S]*?-->/g, " ").replace(/<[^>]+>/g, " ")
63109
+ ));
63110
+ }
63111
+ function decodeHtmlEntities(value) {
63112
+ const namedEntities = {
63113
+ amp: "&",
63114
+ lt: "<",
63115
+ gt: ">",
63116
+ quot: '"',
63117
+ apos: "'",
63118
+ nbsp: " "
63119
+ };
63120
+ return String(value || "").replace(/&(#x[0-9a-fA-F]+|#\d+|[a-zA-Z][a-zA-Z0-9]+);/g, (match2, entity2) => {
63121
+ if (entity2.startsWith("#x")) {
63122
+ const codePoint = Number.parseInt(entity2.slice(2), 16);
63123
+ return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : match2;
63124
+ }
63125
+ if (entity2.startsWith("#")) {
63126
+ const codePoint = Number.parseInt(entity2.slice(1), 10);
63127
+ return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : match2;
63128
+ }
63129
+ return Object.prototype.hasOwnProperty.call(namedEntities, entity2) ? namedEntities[entity2] : match2;
63130
+ });
63131
+ }
63132
+ function normalizeSearchText(value) {
63133
+ return String(value || "").replace(/\s+/g, " ").trim();
63134
+ }
63135
+ function buildSearchAdapterJs() {
63136
+ const fieldWeightsJson = JSON.stringify(SEARCH_FIELD_WEIGHTS, null, 2);
63137
+ return `const FIELD_WEIGHTS = ${fieldWeightsJson};
63138
+ const FIELD_NAMES = Object.keys(FIELD_WEIGHTS);
63139
+ const RECENCY_BOOST_MAX = ${SEARCH_RECENCY_BOOST_MAX};
63140
+ const DEFAULT_LIMIT = 20;
63141
+ const BM25_K1 = 1.2;
63142
+ const BM25_B = 0.75;
63143
+
63144
+ let preparedIndexPromise;
63145
+
63146
+ export async function preload() {
63147
+ await loadPreparedIndex();
63148
+ }
63149
+
63150
+ export async function search(query, options = {}) {
63151
+ const prepared = await loadPreparedIndex();
63152
+ const terms = tokenize(query);
63153
+ const phrase = normalizeText(query);
63154
+ if (terms.length === 0 && !phrase) {
63155
+ return { results: [] };
63156
+ }
63157
+
63158
+ const hits = [];
63159
+ for (const document of prepared.documents) {
63160
+ const score = scoreDocument(document, terms, phrase, prepared);
63161
+ if (score > 0) {
63162
+ hits.push({ document, score });
63163
+ }
63164
+ }
63165
+
63166
+ const limit = normalizeLimit(options.limit);
63167
+ hits.sort((left, right) => right.score - left.score || left.document.raw.title.localeCompare(right.document.raw.title));
63168
+
63169
+ return {
63170
+ results: hits.slice(0, limit).map((hit) => ({
63171
+ id: hit.document.raw.id,
63172
+ score: Number(hit.score.toFixed(6)),
63173
+ data: async () => buildResultData(hit.document.raw, query),
63174
+ })),
63175
+ };
63176
+ }
63177
+
63178
+ async function loadPreparedIndex() {
63179
+ if (!preparedIndexPromise) {
63180
+ preparedIndexPromise = fetch(new URL('./search.json', import.meta.url))
63181
+ .then((response) => {
63182
+ if (!response.ok) {
63183
+ throw new Error('ZeroPress search index not found');
63184
+ }
63185
+ return response.json();
63186
+ })
63187
+ .then((items) => prepareIndex(Array.isArray(items) ? items : []));
63188
+ }
63189
+
63190
+ return preparedIndexPromise;
63191
+ }
63192
+
63193
+ function prepareIndex(items) {
63194
+ const documents = items.map((item) => prepareDocument(item));
63195
+ const documentFrequencies = new Map();
63196
+ const averageLengths = Object.fromEntries(FIELD_NAMES.map((fieldName) => [fieldName, 1]));
63197
+ const newestPostTime = documents.reduce((newest, document) => {
63198
+ if (document.raw.type !== 'post') {
63199
+ return newest;
63200
+ }
63201
+ return Math.max(newest, document.publishedTime || 0);
63202
+ }, 0);
63203
+
63204
+ for (const document of documents) {
63205
+ const seenTerms = new Set();
63206
+ for (const fieldName of FIELD_NAMES) {
63207
+ for (const term of document.fieldTokens[fieldName]) {
63208
+ seenTerms.add(term);
63209
+ }
63210
+ }
63211
+ for (const term of seenTerms) {
63212
+ documentFrequencies.set(term, (documentFrequencies.get(term) || 0) + 1);
63213
+ }
63214
+ }
63215
+
63216
+ for (const fieldName of FIELD_NAMES) {
63217
+ const total = documents.reduce((sum, document) => sum + document.fieldLengths[fieldName], 0);
63218
+ averageLengths[fieldName] = documents.length > 0 ? Math.max(1, total / documents.length) : 1;
63219
+ }
63220
+
63221
+ return {
63222
+ documents,
63223
+ documentFrequencies,
63224
+ averageLengths,
63225
+ documentCount: documents.length,
63226
+ newestPostTime,
63227
+ };
63228
+ }
63229
+
63230
+ function prepareDocument(item) {
63231
+ const raw = normalizeItem(item);
63232
+ const fields = {
63233
+ title: raw.title,
63234
+ headings: raw.headings.join(' '),
63235
+ tags: raw.tags.join(' '),
63236
+ categories: raw.categories.join(' '),
63237
+ excerpt: raw.excerpt,
63238
+ content_text: raw.content_text,
63239
+ };
63240
+ const fieldTexts = {};
63241
+ const fieldTokens = {};
63242
+ const fieldTermCounts = {};
63243
+ const fieldLengths = {};
63244
+
63245
+ for (const [fieldName, value] of Object.entries(fields)) {
63246
+ const normalizedText = normalizeText(value);
63247
+ const tokens = tokenize(normalizedText);
63248
+ fieldTexts[fieldName] = normalizedText;
63249
+ fieldTokens[fieldName] = tokens;
63250
+ fieldTermCounts[fieldName] = countTerms(tokens);
63251
+ fieldLengths[fieldName] = Math.max(1, tokens.length);
63252
+ }
63253
+
63254
+ return {
63255
+ raw,
63256
+ fieldTexts,
63257
+ fieldTokens,
63258
+ fieldTermCounts,
63259
+ fieldLengths,
63260
+ publishedTime: Date.parse(raw.published_at_iso) || 0,
63261
+ };
63262
+ }
63263
+
63264
+ function normalizeItem(item) {
63265
+ return {
63266
+ id: String(item && item.id || ''),
63267
+ type: item && item.type === 'page' ? 'page' : 'post',
63268
+ title: String(item && item.title || ''),
63269
+ url: String(item && item.url || ''),
63270
+ excerpt: String(item && item.excerpt || ''),
63271
+ headings: Array.isArray(item && item.headings) ? item.headings.map(String) : [],
63272
+ categories: Array.isArray(item && item.categories) ? item.categories.map(String) : [],
63273
+ tags: Array.isArray(item && item.tags) ? item.tags.map(String) : [],
63274
+ published_at_iso: String(item && item.published_at_iso || ''),
63275
+ updated_at_iso: String(item && item.updated_at_iso || ''),
63276
+ content_text: String(item && item.content_text || ''),
63277
+ };
63278
+ }
63279
+
63280
+ function scoreDocument(document, terms, phrase, prepared) {
63281
+ let score = 0;
63282
+ const uniqueTerms = Array.from(new Set(terms));
63283
+
63284
+ for (const term of uniqueTerms) {
63285
+ const documentFrequency = prepared.documentFrequencies.get(term) || 0;
63286
+ const idf = Math.log(1 + (prepared.documentCount - documentFrequency + 0.5) / (documentFrequency + 0.5));
63287
+
63288
+ for (const fieldName of FIELD_NAMES) {
63289
+ const termFrequency = document.fieldTermCounts[fieldName].get(term) || 0;
63290
+ if (termFrequency === 0) {
63291
+ continue;
63292
+ }
63293
+
63294
+ const fieldLength = document.fieldLengths[fieldName];
63295
+ const averageLength = prepared.averageLengths[fieldName];
63296
+ const denominator = termFrequency + BM25_K1 * (1 - BM25_B + BM25_B * (fieldLength / averageLength));
63297
+ score += FIELD_WEIGHTS[fieldName] * idf * ((termFrequency * (BM25_K1 + 1)) / denominator);
63298
+ }
63299
+ }
63300
+
63301
+ if (phrase && phrase.length > 1) {
63302
+ for (const fieldName of FIELD_NAMES) {
63303
+ if (document.fieldTexts[fieldName].includes(phrase)) {
63304
+ score += FIELD_WEIGHTS[fieldName] * 0.6;
63305
+ }
63306
+ }
63307
+ }
63308
+
63309
+ if (score <= 0) {
63310
+ return 0;
63311
+ }
63312
+
63313
+ return score * (1 + recencyBoost(document, prepared.newestPostTime));
63314
+ }
63315
+
63316
+ function recencyBoost(document, newestPostTime) {
63317
+ if (document.raw.type !== 'post' || !document.publishedTime || !newestPostTime) {
63318
+ return 0;
63319
+ }
63320
+ const ageMs = Math.max(0, newestPostTime - document.publishedTime);
63321
+ const halfLifeMs = 180 * 24 * 60 * 60 * 1000;
63322
+ return RECENCY_BOOST_MAX * Math.exp(-ageMs / halfLifeMs);
63323
+ }
63324
+
63325
+ function buildResultData(item, query) {
63326
+ const excerpt = buildExcerpt(item, query);
63327
+ return {
63328
+ url: item.url,
63329
+ excerpt,
63330
+ plain_excerpt: excerpt,
63331
+ meta: {
63332
+ title: item.title,
63333
+ type: item.type,
63334
+ published_at_iso: item.published_at_iso,
63335
+ updated_at_iso: item.updated_at_iso,
63336
+ categories: item.categories,
63337
+ tags: item.tags,
63338
+ },
63339
+ sub_results: [],
63340
+ };
63341
+ }
63342
+
63343
+ function buildExcerpt(item, query) {
63344
+ const explicitExcerpt = String(item.excerpt || '').trim();
63345
+ if (explicitExcerpt) {
63346
+ return explicitExcerpt;
63347
+ }
63348
+
63349
+ const text = String(item.content_text || '').replace(/\\s+/g, ' ').trim();
63350
+ if (!text) {
63351
+ return '';
63352
+ }
63353
+
63354
+ const normalizedText = normalizeText(text);
63355
+ const terms = tokenize(query);
63356
+ const firstMatch = terms.map((term) => normalizedText.indexOf(term)).filter((index) => index >= 0).sort((a, b) => a - b)[0];
63357
+ const start = Math.max(0, (firstMatch || 0) - 80);
63358
+ const end = Math.min(text.length, start + 180);
63359
+ const prefix = start > 0 ? '...' : '';
63360
+ const suffix = end < text.length ? '...' : '';
63361
+ return prefix + text.slice(start, end).trim() + suffix;
63362
+ }
63363
+
63364
+ function countTerms(tokens) {
63365
+ const counts = new Map();
63366
+ for (const token of tokens) {
63367
+ counts.set(token, (counts.get(token) || 0) + 1);
63368
+ }
63369
+ return counts;
63370
+ }
63371
+
63372
+ function tokenize(value) {
63373
+ const text = normalizeText(value);
63374
+ if (!text) {
63375
+ return [];
63376
+ }
63377
+
63378
+ const tokens = [];
63379
+ if (typeof Intl !== 'undefined' && typeof Intl.Segmenter === 'function') {
63380
+ try {
63381
+ const segmenter = new Intl.Segmenter(undefined, { granularity: 'word' });
63382
+ for (const part of segmenter.segment(text)) {
63383
+ if (part.isWordLike && isUsefulToken(part.segment)) {
63384
+ tokens.push(part.segment);
63385
+ }
63386
+ }
63387
+ } catch {
63388
+ // Fall through to regex tokenization.
63389
+ }
63390
+ }
63391
+
63392
+ for (const match of text.matchAll(/[\\p{Letter}\\p{Number}]+/gu)) {
63393
+ if (isUsefulToken(match[0])) {
63394
+ tokens.push(match[0]);
63395
+ }
63396
+ }
63397
+
63398
+ for (const match of text.matchAll(/[\\p{Script=Han}\\p{Script=Hiragana}\\p{Script=Katakana}\\p{Script=Hangul}]+/gu)) {
63399
+ tokens.push(...buildNgrams(match[0], 2));
63400
+ }
63401
+
63402
+ return tokens;
63403
+ }
63404
+
63405
+ function buildNgrams(value, size) {
63406
+ const normalized = Array.from(value);
63407
+ if (normalized.length <= size) {
63408
+ return isUsefulToken(value) ? [value] : [];
63409
+ }
63410
+
63411
+ const tokens = [];
63412
+ for (let index = 0; index <= normalized.length - size; index += 1) {
63413
+ tokens.push(normalized.slice(index, index + size).join(''));
63414
+ }
63415
+ tokens.push(value);
63416
+ return tokens;
63417
+ }
63418
+
63419
+ function isUsefulToken(value) {
63420
+ const token = String(value || '').trim();
63421
+ return token.length > 1 || /^\\d$/.test(token);
63422
+ }
63423
+
63424
+ function normalizeText(value) {
63425
+ return String(value || '')
63426
+ .normalize('NFKC')
63427
+ .toLowerCase()
63428
+ .replace(/[\\u2018\\u2019]/g, "'")
63429
+ .replace(/[_-]+/g, ' ')
63430
+ .replace(/\\s+/g, ' ')
63431
+ .trim();
63432
+ }
63433
+
63434
+ function normalizeLimit(value) {
63435
+ return Number.isInteger(value) && value > 0 ? Math.min(value, 100) : DEFAULT_LIMIT;
63436
+ }
63437
+ `;
63438
+ }
63439
+ function buildSitemapXml(site, emitted, generatedAt, stylesheetHref = "") {
61690
63440
  const entries = [
61691
- ...emitted.frontPage ? [{
63441
+ ...emitted.frontPage && emitted.frontPage.includeInSitemap !== false ? [{
61692
63442
  url: emitted.frontPage.url,
61693
63443
  changefreq: "daily",
61694
63444
  priority: 1
@@ -61720,7 +63470,10 @@ function buildSitemapXml(site, emitted, generatedAt) {
61720
63470
  <priority>${entry.priority.toFixed(1)}</priority>
61721
63471
  </url>`;
61722
63472
  }).join("\n");
61723
- return `<?xml version="1.0" encoding="UTF-8"?>
63473
+ const normalizedStylesheetHref = normalizeOptionalString(stylesheetHref);
63474
+ const stylesheet = normalizedStylesheetHref ? `
63475
+ <?xml-stylesheet type="text/xsl" href="${escapeXml(normalizedStylesheetHref)}"?>` : "";
63476
+ return `<?xml version="1.0" encoding="UTF-8"?>${stylesheet}
61724
63477
  <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
61725
63478
  ${body}
61726
63479
  </urlset>`;
@@ -61752,13 +63505,22 @@ ${items}
61752
63505
  </rss>`;
61753
63506
  }
61754
63507
  function buildRobotsTxt(site) {
61755
- const lines = ["User-agent: *", "Allow: /"];
63508
+ const lines = ["User-agent: *"];
63509
+ if (site.indexing === false) {
63510
+ lines.push("Disallow: /");
63511
+ return `${lines.join("\n")}
63512
+ `;
63513
+ }
63514
+ lines.push("Allow: /");
61756
63515
  if (site.url) {
61757
63516
  lines.push("", `Sitemap: ${resolveSiteUrl(site.url, "/sitemap.xml")}`);
61758
63517
  }
61759
63518
  return `${lines.join("\n")}
61760
63519
  `;
61761
63520
  }
63521
+ function shouldGenerateRobotsTxt(options2) {
63522
+ return options2.generateSpecialFiles && options2.generateRobotsTxt !== false;
63523
+ }
61762
63524
  function getContentType(assetPath) {
61763
63525
  const ext = assetPath.split(".").pop()?.toLowerCase();
61764
63526
  const contentTypes = {
@@ -61834,7 +63596,7 @@ async function loadThemePackageFromDir(themeDir) {
61834
63596
  await readThemeDir(fs4, path4, themeDir, themeDir, fileMap);
61835
63597
  const validation = await validateThemeFiles(fileMap);
61836
63598
  if (!validation.ok) {
61837
- throw new Error(`Theme validation failed: ${validation.errors[0]?.message || "Unknown error"}`);
63599
+ throw new Error(formatThemeValidationFailure2(validation));
61838
63600
  }
61839
63601
  const rawThemeJson = String(fileMap.get("theme.json"));
61840
63602
  const themeJson = JSON.parse(rawThemeJson);
@@ -61867,14 +63629,62 @@ async function loadThemePackageFromDir(themeDir) {
61867
63629
  return {
61868
63630
  metadata: {
61869
63631
  ...manifest,
61870
- thumbnail: themeJson.thumbnail,
61871
- settings: themeJson.settings || {}
63632
+ thumbnail: themeJson.thumbnail
61872
63633
  },
61873
63634
  templates,
61874
63635
  partials,
61875
63636
  assets
61876
63637
  };
61877
63638
  }
63639
+ function formatThemeValidationFailure2(validation) {
63640
+ const blocks = [
63641
+ [
63642
+ "Theme validation failed",
63643
+ `Errors: ${validation.errors.length}`,
63644
+ `Checked files: ${validation.checkedFiles}`
63645
+ ].join("\n"),
63646
+ ...validation.errors.map((issue3) => formatThemeValidationIssue2(issue3))
63647
+ ];
63648
+ return blocks.join("\n\n");
63649
+ }
63650
+ function formatThemeValidationIssue2(issue3) {
63651
+ if (!issue3) {
63652
+ return "Reason: Unknown error";
63653
+ }
63654
+ const lines = [`ERROR ${issue3.code || "THEME_VALIDATION_ERROR"}`];
63655
+ const location = splitIssuePath2(issue3.path);
63656
+ if (location.file) {
63657
+ lines.push(`File: ${location.file}`);
63658
+ }
63659
+ if (location.path) {
63660
+ lines.push(`Path: ${location.path}`);
63661
+ }
63662
+ if (Number.isInteger(issue3.line) && Number.isInteger(issue3.column)) {
63663
+ lines.push(`Line: ${issue3.line}, Column: ${issue3.column}`);
63664
+ }
63665
+ if (issue3.category) {
63666
+ lines.push(`Category: ${issue3.category}`);
63667
+ }
63668
+ lines.push(`Reason: ${issue3.message || "Unknown error"}`);
63669
+ if (issue3.snippet) {
63670
+ const lineLabel = Number.isInteger(issue3.line) ? String(issue3.line) : "";
63671
+ lines.push("", `${lineLabel} | ${issue3.snippet.line}`, `${" ".repeat(lineLabel.length)} | ${issue3.snippet.pointer}`);
63672
+ }
63673
+ if (issue3.hint) {
63674
+ lines.push("", "Hint:", issue3.hint);
63675
+ }
63676
+ return lines.join("\n");
63677
+ }
63678
+ function splitIssuePath2(issuePath) {
63679
+ const normalizedPath = String(issuePath || "");
63680
+ if (normalizedPath.startsWith("theme.json.")) {
63681
+ return {
63682
+ file: "theme.json",
63683
+ path: normalizedPath.slice("theme.json.".length)
63684
+ };
63685
+ }
63686
+ return { file: normalizedPath, path: "" };
63687
+ }
61878
63688
  async function readThemeDir(fs4, path4, rootDir, currentDir, fileMap) {
61879
63689
  const entries = await fs4.readdir(currentDir, { withFileTypes: true });
61880
63690
  for (const entry of entries) {
@@ -61915,6 +63725,13 @@ var require2 = createRequire(import.meta.url);
61915
63725
  var { version: PACKAGE_VERSION } = require2("../package.json");
61916
63726
  var DEFAULT_PUBLIC_DIR_NAME = "public";
61917
63727
  var PUBLIC_DIR_ENV_NAME = "ZEROPRESS_PUBLIC_DIR";
63728
+ var PUBLIC_FAVICON_FILES = Object.freeze({
63729
+ icon: "favicon.ico",
63730
+ svg: "favicon.svg",
63731
+ png: "favicon.png",
63732
+ apple_touch_icon: "apple-touch-icon.png"
63733
+ });
63734
+ var PUBLIC_SITEMAP_STYLESHEET_FILE = "sitemap.xsl";
61918
63735
  async function assertThemeDirectory(themeDir) {
61919
63736
  let stat;
61920
63737
  try {
@@ -61929,17 +63746,26 @@ async function assertThemeDirectory(themeDir) {
61929
63746
  throw new Error(`Theme path is not a directory: ${themeDir}`);
61930
63747
  }
61931
63748
  }
61932
- async function runBuild(themeDir, previewData, outDir) {
61933
- assertPublicPathDoesNotOverlap("Theme directory", themeDir);
61934
- assertPublicPathDoesNotOverlap("Output directory", outDir);
63749
+ async function runBuild(themeDir, previewData, outDir, options2 = {}) {
63750
+ const publicDir = resolvePublicDir(process.cwd(), options2.publicDir);
63751
+ assertPublicPathDoesNotOverlap("Theme directory", themeDir, process.cwd(), publicDir);
63752
+ assertPublicPathDoesNotOverlap("Output directory", outDir, process.cwd(), publicDir);
61935
63753
  await assertThemeDirectory(themeDir);
61936
63754
  await assertEmptyOutputDirectory(outDir);
61937
- await copyPublicDirectory(resolvePublicDir(), outDir);
63755
+ const hasPublicRobotsTxt = await publicRobotsTxtExists(publicDir);
63756
+ const publicFavicon = await discoverPublicFavicon(publicDir);
63757
+ const sitemapStylesheetHref = await discoverPublicSitemapStylesheet(publicDir);
63758
+ await copyPublicDirectory(publicDir, outDir);
61938
63759
  const writer = new GeneratedOutputWriter({ outDir });
61939
63760
  return buildSiteFromThemeDir({
61940
63761
  previewData,
61941
63762
  themeDir,
61942
- writer
63763
+ writer,
63764
+ options: {
63765
+ favicon: publicFavicon,
63766
+ sitemapStylesheetHref,
63767
+ generateRobotsTxt: !hasPublicRobotsTxt
63768
+ }
61943
63769
  });
61944
63770
  }
61945
63771
  var GeneratedOutputWriter = class {
@@ -61977,7 +63803,10 @@ async function assertEmptyOutputDirectory(outDir) {
61977
63803
  throw error;
61978
63804
  }
61979
63805
  }
61980
- function resolvePublicDir(cwd = process.cwd()) {
63806
+ function resolvePublicDir(cwd = process.cwd(), publicDir) {
63807
+ if (publicDir) {
63808
+ return path.resolve(cwd, publicDir);
63809
+ }
61981
63810
  const envValue = process.env[PUBLIC_DIR_ENV_NAME]?.trim();
61982
63811
  return path.resolve(cwd, envValue || DEFAULT_PUBLIC_DIR_NAME);
61983
63812
  }
@@ -61996,6 +63825,48 @@ async function copyPublicDirectory(publicDir, outDir) {
61996
63825
  }
61997
63826
  await copyPublicEntries(publicDir, outDir);
61998
63827
  }
63828
+ async function publicRobotsTxtExists(publicDir) {
63829
+ let stat;
63830
+ try {
63831
+ stat = await fs.lstat(path.join(publicDir, "robots.txt"));
63832
+ } catch (error) {
63833
+ if (error && (error.code === "ENOENT" || error.code === "ENOTDIR")) {
63834
+ return false;
63835
+ }
63836
+ throw error;
63837
+ }
63838
+ return stat.isFile();
63839
+ }
63840
+ async function discoverPublicFavicon(publicDir) {
63841
+ const favicon = {};
63842
+ for (const [key, filename] of Object.entries(PUBLIC_FAVICON_FILES)) {
63843
+ let stat;
63844
+ try {
63845
+ stat = await fs.lstat(path.join(publicDir, filename));
63846
+ } catch (error) {
63847
+ if (error && (error.code === "ENOENT" || error.code === "ENOTDIR")) {
63848
+ continue;
63849
+ }
63850
+ throw error;
63851
+ }
63852
+ if (stat.isFile()) {
63853
+ favicon[key] = `/${filename}`;
63854
+ }
63855
+ }
63856
+ return Object.keys(favicon).length ? favicon : void 0;
63857
+ }
63858
+ async function discoverPublicSitemapStylesheet(publicDir) {
63859
+ let stat;
63860
+ try {
63861
+ stat = await fs.lstat(path.join(publicDir, PUBLIC_SITEMAP_STYLESHEET_FILE));
63862
+ } catch (error) {
63863
+ if (error && (error.code === "ENOENT" || error.code === "ENOTDIR")) {
63864
+ return void 0;
63865
+ }
63866
+ throw error;
63867
+ }
63868
+ return stat.isFile() ? `/${PUBLIC_SITEMAP_STYLESHEET_FILE}` : void 0;
63869
+ }
61999
63870
  async function copyPublicEntries(sourceDir, targetDir) {
62000
63871
  const entries = await fs.readdir(sourceDir, { withFileTypes: true });
62001
63872
  for (const entry of entries) {
@@ -62020,8 +63891,7 @@ function shouldIgnorePublicEntry(name) {
62020
63891
  const lowerName = basename.toLowerCase();
62021
63892
  return basename.startsWith(".") || lowerName === "node_modules" || lowerName === "thumbs.db" || lowerName.endsWith(".key") || lowerName.endsWith(".pem");
62022
63893
  }
62023
- function assertPublicPathDoesNotOverlap(label, candidatePath, cwd = process.cwd()) {
62024
- const publicDir = resolvePublicDir(cwd);
63894
+ function assertPublicPathDoesNotOverlap(label, candidatePath, cwd = process.cwd(), publicDir = resolvePublicDir(cwd)) {
62025
63895
  const resolvedCandidate = path.resolve(cwd, candidatePath);
62026
63896
  if (!pathsOverlap(publicDir, resolvedCandidate)) {
62027
63897
  return;
@@ -62332,7 +64202,7 @@ try {
62332
64202
  process.exitCode = 1;
62333
64203
  }
62334
64204
  function input(name) {
62335
- return process.env[`INPUT_${name.toUpperCase().replace(/-/g, "_")}`]?.trim() || "";
64205
+ return process.env[`INPUT_${name.toUpperCase()}`]?.trim() || "";
62336
64206
  }
62337
64207
  function booleanInput(name, fallback) {
62338
64208
  const value = input(name);