@contentrain/types
Advanced tools
+33
-3
@@ -633,5 +633,35 @@ //#region src/provider.d.ts | ||
| /** | ||
| * Rules a field type implies beyond its runtime `typeof`. | ||
| * | ||
| * Severity follows one rule: a check that is *definitional* is an error — a `slug` | ||
| * that does not match `SLUG_PATTERN` is not a slug. A check that is a *heuristic* is | ||
| * a warning, because the pattern is an approximation and a legitimate value may sit | ||
| * outside it (international phone formats, CSS colour keywords). | ||
| * | ||
| * `rating` is deliberately absent: its scale is never declared, so any range here | ||
| * would be invented. Declare `min`/`max` on the field instead. | ||
| * | ||
| * Returns `[]` for types whose only contract is their `typeof` (`string`, `text`, | ||
| * `code`, `icon`, `markdown`, `richtext`, `number`, `decimal`, `boolean`, media). | ||
| */ | ||
| declare function validateSemanticType(value: unknown, type: FieldType): ValidationError[]; | ||
| declare function isMediaType(type: FieldType): boolean; | ||
| /** | ||
| * Check a media value's file extension against an `accept` list. | ||
| * | ||
| * This is an approximation and says so: the value is a path or URL, so the real | ||
| * MIME type is only knowable where the bytes are — the provider, at ingest | ||
| * (`provider.ts:MediaProvider.ingest` owns that policy). An extension can lie, and | ||
| * an unknown extension is not evidence of a violation. Hence: warning, never error, | ||
| * and silence when we cannot tell. | ||
| * | ||
| * `accept` follows the HTML input syntax: `image/jpeg`, `image/*`, `.jpg`, comma-separated. | ||
| */ | ||
| declare function validateAccept(value: string, accept: string): ValidationError[]; | ||
| /** | ||
| * Validate a field value against its FieldDef schema. | ||
| * Checks: required, type match, min/max, pattern, select options. | ||
| * Does NOT check: secrets (use detectSecrets), uniqueness (stateful), relations (I/O). | ||
| * Checks: required, type match, semantic type rules, min/max, pattern, select | ||
| * options, and `accept` on media fields (by extension). | ||
| * Does NOT check: secrets (use detectSecrets), uniqueness (stateful), relations (I/O), | ||
| * or `maxSize` — byte length is not knowable from a stored path. | ||
| */ | ||
@@ -653,3 +683,3 @@ declare function validateFieldValue(value: unknown, fieldDef: FieldDef): ValidationError[]; | ||
| //#endregion | ||
| 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 MediaAsset, type MediaIngestInput, type MediaListOptions, type MediaListResult, type MediaProvider, type MediaUpdateInput, 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 }; | ||
| 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 MediaAsset, type MediaIngestInput, type MediaListOptions, type MediaListResult, type MediaProvider, type MediaUpdateInput, 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, isMediaType, parseMarkdownFrontmatter, serializeMarkdownFrontmatter, sortKeys, validateAccept, validateEntryId, validateFieldValue, validateLocale, validateSemanticType, validateSlug }; | ||
| //# sourceMappingURL=index.d.mts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.d.mts","names":[],"sources":["../src/provider.ts","../src/index.ts"],"mappings":"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"} | ||
| {"version":3,"file":"index.d.mts","names":[],"sources":["../src/provider.ts","../src/index.ts"],"mappings":"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"} |
+135
-3
@@ -149,6 +149,136 @@ //#region src/provider.ts | ||
| } | ||
| /** Hex (#rgb/#rrggbb/#rrggbbaa), rgb()/rgba(), hsl()/hsla(), or a bare CSS keyword. */ | ||
| const COLOR_PATTERN = /^(#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})|(?:rgb|hsl)a?\([^)]*\)|[a-zA-Z]+)$/; | ||
| /** Deliberately loose: digits, spaces, and the usual separators, 4-20 digits total. */ | ||
| const PHONE_PATTERN = /^\+?[\d\s().-]{4,}$/; | ||
| const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; | ||
| const DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/; | ||
| function isParsableDate(value) { | ||
| return !Number.isNaN(new Date(value).getTime()); | ||
| } | ||
| /** | ||
| * Rules a field type implies beyond its runtime `typeof`. | ||
| * | ||
| * Severity follows one rule: a check that is *definitional* is an error — a `slug` | ||
| * that does not match `SLUG_PATTERN` is not a slug. A check that is a *heuristic* is | ||
| * a warning, because the pattern is an approximation and a legitimate value may sit | ||
| * outside it (international phone formats, CSS colour keywords). | ||
| * | ||
| * `rating` is deliberately absent: its scale is never declared, so any range here | ||
| * would be invented. Declare `min`/`max` on the field instead. | ||
| * | ||
| * Returns `[]` for types whose only contract is their `typeof` (`string`, `text`, | ||
| * `code`, `icon`, `markdown`, `richtext`, `number`, `decimal`, `boolean`, media). | ||
| */ | ||
| function validateSemanticType(value, type) { | ||
| if (typeof value === "number") { | ||
| if (type === "integer" && !Number.isInteger(value)) return [{ | ||
| severity: "error", | ||
| message: `Value ${value} is not an integer` | ||
| }]; | ||
| if (type === "percent" && (value < 0 || value > 100)) return [{ | ||
| severity: "error", | ||
| message: `Percent ${value} is outside 0-100` | ||
| }]; | ||
| return []; | ||
| } | ||
| if (typeof value !== "string" || value === "") return []; | ||
| switch (type) { | ||
| case "slug": { | ||
| const err = validateSlug(value); | ||
| return err ? [{ | ||
| severity: "error", | ||
| message: err | ||
| }] : []; | ||
| } | ||
| case "date": return DATE_PATTERN.test(value) && isParsableDate(value) ? [] : [{ | ||
| severity: "error", | ||
| message: `Invalid date "${value}": must be YYYY-MM-DD` | ||
| }]; | ||
| case "datetime": return isParsableDate(value) ? [] : [{ | ||
| severity: "error", | ||
| message: `Invalid datetime "${value}": must be an ISO 8601 date-time` | ||
| }]; | ||
| case "email": return EMAIL_PATTERN.test(value) ? [] : [{ | ||
| severity: "warning", | ||
| message: `"${value}" may not be a valid email` | ||
| }]; | ||
| case "url": return /^https?:\/\/.+/.test(value) || value.startsWith("/") ? [] : [{ | ||
| severity: "warning", | ||
| message: `"${value}" may not be a valid URL` | ||
| }]; | ||
| case "color": return COLOR_PATTERN.test(value) ? [] : [{ | ||
| severity: "warning", | ||
| message: `"${value}" may not be a valid color (expected hex, rgb(), hsl() or a keyword)` | ||
| }]; | ||
| case "phone": return PHONE_PATTERN.test(value) && (value.match(/\d/g)?.length ?? 0) >= 4 ? [] : [{ | ||
| severity: "warning", | ||
| message: `"${value}" may not be a valid phone number` | ||
| }]; | ||
| default: return []; | ||
| } | ||
| } | ||
| /** Media field types — the ones whose value is a path/URL to an asset. */ | ||
| const MEDIA_TYPES = new Set([ | ||
| "image", | ||
| "video", | ||
| "file" | ||
| ]); | ||
| function isMediaType(type) { | ||
| return MEDIA_TYPES.has(type); | ||
| } | ||
| /** Extensions we can name a MIME type for. Deliberately small — this is a sniff. */ | ||
| const EXTENSION_MIME = { | ||
| jpg: "image/jpeg", | ||
| jpeg: "image/jpeg", | ||
| png: "image/png", | ||
| gif: "image/gif", | ||
| webp: "image/webp", | ||
| avif: "image/avif", | ||
| svg: "image/svg+xml", | ||
| ico: "image/x-icon", | ||
| mp4: "video/mp4", | ||
| webm: "video/webm", | ||
| mov: "video/quicktime", | ||
| pdf: "application/pdf", | ||
| zip: "application/zip", | ||
| json: "application/json", | ||
| csv: "text/csv", | ||
| txt: "text/plain" | ||
| }; | ||
| /** | ||
| * Check a media value's file extension against an `accept` list. | ||
| * | ||
| * This is an approximation and says so: the value is a path or URL, so the real | ||
| * MIME type is only knowable where the bytes are — the provider, at ingest | ||
| * (`provider.ts:MediaProvider.ingest` owns that policy). An extension can lie, and | ||
| * an unknown extension is not evidence of a violation. Hence: warning, never error, | ||
| * and silence when we cannot tell. | ||
| * | ||
| * `accept` follows the HTML input syntax: `image/jpeg`, `image/*`, `.jpg`, comma-separated. | ||
| */ | ||
| function validateAccept(value, accept) { | ||
| const path = value.split("?")[0]?.split("#")[0] ?? value; | ||
| const ext = path.split(".").pop()?.toLowerCase(); | ||
| if (!ext || ext === path.toLowerCase()) return []; | ||
| const mime = EXTENSION_MIME[ext]; | ||
| const patterns = accept.split(",").map((a) => a.trim().toLowerCase()).filter(Boolean); | ||
| if (patterns.length === 0) return []; | ||
| if (patterns.some((p) => { | ||
| if (p.startsWith(".")) return p.slice(1) === ext; | ||
| if (p.endsWith("/*")) return mime ? mime.startsWith(p.slice(0, -1)) : false; | ||
| return mime === p; | ||
| })) return []; | ||
| if (!mime && !patterns.some((p) => p.startsWith("."))) return []; | ||
| return [{ | ||
| severity: "warning", | ||
| message: `File extension ".${ext}" does not match accept "${accept}" (extension check — the provider enforces the real MIME type at ingest)` | ||
| }]; | ||
| } | ||
| /** | ||
| * Validate a field value against its FieldDef schema. | ||
| * Checks: required, type match, min/max, pattern, select options. | ||
| * Does NOT check: secrets (use detectSecrets), uniqueness (stateful), relations (I/O). | ||
| * Checks: required, type match, semantic type rules, min/max, pattern, select | ||
| * options, and `accept` on media fields (by extension). | ||
| * Does NOT check: secrets (use detectSecrets), uniqueness (stateful), relations (I/O), | ||
| * or `maxSize` — byte length is not knowable from a stored path. | ||
| */ | ||
@@ -172,2 +302,4 @@ function validateFieldValue(value, fieldDef) { | ||
| } | ||
| issues.push(...validateSemanticType(value, fieldDef.type)); | ||
| if (fieldDef.accept && isMediaType(fieldDef.type) && typeof value === "string") issues.push(...validateAccept(value, fieldDef.accept)); | ||
| const minMaxErr = checkMinMax(value, fieldDef); | ||
@@ -380,4 +512,4 @@ if (minMaxErr) issues.push({ | ||
| //#endregion | ||
| 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 }; | ||
| export { CANONICAL_JSON, CONTENTRAIN_BRANCH, CONTENTRAIN_DIR, ENTRY_ID_PATTERN, LOCALE_PATTERN, LOCAL_CAPABILITIES, PATH_PATTERNS, SECRET_PATTERNS, SLUG_PATTERN, canonicalStringify, detectSecrets, generateEntryId, isMediaType, parseMarkdownFrontmatter, serializeMarkdownFrontmatter, sortKeys, validateAccept, validateEntryId, validateFieldValue, validateLocale, validateSemanticType, validateSlug }; | ||
| //# sourceMappingURL=index.mjs.map |
@@ -1,1 +0,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 * Outcome of the post-merge source-branch cleanup on the remote.\n * `deleted: false` with `skipped` is an expected no-op (cleanup disabled,\n * no remote, ref already gone); `warning` is a real failure (offline,\n * auth) that did NOT fail the merge itself.\n */\n remote?: {\n deleted: boolean\n skipped?: string\n warning?: string\n }\n}\n\n// ─── Media (optional provider facet) ───\n\n/**\n * One media asset as the provider's media stack reports it. `path` is the\n * repo-relative storage path content fields reference (`media/...`); `url`\n * is the absolute delivery URL when the provider can resolve one (see\n * `RepoProvider.mediaBaseUrl`). `meta` carries provider-defined extras\n * (dimensions, blurhash, variants) without widening this contract.\n */\nexport interface MediaAsset {\n id: string\n path: string\n url?: string\n mime?: string\n size?: number\n alt?: string\n tags?: string[]\n createdAt?: string\n meta?: Record<string, unknown>\n}\n\nexport interface MediaListOptions {\n /** Substring match on filename/path/alt. */\n search?: string\n /** Filter to assets carrying this tag. */\n tag?: string\n /** Page size — implementations may clamp. */\n limit?: number\n /** Opaque cursor from a previous `MediaListResult.nextCursor`. */\n cursor?: string\n}\n\nexport interface MediaListResult {\n assets: MediaAsset[]\n nextCursor?: string\n total?: number\n}\n\n/**\n * URL-based ingest — MCP has no binary channel, so the source is always a\n * URL the provider fetches server-side. Implementations MUST enforce their\n * own SSRF, MIME, and size policies before fetching; MCP passes the input\n * through verbatim and never fetches the URL itself.\n */\nexport interface MediaIngestInput {\n url: string\n filename?: string\n alt?: string\n tags?: string[]\n}\n\nexport interface MediaUpdateInput {\n alt?: string\n tags?: string[]\n filename?: string\n}\n\n/**\n * Optional media facet of a provider. When present on `RepoProvider.media`,\n * the MCP server registers the `contentrain_media_*` tools; when absent\n * (LocalProvider, plain GitHub/GitLab providers), those tools are not\n * listed at all. Hosted providers (Studio MCP Cloud) implement this by\n * delegating to their media stack — deterministic passthrough, no content\n * decisions in MCP.\n */\nexport interface MediaProvider {\n list(opts?: MediaListOptions): Promise<MediaListResult>\n get(id: string): Promise<MediaAsset | null>\n ingest(input: MediaIngestInput): Promise<MediaAsset>\n update(id: string, patch: MediaUpdateInput): Promise<MediaAsset>\n delete(id: string): Promise<void>\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 /**\n * Per-project public media delivery base, e.g.\n * `https://studio.example/api/cdn/v1/{projectId}` — already includes the\n * project segment, so the content-write path only joins `{base}/{path}`.\n *\n * Set by hosted providers (Studio's MCP Cloud loopback resolves it from a\n * request header and attaches it here). When present, content writes\n * normalize relative `media/...` references — in media/image/file fields and\n * markdown bodies — to absolute delivery URLs, so external-agent writes\n * render anywhere without an SDK. Undefined for local/CLI providers, where\n * media stays a relative path (the OSS file model). Never affects reads.\n */\n readonly mediaBaseUrl?: string\n\n /**\n * Optional media facet. Present only on providers whose backend exposes a\n * media stack (Studio MCP Cloud); drives the `contentrain_media_*` MCP\n * tools, which are not registered when this is absent.\n */\n readonly media?: MediaProvider\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 /**\n * Merge `branch` into `into`. Like `git merge` and the platform merge APIs,\n * the source branch is left in place by default. Pass\n * `opts.removeSourceBranch: true` to also delete it after a successful merge\n * (best-effort — reported via `MergeResult.remote`, never a thrown error).\n * Even when opted in, a long-lived branch is never deleted: not `into`, not\n * the `contentrain` content branch, and not the repo's default branch.\n * LocalProvider ignores the option: its cleanup is governed by\n * `config.remoteBranchCleanup` and its own `cr/*`-only guard.\n */\n mergeBranch(branch: string, into: string, opts?: { removeSourceBranch?: boolean }): 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 /**\n * Public media delivery base for resolving relative `media/...` references to\n * absolute URLs (e.g. a deployed CDN delivery endpoint). When set,\n * `@contentrain/query`'s `generate` bakes a `media()` resolver into the\n * generated client. The base may already include any project segment — the\n * resolver only joins `{cdn.url}/{path}`. Distinct from `assets_path` (the\n * local on-disk asset directory).\n */\n cdn?: {\n url?: string\n }\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 * Delete the remote copy of a `cr/*` branch when it is merged or deleted\n * locally, and allow lazy pruning of already-merged remote branches.\n * Read-only remote visibility (doctor, branch listing) is not affected.\n * Default: true.\n */\n remoteBranchCleanup?: boolean\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 MediaAsset,\n MediaIngestInput,\n MediaListOptions,\n MediaListResult,\n MediaProvider,\n MediaUpdateInput,\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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"} | ||
| {"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 * Outcome of the post-merge source-branch cleanup on the remote.\n * `deleted: false` with `skipped` is an expected no-op (cleanup disabled,\n * no remote, ref already gone); `warning` is a real failure (offline,\n * auth) that did NOT fail the merge itself.\n */\n remote?: {\n deleted: boolean\n skipped?: string\n warning?: string\n }\n}\n\n// ─── Media (optional provider facet) ───\n\n/**\n * One media asset as the provider's media stack reports it. `path` is the\n * repo-relative storage path content fields reference (`media/...`); `url`\n * is the absolute delivery URL when the provider can resolve one (see\n * `RepoProvider.mediaBaseUrl`). `meta` carries provider-defined extras\n * (dimensions, blurhash, variants) without widening this contract.\n */\nexport interface MediaAsset {\n id: string\n path: string\n url?: string\n mime?: string\n size?: number\n alt?: string\n tags?: string[]\n createdAt?: string\n meta?: Record<string, unknown>\n}\n\nexport interface MediaListOptions {\n /** Substring match on filename/path/alt. */\n search?: string\n /** Filter to assets carrying this tag. */\n tag?: string\n /** Page size — implementations may clamp. */\n limit?: number\n /** Opaque cursor from a previous `MediaListResult.nextCursor`. */\n cursor?: string\n}\n\nexport interface MediaListResult {\n assets: MediaAsset[]\n nextCursor?: string\n total?: number\n}\n\n/**\n * URL-based ingest — MCP has no binary channel, so the source is always a\n * URL the provider fetches server-side. Implementations MUST enforce their\n * own SSRF, MIME, and size policies before fetching; MCP passes the input\n * through verbatim and never fetches the URL itself.\n */\nexport interface MediaIngestInput {\n url: string\n filename?: string\n alt?: string\n tags?: string[]\n}\n\nexport interface MediaUpdateInput {\n alt?: string\n tags?: string[]\n filename?: string\n}\n\n/**\n * Optional media facet of a provider. When present on `RepoProvider.media`,\n * the MCP server registers the `contentrain_media_*` tools; when absent\n * (LocalProvider, plain GitHub/GitLab providers), those tools are not\n * listed at all. Hosted providers (Studio MCP Cloud) implement this by\n * delegating to their media stack — deterministic passthrough, no content\n * decisions in MCP.\n */\nexport interface MediaProvider {\n list(opts?: MediaListOptions): Promise<MediaListResult>\n get(id: string): Promise<MediaAsset | null>\n ingest(input: MediaIngestInput): Promise<MediaAsset>\n update(id: string, patch: MediaUpdateInput): Promise<MediaAsset>\n delete(id: string): Promise<void>\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 /**\n * Per-project public media delivery base, e.g.\n * `https://studio.example/api/cdn/v1/{projectId}` — already includes the\n * project segment, so the content-write path only joins `{base}/{path}`.\n *\n * Set by hosted providers (Studio's MCP Cloud loopback resolves it from a\n * request header and attaches it here). When present, content writes\n * normalize relative `media/...` references — in media/image/file fields and\n * markdown bodies — to absolute delivery URLs, so external-agent writes\n * render anywhere without an SDK. Undefined for local/CLI providers, where\n * media stays a relative path (the OSS file model). Never affects reads.\n */\n readonly mediaBaseUrl?: string\n\n /**\n * Optional media facet. Present only on providers whose backend exposes a\n * media stack (Studio MCP Cloud); drives the `contentrain_media_*` MCP\n * tools, which are not registered when this is absent.\n */\n readonly media?: MediaProvider\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 /**\n * Merge `branch` into `into`. Like `git merge` and the platform merge APIs,\n * the source branch is left in place by default. Pass\n * `opts.removeSourceBranch: true` to also delete it after a successful merge\n * (best-effort — reported via `MergeResult.remote`, never a thrown error).\n * Even when opted in, a long-lived branch is never deleted: not `into`, not\n * the `contentrain` content branch, and not the repo's default branch.\n * LocalProvider ignores the option: its cleanup is governed by\n * `config.remoteBranchCleanup` and its own `cr/*`-only guard.\n */\n mergeBranch(branch: string, into: string, opts?: { removeSourceBranch?: boolean }): 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 /**\n * Public media delivery base for resolving relative `media/...` references to\n * absolute URLs (e.g. a deployed CDN delivery endpoint). When set,\n * `@contentrain/query`'s `generate` bakes a `media()` resolver into the\n * generated client. The base may already include any project segment — the\n * resolver only joins `{cdn.url}/{path}`. Distinct from `assets_path` (the\n * local on-disk asset directory).\n */\n cdn?: {\n url?: string\n }\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 * Delete the remote copy of a `cr/*` branch when it is merged or deleted\n * locally, and allow lazy pruning of already-merged remote branches.\n * Read-only remote visibility (doctor, branch listing) is not affected.\n * Default: true.\n */\n remoteBranchCleanup?: boolean\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// ─── Semantic type validation ───\n\n/** Hex (#rgb/#rrggbb/#rrggbbaa), rgb()/rgba(), hsl()/hsla(), or a bare CSS keyword. */\nconst COLOR_PATTERN = /^(#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})|(?:rgb|hsl)a?\\([^)]*\\)|[a-zA-Z]+)$/\n/** Deliberately loose: digits, spaces, and the usual separators, 4-20 digits total. */\nconst PHONE_PATTERN = /^\\+?[\\d\\s().-]{4,}$/\nconst EMAIL_PATTERN = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\nconst DATE_PATTERN = /^\\d{4}-\\d{2}-\\d{2}$/\n\nfunction isParsableDate(value: string): boolean {\n return !Number.isNaN(new Date(value).getTime())\n}\n\n/**\n * Rules a field type implies beyond its runtime `typeof`.\n *\n * Severity follows one rule: a check that is *definitional* is an error — a `slug`\n * that does not match `SLUG_PATTERN` is not a slug. A check that is a *heuristic* is\n * a warning, because the pattern is an approximation and a legitimate value may sit\n * outside it (international phone formats, CSS colour keywords).\n *\n * `rating` is deliberately absent: its scale is never declared, so any range here\n * would be invented. Declare `min`/`max` on the field instead.\n *\n * Returns `[]` for types whose only contract is their `typeof` (`string`, `text`,\n * `code`, `icon`, `markdown`, `richtext`, `number`, `decimal`, `boolean`, media).\n */\nexport function validateSemanticType(value: unknown, type: FieldType): ValidationError[] {\n if (typeof value === 'number') {\n if (type === 'integer' && !Number.isInteger(value)) {\n return [{ severity: 'error', message: `Value ${value} is not an integer` }]\n }\n if (type === 'percent' && (value < 0 || value > 100)) {\n return [{ severity: 'error', message: `Percent ${value} is outside 0-100` }]\n }\n return []\n }\n\n if (typeof value !== 'string' || value === '') return []\n\n switch (type) {\n case 'slug': {\n const err = validateSlug(value)\n return err ? [{ severity: 'error', message: err }] : []\n }\n case 'date':\n return DATE_PATTERN.test(value) && isParsableDate(value)\n ? []\n : [{ severity: 'error', message: `Invalid date \"${value}\": must be YYYY-MM-DD` }]\n case 'datetime':\n return isParsableDate(value)\n ? []\n : [{ severity: 'error', message: `Invalid datetime \"${value}\": must be an ISO 8601 date-time` }]\n case 'email':\n return EMAIL_PATTERN.test(value)\n ? []\n : [{ severity: 'warning', message: `\"${value}\" may not be a valid email` }]\n case 'url':\n return /^https?:\\/\\/.+/.test(value) || value.startsWith('/')\n ? []\n : [{ severity: 'warning', message: `\"${value}\" may not be a valid URL` }]\n case 'color':\n return COLOR_PATTERN.test(value)\n ? []\n : [{ severity: 'warning', message: `\"${value}\" may not be a valid color (expected hex, rgb(), hsl() or a keyword)` }]\n case 'phone':\n return PHONE_PATTERN.test(value) && (value.match(/\\d/g)?.length ?? 0) >= 4\n ? []\n : [{ severity: 'warning', message: `\"${value}\" may not be a valid phone number` }]\n default:\n return []\n }\n}\n\n/** Media field types — the ones whose value is a path/URL to an asset. */\nconst MEDIA_TYPES = new Set<FieldType>(['image', 'video', 'file'])\n\nexport function isMediaType(type: FieldType): boolean {\n return MEDIA_TYPES.has(type)\n}\n\n/** Extensions we can name a MIME type for. Deliberately small — this is a sniff. */\nconst EXTENSION_MIME: Record<string, string> = {\n jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png', gif: 'image/gif',\n webp: 'image/webp', avif: 'image/avif', svg: 'image/svg+xml', ico: 'image/x-icon',\n mp4: 'video/mp4', webm: 'video/webm', mov: 'video/quicktime',\n pdf: 'application/pdf', zip: 'application/zip', json: 'application/json',\n csv: 'text/csv', txt: 'text/plain',\n}\n\n/**\n * Check a media value's file extension against an `accept` list.\n *\n * This is an approximation and says so: the value is a path or URL, so the real\n * MIME type is only knowable where the bytes are — the provider, at ingest\n * (`provider.ts:MediaProvider.ingest` owns that policy). An extension can lie, and\n * an unknown extension is not evidence of a violation. Hence: warning, never error,\n * and silence when we cannot tell.\n *\n * `accept` follows the HTML input syntax: `image/jpeg`, `image/*`, `.jpg`, comma-separated.\n */\nexport function validateAccept(value: string, accept: string): ValidationError[] {\n const path = value.split('?')[0]?.split('#')[0] ?? value\n const ext = path.split('.').pop()?.toLowerCase()\n if (!ext || ext === path.toLowerCase()) return []\n\n const mime = EXTENSION_MIME[ext]\n const patterns = accept.split(',').map(a => a.trim().toLowerCase()).filter(Boolean)\n if (patterns.length === 0) return []\n\n const matches = patterns.some((p) => {\n if (p.startsWith('.')) return p.slice(1) === ext\n if (p.endsWith('/*')) return mime ? mime.startsWith(p.slice(0, -1)) : false\n return mime === p\n })\n if (matches) return []\n\n // An extension we have no MIME for is not evidence of a violation.\n if (!mime && !patterns.some(p => p.startsWith('.'))) return []\n\n return [{\n severity: 'warning',\n message: `File extension \".${ext}\" does not match accept \"${accept}\" (extension check — the provider enforces the real MIME type at ingest)`,\n }]\n}\n\n/**\n * Validate a field value against its FieldDef schema.\n * Checks: required, type match, semantic type rules, min/max, pattern, select\n * options, and `accept` on media fields (by extension).\n * Does NOT check: secrets (use detectSecrets), uniqueness (stateful), relations (I/O),\n * or `maxSize` — byte length is not knowable from a stored path.\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 issues.push(...validateSemanticType(value, fieldDef.type))\n\n if (fieldDef.accept && isMediaType(fieldDef.type) && typeof value === 'string') {\n issues.push(...validateAccept(value, fieldDef.accept))\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 MediaAsset,\n MediaIngestInput,\n MediaListOptions,\n MediaListResult,\n MediaProvider,\n MediaUpdateInput,\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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"} |
+1
-1
| { | ||
| "name": "@contentrain/types", | ||
| "version": "0.8.0", | ||
| "version": "0.9.0", | ||
| "license": "MIT", | ||
@@ -5,0 +5,0 @@ "description": "Shared TypeScript types for Contentrain ecosystem", |
119755
14.58%512
34.74%