@bettercms-ai/mcp 0.83.0 → 0.83.2
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 +86 -86
- package/SKILL.md +160 -160
- package/dist/index.js +38 -9
- package/dist/index.js.map +1 -1
- package/dist/kit-catalog.json +1509 -1509
- package/dist/skills/bettercms/SKILL.md +206 -206
- package/dist/skills/bettercms/references/accessibility.md +171 -171
- package/dist/skills/bettercms/references/animation.md +296 -296
- package/dist/skills/bettercms/references/astro.md +148 -148
- package/dist/skills/bettercms/references/bindings.md +366 -366
- package/dist/skills/bettercms/references/componentize.md +185 -185
- package/dist/skills/bettercms/references/components.md +415 -415
- package/dist/skills/bettercms/references/content-experiments.md +62 -62
- package/dist/skills/bettercms/references/content-modeling.md +382 -382
- package/dist/skills/bettercms/references/detection.md +465 -465
- package/dist/skills/bettercms/references/dock-input.md +121 -121
- package/dist/skills/bettercms/references/editor-chrome.md +92 -92
- package/dist/skills/bettercms/references/external-content.md +78 -78
- package/dist/skills/bettercms/references/finish-site.md +377 -377
- package/dist/skills/bettercms/references/forms-and-interactions.md +245 -245
- package/dist/skills/bettercms/references/frameworks.md +159 -159
- package/dist/skills/bettercms/references/getting-started.md +156 -156
- package/dist/skills/bettercms/references/integrations.md +364 -364
- package/dist/skills/bettercms/references/kit-catalog.md +3259 -3259
- package/dist/skills/bettercms/references/kit-scenarios.md +712 -712
- package/dist/skills/bettercms/references/localization.md +85 -85
- package/dist/skills/bettercms/references/media-and-brand.md +181 -181
- package/dist/skills/bettercms/references/next.md +189 -189
- package/dist/skills/bettercms/references/other-frameworks.md +85 -85
- package/dist/skills/bettercms/references/portable-text.md +213 -213
- package/dist/skills/bettercms/references/programmatic-seo.md +98 -98
- package/dist/skills/bettercms/references/publishing.md +289 -289
- package/dist/skills/bettercms/references/seo.md +357 -357
- package/dist/skills/bettercms/references/shopify-headless.md +52 -52
- package/dist/skills/bettercms/references/site-blueprints.md +895 -895
- package/dist/skills/bettercms/references/site-review.md +74 -74
- package/dist/skills/bettercms/references/worked-examples-advanced.md +509 -509
- package/dist/skills/bettercms/references/worked-examples.md +885 -885
- package/dist/skills/bettercms/references/write-limits.md +217 -217
- package/package.json +1 -1
|
@@ -1,159 +1,159 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: "Framework wiring"
|
|
3
|
-
description: "Hosting modes, the env map, TypeGen with bettercms-codegen, value shapes, revalidation tags, the media host, static snapshots and the verify steps shared by every framework."
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Framework wiring, hosting modes and media
|
|
7
|
-
|
|
8
|
-
Doctrine (what a headless or imported site needs first): §10 and §13 of
|
|
9
|
-
`bettercms://playbook/schema`. This is the code side shared by both adapters. The repo root has
|
|
10
|
-
`astro.config.*`: also read [astro.md](astro.md); `next.config.*`: [next.md](next.md); neither:
|
|
11
|
-
[other-frameworks.md](other-frameworks.md) (`@bettercms-ai/sdk` `createClient`), and classify the
|
|
12
|
-
codebase first ([bindings.md](bindings.md) §6). Read the adapter reference before writing code; env names differ between the two.
|
|
13
|
-
|
|
14
|
-
## Hosting modes
|
|
15
|
-
|
|
16
|
-
A project's `hostingMode` is `managed`, `hybrid` or `headless`.
|
|
17
|
-
|
|
18
|
-
| Mode | Who builds and serves production | Rule |
|
|
19
|
-
|---|---|---|
|
|
20
|
-
| managed | BetterCMS | `deploy_project` / `create_deploy_upload` are the deploy path |
|
|
21
|
-
| hybrid | BetterCMS, and the client also reads content headlessly | Treat exactly like managed for builds and hosting |
|
|
22
|
-
| headless | The client's own host (Vercel, Netlify, Cloudflare Pages) | Never call `deploy_project`, `deploy_to_host` or `create_deploy_upload`; never add `bcms-rebuild.yml`. Deploy = `npx @bettercms-ai/cli@latest deploy --to <host>`, or push to the repo the host builds. Done = `receipt.frontEnd` closed ([getting-started.md](getting-started.md)) |
|
|
23
|
-
|
|
24
|
-
Headless refusals, by code:
|
|
25
|
-
|
|
26
|
-
| Code | Cause | Fix |
|
|
27
|
-
|---|---|---|
|
|
28
|
-
| `HEADLESS_NO_BUILD` | A build, release or deploy was sent for a headless project | Stop; `bettercms deploy --to <host>` or push to the provider instead |
|
|
29
|
-
| `HEADLESS_NEEDS_CUSTOM_HOST` | GitHub connect without a provider host | Add Vercel, Netlify or Cloudflare Pages under Custom hosting in the dashboard, then connect |
|
|
30
|
-
| `HEADLESS_ARTIFACT_TARGET` | The custom host expects builds from BetterCMS | Switch the host to build from the repo |
|
|
31
|
-
|
|
32
|
-
**Server rendering is one change across four systems, or none.** An SSR build (`output: 'server'`
|
|
33
|
-
with `@astrojs/node`) emits `dist/client` + `dist/server` and no `index.html` at the root. A
|
|
34
|
-
managed project serves that from its build directory only when the Hosting page is set to
|
|
35
|
-
**Run as a server app** — a dashboard setting no tool can change. Until it is set, the server never
|
|
36
|
-
runs: only prerendered pages and assets are served, and every other route answers 404 while the
|
|
37
|
-
build, the release and `get_deploy_status` read green. The deploy workflow refuses an SSR build
|
|
38
|
-
pointed to by `BCMS_BUILD_DIR` (`STATIC_HOST_GOT_SSR_ARTIFACT`) and warns when it packages one it
|
|
39
|
-
found itself. So: switch Hosting first (ask the owner), then ship the build, then fetch the URL.
|
|
40
|
-
|
|
41
|
-
## Env map
|
|
42
|
-
|
|
43
|
-
| Package | Variable | Purpose |
|
|
44
|
-
|---|---|---|
|
|
45
|
-
| `@bettercms-ai/astro` | `PUBLIC_BCMS_API_URL`, `PUBLIC_BCMS_WORKSPACE`, `PUBLIC_BCMS_PROJECT_ID` | Where reads go |
|
|
46
|
-
| `@bettercms-ai/astro` | `PUBLIC_BCMS_MEDIA_URL`, `PUBLIC_BCMS_STUDIO_URL` | Media host, editor origin |
|
|
47
|
-
| `@bettercms-ai/astro` | `BCMS_API_KEY`, `BCMS_DRAFT_SECRET` | Server-only key; draft cookie signing |
|
|
48
|
-
| `@bettercms-ai/react` | `VITE_BCMS_API_URL`, `VITE_BCMS_WORKSPACE`, `VITE_BCMS_PROJECT_ID` | Where reads go (no key, no secret: a browser app) |
|
|
49
|
-
| `@bettercms-ai/sveltekit` | `PUBLIC_BCMS_API_URL`, `PUBLIC_BCMS_WORKSPACE`, `PUBLIC_BCMS_PROJECT_ID`; `BCMS_DRAFT_SECRET` | Where reads go; draft cookie signing |
|
|
50
|
-
| `@bettercms-ai/nuxt` | `NUXT_PUBLIC_BCMS_API_URL`, `NUXT_PUBLIC_BCMS_WORKSPACE`, `NUXT_PUBLIC_BCMS_PROJECT_ID`; `BCMS_DRAFT_SECRET` | Where reads go; draft cookie signing |
|
|
51
|
-
| `@bettercms-ai/next` (snapshot bin) | `BETTERCMS_API_URL`, `BETTERCMS_API_KEY`, `BETTERCMS_WORKSPACE` | Pull `bcms-content.json` |
|
|
52
|
-
| `@bettercms-ai/next` | `BETTERCMS_PREVIEW=1` | Read live instead of the snapshot in a preview build |
|
|
53
|
-
| `@bettercms-ai/codegen` | `BETTERCMS_API_URL`, `BETTERCMS_API_KEY` | Fetch models for typegen |
|
|
54
|
-
| `@bettercms-ai/mcp` | `BETTERCMS_API_URL` | A rung name `prod`, `dev`, `local`, or a full URL |
|
|
55
|
-
|
|
56
|
-
Rule: Astro names start `PUBLIC_BCMS_`/`BCMS_`; Next and codegen names start `BETTERCMS_`. Never
|
|
57
|
-
copy one adapter's `.env` into the other. Keys never go in a `PUBLIC_` variable.
|
|
58
|
-
|
|
59
|
-
## Types
|
|
60
|
-
|
|
61
|
-
Run `bettercms-codegen` after every model or field change and commit the generated file. Why:
|
|
62
|
-
templates typed against an old schema compile and then read `undefined` for a renamed field.
|
|
63
|
-
|
|
64
|
-
1. **Generate into the compiled tree:**
|
|
65
|
-
`npx @bettercms-ai/codegen --out src/bettercms.generated.ts`. The default output is
|
|
66
|
-
`bettercms.generated.ts` at the repo root, which a `tsconfig.json` whose `include` is `src/**`
|
|
67
|
-
never type-checks. `--bindings-out <path>` also writes the Live Preview bindings module,
|
|
68
|
-
`--components-out <path>` typed React `RichText`/`Image` components.
|
|
69
|
-
2. **Auth:** `BETTERCMS_API_KEY` (or `--key`) must be a management key; `BETTERCMS_API_URL` (or
|
|
70
|
-
`--api-url`) picks the rung. The key is a secret: a CI secret or a local `.env` that git
|
|
71
|
-
ignores, never a `PUBLIC_` variable.
|
|
72
|
-
3. **Commit, and let CI check.** Output is sorted and byte-stable, so a committed file diffs
|
|
73
|
-
cleanly. In CI, run the same command, then `git diff --exit-code` on the file: a failure means
|
|
74
|
-
the schema moved and nobody regenerated. Generating only in CI (not committed) also works, but
|
|
75
|
-
then every local typecheck needs the key.
|
|
76
|
-
4. **Never edit the file by hand**; it says so in its header, and the next run overwrites it.
|
|
77
|
-
5. **Without a key** (an agent session, a quick check), `get_content_types` returns the same
|
|
78
|
-
generated types over the MCP. Paste them only as a stopgap; the CLI is the repeatable path.
|
|
79
|
-
|
|
80
|
-
## Value shapes templates read
|
|
81
|
-
|
|
82
|
-
- **Richtext**: `{ format, value, html }`. Render `value` (Portable Text) or `html`; write rules
|
|
83
|
-
are in [portable-text.md](portable-text.md).
|
|
84
|
-
- **Image**: an object `{ id, url, name, altText }`. Read `.url` and `.altText`, never treat the
|
|
85
|
-
field as a string.
|
|
86
|
-
- **Repeater / zoned array**: stored as `{ repeatable: [...] }`. Read `field.repeatable`, not
|
|
87
|
-
`field` as an array. A bare array written to such a field is wrapped on write.
|
|
88
|
-
|
|
89
|
-
## Revalidation
|
|
90
|
-
|
|
91
|
-
- On publish BetterCMS sends a webhook signed with `X-BetterCMS-Signature` (HMAC-SHA256 of the raw
|
|
92
|
-
body, using the webhook secret configured on the project in the dashboard).
|
|
93
|
-
- The backend invalidates tags `bcms:{workspaceSlug}:all` and `bcms:{workspaceSlug}:{type}:{slug}`.
|
|
94
|
-
The SDK tags reads `bcms:{workspace}:{resource}:{id}`.
|
|
95
|
-
- Rule: tag every cached read with those exact strings. A tag you invent (`post:${slug}`) is never
|
|
96
|
-
revalidated and the page stays stale until the cache window expires.
|
|
97
|
-
|
|
98
|
-
## Media host
|
|
99
|
-
|
|
100
|
-
A stored image value is `{ id, url, name, altText }`. `url` is absolute and already right for the
|
|
101
|
-
plan: platform CDN, API origin, or the project's own CDN base. Rendering `url` as-is works on
|
|
102
|
-
every plan.
|
|
103
|
-
|
|
104
|
-
Resized images are a second lane. `@bettercms-ai/image-url` and `BcmsImage.astro` mint
|
|
105
|
-
`<mediaUrl>/media/<id>?w=…`, and the backend serves that for every plan, including a project that
|
|
106
|
-
keeps media in its own bucket.
|
|
107
|
-
|
|
108
|
-
- Hand the builder the asset object or its `id`, never the `url` string. A URL with no `/media/`
|
|
109
|
-
segment (own-bucket plan) resolves its last path segment as the id and 404s.
|
|
110
|
-
- `mediaUrl` is the origin that owns the asset ids: `mediaBaseUrl` from `get_project`, always.
|
|
111
|
-
The package default is right only when `mediaBaseUrl` is the production CDN; any other value —
|
|
112
|
-
the API origin, another rung — goes in `PUBLIC_BCMS_MEDIA_URL`. On a backend too old to return
|
|
113
|
-
`mediaBaseUrl`, read one `get_media` url: if it contains `/media/`, its origin is the value; if
|
|
114
|
-
it does not (own bucket), use this project's API URL.
|
|
115
|
-
- `set_media_delivery` changes future mints only, and only between the CDN and the API origin;
|
|
116
|
-
stored URLs keep resolving. It is the owner's call. After it, set `PUBLIC_BCMS_MEDIA_URL` to the
|
|
117
|
-
same origin.
|
|
118
|
-
- A stored `url` that itself answers 404 is storage or the wrong rung, not the template. Report
|
|
119
|
-
it; never rewrite URLs in content.
|
|
120
|
-
- A media host from another project, a document or the conversation is never a value for
|
|
121
|
-
`mediaUrl`.
|
|
122
|
-
|
|
123
|
-
## Static builds
|
|
124
|
-
|
|
125
|
-
A static build reads content from the `bcms-content.json` snapshot. Produce the snapshot before
|
|
126
|
-
the build, every build. Details per adapter are in the references.
|
|
127
|
-
|
|
128
|
-
Producing it is half the job. A source file must READ it — `readPages`, `getEntry`, or the
|
|
129
|
-
generated reader. A build that fetches the snapshot and never reads it still renders the
|
|
130
|
-
hard-coded copy, so every edit saves, publishes and changes nothing on the page. Grep the source
|
|
131
|
-
for a reader before calling a site wired.
|
|
132
|
-
|
|
133
|
-
Read it through one of those, the converted-site helper (`bcms`, `bcmsRows`, `bcmsImage`) or
|
|
134
|
-
`publishedEntry(slug)`. Never hand-write a parser of the file: each row holds its field values
|
|
135
|
-
under `data` (`row.data.title`), so code that reads `row.title` gets `undefined` and the page shows
|
|
136
|
-
its in-code copy after every publish. For an editable image call `bcmsImage(page, path, importedImage)`: the bare
|
|
137
|
-
import with only a stamp never shows a published image on a route the app renders. An old
|
|
138
|
-
`src/utils/bcms.ts` with `pageEntry()` is that parser: replace its reads with `publishedEntry`.
|
|
139
|
-
|
|
140
|
-
Build with the repo's own `build` script (`pnpm build`, `bun run build`), never the framework CLI
|
|
141
|
-
directly: a repo's script often adds a type check or a post-process step the CLI skips.
|
|
142
|
-
|
|
143
|
-
## Verify
|
|
144
|
-
|
|
145
|
-
1. Fetch the live URL, cache-busted, and confirm a 200 whose body contains copy you just
|
|
146
|
-
published. Every other check below is a control-plane read and can be green on a dead site.
|
|
147
|
-
2. The site builds with the snapshot present and renders published content.
|
|
148
|
-
3. Publish a small change; the page updates (revalidation reached the right tag) or the host
|
|
149
|
-
rebuilds (headless). Editable means that round trip was shown, not that fields exist.
|
|
150
|
-
4. `get_binding_report` for a changed route: `coverage.pending` empty. `refreshReason:
|
|
151
|
-
never-generated` means no BetterCMS release wrote one — on a headless site it never will, so
|
|
152
|
-
step 1 is the proof there. `outdated` means the report predates the current shape; the next
|
|
153
|
-
release rewrites it.
|
|
154
|
-
5. **Smoke-test both render paths** on the dev server or a preview deploy: load a list page,
|
|
155
|
-
click through to a detail page with the site's own link (never paste the URL), and read the
|
|
156
|
-
browser console: clean, no `ReferenceError`, no hard reload to `/`. Then reload that detail
|
|
157
|
-
page from the URL bar. Why: a client-side route transition loads lazy chunks that read env and
|
|
158
|
-
the snapshot through a different code path than the server render, and that is where a
|
|
159
|
-
missing public variable or a server-only import surfaces.
|
|
1
|
+
---
|
|
2
|
+
title: "Framework wiring"
|
|
3
|
+
description: "Hosting modes, the env map, TypeGen with bettercms-codegen, value shapes, revalidation tags, the media host, static snapshots and the verify steps shared by every framework."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Framework wiring, hosting modes and media
|
|
7
|
+
|
|
8
|
+
Doctrine (what a headless or imported site needs first): §10 and §13 of
|
|
9
|
+
`bettercms://playbook/schema`. This is the code side shared by both adapters. The repo root has
|
|
10
|
+
`astro.config.*`: also read [astro.md](astro.md); `next.config.*`: [next.md](next.md); neither:
|
|
11
|
+
[other-frameworks.md](other-frameworks.md) (`@bettercms-ai/sdk` `createClient`), and classify the
|
|
12
|
+
codebase first ([bindings.md](bindings.md) §6). Read the adapter reference before writing code; env names differ between the two.
|
|
13
|
+
|
|
14
|
+
## Hosting modes
|
|
15
|
+
|
|
16
|
+
A project's `hostingMode` is `managed`, `hybrid` or `headless`.
|
|
17
|
+
|
|
18
|
+
| Mode | Who builds and serves production | Rule |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| managed | BetterCMS | `deploy_project` / `create_deploy_upload` are the deploy path |
|
|
21
|
+
| hybrid | BetterCMS, and the client also reads content headlessly | Treat exactly like managed for builds and hosting |
|
|
22
|
+
| headless | The client's own host (Vercel, Netlify, Cloudflare Pages) | Never call `deploy_project`, `deploy_to_host` or `create_deploy_upload`; never add `bcms-rebuild.yml`. Deploy = `npx @bettercms-ai/cli@latest deploy --to <host>`, or push to the repo the host builds. Done = `receipt.frontEnd` closed ([getting-started.md](getting-started.md)) |
|
|
23
|
+
|
|
24
|
+
Headless refusals, by code:
|
|
25
|
+
|
|
26
|
+
| Code | Cause | Fix |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| `HEADLESS_NO_BUILD` | A build, release or deploy was sent for a headless project | Stop; `bettercms deploy --to <host>` or push to the provider instead |
|
|
29
|
+
| `HEADLESS_NEEDS_CUSTOM_HOST` | GitHub connect without a provider host | Add Vercel, Netlify or Cloudflare Pages under Custom hosting in the dashboard, then connect |
|
|
30
|
+
| `HEADLESS_ARTIFACT_TARGET` | The custom host expects builds from BetterCMS | Switch the host to build from the repo |
|
|
31
|
+
|
|
32
|
+
**Server rendering is one change across four systems, or none.** An SSR build (`output: 'server'`
|
|
33
|
+
with `@astrojs/node`) emits `dist/client` + `dist/server` and no `index.html` at the root. A
|
|
34
|
+
managed project serves that from its build directory only when the Hosting page is set to
|
|
35
|
+
**Run as a server app** — a dashboard setting no tool can change. Until it is set, the server never
|
|
36
|
+
runs: only prerendered pages and assets are served, and every other route answers 404 while the
|
|
37
|
+
build, the release and `get_deploy_status` read green. The deploy workflow refuses an SSR build
|
|
38
|
+
pointed to by `BCMS_BUILD_DIR` (`STATIC_HOST_GOT_SSR_ARTIFACT`) and warns when it packages one it
|
|
39
|
+
found itself. So: switch Hosting first (ask the owner), then ship the build, then fetch the URL.
|
|
40
|
+
|
|
41
|
+
## Env map
|
|
42
|
+
|
|
43
|
+
| Package | Variable | Purpose |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| `@bettercms-ai/astro` | `PUBLIC_BCMS_API_URL`, `PUBLIC_BCMS_WORKSPACE`, `PUBLIC_BCMS_PROJECT_ID` | Where reads go |
|
|
46
|
+
| `@bettercms-ai/astro` | `PUBLIC_BCMS_MEDIA_URL`, `PUBLIC_BCMS_STUDIO_URL` | Media host, editor origin |
|
|
47
|
+
| `@bettercms-ai/astro` | `BCMS_API_KEY`, `BCMS_DRAFT_SECRET` | Server-only key; draft cookie signing |
|
|
48
|
+
| `@bettercms-ai/react` | `VITE_BCMS_API_URL`, `VITE_BCMS_WORKSPACE`, `VITE_BCMS_PROJECT_ID` | Where reads go (no key, no secret: a browser app) |
|
|
49
|
+
| `@bettercms-ai/sveltekit` | `PUBLIC_BCMS_API_URL`, `PUBLIC_BCMS_WORKSPACE`, `PUBLIC_BCMS_PROJECT_ID`; `BCMS_DRAFT_SECRET` | Where reads go; draft cookie signing |
|
|
50
|
+
| `@bettercms-ai/nuxt` | `NUXT_PUBLIC_BCMS_API_URL`, `NUXT_PUBLIC_BCMS_WORKSPACE`, `NUXT_PUBLIC_BCMS_PROJECT_ID`; `BCMS_DRAFT_SECRET` | Where reads go; draft cookie signing |
|
|
51
|
+
| `@bettercms-ai/next` (snapshot bin) | `BETTERCMS_API_URL`, `BETTERCMS_API_KEY`, `BETTERCMS_WORKSPACE` | Pull `bcms-content.json` |
|
|
52
|
+
| `@bettercms-ai/next` | `BETTERCMS_PREVIEW=1` | Read live instead of the snapshot in a preview build |
|
|
53
|
+
| `@bettercms-ai/codegen` | `BETTERCMS_API_URL`, `BETTERCMS_API_KEY` | Fetch models for typegen |
|
|
54
|
+
| `@bettercms-ai/mcp` | `BETTERCMS_API_URL` | A rung name `prod`, `dev`, `local`, or a full URL |
|
|
55
|
+
|
|
56
|
+
Rule: Astro names start `PUBLIC_BCMS_`/`BCMS_`; Next and codegen names start `BETTERCMS_`. Never
|
|
57
|
+
copy one adapter's `.env` into the other. Keys never go in a `PUBLIC_` variable.
|
|
58
|
+
|
|
59
|
+
## Types
|
|
60
|
+
|
|
61
|
+
Run `bettercms-codegen` after every model or field change and commit the generated file. Why:
|
|
62
|
+
templates typed against an old schema compile and then read `undefined` for a renamed field.
|
|
63
|
+
|
|
64
|
+
1. **Generate into the compiled tree:**
|
|
65
|
+
`npx @bettercms-ai/codegen --out src/bettercms.generated.ts`. The default output is
|
|
66
|
+
`bettercms.generated.ts` at the repo root, which a `tsconfig.json` whose `include` is `src/**`
|
|
67
|
+
never type-checks. `--bindings-out <path>` also writes the Live Preview bindings module,
|
|
68
|
+
`--components-out <path>` typed React `RichText`/`Image` components.
|
|
69
|
+
2. **Auth:** `BETTERCMS_API_KEY` (or `--key`) must be a management key; `BETTERCMS_API_URL` (or
|
|
70
|
+
`--api-url`) picks the rung. The key is a secret: a CI secret or a local `.env` that git
|
|
71
|
+
ignores, never a `PUBLIC_` variable.
|
|
72
|
+
3. **Commit, and let CI check.** Output is sorted and byte-stable, so a committed file diffs
|
|
73
|
+
cleanly. In CI, run the same command, then `git diff --exit-code` on the file: a failure means
|
|
74
|
+
the schema moved and nobody regenerated. Generating only in CI (not committed) also works, but
|
|
75
|
+
then every local typecheck needs the key.
|
|
76
|
+
4. **Never edit the file by hand**; it says so in its header, and the next run overwrites it.
|
|
77
|
+
5. **Without a key** (an agent session, a quick check), `get_content_types` returns the same
|
|
78
|
+
generated types over the MCP. Paste them only as a stopgap; the CLI is the repeatable path.
|
|
79
|
+
|
|
80
|
+
## Value shapes templates read
|
|
81
|
+
|
|
82
|
+
- **Richtext**: `{ format, value, html }`. Render `value` (Portable Text) or `html`; write rules
|
|
83
|
+
are in [portable-text.md](portable-text.md).
|
|
84
|
+
- **Image**: an object `{ id, url, name, altText }`. Read `.url` and `.altText`, never treat the
|
|
85
|
+
field as a string.
|
|
86
|
+
- **Repeater / zoned array**: stored as `{ repeatable: [...] }`. Read `field.repeatable`, not
|
|
87
|
+
`field` as an array. A bare array written to such a field is wrapped on write.
|
|
88
|
+
|
|
89
|
+
## Revalidation
|
|
90
|
+
|
|
91
|
+
- On publish BetterCMS sends a webhook signed with `X-BetterCMS-Signature` (HMAC-SHA256 of the raw
|
|
92
|
+
body, using the webhook secret configured on the project in the dashboard).
|
|
93
|
+
- The backend invalidates tags `bcms:{workspaceSlug}:all` and `bcms:{workspaceSlug}:{type}:{slug}`.
|
|
94
|
+
The SDK tags reads `bcms:{workspace}:{resource}:{id}`.
|
|
95
|
+
- Rule: tag every cached read with those exact strings. A tag you invent (`post:${slug}`) is never
|
|
96
|
+
revalidated and the page stays stale until the cache window expires.
|
|
97
|
+
|
|
98
|
+
## Media host
|
|
99
|
+
|
|
100
|
+
A stored image value is `{ id, url, name, altText }`. `url` is absolute and already right for the
|
|
101
|
+
plan: platform CDN, API origin, or the project's own CDN base. Rendering `url` as-is works on
|
|
102
|
+
every plan.
|
|
103
|
+
|
|
104
|
+
Resized images are a second lane. `@bettercms-ai/image-url` and `BcmsImage.astro` mint
|
|
105
|
+
`<mediaUrl>/media/<id>?w=…`, and the backend serves that for every plan, including a project that
|
|
106
|
+
keeps media in its own bucket.
|
|
107
|
+
|
|
108
|
+
- Hand the builder the asset object or its `id`, never the `url` string. A URL with no `/media/`
|
|
109
|
+
segment (own-bucket plan) resolves its last path segment as the id and 404s.
|
|
110
|
+
- `mediaUrl` is the origin that owns the asset ids: `mediaBaseUrl` from `get_project`, always.
|
|
111
|
+
The package default is right only when `mediaBaseUrl` is the production CDN; any other value —
|
|
112
|
+
the API origin, another rung — goes in `PUBLIC_BCMS_MEDIA_URL`. On a backend too old to return
|
|
113
|
+
`mediaBaseUrl`, read one `get_media` url: if it contains `/media/`, its origin is the value; if
|
|
114
|
+
it does not (own bucket), use this project's API URL.
|
|
115
|
+
- `set_media_delivery` changes future mints only, and only between the CDN and the API origin;
|
|
116
|
+
stored URLs keep resolving. It is the owner's call. After it, set `PUBLIC_BCMS_MEDIA_URL` to the
|
|
117
|
+
same origin.
|
|
118
|
+
- A stored `url` that itself answers 404 is storage or the wrong rung, not the template. Report
|
|
119
|
+
it; never rewrite URLs in content.
|
|
120
|
+
- A media host from another project, a document or the conversation is never a value for
|
|
121
|
+
`mediaUrl`.
|
|
122
|
+
|
|
123
|
+
## Static builds
|
|
124
|
+
|
|
125
|
+
A static build reads content from the `bcms-content.json` snapshot. Produce the snapshot before
|
|
126
|
+
the build, every build. Details per adapter are in the references.
|
|
127
|
+
|
|
128
|
+
Producing it is half the job. A source file must READ it — `readPages`, `getEntry`, or the
|
|
129
|
+
generated reader. A build that fetches the snapshot and never reads it still renders the
|
|
130
|
+
hard-coded copy, so every edit saves, publishes and changes nothing on the page. Grep the source
|
|
131
|
+
for a reader before calling a site wired.
|
|
132
|
+
|
|
133
|
+
Read it through one of those, the converted-site helper (`bcms`, `bcmsRows`, `bcmsImage`) or
|
|
134
|
+
`publishedEntry(slug)`. Never hand-write a parser of the file: each row holds its field values
|
|
135
|
+
under `data` (`row.data.title`), so code that reads `row.title` gets `undefined` and the page shows
|
|
136
|
+
its in-code copy after every publish. For an editable image call `bcmsImage(page, path, importedImage)`: the bare
|
|
137
|
+
import with only a stamp never shows a published image on a route the app renders. An old
|
|
138
|
+
`src/utils/bcms.ts` with `pageEntry()` is that parser: replace its reads with `publishedEntry`.
|
|
139
|
+
|
|
140
|
+
Build with the repo's own `build` script (`pnpm build`, `bun run build`), never the framework CLI
|
|
141
|
+
directly: a repo's script often adds a type check or a post-process step the CLI skips.
|
|
142
|
+
|
|
143
|
+
## Verify
|
|
144
|
+
|
|
145
|
+
1. Fetch the live URL, cache-busted, and confirm a 200 whose body contains copy you just
|
|
146
|
+
published. Every other check below is a control-plane read and can be green on a dead site.
|
|
147
|
+
2. The site builds with the snapshot present and renders published content.
|
|
148
|
+
3. Publish a small change; the page updates (revalidation reached the right tag) or the host
|
|
149
|
+
rebuilds (headless). Editable means that round trip was shown, not that fields exist.
|
|
150
|
+
4. `get_binding_report` for a changed route: `coverage.pending` empty. `refreshReason:
|
|
151
|
+
never-generated` means no BetterCMS release wrote one — on a headless site it never will, so
|
|
152
|
+
step 1 is the proof there. `outdated` means the report predates the current shape; the next
|
|
153
|
+
release rewrites it.
|
|
154
|
+
5. **Smoke-test both render paths** on the dev server or a preview deploy: load a list page,
|
|
155
|
+
click through to a detail page with the site's own link (never paste the URL), and read the
|
|
156
|
+
browser console: clean, no `ReferenceError`, no hard reload to `/`. Then reload that detail
|
|
157
|
+
page from the URL bar. Why: a client-side route transition loads lazy chunks that read env and
|
|
158
|
+
the snapshot through a different code path than the server render, and that is where a
|
|
159
|
+
missing public variable or a server-only import surfaces.
|