nodality 1.0.151 → 1.0.152

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 (95) hide show
  1. package/layout/animator.js +1 -1
  2. package/layout/audio.js +1 -1
  3. package/layout/audionew.js +1 -1
  4. package/layout/base-2.js +1 -1
  5. package/layout/base.js +1 -1
  6. package/layout/beta-desktop-bar.js +1 -1
  7. package/layout/beta-mobile-bar.js +1 -1
  8. package/layout/box.js +1 -1
  9. package/layout/button.js +1 -1
  10. package/layout/cards.js +1 -1
  11. package/layout/center.js +1 -1
  12. package/layout/checkbox.js +1 -1
  13. package/layout/circle.js +1 -1
  14. package/layout/clean-row.js +1 -1
  15. package/layout/code.js +1 -1
  16. package/layout/container.js +1 -1
  17. package/layout/custom.js +1 -1
  18. package/layout/div-image.js +1 -1
  19. package/layout/dropdown-2025.js +1 -1
  20. package/layout/dropdown.js +1 -1
  21. package/layout/empty-element.js +1 -1
  22. package/layout/external-stylesheet.js +1 -1
  23. package/layout/flex-card.js +1 -1
  24. package/layout/flex-grid.js +1 -1
  25. package/layout/flex-row.js +1 -1
  26. package/layout/footer.js +1 -1
  27. package/layout/form-components/custom.js +1 -1
  28. package/layout/form-components/data-list.js +1 -1
  29. package/layout/form-components/floating-input.js +1 -1
  30. package/layout/form-components/form-all.js +1 -1
  31. package/layout/form-components/form.js +1 -1
  32. package/layout/form-components/image-picker.js +1 -1
  33. package/layout/form-components/picker.js +1 -1
  34. package/layout/form-components/radio.js +1 -1
  35. package/layout/form-components/radiogroup.js +1 -1
  36. package/layout/form-components/range.js +1 -1
  37. package/layout/free.js +1 -1
  38. package/layout/grid-new.js +1 -1
  39. package/layout/grid-switcher.js +1 -1
  40. package/layout/grid.js +1 -1
  41. package/layout/group.js +1 -1
  42. package/layout/header.js +1 -1
  43. package/layout/horizontal-scroller.js +1 -1
  44. package/layout/image-old.js +1 -1
  45. package/layout/image.js +1 -1
  46. package/layout/index.js +1 -1
  47. package/layout/label.js +1 -1
  48. package/layout/link.js +1 -1
  49. package/layout/list-OLD.js +1 -1
  50. package/layout/list.js +1 -1
  51. package/layout/meta-adder.js +1 -1
  52. package/layout/modal-2025.js +1 -1
  53. package/layout/modernwrap.js +1 -1
  54. package/layout/multiswitcher.js +1 -1
  55. package/layout/multiswitcherBeta.js +1 -1
  56. package/layout/nav-bar.js +1 -1
  57. package/layout/nav-factor/custom-div.js +1 -1
  58. package/layout/navBar-OLD.js +1 -1
  59. package/layout/new-flat-adder.js +1 -1
  60. package/layout/new-nav-bar.js +1 -1
  61. package/layout/offset-container.js +1 -1
  62. package/layout/polygon.js +1 -1
  63. package/layout/prerender-site.js +381 -0
  64. package/layout/prerender.js +1 -1
  65. package/layout/progress.js +1 -1
  66. package/layout/row.js +1 -1
  67. package/layout/saved-new-nav-bar.js +1 -1
  68. package/layout/scroll-video.js +1 -1
  69. package/layout/side-bar.js +1 -1
  70. package/layout/side-nav-bar.js +1 -1
  71. package/layout/simple-bar.js +1 -1
  72. package/layout/slider-2025.js +1 -1
  73. package/layout/spacer.js +1 -1
  74. package/layout/stack.js +1 -1
  75. package/layout/styler.js +1 -1
  76. package/layout/svg.js +1 -1
  77. package/layout/switcher.js +1 -1
  78. package/layout/table.js +1 -1
  79. package/layout/text-field.js +1 -1
  80. package/layout/text.js +1 -1
  81. package/layout/ulist.js +1 -1
  82. package/layout/video.js +1 -1
  83. package/layout/without-new.js +1 -1
  84. package/layout/wrap.js +1 -1
  85. package/layout/zoom-card.js +1 -1
  86. package/lib/card-getter.js +1 -1
  87. package/lib/designer.js +1 -1
  88. package/lib/element-mapper.js +1 -1
  89. package/lib/keyframe-animation.js +1 -1
  90. package/lib/link-getter.js +1 -1
  91. package/lib/scroll-video.js +1 -1
  92. package/lib/stacker.js +1 -1
  93. package/lib/theme.js +1 -1
  94. package/lib/transform-anim.js +1 -1
  95. package/package.json +3 -2
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
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.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
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.151
2
+ * nodality v1.0.152
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.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
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.151
2
+ * nodality v1.0.152
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.151
2
+ * nodality v1.0.152
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.151
2
+ * nodality v1.0.152
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.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
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.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
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.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
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.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
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.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
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.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
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.151
2
+ * nodality v1.0.152
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.151
2
+ * nodality v1.0.152
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.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
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.151
2
+ * nodality v1.0.152
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.151
2
+ * nodality v1.0.152
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.151
2
+ * nodality v1.0.152
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.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
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.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
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.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
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.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -0,0 +1,381 @@
1
+ /*!
2
+ * nodality v1.0.152
3
+ * (c) 2026 Filip Vabrousek
4
+ * License: MIT
5
+ */
6
+
7
+ // ════════════════════════════════════════════════════════════════════
8
+ // Nodality — full-site SSG batch renderer
9
+ // ════════════════════════════════════════════════════════════════════
10
+ //
11
+ // Higher-level wrapper around `prerender()` that handles the
12
+ // concerns every multi-page / multi-locale Nodality static site has:
13
+ //
14
+ // • Multi-locale process isolation (one Node subprocess per locale
15
+ // so ESM module-cache doesn't leak the wrong `t` across runs)
16
+ // • Per-locale output directories (default at root, others under
17
+ // /<locale>/)
18
+ // • Head-path rewrite for non-root locales (./script.js → ../script.js)
19
+ // • Body asset-path absolutization so `./assets/foo.jpg` works at
20
+ // any URL depth (becomes `/assets/foo.jpg`)
21
+ // • Per-page canonical + hreflang alternates + <html lang> injection
22
+ // • Sitemap.xml regeneration with hreflang entries
23
+ // • Mobile-first viewport during render (mobile users get no flicker)
24
+ // • Hydration handoff (clear-mount script before runtime scripts)
25
+ //
26
+ // Consumers provide a small config object and call `prerenderSite()`.
27
+ // Everything site-specific (page list, locale list, origin) lives in
28
+ // the config; everything generic stays in this module.
29
+ //
30
+ // ─── Minimal consumer usage ────────────────────────────────────────
31
+ //
32
+ // // scripts/prerender.mjs in the consumer project:
33
+ //
34
+ // import { prerenderSite } from "nodality/ssg-site";
35
+ // import path from "node:path";
36
+ // import { fileURLToPath } from "node:url";
37
+ //
38
+ // await prerenderSite({
39
+ // origin: "https://example.com",
40
+ // uploadDir: path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../upload"),
41
+ // defaultLocale: "en",
42
+ // locales: ["en", "de", "fr"],
43
+ // pages: [
44
+ // { html: "index.html", entry: "app.js" },
45
+ // { html: "about.html", entry: "about.js" },
46
+ // ],
47
+ // });
48
+ //
49
+ // ─── Optional config keys ──────────────────────────────────────────
50
+ //
51
+ // viewport { width, height } default { 390, 844 } (mobile-first)
52
+ // mount CSS selector default "#mount"
53
+ // sitemap boolean default true (writes sitemap.xml)
54
+ // xDefaultLocale string default = defaultLocale
55
+ // assetPrefixes string[] default ["assets/", "dist/", "badge-",
56
+ // "apple-touch-icon", "favicon."]
57
+ // localStorageKey string default "h7lang"
58
+ //
59
+ // ─── Process model ─────────────────────────────────────────────────
60
+ //
61
+ // `prerenderSite()` distinguishes parent vs child runs by env var
62
+ // `NODALITY_SSG_LOCALE`. When called without it, it spawns a Node
63
+ // subprocess per locale (each child runs the SAME script with the
64
+ // locale set in env). When the env var is present, the call renders
65
+ // just that one locale to its output dir.
66
+ //
67
+ // This isolation is required because each page's entry script imports
68
+ // `./lang.js`, which calls `detectLang()` at module evaluation. ESM
69
+ // module cache would otherwise pin the first-imported locale across
70
+ // the whole process.
71
+
72
+ import { prerender } from "./prerender.js";
73
+ import { promises as fs } from "node:fs";
74
+ import path from "node:path";
75
+ import os from "node:os";
76
+ import { spawn } from "node:child_process";
77
+ import { fileURLToPath, pathToFileURL } from "node:url";
78
+
79
+ const ENV_LOCALE = "NODALITY_SSG_LOCALE";
80
+ const ENV_CONFIG = "NODALITY_SSG_CONFIG";
81
+
82
+ const DEFAULT_ASSET_PREFIXES = [
83
+ "assets/",
84
+ "dist/",
85
+ "badge-",
86
+ "apple-touch-icon",
87
+ "favicon\\.",
88
+ ];
89
+
90
+ /**
91
+ * Render an entire static site across N locales.
92
+ *
93
+ * @param {object} config
94
+ * @param {string} config.origin — Public origin, e.g. "https://example.com"
95
+ * @param {string} config.uploadDir — Absolute path to the directory containing
96
+ * the template HTML + entry .js files. Output
97
+ * files are written under it (default locale
98
+ * at the root, others under /<locale>/).
99
+ * @param {string} config.defaultLocale — The locale code whose pages live at the
100
+ * root of uploadDir. All other locales get a
101
+ * same-named subdir.
102
+ * @param {string[]} config.locales — All locale codes to render. Must include
103
+ * `defaultLocale`.
104
+ * @param {Array<{html:string,entry:string}>} config.pages
105
+ * — Each page's public HTML file (used as
106
+ * template) and the JS entry script that
107
+ * builds it.
108
+ * @param {{width:number,height:number}} [config.viewport={width:390,height:844}]
109
+ * @param {string} [config.mount="#mount"]
110
+ * @param {boolean} [config.sitemap=true]
111
+ * @param {string} [config.xDefaultLocale] — defaults to config.defaultLocale
112
+ * @param {string[]} [config.assetPrefixes] — Path prefixes that should be rewritten
113
+ * absolute (so they resolve at site root
114
+ * regardless of locale subdir depth).
115
+ * @param {string} [config.localStorageKey="h7lang"]
116
+ *
117
+ * @returns {Promise<void>}
118
+ */
119
+ export async function prerenderSite(config) {
120
+ validateConfig(config);
121
+
122
+ // Child mode? Render exactly one locale and exit.
123
+ const childLocale = process.env[ENV_LOCALE];
124
+ if (childLocale) {
125
+ return runChild(config, childLocale);
126
+ }
127
+
128
+ // Parent mode — fan out one subprocess per locale, sequentially
129
+ // (parallel would scramble console output and risk thrashing CPU).
130
+ const startedAt = Date.now();
131
+ console.log(`🌍 Prerendering ${config.locales.length} locales × ${config.pages.length} pages…`);
132
+ console.log();
133
+
134
+ let failures = 0;
135
+ for (const locale of config.locales) {
136
+ console.log(`── ${locale} ${"─".repeat(60 - locale.length - 3)}`);
137
+ const ok = await runLocaleSubprocess(locale);
138
+ if (!ok) failures++;
139
+ console.log();
140
+ }
141
+
142
+ const elapsed = ((Date.now() - startedAt) / 1000).toFixed(1);
143
+ if (failures > 0) {
144
+ console.error(`❌ Prerender finished with ${failures}/${config.locales.length} failures in ${elapsed}s`);
145
+ throw new Error(`prerenderSite: ${failures} locale(s) failed`);
146
+ }
147
+ console.log(`✅ Prerender done in ${elapsed}s — ${config.locales.length} locales × ${config.pages.length} pages`);
148
+
149
+ if (config.sitemap !== false) {
150
+ await writeSitemap(config);
151
+ console.log(`✅ Sitemap regenerated — ${config.pages.length * config.locales.length} URLs`);
152
+ }
153
+ }
154
+
155
+ function validateConfig(config) {
156
+ const required = ["origin", "uploadDir", "defaultLocale", "locales", "pages"];
157
+ for (const k of required) {
158
+ if (!config[k]) throw new Error(`prerenderSite: config.${k} is required`);
159
+ }
160
+ if (!Array.isArray(config.locales) || config.locales.length === 0) {
161
+ throw new Error("prerenderSite: config.locales must be a non-empty array");
162
+ }
163
+ if (!config.locales.includes(config.defaultLocale)) {
164
+ throw new Error(`prerenderSite: config.defaultLocale "${config.defaultLocale}" not in config.locales`);
165
+ }
166
+ if (!Array.isArray(config.pages) || config.pages.length === 0) {
167
+ throw new Error("prerenderSite: config.pages must be a non-empty array");
168
+ }
169
+ for (const p of config.pages) {
170
+ if (!p.html || !p.entry) {
171
+ throw new Error("prerenderSite: each page needs { html, entry }");
172
+ }
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Spawn a Node subprocess running the SAME script the parent did,
178
+ * with NODALITY_SSG_LOCALE set so the child enters child-mode.
179
+ */
180
+ function runLocaleSubprocess(locale) {
181
+ return new Promise((resolve) => {
182
+ // process.argv[1] is the entry script of the parent — typically
183
+ // the consumer's `scripts/prerender.mjs`. Re-invoke it so the
184
+ // child has the same imports + same config call.
185
+ const entry = process.argv[1];
186
+ if (!entry) {
187
+ console.error("prerenderSite: cannot determine entry script for subprocess");
188
+ resolve(false);
189
+ return;
190
+ }
191
+ const child = spawn(process.execPath, [entry], {
192
+ cwd: process.cwd(),
193
+ env: { ...process.env, [ENV_LOCALE]: locale },
194
+ stdio: "inherit",
195
+ });
196
+ child.on("exit", (code) => resolve(code === 0));
197
+ child.on("error", (err) => {
198
+ console.error(`subprocess for ${locale} failed to start: ${err.message}`);
199
+ resolve(false);
200
+ });
201
+ });
202
+ }
203
+
204
+ /**
205
+ * Render one locale. Reads each page's template, runs the entry
206
+ * script in jsdom via `prerender()`, post-processes the output to
207
+ * absolutize asset paths, and writes the file.
208
+ */
209
+ async function runChild(config, locale) {
210
+ const {
211
+ origin,
212
+ uploadDir,
213
+ defaultLocale,
214
+ pages,
215
+ mount = "#mount",
216
+ viewport = { width: 390, height: 844 },
217
+ assetPrefixes = DEFAULT_ASSET_PREFIXES,
218
+ localStorageKey = "h7lang",
219
+ } = config;
220
+
221
+ const isSubdir = locale !== defaultLocale;
222
+ const localeDir = isSubdir ? path.join(uploadDir, locale) : uploadDir;
223
+ await fs.mkdir(localeDir, { recursive: true });
224
+
225
+ const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), `nodality-ssg-${locale}-`));
226
+ const stamp = Date.now();
227
+
228
+ // Asset-path rewrite regex (only for non-default locales). The
229
+ // lookbehind `(?<!\.)` is critical — without it the `./` in
230
+ // `../assets/` would match and silently re-break head paths.
231
+ const assetRewriteRe = new RegExp(
232
+ `(?<!\\.)\\.\\/(?=(?:${assetPrefixes.join("|")}))`,
233
+ "g"
234
+ );
235
+
236
+ try {
237
+ for (const page of pages) {
238
+ const srcHtml = path.join(uploadDir, page.html);
239
+ const entryPath = path.join(uploadDir, page.entry);
240
+ const outPath = path.join(localeDir, page.html);
241
+
242
+ try {
243
+ await fs.access(entryPath);
244
+ } catch {
245
+ console.warn(` ⚠️ ${page.html} skipped — entry ${page.entry} not found`);
246
+ continue;
247
+ }
248
+
249
+ // Build a clean template from the live HTML — strip prior
250
+ // prerender output from #mount, rewrite head asset paths
251
+ // (./xxx → ../xxx) for non-default locales.
252
+ const tmplPath = path.join(tmpDir, page.html);
253
+ await fs.writeFile(tmplPath, await buildCleanTemplate(srcHtml, isSubdir, mount));
254
+
255
+ process.stdout.write(` ${page.html.padEnd(22)} `);
256
+
257
+ try {
258
+ const result = await prerender({
259
+ template: tmplPath,
260
+ output: outPath,
261
+ mount,
262
+ locale,
263
+ localStorageKey,
264
+ viewport,
265
+ url: urlFor(config, locale, page.html),
266
+ htmlLang: locale,
267
+ canonical: urlFor(config, locale, page.html),
268
+ alternates: alternatesFor(config, page.html),
269
+ build: async () => {
270
+ const importUrl = `${pathToFileURL(entryPath).href}?t=${stamp}-${page.entry}`;
271
+ await import(importUrl);
272
+ },
273
+ });
274
+
275
+ // Post-process body asset paths only for non-default locales.
276
+ if (isSubdir) {
277
+ let html = await fs.readFile(outPath, "utf8");
278
+ const before = html.length;
279
+ html = html.replace(assetRewriteRe, "/");
280
+ if (html.length !== before) {
281
+ await fs.writeFile(outPath, html, "utf8");
282
+ }
283
+ }
284
+
285
+ console.log(`→ ${path.relative(uploadDir, outPath).padEnd(40)} ${(result.bytes / 1024).toFixed(1).padStart(6)} KB`);
286
+ } catch (err) {
287
+ console.log(`✘ FAILED`);
288
+ console.error(` ${err.message}`);
289
+ }
290
+ }
291
+ } finally {
292
+ await fs.rm(tmpDir, { recursive: true, force: true });
293
+ }
294
+ }
295
+
296
+ /**
297
+ * Read the live HTML at `htmlPath`, strip everything inside the
298
+ * mount container, and (for non-default locales) rewrite head
299
+ * <script src>/<link href>/importmap paths from `./` to `../` so
300
+ * they resolve one directory up from the locale's subdir.
301
+ */
302
+ async function buildCleanTemplate(htmlPath, isSubdir, mount) {
303
+ // Lazy import jsdom so this module can be loaded in environments
304
+ // that haven't installed it (same pattern as prerender.js).
305
+ const { JSDOM } = await import("jsdom");
306
+ const live = await fs.readFile(htmlPath, "utf8");
307
+ const dom = new JSDOM(live);
308
+ const doc = dom.window.document;
309
+
310
+ // Empty the mount so prerender starts fresh (idempotent re-runs).
311
+ const mountEl = doc.querySelector(mount);
312
+ if (mountEl) mountEl.innerHTML = "";
313
+
314
+ if (isSubdir) {
315
+ for (const el of doc.querySelectorAll('script[src^="./"]')) {
316
+ el.setAttribute("src", "../" + el.getAttribute("src").slice(2));
317
+ }
318
+ for (const el of doc.querySelectorAll('link[href^="./"]')) {
319
+ el.setAttribute("href", "../" + el.getAttribute("href").slice(2));
320
+ }
321
+ for (const im of doc.querySelectorAll('script[type="importmap"]')) {
322
+ const text = im.textContent || "";
323
+ im.textContent = text.replace(/"\.\//g, '"../');
324
+ }
325
+ }
326
+
327
+ const cleaned = dom.serialize();
328
+ dom.window.close();
329
+ return cleaned;
330
+ }
331
+
332
+ /** URL for a (locale, page) pair under this site's origin. */
333
+ function urlFor(config, locale, page) {
334
+ const slash = locale === config.defaultLocale ? "" : `${locale}/`;
335
+ const tail = page === "index.html" ? slash : `${slash}${page}`;
336
+ return `${config.origin}/${tail}`;
337
+ }
338
+
339
+ /** hreflang alternates for one page (all locales + x-default). */
340
+ function alternatesFor(config, page) {
341
+ const xDefault = config.xDefaultLocale || config.defaultLocale;
342
+ const alts = config.locales.map((l) => ({ hreflang: l, href: urlFor(config, l, page) }));
343
+ alts.push({ hreflang: "x-default", href: urlFor(config, xDefault, page) });
344
+ return alts;
345
+ }
346
+
347
+ /** Write the sitemap.xml covering every (locale, page) URL. */
348
+ async function writeSitemap(config) {
349
+ const now = new Date().toISOString().slice(0, 10);
350
+ const entries = [];
351
+ for (const page of config.pages) {
352
+ for (const locale of config.locales) {
353
+ const loc = urlFor(config, locale, page.html);
354
+ const alts = alternatesFor(config, page.html);
355
+ const altLines = alts
356
+ .map((a) => ` <xhtml:link rel="alternate" hreflang="${a.hreflang}" href="${a.href}" />`)
357
+ .join("\n");
358
+ const priority = page.html === "index.html" ? "1.0"
359
+ : (page.html === "privacy.html" || page.html === "delete-account.html") ? "0.4"
360
+ : "0.8";
361
+ const changefreq = (page.html === "privacy.html" || page.html === "delete-account.html")
362
+ ? "yearly" : "monthly";
363
+ entries.push(
364
+ ` <url>\n` +
365
+ ` <loc>${loc}</loc>\n` +
366
+ ` <lastmod>${now}</lastmod>\n` +
367
+ ` <changefreq>${changefreq}</changefreq>\n` +
368
+ ` <priority>${priority}</priority>\n` +
369
+ altLines + "\n" +
370
+ ` </url>`
371
+ );
372
+ }
373
+ }
374
+ const xml =
375
+ `<?xml version="1.0" encoding="UTF-8"?>\n` +
376
+ `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"\n` +
377
+ ` xmlns:xhtml="http://www.w3.org/1999/xhtml">\n` +
378
+ entries.join("\n") + "\n" +
379
+ `</urlset>\n`;
380
+ await fs.writeFile(path.join(config.uploadDir, "sitemap.xml"), xml, "utf8");
381
+ }
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/row.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/spacer.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/stack.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/styler.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/svg.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/table.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/text.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/ulist.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/video.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/wrap.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/lib/designer.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/lib/stacker.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/lib/theme.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.151
2
+ * nodality v1.0.152
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodality",
3
- "version": "1.0.151",
3
+ "version": "1.0.152",
4
4
  "description": "A lightweight library for declarative UI elements.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.esm.js",
@@ -14,7 +14,8 @@
14
14
  "require": "./dist/index.js",
15
15
  "import": "./dist/index.esm.js"
16
16
  },
17
- "./ssg": "./layout/prerender.js"
17
+ "./ssg": "./layout/prerender.js",
18
+ "./ssg-site": "./layout/prerender-site.js"
18
19
  },
19
20
  "scripts": {
20
21
  "build": "webpack --config webpack.config.js",