@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,143 +0,0 @@
|
|
|
1
|
-
export type IconName =
|
|
2
|
-
| 'grip' | 'chevron-up' | 'chevron-down' | 'chevron-left' | 'chevron-right' | 'arrow-left'
|
|
3
|
-
| 'copy' | 'trash' | 'x' | 'plus' | 'pencil' | 'check'
|
|
4
|
-
| 'home' | 'file-text' | 'folder' | 'folder-plus' | 'panel-left-close' | 'panel-left-open' | 'log-out'
|
|
5
|
-
| 'sun' | 'moon' | 'calendar' | 'upload' | 'layout-grid' | 'table'
|
|
6
|
-
| 'settings' | 'image' | 'newspaper' | 'columns' | 'filter' | 'search' | 'sliders'
|
|
7
|
-
| 'bold' | 'italic' | 'underline' | 'strikethrough' | 'code' | 'square-code' | 'highlighter'
|
|
8
|
-
| 'subscript' | 'superscript' | 'list' | 'list-ordered' | 'quote' | 'minus' | 'message-square'
|
|
9
|
-
| 'align-left' | 'align-center' | 'align-right' | 'align-justify'
|
|
10
|
-
| 'eraser' | 'undo' | 'redo' | 'link' | 'link-2'
|
|
11
|
-
| 'external-link' | 'triangle-alert' | 'globe'
|
|
12
|
-
| 'eye' | 'eye-off'
|
|
13
|
-
| 'monitor' | 'tablet' | 'smartphone' | 'rotate-cw'
|
|
14
|
-
| 'clock' | 'user'
|
|
15
|
-
|
|
16
|
-
export const icons: Record<IconName, string> = {
|
|
17
|
-
'monitor': '<rect width="20" height="14" x="2" y="3" rx="2"/><line x1="8" x2="16" y1="21" y2="21"/><line x1="12" x2="12" y1="17" y2="21"/>',
|
|
18
|
-
'tablet': '<rect width="16" height="20" x="4" y="2" rx="2" ry="2"/><line x1="12" x2="12.01" y1="18" y2="18"/>',
|
|
19
|
-
'smartphone': '<rect width="14" height="20" x="5" y="2" rx="2" ry="2"/><path d="M12 18h.01"/>',
|
|
20
|
-
'rotate-cw': '<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/>',
|
|
21
|
-
'clock': '<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>',
|
|
22
|
-
'user': '<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>',
|
|
23
|
-
'chevron-up': '<path d="m18 15-6-6-6 6"/>',
|
|
24
|
-
'chevron-down': '<path d="m6 9 6 6 6-6"/>',
|
|
25
|
-
'chevron-left': '<path d="m15 18-6-6 6-6"/>',
|
|
26
|
-
'chevron-right': '<path d="m9 18 6-6-6-6"/>',
|
|
27
|
-
'arrow-left': '<path d="m12 19-7-7 7-7"/><path d="M19 12H5"/>',
|
|
28
|
-
'x': '<path d="M18 6 6 18"/><path d="m6 6 12 12"/>',
|
|
29
|
-
'plus': '<path d="M5 12h14"/><path d="M12 5v14"/>',
|
|
30
|
-
'check': '<path d="M20 6 9 17l-5-5"/>',
|
|
31
|
-
'trash': '<path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/><line x1="10" x2="10" y1="11" y2="17"/><line x1="14" x2="14" y1="11" y2="17"/>',
|
|
32
|
-
'copy': '<rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>',
|
|
33
|
-
'grip': '<circle cx="9" cy="6" r="1"/><circle cx="9" cy="12" r="1"/><circle cx="9" cy="18" r="1"/><circle cx="15" cy="6" r="1"/><circle cx="15" cy="12" r="1"/><circle cx="15" cy="18" r="1"/>',
|
|
34
|
-
'pencil': '<path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"/><path d="m15 5 4 4"/>',
|
|
35
|
-
'home': '<path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><path d="M9 22V12h6v10"/>',
|
|
36
|
-
'file-text': '<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M10 9H8"/><path d="M16 13H8"/><path d="M16 17H8"/>',
|
|
37
|
-
'folder': '<path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/>',
|
|
38
|
-
'panel-left-close': '<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M9 3v18"/><path d="m16 15-3-3 3-3"/>',
|
|
39
|
-
'panel-left-open': '<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M9 3v18"/><path d="m14 9 3 3-3 3"/>',
|
|
40
|
-
'log-out': '<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><path d="m16 17 5-5-5-5"/><path d="M21 12H9"/>',
|
|
41
|
-
'sun': '<circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/>',
|
|
42
|
-
'moon': '<path d="M12 3a6.36 6.36 0 0 0 9 9 9 9 0 1 1-9-9Z"/>',
|
|
43
|
-
'calendar': '<path d="M8 2v4"/><path d="M16 2v4"/><rect width="18" height="18" x="3" y="4" rx="2"/><path d="M3 10h18"/>',
|
|
44
|
-
'folder-plus': '<path d="M12 10v6"/><path d="M9 13h6"/><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/>',
|
|
45
|
-
'upload': '<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" x2="12" y1="3" y2="15"/>',
|
|
46
|
-
'layout-grid': '<rect width="7" height="7" x="3" y="3" rx="1"/><rect width="7" height="7" x="14" y="3" rx="1"/><rect width="7" height="7" x="14" y="14" rx="1"/><rect width="7" height="7" x="3" y="14" rx="1"/>',
|
|
47
|
-
'table': '<path d="M12 3v18"/><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M3 9h18"/><path d="M3 15h18"/>',
|
|
48
|
-
'bold': '<path d="M14 12a4 4 0 0 0 0-8H6v8"/><path d="M15 20a4 4 0 0 0 0-8H6v8Z"/>',
|
|
49
|
-
'italic': '<line x1="19" x2="10" y1="4" y2="4"/><line x1="14" x2="5" y1="20" y2="20"/><line x1="15" x2="9" y1="4" y2="20"/>',
|
|
50
|
-
'underline': '<path d="M6 4v6a6 6 0 0 0 12 0V4"/><line x1="4" x2="20" y1="21" y2="21"/>',
|
|
51
|
-
'strikethrough': '<path d="M16 4H9a3 3 0 0 0-2.83 4"/><path d="M14 12a4 4 0 0 1 0 8H6"/><line x1="4" x2="20" y1="12" y2="12"/>',
|
|
52
|
-
'code': '<polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>',
|
|
53
|
-
'square-code': '<path d="m10 9-3 3 3 3"/><path d="m14 15 3-3-3-3"/><rect width="18" height="18" x="3" y="3" rx="2"/>',
|
|
54
|
-
'highlighter': '<path d="m9 11-6 6v3h9l3-3"/><path d="m22 12-4.6 4.6a2 2 0 0 1-2.8 0l-5.2-5.2a2 2 0 0 1 0-2.8L14 4"/>',
|
|
55
|
-
'subscript': '<path d="m4 5 8 8"/><path d="m12 5-8 8"/><path d="M20 19h-4c0-1.5.44-2 1.5-2.5S20 15.33 20 14c0-.47-.17-.93-.48-1.29a2.11 2.11 0 0 0-2.62-.44c-.42.24-.74.62-.9 1.07"/>',
|
|
56
|
-
'superscript': '<path d="m4 19 8-8"/><path d="m12 19-8-8"/><path d="M20 12h-4c0-1.5.44-2 1.5-2.5S20 8.33 20 7c0-.47-.17-.93-.48-1.29a2.11 2.11 0 0 0-2.62-.44c-.42.24-.74.62-.9 1.07"/>',
|
|
57
|
-
'list': '<line x1="8" x2="21" y1="6" y2="6"/><line x1="8" x2="21" y1="12" y2="12"/><line x1="8" x2="21" y1="18" y2="18"/><line x1="3" x2="3.01" y1="6" y2="6"/><line x1="3" x2="3.01" y1="12" y2="12"/><line x1="3" x2="3.01" y1="18" y2="18"/>',
|
|
58
|
-
'list-ordered': '<line x1="10" x2="21" y1="6" y2="6"/><line x1="10" x2="21" y1="12" y2="12"/><line x1="10" x2="21" y1="18" y2="18"/><path d="M4 6h1v4"/><path d="M4 10h2"/><path d="M6 18H4c0-1 2-2 2-3s-1-1.5-2-1"/>',
|
|
59
|
-
'quote': '<path d="M17 6H3"/><path d="M21 12H8"/><path d="M21 18H8"/><path d="M3 12v6"/>',
|
|
60
|
-
'message-square': '<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>',
|
|
61
|
-
'minus': '<path d="M5 12h14"/>',
|
|
62
|
-
'align-left': '<line x1="21" x2="3" y1="6" y2="6"/><line x1="15" x2="3" y1="12" y2="12"/><line x1="17" x2="3" y1="18" y2="18"/>',
|
|
63
|
-
'align-center': '<line x1="21" x2="3" y1="6" y2="6"/><line x1="17" x2="7" y1="12" y2="12"/><line x1="19" x2="5" y1="18" y2="18"/>',
|
|
64
|
-
'align-right': '<line x1="21" x2="3" y1="6" y2="6"/><line x1="21" x2="9" y1="12" y2="12"/><line x1="21" x2="7" y1="18" y2="18"/>',
|
|
65
|
-
'align-justify': '<line x1="3" x2="21" y1="6" y2="6"/><line x1="3" x2="21" y1="12" y2="12"/><line x1="3" x2="21" y1="18" y2="18"/>',
|
|
66
|
-
'eraser': '<path d="m7 21-4.3-4.3c-1-1-1-2.5 0-3.4l9.6-9.6c1-1 2.5-1 3.4 0l5.6 5.6c1 1 1 2.5 0 3.4L13 21"/><path d="M22 21H7"/><path d="m5 11 9 9"/>',
|
|
67
|
-
'undo': '<path d="M3 7v6h6"/><path d="M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13"/>',
|
|
68
|
-
'redo': '<path d="M21 7v6h-6"/><path d="M3 17a9 9 0 0 1 9-9 9 9 0 0 1 6 2.3l3 2.7"/>',
|
|
69
|
-
'link': '<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>',
|
|
70
|
-
'link-2': '<path d="M9 17H7A5 5 0 0 1 7 7h2"/><path d="M15 7h2a5 5 0 1 1 0 10h-2"/><line x1="8" x2="16" y1="12" y2="12"/>',
|
|
71
|
-
'external-link': '<path d="M15 3h6v6"/><path d="M10 14 21 3"/><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h6"/>',
|
|
72
|
-
'globe': '<circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/>',
|
|
73
|
-
'triangle-alert': '<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/>',
|
|
74
|
-
'settings': '<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/>',
|
|
75
|
-
'image': '<rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/>',
|
|
76
|
-
'newspaper': '<path d="M15 18h-5"/><path d="M18 14h-8"/><path d="M15 22H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v18a2 2 0 0 0 4 0v-9a2 2 0 0 0-2-2h-2"/><rect width="8" height="4" x="6" y="6" rx="1"/>',
|
|
77
|
-
'columns': '<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M9 3v18"/><path d="M15 3v18"/>',
|
|
78
|
-
'filter': '<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"/>',
|
|
79
|
-
'search': '<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>',
|
|
80
|
-
'sliders': '<line x1="21" x2="14" y1="4" y2="4"/><line x1="10" x2="3" y1="4" y2="4"/><line x1="21" x2="12" y1="12" y2="12"/><line x1="8" x2="3" y1="12" y2="12"/><line x1="21" x2="16" y1="20" y2="20"/><line x1="12" x2="3" y1="20" y2="20"/><line x1="14" x2="14" y1="2" y2="6"/><line x1="8" x2="8" y1="10" y2="14"/><line x1="16" x2="16" y1="18" y2="22"/>',
|
|
81
|
-
'eye': '<path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0"/><circle cx="12" cy="12" r="3"/>',
|
|
82
|
-
'eye-off': '<path d="M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49"/><path d="M14.084 14.158a3 3 0 0 1-4.242-4.242"/><path d="M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143"/><path d="m2 2 20 20"/>',
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/** A registry key, or arbitrary (developer-defined) inline SVG. The named-icon overloads keep editor
|
|
86
|
-
* autocomplete while allowing a collection to supply its own icon markup. */
|
|
87
|
-
export type IconSpec = IconName | (string & {})
|
|
88
|
-
|
|
89
|
-
export function isIconName(name: string): name is IconName {
|
|
90
|
-
return Object.hasOwn(icons, name)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Known-safe SVG elements/attributes. Names are lowercased for comparison; the original casing is
|
|
94
|
-
// preserved on output (`viewBox`, `linearGradient`, … are case-sensitive in SVG).
|
|
95
|
-
const ALLOWED_SVG_TAGS = new Set([
|
|
96
|
-
'svg', 'path', 'circle', 'rect', 'line', 'polyline', 'polygon', 'ellipse',
|
|
97
|
-
'g', 'defs', 'lineargradient', 'radialgradient', 'stop', 'clippath', 'mask',
|
|
98
|
-
'title', 'desc',
|
|
99
|
-
])
|
|
100
|
-
const ALLOWED_SVG_ATTRS = new Set([
|
|
101
|
-
'd', 'cx', 'cy', 'r', 'rx', 'ry', 'x', 'y', 'x1', 'y1', 'x2', 'y2', 'fx', 'fy',
|
|
102
|
-
'width', 'height', 'points', 'transform', 'gradienttransform', 'viewbox', 'preserveaspectratio',
|
|
103
|
-
'fill', 'fill-rule', 'fill-opacity', 'stroke', 'stroke-width', 'stroke-linecap',
|
|
104
|
-
'stroke-linejoin', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-opacity', 'stroke-miterlimit',
|
|
105
|
-
'opacity', 'offset', 'stop-color', 'stop-opacity', 'gradientunits', 'spreadmethod',
|
|
106
|
-
'clip-path', 'clip-rule', 'id', 'class',
|
|
107
|
-
])
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Allowlist-sanitise developer-supplied icon SVG before it reaches `v-html` (Icon.vue). We keep ONLY
|
|
111
|
-
* known-safe SVG elements and attributes and drop everything else, rather than blocklisting bad patterns
|
|
112
|
-
* (a denylist is defeated by `/`-separated handlers, entity-encoded `javascript:`, etc.). So `<script>`,
|
|
113
|
-
* `<image>`, `<animate>`, `<a>`, `<use>`, `<foreignObject>`, every `on*` handler and every URL-bearing
|
|
114
|
-
* attribute are removed regardless of how they are written; no allowed element/attribute can load a URL
|
|
115
|
-
* or run script. Collection icons are developer-authored today, but this stays safe if collections become
|
|
116
|
-
* runtime-defined (the schema-engine roadmap), where `icon` would cross a user-controlled boundary.
|
|
117
|
-
*/
|
|
118
|
-
export function sanitizeIconSvg(svg: string): string {
|
|
119
|
-
return svg.replace(/<(\/?)([a-zA-Z][\w:-]*)((?:"[^"]*"|'[^']*'|[^>])*?)(\/?)>/g, (_m, close, tag, attrs, selfClose) => {
|
|
120
|
-
if (!ALLOWED_SVG_TAGS.has(tag.toLowerCase())) return ''
|
|
121
|
-
if (close) return `</${tag}>`
|
|
122
|
-
const kept: string[] = []
|
|
123
|
-
const attrRe = /([a-zA-Z_:][\w:.-]*)\s*=\s*("[^"]*"|'[^']*'|[^\s"'>]+)/g
|
|
124
|
-
let a: RegExpExecArray | null
|
|
125
|
-
while ((a = attrRe.exec(attrs))) {
|
|
126
|
-
if (ALLOWED_SVG_ATTRS.has(a[1].toLowerCase())) kept.push(`${a[1]}=${a[2]}`)
|
|
127
|
-
}
|
|
128
|
-
return `<${tag}${kept.length ? ' ' + kept.join(' ') : ''}${selfClose ? '/' : ''}>`
|
|
129
|
-
})
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Resolve an `IconSpec` to the inner SVG markup rendered inside the standard `<svg>` wrapper. A registry
|
|
134
|
-
* name yields its stored body; anything containing `<` is treated as raw inner-SVG markup (a full
|
|
135
|
-
* `<svg>…</svg>` is unwrapped to its children so it inherits the wrapper's size/`currentColor`), then
|
|
136
|
-
* sanitized. Unknown bare strings render nothing.
|
|
137
|
-
*/
|
|
138
|
-
export function resolveIconBody(name: string): string {
|
|
139
|
-
if (isIconName(name)) return icons[name]
|
|
140
|
-
if (!name.includes('<')) return ''
|
|
141
|
-
const inner = name.replace(/^\s*<svg[^>]*>([\s\S]*)<\/svg\s*>\s*$/i, '$1')
|
|
142
|
-
return sanitizeIconSvg(inner)
|
|
143
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { parseDate, parseDateTime, type DateValue } from '@internationalized/date'
|
|
2
|
-
|
|
3
|
-
export type IsoPrecision = 'date' | 'datetime'
|
|
4
|
-
|
|
5
|
-
/** ISO string → a `@internationalized/date` value for Reka; invalid/empty → undefined. */
|
|
6
|
-
export function isoToDate(iso: string | null | undefined, precision: IsoPrecision): DateValue | undefined {
|
|
7
|
-
if (!iso) return undefined
|
|
8
|
-
try {
|
|
9
|
-
return precision === 'date' ? parseDate(iso) : parseDateTime(iso)
|
|
10
|
-
} catch {
|
|
11
|
-
return undefined
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** A `@internationalized/date` value → ISO string; null/undefined → null. */
|
|
16
|
-
export function dateToIso(value: DateValue | null | undefined): string | null {
|
|
17
|
-
return value ? value.toString() : null
|
|
18
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { Localized } from '@michaelthielemann/kestrel-core'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Resolve a (possibly localized) label for the active admin language. A plain string is returned as-is
|
|
5
|
-
* (language-agnostic); a `{ <lang>: string }` map resolves to the requested language, then `en`, then any
|
|
6
|
-
* present value. Returns `undefined` when there is nothing to show, so callers fall back (e.g. to the
|
|
7
|
-
* collection/block name). One source of label resolution for the nav, list, editor and block panes.
|
|
8
|
-
*/
|
|
9
|
-
export function resolveLocalized(value: Localized | undefined, lang: string): string | undefined {
|
|
10
|
-
if (value == null) return undefined
|
|
11
|
-
if (typeof value === 'string') return value
|
|
12
|
-
return value[lang] ?? value.en ?? Object.values(value)[0]
|
|
13
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { InjectionKey, Ref } from 'vue'
|
|
2
|
-
|
|
3
|
-
/** Editor → field-widget context: lets a widget know the record's id (`'new'` until first save) and whether
|
|
4
|
-
* it has been saved. A field widget that manages out-of-band storage (e.g. the secure gallery's namespace)
|
|
5
|
-
* uses this to clean up after an abandoned, never-saved draft. Provided by `CollectionEditor`; mirrors the
|
|
6
|
-
* `blockEditKey` provide/inject precedent. Auto-imported (no path import needed in an extension). */
|
|
7
|
-
export interface RecordEditContext {
|
|
8
|
-
id: Ref<string>
|
|
9
|
-
saved: Ref<boolean>
|
|
10
|
-
/** The editor's reactive record values, so a widget can read SIBLING fields (e.g. the gallery `slug`,
|
|
11
|
-
* which the proofing overlay uses as the submission key). Reactive object — read `values.<field>`. */
|
|
12
|
-
values: Record<string, unknown>
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const recordEditContextKey = Symbol('kestrel.recordEdit') as InjectionKey<RecordEditContext>
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export function reorder<T>(arr: readonly T[], from: number, to: number): T[] {
|
|
2
|
-
if (from === to || from < 0 || to < 0 || from >= arr.length || to >= arr.length) {
|
|
3
|
-
return [...arr]
|
|
4
|
-
}
|
|
5
|
-
const result = [...arr]
|
|
6
|
-
result.splice(to, 0, result.splice(from, 1)[0] as T)
|
|
7
|
-
return result
|
|
8
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { Mark, Extension } from '@tiptap/vue-3' // re-exports @tiptap/core; keeps deps unchanged
|
|
2
|
-
|
|
3
|
-
// StarterKit has no span/TextStyle mark and no `class` attribute on block nodes, so parsing stored
|
|
4
|
-
// markup discards every consumer presentational class (e.g. the hero title's `text--color-white`).
|
|
5
|
-
// These two extensions teach the schema to keep `class` on span wrappers and on block nodes, matching
|
|
6
|
-
// what sanitize.ts already allows (`*: [class]`) so the class survives an edit/focus round-trip.
|
|
7
|
-
const classAttr = {
|
|
8
|
-
class: {
|
|
9
|
-
default: null as string | null,
|
|
10
|
-
parseHTML: (el: HTMLElement) => el.getAttribute('class'),
|
|
11
|
-
renderHTML: (a: Record<string, unknown>) => (a.class ? { class: a.class } : {}),
|
|
12
|
-
},
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// Inline `<span class="…">` wrapper. Only spans that HAVE a class become this mark, so ordinary spans
|
|
16
|
-
// aren't captured.
|
|
17
|
-
export const RichtextSpanClass = Mark.create({
|
|
18
|
-
name: 'spanClass',
|
|
19
|
-
parseHTML: () => [{ tag: 'span[class]' }],
|
|
20
|
-
renderHTML: ({ HTMLAttributes }) => ['span', HTMLAttributes, 0],
|
|
21
|
-
addAttributes: () => classAttr,
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
// Block-level class (e.g. `<p class="text--section-label">`).
|
|
25
|
-
export const RichtextBlockClass = Extension.create({
|
|
26
|
-
name: 'blockClass',
|
|
27
|
-
addGlobalAttributes: () => [{
|
|
28
|
-
types: ['heading', 'paragraph', 'blockquote', 'bulletList', 'orderedList', 'listItem', 'codeBlock'],
|
|
29
|
-
attributes: classAttr,
|
|
30
|
-
}],
|
|
31
|
-
})
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { computed, useId, type ComputedRef } from 'vue'
|
|
2
|
-
|
|
3
|
-
interface FieldA11yProps {
|
|
4
|
-
hint?: string
|
|
5
|
-
error?: string | null
|
|
6
|
-
required?: boolean
|
|
7
|
-
id?: string
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
interface FieldA11y {
|
|
11
|
-
fieldId: ComputedRef<string>
|
|
12
|
-
hintId: ComputedRef<string>
|
|
13
|
-
errId: ComputedRef<string>
|
|
14
|
-
describedby: ComputedRef<string | undefined>
|
|
15
|
-
ariaInvalid: ComputedRef<'true' | undefined>
|
|
16
|
-
ariaRequired: ComputedRef<'true' | undefined>
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Shared accessibility wiring for labelled controls (UiField) and grouped controls (UiFieldset):
|
|
21
|
-
* a stable id, hint/error ids, the aria-describedby join, and aria-invalid. One source so the
|
|
22
|
-
* describedby ordering and aria contract cannot drift between the two.
|
|
23
|
-
*/
|
|
24
|
-
export function useFieldA11y(props: FieldA11yProps): FieldA11y {
|
|
25
|
-
const auto = useId()
|
|
26
|
-
const fieldId = computed(() => props.id ?? auto)
|
|
27
|
-
const hintId = computed(() => `${fieldId.value}-hint`)
|
|
28
|
-
const errId = computed(() => `${fieldId.value}-error`)
|
|
29
|
-
const describedby = computed(() => {
|
|
30
|
-
const ids: string[] = []
|
|
31
|
-
if (props.hint) ids.push(hintId.value)
|
|
32
|
-
if (props.error) ids.push(errId.value)
|
|
33
|
-
return ids.length ? ids.join(' ') : undefined
|
|
34
|
-
})
|
|
35
|
-
const ariaInvalid = computed(() => (props.error ? 'true' : undefined))
|
|
36
|
-
// Grouped/non-native controls (role=group, teleported widgets) get required conveyed via aria-required,
|
|
37
|
-
// since the visual asterisk is aria-hidden and a bare `required` attr on a non-input is ignored by AT.
|
|
38
|
-
const ariaRequired = computed(() => (props.required ? 'true' : undefined))
|
|
39
|
-
return { fieldId, hintId, errId, describedby, ariaInvalid, ariaRequired }
|
|
40
|
-
}
|
package/layers/ui/nuxt.config.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { kestrelComponents } from '../core/modules/component-namespace/shared'
|
|
2
|
-
|
|
3
|
-
// The UI design system (tokens / reset / base in app/assets/scss/main.scss) is intentionally NOT loaded
|
|
4
|
-
// app-globally — that would leak Kestrel's CSS reset + base styles onto a consumer's public site and fight
|
|
5
|
-
// their own CSS (e.g. Bootstrap). It is imported by the admin layout (layers/admin/app/layouts/admin.vue)
|
|
6
|
-
// instead, which scopes it to /admin: every admin route uses that layout, and /admin/** is ssr:false.
|
|
7
|
-
export default defineNuxtConfig({
|
|
8
|
-
components: [kestrelComponents(import.meta.url)],
|
|
9
|
-
})
|
package/nuxt.config.ts
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import kestrel from './kestrel.config'
|
|
2
|
-
import { layerKestrelOption } from '@michaelthielemann/kestrel-core'
|
|
3
|
-
|
|
4
|
-
// Silence known-harmless, un-actionable THIRD-PARTY build noise (output unaffected — applied to both the
|
|
5
|
-
// Vite client and the Nitro server Rollup passes). Everything else still surfaces — crucially, any
|
|
6
|
-
// circular dependency that touches OUR `layers/` code is NOT dropped, so a real cycle we introduce stays
|
|
7
|
-
// visible. Anything in our own code is fixed, not silenced.
|
|
8
|
-
// - `#__PURE__`: @vueuse/core (pulled in via reka-ui) ships a `/* #__PURE__ */` annotation in a position
|
|
9
|
-
// Rollup can't interpret; it just drops the comment.
|
|
10
|
-
// - `module-preload-polyfill`: Nuxt's own preload-polyfill transform emits no sourcemap (cosmetic).
|
|
11
|
-
// - Circular dependencies entirely inside dependencies (nitropack's runtime internals: app.mjs ↔
|
|
12
|
-
// cache.mjs / utils.mjs, etc.) — upstream code we don't author and can't fix.
|
|
13
|
-
const SILENCED_BUILD_WARNINGS = ['#__PURE__', 'module-preload-polyfill']
|
|
14
|
-
// Typed inline — Rollup's `WarningHandlerWithDefault` isn't importable here (rollup is a transitive, not a
|
|
15
|
-
// direct, dep). `{ message?: string }` reads the bit we filter on; `defaultHandler` is structurally the
|
|
16
|
-
// rollup default handler (cast to call it with our wider warning shape).
|
|
17
|
-
const onwarn = (warning: { message?: string }, defaultHandler: (warning: never) => void) => {
|
|
18
|
-
const msg = warning.message ?? ''
|
|
19
|
-
if (SILENCED_BUILD_WARNINGS.some((s) => msg.includes(s))) return
|
|
20
|
-
// Third-party-internal cycles only (no `layers/` segment ⇒ none of our code is in the loop).
|
|
21
|
-
if (msg.includes('Circular dependency') && !msg.includes('layers/')) return
|
|
22
|
-
;(defaultHandler as (w: unknown) => void)(warning)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export default defineNuxtConfig({
|
|
26
|
-
compatibilityDate: '2026-06-02',
|
|
27
|
-
future: { compatibilityVersion: 4 },
|
|
28
|
-
// `noUncheckedIndexedAccess` is a Nuxt tsconfig DEFAULT the project never opted into; the engine's code
|
|
29
|
-
// (runtime collection-derived tables → `Record<string, Column>` lookups, pervasive index access) was not
|
|
30
|
-
// written for it, so leaving it on would mean ~100 `!`-assertions that mostly HIDE undefined rather than
|
|
31
|
-
// catch it. Off → the `typecheck` gate is meaningful (real type errors surface) without that noise; it
|
|
32
|
-
// also keeps a consumer's own `nuxt typecheck` from drowning in errors against the shipped engine code.
|
|
33
|
-
// It must be set in BOTH places: `typescript.tsConfig` covers the app/shared/node tsconfigs, and
|
|
34
|
-
// `nitro.typescript.tsConfig` covers the separately-generated server (Nitro) tsconfig.
|
|
35
|
-
// Exclude tests from the typecheck (vitest covers them at runtime). The generated config's `include` uses
|
|
36
|
-
// parent-traversing paths (`../layers/…`), so the exclude globs must too — a bare `**/*.test.ts` won't match.
|
|
37
|
-
typescript: { tsConfig: { compilerOptions: { noUncheckedIndexedAccess: false }, exclude: ['../**/*.test.ts', '../../**/*.test.ts'] } },
|
|
38
|
-
// Explicit sub-layer composition so Kestrel works as an installable meta-layer (`extends: ['@michaelthielemann/kestrel']`):
|
|
39
|
-
// Nuxt does NOT transitively auto-scan an extended package's `layers/` dir, but it DOES follow its
|
|
40
|
-
// `extends` chain — so for a consumer this list is the ONLY thing that drives sub-layer order (core first).
|
|
41
|
-
// In-repo it is effectively inert: Nuxt also auto-scans the local `layers/` dir (resolved before the
|
|
42
|
-
// explicit `extends` and deduped by rootDir), and that auto-scan order is reverse-alphabetical — i.e.
|
|
43
|
-
// in-repo precedence is ui > public > media > fields > core > collections > auth > admin > access, NOT this order.
|
|
44
|
-
// The two contexts therefore differ; keep cross-layer basenames unique so the winner can't diverge.
|
|
45
|
-
extends: [
|
|
46
|
-
'./layers/core',
|
|
47
|
-
'./layers/fields',
|
|
48
|
-
'./layers/ui',
|
|
49
|
-
'./layers/media',
|
|
50
|
-
'./layers/auth',
|
|
51
|
-
'./layers/access',
|
|
52
|
-
'./layers/collections',
|
|
53
|
-
'./layers/admin',
|
|
54
|
-
'./layers/public',
|
|
55
|
-
],
|
|
56
|
-
// Prune `node_modules` (and the sibling `playground` app) from Nuxt's dev file-watcher. Nuxt's default
|
|
57
|
-
// `ignore` list does NOT include node_modules; when `@parcel/watcher` is unavailable Nuxt falls back to
|
|
58
|
-
// the granular chokidar watcher, whose recursively-spawned sub-watchers only apply `isIgnored` (not the
|
|
59
|
-
// `/node_modules/` guard the top watcher uses). Under chokidar 5 that walks the pnpm symlink farm in
|
|
60
|
-
// `node_modules/.pnpm` — including the `playground/node_modules/kestrel -> ..` cycle — unbounded, which
|
|
61
|
-
// pegs the event loop and OOMs `nuxt dev` a few minutes after boot. Ignoring node_modules here prunes it
|
|
62
|
-
// in every watcher mode (and benefits downstream consumers of this meta-layer, who'd hit the same bug).
|
|
63
|
-
// `/playground` is ANCHORED (leading slash) to this repo's own dev playground at the root — an unanchored
|
|
64
|
-
// `playground` is a gitignore-style any-depth match that would also exclude a downstream consumer's
|
|
65
|
-
// directory named `playground` from scanning. (`**/node_modules` independently prunes the OOM-causing farm.)
|
|
66
|
-
ignore: ['**/node_modules', '/playground'],
|
|
67
|
-
// `experimental.tasks` enables the `db:migrate` Nitro task — the explicit prod schema step (ADR-0002).
|
|
68
|
-
nitro: { externals: { external: ['better-sqlite3', 'sharp'] }, experimental: { tasks: true }, rollupConfig: { onwarn }, typescript: { tsConfig: { compilerOptions: { noUncheckedIndexedAccess: false } } } },
|
|
69
|
-
// Inline each prerendered page's critical CSS instead of a render-blocking <link> (the global CSS is
|
|
70
|
-
// small, so per-page inlining is cheap and removes a round-trip before first paint). SPA admin is
|
|
71
|
-
// ssr:false so it is unaffected. Explicit so the behaviour can't drift with a future Nuxt default.
|
|
72
|
-
features: { inlineStyles: true },
|
|
73
|
-
// NOTE: public pages ship the normal hydration runtime (`_nuxt`) — consumer projects build their own
|
|
74
|
-
// interactive Vue components, so a zero-JS (`noScripts`) default would break them. The runtime publisher
|
|
75
|
-
// depends on this too: `localFetch` only emits the `_nuxt` scripts + inline payload when noScripts is off.
|
|
76
|
-
// Single non-auth config source; the `kestrel` module (core layer) resolves it (config → KESTREL_* env
|
|
77
|
-
// → default) into runtimeConfig.media / runtimeConfig.public.locales + the /uploads static serving.
|
|
78
|
-
// `layerKestrelOption` contributes this file's values ONLY in-repo — when Kestrel is consumed as a
|
|
79
|
-
// dependency it yields `{}`, so Nuxt's array-concatenating layer merge can't leak the demo `locales`
|
|
80
|
-
// into a consumer (the consumer's own `kestrel: {}` is then the sole source).
|
|
81
|
-
kestrel: layerKestrelOption(kestrel, import.meta.url),
|
|
82
|
-
// Pre-bundle these so the dev server doesn't discover them at runtime and force a full reload.
|
|
83
|
-
vite: {
|
|
84
|
-
build: { rollupOptions: { onwarn } },
|
|
85
|
-
optimizeDeps: {
|
|
86
|
-
// Nested "<pkg> > dep" form: Vite resolves each dep from THIS package's directory rather than from
|
|
87
|
-
// the consumer's project root. It has to be the published name — under pnpm these are kestrel's
|
|
88
|
-
// transitive deps and are not hoisted into a consumer's top-level node_modules, so resolving from
|
|
89
|
-
// the root finds nothing. An unresolvable prefix is swallowed (`resolvePackageData(…)?.dir ||
|
|
90
|
-
// basedir`), and the entry then degrades to a bare lookup from the root: fine in-repo and for a
|
|
91
|
-
// hoisted npm/yarn consumer, silently unbundled for a pnpm one — which is why a stale prefix shows
|
|
92
|
-
// up only in a consumer's terminal. In-repo the name is likewise unresolvable and that same
|
|
93
|
-
// fallback lands on the repo root, where the deps ARE hoisted. The runtime `import('@tiptap/...')`
|
|
94
|
-
// still reuses the pre-bundle (tryOptimizedResolve matches the "> dep" id + src dir).
|
|
95
|
-
// `test/package.test.ts` pins the prefix to package.json's `name`.
|
|
96
|
-
include: [
|
|
97
|
-
'@michaelthielemann/kestrel > @internationalized/date',
|
|
98
|
-
'@michaelthielemann/kestrel > @tiptap/extension-highlight',
|
|
99
|
-
'@michaelthielemann/kestrel > @tiptap/extension-subscript',
|
|
100
|
-
'@michaelthielemann/kestrel > @tiptap/extension-superscript',
|
|
101
|
-
'@michaelthielemann/kestrel > @tiptap/extension-text-align',
|
|
102
|
-
'@michaelthielemann/kestrel > @tiptap/starter-kit',
|
|
103
|
-
'@michaelthielemann/kestrel > @tiptap/vue-3',
|
|
104
|
-
'@michaelthielemann/kestrel > reka-ui',
|
|
105
|
-
],
|
|
106
|
-
},
|
|
107
|
-
},
|
|
108
|
-
})
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
// The ONLY real package-consumption test. Every earlier gate (api:check, pkg:lint) inspects one
|
|
2
|
-
// package's own tarball in isolation; this script is the one that scaffolds an actual consumer from
|
|
3
|
-
// templates/starter (via create-kestrel's real CLI), packs the engine + every @michaelthielemann/kestrel-* package the way
|
|
4
|
-
// `npm publish` would, installs them into that consumer with a real `npm install` (no workspace protocol,
|
|
5
|
-
// no symlinks back into this repo), builds it, boots the production server, and hits it over real HTTP.
|
|
6
|
-
//
|
|
7
|
-
// Local: `pnpm ci:consumer-template`. CI: the `consumer-template` job in .github/workflows/ci.yml.
|
|
8
|
-
import { execFileSync, spawn } from 'node:child_process'
|
|
9
|
-
import { mkdtempSync, mkdirSync, readFileSync, writeFileSync, rmSync } from 'node:fs'
|
|
10
|
-
import { tmpdir } from 'node:os'
|
|
11
|
-
import { join } from 'node:path'
|
|
12
|
-
import { setTimeout as sleep } from 'node:timers/promises'
|
|
13
|
-
|
|
14
|
-
const root = process.cwd()
|
|
15
|
-
const log = (msg) => console.log(`[consumer-template-ci] ${msg}`)
|
|
16
|
-
const run = (cmd, args, opts = {}) => execFileSync(cmd, args, { stdio: 'inherit', ...opts })
|
|
17
|
-
|
|
18
|
-
// @michaelthielemann/kestrel-* in the order `pnpm --filter "@michaelthielemann/kestrel-*" -r exec` resolves them (a real topological walk of
|
|
19
|
-
// the workspace graph, not a hand guess) — mirrors the publish order in .github/workflows/release.yml.
|
|
20
|
-
const PACKAGES = ['contracts', 'core', 'auth', 'access', 'fields', 'collections', 'media', 'publishing', 'delivery-live', 'delivery-static']
|
|
21
|
-
|
|
22
|
-
const work = mkdtempSync(join(tmpdir(), 'kestrel-consumer-ci-'))
|
|
23
|
-
const tarballDir = join(work, 'tarballs')
|
|
24
|
-
const consumerDir = join(work, 'site')
|
|
25
|
-
mkdirSync(tarballDir, { recursive: true })
|
|
26
|
-
log(`work dir: ${work}`)
|
|
27
|
-
|
|
28
|
-
let server
|
|
29
|
-
try {
|
|
30
|
-
// `pnpm --filter "@michaelthielemann/kestrel-*" -r build` does NOT reliably respect the dependency
|
|
31
|
-
// graph (observed: kestrel-access started before kestrel-core/kestrel-auth finished) — `build:packages`
|
|
32
|
-
// builds in explicit topological order instead.
|
|
33
|
-
log('building @michaelthielemann/kestrel-* packages (dist/ must exist before pnpm pack)')
|
|
34
|
-
run('pnpm', ['build:packages'], { cwd: root })
|
|
35
|
-
|
|
36
|
-
log('packing the engine + every @michaelthielemann/kestrel-* package as npm publish would')
|
|
37
|
-
const tarball = {}
|
|
38
|
-
for (const dir of ['.', ...PACKAGES.map((n) => `packages/kestrel-${n}`)]) {
|
|
39
|
-
const out = execFileSync('pnpm', ['pack', '--pack-destination', tarballDir], { cwd: join(root, dir) }).toString().trim()
|
|
40
|
-
const file = out.split('\n').pop().trim()
|
|
41
|
-
const name = JSON.parse(readFileSync(join(root, dir, 'package.json'), 'utf8')).name
|
|
42
|
-
tarball[name] = file
|
|
43
|
-
log(` ${name} -> ${file}`)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
log('scaffolding a consumer from templates/starter via the real create-kestrel CLI')
|
|
47
|
-
const password = 'consumer-ci-password'
|
|
48
|
-
run('node', [join(root, 'packages/create-kestrel/index.mjs'), consumerDir, '--yes', '--password', password], { cwd: work })
|
|
49
|
-
|
|
50
|
-
log('pointing the scaffolded package.json at the packed tarballs (no registry, no workspace protocol)')
|
|
51
|
-
const pkgPath = join(consumerDir, 'package.json')
|
|
52
|
-
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
|
|
53
|
-
pkg.dependencies['@michaelthielemann/kestrel'] = `file:${tarball['@michaelthielemann/kestrel']}`
|
|
54
|
-
// npm's `overrides` redirects a dependency AT ANY DEPTH — the engine tarball's own manifest declares
|
|
55
|
-
// "@michaelthielemann/kestrel-core": "0.1.0" etc. (pnpm pack already rewrote the workspace:* protocol to that real
|
|
56
|
-
// version), which would otherwise 404 against the real registry since these packages are not published
|
|
57
|
-
// yet in this checkout's tag. This is what actually forces npm's resolver to read each package's real
|
|
58
|
-
// `exports` map / `main` / `types` from a standalone tarball, the same way a real end user's install
|
|
59
|
-
// would — not a symlink into this repo's own packages/*/src.
|
|
60
|
-
pkg.overrides = Object.fromEntries(PACKAGES.map((n) => [`@michaelthielemann/kestrel-${n}`, `file:${tarball[`@michaelthielemann/kestrel-${n}`]}`]))
|
|
61
|
-
writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`)
|
|
62
|
-
|
|
63
|
-
// The runner's ambient npm (10.9.8 as of this writing) hits a real npm/arborist bug resolving nuxt's
|
|
64
|
-
// large optional-peer set (`TypeError: Cannot read properties of null (reading 'edgesOut')`, inside
|
|
65
|
-
// `#loadPeerSet`) — reproduced directly, unrelated to anything this script does. npm >= 11 does not
|
|
66
|
-
// have it. `npx --yes npm@11` sidesteps needing a global npm upgrade (which needs root the runner may
|
|
67
|
-
// not have) while still using real npm, not pnpm.
|
|
68
|
-
log('npm install (real npm 11 via npx — the most representative simulation of an actual end user)')
|
|
69
|
-
run('npx', ['--yes', 'npm@11', 'install'], { cwd: consumerDir })
|
|
70
|
-
|
|
71
|
-
log('verifying no @michaelthielemann/kestrel-* dependency resolved back into this repo (would prove nothing)')
|
|
72
|
-
for (const n of PACKAGES) {
|
|
73
|
-
let resolved = ''
|
|
74
|
-
try {
|
|
75
|
-
resolved = execFileSync('npm', ['ls', `@michaelthielemann/kestrel-${n}`, '--json'], { cwd: consumerDir }).toString()
|
|
76
|
-
} catch (err) {
|
|
77
|
-
// `npm ls` exits non-zero on a peer-dep mismatch warning even when the tree itself resolved fine;
|
|
78
|
-
// its stdout (on the error object) still carries the real tree, which is what matters here.
|
|
79
|
-
resolved = err.stdout?.toString() ?? ''
|
|
80
|
-
}
|
|
81
|
-
if (resolved.includes(root)) throw new Error(`@michaelthielemann/kestrel-${n} resolved into ${root} — the override did not take effect`)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
log('reading the .env create-kestrel wrote (session secret + admin password hash)')
|
|
85
|
-
const env = Object.fromEntries(
|
|
86
|
-
readFileSync(join(consumerDir, '.env'), 'utf8')
|
|
87
|
-
.split('\n')
|
|
88
|
-
.map((l) => /^([A-Z][A-Z0-9_]*)=(.*)$/.exec(l))
|
|
89
|
-
.filter(Boolean)
|
|
90
|
-
.map((m) => [m[1], m[2]]),
|
|
91
|
-
)
|
|
92
|
-
// KESTREL_SECURE_COOKIES=false is what create-kestrel writes for a brand-new site (local http dev) —
|
|
93
|
-
// the app itself refuses it under a production boot ("KESTREL_SECURE_COOKIES=false is not allowed in
|
|
94
|
-
// production"), which is the correct, secure-by-default behavior, not a bug this script should route
|
|
95
|
-
// around by keeping the flag. Unset it; the production default is secure.
|
|
96
|
-
delete env.KESTREL_SECURE_COOKIES
|
|
97
|
-
|
|
98
|
-
const devPort = 41822
|
|
99
|
-
log('nuxt dev (brief boot, then kill) — schema auto-syncs additively at dev boot; a production boot never touches it (by design), so this is what a real `pnpm dev` first run also does')
|
|
100
|
-
const dev = spawn('npx', ['nuxt', 'dev', '--port', String(devPort)], {
|
|
101
|
-
cwd: consumerDir,
|
|
102
|
-
env: { ...process.env, ...env },
|
|
103
|
-
stdio: 'inherit',
|
|
104
|
-
})
|
|
105
|
-
try {
|
|
106
|
-
await waitForServer(`http://localhost:${devPort}/robots.txt`, 60000)
|
|
107
|
-
} finally {
|
|
108
|
-
dev.kill()
|
|
109
|
-
await sleep(1000) // let the dev Nitro process release the sqlite file before the production build reopens it
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
log('nuxt build (production) — the real exports-map/deps resolution surface, not a dev-mode shortcut')
|
|
113
|
-
run('npx', ['nuxt', 'build'], { cwd: consumerDir, env: { ...process.env, ...env } })
|
|
114
|
-
|
|
115
|
-
log('booting the built server and hitting it over real HTTP')
|
|
116
|
-
const port = 41823
|
|
117
|
-
server = spawn('node', [join(consumerDir, '.output/server/index.mjs')], {
|
|
118
|
-
cwd: consumerDir,
|
|
119
|
-
env: { ...process.env, ...env, PORT: String(port), NITRO_PORT: String(port) },
|
|
120
|
-
stdio: 'inherit',
|
|
121
|
-
})
|
|
122
|
-
await waitForServer(`http://localhost:${port}/robots.txt`, 30000)
|
|
123
|
-
|
|
124
|
-
const checks = [
|
|
125
|
-
['GET /robots.txt', `http://localhost:${port}/robots.txt`, 200],
|
|
126
|
-
['GET /sitemap.xml', `http://localhost:${port}/sitemap.xml`, 200],
|
|
127
|
-
['GET /admin (login page, SPA shell)', `http://localhost:${port}/admin`, 200],
|
|
128
|
-
]
|
|
129
|
-
for (const [label, url, wantStatus] of checks) {
|
|
130
|
-
const res = await fetch(url)
|
|
131
|
-
if (res.status !== wantStatus) throw new Error(`${label} -> ${res.status}, expected ${wantStatus}`)
|
|
132
|
-
log(` ${label} -> ${res.status} OK`)
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const loginRes = await fetch(`http://localhost:${port}/api/login`, {
|
|
136
|
-
method: 'POST',
|
|
137
|
-
headers: { 'content-type': 'application/json' },
|
|
138
|
-
body: JSON.stringify({ password }),
|
|
139
|
-
})
|
|
140
|
-
if (loginRes.status !== 200) throw new Error(`POST /api/login -> ${loginRes.status}, expected 200 (the packed admin auth stack did not resolve)`)
|
|
141
|
-
log(' POST /api/login -> 200 OK (the packed @michaelthielemann/kestrel-auth + @michaelthielemann/kestrel-access stack authenticates for real)')
|
|
142
|
-
|
|
143
|
-
log('PASS — a real npm install of the packed engine + packages boots and serves under production, and login round-trips through the packed auth stack.')
|
|
144
|
-
} finally {
|
|
145
|
-
server?.kill()
|
|
146
|
-
if (!process.env.KEEP_CONSUMER_TEMPLATE_CI_WORKDIR) rmSync(work, { recursive: true, force: true })
|
|
147
|
-
else log(`KEEP_CONSUMER_TEMPLATE_CI_WORKDIR set — leaving ${work} in place`)
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
async function waitForServer(url, timeoutMs) {
|
|
151
|
-
const deadline = Date.now() + timeoutMs
|
|
152
|
-
for (;;) {
|
|
153
|
-
try {
|
|
154
|
-
const res = await fetch(url)
|
|
155
|
-
if (res.status) return
|
|
156
|
-
} catch { /* not up yet */ }
|
|
157
|
-
if (Date.now() > deadline) throw new Error(`server did not come up at ${url} within ${timeoutMs}ms`)
|
|
158
|
-
await sleep(250)
|
|
159
|
-
}
|
|
160
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
2
|
-
import { dirname, join, resolve } from 'node:path'
|
|
3
|
-
import { fileURLToPath } from 'node:url'
|
|
4
|
-
|
|
5
|
-
// create-kestrel ships copies of the engine's templates + scaffold lib. Generating them at pack time
|
|
6
|
-
// keeps one source of truth; committing them would let the two drift. See ADR-0005.
|
|
7
|
-
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
8
|
-
const PKG = join(ROOT, 'packages/create-kestrel')
|
|
9
|
-
const LIB_FILES = ['scaffold.mjs', 'password.mjs', 'cli.mjs']
|
|
10
|
-
const GENERATED = ['templates', 'lib', 'LICENSE', 'NOTICE']
|
|
11
|
-
|
|
12
|
-
const manifestPath = join(PKG, 'package.json')
|
|
13
|
-
|
|
14
|
-
const clean = () => {
|
|
15
|
-
for (const entry of GENERATED) rmSync(join(PKG, entry), { recursive: true, force: true })
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
if (process.argv.includes('--clean')) {
|
|
19
|
-
clean()
|
|
20
|
-
} else {
|
|
21
|
-
clean()
|
|
22
|
-
const engine = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8'))
|
|
23
|
-
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'))
|
|
24
|
-
|
|
25
|
-
// The version cannot be corrected here: npm derives the tarball name from the manifest as it stood
|
|
26
|
-
// BEFORE prepack, so a rewrite yields a tarball whose name and contents disagree. Refuse instead —
|
|
27
|
-
// test/create-kestrel.test.ts keeps the two manifests in lockstep so this never fires in practice.
|
|
28
|
-
if (manifest.version !== engine.version) {
|
|
29
|
-
console.error(`create-kestrel is at ${manifest.version} but the engine is at ${engine.version} — bump both.`)
|
|
30
|
-
process.exit(1)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
cpSync(join(ROOT, 'templates'), join(PKG, 'templates'), { recursive: true })
|
|
34
|
-
mkdirSync(join(PKG, 'lib'), { recursive: true })
|
|
35
|
-
for (const file of LIB_FILES) cpSync(join(ROOT, 'scripts/lib', file), join(PKG, 'lib', file))
|
|
36
|
-
for (const file of ['LICENSE', 'NOTICE']) {
|
|
37
|
-
if (existsSync(join(ROOT, file))) cpSync(join(ROOT, file), join(PKG, file))
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// A generated file, not a key in the committed manifest: `postpack` is not guaranteed to run (a failed
|
|
41
|
-
// publish skips it), and a stamp left behind in package.json would be committed and then silently pin
|
|
42
|
-
// stale ranges. Deleting `lib/` removes this with everything else.
|
|
43
|
-
const stamp = {
|
|
44
|
-
nuxt: engine.dependencies?.nuxt,
|
|
45
|
-
typescript: engine.dependencies?.typescript,
|
|
46
|
-
'vue-tsc': engine.devDependencies?.['vue-tsc'],
|
|
47
|
-
}
|
|
48
|
-
writeFileSync(join(PKG, 'lib', 'engine-meta.mjs'), `export default ${JSON.stringify(stamp, null, 2)}\n`)
|
|
49
|
-
}
|