@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.
Files changed (340) hide show
  1. package/README.md +26 -201
  2. package/dist/boot.d.ts +45 -0
  3. package/dist/boot.js +274 -0
  4. package/dist/cast.d.ts +3 -0
  5. package/dist/cast.js +6 -0
  6. package/dist/catalogue.d.ts +11 -0
  7. package/dist/catalogue.js +1 -0
  8. package/dist/cli.d.ts +2 -0
  9. package/dist/cli.js +55 -0
  10. package/dist/context.d.ts +62 -0
  11. package/dist/context.js +61 -0
  12. package/dist/dataflow.d.ts +3 -0
  13. package/dist/dataflow.js +65 -0
  14. package/dist/defineConfig.d.ts +157 -0
  15. package/dist/defineConfig.js +41 -0
  16. package/dist/defineContract.d.ts +12 -0
  17. package/dist/defineContract.js +16 -0
  18. package/dist/defineModule.d.ts +75 -0
  19. package/dist/defineModule.js +9 -0
  20. package/dist/definePipeline.d.ts +7 -0
  21. package/dist/definePipeline.js +11 -0
  22. package/dist/errors.d.ts +26 -0
  23. package/dist/errors.js +48 -0
  24. package/dist/index.d.ts +19 -0
  25. package/dist/index.js +14 -0
  26. package/dist/load.d.ts +7 -0
  27. package/dist/load.js +79 -0
  28. package/dist/logger.d.ts +18 -0
  29. package/dist/logger.js +36 -0
  30. package/dist/registry.d.ts +14 -0
  31. package/dist/registry.js +87 -0
  32. package/dist/result.d.ts +19 -0
  33. package/dist/result.js +19 -0
  34. package/dist/runner.d.ts +29 -0
  35. package/dist/runner.js +133 -0
  36. package/dist/schema.d.ts +7 -0
  37. package/dist/schema.js +142 -0
  38. package/dist/sort.d.ts +2 -0
  39. package/dist/sort.js +50 -0
  40. package/dist/testing/runPipeline.d.ts +9 -0
  41. package/dist/testing/runPipeline.js +22 -0
  42. package/dist/triggers/cron.d.ts +10 -0
  43. package/dist/triggers/cron.js +44 -0
  44. package/dist/triggers/cronMatch.d.ts +10 -0
  45. package/dist/triggers/cronMatch.js +68 -0
  46. package/dist/triggers/http.d.ts +58 -0
  47. package/dist/triggers/http.js +292 -0
  48. package/package.json +25 -151
  49. package/NOTICE +0 -12
  50. package/kestrel.config.ts +0 -28
  51. package/layers/access/nuxt.config.ts +0 -5
  52. package/layers/access/server/middleware/00.ip-allowlist.ts +0 -52
  53. package/layers/access/server/middleware/access-guard.ts +0 -32
  54. package/layers/access/server/types.d.ts +0 -7
  55. package/layers/admin/app/components/AdminAccount.vue +0 -212
  56. package/layers/admin/app/components/AdminNav.vue +0 -44
  57. package/layers/admin/app/components/BlockFields.vue +0 -67
  58. package/layers/admin/app/components/BlockPreview.vue +0 -413
  59. package/layers/admin/app/components/BlockTree.vue +0 -433
  60. package/layers/admin/app/components/BlocksBody.vue +0 -163
  61. package/layers/admin/app/components/CollectionDeleteDialog.vue +0 -38
  62. package/layers/admin/app/components/CollectionEditor.vue +0 -324
  63. package/layers/admin/app/components/CollectionList.vue +0 -429
  64. package/layers/admin/app/components/CollectionListBulkBar.vue +0 -62
  65. package/layers/admin/app/components/CollectionListColumnsPanel.vue +0 -37
  66. package/layers/admin/app/components/CollectionListFilterPanel.vue +0 -145
  67. package/layers/admin/app/components/CollectionListPager.vue +0 -70
  68. package/layers/admin/app/components/CollectionListTable.vue +0 -347
  69. package/layers/admin/app/components/EditorStatus.vue +0 -211
  70. package/layers/admin/app/components/EditorUnsupported.vue +0 -28
  71. package/layers/admin/app/components/FieldsBody.vue +0 -23
  72. package/layers/admin/app/components/LocaleBar.vue +0 -107
  73. package/layers/admin/app/components/PageFields.vue +0 -129
  74. package/layers/admin/app/components/PageFieldsPane.vue +0 -39
  75. package/layers/admin/app/components/SeoFields.vue +0 -210
  76. package/layers/admin/app/components/SingletonEditor.vue +0 -93
  77. package/layers/admin/app/composables/useAuth.ts +0 -68
  78. package/layers/admin/app/composables/useBlockTree.ts +0 -95
  79. package/layers/admin/app/composables/useBlocks.ts +0 -7
  80. package/layers/admin/app/composables/useCachedResource.ts +0 -35
  81. package/layers/admin/app/composables/useCollectionOps.ts +0 -102
  82. package/layers/admin/app/composables/useCollections.ts +0 -6
  83. package/layers/admin/app/composables/useContentLocales.ts +0 -23
  84. package/layers/admin/app/composables/useEditForm.ts +0 -402
  85. package/layers/admin/app/composables/useLinkResolver.ts +0 -40
  86. package/layers/admin/app/composables/useListBatchActions.ts +0 -78
  87. package/layers/admin/app/composables/useListColumns.ts +0 -53
  88. package/layers/admin/app/composables/useListFilterDraft.ts +0 -129
  89. package/layers/admin/app/composables/useListRows.ts +0 -75
  90. package/layers/admin/app/composables/useListSelection.ts +0 -28
  91. package/layers/admin/app/composables/useListUrlState.ts +0 -53
  92. package/layers/admin/app/composables/usePendingPublishPoll.ts +0 -15
  93. package/layers/admin/app/composables/usePreviewViewport.ts +0 -30
  94. package/layers/admin/app/composables/usePublishStatus.ts +0 -107
  95. package/layers/admin/app/composables/useRailCollapsed.ts +0 -14
  96. package/layers/admin/app/composables/useTheme.ts +0 -36
  97. package/layers/admin/app/composables/useToolbarPanel.ts +0 -38
  98. package/layers/admin/app/composables/useUnsavedGuard.ts +0 -22
  99. package/layers/admin/app/layouts/admin.vue +0 -266
  100. package/layers/admin/app/middleware/admin-auth.ts +0 -4
  101. package/layers/admin/app/pages/admin/[collection]/[id].vue +0 -203
  102. package/layers/admin/app/pages/admin/[collection]/index.vue +0 -62
  103. package/layers/admin/app/pages/admin/index.vue +0 -118
  104. package/layers/admin/app/pages/admin/login.vue +0 -68
  105. package/layers/admin/app/pages/admin/media.vue +0 -28
  106. package/layers/admin/app/pages/admin/references.vue +0 -146
  107. package/layers/admin/app/plugins/admin-style-guard.client.ts +0 -76
  108. package/layers/admin/app/plugins/reauth.client.ts +0 -20
  109. package/layers/admin/app/utils/admin-style-guard.ts +0 -20
  110. package/layers/admin/app/utils/block-tree.ts +0 -214
  111. package/layers/admin/app/utils/collection-ops.ts +0 -69
  112. package/layers/admin/app/utils/dead-refs.ts +0 -30
  113. package/layers/admin/app/utils/edit-form.ts +0 -267
  114. package/layers/admin/app/utils/editor-expose.ts +0 -36
  115. package/layers/admin/app/utils/editor-form-context.ts +0 -82
  116. package/layers/admin/app/utils/editor-registry.ts +0 -20
  117. package/layers/admin/app/utils/field-keys.ts +0 -11
  118. package/layers/admin/app/utils/list-cell.ts +0 -46
  119. package/layers/admin/app/utils/list-columns.ts +0 -142
  120. package/layers/admin/app/utils/list-query.ts +0 -96
  121. package/layers/admin/app/utils/nav-active.ts +0 -9
  122. package/layers/admin/app/utils/populate-blocks.ts +0 -288
  123. package/layers/admin/app/utils/preview-channel.ts +0 -62
  124. package/layers/admin/app/utils/preview-viewport.ts +0 -80
  125. package/layers/admin/app/utils/reauth.ts +0 -67
  126. package/layers/admin/app/utils/record-title.ts +0 -15
  127. package/layers/admin/app/utils/register-builtin-editors.ts +0 -9
  128. package/layers/admin/app/utils/safe-redirect.ts +0 -12
  129. package/layers/admin/nuxt.config.ts +0 -20
  130. package/layers/auth/nuxt.config.ts +0 -1
  131. package/layers/auth/server/plugins/01.register-auth-pipelines.ts +0 -7
  132. package/layers/collections/nuxt.config.ts +0 -1
  133. package/layers/collections/server/plugins/01.register.ts +0 -29
  134. package/layers/collections/server/plugins/02.register-relation-populate.ts +0 -18
  135. package/layers/core/app/composables/layouts.ts +0 -5
  136. package/layers/core/app/utils/field-factories.ts +0 -129
  137. package/layers/core/app/utils/layouts.ts +0 -38
  138. package/layers/core/modules/auto-discovery/extract-block.ts +0 -179
  139. package/layers/core/modules/auto-discovery/index.ts +0 -112
  140. package/layers/core/modules/auto-discovery/package-registry.ts +0 -19
  141. package/layers/core/modules/auto-discovery/scan.ts +0 -170
  142. package/layers/core/modules/auto-discovery/virtual.d.ts +0 -20
  143. package/layers/core/modules/component-namespace/index.ts +0 -44
  144. package/layers/core/modules/component-namespace/shared.ts +0 -24
  145. package/layers/core/modules/kestrel/app-shell.ts +0 -55
  146. package/layers/core/modules/kestrel/auto-imports.ts +0 -44
  147. package/layers/core/modules/kestrel/index.ts +0 -144
  148. package/layers/core/modules/plugin-order/index.ts +0 -69
  149. package/layers/core/modules/plugin-order/plugin-order.ts +0 -273
  150. package/layers/core/nuxt.config.ts +0 -10
  151. package/layers/core/server/api/[...path].ts +0 -98
  152. package/layers/core/server/plugins/00.config.ts +0 -12
  153. package/layers/core/server/plugins/00.migrate.ts +0 -12
  154. package/layers/core/server/plugins/01.register-introspection-pipeline.ts +0 -5
  155. package/layers/core/server/plugins/01.register-openapi-pipeline.ts +0 -5
  156. package/layers/core/server/plugins/01.register-tooling-pipelines.ts +0 -5
  157. package/layers/core/server/plugins/02.schema-sync.ts +0 -58
  158. package/layers/core/server/plugins/04.outbox-worker.ts +0 -31
  159. package/layers/core/server/plugins/05.reindex-refs.ts +0 -5
  160. package/layers/core/server/routes/__kestrel/dashboard.get.ts +0 -29
  161. package/layers/core/server/tasks/db/migrate-module.ts +0 -83
  162. package/layers/core/server/tasks/db/migrate-revisions.ts +0 -20
  163. package/layers/core/server/tasks/db/migrate.ts +0 -50
  164. package/layers/core/server/utils/server-config.ts +0 -81
  165. package/layers/fields/nuxt.config.ts +0 -1
  166. package/layers/fields/server/plugins/01.register-blocks-pipeline.ts +0 -7
  167. package/layers/fields/server/plugins/01.register-field-populate.ts +0 -11
  168. package/layers/media/app/components/KestrelImg.vue +0 -94
  169. package/layers/media/app/components/MediaConflictDialog.vue +0 -35
  170. package/layers/media/app/components/MediaDeleteDialog.vue +0 -52
  171. package/layers/media/app/components/MediaGrid.vue +0 -113
  172. package/layers/media/app/components/MediaImage.vue +0 -70
  173. package/layers/media/app/components/MediaLibrary.vue +0 -456
  174. package/layers/media/app/components/MediaNewFolderDialog.vue +0 -32
  175. package/layers/media/app/components/MediaPathBar.vue +0 -163
  176. package/layers/media/app/components/MediaPicker.vue +0 -19
  177. package/layers/media/app/components/MediaRenameDialog.vue +0 -32
  178. package/layers/media/app/components/MediaTable.vue +0 -75
  179. package/layers/media/app/components/MediaThumb.vue +0 -25
  180. package/layers/media/app/components/MediaToolbar.vue +0 -90
  181. package/layers/media/app/components/MediaUploadDialog.vue +0 -87
  182. package/layers/media/app/components/MediaViewer.vue +0 -137
  183. package/layers/media/app/components/field/Media.vue +0 -263
  184. package/layers/media/app/composables/useBlurUp.ts +0 -55
  185. package/layers/media/app/composables/useMediaClipboard.ts +0 -14
  186. package/layers/media/app/composables/useMediaContextMenu.ts +0 -56
  187. package/layers/media/app/composables/useMediaDnd.ts +0 -56
  188. package/layers/media/app/composables/useMediaLibrary.ts +0 -152
  189. package/layers/media/app/composables/useMediaOps.ts +0 -50
  190. package/layers/media/app/composables/useMediaRelocate.ts +0 -39
  191. package/layers/media/app/composables/useMediaResolver.ts +0 -50
  192. package/layers/media/app/composables/useMediaUpload.ts +0 -93
  193. package/layers/media/app/composables/useMediaVariant.ts +0 -34
  194. package/layers/media/app/plugins/field-media.client.ts +0 -6
  195. package/layers/media/app/utils/ai-disclosure.ts +0 -19
  196. package/layers/media/app/utils/dnd.ts +0 -69
  197. package/layers/media/app/utils/library.ts +0 -81
  198. package/layers/media/app/utils/ops.ts +0 -60
  199. package/layers/media/app/utils/picture-model.ts +0 -125
  200. package/layers/media/app/utils/thumbhash.ts +0 -19
  201. package/layers/media/app/utils/variant-name.ts +0 -20
  202. package/layers/media/nuxt.config.ts +0 -7
  203. package/layers/media/server/middleware/ondemand-variants.ts +0 -38
  204. package/layers/media/server/plugins/01.register-media-pipelines.ts +0 -7
  205. package/layers/media/server/plugins/02.register-media.ts +0 -22
  206. package/layers/media/server/plugins/04.variant-capture.ts +0 -27
  207. package/layers/media/server/plugins/05.media-cleanup.ts +0 -5
  208. package/layers/media/server/tasks/media/backfill.ts +0 -17
  209. package/layers/public/app/app.vue +0 -5
  210. package/layers/public/app/components/BlockRenderer.vue +0 -56
  211. package/layers/public/app/components/KestrelLink.vue +0 -26
  212. package/layers/public/app/components/KestrelPreviewBridge.vue +0 -133
  213. package/layers/public/app/composables/public-page.ts +0 -24
  214. package/layers/public/app/error.vue +0 -12
  215. package/layers/public/app/layouts/default.vue +0 -5
  216. package/layers/public/app/pages/[...slug].vue +0 -239
  217. package/layers/public/app/pages/__kestrel/preview.vue +0 -46
  218. package/layers/public/app/utils/block-edit-context.ts +0 -16
  219. package/layers/public/app/utils/json-ld.ts +0 -139
  220. package/layers/public/app/utils/link-href.ts +0 -46
  221. package/layers/public/app/utils/page-head.ts +0 -84
  222. package/layers/public/app/utils/page-layout.ts +0 -18
  223. package/layers/public/app/utils/preview-protocol.ts +0 -117
  224. package/layers/public/app/utils/public-route.ts +0 -46
  225. package/layers/public/app/utils/site-head.ts +0 -43
  226. package/layers/public/modules/deploy-output/index.ts +0 -80
  227. package/layers/public/modules/prerender-routes/discover.ts +0 -37
  228. package/layers/public/modules/prerender-routes/index.ts +0 -88
  229. package/layers/public/modules/prerender-routes/routes.ts +0 -16
  230. package/layers/public/modules/prune-media/index.ts +0 -48
  231. package/layers/public/modules/prune-media/prune-media.ts +0 -81
  232. package/layers/public/nuxt.config.ts +0 -14
  233. package/layers/public/server/delivery-live/serve.ts +0 -111
  234. package/layers/public/server/middleware/delivery-live-catchall.ts +0 -7
  235. package/layers/public/server/pipelines/index.ts +0 -23
  236. package/layers/public/server/plugins/00.ensure-snapshot-triggers.ts +0 -14
  237. package/layers/public/server/plugins/01.register-public-pipelines.ts +0 -7
  238. package/layers/public/server/plugins/02.register-links.ts +0 -16
  239. package/layers/public/server/plugins/03.redirects.ts +0 -55
  240. package/layers/public/server/plugins/05.plan-publish.ts +0 -5
  241. package/layers/public/server/plugins/zz.publish.ts +0 -127
  242. package/layers/public/server/routes/llms-full.txt.get.ts +0 -99
  243. package/layers/public/server/routes/llms.txt.get.ts +0 -66
  244. package/layers/public/server/routes/redirects.json.get.ts +0 -58
  245. package/layers/public/server/routes/robots.txt.get.ts +0 -11
  246. package/layers/public/server/routes/sitemap.xml.get.ts +0 -79
  247. package/layers/public/server/tasks/publish/run.ts +0 -32
  248. package/layers/public/server/utils/publish/render-live.ts +0 -26
  249. package/layers/ui/app/assets/scss/_base.scss +0 -16
  250. package/layers/ui/app/assets/scss/_datepicker.scss +0 -82
  251. package/layers/ui/app/assets/scss/_mixins.scss +0 -50
  252. package/layers/ui/app/assets/scss/_reset.scss +0 -65
  253. package/layers/ui/app/assets/scss/_tokens.scss +0 -183
  254. package/layers/ui/app/assets/scss/main.scss +0 -4
  255. package/layers/ui/app/components/field/Boolean.vue +0 -30
  256. package/layers/ui/app/components/field/Choice.vue +0 -73
  257. package/layers/ui/app/components/field/Datetime.vue +0 -98
  258. package/layers/ui/app/components/field/DeadRefNote.vue +0 -28
  259. package/layers/ui/app/components/field/Json.vue +0 -62
  260. package/layers/ui/app/components/field/Layout.vue +0 -138
  261. package/layers/ui/app/components/field/Link.vue +0 -112
  262. package/layers/ui/app/components/field/LinkInternalPicker.vue +0 -95
  263. package/layers/ui/app/components/field/Number.vue +0 -30
  264. package/layers/ui/app/components/field/Relation.vue +0 -49
  265. package/layers/ui/app/components/field/Renderer.vue +0 -30
  266. package/layers/ui/app/components/field/Repeater.vue +0 -460
  267. package/layers/ui/app/components/field/Richtext.vue +0 -26
  268. package/layers/ui/app/components/field/Slug.vue +0 -65
  269. package/layers/ui/app/components/field/Text.vue +0 -35
  270. package/layers/ui/app/components/field/Unsupported.vue +0 -34
  271. package/layers/ui/app/components/ui/Alert.vue +0 -50
  272. package/layers/ui/app/components/ui/Brand.vue +0 -22
  273. package/layers/ui/app/components/ui/Button.vue +0 -116
  274. package/layers/ui/app/components/ui/ButtonGroup.vue +0 -96
  275. package/layers/ui/app/components/ui/Checkbox.vue +0 -53
  276. package/layers/ui/app/components/ui/CheckboxGroup.vue +0 -51
  277. package/layers/ui/app/components/ui/Combobox.vue +0 -345
  278. package/layers/ui/app/components/ui/DatePicker.vue +0 -92
  279. package/layers/ui/app/components/ui/DateRangePicker.vue +0 -100
  280. package/layers/ui/app/components/ui/Dialog.vue +0 -107
  281. package/layers/ui/app/components/ui/EmptyState.vue +0 -46
  282. package/layers/ui/app/components/ui/Field.vue +0 -58
  283. package/layers/ui/app/components/ui/Fieldset.vue +0 -65
  284. package/layers/ui/app/components/ui/Icon.vue +0 -44
  285. package/layers/ui/app/components/ui/Menu.vue +0 -54
  286. package/layers/ui/app/components/ui/NumberInput.vue +0 -175
  287. package/layers/ui/app/components/ui/Richtext.vue +0 -141
  288. package/layers/ui/app/components/ui/RichtextToolbar.vue +0 -251
  289. package/layers/ui/app/components/ui/Select.vue +0 -52
  290. package/layers/ui/app/components/ui/TextInput.vue +0 -111
  291. package/layers/ui/app/components/ui/Textarea.vue +0 -39
  292. package/layers/ui/app/components/ui/TimeInput.vue +0 -18
  293. package/layers/ui/app/components/ui/Toasts.vue +0 -103
  294. package/layers/ui/app/components/ui/Tooltip.vue +0 -45
  295. package/layers/ui/app/composables/useAdminLang.ts +0 -8
  296. package/layers/ui/app/composables/useDragReorder.ts +0 -41
  297. package/layers/ui/app/composables/useEchoGuard.ts +0 -28
  298. package/layers/ui/app/composables/useLinkField.ts +0 -70
  299. package/layers/ui/app/composables/useRecordOptions.ts +0 -84
  300. package/layers/ui/app/composables/useRepeater.ts +0 -99
  301. package/layers/ui/app/composables/useT.ts +0 -32
  302. package/layers/ui/app/composables/useToast.ts +0 -46
  303. package/layers/ui/app/i18n/de.ts +0 -432
  304. package/layers/ui/app/i18n/en.ts +0 -434
  305. package/layers/ui/app/utils/boolean-field.ts +0 -10
  306. package/layers/ui/app/utils/field-component.ts +0 -22
  307. package/layers/ui/app/utils/field-empty.ts +0 -21
  308. package/layers/ui/app/utils/field-registry.ts +0 -36
  309. package/layers/ui/app/utils/humanize.ts +0 -14
  310. package/layers/ui/app/utils/icons.ts +0 -143
  311. package/layers/ui/app/utils/iso-date.ts +0 -18
  312. package/layers/ui/app/utils/localized.ts +0 -13
  313. package/layers/ui/app/utils/record-context.ts +0 -15
  314. package/layers/ui/app/utils/reorder.ts +0 -8
  315. package/layers/ui/app/utils/richtext-preserve-class.ts +0 -31
  316. package/layers/ui/app/utils/useFieldA11y.ts +0 -40
  317. package/layers/ui/nuxt.config.ts +0 -9
  318. package/nuxt.config.ts +0 -108
  319. package/scripts/consumer-template-ci.mjs +0 -160
  320. package/scripts/copy-create-payload.mjs +0 -49
  321. package/scripts/coverage-all.mjs +0 -163
  322. package/scripts/dashboard.mjs +0 -89
  323. package/scripts/hash-password.mjs +0 -29
  324. package/scripts/kestrel.mjs +0 -240
  325. package/scripts/lib/cli.mjs +0 -129
  326. package/scripts/lib/coverage-model.mjs +0 -51
  327. package/scripts/lib/password.mjs +0 -19
  328. package/scripts/lib/scaffold.mjs +0 -205
  329. package/scripts/port-graph.mjs +0 -350
  330. package/scripts/typecheck.mjs +0 -62
  331. package/templates/starter/README.md +0 -65
  332. package/templates/starter/_env.example +0 -17
  333. package/templates/starter/_gitignore +0 -26
  334. package/templates/starter/_package.json +0 -22
  335. package/templates/starter/app/app.vue +0 -7
  336. package/templates/starter/app/blocks/Prose.vue +0 -13
  337. package/templates/starter/app/layouts/default.vue +0 -6
  338. package/templates/starter/nuxt.config.ts +0 -23
  339. package/templates/starter/pnpm-workspace.yaml +0 -8
  340. 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
- }
@@ -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
- }