@janga/norna 0.7.1 → 0.7.3

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 (168) hide show
  1. package/README.md +32 -18
  2. package/astro.config.mjs +2 -0
  3. package/bin/norna-cli.mjs +175 -0
  4. package/bin/norna.mjs +149 -150
  5. package/docs/README.md +41 -22
  6. package/docs/commands.md +25 -12
  7. package/docs/configuration.md +38 -135
  8. package/docs/content.md +108 -265
  9. package/docs/{command-organization.md → design/command-organization.md} +64 -60
  10. package/docs/{site-examples-structure-note.md → design/site-examples-structure.md} +19 -24
  11. package/docs/engine-development.md +35 -7
  12. package/docs/getting-started.md +55 -26
  13. package/docs/images-and-metadata.md +1 -1
  14. package/docs/local-development.md +14 -1
  15. package/docs/publishing.md +24 -1
  16. package/docs/routes.md +90 -0
  17. package/docs/site-structure.md +18 -10
  18. package/docs/theme.md +292 -0
  19. package/docs/typography.md +144 -0
  20. package/examples/dog-gallery/.astro/collections/site.schema.json +802 -0
  21. package/examples/dog-gallery/.astro/collections/theme.schema.json +714 -0
  22. package/examples/dog-gallery/.astro/content-assets.mjs +4 -0
  23. package/examples/dog-gallery/.astro/content-modules.mjs +1 -0
  24. package/examples/dog-gallery/.astro/content.d.ts +175 -0
  25. package/examples/dog-gallery/.astro/data-store.json +1 -0
  26. package/examples/dog-gallery/.astro/dev-local.json +8 -0
  27. package/examples/dog-gallery/.astro/dev.json +13 -0
  28. package/examples/dog-gallery/.astro/dev.log +49 -0
  29. package/examples/dog-gallery/.astro/settings.json +5 -0
  30. package/examples/dog-gallery/.astro/types.d.ts +2 -0
  31. package/examples/dog-gallery/dist/_astro/GalleryGrid.astro_astro_type_script_index_0_lang.B0kxDyGz.js +1 -0
  32. package/examples/dog-gallery/dist/_astro/SitePage.BFHZm5UG.css +1 -0
  33. package/examples/dog-gallery/dist/dog-care/index.html +15 -0
  34. package/examples/dog-gallery/dist/favicon.svg +7 -0
  35. package/examples/dog-gallery/dist/images/generated/black-dogs/black-puppy-meadow-7255e3d9-1080.webp +0 -0
  36. package/examples/dog-gallery/dist/images/generated/black-dogs/black-puppy-meadow-7255e3d9-1440.webp +0 -0
  37. package/examples/dog-gallery/dist/images/generated/black-dogs/black-puppy-meadow-7255e3d9-1536.webp +0 -0
  38. package/examples/dog-gallery/dist/images/generated/black-dogs/black-puppy-meadow-7255e3d9-480.webp +0 -0
  39. package/examples/dog-gallery/dist/images/generated/black-dogs/black-puppy-meadow-7255e3d9-768.webp +0 -0
  40. package/examples/dog-gallery/dist/images/generated/black-dogs/photo-of-a-black-dog-5cf614d2-480.webp +0 -0
  41. package/examples/dog-gallery/dist/images/generated/black-dogs/photo-of-a-black-dog-5cf614d2-768.webp +0 -0
  42. package/examples/dog-gallery/dist/images/generated/black-dogs/photo-of-a-black-dog-5cf614d2-960.webp +0 -0
  43. package/examples/dog-gallery/dist/images/generated/brown-dogs/brown-dog-50ef114e-1080.webp +0 -0
  44. package/examples/dog-gallery/dist/images/generated/brown-dogs/brown-dog-50ef114e-1440.webp +0 -0
  45. package/examples/dog-gallery/dist/images/generated/brown-dogs/brown-dog-50ef114e-1920.webp +0 -0
  46. package/examples/dog-gallery/dist/images/generated/brown-dogs/brown-dog-50ef114e-3939.webp +0 -0
  47. package/examples/dog-gallery/dist/images/generated/brown-dogs/brown-dog-50ef114e-480.webp +0 -0
  48. package/examples/dog-gallery/dist/images/generated/brown-dogs/brown-dog-50ef114e-768.webp +0 -0
  49. package/examples/dog-gallery/dist/images/generated/brown-dogs/dog-accompanies-master-7ef804d5-1080.webp +0 -0
  50. package/examples/dog-gallery/dist/images/generated/brown-dogs/dog-accompanies-master-7ef804d5-1440.webp +0 -0
  51. package/examples/dog-gallery/dist/images/generated/brown-dogs/dog-accompanies-master-7ef804d5-1920.webp +0 -0
  52. package/examples/dog-gallery/dist/images/generated/brown-dogs/dog-accompanies-master-7ef804d5-2400.webp +0 -0
  53. package/examples/dog-gallery/dist/images/generated/brown-dogs/dog-accompanies-master-7ef804d5-480.webp +0 -0
  54. package/examples/dog-gallery/dist/images/generated/brown-dogs/dog-accompanies-master-7ef804d5-768.webp +0 -0
  55. package/examples/dog-gallery/dist/images/generated/golden-dogs/golden-retriever-f797fd88-1080.webp +0 -0
  56. package/examples/dog-gallery/dist/images/generated/golden-dogs/golden-retriever-f797fd88-1440.webp +0 -0
  57. package/examples/dog-gallery/dist/images/generated/golden-dogs/golden-retriever-f797fd88-1600.webp +0 -0
  58. package/examples/dog-gallery/dist/images/generated/golden-dogs/golden-retriever-f797fd88-480.webp +0 -0
  59. package/examples/dog-gallery/dist/images/generated/golden-dogs/golden-retriever-f797fd88-768.webp +0 -0
  60. package/examples/dog-gallery/dist/images/generated/golden-dogs/toller-puppy-a2a8fbc0-1080.webp +0 -0
  61. package/examples/dog-gallery/dist/images/generated/golden-dogs/toller-puppy-a2a8fbc0-1440.webp +0 -0
  62. package/examples/dog-gallery/dist/images/generated/golden-dogs/toller-puppy-a2a8fbc0-1920.webp +0 -0
  63. package/examples/dog-gallery/dist/images/generated/golden-dogs/toller-puppy-a2a8fbc0-2576.webp +0 -0
  64. package/examples/dog-gallery/dist/images/generated/golden-dogs/toller-puppy-a2a8fbc0-480.webp +0 -0
  65. package/examples/dog-gallery/dist/images/generated/golden-dogs/toller-puppy-a2a8fbc0-768.webp +0 -0
  66. package/examples/dog-gallery/dist/images/generated/white-dogs/white-cute-dog-077a0689-1080.webp +0 -0
  67. package/examples/dog-gallery/dist/images/generated/white-dogs/white-cute-dog-077a0689-1277.webp +0 -0
  68. package/examples/dog-gallery/dist/images/generated/white-dogs/white-cute-dog-077a0689-480.webp +0 -0
  69. package/examples/dog-gallery/dist/images/generated/white-dogs/white-cute-dog-077a0689-768.webp +0 -0
  70. package/examples/dog-gallery/dist/images/generated/white-dogs/white-puppy-garden-d3ab2ca0-1080.webp +0 -0
  71. package/examples/dog-gallery/dist/images/generated/white-dogs/white-puppy-garden-d3ab2ca0-1440.webp +0 -0
  72. package/examples/dog-gallery/dist/images/generated/white-dogs/white-puppy-garden-d3ab2ca0-1536.webp +0 -0
  73. package/examples/dog-gallery/dist/images/generated/white-dogs/white-puppy-garden-d3ab2ca0-480.webp +0 -0
  74. package/examples/dog-gallery/dist/images/generated/white-dogs/white-puppy-garden-d3ab2ca0-768.webp +0 -0
  75. package/examples/dog-gallery/dist/index.html +43 -0
  76. package/examples/dog-gallery/dist/robots.txt +2 -0
  77. package/examples/dog-gallery/node_modules/.vite/deps/_metadata.json +74 -0
  78. package/examples/dog-gallery/node_modules/.vite/deps/astro-D_GIpJIE.js +434 -0
  79. package/examples/dog-gallery/node_modules/.vite/deps/astro-D_GIpJIE.js.map +1 -0
  80. package/examples/dog-gallery/node_modules/.vite/deps/astro_n_aria-query.js +6390 -0
  81. package/examples/dog-gallery/node_modules/.vite/deps/astro_n_aria-query.js.map +1 -0
  82. package/examples/dog-gallery/node_modules/.vite/deps/astro_n_axobject-query.js +2702 -0
  83. package/examples/dog-gallery/node_modules/.vite/deps/astro_n_axobject-query.js.map +1 -0
  84. package/examples/dog-gallery/node_modules/.vite/deps/astro_n_html-escaper.js +65 -0
  85. package/examples/dog-gallery/node_modules/.vite/deps/astro_n_html-escaper.js.map +1 -0
  86. package/examples/dog-gallery/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +455 -0
  87. package/examples/dog-gallery/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +1 -0
  88. package/examples/dog-gallery/node_modules/.vite/deps/audit-DM9Hkl5B.js +1534 -0
  89. package/examples/dog-gallery/node_modules/.vite/deps/audit-DM9Hkl5B.js.map +1 -0
  90. package/examples/dog-gallery/node_modules/.vite/deps/highlight-DnZ1ULOL.js +48 -0
  91. package/examples/dog-gallery/node_modules/.vite/deps/highlight-DnZ1ULOL.js.map +1 -0
  92. package/examples/dog-gallery/node_modules/.vite/deps/icons-RyebKi45.js +42 -0
  93. package/examples/dog-gallery/node_modules/.vite/deps/icons-RyebKi45.js.map +1 -0
  94. package/examples/dog-gallery/node_modules/.vite/deps/package.json +3 -0
  95. package/examples/dog-gallery/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +4 -0
  96. package/examples/dog-gallery/node_modules/.vite/deps/settings-DaU-gNnK.js +190 -0
  97. package/examples/dog-gallery/node_modules/.vite/deps/settings-DaU-gNnK.js.map +1 -0
  98. package/examples/dog-gallery/node_modules/.vite/deps/toolbar-63PA619H.js +458 -0
  99. package/examples/dog-gallery/node_modules/.vite/deps/toolbar-63PA619H.js.map +1 -0
  100. package/examples/dog-gallery/node_modules/.vite/deps/ui-library--bkcNSYz.js +1016 -0
  101. package/examples/dog-gallery/node_modules/.vite/deps/ui-library--bkcNSYz.js.map +1 -0
  102. package/examples/dog-gallery/node_modules/.vite/deps/window-jPkpKVcy.js +32 -0
  103. package/examples/dog-gallery/node_modules/.vite/deps/window-jPkpKVcy.js.map +1 -0
  104. package/examples/dog-gallery/node_modules/.vite/deps/xray-BXCHpEJP.js +111 -0
  105. package/examples/dog-gallery/node_modules/.vite/deps/xray-BXCHpEJP.js.map +1 -0
  106. package/examples/dog-gallery/site/.norna/generated-images.json +226 -0
  107. package/examples/dog-gallery/site/config.mjs +67 -0
  108. package/examples/dog-gallery/site/content.md +168 -0
  109. package/examples/dog-gallery/site/images/black-dogs/black-puppy-meadow.png +0 -0
  110. package/examples/dog-gallery/site/images/black-dogs/photo-of-a-black-dog.jpg +0 -0
  111. package/examples/dog-gallery/site/images/brown-dogs/brown-dog.jpg +0 -0
  112. package/examples/dog-gallery/site/images/brown-dogs/dog-accompanies-master.jpg +0 -0
  113. package/examples/dog-gallery/site/images/golden-dogs/golden-retriever.jpg +0 -0
  114. package/examples/dog-gallery/site/images/golden-dogs/toller-puppy.jpg +0 -0
  115. package/examples/dog-gallery/site/images/white-dogs/white-cute-dog.jpg +0 -0
  116. package/examples/dog-gallery/site/images/white-dogs/white-puppy-garden.png +0 -0
  117. package/examples/dog-gallery/site/public/favicon.svg +7 -0
  118. package/examples/dog-gallery/site/public/robots.txt +2 -0
  119. package/examples/dog-gallery/site/routes/dog-care/route-content.md +49 -0
  120. package/examples/dog-gallery/site/theme.md +114 -0
  121. package/fixtures/basic/site/config.mjs +3 -22
  122. package/fixtures/basic/site/content.md +1 -1
  123. package/fixtures/basic/site/theme.md +7 -3
  124. package/package.json +10 -9
  125. package/scripts/check-config.mjs +4 -3
  126. package/scripts/deploy-site.mjs +3 -3
  127. package/scripts/dev-local.mjs +64 -15
  128. package/scripts/init-site.mjs +11 -9
  129. package/scripts/lib/frontmatter-yaml.mjs +85 -0
  130. package/scripts/lib/presentation.mjs +10 -11
  131. package/scripts/lib/project-config.mjs +113 -45
  132. package/scripts/lib/site-content.mjs +21 -2
  133. package/scripts/lib/site-paths.mjs +18 -3
  134. package/scripts/lib/theme-config.mjs +34 -0
  135. package/scripts/lib/typography.mjs +103 -23
  136. package/scripts/show-typography.mjs +202 -75
  137. package/scripts/sync-content-sections.mjs +6 -6
  138. package/scripts/test-cli-discovery.mjs +124 -0
  139. package/scripts/test-content-check.mjs +3 -4
  140. package/scripts/test-engine-commands.mjs +20 -6
  141. package/scripts/test-navigation.mjs +10 -6
  142. package/scripts/test-package-check.mjs +40 -11
  143. package/scripts/test-temporary-visibility.mjs +2 -3
  144. package/src/components/SiteNavigation.astro +7 -5
  145. package/src/components/SitePage.astro +5 -3
  146. package/src/components/SiteSection.astro +63 -29
  147. package/src/content.config.ts +64 -2
  148. package/src/layouts/BaseLayout.astro +19 -1
  149. package/src/lib/basePath.ts +21 -0
  150. package/src/lib/generatedImages.ts +8 -4
  151. package/src/lib/sectionContent.ts +6 -1
  152. package/src/lib/sitePublicAssets.ts +8 -1
  153. package/src/styles/global.css +59 -55
  154. package/starters/basic/.github/workflows/deploy.yml +3 -3
  155. package/starters/basic/README.md +30 -14
  156. package/starters/basic/package.json +3 -3
  157. package/starters/basic/site/config.mjs +4 -24
  158. package/starters/basic/site/content.md +44 -9
  159. package/starters/basic/site/theme.md +113 -16
  160. package/starters/project/.github/workflows/deploy.yml +65 -0
  161. package/starters/project/README.md +72 -0
  162. package/starters/project/package-lock.json +4296 -0
  163. package/starters/project/package.json +35 -0
  164. package/starters/project/site/config.mjs +43 -0
  165. package/starters/project/site/content.md +88 -0
  166. package/starters/project/site/public/robots.txt +2 -0
  167. package/starters/project/site/routes/guide/route-content.md +58 -0
  168. package/starters/project/site/theme.md +15 -0
@@ -0,0 +1,168 @@
1
+ ---
2
+ title: Local Dog Example
3
+ description: A small norna example with free dog images.
4
+ navigation:
5
+ label: Dog site!
6
+ order: 10
7
+ sections:
8
+ - id: black-dogs
9
+ presentation:
10
+ typography:
11
+ preset: statement
12
+ overrides:
13
+ headings:
14
+ h2:
15
+ size: xlarge
16
+ gallery:
17
+ - image: photo-of-a-black-dog.jpg
18
+ alt: "A black dog sitting outdoors and looking at the camera."
19
+ caption:
20
+ "Photo of a dog. ContaDeletada2906, CC0 1.0, via Wikimedia Commons."
21
+ - image: black-puppy-meadow.png
22
+ alt:
23
+ "A black puppy standing in a grassy meadow and looking at the camera."
24
+ caption: "AI-generated image."
25
+ - id: summer-dogs
26
+ visible:
27
+ from: "2026-08-01"
28
+ until: "2026-09-01"
29
+ presentation:
30
+ backgroundColor: "#181818"
31
+ typography:
32
+ preset: compact-gallery
33
+ gallery: []
34
+ - id: brown-dogs
35
+ presentation:
36
+ typography:
37
+ preset: text-forward
38
+ gallery:
39
+ - image: brown-dog.jpg
40
+ alt: "A brown dog looking directly at the camera."
41
+ caption: "DogBrown. Frank-3, CC0 1.0, via Wikimedia Commons."
42
+ - image: dog-accompanies-master.jpg
43
+ alt: "A man walks outdoors with a dog at his side."
44
+ caption:
45
+ "Dog accompanies his master. Steve Hillebrand, U.S. Fish and Wildlife
46
+ Service, public domain."
47
+ - id: golden-dogs
48
+ presentation:
49
+ typography:
50
+ overrides:
51
+ headings:
52
+ h2:
53
+ size: large
54
+ h3:
55
+ size: medium
56
+ body:
57
+ lineHeight: 1.58
58
+ paragraphSpacing: 1em
59
+ gallery:
60
+ - image: golden-retriever.jpg
61
+ alt: "A golden retriever standing outdoors."
62
+ caption: "Golden Retriever. Ribo, public domain, via Wikimedia Commons."
63
+ - image: toller-puppy.jpg
64
+ alt:
65
+ "A young Nova Scotia Duck Tolling Retriever puppy sitting on grass."
66
+ caption:
67
+ "Nova Scotia Duck Tolling Retriever puppy. RM, public domain, via
68
+ Wikimedia Commons."
69
+ - id: white-dogs
70
+ presentation:
71
+ typography:
72
+ overrides:
73
+ caption:
74
+ spacing: 0.35em
75
+ gallery:
76
+ - carousel:
77
+ - image: white-cute-dog.jpg
78
+ alt: "A white dog looking toward the camera."
79
+ caption:
80
+ "White cute dog. Neal Ziring, public domain, via Wikimedia
81
+ Commons."
82
+ - image: white-puppy-garden.png
83
+ alt: "A fluffy white puppy standing on grass in a garden."
84
+ caption: "AI-generated image."
85
+ - id: sources
86
+ ---
87
+
88
+ ## Black Dogs {#black-dogs}
89
+
90
+ Black-coated dogs from the local norna development collection. This
91
+ sentence contains [yellow highlighted text]{.highlight} for inline style
92
+ inspection.
93
+
94
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a
95
+ ante venenatis dapibus posuere velit aliquet.
96
+
97
+ ### Coat notes
98
+
99
+ This `###` heading should follow `typography.headings.h3`.
100
+
101
+ #### Small detail
102
+
103
+ This `####` heading should follow `typography.headings.h4`.
104
+
105
+ Curabitur blandit tempus porttitor. Donec id elit non mi porta gravida at eget
106
+ metus. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.
107
+
108
+ ## Summer Dogs {#summer-dogs}
109
+
110
+ This temporary section is visible during August 2026. It is here to demonstrate
111
+ date-controlled sections and [warm alert text]{.alert} in the local dog example.
112
+
113
+ Lorem ipsum dolor sit amet.
114
+
115
+ Vestibulum id ligula porta felis euismod semper. Maecenas faucibus mollis
116
+ interdum. Nullam quis risus eget urna mollis ornare vel eu leo.
117
+
118
+ ## Brown Dogs {#brown-dogs}
119
+
120
+ Brown dogs provide a second image section for local navigation checks.
121
+
122
+ Donec ullamcorper nulla non metus auctor fringilla. Fusce dapibus, tellus ac
123
+ cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit
124
+ amet risus.
125
+
126
+ Maecenas sed diam eget risus varius blandit sit amet non magna. Morbi leo risus,
127
+ porta ac consectetur ac, vestibulum at eros. Sed posuere consectetur est at
128
+ lobortis.
129
+
130
+ ## Golden Dogs {#golden-dogs}
131
+
132
+ Golden-coated retrievers add a third image section and more scrolling depth.
133
+
134
+ Cras justo odio, dapibus ac facilisis in, egestas eget quam.
135
+
136
+ ### Training note
137
+
138
+ This section overrides `headings.h2` and `headings.h3`, while keeping the
139
+ site-wide `headings.h4` style.
140
+
141
+ Nulla vitae elit libero, a pharetra augue. Integer posuere erat a ante venenatis
142
+ dapibus posuere velit aliquet. Aenean eu leo quam. Pellentesque ornare sem
143
+ lacinia quam venenatis vestibulum.
144
+
145
+ ## White Dogs {#white-dogs}
146
+
147
+ A white dog completes the colour-based image sequence.
148
+
149
+ Etiam porta sem malesuada magna mollis euismod. Donec id elit non mi porta
150
+ gravida at eget metus.
151
+
152
+ Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Lorem ipsum
153
+ dolor sit amet, consectetur adipiscing elit. Donec sed odio dui. Vestibulum id
154
+ ligula porta felis euismod semper.
155
+
156
+ ## Sources {#sources}
157
+
158
+ Most images are sourced from Wikimedia Commons pages that list CC0 or
159
+ public-domain licensing:
160
+
161
+ - [Photo of a dog](https://commons.wikimedia.org/wiki/File:Photo_of_a_dog.jpg)
162
+ - [DogBrown](https://commons.wikimedia.org/wiki/File:DogBrown.jpg)
163
+ - [Dog accompanies his master](https://commons.wikimedia.org/wiki/File:Dog_accompanies_his_master.jpg)
164
+ - [Golden Retriever](https://commons.wikimedia.org/wiki/File:Golden_Retriever.jpg)
165
+ - [Toller pup7wks2](https://commons.wikimedia.org/wiki/File:Toller_pup7wks2.JPG)
166
+ - [White cute dog](https://commons.wikimedia.org/wiki/File:White_cute_dog.jpg)
167
+
168
+ The black and white puppy images are AI-generated.
@@ -0,0 +1,7 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
2
+ <rect width="128" height="128" rx="24" fill="#111111" />
3
+ <circle cx="40" cy="43" r="13" fill="#ffffff" />
4
+ <circle cx="64" cy="34" r="13" fill="#ffffff" />
5
+ <circle cx="88" cy="43" r="13" fill="#ffffff" />
6
+ <path fill="#ffffff" d="M31 83c0-18 15-31 33-31s33 13 33 31c0 12-9 18-20 14-7-3-19-3-26 0-11 4-20-2-20-14Z" />
7
+ </svg>
@@ -0,0 +1,2 @@
1
+ User-agent: *
2
+ Allow: /
@@ -0,0 +1,49 @@
1
+ ---
2
+ title: Dog care
3
+ description: A small route for testing multi-page norna navigation.
4
+ navigation:
5
+ label: Dog care
6
+ order: 20
7
+ sections:
8
+ - id: daily-rhythm
9
+ presentation:
10
+ typography:
11
+ overrides:
12
+ headings:
13
+ h2:
14
+ size: large
15
+ - id: calm-training
16
+ - id: media-notes
17
+ ---
18
+
19
+ ## Daily rhythm {#daily-rhythm}
20
+
21
+ Dogs often settle into a home when the day has a clear rhythm. Meals, walks,
22
+ rest and play do not need to be dramatic events, but they become easier to
23
+ understand when they happen in a predictable order.
24
+
25
+ Short routines also make a media page easier to scan. This route exists mainly
26
+ to test how page navigation and site navigation behave together.
27
+
28
+ ### Morning checks
29
+
30
+ This `###` heading tests route-level body subheadings.
31
+
32
+ #### Quiet detail
33
+
34
+ This `####` heading should stay smaller than the `###` above it.
35
+
36
+ ## Calm training {#calm-training}
37
+
38
+ Good training can be quiet. A short session with one clear task is often more
39
+ useful than a long session with many competing signals.
40
+
41
+ The same principle applies to navigation. The page should show where the visitor
42
+ is in the site first, and then offer a simple way to jump within the current
43
+ page.
44
+
45
+ ## Media notes {#media-notes}
46
+
47
+ This section is intentionally text-only. Image routes can be added later, but a
48
+ first navigation test should make it easy to judge menus, active states and
49
+ anchor positions without layout noise from new media.
@@ -0,0 +1,114 @@
1
+ ---
2
+ # norna:start theme-help
3
+ # Site-wide visual defaults. Remove a value to use the engine default.
4
+ # Active values below are ordinary YAML; this marked comment block is only help text.
5
+ #
6
+ # Available structure:
7
+ # navigation:
8
+ # brand: site-wide brand/home text; defaults to the homepage title
9
+ # layout:
10
+ # pageWidth: CSS length, e.g. 2048px
11
+ # gutter:
12
+ # desktop: CSS length or clamp(), e.g. clamp(1.25rem, 4vw, 3rem)
13
+ # mobile: CSS length, e.g. 16px
14
+ # spacing:
15
+ # firstSectionTop:
16
+ # desktop: CSS length or clamp(), e.g. clamp(1.25rem, 2.5vw, 2rem)
17
+ # mobile: CSS length, e.g. 1rem
18
+ # sectionGap:
19
+ # desktop: CSS length or clamp(), e.g. clamp(1.8rem, 3vw, 2.75rem)
20
+ # mobile: CSS length, e.g. 1.65rem
21
+ # subheadingTop:
22
+ # desktop: CSS length, e.g. 1.35rem
23
+ # mobile: CSS length, e.g. 1.1rem
24
+ # gallery:
25
+ # width: CSS length, e.g. 55vw
26
+ # maxAvailableWidthPercent:
27
+ # desktop: number from 1 through 100
28
+ # mobile: number from 1 through 100
29
+ # maxAvailableHeightPercent:
30
+ # desktop: number from 1 through 100
31
+ # mobile: number from 1 through 100
32
+ # typography:
33
+ # fontFamily: CSS font-family stack
34
+ # preset: quiet-gallery | compact-gallery | text-forward | statement
35
+ # overrides:
36
+ # headings:
37
+ # h2:
38
+ # size: small | medium | large | xlarge
39
+ # h3:
40
+ # size: small | medium | large | xlarge
41
+ # h4:
42
+ # size: small | medium | large | xlarge
43
+ # presentation:
44
+ # backgroundColor: quoted hex color, e.g. "#000000"
45
+ # textColor: quoted hex color, e.g. "#f7f4ee"
46
+ # inlineStyles:
47
+ # style-name:
48
+ # color: quoted hex color
49
+ # frame:
50
+ # colors: presentation | theme
51
+ # colors:
52
+ # backgroundColor: quoted hex color
53
+ # textColor: quoted hex color
54
+ # norna:end theme-help
55
+ layout:
56
+ pageWidth: 2048px
57
+ gutter:
58
+ desktop: clamp(1.25rem, 4vw, 3rem)
59
+ mobile: 16px
60
+ spacing:
61
+ firstSectionTop:
62
+ desktop: clamp(1.25rem, 2.5vw, 2rem)
63
+ mobile: 1rem
64
+ sectionGap:
65
+ desktop: clamp(1.8rem, 3vw, 2.75rem)
66
+ mobile: 1.65rem
67
+ finalSectionBottom:
68
+ desktop: clamp(1.8rem, 3vw, 2.75rem)
69
+ mobile: 1.65rem
70
+ sectionHeadingToBody:
71
+ desktop: 0.55rem
72
+ mobile: 0.45rem
73
+ bodyToImages:
74
+ desktop: 1.25rem
75
+ mobile: 1rem
76
+ imageGap:
77
+ desktop: clamp(1.25rem, 2.8vw, 2rem)
78
+ mobile: 1.8rem
79
+ subheadingTop:
80
+ desktop: 1.35rem
81
+ mobile: 1.1rem
82
+ subheadingRuleTop:
83
+ desktop: 0.65rem
84
+ mobile: 0.55rem
85
+ gallery:
86
+ width: 55vw
87
+ maxAvailableWidthPercent:
88
+ desktop: 100
89
+ mobile: 100
90
+ maxAvailableHeightPercent:
91
+ desktop: 74
92
+ mobile: 68
93
+ typography:
94
+ fontFamily: "Arial, 'Helvetica Neue', Helvetica, sans-serif"
95
+ preset: quiet-gallery
96
+ overrides:
97
+ headings:
98
+ h3:
99
+ size: medium
100
+ spacing: 0.7rem
101
+ h4:
102
+ size: small
103
+ spacing: 0.55rem
104
+ presentation:
105
+ backgroundColor: "#550000"
106
+ textColor: "#ffffff"
107
+ inlineStyles:
108
+ highlight:
109
+ color: "#ffd84d"
110
+ alert:
111
+ color: "#ff9a3d"
112
+ frame:
113
+ colors: presentation
114
+ ---
@@ -1,26 +1,7 @@
1
1
  export default {
2
2
  site: {
3
3
  url: 'https://example.com/',
4
- },
5
- layout: {
6
- pageWidth: '1180px',
7
- gutter: {
8
- desktop: 'clamp(1.25rem, 4vw, 3rem)',
9
- mobile: '1rem',
10
- },
11
- },
12
- gallery: {
13
- maxAvailableWidthPercent: {
14
- desktop: 100,
15
- mobile: 100,
16
- },
17
- maxAvailableHeightPercent: {
18
- desktop: 74,
19
- mobile: 68,
20
- },
21
- },
22
- typography: {
23
- fontFamily: "Arial, 'Helvetica Neue', Helvetica, sans-serif",
4
+ basePath: '/',
24
5
  },
25
6
  navigation: {
26
7
  smoothScroll: {
@@ -35,7 +16,7 @@ export default {
35
16
  labels: {
36
17
  skipToContent: 'Skip to content',
37
18
  sectionNavigation: 'Sections',
38
- gallery: 'Gallery',
19
+ gallery: 'Images',
39
20
  },
40
21
  },
41
22
  footer: {
@@ -52,7 +33,7 @@ export default {
52
33
  },
53
34
  },
54
35
  github: {
55
- repo: 'owner/fixture-gallery',
36
+ repo: 'owner/fixture-site',
56
37
  branch: 'main',
57
38
  pagesWorkflow: 'Deploy to GitHub Pages',
58
39
  },
@@ -1,5 +1,5 @@
1
1
  ---
2
- title: Fixture Gallery
2
+ title: Fixture Site
3
3
  description: Minimal cli-gallery fixture site.
4
4
  sections:
5
5
  - id: intro
@@ -1,7 +1,11 @@
1
1
  ---
2
- presentation:
3
- typography:
4
- preset: quiet-gallery
2
+ layout:
3
+ pageWidth: 1180px
4
+ gallery:
5
+ width: 900px
6
+ typography:
7
+ fontFamily: "Arial, 'Helvetica Neue', Helvetica, sans-serif"
8
+ preset: quiet-gallery
5
9
  frame:
6
10
  colors: presentation
7
11
  ---
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@janga/norna",
3
3
  "description": "A command-line publishing workflow for simple static sites.",
4
4
  "type": "module",
5
- "version": "0.7.1",
5
+ "version": "0.7.3",
6
6
  "license": "GPL-3.0-only",
7
7
  "repository": {
8
8
  "type": "git",
@@ -25,6 +25,7 @@
25
25
  "astro.config.mjs",
26
26
  "bin/",
27
27
  "docs/",
28
+ "examples/",
28
29
  "fixtures/",
29
30
  "scripts/",
30
31
  "src/",
@@ -40,8 +41,8 @@
40
41
  "node": ">=22.12.0"
41
42
  },
42
43
  "scripts": {
43
- "dev": "npm run dev:local",
44
- "dev:local": "npm run demo:dev",
44
+ "dev": "npm run dev:local --",
45
+ "dev:local": "node bin/norna.mjs dev:local",
45
46
  "dev:lan": "node bin/norna.mjs dev:lan",
46
47
  "dev:restart": "node bin/norna.mjs dev:restart",
47
48
  "dev:status": "node bin/norna.mjs dev:status",
@@ -50,22 +51,22 @@
50
51
  "config:check": "node bin/norna.mjs config:check",
51
52
  "content:check": "node bin/norna.mjs content:check",
52
53
  "content:sync": "node bin/norna.mjs content:sync",
53
- "typography:presets": "node bin/norna.mjs typography:presets",
54
- "typography:show": "node bin/norna.mjs typography:show",
54
+ "typography:presets": "node bin/norna.mjs typography presets",
55
+ "typography:show": "node bin/norna.mjs typography show",
55
56
  "site:public": "node bin/norna.mjs site:public",
56
57
  "images": "node bin/norna.mjs images",
57
- "build": "npm run demo:build",
58
+ "build": "node bin/norna.mjs build",
58
59
  "build:local": "node bin/norna.mjs build:local",
59
60
  "deploy": "node bin/norna.mjs deploy",
60
61
  "deploy:commit": "node bin/norna.mjs deploy:commit",
61
62
  "deploy:watch": "node bin/norna.mjs deploy:watch",
62
63
  "doctor": "node bin/norna.mjs doctor",
63
- "demo:dev": "node bin/norna.mjs dev:local",
64
- "demo:build": "node bin/norna.mjs build",
65
- "test": "npm run test:content-check && npm run test:site-public && npm run test:engine-commands && npm run test:ci-lockfile && npm run test:temporary-visibility && npm run test:fixture:build && npm run demo:build && npm run package:check",
64
+ "demo:build": "node bin/norna.mjs --site-dir examples/dog-gallery/site build",
65
+ "test": "npm run test:content-check && npm run test:site-public && npm run test:cli-discovery && npm run test:engine-commands && npm run test:ci-lockfile && npm run test:temporary-visibility && npm run test:fixture:build && npm run package:check",
66
66
  "test:fixture:build": "node bin/norna.mjs --site-dir fixtures/basic/site build",
67
67
  "test:content-check": "node scripts/test-content-check.mjs",
68
68
  "test:site-public": "node scripts/test-site-public.mjs",
69
+ "test:cli-discovery": "node scripts/test-cli-discovery.mjs",
69
70
  "test:engine-commands": "node scripts/test-engine-commands.mjs",
70
71
  "test:ci-lockfile": "node scripts/test-ci-lockfile.mjs",
71
72
  "test:temporary-visibility": "node scripts/test-temporary-visibility.mjs",
@@ -20,11 +20,12 @@ try {
20
20
 
21
21
  console.log('Config check passed.');
22
22
  console.log(`Site URL: ${projectConfig.site.url}`);
23
+ console.log(`Base path: ${projectConfig.site.basePath}`);
23
24
  console.log(`Page width: ${projectConfig.layout.pageWidth}`);
24
25
  console.log(`Gutter: desktop ${projectConfig.layout.gutter.desktop}, mobile ${projectConfig.layout.gutter.mobile}`);
25
- console.log(`Gallery width: ${projectConfig.gallery.width}`);
26
- console.log(`Gallery max available width: desktop ${projectConfig.gallery.maxAvailableWidthPercent.desktop}%, mobile ${projectConfig.gallery.maxAvailableWidthPercent.mobile}%`);
27
- console.log(`Gallery max available height: desktop ${projectConfig.gallery.maxAvailableHeightPercent.desktop}%, mobile ${projectConfig.gallery.maxAvailableHeightPercent.mobile}%`);
26
+ console.log(`Image area width: ${projectConfig.gallery.width}`);
27
+ console.log(`Image max available width: desktop ${projectConfig.gallery.maxAvailableWidthPercent.desktop}%, mobile ${projectConfig.gallery.maxAvailableWidthPercent.mobile}%`);
28
+ console.log(`Image max available height: desktop ${projectConfig.gallery.maxAvailableHeightPercent.desktop}%, mobile ${projectConfig.gallery.maxAvailableHeightPercent.mobile}%`);
28
29
  console.log(`Font family: ${projectConfig.typography.fontFamily}`);
29
30
  console.log(`Language: ${projectConfig.locale.lang}`);
30
31
  console.log(`GitHub repo: ${projectConfig.github.repo}`);
@@ -196,7 +196,7 @@ const assertMainBranch = async () => {
196
196
 
197
197
  const assertDeployableStatus = async (entries, expectedImagePaths) => {
198
198
  if (entries.length === 0) {
199
- fail('Refusing to deploy: no changes to commit after the gallery build.');
199
+ fail('Refusing to deploy: no changes to commit after the Norna build.');
200
200
  }
201
201
 
202
202
  const unexpectedUntracked = entries.filter((entry) => (
@@ -206,7 +206,7 @@ const assertDeployableStatus = async (entries, expectedImagePaths) => {
206
206
  if (unexpectedUntracked.length > 0) {
207
207
  fail([
208
208
  'Refusing to deploy: unexpected untracked files are present.',
209
- `Only new referenced gallery images under ${siteImagesLabel}/<section-id>/ or ${siteRoutesLabel}/<route-folder>/images/<section-id>/ are staged automatically.`,
209
+ `Only new referenced images under ${siteImagesLabel}/<section-id>/ or ${siteRoutesLabel}/<route-folder>/images/<section-id>/ are staged automatically.`,
210
210
  ...unexpectedUntracked.map((entry) => `- ${formatEntry(entry)}`),
211
211
  ].join('\n'));
212
212
  }
@@ -331,7 +331,7 @@ const deployCommittedMain = async () => {
331
331
  await assertPushableBranch();
332
332
  await runBuild();
333
333
  await printStatusShort();
334
- await assertCleanWorktree('Refusing to deploy: the gallery build produced uncommitted changes. Commit them before deploying.');
334
+ await assertCleanWorktree('Refusing to deploy: the Norna build produced uncommitted changes. Commit them before deploying.');
335
335
  await fetchRemoteMain();
336
336
 
337
337
  const relation = await assertPushableBranch();
@@ -5,18 +5,26 @@ import { networkInterfaces } from 'node:os';
5
5
  import path from 'node:path';
6
6
  import { promisify } from 'node:util';
7
7
  import { getAstroArgs, runAstroInherit } from './lib/astro-command.mjs';
8
+ import projectConfig from './lib/project-config.mjs';
8
9
  import { astroCacheDir, engineRoot, siteProjectRoot } from './lib/site-paths.mjs';
9
10
 
10
11
  const execFileAsync = promisify(execFile);
11
12
 
12
13
  const port = 4321;
13
- const localHost = 'localhost';
14
- const localUrl = `http://${localHost}:${port}/`;
15
- const probeUrls = [localUrl, `http://127.0.0.1:${port}/`, `http://[::1]:${port}/`];
14
+ const localHost = '127.0.0.1';
15
+ const localUrl = `http://${localHost}:${port}${projectConfig.site.basePath}`;
16
+ const probeUrls = [
17
+ localUrl,
18
+ `http://localhost:${port}${projectConfig.site.basePath}`,
19
+ ];
16
20
  const skipOpen = process.env.WALDE_NO_OPEN === '1';
17
21
  const statePath = path.join(astroCacheDir, 'dev-local.json');
18
22
  const logPath = path.join(astroCacheDir, 'dev.log');
19
- const command = process.argv[2] ?? 'start';
23
+ const args = process.argv.slice(2);
24
+ const knownCommands = new Set(['start', 'lan', 'status', 'logs', 'restart', 'stop']);
25
+ const command = args.find((arg) => knownCommands.has(arg)) ?? args.find((arg) => !arg.startsWith('-')) ?? 'start';
26
+ const shouldKillBlockingPort = args.includes('--kill');
27
+ const shouldFollowLogs = args.includes('--follow');
20
28
 
21
29
  const runAstro = async (args, options = {}) => execFileAsync(process.execPath, getAstroArgs(args), {
22
30
  cwd: siteProjectRoot,
@@ -57,7 +65,7 @@ const readState = async () => {
57
65
  const getLanUrls = () => Object.values(networkInterfaces())
58
66
  .flat()
59
67
  .filter((network) => network?.family === 'IPv4' && !network.internal)
60
- .map((network) => `http://${network.address}:${port}/`);
68
+ .map((network) => `http://${network.address}:${port}${projectConfig.site.basePath}`);
61
69
 
62
70
  const writeState = async (pid, host) => {
63
71
  await mkdir(path.dirname(statePath), { recursive: true });
@@ -98,7 +106,7 @@ const isPortFreeOnHost = (loopbackHost) => new Promise((resolve, reject) => {
98
106
  });
99
107
 
100
108
  const isPortFree = async () => {
101
- for (const loopbackHost of ['127.0.0.1', '::1']) {
109
+ for (const loopbackHost of ['127.0.0.1']) {
102
110
  if (!(await isPortFreeOnHost(loopbackHost))) {
103
111
  return false;
104
112
  }
@@ -155,6 +163,35 @@ const waitForPidToStopListening = async (pid) => {
155
163
  return false;
156
164
  };
157
165
 
166
+ const killPortPids = async (pids) => {
167
+ for (const pid of pids) {
168
+ console.log(`Stopping process ${pid} on port ${port}.`);
169
+ try {
170
+ process.kill(pid, 'SIGTERM');
171
+ } catch (error) {
172
+ if (error.code !== 'ESRCH') {
173
+ throw error;
174
+ }
175
+ }
176
+ }
177
+
178
+ for (const pid of pids) {
179
+ if (await waitForPidToStopListening(pid)) {
180
+ continue;
181
+ }
182
+
183
+ console.log(`Process ${pid} did not stop; sending SIGKILL.`);
184
+ try {
185
+ process.kill(pid, 'SIGKILL');
186
+ } catch (error) {
187
+ if (error.code !== 'ESRCH') {
188
+ throw error;
189
+ }
190
+ }
191
+ await waitForPidToStopListening(pid);
192
+ }
193
+ };
194
+
158
195
  const openBrowser = async () => {
159
196
  if (skipOpen) {
160
197
  console.log(`Browser open skipped. Open ${localUrl}`);
@@ -210,12 +247,22 @@ const stopServer = async ({ quiet = false } = {}) => {
210
247
  }
211
248
  };
212
249
 
213
- const startServer = async ({ host = localHost, open = true } = {}) => {
250
+ const startServer = async ({ host = localHost, open = true, killBlockingPort = false } = {}) => {
214
251
  await stopServer({ quiet: true });
215
252
 
216
- const existingPids = await getPortPids();
253
+ let existingPids = await getPortPids();
217
254
  if (existingPids.length > 0 || !(await isPortFree())) {
218
- throw new Error(`Port ${port} is already in use. Stop the process using it, then rerun the dev command.`);
255
+ if (!killBlockingPort || existingPids.length === 0) {
256
+ throw new Error(`Port ${port} is already in use. Stop the process using it, then rerun the dev command, or pass --kill.`);
257
+ }
258
+
259
+ await killPortPids(existingPids);
260
+ await removeState();
261
+ existingPids = await getPortPids();
262
+
263
+ if (existingPids.length > 0 || !(await isPortFree())) {
264
+ throw new Error(`Port ${port} is still in use after trying to stop ${existingPids.join(', ')}.`);
265
+ }
219
266
  }
220
267
 
221
268
  await syncSitePublic();
@@ -271,9 +318,7 @@ const showStatus = async () => {
271
318
  };
272
319
 
273
320
  const showLogs = async () => {
274
- const shouldFollow = process.argv.includes('--follow');
275
-
276
- if (shouldFollow) {
321
+ if (shouldFollowLogs) {
277
322
  const tail = spawn('tail', ['-n', '80', '-f', logPath], { stdio: 'inherit' });
278
323
  await new Promise((resolve, reject) => {
279
324
  tail.once('exit', resolve);
@@ -296,16 +341,20 @@ const showLogs = async () => {
296
341
  };
297
342
 
298
343
  if (command === 'start') {
299
- await startServer({ open: !skipOpen });
344
+ await startServer({ open: !skipOpen, killBlockingPort: shouldKillBlockingPort });
300
345
  } else if (command === 'lan') {
301
- await startServer({ host: '0.0.0.0', open: !skipOpen });
346
+ await startServer({ host: '0.0.0.0', open: !skipOpen, killBlockingPort: shouldKillBlockingPort });
302
347
  } else if (command === 'status') {
303
348
  await showStatus();
304
349
  } else if (command === 'logs') {
305
350
  await showLogs();
306
351
  } else if (command === 'restart') {
307
352
  const state = await readState();
308
- await startServer({ host: state?.host === '0.0.0.0' ? '0.0.0.0' : localHost, open: false });
353
+ await startServer({
354
+ host: state?.host === '0.0.0.0' ? '0.0.0.0' : localHost,
355
+ open: false,
356
+ killBlockingPort: shouldKillBlockingPort,
357
+ });
309
358
  } else if (command === 'stop') {
310
359
  await stopServer();
311
360
  } else {