@contentrain/types 0.4.2 โ†’ 0.5.1

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 CHANGED
@@ -18,9 +18,9 @@ This package is the common schema layer used by:
18
18
  - `@contentrain/query`
19
19
  - `@contentrain/rules`
20
20
 
21
- It defines the stable type vocabulary for models, config, metadata, validation, scanning, and context files.
21
+ It defines the stable type vocabulary for models, config, metadata, validation, scanning, context files, and provider contracts (enabling third-party RepoProvider implementations).
22
22
 
23
- ## โœจ When To Use It
23
+ ## When To Use It
24
24
 
25
25
  Use `@contentrain/types` when you are:
26
26
 
@@ -28,14 +28,15 @@ Use `@contentrain/types` when you are:
28
28
  - sharing model/config types between packages in a workspace
29
29
  - authoring framework integrations or SDK extensions
30
30
  - consuming Contentrain JSON structures directly in TypeScript
31
+ - implementing a custom `RepoProvider` for a new git backend
31
32
 
32
- ## ๐Ÿš€ Install
33
+ ## Install
33
34
 
34
35
  ```bash
35
36
  pnpm add @contentrain/types
36
37
  ```
37
38
 
38
- ## ๐Ÿ“ฆ What It Exports
39
+ ## What It Exports
39
40
 
40
41
  Core unions:
41
42
 
@@ -54,6 +55,7 @@ Core interfaces:
54
55
 
55
56
  - `FieldDef`
56
57
  - `ModelDefinition`
58
+ - `ModelSummary`
57
59
  - `ContentrainConfig`
58
60
  - `Vocabulary`
59
61
  - `EntryMeta`
@@ -76,11 +78,40 @@ Storage/runtime helper types:
76
78
  - `DictionaryContentFile`
77
79
  - `CollectionEntry`
78
80
  - `CollectionContentOutput`
81
+ - `DocumentEntry`
82
+ - `DocumentContentOutput`
79
83
  - `SingletonMeta`
80
84
  - `CollectionMeta`
81
85
  - `DocumentMeta`
82
86
  - `DictionaryMeta`
83
87
 
88
+ Normalize/plan types:
89
+
90
+ - `NormalizePlan`
91
+ - `NormalizePlanModel`
92
+ - `NormalizePlanExtraction`
93
+ - `NormalizePlanPatch`
94
+
95
+ Provider contracts (re-exported from `provider.ts` โ€” implement these to add a new git backend):
96
+
97
+ - `RepoProvider`
98
+ - `RepoReader`
99
+ - `RepoWriter`
100
+ - `ProviderCapabilities`
101
+ - `FileChange`
102
+ - `CommitAuthor`
103
+ - `Commit`
104
+ - `ApplyPlanInput`
105
+ - `Branch`
106
+ - `FileDiff`
107
+ - `MergeResult` (includes optional `sync?: SyncResult` for local-worktree providers)
108
+ - `LOCAL_CAPABILITIES` (const โ€” capability set for LocalProvider)
109
+
110
+ Git transaction types:
111
+
112
+ - `SyncResult`
113
+ - `ContentrainError`
114
+
84
115
  Validate functions (pure, dependency-free):
85
116
 
86
117
  - `validateSlug(slug)` โ€” kebab-case slug validation
@@ -97,7 +128,18 @@ Serialize functions (pure, dependency-free):
97
128
  - `parseMarkdownFrontmatter(content)` โ€” parse YAML frontmatter + body from markdown
98
129
  - `serializeMarkdownFrontmatter(data, body)` โ€” serialize data + body into markdown frontmatter
99
130
 
100
- ## ๐Ÿงญ Stability
131
+ Constants:
132
+
133
+ - `CONTENTRAIN_DIR` โ€” default `.contentrain` folder name
134
+ - `CONTENTRAIN_BRANCH` โ€” default `contentrain` branch name for content tracking
135
+ - `PATH_PATTERNS` โ€” file path conventions for models, content, meta
136
+ - `SLUG_PATTERN` โ€” regex for valid slugs
137
+ - `ENTRY_ID_PATTERN` โ€” regex for valid entry IDs
138
+ - `LOCALE_PATTERN` โ€” regex for valid locale codes
139
+ - `CANONICAL_JSON` โ€” serialization rules (indent, encoding, trailing newline, key sort)
140
+ - `SECRET_PATTERNS` โ€” regex patterns for secret detection
141
+
142
+ ## Stability
101
143
 
102
144
  This package is intended to be the shared public contract across the Contentrain ecosystem.
103
145
 
@@ -106,8 +148,9 @@ In practice that means:
106
148
  - types exported from the package root are the public surface
107
149
  - packages should depend on these shared definitions instead of redefining domain types
108
150
  - breaking changes here should be treated as ecosystem-level breaking changes
151
+ - the `RepoProvider` contract enables third-party implementations without depending on `@contentrain/mcp` internals
109
152
 
110
- ## ๐Ÿงช Quick Example
153
+ ## Quick Example
111
154
 
112
155
  ```ts
113
156
  import type {
@@ -145,7 +188,7 @@ const result: ValidationResult = {
145
188
  }
146
189
  ```
147
190
 
148
- ## ๐Ÿ“ Import Style
191
+ ## Import Style
149
192
 
150
193
  Type-only usage:
151
194
 
@@ -166,7 +209,26 @@ import {
166
209
  } from '@contentrain/types'
167
210
  ```
168
211
 
169
- ## ๐Ÿข Studio Integration
212
+ Provider contract usage (for custom RepoProvider implementations):
213
+
214
+ ```ts
215
+ import type { RepoProvider, ProviderCapabilities } from '@contentrain/types'
216
+
217
+ export class MyCustomProvider implements RepoProvider {
218
+ readonly capabilities: ProviderCapabilities = {
219
+ localWorktree: false,
220
+ sourceRead: true,
221
+ sourceWrite: true,
222
+ pushRemote: true,
223
+ branchProtection: true,
224
+ pullRequestFallback: true,
225
+ astScan: false,
226
+ }
227
+ // ...implement RepoProvider methods
228
+ }
229
+ ```
230
+
231
+ ## Studio Integration
170
232
 
171
233
  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
234
 
@@ -194,59 +256,6 @@ These require file system I/O or Node.js dependencies:
194
256
  - `writeContent()` / `deleteContent()` โ€” content persistence with git worktree
195
257
  - `resolveContentDir()` / `resolveJsonFilePath()` โ€” path resolution with `node:path`
196
258
 
197
- ### Studio implementation example
198
-
199
- ```ts
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
- }
230
- ```
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
259
  ### Unique constraints and relation validation
251
260
 
252
261
  `validateFieldValue` handles schema-level checks. Two things require external state:
@@ -256,7 +265,7 @@ export function useDocumentEditor() {
256
265
 
257
266
  These are left to Studio's server-side or API layer to implement on top of the pure validation.
258
267
 
259
- ## ๐Ÿง  Design Role
268
+ ## Design Role
260
269
 
261
270
  `@contentrain/types` exists so every package in the monorepo speaks the same domain language.
262
271
 
@@ -267,6 +276,7 @@ Examples:
267
276
  - SDK codegen consumes `ModelDefinition` and `FieldDef`
268
277
  - AI rules align with the same model and workflow vocabulary
269
278
  - Studio uses the same validation functions in the browser
279
+ - Third-party providers implement `RepoProvider` to plug into MCP
270
280
 
271
281
  This package should stay:
272
282
 
@@ -276,7 +286,7 @@ This package should stay:
276
286
  - stable
277
287
  - free of package-specific behavior
278
288
 
279
- ## ๐Ÿ›  Development
289
+ ## Development
280
290
 
281
291
  From the monorepo root:
282
292
 
@@ -286,13 +296,13 @@ pnpm --filter @contentrain/types test
286
296
  pnpm --filter @contentrain/types typecheck
287
297
  ```
288
298
 
289
- ## ๐Ÿ”— Related Packages
299
+ ## Related Packages
290
300
 
291
301
  - `@contentrain/mcp`
292
302
  - `contentrain`
293
303
  - `@contentrain/query`
294
304
  - `@contentrain/rules`
295
305
 
296
- ## ๐Ÿ“„ License
306
+ ## License
297
307
 
298
308
  MIT
package/dist/index.d.mts CHANGED
@@ -1,3 +1,161 @@
1
+ //#region src/provider.d.ts
2
+ /**
3
+ * A single file change within a plan.
4
+ *
5
+ * - `content: string` โ€” write or overwrite the file with this UTF-8 content.
6
+ * - `content: null` โ€” delete the file.
7
+ *
8
+ * Paths are content-root relative, use forward slashes, and must not contain
9
+ * `..` segments or absolute anchors. Providers are responsible for resolving
10
+ * paths against their backing store (worktree, git tree, etc.).
11
+ */
12
+ interface FileChange {
13
+ path: string;
14
+ content: string | null;
15
+ }
16
+ /**
17
+ * Capabilities describe what a provider can and cannot do. Operations check
18
+ * required capabilities before running. Normalize extract needs `sourceRead`;
19
+ * normalize reuse needs `sourceWrite`; submit needs `pushRemote`; AST scans
20
+ * need `astScan` (which implies a local working tree).
21
+ *
22
+ * The consistent position is: all git hosts are commodity MIT providers; the
23
+ * distinction between providers is operational (how they read/write), not
24
+ * commercial. Enterprise features live in Studio, not in capability gates.
25
+ */
26
+ interface ProviderCapabilities {
27
+ /** Provider backs onto a local worktree and can selectively sync changes into the developer's working tree. */
28
+ localWorktree: boolean;
29
+ /** Provider can read arbitrary source files outside `.contentrain/`. Required for normalize extract. */
30
+ sourceRead: boolean;
31
+ /** Provider can write arbitrary source files outside `.contentrain/`. Required for normalize reuse. */
32
+ sourceWrite: boolean;
33
+ /** Provider can push commits to a remote. Required for submit. */
34
+ pushRemote: boolean;
35
+ /** Provider detects branch protection rules on the remote. */
36
+ branchProtection: boolean;
37
+ /** Provider can open a pull request as a merge fallback when branch protection blocks direct merge. */
38
+ pullRequestFallback: boolean;
39
+ /** Provider can execute AST scanners against source files. Implies local disk access. */
40
+ astScan: boolean;
41
+ }
42
+ /** Capability set for the LocalProvider (simple-git + worktree). */
43
+ declare const LOCAL_CAPABILITIES: ProviderCapabilities;
44
+ /**
45
+ * Read-only interface to a content repository.
46
+ *
47
+ * Paths are relative to the repository's content root (e.g.
48
+ * `.contentrain/config.json`). The `ref` parameter is a branch name, tag,
49
+ * or commit SHA. Providers that operate on a single working tree
50
+ * (LocalReader) ignore `ref`; API-backed providers use it to resolve the
51
+ * correct revision.
52
+ *
53
+ * `readFile` and `listDirectory` deliberately have different error semantics:
54
+ * - `readFile` THROWS when the file is missing so callers must opt into
55
+ * tolerance explicitly (typically with a try/catch returning a default).
56
+ * - `listDirectory` returns `[]` for a missing directory because the empty
57
+ * case is the common, uninteresting one.
58
+ */
59
+ interface RepoReader {
60
+ /**
61
+ * Read a file's contents as UTF-8.
62
+ * @throws when the file does not exist or cannot be read.
63
+ */
64
+ readFile(path: string, ref?: string): Promise<string>;
65
+ /**
66
+ * List file and directory names directly under `path`. Does not recurse.
67
+ * Returns an empty array when the directory does not exist.
68
+ */
69
+ listDirectory(path: string, ref?: string): Promise<string[]>;
70
+ /** Check whether a file or directory exists at `path`. */
71
+ fileExists(path: string, ref?: string): Promise<boolean>;
72
+ }
73
+ interface CommitAuthor {
74
+ name: string;
75
+ email: string;
76
+ }
77
+ interface Commit {
78
+ sha: string;
79
+ message: string;
80
+ author: CommitAuthor;
81
+ timestamp: string;
82
+ }
83
+ /**
84
+ * Input to `applyPlan`. Represents a single atomic commit: all changes land
85
+ * in one commit on `branch`, created from `base` if it does not yet exist.
86
+ */
87
+ interface ApplyPlanInput {
88
+ /** Branch name to commit to. Created from `base` if missing. */
89
+ branch: string;
90
+ /** File additions, modifications and deletions to apply in a single commit. */
91
+ changes: FileChange[];
92
+ /** Commit message. */
93
+ message: string;
94
+ /** Commit author. */
95
+ author: CommitAuthor;
96
+ /**
97
+ * Optional base branch. Defaults to the Contentrain content-tracking
98
+ * branch (`CONTENTRAIN_BRANCH` โ€” the `contentrain` ref) โ€” NOT the
99
+ * repository's default branch. This is the single source of truth for
100
+ * content state; every feature branch forks from it. Pass an explicit
101
+ * `base` only when you know you want to bypass the invariant.
102
+ */
103
+ base?: string;
104
+ }
105
+ /**
106
+ * Write-side interface. Providers implement this to persist a set of file
107
+ * changes as a single atomic commit. LocalProvider writes through a worktree
108
+ * and `git commit`; API-backed providers post to the Git Data API or
109
+ * equivalent.
110
+ */
111
+ interface RepoWriter {
112
+ applyPlan(input: ApplyPlanInput): Promise<Commit>;
113
+ }
114
+ interface Branch {
115
+ name: string;
116
+ sha: string;
117
+ protected?: boolean;
118
+ }
119
+ interface FileDiff {
120
+ path: string;
121
+ status: 'added' | 'modified' | 'removed';
122
+ before: string | null;
123
+ after: string | null;
124
+ }
125
+ interface MergeResult {
126
+ merged: boolean;
127
+ sha: string | null;
128
+ pullRequestUrl: string | null;
129
+ /**
130
+ * Selective-sync bookkeeping โ€” only populated by providers that back onto
131
+ * a local worktree (LocalProvider). Remote-API providers (GitHub, GitLab,
132
+ * etc.) omit it because they do not touch a developer's working tree.
133
+ */
134
+ sync?: SyncResult;
135
+ }
136
+ /**
137
+ * A content repository provider โ€” the unified surface that MCP tools drive.
138
+ *
139
+ * Implementations wrap a git backend:
140
+ *
141
+ * - `LocalProvider` โ€” simple-git + temp worktree + selective sync
142
+ * - `GitHubProvider` โ€” Octokit Git Data API (no clone)
143
+ * - `GitLabProvider` โ€” gitbeaker REST client (no clone)
144
+ * - `BitbucketProvider` โ€” planned, coming soon
145
+ *
146
+ * Providers are commodity and all live in MIT.
147
+ */
148
+ interface RepoProvider extends RepoReader, RepoWriter {
149
+ readonly capabilities: ProviderCapabilities;
150
+ listBranches(prefix?: string): Promise<Branch[]>;
151
+ createBranch(name: string, fromRef?: string): Promise<void>;
152
+ deleteBranch(name: string): Promise<void>;
153
+ getBranchDiff(branch: string, base?: string): Promise<FileDiff[]>;
154
+ mergeBranch(branch: string, into: string): Promise<MergeResult>;
155
+ isMerged(branch: string, into?: string): Promise<boolean>;
156
+ getDefaultBranch(): Promise<string>;
157
+ }
158
+ //#endregion
1
159
  //#region src/index.d.ts
2
160
  type FieldType = 'string' | 'text' | 'email' | 'url' | 'slug' | 'color' | 'phone' | 'code' | 'icon' | 'markdown' | 'richtext' | 'number' | 'integer' | 'decimal' | 'percent' | 'rating' | 'boolean' | 'date' | 'datetime' | 'image' | 'video' | 'file' | 'relation' | 'relations' | 'select' | 'array' | 'object';
3
161
  type ModelKind = 'singleton' | 'collection' | 'document' | 'dictionary';
@@ -44,7 +202,14 @@ interface ContentrainConfig {
44
202
  stack: StackType;
45
203
  workflow: WorkflowMode;
46
204
  repository?: {
47
- provider: 'github';
205
+ /**
206
+ * Git host backing this project's remote. Widens as new providers ship
207
+ * in @contentrain/mcp โ€” `github` and `gitlab` are currently supported.
208
+ * The value is informational for tooling: the concrete provider the
209
+ * MCP server talks to is chosen at server construction time, not from
210
+ * this config.
211
+ */
212
+ provider: 'github' | 'gitlab';
48
213
  owner: string;
49
214
  name: string;
50
215
  default_branch: string;
@@ -55,7 +220,12 @@ interface ContentrainConfig {
55
220
  };
56
221
  domains: string[];
57
222
  assets_path?: string;
223
+ /** Days a merged `cr/*` branch is kept before lazy cleanup. Default: 30. */
58
224
  branchRetention?: number;
225
+ /** Unmerged `cr/*` branch count that triggers a warning. Default: 50. */
226
+ branchWarnLimit?: number;
227
+ /** Unmerged `cr/*` branch count that blocks new writes. Default: 80. */
228
+ branchBlockLimit?: number;
59
229
  }
60
230
  interface Vocabulary {
61
231
  version: number;
@@ -354,5 +524,5 @@ declare function parseMarkdownFrontmatter(content: string): {
354
524
  /** Serialize data + body into markdown frontmatter format */
355
525
  declare function serializeMarkdownFrontmatter(data: Record<string, unknown>, body: string): string;
356
526
  //#endregion
357
- 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, NormalizePlan, NormalizePlanExtraction, NormalizePlanModel, NormalizePlanPatch, 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 };
527
+ export { type ApplyPlanInput, AssetEntry, type Branch, CANONICAL_JSON, CONTENTRAIN_BRANCH, CONTENTRAIN_DIR, CollectionContentFile, CollectionContentOutput, CollectionEntry, CollectionMeta, CollectionRuntimeFormat, type Commit, type CommitAuthor, ContentSource, ContentStatus, ContentrainConfig, ContentrainError, ContextJson, ContextSource, DictionaryContentFile, DictionaryMeta, DocumentContentOutput, DocumentEntry, DocumentMeta, DuplicateGroup, ENTRY_ID_PATTERN, EntryMeta, FieldDef, FieldType, FileCategory, type FileChange, type FileDiff, FileFramework, GraphNode, LOCALE_PATTERN, LOCAL_CAPABILITIES, LocaleStrategy, type MergeResult, ModelDefinition, ModelKind, ModelSummary, NormalizePlan, NormalizePlanExtraction, NormalizePlanModel, NormalizePlanPatch, PATH_PATTERNS, Platform, PolymorphicRelationRef, ProjectGraph, type ProviderCapabilities, type RepoProvider, type RepoReader, type RepoWriter, 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 };
358
528
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/provider.ts","../src/index.ts"],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -1,3 +1,15 @@
1
+ //#region src/provider.ts
2
+ /** Capability set for the LocalProvider (simple-git + worktree). */
3
+ const LOCAL_CAPABILITIES = {
4
+ localWorktree: true,
5
+ sourceRead: true,
6
+ sourceWrite: true,
7
+ pushRemote: true,
8
+ branchProtection: false,
9
+ pullRequestFallback: false,
10
+ astScan: true
11
+ };
12
+ //#endregion
1
13
  //#region src/index.ts
2
14
  /** Root directory name for all Contentrain data */
3
15
  const CONTENTRAIN_DIR = ".contentrain";
@@ -363,6 +375,6 @@ function serializeMarkdownFrontmatter(data, body) {
363
375
  return lines.join("\n");
364
376
  }
365
377
  //#endregion
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 };
378
+ export { CANONICAL_JSON, CONTENTRAIN_BRANCH, CONTENTRAIN_DIR, ENTRY_ID_PATTERN, LOCALE_PATTERN, LOCAL_CAPABILITIES, PATH_PATTERNS, SECRET_PATTERNS, SLUG_PATTERN, canonicalStringify, detectSecrets, generateEntryId, parseMarkdownFrontmatter, serializeMarkdownFrontmatter, sortKeys, validateEntryId, validateFieldValue, validateLocale, validateSlug };
367
379
 
368
380
  //# sourceMappingURL=index.mjs.map
@@ -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 * 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 /** Content confidence score (0-1). Higher = more likely user-visible content. */\n contentScore: number\n /** All locations where this value appears (populated when dedup is enabled). */\n occurrences: Array<{ file: string; line: number }>\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 skipped: number\n low_confidence: number\n unique_candidates: number\n candidates_returned: number\n has_more: boolean\n skip_reasons: Record<string, number>\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// โ”€โ”€โ”€ Normalize Plan โ”€โ”€โ”€\n\nexport interface NormalizePlan {\n version: number\n status: 'pending' | 'approved' | 'rejected'\n created_at: string\n approved_at?: string\n agent: string\n scan_stats: {\n files_scanned: number\n raw_strings: number\n candidates_sent: number\n extracted: number\n skipped: number\n }\n models: NormalizePlanModel[]\n extractions: NormalizePlanExtraction[]\n patches: NormalizePlanPatch[]\n approved_models?: string[]\n}\n\nexport interface NormalizePlanModel {\n id: string\n kind: ModelKind\n domain: string\n i18n?: boolean\n fields: Record<string, FieldDef>\n}\n\nexport interface NormalizePlanExtraction {\n value: string\n file: string\n line: number\n model: string\n field: string\n locale?: string\n}\n\nexport interface NormalizePlanPatch {\n file: string\n line: number\n old_value: string\n new_expression: string\n import_statement?: string\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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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/provider.ts","../src/index.ts"],"sourcesContent":["import type { SyncResult } from './index.js'\n\n// โ”€โ”€โ”€ Repository Provider Contracts โ”€โ”€โ”€\n//\n// Shared interfaces for the provider-agnostic content repository model used\n// by @contentrain/mcp. They live in @contentrain/types so third-party tools\n// can implement a custom RepoProvider (e.g. for a private git host, an\n// internal service, a mock in a test suite) without depending on MCP\n// internals.\n//\n// @contentrain/mcp re-exports every symbol here from\n// @contentrain/mcp/core/contracts so existing consumers do not have to\n// migrate imports.\n\n// โ”€โ”€โ”€ File change โ”€โ”€โ”€\n\n/**\n * A single file change within a plan.\n *\n * - `content: string` โ€” write or overwrite the file with this UTF-8 content.\n * - `content: null` โ€” delete the file.\n *\n * Paths are content-root relative, use forward slashes, and must not contain\n * `..` segments or absolute anchors. Providers are responsible for resolving\n * paths against their backing store (worktree, git tree, etc.).\n */\nexport interface FileChange {\n path: string\n content: string | null\n}\n\n// โ”€โ”€โ”€ Capabilities โ”€โ”€โ”€\n\n/**\n * Capabilities describe what a provider can and cannot do. Operations check\n * required capabilities before running. Normalize extract needs `sourceRead`;\n * normalize reuse needs `sourceWrite`; submit needs `pushRemote`; AST scans\n * need `astScan` (which implies a local working tree).\n *\n * The consistent position is: all git hosts are commodity MIT providers; the\n * distinction between providers is operational (how they read/write), not\n * commercial. Enterprise features live in Studio, not in capability gates.\n */\nexport interface ProviderCapabilities {\n /** Provider backs onto a local worktree and can selectively sync changes into the developer's working tree. */\n localWorktree: boolean\n /** Provider can read arbitrary source files outside `.contentrain/`. Required for normalize extract. */\n sourceRead: boolean\n /** Provider can write arbitrary source files outside `.contentrain/`. Required for normalize reuse. */\n sourceWrite: boolean\n /** Provider can push commits to a remote. Required for submit. */\n pushRemote: boolean\n /** Provider detects branch protection rules on the remote. */\n branchProtection: boolean\n /** Provider can open a pull request as a merge fallback when branch protection blocks direct merge. */\n pullRequestFallback: boolean\n /** Provider can execute AST scanners against source files. Implies local disk access. */\n astScan: boolean\n}\n\n/** Capability set for the LocalProvider (simple-git + worktree). */\nexport const LOCAL_CAPABILITIES: ProviderCapabilities = {\n localWorktree: true,\n sourceRead: true,\n sourceWrite: true,\n pushRemote: true,\n branchProtection: false,\n pullRequestFallback: false,\n astScan: true,\n}\n\n// โ”€โ”€โ”€ Reader โ”€โ”€โ”€\n\n/**\n * Read-only interface to a content repository.\n *\n * Paths are relative to the repository's content root (e.g.\n * `.contentrain/config.json`). The `ref` parameter is a branch name, tag,\n * or commit SHA. Providers that operate on a single working tree\n * (LocalReader) ignore `ref`; API-backed providers use it to resolve the\n * correct revision.\n *\n * `readFile` and `listDirectory` deliberately have different error semantics:\n * - `readFile` THROWS when the file is missing so callers must opt into\n * tolerance explicitly (typically with a try/catch returning a default).\n * - `listDirectory` returns `[]` for a missing directory because the empty\n * case is the common, uninteresting one.\n */\nexport interface RepoReader {\n /**\n * Read a file's contents as UTF-8.\n * @throws when the file does not exist or cannot be read.\n */\n readFile(path: string, ref?: string): Promise<string>\n\n /**\n * List file and directory names directly under `path`. Does not recurse.\n * Returns an empty array when the directory does not exist.\n */\n listDirectory(path: string, ref?: string): Promise<string[]>\n\n /** Check whether a file or directory exists at `path`. */\n fileExists(path: string, ref?: string): Promise<boolean>\n}\n\n// โ”€โ”€โ”€ Writer โ”€โ”€โ”€\n\nexport interface CommitAuthor {\n name: string\n email: string\n}\n\nexport interface Commit {\n sha: string\n message: string\n author: CommitAuthor\n timestamp: string\n}\n\n/**\n * Input to `applyPlan`. Represents a single atomic commit: all changes land\n * in one commit on `branch`, created from `base` if it does not yet exist.\n */\nexport interface ApplyPlanInput {\n /** Branch name to commit to. Created from `base` if missing. */\n branch: string\n /** File additions, modifications and deletions to apply in a single commit. */\n changes: FileChange[]\n /** Commit message. */\n message: string\n /** Commit author. */\n author: CommitAuthor\n /**\n * Optional base branch. Defaults to the Contentrain content-tracking\n * branch (`CONTENTRAIN_BRANCH` โ€” the `contentrain` ref) โ€” NOT the\n * repository's default branch. This is the single source of truth for\n * content state; every feature branch forks from it. Pass an explicit\n * `base` only when you know you want to bypass the invariant.\n */\n base?: string\n}\n\n/**\n * Write-side interface. Providers implement this to persist a set of file\n * changes as a single atomic commit. LocalProvider writes through a worktree\n * and `git commit`; API-backed providers post to the Git Data API or\n * equivalent.\n */\nexport interface RepoWriter {\n applyPlan(input: ApplyPlanInput): Promise<Commit>\n}\n\n// โ”€โ”€โ”€ Branch / diff / merge โ”€โ”€โ”€\n\nexport interface Branch {\n name: string\n sha: string\n protected?: boolean\n}\n\nexport interface FileDiff {\n path: string\n status: 'added' | 'modified' | 'removed'\n before: string | null\n after: string | null\n}\n\nexport interface MergeResult {\n merged: boolean\n sha: string | null\n pullRequestUrl: string | null\n /**\n * Selective-sync bookkeeping โ€” only populated by providers that back onto\n * a local worktree (LocalProvider). Remote-API providers (GitHub, GitLab,\n * etc.) omit it because they do not touch a developer's working tree.\n */\n sync?: SyncResult\n}\n\n// โ”€โ”€โ”€ Provider (full surface) โ”€โ”€โ”€\n\n/**\n * A content repository provider โ€” the unified surface that MCP tools drive.\n *\n * Implementations wrap a git backend:\n *\n * - `LocalProvider` โ€” simple-git + temp worktree + selective sync\n * - `GitHubProvider` โ€” Octokit Git Data API (no clone)\n * - `GitLabProvider` โ€” gitbeaker REST client (no clone)\n * - `BitbucketProvider` โ€” planned, coming soon\n *\n * Providers are commodity and all live in MIT.\n */\nexport interface RepoProvider extends RepoReader, RepoWriter {\n readonly capabilities: ProviderCapabilities\n\n listBranches(prefix?: string): Promise<Branch[]>\n createBranch(name: string, fromRef?: string): Promise<void>\n deleteBranch(name: string): Promise<void>\n getBranchDiff(branch: string, base?: string): Promise<FileDiff[]>\n mergeBranch(branch: string, into: string): Promise<MergeResult>\n isMerged(branch: string, into?: string): Promise<boolean>\n getDefaultBranch(): Promise<string>\n}\n","// โ”€โ”€โ”€ 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 /**\n * Git host backing this project's remote. Widens as new providers ship\n * in @contentrain/mcp โ€” `github` and `gitlab` are currently supported.\n * The value is informational for tooling: the concrete provider the\n * MCP server talks to is chosen at server construction time, not from\n * this config.\n */\n provider: 'github' | 'gitlab'\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 /** Days a merged `cr/*` branch is kept before lazy cleanup. Default: 30. */\n branchRetention?: number\n /** Unmerged `cr/*` branch count that triggers a warning. Default: 50. */\n branchWarnLimit?: number\n /** Unmerged `cr/*` branch count that blocks new writes. Default: 80. */\n branchBlockLimit?: 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 /** Content confidence score (0-1). Higher = more likely user-visible content. */\n contentScore: number\n /** All locations where this value appears (populated when dedup is enabled). */\n occurrences: Array<{ file: string; line: number }>\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 skipped: number\n low_confidence: number\n unique_candidates: number\n candidates_returned: number\n has_more: boolean\n skip_reasons: Record<string, number>\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// โ”€โ”€โ”€ Normalize Plan โ”€โ”€โ”€\n\nexport interface NormalizePlan {\n version: number\n status: 'pending' | 'approved' | 'rejected'\n created_at: string\n approved_at?: string\n agent: string\n scan_stats: {\n files_scanned: number\n raw_strings: number\n candidates_sent: number\n extracted: number\n skipped: number\n }\n models: NormalizePlanModel[]\n extractions: NormalizePlanExtraction[]\n patches: NormalizePlanPatch[]\n approved_models?: string[]\n}\n\nexport interface NormalizePlanModel {\n id: string\n kind: ModelKind\n domain: string\n i18n?: boolean\n fields: Record<string, FieldDef>\n}\n\nexport interface NormalizePlanExtraction {\n value: string\n file: string\n line: number\n model: string\n field: string\n locale?: string\n}\n\nexport interface NormalizePlanPatch {\n file: string\n line: number\n old_value: string\n new_expression: string\n import_statement?: string\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\n// โ”€โ”€โ”€ Repository provider contracts โ”€โ”€โ”€\n//\n// Provider-agnostic engine contracts used by @contentrain/mcp. Exposed from\n// @contentrain/types so third-party tools can implement a custom\n// RepoProvider without taking a dependency on @contentrain/mcp.\nexport type {\n ApplyPlanInput,\n Branch,\n Commit,\n CommitAuthor,\n FileChange,\n FileDiff,\n MergeResult,\n ProviderCapabilities,\n RepoProvider,\n RepoReader,\n RepoWriter,\n} from './provider.js'\nexport { LOCAL_CAPABILITIES } from './provider.js'\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentrain/types",
3
- "version": "0.4.2",
3
+ "version": "0.5.1",
4
4
  "license": "MIT",
5
5
  "description": "Shared TypeScript types for Contentrain ecosystem",
6
6
  "type": "module",