@janga/norna 0.7.18 → 0.7.22

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 (595) hide show
  1. package/README.md +22 -12
  2. package/astro.config.mjs +22 -0
  3. package/bin/norna-cli.mjs +6 -0
  4. package/package.json +45 -12
  5. package/schemas/category.schema.json +20 -0
  6. package/schemas/config.schema.json +16 -6
  7. package/schemas/content-frontmatter.schema.json +5 -5
  8. package/schemas/manifest.json +2 -1
  9. package/schemas/page-theme.schema.json +22 -22
  10. package/schemas/sitewide-content.schema.json +18 -18
  11. package/schemas/theme.schema.json +312 -146
  12. package/scripts/add-site-node.mjs +251 -0
  13. package/scripts/check-config.mjs +10 -2
  14. package/scripts/deploy-site.mjs +5 -8
  15. package/scripts/generate-images.mjs +11 -15
  16. package/scripts/generate-schemas.mjs +4 -1
  17. package/scripts/init-site.mjs +35 -0
  18. package/scripts/lib/astro-command.mjs +14 -9
  19. package/scripts/lib/editor-language-service.mjs +28 -177
  20. package/scripts/lib/heading-ids.mjs +3 -1
  21. package/scripts/lib/navigation-model.mjs +23 -17
  22. package/scripts/lib/norna-markdown-blocks.mjs +64 -145
  23. package/scripts/lib/norna-markdown-render-plugin.mjs +37 -0
  24. package/scripts/lib/object.mjs +28 -0
  25. package/scripts/lib/page-markdown.mjs +282 -0
  26. package/scripts/lib/presentation-contract.mjs +266 -0
  27. package/scripts/lib/presentation-palette-metadata.mjs +51 -0
  28. package/scripts/lib/presentation.mjs +526 -79
  29. package/scripts/lib/project-config.mjs +53 -6
  30. package/scripts/lib/schema-definitions.mjs +53 -13
  31. package/scripts/lib/schema-editor-metadata.mjs +107 -18
  32. package/scripts/lib/schema-value-definitions.mjs +24 -10
  33. package/scripts/lib/site-content.mjs +23 -238
  34. package/scripts/lib/site-paths.mjs +2 -3
  35. package/scripts/lib/site-structure.mjs +189 -0
  36. package/scripts/lib/theme-presets.mjs +75 -104
  37. package/scripts/lib/theme-profiles.mjs +221 -0
  38. package/scripts/lib/typography.mjs +220 -35
  39. package/scripts/lib/yaml-config.mjs +46 -6
  40. package/scripts/lib/yaml-indentation.mjs +86 -0
  41. package/scripts/show-typography.mjs +2 -3
  42. package/scripts/sync-content-sections.mjs +87 -76
  43. package/src/components/DisplaySettings.astro +107 -0
  44. package/src/components/ImageCarousel.astro +34 -13
  45. package/src/components/NavigationPageTree.astro +74 -43
  46. package/src/components/SectionNavigationScript.astro +56 -2
  47. package/src/components/SiteBreadcrumbs.astro +15 -12
  48. package/src/components/SiteNavigation.astro +92 -41
  49. package/src/components/SitePage.astro +35 -39
  50. package/src/components/SiteSection.astro +35 -191
  51. package/src/components/SiteTreeNavigation.astro +10 -9
  52. package/src/layouts/BaseLayout.astro +46 -6
  53. package/src/lib/generatedImages.ts +0 -7
  54. package/src/lib/readerPreferencesScript.mjs +141 -0
  55. package/src/lib/sectionContent.ts +119 -201
  56. package/src/lib/siteNavigation.ts +36 -18
  57. package/src/lib/sitePages.ts +91 -20
  58. package/src/pages/[...slug].astro +2 -2
  59. package/src/styles/global.css +759 -296
  60. package/starters/basic/README.md +2 -2
  61. package/docs/README.md +0 -137
  62. package/docs/assets/norna-dog-shelter.jpg +0 -0
  63. package/docs/commands.md +0 -189
  64. package/docs/configuration.md +0 -142
  65. package/docs/content.md +0 -351
  66. package/docs/design/command-organization.md +0 -414
  67. package/docs/design/navigation-and-theme-plan.md +0 -186
  68. package/docs/design/norna-diagram-design.md +0 -159
  69. package/docs/design/site-examples-structure.md +0 -119
  70. package/docs/engine-development.md +0 -215
  71. package/docs/getting-started.md +0 -206
  72. package/docs/how-to/embedded-site.md +0 -35
  73. package/docs/images-and-metadata.md +0 -188
  74. package/docs/local-development.md +0 -82
  75. package/docs/pages.md +0 -190
  76. package/docs/public-files.md +0 -118
  77. package/docs/publishing.md +0 -117
  78. package/docs/requirements.md +0 -54
  79. package/docs/site-files.md +0 -152
  80. package/docs/sitewide-content.md +0 -73
  81. package/docs/theme.md +0 -237
  82. package/docs/typography.md +0 -168
  83. package/examples/README.md +0 -49
  84. package/examples/complete-sites/dog-shelter-multi-page/.astro/collections/site.schema.json +0 -35
  85. package/examples/complete-sites/dog-shelter-multi-page/.astro/collections/sitewide.schema.json +0 -97
  86. package/examples/complete-sites/dog-shelter-multi-page/.astro/collections/theme.schema.json +0 -852
  87. package/examples/complete-sites/dog-shelter-multi-page/.astro/content-assets.mjs +0 -4
  88. package/examples/complete-sites/dog-shelter-multi-page/.astro/content-modules.mjs +0 -1
  89. package/examples/complete-sites/dog-shelter-multi-page/.astro/content.d.ts +0 -183
  90. package/examples/complete-sites/dog-shelter-multi-page/.astro/data-store.json +0 -1
  91. package/examples/complete-sites/dog-shelter-multi-page/.astro/dev-local.json +0 -8
  92. package/examples/complete-sites/dog-shelter-multi-page/.astro/dev.json +0 -13
  93. package/examples/complete-sites/dog-shelter-multi-page/.astro/dev.log +0 -36
  94. package/examples/complete-sites/dog-shelter-multi-page/.astro/settings.json +0 -5
  95. package/examples/complete-sites/dog-shelter-multi-page/.astro/types.d.ts +0 -2
  96. package/examples/complete-sites/dog-shelter-multi-page/README.md +0 -24
  97. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/_metadata.json +0 -74
  98. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro-B3JHJyiv.js +0 -434
  99. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro-B3JHJyiv.js.map +0 -1
  100. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro_n_aria-query.js +0 -6390
  101. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro_n_aria-query.js.map +0 -1
  102. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro_n_axobject-query.js +0 -2702
  103. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro_n_axobject-query.js.map +0 -1
  104. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro_n_html-escaper.js +0 -65
  105. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro_n_html-escaper.js.map +0 -1
  106. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +0 -455
  107. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +0 -1
  108. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/audit-C3SVr7dT.js +0 -1534
  109. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/audit-C3SVr7dT.js.map +0 -1
  110. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/highlight-BLtvqtzO.js +0 -48
  111. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/highlight-BLtvqtzO.js.map +0 -1
  112. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/icons-C92i61-2.js +0 -42
  113. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/icons-C92i61-2.js.map +0 -1
  114. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/package.json +0 -3
  115. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +0 -4
  116. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/settings-v5GgKks_.js +0 -190
  117. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/settings-v5GgKks_.js.map +0 -1
  118. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/toolbar-BFvJVp26.js +0 -458
  119. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/toolbar-BFvJVp26.js.map +0 -1
  120. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/ui-library-Cd35oAvo.js +0 -1016
  121. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/ui-library-Cd35oAvo.js.map +0 -1
  122. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/window-vo3A6TMn.js +0 -32
  123. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/window-vo3A6TMn.js.map +0 -1
  124. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/xray-DwhzGckb.js +0 -111
  125. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/xray-DwhzGckb.js.map +0 -1
  126. package/examples/complete-sites/dog-shelter-multi-page/site/.norna/generated-images.json +0 -98
  127. package/examples/complete-sites/dog-shelter-multi-page/site/config.yaml +0 -1
  128. package/examples/complete-sites/dog-shelter-multi-page/site/pages/000-home/content.md +0 -23
  129. package/examples/complete-sites/dog-shelter-multi-page/site/pages/000-home/images/shelter-dog.jpg +0 -0
  130. package/examples/complete-sites/dog-shelter-multi-page/site/pages/010-dogs/content.md +0 -21
  131. package/examples/complete-sites/dog-shelter-multi-page/site/pages/010-dogs/images/rex.jpg +0 -0
  132. package/examples/complete-sites/dog-shelter-multi-page/site/pages/010-dogs/images/rover.jpg +0 -0
  133. package/examples/complete-sites/dog-shelter-multi-page/site/pages/020-adopt/content.md +0 -22
  134. package/examples/complete-sites/dog-shelter-multi-page/site/pages/020-adopt/images/adoption-dog.jpg +0 -0
  135. package/examples/complete-sites/dog-shelter-multi-page/site/public/favicon.svg +0 -10
  136. package/examples/complete-sites/dog-shelter-multi-page/site/public/logo.svg +0 -9
  137. package/examples/complete-sites/dog-shelter-multi-page/site/public/robots.txt +0 -2
  138. package/examples/complete-sites/dog-shelter-multi-page/site/sitewide-content.yaml +0 -4
  139. package/examples/complete-sites/dog-shelter-multi-page/site/theme.yaml +0 -1
  140. package/examples/complete-sites/dog-shelter-single-page/.astro/collections/site.schema.json +0 -35
  141. package/examples/complete-sites/dog-shelter-single-page/.astro/collections/sitewide.schema.json +0 -97
  142. package/examples/complete-sites/dog-shelter-single-page/.astro/collections/theme.schema.json +0 -852
  143. package/examples/complete-sites/dog-shelter-single-page/.astro/content-assets.mjs +0 -4
  144. package/examples/complete-sites/dog-shelter-single-page/.astro/content-modules.mjs +0 -1
  145. package/examples/complete-sites/dog-shelter-single-page/.astro/content.d.ts +0 -183
  146. package/examples/complete-sites/dog-shelter-single-page/.astro/data-store.json +0 -1
  147. package/examples/complete-sites/dog-shelter-single-page/.astro/dev-local.json +0 -8
  148. package/examples/complete-sites/dog-shelter-single-page/.astro/dev.json +0 -13
  149. package/examples/complete-sites/dog-shelter-single-page/.astro/dev.log +0 -18
  150. package/examples/complete-sites/dog-shelter-single-page/.astro/settings.json +0 -5
  151. package/examples/complete-sites/dog-shelter-single-page/.astro/types.d.ts +0 -2
  152. package/examples/complete-sites/dog-shelter-single-page/README.md +0 -24
  153. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/_metadata.json +0 -74
  154. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro-B3JHJyiv.js +0 -434
  155. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro-B3JHJyiv.js.map +0 -1
  156. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro_n_aria-query.js +0 -6390
  157. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro_n_aria-query.js.map +0 -1
  158. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro_n_axobject-query.js +0 -2702
  159. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro_n_axobject-query.js.map +0 -1
  160. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro_n_html-escaper.js +0 -65
  161. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro_n_html-escaper.js.map +0 -1
  162. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +0 -455
  163. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +0 -1
  164. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/audit-C3SVr7dT.js +0 -1534
  165. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/audit-C3SVr7dT.js.map +0 -1
  166. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/highlight-BLtvqtzO.js +0 -48
  167. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/highlight-BLtvqtzO.js.map +0 -1
  168. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/icons-C92i61-2.js +0 -42
  169. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/icons-C92i61-2.js.map +0 -1
  170. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/package.json +0 -3
  171. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +0 -4
  172. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/settings-v5GgKks_.js +0 -190
  173. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/settings-v5GgKks_.js.map +0 -1
  174. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/toolbar-BFvJVp26.js +0 -458
  175. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/toolbar-BFvJVp26.js.map +0 -1
  176. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/ui-library-Cd35oAvo.js +0 -1016
  177. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/ui-library-Cd35oAvo.js.map +0 -1
  178. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/window-vo3A6TMn.js +0 -32
  179. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/window-vo3A6TMn.js.map +0 -1
  180. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/xray-DwhzGckb.js +0 -111
  181. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/xray-DwhzGckb.js.map +0 -1
  182. package/examples/complete-sites/dog-shelter-single-page/site/.norna/generated-images.json +0 -50
  183. package/examples/complete-sites/dog-shelter-single-page/site/config.yaml +0 -1
  184. package/examples/complete-sites/dog-shelter-single-page/site/pages/000-home/content.md +0 -37
  185. package/examples/complete-sites/dog-shelter-single-page/site/pages/000-home/images/foster-dog.jpg +0 -0
  186. package/examples/complete-sites/dog-shelter-single-page/site/pages/000-home/images/rescue-dog.jpg +0 -0
  187. package/examples/complete-sites/dog-shelter-single-page/site/public/favicon.svg +0 -10
  188. package/examples/complete-sites/dog-shelter-single-page/site/public/logo.svg +0 -9
  189. package/examples/complete-sites/dog-shelter-single-page/site/public/robots.txt +0 -2
  190. package/examples/complete-sites/dog-shelter-single-page/site/sitewide-content.yaml +0 -4
  191. package/examples/complete-sites/dog-shelter-single-page/site/theme.yaml +0 -1
  192. package/examples/feature-demos/media-and-surfaces/.astro/collections/site.schema.json +0 -35
  193. package/examples/feature-demos/media-and-surfaces/.astro/collections/sitewide.schema.json +0 -97
  194. package/examples/feature-demos/media-and-surfaces/.astro/collections/theme.schema.json +0 -844
  195. package/examples/feature-demos/media-and-surfaces/.astro/content-assets.mjs +0 -4
  196. package/examples/feature-demos/media-and-surfaces/.astro/content-modules.mjs +0 -1
  197. package/examples/feature-demos/media-and-surfaces/.astro/content.d.ts +0 -183
  198. package/examples/feature-demos/media-and-surfaces/.astro/data-store.json +0 -1
  199. package/examples/feature-demos/media-and-surfaces/.astro/dev.log +0 -14
  200. package/examples/feature-demos/media-and-surfaces/.astro/settings.json +0 -5
  201. package/examples/feature-demos/media-and-surfaces/.astro/types.d.ts +0 -2
  202. package/examples/feature-demos/media-and-surfaces/README.md +0 -21
  203. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/_metadata.json +0 -80
  204. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro-B3JHJyiv.js +0 -434
  205. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro-B3JHJyiv.js.map +0 -1
  206. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro_n_aria-query.js +0 -6390
  207. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro_n_aria-query.js.map +0 -1
  208. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro_n_axobject-query.js +0 -2702
  209. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro_n_axobject-query.js.map +0 -1
  210. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro_n_html-escaper.js +0 -65
  211. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro_n_html-escaper.js.map +0 -1
  212. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +0 -455
  213. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +0 -1
  214. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/audit-C3SVr7dT.js +0 -1534
  215. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/audit-C3SVr7dT.js.map +0 -1
  216. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/embla-carousel.js +0 -1389
  217. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/embla-carousel.js.map +0 -1
  218. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/highlight-BLtvqtzO.js +0 -48
  219. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/highlight-BLtvqtzO.js.map +0 -1
  220. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/icons-C92i61-2.js +0 -42
  221. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/icons-C92i61-2.js.map +0 -1
  222. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/package.json +0 -3
  223. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +0 -4
  224. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/settings-v5GgKks_.js +0 -190
  225. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/settings-v5GgKks_.js.map +0 -1
  226. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/toolbar-BFvJVp26.js +0 -458
  227. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/toolbar-BFvJVp26.js.map +0 -1
  228. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/ui-library-Cd35oAvo.js +0 -1016
  229. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/ui-library-Cd35oAvo.js.map +0 -1
  230. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/window-vo3A6TMn.js +0 -32
  231. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/window-vo3A6TMn.js.map +0 -1
  232. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/xray-DwhzGckb.js +0 -111
  233. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/xray-DwhzGckb.js.map +0 -1
  234. package/examples/feature-demos/media-and-surfaces/site/.norna/generated-images.json +0 -66
  235. package/examples/feature-demos/media-and-surfaces/site/config.yaml +0 -1
  236. package/examples/feature-demos/media-and-surfaces/site/pages/000-home/content.md +0 -15
  237. package/examples/feature-demos/media-and-surfaces/site/pages/010-media/content.md +0 -71
  238. package/examples/feature-demos/media-and-surfaces/site/pages/010-media/images/card-carousel.svg +0 -11
  239. package/examples/feature-demos/media-and-surfaces/site/pages/010-media/images/card-stack.svg +0 -9
  240. package/examples/feature-demos/media-and-surfaces/site/pages/010-media/images/card-surfaces.svg +0 -8
  241. package/examples/feature-demos/media-and-surfaces/site/pages/010-media/images/carousel-one.svg +0 -8
  242. package/examples/feature-demos/media-and-surfaces/site/pages/010-media/images/carousel-three.svg +0 -13
  243. package/examples/feature-demos/media-and-surfaces/site/pages/010-media/images/carousel-two.svg +0 -8
  244. package/examples/feature-demos/media-and-surfaces/site/pages/010-media/images/stack-one.svg +0 -8
  245. package/examples/feature-demos/media-and-surfaces/site/pages/010-media/images/stack-two.svg +0 -9
  246. package/examples/feature-demos/media-and-surfaces/site/pages/020-surfaces/content.md +0 -42
  247. package/examples/feature-demos/media-and-surfaces/site/pages/020-surfaces/theme.yaml +0 -2
  248. package/examples/feature-demos/media-and-surfaces/site/public/robots.txt +0 -2
  249. package/examples/feature-demos/media-and-surfaces/site/sitewide-content.yaml +0 -2
  250. package/examples/feature-demos/media-and-surfaces/site/theme.yaml +0 -1
  251. package/examples/feature-demos/sitewide-content/.astro/collections/site.schema.json +0 -59
  252. package/examples/feature-demos/sitewide-content/.astro/collections/sitewide.schema.json +0 -138
  253. package/examples/feature-demos/sitewide-content/.astro/collections/theme.schema.json +0 -755
  254. package/examples/feature-demos/sitewide-content/.astro/content-assets.mjs +0 -1
  255. package/examples/feature-demos/sitewide-content/.astro/content-modules.mjs +0 -1
  256. package/examples/feature-demos/sitewide-content/.astro/content.d.ts +0 -183
  257. package/examples/feature-demos/sitewide-content/.astro/data-store.json +0 -1
  258. package/examples/feature-demos/sitewide-content/.astro/dev.json +0 -13
  259. package/examples/feature-demos/sitewide-content/.astro/dev.log +0 -18
  260. package/examples/feature-demos/sitewide-content/.astro/settings.json +0 -5
  261. package/examples/feature-demos/sitewide-content/.astro/types.d.ts +0 -2
  262. package/examples/feature-demos/sitewide-content/README.md +0 -26
  263. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/_metadata.json +0 -74
  264. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro-B3JHJyiv.js +0 -434
  265. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro-B3JHJyiv.js.map +0 -1
  266. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro_n_aria-query.js +0 -6390
  267. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro_n_aria-query.js.map +0 -1
  268. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro_n_axobject-query.js +0 -2702
  269. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro_n_axobject-query.js.map +0 -1
  270. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro_n_html-escaper.js +0 -65
  271. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro_n_html-escaper.js.map +0 -1
  272. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +0 -455
  273. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +0 -1
  274. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/audit-C3SVr7dT.js +0 -1534
  275. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/audit-C3SVr7dT.js.map +0 -1
  276. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/highlight-BLtvqtzO.js +0 -48
  277. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/highlight-BLtvqtzO.js.map +0 -1
  278. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/icons-C92i61-2.js +0 -42
  279. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/icons-C92i61-2.js.map +0 -1
  280. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/package.json +0 -3
  281. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +0 -4
  282. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/settings-v5GgKks_.js +0 -190
  283. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/settings-v5GgKks_.js.map +0 -1
  284. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/toolbar-BFvJVp26.js +0 -458
  285. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/toolbar-BFvJVp26.js.map +0 -1
  286. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/ui-library-Cd35oAvo.js +0 -1016
  287. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/ui-library-Cd35oAvo.js.map +0 -1
  288. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/window-vo3A6TMn.js +0 -32
  289. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/window-vo3A6TMn.js.map +0 -1
  290. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/xray-DwhzGckb.js +0 -111
  291. package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/xray-DwhzGckb.js.map +0 -1
  292. package/examples/feature-demos/sitewide-content/site/.norna/generated-images.json +0 -1
  293. package/examples/feature-demos/sitewide-content/site/config.yaml +0 -1
  294. package/examples/feature-demos/sitewide-content/site/pages/000-home/content.md +0 -17
  295. package/examples/feature-demos/sitewide-content/site/pages/010-logo-and-navigation/content.md +0 -17
  296. package/examples/feature-demos/sitewide-content/site/pages/020-notices/content.md +0 -23
  297. package/examples/feature-demos/sitewide-content/site/pages/030-footer/content.md +0 -18
  298. package/examples/feature-demos/sitewide-content/site/public/logo.svg +0 -6
  299. package/examples/feature-demos/sitewide-content/site/public/robots.txt +0 -2
  300. package/examples/feature-demos/sitewide-content/site/sitewide-content.yaml +0 -14
  301. package/examples/feature-demos/sitewide-content/site/theme.yaml +0 -1
  302. package/examples/feature-demos/theme-preset-documentation/README.md +0 -16
  303. package/examples/feature-demos/theme-preset-documentation/site/.norna/generated-images.json +0 -74
  304. package/examples/feature-demos/theme-preset-documentation/site/config.yaml +0 -1
  305. package/examples/feature-demos/theme-preset-documentation/site/pages/000-home/content.md +0 -30
  306. package/examples/feature-demos/theme-preset-documentation/site/pages/000-home/images/black-schnauzer.jpg +0 -0
  307. package/examples/feature-demos/theme-preset-documentation/site/pages/000-home/images/retriever-portrait.jpg +0 -0
  308. package/examples/feature-demos/theme-preset-documentation/site/pages/000-home/images/schnauzer-portrait.jpg +0 -0
  309. package/examples/feature-demos/theme-preset-documentation/site/public/robots.txt +0 -2
  310. package/examples/feature-demos/theme-preset-documentation/site/sitewide-content.yaml +0 -3
  311. package/examples/feature-demos/theme-preset-documentation/site/theme.yaml +0 -1
  312. package/examples/feature-demos/theme-preset-portfolio/README.md +0 -16
  313. package/examples/feature-demos/theme-preset-portfolio/site/.norna/generated-images.json +0 -74
  314. package/examples/feature-demos/theme-preset-portfolio/site/config.yaml +0 -1
  315. package/examples/feature-demos/theme-preset-portfolio/site/pages/000-home/content.md +0 -31
  316. package/examples/feature-demos/theme-preset-portfolio/site/pages/000-home/images/puppy-portrait.jpg +0 -0
  317. package/examples/feature-demos/theme-preset-portfolio/site/pages/000-home/images/shepherd-portrait.jpg +0 -0
  318. package/examples/feature-demos/theme-preset-portfolio/site/pages/000-home/images/white-puppy.jpg +0 -0
  319. package/examples/feature-demos/theme-preset-portfolio/site/public/robots.txt +0 -2
  320. package/examples/feature-demos/theme-preset-portfolio/site/sitewide-content.yaml +0 -3
  321. package/examples/feature-demos/theme-preset-portfolio/site/theme.yaml +0 -1
  322. package/examples/feature-demos/theme-preset-project/.astro/collections/routeTheme.schema.json +0 -746
  323. package/examples/feature-demos/theme-preset-project/.astro/collections/site.schema.json +0 -59
  324. package/examples/feature-demos/theme-preset-project/.astro/collections/sitewide.schema.json +0 -138
  325. package/examples/feature-demos/theme-preset-project/.astro/collections/theme.schema.json +0 -755
  326. package/examples/feature-demos/theme-preset-project/.astro/content-assets.mjs +0 -4
  327. package/examples/feature-demos/theme-preset-project/.astro/content-modules.mjs +0 -1
  328. package/examples/feature-demos/theme-preset-project/.astro/content.d.ts +0 -183
  329. package/examples/feature-demos/theme-preset-project/.astro/data-store.json +0 -1
  330. package/examples/feature-demos/theme-preset-project/.astro/dev-local.json +0 -8
  331. package/examples/feature-demos/theme-preset-project/.astro/dev.json +0 -15
  332. package/examples/feature-demos/theme-preset-project/.astro/dev.log +0 -164
  333. package/examples/feature-demos/theme-preset-project/.astro/settings.json +0 -5
  334. package/examples/feature-demos/theme-preset-project/.astro/types.d.ts +0 -2
  335. package/examples/feature-demos/theme-preset-project/README.md +0 -16
  336. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/_metadata.json +0 -80
  337. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/astro-D_GIpJIE.js +0 -434
  338. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/astro-D_GIpJIE.js.map +0 -1
  339. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/astro_n_aria-query.js +0 -6390
  340. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/astro_n_aria-query.js.map +0 -1
  341. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/astro_n_axobject-query.js +0 -2702
  342. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/astro_n_axobject-query.js.map +0 -1
  343. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/astro_n_html-escaper.js +0 -65
  344. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/astro_n_html-escaper.js.map +0 -1
  345. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +0 -455
  346. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +0 -1
  347. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/audit-DM9Hkl5B.js +0 -1534
  348. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/audit-DM9Hkl5B.js.map +0 -1
  349. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/embla-carousel.js +0 -1389
  350. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/embla-carousel.js.map +0 -1
  351. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/highlight-DnZ1ULOL.js +0 -48
  352. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/highlight-DnZ1ULOL.js.map +0 -1
  353. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/icons-RyebKi45.js +0 -42
  354. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/icons-RyebKi45.js.map +0 -1
  355. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/package.json +0 -3
  356. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +0 -4
  357. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/settings-DaU-gNnK.js +0 -190
  358. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/settings-DaU-gNnK.js.map +0 -1
  359. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/toolbar-63PA619H.js +0 -458
  360. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/toolbar-63PA619H.js.map +0 -1
  361. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/ui-library--bkcNSYz.js +0 -1016
  362. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/ui-library--bkcNSYz.js.map +0 -1
  363. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/window-jPkpKVcy.js +0 -32
  364. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/window-jPkpKVcy.js.map +0 -1
  365. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/xray-BXCHpEJP.js +0 -111
  366. package/examples/feature-demos/theme-preset-project/node_modules/.vite/deps/xray-BXCHpEJP.js.map +0 -1
  367. package/examples/feature-demos/theme-preset-project/site/.norna/generated-images.json +0 -74
  368. package/examples/feature-demos/theme-preset-project/site/config.yaml +0 -1
  369. package/examples/feature-demos/theme-preset-project/site/pages/000-home/content.md +0 -31
  370. package/examples/feature-demos/theme-preset-project/site/pages/000-home/images/boxer-portrait.jpg +0 -0
  371. package/examples/feature-demos/theme-preset-project/site/pages/000-home/images/grass-puppy.jpg +0 -0
  372. package/examples/feature-demos/theme-preset-project/site/pages/000-home/images/grey-street-dog.jpg +0 -0
  373. package/examples/feature-demos/theme-preset-project/site/public/robots.txt +0 -2
  374. package/examples/feature-demos/theme-preset-project/site/sitewide-content.yaml +0 -3
  375. package/examples/feature-demos/theme-preset-project/site/theme.yaml +0 -1
  376. package/examples/feature-demos/theme-preset-statement/README.md +0 -16
  377. package/examples/feature-demos/theme-preset-statement/site/.norna/generated-images.json +0 -74
  378. package/examples/feature-demos/theme-preset-statement/site/config.yaml +0 -1
  379. package/examples/feature-demos/theme-preset-statement/site/pages/000-home/content.md +0 -31
  380. package/examples/feature-demos/theme-preset-statement/site/pages/000-home/images/black-pedigree.jpg +0 -0
  381. package/examples/feature-demos/theme-preset-statement/site/pages/000-home/images/sheepdog-grass.jpg +0 -0
  382. package/examples/feature-demos/theme-preset-statement/site/pages/000-home/images/sheepdog-wide.jpg +0 -0
  383. package/examples/feature-demos/theme-preset-statement/site/public/robots.txt +0 -2
  384. package/examples/feature-demos/theme-preset-statement/site/sitewide-content.yaml +0 -3
  385. package/examples/feature-demos/theme-preset-statement/site/theme.yaml +0 -1
  386. package/fixtures/basic/site/.norna/generated-images.json +0 -1
  387. package/fixtures/basic/site/config.yaml +0 -1
  388. package/fixtures/basic/site/pages/000-home/content.md +0 -14
  389. package/fixtures/basic/site/pages/000-home/images/.gitkeep +0 -1
  390. package/fixtures/basic/site/public/robots.txt +0 -2
  391. package/fixtures/basic/site/sitewide-content.yaml +0 -3
  392. package/fixtures/basic/site/theme.yaml +0 -1
  393. package/fixtures/content-model-v2/.astro/collections/site.schema.json +0 -776
  394. package/fixtures/content-model-v2/.astro/collections/theme.schema.json +0 -668
  395. package/fixtures/content-model-v2/.astro/content-assets.mjs +0 -4
  396. package/fixtures/content-model-v2/.astro/content-modules.mjs +0 -1
  397. package/fixtures/content-model-v2/.astro/content.d.ts +0 -175
  398. package/fixtures/content-model-v2/.astro/data-store.json +0 -1
  399. package/fixtures/content-model-v2/.astro/dev-local.json +0 -8
  400. package/fixtures/content-model-v2/.astro/dev.json +0 -13
  401. package/fixtures/content-model-v2/.astro/dev.log +0 -231
  402. package/fixtures/content-model-v2/.astro/settings.json +0 -5
  403. package/fixtures/content-model-v2/.astro/types.d.ts +0 -2
  404. package/fixtures/content-model-v2/dist/_astro/GalleryGrid.astro_astro_type_script_index_0_lang.B0kxDyGz.js +0 -1
  405. package/fixtures/content-model-v2/dist/_astro/SitePage.BbfZub4g.css +0 -1
  406. package/fixtures/content-model-v2/dist/guide/index.html +0 -3
  407. package/fixtures/content-model-v2/dist/images/generated/intro/detail-27930b14-480.webp +0 -0
  408. package/fixtures/content-model-v2/dist/images/generated/intro/detail-27930b14-768.webp +0 -0
  409. package/fixtures/content-model-v2/dist/images/generated/intro/detail-27930b14-971.webp +0 -0
  410. package/fixtures/content-model-v2/dist/images/generated/intro/duplicate-49c98f6c-480.webp +0 -0
  411. package/fixtures/content-model-v2/dist/images/generated/intro/duplicate-49c98f6c-768.webp +0 -0
  412. package/fixtures/content-model-v2/dist/images/generated/intro/duplicate-49c98f6c-971.webp +0 -0
  413. package/fixtures/content-model-v2/dist/images/generated/intro/hero-6d588a83-480.webp +0 -0
  414. package/fixtures/content-model-v2/dist/images/generated/intro/hero-6d588a83-768.webp +0 -0
  415. package/fixtures/content-model-v2/dist/images/generated/intro/hero-6d588a83-971.webp +0 -0
  416. package/fixtures/content-model-v2/dist/images/generated/plain/image-3770d763-1080.webp +0 -0
  417. package/fixtures/content-model-v2/dist/images/generated/plain/image-3770d763-1440.webp +0 -0
  418. package/fixtures/content-model-v2/dist/images/generated/plain/image-3770d763-1600.webp +0 -0
  419. package/fixtures/content-model-v2/dist/images/generated/plain/image-3770d763-480.webp +0 -0
  420. package/fixtures/content-model-v2/dist/images/generated/plain/image-3770d763-768.webp +0 -0
  421. package/fixtures/content-model-v2/dist/images/generated/routes/010-guide/images/intro/duplicate-5cf614d2-480.webp +0 -0
  422. package/fixtures/content-model-v2/dist/images/generated/routes/010-guide/images/intro/duplicate-5cf614d2-768.webp +0 -0
  423. package/fixtures/content-model-v2/dist/images/generated/routes/010-guide/images/intro/duplicate-5cf614d2-960.webp +0 -0
  424. package/fixtures/content-model-v2/dist/images/generated/timed/slide-one-3770d763-1080.webp +0 -0
  425. package/fixtures/content-model-v2/dist/images/generated/timed/slide-one-3770d763-1440.webp +0 -0
  426. package/fixtures/content-model-v2/dist/images/generated/timed/slide-one-3770d763-1600.webp +0 -0
  427. package/fixtures/content-model-v2/dist/images/generated/timed/slide-one-3770d763-480.webp +0 -0
  428. package/fixtures/content-model-v2/dist/images/generated/timed/slide-one-3770d763-768.webp +0 -0
  429. package/fixtures/content-model-v2/dist/images/generated/timed/slide-three-5bdec65e-1080.webp +0 -0
  430. package/fixtures/content-model-v2/dist/images/generated/timed/slide-three-5bdec65e-1440.webp +0 -0
  431. package/fixtures/content-model-v2/dist/images/generated/timed/slide-three-5bdec65e-1600.webp +0 -0
  432. package/fixtures/content-model-v2/dist/images/generated/timed/slide-three-5bdec65e-480.webp +0 -0
  433. package/fixtures/content-model-v2/dist/images/generated/timed/slide-three-5bdec65e-768.webp +0 -0
  434. package/fixtures/content-model-v2/dist/images/generated/timed/slide-two-26c08aee-1080.webp +0 -0
  435. package/fixtures/content-model-v2/dist/images/generated/timed/slide-two-26c08aee-1440.webp +0 -0
  436. package/fixtures/content-model-v2/dist/images/generated/timed/slide-two-26c08aee-1600.webp +0 -0
  437. package/fixtures/content-model-v2/dist/images/generated/timed/slide-two-26c08aee-480.webp +0 -0
  438. package/fixtures/content-model-v2/dist/images/generated/timed/slide-two-26c08aee-768.webp +0 -0
  439. package/fixtures/content-model-v2/dist/index.html +0 -8
  440. package/fixtures/content-model-v2/node_modules/.vite/deps/_metadata.json +0 -80
  441. package/fixtures/content-model-v2/node_modules/.vite/deps/astro-D_GIpJIE.js +0 -434
  442. package/fixtures/content-model-v2/node_modules/.vite/deps/astro-D_GIpJIE.js.map +0 -1
  443. package/fixtures/content-model-v2/node_modules/.vite/deps/astro_n_aria-query.js +0 -6390
  444. package/fixtures/content-model-v2/node_modules/.vite/deps/astro_n_aria-query.js.map +0 -1
  445. package/fixtures/content-model-v2/node_modules/.vite/deps/astro_n_axobject-query.js +0 -2702
  446. package/fixtures/content-model-v2/node_modules/.vite/deps/astro_n_axobject-query.js.map +0 -1
  447. package/fixtures/content-model-v2/node_modules/.vite/deps/astro_n_html-escaper.js +0 -65
  448. package/fixtures/content-model-v2/node_modules/.vite/deps/astro_n_html-escaper.js.map +0 -1
  449. package/fixtures/content-model-v2/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +0 -455
  450. package/fixtures/content-model-v2/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +0 -1
  451. package/fixtures/content-model-v2/node_modules/.vite/deps/audit-DM9Hkl5B.js +0 -1534
  452. package/fixtures/content-model-v2/node_modules/.vite/deps/audit-DM9Hkl5B.js.map +0 -1
  453. package/fixtures/content-model-v2/node_modules/.vite/deps/embla-carousel.js +0 -1389
  454. package/fixtures/content-model-v2/node_modules/.vite/deps/embla-carousel.js.map +0 -1
  455. package/fixtures/content-model-v2/node_modules/.vite/deps/highlight-DnZ1ULOL.js +0 -48
  456. package/fixtures/content-model-v2/node_modules/.vite/deps/highlight-DnZ1ULOL.js.map +0 -1
  457. package/fixtures/content-model-v2/node_modules/.vite/deps/icons-RyebKi45.js +0 -42
  458. package/fixtures/content-model-v2/node_modules/.vite/deps/icons-RyebKi45.js.map +0 -1
  459. package/fixtures/content-model-v2/node_modules/.vite/deps/package.json +0 -3
  460. package/fixtures/content-model-v2/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +0 -4
  461. package/fixtures/content-model-v2/node_modules/.vite/deps/settings-DaU-gNnK.js +0 -190
  462. package/fixtures/content-model-v2/node_modules/.vite/deps/settings-DaU-gNnK.js.map +0 -1
  463. package/fixtures/content-model-v2/node_modules/.vite/deps/toolbar-63PA619H.js +0 -458
  464. package/fixtures/content-model-v2/node_modules/.vite/deps/toolbar-63PA619H.js.map +0 -1
  465. package/fixtures/content-model-v2/node_modules/.vite/deps/ui-library--bkcNSYz.js +0 -1016
  466. package/fixtures/content-model-v2/node_modules/.vite/deps/ui-library--bkcNSYz.js.map +0 -1
  467. package/fixtures/content-model-v2/node_modules/.vite/deps/window-jPkpKVcy.js +0 -32
  468. package/fixtures/content-model-v2/node_modules/.vite/deps/window-jPkpKVcy.js.map +0 -1
  469. package/fixtures/content-model-v2/node_modules/.vite/deps/xray-BXCHpEJP.js +0 -111
  470. package/fixtures/content-model-v2/node_modules/.vite/deps/xray-BXCHpEJP.js.map +0 -1
  471. package/fixtures/content-model-v2/site/.norna/generated-images.json +0 -210
  472. package/fixtures/content-model-v2/site/config.yaml +0 -1
  473. package/fixtures/content-model-v2/site/pages/000-home/content.md +0 -79
  474. package/fixtures/content-model-v2/site/pages/000-home/images/adopt.svg +0 -9
  475. package/fixtures/content-model-v2/site/pages/000-home/images/detail.jpg +0 -0
  476. package/fixtures/content-model-v2/site/pages/000-home/images/duplicate.jpg +0 -0
  477. package/fixtures/content-model-v2/site/pages/000-home/images/foster.svg +0 -12
  478. package/fixtures/content-model-v2/site/pages/000-home/images/hero.jpg +0 -0
  479. package/fixtures/content-model-v2/site/pages/000-home/images/image.jpg +0 -0
  480. package/fixtures/content-model-v2/site/pages/000-home/images/slide-one.jpg +0 -0
  481. package/fixtures/content-model-v2/site/pages/000-home/images/slide-three.jpg +0 -0
  482. package/fixtures/content-model-v2/site/pages/000-home/images/slide-two.jpg +0 -0
  483. package/fixtures/content-model-v2/site/pages/010-guide/content.md +0 -18
  484. package/fixtures/content-model-v2/site/pages/010-guide/images/duplicate.jpg +0 -0
  485. package/fixtures/content-model-v2/site/theme.yaml +0 -4
  486. package/fixtures/nested-pages/.astro/collections/site.schema.json +0 -35
  487. package/fixtures/nested-pages/.astro/collections/sitewide.schema.json +0 -97
  488. package/fixtures/nested-pages/.astro/collections/theme.schema.json +0 -844
  489. package/fixtures/nested-pages/.astro/content-assets.mjs +0 -4
  490. package/fixtures/nested-pages/.astro/content-modules.mjs +0 -1
  491. package/fixtures/nested-pages/.astro/content.d.ts +0 -183
  492. package/fixtures/nested-pages/.astro/data-store.json +0 -1
  493. package/fixtures/nested-pages/.astro/dev-local.json +0 -8
  494. package/fixtures/nested-pages/.astro/dev.log +0 -1128
  495. package/fixtures/nested-pages/.astro/settings.json +0 -5
  496. package/fixtures/nested-pages/.astro/types.d.ts +0 -2
  497. package/fixtures/nested-pages/node_modules/.vite/deps/_metadata.json +0 -74
  498. package/fixtures/nested-pages/node_modules/.vite/deps/astro-D_GIpJIE.js +0 -434
  499. package/fixtures/nested-pages/node_modules/.vite/deps/astro-D_GIpJIE.js.map +0 -1
  500. package/fixtures/nested-pages/node_modules/.vite/deps/astro_n_aria-query.js +0 -6390
  501. package/fixtures/nested-pages/node_modules/.vite/deps/astro_n_aria-query.js.map +0 -1
  502. package/fixtures/nested-pages/node_modules/.vite/deps/astro_n_axobject-query.js +0 -2702
  503. package/fixtures/nested-pages/node_modules/.vite/deps/astro_n_axobject-query.js.map +0 -1
  504. package/fixtures/nested-pages/node_modules/.vite/deps/astro_n_html-escaper.js +0 -65
  505. package/fixtures/nested-pages/node_modules/.vite/deps/astro_n_html-escaper.js.map +0 -1
  506. package/fixtures/nested-pages/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +0 -455
  507. package/fixtures/nested-pages/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +0 -1
  508. package/fixtures/nested-pages/node_modules/.vite/deps/audit-DM9Hkl5B.js +0 -1534
  509. package/fixtures/nested-pages/node_modules/.vite/deps/audit-DM9Hkl5B.js.map +0 -1
  510. package/fixtures/nested-pages/node_modules/.vite/deps/highlight-DnZ1ULOL.js +0 -48
  511. package/fixtures/nested-pages/node_modules/.vite/deps/highlight-DnZ1ULOL.js.map +0 -1
  512. package/fixtures/nested-pages/node_modules/.vite/deps/icons-RyebKi45.js +0 -42
  513. package/fixtures/nested-pages/node_modules/.vite/deps/icons-RyebKi45.js.map +0 -1
  514. package/fixtures/nested-pages/node_modules/.vite/deps/package.json +0 -3
  515. package/fixtures/nested-pages/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +0 -4
  516. package/fixtures/nested-pages/node_modules/.vite/deps/settings-DaU-gNnK.js +0 -190
  517. package/fixtures/nested-pages/node_modules/.vite/deps/settings-DaU-gNnK.js.map +0 -1
  518. package/fixtures/nested-pages/node_modules/.vite/deps/toolbar-63PA619H.js +0 -458
  519. package/fixtures/nested-pages/node_modules/.vite/deps/toolbar-63PA619H.js.map +0 -1
  520. package/fixtures/nested-pages/node_modules/.vite/deps/ui-library--bkcNSYz.js +0 -1016
  521. package/fixtures/nested-pages/node_modules/.vite/deps/ui-library--bkcNSYz.js.map +0 -1
  522. package/fixtures/nested-pages/node_modules/.vite/deps/window-jPkpKVcy.js +0 -32
  523. package/fixtures/nested-pages/node_modules/.vite/deps/window-jPkpKVcy.js.map +0 -1
  524. package/fixtures/nested-pages/node_modules/.vite/deps/xray-BXCHpEJP.js +0 -111
  525. package/fixtures/nested-pages/node_modules/.vite/deps/xray-BXCHpEJP.js.map +0 -1
  526. package/fixtures/nested-pages/site/.norna/generated-images.json +0 -10
  527. package/fixtures/nested-pages/site/config.yaml +0 -3
  528. package/fixtures/nested-pages/site/pages/000-home/content.md +0 -15
  529. package/fixtures/nested-pages/site/pages/010-guides/content.md +0 -15
  530. package/fixtures/nested-pages/site/pages/010-guides/pages/010-installation/content.md +0 -21
  531. package/fixtures/nested-pages/site/pages/010-guides/pages/010-installation/images/diagram.svg +0 -21
  532. package/fixtures/nested-pages/site/pages/010-guides/pages/010-installation/pages/010-macos/content.md +0 -18
  533. package/fixtures/nested-pages/site/pages/010-guides/pages/010-installation/theme.yaml +0 -2
  534. package/fixtures/nested-pages/site/pages/010-guides/pages/020-workflows/content.md +0 -11
  535. package/fixtures/nested-pages/site/pages/010-guides/pages/030-release-notes/content.md +0 -9
  536. package/fixtures/nested-pages/site/pages/010-guides/pages/040-configuration/content.md +0 -3
  537. package/fixtures/nested-pages/site/pages/010-guides/pages/050-content/content.md +0 -3
  538. package/fixtures/nested-pages/site/pages/010-guides/pages/060-themes/content.md +0 -3
  539. package/fixtures/nested-pages/site/pages/010-guides/pages/070-images/content.md +0 -3
  540. package/fixtures/nested-pages/site/pages/010-guides/pages/080-cards/content.md +0 -3
  541. package/fixtures/nested-pages/site/pages/010-guides/pages/090-notes/content.md +0 -3
  542. package/fixtures/nested-pages/site/pages/010-guides/pages/100-validation/content.md +0 -3
  543. package/fixtures/nested-pages/site/pages/010-guides/pages/110-synchronization/content.md +0 -3
  544. package/fixtures/nested-pages/site/pages/010-guides/pages/120-building/content.md +0 -3
  545. package/fixtures/nested-pages/site/pages/010-guides/pages/130-publishing/content.md +0 -3
  546. package/fixtures/nested-pages/site/pages/010-guides/pages/140-troubleshooting/content.md +0 -3
  547. package/fixtures/nested-pages/site/pages/010-guides/pages/150-migration/content.md +0 -3
  548. package/fixtures/nested-pages/site/pages/010-guides/pages/160-accessibility/content.md +0 -3
  549. package/fixtures/nested-pages/site/pages/010-guides/pages/170-performance/content.md +0 -3
  550. package/fixtures/nested-pages/site/pages/010-guides/pages/180-security/content.md +0 -3
  551. package/fixtures/nested-pages/site/pages/010-guides/pages/190-localization/content.md +0 -3
  552. package/fixtures/nested-pages/site/pages/010-guides/pages/200-upgrading/content.md +0 -3
  553. package/fixtures/nested-pages/site/pages/010-guides/pages/210-faq/content.md +0 -3
  554. package/fixtures/nested-pages/site/pages/010-guides/theme.yaml +0 -7
  555. package/fixtures/nested-pages/site/pages/020-reference/content.md +0 -10
  556. package/fixtures/nested-pages/site/pages/020-reference/pages/010-installation/content.md +0 -11
  557. package/fixtures/nested-pages/site/theme.yaml +0 -1
  558. package/scripts/build-pages.mjs +0 -69
  559. package/scripts/lib/example-sites.mjs +0 -49
  560. package/scripts/lib/frontmatter-yaml.mjs +0 -113
  561. package/scripts/lib/git-status.mjs +0 -7
  562. package/scripts/release.mjs +0 -153
  563. package/scripts/test-banner-visibility.mjs +0 -80
  564. package/scripts/test-ci-lockfile.mjs +0 -65
  565. package/scripts/test-cli-discovery.mjs +0 -124
  566. package/scripts/test-client-javascript.mjs +0 -225
  567. package/scripts/test-content-check.mjs +0 -621
  568. package/scripts/test-content-model-v2.mjs +0 -1693
  569. package/scripts/test-documentation.mjs +0 -189
  570. package/scripts/test-editor-language-service.mjs +0 -305
  571. package/scripts/test-engine-commands.mjs +0 -204
  572. package/scripts/test-examples.mjs +0 -53
  573. package/scripts/test-heading-ids.mjs +0 -68
  574. package/scripts/test-navigation-model.mjs +0 -63
  575. package/scripts/test-navigation-preview.mjs +0 -108
  576. package/scripts/test-navigation.mjs +0 -157
  577. package/scripts/test-nested-pages.mjs +0 -125
  578. package/scripts/test-package-check.mjs +0 -500
  579. package/scripts/test-page-model.mjs +0 -108
  580. package/scripts/test-project-config.mjs +0 -139
  581. package/scripts/test-schemas.mjs +0 -169
  582. package/scripts/test-site-public.mjs +0 -85
  583. package/scripts/test-theme-presets.mjs +0 -188
  584. package/src/components/SiteNotesScript.astro +0 -43
  585. package/starters/project/.github/workflows/deploy.yml +0 -64
  586. package/starters/project/README.md +0 -84
  587. package/starters/project/package-lock.json +0 -4296
  588. package/starters/project/package.json +0 -37
  589. package/starters/project/site/config.yaml +0 -1
  590. package/starters/project/site/pages/000-home/content.md +0 -119
  591. package/starters/project/site/pages/010-guide/content.md +0 -80
  592. package/starters/project/site/public/robots.txt +0 -2
  593. package/starters/project/site/sitewide-content.yaml +0 -3
  594. package/starters/project/site/theme.yaml +0 -1
  595. 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
- });