@minamocms/framework 0.0.0-stage → 0.10.2

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 (286) hide show
  1. package/CHANGELOG.md +240 -0
  2. package/LICENSE +105 -0
  3. package/README.md +133 -2
  4. package/dist/boot.d.ts +19 -0
  5. package/dist/boot.js +22 -0
  6. package/dist/config.d.ts +18 -0
  7. package/dist/config.js +44 -0
  8. package/dist/create-site/cli.d.ts +2 -0
  9. package/dist/create-site/cli.js +31 -0
  10. package/dist/create-site/generate-site.d.ts +12 -0
  11. package/dist/create-site/generate-site.js +168 -0
  12. package/dist/create-site/mint-token-cli.d.ts +2 -0
  13. package/dist/create-site/mint-token-cli.js +26 -0
  14. package/dist/create-site/mint-token.d.ts +7 -0
  15. package/dist/create-site/mint-token.js +49 -0
  16. package/dist/create-site/template/AGENTS.md +473 -0
  17. package/dist/create-site/template/content/menus/footerCompany.json +9 -0
  18. package/dist/create-site/template/content/menus/footerProduct.json +9 -0
  19. package/dist/create-site/template/content/menus/footerResources.json +9 -0
  20. package/dist/create-site/template/content/menus/main.json +9 -0
  21. package/dist/create-site/template/content/pages/404.json +23 -0
  22. package/dist/create-site/template/content/pages/about/careers.json +22 -0
  23. package/dist/create-site/template/content/pages/about/team.json +22 -0
  24. package/dist/create-site/template/content/pages/about.json +22 -0
  25. package/dist/create-site/template/content/pages/docs/deployment.json +22 -0
  26. package/dist/create-site/template/content/pages/docs/getting-started/quickstart.json +22 -0
  27. package/dist/create-site/template/content/pages/docs/getting-started.json +24 -0
  28. package/dist/create-site/template/content/pages/docs.json +22 -0
  29. package/dist/create-site/template/content/pages/index.json +182 -0
  30. package/dist/create-site/template/content/redirects.json +4 -0
  31. package/dist/create-site/template/dockerignore +6 -0
  32. package/dist/create-site/template/gitignore +11 -0
  33. package/dist/create-site/template/theme/assets/site.js +49 -0
  34. package/dist/create-site/template/theme/assets/style.css +1223 -0
  35. package/dist/create-site/template/theme/blocks/button.liquid +21 -0
  36. package/dist/create-site/template/theme/blocks/comparison-row.liquid +19 -0
  37. package/dist/create-site/template/theme/blocks/faq-item.liquid +22 -0
  38. package/dist/create-site/template/theme/blocks/feature-card.liquid +23 -0
  39. package/dist/create-site/template/theme/blocks/logo-mark.liquid +13 -0
  40. package/dist/create-site/template/theme/blocks/pricing-tier.liquid +42 -0
  41. package/dist/create-site/template/theme/blocks/step-card.liquid +19 -0
  42. package/dist/create-site/template/theme/blocks/testimonial-card.liquid +25 -0
  43. package/dist/create-site/template/theme/config/site_settings.json +11 -0
  44. package/dist/create-site/template/theme/layouts/theme.liquid +102 -0
  45. package/dist/create-site/template/theme/sections/comparison.liquid +41 -0
  46. package/dist/create-site/template/theme/sections/cta-banner.liquid +36 -0
  47. package/dist/create-site/template/theme/sections/faq.liquid +27 -0
  48. package/dist/create-site/template/theme/sections/feature-grid.liquid +27 -0
  49. package/dist/create-site/template/theme/sections/hero.liquid +48 -0
  50. package/dist/create-site/template/theme/sections/how-it-works.liquid +27 -0
  51. package/dist/create-site/template/theme/sections/pricing.liquid +27 -0
  52. package/dist/create-site/template/theme/sections/social-proof.liquid +21 -0
  53. package/dist/create-site/template/theme/sections/testimonials.liquid +27 -0
  54. package/dist/create-site/template/theme/snippets/icon.liquid +24 -0
  55. package/dist/create-site/template/theme/snippets/logo.liquid +7 -0
  56. package/dist/create-site/template/theme/snippets/responsive-media.liquid +107 -0
  57. package/dist/create-site/template/theme/snippets/site-name.liquid +1 -0
  58. package/dist/create-site/template/vhost/Dockerfile +33 -0
  59. package/dist/create-site/template/vhost/docker-entrypoint.sh +58 -0
  60. package/dist/index.d.ts +2 -0
  61. package/dist/index.js +4 -0
  62. package/dist/media/drivers/driver.d.ts +11 -0
  63. package/dist/media/drivers/driver.js +1 -0
  64. package/dist/media/drivers/local-fs-driver.d.ts +3 -0
  65. package/dist/media/drivers/local-fs-driver.js +94 -0
  66. package/dist/media/filename.d.ts +2 -0
  67. package/dist/media/filename.js +54 -0
  68. package/dist/media/manage-media.d.ts +19 -0
  69. package/dist/media/manage-media.js +45 -0
  70. package/dist/media/seed-media-cli.d.ts +2 -0
  71. package/dist/media/seed-media-cli.js +22 -0
  72. package/dist/media/seed-media.d.ts +11 -0
  73. package/dist/media/seed-media.js +76 -0
  74. package/dist/migrations/index.d.ts +3 -0
  75. package/dist/migrations/index.js +83 -0
  76. package/dist/pull-site/cli.d.ts +2 -0
  77. package/dist/pull-site/cli.js +78 -0
  78. package/dist/pull-site/prompt-token.d.ts +16 -0
  79. package/dist/pull-site/prompt-token.js +69 -0
  80. package/dist/pull-site/prompts.d.ts +22 -0
  81. package/dist/pull-site/prompts.js +127 -0
  82. package/dist/pull-site/pull-site.d.ts +23 -0
  83. package/dist/pull-site/pull-site.js +211 -0
  84. package/dist/renderer/engine.d.ts +2 -0
  85. package/dist/renderer/engine.js +48 -0
  86. package/dist/renderer/render-cache.d.ts +11 -0
  87. package/dist/renderer/render-cache.js +11 -0
  88. package/dist/renderer/render-page.d.ts +34 -0
  89. package/dist/renderer/render-page.js +209 -0
  90. package/dist/renderer/theme-templates.d.ts +7 -0
  91. package/dist/renderer/theme-templates.js +82 -0
  92. package/dist/routes/admin-redirect.d.ts +5 -0
  93. package/dist/routes/admin-redirect.js +26 -0
  94. package/dist/routes/assets.d.ts +6 -0
  95. package/dist/routes/assets.js +30 -0
  96. package/dist/routes/batch.d.ts +10 -0
  97. package/dist/routes/batch.js +148 -0
  98. package/dist/routes/capabilities.d.ts +6 -0
  99. package/dist/routes/capabilities.js +27 -0
  100. package/dist/routes/content.d.ts +8 -0
  101. package/dist/routes/content.js +169 -0
  102. package/dist/routes/deleted-pages.d.ts +8 -0
  103. package/dist/routes/deleted-pages.js +9 -0
  104. package/dist/routes/drafts.d.ts +10 -0
  105. package/dist/routes/drafts.js +73 -0
  106. package/dist/routes/git.d.ts +8 -0
  107. package/dist/routes/git.js +158 -0
  108. package/dist/routes/index.d.ts +12 -0
  109. package/dist/routes/index.js +86 -0
  110. package/dist/routes/links.d.ts +8 -0
  111. package/dist/routes/links.js +18 -0
  112. package/dist/routes/media-public.d.ts +11 -0
  113. package/dist/routes/media-public.js +125 -0
  114. package/dist/routes/media.d.ts +9 -0
  115. package/dist/routes/media.js +80 -0
  116. package/dist/routes/menus.d.ts +8 -0
  117. package/dist/routes/menus.js +148 -0
  118. package/dist/routes/preview-revision.d.ts +10 -0
  119. package/dist/routes/preview-revision.js +96 -0
  120. package/dist/routes/preview.d.ts +10 -0
  121. package/dist/routes/preview.js +56 -0
  122. package/dist/routes/public.d.ts +10 -0
  123. package/dist/routes/public.js +123 -0
  124. package/dist/routes/publish.d.ts +10 -0
  125. package/dist/routes/publish.js +139 -0
  126. package/dist/routes/redirects.d.ts +8 -0
  127. package/dist/routes/redirects.js +132 -0
  128. package/dist/routes/search-public.d.ts +6 -0
  129. package/dist/routes/search-public.js +104 -0
  130. package/dist/routes/search.d.ts +8 -0
  131. package/dist/routes/search.js +15 -0
  132. package/dist/routes/settings.d.ts +10 -0
  133. package/dist/routes/settings.js +68 -0
  134. package/dist/routes/sitemap.d.ts +7 -0
  135. package/dist/routes/sitemap.js +79 -0
  136. package/dist/routes/theme.d.ts +10 -0
  137. package/dist/routes/theme.js +116 -0
  138. package/dist/schemas/instance.schema.json +17 -0
  139. package/dist/schemas/menu.schema.json +24 -0
  140. package/dist/schemas/page.schema.json +26 -0
  141. package/dist/schemas/redirects.schema.json +24 -0
  142. package/dist/search/drivers/driver.d.ts +9 -0
  143. package/dist/search/drivers/driver.js +1 -0
  144. package/dist/search/drivers/node-sqlite-driver.d.ts +7 -0
  145. package/dist/search/drivers/node-sqlite-driver.js +19 -0
  146. package/dist/search/query-content.d.ts +32 -0
  147. package/dist/search/query-content.js +207 -0
  148. package/dist/search/query-index.d.ts +5 -0
  149. package/dist/search/query-index.js +21 -0
  150. package/dist/search/rebuild-index.d.ts +3 -0
  151. package/dist/search/rebuild-index.js +320 -0
  152. package/dist/server-config.d.ts +23 -0
  153. package/dist/server-config.js +237 -0
  154. package/dist/server.d.ts +13 -0
  155. package/dist/server.js +235 -0
  156. package/dist/services/batch.d.ts +35 -0
  157. package/dist/services/batch.js +145 -0
  158. package/dist/services/checkpoint.d.ts +4 -0
  159. package/dist/services/checkpoint.js +45 -0
  160. package/dist/services/content-read.d.ts +27 -0
  161. package/dist/services/content-read.js +151 -0
  162. package/dist/services/delete-content.d.ts +12 -0
  163. package/dist/services/delete-content.js +166 -0
  164. package/dist/services/deleted-pages.d.ts +11 -0
  165. package/dist/services/deleted-pages.js +83 -0
  166. package/dist/services/dev-tunnel.d.ts +5 -0
  167. package/dist/services/dev-tunnel.js +5 -0
  168. package/dist/services/drafts.d.ts +16 -0
  169. package/dist/services/drafts.js +164 -0
  170. package/dist/services/etag.d.ts +2 -0
  171. package/dist/services/etag.js +30 -0
  172. package/dist/services/fs-walk.d.ts +1 -0
  173. package/dist/services/fs-walk.js +26 -0
  174. package/dist/services/git-commit.d.ts +3 -0
  175. package/dist/services/git-commit.js +12 -0
  176. package/dist/services/git-history.d.ts +28 -0
  177. package/dist/services/git-history.js +131 -0
  178. package/dist/services/git-revert.d.ts +12 -0
  179. package/dist/services/git-revert.js +140 -0
  180. package/dist/services/git.d.ts +16 -0
  181. package/dist/services/git.js +127 -0
  182. package/dist/services/interval-job.d.ts +4 -0
  183. package/dist/services/interval-job.js +9 -0
  184. package/dist/services/ip-allowlist.d.ts +7 -0
  185. package/dist/services/ip-allowlist.js +31 -0
  186. package/dist/services/links.d.ts +22 -0
  187. package/dist/services/links.js +169 -0
  188. package/dist/services/manage-menus.d.ts +15 -0
  189. package/dist/services/manage-menus.js +138 -0
  190. package/dist/services/manage-redirects.d.ts +25 -0
  191. package/dist/services/manage-redirects.js +156 -0
  192. package/dist/services/menu-references.d.ts +3 -0
  193. package/dist/services/menu-references.js +38 -0
  194. package/dist/services/menus.d.ts +9 -0
  195. package/dist/services/menus.js +43 -0
  196. package/dist/services/migration-runner.d.ts +16 -0
  197. package/dist/services/migration-runner.js +169 -0
  198. package/dist/services/mime-types.d.ts +3 -0
  199. package/dist/services/mime-types.js +43 -0
  200. package/dist/services/move.d.ts +16 -0
  201. package/dist/services/move.js +236 -0
  202. package/dist/services/path-safety.d.ts +6 -0
  203. package/dist/services/path-safety.js +112 -0
  204. package/dist/services/pid-file.d.ts +13 -0
  205. package/dist/services/pid-file.js +54 -0
  206. package/dist/services/post-urls.d.ts +3 -0
  207. package/dist/services/post-urls.js +27 -0
  208. package/dist/services/prepared-operation.d.ts +4 -0
  209. package/dist/services/prepared-operation.js +1 -0
  210. package/dist/services/publish.d.ts +15 -0
  211. package/dist/services/publish.js +313 -0
  212. package/dist/services/rate-limit-config.d.ts +9 -0
  213. package/dist/services/rate-limit-config.js +22 -0
  214. package/dist/services/redirects.d.ts +27 -0
  215. package/dist/services/redirects.js +175 -0
  216. package/dist/services/reindex-on-write.d.ts +3 -0
  217. package/dist/services/reindex-on-write.js +30 -0
  218. package/dist/services/resolve-blog-url.d.ts +11 -0
  219. package/dist/services/resolve-blog-url.js +31 -0
  220. package/dist/services/resolve-url.d.ts +11 -0
  221. package/dist/services/resolve-url.js +20 -0
  222. package/dist/services/site-settings.d.ts +28 -0
  223. package/dist/services/site-settings.js +116 -0
  224. package/dist/services/startup-checks.d.ts +10 -0
  225. package/dist/services/startup-checks.js +81 -0
  226. package/dist/services/static-file.d.ts +7 -0
  227. package/dist/services/static-file.js +37 -0
  228. package/dist/services/theme-component-file.d.ts +6 -0
  229. package/dist/services/theme-component-file.js +39 -0
  230. package/dist/services/theme-files.d.ts +33 -0
  231. package/dist/services/theme-files.js +148 -0
  232. package/dist/services/theme-page-templates.d.ts +7 -0
  233. package/dist/services/theme-page-templates.js +53 -0
  234. package/dist/services/theme-schemas.d.ts +2 -0
  235. package/dist/services/theme-schemas.js +137 -0
  236. package/dist/services/token-auth.d.ts +10 -0
  237. package/dist/services/token-auth.js +64 -0
  238. package/dist/services/urls.d.ts +2 -0
  239. package/dist/services/urls.js +20 -0
  240. package/dist/services/validation.d.ts +26 -0
  241. package/dist/services/validation.js +195 -0
  242. package/dist/services/write-queue.d.ts +1 -0
  243. package/dist/services/write-queue.js +14 -0
  244. package/dist/site-check/cli.d.ts +2 -0
  245. package/dist/site-check/cli.js +48 -0
  246. package/dist/site-check/run-check.d.ts +12 -0
  247. package/dist/site-check/run-check.js +188 -0
  248. package/dist/site-sync/cli-helpers.d.ts +23 -0
  249. package/dist/site-sync/cli-helpers.js +75 -0
  250. package/dist/site-sync/deploy.d.ts +36 -0
  251. package/dist/site-sync/deploy.js +124 -0
  252. package/dist/site-sync/live-content.d.ts +30 -0
  253. package/dist/site-sync/live-content.js +68 -0
  254. package/dist/site-sync/prompts.d.ts +30 -0
  255. package/dist/site-sync/prompts.js +237 -0
  256. package/dist/site-sync/pull-cli.d.ts +2 -0
  257. package/dist/site-sync/pull-cli.js +75 -0
  258. package/dist/site-sync/pull-site.d.ts +28 -0
  259. package/dist/site-sync/pull-site.js +171 -0
  260. package/dist/site-sync/push-cli.d.ts +2 -0
  261. package/dist/site-sync/push-cli.js +268 -0
  262. package/dist/site-sync/push-site.d.ts +96 -0
  263. package/dist/site-sync/push-site.js +376 -0
  264. package/dist/site-sync/remote-site.d.ts +38 -0
  265. package/dist/site-sync/remote-site.js +122 -0
  266. package/dist/site-sync/sync-record.d.ts +26 -0
  267. package/dist/site-sync/sync-record.js +76 -0
  268. package/dist/site-sync/theme-sync.d.ts +34 -0
  269. package/dist/site-sync/theme-sync.js +128 -0
  270. package/dist/stop-site/cli.d.ts +2 -0
  271. package/dist/stop-site/cli.js +28 -0
  272. package/dist/stop-site/stop-site.d.ts +26 -0
  273. package/dist/stop-site/stop-site.js +56 -0
  274. package/dist/theme-state.d.ts +20 -0
  275. package/dist/theme-state.js +33 -0
  276. package/dist/upgrade/changelog.d.ts +8 -0
  277. package/dist/upgrade/changelog.js +46 -0
  278. package/dist/upgrade/finish.d.ts +9 -0
  279. package/dist/upgrade/finish.js +30 -0
  280. package/dist/upgrade/owned-files.d.ts +11 -0
  281. package/dist/upgrade/owned-files.js +42 -0
  282. package/dist/upgrade/package-names.d.ts +4 -0
  283. package/dist/upgrade/package-names.js +26 -0
  284. package/dist/upgrade/upgrade-cli.d.ts +2 -0
  285. package/dist/upgrade/upgrade-cli.js +170 -0
  286. package/package.json +64 -4
package/CHANGELOG.md ADDED
@@ -0,0 +1,240 @@
1
+ # Changelog
2
+
3
+ All notable changes to this package are documented here. Format loosely follows [Keep a Changelog](https://keepachangelog.com/). Nothing before `0.2.0` was tracked in this file - see git history for anything earlier.
4
+
5
+ ## [Unreleased]
6
+
7
+ ## [0.10.2] - 2026-11-04
8
+
9
+ ### Fixed
10
+
11
+ - **A site with no `media/` folder serves and stores media normally.** `media/` is git-ignored, so a site deployed by a host that builds from its repository (Railway, for one) starts without it. Until now every request for a media file there answered 500 instead of 404, and the first upload failed the same way. A missing folder now reads as an empty one, and the first upload creates it.
12
+
13
+ ## [0.10.1] - 2026-10-15
14
+
15
+ ### Changed
16
+
17
+ - **Published as `@minamocms/framework`**, the first release under the new name, with everything listed under 0.10.0. 0.10.0 was tagged but never published: npm refused the unscoped name `minamo` as too like existing packages' names. Start a new site with `npx -p @minamocms/framework create-site my-site`.
18
+ - **Moving a site from `@o-a/cms-agent`:** from `vhost/`, run `npm run upgrade`. On 0.9.3 that moves the site over to `@minamocms/framework`, switching its dependency and `server.js` to the new name; on 0.6.0 to 0.9.2 it first upgrades to 0.9.3, so run it a second time. On 0.5.x, run `npm install @o-a/cms-agent@0.9.3 --save-exact` and `npx upgrade-site --finish` first. Then test and `npm run push --cms` as for any upgrade.
19
+
20
+ ## [0.10.0] - 2026-10-15 (not published)
21
+
22
+ ### Changed
23
+
24
+ - **Renamed to Minamo.** The CMS is now called Minamo, and this package replaces `@o-a/cms-agent`. New sites depend on it and their `vhost/server.js` imports it. The licence is unchanged (FSL-1.1-ALv2) and now named in `package.json`.
25
+ - **A redeploy also refreshes `vhost/server.js`** on the live volume, alongside the installed CMS, since it imports the CMS by name. Nothing else on the volume changes.
26
+ - **New sites' starter content is Minamo's**, in place of Granite CMS: its copy, logo, page titles and footer, and the comparison section's `minamo`/`minamoLabel` settings (were `granite`/`graniteLabel`; existing sites' themes are their own and unchanged). Its sample commands are real ones, and its GitHub link is the framework's repository. A live site that isn't a CMS site is now reported as "not a Minamo site".
27
+ - Commits the CMS makes itself (checkpoints, and a recovered `.git` on first boot) are authored as `Minamo` in place of `CMS Agent`.
28
+
29
+ ## [0.9.3] - 2026-10-15
30
+
31
+ ### Changed
32
+
33
+ - **`npm run upgrade` looks for the CMS as `@minamocms/framework`.** npm refused the unscoped name `minamo`, so 0.9.2's handover never finds anything to move to. From 0.9.3, once `@minamocms/framework` has a newer version, the upgrade moves the site over to it.
34
+
35
+ ## [0.9.2] - 2026-10-15
36
+
37
+ ### Changed
38
+
39
+ - **The CMS is now called Minamo, and from 0.10.0 it is published as `minamo`** in place of `@o-a/cms-agent`. `npm run upgrade` looks for it under the new name: once `minamo` has a newer version, the upgrade moves the site over, swapping the dependency in `vhost/package.json`, then finishes as any upgrade does. Until then it upgrades exactly as before.
40
+
41
+ ## [0.9.1] - 2026-10-01
42
+
43
+ ### Added
44
+
45
+ - **`GET /v1/deleted-pages`** now also returns each page's `name` (as the admin's page tree shows it), alongside its `title`.
46
+
47
+ ## [0.9.0] - 2026-10-01
48
+
49
+ ### Added
50
+
51
+ - **`GET /v1/deleted-pages`** lists pages deleted in the last 90 days that aren't back, newest first, with who deleted them and when, and the version to restore each from with `POST /v1/git/revert`. Read from git's own record of deletions; a moved page isn't a deletion, and a page that exists again (live or as a draft) isn't listed.
52
+
53
+ ## [0.8.0] - 2026-09-29
54
+
55
+ ### Added
56
+
57
+ - **Moving a page keeps links to it working.** Every internal link to the page, or to a page under it, is rewritten to the new address in live pages, drafts, menus and the site settings, in the same commit as the move (whole-value paths such as link fields and menu items, and `href`s in rich text; `?query`, `#fragment` and a trailing slash are kept). The 301 redirect is still added for links from elsewhere. `GET /v1/links?to=/a-page` lists what links to a page, for the admin to show before a delete.
58
+ - **Link fields suggest the site's pages.** `"format": "url"` is the documented name for a link field (Shopify's word); in the admin it suggests the site's pages by title or path, stores the page's path, still takes any external, `mailto:`, `tel:` or `#anchor` address, and says which page a path reaches or that none is there. The menu item editor and the rich-text link dialog use the same field. `"format": "uri"`, the name before, still works, and `npm run check` now lists it under Suggestions, which never fail the check. Neither is enforced as a strict URI, so a site path such as `/about` is always accepted, and neither prints an "unknown format" warning.
59
+ - **Field groups.** A `"group"` on a field in a section, block or site settings schema puts it under a collapsible heading in the admin, with others sharing that group. A plain annotation like `"title"`: names and templates are unchanged. New sites group their settings into Social links and Typography.
60
+
61
+ ### Changed
62
+
63
+ - **AGENTS.md and the guides catch up.** New sites' AGENTS.md documents the `url` link field, field groups (with advice on grouping well), `theme/config/`, the rule that internal links are paths, `npm run check`'s Suggestions, and the current pull/push model (the old text said push never touches the theme). Favicons are now documented as belonging in `theme/assets/`, linked from the layout; `theme/root/` is optional and only for files that must sit at an exact root path. The content guide's `schemaVersion` is corrected to 7.
64
+
65
+ ### Fixed
66
+
67
+ - **Moving a page takes its draft with it.** The draft used to stay at the old address, where it became a draft-only page. A move is also refused when a draft-only page already sits at the destination.
68
+ - **A site with no `content/drafts` folder can preview and save drafts.** Git keeps no empty folders, so a site cloned or deployed from git with no drafts has none, and every preview and draft save failed with a 500. The folder is now made at start-up.
69
+ - **`npm run push` no longer waits in vain on a CMS upgrade the host can't build.** A deploy builds the version `vhost/package.json` names, so if that disagrees with the version installed here (seen on a real site: package.json put back to the old version after an upgrade), the live site stays on the old version and push used to wait ten minutes for it. Push now stops before deploying and says how to fix `package.json`.
70
+ - **Site settings move to `theme/config/site_settings.json`.** Shopify's VS Code extension checks any `config/settings_schema.json` against Shopify's own format and marks Granite's as wrong (on one real site, the file was then rewritten into Shopify's form and stopped working). The form is unchanged: JSON Schema, like a section's `{% schema %}`. A `settings_schema.json` in that form is still read, with a warning to rename it; one in Shopify's form is left alone.
71
+
72
+ ## [0.7.1] - 2026-09-29
73
+
74
+ ### Added
75
+
76
+ - **Preview unsaved site settings.** `GET /v1/preview/*` takes `?settings=<JSON>`: site settings changed in the admin but not saved, rendered in place of the saved ones and written nowhere. Values the theme's schema rejects are ignored and the saved settings shown. The admin's Settings screen uses it to preview changes as they are made.
77
+
78
+ ## [0.7.0] - 2026-09-28
79
+
80
+ ### Added
81
+
82
+ - **Site settings.** A theme defines site-wide settings in `theme/config/settings_schema.json` - social links, a font choice, anything a whole site shares - in the same JSON Schema form as a section's `{% schema %}`. Every layout, section, block and snippet reads them as `{{ settings.<name> }}`, with the theme's defaults filling anything not yet saved. The values are content, in `content/settings.json`: `GET /v1/settings` returns the schema, the saved values and what templates see; `PUT /v1/settings` (If-Match) validates, commits and puts them live at once, like a menu. Saving refreshes every cached page. New sites' footer links and body font are site settings. `npm run pull` and `npm run push` carry site settings with content, protected the same way (settings changed on the live site since the pull stop a push; a copy with no `settings.json` never clears the live ones). The admin has a matching Settings screen.
83
+
84
+ ## [0.6.1] - 2026-09-28
85
+
86
+ ### Changed
87
+
88
+ - **`npm run push` works on a copy that has never pulled** - a site built locally and deployed, for example. The live site as it is now stands in for the pull: everything that differs locally is listed as a new or updated page, theme file or redirect, with a warning to check it, and nothing is deleted from the live site (a live page missing locally may be one an editor made). Afterwards only what the copy and the live site agree on is recorded, so live pages the copy never had are left alone by later pushes too. A CMS upgrade on its own no longer needs a pull. With no record, push asks for the site's address.
89
+
90
+ ## [0.6.0] - 2026-09-28
91
+
92
+ ### Added
93
+
94
+ - **`npm run pull` and `npm run push` handle the theme too.** Both now ask what to include, with checkboxes for **Content** and **Theme** (`--content` and `--theme` choose without asking); push shows how many changes each has. Theme sync uses three new routes, all needing the `theme` token scope: `GET /v1/theme/files`, `GET /v1/theme/files/*` and `POST /v1/theme/push`. A theme push is protected like content (a file changed on the live site since the pull stops it), is refused if a Liquid template can't be parsed, commits once, and takes effect immediately with no restart. The theme is pushed before content, so pages using a new section type arrive after the theme that defines it.
95
+
96
+ - **`npm run upgrade` upgrades the CMS on your computer** (`upgrade-site`, a new bin), for testing before `npm run push` puts it live. It shows the new version's changelog entries, asks, installs it, and then hands over to the new version, which puts back its own files in `vhost/` (`Dockerfile`, `docker-entrypoint.sh`, `server.js`, `.dockerignore`, and its scripts in `package.json`, keeping the developer's own), updates content to the new format, and runs the site check. Nothing is committed. `CHANGELOG.md` now ships in the package so the upgrade can show it. Sites on 0.5.x run their first upgrade as `npm install @o-a/cms-agent@0.6.0 --save-exact` then `npx upgrade-site --finish`.
97
+
98
+ - **`npm run push` can upgrade the live site's CMS.** When the CMS here is newer than the live site's, push offers a third choice, **CMS upgrade** (`--cms` for scripts). It deploys, after the site's address is typed, using the command in `vhost/deploy.json` if there is one (`{ "command": "git push" }`, or any command that deploys the site), otherwise Railway (`railway up`) or Fly.io (`fly deploy`) when the site is set up for them, otherwise it pauses while you deploy by hand. It then waits for the live site to report the new version and for its home page to load, and only then plans and sends content and theme, with their own list and confirmation. A live site on an older content format only accepts content and theme together with the upgrade.
99
+
100
+ ### Fixed
101
+
102
+ - **A pull rewrote `redirects.json` even when the live redirects were the same,** which showed as a change (only the file's formatting differed). It's now left alone unless the redirects differ.
103
+ - **Nine of the scaffold's starter pages had no `name`,** which the page format has required since content schema 5. They rendered, because the display falls back to the title, but updating their content format validates them, so every new site's first upgrade would have failed on its own starter pages.
104
+
105
+ ### Changed
106
+
107
+ - **A running site reloads its theme without a restart** after a theme push. Routes now read the theme from one shared `ThemeState` rather than a copy taken at start-up, and cached pages are keyed on the theme too.
108
+ - **The content-format update can leave its changes uncommitted** (`runMigrations(..., { commit: false })`), which `npm run upgrade` uses.
109
+ - **The pull record is kept per part** (content, theme), so either can be pulled alone. Records written by 0.5.5 are still read, as the content part.
110
+
111
+ ## [0.5.5] - 2026-09-28
112
+
113
+ ### Added
114
+
115
+ - **`npm run push` sends local content changes back to the live site** (`push-site`, a new bin). It compares each page and menu as last pulled, as it is locally, and as it is live now, and pushes only local changes: a page changed on the live site since the pull is never overwritten (it stops the push and is listed as a conflict), and a live page is only deleted if it was deleted locally. Missing images the pushed pages use are uploaded under the same names. It lists every change, warns that it overwrites the live site, and asks for the site's address to be typed; `--dry-run` only lists. Page and menu changes go as one batch, one commit, or not at all. `npm run pull` now records what it pulled, which push needs. Existing sites add `"push": "push-site"` to `vhost/package.json`'s scripts.
116
+
117
+ ### Changed
118
+
119
+ - **`npm run pull` asks for what it needs**: the live site's address when none is given, then the token when neither `CMS_TOKEN` nor `--token` is, without showing what is typed or pasted, so the token never lands in shell history. An address can be typed as `my-site.example` (taken as `https://`) or `localhost:3600` (`http://`), and is checked before the token is asked for. Piped input still works (address, then token, one per line), so scripts are unaffected.
120
+
121
+ ## [0.5.4] - 2026-09-28
122
+
123
+ ### Added
124
+
125
+ - **`npm run pull` copies a running site's content and media into a local one** (`pull-site`, a new bin): `CMS_TOKEN=<token> npm run pull -- <url>` from `vhost/`. Pages, menus, drafts and redirects are mirrored and missing media downloaded, through the `/v1/` API, so it works wherever the site is hosted. The theme is never touched and nothing is committed. It refuses while `content/` has uncommitted changes (`--force` overrides), refuses a live site on a newer content schema, and checks every server-supplied path before it becomes a local file. Existing sites add `"pull": "pull-site"` to `vhost/package.json`'s scripts.
126
+
127
+ ## [0.5.3] - 2026-09-24
128
+
129
+ ### Added
130
+
131
+ - **`npm run stop` stops a site from any terminal**, not only the one it was started in (`stop-site`, a new bin). The server records the process to stop in `vhost/data/server.pid`; under `npm run dev` that is the watcher, so both it and the server end. It sends `SIGTERM` (the same graceful shutdown as Ctrl+C) and only to a process still answering as a site on the recorded port. Starting a site that is already running now says so instead of reporting a bare port conflict. Existing sites add `"stop": "stop-site"` to `vhost/package.json`'s scripts.
132
+
133
+ ## [0.5.2] - 2026-09-24
134
+
135
+ ### Added
136
+
137
+ - **A menu can have a display name**, separate from its handle: an optional, non-empty `"name"` in `content/menus/<handle>.json` (content schema 7, with an identity migration - no existing menu needs a value). Layouts can print it as `{{ menus.<handle>.name }}`, for example as a footer column heading. Renaming a menu this way never affects a layout.
138
+ - **A menu's handle can be changed** with `POST /v1/menus/rename` (`{ from, to, message, author }`, If-Match): one commit through the write queue, refusing a handle that is taken, a stale ETag, a missing menu, and anything but letters, numbers, hyphens and underscores. It returns the theme files still using the old handle, since every one of them now renders an empty menu until updated.
139
+ - **`GET /v1/menus/references?handle=<handle>`** lists the theme files that use a handle (`menus.<handle>` or `menus['<handle>']`, as a whole name only), so a client can warn before a rename or delete.
140
+
141
+ ### Fixed
142
+
143
+ - **Redeploying never upgraded the agent on an existing volume.** The scaffolded `docker-entrypoint.sh` copied the image onto `/site` on first boot only, so the installed `@o-a/cms-agent` on a persistent volume was never replaced, and the only way to upgrade was destroying the volume and every live edit with it. Every later boot now refreshes `vhost/package.json`, `package-lock.json` and `node_modules` from the image and touches nothing else. Existing sites need this script copied into their own `vhost/` once.
144
+ - **A renamed or deleted menu left cached pages showing their old navigation.** The render cache's menu freshness check used the newest menu file's modification time; a rename keeps that time and a delete removes a file, so when the changed menu was not the most recently edited one, nothing moved. The menus directory's own modification time now counts too.
145
+
146
+ ## [0.5.1] - 2026-09-19
147
+
148
+ ### Fixed
149
+
150
+ - **`seed-media` could not be run the way every document said to run it.** `npx seed-media . <file>` from the site directory never worked: there is no `package.json` or `node_modules` at the site root - the CMS is installed under `vhost/` - so npx resolves nothing locally, asks the public registry for a package called "seed-media" and fails with a 404, *while still exiting 0*. A generated site consequently put all 28 of its images in `theme/root/images/`, because the one documented route into `media/` was a dead end that reported no error. The generated `vhost/package.json` now exposes a `seed-media` script, exactly as it already did for `check`, and both documents give the working form: `npm run seed-media -- .. <file>` from `vhost/`.
151
+ - **`npm run check` now reports a content image or video served from `theme/root/` or `theme/assets/`** as a `misplaced-media` finding. This had no other way of being noticed: the file exists, so the page renders perfectly, and the only symptom is that the media library never shows the image and an editor can never replace it. The rule keys off the same extension list the upload route accepts, so the two cannot drift, and only examines `src`/`srcset`/`poster` - an `href` is a link or a favicon, and `.svg` is absent from that list, so a site's own icon and an inline logo are not mistaken for misplaced photographs.
152
+ - **A `mailto:` link was reported as a missing file**, because the text after the "@" contains a dot and so looked like a filename. One real site produced 39 such findings and not a single true one - output that noisy trains whoever reads it to ignore the check entirely. Any non-http scheme (`mailto:`, `tel:`, and anything future) is now skipped.
153
+ - **A video's `poster` was never checked at all.** It is a real content image reference and is now scanned alongside `src` and `srcset`.
154
+ - **A site with no `media/`, `theme/assets/` or `theme/root/` directory crashed the check** with a raw `ENOENT` instead of reporting the missing file, because path sanitisation resolves its root argument unconditionally. The guard fell over on exactly the sites most likely to hold a broken reference. A root directory that does not exist now means the file does not exist, which is an ordinary finding.
155
+
156
+ ## [0.5.0] - 2026-09-18
157
+
158
+ ### Added
159
+
160
+ - **Every scaffolded site now ships `theme/snippets/responsive-media.liquid`**, and the scaffold uses it: `hero` gains an image setting and `cta-banner` a short silent background loop. It renders a `format: "image"` or `format: "video"` setting and marks the result as a drag-and-drop target, so an editor can drag a file from the admin's media library straight onto the picture in the live preview. Until now the scaffold shipped no image snippet at all, and not one of its sections or blocks even had an image setting, so a theme author had nothing to copy.
161
+ - It deliberately builds no `srcset`. Uploaded files are named by a hash of their own bytes, so a resized variant's name is not derivable from the original's URL - hand-built variant URLs point at files that do not exist, producing a page that looks perfect except at one breakpoint, which is precisely the failure `npm run check` exists to catch.
162
+ - An unset image renders nothing at all rather than a placeholder. A theme cannot tell whether it is rendering for the admin preview or for the public site, so an editor-only affordance would show to real visitors.
163
+ - A video slot emits `data-cms-media`/`data-cms-media-kind` but never `data-cms-image`. An admin predating video support understands only the older attribute and would write an image-shaped value (`{ url, focalX, focalY }`) straight over the field's own `{ url, poster }` - better it ignores the slot than corrupts it.
164
+
165
+ ### Fixed
166
+
167
+ - **The attribute that makes an image editable was documented nowhere.** Drag-and-drop finds a drop target purely by looking for `data-cms-image` in the previewed page, and no document in this project mentioned it. A real generated site consequently shipped with every image un-editable: the theme rendered perfectly, images simply were not droppable, and nothing about the page looked wrong. `AGENTS.md` and `guide-theme-authoring.md` now both spell out the contract, and do so at each point an image actually comes up rather than in one section near the end.
168
+ - **`AGENTS.md` said an image "is just a plain string setting"**, treating `format: "image"` as the exception for when a focal point is wanted. That is backwards: a drop writes an object, so a string-declared field fails validation outright. The object form is now the documented default, and the `seed-media` example that still produced a plain string has been corrected to match.
169
+ - **Both guides told authors to hand-write the markup** - `{{ section.settings.<field>.url }}` for an image, a bare `<video>` for a loop. Both now point at the snippet, including the format table rows, the worked example a section is copied from, and the snippets section itself.
170
+
171
+ ## [0.4.0] - 2026-09-17
172
+
173
+ ### Added
174
+
175
+ - **The media library now accepts and serves video** (`.mp4` and `.webm`), for the short silent loops that stand in for a hero image rather than long-form video. Same 10MB cap as any other upload - anything longer belongs on YouTube or Vimeo, embedded. `.svg` stays rejected (it is never sanitised, so it is a stored-XSS path when loaded as a top-level navigation), and `.mov` with it, since QuickTime frequently will not play in Chrome or Firefox at all.
176
+ - **Real HTTP Range support on `/media/*`**: `Accept-Ranges: bytes` on every response, a `206` with `Content-Range` for a satisfiable range, and a `416` for one starting past the end. This is not a bandwidth optimisation. Safari and iOS open a `<video>` with `Range: bytes=0-1` and refuse to play at all without a `206` in reply, so hosted video simply did not work before this. A suffix range (`bytes=-500`) correctly means the *last* 500 bytes rather than the first, an end past the last byte is clamped rather than rejected (browsers routinely overshoot), and a multi-range request falls back to sending the whole file rather than answering with an incorrect multipart body.
177
+ - **`format: "video"`** is now a recognised UI hint on an `"type": "object"` setting, storing `{ url, poster }`. The admin renders a video picker with a poster picker for it, in place of the raw-JSON fallback an unrecognised object shape otherwise gets.
178
+
179
+ ### Documentation
180
+
181
+ - **The scaffold's `AGENTS.md` now says where images actually go.** An agent building a real site from it put every image in the site root, twice running. `assets/` is now described as design assets only, the workflow names the `seed-media` step and `npm run check` explicitly, and the Images section leads with where images live. It also states plainly that one image is one file and one URL, and that the CMS does not generate resized variants - previously left to inference.
182
+ - **`format: "video"` is documented in both `AGENTS.md` and `guide-theme-authoring.md`.** Both present their format table as an exhaustive, closed set ("never invent a new setting field shape"), so leaving the new value out did not merely omit it - it actively told theme authors the field did not exist.
183
+
184
+ ## [0.3.1] - 2026-09-17
185
+
186
+ ### Fixed
187
+
188
+ - **The scaffold's own `AGENTS.md` now explains what a page's `type` does.** It was described only as "free-form", with nothing saying that a template's `type` is inherited by every page created from it, or that the value is what `GET /search.json?pageType=...` filters on. An Article template left at `"type": "page"` therefore produces articles no blog index can find - and because it still validates and previews perfectly, nothing signals the mistake. Documentation only; no code change.
189
+
190
+ ## [0.3.0] - 2026-09-17
191
+
192
+ ### Added
193
+
194
+ - **`POST /v1/publish-page/:path`**: sets `published: true` on a live page in place and commits - the exact twin of the existing `POST /v1/unpublish/:path`. Deliberately separate from `POST /v1/publish`, which promotes drafts: a page that is live but unpublished has no draft to promote, so publish could not reach it at all, and promoting a draft would push every pending edit live alongside the flag. Like unpublish, it never touches a draft, so a page with unpublished edits keeps them.
195
+
196
+ ## [0.2.2] - 2026-09-09
197
+
198
+ Three gaps found by having a different AI agent build a real demo site from `AGENTS.md` and report back what it wished were different.
199
+
200
+ ### Fixed
201
+
202
+ - **The search index now keeps itself current automatically.** Previously `GET /search.json` stayed empty forever on a fresh site and stale forever after a real edit - nothing ever rebuilt it except a caller manually hitting `POST /v1/search/rebuild`. It now rebuilds in the background after every publish/unpublish/delete/move/batch write, and once at boot if no index exists yet (the index is never git-tracked, so a fresh clone starts with none). Fire-and-forget in both cases - a slow or failed reindex never blocks or fails the write that triggered it.
203
+
204
+ ### Added
205
+
206
+ - **Sections and blocks can now render `page.author`, `page.publishDate`, and `page.tags`** - the same built-in envelope a layout already got (previously just `page.title`), widened and made available to section/block templates too, not just layouts. Closes a real gap: these fields were already indexed for search but had no way to actually appear on the rendered page without duplicating the value into a settings field.
207
+ - **A broken theme component now prints a boot warning instead of vanishing silently.** A section/block with no `{% schema %}` block, invalid JSON in it, or a required property with no valid default was already excluded from the theme (unchanged, still never a boot failure) - it just did so with zero output anywhere. `loadThemeSchemas` now names each excluded type and the specific reason.
208
+
209
+ ## [0.2.1] - 2026-09-09
210
+
211
+ ### Added
212
+
213
+ - **A new `AGENTS.md` in every scaffolded site**, onboarding an AI coding agent to this specific CMS's conventions: theme folder structure, the section/block `{% schema %}` pattern with worked examples, the `format` field-hint table, the current content JSON model, image handling, `GET /search.json`, and the hard constraints. Self-contained - no external link, since generated content shouldn't depend on a live docs site being reachable.
214
+
215
+ ## [0.2.0] - 2026-09-09
216
+
217
+ ### Breaking changes
218
+
219
+ - **The distinct "post" content type is gone.** `author`, `publishDate`, and `tags` are now optional fields any page may carry - there is no separate schema, and `/blog` is no longer a reserved URL namespace (a page can live at `/blog/<slug>` the same way it can live anywhere else, by nesting it under a `blog` page). A new `schemaVersion` 6 migration relocates any existing `content/posts/<slug>.json` file to `content/pages/blog/<slug>.json`, preserving its old URL, and backfills a `name` field if it doesn't already have one.
220
+ - **Known gap**: there's no automatic migration trigger at boot, and no CLI command runs one either (`runMigrations` exists in `src/services/migration-runner.ts` but nothing currently calls it outside tests). No adopter currently has content under `content/posts/`, so this doesn't affect anyone today - but the migration path itself needs a real trigger mechanism before it would.
221
+ - **`GET /v1/search` has moved to `GET /search.json`, unprefixed.** The old path is removed outright, not kept as an alias. Update any theme code calling it directly. The endpoint's behaviour (full-text search, structured filters, sort, pagination) is unchanged - only the path moved, out from under the versioned `/v1/` admin/integration surface to reflect that it's a stable, public, front-end-facing contract instead.
222
+ - **Uploaded media filenames changed shape**: `<slug>-<hash>.<ext>` instead of `<hash>-<slug>.<ext>` for new uploads. Still the same content-addressed name (a sha256 hash of the file's own bytes); only the order changed, so the readable part sorts first in a directory listing or URL. Existing uploaded files are untouched - this only affects new uploads going forward.
223
+
224
+ ### Added
225
+
226
+ - **`GET /search.json`**: one unified, genuinely public query endpoint - no bearer token required, full-text search (`q`), structured filters (`filter=field:op:value`, repeatable, AND'd together), sort, and pagination in a single response. Replaces an earlier, narrower `GET /v1/search/fields` that queried one field at a time.
227
+ - **A new `"api": true` JSON Schema keyword** for section/block settings, exposing that field's value through the search index for structured filtering (e.g. `filter=price:lt:50`) - independent of full-text search.
228
+ - **`GET /admin`**: a configurable, opt-in redirect to your admin instance. Set `adminBaseUrl` in `site.config.json` and visiting `yoursite.com/admin` redirects there with `?site=<host>` appended, so the admin can resolve straight to this site's own dashboard. Leave it unset and the route doesn't exist at all - a site can still use "admin" as an ordinary page path.
229
+ - **An in-process render cache for public pages.** Previously every request re-read and re-rendered a page from scratch; rendered HTML is now cached and validated against real file mtimes (the page's own file, plus every menu file, since menus render into every page's nav), so a repeat request for an unchanged page skips rendering entirely.
230
+ - **`Cache-Control: public, max-age=31536000, immutable`** on every media response - safe unconditionally, since filenames are content-addressed and a given URL's bytes can never change.
231
+
232
+ ### Fixed
233
+
234
+ - **`engines.node` was wrong and is now `>=22.16.0`, not `>=22.6.0`.** The old floor was never actually verified against real Node builds: `node:sqlite` needs `--experimental-sqlite` below Node 22.13.0 (never passed anywhere in this package), and FTS5 support itself - the search index's own full-text engine - isn't compiled in at all until Node 22.16.0. Every version from the old floor up to 22.15.x would hard-crash the moment a search index rebuild ran. Confirmed by downloading and testing real Node builds directly, not assumed from release notes.
235
+ - **A search index rebuild no longer blocks the entire server.** `rebuildIndexJob` was `async` but had no real `await` in its body, so on a busy site a rebuild froze every other request (not just search) for its whole duration. It now yields to the event loop periodically during a rebuild.
236
+ - **A search index rebuild is now atomic.** Previously the index file was deleted then recreated in place, a real window where a concurrent read could see a missing file or one with no tables yet. It's now built into a temp file and swapped in via an atomic rename.
237
+
238
+ ### Documentation
239
+
240
+ - Caught up `guide-theme-authoring.md` (then named `theme-authoring-guide.md`) on several already-shipped theme field conventions that were undocumented or stale: `format: "richtext"`, `format: "image"` (with focal-point support), `format: "color"`'s `swatches` keyword, the range field format, the consolidated toggle/select field formats.
package/LICENSE ADDED
@@ -0,0 +1,105 @@
1
+ # Functional Source License, Version 1.1, ALv2 Future License
2
+
3
+ ## Abbreviation
4
+
5
+ FSL-1.1-ALv2
6
+
7
+ ## Notice
8
+
9
+ Copyright 2026 Scott Gray
10
+
11
+ ## Terms and Conditions
12
+
13
+ ### Licensor ("We")
14
+
15
+ The party offering the Software under these Terms and Conditions.
16
+
17
+ ### The Software
18
+
19
+ The "Software" is each version of the software that we make available under
20
+ these Terms and Conditions, as indicated by our inclusion of these Terms and
21
+ Conditions with the Software.
22
+
23
+ ### License Grant
24
+
25
+ Subject to your compliance with this License Grant and the Patents,
26
+ Redistribution and Trademark clauses below, we hereby grant you the right to
27
+ use, copy, modify, create derivative works, publicly perform, publicly display
28
+ and redistribute the Software for any Permitted Purpose identified below.
29
+
30
+ ### Permitted Purpose
31
+
32
+ A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
33
+ means making the Software available to others in a commercial product or
34
+ service that:
35
+
36
+ 1. substitutes for the Software;
37
+
38
+ 2. substitutes for any other product or service we offer using the Software
39
+ that exists as of the date we make the Software available; or
40
+
41
+ 3. offers the same or substantially similar functionality as the Software.
42
+
43
+ Permitted Purposes specifically include using the Software:
44
+
45
+ 1. for your internal use and access;
46
+
47
+ 2. for non-commercial education;
48
+
49
+ 3. for non-commercial research; and
50
+
51
+ 4. in connection with professional services that you provide to a licensee
52
+ using the Software in accordance with these Terms and Conditions.
53
+
54
+ ### Patents
55
+
56
+ To the extent your use for a Permitted Purpose would necessarily infringe our
57
+ patents, the license grant above includes a license under our patents. If you
58
+ make a claim against any party that the Software infringes or contributes to
59
+ the infringement of any patent, then your patent license to the Software ends
60
+ immediately.
61
+
62
+ ### Redistribution
63
+
64
+ The Terms and Conditions apply to all copies, modifications and derivatives of
65
+ the Software.
66
+
67
+ If you redistribute any copies, modifications or derivatives of the Software,
68
+ you must include a copy of or a link to these Terms and Conditions and not
69
+ remove any copyright notices provided in or with the Software.
70
+
71
+ ### Disclaimer
72
+
73
+ THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
74
+ IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
75
+ PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
76
+
77
+ IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
78
+ SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
79
+ EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
80
+
81
+ ### Trademarks
82
+
83
+ Except for displaying the License Details and identifying us as the origin of
84
+ the Software, you have no right under these Terms and Conditions to use our
85
+ trademarks, trade names, service marks or product names.
86
+
87
+ ## Grant of Future License
88
+
89
+ We hereby irrevocably grant you an additional license to use the Software under
90
+ the Apache License, Version 2.0 that is effective on the second anniversary of
91
+ the date we make the Software available. On or after that date, you may use the
92
+ Software under the Apache License, Version 2.0, in which case the following
93
+ will apply:
94
+
95
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not use
96
+ this file except in compliance with the License.
97
+
98
+ You may obtain a copy of the License at
99
+
100
+ http://www.apache.org/licenses/LICENSE-2.0
101
+
102
+ Unless required by applicable law or agreed to in writing, software distributed
103
+ under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
104
+ CONDITIONS OF ANY KIND, either express or implied. See the License for the
105
+ specific language governing permissions and limitations under the License.
package/README.md CHANGED
@@ -1,3 +1,134 @@
1
- # Temporary Holding Version
1
+ # Minamo framework
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **Own your content, forever.**
4
+
5
+ The Minamo framework is a self-hosted web framework for content sites,
6
+ built on Liquid templates. Every page is a plain JSON file. Every publish
7
+ is a real git commit. There is no database standing between you and your
8
+ own content, and there never will be. Editors manage a site through the
9
+ Minamo CMS, or any tool that speaks its versioned API.
10
+
11
+ If you've ever had a headless CMS raise its prices, change its export
12
+ format, or go down and take your site's editing with it - this is built to
13
+ make that structurally impossible. Your site and its content stay yours:
14
+ moving away is `git clone`. That's the whole migration story.
15
+
16
+ ## Why this is different
17
+
18
+ Most self-hosted, code-first CMS options give you an API and a database.
19
+ Most git-based CMS tools give you markdown files and a static-site rebuild.
20
+ The Minamo framework is the combination neither of those is:
21
+
22
+ - **Git-native content.** Pages, posts, menus, and redirects are JSON files
23
+ in your own git repository. No database is ever the source of truth.
24
+ History, diffs, and rollback are real git operations, not a bolted-on
25
+ revisions table.
26
+ - **A real page builder, not just files.** Themes define reusable, schema
27
+ validated **sections** and **blocks** - a Shopify-style content model - so
28
+ a non-technical editor arranges and fills in components a developer
29
+ already built, instead of hand-editing markdown or JSON.
30
+ - **A real versioned HTTP API.** `/v1/...` is the only thing a site exposes.
31
+ Drafts, publishing, optimistic concurrency (ETags + `If-Match`), batch
32
+ writes, and git history are all first-class API operations - what the
33
+ Minamo admin (or your own tooling) talks to, never the filesystem
34
+ directly.
35
+
36
+ ## Two audiences, one framework
37
+
38
+ - **Developers** build the theme: Liquid layouts, sections, and blocks,
39
+ each with an embedded JSON Schema for its settings. Start with
40
+ [`docs/guide-theme-authoring.md`](docs/guide-theme-authoring.md).
41
+ - **Content editors / marketing managers** never touch this repository at
42
+ all - they work entirely through the Minamo admin (see
43
+ [Editing with Minamo](#editing-with-minamo) below), browsing pages,
44
+ editing sections, and publishing through the API this package exposes.
45
+ The content model itself is documented in
46
+ [`docs/guide-content-authoring.md`](docs/guide-content-authoring.md).
47
+
48
+ ## Quick start
49
+
50
+ ```
51
+ npx -p @minamocms/framework create-site my-site
52
+ cd my-site/vhost
53
+ npm install
54
+ npm start
55
+ ```
56
+
57
+ Visit `http://localhost:3000`. `create-site` prints a real access token to
58
+ your terminal when it scaffolds the site - that's what the Minamo admin
59
+ (or a plain `curl`) uses to authenticate against the site's
60
+ `/v1/` API. Lost it, or need a second one? See the `mint-token` command in
61
+ the same docs page.
62
+
63
+ ## Project layout
64
+
65
+ A scaffolded site is a thin, four-folder scaffold - everything a content
66
+ editor touches lives under `content/`, everything a developer touches lives
67
+ under `theme/`, uploaded media lives under `media/` (never git-tracked),
68
+ and the site's own serving configuration lives under `vhost/`:
69
+
70
+ ```
71
+ my-site/
72
+ content/ pages, menus, redirects, drafts
73
+ theme/ layouts, sections, blocks, snippets, assets, root, templates
74
+ media/ uploaded files - gitignored, backed up separately
75
+ vhost/ site.config.json, package.json, server.js
76
+ ```
77
+
78
+ This repository (the framework itself, the `@minamocms/framework` package) is developed
79
+ separately and installed as a versioned dependency - see [`docs/cms-build-plan.md`](docs/cms-build-plan.md)
80
+ for the full architecture and the reasoning behind it.
81
+
82
+ ## Tech stack
83
+
84
+ Node.js 22+ and TypeScript, compiled to plain JavaScript for distribution.
85
+ Fastify, LiquidJS (sandboxed - no dynamically registered tags or filters,
86
+ ever), SQLite via `node:sqlite` for a fully disposable, rebuildable search
87
+ index, and the real `git` binary for every content mutation. Dependencies
88
+ are kept deliberately minimal - this ships as a package other people
89
+ install.
90
+
91
+ ## Documentation
92
+
93
+ - [`docs/guide-theme-authoring.md`](docs/guide-theme-authoring.md) - building a theme
94
+ - [`docs/guide-content-authoring.md`](docs/guide-content-authoring.md) - the content model
95
+ - [`docs/guide-hosting.md`](docs/guide-hosting.md) - running a site somewhere real
96
+ - [`docs/cms-build-plan.md`](docs/cms-build-plan.md) - full architecture and design rationale
97
+
98
+ Friendlier, browsable documentation, covering both this framework and the
99
+ Minamo admin, is at [docs.minamocms.com](https://docs.minamocms.com).
100
+
101
+ ## Editing with Minamo
102
+
103
+ The framework has no user interface of its own by design - it's an API.
104
+ Day-to-day editing happens in the Minamo admin, a hosted service that
105
+ manages any number of independently hosted sites. Your sites stay yours:
106
+ they run wherever you host them, their content is in your own git
107
+ repository, and the admin reaches them only through the versioned `/v1/`
108
+ API above, with the site's own token. Anything else that speaks that API
109
+ works too.
110
+
111
+ ## Upgrading from `@o-a/cms-agent`
112
+
113
+ Up to 0.9.x this package was published as `@o-a/cms-agent` (and the
114
+ project was called Granite CMS). From `vhost/`, run `npm run upgrade`: on
115
+ 0.9.3 it moves the site over to `@minamocms/framework`, and on 0.6.0 to 0.9.2 it first
116
+ reaches 0.9.3, so run it a second time. On 0.5.x, run
117
+ `npm install @o-a/cms-agent@0.9.3 --save-exact` and
118
+ `npx upgrade-site --finish` first.
119
+
120
+ ## Project status
121
+
122
+ This is young, real software - it runs a genuine production site today,
123
+ not just a demo, but it has one tenant so far, not thousands. Expect rough
124
+ edges, expect things to improve in the open rather than arrive finished,
125
+ and please open an issue if something doesn't work the way this README
126
+ says it should.
127
+
128
+ ## License
129
+
130
+ [Functional Source License, Version 1.1, Apache 2.0 Future Grant](LICENSE)
131
+ (FSL-1.1-ALv2). In short: free to use, modify, and self-host for anything
132
+ except offering it as a competing hosted service. Every version converts
133
+ automatically to the fully permissive Apache License 2.0 two years after
134
+ its own release - this is a head start, not a permanent restriction.
package/dist/boot.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ import type { Liquid } from 'liquidjs';
2
+ import type { SiteConfig } from './config.ts';
3
+ import type { RenderCache } from './renderer/render-cache.ts';
4
+ import type { ThemeTemplates } from './renderer/theme-templates.ts';
5
+ import type { StartupCheckOptions } from './services/startup-checks.ts';
6
+ import type { ThemeSchemas } from './services/validation.ts';
7
+ import type { PageTemplate } from './services/theme-page-templates.ts';
8
+ import { ThemeState } from './theme-state.ts';
9
+ export interface BootedSite {
10
+ config: SiteConfig;
11
+ themeSchemas: ThemeSchemas;
12
+ themeTemplates: ThemeTemplates;
13
+ layouts: Record<string, string>;
14
+ pageTemplates: PageTemplate[];
15
+ engine: Liquid;
16
+ theme: ThemeState;
17
+ renderCache: RenderCache;
18
+ }
19
+ export declare function bootSite(siteRoot: string, options?: StartupCheckOptions): BootedSite;
package/dist/boot.js ADDED
@@ -0,0 +1,22 @@
1
+ import { loadSiteConfig } from "./config.js";
2
+ import { createRenderCache } from "./renderer/render-cache.js";
3
+ import { runStartupChecks } from "./services/startup-checks.js";
4
+ import { ensureDraftsRoot } from "./services/drafts.js";
5
+ import { loadTheme, ThemeState } from "./theme-state.js";
6
+ export function bootSite(siteRoot, options) {
7
+ runStartupChecks(siteRoot, options);
8
+ const config = loadSiteConfig(siteRoot);
9
+ ensureDraftsRoot(config);
10
+ const initial = loadTheme(config);
11
+ const renderCache = createRenderCache();
12
+ return {
13
+ config,
14
+ themeSchemas: initial.themeSchemas,
15
+ themeTemplates: initial.themeTemplates,
16
+ layouts: initial.layouts,
17
+ pageTemplates: initial.pageTemplates,
18
+ engine: initial.engine,
19
+ theme: new ThemeState(config, initial),
20
+ renderCache,
21
+ };
22
+ }
@@ -0,0 +1,18 @@
1
+ export declare const VHOST_DIR_NAME = "vhost";
2
+ export interface SiteConfig {
3
+ siteRoot: string;
4
+ contentRoot: string;
5
+ draftsRoot: string;
6
+ themeRoot: string;
7
+ assetsRoot: string;
8
+ rootMirrorRoot: string;
9
+ templatesRoot: string;
10
+ pagesRoot: string;
11
+ menusRoot: string;
12
+ redirectsPath: string;
13
+ vhostRoot: string;
14
+ dataRoot: string;
15
+ searchIndexPath: string;
16
+ mediaRoot: string;
17
+ }
18
+ export declare function loadSiteConfig(siteRoot: string): SiteConfig;
package/dist/config.js ADDED
@@ -0,0 +1,44 @@
1
+ import { isAbsolute, join, resolve } from 'node:path';
2
+ // Single source of truth for the runtime/serving folder's name, reused
3
+ // by server-config.ts (site.config.json lives inside it) rather than a
4
+ // second hardcoded literal. A real vhost, in hosting terms (Apache/
5
+ // nginx/CyberPanel): the per-site serving configuration, not a
6
+ // multi-tenancy concept - this folder holds exactly that (package.json,
7
+ // server.js, site.config.json's access-control/serving parameters).
8
+ export const VHOST_DIR_NAME = 'vhost';
9
+ // Pure path derivation, no fs calls. siteRoot must be supplied by the
10
+ // caller (agent configuration) and must already be absolute: the agent
11
+ // never assumes it lives inside the site repo, and never falls back to
12
+ // resolving against its own module location or process.cwd().
13
+ export function loadSiteConfig(siteRoot) {
14
+ if (!isAbsolute(siteRoot)) {
15
+ throw new Error(`siteRoot must be an absolute path, got "${siteRoot}"`);
16
+ }
17
+ const normalisedRoot = resolve(siteRoot);
18
+ const contentRoot = join(normalisedRoot, 'content');
19
+ const themeRoot = join(normalisedRoot, 'theme');
20
+ const vhostRoot = join(normalisedRoot, VHOST_DIR_NAME);
21
+ const dataRoot = join(vhostRoot, 'data');
22
+ return {
23
+ siteRoot: normalisedRoot,
24
+ contentRoot,
25
+ // Nested inside contentRoot (content/drafts), not a sibling of it -
26
+ // marketing-manager-managed content lives entirely under content/.
27
+ // See content-read.ts/migration-runner.ts for the inclusion-based
28
+ // walk fix this requires, and checkpoint.ts for the derived (not
29
+ // hardcoded) pathspec fix.
30
+ draftsRoot: join(contentRoot, 'drafts'),
31
+ themeRoot,
32
+ assetsRoot: join(themeRoot, 'assets'),
33
+ rootMirrorRoot: join(themeRoot, 'root'),
34
+ templatesRoot: join(themeRoot, 'templates'),
35
+ pagesRoot: join(contentRoot, 'pages'),
36
+ menusRoot: join(contentRoot, 'menus'),
37
+ // Also nested inside contentRoot, for the same reason.
38
+ redirectsPath: join(contentRoot, 'redirects.json'),
39
+ vhostRoot,
40
+ dataRoot,
41
+ searchIndexPath: join(dataRoot, 'search-index.sqlite'),
42
+ mediaRoot: join(normalisedRoot, 'media'),
43
+ };
44
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};