@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
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
import { readdirSync } from 'node:fs'
|
|
2
|
-
import { basename, join } from 'node:path'
|
|
3
|
-
import { fileURLToPath } from 'node:url'
|
|
4
|
-
import { blockNameFromFile } from './extract-block'
|
|
5
|
-
|
|
6
|
-
type Lister = (dir: string) => string[]
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Resolves a bare `@michaelthielemann/kestrel-*` package specifier to its real entry FILE PATH, via Node's
|
|
10
|
-
* own ESM resolution from THIS module's own location — not the bare specifier itself, which a Nitro
|
|
11
|
-
* virtual module (no real file path of its own) cannot resolve reliably: Rollup falls back to resolving a
|
|
12
|
-
* virtual's imports from the project root, and under pnpm's isolated `node_modules` a package the
|
|
13
|
-
* CONSUMER never declared a direct dependency on (only the engine did, transitively) is invisible there —
|
|
14
|
-
* observed as `Cannot find package` at runtime, npm's flat hoisting having accidentally masked it in
|
|
15
|
-
* testing. This module's own file, once installed, sits inside the engine's package tree, so resolving
|
|
16
|
-
* from here walks the engine's OWN nested `node_modules`, where pnpm DID link the real dependency.
|
|
17
|
-
*/
|
|
18
|
-
export function resolvePackageEntry(spec: string): string {
|
|
19
|
-
return fileURLToPath(import.meta.resolve(spec))
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const isDefinition = (name: string) =>
|
|
23
|
-
name.endsWith('.ts') && !name.endsWith('.test.ts') && !name.endsWith('.d.ts')
|
|
24
|
-
|
|
25
|
-
export function listDefinitionFiles(dir: string, list: Lister = readdirSync): string[] {
|
|
26
|
-
let entries: string[]
|
|
27
|
-
try {
|
|
28
|
-
entries = list(dir)
|
|
29
|
-
} catch (err) {
|
|
30
|
-
if ((err as NodeJS.ErrnoException).code === 'ENOENT') return []
|
|
31
|
-
throw err
|
|
32
|
-
}
|
|
33
|
-
return entries
|
|
34
|
-
.filter(isDefinition)
|
|
35
|
-
.sort()
|
|
36
|
-
.map((name) => join(dir, name))
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Definition files across every layer's `subdir`, deduped by BASENAME with LAYER PRIORITY: `layerRoots` is
|
|
41
|
-
* Nuxt's `_layers` order (consumer/highest-priority first), so a consumer's `collections/pages.ts` (or
|
|
42
|
-
* `field-types/color.ts`) SHADOWS the same-named lower-layer file — a deterministic override, instead of
|
|
43
|
-
* importing both and relying on the registry's last-write-wins under an alphabetical path sort (which made
|
|
44
|
-
* "who wins" a coincidence of the node_modules path). Mirrors `collectBlockSfcs`'s name-first dedup.
|
|
45
|
-
*/
|
|
46
|
-
export function collectDefinitions(layerRoots: string[], subdir: string, list?: Lister): string[] {
|
|
47
|
-
const byBasename = new Map<string, string>()
|
|
48
|
-
for (const root of layerRoots) {
|
|
49
|
-
for (const file of listDefinitionFiles(join(root, subdir), list)) {
|
|
50
|
-
const name = basename(file)
|
|
51
|
-
if (!byBasename.has(name)) byBasename.set(name, file) // first (highest-priority layer) wins
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return [...byBasename.values()].sort()
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const isVue = (name: string) => name.endsWith('.vue') && !name.endsWith('.test.vue')
|
|
58
|
-
|
|
59
|
-
/** `.vue` files in `dir`, sorted; `[]` when the dir is absent. Mirrors `listDefinitionFiles` for blocks. */
|
|
60
|
-
export function listVueFiles(dir: string, list: Lister = readdirSync): string[] {
|
|
61
|
-
let entries: string[]
|
|
62
|
-
try {
|
|
63
|
-
entries = list(dir)
|
|
64
|
-
} catch (err) {
|
|
65
|
-
if ((err as NodeJS.ErrnoException).code === 'ENOENT') return []
|
|
66
|
-
throw err
|
|
67
|
-
}
|
|
68
|
-
return entries.filter(isVue).sort().map((name) => join(dir, name))
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/** Block SFCs across every layer's `app/blocks/` dir, deduped by BLOCK NAME with LAYER PRIORITY: `layerRoots`
|
|
72
|
-
* is Nuxt's `_layers` order (consumer/highest-priority first), so the first file seen for a given block name
|
|
73
|
-
* wins. This matches how the display component is resolved (project-first), so a same-named override takes
|
|
74
|
-
* its schema AND its template from the same layer — a plain alphabetical path sort handed the schema to
|
|
75
|
-
* whichever path sorted last (typically the node_modules layer), silently splitting the two. */
|
|
76
|
-
export function collectBlockSfcs(layerRoots: string[], subdir = 'app/blocks', list?: Lister): string[] {
|
|
77
|
-
const byName = new Map<string, string>()
|
|
78
|
-
for (const root of layerRoots) {
|
|
79
|
-
for (const file of listVueFiles(join(root, subdir), list)) {
|
|
80
|
-
const name = blockNameFromFile(basename(file))
|
|
81
|
-
if (!byName.has(name)) byName.set(name, file) // first (highest-priority layer) wins
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
// Name-unique now, so the final sort is only for a deterministic, stable import order.
|
|
85
|
-
return [...byName.values()].sort()
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Each layer's OWN `server/db/manifest.ts` (ADR-0012 ownership manifest), one per layer that has one.
|
|
90
|
-
* Unlike `collectDefinitions`, this does NOT dedupe by basename: every layer's manifest is a distinct
|
|
91
|
-
* module contribution (no shadow/override semantics — `media`'s manifest and `public`'s manifest both
|
|
92
|
-
* happen to be named `manifest.ts`, and both must survive). Layer order is whatever `layerRoots` is.
|
|
93
|
-
*/
|
|
94
|
-
export function collectManifestFiles(layerRoots: string[], list?: Lister): string[] {
|
|
95
|
-
const files: string[] = []
|
|
96
|
-
for (const root of layerRoots) {
|
|
97
|
-
for (const file of listDefinitionFiles(join(root, 'server/db'), list)) {
|
|
98
|
-
if (basename(file) === 'manifest.ts') files.push(file)
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
return files
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export function renderRegistry(absPaths: string[]): string {
|
|
105
|
-
if (!absPaths.length) return 'export default []'
|
|
106
|
-
const imports = absPaths.map((p, i) => `import _${i} from ${JSON.stringify(p)}`)
|
|
107
|
-
const arr = absPaths.map((_, i) => `_${i}`).join(', ')
|
|
108
|
-
return `${imports.join('\n')}\nexport default [${arr}]`
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* A registry combining package-provided items (each package's `kestrelDiscovery.<property>`, read via a
|
|
113
|
-
* bare specifier — no filesystem/`node_modules` guessing) with consumer/layer-scanned files, with a
|
|
114
|
-
* SAME-NAME layer item overriding a package's (via `mergeKestrelDiscovered`, evaluated at virtual-load
|
|
115
|
-
* time — package names aren't knowable at codegen time without loading the package, which this
|
|
116
|
-
* deliberately avoids doing at build time). `nameOfExpr` is a JS arrow-function source string the
|
|
117
|
-
* generated module evaluates per item (e.g. `'(x) => x.name'` for collections,
|
|
118
|
-
* `'(x) => __kestrelTableName(x)'` for schema-tables, paired with `extraImports` supplying
|
|
119
|
-
* `__kestrelTableName`).
|
|
120
|
-
*/
|
|
121
|
-
export function renderPackageMergedRegistry(opts: {
|
|
122
|
-
packages: string[]
|
|
123
|
-
property: 'collections' | 'schemaTables'
|
|
124
|
-
consumerFiles: string[]
|
|
125
|
-
nameOfExpr: string
|
|
126
|
-
extraImports?: string
|
|
127
|
-
preamble?: string
|
|
128
|
-
}): string {
|
|
129
|
-
const pkgImports = opts.packages.map((spec, i) => `import { kestrelDiscovery as __pkg${i} } from ${JSON.stringify(spec)}`)
|
|
130
|
-
const pkgSpread = opts.packages.map((_, i) => `...(__pkg${i}.${opts.property} ?? [])`).join(', ')
|
|
131
|
-
const consumerImports = opts.consumerFiles.map((p, i) => `import _c${i} from ${JSON.stringify(p)}`)
|
|
132
|
-
const consumerArr = opts.consumerFiles.map((_, i) => `_c${i}`).join(', ')
|
|
133
|
-
return [
|
|
134
|
-
opts.preamble ?? '',
|
|
135
|
-
opts.extraImports ?? '',
|
|
136
|
-
`import { mergeKestrelDiscovered } from ${JSON.stringify(resolvePackageEntry('@michaelthielemann/kestrel-core'))}`,
|
|
137
|
-
...pkgImports,
|
|
138
|
-
...consumerImports,
|
|
139
|
-
`export default mergeKestrelDiscovered([${pkgSpread}], [${consumerArr}], ${opts.nameOfExpr})`,
|
|
140
|
-
].filter(Boolean).join('\n')
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* A registry concatenating package-provided items (`kestrelDiscovery.<property>`) with consumer/layer-
|
|
145
|
-
* scanned files — NO dedup (mirrors `collectManifestFiles`'s own "every contribution is distinct, no
|
|
146
|
-
* shadow semantics" contract; unlike collections/schema-tables, two manifests sharing a name is not a
|
|
147
|
-
* schema conflict, so there is nothing to override).
|
|
148
|
-
*/
|
|
149
|
-
export function renderPackageConcatRegistry(opts: {
|
|
150
|
-
packages: string[]
|
|
151
|
-
property: 'manifest'
|
|
152
|
-
consumerFiles: string[]
|
|
153
|
-
preamble?: string
|
|
154
|
-
}): string {
|
|
155
|
-
const pkgImports = opts.packages.map((spec, i) => `import { kestrelDiscovery as __pkg${i} } from ${JSON.stringify(spec)}`)
|
|
156
|
-
// `opts.packages` is expected to be pre-filtered to packages that actually contribute this property (the
|
|
157
|
-
// caller's per-category list), so every `.manifest` read here SHOULD be defined — but the spread stays
|
|
158
|
-
// conditional (`...(x ? [x] : [])`, not a bare `x`) as a defense-in-depth guard: a package mis-listed in
|
|
159
|
-
// the wrong `PACKAGE_*` category contributes nothing instead of polluting the array with `undefined`.
|
|
160
|
-
const pkgItems = opts.packages.map((_, i) => `...(__pkg${i}.${opts.property} ? [__pkg${i}.${opts.property}] : [])`)
|
|
161
|
-
const consumerImports = opts.consumerFiles.map((p, i) => `import _c${i} from ${JSON.stringify(p)}`)
|
|
162
|
-
const consumerArr = opts.consumerFiles.map((_, i) => `_c${i}`)
|
|
163
|
-
const all = [...pkgItems, ...consumerArr].join(', ')
|
|
164
|
-
return [
|
|
165
|
-
opts.preamble ?? '',
|
|
166
|
-
...pkgImports,
|
|
167
|
-
...consumerImports,
|
|
168
|
-
`export default [${all}]`,
|
|
169
|
-
].filter(Boolean).join('\n')
|
|
170
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
declare module '#kestrel/collections' {
|
|
2
|
-
const collections: unknown[]
|
|
3
|
-
export default collections
|
|
4
|
-
}
|
|
5
|
-
declare module '#kestrel/blocks' {
|
|
6
|
-
const blocks: unknown[]
|
|
7
|
-
export default blocks
|
|
8
|
-
}
|
|
9
|
-
declare module '#kestrel/schema-tables' {
|
|
10
|
-
const tables: unknown[]
|
|
11
|
-
export default tables
|
|
12
|
-
}
|
|
13
|
-
declare module '#kestrel/module-manifests' {
|
|
14
|
-
import type { OwnershipManifest } from '@michaelthielemann/kestrel-contracts'
|
|
15
|
-
const manifests: OwnershipManifest[]
|
|
16
|
-
export default manifests
|
|
17
|
-
}
|
|
18
|
-
declare module '#build/kestrel-layouts.mjs' {
|
|
19
|
-
export const kestrelLayouts: string[]
|
|
20
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { addComponentsDir, defineNuxtModule } from '@nuxt/kit'
|
|
2
|
-
import { resolve } from 'node:path'
|
|
3
|
-
import { KESTREL_COMPONENT_PREFIX, KESTREL_OVERRIDE_PRIORITY } from './shared'
|
|
4
|
-
|
|
5
|
-
/** Where a consumer places a component that replaces a shipped one, relative to their `srcDir`. */
|
|
6
|
-
export const KESTREL_OVERRIDE_DIR = 'Kestrel/components'
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* RULING: the app-side layers (`admin`, `ui`, and the `app/` halves of `media`/`public`) stay LAYERS —
|
|
10
|
-
* they are not bridged into packages, and there is no `addComponentsDir`/`addImportsDir`
|
|
11
|
-
* module doing so. `admin` is the layer with the most at stake here: it registers by far the most
|
|
12
|
-
* components under `kestrelComponents()` (`layers/admin/nuxt.config.ts`, alongside `media`/`public`/`ui`),
|
|
13
|
-
* so it is the layer this override mechanism protects most. This module's own override mechanism (below)
|
|
14
|
-
* is WHY: it depends on Kestrel's own components being real, Nuxt-scannable `.vue` files in a real layer,
|
|
15
|
-
* resolved at a higher `addComponentsDir` priority than a consumer's override — a package's compiled
|
|
16
|
-
* `dist` output cannot participate in that priority system the same way. Bridging would cost the admin its
|
|
17
|
-
* per-component override story for no boot-order/discovery benefit these app-side files need (none of the
|
|
18
|
-
* eager-module-load or field-type-registration hazards the *server*-side package cut existed to fix apply
|
|
19
|
-
* here). Revisit only with strong evidence the cost is worth paying — see `docs/internals/layers-and-packages.md` §
|
|
20
|
-
* "The component/composable bridge".
|
|
21
|
-
*
|
|
22
|
-
* Composables are a separate question this mechanism does NOT cover: none of `admin`/`ui`'s ~30
|
|
23
|
-
* `use*`-named composables carry a `useKestrel*` prefix, and none need to — every one is internal editor
|
|
24
|
-
* state/logic (`useEditForm`, `useBlockTree`, `useCollectionOps`, …). Kestrel's own layers reuse them
|
|
25
|
-
* freely across the merged auto-import namespace (`media` uses `ui`'s `useT`/`useToast`/`useDragReorder`);
|
|
26
|
-
* what stays true is that no extension, package, or third-party consumer imports one, and none is ever
|
|
27
|
-
* documented as something a consumer reaches for — a consumer builds their own UI against collections/
|
|
28
|
-
* pipelines/field types, not by importing Kestrel's editor internals. There is consequently no override
|
|
29
|
-
* mechanism for composables (unlike components) and none is needed.
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
|
-
export default defineNuxtModule({
|
|
33
|
-
meta: { name: 'kestrel-component-namespace' },
|
|
34
|
-
setup(_options, nuxt) {
|
|
35
|
-
// The trailing `components` segment is load-bearing: Nuxt reports a missing registered directory
|
|
36
|
-
// (NUXT_B3001) unless the path matches its default-components pattern, which requires that suffix.
|
|
37
|
-
// Without it every consumer who never uses the seam would see a build warning.
|
|
38
|
-
addComponentsDir({
|
|
39
|
-
path: resolve(nuxt.options.srcDir, KESTREL_OVERRIDE_DIR),
|
|
40
|
-
prefix: KESTREL_COMPONENT_PREFIX,
|
|
41
|
-
priority: KESTREL_OVERRIDE_PRIORITY,
|
|
42
|
-
})
|
|
43
|
-
},
|
|
44
|
-
})
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { fileURLToPath } from 'node:url'
|
|
2
|
-
|
|
3
|
-
export const KESTREL_COMPONENT_PREFIX = 'Kestrel'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Above every layer-derived priority (Nuxt assigns `layerCount - i`, so the consumer's app dir — index 0
|
|
7
|
-
* — otherwise always wins). A consumer file that lands on a `Kestrel*` name anywhere but the override
|
|
8
|
-
* directory therefore loses instead of silently replacing the admin component.
|
|
9
|
-
*/
|
|
10
|
-
export const KESTREL_LAYER_PRIORITY = 100
|
|
11
|
-
|
|
12
|
-
/** The one supported override seam, above the layers. */
|
|
13
|
-
export const KESTREL_OVERRIDE_PRIORITY = 200
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* The components directory of a Kestrel layer, namespaced. Nuxt drops a prefix the filename already
|
|
17
|
-
* carries, so `KestrelImg.vue` stays `KestrelImg` while `ui/Button.vue` becomes `KestrelUiButton`.
|
|
18
|
-
* Pass the layer's own `import.meta.url`.
|
|
19
|
-
*/
|
|
20
|
-
export const kestrelComponents = (layerUrl: string) => ({
|
|
21
|
-
path: fileURLToPath(new URL('./app/components', layerUrl)),
|
|
22
|
-
prefix: KESTREL_COMPONENT_PREFIX,
|
|
23
|
-
priority: KESTREL_LAYER_PRIORITY,
|
|
24
|
-
})
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
export type AppShellDiagnostic = { level: 'error' | 'warn'; message: string }
|
|
2
|
-
|
|
3
|
-
export type AppShellInput = {
|
|
4
|
-
mainComponent: string | null | undefined
|
|
5
|
-
pagesEnabled: boolean
|
|
6
|
-
read: (file: string) => string
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const withoutComments = (src: string) => src.replace(/<!--[\s\S]*?-->/g, '')
|
|
10
|
-
|
|
11
|
-
// `<nuxt-page />` is as valid as `<NuxtPage />`; matching only the Pascal spelling would flag a working app.
|
|
12
|
-
const kebab = (tag: string) => tag.replace(/(?!^)([A-Z])/g, '-$1').toLowerCase()
|
|
13
|
-
const uses = (src: string, tag: string) => new RegExp(`<\\s*(${tag}|${kebab(tag)})[\\s/>]`, 'i').test(src)
|
|
14
|
-
|
|
15
|
-
const FIX = `
|
|
16
|
-
<template>
|
|
17
|
-
<NuxtLayout>
|
|
18
|
-
<NuxtPage />
|
|
19
|
-
</NuxtLayout>
|
|
20
|
-
</template>`
|
|
21
|
-
|
|
22
|
-
/** Reports only — assigning `mainComponent` would defeat a legitimate override. See ADR-0005. */
|
|
23
|
-
export function diagnoseAppShell({ mainComponent, pagesEnabled, read }: AppShellInput): AppShellDiagnostic[] {
|
|
24
|
-
const found: AppShellDiagnostic[] = []
|
|
25
|
-
|
|
26
|
-
if (!pagesEnabled) {
|
|
27
|
-
found.push({
|
|
28
|
-
level: 'error',
|
|
29
|
-
message:
|
|
30
|
-
'the pages feature is disabled (`pages: false`), so no route is registered at all — the admin is a set of pages under `app/pages/admin/`. Remove the override to reach /admin.',
|
|
31
|
-
})
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (mainComponent) {
|
|
35
|
-
let src: string | undefined
|
|
36
|
-
try {
|
|
37
|
-
src = withoutComments(read(mainComponent))
|
|
38
|
-
} catch {
|
|
39
|
-
// An unreadable app root is Nuxt's to report; guessing here would produce a phantom error.
|
|
40
|
-
}
|
|
41
|
-
if (src !== undefined && !uses(src, 'NuxtPage')) {
|
|
42
|
-
found.push({
|
|
43
|
-
level: 'error',
|
|
44
|
-
message: `${mainComponent} renders no <NuxtPage />, so NO route renders — including /admin. It shadows Kestrel's own app.vue because the consumer layer wins. Either delete it (Kestrel ships a working one) or make it:${FIX}`,
|
|
45
|
-
})
|
|
46
|
-
} else if (src !== undefined && !uses(src, 'NuxtLayout')) {
|
|
47
|
-
found.push({
|
|
48
|
-
level: 'warn',
|
|
49
|
-
message: `${mainComponent} renders no <NuxtLayout />, so page-level \`layout\` is ignored and the admin loses its navigation shell. Wrap <NuxtPage /> in <NuxtLayout>.`,
|
|
50
|
-
})
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return found
|
|
55
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Kestrel's authoring API, registered as Nuxt/Nitro auto-imports so a site built on Kestrel can write
|
|
3
|
-
* `defineCollection({...})` or `getCollection('pages')` without importing — the same surface a 3.x site
|
|
4
|
-
* had when this code still lived in the layers' `server/utils`. The names are read off the packages'
|
|
5
|
-
* real exports at build time, so a new public function is auto-importable the moment it is exported;
|
|
6
|
-
* every name stays importable explicitly from its package as well.
|
|
7
|
-
*/
|
|
8
|
-
export interface AutoImportEntry {
|
|
9
|
-
name: string
|
|
10
|
-
from: string
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const PKG = '@michaelthielemann/kestrel-'
|
|
14
|
-
|
|
15
|
-
/** Order matters: a name exported by several packages (re-exports) is registered from the first one. */
|
|
16
|
-
const SERVER_PACKAGES = ['core', 'fields', 'contracts', 'auth', 'access', 'media', 'collections', 'publishing', 'delivery-live', 'delivery-static']
|
|
17
|
-
const APP_PACKAGES = ['core/client', 'fields/client']
|
|
18
|
-
|
|
19
|
-
/** `kestrelDiscovery`: module manifests for the discovery module, never something a site calls.
|
|
20
|
-
* `renderRouteLive`: the public layer's own `server/utils` wrapper of the same name is the wired one, and
|
|
21
|
-
* Nitro's directory scan already auto-imports it — registering the package export too only draws a
|
|
22
|
-
* "duplicated imports" warning on every build. */
|
|
23
|
-
const EXCLUDED = new Set(['default', 'kestrelDiscovery', 'renderRouteLive'])
|
|
24
|
-
|
|
25
|
-
async function collect(packages: string[]): Promise<AutoImportEntry[]> {
|
|
26
|
-
const seen = new Set<string>()
|
|
27
|
-
const entries: AutoImportEntry[] = []
|
|
28
|
-
for (const pkg of packages) {
|
|
29
|
-
const from = PKG + pkg
|
|
30
|
-
const mod = (await import(from)) as Record<string, unknown>
|
|
31
|
-
for (const name of Object.keys(mod)) {
|
|
32
|
-
if (EXCLUDED.has(name) || seen.has(name)) continue
|
|
33
|
-
seen.add(name)
|
|
34
|
-
entries.push({ name, from })
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return entries
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** Server-only (Nitro) authoring API: every runtime export of the server packages. */
|
|
41
|
-
export const serverAutoImports = (): Promise<AutoImportEntry[]> => collect(SERVER_PACKAGES)
|
|
42
|
-
|
|
43
|
-
/** App-side (Vue) authoring API: every runtime export of the client entry points. */
|
|
44
|
-
export const appAutoImports = (): Promise<AutoImportEntry[]> => collect(APP_PACKAGES)
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs'
|
|
2
|
-
import { defineNuxtModule } from '@nuxt/kit'
|
|
3
|
-
import { resolveKestrel, type KestrelConfig } from '@michaelthielemann/kestrel-core'
|
|
4
|
-
import { diagnoseAppShell } from './app-shell'
|
|
5
|
-
import { appAutoImports, serverAutoImports } from './auto-imports'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* The `kestrel` config namespace (`kestrel: { … }` in nuxt.config, sourced from `kestrel.config.ts`).
|
|
9
|
-
* Lives in `core` so it runs before the `media`/`public` layer modules and populates runtimeConfig
|
|
10
|
-
* first. It bridges the resolved config to what the CLIENT + Nitro need: `runtimeConfig.media.*`,
|
|
11
|
-
* `runtimeConfig.public.{locales,primaryLocale}`, and the `/uploads` static serving. Server runtime
|
|
12
|
-
* utils (db/locale/site-url) read the same resolved values directly via `server/utils/kestrel-config.ts`.
|
|
13
|
-
*/
|
|
14
|
-
export default defineNuxtModule<KestrelConfig>({
|
|
15
|
-
meta: { name: 'kestrel', configKey: 'kestrel' },
|
|
16
|
-
async setup(options, nuxt) {
|
|
17
|
-
const c = resolveKestrel(options, process.env, nuxt.options.rootDir)
|
|
18
|
-
|
|
19
|
-
// Registered through hooks rather than `addImports`/`addServerImports`, which need an active Kit
|
|
20
|
-
// instance (`useNuxt()`) that plain `setup()` callers (tests) do not provide.
|
|
21
|
-
const [app, server] = await Promise.all([appAutoImports(), serverAutoImports()])
|
|
22
|
-
// The packages' own compiled output must not be rewritten by the auto-import transform: it would
|
|
23
|
-
// inject an import for a symbol a dist file declares itself. Under `node_modules` the default exclusion
|
|
24
|
-
// covers that; in a workspace checkout the packages resolve to `packages/*/dist`, which it does not.
|
|
25
|
-
// Nitro REPLACES its `node_modules` default with any non-empty `exclude`, so that one is restated.
|
|
26
|
-
const ownDist = /[\\/](node_modules|packages)[\\/](@michaelthielemann[\\/])?kestrel-[^\\/]+[\\/]/
|
|
27
|
-
const imports = (nuxt.options.imports ||= {} as typeof nuxt.options.imports)
|
|
28
|
-
imports.transform ||= {}
|
|
29
|
-
imports.transform.exclude = [...(imports.transform.exclude ?? []), ownDist]
|
|
30
|
-
nuxt.hook('imports:extend', (imports) => {
|
|
31
|
-
imports.push(...app)
|
|
32
|
-
})
|
|
33
|
-
nuxt.hook('nitro:config', (config) => {
|
|
34
|
-
config.imports ||= {}
|
|
35
|
-
config.imports.imports ||= []
|
|
36
|
-
config.imports.imports.push(...server)
|
|
37
|
-
const prior = config.imports.exclude
|
|
38
|
-
const excluded = Array.isArray(prior) ? [...prior] : prior ? [prior] : []
|
|
39
|
-
config.imports.exclude = [...excluded, /node_modules/, ownDist]
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
// `app:resolve` is the first hook where `mainComponent` is settled across all layers. In dev it fires
|
|
43
|
-
// on every watched change, so an unfixed problem would repaint the whole message on each keystroke.
|
|
44
|
-
const reported = new Set<string>()
|
|
45
|
-
nuxt.hook('app:resolve', (app) => {
|
|
46
|
-
for (const d of diagnoseAppShell({
|
|
47
|
-
mainComponent: app.mainComponent,
|
|
48
|
-
pagesEnabled: nuxt.options.pages !== false,
|
|
49
|
-
read: (file) => readFileSync(file, 'utf8'),
|
|
50
|
-
})) {
|
|
51
|
-
if (reported.has(d.message)) continue
|
|
52
|
-
reported.add(d.message)
|
|
53
|
-
console[d.level === 'error' ? 'error' : 'warn'](`[kestrel] ${d.message}`)
|
|
54
|
-
}
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
const rc = nuxt.options.runtimeConfig
|
|
58
|
-
rc.media = {
|
|
59
|
-
driver: c.media.driver,
|
|
60
|
-
maxUploadBytes: c.media.maxUploadBytes,
|
|
61
|
-
allowedMimes: c.media.allowedMimes,
|
|
62
|
-
imagePolicy: c.media.imagePolicy,
|
|
63
|
-
local: { dir: c.media.dir, baseUrl: c.media.baseUrl },
|
|
64
|
-
// Non-secret S3 settings come from the resolved config; the credentials are env-only (never
|
|
65
|
-
// committed) and merged in here so the server driver gets everything from runtimeConfig.
|
|
66
|
-
s3: {
|
|
67
|
-
...c.media.s3,
|
|
68
|
-
accessKeyId: process.env.KESTREL_S3_ACCESS_KEY_ID ?? '',
|
|
69
|
-
secretAccessKey: process.env.KESTREL_S3_SECRET_ACCESS_KEY ?? '',
|
|
70
|
-
sessionToken: process.env.KESTREL_S3_SESSION_TOKEN ?? '',
|
|
71
|
-
},
|
|
72
|
-
}
|
|
73
|
-
const pub = (rc.public ??= {} as never) as Record<string, unknown>
|
|
74
|
-
pub.locales = c.supportedLocales
|
|
75
|
-
pub.primaryLocale = c.primaryLocale
|
|
76
|
-
pub.prefixPrimary = c.prefixPrimary
|
|
77
|
-
// The public head (canonical/og:url/hreflang) needs the absolute origin CLIENT-side too (SPA navs
|
|
78
|
-
// update the head after hydration). Not a secret — it is the site's own public address.
|
|
79
|
-
pub.siteUrl = c.siteUrl ?? ''
|
|
80
|
-
pub.siteName = c.siteName ?? ''
|
|
81
|
-
// Reference desktop width (px) for the admin page-builder preset; the editor's scale-to-fit shrinks it.
|
|
82
|
-
pub.previewDesktopWidth = c.preview.desktopWidth
|
|
83
|
-
// Whether the admin shows the EU AI Act disclosure controls on a media asset; the data itself is
|
|
84
|
-
// always resolved server-side, so flipping this off only hides the editor.
|
|
85
|
-
pub.aiDisclosureEnabled = c.aiDisclosure.enabled
|
|
86
|
-
// Article metadata is an APP-side concern on both ends — the admin SEO panel decides whether to offer
|
|
87
|
-
// the fields, the public page decides whether to publish them as JSON-LD — so the flag has to be
|
|
88
|
-
// client-visible. It gates a disclosure, not a secret.
|
|
89
|
-
pub.seoArticleMeta = c.seo.articleMeta
|
|
90
|
-
|
|
91
|
-
// Server-only resolved settings, so server utils read the CONSUMER's `kestrel: {}` (via this module)
|
|
92
|
-
// rather than importing Kestrel's own `kestrel.config.ts` file. Essential when Kestrel is consumed as
|
|
93
|
-
// a layer: the package's config file is empty, the real config lives in the consumer's nuxt.config.
|
|
94
|
-
const srv = (rc.kestrel ??= {} as never) as Record<string, unknown>
|
|
95
|
-
srv.dbPath = c.dbPath
|
|
96
|
-
srv.siteUrl = c.siteUrl
|
|
97
|
-
srv.siteName = c.siteName
|
|
98
|
-
srv.siteDescription = c.siteDescription
|
|
99
|
-
// Built-in collection toggles (pages/media) — read by the register plugin to gate built-ins.
|
|
100
|
-
srv.collections = c.collections
|
|
101
|
-
// Read by the upload route to decide whether to run the AI-signal scan at all.
|
|
102
|
-
srv.aiDisclosure = c.aiDisclosure
|
|
103
|
-
// Answer-engine toggles; `llmsFull` is server-only (it gates a Nitro route and what the publisher writes).
|
|
104
|
-
srv.seo = c.seo
|
|
105
|
-
// Read by delivery-live's catch-all/read pipeline (see `KestrelConfig.delivery`'s own TSDoc) to
|
|
106
|
-
// decide whether to answer at all — everything else about it stays 'static'-shaped either way.
|
|
107
|
-
srv.delivery = c.delivery
|
|
108
|
-
// Extra path prefixes the live catch-all must fall through on (see `KestrelConfig.deliveryExempt`).
|
|
109
|
-
srv.deliveryExempt = c.deliveryExempt
|
|
110
|
-
// Static-publish target for the runtime publisher (server-only). S3 creds are env-only; prefer the
|
|
111
|
-
// output-specific creds, fall back to the shared media creds so a single S3 account "just works".
|
|
112
|
-
srv.output = {
|
|
113
|
-
driver: c.output.driver,
|
|
114
|
-
dir: c.output.dir,
|
|
115
|
-
publicDir: c.output.publicDir,
|
|
116
|
-
auto: c.output.auto,
|
|
117
|
-
publishOnSave: c.output.publishOnSave,
|
|
118
|
-
reconcileMinutes: c.output.reconcileMinutes,
|
|
119
|
-
verbose: c.output.verbose,
|
|
120
|
-
s3: {
|
|
121
|
-
...c.output.s3,
|
|
122
|
-
accessKeyId: process.env.KESTREL_OUTPUT_S3_ACCESS_KEY_ID ?? process.env.KESTREL_S3_ACCESS_KEY_ID ?? '',
|
|
123
|
-
secretAccessKey: process.env.KESTREL_OUTPUT_S3_SECRET_ACCESS_KEY ?? process.env.KESTREL_S3_SECRET_ACCESS_KEY ?? '',
|
|
124
|
-
sessionToken: process.env.KESTREL_OUTPUT_S3_SESSION_TOKEN ?? process.env.KESTREL_S3_SESSION_TOKEN ?? '',
|
|
125
|
-
},
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
nuxt.options.nitro ??= {}
|
|
129
|
-
// Only the local driver serves uploads off disk from the app origin (dev + baked into `nuxt
|
|
130
|
-
// generate`). On S3 the bucket/CDN serves them, so we must not register a publicAssets entry for
|
|
131
|
-
// a directory that isn't the store (it would 404 or shadow real keys).
|
|
132
|
-
if (c.media.driver === 'local') {
|
|
133
|
-
nuxt.options.nitro.publicAssets = [
|
|
134
|
-
...(nuxt.options.nitro.publicAssets ?? []),
|
|
135
|
-
{ baseURL: c.media.baseUrl, dir: c.media.dir, maxAge: 0 },
|
|
136
|
-
]
|
|
137
|
-
// Uploads are served from the app origin; never let the browser content-sniff past the declared type.
|
|
138
|
-
nuxt.options.nitro.routeRules = {
|
|
139
|
-
...(nuxt.options.nitro.routeRules ?? {}),
|
|
140
|
-
[`${c.media.baseUrl}/**`]: { headers: { 'X-Content-Type-Options': 'nosniff' } },
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
},
|
|
144
|
-
})
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { defineNuxtModule } from '@nuxt/kit'
|
|
2
|
-
import { assertEffectiveOrderMatches, resolvePluginOrder, validatePluginOrder } from './plugin-order'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Owns the execution order of the plugins Kestrel's own 6 layers ship (core/fields/media/auth/
|
|
6
|
-
* collections/public — see `plugin-order.ts`'s `PLUGIN_ORDER`), replacing Nitro's layer-then-filename
|
|
7
|
-
* sort with an explicit, declared list. A consumer's own plugins (and any extension layer's) are
|
|
8
|
-
* untouched — they keep using Nitro's default scan-and-append, landing wherever their own layer position
|
|
9
|
-
* in `extends` naturally puts them; this module only orders Kestrel's own.
|
|
10
|
-
*
|
|
11
|
-
* Mechanism: the `nitro:config` hook's config object (which becomes `nitro.options` once Nitro's own
|
|
12
|
-
* instance is built) gets `.plugins` pushed with `PLUGIN_ORDER`'s resolved absolute paths, in order — this
|
|
13
|
-
* hook runs BEFORE Nitro's own `scanPlugins` step. That step only appends a scanned file when it is not
|
|
14
|
-
* ALREADY in `nitro.options.plugins`
|
|
15
|
-
* (`nitropack/dist/core/index.mjs`: `if (!nitro.options.plugins.includes(plugin)) plugins.push(plugin)`),
|
|
16
|
-
* so every one of our explicitly-pushed paths is skipped by the scan (already present, same absolute path)
|
|
17
|
-
* and keeps our declared position — nothing needs renaming or moving on disk. `nitro:config` alone only
|
|
18
|
-
* proves what WE push, though — a separate `nitro:init` hook (which fires after every `nitro:config` hook
|
|
19
|
-
* and Nitro's own scan-and-append have both already run, against the fully-built Nitro instance) asserts
|
|
20
|
-
* the REAL, final `nitro.options.plugins` still starts with our declared order, contiguous and duplicate-
|
|
21
|
-
* free — see `assertEffectiveOrderMatches`'s own TSDoc for why this observer, not the push alone, is what
|
|
22
|
-
* actually proves "declared order ≡ effective order".
|
|
23
|
-
*
|
|
24
|
-
* The ADR-0029 eager-barrel-load guard for a future `00.*` plugin: NOT a mechanism in this module. It
|
|
25
|
-
* doesn't need to be — `@michaelthielemann/kestrel-media`/`@michaelthielemann/kestrel-collections`/`@michaelthielemann/kestrel-publishing`'s own barrels now
|
|
26
|
-
* self-guard (a used-binding `@michaelthielemann/kestrel-fields` import, first), so importing ANY of them is safe from ANY
|
|
27
|
-
* plugin position, including a hypothetical new `00.*` file. The computed rail proving this
|
|
28
|
-
* (`test/architecture/kestrel-discovery.test.ts`'s "every package whose OWN module graph reaches
|
|
29
|
-
* buildCollection()...") already re-checks every package on every test run, so a FUTURE package that
|
|
30
|
-
* starts calling `buildCollection()` without the guard fails there — independent of whatever plugin order
|
|
31
|
-
* this module declares. Plugin order literally cannot reintroduce this hazard class any more, which is the
|
|
32
|
-
* honest, verifiable version of "prevents a future 00.* plugin from eagerly loading a heavy barrel
|
|
33
|
-
* pre-seed": the guard lives at the package boundary, not the boot-order boundary.
|
|
34
|
-
*/
|
|
35
|
-
/** The minimal shape this module needs from `nuxt` — narrowed so a test can pass a plain mock object
|
|
36
|
-
* instead of a real Nuxt instance (mirrors testing `defineNuxtModule`'s callback logic directly, not
|
|
37
|
-
* through Nuxt's own module-loading machinery). */
|
|
38
|
-
export interface MinimalNuxt {
|
|
39
|
-
options: { _layers: readonly { cwd: string }[] }
|
|
40
|
-
hook: {
|
|
41
|
-
(name: 'nitro:config', fn: (nitro: { plugins?: (string | undefined)[] }) => void): void
|
|
42
|
-
(name: 'nitro:init', fn: (nitro: { options: { plugins: (string | undefined)[] } }) => void): void
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** The module's actual logic, exported separately from `defineNuxtModule`'s wrapper so a test can call it
|
|
47
|
-
* against a plain mock `nuxt`/`nitro` pair and assert `nitro.plugins` ends up EXACTLY
|
|
48
|
-
* `resolvePluginOrder(roots)`, in order — the "declared order ≡ effective order" proof, at unit speed,
|
|
49
|
-
* independent of the real-boot proof (a real `nuxt build`, inspected directly). */
|
|
50
|
-
export function setupPluginOrder(nuxt: MinimalNuxt): void {
|
|
51
|
-
const roots = nuxt.options._layers.map((layer) => layer.cwd)
|
|
52
|
-
validatePluginOrder(roots) // throws — fails the build loudly on any drift, before Nitro ever scans
|
|
53
|
-
|
|
54
|
-
nuxt.hook('nitro:config', (nitro) => {
|
|
55
|
-
nitro.plugins ||= []
|
|
56
|
-
nitro.plugins.push(...resolvePluginOrder(roots))
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
nuxt.hook('nitro:init', (nitro) => {
|
|
60
|
-
assertEffectiveOrderMatches(nitro.options.plugins, resolvePluginOrder(roots))
|
|
61
|
-
})
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export default defineNuxtModule({
|
|
65
|
-
meta: { name: 'kestrel-plugin-order' },
|
|
66
|
-
setup(_options, nuxt) {
|
|
67
|
-
setupPluginOrder(nuxt)
|
|
68
|
-
},
|
|
69
|
-
})
|