@contentrain/types 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +124 -3
- package/dist/index.d.mts +57 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +325 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,9 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@contentrain/types)
|
|
4
4
|
[](https://github.com/Contentrain/ai/tree/main/packages/types)
|
|
5
|
+
[](https://ai.contentrain.io/reference/config)
|
|
5
6
|
|
|
6
7
|
Shared TypeScript types for the Contentrain ecosystem.
|
|
7
8
|
|
|
9
|
+
Start here:
|
|
10
|
+
|
|
11
|
+
- [2-minute product demo](https://ai.contentrain.io/demo)
|
|
12
|
+
- [Config and format reference](https://ai.contentrain.io/reference/config)
|
|
13
|
+
|
|
8
14
|
This package is the common schema layer used by:
|
|
9
15
|
|
|
10
16
|
- `@contentrain/mcp`
|
|
@@ -75,6 +81,22 @@ Storage/runtime helper types:
|
|
|
75
81
|
- `DocumentMeta`
|
|
76
82
|
- `DictionaryMeta`
|
|
77
83
|
|
|
84
|
+
Validate functions (pure, dependency-free):
|
|
85
|
+
|
|
86
|
+
- `validateSlug(slug)` — kebab-case slug validation
|
|
87
|
+
- `validateEntryId(id)` — entry ID format validation
|
|
88
|
+
- `validateLocale(locale, config)` — locale format + config support check
|
|
89
|
+
- `detectSecrets(value)` — detect potential secrets in field values
|
|
90
|
+
- `validateFieldValue(value, fieldDef)` — full field schema validation (type, required, min/max, pattern, select)
|
|
91
|
+
|
|
92
|
+
Serialize functions (pure, dependency-free):
|
|
93
|
+
|
|
94
|
+
- `sortKeys(obj, fieldOrder?)` — recursive key sorting for canonical output
|
|
95
|
+
- `canonicalStringify(data, fieldOrder?)` — deterministic JSON serialization
|
|
96
|
+
- `generateEntryId()` — 12-char hex ID generation
|
|
97
|
+
- `parseMarkdownFrontmatter(content)` — parse YAML frontmatter + body from markdown
|
|
98
|
+
- `serializeMarkdownFrontmatter(data, body)` — serialize data + body into markdown frontmatter
|
|
99
|
+
|
|
78
100
|
## 🧭 Stability
|
|
79
101
|
|
|
80
102
|
This package is intended to be the shared public contract across the Contentrain ecosystem.
|
|
@@ -131,12 +153,109 @@ Type-only usage:
|
|
|
131
153
|
import type { ModelDefinition, ContentrainConfig } from '@contentrain/types'
|
|
132
154
|
```
|
|
133
155
|
|
|
134
|
-
|
|
156
|
+
Mixed usage (types + runtime functions):
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
import type { FieldDef, ValidationError } from '@contentrain/types'
|
|
160
|
+
import {
|
|
161
|
+
validateFieldValue,
|
|
162
|
+
validateSlug,
|
|
163
|
+
detectSecrets,
|
|
164
|
+
canonicalStringify,
|
|
165
|
+
parseMarkdownFrontmatter,
|
|
166
|
+
} from '@contentrain/types'
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## 🏢 Studio Integration
|
|
170
|
+
|
|
171
|
+
Studio (Nuxt 4, web) cannot import `@contentrain/mcp` directly because MCP depends on Node.js-only packages (`simple-git`, `@modelcontextprotocol/sdk`). The validate and serialize functions in this package are **pure, dependency-free, and browser-compatible** — designed for Studio to share the same validation contract as MCP.
|
|
172
|
+
|
|
173
|
+
### What Studio gets from `@contentrain/types`
|
|
174
|
+
|
|
175
|
+
| Function | Use case |
|
|
176
|
+
|---|---|
|
|
177
|
+
| `validateSlug(slug)` | Form validation for document slugs |
|
|
178
|
+
| `validateEntryId(id)` | Validate collection entry IDs |
|
|
179
|
+
| `validateLocale(locale, config)` | Locale picker validation |
|
|
180
|
+
| `detectSecrets(value)` | Content editor secret detection warnings |
|
|
181
|
+
| `validateFieldValue(value, fieldDef)` | Full field-level validation in content forms |
|
|
182
|
+
| `canonicalStringify(data, fieldOrder?)` | Preview canonical JSON output |
|
|
183
|
+
| `parseMarkdownFrontmatter(content)` | Document editor frontmatter parsing |
|
|
184
|
+
| `serializeMarkdownFrontmatter(data, body)` | Document editor serialization |
|
|
185
|
+
| `generateEntryId()` | Client-side entry ID generation |
|
|
186
|
+
| `SECRET_PATTERNS` | Extend or customize secret detection |
|
|
187
|
+
|
|
188
|
+
### What stays in MCP (not available to Studio directly)
|
|
189
|
+
|
|
190
|
+
These require file system I/O or Node.js dependencies:
|
|
191
|
+
|
|
192
|
+
- `checkRelation()` — validates relation references against actual content files on disk
|
|
193
|
+
- `validateProject()` — full project validation with file reading
|
|
194
|
+
- `writeContent()` / `deleteContent()` — content persistence with git worktree
|
|
195
|
+
- `resolveContentDir()` / `resolveJsonFilePath()` — path resolution with `node:path`
|
|
196
|
+
|
|
197
|
+
### Studio implementation example
|
|
135
198
|
|
|
136
199
|
```ts
|
|
137
|
-
|
|
200
|
+
// composables/useContentValidation.ts
|
|
201
|
+
import type { FieldDef, ContentrainConfig, ValidationError } from '@contentrain/types'
|
|
202
|
+
import { validateFieldValue, validateSlug, detectSecrets } from '@contentrain/types'
|
|
203
|
+
|
|
204
|
+
export function useContentValidation(config: ContentrainConfig) {
|
|
205
|
+
function validateEntry(
|
|
206
|
+
fields: Record<string, FieldDef>,
|
|
207
|
+
data: Record<string, unknown>,
|
|
208
|
+
): ValidationError[] {
|
|
209
|
+
const issues: ValidationError[] = []
|
|
210
|
+
|
|
211
|
+
for (const [fieldName, fieldDef] of Object.entries(fields)) {
|
|
212
|
+
// Schema validation (type, required, min/max, pattern, select)
|
|
213
|
+
const fieldErrors = validateFieldValue(data[fieldName], fieldDef)
|
|
214
|
+
for (const err of fieldErrors) {
|
|
215
|
+
issues.push({ ...err, field: fieldName })
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Secret detection on all string values
|
|
219
|
+
const secretErrors = detectSecrets(data[fieldName])
|
|
220
|
+
for (const err of secretErrors) {
|
|
221
|
+
issues.push({ ...err, field: fieldName })
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return issues
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return { validateEntry, validateSlug }
|
|
229
|
+
}
|
|
138
230
|
```
|
|
139
231
|
|
|
232
|
+
```ts
|
|
233
|
+
// composables/useDocumentEditor.ts
|
|
234
|
+
import { parseMarkdownFrontmatter, serializeMarkdownFrontmatter } from '@contentrain/types'
|
|
235
|
+
|
|
236
|
+
export function useDocumentEditor() {
|
|
237
|
+
function loadDocument(rawMarkdown: string) {
|
|
238
|
+
const { frontmatter, body } = parseMarkdownFrontmatter(rawMarkdown)
|
|
239
|
+
return { frontmatter, body }
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function saveDocument(data: Record<string, unknown>, body: string): string {
|
|
243
|
+
return serializeMarkdownFrontmatter(data, body)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return { loadDocument, saveDocument }
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Unique constraints and relation validation
|
|
251
|
+
|
|
252
|
+
`validateFieldValue` handles schema-level checks. Two things require external state:
|
|
253
|
+
|
|
254
|
+
- **Unique constraints** — need to check across all entries (Studio should query its API/store)
|
|
255
|
+
- **Relation references** — need to verify target entries exist (Studio should query its content API)
|
|
256
|
+
|
|
257
|
+
These are left to Studio's server-side or API layer to implement on top of the pure validation.
|
|
258
|
+
|
|
140
259
|
## 🧠 Design Role
|
|
141
260
|
|
|
142
261
|
`@contentrain/types` exists so every package in the monorepo speaks the same domain language.
|
|
@@ -147,11 +266,13 @@ Examples:
|
|
|
147
266
|
- CLI reads `ContextJson`
|
|
148
267
|
- SDK codegen consumes `ModelDefinition` and `FieldDef`
|
|
149
268
|
- AI rules align with the same model and workflow vocabulary
|
|
269
|
+
- Studio uses the same validation functions in the browser
|
|
150
270
|
|
|
151
271
|
This package should stay:
|
|
152
272
|
|
|
153
273
|
- small
|
|
154
|
-
-
|
|
274
|
+
- zero runtime dependencies
|
|
275
|
+
- browser + Node.js compatible
|
|
155
276
|
- stable
|
|
156
277
|
- free of package-specific behavior
|
|
157
278
|
|
package/dist/index.d.mts
CHANGED
|
@@ -125,8 +125,12 @@ type FileFramework = 'vue' | 'svelte' | 'jsx' | 'astro' | 'script';
|
|
|
125
125
|
/** Root directory name for all Contentrain data */
|
|
126
126
|
declare const CONTENTRAIN_DIR: ".contentrain";
|
|
127
127
|
/**
|
|
128
|
-
*
|
|
128
|
+
* Default path patterns for Contentrain projects.
|
|
129
129
|
* Variables: {domain}, {modelId}, {locale}, {slug}
|
|
130
|
+
*
|
|
131
|
+
* When a model has `content_path` set, content files are written to
|
|
132
|
+
* `{projectRoot}/{content_path}/` instead — but meta files always
|
|
133
|
+
* remain under `.contentrain/meta/`.
|
|
130
134
|
*/
|
|
131
135
|
declare const PATH_PATTERNS: {
|
|
132
136
|
readonly config: ".contentrain/config.json";
|
|
@@ -248,6 +252,57 @@ interface ContextJson {
|
|
|
248
252
|
lastSync: string;
|
|
249
253
|
};
|
|
250
254
|
}
|
|
255
|
+
/** Standard name for the dedicated content branch */
|
|
256
|
+
declare const CONTENTRAIN_BRANCH: "contentrain";
|
|
257
|
+
/** Result of selective file sync to developer's working tree */
|
|
258
|
+
interface SyncResult {
|
|
259
|
+
/** Files successfully synced to working tree */
|
|
260
|
+
synced: string[];
|
|
261
|
+
/** Files skipped due to uncommitted local changes */
|
|
262
|
+
skipped: string[];
|
|
263
|
+
/** Human-readable warning if files were skipped */
|
|
264
|
+
warning?: string;
|
|
265
|
+
}
|
|
266
|
+
/** Structured error for git operations with actionable guidance */
|
|
267
|
+
interface ContentrainError {
|
|
268
|
+
/** Machine-readable error code */
|
|
269
|
+
code: string;
|
|
270
|
+
/** Human-readable error message */
|
|
271
|
+
message: string;
|
|
272
|
+
/** Guidance for AI agents on how to handle this error */
|
|
273
|
+
agent_hint: string;
|
|
274
|
+
/** Command or action the developer should take */
|
|
275
|
+
developer_action: string;
|
|
276
|
+
}
|
|
277
|
+
/** Common patterns for detecting potential secrets in content values */
|
|
278
|
+
declare const SECRET_PATTERNS: ReadonlyArray<RegExp>;
|
|
279
|
+
/** Validate a slug — returns error message or null if valid */
|
|
280
|
+
declare function validateSlug(slug: string): string | null;
|
|
281
|
+
/** Validate an entry ID — returns error message or null if valid */
|
|
282
|
+
declare function validateEntryId(id: string): string | null;
|
|
283
|
+
/** Validate a locale code against pattern and config — returns error message or null if valid */
|
|
284
|
+
declare function validateLocale(locale: string, config: ContentrainConfig): string | null;
|
|
285
|
+
/** Detect potential secrets in a value — returns validation errors if found */
|
|
286
|
+
declare function detectSecrets(value: unknown): ValidationError[];
|
|
287
|
+
/**
|
|
288
|
+
* Validate a field value against its FieldDef schema.
|
|
289
|
+
* Checks: required, type match, min/max, pattern, select options.
|
|
290
|
+
* Does NOT check: secrets (use detectSecrets), uniqueness (stateful), relations (I/O).
|
|
291
|
+
*/
|
|
292
|
+
declare function validateFieldValue(value: unknown, fieldDef: FieldDef): ValidationError[];
|
|
293
|
+
/** Recursively sort object keys — respects optional fieldOrder for top-level keys */
|
|
294
|
+
declare function sortKeys(obj: unknown, fieldOrder?: string[]): unknown;
|
|
295
|
+
/** Canonical JSON serialization — deterministic output for stable git diffs */
|
|
296
|
+
declare function canonicalStringify(data: unknown, fieldOrder?: string[]): string;
|
|
297
|
+
/** Generate a 12-character hex entry ID */
|
|
298
|
+
declare function generateEntryId(): string;
|
|
299
|
+
/** Parse markdown frontmatter — extracts YAML frontmatter and body from a markdown string */
|
|
300
|
+
declare function parseMarkdownFrontmatter(content: string): {
|
|
301
|
+
frontmatter: Record<string, unknown>;
|
|
302
|
+
body: string;
|
|
303
|
+
};
|
|
304
|
+
/** Serialize data + body into markdown frontmatter format */
|
|
305
|
+
declare function serializeMarkdownFrontmatter(data: Record<string, unknown>, body: string): string;
|
|
251
306
|
//#endregion
|
|
252
|
-
export { AssetEntry, CANONICAL_JSON, CONTENTRAIN_DIR, CollectionContentFile, CollectionContentOutput, CollectionEntry, CollectionMeta, CollectionRuntimeFormat, ContentSource, ContentStatus, ContentrainConfig, ContextJson, ContextSource, DictionaryContentFile, DictionaryMeta, DocumentContentOutput, DocumentEntry, DocumentMeta, DuplicateGroup, ENTRY_ID_PATTERN, EntryMeta, FieldDef, FieldType, FileCategory, FileFramework, GraphNode, LOCALE_PATTERN, LocaleStrategy, ModelDefinition, ModelKind, ModelSummary, PATH_PATTERNS, Platform, PolymorphicRelationRef, ProjectGraph, SLUG_PATTERN, ScaffoldTemplate, ScanCandidate, ScanCandidatesResult, ScanSummaryResult, SingletonContentFile, SingletonMeta, StackType, StringContext, ValidationError, ValidationResult, Vocabulary, WorkflowMode };
|
|
307
|
+
export { AssetEntry, CANONICAL_JSON, CONTENTRAIN_BRANCH, CONTENTRAIN_DIR, CollectionContentFile, CollectionContentOutput, CollectionEntry, CollectionMeta, CollectionRuntimeFormat, ContentSource, ContentStatus, ContentrainConfig, ContentrainError, ContextJson, ContextSource, DictionaryContentFile, DictionaryMeta, DocumentContentOutput, DocumentEntry, DocumentMeta, DuplicateGroup, ENTRY_ID_PATTERN, EntryMeta, FieldDef, FieldType, FileCategory, FileFramework, GraphNode, LOCALE_PATTERN, LocaleStrategy, ModelDefinition, ModelKind, ModelSummary, PATH_PATTERNS, Platform, PolymorphicRelationRef, ProjectGraph, SECRET_PATTERNS, SLUG_PATTERN, ScaffoldTemplate, ScanCandidate, ScanCandidatesResult, ScanSummaryResult, SingletonContentFile, SingletonMeta, StackType, StringContext, SyncResult, ValidationError, ValidationResult, Vocabulary, WorkflowMode, canonicalStringify, detectSecrets, generateEntryId, parseMarkdownFrontmatter, serializeMarkdownFrontmatter, sortKeys, validateEntryId, validateFieldValue, validateLocale, validateSlug };
|
|
253
308
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";KAEY,SAAA;AAAA,KAUA,SAAA;AAAA,KACA,aAAA;AAAA,KACA,aAAA;AAAA,KACA,YAAA;AAAA,KAEA,SAAA;AAAA,KAeA,QAAA;AAAA,KACA,aAAA;AAAA,KACA,uBAAA;AAAA,UAIK,QAAA;EACf,IAAA,EAAM,SAAA;EACN,QAAA;EACA,MAAA;EACA,OAAA;EACA,GAAA;EACA,GAAA;EACA,OAAA;EACA,OAAA;EACA,KAAA;EACA,KAAA,YAAiB,QAAA;EACjB,MAAA,GAAS,MAAA,SAAe,QAAA;EACxB,MAAA;EACA,OAAA;EACA,WAAA;AAAA;AAAA,KAKU,cAAA;AAAA,UAEK,eAAA;EACf,EAAA;EACA,IAAA;EACA,IAAA,EAAM,SAAA;EACN,MAAA;EACA,IAAA;EACA,WAAA;EACA,MAAA,GAAS,MAAA,SAAe,QAAA;EAlCN;EAoClB,YAAA;EAnCU;EAqCV,eAAA,GAAkB,cAAA;AAAA;AAAA,UAKH,iBAAA;EACf,OAAA;EACA,QAAA,GAAW,QAAA;EACX,KAAA,EAAO,SAAA;EACP,QAAA,EAAU,YAAA;EACV,UAAA;IACE,QAAA;IACA,KAAA;IACA,IAAA;IACA,cAAA;EAAA;EAEF,OAAA;IACE,OAAA;IACA,SAAA;EAAA;EAEF,OAAA;EACA,WAAA;EACA,eAAA;AAAA;AAAA,UAKe,UAAA;EACf,OAAA;EACA,KAAA,EAAO,MAAA,SAAe,MAAA;AAAA;AAAA,UAKP,SAAA;EACf,MAAA,EAAQ,aAAA;EACR,MAAA,EAAQ,aAAA;EACR,UAAA;EACA,WAAA;EACA,OAAA;EACA,UAAA;EACA,SAAA;AAAA;AAAA,KAGU,aAAA,GAAgB,SAAA;AAAA,KAChB,cAAA,GAAiB,MAAA,SAAe,SAAA;AAAA,KAChC,YAAA,GAAe,SAAA;AAAA,KACf,cAAA,GAAiB,SAAA;AAAA,UAIZ,UAAA;EACf,IAAA;EACA,IAAA;EACA,IAAA;EACA,GAAA;AAAA;AAAA,UAKe,eAAA;EACf,QAAA;EACA,KAAA;EACA,MAAA;EACA,KAAA;EACA,IAAA;EACA,KAAA;EACA,OAAA;AAAA;AAAA,UAGe,gBAAA;EACf,KAAA;EACA,MAAA,EAAQ,eAAA;AAAA;AAAA,KAME,oBAAA,GAAuB,MAAA;AAAA,KACvB,qBAAA,GAAwB,MAAA,SAAe,MAAA;AAAA,KACvC,qBAAA,GAAwB,MAAA;AAAA,KAIxB,eAAA;EAAoB,EAAA;AAAA,IAAe,MAAA;AAAA,KACnC,uBAAA,GAA0B,eAAA;;UAGrB,aAAA;EACf,IAAA;EACA,WAAA,EAAa,MAAA;EACb,IAAA;AAAA;AAAA,KAGU,qBAAA,GAAwB,aAAA;;UAGnB,sBAAA;EACf,KAAA;EACA,GAAA;AAAA;;UAMe,YAAA;EACf,EAAA;EACA,IAAA,EAAM,SAAA;EACN,MAAA;EACA,IAAA;EACA,MAAA;AAAA;;KAMU,aAAA;;cAKC,eAAA
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -2,8 +2,12 @@
|
|
|
2
2
|
/** Root directory name for all Contentrain data */
|
|
3
3
|
const CONTENTRAIN_DIR = ".contentrain";
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Default path patterns for Contentrain projects.
|
|
6
6
|
* Variables: {domain}, {modelId}, {locale}, {slug}
|
|
7
|
+
*
|
|
8
|
+
* When a model has `content_path` set, content files are written to
|
|
9
|
+
* `{projectRoot}/{content_path}/` instead — but meta files always
|
|
10
|
+
* remain under `.contentrain/meta/`.
|
|
7
11
|
*/
|
|
8
12
|
const PATH_PATTERNS = {
|
|
9
13
|
config: ".contentrain/config.json",
|
|
@@ -39,7 +43,326 @@ const CANONICAL_JSON = {
|
|
|
39
43
|
omitDefaults: true,
|
|
40
44
|
sortKeys: true
|
|
41
45
|
};
|
|
46
|
+
/** Standard name for the dedicated content branch */
|
|
47
|
+
const CONTENTRAIN_BRANCH = "contentrain";
|
|
48
|
+
/** Common patterns for detecting potential secrets in content values */
|
|
49
|
+
const SECRET_PATTERNS = [
|
|
50
|
+
/sk_/,
|
|
51
|
+
/pk_/,
|
|
52
|
+
/api_key/i,
|
|
53
|
+
/apikey/i,
|
|
54
|
+
/ghp_/,
|
|
55
|
+
/gho_/,
|
|
56
|
+
/Bearer /,
|
|
57
|
+
/postgres:\/\//,
|
|
58
|
+
/mysql:\/\//,
|
|
59
|
+
/mongodb:\/\//,
|
|
60
|
+
/-----BEGIN/,
|
|
61
|
+
/AKIA/,
|
|
62
|
+
/aws_secret/i
|
|
63
|
+
];
|
|
64
|
+
/** Validate a slug — returns error message or null if valid */
|
|
65
|
+
function validateSlug(slug) {
|
|
66
|
+
if (!slug) return "Slug is required";
|
|
67
|
+
if (!SLUG_PATTERN.test(slug)) return `Invalid slug "${slug}": must be kebab-case alphanumeric (a-z, 0-9, hyphens)`;
|
|
68
|
+
if (slug.startsWith(".") || slug.includes("..")) return `Invalid slug "${slug}": path traversal not allowed`;
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
/** Validate an entry ID — returns error message or null if valid */
|
|
72
|
+
function validateEntryId(id) {
|
|
73
|
+
if (!ENTRY_ID_PATTERN.test(id)) return `Invalid entry ID "${id}": must be 1-40 alphanumeric characters (hyphens and underscores allowed)`;
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
/** Validate a locale code against pattern and config — returns error message or null if valid */
|
|
77
|
+
function validateLocale(locale, config) {
|
|
78
|
+
if (!LOCALE_PATTERN.test(locale)) return `Invalid locale "${locale}": must be ISO 639-1 format (e.g. "en", "tr", "pt-BR")`;
|
|
79
|
+
if (!config.locales.supported.includes(locale)) return `Locale "${locale}" is not in supported locales [${config.locales.supported.join(", ")}]. Update config first.`;
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
/** Detect potential secrets in a value — returns validation errors if found */
|
|
83
|
+
function detectSecrets(value) {
|
|
84
|
+
if (typeof value !== "string") return [];
|
|
85
|
+
if (SECRET_PATTERNS.some((p) => p.test(value))) return [{
|
|
86
|
+
severity: "error",
|
|
87
|
+
message: "Potential secret detected in value"
|
|
88
|
+
}];
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
function fieldTypeMatches(value, fieldDef) {
|
|
92
|
+
if (value === null || value === void 0) return true;
|
|
93
|
+
switch (fieldDef.type) {
|
|
94
|
+
case "string":
|
|
95
|
+
case "text":
|
|
96
|
+
case "email":
|
|
97
|
+
case "url":
|
|
98
|
+
case "slug":
|
|
99
|
+
case "color":
|
|
100
|
+
case "phone":
|
|
101
|
+
case "code":
|
|
102
|
+
case "icon":
|
|
103
|
+
case "markdown":
|
|
104
|
+
case "richtext":
|
|
105
|
+
case "date":
|
|
106
|
+
case "datetime":
|
|
107
|
+
case "image":
|
|
108
|
+
case "video":
|
|
109
|
+
case "file":
|
|
110
|
+
case "select": return typeof value === "string";
|
|
111
|
+
case "number":
|
|
112
|
+
case "integer":
|
|
113
|
+
case "decimal":
|
|
114
|
+
case "percent":
|
|
115
|
+
case "rating": return typeof value === "number";
|
|
116
|
+
case "boolean": return typeof value === "boolean";
|
|
117
|
+
case "relation": return typeof value === "string";
|
|
118
|
+
case "relations":
|
|
119
|
+
case "array": return Array.isArray(value);
|
|
120
|
+
case "object": return typeof value === "object" && !Array.isArray(value);
|
|
121
|
+
default: return true;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function checkMinMax(value, fieldDef) {
|
|
125
|
+
if (fieldDef.min !== void 0) {
|
|
126
|
+
if (typeof value === "number" && value < fieldDef.min) return `Value ${value} is below minimum ${fieldDef.min}`;
|
|
127
|
+
if (typeof value === "string" && value.length < fieldDef.min) return `String length ${value.length} is below minimum ${fieldDef.min}`;
|
|
128
|
+
if (Array.isArray(value) && value.length < fieldDef.min) return `Array length ${value.length} is below minimum ${fieldDef.min}`;
|
|
129
|
+
}
|
|
130
|
+
if (fieldDef.max !== void 0) {
|
|
131
|
+
if (typeof value === "number" && value > fieldDef.max) return `Value ${value} is above maximum ${fieldDef.max}`;
|
|
132
|
+
if (typeof value === "string" && value.length > fieldDef.max) return `String length ${value.length} is above maximum ${fieldDef.max}`;
|
|
133
|
+
if (Array.isArray(value) && value.length > fieldDef.max) return `Array length ${value.length} is above maximum ${fieldDef.max}`;
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Validate a field value against its FieldDef schema.
|
|
139
|
+
* Checks: required, type match, min/max, pattern, select options.
|
|
140
|
+
* Does NOT check: secrets (use detectSecrets), uniqueness (stateful), relations (I/O).
|
|
141
|
+
*/
|
|
142
|
+
function validateFieldValue(value, fieldDef) {
|
|
143
|
+
const issues = [];
|
|
144
|
+
if (fieldDef.required && (value === void 0 || value === null || value === "")) {
|
|
145
|
+
issues.push({
|
|
146
|
+
severity: "error",
|
|
147
|
+
message: "Required field is missing or empty"
|
|
148
|
+
});
|
|
149
|
+
return issues;
|
|
150
|
+
}
|
|
151
|
+
if (value === void 0 || value === null) return issues;
|
|
152
|
+
if (!fieldTypeMatches(value, fieldDef)) {
|
|
153
|
+
issues.push({
|
|
154
|
+
severity: "error",
|
|
155
|
+
message: `Type mismatch: expected ${fieldDef.type}, got ${typeof value}`
|
|
156
|
+
});
|
|
157
|
+
return issues;
|
|
158
|
+
}
|
|
159
|
+
const minMaxErr = checkMinMax(value, fieldDef);
|
|
160
|
+
if (minMaxErr) issues.push({
|
|
161
|
+
severity: "error",
|
|
162
|
+
message: minMaxErr
|
|
163
|
+
});
|
|
164
|
+
if (fieldDef.pattern && typeof value === "string") try {
|
|
165
|
+
if (!new RegExp(fieldDef.pattern).test(value)) issues.push({
|
|
166
|
+
severity: "error",
|
|
167
|
+
message: `Value "${value}" does not match pattern /${fieldDef.pattern}/`
|
|
168
|
+
});
|
|
169
|
+
} catch {
|
|
170
|
+
issues.push({
|
|
171
|
+
severity: "warning",
|
|
172
|
+
message: `Invalid pattern regex: ${fieldDef.pattern}`
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
if (fieldDef.type === "select" && fieldDef.options && typeof value === "string") {
|
|
176
|
+
if (!fieldDef.options.includes(value)) issues.push({
|
|
177
|
+
severity: "error",
|
|
178
|
+
message: `Value "${value}" is not in allowed options: [${fieldDef.options.join(", ")}]`
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
return issues;
|
|
182
|
+
}
|
|
183
|
+
/** Recursively sort object keys — respects optional fieldOrder for top-level keys */
|
|
184
|
+
function sortKeys(obj, fieldOrder) {
|
|
185
|
+
if (obj === null || obj === void 0) return void 0;
|
|
186
|
+
if (Array.isArray(obj)) return obj.map((item) => sortKeys(item, fieldOrder));
|
|
187
|
+
if (typeof obj !== "object") return obj;
|
|
188
|
+
const record = obj;
|
|
189
|
+
const sorted = {};
|
|
190
|
+
const keys = fieldOrder ? [...new Set([...fieldOrder, ...Object.keys(record).toSorted()])] : Object.keys(record).toSorted();
|
|
191
|
+
for (const key of keys) {
|
|
192
|
+
if (!(key in record)) continue;
|
|
193
|
+
const val = record[key];
|
|
194
|
+
if (val === null || val === void 0) continue;
|
|
195
|
+
sorted[key] = sortKeys(val);
|
|
196
|
+
}
|
|
197
|
+
return sorted;
|
|
198
|
+
}
|
|
199
|
+
/** Canonical JSON serialization — deterministic output for stable git diffs */
|
|
200
|
+
function canonicalStringify(data, fieldOrder) {
|
|
201
|
+
const sorted = sortKeys(data, fieldOrder);
|
|
202
|
+
return `${JSON.stringify(sorted, null, 2)}\n`;
|
|
203
|
+
}
|
|
204
|
+
/** Generate a 12-character hex entry ID */
|
|
205
|
+
function generateEntryId() {
|
|
206
|
+
const hex = "0123456789abcdef";
|
|
207
|
+
let id = "";
|
|
208
|
+
for (let i = 0; i < 12; i++) id += hex[Math.floor(Math.random() * 16)];
|
|
209
|
+
return id;
|
|
210
|
+
}
|
|
211
|
+
function parseYamlValue(raw) {
|
|
212
|
+
if (raw === "true") return true;
|
|
213
|
+
if (raw === "false") return false;
|
|
214
|
+
if (raw === "null") return null;
|
|
215
|
+
if (/^-?\d+$/.test(raw)) return parseInt(raw, 10);
|
|
216
|
+
if (/^-?\d+\.\d+$/.test(raw)) return parseFloat(raw);
|
|
217
|
+
if (raw.startsWith("\"") && raw.endsWith("\"") || raw.startsWith("'") && raw.endsWith("'")) return raw.slice(1, -1);
|
|
218
|
+
return raw;
|
|
219
|
+
}
|
|
220
|
+
function yamlScalar(value) {
|
|
221
|
+
const str = String(value);
|
|
222
|
+
if (str.includes(":") || str.includes("#") || str.includes("{") || str.includes("}") || str.includes("[") || str.includes("]") || str.includes("*") || str.includes("&") || str.includes("!") || str.includes("|") || str.includes(">") || str.includes("'") || str.includes("\"") || str.includes("%") || str.includes("@") || str.includes("`") || str.startsWith("-") || str.startsWith("?") || str === "true" || str === "false" || str === "null" || str === "yes" || str === "no" || str === "") return `"${str.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
|
|
223
|
+
return str;
|
|
224
|
+
}
|
|
225
|
+
function serializeYamlValue(value, indent) {
|
|
226
|
+
const pad = " ".repeat(indent);
|
|
227
|
+
if (value === null || value === void 0) return [];
|
|
228
|
+
if (typeof value === "object" && !Array.isArray(value)) {
|
|
229
|
+
const lines = [];
|
|
230
|
+
for (const [k, v] of Object.entries(value)) {
|
|
231
|
+
if (v === null || v === void 0) continue;
|
|
232
|
+
if (typeof v === "object" && !Array.isArray(v)) {
|
|
233
|
+
lines.push(`${pad}${k}:`);
|
|
234
|
+
lines.push(...serializeYamlValue(v, indent + 1));
|
|
235
|
+
} else if (Array.isArray(v)) {
|
|
236
|
+
lines.push(`${pad}${k}:`);
|
|
237
|
+
for (const item of v) if (typeof item === "object" && item !== null && !Array.isArray(item)) {
|
|
238
|
+
const nested = serializeYamlValue(item, indent + 2);
|
|
239
|
+
if (nested.length > 0) {
|
|
240
|
+
lines.push(`${pad} - ${nested[0].trimStart()}`);
|
|
241
|
+
lines.push(...nested.slice(1));
|
|
242
|
+
}
|
|
243
|
+
} else lines.push(`${pad} - ${yamlScalar(item)}`);
|
|
244
|
+
} else lines.push(`${pad}${k}: ${yamlScalar(v)}`);
|
|
245
|
+
}
|
|
246
|
+
return lines;
|
|
247
|
+
}
|
|
248
|
+
if (Array.isArray(value)) {
|
|
249
|
+
const lines = [];
|
|
250
|
+
for (const item of value) if (typeof item === "object" && item !== null && !Array.isArray(item)) {
|
|
251
|
+
const nested = serializeYamlValue(item, indent + 1);
|
|
252
|
+
if (nested.length > 0) {
|
|
253
|
+
lines.push(`${pad}- ${nested[0].trimStart()}`);
|
|
254
|
+
lines.push(...nested.slice(1));
|
|
255
|
+
}
|
|
256
|
+
} else lines.push(`${pad}- ${yamlScalar(item)}`);
|
|
257
|
+
return lines;
|
|
258
|
+
}
|
|
259
|
+
return [`${pad}${yamlScalar(value)}`];
|
|
260
|
+
}
|
|
261
|
+
function serializeYamlFields(data) {
|
|
262
|
+
const lines = [];
|
|
263
|
+
for (const [key, value] of Object.entries(data)) {
|
|
264
|
+
if (key === "body") continue;
|
|
265
|
+
if (value === null || value === void 0) continue;
|
|
266
|
+
if (typeof value === "object" && !Array.isArray(value)) {
|
|
267
|
+
lines.push(`${key}:`);
|
|
268
|
+
lines.push(...serializeYamlValue(value, 1));
|
|
269
|
+
} else if (Array.isArray(value)) {
|
|
270
|
+
lines.push(`${key}:`);
|
|
271
|
+
for (const item of value) if (typeof item === "object" && item !== null && !Array.isArray(item)) {
|
|
272
|
+
const nested = serializeYamlValue(item, 2);
|
|
273
|
+
if (nested.length > 0) {
|
|
274
|
+
lines.push(` - ${nested[0].trimStart()}`);
|
|
275
|
+
lines.push(...nested.slice(1));
|
|
276
|
+
}
|
|
277
|
+
} else lines.push(` - ${yamlScalar(item)}`);
|
|
278
|
+
} else lines.push(`${key}: ${yamlScalar(value)}`);
|
|
279
|
+
}
|
|
280
|
+
return lines;
|
|
281
|
+
}
|
|
282
|
+
/** Parse markdown frontmatter — extracts YAML frontmatter and body from a markdown string */
|
|
283
|
+
function parseMarkdownFrontmatter(content) {
|
|
284
|
+
const normalized = content.replace(/\r\n/g, "\n");
|
|
285
|
+
const match = normalized.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
286
|
+
if (!match) return {
|
|
287
|
+
frontmatter: {},
|
|
288
|
+
body: normalized
|
|
289
|
+
};
|
|
290
|
+
const frontmatterStr = match[1];
|
|
291
|
+
const body = match[2].trim();
|
|
292
|
+
const frontmatter = {};
|
|
293
|
+
let currentKey = null;
|
|
294
|
+
let currentArray = null;
|
|
295
|
+
for (const line of frontmatterStr.split("\n")) {
|
|
296
|
+
if (/^\s+-\s+/.test(line) && currentKey) {
|
|
297
|
+
const value = line.replace(/^\s+-\s+/, "").trim();
|
|
298
|
+
if (!currentArray) currentArray = [];
|
|
299
|
+
currentArray.push(value);
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
if (currentKey && currentArray) {
|
|
303
|
+
frontmatter[currentKey] = currentArray;
|
|
304
|
+
currentKey = null;
|
|
305
|
+
currentArray = null;
|
|
306
|
+
}
|
|
307
|
+
const kvMatch = line.match(/^([\w][\w.-]*)\s*:\s*(.*)$/);
|
|
308
|
+
if (!kvMatch) continue;
|
|
309
|
+
const key = kvMatch[1];
|
|
310
|
+
const rawValue = kvMatch[2].trim();
|
|
311
|
+
if (rawValue === "") {
|
|
312
|
+
currentKey = key;
|
|
313
|
+
currentArray = [];
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
if (rawValue.startsWith("[") && rawValue.endsWith("]")) {
|
|
317
|
+
frontmatter[key] = rawValue.slice(1, -1).split(",").map((s) => s.trim()).filter(Boolean);
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
frontmatter[key] = parseYamlValue(rawValue);
|
|
321
|
+
}
|
|
322
|
+
if (currentKey && currentArray) frontmatter[currentKey] = currentArray;
|
|
323
|
+
return {
|
|
324
|
+
frontmatter,
|
|
325
|
+
body
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
/** Serialize data + body into markdown frontmatter format */
|
|
329
|
+
function serializeMarkdownFrontmatter(data, body) {
|
|
330
|
+
const trimmedBody = body.trimStart();
|
|
331
|
+
if (trimmedBody.startsWith("---")) {
|
|
332
|
+
const endIdx = trimmedBody.indexOf("---", 3);
|
|
333
|
+
if (endIdx > 0) {
|
|
334
|
+
const bodyFmContent = trimmedBody.slice(3, endIdx).trim();
|
|
335
|
+
const afterFm = trimmedBody.slice(endIdx + 3);
|
|
336
|
+
const lines = ["---"];
|
|
337
|
+
lines.push(...serializeYamlFields(data));
|
|
338
|
+
if (bodyFmContent) {
|
|
339
|
+
const modelKeys = new Set(Object.keys(data));
|
|
340
|
+
for (const line of bodyFmContent.split("\n")) {
|
|
341
|
+
if (line.length > 0 && line[0] !== " " && line[0] !== "-") {
|
|
342
|
+
const colonIdx = line.indexOf(":");
|
|
343
|
+
const lineKey = colonIdx > 0 ? line.slice(0, colonIdx).trim() : "";
|
|
344
|
+
if (lineKey && modelKeys.has(lineKey)) continue;
|
|
345
|
+
}
|
|
346
|
+
lines.push(line);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
lines.push("---");
|
|
350
|
+
if (afterFm.trim()) lines.push(afterFm.trimStart());
|
|
351
|
+
lines.push("");
|
|
352
|
+
return lines.join("\n");
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
const lines = ["---"];
|
|
356
|
+
lines.push(...serializeYamlFields(data));
|
|
357
|
+
lines.push("---");
|
|
358
|
+
lines.push("");
|
|
359
|
+
if (body) {
|
|
360
|
+
lines.push(body);
|
|
361
|
+
lines.push("");
|
|
362
|
+
}
|
|
363
|
+
return lines.join("\n");
|
|
364
|
+
}
|
|
42
365
|
//#endregion
|
|
43
|
-
export { CANONICAL_JSON, CONTENTRAIN_DIR, ENTRY_ID_PATTERN, LOCALE_PATTERN, PATH_PATTERNS, SLUG_PATTERN };
|
|
366
|
+
export { CANONICAL_JSON, CONTENTRAIN_BRANCH, CONTENTRAIN_DIR, ENTRY_ID_PATTERN, LOCALE_PATTERN, PATH_PATTERNS, SECRET_PATTERNS, SLUG_PATTERN, canonicalStringify, detectSecrets, generateEntryId, parseMarkdownFrontmatter, serializeMarkdownFrontmatter, sortKeys, validateEntryId, validateFieldValue, validateLocale, validateSlug };
|
|
44
367
|
|
|
45
368
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["// ─── Type System ───\n\nexport type FieldType =\n | 'string' | 'text' | 'email' | 'url' | 'slug' | 'color' | 'phone' | 'code' | 'icon'\n | 'markdown' | 'richtext'\n | 'number' | 'integer' | 'decimal' | 'percent' | 'rating'\n | 'boolean'\n | 'date' | 'datetime'\n | 'image' | 'video' | 'file'\n | 'relation' | 'relations'\n | 'select' | 'array' | 'object'\n\nexport type ModelKind = 'singleton' | 'collection' | 'document' | 'dictionary'\nexport type ContentStatus = 'draft' | 'in_review' | 'published' | 'rejected' | 'archived'\nexport type ContentSource = 'agent' | 'human' | 'import'\nexport type WorkflowMode = 'auto-merge' | 'review'\n// Meta-frameworks\nexport type StackType =\n | 'nuxt' | 'next' | 'astro' | 'sveltekit' | 'remix' | 'analog'\n // Plain frameworks\n | 'vue' | 'react' | 'svelte' | 'solid' | 'angular'\n // Mobile\n | 'react-native' | 'expo' | 'flutter'\n // Backend\n | 'node' | 'express' | 'fastify' | 'nestjs' | 'django' | 'rails' | 'laravel' | 'go' | 'rust' | 'dotnet'\n // Static site generators\n | 'hugo' | 'jekyll' | 'eleventy'\n // Desktop\n | 'electron' | 'tauri'\n // Catch-all\n | 'other'\n\nexport type Platform = 'web' | 'mobile' | 'api' | 'desktop' | 'static' | 'other'\nexport type ContextSource = 'mcp-local' | 'mcp-studio' | 'studio-ui'\nexport type CollectionRuntimeFormat = 'map' | 'array'\n\n// ─── Field Definition ───\n\nexport interface FieldDef {\n type: FieldType\n required?: boolean\n unique?: boolean\n default?: unknown\n min?: number\n max?: number\n pattern?: string\n options?: string[]\n model?: string | string[]\n items?: string | FieldDef\n fields?: Record<string, FieldDef>\n accept?: string\n maxSize?: number\n description?: string\n}\n\n// ─── Model Definition ───\n\nexport type LocaleStrategy = 'file' | 'suffix' | 'directory' | 'none'\n\nexport interface ModelDefinition {\n id: string\n name: string\n kind: ModelKind\n domain: string\n i18n: boolean\n description?: string\n fields?: Record<string, FieldDef>\n /** Framework-relative path for content files (e.g. \"content/blog\", \"locales\"). When set, content is written here instead of .contentrain/content/ */\n content_path?: string\n /** How locale is encoded in file names. Default: \"file\" ({dir}/{locale}.json or {dir}/{slug}/{locale}.md) */\n locale_strategy?: LocaleStrategy\n}\n\n// ─── Config ───\n\nexport interface ContentrainConfig {\n version: number\n platform?: Platform\n stack: StackType\n workflow: WorkflowMode\n repository?: {\n provider: 'github'\n owner: string\n name: string\n default_branch: string\n }\n locales: {\n default: string\n supported: string[]\n }\n domains: string[]\n assets_path?: string\n branchRetention?: number\n}\n\n// ─── Vocabulary ───\n\nexport interface Vocabulary {\n version: number\n terms: Record<string, Record<string, string>>\n}\n\n// ─── Metadata ───\n\nexport interface EntryMeta {\n status: ContentStatus\n source: ContentSource\n updated_by: string\n approved_by?: string | null\n version?: string\n publish_at?: string\n expire_at?: string\n}\n\nexport type SingletonMeta = EntryMeta\nexport type CollectionMeta = Record<string, EntryMeta>\nexport type DocumentMeta = EntryMeta\nexport type DictionaryMeta = EntryMeta\n\n// ─── Asset ───\n\nexport interface AssetEntry {\n path: string\n type: string\n size: number\n alt?: string\n}\n\n// ─── Validation ───\n\nexport interface ValidationError {\n severity: 'error' | 'warning' | 'notice'\n model?: string\n locale?: string\n entry?: string\n slug?: string\n field?: string\n message: string\n}\n\nexport interface ValidationResult {\n valid: boolean\n errors: ValidationError[]\n}\n\n// ─── Content Types (Storage vs Output) ───\n\n// Storage formats — how content is written to disk (canonical JSON)\nexport type SingletonContentFile = Record<string, unknown>\nexport type CollectionContentFile = Record<string, Record<string, unknown>>\nexport type DictionaryContentFile = Record<string, string>\n// Document storage = markdown file (frontmatter + body), not JSON\n\n// Output formats — how MCP/SDK returns content to consumers\nexport type CollectionEntry = { id: string } & Record<string, unknown>\nexport type CollectionContentOutput = CollectionEntry[]\n\n/** Document entry as returned by MCP/SDK — parsed markdown with frontmatter */\nexport interface DocumentEntry {\n slug: string\n frontmatter: Record<string, unknown>\n body: string\n}\n\nexport type DocumentContentOutput = DocumentEntry[]\n\n/** Polymorphic relation storage — used when model field is string[] (multiple targets) */\nexport interface PolymorphicRelationRef {\n model: string\n ref: string\n}\n\n// ─── Model Summary ───\n\n/** Lightweight model info for listing and status operations */\nexport interface ModelSummary {\n id: string\n kind: ModelKind\n domain: string\n i18n: boolean\n fields: number\n}\n\n// ─── File Framework ───\n\n/** Source file framework — detected from extension for normalize/apply operations */\nexport type FileFramework = 'vue' | 'svelte' | 'jsx' | 'astro' | 'script'\n\n// ─── Path Conventions ───\n\n/** Root directory name for all Contentrain data */\nexport const CONTENTRAIN_DIR = '.contentrain' as const\n\n/**\n * Standard path patterns for Contentrain projects.\n * Variables: {domain}, {modelId}, {locale}, {slug}\n */\nexport const PATH_PATTERNS = {\n config: '.contentrain/config.json',\n context: '.contentrain/context.json',\n vocabulary: '.contentrain/vocabulary.json',\n model: '.contentrain/models/{modelId}.json',\n content: {\n singleton: '.contentrain/content/{domain}/{modelId}/{locale}.json',\n collection: '.contentrain/content/{domain}/{modelId}/{locale}.json',\n document: '.contentrain/content/{domain}/{slug}/{locale}.md',\n dictionary: '.contentrain/content/{domain}/{modelId}/{locale}.json',\n /** Non-i18n content (i18n: false) */\n noLocale: '.contentrain/content/{domain}/{modelId}/data.json',\n },\n meta: {\n singleton: '.contentrain/meta/{modelId}/{locale}.json',\n collection: '.contentrain/meta/{modelId}/{locale}.json',\n document: '.contentrain/meta/{modelId}/{slug}/{locale}.json',\n dictionary: '.contentrain/meta/{modelId}/{locale}.json',\n },\n} as const\n\n// ─── Validation Patterns ───\n\n/** Slug: lowercase alphanumeric with hyphens */\nexport const SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/\n/** Entry ID: alphanumeric, 1-40 chars, starts with alphanumeric */\nexport const ENTRY_ID_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,39}$/\n/** Locale: ISO 639-1 with optional region code */\nexport const LOCALE_PATTERN = /^[a-z]{2}(?:-[A-Z]{2})?$/\n\n// ─── Canonical Serialization ───\n\n/** Rules for deterministic JSON output — ensures stable git diffs */\nexport const CANONICAL_JSON = {\n indent: 2,\n encoding: 'utf-8',\n trailingNewline: true,\n omitNull: true,\n omitDefaults: true,\n sortKeys: true,\n} as const\n\n// ─── Scaffold ───\n\nexport interface ScaffoldTemplate {\n id: string\n models: ModelDefinition[]\n sample_content?: Record<string, Record<string, unknown>>\n vocabulary?: Record<string, Record<string, string>>\n}\n\n// ─── Scan Types ───\n\nexport type StringContext =\n | 'jsx_text' | 'jsx_attribute'\n | 'template_text' | 'template_attribute'\n | 'variable_assignment' | 'function_argument'\n | 'object_value' | 'other'\n\nexport type FileCategory = 'page' | 'component' | 'layout' | 'other'\n\nexport interface ScanCandidate {\n file: string\n line: number\n column: number\n value: string\n context: StringContext\n surrounding: string\n}\n\nexport interface DuplicateGroup {\n value: string\n count: number\n occurrences: Array<{ file: string; line: number }>\n}\n\nexport interface GraphNode {\n file: string\n category: FileCategory\n components?: string[]\n imports: string[]\n used_by: string[]\n strings: number\n}\n\nexport interface ProjectGraph {\n pages: GraphNode[]\n components: GraphNode[]\n layouts: GraphNode[]\n orphan_files: string[]\n stats: {\n total_files: number\n total_components: number\n total_pages: number\n total_strings_estimate: number\n }\n}\n\nexport interface ScanCandidatesResult {\n candidates: ScanCandidate[]\n duplicates: DuplicateGroup[]\n stats: {\n files_scanned: number\n raw_strings_found: number\n after_filtering: number\n candidates_returned: number\n has_more: boolean\n }\n}\n\nexport interface ScanSummaryResult {\n total_files: number\n total_candidates_estimate: number\n by_directory: Record<string, { files: number; candidates: number }>\n top_repeated: Array<{ value: string; count: number }>\n sampling_note?: string\n file_types: Record<string, number>\n}\n\n// ─── Context ───\n\nexport interface ContextJson {\n version: string\n lastOperation: {\n tool: string\n model: string\n locale: string\n entries?: string[]\n timestamp: string\n source: ContextSource\n }\n stats: {\n models: number\n entries: number\n locales: string[]\n lastSync: string\n }\n}\n"],"mappings":";;AA+LA,MAAa,kBAAkB;;;;;AAM/B,MAAa,gBAAgB;CAC3B,QAAQ;CACR,SAAS;CACT,YAAY;CACZ,OAAO;CACP,SAAS;EACP,WAAW;EACX,YAAY;EACZ,UAAU;EACV,YAAY;EAEZ,UAAU;EACX;CACD,MAAM;EACJ,WAAW;EACX,YAAY;EACZ,UAAU;EACV,YAAY;EACb;CACF;;AAKD,MAAa,eAAe;;AAE5B,MAAa,mBAAmB;;AAEhC,MAAa,iBAAiB;;AAK9B,MAAa,iBAAiB;CAC5B,QAAQ;CACR,UAAU;CACV,iBAAiB;CACjB,UAAU;CACV,cAAc;CACd,UAAU;CACX"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["// ─── Type System ───\n\nexport type FieldType =\n | 'string' | 'text' | 'email' | 'url' | 'slug' | 'color' | 'phone' | 'code' | 'icon'\n | 'markdown' | 'richtext'\n | 'number' | 'integer' | 'decimal' | 'percent' | 'rating'\n | 'boolean'\n | 'date' | 'datetime'\n | 'image' | 'video' | 'file'\n | 'relation' | 'relations'\n | 'select' | 'array' | 'object'\n\nexport type ModelKind = 'singleton' | 'collection' | 'document' | 'dictionary'\nexport type ContentStatus = 'draft' | 'in_review' | 'published' | 'rejected' | 'archived'\nexport type ContentSource = 'agent' | 'human' | 'import'\nexport type WorkflowMode = 'auto-merge' | 'review'\n// Meta-frameworks\nexport type StackType =\n | 'nuxt' | 'next' | 'astro' | 'sveltekit' | 'remix' | 'analog'\n // Plain frameworks\n | 'vue' | 'react' | 'svelte' | 'solid' | 'angular'\n // Mobile\n | 'react-native' | 'expo' | 'flutter'\n // Backend\n | 'node' | 'express' | 'fastify' | 'nestjs' | 'django' | 'rails' | 'laravel' | 'go' | 'rust' | 'dotnet'\n // Static site generators\n | 'hugo' | 'jekyll' | 'eleventy'\n // Desktop\n | 'electron' | 'tauri'\n // Catch-all\n | 'other'\n\nexport type Platform = 'web' | 'mobile' | 'api' | 'desktop' | 'static' | 'other'\nexport type ContextSource = 'mcp-local' | 'mcp-studio' | 'studio-ui'\nexport type CollectionRuntimeFormat = 'map' | 'array'\n\n// ─── Field Definition ───\n\nexport interface FieldDef {\n type: FieldType\n required?: boolean\n unique?: boolean\n default?: unknown\n min?: number\n max?: number\n pattern?: string\n options?: string[]\n model?: string | string[]\n items?: string | FieldDef\n fields?: Record<string, FieldDef>\n accept?: string\n maxSize?: number\n description?: string\n}\n\n// ─── Model Definition ───\n\nexport type LocaleStrategy = 'file' | 'suffix' | 'directory' | 'none'\n\nexport interface ModelDefinition {\n id: string\n name: string\n kind: ModelKind\n domain: string\n i18n: boolean\n description?: string\n fields?: Record<string, FieldDef>\n /** Framework-relative path for content files (e.g. \"content/blog\", \"locales\"). When set, content is written here instead of .contentrain/content/ */\n content_path?: string\n /** How locale is encoded in file names. Default: \"file\" ({dir}/{locale}.json or {dir}/{slug}/{locale}.md) */\n locale_strategy?: LocaleStrategy\n}\n\n// ─── Config ───\n\nexport interface ContentrainConfig {\n version: number\n platform?: Platform\n stack: StackType\n workflow: WorkflowMode\n repository?: {\n provider: 'github'\n owner: string\n name: string\n default_branch: string\n }\n locales: {\n default: string\n supported: string[]\n }\n domains: string[]\n assets_path?: string\n branchRetention?: number\n}\n\n// ─── Vocabulary ───\n\nexport interface Vocabulary {\n version: number\n terms: Record<string, Record<string, string>>\n}\n\n// ─── Metadata ───\n\nexport interface EntryMeta {\n status: ContentStatus\n source: ContentSource\n updated_by: string\n approved_by?: string | null\n version?: string\n publish_at?: string\n expire_at?: string\n}\n\nexport type SingletonMeta = EntryMeta\nexport type CollectionMeta = Record<string, EntryMeta>\nexport type DocumentMeta = EntryMeta\nexport type DictionaryMeta = EntryMeta\n\n// ─── Asset ───\n\nexport interface AssetEntry {\n path: string\n type: string\n size: number\n alt?: string\n}\n\n// ─── Validation ───\n\nexport interface ValidationError {\n severity: 'error' | 'warning' | 'notice'\n model?: string\n locale?: string\n entry?: string\n slug?: string\n field?: string\n message: string\n}\n\nexport interface ValidationResult {\n valid: boolean\n errors: ValidationError[]\n}\n\n// ─── Content Types (Storage vs Output) ───\n\n// Storage formats — how content is written to disk (canonical JSON)\nexport type SingletonContentFile = Record<string, unknown>\nexport type CollectionContentFile = Record<string, Record<string, unknown>>\nexport type DictionaryContentFile = Record<string, string>\n// Document storage = markdown file (frontmatter + body), not JSON\n\n// Output formats — how MCP/SDK returns content to consumers\nexport type CollectionEntry = { id: string } & Record<string, unknown>\nexport type CollectionContentOutput = CollectionEntry[]\n\n/** Document entry as returned by MCP/SDK — parsed markdown with frontmatter */\nexport interface DocumentEntry {\n slug: string\n frontmatter: Record<string, unknown>\n body: string\n}\n\nexport type DocumentContentOutput = DocumentEntry[]\n\n/** Polymorphic relation storage — used when model field is string[] (multiple targets) */\nexport interface PolymorphicRelationRef {\n model: string\n ref: string\n}\n\n// ─── Model Summary ───\n\n/** Lightweight model info for listing and status operations */\nexport interface ModelSummary {\n id: string\n kind: ModelKind\n domain: string\n i18n: boolean\n fields: number\n}\n\n// ─── File Framework ───\n\n/** Source file framework — detected from extension for normalize/apply operations */\nexport type FileFramework = 'vue' | 'svelte' | 'jsx' | 'astro' | 'script'\n\n// ─── Path Conventions ───\n\n/** Root directory name for all Contentrain data */\nexport const CONTENTRAIN_DIR = '.contentrain' as const\n\n/**\n * Default path patterns for Contentrain projects.\n * Variables: {domain}, {modelId}, {locale}, {slug}\n *\n * When a model has `content_path` set, content files are written to\n * `{projectRoot}/{content_path}/` instead — but meta files always\n * remain under `.contentrain/meta/`.\n */\nexport const PATH_PATTERNS = {\n config: '.contentrain/config.json',\n context: '.contentrain/context.json',\n vocabulary: '.contentrain/vocabulary.json',\n model: '.contentrain/models/{modelId}.json',\n content: {\n singleton: '.contentrain/content/{domain}/{modelId}/{locale}.json',\n collection: '.contentrain/content/{domain}/{modelId}/{locale}.json',\n document: '.contentrain/content/{domain}/{slug}/{locale}.md',\n dictionary: '.contentrain/content/{domain}/{modelId}/{locale}.json',\n /** Non-i18n content (i18n: false) */\n noLocale: '.contentrain/content/{domain}/{modelId}/data.json',\n },\n meta: {\n singleton: '.contentrain/meta/{modelId}/{locale}.json',\n collection: '.contentrain/meta/{modelId}/{locale}.json',\n document: '.contentrain/meta/{modelId}/{slug}/{locale}.json',\n dictionary: '.contentrain/meta/{modelId}/{locale}.json',\n },\n} as const\n\n// ─── Validation Patterns ───\n\n/** Slug: lowercase alphanumeric with hyphens */\nexport const SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/\n/** Entry ID: alphanumeric, 1-40 chars, starts with alphanumeric */\nexport const ENTRY_ID_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,39}$/\n/** Locale: ISO 639-1 with optional region code */\nexport const LOCALE_PATTERN = /^[a-z]{2}(?:-[A-Z]{2})?$/\n\n// ─── Canonical Serialization ───\n\n/** Rules for deterministic JSON output — ensures stable git diffs */\nexport const CANONICAL_JSON = {\n indent: 2,\n encoding: 'utf-8',\n trailingNewline: true,\n omitNull: true,\n omitDefaults: true,\n sortKeys: true,\n} as const\n\n// ─── Scaffold ───\n\nexport interface ScaffoldTemplate {\n id: string\n models: ModelDefinition[]\n sample_content?: Record<string, Record<string, unknown>>\n vocabulary?: Record<string, Record<string, string>>\n}\n\n// ─── Scan Types ───\n\nexport type StringContext =\n | 'jsx_text' | 'jsx_attribute'\n | 'template_text' | 'template_attribute'\n | 'variable_assignment' | 'function_argument'\n | 'object_value' | 'other'\n\nexport type FileCategory = 'page' | 'component' | 'layout' | 'other'\n\nexport interface ScanCandidate {\n file: string\n line: number\n column: number\n value: string\n context: StringContext\n surrounding: string\n}\n\nexport interface DuplicateGroup {\n value: string\n count: number\n occurrences: Array<{ file: string; line: number }>\n}\n\nexport interface GraphNode {\n file: string\n category: FileCategory\n components?: string[]\n imports: string[]\n used_by: string[]\n strings: number\n}\n\nexport interface ProjectGraph {\n pages: GraphNode[]\n components: GraphNode[]\n layouts: GraphNode[]\n orphan_files: string[]\n stats: {\n total_files: number\n total_components: number\n total_pages: number\n total_strings_estimate: number\n }\n}\n\nexport interface ScanCandidatesResult {\n candidates: ScanCandidate[]\n duplicates: DuplicateGroup[]\n stats: {\n files_scanned: number\n raw_strings_found: number\n after_filtering: number\n candidates_returned: number\n has_more: boolean\n }\n}\n\nexport interface ScanSummaryResult {\n total_files: number\n total_candidates_estimate: number\n by_directory: Record<string, { files: number; candidates: number }>\n top_repeated: Array<{ value: string; count: number }>\n sampling_note?: string\n file_types: Record<string, number>\n}\n\n// ─── Context ───\n\nexport interface ContextJson {\n version: string\n lastOperation: {\n tool: string\n model: string\n locale: string\n entries?: string[]\n timestamp: string\n source: ContextSource\n }\n stats: {\n models: number\n entries: number\n locales: string[]\n lastSync: string\n }\n}\n\n// ─── Git Transaction Types ───\n\n/** Standard name for the dedicated content branch */\nexport const CONTENTRAIN_BRANCH = 'contentrain' as const\n\n/** Result of selective file sync to developer's working tree */\nexport interface SyncResult {\n /** Files successfully synced to working tree */\n synced: string[]\n /** Files skipped due to uncommitted local changes */\n skipped: string[]\n /** Human-readable warning if files were skipped */\n warning?: string\n}\n\n/** Structured error for git operations with actionable guidance */\nexport interface ContentrainError {\n /** Machine-readable error code */\n code: string\n /** Human-readable error message */\n message: string\n /** Guidance for AI agents on how to handle this error */\n agent_hint: string\n /** Command or action the developer should take */\n developer_action: string\n}\n\n// ─── Validate: Pure Functions ───\n\n/** Common patterns for detecting potential secrets in content values */\nexport const SECRET_PATTERNS: ReadonlyArray<RegExp> = [\n /sk_/, /pk_/, /api_key/i, /apikey/i,\n /ghp_/, /gho_/, /Bearer /,\n /postgres:\\/\\//, /mysql:\\/\\//, /mongodb:\\/\\//,\n /-----BEGIN/, /AKIA/, /aws_secret/i,\n]\n\n/** Validate a slug — returns error message or null if valid */\nexport function validateSlug(slug: string): string | null {\n if (!slug) return 'Slug is required'\n if (!SLUG_PATTERN.test(slug)) return `Invalid slug \"${slug}\": must be kebab-case alphanumeric (a-z, 0-9, hyphens)`\n if (slug.startsWith('.') || slug.includes('..')) return `Invalid slug \"${slug}\": path traversal not allowed`\n return null\n}\n\n/** Validate an entry ID — returns error message or null if valid */\nexport function validateEntryId(id: string): string | null {\n if (!ENTRY_ID_PATTERN.test(id)) return `Invalid entry ID \"${id}\": must be 1-40 alphanumeric characters (hyphens and underscores allowed)`\n return null\n}\n\n/** Validate a locale code against pattern and config — returns error message or null if valid */\nexport function validateLocale(locale: string, config: ContentrainConfig): string | null {\n if (!LOCALE_PATTERN.test(locale)) return `Invalid locale \"${locale}\": must be ISO 639-1 format (e.g. \"en\", \"tr\", \"pt-BR\")`\n if (!config.locales.supported.includes(locale)) {\n return `Locale \"${locale}\" is not in supported locales [${config.locales.supported.join(', ')}]. Update config first.`\n }\n return null\n}\n\n/** Detect potential secrets in a value — returns validation errors if found */\nexport function detectSecrets(value: unknown): ValidationError[] {\n if (typeof value !== 'string') return []\n if (SECRET_PATTERNS.some(p => p.test(value))) {\n return [{ severity: 'error', message: 'Potential secret detected in value' }]\n }\n return []\n}\n\nfunction fieldTypeMatches(value: unknown, fieldDef: FieldDef): boolean {\n if (value === null || value === undefined) return true\n\n switch (fieldDef.type) {\n case 'string':\n case 'text':\n case 'email':\n case 'url':\n case 'slug':\n case 'color':\n case 'phone':\n case 'code':\n case 'icon':\n case 'markdown':\n case 'richtext':\n case 'date':\n case 'datetime':\n case 'image':\n case 'video':\n case 'file':\n case 'select':\n return typeof value === 'string'\n\n case 'number':\n case 'integer':\n case 'decimal':\n case 'percent':\n case 'rating':\n return typeof value === 'number'\n\n case 'boolean':\n return typeof value === 'boolean'\n\n case 'relation':\n return typeof value === 'string'\n\n case 'relations':\n case 'array':\n return Array.isArray(value)\n\n case 'object':\n return typeof value === 'object' && !Array.isArray(value)\n\n default:\n return true\n }\n}\n\nfunction checkMinMax(value: unknown, fieldDef: FieldDef): string | null {\n if (fieldDef.min !== undefined) {\n if (typeof value === 'number' && value < fieldDef.min) {\n return `Value ${value} is below minimum ${fieldDef.min}`\n }\n if (typeof value === 'string' && value.length < fieldDef.min) {\n return `String length ${value.length} is below minimum ${fieldDef.min}`\n }\n if (Array.isArray(value) && value.length < fieldDef.min) {\n return `Array length ${value.length} is below minimum ${fieldDef.min}`\n }\n }\n if (fieldDef.max !== undefined) {\n if (typeof value === 'number' && value > fieldDef.max) {\n return `Value ${value} is above maximum ${fieldDef.max}`\n }\n if (typeof value === 'string' && value.length > fieldDef.max) {\n return `String length ${value.length} is above maximum ${fieldDef.max}`\n }\n if (Array.isArray(value) && value.length > fieldDef.max) {\n return `Array length ${value.length} is above maximum ${fieldDef.max}`\n }\n }\n return null\n}\n\n/**\n * Validate a field value against its FieldDef schema.\n * Checks: required, type match, min/max, pattern, select options.\n * Does NOT check: secrets (use detectSecrets), uniqueness (stateful), relations (I/O).\n */\nexport function validateFieldValue(value: unknown, fieldDef: FieldDef): ValidationError[] {\n const issues: ValidationError[] = []\n\n if (fieldDef.required && (value === undefined || value === null || value === '')) {\n issues.push({ severity: 'error', message: 'Required field is missing or empty' })\n return issues\n }\n\n if (value === undefined || value === null) return issues\n\n if (!fieldTypeMatches(value, fieldDef)) {\n issues.push({ severity: 'error', message: `Type mismatch: expected ${fieldDef.type}, got ${typeof value}` })\n return issues\n }\n\n const minMaxErr = checkMinMax(value, fieldDef)\n if (minMaxErr) {\n issues.push({ severity: 'error', message: minMaxErr })\n }\n\n if (fieldDef.pattern && typeof value === 'string') {\n try {\n const regex = new RegExp(fieldDef.pattern)\n if (!regex.test(value)) {\n issues.push({ severity: 'error', message: `Value \"${value}\" does not match pattern /${fieldDef.pattern}/` })\n }\n } catch {\n issues.push({ severity: 'warning', message: `Invalid pattern regex: ${fieldDef.pattern}` })\n }\n }\n\n if (fieldDef.type === 'select' && fieldDef.options && typeof value === 'string') {\n if (!fieldDef.options.includes(value)) {\n issues.push({ severity: 'error', message: `Value \"${value}\" is not in allowed options: [${fieldDef.options.join(', ')}]` })\n }\n }\n\n return issues\n}\n\n// ─── Serialize: Pure Functions ───\n\n/** Recursively sort object keys — respects optional fieldOrder for top-level keys */\nexport function sortKeys(obj: unknown, fieldOrder?: string[]): unknown {\n if (obj === null || obj === undefined) return undefined\n if (Array.isArray(obj)) return obj.map(item => sortKeys(item, fieldOrder))\n if (typeof obj !== 'object') return obj\n\n const record = obj as Record<string, unknown>\n const sorted: Record<string, unknown> = {}\n\n const keys = fieldOrder\n ? [...new Set([...fieldOrder, ...Object.keys(record).toSorted()])]\n : Object.keys(record).toSorted()\n\n for (const key of keys) {\n if (!(key in record)) continue\n const val = record[key]\n if (val === null || val === undefined) continue\n sorted[key] = sortKeys(val)\n }\n\n return sorted\n}\n\n/** Canonical JSON serialization — deterministic output for stable git diffs */\nexport function canonicalStringify(data: unknown, fieldOrder?: string[]): string {\n const sorted = sortKeys(data, fieldOrder)\n return `${JSON.stringify(sorted, null, 2)}\\n`\n}\n\n/** Generate a 12-character hex entry ID */\nexport function generateEntryId(): string {\n const hex = '0123456789abcdef'\n let id = ''\n for (let i = 0; i < 12; i++) {\n id += hex[Math.floor(Math.random() * 16)]\n }\n return id\n}\n\n// ─── Markdown Frontmatter ───\n\nfunction parseYamlValue(raw: string): unknown {\n if (raw === 'true') return true\n if (raw === 'false') return false\n if (raw === 'null') return null\n if (/^-?\\d+$/.test(raw)) return parseInt(raw, 10)\n if (/^-?\\d+\\.\\d+$/.test(raw)) return parseFloat(raw)\n if ((raw.startsWith('\"') && raw.endsWith('\"')) || (raw.startsWith(\"'\") && raw.endsWith(\"'\"))) {\n return raw.slice(1, -1)\n }\n return raw\n}\n\nfunction yamlScalar(value: unknown): string {\n const str = String(value)\n if (str.includes(':') || str.includes('#') || str.includes('{') || str.includes('}')\n || str.includes('[') || str.includes(']') || str.includes('*') || str.includes('&')\n || str.includes('!') || str.includes('|') || str.includes('>') || str.includes(\"'\")\n || str.includes('\"') || str.includes('%') || str.includes('@') || str.includes('`')\n || str.startsWith('-') || str.startsWith('?')\n || str === 'true' || str === 'false' || str === 'null' || str === 'yes' || str === 'no'\n || str === '') {\n return `\"${str.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"')}\"`\n }\n return str\n}\n\nfunction serializeYamlValue(value: unknown, indent: number): string[] {\n const pad = ' '.repeat(indent)\n if (value === null || value === undefined) return []\n if (typeof value === 'object' && !Array.isArray(value)) {\n const lines: string[] = []\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n if (v === null || v === undefined) continue\n if (typeof v === 'object' && !Array.isArray(v)) {\n lines.push(`${pad}${k}:`)\n lines.push(...serializeYamlValue(v, indent + 1))\n } else if (Array.isArray(v)) {\n lines.push(`${pad}${k}:`)\n for (const item of v) {\n if (typeof item === 'object' && item !== null && !Array.isArray(item)) {\n const nested = serializeYamlValue(item, indent + 2)\n if (nested.length > 0) {\n lines.push(`${pad} - ${nested[0]!.trimStart()}`)\n lines.push(...nested.slice(1))\n }\n } else {\n lines.push(`${pad} - ${yamlScalar(item)}`)\n }\n }\n } else {\n lines.push(`${pad}${k}: ${yamlScalar(v)}`)\n }\n }\n return lines\n }\n if (Array.isArray(value)) {\n const lines: string[] = []\n for (const item of value) {\n if (typeof item === 'object' && item !== null && !Array.isArray(item)) {\n const nested = serializeYamlValue(item, indent + 1)\n if (nested.length > 0) {\n lines.push(`${pad}- ${nested[0]!.trimStart()}`)\n lines.push(...nested.slice(1))\n }\n } else {\n lines.push(`${pad}- ${yamlScalar(item)}`)\n }\n }\n return lines\n }\n return [`${pad}${yamlScalar(value)}`]\n}\n\nfunction serializeYamlFields(data: Record<string, unknown>): string[] {\n const lines: string[] = []\n for (const [key, value] of Object.entries(data)) {\n if (key === 'body') continue\n if (value === null || value === undefined) continue\n if (typeof value === 'object' && !Array.isArray(value)) {\n lines.push(`${key}:`)\n lines.push(...serializeYamlValue(value, 1))\n } else if (Array.isArray(value)) {\n lines.push(`${key}:`)\n for (const item of value) {\n if (typeof item === 'object' && item !== null && !Array.isArray(item)) {\n const nested = serializeYamlValue(item, 2)\n if (nested.length > 0) {\n lines.push(` - ${nested[0]!.trimStart()}`)\n lines.push(...nested.slice(1))\n }\n } else {\n lines.push(` - ${yamlScalar(item)}`)\n }\n }\n } else {\n lines.push(`${key}: ${yamlScalar(value)}`)\n }\n }\n return lines\n}\n\n/** Parse markdown frontmatter — extracts YAML frontmatter and body from a markdown string */\nexport function parseMarkdownFrontmatter(content: string): { frontmatter: Record<string, unknown>; body: string } {\n const normalized = content.replace(/\\r\\n/g, '\\n')\n const match = normalized.match(/^---\\n([\\s\\S]*?)\\n---\\n?([\\s\\S]*)$/)\n if (!match) return { frontmatter: {}, body: normalized }\n\n const frontmatterStr = match[1]!\n const body = match[2]!.trim()\n const frontmatter: Record<string, unknown> = {}\n\n let currentKey: string | null = null\n let currentArray: string[] | null = null\n\n for (const line of frontmatterStr.split('\\n')) {\n if (/^\\s+-\\s+/.test(line) && currentKey) {\n const value = line.replace(/^\\s+-\\s+/, '').trim()\n if (!currentArray) currentArray = []\n currentArray.push(value)\n continue\n }\n\n if (currentKey && currentArray) {\n frontmatter[currentKey] = currentArray\n currentKey = null\n currentArray = null\n }\n\n const kvMatch = line.match(/^([\\w][\\w.-]*)\\s*:\\s*(.*)$/)\n if (!kvMatch) continue\n\n const key = kvMatch[1]!\n const rawValue = kvMatch[2]!.trim()\n\n if (rawValue === '') {\n currentKey = key\n currentArray = []\n continue\n }\n\n if (rawValue.startsWith('[') && rawValue.endsWith(']')) {\n const items = rawValue.slice(1, -1).split(',').map(s => s.trim()).filter(Boolean)\n frontmatter[key] = items\n continue\n }\n\n frontmatter[key] = parseYamlValue(rawValue)\n }\n\n if (currentKey && currentArray) {\n frontmatter[currentKey] = currentArray\n }\n\n return { frontmatter, body }\n}\n\n/** Serialize data + body into markdown frontmatter format */\nexport function serializeMarkdownFrontmatter(data: Record<string, unknown>, body: string): string {\n const trimmedBody = body.trimStart()\n if (trimmedBody.startsWith('---')) {\n const endIdx = trimmedBody.indexOf('---', 3)\n if (endIdx > 0) {\n const bodyFmContent = trimmedBody.slice(3, endIdx).trim()\n const afterFm = trimmedBody.slice(endIdx + 3)\n\n const lines: string[] = ['---']\n lines.push(...serializeYamlFields(data))\n if (bodyFmContent) {\n const modelKeys = new Set(Object.keys(data))\n for (const line of bodyFmContent.split('\\n')) {\n if (line.length > 0 && line[0] !== ' ' && line[0] !== '-') {\n const colonIdx = line.indexOf(':')\n const lineKey = colonIdx > 0 ? line.slice(0, colonIdx).trim() : ''\n if (lineKey && modelKeys.has(lineKey)) continue\n }\n lines.push(line)\n }\n }\n lines.push('---')\n if (afterFm.trim()) {\n lines.push(afterFm.trimStart())\n }\n lines.push('')\n return lines.join('\\n')\n }\n }\n\n const lines: string[] = ['---']\n lines.push(...serializeYamlFields(data))\n lines.push('---')\n lines.push('')\n if (body) {\n lines.push(body)\n lines.push('')\n }\n return lines.join('\\n')\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentrain/types",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Shared TypeScript types for Contentrain ecosystem",
|
|
6
6
|
"type": "module",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"url": "git+https://github.com/Contentrain/ai.git",
|
|
10
10
|
"directory": "packages/types"
|
|
11
11
|
},
|
|
12
|
-
"homepage": "https://
|
|
12
|
+
"homepage": "https://ai.contentrain.io/reference/config",
|
|
13
13
|
"bugs": {
|
|
14
14
|
"url": "https://github.com/Contentrain/ai/issues"
|
|
15
15
|
},
|