@janga/norna 0.7.18 → 0.7.23

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 (615) hide show
  1. package/README.md +22 -12
  2. package/astro.config.mjs +25 -0
  3. package/bin/norna-cli.mjs +8 -2
  4. package/package.json +60 -13
  5. package/schemas/category.schema.json +20 -0
  6. package/schemas/config.schema.json +22 -14
  7. package/schemas/content-frontmatter.schema.json +25 -5
  8. package/schemas/manifest.json +2 -1
  9. package/schemas/page-theme.schema.json +58 -30
  10. package/schemas/sitewide-content.schema.json +20 -19
  11. package/schemas/theme.schema.json +542 -243
  12. package/scripts/add-site-node.mjs +251 -0
  13. package/scripts/check-config.mjs +32 -4
  14. package/scripts/deploy-site.mjs +5 -9
  15. package/scripts/dev-local.mjs +175 -112
  16. package/scripts/generate-images.mjs +11 -15
  17. package/scripts/generate-schemas.mjs +4 -1
  18. package/scripts/init-site.mjs +35 -0
  19. package/scripts/lib/astro-command.mjs +14 -9
  20. package/scripts/lib/content-sync-apply.mjs +34 -0
  21. package/scripts/lib/content-sync-plan.mjs +179 -0
  22. package/scripts/lib/editor-language-service.mjs +156 -177
  23. package/scripts/lib/heading-ids.mjs +4 -2
  24. package/scripts/lib/image-presentation.mjs +2 -0
  25. package/scripts/lib/markdown-links.mjs +182 -0
  26. package/scripts/lib/navigation-model.mjs +36 -19
  27. package/scripts/lib/norna-markdown-blocks.mjs +65 -145
  28. package/scripts/lib/norna-markdown-render-plugin.mjs +37 -0
  29. package/scripts/lib/object.mjs +28 -0
  30. package/scripts/lib/page-aliases.mjs +164 -0
  31. package/scripts/lib/page-markdown.mjs +332 -0
  32. package/scripts/lib/presentation-contract.mjs +266 -0
  33. package/scripts/lib/presentation-palette-metadata.mjs +51 -0
  34. package/scripts/lib/presentation.mjs +508 -79
  35. package/scripts/lib/project-config.mjs +78 -10
  36. package/scripts/lib/schema-definitions.mjs +67 -16
  37. package/scripts/lib/schema-editor-metadata.mjs +126 -24
  38. package/scripts/lib/schema-value-definitions.mjs +30 -12
  39. package/scripts/lib/site-content.mjs +46 -239
  40. package/scripts/lib/site-link-graph.mjs +326 -0
  41. package/scripts/lib/site-page-urls.mjs +10 -0
  42. package/scripts/lib/site-paths.mjs +2 -3
  43. package/scripts/lib/site-structure.mjs +189 -0
  44. package/scripts/lib/sitemap.mjs +34 -0
  45. package/scripts/lib/theme-presets.mjs +118 -119
  46. package/scripts/lib/theme-profiles.mjs +223 -0
  47. package/scripts/lib/typography.mjs +220 -35
  48. package/scripts/lib/yaml-config.mjs +50 -6
  49. package/scripts/lib/yaml-indentation.mjs +86 -0
  50. package/scripts/show-typography.mjs +2 -3
  51. package/scripts/sync-content-sections.mjs +158 -244
  52. package/scripts/sync-site-public.mjs +23 -2
  53. package/src/components/CodeBlockCopyScript.astro +92 -0
  54. package/src/components/DisplaySettings.astro +107 -0
  55. package/src/components/ImageCarousel.astro +38 -13
  56. package/src/components/NavigationPageTree.astro +103 -88
  57. package/src/components/PageAliasRedirect.astro +49 -0
  58. package/src/components/PageContentsNavigation.astro +21 -0
  59. package/src/components/SectionNavigationScript.astro +112 -2
  60. package/src/components/SiteBreadcrumbs.astro +15 -12
  61. package/src/components/SiteNavigation.astro +104 -41
  62. package/src/components/SitePage.astro +76 -40
  63. package/src/components/SiteSection.astro +40 -191
  64. package/src/components/SiteTreeNavigation.astro +10 -10
  65. package/src/content.config.ts +5 -2
  66. package/src/layouts/BaseLayout.astro +55 -9
  67. package/src/lib/generatedImages.ts +0 -7
  68. package/src/lib/readerPreferencesScript.mjs +141 -0
  69. package/src/lib/sectionContent.ts +119 -201
  70. package/src/lib/siteNavigation.ts +36 -18
  71. package/src/lib/sitePages.ts +92 -20
  72. package/src/pages/[...slug].astro +35 -8
  73. package/src/styles/content.css +494 -0
  74. package/src/styles/foundations.css +284 -0
  75. package/src/styles/global.css +6 -2047
  76. package/src/styles/media.css +523 -0
  77. package/src/styles/navigation.css +495 -0
  78. package/src/styles/page-layout.css +437 -0
  79. package/src/styles/responsive.css +525 -0
  80. package/starters/basic/README.md +3 -3
  81. package/docs/README.md +0 -137
  82. package/docs/assets/norna-dog-shelter.jpg +0 -0
  83. package/docs/commands.md +0 -189
  84. package/docs/configuration.md +0 -142
  85. package/docs/content.md +0 -351
  86. package/docs/design/command-organization.md +0 -414
  87. package/docs/design/navigation-and-theme-plan.md +0 -186
  88. package/docs/design/norna-diagram-design.md +0 -159
  89. package/docs/design/site-examples-structure.md +0 -119
  90. package/docs/engine-development.md +0 -215
  91. package/docs/getting-started.md +0 -206
  92. package/docs/how-to/embedded-site.md +0 -35
  93. package/docs/images-and-metadata.md +0 -188
  94. package/docs/local-development.md +0 -82
  95. package/docs/pages.md +0 -190
  96. package/docs/public-files.md +0 -118
  97. package/docs/publishing.md +0 -117
  98. package/docs/requirements.md +0 -54
  99. package/docs/site-files.md +0 -152
  100. package/docs/sitewide-content.md +0 -73
  101. package/docs/theme.md +0 -237
  102. package/docs/typography.md +0 -168
  103. package/examples/README.md +0 -49
  104. package/examples/complete-sites/dog-shelter-multi-page/.astro/collections/site.schema.json +0 -35
  105. package/examples/complete-sites/dog-shelter-multi-page/.astro/collections/sitewide.schema.json +0 -97
  106. package/examples/complete-sites/dog-shelter-multi-page/.astro/collections/theme.schema.json +0 -852
  107. package/examples/complete-sites/dog-shelter-multi-page/.astro/content-assets.mjs +0 -4
  108. package/examples/complete-sites/dog-shelter-multi-page/.astro/content-modules.mjs +0 -1
  109. package/examples/complete-sites/dog-shelter-multi-page/.astro/content.d.ts +0 -183
  110. package/examples/complete-sites/dog-shelter-multi-page/.astro/data-store.json +0 -1
  111. package/examples/complete-sites/dog-shelter-multi-page/.astro/dev-local.json +0 -8
  112. package/examples/complete-sites/dog-shelter-multi-page/.astro/dev.json +0 -13
  113. package/examples/complete-sites/dog-shelter-multi-page/.astro/dev.log +0 -36
  114. package/examples/complete-sites/dog-shelter-multi-page/.astro/settings.json +0 -5
  115. package/examples/complete-sites/dog-shelter-multi-page/.astro/types.d.ts +0 -2
  116. package/examples/complete-sites/dog-shelter-multi-page/README.md +0 -24
  117. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/_metadata.json +0 -74
  118. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro-B3JHJyiv.js +0 -434
  119. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro-B3JHJyiv.js.map +0 -1
  120. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro_n_aria-query.js +0 -6390
  121. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro_n_aria-query.js.map +0 -1
  122. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro_n_axobject-query.js +0 -2702
  123. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro_n_axobject-query.js.map +0 -1
  124. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro_n_html-escaper.js +0 -65
  125. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro_n_html-escaper.js.map +0 -1
  126. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +0 -455
  127. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +0 -1
  128. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/audit-C3SVr7dT.js +0 -1534
  129. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/audit-C3SVr7dT.js.map +0 -1
  130. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/highlight-BLtvqtzO.js +0 -48
  131. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/highlight-BLtvqtzO.js.map +0 -1
  132. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/icons-C92i61-2.js +0 -42
  133. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/icons-C92i61-2.js.map +0 -1
  134. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/package.json +0 -3
  135. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +0 -4
  136. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/settings-v5GgKks_.js +0 -190
  137. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/settings-v5GgKks_.js.map +0 -1
  138. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/toolbar-BFvJVp26.js +0 -458
  139. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/toolbar-BFvJVp26.js.map +0 -1
  140. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/ui-library-Cd35oAvo.js +0 -1016
  141. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/ui-library-Cd35oAvo.js.map +0 -1
  142. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/window-vo3A6TMn.js +0 -32
  143. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/window-vo3A6TMn.js.map +0 -1
  144. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/xray-DwhzGckb.js +0 -111
  145. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/xray-DwhzGckb.js.map +0 -1
  146. package/examples/complete-sites/dog-shelter-multi-page/site/.norna/generated-images.json +0 -98
  147. package/examples/complete-sites/dog-shelter-multi-page/site/config.yaml +0 -1
  148. package/examples/complete-sites/dog-shelter-multi-page/site/pages/000-home/content.md +0 -23
  149. package/examples/complete-sites/dog-shelter-multi-page/site/pages/000-home/images/shelter-dog.jpg +0 -0
  150. package/examples/complete-sites/dog-shelter-multi-page/site/pages/010-dogs/content.md +0 -21
  151. package/examples/complete-sites/dog-shelter-multi-page/site/pages/010-dogs/images/rex.jpg +0 -0
  152. package/examples/complete-sites/dog-shelter-multi-page/site/pages/010-dogs/images/rover.jpg +0 -0
  153. package/examples/complete-sites/dog-shelter-multi-page/site/pages/020-adopt/content.md +0 -22
  154. package/examples/complete-sites/dog-shelter-multi-page/site/pages/020-adopt/images/adoption-dog.jpg +0 -0
  155. package/examples/complete-sites/dog-shelter-multi-page/site/public/favicon.svg +0 -10
  156. package/examples/complete-sites/dog-shelter-multi-page/site/public/logo.svg +0 -9
  157. package/examples/complete-sites/dog-shelter-multi-page/site/public/robots.txt +0 -2
  158. package/examples/complete-sites/dog-shelter-multi-page/site/sitewide-content.yaml +0 -4
  159. package/examples/complete-sites/dog-shelter-multi-page/site/theme.yaml +0 -1
  160. package/examples/complete-sites/dog-shelter-single-page/.astro/collections/site.schema.json +0 -35
  161. package/examples/complete-sites/dog-shelter-single-page/.astro/collections/sitewide.schema.json +0 -97
  162. package/examples/complete-sites/dog-shelter-single-page/.astro/collections/theme.schema.json +0 -852
  163. package/examples/complete-sites/dog-shelter-single-page/.astro/content-assets.mjs +0 -4
  164. package/examples/complete-sites/dog-shelter-single-page/.astro/content-modules.mjs +0 -1
  165. package/examples/complete-sites/dog-shelter-single-page/.astro/content.d.ts +0 -183
  166. package/examples/complete-sites/dog-shelter-single-page/.astro/data-store.json +0 -1
  167. package/examples/complete-sites/dog-shelter-single-page/.astro/dev-local.json +0 -8
  168. package/examples/complete-sites/dog-shelter-single-page/.astro/dev.json +0 -13
  169. package/examples/complete-sites/dog-shelter-single-page/.astro/dev.log +0 -18
  170. package/examples/complete-sites/dog-shelter-single-page/.astro/settings.json +0 -5
  171. package/examples/complete-sites/dog-shelter-single-page/.astro/types.d.ts +0 -2
  172. package/examples/complete-sites/dog-shelter-single-page/README.md +0 -24
  173. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/_metadata.json +0 -74
  174. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro-B3JHJyiv.js +0 -434
  175. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro-B3JHJyiv.js.map +0 -1
  176. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro_n_aria-query.js +0 -6390
  177. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro_n_aria-query.js.map +0 -1
  178. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro_n_axobject-query.js +0 -2702
  179. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro_n_axobject-query.js.map +0 -1
  180. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro_n_html-escaper.js +0 -65
  181. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro_n_html-escaper.js.map +0 -1
  182. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +0 -455
  183. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +0 -1
  184. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/audit-C3SVr7dT.js +0 -1534
  185. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/audit-C3SVr7dT.js.map +0 -1
  186. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/highlight-BLtvqtzO.js +0 -48
  187. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/highlight-BLtvqtzO.js.map +0 -1
  188. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/icons-C92i61-2.js +0 -42
  189. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/icons-C92i61-2.js.map +0 -1
  190. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/package.json +0 -3
  191. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +0 -4
  192. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/settings-v5GgKks_.js +0 -190
  193. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/settings-v5GgKks_.js.map +0 -1
  194. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/toolbar-BFvJVp26.js +0 -458
  195. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/toolbar-BFvJVp26.js.map +0 -1
  196. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/ui-library-Cd35oAvo.js +0 -1016
  197. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/ui-library-Cd35oAvo.js.map +0 -1
  198. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/window-vo3A6TMn.js +0 -32
  199. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/window-vo3A6TMn.js.map +0 -1
  200. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/xray-DwhzGckb.js +0 -111
  201. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/xray-DwhzGckb.js.map +0 -1
  202. package/examples/complete-sites/dog-shelter-single-page/site/.norna/generated-images.json +0 -50
  203. package/examples/complete-sites/dog-shelter-single-page/site/config.yaml +0 -1
  204. package/examples/complete-sites/dog-shelter-single-page/site/pages/000-home/content.md +0 -37
  205. package/examples/complete-sites/dog-shelter-single-page/site/pages/000-home/images/foster-dog.jpg +0 -0
  206. package/examples/complete-sites/dog-shelter-single-page/site/pages/000-home/images/rescue-dog.jpg +0 -0
  207. package/examples/complete-sites/dog-shelter-single-page/site/public/favicon.svg +0 -10
  208. package/examples/complete-sites/dog-shelter-single-page/site/public/logo.svg +0 -9
  209. package/examples/complete-sites/dog-shelter-single-page/site/public/robots.txt +0 -2
  210. package/examples/complete-sites/dog-shelter-single-page/site/sitewide-content.yaml +0 -4
  211. package/examples/complete-sites/dog-shelter-single-page/site/theme.yaml +0 -1
  212. package/examples/feature-demos/media-and-surfaces/.astro/collections/site.schema.json +0 -35
  213. package/examples/feature-demos/media-and-surfaces/.astro/collections/sitewide.schema.json +0 -97
  214. package/examples/feature-demos/media-and-surfaces/.astro/collections/theme.schema.json +0 -844
  215. package/examples/feature-demos/media-and-surfaces/.astro/content-assets.mjs +0 -4
  216. package/examples/feature-demos/media-and-surfaces/.astro/content-modules.mjs +0 -1
  217. package/examples/feature-demos/media-and-surfaces/.astro/content.d.ts +0 -183
  218. package/examples/feature-demos/media-and-surfaces/.astro/data-store.json +0 -1
  219. package/examples/feature-demos/media-and-surfaces/.astro/dev.log +0 -14
  220. package/examples/feature-demos/media-and-surfaces/.astro/settings.json +0 -5
  221. package/examples/feature-demos/media-and-surfaces/.astro/types.d.ts +0 -2
  222. package/examples/feature-demos/media-and-surfaces/README.md +0 -21
  223. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/_metadata.json +0 -80
  224. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro-B3JHJyiv.js +0 -434
  225. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro-B3JHJyiv.js.map +0 -1
  226. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro_n_aria-query.js +0 -6390
  227. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro_n_aria-query.js.map +0 -1
  228. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro_n_axobject-query.js +0 -2702
  229. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro_n_axobject-query.js.map +0 -1
  230. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro_n_html-escaper.js +0 -65
  231. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro_n_html-escaper.js.map +0 -1
  232. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +0 -455
  233. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +0 -1
  234. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/audit-C3SVr7dT.js +0 -1534
  235. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/audit-C3SVr7dT.js.map +0 -1
  236. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/embla-carousel.js +0 -1389
  237. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/embla-carousel.js.map +0 -1
  238. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/highlight-BLtvqtzO.js +0 -48
  239. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/highlight-BLtvqtzO.js.map +0 -1
  240. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/icons-C92i61-2.js +0 -42
  241. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/icons-C92i61-2.js.map +0 -1
  242. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/package.json +0 -3
  243. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +0 -4
  244. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/settings-v5GgKks_.js +0 -190
  245. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/settings-v5GgKks_.js.map +0 -1
  246. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/toolbar-BFvJVp26.js +0 -458
  247. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/toolbar-BFvJVp26.js.map +0 -1
  248. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/ui-library-Cd35oAvo.js +0 -1016
  249. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/ui-library-Cd35oAvo.js.map +0 -1
  250. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/window-vo3A6TMn.js +0 -32
  251. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/window-vo3A6TMn.js.map +0 -1
  252. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/xray-DwhzGckb.js +0 -111
  253. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/xray-DwhzGckb.js.map +0 -1
  254. package/examples/feature-demos/media-and-surfaces/site/.norna/generated-images.json +0 -66
  255. package/examples/feature-demos/media-and-surfaces/site/config.yaml +0 -1
  256. package/examples/feature-demos/media-and-surfaces/site/pages/000-home/content.md +0 -15
  257. package/examples/feature-demos/media-and-surfaces/site/pages/010-media/content.md +0 -71
  258. package/examples/feature-demos/media-and-surfaces/site/pages/010-media/images/card-carousel.svg +0 -11
  259. package/examples/feature-demos/media-and-surfaces/site/pages/010-media/images/card-stack.svg +0 -9
  260. package/examples/feature-demos/media-and-surfaces/site/pages/010-media/images/card-surfaces.svg +0 -8
  261. package/examples/feature-demos/media-and-surfaces/site/pages/010-media/images/carousel-one.svg +0 -8
  262. package/examples/feature-demos/media-and-surfaces/site/pages/010-media/images/carousel-three.svg +0 -13
  263. package/examples/feature-demos/media-and-surfaces/site/pages/010-media/images/carousel-two.svg +0 -8
  264. package/examples/feature-demos/media-and-surfaces/site/pages/010-media/images/stack-one.svg +0 -8
  265. package/examples/feature-demos/media-and-surfaces/site/pages/010-media/images/stack-two.svg +0 -9
  266. package/examples/feature-demos/media-and-surfaces/site/pages/020-surfaces/content.md +0 -42
  267. package/examples/feature-demos/media-and-surfaces/site/pages/020-surfaces/theme.yaml +0 -2
  268. package/examples/feature-demos/media-and-surfaces/site/public/robots.txt +0 -2
  269. package/examples/feature-demos/media-and-surfaces/site/sitewide-content.yaml +0 -2
  270. package/examples/feature-demos/media-and-surfaces/site/theme.yaml +0 -1
  271. package/examples/feature-demos/sitewide-content/.astro/collections/site.schema.json +0 -59
  272. package/examples/feature-demos/sitewide-content/.astro/collections/sitewide.schema.json +0 -138
  273. package/examples/feature-demos/sitewide-content/.astro/collections/theme.schema.json +0 -755
  274. package/examples/feature-demos/sitewide-content/.astro/content-assets.mjs +0 -1
  275. package/examples/feature-demos/sitewide-content/.astro/content-modules.mjs +0 -1
  276. package/examples/feature-demos/sitewide-content/.astro/content.d.ts +0 -183
  277. package/examples/feature-demos/sitewide-content/.astro/data-store.json +0 -1
  278. package/examples/feature-demos/sitewide-content/.astro/dev.json +0 -13
  279. package/examples/feature-demos/sitewide-content/.astro/dev.log +0 -18
  280. package/examples/feature-demos/sitewide-content/.astro/settings.json +0 -5
  281. package/examples/feature-demos/sitewide-content/.astro/types.d.ts +0 -2
  282. package/examples/feature-demos/sitewide-content/README.md +0 -26
  283. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/_metadata.json +0 -74
  284. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro-B3JHJyiv.js +0 -434
  285. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro-B3JHJyiv.js.map +0 -1
  286. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro_n_aria-query.js +0 -6390
  287. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro_n_aria-query.js.map +0 -1
  288. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro_n_axobject-query.js +0 -2702
  289. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro_n_axobject-query.js.map +0 -1
  290. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro_n_html-escaper.js +0 -65
  291. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro_n_html-escaper.js.map +0 -1
  292. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +0 -455
  293. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +0 -1
  294. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/audit-C3SVr7dT.js +0 -1534
  295. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/audit-C3SVr7dT.js.map +0 -1
  296. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/highlight-BLtvqtzO.js +0 -48
  297. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/highlight-BLtvqtzO.js.map +0 -1
  298. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/icons-C92i61-2.js +0 -42
  299. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/icons-C92i61-2.js.map +0 -1
  300. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/package.json +0 -3
  301. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +0 -4
  302. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/settings-v5GgKks_.js +0 -190
  303. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/settings-v5GgKks_.js.map +0 -1
  304. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/toolbar-BFvJVp26.js +0 -458
  305. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/toolbar-BFvJVp26.js.map +0 -1
  306. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/ui-library-Cd35oAvo.js +0 -1016
  307. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/ui-library-Cd35oAvo.js.map +0 -1
  308. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/window-vo3A6TMn.js +0 -32
  309. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/window-vo3A6TMn.js.map +0 -1
  310. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/xray-DwhzGckb.js +0 -111
  311. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/xray-DwhzGckb.js.map +0 -1
  312. package/examples/feature-demos/sitewide-content/site/.norna/generated-images.json +0 -1
  313. package/examples/feature-demos/sitewide-content/site/config.yaml +0 -1
  314. package/examples/feature-demos/sitewide-content/site/pages/000-home/content.md +0 -17
  315. package/examples/feature-demos/sitewide-content/site/pages/010-logo-and-navigation/content.md +0 -17
  316. package/examples/feature-demos/sitewide-content/site/pages/020-notices/content.md +0 -23
  317. package/examples/feature-demos/sitewide-content/site/pages/030-footer/content.md +0 -18
  318. package/examples/feature-demos/sitewide-content/site/public/logo.svg +0 -6
  319. package/examples/feature-demos/sitewide-content/site/public/robots.txt +0 -2
  320. package/examples/feature-demos/sitewide-content/site/sitewide-content.yaml +0 -14
  321. package/examples/feature-demos/sitewide-content/site/theme.yaml +0 -1
  322. package/examples/feature-demos/theme-preset-documentation/README.md +0 -16
  323. package/examples/feature-demos/theme-preset-documentation/site/.norna/generated-images.json +0 -74
  324. package/examples/feature-demos/theme-preset-documentation/site/config.yaml +0 -1
  325. package/examples/feature-demos/theme-preset-documentation/site/pages/000-home/content.md +0 -30
  326. package/examples/feature-demos/theme-preset-documentation/site/pages/000-home/images/black-schnauzer.jpg +0 -0
  327. package/examples/feature-demos/theme-preset-documentation/site/pages/000-home/images/retriever-portrait.jpg +0 -0
  328. package/examples/feature-demos/theme-preset-documentation/site/pages/000-home/images/schnauzer-portrait.jpg +0 -0
  329. package/examples/feature-demos/theme-preset-documentation/site/public/robots.txt +0 -2
  330. package/examples/feature-demos/theme-preset-documentation/site/sitewide-content.yaml +0 -3
  331. package/examples/feature-demos/theme-preset-documentation/site/theme.yaml +0 -1
  332. package/examples/feature-demos/theme-preset-portfolio/README.md +0 -16
  333. package/examples/feature-demos/theme-preset-portfolio/site/.norna/generated-images.json +0 -74
  334. package/examples/feature-demos/theme-preset-portfolio/site/config.yaml +0 -1
  335. package/examples/feature-demos/theme-preset-portfolio/site/pages/000-home/content.md +0 -31
  336. package/examples/feature-demos/theme-preset-portfolio/site/pages/000-home/images/puppy-portrait.jpg +0 -0
  337. package/examples/feature-demos/theme-preset-portfolio/site/pages/000-home/images/shepherd-portrait.jpg +0 -0
  338. package/examples/feature-demos/theme-preset-portfolio/site/pages/000-home/images/white-puppy.jpg +0 -0
  339. package/examples/feature-demos/theme-preset-portfolio/site/public/robots.txt +0 -2
  340. package/examples/feature-demos/theme-preset-portfolio/site/sitewide-content.yaml +0 -3
  341. package/examples/feature-demos/theme-preset-portfolio/site/theme.yaml +0 -1
  342. package/examples/feature-demos/theme-preset-project/.astro/collections/routeTheme.schema.json +0 -746
  343. package/examples/feature-demos/theme-preset-project/.astro/collections/site.schema.json +0 -59
  344. package/examples/feature-demos/theme-preset-project/.astro/collections/sitewide.schema.json +0 -138
  345. package/examples/feature-demos/theme-preset-project/.astro/collections/theme.schema.json +0 -755
  346. package/examples/feature-demos/theme-preset-project/.astro/content-assets.mjs +0 -4
  347. package/examples/feature-demos/theme-preset-project/.astro/content-modules.mjs +0 -1
  348. package/examples/feature-demos/theme-preset-project/.astro/content.d.ts +0 -183
  349. package/examples/feature-demos/theme-preset-project/.astro/data-store.json +0 -1
  350. package/examples/feature-demos/theme-preset-project/.astro/dev-local.json +0 -8
  351. package/examples/feature-demos/theme-preset-project/.astro/dev.json +0 -15
  352. package/examples/feature-demos/theme-preset-project/.astro/dev.log +0 -164
  353. package/examples/feature-demos/theme-preset-project/.astro/settings.json +0 -5
  354. package/examples/feature-demos/theme-preset-project/.astro/types.d.ts +0 -2
  355. package/examples/feature-demos/theme-preset-project/README.md +0 -16
  356. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/_metadata.json +0 -80
  357. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/astro-D_GIpJIE.js +0 -434
  358. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/astro-D_GIpJIE.js.map +0 -1
  359. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/astro_n_aria-query.js +0 -6390
  360. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/astro_n_aria-query.js.map +0 -1
  361. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/astro_n_axobject-query.js +0 -2702
  362. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/astro_n_axobject-query.js.map +0 -1
  363. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/astro_n_html-escaper.js +0 -65
  364. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/astro_n_html-escaper.js.map +0 -1
  365. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +0 -455
  366. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +0 -1
  367. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/audit-DM9Hkl5B.js +0 -1534
  368. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/audit-DM9Hkl5B.js.map +0 -1
  369. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/embla-carousel.js +0 -1389
  370. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/embla-carousel.js.map +0 -1
  371. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/highlight-DnZ1ULOL.js +0 -48
  372. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/highlight-DnZ1ULOL.js.map +0 -1
  373. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/icons-RyebKi45.js +0 -42
  374. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/icons-RyebKi45.js.map +0 -1
  375. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/package.json +0 -3
  376. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +0 -4
  377. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/settings-DaU-gNnK.js +0 -190
  378. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/settings-DaU-gNnK.js.map +0 -1
  379. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/toolbar-63PA619H.js +0 -458
  380. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/toolbar-63PA619H.js.map +0 -1
  381. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/ui-library--bkcNSYz.js +0 -1016
  382. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/ui-library--bkcNSYz.js.map +0 -1
  383. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/window-jPkpKVcy.js +0 -32
  384. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/window-jPkpKVcy.js.map +0 -1
  385. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/xray-BXCHpEJP.js +0 -111
  386. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/xray-BXCHpEJP.js.map +0 -1
  387. package/examples/feature-demos/theme-preset-project/site/.norna/generated-images.json +0 -74
  388. package/examples/feature-demos/theme-preset-project/site/config.yaml +0 -1
  389. package/examples/feature-demos/theme-preset-project/site/pages/000-home/content.md +0 -31
  390. package/examples/feature-demos/theme-preset-project/site/pages/000-home/images/boxer-portrait.jpg +0 -0
  391. package/examples/feature-demos/theme-preset-project/site/pages/000-home/images/grass-puppy.jpg +0 -0
  392. package/examples/feature-demos/theme-preset-project/site/pages/000-home/images/grey-street-dog.jpg +0 -0
  393. package/examples/feature-demos/theme-preset-project/site/public/robots.txt +0 -2
  394. package/examples/feature-demos/theme-preset-project/site/sitewide-content.yaml +0 -3
  395. package/examples/feature-demos/theme-preset-project/site/theme.yaml +0 -1
  396. package/examples/feature-demos/theme-preset-statement/README.md +0 -16
  397. package/examples/feature-demos/theme-preset-statement/site/.norna/generated-images.json +0 -74
  398. package/examples/feature-demos/theme-preset-statement/site/config.yaml +0 -1
  399. package/examples/feature-demos/theme-preset-statement/site/pages/000-home/content.md +0 -31
  400. package/examples/feature-demos/theme-preset-statement/site/pages/000-home/images/black-pedigree.jpg +0 -0
  401. package/examples/feature-demos/theme-preset-statement/site/pages/000-home/images/sheepdog-grass.jpg +0 -0
  402. package/examples/feature-demos/theme-preset-statement/site/pages/000-home/images/sheepdog-wide.jpg +0 -0
  403. package/examples/feature-demos/theme-preset-statement/site/public/robots.txt +0 -2
  404. package/examples/feature-demos/theme-preset-statement/site/sitewide-content.yaml +0 -3
  405. package/examples/feature-demos/theme-preset-statement/site/theme.yaml +0 -1
  406. package/fixtures/basic/site/.norna/generated-images.json +0 -1
  407. package/fixtures/basic/site/config.yaml +0 -1
  408. package/fixtures/basic/site/pages/000-home/content.md +0 -14
  409. package/fixtures/basic/site/pages/000-home/images/.gitkeep +0 -1
  410. package/fixtures/basic/site/public/robots.txt +0 -2
  411. package/fixtures/basic/site/sitewide-content.yaml +0 -3
  412. package/fixtures/basic/site/theme.yaml +0 -1
  413. package/fixtures/content-model-v2/.astro/collections/site.schema.json +0 -776
  414. package/fixtures/content-model-v2/.astro/collections/theme.schema.json +0 -668
  415. package/fixtures/content-model-v2/.astro/content-assets.mjs +0 -4
  416. package/fixtures/content-model-v2/.astro/content-modules.mjs +0 -1
  417. package/fixtures/content-model-v2/.astro/content.d.ts +0 -175
  418. package/fixtures/content-model-v2/.astro/data-store.json +0 -1
  419. package/fixtures/content-model-v2/.astro/dev-local.json +0 -8
  420. package/fixtures/content-model-v2/.astro/dev.json +0 -13
  421. package/fixtures/content-model-v2/.astro/dev.log +0 -231
  422. package/fixtures/content-model-v2/.astro/settings.json +0 -5
  423. package/fixtures/content-model-v2/.astro/types.d.ts +0 -2
  424. package/fixtures/content-model-v2/dist/_astro/GalleryGrid.astro_astro_type_script_index_0_lang.B0kxDyGz.js +0 -1
  425. package/fixtures/content-model-v2/dist/_astro/SitePage.BbfZub4g.css +0 -1
  426. package/fixtures/content-model-v2/dist/guide/index.html +0 -3
  427. package/fixtures/content-model-v2/dist/images/generated/intro/detail-27930b14-480.webp +0 -0
  428. package/fixtures/content-model-v2/dist/images/generated/intro/detail-27930b14-768.webp +0 -0
  429. package/fixtures/content-model-v2/dist/images/generated/intro/detail-27930b14-971.webp +0 -0
  430. package/fixtures/content-model-v2/dist/images/generated/intro/duplicate-49c98f6c-480.webp +0 -0
  431. package/fixtures/content-model-v2/dist/images/generated/intro/duplicate-49c98f6c-768.webp +0 -0
  432. package/fixtures/content-model-v2/dist/images/generated/intro/duplicate-49c98f6c-971.webp +0 -0
  433. package/fixtures/content-model-v2/dist/images/generated/intro/hero-6d588a83-480.webp +0 -0
  434. package/fixtures/content-model-v2/dist/images/generated/intro/hero-6d588a83-768.webp +0 -0
  435. package/fixtures/content-model-v2/dist/images/generated/intro/hero-6d588a83-971.webp +0 -0
  436. package/fixtures/content-model-v2/dist/images/generated/plain/image-3770d763-1080.webp +0 -0
  437. package/fixtures/content-model-v2/dist/images/generated/plain/image-3770d763-1440.webp +0 -0
  438. package/fixtures/content-model-v2/dist/images/generated/plain/image-3770d763-1600.webp +0 -0
  439. package/fixtures/content-model-v2/dist/images/generated/plain/image-3770d763-480.webp +0 -0
  440. package/fixtures/content-model-v2/dist/images/generated/plain/image-3770d763-768.webp +0 -0
  441. package/fixtures/content-model-v2/dist/images/generated/routes/010-guide/images/intro/duplicate-5cf614d2-480.webp +0 -0
  442. package/fixtures/content-model-v2/dist/images/generated/routes/010-guide/images/intro/duplicate-5cf614d2-768.webp +0 -0
  443. package/fixtures/content-model-v2/dist/images/generated/routes/010-guide/images/intro/duplicate-5cf614d2-960.webp +0 -0
  444. package/fixtures/content-model-v2/dist/images/generated/timed/slide-one-3770d763-1080.webp +0 -0
  445. package/fixtures/content-model-v2/dist/images/generated/timed/slide-one-3770d763-1440.webp +0 -0
  446. package/fixtures/content-model-v2/dist/images/generated/timed/slide-one-3770d763-1600.webp +0 -0
  447. package/fixtures/content-model-v2/dist/images/generated/timed/slide-one-3770d763-480.webp +0 -0
  448. package/fixtures/content-model-v2/dist/images/generated/timed/slide-one-3770d763-768.webp +0 -0
  449. package/fixtures/content-model-v2/dist/images/generated/timed/slide-three-5bdec65e-1080.webp +0 -0
  450. package/fixtures/content-model-v2/dist/images/generated/timed/slide-three-5bdec65e-1440.webp +0 -0
  451. package/fixtures/content-model-v2/dist/images/generated/timed/slide-three-5bdec65e-1600.webp +0 -0
  452. package/fixtures/content-model-v2/dist/images/generated/timed/slide-three-5bdec65e-480.webp +0 -0
  453. package/fixtures/content-model-v2/dist/images/generated/timed/slide-three-5bdec65e-768.webp +0 -0
  454. package/fixtures/content-model-v2/dist/images/generated/timed/slide-two-26c08aee-1080.webp +0 -0
  455. package/fixtures/content-model-v2/dist/images/generated/timed/slide-two-26c08aee-1440.webp +0 -0
  456. package/fixtures/content-model-v2/dist/images/generated/timed/slide-two-26c08aee-1600.webp +0 -0
  457. package/fixtures/content-model-v2/dist/images/generated/timed/slide-two-26c08aee-480.webp +0 -0
  458. package/fixtures/content-model-v2/dist/images/generated/timed/slide-two-26c08aee-768.webp +0 -0
  459. package/fixtures/content-model-v2/dist/index.html +0 -8
  460. package/fixtures/content-model-v2/node_modules/.vite/deps/_metadata.json +0 -80
  461. package/fixtures/content-model-v2/node_modules/.vite/deps/astro-D_GIpJIE.js +0 -434
  462. package/fixtures/content-model-v2/node_modules/.vite/deps/astro-D_GIpJIE.js.map +0 -1
  463. package/fixtures/content-model-v2/node_modules/.vite/deps/astro_n_aria-query.js +0 -6390
  464. package/fixtures/content-model-v2/node_modules/.vite/deps/astro_n_aria-query.js.map +0 -1
  465. package/fixtures/content-model-v2/node_modules/.vite/deps/astro_n_axobject-query.js +0 -2702
  466. package/fixtures/content-model-v2/node_modules/.vite/deps/astro_n_axobject-query.js.map +0 -1
  467. package/fixtures/content-model-v2/node_modules/.vite/deps/astro_n_html-escaper.js +0 -65
  468. package/fixtures/content-model-v2/node_modules/.vite/deps/astro_n_html-escaper.js.map +0 -1
  469. package/fixtures/content-model-v2/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +0 -455
  470. package/fixtures/content-model-v2/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +0 -1
  471. package/fixtures/content-model-v2/node_modules/.vite/deps/audit-DM9Hkl5B.js +0 -1534
  472. package/fixtures/content-model-v2/node_modules/.vite/deps/audit-DM9Hkl5B.js.map +0 -1
  473. package/fixtures/content-model-v2/node_modules/.vite/deps/embla-carousel.js +0 -1389
  474. package/fixtures/content-model-v2/node_modules/.vite/deps/embla-carousel.js.map +0 -1
  475. package/fixtures/content-model-v2/node_modules/.vite/deps/highlight-DnZ1ULOL.js +0 -48
  476. package/fixtures/content-model-v2/node_modules/.vite/deps/highlight-DnZ1ULOL.js.map +0 -1
  477. package/fixtures/content-model-v2/node_modules/.vite/deps/icons-RyebKi45.js +0 -42
  478. package/fixtures/content-model-v2/node_modules/.vite/deps/icons-RyebKi45.js.map +0 -1
  479. package/fixtures/content-model-v2/node_modules/.vite/deps/package.json +0 -3
  480. package/fixtures/content-model-v2/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +0 -4
  481. package/fixtures/content-model-v2/node_modules/.vite/deps/settings-DaU-gNnK.js +0 -190
  482. package/fixtures/content-model-v2/node_modules/.vite/deps/settings-DaU-gNnK.js.map +0 -1
  483. package/fixtures/content-model-v2/node_modules/.vite/deps/toolbar-63PA619H.js +0 -458
  484. package/fixtures/content-model-v2/node_modules/.vite/deps/toolbar-63PA619H.js.map +0 -1
  485. package/fixtures/content-model-v2/node_modules/.vite/deps/ui-library--bkcNSYz.js +0 -1016
  486. package/fixtures/content-model-v2/node_modules/.vite/deps/ui-library--bkcNSYz.js.map +0 -1
  487. package/fixtures/content-model-v2/node_modules/.vite/deps/window-jPkpKVcy.js +0 -32
  488. package/fixtures/content-model-v2/node_modules/.vite/deps/window-jPkpKVcy.js.map +0 -1
  489. package/fixtures/content-model-v2/node_modules/.vite/deps/xray-BXCHpEJP.js +0 -111
  490. package/fixtures/content-model-v2/node_modules/.vite/deps/xray-BXCHpEJP.js.map +0 -1
  491. package/fixtures/content-model-v2/site/.norna/generated-images.json +0 -210
  492. package/fixtures/content-model-v2/site/config.yaml +0 -1
  493. package/fixtures/content-model-v2/site/pages/000-home/content.md +0 -79
  494. package/fixtures/content-model-v2/site/pages/000-home/images/adopt.svg +0 -9
  495. package/fixtures/content-model-v2/site/pages/000-home/images/detail.jpg +0 -0
  496. package/fixtures/content-model-v2/site/pages/000-home/images/duplicate.jpg +0 -0
  497. package/fixtures/content-model-v2/site/pages/000-home/images/foster.svg +0 -12
  498. package/fixtures/content-model-v2/site/pages/000-home/images/hero.jpg +0 -0
  499. package/fixtures/content-model-v2/site/pages/000-home/images/image.jpg +0 -0
  500. package/fixtures/content-model-v2/site/pages/000-home/images/slide-one.jpg +0 -0
  501. package/fixtures/content-model-v2/site/pages/000-home/images/slide-three.jpg +0 -0
  502. package/fixtures/content-model-v2/site/pages/000-home/images/slide-two.jpg +0 -0
  503. package/fixtures/content-model-v2/site/pages/010-guide/content.md +0 -18
  504. package/fixtures/content-model-v2/site/pages/010-guide/images/duplicate.jpg +0 -0
  505. package/fixtures/content-model-v2/site/theme.yaml +0 -4
  506. package/fixtures/nested-pages/.astro/collections/site.schema.json +0 -35
  507. package/fixtures/nested-pages/.astro/collections/sitewide.schema.json +0 -97
  508. package/fixtures/nested-pages/.astro/collections/theme.schema.json +0 -844
  509. package/fixtures/nested-pages/.astro/content-assets.mjs +0 -4
  510. package/fixtures/nested-pages/.astro/content-modules.mjs +0 -1
  511. package/fixtures/nested-pages/.astro/content.d.ts +0 -183
  512. package/fixtures/nested-pages/.astro/data-store.json +0 -1
  513. package/fixtures/nested-pages/.astro/dev-local.json +0 -8
  514. package/fixtures/nested-pages/.astro/dev.log +0 -1128
  515. package/fixtures/nested-pages/.astro/settings.json +0 -5
  516. package/fixtures/nested-pages/.astro/types.d.ts +0 -2
  517. package/fixtures/nested-pages/node_modules/.vite/deps/_metadata.json +0 -74
  518. package/fixtures/nested-pages/node_modules/.vite/deps/astro-D_GIpJIE.js +0 -434
  519. package/fixtures/nested-pages/node_modules/.vite/deps/astro-D_GIpJIE.js.map +0 -1
  520. package/fixtures/nested-pages/node_modules/.vite/deps/astro_n_aria-query.js +0 -6390
  521. package/fixtures/nested-pages/node_modules/.vite/deps/astro_n_aria-query.js.map +0 -1
  522. package/fixtures/nested-pages/node_modules/.vite/deps/astro_n_axobject-query.js +0 -2702
  523. package/fixtures/nested-pages/node_modules/.vite/deps/astro_n_axobject-query.js.map +0 -1
  524. package/fixtures/nested-pages/node_modules/.vite/deps/astro_n_html-escaper.js +0 -65
  525. package/fixtures/nested-pages/node_modules/.vite/deps/astro_n_html-escaper.js.map +0 -1
  526. package/fixtures/nested-pages/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +0 -455
  527. package/fixtures/nested-pages/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +0 -1
  528. package/fixtures/nested-pages/node_modules/.vite/deps/audit-DM9Hkl5B.js +0 -1534
  529. package/fixtures/nested-pages/node_modules/.vite/deps/audit-DM9Hkl5B.js.map +0 -1
  530. package/fixtures/nested-pages/node_modules/.vite/deps/highlight-DnZ1ULOL.js +0 -48
  531. package/fixtures/nested-pages/node_modules/.vite/deps/highlight-DnZ1ULOL.js.map +0 -1
  532. package/fixtures/nested-pages/node_modules/.vite/deps/icons-RyebKi45.js +0 -42
  533. package/fixtures/nested-pages/node_modules/.vite/deps/icons-RyebKi45.js.map +0 -1
  534. package/fixtures/nested-pages/node_modules/.vite/deps/package.json +0 -3
  535. package/fixtures/nested-pages/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +0 -4
  536. package/fixtures/nested-pages/node_modules/.vite/deps/settings-DaU-gNnK.js +0 -190
  537. package/fixtures/nested-pages/node_modules/.vite/deps/settings-DaU-gNnK.js.map +0 -1
  538. package/fixtures/nested-pages/node_modules/.vite/deps/toolbar-63PA619H.js +0 -458
  539. package/fixtures/nested-pages/node_modules/.vite/deps/toolbar-63PA619H.js.map +0 -1
  540. package/fixtures/nested-pages/node_modules/.vite/deps/ui-library--bkcNSYz.js +0 -1016
  541. package/fixtures/nested-pages/node_modules/.vite/deps/ui-library--bkcNSYz.js.map +0 -1
  542. package/fixtures/nested-pages/node_modules/.vite/deps/window-jPkpKVcy.js +0 -32
  543. package/fixtures/nested-pages/node_modules/.vite/deps/window-jPkpKVcy.js.map +0 -1
  544. package/fixtures/nested-pages/node_modules/.vite/deps/xray-BXCHpEJP.js +0 -111
  545. package/fixtures/nested-pages/node_modules/.vite/deps/xray-BXCHpEJP.js.map +0 -1
  546. package/fixtures/nested-pages/site/.norna/generated-images.json +0 -10
  547. package/fixtures/nested-pages/site/config.yaml +0 -3
  548. package/fixtures/nested-pages/site/pages/000-home/content.md +0 -15
  549. package/fixtures/nested-pages/site/pages/010-guides/content.md +0 -15
  550. package/fixtures/nested-pages/site/pages/010-guides/pages/010-installation/content.md +0 -21
  551. package/fixtures/nested-pages/site/pages/010-guides/pages/010-installation/images/diagram.svg +0 -21
  552. package/fixtures/nested-pages/site/pages/010-guides/pages/010-installation/pages/010-macos/content.md +0 -18
  553. package/fixtures/nested-pages/site/pages/010-guides/pages/010-installation/theme.yaml +0 -2
  554. package/fixtures/nested-pages/site/pages/010-guides/pages/020-workflows/content.md +0 -11
  555. package/fixtures/nested-pages/site/pages/010-guides/pages/030-release-notes/content.md +0 -9
  556. package/fixtures/nested-pages/site/pages/010-guides/pages/040-configuration/content.md +0 -3
  557. package/fixtures/nested-pages/site/pages/010-guides/pages/050-content/content.md +0 -3
  558. package/fixtures/nested-pages/site/pages/010-guides/pages/060-themes/content.md +0 -3
  559. package/fixtures/nested-pages/site/pages/010-guides/pages/070-images/content.md +0 -3
  560. package/fixtures/nested-pages/site/pages/010-guides/pages/080-cards/content.md +0 -3
  561. package/fixtures/nested-pages/site/pages/010-guides/pages/090-notes/content.md +0 -3
  562. package/fixtures/nested-pages/site/pages/010-guides/pages/100-validation/content.md +0 -3
  563. package/fixtures/nested-pages/site/pages/010-guides/pages/110-synchronization/content.md +0 -3
  564. package/fixtures/nested-pages/site/pages/010-guides/pages/120-building/content.md +0 -3
  565. package/fixtures/nested-pages/site/pages/010-guides/pages/130-publishing/content.md +0 -3
  566. package/fixtures/nested-pages/site/pages/010-guides/pages/140-troubleshooting/content.md +0 -3
  567. package/fixtures/nested-pages/site/pages/010-guides/pages/150-migration/content.md +0 -3
  568. package/fixtures/nested-pages/site/pages/010-guides/pages/160-accessibility/content.md +0 -3
  569. package/fixtures/nested-pages/site/pages/010-guides/pages/170-performance/content.md +0 -3
  570. package/fixtures/nested-pages/site/pages/010-guides/pages/180-security/content.md +0 -3
  571. package/fixtures/nested-pages/site/pages/010-guides/pages/190-localization/content.md +0 -3
  572. package/fixtures/nested-pages/site/pages/010-guides/pages/200-upgrading/content.md +0 -3
  573. package/fixtures/nested-pages/site/pages/010-guides/pages/210-faq/content.md +0 -3
  574. package/fixtures/nested-pages/site/pages/010-guides/theme.yaml +0 -7
  575. package/fixtures/nested-pages/site/pages/020-reference/content.md +0 -10
  576. package/fixtures/nested-pages/site/pages/020-reference/pages/010-installation/content.md +0 -11
  577. package/fixtures/nested-pages/site/theme.yaml +0 -1
  578. package/scripts/build-pages.mjs +0 -69
  579. package/scripts/lib/example-sites.mjs +0 -49
  580. package/scripts/lib/frontmatter-yaml.mjs +0 -113
  581. package/scripts/lib/git-status.mjs +0 -7
  582. package/scripts/release.mjs +0 -153
  583. package/scripts/test-banner-visibility.mjs +0 -80
  584. package/scripts/test-ci-lockfile.mjs +0 -65
  585. package/scripts/test-cli-discovery.mjs +0 -124
  586. package/scripts/test-client-javascript.mjs +0 -225
  587. package/scripts/test-content-check.mjs +0 -621
  588. package/scripts/test-content-model-v2.mjs +0 -1693
  589. package/scripts/test-documentation.mjs +0 -189
  590. package/scripts/test-editor-language-service.mjs +0 -305
  591. package/scripts/test-engine-commands.mjs +0 -204
  592. package/scripts/test-examples.mjs +0 -53
  593. package/scripts/test-heading-ids.mjs +0 -68
  594. package/scripts/test-navigation-model.mjs +0 -63
  595. package/scripts/test-navigation-preview.mjs +0 -108
  596. package/scripts/test-navigation.mjs +0 -157
  597. package/scripts/test-nested-pages.mjs +0 -125
  598. package/scripts/test-package-check.mjs +0 -500
  599. package/scripts/test-page-model.mjs +0 -108
  600. package/scripts/test-project-config.mjs +0 -139
  601. package/scripts/test-schemas.mjs +0 -169
  602. package/scripts/test-site-public.mjs +0 -85
  603. package/scripts/test-theme-presets.mjs +0 -188
  604. package/src/components/SiteNotesScript.astro +0 -43
  605. package/starters/project/.github/workflows/deploy.yml +0 -64
  606. package/starters/project/README.md +0 -84
  607. package/starters/project/package-lock.json +0 -4296
  608. package/starters/project/package.json +0 -37
  609. package/starters/project/site/config.yaml +0 -1
  610. package/starters/project/site/pages/000-home/content.md +0 -119
  611. package/starters/project/site/pages/010-guide/content.md +0 -80
  612. package/starters/project/site/public/robots.txt +0 -2
  613. package/starters/project/site/sitewide-content.yaml +0 -3
  614. package/starters/project/site/theme.yaml +0 -1
  615. package/tsconfig.json +0 -5
@@ -1,1693 +0,0 @@
1
- import assert from 'node:assert/strict';
2
- import { execFile } from 'node:child_process';
3
- import { access, cp, mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
4
- import os from 'node:os';
5
- import path from 'node:path';
6
- import { promisify } from 'node:util';
7
- import { test } from 'node:test';
8
-
9
- const execFileAsync = promisify(execFile);
10
- const repoRoot = path.resolve(import.meta.dirname, '..');
11
- const nodeBin = process.execPath;
12
- const nornaBin = path.join(repoRoot, 'bin', 'norna.mjs');
13
- const contentScript = path.join(repoRoot, 'scripts', 'sync-content-sections.mjs');
14
- const fixtureSiteDir = path.join(repoRoot, 'fixtures', 'content-model-v2', 'site');
15
- const fixtureRoot = path.dirname(fixtureSiteDir);
16
-
17
- const runNorna = async (args, options = {}) => {
18
- try {
19
- return await execFileAsync(nodeBin, [nornaBin, ...args], {
20
- cwd: repoRoot,
21
- maxBuffer: 1024 * 1024 * 10,
22
- ...options,
23
- });
24
- } catch (error) {
25
- error.output = `${error.stdout ?? ''}${error.stderr ?? ''}`;
26
- throw error;
27
- }
28
- };
29
-
30
- const runContentScript = async (siteDir, args) => {
31
- try {
32
- return await execFileAsync(nodeBin, [contentScript, ...args], {
33
- cwd: repoRoot,
34
- env: {
35
- ...process.env,
36
- NORNA_SITE_DIR: siteDir,
37
- },
38
- maxBuffer: 1024 * 1024 * 10,
39
- });
40
- } catch (error) {
41
- error.output = `${error.stdout ?? ''}${error.stderr ?? ''}`;
42
- throw error;
43
- }
44
- };
45
-
46
- const createTempSite = async ({ underRepoCache = false } = {}) => {
47
- const tempParent = underRepoCache
48
- ? path.join(repoRoot, 'node_modules', '.cache')
49
- : os.tmpdir();
50
- await mkdir(tempParent, { recursive: true });
51
- const root = await mkdtemp(path.join(tempParent, 'norna-content-model-v2-'));
52
- const siteDir = path.join(root, 'site');
53
- await mkdir(path.join(siteDir, 'pages', '000-home'), { recursive: true });
54
- await writeFile(path.join(siteDir, 'config.yaml'), 'url: https://example.com/\n');
55
- await writeFile(path.join(siteDir, 'theme.yaml'), `typography:
56
- profile: reading
57
- `);
58
- return { root, siteDir };
59
- };
60
-
61
- const fileExists = async (filePath) => access(filePath).then(() => true, () => false);
62
-
63
- const runGit = async (cwd, args) => execFileAsync('git', args, {
64
- cwd,
65
- env: {
66
- ...process.env,
67
- GIT_AUTHOR_NAME: 'Norna Test',
68
- GIT_AUTHOR_EMAIL: 'norna@example.test',
69
- GIT_COMMITTER_NAME: 'Norna Test',
70
- GIT_COMMITTER_EMAIL: 'norna@example.test',
71
- },
72
- maxBuffer: 1024 * 1024,
73
- });
74
-
75
- const initCleanGitWorktree = async (root) => {
76
- await runGit(root, ['init']);
77
- await runGit(root, ['config', 'user.email', 'norna@example.test']);
78
- await runGit(root, ['config', 'user.name', 'Norna Test']);
79
- await runGit(root, ['add', '.']);
80
- await runGit(root, ['commit', '-m', 'initial']);
81
- };
82
-
83
- const createTempFixtureCopy = async () => {
84
- const tempParent = path.join(repoRoot, 'node_modules', '.cache');
85
- await mkdir(tempParent, { recursive: true });
86
- const root = await mkdtemp(path.join(tempParent, 'norna-content-model-v2-fixture-'));
87
- const fixtureCopyRoot = path.join(root, 'content-model-v2');
88
-
89
- await cp(fixtureRoot, fixtureCopyRoot, {
90
- recursive: true,
91
- filter: (source) => ![
92
- path.join(fixtureRoot, '.astro'),
93
- path.join(fixtureRoot, 'dist'),
94
- path.join(fixtureRoot, 'node_modules'),
95
- path.join(fixtureSiteDir, '.norna'),
96
- ].some((ignoredPath) => source === ignoredPath || source.startsWith(`${ignoredPath}${path.sep}`)),
97
- });
98
-
99
- return {
100
- root,
101
- siteDir: path.join(fixtureCopyRoot, 'site'),
102
- };
103
- };
104
-
105
- test('content model v2 fixture checks and builds', async () => {
106
- const { root, siteDir } = await createTempFixtureCopy();
107
- try {
108
- await runNorna(['--site-dir', siteDir, 'content:check']);
109
- await runNorna(['--site-dir', siteDir, 'build']);
110
-
111
- const manifest = JSON.parse(await readFile(path.join(siteDir, '.norna', 'generated-images.json'), 'utf8'));
112
- assert.ok(manifest['pages/000-home/images/duplicate.jpg']);
113
- assert.ok(manifest['pages/010-guide/images/duplicate.jpg']);
114
-
115
- const homepageHtml = await readFile(path.join(path.dirname(siteDir), 'dist', 'index.html'), 'utf8');
116
- assert.match(homepageHtml, /site-section-page-title/);
117
- assert.match(homepageHtml, /site-section-page-title-empty/);
118
- assert.match(homepageHtml, /fetchpriority="high"/);
119
- } finally {
120
- await rm(root, { recursive: true, force: true });
121
- }
122
- });
123
-
124
- test('the removed root page structure is rejected with a migration hint', async () => {
125
- const { root, siteDir } = await createTempSite();
126
- try {
127
- await writeFile(path.join(siteDir, 'content.md'), `# Home
128
- Home content.
129
- `);
130
-
131
- await assert.rejects(
132
- runNorna(['--site-dir', siteDir, 'content:check']),
133
- (error) => {
134
- assert.match(error.output, /The old root-page structure is no longer supported: site\/content\.md\./);
135
- assert.match(error.output, /Move the homepage content to site\/pages\/000-home\/content\.md/);
136
- return true;
137
- },
138
- );
139
- } finally {
140
- await rm(root, { recursive: true, force: true });
141
- }
142
- });
143
-
144
- test('the removed routes directory is rejected with a page migration hint', async () => {
145
- const { root, siteDir } = await createTempSite();
146
- try {
147
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `# Home
148
- Home content.
149
- `);
150
- await mkdir(path.join(siteDir, 'routes', '010-guide'), { recursive: true });
151
- await writeFile(path.join(siteDir, 'routes', '010-guide', 'content.md'), 'Old page content.\n');
152
-
153
- await assert.rejects(
154
- runNorna(['--site-dir', siteDir, 'content:check']),
155
- (error) => {
156
- assert.match(error.output, /site\/routes is no longer supported\. Rename it to site\/pages and use NNN-page-id directory names\./);
157
- return true;
158
- },
159
- );
160
- } finally {
161
- await rm(root, { recursive: true, force: true });
162
- }
163
- });
164
-
165
- test('the required home page cannot own child pages', async () => {
166
- const { root, siteDir } = await createTempSite();
167
- try {
168
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `# Home
169
-
170
- ## Welcome {#welcome}
171
-
172
- Home content.
173
- `);
174
- const homeChildDir = path.join(siteDir, 'pages', '000-home', 'pages', '010-news');
175
- await mkdir(homeChildDir, { recursive: true });
176
- await writeFile(path.join(homeChildDir, 'content.md'), '# News\n\nNews content.\n');
177
-
178
- await assert.rejects(
179
- runNorna(['--site-dir', siteDir, 'content:check']),
180
- (error) => {
181
- assert.match(error.output, /site\/pages\/000-home is the homepage and cannot contain child pages\./);
182
- assert.match(error.output, /Move these page directories beside 000-home under site\/pages\//);
183
- assert.match(error.output, /site\/pages\/000-home\/pages\/010-news/);
184
- return true;
185
- },
186
- );
187
- } finally {
188
- await rm(root, { recursive: true, force: true });
189
- }
190
- });
191
-
192
- test('page theme changes page presentation while preserving site visual identity', async () => {
193
- const { root, siteDir } = await createTempSite({ underRepoCache: true });
194
- try {
195
- await writeFile(path.join(siteDir, 'theme.yaml'), `preset: documentation
196
- palette: dark
197
- `);
198
- await mkdir(path.join(siteDir, 'pages', '010-guide'), { recursive: true });
199
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
200
- page:
201
- description: Root page
202
- ---
203
-
204
- # Root
205
-
206
- ## Home {#home}
207
-
208
- Root content.
209
- `);
210
- await writeFile(path.join(siteDir, 'pages', '010-guide', 'content.md'), `---
211
- page:
212
- description: Guide page
213
- ---
214
-
215
- # Guide
216
- Page content.
217
- `);
218
- await mkdir(path.join(siteDir, 'pages', '010-guide', 'pages', '010-detail'), { recursive: true });
219
- await writeFile(path.join(siteDir, 'pages', '010-guide', 'pages', '010-detail', 'content.md'), `# Detail
220
-
221
- Inherited page content.
222
- `);
223
- await writeFile(path.join(siteDir, 'pages', '010-guide', 'theme.yaml'), `layout:
224
- contentSpacing: spacious
225
- textWidth: wide
226
- sections:
227
- backgroundPattern: cycling
228
- `);
229
- await writeFile(path.join(siteDir, 'pages', '010-guide', 'pages', '010-detail', 'theme.yaml'), `layout:
230
- textWidth: narrow
231
- `);
232
-
233
- await runNorna(['--site-dir', siteDir, 'build']);
234
- const rootHtml = await readFile(path.join(path.dirname(siteDir), 'dist', 'index.html'), 'utf8');
235
- const pageHtml = await readFile(path.join(path.dirname(siteDir), 'dist', 'guide', 'index.html'), 'utf8');
236
- const detailHtml = await readFile(path.join(path.dirname(siteDir), 'dist', 'guide', 'detail', 'index.html'), 'utf8');
237
- assert.match(rootHtml, /--color-page: #000000/);
238
- assert.match(pageHtml, /--color-page: #000000/);
239
- assert.match(rootHtml, /--font-sans: Georgia, 'Times New Roman', serif/);
240
- assert.match(pageHtml, /--font-sans: Georgia, 'Times New Roman', serif/);
241
- assert.match(pageHtml, /--space-section-to-section-desktop: clamp\(2\.25rem, 5vw, 4\.5rem\)/);
242
- assert.match(pageHtml, /--section-body-width-desktop: min\(72ch,/);
243
- assert.match(detailHtml, /--space-section-to-section-desktop: clamp\(2\.25rem, 5vw, 4\.5rem\)/);
244
- assert.match(detailHtml, /--section-body-width-desktop: min\(60ch,/);
245
- assert.match(pageHtml, /<a href="\/">\s*Root\s*<\/a>/);
246
- } finally {
247
- await rm(root, { recursive: true, force: true });
248
- }
249
- });
250
-
251
- test('page metadata, navigation logo, and page listing have separate roles', async () => {
252
- const { root, siteDir } = await createTempSite({ underRepoCache: true });
253
- try {
254
- await mkdir(path.join(siteDir, 'public'), { recursive: true });
255
- await writeFile(path.join(siteDir, 'public', 'logo.svg'), '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10"/>');
256
- await writeFile(path.join(siteDir, 'sitewide-content.yaml'), `logo:
257
- height: 2rem
258
- `);
259
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `# Welcome
260
-
261
- ## Intro {#intro}
262
-
263
- Homepage content.
264
- `);
265
- await mkdir(path.join(siteDir, 'pages', '010-guide'), { recursive: true });
266
- await writeFile(path.join(siteDir, 'pages', '010-guide', 'content.md'), `---
267
- page:
268
- description: The visible guide page.
269
- ---
270
-
271
- # Guide
272
- Guide content.
273
- `);
274
- await mkdir(path.join(siteDir, 'pages', '020-private'), { recursive: true });
275
- await writeFile(path.join(siteDir, 'pages', '020-private', 'content.md'), `---
276
- navigation:
277
- listed: false
278
- ---
279
-
280
- # Unlisted
281
- Public but unlisted content.
282
- `);
283
-
284
- await runNorna(['--site-dir', siteDir, 'build']);
285
- const distDir = path.join(path.dirname(siteDir), 'dist');
286
- const homeHtml = await readFile(path.join(distDir, 'index.html'), 'utf8');
287
- const guideHtml = await readFile(path.join(distDir, 'guide', 'index.html'), 'utf8');
288
- const unlistedHtml = await readFile(path.join(distDir, 'private', 'index.html'), 'utf8');
289
-
290
- assert.match(homeHtml, /<title>Welcome<\/title>/);
291
- assert.doesNotMatch(homeHtml, /<meta name="description"/);
292
- assert.match(homeHtml, /<img class="site-brand-logo"[^>]+alt="Welcome"/);
293
- assert.match(homeHtml, /<a href="\/" aria-current="page">\s*Welcome\s*<\/a>/);
294
- assert.match(homeHtml, /<a href="\/guide\/">\s*Guide\s*<\/a>/);
295
- assert.doesNotMatch(homeHtml, />\s*Unlisted\s*<\/a>/);
296
- assert.match(guideHtml, /<meta name="description" content="The visible guide page\."/);
297
- assert.match(unlistedHtml, /Public but unlisted content/);
298
- } finally {
299
- await rm(root, { recursive: true, force: true });
300
- }
301
- });
302
-
303
- test('footer build information uses the site language and fixed formatting', async () => {
304
- const { root, siteDir } = await createTempSite({ underRepoCache: true });
305
- try {
306
- await writeFile(path.join(siteDir, 'config.yaml'), 'url: https://example.com/\nlanguage: sv-SE\n');
307
- await writeFile(path.join(siteDir, 'sitewide-content.yaml'), `footer:
308
- buildInfo: true
309
- `);
310
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
311
- page:
312
- description: Footer build information.
313
- ---
314
-
315
- # Footer
316
-
317
- ## Home {#home}
318
-
319
- Content.
320
- `);
321
-
322
- await runNorna(['--site-dir', siteDir, 'build']);
323
- const html = await readFile(path.join(path.dirname(siteDir), 'dist', 'index.html'), 'utf8');
324
- assert.match(html, /<span>Byggd [^<]+<\/span>/);
325
- } finally {
326
- await rm(root, { recursive: true, force: true });
327
- }
328
- });
329
-
330
- test('page theme cannot define navigation logo settings', async () => {
331
- const { root, siteDir } = await createTempSite({ underRepoCache: true });
332
- try {
333
- await mkdir(path.join(siteDir, 'pages', '010-guide'), { recursive: true });
334
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
335
- page:
336
- description: Root page
337
- ---
338
-
339
- # Root
340
-
341
- ## Home {#home}
342
-
343
- Root content.
344
- `);
345
- await writeFile(path.join(siteDir, 'pages', '010-guide', 'content.md'), `---
346
- page:
347
- description: Guide page
348
- ---
349
-
350
- # Guide
351
- Page content.
352
- `);
353
- await writeFile(path.join(siteDir, 'pages', '010-guide', 'theme.yaml'), `logo:
354
- height: 3rem
355
- `);
356
-
357
- await assert.rejects(
358
- runNorna(['--site-dir', siteDir, 'config:check']),
359
- (error) => {
360
- assert.match(error.output, /may not define navigation logo settings\. Optional logo display settings belong under "logo:" in site\/sitewide-content\.yaml/);
361
- return true;
362
- },
363
- );
364
- } finally {
365
- await rm(root, { recursive: true, force: true });
366
- }
367
- });
368
-
369
- test('navigation logo settings require one conventional logo file', async () => {
370
- const { root, siteDir } = await createTempSite();
371
- try {
372
- await writeFile(path.join(siteDir, 'sitewide-content.yaml'), `logo:
373
- height: 2rem
374
- `);
375
-
376
- await assert.rejects(
377
- runNorna(['--site-dir', siteDir, 'config:check']),
378
- (error) => {
379
- assert.match(error.output, /Site-wide logo is configured, but no logo file was found/);
380
- assert.match(error.output, /site\/public\/logo\.svg/);
381
- return true;
382
- },
383
- );
384
- } finally {
385
- await rm(root, { recursive: true, force: true });
386
- }
387
- });
388
-
389
- test('multiple conventional navigation logo files stop config validation', async () => {
390
- const { root, siteDir } = await createTempSite();
391
- try {
392
- await mkdir(path.join(siteDir, 'public'), { recursive: true });
393
- await writeFile(path.join(siteDir, 'public', 'logo.svg'), '<svg/>');
394
- await writeFile(path.join(siteDir, 'public', 'logo.png'), 'png');
395
-
396
- await assert.rejects(
397
- runNorna(['--site-dir', siteDir, 'config:check']),
398
- (error) => {
399
- assert.match(error.output, /Found multiple logo files/);
400
- assert.match(error.output, /site\/public\/logo\.svg/);
401
- assert.match(error.output, /site\/public\/logo\.png/);
402
- return true;
403
- },
404
- );
405
- } finally {
406
- await rm(root, { recursive: true, force: true });
407
- }
408
- });
409
-
410
- test('content:check warns for local Markdown images but not external Markdown images', async () => {
411
- const { root, siteDir } = await createTempSite();
412
- try {
413
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
414
- page:
415
- description: Fixture
416
- ---
417
-
418
- # Markdown Image Warning
419
-
420
- ## Intro {#intro}
421
-
422
- ![Local](portrait.jpg)
423
-
424
- ![External](https://example.com/portrait.jpg)
425
-
426
- ![Public](/favicon.svg)
427
- `);
428
-
429
- const { stdout } = await runContentScript(siteDir, ['--check']);
430
- assert.match(stdout, /Markdown image "portrait\.jpg" references a local image that is not managed by Norna\./);
431
- assert.doesNotMatch(stdout, /https:\/\/example\.com\/portrait\.jpg/);
432
- assert.doesNotMatch(stdout, /\/favicon\.svg/);
433
- } finally {
434
- await rm(root, { recursive: true, force: true });
435
- }
436
- });
437
-
438
- test('managed SVG images are copied as static image output', async () => {
439
- const { root, siteDir } = await createTempSite();
440
- try {
441
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
442
- page:
443
- description: Fixture
444
- ---
445
-
446
- # SVG Fixture
447
-
448
- ## Intro {#intro}
449
-
450
- \`\`\`norna-image-stack
451
- - image: diagram.svg
452
- alt: Diagram
453
- \`\`\`
454
- `);
455
- await mkdir(path.join(siteDir, 'pages', '000-home', 'images'), { recursive: true });
456
- await writeFile(path.join(siteDir, 'pages', '000-home', 'images', 'diagram.svg'), '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 620"><rect width="900" height="620"/></svg>\n');
457
-
458
- await runContentScript(siteDir, ['--check']);
459
- await runNorna(['--site-dir', siteDir, 'images']);
460
-
461
- const manifest = JSON.parse(await readFile(path.join(siteDir, '.norna', 'generated-images.json'), 'utf8'));
462
- const entry = manifest['pages/000-home/images/diagram.svg'];
463
- assert.equal(entry.kind, 'static');
464
- assert.equal(entry.width, 900);
465
- assert.equal(entry.height, 620);
466
- assert.match(entry.src, /^\/images\/original\/pages\/000-home\/images\/diagram-[a-f0-9]{8}\.svg$/);
467
- assert.equal(entry.variants, undefined);
468
- assert.equal(await fileExists(path.join(siteDir, '.norna', 'public', entry.src.replace(/^\//, ''))), true);
469
- } finally {
470
- await rm(root, { recursive: true, force: true });
471
- }
472
- });
473
-
474
- test('inline notes render as linked numbered margin notes', async () => {
475
- const { root, siteDir } = await createTempSite({ underRepoCache: true });
476
- try {
477
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
478
- page:
479
- description: Fixture
480
- ---
481
-
482
- # Inline notes
483
-
484
- ## Intro {#intro}
485
-
486
- The first paragraph points to an explanation.{note-ref}
487
-
488
- {note: This explanation names \`sitewide-content.yaml\` and stays beside the
489
- paragraph on wide screens.}
490
-
491
- The second paragraph has its own explanation.{note-ref}
492
-
493
- {note:
494
- This is the second
495
- explanation.
496
- }
497
- `);
498
-
499
- await runNorna(['--site-dir', siteDir, 'build']);
500
- const html = await readFile(path.join(path.dirname(siteDir), 'dist', 'index.html'), 'utf8');
501
- assert.match(html, /<sup class="section-note-ref"><a id="note-ref-home-intro-1" href="#note-home-intro-1" aria-label="Note 1" aria-describedby="note-home-intro-1" data-note-id="note-home-intro-1">1<\/a><\/sup>/);
502
- assert.match(html, /<aside class="section-note section-note-margin" id="note-home-intro-1" aria-label="Note 1" role="note" data-note-id="note-home-intro-1" style="grid-row: 1;">/);
503
- assert.match(html, /<a class="section-note-number" href="#note-ref-home-intro-1" aria-label="Note 1">\s*1\s*<\/a>/);
504
- assert.match(html, /This explanation names <code>sitewide-content\.yaml<\/code> and stays beside the paragraph on wide screens\./);
505
- assert.match(html, /<sup class="section-note-ref"><a id="note-ref-home-intro-2" href="#note-home-intro-2" aria-label="Note 2" aria-describedby="note-home-intro-2" data-note-id="note-home-intro-2">2<\/a><\/sup>/);
506
- assert.match(html, /<aside class="section-note section-note-margin" id="note-home-intro-2" aria-label="Note 2" role="note" data-note-id="note-home-intro-2" style="grid-row: 2;">/);
507
- assert.match(html, /This is the second explanation\./);
508
- assert.doesNotMatch(html, /\{note:/);
509
- } finally {
510
- await rm(root, { recursive: true, force: true });
511
- }
512
- });
513
-
514
- test('updated managed SVG images get updated static output', async () => {
515
- const { root, siteDir } = await createTempSite();
516
- try {
517
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
518
- page:
519
- description: Fixture
520
- ---
521
-
522
- # SVG Update Fixture
523
-
524
- ## Intro {#intro}
525
-
526
- \`\`\`norna-image-stack
527
- - image: diagram.svg
528
- alt: Diagram
529
- \`\`\`
530
- `);
531
- await mkdir(path.join(siteDir, 'pages', '000-home', 'images'), { recursive: true });
532
- const sourcePath = path.join(siteDir, 'pages', '000-home', 'images', 'diagram.svg');
533
- await writeFile(sourcePath, '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 620"><rect width="900" height="620" fill="red"/></svg>\n');
534
-
535
- await runContentScript(siteDir, ['--check']);
536
- await runNorna(['--site-dir', siteDir, 'images']);
537
-
538
- const firstManifest = JSON.parse(await readFile(path.join(siteDir, '.norna', 'generated-images.json'), 'utf8'));
539
- const firstEntry = firstManifest['pages/000-home/images/diagram.svg'];
540
- const firstOutputPath = path.join(siteDir, '.norna', 'public', firstEntry.src.replace(/^\//, ''));
541
-
542
- await writeFile(sourcePath, '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 620"><rect width="900" height="620" fill="blue"/></svg>\n');
543
- await runNorna(['--site-dir', siteDir, 'images']);
544
-
545
- const secondManifest = JSON.parse(await readFile(path.join(siteDir, '.norna', 'generated-images.json'), 'utf8'));
546
- const secondEntry = secondManifest['pages/000-home/images/diagram.svg'];
547
- const secondOutputPath = path.join(siteDir, '.norna', 'public', secondEntry.src.replace(/^\//, ''));
548
-
549
- assert.notEqual(secondEntry.sourceHash, firstEntry.sourceHash);
550
- assert.notEqual(secondEntry.src, firstEntry.src);
551
- assert.equal(await fileExists(firstOutputPath), false);
552
- assert.match(await readFile(secondOutputPath, 'utf8'), /fill="blue"/);
553
- } finally {
554
- await rm(root, { recursive: true, force: true });
555
- }
556
- });
557
-
558
- test('content:check warns when carousel SVG images have no intrinsic aspect ratio', async () => {
559
- const { root, siteDir } = await createTempSite();
560
- try {
561
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
562
- page:
563
- description: Fixture
564
- ---
565
-
566
- # SVG Carousel Fixture
567
-
568
- ## Intro {#intro}
569
-
570
- \`\`\`norna-image-carousel
571
- - image: first.svg
572
- alt: First
573
- - image: second.svg
574
- alt: Second
575
- \`\`\`
576
- `);
577
- await mkdir(path.join(siteDir, 'pages', '000-home', 'images'), { recursive: true });
578
- await writeFile(path.join(siteDir, 'pages', '000-home', 'images', 'first.svg'), '<svg xmlns="http://www.w3.org/2000/svg"><rect width="100" height="100"/></svg>\n');
579
- await writeFile(path.join(siteDir, 'pages', '000-home', 'images', 'second.svg'), '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><rect width="100" height="100"/></svg>\n');
580
-
581
- const { stdout } = await runContentScript(siteDir, ['--check']);
582
- assert.match(stdout, /Content check completed with warnings\./);
583
- assert.match(stdout, /Carousel on line \d+ uses images without an intrinsic aspect ratio: first\.svg\./);
584
- } finally {
585
- await rm(root, { recursive: true, force: true });
586
- }
587
- });
588
-
589
- test('norna-card-list images are managed image references', async () => {
590
- const { root, siteDir } = await createTempSite({ underRepoCache: true });
591
- try {
592
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
593
- page:
594
- description: Fixture
595
- ---
596
-
597
- # Card List Fixture
598
-
599
- ## Help {#help}
600
-
601
- \`\`\`norna-card-list
602
- layout: image-left
603
- flow: stack
604
- size: l
605
- width: narrow
606
-
607
- - title: Adopt
608
- text: Give a dog a new home.
609
- image: adopt.svg
610
- link: /adopt/
611
- badge-text: Recommended
612
- - title: Donate
613
- text: Support the shelter.
614
- \`\`\`
615
- `);
616
- await mkdir(path.join(siteDir, 'pages', '000-home', 'images'), { recursive: true });
617
- await writeFile(path.join(siteDir, 'pages', '000-home', 'images', 'adopt.svg'), '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 100"><rect width="160" height="100"/></svg>\n');
618
-
619
- const { stdout } = await runContentScript(siteDir, ['--check']);
620
- assert.match(stdout, /Content check passed\./);
621
-
622
- await runNorna(['--site-dir', siteDir, 'images']);
623
- const manifest = JSON.parse(await readFile(path.join(siteDir, '.norna', 'generated-images.json'), 'utf8'));
624
- assert.equal(manifest['pages/000-home/images/adopt.svg'].kind, 'static');
625
-
626
- await runNorna(['--site-dir', siteDir, 'build']);
627
- const html = await readFile(path.join(root, 'dist', 'index.html'), 'utf8');
628
- assert.match(html, /card-list-layout-image-left/);
629
- assert.match(html, /card-list-flow-stack/);
630
- assert.match(html, /card-list-size-l/);
631
- assert.match(html, /card-list-width-narrow/);
632
- assert.match(html, /card-list-item-has-image/);
633
- assert.match(html, /class="card-list-badge">Recommended<\/p>/);
634
- assert.match(html, /Give a dog a new home\./);
635
- assert.match(html, /href="\/adopt\/"/);
636
- assert.match(html, /src="\/images\/original\/pages\/000-home\/images\/adopt-[a-f0-9]{8}\.svg"/);
637
- } finally {
638
- await rm(root, { recursive: true, force: true });
639
- }
640
- });
641
-
642
- test('content:check fails when a norna-card-list image file is missing', async () => {
643
- const { root, siteDir } = await createTempSite();
644
- try {
645
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
646
- page:
647
- description: Fixture
648
- ---
649
-
650
- # Missing Card Image
651
-
652
- ## Help {#help}
653
-
654
- \`\`\`norna-card-list
655
- - title: Adopt
656
- image: missing.svg
657
- \`\`\`
658
- `);
659
-
660
- await assert.rejects(
661
- () => runContentScript(siteDir, ['--check']),
662
- (error) => {
663
- assert.match(error.output, /Image "missing\.svg" does not exist at .*site\/pages\/000-home\/images\/missing\.svg or anywhere under any page image root\./);
664
- return true;
665
- },
666
- );
667
- } finally {
668
- await rm(root, { recursive: true, force: true });
669
- }
670
- });
671
-
672
- test('content:check fails for malformed norna-card-list blocks', async () => {
673
- const { root, siteDir } = await createTempSite();
674
- try {
675
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
676
- page:
677
- description: Fixture
678
- ---
679
-
680
- # Malformed Card List
681
-
682
- ## Help {#help}
683
-
684
- \`\`\`norna-card-list
685
- - text: Missing title
686
- \`\`\`
687
- `);
688
-
689
- await assert.rejects(
690
- () => runContentScript(siteDir, ['--check']),
691
- (error) => {
692
- assert.match(error.output, /Invalid norna-card-list entry "- text: Missing title"\. Start each card with "- title: Card title"\./);
693
- return true;
694
- },
695
- );
696
- } finally {
697
- await rm(root, { recursive: true, force: true });
698
- }
699
- });
700
-
701
- test('content:check fails for invalid norna-card-list options', async () => {
702
- const { root, siteDir } = await createTempSite();
703
- try {
704
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
705
- page:
706
- description: Fixture
707
- ---
708
-
709
- # Invalid Card List Options
710
-
711
- ## Help {#help}
712
-
713
- \`\`\`norna-card-list
714
- layout: floating
715
- flow: list
716
- size: huge
717
- width: full
718
-
719
- - title: Adopt
720
- text: Give a dog a new home.
721
- \`\`\`
722
- `);
723
-
724
- await assert.rejects(
725
- () => runContentScript(siteDir, ['--check']),
726
- (error) => {
727
- assert.match(error.output, /Invalid norna-card-list layout "floating"\. Use one of: image-top, image-left, image-right\./);
728
- return true;
729
- },
730
- );
731
- } finally {
732
- await rm(root, { recursive: true, force: true });
733
- }
734
- });
735
-
736
- test('content:check fails for invalid norna-card-list width', async () => {
737
- const { root, siteDir } = await createTempSite();
738
- try {
739
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
740
- page:
741
- description: Fixture
742
- ---
743
-
744
- # Invalid Card List Width
745
-
746
- ## Help {#help}
747
-
748
- \`\`\`norna-card-list
749
- width: full
750
-
751
- - title: Adopt
752
- text: Give a dog a new home.
753
- \`\`\`
754
- `);
755
-
756
- await assert.rejects(
757
- () => runContentScript(siteDir, ['--check']),
758
- (error) => {
759
- assert.match(error.output, /Invalid norna-card-list width "full"\. Use one of: text, narrow, normal, wide\./);
760
- return true;
761
- },
762
- );
763
- } finally {
764
- await rm(root, { recursive: true, force: true });
765
- }
766
- });
767
-
768
- test('content:check fails for invalid norna-card-list size', async () => {
769
- const { root, siteDir } = await createTempSite();
770
- try {
771
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
772
- page:
773
- description: Fixture
774
- ---
775
-
776
- # Invalid Card List Size
777
-
778
- ## Help {#help}
779
-
780
- \`\`\`norna-card-list
781
- size: huge
782
-
783
- - title: Adopt
784
- text: Give a dog a new home.
785
- \`\`\`
786
- `);
787
-
788
- await assert.rejects(
789
- () => runContentScript(siteDir, ['--check']),
790
- (error) => {
791
- assert.match(error.output, /Invalid norna-card-list size "huge"\. Use one of: s, m, l, xl\./);
792
- return true;
793
- },
794
- );
795
- } finally {
796
- await rm(root, { recursive: true, force: true });
797
- }
798
- });
799
-
800
- test('content:sync moves Norna-managed images inside the same page image root', async () => {
801
- const { root, siteDir } = await createTempSite();
802
- try {
803
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
804
- page:
805
- description: Fixture
806
- ---
807
-
808
- # Sync Fixture
809
-
810
- ## Work {#work}
811
-
812
- \`\`\`norna-image-stack
813
- - image: moved.jpg
814
- alt: Moved image
815
- \`\`\`
816
- `);
817
- await mkdir(path.join(siteDir, 'pages', '000-home', 'images', 'old'), { recursive: true });
818
- await writeFile(path.join(siteDir, 'pages', '000-home', 'images', 'old', 'moved.jpg'), 'fixture image');
819
-
820
- await runContentScript(siteDir, ['--write', '--yes']);
821
-
822
- const moved = await readFile(path.join(siteDir, 'pages', '000-home', 'images', 'moved.jpg'), 'utf8');
823
- assert.equal(moved, 'fixture image');
824
- } finally {
825
- await rm(root, { recursive: true, force: true });
826
- }
827
- });
828
-
829
- test('content:sync moves managed SVG images inside the same page image root', async () => {
830
- const { root, siteDir } = await createTempSite();
831
- try {
832
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
833
- page:
834
- description: Fixture
835
- ---
836
-
837
- # SVG Sync Fixture
838
-
839
- ## Work {#work}
840
-
841
- \`\`\`norna-image-stack
842
- - image: moved.svg
843
- alt: Moved diagram
844
- \`\`\`
845
- `);
846
- await mkdir(path.join(siteDir, 'pages', '000-home', 'images', 'old'), { recursive: true });
847
- await writeFile(path.join(siteDir, 'pages', '000-home', 'images', 'old', 'moved.svg'), '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10"></svg>\n');
848
-
849
- await runContentScript(siteDir, ['--write', '--yes']);
850
-
851
- const moved = await readFile(path.join(siteDir, 'pages', '000-home', 'images', 'moved.svg'), 'utf8');
852
- assert.match(moved, /viewBox="0 0 10 10"/);
853
- } finally {
854
- await rm(root, { recursive: true, force: true });
855
- }
856
- });
857
-
858
- test('content:sync moves Norna-managed images across page image roots when git is clean', async () => {
859
- const { root, siteDir } = await createTempSite();
860
- try {
861
- const sourcePageDir = path.join(siteDir, 'pages', '010-source');
862
- const targetPageDir = path.join(siteDir, 'pages', '020-target');
863
- await mkdir(path.join(sourcePageDir, 'images'), { recursive: true });
864
- await mkdir(targetPageDir, { recursive: true });
865
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
866
- page:
867
- description: Fixture
868
- ---
869
-
870
- # Cross Page Sync
871
-
872
- ## Home {#home}
873
-
874
- Text.
875
- `);
876
- await writeFile(path.join(sourcePageDir, 'content.md'), `---
877
- page:
878
- description: Fixture
879
- ---
880
-
881
- # Source
882
-
883
- ## Old {#old}
884
-
885
- Text.
886
- `);
887
- await writeFile(path.join(targetPageDir, 'content.md'), `---
888
- page:
889
- description: Fixture
890
- ---
891
-
892
- # Target
893
-
894
- ## Work {#work}
895
-
896
- \`\`\`norna-image-stack
897
- - image: moved.jpg
898
- \`\`\`
899
- `);
900
- await writeFile(path.join(sourcePageDir, 'images', 'moved.jpg'), 'page image');
901
- await initCleanGitWorktree(root);
902
-
903
- await runContentScript(siteDir, ['--write', '--yes']);
904
-
905
- assert.equal(await fileExists(path.join(targetPageDir, 'images', 'moved.jpg')), true);
906
- assert.equal(await fileExists(path.join(sourcePageDir, 'images', 'moved.jpg')), false);
907
- } finally {
908
- await rm(root, { recursive: true, force: true });
909
- }
910
- });
911
-
912
- test('content:sync refuses cross-page image moves when git status is dirty', async () => {
913
- const { root, siteDir } = await createTempSite();
914
- try {
915
- const sourcePageDir = path.join(siteDir, 'pages', '010-source');
916
- const targetPageDir = path.join(siteDir, 'pages', '020-target');
917
- await mkdir(path.join(sourcePageDir, 'images'), { recursive: true });
918
- await mkdir(targetPageDir, { recursive: true });
919
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
920
- page:
921
- description: Fixture
922
- ---
923
-
924
- # Dirty Cross Page Sync
925
-
926
- ## Home {#home}
927
-
928
- Text.
929
- `);
930
- await writeFile(path.join(sourcePageDir, 'content.md'), `---
931
- page:
932
- description: Fixture
933
- ---
934
-
935
- # Source
936
-
937
- ## Old {#old}
938
-
939
- Text.
940
- `);
941
- await writeFile(path.join(targetPageDir, 'content.md'), `---
942
- page:
943
- description: Fixture
944
- ---
945
-
946
- # Target
947
-
948
- ## Work {#work}
949
-
950
- \`\`\`norna-image-stack
951
- - image: moved.jpg
952
- \`\`\`
953
- `);
954
- await writeFile(path.join(sourcePageDir, 'images', 'moved.jpg'), 'page image');
955
- await initCleanGitWorktree(root);
956
- await writeFile(path.join(root, 'dirty.txt'), 'dirty');
957
-
958
- await assert.rejects(
959
- () => runContentScript(siteDir, ['--write', '--yes']),
960
- (error) => {
961
- assert.match(error.output, /Cross-page content sync requires a clean git working tree before moving files between page image roots\./);
962
- assert.match(error.output, /dirty\.txt/);
963
- return true;
964
- },
965
- );
966
-
967
- assert.equal(await fileExists(path.join(targetPageDir, 'images', 'moved.jpg')), false);
968
- assert.equal(await fileExists(path.join(sourcePageDir, 'images', 'moved.jpg')), true);
969
- } finally {
970
- await rm(root, { recursive: true, force: true });
971
- }
972
- });
973
-
974
- test('content:check reports cross-page image moves without requiring clean git status', async () => {
975
- const { root, siteDir } = await createTempSite();
976
- try {
977
- const sourcePageDir = path.join(siteDir, 'pages', '010-source');
978
- const targetPageDir = path.join(siteDir, 'pages', '020-target');
979
- await mkdir(path.join(sourcePageDir, 'images'), { recursive: true });
980
- await mkdir(targetPageDir, { recursive: true });
981
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
982
- page:
983
- description: Fixture
984
- ---
985
-
986
- # Cross Page Check
987
-
988
- ## Home {#home}
989
-
990
- Text.
991
- `);
992
- await writeFile(path.join(sourcePageDir, 'content.md'), `---
993
- page:
994
- description: Fixture
995
- ---
996
-
997
- # Source
998
-
999
- ## Old {#old}
1000
-
1001
- Text.
1002
- `);
1003
- await writeFile(path.join(targetPageDir, 'content.md'), `---
1004
- page:
1005
- description: Fixture
1006
- ---
1007
-
1008
- # Target
1009
-
1010
- ## Work {#work}
1011
-
1012
- \`\`\`norna-image-stack
1013
- - image: moved.jpg
1014
- \`\`\`
1015
- `);
1016
- await writeFile(path.join(sourcePageDir, 'images', 'moved.jpg'), 'page image');
1017
- await writeFile(path.join(root, 'dirty.txt'), 'dirty');
1018
-
1019
- await assert.rejects(
1020
- () => runContentScript(siteDir, ['--check']),
1021
- (error) => {
1022
- assert.match(error.output, /Image "moved\.jpg" is used here but is located in .*site\/pages\/010-source\/images\/moved\.jpg\./);
1023
- assert.match(error.output, /Run norna content:sync to move it from .*site\/pages\/010-source to .*site\/pages\/020-target\./);
1024
- assert.doesNotMatch(error.output, /requires a clean git working tree before moving files/);
1025
- return true;
1026
- },
1027
- );
1028
- } finally {
1029
- await rm(root, { recursive: true, force: true });
1030
- }
1031
- });
1032
-
1033
- test('content:sync refuses to move a cross-page image still referenced by its current section', async () => {
1034
- const { root, siteDir } = await createTempSite();
1035
- try {
1036
- const sourcePageDir = path.join(siteDir, 'pages', '010-source');
1037
- const targetPageDir = path.join(siteDir, 'pages', '020-target');
1038
- await mkdir(path.join(sourcePageDir, 'images'), { recursive: true });
1039
- await mkdir(targetPageDir, { recursive: true });
1040
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
1041
- page:
1042
- description: Fixture
1043
- ---
1044
-
1045
- # Cross Page Copy
1046
-
1047
- ## Home {#home}
1048
-
1049
- Text.
1050
- `);
1051
- await writeFile(path.join(sourcePageDir, 'content.md'), `---
1052
- page:
1053
- description: Fixture
1054
- ---
1055
-
1056
- # Source
1057
-
1058
- ## Old {#old}
1059
-
1060
- \`\`\`norna-image-stack
1061
- - image: shared.jpg
1062
- \`\`\`
1063
- `);
1064
- await writeFile(path.join(targetPageDir, 'content.md'), `---
1065
- page:
1066
- description: Fixture
1067
- ---
1068
-
1069
- # Target
1070
-
1071
- ## Work {#work}
1072
-
1073
- \`\`\`norna-image-stack
1074
- - image: shared.jpg
1075
- \`\`\`
1076
- `);
1077
- await writeFile(path.join(sourcePageDir, 'images', 'shared.jpg'), 'page image');
1078
- await initCleanGitWorktree(root);
1079
-
1080
- await assert.rejects(
1081
- () => runContentScript(siteDir, ['--write', '--yes']),
1082
- (error) => {
1083
- assert.match(error.output, /Cannot relocate "shared\.jpg" from .*site\/pages\/010-source\/images\/shared\.jpg because it is still referenced from .*site\/pages\/010-source\/content\.md \[old\]\./);
1084
- return true;
1085
- },
1086
- );
1087
-
1088
- assert.equal(await fileExists(path.join(targetPageDir, 'images', 'shared.jpg')), false);
1089
- assert.equal(await fileExists(path.join(sourcePageDir, 'images', 'shared.jpg')), true);
1090
- } finally {
1091
- await rm(root, { recursive: true, force: true });
1092
- }
1093
- });
1094
-
1095
- test('CLI content:sync refreshes generated images after moving a carousel image', async () => {
1096
- const { root, siteDir } = await createTempSite();
1097
- try {
1098
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
1099
- page:
1100
- description: Fixture
1101
- ---
1102
-
1103
- # Sync Fixture
1104
-
1105
- ## Work {#work}
1106
-
1107
- \`\`\`norna-image-carousel
1108
- - image: slide-one.jpg
1109
- alt: Slide one
1110
- - image: slide-two.jpg
1111
- alt: Slide two
1112
- \`\`\`
1113
- `);
1114
- await mkdir(path.join(siteDir, 'pages', '000-home', 'images', 'old'), { recursive: true });
1115
- await cp(path.join(fixtureSiteDir, 'pages', '000-home', 'images', 'hero.jpg'), path.join(siteDir, 'pages', '000-home', 'images', 'old', 'slide-one.jpg'));
1116
- await cp(path.join(fixtureSiteDir, 'pages', '000-home', 'images', 'detail.jpg'), path.join(siteDir, 'pages', '000-home', 'images', 'slide-two.jpg'), { force: true });
1117
-
1118
- await runNorna(['--site-dir', siteDir, 'content:sync', '--yes']);
1119
-
1120
- const manifest = JSON.parse(await readFile(path.join(siteDir, '.norna', 'generated-images.json'), 'utf8'));
1121
- assert.equal(await fileExists(path.join(siteDir, 'pages', '000-home', 'images', 'slide-one.jpg')), true);
1122
- assert.ok(manifest['pages/000-home/images/slide-one.jpg']);
1123
- assert.ok(manifest['pages/000-home/images/slide-two.jpg']);
1124
- } finally {
1125
- await rm(root, { recursive: true, force: true });
1126
- }
1127
- });
1128
-
1129
- test('automatic H2 and H3 ids render while explicit ids remain stable', async () => {
1130
- const { root, siteDir } = await createTempSite({ underRepoCache: true });
1131
- try {
1132
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
1133
- page:
1134
- description: Fixture
1135
- ---
1136
-
1137
- # Heading IDs
1138
-
1139
- ## Café and **code**
1140
-
1141
- ### More details
1142
-
1143
- Automatic identifiers.
1144
-
1145
- ## Renamed heading {#stable-id}
1146
-
1147
- Explicit identifier.
1148
- `);
1149
-
1150
- await runContentScript(siteDir, ['--check']);
1151
- await runNorna(['--site-dir', siteDir, 'build']);
1152
-
1153
- const html = await readFile(path.join(root, 'dist', 'index.html'), 'utf8');
1154
- assert.match(html, /<h2 id="cafe-and-code">Café and <strong>code<\/strong><\/h2>/);
1155
- assert.match(html, /<h3 id="more-details">More details<\/h3>/);
1156
- assert.match(html, /<h2 id="stable-id">Renamed heading<\/h2>/);
1157
- } finally {
1158
- await rm(root, { recursive: true, force: true });
1159
- }
1160
- });
1161
-
1162
- test('content:check fails for malformed Norna image blocks', async () => {
1163
- const { root, siteDir } = await createTempSite();
1164
- try {
1165
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
1166
- page:
1167
- description: Fixture
1168
- ---
1169
-
1170
- # Malformed Block
1171
-
1172
- ## Intro {#intro}
1173
-
1174
- \`\`\`norna-image-stack
1175
- image hero.jpg
1176
- alt: Hero
1177
- \`\`\`
1178
- `);
1179
-
1180
- await assert.rejects(
1181
- () => runContentScript(siteDir, ['--check']),
1182
- (error) => {
1183
- assert.match(error.output, /Invalid norna-image-stack entry "image hero\.jpg"\. Start each image with "- image: filename\.jpg"\./);
1184
- return true;
1185
- },
1186
- );
1187
- } finally {
1188
- await rm(root, { recursive: true, force: true });
1189
- }
1190
- });
1191
-
1192
- test('content:check fails for unknown Norna block names', async () => {
1193
- const { root, siteDir } = await createTempSite();
1194
- try {
1195
- await mkdir(path.join(siteDir, 'pages', '000-home', 'images', 'plain'), { recursive: true });
1196
- await writeFile(path.join(siteDir, 'pages', '000-home', 'images', 'plain', 'image.jpg'), 'fixture image');
1197
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
1198
- page:
1199
- description: Fixture
1200
- ---
1201
-
1202
- # Unknown Block
1203
-
1204
- ## Plain {#plain}
1205
-
1206
- \`\`\`norna-gallery-stack
1207
- - image: image.jpg
1208
- \`\`\`
1209
- `);
1210
-
1211
- await assert.rejects(
1212
- () => runContentScript(siteDir, ['--check']),
1213
- (error) => {
1214
- assert.match(error.output, /Unknown Norna block "norna-gallery-stack"\. Use one of: norna-image-stack, norna-image-carousel, norna-card-list\./);
1215
- assert.match(error.output, /Use norna-image-stack for one or more stacked images\./);
1216
- assert.match(error.output, /Example: ```norna-image-stack\n- image: filename\.jpg\n```/);
1217
- return true;
1218
- },
1219
- );
1220
- } finally {
1221
- await rm(root, { recursive: true, force: true });
1222
- }
1223
- });
1224
-
1225
- test('content:check rejects the removed norna-note block', async () => {
1226
- const { root, siteDir } = await createTempSite();
1227
- try {
1228
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
1229
- page:
1230
- description: Fixture
1231
- ---
1232
-
1233
- # Removed note block
1234
-
1235
- ## Intro {#intro}
1236
-
1237
- \`\`\`norna-note
1238
- This block is no longer supported.
1239
- \`\`\`
1240
- `);
1241
-
1242
- await assert.rejects(
1243
- () => runContentScript(siteDir, ['--check']),
1244
- (error) => {
1245
- assert.match(error.output, /Unknown Norna block "norna-note"\. Use one of: norna-image-stack, norna-image-carousel, norna-card-list\./);
1246
- return true;
1247
- },
1248
- );
1249
- } finally {
1250
- await rm(root, { recursive: true, force: true });
1251
- }
1252
- });
1253
-
1254
- test('content:check supports tilde fenced Norna blocks', async () => {
1255
- const { root, siteDir } = await createTempSite();
1256
- try {
1257
- await mkdir(path.join(siteDir, 'pages', '000-home', 'images'), { recursive: true });
1258
- await writeFile(path.join(siteDir, 'pages', '000-home', 'images', 'hero.jpg'), 'fixture image');
1259
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
1260
- page:
1261
- description: Fixture
1262
- ---
1263
-
1264
- # Tilde Fence
1265
-
1266
- ## Intro {#intro}
1267
-
1268
- ~~~norna-image-stack
1269
- - image: hero.jpg
1270
- ~~~
1271
- `);
1272
-
1273
- const result = await runContentScript(siteDir, ['--check']);
1274
- assert.match(result.stdout, /Content check passed\./);
1275
- } finally {
1276
- await rm(root, { recursive: true, force: true });
1277
- }
1278
- });
1279
-
1280
- test('content:check ignores Norna blocks shown inside longer code fences', async () => {
1281
- const { root, siteDir } = await createTempSite();
1282
- try {
1283
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
1284
- page:
1285
- description: Fixture
1286
- ---
1287
-
1288
- # Code Example
1289
-
1290
- ## Intro {#intro}
1291
-
1292
- \`\`\`\`md
1293
- \`\`\`norna-image-stack
1294
- - image: missing-example.jpg
1295
- \`\`\`
1296
- \`\`\`\`
1297
- `);
1298
-
1299
- const result = await runContentScript(siteDir, ['--check']);
1300
- assert.match(result.stdout, /Content check passed\./);
1301
- } finally {
1302
- await rm(root, { recursive: true, force: true });
1303
- }
1304
- });
1305
-
1306
- test('content:check fails for likely Norna block names outside fences', async () => {
1307
- const { root, siteDir } = await createTempSite();
1308
- try {
1309
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
1310
- page:
1311
- description: Fixture
1312
- ---
1313
-
1314
- # Missing Fence
1315
-
1316
- ## Intro {#intro}
1317
-
1318
- norna-image-stack
1319
- - image: hero.jpg
1320
- `);
1321
-
1322
- await assert.rejects(
1323
- () => runContentScript(siteDir, ['--check']),
1324
- (error) => {
1325
- assert.match(error.output, /Found "norna-image-stack" outside a code block\./);
1326
- assert.match(error.output, /Start the block like this:\n```norna-image-stack\n- image: filename\.jpg\n```/);
1327
- return true;
1328
- },
1329
- );
1330
- } finally {
1331
- await rm(root, { recursive: true, force: true });
1332
- }
1333
- });
1334
-
1335
- test('content:check fails for short Norna block fences', async () => {
1336
- const { root, siteDir } = await createTempSite();
1337
- try {
1338
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
1339
- page:
1340
- description: Fixture
1341
- ---
1342
-
1343
- # Short Fence
1344
-
1345
- ## Intro {#intro}
1346
-
1347
- \`\`norna-image-stack
1348
- - image: hero.jpg
1349
- \`\`
1350
- `);
1351
-
1352
- await assert.rejects(
1353
- () => runContentScript(siteDir, ['--check']),
1354
- (error) => {
1355
- assert.match(error.output, /Invalid Norna block start for "norna-image-stack"\./);
1356
- assert.match(error.output, /Use three backticks or three tildes/);
1357
- return true;
1358
- },
1359
- );
1360
- } finally {
1361
- await rm(root, { recursive: true, force: true });
1362
- }
1363
- });
1364
-
1365
- test('content:check fails for unclosed Norna blocks', async () => {
1366
- const { root, siteDir } = await createTempSite();
1367
- try {
1368
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
1369
- page:
1370
- description: Fixture
1371
- ---
1372
-
1373
- # Unclosed Fence
1374
-
1375
- ## Intro {#intro}
1376
-
1377
- \`\`\`norna-image-stack
1378
- - image: hero.jpg
1379
- ~~~
1380
- `);
1381
-
1382
- await assert.rejects(
1383
- () => runContentScript(siteDir, ['--check']),
1384
- (error) => {
1385
- assert.match(error.output, /This Norna block was started on line \d+ but not closed\./);
1386
- assert.match(error.output, /Add a closing ``` line after the last entry\./);
1387
- return true;
1388
- },
1389
- );
1390
- } finally {
1391
- await rm(root, { recursive: true, force: true });
1392
- }
1393
- });
1394
-
1395
- test('content:check continues after malformed Norna blocks', async () => {
1396
- const { root, siteDir } = await createTempSite();
1397
- try {
1398
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
1399
- page:
1400
- description: Fixture
1401
- ---
1402
-
1403
- # Multiple Issues
1404
-
1405
- ## Intro {#intro}
1406
-
1407
- \`\`\`norna-image-stack
1408
- image broken.jpg
1409
- alt: Broken
1410
- \`\`\`
1411
-
1412
- \`\`\`norna-image-stack
1413
- - image: missing-intro.jpg
1414
- alt: Missing intro
1415
- \`\`\`
1416
-
1417
- ## More {#more}
1418
-
1419
- \`\`\`norna-image-stack
1420
- - image: missing-more.jpg
1421
- alt: Missing more
1422
- \`\`\`
1423
- `);
1424
-
1425
- await assert.rejects(
1426
- () => runContentScript(siteDir, ['--check']),
1427
- (error) => {
1428
- assert.match(error.output, /Invalid norna-image-stack entry "image broken\.jpg"\. Start each image with "- image: filename\.jpg"\./);
1429
- assert.match(error.output, /Image "missing-intro\.jpg" does not exist at .*site\/pages\/000-home\/images\/missing-intro\.jpg or anywhere under any page image root\./);
1430
- assert.match(error.output, /Image "missing-more\.jpg" does not exist at .*site\/pages\/000-home\/images\/missing-more\.jpg or anywhere under any page image root\./);
1431
- return true;
1432
- },
1433
- );
1434
- } finally {
1435
- await rm(root, { recursive: true, force: true });
1436
- }
1437
- });
1438
-
1439
- test('content:check allows Norna images without alt text', async () => {
1440
- const { root, siteDir } = await createTempSite();
1441
- try {
1442
- await mkdir(path.join(siteDir, 'pages', '000-home', 'images'), { recursive: true });
1443
- await writeFile(path.join(siteDir, 'pages', '000-home', 'images', 'hero.jpg'), 'fixture image');
1444
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
1445
- page:
1446
- description: Fixture
1447
- ---
1448
-
1449
- # Missing Alt
1450
-
1451
- ## Intro {#intro}
1452
-
1453
- \`\`\`norna-image-stack
1454
- - image: hero.jpg
1455
- \`\`\`
1456
- `);
1457
-
1458
- const result = await runContentScript(siteDir, ['--check']);
1459
- assert.match(result.stdout, /Content check passed\./);
1460
- } finally {
1461
- await rm(root, { recursive: true, force: true });
1462
- }
1463
- });
1464
-
1465
- test('content:check reports single-image carousel and missing file together', async () => {
1466
- const { root, siteDir } = await createTempSite();
1467
- try {
1468
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
1469
- page:
1470
- description: Fixture
1471
- ---
1472
-
1473
- # Beginner Errors
1474
-
1475
- ## Plain {#plain}
1476
-
1477
- \`\`\`norna-image-carousel
1478
- - image: foo.jpg
1479
- \`\`\`
1480
- `);
1481
-
1482
- await assert.rejects(
1483
- () => runContentScript(siteDir, ['--check']),
1484
- (error) => {
1485
- assert.match(error.output, /norna-image-carousel on line \d+ contains 1 image\. A carousel needs at least two images\./);
1486
- assert.match(error.output, /Image "foo\.jpg" does not exist at .*site\/pages\/000-home\/images\/foo\.jpg or anywhere under any page image root\./);
1487
- return true;
1488
- },
1489
- );
1490
- } finally {
1491
- await rm(root, { recursive: true, force: true });
1492
- }
1493
- });
1494
-
1495
- test('content:check fails when a Norna-managed image file is missing', async () => {
1496
- const { root, siteDir } = await createTempSite();
1497
- try {
1498
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
1499
- page:
1500
- description: Fixture
1501
- ---
1502
-
1503
- # Missing Image
1504
-
1505
- ## Intro {#intro}
1506
-
1507
- \`\`\`norna-image-stack
1508
- - image: missing.jpg
1509
- alt: Missing
1510
- \`\`\`
1511
- `);
1512
-
1513
- await assert.rejects(
1514
- () => runContentScript(siteDir, ['--check']),
1515
- (error) => {
1516
- assert.match(error.output, /Image "missing\.jpg" does not exist at .*site\/pages\/000-home\/images\/missing\.jpg or anywhere under any page image root\./);
1517
- return true;
1518
- },
1519
- );
1520
- } finally {
1521
- await rm(root, { recursive: true, force: true });
1522
- }
1523
- });
1524
-
1525
- test('content:check fails when a page repeats a section id', async () => {
1526
- const { root, siteDir } = await createTempSite();
1527
- try {
1528
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
1529
- page:
1530
- description: Fixture
1531
- ---
1532
-
1533
- # Duplicate Section
1534
-
1535
- ## Intro {#intro}
1536
-
1537
- First.
1538
-
1539
- ## Again {#intro}
1540
-
1541
- Second.
1542
- `);
1543
-
1544
- await assert.rejects(
1545
- () => runContentScript(siteDir, ['--check']),
1546
- (error) => {
1547
- assert.match(error.output, /Two headings resolve to id "intro"\./);
1548
- return true;
1549
- },
1550
- );
1551
- } finally {
1552
- await rm(root, { recursive: true, force: true });
1553
- }
1554
- });
1555
-
1556
- test('content:check reports collisions between automatic H2 and H3 ids', async () => {
1557
- const { root, siteDir } = await createTempSite();
1558
- try {
1559
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
1560
- page:
1561
- description: Fixture
1562
- ---
1563
-
1564
- # Automatic collision
1565
-
1566
- ## Förstå
1567
-
1568
- Section text.
1569
-
1570
- ### Forsta
1571
-
1572
- Topic text.
1573
- `);
1574
-
1575
- await assert.rejects(
1576
- () => runContentScript(siteDir, ['--check']),
1577
- (error) => {
1578
- assert.match(error.output, /Two headings resolve to id "forsta"\./);
1579
- assert.match(error.output, /The other heading is on line \d+\./);
1580
- assert.match(error.output, /Add a unique explicit id to at least one heading/);
1581
- return true;
1582
- },
1583
- );
1584
- } finally {
1585
- await rm(root, { recursive: true, force: true });
1586
- }
1587
- });
1588
-
1589
- test('content:check rejects removed sections frontmatter', async () => {
1590
- const { root, siteDir } = await createTempSite();
1591
- try {
1592
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
1593
- page:
1594
- description: Fixture
1595
- sections:
1596
- intro: {}
1597
- ---
1598
-
1599
- # Removed Section Metadata
1600
-
1601
- ## Intro {#intro}
1602
-
1603
- Text.
1604
- `);
1605
-
1606
- await assert.rejects(
1607
- () => runContentScript(siteDir, ['--check']),
1608
- (error) => {
1609
- assert.match(error.output, /defines "sections" at the top level, but it is not a valid top-level content field\./);
1610
- return true;
1611
- },
1612
- );
1613
- } finally {
1614
- await rm(root, { recursive: true, force: true });
1615
- }
1616
- });
1617
-
1618
- test('content:sync refuses ambiguous image relocation', async () => {
1619
- const { root, siteDir } = await createTempSite();
1620
- try {
1621
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
1622
- page:
1623
- description: Fixture
1624
- ---
1625
-
1626
- # Ambiguous Move
1627
-
1628
- ## Work {#work}
1629
-
1630
- \`\`\`norna-image-stack
1631
- - image: shared.jpg
1632
- alt: Shared
1633
- \`\`\`
1634
- `);
1635
- await mkdir(path.join(siteDir, 'pages', '000-home', 'images', 'old-a'), { recursive: true });
1636
- await mkdir(path.join(siteDir, 'pages', '000-home', 'images', 'old-b'), { recursive: true });
1637
- await writeFile(path.join(siteDir, 'pages', '000-home', 'images', 'old-a', 'shared.jpg'), 'a');
1638
- await writeFile(path.join(siteDir, 'pages', '000-home', 'images', 'old-b', 'shared.jpg'), 'b');
1639
-
1640
- await assert.rejects(
1641
- () => runContentScript(siteDir, ['--write', '--yes']),
1642
- (error) => {
1643
- assert.match(error.output, /Cannot relocate "shared\.jpg"\. Multiple files with this filename were found:/);
1644
- assert.match(error.output, /site\/pages\/000-home\/images\/old-a\/shared\.jpg/);
1645
- assert.match(error.output, /site\/pages\/000-home\/images\/old-b\/shared\.jpg/);
1646
- return true;
1647
- },
1648
- );
1649
-
1650
- assert.equal(await fileExists(path.join(siteDir, 'pages', '000-home', 'images', 'shared.jpg')), false);
1651
- assert.equal(await fileExists(path.join(siteDir, 'pages', '000-home', 'images', 'old-a', 'shared.jpg')), true);
1652
- assert.equal(await fileExists(path.join(siteDir, 'pages', '000-home', 'images', 'old-b', 'shared.jpg')), true);
1653
- } finally {
1654
- await rm(root, { recursive: true, force: true });
1655
- }
1656
- });
1657
-
1658
- test('content:sync moves one page-local image once when multiple sections reference it', async () => {
1659
- const { root, siteDir } = await createTempSite();
1660
- try {
1661
- await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
1662
- page:
1663
- description: Fixture
1664
- ---
1665
-
1666
- # Copied Image Reference
1667
-
1668
- ## Intro {#intro}
1669
-
1670
- \`\`\`norna-image-stack
1671
- - image: shared.jpg
1672
- alt: Shared original
1673
- \`\`\`
1674
-
1675
- ## Plain {#plain}
1676
-
1677
- \`\`\`norna-image-stack
1678
- - image: shared.jpg
1679
- alt: Shared copy
1680
- \`\`\`
1681
- `);
1682
- await mkdir(path.join(siteDir, 'pages', '000-home', 'images', 'intro'), { recursive: true });
1683
- await writeFile(path.join(siteDir, 'pages', '000-home', 'images', 'intro', 'shared.jpg'), 'shared image');
1684
-
1685
- await runContentScript(siteDir, ['--write', '--yes']);
1686
-
1687
- assert.equal(await fileExists(path.join(siteDir, 'pages', '000-home', 'images', 'intro', 'shared.jpg')), false);
1688
- assert.equal(await fileExists(path.join(siteDir, 'pages', '000-home', 'images', 'shared.jpg')), true);
1689
- await runContentScript(siteDir, ['--check']);
1690
- } finally {
1691
- await rm(root, { recursive: true, force: true });
1692
- }
1693
- });