@michaelthielemann/kestrel 4.1.2 → 5.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 +26 -201
- package/dist/boot.d.ts +45 -0
- package/dist/boot.js +274 -0
- package/dist/cast.d.ts +3 -0
- package/dist/cast.js +6 -0
- package/dist/catalogue.d.ts +11 -0
- package/dist/catalogue.js +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +55 -0
- package/dist/context.d.ts +62 -0
- package/dist/context.js +61 -0
- package/dist/dataflow.d.ts +3 -0
- package/dist/dataflow.js +65 -0
- package/dist/defineConfig.d.ts +157 -0
- package/dist/defineConfig.js +41 -0
- package/dist/defineContract.d.ts +12 -0
- package/dist/defineContract.js +16 -0
- package/dist/defineModule.d.ts +75 -0
- package/dist/defineModule.js +9 -0
- package/dist/definePipeline.d.ts +7 -0
- package/dist/definePipeline.js +11 -0
- package/dist/errors.d.ts +26 -0
- package/dist/errors.js +48 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +14 -0
- package/dist/load.d.ts +7 -0
- package/dist/load.js +79 -0
- package/dist/logger.d.ts +18 -0
- package/dist/logger.js +36 -0
- package/dist/registry.d.ts +14 -0
- package/dist/registry.js +87 -0
- package/dist/result.d.ts +19 -0
- package/dist/result.js +19 -0
- package/dist/runner.d.ts +29 -0
- package/dist/runner.js +133 -0
- package/dist/schema.d.ts +7 -0
- package/dist/schema.js +142 -0
- package/dist/sort.d.ts +2 -0
- package/dist/sort.js +50 -0
- package/dist/testing/runPipeline.d.ts +9 -0
- package/dist/testing/runPipeline.js +22 -0
- package/dist/triggers/cron.d.ts +10 -0
- package/dist/triggers/cron.js +44 -0
- package/dist/triggers/cronMatch.d.ts +10 -0
- package/dist/triggers/cronMatch.js +68 -0
- package/dist/triggers/http.d.ts +58 -0
- package/dist/triggers/http.js +292 -0
- package/package.json +25 -151
- package/NOTICE +0 -12
- package/kestrel.config.ts +0 -28
- package/layers/access/nuxt.config.ts +0 -5
- package/layers/access/server/middleware/00.ip-allowlist.ts +0 -52
- package/layers/access/server/middleware/access-guard.ts +0 -32
- package/layers/access/server/types.d.ts +0 -7
- package/layers/admin/app/components/AdminAccount.vue +0 -212
- package/layers/admin/app/components/AdminNav.vue +0 -44
- package/layers/admin/app/components/BlockFields.vue +0 -67
- package/layers/admin/app/components/BlockPreview.vue +0 -413
- package/layers/admin/app/components/BlockTree.vue +0 -433
- package/layers/admin/app/components/BlocksBody.vue +0 -163
- package/layers/admin/app/components/CollectionDeleteDialog.vue +0 -38
- package/layers/admin/app/components/CollectionEditor.vue +0 -324
- package/layers/admin/app/components/CollectionList.vue +0 -429
- package/layers/admin/app/components/CollectionListBulkBar.vue +0 -62
- package/layers/admin/app/components/CollectionListColumnsPanel.vue +0 -37
- package/layers/admin/app/components/CollectionListFilterPanel.vue +0 -145
- package/layers/admin/app/components/CollectionListPager.vue +0 -70
- package/layers/admin/app/components/CollectionListTable.vue +0 -347
- package/layers/admin/app/components/EditorStatus.vue +0 -211
- package/layers/admin/app/components/EditorUnsupported.vue +0 -28
- package/layers/admin/app/components/FieldsBody.vue +0 -23
- package/layers/admin/app/components/LocaleBar.vue +0 -107
- package/layers/admin/app/components/PageFields.vue +0 -129
- package/layers/admin/app/components/PageFieldsPane.vue +0 -39
- package/layers/admin/app/components/SeoFields.vue +0 -210
- package/layers/admin/app/components/SingletonEditor.vue +0 -93
- package/layers/admin/app/composables/useAuth.ts +0 -68
- package/layers/admin/app/composables/useBlockTree.ts +0 -95
- package/layers/admin/app/composables/useBlocks.ts +0 -7
- package/layers/admin/app/composables/useCachedResource.ts +0 -35
- package/layers/admin/app/composables/useCollectionOps.ts +0 -102
- package/layers/admin/app/composables/useCollections.ts +0 -6
- package/layers/admin/app/composables/useContentLocales.ts +0 -23
- package/layers/admin/app/composables/useEditForm.ts +0 -402
- package/layers/admin/app/composables/useLinkResolver.ts +0 -40
- package/layers/admin/app/composables/useListBatchActions.ts +0 -78
- package/layers/admin/app/composables/useListColumns.ts +0 -53
- package/layers/admin/app/composables/useListFilterDraft.ts +0 -129
- package/layers/admin/app/composables/useListRows.ts +0 -75
- package/layers/admin/app/composables/useListSelection.ts +0 -28
- package/layers/admin/app/composables/useListUrlState.ts +0 -53
- package/layers/admin/app/composables/usePendingPublishPoll.ts +0 -15
- package/layers/admin/app/composables/usePreviewViewport.ts +0 -30
- package/layers/admin/app/composables/usePublishStatus.ts +0 -107
- package/layers/admin/app/composables/useRailCollapsed.ts +0 -14
- package/layers/admin/app/composables/useTheme.ts +0 -36
- package/layers/admin/app/composables/useToolbarPanel.ts +0 -38
- package/layers/admin/app/composables/useUnsavedGuard.ts +0 -22
- package/layers/admin/app/layouts/admin.vue +0 -266
- package/layers/admin/app/middleware/admin-auth.ts +0 -4
- package/layers/admin/app/pages/admin/[collection]/[id].vue +0 -203
- package/layers/admin/app/pages/admin/[collection]/index.vue +0 -62
- package/layers/admin/app/pages/admin/index.vue +0 -118
- package/layers/admin/app/pages/admin/login.vue +0 -68
- package/layers/admin/app/pages/admin/media.vue +0 -28
- package/layers/admin/app/pages/admin/references.vue +0 -146
- package/layers/admin/app/plugins/admin-style-guard.client.ts +0 -76
- package/layers/admin/app/plugins/reauth.client.ts +0 -20
- package/layers/admin/app/utils/admin-style-guard.ts +0 -20
- package/layers/admin/app/utils/block-tree.ts +0 -214
- package/layers/admin/app/utils/collection-ops.ts +0 -69
- package/layers/admin/app/utils/dead-refs.ts +0 -30
- package/layers/admin/app/utils/edit-form.ts +0 -267
- package/layers/admin/app/utils/editor-expose.ts +0 -36
- package/layers/admin/app/utils/editor-form-context.ts +0 -82
- package/layers/admin/app/utils/editor-registry.ts +0 -20
- package/layers/admin/app/utils/field-keys.ts +0 -11
- package/layers/admin/app/utils/list-cell.ts +0 -46
- package/layers/admin/app/utils/list-columns.ts +0 -142
- package/layers/admin/app/utils/list-query.ts +0 -96
- package/layers/admin/app/utils/nav-active.ts +0 -9
- package/layers/admin/app/utils/populate-blocks.ts +0 -288
- package/layers/admin/app/utils/preview-channel.ts +0 -62
- package/layers/admin/app/utils/preview-viewport.ts +0 -80
- package/layers/admin/app/utils/reauth.ts +0 -67
- package/layers/admin/app/utils/record-title.ts +0 -15
- package/layers/admin/app/utils/register-builtin-editors.ts +0 -9
- package/layers/admin/app/utils/safe-redirect.ts +0 -12
- package/layers/admin/nuxt.config.ts +0 -20
- package/layers/auth/nuxt.config.ts +0 -1
- package/layers/auth/server/plugins/01.register-auth-pipelines.ts +0 -7
- package/layers/collections/nuxt.config.ts +0 -1
- package/layers/collections/server/plugins/01.register.ts +0 -29
- package/layers/collections/server/plugins/02.register-relation-populate.ts +0 -18
- package/layers/core/app/composables/layouts.ts +0 -5
- package/layers/core/app/utils/field-factories.ts +0 -129
- package/layers/core/app/utils/layouts.ts +0 -38
- package/layers/core/modules/auto-discovery/extract-block.ts +0 -179
- package/layers/core/modules/auto-discovery/index.ts +0 -112
- package/layers/core/modules/auto-discovery/package-registry.ts +0 -19
- package/layers/core/modules/auto-discovery/scan.ts +0 -170
- package/layers/core/modules/auto-discovery/virtual.d.ts +0 -20
- package/layers/core/modules/component-namespace/index.ts +0 -44
- package/layers/core/modules/component-namespace/shared.ts +0 -24
- package/layers/core/modules/kestrel/app-shell.ts +0 -55
- package/layers/core/modules/kestrel/auto-imports.ts +0 -44
- package/layers/core/modules/kestrel/index.ts +0 -144
- package/layers/core/modules/plugin-order/index.ts +0 -69
- package/layers/core/modules/plugin-order/plugin-order.ts +0 -273
- package/layers/core/nuxt.config.ts +0 -10
- package/layers/core/server/api/[...path].ts +0 -98
- package/layers/core/server/plugins/00.config.ts +0 -12
- package/layers/core/server/plugins/00.migrate.ts +0 -12
- package/layers/core/server/plugins/01.register-introspection-pipeline.ts +0 -5
- package/layers/core/server/plugins/01.register-openapi-pipeline.ts +0 -5
- package/layers/core/server/plugins/01.register-tooling-pipelines.ts +0 -5
- package/layers/core/server/plugins/02.schema-sync.ts +0 -58
- package/layers/core/server/plugins/04.outbox-worker.ts +0 -31
- package/layers/core/server/plugins/05.reindex-refs.ts +0 -5
- package/layers/core/server/routes/__kestrel/dashboard.get.ts +0 -29
- package/layers/core/server/tasks/db/migrate-module.ts +0 -83
- package/layers/core/server/tasks/db/migrate-revisions.ts +0 -20
- package/layers/core/server/tasks/db/migrate.ts +0 -50
- package/layers/core/server/utils/server-config.ts +0 -81
- package/layers/fields/nuxt.config.ts +0 -1
- package/layers/fields/server/plugins/01.register-blocks-pipeline.ts +0 -7
- package/layers/fields/server/plugins/01.register-field-populate.ts +0 -11
- package/layers/media/app/components/KestrelImg.vue +0 -94
- package/layers/media/app/components/MediaConflictDialog.vue +0 -35
- package/layers/media/app/components/MediaDeleteDialog.vue +0 -52
- package/layers/media/app/components/MediaGrid.vue +0 -113
- package/layers/media/app/components/MediaImage.vue +0 -70
- package/layers/media/app/components/MediaLibrary.vue +0 -456
- package/layers/media/app/components/MediaNewFolderDialog.vue +0 -32
- package/layers/media/app/components/MediaPathBar.vue +0 -163
- package/layers/media/app/components/MediaPicker.vue +0 -19
- package/layers/media/app/components/MediaRenameDialog.vue +0 -32
- package/layers/media/app/components/MediaTable.vue +0 -75
- package/layers/media/app/components/MediaThumb.vue +0 -25
- package/layers/media/app/components/MediaToolbar.vue +0 -90
- package/layers/media/app/components/MediaUploadDialog.vue +0 -87
- package/layers/media/app/components/MediaViewer.vue +0 -137
- package/layers/media/app/components/field/Media.vue +0 -263
- package/layers/media/app/composables/useBlurUp.ts +0 -55
- package/layers/media/app/composables/useMediaClipboard.ts +0 -14
- package/layers/media/app/composables/useMediaContextMenu.ts +0 -56
- package/layers/media/app/composables/useMediaDnd.ts +0 -56
- package/layers/media/app/composables/useMediaLibrary.ts +0 -152
- package/layers/media/app/composables/useMediaOps.ts +0 -50
- package/layers/media/app/composables/useMediaRelocate.ts +0 -39
- package/layers/media/app/composables/useMediaResolver.ts +0 -50
- package/layers/media/app/composables/useMediaUpload.ts +0 -93
- package/layers/media/app/composables/useMediaVariant.ts +0 -34
- package/layers/media/app/plugins/field-media.client.ts +0 -6
- package/layers/media/app/utils/ai-disclosure.ts +0 -19
- package/layers/media/app/utils/dnd.ts +0 -69
- package/layers/media/app/utils/library.ts +0 -81
- package/layers/media/app/utils/ops.ts +0 -60
- package/layers/media/app/utils/picture-model.ts +0 -125
- package/layers/media/app/utils/thumbhash.ts +0 -19
- package/layers/media/app/utils/variant-name.ts +0 -20
- package/layers/media/nuxt.config.ts +0 -7
- package/layers/media/server/middleware/ondemand-variants.ts +0 -38
- package/layers/media/server/plugins/01.register-media-pipelines.ts +0 -7
- package/layers/media/server/plugins/02.register-media.ts +0 -22
- package/layers/media/server/plugins/04.variant-capture.ts +0 -27
- package/layers/media/server/plugins/05.media-cleanup.ts +0 -5
- package/layers/media/server/tasks/media/backfill.ts +0 -17
- package/layers/public/app/app.vue +0 -5
- package/layers/public/app/components/BlockRenderer.vue +0 -56
- package/layers/public/app/components/KestrelLink.vue +0 -26
- package/layers/public/app/components/KestrelPreviewBridge.vue +0 -133
- package/layers/public/app/composables/public-page.ts +0 -24
- package/layers/public/app/error.vue +0 -12
- package/layers/public/app/layouts/default.vue +0 -5
- package/layers/public/app/pages/[...slug].vue +0 -239
- package/layers/public/app/pages/__kestrel/preview.vue +0 -46
- package/layers/public/app/utils/block-edit-context.ts +0 -16
- package/layers/public/app/utils/json-ld.ts +0 -139
- package/layers/public/app/utils/link-href.ts +0 -46
- package/layers/public/app/utils/page-head.ts +0 -84
- package/layers/public/app/utils/page-layout.ts +0 -18
- package/layers/public/app/utils/preview-protocol.ts +0 -117
- package/layers/public/app/utils/public-route.ts +0 -46
- package/layers/public/app/utils/site-head.ts +0 -43
- package/layers/public/modules/deploy-output/index.ts +0 -80
- package/layers/public/modules/prerender-routes/discover.ts +0 -37
- package/layers/public/modules/prerender-routes/index.ts +0 -88
- package/layers/public/modules/prerender-routes/routes.ts +0 -16
- package/layers/public/modules/prune-media/index.ts +0 -48
- package/layers/public/modules/prune-media/prune-media.ts +0 -81
- package/layers/public/nuxt.config.ts +0 -14
- package/layers/public/server/delivery-live/serve.ts +0 -111
- package/layers/public/server/middleware/delivery-live-catchall.ts +0 -7
- package/layers/public/server/pipelines/index.ts +0 -23
- package/layers/public/server/plugins/00.ensure-snapshot-triggers.ts +0 -14
- package/layers/public/server/plugins/01.register-public-pipelines.ts +0 -7
- package/layers/public/server/plugins/02.register-links.ts +0 -16
- package/layers/public/server/plugins/03.redirects.ts +0 -55
- package/layers/public/server/plugins/05.plan-publish.ts +0 -5
- package/layers/public/server/plugins/zz.publish.ts +0 -127
- package/layers/public/server/routes/llms-full.txt.get.ts +0 -99
- package/layers/public/server/routes/llms.txt.get.ts +0 -66
- package/layers/public/server/routes/redirects.json.get.ts +0 -58
- package/layers/public/server/routes/robots.txt.get.ts +0 -11
- package/layers/public/server/routes/sitemap.xml.get.ts +0 -79
- package/layers/public/server/tasks/publish/run.ts +0 -32
- package/layers/public/server/utils/publish/render-live.ts +0 -26
- package/layers/ui/app/assets/scss/_base.scss +0 -16
- package/layers/ui/app/assets/scss/_datepicker.scss +0 -82
- package/layers/ui/app/assets/scss/_mixins.scss +0 -50
- package/layers/ui/app/assets/scss/_reset.scss +0 -65
- package/layers/ui/app/assets/scss/_tokens.scss +0 -183
- package/layers/ui/app/assets/scss/main.scss +0 -4
- package/layers/ui/app/components/field/Boolean.vue +0 -30
- package/layers/ui/app/components/field/Choice.vue +0 -73
- package/layers/ui/app/components/field/Datetime.vue +0 -98
- package/layers/ui/app/components/field/DeadRefNote.vue +0 -28
- package/layers/ui/app/components/field/Json.vue +0 -62
- package/layers/ui/app/components/field/Layout.vue +0 -138
- package/layers/ui/app/components/field/Link.vue +0 -112
- package/layers/ui/app/components/field/LinkInternalPicker.vue +0 -95
- package/layers/ui/app/components/field/Number.vue +0 -30
- package/layers/ui/app/components/field/Relation.vue +0 -49
- package/layers/ui/app/components/field/Renderer.vue +0 -30
- package/layers/ui/app/components/field/Repeater.vue +0 -460
- package/layers/ui/app/components/field/Richtext.vue +0 -26
- package/layers/ui/app/components/field/Slug.vue +0 -65
- package/layers/ui/app/components/field/Text.vue +0 -35
- package/layers/ui/app/components/field/Unsupported.vue +0 -34
- package/layers/ui/app/components/ui/Alert.vue +0 -50
- package/layers/ui/app/components/ui/Brand.vue +0 -22
- package/layers/ui/app/components/ui/Button.vue +0 -116
- package/layers/ui/app/components/ui/ButtonGroup.vue +0 -96
- package/layers/ui/app/components/ui/Checkbox.vue +0 -53
- package/layers/ui/app/components/ui/CheckboxGroup.vue +0 -51
- package/layers/ui/app/components/ui/Combobox.vue +0 -345
- package/layers/ui/app/components/ui/DatePicker.vue +0 -92
- package/layers/ui/app/components/ui/DateRangePicker.vue +0 -100
- package/layers/ui/app/components/ui/Dialog.vue +0 -107
- package/layers/ui/app/components/ui/EmptyState.vue +0 -46
- package/layers/ui/app/components/ui/Field.vue +0 -58
- package/layers/ui/app/components/ui/Fieldset.vue +0 -65
- package/layers/ui/app/components/ui/Icon.vue +0 -44
- package/layers/ui/app/components/ui/Menu.vue +0 -54
- package/layers/ui/app/components/ui/NumberInput.vue +0 -175
- package/layers/ui/app/components/ui/Richtext.vue +0 -141
- package/layers/ui/app/components/ui/RichtextToolbar.vue +0 -251
- package/layers/ui/app/components/ui/Select.vue +0 -52
- package/layers/ui/app/components/ui/TextInput.vue +0 -111
- package/layers/ui/app/components/ui/Textarea.vue +0 -39
- package/layers/ui/app/components/ui/TimeInput.vue +0 -18
- package/layers/ui/app/components/ui/Toasts.vue +0 -103
- package/layers/ui/app/components/ui/Tooltip.vue +0 -45
- package/layers/ui/app/composables/useAdminLang.ts +0 -8
- package/layers/ui/app/composables/useDragReorder.ts +0 -41
- package/layers/ui/app/composables/useEchoGuard.ts +0 -28
- package/layers/ui/app/composables/useLinkField.ts +0 -70
- package/layers/ui/app/composables/useRecordOptions.ts +0 -84
- package/layers/ui/app/composables/useRepeater.ts +0 -99
- package/layers/ui/app/composables/useT.ts +0 -32
- package/layers/ui/app/composables/useToast.ts +0 -46
- package/layers/ui/app/i18n/de.ts +0 -432
- package/layers/ui/app/i18n/en.ts +0 -434
- package/layers/ui/app/utils/boolean-field.ts +0 -10
- package/layers/ui/app/utils/field-component.ts +0 -22
- package/layers/ui/app/utils/field-empty.ts +0 -21
- package/layers/ui/app/utils/field-registry.ts +0 -36
- package/layers/ui/app/utils/humanize.ts +0 -14
- package/layers/ui/app/utils/icons.ts +0 -143
- package/layers/ui/app/utils/iso-date.ts +0 -18
- package/layers/ui/app/utils/localized.ts +0 -13
- package/layers/ui/app/utils/record-context.ts +0 -15
- package/layers/ui/app/utils/reorder.ts +0 -8
- package/layers/ui/app/utils/richtext-preserve-class.ts +0 -31
- package/layers/ui/app/utils/useFieldA11y.ts +0 -40
- package/layers/ui/nuxt.config.ts +0 -9
- package/nuxt.config.ts +0 -108
- package/scripts/consumer-template-ci.mjs +0 -160
- package/scripts/copy-create-payload.mjs +0 -49
- package/scripts/coverage-all.mjs +0 -163
- package/scripts/dashboard.mjs +0 -89
- package/scripts/hash-password.mjs +0 -29
- package/scripts/kestrel.mjs +0 -240
- package/scripts/lib/cli.mjs +0 -129
- package/scripts/lib/coverage-model.mjs +0 -51
- package/scripts/lib/password.mjs +0 -19
- package/scripts/lib/scaffold.mjs +0 -205
- package/scripts/port-graph.mjs +0 -350
- package/scripts/typecheck.mjs +0 -62
- package/templates/starter/README.md +0 -65
- package/templates/starter/_env.example +0 -17
- package/templates/starter/_gitignore +0 -26
- package/templates/starter/_package.json +0 -22
- package/templates/starter/app/app.vue +0 -7
- package/templates/starter/app/blocks/Prose.vue +0 -13
- package/templates/starter/app/layouts/default.vue +0 -6
- package/templates/starter/nuxt.config.ts +0 -23
- package/templates/starter/pnpm-workspace.yaml +0 -8
- package/templates/starter/tsconfig.json +0 -3
package/package.json
CHANGED
|
@@ -1,166 +1,40 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@michaelthielemann/kestrel",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
|
+
"description": "Kestrel core: contract registry, boot check and pipeline runner. Ships no contracts and no modules.",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"license": "Apache-2.0",
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
"repository": {
|
|
9
|
-
"type": "git",
|
|
10
|
-
"url": "git+https://github.com/MichaelThielemann/kestrel.git"
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=22.13.0"
|
|
11
9
|
},
|
|
12
|
-
"bugs": "https://github.com/MichaelThielemann/kestrel/issues",
|
|
13
|
-
"keywords": [
|
|
14
|
-
"cms",
|
|
15
|
-
"nuxt",
|
|
16
|
-
"nuxt-layer",
|
|
17
|
-
"static-site-generator",
|
|
18
|
-
"drizzle",
|
|
19
|
-
"sqlite",
|
|
20
|
-
"headless-cms"
|
|
21
|
-
],
|
|
22
|
-
"type": "module",
|
|
23
|
-
"main": "./nuxt.config.ts",
|
|
24
10
|
"bin": {
|
|
25
|
-
"kestrel": "./
|
|
11
|
+
"kestrel": "./dist/cli.js"
|
|
26
12
|
},
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./*": {
|
|
19
|
+
"types": "./dist/*.d.ts",
|
|
20
|
+
"default": "./dist/*.js"
|
|
21
|
+
}
|
|
30
22
|
},
|
|
31
|
-
"//publish": "Consumed as a meta-layer: `extends: ['@michaelthielemann/kestrel']`. `main` points at the layer's nuxt.config so the BARE specifier resolves — without it c12 can't resolve the package, silently drops the whole layer (\"Cannot extend config from …\"), and every auto-import/component vanishes. Use `main`, NOT `exports` (exports would gate subpaths like `@michaelthielemann/kestrel/scripts/hash-password.mjs` + the deep config-type import); `bin` resolves by path from the package root, so it coexists with `main` untouched. `files` ships the entry + sub-layers + operator scripts + the `kestrel init` templates; the `!**/*.test.ts` globs strip tests — they are GLOBAL, so nothing under `templates/` may be named `*.test.ts` or it vanishes from the tarball. Template dotfiles are `_`-prefixed for the same class of reason: npm strips a literal `.gitignore` from a tarball and then applies it, taking its listed siblings with it. Releases run from the tag workflow via npm trusted publishing (OIDC), so no npm token exists anywhere.",
|
|
32
23
|
"files": [
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"kestrel.config.ts",
|
|
36
|
-
"layers",
|
|
37
|
-
"scripts",
|
|
38
|
-
"templates",
|
|
39
|
-
"!**/*.test.ts",
|
|
40
|
-
"!**/*.dom.test.ts",
|
|
41
|
-
"!**/*.nuxt.test.ts"
|
|
24
|
+
"dist",
|
|
25
|
+
"README.md"
|
|
42
26
|
],
|
|
43
|
-
"engines": {
|
|
44
|
-
"node": ">=22.12.0"
|
|
45
|
-
},
|
|
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.",
|
|
47
27
|
"dependencies": {
|
|
48
|
-
"
|
|
49
|
-
"@internationalized/date": "^3.12.3",
|
|
50
|
-
"@tiptap/extension-highlight": "^3.29.2",
|
|
51
|
-
"@tiptap/extension-subscript": "^3.29.2",
|
|
52
|
-
"@tiptap/extension-superscript": "^3.29.2",
|
|
53
|
-
"@tiptap/extension-text-align": "^3.29.2",
|
|
54
|
-
"@tiptap/pm": "^3.29.2",
|
|
55
|
-
"@tiptap/starter-kit": "^3.29.2",
|
|
56
|
-
"@tiptap/vue-3": "^3.29.2",
|
|
57
|
-
"@types/better-sqlite3": "^7.6.13",
|
|
58
|
-
"@types/jsdom": "^28.0.3",
|
|
59
|
-
"@types/sanitize-html": "^2.16.1",
|
|
60
|
-
"aws4fetch": "^1.0.20",
|
|
61
|
-
"better-sqlite3": "^12.11.1",
|
|
62
|
-
"dompurify": "^3.4.13",
|
|
63
|
-
"drizzle-orm": "^0.45.2",
|
|
64
|
-
"drizzle-zod": "^0.8.3",
|
|
65
|
-
"effect": "~3.22.1",
|
|
66
|
-
"exifr": "^7.1.3",
|
|
67
|
-
"file-type": "^22.0.1",
|
|
68
|
-
"jsdom": "^29.1.1",
|
|
69
|
-
"nanoid": "^5.1.16",
|
|
70
|
-
"nitropack": "^2.13.4",
|
|
71
|
-
"nuxt": "^4.5.2",
|
|
72
|
-
"reka-ui": "^2.10.1",
|
|
73
|
-
"sanitize-html": "^2.17.6",
|
|
74
|
-
"sass": "^1.102.0",
|
|
75
|
-
"sharp": "^0.35.3",
|
|
76
|
-
"thumbhash": "^0.1.1",
|
|
77
|
-
"typescript": "^6.0.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-static": "0.1.0",
|
|
85
|
-
"@michaelthielemann/kestrel-fields": "0.1.0",
|
|
86
|
-
"@michaelthielemann/kestrel-delivery-live": "0.1.0",
|
|
87
|
-
"@michaelthielemann/kestrel-media": "0.1.0",
|
|
88
|
-
"@michaelthielemann/kestrel-publishing": "0.1.0"
|
|
89
|
-
},
|
|
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.",
|
|
91
|
-
"peerDependencies": {
|
|
92
|
-
"@nuxt/kit": "^4.4.0",
|
|
93
|
-
"h3": "^1.15.0 || ^2.0.0",
|
|
94
|
-
"vue": "^3.5.0",
|
|
95
|
-
"vue-router": "^4.5.0 || ^5.0.0"
|
|
28
|
+
"zod": "^3.23.8"
|
|
96
29
|
},
|
|
97
|
-
"
|
|
98
|
-
"
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
"h3": {
|
|
102
|
-
"optional": true
|
|
103
|
-
},
|
|
104
|
-
"vue": {
|
|
105
|
-
"optional": true
|
|
106
|
-
},
|
|
107
|
-
"vue-router": {
|
|
108
|
-
"optional": true
|
|
109
|
-
}
|
|
110
|
-
},
|
|
111
|
-
"devDependencies": {
|
|
112
|
-
"@arethetypeswrong/cli": "^0.18.5",
|
|
113
|
-
"@fast-check/vitest": "^0.4.1",
|
|
114
|
-
"@microsoft/api-extractor": "^7.58.13",
|
|
115
|
-
"@nuxt/eslint": "^1.9.0",
|
|
116
|
-
"@nuxt/kit": "^4.5.2",
|
|
117
|
-
"@nuxt/test-utils": "^4.1.0",
|
|
118
|
-
"@stryker-mutator/core": "^10.0.0",
|
|
119
|
-
"@stryker-mutator/vitest-runner": "^10.0.0",
|
|
120
|
-
"@vitejs/plugin-vue": "^6.0.8",
|
|
121
|
-
"@vitest/coverage-v8": "4.1.10",
|
|
122
|
-
"@vue/test-utils": "^2.4.11",
|
|
123
|
-
"drizzle-kit": "^0.31.10",
|
|
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",
|
|
128
|
-
"eslint-plugin-vuejs-accessibility": "^2.4.1",
|
|
129
|
-
"fast-check": "^4.9.0",
|
|
130
|
-
"h3": "^1.15.11",
|
|
131
|
-
"happy-dom": "^20.11.2",
|
|
132
|
-
"playwright-core": "^1.62.1",
|
|
133
|
-
"publint": "^0.3.24",
|
|
134
|
-
"typedoc": "^0.28.20",
|
|
135
|
-
"vitest": "^4.1.10",
|
|
136
|
-
"vitest-openapi": "^1.0.3",
|
|
137
|
-
"vue": "3.5.35",
|
|
138
|
-
"vue-router": "^5.2.0",
|
|
139
|
-
"vue-tsc": "^3.3.9"
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/MichaelThielemann/kestrel.git",
|
|
33
|
+
"directory": "packages/core"
|
|
140
34
|
},
|
|
35
|
+
"homepage": "https://github.com/MichaelThielemann/kestrel#readme",
|
|
36
|
+
"bugs": "https://github.com/MichaelThielemann/kestrel/issues",
|
|
141
37
|
"scripts": {
|
|
142
|
-
"
|
|
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"
|
|
38
|
+
"build": "tsc -b tsconfig.json"
|
|
165
39
|
}
|
|
166
40
|
}
|
package/NOTICE
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
Kestrel
|
|
2
|
-
Copyright 2026 Michael Thielemann
|
|
3
|
-
|
|
4
|
-
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
|
5
|
-
this software except in compliance with the License. You may obtain a copy of
|
|
6
|
-
the License in the LICENSE file distributed with this software, or at
|
|
7
|
-
|
|
8
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
|
|
10
|
-
This product is provided free of charge and is developed and made available
|
|
11
|
-
outside of any commercial activity. It comes with NO WARRANTY and no commercial
|
|
12
|
-
support; see sections 7 and 8 of the License.
|
package/kestrel.config.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import type { KestrelConfig } from '@michaelthielemann/kestrel-core'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Central Kestrel configuration (everything except auth/session, which stays env-only because secrets
|
|
5
|
-
* don't belong in committed config). This one file is the single source: it flows into `nuxt.config`'s
|
|
6
|
-
* `kestrel: { … }`, into `drizzle.config` (so `drizzle-kit` uses the same DB), and into the server at
|
|
7
|
-
* runtime. Precedence per setting: the matching `KESTREL_*` env var → a value here → a built-in default.
|
|
8
|
-
*
|
|
9
|
-
* This instance's non-auth config lives here (not in `.env`) so `nuxt.config`, `drizzle-kit` and the
|
|
10
|
-
* runtime server all derive the SAME values from one place — in particular the same SQLite file, so
|
|
11
|
-
* `pnpm dev`, `pnpm build` and a migration never drift onto different DBs (they set no `KESTREL_*`, so
|
|
12
|
-
* this file wins). `.env` keeps only the auth/session secrets. An explicit `KESTREL_*` env var OVERRIDES
|
|
13
|
-
* this file — the escape-hatch for a per-environment value or an isolated test (the e2e suite sets
|
|
14
|
-
* `KESTREL_DB` / `KESTREL_MEDIA_LOCAL_DIR` / `KESTREL_SITE_URL` per run). The static-publish `output`
|
|
15
|
-
* target is left to its defaults (local `.data/published`, auto-publish on).
|
|
16
|
-
*/
|
|
17
|
-
export default {
|
|
18
|
-
db: '.data/dev.sqlite',
|
|
19
|
-
siteUrl: 'http://localhost:3000',
|
|
20
|
-
locales: ['en', 'de'],
|
|
21
|
-
primaryLocale: 'en',
|
|
22
|
-
media: {
|
|
23
|
-
driver: 'local',
|
|
24
|
-
uploadDir: '.data/uploads',
|
|
25
|
-
baseUrl: '/uploads',
|
|
26
|
-
maxBytes: 26214400, // 25 MiB
|
|
27
|
-
},
|
|
28
|
-
} satisfies KestrelConfig
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
// Authorization (authz) layer — the access guard, policy, CSRF + (later) the grant registry. Split out of
|
|
2
|
-
// `auth` (which keeps authentication: session/password/login). Composes AFTER `auth`: the guard verifies the
|
|
3
|
-
// session (via auth's `verifySession`/`sessionSettings`) to derive the request principal, then evaluates the
|
|
4
|
-
// policy. Keeping authz separate is the seam a future User&Roles extension plugs into.
|
|
5
|
-
export default defineNuxtConfig({})
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
// Stage-level IP gate: the ENFORCEMENT of an allow-list Kestrel provides; the list itself is consumer
|
|
2
|
-
// config (env `KESTREL_IP_ALLOWLIST`, typically only on non-public DEV/EDIT stages). Runs before the API
|
|
3
|
-
// access-guard (00. sort prefix) and covers every route this app defines — the admin UI, public preview,
|
|
4
|
-
// and API. It does NOT cover assets Nitro serves itself ahead of this stack (`/_nuxt/**`, and `/uploads`
|
|
5
|
-
// when `media.driver: 'local'`) — the `00.` prefix only orders files within this layer, while Nitro's own
|
|
6
|
-
// static handler is unconditionally unshifted to the front of the whole handler list. `off` when no list
|
|
7
|
-
// is set → zero cost on PROD/local.
|
|
8
|
-
import { clientIp } from '@michaelthielemann/kestrel-auth'
|
|
9
|
-
import { allowlistConfig, ipAllowed, ipv4ToInt, isRendererContext, isStageGatePassedContext, markStageGatePassed } from '@michaelthielemann/kestrel-access'
|
|
10
|
-
|
|
11
|
-
// A listener that never shows the gate a usable client address blocks everything, which looks like an
|
|
12
|
-
// outage rather than a misconfiguration unless we say once what to set.
|
|
13
|
-
let warnedUnresolvable = false
|
|
14
|
-
|
|
15
|
-
export default defineEventHandler((event) => {
|
|
16
|
-
const { mode, cidrs } = allowlistConfig()
|
|
17
|
-
if (mode === 'off') return
|
|
18
|
-
|
|
19
|
-
// The runtime publisher renders the public site via a nested in-process $fetch (renderer principal), and
|
|
20
|
-
// build-time prerender the same. Neither is an external client — exempt them or publishing 403s itself.
|
|
21
|
-
if (import.meta.prerender === true || isRendererContext()) return
|
|
22
|
-
|
|
23
|
-
// Nitro's other in-process sub-requests (the SSR page's own `$fetch`, the preview) run through this
|
|
24
|
-
// stack on a mocked socket with no peer, so they could never match a CIDR — gating them would 403 the
|
|
25
|
-
// very pages the gate protects. They are recognised by the async context of the request that spawned
|
|
26
|
-
// them: a missing peer alone is NOT a pass, or a listener that reports none for anybody (a unix domain
|
|
27
|
-
// socket) would turn the gate into a silent no-op.
|
|
28
|
-
if (isStageGatePassedContext() && !event.node?.req?.socket?.remoteAddress) return
|
|
29
|
-
|
|
30
|
-
const ip = clientIp(event)
|
|
31
|
-
if (ipAllowed(ip, cidrs)) {
|
|
32
|
-
// The in-process sub-requests this one spawns inherit the decision (see above).
|
|
33
|
-
markStageGatePassed()
|
|
34
|
-
return
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (ipv4ToInt(ip) === null && !warnedUnresolvable) {
|
|
38
|
-
warnedUnresolvable = true
|
|
39
|
-
console.warn(`[ip-allowlist] cannot resolve an IPv4 client address (got "${ip}") — such requests are blocked in enforce mode; set KESTREL_TRUST_PROXY when a reverse proxy fronts this listener`)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (mode === 'log') {
|
|
43
|
-
// Calibration mode: never block, just surface what the gate WOULD reject so the operator can confirm
|
|
44
|
-
// the resolved client IP + KESTREL_TRUST_PROXY depth before switching to enforce.
|
|
45
|
-
console.warn(`[ip-allowlist] would block ${ip} — ${event.method} ${event.path} (xff=${getRequestHeader(event, 'x-forwarded-for') ?? '-'})`)
|
|
46
|
-
// Nothing is blocked in this mode, so the report is about the external request, not the in-process
|
|
47
|
-
// renders it goes on to spawn.
|
|
48
|
-
markStageGatePassed()
|
|
49
|
-
return
|
|
50
|
-
}
|
|
51
|
-
throw createError({ statusCode: 403, statusMessage: 'Forbidden' })
|
|
52
|
-
})
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { Forbidden, Unauthorized } from '@michaelthielemann/kestrel-contracts'
|
|
2
|
-
import { toHttpError } from '@michaelthielemann/kestrel-core'
|
|
3
|
-
import { refreshAuthSession } from '@michaelthielemann/kestrel-auth'
|
|
4
|
-
import { claimedByPipelineRoute, resolveEventPrincipal } from '@michaelthielemann/kestrel-access'
|
|
5
|
-
|
|
6
|
-
export default defineEventHandler((event) => {
|
|
7
|
-
if (!event.path.startsWith('/api/')) return // default-deny applies to the API only
|
|
8
|
-
|
|
9
|
-
// Authorization itself belongs to the pipeline the URL names — its `access`/`csrf`/`ipAllowlist`
|
|
10
|
-
// declarations, evaluated by the engine before step 1. Only the principal is resolved here, because the
|
|
11
|
-
// gates need it and because the session refresh below is a property of the request, not of the route.
|
|
12
|
-
event.context.principal = resolveEventPrincipal(event)
|
|
13
|
-
|
|
14
|
-
// Default-deny for everything no pipeline claims — an unknown name, a wrong verb, a stray route a
|
|
15
|
-
// consumer mounted under /api without declaring who may run it. The admin falls through instead, so the
|
|
16
|
-
// router can still answer accurately (404 for an unknown pipeline, 405 for the wrong verb) without that
|
|
17
|
-
// answer confirming to an anonymous prober which pipelines exist. For the same reason an anonymous
|
|
18
|
-
// caller gets the access gate's 401 here, not a 403 — a 401/403 split would itself be the oracle.
|
|
19
|
-
if (!claimedByPipelineRoute(getMethod(event), event.path) && event.context.principal.role !== 'admin') {
|
|
20
|
-
// toHttpError is the same single translation every pipeline-thrown KestrelError goes through
|
|
21
|
-
// (core/server/api/[...path].ts's catch) — this middleware runs before any pipeline, so it calls the
|
|
22
|
-
// map directly instead of growing a second one.
|
|
23
|
-
if (event.context.principal.role === 'anonymous') {
|
|
24
|
-
throw toHttpError(new Unauthorized({ reason: 'Authentication required' }))
|
|
25
|
-
}
|
|
26
|
-
throw toHttpError(new Forbidden({ reason: 'Forbidden' }))
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Sliding-expiry: any authenticated API activity refreshes the session so it stays alive while in use and
|
|
30
|
-
// auto-expires after `maxAge` of inactivity (idle logout). No-op for anonymous/public requests.
|
|
31
|
-
refreshAuthSession(event)
|
|
32
|
-
})
|
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
DropdownMenuRoot, DropdownMenuTrigger, DropdownMenuPortal, DropdownMenuContent,
|
|
4
|
-
DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuRadioGroup, DropdownMenuRadioItem,
|
|
5
|
-
DropdownMenuItemIndicator, DropdownMenuItem,
|
|
6
|
-
} from 'reka-ui'
|
|
7
|
-
|
|
8
|
-
// The rail's pseudo-account area: an avatar that opens an account menu (UI-language + sign out).
|
|
9
|
-
const { t, lang } = useT()
|
|
10
|
-
const { logout } = useAuth()
|
|
11
|
-
|
|
12
|
-
// Single-user admin for now — static initials. A natural seam for a real display name later.
|
|
13
|
-
const initials = 'AD'
|
|
14
|
-
|
|
15
|
-
// reka's DropdownMenuRadioGroup emits the broad `AcceptableValue`; accept `unknown` and coerce to our lang.
|
|
16
|
-
function selectLang(value: unknown) { lang.value = value as string }
|
|
17
|
-
function signOut() { logout() }
|
|
18
|
-
|
|
19
|
-
// Exposed for the component test: the menu itself is teleported (not rendered by happy-dom), so the
|
|
20
|
-
// load-bearing handlers are asserted directly.
|
|
21
|
-
defineExpose({ lang, selectLang, signOut })
|
|
22
|
-
</script>
|
|
23
|
-
|
|
24
|
-
<template>
|
|
25
|
-
<DropdownMenuRoot>
|
|
26
|
-
<DropdownMenuTrigger as-child>
|
|
27
|
-
<button type="button" class="rail-account__trigger" :aria-label="t('account.label')" :title="t('account.label')">
|
|
28
|
-
<span class="rail-account__avatar" aria-hidden="true">{{ initials }}</span>
|
|
29
|
-
<span class="rail-account__name rail__label">{{ t('account.name') }}</span>
|
|
30
|
-
<KestrelUiIcon name="chevron-down" class="rail-account__caret rail__label" size="1rem" />
|
|
31
|
-
</button>
|
|
32
|
-
</DropdownMenuTrigger>
|
|
33
|
-
|
|
34
|
-
<DropdownMenuPortal>
|
|
35
|
-
<DropdownMenuContent
|
|
36
|
-
class="rail-account__menu"
|
|
37
|
-
side="top"
|
|
38
|
-
align="start"
|
|
39
|
-
:side-offset="6"
|
|
40
|
-
:collision-padding="8"
|
|
41
|
-
>
|
|
42
|
-
<DropdownMenuLabel class="rail-account__head">
|
|
43
|
-
<span class="rail-account__avatar rail-account__avatar--sm" aria-hidden="true">{{ initials }}</span>
|
|
44
|
-
<span class="rail-account__head-name">{{ t('account.name') }}</span>
|
|
45
|
-
</DropdownMenuLabel>
|
|
46
|
-
|
|
47
|
-
<DropdownMenuSeparator class="rail-account__sep" />
|
|
48
|
-
|
|
49
|
-
<DropdownMenuLabel class="rail-account__group-label">{{ t('lang.label') }}</DropdownMenuLabel>
|
|
50
|
-
<DropdownMenuRadioGroup :model-value="lang" @update:model-value="selectLang">
|
|
51
|
-
<DropdownMenuRadioItem
|
|
52
|
-
v-for="l in ADMIN_LANGS"
|
|
53
|
-
:key="l"
|
|
54
|
-
:value="l"
|
|
55
|
-
class="rail-account__item"
|
|
56
|
-
>
|
|
57
|
-
<span class="rail-account__check">
|
|
58
|
-
<DropdownMenuItemIndicator>
|
|
59
|
-
<KestrelUiIcon name="check" size="1rem" />
|
|
60
|
-
</DropdownMenuItemIndicator>
|
|
61
|
-
</span>
|
|
62
|
-
<span>{{ l.toUpperCase() }}</span>
|
|
63
|
-
</DropdownMenuRadioItem>
|
|
64
|
-
</DropdownMenuRadioGroup>
|
|
65
|
-
|
|
66
|
-
<DropdownMenuSeparator class="rail-account__sep" />
|
|
67
|
-
|
|
68
|
-
<DropdownMenuItem class="rail-account__item rail-account__item--danger" @select="signOut">
|
|
69
|
-
<KestrelUiIcon name="log-out" size="1rem" class="rail-account__item-icon" />
|
|
70
|
-
<span>{{ t('nav.signOut') }}</span>
|
|
71
|
-
</DropdownMenuItem>
|
|
72
|
-
</DropdownMenuContent>
|
|
73
|
-
</DropdownMenuPortal>
|
|
74
|
-
</DropdownMenuRoot>
|
|
75
|
-
</template>
|
|
76
|
-
|
|
77
|
-
<style lang="scss">
|
|
78
|
-
// Not scoped: Reka teleports DropdownMenuContent to <body>, where a scoped data-v selector no longer
|
|
79
|
-
// matches (same reason UiMenu's styles are global). The trigger lives in the rail; keeping the whole
|
|
80
|
-
// component's styles global is simplest and consistent with the other portaled overlays.
|
|
81
|
-
.rail-account__trigger {
|
|
82
|
-
display: flex;
|
|
83
|
-
align-items: center;
|
|
84
|
-
gap: var(--space-3);
|
|
85
|
-
width: 100%;
|
|
86
|
-
padding: var(--space-2) var(--space-3);
|
|
87
|
-
margin-bottom: var(--space-1);
|
|
88
|
-
border: 0;
|
|
89
|
-
border-radius: var(--radius-md);
|
|
90
|
-
background: transparent;
|
|
91
|
-
color: var(--color-text);
|
|
92
|
-
font: inherit;
|
|
93
|
-
font-size: var(--text-sm);
|
|
94
|
-
text-align: left;
|
|
95
|
-
cursor: pointer;
|
|
96
|
-
|
|
97
|
-
&:hover {
|
|
98
|
-
background: var(--color-hover);
|
|
99
|
-
}
|
|
100
|
-
&:focus-visible {
|
|
101
|
-
outline: 2px solid var(--color-focus);
|
|
102
|
-
outline-offset: -2px;
|
|
103
|
-
}
|
|
104
|
-
&[data-state='open'] {
|
|
105
|
-
background: var(--color-active, var(--color-surface-2));
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
.rail-account__avatar {
|
|
110
|
-
display: inline-flex;
|
|
111
|
-
align-items: center;
|
|
112
|
-
justify-content: center;
|
|
113
|
-
flex-shrink: 0;
|
|
114
|
-
width: 1.75rem;
|
|
115
|
-
height: 1.75rem;
|
|
116
|
-
border-radius: var(--radius-full);
|
|
117
|
-
border: 1px solid var(--color-border-strong, var(--color-border));
|
|
118
|
-
background: var(--color-surface-2);
|
|
119
|
-
color: var(--color-text);
|
|
120
|
-
font-size: var(--text-xs, 0.75rem);
|
|
121
|
-
font-weight: var(--weight-bold);
|
|
122
|
-
letter-spacing: 0.02em;
|
|
123
|
-
|
|
124
|
-
&--sm {
|
|
125
|
-
width: 1.5rem;
|
|
126
|
-
height: 1.5rem;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
.rail-account__name {
|
|
131
|
-
font-weight: var(--weight-medium);
|
|
132
|
-
}
|
|
133
|
-
.rail-account__caret {
|
|
134
|
-
margin-left: auto;
|
|
135
|
-
flex-shrink: 0;
|
|
136
|
-
color: var(--color-text-muted);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Menu surface — mirrors .ui-menu so the account dropdown reads like the rest of the admin chrome.
|
|
140
|
-
.rail-account__menu {
|
|
141
|
-
min-width: 13rem;
|
|
142
|
-
background: var(--color-surface);
|
|
143
|
-
border: 1px solid var(--color-border);
|
|
144
|
-
border-radius: var(--radius-md);
|
|
145
|
-
box-shadow: var(--shadow-md);
|
|
146
|
-
padding: var(--space-1);
|
|
147
|
-
z-index: var(--z-dropdown);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
.rail-account__head {
|
|
151
|
-
display: flex;
|
|
152
|
-
align-items: center;
|
|
153
|
-
gap: var(--space-2);
|
|
154
|
-
padding: var(--space-2) var(--space-2);
|
|
155
|
-
}
|
|
156
|
-
.rail-account__head-name {
|
|
157
|
-
font-size: var(--text-sm);
|
|
158
|
-
font-weight: var(--weight-medium);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
.rail-account__group-label {
|
|
162
|
-
padding: var(--space-1) var(--space-2);
|
|
163
|
-
font-size: var(--text-xs, 0.75rem);
|
|
164
|
-
font-weight: var(--weight-medium);
|
|
165
|
-
color: var(--color-text-muted);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
.rail-account__sep {
|
|
169
|
-
height: 1px;
|
|
170
|
-
margin: var(--space-1) calc(var(--space-1) * -1);
|
|
171
|
-
background: var(--color-border);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
.rail-account__item {
|
|
175
|
-
display: flex;
|
|
176
|
-
align-items: center;
|
|
177
|
-
gap: var(--space-2);
|
|
178
|
-
width: 100%;
|
|
179
|
-
padding: var(--space-1) var(--space-2);
|
|
180
|
-
border-radius: var(--radius-sm);
|
|
181
|
-
font-size: var(--text-sm);
|
|
182
|
-
color: var(--color-text);
|
|
183
|
-
cursor: pointer;
|
|
184
|
-
user-select: none;
|
|
185
|
-
|
|
186
|
-
&[data-highlighted] {
|
|
187
|
-
background: var(--color-hover);
|
|
188
|
-
outline: none;
|
|
189
|
-
}
|
|
190
|
-
&--danger {
|
|
191
|
-
color: var(--color-danger);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
.rail-account__check {
|
|
196
|
-
display: inline-flex;
|
|
197
|
-
align-items: center;
|
|
198
|
-
justify-content: center;
|
|
199
|
-
width: 1rem;
|
|
200
|
-
flex-shrink: 0;
|
|
201
|
-
color: var(--color-primary);
|
|
202
|
-
}
|
|
203
|
-
.rail-account__item-icon {
|
|
204
|
-
flex-shrink: 0;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Collapsed rail: drop the label + caret and centre the avatar (mirrors .rail__item's collapsed rule).
|
|
208
|
-
.admin--rail-collapsed .rail-account__trigger {
|
|
209
|
-
justify-content: center;
|
|
210
|
-
gap: 0;
|
|
211
|
-
}
|
|
212
|
-
</style>
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { resolveLocalized } from '../../../ui/app/utils/localized'
|
|
3
|
-
|
|
4
|
-
const { t, lang } = useT()
|
|
5
|
-
const { collections, load } = useCollections()
|
|
6
|
-
const { collapsed } = useRailCollapsed()
|
|
7
|
-
const route = useRoute()
|
|
8
|
-
try {
|
|
9
|
-
await load()
|
|
10
|
-
} catch {
|
|
11
|
-
// Nav is non-critical chrome; a failed fetch leaves it empty rather than breaking the layout.
|
|
12
|
-
}
|
|
13
|
-
</script>
|
|
14
|
-
|
|
15
|
-
<template>
|
|
16
|
-
<nav class="admin-nav" :aria-label="t('nav.collections')">
|
|
17
|
-
<NuxtLink
|
|
18
|
-
v-for="c in (collections ?? []).filter((c) => c.nav !== false)"
|
|
19
|
-
:key="c.name"
|
|
20
|
-
:to="`/admin/${c.name}`"
|
|
21
|
-
class="admin-nav__link rail__item"
|
|
22
|
-
:class="{ 'router-link-active': isNavItemActive(route.path, `/admin/${c.name}`) }"
|
|
23
|
-
:title="collapsed ? (resolveLocalized(c.label?.plural, lang) ?? c.name) : undefined"
|
|
24
|
-
>
|
|
25
|
-
<KestrelUiIcon :name="c.icon ?? 'file-text'" class="rail__icon" size="1.25rem" />
|
|
26
|
-
<span class="rail__label">{{ resolveLocalized(c.label?.plural, lang) ?? c.name }}</span>
|
|
27
|
-
</NuxtLink>
|
|
28
|
-
</nav>
|
|
29
|
-
</template>
|
|
30
|
-
|
|
31
|
-
<style lang="scss">
|
|
32
|
-
.admin-nav {
|
|
33
|
-
display: flex;
|
|
34
|
-
flex-direction: column;
|
|
35
|
-
gap: var(--space-1);
|
|
36
|
-
|
|
37
|
-
// Layout, colour and active state come from the shared .rail__item (admin layout).
|
|
38
|
-
// Only the cosmetic capitalisation is nav-specific — kept CSS-only so the rendered
|
|
39
|
-
// text node stays the raw collection name (e.g. "settings").
|
|
40
|
-
&__link {
|
|
41
|
-
text-transform: capitalize;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
</style>
|