@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.
- package/README.md +195 -12
- package/dist/index.d.ts +26 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +113 -12
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +35 -3
- package/dist/loader.js.map +1 -1
- package/dist/markdown/paragraphs.d.ts +14 -0
- package/dist/markdown/paragraphs.d.ts.map +1 -0
- package/dist/markdown/paragraphs.js +61 -0
- package/dist/markdown/paragraphs.js.map +1 -0
- package/dist/markdown/remark.d.ts.map +1 -1
- package/dist/markdown/remark.js +1 -0
- package/dist/markdown/remark.js.map +1 -1
- package/dist/markdown/satteri.d.ts.map +1 -1
- package/dist/markdown/satteri.js +35 -9
- package/dist/markdown/satteri.js.map +1 -1
- package/dist/markdown/stamp.d.ts +0 -9
- package/dist/markdown/stamp.d.ts.map +1 -1
- package/dist/markdown/stamp.js +6 -0
- package/dist/markdown/stamp.js.map +1 -1
- package/dist/providers/deployment/github.d.ts +27 -0
- package/dist/providers/deployment/github.d.ts.map +1 -0
- package/dist/providers/deployment/github.js +180 -0
- package/dist/providers/deployment/github.js.map +1 -0
- package/dist/providers/deployment/simulated.d.ts +10 -0
- package/dist/providers/deployment/simulated.d.ts.map +1 -0
- package/dist/providers/deployment/simulated.js +29 -0
- package/dist/providers/deployment/simulated.js.map +1 -0
- package/dist/runtime/auth/session.d.ts +1 -0
- package/dist/runtime/auth/session.d.ts.map +1 -1
- package/dist/runtime/auth/session.js +16 -0
- package/dist/runtime/auth/session.js.map +1 -1
- package/dist/runtime/authorization.d.ts +11 -0
- package/dist/runtime/authorization.d.ts.map +1 -0
- package/dist/runtime/authorization.js +29 -0
- package/dist/runtime/authorization.js.map +1 -0
- package/dist/runtime/collection-policy.d.ts +11 -0
- package/dist/runtime/collection-policy.d.ts.map +1 -0
- package/dist/runtime/collection-policy.js +44 -0
- package/dist/runtime/collection-policy.js.map +1 -0
- package/dist/runtime/config.d.ts +3 -0
- package/dist/runtime/config.d.ts.map +1 -1
- package/dist/runtime/config.js +4 -1
- package/dist/runtime/config.js.map +1 -1
- package/dist/runtime/content-errors.d.ts +9 -0
- package/dist/runtime/content-errors.d.ts.map +1 -0
- package/dist/runtime/content-errors.js +17 -0
- package/dist/runtime/content-errors.js.map +1 -0
- package/dist/runtime/content.d.ts.map +1 -1
- package/dist/runtime/content.js +13 -4
- package/dist/runtime/content.js.map +1 -1
- package/dist/runtime/deployment-status.d.ts +14 -0
- package/dist/runtime/deployment-status.d.ts.map +1 -0
- package/dist/runtime/deployment-status.js +57 -0
- package/dist/runtime/deployment-status.js.map +1 -0
- package/dist/runtime/draft-state.d.ts +16 -0
- package/dist/runtime/draft-state.d.ts.map +1 -0
- package/dist/runtime/draft-state.js +29 -0
- package/dist/runtime/draft-state.js.map +1 -0
- package/dist/runtime/i18n.d.ts +97 -0
- package/dist/runtime/i18n.d.ts.map +1 -0
- package/dist/runtime/i18n.js +190 -0
- package/dist/runtime/i18n.js.map +1 -0
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/middleware.d.ts.map +1 -1
- package/dist/runtime/middleware.js +61 -4
- package/dist/runtime/middleware.js.map +1 -1
- package/dist/runtime/mutations/contracts.d.ts +12 -0
- package/dist/runtime/mutations/contracts.d.ts.map +1 -1
- package/dist/runtime/mutations/contracts.js +54 -4
- package/dist/runtime/mutations/contracts.js.map +1 -1
- package/dist/runtime/mutations/engine.d.ts +2 -0
- package/dist/runtime/mutations/engine.d.ts.map +1 -1
- package/dist/runtime/mutations/engine.js +220 -57
- package/dist/runtime/mutations/engine.js.map +1 -1
- package/dist/runtime/providers.d.ts +4 -1
- package/dist/runtime/providers.d.ts.map +1 -1
- package/dist/runtime/providers.js +18 -1
- package/dist/runtime/providers.js.map +1 -1
- package/dist/runtime/publish-recovery.d.ts +32 -0
- package/dist/runtime/publish-recovery.d.ts.map +1 -0
- package/dist/runtime/publish-recovery.js +98 -0
- package/dist/runtime/publish-recovery.js.map +1 -0
- package/dist/runtime/publish.d.ts +10 -4
- package/dist/runtime/publish.d.ts.map +1 -1
- package/dist/runtime/publish.js +105 -63
- package/dist/runtime/publish.js.map +1 -1
- package/dist/runtime/rebuild-webhook.d.ts +4 -0
- package/dist/runtime/rebuild-webhook.d.ts.map +1 -1
- package/dist/runtime/rebuild-webhook.js +12 -2
- package/dist/runtime/rebuild-webhook.js.map +1 -1
- package/dist/runtime/request-context.d.ts +7 -1
- package/dist/runtime/request-context.d.ts.map +1 -1
- package/dist/runtime/request-context.js +7 -1
- package/dist/runtime/request-context.js.map +1 -1
- package/dist/runtime/rewrite.d.ts.map +1 -1
- package/dist/runtime/rewrite.js +28 -2
- package/dist/runtime/rewrite.js.map +1 -1
- package/dist/runtime/rich-allowlist.js +1 -1
- package/dist/runtime/rich-allowlist.js.map +1 -1
- package/dist/runtime/routes/admin-entry.d.ts.map +1 -1
- package/dist/runtime/routes/admin-entry.js +129 -21
- package/dist/runtime/routes/admin-entry.js.map +1 -1
- package/dist/runtime/routes/admin.d.ts.map +1 -1
- package/dist/runtime/routes/admin.js +28 -14
- package/dist/runtime/routes/admin.js.map +1 -1
- package/dist/runtime/routes/auth-login.d.ts.map +1 -1
- package/dist/runtime/routes/auth-login.js +4 -0
- package/dist/runtime/routes/auth-login.js.map +1 -1
- package/dist/runtime/routes/auth-logout.d.ts.map +1 -1
- package/dist/runtime/routes/auth-logout.js +7 -2
- package/dist/runtime/routes/auth-logout.js.map +1 -1
- package/dist/runtime/routes/auth-session.d.ts.map +1 -1
- package/dist/runtime/routes/auth-session.js +10 -2
- package/dist/runtime/routes/auth-session.js.map +1 -1
- package/dist/runtime/routes/collection-list.d.ts.map +1 -1
- package/dist/runtime/routes/collection-list.js +213 -66
- package/dist/runtime/routes/collection-list.js.map +1 -1
- package/dist/runtime/routes/deployment.d.ts +5 -0
- package/dist/runtime/routes/deployment.d.ts.map +1 -0
- package/dist/runtime/routes/deployment.js +38 -0
- package/dist/runtime/routes/deployment.js.map +1 -0
- package/dist/runtime/routes/draft.d.ts.map +1 -1
- package/dist/runtime/routes/draft.js +10 -3
- package/dist/runtime/routes/draft.js.map +1 -1
- package/dist/runtime/routes/entries.d.ts.map +1 -1
- package/dist/runtime/routes/entries.js +87 -43
- package/dist/runtime/routes/entries.js.map +1 -1
- package/dist/runtime/routes/history.d.ts.map +1 -1
- package/dist/runtime/routes/history.js +40 -21
- package/dist/runtime/routes/history.js.map +1 -1
- package/dist/runtime/routes/mutate.d.ts.map +1 -1
- package/dist/runtime/routes/mutate.js +4 -1
- package/dist/runtime/routes/mutate.js.map +1 -1
- package/dist/runtime/routes/publish.d.ts.map +1 -1
- package/dist/runtime/routes/publish.js +127 -33
- package/dist/runtime/routes/publish.js.map +1 -1
- package/dist/runtime/routes/schema.d.ts.map +1 -1
- package/dist/runtime/routes/schema.js +11 -2
- package/dist/runtime/routes/schema.js.map +1 -1
- package/dist/runtime/routes/studio-home.d.ts.map +1 -1
- package/dist/runtime/routes/studio-home.js +41 -20
- package/dist/runtime/routes/studio-home.js.map +1 -1
- package/dist/runtime/routes/upload.d.ts.map +1 -1
- package/dist/runtime/routes/upload.js +3 -0
- package/dist/runtime/routes/upload.js.map +1 -1
- package/dist/runtime/schema-registry.d.ts +8 -1
- package/dist/runtime/schema-registry.d.ts.map +1 -1
- package/dist/runtime/schema-registry.js +49 -3
- package/dist/runtime/schema-registry.js.map +1 -1
- package/dist/runtime/storage/entry-commit.d.ts +8 -0
- package/dist/runtime/storage/entry-commit.d.ts.map +1 -0
- package/dist/runtime/storage/entry-commit.js +45 -0
- package/dist/runtime/storage/entry-commit.js.map +1 -0
- package/dist/runtime/storage/filesystem-adapter.d.ts +5 -0
- package/dist/runtime/storage/filesystem-adapter.d.ts.map +1 -1
- package/dist/runtime/storage/filesystem-adapter.js +12 -3
- package/dist/runtime/storage/filesystem-adapter.js.map +1 -1
- package/dist/runtime/storage/frontmatter-codec.d.ts +5 -23
- package/dist/runtime/storage/frontmatter-codec.d.ts.map +1 -1
- package/dist/runtime/storage/frontmatter-codec.js +156 -13
- package/dist/runtime/storage/frontmatter-codec.js.map +1 -1
- package/dist/runtime/storage/in-memory-adapter.d.ts +7 -0
- package/dist/runtime/storage/in-memory-adapter.d.ts.map +1 -1
- package/dist/runtime/storage/in-memory-adapter.js +6 -0
- package/dist/runtime/storage/in-memory-adapter.js.map +1 -1
- package/dist/runtime/storage/markdown-adapter.d.ts.map +1 -1
- package/dist/runtime/storage/markdown-adapter.js +18 -20
- package/dist/runtime/storage/markdown-adapter.js.map +1 -1
- package/dist/runtime/storage/session-overlay-adapter.d.ts +12 -4
- package/dist/runtime/storage/session-overlay-adapter.d.ts.map +1 -1
- package/dist/runtime/storage/session-overlay-adapter.js +100 -13
- package/dist/runtime/storage/session-overlay-adapter.js.map +1 -1
- package/dist/runtime/storage/sidecar-meta-store.d.ts +5 -0
- package/dist/runtime/storage/sidecar-meta-store.d.ts.map +1 -1
- package/dist/runtime/storage/sidecar-meta-store.js +48 -5
- package/dist/runtime/storage/sidecar-meta-store.js.map +1 -1
- package/dist/runtime/themes/preset-tokens.js +4 -4
- package/dist/runtime/themes/preset-tokens.js.map +1 -1
- package/dist/runtime/utils.d.ts.map +1 -1
- package/dist/runtime/utils.js +3 -4
- package/dist/runtime/utils.js.map +1 -1
- package/dist/runtime/views/studio-layout.d.ts +3 -0
- package/dist/runtime/views/studio-layout.d.ts.map +1 -1
- package/dist/runtime/views/studio-layout.js +36 -13
- package/dist/runtime/views/studio-layout.js.map +1 -1
- package/dist/schema-utils.d.ts +7 -0
- package/dist/schema-utils.d.ts.map +1 -1
- package/dist/schema-utils.js +143 -0
- package/dist/schema-utils.js.map +1 -1
- package/dist/types.d.ts +165 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +12 -3
- package/static/cms/admin-entry.css +144 -13
- package/static/cms/admin-entry.js +539 -586
- package/static/cms/dev-toolbar/app.js +78 -19
- package/static/cms/editor/config.js +35 -15
- package/static/cms/editor/content-map.css +8 -1
- package/static/cms/editor/content-map.js +62 -23
- package/static/cms/editor/deployment-status.js +66 -0
- package/static/cms/editor/draft-client.js +87 -0
- package/static/cms/editor/guards.js +20 -0
- package/static/cms/editor/helpers.js +30 -1
- package/static/cms/editor/highlight.js +1 -0
- package/static/cms/editor/image-edit.js +44 -1
- package/static/cms/editor/image-utils.js +23 -7
- package/static/cms/editor/link-follow.js +122 -41
- package/static/cms/editor/link-ui.css +10 -1
- package/static/cms/editor/linkify.js +2 -0
- package/static/cms/editor/md-block-edit.js +140 -37
- package/static/cms/editor/md-paragraphs.js +98 -0
- package/static/cms/editor/md-serialize.js +1 -1
- package/static/cms/editor/panel.js +53 -7
- package/static/cms/editor/preview-mode.js +44 -0
- package/static/cms/editor/publish-result.js +100 -0
- package/static/cms/editor/rich-toolbar.js +58 -15
- package/static/cms/editor/sanitize.js +6 -2
- package/static/cms/editor/save-queue.js +75 -17
- package/static/cms/editor/section-controls/activation.js +3 -0
- package/static/cms/editor/section-controls/api.js +25 -7
- package/static/cms/editor/section-controls/index.js +60 -8
- package/static/cms/editor/section-controls/model.js +39 -4
- package/static/cms/editor/section-controls/mutations.js +15 -0
- package/static/cms/editor/section-controls/page-context.js +9 -0
- package/static/cms/editor/section-controls/pickers.js +53 -2
- package/static/cms/editor/section-controls/reorder.js +23 -0
- package/static/cms/editor/section-controls/selection.js +6 -0
- package/static/cms/editor/section-controls/spacing-drag.js +16 -0
- package/static/cms/editor/section-controls/ui-elements.js +7 -0
- package/static/cms/editor/section-controls/utils.js +7 -0
- package/static/cms/editor/section-controls.css +18 -3
- package/static/cms/editor/security.js +13 -2
- package/static/cms/editor/stega-hydrate.js +17 -4
- package/static/cms/editor/sync.js +475 -56
- package/static/cms/editor/text-edit.js +37 -3
- package/static/cms/editor/text-link-interactions.js +59 -17
- package/static/cms/editor/toast.js +23 -9
- package/static/cms/editor/tokens-and-text.css +9 -0
- package/static/cms/editor/toolbar.css +5 -0
- package/static/cms/editor/toolbar.js +91 -101
- package/static/cms/editor.css +1 -1
- package/static/cms/editor.js +1 -0
- package/static/cms/studio/entry-loader.js +102 -0
- package/static/cms/studio/field-model.js +110 -0
- package/static/cms/studio/fields.js +319 -0
- package/static/cms/studio/history-client.js +74 -0
- package/static/cms/studio/mutation-client.js +74 -0
- package/static/cms/studio/sync-client.js +107 -0
- package/static/cms/studio/upload-client.js +109 -0
- package/static/cms/studio-tokens.css +4 -4
- package/static/cms/studio.css +24 -0
package/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# @caretcms/core
|
|
2
2
|
|
|
3
|
-
Inline editing
|
|
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
|
-

|
|
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`
|
|
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
|
-
|
|
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**
|
|
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`
|
|
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
|
|
379
|
-
- Node
|
|
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
|
-
|
|
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;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AAE9C,OAAO,KAAK,EAEV,oBAAoB,EACpB,mBAAmB,
|
|
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
|
|
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
|
|
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 = ${
|
|
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
|
|
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(){});
|