@caretcms/core 0.2.0 → 0.4.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 (256) hide show
  1. package/README.md +195 -12
  2. package/dist/index.d.ts +26 -2
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +113 -12
  5. package/dist/index.js.map +1 -1
  6. package/dist/loader.d.ts.map +1 -1
  7. package/dist/loader.js +35 -3
  8. package/dist/loader.js.map +1 -1
  9. package/dist/markdown/paragraphs.d.ts +14 -0
  10. package/dist/markdown/paragraphs.d.ts.map +1 -0
  11. package/dist/markdown/paragraphs.js +61 -0
  12. package/dist/markdown/paragraphs.js.map +1 -0
  13. package/dist/markdown/remark.d.ts.map +1 -1
  14. package/dist/markdown/remark.js +1 -0
  15. package/dist/markdown/remark.js.map +1 -1
  16. package/dist/markdown/satteri.d.ts.map +1 -1
  17. package/dist/markdown/satteri.js +35 -9
  18. package/dist/markdown/satteri.js.map +1 -1
  19. package/dist/markdown/stamp.d.ts +0 -9
  20. package/dist/markdown/stamp.d.ts.map +1 -1
  21. package/dist/markdown/stamp.js +6 -0
  22. package/dist/markdown/stamp.js.map +1 -1
  23. package/dist/providers/deployment/github.d.ts +27 -0
  24. package/dist/providers/deployment/github.d.ts.map +1 -0
  25. package/dist/providers/deployment/github.js +180 -0
  26. package/dist/providers/deployment/github.js.map +1 -0
  27. package/dist/providers/deployment/simulated.d.ts +10 -0
  28. package/dist/providers/deployment/simulated.d.ts.map +1 -0
  29. package/dist/providers/deployment/simulated.js +29 -0
  30. package/dist/providers/deployment/simulated.js.map +1 -0
  31. package/dist/runtime/auth/session.d.ts +1 -0
  32. package/dist/runtime/auth/session.d.ts.map +1 -1
  33. package/dist/runtime/auth/session.js +16 -0
  34. package/dist/runtime/auth/session.js.map +1 -1
  35. package/dist/runtime/authorization.d.ts +11 -0
  36. package/dist/runtime/authorization.d.ts.map +1 -0
  37. package/dist/runtime/authorization.js +29 -0
  38. package/dist/runtime/authorization.js.map +1 -0
  39. package/dist/runtime/collection-policy.d.ts +11 -0
  40. package/dist/runtime/collection-policy.d.ts.map +1 -0
  41. package/dist/runtime/collection-policy.js +44 -0
  42. package/dist/runtime/collection-policy.js.map +1 -0
  43. package/dist/runtime/config.d.ts +3 -0
  44. package/dist/runtime/config.d.ts.map +1 -1
  45. package/dist/runtime/config.js +4 -1
  46. package/dist/runtime/config.js.map +1 -1
  47. package/dist/runtime/content-errors.d.ts +9 -0
  48. package/dist/runtime/content-errors.d.ts.map +1 -0
  49. package/dist/runtime/content-errors.js +17 -0
  50. package/dist/runtime/content-errors.js.map +1 -0
  51. package/dist/runtime/content.d.ts.map +1 -1
  52. package/dist/runtime/content.js +13 -4
  53. package/dist/runtime/content.js.map +1 -1
  54. package/dist/runtime/deployment-status.d.ts +14 -0
  55. package/dist/runtime/deployment-status.d.ts.map +1 -0
  56. package/dist/runtime/deployment-status.js +57 -0
  57. package/dist/runtime/deployment-status.js.map +1 -0
  58. package/dist/runtime/draft-state.d.ts +16 -0
  59. package/dist/runtime/draft-state.d.ts.map +1 -0
  60. package/dist/runtime/draft-state.js +29 -0
  61. package/dist/runtime/draft-state.js.map +1 -0
  62. package/dist/runtime/i18n.d.ts +97 -0
  63. package/dist/runtime/i18n.d.ts.map +1 -0
  64. package/dist/runtime/i18n.js +190 -0
  65. package/dist/runtime/i18n.js.map +1 -0
  66. package/dist/runtime/index.d.ts +1 -0
  67. package/dist/runtime/index.d.ts.map +1 -1
  68. package/dist/runtime/index.js +1 -0
  69. package/dist/runtime/index.js.map +1 -1
  70. package/dist/runtime/middleware.d.ts.map +1 -1
  71. package/dist/runtime/middleware.js +61 -4
  72. package/dist/runtime/middleware.js.map +1 -1
  73. package/dist/runtime/mutations/contracts.d.ts +12 -0
  74. package/dist/runtime/mutations/contracts.d.ts.map +1 -1
  75. package/dist/runtime/mutations/contracts.js +54 -4
  76. package/dist/runtime/mutations/contracts.js.map +1 -1
  77. package/dist/runtime/mutations/engine.d.ts +2 -0
  78. package/dist/runtime/mutations/engine.d.ts.map +1 -1
  79. package/dist/runtime/mutations/engine.js +220 -57
  80. package/dist/runtime/mutations/engine.js.map +1 -1
  81. package/dist/runtime/providers.d.ts +4 -1
  82. package/dist/runtime/providers.d.ts.map +1 -1
  83. package/dist/runtime/providers.js +18 -1
  84. package/dist/runtime/providers.js.map +1 -1
  85. package/dist/runtime/publish-recovery.d.ts +32 -0
  86. package/dist/runtime/publish-recovery.d.ts.map +1 -0
  87. package/dist/runtime/publish-recovery.js +98 -0
  88. package/dist/runtime/publish-recovery.js.map +1 -0
  89. package/dist/runtime/publish.d.ts +10 -4
  90. package/dist/runtime/publish.d.ts.map +1 -1
  91. package/dist/runtime/publish.js +105 -63
  92. package/dist/runtime/publish.js.map +1 -1
  93. package/dist/runtime/rebuild-webhook.d.ts +4 -0
  94. package/dist/runtime/rebuild-webhook.d.ts.map +1 -1
  95. package/dist/runtime/rebuild-webhook.js +12 -2
  96. package/dist/runtime/rebuild-webhook.js.map +1 -1
  97. package/dist/runtime/request-context.d.ts +7 -1
  98. package/dist/runtime/request-context.d.ts.map +1 -1
  99. package/dist/runtime/request-context.js +7 -1
  100. package/dist/runtime/request-context.js.map +1 -1
  101. package/dist/runtime/rewrite.d.ts.map +1 -1
  102. package/dist/runtime/rewrite.js +28 -2
  103. package/dist/runtime/rewrite.js.map +1 -1
  104. package/dist/runtime/rich-allowlist.js +1 -1
  105. package/dist/runtime/rich-allowlist.js.map +1 -1
  106. package/dist/runtime/routes/admin-entry.d.ts.map +1 -1
  107. package/dist/runtime/routes/admin-entry.js +129 -21
  108. package/dist/runtime/routes/admin-entry.js.map +1 -1
  109. package/dist/runtime/routes/admin.d.ts.map +1 -1
  110. package/dist/runtime/routes/admin.js +28 -14
  111. package/dist/runtime/routes/admin.js.map +1 -1
  112. package/dist/runtime/routes/auth-login.d.ts.map +1 -1
  113. package/dist/runtime/routes/auth-login.js +4 -0
  114. package/dist/runtime/routes/auth-login.js.map +1 -1
  115. package/dist/runtime/routes/auth-logout.d.ts.map +1 -1
  116. package/dist/runtime/routes/auth-logout.js +7 -2
  117. package/dist/runtime/routes/auth-logout.js.map +1 -1
  118. package/dist/runtime/routes/auth-session.d.ts.map +1 -1
  119. package/dist/runtime/routes/auth-session.js +10 -2
  120. package/dist/runtime/routes/auth-session.js.map +1 -1
  121. package/dist/runtime/routes/collection-list.d.ts.map +1 -1
  122. package/dist/runtime/routes/collection-list.js +213 -66
  123. package/dist/runtime/routes/collection-list.js.map +1 -1
  124. package/dist/runtime/routes/deployment.d.ts +5 -0
  125. package/dist/runtime/routes/deployment.d.ts.map +1 -0
  126. package/dist/runtime/routes/deployment.js +38 -0
  127. package/dist/runtime/routes/deployment.js.map +1 -0
  128. package/dist/runtime/routes/draft.d.ts.map +1 -1
  129. package/dist/runtime/routes/draft.js +10 -3
  130. package/dist/runtime/routes/draft.js.map +1 -1
  131. package/dist/runtime/routes/entries.d.ts.map +1 -1
  132. package/dist/runtime/routes/entries.js +87 -43
  133. package/dist/runtime/routes/entries.js.map +1 -1
  134. package/dist/runtime/routes/history.d.ts.map +1 -1
  135. package/dist/runtime/routes/history.js +40 -21
  136. package/dist/runtime/routes/history.js.map +1 -1
  137. package/dist/runtime/routes/mutate.d.ts.map +1 -1
  138. package/dist/runtime/routes/mutate.js +4 -1
  139. package/dist/runtime/routes/mutate.js.map +1 -1
  140. package/dist/runtime/routes/publish.d.ts.map +1 -1
  141. package/dist/runtime/routes/publish.js +127 -33
  142. package/dist/runtime/routes/publish.js.map +1 -1
  143. package/dist/runtime/routes/schema.d.ts.map +1 -1
  144. package/dist/runtime/routes/schema.js +11 -2
  145. package/dist/runtime/routes/schema.js.map +1 -1
  146. package/dist/runtime/routes/studio-home.d.ts.map +1 -1
  147. package/dist/runtime/routes/studio-home.js +41 -20
  148. package/dist/runtime/routes/studio-home.js.map +1 -1
  149. package/dist/runtime/routes/upload.d.ts.map +1 -1
  150. package/dist/runtime/routes/upload.js +3 -0
  151. package/dist/runtime/routes/upload.js.map +1 -1
  152. package/dist/runtime/schema-registry.d.ts +8 -1
  153. package/dist/runtime/schema-registry.d.ts.map +1 -1
  154. package/dist/runtime/schema-registry.js +49 -3
  155. package/dist/runtime/schema-registry.js.map +1 -1
  156. package/dist/runtime/storage/entry-commit.d.ts +8 -0
  157. package/dist/runtime/storage/entry-commit.d.ts.map +1 -0
  158. package/dist/runtime/storage/entry-commit.js +45 -0
  159. package/dist/runtime/storage/entry-commit.js.map +1 -0
  160. package/dist/runtime/storage/filesystem-adapter.d.ts +5 -0
  161. package/dist/runtime/storage/filesystem-adapter.d.ts.map +1 -1
  162. package/dist/runtime/storage/filesystem-adapter.js +12 -3
  163. package/dist/runtime/storage/filesystem-adapter.js.map +1 -1
  164. package/dist/runtime/storage/frontmatter-codec.d.ts +5 -23
  165. package/dist/runtime/storage/frontmatter-codec.d.ts.map +1 -1
  166. package/dist/runtime/storage/frontmatter-codec.js +156 -13
  167. package/dist/runtime/storage/frontmatter-codec.js.map +1 -1
  168. package/dist/runtime/storage/in-memory-adapter.d.ts +7 -0
  169. package/dist/runtime/storage/in-memory-adapter.d.ts.map +1 -1
  170. package/dist/runtime/storage/in-memory-adapter.js +6 -0
  171. package/dist/runtime/storage/in-memory-adapter.js.map +1 -1
  172. package/dist/runtime/storage/markdown-adapter.d.ts.map +1 -1
  173. package/dist/runtime/storage/markdown-adapter.js +18 -20
  174. package/dist/runtime/storage/markdown-adapter.js.map +1 -1
  175. package/dist/runtime/storage/session-overlay-adapter.d.ts +12 -4
  176. package/dist/runtime/storage/session-overlay-adapter.d.ts.map +1 -1
  177. package/dist/runtime/storage/session-overlay-adapter.js +100 -13
  178. package/dist/runtime/storage/session-overlay-adapter.js.map +1 -1
  179. package/dist/runtime/storage/sidecar-meta-store.d.ts +5 -0
  180. package/dist/runtime/storage/sidecar-meta-store.d.ts.map +1 -1
  181. package/dist/runtime/storage/sidecar-meta-store.js +48 -5
  182. package/dist/runtime/storage/sidecar-meta-store.js.map +1 -1
  183. package/dist/runtime/themes/preset-tokens.js +4 -4
  184. package/dist/runtime/themes/preset-tokens.js.map +1 -1
  185. package/dist/runtime/utils.d.ts.map +1 -1
  186. package/dist/runtime/utils.js +3 -4
  187. package/dist/runtime/utils.js.map +1 -1
  188. package/dist/runtime/views/studio-layout.d.ts +3 -0
  189. package/dist/runtime/views/studio-layout.d.ts.map +1 -1
  190. package/dist/runtime/views/studio-layout.js +36 -13
  191. package/dist/runtime/views/studio-layout.js.map +1 -1
  192. package/dist/schema-utils.d.ts +7 -0
  193. package/dist/schema-utils.d.ts.map +1 -1
  194. package/dist/schema-utils.js +143 -0
  195. package/dist/schema-utils.js.map +1 -1
  196. package/dist/types.d.ts +165 -1
  197. package/dist/types.d.ts.map +1 -1
  198. package/package.json +12 -3
  199. package/static/cms/admin-entry.css +144 -13
  200. package/static/cms/admin-entry.js +539 -586
  201. package/static/cms/dev-toolbar/app.js +78 -19
  202. package/static/cms/editor/config.js +35 -15
  203. package/static/cms/editor/content-map.css +8 -1
  204. package/static/cms/editor/content-map.js +62 -23
  205. package/static/cms/editor/deployment-status.js +66 -0
  206. package/static/cms/editor/draft-client.js +87 -0
  207. package/static/cms/editor/guards.js +20 -0
  208. package/static/cms/editor/helpers.js +30 -1
  209. package/static/cms/editor/highlight.js +1 -0
  210. package/static/cms/editor/image-edit.js +44 -1
  211. package/static/cms/editor/image-utils.js +23 -7
  212. package/static/cms/editor/link-follow.js +122 -41
  213. package/static/cms/editor/link-ui.css +10 -1
  214. package/static/cms/editor/linkify.js +2 -0
  215. package/static/cms/editor/md-block-edit.js +140 -37
  216. package/static/cms/editor/md-paragraphs.js +98 -0
  217. package/static/cms/editor/md-serialize.js +1 -1
  218. package/static/cms/editor/panel.js +53 -7
  219. package/static/cms/editor/preview-mode.js +44 -0
  220. package/static/cms/editor/publish-result.js +100 -0
  221. package/static/cms/editor/rich-toolbar.js +58 -15
  222. package/static/cms/editor/sanitize.js +6 -2
  223. package/static/cms/editor/save-queue.js +75 -17
  224. package/static/cms/editor/section-controls/activation.js +3 -0
  225. package/static/cms/editor/section-controls/api.js +25 -7
  226. package/static/cms/editor/section-controls/index.js +60 -8
  227. package/static/cms/editor/section-controls/model.js +39 -4
  228. package/static/cms/editor/section-controls/mutations.js +15 -0
  229. package/static/cms/editor/section-controls/page-context.js +9 -0
  230. package/static/cms/editor/section-controls/pickers.js +53 -2
  231. package/static/cms/editor/section-controls/reorder.js +23 -0
  232. package/static/cms/editor/section-controls/selection.js +6 -0
  233. package/static/cms/editor/section-controls/spacing-drag.js +16 -0
  234. package/static/cms/editor/section-controls/ui-elements.js +7 -0
  235. package/static/cms/editor/section-controls/utils.js +7 -0
  236. package/static/cms/editor/section-controls.css +18 -3
  237. package/static/cms/editor/security.js +13 -2
  238. package/static/cms/editor/stega-hydrate.js +17 -4
  239. package/static/cms/editor/sync.js +475 -56
  240. package/static/cms/editor/text-edit.js +37 -3
  241. package/static/cms/editor/text-link-interactions.js +59 -17
  242. package/static/cms/editor/toast.js +23 -9
  243. package/static/cms/editor/tokens-and-text.css +9 -0
  244. package/static/cms/editor/toolbar.css +5 -0
  245. package/static/cms/editor/toolbar.js +91 -101
  246. package/static/cms/editor.css +1 -1
  247. package/static/cms/editor.js +1 -0
  248. package/static/cms/studio/entry-loader.js +102 -0
  249. package/static/cms/studio/field-model.js +110 -0
  250. package/static/cms/studio/fields.js +319 -0
  251. package/static/cms/studio/history-client.js +74 -0
  252. package/static/cms/studio/mutation-client.js +74 -0
  253. package/static/cms/studio/sync-client.js +107 -0
  254. package/static/cms/studio/upload-client.js +109 -0
  255. package/static/cms/studio-tokens.css +4 -4
  256. package/static/cms/studio.css +24 -0
package/README.md CHANGED
@@ -1,8 +1,10 @@
1
1
  # @caretcms/core
2
2
 
3
- Inline editing and live content collections for Astro. Add one HTML attribute to make any element editable, and use Astro's live loaders to query CMS data with `getLiveEntry` / `getLiveCollection` (stable on Astro 6, experimental on Astro 5.10+).
3
+ Inline editing, Markdown prose editing, and live content collections for Astro 6
4
+ and 7. Add one HTML attribute to make any element editable, or edit supported
5
+ blocks rendered from existing `.md` content collections.
4
6
 
5
- ![CaretCMS inline editor demo](https://caretcms.com/preview/caretcms-editor-demo.gif)
7
+ ![CaretCMS inline editor demo](https://caretcms.com/preview/caretcms-demo.gif)
6
8
 
7
9
  ## Install
8
10
 
@@ -56,6 +58,11 @@ export default defineConfig({
56
58
  });
57
59
  ```
58
60
 
61
+ Enable public access for the R2 bucket and set `R2_PUBLIC_DOMAIN` to that
62
+ hostname, or pass `publicBaseUrl` to `r2Uploads()`. Uploads fail with an
63
+ actionable configuration error if no public URL is available; Caret core does
64
+ not mount an R2 image proxy.
65
+
59
66
  - **`astro dev`** — full authoring: `/admin`, `/api/cms`, inline editor.
60
67
  - **`astro build`** — bakes stored overrides into generated HTML; authoring routes stay out of production output.
61
68
  - **Public updates** — after Publish, run CI/build/deploy (optionally via `delivery.publish.webhookUrl`).
@@ -80,9 +87,32 @@ Edits are visible to visitors immediately — middleware rewrites HTML on each r
80
87
  **Already have Astro content collections?** If your project has collections under
81
88
  `src/content/` and you haven't set `storage`, CaretCMS auto-selects
82
89
  `markdownStorage()` so the Studio lists those collections immediately (instead of
83
- "No collections yet") and edits write back to your `.md` frontmatter. Pass an
90
+ "No collections yet") and edits write back to your `.md` files. Pass an
84
91
  explicit `storage` to override — `storage: filesystemStorage()` opts back out.
85
92
 
93
+ ### Edit Markdown prose on the page
94
+
95
+ When Astro renders a `.md` entry through `markdownStorage()`, Caret marks safe
96
+ prose blocks automatically. Sign in, click a paragraph or heading, edit it on the
97
+ page, then publish the draft to write the change back to the source file.
98
+
99
+ Caret supports paragraphs, ATX headings, list items, and paragraphs inside
100
+ blockquotes. Bold, emphasis, links, inline code, and line breaks round-trip to
101
+ Markdown. Code blocks, tables, raw HTML blocks, and MDX stay read-only. Nested
102
+ blocks must fit on one source line.
103
+
104
+ Consecutive top-level paragraphs support Enter to split or insert, Backspace and
105
+ Delete to merge, selection deletion, and browser undo. Paste accepts plain text
106
+ with paragraph breaks. See [paragraph editing](../../docs/markdown-paragraph-editing.md)
107
+ for boundaries, source preservation, preview styling, and browser coverage.
108
+
109
+ Each draft records the source range and a hash of the original text. If the file
110
+ changes before publish, Caret returns a conflict and leaves both the file and the
111
+ draft untouched. Set `bodyEditing: false` in `caret()` to disable this feature.
112
+
113
+ See [Markdown body editing](https://caretcms.com/docs/markdown-body-editing/) for
114
+ the full setup, supported syntax, and publish workflow.
115
+
86
116
  ## Choose your path
87
117
 
88
118
  CaretCMS gives you two ways to make content editable. They share one storage layer and one login — you can mix them on the same site — but they answer different questions. Start at the top; reach for the next row only when you need it.
@@ -124,11 +154,16 @@ production stays locked). To set a permanent one, add `CARET_EDIT_PASSWORD=<your
124
154
  Log in at `/admin`, then click any tagged element on the page to edit it. The content Studio
125
155
  lives at `/admin/cms`.
126
156
 
127
- The inline editor only bootstraps on pages that contain `data-caret` bindings and only after
157
+ For editable content inside a link, a normal click edits while Command-click
158
+ (macOS) or Ctrl-click (Windows/Linux) opens the destination in a new tab. The
159
+ editor also shows a collision-aware Open action on hover or keyboard focus;
160
+ linked images keep separate Replace image and Open link actions.
161
+
162
+ The inline editor only bootstraps on pages that contain `data-caret` or `data-caret-md` bindings and only after
128
163
  `GET /api/cms/auth/session` confirms an authenticated editor session. Session cookies are issued
129
164
  as `HttpOnly`, `SameSite=Lax`, and automatically add `Secure` on HTTPS requests.
130
165
 
131
- When you're signed in and land on a live page that has **no** `data-caret` bindings yet, CaretCMS
166
+ When you're signed in and land on a live page that has **no** editable bindings yet, CaretCMS
132
167
  shows a small "signed in · no editable fields on this page" hint pointing you at the next step —
133
168
  so a page that isn't annotated yet reads as "nothing to edit here" rather than "is this broken?".
134
169
  The hint is editor-only (anonymous visitors never see it) and never appears inside the Studio.
@@ -137,9 +172,7 @@ The hint is editor-only (anonymous visitors never see it) and never appears insi
137
172
 
138
173
  You can also wire collections into Astro's native content layer using `caretLoader`. This lets you query CMS data with `getLiveEntry` and `getLiveCollection` from `astro:content` — the same API you use for any Astro live collection.
139
174
 
140
- > **Astro 6:** stable, no flag required.
141
- > **Astro 5.10+:** available behind `experimental.liveContentCollections: true` in `astro.config.*`.
142
- > **Astro 5.0–5.9:** the live-loader API (`defineLiveCollection`, `getLiveEntry`, `getLiveCollection`) doesn't exist; use `data-caret` inline editing, `bindEntry()`, and `loadEntry()` instead.
175
+ > **Astro 6 and 7:** stable, no experimental flag required.
143
176
 
144
177
  ### 1. Define collections
145
178
 
@@ -178,6 +211,65 @@ Inline editing (`data-caret`) and live collections read from the same storage ad
178
211
  - `data-caret` for visual, in-place editing of rendered pages
179
212
  - `getLiveEntry` / `getLiveCollection` for programmatic data access in frontmatter
180
213
 
214
+ Authenticated live-loader responses hide binding metadata inside string values.
215
+ Rendered text uses that metadata for automatic click-to-edit. Strip it before a
216
+ value enters an HTML attribute that is not itself editable, such as `href`,
217
+ `src`, `alt`, `<title>`, or a meta description:
218
+
219
+ ```astro
220
+ ---
221
+ import { stegaClean } from '@caretcms/core';
222
+ const href = stegaClean(home?.data.ctaHref ?? '/');
223
+ ---
224
+ <a href={href}>{home?.data.ctaLabel}</a>
225
+ ```
226
+
227
+ ### Preview routes and managed publication
228
+
229
+ Collection metadata can connect Studio entries to their actual site routes and
230
+ make publication a reversible visibility state:
231
+
232
+ ```js
233
+ caret({
234
+ collections: {
235
+ pages: {
236
+ previewPath: { home: '/', about: '/about' },
237
+ publication: { field: 'published' },
238
+ },
239
+ posts: {
240
+ previewPath: '/blog/{id}',
241
+ publication: { field: 'published' },
242
+ },
243
+ },
244
+ })
245
+ ```
246
+
247
+ Selecting a Studio field first uses the current page when that binding is
248
+ present. Otherwise Caret navigates the preview to `previewPath`, then scrolls to
249
+ and highlights the field. Preview paths must be same-origin absolute paths.
250
+
251
+ For a collection with `publication`, public `caretLoader`, `loadEntry`, and
252
+ `loadCollection` reads include only entries whose configured field is exactly
253
+ `true`. Authenticated editor previews include both published and unpublished
254
+ entries. Turning the field off and saving therefore removes the entry publicly
255
+ without deleting it; it can be turned on again later. The configured field must
256
+ also be a top-level boolean in that collection's explicit schema, so an invalid
257
+ publication setup fails during configuration instead of behaving ambiguously.
258
+
259
+ Astro's native build-time `getCollection()` does not pass through Caret's
260
+ request-aware loader. Filter those results explicitly:
261
+
262
+ ```astro
263
+ ---
264
+ import { getCollection } from 'astro:content';
265
+ import { isPublishedEntry } from '@caretcms/core/runtime';
266
+
267
+ const posts = (await getCollection('posts')).filter((post) =>
268
+ isPublishedEntry(post.data),
269
+ );
270
+ ---
271
+ ```
272
+
181
273
  ## Explicit schemas (optional)
182
274
 
183
275
  By default, Studio infers field types from your stored data. For better field labels, validation, and widget hints, pass JSON Schemas via the integration config:
@@ -223,7 +315,7 @@ existing entries; deriving from Zod just adds proper labels, types, and widget h
223
315
  ## What you get
224
316
 
225
317
  - **Inline editing** on any `data-caret` element (text and images)
226
- - **Live content collections** via `caretLoader` for `getLiveEntry` / `getLiveCollection` (Astro 6 stable, Astro 5.10+ behind `experimental.liveContentCollections`)
318
+ - **Live content collections** via `caretLoader` for `getLiveEntry` / `getLiveCollection` on Astro 6 and 7
227
319
  - **Content Studio** at `/admin/cms` for structured CRUD
228
320
  - **Section composer** for reordering and spacing page sections
229
321
  - **Response rewriting** middleware (server delivery) or **build-time HTML bake** (static delivery)
@@ -264,6 +356,7 @@ All routes are under `/api/cms` by default (configurable via `apiBasePath`):
264
356
  | `/api/cms/mutate` | POST | Save content mutations |
265
357
  | `/api/cms/history` | GET | Entry revision history |
266
358
  | `/api/cms/upload` | POST | File uploads |
359
+ | `/api/cms/deployment` | GET | Provider-backed status for the editor's latest accepted deployment |
267
360
  | `/api/cms/auth/login` | POST | Editor login |
268
361
  | `/api/cms/auth/session` | GET | Editor session status |
269
362
  | `/api/cms/auth/logout` | POST | Editor logout |
@@ -283,10 +376,70 @@ caret({
283
376
  storage: filesystemStorage(), // Storage adapter (default: markdownStorage when
284
377
  // src/content collections exist, else filesystem)
285
378
  uploads: localUploads(), // Upload handler (default: local filesystem)
379
+ deployment: undefined, // Optional build/deployment status provider
286
380
  schemas: {}, // Optional JSON Schema map for Studio (default: inferred)
381
+ collections: {}, // Labels, capabilities, preview paths, publication
382
+ locale: 'en', // 'en' | 'es'; dictionary overrides are also supported
383
+ bodyEditing: true, // Inline editing for rendered Markdown prose
287
384
  })
288
385
  ```
289
386
 
387
+ ### Optional named identity adapter
388
+
389
+ Shared-password mode remains the zero-dependency default. Server deployments can
390
+ instead configure an authoritative identity provider:
391
+
392
+ ```js
393
+ import caret, { defineIdentityProvider } from '@caretcms/core';
394
+
395
+ caret({
396
+ identity: defineIdentityProvider({
397
+ entrypoint: './src/caret-identity.ts',
398
+ exportName: 'identityProvider',
399
+ options: { loginOrigin: 'https://login.example.com' },
400
+ }),
401
+ });
402
+ ```
403
+
404
+ The provider module exports a factory returning an `IdentityAdapter`:
405
+
406
+ ```ts
407
+ import type { IdentityAdapter } from '@caretcms/core';
408
+
409
+ export function identityProvider(options: { loginOrigin: string }): IdentityAdapter {
410
+ return {
411
+ async authenticate(request) {
412
+ // Verify a trusted session/header. Return null to deny authentication.
413
+ // Return null to deny access. IDs must match /^[A-Za-z0-9_-]{1,64}$/.
414
+ return { id: 'editor_01', name: 'Alex Rivera', roles: ['editor'] };
415
+ },
416
+ loginUrl({ redirectTo }) {
417
+ return `${options.loginOrigin}/login?returnTo=${encodeURIComponent(redirectTo)}`;
418
+ },
419
+ logoutUrl({ redirectTo }) {
420
+ return `${options.loginOrigin}/logout?returnTo=${encodeURIComponent(redirectTo)}`;
421
+ },
422
+ async authorize({ identity, action, collection }) {
423
+ if (identity.roles?.includes('admin')) return true;
424
+ if (action === 'edit') return collection === 'pages';
425
+ if (action === 'publish') return identity.roles?.includes('reviewer') === true;
426
+ return false;
427
+ },
428
+ };
429
+ }
430
+ ```
431
+
432
+ When `identity` is configured it is authoritative: Caret does not fall back to
433
+ the shared password. Authentication errors and unsafe IDs fail closed. Named
434
+ identity is exposed by `/api/cms/auth/session`, shown in Studio, used as the
435
+ private-preview overlay key, and attached to new history snapshots. Only trust
436
+ identity headers when a proxy strips client-supplied copies and writes its own.
437
+ The optional `authorize` hook controls edit, publish, delete, collection-management,
438
+ and upload writes. When present, all content saves go to per-editor drafts so a
439
+ writer cannot bypass publish permission in server delivery. See the
440
+ [authorization policy](../../docs/authorization-policy.md) for exact action
441
+ semantics, backward-compatible defaults, and review-workflow boundaries.
442
+
290
443
  ## Rendering & output
291
444
 
292
445
  CaretCMS defaults to automatic delivery. With `caret()` or `delivery: 'auto'`, Astro
@@ -314,6 +467,7 @@ caret({
314
467
  bake: true,
315
468
  publish: {
316
469
  webhookUrl: 'https://ci.example.com/hooks/rebuild',
470
+ timeoutMs: 5000, // bounded webhook wait; 1–30000 ms
317
471
  },
318
472
  },
319
473
  })
@@ -323,6 +477,17 @@ Use `delivery: 'static'` when you want the integration to error if Astro output
323
477
  changed away from static.
324
478
 
325
479
  Full guide: [docs/static-delivery.md](../../docs/static-delivery.md).
480
+ Draft migration, conflicts, interrupted publication, and deployment retries:
481
+ [publish recovery](../../docs/publish-recovery.md).
482
+
483
+ The webhook's 2xx response means accepted, not deployed. Configure a
484
+ `DeploymentStatusProvider` to show provider-backed Deploying, Live, and failed
485
+ states; Live requires evidence for the published commit or every target entry
486
+ revision. The bundled `simulatedDeployment()` provider exercises the complete
487
+ local flow. `githubDeployment()` reads exact-correlation build evidence from the
488
+ GitHub Deployments API, including progress, failure, environment URL, commit,
489
+ and published revisions. See
490
+ [deployment completion status](../../docs/deployment-status.md).
326
491
 
327
492
  ### Server delivery
328
493
 
@@ -343,7 +508,7 @@ The default (embedded) providers write inside your project:
343
508
  | `.caret/data/` | entry JSON | `filesystemStorage()` (default) |
344
509
  | `.caret/drafts/` | per-editor draft overlays | `filesystemStorage()` |
345
510
  | `.caretcms/` | revisions + history sidecar | both filesystem and markdown storage |
346
- | `src/content/**.md` | frontmatter edits | `markdownStorage()` (auto-selected when you have content collections) |
511
+ | `src/content/**.md` | frontmatter and published prose edits | `markdownStorage()` (auto-selected when you have content collections) |
347
512
  | `public/uploads/` | uploaded images | `localUploads()` (default) |
348
513
 
349
514
  Recommended `.gitignore` for the transient state (keep `.caret/data/` or your
@@ -356,12 +521,19 @@ workflow in [docs/deployment.md](../../docs/deployment.md)):
356
521
  public/uploads/
357
522
  ```
358
523
 
524
+ Multiline literal/folded strings and source-preserving saves are supported by
525
+ Markdown storage. See [frontmatter compatibility](../../docs/markdown-frontmatter.md)
526
+ for supported forms and formatting limits.
527
+
359
528
  ## Production checklist
360
529
 
361
530
  - `CARET_EDIT_PASSWORD` — the editor password. Without it, production is locked (no dev fallback).
362
531
  - `CARET_SESSION_SECRET` — **required in production** when a password is set; sessions are
363
532
  HMAC-signed with it. Generate one with `openssl rand -base64 32`. If it's missing, logins
364
533
  return a configuration error and existing sessions are treated as signed out.
534
+ - Rotate `CARET_EDIT_PASSWORD` and `CARET_SESSION_SECRET` together, then restart or redeploy
535
+ every instance. Changing the session secret immediately invalidates all existing editor
536
+ cookies; changing only the password does not revoke sessions that are already signed in.
365
537
  - `markdownStorage()` edits `src/content/*.md` **at request time** — a dev/git workflow. In
366
538
  production, pair it with commit-on-publish + a CI rebuild (fields rendered through
367
539
  `getCollection()` are baked at build time and only refresh on rebuild), or use a server-side
@@ -373,13 +545,24 @@ public/uploads/
373
545
  from your project root, or pass explicit paths (`filesystemStorage({ dataRoot })`,
374
546
  `markdownStorage({ contentRoot })`, `localUploads({ uploadsDir })`).
375
547
 
548
+ Password mode is deliberately a single shared-editor workflow: it has no named users, roles,
549
+ or per-user attribution. Teams needing those controls should configure the authoritative
550
+ identity adapter described above.
551
+
376
552
  ## Requirements
377
553
 
378
- - Astro 5 or 6
379
- - Node 20.19.1+ or 22.12.0+
554
+ - Astro 6 or 7
555
+ - Node 22.12.0+
380
556
  - **Static delivery:** default Astro static output (no adapter)
381
557
  - **Server delivery:** `output: 'server'` plus an SSR adapter
382
558
 
559
+ ## Browser development
560
+
561
+ `npm run typecheck:browser` checks the extracted Studio modules with strict
562
+ JavaScript types. It also runs as part of core type checking and the standard
563
+ check gate. See [browser maintenance](../../docs/browser-maintenance.md) for
564
+ module boundaries, asset delivery, and the remaining incremental work.
565
+
383
566
  ## License
384
567
 
385
568
  MIT
package/dist/index.d.ts CHANGED
@@ -1,6 +1,10 @@
1
1
  import type { AstroIntegration } from "astro";
2
- import type { CaretStorageProvider, CaretUploadProvider } from "./types.js";
3
- export type { StorageAdapter, UploadHandler, UploadContext, EntryData, HistoryEntry, CollectionMetadata, CollectionSchema, CaretMode, CaretStorageProvider, CaretUploadProvider, RuntimeProviderReference, } from "./types.js";
2
+ import type { CaretStorageProvider, CaretUploadProvider, CollectionStudioConfig, CaretIdentityProvider, CaretDeploymentProvider } from "./types.js";
3
+ import { type StudioDictionary, type StudioLocale } from "./runtime/i18n.js";
4
+ import type { GitHubDeploymentOptions } from "./providers/deployment/github.js";
5
+ export type { StorageAdapter, UploadHandler, UploadContext, EntryData, HistoryEntry, RebuildReceipt, EntryCommit, EntryCommitResult, CollectionMetadata, CollectionSchema, CollectionStudioConfig, CaretIdentityProvider, CaretDeploymentProvider, DeploymentStatusProvider, DeploymentStatusEvidence, DeploymentState, DeploymentTarget, IdentityAdapter, AuthorizationPolicy, AuthorizationAction, AuthorizationRequest, EditorIdentity, CaretMode, CaretStorageProvider, CaretUploadProvider, RuntimeProviderReference, } from "./types.js";
6
+ export type { StudioDictionary, StudioLocale, StudioMessageKey } from "./runtime/i18n.js";
7
+ export type { GitHubDeploymentOptions } from "./providers/deployment/github.js";
4
8
  export { FilesystemAdapter } from "./runtime/storage/filesystem-adapter.js";
5
9
  export { MarkdownAdapter } from "./runtime/storage/markdown-adapter.js";
6
10
  export { InMemoryAdapter } from "./runtime/storage/in-memory-adapter.js";
@@ -56,6 +60,12 @@ type BaseCaretOptions = {
56
60
  * by z.toJSONSchema() from a Zod schema).
57
61
  */
58
62
  schemas?: Record<string, JsonSchemaDefinition>;
63
+ /** Studio labels, ordering, and mutation capabilities by collection. */
64
+ collections?: Record<string, CollectionStudioConfig>;
65
+ /** Fixed Studio chrome language. Spanish and English ship with core. */
66
+ locale?: StudioLocale;
67
+ /** Per-message overrides applied after the selected first-party locale. */
68
+ dictionary?: StudioDictionary;
59
69
  /**
60
70
  * Per-tag class allowlist for rich-text (`data-caret-rich`) fields. By default
61
71
  * the rich-text sanitizer strips every `class` (keeping only semantic inline
@@ -99,6 +109,7 @@ type BaseCaretOptions = {
99
109
  * The publish remains committed if this webhook fails.
100
110
  */
101
111
  webhookUrl?: string;
112
+ timeoutMs?: number;
102
113
  method?: "POST" | "PUT";
103
114
  headers?: Record<string, string>;
104
115
  };
@@ -120,15 +131,28 @@ export type CaretOptions = (BaseCaretOptions & {
120
131
  mode?: "embedded";
121
132
  storage?: CaretStorageProvider;
122
133
  uploads?: CaretUploadProvider;
134
+ identity?: CaretIdentityProvider;
135
+ deployment?: CaretDeploymentProvider;
123
136
  cloud?: never;
124
137
  }) | (BaseCaretOptions & {
125
138
  mode: "cloud";
126
139
  cloud: CaretCloudOptions;
127
140
  storage?: never;
128
141
  uploads?: never;
142
+ identity?: never;
143
+ deployment?: never;
129
144
  });
130
145
  export declare function defineStorageProvider(options: ProviderDefinitionOptions): CaretStorageProvider;
131
146
  export declare function defineUploadProvider(options: ProviderDefinitionOptions): CaretUploadProvider;
147
+ export declare function defineIdentityProvider(options: ProviderDefinitionOptions): CaretIdentityProvider;
148
+ export declare function defineDeploymentProvider(options: ProviderDefinitionOptions): CaretDeploymentProvider;
149
+ /** Deterministic local deployment status provider for development and tests. */
150
+ export declare function simulatedDeployment(options?: {
151
+ durationMs?: number;
152
+ result?: "live" | "failed";
153
+ }): CaretDeploymentProvider;
154
+ /** GitHub Deployments status provider for deployment pipelines that preserve Caret metadata. */
155
+ export declare function githubDeployment(options: GitHubDeploymentOptions): CaretDeploymentProvider;
132
156
  export declare function filesystemStorage(options?: {
133
157
  dataRoot?: string;
134
158
  metaRoot?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -5,7 +5,9 @@ import { join, resolve } from "node:path";
5
5
  import { COLLECTION_NAME_RE } from "./runtime/storage/id-contracts.js";
6
6
  import { bakeStaticHtmlFiles } from "./runtime/static-bake.js";
7
7
  import { caretSatteriPlugin } from "./markdown/satteri.js";
8
+ import { resolveStudioDictionary, } from "./runtime/i18n.js";
8
9
  import { caretRemarkPlugin } from "./markdown/remark.js";
10
+ import { publicationField, resolvePreviewPath } from "./runtime/collection-policy.js";
9
11
  export { FilesystemAdapter } from "./runtime/storage/filesystem-adapter.js";
10
12
  export { MarkdownAdapter } from "./runtime/storage/markdown-adapter.js";
11
13
  export { InMemoryAdapter } from "./runtime/storage/in-memory-adapter.js";
@@ -25,6 +27,8 @@ const RESOLVED_VIRTUAL_SCHEMAS_MODULE_ID = `\0${VIRTUAL_SCHEMAS_MODULE_ID}`;
25
27
  const FILESYSTEM_STORAGE_ENTRYPOINT = "@caretcms/core/providers/storage/filesystem";
26
28
  const MARKDOWN_STORAGE_ENTRYPOINT = "@caretcms/core/providers/storage/markdown";
27
29
  const LOCAL_UPLOADS_ENTRYPOINT = "@caretcms/core/providers/uploads/local";
30
+ const SIMULATED_DEPLOYMENT_ENTRYPOINT = "@caretcms/core/providers/deployment/simulated";
31
+ const GITHUB_DEPLOYMENT_ENTRYPOINT = "@caretcms/core/providers/deployment/github";
28
32
  export function defineStorageProvider(options) {
29
33
  return {
30
34
  kind: "storage",
@@ -41,6 +45,38 @@ export function defineUploadProvider(options) {
41
45
  options: options.options ?? null,
42
46
  };
43
47
  }
48
+ export function defineIdentityProvider(options) {
49
+ return {
50
+ kind: "identity",
51
+ entrypoint: options.entrypoint,
52
+ exportName: options.exportName ?? "default",
53
+ options: options.options ?? null,
54
+ };
55
+ }
56
+ export function defineDeploymentProvider(options) {
57
+ return {
58
+ kind: "deployment",
59
+ entrypoint: options.entrypoint,
60
+ exportName: options.exportName ?? "default",
61
+ options: options.options ?? null,
62
+ };
63
+ }
64
+ /** Deterministic local deployment status provider for development and tests. */
65
+ export function simulatedDeployment(options) {
66
+ return defineDeploymentProvider({
67
+ entrypoint: SIMULATED_DEPLOYMENT_ENTRYPOINT,
68
+ exportName: "simulatedDeploymentProvider",
69
+ options,
70
+ });
71
+ }
72
+ /** GitHub Deployments status provider for deployment pipelines that preserve Caret metadata. */
73
+ export function githubDeployment(options) {
74
+ return defineDeploymentProvider({
75
+ entrypoint: GITHUB_DEPLOYMENT_ENTRYPOINT,
76
+ exportName: "githubDeploymentProvider",
77
+ options,
78
+ });
79
+ }
44
80
  export function filesystemStorage(options) {
45
81
  return defineStorageProvider({
46
82
  entrypoint: FILESYSTEM_STORAGE_ENTRYPOINT,
@@ -66,6 +102,39 @@ import { normalizePath as normalizeMountPath } from "./runtime/config.js";
66
102
  function normalizeMode(input) {
67
103
  return input ?? "embedded";
68
104
  }
105
+ function validateCollectionOptions(collections, schemas) {
106
+ for (const [collection, config] of Object.entries(collections)) {
107
+ if (config.publication) {
108
+ const field = publicationField(config);
109
+ if (!field) {
110
+ throw new Error(`[caretcms] Collection "${collection}" has an invalid publication field.`);
111
+ }
112
+ const schema = schemas[collection];
113
+ const properties = schema?.properties;
114
+ const fieldSchema = properties && typeof properties === "object" && !Array.isArray(properties)
115
+ ? properties[field]
116
+ : null;
117
+ if (!fieldSchema || typeof fieldSchema !== "object" || Array.isArray(fieldSchema)
118
+ || fieldSchema.type !== "boolean") {
119
+ throw new Error(`[caretcms] Collection "${collection}" manages publication through "${field}", `
120
+ + `so schemas.${collection}.properties.${field} must be a boolean field.`);
121
+ }
122
+ }
123
+ if (typeof config.previewPath === "string") {
124
+ if (!resolvePreviewPath(config, "preview")) {
125
+ throw new Error(`[caretcms] Collection "${collection}" has an invalid previewPath.`);
126
+ }
127
+ continue;
128
+ }
129
+ if (config.previewPath) {
130
+ for (const id of Object.keys(config.previewPath)) {
131
+ if (!resolvePreviewPath(config, id)) {
132
+ throw new Error(`[caretcms] Collection "${collection}" has an invalid previewPath for "${id}".`);
133
+ }
134
+ }
135
+ }
136
+ }
137
+ }
69
138
  function normalizeCloudOptions(input) {
70
139
  const endpoint = input.endpoint.trim().replace(/\/$/, "");
71
140
  const projectId = input.projectId.trim();
@@ -151,6 +220,7 @@ function resolveDelivery(input) {
151
220
  bake: input.bake ?? (mode === "auto" ? true : staticDelivery),
152
221
  publish: {
153
222
  webhookUrl: input.publish?.webhookUrl?.trim() || null,
223
+ timeoutMs: input.publish?.timeoutMs,
154
224
  method: input.publish?.method ?? "POST",
155
225
  headers: input.publish?.headers ?? {},
156
226
  },
@@ -176,9 +246,9 @@ function assertDeliveryMatchesAstroOutput(delivery, astroOutput) {
176
246
  /** Every key resolveCaretOptions reads. Anything else in the options object is
177
247
  * a typo (`mountpath`) silently falling back to a default — name it instead. */
178
248
  const KNOWN_OPTION_KEYS = new Set([
179
- "mode", "cloud", "storage", "uploads", "mountPath", "apiBasePath",
180
- "enableAdmin", "enableInlineEditor", "editorHome", "schemas",
181
- "allowedClasses", "delivery", "theme", "brand",
249
+ "mode", "cloud", "storage", "uploads", "identity", "deployment", "mountPath", "apiBasePath",
250
+ "enableAdmin", "enableInlineEditor", "editorHome", "schemas", "collections", "locale", "dictionary",
251
+ "allowedClasses", "bodyEditing", "delivery", "theme", "brand",
182
252
  ]);
183
253
  /** Unknown option keys, each with its closest known key when one is plausible. */
184
254
  function unknownOptionWarnings(options) {
@@ -200,6 +270,9 @@ function resolveCaretOptions(options) {
200
270
  if (editorHome.startsWith("//")) {
201
271
  throw new Error(`[caretcms] editorHome must be a same-origin absolute path (got ${JSON.stringify(options.editorHome)}).`);
202
272
  }
273
+ const collections = options.collections ?? {};
274
+ const schemas = options.schemas ?? {};
275
+ validateCollectionOptions(collections, schemas);
203
276
  return {
204
277
  mountPath: normalizeMountPath(options.mountPath, "/admin"),
205
278
  apiBasePath: normalizeMountPath(options.apiBasePath, "/api/cms"),
@@ -210,11 +283,20 @@ function resolveCaretOptions(options) {
210
283
  uploads: isProviderReference(options.uploads, "uploads")
211
284
  ? options.uploads
212
285
  : defaults.uploads,
286
+ identity: isProviderReference(options.identity, "identity")
287
+ ? options.identity
288
+ : null,
289
+ deployment: isProviderReference(options.deployment, "deployment")
290
+ ? options.deployment
291
+ : null,
213
292
  enableAdmin: options.enableAdmin ?? mode !== "cloud",
214
293
  enableInlineEditor: options.enableInlineEditor ?? mode !== "cloud",
215
294
  editorHome,
216
295
  cloud,
217
- schemas: options.schemas ?? {},
296
+ schemas,
297
+ collections,
298
+ locale: options.locale ?? "en",
299
+ dictionary: resolveStudioDictionary(options.locale ?? "en", options.dictionary),
218
300
  allowedClasses: options.allowedClasses ?? {},
219
301
  bodyEditing: options.bodyEditing ?? true,
220
302
  delivery: resolveDelivery(options.delivery),
@@ -226,7 +308,8 @@ async function loadProviderReference(reference) {
226
308
  if (!reference)
227
309
  return null;
228
310
  const mod = (await import(/* @vite-ignore */ reference.entrypoint));
229
- const resolvedExport = mod[reference.exportName ?? "default"] ?? mod.default;
311
+ const exportName = reference.exportName ?? "default";
312
+ const resolvedExport = mod[exportName];
230
313
  if (resolvedExport == null) {
231
314
  throw new Error(`[caretcms] Could not resolve provider export "${reference.exportName ?? "default"}" from ${reference.entrypoint}.`);
232
315
  }
@@ -239,10 +322,14 @@ function buildProviderLoader(functionName, reference) {
239
322
  return `export async function ${functionName}(){return null;}`;
240
323
  }
241
324
  const importName = `__caret_${functionName}`;
325
+ const exportName = reference.exportName ?? "default";
326
+ const exportLookup = exportName === "default"
327
+ ? `${importName}.default`
328
+ : `${importName}[${JSON.stringify(exportName)}]`;
242
329
  return [
243
330
  `import * as ${importName} from ${JSON.stringify(reference.entrypoint)};`,
244
331
  `export async function ${functionName}(){`,
245
- ` const resolvedExport = ${importName}[${JSON.stringify(reference.exportName ?? "default")}] ?? ${importName}.default;`,
332
+ ` const resolvedExport = ${exportLookup};`,
246
333
  " if (resolvedExport == null) {",
247
334
  ` throw new Error(${JSON.stringify(`[caretcms] Could not resolve provider export "${reference.exportName ?? "default"}" from ${reference.entrypoint}.`)});`,
248
335
  " }",
@@ -256,6 +343,8 @@ function createRuntimeProvidersPlugin(resolved, delivery) {
256
343
  const source = [
257
344
  buildProviderLoader("loadConfiguredStorage", resolved.storage),
258
345
  buildProviderLoader("loadConfiguredUploadHandler", resolved.uploads),
346
+ buildProviderLoader("loadConfiguredIdentityAdapter", resolved.identity),
347
+ buildProviderLoader("loadConfiguredDeploymentStatus", resolved.deployment),
259
348
  `export const allowedClasses = ${JSON.stringify(resolved.allowedClasses)};`,
260
349
  `export const delivery = ${JSON.stringify(delivery)};`,
261
350
  // Surfaced for the middleware's authenticated empty-state affordance: it
@@ -281,15 +370,20 @@ function createRuntimeProvidersPlugin(resolved, delivery) {
281
370
  },
282
371
  };
283
372
  }
284
- function createSchemasPlugin(schemas) {
373
+ function createSchemasPlugin(schemas, collections) {
285
374
  const hasSchemas = Object.keys(schemas).length > 0;
286
- const source = hasSchemas
375
+ const hasCollections = Object.keys(collections).length > 0;
376
+ const source = hasSchemas || hasCollections
287
377
  ? [
288
- `import { registerCollectionSchema } from "@caretcms/core/schema-registry";`,
378
+ `import { registerCollectionSchema, registerCollectionStudioConfig } from "@caretcms/core/schema-registry";`,
289
379
  `import { buildTemplate } from "@caretcms/core/schema-utils";`,
290
380
  `const schemas = ${JSON.stringify(schemas)};`,
381
+ `const collections = ${JSON.stringify(collections)};`,
291
382
  `for (const [collection, schema] of Object.entries(schemas)) {`,
292
- ` registerCollectionSchema(collection, schema, buildTemplate(schema));`,
383
+ ` registerCollectionSchema(collection, schema, buildTemplate(schema), collections[collection]);`,
384
+ `}`,
385
+ `for (const [collection, metadata] of Object.entries(collections)) {`,
386
+ ` registerCollectionStudioConfig(collection, metadata);`,
293
387
  `}`,
294
388
  `export const registered = true;`,
295
389
  ].join("\n")
@@ -381,7 +475,7 @@ function injectMarkdownStamping(args) {
381
475
  logger.info("[caretcms] Markdown body editing enabled (Sätteri mdast plugin).");
382
476
  }
383
477
  else {
384
- updateConfig({ markdown: { remarkPlugins: [caretRemarkPlugin({ contentRoot })] } });
478
+ updateConfig({ markdown: { remarkPlugins: [[caretRemarkPlugin({ contentRoot }), { caretStampVersion: "paragraphs-v1" }]] } });
385
479
  logger.info("[caretcms] Markdown body editing enabled (remark plugin).");
386
480
  }
387
481
  }
@@ -478,6 +572,8 @@ export function caret(options = {}) {
478
572
  __ASTRO_CARET_MODE__: JSON.stringify(resolved.mode),
479
573
  __ASTRO_CARET_THEME_CONFIG__: JSON.stringify(JSON.stringify(resolved.theme)),
480
574
  __ASTRO_CARET_BRAND_CONFIG__: JSON.stringify(JSON.stringify(resolved.brand)),
575
+ __ASTRO_CARET_LOCALE__: JSON.stringify(resolved.locale),
576
+ __ASTRO_CARET_DICTIONARY__: JSON.stringify(JSON.stringify(resolved.dictionary)),
481
577
  __ASTRO_CARET_DEV_PASSWORD__: JSON.stringify(devEditorPassword ?? ""),
482
578
  // Authoritative dev signal for the auth layer: the public fallback
483
579
  // session secret is honored only when this is true. Baked false in
@@ -487,7 +583,7 @@ export function caret(options = {}) {
487
583
  },
488
584
  plugins: [
489
585
  createRuntimeProvidersPlugin(resolved, effectiveDelivery),
490
- createSchemasPlugin(resolved.schemas),
586
+ createSchemasPlugin(resolved.schemas, resolved.collections),
491
587
  ],
492
588
  },
493
589
  });
@@ -583,6 +679,10 @@ export function caret(options = {}) {
583
679
  pattern: `${resolved.apiBasePath}/draft`,
584
680
  entrypoint: new URL("./runtime/routes/draft.js", import.meta.url),
585
681
  });
682
+ injectRoute({
683
+ pattern: `${resolved.apiBasePath}/deployment`,
684
+ entrypoint: new URL("./runtime/routes/deployment.js", import.meta.url),
685
+ });
586
686
  injectRoute({
587
687
  pattern: `${resolved.apiBasePath}/collections-metadata`,
588
688
  entrypoint: new URL("./runtime/routes/collections-metadata.js", import.meta.url),
@@ -628,6 +728,7 @@ export function caret(options = {}) {
628
728
  if(!session||session.authenticated!==true)return;
629
729
  var l=document.createElement('link');l.rel='stylesheet';l.href='/__caret/editor.css';document.head.appendChild(l);
630
730
  window.__CARET__=${jsonForInlineScript(clientConfig)};
731
+ window.__CARET__.draftMode=session.draftMode===true;
631
732
  var s=document.createElement('script');s.type='module';s.src='/__caret/editor.js?v='+Date.now();document.head.appendChild(s);
632
733
  })
633
734
  .catch(function(){});