@areca-cms/skills 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +54 -2
- package/dist/agents.d.ts +53 -0
- package/dist/agents.js +302 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +326 -0
- package/dist/install.d.ts +18 -0
- package/dist/install.js +58 -0
- package/dist/mcp-config.d.ts +20 -0
- package/dist/mcp-config.js +97 -0
- package/dist/skills.d.ts +16 -0
- package/dist/skills.js +55 -0
- package/dist/zip.d.ts +6 -0
- package/dist/zip.js +51 -0
- package/package.json +51 -3
- package/skills/areca-mcp/SKILL.md +72 -0
- package/skills/areca-mcp/references/connect.md +66 -0
- package/skills/areca-mcp/references/content.md +69 -0
- package/skills/areca-mcp/references/tools.md +65 -0
- package/skills/areca-sdk/SKILL.md +148 -0
- package/skills/areca-sdk/references/api.md +68 -0
- package/skills/areca-sdk/references/production.md +88 -0
package/package.json
CHANGED
|
@@ -1,6 +1,54 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@areca-cms/skills",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Agent skills for Areca CMS: the site SDK and the MCP server. Installs into Claude Code, Codex, Cursor, Copilot, OpenCode, Gemini CLI and 20+ other agents.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"areca",
|
|
7
|
+
"cms",
|
|
8
|
+
"agent-skills",
|
|
9
|
+
"skills",
|
|
10
|
+
"mcp",
|
|
11
|
+
"claude-code",
|
|
12
|
+
"codex",
|
|
13
|
+
"cursor"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://github.com/ekozf/areca-cms/tree/master/packages/skills#readme",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/ekozf/areca-cms/issues"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/ekozf/areca-cms.git",
|
|
22
|
+
"directory": "packages/skills"
|
|
23
|
+
},
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"type": "module",
|
|
26
|
+
"bin": {
|
|
27
|
+
"areca-skills": "dist/cli.js"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"skills",
|
|
32
|
+
"CHANGELOG.md"
|
|
33
|
+
],
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=22.6.0"
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@clack/prompts": "^1.8.1"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/node": "^24.13.4",
|
|
45
|
+
"typescript": "^6.0.3",
|
|
46
|
+
"vitest": "^5.0.1"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"clean": "node --eval \"for (const d of ['dist', 'skills']) require('node:fs').rmSync(d, { recursive: true, force: true })\"",
|
|
50
|
+
"build": "pnpm run clean && tsc && node --eval \"const fs = require('node:fs'); fs.chmodSync('dist/cli.js', 0o755); fs.cpSync('../../skills', 'skills', { recursive: true })\"",
|
|
51
|
+
"typecheck": "tsc --noEmit",
|
|
52
|
+
"test": "vitest run"
|
|
53
|
+
}
|
|
6
54
|
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: areca-mcp
|
|
3
|
+
description: Connects an AI client to an Areca CMS site through its MCP server and uses its tools to write, edit, review and publish content. Covers the per-site connector URL, OAuth sign-in or personal keys, roles and go-live permission, the draft/ready/live workflow, field and Markdown formats, SEO, images, and every tool. Use when the user wants to connect Areca to an AI app or coding agent, or to create, update, approve, schedule or publish Areca CMS entries, upload images, or check what is live.
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
version: "0.1.0"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Areca MCP
|
|
10
|
+
|
|
11
|
+
Every Areca site has its own remote MCP server. Through it an AI can read and write that site's content: entries, the media Library, releases and, for agency admins, content types. It edits CMS content and is separate from the `@areca-cms/site-sdk` website code (see the `areca-sdk` skill).
|
|
12
|
+
|
|
13
|
+
## Connect
|
|
14
|
+
|
|
15
|
+
- **Connector URL:** `https://<cms-host>/api/mcp/<site-slug>`. The user copies it from the site's **Connect AI** page in the CMS. Don't guess it: if `ARECA_CMS_URL` and the `siteKey` in `areca.config.ts` exist, it is `${ARECA_CMS_URL}/api/mcp/${siteKey}`. Confirm that with the user.
|
|
16
|
+
- **Transport:** Streamable HTTP (stateless, JSON responses). Suggested server name: `areca-<site-slug>`.
|
|
17
|
+
- **Auth:** OAuth 2.1 in the browser. Add only the URL, and the client opens a sign-in and consent page on first use. No secret goes in config.
|
|
18
|
+
- Claude Code: `claude mcp add --transport http areca-<slug> <url>`, then `/mcp` > Authenticate.
|
|
19
|
+
- Other clients: see [references/connect.md](references/connect.md). It covers Cursor, VS Code/Copilot, Codex, OpenCode, Gemini, Windsurf, ChatGPT, the Claude app and more, plus personal keys (`areca_mcp_…`) for clients without OAuth.
|
|
20
|
+
|
|
21
|
+
## First calls (always)
|
|
22
|
+
|
|
23
|
+
1. `get_site_info`: returns your role, what you **cannot** do, the collections and the site's publishing mode.
|
|
24
|
+
2. `get_collection_schema({ collection })`: returns exact field formats, an example payload and SEO guidance. Call it before every first write to a collection.
|
|
25
|
+
|
|
26
|
+
Tools your role can't use are not listed at all. If the user asks for one of those actions, explain who can do it and don't look for workarounds.
|
|
27
|
+
|
|
28
|
+
## Workflow and vocabulary
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
create_entry/update_entry -> draft
|
|
32
|
+
mark_ready (owner) -> ready approved for the next release, NOT live yet
|
|
33
|
+
go_live (go-live perm.) -> live builds and deploys every ready change
|
|
34
|
+
schedule_entry -> scheduled goes live at a set time (also publishes other ready changes)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
- `status`: `draft` | `ready` | `scheduled` | `archived`. To answer "what is live" use the flags: `live` (on the production site now), `pendingRelease` (approved changes waiting for the next go live) and `editedSinceReady`.
|
|
38
|
+
- Drafts are safe. Nothing changes the live site until an entry is marked ready **and** a go live happens.
|
|
39
|
+
- Only call `mark_ready`, `go_live`, `schedule_entry`, `rollback`, `delete_*` or `archive_entry` when the user explicitly asks.
|
|
40
|
+
- Writes return a `version`. Pass it as `expectedVersion` on the next `update_entry`. On a version conflict, call `get_entry`, reapply the change and retry.
|
|
41
|
+
- Show the user the `previewUrl` that create and update return (it's signed and valid for about 1 hour). If the site has no production URL, preview isn't available.
|
|
42
|
+
|
|
43
|
+
## Roles
|
|
44
|
+
|
|
45
|
+
| Role | Can |
|
|
46
|
+
| --- | --- |
|
|
47
|
+
| editor | read everything, create and edit drafts, upload images, edit alt text, preview |
|
|
48
|
+
| owner | everything an editor can, plus `mark_ready`, `unpublish_entry`, `archive_entry`, `cancel_schedule`, `delete_entry`, `restore_revision`, `delete_asset` |
|
|
49
|
+
| + go-live permission | `go_live`, `schedule_entry`, `rollback`, `cancel_build`. Granted per connection or key with "Allow going live" (owners and agency admins only). |
|
|
50
|
+
| agency admin | everything above, plus `upsert_collection` and `delete_collection` (needs 2FA) |
|
|
51
|
+
|
|
52
|
+
## Writing content
|
|
53
|
+
|
|
54
|
+
- **Rich text = Markdown**, never HTML (HTML tags show up as literal text). Use `##`, `###` and `####` headings, lists, links, tables and `>` quotes. Images go in as ``.
|
|
55
|
+
- **Images:** call `upload_asset_from_url({ url, altText })` with a *direct* file link (JPEG, PNG, WebP, GIF or PDF, up to 20 MB), then use the returned `assetId` in image fields, `seo.ogImage` and Markdown. Always include alt text. If an upload fails, don't try other URLs. Ask the user for a working link, or continue without the image.
|
|
56
|
+
- **Field values:** `image` and `reference` take ids, `date` is `YYYY-MM-DD`, `datetime` is ISO 8601 with an offset, `select` must match an option exactly, and `slug` is lowercase with dashes and unique. Date fields are content only and never schedule an entry.
|
|
57
|
+
- **Clearing:** use `""` for a field or SEO text, `[]` for SEO lists and `false` for `noindex`/`nofollow`. Avoid `null` (`seo` rejects it). A key you leave out keeps its value.
|
|
58
|
+
- **SEO on every new entry:** `seo.title` (50-60 chars), `seo.description` (140-160), `focusKeyphrase`, `summary` (2-3 sentences) and 3-5 `keyTakeaways`. Add `faq` only when the body really answers those questions.
|
|
59
|
+
- **Schema:** if the site project has `areca.config.ts`, change the schema there and push it with the SDK rather than calling `upsert_collection`. `upsert_collection` replaces the whole field list.
|
|
60
|
+
|
|
61
|
+
Full formats, the SEO object, limits and errors are in [references/content.md](references/content.md). Every tool with its inputs is in [references/tools.md](references/tools.md).
|
|
62
|
+
|
|
63
|
+
## Errors
|
|
64
|
+
|
|
65
|
+
Tool errors come back as normal results with `isError: true` and a message that tells you the next step. Follow it. Common ones:
|
|
66
|
+
|
|
67
|
+
- `Validation failed`: call `get_collection_schema`.
|
|
68
|
+
- `changed since you last read it`: call `get_entry` and retry.
|
|
69
|
+
- `Only site owners…` or `Going live…`: tell the user who can do it.
|
|
70
|
+
- `Something went wrong on the Areca server (ref …)`: nothing is confirmed saved. Retry once, then report the ref.
|
|
71
|
+
|
|
72
|
+
Over HTTP, `401` means sign in again or reconnect. `403 mfa_required` means the agency admin must turn on 2FA. `429` means wait for `Retry-After` (120 requests/min per credential).
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Connecting clients to the Areca MCP server
|
|
2
|
+
|
|
3
|
+
The examples use `NAME` = `areca-<site-slug>` and `URL` = `https://<cms-host>/api/mcp/<site-slug>`. Every client signs in with OAuth on first use: a browser opens, the user signs in to Areca, picks what the AI may do and approves. Only owners and agency admins see the "Allow going live" box, and it is off by default.
|
|
4
|
+
|
|
5
|
+
## Coding agents (project config, URL only, safe to commit)
|
|
6
|
+
|
|
7
|
+
| Client | File | Entry |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| Claude Code | `.mcp.json` | `{"mcpServers":{"NAME":{"type":"http","url":"URL"}}}`. Or `claude mcp add --transport http NAME URL`, then `/mcp` > Authenticate. |
|
|
10
|
+
| Cursor | `.cursor/mcp.json` | `{"mcpServers":{"NAME":{"url":"URL"}}}` |
|
|
11
|
+
| VS Code / GitHub Copilot | `.vscode/mcp.json` | `{"servers":{"NAME":{"type":"http","url":"URL"}}}` |
|
|
12
|
+
| Copilot CLI | `.mcp.json` | `{"mcpServers":{"NAME":{"type":"http","url":"URL"}}}` |
|
|
13
|
+
| Codex (CLI/IDE/app) | `.codex/config.toml` (trusted projects) or `~/.codex/config.toml` | `[mcp_servers.NAME]` then `url = "URL"`. Then run `codex mcp login NAME`. |
|
|
14
|
+
| OpenCode | `opencode.json` | `{"mcp":{"NAME":{"type":"remote","url":"URL","enabled":true}}}`. Then run `opencode mcp auth NAME`. |
|
|
15
|
+
| Gemini CLI / Qwen Code | `.gemini/settings.json` / `.qwen/settings.json` | `{"mcpServers":{"NAME":{"httpUrl":"URL"}}}`. Then run `/mcp auth NAME`. |
|
|
16
|
+
| Roo Code | `.roo/mcp.json` | `{"mcpServers":{"NAME":{"type":"streamable-http","url":"URL"}}}` |
|
|
17
|
+
| Kiro | `.kiro/settings/mcp.json` | `{"mcpServers":{"NAME":{"url":"URL"}}}` |
|
|
18
|
+
| Factory Droid | `.factory/mcp.json` | `{"mcpServers":{"NAME":{"type":"http","url":"URL"}}}` |
|
|
19
|
+
| Junie | `.junie/mcp/mcp.json` | `{"mcpServers":{"NAME":{"url":"URL"}}}` |
|
|
20
|
+
| Amp | `.amp/settings.json` | `{"amp.mcpServers":{"NAME":{"url":"URL"}}}` |
|
|
21
|
+
| Continue | `.continue/mcpServers/NAME.yaml` | `name: NAME`, `version: 0.0.1`, `schema: v1`, `mcpServers: [{name: NAME, type: streamable-http, url: URL}]` |
|
|
22
|
+
| Antigravity / Kilo Code / Trae / Warp / OpenHands | MCP settings UI | Add a remote (Streamable HTTP) server with the URL |
|
|
23
|
+
|
|
24
|
+
These clients only have a user-level config. Merge the entry there:
|
|
25
|
+
|
|
26
|
+
| Client | File | Entry |
|
|
27
|
+
| --- | --- | --- |
|
|
28
|
+
| Windsurf | `~/.codeium/windsurf/mcp_config.json` | `{"mcpServers":{"NAME":{"serverUrl":"URL"}}}` |
|
|
29
|
+
| Cline | Cline > MCP Servers > Configure (`cline_mcp_settings.json`) | `{"mcpServers":{"NAME":{"type":"streamableHttp","url":"URL"}}}` |
|
|
30
|
+
| Zed | `~/.config/zed/settings.json` | `{"context_servers":{"NAME":{"url":"URL"}}}` |
|
|
31
|
+
| Augment | `~/.augment/settings.json` | `{"mcpServers":{"NAME":{"type":"http","url":"URL"}}}` |
|
|
32
|
+
| Goose | `~/.config/goose/config.yaml` | `extensions: { NAME: { name: NAME, type: streamable_http, uri: URL, enabled: true } }` |
|
|
33
|
+
|
|
34
|
+
When you edit an existing config file, merge into the existing object. Don't replace other servers.
|
|
35
|
+
|
|
36
|
+
## Chat apps
|
|
37
|
+
|
|
38
|
+
- **Claude (web, desktop, mobile):** Customize > Connectors > + Add > Add custom connector. Name it after the site, paste the URL, then Add > Connect. Sign in and approve.
|
|
39
|
+
- **ChatGPT:** Settings > Apps (or Connectors) > Create. If Create isn't there, turn on Developer mode under Advanced first. Paste the URL as the MCP server URL, set Authentication to OAuth, then Create. Sign in and approve.
|
|
40
|
+
- **Any other app:** paste the URL wherever it asks for an MCP server, remote connector or custom connector. If it needs JSON, use `{"mcpServers":{"NAME":{"url":"URL"}}}`.
|
|
41
|
+
|
|
42
|
+
## Personal keys (scripts, CI, clients without OAuth)
|
|
43
|
+
|
|
44
|
+
- The user creates one under Connect AI > Advanced: personal keys > Create AI key. It lasts 30 days, 90 (default), 1 year or never, with optional "Allow going live". It is shown once, and each user can have up to 20 per site.
|
|
45
|
+
- Format: `areca_mcp_…`. Send it as the header `Authorization: Bearer <key>` to either the connector URL or `https://<cms-host>/api/mcp`, where the key decides the site.
|
|
46
|
+
- Keep keys out of git. Reference an env var (e.g. `ARECA_MCP_KEY`) where the client supports it.
|
|
47
|
+
- Claude Code: `claude mcp add --transport http NAME URL --header "Authorization: Bearer $ARECA_MCP_KEY"`
|
|
48
|
+
- JSON clients: `"headers": {"Authorization": "Bearer ${ARECA_MCP_KEY}"}` (the interpolation syntax depends on the client)
|
|
49
|
+
- stdio-only clients: `npx -y mcp-remote URL --header "Authorization:${ARECA_AUTH}"` with env `ARECA_AUTH="Bearer <key>"`
|
|
50
|
+
|
|
51
|
+
## OAuth details (for custom clients)
|
|
52
|
+
|
|
53
|
+
- An unauthenticated request returns 401 with `WWW-Authenticate: Bearer resource_metadata="<cms>/.well-known/oauth-protected-resource/api/mcp/<slug>"`.
|
|
54
|
+
- The authorization server metadata is at `<cms>/.well-known/oauth-authorization-server/api/auth`. Dynamic client registration uses `POST <cms>/api/auth/oauth2/register`. Requests need PKCE S256 and `resource=<connector URL>`, and the scope must include `mcp`; add `offline_access` for refresh tokens.
|
|
55
|
+
- Access tokens (`areca_at_`) last 1 hour. Refresh tokens (`areca_rt_`) rotate on use and last 90 days from their last use. A connection idle for 90 days is revoked. The role is checked again on every request.
|
|
56
|
+
|
|
57
|
+
## Troubleshooting
|
|
58
|
+
|
|
59
|
+
| Symptom | Fix |
|
|
60
|
+
| --- | --- |
|
|
61
|
+
| 401 "Sign in to connect" | Run the client's auth step (e.g. `/mcp`, `codex mcp login`) |
|
|
62
|
+
| 401 "Key is for a different site" | Use the connector URL of the key's site, or create a key there |
|
|
63
|
+
| 401 "This app is no longer connected" | Someone disconnected it in Connect AI. Reconnect. |
|
|
64
|
+
| 403 `mfa_required` | The agency admin must enable 2FA in Account settings |
|
|
65
|
+
| 404 "Site not found" | Wrong site slug in the URL |
|
|
66
|
+
| Go-live tools missing | Reconnect with "Allow going live" ticked (owners only), or ask an owner to press Go live |
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Field formats, SEO, limits and errors
|
|
2
|
+
|
|
3
|
+
## Field values (write → read)
|
|
4
|
+
|
|
5
|
+
| Type | Write | Read back |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| `text` | Plain text, up to 1,000 chars | same |
|
|
8
|
+
| `textarea` | Plain text with line breaks, up to 50,000 chars, no Markdown | same |
|
|
9
|
+
| `richtext` | Markdown string (see below): at most 200,000 text chars, 20,000 nodes, depth 32 | Markdown |
|
|
10
|
+
| `image` | asset id from `list_assets` / `upload_asset_from_url` | `{ assetId, url, alt }` or `null` |
|
|
11
|
+
| `reference` | entry id from `list_entries` of the referenced collection | `{ entryId }` or `null` |
|
|
12
|
+
| `date` | `YYYY-MM-DD` | same |
|
|
13
|
+
| `datetime` | ISO 8601 with offset, e.g. `2026-10-10T09:00:00+02:00` | same |
|
|
14
|
+
| `select` | one listed option, spelled exactly | same |
|
|
15
|
+
| `slug` | `a-z`, `0-9` and `-`, unique in the collection, up to 200 chars | same |
|
|
16
|
+
| `boolean` / `number` / `url` | `true`/`false` / JSON number / absolute http(s) URL | same |
|
|
17
|
+
|
|
18
|
+
Unknown field keys are refused, and the error lists the valid ones. Entry data is limited to 1 MB.
|
|
19
|
+
|
|
20
|
+
**Clearing on update:** use `""` for fields and SEO text, `false` for `noindex`/`nofollow`, `[]` for `keyTakeaways`/`faq`/`about`, and an empty `name` for `author`. Don't use `null`: fields accept it, but `seo` rejects it. A key you leave out keeps its value.
|
|
21
|
+
|
|
22
|
+
## Rich text Markdown
|
|
23
|
+
|
|
24
|
+
- Use `##` for sections and `###` or `####` for subsections. Other heading levels are clamped.
|
|
25
|
+
- Separate paragraphs with a blank line. `**bold**`, `*italic*`, `[link](https://…)`, lists, `>` quotes and tables all work.
|
|
26
|
+
- Images: ``. The asset must be in this client's Library. Read-back keeps the `asset:` form, so the text can be sent again as is.
|
|
27
|
+
- Never send HTML: it is not rendered, and the tags end up as visible text. A full https image URL works for images, but uploaded assets are better.
|
|
28
|
+
|
|
29
|
+
## SEO object (`seo` on create/update)
|
|
30
|
+
|
|
31
|
+
| Key | Limit | Guidance |
|
|
32
|
+
| --- | --- | --- |
|
|
33
|
+
| `title` | 300 | aim for 50-60 chars |
|
|
34
|
+
| `description` | 1,000 | aim for 140-160 chars |
|
|
35
|
+
| `focusKeyphrase` | 200 | the search phrase the entry targets |
|
|
36
|
+
| `summary` | 2,000 | 2-3 sentences that answer the main question |
|
|
37
|
+
| `keyTakeaways` | 12 × 500 | 3-5 self-contained points |
|
|
38
|
+
| `faq` | 20 × `{question (500), answer (3,000)}` | only questions the body really answers |
|
|
39
|
+
| `ogImage` | asset id | share image, ideally 1200×630 |
|
|
40
|
+
| `ogTitle`, `ogDescription` | 300 / 1,000 | fall back to title and description |
|
|
41
|
+
| `canonicalUrl` | URL | only when the page duplicates another URL |
|
|
42
|
+
| `noindex`, `nofollow` | boolean | |
|
|
43
|
+
| `twitterCard` | `summary_large_image` or `summary` | |
|
|
44
|
+
| `schemaType` | `Article`, `BlogPosting`, `NewsArticle` or `WebPage` | |
|
|
45
|
+
| `about` | 12 × `{name, sameAs?}` | entities the entry is about |
|
|
46
|
+
| `author` | `{name, url?, bio?}` | |
|
|
47
|
+
| `jsonLd` | 20,000 chars of JSON | usually leave unset, because the CMS builds the main node |
|
|
48
|
+
|
|
49
|
+
Unknown SEO keys are refused. There is no SEO-generation tool, so you write these values yourself.
|
|
50
|
+
|
|
51
|
+
## Uploads
|
|
52
|
+
|
|
53
|
+
`upload_asset_from_url` accepts JPEG, PNG, WebP, GIF and PDF (detected from the bytes) up to 20 MB. URLs must be public `http`/`https` on ports 80, 443, 8080 or 8443, with no credentials. Private and loopback addresses are refused, and there is a 15 s timeout with at most 3 redirects. Use a direct file link, not an HTML page.
|
|
54
|
+
|
|
55
|
+
## Errors
|
|
56
|
+
|
|
57
|
+
| Message starts with | Do |
|
|
58
|
+
| --- | --- |
|
|
59
|
+
| `Validation failed.` | Call `get_collection_schema`, fix the listed fields, retry |
|
|
60
|
+
| `This entry changed since you last read it` | Call `get_entry`, reapply the change, retry with the new `version` |
|
|
61
|
+
| slug message | Pick another unique slug |
|
|
62
|
+
| `This asset is still used by N item(s)` | Remove it from those items first |
|
|
63
|
+
| `Could not import the file:` | Ask the user for a direct, public file link |
|
|
64
|
+
| `Only site owners…` / `Going live…` / `Only agency admins…` | Tell the user who can do it, e.g. an owner presses Go live in the CMS |
|
|
65
|
+
| `No entry with id…` (or asset/job/publication) | Use the list tools to find valid ids |
|
|
66
|
+
| `No collection "…" on this site` | Use one of the listed keys (see `get_site_info`) |
|
|
67
|
+
| `Something went wrong on the Areca server (ref …)` | Nothing is confirmed saved. Retry once, then report the ref. |
|
|
68
|
+
|
|
69
|
+
HTTP: `401` = token or key invalid, expired, revoked or for another site, so reconnect. `403 mfa_required` = the agency admin must enable 2FA. `429` = wait `Retry-After` seconds (120/min per credential, plus 600/min per client IP when the CMS trusts its proxy).
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Areca MCP tools
|
|
2
|
+
|
|
3
|
+
`*` means required. Access: **all** = every role, **owner** = owners and agency admins, **go-live** = also needs "Allow going live" on the connection or key, **agency** = agency admins only. Tools a caller cannot use are not listed by the server.
|
|
4
|
+
|
|
5
|
+
## Site and schema
|
|
6
|
+
|
|
7
|
+
| Tool | Access | Inputs | Returns |
|
|
8
|
+
| --- | --- | --- | --- |
|
|
9
|
+
| `get_site_info` | all | none | `site` (name, slug, productionUrl, publishingMode), `you` (role, capabilities), `cannotDo[]`, `collections[]`, `guide`. **Call first.** |
|
|
10
|
+
| `get_collection_schema` | all | `collection*` | Per-field key, type, required, format, options; example `fields` and `seo` payloads; SEO guide |
|
|
11
|
+
|
|
12
|
+
## Entries
|
|
13
|
+
|
|
14
|
+
| Tool | Access | Inputs | Returns |
|
|
15
|
+
| --- | --- | --- | --- |
|
|
16
|
+
| `list_entries` | all | `collection*`, `status?` (draft, ready, scheduled, archived), `search?`, `limit?` (1-100, default 20), `offset?` | `items[]` (id, slug, status, live, pendingRelease, title, version), `total`, `nextOffset` |
|
|
17
|
+
| `get_entry` | all | `id*`, `includePublished?` | `fields`, `seo`, `status`, `live`, `pendingRelease`, `editedSinceReady`, `version`. With `includePublished`, also `liveFields` and `approvedFields`. |
|
|
18
|
+
| `create_entry` | all | `collection*`, `fields?`, `seo?` | New draft + `previewUrl` |
|
|
19
|
+
| `update_entry` | all | `id*`, `fields?`, `seo?` (at least one), `expectedVersion?` | Updated draft + `previewUrl`. Keys you leave out keep their values. |
|
|
20
|
+
| `list_revisions` | all | `id*` | Revision id, note, createdAt, createdBy |
|
|
21
|
+
| `get_preview_url` | all | `id*` | `{ url }`, signed, about 1 hour. Fails without a site production URL. |
|
|
22
|
+
|
|
23
|
+
## Workflow
|
|
24
|
+
|
|
25
|
+
| Tool | Access | Inputs | Effect |
|
|
26
|
+
| --- | --- | --- | --- |
|
|
27
|
+
| `mark_ready` | owner | `id*` | Approves the current draft for the next release. Not live yet. |
|
|
28
|
+
| `unpublish_entry` | owner | `id*` | Takes it out of the release and back to draft. Removed from the live site at the next go live. |
|
|
29
|
+
| `archive_entry` | owner | `id*` | Archives it. Removed from the live site at the next go live. |
|
|
30
|
+
| `cancel_schedule` | owner | `id*` | Clears the scheduled time |
|
|
31
|
+
| `delete_entry` | owner | `id*` | Deletes permanently. Destructive. |
|
|
32
|
+
| `restore_revision` | owner | `id*`, `revisionId*`, `expectedVersion?` | Copies a revision into the draft and returns `skippedFields`. Doesn't mark it ready. |
|
|
33
|
+
| `schedule_entry` | go-live | `id*`, `publishAt*` (future ISO 8601 with offset), `timezone*` (IANA, e.g. `Europe/Amsterdam`) | Goes live at that time, together with the site's other ready changes |
|
|
34
|
+
|
|
35
|
+
## Library (assets)
|
|
36
|
+
|
|
37
|
+
| Tool | Access | Inputs | Returns |
|
|
38
|
+
| --- | --- | --- | --- |
|
|
39
|
+
| `list_assets` | all | `search?`, `limit?` (1-100), `offset?` | assetId, filename, mimeType, width, height, alt, url |
|
|
40
|
+
| `get_asset` | all | `id*` | Asset + `usageCount` |
|
|
41
|
+
| `upload_asset_from_url` | all | `url*` (direct http(s) file link), `altText?`, `filename?` | Asset with `assetId` + `howToUse` |
|
|
42
|
+
| `update_asset_alt` | all | `id*`, `altText*` (`""` clears) | Asset |
|
|
43
|
+
| `delete_asset` | owner | `id*` | Refused while the asset is in use |
|
|
44
|
+
|
|
45
|
+
## Publishing
|
|
46
|
+
|
|
47
|
+
| Tool | Access | Inputs | Returns |
|
|
48
|
+
| --- | --- | --- | --- |
|
|
49
|
+
| `get_release_status` | all | none | `publishingMode`, `live`, `demo`, `activeJob`, `pendingCount`, `pendingChanges[]` |
|
|
50
|
+
| `list_publications` | all | `limit?` (1-50, default 10) | Publications, newest first |
|
|
51
|
+
| `get_build_job` | all | `jobId*` | Status, attempts, timing, error |
|
|
52
|
+
| `go_live` | go-live | `note?` | Queues a build that publishes **all** ready changes and returns `publicationId` and `jobId`. Poll `get_build_job`. |
|
|
53
|
+
| `rollback` | go-live | `publicationId*` | Queues a rollback to an earlier publication |
|
|
54
|
+
| `cancel_build` | go-live | `jobId*` | Cancels a queued build |
|
|
55
|
+
|
|
56
|
+
## Content types (agency)
|
|
57
|
+
|
|
58
|
+
| Tool | Access | Inputs | Effect |
|
|
59
|
+
| --- | --- | --- | --- |
|
|
60
|
+
| `upsert_collection` | agency | `key*`, `label*`, `routePattern*`, `fields*` (up to 100: `key*`, `label*`, `type*`, `required?`, `options?`, `collection?`, `description?`, `after?`), `enabled?` | Creates the collection or **replaces** its field list. Prefer `areca.config.ts` + `areca manifest push` when the site syncs its schema from code. |
|
|
61
|
+
| `delete_collection` | agency | `key*` | Deletes the collection and its entries. Destructive. |
|
|
62
|
+
|
|
63
|
+
## Prompt
|
|
64
|
+
|
|
65
|
+
`write_post({ collection*, topic* })` returns a step-by-step writing brief: schema, images, draft and SEO. It ends with "mark ready or go live only when asked".
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: areca-sdk
|
|
3
|
+
description: Connects a website project to Areca CMS with the @areca-cms/site-sdk package. Covers setup, tokens and env vars, areca.config.ts collections and field types, the areca CLI (init, manifest push/pull, pull, run), reading pulled content, rich text, SEO head tags, draft preview routes and Go-live builds. Use when a project imports @areca-cms/site-sdk, has an areca.config.ts, mentions ARECA_* env vars, or the user wants to add, render or deploy Areca CMS content.
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
version: "0.1.0"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Areca site SDK
|
|
10
|
+
|
|
11
|
+
`@areca-cms/site-sdk` connects a Node.js website to one site in an Areca CMS. The schema is declared in code and pushed to the CMS. Editors write and publish there, and the website pulls the published snapshot as JSON at build time. Visitor requests never call the CMS.
|
|
12
|
+
|
|
13
|
+
Requirements: Node >= 22.6 (24 LTS recommended), ESM only. It works with any framework because the core has no React dependency.
|
|
14
|
+
|
|
15
|
+
## Mental model
|
|
16
|
+
|
|
17
|
+
1. `areca.config.ts` declares **collections** (content types) and their **fields**.
|
|
18
|
+
2. `areca manifest push` sends that schema to the CMS (needs the schema token).
|
|
19
|
+
3. Editors create entries in the CMS and **Go live**, which creates an immutable **publication** (snapshot).
|
|
20
|
+
4. `areca pull` downloads the publication into `src/generated/content/`.
|
|
21
|
+
5. Server code reads it with `readContent()` from `@areca-cms/site-sdk/pull`.
|
|
22
|
+
|
|
23
|
+
There are no singletons, blocks, locales, typegen, webhooks or runtime query API. A "page" is an entry in a collection. Filter, sort and paginate the returned arrays in your own code.
|
|
24
|
+
|
|
25
|
+
## Setup
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pnpm add @areca-cms/site-sdk # or npm install / yarn add
|
|
29
|
+
pnpm exec areca init # interactive; writes config, env files, scripts
|
|
30
|
+
# non-interactive:
|
|
31
|
+
pnpm exec areca init --cms-url https://cms.example.com --token "$ARECA_SITE_TOKEN" --site-key acme-site --yes
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`init` writes `areca.config.ts`, `.env.example`, `.env.local` (`ARECA_CMS_URL`, `ARECA_SITE_TOKEN`), `.gitignore` entries, `src/generated/content/manifest.json`, and the package scripts `sync-manifest` and `content:pull`. It also wraps `dev` in `areca run --`. Use `--force` to overwrite an existing config. `--out` and `--config` change the paths.
|
|
35
|
+
|
|
36
|
+
| Env var | Needed for | Where the user gets it |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| `ARECA_CMS_URL` | everything | The CMS base URL, e.g. `https://cms.example.com` |
|
|
39
|
+
| `ARECA_SITE_TOKEN` | pull, client, preview | CMS: site Settings > Developer > build token (shown once; rotate to get a new one) |
|
|
40
|
+
| `ARECA_SCHEMA_TOKEN` | `manifest push` only | Settings > Developer > Rotate schema token. Keep it in the authoring env only, never in builds. |
|
|
41
|
+
| `ARECA_SITE_KEY` | `manifest pull` without a config | The site key shown in Settings |
|
|
42
|
+
| `ARECA_SNAPSHOT_ID` | pinned pulls during Go-live builds | Set by the CMS deploy; empty or `0` means current live |
|
|
43
|
+
|
|
44
|
+
The CLI loads `.env.local` and then `.env`, and existing process env wins. Library imports do **not** load env files.
|
|
45
|
+
|
|
46
|
+
**Security:** both tokens are server secrets. Never put them in browser code or behind a `VITE_`, `NEXT_PUBLIC_` or `PUBLIC_` prefix, and never commit them. Only import `readContent` and the client in server or build code.
|
|
47
|
+
|
|
48
|
+
## Declare content
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
// areca.config.ts
|
|
52
|
+
import { defineSiteContent } from "@areca-cms/site-sdk";
|
|
53
|
+
|
|
54
|
+
export default defineSiteContent({
|
|
55
|
+
siteKey: "acme-site", // must match the CMS site key
|
|
56
|
+
collections: {
|
|
57
|
+
articles: {
|
|
58
|
+
route: "/blog/$slug", // documentation only; build the route yourself
|
|
59
|
+
label: "Articles",
|
|
60
|
+
fields: [
|
|
61
|
+
{ key: "title", label: "Title", type: "text", required: true },
|
|
62
|
+
{ key: "slug", label: "Slug", type: "slug", required: true },
|
|
63
|
+
{ key: "body", label: "Body", type: "richtext" },
|
|
64
|
+
{ key: "cover", label: "Cover", type: "image" },
|
|
65
|
+
{ key: "category", label: "Category", type: "select", options: ["news", "guides"] },
|
|
66
|
+
{ key: "author", label: "Author", type: "reference", collection: "authors" },
|
|
67
|
+
{ key: "startsAt", label: "Starts", type: "datetime" },
|
|
68
|
+
{ key: "endsAt", label: "Ends", type: "datetime", after: "startsAt" },
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
- Field types: `text`, `textarea`, `richtext`, `image`, `date`, `datetime`, `select` (`options`), `reference` (`collection`), `slug`, `boolean`, `number`, `url`. Optional per field: `required`, `description`, `after` (date or datetime key this must be on or after).
|
|
76
|
+
- Keys match `^[A-Za-z][A-Za-z0-9_-]{0,63}$`. Collection keys must be unique case-insensitively. Reserved: `manifest`, `constructor`, `prototype`, `__proto__`, and Windows device names (`con`, `prn`, `aux`, `nul`, `com1`, `lpt1`, ...).
|
|
77
|
+
- A push is the **full** manifest. Collections left out are disabled (their entries are kept). The CMS adds a required `slug` field if one is missing. Limits: 100 collections, 100 fields each, 256 KiB.
|
|
78
|
+
- `areca manifest pull --config areca.config.ts [--yes]` regenerates the config from the CMS. It replaces the whole file and keeps `siteKey`. Without a TTY it needs `--yes` if the config already has collections.
|
|
79
|
+
|
|
80
|
+
## CLI
|
|
81
|
+
|
|
82
|
+
```text
|
|
83
|
+
areca init [--cms-url <url>] [--token <t>] [--site-key <key>] [--out <dir>] [--config <path>] [--yes] [--force]
|
|
84
|
+
areca manifest push [config] # needs ARECA_SCHEMA_TOKEN (+ URL and site token)
|
|
85
|
+
areca manifest pull [--config <path>] [--yes]
|
|
86
|
+
areca pull [--out <dir>] [--publication <id>] # default out: src/generated/content
|
|
87
|
+
areca run -- <command> # run a command with .env.local/.env loaded
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`areca sync-manifest` is a deprecated alias for `manifest push`. Run the CLI as `pnpm exec areca`, `npx areca` or `yarn areca`. A pull fails until something has gone live in the CMS. To pull before every build, add `"prebuild": "areca pull"`.
|
|
91
|
+
|
|
92
|
+
## Read content (server or build only)
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { readContent, readContentManifest } from "@areca-cms/site-sdk/pull";
|
|
96
|
+
import type { ImageValue, ReferenceValue, RichTextValue, SnapshotEntry } from "@areca-cms/site-sdk";
|
|
97
|
+
|
|
98
|
+
type Article = SnapshotEntry & {
|
|
99
|
+
data: { title: string; body: RichTextValue; cover: ImageValue | null; author: ReferenceValue | null };
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const dir = "src/generated/content";
|
|
103
|
+
const manifest = readContentManifest(dir); // capture once for consistent multi-collection reads
|
|
104
|
+
const articles = readContent<Article>(dir, "articles", manifest);
|
|
105
|
+
const post = articles.find((a) => a.slug === slug);
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
- Entry: `{ id, slug, publishedAt, data, seo? }`. The field values live in `data`. You write the types yourself because there is no codegen.
|
|
109
|
+
- `ImageValue` = `{ url, width|null, height|null, alt, mimeType }`, where `url` is absolute on the CMS origin. `ReferenceValue` = `{ collection, slug, id }`. Optional and image fields can be `null` or missing, so check them.
|
|
110
|
+
- `readContent` reads files at runtime, so ship `src/generated/content/` (including `.publications/`) with the server build or container. For TanStack Start, call it inside a `createServerFn` handler, never in route or component code.
|
|
111
|
+
|
|
112
|
+
## Rich text
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
import { Markdown } from "@tanstack/markdown/react"; // pnpm add @tanstack/markdown@1.0.0 (match the SDK)
|
|
116
|
+
import { richTextComponents } from "@areca-cms/site-sdk/rich-text-react";
|
|
117
|
+
import { richTextDocument } from "@areca-cms/site-sdk";
|
|
118
|
+
|
|
119
|
+
<Markdown components={richTextComponents()}>{richTextDocument(article.data.body)}</Markdown>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`RichTextValue` = `{ document, markdown, html, json }`. Outside React, use `richTextHtml(value)`, which is trusted CMS HTML, or `richTextMarkdown(value)`. Keep `img { max-width: 100% }` and don't force `width: 100%`, because editors set image sizes.
|
|
123
|
+
|
|
124
|
+
## SEO
|
|
125
|
+
|
|
126
|
+
- TanStack Router: `head: ({ loaderData }) => seoHead(loaderData?.post.seo, { titleTemplate: "%s · Acme", siteName: "Acme" })`.
|
|
127
|
+
- Astro or plain SSR: `seoHeadHtml(entry.seo, options)` from `@areca-cms/site-sdk/seo` returns an escaped HTML string. `seoJsonLd(seo)` returns safe JSON-LD strings.
|
|
128
|
+
- `seo` is optional. When `seo.summary`, `keyTakeaways` or `faq` exist, render them visibly on the page so the content matches the JSON-LD.
|
|
129
|
+
|
|
130
|
+
## Preview, Go live and the client
|
|
131
|
+
|
|
132
|
+
Read [references/production.md](references/production.md) when the user needs any of these:
|
|
133
|
+
|
|
134
|
+
- A draft preview route: `getPreviewEntry` and `previewHeaders` at `/__preview/<collection>/<slug>?token=`.
|
|
135
|
+
- The production health route that Go live waits for.
|
|
136
|
+
- Docker or CI builds with `ARECA_SNAPSHOT_ID`.
|
|
137
|
+
- The CMS settings needed: production URL, deployment target, publishing mode.
|
|
138
|
+
- Direct use of `createArecaClient` and error messages.
|
|
139
|
+
|
|
140
|
+
Read [references/api.md](references/api.md) for every export, type and signature.
|
|
141
|
+
|
|
142
|
+
## Rules
|
|
143
|
+
|
|
144
|
+
- Change the schema in `areca.config.ts` and then `manifest push`. Don't hand-edit generated content.
|
|
145
|
+
- Never import `@areca-cms/site-sdk/pull` or a client with tokens into browser bundles.
|
|
146
|
+
- Exclude `/__preview/*` from prerendering and caching.
|
|
147
|
+
- The SDK doesn't create CMS sites or tokens. An agency admin creates the site in the CMS. Ask the user for the URL, site key and tokens instead of guessing them.
|
|
148
|
+
- To edit content (not code) through an AI, use the Areca MCP server (see the `areca-mcp` skill).
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# @areca-cms/site-sdk API reference
|
|
2
|
+
|
|
3
|
+
ESM only. Import paths and their exports:
|
|
4
|
+
|
|
5
|
+
## `@areca-cms/site-sdk`
|
|
6
|
+
|
|
7
|
+
| Export | Signature | Notes |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| `defineSiteContent` | `<T extends SiteContentConfig>(config: T) => T` | Identity helper for `areca.config.ts` default export |
|
|
10
|
+
| `manifestFromConfig` | `(config: SiteContentConfig) => SiteManifest` | Validates identifiers and duplicates, returns `{ collections }` |
|
|
11
|
+
| `createArecaClient` | `(options?: { cmsUrl?; siteToken?; schemaToken? }) => ArecaClient` | Falls back to `ARECA_CMS_URL`, `ARECA_SITE_TOKEN`, `ARECA_SCHEMA_TOKEN`; URL and site token required at creation |
|
|
12
|
+
| `getPreviewEntry` | `({ token: string; client?: ArecaClient }) => Promise<PreviewResult>` | `PreviewResult` = `{ collection, slug, entry }`. Needs `ARECA_CMS_URL` and `ARECA_SITE_TOKEN` (or pass `client`). |
|
|
13
|
+
| `previewHeaders` | `() => { 'X-Robots-Tag': 'noindex, nofollow'; 'Cache-Control': 'no-store' }` | Apply to preview responses |
|
|
14
|
+
| `seoHead` | `(seo: SeoMeta \| null \| undefined, options?: SeoHeadOptions) => { title, meta, links, scripts }` | TanStack Router `head()` shape |
|
|
15
|
+
| `seoHeadHtml` | `(seo, options?) => string` | Escaped `<head>` HTML |
|
|
16
|
+
| `seoJsonLd` | `(seo) => string[]` | Escaped JSON-LD strings |
|
|
17
|
+
| `richTextDocument` | `(value: RichTextValue \| null \| undefined) => MarkdownDocument` | Parses `markdown` for old snapshots; empty root for null |
|
|
18
|
+
| `richTextMarkdown` | `(value) => string` | `''` when empty |
|
|
19
|
+
| `richTextHtml` | `(value) => string` | Static HTML; trusted CMS output only |
|
|
20
|
+
|
|
21
|
+
`SeoHeadOptions` = `{ titleTemplate?: string /* contains %s */ | ((title: string) => string); siteName?: string }`.
|
|
22
|
+
|
|
23
|
+
`ArecaClient` methods (plain `fetch`, no retries or caching):
|
|
24
|
+
|
|
25
|
+
| Method | HTTP | Auth |
|
|
26
|
+
| --- | --- | --- |
|
|
27
|
+
| `fetchManifest(): Promise<SiteManifest>` | `GET /api/sites/manifest` | site token |
|
|
28
|
+
| `syncManifest(config): Promise<{ ok: true }>` | `POST /api/sites/manifest` | schema token |
|
|
29
|
+
| `fetchSnapshot(opts?: { publicationId?: string }): Promise<Snapshot>` | `GET /api/sites/snapshot/current[?publicationId=]` | site token |
|
|
30
|
+
| `fetchPreview(token: string): Promise<PreviewResult>` | `GET /api/sites/preview?token=` | signed preview token |
|
|
31
|
+
|
|
32
|
+
Types: `ArecaClient`, `ArecaClientOptions`, `SiteContentConfig`, `CollectionConfig` (`{ route; label?; fields: Field[] }`), `Field` (`{ key; label; type: FieldType; required?; options?; collection?; description?; after? }`), `FieldType`, `SiteManifest`, `Snapshot` (`{ publicationId, siteKey, createdAt, hash, collections: Record<string, SnapshotEntry[]> }`), `SnapshotEntry` (`{ id, slug, publishedAt: string | null, data: Record<string, unknown>, seo?: SeoMeta }`), `ImageValue`, `ReferenceValue`, `RichTextValue`, `SeoMeta`, `PreviewResult`, `PulledContentManifest`, `HeadMeta`, `HeadLink`, `HeadScript`, `SeoHeadOptions`, `SeoHeadResult`.
|
|
33
|
+
|
|
34
|
+
## `@areca-cms/site-sdk/pull` (Node only)
|
|
35
|
+
|
|
36
|
+
| Export | Signature | Notes |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| `pullContent` | `({ outDir: string; client?: ArecaClient; publicationId?: string }) => Promise<Snapshot>` | Writes `outDir/.publications/publication-<random>/<collection>.json`, then atomically swaps `manifest.json`, which records the `publicationId`. Defaults `publicationId` to `ARECA_SNAPSHOT_ID`. A failed pull keeps the previous generation. |
|
|
39
|
+
| `readContentManifest` | `(outDir: string) => PulledContentManifest` | `{ publicationId, hash, createdAt, collections counts, ... }` |
|
|
40
|
+
| `readContent` | `<T = SnapshotEntry>(outDir: string, collectionKey: string, manifest?: PulledContentManifest) => T[]` | Throws `No content file for collection "x" ... Run \`areca pull\` first.` |
|
|
41
|
+
|
|
42
|
+
Type: `PullContentOptions`.
|
|
43
|
+
|
|
44
|
+
## Other subpaths
|
|
45
|
+
|
|
46
|
+
- `@areca-cms/site-sdk/seo`: `seoHead`, `seoHeadHtml`, `seoJsonLd` and their types.
|
|
47
|
+
- `@areca-cms/site-sdk/rich-text`: `richTextDocument`, `richTextMarkdown`, `richTextHtml`.
|
|
48
|
+
- `@areca-cms/site-sdk/rich-text-react`: `richTextComponents(components?: MarkdownComponents) => MarkdownComponents`. Wraps `img` so editor sizes are kept. Needs `react` (optional peer) and `@tanstack/markdown@1.0.0`.
|
|
49
|
+
|
|
50
|
+
## SeoMeta (snapshot `entry.seo`, already resolved with fallbacks)
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
{
|
|
54
|
+
title: string; description: string | null; canonicalUrl: string | null;
|
|
55
|
+
robots: { index: boolean; follow: boolean; content: string };
|
|
56
|
+
focusKeyphrase: string | null;
|
|
57
|
+
openGraph: { type: 'article' | 'website'; title; description; url; siteName; image: ImageValue | null; publishedTime; modifiedTime };
|
|
58
|
+
twitter: { card: 'summary' | 'summary_large_image'; title; description; image: ImageValue | null };
|
|
59
|
+
author: { name; url; bio } | null;
|
|
60
|
+
summary: string | null; keyTakeaways: string[];
|
|
61
|
+
faq: { question; answer }[]; about: { name; sameAs }[];
|
|
62
|
+
jsonLd: Record<string, unknown>[]; // ready-to-embed objects
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`seo` is missing on snapshots from older CMS versions. `seoHead` and `seoHeadHtml` then return empty head data.
|
|
67
|
+
|
|
68
|
+
`PulledContentManifest` = `{ publicationId, hash, createdAt, collections: Record<string, number>, generation? }`.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Preview, Go-live builds and errors
|
|
2
|
+
|
|
3
|
+
## CMS settings the site needs (asked of an owner or agency admin)
|
|
4
|
+
|
|
5
|
+
- **Production URL** (site settings). This enables preview links: the CMS opens `<productionUrl>/__preview/<collection>/<slug>?token=<signed>`.
|
|
6
|
+
- **Publishing mode**: `Demo` skips deploy and health checks, which is good for trying things out. `Production` needs a deployment target (Dokploy) and a **health check URL**.
|
|
7
|
+
- **Site key and tokens**: Settings > Developer. Rotating a token invalidates the old one immediately.
|
|
8
|
+
|
|
9
|
+
## Draft preview route
|
|
10
|
+
|
|
11
|
+
Server-side only. The preview token is signed, valid for about 1 hour, and separate from the site token. Never log, cache or store it.
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { getPreviewEntry, previewHeaders } from "@areca-cms/site-sdk";
|
|
15
|
+
|
|
16
|
+
// route: /__preview/$collection/$slug (read ?token= from the URL)
|
|
17
|
+
try {
|
|
18
|
+
const { collection, slug, entry } = await getPreviewEntry({ token });
|
|
19
|
+
// render `entry` (same shape as SnapshotEntry) with the normal template
|
|
20
|
+
// and set every header from previewHeaders() plus <meta name="robots" content="noindex">
|
|
21
|
+
} catch (error) {
|
|
22
|
+
// Plain Error, no status field. The message holds the status:
|
|
23
|
+
// "Failed to fetch preview (401): …" => link expired or invalid, ask for a new one
|
|
24
|
+
// "(404)" => the entry no longer exists
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`getPreviewEntry` needs `ARECA_CMS_URL` and `ARECA_SITE_TOKEN` at runtime. Exclude `/__preview` from prerendering and CDN caching. In TanStack Start, wrap the fetch in a `createServerFn` handler.
|
|
29
|
+
|
|
30
|
+
## Health route (Production mode)
|
|
31
|
+
|
|
32
|
+
Go live waits until the deployed site reports the publication it was built from:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
// GET /api/health -> { ok: true, publicationId } or 503
|
|
36
|
+
import { readContentManifest } from "@areca-cms/site-sdk/pull";
|
|
37
|
+
|
|
38
|
+
export function health() {
|
|
39
|
+
try {
|
|
40
|
+
const { publicationId } = readContentManifest("src/generated/content");
|
|
41
|
+
if (!publicationId) throw new Error("missing");
|
|
42
|
+
return Response.json({ ok: true, publicationId });
|
|
43
|
+
} catch {
|
|
44
|
+
return Response.json({ ok: false }, { status: 503 });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Set the CMS health check URL to this route, e.g. `https://www.example.com/api/health`.
|
|
50
|
+
|
|
51
|
+
## Builds (Docker / CI)
|
|
52
|
+
|
|
53
|
+
- Pull **before** the build: `"prebuild": "areca pull"` or an explicit step.
|
|
54
|
+
- The CMS deploy passes the build arg `ARECA_SNAPSHOT_ID`, and `areca pull` fetches exactly that publication. Declare the arg right before the pull step so the cache is busted for each release.
|
|
55
|
+
- Pass `ARECA_SITE_TOKEN` as a build secret, not as an ARG or ENV in the image.
|
|
56
|
+
- Copy `src/generated` into the runtime image when the server reads content at runtime.
|
|
57
|
+
|
|
58
|
+
```dockerfile
|
|
59
|
+
ARG ARECA_CMS_URL
|
|
60
|
+
ARG ARECA_SNAPSHOT_ID
|
|
61
|
+
RUN --mount=type=secret,id=ARECA_SITE_TOKEN,required=true \
|
|
62
|
+
ARECA_SITE_TOKEN="$(cat /run/secrets/ARECA_SITE_TOKEN)" pnpm build # build runs areca pull via prebuild
|
|
63
|
+
# runtime stage:
|
|
64
|
+
COPY --from=build /app/src/generated ./src/generated
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Client and errors
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import { createArecaClient } from "@areca-cms/site-sdk";
|
|
71
|
+
import { pullContent } from "@areca-cms/site-sdk/pull";
|
|
72
|
+
|
|
73
|
+
const client = createArecaClient({ cmsUrl, siteToken }); // or env vars
|
|
74
|
+
await pullContent({ outDir: "src/generated/content", client });
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
| Error | Fix |
|
|
78
|
+
| --- | --- |
|
|
79
|
+
| `Missing ARECA_CMS_URL` / `ARECA_SITE_TOKEN` | Set them in env or `.env.local` (CLI) |
|
|
80
|
+
| Missing `ARECA_SCHEMA_TOKEN` on push | Add it to the authoring env only |
|
|
81
|
+
| `401` | Wrong or rotated token, or the wrong token type (schema vs site) |
|
|
82
|
+
| `404` / no publication on pull | Nothing is live yet. Go live in the CMS, then pull again. |
|
|
83
|
+
| `429` | 300 requests/min per token. Wait for `Retry-After`. |
|
|
84
|
+
| `400` / `413` on push | Invalid schema, or the manifest is over 256 KiB |
|
|
85
|
+
| `No content file for collection` | Run `areca pull`, then check the collection key |
|
|
86
|
+
| `Unsafe collection or field identifier` | Rename the key to match `^[A-Za-z][A-Za-z0-9_-]{0,63}$` |
|
|
87
|
+
| `Expected publication X, received Y` | Pinned pull mismatch. Check `ARECA_SNAPSHOT_ID`. |
|
|
88
|
+
| TS config import fails | Use Node 24, or run through `tsx` |
|