@contentrain/mcp 0.3.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +147 -238
  3. package/dist/apply-manager-CaTuu7Lj.mjs +751 -0
  4. package/dist/apply-manager-CaTuu7Lj.mjs.map +1 -0
  5. package/dist/astro-parser-RofKmKQi.mjs +166 -0
  6. package/dist/astro-parser-RofKmKQi.mjs.map +1 -0
  7. package/dist/branch-lifecycle-DB6sgeJi.mjs +97 -0
  8. package/dist/branch-lifecycle-DB6sgeJi.mjs.map +1 -0
  9. package/dist/chunk-BEJ448es.mjs +35 -0
  10. package/dist/compiler-rIC4ETHv.mjs +44509 -0
  11. package/dist/compiler-rIC4ETHv.mjs.map +1 -0
  12. package/dist/compiler-sfc.cjs-BbD04rn_.mjs +50279 -0
  13. package/dist/compiler-sfc.cjs-BbD04rn_.mjs.map +1 -0
  14. package/dist/config-CPlhDwsv.mjs +27 -0
  15. package/dist/config-CPlhDwsv.mjs.map +1 -0
  16. package/dist/context-yh3cuw5N.mjs +46 -0
  17. package/dist/context-yh3cuw5N.mjs.map +1 -0
  18. package/dist/core/apply-manager.d.mts +144 -0
  19. package/dist/core/apply-manager.d.mts.map +1 -0
  20. package/dist/core/apply-manager.mjs +6 -0
  21. package/dist/core/config.d.mts +8 -0
  22. package/dist/core/config.d.mts.map +1 -0
  23. package/dist/core/config.mjs +2 -0
  24. package/dist/core/content-manager.d.mts +53 -0
  25. package/dist/core/content-manager.d.mts.map +1 -0
  26. package/dist/core/content-manager.mjs +2 -0
  27. package/dist/core/context.d.mts +13 -0
  28. package/dist/core/context.d.mts.map +1 -0
  29. package/dist/core/context.mjs +3 -0
  30. package/dist/core/graph-builder.d.mts +12 -0
  31. package/dist/core/graph-builder.d.mts.map +1 -0
  32. package/dist/core/graph-builder.mjs +2 -0
  33. package/dist/core/meta-manager.d.mts +14 -0
  34. package/dist/core/meta-manager.d.mts.map +1 -0
  35. package/dist/core/meta-manager.mjs +2 -0
  36. package/dist/core/model-manager.d.mts +44 -0
  37. package/dist/core/model-manager.d.mts.map +1 -0
  38. package/dist/core/model-manager.mjs +2 -0
  39. package/dist/core/scan-config.d.mts +24 -0
  40. package/dist/core/scan-config.d.mts.map +1 -0
  41. package/dist/core/scan-config.mjs +2 -0
  42. package/dist/core/scanner.d.mts +25 -0
  43. package/dist/core/scanner.d.mts.map +1 -0
  44. package/dist/core/scanner.mjs +3 -0
  45. package/dist/core/validator.d.mts +23 -0
  46. package/dist/core/validator.d.mts.map +1 -0
  47. package/dist/core/validator.mjs +3 -0
  48. package/dist/detect-C8XmJ4S6.mjs +362 -0
  49. package/dist/detect-C8XmJ4S6.mjs.map +1 -0
  50. package/dist/fs-BbuGnVcG.mjs +52 -0
  51. package/dist/fs-BbuGnVcG.mjs.map +1 -0
  52. package/dist/git/branch-lifecycle.d.mts +28 -0
  53. package/dist/git/branch-lifecycle.d.mts.map +1 -0
  54. package/dist/git/branch-lifecycle.mjs +2 -0
  55. package/dist/git/transaction.d.mts +25 -0
  56. package/dist/git/transaction.d.mts.map +1 -0
  57. package/dist/git/transaction.mjs +4 -0
  58. package/dist/graph-builder-fPrDDwnu.mjs +162 -0
  59. package/dist/graph-builder-fPrDDwnu.mjs.map +1 -0
  60. package/dist/id-BdD4HvMY.mjs +12 -0
  61. package/dist/id-BdD4HvMY.mjs.map +1 -0
  62. package/dist/index.d.mts +1 -359
  63. package/dist/index.mjs +15 -2161
  64. package/dist/index.mjs.map +1 -1
  65. package/dist/meta-manager-CECrfsKm.mjs +53 -0
  66. package/dist/meta-manager-CECrfsKm.mjs.map +1 -0
  67. package/dist/model-manager-BcWWu5l2.mjs +963 -0
  68. package/dist/model-manager-BcWWu5l2.mjs.map +1 -0
  69. package/dist/node-Cbq5eR5i.mjs +300 -0
  70. package/dist/node-Cbq5eR5i.mjs.map +1 -0
  71. package/dist/scan-config-AJGxCdNd.mjs +159 -0
  72. package/dist/scan-config-AJGxCdNd.mjs.map +1 -0
  73. package/dist/scanner-BdTiv8sw.mjs +619 -0
  74. package/dist/scanner-BdTiv8sw.mjs.map +1 -0
  75. package/dist/serializer-Dueih-i2.mjs +33 -0
  76. package/dist/serializer-Dueih-i2.mjs.map +1 -0
  77. package/dist/server-CTcDpxwW.mjs +1861 -0
  78. package/dist/server-CTcDpxwW.mjs.map +1 -0
  79. package/dist/server.d.mts +7 -0
  80. package/dist/server.d.mts.map +1 -0
  81. package/dist/server.mjs +11 -0
  82. package/dist/source-map-generator-DpO-ejrt.mjs +893 -0
  83. package/dist/source-map-generator-DpO-ejrt.mjs.map +1 -0
  84. package/dist/svelte-parser-COTNG92w.mjs +225 -0
  85. package/dist/svelte-parser-COTNG92w.mjs.map +1 -0
  86. package/dist/templates/index.d.mts +8 -0
  87. package/dist/templates/index.d.mts.map +1 -0
  88. package/dist/templates/index.mjs +2 -0
  89. package/dist/templates-3HWi-PTu.mjs +824 -0
  90. package/dist/templates-3HWi-PTu.mjs.map +1 -0
  91. package/dist/transaction-BsECnmL5.mjs +128 -0
  92. package/dist/transaction-BsECnmL5.mjs.map +1 -0
  93. package/dist/tsx-parser-Bf8Firrq.mjs +2 -0
  94. package/dist/tsx-parser-CYVyBWjn.mjs +234 -0
  95. package/dist/tsx-parser-CYVyBWjn.mjs.map +1 -0
  96. package/dist/util/detect.d.mts +20 -0
  97. package/dist/util/detect.d.mts.map +1 -0
  98. package/dist/util/detect.mjs +2 -0
  99. package/dist/util/fs.d.mts +12 -0
  100. package/dist/util/fs.d.mts.map +1 -0
  101. package/dist/util/fs.mjs +2 -0
  102. package/dist/util/id.d.mts +6 -0
  103. package/dist/util/id.d.mts.map +1 -0
  104. package/dist/util/id.mjs +2 -0
  105. package/dist/util/serializer.d.mts +14 -0
  106. package/dist/util/serializer.d.mts.map +1 -0
  107. package/dist/util/serializer.mjs +2 -0
  108. package/dist/validator-ku84AyFr.mjs +755 -0
  109. package/dist/validator-ku84AyFr.mjs.map +1 -0
  110. package/dist/vue-parser-CjS4YAoQ.mjs +225 -0
  111. package/dist/vue-parser-CjS4YAoQ.mjs.map +1 -0
  112. package/package.json +107 -43
  113. package/dist/fs-D83-Ii_y.mjs +0 -75
  114. package/dist/fs-D83-Ii_y.mjs.map +0 -1
  115. package/dist/index.d.mts.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator-ku84AyFr.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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"}
@@ -0,0 +1,225 @@
1
+ import { i as __toESM } from "./chunk-BEJ448es.mjs";
2
+ //#region src/core/ast-scanner/vue-parser.ts
3
+ const NodeTypes = {
4
+ ROOT: 0,
5
+ ELEMENT: 1,
6
+ TEXT: 2,
7
+ COMMENT: 3,
8
+ SIMPLE_EXPRESSION: 4,
9
+ INTERPOLATION: 5,
10
+ ATTRIBUTE: 6,
11
+ DIRECTIVE: 7,
12
+ COMPOUND_EXPRESSION: 8,
13
+ IF: 9,
14
+ IF_BRANCH: 10,
15
+ FOR: 11,
16
+ TEXT_CALL: 12,
17
+ VNODE_CALL: 13,
18
+ JS_CALL_EXPRESSION: 14
19
+ };
20
+ let _compiler = null;
21
+ async function loadCompiler() {
22
+ if (_compiler) return _compiler;
23
+ try {
24
+ _compiler = await import("./compiler-sfc.cjs-BbD04rn_.mjs").then((m) => /* @__PURE__ */ __toESM(m.default, 1));
25
+ return _compiler;
26
+ } catch {
27
+ throw new Error("@vue/compiler-sfc is required to parse .vue files. Install it with: pnpm add -D @vue/compiler-sfc");
28
+ }
29
+ }
30
+ const CODE_DIRECTIVES = new Set([
31
+ "if",
32
+ "else-if",
33
+ "else",
34
+ "show",
35
+ "for",
36
+ "on",
37
+ "model",
38
+ "memo",
39
+ "once",
40
+ "pre",
41
+ "cloak",
42
+ "is",
43
+ "slot",
44
+ "key"
45
+ ]);
46
+ const CODE_DIRECTIVE_ARGS = new Set([
47
+ "class",
48
+ "style",
49
+ "key",
50
+ "ref",
51
+ "is"
52
+ ]);
53
+ const CSS_ATTRIBUTES = new Set(["class", "style"]);
54
+ const SURROUNDING_MAX = 120;
55
+ function getSurroundingByLine(content, line) {
56
+ const lines = content.split("\n");
57
+ const idx = line - 1;
58
+ if (idx >= 0 && idx < lines.length) return (lines[idx] ?? "").slice(0, SURROUNDING_MAX);
59
+ return "";
60
+ }
61
+ function walkTemplate(node, templateLineOffset, sfcContent, results, parentTag = "") {
62
+ switch (node.type) {
63
+ case NodeTypes.TEXT: {
64
+ const trimmed = (typeof node.content === "string" ? node.content : node.loc?.source ?? "").trim();
65
+ if (trimmed.length > 0 && /\S/.test(trimmed)) results.push({
66
+ value: trimmed,
67
+ line: node.loc.start.line + templateLineOffset - 1,
68
+ column: node.loc.start.column,
69
+ context: "template_text",
70
+ scope: "template",
71
+ parent: parentTag,
72
+ surrounding: getSurroundingByLine(sfcContent, node.loc.start.line + templateLineOffset - 1)
73
+ });
74
+ break;
75
+ }
76
+ case NodeTypes.INTERPOLATION:
77
+ if (node.content && typeof node.content !== "string" && node.content.type === NodeTypes.SIMPLE_EXPRESSION) {
78
+ const stringMatch = (typeof node.content.content === "string" ? node.content.content : "").match(/^(['"`])(.+)\1$/);
79
+ if (stringMatch?.[2]) results.push({
80
+ value: stringMatch[2],
81
+ line: node.loc.start.line + templateLineOffset - 1,
82
+ column: node.loc.start.column,
83
+ context: "template_text",
84
+ scope: "template",
85
+ parent: parentTag,
86
+ surrounding: getSurroundingByLine(sfcContent, node.loc.start.line + templateLineOffset - 1)
87
+ });
88
+ }
89
+ break;
90
+ case NodeTypes.ELEMENT: {
91
+ const tag = node.tag ?? "";
92
+ if (node.props) for (const prop of node.props) processProp(prop, tag, templateLineOffset, sfcContent, results);
93
+ if (node.children) for (const child of node.children) walkTemplate(child, templateLineOffset, sfcContent, results, tag);
94
+ break;
95
+ }
96
+ case NodeTypes.IF:
97
+ if (node.branches) {
98
+ for (const branch of node.branches) if (branch.children) for (const child of branch.children) walkTemplate(child, templateLineOffset, sfcContent, results, parentTag);
99
+ }
100
+ break;
101
+ case NodeTypes.IF_BRANCH:
102
+ if (node.children) for (const child of node.children) walkTemplate(child, templateLineOffset, sfcContent, results, parentTag);
103
+ break;
104
+ case NodeTypes.FOR:
105
+ if (node.children) for (const child of node.children) walkTemplate(child, templateLineOffset, sfcContent, results, parentTag);
106
+ break;
107
+ case NodeTypes.ROOT:
108
+ if (node.children) for (const child of node.children) walkTemplate(child, templateLineOffset, sfcContent, results, parentTag);
109
+ break;
110
+ case NodeTypes.COMPOUND_EXPRESSION:
111
+ if (node.children) {
112
+ for (const child of node.children) if (typeof child !== "string") walkTemplate(child, templateLineOffset, sfcContent, results, parentTag);
113
+ }
114
+ break;
115
+ case NodeTypes.TEXT_CALL:
116
+ if (node.content && typeof node.content !== "string") walkTemplate(node.content, templateLineOffset, sfcContent, results, parentTag);
117
+ if (node.children) for (const child of node.children) walkTemplate(child, templateLineOffset, sfcContent, results, parentTag);
118
+ break;
119
+ default:
120
+ if (node.children) for (const child of node.children) walkTemplate(child, templateLineOffset, sfcContent, results, parentTag);
121
+ break;
122
+ }
123
+ }
124
+ function processProp(prop, parentTag, templateLineOffset, sfcContent, results) {
125
+ if (prop.type === NodeTypes.ATTRIBUTE) {
126
+ if (!prop.value) return;
127
+ const attrName = prop.name;
128
+ const attrValue = prop.value.content;
129
+ if (!attrValue || attrValue.trim().length === 0) return;
130
+ if (CSS_ATTRIBUTES.has(attrName)) {
131
+ results.push({
132
+ value: attrValue,
133
+ line: prop.value.loc.start.line + templateLineOffset - 1,
134
+ column: prop.value.loc.start.column,
135
+ context: "css_class",
136
+ scope: "template",
137
+ parent: parentTag,
138
+ parentProperty: attrName,
139
+ surrounding: getSurroundingByLine(sfcContent, prop.value.loc.start.line + templateLineOffset - 1)
140
+ });
141
+ return;
142
+ }
143
+ results.push({
144
+ value: attrValue,
145
+ line: prop.value.loc.start.line + templateLineOffset - 1,
146
+ column: prop.value.loc.start.column,
147
+ context: "template_attribute",
148
+ scope: "template",
149
+ parent: parentTag,
150
+ parentProperty: attrName,
151
+ surrounding: getSurroundingByLine(sfcContent, prop.value.loc.start.line + templateLineOffset - 1)
152
+ });
153
+ } else if (prop.type === NodeTypes.DIRECTIVE) {
154
+ const directiveName = prop.name;
155
+ if (CODE_DIRECTIVES.has(directiveName)) return;
156
+ if (directiveName === "bind" && prop.arg) {
157
+ const argName = typeof prop.arg.content === "string" ? prop.arg.content : "";
158
+ if (CODE_DIRECTIVE_ARGS.has(argName)) return;
159
+ }
160
+ if (prop.exp) {
161
+ const expr = typeof prop.exp.content === "string" ? prop.exp.content : "";
162
+ const argName = prop.arg && typeof prop.arg.content === "string" ? prop.arg.content : "";
163
+ const stringMatch = expr.match(/^(['"`])(.+)\1$/);
164
+ if (stringMatch?.[2]) results.push({
165
+ value: stringMatch[2],
166
+ line: prop.exp.loc.start.line + templateLineOffset - 1,
167
+ column: prop.exp.loc.start.column,
168
+ context: "template_attribute",
169
+ scope: "template",
170
+ parent: parentTag,
171
+ parentProperty: argName || directiveName,
172
+ surrounding: getSurroundingByLine(sfcContent, prop.exp.loc.start.line + templateLineOffset - 1)
173
+ });
174
+ }
175
+ }
176
+ }
177
+ let _tsxParser = null;
178
+ async function loadTsxParser() {
179
+ if (_tsxParser) return _tsxParser;
180
+ try {
181
+ _tsxParser = (await import("./tsx-parser-Bf8Firrq.mjs")).parseTsx;
182
+ return _tsxParser;
183
+ } catch {
184
+ return null;
185
+ }
186
+ }
187
+ function parseScriptBlock(scriptContent, scriptStartLine, fileName, parseTsx) {
188
+ return parseTsx(scriptContent, fileName).map((r) => {
189
+ r.line = r.line + scriptStartLine - 1;
190
+ r.scope = "script";
191
+ return r;
192
+ });
193
+ }
194
+ async function parseVue(content, fileName) {
195
+ const compiler = await loadCompiler();
196
+ const results = [];
197
+ const { descriptor } = compiler.parse(content, { filename: fileName });
198
+ if (descriptor.template) {
199
+ const templateLineOffset = descriptor.template.loc.start.line;
200
+ const templateAst = descriptor.template.ast;
201
+ if (templateAst) walkTemplate(templateAst, templateLineOffset, content, results);
202
+ else try {
203
+ const compiled = compiler.compileTemplate({
204
+ source: descriptor.template.content,
205
+ filename: fileName,
206
+ id: "ast-scanner"
207
+ });
208
+ if (compiled.ast) walkTemplate(compiled.ast, templateLineOffset, content, results);
209
+ } catch {}
210
+ }
211
+ const scriptBlock = descriptor.scriptSetup ?? descriptor.script;
212
+ if (scriptBlock) {
213
+ const parseTsx = await loadTsxParser();
214
+ if (parseTsx) {
215
+ const scriptStartLine = scriptBlock.loc.start.line;
216
+ const scriptResults = parseScriptBlock(scriptBlock.content, scriptStartLine, fileName, parseTsx);
217
+ results.push(...scriptResults);
218
+ }
219
+ }
220
+ return results;
221
+ }
222
+ //#endregion
223
+ export { parseVue };
224
+
225
+ //# sourceMappingURL=vue-parser-CjS4YAoQ.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vue-parser-CjS4YAoQ.mjs","names":[],"sources":["../src/core/ast-scanner/vue-parser.ts"],"sourcesContent":["// ─── Vue SFC Parser for Scanner v2 ───\n// Parses .vue Single File Components using @vue/compiler-sfc.\n// Extracts ALL strings with structural context metadata.\n// Scanner does NOT classify — agent does. When in doubt, INCLUDE.\n\n// ─── Types ───\n\nimport type { ExtractedString } from './types.js'\nexport type { ExtractedString }\n\n// ─── Lazy-loaded @vue/compiler-sfc ───\n\ninterface VueCompilerSFC {\n parse: (source: string, options?: { filename?: string }) => {\n descriptor: {\n template: {\n content: string\n loc: { start: { line: number; column: number; offset: number } }\n ast?: VueTemplateNode\n } | null\n script: {\n content: string\n loc: { start: { line: number; column: number; offset: number } }\n lang?: string\n } | null\n scriptSetup: {\n content: string\n loc: { start: { line: number; column: number; offset: number } }\n lang?: string\n } | null\n }\n }\n compileTemplate: (options: {\n source: string\n filename: string\n id: string\n }) => {\n ast?: VueTemplateNode\n }\n}\n\n// Vue template AST node types\nconst NodeTypes = {\n ROOT: 0,\n ELEMENT: 1,\n TEXT: 2,\n COMMENT: 3,\n SIMPLE_EXPRESSION: 4,\n INTERPOLATION: 5,\n ATTRIBUTE: 6,\n DIRECTIVE: 7,\n COMPOUND_EXPRESSION: 8,\n IF: 9,\n IF_BRANCH: 10,\n FOR: 11,\n TEXT_CALL: 12,\n VNODE_CALL: 13,\n JS_CALL_EXPRESSION: 14,\n} as const\n\ninterface VueTemplateLoc {\n start: { line: number; column: number; offset: number }\n end: { line: number; column: number; offset: number }\n source: string\n}\n\ninterface VueTemplateNode {\n type: number\n loc: VueTemplateLoc\n children?: VueTemplateNode[]\n tag?: string // for ELEMENT\n props?: VueTemplateProp[]\n content?: VueTemplateNode | string // for INTERPOLATION, TEXT, SIMPLE_EXPRESSION\n branches?: VueTemplateNode[] // for IF\n}\n\ninterface VueTemplateProp {\n type: number // ATTRIBUTE (6) or DIRECTIVE (7)\n name: string\n loc: VueTemplateLoc\n value?: {\n type: number\n content: string\n loc: VueTemplateLoc\n }\n exp?: {\n type: number\n content: string\n loc: VueTemplateLoc\n }\n arg?: {\n type: number\n content: string\n loc: VueTemplateLoc\n }\n}\n\nlet _compiler: VueCompilerSFC | null = null\n\nasync function loadCompiler(): Promise<VueCompilerSFC> {\n if (_compiler) return _compiler\n try {\n // Dynamic import — @vue/compiler-sfc is optional\n const mod = await import('@vue/compiler-sfc')\n _compiler = mod as unknown as VueCompilerSFC\n return _compiler\n } catch {\n throw new Error(\n '@vue/compiler-sfc is required to parse .vue files. '\n + 'Install it with: pnpm add -D @vue/compiler-sfc',\n )\n }\n}\n\n// ─── Directives that contain code expressions (not user content) ───\n\nconst CODE_DIRECTIVES = new Set([\n 'if', 'else-if', 'else', 'show',\n 'for',\n 'on', // @click, v-on:click\n 'model',\n 'memo',\n 'once',\n 'pre',\n 'cloak',\n 'is',\n 'slot',\n 'key',\n])\n\n// Directive args that are code/binding expressions, not content\nconst CODE_DIRECTIVE_ARGS = new Set([\n 'class', 'style', // :class, :style → CSS bindings\n 'key',\n 'ref',\n 'is',\n])\n\n// Static attributes whose values are CSS, not content\nconst CSS_ATTRIBUTES = new Set([\n 'class', 'style',\n])\n\n// ─── Surrounding text helper ───\n\nconst SURROUNDING_MAX = 120\n\nfunction _getSurrounding(content: string, offset: number): string {\n const lines = content.split('\\n')\n let charCount = 0\n for (let i = 0; i < lines.length; i++) {\n const lineLen = (lines[i]?.length ?? 0) + 1 // +1 for newline\n if (charCount + lineLen > offset) {\n return (lines[i] ?? '').slice(0, SURROUNDING_MAX)\n }\n charCount += lineLen\n }\n return ''\n}\n\nfunction getSurroundingByLine(content: string, line: number): string {\n const lines = content.split('\\n')\n const idx = line - 1\n if (idx >= 0 && idx < lines.length) {\n return (lines[idx] ?? '').slice(0, SURROUNDING_MAX)\n }\n return ''\n}\n\n// ─── Template AST Walker ───\n\nfunction walkTemplate(\n node: VueTemplateNode,\n templateLineOffset: number,\n sfcContent: string,\n results: ExtractedString[],\n parentTag: string = '',\n): void {\n switch (node.type) {\n case NodeTypes.TEXT: {\n // Static text between tags\n const text = typeof node.content === 'string'\n ? node.content\n : (node.loc?.source ?? '')\n const trimmed = text.trim()\n if (trimmed.length > 0 && /\\S/.test(trimmed)) {\n results.push({\n value: trimmed,\n line: node.loc.start.line + templateLineOffset - 1,\n column: node.loc.start.column,\n context: 'template_text',\n scope: 'template',\n parent: parentTag,\n surrounding: getSurroundingByLine(sfcContent, node.loc.start.line + templateLineOffset - 1),\n })\n }\n break\n }\n\n case NodeTypes.INTERPOLATION: {\n // {{ expression }} — extract string literals from within\n if (node.content && typeof node.content !== 'string' && node.content.type === NodeTypes.SIMPLE_EXPRESSION) {\n const expr = typeof node.content.content === 'string'\n ? node.content.content\n : ''\n // Only extract if the expression itself is a string literal\n // e.g., {{ 'Hello' }} or {{ \"World\" }}\n const stringMatch = expr.match(/^(['\"`])(.+)\\1$/)\n if (stringMatch?.[2]) {\n results.push({\n value: stringMatch[2],\n line: node.loc.start.line + templateLineOffset - 1,\n column: node.loc.start.column,\n context: 'template_text',\n scope: 'template',\n parent: parentTag,\n surrounding: getSurroundingByLine(sfcContent, node.loc.start.line + templateLineOffset - 1),\n })\n }\n // If it's a variable reference like {{ greeting }}, skip — that's code\n }\n break\n }\n\n case NodeTypes.ELEMENT: {\n const tag = node.tag ?? ''\n\n // Process props/attributes\n if (node.props) {\n for (const prop of node.props) {\n processProp(prop, tag, templateLineOffset, sfcContent, results)\n }\n }\n\n // Recurse into children\n if (node.children) {\n for (const child of node.children) {\n walkTemplate(child, templateLineOffset, sfcContent, results, tag)\n }\n }\n break\n }\n\n case NodeTypes.IF: {\n // v-if creates branches — walk each branch's children\n if (node.branches) {\n for (const branch of node.branches) {\n if (branch.children) {\n for (const child of branch.children) {\n walkTemplate(child, templateLineOffset, sfcContent, results, parentTag)\n }\n }\n }\n }\n break\n }\n\n case NodeTypes.IF_BRANCH: {\n // Walk children of if branch\n if (node.children) {\n for (const child of node.children) {\n walkTemplate(child, templateLineOffset, sfcContent, results, parentTag)\n }\n }\n break\n }\n\n case NodeTypes.FOR: {\n // v-for node — walk children\n if (node.children) {\n for (const child of node.children) {\n walkTemplate(child, templateLineOffset, sfcContent, results, parentTag)\n }\n }\n break\n }\n\n case NodeTypes.ROOT: {\n // Root node — walk children\n if (node.children) {\n for (const child of node.children) {\n walkTemplate(child, templateLineOffset, sfcContent, results, parentTag)\n }\n }\n break\n }\n\n case NodeTypes.COMPOUND_EXPRESSION: {\n // Compound expression — walk children\n if (node.children) {\n for (const child of node.children) {\n if (typeof child !== 'string') {\n walkTemplate(child, templateLineOffset, sfcContent, results, parentTag)\n }\n }\n }\n break\n }\n\n case NodeTypes.TEXT_CALL: {\n // Text call node (wrapper for text in v-if, etc.) — walk content\n if (node.content && typeof node.content !== 'string') {\n walkTemplate(node.content, templateLineOffset, sfcContent, results, parentTag)\n }\n // Also walk children\n if (node.children) {\n for (const child of node.children) {\n walkTemplate(child, templateLineOffset, sfcContent, results, parentTag)\n }\n }\n break\n }\n\n default: {\n // For any unknown node type, try to walk children\n if (node.children) {\n for (const child of node.children) {\n walkTemplate(child, templateLineOffset, sfcContent, results, parentTag)\n }\n }\n break\n }\n }\n}\n\nfunction processProp(\n prop: VueTemplateProp,\n parentTag: string,\n templateLineOffset: number,\n sfcContent: string,\n results: ExtractedString[],\n): void {\n if (prop.type === NodeTypes.ATTRIBUTE) {\n // Static attribute: title=\"Hello\"\n if (!prop.value) return\n\n const attrName = prop.name\n const attrValue = prop.value.content\n\n if (!attrValue || attrValue.trim().length === 0) return\n\n // CSS attributes get css_class context\n if (CSS_ATTRIBUTES.has(attrName)) {\n results.push({\n value: attrValue,\n line: prop.value.loc.start.line + templateLineOffset - 1,\n column: prop.value.loc.start.column,\n context: 'css_class',\n scope: 'template',\n parent: parentTag,\n parentProperty: attrName,\n surrounding: getSurroundingByLine(sfcContent, prop.value.loc.start.line + templateLineOffset - 1),\n })\n return\n }\n\n results.push({\n value: attrValue,\n line: prop.value.loc.start.line + templateLineOffset - 1,\n column: prop.value.loc.start.column,\n context: 'template_attribute',\n scope: 'template',\n parent: parentTag,\n parentProperty: attrName,\n surrounding: getSurroundingByLine(sfcContent, prop.value.loc.start.line + templateLineOffset - 1),\n })\n } else if (prop.type === NodeTypes.DIRECTIVE) {\n // Dynamic directive: :title=\"expr\", v-bind:title=\"expr\", @click=\"handler\"\n const directiveName = prop.name // 'bind', 'on', 'if', 'for', etc.\n\n // Skip code directives entirely (v-if, v-for, @click, etc.)\n if (CODE_DIRECTIVES.has(directiveName)) return\n\n // For v-bind (:attr=\"expr\"), check if the arg is a code binding\n if (directiveName === 'bind' && prop.arg) {\n const argName = typeof prop.arg.content === 'string' ? prop.arg.content : ''\n if (CODE_DIRECTIVE_ARGS.has(argName)) return\n }\n\n // Extract string literals from directive expressions\n if (prop.exp) {\n const expr = typeof prop.exp.content === 'string' ? prop.exp.content : ''\n const argName = prop.arg && typeof prop.arg.content === 'string' ? prop.arg.content : ''\n\n // Check if expression is a simple string literal: 'text' or \"text\"\n const stringMatch = expr.match(/^(['\"`])(.+)\\1$/)\n if (stringMatch?.[2]) {\n results.push({\n value: stringMatch[2],\n line: prop.exp.loc.start.line + templateLineOffset - 1,\n column: prop.exp.loc.start.column,\n context: 'template_attribute',\n scope: 'template',\n parent: parentTag,\n parentProperty: argName || directiveName,\n surrounding: getSurroundingByLine(sfcContent, prop.exp.loc.start.line + templateLineOffset - 1),\n })\n }\n // If it's a variable or complex expression, skip — scanner doesn't interpret code\n }\n }\n}\n\n// ─── Script Block Parsing ───\n\n// tsx-parser is being built in parallel; define the interface we expect\ntype TsxParserFn = (content: string, fileName: string) => ExtractedString[]\n\nlet _tsxParser: TsxParserFn | null = null\n\nasync function loadTsxParser(): Promise<TsxParserFn | null> {\n if (_tsxParser) return _tsxParser\n try {\n const mod = await import('./tsx-parser.js')\n _tsxParser = mod.parseTsx\n return _tsxParser\n } catch {\n // tsx-parser not yet available — fall back to no script parsing\n return null\n }\n}\n\nfunction parseScriptBlock(\n scriptContent: string,\n scriptStartLine: number,\n fileName: string,\n parseTsx: TsxParserFn,\n): ExtractedString[] {\n // Parse script content using tsx-parser\n const scriptResults = parseTsx(scriptContent, fileName)\n\n // Adjust line numbers by script block offset\n return scriptResults.map(r => {\n r.line = r.line + scriptStartLine - 1\n r.scope = 'script'\n return r\n })\n}\n\n// ─── Main Export ───\n\nexport async function parseVue(content: string, fileName: string): Promise<ExtractedString[]> {\n const compiler = await loadCompiler()\n const results: ExtractedString[] = []\n\n const { descriptor } = compiler.parse(content, { filename: fileName })\n\n // ─── Template Block ───\n if (descriptor.template) {\n const templateLineOffset = descriptor.template.loc.start.line\n const templateAst = descriptor.template.ast\n\n if (templateAst) {\n walkTemplate(templateAst, templateLineOffset, content, results)\n } else {\n // Fallback: compile template to get AST\n try {\n const compiled = compiler.compileTemplate({\n source: descriptor.template.content,\n filename: fileName,\n id: 'ast-scanner',\n })\n if (compiled.ast) {\n walkTemplate(compiled.ast, templateLineOffset, content, results)\n }\n } catch {\n // If template compilation fails, we skip template extraction\n // This is acceptable — malformed templates shouldn't block scanning\n }\n }\n }\n\n // ─── Script Block ───\n const scriptBlock = descriptor.scriptSetup ?? descriptor.script\n if (scriptBlock) {\n const parseTsx = await loadTsxParser()\n if (parseTsx) {\n const scriptStartLine = scriptBlock.loc.start.line\n const scriptResults = parseScriptBlock(\n scriptBlock.content,\n scriptStartLine,\n fileName,\n parseTsx,\n )\n results.push(...scriptResults)\n }\n }\n\n // Style blocks are intentionally skipped — no content strings in CSS\n\n return results\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,61 +1,125 @@
1
1
  {
2
2
  "name": "@contentrain/mcp",
3
- "mcpName": "io.github.Contentrain/mcp",
4
- "version": "0.3.1",
5
- "description": "MCP server for Contentrain CMS content management - AI agent and human friendly",
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "description": "MCP tools for AI-powered content management with Contentrain",
6
6
  "type": "module",
7
- "bin": {
8
- "contentrain-mcp": "./dist/index.mjs"
9
- },
10
- "main": "./dist/index.mjs",
11
- "types": "./dist/index.d.mts",
12
- "files": [
13
- "dist",
14
- "README.md",
15
- "LICENSE"
16
- ],
17
7
  "repository": {
18
8
  "type": "git",
19
- "url": "https://github.com/Contentrain/mcp.git"
9
+ "url": "git+https://github.com/contentrain/contentrain-ai.git",
10
+ "directory": "packages/mcp"
20
11
  },
21
- "homepage": "https://github.com/Contentrain/mcp#readme",
22
- "scripts": {
23
- "build": "tsdown",
24
- "dev": "tsdown --watch",
25
- "start": "node dist/index.mjs",
26
- "lint": "oxlint src/",
27
- "lint:fix": "oxlint --fix src/",
28
- "test": "vitest",
29
- "test:run": "vitest run",
30
- "prepublishOnly": "pnpm run build"
12
+ "homepage": "https://github.com/contentrain/contentrain-ai/tree/main/packages/mcp",
13
+ "bugs": {
14
+ "url": "https://github.com/contentrain/contentrain-ai/issues"
31
15
  },
32
16
  "keywords": [
33
17
  "contentrain",
34
18
  "mcp",
35
- "headless-cms",
36
- "ai-agent",
37
- "content-management",
38
- "git-based-cms"
19
+ "ai",
20
+ "content-governance",
21
+ "git",
22
+ "i18n",
23
+ "local-first"
24
+ ],
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "bin": {
29
+ "contentrain-mcp": "./dist/index.mjs"
30
+ },
31
+ "exports": {
32
+ ".": {
33
+ "types": "./dist/index.d.ts",
34
+ "import": "./dist/index.mjs"
35
+ },
36
+ "./server": {
37
+ "types": "./dist/server.d.ts",
38
+ "import": "./dist/server.mjs"
39
+ },
40
+ "./core/config": {
41
+ "types": "./dist/core/config.d.ts",
42
+ "import": "./dist/core/config.mjs"
43
+ },
44
+ "./core/context": {
45
+ "types": "./dist/core/context.d.ts",
46
+ "import": "./dist/core/context.mjs"
47
+ },
48
+ "./core/model-manager": {
49
+ "types": "./dist/core/model-manager.d.ts",
50
+ "import": "./dist/core/model-manager.mjs"
51
+ },
52
+ "./core/content-manager": {
53
+ "types": "./dist/core/content-manager.d.ts",
54
+ "import": "./dist/core/content-manager.mjs"
55
+ },
56
+ "./core/validator": {
57
+ "types": "./dist/core/validator.d.ts",
58
+ "import": "./dist/core/validator.mjs"
59
+ },
60
+ "./core/scanner": {
61
+ "types": "./dist/core/scanner.d.ts",
62
+ "import": "./dist/core/scanner.mjs"
63
+ },
64
+ "./core/graph-builder": {
65
+ "types": "./dist/core/graph-builder.d.ts",
66
+ "import": "./dist/core/graph-builder.mjs"
67
+ },
68
+ "./core/apply-manager": {
69
+ "types": "./dist/core/apply-manager.d.ts",
70
+ "import": "./dist/core/apply-manager.mjs"
71
+ },
72
+ "./util/detect": {
73
+ "types": "./dist/util/detect.d.ts",
74
+ "import": "./dist/util/detect.mjs"
75
+ },
76
+ "./util/fs": {
77
+ "types": "./dist/util/fs.d.ts",
78
+ "import": "./dist/util/fs.mjs"
79
+ },
80
+ "./git/transaction": {
81
+ "types": "./dist/git/transaction.d.ts",
82
+ "import": "./dist/git/transaction.mjs"
83
+ },
84
+ "./git/branch-lifecycle": {
85
+ "types": "./dist/git/branch-lifecycle.d.ts",
86
+ "import": "./dist/git/branch-lifecycle.mjs"
87
+ },
88
+ "./templates": {
89
+ "types": "./dist/templates/index.d.ts",
90
+ "import": "./dist/templates/index.mjs"
91
+ }
92
+ },
93
+ "main": "./dist/index.mjs",
94
+ "types": "./dist/index.d.ts",
95
+ "files": [
96
+ "dist"
39
97
  ],
40
- "license": "MIT",
41
98
  "dependencies": {
42
- "@modelcontextprotocol/sdk": "^1.12.1",
99
+ "@modelcontextprotocol/sdk": "^1.12.0",
43
100
  "simple-git": "^3.27.0",
44
- "zod": "^3.24.0"
101
+ "typescript": "^5.7.0",
102
+ "zod": "^3.24.0",
103
+ "@contentrain/types": "0.1.0"
104
+ },
105
+ "optionalDependencies": {
106
+ "@astrojs/compiler": "^2.10.0",
107
+ "@vue/compiler-sfc": "^3.5.30",
108
+ "svelte": "^4.2.0"
45
109
  },
46
110
  "devDependencies": {
47
- "@types/node": "^22.0.0",
48
- "oxlint": "^1.51.0",
49
- "tsdown": "^0.21.1",
50
- "typescript": "^5.7.0",
111
+ "@astrojs/compiler": "^2.10.0",
112
+ "@types/node": "^25.4.0",
113
+ "@vue/compiler-sfc": "^3.5.30",
114
+ "svelte": "^4.2.0",
115
+ "tsdown": "^0.21.0",
51
116
  "vitest": "^3.0.0"
52
117
  },
53
- "engines": {
54
- "node": ">=18"
55
- },
56
- "pnpm": {
57
- "onlyBuiltDependencies": [
58
- "esbuild"
59
- ]
118
+ "scripts": {
119
+ "build": "tsdown src/index.ts src/server.ts src/core/config.ts src/core/context.ts src/core/model-manager.ts src/core/content-manager.ts src/core/meta-manager.ts src/core/validator.ts src/core/scanner.ts src/core/scan-config.ts src/core/graph-builder.ts src/core/apply-manager.ts src/util/detect.ts src/util/fs.ts src/util/id.ts src/util/serializer.ts src/git/transaction.ts src/git/branch-lifecycle.ts src/templates/index.ts --format esm --dts --external typescript",
120
+ "dev": "tsdown src/index.ts src/server.ts src/core/config.ts src/core/context.ts src/core/model-manager.ts src/core/content-manager.ts src/core/meta-manager.ts src/core/validator.ts src/core/scanner.ts src/core/scan-config.ts src/core/graph-builder.ts src/core/apply-manager.ts src/util/detect.ts src/util/fs.ts src/util/id.ts src/util/serializer.ts src/git/transaction.ts src/git/branch-lifecycle.ts src/templates/index.ts --format esm --dts --external typescript --watch",
121
+ "test": "vitest run",
122
+ "typecheck": "tsc --noEmit",
123
+ "clean": "rm -rf dist"
60
124
  }
61
- }
125
+ }
@@ -1,75 +0,0 @@
1
- import { access, mkdir, readdir, rm, unlink } from "node:fs/promises";
2
- //#region \0rolldown/runtime.js
3
- var __defProp = Object.defineProperty;
4
- var __exportAll = (all, no_symbols) => {
5
- let target = {};
6
- for (var name in all) __defProp(target, name, {
7
- get: all[name],
8
- enumerable: true
9
- });
10
- if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
11
- return target;
12
- };
13
- //#endregion
14
- //#region src/util/fs.ts
15
- var fs_exports = /* @__PURE__ */ __exportAll({
16
- ensureDir: () => ensureDir,
17
- fileExists: () => fileExists,
18
- listJsonFiles: () => listJsonFiles,
19
- removeDir: () => removeDir,
20
- removeFile: () => removeFile
21
- });
22
- /**
23
- * Ensure a directory exists, creating it recursively if needed.
24
- */
25
- async function ensureDir(dirPath) {
26
- await mkdir(dirPath, { recursive: true });
27
- }
28
- /**
29
- * Check if a file exists.
30
- */
31
- async function fileExists(filePath) {
32
- try {
33
- await access(filePath);
34
- return true;
35
- } catch {
36
- return false;
37
- }
38
- }
39
- /**
40
- * Remove a file if it exists.
41
- */
42
- async function removeFile(filePath) {
43
- try {
44
- await unlink(filePath);
45
- } catch (err) {
46
- if (err instanceof Error && "code" in err && err.code !== "ENOENT") throw err;
47
- }
48
- }
49
- /**
50
- * Remove a directory recursively.
51
- */
52
- async function removeDir(dirPath) {
53
- try {
54
- await rm(dirPath, {
55
- recursive: true,
56
- force: true
57
- });
58
- } catch (err) {
59
- if (err instanceof Error && "code" in err && err.code !== "ENOENT") throw err;
60
- }
61
- }
62
- /**
63
- * List JSON files in a directory (returns filenames without extension).
64
- */
65
- async function listJsonFiles(dirPath) {
66
- try {
67
- return (await readdir(dirPath)).filter((f) => f.endsWith(".json")).map((f) => f.replace(".json", ""));
68
- } catch {
69
- return [];
70
- }
71
- }
72
- //#endregion
73
- export { removeDir as a, listJsonFiles as i, fileExists as n, removeFile as o, fs_exports as r, ensureDir as t };
74
-
75
- //# sourceMappingURL=fs-D83-Ii_y.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"fs-D83-Ii_y.mjs","names":[],"sources":["../src/util/fs.ts"],"sourcesContent":["import { mkdir, access, unlink, readdir, rm } from 'node:fs/promises'\n\n/**\n * Ensure a directory exists, creating it recursively if needed.\n */\nexport async function ensureDir(dirPath: string): Promise<void> {\n await mkdir(dirPath, { recursive: true })\n}\n\n/**\n * Check if a file exists.\n */\nexport async function fileExists(filePath: string): Promise<boolean> {\n try {\n await access(filePath)\n return true\n }\n catch {\n return false\n }\n}\n\n/**\n * Remove a file if it exists.\n */\nexport async function removeFile(filePath: string): Promise<void> {\n try {\n await unlink(filePath)\n }\n catch (err: unknown) {\n if (err instanceof Error && 'code' in err && (err as NodeJS.ErrnoException).code !== 'ENOENT') {\n throw err\n }\n }\n}\n\n/**\n * Remove a directory recursively.\n */\nexport async function removeDir(dirPath: string): Promise<void> {\n try {\n await rm(dirPath, { recursive: true, force: true })\n }\n catch (err: unknown) {\n if (err instanceof Error && 'code' in err && (err as NodeJS.ErrnoException).code !== 'ENOENT') {\n throw err\n }\n }\n}\n\n/**\n * List JSON files in a directory (returns filenames without extension).\n */\nexport async function listJsonFiles(dirPath: string): Promise<string[]> {\n try {\n const files = await readdir(dirPath)\n return files\n .filter(f => f.endsWith('.json'))\n .map(f => f.replace('.json', ''))\n }\n catch {\n return []\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAKA,eAAsB,UAAU,SAAgC;AAC9D,OAAM,MAAM,SAAS,EAAE,WAAW,MAAM,CAAC;;;;;AAM3C,eAAsB,WAAW,UAAoC;AACnE,KAAI;AACF,QAAM,OAAO,SAAS;AACtB,SAAO;SAEH;AACJ,SAAO;;;;;;AAOX,eAAsB,WAAW,UAAiC;AAChE,KAAI;AACF,QAAM,OAAO,SAAS;UAEjB,KAAc;AACnB,MAAI,eAAe,SAAS,UAAU,OAAQ,IAA8B,SAAS,SACnF,OAAM;;;;;;AAQZ,eAAsB,UAAU,SAAgC;AAC9D,KAAI;AACF,QAAM,GAAG,SAAS;GAAE,WAAW;GAAM,OAAO;GAAM,CAAC;UAE9C,KAAc;AACnB,MAAI,eAAe,SAAS,UAAU,OAAQ,IAA8B,SAAS,SACnF,OAAM;;;;;;AAQZ,eAAsB,cAAc,SAAoC;AACtE,KAAI;AAEF,UADc,MAAM,QAAQ,QAAQ,EAEjC,QAAO,MAAK,EAAE,SAAS,QAAQ,CAAC,CAChC,KAAI,MAAK,EAAE,QAAQ,SAAS,GAAG,CAAC;SAE/B;AACJ,SAAO,EAAE"}