gazetta 0.6.0 → 0.8.0

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 (983) hide show
  1. package/admin-dist/assets/index-CBeq0rRb.js +693 -0
  2. package/admin-dist/assets/index-Dtg1dTZQ.css +1 -0
  3. package/admin-dist/assets/rolldown-runtime-BYbx6iT9.js +1 -0
  4. package/admin-dist/assets/{vendor-primevue-C0Q_YTCb.js → vendor-primevue-CBGHkaXv.js} +183 -39
  5. package/admin-dist/assets/{vendor-react-BipDVGow.js → vendor-react-BdW_kNCG.js} +2 -2
  6. package/admin-dist/assets/vendor-rjsf-lN2SztQt.js +33 -0
  7. package/admin-dist/assets/vendor-tiptap-C36yDquB.js +141 -0
  8. package/admin-dist/assets/vendor-vue-Bt5uR1VW.js +1 -0
  9. package/admin-dist/assets/workbox-window.prod.es5-DGMtIXHc.js +2 -0
  10. package/admin-dist/index.html +8 -8
  11. package/admin-dist/sw.js +1 -0
  12. package/dist/admin-api/archived-name-conflict.d.ts +31 -0
  13. package/dist/admin-api/archived-name-conflict.d.ts.map +1 -0
  14. package/dist/admin-api/archived-name-conflict.js +226 -0
  15. package/dist/admin-api/archived-name-conflict.js.map +1 -0
  16. package/dist/admin-api/cache-stats-logger.d.ts +83 -0
  17. package/dist/admin-api/cache-stats-logger.d.ts.map +1 -0
  18. package/dist/admin-api/cache-stats-logger.js +59 -0
  19. package/dist/admin-api/cache-stats-logger.js.map +1 -0
  20. package/dist/admin-api/error-response.d.ts +21 -0
  21. package/dist/admin-api/error-response.d.ts.map +1 -0
  22. package/dist/admin-api/error-response.js +12 -0
  23. package/dist/admin-api/error-response.js.map +1 -0
  24. package/dist/admin-api/hook-audit-emitter.d.ts +38 -0
  25. package/dist/admin-api/hook-audit-emitter.d.ts.map +1 -0
  26. package/dist/admin-api/hook-audit-emitter.js +21 -0
  27. package/dist/admin-api/hook-audit-emitter.js.map +1 -0
  28. package/dist/admin-api/index.d.ts +84 -2
  29. package/dist/admin-api/index.d.ts.map +1 -1
  30. package/dist/admin-api/index.js +257 -32
  31. package/dist/admin-api/index.js.map +1 -1
  32. package/dist/admin-api/middleware/audit.d.ts +25 -0
  33. package/dist/admin-api/middleware/audit.d.ts.map +1 -0
  34. package/dist/admin-api/middleware/audit.js +65 -0
  35. package/dist/admin-api/middleware/audit.js.map +1 -0
  36. package/dist/admin-api/middleware/capability.d.ts +8 -0
  37. package/dist/admin-api/middleware/capability.d.ts.map +1 -0
  38. package/dist/admin-api/middleware/capability.js +65 -0
  39. package/dist/admin-api/middleware/capability.js.map +1 -0
  40. package/dist/admin-api/middleware/principal.d.ts +18 -0
  41. package/dist/admin-api/middleware/principal.d.ts.map +1 -0
  42. package/dist/admin-api/middleware/principal.js +128 -0
  43. package/dist/admin-api/middleware/principal.js.map +1 -0
  44. package/dist/admin-api/routes/archive-review.d.ts +80 -0
  45. package/dist/admin-api/routes/archive-review.d.ts.map +1 -0
  46. package/dist/admin-api/routes/archive-review.js +70 -0
  47. package/dist/admin-api/routes/archive-review.js.map +1 -0
  48. package/dist/admin-api/routes/archive.d.ts +145 -0
  49. package/dist/admin-api/routes/archive.d.ts.map +1 -0
  50. package/dist/admin-api/routes/archive.js +540 -0
  51. package/dist/admin-api/routes/archive.js.map +1 -0
  52. package/dist/admin-api/routes/assets.d.ts +21 -0
  53. package/dist/admin-api/routes/assets.d.ts.map +1 -0
  54. package/dist/admin-api/routes/assets.js +586 -0
  55. package/dist/admin-api/routes/assets.js.map +1 -0
  56. package/dist/admin-api/routes/audit.d.ts +71 -0
  57. package/dist/admin-api/routes/audit.d.ts.map +1 -0
  58. package/dist/admin-api/routes/audit.js +178 -0
  59. package/dist/admin-api/routes/audit.js.map +1 -0
  60. package/dist/admin-api/routes/compare.d.ts.map +1 -1
  61. package/dist/admin-api/routes/compare.js +3 -2
  62. package/dist/admin-api/routes/compare.js.map +1 -1
  63. package/dist/admin-api/routes/fields.d.ts.map +1 -1
  64. package/dist/admin-api/routes/fields.js +2 -1
  65. package/dist/admin-api/routes/fields.js.map +1 -1
  66. package/dist/admin-api/routes/fragments.d.ts +13 -1
  67. package/dist/admin-api/routes/fragments.d.ts.map +1 -1
  68. package/dist/admin-api/routes/fragments.js +128 -67
  69. package/dist/admin-api/routes/fragments.js.map +1 -1
  70. package/dist/admin-api/routes/health.d.ts +60 -0
  71. package/dist/admin-api/routes/health.d.ts.map +1 -0
  72. package/dist/admin-api/routes/health.js +65 -0
  73. package/dist/admin-api/routes/health.js.map +1 -0
  74. package/dist/admin-api/routes/history.d.ts +2 -1
  75. package/dist/admin-api/routes/history.d.ts.map +1 -1
  76. package/dist/admin-api/routes/history.js +26 -4
  77. package/dist/admin-api/routes/history.js.map +1 -1
  78. package/dist/admin-api/routes/pages.d.ts +20 -1
  79. package/dist/admin-api/routes/pages.d.ts.map +1 -1
  80. package/dist/admin-api/routes/pages.js +158 -85
  81. package/dist/admin-api/routes/pages.js.map +1 -1
  82. package/dist/admin-api/routes/preview.d.ts.map +1 -1
  83. package/dist/admin-api/routes/preview.js +56 -17
  84. package/dist/admin-api/routes/preview.js.map +1 -1
  85. package/dist/admin-api/routes/publish.d.ts +19 -1
  86. package/dist/admin-api/routes/publish.d.ts.map +1 -1
  87. package/dist/admin-api/routes/publish.js +548 -99
  88. package/dist/admin-api/routes/publish.js.map +1 -1
  89. package/dist/admin-api/routes/rename.d.ts +62 -0
  90. package/dist/admin-api/routes/rename.d.ts.map +1 -0
  91. package/dist/admin-api/routes/rename.js +366 -0
  92. package/dist/admin-api/routes/rename.js.map +1 -0
  93. package/dist/admin-api/routes/site.d.ts.map +1 -1
  94. package/dist/admin-api/routes/site.js +6 -18
  95. package/dist/admin-api/routes/site.js.map +1 -1
  96. package/dist/admin-api/routes/system.d.ts +23 -0
  97. package/dist/admin-api/routes/system.d.ts.map +1 -0
  98. package/dist/admin-api/routes/system.js +115 -0
  99. package/dist/admin-api/routes/system.js.map +1 -0
  100. package/dist/admin-api/routes/templates.d.ts +11 -1
  101. package/dist/admin-api/routes/templates.d.ts.map +1 -1
  102. package/dist/admin-api/routes/templates.js +36 -3
  103. package/dist/admin-api/routes/templates.js.map +1 -1
  104. package/dist/admin-api/routes/validation.d.ts +47 -0
  105. package/dist/admin-api/routes/validation.d.ts.map +1 -0
  106. package/dist/admin-api/routes/validation.js +120 -0
  107. package/dist/admin-api/routes/validation.js.map +1 -0
  108. package/dist/admin-api/schemas/archive.d.ts +124 -0
  109. package/dist/admin-api/schemas/archive.d.ts.map +1 -0
  110. package/dist/admin-api/schemas/archive.js +93 -0
  111. package/dist/admin-api/schemas/archive.js.map +1 -0
  112. package/dist/admin-api/schemas/assets.d.ts +64 -0
  113. package/dist/admin-api/schemas/assets.d.ts.map +1 -0
  114. package/dist/admin-api/schemas/assets.js +59 -0
  115. package/dist/admin-api/schemas/assets.js.map +1 -0
  116. package/dist/admin-api/schemas/audit.d.ts +175 -0
  117. package/dist/admin-api/schemas/audit.d.ts.map +1 -0
  118. package/dist/admin-api/schemas/audit.js +91 -0
  119. package/dist/admin-api/schemas/audit.js.map +1 -0
  120. package/dist/admin-api/schemas/error.d.ts +94 -0
  121. package/dist/admin-api/schemas/error.d.ts.map +1 -0
  122. package/dist/admin-api/schemas/error.js +79 -0
  123. package/dist/admin-api/schemas/error.js.map +1 -0
  124. package/dist/admin-api/schemas/fragments.d.ts +2 -0
  125. package/dist/admin-api/schemas/fragments.d.ts.map +1 -1
  126. package/dist/admin-api/schemas/fragments.js +4 -0
  127. package/dist/admin-api/schemas/fragments.js.map +1 -1
  128. package/dist/admin-api/schemas/index.d.ts +10 -0
  129. package/dist/admin-api/schemas/index.d.ts.map +1 -1
  130. package/dist/admin-api/schemas/index.js +10 -0
  131. package/dist/admin-api/schemas/index.js.map +1 -1
  132. package/dist/admin-api/schemas/pages.d.ts +2 -0
  133. package/dist/admin-api/schemas/pages.d.ts.map +1 -1
  134. package/dist/admin-api/schemas/pages.js +11 -0
  135. package/dist/admin-api/schemas/pages.js.map +1 -1
  136. package/dist/admin-api/schemas/rename.d.ts +77 -0
  137. package/dist/admin-api/schemas/rename.d.ts.map +1 -0
  138. package/dist/admin-api/schemas/rename.js +75 -0
  139. package/dist/admin-api/schemas/rename.js.map +1 -0
  140. package/dist/admin-api/schemas/site.d.ts +3 -2
  141. package/dist/admin-api/schemas/site.d.ts.map +1 -1
  142. package/dist/admin-api/schemas/site.js +3 -2
  143. package/dist/admin-api/schemas/site.js.map +1 -1
  144. package/dist/admin-api/schemas/system.d.ts +28 -0
  145. package/dist/admin-api/schemas/system.d.ts.map +1 -0
  146. package/dist/admin-api/schemas/system.js +35 -0
  147. package/dist/admin-api/schemas/system.js.map +1 -0
  148. package/dist/admin-api/schemas/targets.d.ts +55 -0
  149. package/dist/admin-api/schemas/targets.d.ts.map +1 -1
  150. package/dist/admin-api/schemas/targets.js +46 -0
  151. package/dist/admin-api/schemas/targets.js.map +1 -1
  152. package/dist/admin-api/schemas/templates.d.ts +54 -0
  153. package/dist/admin-api/schemas/templates.d.ts.map +1 -1
  154. package/dist/admin-api/schemas/templates.js +21 -0
  155. package/dist/admin-api/schemas/templates.js.map +1 -1
  156. package/dist/admin-api/schemas/validation.d.ts +101 -0
  157. package/dist/admin-api/schemas/validation.d.ts.map +1 -0
  158. package/dist/admin-api/schemas/validation.js +57 -0
  159. package/dist/admin-api/schemas/validation.js.map +1 -0
  160. package/dist/admin-api/source-context.d.ts +66 -17
  161. package/dist/admin-api/source-context.d.ts.map +1 -1
  162. package/dist/admin-api/source-context.js +43 -8
  163. package/dist/admin-api/source-context.js.map +1 -1
  164. package/dist/ai/adapter-scaffold.d.ts +63 -0
  165. package/dist/ai/adapter-scaffold.d.ts.map +1 -0
  166. package/dist/ai/adapter-scaffold.js +89 -0
  167. package/dist/ai/adapter-scaffold.js.map +1 -0
  168. package/dist/ai/compose-prompt.d.ts +50 -0
  169. package/dist/ai/compose-prompt.d.ts.map +1 -0
  170. package/dist/ai/compose-prompt.js +49 -0
  171. package/dist/ai/compose-prompt.js.map +1 -0
  172. package/dist/ai/errors.d.ts +65 -0
  173. package/dist/ai/errors.d.ts.map +1 -0
  174. package/dist/ai/errors.js +59 -0
  175. package/dist/ai/errors.js.map +1 -0
  176. package/dist/ai/index.d.ts +17 -0
  177. package/dist/ai/index.d.ts.map +1 -0
  178. package/dist/ai/index.js +16 -0
  179. package/dist/ai/index.js.map +1 -0
  180. package/dist/ai/provider.d.ts +76 -0
  181. package/dist/ai/provider.d.ts.map +1 -0
  182. package/dist/ai/provider.js +13 -0
  183. package/dist/ai/provider.js.map +1 -0
  184. package/dist/ai/refusal.d.ts +50 -0
  185. package/dist/ai/refusal.d.ts.map +1 -0
  186. package/dist/ai/refusal.js +100 -0
  187. package/dist/ai/refusal.js.map +1 -0
  188. package/dist/ai/vision-prep.d.ts +32 -0
  189. package/dist/ai/vision-prep.d.ts.map +1 -0
  190. package/dist/ai/vision-prep.js +113 -0
  191. package/dist/ai/vision-prep.js.map +1 -0
  192. package/dist/alt/adapter.d.ts +140 -0
  193. package/dist/alt/adapter.d.ts.map +1 -0
  194. package/dist/alt/adapter.js +7 -0
  195. package/dist/alt/adapter.js.map +1 -0
  196. package/dist/alt/anthropic.d.ts +63 -0
  197. package/dist/alt/anthropic.d.ts.map +1 -0
  198. package/dist/alt/anthropic.js +147 -0
  199. package/dist/alt/anthropic.js.map +1 -0
  200. package/dist/alt/config.d.ts +67 -0
  201. package/dist/alt/config.d.ts.map +1 -0
  202. package/dist/alt/config.js +41 -0
  203. package/dist/alt/config.js.map +1 -0
  204. package/dist/alt/factory.d.ts +19 -0
  205. package/dist/alt/factory.d.ts.map +1 -0
  206. package/dist/alt/factory.js +69 -0
  207. package/dist/alt/factory.js.map +1 -0
  208. package/dist/alt/null-adapter.d.ts +3 -0
  209. package/dist/alt/null-adapter.d.ts.map +1 -0
  210. package/dist/alt/null-adapter.js +43 -0
  211. package/dist/alt/null-adapter.js.map +1 -0
  212. package/dist/alt/ollama.d.ts +40 -0
  213. package/dist/alt/ollama.d.ts.map +1 -0
  214. package/dist/alt/ollama.js +139 -0
  215. package/dist/alt/ollama.js.map +1 -0
  216. package/dist/alt/openai.d.ts +46 -0
  217. package/dist/alt/openai.d.ts.map +1 -0
  218. package/dist/alt/openai.js +118 -0
  219. package/dist/alt/openai.js.map +1 -0
  220. package/dist/alt/prompt-policies.d.ts +79 -0
  221. package/dist/alt/prompt-policies.d.ts.map +1 -0
  222. package/dist/alt/prompt-policies.js +67 -0
  223. package/dist/alt/prompt-policies.js.map +1 -0
  224. package/dist/alt/route-handler.d.ts +56 -0
  225. package/dist/alt/route-handler.d.ts.map +1 -0
  226. package/dist/alt/route-handler.js +122 -0
  227. package/dist/alt/route-handler.js.map +1 -0
  228. package/dist/alt/suggester.d.ts +57 -0
  229. package/dist/alt/suggester.d.ts.map +1 -0
  230. package/dist/alt/suggester.js +133 -0
  231. package/dist/alt/suggester.js.map +1 -0
  232. package/dist/app.js +1 -1
  233. package/dist/app.js.map +1 -1
  234. package/dist/archive-aliases.d.ts +79 -0
  235. package/dist/archive-aliases.d.ts.map +1 -0
  236. package/dist/archive-aliases.js +60 -0
  237. package/dist/archive-aliases.js.map +1 -0
  238. package/dist/archive-helpers.d.ts +73 -0
  239. package/dist/archive-helpers.d.ts.map +1 -0
  240. package/dist/archive-helpers.js +94 -0
  241. package/dist/archive-helpers.js.map +1 -0
  242. package/dist/assets/analyze-audio.d.ts +3 -0
  243. package/dist/assets/analyze-audio.d.ts.map +1 -0
  244. package/dist/assets/analyze-audio.js +80 -0
  245. package/dist/assets/analyze-audio.js.map +1 -0
  246. package/dist/assets/analyze-image.d.ts +19 -0
  247. package/dist/assets/analyze-image.d.ts.map +1 -0
  248. package/dist/assets/analyze-image.js +123 -0
  249. package/dist/assets/analyze-image.js.map +1 -0
  250. package/dist/assets/analyze.d.ts +94 -0
  251. package/dist/assets/analyze.d.ts.map +1 -0
  252. package/dist/assets/analyze.js +45 -0
  253. package/dist/assets/analyze.js.map +1 -0
  254. package/dist/assets/asset-deps.d.ts +30 -0
  255. package/dist/assets/asset-deps.d.ts.map +1 -0
  256. package/dist/assets/asset-deps.js +42 -0
  257. package/dist/assets/asset-deps.js.map +1 -0
  258. package/dist/assets/asset-paths.d.ts +155 -0
  259. package/dist/assets/asset-paths.d.ts.map +1 -0
  260. package/dist/assets/asset-paths.js +197 -0
  261. package/dist/assets/asset-paths.js.map +1 -0
  262. package/dist/assets/delete.d.ts +75 -0
  263. package/dist/assets/delete.d.ts.map +1 -0
  264. package/dist/assets/delete.js +82 -0
  265. package/dist/assets/delete.js.map +1 -0
  266. package/dist/assets/errors.d.ts +241 -0
  267. package/dist/assets/errors.d.ts.map +1 -0
  268. package/dist/assets/errors.js +300 -0
  269. package/dist/assets/errors.js.map +1 -0
  270. package/dist/assets/find-refs.d.ts +37 -0
  271. package/dist/assets/find-refs.d.ts.map +1 -0
  272. package/dist/assets/find-refs.js +35 -0
  273. package/dist/assets/find-refs.js.map +1 -0
  274. package/dist/assets/hash.d.ts +13 -0
  275. package/dist/assets/hash.d.ts.map +1 -0
  276. package/dist/assets/hash.js +43 -0
  277. package/dist/assets/hash.js.map +1 -0
  278. package/dist/assets/image-metadata.d.ts +11 -0
  279. package/dist/assets/image-metadata.d.ts.map +1 -0
  280. package/dist/assets/image-metadata.js +31 -0
  281. package/dist/assets/image-metadata.js.map +1 -0
  282. package/dist/assets/ingest-locale.d.ts +86 -0
  283. package/dist/assets/ingest-locale.d.ts.map +1 -0
  284. package/dist/assets/ingest-locale.js +209 -0
  285. package/dist/assets/ingest-locale.js.map +1 -0
  286. package/dist/assets/ingest.d.ts +96 -0
  287. package/dist/assets/ingest.d.ts.map +1 -0
  288. package/dist/assets/ingest.js +308 -0
  289. package/dist/assets/ingest.js.map +1 -0
  290. package/dist/assets/kind-compat.d.ts +34 -0
  291. package/dist/assets/kind-compat.d.ts.map +1 -0
  292. package/dist/assets/kind-compat.js +33 -0
  293. package/dist/assets/kind-compat.js.map +1 -0
  294. package/dist/assets/list.d.ts +46 -0
  295. package/dist/assets/list.d.ts.map +1 -0
  296. package/dist/assets/list.js +102 -0
  297. package/dist/assets/list.js.map +1 -0
  298. package/dist/assets/manifest-default.d.ts +56 -0
  299. package/dist/assets/manifest-default.d.ts.map +1 -0
  300. package/dist/assets/manifest-default.js +120 -0
  301. package/dist/assets/manifest-default.js.map +1 -0
  302. package/dist/assets/manifest-filename.d.ts +52 -0
  303. package/dist/assets/manifest-filename.d.ts.map +1 -0
  304. package/dist/assets/manifest-filename.js +104 -0
  305. package/dist/assets/manifest-filename.js.map +1 -0
  306. package/dist/assets/manifest-locale.d.ts +60 -0
  307. package/dist/assets/manifest-locale.d.ts.map +1 -0
  308. package/dist/assets/manifest-locale.js +206 -0
  309. package/dist/assets/manifest-locale.js.map +1 -0
  310. package/dist/assets/manifest-merge.d.ts +66 -0
  311. package/dist/assets/manifest-merge.d.ts.map +1 -0
  312. package/dist/assets/manifest-merge.js +82 -0
  313. package/dist/assets/manifest-merge.js.map +1 -0
  314. package/dist/assets/manifest.d.ts +83 -0
  315. package/dist/assets/manifest.d.ts.map +1 -0
  316. package/dist/assets/manifest.js +93 -0
  317. package/dist/assets/manifest.js.map +1 -0
  318. package/dist/assets/mime-sniff.d.ts +18 -0
  319. package/dist/assets/mime-sniff.d.ts.map +1 -0
  320. package/dist/assets/mime-sniff.js +84 -0
  321. package/dist/assets/mime-sniff.js.map +1 -0
  322. package/dist/assets/preprocess-svg.d.ts +3 -0
  323. package/dist/assets/preprocess-svg.d.ts.map +1 -0
  324. package/dist/assets/preprocess-svg.js +49 -0
  325. package/dist/assets/preprocess-svg.js.map +1 -0
  326. package/dist/assets/preprocess.d.ts +62 -0
  327. package/dist/assets/preprocess.d.ts.map +1 -0
  328. package/dist/assets/preprocess.js +86 -0
  329. package/dist/assets/preprocess.js.map +1 -0
  330. package/dist/assets/publish-plan.d.ts +41 -0
  331. package/dist/assets/publish-plan.d.ts.map +1 -0
  332. package/dist/assets/publish-plan.js +49 -0
  333. package/dist/assets/publish-plan.js.map +1 -0
  334. package/dist/assets/publish.d.ts +33 -0
  335. package/dist/assets/publish.d.ts.map +1 -0
  336. package/dist/assets/publish.js +81 -0
  337. package/dist/assets/publish.js.map +1 -0
  338. package/dist/assets/refs.d.ts +37 -0
  339. package/dist/assets/refs.d.ts.map +1 -0
  340. package/dist/assets/refs.js +33 -0
  341. package/dist/assets/refs.js.map +1 -0
  342. package/dist/assets/remove-override.d.ts +42 -0
  343. package/dist/assets/remove-override.d.ts.map +1 -0
  344. package/dist/assets/remove-override.js +53 -0
  345. package/dist/assets/remove-override.js.map +1 -0
  346. package/dist/assets/rename.d.ts +43 -0
  347. package/dist/assets/rename.d.ts.map +1 -0
  348. package/dist/assets/rename.js +271 -0
  349. package/dist/assets/rename.js.map +1 -0
  350. package/dist/assets/replace.d.ts +37 -0
  351. package/dist/assets/replace.d.ts.map +1 -0
  352. package/dist/assets/replace.js +195 -0
  353. package/dist/assets/replace.js.map +1 -0
  354. package/dist/assets/resolve.d.ts +141 -0
  355. package/dist/assets/resolve.d.ts.map +1 -0
  356. package/dist/assets/resolve.js +381 -0
  357. package/dist/assets/resolve.js.map +1 -0
  358. package/dist/assets/rewrite-manifest-asset-ref.d.ts +44 -0
  359. package/dist/assets/rewrite-manifest-asset-ref.d.ts.map +1 -0
  360. package/dist/assets/rewrite-manifest-asset-ref.js +51 -0
  361. package/dist/assets/rewrite-manifest-asset-ref.js.map +1 -0
  362. package/dist/assets/scan-manifest-for-asset.d.ts +63 -0
  363. package/dist/assets/scan-manifest-for-asset.d.ts.map +1 -0
  364. package/dist/assets/scan-manifest-for-asset.js +105 -0
  365. package/dist/assets/scan-manifest-for-asset.js.map +1 -0
  366. package/dist/assets/serve-route.d.ts +45 -0
  367. package/dist/assets/serve-route.d.ts.map +1 -0
  368. package/dist/assets/serve-route.js +123 -0
  369. package/dist/assets/serve-route.js.map +1 -0
  370. package/dist/assets/svg-sanitize.d.ts +38 -0
  371. package/dist/assets/svg-sanitize.d.ts.map +1 -0
  372. package/dist/assets/svg-sanitize.js +209 -0
  373. package/dist/assets/svg-sanitize.js.map +1 -0
  374. package/dist/assets/update-metadata.d.ts +61 -0
  375. package/dist/assets/update-metadata.d.ts.map +1 -0
  376. package/dist/assets/update-metadata.js +82 -0
  377. package/dist/assets/update-metadata.js.map +1 -0
  378. package/dist/assets/url.d.ts +82 -0
  379. package/dist/assets/url.d.ts.map +1 -0
  380. package/dist/assets/url.js +103 -0
  381. package/dist/assets/url.js.map +1 -0
  382. package/dist/assets/validate.d.ts +74 -0
  383. package/dist/assets/validate.d.ts.map +1 -0
  384. package/dist/assets/validate.js +136 -0
  385. package/dist/assets/validate.js.map +1 -0
  386. package/dist/assets/variants.d.ts +23 -0
  387. package/dist/assets/variants.d.ts.map +1 -0
  388. package/dist/assets/variants.js +74 -0
  389. package/dist/assets/variants.js.map +1 -0
  390. package/dist/audit/config.d.ts +75 -0
  391. package/dist/audit/config.d.ts.map +1 -0
  392. package/dist/audit/config.js +91 -0
  393. package/dist/audit/config.js.map +1 -0
  394. package/dist/audit/context.d.ts +98 -0
  395. package/dist/audit/context.d.ts.map +1 -0
  396. package/dist/audit/context.js +51 -0
  397. package/dist/audit/context.js.map +1 -0
  398. package/dist/audit/errors.d.ts +73 -0
  399. package/dist/audit/errors.d.ts.map +1 -0
  400. package/dist/audit/errors.js +78 -0
  401. package/dist/audit/errors.js.map +1 -0
  402. package/dist/audit/index.d.ts +16 -0
  403. package/dist/audit/index.d.ts.map +1 -0
  404. package/dist/audit/index.js +10 -0
  405. package/dist/audit/index.js.map +1 -0
  406. package/dist/audit/provider.d.ts +73 -0
  407. package/dist/audit/provider.d.ts.map +1 -0
  408. package/dist/audit/provider.js +2 -0
  409. package/dist/audit/provider.js.map +1 -0
  410. package/dist/audit/providers/history.d.ts +66 -0
  411. package/dist/audit/providers/history.d.ts.map +1 -0
  412. package/dist/audit/providers/history.js +102 -0
  413. package/dist/audit/providers/history.js.map +1 -0
  414. package/dist/audit/pseudonymize.d.ts +26 -0
  415. package/dist/audit/pseudonymize.d.ts.map +1 -0
  416. package/dist/audit/pseudonymize.js +86 -0
  417. package/dist/audit/pseudonymize.js.map +1 -0
  418. package/dist/audit/recorder.d.ts +102 -0
  419. package/dist/audit/recorder.d.ts.map +1 -0
  420. package/dist/audit/recorder.js +55 -0
  421. package/dist/audit/recorder.js.map +1 -0
  422. package/dist/audit/retention.d.ts +83 -0
  423. package/dist/audit/retention.d.ts.map +1 -0
  424. package/dist/audit/retention.js +142 -0
  425. package/dist/audit/retention.js.map +1 -0
  426. package/dist/audit/source-ip.d.ts +32 -0
  427. package/dist/audit/source-ip.d.ts.map +1 -0
  428. package/dist/audit/source-ip.js +164 -0
  429. package/dist/audit/source-ip.js.map +1 -0
  430. package/dist/audit/types.d.ts +143 -0
  431. package/dist/audit/types.d.ts.map +1 -0
  432. package/dist/audit/types.js +33 -0
  433. package/dist/audit/types.js.map +1 -0
  434. package/dist/audit/user-agent.d.ts +28 -0
  435. package/dist/audit/user-agent.d.ts.map +1 -0
  436. package/dist/audit/user-agent.js +63 -0
  437. package/dist/audit/user-agent.js.map +1 -0
  438. package/dist/auth/capabilities.d.ts +28 -0
  439. package/dist/auth/capabilities.d.ts.map +1 -0
  440. package/dist/auth/capabilities.js +101 -0
  441. package/dist/auth/capabilities.js.map +1 -0
  442. package/dist/auth/config.d.ts +109 -0
  443. package/dist/auth/config.d.ts.map +1 -0
  444. package/dist/auth/config.js +221 -0
  445. package/dist/auth/config.js.map +1 -0
  446. package/dist/auth/errors.d.ts +72 -0
  447. package/dist/auth/errors.d.ts.map +1 -0
  448. package/dist/auth/errors.js +78 -0
  449. package/dist/auth/errors.js.map +1 -0
  450. package/dist/auth/factory.d.ts +43 -0
  451. package/dist/auth/factory.d.ts.map +1 -0
  452. package/dist/auth/factory.js +48 -0
  453. package/dist/auth/factory.js.map +1 -0
  454. package/dist/auth/index.d.ts +21 -0
  455. package/dist/auth/index.d.ts.map +1 -0
  456. package/dist/auth/index.js +14 -0
  457. package/dist/auth/index.js.map +1 -0
  458. package/dist/auth/ip-match.d.ts +29 -0
  459. package/dist/auth/ip-match.d.ts.map +1 -0
  460. package/dist/auth/ip-match.js +162 -0
  461. package/dist/auth/ip-match.js.map +1 -0
  462. package/dist/auth/provider.d.ts +76 -0
  463. package/dist/auth/provider.d.ts.map +1 -0
  464. package/dist/auth/provider.js +2 -0
  465. package/dist/auth/provider.js.map +1 -0
  466. package/dist/auth/providers/aws-cognito.d.ts +55 -0
  467. package/dist/auth/providers/aws-cognito.d.ts.map +1 -0
  468. package/dist/auth/providers/aws-cognito.js +114 -0
  469. package/dist/auth/providers/aws-cognito.js.map +1 -0
  470. package/dist/auth/providers/azure-easy-auth.d.ts +7 -0
  471. package/dist/auth/providers/azure-easy-auth.d.ts.map +1 -0
  472. package/dist/auth/providers/azure-easy-auth.js +48 -0
  473. package/dist/auth/providers/azure-easy-auth.js.map +1 -0
  474. package/dist/auth/providers/cloudflare-access.d.ts +71 -0
  475. package/dist/auth/providers/cloudflare-access.d.ts.map +1 -0
  476. package/dist/auth/providers/cloudflare-access.js +120 -0
  477. package/dist/auth/providers/cloudflare-access.js.map +1 -0
  478. package/dist/auth/providers/forwarded-user.d.ts +31 -0
  479. package/dist/auth/providers/forwarded-user.d.ts.map +1 -0
  480. package/dist/auth/providers/forwarded-user.js +72 -0
  481. package/dist/auth/providers/forwarded-user.js.map +1 -0
  482. package/dist/auth/providers/none.d.ts +6 -0
  483. package/dist/auth/providers/none.d.ts.map +1 -0
  484. package/dist/auth/providers/none.js +19 -0
  485. package/dist/auth/providers/none.js.map +1 -0
  486. package/dist/auth/providers/tailscale.d.ts +7 -0
  487. package/dist/auth/providers/tailscale.d.ts.map +1 -0
  488. package/dist/auth/providers/tailscale.js +30 -0
  489. package/dist/auth/providers/tailscale.js.map +1 -0
  490. package/dist/auth/role-resolver.d.ts +38 -0
  491. package/dist/auth/role-resolver.d.ts.map +1 -0
  492. package/dist/auth/role-resolver.js +92 -0
  493. package/dist/auth/role-resolver.js.map +1 -0
  494. package/dist/auth/types.d.ts +150 -0
  495. package/dist/auth/types.d.ts.map +1 -0
  496. package/dist/auth/types.js +60 -0
  497. package/dist/auth/types.js.map +1 -0
  498. package/dist/cache/errors.d.ts +41 -0
  499. package/dist/cache/errors.d.ts.map +1 -0
  500. package/dist/cache/errors.js +44 -0
  501. package/dist/cache/errors.js.map +1 -0
  502. package/dist/cache/factories.d.ts +17 -0
  503. package/dist/cache/factories.d.ts.map +1 -0
  504. package/dist/cache/factories.js +17 -0
  505. package/dist/cache/factories.js.map +1 -0
  506. package/dist/cache/keys.d.ts +63 -0
  507. package/dist/cache/keys.d.ts.map +1 -0
  508. package/dist/cache/keys.js +145 -0
  509. package/dist/cache/keys.js.map +1 -0
  510. package/dist/cache/memory.d.ts +51 -0
  511. package/dist/cache/memory.d.ts.map +1 -0
  512. package/dist/cache/memory.js +204 -0
  513. package/dist/cache/memory.js.map +1 -0
  514. package/dist/cache/per-site.d.ts +22 -0
  515. package/dist/cache/per-site.d.ts.map +1 -0
  516. package/dist/cache/per-site.js +114 -0
  517. package/dist/cache/per-site.js.map +1 -0
  518. package/dist/cache/types.d.ts +142 -0
  519. package/dist/cache/types.d.ts.map +1 -0
  520. package/dist/cache/types.js +33 -0
  521. package/dist/cache/types.js.map +1 -0
  522. package/dist/cli/archive.d.ts +44 -0
  523. package/dist/cli/archive.d.ts.map +1 -0
  524. package/dist/cli/archive.js +310 -0
  525. package/dist/cli/archive.js.map +1 -0
  526. package/dist/cli/assets-cli.d.ts +58 -0
  527. package/dist/cli/assets-cli.d.ts.map +1 -0
  528. package/dist/cli/assets-cli.js +233 -0
  529. package/dist/cli/assets-cli.js.map +1 -0
  530. package/dist/cli/assets-display.d.ts +112 -0
  531. package/dist/cli/assets-display.d.ts.map +1 -0
  532. package/dist/cli/assets-display.js +106 -0
  533. package/dist/cli/assets-display.js.map +1 -0
  534. package/dist/cli/bootstrap.d.ts +15 -10
  535. package/dist/cli/bootstrap.d.ts.map +1 -1
  536. package/dist/cli/bootstrap.js +59 -24
  537. package/dist/cli/bootstrap.js.map +1 -1
  538. package/dist/cli/dev-template-watcher.d.ts +29 -0
  539. package/dist/cli/dev-template-watcher.d.ts.map +1 -0
  540. package/dist/cli/dev-template-watcher.js +38 -0
  541. package/dist/cli/dev-template-watcher.js.map +1 -0
  542. package/dist/cli/history.d.ts.map +1 -1
  543. package/dist/cli/history.js +5 -3
  544. package/dist/cli/history.js.map +1 -1
  545. package/dist/cli/index.js +737 -374
  546. package/dist/cli/index.js.map +1 -1
  547. package/dist/cli/validate-flags.d.ts +29 -0
  548. package/dist/cli/validate-flags.d.ts.map +1 -0
  549. package/dist/cli/validate-flags.js +49 -0
  550. package/dist/cli/validate-flags.js.map +1 -0
  551. package/dist/compare.d.ts +1 -1
  552. package/dist/compare.d.ts.map +1 -1
  553. package/dist/compare.js +40 -35
  554. package/dist/compare.js.map +1 -1
  555. package/dist/component-ids.d.ts +25 -0
  556. package/dist/component-ids.d.ts.map +1 -0
  557. package/dist/component-ids.js +83 -0
  558. package/dist/component-ids.js.map +1 -0
  559. package/dist/config/define.d.ts +61 -0
  560. package/dist/config/define.d.ts.map +1 -0
  561. package/dist/config/define.js +64 -0
  562. package/dist/config/define.js.map +1 -0
  563. package/dist/config/errors.d.ts +32 -0
  564. package/dist/config/errors.d.ts.map +1 -0
  565. package/dist/config/errors.js +40 -0
  566. package/dist/config/errors.js.map +1 -0
  567. package/dist/config/index.d.ts +13 -0
  568. package/dist/config/index.d.ts.map +1 -0
  569. package/dist/config/index.js +20 -0
  570. package/dist/config/index.js.map +1 -0
  571. package/dist/config/loader.d.ts +105 -0
  572. package/dist/config/loader.d.ts.map +1 -0
  573. package/dist/config/loader.js +265 -0
  574. package/dist/config/loader.js.map +1 -0
  575. package/dist/config/schemas.d.ts +89 -0
  576. package/dist/config/schemas.d.ts.map +1 -0
  577. package/dist/config/schemas.js +172 -0
  578. package/dist/config/schemas.js.map +1 -0
  579. package/dist/config/types.d.ts +32 -0
  580. package/dist/config/types.d.ts.map +1 -0
  581. package/dist/config/types.js +15 -0
  582. package/dist/config/types.js.map +1 -0
  583. package/dist/dep-sidecars.d.ts +127 -0
  584. package/dist/dep-sidecars.d.ts.map +1 -0
  585. package/dist/dep-sidecars.js +122 -0
  586. package/dist/dep-sidecars.js.map +1 -0
  587. package/dist/deploy/cloudflare-workers.d.ts +46 -0
  588. package/dist/deploy/cloudflare-workers.d.ts.map +1 -0
  589. package/dist/deploy/cloudflare-workers.js +213 -0
  590. package/dist/deploy/cloudflare-workers.js.map +1 -0
  591. package/dist/deploy/errors.d.ts +66 -0
  592. package/dist/deploy/errors.d.ts.map +1 -0
  593. package/dist/deploy/errors.js +82 -0
  594. package/dist/deploy/errors.js.map +1 -0
  595. package/dist/deploy/index.d.ts +9 -0
  596. package/dist/deploy/index.d.ts.map +1 -0
  597. package/dist/deploy/index.js +3 -0
  598. package/dist/deploy/index.js.map +1 -0
  599. package/dist/deploy/types.d.ts +162 -0
  600. package/dist/deploy/types.d.ts.map +1 -0
  601. package/dist/deploy/types.js +2 -0
  602. package/dist/deploy/types.js.map +1 -0
  603. package/dist/editor/AssetEmbeddedWidget.d.ts +3 -0
  604. package/dist/editor/AssetEmbeddedWidget.d.ts.map +1 -0
  605. package/dist/editor/AssetEmbeddedWidget.js +146 -0
  606. package/dist/editor/AssetEmbeddedWidget.js.map +1 -0
  607. package/dist/editor/mount.d.ts +12 -1
  608. package/dist/editor/mount.d.ts.map +1 -1
  609. package/dist/editor/mount.js +36 -5
  610. package/dist/editor/mount.js.map +1 -1
  611. package/dist/format.d.ts +44 -0
  612. package/dist/format.d.ts.map +1 -0
  613. package/dist/format.js +65 -0
  614. package/dist/format.js.map +1 -0
  615. package/dist/fragment-deps.d.ts +24 -0
  616. package/dist/fragment-deps.d.ts.map +1 -0
  617. package/dist/fragment-deps.js +20 -0
  618. package/dist/fragment-deps.js.map +1 -0
  619. package/dist/fragments/create.d.ts +70 -0
  620. package/dist/fragments/create.d.ts.map +1 -0
  621. package/dist/fragments/create.js +93 -0
  622. package/dist/fragments/create.js.map +1 -0
  623. package/dist/fragments/publish.d.ts +37 -0
  624. package/dist/fragments/publish.d.ts.map +1 -0
  625. package/dist/fragments/publish.js +52 -0
  626. package/dist/fragments/publish.js.map +1 -0
  627. package/dist/fragments/save.d.ts +81 -0
  628. package/dist/fragments/save.d.ts.map +1 -0
  629. package/dist/fragments/save.js +105 -0
  630. package/dist/fragments/save.js.map +1 -0
  631. package/dist/hash.d.ts +0 -6
  632. package/dist/hash.d.ts.map +1 -1
  633. package/dist/hash.js +0 -18
  634. package/dist/hash.js.map +1 -1
  635. package/dist/history-provider.d.ts.map +1 -1
  636. package/dist/history-provider.js +30 -8
  637. package/dist/history-provider.js.map +1 -1
  638. package/dist/history-recorder.d.ts +10 -6
  639. package/dist/history-recorder.d.ts.map +1 -1
  640. package/dist/history-recorder.js +13 -5
  641. package/dist/history-recorder.js.map +1 -1
  642. package/dist/history-restorer.d.ts.map +1 -1
  643. package/dist/history-restorer.js +34 -2
  644. package/dist/history-restorer.js.map +1 -1
  645. package/dist/history.d.ts +26 -8
  646. package/dist/history.d.ts.map +1 -1
  647. package/dist/hooks/audit-emitter.d.ts +73 -0
  648. package/dist/hooks/audit-emitter.d.ts.map +1 -0
  649. package/dist/hooks/audit-emitter.js +13 -0
  650. package/dist/hooks/audit-emitter.js.map +1 -0
  651. package/dist/hooks/context.d.ts +78 -0
  652. package/dist/hooks/context.d.ts.map +1 -0
  653. package/dist/hooks/context.js +56 -0
  654. package/dist/hooks/context.js.map +1 -0
  655. package/dist/hooks/contribution.d.ts +90 -0
  656. package/dist/hooks/contribution.d.ts.map +1 -0
  657. package/dist/hooks/contribution.js +2 -0
  658. package/dist/hooks/contribution.js.map +1 -0
  659. package/dist/hooks/dispatch.d.ts +30 -0
  660. package/dist/hooks/dispatch.d.ts.map +1 -0
  661. package/dist/hooks/dispatch.js +252 -0
  662. package/dist/hooks/dispatch.js.map +1 -0
  663. package/dist/hooks/errors.d.ts +100 -0
  664. package/dist/hooks/errors.d.ts.map +1 -0
  665. package/dist/hooks/errors.js +103 -0
  666. package/dist/hooks/errors.js.map +1 -0
  667. package/dist/hooks/index.d.ts +15 -0
  668. package/dist/hooks/index.d.ts.map +1 -0
  669. package/dist/hooks/index.js +6 -0
  670. package/dist/hooks/index.js.map +1 -0
  671. package/dist/hooks/registry.d.ts +53 -0
  672. package/dist/hooks/registry.d.ts.map +1 -0
  673. package/dist/hooks/registry.js +139 -0
  674. package/dist/hooks/registry.js.map +1 -0
  675. package/dist/hooks/storage.d.ts +43 -0
  676. package/dist/hooks/storage.d.ts.map +1 -0
  677. package/dist/hooks/storage.js +2 -0
  678. package/dist/hooks/storage.js.map +1 -0
  679. package/dist/hooks/types.d.ts +324 -0
  680. package/dist/hooks/types.d.ts.map +1 -0
  681. package/dist/hooks/types.js +2 -0
  682. package/dist/hooks/types.js.map +1 -0
  683. package/dist/index.d.ts +27 -9
  684. package/dist/index.d.ts.map +1 -1
  685. package/dist/index.js +50 -7
  686. package/dist/index.js.map +1 -1
  687. package/dist/locale.d.ts +25 -1
  688. package/dist/locale.d.ts.map +1 -1
  689. package/dist/locale.js +44 -2
  690. package/dist/locale.js.map +1 -1
  691. package/dist/manifest-save.d.ts +255 -0
  692. package/dist/manifest-save.d.ts.map +1 -0
  693. package/dist/manifest-save.js +260 -0
  694. package/dist/manifest-save.js.map +1 -0
  695. package/dist/manifest.d.ts +1 -2
  696. package/dist/manifest.d.ts.map +1 -1
  697. package/dist/manifest.js +43 -44
  698. package/dist/manifest.js.map +1 -1
  699. package/dist/node-floor.d.ts +3 -0
  700. package/dist/node-floor.d.ts.map +1 -0
  701. package/dist/node-floor.js +3 -0
  702. package/dist/node-floor.js.map +1 -0
  703. package/dist/pages/create.d.ts +103 -0
  704. package/dist/pages/create.d.ts.map +1 -0
  705. package/dist/pages/create.js +117 -0
  706. package/dist/pages/create.js.map +1 -0
  707. package/dist/pages/publish.d.ts +59 -0
  708. package/dist/pages/publish.d.ts.map +1 -0
  709. package/dist/pages/publish.js +78 -0
  710. package/dist/pages/publish.js.map +1 -0
  711. package/dist/pages/save.d.ts +97 -0
  712. package/dist/pages/save.d.ts.map +1 -0
  713. package/dist/pages/save.js +138 -0
  714. package/dist/pages/save.js.map +1 -0
  715. package/dist/providers/_atomic-write.d.ts +9 -0
  716. package/dist/providers/_atomic-write.d.ts.map +1 -0
  717. package/dist/providers/_atomic-write.js +72 -0
  718. package/dist/providers/_atomic-write.js.map +1 -0
  719. package/dist/providers/_rm-ignore-missing.d.ts +31 -0
  720. package/dist/providers/_rm-ignore-missing.d.ts.map +1 -0
  721. package/dist/providers/_rm-ignore-missing.js +12 -0
  722. package/dist/providers/_rm-ignore-missing.js.map +1 -0
  723. package/dist/providers/_stream-interop.d.ts +23 -0
  724. package/dist/providers/_stream-interop.d.ts.map +1 -0
  725. package/dist/providers/_stream-interop.js +21 -0
  726. package/dist/providers/_stream-interop.js.map +1 -0
  727. package/dist/providers/azure-blob.d.ts.map +1 -1
  728. package/dist/providers/azure-blob.js +60 -0
  729. package/dist/providers/azure-blob.js.map +1 -1
  730. package/dist/providers/factories.d.ts +65 -0
  731. package/dist/providers/factories.d.ts.map +1 -0
  732. package/dist/providers/factories.js +189 -0
  733. package/dist/providers/factories.js.map +1 -0
  734. package/dist/providers/filesystem.d.ts +4 -0
  735. package/dist/providers/filesystem.d.ts.map +1 -1
  736. package/dist/providers/filesystem.js +63 -2
  737. package/dist/providers/filesystem.js.map +1 -1
  738. package/dist/providers/s3.d.ts.map +1 -1
  739. package/dist/providers/s3.js +84 -1
  740. package/dist/providers/s3.js.map +1 -1
  741. package/dist/publish-item.d.ts +225 -0
  742. package/dist/publish-item.d.ts.map +1 -0
  743. package/dist/publish-item.js +210 -0
  744. package/dist/publish-item.js.map +1 -0
  745. package/dist/publish-rendered.d.ts +37 -17
  746. package/dist/publish-rendered.d.ts.map +1 -1
  747. package/dist/publish-rendered.js +144 -71
  748. package/dist/publish-rendered.js.map +1 -1
  749. package/dist/publish-renderers.d.ts +132 -0
  750. package/dist/publish-renderers.d.ts.map +1 -0
  751. package/dist/publish-renderers.js +240 -0
  752. package/dist/publish-renderers.js.map +1 -0
  753. package/dist/publish-run.d.ts +223 -0
  754. package/dist/publish-run.d.ts.map +1 -0
  755. package/dist/publish-run.js +307 -0
  756. package/dist/publish-run.js.map +1 -0
  757. package/dist/publish.d.ts +13 -12
  758. package/dist/publish.d.ts.map +1 -1
  759. package/dist/publish.js +24 -57
  760. package/dist/publish.js.map +1 -1
  761. package/dist/render-for-analysis.d.ts +24 -0
  762. package/dist/render-for-analysis.d.ts.map +1 -0
  763. package/dist/render-for-analysis.js +146 -0
  764. package/dist/render-for-analysis.js.map +1 -0
  765. package/dist/resolver.d.ts +12 -2
  766. package/dist/resolver.d.ts.map +1 -1
  767. package/dist/resolver.js +101 -32
  768. package/dist/resolver.js.map +1 -1
  769. package/dist/runtime/archive-marker.d.ts +62 -0
  770. package/dist/runtime/archive-marker.d.ts.map +1 -0
  771. package/dist/runtime/archive-marker.js +88 -0
  772. package/dist/runtime/archive-marker.js.map +1 -0
  773. package/dist/runtime/capability-gap-warnings.d.ts +42 -0
  774. package/dist/runtime/capability-gap-warnings.d.ts.map +1 -0
  775. package/dist/runtime/capability-gap-warnings.js +28 -0
  776. package/dist/runtime/capability-gap-warnings.js.map +1 -0
  777. package/dist/runtime/redirects-emit.d.ts +93 -0
  778. package/dist/runtime/redirects-emit.d.ts.map +1 -0
  779. package/dist/runtime/redirects-emit.js +89 -0
  780. package/dist/runtime/redirects-emit.js.map +1 -0
  781. package/dist/runtime/runtime-capabilities.d.ts +79 -0
  782. package/dist/runtime/runtime-capabilities.d.ts.map +1 -0
  783. package/dist/runtime/runtime-capabilities.js +60 -0
  784. package/dist/runtime/runtime-capabilities.js.map +1 -0
  785. package/dist/save-etag.d.ts +69 -0
  786. package/dist/save-etag.d.ts.map +1 -0
  787. package/dist/save-etag.js +118 -0
  788. package/dist/save-etag.js.map +1 -0
  789. package/dist/schema/dimensions.d.ts +78 -0
  790. package/dist/schema/dimensions.d.ts.map +1 -0
  791. package/dist/schema/dimensions.js +97 -0
  792. package/dist/schema/dimensions.js.map +1 -0
  793. package/dist/schema/helpers.d.ts +108 -0
  794. package/dist/schema/helpers.d.ts.map +1 -0
  795. package/dist/schema/helpers.js +133 -0
  796. package/dist/schema/helpers.js.map +1 -0
  797. package/dist/schema/index.d.ts +27 -0
  798. package/dist/schema/index.d.ts.map +1 -0
  799. package/dist/schema/index.js +25 -0
  800. package/dist/schema/index.js.map +1 -0
  801. package/dist/schema/types.d.ts +390 -0
  802. package/dist/schema/types.d.ts.map +1 -0
  803. package/dist/schema/types.js +25 -0
  804. package/dist/schema/types.js.map +1 -0
  805. package/dist/selector-chain.d.ts +63 -0
  806. package/dist/selector-chain.d.ts.map +1 -0
  807. package/dist/selector-chain.js +58 -0
  808. package/dist/selector-chain.js.map +1 -0
  809. package/dist/sidecars.d.ts +19 -18
  810. package/dist/sidecars.d.ts.map +1 -1
  811. package/dist/sidecars.js +70 -62
  812. package/dist/sidecars.js.map +1 -1
  813. package/dist/site-loader.d.ts +42 -4
  814. package/dist/site-loader.d.ts.map +1 -1
  815. package/dist/site-loader.js +27 -8
  816. package/dist/site-loader.js.map +1 -1
  817. package/dist/targets.d.ts +21 -12
  818. package/dist/targets.d.ts.map +1 -1
  819. package/dist/targets.js +27 -117
  820. package/dist/targets.js.map +1 -1
  821. package/dist/testing/admin-cache-contract.d.ts +52 -0
  822. package/dist/testing/admin-cache-contract.d.ts.map +1 -0
  823. package/dist/testing/admin-cache-contract.js +203 -0
  824. package/dist/testing/admin-cache-contract.js.map +1 -0
  825. package/dist/testing/index.d.ts +11 -0
  826. package/dist/testing/index.d.ts.map +1 -0
  827. package/dist/testing/index.js +11 -0
  828. package/dist/testing/index.js.map +1 -0
  829. package/dist/themes.d.ts +69 -0
  830. package/dist/themes.d.ts.map +1 -0
  831. package/dist/themes.js +85 -0
  832. package/dist/themes.js.map +1 -0
  833. package/dist/transforms/adapter.d.ts +115 -0
  834. package/dist/transforms/adapter.d.ts.map +1 -0
  835. package/dist/transforms/adapter.js +2 -0
  836. package/dist/transforms/adapter.js.map +1 -0
  837. package/dist/transforms/cloudflare.d.ts +17 -0
  838. package/dist/transforms/cloudflare.d.ts.map +1 -0
  839. package/dist/transforms/cloudflare.js +110 -0
  840. package/dist/transforms/cloudflare.js.map +1 -0
  841. package/dist/transforms/factories.d.ts +16 -0
  842. package/dist/transforms/factories.d.ts.map +1 -0
  843. package/dist/transforms/factories.js +18 -0
  844. package/dist/transforms/factories.js.map +1 -0
  845. package/dist/transforms/index.d.ts +17 -0
  846. package/dist/transforms/index.d.ts.map +1 -0
  847. package/dist/transforms/index.js +6 -0
  848. package/dist/transforms/index.js.map +1 -0
  849. package/dist/transforms/sharp.d.ts +17 -0
  850. package/dist/transforms/sharp.d.ts.map +1 -0
  851. package/dist/transforms/sharp.js +57 -0
  852. package/dist/transforms/sharp.js.map +1 -0
  853. package/dist/types.d.ts +485 -34
  854. package/dist/types.d.ts.map +1 -1
  855. package/dist/types.js +20 -1
  856. package/dist/types.js.map +1 -1
  857. package/dist/validation/alt-required-walker.d.ts +27 -0
  858. package/dist/validation/alt-required-walker.d.ts.map +1 -0
  859. package/dist/validation/alt-required-walker.js +108 -0
  860. package/dist/validation/alt-required-walker.js.map +1 -0
  861. package/dist/validation/default-registry.d.ts +12 -0
  862. package/dist/validation/default-registry.d.ts.map +1 -0
  863. package/dist/validation/default-registry.js +55 -0
  864. package/dist/validation/default-registry.js.map +1 -0
  865. package/dist/validation/publish-audit.d.ts +44 -0
  866. package/dist/validation/publish-audit.d.ts.map +1 -0
  867. package/dist/validation/publish-audit.js +64 -0
  868. package/dist/validation/publish-audit.js.map +1 -0
  869. package/dist/validation/registry.d.ts +23 -0
  870. package/dist/validation/registry.d.ts.map +1 -0
  871. package/dist/validation/registry.js +15 -0
  872. package/dist/validation/registry.js.map +1 -0
  873. package/dist/validation/save-delta.d.ts +46 -0
  874. package/dist/validation/save-delta.d.ts.map +1 -0
  875. package/dist/validation/save-delta.js +57 -0
  876. package/dist/validation/save-delta.js.map +1 -0
  877. package/dist/validation/scanner.d.ts +91 -0
  878. package/dist/validation/scanner.d.ts.map +1 -0
  879. package/dist/validation/scanner.js +327 -0
  880. package/dist/validation/scanner.js.map +1 -0
  881. package/dist/validation/template-impact.d.ts +52 -0
  882. package/dist/validation/template-impact.d.ts.map +1 -0
  883. package/dist/validation/template-impact.js +53 -0
  884. package/dist/validation/template-impact.js.map +1 -0
  885. package/dist/validation/types.d.ts +123 -0
  886. package/dist/validation/types.d.ts.map +1 -0
  887. package/dist/validation/types.js +7 -0
  888. package/dist/validation/types.js.map +1 -0
  889. package/dist/validation/validators/accessibility.d.ts +3 -0
  890. package/dist/validation/validators/accessibility.d.ts.map +1 -0
  891. package/dist/validation/validators/accessibility.js +106 -0
  892. package/dist/validation/validators/accessibility.js.map +1 -0
  893. package/dist/validation/validators/aliasof-points-to-archived.d.ts +40 -0
  894. package/dist/validation/validators/aliasof-points-to-archived.d.ts.map +1 -0
  895. package/dist/validation/validators/aliasof-points-to-archived.js +34 -0
  896. package/dist/validation/validators/aliasof-points-to-archived.js.map +1 -0
  897. package/dist/validation/validators/alt-required.d.ts +3 -0
  898. package/dist/validation/validators/alt-required.d.ts.map +1 -0
  899. package/dist/validation/validators/alt-required.js +118 -0
  900. package/dist/validation/validators/alt-required.js.map +1 -0
  901. package/dist/validation/validators/archive-not-supported-on-target.d.ts +3 -0
  902. package/dist/validation/validators/archive-not-supported-on-target.d.ts.map +1 -0
  903. package/dist/validation/validators/archive-not-supported-on-target.js +38 -0
  904. package/dist/validation/validators/archive-not-supported-on-target.js.map +1 -0
  905. package/dist/validation/validators/broken-links.d.ts +3 -0
  906. package/dist/validation/validators/broken-links.d.ts.map +1 -0
  907. package/dist/validation/validators/broken-links.js +190 -0
  908. package/dist/validation/validators/broken-links.js.map +1 -0
  909. package/dist/validation/validators/circular-alias.d.ts +36 -0
  910. package/dist/validation/validators/circular-alias.d.ts.map +1 -0
  911. package/dist/validation/validators/circular-alias.js +63 -0
  912. package/dist/validation/validators/circular-alias.js.map +1 -0
  913. package/dist/validation/validators/circular-fragment.d.ts +15 -0
  914. package/dist/validation/validators/circular-fragment.d.ts.map +1 -0
  915. package/dist/validation/validators/circular-fragment.js +97 -0
  916. package/dist/validation/validators/circular-fragment.js.map +1 -0
  917. package/dist/validation/validators/dangling-alias.d.ts +38 -0
  918. package/dist/validation/validators/dangling-alias.d.ts.map +1 -0
  919. package/dist/validation/validators/dangling-alias.js +31 -0
  920. package/dist/validation/validators/dangling-alias.js.map +1 -0
  921. package/dist/validation/validators/deploy-target-type-supported.d.ts +3 -0
  922. package/dist/validation/validators/deploy-target-type-supported.d.ts.map +1 -0
  923. package/dist/validation/validators/deploy-target-type-supported.js +32 -0
  924. package/dist/validation/validators/deploy-target-type-supported.js.map +1 -0
  925. package/dist/validation/validators/dynamic-route-conflict.d.ts +18 -0
  926. package/dist/validation/validators/dynamic-route-conflict.d.ts.map +1 -0
  927. package/dist/validation/validators/dynamic-route-conflict.js +80 -0
  928. package/dist/validation/validators/dynamic-route-conflict.js.map +1 -0
  929. package/dist/validation/validators/html-validity.d.ts +3 -0
  930. package/dist/validation/validators/html-validity.d.ts.map +1 -0
  931. package/dist/validation/validators/html-validity.js +89 -0
  932. package/dist/validation/validators/html-validity.js.map +1 -0
  933. package/dist/validation/validators/orphaned-locale-file.d.ts +21 -0
  934. package/dist/validation/validators/orphaned-locale-file.d.ts.map +1 -0
  935. package/dist/validation/validators/orphaned-locale-file.js +84 -0
  936. package/dist/validation/validators/orphaned-locale-file.js.map +1 -0
  937. package/dist/validation/validators/referenced-archived-without-alias.d.ts +3 -0
  938. package/dist/validation/validators/referenced-archived-without-alias.d.ts.map +1 -0
  939. package/dist/validation/validators/referenced-archived-without-alias.js +65 -0
  940. package/dist/validation/validators/referenced-archived-without-alias.js.map +1 -0
  941. package/dist/validation/validators/referenced-asset-exists.d.ts +13 -0
  942. package/dist/validation/validators/referenced-asset-exists.d.ts.map +1 -0
  943. package/dist/validation/validators/referenced-asset-exists.js +80 -0
  944. package/dist/validation/validators/referenced-asset-exists.js.map +1 -0
  945. package/dist/validation/validators/referenced-fragment-exists.d.ts +9 -0
  946. package/dist/validation/validators/referenced-fragment-exists.d.ts.map +1 -0
  947. package/dist/validation/validators/referenced-fragment-exists.js +52 -0
  948. package/dist/validation/validators/referenced-fragment-exists.js.map +1 -0
  949. package/dist/validation/validators/referenced-template-exists.d.ts +10 -0
  950. package/dist/validation/validators/referenced-template-exists.d.ts.map +1 -0
  951. package/dist/validation/validators/referenced-template-exists.js +74 -0
  952. package/dist/validation/validators/referenced-template-exists.js.map +1 -0
  953. package/dist/validation/validators/schema-conformance.d.ts +17 -0
  954. package/dist/validation/validators/schema-conformance.d.ts.map +1 -0
  955. package/dist/validation/validators/schema-conformance.js +94 -0
  956. package/dist/validation/validators/schema-conformance.js.map +1 -0
  957. package/dist/validation/validators/target-deploy-coverage.d.ts +3 -0
  958. package/dist/validation/validators/target-deploy-coverage.d.ts.map +1 -0
  959. package/dist/validation/validators/target-deploy-coverage.js +37 -0
  960. package/dist/validation/validators/target-deploy-coverage.js.map +1 -0
  961. package/dist/validation/validators/unused-fragment.d.ts +16 -0
  962. package/dist/validation/validators/unused-fragment.d.ts.map +1 -0
  963. package/dist/validation/validators/unused-fragment.js +86 -0
  964. package/dist/validation/validators/unused-fragment.js.map +1 -0
  965. package/package.json +69 -27
  966. package/admin-dist/assets/index-B6pVot0Y.css +0 -1
  967. package/admin-dist/assets/index-DniLwxJA.js +0 -609
  968. package/admin-dist/assets/rolldown-runtime-COnpUsM8.js +0 -1
  969. package/admin-dist/assets/vendor-rjsf-HKBAjOmQ.js +0 -32
  970. package/admin-dist/assets/vendor-tiptap-IyO99U4R.js +0 -142
  971. package/admin-dist/assets/vendor-vue-D3wBSmDf.js +0 -1
  972. package/dist/providers/r2.d.ts +0 -8
  973. package/dist/providers/r2.d.ts.map +0 -1
  974. package/dist/providers/r2.js +0 -86
  975. package/dist/providers/r2.js.map +0 -1
  976. package/dist/publish-locale.d.ts +0 -44
  977. package/dist/publish-locale.d.ts.map +0 -1
  978. package/dist/publish-locale.js +0 -103
  979. package/dist/publish-locale.js.map +0 -1
  980. package/dist/source-sidecars.d.ts +0 -32
  981. package/dist/source-sidecars.d.ts.map +0 -1
  982. package/dist/source-sidecars.js +0 -98
  983. package/dist/source-sidecars.js.map +0 -1
package/dist/cli/index.js CHANGED
@@ -5,14 +5,16 @@ import { serve } from '@hono/node-server';
5
5
  import { serveStatic } from '@hono/node-server/serve-static';
6
6
  import { Hono } from 'hono';
7
7
  import { streamSSE } from 'hono/streaming';
8
- import yaml from 'js-yaml';
9
8
  import { loadSite } from '../site-loader.js';
10
9
  import { resolvePage } from '../resolver.js';
11
10
  import { renderPage } from '../renderer.js';
12
11
  import { createFilesystemProvider } from '../providers/filesystem.js';
13
12
  import { invalidateTemplate, invalidateAllTemplates } from '../template-loader.js';
13
+ import { REQUIRED_NODE_FLOOR } from '../node-floor.js';
14
+ import { isTemplateEventRecent, TEMPLATE_RECENT_CHANGE_WINDOW_MS } from './dev-template-watcher.js';
14
15
  import { getEnvironment, getType, isEditable } from '../types.js';
15
- import { createAdminApp } from '../admin-api/index.js';
16
+ import { buildHooksRegistry, createAdminApp } from '../admin-api/index.js';
17
+ import { parseValidateFlags } from './validate-flags.js';
16
18
  // ANSI color helpers — no dependency, suppressed when NO_COLOR or CI
17
19
  const noColor = !!process.env.NO_COLOR || !process.stdout.isTTY;
18
20
  const c = {
@@ -27,6 +29,39 @@ const c = {
27
29
  };
28
30
  const args = process.argv.slice(2);
29
31
  const command = args[0];
32
+ /**
33
+ * Load a site manifest from `siteDir` via the TS config loader. Recognizes
34
+ * `site.config.ts`, `site.config.js`, and `site.config.mjs`.
35
+ *
36
+ * Returns null when no config file is found.
37
+ */
38
+ async function loadSiteManifestForCli(siteDir) {
39
+ const tsConfigCandidates = ['site.config.ts', 'site.config.js', 'site.config.mjs'];
40
+ if (!tsConfigCandidates.some(f => existsSync(join(siteDir, f))))
41
+ return null;
42
+ const { loadSiteConfig, siteConfigToManifest } = await import('../config/loader.js');
43
+ const loaded = await loadSiteConfig(siteDir);
44
+ if (!loaded)
45
+ return null;
46
+ return siteConfigToManifest(loaded.config);
47
+ }
48
+ /**
49
+ * Read `admin.hooks` factory contributions from a site manifest.
50
+ *
51
+ * The manifest types `admin?` as a loose record (`Record<string, unknown>`)
52
+ * to keep `SiteManifest` stable across foundation additions; each foundation
53
+ * narrow-types its own block at the consumption site. For hooks the runtime
54
+ * shape is `ReadonlyArray<HookContribution>` (per design-hooks.md
55
+ * "Registration"); we accept it as `unknown` here and cast at the boundary.
56
+ *
57
+ * Returns undefined when the field is absent or empty.
58
+ */
59
+ function readHookContributions(manifest) {
60
+ const hooks = manifest?.admin?.hooks;
61
+ if (!Array.isArray(hooks) || hooks.length === 0)
62
+ return undefined;
63
+ return hooks;
64
+ }
30
65
  // Served to /admin/* requests during dev-server startup before Vite middleware
31
66
  // is attached. Polls /admin/ping every 500ms and reloads when the admin becomes
32
67
  // reachable. See #132 and cli/index.ts for why this is needed.
@@ -122,6 +157,84 @@ function detectProjectRoot(siteDir) {
122
157
  // Fallback — use siteDir (templates/ may not exist yet)
123
158
  return siteDir;
124
159
  }
160
+ /**
161
+ * Resolve the siteDir for any CLI command by extracting the site
162
+ * positional from the parsed args per the command's documented
163
+ * positional layout. Pure fs walk; no config eval — safe to call
164
+ * before env-load.
165
+ *
166
+ * Single dispatch table so env-load happens exactly once at the top
167
+ * of `main()`, before any config-eval can read process.env.
168
+ */
169
+ async function resolveSiteDirForCommand(cmd, parsed) {
170
+ // Commands whose site positional sits at a fixed index.
171
+ if (cmd === 'build')
172
+ return resolveSiteDir(parsed.positional[0]);
173
+ if (cmd === 'dev' || cmd === 'validate' || cmd === 'admin')
174
+ return resolveSiteDir(parsed.positional[0]);
175
+ // `rollback <rev> [target|site] [site]`: site is positional[1] when
176
+ // site-shaped, else positional[2].
177
+ if (cmd === 'rollback') {
178
+ const second = parsed.positional[1];
179
+ const third = parsed.positional[2];
180
+ const secondIsSite = second && (second.includes('/') || hasSiteConfig(resolve(second)));
181
+ return resolveSiteDir(secondIsSite ? second : third);
182
+ }
183
+ // `publish|serve|deploy|history|undo [target|site] [site]`: site is
184
+ // positional[0] when site-shaped, else positional[1].
185
+ if (cmd === 'publish' || cmd === 'serve' || cmd === 'deploy' || cmd === 'history' || cmd === 'undo') {
186
+ const first = parsed.positional[0];
187
+ const firstIsSite = first && (first.includes('/') || hasSiteConfig(resolve(first)));
188
+ return resolveSiteDir(firstIsSite ? first : parsed.positional[1]);
189
+ }
190
+ // `translate <item> --to <locale> [target]` — no explicit site arg.
191
+ if (cmd === 'translate')
192
+ return resolveSiteDir(undefined);
193
+ // `assets <subcmd> [args...] [target] [site]`: layout differs by subcmd.
194
+ if (cmd === 'assets') {
195
+ const subcmd = parsed.positional[0];
196
+ const sitePositional = subcmd === 'info' ? parsed.positional[3] : parsed.positional[2];
197
+ return resolveSiteDir(sitePositional);
198
+ }
199
+ // `archive <subcmd> [args...] [target] [site]`: site at trailingStart+1.
200
+ if (cmd === 'archive') {
201
+ const subcmd = parsed.positional[0];
202
+ const requiredArgs = subcmd === 'rename' ? 2 : subcmd === 'purge' || subcmd === 'restore' ? 1 : 0;
203
+ return resolveSiteDir(parsed.positional[1 + requiredArgs + 1]);
204
+ }
205
+ // Unknown command → auto-detect (main's switch will reject with a
206
+ // clear "Unknown command" error after env-load runs).
207
+ return resolveSiteDir(undefined);
208
+ }
209
+ /**
210
+ * Load `.env` + `.env.local` from project root and site dir, in that
211
+ * order, so per-site secrets override project-wide defaults. Must run
212
+ * BEFORE the first config-eval (the site's storage / deploy / etc.
213
+ * factories read `process.env` synchronously at construction). Skipped
214
+ * in CI — CI runners inject env directly.
215
+ *
216
+ * Per Q1 of CLI env-loading discipline: site-local `.env` is honored
217
+ * (gazetta.studio has its own `.env` with R2/Cloudflare creds; this
218
+ * function ensures factories see them at config-eval time).
219
+ */
220
+ function loadEnvFiles(siteDir) {
221
+ if (process.env.CI)
222
+ return;
223
+ const projectRoot = detectProjectRoot(siteDir);
224
+ const envDirs = projectRoot !== siteDir ? [projectRoot, siteDir] : [siteDir];
225
+ for (const dir of envDirs) {
226
+ for (const name of ['.env', '.env.local']) {
227
+ const envPath = join(dir, name);
228
+ if (!existsSync(envPath))
229
+ continue;
230
+ for (const line of readFileSync(envPath, 'utf-8').split('\n')) {
231
+ const m = line.match(/^\s*([A-Z_][A-Z0-9_]*)\s*=\s*(.*)$/);
232
+ if (m && !(m[1] in process.env))
233
+ process.env[m[1]] = m[2].replace(/^["']|["']$/g, '');
234
+ }
235
+ }
236
+ }
237
+ }
125
238
  function printHelp() {
126
239
  console.log(`
127
240
  gazetta - Stateless CMS for composable websites
@@ -141,6 +254,20 @@ function printHelp() {
141
254
  gazetta undo [target] [site] Restore the previous revision (soft undo)
142
255
  gazetta rollback <rev> [target] [site]
143
256
  Restore an arbitrary revision by id
257
+ gazetta assets list [target] [site]
258
+ List assets on a target
259
+ gazetta assets info <name> [target] [site]
260
+ Show full detail (variants, overrides, refs) for one asset
261
+ gazetta assets reindex [target] [site]
262
+ Rebuild the asset-refs sidecar index from manifests
263
+ gazetta archive list [--kind=page|fragment] [target] [site]
264
+ List archived items
265
+ gazetta archive purge <name> [--kind=...] [--force] [target] [site]
266
+ Permanently delete an archive
267
+ gazetta archive restore <name> [--kind=...] [target] [site]
268
+ Unarchive an item (back to live)
269
+ gazetta archive rename <oldname> <newname> [--kind=...] [--no-keep-alias] [target] [site]
270
+ Rename a live item; keeps the redirect by default
144
271
  gazetta help Show this help message
145
272
 
146
273
  Options:
@@ -154,7 +281,7 @@ function printHelp() {
154
281
  Site is auto-detected from sites/ directory. If multiple sites exist,
155
282
  you'll be prompted to choose (or pass it as an argument).
156
283
 
157
- Target is auto-detected as the first target in site.yaml. If multiple
284
+ Target is auto-detected as the first target in site.config.ts. If multiple
158
285
  targets exist, you'll be prompted to choose (or pass it as an argument).
159
286
 
160
287
  Examples:
@@ -207,24 +334,30 @@ function parseArgs(input) {
207
334
  * For commands like `publish` and `serve`, the first positional is the target
208
335
  * and the second is the site.
209
336
  */
337
+ /** Returns true if the directory contains a Gazetta site config (`site.config.ts`/.js/.mjs). */
338
+ function hasSiteConfig(dir) {
339
+ return (existsSync(join(dir, 'site.config.ts')) ||
340
+ existsSync(join(dir, 'site.config.js')) ||
341
+ existsSync(join(dir, 'site.config.mjs')));
342
+ }
210
343
  async function resolveSiteDir(positionalSite) {
211
344
  // Explicit site dir provided
212
345
  if (positionalSite) {
213
346
  const dir = resolve(positionalSite);
214
- if (existsSync(join(dir, 'site.yaml')))
347
+ if (hasSiteConfig(dir))
215
348
  return dir;
216
349
  // Maybe it's a site name under sites/
217
350
  const sitesSubdir = resolve('sites', positionalSite);
218
- if (existsSync(join(sitesSubdir, 'site.yaml')))
351
+ if (hasSiteConfig(sitesSubdir))
219
352
  return sitesSubdir;
220
353
  // Maybe it's a project root with sites/
221
354
  const mainSite = resolve(dir, 'sites/main');
222
- if (existsSync(join(mainSite, 'site.yaml')))
355
+ if (hasSiteConfig(mainSite))
223
356
  return mainSite;
224
357
  return dir; // let loadSite produce a clear error
225
358
  }
226
359
  // Auto-detect: check current dir first
227
- if (existsSync(join(resolve('.'), 'site.yaml')))
360
+ if (hasSiteConfig(resolve('.')))
228
361
  return resolve('.');
229
362
  // Check sites/ directory
230
363
  const sitesDir = resolve('sites');
@@ -232,7 +365,7 @@ async function resolveSiteDir(positionalSite) {
232
365
  const { readdirSync, statSync } = await import('node:fs');
233
366
  const sites = readdirSync(sitesDir).filter(name => {
234
367
  const dir = join(sitesDir, name);
235
- return statSync(dir).isDirectory() && existsSync(join(dir, 'site.yaml'));
368
+ return statSync(dir).isDirectory() && hasSiteConfig(dir);
236
369
  });
237
370
  if (sites.length === 1)
238
371
  return join(sitesDir, sites[0]);
@@ -264,11 +397,10 @@ async function resolveSiteDir(positionalSite) {
264
397
  async function resolveTarget(positionalTarget, siteDir) {
265
398
  if (positionalTarget)
266
399
  return positionalTarget;
267
- const siteYamlPath = join(siteDir, 'site.yaml');
268
- if (!existsSync(siteYamlPath))
400
+ const manifest = await loadSiteManifestForCli(siteDir);
401
+ if (!manifest)
269
402
  return undefined;
270
- const siteYaml = yaml.load(readFileSync(siteYamlPath, 'utf-8'));
271
- const targets = Object.keys(siteYaml.targets ?? {});
403
+ const targets = Object.keys(manifest.targets ?? {});
272
404
  if (targets.length <= 1)
273
405
  return targets[0]; // auto-select if 0 or 1
274
406
  if (process.env.CI) {
@@ -287,13 +419,27 @@ async function resolveTarget(positionalTarget, siteDir) {
287
419
  async function runInit(dir) {
288
420
  const { writeFile, mkdir } = await import('node:fs/promises');
289
421
  const target = resolve(dir);
290
- if (existsSync(join(target, 'sites')) || existsSync(join(target, 'site.yaml'))) {
422
+ if (existsSync(join(target, 'sites')) || existsSync(join(target, 'site.config.ts'))) {
291
423
  console.error(`\n Error: project already exists in ${target}\n`);
292
424
  process.exit(1);
293
425
  }
294
426
  const name = target.split('/').pop() ?? 'my-site';
295
427
  const files = {
296
- 'sites/main/site.yaml': `name: ${name}\nversion: 1.0.0\nsystemPages:\n - "404"\ntargets:\n local:\n storage:\n type: filesystem\n # environment=local, editable=true (defaults); path=./targets/local (default)\n`,
428
+ 'sites/main/site.config.ts': `import { defineSite, filesystemStorage } from 'gazetta'
429
+
430
+ export default defineSite({
431
+ name: '${name}',
432
+ version: '1.0.0',
433
+ systemPages: ['404'],
434
+ targets: {
435
+ local: {
436
+ storage: filesystemStorage(),
437
+ // environment: 'local' (default); editable: true (default for local);
438
+ // filesystemStorage() defaults path to ./targets/local
439
+ },
440
+ },
441
+ })
442
+ `,
297
443
  'templates/page-layout/index.ts': `import { z } from 'zod'
298
444
  import type { TemplateFunction } from 'gazetta'
299
445
 
@@ -413,7 +559,7 @@ export default template
413
559
  name,
414
560
  private: true,
415
561
  type: 'module',
416
- engines: { node: '>=22' },
562
+ engines: { node: REQUIRED_NODE_FLOOR },
417
563
  scripts: { dev: 'gazetta dev' },
418
564
  dependencies: { gazetta: '*', react: '^19.0.0', 'react-dom': '^19.0.0', zod: '^4.0.0' },
419
565
  }, null, 2) + '\n',
@@ -431,7 +577,7 @@ export default template
431
577
  ` ${c.dim('pages/home/')} ${c.dim('home page with hero + intro')}\n` +
432
578
  ` ${c.dim('pages/404/')} ${c.dim('error page')}\n` +
433
579
  ` ${c.dim('fragments/header/')} ${c.dim('shared header nav')}\n` +
434
- ` ${c.dim('site.yaml')} ${c.dim('site config + local target')}\n` +
580
+ ` ${c.dim('site.config.ts')} ${c.dim('site config + local target')}\n` +
435
581
  `${c.bold('package.json')}`, `Created ${c.green(name)}/`);
436
582
  // Run npm install
437
583
  const s = spinner();
@@ -450,7 +596,7 @@ export default template
450
596
  async function runPublish(siteDir, targetName, opts = {}) {
451
597
  const projectRoot = detectProjectRoot(siteDir);
452
598
  const templatesDir = join(projectRoot, 'templates');
453
- // Source comes from the default editable target in site.yaml.
599
+ // Source comes from the default editable target in site.config.ts.
454
600
  const { buildSourceContext } = await import('./bootstrap.js');
455
601
  let source, manifest, targetConfigs;
456
602
  try {
@@ -465,7 +611,7 @@ async function runPublish(siteDir, targetName, opts = {}) {
465
611
  const site = await loadSite({ contentRoot: source.contentRoot, templatesDir, manifest });
466
612
  const siteYaml = manifest;
467
613
  if (!siteYaml.targets || Object.keys(siteYaml.targets).length === 0) {
468
- console.error(`\n Error: no targets configured in site.yaml`);
614
+ console.error(`\n Error: no targets configured in site.config.ts`);
469
615
  process.exit(1);
470
616
  }
471
617
  // Determine which targets to publish to
@@ -478,19 +624,23 @@ async function runPublish(siteDir, targetName, opts = {}) {
478
624
  }
479
625
  // Initialize targets
480
626
  const { createTargetRegistry } = await import('../targets.js');
481
- const targets = await createTargetRegistry(Object.fromEntries(targetNames.map(n => [n, siteYaml.targets[n]])), siteDir);
482
- const { publishPageRendered, publishPageStatic, publishFragmentRendered, publishSiteManifest, publishFragmentIndex } = await import('../publish-rendered.js');
483
- const { publishPageAllLocales, publishFragmentAllLocales } = await import('../publish-locale.js');
484
- const { scanTemplates, templateHashesFrom, reportTemplateErrors } = await import('../templates-scan.js');
485
- const { hashManifest } = await import('../hash.js');
486
- // Validate + hash templates once for this publish run
627
+ const targets = await createTargetRegistry(Object.fromEntries(targetNames.map(n => [n, siteYaml.targets[n]])));
628
+ // Per Cut 6 cutover: per-target render loop now goes through publishRun
629
+ // (imported below at use site). publishRun owns: per-item dispatch +
630
+ // asset publish + dep indices + site manifest. CLI keeps: incremental
631
+ // skip via compareTargets, locale fan-out into ItemRefs, console
632
+ // output via onProgress, sitemap+robots+redirects+purge post-loop.
633
+ const { scanTemplates, reportTemplateErrors } = await import('../templates-scan.js');
634
+ // Validate templates once for this publish run. publishRun consumes
635
+ // templateInfos directly and computes hashes internally (per Cut 5
636
+ // expansion); CLI only needs to scan + bail on invalid templates
637
+ // before launching the run.
487
638
  const templateInfos = await scanTemplates(templatesDir, projectRoot);
488
639
  const invalid = reportTemplateErrors(templateInfos);
489
640
  if (invalid > 0) {
490
641
  console.error(`\n ${c.red('✗')} Refusing to publish with invalid templates.`);
491
642
  process.exit(1);
492
643
  }
493
- const templateHashes = templateHashesFrom(templateInfos);
494
644
  console.log();
495
645
  console.log(` ${c.bgGreen(c.bold(' gazetta '))} ${c.green('publish')} ${c.dim(site.manifest.name)}`);
496
646
  console.log();
@@ -498,6 +648,24 @@ async function runPublish(siteDir, targetName, opts = {}) {
498
648
  console.log(` ${c.dim('┃')} Fragments ${c.dim([...site.fragments.keys()].join(', '))}`);
499
649
  console.log(` ${c.dim('┃')} Targets ${targetNames.join(', ')}`);
500
650
  console.log();
651
+ // Cutover (Cut 6 of publish-pipeline-extraction): per-target render
652
+ // loop now delegates to `publishRun` for the per-target × per-item
653
+ // dispatch + asset publish + dep indices + site manifest. CLI keeps
654
+ // ownership of:
655
+ // - target registry init + per-target loop (so target-init failures
656
+ // can short-circuit just that target)
657
+ // - incremental skip via compareTargets (publishRun is filter-blind;
658
+ // CLI pre-filters ItemRefs[] based on per-locale unchanged sets)
659
+ // - locale fan-out (CLI expands page name → ItemRef[] including all
660
+ // locale variants supported by the target)
661
+ // - console output (onProgress callback formats human-readable lines)
662
+ // - sitemap / robots.txt / _redirects / cache purge (post-publishRun
663
+ // side effects on each target — kept inline)
664
+ //
665
+ // publishRun owns: per-item mode dispatch, archive short-circuit,
666
+ // sidecar emit, asset publish, dep indices, site manifest emit.
667
+ const { publishRun } = await import('../publish-run.js');
668
+ const { createContentRoot: _createContentRoot } = await import('../content-root.js');
501
669
  for (const name of targetNames) {
502
670
  const targetStorage = targets.get(name);
503
671
  if (!targetStorage) {
@@ -507,7 +675,6 @@ async function runPublish(siteDir, targetName, opts = {}) {
507
675
  const targetConfig = siteYaml.targets[name];
508
676
  const { getType } = await import('../types.js');
509
677
  const targetType = targetConfig ? getType(targetConfig) : 'static';
510
- const isStatic = targetType === 'static';
511
678
  console.log(` ${c.bold(name)} ${c.dim(`(${targetType})`)}`);
512
679
  let totalFiles = 0;
513
680
  let totalRemoved = 0;
@@ -529,96 +696,102 @@ async function runPublish(siteDir, targetName, opts = {}) {
529
696
  unchanged.add(item);
530
697
  }
531
698
  let skipped = 0;
532
- const sourceRoot = source.contentRoot;
533
- // SEO context for this target — built once, shared across all page renders.
534
- const seo = {
535
- siteName: site.manifest.name,
536
- siteUrl: targetConfig?.siteUrl,
537
- locale: site.manifest.locale,
538
- defaultOgImage: site.manifest.defaultOgImage,
539
- };
540
- if (isStatic) {
541
- // Static mode — fully assembled HTML, no fragments needed separately.
542
- // Page hash must include fragment hashes so a fragment change
543
- // invalidates every page that bakes it in (compareTargets uses the
544
- // same combination on the local side).
545
- const fragmentHashes = new Map();
546
- for (const [fragName, frag] of site.fragments) {
547
- fragmentHashes.set(fragName, hashManifest(frag, { templateHashes }));
699
+ // Build the per-target ItemRef list. CLI does the locale fan-out
700
+ // explicitly here (vs publishRun, which treats the input list
701
+ // verbatim). Each (page|fragment, locale) cell becomes one ItemRef
702
+ // unless the unchanged set covers it.
703
+ const targetLocales = targetConfig?.locales;
704
+ const itemRefs = [];
705
+ const skippedNames = new Set(); // for console output (item entirely skipped)
706
+ // Pages — default + per-locale variants, filtered by target.locales when set.
707
+ for (const [pageName] of site.pages) {
708
+ const pageLocales = site.pageLocales.get(pageName);
709
+ const localesForPage = [undefined];
710
+ if (pageLocales) {
711
+ for (const loc of pageLocales.locales.keys()) {
712
+ if (targetLocales && !targetLocales.includes(loc))
713
+ continue;
714
+ localesForPage.push(loc);
715
+ }
548
716
  }
549
- for (const [pageName, page] of site.pages) {
550
- if (unchanged.has(`pages/${pageName}`)) {
551
- skipped++;
717
+ let added = 0;
718
+ for (const loc of localesForPage) {
719
+ const key = loc ? `pages/${pageName}:${loc}` : `pages/${pageName}`;
720
+ if (unchanged.has(key))
552
721
  continue;
553
- }
554
- const manifestHash = hashManifest(page, { templateHashes, fragmentHashes });
555
- const { files } = await publishPageStatic(pageName, sourceRoot, targetStorage, templatesDir, manifestHash, site, seo);
556
- totalFiles += files;
557
- console.log(` ${c.green('✓')} ${pageName}`);
722
+ itemRefs.push({ kind: 'page', name: pageName, locale: loc });
723
+ added++;
724
+ }
725
+ if (added === 0 && localesForPage.length > 0) {
726
+ skippedNames.add(`pages/${pageName}`);
727
+ skipped++;
558
728
  }
559
729
  }
560
- else {
561
- // ESI mode fragments separate, pages with placeholders
730
+ // Fragments — only emitted as separate items for non-static targets
731
+ // (static bakes them into pages). For static, the publishRun spine
732
+ // still needs them in the site for page rendering (already loaded).
733
+ if (targetType !== 'static') {
562
734
  for (const [fragName] of site.fragments) {
563
- // Build per-locale unchanged set: null = default, 'fr' = French
564
- const fragUnchanged = new Set();
565
- if (unchanged.has(`fragments/${fragName}`))
566
- fragUnchanged.add(null);
567
735
  const fragLocales = site.fragmentLocales.get(fragName);
736
+ const localesForFrag = [undefined];
568
737
  if (fragLocales) {
569
738
  for (const loc of fragLocales.locales.keys()) {
570
- if (unchanged.has(`fragments/${fragName}:${loc}`))
571
- fragUnchanged.add(loc);
739
+ if (targetLocales && !targetLocales.includes(loc))
740
+ continue;
741
+ localesForFrag.push(loc);
572
742
  }
573
743
  }
574
- // Skip entirely if all locales unchanged
575
- const totalFragLocales = 1 + (fragLocales?.locales.size ?? 0);
576
- if (fragUnchanged.size >= totalFragLocales) {
744
+ let added = 0;
745
+ for (const loc of localesForFrag) {
746
+ const key = loc ? `fragments/${fragName}:${loc}` : `fragments/${fragName}`;
747
+ if (unchanged.has(key))
748
+ continue;
749
+ itemRefs.push({ kind: 'fragment', name: fragName, locale: loc });
750
+ added++;
751
+ }
752
+ if (added === 0 && localesForFrag.length > 0) {
753
+ skippedNames.add(`fragments/${fragName}`);
577
754
  skipped++;
578
- continue;
579
755
  }
580
- const { files, removed } = await publishFragmentAllLocales(fragName, sourceRoot, targetStorage, site, { templateHashes }, { templatesDir, targetLocales: targetConfig?.locales, unchangedLocales: fragUnchanged });
581
- totalFiles += files;
582
- totalRemoved += removed;
583
- const skippedCount = fragUnchanged.size > 0 ? ` (${fragUnchanged.size} locale${fragUnchanged.size > 1 ? 's' : ''} skipped)` : '';
584
- console.log(` ${c.green('✓')} @${fragName}${skippedCount}`);
585
756
  }
586
- for (const [pageName] of site.pages) {
587
- // Build per-locale unchanged set
588
- const pageUnchanged = new Set();
589
- if (unchanged.has(`pages/${pageName}`))
590
- pageUnchanged.add(null);
591
- const pageLocales = site.pageLocales.get(pageName);
592
- if (pageLocales) {
593
- for (const loc of pageLocales.locales.keys()) {
594
- if (unchanged.has(`pages/${pageName}:${loc}`))
595
- pageUnchanged.add(loc);
757
+ }
758
+ // Delegate the per-item × per-target render loop + asset publish +
759
+ // dep indices + site manifest to publishRun. Single-target call;
760
+ // CLI loops targets itself so per-target init failures (storage
761
+ // missing) short-circuit just that target.
762
+ const runResult = await publishRun({
763
+ items: itemRefs,
764
+ targets: [name],
765
+ site,
766
+ sourceRoot: source.contentRoot,
767
+ siteManifest: manifest,
768
+ targetStorages: new Map([[name, targetStorage]]),
769
+ templateInfos,
770
+ onProgress: ev => {
771
+ if (ev.kind === 'item-done') {
772
+ if (ev.result.ok) {
773
+ const label = ev.result.kind === 'fragment' ? `@${ev.result.name}` : ev.result.name;
774
+ const localeMark = ev.result.locale ? c.dim(` (${ev.result.locale})`) : '';
775
+ console.log(` ${c.green('✓')} ${label}${localeMark}`);
776
+ }
777
+ else {
778
+ const label = ev.result.kind === 'fragment' ? `@${ev.result.name}` : ev.result.name;
779
+ console.error(` ${c.red('✗')} ${label}: ${ev.result.code}`);
596
780
  }
597
781
  }
598
- const totalPageLocales = 1 + (pageLocales?.locales.size ?? 0);
599
- if (pageUnchanged.size >= totalPageLocales) {
600
- skipped++;
601
- continue;
602
- }
603
- const { files, removed } = await publishPageAllLocales(pageName, sourceRoot, targetStorage, site, { templateHashes }, {
604
- cache: targetConfig?.cache,
605
- templatesDir,
606
- seo,
607
- targetLocales: targetConfig?.locales,
608
- unchangedLocales: pageUnchanged,
609
- });
610
- totalFiles += files;
611
- totalRemoved += removed;
612
- const skippedCount = pageUnchanged.size > 0 ? ` (${pageUnchanged.size} locale${pageUnchanged.size > 1 ? 's' : ''} skipped)` : '';
613
- console.log(` ${c.green('✓')} ${pageName}${skippedCount}`);
782
+ },
783
+ });
784
+ // Aggregate counts from publishRun's per-target result.
785
+ const tr = runResult.targets.find(t => t.name === name);
786
+ if (tr) {
787
+ totalFiles += tr.filesWritten;
788
+ totalRemoved += tr.filesRemoved;
789
+ if (tr.failed) {
790
+ console.error(` ${c.red('✗')} target failed: ${tr.failureReason ?? 'all items failed'}`);
614
791
  }
615
792
  }
616
793
  if (skipped > 0)
617
794
  console.log(` ${c.dim(`· ${skipped} unchanged (skipped)`)}`);
618
- // Site manifest + fragment index
619
- await publishSiteManifest(sourceRoot, targetStorage, site);
620
- await publishFragmentIndex(sourceRoot, targetStorage, site);
621
- totalFiles += 2;
622
795
  // Sitemap + robots.txt — generated from target sidecars
623
796
  const siteUrl = targetConfig?.siteUrl;
624
797
  if (siteUrl) {
@@ -634,8 +807,6 @@ async function runPublish(siteDir, targetName, opts = {}) {
634
807
  if (!targetPageSidecars.has(pageName)) {
635
808
  targetPageSidecars.set(pageName, {
636
809
  hash: '',
637
- uses: [],
638
- template: page.template,
639
810
  pub: { lastPublished: now, noindex: !!page.metadata?.robots?.includes('noindex') },
640
811
  });
641
812
  }
@@ -646,8 +817,6 @@ async function runPublish(siteDir, targetName, opts = {}) {
646
817
  if (!targetPageSidecars.has(key)) {
647
818
  targetPageSidecars.set(key, {
648
819
  hash: '',
649
- uses: [],
650
- template: localePage.template,
651
820
  pub: { lastPublished: now, noindex: !!localePage.metadata?.robots?.includes('noindex') },
652
821
  });
653
822
  }
@@ -732,6 +901,36 @@ async function runPublish(siteDir, targetName, opts = {}) {
732
901
  console.log(` ${c.dim('· robots.txt')}`);
733
902
  }
734
903
  }
904
+ // Host-format redirects file (e.g. _redirects for Cloudflare/Netlify).
905
+ // Per design-soft-delete.md Q10: plain-static targets without a worker
906
+ // can't read the per-page HTML marker; this file is the host-glue
907
+ // that makes archived-page redirects work for them. Independent of
908
+ // siteUrl — the format determines whether to emit at all.
909
+ const redirectsFormat = targetConfig?.redirects?.format;
910
+ if (redirectsFormat && redirectsFormat !== 'none') {
911
+ const { emitRedirects } = await import('../runtime/redirects-emit.js');
912
+ const { allPageEntries, deriveRoute } = await import('../site-loader.js');
913
+ const archives = [];
914
+ for (const entry of allPageEntries(site)) {
915
+ if (entry.page.archived !== true)
916
+ continue;
917
+ // Locale variants share the parent's archive state in v1; the
918
+ // default-locale row is the canonical entry. Per-locale archive
919
+ // is reserved for a future cut (per `design-soft-delete.md`
920
+ // future directions).
921
+ if (entry.locale)
922
+ continue;
923
+ const from = deriveRoute(entry.name);
924
+ const to = entry.page.aliasOf ? deriveRoute(entry.page.aliasOf) : undefined;
925
+ archives.push(to !== undefined ? { from, to } : { from });
926
+ }
927
+ const result = emitRedirects(redirectsFormat, archives);
928
+ if (result && result.body !== '') {
929
+ await targetStorage.writeFile(result.filename, result.body);
930
+ totalFiles++;
931
+ console.log(` ${c.dim(`· ${result.filename}`)}`);
932
+ }
933
+ }
735
934
  const removedMsg = totalRemoved > 0 ? c.dim(` (${totalRemoved} old files cleaned)`) : '';
736
935
  console.log(`\n ${c.green('✓')} ${c.bold(name)}: ${totalFiles} files published${removedMsg}\n`);
737
936
  }
@@ -937,7 +1136,9 @@ async function runAdmin(siteDir, port) {
937
1136
  app.get('/__reload', ctx => ctx.body(null, 204));
938
1137
  const { buildSourceContext } = await import('./bootstrap.js');
939
1138
  const { source, targetConfigs } = await buildSourceContext({ projectSiteDir: siteDir });
940
- await setupProductionMode(app, source, siteDir, builtAdminDir, templatesDir, adminDir, targetConfigs);
1139
+ const manifest = await loadSiteManifestForCli(siteDir);
1140
+ const hookContributions = readHookContributions(manifest);
1141
+ await setupProductionMode(app, source, siteDir, builtAdminDir, templatesDir, adminDir, targetConfigs, hookContributions, manifest);
941
1142
  // SPA fallback for non-API admin routes
942
1143
  app.get('*', ctx => {
943
1144
  const indexPath = join(builtAdminDir, 'index.html');
@@ -945,10 +1146,10 @@ async function runAdmin(siteDir, port) {
945
1146
  return ctx.html(readFileSync(indexPath, 'utf-8'));
946
1147
  return ctx.notFound();
947
1148
  });
948
- const siteYaml = yaml.load(readFileSync(join(siteDir, 'site.yaml'), 'utf-8'));
1149
+ const siteManifest = (await loadSiteManifestForCli(siteDir)) ?? { name: 'gazetta' };
949
1150
  const server = serve({ fetch: app.fetch, port }, () => {
950
1151
  console.log();
951
- console.log(` ${c.bgGreen(c.bold(' gazetta '))} ${c.green('admin')} ${c.dim(siteYaml.name)}`);
1152
+ console.log(` ${c.bgGreen(c.bold(' gazetta '))} ${c.green('admin')} ${c.dim(siteManifest.name)}`);
952
1153
  console.log();
953
1154
  console.log(` ${c.dim('┃')} Admin ${c.cyan(`http://localhost:${port}/admin`)}`);
954
1155
  console.log();
@@ -961,25 +1162,24 @@ async function runAdmin(siteDir, port) {
961
1162
  }
962
1163
  }
963
1164
  async function runServe(siteDir, port, targetName) {
964
- const siteYamlPath = join(siteDir, 'site.yaml');
965
- if (!existsSync(siteYamlPath)) {
966
- console.error(`\n Error: ${siteYamlPath} not found\n`);
1165
+ const siteYaml = await loadSiteManifestForCli(siteDir);
1166
+ if (!siteYaml) {
1167
+ console.error(`\n Error: no site config found in ${siteDir} (looked for site.config.ts)\n`);
967
1168
  process.exit(1);
968
1169
  }
969
- const siteYaml = yaml.load(readFileSync(siteYamlPath, 'utf-8'));
970
1170
  if (!siteYaml.targets || Object.keys(siteYaml.targets).length === 0) {
971
- console.error('\n Error: no targets configured in site.yaml\n');
1171
+ console.error('\n Error: no targets configured in site config\n');
972
1172
  process.exit(1);
973
1173
  }
974
1174
  const name = targetName ?? Object.keys(siteYaml.targets)[0];
975
1175
  const config = siteYaml.targets[name];
976
1176
  if (!config) {
977
- console.error(`\n Error: target "${name}" not found in site.yaml\n`);
1177
+ console.error(`\n Error: target "${name}" not found in site config\n`);
978
1178
  process.exit(1);
979
1179
  }
980
- const { createStorageProvider } = await import('../targets.js');
981
- // Pass targetName so filesystem path defaults to ./targets/<name> when unset.
982
- const storage = await createStorageProvider(config.storage, siteDir, name);
1180
+ // Storage is already a constructed provider (Path X — operator-facing
1181
+ // factory ran at config-eval).
1182
+ const storage = config.storage;
983
1183
  const { getType } = await import('../types.js');
984
1184
  const { createServer } = await import('../serve.js');
985
1185
  const app = createServer({ storage, type: getType(config) });
@@ -998,16 +1198,13 @@ async function runServe(siteDir, port, targetName) {
998
1198
  }
999
1199
  }
1000
1200
  async function runDeploy(siteDir, targetName) {
1001
- const { execSync } = await import('node:child_process');
1002
- const { writeFile, mkdir, rm } = await import('node:fs/promises');
1003
- const siteYamlPath = join(siteDir, 'site.yaml');
1004
- if (!existsSync(siteYamlPath)) {
1005
- console.error(`\n Error: No site.yaml found at ${siteDir}\n`);
1201
+ const siteYaml = await loadSiteManifestForCli(siteDir);
1202
+ if (!siteYaml) {
1203
+ console.error(`\n Error: no site config found at ${siteDir} (looked for site.config.ts)\n`);
1006
1204
  process.exit(1);
1007
1205
  }
1008
- const siteYaml = yaml.load(readFileSync(siteYamlPath, 'utf-8'));
1009
1206
  if (!siteYaml.targets) {
1010
- console.error(`\n Error: No targets configured in site.yaml\n`);
1207
+ console.error(`\n Error: No targets configured in site config\n`);
1011
1208
  process.exit(1);
1012
1209
  }
1013
1210
  if (!targetName) {
@@ -1019,130 +1216,193 @@ async function runDeploy(siteDir, targetName) {
1019
1216
  console.error(`\n Error: Unknown target "${targetName}". Available: ${Object.keys(siteYaml.targets).join(', ')}\n`);
1020
1217
  process.exit(1);
1021
1218
  }
1022
- if (!target.worker) {
1023
- console.error(`\n Error: Target "${targetName}" has no worker config. Add to site.yaml:\n\n worker:\n type: cloudflare\n name: my-site\n`);
1219
+ if (!target.deploy) {
1220
+ console.error(`\n Error: Target "${targetName}" has no \`deploy:\` adapter configured. Add to site.config.ts:\n\n ` +
1221
+ `import { cloudflareWorkersDeploy } from 'gazetta'\n ` +
1222
+ `deploy: cloudflareWorkersDeploy({\n ` +
1223
+ ` apiToken: process.env.CLOUDFLARE_API_TOKEN!,\n ` +
1224
+ ` accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,\n ` +
1225
+ ` name: '${targetName}',\n ` +
1226
+ ` bucket: '${targetName}',\n })\n\n` +
1227
+ `For container hosts (Fly.io, Cloud Run, Railway, Render), use platform-native\n` +
1228
+ `deploy tooling; see docs/container-deployment.md.\n`);
1024
1229
  process.exit(1);
1025
1230
  }
1026
- if (target.worker.type !== 'cloudflare') {
1027
- console.error(`\n Error: Unsupported worker type "${target.worker.type}". Currently only "cloudflare" is supported.\n`);
1028
- process.exit(1);
1029
- }
1030
- // Generate worker in temp dir
1031
- const workerName = target.worker.name ?? targetName;
1032
- const bucketName = target.storage.bucket ?? workerName;
1033
- const tmpDir = join(siteDir, '.gazetta-deploy');
1034
- await rm(tmpDir, { recursive: true, force: true });
1035
- await mkdir(tmpDir, { recursive: true });
1036
- // Generate wrangler.toml
1037
- let wranglerToml = `name = "${workerName}"\nmain = "index.ts"\ncompatibility_date = "2024-12-01"\nworkers_dev = true\n\n[[r2_buckets]]\nbinding = "SITE_BUCKET"\nbucket_name = "${bucketName}"\n`;
1038
- // Add custom domain route if siteUrl is configured
1039
- if (target.siteUrl) {
1040
- const url = new URL(target.siteUrl);
1041
- const hostname = url.hostname;
1042
- wranglerToml += `\n[[routes]]\npattern = "${hostname}/*"\nzone_name = "${hostname}"\n`;
1043
- }
1044
- await writeFile(join(tmpDir, 'wrangler.toml'), wranglerToml);
1045
- // Generate worker entry point
1046
- const workerCode = `import { createWorker } from 'gazetta/workers/cloudflare-r2'\nexport default createWorker()\n`;
1047
- await writeFile(join(tmpDir, 'index.ts'), workerCode);
1048
- // Generate package.json for wrangler
1049
- const pkgJson = JSON.stringify({
1050
- type: 'module',
1051
- dependencies: { gazetta: '*', hono: '*' },
1052
- });
1053
- await writeFile(join(tmpDir, 'package.json'), pkgJson);
1054
- // Install deps and deploy
1055
- console.log(` Deploying worker "${workerName}" to Cloudflare...`);
1231
+ const adapter = target.deploy;
1232
+ console.log(` ${c.cyan(`Deploying via ${adapter.name}...`)}`);
1233
+ // Build a no-op logger; v1 deploy doesn't require structured logging yet.
1234
+ const logger = {
1235
+ debug: (_o, _m) => { },
1236
+ info: (_o, _m) => { },
1237
+ warn: (o, m) => console.warn(` ${typeof o === 'string' ? o : (m ?? '')}`),
1238
+ error: (o, m) => console.error(` ${typeof o === 'string' ? o : (m ?? '')}`),
1239
+ };
1056
1240
  try {
1057
- execSync('npm install --install-links ' + resolve(import.meta.dirname, '../..'), { cwd: tmpDir, stdio: 'pipe' });
1058
- const output = execSync('npx wrangler deploy', { cwd: tmpDir, stdio: 'pipe' }).toString();
1059
- const urlMatch = output.match(/https:\/\/[^\s]+/);
1060
- console.log(` Worker deployed: ${urlMatch?.[0] ?? workerName}`);
1241
+ const result = await adapter.execute({
1242
+ target,
1243
+ targetName,
1244
+ // outputDir not used by worker-deploy adapters (worker reads R2 at request).
1245
+ // Adapters that need bytes (static-host) read storage themselves.
1246
+ outputDir: join(siteDir, 'dist', targetName),
1247
+ storage: target.storage,
1248
+ env: process.env,
1249
+ logger,
1250
+ signal: new AbortController().signal,
1251
+ });
1252
+ if (result.url)
1253
+ console.log(` ${c.green('✓')} Deployed: ${result.url}`);
1254
+ else
1255
+ console.log(` ${c.green('✓')} Deployed.`);
1061
1256
  if (target.siteUrl)
1062
1257
  console.log(` Site: ${target.siteUrl}`);
1063
1258
  }
1064
1259
  catch (err) {
1065
- const stderr = err.stderr?.toString() ?? err.message;
1066
- console.error(`\n Deploy failed: ${stderr}\n`);
1260
+ const message = err instanceof Error ? err.message : String(err);
1261
+ const adapterTag = err.adapter ?? adapter.name;
1262
+ console.error(`\n ${c.red(`Deploy failed (${adapterTag}):`)} ${message}\n`);
1067
1263
  process.exit(1);
1068
1264
  }
1069
- finally {
1070
- await rm(tmpDir, { recursive: true, force: true });
1071
- }
1072
- console.log(`\n ${c.green('✓')} Worker deployed. Now publish content:\n ${c.cyan(`gazetta publish ${targetName}`)}\n`);
1265
+ console.log(`\n ${c.green('✓')} Deploy complete. Publish content with:\n ${c.cyan(`gazetta publish ${targetName}`)}\n`);
1073
1266
  }
1074
- async function runValidate(siteDir) {
1267
+ const QUALITY_VALIDATORS = new Set(['accessibility', 'html-validity', 'broken-links']);
1268
+ async function runValidate(siteDir, rawArgs = []) {
1075
1269
  const projectRoot = detectProjectRoot(siteDir);
1076
1270
  const templatesDir = join(projectRoot, 'templates');
1271
+ const opts = parseValidateFlags(rawArgs);
1077
1272
  console.log();
1078
1273
  console.log(` ${c.bgGreen(c.bold(' gazetta '))} ${c.green('validate')} ${c.dim(siteDir)}`);
1079
1274
  console.log();
1080
- // 1. Check site.yaml + load default editable target's content
1275
+ // 1. Load site
1276
+ const configLabel = existsSync(join(siteDir, 'site.config.ts'))
1277
+ ? 'site.config.ts'
1278
+ : existsSync(join(siteDir, 'site.config.js'))
1279
+ ? 'site.config.js'
1280
+ : 'site.config.mjs';
1281
+ const { buildSourceContext } = await import('./bootstrap.js');
1081
1282
  let site;
1283
+ let source;
1082
1284
  try {
1083
- const { buildSourceContext } = await import('./bootstrap.js');
1084
- const { source, manifest } = await buildSourceContext({ projectSiteDir: siteDir });
1085
- site = await loadSite({ contentRoot: source.contentRoot, templatesDir, manifest });
1086
- console.log(` ${c.green('✓')} site.yaml ${c.dim(`— ${site.manifest.name}`)}`);
1285
+ const built = await buildSourceContext({ projectSiteDir: siteDir });
1286
+ source = built.source;
1287
+ site = await loadSite({ contentRoot: source.contentRoot, templatesDir, manifest: built.manifest });
1288
+ console.log(` ${c.green('✓')} ${configLabel} ${c.dim(`— ${site.manifest.name}`)}`);
1087
1289
  }
1088
1290
  catch (err) {
1089
- console.error(` ${c.red('✗')} site.yaml ${c.dim(`— ${err.message}`)}`);
1291
+ console.error(` ${c.red('✗')} ${configLabel} ${c.dim(`— ${err.message}`)}`);
1090
1292
  process.exit(1);
1091
1293
  }
1092
- let errors = 0;
1093
- // 2. Validate all fragments
1094
- for (const [fragName, frag] of site.fragments) {
1095
- try {
1096
- const { resolveComponent } = await import('../resolver.js');
1097
- const ctx = { site, templatesDir: site.templatesDir, visited: new Set(), path: [`@${fragName}`] };
1098
- await resolveComponent(`@${fragName}`, ctx);
1099
- const childCount = frag.components?.length ?? 0;
1100
- console.log(` ${c.green('✓')} @${fragName} ${c.dim(`(${childCount} components)`)}`);
1101
- }
1102
- catch (err) {
1103
- console.error(` ${c.red('✗')} @${fragName} ${c.dim(`— ${err.message}`)}`);
1104
- errors++;
1105
- }
1294
+ // 2. Build the registry. Skip quality validators (a11y, html-validity)
1295
+ // unless `--include-quality` they trigger rendering and add seconds
1296
+ // per page; default keeps the CLI snappy for CI ref-existence gating.
1297
+ const { defaultValidatorRegistry } = await import('../validation/default-registry.js');
1298
+ const { createValidatorRegistry } = await import('../validation/registry.js');
1299
+ const fullRegistry = defaultValidatorRegistry();
1300
+ const filtered = opts.includeQuality
1301
+ ? fullRegistry
1302
+ : createValidatorRegistry(fullRegistry.all().filter(v => !QUALITY_VALIDATORS.has(v.name)));
1303
+ // 3. Run via the scanner — same orchestrator as the admin background scan,
1304
+ // so the CLI exercises identical code paths.
1305
+ const { createValidationScanner } = await import('../validation/scanner.js');
1306
+ const { createMemoryCache } = await import('../cache/memory.js');
1307
+ const scanner = createValidationScanner({
1308
+ storage: source.storage,
1309
+ contentRoot: source.contentRoot,
1310
+ registry: filtered,
1311
+ cache: createMemoryCache(),
1312
+ siteOptions: { templatesDir, manifest: site.manifest },
1313
+ loadSiteImpl: async () => site,
1314
+ });
1315
+ await scanner.scanAll();
1316
+ const allIssues = scanner.allIssues();
1317
+ // 4. Per-item summary. Pages first, then fragments — matches the existing
1318
+ // output ordering for unsurprising diff vs. the prior implementation.
1319
+ let errorCount = 0;
1320
+ let warnCount = 0;
1321
+ let infoCount = 0;
1322
+ const issuesByPath = new Map();
1323
+ for (const issue of allIssues) {
1324
+ const list = issuesByPath.get(issue.itemPath) ?? [];
1325
+ list.push(issue);
1326
+ issuesByPath.set(issue.itemPath, list);
1327
+ if (issue.severity === 'error')
1328
+ errorCount++;
1329
+ else if (issue.severity === 'warn')
1330
+ warnCount++;
1331
+ else
1332
+ infoCount++;
1333
+ }
1334
+ function summaryGlyph(issues) {
1335
+ if (issues.some(i => i.severity === 'error'))
1336
+ return { glyph: '✗', color: c.red };
1337
+ if (issues.some(i => i.severity === 'warn'))
1338
+ return { glyph: '⚠', color: c.yellow };
1339
+ if (issues.length > 0)
1340
+ return { glyph: 'ⓘ', color: c.cyan };
1341
+ return { glyph: '✓', color: c.green };
1342
+ }
1343
+ function shouldShow(severity) {
1344
+ if (opts.severity === 'all')
1345
+ return true;
1346
+ if (opts.severity === 'warn')
1347
+ return severity !== 'info';
1348
+ return severity === 'error';
1106
1349
  }
1107
- // 3. Validate all pages
1108
1350
  for (const [pageName, page] of site.pages) {
1109
- try {
1110
- await resolvePage(pageName, site);
1111
- const componentCount = page.components?.length ?? 0;
1112
- const fragmentCount = page.components?.filter(cc => typeof cc === 'string' && cc.startsWith('@')).length ?? 0;
1113
- console.log(` ${c.green('✓')} ${pageName} ${c.dim(`(${componentCount} components, ${fragmentCount} fragments)`)}`);
1351
+ const path = `${page.dir}/page.json`;
1352
+ const issues = issuesByPath.get(path) ?? [];
1353
+ const visible = issues.filter(i => shouldShow(i.severity));
1354
+ const { glyph, color } = summaryGlyph(visible);
1355
+ const componentCount = page.components?.length ?? 0;
1356
+ console.log(` ${color(glyph)} ${pageName} ${c.dim(`(${componentCount} components)`)}`);
1357
+ if (opts.verbose) {
1358
+ for (const issue of visible) {
1359
+ console.log(` ${severityIcon(issue.severity)} ${issue.message}`);
1360
+ }
1114
1361
  }
1115
- catch (err) {
1116
- console.error(` ${c.red('✗')} ${pageName} ${c.dim(`— ${err.message}`)}`);
1117
- errors++;
1362
+ }
1363
+ for (const [fragName, frag] of site.fragments) {
1364
+ const path = `${frag.dir}/fragment.json`;
1365
+ const issues = issuesByPath.get(path) ?? [];
1366
+ const visible = issues.filter(i => shouldShow(i.severity));
1367
+ const { glyph, color } = summaryGlyph(visible);
1368
+ const childCount = frag.components?.length ?? 0;
1369
+ console.log(` ${color(glyph)} @${fragName} ${c.dim(`(${childCount} components)`)}`);
1370
+ if (opts.verbose) {
1371
+ for (const issue of visible) {
1372
+ console.log(` ${severityIcon(issue.severity)} ${issue.message}`);
1373
+ }
1118
1374
  }
1119
1375
  }
1120
- // 4. List templates (project-level filesystem, not target content)
1376
+ // 5. Project-structure checks (orphaned editors, missing custom fields).
1377
+ // These are project-layout concerns rather than per-item content rules,
1378
+ // so they don't fit the Validator interface — kept inline here.
1379
+ const adminDir = join(projectRoot, 'admin');
1121
1380
  const projectStorage = createFilesystemProvider();
1122
1381
  let templateNames = [];
1123
1382
  try {
1124
1383
  const entries = await projectStorage.readDir(templatesDir);
1125
- templateNames = entries.filter(e => e.isDirectory).map((e) => e.name);
1126
- console.log(` ${c.green('✓')} ${c.dim(`${templateNames.length} templates`)}`);
1384
+ templateNames = entries.filter(e => e.isDirectory).map(e => e.name);
1127
1385
  }
1128
1386
  catch {
1129
- console.log(` ${c.yellow('⚠')} ${c.dim('templates/ directory not found')}`);
1387
+ /* templates dir missing — site already errored above */
1130
1388
  }
1131
- // 5. Check for orphaned editors (editor exists but template doesn't)
1132
- const adminDir = join(projectRoot, 'admin');
1389
+ // 5a. Orphaned editors: editor file exists but no matching template.
1390
+ // Always shown regardless of --severity since these are structural
1391
+ // issues operators need to know about even at the strictest filter.
1133
1392
  const editorsDir = join(adminDir, 'editors');
1134
1393
  if (existsSync(editorsDir)) {
1135
- const editorFiles = (await import('node:fs'))
1136
- .readdirSync(editorsDir)
1137
- .filter(f => f.endsWith('.ts') || f.endsWith('.tsx'));
1394
+ const fs = await import('node:fs');
1395
+ const editorFiles = fs.readdirSync(editorsDir).filter(f => f.endsWith('.ts') || f.endsWith('.tsx'));
1138
1396
  for (const file of editorFiles) {
1139
1397
  const editorName = file.replace(/\.(ts|tsx)$/, '');
1140
1398
  if (!templateNames.includes(editorName)) {
1141
1399
  console.log(` ${c.yellow('⚠')} orphaned editor: ${c.dim(`admin/editors/${file}`)} ${c.dim('— no matching template')}`);
1400
+ warnCount++;
1142
1401
  }
1143
1402
  }
1144
1403
  }
1145
- // 6. Check for missing custom fields (schema references field but file doesn't exist)
1404
+ // 5b. Missing custom fields: schema references field: 'name' but no
1405
+ // admin/fields/name.{ts,tsx}. Hard error — render fails without it.
1146
1406
  const fieldsDir = join(adminDir, 'fields');
1147
1407
  const fieldFiles = existsSync(fieldsDir)
1148
1408
  ? (await import('node:fs'))
@@ -1150,96 +1410,60 @@ async function runValidate(siteDir) {
1150
1410
  .filter(f => f.endsWith('.ts') || f.endsWith('.tsx'))
1151
1411
  .map(f => f.replace(/\.(ts|tsx)$/, ''))
1152
1412
  : [];
1153
- const { loadTemplate } = await import('../template-loader.js');
1154
- const zod = await import('zod');
1155
- for (const tplName of templateNames) {
1156
- try {
1157
- const loaded = await loadTemplate(projectStorage, templatesDir, tplName);
1158
- const jsonSchema = zod.z.toJSONSchema(loaded.schema);
1159
- const props = jsonSchema.properties;
1160
- if (!props)
1161
- continue;
1162
- for (const [propName, prop] of Object.entries(props)) {
1163
- const fieldRef = prop.field;
1164
- if (fieldRef && !fieldFiles.includes(fieldRef)) {
1165
- console.error(` ${c.red('✗')} template ${tplName}.${propName} references field "${fieldRef}" ${c.dim('— not found in admin/fields/')}`);
1166
- errors++;
1167
- }
1168
- }
1169
- }
1170
- catch {
1171
- /* template load errors already caught above */
1172
- }
1173
- }
1174
- // 7. Locale validation
1175
- const { defaultLocaleFor } = await import('../locale.js');
1176
- const defLoc = defaultLocaleFor(site.manifest);
1177
- const hasI18n = !!site.manifest.locales?.supported?.length;
1178
- // 7a. Warn about orphaned locale files when i18n is disabled
1179
- if (!hasI18n && (site.pageLocales.size > 0 || site.fragmentLocales.size > 0)) {
1180
- const orphanCount = site.pageLocales.size + site.fragmentLocales.size;
1181
- console.log(` ${c.yellow('⚠')} ${orphanCount} locale file${orphanCount > 1 ? 's' : ''} found but i18n is disabled ${c.dim('— add locales.supported to site.yaml or remove *.locale.json files')}`);
1182
- }
1183
- // 7b. Warn about ambiguous page.en.json when en is default
1184
- if (hasI18n) {
1185
- for (const [name, entry] of site.pageLocales) {
1186
- if (entry.locales.has(defLoc)) {
1187
- console.log(` ${c.yellow('⚠')} page.${defLoc}.json in ${name} is ambiguous ${c.dim(`— "${defLoc}" is the default locale, use page.json instead`)}`);
1188
- }
1189
- }
1190
- for (const [name, entry] of site.fragmentLocales) {
1191
- if (entry.locales.has(defLoc)) {
1192
- console.log(` ${c.yellow('⚠')} fragment.${defLoc}.json in ${name} is ambiguous ${c.dim(`— "${defLoc}" is the default locale, use fragment.json instead`)}`);
1193
- }
1194
- }
1195
- }
1196
- // 7c. Validate locale variant template/fragment refs
1197
- if (hasI18n) {
1198
- for (const [pageName, entry] of site.pageLocales) {
1199
- for (const [locale] of entry.locales) {
1200
- try {
1201
- await resolvePage(pageName, site, locale);
1202
- }
1203
- catch (err) {
1204
- console.error(` ${c.red('✗')} ${pageName} (${locale}) ${c.dim(`— ${err.message}`)}`);
1205
- errors++;
1206
- }
1207
- }
1208
- }
1209
- }
1210
- // 8. Cross-domain hreflang bidirectional check
1211
- // For per-domain targets (each with siteUrl + single locale), verify that
1212
- // all targets serving the same page cross-link to each other.
1213
- if (hasI18n && site.manifest.targets) {
1214
- const targetsWithSiteUrl = Object.entries(site.manifest.targets).filter(([, cfg]) => cfg.siteUrl && cfg.locales?.length === 1);
1215
- if (targetsWithSiteUrl.length > 1) {
1216
- const localeToUrl = new Map();
1217
- for (const [, cfg] of targetsWithSiteUrl) {
1218
- localeToUrl.set(cfg.locales[0], cfg.siteUrl);
1219
- }
1220
- const missingPairs = [];
1221
- for (const [locA, urlA] of localeToUrl) {
1222
- for (const [locB, urlB] of localeToUrl) {
1223
- if (locA === locB)
1224
- continue;
1225
- // Each target's sitemap should cross-link to the other
1226
- // We can't check the actual sitemaps here (would need network),
1227
- // but we can verify the config is consistent
1413
+ if (templateNames.length > 0) {
1414
+ const { loadTemplate } = await import('../template-loader.js');
1415
+ const zod = await import('zod');
1416
+ for (const tplName of templateNames) {
1417
+ try {
1418
+ const loaded = await loadTemplate(projectStorage, templatesDir, tplName);
1419
+ const jsonSchema = zod.z.toJSONSchema(loaded.schema);
1420
+ const props = jsonSchema.properties;
1421
+ if (!props)
1422
+ continue;
1423
+ for (const [propName, prop] of Object.entries(props)) {
1424
+ const fieldRef = prop.field;
1425
+ if (fieldRef && !fieldFiles.includes(fieldRef)) {
1426
+ console.error(` ${c.red('✗')} template ${tplName}.${propName} references field "${fieldRef}" ${c.dim('— not found in admin/fields/')}`);
1427
+ errorCount++;
1428
+ }
1228
1429
  }
1229
1430
  }
1230
- if (localeToUrl.size > 1) {
1231
- console.log(` ${c.green('✓')} cross-domain hreflang: ${[...localeToUrl.entries()].map(([l, u]) => `${l} → ${u}`).join(', ')}`);
1431
+ catch {
1432
+ /* template load errors surface via referenced-template-exists */
1232
1433
  }
1233
1434
  }
1234
1435
  }
1436
+ // 6. Footer + exit code.
1235
1437
  console.log();
1236
- if (errors > 0) {
1237
- console.error(` ${errors} error${errors > 1 ? 's' : ''} found.\n`);
1238
- process.exit(1);
1239
- }
1240
- else {
1241
- console.log(` All good.\n`);
1438
+ const totalShown = opts.severity === 'all'
1439
+ ? errorCount + warnCount + infoCount
1440
+ : opts.severity === 'warn'
1441
+ ? errorCount + warnCount
1442
+ : errorCount;
1443
+ if (totalShown === 0) {
1444
+ console.log(` ${c.green('All good.')}\n`);
1445
+ return;
1242
1446
  }
1447
+ const parts = [];
1448
+ if (errorCount > 0)
1449
+ parts.push(`${errorCount} error${errorCount > 1 ? 's' : ''}`);
1450
+ if (warnCount > 0 && opts.severity !== 'error')
1451
+ parts.push(`${warnCount} warning${warnCount > 1 ? 's' : ''}`);
1452
+ if (infoCount > 0 && opts.severity === 'all')
1453
+ parts.push(`${infoCount} info`);
1454
+ const summary = parts.join(', ');
1455
+ console.log(` ${summary}.\n`);
1456
+ // Exit non-zero if errors OR (warns AND warn-as-error is on).
1457
+ const fail = errorCount > 0 || (opts.warnAsError && warnCount > 0);
1458
+ if (fail)
1459
+ process.exit(1);
1460
+ }
1461
+ function severityIcon(severity) {
1462
+ if (severity === 'error')
1463
+ return c.red('✗');
1464
+ if (severity === 'warn')
1465
+ return c.yellow('⚠');
1466
+ return c.cyan('ⓘ');
1243
1467
  }
1244
1468
  function renderErrorOverlay(err) {
1245
1469
  const message = err.message.replace(/</g, '&lt;').replace(/>/g, '&gt;');
@@ -1290,7 +1514,7 @@ async function runDev(siteDir, port) {
1290
1514
  const projectRoot = detectProjectRoot(siteDir);
1291
1515
  const templatesDir = join(projectRoot, 'templates');
1292
1516
  const adminDir = join(projectRoot, 'admin');
1293
- // Build the source context from the default editable target in site.yaml.
1517
+ // Build the source context from the default editable target in site.config.ts.
1294
1518
  // Cloud targets aren't init'd — admin API handles them lazily.
1295
1519
  const { buildSourceContext } = await import('./bootstrap.js');
1296
1520
  const { source, manifest, targetConfigs } = await buildSourceContext({ projectSiteDir: siteDir });
@@ -1337,8 +1561,15 @@ async function runDev(siteDir, port) {
1337
1561
  }
1338
1562
  return next();
1339
1563
  });
1564
+ // ---- Asset serve route ----
1565
+ // Serves /assets/* from the active source target's storage. Matches the
1566
+ // URL pattern emitted by the asset resolver, so templates rendering
1567
+ // <img src="/assets/hero-a3b2c1d4.jpg"> load from here in dev.
1568
+ const { assetServeRoutes } = await import('../assets/serve-route.js');
1569
+ app.route('/', assetServeRoutes(async () => source.storage));
1340
1570
  // ---- Site page routes (default + locale variants) ----
1341
1571
  const { allPageEntries } = await import('../site-loader.js');
1572
+ const { defaultLocaleFor: _devDefaultLocaleFor } = await import('../locale.js');
1342
1573
  for (const { name: pageName, page, locale: pageLocale } of allPageEntries(site)) {
1343
1574
  app.get(page.route, async (c) => {
1344
1575
  try {
@@ -1353,7 +1584,7 @@ async function runDev(siteDir, port) {
1353
1584
  route: freshPage?.route ?? page.route,
1354
1585
  seo: {
1355
1586
  siteName: freshSite.manifest.name,
1356
- locale: pageLocale ?? freshSite.manifest.locale,
1587
+ locale: pageLocale ?? _devDefaultLocaleFor(freshSite.manifest),
1357
1588
  defaultOgImage: freshSite.manifest.defaultOgImage,
1358
1589
  },
1359
1590
  });
@@ -1408,14 +1639,17 @@ async function runDev(siteDir, port) {
1408
1639
  const isDevMode = cmsWebDir !== null;
1409
1640
  // Admin Hono instance — captured so the template file watcher can
1410
1641
  // invalidate its memoized template-scan cache on .ts/.tsx changes.
1642
+ // `rescanForTemplate` is optional: only the dev-mode setup decorates
1643
+ // it (production has no file watcher). Watcher uses optional-chaining.
1411
1644
  let cmsApp = null;
1645
+ const hookContributions = readHookContributions(manifest);
1412
1646
  if (isDevMode) {
1413
1647
  // Dev mode: mount CMS API inline (same process = shared template cache)
1414
- cmsApp = await setupCmsApi(app, source, siteDir, templatesDir, adminDir, targetConfigs);
1648
+ cmsApp = await setupCmsApi(app, source, siteDir, templatesDir, adminDir, targetConfigs, hookContributions, manifest);
1415
1649
  }
1416
1650
  else if (cmsStaticDir) {
1417
1651
  // Production mode: inline CMS API + static files
1418
- cmsApp = await setupProductionMode(app, source, siteDir, cmsStaticDir, templatesDir, adminDir, targetConfigs);
1652
+ cmsApp = await setupProductionMode(app, source, siteDir, cmsStaticDir, templatesDir, adminDir, targetConfigs, hookContributions, manifest);
1419
1653
  }
1420
1654
  // ---- 404 ----
1421
1655
  app.notFound(c => {
@@ -1464,8 +1698,10 @@ async function runDev(siteDir, port) {
1464
1698
  const env = getEnvironment(cfg);
1465
1699
  const type = getType(cfg);
1466
1700
  const ed = isEditable(cfg) ? 'editable ' : 'read-only';
1467
- const storagePath = cfg.storage?.type === 'filesystem' ? (cfg.storage.path ?? `targets/${name}`) : `${cfg.storage?.type ?? '?'}`;
1468
- console.log(` ${c.dim('┃')} ${c.dim('•')} ${name.padEnd(14)} ${c.dim(env.padEnd(11))} ${c.dim(ed)} ${c.dim(type.padEnd(8))} ${c.dim('→ ' + storagePath)}`);
1701
+ // Path X — the storage provider is opaque (operator constructed it via
1702
+ // factory at config-eval). Display target name + axes; the provider
1703
+ // identity is not introspectable from the StorageProvider interface.
1704
+ console.log(` ${c.dim('┃')} ${c.dim('•')} ${name.padEnd(14)} ${c.dim(env.padEnd(11))} ${c.dim(ed)} ${c.dim(type.padEnd(8))}`);
1469
1705
  }
1470
1706
  }
1471
1707
  if (isDevMode && cmsWebDir) {
@@ -1551,7 +1787,15 @@ async function runDev(siteDir, port) {
1551
1787
  // written into the source, so the scanner misses it. Include it
1552
1788
  // explicitly to avoid a mid-session page reload when a TSX editor
1553
1789
  // is first loaded (#122).
1554
- include: ['react/jsx-dev-runtime', 'react/jsx-runtime'],
1790
+ //
1791
+ // @formkit/drag-and-drop/vue is the ComponentTree's reorder lib
1792
+ // (#105). The package's main entry is CJS; the Vue subpath is
1793
+ // ESM. Vite's auto-scanner doesn't always pre-bundle subpath
1794
+ // ESM imports with mixed CJS-main packages on first load,
1795
+ // which triggers a runtime "Re-optimizing dependencies"
1796
+ // page reload mid-test on CI cold start. Explicit include
1797
+ // pins the subpath in the boot-time pre-bundle.
1798
+ include: ['react/jsx-dev-runtime', 'react/jsx-runtime', '@formkit/drag-and-drop/vue'],
1555
1799
  },
1556
1800
  server: {
1557
1801
  middlewareMode: true,
@@ -1617,7 +1861,7 @@ async function runDev(siteDir, port) {
1617
1861
  console.log();
1618
1862
  });
1619
1863
  // ---- File watching ----
1620
- // Watch site dir for content changes (JSON manifests + site.yaml config).
1864
+ // Watch site dir for content changes (JSON manifests + site.config.ts).
1621
1865
  // Swallow FSWatcher 'error' events — Node's recursive watcher throws ENOENT
1622
1866
  // when a watched subdir disappears (rm -rf during publish, git checkout).
1623
1867
  // Letting it crash would take the whole dev server down; logging a warning
@@ -1636,15 +1880,12 @@ async function runDev(siteDir, port) {
1636
1880
  if (filename.endsWith('.json') || filename.endsWith('.yaml')) {
1637
1881
  console.log(` Manifest changed: ${filename}`);
1638
1882
  invalidateAllTemplates();
1639
- // Refresh source sidecars for external edits (git pull, direct file
1640
- // edit). PUT routes already handle their own writes — this catches
1641
- // everything outside the admin UI.
1642
- const pageMatch = /^pages\/(.+)\/page\.json$/.exec(norm);
1643
- const fragMatch = /^fragments\/(.+)\/fragment\.json$/.exec(norm);
1644
- if (pageMatch)
1645
- cmsApp?.writeSourceSidecar('page', pageMatch[1]).catch(() => { });
1646
- else if (fragMatch)
1647
- cmsApp?.writeSourceSidecar('fragment', fragMatch[1]).catch(() => { });
1883
+ // Out-of-band manifest changes (git pull, manual edit, e2e
1884
+ // test wipes) bypass the admin-api save handler that would
1885
+ // otherwise invalidate the AdminCache. Drop content-summary
1886
+ // entries so the next /api/pages or /api/fragments rebuilds
1887
+ // from disk. Best-effort; the void promise is fire-and-forget.
1888
+ void cmsApp?.invalidateContentCache();
1648
1889
  notifyReload();
1649
1890
  }
1650
1891
  });
@@ -1657,13 +1898,34 @@ async function runDev(siteDir, port) {
1657
1898
  if (filename.endsWith('.ts') || filename.endsWith('.tsx')) {
1658
1899
  const parts = filename.split('/');
1659
1900
  if (parts.length >= 1) {
1660
- console.log(` Template changed: ${parts[0]}`);
1661
- invalidateTemplate(parts[0]);
1662
- // Drop the admin-api's cached scan so next compare/publish
1663
- // rehashes. Cheap (the scan is what's slow, not invalidation).
1664
- cmsApp?.invalidateTemplatesCache();
1665
- cmsApp?.invalidateSourceSidecars();
1666
- notifyReload();
1901
+ const templateName = parts[0];
1902
+ const fullPath = join(templatesDir, filename);
1903
+ // Suppress spurious events for files whose mtime predates the
1904
+ // watcher's recent window these are kernel-delayed metadata
1905
+ // flushes for files written before the watcher began observing,
1906
+ // typical of the worker-scoped `cp -r` setup in the e2e fixture
1907
+ // (#286). Without the gate, the resulting notifyReload() closes
1908
+ // any open admin UI mid-interaction.
1909
+ void (async () => {
1910
+ if (!(await isTemplateEventRecent(fullPath, TEMPLATE_RECENT_CHANGE_WINDOW_MS))) {
1911
+ return;
1912
+ }
1913
+ console.log(` Template changed: ${templateName}`);
1914
+ invalidateTemplate(templateName);
1915
+ // Drop the admin-api's cached scan so next compare/publish
1916
+ // rehashes. Cheap (the scan is what's slow, not invalidation).
1917
+ cmsApp?.invalidateTemplatesCache();
1918
+ // Cut 6 — fire a validation rescan with the template-edit
1919
+ // cause so the scanner re-runs schema-conformance against
1920
+ // every page+fragment using this template. The ScanEvent
1921
+ // emits on the /__validation SSE channel; the admin's
1922
+ // TemplateChangedBanner consumes it to show the
1923
+ // template-developer's "did I break anything?" surface.
1924
+ // Fire-and-forget — the scan runs in the background and
1925
+ // the SSE event is the signal that it finished.
1926
+ void cmsApp?.rescanForTemplate?.(templateName);
1927
+ notifyReload();
1928
+ })();
1667
1929
  }
1668
1930
  }
1669
1931
  });
@@ -1692,16 +1954,98 @@ function mountUserThemeRoute(cmsApp, adminDir) {
1692
1954
  return c.body(readFileSync(themePath, 'utf-8'));
1693
1955
  });
1694
1956
  }
1695
- async function setupCmsApi(app, source, siteDir, templatesDir, adminDir, targetConfigs) {
1696
- const cmsApp = createAdminApp({ source, siteDir, templatesDir, adminDir, targetConfigs });
1957
+ /**
1958
+ * Build the background validation scanner (validation Cut 2). The scanner
1959
+ * is constructed against the resolved source's storage + cache; the boot
1960
+ * path kicks off an initial full-site scan in the background so admin
1961
+ * responses don't block on it.
1962
+ *
1963
+ * Returns null when the manifest is unavailable (site lacks site.config.ts)
1964
+ * — the admin app degrades gracefully (route returns empty issues; SSE
1965
+ * channel idle).
1966
+ */
1967
+ async function buildValidationScanner(opts) {
1968
+ if (!opts.manifest)
1969
+ return null;
1970
+ const { createValidationScanner } = await import('../validation/scanner.js');
1971
+ const { defaultValidatorRegistry } = await import('../validation/default-registry.js');
1972
+ const scanner = createValidationScanner({
1973
+ storage: opts.source.storage,
1974
+ contentRoot: opts.source.contentRoot,
1975
+ registry: defaultValidatorRegistry(),
1976
+ cache: opts.source.cache,
1977
+ siteOptions: { templatesDir: opts.templatesDir, manifest: opts.manifest },
1978
+ });
1979
+ // Boot warm: kick off the initial scan in the background. Errors are
1980
+ // logged but don't block boot — broken validators surface as info-issues
1981
+ // rather than failing the admin process.
1982
+ void scanner.scanAll().catch(err => {
1983
+ console.error('[validation] initial scan failed:', err);
1984
+ });
1985
+ return scanner;
1986
+ }
1987
+ async function setupCmsApi(app, source, siteDir, templatesDir, adminDir, targetConfigs, contributions, manifest) {
1988
+ // Build + seal the hook registry from `admin.hooks` factory
1989
+ // contributions before wiring the admin app. Hooks are an opt-in
1990
+ // extension surface; sites without `admin.hooks` get an empty
1991
+ // registry (no overhead).
1992
+ const hooks = await buildHooksRegistry({ contributions });
1993
+ const validationScanner = await buildValidationScanner({ source, templatesDir, manifest });
1994
+ // Mount SSE on the OUTER Hono app — matches `/__reload`'s placement so
1995
+ // the browser EventSource (which connects without an `/admin/` prefix)
1996
+ // bypasses Vite's middleware and reaches the route. Same in prod for
1997
+ // consistency: a save from one tab updates the badge in every other
1998
+ // tab without polling.
1999
+ const { mountValidationSse } = await import('../admin-api/routes/validation.js');
2000
+ mountValidationSse(app, validationScanner);
2001
+ const cmsApp = createAdminApp({
2002
+ source,
2003
+ siteDir,
2004
+ templatesDir,
2005
+ adminDir,
2006
+ targetConfigs,
2007
+ hooks,
2008
+ validationScanner,
2009
+ });
2010
+ // Decorate cmsApp with the template-rescan hook before returning. The
2011
+ // file watcher in startServer() invokes this on `.ts/.tsx` changes
2012
+ // under `templates/{name}/`. Failure is fail-open (logged + dropped)
2013
+ // — a scan failure shouldn't break dev-mode hot reload.
2014
+ const decoratedApp = cmsApp;
2015
+ decoratedApp.rescanForTemplate = async (name) => {
2016
+ if (!validationScanner)
2017
+ return;
2018
+ try {
2019
+ await validationScanner.rescan({ kind: 'template', name });
2020
+ }
2021
+ catch (err) {
2022
+ console.warn(` Validation scanner: template rescan failed for "${name}": ${err.message}`);
2023
+ }
2024
+ };
1697
2025
  mountUserThemeRoute(cmsApp, adminDir);
1698
2026
  app.route('/admin', cmsApp);
1699
- return cmsApp;
2027
+ return decoratedApp;
1700
2028
  }
1701
2029
  // ---- Production mode: inline CMS API + static files from admin-dist/ ----
1702
- async function setupProductionMode(app, source, siteDir, cmsStaticDir, templatesDir, adminDir, targetConfigs) {
2030
+ async function setupProductionMode(app, source, siteDir, cmsStaticDir, templatesDir, adminDir, targetConfigs, contributions, manifest) {
2031
+ // Same shape as dev mode — `gazetta serve` reads `admin.hooks`
2032
+ // factory contributions from the same site config.
2033
+ const hooks = await buildHooksRegistry({ contributions });
2034
+ const validationScanner = await buildValidationScanner({ source, templatesDir, manifest });
2035
+ // SSE channel at the outer app's root; see setupCmsApi for rationale.
2036
+ const { mountValidationSse } = await import('../admin-api/routes/validation.js');
2037
+ mountValidationSse(app, validationScanner);
1703
2038
  // Mount CMS API inline at /admin (production mode — bundled editors/fields)
1704
- const cmsApp = createAdminApp({ source, siteDir, templatesDir, adminDir, production: true, targetConfigs });
2039
+ const cmsApp = createAdminApp({
2040
+ source,
2041
+ siteDir,
2042
+ templatesDir,
2043
+ adminDir,
2044
+ production: true,
2045
+ targetConfigs,
2046
+ hooks,
2047
+ validationScanner,
2048
+ });
1705
2049
  mountUserThemeRoute(cmsApp, adminDir);
1706
2050
  app.route('/admin', cmsApp);
1707
2051
  // Serve pre-built CMS static files (includes bundled editors/fields)
@@ -1766,79 +2110,83 @@ async function main() {
1766
2110
  await runInit(parsed.positional[0] ?? '.');
1767
2111
  return;
1768
2112
  }
1769
- else if (command === 'build') {
1770
- const siteDir = await resolveSiteDir(parsed.positional[0]);
2113
+ // ── Resolve siteDir once (fs walk; no config eval) + load .env
2114
+ // ── BEFORE any config-eval. Storage / deploy / AI factories read
2115
+ // ── process.env synchronously at construction; resolveTarget()
2116
+ // ── triggers config-eval, so env must be in place first. Per-
2117
+ // ── command positional layouts determine which slot carries the
2118
+ // ── site path. Site-local `.env` is honored (e.g.,
2119
+ // ── sites/gazetta.studio/.env with R2 creds) per design-config.md.
2120
+ siteDir = await resolveSiteDirForCommand(command, parsed);
2121
+ loadEnvFiles(siteDir);
2122
+ if (command === 'build') {
1771
2123
  await runBuild(siteDir);
1772
2124
  return;
1773
2125
  }
1774
2126
  else if (command === 'rollback') {
1775
2127
  // gazetta rollback <rev> [target] [site]
1776
- const [rev, second, third] = parsed.positional;
2128
+ const [rev, second] = parsed.positional;
1777
2129
  if (!rev || !rev.startsWith('rev-')) {
1778
2130
  console.error(`\n Error: rollback requires a revision id as the first argument (e.g. gazetta rollback rev-1776337441608 [target])\n`);
1779
2131
  process.exit(1);
1780
2132
  return;
1781
2133
  }
1782
2134
  rollbackRevisionId = rev;
1783
- const secondIsSite = second && (second.includes('/') || existsSync(join(resolve(second), 'site.yaml')));
1784
- if (secondIsSite) {
1785
- siteDir = await resolveSiteDir(second);
1786
- targetName = await resolveTarget(undefined, siteDir);
1787
- }
1788
- else {
1789
- siteDir = await resolveSiteDir(third);
1790
- targetName = await resolveTarget(second, siteDir);
1791
- }
2135
+ const secondIsSite = second && (second.includes('/') || hasSiteConfig(resolve(second)));
2136
+ targetName = secondIsSite ? await resolveTarget(undefined, siteDir) : await resolveTarget(second, siteDir);
1792
2137
  }
1793
2138
  else if (targetFirstCommands.has(command)) {
1794
2139
  // gazetta publish [target] [site]
1795
- const [first, second] = parsed.positional;
1796
- // If first arg looks like a site path (contains / or has site.yaml), it's the site
1797
- const firstIsSite = first && (first.includes('/') || existsSync(join(resolve(first), 'site.yaml')));
1798
- if (firstIsSite) {
1799
- siteDir = await resolveSiteDir(first);
1800
- targetName = await resolveTarget(undefined, siteDir);
1801
- }
1802
- else {
1803
- siteDir = await resolveSiteDir(second);
1804
- targetName = await resolveTarget(first, siteDir);
1805
- }
2140
+ const [first] = parsed.positional;
2141
+ const firstIsSite = first && (first.includes('/') || hasSiteConfig(resolve(first)));
2142
+ targetName = firstIsSite ? await resolveTarget(undefined, siteDir) : await resolveTarget(first, siteDir);
1806
2143
  }
1807
2144
  else if (siteOnlyCommands.has(command)) {
1808
- siteDir = await resolveSiteDir(parsed.positional[0]);
2145
+ // siteDir set up front; nothing more needed.
1809
2146
  }
1810
2147
  else if (command === 'translate') {
1811
2148
  // gazetta translate <item> --to <locale> [target]
1812
- // positional args after the item are the optional target name
1813
- siteDir = await resolveSiteDir(undefined);
1814
- // Find the target arg — skip the item (pages/... or fragments/...) and --to/locale flags
1815
2149
  const translatePositionals = parsed.positional.filter(p => !p.startsWith('pages/') && !p.startsWith('fragments/'));
1816
2150
  if (translatePositionals.length > 0)
1817
2151
  targetName = translatePositionals[0];
1818
2152
  }
2153
+ else if (command === 'assets') {
2154
+ // gazetta assets <subcommand> [args...] [target] [site]
2155
+ // assets list [target] [site] → subcmd, target, site
2156
+ // assets info <name> [target] [site] → subcmd, name, target, site
2157
+ // assets reindex [target] [site] → subcmd, target, site
2158
+ const subcmd = parsed.positional[0];
2159
+ const targetArg = subcmd === 'info' ? parsed.positional[2] : parsed.positional[1];
2160
+ targetName = targetArg ? await resolveTarget(targetArg, siteDir) : undefined;
2161
+ }
2162
+ else if (command === 'archive') {
2163
+ // gazetta archive <subcommand> [args...]
2164
+ //
2165
+ // Site/target resolution: positional after the subcommand's
2166
+ // required args are interpreted as [target] [site]. The archive
2167
+ // module honors `--kind=` / `--force` / `--no-keep-alias` flags
2168
+ // separately (those don't reach the parsed.positional list per
2169
+ // global parseArgs).
2170
+ //
2171
+ // Subcommand positional shapes:
2172
+ // archive list → []
2173
+ // archive purge <name> → [name]
2174
+ // archive restore <name> → [name]
2175
+ // archive rename <oldname> <newname> → [oldname, newname]
2176
+ //
2177
+ // Remaining slots are [target] [site] in that order.
2178
+ const subcmd = parsed.positional[0];
2179
+ const requiredArgs = subcmd === 'rename' ? 2 : subcmd === 'purge' || subcmd === 'restore' ? 1 : 0;
2180
+ const trailingStart = 1 + requiredArgs;
2181
+ const targetArg = parsed.positional[trailingStart];
2182
+ targetName = targetArg ? await resolveTarget(targetArg, siteDir) : undefined;
2183
+ }
1819
2184
  else {
1820
2185
  console.error(` Unknown command: ${command}\n`);
1821
2186
  printHelp();
1822
2187
  process.exit(1);
1823
2188
  return;
1824
2189
  }
1825
- // Load .env from project root and site dir (skipped in CI)
1826
- if (!process.env.CI) {
1827
- const projectRoot = detectProjectRoot(siteDir);
1828
- const envDirs = projectRoot !== siteDir ? [projectRoot, siteDir] : [siteDir];
1829
- for (const dir of envDirs) {
1830
- for (const name of ['.env', '.env.local']) {
1831
- const envPath = join(dir, name);
1832
- if (existsSync(envPath)) {
1833
- for (const line of readFileSync(envPath, 'utf-8').split('\n')) {
1834
- const m = line.match(/^\s*([A-Z_][A-Z0-9_]*)\s*=\s*(.*)$/);
1835
- if (m && !(m[1] in process.env))
1836
- process.env[m[1]] = m[2].replace(/^["']|["']$/g, '');
1837
- }
1838
- }
1839
- }
1840
- }
1841
- }
1842
2190
  switch (command) {
1843
2191
  case 'publish':
1844
2192
  await runPublish(siteDir, targetName, { force: parsed.force });
@@ -1850,7 +2198,7 @@ async function main() {
1850
2198
  await runDeploy(siteDir, targetName);
1851
2199
  break;
1852
2200
  case 'validate':
1853
- await runValidate(siteDir);
2201
+ await runValidate(siteDir, args.slice(1));
1854
2202
  break;
1855
2203
  case 'dev':
1856
2204
  await runDev(siteDir, parsed.port ?? 3000);
@@ -1880,7 +2228,11 @@ async function main() {
1880
2228
  }
1881
2229
  // Resolve the content directory — translate operates on a target's filesystem.
1882
2230
  // Uses the specified target or falls back to the first editable target.
1883
- const siteYaml = yaml.load(readFileSync(join(siteDir, 'site.yaml'), 'utf-8'));
2231
+ const siteYaml = await loadSiteManifestForCli(siteDir);
2232
+ if (!siteYaml) {
2233
+ console.error(` Error: no site config found at ${siteDir}`);
2234
+ process.exit(1);
2235
+ }
1884
2236
  const { isEditable } = await import('../types.js');
1885
2237
  const resolvedTarget = targetName ?? Object.entries(siteYaml.targets ?? {}).find(([, cfg]) => isEditable(cfg))?.[0];
1886
2238
  if (!resolvedTarget) {
@@ -1889,26 +2241,27 @@ async function main() {
1889
2241
  }
1890
2242
  const targetConfig = siteYaml.targets[resolvedTarget];
1891
2243
  if (!targetConfig) {
1892
- console.error(` Error: target "${resolvedTarget}" not found in site.yaml`);
2244
+ console.error(` Error: target "${resolvedTarget}" not found in site config`);
1893
2245
  process.exit(1);
1894
2246
  }
1895
- const storagePath = targetConfig.storage.path ?? join('targets', resolvedTarget);
1896
- const contentDir = resolve(siteDir, storagePath);
2247
+ // Translate goes through the storage provider so it works on any
2248
+ // storage backend (filesystem / R2 / S3 / Azure). Path X — the storage
2249
+ // provider was constructed by the operator's factory at config-eval.
2250
+ const storage = targetConfig.storage;
1897
2251
  const baseName = isPage ? 'page' : 'fragment';
1898
- const dir = join(contentDir, itemArg);
1899
- const sourceFile = join(dir, `${baseName}.json`);
1900
- const destFile = join(dir, localeFilename(baseName, locale));
1901
- const fs = await import('node:fs/promises');
1902
- if (!existsSync(sourceFile)) {
1903
- console.error(` Error: ${sourceFile} not found`);
2252
+ const sourcePath = `${itemArg}/${baseName}.json`;
2253
+ const destPath = `${itemArg}/${localeFilename(baseName, locale)}`;
2254
+ if (!(await storage.exists(sourcePath))) {
2255
+ console.error(` Error: ${sourcePath} not found on target "${resolvedTarget}"`);
1904
2256
  process.exit(1);
1905
2257
  }
1906
- if (existsSync(destFile)) {
1907
- console.error(` Error: ${destFile} already exists`);
2258
+ if (await storage.exists(destPath)) {
2259
+ console.error(` Error: ${destPath} already exists on target "${resolvedTarget}"`);
1908
2260
  process.exit(1);
1909
2261
  }
1910
- await fs.copyFile(sourceFile, destFile);
1911
- console.log(` ${c.green('✓')} Created ${relative(process.cwd(), destFile)}`);
2262
+ const sourceContent = await storage.readFile(sourcePath);
2263
+ await storage.writeFile(destPath, sourceContent);
2264
+ console.log(` ${c.green('✓')} Created ${destPath}`);
1912
2265
  console.log(` Edit the file to translate the content.`);
1913
2266
  break;
1914
2267
  }
@@ -1925,12 +2278,22 @@ async function main() {
1925
2278
  await runHistoryRollback(ctx, rollbackRevisionId, { yes: parsed.yes });
1926
2279
  break;
1927
2280
  }
2281
+ case 'assets': {
2282
+ const { runAssetsSubcommand } = await import('./assets-cli.js');
2283
+ await runAssetsSubcommand({ args: args.slice(1), siteDir, targetName });
2284
+ break;
2285
+ }
2286
+ case 'archive': {
2287
+ const { runArchiveSubcommand } = await import('./archive.js');
2288
+ await runArchiveSubcommand({ args: args.slice(1), siteDir, targetName });
2289
+ break;
2290
+ }
1928
2291
  }
1929
2292
  }
1930
2293
  /**
1931
2294
  * Resolve site + target + config into the shape HistoryCommandContext
1932
2295
  * expects. Lives here rather than in cli/history.ts so the target-
1933
- * resolution logic (site.yaml parsing, CI env handling) stays with
2296
+ * resolution logic (site config parsing, CI env handling) stays with
1934
2297
  * the other CLI commands that already do it the same way.
1935
2298
  */
1936
2299
  async function resolveHistoryContext(siteDir, targetName) {