{"version":3,"file":"fileSystem.cjs","names":["access","join","readFile","writeFile","mkdir","ALL_LOCALES","fg","EXCLUDED_PATHS","dirname","resolve","relative","sep"],"sources":["../../../../src/init/utils/fileSystem.ts"],"sourcesContent":["import { access, mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { dirname, join, relative, resolve, sep } from 'node:path';\nimport { EXCLUDED_PATHS } from '@intlayer/config/defaultValues';\nimport { ALL_LOCALES } from '@intlayer/types/allLocales';\nimport fg from 'fast-glob';\n\n/**\n * Helper to check if a file exists\n */\nexport const exists = async (rootDir: string, filePath: string) => {\n  try {\n    await access(join(rootDir, filePath));\n    return true;\n  } catch {\n    return false;\n  }\n};\n\n/**\n * Helper to read a file\n */\nexport const readFileFromRoot = async (rootDir: string, filePath: string) =>\n  await readFile(join(rootDir, filePath), 'utf8');\n\n/**\n * Helper to write a file\n */\nexport const writeFileToRoot = async (\n  rootDir: string,\n  filePath: string,\n  content: string\n) => await writeFile(join(rootDir, filePath), content, 'utf8');\n\n/**\n * Helper to ensure a directory exists\n */\nexport const ensureDirectory = async (rootDir: string, dirPath: string) => {\n  try {\n    await mkdir(join(rootDir, dirPath), { recursive: true });\n  } catch {\n    // Directory already exists or could not be created\n  }\n};\n\n/**\n * Pattern type for locale JSON file organisation.\n * - 'nested': files are at `{base}/{locale}/{key}.json`\n * - 'flat':   files are at `{base}/{locale}.json`\n */\nexport type JsonLocalePatternType = 'nested' | 'flat';\n\n/**\n * Detected locale JSON file pattern and the corresponding source template.\n * `template` uses `${locale}` and `${key}` as literal placeholders (not JS\n * expressions) so it can be embedded directly in a template-literal string.\n */\nexport type JsonLocalePattern = {\n  type: JsonLocalePatternType;\n  /**\n   * Source path template for syncJSON `source` option.\n   * Example nested: `./locales/${locale}/${key}.json`\n   * Example flat:   `./locales/${locale}.json`\n   */\n  template: string;\n  /**\n   * Detected locales in the directory.\n   */\n  locales: string[];\n};\n\n/**\n * Set of all known locale string values from the intlayer Locales registry\n * (e.g. `'en'`, `'fr'`, `'zh-TW'`).  Keyed by the exact locale string so\n * `.has()` lookups are O(1) and common short directory names (`src`, `lib`,\n * `app`, …) are not mistaken for locales.\n */\nconst ALL_LOCALE_VALUES = new Set<string>(Object.values(ALL_LOCALES));\n\n/**\n * Returns true when `segment` matches a known BCP-47 locale identifier, e.g.\n * `en`, `fr`, `zh-TW`, `pt-BR`, `en-US`.\n */\nconst isLocaleSegment = (segment: string): boolean =>\n  ALL_LOCALE_VALUES.has(segment);\n\n/** JSON filenames that are never locale translation files. */\nconst KNOWN_CONFIG_FILENAMES = new Set([\n  'package.json',\n  'tsconfig.json',\n  'jsconfig.json',\n  'biome.json',\n  'turbo.json',\n  'lerna.json',\n  'vercel.json',\n  'netlify.json',\n  'babel.config.json',\n  'jest.config.json',\n  'vitest.config.json',\n  '.eslintrc.json',\n  '.prettierrc.json',\n]);\n\n/**\n * Scans the project for JSON files and determines whether locale files are\n * organised as `{base}/{locale}/{key}.json` (nested) or `{base}/{locale}.json`\n * (flat).  Returns the most likely source template, or `null` when no locale\n * JSON files are found.\n *\n * The returned `template` contains `${locale}` and `${key}` as **literal**\n * placeholder strings so it can be embedded inside a JS template literal.\n */\nexport const detectJsonLocalePattern = async (\n  rootDir: string\n): Promise<JsonLocalePattern | null> => {\n  const files = await fg('**/*.json', {\n    cwd: rootDir,\n    ignore: EXCLUDED_PATHS,\n    absolute: false,\n    onlyFiles: true,\n  });\n\n  const nestedBasePaths: string[] = [];\n  const flatBasePaths: string[] = [];\n  const nestedLocales = new Set<string>();\n  const flatLocales = new Set<string>();\n\n  for (const file of files) {\n    const parts = file.split('/');\n    const filename = parts[parts.length - 1] ?? '';\n\n    if (KNOWN_CONFIG_FILENAMES.has(filename)) continue;\n\n    // Nested: …/{locale}/{key}.json — parent directory is a locale code\n    if (parts.length >= 3) {\n      const localeDir = parts[parts.length - 2] ?? '';\n      if (isLocaleSegment(localeDir)) {\n        nestedBasePaths.push(parts.slice(0, -2).join('/') || '.');\n        nestedLocales.add(localeDir);\n      }\n    }\n\n    // Flat: …/{locale}.json — filename (without extension) is a locale code\n    if (parts.length >= 2) {\n      const baseName = filename.slice(0, -5); // strip \".json\"\n      if (isLocaleSegment(baseName)) {\n        flatBasePaths.push(parts.slice(0, -1).join('/') || '.');\n        flatLocales.add(baseName);\n      }\n    }\n  }\n\n  if (nestedBasePaths.length === 0 && flatBasePaths.length === 0) {\n    return null;\n  }\n\n  /**\n   * Returns the path prefix that appears most frequently among the matches,\n   * formatted as a relative path suitable for a source template.\n   */\n  const mostFrequentPrefix = (paths: string[]): string => {\n    const counts = paths.reduce<Record<string, number>>((accumulator, path) => {\n      accumulator[path] = (accumulator[path] ?? 0) + 1;\n      return accumulator;\n    }, {});\n    const topEntry = Object.entries(counts).sort(([, a], [, b]) => b - a)[0];\n    const basePath = topEntry?.[0] ?? '.';\n    return basePath === '.' ? '.' : `./${basePath}`;\n  };\n\n  if (nestedBasePaths.length >= flatBasePaths.length) {\n    const prefix = mostFrequentPrefix(nestedBasePaths);\n    return {\n      type: 'nested',\n      // Literal ${locale} and ${key} — not evaluated here, used in template literals\n      template: `${prefix}/\\${locale}/\\${key}.json`,\n      locales: Array.from(nestedLocales),\n    };\n  }\n\n  const prefix = mostFrequentPrefix(flatBasePaths);\n  return {\n    type: 'flat',\n    template: `${prefix}/\\${locale}.json`,\n    locales: Array.from(flatLocales),\n  };\n};\n\n/**\n * Detected lingui catalog pattern. lingui stores one catalog file per locale\n * (default name `messages`), as `.po` (its default) or `.json`.\n */\nexport type LinguiCatalogPattern = {\n  /** Which sync plugin should ingest the catalogs. */\n  format: 'po' | 'json';\n  /**\n   * Source path template, with `${locale}` and `${key}` as literal\n   * placeholders. The catalog filename (`messages`) is captured by `${key}` so\n   * the produced intlayer dictionary key is `messages` — matching the fixed\n   * `messages` namespace the lingui compat runtime reads from.\n   * Example: `./src/locales/${locale}/${key}.po`.\n   */\n  template: string;\n  /** Detected locales. */\n  locales: string[];\n};\n\n/**\n * Scans the project for lingui catalog files (`{base}/{locale}/messages.po` or\n * `…/messages.json`, lingui's default layout) and derives the `syncPO` /\n * `syncJSON` `source` template.\n *\n * `.po` is preferred when present (lingui's default format). Returns `null`\n * when no lingui catalog is found, in which case the caller falls back to the\n * generic JSON detection.\n *\n * @param rootDir - Project root directory.\n */\nexport const detectLinguiCatalogPattern = async (\n  rootDir: string\n): Promise<LinguiCatalogPattern | null> => {\n  // lingui's default catalog name is `messages`; match that basename for both\n  // formats so the captured `${key}` is `messages`.\n  const files = await fg(['**/messages.po', '**/messages.json'], {\n    cwd: rootDir,\n    ignore: EXCLUDED_PATHS,\n    absolute: false,\n    onlyFiles: true,\n  });\n\n  const basePathsByFormat: Record<'po' | 'json', string[]> = {\n    po: [],\n    json: [],\n  };\n  const localesByFormat: Record<'po' | 'json', Set<string>> = {\n    po: new Set(),\n    json: new Set(),\n  };\n\n  for (const file of files) {\n    const parts = file.split('/');\n    // Nested layout: …/{locale}/messages.{po,json} — parent dir is the locale.\n    if (parts.length < 2) continue;\n\n    const localeDir = parts[parts.length - 2] ?? '';\n    if (!isLocaleSegment(localeDir)) continue;\n\n    const extension = (file.endsWith('.po') ? 'po' : 'json') as 'po' | 'json';\n    basePathsByFormat[extension].push(parts.slice(0, -2).join('/') || '.');\n    localesByFormat[extension].add(localeDir);\n  }\n\n  // Prefer `.po` (lingui's default) when catalogs of both formats coexist.\n  const format: 'po' | 'json' = basePathsByFormat.po.length > 0 ? 'po' : 'json';\n\n  const basePaths = basePathsByFormat[format];\n  if (basePaths.length === 0) return null;\n\n  const counts = basePaths.reduce<Record<string, number>>(\n    (accumulator, path) => {\n      accumulator[path] = (accumulator[path] ?? 0) + 1;\n      return accumulator;\n    },\n    {}\n  );\n  const topBasePath =\n    Object.entries(counts).sort(([, a], [, b]) => b - a)[0]?.[0] ?? '.';\n  const prefix = topBasePath === '.' ? '.' : `./${topBasePath}`;\n\n  return {\n    format,\n    template: `${prefix}/\\${locale}/\\${key}.${format}`,\n    locales: Array.from(localesByFormat[format]),\n  };\n};\n\n/**\n * The messages source template derived from a `next-intl` `i18n/request.ts`\n * file, ready to be used as a `syncJSON` `source` builder.\n */\nexport type NextIntlMessagesPattern = {\n  /** `'flat'` (one file per locale) or `'nested'` (per-namespace files). */\n  type: JsonLocalePatternType;\n  /**\n   * Source path template relative to the project root, with `${locale}` (and\n   * `${key}` when nested) as literal placeholders, e.g. `./messages/${locale}.json`.\n   */\n  template: string;\n};\n\n/** Common locations of the `next-intl` request config, relative to the root. */\nconst NEXT_INTL_REQUEST_FILES = [\n  'i18n/request.ts',\n  'i18n/request.tsx',\n  'i18n/request.js',\n  'i18n/request.mjs',\n  'src/i18n/request.ts',\n  'src/i18n/request.tsx',\n  'src/i18n/request.js',\n  'src/i18n/request.mjs',\n  'app/i18n/request.ts',\n  'src/app/i18n/request.ts',\n];\n\n/**\n * Derives the messages source template from a `next-intl` `i18n/request.ts`\n * file, which is the authoritative location of the messages path in a next-intl\n * project (e.g. `messages: (await import(\\`../messages/${locale}.json\\`)).default`).\n *\n * Reading it removes the ambiguity of globbing the file system and yields the\n * exact `source` template for `syncJSON`. When the resulting template has no\n * `${key}` segment (the common single-file-per-locale layout, where top-level\n * keys are namespaces), `syncJSON` `splitKeys` auto-detection turns each\n * top-level key into its own dictionary.\n *\n * Returns `null` when no request file is found, the messages import cannot be\n * parsed, or the path is not project-root-relative (e.g. uses a TS path alias).\n *\n * @param rootDir - Project root directory.\n */\nexport const detectNextIntlMessagesPattern = async (\n  rootDir: string\n): Promise<NextIntlMessagesPattern | null> => {\n  for (const requestFile of NEXT_INTL_REQUEST_FILES) {\n    if (!(await exists(rootDir, requestFile))) continue;\n\n    const content = await readFileFromRoot(rootDir, requestFile);\n\n    // Capture the template-literal path of the messages dynamic import, e.g.\n    // import(`../messages/${locale}.json`) → `../messages/${locale}.json`.\n    const importMatch = content.match(\n      /import\\(\\s*`([^`]*\\$\\{\\s*locale\\s*\\}[^`]*\\.json)`/\n    );\n\n    const importPath = importMatch?.[1];\n    if (!importPath) continue;\n\n    // Only relative imports can be resolved against the file system; path\n    // aliases (e.g. `@/messages/...`) are skipped in favour of glob detection.\n    if (!importPath.startsWith('.')) continue;\n\n    // Resolve the import (relative to the request file) back to a\n    // project-root-relative template. The `${locale}` / `${key}` placeholders\n    // are kept literal — path utilities treat them as ordinary segments.\n    const requestDir = dirname(resolve(rootDir, requestFile));\n    const absoluteTemplate = resolve(requestDir, importPath);\n    const relativeTemplate = relative(rootDir, absoluteTemplate)\n      .split(sep)\n      .join('/');\n\n    const template = relativeTemplate.startsWith('.')\n      ? relativeTemplate\n      : `./${relativeTemplate}`;\n\n    const type: JsonLocalePatternType =\n      template.includes('${key}') || template.includes('${namespace}')\n        ? 'nested'\n        : 'flat';\n\n    return { type, template };\n  }\n\n  return null;\n};\n"],"mappings":";;;;;;;;;;;;;AASA,MAAa,SAAS,OAAO,SAAiB,aAAqB;CACjE,IAAI;EACF,UAAMA,6BAAOC,gBAAK,SAAS,QAAQ,CAAC;EACpC,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;;;;AAKA,MAAa,mBAAmB,OAAO,SAAiB,aACtD,UAAMC,+BAASD,gBAAK,SAAS,QAAQ,GAAG,MAAM;;;;AAKhD,MAAa,kBAAkB,OAC7B,SACA,UACA,YACG,UAAME,gCAAUF,gBAAK,SAAS,QAAQ,GAAG,SAAS,MAAM;;;;AAK7D,MAAa,kBAAkB,OAAO,SAAiB,YAAoB;CACzE,IAAI;EACF,UAAMG,4BAAMH,gBAAK,SAAS,OAAO,GAAG,EAAE,WAAW,KAAK,CAAC;CACzD,QAAQ,CAER;AACF;;;;;;;AAkCA,MAAM,oBAAoB,IAAI,IAAY,OAAO,OAAOI,sCAAW,CAAC;;;;;AAMpE,MAAM,mBAAmB,YACvB,kBAAkB,IAAI,OAAO;;AAG/B,MAAM,yCAAyB,IAAI,IAAI;CACrC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;;;;;;;;;AAWD,MAAa,0BAA0B,OACrC,YACsC;CACtC,MAAM,QAAQ,UAAMC,mBAAG,aAAa;EAClC,KAAK;EACL,QAAQC;EACR,UAAU;EACV,WAAW;CACb,CAAC;CAED,MAAM,kBAA4B,CAAC;CACnC,MAAM,gBAA0B,CAAC;CACjC,MAAM,gCAAgB,IAAI,IAAY;CACtC,MAAM,8BAAc,IAAI,IAAY;CAEpC,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,QAAQ,KAAK,MAAM,GAAG;EAC5B,MAAM,WAAW,MAAM,MAAM,SAAS,MAAM;EAE5C,IAAI,uBAAuB,IAAI,QAAQ,GAAG;EAG1C,IAAI,MAAM,UAAU,GAAG;GACrB,MAAM,YAAY,MAAM,MAAM,SAAS,MAAM;GAC7C,IAAI,gBAAgB,SAAS,GAAG;IAC9B,gBAAgB,KAAK,MAAM,MAAM,GAAG,EAAE,CAAC,CAAC,KAAK,GAAG,KAAK,GAAG;IACxD,cAAc,IAAI,SAAS;GAC7B;EACF;EAGA,IAAI,MAAM,UAAU,GAAG;GACrB,MAAM,WAAW,SAAS,MAAM,GAAG,EAAE;GACrC,IAAI,gBAAgB,QAAQ,GAAG;IAC7B,cAAc,KAAK,MAAM,MAAM,GAAG,EAAE,CAAC,CAAC,KAAK,GAAG,KAAK,GAAG;IACtD,YAAY,IAAI,QAAQ;GAC1B;EACF;CACF;CAEA,IAAI,gBAAgB,WAAW,KAAK,cAAc,WAAW,GAC3D,OAAO;;;;;CAOT,MAAM,sBAAsB,UAA4B;EACtD,MAAM,SAAS,MAAM,QAAgC,aAAa,SAAS;GACzE,YAAY,SAAS,YAAY,SAAS,KAAK;GAC/C,OAAO;EACT,GAAG,CAAC,CAAC;EAEL,MAAM,WADW,OAAO,QAAQ,MAAM,CAAC,CAAC,MAAM,GAAG,IAAI,GAAG,OAAO,IAAI,CAAC,CAAC,CAAC,EAC7C,GAAG,MAAM;EAClC,OAAO,aAAa,MAAM,MAAM,KAAK;CACvC;CAEA,IAAI,gBAAgB,UAAU,cAAc,QAE1C,OAAO;EACL,MAAM;EAEN,UAAU,GAJG,mBAAmB,eAId,EAAE;EACpB,SAAS,MAAM,KAAK,aAAa;CACnC;CAIF,OAAO;EACL,MAAM;EACN,UAAU,GAHG,mBAAmB,aAGd,EAAE;EACpB,SAAS,MAAM,KAAK,WAAW;CACjC;AACF;;;;;;;;;;;;AAgCA,MAAa,6BAA6B,OACxC,YACyC;CAGzC,MAAM,QAAQ,UAAMD,mBAAG,CAAC,kBAAkB,kBAAkB,GAAG;EAC7D,KAAK;EACL,QAAQC;EACR,UAAU;EACV,WAAW;CACb,CAAC;CAED,MAAM,oBAAqD;EACzD,IAAI,CAAC;EACL,MAAM,CAAC;CACT;CACA,MAAM,kBAAsD;EAC1D,oBAAI,IAAI,IAAI;EACZ,sBAAM,IAAI,IAAI;CAChB;CAEA,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,QAAQ,KAAK,MAAM,GAAG;EAE5B,IAAI,MAAM,SAAS,GAAG;EAEtB,MAAM,YAAY,MAAM,MAAM,SAAS,MAAM;EAC7C,IAAI,CAAC,gBAAgB,SAAS,GAAG;EAEjC,MAAM,YAAa,KAAK,SAAS,KAAK,IAAI,OAAO;EACjD,kBAAkB,UAAU,CAAC,KAAK,MAAM,MAAM,GAAG,EAAE,CAAC,CAAC,KAAK,GAAG,KAAK,GAAG;EACrE,gBAAgB,UAAU,CAAC,IAAI,SAAS;CAC1C;CAGA,MAAM,SAAwB,kBAAkB,GAAG,SAAS,IAAI,OAAO;CAEvE,MAAM,YAAY,kBAAkB;CACpC,IAAI,UAAU,WAAW,GAAG,OAAO;CAEnC,MAAM,SAAS,UAAU,QACtB,aAAa,SAAS;EACrB,YAAY,SAAS,YAAY,SAAS,KAAK;EAC/C,OAAO;CACT,GACA,CAAC,CACH;CACA,MAAM,cACJ,OAAO,QAAQ,MAAM,CAAC,CAAC,MAAM,GAAG,IAAI,GAAG,OAAO,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM;CAGlE,OAAO;EACL;EACA,UAAU,GAJG,gBAAgB,MAAM,MAAM,KAAK,cAI1B,sBAAsB;EAC1C,SAAS,MAAM,KAAK,gBAAgB,OAAO;CAC7C;AACF;;AAiBA,MAAM,0BAA0B;CAC9B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;;;;;;;;;;;;;;;;AAkBA,MAAa,gCAAgC,OAC3C,YAC4C;CAC5C,KAAK,MAAM,eAAe,yBAAyB;EACjD,IAAI,CAAE,MAAM,OAAO,SAAS,WAAW,GAAI;EAU3C,MAAM,cAJc,MAJE,iBAAiB,SAAS,WAAW,EAIhC,CAAC,MAC1B,mDAG2B,CAAC,GAAG;EACjC,IAAI,CAAC,YAAY;EAIjB,IAAI,CAAC,WAAW,WAAW,GAAG,GAAG;EAKjC,MAAM,iBAAaC,uBAAQC,mBAAQ,SAAS,WAAW,CAAC;EACxD,MAAM,uBAAmBA,mBAAQ,YAAY,UAAU;EACvD,MAAM,uBAAmBC,oBAAS,SAAS,gBAAgB,CAAC,CACzD,MAAMC,aAAG,CAAC,CACV,KAAK,GAAG;EAEX,MAAM,WAAW,iBAAiB,WAAW,GAAG,IAC5C,mBACA,KAAK;EAOT,OAAO;GAAE,MAJP,SAAS,SAAS,QAAQ,KAAK,SAAS,SAAS,cAAc,IAC3D,WACA;GAES;EAAS;CAC1B;CAEA,OAAO;AACT"}