@minamocms/framework 0.0.0-stage → 0.10.1

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 +234 -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 +81 -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
@@ -0,0 +1,12 @@
1
+ export declare const TEMPLATE_ROOT: string;
2
+ export declare function generateToken(): {
3
+ raw: string;
4
+ hash: string;
5
+ };
6
+ export declare const SERVER_JS = "import { startServer } from '@minamocms/framework';\nimport { join } from 'node:path';\n\nawait startServer(join(import.meta.dirname, '..'), { tunnel: process.argv.includes('--tunnel') });\n";
7
+ export declare const SCAFFOLD_SCRIPTS: Record<string, string>;
8
+ export declare class ScaffoldError extends Error {
9
+ }
10
+ export declare function scaffoldSite(targetDir: string): {
11
+ raw: string;
12
+ };
@@ -0,0 +1,168 @@
1
+ import { createHash, randomBytes } from 'node:crypto';
2
+ import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { basename, join } from 'node:path';
4
+ import { execFileSync } from 'node:child_process';
5
+ import { CHECKPOINT_AUTHOR } from "../services/checkpoint.js";
6
+ import { commitPaths } from "../services/git.js";
7
+ import { PACKAGE_NAME } from "../upgrade/package-names.js";
8
+ // This file's own bundled template (dist/create-site/template, copied
9
+ // there by `npm run build`'s cp step) - the agent's own asset, not
10
+ // site data, matching validation.ts's/capabilities.ts's existing
11
+ // import.meta.dirname-relative pattern for the agent's own bundled
12
+ // files (see test/static/static-analysis.test.ts's B1 allowlist).
13
+ export const TEMPLATE_ROOT = join(import.meta.dirname, 'template');
14
+ // The agent's own bundled package.json, reused for the exact version
15
+ // to pin the scaffold's dependency to - never a second hardcoded
16
+ // literal, same reasoning as capabilities.ts's readAgentVersion.
17
+ function readAgentVersion() {
18
+ const packageJsonPath = join(import.meta.dirname, '..', '..', 'package.json');
19
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
20
+ return packageJson.version ?? '0.0.0';
21
+ }
22
+ function sanitisePackageName(dirName) {
23
+ const cleaned = dirName.toLowerCase().replace(/[^a-z0-9-]+/g, '-').replace(/^-+|-+$/g, '');
24
+ return cleaned.length > 0 ? cleaned : 'my-site';
25
+ }
26
+ export function generateToken() {
27
+ const raw = randomBytes(32).toString('hex');
28
+ const hash = createHash('sha256').update(raw).digest('hex');
29
+ return { raw, hash };
30
+ }
31
+ // server.js lives in vhost/, one level below the real site root - it
32
+ // states its own fixed, known relationship to its sibling directory
33
+ // rather than the agent inferring anything (constraint 2 stays intact:
34
+ // the site root is always explicit, never assumed from the agent's own
35
+ // location).
36
+ export const SERVER_JS = `import { startServer } from '${PACKAGE_NAME}';
37
+ import { join } from 'node:path';
38
+
39
+ await startServer(join(import.meta.dirname, '..'), { tunnel: process.argv.includes('--tunnel') });
40
+ `;
41
+ // The scripts every site's vhost/package.json gets. The CMS owns these:
42
+ // npm run upgrade puts them back as the new version defines them.
43
+ // "start" matters beyond convenience: several PaaS platforms
44
+ // auto-detect and run `npm start` by default for a Node app
45
+ // with no other deploy config - without this, a site pushed
46
+ // straight to one of those (no Dockerfile in the loop) simply
47
+ // wouldn't boot. "tunnel" mirrors the --tunnel flag create-site
48
+ // already tells the operator about in its own next-steps output.
49
+ // "dev" is Node's own --watch-path, not custom fs-watching code
50
+ // - confirmed empirically that it both picks up changes to a
51
+ // plain file under a watched directory that's never imported
52
+ // (theme/*.liquid is only ever read via readFileSync) and sends
53
+ // a real SIGTERM on restart, which server.js's own shutdown
54
+ // handling already listens for. "../theme" because npm run dev
55
+ // runs with cwd vhost/, a sibling of theme/. Deliberately not
56
+ // content/ - that's already read fresh on every request, so
57
+ // watching it would only cause pointless restarts on every
58
+ // content edit. "check" is check-site, the package's own
59
+ // installed CLI bin - runs from vhost/ (site-check/cli.ts's own
60
+ // assumed cwd relationship), same pattern as start/tunnel/dev.
61
+ export const SCAFFOLD_SCRIPTS = {
62
+ start: 'node server.js',
63
+ tunnel: 'node server.js --tunnel',
64
+ dev: 'node --watch-path=../theme server.js',
65
+ check: 'check-site',
66
+ // seed-media is the package's own installed CLI bin, exposed
67
+ // as a script for exactly the same reason check is: there is
68
+ // no package.json or node_modules at the SITE root, only here
69
+ // in vhost/, so the obvious-looking `npx seed-media` run from
70
+ // the site directory resolves nothing locally, goes to the
71
+ // public registry for a package by that name and dies with
72
+ // E404 - while still exiting 0. That is not hypothetical: it
73
+ // is what sent a generated site's images into theme/root/
74
+ // instead of media/. Run it from here as
75
+ // `npm run seed-media -- .. <file>`.
76
+ 'seed-media': 'seed-media',
77
+ // Stops the site from any terminal, not only the one it was
78
+ // started in (stop-site, via vhost/data/server.pid).
79
+ stop: 'stop-site',
80
+ // Copies a running site's content and media into this one
81
+ // (pull-site): `npm run pull`, which asks for the address and token.
82
+ pull: 'pull-site',
83
+ // Sends changes made here since the last pull back to that
84
+ // live site (push-site), refusing to overwrite anything
85
+ // edited there since, and only after typing its address.
86
+ push: 'push-site',
87
+ // Upgrades the CMS itself (upgrade-site): shows what's new, installs
88
+ // it, and updates the files in vhost/ that belong to the CMS.
89
+ upgrade: 'upgrade-site',
90
+ };
91
+ export class ScaffoldError extends Error {
92
+ }
93
+ // A standalone, one-shot CLI operation - no running server exists yet
94
+ // for enqueue()'s serialisation to matter, and this whole function
95
+ // runs synchronously start to finish, so there's no concurrent-write
96
+ // hazard to guard against.
97
+ export function scaffoldSite(targetDir) {
98
+ if (existsSync(targetDir) && readdirSync(targetDir).length > 0) {
99
+ throw new ScaffoldError(`"${targetDir}" already exists and is not empty`);
100
+ }
101
+ mkdirSync(targetDir, { recursive: true });
102
+ // The static theme/content template, copied verbatim except its own
103
+ // "gitignore"/"dockerignore" (no leading dot - a literal .gitignore
104
+ // or .dockerignore inside the template tree would be silently
105
+ // dropped by npm pack's own packlist logic, verified empirically
106
+ // before this was written).
107
+ cpSync(TEMPLATE_ROOT, targetDir, {
108
+ recursive: true,
109
+ filter: (source) => basename(source) !== 'gitignore' && basename(source) !== 'dockerignore',
110
+ });
111
+ cpSync(join(TEMPLATE_ROOT, 'gitignore'), join(targetDir, '.gitignore'));
112
+ cpSync(join(TEMPLATE_ROOT, 'dockerignore'), join(targetDir, '.dockerignore'));
113
+ // content/drafts/ starts empty - not part of the static template
114
+ // (nothing to copy), created directly so a fresh site has somewhere
115
+ // to write its first draft immediately. Nested inside content/, not
116
+ // a sibling of it - see config.ts's own comment for why.
117
+ mkdirSync(join(targetDir, 'content', 'drafts'), { recursive: true });
118
+ // media/ also starts empty, also created directly (no template
119
+ // content to copy - see config.ts's own comment for why this is a
120
+ // sibling of content/theme/vhost, not nested under content/). Not
121
+ // passed to commitPaths below: media is never git-tracked
122
+ // (constraint 2), even at scaffold time - an empty directory has
123
+ // nothing to commit anyway (git doesn't track empty directories), so
124
+ // this omission is a non-issue mechanically, but it's deliberate,
125
+ // not an oversight, should anyone go looking for why it's absent
126
+ // from that list.
127
+ mkdirSync(join(targetDir, 'media'), { recursive: true });
128
+ // vhost/: the site's own serving configuration (package.json,
129
+ // server.js, site.config.json), matching a real vhost's own scope in
130
+ // hosting terms - never a sibling of content/theme.
131
+ const vhostDir = join(targetDir, 'vhost');
132
+ mkdirSync(vhostDir, { recursive: true });
133
+ // A zero-token site fails closed on every write route (401) - not
134
+ // usable for editing at all. Generated fresh per scaffold, printed
135
+ // once, never written to disk - matches every other write-capable
136
+ // identity in this codebase never depending on a value the operator
137
+ // could lose or that leaks into version control.
138
+ const token = generateToken();
139
+ writeFileSync(join(vhostDir, 'site.config.json'), JSON.stringify({
140
+ // Written explicitly, matching server-config.ts's own
141
+ // DEFAULT_PORT, rather than left absent - a real value here
142
+ // is a visible, editable config knob; an absent key is
143
+ // invisible until something else is already using the
144
+ // default port, at which point the failure is a raw
145
+ // EADDRINUSE crash instead of an obvious setting to change.
146
+ port: 3000,
147
+ tokens: [{ hash: token.hash, scopes: ['content', 'theme', 'media'] }],
148
+ }, null, 2));
149
+ const packageName = sanitisePackageName(basename(targetDir));
150
+ writeFileSync(join(vhostDir, 'package.json'), JSON.stringify({
151
+ name: packageName,
152
+ version: '0.0.0',
153
+ private: true,
154
+ type: 'module',
155
+ scripts: SCAFFOLD_SCRIPTS,
156
+ dependencies: {
157
+ // Pinned exact, never a ^range - at v0.x even a minor bump
158
+ // can be breaking (build plan's own word is "pinned").
159
+ [PACKAGE_NAME]: readAgentVersion(),
160
+ },
161
+ }, null, 2));
162
+ writeFileSync(join(vhostDir, 'server.js'), SERVER_JS);
163
+ // runStartupChecks hard-fails not-a-git-repo otherwise - a scaffold
164
+ // without a real git repo cannot boot at all.
165
+ execFileSync('git', ['init', '--quiet'], { cwd: targetDir });
166
+ commitPaths(targetDir, ['theme', 'content', 'vhost', 'AGENTS.md', '.gitignore', '.dockerignore'], 'chore: initial scaffold', CHECKPOINT_AUTHOR);
167
+ return { raw: token.raw };
168
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ import { resolve } from 'node:path';
3
+ import { MintTokenError, mintToken, parseScopes } from "./mint-token.js";
4
+ const targetArg = process.argv[2];
5
+ const scopesFlagIndex = process.argv.indexOf('--scopes');
6
+ const scopesArg = scopesFlagIndex !== -1 ? process.argv[scopesFlagIndex + 1] : undefined;
7
+ if (!targetArg) {
8
+ console.error('Usage: mint-token <site-directory> [--scopes content,theme,media]');
9
+ process.exit(1);
10
+ }
11
+ try {
12
+ const scopes = parseScopes(scopesArg);
13
+ const siteDir = resolve(process.cwd(), targetArg);
14
+ const { raw } = mintToken(siteDir, scopes);
15
+ console.log('New API token (save this now - it will not be shown again):');
16
+ console.log(` ${raw}`);
17
+ console.log('');
18
+ console.log(`Scopes: ${scopes.join(', ')}`);
19
+ }
20
+ catch (error) {
21
+ if (error instanceof MintTokenError) {
22
+ console.error(error.message);
23
+ process.exit(1);
24
+ }
25
+ throw error;
26
+ }
@@ -0,0 +1,7 @@
1
+ import type { Scope } from '../server-config.ts';
2
+ export declare class MintTokenError extends Error {
3
+ }
4
+ export declare function parseScopes(raw: string | undefined): Scope[];
5
+ export declare function mintToken(siteDir: string, scopes: Scope[]): {
6
+ raw: string;
7
+ };
@@ -0,0 +1,49 @@
1
+ import { readFileSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { CHECKPOINT_AUTHOR } from "../services/checkpoint.js";
4
+ import { commitPaths } from "../services/git.js";
5
+ import { generateToken } from "./generate-site.js";
6
+ export class MintTokenError extends Error {
7
+ }
8
+ const VALID_SCOPES = new Set(['content', 'theme', 'media']);
9
+ export function parseScopes(raw) {
10
+ if (!raw) {
11
+ return ['content', 'theme', 'media'];
12
+ }
13
+ const scopes = raw.split(',').map((scope) => scope.trim());
14
+ for (const scope of scopes) {
15
+ if (!VALID_SCOPES.has(scope)) {
16
+ throw new MintTokenError(`Unknown scope "${scope}" - valid scopes are content, theme, media`);
17
+ }
18
+ }
19
+ return scopes;
20
+ }
21
+ // A standalone CLI operation against an operator-supplied site
22
+ // directory, same category as scaffoldSite - not a web request's
23
+ // :path, so sanitisePath's traversal concern doesn't apply here.
24
+ export function mintToken(siteDir, scopes) {
25
+ const configPath = join(siteDir, 'vhost', 'site.config.json');
26
+ let raw;
27
+ try {
28
+ raw = readFileSync(configPath, 'utf-8');
29
+ }
30
+ catch (error) {
31
+ throw new MintTokenError(`Could not read ${configPath} - is "${siteDir}" a real site created by create-site?`, {
32
+ cause: error,
33
+ });
34
+ }
35
+ let config;
36
+ try {
37
+ config = JSON.parse(raw);
38
+ }
39
+ catch (error) {
40
+ throw new MintTokenError(`${configPath} is not valid JSON`, { cause: error });
41
+ }
42
+ const token = generateToken();
43
+ const tokens = Array.isArray(config.tokens) ? config.tokens : [];
44
+ tokens.push({ hash: token.hash, scopes });
45
+ config.tokens = tokens;
46
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
47
+ commitPaths(siteDir, ['vhost/site.config.json'], 'chore: mint a new API token', CHECKPOINT_AUTHOR);
48
+ return { raw: token.raw };
49
+ }