@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
package/src/client-site.ts
CHANGED
|
@@ -2,14 +2,21 @@
|
|
|
2
2
|
* Static site client entry point (Hugo theme).
|
|
3
3
|
*
|
|
4
4
|
* Loaded on exported Hugo sites to enable reading-surface interactions —
|
|
5
|
-
* media lightbox, feed video autoplay, audio waveform,
|
|
6
|
-
*
|
|
7
|
-
* are runtime-only concerns for the authenticated app.
|
|
5
|
+
* media lightbox, feed video autoplay, audio waveform, gallery scroll hints,
|
|
6
|
+
* and the thread-context shell. Does NOT include Datastar, auth, toast, or
|
|
7
|
+
* form plumbing; those are runtime-only concerns for the authenticated app.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import "./client/audio-player.js";
|
|
11
11
|
import "./client/feed-video-player.js";
|
|
12
12
|
import "./client/media-scroll-hint.js";
|
|
13
13
|
import "./client/site-header-nav.js";
|
|
14
|
+
// A thread preview's ancestor context is server-rendered collapsed, and its
|
|
15
|
+
// "Show more" is the only way back out of that. Off-site pages render the same
|
|
16
|
+
// shell — the Hugo export's own thread-preview partial had to leave the shell
|
|
17
|
+
// out entirely because this was missing — so the reading surface owes them the
|
|
18
|
+
// script that makes it work. It binds on DOMContentLoaded to
|
|
19
|
+
// `[data-thread-context-toggle]` and does nothing on a page carrying none.
|
|
20
|
+
import "./client/thread-context.js";
|
|
14
21
|
import "./client/components/jant-media-lightbox.js";
|
|
15
22
|
import "./styles/site-media.css";
|
package/src/client.ts
CHANGED
|
@@ -11,8 +11,12 @@ import "./client/audio-player.js";
|
|
|
11
11
|
import "./client/feed-video-player.js";
|
|
12
12
|
import "./client/media-scroll-hint.js";
|
|
13
13
|
import "./client/form-enter-submit.js";
|
|
14
|
+
import "./client/copy-field.js";
|
|
14
15
|
import "./client/components/jant-media-lightbox.js";
|
|
15
16
|
import "./client/components/jant-text-preview.js";
|
|
17
|
+
// First-run setup is a signed-out page, so the language picker it shares with
|
|
18
|
+
// the settings page has to ship in the public bundle.
|
|
19
|
+
import "./client/components/jant-locale-picker.js";
|
|
16
20
|
import "./client/toast.js";
|
|
17
21
|
import "./client/thread-context.js";
|
|
18
22
|
import "./client/note-expand.js";
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Executes the real 0006 backfill SQL against snapshot-shaped nav rows.
|
|
3
|
+
*
|
|
4
|
+
* The backfill runs on user data through `jant migrate`, and the same file is
|
|
5
|
+
* executed on both SQLite and Postgres, so it has to be portable and safe to
|
|
6
|
+
* rerun. These tests cover the SQLite side; the SQL avoids dialect-specific
|
|
7
|
+
* syntax (no `UPDATE ... FROM`, only correlated subqueries).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { readFileSync } from "fs";
|
|
11
|
+
import { resolve } from "path";
|
|
12
|
+
import type BetterSqlite3 from "better-sqlite3";
|
|
13
|
+
import { beforeEach, describe, expect, it } from "vitest";
|
|
14
|
+
import {
|
|
15
|
+
createTestDatabase,
|
|
16
|
+
DEFAULT_TEST_SITE_ID,
|
|
17
|
+
} from "../../__tests__/helpers/db.js";
|
|
18
|
+
|
|
19
|
+
const BACKFILL_SQL = readFileSync(
|
|
20
|
+
resolve(
|
|
21
|
+
import.meta.dirname,
|
|
22
|
+
"../backfills/0006_clear_mirrored_page_nav_labels.sql",
|
|
23
|
+
),
|
|
24
|
+
"utf-8",
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const POST_ID = "pst_backfill00000000000000001";
|
|
28
|
+
const COLLECTION_ID = "col_backfill00000000000000001";
|
|
29
|
+
|
|
30
|
+
describe("0006 backfill — clear nav labels that mirror their target", () => {
|
|
31
|
+
let sqlite: BetterSqlite3.Database;
|
|
32
|
+
|
|
33
|
+
function runBackfill() {
|
|
34
|
+
sqlite.exec(BACKFILL_SQL);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function labelOf(id: string): string {
|
|
38
|
+
const row = sqlite
|
|
39
|
+
.prepare(`SELECT label FROM nav_item WHERE id = ?`)
|
|
40
|
+
.get(id) as { label: string };
|
|
41
|
+
return row.label;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function insertNavItem(row: {
|
|
45
|
+
id: string;
|
|
46
|
+
type: "page" | "collection" | "link";
|
|
47
|
+
label: string;
|
|
48
|
+
position?: string;
|
|
49
|
+
postId?: string;
|
|
50
|
+
collectionId?: string;
|
|
51
|
+
}) {
|
|
52
|
+
sqlite
|
|
53
|
+
.prepare(
|
|
54
|
+
`INSERT INTO nav_item (
|
|
55
|
+
id, site_id, type, post_id, collection_id, label, url,
|
|
56
|
+
placement, position, created_at, updated_at
|
|
57
|
+
) VALUES (?, ?, ?, ?, ?, ?, '/x', 'header', ?, 1, 1)`,
|
|
58
|
+
)
|
|
59
|
+
.run(
|
|
60
|
+
row.id,
|
|
61
|
+
DEFAULT_TEST_SITE_ID,
|
|
62
|
+
row.type,
|
|
63
|
+
row.postId ?? null,
|
|
64
|
+
row.collectionId ?? null,
|
|
65
|
+
row.label,
|
|
66
|
+
row.position ?? "a0",
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
beforeEach(() => {
|
|
71
|
+
sqlite = createTestDatabase().sqlite;
|
|
72
|
+
sqlite
|
|
73
|
+
.prepare(
|
|
74
|
+
`INSERT INTO post (
|
|
75
|
+
id, site_id, format, status, visibility, title, thread_id,
|
|
76
|
+
published_at, last_activity_at, created_at, updated_at
|
|
77
|
+
) VALUES (?, ?, 'note', 'published', 'latest_hidden', 'About me', ?, 1, 1, 1, 1)`,
|
|
78
|
+
)
|
|
79
|
+
.run(POST_ID, DEFAULT_TEST_SITE_ID, POST_ID);
|
|
80
|
+
sqlite
|
|
81
|
+
.prepare(
|
|
82
|
+
`INSERT INTO collection (id, site_id, title, sort_order, created_at, updated_at)
|
|
83
|
+
VALUES (?, ?, 'Design Notes', 'newest', 1, 1)`,
|
|
84
|
+
)
|
|
85
|
+
.run(COLLECTION_ID, DEFAULT_TEST_SITE_ID);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("clears a page label that only mirrors the post title", () => {
|
|
89
|
+
insertNavItem({
|
|
90
|
+
id: "nav_mirror0000000000000000001",
|
|
91
|
+
type: "page",
|
|
92
|
+
label: "About me",
|
|
93
|
+
postId: POST_ID,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
runBackfill();
|
|
97
|
+
|
|
98
|
+
expect(labelOf("nav_mirror0000000000000000001")).toBe("");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("clears a collection label that only mirrors the collection title", () => {
|
|
102
|
+
insertNavItem({
|
|
103
|
+
id: "nav_mirror0000000000000000002",
|
|
104
|
+
type: "collection",
|
|
105
|
+
label: "Design Notes",
|
|
106
|
+
collectionId: COLLECTION_ID,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
runBackfill();
|
|
110
|
+
|
|
111
|
+
expect(labelOf("nav_mirror0000000000000000002")).toBe("");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("keeps a label the author typed", () => {
|
|
115
|
+
insertNavItem({
|
|
116
|
+
id: "nav_custom0000000000000000001",
|
|
117
|
+
type: "page",
|
|
118
|
+
label: "Colophon",
|
|
119
|
+
postId: POST_ID,
|
|
120
|
+
});
|
|
121
|
+
insertNavItem({
|
|
122
|
+
id: "nav_custom0000000000000000002",
|
|
123
|
+
type: "link",
|
|
124
|
+
label: "About me",
|
|
125
|
+
position: "a1",
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
runBackfill();
|
|
129
|
+
|
|
130
|
+
expect(labelOf("nav_custom0000000000000000001")).toBe("Colophon");
|
|
131
|
+
// A free-form link has no target to follow, so its label is never cleared
|
|
132
|
+
// even when it happens to read like one.
|
|
133
|
+
expect(labelOf("nav_custom0000000000000000002")).toBe("About me");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("is safe to rerun", () => {
|
|
137
|
+
insertNavItem({
|
|
138
|
+
id: "nav_mirror0000000000000000003",
|
|
139
|
+
type: "page",
|
|
140
|
+
label: "About me",
|
|
141
|
+
postId: POST_ID,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
runBackfill();
|
|
145
|
+
runBackfill();
|
|
146
|
+
|
|
147
|
+
expect(labelOf("nav_mirror0000000000000000003")).toBe("");
|
|
148
|
+
});
|
|
149
|
+
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
1
2
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
3
|
|
|
3
4
|
const { runLocalWranglerMock } = vi.hoisted(() => ({
|
|
@@ -8,7 +9,7 @@ vi.mock("../../../bin/lib/wrangler-cli.js", () => ({
|
|
|
8
9
|
runLocalWrangler: runLocalWranglerMock,
|
|
9
10
|
}));
|
|
10
11
|
|
|
11
|
-
const { executeD1, parseWranglerError, queryD1 } =
|
|
12
|
+
const { exceedsInlineCommandLimit, executeD1, parseWranglerError, queryD1 } =
|
|
12
13
|
await import("../../../bin/lib/d1-query.js");
|
|
13
14
|
|
|
14
15
|
function createWranglerError(stderr) {
|
|
@@ -124,3 +125,57 @@ describe("d1-query Wrangler error parsing", () => {
|
|
|
124
125
|
);
|
|
125
126
|
});
|
|
126
127
|
});
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Oversized statement batches.
|
|
131
|
+
*
|
|
132
|
+
* A batch travels to Wrangler as one `--command=` argument, and Linux caps a
|
|
133
|
+
* single argv entry at 128 KiB (`MAX_ARG_STRLEN`). macOS has no such cap, so
|
|
134
|
+
* the first snapshot whose `db.sql` grew past it imported fine on a Mac and
|
|
135
|
+
* failed CI with `E2BIG`. Anything that large has to go through `--file`.
|
|
136
|
+
*/
|
|
137
|
+
describe("d1-query oversized batches", () => {
|
|
138
|
+
beforeEach(() => {
|
|
139
|
+
runLocalWranglerMock.mockReset();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("sends a small batch inline", () => {
|
|
143
|
+
runLocalWranglerMock.mockReturnValue(JSON.stringify([{ success: true }]));
|
|
144
|
+
|
|
145
|
+
executeD1("SELECT 1;", "d1-local", { quiet: true, retryAttempts: 1 });
|
|
146
|
+
|
|
147
|
+
expect(runLocalWranglerMock).toHaveBeenCalledWith(
|
|
148
|
+
expect.arrayContaining(["--command=SELECT 1;"]),
|
|
149
|
+
expect.anything(),
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("writes a batch too large for one argument to a file", () => {
|
|
154
|
+
const sql = `${"INSERT INTO t VALUES ('x');".repeat(8000)}\n`;
|
|
155
|
+
expect(exceedsInlineCommandLimit(sql)).toBe(true);
|
|
156
|
+
|
|
157
|
+
let seenPath: string | undefined;
|
|
158
|
+
let seenContents: string | undefined;
|
|
159
|
+
runLocalWranglerMock.mockImplementation((args: string[]) => {
|
|
160
|
+
const index = args.indexOf("--file");
|
|
161
|
+
seenPath = args[index + 1];
|
|
162
|
+
seenContents = readFileSync(seenPath as string, "utf8");
|
|
163
|
+
return JSON.stringify([{ success: true }]);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
executeD1(sql, "d1-local", { quiet: true, retryAttempts: 1 });
|
|
167
|
+
|
|
168
|
+
const args = runLocalWranglerMock.mock.calls[0]?.[0] as string[];
|
|
169
|
+
expect(args).toContain("--file");
|
|
170
|
+
expect(args.some((arg) => arg.startsWith("--command="))).toBe(false);
|
|
171
|
+
expect(seenContents).toBe(sql);
|
|
172
|
+
// The temp file is Wrangler's to read and ours to clean up.
|
|
173
|
+
expect(existsSync(seenPath as string)).toBe(false);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("keeps the threshold under the Linux single-argument cap", () => {
|
|
177
|
+
const MAX_ARG_STRLEN = 128 * 1024;
|
|
178
|
+
const justUnder = "a".repeat(MAX_ARG_STRLEN - 1);
|
|
179
|
+
expect(exceedsInlineCommandLimit(justUnder)).toBe(true);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The schema files must not keep private copies of the value lists that
|
|
7
|
+
* `types/constants.ts` already owns.
|
|
8
|
+
*
|
|
9
|
+
* This is not a style rule. A CHECK constraint is generated from whatever list
|
|
10
|
+
* the schema file can see, so a private copy that stops matching the shared one
|
|
11
|
+
* produces a constraint that rejects rows the application considers valid — and
|
|
12
|
+
* the schema files are the one place where nothing else would catch it, because
|
|
13
|
+
* `drizzle-kit generate` happily emits the wrong constraint and the types still
|
|
14
|
+
* line up on both sides.
|
|
15
|
+
*/
|
|
16
|
+
const SCHEMA_FILES = ["../schema.ts", "../pg/schema.ts"];
|
|
17
|
+
|
|
18
|
+
/** `const NAME = [...] as const`, the shape a value list is declared in. */
|
|
19
|
+
const LOCAL_VALUE_LIST =
|
|
20
|
+
/const\s+([A-Z][A-Z0-9_]*)\s*=\s*\[[^\]]*\]\s*as const/g;
|
|
21
|
+
|
|
22
|
+
describe("schema value lists", () => {
|
|
23
|
+
for (const file of SCHEMA_FILES) {
|
|
24
|
+
it(`${file} declares no value list of its own`, () => {
|
|
25
|
+
const source = readFileSync(resolve(__dirname, file), "utf8");
|
|
26
|
+
const declared = [...source.matchAll(LOCAL_VALUE_LIST)].map(
|
|
27
|
+
(match) => match[1],
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
expect(declared).toEqual([]);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
});
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it } from "vitest";
|
|
2
|
+
import type Database from "better-sqlite3";
|
|
3
|
+
import {
|
|
4
|
+
createTestDatabase,
|
|
5
|
+
DEFAULT_TEST_SITE_ID,
|
|
6
|
+
} from "../../__tests__/helpers/db.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The three tables a smart collection can be placed in, checked by inserting
|
|
10
|
+
* real rows.
|
|
11
|
+
*
|
|
12
|
+
* Reading a CHECK constraint and believing it is not the same as running it.
|
|
13
|
+
* The last time these constraints were written by hand, a test shaped exactly
|
|
14
|
+
* like this one found three bugs in a single pass: a branch that forgot to
|
|
15
|
+
* require its own foreign key, a branch that allowed two of them at once, and a
|
|
16
|
+
* unique index whose partial predicate never matched.
|
|
17
|
+
*
|
|
18
|
+
* Each case states the row it inserts and what the schema promises about it, so
|
|
19
|
+
* a failure names the rule rather than the constraint.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const SITE = DEFAULT_TEST_SITE_ID;
|
|
23
|
+
const NOW = 1_700_000_000;
|
|
24
|
+
|
|
25
|
+
function ids(prefix: string, n: number): string[] {
|
|
26
|
+
return Array.from(
|
|
27
|
+
{ length: n },
|
|
28
|
+
(_, i) => `${prefix}_0000000000000000000000000${i}`,
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe("smart collection placement constraints", () => {
|
|
33
|
+
let sqlite: Database.Database;
|
|
34
|
+
let smartA: string;
|
|
35
|
+
let smartB: string;
|
|
36
|
+
let collectionA: string;
|
|
37
|
+
let postA: string;
|
|
38
|
+
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
({ sqlite } = createTestDatabase());
|
|
41
|
+
[smartA, smartB] = ids("smc", 2) as [string, string];
|
|
42
|
+
[collectionA] = ids("col", 1) as [string];
|
|
43
|
+
[postA] = ids("pst", 1) as [string];
|
|
44
|
+
|
|
45
|
+
sqlite
|
|
46
|
+
.prepare(
|
|
47
|
+
`INSERT INTO collection (id, site_id, title, sort_order, created_at, updated_at)
|
|
48
|
+
VALUES (?, ?, 'Books', 'newest', ?, ?)`,
|
|
49
|
+
)
|
|
50
|
+
.run(collectionA, SITE, NOW, NOW);
|
|
51
|
+
|
|
52
|
+
for (const id of [smartA, smartB]) {
|
|
53
|
+
sqlite
|
|
54
|
+
.prepare(
|
|
55
|
+
`INSERT INTO smart_collection (id, site_id, title, sort, created_at, updated_at)
|
|
56
|
+
VALUES (?, ?, 'Quotes', 'newest', ?, ?)`,
|
|
57
|
+
)
|
|
58
|
+
.run(id, SITE, NOW, NOW);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
sqlite
|
|
62
|
+
.prepare(
|
|
63
|
+
`INSERT INTO post (
|
|
64
|
+
id, site_id, thread_id, format, status, visibility,
|
|
65
|
+
body_html, body_text, created_at, updated_at
|
|
66
|
+
) VALUES (?, ?, ?, 'note', 'published', 'public', '', '', ?, ?)`,
|
|
67
|
+
)
|
|
68
|
+
.run(postA, SITE, postA, NOW, NOW);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
function insertPath(values: Record<string, unknown>) {
|
|
72
|
+
const row = {
|
|
73
|
+
id: `pth_${Math.random().toString(36).slice(2, 12)}`,
|
|
74
|
+
site_id: SITE,
|
|
75
|
+
path: `p-${Math.random().toString(36).slice(2, 8)}`,
|
|
76
|
+
kind: "slug",
|
|
77
|
+
post_id: null,
|
|
78
|
+
collection_id: null,
|
|
79
|
+
smart_collection_id: null,
|
|
80
|
+
redirect_to_path: null,
|
|
81
|
+
redirect_type: null,
|
|
82
|
+
archive_query: null,
|
|
83
|
+
created_at: NOW,
|
|
84
|
+
updated_at: NOW,
|
|
85
|
+
...values,
|
|
86
|
+
};
|
|
87
|
+
const keys = Object.keys(row);
|
|
88
|
+
sqlite
|
|
89
|
+
.prepare(
|
|
90
|
+
`INSERT INTO path_registry (${keys.join(", ")})
|
|
91
|
+
VALUES (${keys.map(() => "?").join(", ")})`,
|
|
92
|
+
)
|
|
93
|
+
.run(...keys.map((key) => row[key]));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function insertDirectoryItem(values: Record<string, unknown>) {
|
|
97
|
+
const row = {
|
|
98
|
+
id: `cdi_${Math.random().toString(36).slice(2, 12)}`,
|
|
99
|
+
site_id: SITE,
|
|
100
|
+
type: "smart_collection",
|
|
101
|
+
collection_id: null,
|
|
102
|
+
smart_collection_id: null,
|
|
103
|
+
label: null,
|
|
104
|
+
url: null,
|
|
105
|
+
description: null,
|
|
106
|
+
position: `a${Math.random().toString(36).slice(2, 8)}`,
|
|
107
|
+
created_at: NOW,
|
|
108
|
+
updated_at: NOW,
|
|
109
|
+
...values,
|
|
110
|
+
};
|
|
111
|
+
const keys = Object.keys(row);
|
|
112
|
+
sqlite
|
|
113
|
+
.prepare(
|
|
114
|
+
`INSERT INTO collection_directory_item (${keys.join(", ")})
|
|
115
|
+
VALUES (${keys.map(() => "?").join(", ")})`,
|
|
116
|
+
)
|
|
117
|
+
.run(...keys.map((key) => row[key]));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function insertNavItem(values: Record<string, unknown>) {
|
|
121
|
+
const row = {
|
|
122
|
+
id: `nav_${Math.random().toString(36).slice(2, 12)}`,
|
|
123
|
+
site_id: SITE,
|
|
124
|
+
type: "smart_collection",
|
|
125
|
+
system_key: null,
|
|
126
|
+
collection_id: null,
|
|
127
|
+
smart_collection_id: null,
|
|
128
|
+
post_id: null,
|
|
129
|
+
label: "",
|
|
130
|
+
url: "/quotes",
|
|
131
|
+
placement: "header",
|
|
132
|
+
position: `a${Math.random().toString(36).slice(2, 8)}`,
|
|
133
|
+
created_at: NOW,
|
|
134
|
+
updated_at: NOW,
|
|
135
|
+
...values,
|
|
136
|
+
};
|
|
137
|
+
const keys = Object.keys(row);
|
|
138
|
+
sqlite
|
|
139
|
+
.prepare(
|
|
140
|
+
`INSERT INTO nav_item (${keys.join(", ")})
|
|
141
|
+
VALUES (${keys.map(() => "?").join(", ")})`,
|
|
142
|
+
)
|
|
143
|
+
.run(...keys.map((key) => row[key]));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
describe("path_registry", () => {
|
|
147
|
+
it("accepts a slug pointing at a smart collection", () => {
|
|
148
|
+
expect(() =>
|
|
149
|
+
insertPath({ smart_collection_id: smartA, path: "quotes" }),
|
|
150
|
+
).not.toThrow();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("accepts an alias pointing at a smart collection", () => {
|
|
154
|
+
expect(() =>
|
|
155
|
+
insertPath({
|
|
156
|
+
kind: "alias",
|
|
157
|
+
smart_collection_id: smartA,
|
|
158
|
+
path: "old-quotes",
|
|
159
|
+
}),
|
|
160
|
+
).not.toThrow();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("refuses a slug that points at two things at once", () => {
|
|
164
|
+
expect(() =>
|
|
165
|
+
insertPath({ smart_collection_id: smartA, collection_id: collectionA }),
|
|
166
|
+
).toThrow();
|
|
167
|
+
expect(() =>
|
|
168
|
+
insertPath({ smart_collection_id: smartA, post_id: postA }),
|
|
169
|
+
).toThrow();
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("refuses a redirect or archive row that names a smart collection", () => {
|
|
173
|
+
expect(() =>
|
|
174
|
+
insertPath({
|
|
175
|
+
kind: "redirect",
|
|
176
|
+
smart_collection_id: smartA,
|
|
177
|
+
redirect_to_path: "quotes",
|
|
178
|
+
redirect_type: 301,
|
|
179
|
+
}),
|
|
180
|
+
).toThrow();
|
|
181
|
+
expect(() =>
|
|
182
|
+
insertPath({
|
|
183
|
+
kind: "archive",
|
|
184
|
+
smart_collection_id: smartA,
|
|
185
|
+
archive_query: "format=quote",
|
|
186
|
+
}),
|
|
187
|
+
).toThrow();
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("allows only one slug per smart collection", () => {
|
|
191
|
+
insertPath({ smart_collection_id: smartA, path: "quotes" });
|
|
192
|
+
expect(() =>
|
|
193
|
+
insertPath({ smart_collection_id: smartA, path: "quotes-2" }),
|
|
194
|
+
).toThrow();
|
|
195
|
+
|
|
196
|
+
// An alias is not a second slug, so it is still allowed.
|
|
197
|
+
expect(() =>
|
|
198
|
+
insertPath({
|
|
199
|
+
kind: "alias",
|
|
200
|
+
smart_collection_id: smartA,
|
|
201
|
+
path: "old-quotes",
|
|
202
|
+
}),
|
|
203
|
+
).not.toThrow();
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("keeps smart collections in the same address space as everything else", () => {
|
|
207
|
+
insertPath({ smart_collection_id: smartA, path: "quotes" });
|
|
208
|
+
expect(() =>
|
|
209
|
+
insertPath({ collection_id: collectionA, path: "quotes" }),
|
|
210
|
+
).toThrow();
|
|
211
|
+
expect(() => insertPath({ post_id: postA, path: "quotes" })).toThrow();
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
describe("collection_directory_item", () => {
|
|
216
|
+
it("accepts a smart collection row", () => {
|
|
217
|
+
expect(() =>
|
|
218
|
+
insertDirectoryItem({ smart_collection_id: smartA }),
|
|
219
|
+
).not.toThrow();
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("refuses a smart collection row with no target", () => {
|
|
223
|
+
expect(() => insertDirectoryItem({})).toThrow();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it("refuses a smart collection row carrying a label or url", () => {
|
|
227
|
+
expect(() =>
|
|
228
|
+
insertDirectoryItem({ smart_collection_id: smartA, label: "Quotes" }),
|
|
229
|
+
).toThrow();
|
|
230
|
+
expect(() =>
|
|
231
|
+
insertDirectoryItem({ smart_collection_id: smartA, url: "/quotes" }),
|
|
232
|
+
).toThrow();
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it("refuses every other row type carrying a smart collection", () => {
|
|
236
|
+
expect(() =>
|
|
237
|
+
insertDirectoryItem({
|
|
238
|
+
type: "collection",
|
|
239
|
+
collection_id: collectionA,
|
|
240
|
+
smart_collection_id: smartA,
|
|
241
|
+
}),
|
|
242
|
+
).toThrow();
|
|
243
|
+
expect(() =>
|
|
244
|
+
insertDirectoryItem({ type: "divider", smart_collection_id: smartA }),
|
|
245
|
+
).toThrow();
|
|
246
|
+
expect(() =>
|
|
247
|
+
insertDirectoryItem({
|
|
248
|
+
type: "link",
|
|
249
|
+
label: "Elsewhere",
|
|
250
|
+
url: "https://example.com",
|
|
251
|
+
smart_collection_id: smartA,
|
|
252
|
+
}),
|
|
253
|
+
).toThrow();
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it("places a smart collection at most once", () => {
|
|
257
|
+
insertDirectoryItem({ smart_collection_id: smartA });
|
|
258
|
+
expect(() =>
|
|
259
|
+
insertDirectoryItem({ smart_collection_id: smartA }),
|
|
260
|
+
).toThrow();
|
|
261
|
+
expect(() =>
|
|
262
|
+
insertDirectoryItem({ smart_collection_id: smartB }),
|
|
263
|
+
).not.toThrow();
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
describe("nav_item", () => {
|
|
268
|
+
it("accepts a smart collection nav item", () => {
|
|
269
|
+
expect(() =>
|
|
270
|
+
insertNavItem({ smart_collection_id: smartA }),
|
|
271
|
+
).not.toThrow();
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it("refuses a smart collection nav item with no target", () => {
|
|
275
|
+
expect(() => insertNavItem({})).toThrow();
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it("refuses every other nav type carrying a smart collection", () => {
|
|
279
|
+
expect(() =>
|
|
280
|
+
insertNavItem({
|
|
281
|
+
type: "link",
|
|
282
|
+
smart_collection_id: smartA,
|
|
283
|
+
label: "Elsewhere",
|
|
284
|
+
}),
|
|
285
|
+
).toThrow();
|
|
286
|
+
expect(() =>
|
|
287
|
+
insertNavItem({
|
|
288
|
+
type: "system",
|
|
289
|
+
system_key: "latest",
|
|
290
|
+
smart_collection_id: smartA,
|
|
291
|
+
}),
|
|
292
|
+
).toThrow();
|
|
293
|
+
expect(() =>
|
|
294
|
+
insertNavItem({
|
|
295
|
+
type: "collection",
|
|
296
|
+
collection_id: collectionA,
|
|
297
|
+
smart_collection_id: smartA,
|
|
298
|
+
}),
|
|
299
|
+
).toThrow();
|
|
300
|
+
expect(() =>
|
|
301
|
+
insertNavItem({
|
|
302
|
+
type: "page",
|
|
303
|
+
post_id: postA,
|
|
304
|
+
smart_collection_id: smartA,
|
|
305
|
+
}),
|
|
306
|
+
).toThrow();
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it("adds a smart collection to navigation at most once", () => {
|
|
310
|
+
insertNavItem({ smart_collection_id: smartA });
|
|
311
|
+
expect(() => insertNavItem({ smart_collection_id: smartA })).toThrow();
|
|
312
|
+
expect(() =>
|
|
313
|
+
insertNavItem({ smart_collection_id: smartB }),
|
|
314
|
+
).not.toThrow();
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
describe("smart_collection", () => {
|
|
319
|
+
it("refuses a private visibility condition", () => {
|
|
320
|
+
// The one value a published page can never name. Enforced in the column,
|
|
321
|
+
// not only in Zod, so no import path or backfill can slip one in.
|
|
322
|
+
expect(() =>
|
|
323
|
+
sqlite
|
|
324
|
+
.prepare(
|
|
325
|
+
`INSERT INTO smart_collection (id, site_id, title, visibility, sort, created_at, updated_at)
|
|
326
|
+
VALUES (?, ?, 'Private', 'private', 'newest', ?, ?)`,
|
|
327
|
+
)
|
|
328
|
+
.run(`smc_private0000000000000000000`, SITE, NOW, NOW),
|
|
329
|
+
).toThrow();
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it("refuses a sort or layout it cannot render", () => {
|
|
333
|
+
expect(() =>
|
|
334
|
+
sqlite
|
|
335
|
+
.prepare(
|
|
336
|
+
`INSERT INTO smart_collection (id, site_id, title, sort, created_at, updated_at)
|
|
337
|
+
VALUES (?, ?, 'Bad sort', 'rating_asc', ?, ?)`,
|
|
338
|
+
)
|
|
339
|
+
.run(`smc_badsort00000000000000000000`, SITE, NOW, NOW),
|
|
340
|
+
).toThrow();
|
|
341
|
+
expect(() =>
|
|
342
|
+
sqlite
|
|
343
|
+
.prepare(
|
|
344
|
+
`INSERT INTO smart_collection (id, site_id, title, sort, layout, created_at, updated_at)
|
|
345
|
+
VALUES (?, ?, 'Bad layout', 'newest', 'carousel', ?, ?)`,
|
|
346
|
+
)
|
|
347
|
+
.run(`smc_badlayout00000000000000000`, SITE, NOW, NOW),
|
|
348
|
+
).toThrow();
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it("leaves a deleted collection's id in place rather than widening", () => {
|
|
352
|
+
sqlite
|
|
353
|
+
.prepare(`UPDATE smart_collection SET collection_id = ? WHERE id = ?`)
|
|
354
|
+
.run(collectionA, smartA);
|
|
355
|
+
|
|
356
|
+
// No foreign key on this column, alone in the schema. RESTRICT would fail
|
|
357
|
+
// anonymously and make every bulk delete an ordering rule; SET NULL would
|
|
358
|
+
// turn a curated page into the whole archive. The refusal an author sees
|
|
359
|
+
// is `smartCollections.assertCollectionUnused()`, and what is left if
|
|
360
|
+
// something reaches around it is a condition naming a collection that no
|
|
361
|
+
// longer exists — which matches nothing.
|
|
362
|
+
expect(() =>
|
|
363
|
+
sqlite.prepare(`DELETE FROM collection WHERE id = ?`).run(collectionA),
|
|
364
|
+
).not.toThrow();
|
|
365
|
+
|
|
366
|
+
const row = sqlite
|
|
367
|
+
.prepare(`SELECT collection_id FROM smart_collection WHERE id = ?`)
|
|
368
|
+
.get(smartA) as { collection_id: string | null };
|
|
369
|
+
expect(row.collection_id).toBe(collectionA);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it("refuses a year no timestamp could carry", () => {
|
|
373
|
+
// `Date.UTC` returns NaN past year 275760, and a NaN bound is a
|
|
374
|
+
// comparison every row fails with nothing to report.
|
|
375
|
+
for (const year of [1970, 10000]) {
|
|
376
|
+
expect(() =>
|
|
377
|
+
sqlite
|
|
378
|
+
.prepare(
|
|
379
|
+
`INSERT INTO smart_collection (id, site_id, title, year, sort, created_at, updated_at)
|
|
380
|
+
VALUES (?, ?, 'Bad year', ?, 'newest', ?, ?)`,
|
|
381
|
+
)
|
|
382
|
+
.run(
|
|
383
|
+
`smc_year${year}000000000000000000`.slice(0, 30),
|
|
384
|
+
SITE,
|
|
385
|
+
year,
|
|
386
|
+
NOW,
|
|
387
|
+
NOW,
|
|
388
|
+
),
|
|
389
|
+
).toThrow();
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
expect(() =>
|
|
393
|
+
sqlite
|
|
394
|
+
.prepare(
|
|
395
|
+
`INSERT INTO smart_collection (id, site_id, title, year, sort, created_at, updated_at)
|
|
396
|
+
VALUES (?, ?, 'Edge years', 9999, 'newest', ?, ?)`,
|
|
397
|
+
)
|
|
398
|
+
.run(`smc_year9999000000000000000000`.slice(0, 30), SITE, NOW, NOW),
|
|
399
|
+
).not.toThrow();
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
});
|