{"version":3,"file":"convert-vidaimock.cjs","names":[],"sources":["../src/convert-vidaimock.ts"],"sourcesContent":["/**\n * VidaiMock -> aimock Fixture Converter\n *\n * Core conversion logic. Used by both the CLI (`aimock convert vidaimock`)\n * and the standalone script (`scripts/convert-vidaimock.ts`).\n */\n\nimport { readFileSync, readdirSync, statSync } from \"node:fs\";\nimport { resolve, basename, extname } from \"node:path\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface AimockFixture {\n  match: { userMessage: string };\n  response: { content: string };\n}\n\nexport interface AimockFixtureFile {\n  fixtures: AimockFixture[];\n}\n\n// ---------------------------------------------------------------------------\n// Tera template stripping\n// ---------------------------------------------------------------------------\n\n/**\n * Strip Tera template syntax and extract a usable response content string.\n *\n * Strategy:\n * 1. If the template looks like JSON, try to pull out the nested\n *    `choices[].message.content` value (the most common VidaiMock pattern).\n * 2. Otherwise fall back to stripping all Tera delimiters and returning the\n *    remaining text with placeholder variable names.\n */\nexport function stripTeraTemplate(raw: string): string {\n  const trimmed = raw.trim();\n\n  // --- Attempt JSON extraction first -----------------------------------\n  const contentValue = extractJsonContent(trimmed);\n  if (contentValue !== null) return contentValue;\n\n  // --- Fallback: strip Tera syntax -------------------------------------\n  let text = trimmed;\n\n  // Remove comment blocks {# ... #}\n  text = text.replace(/\\{#[\\s\\S]*?#\\}/g, \"\");\n\n  // Remove block tags {% ... %}\n  text = text.replace(/\\{%[\\s\\S]*?%\\}/g, \"\");\n\n  // Replace expression tags {{ expr }} with the expression name\n  text = text.replace(/\\{\\{\\s*([\\w.]+)\\s*\\}\\}/g, \"[$1]\");\n\n  // Collapse excessive whitespace but preserve intentional newlines\n  text = text\n    .split(\"\\n\")\n    .map((l) => l.trim())\n    .filter((l) => l.length > 0)\n    .join(\"\\n\");\n\n  return text;\n}\n\n/**\n * Try to parse the template as JSON (after substituting Tera expressions with\n * dummy strings) and pull out `choices[0].message.content`.\n */\nfunction extractJsonContent(raw: string): string | null {\n  try {\n    // Step 1: remove Tera comments and blocks\n    let substituted = raw.replace(/\\{#[\\s\\S]*?#\\}/g, \"\").replace(/\\{%[\\s\\S]*?%\\}/g, \"\");\n\n    // Step 2: Replace Tera expressions inside existing JSON strings.\n    // Pattern: the expression is already within quotes, e.g. \"foo-{{ bar }}-baz\"\n    // We replace the {{ ... }} with the placeholder without adding extra quotes.\n    substituted = substituted.replace(\n      /\"([^\"]*?)\\{\\{\\s*([\\w.]+)\\s*\\}\\}([^\"]*?)\"/g,\n      (_, before, varName, after) => `\"${before}[${varName}]${after}\"`,\n    );\n\n    // Step 3: Replace standalone Tera expressions (not inside quotes),\n    // e.g. a bare `{{ content }}` used as a JSON value — wrap with quotes.\n    substituted = substituted.replace(/\\{\\{\\s*([\\w.]+)\\s*\\}\\}/g, '\"[$1]\"');\n\n    const parsed = JSON.parse(substituted);\n\n    if (\n      parsed &&\n      Array.isArray(parsed.choices) &&\n      parsed.choices.length > 0 &&\n      parsed.choices[0]?.message?.content !== undefined\n    ) {\n      return String(parsed.choices[0].message.content);\n    }\n  } catch {\n    // Not valid JSON even after substitution — fall through\n  }\n  return null;\n}\n\n// ---------------------------------------------------------------------------\n// Filename -> match derivation\n// ---------------------------------------------------------------------------\n\n/**\n * Derive a `userMessage` match string from the template filename.\n *\n * Examples:\n *   \"greeting.tera\"       -> \"greeting\"\n *   \"tell_me_a_joke.json\" -> \"tell me a joke\"\n *   \"003-weather.txt\"     -> \"weather\"\n */\nexport function deriveMatchFromFilename(filename: string): string {\n  let name = basename(filename, extname(filename));\n\n  // Strip leading numeric prefixes like \"003-\"\n  name = name.replace(/^\\d+[-_]/, \"\");\n\n  // Replace underscores / hyphens with spaces\n  name = name.replace(/[-_]+/g, \" \");\n\n  return name.trim();\n}\n\n// ---------------------------------------------------------------------------\n// File / directory conversion\n// ---------------------------------------------------------------------------\n\nconst TEMPLATE_EXTENSIONS = new Set([\n  \".tera\",\n  \".json\",\n  \".txt\",\n  \".html\",\n  \".jinja\",\n  \".jinja2\",\n  \".j2\",\n]);\n\nexport function convertFile(filePath: string): AimockFixture | null {\n  let raw: string;\n  try {\n    raw = readFileSync(filePath, \"utf-8\");\n  } catch {\n    // Unreadable / binary file — skip gracefully\n    return null;\n  }\n\n  const content = stripTeraTemplate(raw);\n  if (!content) return null;\n\n  const match = deriveMatchFromFilename(filePath);\n  return { match: { userMessage: match }, response: { content } };\n}\n\nexport function convertDirectory(dirPath: string): AimockFixture[] {\n  const fixtures: AimockFixture[] = [];\n\n  let entries: string[];\n  try {\n    entries = readdirSync(dirPath);\n  } catch (err) {\n    if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return fixtures;\n    throw err; // permission errors, etc. should propagate\n  }\n\n  for (const entry of entries.sort()) {\n    const fullPath = resolve(dirPath, entry);\n    try {\n      if (!statSync(fullPath).isFile()) continue;\n    } catch {\n      continue;\n    }\n\n    const ext = extname(entry).toLowerCase();\n    if (!TEMPLATE_EXTENSIONS.has(ext)) continue;\n\n    const fixture = convertFile(fullPath);\n    if (fixture) fixtures.push(fixture);\n  }\n\n  return fixtures;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAoCA,SAAgB,kBAAkB,KAAqB;CACrD,MAAM,UAAU,IAAI,MAAM;CAG1B,MAAM,eAAe,mBAAmB,QAAQ;AAChD,KAAI,iBAAiB,KAAM,QAAO;CAGlC,IAAI,OAAO;AAGX,QAAO,KAAK,QAAQ,mBAAmB,GAAG;AAG1C,QAAO,KAAK,QAAQ,mBAAmB,GAAG;AAG1C,QAAO,KAAK,QAAQ,2BAA2B,OAAO;AAGtD,QAAO,KACJ,MAAM,KAAK,CACX,KAAK,MAAM,EAAE,MAAM,CAAC,CACpB,QAAQ,MAAM,EAAE,SAAS,EAAE,CAC3B,KAAK,KAAK;AAEb,QAAO;;;;;;AAOT,SAAS,mBAAmB,KAA4B;AACtD,KAAI;EAEF,IAAI,cAAc,IAAI,QAAQ,mBAAmB,GAAG,CAAC,QAAQ,mBAAmB,GAAG;AAKnF,gBAAc,YAAY,QACxB,8CACC,GAAG,QAAQ,SAAS,UAAU,IAAI,OAAO,GAAG,QAAQ,GAAG,MAAM,GAC/D;AAID,gBAAc,YAAY,QAAQ,2BAA2B,WAAS;EAEtE,MAAM,SAAS,KAAK,MAAM,YAAY;AAEtC,MACE,UACA,MAAM,QAAQ,OAAO,QAAQ,IAC7B,OAAO,QAAQ,SAAS,KACxB,OAAO,QAAQ,IAAI,SAAS,YAAY,OAExC,QAAO,OAAO,OAAO,QAAQ,GAAG,QAAQ,QAAQ;SAE5C;AAGR,QAAO;;;;;;;;;;AAeT,SAAgB,wBAAwB,UAA0B;CAChE,IAAI,+BAAgB,iCAAkB,SAAS,CAAC;AAGhD,QAAO,KAAK,QAAQ,YAAY,GAAG;AAGnC,QAAO,KAAK,QAAQ,UAAU,IAAI;AAElC,QAAO,KAAK,MAAM;;AAOpB,MAAM,sBAAsB,IAAI,IAAI;CAClC;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,SAAgB,YAAY,UAAwC;CAClE,IAAI;AACJ,KAAI;AACF,kCAAmB,UAAU,QAAQ;SAC/B;AAEN,SAAO;;CAGT,MAAM,UAAU,kBAAkB,IAAI;AACtC,KAAI,CAAC,QAAS,QAAO;AAGrB,QAAO;EAAE,OAAO,EAAE,aADJ,wBAAwB,SAAS,EACT;EAAE,UAAU,EAAE,SAAS;EAAE;;AAGjE,SAAgB,iBAAiB,SAAkC;CACjE,MAAM,WAA4B,EAAE;CAEpC,IAAI;AACJ,KAAI;AACF,qCAAsB,QAAQ;UACvB,KAAK;AACZ,MAAK,IAA8B,SAAS,SAAU,QAAO;AAC7D,QAAM;;AAGR,MAAK,MAAM,SAAS,QAAQ,MAAM,EAAE;EAClC,MAAM,kCAAmB,SAAS,MAAM;AACxC,MAAI;AACF,OAAI,uBAAU,SAAS,CAAC,QAAQ,CAAE;UAC5B;AACN;;EAGF,MAAM,6BAAc,MAAM,CAAC,aAAa;AACxC,MAAI,CAAC,oBAAoB,IAAI,IAAI,CAAE;EAEnC,MAAM,UAAU,YAAY,SAAS;AACrC,MAAI,QAAS,UAAS,KAAK,QAAQ;;AAGrC,QAAO"}