@areca-cms/skills 0.4.0 → 0.5.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/CHANGELOG.md +7 -0
- package/README.md +2 -1
- package/dist/cli.js +3 -1
- package/dist/skills.d.ts +8 -3
- package/dist/skills.js +19 -12
- package/package.json +1 -1
- package/skills/areca-mcp/SKILL.md +19 -5
- package/skills/areca-mcp/references/languages.md +86 -0
- package/skills/areca-mcp/references/tools.md +21 -12
- package/skills/areca-quickstart/SKILL.md +245 -0
- package/skills/areca-quickstart/references/connect.md +123 -0
- package/skills/areca-quickstart/references/first-release.md +181 -0
- package/skills/areca-quickstart/references/frameworks.md +533 -0
- package/skills/areca-quickstart/references/interview.md +169 -0
- package/skills/areca-quickstart/references/recipes.md +243 -0
- package/skills/areca-sdk/SKILL.md +15 -10
- package/skills/areca-sdk/references/api.md +17 -9
- package/skills/areca-sdk/references/languages.md +177 -0
- package/skills/areca-sdk/references/production.md +1 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Interview
|
|
2
|
+
|
|
3
|
+
Ask four batches, one at a time. Wait for the answer before you ask the next batch.
|
|
4
|
+
|
|
5
|
+
Rules for every batch:
|
|
6
|
+
|
|
7
|
+
- Number the questions. The user answers by number, for example "1 yes, 2 nl, 3 defaults".
|
|
8
|
+
- Show the default in brackets for each question.
|
|
9
|
+
- Say that the user can reply `defaults` to accept every default in the batch.
|
|
10
|
+
- Skip a question when you already detected the answer. Show the detected value and ask "correct?" instead.
|
|
11
|
+
- Keep each question to one line where you can.
|
|
12
|
+
|
|
13
|
+
## Batch A: Project
|
|
14
|
+
|
|
15
|
+
Send this when step 0 is done. Fill in the detected values.
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
A. Project
|
|
19
|
+
|
|
20
|
+
1. CMS URL? [detected: https://cms.example.com, or none. Local dev: http://localhost:3000]
|
|
21
|
+
2. Site key (the CMS slug, lowercase, e.g. acme-site)? [detected: from areca.config.ts, or ask]
|
|
22
|
+
3. Does this site exist in the CMS yet? [no]
|
|
23
|
+
If no, I will walk you through creating it. You will need the production URL field: use http://localhost:<dev port> for now.
|
|
24
|
+
4. Framework: <detected>. Correct? [yes]
|
|
25
|
+
5. Package manager: <detected>. Correct? [yes]
|
|
26
|
+
6. Dev port: <detected, e.g. 3001>. [keep]
|
|
27
|
+
7. Content folder for pulled content? [src/generated/content]
|
|
28
|
+
8. Types file (areca.gen.ts)? [src/generated/content/areca.gen.ts]
|
|
29
|
+
9. Commit the generated types file? [yes; the pulled JSON stays out of Git]
|
|
30
|
+
10. Download images and files into the repo on pull? [yes]
|
|
31
|
+
10a. Folder for them? [public/areca]
|
|
32
|
+
10b. URL path the site serves that folder from? [only asked when the folder is not under public/]
|
|
33
|
+
11. Schema source: connect the CMS over MCP (the schema is created in the CMS, then saved to areca.config.ts) or write areca.config.ts in code? [MCP, if you are an agency admin; otherwise code]
|
|
34
|
+
|
|
35
|
+
Reply "defaults" to accept all of A.
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Notes for the agent:
|
|
39
|
+
|
|
40
|
+
- Q3: if the site does not exist, read the "Create the site" section of `connect.md` before you go on.
|
|
41
|
+
- Q11: if MCP is not connected, ask only this: "Do you want to connect the CMS over MCP now?" If yes, go to step 1. If no, code.
|
|
42
|
+
- If the CMS URL is `localhost`, say that a remote CMS needs its own site token.
|
|
43
|
+
|
|
44
|
+
## Batch B: Content model
|
|
45
|
+
|
|
46
|
+
Ask what the site needs. Then go through each item with its recipe in `recipes.md`.
|
|
47
|
+
|
|
48
|
+
```text
|
|
49
|
+
B. Content model
|
|
50
|
+
|
|
51
|
+
1. What does the website need? Name the content in your own words.
|
|
52
|
+
Examples: blog posts, team members, vacancies, services, case studies, events, FAQ.
|
|
53
|
+
2. Which company pages does the site need?
|
|
54
|
+
[company info, home page, about page, footer]
|
|
55
|
+
3. Do you want any of these extras? [all yes]
|
|
56
|
+
a. Categories for blog posts (a collection)
|
|
57
|
+
b. Authors for blog posts (a collection, used as a reference)
|
|
58
|
+
c. Departments for vacancies (a collection, used as a reference)
|
|
59
|
+
d. Navigation links (a collection, see the note in recipes.md)
|
|
60
|
+
e. SEO on the home and about pages (on by default)
|
|
61
|
+
4. Routes: each collection gets a route. These are the defaults from the recipe, for example /blog/$slug and /vacancies/$slug. Change them?
|
|
62
|
+
5. Anything that does not fit the recipes? Describe the fields in your own words. I will map them to field types.
|
|
63
|
+
6. Company details for the company info section: name, email, phone and address. [name: the client's name, the rest empty]
|
|
64
|
+
Give only what you know. I will leave the others empty. I will not guess a founding year, a phone number or an address.
|
|
65
|
+
|
|
66
|
+
Reply "defaults" to accept the recipe fields and all the extras in B (the company details stay empty except the name).
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
For each item the user names:
|
|
70
|
+
|
|
71
|
+
1. Find its recipe in `recipes.md`. If no recipe fits, build one from the field types in `areca-sdk`.
|
|
72
|
+
2. Show the default fields in a short list. Ask: "Keep these, add, or remove?" Ask only one question per item.
|
|
73
|
+
3. Note the translatable choice for each text field. Use the recipe defaults.
|
|
74
|
+
|
|
75
|
+
Do not invent extra fields. Add what the user asked for and the recipe defaults.
|
|
76
|
+
- A declined extra removes the fields that point to it. For example, no authors means no `author` field on posts. Show each removal in the plan.
|
|
77
|
+
- Never invent a fact. A value the user did not give stays empty when the field is optional.
|
|
78
|
+
|
|
79
|
+
## Batch C: Languages
|
|
80
|
+
|
|
81
|
+
Skip this batch when the user said "single language" in batch B or when the project has no i18n. The default is one language.
|
|
82
|
+
|
|
83
|
+
```text
|
|
84
|
+
C. Languages
|
|
85
|
+
|
|
86
|
+
1. One language or several? [one]
|
|
87
|
+
2. If several: the main language (the fallback) and all languages in display order?
|
|
88
|
+
[main: <detected or en>, all: <main> only]
|
|
89
|
+
3. Route for each collection in each extra language. Default: the same route in every language. The site adds the language prefix itself (for example /nl/blog/...). Ask per collection only when the user wants different words, for example /journal in English and /journaal in Dutch.
|
|
90
|
+
4. Which languages must be complete before an item can be marked ready? [all extra languages, for every collection and section]
|
|
91
|
+
5. Which fields are the same in every language (names, prices, contact details, URLs, emails)? [the recipe defaults]
|
|
92
|
+
6. i18n approach:
|
|
93
|
+
- If the project already uses a library, use it. Only Paraglide is wired by the SDK. For another library, the content is read with an explicit locale.
|
|
94
|
+
- If the project has none: Paraglide is recommended. It needs a new package, and the recipe is in `frameworks.md` (TanStack Start is verified). Install it only if you say yes.
|
|
95
|
+
The lighter alternative is the explicit locale: no new package, and the locale is passed to each read.
|
|
96
|
+
[Paraglide]
|
|
97
|
+
|
|
98
|
+
Reply "defaults" to accept all of C (one language, so C is skipped).
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Notes for the agent:
|
|
102
|
+
|
|
103
|
+
- Languages cannot be turned off once set in the CMS. Say so before Q2 is confirmed.
|
|
104
|
+
- Do not install any i18n library without a yes to Q6.
|
|
105
|
+
|
|
106
|
+
## Batch D: Build and preview
|
|
107
|
+
|
|
108
|
+
```text
|
|
109
|
+
D. Build and preview
|
|
110
|
+
|
|
111
|
+
1. Pages for each collection:
|
|
112
|
+
a. Connect to existing pages. I change the data source only.
|
|
113
|
+
b. Build simple pages (list and detail) using the site's own components. No styling beyond what the site uses.
|
|
114
|
+
c. Skip for now.
|
|
115
|
+
[b when the project has no pages for the collection, otherwise a]
|
|
116
|
+
2. Connect the home page and the about page to their sections? [yes, when the plan has them]
|
|
117
|
+
3. Draft preview route (so editors can see drafts in the site)? [yes]
|
|
118
|
+
It needs the production URL in the CMS. A localhost URL is fine for development.
|
|
119
|
+
4. Run a content pull before every build and before the dev server? [yes, prebuild only; predev only after the first release]
|
|
120
|
+
5. Will you deploy with go-live builds (Dokploy) later? [no]
|
|
121
|
+
If yes, I add the health route now. Read the areca-sdk skill, references/production.md.
|
|
122
|
+
6. First release mode: demo or production? [demo]
|
|
123
|
+
Demo does not deploy and never shows to visitors. Production needs a deployment target and a health check URL. Use demo for the first release.
|
|
124
|
+
|
|
125
|
+
Reply "defaults" to accept all of D.
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Plan format
|
|
129
|
+
|
|
130
|
+
After batch D, show the plan. Keep it short. Then ask: "Reply yes to confirm, or tell me what to change."
|
|
131
|
+
|
|
132
|
+
```text
|
|
133
|
+
Plan
|
|
134
|
+
|
|
135
|
+
Project
|
|
136
|
+
- Framework: TanStack Start, pnpm, dev port 3001
|
|
137
|
+
- Site key: acme-site (exists in the CMS: yes)
|
|
138
|
+
- CMS: https://cms.example.com (schema over MCP)
|
|
139
|
+
|
|
140
|
+
Content folder
|
|
141
|
+
- src/generated/content (types committed, media in public/areca, downloaded on pull)
|
|
142
|
+
|
|
143
|
+
Collections
|
|
144
|
+
- posts: /blog/$slug, 9 fields, author -> authors
|
|
145
|
+
- authors: /team/$slug, 5 fields
|
|
146
|
+
- ...
|
|
147
|
+
|
|
148
|
+
Sections
|
|
149
|
+
- companyInfo (no SEO), homePage (SEO, preview /), aboutPage (SEO, preview /about)
|
|
150
|
+
|
|
151
|
+
Languages
|
|
152
|
+
- none, or: en (main), nl. Routes: /blog/$slug and /nl/blog/$slug. Required: nl.
|
|
153
|
+
|
|
154
|
+
i18n
|
|
155
|
+
- none, or Paraglide (needs your yes to install)
|
|
156
|
+
|
|
157
|
+
Pages
|
|
158
|
+
- posts: list and detail, simple pages
|
|
159
|
+
- home and about: connected to their sections
|
|
160
|
+
|
|
161
|
+
Preview route: yes. Prebuild pull: yes.
|
|
162
|
+
First release: demo mode.
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Keep the plan to the facts. Mark every item the user has not confirmed.
|
|
166
|
+
|
|
167
|
+
## Recording answers
|
|
168
|
+
|
|
169
|
+
Keep the answers in your working notes for the rest of the run. Re-read them before step 3 and step 4. If the user changes an answer later, update the plan and show the changed lines only.
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# Recipes
|
|
2
|
+
|
|
3
|
+
Starting points for collections and sections. Use them in batch B and in the approval table (step 3). Adapt the fields to the client. Keep the list short.
|
|
4
|
+
|
|
5
|
+
Field rules that apply to every recipe (see the `areca-sdk` skill for the full list):
|
|
6
|
+
|
|
7
|
+
- Keys (collections, sections and fields) start with a letter. They use letters, digits, `_` or `-`, and are 64 characters at most.
|
|
8
|
+
- Collection and section keys cannot be `resolve`, `href`, `preview`, `globals`, `previewGlobals`, `in`, `languages` or `matchLocale`, in any case. Field keys may use them. Also avoid `manifest`, `constructor`, `prototype` and the Windows device names (`con`, `aux`, `nul`, `com1` and so on).
|
|
9
|
+
- Every collection needs a `route` with `$slug`. Even a collection with no page (for example testimonials) needs one.
|
|
10
|
+
- Every collection gets a `slug` field. Add it in the config, as the examples do.
|
|
11
|
+
- Text, textarea, richtext and url are translatable by default on a multilingual site. Names, prices, contact details and shop or apply links are usually not. Mark them `translatable: false`.
|
|
12
|
+
- The SDK has no list field. Use one of these instead: a collection with a number field for order, a reference, or several plain fields.
|
|
13
|
+
- `group` works only on sections (globals), not on collection fields.
|
|
14
|
+
- A `date` or `datetime` field is content only. It does not schedule an entry.
|
|
15
|
+
- When the user declines an extra (for example authors or departments), remove every field that points to it (`posts.author`, `vacancies.department`, `posts.category`, `homePage.featuredPost`). Show each removal in the approval table, for example "removed: author (authors declined)".
|
|
16
|
+
- Never invent facts for a field (a founding year, a phone number, an address, a salary). If the interview did not give the value, leave an optional field empty. Use placeholder text only for copy, and say so.
|
|
17
|
+
|
|
18
|
+
Field lines below use: `key` (type, required, notes). `slug` is always `slug` (type slug, required).
|
|
19
|
+
|
|
20
|
+
## Collections
|
|
21
|
+
|
|
22
|
+
### Blog posts
|
|
23
|
+
|
|
24
|
+
Collection `posts`. Route `/blog/$slug`. Label "Blog posts" (or the client's word, such as "News" or "Journal").
|
|
25
|
+
|
|
26
|
+
- `slug` (slug, required)
|
|
27
|
+
- `title` (text, required)
|
|
28
|
+
- `excerpt` (textarea), short summary for lists and SEO
|
|
29
|
+
- `body` (richtext, required)
|
|
30
|
+
- `coverImage` (image)
|
|
31
|
+
- `category` (select: `news`, `guides`, `updates`). Use a reference to categories instead if categories need their own page.
|
|
32
|
+
- `tags` (text). Comma-separated, with a description that says so. There is no list type.
|
|
33
|
+
- `author` (reference to `authors`)
|
|
34
|
+
- `publishedAt` (datetime). Content only, does not schedule.
|
|
35
|
+
|
|
36
|
+
Suggested extras: categories, authors, a home page section that features the latest post.
|
|
37
|
+
|
|
38
|
+
### Categories
|
|
39
|
+
|
|
40
|
+
Collection `categories`. Route `/blog/category/$slug`.
|
|
41
|
+
|
|
42
|
+
- `slug` (slug, required)
|
|
43
|
+
- `name` (text, required)
|
|
44
|
+
- `description` (textarea)
|
|
45
|
+
|
|
46
|
+
### Authors (team members)
|
|
47
|
+
|
|
48
|
+
Collection `authors`. Route `/team/$slug`. Label "Team".
|
|
49
|
+
|
|
50
|
+
- `slug` (slug, required)
|
|
51
|
+
- `name` (text, required, `translatable: false`)
|
|
52
|
+
- `role` (text)
|
|
53
|
+
- `bio` (textarea)
|
|
54
|
+
- `portrait` (image)
|
|
55
|
+
|
|
56
|
+
Used by posts (author reference) and projects.
|
|
57
|
+
|
|
58
|
+
### Vacancies (job openings)
|
|
59
|
+
|
|
60
|
+
Collection `vacancies`. Route `/vacancies/$slug`. Label "Vacancies".
|
|
61
|
+
|
|
62
|
+
- `slug` (slug, required)
|
|
63
|
+
- `title` (text, required)
|
|
64
|
+
- `summary` (textarea, required). Shown on the list page and in SEO.
|
|
65
|
+
- `description` (richtext, required). Role, tasks, requirements.
|
|
66
|
+
- `department` (reference to `departments`)
|
|
67
|
+
- `location` (text). For example "Amsterdam (hybrid)".
|
|
68
|
+
- `employmentType` (select: `full-time`, `part-time`, `contract`, `internship`, `freelance`). Shared across languages.
|
|
69
|
+
- `hoursPerWeek` (number)
|
|
70
|
+
- `salaryRange` (text, `translatable: false`). For example "3,000 to 3,800 EUR per month". Keep it as text, the SDK has no currency type.
|
|
71
|
+
- `remote` (boolean). Open to remote work.
|
|
72
|
+
- `startDate` (date)
|
|
73
|
+
- `closingDate` (date). When it closes, the page can say "closed".
|
|
74
|
+
- `applyUrl` (url, `translatable: false`). Link to the applicant tracking system.
|
|
75
|
+
- `applyEmail` (text, `translatable: false`)
|
|
76
|
+
|
|
77
|
+
Suggested extras: departments, and a `/vacancies` list page on the site. Entries already have SEO in the CMS, so there is no SEO flag to set here.
|
|
78
|
+
|
|
79
|
+
### Departments
|
|
80
|
+
|
|
81
|
+
Collection `departments`. Route `/departments/$slug`.
|
|
82
|
+
|
|
83
|
+
- `slug` (slug, required)
|
|
84
|
+
- `name` (text, required)
|
|
85
|
+
- `summary` (textarea)
|
|
86
|
+
- `body` (richtext)
|
|
87
|
+
- `contactEmail` (text, `translatable: false`)
|
|
88
|
+
|
|
89
|
+
### Services
|
|
90
|
+
|
|
91
|
+
Collection `services`. Route `/services/$slug`.
|
|
92
|
+
|
|
93
|
+
- `slug` (slug, required)
|
|
94
|
+
- `title` (text, required)
|
|
95
|
+
- `summary` (textarea)
|
|
96
|
+
- `body` (richtext)
|
|
97
|
+
- `icon` (image)
|
|
98
|
+
- `order` (number). Lower numbers show first. Use `orderBy` in the code.
|
|
99
|
+
|
|
100
|
+
### Projects and case studies
|
|
101
|
+
|
|
102
|
+
Collection `projects`. Route `/work/$slug`. Label "Projects" or "Work".
|
|
103
|
+
|
|
104
|
+
- `slug` (slug, required)
|
|
105
|
+
- `title` (text, required)
|
|
106
|
+
- `client` (text). The client name in the project. Use `translatable: false` if it is a name.
|
|
107
|
+
- `summary` (textarea)
|
|
108
|
+
- `coverImage` (image)
|
|
109
|
+
- `body` (richtext)
|
|
110
|
+
- `completedOn` (date)
|
|
111
|
+
- `services` (reference to `services`)
|
|
112
|
+
|
|
113
|
+
### Testimonials
|
|
114
|
+
|
|
115
|
+
Collection `testimonials`. Route `/testimonials/$slug`. Even when the site shows them on one page, the route is still required.
|
|
116
|
+
|
|
117
|
+
- `slug` (slug, required)
|
|
118
|
+
- `quote` (textarea, required)
|
|
119
|
+
- `name` (text, required, `translatable: false`)
|
|
120
|
+
- `role` (text)
|
|
121
|
+
- `company` (text, `translatable: false`)
|
|
122
|
+
- `photo` (image)
|
|
123
|
+
- `order` (number)
|
|
124
|
+
|
|
125
|
+
### FAQ
|
|
126
|
+
|
|
127
|
+
Collection `faqs`. Route `/faq/$slug`.
|
|
128
|
+
|
|
129
|
+
- `slug` (slug, required)
|
|
130
|
+
- `question` (text, required)
|
|
131
|
+
- `answer` (richtext, required)
|
|
132
|
+
- `order` (number)
|
|
133
|
+
|
|
134
|
+
### Events
|
|
135
|
+
|
|
136
|
+
Collection `events`. Route `/events/$slug`.
|
|
137
|
+
|
|
138
|
+
- `slug` (slug, required)
|
|
139
|
+
- `title` (text, required)
|
|
140
|
+
- `summary` (textarea)
|
|
141
|
+
- `body` (richtext)
|
|
142
|
+
- `coverImage` (image)
|
|
143
|
+
- `startsAt` (datetime, required)
|
|
144
|
+
- `endsAt` (datetime, `after: "startsAt"`)
|
|
145
|
+
- `location` (text)
|
|
146
|
+
- `registrationUrl` (url, `translatable: false`)
|
|
147
|
+
|
|
148
|
+
### Navigation links (optional)
|
|
149
|
+
|
|
150
|
+
Collection `navigationLinks`. Route `/navigation/$slug`. The site never renders this as a page. It only reads the links.
|
|
151
|
+
|
|
152
|
+
- `slug` (slug, required)
|
|
153
|
+
- `label` (text, required)
|
|
154
|
+
- `url` (url, `translatable: false`)
|
|
155
|
+
- `order` (number)
|
|
156
|
+
|
|
157
|
+
Mention the trade-off to the user. A menu that rarely changes is simpler in code. Use this collection when editors must change the menu themselves.
|
|
158
|
+
|
|
159
|
+
## Sections (site content)
|
|
160
|
+
|
|
161
|
+
Sections are single sets of values. They have no slug and no route. Set `seo: true` only on pages that have their own SEO (home, about). Set `previewPath` to the page that shows the section, so editors can preview drafts there.
|
|
162
|
+
|
|
163
|
+
### Company info
|
|
164
|
+
|
|
165
|
+
Section `companyInfo`. Label "Company info". No SEO.
|
|
166
|
+
|
|
167
|
+
Ask the user for the company details in batch B: name, email, phone and address. Use only what they give. Leave the others empty. Do not guess a founding year, a phone number or an address.
|
|
168
|
+
|
|
169
|
+
- `name` (text, required, `translatable: false`)
|
|
170
|
+
- `tagline` (textarea). The site's default description in the root head.
|
|
171
|
+
- `email` (text, `translatable: false`, `group: "Contact"`)
|
|
172
|
+
- `phone` (text, `translatable: false`, `group: "Contact"`)
|
|
173
|
+
- `address` (textarea, `group: "Contact"`)
|
|
174
|
+
- `mapUrl` (url, `translatable: false`, `group: "Contact"`)
|
|
175
|
+
- `instagramUrl` (url, `translatable: false`, `group: "Social"`)
|
|
176
|
+
- `linkedinUrl` (url, `translatable: false`, `group: "Social"`)
|
|
177
|
+
- `foundedYear` (number)
|
|
178
|
+
|
|
179
|
+
### Home page
|
|
180
|
+
|
|
181
|
+
Section `homePage`. Label "Home page". `seo: true`. `previewPath: "/"`.
|
|
182
|
+
|
|
183
|
+
- `heroEyebrow` (text)
|
|
184
|
+
- `heroTitle` (text, required)
|
|
185
|
+
- `heroIntro` (textarea, required)
|
|
186
|
+
- `heroImage` (image)
|
|
187
|
+
- `ctaLabel` (text)
|
|
188
|
+
- `ctaUrl` (url, `translatable: false`)
|
|
189
|
+
- `featuredPost` (reference to `posts`), when the plan has posts
|
|
190
|
+
|
|
191
|
+
### About page
|
|
192
|
+
|
|
193
|
+
Section `aboutPage`. Label "About page". `seo: true`. `previewPath: "/about"`.
|
|
194
|
+
|
|
195
|
+
- `heading` (text, required)
|
|
196
|
+
- `body` (richtext, required)
|
|
197
|
+
- `image` (image)
|
|
198
|
+
|
|
199
|
+
### Footer
|
|
200
|
+
|
|
201
|
+
Section `footer`. Label "Footer". No SEO. Keep it small.
|
|
202
|
+
|
|
203
|
+
- `footerNote` (textarea). Short line under the links.
|
|
204
|
+
- `copyrightName` (text). Shown after the year.
|
|
205
|
+
- `legalNotice` (richtext). Terms or imprint text.
|
|
206
|
+
|
|
207
|
+
Use `companyInfo` for the address and contact fields. Do not repeat them.
|
|
208
|
+
|
|
209
|
+
## SEO defaults
|
|
210
|
+
|
|
211
|
+
- Every entry has SEO in the CMS. Editors fill it in, and the SDK gives `entry.seo`. Use `seoHead` or `seoHeadHtml` in every entry page.
|
|
212
|
+
- Sections get SEO only with `seo: true`. Use it for home and about. Read the value from `content.globals.<key>.$seo`.
|
|
213
|
+
- The site name and the default description come from `companyInfo`. Use them in the root layout.
|
|
214
|
+
- Do not add SEO fields as collection fields. Use the CMS SEO panel.
|
|
215
|
+
|
|
216
|
+
## Routes and languages
|
|
217
|
+
|
|
218
|
+
A collection has one `route` in the main language, for example `/blog/$slug`. A language that uses other words gets its own pattern in `routes`, for example `routes: { nl: '/vacatures/$slug' }`. Read from the SDK source (`packages/site-sdk/src/content.ts`):
|
|
219
|
+
|
|
220
|
+
- The SDK uses `route` and each `routes[locale]` value exactly as written. It never adds a language prefix. Write `/vacatures/$slug`, not `/nl/vacatures/$slug`. A prefix in `routes` is doubled once the site adds `/nl`.
|
|
221
|
+
- `content.posts.href(slug)` returns the route of the language in use, with `$slug` replaced. Example: `content.posts.href('my-post')` gives `/blog/my-post`. `content.in('nl').vacancies.href('dev')` gives `/vacatures/dev`. A string slug uses the language from `in(locale)` if set, else the locale function (Paraglide's `getLocale` when it is wired), else the main language. `href(entry)` uses the entry's own language.
|
|
222
|
+
- `entry.alternates` maps each complete language tag to its route, again with no prefix: `{ en: '/vacancies/dev', nl: '/vacatures/dev' }`.
|
|
223
|
+
- The site adds the prefix. The main language has none. Every other language is `/<tag>` followed by the route, so the Dutch vacancy is `/nl/vacatures/dev`. With Paraglide, the router rewrite does this (see [frameworks.md](frameworks.md)). Without Paraglide, add the prefix in code.
|
|
224
|
+
|
|
225
|
+
**The rule:** `href()` and `alternates` never include the language prefix. Add `/<tag>` yourself for every language except the main one.
|
|
226
|
+
|
|
227
|
+
## Route suggestions
|
|
228
|
+
|
|
229
|
+
| Collection | Route | Page type |
|
|
230
|
+
| --- | --- | --- |
|
|
231
|
+
| posts | `/blog/$slug` | list at `/blog`, detail |
|
|
232
|
+
| categories | `/blog/category/$slug` | list of posts, filtered |
|
|
233
|
+
| authors | `/team/$slug` | list at `/team`, detail |
|
|
234
|
+
| vacancies | `/vacancies/$slug` | list at `/vacancies`, detail |
|
|
235
|
+
| departments | `/departments/$slug` | detail, linked from vacancies |
|
|
236
|
+
| services | `/services/$slug` | list at `/services`, detail |
|
|
237
|
+
| projects | `/work/$slug` | list at `/work`, detail |
|
|
238
|
+
| testimonials | `/testimonials/$slug` | route kept, shown on home |
|
|
239
|
+
| faqs | `/faq/$slug` | list at `/faq` |
|
|
240
|
+
| events | `/events/$slug` | list at `/events`, detail |
|
|
241
|
+
| navigationLinks | `/navigation/$slug` | none (read only) |
|
|
242
|
+
|
|
243
|
+
Sections have no route. Their pages are `/` (home), `/about`, and the footer appears on every page.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
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, site content globals and field types, the areca CLI (init, manifest push/pull, pull, run), typed content from the generated areca.gen.ts (import { 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 or areca.gen.ts, mentions ARECA_* env vars, or the user wants to add, render or deploy Areca CMS content.
|
|
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, site content globals and field types, the areca CLI (init, manifest push/pull, pull, run), typed content from the generated areca.gen.ts (import { content }), rich text, SEO head tags, multilingual sites (languages, locales, hreflang), draft preview routes and Go-live builds. Use when a project imports @areca-cms/site-sdk, has an areca.config.ts or areca.gen.ts, mentions ARECA_* env vars, or the user wants to add, render or deploy Areca CMS content.
|
|
4
4
|
license: MIT
|
|
5
5
|
metadata:
|
|
6
|
-
version: "0.
|
|
6
|
+
version: "0.5.0"
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Areca site SDK
|
|
@@ -15,12 +15,12 @@ Requirements: Node >= 22.6 (24 LTS recommended), ESM only. It works with any fra
|
|
|
15
15
|
## Mental model
|
|
16
16
|
|
|
17
17
|
1. `areca.config.ts` declares **collections** (content types), **globals** (single site content sections) and their **fields**.
|
|
18
|
-
2. `areca manifest push` sends that schema to the CMS (needs the schema token).
|
|
18
|
+
2. `areca manifest push` sends that schema to the CMS (needs the schema token). It is refused while a removed field still holds content, until the removal is confirmed.
|
|
19
19
|
3. Editors create entries in the CMS and **Go live**, which creates an immutable **publication** (snapshot) with the schema embedded.
|
|
20
20
|
4. `areca pull` downloads the publication into `src/generated/content/` and writes `src/generated/content/areca.gen.ts`.
|
|
21
21
|
5. Server code reads it through the typed `content` object from `areca.gen.ts`: `import { content } from "../generated/content/areca.gen"`.
|
|
22
22
|
|
|
23
|
-
Typing is generated, not hand-written: after a pull, every collection, field, value, select option, reference and route is typed and autocompletes. Commit `areca.gen.ts`; never commit the pulled JSON. Single sections of values, such as company details or the home page, are site content (`globals`). There are no blocks,
|
|
23
|
+
Typing is generated, not hand-written: after a pull, every collection, field, value, select option, reference and route is typed and autocompletes. Commit `areca.gen.ts`; never commit the pulled JSON. Single sections of values, such as company details or the home page, are site content (`globals`). There are no blocks, webhooks or HTTP query API. A "page" is an entry in a collection, or a site content section. Queries run in memory on the pulled files, with `where`, `orderBy`, `limit`, `offset` and `with`.
|
|
24
24
|
|
|
25
25
|
## Setup
|
|
26
26
|
|
|
@@ -36,13 +36,13 @@ pnpm exec areca init --cms-url https://cms.example.com --token "$ARECA_SITE_TOKE
|
|
|
36
36
|
| Env var | Needed for | Where the user gets it |
|
|
37
37
|
| --- | --- | --- |
|
|
38
38
|
| `ARECA_CMS_URL` | everything | The CMS base URL, e.g. `https://cms.example.com` |
|
|
39
|
-
| `ARECA_SITE_TOKEN` | pull, client
|
|
39
|
+
| `ARECA_SITE_TOKEN` | reads (pull, `manifest pull`, the client's snapshot calls) | CMS: site Settings > Developer > build token (shown once; rotate to get a new one) |
|
|
40
40
|
| `ARECA_SCHEMA_TOKEN` | `manifest push` only | Settings > Developer > Rotate schema token. Keep it in the authoring env only, never in builds. |
|
|
41
41
|
| `ARECA_SITE_KEY` | `manifest pull` without a config | The site key shown in Settings |
|
|
42
42
|
| `ARECA_SNAPSHOT_ID` | pinned pulls during Go-live builds | Set by the CMS deploy; empty or `0` means current live |
|
|
43
43
|
| `ARECA_CONTENT_DIR` | the generated `content` object at run time | Optional. The content folder, when it is not the folder that was pulled into |
|
|
44
44
|
|
|
45
|
-
The CLI loads `.env.local` and then `.env`, and existing process env wins. Library imports do **not** load env files.
|
|
45
|
+
The CLI loads `.env.local` and then `.env`, and existing process env wins. Library imports do **not** load env files. Each call reads only the credential it sends: a schema push needs the schema token but not the site token, and a preview needs neither (its token is signed).
|
|
46
46
|
|
|
47
47
|
**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 the content object, `readContent` and the client in server or build code.
|
|
48
48
|
|
|
@@ -83,7 +83,8 @@ export default defineSiteContent({
|
|
|
83
83
|
- Collection keys cannot be `resolve`, `href`, `preview`, `globals` or `previewGlobals` (any case, and `preview-globals` too), because the content object uses those names. Field keys may use them.
|
|
84
84
|
- In code, keys are camelCase: `blog-posts` is `content.blogPosts`, `cover_image` is `entry.data.coverImage`. The pull fails when two keys collide after this conversion (`blog-posts` and `blog_posts`).
|
|
85
85
|
- 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.
|
|
86
|
-
- `areca manifest pull --config areca.config.ts [--yes]` regenerates the config from the CMS. It replaces the whole file and keeps `siteKey` and `assets`.
|
|
86
|
+
- `areca manifest pull --config areca.config.ts [--yes]` regenerates the config from the CMS. It replaces the whole file and keeps `siteKey` and `assets`. It prints the changes first. It asks (`--yes` without a TTY) only when something would be removed: a collection, a site content section, a field, a language, or the main language. Additions and changes are written without a question. A file that already matches writes nothing. A hand-written config that defines collections still asks before it is replaced.
|
|
87
|
+
- **Removing a field that still holds content** deletes that content. `manifest push` lists the affected fields (`key.field`, with the entry count) and pushes nothing until they are confirmed: `--confirm-drop <key.field>` per field (repeatable), or `--yes` for all. In code, `syncManifest(config, { confirmDropFields: ["articles.subtitle"] })` does the same. An unconfirmed push throws `FieldDropsRequiredError` (exported; `.drops` holds the `FieldDrop` list, each `{ kind, key, field, entries }`). **Ask the user before confirming a drop**; never add `--yes` or `--confirm-drop` on your own.
|
|
87
88
|
|
|
88
89
|
### Site content (globals)
|
|
89
90
|
|
|
@@ -118,7 +119,7 @@ assets: { download: true, dir: "public/areca" /*, publicPath: "/areca" */ }, //
|
|
|
118
119
|
```text
|
|
119
120
|
areca init [--cms-url <url>] [--token <t>] [--site-key <key>] [--out <dir>] [--config <path>]
|
|
120
121
|
[--assets-dir <dir>] [--assets-public-path <path>] [--no-assets] [--yes] [--force]
|
|
121
|
-
areca manifest push [config]
|
|
122
|
+
areca manifest push [config] [--confirm-drop <key.field>]... [--yes] # needs ARECA_SCHEMA_TOKEN and ARECA_CMS_URL
|
|
122
123
|
areca manifest pull [--config <path>] [--yes]
|
|
123
124
|
areca pull [--out <dir>] [--publication <id>] [--config <path>] [--types <path>] # default out: src/generated/content; types: <out>/areca.gen.ts; media: public/areca
|
|
124
125
|
areca run -- <command> # run a command with .env.local/.env loaded
|
|
@@ -157,6 +158,10 @@ const author = content.resolve(article.data.author); // Entry<"authors"> | null
|
|
|
157
158
|
- `content.globals.<camelKey>` returns a section as a flat object of its field values, plus `$seo` when SEO is on. Types: `CompanyInfoGlobal` (exported from `areca.gen.ts`), and `Global<K>`, `Globals`, `GlobalKey`, `GlobalData` and `GlobalSchema` from the package root. Without a registered schema, the value is typed as a loose record.
|
|
158
159
|
- `content.previewGlobals(token)` returns `{ globals, previewing, complete, issues }`. `previewing` is the camel key of the section the token targets (or `null`). A valid draft is in `globals`; an incomplete one keeps the published section, with `complete: false` and `issues` listing the problems. It also accepts a `PreviewResult` or `null`.
|
|
159
160
|
|
|
161
|
+
## Languages
|
|
162
|
+
|
|
163
|
+
Only when the CMS site has content languages. Set `languages`, `routes`, `requiredLanguages` and `translatable` in `areca.config.ts`. Reads pick a language with `content.in(locale)`, a per-call `{ locale, fallback }` or the `createContent({ locale })` resolver, and fall back to the main language. Entries carry `locale`, `isFallback`, `locales` and `alternates`; globals carry `$locale` and `$isFallback`. `seoHead` takes `locale` and `alternates` for hreflang, and `content.matchLocale` picks a visitor's language. `Href` is the union of every language's route; `content.in(locale)` narrows `href()` to that language's route. A pulled media URL is rewritten in every language view, so each language gets local files. Read [references/languages.md](references/languages.md) for the Paraglide setup, the other i18n libraries, preview with `locale`, and every export.
|
|
164
|
+
|
|
160
165
|
## Rich text
|
|
161
166
|
|
|
162
167
|
```tsx
|
|
@@ -187,11 +192,11 @@ Read [references/production.md](references/production.md) when the user needs an
|
|
|
187
192
|
- The CMS settings needed: production URL, deployment target, publishing mode.
|
|
188
193
|
- Direct use of `createArecaClient` and error messages.
|
|
189
194
|
|
|
190
|
-
Read [references/api.md](references/api.md) for every export, type and signature.
|
|
195
|
+
Read [references/api.md](references/api.md) for every export, type and signature, and [references/languages.md](references/languages.md) for languages.
|
|
191
196
|
|
|
192
197
|
## Rules
|
|
193
198
|
|
|
194
|
-
- Change the schema in `areca.config.ts` and then `manifest push`, then go live and pull. Don't hand-edit generated content or `areca.gen.ts`; a pull rewrites both.
|
|
199
|
+
- Change the schema in `areca.config.ts` and then `manifest push`, then go live and pull. Confirm a field removal or a language removal with the user first: both can delete content or stop a language being served. Don't hand-edit generated content or `areca.gen.ts`; a pull rewrites both.
|
|
195
200
|
- Commit `areca.gen.ts`. Keep the pulled JSON out of Git.
|
|
196
201
|
- Never import the content object, `@areca-cms/site-sdk/pull` or a client with tokens into browser bundles.
|
|
197
202
|
- Exclude `/__preview/*` from prerendering and caching.
|
|
@@ -7,11 +7,13 @@ ESM only. Import paths and their exports:
|
|
|
7
7
|
| Export | Signature | Notes |
|
|
8
8
|
| --- | --- | --- |
|
|
9
9
|
| `defineSiteContent` | `<const T extends {...}, const G extends {...}>(config: { siteKey; collections: T; globals?: G; assets? }) => same object` | Identity helper for `areca.config.ts`. Checks at compile time: `options` only on `select`, `collection` required on `reference` and must be a collection key, `after` must name a date key, `route` contains `$slug`. Globals take the same fields, with no `slug`, and may set `group` |
|
|
10
|
-
| `manifestFromConfig` | `(config: SiteContentConfig) => SiteManifest` | Validates identifiers, duplicates and reserved keys (`resolve`, `href`, `preview`, `globals`, `previewGlobals`), returns `{ collections }` plus `globals` only when there are sections |
|
|
11
|
-
| `createArecaClient` | `(options?: { cmsUrl?; siteToken?; schemaToken? }) => ArecaClient` | Falls back to `ARECA_CMS_URL`, `ARECA_SITE_TOKEN`, `ARECA_SCHEMA_TOKEN
|
|
12
|
-
| `
|
|
10
|
+
| `manifestFromConfig` | `(config: SiteContentConfig) => SiteManifest` | Validates identifiers, duplicates and reserved keys (`resolve`, `href`, `preview`, `globals`, `previewGlobals`, `in`, `languages`, `matchLocale`), returns `{ collections }` plus `globals` only when there are sections |
|
|
11
|
+
| `createArecaClient` | `(options?: { cmsUrl?; siteToken?; schemaToken? }) => ArecaClient` | Falls back to `ARECA_CMS_URL`, `ARECA_SITE_TOKEN`, `ARECA_SCHEMA_TOKEN`. Only the URL is required at creation; each call resolves the one credential it sends and fails before any request if it is missing |
|
|
12
|
+
| `FieldDropsRequiredError` | `class extends Error { drops: FieldDrop[] }` | Thrown by `syncManifest` when the CMS refuses removed fields that still hold content. Nothing was changed. Retry with `confirmDropFields` only after the user confirms |
|
|
13
|
+
| `getPreviewEntry` | `({ token: string; client?: ArecaClient; locale?: string }) => Promise<PreviewResult>` | `PreviewResult` = `PreviewEntryResult` `{ kind: 'entry', collection, slug, entry }` or `PreviewGlobalResult` `{ kind: 'global', global, entry }`. Needs `ARECA_CMS_URL` only (or pass `client`): the signed token is the credential. `locale` previews in that language |
|
|
13
14
|
| `toCamelKey` | `(raw: string) => string` | Converts a raw CMS key such as `blog-posts` or `company_info` to its code name (`blogPosts`, `companyInfo`) |
|
|
14
15
|
| `previewHeaders` | `() => { 'X-Robots-Tag': 'noindex, nofollow'; 'Cache-Control': 'no-store' }` | Apply to preview responses |
|
|
16
|
+
| `matchLocale` | `(input: string \| readonly string[] \| null \| undefined, languages: SiteLanguages \| null \| undefined) => string \| undefined` | Picks a site language from a tag, a list of tags or an `Accept-Language` header: exact match, then subtag, then `languages.main`. Returns `undefined` without languages |
|
|
15
17
|
| `seoHead` | `(seo: SeoMeta \| null \| undefined, options?: SeoHeadOptions) => { title, meta, links, scripts }` | TanStack Router `head()` shape |
|
|
16
18
|
| `seoHeadHtml` | `(seo, options?) => string` | Escaped `<head>` HTML |
|
|
17
19
|
| `seoJsonLd` | `(seo) => string[]` | Escaped JSON-LD strings |
|
|
@@ -19,18 +21,20 @@ ESM only. Import paths and their exports:
|
|
|
19
21
|
| `richTextMarkdown` | `(value) => string` | `''` when empty |
|
|
20
22
|
| `richTextHtml` | `(value) => string` | Static HTML; trusted CMS output only |
|
|
21
23
|
|
|
22
|
-
`SeoHeadOptions` = `{ titleTemplate?: string /* contains %s */ | ((title: string) => string); siteName?: string }`.
|
|
24
|
+
`SeoHeadOptions` = `{ titleTemplate?: string /* contains %s */ | ((title: string) => string); siteName?: string; locale?: string; alternates?: Partial<Record<string, string>>; main?: string }`. `locale` sets `og:locale`, `alternates` become `hreflang` links and `og:locale:alternate`, and `main` makes its alternate `x-default`.
|
|
23
25
|
|
|
24
26
|
`ArecaClient` methods (plain `fetch`, no retries or caching):
|
|
25
27
|
|
|
26
28
|
| Method | HTTP | Auth |
|
|
27
29
|
| --- | --- | --- |
|
|
28
30
|
| `fetchManifest(): Promise<SiteManifest>` | `GET /api/sites/manifest` | site token |
|
|
29
|
-
| `syncManifest(config): Promise<{ ok: true }>` | `POST /api/sites/manifest` | schema token |
|
|
31
|
+
| `syncManifest(config, opts?: { confirmDropFields?: string[] }): Promise<{ ok: true }>` | `POST /api/sites/manifest` | schema token. `confirmDropFields` lists confirmed removals as `<key>.<field>` |
|
|
30
32
|
| `fetchSnapshot(opts?: { publicationId?: string }): Promise<Snapshot>` | `GET /api/sites/snapshot/current[?publicationId=]` | site token |
|
|
31
|
-
| `fetchPreview(token: string): Promise<PreviewResult>` | `GET /api/sites/preview?token
|
|
33
|
+
| `fetchPreview(token: string, opts?: { locale?: string }): Promise<PreviewResult>` | `GET /api/sites/preview?token=[&locale=]` | signed preview token (no `Authorization` header) |
|
|
32
34
|
|
|
33
|
-
|
|
35
|
+
Language types from the main entry: `SiteLanguages` (`{ main; all }`), `I18nConfig` (`{ paraglide? }`), `LocalizedValues` (`{ slug?; data; seo? }`, one language of an entry as published), `PreviewLocaleStatus` (`{ complete; missing }`), `PreviewSnapshotEntry` and `PreviewSnapshotGlobal` (the preview snapshot with `localeStatus`).
|
|
36
|
+
|
|
37
|
+
Types from the main entry: `ArecaClient`, `ArecaClientOptions`, `FieldDrop` (`{ kind: 'collection' | 'global'; key; field; entries }`), `SiteContentConfig`, `AssetsConfig` (`{ download?, dir?, publicPath? }`), `CollectionConfig` (`{ route; label?; fields: Field[] }`), `Field` (a union discriminated on `type`: `reference` requires `collection`, `select` takes `options`, `date` and `datetime` take `after`), `FieldType`, `SiteManifest`, `Snapshot` (`{ publicationId, siteKey, createdAt, hash, collections, assets?, siteUrl?, schema? }`), `SnapshotAsset` (`{ id, storageKey, url, filename, mimeType, size }`), `SnapshotEntry` (the raw entry: `{ id, slug, publishedAt: string | null, data: Record<string, unknown>, seo? }`), `ImageValue`, `ReferenceValue<C = string>` (`{ collection: C; slug; id }`), `RichTextValue`, `SeoMeta`, `PreviewResult`, `PulledContentManifest`, `HeadMeta`, `HeadLink`, `HeadScript`, `SeoHeadOptions`, `SeoHeadResult`.
|
|
34
38
|
|
|
35
39
|
Site content types: `GlobalConfig` (`{ label?, fields, seo?, previewPath? }`, `seo` defaults to `false`), `SnapshotGlobal` (`{ id, publishedAt, data, seo? }`), `PreviewEntryResult`, `PreviewGlobalResult`, and `SiteManifest` / `Snapshot`, which gain optional `globals` (and `globalSchema` on the snapshot).
|
|
36
40
|
|
|
@@ -52,7 +56,11 @@ Typed content types (main entry, generated against `Register`):
|
|
|
52
56
|
| `QueryOptions<C, S>` | `where`, `orderBy`, `limit`, `offset`, `with` |
|
|
53
57
|
| `QueryResult<C, O, S>` | the entry type returned for query options `O`, with expanded references |
|
|
54
58
|
| `RoutePath<R>` | a route with `$slug` replaced by `${string}` |
|
|
55
|
-
| `
|
|
59
|
+
| `Locale` | union of the site's language codes, from the generated `Register` (`string` without languages) |
|
|
60
|
+
| `EntryLocale` | `{ locale; isFallback; locales; alternates }` added to each entry on a languages site |
|
|
61
|
+
| `GlobalLocale` | `{ $locale; $isFallback }` added to each section value on a languages site |
|
|
62
|
+
| `LocaleOptions` | per-call options: `{ locale?: Locale; fallback?: boolean }` (`fallback` defaults to `true`) |
|
|
63
|
+
| `Href<C, S, L>` | the route type of collection `C`: the union of every language's route on a languages site, such as `` `/blog/${string}` ``. With language `L`, the route of that language, as `content.in(L)` gives |
|
|
56
64
|
| `Content<S, G>` | the content object: one property per collection, plus `resolve`, `href`, `preview`, `globals` and `previewGlobals` |
|
|
57
65
|
| `ContentGlobalSchema` | one site content section in the generated `globalSchema`: `key`, `label?`, `seo`, `previewPath?`, `fields` |
|
|
58
66
|
| `GlobalSchema`, `RegisteredGlobalSchema` | the generated `globalSchema` (or the loose type when nothing is registered) |
|
|
@@ -100,7 +108,7 @@ Typed content types (main entry, generated against `Register`):
|
|
|
100
108
|
|
|
101
109
|
## Other subpaths
|
|
102
110
|
|
|
103
|
-
- `@areca-cms/site-sdk/seo`: `seoHead`, `seoHeadHtml`, `seoJsonLd` and their types.
|
|
111
|
+
- `@areca-cms/site-sdk/seo`: `seoHead`, `seoHeadHtml`, `seoJsonLd`, `ogLocale(tag)` (`nl-BE` gives `nl_BE` for `og:locale`) and their types.
|
|
104
112
|
- `@areca-cms/site-sdk/rich-text`: `richTextDocument`, `richTextMarkdown`, `richTextHtml`.
|
|
105
113
|
- `@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`.
|
|
106
114
|
|