@michaelthielemann/kestrel 3.0.0 → 4.0.0
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 +35 -110
- package/kestrel.config.ts +1 -1
- package/layers/access/server/middleware/00.ip-allowlist.ts +3 -18
- package/layers/access/server/middleware/access-guard.ts +25 -23
- package/layers/access/server/types.d.ts +1 -2
- package/layers/admin/app/components/AdminAccount.vue +1 -1
- package/layers/admin/app/components/BlockFields.vue +1 -2
- package/layers/admin/app/components/BlockPreview.vue +17 -5
- package/layers/admin/app/components/BlocksBody.vue +1 -1
- package/layers/admin/app/components/CollectionEditor.vue +35 -16
- package/layers/admin/app/components/CollectionList.vue +34 -3
- package/layers/admin/app/components/CollectionListBulkBar.vue +17 -3
- package/layers/admin/app/components/CollectionListFilterPanel.vue +1 -1
- package/layers/admin/app/components/CollectionListPager.vue +1 -1
- package/layers/admin/app/components/CollectionListTable.vue +41 -2
- package/layers/admin/app/components/EditorStatus.vue +8 -7
- package/layers/admin/app/components/LocaleBar.vue +3 -1
- package/layers/admin/app/components/PageFields.vue +2 -4
- package/layers/admin/app/components/SeoFields.vue +2 -4
- package/layers/admin/app/components/SingletonEditor.vue +3 -2
- package/layers/admin/app/composables/useAuth.ts +3 -3
- package/layers/admin/app/composables/useBlockTree.ts +1 -1
- package/layers/admin/app/composables/useBlocks.ts +1 -1
- package/layers/admin/app/composables/useCollectionOps.ts +45 -8
- package/layers/admin/app/composables/useCollections.ts +1 -2
- package/layers/admin/app/composables/useEditForm.ts +35 -17
- package/layers/admin/app/composables/useLinkResolver.ts +1 -1
- package/layers/admin/app/composables/useListBatchActions.ts +20 -3
- package/layers/admin/app/composables/useListColumns.ts +1 -1
- package/layers/admin/app/composables/useListFilterDraft.ts +3 -2
- package/layers/admin/app/composables/useListRows.ts +7 -2
- package/layers/admin/app/composables/useListUrlState.ts +1 -1
- package/layers/admin/app/composables/usePublishStatus.ts +2 -2
- package/layers/admin/app/layouts/admin.vue +0 -1
- package/layers/admin/app/pages/admin/[collection]/[id].vue +5 -4
- package/layers/admin/app/pages/admin/index.vue +1 -1
- package/layers/admin/app/pages/admin/references.vue +2 -2
- package/layers/admin/app/utils/block-tree.ts +1 -1
- package/layers/admin/app/utils/collection-ops.ts +28 -3
- package/layers/admin/app/utils/edit-form.ts +34 -2
- package/layers/admin/app/utils/editor-expose.ts +2 -0
- package/layers/admin/app/utils/editor-form-context.ts +1 -2
- package/layers/admin/app/utils/field-keys.ts +1 -1
- package/layers/admin/app/utils/list-columns.ts +3 -2
- package/layers/admin/app/utils/list-query.ts +2 -2
- package/layers/admin/app/utils/populate-blocks.ts +2 -2
- package/layers/admin/app/utils/reauth.ts +5 -6
- package/layers/auth/server/plugins/01.register-auth-pipelines.ts +7 -0
- package/layers/collections/server/plugins/01.register.ts +9 -12
- package/layers/collections/server/plugins/02.register-relation-populate.ts +5 -7
- package/layers/{fields → core}/app/utils/field-factories.ts +1 -1
- package/layers/core/modules/auto-discovery/extract-block.ts +3 -3
- package/layers/core/modules/auto-discovery/index.ts +68 -11
- package/layers/core/modules/auto-discovery/package-registry.ts +19 -0
- package/layers/core/modules/auto-discovery/scan.ts +77 -0
- package/layers/core/modules/auto-discovery/virtual.d.ts +5 -0
- package/layers/core/modules/component-namespace/index.ts +24 -0
- package/layers/core/modules/kestrel/index.ts +6 -1
- package/layers/core/modules/plugin-order/index.ts +69 -0
- package/layers/core/modules/plugin-order/plugin-order.ts +273 -0
- package/layers/core/nuxt.config.ts +1 -0
- package/layers/core/server/api/[...path].ts +98 -0
- package/layers/core/server/plugins/00.config.ts +12 -0
- package/layers/core/server/plugins/00.migrate.ts +1 -0
- package/layers/core/server/plugins/01.register-introspection-pipeline.ts +5 -0
- package/layers/core/server/plugins/01.register-openapi-pipeline.ts +5 -0
- package/layers/core/server/plugins/01.register-tooling-pipelines.ts +5 -0
- package/layers/core/server/plugins/02.schema-sync.ts +2 -7
- package/layers/core/server/plugins/04.outbox-worker.ts +31 -0
- package/layers/core/server/plugins/05.reindex-refs.ts +5 -0
- package/layers/core/server/routes/__kestrel/dashboard.get.ts +29 -0
- package/layers/core/server/tasks/db/migrate-module.ts +83 -0
- package/layers/core/server/tasks/db/migrate-revisions.ts +20 -0
- package/layers/core/server/tasks/db/migrate.ts +2 -6
- package/layers/core/server/utils/server-config.ts +66 -3
- package/layers/fields/server/plugins/01.register-blocks-pipeline.ts +7 -0
- package/layers/fields/server/plugins/01.register-field-populate.ts +2 -2
- package/layers/media/app/components/KestrelImg.vue +3 -3
- package/layers/media/app/components/MediaLibrary.vue +2 -2
- package/layers/media/app/components/field/Media.vue +1 -1
- package/layers/media/app/composables/useMediaResolver.ts +1 -1
- package/layers/media/app/composables/useMediaUpload.ts +1 -1
- package/layers/media/app/composables/useMediaVariant.ts +2 -2
- package/layers/media/app/utils/ai-disclosure.ts +1 -1
- package/layers/media/app/utils/library.ts +1 -1
- package/layers/media/app/utils/ops.ts +1 -3
- package/layers/media/app/utils/picture-model.ts +2 -2
- package/layers/media/app/utils/variant-name.ts +1 -1
- package/layers/media/server/middleware/ondemand-variants.ts +6 -5
- package/layers/media/server/plugins/01.register-media-pipelines.ts +7 -0
- package/layers/media/server/plugins/02.register-media.ts +10 -7
- package/layers/media/server/plugins/04.variant-capture.ts +12 -8
- package/layers/media/server/plugins/05.media-cleanup.ts +5 -0
- package/layers/media/server/tasks/media/backfill.ts +3 -4
- package/layers/public/app/components/KestrelLink.vue +1 -1
- package/layers/public/app/pages/[...slug].vue +1 -1
- package/layers/public/app/pages/__kestrel/preview.vue +2 -2
- package/layers/public/app/utils/json-ld.ts +1 -1
- package/layers/public/app/utils/link-href.ts +1 -1
- package/layers/public/app/utils/page-head.ts +1 -1
- package/layers/public/modules/deploy-output/index.ts +4 -6
- package/layers/public/modules/prerender-routes/index.ts +4 -4
- package/layers/public/modules/prerender-routes/routes.ts +1 -1
- package/layers/public/modules/prune-media/index.ts +2 -2
- package/layers/public/server/delivery-live/serve.ts +111 -0
- package/layers/public/server/middleware/delivery-live-catchall.ts +7 -0
- package/layers/public/server/pipelines/index.ts +23 -0
- package/layers/public/server/plugins/00.ensure-snapshot-triggers.ts +14 -0
- package/layers/public/server/plugins/01.register-public-pipelines.ts +7 -0
- package/layers/public/server/plugins/02.register-links.ts +2 -3
- package/layers/public/server/plugins/03.redirects.ts +41 -23
- package/layers/public/server/plugins/05.plan-publish.ts +5 -0
- package/layers/public/server/plugins/zz.publish.ts +79 -24
- package/layers/public/server/routes/llms-full.txt.get.ts +5 -5
- package/layers/public/server/routes/llms.txt.get.ts +4 -2
- package/layers/public/server/routes/redirects.json.get.ts +2 -2
- package/layers/public/server/routes/robots.txt.get.ts +2 -0
- package/layers/public/server/routes/sitemap.xml.get.ts +16 -6
- package/layers/public/server/tasks/publish/run.ts +9 -4
- package/layers/public/server/utils/publish/render-live.ts +26 -0
- package/layers/ui/app/components/field/Choice.vue +1 -1
- package/layers/ui/app/components/field/Datetime.vue +1 -1
- package/layers/ui/app/components/field/Json.vue +1 -2
- package/layers/ui/app/components/field/Layout.vue +2 -4
- package/layers/ui/app/components/field/Link.vue +1 -1
- package/layers/ui/app/components/field/LinkInternalPicker.vue +3 -3
- package/layers/ui/app/components/field/Number.vue +2 -2
- package/layers/ui/app/components/field/Relation.vue +1 -1
- package/layers/ui/app/components/field/Repeater.vue +6 -8
- package/layers/ui/app/components/field/Slug.vue +2 -3
- package/layers/ui/app/components/field/Text.vue +1 -1
- package/layers/ui/app/components/ui/Button.vue +2 -2
- package/layers/ui/app/components/ui/ButtonGroup.vue +2 -2
- package/layers/ui/app/components/ui/Checkbox.vue +2 -2
- package/layers/ui/app/components/ui/Combobox.vue +5 -5
- package/layers/ui/app/components/ui/DatePicker.vue +2 -2
- package/layers/ui/app/components/ui/DateRangePicker.vue +2 -2
- package/layers/ui/app/components/ui/NumberInput.vue +4 -4
- package/layers/ui/app/components/ui/Richtext.vue +1 -1
- package/layers/ui/app/components/ui/RichtextToolbar.vue +1 -3
- package/layers/ui/app/components/ui/Select.vue +2 -2
- package/layers/ui/app/components/ui/TextInput.vue +4 -4
- package/layers/ui/app/components/ui/Textarea.vue +2 -2
- package/layers/ui/app/components/ui/TimeInput.vue +2 -2
- package/layers/ui/app/components/ui/Toasts.vue +3 -3
- package/layers/ui/app/composables/useLinkField.ts +1 -1
- package/layers/ui/app/composables/useRepeater.ts +1 -1
- package/layers/ui/app/i18n/de.ts +7 -7
- package/layers/ui/app/i18n/en.ts +7 -39
- package/layers/ui/app/utils/field-component.ts +1 -1
- package/layers/ui/app/utils/field-registry.ts +1 -1
- package/layers/ui/app/utils/icons.ts +2 -6
- package/layers/ui/app/utils/localized.ts +1 -1
- package/nuxt.config.ts +1 -1
- package/package.json +53 -19
- package/scripts/consumer-template-ci.mjs +157 -0
- package/scripts/coverage-all.mjs +163 -0
- package/scripts/dashboard.mjs +89 -0
- package/scripts/hash-password.mjs +1 -1
- package/scripts/lib/coverage-model.mjs +51 -0
- package/scripts/port-graph.mjs +350 -0
- package/scripts/typecheck.mjs +11 -0
- package/templates/starter/app/blocks/Prose.vue +1 -1
- package/layers/access/server/utils/csrf.ts +0 -39
- package/layers/access/server/utils/grant-registry.ts +0 -36
- package/layers/access/server/utils/guard.ts +0 -44
- package/layers/access/server/utils/ip-allowlist.ts +0 -79
- package/layers/access/server/utils/policy.ts +0 -98
- package/layers/access/server/utils/public-resources.ts +0 -11
- package/layers/access/server/utils/render-context.ts +0 -41
- package/layers/access/server/utils/require-admin.ts +0 -15
- package/layers/admin/app/pages/admin/[collection]/[id].nuxt.test.ts +0 -200
- package/layers/admin/app/pages/admin/[collection]/publish-preview.nuxt.test.ts +0 -142
- package/layers/auth/server/api/auth/login.post.ts +0 -38
- package/layers/auth/server/api/auth/logout.post.ts +0 -9
- package/layers/auth/server/api/auth/session.get.ts +0 -1
- package/layers/auth/server/utils/admin-credential.ts +0 -42
- package/layers/auth/server/utils/client-ip.ts +0 -88
- package/layers/auth/server/utils/login-throttle.ts +0 -126
- package/layers/auth/server/utils/password.ts +0 -52
- package/layers/auth/server/utils/session-cookie.ts +0 -44
- package/layers/auth/server/utils/session-epoch.ts +0 -96
- package/layers/auth/server/utils/session.ts +0 -88
- package/layers/collections/server/collections/pages.ts +0 -19
- package/layers/collections/server/utils/populate-relations.ts +0 -79
- package/layers/core/app/utils/bulk-actions.ts +0 -9
- package/layers/core/app/utils/filter-ops.ts +0 -98
- package/layers/core/app/utils/list-limits.ts +0 -27
- package/layers/core/app/utils/locale-path.ts +0 -12
- package/layers/core/app/utils/slugify.ts +0 -16
- package/layers/core/server/api/[collection]/[id]/dead-refs.get.ts +0 -8
- package/layers/core/server/api/[collection]/[id]/translations.get.ts +0 -4
- package/layers/core/server/api/[collection]/[id].delete.ts +0 -5
- package/layers/core/server/api/[collection]/[id].get.ts +0 -11
- package/layers/core/server/api/[collection]/[id].patch.ts +0 -8
- package/layers/core/server/api/[collection]/bulk.post.ts +0 -37
- package/layers/core/server/api/[collection]/index.get.ts +0 -24
- package/layers/core/server/api/[collection]/index.post.ts +0 -8
- package/layers/core/server/api/[collection]/index.put.test.ts +0 -76
- package/layers/core/server/api/[collection]/index.put.ts +0 -24
- package/layers/core/server/api/[collection]/options.get.test.ts +0 -60
- package/layers/core/server/api/[collection]/options.get.ts +0 -18
- package/layers/core/server/api/[collection]/populate-scope.test.ts +0 -119
- package/layers/core/server/api/[collection]/translations.get.test.ts +0 -90
- package/layers/core/server/api/[collection]/translations.get.ts +0 -25
- package/layers/core/server/api/collections/[name].get.ts +0 -4
- package/layers/core/server/api/collections/index.get.ts +0 -3
- package/layers/core/server/api/references/broken.get.ts +0 -9
- package/layers/core/server/api/references/referrers.get.ts +0 -24
- package/layers/core/server/database/record-refs.ts +0 -20
- package/layers/core/server/plugins/03.record-refs.ts +0 -12
- package/layers/core/server/schema/bootstrap.ts +0 -41
- package/layers/core/server/schema/desired.ts +0 -99
- package/layers/core/server/schema/dev-sync.ts +0 -23
- package/layers/core/server/schema/dialect.ts +0 -61
- package/layers/core/server/schema/diff.ts +0 -110
- package/layers/core/server/schema/introspect.ts +0 -68
- package/layers/core/server/schema/model.ts +0 -61
- package/layers/core/server/schema/render-sqlite.ts +0 -89
- package/layers/core/server/schema/sync.ts +0 -201
- package/layers/core/server/utils/blocks.ts +0 -6
- package/layers/core/server/utils/collection-types.ts +0 -28
- package/layers/core/server/utils/crud.ts +0 -578
- package/layers/core/server/utils/db.ts +0 -26
- package/layers/core/server/utils/defineCollection.ts +0 -213
- package/layers/core/server/utils/duplicate.ts +0 -162
- package/layers/core/server/utils/field-layout.ts +0 -125
- package/layers/core/server/utils/filter-predicate.ts +0 -101
- package/layers/core/server/utils/http.ts +0 -50
- package/layers/core/server/utils/if-unmodified.ts +0 -13
- package/layers/core/server/utils/kestrel-config.ts +0 -451
- package/layers/core/server/utils/key-lock.ts +0 -33
- package/layers/core/server/utils/locale.ts +0 -53
- package/layers/core/server/utils/page-route.ts +0 -58
- package/layers/core/server/utils/page-slug.ts +0 -127
- package/layers/core/server/utils/picker.ts +0 -82
- package/layers/core/server/utils/populate.ts +0 -50
- package/layers/core/server/utils/prerender-memo.ts +0 -23
- package/layers/core/server/utils/read-capture.ts +0 -37
- package/layers/core/server/utils/record-ref-index.ts +0 -200
- package/layers/core/server/utils/registry.ts +0 -24
- package/layers/core/server/utils/resolve-scope.ts +0 -79
- package/layers/core/server/utils/seo.ts +0 -42
- package/layers/core/server/utils/serialize-collection.ts +0 -147
- package/layers/core/server/utils/sql.ts +0 -2
- package/layers/core/server/utils/storage.local.ts +0 -117
- package/layers/core/server/utils/storage.s3.ts +0 -197
- package/layers/core/server/utils/storage.ts +0 -90
- package/layers/core/server/utils/write-effects.ts +0 -40
- package/layers/core/server/utils/write-events.ts +0 -31
- package/layers/fields/app/utils/condition.ts +0 -71
- package/layers/fields/app/utils/define-block.ts +0 -13
- package/layers/fields/app/utils/field-constraints.ts +0 -17
- package/layers/fields/app/utils/field-meta.ts +0 -33
- package/layers/fields/app/utils/field-validate.ts +0 -75
- package/layers/fields/app/utils/field-value.ts +0 -7
- package/layers/fields/app/utils/richtext-links.ts +0 -48
- package/layers/fields/server/api/blocks/index.get.ts +0 -9
- package/layers/fields/server/field-registry/index.ts +0 -249
- package/layers/fields/server/field-registry/naming.ts +0 -10
- package/layers/fields/server/field-registry/sanitize.ts +0 -43
- package/layers/fields/server/field-registry/types.ts +0 -19
- package/layers/fields/server/utils/block-ids.ts +0 -43
- package/layers/fields/server/utils/block-populate.ts +0 -41
- package/layers/fields/server/utils/buildCollection.ts +0 -90
- package/layers/fields/server/utils/buildFieldSchema.ts +0 -22
- package/layers/fields/server/utils/buildTable.ts +0 -113
- package/layers/fields/server/utils/conditional-required.ts +0 -25
- package/layers/fields/server/utils/defineBlock.ts +0 -86
- package/layers/fields/server/utils/defineFieldType.ts +0 -33
- package/layers/fields/server/utils/extract-refs.ts +0 -152
- package/layers/fields/server/utils/field-populate.ts +0 -68
- package/layers/media/server/api/media/[id]/usages.get.ts +0 -8
- package/layers/media/server/api/media/[id].delete.ts +0 -18
- package/layers/media/server/api/media/[id].get.ts +0 -10
- package/layers/media/server/api/media/[id].patch.test.ts +0 -66
- package/layers/media/server/api/media/[id].patch.ts +0 -65
- package/layers/media/server/api/media/backfill.post.ts +0 -18
- package/layers/media/server/api/media/copy.post.ts +0 -7
- package/layers/media/server/api/media/delete.post.ts +0 -13
- package/layers/media/server/api/media/folders.post.ts +0 -18
- package/layers/media/server/api/media/index.get.ts +0 -15
- package/layers/media/server/api/media/index.post.ts +0 -173
- package/layers/media/server/api/media/library.get.ts +0 -17
- package/layers/media/server/api/media/move.post.ts +0 -7
- package/layers/media/server/api/media/rename.post.ts +0 -7
- package/layers/media/server/api/media/resolve.get.ts +0 -13
- package/layers/media/server/collections/media-settings.ts +0 -25
- package/layers/media/server/collections/media.ts +0 -43
- package/layers/media/server/database/folders.ts +0 -10
- package/layers/media/server/plugins/03.media-cleanup.ts +0 -20
- package/layers/media/server/schema-tables/folders.ts +0 -4
- package/layers/media/server/utils/ai-disclosure-enabled.ts +0 -16
- package/layers/media/server/utils/ai-signal-detect.ts +0 -155
- package/layers/media/server/utils/backfill.ts +0 -144
- package/layers/media/server/utils/derive.ts +0 -59
- package/layers/media/server/utils/folder-paths.ts +0 -42
- package/layers/media/server/utils/folders.ts +0 -12
- package/layers/media/server/utils/library.ts +0 -91
- package/layers/media/server/utils/media-cleanup.ts +0 -27
- package/layers/media/server/utils/media-enabled.ts +0 -23
- package/layers/media/server/utils/media-ops.ts +0 -118
- package/layers/media/server/utils/media-write.ts +0 -13
- package/layers/media/server/utils/naming.ts +0 -52
- package/layers/media/server/utils/ondemand.ts +0 -69
- package/layers/media/server/utils/persist-upload.ts +0 -62
- package/layers/media/server/utils/populate.ts +0 -47
- package/layers/media/server/utils/record.ts +0 -71
- package/layers/media/server/utils/relocate-ops.ts +0 -341
- package/layers/media/server/utils/relocate-plan.ts +0 -63
- package/layers/media/server/utils/resolve.ts +0 -98
- package/layers/media/server/utils/sanitize-svg.ts +0 -12
- package/layers/media/server/utils/sniff.ts +0 -68
- package/layers/media/server/utils/storage-relocate.ts +0 -139
- package/layers/media/server/utils/translations.ts +0 -19
- package/layers/media/server/utils/usages.ts +0 -66
- package/layers/media/server/utils/variant-capture.ts +0 -70
- package/layers/media/server/utils/variants.ts +0 -123
- package/layers/public/modules/deploy-output/deploy-output.ts +0 -384
- package/layers/public/server/api/links/resolve.get.ts +0 -19
- package/layers/public/server/api/preview.get.ts +0 -28
- package/layers/public/server/api/preview.post.ts +0 -93
- package/layers/public/server/api/publish-status.get.ts +0 -58
- package/layers/public/server/api/publish.post.ts +0 -84
- package/layers/public/server/api/route.get.ts +0 -54
- package/layers/public/server/collections/redirects.ts +0 -75
- package/layers/public/server/collections/site.ts +0 -44
- package/layers/public/server/database/publish-deps.ts +0 -12
- package/layers/public/server/database/publish-status.ts +0 -19
- package/layers/public/server/schema-tables/publish-deps.ts +0 -4
- package/layers/public/server/schema-tables/publish-status.ts +0 -4
- package/layers/public/server/utils/link-resolve.ts +0 -38
- package/layers/public/server/utils/llms-full.ts +0 -125
- package/layers/public/server/utils/llms.ts +0 -52
- package/layers/public/server/utils/page-resolve.ts +0 -208
- package/layers/public/server/utils/populate-links.ts +0 -66
- package/layers/public/server/utils/preview-token.ts +0 -109
- package/layers/public/server/utils/publish/coalesce.ts +0 -22
- package/layers/public/server/utils/publish/deps-persistence.ts +0 -43
- package/layers/public/server/utils/publish/deps.ts +0 -87
- package/layers/public/server/utils/publish/invalidation.ts +0 -225
- package/layers/public/server/utils/publish/pending.ts +0 -74
- package/layers/public/server/utils/publish/publish-runtime.ts +0 -28
- package/layers/public/server/utils/publish/publish-status.ts +0 -72
- package/layers/public/server/utils/publish/publisher.ts +0 -362
- package/layers/public/server/utils/publish/queue.ts +0 -67
- package/layers/public/server/utils/publish/redirect-rules.ts +0 -221
- package/layers/public/server/utils/publish/redirects-artifact.ts +0 -20
- package/layers/public/server/utils/publish/route-for-record.ts +0 -16
- package/layers/public/server/utils/publish/route-keys.ts +0 -13
- package/layers/public/server/utils/richtext-markdown.ts +0 -260
- package/layers/public/server/utils/site-url.ts +0 -40
- package/layers/public/server/utils/sitemap.ts +0 -99
package/layers/ui/app/i18n/en.ts
CHANGED
|
@@ -4,7 +4,6 @@ import type { Catalog } from '../composables/useT'
|
|
|
4
4
|
// carry `{name}` placeholders interpolated by `useT().t`. Content (collection data) is NOT translated
|
|
5
5
|
// here — only the dashboard chrome. Keep `en` the complete set; other catalogs may be partial.
|
|
6
6
|
export const en: Catalog = {
|
|
7
|
-
// common / shared
|
|
8
7
|
'common.new': 'New {label}',
|
|
9
8
|
'common.save': 'Save',
|
|
10
9
|
'common.publish': 'Publish',
|
|
@@ -14,7 +13,6 @@ export const en: Catalog = {
|
|
|
14
13
|
'common.close': 'Close',
|
|
15
14
|
'common.retry': 'Retry',
|
|
16
15
|
|
|
17
|
-
// navigation rail + shell
|
|
18
16
|
'nav.dashboard': 'Dashboard',
|
|
19
17
|
'nav.signOut': 'Sign out',
|
|
20
18
|
'theme.dark': 'Dark mode',
|
|
@@ -27,21 +25,17 @@ export const en: Catalog = {
|
|
|
27
25
|
'a11y.rowActions': 'Actions',
|
|
28
26
|
'lang.label': 'Language',
|
|
29
27
|
|
|
30
|
-
// account menu (rail foot)
|
|
31
28
|
'account.label': 'Account',
|
|
32
29
|
'account.name': 'Admin',
|
|
33
30
|
|
|
34
|
-
// text input (password reveal)
|
|
35
31
|
'input.showPassword': 'Show password',
|
|
36
32
|
'input.hidePassword': 'Hide password',
|
|
37
33
|
|
|
38
|
-
// login
|
|
39
34
|
'login.signIn': 'Sign in',
|
|
40
35
|
'login.password': 'Password',
|
|
41
36
|
'login.invalid': 'Invalid credentials',
|
|
42
37
|
'login.error': 'Something went wrong — please try again',
|
|
43
38
|
|
|
44
|
-
// dashboard
|
|
45
39
|
'dash.title': 'Dashboard',
|
|
46
40
|
'dash.lede': 'Choose a collection to manage its content.',
|
|
47
41
|
'dash.empty.title': 'No collections yet',
|
|
@@ -49,10 +43,8 @@ export const en: Catalog = {
|
|
|
49
43
|
'dash.mode.singleton': 'Singleton',
|
|
50
44
|
'dash.mode.collection': 'Collection',
|
|
51
45
|
|
|
52
|
-
// collection (list page wrapper)
|
|
53
46
|
'collection.unknown': 'Unknown collection: {name}',
|
|
54
47
|
|
|
55
|
-
// record editor page + toasts
|
|
56
48
|
'editor.back': '{collection}',
|
|
57
49
|
'editor.newRecord': 'New {collection}',
|
|
58
50
|
'editor.editRecord': 'Edit {collection} #{id}',
|
|
@@ -61,6 +53,8 @@ export const en: Catalog = {
|
|
|
61
53
|
'editor.fixBlockContent': 'Some block content is invalid. Check the highlighted block fields.',
|
|
62
54
|
'editor.saveConflict': 'This conflicts with an existing record.',
|
|
63
55
|
'editor.saveFailed': 'Save failed.',
|
|
56
|
+
'editor.quarantined.title': 'Record quarantined',
|
|
57
|
+
'editor.quarantined.desc': 'This record no longer matches its collection’s schema and is shown read-only. It cannot be edited or saved until the underlying data is fixed.',
|
|
64
58
|
'toast.saved': 'Saved',
|
|
65
59
|
'toast.deleted': 'Deleted',
|
|
66
60
|
'toast.duplicated': 'Duplicated',
|
|
@@ -69,7 +63,6 @@ export const en: Catalog = {
|
|
|
69
63
|
'toast.region': 'Notifications',
|
|
70
64
|
'toast.dismiss': 'Dismiss',
|
|
71
65
|
|
|
72
|
-
// collection list
|
|
73
66
|
'list.translations': 'Translations',
|
|
74
67
|
'list.deadRefs': 'Refs',
|
|
75
68
|
'list.filter': 'Filter',
|
|
@@ -113,7 +106,9 @@ export const en: Catalog = {
|
|
|
113
106
|
'list.result': '{total} result',
|
|
114
107
|
'list.results': '{total} results',
|
|
115
108
|
'list.loadError': "Couldn't load this collection.",
|
|
116
|
-
|
|
109
|
+
'list.quarantined': 'Quarantined',
|
|
110
|
+
'list.quarantinedOne': '{n} quarantined record',
|
|
111
|
+
'list.quarantinedCount': '{n} quarantined records',
|
|
117
112
|
'list.selectAll': 'Select all rows on this page',
|
|
118
113
|
'list.selectRow': 'Select {name}',
|
|
119
114
|
'list.selected': '{n} selected',
|
|
@@ -129,8 +124,9 @@ export const en: Catalog = {
|
|
|
129
124
|
'list.deleteRefsUnverified': 'References could not be verified — some records may be left with a broken link. Proceed with caution.',
|
|
130
125
|
'list.opFailed': 'The action could not be completed.',
|
|
131
126
|
'list.refCheckFailed': 'References could not be checked.',
|
|
127
|
+
'list.actionConfirm': 'Run "{name}" on {n} record(s)?',
|
|
128
|
+
'list.actionDone': '{name} done',
|
|
132
129
|
|
|
133
|
-
// editor preview pane
|
|
134
130
|
'preview.ariaLabel': 'Live preview',
|
|
135
131
|
'preview.label': 'Preview',
|
|
136
132
|
'preview.deviceLabel': 'Preview size',
|
|
@@ -142,7 +138,6 @@ export const en: Catalog = {
|
|
|
142
138
|
'preview.width': 'Width',
|
|
143
139
|
'preview.height': 'Height',
|
|
144
140
|
|
|
145
|
-
// page settings (system fields on pageLike collections)
|
|
146
141
|
'pageSettings.slugLabel': 'Slug',
|
|
147
142
|
'pageSettings.slugHint': 'URL path, e.g. /about (blank = auto-generated from the title)',
|
|
148
143
|
'pageSettings.layoutLabel': 'Layout',
|
|
@@ -153,7 +148,6 @@ export const en: Catalog = {
|
|
|
153
148
|
'pageSettings.statusDraft': 'Draft',
|
|
154
149
|
'pageSettings.statusPublished': 'Published',
|
|
155
150
|
|
|
156
|
-
// editor action bar — live preview + status "Ampel"
|
|
157
151
|
'editor.openInNewTab': 'Open in new tab',
|
|
158
152
|
'editor.previewUnsaved': 'Preview unsaved changes in a new tab',
|
|
159
153
|
'editor.previewFailed': 'Could not open the preview.',
|
|
@@ -189,7 +183,6 @@ export const en: Catalog = {
|
|
|
189
183
|
'editorStatus.live.draft': 'Draft — not generated. It stays off the live site until published.',
|
|
190
184
|
'editorStatus.live.updatedAt': 'Last published {time}',
|
|
191
185
|
|
|
192
|
-
// SEO section + Google preview
|
|
193
186
|
'seo.sectionLabel': 'SEO',
|
|
194
187
|
'seo.metaTitle': 'Meta title',
|
|
195
188
|
'seo.metaDescription': 'Meta description',
|
|
@@ -208,7 +201,6 @@ export const en: Catalog = {
|
|
|
208
201
|
'seo.keywords': 'Keywords',
|
|
209
202
|
'seo.keywordsHint': 'Comma-separated topics for this page.',
|
|
210
203
|
|
|
211
|
-
// locale bar (translations)
|
|
212
204
|
'localeBar.fieldLabel': 'Locale',
|
|
213
205
|
'localeBar.groupLabel': 'Content locale',
|
|
214
206
|
'localeBar.editLocale': 'Edit {loc}',
|
|
@@ -216,12 +208,10 @@ export const en: Catalog = {
|
|
|
216
208
|
'localeBar.createTranslation': 'Create {loc} translation',
|
|
217
209
|
'localeBar.copyFailed': "Couldn't copy from {locale}.",
|
|
218
210
|
|
|
219
|
-
// editor history (undo/redo)
|
|
220
211
|
'history.label': 'Edit history',
|
|
221
212
|
'history.undo': 'Undo',
|
|
222
213
|
'history.redo': 'Redo',
|
|
223
214
|
|
|
224
|
-
// block editor
|
|
225
215
|
'blocks.empty': 'No blocks yet.',
|
|
226
216
|
'blocks.moveUp': 'Move block {n} up',
|
|
227
217
|
'blocks.moveDown': 'Move block {n} down',
|
|
@@ -238,7 +228,6 @@ export const en: Catalog = {
|
|
|
238
228
|
'blocks.fieldsLabel': 'Block fields',
|
|
239
229
|
'blocks.treeLabel': 'Block structure',
|
|
240
230
|
|
|
241
|
-
// dead references (derived stale-reference warnings)
|
|
242
231
|
'deadRefs.listWarning': 'Links to a deleted or unpublished record',
|
|
243
232
|
'deadRefs.blockBadge': 'Broken reference',
|
|
244
233
|
'deadRefs.fieldNote': 'Points at a deleted or unpublished record.',
|
|
@@ -256,16 +245,13 @@ export const en: Catalog = {
|
|
|
256
245
|
'refs.reason.unpublished': 'Unpublished',
|
|
257
246
|
'refs.dashAlert': '{n} broken reference(s) — review',
|
|
258
247
|
|
|
259
|
-
// field gallery
|
|
260
248
|
'gallery.title': 'Field gallery',
|
|
261
249
|
'gallery.lede.before': 'Every implemented field widget, live. The relation and internal-link pickers query ',
|
|
262
250
|
'gallery.lede.after': ' — create a post or two to see results.',
|
|
263
251
|
|
|
264
|
-
// media library
|
|
265
252
|
'media.title': 'Media',
|
|
266
253
|
'media.picker.titleSingle': 'Select a file',
|
|
267
254
|
'media.picker.titleMultiple': 'Select media',
|
|
268
|
-
// media — right-click context menu
|
|
269
255
|
'media.menu.cut': 'Cut',
|
|
270
256
|
'media.menu.copy': 'Copy',
|
|
271
257
|
'media.menu.paste': 'Paste',
|
|
@@ -295,7 +281,6 @@ export const en: Catalog = {
|
|
|
295
281
|
'media.selected': 'selected',
|
|
296
282
|
'media.useSelected': 'Use selected',
|
|
297
283
|
'media.dropToUpload': 'Drop files to upload',
|
|
298
|
-
// media — upload conflict resolution
|
|
299
284
|
'media.conflicts.title': 'Resolve upload conflicts',
|
|
300
285
|
'media.conflicts.desc': '{count} file(s) already exist. Choose how to resolve each.',
|
|
301
286
|
'media.conflicts.applyAll': 'Apply to all:',
|
|
@@ -306,20 +291,17 @@ export const en: Catalog = {
|
|
|
306
291
|
'media.conflicts.overwrite': 'Overwrite',
|
|
307
292
|
'media.conflicts.rename': 'Rename',
|
|
308
293
|
'media.conflicts.skip': 'Skip',
|
|
309
|
-
// media — new folder / rename dialogs
|
|
310
294
|
'media.newFolder': 'New folder',
|
|
311
295
|
'media.folderName': 'Folder name',
|
|
312
296
|
'media.folderNamePlaceholder': 'e.g. holiday',
|
|
313
297
|
'media.rename': 'Rename',
|
|
314
298
|
'media.newName': 'New name',
|
|
315
|
-
// media — delete dialog
|
|
316
299
|
'media.deleteSummary': 'Delete {files} {filesUnit} and {folders} {foldersUnit} — {size}.',
|
|
317
300
|
'media.file': 'file',
|
|
318
301
|
'media.files': 'files',
|
|
319
302
|
'media.folder': 'folder',
|
|
320
303
|
'media.folders': 'folders',
|
|
321
304
|
'media.usageWarning': 'These files are used by published content and links will break:',
|
|
322
|
-
// media — move/copy conflict dialog
|
|
323
305
|
'media.conflictTitleMove': 'Move — conflicts',
|
|
324
306
|
'media.conflictTitleCopy': 'Copy — conflicts',
|
|
325
307
|
'media.conflictItems': '{count} item(s) already exist in "{dest}":',
|
|
@@ -329,7 +311,6 @@ export const en: Catalog = {
|
|
|
329
311
|
'media.skip': 'Skip',
|
|
330
312
|
'media.replace': 'Replace',
|
|
331
313
|
'media.keepBoth': 'Keep both',
|
|
332
|
-
// media — table view
|
|
333
314
|
'media.colName': 'Name',
|
|
334
315
|
'media.colType': 'Type',
|
|
335
316
|
'media.colSize': 'Size',
|
|
@@ -337,7 +318,6 @@ export const en: Catalog = {
|
|
|
337
318
|
'media.typeFolder': 'Folder',
|
|
338
319
|
'media.parentFolder': 'Go to the parent folder',
|
|
339
320
|
|
|
340
|
-
// media — viewer
|
|
341
321
|
'mediaViewer.folder': 'Folder',
|
|
342
322
|
'mediaViewer.uploaded': 'Uploaded',
|
|
343
323
|
'mediaViewer.alt': 'Alt text',
|
|
@@ -351,7 +331,6 @@ export const en: Catalog = {
|
|
|
351
331
|
'mediaViewer.aiNote': 'Disclosure note',
|
|
352
332
|
'mediaViewer.aiNoteHint': 'Free text, e.g. the tool used. Pre-filled with any AI signals found in the file at upload — evidence only, not a verified claim.',
|
|
353
333
|
|
|
354
|
-
// richtext toolbar
|
|
355
334
|
'richtext.bold': 'Bold',
|
|
356
335
|
'richtext.italic': 'Italic',
|
|
357
336
|
'richtext.underline': 'Underline',
|
|
@@ -386,7 +365,6 @@ export const en: Catalog = {
|
|
|
386
365
|
'richtext.linkUrl': 'Link URL',
|
|
387
366
|
'richtext.apply': 'Apply',
|
|
388
367
|
|
|
389
|
-
// media toolbar
|
|
390
368
|
'mediaToolbar.searchPlaceholder': 'Search…',
|
|
391
369
|
'mediaToolbar.searchAriaLabel': 'Search media',
|
|
392
370
|
'mediaToolbar.viewGrid': 'Grid',
|
|
@@ -395,18 +373,14 @@ export const en: Catalog = {
|
|
|
395
373
|
'mediaToolbar.upload': 'Upload',
|
|
396
374
|
'mediaToolbar.newFolder': 'New folder',
|
|
397
375
|
|
|
398
|
-
// media path bar
|
|
399
376
|
'mediaPath.navAriaLabel': 'Path',
|
|
400
377
|
'mediaPath.editAriaLabel': 'Edit path',
|
|
401
378
|
'mediaPath.inputAriaLabel': 'Enter path',
|
|
402
379
|
|
|
403
|
-
// collections nav
|
|
404
380
|
'nav.collections': 'Collections',
|
|
405
381
|
|
|
406
|
-
// field widgets — datetime
|
|
407
382
|
'field.datetime.range_start': 'Start',
|
|
408
383
|
'field.datetime.range_end': 'End',
|
|
409
|
-
// field widgets — link
|
|
410
384
|
'field.link.type_internal': 'Internal',
|
|
411
385
|
'field.link.type_url': 'URL',
|
|
412
386
|
'field.link.type_email': 'Email',
|
|
@@ -415,14 +389,11 @@ export const en: Catalog = {
|
|
|
415
389
|
'field.link.url_placeholder': 'https://…',
|
|
416
390
|
'field.link.label_placeholder': 'Link text (optional)',
|
|
417
391
|
'field.link.hash_placeholder': 'Anchor (optional, e.g. section-id)',
|
|
418
|
-
// field widgets — relation
|
|
419
392
|
'field.relation.placeholder': 'Search…',
|
|
420
|
-
// field widgets — internal-link picker
|
|
421
393
|
'field.linkPicker.loadError': 'Could not load collections.',
|
|
422
394
|
'field.linkPicker.chooseCollection': 'Choose a collection…',
|
|
423
395
|
'field.linkPicker.collectionLabel': 'Collection',
|
|
424
396
|
'field.linkPicker.searchRecords': 'Search records…',
|
|
425
|
-
// field widgets — repeater
|
|
426
397
|
'field.repeater.moved': 'Moved item to position {n} of {total}',
|
|
427
398
|
'field.repeater.removed': 'Item {n} removed, {remaining} remaining',
|
|
428
399
|
'field.repeater.duplicated': 'Duplicated item {n}',
|
|
@@ -435,7 +406,6 @@ export const en: Catalog = {
|
|
|
435
406
|
'field.repeater.remove_label': 'Remove item {n}',
|
|
436
407
|
'field.repeater.empty': 'No items yet.',
|
|
437
408
|
'field.repeater.add': 'Add item',
|
|
438
|
-
// field widgets — media
|
|
439
409
|
'field.media.empty': 'No media selected.',
|
|
440
410
|
'field.media.add': 'Add media',
|
|
441
411
|
'field.media.replace': 'Replace',
|
|
@@ -446,7 +416,6 @@ export const en: Catalog = {
|
|
|
446
416
|
'field.media.move_later': 'Move {name} later',
|
|
447
417
|
'field.media.moved': 'Moved {name} to position {pos} of {total}',
|
|
448
418
|
|
|
449
|
-
// combobox
|
|
450
419
|
'combobox.moved': 'Moved {label} to position {pos} of {total}',
|
|
451
420
|
'combobox.move_earlier': 'Move {label} earlier',
|
|
452
421
|
'combobox.move_later': 'Move {label} later',
|
|
@@ -456,7 +425,6 @@ export const en: Catalog = {
|
|
|
456
425
|
'combobox.loading': 'Loading…',
|
|
457
426
|
'combobox.no_results': 'No results',
|
|
458
427
|
|
|
459
|
-
// date pickers
|
|
460
428
|
'datePicker.openCalendar': 'Open calendar',
|
|
461
429
|
'datePicker.previousMonth': 'Previous month',
|
|
462
430
|
'datePicker.nextMonth': 'Next month',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FieldDef } from '
|
|
1
|
+
import type { FieldDef } from '@michaelthielemann/kestrel-core'
|
|
2
2
|
|
|
3
3
|
/** A record reference: its id plus a human label. Shared by UiCombobox and FieldRelation. */
|
|
4
4
|
export interface FieldOption { value: number; label: string }
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineAsyncComponent, type Component } from 'vue'
|
|
2
|
-
import type { FieldType } from '
|
|
2
|
+
import type { FieldType } from '@michaelthielemann/kestrel-core'
|
|
3
3
|
import FieldText from '../components/field/Text.vue'
|
|
4
4
|
import FieldNumber from '../components/field/Number.vue'
|
|
5
5
|
import FieldBoolean from '../components/field/Boolean.vue'
|
|
@@ -3,19 +3,14 @@ export type IconName =
|
|
|
3
3
|
| 'copy' | 'trash' | 'x' | 'plus' | 'pencil' | 'check'
|
|
4
4
|
| 'home' | 'file-text' | 'folder' | 'folder-plus' | 'panel-left-close' | 'panel-left-open' | 'log-out'
|
|
5
5
|
| 'sun' | 'moon' | 'calendar' | 'upload' | 'layout-grid' | 'table'
|
|
6
|
-
// collection icons + list toolbar
|
|
7
6
|
| 'settings' | 'image' | 'newspaper' | 'columns' | 'filter' | 'search' | 'sliders'
|
|
8
|
-
// richtext toolbar
|
|
9
7
|
| 'bold' | 'italic' | 'underline' | 'strikethrough' | 'code' | 'square-code' | 'highlighter'
|
|
10
8
|
| 'subscript' | 'superscript' | 'list' | 'list-ordered' | 'quote' | 'minus' | 'message-square'
|
|
11
9
|
| 'align-left' | 'align-center' | 'align-right' | 'align-justify'
|
|
12
10
|
| 'eraser' | 'undo' | 'redo' | 'link' | 'link-2'
|
|
13
|
-
| 'external-link' | 'triangle-alert'
|
|
14
|
-
// password reveal
|
|
11
|
+
| 'external-link' | 'triangle-alert' | 'globe'
|
|
15
12
|
| 'eye' | 'eye-off'
|
|
16
|
-
// preview device widths + reload
|
|
17
13
|
| 'monitor' | 'tablet' | 'smartphone' | 'rotate-cw'
|
|
18
|
-
// block-type tiles
|
|
19
14
|
| 'clock' | 'user'
|
|
20
15
|
|
|
21
16
|
export const icons: Record<IconName, string> = {
|
|
@@ -74,6 +69,7 @@ export const icons: Record<IconName, string> = {
|
|
|
74
69
|
'link': '<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>',
|
|
75
70
|
'link-2': '<path d="M9 17H7A5 5 0 0 1 7 7h2"/><path d="M15 7h2a5 5 0 1 1 0 10h-2"/><line x1="8" x2="16" y1="12" y2="12"/>',
|
|
76
71
|
'external-link': '<path d="M15 3h6v6"/><path d="M10 14 21 3"/><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h6"/>',
|
|
72
|
+
'globe': '<circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/>',
|
|
77
73
|
'triangle-alert': '<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/>',
|
|
78
74
|
'settings': '<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/>',
|
|
79
75
|
'image': '<rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/>',
|
package/nuxt.config.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import kestrel from './kestrel.config'
|
|
2
|
-
import { layerKestrelOption } from '
|
|
2
|
+
import { layerKestrelOption } from '@michaelthielemann/kestrel-core'
|
|
3
3
|
|
|
4
4
|
// Silence known-harmless, un-actionable THIRD-PARTY build noise (output unaffected — applied to both the
|
|
5
5
|
// Vite client and the Nitro server Rollup passes). Everything else still surfaces — crucially, any
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@michaelthielemann/kestrel",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "A slim, collection-driven Nuxt 4 CMS meta-layer with a runtime schema engine. Add `extends: ['@michaelthielemann/kestrel']`, define collections, and the database migrates itself.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Michael Thielemann <283621694+MichaelThielemann@users.noreply.github.com>",
|
|
@@ -24,7 +24,6 @@
|
|
|
24
24
|
"bin": {
|
|
25
25
|
"kestrel": "./scripts/kestrel.mjs"
|
|
26
26
|
},
|
|
27
|
-
"packageManager": "pnpm@11.9.0",
|
|
28
27
|
"publishConfig": {
|
|
29
28
|
"access": "public",
|
|
30
29
|
"provenance": true
|
|
@@ -45,21 +44,6 @@
|
|
|
45
44
|
"node": ">=22.12.0"
|
|
46
45
|
},
|
|
47
46
|
"//lint": "The root eslint.config.mjs imports the generated playground/.nuxt/eslint.config.mjs (Nuxt-aware: auto-import globals for every layer + extension) — `nuxt prepare` must run first or that import 404s on a fresh checkout. Cheap on a warm .nuxt cache, so it's chained rather than a separate step.",
|
|
48
|
-
"scripts": {
|
|
49
|
-
"dev": "nuxt dev",
|
|
50
|
-
"build": "nuxt build",
|
|
51
|
-
"preview": "nuxt preview",
|
|
52
|
-
"generate": "nuxt generate",
|
|
53
|
-
"db:generate": "drizzle-kit generate",
|
|
54
|
-
"db:migrate": "drizzle-kit migrate",
|
|
55
|
-
"test": "vitest run",
|
|
56
|
-
"test:watch": "vitest",
|
|
57
|
-
"test:e2e": "vitest run --config vitest.e2e.config.ts",
|
|
58
|
-
"test:nuxt": "vitest run --config vitest.nuxt.config.ts",
|
|
59
|
-
"typecheck": "node scripts/typecheck.mjs",
|
|
60
|
-
"lint": "nuxt prepare playground && eslint .",
|
|
61
|
-
"hash-password": "node scripts/hash-password.mjs"
|
|
62
|
-
},
|
|
63
47
|
"dependencies": {
|
|
64
48
|
"@babel/parser": "^7.29.8",
|
|
65
49
|
"@internationalized/date": "^3.12.3",
|
|
@@ -78,10 +62,12 @@
|
|
|
78
62
|
"dompurify": "^3.4.13",
|
|
79
63
|
"drizzle-orm": "^0.45.2",
|
|
80
64
|
"drizzle-zod": "^0.8.3",
|
|
65
|
+
"effect": "~3.22.1",
|
|
81
66
|
"exifr": "^7.1.3",
|
|
82
67
|
"file-type": "^22.0.1",
|
|
83
68
|
"jsdom": "^29.1.1",
|
|
84
69
|
"nanoid": "^5.1.16",
|
|
70
|
+
"nitropack": "^2.13.4",
|
|
85
71
|
"nuxt": "^4.5.2",
|
|
86
72
|
"reka-ui": "^2.10.1",
|
|
87
73
|
"sanitize-html": "^2.17.6",
|
|
@@ -89,7 +75,17 @@
|
|
|
89
75
|
"sharp": "^0.35.3",
|
|
90
76
|
"thumbhash": "^0.1.1",
|
|
91
77
|
"typescript": "^6.0.3",
|
|
92
|
-
"zod": "^4.4.3"
|
|
78
|
+
"zod": "^4.4.3",
|
|
79
|
+
"@michaelthielemann/kestrel-access": "0.1.0",
|
|
80
|
+
"@michaelthielemann/kestrel-auth": "0.1.0",
|
|
81
|
+
"@michaelthielemann/kestrel-core": "0.1.0",
|
|
82
|
+
"@michaelthielemann/kestrel-contracts": "0.1.0",
|
|
83
|
+
"@michaelthielemann/kestrel-collections": "0.1.0",
|
|
84
|
+
"@michaelthielemann/kestrel-delivery-live": "0.1.0",
|
|
85
|
+
"@michaelthielemann/kestrel-delivery-static": "0.1.0",
|
|
86
|
+
"@michaelthielemann/kestrel-fields": "0.1.0",
|
|
87
|
+
"@michaelthielemann/kestrel-media": "0.1.0",
|
|
88
|
+
"@michaelthielemann/kestrel-publishing": "0.1.0"
|
|
93
89
|
},
|
|
94
90
|
"//peers": "Framework singletons the layer sources import by bare specifier. Peers, not dependencies: a second copy breaks identity (two Vue instances; an h3 v1 `createError` handed to an h3 v2 error handler, which surfaces our 400/404/409 as bare 500s). All optional, because kestrel's own `nuxt` dependency already supplies them transitively — the entry pins identity where a copy exists rather than demanding one, and a required peer would ERESOLVE-fail npm installs on any Nuxt whose bundled range we did not anticipate. Each is also a devDependency so this repo resolves it without auto-install-peers writing a phantom root dependency into the lockfile.",
|
|
95
91
|
"peerDependencies": {
|
|
@@ -113,20 +109,58 @@
|
|
|
113
109
|
}
|
|
114
110
|
},
|
|
115
111
|
"devDependencies": {
|
|
112
|
+
"@arethetypeswrong/cli": "^0.18.5",
|
|
113
|
+
"@fast-check/vitest": "^0.4.1",
|
|
114
|
+
"@microsoft/api-extractor": "^7.58.13",
|
|
116
115
|
"@nuxt/eslint": "^1.9.0",
|
|
117
116
|
"@nuxt/kit": "^4.5.2",
|
|
118
117
|
"@nuxt/test-utils": "^4.1.0",
|
|
118
|
+
"@stryker-mutator/core": "^10.0.0",
|
|
119
|
+
"@stryker-mutator/vitest-runner": "^10.0.0",
|
|
119
120
|
"@vitejs/plugin-vue": "^6.0.8",
|
|
121
|
+
"@vitest/coverage-v8": "4.1.10",
|
|
120
122
|
"@vue/test-utils": "^2.4.11",
|
|
121
123
|
"drizzle-kit": "^0.31.10",
|
|
122
124
|
"eslint": "^9.39.1",
|
|
125
|
+
"eslint-plugin-boundaries": "^7.2.0",
|
|
126
|
+
"eslint-plugin-jsdoc": "^64.2.1",
|
|
127
|
+
"eslint-plugin-tsdoc": "^0.5.2",
|
|
123
128
|
"eslint-plugin-vuejs-accessibility": "^2.4.1",
|
|
129
|
+
"fast-check": "^4.9.0",
|
|
124
130
|
"h3": "^1.15.11",
|
|
125
131
|
"happy-dom": "^20.11.2",
|
|
126
132
|
"playwright-core": "^1.62.1",
|
|
133
|
+
"publint": "^0.3.24",
|
|
134
|
+
"typedoc": "^0.28.20",
|
|
127
135
|
"vitest": "^4.1.10",
|
|
136
|
+
"vitest-openapi": "^1.0.3",
|
|
128
137
|
"vue": "3.5.35",
|
|
129
138
|
"vue-router": "^5.2.0",
|
|
130
139
|
"vue-tsc": "^3.3.9"
|
|
140
|
+
},
|
|
141
|
+
"scripts": {
|
|
142
|
+
"dev": "nuxt dev",
|
|
143
|
+
"build": "nuxt build",
|
|
144
|
+
"build:packages": "pnpm -F @michaelthielemann/kestrel-contracts build && pnpm -F @michaelthielemann/kestrel-core build && pnpm -F @michaelthielemann/kestrel-fields build && pnpm -F @michaelthielemann/kestrel-auth build && pnpm -F @michaelthielemann/kestrel-access build && pnpm -F @michaelthielemann/kestrel-media build && pnpm -F @michaelthielemann/kestrel-collections build && pnpm -F @michaelthielemann/kestrel-publishing build && pnpm -F @michaelthielemann/kestrel-delivery-live build && pnpm -F @michaelthielemann/kestrel-delivery-static build",
|
|
145
|
+
"preview": "nuxt preview",
|
|
146
|
+
"generate": "nuxt generate",
|
|
147
|
+
"db:generate": "drizzle-kit generate",
|
|
148
|
+
"db:migrate": "drizzle-kit migrate",
|
|
149
|
+
"test": "vitest run",
|
|
150
|
+
"test:coverage": "node scripts/coverage-all.mjs",
|
|
151
|
+
"test:watch": "vitest",
|
|
152
|
+
"test:e2e": "vitest run --config vitest.e2e.config.ts",
|
|
153
|
+
"test:nuxt": "vitest run --config vitest.nuxt.config.ts",
|
|
154
|
+
"test:packages": "pnpm --filter \"@michaelthielemann/kestrel-*\" -r test",
|
|
155
|
+
"typecheck": "node scripts/typecheck.mjs",
|
|
156
|
+
"typecheck:packages": "pnpm --filter \"@michaelthielemann/kestrel-*\" -r typecheck",
|
|
157
|
+
"lint": "nuxt prepare playground && eslint .",
|
|
158
|
+
"docs:api": "nuxt prepare playground && typedoc",
|
|
159
|
+
"api:check": "pnpm -F @michaelthielemann/kestrel-contracts build && api-extractor run -c packages/kestrel-contracts/api-extractor.json && pnpm -F @michaelthielemann/kestrel-core build && api-extractor run -c packages/kestrel-core/api-extractor.json && api-extractor run -c packages/kestrel-core/api-extractor.client.json && pnpm -F @michaelthielemann/kestrel-fields build && api-extractor run -c packages/kestrel-fields/api-extractor.json && api-extractor run -c packages/kestrel-fields/api-extractor.client.json && pnpm -F @michaelthielemann/kestrel-auth build && api-extractor run -c packages/kestrel-auth/api-extractor.json && pnpm -F @michaelthielemann/kestrel-access build && api-extractor run -c packages/kestrel-access/api-extractor.json && pnpm -F @michaelthielemann/kestrel-media build && api-extractor run -c packages/kestrel-media/api-extractor.json && pnpm -F @michaelthielemann/kestrel-collections build && api-extractor run -c packages/kestrel-collections/api-extractor.json && pnpm -F @michaelthielemann/kestrel-publishing build && api-extractor run -c packages/kestrel-publishing/api-extractor.json && pnpm -F @michaelthielemann/kestrel-delivery-live build && api-extractor run -c packages/kestrel-delivery-live/api-extractor.json && pnpm -F @michaelthielemann/kestrel-delivery-static build && api-extractor run -c packages/kestrel-delivery-static/api-extractor.json",
|
|
160
|
+
"api:update": "pnpm -F @michaelthielemann/kestrel-contracts build && api-extractor run --local -c packages/kestrel-contracts/api-extractor.json && pnpm -F @michaelthielemann/kestrel-core build && api-extractor run --local -c packages/kestrel-core/api-extractor.json && api-extractor run --local -c packages/kestrel-core/api-extractor.client.json && pnpm -F @michaelthielemann/kestrel-fields build && api-extractor run --local -c packages/kestrel-fields/api-extractor.json && api-extractor run --local -c packages/kestrel-fields/api-extractor.client.json && pnpm -F @michaelthielemann/kestrel-auth build && api-extractor run --local -c packages/kestrel-auth/api-extractor.json && pnpm -F @michaelthielemann/kestrel-access build && api-extractor run --local -c packages/kestrel-access/api-extractor.json && pnpm -F @michaelthielemann/kestrel-media build && api-extractor run --local -c packages/kestrel-media/api-extractor.json && pnpm -F @michaelthielemann/kestrel-collections build && api-extractor run --local -c packages/kestrel-collections/api-extractor.json && pnpm -F @michaelthielemann/kestrel-publishing build && api-extractor run --local -c packages/kestrel-publishing/api-extractor.json && pnpm -F @michaelthielemann/kestrel-delivery-live build && api-extractor run --local -c packages/kestrel-delivery-live/api-extractor.json && pnpm -F @michaelthielemann/kestrel-delivery-static build && api-extractor run --local -c packages/kestrel-delivery-static/api-extractor.json",
|
|
161
|
+
"pkg:lint": "publint . && pnpm -F @michaelthielemann/kestrel-contracts build && publint packages/kestrel-contracts && attw --pack packages/kestrel-contracts --profile esm-only && pnpm -F @michaelthielemann/kestrel-core build && publint packages/kestrel-core && attw --pack packages/kestrel-core --profile esm-only && pnpm -F @michaelthielemann/kestrel-fields build && publint packages/kestrel-fields && attw --pack packages/kestrel-fields --profile esm-only && pnpm -F @michaelthielemann/kestrel-auth build && publint packages/kestrel-auth && attw --pack packages/kestrel-auth --profile esm-only && pnpm -F @michaelthielemann/kestrel-access build && publint packages/kestrel-access && attw --pack packages/kestrel-access --profile esm-only && pnpm -F @michaelthielemann/kestrel-media build && publint packages/kestrel-media && attw --pack packages/kestrel-media --profile esm-only && pnpm -F @michaelthielemann/kestrel-collections build && publint packages/kestrel-collections && attw --pack packages/kestrel-collections --profile esm-only && pnpm -F @michaelthielemann/kestrel-publishing build && publint packages/kestrel-publishing && attw --pack packages/kestrel-publishing --profile esm-only && pnpm -F @michaelthielemann/kestrel-delivery-live build && publint packages/kestrel-delivery-live && attw --pack packages/kestrel-delivery-live --profile esm-only && pnpm -F @michaelthielemann/kestrel-delivery-static build && publint packages/kestrel-delivery-static && attw --pack packages/kestrel-delivery-static --profile esm-only",
|
|
162
|
+
"ci:consumer-template": "node scripts/consumer-template-ci.mjs",
|
|
163
|
+
"dashboard": "npx tsx scripts/dashboard.mjs",
|
|
164
|
+
"hash-password": "node scripts/hash-password.mjs"
|
|
131
165
|
}
|
|
132
|
-
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// The ONLY real package-consumption test. Every earlier gate (api:check, pkg:lint) inspects one
|
|
2
|
+
// package's own tarball in isolation; this script is the one that scaffolds an actual consumer from
|
|
3
|
+
// templates/starter (via create-kestrel's real CLI), packs the engine + every @michaelthielemann/kestrel-* package the way
|
|
4
|
+
// `npm publish` would, installs them into that consumer with a real `npm install` (no workspace protocol,
|
|
5
|
+
// no symlinks back into this repo), builds it, boots the production server, and hits it over real HTTP.
|
|
6
|
+
//
|
|
7
|
+
// Local: `pnpm ci:consumer-template`. CI: the `consumer-template` job in .github/workflows/ci.yml.
|
|
8
|
+
import { execFileSync, spawn } from 'node:child_process'
|
|
9
|
+
import { mkdtempSync, mkdirSync, readFileSync, writeFileSync, rmSync } from 'node:fs'
|
|
10
|
+
import { tmpdir } from 'node:os'
|
|
11
|
+
import { join } from 'node:path'
|
|
12
|
+
import { setTimeout as sleep } from 'node:timers/promises'
|
|
13
|
+
|
|
14
|
+
const root = process.cwd()
|
|
15
|
+
const log = (msg) => console.log(`[consumer-template-ci] ${msg}`)
|
|
16
|
+
const run = (cmd, args, opts = {}) => execFileSync(cmd, args, { stdio: 'inherit', ...opts })
|
|
17
|
+
|
|
18
|
+
// @michaelthielemann/kestrel-* in the order `pnpm --filter "@michaelthielemann/kestrel-*" -r exec` resolves them (a real topological walk of
|
|
19
|
+
// the workspace graph, not a hand guess) — mirrors the publish order in .github/workflows/release.yml.
|
|
20
|
+
const PACKAGES = ['contracts', 'core', 'auth', 'access', 'fields', 'collections', 'media', 'publishing', 'delivery-live', 'delivery-static']
|
|
21
|
+
|
|
22
|
+
const work = mkdtempSync(join(tmpdir(), 'kestrel-consumer-ci-'))
|
|
23
|
+
const tarballDir = join(work, 'tarballs')
|
|
24
|
+
const consumerDir = join(work, 'site')
|
|
25
|
+
mkdirSync(tarballDir, { recursive: true })
|
|
26
|
+
log(`work dir: ${work}`)
|
|
27
|
+
|
|
28
|
+
let server
|
|
29
|
+
try {
|
|
30
|
+
log('building @michaelthielemann/kestrel-* packages (dist/ must exist before pnpm pack)')
|
|
31
|
+
run('pnpm', ['--filter', '@michaelthielemann/kestrel-*', '-r', 'build'], { cwd: root })
|
|
32
|
+
|
|
33
|
+
log('packing the engine + every @michaelthielemann/kestrel-* package as npm publish would')
|
|
34
|
+
const tarball = {}
|
|
35
|
+
for (const dir of ['.', ...PACKAGES.map((n) => `packages/kestrel-${n}`)]) {
|
|
36
|
+
const out = execFileSync('pnpm', ['pack', '--pack-destination', tarballDir], { cwd: join(root, dir) }).toString().trim()
|
|
37
|
+
const file = out.split('\n').pop().trim()
|
|
38
|
+
const name = JSON.parse(readFileSync(join(root, dir, 'package.json'), 'utf8')).name
|
|
39
|
+
tarball[name] = file
|
|
40
|
+
log(` ${name} -> ${file}`)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
log('scaffolding a consumer from templates/starter via the real create-kestrel CLI')
|
|
44
|
+
const password = 'consumer-ci-password'
|
|
45
|
+
run('node', [join(root, 'packages/create-kestrel/index.mjs'), consumerDir, '--yes', '--password', password], { cwd: work })
|
|
46
|
+
|
|
47
|
+
log('pointing the scaffolded package.json at the packed tarballs (no registry, no workspace protocol)')
|
|
48
|
+
const pkgPath = join(consumerDir, 'package.json')
|
|
49
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
|
|
50
|
+
pkg.dependencies['@michaelthielemann/kestrel'] = `file:${tarball['@michaelthielemann/kestrel']}`
|
|
51
|
+
// npm's `overrides` redirects a dependency AT ANY DEPTH — the engine tarball's own manifest declares
|
|
52
|
+
// "@michaelthielemann/kestrel-core": "0.1.0" etc. (pnpm pack already rewrote the workspace:* protocol to that real
|
|
53
|
+
// version), which would otherwise 404 against the real registry since these packages are not published
|
|
54
|
+
// yet in this checkout's tag. This is what actually forces npm's resolver to read each package's real
|
|
55
|
+
// `exports` map / `main` / `types` from a standalone tarball, the same way a real end user's install
|
|
56
|
+
// would — not a symlink into this repo's own packages/*/src.
|
|
57
|
+
pkg.overrides = Object.fromEntries(PACKAGES.map((n) => [`@michaelthielemann/kestrel-${n}`, `file:${tarball[`@michaelthielemann/kestrel-${n}`]}`]))
|
|
58
|
+
writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`)
|
|
59
|
+
|
|
60
|
+
// The runner's ambient npm (10.9.8 as of this writing) hits a real npm/arborist bug resolving nuxt's
|
|
61
|
+
// large optional-peer set (`TypeError: Cannot read properties of null (reading 'edgesOut')`, inside
|
|
62
|
+
// `#loadPeerSet`) — reproduced directly, unrelated to anything this script does. npm >= 11 does not
|
|
63
|
+
// have it. `npx --yes npm@11` sidesteps needing a global npm upgrade (which needs root the runner may
|
|
64
|
+
// not have) while still using real npm, not pnpm.
|
|
65
|
+
log('npm install (real npm 11 via npx — the most representative simulation of an actual end user)')
|
|
66
|
+
run('npx', ['--yes', 'npm@11', 'install'], { cwd: consumerDir })
|
|
67
|
+
|
|
68
|
+
log('verifying no @michaelthielemann/kestrel-* dependency resolved back into this repo (would prove nothing)')
|
|
69
|
+
for (const n of PACKAGES) {
|
|
70
|
+
let resolved = ''
|
|
71
|
+
try {
|
|
72
|
+
resolved = execFileSync('npm', ['ls', `@michaelthielemann/kestrel-${n}`, '--json'], { cwd: consumerDir }).toString()
|
|
73
|
+
} catch (err) {
|
|
74
|
+
// `npm ls` exits non-zero on a peer-dep mismatch warning even when the tree itself resolved fine;
|
|
75
|
+
// its stdout (on the error object) still carries the real tree, which is what matters here.
|
|
76
|
+
resolved = err.stdout?.toString() ?? ''
|
|
77
|
+
}
|
|
78
|
+
if (resolved.includes(root)) throw new Error(`@michaelthielemann/kestrel-${n} resolved into ${root} — the override did not take effect`)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
log('reading the .env create-kestrel wrote (session secret + admin password hash)')
|
|
82
|
+
const env = Object.fromEntries(
|
|
83
|
+
readFileSync(join(consumerDir, '.env'), 'utf8')
|
|
84
|
+
.split('\n')
|
|
85
|
+
.map((l) => /^([A-Z][A-Z0-9_]*)=(.*)$/.exec(l))
|
|
86
|
+
.filter(Boolean)
|
|
87
|
+
.map((m) => [m[1], m[2]]),
|
|
88
|
+
)
|
|
89
|
+
// KESTREL_SECURE_COOKIES=false is what create-kestrel writes for a brand-new site (local http dev) —
|
|
90
|
+
// the app itself refuses it under a production boot ("KESTREL_SECURE_COOKIES=false is not allowed in
|
|
91
|
+
// production"), which is the correct, secure-by-default behavior, not a bug this script should route
|
|
92
|
+
// around by keeping the flag. Unset it; the production default is secure.
|
|
93
|
+
delete env.KESTREL_SECURE_COOKIES
|
|
94
|
+
|
|
95
|
+
const devPort = 41822
|
|
96
|
+
log('nuxt dev (brief boot, then kill) — schema auto-syncs additively at dev boot; a production boot never touches it (by design), so this is what a real `pnpm dev` first run also does')
|
|
97
|
+
const dev = spawn('npx', ['nuxt', 'dev', '--port', String(devPort)], {
|
|
98
|
+
cwd: consumerDir,
|
|
99
|
+
env: { ...process.env, ...env },
|
|
100
|
+
stdio: 'inherit',
|
|
101
|
+
})
|
|
102
|
+
try {
|
|
103
|
+
await waitForServer(`http://localhost:${devPort}/robots.txt`, 60000)
|
|
104
|
+
} finally {
|
|
105
|
+
dev.kill()
|
|
106
|
+
await sleep(1000) // let the dev Nitro process release the sqlite file before the production build reopens it
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
log('nuxt build (production) — the real exports-map/deps resolution surface, not a dev-mode shortcut')
|
|
110
|
+
run('npx', ['nuxt', 'build'], { cwd: consumerDir, env: { ...process.env, ...env } })
|
|
111
|
+
|
|
112
|
+
log('booting the built server and hitting it over real HTTP')
|
|
113
|
+
const port = 41823
|
|
114
|
+
server = spawn('node', [join(consumerDir, '.output/server/index.mjs')], {
|
|
115
|
+
cwd: consumerDir,
|
|
116
|
+
env: { ...process.env, ...env, PORT: String(port), NITRO_PORT: String(port) },
|
|
117
|
+
stdio: 'inherit',
|
|
118
|
+
})
|
|
119
|
+
await waitForServer(`http://localhost:${port}/robots.txt`, 30000)
|
|
120
|
+
|
|
121
|
+
const checks = [
|
|
122
|
+
['GET /robots.txt', `http://localhost:${port}/robots.txt`, 200],
|
|
123
|
+
['GET /sitemap.xml', `http://localhost:${port}/sitemap.xml`, 200],
|
|
124
|
+
['GET /admin (login page, SPA shell)', `http://localhost:${port}/admin`, 200],
|
|
125
|
+
]
|
|
126
|
+
for (const [label, url, wantStatus] of checks) {
|
|
127
|
+
const res = await fetch(url)
|
|
128
|
+
if (res.status !== wantStatus) throw new Error(`${label} -> ${res.status}, expected ${wantStatus}`)
|
|
129
|
+
log(` ${label} -> ${res.status} OK`)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const loginRes = await fetch(`http://localhost:${port}/api/login`, {
|
|
133
|
+
method: 'POST',
|
|
134
|
+
headers: { 'content-type': 'application/json' },
|
|
135
|
+
body: JSON.stringify({ password }),
|
|
136
|
+
})
|
|
137
|
+
if (loginRes.status !== 200) throw new Error(`POST /api/login -> ${loginRes.status}, expected 200 (the packed admin auth stack did not resolve)`)
|
|
138
|
+
log(' POST /api/login -> 200 OK (the packed @michaelthielemann/kestrel-auth + @michaelthielemann/kestrel-access stack authenticates for real)')
|
|
139
|
+
|
|
140
|
+
log('PASS — a real npm install of the packed engine + packages boots and serves under production, and login round-trips through the packed auth stack.')
|
|
141
|
+
} finally {
|
|
142
|
+
server?.kill()
|
|
143
|
+
if (!process.env.KEEP_CONSUMER_TEMPLATE_CI_WORKDIR) rmSync(work, { recursive: true, force: true })
|
|
144
|
+
else log(`KEEP_CONSUMER_TEMPLATE_CI_WORKDIR set — leaving ${work} in place`)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function waitForServer(url, timeoutMs) {
|
|
148
|
+
const deadline = Date.now() + timeoutMs
|
|
149
|
+
for (;;) {
|
|
150
|
+
try {
|
|
151
|
+
const res = await fetch(url)
|
|
152
|
+
if (res.status) return
|
|
153
|
+
} catch { /* not up yet */ }
|
|
154
|
+
if (Date.now() > deadline) throw new Error(`server did not come up at ${url} within ${timeoutMs}ms`)
|
|
155
|
+
await sleep(250)
|
|
156
|
+
}
|
|
157
|
+
}
|