@contentrain/rules 0.7.2 → 0.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/index.cjs +4 -0
- package/dist/index.d.cts +3 -0
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +3 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +4 -0
- package/dist/index.mjs.map +1 -1
- package/essential/contentrain-essentials.md +1 -1
- package/package.json +2 -2
- package/shared/content-conventions.md +3 -2
- package/shared/i18n-quality.md +2 -1
- package/shared/mcp-usage.md +3 -2
- package/shared/media-rules.md +1 -1
- package/shared/schema-rules.md +7 -1
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Start here:
|
|
|
14
14
|
|
|
15
15
|
This package is the **policy layer** of the Contentrain ecosystem. It defines how agents should behave when they work with:
|
|
16
16
|
|
|
17
|
-
- Contentrain MCP tools (
|
|
17
|
+
- Contentrain MCP tools (27 operations)
|
|
18
18
|
- schema and model design
|
|
19
19
|
- content quality (SEO, accessibility, media, i18n)
|
|
20
20
|
- normalize workflows (extraction + reuse)
|
|
@@ -75,7 +75,7 @@ The package root exports constants for tooling:
|
|
|
75
75
|
|
|
76
76
|
- `FIELD_TYPES` — 27 flat field types
|
|
77
77
|
- `MODEL_KINDS` — `singleton`, `collection`, `document`, `dictionary`
|
|
78
|
-
- `MCP_TOOLS` —
|
|
78
|
+
- `MCP_TOOLS` — 27 MCP tool names: 22 core + 5 media (`contentrain_media_*`, registered only on media-capable providers)
|
|
79
79
|
- `ESSENTIAL_RULES_FILE` — path to essential guardrails markdown
|
|
80
80
|
- `STACKS` — supported framework stacks
|
|
81
81
|
|
|
@@ -84,7 +84,7 @@ The package root exports constants for tooling:
|
|
|
84
84
|
```ts
|
|
85
85
|
import { MCP_TOOLS, ESSENTIAL_RULES_FILE, FIELD_TYPES } from '@contentrain/rules'
|
|
86
86
|
|
|
87
|
-
console.log(MCP_TOOLS.length) //
|
|
87
|
+
console.log(MCP_TOOLS.length) // 27
|
|
88
88
|
console.log(MCP_TOOLS.includes('contentrain_merge')) // true
|
|
89
89
|
console.log(MCP_TOOLS.includes('contentrain_doctor')) // true
|
|
90
90
|
console.log(ESSENTIAL_RULES_FILE) // 'essential/contentrain-essentials.md'
|
package/dist/index.cjs
CHANGED
package/dist/index.d.cts
CHANGED
package/dist/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/index.ts"],"mappings":";;AAQA;;;;cAAa,WAAA;AAAA,KAUD,SAAA,WAAoB,WAAA;AAAA,cAInB,WAAA;AAAA,KACD,SAAA,WAAoB,WAAA;AAAA,cASnB,gBAAA;EAAA
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/index.ts"],"mappings":";;AAQA;;;;cAAa,WAAA;AAAA,KAUD,SAAA,WAAoB,WAAA;AAAA,cAInB,WAAA;AAAA,KACD,SAAA,WAAoB,WAAA;AAAA,cASnB,gBAAA;EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAoBD,aAAA,WAAwB,gBAAA;AAAA,cAIvB,SAAA;AAAA,KAkBD,OAAA,WAAkB,SAAA;AAAA,cAIjB,oBAAA;AAAA,cAIA,MAAA;AAAA,KACD,KAAA,WAAgB,MAAA"}
|
package/dist/index.d.mts
CHANGED
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;AAQA;;;;cAAa,WAAA;AAAA,KAUD,SAAA,WAAoB,WAAA;AAAA,cAInB,WAAA;AAAA,KACD,SAAA,WAAoB,WAAA;AAAA,cASnB,gBAAA;EAAA
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;AAQA;;;;cAAa,WAAA;AAAA,KAUD,SAAA,WAAoB,WAAA;AAAA,cAInB,WAAA;AAAA,KACD,SAAA,WAAoB,WAAA;AAAA,cASnB,gBAAA;EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAoBD,aAAA,WAAwB,gBAAA;AAAA,cAIvB,SAAA;AAAA,KAkBD,OAAA,WAAkB,SAAA;AAAA,cAIjB,oBAAA;AAAA,cAIA,MAAA;AAAA,KACD,KAAA,WAAgB,MAAA"}
|
package/dist/index.mjs
CHANGED
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @contentrain/rules — AI agent rules for Contentrain\n *\n * Quality standards, architecture conventions, and IDE integration.\n */\n\n// ─── Field Types (27 flat types) ───\n\nexport const FIELD_TYPES = [\n 'string', 'text', 'email', 'url', 'slug', 'color', 'phone', 'code', 'icon',\n 'markdown', 'richtext',\n 'number', 'integer', 'decimal', 'percent', 'rating',\n 'boolean', 'date', 'datetime',\n 'image', 'video', 'file',\n 'relation', 'relations',\n 'select', 'array', 'object',\n] as const\n\nexport type FieldType = (typeof FIELD_TYPES)[number]\n\n// ─── Model Kinds ───\n\nexport const MODEL_KINDS = ['singleton', 'collection', 'document', 'dictionary'] as const\nexport type ModelKind = (typeof MODEL_KINDS)[number]\n\n// ─── Model Definition Properties ───\n//\n// The parity anchor for the model schema. Docs describing `ModelDefinition` drift\n// silently: nothing failed when a property landed in @contentrain/types and never\n// reached schema-rules.md. The tests in tests/mcp-parity.test.ts assert this list\n// against MODEL_FIELD_ORDER in @contentrain/mcp and against the prose tables.\n\nexport const MODEL_PROPERTIES = [\n { name: 'id', required: true },\n { name: 'name', required: true },\n { name: 'kind', required: true },\n { name: 'domain', required: true },\n { name: 'i18n', required: true },\n { name: 'title_field', required: true },\n { name: 'description', required: false },\n { name: 'content_path', required: false },\n { name: 'locale_strategy', required: false },\n // `required` here means \"documented as required\", not the TS optionality.\n // `fields` is `fields?` on the interface but every non-dictionary model needs\n // it — a collection with no fields cannot even name a title_field. The\n // per-kind nuance lives in the docs cell (\"Yes (except dictionary)\").\n { name: 'fields', required: true },\n { name: 'form', required: false, runtimeOwned: true },\n { name: 'comments', required: false, runtimeOwned: true },\n] as const\n\nexport type ModelProperty = (typeof MODEL_PROPERTIES)[number]['name']\n\n// ─── MCP Tools (27 tools: 22 core + 5 media) ───\n\nexport const MCP_TOOLS = [\n 'contentrain_status', 'contentrain_describe', 'contentrain_describe_format',\n 'contentrain_init', 'contentrain_scaffold',\n 'contentrain_model_save', 'contentrain_model_delete',\n 'contentrain_content_save', 'contentrain_content_delete', 'contentrain_content_list',\n 'contentrain_vocabulary_save', 'contentrain_vocabulary_delete',\n 'contentrain_scan', 'contentrain_apply',\n 'contentrain_validate', 'contentrain_submit',\n 'contentrain_merge', 'contentrain_reconcile',\n 'contentrain_branch_list', 'contentrain_branch_delete',\n 'contentrain_bulk',\n 'contentrain_doctor',\n // Media tools — registered only when the provider exposes a media stack\n // (Studio MCP Cloud); absent from local stdio servers.\n 'contentrain_media_list', 'contentrain_media_get', 'contentrain_media_ingest',\n 'contentrain_media_update', 'contentrain_media_delete',\n] as const\n\nexport type McpTool = (typeof MCP_TOOLS)[number]\n\n// ─── Essential Rules ───\n\nexport const ESSENTIAL_RULES_FILE = 'essential/contentrain-essentials.md' as const\n\n// ─── Stacks ───\n\nexport const STACKS = ['nuxt', 'next', 'astro', 'sveltekit', 'react', 'node'] as const\nexport type Stack = (typeof STACKS)[number]\n"],"mappings":";;;;;;AAQA,MAAa,cAAc;CACzB;CAAU;CAAQ;CAAS;CAAO;CAAQ;CAAS;CAAS;CAAQ;CACpE;CAAY;CACZ;CAAU;CAAW;CAAW;CAAW;CAC3C;CAAW;CAAQ;CACnB;CAAS;CAAS;CAClB;CAAY;CACZ;CAAU;CAAS;CACpB;AAMD,MAAa,cAAc;CAAC;CAAa;CAAc;CAAY;CAAa;AAUhF,MAAa,mBAAmB;CAC9B;EAAE,MAAM;EAAM,UAAU;EAAM;CAC9B;EAAE,MAAM;EAAQ,UAAU;EAAM;CAChC;EAAE,MAAM;EAAQ,UAAU;EAAM;CAChC;EAAE,MAAM;EAAU,UAAU;EAAM;CAClC;EAAE,MAAM;EAAQ,UAAU;EAAM;CAChC;EAAE,MAAM;EAAe,UAAU;EAAM;CACvC;EAAE,MAAM;EAAe,UAAU;EAAO;CACxC;EAAE,MAAM;EAAgB,UAAU;EAAO;CACzC;EAAE,MAAM;EAAmB,UAAU;EAAO;CAK5C;EAAE,MAAM;EAAU,UAAU;EAAM;CAClC;EAAE,MAAM;EAAQ,UAAU;EAAO,cAAc;EAAM;CACrD;EAAE,MAAM;EAAY,UAAU;EAAO,cAAc;EAAM;CAC1D;AAMD,MAAa,YAAY;CACvB;CAAsB;CAAwB;CAC9C;CAAoB;CACpB;CAA0B;CAC1B;CAA4B;CAA8B;CAC1D;CAA+B;CAC/B;CAAoB;CACpB;CAAwB;CACxB;CAAqB;CACrB;CAA2B;CAC3B;CACA;CAGA;CAA0B;CAAyB;CACnD;CAA4B;CAC7B;AAMD,MAAa,uBAAuB;AAIpC,MAAa,SAAS;CAAC;CAAQ;CAAQ;CAAS;CAAa;CAAS;CAAO"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @contentrain/rules — AI agent rules for Contentrain\n *\n * Quality standards, architecture conventions, and IDE integration.\n */\n\n// ─── Field Types (27 flat types) ───\n\nexport const FIELD_TYPES = [\n 'string', 'text', 'email', 'url', 'slug', 'color', 'phone', 'code', 'icon',\n 'markdown', 'richtext',\n 'number', 'integer', 'decimal', 'percent', 'rating',\n 'boolean', 'date', 'datetime',\n 'image', 'video', 'file',\n 'relation', 'relations',\n 'select', 'array', 'object',\n] as const\n\nexport type FieldType = (typeof FIELD_TYPES)[number]\n\n// ─── Model Kinds ───\n\nexport const MODEL_KINDS = ['singleton', 'collection', 'document', 'dictionary'] as const\nexport type ModelKind = (typeof MODEL_KINDS)[number]\n\n// ─── Model Definition Properties ───\n//\n// The parity anchor for the model schema. Docs describing `ModelDefinition` drift\n// silently: nothing failed when a property landed in @contentrain/types and never\n// reached schema-rules.md. The tests in tests/mcp-parity.test.ts assert this list\n// against MODEL_FIELD_ORDER in @contentrain/mcp and against the prose tables.\n\nexport const MODEL_PROPERTIES = [\n { name: 'id', required: true },\n { name: 'name', required: true },\n { name: 'kind', required: true },\n { name: 'domain', required: true },\n { name: 'i18n', required: true },\n { name: 'locales', required: false },\n { name: 'title_field', required: true },\n { name: 'description', required: false },\n { name: 'content_path', required: false },\n { name: 'locale_strategy', required: false },\n // `required` here means \"documented as required\", not the TS optionality.\n // `fields` is `fields?` on the interface but every non-dictionary model needs\n // it — a collection with no fields cannot even name a title_field. The\n // per-kind nuance lives in the docs cell (\"Yes (except dictionary)\").\n { name: 'fields', required: true },\n { name: 'form', required: false, runtimeOwned: true },\n { name: 'comments', required: false, runtimeOwned: true },\n] as const\n\nexport type ModelProperty = (typeof MODEL_PROPERTIES)[number]['name']\n\n// ─── MCP Tools (27 tools: 22 core + 5 media) ───\n\nexport const MCP_TOOLS = [\n 'contentrain_status', 'contentrain_describe', 'contentrain_describe_format',\n 'contentrain_init', 'contentrain_scaffold',\n 'contentrain_model_save', 'contentrain_model_delete',\n 'contentrain_content_save', 'contentrain_content_delete', 'contentrain_content_list',\n 'contentrain_vocabulary_save', 'contentrain_vocabulary_delete',\n 'contentrain_scan', 'contentrain_apply',\n 'contentrain_validate', 'contentrain_submit',\n 'contentrain_merge', 'contentrain_reconcile',\n 'contentrain_branch_list', 'contentrain_branch_delete',\n 'contentrain_bulk',\n 'contentrain_doctor',\n // Media tools — registered only when the provider exposes a media stack\n // (Studio MCP Cloud); absent from local stdio servers.\n 'contentrain_media_list', 'contentrain_media_get', 'contentrain_media_ingest',\n 'contentrain_media_update', 'contentrain_media_delete',\n] as const\n\nexport type McpTool = (typeof MCP_TOOLS)[number]\n\n// ─── Essential Rules ───\n\nexport const ESSENTIAL_RULES_FILE = 'essential/contentrain-essentials.md' as const\n\n// ─── Stacks ───\n\nexport const STACKS = ['nuxt', 'next', 'astro', 'sveltekit', 'react', 'node'] as const\nexport type Stack = (typeof STACKS)[number]\n"],"mappings":";;;;;;AAQA,MAAa,cAAc;CACzB;CAAU;CAAQ;CAAS;CAAO;CAAQ;CAAS;CAAS;CAAQ;CACpE;CAAY;CACZ;CAAU;CAAW;CAAW;CAAW;CAC3C;CAAW;CAAQ;CACnB;CAAS;CAAS;CAClB;CAAY;CACZ;CAAU;CAAS;CACpB;AAMD,MAAa,cAAc;CAAC;CAAa;CAAc;CAAY;CAAa;AAUhF,MAAa,mBAAmB;CAC9B;EAAE,MAAM;EAAM,UAAU;EAAM;CAC9B;EAAE,MAAM;EAAQ,UAAU;EAAM;CAChC;EAAE,MAAM;EAAQ,UAAU;EAAM;CAChC;EAAE,MAAM;EAAU,UAAU;EAAM;CAClC;EAAE,MAAM;EAAQ,UAAU;EAAM;CAChC;EAAE,MAAM;EAAW,UAAU;EAAO;CACpC;EAAE,MAAM;EAAe,UAAU;EAAM;CACvC;EAAE,MAAM;EAAe,UAAU;EAAO;CACxC;EAAE,MAAM;EAAgB,UAAU;EAAO;CACzC;EAAE,MAAM;EAAmB,UAAU;EAAO;CAK5C;EAAE,MAAM;EAAU,UAAU;EAAM;CAClC;EAAE,MAAM;EAAQ,UAAU;EAAO,cAAc;EAAM;CACrD;EAAE,MAAM;EAAY,UAAU;EAAO,cAAc;EAAM;CAC1D;AAMD,MAAa,YAAY;CACvB;CAAsB;CAAwB;CAC9C;CAAoB;CACpB;CAA0B;CAC1B;CAA4B;CAA8B;CAC1D;CAA+B;CAC/B;CAAoB;CACpB;CAAwB;CACxB;CAAqB;CACrB;CAA2B;CAC3B;CACA;CAGA;CAA0B;CAAyB;CACnD;CAA4B;CAC7B;AAMD,MAAa,uBAAuB;AAIpC,MAAa,SAAS;CAAC;CAAQ;CAAQ;CAAS;CAAa;CAAS;CAAO"}
|
|
@@ -35,7 +35,7 @@ MCP is **deterministic infrastructure**. The agent (you) is the **intelligence l
|
|
|
35
35
|
- **ALWAYS** use MCP tools — do not write `.contentrain/` JSON files directly
|
|
36
36
|
- **Publishing is yours, not MCP's.** `contentrain_content_save` preserves an existing entry's `status`; only a brand-new entry starts at `draft`. To change publish state, call `contentrain_bulk update_status` deliberately (`entry_ids` for collections, `slugs` for documents, neither for singletons/dictionaries) — and note that on the CDN an entry is delivered only when its status is `published`. `publish_at`/`expire_at` on `content_save` never change status: they gate delivery of a published entry, live in meta only, and `null` clears them.
|
|
37
37
|
- **Schema validity is MCP's.** `contentrain_content_save` validates before it writes and **refuses** the save on any error — nothing is committed, so there is no branch to clean up. Fix the values and call it again. Warnings (email/url/colour/phone heuristics, `accept` extension checks) do not block; they come back in the response.
|
|
38
|
-
- **Every model declares its `title_field`.** Required on create, and it must stay correct when you rename or drop a field. It names the field shown as an entry's title and must be a `string`/`text`/`slug`/`email`/`url`/`code`/`markdown`/`richtext` field on that model; dictionaries have no fields, so they use the reserved value `"key"
|
|
38
|
+
- **Every model declares its `title_field`.** Required on create, and it must stay correct when you rename or drop a field. It names the field shown as an entry's title and must be a `string`/`text`/`slug`/`email`/`url`/`code`/`markdown`/`richtext` field on that model; dictionaries have no fields, so they use the reserved value `"key"`; a singleton of section objects uses a dotted path one level deep (`hero.heading`). Pick what a human reads to tell entries apart — the headline, the name, the question — never the slug, icon or a relation ID. Consumers used to guess this and titled rows `i-lucide-bot`.
|
|
39
39
|
- **A declared constraint is a real constraint.** `model_save` rejects a property declared where it cannot apply (`options` on a non-select, `accept` on a non-media field, `unique` on a singleton) rather than accepting it and doing nothing. The one exception is `maxSize`, which MCP cannot enforce — your media provider does, at ingest, and `model_save` says so.
|
|
40
40
|
|
|
41
41
|
## MCP Tools
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentrain/rules",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "AI agent rules for Contentrain — quality standards, architecture conventions, IDE integration",
|
|
6
6
|
"type": "module",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"tsdown": "^0.21.0",
|
|
56
56
|
"typescript": "^5.7.0",
|
|
57
57
|
"vitest": "^3.0.0",
|
|
58
|
-
"@contentrain/mcp": "3.
|
|
58
|
+
"@contentrain/mcp": "3.11.18"
|
|
59
59
|
},
|
|
60
60
|
"scripts": {
|
|
61
61
|
"build": "tsdown src/index.ts --format esm,cjs --dts",
|
|
@@ -224,8 +224,9 @@ Several fields are managed by the platform. Agents MUST NOT set or modify them.
|
|
|
224
224
|
| `i18n: false` on model | Single file: `data.json` (or `{slug}.md` for documents) |
|
|
225
225
|
| Locale code | ISO 639-1: `en`, `tr`, `de`, `fr`, `ja`, `ar` |
|
|
226
226
|
| ID/slug are locale-agnostic | The same entry ID or document slug is used across all locales |
|
|
227
|
-
| Entry parity (collections) | All locales MUST have the same set of entry IDs |
|
|
228
|
-
| Key parity (dictionaries) | All locales MUST have the same set of keys |
|
|
227
|
+
| Entry parity (collections) | All covered locales MUST have the same set of entry IDs |
|
|
228
|
+
| Key parity (dictionaries) | All covered locales MUST have the same set of keys |
|
|
229
|
+
| `locales` on model (optional) | The project locales this model covers — a subset of `locales.supported`. Absent means all of them. Parity is checked against it |
|
|
229
230
|
|
|
230
231
|
### Validation Checks
|
|
231
232
|
|
package/shared/i18n-quality.md
CHANGED
|
@@ -10,7 +10,8 @@ Every rule applies whenever the project has more than one locale configured.
|
|
|
10
10
|
### 1.1 Coverage Requirement
|
|
11
11
|
|
|
12
12
|
- Read `.contentrain/config.json` → `locales.supported` to determine the full list of supported locales.
|
|
13
|
-
-
|
|
13
|
+
- A model MAY narrow that list with an optional `locales` array (a subset of `locales.supported`). When present, it — not the project list — is the model's coverage. Absent means every supported locale.
|
|
14
|
+
- EVERY entry that exists in the source locale MUST have a corresponding entry in ALL of the model's covered target locales.
|
|
14
15
|
- Missing translations are deployment blockers. Treat a missing locale file as an error, not a warning.
|
|
15
16
|
|
|
16
17
|
### 1.2 Dictionary Entries
|
package/shared/mcp-usage.md
CHANGED
|
@@ -48,7 +48,7 @@ MCP is **deterministic infrastructure**. The agent is the **intelligence layer**
|
|
|
48
48
|
|
|
49
49
|
| Tool | Purpose | Parameters |
|
|
50
50
|
|------|---------|------------|
|
|
51
|
-
| `contentrain_model_save` | Create or update a model definition (upsert) | `id`, `name`, `kind`, `domain`, `i18n`, `title_field`, `fields?`, `description?`, `content_path?`, `locale_strategy?` |
|
|
51
|
+
| `contentrain_model_save` | Create or update a model definition (upsert) | `id`, `name`, `kind`, `domain`, `i18n`, `title_field`, `fields?`, `description?`, `locales?`, `content_path?`, `locale_strategy?` |
|
|
52
52
|
| `contentrain_model_delete` | Delete a model and its content | `model`, `confirm: true` |
|
|
53
53
|
|
|
54
54
|
### 2.4 Content Tools
|
|
@@ -388,7 +388,8 @@ Creates or updates a model definition. Key parameters:
|
|
|
388
388
|
- `id`: kebab-case model ID (e.g., `"blog-post"`).
|
|
389
389
|
- `kind`: one of `singleton`, `collection`, `document`, `dictionary`.
|
|
390
390
|
- `i18n`: whether the model supports localization.
|
|
391
|
-
- `title_field`: **required** — the field shown as an entry's title. Must name a field declared on this model whose type is `string`, `text`, `slug`, `email`, `url`, `code`, `markdown` or `richtext`. Dictionary models use the reserved value `"key"`. Set it when creating a model, and keep it correct when renaming or removing fields.
|
|
391
|
+
- `title_field`: **required** — the field shown as an entry's title. Must name a field declared on this model whose type is `string`, `text`, `slug`, `email`, `url`, `code`, `markdown` or `richtext`. Dictionary models use the reserved value `"key"`. A dotted path (`hero.heading`) names a field one level inside an `object` field — for a singleton built from sections. Set it when creating a model, and keep it correct when renaming or removing fields.
|
|
392
|
+
- `locales`: optional subset of `config.locales.supported` naming the project locales this model covers. Omit it and the model covers every supported locale, which is what validation checks parity against. Declare it only when the content genuinely exists in a subset — a partially-translated site. A locale outside `locales.supported` is rejected.
|
|
392
393
|
- `content_path`: optional framework-relative path (e.g., `"content/blog"`, `"locales"`). When set, content is written here instead of `.contentrain/content/`.
|
|
393
394
|
- `locale_strategy`: how locale is encoded in filenames: `"file"` (default), `"suffix"`, `"directory"`, `"none"`.
|
|
394
395
|
- `contentrain_model_save` preserves existing `form` configuration and preserves existing `comments` configuration. These runtime-owned blocks are not structural tool inputs; use the runtime provider's settings surface to change them.
|
package/shared/media-rules.md
CHANGED
|
@@ -9,7 +9,7 @@ Follow these guidelines when populating image, video, or file fields in any cont
|
|
|
9
9
|
|
|
10
10
|
Media references are stored in one of two models. Identify which one the project uses before populating image, video, or file fields. The asset rules below (dimensions, formats, size limits, naming) apply to **both**; only the **Asset Organization** section (paths under `assets_path`, relative references) is specific to the local-file model.
|
|
11
11
|
|
|
12
|
-
1. **Local file model (default / OSS).** Media lives on disk under `config.json > assets_path` (default `.contentrain/assets/`) and is referenced by a **relative path from the project root** (e.g. `.contentrain/assets/blog/hero.webp`). To turn relative paths into URLs at read time, set `config.json > cdn.url` (or `contentrain generate --
|
|
12
|
+
1. **Local file model (default / OSS).** Media lives on disk under `config.json > assets_path` (default `.contentrain/assets/`) and is referenced by a **relative path from the project root** (e.g. `.contentrain/assets/blog/hero.webp`). To turn relative paths into URLs at read time, set `config.json > cdn.url` (or `contentrain generate --mediaBaseUrl <base>`) and use the `media()`/`mediaBody()` resolvers baked into the `@contentrain/query` generated client — `media('media/...') → {mediaBaseUrl}/{path}`, `mediaBody(markdown)` for `](media/...)` references inside markdown. The Astro loader (`contentrainLoader({ mediaBaseUrl })`) applies the same resolver automatically to media fields and markdown bodies.
|
|
13
13
|
|
|
14
14
|
2. **Studio CDN model.** Media lives in the Studio CDN/object store and is referenced by a relative storage path (`media/...`) returned by the media library. Pass either that `media/...` path or its URL to `contentrain_content_save`: in **cloud mode** the value is **automatically normalized to an absolute public delivery URL** on save (`{base}/{path}`), in both media fields and markdown bodies, so the committed content renders in a browser anywhere with no SDK. In **local mode** the relative path is kept verbatim. The rewrite is idempotent and never touches external URLs (`http(s)://`, `//`, `data:`) or already-absolute delivery URLs — so for external images (a CDN or Unsplash URL), just save the URL directly.
|
|
15
15
|
|
package/shared/schema-rules.md
CHANGED
|
@@ -156,7 +156,8 @@ Model definitions live at `.contentrain/models/{model-id}.json`. One file per mo
|
|
|
156
156
|
| `kind` | `string` | Yes | One of: `singleton`, `collection`, `document`, `dictionary`. |
|
|
157
157
|
| `domain` | `string` | Yes | Organizational group (maps to content subdirectory). |
|
|
158
158
|
| `i18n` | `boolean` | Yes | Whether the model supports multiple locales. |
|
|
159
|
-
| `
|
|
159
|
+
| `locales` | `string[]` | No | The project locales this model covers — a subset of `config.locales.supported`. Absent (the default) means every supported locale, which is what parity is validated against. Declare it only when the content genuinely exists in a subset, e.g. a partially-translated site. A locale outside `locales.supported` is a validation error; the field has no effect on an `i18n: false` model. |
|
|
160
|
+
| `title_field` | `string` | Yes | Field shown as an entry's title in listings, pickers and relation references. Must name a field declared on this model whose type is `string`, `text`, `slug`, `email`, `url`, `code`, `markdown` or `richtext`. Dictionary models have no fields — they use the reserved value `"key"`, meaning the entry key is the title. A dotted path (`hero.heading`) names a field one level inside an `object` field. |
|
|
160
161
|
| `description` | `string` | No | Model description for documentation and agent context. |
|
|
161
162
|
| `fields` | `object` | Yes (except dictionary) | Field definitions. Dictionary kind has NO fields. |
|
|
162
163
|
| `content_path` | `string` | No | Framework-relative path for content files (e.g., `"content/blog"`, `"locales"`). When set, content is written here instead of `.contentrain/content/`. |
|
|
@@ -169,6 +170,11 @@ Model definitions live at `.contentrain/models/{model-id}.json`. One file per mo
|
|
|
169
170
|
Declare it on create AND keep it correct when renaming or removing fields — a
|
|
170
171
|
`title_field` pointing at a field that no longer exists fails validation.
|
|
171
172
|
|
|
173
|
+
A singleton built from section objects (`hero`, `services`, …) has no top-level
|
|
174
|
+
text field. Title it by a section's heading with a dotted path: `"hero.heading"`.
|
|
175
|
+
The path reaches one level into an `object` field — never deeper, never into an
|
|
176
|
+
array. Do not add a copied top-level `title` for this: it is a field no page prints.
|
|
177
|
+
|
|
172
178
|
Pick the field a human would read to tell one entry from another: the headline,
|
|
173
179
|
the name, the question. Not the slug, not the icon, not a relation ID — those are
|
|
174
180
|
identifiers, and a list titled `instagram-launch-post` or `i-lucide-bot` tells the
|