nodality 1.0.167 → 1.0.168

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.
Files changed (98) hide show
  1. package/bin/nodality.js +63 -1
  2. package/layout/animator.js +1 -1
  3. package/layout/audio.js +1 -1
  4. package/layout/audionew.js +1 -1
  5. package/layout/base-2.js +1 -1
  6. package/layout/base.js +1 -1
  7. package/layout/beta-desktop-bar.js +1 -1
  8. package/layout/beta-mobile-bar.js +1 -1
  9. package/layout/box.js +1 -1
  10. package/layout/button.js +1 -1
  11. package/layout/cards.js +1 -1
  12. package/layout/center.js +1 -1
  13. package/layout/checkbox.js +1 -1
  14. package/layout/circle.js +1 -1
  15. package/layout/clean-row.js +1 -1
  16. package/layout/code.js +1 -1
  17. package/layout/container.js +1 -1
  18. package/layout/custom.js +1 -1
  19. package/layout/div-image.js +1 -1
  20. package/layout/dropdown-2025.js +1 -1
  21. package/layout/dropdown.js +1 -1
  22. package/layout/empty-element.js +1 -1
  23. package/layout/external-stylesheet.js +1 -1
  24. package/layout/flex-card.js +1 -1
  25. package/layout/flex-grid.js +1 -1
  26. package/layout/flex-row.js +1 -1
  27. package/layout/footer.js +1 -1
  28. package/layout/form-components/custom.js +1 -1
  29. package/layout/form-components/data-list.js +1 -1
  30. package/layout/form-components/floating-input.js +1 -1
  31. package/layout/form-components/form-all.js +1 -1
  32. package/layout/form-components/form.js +1 -1
  33. package/layout/form-components/image-picker.js +1 -1
  34. package/layout/form-components/picker.js +1 -1
  35. package/layout/form-components/radio.js +1 -1
  36. package/layout/form-components/radiogroup.js +1 -1
  37. package/layout/form-components/range.js +1 -1
  38. package/layout/free.js +1 -1
  39. package/layout/grid-new.js +1 -1
  40. package/layout/grid-switcher.js +1 -1
  41. package/layout/grid.js +1 -1
  42. package/layout/group.js +1 -1
  43. package/layout/header.js +1 -1
  44. package/layout/horizontal-scroller.js +1 -1
  45. package/layout/image-old.js +1 -1
  46. package/layout/image.js +1 -1
  47. package/layout/index.js +1 -1
  48. package/layout/label.js +1 -1
  49. package/layout/link.js +1 -1
  50. package/layout/list-OLD.js +1 -1
  51. package/layout/list.js +1 -1
  52. package/layout/meta-adder.js +1 -1
  53. package/layout/modal-2025.js +1 -1
  54. package/layout/modernwrap.js +1 -1
  55. package/layout/multiswitcher.js +1 -1
  56. package/layout/multiswitcherBeta.js +1 -1
  57. package/layout/nav-bar.js +1 -1
  58. package/layout/nav-factor/custom-div.js +1 -1
  59. package/layout/navBar-OLD.js +1 -1
  60. package/layout/new-flat-adder.js +1 -1
  61. package/layout/new-nav-bar.js +1 -1
  62. package/layout/offset-container.js +1 -1
  63. package/layout/polygon.js +1 -1
  64. package/layout/prerender-site.js +16 -2
  65. package/layout/prerender.js +15 -5
  66. package/layout/progress.js +1 -1
  67. package/layout/row.js +1 -1
  68. package/layout/saved-new-nav-bar.js +1 -1
  69. package/layout/scroll-video.js +1 -1
  70. package/layout/side-bar.js +1 -1
  71. package/layout/side-nav-bar.js +1 -1
  72. package/layout/simple-bar.js +1 -1
  73. package/layout/slider-2025.js +1 -1
  74. package/layout/spacer.js +1 -1
  75. package/layout/stack.js +1 -1
  76. package/layout/styler.js +1 -1
  77. package/layout/svg.js +1 -1
  78. package/layout/switcher.js +1 -1
  79. package/layout/table.js +1 -1
  80. package/layout/text-field.js +1 -1
  81. package/layout/text.js +1 -1
  82. package/layout/ulist.js +1 -1
  83. package/layout/video.js +1 -1
  84. package/layout/without-new.js +1 -1
  85. package/layout/wrap.js +1 -1
  86. package/layout/zoom-card.js +1 -1
  87. package/lib/card-getter.js +1 -1
  88. package/lib/data.js +48 -0
  89. package/lib/designer.js +1 -1
  90. package/lib/element-mapper.js +1 -1
  91. package/lib/keyframe-animation.js +1 -1
  92. package/lib/link-getter.js +1 -1
  93. package/lib/scroll-video.js +1 -1
  94. package/lib/seo.js +198 -0
  95. package/lib/stacker.js +1 -1
  96. package/lib/theme.js +1 -1
  97. package/lib/transform-anim.js +1 -1
  98. package/package.json +4 -2
package/bin/nodality.js CHANGED
@@ -206,7 +206,22 @@ function runFanout(cwd, uploadDir, fanoutConfig) {
206
206
 
207
207
  let totalWrote = 0;
208
208
  for (const spec of fanoutConfig) {
209
- const { template, data, items, id = "id", title, entry, bodyAttr = "data-product-id" } = spec;
209
+ const {
210
+ template, data, items, id = "id", title, entry,
211
+ bodyAttr = "data-product-id",
212
+ // ─── Auto-injected JSON-LD per item (1.0.168+) ─────────
213
+ // When `jsonLdType` is set, fanout emits a
214
+ // `<script type="application/ld+json" data-seo="1">` block per
215
+ // generated HTML with the right schema.org type and fields
216
+ // mapped from each item. Saves projects from importing
217
+ // nodality/seo and calling productJsonLd() by hand inside the
218
+ // page entry — useful because the page entry runs LATE (after
219
+ // the static HTML is already served) so crawlers that don't
220
+ // execute JS miss any client-injected structured data.
221
+ jsonLdType, // e.g. "Product" | "Article"
222
+ jsonLdFields, // map of schema-key → dot-path on item (e.g. { name: "name", image: "images.0", sku: "id" })
223
+ jsonLdExtra, // static fields to add verbatim (e.g. { brand: { "@type": "Brand", name: "SLS3" }, priceCurrency: "CZK" })
224
+ } = spec;
210
225
  if (!template || !data) {
211
226
  console.warn(`[nodality] fanout: skipping spec without template/data`);
212
227
  continue;
@@ -292,6 +307,18 @@ function runFanout(cwd, uploadDir, fanoutConfig) {
292
307
  );
293
308
  html = html.replace(srcRx, `$1$2${entryBase}-${itemId}.js$3`);
294
309
 
310
+ // Auto-injected JSON-LD per item, if the spec asked for it.
311
+ // Inserted inside <head> (before </head>) so crawlers see it
312
+ // without executing the page entry — critical for Bing /
313
+ // social-card scrapers / non-Google crawlers that don't run JS.
314
+ if (jsonLdType && jsonLdFields) {
315
+ const ld = buildItemJsonLd(jsonLdType, jsonLdFields, jsonLdExtra, item);
316
+ const tag = `<script type="application/ld+json" data-seo="1">${
317
+ JSON.stringify(ld)
318
+ }</script>`;
319
+ html = html.replace(/<\/head>/i, `${tag}\n</head>`);
320
+ }
321
+
295
322
  fs.writeFileSync(path.join(uploadDir, `${base}-${itemId}.html`), html);
296
323
 
297
324
  // JS wrapper. The user's entry must export an async function
@@ -335,6 +362,41 @@ await fn(${JSON.stringify(String(itemId))});
335
362
  return totalWrote;
336
363
  }
337
364
 
365
+ /**
366
+ * Look up a dot/bracket path on a single item, e.g. "images.0" or
367
+ * "sizing.sizes.0.range". Returns undefined for missing branches.
368
+ * Used by fanout's per-item JSON-LD field mapper.
369
+ */
370
+ function getByPath(obj, path) {
371
+ if (obj == null || !path) return undefined;
372
+ const parts = String(path).split(".");
373
+ let acc = obj;
374
+ for (const p of parts) {
375
+ if (acc == null) return undefined;
376
+ acc = acc[p];
377
+ }
378
+ return acc;
379
+ }
380
+
381
+ /**
382
+ * Build a schema.org JSON-LD object for one fanout item. `fields`
383
+ * maps schema-key → dot-path on the item; `extra` is merged in
384
+ * verbatim (static fields like brand, priceCurrency, "@context").
385
+ */
386
+ function buildItemJsonLd(type, fields, extra, item) {
387
+ const out = { "@context": "https://schema.org", "@type": type };
388
+ for (const [key, fieldPath] of Object.entries(fields || {})) {
389
+ const v = getByPath(item, fieldPath);
390
+ if (v !== undefined && v !== null && v !== "") out[key] = v;
391
+ }
392
+ if (extra && typeof extra === "object") {
393
+ for (const [k, v] of Object.entries(extra)) {
394
+ if (v !== undefined) out[k] = v;
395
+ }
396
+ }
397
+ return out;
398
+ }
399
+
338
400
  /** Resolve a dot-path with `[]` segments to an array of items. */
339
401
  function resolveItemsPath(data, path) {
340
402
  if (!path) return Array.isArray(data) ? data : [];
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/audio.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/base-2.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/base.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/box.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/button.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/cards.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/center.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/circle.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/code.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/custom.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/footer.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/free.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/grid.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/group.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/header.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/image.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/label.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/link.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/list.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/nav-bar.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/polygon.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.167
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -146,6 +146,16 @@ export async function prerenderSite(config) {
146
146
  await runChild(config, locale);
147
147
  const elapsed = ((Date.now() - startedAt) / 1000).toFixed(1);
148
148
  console.log(`✅ Prerender done in ${elapsed}s — ${config.pages.length} page(s)`);
149
+ // Single-locale sitemap (1.0.168+). Earlier versions only wrote
150
+ // a sitemap on multi-locale builds, so single-locale projects had
151
+ // to bolt on their own `scripts/generate-sitemap.mjs`. We now
152
+ // emit on every build unless the caller explicitly opts out
153
+ // with `sitemap: false`. URLs are origin + page.html, lastmod is
154
+ // build time, no hreflang (single locale has nothing to alternate).
155
+ if (config.sitemap !== false) {
156
+ await writeSitemap(config);
157
+ console.log(`✅ Sitemap regenerated — ${config.pages.length} URL(s)`);
158
+ }
149
159
  return;
150
160
  }
151
161
 
@@ -399,6 +409,10 @@ function urlFor(config, locale, page) {
399
409
 
400
410
  /** hreflang alternates for one page (all locales + x-default). */
401
411
  function alternatesFor(config, page) {
412
+ // Single-locale builds (incl. the implicit untagged-locale case)
413
+ // have nothing to alternate to — emitting `xhtml:link rel="alternate"`
414
+ // with the same URL as the `<loc>` would just clutter the sitemap.
415
+ if (config.locales.length <= 1) return [];
402
416
  const xDefault = config.xDefaultLocale || config.defaultLocale;
403
417
  const alts = config.locales.map((l) => ({ hreflang: l, href: urlFor(config, l, page) }));
404
418
  alts.push({ hreflang: "x-default", href: urlFor(config, xDefault, page) });
@@ -427,7 +441,7 @@ async function writeSitemap(config) {
427
441
  ` <lastmod>${now}</lastmod>\n` +
428
442
  ` <changefreq>${changefreq}</changefreq>\n` +
429
443
  ` <priority>${priority}</priority>\n` +
430
- altLines + "\n" +
444
+ (altLines ? altLines + "\n" : "") +
431
445
  ` </url>`
432
446
  );
433
447
  }