@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.
- package/README.md +11 -11
- package/bin/commands/assets/upload.js +192 -15
- package/bin/commands/export.js +3 -1
- package/bin/commands/import-site.js +749 -220
- package/bin/commands/reset-password.js +21 -8
- package/bin/commands/setup.js +136 -0
- package/bin/commands/site/export.js +71 -34
- package/bin/commands/site/pull-media.js +2 -6
- package/bin/commands/site/snapshot/export.js +19 -61
- package/bin/commands/site/snapshot/import.js +23 -24
- package/bin/lib/d1-query.js +92 -18
- package/bin/lib/hugo-markdown.js +4 -0
- package/bin/lib/node-env.js +40 -4
- package/bin/lib/site-pull-media.js +21 -28
- package/bin/lib/site-selection.js +94 -29
- package/bin/lib/site-snapshot.js +338 -3
- package/bin/lib/sql-export.js +68 -5
- package/bin/lib/wrangler-cli.js +37 -2
- package/bin/lib/wrangler-json.js +35 -0
- package/bin/lib/zip-archive.js +187 -0
- package/dist/{app-C7_4oab9.js → app-ZcaI1kPN.js} +53190 -13529
- package/dist/client/.vite/manifest.json +255 -90
- package/dist/client/_assets/chunks/confirm-D7GrwsCN.js +45 -0
- package/dist/client/_assets/chunks/create-editor-B-m7X7S5.js +261 -0
- package/dist/client/_assets/chunks/{native-DpcrFAPh.js → emoji-mart-C4RpRKcU.js} +716 -1
- package/dist/client/_assets/chunks/footnote-rail-CmeoVtnU.js +1 -0
- package/dist/client/_assets/chunks/heic-to-CuP2Qlsw.js +1 -0
- package/dist/client/_assets/chunks/jant-locale-picker-DL6mIOT5.js +69 -0
- package/dist/client/_assets/chunks/lit-DW8VmJAT.js +2 -0
- package/dist/client/_assets/chunks/mediabunny-DRaCQ1r-.js +31 -0
- package/dist/client/_assets/chunks/rolldown-runtime-hePW80VL.js +1 -0
- package/dist/client/_assets/chunks/settings-paths-BcdySNx6.js +84 -0
- package/dist/client/_assets/chunks/site-header-nav-DzXsFk05.js +1 -0
- package/dist/client/_assets/chunks/slug-format-CRXO1AL5.js +1 -0
- package/dist/client/_assets/chunks/slugify-373citbs.js +1 -0
- package/dist/client/_assets/chunks/sortable-list-BJyd-LXE.js +2 -0
- package/dist/client/_assets/chunks/types-D5iF3H1a.js +1 -0
- package/dist/client/_assets/chunks/unsafe-html-B5ZEXaxg.js +1 -0
- package/dist/client/_assets/chunks/unsafe-svg-BkscJx69.js +2 -0
- package/dist/client/_assets/client-DAhoqPdr.css +2 -0
- package/dist/client/_assets/client-auth-CAMfTrKW.js +828 -0
- package/dist/client/_assets/client-author-DQ8R-8KR.css +2 -0
- package/dist/client/_assets/client-compose-D6rExKVK.js +2004 -0
- package/dist/client/_assets/client-knSEyUJO.js +295 -0
- package/dist/client/_assets/client-manage-sVOkqVbH.js +2170 -0
- package/dist/client/_assets/client-settings-CGR_nZ78.js +1112 -0
- package/dist/github-sync-Gw4orAHk.js +8398 -0
- package/dist/index.js +2 -5
- package/dist/node.js +11 -9
- package/package.json +15 -5
- package/src/__tests__/auth-session-freshness.test.ts +57 -0
- package/src/__tests__/basecoat-variants.test.ts +121 -0
- package/src/__tests__/bin/assets-upload.test.ts +68 -0
- package/src/__tests__/dev-scripts.test.ts +203 -0
- package/src/__tests__/export-collection-order.test.ts +276 -0
- package/src/__tests__/export-feed-ids.test.ts +184 -0
- package/src/__tests__/export-feed-order.test.ts +294 -0
- package/src/__tests__/export-hugo-build.test.ts +390 -0
- package/src/__tests__/export-import-roundtrip.test.ts +94 -0
- package/src/__tests__/export-service.test.ts +842 -28
- package/src/__tests__/export-smart-collection.test.ts +329 -0
- package/src/__tests__/helpers/component-css.ts +91 -0
- package/src/__tests__/helpers/db.ts +33 -0
- package/src/__tests__/helpers/export-fixtures.ts +3 -0
- package/src/__tests__/helpers/hugo-site.ts +146 -0
- package/src/__tests__/import-site-command.test.ts +487 -1
- package/src/__tests__/mise-config.test.ts +75 -4
- package/src/__tests__/node-dev-tasks.test.ts +264 -0
- package/src/__tests__/site-export-canonical-import.test.ts +149 -0
- package/src/__tests__/snapshot-canonical-replay.test.ts +179 -0
- package/src/__tests__/snapshot-settings.test.ts +97 -0
- package/src/__tests__/snapshot-tables.test.ts +219 -0
- package/src/__tests__/sql-export.test.ts +173 -0
- package/src/__tests__/stylesheet-audience.test.ts +159 -0
- package/src/__tests__/stylesheet-breakpoints.test.ts +183 -0
- package/src/__tests__/zip-archive.test.ts +106 -0
- package/src/app.tsx +86 -22
- package/src/auth.ts +18 -1
- package/src/client/__tests__/collection-page-actions.test.ts +51 -2
- package/src/client/__tests__/compose-bridge-image-upload.test.ts +196 -0
- package/src/client/__tests__/compose-bridge-video-poster.test.ts +159 -0
- package/src/client/__tests__/compose-bridge.test.ts +272 -0
- package/src/client/__tests__/compose-launch.test.ts +62 -1
- package/src/client/__tests__/compose-shortcuts.test.ts +38 -9
- package/src/client/__tests__/compose-triggers.test.ts +90 -0
- package/src/client/__tests__/copy-field.test.ts +162 -0
- package/src/client/__tests__/custom-url-menu.test.ts +127 -6
- package/src/client/__tests__/image-processor.test.ts +324 -2
- package/src/client/__tests__/lazy-entries.test.ts +92 -0
- package/src/client/__tests__/media-scroll-hint.test.ts +155 -0
- package/src/client/__tests__/runtime-paths.test.ts +84 -0
- package/src/client/__tests__/smart-collection-page-actions.test.ts +158 -0
- package/src/client/__tests__/toast.test.ts +23 -0
- package/src/client/__tests__/video-processor-probe.test.ts +175 -0
- package/src/client/__tests__/video-processor.test.ts +181 -0
- package/src/client/audio-processor.ts +24 -15
- package/src/client/audio-waveform.ts +49 -0
- package/src/client/collection-dialog-host.ts +72 -0
- package/src/client/collection-navigation.ts +51 -16
- package/src/client/collection-page-actions.ts +26 -0
- package/src/client/components/__tests__/jant-collection-dialog.test.ts +329 -0
- package/src/client/components/__tests__/jant-collection-directory.test.ts +274 -10
- package/src/client/components/__tests__/jant-collection-form.test.ts +59 -178
- package/src/client/components/__tests__/jant-command-palette.test.ts +28 -0
- package/src/client/components/__tests__/jant-compose-dialog-draft-storage.test.ts +340 -0
- package/src/client/components/__tests__/jant-compose-dialog.test.ts +1250 -53
- package/src/client/components/__tests__/jant-compose-editor.test.ts +544 -40
- package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +0 -1
- package/src/client/components/__tests__/jant-locale-picker.test.ts +207 -0
- package/src/client/components/__tests__/jant-nav-manager.test.ts +234 -15
- package/src/client/components/__tests__/jant-post-menu.test.ts +413 -2
- package/src/client/components/__tests__/jant-post-picker.test.ts +197 -0
- package/src/client/components/__tests__/jant-settings-avatar.test.ts +15 -12
- package/src/client/components/__tests__/jant-settings-general.test.ts +561 -172
- package/src/client/components/__tests__/jant-settings-language.test.ts +851 -0
- package/src/client/components/__tests__/jant-smart-collection-dialog.test.ts +665 -0
- package/src/client/components/collection-dialog-types.ts +49 -0
- package/src/client/components/collection-manager-types.ts +32 -1
- package/src/client/components/collection-types.ts +1 -15
- package/src/client/components/compose-types.ts +75 -1
- package/src/client/components/jant-collection-dialog.ts +722 -0
- package/src/client/components/jant-collection-directory.ts +567 -182
- package/src/client/components/jant-collection-form.ts +53 -232
- package/src/client/components/jant-command-palette.ts +100 -49
- package/src/client/components/jant-compose-dialog.ts +1486 -707
- package/src/client/components/jant-compose-editor.ts +673 -336
- package/src/client/components/jant-compose-fullscreen.ts +51 -43
- package/src/client/components/jant-config-editor.ts +65 -53
- package/src/client/components/jant-confirm-dialog.ts +13 -11
- package/src/client/components/jant-locale-picker.ts +418 -0
- package/src/client/components/jant-media-lightbox.ts +146 -136
- package/src/client/components/jant-nav-manager.ts +690 -366
- package/src/client/components/jant-post-menu.ts +1086 -282
- package/src/client/components/jant-post-picker.ts +324 -0
- package/src/client/components/jant-repo-picker-types.ts +6 -1
- package/src/client/components/jant-repo-picker.ts +137 -108
- package/src/client/components/jant-settings-avatar.ts +31 -27
- package/src/client/components/jant-settings-general.ts +622 -543
- package/src/client/components/jant-settings-language.ts +1087 -0
- package/src/client/components/jant-smart-collection-dialog.ts +1021 -0
- package/src/client/components/jant-text-preview.ts +94 -86
- package/src/client/components/nav-manager-types.ts +62 -1
- package/src/client/components/settings-types.ts +14 -29
- package/src/client/components/smart-collection-conditions.ts +285 -0
- package/src/client/components/smart-collection-dialog-types.ts +68 -0
- package/src/client/compose-bridge.ts +214 -141
- package/src/client/compose-launch.ts +76 -2
- package/src/client/compose-shortcuts.ts +6 -8
- package/src/client/compose-triggers.ts +50 -0
- package/src/client/copy-field.ts +108 -0
- package/src/client/custom-url-menu.ts +60 -4
- package/src/client/icons.ts +14 -0
- package/src/client/image-plan.ts +165 -0
- package/src/client/image-processor.ts +195 -117
- package/src/client/image-worker-client.ts +45 -0
- package/src/client/image-worker.ts +135 -0
- package/src/client/lazy-entries.ts +95 -0
- package/src/client/lazy-slugify.ts +2 -2
- package/src/client/media-scroll-hint.ts +43 -2
- package/src/client/mediabunny.ts +29 -0
- package/src/client/post-picker.ts +41 -0
- package/src/client/runtime-paths.ts +74 -1
- package/src/client/settings-bridge.ts +4 -10
- package/src/client/site-header-nav.js +18 -6
- package/src/client/smart-collection-dialog-host.ts +116 -0
- package/src/client/smart-collection-page-actions.ts +208 -0
- package/src/client/tiptap/__tests__/bubble-menu.test.ts +110 -0
- package/src/client/tiptap/__tests__/floating-position.test.ts +136 -1
- package/src/client/tiptap/__tests__/inline-image-upload.test.ts +37 -0
- package/src/client/tiptap/__tests__/list-editing.test.ts +224 -86
- package/src/client/tiptap/__tests__/mark-exit.test.ts +1 -2
- package/src/client/tiptap/__tests__/markdown-clipboard.test.ts +26 -0
- package/src/client/tiptap/bubble-menu.ts +47 -16
- package/src/client/tiptap/extensions.ts +0 -3
- package/src/client/tiptap/floating-position.ts +216 -2
- package/src/client/tiptap/inline-image-upload.ts +7 -3
- package/src/client/tiptap/link-toolbar.ts +22 -9
- package/src/client/tiptap/structural-keymap.ts +158 -47
- package/src/client/toast.ts +13 -0
- package/src/client/types/vite-worker.d.ts +12 -0
- package/src/client/upload-with-metadata.ts +3 -13
- package/src/client/video-processor.ts +351 -107
- package/src/client-auth.ts +14 -31
- package/src/client-compose.ts +19 -0
- package/src/client-manage.ts +13 -0
- package/src/client-settings.ts +14 -0
- package/src/client-site.ts +10 -3
- package/src/client.ts +4 -0
- package/src/db/__tests__/backfill-nav-label-mirrors.test.ts +149 -0
- package/src/db/__tests__/d1-query.test.ts +56 -1
- package/src/db/__tests__/schema-enum-sources.test.ts +33 -0
- package/src/db/__tests__/smart-collection-placement.test.ts +402 -0
- package/src/db/backfills/0006_clear_mirrored_page_nav_labels.sql +31 -0
- package/src/db/migrations/0032_married_tomas.sql +3 -0
- package/src/db/migrations/0033_busy_johnny_storm.sql +198 -0
- package/src/db/migrations/0034_cheerful_rumiko_fujikawa.sql +35 -0
- package/src/db/migrations/meta/0032_snapshot.json +2582 -0
- package/src/db/migrations/meta/0033_snapshot.json +2816 -0
- package/src/db/migrations/meta/0034_snapshot.json +2816 -0
- package/src/db/migrations/meta/_journal.json +21 -0
- package/src/db/migrations/pg/0030_abandoned_nighthawk.sql +3 -0
- package/src/db/migrations/pg/0031_strong_ultimatum.sql +128 -0
- package/src/db/migrations/pg/0032_furry_multiple_man.sql +6 -0
- package/src/db/migrations/pg/meta/0030_snapshot.json +3329 -0
- package/src/db/migrations/pg/meta/0031_snapshot.json +3660 -0
- package/src/db/migrations/pg/meta/0032_snapshot.json +3660 -0
- package/src/db/migrations/pg/meta/_journal.json +21 -0
- package/src/db/pg/schema.ts +195 -27
- package/src/db/post-visibility.ts +117 -0
- package/src/db/schema.ts +222 -27
- package/src/i18n/__tests__/detect.test.ts +26 -81
- package/src/i18n/__tests__/supported-locales.test.ts +83 -0
- package/src/i18n/context.tsx +12 -2
- package/src/i18n/detect.ts +7 -66
- package/src/i18n/locales/glossary.zh-Hans.yml +44 -4
- package/src/i18n/locales/glossary.zh-Hant.yml +44 -4
- package/src/i18n/locales/public/en.po +490 -158
- package/src/i18n/locales/public/en.ts +1 -1
- package/src/i18n/locales/public/zh-Hans.po +487 -155
- package/src/i18n/locales/public/zh-Hans.ts +1 -1
- package/src/i18n/locales/public/zh-Hant.po +487 -155
- package/src/i18n/locales/public/zh-Hant.ts +1 -1
- package/src/i18n/locales/settings/en.po +555 -73
- package/src/i18n/locales/settings/en.ts +1 -1
- package/src/i18n/locales/settings/zh-Hans.po +601 -119
- package/src/i18n/locales/settings/zh-Hans.ts +1 -1
- package/src/i18n/locales/settings/zh-Hant.po +614 -132
- package/src/i18n/locales/settings/zh-Hant.ts +1 -1
- package/src/i18n/locales.ts +94 -0
- package/src/i18n/middleware.ts +12 -2
- package/src/i18n/supported-locales.ts +80 -0
- package/src/lib/__tests__/api-settings.test.ts +11 -2
- package/src/lib/__tests__/asset-path.test.ts +92 -0
- package/src/lib/__tests__/deferred.test.ts +90 -0
- package/src/lib/__tests__/discover-ping.test.ts +89 -0
- package/src/lib/__tests__/discover.test.ts +410 -0
- package/src/lib/__tests__/feed-policy.test.ts +26 -0
- package/src/lib/__tests__/feed.test.ts +1526 -15
- package/src/lib/__tests__/filter-dimensions.test.ts +341 -0
- package/src/lib/__tests__/github-sync-repo-name.test.ts +40 -0
- package/src/lib/__tests__/hosted-signin.test.ts +1 -1
- package/src/lib/__tests__/http-cache.test.ts +162 -0
- package/src/lib/__tests__/image.test.ts +27 -1
- package/src/lib/__tests__/jant-docs-dev.test.ts +21 -0
- package/src/lib/__tests__/jant-docs.test.ts +23 -0
- package/src/lib/__tests__/lang-detect.test.ts +231 -0
- package/src/lib/__tests__/markdown-to-tiptap.test.ts +105 -0
- package/src/lib/__tests__/markdown.test.ts +10 -0
- package/src/lib/__tests__/media-helpers.test.ts +81 -0
- package/src/lib/__tests__/navigation.test.ts +135 -1
- package/src/lib/__tests__/resolve-config.test.ts +131 -3
- package/src/lib/__tests__/schemas.test.ts +69 -5
- package/src/lib/__tests__/site-unavailable-page.test.ts +25 -0
- package/src/lib/__tests__/slug.test.ts +70 -0
- package/src/lib/__tests__/slugify.test.ts +85 -0
- package/src/lib/__tests__/storage.test.ts +32 -0
- package/src/lib/__tests__/thread-fold.test.ts +113 -0
- package/src/lib/__tests__/timeline.test.ts +87 -0
- package/src/lib/__tests__/tiptap-to-markdown.test.ts +172 -4
- package/src/lib/__tests__/upload-sideload.test.ts +14 -0
- package/src/lib/__tests__/url-fetch.test.ts +1 -2
- package/src/lib/__tests__/url.test.ts +78 -72
- package/src/lib/__tests__/view-language.test.ts +413 -0
- package/src/lib/__tests__/worker-response-cache.test.ts +90 -0
- package/src/lib/address.ts +57 -0
- package/src/lib/api-posts.ts +12 -0
- package/src/lib/asset-path.ts +98 -1
- package/src/lib/build-env.ts +16 -0
- package/src/lib/collection-groups.ts +5 -1
- package/src/lib/collection-paths.ts +3 -10
- package/src/lib/constants.ts +28 -3
- package/src/lib/copy-field.ts +36 -0
- package/src/lib/crypto.ts +40 -0
- package/src/lib/csp-builder.ts +2 -1
- package/src/lib/deferred.ts +79 -0
- package/src/lib/discover-ping.ts +141 -0
- package/src/lib/discover.ts +439 -0
- package/src/lib/env.ts +134 -0
- package/src/lib/errors.ts +33 -0
- package/src/lib/feed-policy.ts +236 -2
- package/src/lib/feed.ts +926 -73
- package/src/lib/filter-dimensions.ts +1349 -0
- package/src/lib/github-sync-repo-name.ts +45 -0
- package/src/lib/github-sync-site-config.ts +3 -0
- package/src/lib/hosted-signin.ts +1 -1
- package/src/lib/http-cache.ts +148 -0
- package/src/lib/hugo-markdown.ts +62 -0
- package/src/lib/icons.ts +9 -2
- package/src/lib/ids.ts +3 -0
- package/src/lib/image.ts +17 -4
- package/src/lib/jant-docs.ts +42 -0
- package/src/lib/lang-detect.ts +308 -0
- package/src/lib/markdown-manager.ts +392 -2
- package/src/lib/media-helpers.ts +13 -1
- package/src/lib/navigation.ts +78 -11
- package/src/lib/pagination.ts +28 -24
- package/src/lib/per-language-surfaces.ts +67 -0
- package/src/lib/post-body-html.ts +11 -4
- package/src/lib/post-meta.ts +19 -0
- package/src/lib/render.tsx +55 -7
- package/src/lib/resolve-config.ts +111 -6
- package/src/lib/schemas.ts +251 -15
- package/src/lib/site-header-fragment.tsx +2 -1
- package/src/lib/site-unavailable-page.ts +47 -0
- package/src/lib/slug.ts +51 -7
- package/src/lib/slugify.ts +113 -0
- package/src/lib/startup-config.ts +4 -6
- package/src/lib/storage.ts +7 -0
- package/src/lib/summary.ts +50 -0
- package/src/lib/thread-fold.ts +161 -0
- package/src/lib/time.ts +32 -0
- package/src/lib/timeline.ts +43 -2
- package/src/lib/tiptap-render.ts +1 -2
- package/src/lib/tiptap-to-markdown.ts +13 -7
- package/src/lib/upload.ts +16 -13
- package/src/lib/url.ts +159 -86
- package/src/lib/version.ts +10 -8
- package/src/lib/view-language.ts +436 -0
- package/src/lib/view.ts +53 -25
- package/src/lib/viewer-context.ts +62 -0
- package/src/middleware/__tests__/auth.test.ts +70 -2
- package/src/middleware/__tests__/cache-control.test.ts +71 -3
- package/src/middleware/__tests__/onboarding.test.ts +76 -5
- package/src/middleware/__tests__/secure-headers.test.ts +1 -1
- package/src/middleware/__tests__/session.test.ts +181 -33
- package/src/middleware/auth.ts +50 -31
- package/src/middleware/cache-control.ts +75 -10
- package/src/middleware/config.ts +6 -6
- package/src/middleware/error-handler.ts +12 -0
- package/src/middleware/onboarding.ts +32 -10
- package/src/middleware/secure-headers.ts +1 -1
- package/src/middleware/session.ts +96 -8
- package/src/node/__tests__/cli-migrate.test.ts +21 -19
- package/src/node/__tests__/cli-reset-password.test.ts +81 -8
- package/src/node/__tests__/cli-runtime-target.test.ts +48 -1
- package/src/node/__tests__/cli-setup.test.ts +163 -0
- package/src/node/__tests__/cli-site-selection.test.ts +128 -38
- package/src/node/__tests__/cli-site-snapshot.test.ts +67 -0
- package/src/node/__tests__/cli-snapshot-meta.test.ts +20 -0
- package/src/node/__tests__/runtime.test.ts +75 -0
- package/src/node/index.ts +3 -0
- package/src/node/request-handler.ts +3 -1
- package/src/preset.css +76 -22
- package/src/routes/__tests__/feed-validator.test.ts +156 -0
- package/src/routes/api/__tests__/collections.test.ts +114 -0
- package/src/routes/api/__tests__/discover.test.ts +222 -0
- package/src/routes/api/__tests__/nav-items.test.ts +212 -2
- package/src/routes/api/__tests__/post-translations.test.ts +829 -0
- package/src/routes/api/__tests__/posts.test.ts +24 -0
- package/src/routes/api/__tests__/settings.test.ts +24 -3
- package/src/routes/api/__tests__/telegram.test.ts +6 -5
- package/src/routes/api/__tests__/upload.test.ts +34 -0
- package/src/routes/api/collections.ts +49 -6
- package/src/routes/api/custom-urls.ts +0 -1
- package/src/routes/api/discover.ts +95 -0
- package/src/routes/api/export.ts +6 -3
- package/src/routes/api/internal/__tests__/sites.test.ts +42 -2
- package/src/routes/api/internal/sites.ts +0 -1
- package/src/routes/api/nav-items.ts +55 -8
- package/src/routes/api/posts.ts +161 -0
- package/src/routes/api/public/__tests__/archive.test.ts +111 -0
- package/src/routes/api/public/archive.ts +77 -81
- package/src/routes/api/public/posts.ts +44 -7
- package/src/routes/api/smart-collections.ts +132 -0
- package/src/routes/api/telegram.ts +2 -40
- package/src/routes/api/upload.ts +10 -3
- package/src/routes/api/uploads.ts +1 -2
- package/src/routes/auth/__tests__/setup-auth.test.ts +137 -0
- package/src/routes/auth/__tests__/setup-discover.test.ts +204 -0
- package/src/routes/auth/__tests__/setup-flow.test.ts +270 -0
- package/src/routes/auth/__tests__/setup-page.test.tsx +329 -0
- package/src/routes/auth/__tests__/setup-steps.test.ts +310 -0
- package/src/routes/auth/__tests__/setup.test.ts +278 -26
- package/src/routes/auth/__tests__/signin.test.ts +135 -0
- package/src/routes/auth/setup.tsx +934 -186
- package/src/routes/auth/signin.tsx +10 -8
- package/src/routes/compose.tsx +9 -0
- package/src/routes/dash/__tests__/custom-urls-archive.test.ts +44 -0
- package/src/routes/dash/__tests__/custom-urls-page.test.ts +49 -0
- package/src/routes/dash/__tests__/delete-account.test.ts +106 -0
- package/src/routes/dash/__tests__/github-sync-app.test.ts +365 -0
- package/src/routes/dash/__tests__/settings-discover.test.ts +213 -0
- package/src/routes/dash/custom-urls.tsx +1 -37
- package/src/routes/dash/settings.tsx +714 -111
- package/src/routes/discover-announce.ts +96 -0
- package/src/routes/feed/__tests__/feed.test.ts +165 -2
- package/src/routes/feed/feed.ts +69 -40
- package/src/routes/feed/sitemap.ts +95 -11
- package/src/routes/pages/__tests__/archive-indexing.test.ts +200 -0
- package/src/routes/pages/__tests__/archive-params.test.ts +370 -6
- package/src/routes/pages/__tests__/collection-routing.test.ts +16 -0
- package/src/routes/pages/__tests__/collections-feed-links.test.ts +72 -0
- package/src/routes/pages/__tests__/collections.test.ts +9 -3
- package/src/routes/pages/__tests__/custom-archive-url.test.ts +119 -0
- package/src/routes/pages/__tests__/feed-autodiscovery.test.ts +148 -0
- package/src/routes/pages/__tests__/language-routing.test.ts +679 -0
- package/src/routes/pages/__tests__/post-page-round-trips.test.ts +211 -0
- package/src/routes/pages/__tests__/post-preview-partial.test.ts +96 -0
- package/src/routes/pages/__tests__/smart-collection.test.ts +298 -0
- package/src/routes/pages/__tests__/subscribe.test.ts +248 -0
- package/src/routes/pages/__tests__/thread-order.test.ts +179 -0
- package/src/routes/pages/archive.tsx +487 -379
- package/src/routes/pages/collection.tsx +94 -96
- package/src/routes/pages/collections.tsx +38 -50
- package/src/routes/pages/featured.tsx +43 -18
- package/src/routes/pages/home.tsx +25 -6
- package/src/routes/pages/language.tsx +113 -0
- package/src/routes/pages/latest.tsx +26 -16
- package/src/routes/pages/new.tsx +3 -0
- package/src/routes/pages/page.tsx +228 -45
- package/src/routes/pages/partials.tsx +41 -0
- package/src/routes/pages/search.tsx +22 -5
- package/src/routes/pages/smart-collection.tsx +340 -0
- package/src/routes/pages/subscribe.tsx +182 -0
- package/src/runtime/__tests__/index.test.ts +19 -0
- package/src/runtime/__tests__/node.test.ts +129 -4
- package/src/runtime/__tests__/readiness.test.ts +23 -0
- package/src/runtime/cloudflare.ts +21 -2
- package/src/runtime/index.ts +27 -1
- package/src/runtime/node.ts +67 -1
- package/src/runtime/readiness.ts +15 -0
- package/src/runtime/site.ts +95 -24
- package/src/services/__tests__/auth.test.ts +116 -7
- package/src/services/__tests__/bootstrap-setup-instance.test.ts +230 -0
- package/src/services/__tests__/collection.test.ts +108 -4
- package/src/services/__tests__/custom-url.test.ts +34 -0
- package/src/services/__tests__/github-app-installations.test.ts +153 -0
- package/src/services/__tests__/github-sync-push.test.ts +46 -0
- package/src/services/__tests__/language.test.ts +495 -0
- package/src/services/__tests__/media.test.ts +31 -0
- package/src/services/__tests__/navigation.test.ts +213 -10
- package/src/services/__tests__/path.test.ts +236 -0
- package/src/services/__tests__/post-language.test.ts +830 -0
- package/src/services/__tests__/post-timeline.test.ts +127 -0
- package/src/services/__tests__/post.test.ts +120 -9
- package/src/services/__tests__/settings.test.ts +228 -17
- package/src/services/__tests__/site-admin.test.ts +5 -5
- package/src/services/__tests__/smart-collection.test.ts +651 -0
- package/src/services/about-page.ts +22 -1
- package/src/services/auth.ts +51 -49
- package/src/services/bootstrap.ts +408 -39
- package/src/services/collection-directory-position.ts +76 -0
- package/src/services/collection.ts +331 -46
- package/src/services/custom-url.ts +29 -24
- package/src/services/export-theme/assets/client-site.css +1 -1
- package/src/services/export-theme/assets/client-site.js +123 -123
- package/src/services/export-theme/layouts/_default/alias.html +27 -1
- package/src/services/export-theme/layouts/_default/list.html +2 -63
- package/src/services/export-theme/layouts/_default/rss.xml +103 -38
- package/src/services/export-theme/layouts/collections/list.html +3 -3
- package/src/services/export-theme/layouts/featured/list.html +1 -4
- package/src/services/export-theme/layouts/index.html +40 -26
- package/src/services/export-theme/layouts/partials/collection-members.html +100 -0
- package/src/services/export-theme/layouts/partials/collection-threads.html +33 -0
- package/src/services/export-theme/layouts/partials/featured-members.html +35 -0
- package/src/services/export-theme/layouts/partials/featured-thread.html +1 -1
- package/src/services/export-theme/layouts/partials/feed-post-content.xml +105 -2
- package/src/services/export-theme/layouts/partials/footer.html +1 -1
- package/src/services/export-theme/layouts/partials/head.html +1 -1
- package/src/services/export-theme/layouts/partials/header.html +5 -3
- package/src/services/export-theme/layouts/partials/jant-data.html +23 -0
- package/src/services/export-theme/layouts/partials/latest-members.html +48 -0
- package/src/services/export-theme/layouts/partials/media-gallery.html +35 -21
- package/src/services/export-theme/layouts/partials/pagination.html +14 -13
- package/src/services/export-theme/layouts/partials/post-card.html +8 -4
- package/src/services/export-theme/layouts/partials/reply.html +2 -1
- package/src/services/export-theme/layouts/partials/smart-collection-members.html +129 -0
- package/src/services/export-theme/layouts/partials/thread-preview.html +41 -31
- package/src/services/export-theme/layouts/post/list.html +1 -1
- package/src/services/export-theme/layouts/smart_collection/list.html +24 -0
- package/src/services/export-theme/styles/main.css +13 -2
- package/src/services/export-theme/theme.toml +1 -1
- package/src/services/export.ts +880 -72
- package/src/services/github-app-installations.ts +171 -4
- package/src/services/github-sync.ts +69 -15
- package/src/services/index.ts +34 -3
- package/src/services/language.ts +412 -0
- package/src/services/mcp.ts +22 -2
- package/src/services/media.ts +22 -7
- package/src/services/navigation.ts +429 -67
- package/src/services/path.ts +254 -6
- package/src/services/post.ts +1234 -190
- package/src/services/search.ts +39 -2
- package/src/services/settings.ts +238 -43
- package/src/services/site-admin.ts +27 -14
- package/src/services/site.ts +160 -32
- package/src/services/smart-collection.ts +636 -0
- package/src/services/telegram.ts +20 -22
- package/src/services/upload-session.ts +1 -3
- package/src/style-author.css +27 -0
- package/src/styles/components-author.css +1622 -0
- package/src/styles/components.css +24 -1571
- package/src/styles/site-media.css +16 -2
- package/src/styles/tokens.css +82 -12
- package/src/styles/ui-author.css +7602 -0
- package/src/styles/ui.css +1090 -7216
- package/src/types/app-context.ts +23 -0
- package/src/types/bindings.ts +8 -0
- package/src/types/config.ts +94 -15
- package/src/types/constants.ts +188 -16
- package/src/types/entities.ts +87 -1
- package/src/types/operations.ts +74 -2
- package/src/types/props.ts +157 -31
- package/src/types/views.ts +55 -1
- package/src/ui/__tests__/font-themes.test.ts +52 -12
- package/src/ui/compose/ComposeDialog.tsx +201 -7
- package/src/ui/compose/ComposePrompt.tsx +1 -5
- package/src/ui/compose/__tests__/ComposeDialog.test.tsx +125 -0
- package/src/ui/dash/appearance/AdvancedContent.tsx +2 -2
- package/src/ui/dash/appearance/CodeInjectionContent.tsx +12 -16
- package/src/ui/dash/appearance/NavigationContent.tsx +117 -16
- package/src/ui/dash/appearance/__tests__/NavigationContent.test.tsx +33 -11
- package/src/ui/dash/appearance/__tests__/system-nav-descriptions.test.ts +44 -0
- package/src/ui/dash/appearance/system-nav-descriptions.ts +75 -0
- package/src/ui/dash/settings/AccountMenuContent.tsx +2 -2
- package/src/ui/dash/settings/ApiTokensContent.tsx +2 -1
- package/src/ui/dash/settings/ConfigEditorContent.tsx +35 -16
- package/src/ui/dash/settings/GeneralContent.tsx +277 -113
- package/src/ui/dash/settings/LanguageContent.tsx +409 -0
- package/src/ui/dash/settings/SettingsRootContent.tsx +19 -1
- package/src/ui/dash/settings/__tests__/GeneralContent.test.tsx +324 -4
- package/src/ui/dash/settings/__tests__/LanguageContent.test.tsx +144 -0
- package/src/ui/dash/settings/__tests__/discover-copy.test.ts +66 -0
- package/src/ui/dash/settings/discover-copy.ts +116 -0
- package/src/ui/feed/LinkCard.tsx +55 -45
- package/src/ui/feed/NoteCard.tsx +9 -4
- package/src/ui/feed/PostStatusBadges.tsx +90 -43
- package/src/ui/feed/QuoteCard.tsx +55 -30
- package/src/ui/feed/ThreadPreview.tsx +6 -2
- package/src/ui/feed/__tests__/thread-preview.test.ts +13 -16
- package/src/ui/feed/__tests__/timeline-cards.test.ts +314 -49
- package/src/ui/feed/thread-preview-state.ts +20 -6
- package/src/ui/font-themes.ts +39 -29
- package/src/ui/layouts/BaseLayout.tsx +155 -10
- package/src/ui/layouts/SiteLayout.tsx +192 -34
- package/src/ui/layouts/__tests__/BaseLayout.test.tsx +196 -17
- package/src/ui/layouts/__tests__/SiteLayout.test.tsx +109 -85
- package/src/ui/pages/ArchivePage.tsx +282 -289
- package/src/ui/pages/CollectionPage.tsx +86 -76
- package/src/ui/pages/CollectionsPage.tsx +5 -0
- package/src/ui/pages/ComposePage.tsx +8 -0
- package/src/ui/pages/PostPage.tsx +47 -0
- package/src/ui/pages/SearchPage.tsx +3 -3
- package/src/ui/pages/SmartCollectionPage.tsx +434 -0
- package/src/ui/pages/SubscribePage.tsx +236 -0
- package/src/ui/pages/__tests__/ArchivePage.test.tsx +65 -9
- package/src/ui/pages/__tests__/collections-directory-rows.test.tsx +92 -0
- package/src/ui/pages/__tests__/collections-view-links.test.tsx +81 -0
- package/src/ui/shared/CollectionDirectory.tsx +178 -3
- package/src/ui/shared/CollectionsManager.tsx +70 -17
- package/src/ui/shared/CopyField.tsx +127 -0
- package/src/ui/shared/EmptyPostContent.tsx +23 -0
- package/src/ui/shared/MediaGallery.tsx +125 -46
- package/src/ui/shared/Pagination.tsx +20 -5
- package/src/ui/shared/PostFooter.tsx +20 -11
- package/src/ui/shared/__tests__/archive-labels.test.ts +100 -0
- package/src/ui/shared/__tests__/media-gallery.test.ts +210 -2
- package/src/ui/shared/__tests__/navigation-labels.test.ts +16 -22
- package/src/ui/shared/__tests__/pagination.test.ts +38 -24
- package/src/ui/shared/__tests__/post-footer.test.ts +32 -0
- package/src/ui/shared/archive-labels.ts +98 -0
- package/src/ui/shared/collection-management-labels.ts +94 -16
- package/src/ui/shared/navigation-labels.ts +45 -71
- package/src/ui/shared/post-article-attributes.ts +2 -0
- package/src/ui/shared/smart-collection-labels.ts +343 -0
- package/dist/app-C75Zc8dD.js +0 -6
- package/dist/client/_assets/chunks/heic-to-DUUaO23q.js +0 -1
- package/dist/client/_assets/chunks/module-DcsAZQZ_.js +0 -716
- package/dist/client/_assets/chunks/url-CU9pEbk5.js +0 -1
- package/dist/client/_assets/client-Bq0vre8Y.js +0 -296
- package/dist/client/_assets/client-D3VZyG4L.css +0 -2
- package/dist/client/_assets/client-auth-F2rb9WfF.js +0 -5571
- package/dist/env-BPYViDJJ.js +0 -281
- package/dist/export-Dl5KCiEs.js +0 -5562
- package/dist/github-api-BNF35Lkx.js +0 -176
- package/dist/github-app-DdiD-bC4.js +0 -275
- package/dist/github-sync-Bqg62IvV.js +0 -4
- package/dist/github-sync-HdK2EgvJ.js +0 -431
- package/dist/url-CbLAtlZe.js +0 -900
- package/src/client/__tests__/collection-created-notice.test.ts +0 -36
- package/src/client/__tests__/collection-form-bridge.test.ts +0 -216
- package/src/client/__tests__/post-form-bridge.test.ts +0 -82
- package/src/client/collection-created-notice.ts +0 -66
- package/src/client/collection-form-bridge.ts +0 -191
- package/src/client/components/__tests__/jant-post-form.test.ts +0 -193
- package/src/client/components/jant-post-form.ts +0 -422
- package/src/client/components/post-form-template.ts +0 -352
- package/src/client/components/post-form-types.ts +0 -96
- package/src/client/media-metadata.ts +0 -247
- package/src/client/post-form-bridge.ts +0 -193
- package/src/client/tiptap/exitable-marks.ts +0 -73
- package/src/ui/pages/CollectionEditorPage.tsx +0 -175
|
@@ -5,10 +5,15 @@ import {
|
|
|
5
5
|
} from "../../__tests__/helpers/db.js";
|
|
6
6
|
import { sqliteSchemaBundle } from "../../db/schema-bundle.js";
|
|
7
7
|
import { createRequestRuntime } from "../index.js";
|
|
8
|
-
import { createNodeRequestRuntime } from "../node.js";
|
|
8
|
+
import { createNodeCliRuntime, createNodeRequestRuntime } from "../node.js";
|
|
9
|
+
import type { CliSiteSelector } from "../site.js";
|
|
9
10
|
import type { Bindings } from "../../types.js";
|
|
10
11
|
import { siteDomains, sites } from "../../db/schema.js";
|
|
11
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
ConflictError,
|
|
14
|
+
NotFoundError,
|
|
15
|
+
SiteUnavailableError,
|
|
16
|
+
} from "../../lib/errors.js";
|
|
12
17
|
import { TRANSIENT_SINGLE_SITE_ID } from "../../services/site.js";
|
|
13
18
|
|
|
14
19
|
afterEach(() => {
|
|
@@ -217,7 +222,7 @@ describe("createNodeRequestRuntime", () => {
|
|
|
217
222
|
);
|
|
218
223
|
});
|
|
219
224
|
|
|
220
|
-
it("treats suspended host-based sites as not
|
|
225
|
+
it("treats suspended host-based sites as unavailable, not missing", async () => {
|
|
221
226
|
const { db, sqlite } = createTestDatabase();
|
|
222
227
|
const consoleError = vi
|
|
223
228
|
.spyOn(console, "error")
|
|
@@ -250,9 +255,129 @@ describe("createNodeRequestRuntime", () => {
|
|
|
250
255
|
} as Bindings,
|
|
251
256
|
"http://suspended.localtest.me/",
|
|
252
257
|
),
|
|
253
|
-
).rejects.toBeInstanceOf(
|
|
258
|
+
).rejects.toBeInstanceOf(SiteUnavailableError);
|
|
254
259
|
expect(consoleError).toHaveBeenCalledWith(
|
|
255
260
|
"[Jant] Hosted site resolution failed: host=suspended.localtest.me path=/ reason=site-not-active siteId=sit_test00000000000000000000000 siteKey=default siteStatus=suspended",
|
|
256
261
|
);
|
|
257
262
|
});
|
|
258
263
|
});
|
|
264
|
+
|
|
265
|
+
describe("createNodeCliRuntime", () => {
|
|
266
|
+
const OTHER_SITE_ID = "sit_other0000000000000000000000";
|
|
267
|
+
|
|
268
|
+
function nodeDatabaseBindings(
|
|
269
|
+
testDb: ReturnType<typeof createTestDatabase>,
|
|
270
|
+
env: Partial<Bindings> = {},
|
|
271
|
+
): Bindings {
|
|
272
|
+
return {
|
|
273
|
+
NODE_DATABASE: {
|
|
274
|
+
db: testDb.db,
|
|
275
|
+
dialect: "sqlite",
|
|
276
|
+
rawQuery: createSqliteRawQuery(testDb.sqlite),
|
|
277
|
+
schema: sqliteSchemaBundle,
|
|
278
|
+
},
|
|
279
|
+
...env,
|
|
280
|
+
} as Bindings;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** The default test site plus a second tenant on its own host. */
|
|
284
|
+
function createDatabaseWithTwoSites() {
|
|
285
|
+
const testDb = createTestDatabase();
|
|
286
|
+
testDb.sqlite
|
|
287
|
+
.prepare(
|
|
288
|
+
`
|
|
289
|
+
INSERT INTO site (id, key, status, created_at, updated_at)
|
|
290
|
+
VALUES (?, 'other', 'active', 1774200001, 1774200001)
|
|
291
|
+
`,
|
|
292
|
+
)
|
|
293
|
+
.run(OTHER_SITE_ID);
|
|
294
|
+
testDb.sqlite
|
|
295
|
+
.prepare(
|
|
296
|
+
`
|
|
297
|
+
INSERT INTO site_domain (id, site_id, host, path_prefix, kind, redirect_to_primary, created_at, updated_at)
|
|
298
|
+
VALUES ('std_other_1', ?, 'other.localtest.me', NULL, 'primary', 1, 1774200001, 1774200001)
|
|
299
|
+
`,
|
|
300
|
+
)
|
|
301
|
+
.run(OTHER_SITE_ID);
|
|
302
|
+
return testDb;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// A hosted database holds every tenant, so there is no "only site" to fall
|
|
306
|
+
// back to. The single-site lookup would blame SITE_RESOLUTION_MODE, which is
|
|
307
|
+
// already host-based; the CLI has to say which flag picks the site.
|
|
308
|
+
it("requires a site selector in host-based mode with several sites", async () => {
|
|
309
|
+
const testDb = createDatabaseWithTwoSites();
|
|
310
|
+
|
|
311
|
+
await expect(
|
|
312
|
+
createNodeCliRuntime(
|
|
313
|
+
nodeDatabaseBindings(testDb, { SITE_RESOLUTION_MODE: "host-based" }),
|
|
314
|
+
),
|
|
315
|
+
).rejects.toThrow(
|
|
316
|
+
"host-based mode needs a target site. Pass --site <key|id>, --host <host>, or --url <url>.",
|
|
317
|
+
);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it.each<[string, CliSiteSelector]>([
|
|
321
|
+
["key", { kind: "site", idOrKey: "other" }],
|
|
322
|
+
["id", { kind: "site", idOrKey: OTHER_SITE_ID }],
|
|
323
|
+
["host", { kind: "host", host: "other.localtest.me", pathPrefix: null }],
|
|
324
|
+
])(
|
|
325
|
+
"scopes a host-based runtime to the site selected by %s",
|
|
326
|
+
async (_label, selector) => {
|
|
327
|
+
const testDb = createDatabaseWithTwoSites();
|
|
328
|
+
|
|
329
|
+
const runtime = await createNodeCliRuntime(
|
|
330
|
+
nodeDatabaseBindings(testDb, { SITE_RESOLUTION_MODE: "host-based" }),
|
|
331
|
+
selector,
|
|
332
|
+
);
|
|
333
|
+
await runtime.services.settings.set("PASSWORD_RESET_TOKEN", "hash:1");
|
|
334
|
+
|
|
335
|
+
expect(runtime.currentSite.id).toBe(OTHER_SITE_ID);
|
|
336
|
+
expect(runtime.currentSiteDomain?.host).toBe("other.localtest.me");
|
|
337
|
+
expect(
|
|
338
|
+
testDb.sqlite
|
|
339
|
+
.prepare(
|
|
340
|
+
`SELECT site_id FROM site_setting WHERE key = 'PASSWORD_RESET_TOKEN'`,
|
|
341
|
+
)
|
|
342
|
+
.pluck()
|
|
343
|
+
.all(),
|
|
344
|
+
).toEqual([OTHER_SITE_ID]);
|
|
345
|
+
},
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
it("names the selector that matched no site", async () => {
|
|
349
|
+
const testDb = createDatabaseWithTwoSites();
|
|
350
|
+
const env = nodeDatabaseBindings(testDb, {
|
|
351
|
+
SITE_RESOLUTION_MODE: "host-based",
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
await expect(
|
|
355
|
+
createNodeCliRuntime(env, { kind: "site", idOrKey: "missing" }),
|
|
356
|
+
).rejects.toThrow("No site found for --site missing.");
|
|
357
|
+
await expect(
|
|
358
|
+
createNodeCliRuntime(env, {
|
|
359
|
+
kind: "host",
|
|
360
|
+
host: "other.localtest.me",
|
|
361
|
+
pathPrefix: "/blog",
|
|
362
|
+
}),
|
|
363
|
+
).rejects.toThrow(
|
|
364
|
+
'No site found for host "other.localtest.me" and path prefix "/blog".',
|
|
365
|
+
);
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it("uses the instance's one site in single-site mode", async () => {
|
|
369
|
+
const runtime = await createNodeCliRuntime(
|
|
370
|
+
nodeDatabaseBindings(createTestDatabase()),
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
expect(runtime.currentSite.id).toBe(DEFAULT_TEST_SITE_ID);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
// Here the single-site error is the accurate one: the database really does
|
|
377
|
+
// belong to a host-based install.
|
|
378
|
+
it("reports a multi-site database in single-site mode as misconfigured", async () => {
|
|
379
|
+
await expect(
|
|
380
|
+
createNodeCliRuntime(nodeDatabaseBindings(createDatabaseWithTwoSites())),
|
|
381
|
+
).rejects.toThrow("single-site mode found multiple sites in the database:");
|
|
382
|
+
});
|
|
383
|
+
});
|
|
@@ -24,6 +24,7 @@ describe("getInstanceReadiness", () => {
|
|
|
24
24
|
}),
|
|
25
25
|
).resolves.toEqual({
|
|
26
26
|
status: "ok",
|
|
27
|
+
version: "test-version",
|
|
27
28
|
checks: {
|
|
28
29
|
startupConfig: { ok: true },
|
|
29
30
|
database: { ok: true },
|
|
@@ -40,6 +41,7 @@ describe("getInstanceReadiness", () => {
|
|
|
40
41
|
}),
|
|
41
42
|
).resolves.toEqual({
|
|
42
43
|
status: "error",
|
|
44
|
+
version: "test-version",
|
|
43
45
|
checks: {
|
|
44
46
|
startupConfig: {
|
|
45
47
|
ok: false,
|
|
@@ -104,6 +106,26 @@ describe("getInstanceReadiness", () => {
|
|
|
104
106
|
);
|
|
105
107
|
});
|
|
106
108
|
|
|
109
|
+
// What a deploy check compares: the build that answers, and when its
|
|
110
|
+
// process started. A Worker has no process, so it reports only the build.
|
|
111
|
+
it("reports the build, and the process start where there is one", async () => {
|
|
112
|
+
const { sqlite } = createTestDatabase();
|
|
113
|
+
const env = {
|
|
114
|
+
AUTH_SECRET: "test-secret-with-enough-entropy-for-readiness",
|
|
115
|
+
NODE_SQLITE: sqlite,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const withoutStart = await getInstanceReadiness(env);
|
|
119
|
+
expect(withoutStart.version).toBe("test-version");
|
|
120
|
+
expect(withoutStart).not.toHaveProperty("startedAt");
|
|
121
|
+
|
|
122
|
+
const withStart = await getInstanceReadiness({
|
|
123
|
+
...env,
|
|
124
|
+
NODE_STARTED_AT: 1789000000,
|
|
125
|
+
});
|
|
126
|
+
expect(withStart.startedAt).toBe(1789000000);
|
|
127
|
+
});
|
|
128
|
+
|
|
107
129
|
it("reports a missing database binding", async () => {
|
|
108
130
|
await expect(
|
|
109
131
|
getInstanceReadiness({
|
|
@@ -111,6 +133,7 @@ describe("getInstanceReadiness", () => {
|
|
|
111
133
|
}),
|
|
112
134
|
).resolves.toEqual({
|
|
113
135
|
status: "error",
|
|
136
|
+
version: "test-version",
|
|
114
137
|
checks: {
|
|
115
138
|
startupConfig: { ok: true },
|
|
116
139
|
database: {
|
|
@@ -52,6 +52,13 @@ export interface CloudflareRequestRuntime {
|
|
|
52
52
|
export async function createCloudflareRequestRuntime(
|
|
53
53
|
env: Bindings,
|
|
54
54
|
publicRequestUrl: string,
|
|
55
|
+
options?: {
|
|
56
|
+
/**
|
|
57
|
+
* Anchor this request's first read to the primary instead of any replica.
|
|
58
|
+
* Set for requests that carry a session cookie — see `createRequestRuntime`.
|
|
59
|
+
*/
|
|
60
|
+
anchorReadsToPrimary?: boolean;
|
|
61
|
+
},
|
|
55
62
|
): Promise<CloudflareRequestRuntime> {
|
|
56
63
|
if (!env.DB) {
|
|
57
64
|
throw new Error("Cloudflare runtime requires a DB binding.");
|
|
@@ -62,8 +69,19 @@ export async function createCloudflareRequestRuntime(
|
|
|
62
69
|
throw new Error("AUTH_SECRET should be set after startup validation.");
|
|
63
70
|
}
|
|
64
71
|
|
|
65
|
-
// Use withSession() to enable D1 Read Replication.
|
|
66
|
-
|
|
72
|
+
// Use withSession() to enable D1 Read Replication. The constraint applies to
|
|
73
|
+
// the session's *first* query; every later query is anchored to the bookmark
|
|
74
|
+
// that one returns, so the whole request is sequentially consistent.
|
|
75
|
+
//
|
|
76
|
+
// Bookmarks are not carried across requests, so an unconstrained first read
|
|
77
|
+
// may land on a replica that has not caught up yet. For anonymous traffic
|
|
78
|
+
// that is fine and it is the point of replication. For a request holding a
|
|
79
|
+
// session cookie it is not: the session row is written on the primary, and a
|
|
80
|
+
// lagging replica answers "no such session" — the visitor is bounced to
|
|
81
|
+
// sign-in even though they never signed out.
|
|
82
|
+
const session = env.DB.withSession(
|
|
83
|
+
options?.anchorReadsToPrimary ? "first-primary" : "first-unconstrained",
|
|
84
|
+
);
|
|
67
85
|
|
|
68
86
|
// Note: Drizzle ORM doesn't officially support D1DatabaseSession yet
|
|
69
87
|
// (issue #2226), but it works at runtime.
|
|
@@ -89,6 +107,7 @@ export async function createCloudflareRequestRuntime(
|
|
|
89
107
|
|
|
90
108
|
const servicesConfig = {
|
|
91
109
|
databaseDialect: "sqlite" as const,
|
|
110
|
+
authSecret,
|
|
92
111
|
bootstrapSite: getSingleSiteBootstrapOptions(env),
|
|
93
112
|
enforceHostedMediaQuota: getSiteResolutionMode(env) === "host-based",
|
|
94
113
|
hostedControlPlane: createHostedControlPlaneClient(env),
|
package/src/runtime/index.ts
CHANGED
|
@@ -2,13 +2,39 @@ import { createCloudflareRequestRuntime } from "./cloudflare.js";
|
|
|
2
2
|
import { createNodeRequestRuntime } from "./node.js";
|
|
3
3
|
import type { Bindings } from "../types/bindings.js";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Whether a `Cookie` header carries a better-auth session token.
|
|
7
|
+
*
|
|
8
|
+
* Matched on the `.session_token` suffix so it holds for both the plain and
|
|
9
|
+
* the `__Secure-`-prefixed cookie name. This is a cheap "probably signed in"
|
|
10
|
+
* hint used to pick a database consistency mode — it does not verify the
|
|
11
|
+
* cookie's signature, and it must not be used to make an auth decision.
|
|
12
|
+
*
|
|
13
|
+
* @param cookieHeader - Raw `Cookie` request header, if any
|
|
14
|
+
* @returns `true` when a session token cookie appears to be present
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* hasSessionCookie("__Secure-better-auth.session_token=abc"); // true
|
|
19
|
+
* hasSessionCookie("theme=dark"); // false
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export function hasSessionCookie(
|
|
23
|
+
cookieHeader: string | null | undefined,
|
|
24
|
+
): boolean {
|
|
25
|
+
return !!cookieHeader?.includes(".session_token=");
|
|
26
|
+
}
|
|
27
|
+
|
|
5
28
|
export async function createRequestRuntime(
|
|
6
29
|
env: Bindings,
|
|
7
30
|
publicRequestUrl: string,
|
|
31
|
+
request?: Request,
|
|
8
32
|
) {
|
|
9
33
|
if (env.NODE_DATABASE || env.NODE_SQLITE) {
|
|
10
34
|
return createNodeRequestRuntime(env, publicRequestUrl);
|
|
11
35
|
}
|
|
12
36
|
|
|
13
|
-
return createCloudflareRequestRuntime(env, publicRequestUrl
|
|
37
|
+
return createCloudflareRequestRuntime(env, publicRequestUrl, {
|
|
38
|
+
anchorReadsToPrimary: hasSessionCookie(request?.headers.get("cookie")),
|
|
39
|
+
});
|
|
14
40
|
}
|
package/src/runtime/node.ts
CHANGED
|
@@ -12,14 +12,21 @@ import {
|
|
|
12
12
|
shouldUseSecureCookies,
|
|
13
13
|
} from "../lib/env.js";
|
|
14
14
|
import { createHostedControlPlaneClient } from "../lib/hosted-control-plane.js";
|
|
15
|
+
import { ValidationError } from "../lib/errors.js";
|
|
15
16
|
import { createMemoryRateLimiter } from "../lib/rate-limit-memory.js";
|
|
16
17
|
import type { RateLimiter } from "../lib/rate-limit.js";
|
|
18
|
+
import { InstanceSetupSchema } from "../lib/schemas.js";
|
|
17
19
|
import { createStorageDriver, type StorageDriver } from "../lib/storage.js";
|
|
18
20
|
import {
|
|
19
21
|
createHostedHandoffService,
|
|
20
22
|
type HostedHandoffService,
|
|
21
23
|
} from "../services/hosted-handoff.js";
|
|
24
|
+
import {
|
|
25
|
+
createBootstrapService,
|
|
26
|
+
type SetUpInstanceResult,
|
|
27
|
+
} from "../services/bootstrap.js";
|
|
22
28
|
import { createServices, type Services } from "../services/index.js";
|
|
29
|
+
import { TRANSIENT_SINGLE_SITE_ID } from "../services/site.js";
|
|
23
30
|
import type { Site, SiteDomain } from "../types/entities.js";
|
|
24
31
|
import type { Bindings } from "../types/bindings.js";
|
|
25
32
|
import {
|
|
@@ -27,6 +34,7 @@ import {
|
|
|
27
34
|
getResolvedSiteBaseUrl,
|
|
28
35
|
resolveCliSite,
|
|
29
36
|
resolveRequestSite,
|
|
37
|
+
type CliSiteSelector,
|
|
30
38
|
} from "./site.js";
|
|
31
39
|
|
|
32
40
|
export interface NodeRequestRuntime {
|
|
@@ -144,6 +152,7 @@ export async function createNodeRequestRuntime(
|
|
|
144
152
|
|
|
145
153
|
const servicesConfig = {
|
|
146
154
|
databaseDialect,
|
|
155
|
+
authSecret,
|
|
147
156
|
bootstrapSite: getSingleSiteBootstrapOptions(env),
|
|
148
157
|
enforceHostedMediaQuota: getSiteResolutionMode(env) === "host-based",
|
|
149
158
|
hostedControlPlane: createHostedControlPlaneClient(env),
|
|
@@ -175,9 +184,24 @@ export async function createNodeRequestRuntime(
|
|
|
175
184
|
* Builds the runtime objects needed by local CLI commands.
|
|
176
185
|
*
|
|
177
186
|
* Unlike the request runtime, this path does not require auth configuration.
|
|
187
|
+
*
|
|
188
|
+
* @param env - Bindings with a resolved `NODE_DATABASE` or `NODE_SQLITE`
|
|
189
|
+
* @param selector - The site the command acts on. Required in host-based
|
|
190
|
+
* mode; single-site mode falls back to the instance's one site.
|
|
191
|
+
* @returns Services, storage and database scoped to that site
|
|
192
|
+
* @throws {Error} When host-based mode has no selector, or the selector
|
|
193
|
+
* matches no site
|
|
194
|
+
* @example
|
|
195
|
+
* ```ts
|
|
196
|
+
* const runtime = await createNodeCliRuntime(bindings, {
|
|
197
|
+
* kind: "site",
|
|
198
|
+
* idOrKey: "demo",
|
|
199
|
+
* });
|
|
200
|
+
* ```
|
|
178
201
|
*/
|
|
179
202
|
export async function createNodeCliRuntime(
|
|
180
203
|
env: Bindings,
|
|
204
|
+
selector: CliSiteSelector | null = null,
|
|
181
205
|
): Promise<NodeCliRuntime> {
|
|
182
206
|
const nodeDatabase = env.NODE_DATABASE;
|
|
183
207
|
const sqlite = env.NODE_SQLITE;
|
|
@@ -194,7 +218,7 @@ export async function createNodeCliRuntime(
|
|
|
194
218
|
|
|
195
219
|
const slugIdLength =
|
|
196
220
|
parseInt(getEnvString(env, "SLUG_ID_LENGTH") ?? "5", 10) || 5;
|
|
197
|
-
const siteLookup = await resolveCliSite(db, env, databaseSchema);
|
|
221
|
+
const siteLookup = await resolveCliSite(db, env, selector, databaseSchema);
|
|
198
222
|
|
|
199
223
|
return {
|
|
200
224
|
currentSite: siteLookup.site,
|
|
@@ -212,3 +236,45 @@ export async function createNodeCliRuntime(
|
|
|
212
236
|
storage: createStorageDriver(env),
|
|
213
237
|
};
|
|
214
238
|
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Set up a self-hosted Node install without a browser, the way the two setup
|
|
242
|
+
* screens would. Backs `jant setup`.
|
|
243
|
+
*
|
|
244
|
+
* @param env - Bindings with a resolved `NODE_DATABASE`
|
|
245
|
+
* @param input - The owner's credentials and the site's answers, unvalidated
|
|
246
|
+
* @returns What setup found, and the site
|
|
247
|
+
* @throws {ValidationError} When the input fails `InstanceSetupSchema`
|
|
248
|
+
* @throws {Error} Whenever `BootstrapService.setUpInstance` refuses
|
|
249
|
+
* @example
|
|
250
|
+
* ```ts
|
|
251
|
+
* await setUpNodeInstance(bindings, {
|
|
252
|
+
* email: "owner@example.com",
|
|
253
|
+
* password: "correct horse battery",
|
|
254
|
+
* });
|
|
255
|
+
* ```
|
|
256
|
+
*/
|
|
257
|
+
export async function setUpNodeInstance(
|
|
258
|
+
env: Bindings,
|
|
259
|
+
input: unknown,
|
|
260
|
+
): Promise<SetUpInstanceResult> {
|
|
261
|
+
const nodeDatabase = env.NODE_DATABASE;
|
|
262
|
+
if (!nodeDatabase) {
|
|
263
|
+
throw new Error("Node setup requires a resolved database binding.");
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const parsed = InstanceSetupSchema.safeParse(input);
|
|
267
|
+
if (!parsed.success) {
|
|
268
|
+
throw new ValidationError(
|
|
269
|
+
parsed.error.issues[0]?.message ?? "Invalid setup input.",
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// No request resolved a site, and setup never reads the one it is bound to.
|
|
274
|
+
return createBootstrapService(nodeDatabase.db, TRANSIENT_SINGLE_SITE_ID, {
|
|
275
|
+
schema: nodeDatabase.schema,
|
|
276
|
+
databaseDialect: nodeDatabase.dialect,
|
|
277
|
+
bootstrapSite: getSingleSiteBootstrapOptions(env),
|
|
278
|
+
siteResolutionMode: getSiteResolutionMode(env),
|
|
279
|
+
}).setUpInstance(parsed.data);
|
|
280
|
+
}
|
package/src/runtime/readiness.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
getAuthSecretReadinessError,
|
|
6
6
|
getHostBasedStartupConfigurationIssues,
|
|
7
7
|
} from "../lib/startup-config.js";
|
|
8
|
+
import { CORE_VERSION } from "../lib/version.js";
|
|
8
9
|
import { createSiteService } from "../services/site.js";
|
|
9
10
|
import type { Bindings } from "../types/bindings.js";
|
|
10
11
|
|
|
@@ -15,6 +16,15 @@ export interface ReadinessCheckStatus {
|
|
|
15
16
|
|
|
16
17
|
export interface InstanceReadinessResult {
|
|
17
18
|
status: "ok" | "error";
|
|
19
|
+
/** The running build: the package version, then the build's commit id. */
|
|
20
|
+
version: string;
|
|
21
|
+
/**
|
|
22
|
+
* When the Node process started serving, in Unix seconds. With `version`,
|
|
23
|
+
* it lets a deploy check from outside that the instance answering is one
|
|
24
|
+
* started after the deploy, not an older one a rollback left running.
|
|
25
|
+
* Absent on Workers.
|
|
26
|
+
*/
|
|
27
|
+
startedAt?: number;
|
|
18
28
|
checks: {
|
|
19
29
|
startupConfig: ReadinessCheckStatus;
|
|
20
30
|
database: ReadinessCheckStatus;
|
|
@@ -117,6 +127,7 @@ export async function getInstanceReadiness(
|
|
|
117
127
|
| "INTERNAL_ADMIN_TOKEN"
|
|
118
128
|
| "NODE_DATABASE"
|
|
119
129
|
| "NODE_SQLITE"
|
|
130
|
+
| "NODE_STARTED_AT"
|
|
120
131
|
| "SITE_RESOLUTION_MODE"
|
|
121
132
|
>,
|
|
122
133
|
): Promise<InstanceReadinessResult> {
|
|
@@ -125,6 +136,10 @@ export async function getInstanceReadiness(
|
|
|
125
136
|
|
|
126
137
|
return {
|
|
127
138
|
status: startupConfig.ok && database.ok ? "ok" : "error",
|
|
139
|
+
version: CORE_VERSION,
|
|
140
|
+
...(env.NODE_STARTED_AT === undefined
|
|
141
|
+
? {}
|
|
142
|
+
: { startedAt: env.NODE_STARTED_AT }),
|
|
128
143
|
checks: {
|
|
129
144
|
startupConfig,
|
|
130
145
|
database,
|
package/src/runtime/site.ts
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
type SiteLookupResult,
|
|
18
18
|
TRANSIENT_SINGLE_SITE_ID,
|
|
19
19
|
} from "../services/site.js";
|
|
20
|
-
import { NotFoundError } from "../lib/errors.js";
|
|
20
|
+
import { NotFoundError, SiteUnavailableError } from "../lib/errors.js";
|
|
21
21
|
import type { Bindings } from "../types/bindings.js";
|
|
22
22
|
|
|
23
23
|
function logHostedSiteResolutionFailure(input: {
|
|
@@ -115,45 +115,116 @@ export async function resolveRequestSite(
|
|
|
115
115
|
siteKey: resolved.site.key,
|
|
116
116
|
siteStatus: resolved.site.status,
|
|
117
117
|
});
|
|
118
|
-
|
|
118
|
+
// The host resolved, so the site is real — it is just suspended (hosted
|
|
119
|
+
// plan ended, or an operator took it down). Serving a 404 here told the
|
|
120
|
+
// visitor the address was wrong and told crawlers to drop the site, both
|
|
121
|
+
// of which are false while it can still be restored.
|
|
122
|
+
throw new SiteUnavailableError();
|
|
119
123
|
}
|
|
120
124
|
return resolved;
|
|
121
125
|
}
|
|
122
126
|
|
|
123
|
-
|
|
127
|
+
/**
|
|
128
|
+
* The site a CLI command was told to act on. The `jant` CLI builds it from
|
|
129
|
+
* `--site <key|id>`, `--host <host>` with `--path-prefix`, or `--url <url>`
|
|
130
|
+
* (see `parseCliSiteSelector` in `bin/lib/site-selection.js`).
|
|
131
|
+
*/
|
|
132
|
+
export type CliSiteSelector =
|
|
133
|
+
| { kind: "site"; idOrKey: string }
|
|
134
|
+
| {
|
|
135
|
+
kind: "host";
|
|
136
|
+
host: string;
|
|
137
|
+
/** The domain's stored prefix (`/blog`), or null for a root domain. */
|
|
138
|
+
pathPrefix: string | null;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Printed when a host-based CLI command names no site. Kept word for word with
|
|
143
|
+
* the raw-SQL resolver the D1 path uses in `bin/lib/site-selection.js`.
|
|
144
|
+
*/
|
|
145
|
+
const HOST_BASED_CLI_SITE_REQUIRED_MESSAGE =
|
|
146
|
+
"host-based mode needs a target site. Pass --site <key|id>, --host <host>, or --url <url>.";
|
|
147
|
+
|
|
148
|
+
async function resolveSelectedCliSite(
|
|
124
149
|
db: Database,
|
|
125
|
-
|
|
126
|
-
databaseSchema: DatabaseSchema
|
|
150
|
+
selector: CliSiteSelector,
|
|
151
|
+
databaseSchema: DatabaseSchema,
|
|
127
152
|
): Promise<SiteLookupResult> {
|
|
128
153
|
const siteService = createSiteService(db, databaseSchema);
|
|
129
|
-
const resolutionMode = getSiteResolutionMode(env);
|
|
130
154
|
|
|
131
|
-
if (
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
if (resolved.site.id === TRANSIENT_SINGLE_SITE_ID) {
|
|
138
|
-
throw new Error(
|
|
139
|
-
"No site is configured for this instance yet. Finish /setup before running this command.",
|
|
140
|
-
);
|
|
155
|
+
if (selector.kind === "site") {
|
|
156
|
+
const site = await siteService.getByIdOrKey(selector.idOrKey);
|
|
157
|
+
if (!site) {
|
|
158
|
+
throw new Error(`No site found for --site ${selector.idOrKey}.`);
|
|
141
159
|
}
|
|
160
|
+
return {
|
|
161
|
+
site,
|
|
162
|
+
domain: await siteService.getPrimaryDomainForSite(site.id),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
142
165
|
|
|
143
|
-
|
|
166
|
+
const resolved = await siteService.resolveByHost(
|
|
167
|
+
selector.host,
|
|
168
|
+
selector.pathPrefix,
|
|
169
|
+
);
|
|
170
|
+
if (!resolved) {
|
|
171
|
+
throw new Error(
|
|
172
|
+
`No site found for host "${selector.host}"${selector.pathPrefix ? ` and path prefix "${selector.pathPrefix}"` : ""}.`,
|
|
173
|
+
);
|
|
144
174
|
}
|
|
175
|
+
return resolved;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Resolve the site a CLI command acts on.
|
|
180
|
+
*
|
|
181
|
+
* A selector wins in either mode. Without one, single-site mode uses the
|
|
182
|
+
* instance's one site, and host-based mode fails before touching the
|
|
183
|
+
* database: a hosted database holds every tenant, so there is no default to
|
|
184
|
+
* fall back to — not even when it happens to hold one site today.
|
|
185
|
+
*
|
|
186
|
+
* @param db - The database
|
|
187
|
+
* @param env - Bindings that carry `SITE_RESOLUTION_MODE` and the single-site
|
|
188
|
+
* origin
|
|
189
|
+
* @param selector - The site the command was told to act on, if any
|
|
190
|
+
* @param databaseSchema - The schema bundle for the database's dialect
|
|
191
|
+
* @returns The site and its domain
|
|
192
|
+
* @throws {Error} When the selector matches no site, when host-based mode has
|
|
193
|
+
* no selector, or when single-site mode has not finished setup
|
|
194
|
+
* @example
|
|
195
|
+
* ```ts
|
|
196
|
+
* await resolveCliSite(db, env, { kind: "site", idOrKey: "demo" });
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
199
|
+
export async function resolveCliSite(
|
|
200
|
+
db: Database,
|
|
201
|
+
env: Bindings,
|
|
202
|
+
selector: CliSiteSelector | null = null,
|
|
203
|
+
databaseSchema: DatabaseSchema = sqliteSchemaBundle,
|
|
204
|
+
): Promise<SiteLookupResult> {
|
|
205
|
+
if (selector) {
|
|
206
|
+
return resolveSelectedCliSite(db, selector, databaseSchema);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (getSiteResolutionMode(env) === "host-based") {
|
|
210
|
+
throw new Error(HOST_BASED_CLI_SITE_REQUIRED_MESSAGE);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const resolved = await createSiteService(
|
|
214
|
+
db,
|
|
215
|
+
databaseSchema,
|
|
216
|
+
).resolveSingleSite({
|
|
217
|
+
...getSingleSiteBootstrapOptions(env),
|
|
218
|
+
createIfMissing: false,
|
|
219
|
+
});
|
|
145
220
|
|
|
146
|
-
|
|
147
|
-
if (!onlySite) {
|
|
221
|
+
if (resolved.site.id === TRANSIENT_SINGLE_SITE_ID) {
|
|
148
222
|
throw new Error(
|
|
149
|
-
"
|
|
223
|
+
"No site is configured for this instance yet. Finish /setup before running this command.",
|
|
150
224
|
);
|
|
151
225
|
}
|
|
152
226
|
|
|
153
|
-
return
|
|
154
|
-
site: onlySite,
|
|
155
|
-
domain: null,
|
|
156
|
-
};
|
|
227
|
+
return resolved;
|
|
157
228
|
}
|
|
158
229
|
|
|
159
230
|
export function getResolvedSiteBaseUrl(
|