@contentrain/mcp 1.1.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/dist/{apply-manager-CMAoS_nu.mjs → apply-manager-Cf5OWOJ7.mjs} +7 -7
- package/dist/{apply-manager-CMAoS_nu.mjs.map → apply-manager-Cf5OWOJ7.mjs.map} +1 -1
- package/dist/{branch-lifecycle-B7TgkxGr.mjs → branch-lifecycle-wGJg25LT.mjs} +3 -3
- package/dist/{branch-lifecycle-B7TgkxGr.mjs.map → branch-lifecycle-wGJg25LT.mjs.map} +1 -1
- package/dist/{config-CPlhDwsv.mjs → config-B5kc2oeV.mjs} +2 -2
- package/dist/{config-CPlhDwsv.mjs.map → config-B5kc2oeV.mjs.map} +1 -1
- package/dist/{context-CUFulSi2.mjs → context-DIDt_TxX.mjs} +4 -4
- package/dist/{context-CUFulSi2.mjs.map → context-DIDt_TxX.mjs.map} +1 -1
- package/dist/core/apply-manager.mjs +10 -5
- package/dist/core/config.mjs +3 -1
- package/dist/core/content-manager.d.mts +1 -9
- package/dist/core/content-manager.d.mts.map +1 -1
- package/dist/core/content-manager.mjs +4 -1
- package/dist/core/context.mjs +6 -2
- package/dist/core/graph-builder.mjs +4 -1
- package/dist/core/meta-manager.mjs +3 -1
- package/dist/core/model-manager.mjs +4 -1
- package/dist/core/scan-config.mjs +3 -1
- package/dist/core/scanner.mjs +4 -1
- package/dist/core/validator.d.mts.map +1 -1
- package/dist/core/validator.mjs +6 -2
- package/dist/{detect-C8XmJ4S6.mjs → detect-DinOQAmK.mjs} +2 -2
- package/dist/{detect-C8XmJ4S6.mjs.map → detect-DinOQAmK.mjs.map} +1 -1
- package/dist/{fs-BbuGnVcG.mjs → fs-BnIQ4dKj.mjs} +2 -2
- package/dist/{fs-BbuGnVcG.mjs.map → fs-BnIQ4dKj.mjs.map} +1 -1
- package/dist/git/branch-lifecycle.mjs +4 -1
- package/dist/git/transaction.mjs +8 -3
- package/dist/{graph-builder-fPrDDwnu.mjs → graph-builder-tM7zj-SX.mjs} +3 -3
- package/dist/{graph-builder-fPrDDwnu.mjs.map → graph-builder-tM7zj-SX.mjs.map} +1 -1
- package/dist/{id-BdD4HvMY.mjs → id-DV_T9Ic8.mjs} +3 -5
- package/dist/id-DV_T9Ic8.mjs.map +1 -0
- package/dist/index.mjs +16 -9
- package/dist/index.mjs.map +1 -1
- package/dist/{meta-manager-CECrfsKm.mjs → meta-manager--0IwAPCl.mjs} +2 -2
- package/dist/{meta-manager-CECrfsKm.mjs.map → meta-manager--0IwAPCl.mjs.map} +1 -1
- package/dist/{model-manager-kl78AAMg.mjs → model-manager-DW_fOGRM.mjs} +16 -186
- package/dist/model-manager-DW_fOGRM.mjs.map +1 -0
- package/dist/{scan-config-AJGxCdNd.mjs → scan-config-QgnJ1-52.mjs} +2 -2
- package/dist/{scan-config-AJGxCdNd.mjs.map → scan-config-QgnJ1-52.mjs.map} +1 -1
- package/dist/{scanner-BdTiv8sw.mjs → scanner-DPrbU-oK.mjs} +3 -3
- package/dist/{scanner-BdTiv8sw.mjs.map → scanner-DPrbU-oK.mjs.map} +1 -1
- package/dist/serializer-CnHTtmc1.mjs +2 -0
- package/dist/{server-CDv6JNRi.mjs → server-BXguCuJb.mjs} +16 -16
- package/dist/{server-CDv6JNRi.mjs.map → server-BXguCuJb.mjs.map} +1 -1
- package/dist/server.mjs +16 -9
- package/dist/templates/index.mjs +1 -1
- package/dist/{templates-3HWi-PTu.mjs → templates-bRPzYq4M.mjs} +1 -1
- package/dist/{templates-3HWi-PTu.mjs.map → templates-bRPzYq4M.mjs.map} +1 -1
- package/dist/{transaction-CncRbpI9.mjs → transaction-B0V0_SE2.mjs} +6 -6
- package/dist/{transaction-CncRbpI9.mjs.map → transaction-B0V0_SE2.mjs.map} +1 -1
- package/dist/util/detect.mjs +3 -1
- package/dist/util/fs.mjs +2 -1
- package/dist/util/id.d.mts +2 -1
- package/dist/util/id.d.mts.map +1 -1
- package/dist/util/id.mjs +1 -1
- package/dist/util/serializer.d.mts +2 -14
- package/dist/util/serializer.mjs +2 -2
- package/dist/{validator-CyckZAVr.mjs → validator-SQYUV2JP.mjs} +23 -197
- package/dist/validator-SQYUV2JP.mjs.map +1 -0
- package/package.json +3 -3
- package/dist/id-BdD4HvMY.mjs.map +0 -1
- package/dist/model-manager-kl78AAMg.mjs.map +0 -1
- package/dist/serializer-Dueih-i2.mjs +0 -33
- package/dist/serializer-Dueih-i2.mjs.map +0 -1
- package/dist/util/serializer.d.mts.map +0 -1
- package/dist/validator-CyckZAVr.mjs.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"serializer.d.mts","names":[],"sources":["../../src/util/serializer.ts"],"mappings":";;AAgCA;;;;;;;;iBAAgB,kBAAA,CAAmB,IAAA,WAAe,UAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"validator-CyckZAVr.mjs","names":[],"sources":["../src/core/validator.ts"],"sourcesContent":["import type { ValidationError, ModelDefinition, ContentrainConfig, FieldDef, EntryMeta } from '@contentrain/types'\nimport { join } from 'node:path'\nimport { rm } from 'node:fs/promises'\nimport { contentrainDir, readDir, readJson, readText, writeJson, writeText, pathExists } from '../util/fs.js'\nimport { readConfig } from './config.js'\nimport { listModels, readModel } from './model-manager.js'\nimport { writeMeta } from './meta-manager.js'\nimport { parseFrontmatter, resolveContentDir, resolveJsonFilePath, resolveMdFilePath, resolveLocaleStrategy } from './content-manager.js'\n\n// ─── Types ───\n\nexport interface ValidateOptions {\n model?: string\n fix?: boolean\n}\n\nexport interface ValidateResult {\n valid: boolean\n summary: {\n errors: number\n warnings: number\n notices: number\n models_checked: number\n entries_checked: number\n }\n issues: ValidationError[]\n fixed: number\n}\n\n// ─── Secret detection patterns ───\n\nconst SECRET_PATTERNS = [\n /sk_/,\n /pk_/,\n /api_key/i,\n /apikey/i,\n /ghp_/,\n /gho_/,\n /Bearer /,\n /postgres:\\/\\//,\n /mysql:\\/\\//,\n /mongodb:\\/\\//,\n /-----BEGIN/,\n /AKIA/,\n /aws_secret/i,\n]\n\n// ─── Validation helpers ───\n\nfunction checkSecret(value: unknown): boolean {\n if (typeof value !== 'string') return false\n return SECRET_PATTERNS.some(p => p.test(value))\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// ─── Schedule validation ───\n\nfunction validateScheduleFields(\n meta: EntryMeta,\n ctx: { model: string; locale: string; entry?: string; slug?: string },\n issues: ValidationError[],\n): void {\n if (meta.publish_at !== undefined) {\n const d = new Date(meta.publish_at)\n if (Number.isNaN(d.getTime())) {\n issues.push({\n severity: 'error',\n ...ctx,\n message: `Invalid publish_at date: \"${meta.publish_at}\". Must be a valid ISO 8601 date string.`,\n })\n }\n }\n if (meta.expire_at !== undefined) {\n const d = new Date(meta.expire_at)\n if (Number.isNaN(d.getTime())) {\n issues.push({\n severity: 'error',\n ...ctx,\n message: `Invalid expire_at date: \"${meta.expire_at}\". Must be a valid ISO 8601 date string.`,\n })\n }\n }\n if (meta.publish_at !== undefined && meta.expire_at !== undefined) {\n const pubDate = new Date(meta.publish_at)\n const expDate = new Date(meta.expire_at)\n if (!Number.isNaN(pubDate.getTime()) && !Number.isNaN(expDate.getTime()) && expDate <= pubDate) {\n issues.push({\n severity: 'error',\n ...ctx,\n message: `expire_at (\"${meta.expire_at}\") must be after publish_at (\"${meta.publish_at}\").`,\n })\n }\n }\n}\n\n// ─── Shared field validation ───\n\ninterface FieldContext {\n model: string\n locale: string\n entry?: string\n slug?: string\n}\n\nasync function checkRelation(\n projectRoot: string,\n fieldName: string,\n fieldDef: FieldDef,\n value: unknown,\n locale: string,\n ctx: FieldContext,\n issues: ValidationError[],\n): Promise<void> {\n if ((fieldDef.type !== 'relation' && fieldDef.type !== 'relations') || value === undefined || value === null) return\n\n const targets = Array.isArray(fieldDef.model) ? fieldDef.model : fieldDef.model ? [fieldDef.model] : []\n const refs = fieldDef.type === 'relations' && Array.isArray(value) ? value : [value]\n\n for (const ref of refs) {\n if (typeof ref !== 'string') continue\n let found = false\n for (const targetModelId of targets) {\n const targetModel = await readModel(projectRoot, targetModelId)\n if (!targetModel) {\n issues.push({\n severity: 'error',\n ...ctx,\n field: fieldName,\n message: `Broken relation: target model \"${targetModelId}\" not found`,\n })\n found = true\n break\n }\n\n if (targetModel.kind === 'document') {\n // Document relations reference by slug — use locale-strategy-aware discovery\n const docContentDir = resolveContentDir(projectRoot, targetModel)\n const slugs = await discoverDocumentSlugs(docContentDir, targetModel)\n if (slugs.includes(ref)) {\n found = true\n break\n }\n } else if (targetModel.kind === 'singleton' || targetModel.kind === 'dictionary') {\n // Relations to singletons/dictionaries are unusual but shouldn't error\n found = true\n break\n } else {\n // Collection: validate against entry IDs (object-map keys)\n const targetDir = resolveContentDir(projectRoot, targetModel)\n const targetData = await readJson<Record<string, unknown>>(\n resolveJsonFilePath(targetDir, targetModel, locale),\n )\n if (targetData && ref in targetData) {\n found = true\n break\n }\n }\n }\n if (!found) {\n issues.push({\n severity: 'error',\n ...ctx,\n field: fieldName,\n message: `Broken relation: referenced ${targets.some(t => t) ? 'ref' : 'ID'} \"${ref}\" not found in target model(s)`,\n })\n }\n }\n}\n\n// ─── Per-model validators ───\n\nasync function validateCollectionModel(\n projectRoot: string,\n model: ModelDefinition,\n config: ContentrainConfig,\n issues: ValidationError[],\n fix: boolean,\n): Promise<{ entries: number; fixed: number }> {\n let entriesChecked = 0\n let fixed = 0\n const cDir = resolveContentDir(projectRoot, model)\n const metaDir = join(contentrainDir(projectRoot), 'meta', model.id)\n const locales = config.locales.supported\n\n // Collect all entry IDs per locale for parity check\n const localeEntryIds: Record<string, Set<string>> = {}\n const allEntryIds = new Set<string>()\n\n // Collect unique values for unique constraint check\n const uniqueFieldValues: Record<string, Map<string, string>> = {}\n\n for (const locale of locales) {\n const filePath = resolveJsonFilePath(cDir, model, locale)\n const data = await readJson<Record<string, Record<string, unknown>>>(filePath)\n if (!data) {\n if (model.i18n) {\n issues.push({\n severity: 'error',\n model: model.id,\n locale,\n message: `Locale file missing: ${locale}.json`,\n })\n\n if (fix) {\n await writeJson(filePath, {})\n fixed++\n }\n }\n continue\n }\n\n const ids = new Set(Object.keys(data))\n localeEntryIds[locale] = ids\n for (const id of ids) allEntryIds.add(id)\n\n // Check canonical sort\n const keys = Object.keys(data)\n const sorted = [...keys].toSorted()\n if (keys.join(',') !== sorted.join(',')) {\n issues.push({\n severity: 'warning',\n model: model.id,\n locale,\n message: 'Content file keys not in canonical order',\n })\n if (fix) {\n const resorted: Record<string, Record<string, unknown>> = {}\n for (const key of sorted) {\n resorted[key] = data[key]!\n }\n await writeJson(filePath, resorted)\n fixed++\n }\n }\n\n // Validate each entry\n for (const [entryId, fields] of Object.entries(data)) {\n entriesChecked++\n\n // Secret detection\n for (const [fieldName, value] of Object.entries(fields)) {\n if (checkSecret(value)) {\n issues.push({\n severity: 'error',\n model: model.id,\n locale,\n entry: entryId,\n field: fieldName,\n message: `Potential secret detected in field \"${fieldName}\"`,\n })\n }\n }\n\n if (!model.fields) continue\n\n // Field-level validation\n for (const [fieldName, fieldDef] of Object.entries(model.fields)) {\n const value = fields[fieldName]\n\n // Required check\n if (fieldDef.required && (value === undefined || value === null || value === '')) {\n issues.push({\n severity: 'error',\n model: model.id,\n locale,\n entry: entryId,\n field: fieldName,\n message: `Required field \"${fieldName}\" is missing`,\n })\n }\n\n // Type mismatch\n if (value !== undefined && value !== null && !fieldTypeMatches(value, fieldDef)) {\n issues.push({\n severity: 'error',\n model: model.id,\n locale,\n entry: entryId,\n field: fieldName,\n message: `Type mismatch: expected ${fieldDef.type}, got ${typeof value}`,\n })\n }\n\n // Min/max\n if (value !== undefined && value !== null) {\n const minMaxErr = checkMinMax(value, fieldDef)\n if (minMaxErr) {\n issues.push({\n severity: 'error',\n model: model.id,\n locale,\n entry: entryId,\n field: fieldName,\n message: minMaxErr,\n })\n }\n }\n\n // Pattern regex check\n if (fieldDef.pattern && typeof value === 'string') {\n try {\n const regex = new RegExp(fieldDef.pattern)\n if (!regex.test(value)) {\n issues.push({\n severity: 'error',\n model: model.id,\n locale,\n entry: entryId,\n field: fieldName,\n message: `Value \"${value}\" does not match pattern /${fieldDef.pattern}/`,\n })\n }\n } catch {\n // Invalid regex in schema — report as warning\n issues.push({\n severity: 'warning',\n model: model.id,\n locale,\n entry: entryId,\n field: fieldName,\n message: `Invalid pattern regex: ${fieldDef.pattern}`,\n })\n }\n }\n\n // Unique constraint\n if (fieldDef.unique && value !== undefined && value !== null) {\n const key = `${fieldName}:${locale}`\n if (!uniqueFieldValues[key]) uniqueFieldValues[key] = new Map()\n const existing = uniqueFieldValues[key]!.get(String(value))\n if (existing) {\n issues.push({\n severity: 'error',\n model: model.id,\n locale,\n entry: entryId,\n field: fieldName,\n message: `Duplicate value \"${value}\" violates unique constraint (also in entry \"${existing}\")`,\n })\n } else {\n uniqueFieldValues[key]!.set(String(value), entryId)\n }\n }\n\n // Broken relation\n await checkRelation(projectRoot, fieldName, fieldDef, value, locale, { model: model.id, locale, entry: entryId }, issues)\n }\n }\n }\n\n // Entry parity check (i18n)\n if (model.i18n && Object.keys(localeEntryIds).length > 1) {\n const localeKeys = Object.keys(localeEntryIds)\n for (let i = 1; i < localeKeys.length; i++) {\n const locA = localeKeys[0]!\n const locB = localeKeys[i]!\n const idsA = localeEntryIds[locA]!\n const idsB = localeEntryIds[locB]!\n\n for (const id of idsA) {\n if (!idsB.has(id)) {\n issues.push({\n severity: 'error',\n model: model.id,\n locale: locB,\n entry: id,\n message: `Entry parity: entry \"${id}\" exists in ${locA} but missing in ${locB}`,\n })\n }\n }\n for (const id of idsB) {\n if (!idsA.has(id)) {\n issues.push({\n severity: 'error',\n model: model.id,\n locale: locA,\n entry: id,\n message: `Entry parity: entry \"${id}\" exists in ${locB} but missing in ${locA}`,\n })\n }\n }\n }\n }\n\n // Orphan meta check\n for (const locale of locales) {\n const metaPath = join(metaDir, `${locale}.json`)\n const metaData = await readJson<Record<string, EntryMeta>>(metaPath)\n const contentData = await readJson<Record<string, unknown>>(resolveJsonFilePath(cDir, model, locale)) ?? {}\n\n // Forward check: meta entries without content + schedule validation\n if (metaData) {\n for (const metaEntryId of Object.keys(metaData)) {\n // Validate schedule fields on every meta entry\n validateScheduleFields(metaData[metaEntryId]!, { model: model.id, locale, entry: metaEntryId }, issues)\n\n if (!(metaEntryId in contentData)) {\n issues.push({\n severity: 'warning',\n model: model.id,\n locale,\n entry: metaEntryId,\n message: `Orphan meta: meta entry \"${metaEntryId}\" exists but content entry missing`,\n })\n if (fix) {\n delete metaData[metaEntryId]\n if (Object.keys(metaData).length > 0) {\n await writeJson(metaPath, metaData)\n } else {\n await rm(metaPath, { force: true })\n }\n fixed++\n }\n }\n }\n }\n\n // Reverse check: content entries without meta\n for (const entryId of Object.keys(contentData)) {\n if (!metaData || !(entryId in metaData)) {\n issues.push({\n severity: 'warning',\n model: model.id,\n locale,\n entry: entryId,\n message: `Orphan content: entry \"${entryId}\" has no metadata`,\n })\n if (fix) {\n await writeMeta(projectRoot, model, { locale, entryId }, {\n status: 'draft',\n source: 'import',\n updated_by: 'contentrain-mcp',\n })\n fixed++\n }\n }\n }\n }\n\n return { entries: entriesChecked, fixed }\n}\n\nasync function validateSingletonModel(\n projectRoot: string,\n model: ModelDefinition,\n config: ContentrainConfig,\n issues: ValidationError[],\n fix: boolean,\n): Promise<{ entries: number; fixed: number }> {\n let entriesChecked = 0\n let fixed = 0\n const cDir = resolveContentDir(projectRoot, model)\n\n for (const locale of config.locales.supported) {\n const filePath = resolveJsonFilePath(cDir, model, locale)\n const data = await readJson<Record<string, unknown>>(filePath)\n\n if (!data) {\n if (model.i18n) {\n issues.push({\n severity: 'error',\n model: model.id,\n locale,\n message: `Locale file missing: ${locale}.json`,\n })\n if (fix) {\n await writeJson(filePath, {})\n fixed++\n }\n }\n continue\n }\n\n entriesChecked++\n\n // Secret detection\n for (const [fieldName, value] of Object.entries(data)) {\n if (checkSecret(value)) {\n issues.push({\n severity: 'error',\n model: model.id,\n locale,\n field: fieldName,\n message: `Potential secret detected in field \"${fieldName}\"`,\n })\n }\n }\n\n // Field validation\n if (model.fields) {\n for (const [fieldName, fieldDef] of Object.entries(model.fields)) {\n const value = data[fieldName]\n\n if (fieldDef.required && (value === undefined || value === null || value === '')) {\n issues.push({\n severity: 'error',\n model: model.id,\n locale,\n field: fieldName,\n message: `Required field \"${fieldName}\" is missing`,\n })\n }\n\n if (value !== undefined && value !== null && !fieldTypeMatches(value, fieldDef)) {\n issues.push({\n severity: 'error',\n model: model.id,\n locale,\n field: fieldName,\n message: `Type mismatch: expected ${fieldDef.type}, got ${typeof value}`,\n })\n }\n\n if (value !== undefined && value !== null) {\n const minMaxErr = checkMinMax(value, fieldDef)\n if (minMaxErr) {\n issues.push({\n severity: 'error',\n model: model.id,\n locale,\n field: fieldName,\n message: minMaxErr,\n })\n }\n }\n\n // Pattern regex check\n if (fieldDef.pattern && typeof value === 'string') {\n try {\n const regex = new RegExp(fieldDef.pattern)\n if (!regex.test(value)) {\n issues.push({\n severity: 'error',\n model: model.id,\n locale,\n field: fieldName,\n message: `Value \"${value}\" does not match pattern /${fieldDef.pattern}/`,\n })\n }\n } catch {\n issues.push({\n severity: 'warning',\n model: model.id,\n locale,\n field: fieldName,\n message: `Invalid pattern regex: ${fieldDef.pattern}`,\n })\n }\n }\n\n // Broken relation\n await checkRelation(projectRoot, fieldName, fieldDef, value, locale, { model: model.id, locale }, issues)\n }\n }\n\n // Canonical sort check\n const keys = Object.keys(data)\n const sorted = [...keys].toSorted()\n if (keys.join(',') !== sorted.join(',')) {\n issues.push({\n severity: 'warning',\n model: model.id,\n locale,\n message: 'Content file keys not in canonical order',\n })\n if (fix) {\n const resorted: Record<string, unknown> = {}\n for (const key of sorted) {\n resorted[key] = data[key]\n }\n await writeJson(filePath, resorted)\n fixed++\n }\n }\n\n // Validate schedule fields in singleton meta\n const singletonMetaPath = join(contentrainDir(projectRoot), 'meta', model.id, `${locale}.json`)\n const singletonMetaData = await readJson<EntryMeta>(singletonMetaPath)\n if (singletonMetaData) {\n validateScheduleFields(singletonMetaData, { model: model.id, locale }, issues)\n }\n }\n\n return { entries: entriesChecked, fixed }\n}\n\nasync function validateDictionaryModel(\n projectRoot: string,\n model: ModelDefinition,\n config: ContentrainConfig,\n issues: ValidationError[],\n fix: boolean,\n): Promise<{ entries: number; fixed: number }> {\n let entriesChecked = 0\n let fixed = 0\n const cDir = resolveContentDir(projectRoot, model)\n\n const localeKeys: Record<string, Set<string>> = {}\n\n for (const locale of config.locales.supported) {\n const filePath = resolveJsonFilePath(cDir, model, locale)\n const data = await readJson<Record<string, string>>(filePath)\n\n if (!data) {\n if (model.i18n) {\n issues.push({\n severity: 'error',\n model: model.id,\n locale,\n message: `Locale file missing: ${locale}.json`,\n })\n if (fix) {\n await writeJson(filePath, {})\n fixed++\n }\n }\n continue\n }\n\n entriesChecked++\n localeKeys[locale] = new Set(Object.keys(data))\n\n // Secret detection\n for (const [key, value] of Object.entries(data)) {\n if (checkSecret(value)) {\n issues.push({\n severity: 'error',\n model: model.id,\n locale,\n field: key,\n message: `Potential secret detected in key \"${key}\"`,\n })\n }\n }\n\n // Canonical sort check\n const keys = Object.keys(data)\n const sorted = [...keys].toSorted()\n if (keys.join(',') !== sorted.join(',')) {\n issues.push({\n severity: 'warning',\n model: model.id,\n locale,\n message: 'Content file keys not in canonical order',\n })\n if (fix) {\n const resorted: Record<string, string> = {}\n for (const key of sorted) {\n resorted[key] = data[key]!\n }\n await writeJson(filePath, resorted)\n fixed++\n }\n }\n }\n\n // Key parity check\n if (model.i18n && Object.keys(localeKeys).length > 1) {\n const localeNames = Object.keys(localeKeys)\n for (let i = 1; i < localeNames.length; i++) {\n const locA = localeNames[0]!\n const locB = localeNames[i]!\n const keysA = localeKeys[locA]!\n const keysB = localeKeys[locB]!\n\n for (const k of keysA) {\n if (!keysB.has(k)) {\n issues.push({\n severity: 'warning',\n model: model.id,\n locale: locB,\n field: k,\n message: `Key parity: key \"${k}\" exists in ${locA} but missing in ${locB}`,\n })\n }\n }\n for (const k of keysB) {\n if (!keysA.has(k)) {\n issues.push({\n severity: 'warning',\n model: model.id,\n locale: locA,\n field: k,\n message: `Key parity: key \"${k}\" exists in ${locB} but missing in ${locA}`,\n })\n }\n }\n }\n }\n\n return { entries: entriesChecked, fixed }\n}\n\nasync function discoverDocumentSlugs(\n cDir: string,\n model: ModelDefinition,\n): Promise<string[]> {\n const strategy = resolveLocaleStrategy(model)\n const entries = await readDir(cDir)\n\n // When i18n is disabled, documents are flat {slug}.md files\n if (!model.i18n) {\n return entries.filter(f => f.endsWith('.md')).map(f => f.replace(/\\.md$/, ''))\n }\n\n if (strategy === 'file') {\n // Each slug is a subdirectory\n return entries.filter(e => !e.startsWith('.'))\n }\n\n if (strategy === 'suffix') {\n // Files like {slug}.{locale}.md — extract unique slugs\n const slugs = new Set<string>()\n for (const f of entries) {\n if (!f.endsWith('.md')) continue\n // Remove last two dot-separated segments (.locale.md)\n const parts = f.replace(/\\.md$/, '').split('.')\n if (parts.length >= 2) {\n parts.pop() // remove locale\n slugs.add(parts.join('.'))\n }\n }\n return [...slugs]\n }\n\n if (strategy === 'directory') {\n // Locale subdirectories contain {slug}.md files — collect slugs from all locale dirs\n const slugs = new Set<string>()\n for (const localeDir of entries) {\n if (localeDir.startsWith('.')) continue\n const files = await readDir(join(cDir, localeDir))\n for (const f of files) {\n if (f.endsWith('.md')) slugs.add(f.replace(/\\.md$/, ''))\n }\n }\n return [...slugs]\n }\n\n // 'none' — {slug}.md files, no locale\n return entries.filter(f => f.endsWith('.md')).map(f => f.replace(/\\.md$/, ''))\n}\n\nasync function validateDocumentModel(\n projectRoot: string,\n model: ModelDefinition,\n config: ContentrainConfig,\n issues: ValidationError[],\n fix: boolean,\n): Promise<{ entries: number; fixed: number }> {\n let entriesChecked = 0\n let fixed = 0\n const cDir = resolveContentDir(projectRoot, model)\n\n if (!await pathExists(cDir)) return { entries: 0, fixed: 0 }\n\n const slugs = await discoverDocumentSlugs(cDir, model)\n const locales = config.locales.supported\n\n for (const slug of slugs) {\n if (slug.startsWith('.')) continue\n\n for (const locale of locales) {\n const filePath = resolveMdFilePath(cDir, model, locale, slug)\n const raw = await readText(filePath)\n\n if (!raw) {\n if (model.i18n) {\n issues.push({\n severity: 'warning',\n model: model.id,\n locale,\n slug,\n message: `Missing translation: document \"${slug}\" missing ${locale} locale file`,\n })\n if (fix) {\n // Create empty template\n const template = `---\\nslug: ${slug}\\n---\\n`\n await writeText(filePath, template)\n fixed++\n }\n }\n continue\n }\n\n entriesChecked++\n const { frontmatter, body } = parseFrontmatter(raw)\n\n // Secret detection in frontmatter\n for (const [fieldName, value] of Object.entries(frontmatter)) {\n if (checkSecret(value)) {\n issues.push({\n severity: 'error',\n model: model.id,\n locale,\n slug,\n field: fieldName,\n message: `Potential secret detected in field \"${fieldName}\"`,\n })\n }\n }\n\n // Secret detection in body\n if (checkSecret(body)) {\n issues.push({\n severity: 'error',\n model: model.id,\n locale,\n slug,\n field: 'body',\n message: 'Potential secret detected in document body',\n })\n }\n\n // Field validation\n if (model.fields) {\n for (const [fieldName, fieldDef] of Object.entries(model.fields)) {\n if (fieldName === 'body') continue\n const value = frontmatter[fieldName]\n\n if (fieldDef.required && (value === undefined || value === null || value === '')) {\n issues.push({\n severity: 'error',\n model: model.id,\n locale,\n slug,\n field: fieldName,\n message: `Required field \"${fieldName}\" is missing`,\n })\n }\n\n if (value !== undefined && value !== null && !fieldTypeMatches(value, fieldDef)) {\n issues.push({\n severity: 'error',\n model: model.id,\n locale,\n slug,\n field: fieldName,\n message: `Type mismatch: expected ${fieldDef.type}, got ${typeof value}`,\n })\n }\n\n if (value !== undefined && value !== null) {\n const minMaxErr = checkMinMax(value, fieldDef)\n if (minMaxErr) {\n issues.push({\n severity: 'error',\n model: model.id,\n locale,\n slug,\n field: fieldName,\n message: minMaxErr,\n })\n }\n }\n\n // Pattern regex check\n if (fieldDef.pattern && typeof value === 'string') {\n try {\n const regex = new RegExp(fieldDef.pattern)\n if (!regex.test(value)) {\n issues.push({\n severity: 'error',\n model: model.id,\n locale,\n slug,\n field: fieldName,\n message: `Value \"${value}\" does not match pattern /${fieldDef.pattern}/`,\n })\n }\n } catch {\n issues.push({\n severity: 'warning',\n model: model.id,\n locale,\n slug,\n field: fieldName,\n message: `Invalid pattern regex: ${fieldDef.pattern}`,\n })\n }\n }\n\n // Broken relation\n await checkRelation(projectRoot, fieldName, fieldDef, value, locale, { model: model.id, locale, slug }, issues)\n }\n }\n }\n }\n\n return { entries: entriesChecked, fixed }\n}\n\n// ─── Orphan content check ───\n\nasync function checkOrphanContent(\n projectRoot: string,\n validModelIds: Set<string>,\n issues: ValidationError[],\n _fix: boolean,\n): Promise<number> {\n let fixed = 0\n const contentBase = join(contentrainDir(projectRoot), 'content')\n const domains = await readDir(contentBase)\n\n for (const domain of domains) {\n if (domain.startsWith('.')) continue\n const domainDir = join(contentBase, domain)\n const modelDirs = await readDir(domainDir)\n\n for (const modelDir of modelDirs) {\n if (modelDir.startsWith('.')) continue\n if (!validModelIds.has(modelDir)) {\n issues.push({\n severity: 'warning',\n model: modelDir,\n message: `Orphan content: content directory exists for deleted model \"${modelDir}\"`,\n })\n }\n }\n }\n\n return fixed\n}\n\n// ─── Main validation function ───\n\nexport async function validateProject(\n projectRoot: string,\n options?: ValidateOptions,\n): Promise<ValidateResult> {\n const issues: ValidationError[] = []\n const fix = options?.fix ?? false\n let totalEntries = 0\n let totalFixed = 0\n let modelsChecked = 0\n\n const config = await readConfig(projectRoot)\n if (!config) {\n return {\n valid: false,\n summary: { errors: 1, warnings: 0, notices: 0, models_checked: 0, entries_checked: 0 },\n issues: [{ severity: 'error', message: 'Project not initialized: config.json missing' }],\n fixed: 0,\n }\n }\n\n const modelSummaries = await listModels(projectRoot)\n const validModelIds = new Set(modelSummaries.map(m => m.id))\n const modelsToCheck = options?.model\n ? modelSummaries.filter(m => m.id === options.model)\n : modelSummaries\n\n for (const summary of modelsToCheck) {\n const model = await readModel(projectRoot, summary.id)\n if (!model) continue\n\n modelsChecked++\n let result: { entries: number; fixed: number }\n\n switch (model.kind) {\n case 'collection':\n result = await validateCollectionModel(projectRoot, model, config, issues, fix)\n break\n case 'singleton':\n result = await validateSingletonModel(projectRoot, model, config, issues, fix)\n break\n case 'dictionary':\n result = await validateDictionaryModel(projectRoot, model, config, issues, fix)\n break\n case 'document':\n result = await validateDocumentModel(projectRoot, model, config, issues, fix)\n break\n default:\n result = { entries: 0, fixed: 0 }\n }\n\n totalEntries += result.entries\n totalFixed += result.fixed\n }\n\n // Orphan content check (only when checking all models)\n if (!options?.model) {\n totalFixed += await checkOrphanContent(projectRoot, validModelIds, issues, fix)\n }\n\n const errors = issues.filter(i => i.severity === 'error').length\n const warnings = issues.filter(i => i.severity === 'warning').length\n const notices = issues.filter(i => i.severity === 'notice').length\n\n return {\n valid: errors === 0,\n summary: {\n errors,\n warnings,\n notices,\n models_checked: modelsChecked,\n entries_checked: totalEntries,\n },\n issues,\n fixed: totalFixed,\n }\n}\n"],"mappings":"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"}
|