@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
@@ -0,0 +1,268 @@
1
+ #!/usr/bin/env node
2
+ import { resolve } from 'node:path';
3
+ import { loadSiteConfig } from "../config.js";
4
+ import { ask, chooseParts, parseSyncArgs, resolveToken, TOKEN_HELP } from "./cli-helpers.js";
5
+ import { normaliseSiteUrl } from "./prompts.js";
6
+ import { readAgentVersion } from "../routes/capabilities.js";
7
+ import { compareVersions } from "../upgrade/changelog.js";
8
+ import { deployedVersionProblem, describeDeployMethod, detectDeployMethod, runDeploy, waitForLiveVersion } from "./deploy.js";
9
+ import { executePush, preparePush } from "./push-site.js";
10
+ import { SiteSyncError } from "./remote-site.js";
11
+ import { readSyncRecord } from "./sync-record.js";
12
+ const args = parseSyncArgs(process.argv.slice(2));
13
+ const dryRun = args.flags.has('--dry-run');
14
+ // Run from vhost/ (the scaffold's own "push" script), so the site root
15
+ // is one level up - the same relationship check-site relies on.
16
+ const config = loadSiteConfig(resolve(process.cwd(), '..'));
17
+ function fail(message, ...more) {
18
+ console.error(message);
19
+ for (const line of more) {
20
+ console.error(line);
21
+ }
22
+ process.exit(1);
23
+ }
24
+ // Push only ever goes back to the site this copy was pulled from, so
25
+ // with no address given, that's the one. A copy that has never pulled
26
+ // asks, as pull does.
27
+ const record = readSyncRecord(config);
28
+ const given = args.positional[0] ?? record?.siteUrl ?? (await ask('Live site address: '));
29
+ const siteUrl = normaliseSiteUrl(given);
30
+ if (!siteUrl) {
31
+ fail(given.trim() === '' ? 'No address entered.' : `"${given}" is not a web address.`);
32
+ }
33
+ function printContentPlan(plan) {
34
+ const labels = { create: 'new page ', update: 'update ', delete: 'DELETE ' };
35
+ for (const change of plan.changes) {
36
+ console.error(` ${labels[change.action]} content/${change.path}`);
37
+ }
38
+ for (const operation of plan.redirectOperations) {
39
+ const { from, to } = operation.entry;
40
+ const verb = { POST: 'add redirect ', PUT: 'change redirect', DELETE: 'REMOVE redirect' }[operation.method];
41
+ console.error(` ${verb} ${from}${operation.method === 'DELETE' ? '' : ` -> ${to}`}`);
42
+ }
43
+ for (const name of plan.mediaToUpload) {
44
+ console.error(` upload media/${name}`);
45
+ }
46
+ if (plan.settings) {
47
+ console.error(' update content/settings.json (site settings)');
48
+ }
49
+ }
50
+ function printThemePlan(plan) {
51
+ const labels = { create: 'new file ', update: 'update ', delete: 'DELETE ' };
52
+ for (const change of plan.changes) {
53
+ console.error(` ${labels[change.action]} theme/${change.path}`);
54
+ }
55
+ }
56
+ const plural = (count, word) => `${count} ${word}${count === 1 ? '' : 's'}`;
57
+ function contentCount(plan) {
58
+ return plan.changes.length + plan.redirectOperations.length + plan.mediaToUpload.length + (plan.settings ? 1 : 0);
59
+ }
60
+ function reportConflicts(conflicts, folder) {
61
+ for (const conflict of conflicts) {
62
+ const path = conflict.path === 'redirects.json' ? 'content/redirects.json' : `${folder}/${conflict.path}`;
63
+ console.error(` ${path}: ${conflict.reason}`);
64
+ }
65
+ }
66
+ // Narrowed once here, for the functions below.
67
+ const liveUrl = siteUrl;
68
+ const host = new URL(liveUrl).host;
69
+ async function confirmByAddress() {
70
+ const typed = await ask(`Type ${host} to go ahead, or anything else to cancel: `);
71
+ if (typed.trim().toLowerCase() !== host.toLowerCase()) {
72
+ fail('Cancelled. Nothing was pushed.');
73
+ }
74
+ }
75
+ // Step 1 when chosen: put the CMS here onto the live site by deploying,
76
+ // and wait until the live site says it is running it. Content and theme
77
+ // are only sent once it has, so they arrive on a CMS that understands
78
+ // them - and nothing else is sent if it doesn't.
79
+ async function upgradeLiveCms(liveVersion, localVersion) {
80
+ const method = detectDeployMethod(config.siteRoot);
81
+ console.error(`\nStep 1: upgrade the live site's CMS from ${liveVersion} to ${localVersion}, using ${describeDeployMethod(method)}.`);
82
+ console.error(' - The live site restarts, and is unavailable for a moment.');
83
+ console.error(' - Its content and theme are not touched by the upgrade.');
84
+ if (dryRun) {
85
+ return;
86
+ }
87
+ console.error('');
88
+ await confirmByAddress();
89
+ if (method.kind === 'manual') {
90
+ console.error(`\nDeploy the site now, the way you usually deploy to your host. (To have push do it for you next time, put the command in vhost/deploy.json, for example { "command": "git push" }.)`);
91
+ await ask('Press Enter once the deploy has started: ');
92
+ }
93
+ else if (!runDeploy(method, config.siteRoot)) {
94
+ fail('The deploy failed, so nothing else was pushed. Its output is above.');
95
+ }
96
+ console.error(`Waiting for the live site to come back on ${localVersion}...`);
97
+ let lastReported;
98
+ const outcome = await waitForLiveVersion(liveUrl, localVersion, {
99
+ onPoll: (seen) => {
100
+ if (seen !== lastReported) {
101
+ console.error(seen === null ? ' (restarting, not answering yet)' : ` live site on ${seen}`);
102
+ lastReported = seen;
103
+ }
104
+ },
105
+ });
106
+ if (!outcome.ok) {
107
+ fail(`Stopped without pushing anything else: ${outcome.reason}. Check the deploy on your host.`);
108
+ }
109
+ console.error(`The live site is now on ${localVersion}.`);
110
+ }
111
+ try {
112
+ const token = await resolveToken(args, siteUrl);
113
+ const prepare = () => preparePush(config, {
114
+ siteUrl,
115
+ token,
116
+ onWait: (seconds) => console.error(` The live site asked to slow down; waiting ${seconds}s...`),
117
+ });
118
+ console.error(`Comparing this copy with ${siteUrl}...`);
119
+ let prepared = await prepare();
120
+ const localVersion = readAgentVersion();
121
+ const versionOrder = compareVersions(localVersion, prepared.liveVersion);
122
+ if (versionOrder < 0) {
123
+ console.error(`Note: the live site runs a newer CMS (${prepared.liveVersion}) than this copy (${localVersion}). Run npm run upgrade here to match it.`);
124
+ }
125
+ const describe = () => {
126
+ const content = prepared.content.ready ? prepared.content.value : null;
127
+ const theme = prepared.theme.ready ? prepared.theme.value : null;
128
+ return { content, theme, contentChanges: content ? contentCount(content.plan) : 0, themeChanges: theme ? theme.plan.changes.length : 0 };
129
+ };
130
+ const first = describe();
131
+ const firstPush = (first.content?.firstPush ?? false) || (first.theme?.firstPush ?? false);
132
+ if (firstPush) {
133
+ console.error(`\nFirst push from this copy to ${siteUrl}. Until this copy has pulled from the live site, push can't tell your changes apart from changes made on the live site, so it lists everything that differs. Check the list carefully. Nothing is deleted from the live site on a first push.`);
134
+ }
135
+ const parts = await chooseParts(args, 'push', {
136
+ content: first.content
137
+ ? {
138
+ available: true,
139
+ detail: first.content.plan.conflicts.length > 0 ? plural(first.content.plan.conflicts.length, 'conflict') : first.contentChanges > 0 ? plural(first.contentChanges, 'change') : 'no changes',
140
+ checked: first.contentChanges > 0 || first.content.plan.conflicts.length > 0,
141
+ }
142
+ : { available: false, detail: prepared.content.reason, checked: false },
143
+ theme: first.theme
144
+ ? {
145
+ available: true,
146
+ detail: first.theme.plan.conflicts.length > 0 ? plural(first.theme.plan.conflicts.length, 'conflict') : first.themeChanges > 0 ? `${plural(first.themeChanges, 'file')} changed` : 'no changes',
147
+ checked: first.themeChanges > 0 || first.theme.plan.conflicts.length > 0,
148
+ }
149
+ : { available: false, detail: prepared.theme.reason, checked: false },
150
+ cms: versionOrder > 0
151
+ ? { available: true, detail: `live site: ${prepared.liveVersion}, here: ${localVersion}`, checked: true }
152
+ : { available: false, detail: `the live site already runs ${prepared.liveVersion}`, checked: false },
153
+ });
154
+ if (!parts.content && !parts.theme && !parts.cms) {
155
+ console.log('Nothing chosen. Nothing was pushed.');
156
+ process.exit(0);
157
+ }
158
+ if ((parts.content || parts.theme) && prepared.liveSchemaOlder && !parts.cms) {
159
+ fail(`The live site's CMS (${prepared.liveVersion}) uses an older content format than this copy's (${localVersion}), and could reject what it's sent. Push the CMS upgrade with it (npm run push, and tick CMS upgrade).`);
160
+ }
161
+ if (parts.cms) {
162
+ const versionProblem = deployedVersionProblem(config.siteRoot, localVersion);
163
+ if (versionProblem !== null) {
164
+ fail(`Can't push the CMS upgrade, so nothing was pushed: ${versionProblem}`);
165
+ }
166
+ await upgradeLiveCms(prepared.liveVersion, localVersion);
167
+ if (!parts.content && !parts.theme) {
168
+ console.log(dryRun ? '\nDry run: nothing was pushed.' : `Pushed to ${siteUrl}: the CMS upgrade to ${localVersion}.`);
169
+ process.exit(0);
170
+ }
171
+ if (dryRun) {
172
+ console.log('\nDry run: nothing was pushed. (What content and theme would change is worked out once the upgrade is live.)');
173
+ process.exit(0);
174
+ }
175
+ // Planned again against the upgraded site: what it reports can have
176
+ // changed with it (a theme it could not report before, for one).
177
+ console.error(`\nStep 2: your content and theme changes. Comparing again with the upgraded site...`);
178
+ prepared = await prepare();
179
+ }
180
+ const { content, theme, contentChanges, themeChanges } = describe();
181
+ const pushContent = parts.content && content !== null;
182
+ const pushTheme = parts.theme && theme !== null;
183
+ if (parts.theme && theme === null) {
184
+ console.error(`The theme can't be pushed: ${prepared.theme.reason}.`);
185
+ }
186
+ if (!pushContent && !pushTheme) {
187
+ process.exit(parts.cms ? 0 : 1);
188
+ }
189
+ const contentConflicts = pushContent ? content.plan.conflicts : [];
190
+ const themeConflicts = pushTheme ? theme.plan.conflicts : [];
191
+ const mediaMissing = pushContent ? content.plan.mediaMissing : [];
192
+ if (contentConflicts.length > 0 || themeConflicts.length > 0 || mediaMissing.length > 0) {
193
+ console.error('Nothing more was pushed.');
194
+ if (contentConflicts.length > 0 || themeConflicts.length > 0) {
195
+ console.error('\nThese were changed on the live site since your last pull, and pushing would overwrite that work:');
196
+ reportConflicts(themeConflicts, 'theme');
197
+ reportConflicts(contentConflicts, 'content');
198
+ console.error('\nTo keep both: commit your work here, run npm run pull to bring in the live changes, reapply yours on top, then push again.');
199
+ }
200
+ if (mediaMissing.length > 0) {
201
+ console.error('\nThese images are used by pages being pushed, but are missing here and on the live site:');
202
+ for (const name of mediaMissing) {
203
+ console.error(` media/${name}`);
204
+ }
205
+ }
206
+ process.exit(1);
207
+ }
208
+ const total = (pushContent ? contentChanges : 0) + (pushTheme ? themeChanges : 0);
209
+ if (total === 0) {
210
+ console.log('Nothing to push: the live site already has everything changed here since your last pull.');
211
+ process.exit(0);
212
+ }
213
+ console.error(`\nThis will change the LIVE site at ${siteUrl}:\n`);
214
+ if (pushTheme) {
215
+ printThemePlan(theme.plan);
216
+ }
217
+ if (pushContent) {
218
+ printContentPlan(content.plan);
219
+ }
220
+ if (dryRun) {
221
+ console.log('\nDry run: nothing was pushed.');
222
+ process.exit(0);
223
+ }
224
+ const deletes = pushContent ? content.plan.changes.filter((change) => change.action === 'delete').length : 0;
225
+ console.error('\nWARNING: this overwrites the live website, straight away.');
226
+ if (pushTheme) {
227
+ console.error(' - The theme files listed replace the live ones, and every page shows the new theme at once.');
228
+ }
229
+ if (pushContent) {
230
+ console.error(' - Every page listed is published as it is here, replacing what visitors see now.');
231
+ }
232
+ if (deletes > 0) {
233
+ console.error(` - ${plural(deletes, 'page')} ${deletes === 1 ? 'is' : 'are'} deleted from the live site.`);
234
+ }
235
+ if ((pushContent && content.firstPush) || (pushTheme && theme.firstPush)) {
236
+ console.error(' - FIRST PUSH: every difference is listed, including any that came from editors on the live site. Anything listed replaces the live version.');
237
+ }
238
+ else {
239
+ console.error(' - Anything changed on the live site since your last pull is left alone (none is listed above).');
240
+ }
241
+ console.error(' - Earlier versions stay in the live site\'s history, if you need to go back.\n');
242
+ await confirmByAddress();
243
+ const result = await executePush(prepared, { content: pushContent, theme: pushTheme });
244
+ console.log(`Pushed to ${siteUrl}:`);
245
+ if (parts.cms) {
246
+ console.log(` CMS: upgraded to ${localVersion}`);
247
+ }
248
+ if (result.theme) {
249
+ console.log(` theme: ${plural(result.theme.files, 'file')} changed, live now`);
250
+ for (const warning of result.theme.warnings) {
251
+ console.log(` warning: ${warning}`);
252
+ }
253
+ }
254
+ if (result.content) {
255
+ const done = result.content;
256
+ console.log(` content: ${done.created} new, ${done.updated} updated, ${done.deleted} deleted`);
257
+ console.log(` ${plural(done.redirects, 'redirect change')}, ${plural(done.mediaUploaded, 'image')} uploaded${done.settings ? ', site settings updated' : ''}`);
258
+ }
259
+ }
260
+ catch (error) {
261
+ if (error instanceof SiteSyncError) {
262
+ fail(error.message, ...(error.reason === 'unauthorised' ? [TOKEN_HELP] : []));
263
+ }
264
+ if (error instanceof Error && error.message.startsWith('vhost/deploy.json')) {
265
+ fail(error.message);
266
+ }
267
+ throw error;
268
+ }
@@ -0,0 +1,96 @@
1
+ import type { SiteConfig } from '../config.ts';
2
+ import { type RedirectEntry } from '../services/redirects.ts';
3
+ import { type LiveContent } from './live-content.ts';
4
+ import { RemoteSite } from './remote-site.ts';
5
+ import { type ContentRecord } from './sync-record.ts';
6
+ import { type LiveTheme, type ThemePlan } from './theme-sync.ts';
7
+ export type PushAction = 'create' | 'update' | 'delete';
8
+ export interface PushChange {
9
+ path: string;
10
+ action: PushAction;
11
+ }
12
+ export interface PushConflict {
13
+ path: string;
14
+ reason: string;
15
+ }
16
+ export interface RedirectOperation {
17
+ method: 'POST' | 'PUT' | 'DELETE';
18
+ entry: RedirectEntry;
19
+ }
20
+ export interface PushPlan {
21
+ changes: PushChange[];
22
+ conflicts: PushConflict[];
23
+ redirectOperations: RedirectOperation[];
24
+ mediaToUpload: string[];
25
+ mediaMissing: string[];
26
+ settings: boolean;
27
+ }
28
+ export interface LocalContent {
29
+ files: Map<string, Buffer>;
30
+ redirects: RedirectEntry[];
31
+ mediaNames: Set<string>;
32
+ settings: Record<string, unknown> | null;
33
+ }
34
+ export declare function planPush(record: ContentRecord, local: LocalContent, live: LiveContent, liveMedia: Set<string>): PushPlan;
35
+ export interface PreparedContent {
36
+ plan: PushPlan;
37
+ record: ContentRecord;
38
+ firstPush: boolean;
39
+ local: LocalContent;
40
+ live: LiveContent;
41
+ }
42
+ export interface PreparedTheme {
43
+ plan: ThemePlan;
44
+ pulled: Record<string, string>;
45
+ firstPush: boolean;
46
+ local: Map<string, Buffer>;
47
+ live: LiveTheme;
48
+ }
49
+ export type Prepared<T> = {
50
+ ready: true;
51
+ value: T;
52
+ } | {
53
+ ready: false;
54
+ reason: string;
55
+ };
56
+ export interface PreparedPush {
57
+ config: SiteConfig;
58
+ remote: RemoteSite;
59
+ siteUrl: string;
60
+ author: {
61
+ name: string;
62
+ email: string;
63
+ };
64
+ liveVersion: string;
65
+ liveSchemaOlder: boolean;
66
+ content: Prepared<PreparedContent>;
67
+ theme: Prepared<PreparedTheme>;
68
+ }
69
+ export interface PushSiteOptions {
70
+ siteUrl: string;
71
+ token: string;
72
+ fetchImpl?: typeof fetch;
73
+ sleep?: (ms: number) => Promise<void>;
74
+ onWait?: (seconds: number) => void;
75
+ }
76
+ export declare function preparePush(config: SiteConfig, options: PushSiteOptions): Promise<PreparedPush>;
77
+ export interface PushContentResult {
78
+ created: number;
79
+ updated: number;
80
+ deleted: number;
81
+ redirects: number;
82
+ mediaUploaded: number;
83
+ settings: boolean;
84
+ }
85
+ export interface PushSiteResult {
86
+ content?: PushContentResult;
87
+ theme?: {
88
+ files: number;
89
+ warnings: string[];
90
+ };
91
+ }
92
+ export interface PushParts {
93
+ content: boolean;
94
+ theme: boolean;
95
+ }
96
+ export declare function executePush(prepared: PreparedPush, parts: PushParts): Promise<PushSiteResult>;