@jant/core 0.6.16 → 0.7.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 (592) hide show
  1. package/README.md +11 -11
  2. package/bin/commands/assets/upload.js +192 -15
  3. package/bin/commands/export.js +3 -1
  4. package/bin/commands/import-site.js +749 -220
  5. package/bin/commands/reset-password.js +21 -8
  6. package/bin/commands/setup.js +136 -0
  7. package/bin/commands/site/export.js +71 -34
  8. package/bin/commands/site/pull-media.js +2 -6
  9. package/bin/commands/site/snapshot/export.js +19 -61
  10. package/bin/commands/site/snapshot/import.js +23 -24
  11. package/bin/lib/d1-query.js +92 -18
  12. package/bin/lib/hugo-markdown.js +4 -0
  13. package/bin/lib/node-env.js +40 -4
  14. package/bin/lib/site-pull-media.js +21 -28
  15. package/bin/lib/site-selection.js +94 -29
  16. package/bin/lib/site-snapshot.js +338 -3
  17. package/bin/lib/sql-export.js +68 -5
  18. package/bin/lib/wrangler-cli.js +37 -2
  19. package/bin/lib/wrangler-json.js +35 -0
  20. package/bin/lib/zip-archive.js +187 -0
  21. package/dist/{app-C7_4oab9.js → app-ZcaI1kPN.js} +53190 -13529
  22. package/dist/client/.vite/manifest.json +255 -90
  23. package/dist/client/_assets/chunks/confirm-D7GrwsCN.js +45 -0
  24. package/dist/client/_assets/chunks/create-editor-B-m7X7S5.js +261 -0
  25. package/dist/client/_assets/chunks/{native-DpcrFAPh.js → emoji-mart-C4RpRKcU.js} +716 -1
  26. package/dist/client/_assets/chunks/footnote-rail-CmeoVtnU.js +1 -0
  27. package/dist/client/_assets/chunks/heic-to-CuP2Qlsw.js +1 -0
  28. package/dist/client/_assets/chunks/jant-locale-picker-DL6mIOT5.js +69 -0
  29. package/dist/client/_assets/chunks/lit-DW8VmJAT.js +2 -0
  30. package/dist/client/_assets/chunks/mediabunny-DRaCQ1r-.js +31 -0
  31. package/dist/client/_assets/chunks/rolldown-runtime-hePW80VL.js +1 -0
  32. package/dist/client/_assets/chunks/settings-paths-BcdySNx6.js +84 -0
  33. package/dist/client/_assets/chunks/site-header-nav-DzXsFk05.js +1 -0
  34. package/dist/client/_assets/chunks/slug-format-CRXO1AL5.js +1 -0
  35. package/dist/client/_assets/chunks/slugify-373citbs.js +1 -0
  36. package/dist/client/_assets/chunks/sortable-list-BJyd-LXE.js +2 -0
  37. package/dist/client/_assets/chunks/types-D5iF3H1a.js +1 -0
  38. package/dist/client/_assets/chunks/unsafe-html-B5ZEXaxg.js +1 -0
  39. package/dist/client/_assets/chunks/unsafe-svg-BkscJx69.js +2 -0
  40. package/dist/client/_assets/client-DAhoqPdr.css +2 -0
  41. package/dist/client/_assets/client-auth-CAMfTrKW.js +828 -0
  42. package/dist/client/_assets/client-author-DQ8R-8KR.css +2 -0
  43. package/dist/client/_assets/client-compose-D6rExKVK.js +2004 -0
  44. package/dist/client/_assets/client-knSEyUJO.js +295 -0
  45. package/dist/client/_assets/client-manage-sVOkqVbH.js +2170 -0
  46. package/dist/client/_assets/client-settings-CGR_nZ78.js +1112 -0
  47. package/dist/github-sync-Gw4orAHk.js +8398 -0
  48. package/dist/index.js +2 -5
  49. package/dist/node.js +11 -9
  50. package/package.json +15 -5
  51. package/src/__tests__/auth-session-freshness.test.ts +57 -0
  52. package/src/__tests__/basecoat-variants.test.ts +121 -0
  53. package/src/__tests__/bin/assets-upload.test.ts +68 -0
  54. package/src/__tests__/dev-scripts.test.ts +203 -0
  55. package/src/__tests__/export-collection-order.test.ts +276 -0
  56. package/src/__tests__/export-feed-ids.test.ts +184 -0
  57. package/src/__tests__/export-feed-order.test.ts +294 -0
  58. package/src/__tests__/export-hugo-build.test.ts +390 -0
  59. package/src/__tests__/export-import-roundtrip.test.ts +94 -0
  60. package/src/__tests__/export-service.test.ts +842 -28
  61. package/src/__tests__/export-smart-collection.test.ts +329 -0
  62. package/src/__tests__/helpers/component-css.ts +91 -0
  63. package/src/__tests__/helpers/db.ts +33 -0
  64. package/src/__tests__/helpers/export-fixtures.ts +3 -0
  65. package/src/__tests__/helpers/hugo-site.ts +146 -0
  66. package/src/__tests__/import-site-command.test.ts +487 -1
  67. package/src/__tests__/mise-config.test.ts +75 -4
  68. package/src/__tests__/node-dev-tasks.test.ts +264 -0
  69. package/src/__tests__/site-export-canonical-import.test.ts +149 -0
  70. package/src/__tests__/snapshot-canonical-replay.test.ts +179 -0
  71. package/src/__tests__/snapshot-settings.test.ts +97 -0
  72. package/src/__tests__/snapshot-tables.test.ts +219 -0
  73. package/src/__tests__/sql-export.test.ts +173 -0
  74. package/src/__tests__/stylesheet-audience.test.ts +159 -0
  75. package/src/__tests__/stylesheet-breakpoints.test.ts +183 -0
  76. package/src/__tests__/zip-archive.test.ts +106 -0
  77. package/src/app.tsx +86 -22
  78. package/src/auth.ts +18 -1
  79. package/src/client/__tests__/collection-page-actions.test.ts +51 -2
  80. package/src/client/__tests__/compose-bridge-image-upload.test.ts +196 -0
  81. package/src/client/__tests__/compose-bridge-video-poster.test.ts +159 -0
  82. package/src/client/__tests__/compose-bridge.test.ts +272 -0
  83. package/src/client/__tests__/compose-launch.test.ts +62 -1
  84. package/src/client/__tests__/compose-shortcuts.test.ts +38 -9
  85. package/src/client/__tests__/compose-triggers.test.ts +90 -0
  86. package/src/client/__tests__/copy-field.test.ts +162 -0
  87. package/src/client/__tests__/custom-url-menu.test.ts +127 -6
  88. package/src/client/__tests__/image-processor.test.ts +324 -2
  89. package/src/client/__tests__/lazy-entries.test.ts +92 -0
  90. package/src/client/__tests__/media-scroll-hint.test.ts +155 -0
  91. package/src/client/__tests__/runtime-paths.test.ts +84 -0
  92. package/src/client/__tests__/smart-collection-page-actions.test.ts +158 -0
  93. package/src/client/__tests__/toast.test.ts +23 -0
  94. package/src/client/__tests__/video-processor-probe.test.ts +175 -0
  95. package/src/client/__tests__/video-processor.test.ts +181 -0
  96. package/src/client/audio-processor.ts +24 -15
  97. package/src/client/audio-waveform.ts +49 -0
  98. package/src/client/collection-dialog-host.ts +72 -0
  99. package/src/client/collection-navigation.ts +51 -16
  100. package/src/client/collection-page-actions.ts +26 -0
  101. package/src/client/components/__tests__/jant-collection-dialog.test.ts +329 -0
  102. package/src/client/components/__tests__/jant-collection-directory.test.ts +274 -10
  103. package/src/client/components/__tests__/jant-collection-form.test.ts +59 -178
  104. package/src/client/components/__tests__/jant-command-palette.test.ts +28 -0
  105. package/src/client/components/__tests__/jant-compose-dialog-draft-storage.test.ts +340 -0
  106. package/src/client/components/__tests__/jant-compose-dialog.test.ts +1250 -53
  107. package/src/client/components/__tests__/jant-compose-editor.test.ts +544 -40
  108. package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +0 -1
  109. package/src/client/components/__tests__/jant-locale-picker.test.ts +207 -0
  110. package/src/client/components/__tests__/jant-nav-manager.test.ts +234 -15
  111. package/src/client/components/__tests__/jant-post-menu.test.ts +413 -2
  112. package/src/client/components/__tests__/jant-post-picker.test.ts +197 -0
  113. package/src/client/components/__tests__/jant-settings-avatar.test.ts +15 -12
  114. package/src/client/components/__tests__/jant-settings-general.test.ts +561 -172
  115. package/src/client/components/__tests__/jant-settings-language.test.ts +851 -0
  116. package/src/client/components/__tests__/jant-smart-collection-dialog.test.ts +665 -0
  117. package/src/client/components/collection-dialog-types.ts +49 -0
  118. package/src/client/components/collection-manager-types.ts +32 -1
  119. package/src/client/components/collection-types.ts +1 -15
  120. package/src/client/components/compose-types.ts +75 -1
  121. package/src/client/components/jant-collection-dialog.ts +722 -0
  122. package/src/client/components/jant-collection-directory.ts +567 -182
  123. package/src/client/components/jant-collection-form.ts +53 -232
  124. package/src/client/components/jant-command-palette.ts +100 -49
  125. package/src/client/components/jant-compose-dialog.ts +1486 -707
  126. package/src/client/components/jant-compose-editor.ts +673 -336
  127. package/src/client/components/jant-compose-fullscreen.ts +51 -43
  128. package/src/client/components/jant-config-editor.ts +65 -53
  129. package/src/client/components/jant-confirm-dialog.ts +13 -11
  130. package/src/client/components/jant-locale-picker.ts +418 -0
  131. package/src/client/components/jant-media-lightbox.ts +146 -136
  132. package/src/client/components/jant-nav-manager.ts +690 -366
  133. package/src/client/components/jant-post-menu.ts +1086 -282
  134. package/src/client/components/jant-post-picker.ts +324 -0
  135. package/src/client/components/jant-repo-picker-types.ts +6 -1
  136. package/src/client/components/jant-repo-picker.ts +137 -108
  137. package/src/client/components/jant-settings-avatar.ts +31 -27
  138. package/src/client/components/jant-settings-general.ts +622 -543
  139. package/src/client/components/jant-settings-language.ts +1087 -0
  140. package/src/client/components/jant-smart-collection-dialog.ts +1021 -0
  141. package/src/client/components/jant-text-preview.ts +94 -86
  142. package/src/client/components/nav-manager-types.ts +62 -1
  143. package/src/client/components/settings-types.ts +14 -29
  144. package/src/client/components/smart-collection-conditions.ts +285 -0
  145. package/src/client/components/smart-collection-dialog-types.ts +68 -0
  146. package/src/client/compose-bridge.ts +214 -141
  147. package/src/client/compose-launch.ts +76 -2
  148. package/src/client/compose-shortcuts.ts +6 -8
  149. package/src/client/compose-triggers.ts +50 -0
  150. package/src/client/copy-field.ts +108 -0
  151. package/src/client/custom-url-menu.ts +60 -4
  152. package/src/client/icons.ts +14 -0
  153. package/src/client/image-plan.ts +165 -0
  154. package/src/client/image-processor.ts +195 -117
  155. package/src/client/image-worker-client.ts +45 -0
  156. package/src/client/image-worker.ts +135 -0
  157. package/src/client/lazy-entries.ts +95 -0
  158. package/src/client/lazy-slugify.ts +2 -2
  159. package/src/client/media-scroll-hint.ts +43 -2
  160. package/src/client/mediabunny.ts +29 -0
  161. package/src/client/post-picker.ts +41 -0
  162. package/src/client/runtime-paths.ts +74 -1
  163. package/src/client/settings-bridge.ts +4 -10
  164. package/src/client/site-header-nav.js +18 -6
  165. package/src/client/smart-collection-dialog-host.ts +116 -0
  166. package/src/client/smart-collection-page-actions.ts +208 -0
  167. package/src/client/tiptap/__tests__/bubble-menu.test.ts +110 -0
  168. package/src/client/tiptap/__tests__/floating-position.test.ts +136 -1
  169. package/src/client/tiptap/__tests__/inline-image-upload.test.ts +37 -0
  170. package/src/client/tiptap/__tests__/list-editing.test.ts +224 -86
  171. package/src/client/tiptap/__tests__/mark-exit.test.ts +1 -2
  172. package/src/client/tiptap/__tests__/markdown-clipboard.test.ts +26 -0
  173. package/src/client/tiptap/bubble-menu.ts +47 -16
  174. package/src/client/tiptap/extensions.ts +0 -3
  175. package/src/client/tiptap/floating-position.ts +216 -2
  176. package/src/client/tiptap/inline-image-upload.ts +7 -3
  177. package/src/client/tiptap/link-toolbar.ts +22 -9
  178. package/src/client/tiptap/structural-keymap.ts +158 -47
  179. package/src/client/toast.ts +13 -0
  180. package/src/client/types/vite-worker.d.ts +12 -0
  181. package/src/client/upload-with-metadata.ts +3 -13
  182. package/src/client/video-processor.ts +351 -107
  183. package/src/client-auth.ts +14 -31
  184. package/src/client-compose.ts +19 -0
  185. package/src/client-manage.ts +13 -0
  186. package/src/client-settings.ts +14 -0
  187. package/src/client-site.ts +10 -3
  188. package/src/client.ts +4 -0
  189. package/src/db/__tests__/backfill-nav-label-mirrors.test.ts +149 -0
  190. package/src/db/__tests__/d1-query.test.ts +56 -1
  191. package/src/db/__tests__/schema-enum-sources.test.ts +33 -0
  192. package/src/db/__tests__/smart-collection-placement.test.ts +402 -0
  193. package/src/db/backfills/0006_clear_mirrored_page_nav_labels.sql +31 -0
  194. package/src/db/migrations/0032_married_tomas.sql +3 -0
  195. package/src/db/migrations/0033_busy_johnny_storm.sql +198 -0
  196. package/src/db/migrations/0034_cheerful_rumiko_fujikawa.sql +35 -0
  197. package/src/db/migrations/meta/0032_snapshot.json +2582 -0
  198. package/src/db/migrations/meta/0033_snapshot.json +2816 -0
  199. package/src/db/migrations/meta/0034_snapshot.json +2816 -0
  200. package/src/db/migrations/meta/_journal.json +21 -0
  201. package/src/db/migrations/pg/0030_abandoned_nighthawk.sql +3 -0
  202. package/src/db/migrations/pg/0031_strong_ultimatum.sql +128 -0
  203. package/src/db/migrations/pg/0032_furry_multiple_man.sql +6 -0
  204. package/src/db/migrations/pg/meta/0030_snapshot.json +3329 -0
  205. package/src/db/migrations/pg/meta/0031_snapshot.json +3660 -0
  206. package/src/db/migrations/pg/meta/0032_snapshot.json +3660 -0
  207. package/src/db/migrations/pg/meta/_journal.json +21 -0
  208. package/src/db/pg/schema.ts +195 -27
  209. package/src/db/post-visibility.ts +117 -0
  210. package/src/db/schema.ts +222 -27
  211. package/src/i18n/__tests__/detect.test.ts +26 -81
  212. package/src/i18n/__tests__/supported-locales.test.ts +83 -0
  213. package/src/i18n/context.tsx +12 -2
  214. package/src/i18n/detect.ts +7 -66
  215. package/src/i18n/locales/glossary.zh-Hans.yml +44 -4
  216. package/src/i18n/locales/glossary.zh-Hant.yml +44 -4
  217. package/src/i18n/locales/public/en.po +490 -158
  218. package/src/i18n/locales/public/en.ts +1 -1
  219. package/src/i18n/locales/public/zh-Hans.po +487 -155
  220. package/src/i18n/locales/public/zh-Hans.ts +1 -1
  221. package/src/i18n/locales/public/zh-Hant.po +487 -155
  222. package/src/i18n/locales/public/zh-Hant.ts +1 -1
  223. package/src/i18n/locales/settings/en.po +555 -73
  224. package/src/i18n/locales/settings/en.ts +1 -1
  225. package/src/i18n/locales/settings/zh-Hans.po +601 -119
  226. package/src/i18n/locales/settings/zh-Hans.ts +1 -1
  227. package/src/i18n/locales/settings/zh-Hant.po +614 -132
  228. package/src/i18n/locales/settings/zh-Hant.ts +1 -1
  229. package/src/i18n/locales.ts +94 -0
  230. package/src/i18n/middleware.ts +12 -2
  231. package/src/i18n/supported-locales.ts +80 -0
  232. package/src/lib/__tests__/api-settings.test.ts +11 -2
  233. package/src/lib/__tests__/asset-path.test.ts +92 -0
  234. package/src/lib/__tests__/deferred.test.ts +90 -0
  235. package/src/lib/__tests__/discover-ping.test.ts +89 -0
  236. package/src/lib/__tests__/discover.test.ts +410 -0
  237. package/src/lib/__tests__/feed-policy.test.ts +26 -0
  238. package/src/lib/__tests__/feed.test.ts +1526 -15
  239. package/src/lib/__tests__/filter-dimensions.test.ts +341 -0
  240. package/src/lib/__tests__/github-sync-repo-name.test.ts +40 -0
  241. package/src/lib/__tests__/hosted-signin.test.ts +1 -1
  242. package/src/lib/__tests__/http-cache.test.ts +162 -0
  243. package/src/lib/__tests__/image.test.ts +27 -1
  244. package/src/lib/__tests__/jant-docs-dev.test.ts +21 -0
  245. package/src/lib/__tests__/jant-docs.test.ts +23 -0
  246. package/src/lib/__tests__/lang-detect.test.ts +231 -0
  247. package/src/lib/__tests__/markdown-to-tiptap.test.ts +105 -0
  248. package/src/lib/__tests__/markdown.test.ts +10 -0
  249. package/src/lib/__tests__/media-helpers.test.ts +81 -0
  250. package/src/lib/__tests__/navigation.test.ts +135 -1
  251. package/src/lib/__tests__/resolve-config.test.ts +131 -3
  252. package/src/lib/__tests__/schemas.test.ts +69 -5
  253. package/src/lib/__tests__/site-unavailable-page.test.ts +25 -0
  254. package/src/lib/__tests__/slug.test.ts +70 -0
  255. package/src/lib/__tests__/slugify.test.ts +85 -0
  256. package/src/lib/__tests__/storage.test.ts +32 -0
  257. package/src/lib/__tests__/thread-fold.test.ts +113 -0
  258. package/src/lib/__tests__/timeline.test.ts +87 -0
  259. package/src/lib/__tests__/tiptap-to-markdown.test.ts +172 -4
  260. package/src/lib/__tests__/upload-sideload.test.ts +14 -0
  261. package/src/lib/__tests__/url-fetch.test.ts +1 -2
  262. package/src/lib/__tests__/url.test.ts +78 -72
  263. package/src/lib/__tests__/view-language.test.ts +413 -0
  264. package/src/lib/__tests__/worker-response-cache.test.ts +90 -0
  265. package/src/lib/address.ts +57 -0
  266. package/src/lib/api-posts.ts +12 -0
  267. package/src/lib/asset-path.ts +98 -1
  268. package/src/lib/build-env.ts +16 -0
  269. package/src/lib/collection-groups.ts +5 -1
  270. package/src/lib/collection-paths.ts +3 -10
  271. package/src/lib/constants.ts +28 -3
  272. package/src/lib/copy-field.ts +36 -0
  273. package/src/lib/crypto.ts +40 -0
  274. package/src/lib/csp-builder.ts +2 -1
  275. package/src/lib/deferred.ts +79 -0
  276. package/src/lib/discover-ping.ts +141 -0
  277. package/src/lib/discover.ts +439 -0
  278. package/src/lib/env.ts +134 -0
  279. package/src/lib/errors.ts +33 -0
  280. package/src/lib/feed-policy.ts +236 -2
  281. package/src/lib/feed.ts +926 -73
  282. package/src/lib/filter-dimensions.ts +1349 -0
  283. package/src/lib/github-sync-repo-name.ts +45 -0
  284. package/src/lib/github-sync-site-config.ts +3 -0
  285. package/src/lib/hosted-signin.ts +1 -1
  286. package/src/lib/http-cache.ts +148 -0
  287. package/src/lib/hugo-markdown.ts +62 -0
  288. package/src/lib/icons.ts +9 -2
  289. package/src/lib/ids.ts +3 -0
  290. package/src/lib/image.ts +17 -4
  291. package/src/lib/jant-docs.ts +42 -0
  292. package/src/lib/lang-detect.ts +308 -0
  293. package/src/lib/markdown-manager.ts +392 -2
  294. package/src/lib/media-helpers.ts +13 -1
  295. package/src/lib/navigation.ts +78 -11
  296. package/src/lib/pagination.ts +28 -24
  297. package/src/lib/per-language-surfaces.ts +67 -0
  298. package/src/lib/post-body-html.ts +11 -4
  299. package/src/lib/post-meta.ts +19 -0
  300. package/src/lib/render.tsx +55 -7
  301. package/src/lib/resolve-config.ts +111 -6
  302. package/src/lib/schemas.ts +251 -15
  303. package/src/lib/site-header-fragment.tsx +2 -1
  304. package/src/lib/site-unavailable-page.ts +47 -0
  305. package/src/lib/slug.ts +51 -7
  306. package/src/lib/slugify.ts +113 -0
  307. package/src/lib/startup-config.ts +4 -6
  308. package/src/lib/storage.ts +7 -0
  309. package/src/lib/summary.ts +50 -0
  310. package/src/lib/thread-fold.ts +161 -0
  311. package/src/lib/time.ts +32 -0
  312. package/src/lib/timeline.ts +43 -2
  313. package/src/lib/tiptap-render.ts +1 -2
  314. package/src/lib/tiptap-to-markdown.ts +13 -7
  315. package/src/lib/upload.ts +16 -13
  316. package/src/lib/url.ts +159 -86
  317. package/src/lib/version.ts +10 -8
  318. package/src/lib/view-language.ts +436 -0
  319. package/src/lib/view.ts +53 -25
  320. package/src/lib/viewer-context.ts +62 -0
  321. package/src/middleware/__tests__/auth.test.ts +70 -2
  322. package/src/middleware/__tests__/cache-control.test.ts +71 -3
  323. package/src/middleware/__tests__/onboarding.test.ts +76 -5
  324. package/src/middleware/__tests__/secure-headers.test.ts +1 -1
  325. package/src/middleware/__tests__/session.test.ts +181 -33
  326. package/src/middleware/auth.ts +50 -31
  327. package/src/middleware/cache-control.ts +75 -10
  328. package/src/middleware/config.ts +6 -6
  329. package/src/middleware/error-handler.ts +12 -0
  330. package/src/middleware/onboarding.ts +32 -10
  331. package/src/middleware/secure-headers.ts +1 -1
  332. package/src/middleware/session.ts +96 -8
  333. package/src/node/__tests__/cli-migrate.test.ts +21 -19
  334. package/src/node/__tests__/cli-reset-password.test.ts +81 -8
  335. package/src/node/__tests__/cli-runtime-target.test.ts +48 -1
  336. package/src/node/__tests__/cli-setup.test.ts +163 -0
  337. package/src/node/__tests__/cli-site-selection.test.ts +128 -38
  338. package/src/node/__tests__/cli-site-snapshot.test.ts +67 -0
  339. package/src/node/__tests__/cli-snapshot-meta.test.ts +20 -0
  340. package/src/node/__tests__/runtime.test.ts +75 -0
  341. package/src/node/index.ts +3 -0
  342. package/src/node/request-handler.ts +3 -1
  343. package/src/preset.css +76 -22
  344. package/src/routes/__tests__/feed-validator.test.ts +156 -0
  345. package/src/routes/api/__tests__/collections.test.ts +114 -0
  346. package/src/routes/api/__tests__/discover.test.ts +222 -0
  347. package/src/routes/api/__tests__/nav-items.test.ts +212 -2
  348. package/src/routes/api/__tests__/post-translations.test.ts +829 -0
  349. package/src/routes/api/__tests__/posts.test.ts +24 -0
  350. package/src/routes/api/__tests__/settings.test.ts +24 -3
  351. package/src/routes/api/__tests__/telegram.test.ts +6 -5
  352. package/src/routes/api/__tests__/upload.test.ts +34 -0
  353. package/src/routes/api/collections.ts +49 -6
  354. package/src/routes/api/custom-urls.ts +0 -1
  355. package/src/routes/api/discover.ts +95 -0
  356. package/src/routes/api/export.ts +6 -3
  357. package/src/routes/api/internal/__tests__/sites.test.ts +42 -2
  358. package/src/routes/api/internal/sites.ts +0 -1
  359. package/src/routes/api/nav-items.ts +55 -8
  360. package/src/routes/api/posts.ts +161 -0
  361. package/src/routes/api/public/__tests__/archive.test.ts +111 -0
  362. package/src/routes/api/public/archive.ts +77 -81
  363. package/src/routes/api/public/posts.ts +44 -7
  364. package/src/routes/api/smart-collections.ts +132 -0
  365. package/src/routes/api/telegram.ts +2 -40
  366. package/src/routes/api/upload.ts +10 -3
  367. package/src/routes/api/uploads.ts +1 -2
  368. package/src/routes/auth/__tests__/setup-auth.test.ts +137 -0
  369. package/src/routes/auth/__tests__/setup-discover.test.ts +204 -0
  370. package/src/routes/auth/__tests__/setup-flow.test.ts +270 -0
  371. package/src/routes/auth/__tests__/setup-page.test.tsx +329 -0
  372. package/src/routes/auth/__tests__/setup-steps.test.ts +310 -0
  373. package/src/routes/auth/__tests__/setup.test.ts +278 -26
  374. package/src/routes/auth/__tests__/signin.test.ts +135 -0
  375. package/src/routes/auth/setup.tsx +934 -186
  376. package/src/routes/auth/signin.tsx +10 -8
  377. package/src/routes/compose.tsx +9 -0
  378. package/src/routes/dash/__tests__/custom-urls-archive.test.ts +44 -0
  379. package/src/routes/dash/__tests__/custom-urls-page.test.ts +49 -0
  380. package/src/routes/dash/__tests__/delete-account.test.ts +106 -0
  381. package/src/routes/dash/__tests__/github-sync-app.test.ts +365 -0
  382. package/src/routes/dash/__tests__/settings-discover.test.ts +213 -0
  383. package/src/routes/dash/custom-urls.tsx +1 -37
  384. package/src/routes/dash/settings.tsx +714 -111
  385. package/src/routes/discover-announce.ts +96 -0
  386. package/src/routes/feed/__tests__/feed.test.ts +165 -2
  387. package/src/routes/feed/feed.ts +69 -40
  388. package/src/routes/feed/sitemap.ts +95 -11
  389. package/src/routes/pages/__tests__/archive-indexing.test.ts +200 -0
  390. package/src/routes/pages/__tests__/archive-params.test.ts +370 -6
  391. package/src/routes/pages/__tests__/collection-routing.test.ts +16 -0
  392. package/src/routes/pages/__tests__/collections-feed-links.test.ts +72 -0
  393. package/src/routes/pages/__tests__/collections.test.ts +9 -3
  394. package/src/routes/pages/__tests__/custom-archive-url.test.ts +119 -0
  395. package/src/routes/pages/__tests__/feed-autodiscovery.test.ts +148 -0
  396. package/src/routes/pages/__tests__/language-routing.test.ts +679 -0
  397. package/src/routes/pages/__tests__/post-page-round-trips.test.ts +211 -0
  398. package/src/routes/pages/__tests__/post-preview-partial.test.ts +96 -0
  399. package/src/routes/pages/__tests__/smart-collection.test.ts +298 -0
  400. package/src/routes/pages/__tests__/subscribe.test.ts +248 -0
  401. package/src/routes/pages/__tests__/thread-order.test.ts +179 -0
  402. package/src/routes/pages/archive.tsx +487 -379
  403. package/src/routes/pages/collection.tsx +94 -96
  404. package/src/routes/pages/collections.tsx +38 -50
  405. package/src/routes/pages/featured.tsx +43 -18
  406. package/src/routes/pages/home.tsx +25 -6
  407. package/src/routes/pages/language.tsx +113 -0
  408. package/src/routes/pages/latest.tsx +26 -16
  409. package/src/routes/pages/new.tsx +3 -0
  410. package/src/routes/pages/page.tsx +228 -45
  411. package/src/routes/pages/partials.tsx +41 -0
  412. package/src/routes/pages/search.tsx +22 -5
  413. package/src/routes/pages/smart-collection.tsx +340 -0
  414. package/src/routes/pages/subscribe.tsx +182 -0
  415. package/src/runtime/__tests__/index.test.ts +19 -0
  416. package/src/runtime/__tests__/node.test.ts +129 -4
  417. package/src/runtime/__tests__/readiness.test.ts +23 -0
  418. package/src/runtime/cloudflare.ts +21 -2
  419. package/src/runtime/index.ts +27 -1
  420. package/src/runtime/node.ts +67 -1
  421. package/src/runtime/readiness.ts +15 -0
  422. package/src/runtime/site.ts +95 -24
  423. package/src/services/__tests__/auth.test.ts +116 -7
  424. package/src/services/__tests__/bootstrap-setup-instance.test.ts +230 -0
  425. package/src/services/__tests__/collection.test.ts +108 -4
  426. package/src/services/__tests__/custom-url.test.ts +34 -0
  427. package/src/services/__tests__/github-app-installations.test.ts +153 -0
  428. package/src/services/__tests__/github-sync-push.test.ts +46 -0
  429. package/src/services/__tests__/language.test.ts +495 -0
  430. package/src/services/__tests__/media.test.ts +31 -0
  431. package/src/services/__tests__/navigation.test.ts +213 -10
  432. package/src/services/__tests__/path.test.ts +236 -0
  433. package/src/services/__tests__/post-language.test.ts +830 -0
  434. package/src/services/__tests__/post-timeline.test.ts +127 -0
  435. package/src/services/__tests__/post.test.ts +120 -9
  436. package/src/services/__tests__/settings.test.ts +228 -17
  437. package/src/services/__tests__/site-admin.test.ts +5 -5
  438. package/src/services/__tests__/smart-collection.test.ts +651 -0
  439. package/src/services/about-page.ts +22 -1
  440. package/src/services/auth.ts +51 -49
  441. package/src/services/bootstrap.ts +408 -39
  442. package/src/services/collection-directory-position.ts +76 -0
  443. package/src/services/collection.ts +331 -46
  444. package/src/services/custom-url.ts +29 -24
  445. package/src/services/export-theme/assets/client-site.css +1 -1
  446. package/src/services/export-theme/assets/client-site.js +123 -123
  447. package/src/services/export-theme/layouts/_default/alias.html +27 -1
  448. package/src/services/export-theme/layouts/_default/list.html +2 -63
  449. package/src/services/export-theme/layouts/_default/rss.xml +103 -38
  450. package/src/services/export-theme/layouts/collections/list.html +3 -3
  451. package/src/services/export-theme/layouts/featured/list.html +1 -4
  452. package/src/services/export-theme/layouts/index.html +40 -26
  453. package/src/services/export-theme/layouts/partials/collection-members.html +100 -0
  454. package/src/services/export-theme/layouts/partials/collection-threads.html +33 -0
  455. package/src/services/export-theme/layouts/partials/featured-members.html +35 -0
  456. package/src/services/export-theme/layouts/partials/featured-thread.html +1 -1
  457. package/src/services/export-theme/layouts/partials/feed-post-content.xml +105 -2
  458. package/src/services/export-theme/layouts/partials/footer.html +1 -1
  459. package/src/services/export-theme/layouts/partials/head.html +1 -1
  460. package/src/services/export-theme/layouts/partials/header.html +5 -3
  461. package/src/services/export-theme/layouts/partials/jant-data.html +23 -0
  462. package/src/services/export-theme/layouts/partials/latest-members.html +48 -0
  463. package/src/services/export-theme/layouts/partials/media-gallery.html +35 -21
  464. package/src/services/export-theme/layouts/partials/pagination.html +14 -13
  465. package/src/services/export-theme/layouts/partials/post-card.html +8 -4
  466. package/src/services/export-theme/layouts/partials/reply.html +2 -1
  467. package/src/services/export-theme/layouts/partials/smart-collection-members.html +129 -0
  468. package/src/services/export-theme/layouts/partials/thread-preview.html +41 -31
  469. package/src/services/export-theme/layouts/post/list.html +1 -1
  470. package/src/services/export-theme/layouts/smart_collection/list.html +24 -0
  471. package/src/services/export-theme/styles/main.css +13 -2
  472. package/src/services/export-theme/theme.toml +1 -1
  473. package/src/services/export.ts +880 -72
  474. package/src/services/github-app-installations.ts +171 -4
  475. package/src/services/github-sync.ts +69 -15
  476. package/src/services/index.ts +34 -3
  477. package/src/services/language.ts +412 -0
  478. package/src/services/mcp.ts +22 -2
  479. package/src/services/media.ts +22 -7
  480. package/src/services/navigation.ts +429 -67
  481. package/src/services/path.ts +254 -6
  482. package/src/services/post.ts +1234 -190
  483. package/src/services/search.ts +39 -2
  484. package/src/services/settings.ts +238 -43
  485. package/src/services/site-admin.ts +27 -14
  486. package/src/services/site.ts +160 -32
  487. package/src/services/smart-collection.ts +636 -0
  488. package/src/services/telegram.ts +20 -22
  489. package/src/services/upload-session.ts +1 -3
  490. package/src/style-author.css +27 -0
  491. package/src/styles/components-author.css +1622 -0
  492. package/src/styles/components.css +24 -1571
  493. package/src/styles/site-media.css +16 -2
  494. package/src/styles/tokens.css +82 -12
  495. package/src/styles/ui-author.css +7602 -0
  496. package/src/styles/ui.css +1090 -7216
  497. package/src/types/app-context.ts +23 -0
  498. package/src/types/bindings.ts +8 -0
  499. package/src/types/config.ts +94 -15
  500. package/src/types/constants.ts +188 -16
  501. package/src/types/entities.ts +87 -1
  502. package/src/types/operations.ts +74 -2
  503. package/src/types/props.ts +157 -31
  504. package/src/types/views.ts +55 -1
  505. package/src/ui/__tests__/font-themes.test.ts +52 -12
  506. package/src/ui/compose/ComposeDialog.tsx +201 -7
  507. package/src/ui/compose/ComposePrompt.tsx +1 -5
  508. package/src/ui/compose/__tests__/ComposeDialog.test.tsx +125 -0
  509. package/src/ui/dash/appearance/AdvancedContent.tsx +2 -2
  510. package/src/ui/dash/appearance/CodeInjectionContent.tsx +12 -16
  511. package/src/ui/dash/appearance/NavigationContent.tsx +117 -16
  512. package/src/ui/dash/appearance/__tests__/NavigationContent.test.tsx +33 -11
  513. package/src/ui/dash/appearance/__tests__/system-nav-descriptions.test.ts +44 -0
  514. package/src/ui/dash/appearance/system-nav-descriptions.ts +75 -0
  515. package/src/ui/dash/settings/AccountMenuContent.tsx +2 -2
  516. package/src/ui/dash/settings/ApiTokensContent.tsx +2 -1
  517. package/src/ui/dash/settings/ConfigEditorContent.tsx +35 -16
  518. package/src/ui/dash/settings/GeneralContent.tsx +277 -113
  519. package/src/ui/dash/settings/LanguageContent.tsx +409 -0
  520. package/src/ui/dash/settings/SettingsRootContent.tsx +19 -1
  521. package/src/ui/dash/settings/__tests__/GeneralContent.test.tsx +324 -4
  522. package/src/ui/dash/settings/__tests__/LanguageContent.test.tsx +144 -0
  523. package/src/ui/dash/settings/__tests__/discover-copy.test.ts +66 -0
  524. package/src/ui/dash/settings/discover-copy.ts +116 -0
  525. package/src/ui/feed/LinkCard.tsx +55 -45
  526. package/src/ui/feed/NoteCard.tsx +9 -4
  527. package/src/ui/feed/PostStatusBadges.tsx +90 -43
  528. package/src/ui/feed/QuoteCard.tsx +55 -30
  529. package/src/ui/feed/ThreadPreview.tsx +6 -2
  530. package/src/ui/feed/__tests__/thread-preview.test.ts +13 -16
  531. package/src/ui/feed/__tests__/timeline-cards.test.ts +314 -49
  532. package/src/ui/feed/thread-preview-state.ts +20 -6
  533. package/src/ui/font-themes.ts +39 -29
  534. package/src/ui/layouts/BaseLayout.tsx +155 -10
  535. package/src/ui/layouts/SiteLayout.tsx +192 -34
  536. package/src/ui/layouts/__tests__/BaseLayout.test.tsx +196 -17
  537. package/src/ui/layouts/__tests__/SiteLayout.test.tsx +109 -85
  538. package/src/ui/pages/ArchivePage.tsx +282 -289
  539. package/src/ui/pages/CollectionPage.tsx +86 -76
  540. package/src/ui/pages/CollectionsPage.tsx +5 -0
  541. package/src/ui/pages/ComposePage.tsx +8 -0
  542. package/src/ui/pages/PostPage.tsx +47 -0
  543. package/src/ui/pages/SearchPage.tsx +3 -3
  544. package/src/ui/pages/SmartCollectionPage.tsx +434 -0
  545. package/src/ui/pages/SubscribePage.tsx +236 -0
  546. package/src/ui/pages/__tests__/ArchivePage.test.tsx +65 -9
  547. package/src/ui/pages/__tests__/collections-directory-rows.test.tsx +92 -0
  548. package/src/ui/pages/__tests__/collections-view-links.test.tsx +81 -0
  549. package/src/ui/shared/CollectionDirectory.tsx +178 -3
  550. package/src/ui/shared/CollectionsManager.tsx +70 -17
  551. package/src/ui/shared/CopyField.tsx +127 -0
  552. package/src/ui/shared/EmptyPostContent.tsx +23 -0
  553. package/src/ui/shared/MediaGallery.tsx +125 -46
  554. package/src/ui/shared/Pagination.tsx +20 -5
  555. package/src/ui/shared/PostFooter.tsx +20 -11
  556. package/src/ui/shared/__tests__/archive-labels.test.ts +100 -0
  557. package/src/ui/shared/__tests__/media-gallery.test.ts +210 -2
  558. package/src/ui/shared/__tests__/navigation-labels.test.ts +16 -22
  559. package/src/ui/shared/__tests__/pagination.test.ts +38 -24
  560. package/src/ui/shared/__tests__/post-footer.test.ts +32 -0
  561. package/src/ui/shared/archive-labels.ts +98 -0
  562. package/src/ui/shared/collection-management-labels.ts +94 -16
  563. package/src/ui/shared/navigation-labels.ts +45 -71
  564. package/src/ui/shared/post-article-attributes.ts +2 -0
  565. package/src/ui/shared/smart-collection-labels.ts +343 -0
  566. package/dist/app-C75Zc8dD.js +0 -6
  567. package/dist/client/_assets/chunks/heic-to-DUUaO23q.js +0 -1
  568. package/dist/client/_assets/chunks/module-DcsAZQZ_.js +0 -716
  569. package/dist/client/_assets/chunks/url-CU9pEbk5.js +0 -1
  570. package/dist/client/_assets/client-Bq0vre8Y.js +0 -296
  571. package/dist/client/_assets/client-D3VZyG4L.css +0 -2
  572. package/dist/client/_assets/client-auth-F2rb9WfF.js +0 -5571
  573. package/dist/env-BPYViDJJ.js +0 -281
  574. package/dist/export-Dl5KCiEs.js +0 -5562
  575. package/dist/github-api-BNF35Lkx.js +0 -176
  576. package/dist/github-app-DdiD-bC4.js +0 -275
  577. package/dist/github-sync-Bqg62IvV.js +0 -4
  578. package/dist/github-sync-HdK2EgvJ.js +0 -431
  579. package/dist/url-CbLAtlZe.js +0 -900
  580. package/src/client/__tests__/collection-created-notice.test.ts +0 -36
  581. package/src/client/__tests__/collection-form-bridge.test.ts +0 -216
  582. package/src/client/__tests__/post-form-bridge.test.ts +0 -82
  583. package/src/client/collection-created-notice.ts +0 -66
  584. package/src/client/collection-form-bridge.ts +0 -191
  585. package/src/client/components/__tests__/jant-post-form.test.ts +0 -193
  586. package/src/client/components/jant-post-form.ts +0 -422
  587. package/src/client/components/post-form-template.ts +0 -352
  588. package/src/client/components/post-form-types.ts +0 -96
  589. package/src/client/media-metadata.ts +0 -247
  590. package/src/client/post-form-bridge.ts +0 -193
  591. package/src/client/tiptap/exitable-marks.ts +0 -73
  592. package/src/ui/pages/CollectionEditorPage.tsx +0 -175
@@ -7,7 +7,7 @@ import {
7
7
  bootstrapCliRuntime,
8
8
  getCliRuntimeLabel,
9
9
  } from "../lib/runtime-target.js";
10
- import { resolveCliSite } from "../lib/site-selection.js";
10
+ import { parseCliSiteSelector, resolveCliSite } from "../lib/site-selection.js";
11
11
 
12
12
  export async function run(argv) {
13
13
  const { values } = parseArgs({
@@ -29,7 +29,9 @@ export async function run(argv) {
29
29
  });
30
30
 
31
31
  if (values.help) {
32
- console.log("Usage: jant reset-password [--local | --remote | --node]");
32
+ console.log(
33
+ "Usage: jant reset-password [--local | --remote | --node] [--site <key|id> | --host <host> | --url <url>]",
34
+ );
33
35
  console.log("");
34
36
  console.log("Generate a password reset token (expires in 15 minutes).");
35
37
  console.log("");
@@ -37,7 +39,7 @@ export async function run(argv) {
37
39
  console.log(" --local Force local D1 instead of DATABASE_URL");
38
40
  console.log(" --remote Run against remote D1 database (default: local)");
39
41
  console.log(" --node Force Node runtime even if DATABASE_URL is unset");
40
- console.log(" --site Target site id");
42
+ console.log(" --site Target site key or id");
41
43
  console.log(" --host Target site host");
42
44
  console.log(" --url Target site URL");
43
45
  console.log(" --path-prefix Path prefix used with --host");
@@ -53,10 +55,20 @@ export async function run(argv) {
53
55
  "If DATABASE_URL or DATA_DIR is then set and no runtime flag is passed,",
54
56
  );
55
57
  console.log("this command uses the Node database runtime.");
58
+ console.log("");
59
+ console.log(
60
+ "With SITE_RESOLUTION_MODE=host-based, pass one of --site, --host, or --url.",
61
+ );
56
62
  process.exit(0);
57
63
  }
58
64
 
59
65
  const { runtime } = bootstrapCliRuntime(values);
66
+ const siteFlags = {
67
+ host: values.host,
68
+ pathPrefix: values["path-prefix"],
69
+ site: values.site,
70
+ url: values.url,
71
+ };
60
72
 
61
73
  const token = randomBytes(32).toString("hex");
62
74
  const hash = createHash("sha256").update(token).digest("hex");
@@ -65,11 +77,15 @@ export async function run(argv) {
65
77
  const timestamp = Math.floor(Date.now() / 1000);
66
78
 
67
79
  if (runtime === "node") {
80
+ const siteSelector = parseCliSiteSelector(siteFlags);
68
81
  const { createNodeCliRuntime } = await loadNodeRuntime();
69
82
  const nodeDatabase = await openNodeDatabase(process.env);
70
83
 
71
84
  try {
72
- const nodeRuntime = await createNodeCliRuntime(nodeDatabase.bindings);
85
+ const nodeRuntime = await createNodeCliRuntime(
86
+ nodeDatabase.bindings,
87
+ siteSelector,
88
+ );
73
89
  await nodeRuntime.services.settings.set("PASSWORD_RESET_TOKEN", value);
74
90
  } finally {
75
91
  await nodeDatabase.close();
@@ -95,10 +111,7 @@ export async function run(argv) {
95
111
  },
96
112
  {
97
113
  env: process.env,
98
- host: values.host,
99
- pathPrefix: values["path-prefix"],
100
- site: values.site,
101
- url: values.url,
114
+ ...siteFlags,
102
115
  },
103
116
  );
104
117
 
@@ -0,0 +1,136 @@
1
+ import { parseArgs } from "node:util";
2
+ import { loadNodeRuntime } from "../lib/load-node-runtime.js";
3
+ import { openNodeDatabase } from "../lib/node-database.js";
4
+ import { bootstrapCliRuntime } from "../lib/runtime-target.js";
5
+
6
+ function printHelp() {
7
+ console.log(
8
+ "Usage: jant setup --email <address> --password-stdin [--site-name <name>] [--language <tag>] [--time-zone <zone>] [--site-id <id>]",
9
+ );
10
+ console.log("");
11
+ console.log(
12
+ "Set up a self-hosted install without the browser: the owner account, the site, and onboarding.",
13
+ );
14
+ console.log("Does the same as the two /setup screens.");
15
+ console.log("");
16
+ console.log("Options:");
17
+ console.log(" --email The owner's sign-in address");
18
+ console.log(
19
+ " --password-stdin Read the owner's password from standard input",
20
+ );
21
+ console.log(
22
+ " --site-name The site's name (default: the built-in fallback name)",
23
+ );
24
+ console.log(
25
+ " --language Language the site publishes in, as a BCP 47 tag (default: en)",
26
+ );
27
+ console.log(" --time-zone The site's IANA time zone (default: UTC)");
28
+ console.log(
29
+ " --site-id Create the site with this id instead of a random one",
30
+ );
31
+ console.log(
32
+ " --node Force Node runtime even if DATABASE_URL is unset",
33
+ );
34
+ console.log("");
35
+ console.log("Safe to run on every start. An install that has finished setup");
36
+ console.log(
37
+ "is left unchanged, so a password changed since then is not reset.",
38
+ );
39
+ console.log("");
40
+ console.log("Example:");
41
+ console.log(
42
+ ` printf '%s' "$OWNER_PASSWORD" | jant setup --email owner@example.com --password-stdin`,
43
+ );
44
+ console.log("");
45
+ console.log(
46
+ "Node runtime and SITE_RESOLUTION_MODE=single-site only. `.env.node` next to",
47
+ );
48
+ console.log("your project (or in packages/core/) is auto-loaded.");
49
+ }
50
+
51
+ /**
52
+ * Read a secret piped to the command, the way `docker login --password-stdin`
53
+ * does: everything on the stream, minus the one line break `echo` adds.
54
+ *
55
+ * @param stream - Where the secret arrives
56
+ * @returns The secret
57
+ */
58
+ async function readPasswordFromStdin(stream) {
59
+ if (stream.isTTY) {
60
+ throw new Error(
61
+ "--password-stdin reads the password from a pipe. Example: printf '%s' \"$OWNER_PASSWORD\" | jant setup --email <address> --password-stdin",
62
+ );
63
+ }
64
+
65
+ const chunks = [];
66
+ for await (const chunk of stream) {
67
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
68
+ }
69
+ return Buffer.concat(chunks)
70
+ .toString("utf8")
71
+ .replace(/\r?\n$/, "");
72
+ }
73
+
74
+ const OUTCOME_MESSAGES = {
75
+ created: (siteId) => `Set up site ${siteId}.`,
76
+ resumed: (siteId) =>
77
+ `Finished setting up site ${siteId}, which an earlier run had started.`,
78
+ "already-set-up": (siteId) =>
79
+ `Site ${siteId} was already set up. Nothing changed.`,
80
+ };
81
+
82
+ export async function run(argv, io = { stdin: process.stdin }) {
83
+ const { values } = parseArgs({
84
+ args: argv,
85
+ options: {
86
+ email: { type: "string" },
87
+ help: { type: "boolean", short: "h" },
88
+ language: { type: "string" },
89
+ node: { type: "boolean", default: false },
90
+ "password-stdin": { type: "boolean", default: false },
91
+ "site-id": { type: "string" },
92
+ "site-name": { type: "string" },
93
+ "time-zone": { type: "string" },
94
+ },
95
+ });
96
+
97
+ if (values.help) {
98
+ printHelp();
99
+ process.exit(0);
100
+ }
101
+
102
+ if (!values.email) {
103
+ throw new Error("Pass the owner's sign-in address with --email.");
104
+ }
105
+ if (!values["password-stdin"]) {
106
+ throw new Error(
107
+ "Pass --password-stdin and pipe the owner's password in. The password is never read from an argument, where it would show up in the process list.",
108
+ );
109
+ }
110
+
111
+ const { runtime } = bootstrapCliRuntime(values);
112
+ if (runtime !== "node") {
113
+ throw new Error(
114
+ "jant setup runs against a Node database. Set DATABASE_URL or DATA_DIR, or pass --node.",
115
+ );
116
+ }
117
+
118
+ const password = await readPasswordFromStdin(io.stdin);
119
+ const { setUpNodeInstance } = await loadNodeRuntime();
120
+ const nodeDatabase = await openNodeDatabase(process.env);
121
+
122
+ try {
123
+ const result = await setUpNodeInstance(nodeDatabase.bindings, {
124
+ email: values.email,
125
+ password,
126
+ siteId: values["site-id"],
127
+ siteName: values["site-name"],
128
+ siteLanguage: values.language,
129
+ timeZone: values["time-zone"],
130
+ });
131
+ console.log(OUTCOME_MESSAGES[result.outcome](result.siteId));
132
+ return result;
133
+ } finally {
134
+ await nodeDatabase.close();
135
+ }
136
+ }
@@ -1,14 +1,26 @@
1
- import { mkdirSync, readdirSync, writeFileSync } from "node:fs";
2
- import { dirname, resolve } from "node:path";
1
+ import { createWriteStream, mkdirSync, readdirSync } from "node:fs";
2
+ import { mkdir, mkdtemp, rename, rm } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { dirname, join, resolve } from "node:path";
5
+ import { Readable } from "node:stream";
6
+ import { pipeline } from "node:stream/promises";
3
7
  import { parseArgs } from "node:util";
4
- import { unzipSync } from "fflate";
5
8
  import {
6
9
  CLI_API_TOKEN_ENV_VAR,
7
10
  getCliApiToken,
8
11
  } from "../../lib/cli-api-token.js";
9
- import { pullSiteExportZipBytes } from "../../lib/site-pull-media.js";
12
+ import { pullSiteExportDirectory } from "../../lib/site-pull-media.js";
13
+ import { extractZipFile, writeDirectoryToZip } from "../../lib/zip-archive.js";
10
14
 
11
- async function exportRemoteSite(url, token) {
15
+ /**
16
+ * Stream the site's export archive to a file. The site streams it too, and a
17
+ * site that bundles its media answers with gigabytes.
18
+ *
19
+ * @param {string} url - Site URL
20
+ * @param {string} token - API token
21
+ * @param {string} destination - File to write
22
+ */
23
+ async function downloadSiteArchive(url, token, destination) {
12
24
  const response = await fetch(`${url.replace(/\/$/, "")}/api/export/hugo`, {
13
25
  method: "POST",
14
26
  headers: {
@@ -16,12 +28,16 @@ async function exportRemoteSite(url, token) {
16
28
  },
17
29
  });
18
30
 
19
- if (!response.ok) {
31
+ if (!response.ok || !response.body) {
20
32
  const text = await response.text();
21
33
  throw new Error(`HTTP ${response.status}: ${text}`);
22
34
  }
23
35
 
24
- return new Uint8Array(await response.arrayBuffer());
36
+ await mkdir(dirname(destination), { recursive: true });
37
+ await pipeline(
38
+ Readable.fromWeb(response.body),
39
+ createWriteStream(destination),
40
+ );
25
41
  }
26
42
 
27
43
  function describeProgressUrl(value) {
@@ -161,22 +177,8 @@ export async function run(argv) {
161
177
  process.exit(1);
162
178
  }
163
179
 
164
- console.log(`Exporting site from ${url}...`);
165
-
166
- const zipBytes = await exportRemoteSite(url, token);
167
- let zip = zipBytes;
168
- let pullStats = null;
169
-
170
- if (pullMedia) {
171
- console.log("Preparing pull-media export ZIP...");
172
- const pulled = await pullSiteExportZipBytes(zip, {
173
- assetLoader: null,
174
- logger: logPullProgress,
175
- });
176
- zip = pulled.zipBytes;
177
- pullStats = pulled.stats;
178
- }
179
-
180
+ // Fail before the download, not after it: pulling a real site's media
181
+ // takes minutes.
180
182
  if (outputDirectory) {
181
183
  let existingEntries = [];
182
184
  try {
@@ -194,20 +196,55 @@ export async function run(argv) {
194
196
  );
195
197
  process.exit(1);
196
198
  }
199
+ }
197
200
 
198
- console.log(`Writing export directory ${values.directory}...`);
199
- const files = unzipSync(zip);
200
- for (const [relativePath, bytes] of Object.entries(files)) {
201
- const fullPath = resolve(outputDirectory, relativePath);
202
- mkdirSync(dirname(fullPath), { recursive: true });
203
- writeFileSync(fullPath, Buffer.from(bytes));
204
- }
205
- console.log(`Exported site from ${url} to ${values.directory}`);
206
- console.log(`Preview with: cd ${values.directory} && hugo serve`);
207
- } else {
201
+ console.log(`Exporting site from ${url}...`);
202
+
203
+ // Nothing to add: the site's archive is the export.
204
+ if (!pullMedia && !outputDirectory) {
208
205
  console.log(`Writing ${values.output}...`);
209
- writeFileSync(output, Buffer.from(zip));
206
+ const partial = `${output}.partial`;
207
+ try {
208
+ await downloadSiteArchive(url, token, partial);
209
+ await rename(partial, output);
210
+ } catch (error) {
211
+ await rm(partial, { force: true });
212
+ throw error;
213
+ }
210
214
  console.log(`Exported site from ${url} to ${values.output}`);
215
+ return;
216
+ }
217
+
218
+ // Unpacked to disk and media downloaded next to it, so the export never
219
+ // holds more than one media file in memory.
220
+ const tempDir = await mkdtemp(join(tmpdir(), "jant-site-export-"));
221
+ const workDir = outputDirectory ?? join(tempDir, "site");
222
+ let pullStats = null;
223
+
224
+ try {
225
+ const archivePath = join(tempDir, "site.zip");
226
+ await downloadSiteArchive(url, token, archivePath);
227
+ await extractZipFile(archivePath, workDir);
228
+ await rm(archivePath, { force: true });
229
+
230
+ if (pullMedia) {
231
+ console.log("Pulling media...");
232
+ pullStats = await pullSiteExportDirectory(workDir, {
233
+ assetLoader: null,
234
+ logger: logPullProgress,
235
+ });
236
+ }
237
+
238
+ if (outputDirectory) {
239
+ console.log(`Exported site from ${url} to ${values.directory}`);
240
+ console.log(`Preview with: cd ${values.directory} && hugo serve`);
241
+ } else {
242
+ console.log(`Writing ${values.output}...`);
243
+ await writeDirectoryToZip(workDir, output);
244
+ console.log(`Exported site from ${url} to ${values.output}`);
245
+ }
246
+ } finally {
247
+ await rm(tempDir, { recursive: true, force: true });
211
248
  }
212
249
 
213
250
  if (pullStats) {
@@ -1,10 +1,9 @@
1
- import { readFileSync, writeFileSync } from "node:fs";
2
1
  import { stat } from "node:fs/promises";
3
2
  import { resolve } from "node:path";
4
3
  import { parseArgs } from "node:util";
5
4
  import {
6
5
  pullSiteExportDirectory,
7
- pullSiteExportZipBytes,
6
+ pullSiteExportZipFile,
8
7
  } from "../../lib/site-pull-media.js";
9
8
 
10
9
  function describeProgressUrl(value) {
@@ -102,13 +101,10 @@ export async function run(argv) {
102
101
  }
103
102
 
104
103
  const outputPath = resolve(process.cwd(), values.output || values.path);
105
- const inputBytes = new Uint8Array(readFileSync(inputPath));
106
104
  console.log(`Pulling media in ${values.path}...`);
107
- const { zipBytes, stats } = await pullSiteExportZipBytes(inputBytes, {
105
+ const stats = await pullSiteExportZipFile(inputPath, outputPath, {
108
106
  logger: logPullProgress,
109
107
  });
110
- console.log(`Writing ${values.output || values.path}...`);
111
- writeFileSync(outputPath, Buffer.from(zipBytes));
112
108
  console.log(
113
109
  `Pulled media in ${values.path} -> ${values.output || values.path}`,
114
110
  );
@@ -1,17 +1,12 @@
1
- import { existsSync } from "node:fs";
2
- import {
3
- mkdir,
4
- mkdtemp,
5
- readFile,
6
- readdir,
7
- rm,
8
- writeFile,
9
- } from "node:fs/promises";
1
+ import { createWriteStream, existsSync } from "node:fs";
2
+ import { Readable } from "node:stream";
3
+ import { pipeline } from "node:stream/promises";
4
+ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
10
5
  import { tmpdir } from "node:os";
11
- import { dirname, join, relative, resolve } from "node:path";
6
+ import { dirname, join, resolve } from "node:path";
12
7
  import { parseArgs } from "node:util";
13
- import { zipSync } from "fflate";
14
- import { queryD1 } from "../../../lib/d1-query.js";
8
+ import { writeDirectoryToZip } from "../../../lib/zip-archive.js";
9
+ import { D1_DUMP_PAGE_SIZE, queryD1 } from "../../../lib/d1-query.js";
15
10
  import { loadNodeRuntime } from "../../../lib/load-node-runtime.js";
16
11
  import { openNodeDatabase } from "../../../lib/node-database.js";
17
12
  import {
@@ -23,6 +18,7 @@ import {
23
18
  buildSnapshotStorageQuery,
24
19
  collectSnapshotObjects,
25
20
  getSnapshotSelectSql,
21
+ orderSnapshotPostRows,
26
22
  SNAPSHOT_TABLES,
27
23
  snapshotObjectPath,
28
24
  } from "../../../lib/site-snapshot.js";
@@ -38,49 +34,6 @@ function isZipPath(filePath) {
38
34
  return filePath.toLowerCase().endsWith(".zip");
39
35
  }
40
36
 
41
- async function readStorageBody(body) {
42
- const reader = body.getReader();
43
- const chunks = [];
44
- let totalLength = 0;
45
-
46
- for (;;) {
47
- const { done, value } = await reader.read();
48
- if (done) break;
49
- chunks.push(value);
50
- totalLength += value.length;
51
- }
52
-
53
- const bytes = new Uint8Array(totalLength);
54
- let offset = 0;
55
- for (const chunk of chunks) {
56
- bytes.set(chunk, offset);
57
- offset += chunk.length;
58
- }
59
-
60
- return bytes;
61
- }
62
-
63
- async function readDirectoryEntries(rootDir) {
64
- const entries = {};
65
-
66
- async function walk(dir) {
67
- const items = await readdir(dir, { withFileTypes: true });
68
- for (const item of items) {
69
- const fullPath = join(dir, item.name);
70
- if (item.isDirectory()) {
71
- await walk(fullPath);
72
- continue;
73
- }
74
-
75
- const relativePath = relative(rootDir, fullPath).replace(/\\/g, "/");
76
- entries[relativePath] = new Uint8Array(await readFile(fullPath));
77
- }
78
- }
79
-
80
- await walk(rootDir);
81
- return entries;
82
- }
83
-
84
37
  async function assertWritableOutput(outputPath, force) {
85
38
  if (!existsSync(outputPath)) {
86
39
  return;
@@ -123,7 +76,12 @@ async function createNodeExportContext() {
123
76
  }
124
77
 
125
78
  await mkdir(dirname(filePath), { recursive: true });
126
- await writeFile(filePath, await readStorageBody(object.body));
79
+ // Straight to disk: a snapshot holds every media file, and collecting
80
+ // each one in memory first ran a 3 GB export to a 2 GB peak.
81
+ await pipeline(
82
+ Readable.fromWeb(object.body),
83
+ createWriteStream(filePath),
84
+ );
127
85
  },
128
86
  };
129
87
  }
@@ -215,7 +173,7 @@ export async function run(argv) {
215
173
  console.log(
216
174
  " --output, -o Output directory or .zip file (default: jant-site-snapshot)",
217
175
  );
218
- console.log(" --site Target site id");
176
+ console.log(" --site Target site key or id");
219
177
  console.log(" --host Target site host");
220
178
  console.log(" --url Target site URL");
221
179
  console.log(" --path-prefix Path prefix used with --host");
@@ -295,6 +253,9 @@ export async function run(argv) {
295
253
  {
296
254
  dialect: context.dialect,
297
255
  source: runtime,
256
+ // D1 answers through Wrangler's buffered output; read it in pages.
257
+ pageSize: runtime === "node" ? undefined : D1_DUMP_PAGE_SIZE,
258
+ orderRowsByTable: { post: orderSnapshotPostRows },
298
259
  tables: SNAPSHOT_TABLES,
299
260
  selectSqlByTable: Object.fromEntries(
300
261
  SNAPSHOT_TABLES.map((tableName) => [
@@ -344,10 +305,7 @@ export async function run(argv) {
344
305
 
345
306
  if (shouldZip) {
346
307
  await mkdir(dirname(outputPath), { recursive: true });
347
- const zipped = zipSync(await readDirectoryEntries(scratchDir), {
348
- level: 6,
349
- });
350
- await writeFile(outputPath, zipped);
308
+ await writeDirectoryToZip(scratchDir, outputPath);
351
309
  if (process.env.SNAPSHOT_SUPPRESS_SUCCESS_LOG !== "true") {
352
310
  console.log(
353
311
  `Exported ${getCliRuntimeLabel(runtime)} snapshot to ${values.output}`,
@@ -1,16 +1,10 @@
1
- import { existsSync } from "node:fs";
2
- import {
3
- mkdir,
4
- mkdtemp,
5
- readFile,
6
- rm,
7
- stat,
8
- writeFile,
9
- } from "node:fs/promises";
1
+ import { createReadStream, existsSync } from "node:fs";
2
+ import { Readable } from "node:stream";
3
+ import { mkdtemp, readFile, rm, stat } from "node:fs/promises";
10
4
  import { tmpdir } from "node:os";
11
- import { dirname, join, resolve } from "node:path";
5
+ import { join, resolve } from "node:path";
12
6
  import { parseArgs } from "node:util";
13
- import { unzipSync } from "fflate";
7
+ import { extractZipFile } from "../../../lib/zip-archive.js";
14
8
  import { executeD1, queryD1 } from "../../../lib/d1-query.js";
15
9
  import { loadNodeRuntime } from "../../../lib/load-node-runtime.js";
16
10
  import { openNodeDatabase } from "../../../lib/node-database.js";
@@ -18,6 +12,8 @@ import { deleteR2Object, uploadR2Object } from "../../../lib/r2-query.js";
18
12
  import {
19
13
  assertSnapshotDialectMatches,
20
14
  assertSnapshotMeta,
15
+ buildMediaProviderSql,
16
+ DEFER_FOREIGN_KEYS_SQL,
21
17
  buildReplaceSql,
22
18
  buildSnapshotStorageQuery,
23
19
  collectSnapshotObjects,
@@ -58,11 +54,13 @@ async function createNodeImportContext() {
58
54
  // the current site, which (a) is redundant with the bin-level resolveCliSite
59
55
  // call below and (b) prints a generic "/setup first" error when the
60
56
  // snapshot's own error path is more informative.
61
- const { createStorageDriver } = await loadNodeRuntime();
57
+ const { createStorageDriver, getConfiguredStorageDriver } =
58
+ await loadNodeRuntime();
62
59
  const storage = createStorageDriver(nodeDatabase.bindings);
63
60
 
64
61
  return {
65
62
  dialect: nodeDatabase.database.dialect,
63
+ storageProvider: getConfiguredStorageDriver(nodeDatabase.bindings),
66
64
  async close() {
67
65
  await nodeDatabase.close();
68
66
  },
@@ -77,8 +75,7 @@ async function createNodeImportContext() {
77
75
  throw new Error("Snapshot import requires configured storage.");
78
76
  }
79
77
 
80
- const bytes = new Uint8Array(await readFile(filePath));
81
- await storage.put(key, bytes, {
78
+ await storage.put(key, Readable.toWeb(createReadStream(filePath)), {
82
79
  contentType: contentType || undefined,
83
80
  });
84
81
  },
@@ -96,6 +93,8 @@ function createD1ImportContext(runtime, values) {
96
93
 
97
94
  return {
98
95
  dialect: "sqlite",
96
+ // Objects go up through the wrangler R2 binding below.
97
+ storageProvider: "r2",
99
98
  async close() {},
100
99
  async query(sql) {
101
100
  return queryD1(sql, runtime, wranglerOptions);
@@ -141,14 +140,7 @@ async function materializeSnapshotInput(inputPath) {
141
140
  }
142
141
 
143
142
  const outputDir = await mkdtemp(join(tmpdir(), "jant-site-snapshot-import-"));
144
- const bytes = new Uint8Array(await readFile(inputPath));
145
- const files = unzipSync(bytes);
146
-
147
- for (const [relativePath, data] of Object.entries(files)) {
148
- const absolutePath = join(outputDir, relativePath);
149
- await mkdir(dirname(absolutePath), { recursive: true });
150
- await writeFile(absolutePath, data);
151
- }
143
+ await extractZipFile(inputPath, outputDir);
152
144
 
153
145
  return {
154
146
  cleanup: async () => {
@@ -204,7 +196,7 @@ export async function run(argv) {
204
196
  console.log(
205
197
  " --replace Replace the current content scope before importing",
206
198
  );
207
- console.log(" --site Target site id");
199
+ console.log(" --site Target site key or id");
208
200
  console.log(" --host Target site host");
209
201
  console.log(" --url Target site URL");
210
202
  console.log(" --path-prefix Path prefix used with --host");
@@ -402,7 +394,14 @@ export async function run(argv) {
402
394
  );
403
395
  }
404
396
 
405
- await context.execute(`${buildReplaceSql(targetSite.id)}\n${dbSql}`);
397
+ await context.execute(
398
+ [
399
+ ...(context.dialect === "postgres" ? [] : [DEFER_FOREIGN_KEYS_SQL]),
400
+ buildReplaceSql(targetSite.id),
401
+ dbSql,
402
+ buildMediaProviderSql(targetSite.id, context.storageProvider),
403
+ ].join("\n"),
404
+ );
406
405
 
407
406
  const keysToDelete = [...currentKeys].filter(
408
407
  (key) => !snapshotKeys.has(key),