@intlayer/cli 9.7.1 → 9.7.2

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 (92) hide show
  1. package/dist/cjs/_virtual/_utils_asset.cjs +1 -1
  2. package/dist/cjs/build.cjs +1 -1
  3. package/dist/cjs/bundle.cjs.map +1 -1
  4. package/dist/cjs/ci.cjs +1 -1
  5. package/dist/cjs/cli.cjs +5 -1
  6. package/dist/cjs/cli.cjs.map +1 -1
  7. package/dist/cjs/editor.cjs +2 -2
  8. package/dist/cjs/extract.cjs +4 -4
  9. package/dist/cjs/fill/fill.cjs +2 -2
  10. package/dist/cjs/fill/formatAutoFilledFilePath.cjs +1 -1
  11. package/dist/cjs/fill/listTranslationsTasks.cjs +2 -2
  12. package/dist/cjs/fill/translateDictionary.cjs +3 -3
  13. package/dist/cjs/fill/writeFill.cjs +2 -2
  14. package/dist/cjs/getTargetDictionary.cjs +2 -2
  15. package/dist/cjs/index.cjs +3 -2
  16. package/dist/cjs/init.cjs +20 -2
  17. package/dist/cjs/init.cjs.map +1 -1
  18. package/dist/cjs/initInfra.cjs +1 -1
  19. package/dist/cjs/listContentDeclaration.cjs +2 -2
  20. package/dist/cjs/listProjects.cjs +1 -1
  21. package/dist/cjs/liveSync.cjs +4 -4
  22. package/dist/cjs/pull.cjs +16 -7
  23. package/dist/cjs/pull.cjs.map +1 -1
  24. package/dist/cjs/push/push.cjs +4 -4
  25. package/dist/cjs/reviewDoc/reviewDoc.cjs +3 -3
  26. package/dist/cjs/reviewDoc/reviewDocBlockAware.cjs +5 -5
  27. package/dist/cjs/reviewDoc/reviewDocBlockAware.cjs.map +1 -1
  28. package/dist/cjs/reviewDoc/reviewDocLog.cjs +2 -2
  29. package/dist/cjs/test/listMissingTranslations.cjs +1 -1
  30. package/dist/cjs/test/test.cjs +1 -1
  31. package/dist/cjs/translateDoc/translateDoc.cjs +2 -2
  32. package/dist/cjs/translateDoc/translateFile.cjs +4 -4
  33. package/dist/cjs/translateDoc/translateFile.cjs.map +1 -1
  34. package/dist/cjs/upgrade.cjs +1 -1
  35. package/dist/cjs/utils/checkAccess.cjs +1 -1
  36. package/dist/cjs/utils/detectGitRepository.cjs +1 -1
  37. package/dist/cjs/utils/getParentPackageJSON.cjs +1 -1
  38. package/dist/esm/_virtual/_utils_asset.mjs +1 -1
  39. package/dist/esm/build.mjs +1 -1
  40. package/dist/esm/bundle.mjs.map +1 -1
  41. package/dist/esm/ci.mjs +1 -1
  42. package/dist/esm/cli.mjs +5 -1
  43. package/dist/esm/cli.mjs.map +1 -1
  44. package/dist/esm/editor.mjs +2 -2
  45. package/dist/esm/extract.mjs +5 -5
  46. package/dist/esm/extract.mjs.map +1 -1
  47. package/dist/esm/fill/fill.mjs +4 -4
  48. package/dist/esm/fill/fill.mjs.map +1 -1
  49. package/dist/esm/fill/formatAutoFilledFilePath.mjs +1 -1
  50. package/dist/esm/fill/listTranslationsTasks.mjs +2 -2
  51. package/dist/esm/fill/translateDictionary.mjs +3 -3
  52. package/dist/esm/fill/writeFill.mjs +2 -2
  53. package/dist/esm/getTargetDictionary.mjs +2 -2
  54. package/dist/esm/index.mjs +4 -4
  55. package/dist/esm/init.mjs +20 -3
  56. package/dist/esm/init.mjs.map +1 -1
  57. package/dist/esm/initInfra.mjs +1 -1
  58. package/dist/esm/listContentDeclaration.mjs +2 -2
  59. package/dist/esm/listProjects.mjs +1 -1
  60. package/dist/esm/liveSync.mjs +3 -3
  61. package/dist/esm/pull.mjs +16 -7
  62. package/dist/esm/pull.mjs.map +1 -1
  63. package/dist/esm/push/push.mjs +6 -6
  64. package/dist/esm/push/push.mjs.map +1 -1
  65. package/dist/esm/reviewDoc/reviewDoc.mjs +3 -3
  66. package/dist/esm/reviewDoc/reviewDocBlockAware.mjs +5 -5
  67. package/dist/esm/reviewDoc/reviewDocBlockAware.mjs.map +1 -1
  68. package/dist/esm/reviewDoc/reviewDocLog.mjs +2 -2
  69. package/dist/esm/test/listMissingTranslations.mjs +1 -1
  70. package/dist/esm/test/test.mjs +1 -1
  71. package/dist/esm/translateDoc/translateDoc.mjs +2 -2
  72. package/dist/esm/translateDoc/translateFile.mjs +4 -4
  73. package/dist/esm/translateDoc/translateFile.mjs.map +1 -1
  74. package/dist/esm/upgrade.mjs +1 -1
  75. package/dist/esm/utils/checkAccess.mjs +1 -1
  76. package/dist/esm/utils/detectGitRepository.mjs +1 -1
  77. package/dist/esm/utils/getParentPackageJSON.mjs +1 -1
  78. package/dist/types/IntlayerEventListener.d.ts +1 -1
  79. package/dist/types/bundle.d.ts.map +1 -1
  80. package/dist/types/fill/fill.d.ts +1 -1
  81. package/dist/types/fill/formatAutoFilledFilePath.d.ts +1 -1
  82. package/dist/types/index.d.ts +3 -3
  83. package/dist/types/init.d.ts +5 -1
  84. package/dist/types/init.d.ts.map +1 -1
  85. package/dist/types/pull.d.ts +14 -1
  86. package/dist/types/pull.d.ts.map +1 -1
  87. package/dist/types/reviewDoc/reviewDoc.d.ts +1 -1
  88. package/dist/types/test/listMissingTranslations.d.ts +1 -1
  89. package/dist/types/translateDoc/types.d.ts +2 -2
  90. package/dist/types/utils/checkAccess.d.ts +1 -1
  91. package/dist/types/utils/selectCmsEnvironment.d.ts +1 -1
  92. package/package.json +30 -30
@@ -1 +1 @@
1
- {"version":3,"file":"fill.mjs","names":["prepareIntlayer"],"sources":["../../../src/fill/fill.ts"],"sourcesContent":["import { basename, join, relative } from 'node:path';\nimport type { AIOptions } from '@intlayer/ai';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport {\n colorize,\n colorizeKey,\n colorizePath,\n getAppLogger,\n x,\n} from '@intlayer/config/logger';\nimport { getConfiguration } from '@intlayer/config/node';\nimport {\n loadContentDeclarations,\n prepareIntlayer,\n writeContentDeclaration,\n} from '@intlayer/engine/build';\nimport {\n type ListGitFilesOptions,\n logConfigDetails,\n} from '@intlayer/engine/cli';\nimport {\n formatPath,\n getGlobalLimiter,\n getTaskLimiter,\n} from '@intlayer/engine/utils';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { Fill } from '@intlayer/types/dictionary';\nimport {\n ensureArray,\n type GetTargetDictionaryOptions,\n getTargetUnmergedDictionaries,\n} from '../getTargetDictionary';\nimport { setupAI } from '../utils/setupAI';\nimport {\n listTranslationsTasks,\n type TranslationTask,\n} from './listTranslationsTasks';\nimport { translateDictionary } from './translateDictionary';\nimport { writeFill } from './writeFill';\n\nconst NB_CONCURRENT_TRANSLATIONS = 7;\n\n// Arguments for the fill function\nexport type FillOptions = {\n sourceLocale?: Locale;\n outputLocales?: Locale | Locale[];\n mode?: 'complete' | 'review';\n gitOptions?: ListGitFilesOptions;\n aiOptions?: AIOptions; // Added aiOptions to be passed to translateJSON\n verbose?: boolean;\n nbConcurrentTranslations?: number;\n nbConcurrentTasks?: number; // NEW: number of tasks that may run at once\n build?: boolean;\n skipMetadata?: boolean;\n} & GetTargetDictionaryOptions;\n\n/** Outcome of a {@link fill} run. */\nexport type FillResult =\n | {\n /** No AI provider is reachable: nothing was translated. */\n status: 'no-ai-access';\n /** Why the AI provider could not be used, when known. */\n error?: string;\n }\n | {\n /** No dictionary matched the filters: nothing was translated. */\n status: 'no-dictionaries';\n }\n | {\n status: 'completed';\n /** Keys of the dictionaries processed. */\n dictionaryKeys: string[];\n /** Translation tasks (one per dictionary and locale set) run. */\n taskCount: number;\n /** Tasks whose translation was written. */\n writtenCount: number;\n };\n\n/**\n * Fill translations based on the provided options.\n *\n * @returns What was done, so callers without a terminal (editor extension,\n * MCP) can tell a completed run from one that translated nothing.\n */\nexport const fill = async (options?: FillOptions): Promise<FillResult> => {\n const configuration = getConfiguration(options?.configOptions);\n logConfigDetails(options?.configOptions);\n\n const appLogger = getAppLogger(configuration);\n\n if (options?.build === true) {\n await prepareIntlayer(configuration, { forceRun: true });\n } else if (typeof options?.build === 'undefined') {\n await prepareIntlayer(configuration);\n }\n\n const { defaultLocale, locales } = configuration.internationalization;\n const mode = options?.mode ?? 'complete';\n const baseLocale = options?.sourceLocale ?? defaultLocale;\n\n const outputLocales = options?.outputLocales\n ? ensureArray(options.outputLocales)\n : locales;\n\n const aiResult = await setupAI(configuration, options?.aiOptions);\n\n if (!aiResult?.hasAIAccess) return { status: 'no-ai-access' };\n\n const { aiClient, aiConfig, isCustomAI } = aiResult;\n\n if (isCustomAI && aiClient && aiConfig) {\n const { hasAIAccess, error } = await aiClient.checkAISDKAccess(aiConfig);\n if (!hasAIAccess) {\n appLogger(`${x} ${error}`);\n return { status: 'no-ai-access', error };\n }\n }\n\n const targetUnmergedDictionaries =\n await getTargetUnmergedDictionaries(options);\n\n // Load the original source content declaration files to recover function-type\n // `fill` values that are lost when dictionaries are JSON-serialised into\n // unmerged_dictionaries.cjs. Dictionary-level fill takes priority over the\n // config-level fill, but we can only know that by reading the source files.\n const uniqueSourcePaths = [\n ...new Set(\n targetUnmergedDictionaries\n .filter((unmergedDictionary) =>\n ['local', 'hybrid'].includes(unmergedDictionary.location!)\n )\n .map((unmergedDictionary) => unmergedDictionary.filePath)\n .filter(Boolean) as string[]\n ),\n ];\n const sourceDictionaries = await loadContentDeclarations(\n uniqueSourcePaths.map((sourcePath) =>\n join(configuration.system.baseDir, sourcePath)\n ),\n configuration,\n undefined,\n {\n logError: false,\n }\n );\n // Map relative filePath → original fill value from the source file\n const originalFillByPath = new Map<string, Fill | undefined>();\n\n for (const dictionary of sourceDictionaries) {\n if (dictionary.filePath) {\n originalFillByPath.set(\n dictionary.filePath,\n dictionary.fill as Fill | undefined\n );\n }\n }\n\n const affectedDictionaryKeys = new Set<string>();\n\n targetUnmergedDictionaries.forEach((dict) => {\n affectedDictionaryKeys.add(dict.key);\n });\n\n const keysToProcess = Array.from(affectedDictionaryKeys);\n\n appLogger([\n 'Affected dictionary keys for processing:',\n keysToProcess.length > 0\n ? keysToProcess.map((key) => colorizeKey(key)).join(', ')\n : colorize('No keys found', ANSIColors.YELLOW),\n ]);\n\n if (keysToProcess.length === 0) return { status: 'no-dictionaries' };\n\n /**\n * List the translations tasks\n *\n * Create a list of per-locale dictionaries to translate\n *\n * In 'complete' mode, filter only the missing locales to translate\n */\n const translationTasks: TranslationTask[] = listTranslationsTasks(\n targetUnmergedDictionaries.map((dictionary) => dictionary.localId!),\n outputLocales,\n mode,\n baseLocale,\n configuration\n );\n\n // AI calls in flight at once (translateJSON + metadata audit)\n const nbConcurrentTranslations =\n options?.nbConcurrentTranslations ?? NB_CONCURRENT_TRANSLATIONS;\n const globalLimiter = getGlobalLimiter(nbConcurrentTranslations);\n\n // NEW: number of *tasks* that may run at once (start/prepare/log/write)\n const nbConcurrentTasks = Math.max(\n 1,\n Math.min(\n options?.nbConcurrentTasks ?? nbConcurrentTranslations,\n translationTasks.length\n )\n );\n\n const taskLimiter = getTaskLimiter(nbConcurrentTasks);\n\n let writtenCount = 0;\n\n const runners = translationTasks.map((task) =>\n taskLimiter(async () => {\n const relativePath = relative(\n configuration?.system?.baseDir ?? process.cwd(),\n task?.dictionaryFilePath ?? ''\n );\n\n // log AFTER acquiring a task slot\n appLogger(\n `${task.dictionaryPreset} Processing ${colorizePath(basename(relativePath))}`,\n { level: 'info' }\n );\n\n const translationTaskResult = await translateDictionary(\n task,\n configuration,\n {\n mode,\n aiOptions: options?.aiOptions,\n fillMetadata: !options?.skipMetadata,\n onHandle: globalLimiter,\n aiClient,\n aiConfig,\n }\n );\n\n if (!translationTaskResult?.dictionaryOutput) return;\n\n const { dictionaryOutput, sourceLocale } = translationTaskResult;\n\n // Determine if we should write to separate files\n // - If dictionary has explicit fill setting (string, function, or object), use it\n // - If dictionary is per-locale AND has no explicit fill=false, use global fill config\n // - If dictionary is multilingual (no locale property), always write to same file\n //\n // NOTE: function-type fill values are lost during JSON serialisation of\n // unmerged_dictionaries.cjs. We recover them by checking the original\n // source file that was loaded above (originalFillByPath). Dictionary-level\n // fill always takes priority over config-level fill.\n const originalFill = originalFillByPath.get(\n dictionaryOutput.filePath ?? ''\n );\n\n // originalFill is undefined when the source file had no fill property; use\n // the (possibly JSON-preserved) dictionaryOutput.fill as a fallback so that\n // string/boolean fill values set directly on the dict still work.\n const dictFill: Fill | undefined =\n originalFill !== undefined ? originalFill : dictionaryOutput.fill;\n\n const hasDictionaryLevelFill =\n typeof dictFill === 'string' ||\n typeof dictFill === 'function' ||\n (typeof dictFill === 'object' && dictFill !== null);\n\n const isPerLocale = typeof dictionaryOutput.locale === 'string';\n\n const effectiveFill = hasDictionaryLevelFill\n ? dictFill\n : isPerLocale\n ? (configuration.dictionary?.fill ?? true)\n : false; // Multilingual dictionaries (no locale property) always write to same file unless explicitly set on the dictionary\n\n const isFillOtherFile =\n typeof effectiveFill === 'string' ||\n typeof effectiveFill === 'function' ||\n (typeof effectiveFill === 'object' && effectiveFill !== null);\n\n if (isFillOtherFile) {\n await writeFill(\n {\n ...dictionaryOutput,\n // Ensure fill is set on the dictionary for writeFill to use\n fill: effectiveFill,\n },\n outputLocales,\n [sourceLocale],\n configuration\n );\n writtenCount++;\n } else {\n await writeContentDeclaration(dictionaryOutput, configuration);\n writtenCount++;\n\n if (dictionaryOutput.filePath) {\n appLogger(\n `${task.dictionaryPreset} Content declaration written to ${formatPath(basename(dictionaryOutput.filePath))}`,\n { level: 'info' }\n );\n }\n }\n })\n );\n\n await Promise.all(runners);\n await (globalLimiter as any).onIdle();\n\n return {\n status: 'completed',\n dictionaryKeys: keysToProcess,\n taskCount: translationTasks.length,\n writtenCount,\n };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"fill.mjs","names":[],"sources":["../../../src/fill/fill.ts"],"sourcesContent":["import { basename, join, relative } from 'node:path';\nimport type { AIOptions } from '@intlayer/ai';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport {\n colorize,\n colorizeKey,\n colorizePath,\n getAppLogger,\n x,\n} from '@intlayer/config/logger';\nimport { getConfiguration } from '@intlayer/config/node';\nimport {\n loadContentDeclarations,\n prepareIntlayer,\n writeContentDeclaration,\n} from '@intlayer/engine/build';\nimport {\n type ListGitFilesOptions,\n logConfigDetails,\n} from '@intlayer/engine/cli';\nimport {\n formatPath,\n getGlobalLimiter,\n getTaskLimiter,\n} from '@intlayer/engine/utils';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { Fill } from '@intlayer/types/dictionary';\nimport {\n ensureArray,\n type GetTargetDictionaryOptions,\n getTargetUnmergedDictionaries,\n} from '../getTargetDictionary';\nimport { setupAI } from '../utils/setupAI';\nimport {\n listTranslationsTasks,\n type TranslationTask,\n} from './listTranslationsTasks';\nimport { translateDictionary } from './translateDictionary';\nimport { writeFill } from './writeFill';\n\nconst NB_CONCURRENT_TRANSLATIONS = 7;\n\n// Arguments for the fill function\nexport type FillOptions = {\n sourceLocale?: Locale;\n outputLocales?: Locale | Locale[];\n mode?: 'complete' | 'review';\n gitOptions?: ListGitFilesOptions;\n aiOptions?: AIOptions; // Added aiOptions to be passed to translateJSON\n verbose?: boolean;\n nbConcurrentTranslations?: number;\n nbConcurrentTasks?: number; // NEW: number of tasks that may run at once\n build?: boolean;\n skipMetadata?: boolean;\n} & GetTargetDictionaryOptions;\n\n/** Outcome of a {@link fill} run. */\nexport type FillResult =\n | {\n /** No AI provider is reachable: nothing was translated. */\n status: 'no-ai-access';\n /** Why the AI provider could not be used, when known. */\n error?: string;\n }\n | {\n /** No dictionary matched the filters: nothing was translated. */\n status: 'no-dictionaries';\n }\n | {\n status: 'completed';\n /** Keys of the dictionaries processed. */\n dictionaryKeys: string[];\n /** Translation tasks (one per dictionary and locale set) run. */\n taskCount: number;\n /** Tasks whose translation was written. */\n writtenCount: number;\n };\n\n/**\n * Fill translations based on the provided options.\n *\n * @returns What was done, so callers without a terminal (editor extension,\n * MCP) can tell a completed run from one that translated nothing.\n */\nexport const fill = async (options?: FillOptions): Promise<FillResult> => {\n const configuration = getConfiguration(options?.configOptions);\n logConfigDetails(options?.configOptions);\n\n const appLogger = getAppLogger(configuration);\n\n if (options?.build === true) {\n await prepareIntlayer(configuration, { forceRun: true });\n } else if (typeof options?.build === 'undefined') {\n await prepareIntlayer(configuration);\n }\n\n const { defaultLocale, locales } = configuration.internationalization;\n const mode = options?.mode ?? 'complete';\n const baseLocale = options?.sourceLocale ?? defaultLocale;\n\n const outputLocales = options?.outputLocales\n ? ensureArray(options.outputLocales)\n : locales;\n\n const aiResult = await setupAI(configuration, options?.aiOptions);\n\n if (!aiResult?.hasAIAccess) return { status: 'no-ai-access' };\n\n const { aiClient, aiConfig, isCustomAI } = aiResult;\n\n if (isCustomAI && aiClient && aiConfig) {\n const { hasAIAccess, error } = await aiClient.checkAISDKAccess(aiConfig);\n if (!hasAIAccess) {\n appLogger(`${x} ${error}`);\n return { status: 'no-ai-access', error };\n }\n }\n\n const targetUnmergedDictionaries =\n await getTargetUnmergedDictionaries(options);\n\n // Load the original source content declaration files to recover function-type\n // `fill` values that are lost when dictionaries are JSON-serialised into\n // unmerged_dictionaries.cjs. Dictionary-level fill takes priority over the\n // config-level fill, but we can only know that by reading the source files.\n const uniqueSourcePaths = [\n ...new Set(\n targetUnmergedDictionaries\n .filter((unmergedDictionary) =>\n ['local', 'hybrid'].includes(unmergedDictionary.location!)\n )\n .map((unmergedDictionary) => unmergedDictionary.filePath)\n .filter(Boolean) as string[]\n ),\n ];\n const sourceDictionaries = await loadContentDeclarations(\n uniqueSourcePaths.map((sourcePath) =>\n join(configuration.system.baseDir, sourcePath)\n ),\n configuration,\n undefined,\n {\n logError: false,\n }\n );\n // Map relative filePath → original fill value from the source file\n const originalFillByPath = new Map<string, Fill | undefined>();\n\n for (const dictionary of sourceDictionaries) {\n if (dictionary.filePath) {\n originalFillByPath.set(\n dictionary.filePath,\n dictionary.fill as Fill | undefined\n );\n }\n }\n\n const affectedDictionaryKeys = new Set<string>();\n\n targetUnmergedDictionaries.forEach((dict) => {\n affectedDictionaryKeys.add(dict.key);\n });\n\n const keysToProcess = Array.from(affectedDictionaryKeys);\n\n appLogger([\n 'Affected dictionary keys for processing:',\n keysToProcess.length > 0\n ? keysToProcess.map((key) => colorizeKey(key)).join(', ')\n : colorize('No keys found', ANSIColors.YELLOW),\n ]);\n\n if (keysToProcess.length === 0) return { status: 'no-dictionaries' };\n\n /**\n * List the translations tasks\n *\n * Create a list of per-locale dictionaries to translate\n *\n * In 'complete' mode, filter only the missing locales to translate\n */\n const translationTasks: TranslationTask[] = listTranslationsTasks(\n targetUnmergedDictionaries.map((dictionary) => dictionary.localId!),\n outputLocales,\n mode,\n baseLocale,\n configuration\n );\n\n // AI calls in flight at once (translateJSON + metadata audit)\n const nbConcurrentTranslations =\n options?.nbConcurrentTranslations ?? NB_CONCURRENT_TRANSLATIONS;\n const globalLimiter = getGlobalLimiter(nbConcurrentTranslations);\n\n // NEW: number of *tasks* that may run at once (start/prepare/log/write)\n const nbConcurrentTasks = Math.max(\n 1,\n Math.min(\n options?.nbConcurrentTasks ?? nbConcurrentTranslations,\n translationTasks.length\n )\n );\n\n const taskLimiter = getTaskLimiter(nbConcurrentTasks);\n\n let writtenCount = 0;\n\n const runners = translationTasks.map((task) =>\n taskLimiter(async () => {\n const relativePath = relative(\n configuration?.system?.baseDir ?? process.cwd(),\n task?.dictionaryFilePath ?? ''\n );\n\n // log AFTER acquiring a task slot\n appLogger(\n `${task.dictionaryPreset} Processing ${colorizePath(basename(relativePath))}`,\n { level: 'info' }\n );\n\n const translationTaskResult = await translateDictionary(\n task,\n configuration,\n {\n mode,\n aiOptions: options?.aiOptions,\n fillMetadata: !options?.skipMetadata,\n onHandle: globalLimiter,\n aiClient,\n aiConfig,\n }\n );\n\n if (!translationTaskResult?.dictionaryOutput) return;\n\n const { dictionaryOutput, sourceLocale } = translationTaskResult;\n\n // Determine if we should write to separate files\n // - If dictionary has explicit fill setting (string, function, or object), use it\n // - If dictionary is per-locale AND has no explicit fill=false, use global fill config\n // - If dictionary is multilingual (no locale property), always write to same file\n //\n // NOTE: function-type fill values are lost during JSON serialisation of\n // unmerged_dictionaries.cjs. We recover them by checking the original\n // source file that was loaded above (originalFillByPath). Dictionary-level\n // fill always takes priority over config-level fill.\n const originalFill = originalFillByPath.get(\n dictionaryOutput.filePath ?? ''\n );\n\n // originalFill is undefined when the source file had no fill property; use\n // the (possibly JSON-preserved) dictionaryOutput.fill as a fallback so that\n // string/boolean fill values set directly on the dict still work.\n const dictFill: Fill | undefined =\n originalFill !== undefined ? originalFill : dictionaryOutput.fill;\n\n const hasDictionaryLevelFill =\n typeof dictFill === 'string' ||\n typeof dictFill === 'function' ||\n (typeof dictFill === 'object' && dictFill !== null);\n\n const isPerLocale = typeof dictionaryOutput.locale === 'string';\n\n const effectiveFill = hasDictionaryLevelFill\n ? dictFill\n : isPerLocale\n ? (configuration.dictionary?.fill ?? true)\n : false; // Multilingual dictionaries (no locale property) always write to same file unless explicitly set on the dictionary\n\n const isFillOtherFile =\n typeof effectiveFill === 'string' ||\n typeof effectiveFill === 'function' ||\n (typeof effectiveFill === 'object' && effectiveFill !== null);\n\n if (isFillOtherFile) {\n await writeFill(\n {\n ...dictionaryOutput,\n // Ensure fill is set on the dictionary for writeFill to use\n fill: effectiveFill,\n },\n outputLocales,\n [sourceLocale],\n configuration\n );\n writtenCount++;\n } else {\n await writeContentDeclaration(dictionaryOutput, configuration);\n writtenCount++;\n\n if (dictionaryOutput.filePath) {\n appLogger(\n `${task.dictionaryPreset} Content declaration written to ${formatPath(basename(dictionaryOutput.filePath))}`,\n { level: 'info' }\n );\n }\n }\n })\n );\n\n await Promise.all(runners);\n await (globalLimiter as any).onIdle();\n\n return {\n status: 'completed',\n dictionaryKeys: keysToProcess,\n taskCount: translationTasks.length,\n writtenCount,\n };\n};\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
+ import { basename, dirname, isAbsolute, normalize, resolve } from "node:path";
1
2
  import { GREY_DARK } from "@intlayer/config/colors";
2
3
  import { colorize, colorizePath } from "@intlayer/config/logger";
3
- import { basename, dirname, isAbsolute, normalize, resolve } from "node:path";
4
4
  import { getFormatFromExtension } from "@intlayer/engine/utils";
5
5
  import { parseFilePathPattern } from "@intlayer/config/utils";
6
6
 
@@ -1,9 +1,9 @@
1
1
  import { listMissingTranslationsWithConfig } from "../test/listMissingTranslations.mjs";
2
+ import { basename } from "node:path";
2
3
  import * as ANSIColors from "@intlayer/config/colors";
3
4
  import { colon, colorize, colorizeKey, colorizePath, getAppLogger } from "@intlayer/config/logger";
4
- import { basename } from "node:path";
5
- import { formatLocale } from "@intlayer/engine/utils";
6
5
  import { getUnmergedDictionaries } from "@intlayer/dictionaries-entry/unmerged";
6
+ import { formatLocale } from "@intlayer/engine/utils";
7
7
  import { getFilterTranslationsOnlyDictionary } from "@intlayer/core/plugins";
8
8
  import { getDictionaries } from "@intlayer/dictionaries-entry";
9
9
 
@@ -1,13 +1,13 @@
1
1
  import { getAuthenticatedAPI } from "../utils/checkAccess.mjs";
2
2
  import { deepMergeContent } from "./deepMergeContent.mjs";
3
3
  import { extractTranslatableContent, reinsertTranslatedContent } from "./extractTranslatableContent.mjs";
4
+ import { basename } from "node:path";
4
5
  import * as ANSIColors from "@intlayer/config/colors";
5
6
  import { colon, colorize, colorizeNumber, colorizePath, getAppLogger } from "@intlayer/config/logger";
6
- import { basename } from "node:path";
7
- import { chunkJSON, excludeObjectFormat, formatLocale, mergeChunks, reconstructFromSingleChunk, verifyIdenticObjectFormat } from "@intlayer/engine/utils";
8
- import { retryManager } from "@intlayer/config/utils";
9
7
  import { getUnmergedDictionaries } from "@intlayer/dictionaries-entry/unmerged";
8
+ import { chunkJSON, excludeObjectFormat, formatLocale, mergeChunks, reconstructFromSingleChunk, verifyIdenticObjectFormat } from "@intlayer/engine/utils";
10
9
  import { getFilterMissingTranslationsDictionary, getMultilingualDictionary, getPerLocaleDictionary, insertContentInDictionary } from "@intlayer/core/plugins";
10
+ import { retryManager } from "@intlayer/config/utils";
11
11
 
12
12
  // src/fill/translateDictionary.ts
13
13
  const createChunkPreset = (chunkIndex, totalChunks) => {
@@ -1,9 +1,9 @@
1
1
  import { formatFillData } from "./formatFillData.mjs";
2
2
  import { getAvailableLocalesInDictionary } from "./getAvailableLocalesInDictionary.mjs";
3
- import { colorizeKey, getAppLogger } from "@intlayer/config/logger";
4
3
  import { resolve } from "node:path";
5
- import { formatLocale, formatPath } from "@intlayer/engine/utils";
4
+ import { colorizeKey, getAppLogger } from "@intlayer/config/logger";
6
5
  import { writeContentDeclaration } from "@intlayer/engine/build";
6
+ import { formatLocale, formatPath } from "@intlayer/engine/utils";
7
7
  import { getDictionaries } from "@intlayer/dictionaries-entry";
8
8
 
9
9
  // src/fill/writeFill.ts
@@ -1,7 +1,7 @@
1
- import { getConfiguration } from "@intlayer/config/node";
2
- import { listGitFiles } from "@intlayer/engine/cli";
3
1
  import { join, relative } from "node:path";
2
+ import { getConfiguration } from "@intlayer/config/node";
4
3
  import { getUnmergedDictionaries } from "@intlayer/dictionaries-entry/unmerged";
4
+ import { listGitFiles } from "@intlayer/engine/cli";
5
5
 
6
6
  // src/getTargetDictionary.ts
7
7
  const ensureArray = (value) => [value].flat();
@@ -7,9 +7,7 @@ import { getRemoteEditorRunners, startEditor } from "./editor.mjs";
7
7
  import { extract } from "./extract.mjs";
8
8
  import { fill } from "./fill/fill.mjs";
9
9
  import { IntlayerEventListener } from "./IntlayerEventListener.mjs";
10
- import { listMissingTranslations, listMissingTranslationsWithConfig } from "./test/listMissingTranslations.mjs";
11
- import { testMissingTranslations } from "./test/test.mjs";
12
- import { CONTENT_LAYOUT_CHOICES, DEFAULT_INIT_STEPS, ESLINT_CONFIG_FILES, INIT_STEP_COMMANDS, INIT_STEP_GROUPS, LOCALE_ROUTING_CHOICES, OPT_IN_INIT_STEPS, findProjectRoot, getContentInitOptions, getInitOptionsForSteps, getInitialInitSteps, getProjectDependencies, getRoutingInitOptions, init, initCms, initRepository, initSteps, isEslintInstalled, parseContentLayoutChoice, parseLocaleRoutingChoice, resolveProjectRoot } from "./init.mjs";
10
+ import { CONTENT_LAYOUT_CHOICES, DEFAULT_INIT_STEPS, ESLINT_CONFIG_FILES, INIT_STEP_COMMANDS, INIT_STEP_GROUPS, LOCALE_ROUTING_CHOICES, OPT_IN_INIT_STEPS, findProjectRoot, getContentInitOptions, getInitOptionsForSteps, getInitialInitSteps, getProjectDependencies, getRoutingInitOptions, init, initCms, initRepository, initSteps, isEslintInstalled, isPrettierInstalled, parseContentLayoutChoice, parseLocaleRoutingChoice, resolveProjectRoot } from "./init.mjs";
13
11
  import { BROWSER_EXTENSION_OPTIONS, CHROME_EXTENSION_URL, EXTENSION_BROWSERS, EXTENSION_URL_BY_BROWSER, MOZILLA_EXTENSION_URL, initChromeExtension, parseExtensionBrowsers } from "./initChromeExtension.mjs";
14
12
  import { INFRA_MODES, INSTALL_SCRIPT_URL, INSTALL_SCRIPT_URL_WINDOWS, initInfra, parseInfraMode } from "./initInfra.mjs";
15
13
  import { PLATFORM_OPTIONS, initSkills, parsePlatform, parseSkills, promptPlatform, resolvePlatformWithoutPrompt } from "./initSkills.mjs";
@@ -18,6 +16,8 @@ import { listProjectsCommand } from "./listProjects.mjs";
18
16
  import { liveSync } from "./liveSync.mjs";
19
17
  import { pull } from "./pull.mjs";
20
18
  import { push } from "./push/push.mjs";
19
+ import { listMissingTranslations, listMissingTranslationsWithConfig } from "./test/listMissingTranslations.mjs";
20
+ import { testMissingTranslations } from "./test/test.mjs";
21
21
  import { pushConfig } from "./pushConfig.mjs";
22
22
  import { reviewDoc } from "./reviewDoc/reviewDoc.mjs";
23
23
  import { scan } from "./scan.mjs";
@@ -26,4 +26,4 @@ import { translateDoc } from "./translateDoc/translateDoc.mjs";
26
26
  import { checkCMSAuth, getAuthenticatedAPI } from "./utils/checkAccess.mjs";
27
27
  import { checkConfigConsistency } from "./utils/checkConfigConsistency.mjs";
28
28
 
29
- export { BROWSER_EXTENSION_OPTIONS, CHROME_EXTENSION_URL, CONTENT_LAYOUT_CHOICES, DEFAULT_INIT_STEPS, ESLINT_CONFIG_FILES, EXTENSION_BROWSERS, EXTENSION_URL_BY_BROWSER, INFRA_MODES, INIT_STEP_COMMANDS, INIT_STEP_GROUPS, INSTALL_SCRIPT_URL, INSTALL_SCRIPT_URL_WINDOWS, IntlayerEventListener, LOCALE_ROUTING_CHOICES, MOZILLA_EXTENSION_URL, OPT_IN_INIT_STEPS, PLATFORM_OPTIONS, build, bundle, checkCMSAuth, checkConfigConsistency, clearCliSessionToken, dirname, extract, fill, findProjectRoot, getAuthenticatedAPI, getContentInitOptions, getInitOptionsForSteps, getInitialInitSteps, getProjectDependencies, getRemoteEditorRunners, getRoutingInitOptions, init, initChromeExtension, initCms, initInfra, initRepository, initSkills, initSteps, isEslintInstalled, listContentDeclaration, listContentDeclarationRows, listMissingTranslations, listMissingTranslationsWithConfig, listProjectsCommand, liveSync, login, parseContentLayoutChoice, parseExtensionBrowsers, parseInfraMode, parseLocaleRoutingChoice, parsePlatform, parseSkills, promptPlatform, pull, push, pushConfig, readCliSessionToken, resolvePlatformWithoutPrompt, resolveProjectRoot, reviewDoc, scan, searchDoc, setAPI, startEditor, testMissingTranslations, translateDoc };
29
+ export { BROWSER_EXTENSION_OPTIONS, CHROME_EXTENSION_URL, CONTENT_LAYOUT_CHOICES, DEFAULT_INIT_STEPS, ESLINT_CONFIG_FILES, EXTENSION_BROWSERS, EXTENSION_URL_BY_BROWSER, INFRA_MODES, INIT_STEP_COMMANDS, INIT_STEP_GROUPS, INSTALL_SCRIPT_URL, INSTALL_SCRIPT_URL_WINDOWS, IntlayerEventListener, LOCALE_ROUTING_CHOICES, MOZILLA_EXTENSION_URL, OPT_IN_INIT_STEPS, PLATFORM_OPTIONS, build, bundle, checkCMSAuth, checkConfigConsistency, clearCliSessionToken, dirname, extract, fill, findProjectRoot, getAuthenticatedAPI, getContentInitOptions, getInitOptionsForSteps, getInitialInitSteps, getProjectDependencies, getRemoteEditorRunners, getRoutingInitOptions, init, initChromeExtension, initCms, initInfra, initRepository, initSkills, initSteps, isEslintInstalled, isPrettierInstalled, listContentDeclaration, listContentDeclarationRows, listMissingTranslations, listMissingTranslationsWithConfig, listProjectsCommand, liveSync, login, parseContentLayoutChoice, parseExtensionBrowsers, parseInfraMode, parseLocaleRoutingChoice, parsePlatform, parseSkills, promptPlatform, pull, push, pushConfig, readCliSessionToken, resolvePlatformWithoutPrompt, resolveProjectRoot, reviewDoc, scan, searchDoc, setAPI, startEditor, testMissingTranslations, translateDoc };
package/dist/esm/init.mjs CHANGED
@@ -8,9 +8,9 @@ import { loadPrompts } from "./loadPrompts.mjs";
8
8
  import { detectGitRepository } from "./utils/detectGitRepository.mjs";
9
9
  import { isInteractiveTerminal } from "./utils/isInteractiveTerminal.mjs";
10
10
  import { parseChoice } from "./utils/parseChoice.mjs";
11
- import { CATALOG_MESSAGE_FORMATS, CONTENT_FORMATS_BY_LAYOUT, detectCompatI18nLibraries, hasLintTooling, hasUrlRoutingFramework, initIntlayer, logInitSuccessMessage, setupCmsCredentials } from "@intlayer/engine/cli";
12
- import { join, resolve } from "node:path";
13
11
  import { existsSync, readFileSync } from "node:fs";
12
+ import { join, resolve } from "node:path";
13
+ import { CATALOG_MESSAGE_FORMATS, CONTENT_FORMATS_BY_LAYOUT, PRETTIER_CONFIG_FILES, detectCompatI18nLibraries, hasLintTooling, hasPrettier, hasUrlRoutingFramework, initIntlayer, logInitSuccessMessage, setupCmsCredentials } from "@intlayer/engine/cli";
14
14
 
15
15
  // src/init.ts
16
16
  const findProjectRoot = (startDir) => {
@@ -46,6 +46,7 @@ const INIT_STEP_COMMANDS = {
46
46
  githubActions: "intlayer init github-actions",
47
47
  vscodeExtension: "intlayer init vscode-extension",
48
48
  eslint: "intlayer init eslint",
49
+ prettier: "intlayer init prettier",
49
50
  chromeExtension: "intlayer init extension [--browser <chrome|firefox>]",
50
51
  skills: "intlayer init skills [--platform <platform>] [--skills <skills...>]",
51
52
  mcp: "intlayer init mcp [--platform <platform>] [--transport <stdio|sse>]",
@@ -68,6 +69,7 @@ const getInitOptionsForSteps = (steps, baseOptions) => ({
68
69
  noVscodeExtension: baseOptions?.noVscodeExtension || !steps.includes("vscodeExtension"),
69
70
  noLsp: baseOptions?.noLsp || !steps.includes("lsp"),
70
71
  noEslint: baseOptions?.noEslint || !steps.includes("eslint"),
72
+ noPrettier: baseOptions?.noPrettier || !steps.includes("prettier"),
71
73
  skipFinalMessage: baseOptions?.skipFinalMessage || !steps.includes("projectSetup")
72
74
  });
73
75
  /**
@@ -139,6 +141,11 @@ const INIT_STEP_GROUPS = {
139
141
  label: "Lint rules (ESLint / oxlint)",
140
142
  hint: "flag hardcoded text and dynamic calls the compiler cannot optimize"
141
143
  },
144
+ {
145
+ value: "prettier",
146
+ label: "Prettier plugin",
147
+ hint: "sort the keys of t(), plural(), enu() and select() on format"
148
+ },
142
149
  {
143
150
  value: "chromeExtension",
144
151
  label: "Chrome extension",
@@ -324,15 +331,25 @@ const isEslintInstalled = (dependencies, root) => {
324
331
  return false;
325
332
  };
326
333
  /**
334
+ * Checks whether Prettier is installed or configured in the project.
335
+ */
336
+ const isPrettierInstalled = (dependencies, root) => {
337
+ if (hasPrettier(dependencies)) return true;
338
+ if (root) return PRETTIER_CONFIG_FILES.some((file) => existsSync(join(root, file)));
339
+ return false;
340
+ };
341
+ /**
327
342
  * Computes the initial setup steps selected by default in the interactive prompt.
328
343
  * Steps like infrastructure and browser extension require opt-in, while
329
344
  * eslint is preselected only if installed on the project.
330
345
  */
331
346
  const getInitialInitSteps = (dependencies, root) => {
332
347
  const isEslintPresent = isEslintInstalled(dependencies, root);
348
+ const isPrettierPresent = isPrettierInstalled(dependencies, root);
333
349
  return Object.values(INIT_STEP_GROUPS).flat().map((option) => option.value).filter((step) => {
334
350
  if (OPT_IN_INIT_STEPS.includes(step)) return false;
335
351
  if (step === "eslint" && !isEslintPresent) return false;
352
+ if (step === "prettier" && !isPrettierPresent) return false;
336
353
  return true;
337
354
  });
338
355
  };
@@ -472,5 +489,5 @@ const init = async (projectRoot, options, interactive) => {
472
489
  await runInteractiveInit(resolveProjectRoot(projectRoot), options);
473
490
  };
474
491
 
475
- export { CONTENT_LAYOUT_CHOICES, DEFAULT_INIT_STEPS, ESLINT_CONFIG_FILES, INIT_STEP_COMMANDS, INIT_STEP_GROUPS, LOCALE_ROUTING_CHOICES, OPT_IN_INIT_STEPS, findProjectRoot, getContentInitOptions, getInitOptionsForSteps, getInitialInitSteps, getProjectDependencies, getRoutingInitOptions, init, initCms, initRepository, initSteps, isEslintInstalled, parseContentLayoutChoice, parseLocaleRoutingChoice, resolveProjectRoot };
492
+ export { CONTENT_LAYOUT_CHOICES, DEFAULT_INIT_STEPS, ESLINT_CONFIG_FILES, INIT_STEP_COMMANDS, INIT_STEP_GROUPS, LOCALE_ROUTING_CHOICES, OPT_IN_INIT_STEPS, findProjectRoot, getContentInitOptions, getInitOptionsForSteps, getInitialInitSteps, getProjectDependencies, getRoutingInitOptions, init, initCms, initRepository, initSteps, isEslintInstalled, isPrettierInstalled, parseContentLayoutChoice, parseLocaleRoutingChoice, resolveProjectRoot };
476
493
  //# sourceMappingURL=init.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"init.mjs","names":[],"sources":["../../src/init.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport {\n CATALOG_MESSAGE_FORMATS,\n type CatalogMessageFormat,\n CONTENT_FORMATS_BY_LAYOUT,\n type ContentFormat,\n type ContentLayout,\n detectCompatI18nLibraries,\n hasLintTooling,\n hasUrlRoutingFramework,\n type InitOptions,\n initIntlayer,\n logInitSuccessMessage,\n type RoutingMode,\n setupCmsCredentials,\n} from '@intlayer/engine/cli';\nimport { login } from './auth/login';\nimport { setupRepository } from './auth/setupRepository';\nimport { initChromeExtension } from './initChromeExtension';\nimport { initInfra } from './initInfra';\nimport { initMCP } from './initMCP';\nimport { initSkills } from './initSkills';\nimport { loadPrompts } from './loadPrompts';\nimport { detectGitRepository } from './utils/detectGitRepository';\nimport { isInteractiveTerminal } from './utils/isInteractiveTerminal';\nimport { parseChoice } from './utils/parseChoice';\n\nexport const findProjectRoot = (startDir: string) => {\n let currentDir = startDir;\n\n while (currentDir !== resolve(currentDir, '..')) {\n if (existsSync(join(currentDir, 'package.json'))) {\n return currentDir;\n }\n currentDir = resolve(currentDir, '..');\n }\n\n // If no package.json is found, return the start directory.\n // The initIntlayer function will handle the missing package.json error.\n return startDir;\n};\n\n/** Resolves the project root from `--project-root`, else the working directory. */\nexport const resolveProjectRoot = (projectRoot?: string): string =>\n findProjectRoot(projectRoot ? resolve(projectRoot) : process.cwd());\n\n/** Individually selectable setup steps exposed by the interactive init flow. */\nexport type InitStep =\n | 'packages'\n | 'githubActions'\n | 'projectSetup'\n | 'vscodeExtension'\n | 'lsp'\n | 'eslint'\n | 'skills'\n | 'mcp'\n | 'infra'\n | 'cms'\n | 'repository'\n | 'chromeExtension';\n\n/** A checkbox entry of the interactive init flow. */\nexport type InitStepOption = {\n value: InitStep;\n label: string;\n hint: string;\n};\n\n/**\n * Steps shown but not pre-selected. The infrastructure installer touches the\n * machine (downloads an app, pulls Docker images) and browser extensions open external URLs\n * rather than modifying the project, so they must be an explicit choice.\n */\nexport const OPT_IN_INIT_STEPS: InitStep[] = ['infra', 'chromeExtension'];\n\n/**\n * Steps run by `intlayer init` without `--interactive`: install the packages\n * and set up the framework. Every other step has its own command\n * ({@link INIT_STEP_COMMANDS}), so nothing outside the app code is touched\n * unless asked for.\n */\nexport const DEFAULT_INIT_STEPS: InitStep[] = ['packages', 'projectSetup'];\n\n/**\n * Command running each interactive step on its own, without prompts when every\n * value is passed as a flag. Printed when `--interactive` has no terminal.\n */\nexport const INIT_STEP_COMMANDS: Record<InitStep, string> = {\n packages: 'intlayer init packages',\n projectSetup:\n 'intlayer init project [--routing <routing>] [--content <layout>] [--content-format <format>] [--message-format <format>]',\n githubActions: 'intlayer init github-actions',\n vscodeExtension: 'intlayer init vscode-extension',\n eslint: 'intlayer init eslint',\n chromeExtension: 'intlayer init extension [--browser <chrome|firefox>]',\n skills: 'intlayer init skills [--platform <platform>] [--skills <skills...>]',\n mcp: 'intlayer init mcp [--platform <platform>] [--transport <stdio|sse>]',\n lsp: 'intlayer init lsp',\n cms: 'intlayer init cms',\n repository: 'intlayer init repository',\n infra: 'intlayer init infra --mode <desktop|docker|compose>',\n};\n\n/**\n * Maps setup steps to the {@link initIntlayer} options: every step handled by\n * `initIntlayer` that is not listed is skipped. Explicit `--no-*` flags in\n * `baseOptions` still win over a listed step.\n */\nexport const getInitOptionsForSteps = (\n steps: InitStep[],\n baseOptions?: InitOptions\n): InitOptions => ({\n ...baseOptions,\n noInstallPackages:\n baseOptions?.noInstallPackages || !steps.includes('packages'),\n // The `.gitignore` entry is tied to project setup.\n noGitignore: baseOptions?.noGitignore || !steps.includes('projectSetup'),\n noGithubActions:\n baseOptions?.noGithubActions || !steps.includes('githubActions'),\n noFrameworkSetup:\n baseOptions?.noFrameworkSetup || !steps.includes('projectSetup'),\n noVscodeExtension:\n baseOptions?.noVscodeExtension || !steps.includes('vscodeExtension'),\n noLsp: baseOptions?.noLsp || !steps.includes('lsp'),\n noEslint: baseOptions?.noEslint || !steps.includes('eslint'),\n // The documentation link only matters once the project itself is set up.\n skipFinalMessage:\n baseOptions?.skipFinalMessage || !steps.includes('projectSetup'),\n});\n\n/**\n * Runs the given {@link initIntlayer} steps only (packages, project setup, CI,\n * VS Code extension, LSP, lint rules). Backs `intlayer init` and its\n * single-step subcommands.\n */\nexport const initSteps = async (\n projectRoot: string | undefined,\n steps: InitStep[],\n options?: InitOptions\n): Promise<void> => {\n await initIntlayer(\n resolveProjectRoot(projectRoot),\n getInitOptionsForSteps(steps, options)\n );\n};\n\n/**\n * Logs in to the Intlayer CMS through the browser, then stores the access-key\n * credentials in `.env` and enables the editor in the config file.\n */\nexport const initCms = async (projectRoot?: string): Promise<void> => {\n const root = resolveProjectRoot(projectRoot);\n const p = await loadPrompts();\n\n p.log.info('Opening your browser to log in to the Intlayer CMS...');\n // `exitAfter: false` keeps the process alive so the flow can finish; the\n // credentials are persisted to `.env` and the editor enabled in the config.\n await login({\n exitAfter: false,\n onCredentials: (credentials) => setupCmsCredentials(root, credentials),\n });\n};\n\n/**\n * Connects the git repository of the project to its CMS project (and sets\n * its build settings) in the browser, pre-filled with the `origin` remote.\n */\nexport const initRepository = async (projectRoot?: string): Promise<void> => {\n const root = resolveProjectRoot(projectRoot);\n const p = await loadPrompts();\n const detectedRepository = detectGitRepository(root);\n\n p.log.info(\n detectedRepository\n ? `Detected repository: ${detectedRepository.provider} ${detectedRepository.owner}/${detectedRepository.repository}`\n : 'No supported git remote detected: pick the repository in the browser.'\n );\n p.log.info('Opening your browser to connect the repository...');\n\n await setupRepository({ detectedRepository });\n};\n\n/** Explains why `--interactive` cannot run, and what to run instead. */\nconst getNoTerminalMessage = (): string =>\n [\n '`intlayer init --interactive` needs a terminal to answer its prompts (none is attached, e.g. when run by an AI agent or in CI).',\n 'Run the steps directly instead:',\n ' intlayer init (installs the packages and sets up the framework)',\n ...Object.values(INIT_STEP_COMMANDS).map((command) => ` ${command}`),\n ].join('\\n');\n\n/** Grouped checkbox entries of the interactive init flow, in display order. */\nexport const INIT_STEP_GROUPS: Record<string, InitStepOption[]> = {\n Codebase: [\n {\n value: 'packages',\n label: 'Install & upgrade packages',\n hint: 'install missing Intlayer dependencies and upgrade outdated ones',\n },\n {\n value: 'projectSetup',\n label: 'Project setup',\n hint: 'intlayer config, tsconfig, bundler plugin, middleware/proxy and providers in layout/page',\n },\n {\n value: 'githubActions',\n label: 'CI/CD (GitHub Actions)',\n hint: 'scaffold the fill and test workflows that run on every pull request (covers every project in a monorepo)',\n },\n ],\n DevTools: [\n {\n value: 'vscodeExtension',\n label: 'VS Code extension',\n hint: 'recommend the Intlayer extension',\n },\n {\n value: 'eslint',\n label: 'Lint rules (ESLint / oxlint)',\n hint: 'flag hardcoded text and dynamic calls the compiler cannot optimize',\n },\n {\n value: 'chromeExtension',\n label: 'Chrome extension',\n hint: 'for audit, debug and analysis purpose',\n },\n ],\n 'Coding assistant': [\n {\n value: 'skills',\n label: 'AI skills',\n hint: 'install the Intlayer documentation as agent skills',\n },\n {\n value: 'mcp',\n label: 'MCP server',\n hint: 'configure the Intlayer MCP server',\n },\n {\n value: 'lsp',\n label: 'Editor LSP',\n hint: 'go-to-definition from keys to .content files',\n },\n ],\n CMS: [\n {\n value: 'cms',\n label: 'CMS',\n hint: 'log in through your browser, then store the credentials in your .env',\n },\n {\n value: 'repository',\n label: 'Git repository',\n hint: 'connect the git repository to the CMS project and set its build settings (GitHub, GitLab, Bitbucket, Codeberg, Gitee)',\n },\n {\n value: 'infra',\n label: 'Infrastructure (desktop app / self-hosting)',\n hint: 'install the desktop app, or self-host with Docker (all-in-one or Compose)',\n },\n ],\n};\n\n/**\n * Locale routing choice: a `routing.mode` served through the locale proxy, or\n * `none` to leave routing to the app (`no-prefix` with the proxy disabled).\n */\nexport type LocaleRoutingChoice = RoutingMode | 'none';\n\n/** Locale routing strategies offered by the interactive init flow. */\nconst ROUTING_OPTIONS: Array<{\n value: LocaleRoutingChoice;\n label: string;\n hint: string;\n}> = [\n {\n value: 'prefix-no-default',\n label: 'Prefix all except the default locale',\n hint: '/about, /fr/about (default)',\n },\n {\n value: 'prefix-all',\n label: 'Prefix all locales',\n hint: '/en/about, /fr/about',\n },\n {\n value: 'no-prefix',\n label: 'No locale in the URL',\n hint: '/about',\n },\n {\n value: 'search-params',\n label: 'Use a search parameter',\n hint: '/about?locale=fr',\n },\n {\n value: 'none',\n label: 'No locale routing',\n hint: 'no proxy, no locale route segment — handle the locale yourself',\n },\n];\n\n/** Locale routing choices accepted by `--routing`. */\nexport const LOCALE_ROUTING_CHOICES = ROUTING_OPTIONS.map(\n (option) => option.value\n);\n\n/** Validates a `--routing` value, throwing on an unknown choice. */\nexport const parseLocaleRoutingChoice = (value: string): LocaleRoutingChoice =>\n parseChoice(value, LOCALE_ROUTING_CHOICES, '--routing');\n\n/** Maps a routing choice to the init options it sets. */\nexport const getRoutingInitOptions = (\n choice: LocaleRoutingChoice\n): Pick<InitOptions, 'routingMode' | 'enableProxy'> =>\n choice === 'none'\n ? { routingMode: 'no-prefix', enableProxy: false }\n : { routingMode: choice, enableProxy: true };\n\n/** Content declaration layouts offered by the interactive init flow. */\nconst CONTENT_LAYOUT_OPTIONS: Array<{\n value: ContentLayout;\n label: string;\n hint: string;\n}> = [\n {\n value: 'multilingual',\n label: 'Multilingual content declaration',\n hint: '/**/{fileName}.content.{ts,json} — every locale in one file',\n },\n {\n value: 'per-locale',\n label: 'Per-locale content declaration',\n hint: '/**/{fileName}.{locale}.content.{ts,json}',\n },\n {\n value: 'centralized',\n label: 'Centralized',\n hint: '/locales/{locale}.{json,po}',\n },\n {\n value: 'namespaces',\n label: 'Namespaces',\n hint: '/locales/{locale}/{namespace}.{json,po}',\n },\n];\n\n/** Labels of the content formats offered by the interactive init flow. */\nconst CONTENT_FORMAT_LABELS: Record<ContentFormat, string> = {\n ts: 'TypeScript (.ts)',\n json: 'JSON (.json)',\n po: 'Gettext (.po)',\n};\n\n/** Labels of the JSON catalog message formats, in display order. */\nconst CATALOG_MESSAGE_FORMAT_OPTIONS: Record<\n CatalogMessageFormat,\n { label: string; hint: string }\n> = {\n icu: { label: 'ICU', hint: '{count, plural, one {# item} other {# items}}' },\n i18next: { label: 'i18next', hint: '{{count}} item, key_one / key_other' },\n 'vue-i18n': { label: 'vue-i18n', hint: '{count} item | {count} items' },\n intlayer: { label: 'Intlayer', hint: 'Intlayer dictionary content' },\n};\n\n/** Content layouts accepted by `--content`. */\nexport const CONTENT_LAYOUT_CHOICES = CONTENT_LAYOUT_OPTIONS.map(\n (option) => option.value\n);\n\n/** Validates a `--content` value, throwing on an unknown layout. */\nexport const parseContentLayoutChoice = (value: string): ContentLayout =>\n parseChoice(value, CONTENT_LAYOUT_CHOICES, '--content');\n\n/** Init options set by the content layout questions / flags. */\ntype ContentInitOptions = Pick<\n InitOptions,\n 'contentLayout' | 'contentFormat' | 'contentMessageFormat'\n>;\n\n/** Whether a layout + format pair produces a JSON catalog (sync plugin). */\nconst isJsonCatalog = (layout: ContentLayout, format: ContentFormat): boolean =>\n (layout === 'centralized' || layout === 'namespaces') && format === 'json';\n\n/**\n * Validates `--content` / `--content-format` / `--message-format` and maps\n * them to the init options. The format must be one the layout accepts, and the\n * message format only applies to JSON catalogs.\n */\nexport const getContentInitOptions = (\n layoutValue?: string,\n formatValue?: string,\n messageFormatValue?: string\n): ContentInitOptions => {\n if (!layoutValue) {\n if (formatValue || messageFormatValue) {\n throw new Error(\n '--content-format and --message-format require --content.'\n );\n }\n return {};\n }\n\n const contentLayout = parseContentLayoutChoice(layoutValue);\n const contentFormat = formatValue\n ? parseChoice(\n formatValue,\n CONTENT_FORMATS_BY_LAYOUT[contentLayout],\n '--content-format'\n )\n : undefined;\n\n if (!messageFormatValue) return { contentLayout, contentFormat };\n\n // Catalog layouts default to JSON.\n if (!isJsonCatalog(contentLayout, contentFormat ?? 'json')) {\n throw new Error(\n '--message-format only applies to JSON catalogs (--content centralized | namespaces with json).'\n );\n }\n\n return {\n contentLayout,\n contentFormat,\n contentMessageFormat: parseChoice(\n messageFormatValue,\n CATALOG_MESSAGE_FORMATS,\n '--message-format'\n ),\n };\n};\n\n/** Known ESLint and oxlint configuration file names. */\nexport const ESLINT_CONFIG_FILES = [\n 'eslint.config.js',\n 'eslint.config.mjs',\n 'eslint.config.cjs',\n 'eslint.config.ts',\n 'eslint.config.mts',\n 'eslint.config.cts',\n '.eslintrc.js',\n '.eslintrc.cjs',\n '.eslintrc.yaml',\n '.eslintrc.yml',\n '.eslintrc.json',\n '.eslintrc',\n '.oxlintrc.json',\n];\n\n/**\n * Checks whether ESLint or a compatible linter (oxlint) is installed or\n * configured in the project.\n */\nexport const isEslintInstalled = (\n dependencies: Record<string, string>,\n root?: string\n): boolean => {\n if (hasLintTooling(dependencies)) return true;\n if (dependencies.eslint || dependencies.oxlint) return true;\n\n if (root) {\n return ESLINT_CONFIG_FILES.some((file) => existsSync(join(root, file)));\n }\n\n return false;\n};\n\n/**\n * Computes the initial setup steps selected by default in the interactive prompt.\n * Steps like infrastructure and browser extension require opt-in, while\n * eslint is preselected only if installed on the project.\n */\nexport const getInitialInitSteps = (\n dependencies: Record<string, string>,\n root?: string\n): InitStep[] => {\n const isEslintPresent = isEslintInstalled(dependencies, root);\n\n return Object.values(INIT_STEP_GROUPS)\n .flat()\n .map((option) => option.value)\n .filter((step) => {\n if (OPT_IN_INIT_STEPS.includes(step)) return false;\n if (step === 'eslint' && !isEslintPresent) return false;\n return true;\n });\n};\n\n/** Reads the merged dependencies of the project at `root`. */\nexport const getProjectDependencies = (\n root: string\n): Record<string, string> => {\n try {\n const packageJsonPath = join(root, 'package.json');\n if (!existsSync(packageJsonPath)) return {};\n const { dependencies = {}, devDependencies = {} } = JSON.parse(\n readFileSync(packageJsonPath, 'utf-8')\n );\n return { ...dependencies, ...devDependencies };\n } catch {\n return {};\n }\n};\n\n/**\n * Runs `init` in interactive mode: prompts the user with a checkbox of setup\n * steps, then forwards the selection to {@link initIntlayer} (packages, GitHub\n * Actions, VS Code extension, LSP) and runs the dedicated skills/MCP installers\n * for the steps that own their own prompts. The `.gitignore` entry is not\n * offered as a checkbox — it is always added (unless `--no-gitignore` is set).\n */\nconst runInteractiveInit = async (\n root: string,\n baseOptions?: InitOptions\n): Promise<void> => {\n const p = await loadPrompts();\n\n p.intro('Initialize Intlayer');\n\n const projectDependencies = getProjectDependencies(root);\n\n const selected = await p.groupMultiselect<InitStep>({\n message: 'Select what you want to set up:',\n options: INIT_STEP_GROUPS,\n initialValues: getInitialInitSteps(projectDependencies, root),\n required: false,\n });\n\n if (p.isCancel(selected)) {\n p.cancel('Operation cancelled.');\n return;\n }\n\n const steps = selected as InitStep[];\n\n // Compat i18n library detection — never asked, always derived from the\n // installed packages. `detectMissingIntlayerPackages` reads the very same\n // dependency map to schedule the compat adapter, the sync plugin and the\n // catalog template, so a project without any of those packages is simply set\n // up on Intlayer alone.\n const detectedCompatLibraries =\n detectCompatI18nLibraries(projectDependencies);\n const hasCompatLib = detectedCompatLibraries.length > 0;\n\n if (hasCompatLib) {\n p.log.info(\n `Detected existing i18n ${detectedCompatLibraries.length > 1 ? 'libraries' : 'library'}: ${detectedCompatLibraries.join(', ')} — the matching Intlayer compat adapter and catalog sync will be set up.`\n );\n }\n\n // Locale routing → `routing.mode` + `routing.enableProxy` in the config, and\n // the route segment the framework scaffolding creates. Only asked for web\n // frontends; projects without URL routing (backend, React Native / Expo) keep\n // only `routing.storage` in a freshly created config. Skipped for compat\n // i18n libraries, which keep their own routing.\n let routingOptions: Pick<InitOptions, 'routingMode' | 'enableProxy'> = {};\n\n if (\n steps.includes('projectSetup') &&\n baseOptions?.routingMode === undefined &&\n !hasCompatLib &&\n hasUrlRoutingFramework(projectDependencies)\n ) {\n const selectedRouting = await p.select<LocaleRoutingChoice>({\n message: 'How should the locale appear in your URLs?',\n options: ROUTING_OPTIONS,\n initialValue: 'prefix-no-default',\n });\n\n if (p.isCancel(selectedRouting)) {\n p.cancel('Operation cancelled.');\n return;\n }\n\n routingOptions = getRoutingInitOptions(selectedRouting);\n }\n\n // Content layout → `compiler.output` (+ `dictionary.locale`) for `.content`\n // files, or the syncJSON / syncPO plugin for catalogs. Skipped for compat\n // i18n libraries, which keep their own catalogs.\n let contentOptions: ContentInitOptions = {};\n\n if (\n steps.includes('projectSetup') &&\n baseOptions?.contentLayout === undefined &&\n !hasCompatLib\n ) {\n const contentLayout = await p.select<ContentLayout>({\n message: 'How do you want to declare your content?',\n options: CONTENT_LAYOUT_OPTIONS,\n initialValue: 'multilingual',\n });\n\n if (p.isCancel(contentLayout)) {\n p.cancel('Operation cancelled.');\n return;\n }\n\n const acceptedFormats = CONTENT_FORMATS_BY_LAYOUT[contentLayout];\n const hasTypeScript = existsSync(join(root, 'tsconfig.json'));\n\n const contentFormat = await p.select<ContentFormat>({\n message: 'Which format for your content?',\n options: acceptedFormats.map((format) => ({\n value: format,\n label: CONTENT_FORMAT_LABELS[format],\n })),\n initialValue:\n acceptedFormats.includes('ts') && !hasTypeScript\n ? 'json'\n : acceptedFormats[0],\n });\n\n if (p.isCancel(contentFormat)) {\n p.cancel('Operation cancelled.');\n return;\n }\n\n contentOptions = { contentLayout, contentFormat };\n\n // JSON catalogs carry a message syntax (plurals, interpolation…).\n if (isJsonCatalog(contentLayout, contentFormat)) {\n const contentMessageFormat = await p.select<CatalogMessageFormat>({\n message: 'Which message format do your JSON catalogs use?',\n options: CATALOG_MESSAGE_FORMATS.map((messageFormat) => ({\n value: messageFormat,\n ...CATALOG_MESSAGE_FORMAT_OPTIONS[messageFormat],\n })),\n initialValue: CATALOG_MESSAGE_FORMATS[0],\n });\n\n if (p.isCancel(contentMessageFormat)) {\n p.cancel('Operation cancelled.');\n return;\n }\n\n contentOptions = { ...contentOptions, contentMessageFormat };\n }\n }\n\n const options: InitOptions = {\n ...getInitOptionsForSteps(steps, {\n ...baseOptions,\n ...routingOptions,\n ...contentOptions,\n }),\n skipFinalMessage: true,\n };\n\n const { guideUrl } = await initIntlayer(root, options);\n\n // Skills ask for the platform after the skill selection; MCP reuses it.\n const skillsPlatform = steps.includes('skills')\n ? await initSkills(root)\n : undefined;\n\n if (steps.includes('mcp')) {\n await initMCP(root, { platform: skillsPlatform });\n }\n\n // Delegated to the hosted install script, which owns its own menu.\n if (steps.includes('infra')) {\n await initInfra();\n }\n\n if (steps.includes('chromeExtension')) {\n await initChromeExtension();\n }\n\n // CMS / visual editor runs last: the browser login persists the access-key\n // credentials to `.env` and enables the editor in the config file. Kept last\n // so the browser flow does not interrupt setup.\n if (steps.includes('cms')) {\n await initCms(root);\n }\n\n // After the CMS login, so the browser is already signed in\n if (steps.includes('repository')) {\n await initRepository(root);\n }\n\n p.outro('Intlayer initialization complete');\n\n if (guideUrl) {\n logInitSuccessMessage(guideUrl);\n }\n};\n\n/**\n * `intlayer init`: installs the packages and sets up the framework, or, with\n * `interactive`, lets the user pick every setup step. `--interactive` needs a\n * terminal; without one it fails and lists the single-step commands instead.\n */\nexport const init = async (\n projectRoot?: string,\n options?: InitOptions,\n interactive?: boolean\n) => {\n if (!interactive) {\n await initSteps(projectRoot, DEFAULT_INIT_STEPS, options);\n return;\n }\n\n if (!isInteractiveTerminal()) {\n const p = await loadPrompts();\n\n p.log.error(getNoTerminalMessage());\n process.exitCode = 1;\n return;\n }\n\n await runInteractiveInit(resolveProjectRoot(projectRoot), options);\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"init.mjs","names":[],"sources":["../../src/init.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport {\n CATALOG_MESSAGE_FORMATS,\n type CatalogMessageFormat,\n CONTENT_FORMATS_BY_LAYOUT,\n type ContentFormat,\n type ContentLayout,\n detectCompatI18nLibraries,\n hasLintTooling,\n hasPrettier,\n hasUrlRoutingFramework,\n type InitOptions,\n initIntlayer,\n logInitSuccessMessage,\n PRETTIER_CONFIG_FILES,\n type RoutingMode,\n setupCmsCredentials,\n} from '@intlayer/engine/cli';\nimport { login } from './auth/login';\nimport { setupRepository } from './auth/setupRepository';\nimport { initChromeExtension } from './initChromeExtension';\nimport { initInfra } from './initInfra';\nimport { initMCP } from './initMCP';\nimport { initSkills } from './initSkills';\nimport { loadPrompts } from './loadPrompts';\nimport { detectGitRepository } from './utils/detectGitRepository';\nimport { isInteractiveTerminal } from './utils/isInteractiveTerminal';\nimport { parseChoice } from './utils/parseChoice';\n\nexport const findProjectRoot = (startDir: string) => {\n let currentDir = startDir;\n\n while (currentDir !== resolve(currentDir, '..')) {\n if (existsSync(join(currentDir, 'package.json'))) {\n return currentDir;\n }\n currentDir = resolve(currentDir, '..');\n }\n\n // If no package.json is found, return the start directory.\n // The initIntlayer function will handle the missing package.json error.\n return startDir;\n};\n\n/** Resolves the project root from `--project-root`, else the working directory. */\nexport const resolveProjectRoot = (projectRoot?: string): string =>\n findProjectRoot(projectRoot ? resolve(projectRoot) : process.cwd());\n\n/** Individually selectable setup steps exposed by the interactive init flow. */\nexport type InitStep =\n | 'packages'\n | 'githubActions'\n | 'projectSetup'\n | 'vscodeExtension'\n | 'lsp'\n | 'eslint'\n | 'prettier'\n | 'skills'\n | 'mcp'\n | 'infra'\n | 'cms'\n | 'repository'\n | 'chromeExtension';\n\n/** A checkbox entry of the interactive init flow. */\nexport type InitStepOption = {\n value: InitStep;\n label: string;\n hint: string;\n};\n\n/**\n * Steps shown but not pre-selected. The infrastructure installer touches the\n * machine (downloads an app, pulls Docker images) and browser extensions open external URLs\n * rather than modifying the project, so they must be an explicit choice.\n */\nexport const OPT_IN_INIT_STEPS: InitStep[] = ['infra', 'chromeExtension'];\n\n/**\n * Steps run by `intlayer init` without `--interactive`: install the packages\n * and set up the framework. Every other step has its own command\n * ({@link INIT_STEP_COMMANDS}), so nothing outside the app code is touched\n * unless asked for.\n */\nexport const DEFAULT_INIT_STEPS: InitStep[] = ['packages', 'projectSetup'];\n\n/**\n * Command running each interactive step on its own, without prompts when every\n * value is passed as a flag. Printed when `--interactive` has no terminal.\n */\nexport const INIT_STEP_COMMANDS: Record<InitStep, string> = {\n packages: 'intlayer init packages',\n projectSetup:\n 'intlayer init project [--routing <routing>] [--content <layout>] [--content-format <format>] [--message-format <format>]',\n githubActions: 'intlayer init github-actions',\n vscodeExtension: 'intlayer init vscode-extension',\n eslint: 'intlayer init eslint',\n prettier: 'intlayer init prettier',\n chromeExtension: 'intlayer init extension [--browser <chrome|firefox>]',\n skills: 'intlayer init skills [--platform <platform>] [--skills <skills...>]',\n mcp: 'intlayer init mcp [--platform <platform>] [--transport <stdio|sse>]',\n lsp: 'intlayer init lsp',\n cms: 'intlayer init cms',\n repository: 'intlayer init repository',\n infra: 'intlayer init infra --mode <desktop|docker|compose>',\n};\n\n/**\n * Maps setup steps to the {@link initIntlayer} options: every step handled by\n * `initIntlayer` that is not listed is skipped. Explicit `--no-*` flags in\n * `baseOptions` still win over a listed step.\n */\nexport const getInitOptionsForSteps = (\n steps: InitStep[],\n baseOptions?: InitOptions\n): InitOptions => ({\n ...baseOptions,\n noInstallPackages:\n baseOptions?.noInstallPackages || !steps.includes('packages'),\n // The `.gitignore` entry is tied to project setup.\n noGitignore: baseOptions?.noGitignore || !steps.includes('projectSetup'),\n noGithubActions:\n baseOptions?.noGithubActions || !steps.includes('githubActions'),\n noFrameworkSetup:\n baseOptions?.noFrameworkSetup || !steps.includes('projectSetup'),\n noVscodeExtension:\n baseOptions?.noVscodeExtension || !steps.includes('vscodeExtension'),\n noLsp: baseOptions?.noLsp || !steps.includes('lsp'),\n noEslint: baseOptions?.noEslint || !steps.includes('eslint'),\n noPrettier: baseOptions?.noPrettier || !steps.includes('prettier'),\n // The documentation link only matters once the project itself is set up.\n skipFinalMessage:\n baseOptions?.skipFinalMessage || !steps.includes('projectSetup'),\n});\n\n/**\n * Runs the given {@link initIntlayer} steps only (packages, project setup, CI,\n * VS Code extension, LSP, lint rules). Backs `intlayer init` and its\n * single-step subcommands.\n */\nexport const initSteps = async (\n projectRoot: string | undefined,\n steps: InitStep[],\n options?: InitOptions\n): Promise<void> => {\n await initIntlayer(\n resolveProjectRoot(projectRoot),\n getInitOptionsForSteps(steps, options)\n );\n};\n\n/**\n * Logs in to the Intlayer CMS through the browser, then stores the access-key\n * credentials in `.env` and enables the editor in the config file.\n */\nexport const initCms = async (projectRoot?: string): Promise<void> => {\n const root = resolveProjectRoot(projectRoot);\n const p = await loadPrompts();\n\n p.log.info('Opening your browser to log in to the Intlayer CMS...');\n // `exitAfter: false` keeps the process alive so the flow can finish; the\n // credentials are persisted to `.env` and the editor enabled in the config.\n await login({\n exitAfter: false,\n onCredentials: (credentials) => setupCmsCredentials(root, credentials),\n });\n};\n\n/**\n * Connects the git repository of the project to its CMS project (and sets\n * its build settings) in the browser, pre-filled with the `origin` remote.\n */\nexport const initRepository = async (projectRoot?: string): Promise<void> => {\n const root = resolveProjectRoot(projectRoot);\n const p = await loadPrompts();\n const detectedRepository = detectGitRepository(root);\n\n p.log.info(\n detectedRepository\n ? `Detected repository: ${detectedRepository.provider} ${detectedRepository.owner}/${detectedRepository.repository}`\n : 'No supported git remote detected: pick the repository in the browser.'\n );\n p.log.info('Opening your browser to connect the repository...');\n\n await setupRepository({ detectedRepository });\n};\n\n/** Explains why `--interactive` cannot run, and what to run instead. */\nconst getNoTerminalMessage = (): string =>\n [\n '`intlayer init --interactive` needs a terminal to answer its prompts (none is attached, e.g. when run by an AI agent or in CI).',\n 'Run the steps directly instead:',\n ' intlayer init (installs the packages and sets up the framework)',\n ...Object.values(INIT_STEP_COMMANDS).map((command) => ` ${command}`),\n ].join('\\n');\n\n/** Grouped checkbox entries of the interactive init flow, in display order. */\nexport const INIT_STEP_GROUPS: Record<string, InitStepOption[]> = {\n Codebase: [\n {\n value: 'packages',\n label: 'Install & upgrade packages',\n hint: 'install missing Intlayer dependencies and upgrade outdated ones',\n },\n {\n value: 'projectSetup',\n label: 'Project setup',\n hint: 'intlayer config, tsconfig, bundler plugin, middleware/proxy and providers in layout/page',\n },\n {\n value: 'githubActions',\n label: 'CI/CD (GitHub Actions)',\n hint: 'scaffold the fill and test workflows that run on every pull request (covers every project in a monorepo)',\n },\n ],\n DevTools: [\n {\n value: 'vscodeExtension',\n label: 'VS Code extension',\n hint: 'recommend the Intlayer extension',\n },\n {\n value: 'eslint',\n label: 'Lint rules (ESLint / oxlint)',\n hint: 'flag hardcoded text and dynamic calls the compiler cannot optimize',\n },\n {\n value: 'prettier',\n label: 'Prettier plugin',\n hint: 'sort the keys of t(), plural(), enu() and select() on format',\n },\n {\n value: 'chromeExtension',\n label: 'Chrome extension',\n hint: 'for audit, debug and analysis purpose',\n },\n ],\n 'Coding assistant': [\n {\n value: 'skills',\n label: 'AI skills',\n hint: 'install the Intlayer documentation as agent skills',\n },\n {\n value: 'mcp',\n label: 'MCP server',\n hint: 'configure the Intlayer MCP server',\n },\n {\n value: 'lsp',\n label: 'Editor LSP',\n hint: 'go-to-definition from keys to .content files',\n },\n ],\n CMS: [\n {\n value: 'cms',\n label: 'CMS',\n hint: 'log in through your browser, then store the credentials in your .env',\n },\n {\n value: 'repository',\n label: 'Git repository',\n hint: 'connect the git repository to the CMS project and set its build settings (GitHub, GitLab, Bitbucket, Codeberg, Gitee)',\n },\n {\n value: 'infra',\n label: 'Infrastructure (desktop app / self-hosting)',\n hint: 'install the desktop app, or self-host with Docker (all-in-one or Compose)',\n },\n ],\n};\n\n/**\n * Locale routing choice: a `routing.mode` served through the locale proxy, or\n * `none` to leave routing to the app (`no-prefix` with the proxy disabled).\n */\nexport type LocaleRoutingChoice = RoutingMode | 'none';\n\n/** Locale routing strategies offered by the interactive init flow. */\nconst ROUTING_OPTIONS: Array<{\n value: LocaleRoutingChoice;\n label: string;\n hint: string;\n}> = [\n {\n value: 'prefix-no-default',\n label: 'Prefix all except the default locale',\n hint: '/about, /fr/about (default)',\n },\n {\n value: 'prefix-all',\n label: 'Prefix all locales',\n hint: '/en/about, /fr/about',\n },\n {\n value: 'no-prefix',\n label: 'No locale in the URL',\n hint: '/about',\n },\n {\n value: 'search-params',\n label: 'Use a search parameter',\n hint: '/about?locale=fr',\n },\n {\n value: 'none',\n label: 'No locale routing',\n hint: 'no proxy, no locale route segment — handle the locale yourself',\n },\n];\n\n/** Locale routing choices accepted by `--routing`. */\nexport const LOCALE_ROUTING_CHOICES = ROUTING_OPTIONS.map(\n (option) => option.value\n);\n\n/** Validates a `--routing` value, throwing on an unknown choice. */\nexport const parseLocaleRoutingChoice = (value: string): LocaleRoutingChoice =>\n parseChoice(value, LOCALE_ROUTING_CHOICES, '--routing');\n\n/** Maps a routing choice to the init options it sets. */\nexport const getRoutingInitOptions = (\n choice: LocaleRoutingChoice\n): Pick<InitOptions, 'routingMode' | 'enableProxy'> =>\n choice === 'none'\n ? { routingMode: 'no-prefix', enableProxy: false }\n : { routingMode: choice, enableProxy: true };\n\n/** Content declaration layouts offered by the interactive init flow. */\nconst CONTENT_LAYOUT_OPTIONS: Array<{\n value: ContentLayout;\n label: string;\n hint: string;\n}> = [\n {\n value: 'multilingual',\n label: 'Multilingual content declaration',\n hint: '/**/{fileName}.content.{ts,json} — every locale in one file',\n },\n {\n value: 'per-locale',\n label: 'Per-locale content declaration',\n hint: '/**/{fileName}.{locale}.content.{ts,json}',\n },\n {\n value: 'centralized',\n label: 'Centralized',\n hint: '/locales/{locale}.{json,po}',\n },\n {\n value: 'namespaces',\n label: 'Namespaces',\n hint: '/locales/{locale}/{namespace}.{json,po}',\n },\n];\n\n/** Labels of the content formats offered by the interactive init flow. */\nconst CONTENT_FORMAT_LABELS: Record<ContentFormat, string> = {\n ts: 'TypeScript (.ts)',\n json: 'JSON (.json)',\n po: 'Gettext (.po)',\n};\n\n/** Labels of the JSON catalog message formats, in display order. */\nconst CATALOG_MESSAGE_FORMAT_OPTIONS: Record<\n CatalogMessageFormat,\n { label: string; hint: string }\n> = {\n icu: { label: 'ICU', hint: '{count, plural, one {# item} other {# items}}' },\n i18next: { label: 'i18next', hint: '{{count}} item, key_one / key_other' },\n 'vue-i18n': { label: 'vue-i18n', hint: '{count} item | {count} items' },\n intlayer: { label: 'Intlayer', hint: 'Intlayer dictionary content' },\n};\n\n/** Content layouts accepted by `--content`. */\nexport const CONTENT_LAYOUT_CHOICES = CONTENT_LAYOUT_OPTIONS.map(\n (option) => option.value\n);\n\n/** Validates a `--content` value, throwing on an unknown layout. */\nexport const parseContentLayoutChoice = (value: string): ContentLayout =>\n parseChoice(value, CONTENT_LAYOUT_CHOICES, '--content');\n\n/** Init options set by the content layout questions / flags. */\ntype ContentInitOptions = Pick<\n InitOptions,\n 'contentLayout' | 'contentFormat' | 'contentMessageFormat'\n>;\n\n/** Whether a layout + format pair produces a JSON catalog (sync plugin). */\nconst isJsonCatalog = (layout: ContentLayout, format: ContentFormat): boolean =>\n (layout === 'centralized' || layout === 'namespaces') && format === 'json';\n\n/**\n * Validates `--content` / `--content-format` / `--message-format` and maps\n * them to the init options. The format must be one the layout accepts, and the\n * message format only applies to JSON catalogs.\n */\nexport const getContentInitOptions = (\n layoutValue?: string,\n formatValue?: string,\n messageFormatValue?: string\n): ContentInitOptions => {\n if (!layoutValue) {\n if (formatValue || messageFormatValue) {\n throw new Error(\n '--content-format and --message-format require --content.'\n );\n }\n return {};\n }\n\n const contentLayout = parseContentLayoutChoice(layoutValue);\n const contentFormat = formatValue\n ? parseChoice(\n formatValue,\n CONTENT_FORMATS_BY_LAYOUT[contentLayout],\n '--content-format'\n )\n : undefined;\n\n if (!messageFormatValue) return { contentLayout, contentFormat };\n\n // Catalog layouts default to JSON.\n if (!isJsonCatalog(contentLayout, contentFormat ?? 'json')) {\n throw new Error(\n '--message-format only applies to JSON catalogs (--content centralized | namespaces with json).'\n );\n }\n\n return {\n contentLayout,\n contentFormat,\n contentMessageFormat: parseChoice(\n messageFormatValue,\n CATALOG_MESSAGE_FORMATS,\n '--message-format'\n ),\n };\n};\n\n/** Known ESLint and oxlint configuration file names. */\nexport const ESLINT_CONFIG_FILES = [\n 'eslint.config.js',\n 'eslint.config.mjs',\n 'eslint.config.cjs',\n 'eslint.config.ts',\n 'eslint.config.mts',\n 'eslint.config.cts',\n '.eslintrc.js',\n '.eslintrc.cjs',\n '.eslintrc.yaml',\n '.eslintrc.yml',\n '.eslintrc.json',\n '.eslintrc',\n '.oxlintrc.json',\n];\n\n/**\n * Checks whether ESLint or a compatible linter (oxlint) is installed or\n * configured in the project.\n */\nexport const isEslintInstalled = (\n dependencies: Record<string, string>,\n root?: string\n): boolean => {\n if (hasLintTooling(dependencies)) return true;\n if (dependencies.eslint || dependencies.oxlint) return true;\n\n if (root) {\n return ESLINT_CONFIG_FILES.some((file) => existsSync(join(root, file)));\n }\n\n return false;\n};\n\n/**\n * Checks whether Prettier is installed or configured in the project.\n */\nexport const isPrettierInstalled = (\n dependencies: Record<string, string>,\n root?: string\n): boolean => {\n if (hasPrettier(dependencies)) return true;\n\n if (root) {\n return PRETTIER_CONFIG_FILES.some((file) => existsSync(join(root, file)));\n }\n\n return false;\n};\n\n/**\n * Computes the initial setup steps selected by default in the interactive prompt.\n * Steps like infrastructure and browser extension require opt-in, while\n * eslint is preselected only if installed on the project.\n */\nexport const getInitialInitSteps = (\n dependencies: Record<string, string>,\n root?: string\n): InitStep[] => {\n const isEslintPresent = isEslintInstalled(dependencies, root);\n const isPrettierPresent = isPrettierInstalled(dependencies, root);\n\n return Object.values(INIT_STEP_GROUPS)\n .flat()\n .map((option) => option.value)\n .filter((step) => {\n if (OPT_IN_INIT_STEPS.includes(step)) return false;\n if (step === 'eslint' && !isEslintPresent) return false;\n if (step === 'prettier' && !isPrettierPresent) return false;\n return true;\n });\n};\n\n/** Reads the merged dependencies of the project at `root`. */\nexport const getProjectDependencies = (\n root: string\n): Record<string, string> => {\n try {\n const packageJsonPath = join(root, 'package.json');\n if (!existsSync(packageJsonPath)) return {};\n const { dependencies = {}, devDependencies = {} } = JSON.parse(\n readFileSync(packageJsonPath, 'utf-8')\n );\n return { ...dependencies, ...devDependencies };\n } catch {\n return {};\n }\n};\n\n/**\n * Runs `init` in interactive mode: prompts the user with a checkbox of setup\n * steps, then forwards the selection to {@link initIntlayer} (packages, GitHub\n * Actions, VS Code extension, LSP) and runs the dedicated skills/MCP installers\n * for the steps that own their own prompts. The `.gitignore` entry is not\n * offered as a checkbox — it is always added (unless `--no-gitignore` is set).\n */\nconst runInteractiveInit = async (\n root: string,\n baseOptions?: InitOptions\n): Promise<void> => {\n const p = await loadPrompts();\n\n p.intro('Initialize Intlayer');\n\n const projectDependencies = getProjectDependencies(root);\n\n const selected = await p.groupMultiselect<InitStep>({\n message: 'Select what you want to set up:',\n options: INIT_STEP_GROUPS,\n initialValues: getInitialInitSteps(projectDependencies, root),\n required: false,\n });\n\n if (p.isCancel(selected)) {\n p.cancel('Operation cancelled.');\n return;\n }\n\n const steps = selected as InitStep[];\n\n // Compat i18n library detection — never asked, always derived from the\n // installed packages. `detectMissingIntlayerPackages` reads the very same\n // dependency map to schedule the compat adapter, the sync plugin and the\n // catalog template, so a project without any of those packages is simply set\n // up on Intlayer alone.\n const detectedCompatLibraries =\n detectCompatI18nLibraries(projectDependencies);\n const hasCompatLib = detectedCompatLibraries.length > 0;\n\n if (hasCompatLib) {\n p.log.info(\n `Detected existing i18n ${detectedCompatLibraries.length > 1 ? 'libraries' : 'library'}: ${detectedCompatLibraries.join(', ')} — the matching Intlayer compat adapter and catalog sync will be set up.`\n );\n }\n\n // Locale routing → `routing.mode` + `routing.enableProxy` in the config, and\n // the route segment the framework scaffolding creates. Only asked for web\n // frontends; projects without URL routing (backend, React Native / Expo) keep\n // only `routing.storage` in a freshly created config. Skipped for compat\n // i18n libraries, which keep their own routing.\n let routingOptions: Pick<InitOptions, 'routingMode' | 'enableProxy'> = {};\n\n if (\n steps.includes('projectSetup') &&\n baseOptions?.routingMode === undefined &&\n !hasCompatLib &&\n hasUrlRoutingFramework(projectDependencies)\n ) {\n const selectedRouting = await p.select<LocaleRoutingChoice>({\n message: 'How should the locale appear in your URLs?',\n options: ROUTING_OPTIONS,\n initialValue: 'prefix-no-default',\n });\n\n if (p.isCancel(selectedRouting)) {\n p.cancel('Operation cancelled.');\n return;\n }\n\n routingOptions = getRoutingInitOptions(selectedRouting);\n }\n\n // Content layout → `compiler.output` (+ `dictionary.locale`) for `.content`\n // files, or the syncJSON / syncPO plugin for catalogs. Skipped for compat\n // i18n libraries, which keep their own catalogs.\n let contentOptions: ContentInitOptions = {};\n\n if (\n steps.includes('projectSetup') &&\n baseOptions?.contentLayout === undefined &&\n !hasCompatLib\n ) {\n const contentLayout = await p.select<ContentLayout>({\n message: 'How do you want to declare your content?',\n options: CONTENT_LAYOUT_OPTIONS,\n initialValue: 'multilingual',\n });\n\n if (p.isCancel(contentLayout)) {\n p.cancel('Operation cancelled.');\n return;\n }\n\n const acceptedFormats = CONTENT_FORMATS_BY_LAYOUT[contentLayout];\n const hasTypeScript = existsSync(join(root, 'tsconfig.json'));\n\n const contentFormat = await p.select<ContentFormat>({\n message: 'Which format for your content?',\n options: acceptedFormats.map((format) => ({\n value: format,\n label: CONTENT_FORMAT_LABELS[format],\n })),\n initialValue:\n acceptedFormats.includes('ts') && !hasTypeScript\n ? 'json'\n : acceptedFormats[0],\n });\n\n if (p.isCancel(contentFormat)) {\n p.cancel('Operation cancelled.');\n return;\n }\n\n contentOptions = { contentLayout, contentFormat };\n\n // JSON catalogs carry a message syntax (plurals, interpolation…).\n if (isJsonCatalog(contentLayout, contentFormat)) {\n const contentMessageFormat = await p.select<CatalogMessageFormat>({\n message: 'Which message format do your JSON catalogs use?',\n options: CATALOG_MESSAGE_FORMATS.map((messageFormat) => ({\n value: messageFormat,\n ...CATALOG_MESSAGE_FORMAT_OPTIONS[messageFormat],\n })),\n initialValue: CATALOG_MESSAGE_FORMATS[0],\n });\n\n if (p.isCancel(contentMessageFormat)) {\n p.cancel('Operation cancelled.');\n return;\n }\n\n contentOptions = { ...contentOptions, contentMessageFormat };\n }\n }\n\n const options: InitOptions = {\n ...getInitOptionsForSteps(steps, {\n ...baseOptions,\n ...routingOptions,\n ...contentOptions,\n }),\n skipFinalMessage: true,\n };\n\n const { guideUrl } = await initIntlayer(root, options);\n\n // Skills ask for the platform after the skill selection; MCP reuses it.\n const skillsPlatform = steps.includes('skills')\n ? await initSkills(root)\n : undefined;\n\n if (steps.includes('mcp')) {\n await initMCP(root, { platform: skillsPlatform });\n }\n\n // Delegated to the hosted install script, which owns its own menu.\n if (steps.includes('infra')) {\n await initInfra();\n }\n\n if (steps.includes('chromeExtension')) {\n await initChromeExtension();\n }\n\n // CMS / visual editor runs last: the browser login persists the access-key\n // credentials to `.env` and enables the editor in the config file. Kept last\n // so the browser flow does not interrupt setup.\n if (steps.includes('cms')) {\n await initCms(root);\n }\n\n // After the CMS login, so the browser is already signed in\n if (steps.includes('repository')) {\n await initRepository(root);\n }\n\n p.outro('Intlayer initialization complete');\n\n if (guideUrl) {\n logInitSuccessMessage(guideUrl);\n }\n};\n\n/**\n * `intlayer init`: installs the packages and sets up the framework, or, with\n * `interactive`, lets the user pick every setup step. `--interactive` needs a\n * terminal; without one it fails and lists the single-step commands instead.\n */\nexport const init = async (\n projectRoot?: string,\n options?: InitOptions,\n interactive?: boolean\n) => {\n if (!interactive) {\n await initSteps(projectRoot, DEFAULT_INIT_STEPS, options);\n return;\n }\n\n if (!isInteractiveTerminal()) {\n const p = await loadPrompts();\n\n p.log.error(getNoTerminalMessage());\n process.exitCode = 1;\n return;\n }\n\n await runInteractiveInit(resolveProjectRoot(projectRoot), options);\n};\n"],"mappings":";;;;;;;;;;;;;;;AA8BA,MAAa,mBAAmB,aAAqB;CACnD,IAAI,aAAa;CAEjB,OAAO,eAAe,QAAQ,YAAY,IAAI,GAAG;EAC/C,IAAI,WAAW,KAAK,YAAY,cAAc,CAAC,GAC7C,OAAO;EAET,aAAa,QAAQ,YAAY,IAAI;CACvC;CAIA,OAAO;AACT;;AAGA,MAAa,sBAAsB,gBACjC,gBAAgB,cAAc,QAAQ,WAAW,IAAI,QAAQ,IAAI,CAAC;;;;;;AA8BpE,MAAa,oBAAgC,CAAC,SAAS,iBAAiB;;;;;;;AAQxE,MAAa,qBAAiC,CAAC,YAAY,cAAc;;;;;AAMzE,MAAa,qBAA+C;CAC1D,UAAU;CACV,cACE;CACF,eAAe;CACf,iBAAiB;CACjB,QAAQ;CACR,UAAU;CACV,iBAAiB;CACjB,QAAQ;CACR,KAAK;CACL,KAAK;CACL,KAAK;CACL,YAAY;CACZ,OAAO;AACT;;;;;;AAOA,MAAa,0BACX,OACA,iBACiB;CACjB,GAAG;CACH,mBACE,aAAa,qBAAqB,CAAC,MAAM,SAAS,UAAU;CAE9D,aAAa,aAAa,eAAe,CAAC,MAAM,SAAS,cAAc;CACvE,iBACE,aAAa,mBAAmB,CAAC,MAAM,SAAS,eAAe;CACjE,kBACE,aAAa,oBAAoB,CAAC,MAAM,SAAS,cAAc;CACjE,mBACE,aAAa,qBAAqB,CAAC,MAAM,SAAS,iBAAiB;CACrE,OAAO,aAAa,SAAS,CAAC,MAAM,SAAS,KAAK;CAClD,UAAU,aAAa,YAAY,CAAC,MAAM,SAAS,QAAQ;CAC3D,YAAY,aAAa,cAAc,CAAC,MAAM,SAAS,UAAU;CAEjE,kBACE,aAAa,oBAAoB,CAAC,MAAM,SAAS,cAAc;AACnE;;;;;;AAOA,MAAa,YAAY,OACvB,aACA,OACA,YACkB;CAClB,MAAM,aACJ,mBAAmB,WAAW,GAC9B,uBAAuB,OAAO,OAAO,CACvC;AACF;;;;;AAMA,MAAa,UAAU,OAAO,gBAAwC;CACpE,MAAM,OAAO,mBAAmB,WAAW;CAG3C,OAFgB,YAAY,EAE3B,CAAC,IAAI,KAAK,uDAAuD;CAGlE,MAAM,MAAM;EACV,WAAW;EACX,gBAAgB,gBAAgB,oBAAoB,MAAM,WAAW;CACvE,CAAC;AACH;;;;;AAMA,MAAa,iBAAiB,OAAO,gBAAwC;CAC3E,MAAM,OAAO,mBAAmB,WAAW;CAC3C,MAAM,IAAI,MAAM,YAAY;CAC5B,MAAM,qBAAqB,oBAAoB,IAAI;CAEnD,EAAE,IAAI,KACJ,qBACI,wBAAwB,mBAAmB,SAAS,GAAG,mBAAmB,MAAM,GAAG,mBAAmB,eACtG,uEACN;CACA,EAAE,IAAI,KAAK,mDAAmD;CAE9D,MAAM,gBAAgB,EAAE,mBAAmB,CAAC;AAC9C;;AAGA,MAAM,6BACJ;CACE;CACA;CACA;CACA,GAAG,OAAO,OAAO,kBAAkB,CAAC,CAAC,KAAK,YAAY,KAAK,SAAS;AACtE,CAAC,CAAC,KAAK,IAAI;;AAGb,MAAa,mBAAqD;CAChE,UAAU;EACR;GACE,OAAO;GACP,OAAO;GACP,MAAM;EACR;EACA;GACE,OAAO;GACP,OAAO;GACP,MAAM;EACR;EACA;GACE,OAAO;GACP,OAAO;GACP,MAAM;EACR;CACF;CACA,UAAU;EACR;GACE,OAAO;GACP,OAAO;GACP,MAAM;EACR;EACA;GACE,OAAO;GACP,OAAO;GACP,MAAM;EACR;EACA;GACE,OAAO;GACP,OAAO;GACP,MAAM;EACR;EACA;GACE,OAAO;GACP,OAAO;GACP,MAAM;EACR;CACF;CACA,oBAAoB;EAClB;GACE,OAAO;GACP,OAAO;GACP,MAAM;EACR;EACA;GACE,OAAO;GACP,OAAO;GACP,MAAM;EACR;EACA;GACE,OAAO;GACP,OAAO;GACP,MAAM;EACR;CACF;CACA,KAAK;EACH;GACE,OAAO;GACP,OAAO;GACP,MAAM;EACR;EACA;GACE,OAAO;GACP,OAAO;GACP,MAAM;EACR;EACA;GACE,OAAO;GACP,OAAO;GACP,MAAM;EACR;CACF;AACF;;AASA,MAAM,kBAID;CACH;EACE,OAAO;EACP,OAAO;EACP,MAAM;CACR;CACA;EACE,OAAO;EACP,OAAO;EACP,MAAM;CACR;CACA;EACE,OAAO;EACP,OAAO;EACP,MAAM;CACR;CACA;EACE,OAAO;EACP,OAAO;EACP,MAAM;CACR;CACA;EACE,OAAO;EACP,OAAO;EACP,MAAM;CACR;AACF;;AAGA,MAAa,yBAAyB,gBAAgB,KACnD,WAAW,OAAO,KACrB;;AAGA,MAAa,4BAA4B,UACvC,YAAY,OAAO,wBAAwB,WAAW;;AAGxD,MAAa,yBACX,WAEA,WAAW,SACP;CAAE,aAAa;CAAa,aAAa;AAAM,IAC/C;CAAE,aAAa;CAAQ,aAAa;AAAK;;AAG/C,MAAM,yBAID;CACH;EACE,OAAO;EACP,OAAO;EACP,MAAM;CACR;CACA;EACE,OAAO;EACP,OAAO;EACP,MAAM;CACR;CACA;EACE,OAAO;EACP,OAAO;EACP,MAAM;CACR;CACA;EACE,OAAO;EACP,OAAO;EACP,MAAM;CACR;AACF;;AAGA,MAAM,wBAAuD;CAC3D,IAAI;CACJ,MAAM;CACN,IAAI;AACN;;AAGA,MAAM,iCAGF;CACF,KAAK;EAAE,OAAO;EAAO,MAAM;CAAgD;CAC3E,SAAS;EAAE,OAAO;EAAW,MAAM;CAAsC;CACzE,YAAY;EAAE,OAAO;EAAY,MAAM;CAA+B;CACtE,UAAU;EAAE,OAAO;EAAY,MAAM;CAA8B;AACrE;;AAGA,MAAa,yBAAyB,uBAAuB,KAC1D,WAAW,OAAO,KACrB;;AAGA,MAAa,4BAA4B,UACvC,YAAY,OAAO,wBAAwB,WAAW;;AASxD,MAAM,iBAAiB,QAAuB,YAC3C,WAAW,iBAAiB,WAAW,iBAAiB,WAAW;;;;;;AAOtE,MAAa,yBACX,aACA,aACA,uBACuB;CACvB,IAAI,CAAC,aAAa;EAChB,IAAI,eAAe,oBACjB,MAAM,IAAI,MACR,0DACF;EAEF,OAAO,CAAC;CACV;CAEA,MAAM,gBAAgB,yBAAyB,WAAW;CAC1D,MAAM,gBAAgB,cAClB,YACE,aACA,0BAA0B,gBAC1B,kBACF,IACA;CAEJ,IAAI,CAAC,oBAAoB,OAAO;EAAE;EAAe;CAAc;CAG/D,IAAI,CAAC,cAAc,eAAe,iBAAiB,MAAM,GACvD,MAAM,IAAI,MACR,gGACF;CAGF,OAAO;EACL;EACA;EACA,sBAAsB,YACpB,oBACA,yBACA,kBACF;CACF;AACF;;AAGA,MAAa,sBAAsB;CACjC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;;;;AAMA,MAAa,qBACX,cACA,SACY;CACZ,IAAI,eAAe,YAAY,GAAG,OAAO;CACzC,IAAI,aAAa,UAAU,aAAa,QAAQ,OAAO;CAEvD,IAAI,MACF,OAAO,oBAAoB,MAAM,SAAS,WAAW,KAAK,MAAM,IAAI,CAAC,CAAC;CAGxE,OAAO;AACT;;;;AAKA,MAAa,uBACX,cACA,SACY;CACZ,IAAI,YAAY,YAAY,GAAG,OAAO;CAEtC,IAAI,MACF,OAAO,sBAAsB,MAAM,SAAS,WAAW,KAAK,MAAM,IAAI,CAAC,CAAC;CAG1E,OAAO;AACT;;;;;;AAOA,MAAa,uBACX,cACA,SACe;CACf,MAAM,kBAAkB,kBAAkB,cAAc,IAAI;CAC5D,MAAM,oBAAoB,oBAAoB,cAAc,IAAI;CAEhE,OAAO,OAAO,OAAO,gBAAgB,CAAC,CACnC,KAAK,CAAC,CACN,KAAK,WAAW,OAAO,KAAK,CAAC,CAC7B,QAAQ,SAAS;EAChB,IAAI,kBAAkB,SAAS,IAAI,GAAG,OAAO;EAC7C,IAAI,SAAS,YAAY,CAAC,iBAAiB,OAAO;EAClD,IAAI,SAAS,cAAc,CAAC,mBAAmB,OAAO;EACtD,OAAO;CACT,CAAC;AACL;;AAGA,MAAa,0BACX,SAC2B;CAC3B,IAAI;EACF,MAAM,kBAAkB,KAAK,MAAM,cAAc;EACjD,IAAI,CAAC,WAAW,eAAe,GAAG,OAAO,CAAC;EAC1C,MAAM,EAAE,eAAe,CAAC,GAAG,kBAAkB,CAAC,MAAM,KAAK,MACvD,aAAa,iBAAiB,OAAO,CACvC;EACA,OAAO;GAAE,GAAG;GAAc,GAAG;EAAgB;CAC/C,QAAQ;EACN,OAAO,CAAC;CACV;AACF;;;;;;;;AASA,MAAM,qBAAqB,OACzB,MACA,gBACkB;CAClB,MAAM,IAAI,MAAM,YAAY;CAE5B,EAAE,MAAM,qBAAqB;CAE7B,MAAM,sBAAsB,uBAAuB,IAAI;CAEvD,MAAM,WAAW,MAAM,EAAE,iBAA2B;EAClD,SAAS;EACT,SAAS;EACT,eAAe,oBAAoB,qBAAqB,IAAI;EAC5D,UAAU;CACZ,CAAC;CAED,IAAI,EAAE,SAAS,QAAQ,GAAG;EACxB,EAAE,OAAO,sBAAsB;EAC/B;CACF;CAEA,MAAM,QAAQ;CAOd,MAAM,0BACJ,0BAA0B,mBAAmB;CAC/C,MAAM,eAAe,wBAAwB,SAAS;CAEtD,IAAI,cACF,EAAE,IAAI,KACJ,0BAA0B,wBAAwB,SAAS,IAAI,cAAc,UAAU,IAAI,wBAAwB,KAAK,IAAI,EAAE,yEAChI;CAQF,IAAI,iBAAmE,CAAC;CAExE,IACE,MAAM,SAAS,cAAc,KAC7B,aAAa,gBAAgB,UAC7B,CAAC,gBACD,uBAAuB,mBAAmB,GAC1C;EACA,MAAM,kBAAkB,MAAM,EAAE,OAA4B;GAC1D,SAAS;GACT,SAAS;GACT,cAAc;EAChB,CAAC;EAED,IAAI,EAAE,SAAS,eAAe,GAAG;GAC/B,EAAE,OAAO,sBAAsB;GAC/B;EACF;EAEA,iBAAiB,sBAAsB,eAAe;CACxD;CAKA,IAAI,iBAAqC,CAAC;CAE1C,IACE,MAAM,SAAS,cAAc,KAC7B,aAAa,kBAAkB,UAC/B,CAAC,cACD;EACA,MAAM,gBAAgB,MAAM,EAAE,OAAsB;GAClD,SAAS;GACT,SAAS;GACT,cAAc;EAChB,CAAC;EAED,IAAI,EAAE,SAAS,aAAa,GAAG;GAC7B,EAAE,OAAO,sBAAsB;GAC/B;EACF;EAEA,MAAM,kBAAkB,0BAA0B;EAClD,MAAM,gBAAgB,WAAW,KAAK,MAAM,eAAe,CAAC;EAE5D,MAAM,gBAAgB,MAAM,EAAE,OAAsB;GAClD,SAAS;GACT,SAAS,gBAAgB,KAAK,YAAY;IACxC,OAAO;IACP,OAAO,sBAAsB;GAC/B,EAAE;GACF,cACE,gBAAgB,SAAS,IAAI,KAAK,CAAC,gBAC/B,SACA,gBAAgB;EACxB,CAAC;EAED,IAAI,EAAE,SAAS,aAAa,GAAG;GAC7B,EAAE,OAAO,sBAAsB;GAC/B;EACF;EAEA,iBAAiB;GAAE;GAAe;EAAc;EAGhD,IAAI,cAAc,eAAe,aAAa,GAAG;GAC/C,MAAM,uBAAuB,MAAM,EAAE,OAA6B;IAChE,SAAS;IACT,SAAS,wBAAwB,KAAK,mBAAmB;KACvD,OAAO;KACP,GAAG,+BAA+B;IACpC,EAAE;IACF,cAAc,wBAAwB;GACxC,CAAC;GAED,IAAI,EAAE,SAAS,oBAAoB,GAAG;IACpC,EAAE,OAAO,sBAAsB;IAC/B;GACF;GAEA,iBAAiB;IAAE,GAAG;IAAgB;GAAqB;EAC7D;CACF;CAEA,MAAM,UAAuB;EAC3B,GAAG,uBAAuB,OAAO;GAC/B,GAAG;GACH,GAAG;GACH,GAAG;EACL,CAAC;EACD,kBAAkB;CACpB;CAEA,MAAM,EAAE,aAAa,MAAM,aAAa,MAAM,OAAO;CAGrD,MAAM,iBAAiB,MAAM,SAAS,QAAQ,IAC1C,MAAM,WAAW,IAAI,IACrB;CAEJ,IAAI,MAAM,SAAS,KAAK,GACtB,MAAM,QAAQ,MAAM,EAAE,UAAU,eAAe,CAAC;CAIlD,IAAI,MAAM,SAAS,OAAO,GACxB,MAAM,UAAU;CAGlB,IAAI,MAAM,SAAS,iBAAiB,GAClC,MAAM,oBAAoB;CAM5B,IAAI,MAAM,SAAS,KAAK,GACtB,MAAM,QAAQ,IAAI;CAIpB,IAAI,MAAM,SAAS,YAAY,GAC7B,MAAM,eAAe,IAAI;CAG3B,EAAE,MAAM,kCAAkC;CAE1C,IAAI,UACF,sBAAsB,QAAQ;AAElC;;;;;;AAOA,MAAa,OAAO,OAClB,aACA,SACA,gBACG;CACH,IAAI,CAAC,aAAa;EAChB,MAAM,UAAU,aAAa,oBAAoB,OAAO;EACxD;CACF;CAEA,IAAI,CAAC,sBAAsB,GAAG;EAG5B,OAFgB,YAAY,EAE3B,CAAC,IAAI,MAAM,qBAAqB,CAAC;EAClC,QAAQ,WAAW;EACnB;CACF;CAEA,MAAM,mBAAmB,mBAAmB,WAAW,GAAG,OAAO;AACnE"}
@@ -2,8 +2,8 @@ import { loadPrompts } from "./loadPrompts.mjs";
2
2
  import { isInteractiveTerminal } from "./utils/isInteractiveTerminal.mjs";
3
3
  import { join } from "node:path";
4
4
  import { spawn } from "node:child_process";
5
- import { tmpdir } from "node:os";
6
5
  import { mkdtemp, rm, writeFile } from "node:fs/promises";
6
+ import { tmpdir } from "node:os";
7
7
 
8
8
  // src/initInfra.ts
9
9
  /** Setup modes understood by the hosted installer. */
@@ -1,9 +1,9 @@
1
+ import { relative } from "node:path";
1
2
  import { colon, colorizeKey, colorizeNumber, getAppLogger } from "@intlayer/config/logger";
2
3
  import { getConfiguration } from "@intlayer/config/node";
4
+ import { getUnmergedDictionaries } from "@intlayer/dictionaries-entry/unmerged";
3
5
  import { prepareIntlayer } from "@intlayer/engine/cli";
4
- import { relative } from "node:path";
5
6
  import { formatPath } from "@intlayer/engine/utils";
6
- import { getUnmergedDictionaries } from "@intlayer/dictionaries-entry/unmerged";
7
7
 
8
8
  // src/listContentDeclaration.ts
9
9
  const listContentDeclarationRows = async (options) => {
@@ -1,5 +1,5 @@
1
- import { listProjects } from "@intlayer/engine/cli";
2
1
  import { relative } from "node:path";
2
+ import { listProjects } from "@intlayer/engine/cli";
3
3
 
4
4
  // src/listProjects.ts
5
5
  const listProjectsCommand = async (options) => {
@@ -2,13 +2,13 @@ import { IntlayerEventListener } from "./IntlayerEventListener.mjs";
2
2
  import * as ANSIColors from "@intlayer/config/colors";
3
3
  import { colorize, getAppLogger } from "@intlayer/config/logger";
4
4
  import { getConfiguration } from "@intlayer/config/node";
5
- import { createServer } from "node:http";
6
- import { runParallel } from "@intlayer/engine/utils";
7
5
  import { getUnmergedDictionaries } from "@intlayer/dictionaries-entry/unmerged";
8
6
  import { buildDictionary } from "@intlayer/engine/build";
7
+ import { runParallel } from "@intlayer/engine/utils";
8
+ import { createServer } from "node:http";
9
+ import packageJson from "@intlayer/config/package.json" with { type: "json" };
9
10
  import { getLocalizedContent } from "@intlayer/core/plugins";
10
11
  import { getDictionaries } from "@intlayer/dictionaries-entry";
11
- import packageJson from "@intlayer/config/package.json" with { type: "json" };
12
12
 
13
13
  // src/liveSync.ts
14
14
  const writeDictionary = async (dictionary, configuration) => {
package/dist/esm/pull.mjs CHANGED
@@ -1,27 +1,29 @@
1
1
  import { PullLogger } from "./push/pullLog.mjs";
2
2
  import { checkCMSAuth, getAuthenticatedAPI } from "./utils/checkAccess.mjs";
3
3
  import { selectCmsEnvironment } from "./utils/selectCmsEnvironment.mjs";
4
+ import { existsSync } from "node:fs";
5
+ import { join } from "node:path";
4
6
  import * as ANSIColors from "@intlayer/config/colors";
5
7
  import { getAppLogger } from "@intlayer/config/logger";
6
8
  import { getConfiguration } from "@intlayer/config/node";
7
- import { logConfigDetails } from "@intlayer/engine/cli";
8
- import { join } from "node:path";
9
- import { existsSync } from "node:fs";
10
- import { getProjectRequire, parallelize } from "@intlayer/config/utils";
11
9
  import { getUnmergedDictionaries } from "@intlayer/dictionaries-entry/unmerged";
12
10
  import { writeContentDeclaration } from "@intlayer/engine/build";
11
+ import { logConfigDetails } from "@intlayer/engine/cli";
12
+ import { getProjectRequire, parallelize } from "@intlayer/config/utils";
13
13
 
14
14
  // src/pull.ts
15
15
  /**
16
16
  * Fetch distant dictionaries and write them locally,
17
17
  * with progress indicators and concurrency control.
18
+ *
19
+ * @returns The outcome of each pulled dictionary (empty when nothing ran).
18
20
  */
19
21
  const pull = async (options) => {
20
22
  const config = getConfiguration(options?.configOptions);
21
23
  const appLogger = getAppLogger(config);
22
24
  try {
23
25
  logConfigDetails(options?.configOptions);
24
- if (!await checkCMSAuth(config)) return;
26
+ if (!await checkCMSAuth(config)) return [];
25
27
  const intlayerAPI = await getAuthenticatedAPI(config);
26
28
  await selectCmsEnvironment(options?.configOptions?.env, intlayerAPI, config);
27
29
  const unmergedDictionariesRecord = getUnmergedDictionaries(config);
@@ -55,7 +57,7 @@ const pull = async (options) => {
55
57
  }).map(([key]) => key);
56
58
  if (entries.length === 0) {
57
59
  appLogger("No dictionaries to fetch", { level: "error" });
58
- return;
60
+ return [];
59
61
  }
60
62
  appLogger("Fetching dictionaries:");
61
63
  const dictionariesStatuses = [...cachedKeys.map((dictionaryKey) => ({
@@ -92,8 +94,9 @@ const pull = async (options) => {
92
94
  filePath: localAndRemoteDictionary.filePath,
93
95
  localId: localAndRemoteDictionary.localId
94
96
  };
95
- const { status } = await writeContentDeclaration(sourceDictionary, config, options);
97
+ const { status, path } = await writeContentDeclaration(sourceDictionary, config, options);
96
98
  statusObj.status = status;
99
+ statusObj.filePath = path;
97
100
  logger.update([{
98
101
  dictionaryKey: statusObj.dictionaryKey,
99
102
  status
@@ -141,8 +144,14 @@ const pull = async (options) => {
141
144
  appLogger(` - ${s.dictionaryKey} ${ANSIColors.GREY}[${color}${icon} ${s.status}${ANSIColors.GREY}]${ANSIColors.RESET}`);
142
145
  }
143
146
  for (const statusObj of dictionariesStatuses) if (statusObj.errorMessage) appLogger(statusObj.errorMessage, { level: "error" });
147
+ return dictionariesStatuses.map(({ dictionaryKey, status, filePath }) => ({
148
+ dictionaryKey,
149
+ status,
150
+ filePath
151
+ }));
144
152
  } catch (error) {
145
153
  appLogger(error, { level: "error" });
154
+ return [];
146
155
  }
147
156
  };
148
157
 
@@ -1 +1 @@
1
- {"version":3,"file":"pull.mjs","names":[],"sources":["../../src/pull.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { getAppLogger } from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport { getProjectRequire, parallelize } from '@intlayer/config/utils';\nimport { getUnmergedDictionaries } from '@intlayer/dictionaries-entry/unmerged';\nimport {\n type DictionaryStatus,\n writeContentDeclaration,\n} from '@intlayer/engine/build';\nimport { logConfigDetails } from '@intlayer/engine/cli';\nimport type { Dictionary } from '@intlayer/types/dictionary';\nimport { PullLogger, type PullStatus } from './push/pullLog';\nimport { checkCMSAuth, getAuthenticatedAPI } from './utils/checkAccess';\nimport { selectCmsEnvironment } from './utils/selectCmsEnvironment';\n\ntype PullOptions = {\n dictionaries?: string[];\n newDictionariesPath?: string;\n configOptions?: GetConfigurationOptions;\n};\n\ntype DictionariesStatus = {\n dictionaryKey: string;\n status: DictionaryStatus | 'pending' | 'fetching' | 'error';\n error?: Error;\n errorMessage?: string;\n};\n\n/**\n * Fetch distant dictionaries and write them locally,\n * with progress indicators and concurrency control.\n */\nexport const pull = async (options?: PullOptions): Promise<void> => {\n const config = getConfiguration(options?.configOptions);\n const appLogger = getAppLogger(config);\n\n try {\n logConfigDetails(options?.configOptions);\n\n const hasCMSAuth = await checkCMSAuth(config);\n\n if (!hasCMSAuth) return;\n\n const intlayerAPI = await getAuthenticatedAPI(config);\n\n await selectCmsEnvironment(\n options?.configOptions?.env,\n intlayerAPI,\n config\n );\n\n const unmergedDictionariesRecord = getUnmergedDictionaries(config);\n\n // Get remote update timestamps map\n const getDictionariesUpdateTimestampResult =\n await intlayerAPI.dictionary.getDictionariesUpdateTimestamp();\n\n if (!getDictionariesUpdateTimestampResult.data) {\n throw new Error('No distant dictionaries found');\n }\n\n let distantDictionariesUpdateTimeStamp: Record<string, any> =\n getDictionariesUpdateTimestampResult.data;\n\n // Optional filtering by requested dictionaries\n if (options?.dictionaries) {\n distantDictionariesUpdateTimeStamp = Object.fromEntries(\n Object.entries(distantDictionariesUpdateTimeStamp).filter(([key]) =>\n options.dictionaries?.includes(key)\n )\n );\n }\n\n // Filter by location\n distantDictionariesUpdateTimeStamp = Object.fromEntries(\n Object.entries(distantDictionariesUpdateTimeStamp).filter(([key]) => {\n const localDictionaries = unmergedDictionariesRecord[key];\n const location =\n localDictionaries?.[0]?.location ??\n config.dictionary?.location ??\n 'remote';\n\n return location === 'remote' || location === 'hybrid';\n })\n );\n\n // Load local cached remote dictionaries (if any)\n const remoteDictionariesPath = join(\n config.system.mainDir,\n 'remote_dictionaries.cjs'\n );\n const requireFunction = config.build?.require ?? getProjectRequire();\n const remoteDictionariesRecord: Record<string, any> = existsSync(\n remoteDictionariesPath\n )\n ? (requireFunction(remoteDictionariesPath) as any)\n : {};\n\n // Determine which keys need fetching by comparing updatedAt with local cache\n const entries = Object.entries(distantDictionariesUpdateTimeStamp);\n const keysToFetch = entries\n .filter(([key, remoteUpdatedAtValue]) => {\n if (!remoteUpdatedAtValue) return true;\n\n const remoteUpdatedAt =\n typeof remoteUpdatedAtValue === 'object'\n ? (remoteUpdatedAtValue as any).updatedAt\n : remoteUpdatedAtValue;\n\n const local = (remoteDictionariesRecord as any)[key];\n if (!local) return true;\n const localUpdatedAtRaw = (local as any)?.updatedAt as\n | number\n | string\n | undefined;\n const localUpdatedAt =\n typeof localUpdatedAtRaw === 'number'\n ? localUpdatedAtRaw\n : localUpdatedAtRaw\n ? new Date(localUpdatedAtRaw).getTime()\n : undefined;\n if (typeof localUpdatedAt !== 'number') return true;\n return remoteUpdatedAt > localUpdatedAt;\n })\n .map(([key]) => key);\n\n const cachedKeys = entries\n .filter(([key, remoteUpdatedAtValue]) => {\n const remoteUpdatedAt =\n typeof remoteUpdatedAtValue === 'object'\n ? (remoteUpdatedAtValue as any).updatedAt\n : remoteUpdatedAtValue;\n\n const local = (remoteDictionariesRecord as any)[key];\n const localUpdatedAtRaw = (local as any)?.updatedAt as\n | number\n | string\n | undefined;\n const localUpdatedAt =\n typeof localUpdatedAtRaw === 'number'\n ? localUpdatedAtRaw\n : localUpdatedAtRaw\n ? new Date(localUpdatedAtRaw).getTime()\n : undefined;\n return (\n typeof localUpdatedAt === 'number' &&\n typeof remoteUpdatedAt === 'number' &&\n localUpdatedAt >= remoteUpdatedAt\n );\n })\n .map(([key]) => key);\n\n // Check if dictionaries list is empty\n if (entries.length === 0) {\n appLogger('No dictionaries to fetch', {\n level: 'error',\n });\n return;\n }\n\n appLogger('Fetching dictionaries:');\n\n // Prepare dictionaries statuses\n const dictionariesStatuses: DictionariesStatus[] = [\n ...cachedKeys.map((dictionaryKey) => ({\n dictionaryKey,\n status: 'imported' as DictionaryStatus,\n })),\n ...keysToFetch.map((dictionaryKey) => ({\n dictionaryKey,\n status: 'pending' as const,\n })),\n ];\n\n // Initialize aggregated logger\n const logger = new PullLogger();\n logger.update(\n dictionariesStatuses.map<PullStatus>((s) => ({\n dictionaryKey: s.dictionaryKey,\n status: s.status,\n }))\n );\n\n const successfullyFetchedDictionaries: Dictionary[] = [];\n\n const processDictionary = async (\n statusObj: DictionariesStatus\n ): Promise<void> => {\n const isCached =\n statusObj.status === 'imported' || statusObj.status === 'up-to-date';\n\n if (!isCached) {\n statusObj.status = 'fetching';\n logger.update([\n { dictionaryKey: statusObj.dictionaryKey, status: 'fetching' },\n ]);\n }\n\n try {\n let sourceDictionary: Dictionary | undefined;\n\n if (isCached) {\n sourceDictionary = remoteDictionariesRecord[\n statusObj.dictionaryKey\n ] as Dictionary | undefined;\n }\n\n if (!sourceDictionary) {\n // Fetch the dictionary\n const getDictionaryResult =\n await intlayerAPI.dictionary.getDictionary(statusObj.dictionaryKey);\n\n sourceDictionary = getDictionaryResult.data as Dictionary | undefined;\n }\n\n if (!sourceDictionary) {\n throw new Error(\n `Dictionary ${statusObj.dictionaryKey} not found on remote`\n );\n }\n\n // Check if there is a local version of this dictionary that is hybrid\n const localDictionaries =\n unmergedDictionariesRecord[statusObj.dictionaryKey];\n const localAndRemoteDictionary = localDictionaries?.find(\n (d) => d.location === 'hybrid'\n );\n\n if (localAndRemoteDictionary) {\n // We want to preserve the local properties but use the remote content\n sourceDictionary = {\n ...sourceDictionary,\n location: 'hybrid',\n filePath: localAndRemoteDictionary.filePath,\n localId: localAndRemoteDictionary.localId,\n };\n }\n\n // Now, write the dictionary to local file\n const { status } = await writeContentDeclaration(\n sourceDictionary,\n config,\n options\n );\n\n statusObj.status = status;\n logger.update([{ dictionaryKey: statusObj.dictionaryKey, status }]);\n\n successfullyFetchedDictionaries.push(sourceDictionary);\n } catch (error) {\n statusObj.status = 'error';\n statusObj.error = error as Error;\n statusObj.errorMessage = `Error fetching dictionary ${statusObj.dictionaryKey}: ${error}`;\n logger.update([\n { dictionaryKey: statusObj.dictionaryKey, status: 'error' },\n ]);\n }\n };\n\n // Process dictionaries in parallel with concurrency limit\n await parallelize(dictionariesStatuses, processDictionary, 5);\n\n // Stop the logger and render final state\n logger.finish();\n\n // Per-dictionary summary\n const iconFor = (status: DictionariesStatus['status']) => {\n switch (status) {\n case 'fetched':\n case 'imported':\n case 'updated':\n case 'up-to-date':\n case 'reimported in JSON':\n case 'new content file':\n return '✔';\n case 'error':\n return '✖';\n default:\n return '⏲';\n }\n };\n\n const colorFor = (status: DictionariesStatus['status']) => {\n switch (status) {\n case 'fetched':\n case 'imported':\n case 'updated':\n case 'up-to-date':\n return ANSIColors.GREEN;\n case 'reimported in JSON':\n case 'new content file':\n return ANSIColors.YELLOW;\n case 'error':\n return ANSIColors.RED;\n default:\n return ANSIColors.BLUE;\n }\n };\n\n for (const s of dictionariesStatuses) {\n const icon = iconFor(s.status);\n const color = colorFor(s.status);\n appLogger(\n ` - ${s.dictionaryKey} ${ANSIColors.GREY}[${color}${icon} ${s.status}${ANSIColors.GREY}]${ANSIColors.RESET}`\n );\n }\n\n // Output any error messages\n for (const statusObj of dictionariesStatuses) {\n if (statusObj.errorMessage) {\n appLogger(statusObj.errorMessage, {\n level: 'error',\n });\n }\n }\n } catch (error) {\n appLogger(error, {\n level: 'error',\n });\n }\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"pull.mjs","names":[],"sources":["../../src/pull.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { getAppLogger } from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport { getProjectRequire, parallelize } from '@intlayer/config/utils';\nimport { getUnmergedDictionaries } from '@intlayer/dictionaries-entry/unmerged';\nimport {\n type DictionaryStatus,\n writeContentDeclaration,\n} from '@intlayer/engine/build';\nimport { logConfigDetails } from '@intlayer/engine/cli';\nimport type { Dictionary } from '@intlayer/types/dictionary';\nimport { PullLogger, type PullStatus } from './push/pullLog';\nimport { checkCMSAuth, getAuthenticatedAPI } from './utils/checkAccess';\nimport { selectCmsEnvironment } from './utils/selectCmsEnvironment';\n\ntype PullOptions = {\n dictionaries?: string[];\n newDictionariesPath?: string;\n configOptions?: GetConfigurationOptions;\n};\n\ntype DictionariesStatus = {\n dictionaryKey: string;\n status: DictionaryStatus | 'pending' | 'fetching' | 'error';\n error?: Error;\n errorMessage?: string;\n /** Content declaration file written for the dictionary */\n filePath?: string;\n};\n\n/** Outcome of one dictionary of a pull. */\nexport type PulledDictionary = Pick<\n DictionariesStatus,\n 'dictionaryKey' | 'status' | 'filePath'\n>;\n\n/**\n * Fetch distant dictionaries and write them locally,\n * with progress indicators and concurrency control.\n *\n * @returns The outcome of each pulled dictionary (empty when nothing ran).\n */\nexport const pull = async (\n options?: PullOptions\n): Promise<PulledDictionary[]> => {\n const config = getConfiguration(options?.configOptions);\n const appLogger = getAppLogger(config);\n\n try {\n logConfigDetails(options?.configOptions);\n\n const hasCMSAuth = await checkCMSAuth(config);\n\n if (!hasCMSAuth) return [];\n\n const intlayerAPI = await getAuthenticatedAPI(config);\n\n await selectCmsEnvironment(\n options?.configOptions?.env,\n intlayerAPI,\n config\n );\n\n const unmergedDictionariesRecord = getUnmergedDictionaries(config);\n\n // Get remote update timestamps map\n const getDictionariesUpdateTimestampResult =\n await intlayerAPI.dictionary.getDictionariesUpdateTimestamp();\n\n if (!getDictionariesUpdateTimestampResult.data) {\n throw new Error('No distant dictionaries found');\n }\n\n let distantDictionariesUpdateTimeStamp: Record<string, any> =\n getDictionariesUpdateTimestampResult.data;\n\n // Optional filtering by requested dictionaries\n if (options?.dictionaries) {\n distantDictionariesUpdateTimeStamp = Object.fromEntries(\n Object.entries(distantDictionariesUpdateTimeStamp).filter(([key]) =>\n options.dictionaries?.includes(key)\n )\n );\n }\n\n // Filter by location\n distantDictionariesUpdateTimeStamp = Object.fromEntries(\n Object.entries(distantDictionariesUpdateTimeStamp).filter(([key]) => {\n const localDictionaries = unmergedDictionariesRecord[key];\n const location =\n localDictionaries?.[0]?.location ??\n config.dictionary?.location ??\n 'remote';\n\n return location === 'remote' || location === 'hybrid';\n })\n );\n\n // Load local cached remote dictionaries (if any)\n const remoteDictionariesPath = join(\n config.system.mainDir,\n 'remote_dictionaries.cjs'\n );\n const requireFunction = config.build?.require ?? getProjectRequire();\n const remoteDictionariesRecord: Record<string, any> = existsSync(\n remoteDictionariesPath\n )\n ? (requireFunction(remoteDictionariesPath) as any)\n : {};\n\n // Determine which keys need fetching by comparing updatedAt with local cache\n const entries = Object.entries(distantDictionariesUpdateTimeStamp);\n const keysToFetch = entries\n .filter(([key, remoteUpdatedAtValue]) => {\n if (!remoteUpdatedAtValue) return true;\n\n const remoteUpdatedAt =\n typeof remoteUpdatedAtValue === 'object'\n ? (remoteUpdatedAtValue as any).updatedAt\n : remoteUpdatedAtValue;\n\n const local = (remoteDictionariesRecord as any)[key];\n if (!local) return true;\n const localUpdatedAtRaw = (local as any)?.updatedAt as\n | number\n | string\n | undefined;\n const localUpdatedAt =\n typeof localUpdatedAtRaw === 'number'\n ? localUpdatedAtRaw\n : localUpdatedAtRaw\n ? new Date(localUpdatedAtRaw).getTime()\n : undefined;\n if (typeof localUpdatedAt !== 'number') return true;\n return remoteUpdatedAt > localUpdatedAt;\n })\n .map(([key]) => key);\n\n const cachedKeys = entries\n .filter(([key, remoteUpdatedAtValue]) => {\n const remoteUpdatedAt =\n typeof remoteUpdatedAtValue === 'object'\n ? (remoteUpdatedAtValue as any).updatedAt\n : remoteUpdatedAtValue;\n\n const local = (remoteDictionariesRecord as any)[key];\n const localUpdatedAtRaw = (local as any)?.updatedAt as\n | number\n | string\n | undefined;\n const localUpdatedAt =\n typeof localUpdatedAtRaw === 'number'\n ? localUpdatedAtRaw\n : localUpdatedAtRaw\n ? new Date(localUpdatedAtRaw).getTime()\n : undefined;\n return (\n typeof localUpdatedAt === 'number' &&\n typeof remoteUpdatedAt === 'number' &&\n localUpdatedAt >= remoteUpdatedAt\n );\n })\n .map(([key]) => key);\n\n // Check if dictionaries list is empty\n if (entries.length === 0) {\n appLogger('No dictionaries to fetch', {\n level: 'error',\n });\n return [];\n }\n\n appLogger('Fetching dictionaries:');\n\n // Prepare dictionaries statuses\n const dictionariesStatuses: DictionariesStatus[] = [\n ...cachedKeys.map((dictionaryKey) => ({\n dictionaryKey,\n status: 'imported' as DictionaryStatus,\n })),\n ...keysToFetch.map((dictionaryKey) => ({\n dictionaryKey,\n status: 'pending' as const,\n })),\n ];\n\n // Initialize aggregated logger\n const logger = new PullLogger();\n logger.update(\n dictionariesStatuses.map<PullStatus>((s) => ({\n dictionaryKey: s.dictionaryKey,\n status: s.status,\n }))\n );\n\n const successfullyFetchedDictionaries: Dictionary[] = [];\n\n const processDictionary = async (\n statusObj: DictionariesStatus\n ): Promise<void> => {\n const isCached =\n statusObj.status === 'imported' || statusObj.status === 'up-to-date';\n\n if (!isCached) {\n statusObj.status = 'fetching';\n logger.update([\n { dictionaryKey: statusObj.dictionaryKey, status: 'fetching' },\n ]);\n }\n\n try {\n let sourceDictionary: Dictionary | undefined;\n\n if (isCached) {\n sourceDictionary = remoteDictionariesRecord[\n statusObj.dictionaryKey\n ] as Dictionary | undefined;\n }\n\n if (!sourceDictionary) {\n // Fetch the dictionary\n const getDictionaryResult =\n await intlayerAPI.dictionary.getDictionary(statusObj.dictionaryKey);\n\n sourceDictionary = getDictionaryResult.data as Dictionary | undefined;\n }\n\n if (!sourceDictionary) {\n throw new Error(\n `Dictionary ${statusObj.dictionaryKey} not found on remote`\n );\n }\n\n // Check if there is a local version of this dictionary that is hybrid\n const localDictionaries =\n unmergedDictionariesRecord[statusObj.dictionaryKey];\n const localAndRemoteDictionary = localDictionaries?.find(\n (d) => d.location === 'hybrid'\n );\n\n if (localAndRemoteDictionary) {\n // We want to preserve the local properties but use the remote content\n sourceDictionary = {\n ...sourceDictionary,\n location: 'hybrid',\n filePath: localAndRemoteDictionary.filePath,\n localId: localAndRemoteDictionary.localId,\n };\n }\n\n // Now, write the dictionary to local file\n const { status, path } = await writeContentDeclaration(\n sourceDictionary,\n config,\n options\n );\n\n statusObj.status = status;\n statusObj.filePath = path;\n logger.update([{ dictionaryKey: statusObj.dictionaryKey, status }]);\n\n successfullyFetchedDictionaries.push(sourceDictionary);\n } catch (error) {\n statusObj.status = 'error';\n statusObj.error = error as Error;\n statusObj.errorMessage = `Error fetching dictionary ${statusObj.dictionaryKey}: ${error}`;\n logger.update([\n { dictionaryKey: statusObj.dictionaryKey, status: 'error' },\n ]);\n }\n };\n\n // Process dictionaries in parallel with concurrency limit\n await parallelize(dictionariesStatuses, processDictionary, 5);\n\n // Stop the logger and render final state\n logger.finish();\n\n // Per-dictionary summary\n const iconFor = (status: DictionariesStatus['status']) => {\n switch (status) {\n case 'fetched':\n case 'imported':\n case 'updated':\n case 'up-to-date':\n case 'reimported in JSON':\n case 'new content file':\n return '✔';\n case 'error':\n return '✖';\n default:\n return '⏲';\n }\n };\n\n const colorFor = (status: DictionariesStatus['status']) => {\n switch (status) {\n case 'fetched':\n case 'imported':\n case 'updated':\n case 'up-to-date':\n return ANSIColors.GREEN;\n case 'reimported in JSON':\n case 'new content file':\n return ANSIColors.YELLOW;\n case 'error':\n return ANSIColors.RED;\n default:\n return ANSIColors.BLUE;\n }\n };\n\n for (const s of dictionariesStatuses) {\n const icon = iconFor(s.status);\n const color = colorFor(s.status);\n appLogger(\n ` - ${s.dictionaryKey} ${ANSIColors.GREY}[${color}${icon} ${s.status}${ANSIColors.GREY}]${ANSIColors.RESET}`\n );\n }\n\n // Output any error messages\n for (const statusObj of dictionariesStatuses) {\n if (statusObj.errorMessage) {\n appLogger(statusObj.errorMessage, {\n level: 'error',\n });\n }\n }\n\n return dictionariesStatuses.map(({ dictionaryKey, status, filePath }) => ({\n dictionaryKey,\n status,\n filePath,\n }));\n } catch (error) {\n appLogger(error, {\n level: 'error',\n });\n\n return [];\n }\n};\n"],"mappings":"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"}