@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
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Snapshot Table Registry Guard
|
|
3
|
+
*
|
|
4
|
+
* `jant site snapshot export/import` walks two hand-written arrays in
|
|
5
|
+
* `bin/lib/site-snapshot.js`. Nothing connects them to `db/schema.ts`: `bin/`
|
|
6
|
+
* is plain JavaScript, outside the type system, so a new site-scoped table
|
|
7
|
+
* compiles, lints and tests clean while the snapshot silently skips it.
|
|
8
|
+
*
|
|
9
|
+
* That already happened. `smart_collection` arrived with migration 0033, and
|
|
10
|
+
* the commit that added it updated the schema, both dialects, five services, a
|
|
11
|
+
* route and three test files — but not these arrays. The damage was silent in
|
|
12
|
+
* both directions: export dropped smart collections, and `--replace` could not
|
|
13
|
+
* delete the ones a visitor created, so they outlived every nightly demo
|
|
14
|
+
* rebuild as rows with no slug and no nav entry.
|
|
15
|
+
*
|
|
16
|
+
* These tests close the gap. Every site-scoped table has to be named in one of
|
|
17
|
+
* the two registries, so a new table forces a decision instead of defaulting to
|
|
18
|
+
* "forgotten". The order assertions pin the arrays' other implicit contract:
|
|
19
|
+
* `SNAPSHOT_TABLES` is an INSERT order and `SNAPSHOT_CLEAR_TABLES` is a DELETE
|
|
20
|
+
* order, and both have to respect foreign keys — which are enforced, on D1 and
|
|
21
|
+
* on Node alike (`bin/lib/node-sqlite.js` sets `foreign_keys = ON`).
|
|
22
|
+
*
|
|
23
|
+
* Reading `db/schema.ts` alone covers both dialects: the registries are shared
|
|
24
|
+
* (only the dumped SQL is dialect-specific), and `db/pg/schema.ts` is required
|
|
25
|
+
* to declare the same tables.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { is } from "drizzle-orm";
|
|
29
|
+
import { SQLiteTable, getTableConfig } from "drizzle-orm/sqlite-core";
|
|
30
|
+
import { describe, expect, it } from "vitest";
|
|
31
|
+
import {
|
|
32
|
+
SNAPSHOT_CLEAR_TABLES,
|
|
33
|
+
SNAPSHOT_EXCLUDED_TABLES,
|
|
34
|
+
SNAPSHOT_TABLES,
|
|
35
|
+
getSnapshotSelectSql,
|
|
36
|
+
} from "../../bin/lib/site-snapshot.js";
|
|
37
|
+
import {
|
|
38
|
+
TABLE_EXPORT_ORDER,
|
|
39
|
+
sortExportTables,
|
|
40
|
+
} from "../../bin/lib/sql-export.js";
|
|
41
|
+
import * as schema from "../db/schema.js";
|
|
42
|
+
|
|
43
|
+
/** `site_setting` is cleared by key, not wholesale — see `buildReplaceSql`. */
|
|
44
|
+
const CLEARED_BY_KEY = "site_setting";
|
|
45
|
+
|
|
46
|
+
type SchemaTable = {
|
|
47
|
+
/** Physical table name, as it appears in SQL. */
|
|
48
|
+
name: string;
|
|
49
|
+
/** Tables this one points at with a foreign key, excluding self-references. */
|
|
50
|
+
dependsOn: string[];
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Reads every site-scoped table out of the Drizzle schema with its foreign-key
|
|
55
|
+
* dependencies. "Site-scoped" means the table carries a `site_id` column, which
|
|
56
|
+
* is what makes a row belong to one site's content rather than the instance.
|
|
57
|
+
*
|
|
58
|
+
* @returns One entry per site-scoped table, in declaration order.
|
|
59
|
+
*/
|
|
60
|
+
function readSiteScopedTables(): SchemaTable[] {
|
|
61
|
+
const tables: SchemaTable[] = [];
|
|
62
|
+
|
|
63
|
+
for (const exported of Object.values(schema)) {
|
|
64
|
+
if (!is(exported, SQLiteTable)) continue;
|
|
65
|
+
const config = getTableConfig(exported);
|
|
66
|
+
if (!config.columns.some((column) => column.name === "site_id")) continue;
|
|
67
|
+
|
|
68
|
+
const dependsOn = [
|
|
69
|
+
...new Set(
|
|
70
|
+
config.foreignKeys.map(
|
|
71
|
+
(foreignKey) =>
|
|
72
|
+
getTableConfig(foreignKey.reference().foreignTable).name,
|
|
73
|
+
),
|
|
74
|
+
),
|
|
75
|
+
].filter((target) => target !== config.name);
|
|
76
|
+
|
|
77
|
+
tables.push({ name: config.name, dependsOn });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return tables;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const siteScopedTables = readSiteScopedTables();
|
|
84
|
+
const siteScopedNames = siteScopedTables.map((table) => table.name);
|
|
85
|
+
|
|
86
|
+
describe("snapshot table registry", () => {
|
|
87
|
+
it("covers every site-scoped table as content or an explicit exclusion", () => {
|
|
88
|
+
const registered = new Set<string>([
|
|
89
|
+
...SNAPSHOT_TABLES,
|
|
90
|
+
...SNAPSHOT_EXCLUDED_TABLES,
|
|
91
|
+
]);
|
|
92
|
+
const unregistered = siteScopedNames.filter(
|
|
93
|
+
(name) => !registered.has(name),
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
expect(
|
|
97
|
+
unregistered,
|
|
98
|
+
[
|
|
99
|
+
"These site-scoped tables are in db/schema.ts but in neither snapshot registry.",
|
|
100
|
+
"A table the snapshot does not know about is dropped on export and survives",
|
|
101
|
+
"`--replace` on import. Add it to SNAPSHOT_TABLES if it is site content, or to",
|
|
102
|
+
"SNAPSHOT_EXCLUDED_TABLES with the reason if it is shell, credential or",
|
|
103
|
+
"transient state, in bin/lib/site-snapshot.js.",
|
|
104
|
+
].join("\n"),
|
|
105
|
+
).toEqual([]);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("does not register a table as both content and excluded", () => {
|
|
109
|
+
const excluded = new Set<string>(SNAPSHOT_EXCLUDED_TABLES);
|
|
110
|
+
const both = SNAPSHOT_TABLES.filter((name) => excluded.has(name));
|
|
111
|
+
|
|
112
|
+
expect(both).toEqual([]);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("can build an export query for every content table", () => {
|
|
116
|
+
// `getSnapshotSelectSql` has no default branch: a table listed as content
|
|
117
|
+
// without a matching statement throws "Unsupported snapshot table" at
|
|
118
|
+
// export time, which is the same silent-until-used failure the registry
|
|
119
|
+
// itself had.
|
|
120
|
+
for (const table of SNAPSHOT_TABLES) {
|
|
121
|
+
expect(() =>
|
|
122
|
+
getSnapshotSelectSql(table, "sit_00000000000000000000000000"),
|
|
123
|
+
).not.toThrow();
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("does not keep registry entries for tables the schema dropped", () => {
|
|
128
|
+
const known = new Set(siteScopedNames);
|
|
129
|
+
const stale = [...SNAPSHOT_TABLES, ...SNAPSHOT_EXCLUDED_TABLES].filter(
|
|
130
|
+
(name) => !known.has(name),
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
expect(stale).toEqual([]);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe("snapshot table ordering", () => {
|
|
138
|
+
it("registers every content table for export ordering", () => {
|
|
139
|
+
// A table missing here is not merely unordered: `sortExportTables` places
|
|
140
|
+
// unknown names after every known one, so a new child table is dumped
|
|
141
|
+
// behind its own parents and the import fails the first time a row exists
|
|
142
|
+
// to violate the constraint.
|
|
143
|
+
const ordered = new Set<string>(TABLE_EXPORT_ORDER);
|
|
144
|
+
expect(SNAPSHOT_TABLES.filter((name) => !ordered.has(name))).toEqual([]);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("inserts a table only after every table it references", () => {
|
|
148
|
+
// The order an export actually follows. `SNAPSHOT_TABLES` is re-sorted by
|
|
149
|
+
// `TABLE_EXPORT_ORDER` on the way out, so asserting on the raw list would
|
|
150
|
+
// check something the dump ignores.
|
|
151
|
+
const position = new Map(
|
|
152
|
+
sortExportTables(SNAPSHOT_TABLES).map((name, index) => [name, index]),
|
|
153
|
+
);
|
|
154
|
+
const violations: string[] = [];
|
|
155
|
+
|
|
156
|
+
for (const table of siteScopedTables) {
|
|
157
|
+
const self = position.get(table.name);
|
|
158
|
+
if (self === undefined) continue;
|
|
159
|
+
|
|
160
|
+
for (const dependency of table.dependsOn) {
|
|
161
|
+
const parent = position.get(dependency);
|
|
162
|
+
if (parent === undefined) continue;
|
|
163
|
+
if (parent > self) {
|
|
164
|
+
violations.push(
|
|
165
|
+
`${table.name} (${self}) references ${dependency} (${parent})`,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
expect(
|
|
172
|
+
violations,
|
|
173
|
+
[
|
|
174
|
+
"TABLE_EXPORT_ORDER in sql-export.js is the order rows are dumped, and so the",
|
|
175
|
+
"order they are inserted on import. A table has to come after everything it",
|
|
176
|
+
"points at with a foreign key, or the child row lands before its parent exists",
|
|
177
|
+
"and the insert fails the constraint.",
|
|
178
|
+
].join("\n"),
|
|
179
|
+
).toEqual([]);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("deletes a table before every table it references", () => {
|
|
183
|
+
const position = new Map(
|
|
184
|
+
SNAPSHOT_CLEAR_TABLES.map((name, index) => [name, index]),
|
|
185
|
+
);
|
|
186
|
+
const violations: string[] = [];
|
|
187
|
+
|
|
188
|
+
for (const table of siteScopedTables) {
|
|
189
|
+
const self = position.get(table.name);
|
|
190
|
+
if (self === undefined) continue;
|
|
191
|
+
|
|
192
|
+
for (const dependency of table.dependsOn) {
|
|
193
|
+
const parent = position.get(dependency);
|
|
194
|
+
if (parent === undefined) continue;
|
|
195
|
+
if (parent < self) {
|
|
196
|
+
violations.push(
|
|
197
|
+
`${table.name} (${self}) references ${dependency} (${parent})`,
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
expect(
|
|
204
|
+
violations,
|
|
205
|
+
[
|
|
206
|
+
"SNAPSHOT_CLEAR_TABLES is the order rows are deleted by `--replace`, so a table",
|
|
207
|
+
"has to come before everything it points at. Deleting the parent first only",
|
|
208
|
+
"appears to work because the cascade takes the children with it — which is",
|
|
209
|
+
"implicit, and stops being true the moment a reference is not ON DELETE CASCADE.",
|
|
210
|
+
].join("\n"),
|
|
211
|
+
).toEqual([]);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("clears every content table except the one cleared by key", () => {
|
|
215
|
+
expect([...SNAPSHOT_CLEAR_TABLES].sort()).toEqual(
|
|
216
|
+
SNAPSHOT_TABLES.filter((name) => name !== CLEARED_BY_KEY).sort(),
|
|
217
|
+
);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The SQL dump behind `db export` and `site snapshot export`.
|
|
3
|
+
*
|
|
4
|
+
* On D1 every query goes through `wrangler d1 execute --json`, whose output
|
|
5
|
+
* the CLI buffers. One `SELECT *` of a real site's `post` table overflowed
|
|
6
|
+
* that buffer, so the dump reads D1 a page at a time.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, expect, it } from "vitest";
|
|
10
|
+
import Database from "better-sqlite3";
|
|
11
|
+
import { dumpDatabaseToSql } from "../../bin/lib/sql-export.js";
|
|
12
|
+
import { orderSnapshotPostRows } from "../../bin/lib/site-snapshot.js";
|
|
13
|
+
import { describeWranglerFailure } from "../../bin/lib/d1-query.js";
|
|
14
|
+
import { WRANGLER_MAX_BUFFER } from "../../bin/lib/wrangler-cli.js";
|
|
15
|
+
|
|
16
|
+
function createQueryRunner() {
|
|
17
|
+
const db = new Database(":memory:");
|
|
18
|
+
db.exec(`
|
|
19
|
+
CREATE TABLE "post" ("id" TEXT PRIMARY KEY, "site_id" TEXT, "body" TEXT);
|
|
20
|
+
`);
|
|
21
|
+
const insert = db.prepare(
|
|
22
|
+
`INSERT INTO "post" ("id", "site_id", "body") VALUES (?, ?, ?)`,
|
|
23
|
+
);
|
|
24
|
+
for (let index = 0; index < 7; index += 1) {
|
|
25
|
+
insert.run(`pst_${index}`, "site", `body ${index}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const statements: string[] = [];
|
|
29
|
+
return {
|
|
30
|
+
statements,
|
|
31
|
+
runner: {
|
|
32
|
+
async query(sql: string) {
|
|
33
|
+
statements.push(sql);
|
|
34
|
+
return db.prepare(sql).all() as Record<string, unknown>[];
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
describe("dumpDatabaseToSql", () => {
|
|
41
|
+
it("reads a table in pages when given a page size", async () => {
|
|
42
|
+
const { runner, statements } = createQueryRunner();
|
|
43
|
+
|
|
44
|
+
const sql = await dumpDatabaseToSql(runner, {
|
|
45
|
+
source: "local",
|
|
46
|
+
dialect: "sqlite",
|
|
47
|
+
tables: ["post"],
|
|
48
|
+
pageSize: 3,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const reads = statements.filter((statement) =>
|
|
52
|
+
statement.includes('FROM "post"'),
|
|
53
|
+
);
|
|
54
|
+
expect(reads).toEqual([
|
|
55
|
+
'SELECT * FROM "post" ORDER BY rowid LIMIT 3 OFFSET 0',
|
|
56
|
+
'SELECT * FROM "post" ORDER BY rowid LIMIT 3 OFFSET 3',
|
|
57
|
+
'SELECT * FROM "post" ORDER BY rowid LIMIT 3 OFFSET 6',
|
|
58
|
+
]);
|
|
59
|
+
for (let index = 0; index < 7; index += 1) {
|
|
60
|
+
expect(sql).toContain(`'pst_${index}'`);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("pages a table's own ordered select", async () => {
|
|
65
|
+
const { runner, statements } = createQueryRunner();
|
|
66
|
+
|
|
67
|
+
const sql = await dumpDatabaseToSql(runner, {
|
|
68
|
+
source: "local",
|
|
69
|
+
dialect: "sqlite",
|
|
70
|
+
tables: ["post"],
|
|
71
|
+
pageSize: 5,
|
|
72
|
+
selectSqlByTable: {
|
|
73
|
+
post: `SELECT * FROM "post" WHERE "site_id" = 'site' ORDER BY "id"\n`,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
expect(
|
|
78
|
+
statements.filter((statement) => statement.includes('FROM "post"')),
|
|
79
|
+
).toEqual([
|
|
80
|
+
`SELECT * FROM "post" WHERE "site_id" = 'site' ORDER BY "id" LIMIT 5 OFFSET 0`,
|
|
81
|
+
`SELECT * FROM "post" WHERE "site_id" = 'site' ORDER BY "id" LIMIT 5 OFFSET 5`,
|
|
82
|
+
]);
|
|
83
|
+
expect(sql.match(/INSERT INTO "post"/g)).toHaveLength(7);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("reads a table in one query without a page size", async () => {
|
|
87
|
+
const { runner, statements } = createQueryRunner();
|
|
88
|
+
|
|
89
|
+
await dumpDatabaseToSql(runner, {
|
|
90
|
+
source: "node",
|
|
91
|
+
dialect: "sqlite",
|
|
92
|
+
tables: ["post"],
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
expect(
|
|
96
|
+
statements.filter((statement) => statement.includes('FROM "post"')),
|
|
97
|
+
).toEqual(['SELECT * FROM "post"']);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe("describeWranglerFailure", () => {
|
|
102
|
+
it("names the buffer overflow instead of echoing the rows", () => {
|
|
103
|
+
const error = Object.assign(new Error("spawnSync node ENOBUFS"), {
|
|
104
|
+
code: "ENOBUFS",
|
|
105
|
+
stdout: '[{"results":[' + '{"id":"pst_x","body":"…"},'.repeat(50_000),
|
|
106
|
+
stderr: "",
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const message = describeWranglerFailure(
|
|
110
|
+
["d1", "execute", "DB", "--local", "--json"],
|
|
111
|
+
error,
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
expect(message).toBe(
|
|
115
|
+
`\`wrangler d1 execute DB\` printed more than ${WRANGLER_MAX_BUFFER} bytes, the most the CLI buffers.`,
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("keeps a long failure's stdout to an excerpt", () => {
|
|
120
|
+
const message = describeWranglerFailure(["d1", "execute", "DB"], {
|
|
121
|
+
name: "Error",
|
|
122
|
+
message: "Command failed",
|
|
123
|
+
stdout: "x".repeat(10_000),
|
|
124
|
+
stderr: "",
|
|
125
|
+
} as Error & { stdout: string; stderr: string });
|
|
126
|
+
|
|
127
|
+
expect(message.length).toBeLessThan(2_200);
|
|
128
|
+
expect(message).toContain("(10000 characters)");
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe("snapshot post order", () => {
|
|
133
|
+
it("puts every post after its root and the post it replies to", () => {
|
|
134
|
+
const rows = [
|
|
135
|
+
{ id: "reply-2", thread_id: "root", reply_to_id: "reply-1" },
|
|
136
|
+
{ id: "reply-1", thread_id: "root", reply_to_id: "root" },
|
|
137
|
+
{ id: "other", thread_id: "other", reply_to_id: null },
|
|
138
|
+
{ id: "root", thread_id: "root", reply_to_id: null },
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
expect(orderSnapshotPostRows(rows).map((row) => row.id)).toEqual([
|
|
142
|
+
"root",
|
|
143
|
+
"reply-1",
|
|
144
|
+
"reply-2",
|
|
145
|
+
"other",
|
|
146
|
+
]);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("keeps the dump order when parents already come first", () => {
|
|
150
|
+
const rows = [
|
|
151
|
+
{ id: "a", thread_id: "a", reply_to_id: null },
|
|
152
|
+
{ id: "b", thread_id: "a", reply_to_id: "a" },
|
|
153
|
+
{ id: "c", thread_id: "c", reply_to_id: null },
|
|
154
|
+
];
|
|
155
|
+
|
|
156
|
+
expect(orderSnapshotPostRows(rows)).toEqual(rows);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("applies a table's row order to the dump", async () => {
|
|
160
|
+
const { runner } = createQueryRunner();
|
|
161
|
+
|
|
162
|
+
const sql = await dumpDatabaseToSql(runner, {
|
|
163
|
+
source: "node",
|
|
164
|
+
dialect: "sqlite",
|
|
165
|
+
tables: ["post"],
|
|
166
|
+
orderRowsByTable: {
|
|
167
|
+
post: (rows: Record<string, unknown>[]) => [...rows].reverse(),
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
expect(sql.indexOf("'pst_6'")).toBeLessThan(sql.indexOf("'pst_0'"));
|
|
172
|
+
});
|
|
173
|
+
});
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
readAuthorCss,
|
|
4
|
+
readComponentCss,
|
|
5
|
+
readReaderCss,
|
|
6
|
+
} from "./helpers/component-css.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* `client.css` is what a signed-out visitor waits on before the page paints,
|
|
10
|
+
* and `styles/ui.css` plus `styles/components.css` are the hand-written half
|
|
11
|
+
* of it. Author-only component CSS belongs in `styles/ui-author.css` and
|
|
12
|
+
* `styles/components-author.css`, which `BaseLayout` links only on
|
|
13
|
+
* authenticated pages.
|
|
14
|
+
*
|
|
15
|
+
* Class families that only ever appear in authenticated markup. The editor's
|
|
16
|
+
* `.tiptap-*` chrome is here too, minus the names TipTap also writes into
|
|
17
|
+
* published post HTML — readers see those.
|
|
18
|
+
*/
|
|
19
|
+
const AUTHOR_ONLY = [
|
|
20
|
+
"compose",
|
|
21
|
+
"command",
|
|
22
|
+
"settings",
|
|
23
|
+
"picker",
|
|
24
|
+
"confirm",
|
|
25
|
+
"draft",
|
|
26
|
+
"theme-preview",
|
|
27
|
+
// The floating compose button on phones, and the page modifier that keeps
|
|
28
|
+
// room for it. Reader layout renders both, but only for a signed-in author.
|
|
29
|
+
"site-mobile-compose",
|
|
30
|
+
"site-page-mobile-compose-enabled",
|
|
31
|
+
// From `components.css`: the config editor, the navigation manager, the
|
|
32
|
+
// custom URL manager, the dash chrome and the form skeletons. `.nav-*` here
|
|
33
|
+
// is `jant-nav-manager`, not the site header — that is `.site-header-nav`.
|
|
34
|
+
// `.collection-picker-*` is spelled out because plain `.collection-*` is a
|
|
35
|
+
// reader family (the collection pages).
|
|
36
|
+
"config",
|
|
37
|
+
"nav",
|
|
38
|
+
"custom",
|
|
39
|
+
"dash",
|
|
40
|
+
"skel",
|
|
41
|
+
"collection-picker",
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
/** TipTap class names that land in reader-rendered post HTML. */
|
|
45
|
+
const READER_TIPTAP = [
|
|
46
|
+
"tiptap-table",
|
|
47
|
+
"tiptap-html-block",
|
|
48
|
+
"tiptap-more-break",
|
|
49
|
+
"tiptap-embed-figure",
|
|
50
|
+
"tiptap-embed-frame",
|
|
51
|
+
"tiptap-embed-fallback",
|
|
52
|
+
"tiptap-footnote-reference",
|
|
53
|
+
"tiptap-footnote-definition",
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* `.compose-thread-layout` shares the compose rail's marker vocabulary with
|
|
58
|
+
* the reading rail's `.thread-group` in one declaration, so that the two
|
|
59
|
+
* cannot drift apart. That single grouped selector is the documented
|
|
60
|
+
* exception; see the comment on the rule in `styles/ui.css`.
|
|
61
|
+
*/
|
|
62
|
+
const READER_SHEET_EXCEPTIONS = [".compose-thread-layout"];
|
|
63
|
+
|
|
64
|
+
/** Every class named in a selector, ignoring declaration bodies. */
|
|
65
|
+
function selectorClasses(css: string): string[] {
|
|
66
|
+
const found: string[] = [];
|
|
67
|
+
// Selectors are what precedes a `{` that opens a block; declarations end in
|
|
68
|
+
// `;`, so anything after the last `;` or `}` is the selector text.
|
|
69
|
+
for (const chunk of css.split("{")) {
|
|
70
|
+
const selector = chunk.slice(
|
|
71
|
+
Math.max(chunk.lastIndexOf(";"), chunk.lastIndexOf("}")) + 1,
|
|
72
|
+
);
|
|
73
|
+
const withoutComments = selector.replace(/\/\*[\s\S]*?\*\//g, " ");
|
|
74
|
+
for (const match of withoutComments.matchAll(
|
|
75
|
+
/\.(-?[A-Za-z_][A-Za-z0-9_-]*)/g,
|
|
76
|
+
)) {
|
|
77
|
+
found.push(match[1]!);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return found;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
describe("stylesheet audience", () => {
|
|
84
|
+
it("keeps author-only component CSS out of the reader stylesheet", () => {
|
|
85
|
+
const css = readReaderCss();
|
|
86
|
+
const exceptions = new Set(
|
|
87
|
+
READER_SHEET_EXCEPTIONS.map((name) => name.replace(/^\./, "")),
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const leaked = [
|
|
91
|
+
...new Set(
|
|
92
|
+
selectorClasses(css).filter(
|
|
93
|
+
(name) =>
|
|
94
|
+
!exceptions.has(name) &&
|
|
95
|
+
AUTHOR_ONLY.some(
|
|
96
|
+
(family) => name === family || name.startsWith(`${family}-`),
|
|
97
|
+
),
|
|
98
|
+
),
|
|
99
|
+
),
|
|
100
|
+
].sort();
|
|
101
|
+
|
|
102
|
+
expect(leaked).toEqual([]);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("keeps the editor chrome out of the reader stylesheet", () => {
|
|
106
|
+
const readerTiptap = new Set(READER_TIPTAP);
|
|
107
|
+
const leaked = [
|
|
108
|
+
...new Set(
|
|
109
|
+
selectorClasses(readReaderCss()).filter(
|
|
110
|
+
(name) =>
|
|
111
|
+
(name === "tiptap" || name.startsWith("tiptap-")) &&
|
|
112
|
+
!readerTiptap.has(name),
|
|
113
|
+
),
|
|
114
|
+
),
|
|
115
|
+
].sort();
|
|
116
|
+
|
|
117
|
+
expect(leaked).toEqual([]);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("still styles the TipTap output a published post carries", () => {
|
|
121
|
+
// `lib/embed-render.ts` and `lib/markdown-manager.ts` write these into post
|
|
122
|
+
// HTML, and they are the ones with rules of their own — a reader holding
|
|
123
|
+
// only `client.css` has to be able to style them. The rest of
|
|
124
|
+
// READER_TIPTAP reaches published posts too but is styled by element
|
|
125
|
+
// through `.prose`, so there is no rule here to keep.
|
|
126
|
+
const css = readReaderCss();
|
|
127
|
+
for (const name of [
|
|
128
|
+
"tiptap-embed-figure",
|
|
129
|
+
"tiptap-embed-frame",
|
|
130
|
+
"tiptap-embed-fallback",
|
|
131
|
+
"tiptap-more-break",
|
|
132
|
+
]) {
|
|
133
|
+
expect(selectorClasses(css)).toContain(name);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("moved real weight, not a handful of rules", () => {
|
|
138
|
+
// The split is only worth its complexity while the author half is
|
|
139
|
+
// substantial. If it ever shrinks to a rounding error, fold it back in.
|
|
140
|
+
expect(readAuthorCss().length).toBeGreaterThan(readReaderCss().length / 2);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("keeps the author half of components.css out of the reader sheet", () => {
|
|
144
|
+
// `preset.css` imports `components.css` on every page, so the settings
|
|
145
|
+
// pages and the config editor must not be left in it.
|
|
146
|
+
const author = readAuthorCss();
|
|
147
|
+
expect(author).toContain(".settings-root");
|
|
148
|
+
expect(author).toContain(".config-editor-page");
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("leaves the reader-side families of components.css in place", () => {
|
|
152
|
+
// Verified against every `class=` usage in the tree: `BaseLayout` renders
|
|
153
|
+
// the toast container on every page, and `.page-intro-*` is the header of
|
|
154
|
+
// the archive and collections pages, both public.
|
|
155
|
+
const reader = readReaderCss();
|
|
156
|
+
expect(reader).toContain(".toast-container");
|
|
157
|
+
expect(reader).toContain(".page-intro");
|
|
158
|
+
});
|
|
159
|
+
});
|