@intlayer/engine 9.0.0-canary.13 → 9.0.0-canary.15

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 (90) hide show
  1. package/dist/cjs/createType/createModuleAugmentation.cjs +1 -1
  2. package/dist/cjs/createType/createModuleAugmentation.cjs.map +1 -1
  3. package/dist/cjs/createType/createType.cjs +2 -1
  4. package/dist/cjs/createType/createType.cjs.map +1 -1
  5. package/dist/cjs/init/index.cjs +2 -1
  6. package/dist/cjs/init/index.cjs.map +1 -1
  7. package/dist/cjs/init/utils/configManipulation.cjs +39 -5
  8. package/dist/cjs/init/utils/configManipulation.cjs.map +1 -1
  9. package/dist/cjs/init/utils/githubActions.cjs +3 -0
  10. package/dist/cjs/init/utils/githubActions.cjs.map +1 -1
  11. package/dist/cjs/init/utils/index.cjs +1 -0
  12. package/dist/cjs/init/utils/packageManager.cjs +8 -0
  13. package/dist/cjs/init/utils/packageManager.cjs.map +1 -1
  14. package/dist/cjs/loadDictionaries/loadContentDeclaration.cjs +2 -2
  15. package/dist/cjs/loadDictionaries/loadContentDeclaration.cjs.map +1 -1
  16. package/dist/cjs/prepareIntlayer.cjs +11 -7
  17. package/dist/cjs/prepareIntlayer.cjs.map +1 -1
  18. package/dist/cjs/syncPluginKit/createFileAdapter.cjs +119 -0
  19. package/dist/cjs/syncPluginKit/createFileAdapter.cjs.map +1 -0
  20. package/dist/cjs/syncPluginKit/createSyncPlugin.cjs +154 -0
  21. package/dist/cjs/syncPluginKit/createSyncPlugin.cjs.map +1 -0
  22. package/dist/cjs/syncPluginKit/extractKeyAndLocaleFromPath.cjs +37 -0
  23. package/dist/cjs/syncPluginKit/extractKeyAndLocaleFromPath.cjs.map +1 -0
  24. package/dist/cjs/syncPluginKit/filePathPatternHelpers.cjs +20 -0
  25. package/dist/cjs/syncPluginKit/filePathPatternHelpers.cjs.map +1 -0
  26. package/dist/cjs/syncPluginKit/index.cjs +11 -0
  27. package/dist/cjs/syncPluginKit/types.cjs +0 -0
  28. package/dist/cjs/utils/runParallel/index.cjs +2 -2
  29. package/dist/cjs/utils/runParallel/index.cjs.map +1 -1
  30. package/dist/cjs/watcher.cjs +2 -3
  31. package/dist/cjs/watcher.cjs.map +1 -1
  32. package/dist/cjs/writeFileIfChanged.cjs +41 -17
  33. package/dist/cjs/writeFileIfChanged.cjs.map +1 -1
  34. package/dist/esm/createType/createModuleAugmentation.mjs +1 -1
  35. package/dist/esm/createType/createModuleAugmentation.mjs.map +1 -1
  36. package/dist/esm/createType/createType.mjs +3 -2
  37. package/dist/esm/createType/createType.mjs.map +1 -1
  38. package/dist/esm/init/index.mjs +3 -2
  39. package/dist/esm/init/index.mjs.map +1 -1
  40. package/dist/esm/init/utils/configManipulation.mjs +39 -6
  41. package/dist/esm/init/utils/configManipulation.mjs.map +1 -1
  42. package/dist/esm/init/utils/githubActions.mjs +3 -0
  43. package/dist/esm/init/utils/githubActions.mjs.map +1 -1
  44. package/dist/esm/init/utils/index.mjs +2 -2
  45. package/dist/esm/init/utils/packageManager.mjs +8 -0
  46. package/dist/esm/init/utils/packageManager.mjs.map +1 -1
  47. package/dist/esm/loadDictionaries/loadContentDeclaration.mjs +2 -2
  48. package/dist/esm/loadDictionaries/loadContentDeclaration.mjs.map +1 -1
  49. package/dist/esm/prepareIntlayer.mjs +11 -7
  50. package/dist/esm/prepareIntlayer.mjs.map +1 -1
  51. package/dist/esm/syncPluginKit/createFileAdapter.mjs +115 -0
  52. package/dist/esm/syncPluginKit/createFileAdapter.mjs.map +1 -0
  53. package/dist/esm/syncPluginKit/createSyncPlugin.mjs +152 -0
  54. package/dist/esm/syncPluginKit/createSyncPlugin.mjs.map +1 -0
  55. package/dist/esm/syncPluginKit/extractKeyAndLocaleFromPath.mjs +35 -0
  56. package/dist/esm/syncPluginKit/extractKeyAndLocaleFromPath.mjs.map +1 -0
  57. package/dist/esm/syncPluginKit/filePathPatternHelpers.mjs +18 -0
  58. package/dist/esm/syncPluginKit/filePathPatternHelpers.mjs.map +1 -0
  59. package/dist/esm/syncPluginKit/index.mjs +6 -0
  60. package/dist/esm/syncPluginKit/types.mjs +0 -0
  61. package/dist/esm/utils/runParallel/index.mjs +2 -2
  62. package/dist/esm/utils/runParallel/index.mjs.map +1 -1
  63. package/dist/esm/watcher.mjs +2 -3
  64. package/dist/esm/watcher.mjs.map +1 -1
  65. package/dist/esm/writeFileIfChanged.mjs +44 -20
  66. package/dist/esm/writeFileIfChanged.mjs.map +1 -1
  67. package/dist/types/createType/createType.d.ts.map +1 -1
  68. package/dist/types/init/index.d.ts.map +1 -1
  69. package/dist/types/init/utils/configManipulation.d.ts +9 -1
  70. package/dist/types/init/utils/configManipulation.d.ts.map +1 -1
  71. package/dist/types/init/utils/githubActions.d.ts.map +1 -1
  72. package/dist/types/init/utils/index.d.ts +2 -2
  73. package/dist/types/init/utils/packageManager.d.ts.map +1 -1
  74. package/dist/types/syncPluginKit/createFileAdapter.d.ts +51 -0
  75. package/dist/types/syncPluginKit/createFileAdapter.d.ts.map +1 -0
  76. package/dist/types/syncPluginKit/createSyncPlugin.d.ts +18 -0
  77. package/dist/types/syncPluginKit/createSyncPlugin.d.ts.map +1 -0
  78. package/dist/types/syncPluginKit/extractKeyAndLocaleFromPath.d.ts +20 -0
  79. package/dist/types/syncPluginKit/extractKeyAndLocaleFromPath.d.ts.map +1 -0
  80. package/dist/types/syncPluginKit/filePathPatternHelpers.d.ts +16 -0
  81. package/dist/types/syncPluginKit/filePathPatternHelpers.d.ts.map +1 -0
  82. package/dist/types/syncPluginKit/index.d.ts +6 -0
  83. package/dist/types/syncPluginKit/types.d.ts +149 -0
  84. package/dist/types/syncPluginKit/types.d.ts.map +1 -0
  85. package/dist/types/utils/runParallel/index.d.ts +6 -1
  86. package/dist/types/utils/runParallel/index.d.ts.map +1 -1
  87. package/dist/types/watcher.d.ts.map +1 -1
  88. package/dist/types/writeFileIfChanged.d.ts +30 -6
  89. package/dist/types/writeFileIfChanged.d.ts.map +1 -1
  90. package/package.json +24 -8
@@ -1 +1 @@
1
- {"version":3,"file":"prepareIntlayer.mjs","names":[],"sources":["../../src/prepareIntlayer.ts"],"sourcesContent":["import { stat } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, getAppLogger } from '@intlayer/config/logger';\nimport packageJson from '@intlayer/config/package.json' with { type: 'json' };\nimport { cacheDisk } from '@intlayer/config/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { buildDictionary } from './buildIntlayerDictionary/buildIntlayerDictionary';\nimport { writeRemoteDictionary } from './buildIntlayerDictionary/writeRemoteDictionary';\nimport { cleanOutputDir } from './cleanOutputDir';\nimport { createDictionaryEntryPoint } from './createDictionaryEntryPoint/createDictionaryEntryPoint';\nimport { createModuleAugmentation, createTypes } from './createType/index';\nimport { listDictionariesWithStats } from './listDictionariesPath';\nimport { loadDictionaries } from './loadDictionaries/loadDictionaries';\nimport { runOnce } from './utils/runOnce';\nimport {\n isCachedConfigurationUpToDate,\n writeConfiguration,\n} from './writeConfiguration';\n\ntype PrepareIntlayerOptions = {\n clean?: boolean;\n env?: 'prod' | 'dev';\n format?: ('cjs' | 'esm')[];\n forceRun?: boolean;\n cacheTimeoutMs?: number;\n onIsCached?: () => void | Promise<void>;\n};\n\nconst DEFAULT_PREPARE_INTLAYER_OPTIONS = {\n clean: false,\n env: 'dev',\n format: ['cjs', 'esm'],\n cacheTimeoutMs: 1000 * 60 * 60, // 1 hour\n} satisfies PrepareIntlayerOptions;\n\nexport const prepareIntlayer = async (\n configuration: IntlayerConfig,\n options?: PrepareIntlayerOptions\n) => {\n const appLogger = getAppLogger(configuration);\n\n const sentinelPath = join(\n configuration.system.cacheDir,\n 'intlayer-prepared.lock'\n );\n // Clean output dir if the intlayer version has changed\n const versionCache = cacheDisk(configuration, ['intlayer-version']);\n const intlayerCacheVersion = await versionCache.get();\n const isCorrectVersion = Boolean(\n intlayerCacheVersion && intlayerCacheVersion === packageJson.version\n );\n\n const isConfigSimilar = await isCachedConfigurationUpToDate(configuration);\n\n // Check if any dictionary has been changed to force a new rebuild\n const dictionariesWithStats = await listDictionariesWithStats(configuration);\n let isDictionaryChanged = false;\n try {\n // Try catch as sentinel file may not exist yet\n const sentinelStats = await stat(sentinelPath);\n isDictionaryChanged = dictionariesWithStats.some(\n (dictionary) =>\n dictionary.stats.mtime.getTime() > sentinelStats.mtime.getTime()\n );\n } catch {}\n\n const resolvedPlugins = await Promise.all(configuration.plugins ?? []);\n const hasPluginLoadDictionaries = resolvedPlugins.some((plugin) =>\n Boolean(plugin.loadDictionaries)\n ); // Disable cache if any plugin because it can have custom behavior\n\n const { clean, format, forceRun, onIsCached, cacheTimeoutMs, env } = {\n ...DEFAULT_PREPARE_INTLAYER_OPTIONS,\n forceRun:\n !isCorrectVersion ||\n !isConfigSimilar ||\n isDictionaryChanged ||\n hasPluginLoadDictionaries,\n ...(options ?? {}),\n };\n\n // Skip preparation if it has already been done recently\n await runOnce(\n sentinelPath,\n async () => {\n // comment because of issue with next and webpack\n // await checkVersionsConsistency(configuration);\n\n if (clean || !isCorrectVersion) {\n await cleanOutputDir(configuration);\n }\n\n await versionCache.set(packageJson.version);\n\n const preparationStartMs = Date.now();\n\n appLogger([\n 'Preparing Intlayer',\n colorize(`(v${packageJson.version})`, ANSIColors.GREY_DARK),\n ]);\n\n await writeConfiguration(configuration);\n\n const configurationWrittenTime = Date.now();\n\n appLogger(\n [\n 'Configuration written',\n colorize(\n `(${configurationWrittenTime - preparationStartMs}ms)`,\n ANSIColors.GREY_DARK\n ),\n ],\n {\n isVerbose: true,\n }\n );\n\n const contentDeclarationPaths = dictionariesWithStats.map(\n (dictionary) => dictionary.path\n );\n\n const dictionaries = await loadDictionaries(\n contentDeclarationPaths,\n configuration\n );\n\n const dictionariesLoadedTime = Date.now();\n\n appLogger(\n [\n 'Content loaded',\n colorize(\n [\n dictionaries.remoteDictionaries.length +\n dictionaries.pluginDictionaries.length >\n 0\n ? [\n `(Total: ${dictionariesLoadedTime - configurationWrittenTime}ms`,\n dictionaries.localDictionaries.length > 0\n ? ` - Local: ${dictionaries.time.localDictionaries}ms`\n : '',\n dictionaries.remoteDictionaries.length > 0\n ? ` - Remote: ${dictionaries.time.remoteDictionaries}ms`\n : '',\n dictionaries.pluginDictionaries.length > 0\n ? ` - Plugin: ${dictionaries.time.pluginDictionaries}ms`\n : '',\n `)`,\n ].join('')\n : `(${dictionariesLoadedTime - configurationWrittenTime}ms)`,\n ].join(''),\n ANSIColors.GREY_DARK\n ),\n ],\n {\n isVerbose: true,\n }\n );\n\n // Build local dictionaries\n const dictionariesOutput = await buildDictionary(\n [\n ...dictionaries.localDictionaries,\n ...dictionaries.remoteDictionaries,\n ...dictionaries.pluginDictionaries,\n ],\n configuration,\n { formats: format, importOtherDictionaries: false, env }\n );\n\n // Write remote dictionaries\n // Used as cache for next fetch\n await writeRemoteDictionary(\n dictionaries.remoteDictionaries,\n configuration\n );\n\n const dictionariesToBuild = Object.values(\n dictionariesOutput?.mergedDictionaries ?? {}\n ).map((dictionary) => dictionary.dictionary);\n\n await createTypes(dictionariesToBuild, configuration);\n\n await createDictionaryEntryPoint(configuration, {\n formats: options?.format,\n });\n\n const dictionariesBuiltTime = Date.now();\n\n appLogger([\n 'Dictionaries built',\n colorize(\n `(${dictionariesBuiltTime - preparationStartMs}ms)`,\n ANSIColors.GREY_DARK\n ),\n ]);\n\n await createModuleAugmentation(configuration);\n\n const moduleAugmentationBuiltTime = Date.now();\n\n appLogger(\n [\n 'Module augmentation built',\n colorize(\n `(${moduleAugmentationBuiltTime - dictionariesBuiltTime}ms)`,\n ANSIColors.GREY_DARK\n ),\n ],\n {\n isVerbose: true,\n }\n );\n\n // Plugin transformation\n // Allow plugins to post-process the final build output (e.g., write back ICU JSON)\n for await (const plugin of configuration.plugins ?? []) {\n const { unmergedDictionaries, mergedDictionaries } = dictionariesOutput;\n\n await plugin.afterBuild?.({\n dictionaries: {\n unmergedDictionaries,\n mergedDictionaries,\n },\n configuration,\n });\n }\n\n const preparationElapsedMs = Date.now() - preparationStartMs;\n appLogger(\n [`Done`, colorize(`${preparationElapsedMs}ms`, ANSIColors.GREEN)],\n {\n level: 'info',\n isVerbose: true,\n }\n );\n },\n {\n forceRun,\n onIsCached,\n cacheTimeoutMs,\n }\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AA6BA,MAAM,mCAAmC;CACvC,OAAO;CACP,KAAK;CACL,QAAQ,CAAC,OAAO,MAAM;CACtB,gBAAgB,MAAO,KAAK;CAC7B;AAED,MAAa,kBAAkB,OAC7B,eACA,YACG;CACH,MAAM,YAAY,aAAa,cAAc;CAE7C,MAAM,eAAe,KACnB,cAAc,OAAO,UACrB,yBACD;CAED,MAAM,eAAe,UAAU,eAAe,CAAC,mBAAmB,CAAC;CACnE,MAAM,uBAAuB,MAAM,aAAa,KAAK;CACrD,MAAM,mBAAmB,QACvB,wBAAwB,yBAAyB,YAAY,QAC9D;CAED,MAAM,kBAAkB,MAAM,8BAA8B,cAAc;CAG1E,MAAM,wBAAwB,MAAM,0BAA0B,cAAc;CAC5E,IAAI,sBAAsB;AAC1B,KAAI;EAEF,MAAM,gBAAgB,MAAM,KAAK,aAAa;AAC9C,wBAAsB,sBAAsB,MACzC,eACC,WAAW,MAAM,MAAM,SAAS,GAAG,cAAc,MAAM,SAAS,CACnE;SACK;CAGR,MAAM,6BAA4B,MADJ,QAAQ,IAAI,cAAc,WAAW,EAAE,CAAC,EACpB,MAAM,WACtD,QAAQ,OAAO,iBAAiB,CACjC;CAED,MAAM,EAAE,OAAO,QAAQ,UAAU,YAAY,gBAAgB,QAAQ;EACnE,GAAG;EACH,UACE,CAAC,oBACD,CAAC,mBACD,uBACA;EACF,GAAI,WAAW,EAAE;EAClB;AAGD,OAAM,QACJ,cACA,YAAY;AAIV,MAAI,SAAS,CAAC,iBACZ,OAAM,eAAe,cAAc;AAGrC,QAAM,aAAa,IAAI,YAAY,QAAQ;EAE3C,MAAM,qBAAqB,KAAK,KAAK;AAErC,YAAU,CACR,sBACA,SAAS,KAAK,YAAY,QAAQ,IAAI,WAAW,UAAU,CAC5D,CAAC;AAEF,QAAM,mBAAmB,cAAc;EAEvC,MAAM,2BAA2B,KAAK,KAAK;AAE3C,YACE,CACE,yBACA,SACE,IAAI,2BAA2B,mBAAmB,MAClD,WAAW,UACZ,CACF,EACD,EACE,WAAW,MACZ,CACF;EAMD,MAAM,eAAe,MAAM,iBAJK,sBAAsB,KACnD,eAAe,WAAW,KAIJ,EACvB,cACD;EAED,MAAM,yBAAyB,KAAK,KAAK;AAEzC,YACE,CACE,kBACA,SACE,CACE,aAAa,mBAAmB,SAC9B,aAAa,mBAAmB,SAClC,IACI;GACE,WAAW,yBAAyB,yBAAyB;GAC7D,aAAa,kBAAkB,SAAS,IACpC,aAAa,aAAa,KAAK,kBAAkB,MACjD;GACJ,aAAa,mBAAmB,SAAS,IACrC,cAAc,aAAa,KAAK,mBAAmB,MACnD;GACJ,aAAa,mBAAmB,SAAS,IACrC,cAAc,aAAa,KAAK,mBAAmB,MACnD;GACJ;GACD,CAAC,KAAK,GAAG,GACV,IAAI,yBAAyB,yBAAyB,KAC3D,CAAC,KAAK,GAAG,EACV,WAAW,UACZ,CACF,EACD,EACE,WAAW,MACZ,CACF;EAGD,MAAM,qBAAqB,MAAM,gBAC/B;GACE,GAAG,aAAa;GAChB,GAAG,aAAa;GAChB,GAAG,aAAa;GACjB,EACD,eACA;GAAE,SAAS;GAAQ,yBAAyB;GAAO;GAAK,CACzD;AAID,QAAM,sBACJ,aAAa,oBACb,cACD;AAMD,QAAM,YAJsB,OAAO,OACjC,oBAAoB,sBAAsB,EAAE,CAC7C,CAAC,KAAK,eAAe,WAAW,WAEI,EAAE,cAAc;AAErD,QAAM,2BAA2B,eAAe,EAC9C,SAAS,SAAS,QACnB,CAAC;EAEF,MAAM,wBAAwB,KAAK,KAAK;AAExC,YAAU,CACR,sBACA,SACE,IAAI,wBAAwB,mBAAmB,MAC/C,WAAW,UACZ,CACF,CAAC;AAEF,QAAM,yBAAyB,cAAc;AAI7C,YACE,CACE,6BACA,SACE,IAN8B,KAAK,KAMJ,GAAG,sBAAsB,MACxD,WAAW,UACZ,CACF,EACD,EACE,WAAW,MACZ,CACF;AAID,aAAW,MAAM,UAAU,cAAc,WAAW,EAAE,EAAE;GACtD,MAAM,EAAE,sBAAsB,uBAAuB;AAErD,SAAM,OAAO,aAAa;IACxB,cAAc;KACZ;KACA;KACD;IACD;IACD,CAAC;;AAIJ,YACE,CAAC,QAAQ,SAAS,GAFS,KAAK,KAAK,GAAG,mBAEE,KAAK,WAAW,MAAM,CAAC,EACjE;GACE,OAAO;GACP,WAAW;GACZ,CACF;IAEH;EACE;EACA;EACA;EACD,CACF"}
1
+ {"version":3,"file":"prepareIntlayer.mjs","names":[],"sources":["../../src/prepareIntlayer.ts"],"sourcesContent":["import { stat } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, getAppLogger } from '@intlayer/config/logger';\nimport packageJson from '@intlayer/config/package.json' with { type: 'json' };\nimport { cacheDisk } from '@intlayer/config/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { buildDictionary } from './buildIntlayerDictionary/buildIntlayerDictionary';\nimport { writeRemoteDictionary } from './buildIntlayerDictionary/writeRemoteDictionary';\nimport { cleanOutputDir } from './cleanOutputDir';\nimport { createDictionaryEntryPoint } from './createDictionaryEntryPoint/createDictionaryEntryPoint';\nimport { createModuleAugmentation, createTypes } from './createType/index';\nimport { listDictionariesWithStats } from './listDictionariesPath';\nimport { loadDictionaries } from './loadDictionaries/loadDictionaries';\nimport { runOnce } from './utils/runOnce';\nimport {\n isCachedConfigurationUpToDate,\n writeConfiguration,\n} from './writeConfiguration';\n\ntype PrepareIntlayerOptions = {\n clean?: boolean;\n env?: 'prod' | 'dev';\n format?: ('cjs' | 'esm')[];\n forceRun?: boolean;\n cacheTimeoutMs?: number;\n onIsCached?: () => void | Promise<void>;\n};\n\nconst DEFAULT_PREPARE_INTLAYER_OPTIONS = {\n clean: false,\n env: 'dev',\n format: ['cjs', 'esm'],\n cacheTimeoutMs: 1000 * 60 * 60, // 1 hour\n} satisfies PrepareIntlayerOptions;\n\nexport const prepareIntlayer = async (\n configuration: IntlayerConfig,\n options?: PrepareIntlayerOptions\n) => {\n const appLogger = getAppLogger(configuration);\n\n const sentinelPath = join(\n configuration.system.cacheDir,\n 'intlayer-prepared.lock'\n );\n // Clean output dir if the intlayer version has changed\n const versionCache = cacheDisk(configuration, ['intlayer-version']);\n const intlayerCacheVersion = await versionCache.get();\n const isCorrectVersion = Boolean(\n intlayerCacheVersion && intlayerCacheVersion === packageJson.version\n );\n\n const isConfigSimilar = await isCachedConfigurationUpToDate(configuration);\n\n // Check if any dictionary has been changed to force a new rebuild\n const dictionariesWithStats = await listDictionariesWithStats(configuration);\n let isDictionaryChanged = false;\n try {\n // Try catch as sentinel file may not exist yet\n const sentinelStats = await stat(sentinelPath);\n isDictionaryChanged = dictionariesWithStats.some(\n (dictionary) =>\n dictionary.stats.mtime.getTime() > sentinelStats.mtime.getTime()\n );\n } catch {}\n\n const resolvedPlugins = await Promise.all(configuration.plugins ?? []);\n const hasPluginLoadDictionaries = resolvedPlugins.some((plugin) =>\n Boolean(plugin.loadDictionaries)\n ); // Disable cache if any plugin because it can have custom behavior\n\n const { clean, format, forceRun, onIsCached, cacheTimeoutMs, env } = {\n ...DEFAULT_PREPARE_INTLAYER_OPTIONS,\n forceRun:\n !isCorrectVersion ||\n !isConfigSimilar ||\n isDictionaryChanged ||\n hasPluginLoadDictionaries,\n ...(options ?? {}),\n };\n\n // Skip preparation if it has already been done recently\n await runOnce(\n sentinelPath,\n async () => {\n // comment because of issue with next and webpack\n // await checkVersionsConsistency(configuration);\n\n if (clean || !isCorrectVersion) {\n await cleanOutputDir(configuration);\n }\n\n await versionCache.set(packageJson.version);\n\n const preparationStartMs = Date.now();\n\n appLogger([\n 'Preparing Intlayer',\n colorize(`(v${packageJson.version})`, ANSIColors.GREY_DARK),\n ]);\n\n await writeConfiguration(configuration);\n\n const configurationWrittenTime = Date.now();\n\n appLogger(\n [\n 'Configuration written',\n colorize(\n `(${configurationWrittenTime - preparationStartMs}ms)`,\n ANSIColors.GREY_DARK\n ),\n ],\n {\n isVerbose: true,\n }\n );\n\n const contentDeclarationPaths = dictionariesWithStats.map(\n (dictionary) => dictionary.path\n );\n\n const dictionaries = await loadDictionaries(\n contentDeclarationPaths,\n configuration\n );\n\n const dictionariesLoadedTime = Date.now();\n\n appLogger(\n [\n 'Content loaded',\n colorize(\n [\n dictionaries.remoteDictionaries.length +\n dictionaries.pluginDictionaries.length >\n 0\n ? [\n `(Total: ${dictionariesLoadedTime - configurationWrittenTime}ms`,\n dictionaries.localDictionaries.length > 0\n ? ` - Local: ${dictionaries.time.localDictionaries}ms`\n : '',\n dictionaries.remoteDictionaries.length > 0\n ? ` - Remote: ${dictionaries.time.remoteDictionaries}ms`\n : '',\n dictionaries.pluginDictionaries.length > 0\n ? ` - Plugin: ${dictionaries.time.pluginDictionaries}ms`\n : '',\n `)`,\n ].join('')\n : `(${dictionariesLoadedTime - configurationWrittenTime}ms)`,\n ].join(''),\n ANSIColors.GREY_DARK\n ),\n ],\n {\n isVerbose: true,\n }\n );\n\n // Build local dictionaries\n const dictionariesOutput = await buildDictionary(\n [\n ...dictionaries.localDictionaries,\n ...dictionaries.remoteDictionaries,\n ...dictionaries.pluginDictionaries,\n ],\n configuration,\n { formats: format, importOtherDictionaries: false, env }\n );\n\n // Write remote dictionaries\n // Used as cache for next fetch\n await writeRemoteDictionary(\n dictionaries.remoteDictionaries,\n configuration\n );\n\n const dictionariesToBuild = Object.values(\n dictionariesOutput?.mergedDictionaries ?? {}\n ).map((dictionary) => dictionary.dictionary);\n\n await createTypes(dictionariesToBuild, configuration);\n\n await createDictionaryEntryPoint(configuration, {\n formats: options?.format,\n });\n\n const dictionariesBuiltTime = Date.now();\n\n appLogger([\n 'Dictionaries built',\n colorize(\n `(${dictionariesBuiltTime - preparationStartMs}ms)`,\n ANSIColors.GREY_DARK\n ),\n ]);\n\n await createModuleAugmentation(configuration);\n\n const moduleAugmentationBuiltTime = Date.now();\n\n appLogger(\n [\n 'Module augmentation built',\n colorize(\n `(${moduleAugmentationBuiltTime - dictionariesBuiltTime}ms)`,\n ANSIColors.GREY_DARK\n ),\n ],\n {\n isVerbose: true,\n }\n );\n\n // Plugin transformation\n // Allow plugins to post-process the final build output (e.g., write back ICU JSON)\n for await (const plugin of configuration.plugins ?? []) {\n const { unmergedDictionaries, mergedDictionaries } = dictionariesOutput;\n\n try {\n await plugin.afterBuild?.({\n dictionaries: {\n unmergedDictionaries,\n mergedDictionaries,\n },\n configuration,\n });\n } catch (error) {\n // A failing write-back (e.g. transient network error on a remote\n // target) must not invalidate the dictionaries already built.\n appLogger(\n [\n `Plugin ${plugin.name} afterBuild failed:`,\n (error as Error).message,\n ],\n { level: 'error' }\n );\n }\n }\n\n const preparationElapsedMs = Date.now() - preparationStartMs;\n appLogger(\n [`Done`, colorize(`${preparationElapsedMs}ms`, ANSIColors.GREEN)],\n {\n level: 'info',\n isVerbose: true,\n }\n );\n },\n {\n forceRun,\n onIsCached,\n cacheTimeoutMs,\n }\n );\n};\n"],"mappings":"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"}
@@ -0,0 +1,115 @@
1
+ import { writeFileIfChanged } from "../writeFileIfChanged.mjs";
2
+ import { extractKeyAndLocaleFromPath } from "./extractKeyAndLocaleFromPath.mjs";
3
+ import { buildFilePathPatternContext } from "./filePathPatternHelpers.mjs";
4
+ import { mkdir, readFile } from "node:fs/promises";
5
+ import { dirname, isAbsolute, resolve } from "node:path";
6
+ import fg from "fast-glob";
7
+ import { parseFilePathPattern } from "@intlayer/config/utils";
8
+
9
+ //#region src/syncPluginKit/createFileAdapter.ts
10
+ /**
11
+ * Create a filesystem {@link ContentAdapter} for `createSyncPlugin`.
12
+ *
13
+ * Handles glob discovery of the source pattern, key/locale extraction from
14
+ * paths, base-directory resolution, and change-detected atomic writes.
15
+ */
16
+ const createFileAdapter = (options) => {
17
+ const { source, codec, discovery = "strict" } = options;
18
+ const readEntry = options.readEntry ?? createFileReader(codec.parse);
19
+ let patternMarkerPromise;
20
+ /**
21
+ * Source pattern with `{{key}}` / `{{locale}}` markers kept verbatim,
22
+ * memoized because pattern functions can be asynchronous.
23
+ */
24
+ const getPatternMarker = () => {
25
+ patternMarkerPromise ??= parseFilePathPattern(source, buildFilePathPatternContext("{{key}}", "{{locale}}"));
26
+ return patternMarkerPromise;
27
+ };
28
+ const listMessages = async (context) => {
29
+ const { system, internationalization } = context.configuration;
30
+ const { baseDir } = system;
31
+ const { locales } = internationalization;
32
+ const result = {};
33
+ for (const locale of locales) {
34
+ const globPatternLocale = await parseFilePathPattern(source, buildFilePathPatternContext("**", locale));
35
+ const maskPatternLocale = await parseFilePathPattern(source, buildFilePathPatternContext("{{__KEY__}}", locale));
36
+ if (!globPatternLocale || !maskPatternLocale) continue;
37
+ const files = await fg(globPatternLocale.startsWith("./") ? globPatternLocale.slice(2) : globPatternLocale, { cwd: baseDir });
38
+ const hasLocaleInMask = maskPatternLocale.includes("{{__LOCALE__}}");
39
+ const hasKeyInMask = maskPatternLocale.includes("{{__KEY__}}");
40
+ for (const file of files) {
41
+ let key;
42
+ let extractedLocale;
43
+ if (hasLocaleInMask || hasKeyInMask) {
44
+ const extraction = extractKeyAndLocaleFromPath(file, maskPatternLocale, locales, locale);
45
+ if (!extraction) continue;
46
+ key = extraction.key;
47
+ extractedLocale = extraction.locale;
48
+ } else if (discovery === "inclusive") {
49
+ key = "index";
50
+ extractedLocale = locale;
51
+ } else continue;
52
+ const absoluteFoundPath = isAbsolute(file) ? file : resolve(baseDir, file);
53
+ if (discovery === "strict") {
54
+ const expectedPath = await parseFilePathPattern(source, buildFilePathPatternContext(key, extractedLocale));
55
+ if (absoluteFoundPath !== (isAbsolute(expectedPath) ? expectedPath : resolve(baseDir, expectedPath))) continue;
56
+ }
57
+ result[extractedLocale] ??= {};
58
+ result[extractedLocale][key] = absoluteFoundPath;
59
+ }
60
+ }
61
+ if (discovery === "inclusive") return result;
62
+ const hasKeyInMask = (await parseFilePathPattern(source, buildFilePathPatternContext("{{__KEY__}}", locales[0]))).includes("{{__KEY__}}");
63
+ const discoveredKeys = /* @__PURE__ */ new Set();
64
+ for (const locale of Object.keys(result)) for (const key of Object.keys(result[locale] ?? {})) discoveredKeys.add(key);
65
+ if (!hasKeyInMask) discoveredKeys.add("index");
66
+ for (const locale of locales) {
67
+ result[locale] ??= {};
68
+ for (const key of discoveredKeys) if (!result[locale][key]) {
69
+ const builtPath = await parseFilePathPattern(source, buildFilePathPatternContext(key, locale));
70
+ result[locale][key] = isAbsolute(builtPath) ? builtPath : resolve(baseDir, builtPath);
71
+ }
72
+ }
73
+ return result;
74
+ };
75
+ return {
76
+ list: async (context) => {
77
+ const messages = await listMessages(context);
78
+ return Object.entries(messages).flatMap(([locale, keysRecord]) => Object.entries(keysRecord).map(([key, absoluteFilePath]) => ({
79
+ key,
80
+ locale,
81
+ uri: absoluteFilePath,
82
+ filePath: absoluteFilePath
83
+ })));
84
+ },
85
+ read: async (entry, context) => await readEntry(entry.uri, context),
86
+ write: async ({ key, locale }, content, { configuration }) => {
87
+ const builderPath = await parseFilePathPattern(source, buildFilePathPatternContext(key, locale));
88
+ const absoluteFilePath = resolve(configuration.system.baseDir, builderPath);
89
+ await mkdir(dirname(absoluteFilePath), { recursive: true });
90
+ await writeFileIfChanged(absoluteFilePath, codec.serialize(content, { locale }), { tempDir: configuration.system?.tempDir });
91
+ },
92
+ resolveUri: async ({ key, locale }, { configuration }) => {
93
+ const builderPath = await parseFilePathPattern(source, buildFilePathPatternContext(key, locale));
94
+ return resolve(configuration.system.baseDir, builderPath);
95
+ },
96
+ getFillPattern: async () => await getPatternMarker(),
97
+ describeSource: async () => await getPatternMarker(),
98
+ detectSplitKeys: async () => !(await getPatternMarker()).includes("{{key}}")
99
+ };
100
+ };
101
+ /**
102
+ * Default `readEntry` implementation: plain utf-8 read followed by the given
103
+ * parser. Returns `undefined` for missing or unreadable files.
104
+ */
105
+ const createFileReader = (parse) => async (absoluteFilePath) => {
106
+ try {
107
+ return parse(await readFile(absoluteFilePath, "utf-8"));
108
+ } catch {
109
+ return;
110
+ }
111
+ };
112
+
113
+ //#endregion
114
+ export { createFileAdapter, createFileReader };
115
+ //# sourceMappingURL=createFileAdapter.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createFileAdapter.mjs","names":[],"sources":["../../../src/syncPluginKit/createFileAdapter.ts"],"sourcesContent":["import { mkdir, readFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, resolve } from 'node:path';\nimport { parseFilePathPattern } from '@intlayer/config/utils';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { FilePathPattern } from '@intlayer/types/filePathPattern';\nimport fg from 'fast-glob';\nimport { writeFileIfChanged } from '../writeFileIfChanged';\nimport { extractKeyAndLocaleFromPath } from './extractKeyAndLocaleFromPath';\nimport { buildFilePathPatternContext } from './filePathPatternHelpers';\nimport type {\n ContentAdapter,\n ContentEntry,\n FormatCodec,\n SyncContent,\n SyncPluginContext,\n} from './types';\n\n/**\n * Discovery strategy of the file adapter:\n * - `strict`: keep only files whose path rebuilds identically from the source\n * pattern, and fabricate entries for missing locales/keys so every declared\n * locale has a write-back target (sync plugins).\n * - `inclusive`: keep every glob match without fabricating missing entries\n * (read-only load plugins, whose patterns may contain free `**` globs).\n */\nexport type FileAdapterDiscovery = 'strict' | 'inclusive';\n\nexport type CreateFileAdapterOptions = {\n /**\n * Location of the files, as a static string, a templated string\n * (e.g. `./messages/{{locale}}/{{key}}.json`) or a function building the\n * path from the key and locale.\n */\n source: FilePathPattern;\n\n /**\n * Format of the files: string payload ↔ structured content.\n */\n codec: FormatCodec;\n\n /**\n * Optional override of how one file is read and parsed. Defaults to a plain\n * utf-8 read followed by `codec.parse`, returning `undefined` for missing or\n * unreadable files. Adapters needing richer loading (e.g. JSON5 or\n * transpiled TS sources) provide their own implementation here.\n */\n readEntry?: (\n absoluteFilePath: string,\n context: SyncPluginContext\n ) => Promise<SyncContent | undefined>;\n\n /**\n * Discovery strategy. Defaults to `'strict'`.\n */\n discovery?: FileAdapterDiscovery;\n};\n\ntype FilePath = string;\ntype MessagesRecord = Record<Locale, Record<string, FilePath>>;\n\n/**\n * Create a filesystem {@link ContentAdapter} for `createSyncPlugin`.\n *\n * Handles glob discovery of the source pattern, key/locale extraction from\n * paths, base-directory resolution, and change-detected atomic writes.\n */\nexport const createFileAdapter = (\n options: CreateFileAdapterOptions\n): ContentAdapter => {\n const { source, codec, discovery = 'strict' } = options;\n const readEntry = options.readEntry ?? createFileReader(codec.parse);\n\n let patternMarkerPromise: Promise<string> | undefined;\n\n /**\n * Source pattern with `{{key}}` / `{{locale}}` markers kept verbatim,\n * memoized because pattern functions can be asynchronous.\n */\n const getPatternMarker = () => {\n patternMarkerPromise ??= parseFilePathPattern(\n source,\n buildFilePathPatternContext('{{key}}', '{{locale}}')\n );\n\n return patternMarkerPromise;\n };\n\n const listMessages = async (\n context: SyncPluginContext\n ): Promise<MessagesRecord> => {\n const { system, internationalization } = context.configuration;\n const { baseDir } = system;\n const { locales } = internationalization;\n\n const result: MessagesRecord = {} as MessagesRecord;\n\n for (const locale of locales) {\n const globPatternLocale = await parseFilePathPattern(\n source,\n buildFilePathPatternContext('**', locale)\n );\n\n const maskPatternLocale = await parseFilePathPattern(\n source,\n buildFilePathPatternContext('{{__KEY__}}', locale)\n );\n\n if (!globPatternLocale || !maskPatternLocale) {\n continue;\n }\n\n const normalizedGlobPattern = globPatternLocale.startsWith('./')\n ? globPatternLocale.slice(2)\n : globPatternLocale;\n\n const files = await fg(normalizedGlobPattern, {\n cwd: baseDir,\n });\n\n const hasLocaleInMask = maskPatternLocale.includes('{{__LOCALE__}}');\n const hasKeyInMask = maskPatternLocale.includes('{{__KEY__}}');\n\n for (const file of files) {\n let key: string;\n let extractedLocale: Locale;\n\n if (hasLocaleInMask || hasKeyInMask) {\n const extraction = extractKeyAndLocaleFromPath(\n file,\n maskPatternLocale,\n locales,\n locale\n );\n\n if (!extraction) {\n continue;\n }\n\n key = extraction.key;\n extractedLocale = extraction.locale;\n } else if (discovery === 'inclusive') {\n // Mask has no placeholders — the file was found via a concrete\n // locale glob. Attribute it directly to the current loop locale.\n key = 'index';\n extractedLocale = locale;\n } else {\n // Strict mode relies on the fabrication step below to attribute\n // placeholder-free sources to their canonical paths.\n continue;\n }\n\n const absoluteFoundPath = isAbsolute(file)\n ? file\n : resolve(baseDir, file);\n\n if (discovery === 'strict') {\n // Rebuild what the path SHOULD be for this key/locale. A mismatch\n // means the file belongs to another plugin/structure.\n const expectedPath = await parseFilePathPattern(\n source,\n buildFilePathPatternContext(key, extractedLocale)\n );\n\n const absoluteExpectedPath = isAbsolute(expectedPath)\n ? expectedPath\n : resolve(baseDir, expectedPath);\n\n if (absoluteFoundPath !== absoluteExpectedPath) {\n continue;\n }\n }\n\n result[extractedLocale] ??= {};\n result[extractedLocale][key] = absoluteFoundPath;\n }\n }\n\n if (discovery === 'inclusive') {\n // Read-only mode only uses actual discovered files; do not fabricate\n // missing locales or keys, since no outputs are written.\n return result;\n }\n\n // Ensure all declared locales are present even if the file doesn't exist yet\n const maskWithKey = await parseFilePathPattern(\n source,\n buildFilePathPatternContext('{{__KEY__}}', locales[0])\n );\n\n const hasKeyInMask = maskWithKey.includes('{{__KEY__}}');\n const discoveredKeys = new Set<string>();\n\n for (const locale of Object.keys(result) as Locale[]) {\n for (const key of Object.keys(result[locale] ?? {})) {\n discoveredKeys.add(key);\n }\n }\n\n if (!hasKeyInMask) {\n discoveredKeys.add('index');\n }\n\n for (const locale of locales) {\n result[locale] ??= {};\n\n for (const key of discoveredKeys) {\n if (!result[locale][key]) {\n const builtPath = await parseFilePathPattern(\n source,\n buildFilePathPatternContext(key, locale)\n );\n\n result[locale][key] = isAbsolute(builtPath)\n ? builtPath\n : resolve(baseDir, builtPath);\n }\n }\n }\n\n return result;\n };\n\n return {\n list: async (context) => {\n const messages = await listMessages(context);\n\n const entries: ContentEntry[] = (\n Object.entries(messages) as [Locale, Record<string, FilePath>][]\n ).flatMap(([locale, keysRecord]) =>\n Object.entries(keysRecord).map(([key, absoluteFilePath]) => ({\n key,\n locale,\n uri: absoluteFilePath,\n filePath: absoluteFilePath,\n }))\n );\n\n return entries;\n },\n\n read: async (entry, context) => await readEntry(entry.uri, context),\n\n write: async ({ key, locale }, content, { configuration }) => {\n const builderPath = await parseFilePathPattern(\n source,\n buildFilePathPatternContext(key, locale)\n );\n\n const absoluteFilePath = resolve(\n configuration.system.baseDir,\n builderPath\n );\n\n await mkdir(dirname(absoluteFilePath), { recursive: true });\n\n await writeFileIfChanged(\n absoluteFilePath,\n codec.serialize(content, { locale }),\n { tempDir: configuration.system?.tempDir }\n );\n },\n\n resolveUri: async ({ key, locale }, { configuration }) => {\n const builderPath = await parseFilePathPattern(\n source,\n buildFilePathPatternContext(key, locale)\n );\n\n return resolve(configuration.system.baseDir, builderPath);\n },\n\n getFillPattern: async () => await getPatternMarker(),\n\n describeSource: async () => await getPatternMarker(),\n\n detectSplitKeys: async () =>\n !(await getPatternMarker()).includes('{{key}}'),\n };\n};\n\n/**\n * Default `readEntry` implementation: plain utf-8 read followed by the given\n * parser. Returns `undefined` for missing or unreadable files.\n */\nexport const createFileReader =\n (parse: (raw: string) => SyncContent) =>\n async (absoluteFilePath: string): Promise<SyncContent | undefined> => {\n try {\n const raw = await readFile(absoluteFilePath, 'utf-8');\n\n return parse(raw);\n } catch {\n return undefined;\n }\n };\n"],"mappings":"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"}
@@ -0,0 +1,152 @@
1
+ import { relative, resolve } from "node:path";
2
+ import { colorizePath, getAppLogger } from "@intlayer/config/logger";
3
+
4
+ //#region src/syncPluginKit/createSyncPlugin.ts
5
+ /**
6
+ * Content that carries nothing to persist: `undefined`, `null`, or an object
7
+ * without any own key. Writing it back would erase the target.
8
+ */
9
+ const isEmptyContent = (content) => typeof content === "undefined" || content === null || typeof content === "object" && Object.keys(content).length === 0;
10
+ /**
11
+ * Deep-clone formatted output into plain JSON data, stripping prototypes and
12
+ * non-serializable values before the emptiness check and the write.
13
+ */
14
+ const toPlainContent = (content) => typeof content === "undefined" ? void 0 : JSON.parse(JSON.stringify(content));
15
+ /**
16
+ * Build an Intlayer {@link Plugin} from a transport adapter.
17
+ *
18
+ * The factory implements the three plugin hooks once — ingestion
19
+ * (`loadDictionaries`), content-declaration reformatting (`formatOutput`) and
20
+ * write-back (`afterBuild`) — so adapters only describe *where* content lives
21
+ * (filesystem, TMS such as Crowdin, extra CMS…) and codecs only describe the
22
+ * payload format. Write-back only processes dictionaries whose `location`
23
+ * matches this plugin instance, so adapters never see foreign content.
24
+ */
25
+ const createSyncPlugin = (options) => {
26
+ const { name, adapter, direction = "both", location, priority = 0, format, localeOverride } = options;
27
+ let splitKeysPromise;
28
+ /**
29
+ * `splitKeys` resolution is asynchronous (adapter auto-detection may parse
30
+ * an async source pattern), so it is resolved lazily and memoized.
31
+ */
32
+ const resolveSplitKeys = () => {
33
+ splitKeysPromise ??= (async () => options.splitKeys ?? await adapter.detectSplitKeys?.() ?? false)();
34
+ return splitKeysPromise;
35
+ };
36
+ const loadDictionaries = async ({ configuration }) => {
37
+ const context = { configuration };
38
+ const appLogger = getAppLogger(configuration);
39
+ const entries = await adapter.list(context);
40
+ if (entries.length === 0) appLogger(`[${name}] No dictionaries found at locations matching source pattern: ${colorizePath(await adapter.describeSource?.(context) ?? name)}`, { level: "warn" });
41
+ const shouldSplitByKeys = await resolveSplitKeys();
42
+ const { baseDir } = configuration.system;
43
+ const { defaultLocale } = configuration.internationalization;
44
+ let patternFill;
45
+ if (direction === "both" && adapter.getFillPattern) patternFill = relative(baseDir, resolve(baseDir, await adapter.getFillPattern(context)));
46
+ const dictionaries = [];
47
+ for (const entry of entries) {
48
+ const content = await adapter.read(entry, context) ?? {};
49
+ const relativeFilePath = entry.filePath ? relative(baseDir, entry.filePath) : void 0;
50
+ const usedLocale = localeOverride ?? entry.locale;
51
+ const filled = usedLocale !== defaultLocale ? true : void 0;
52
+ const fill = patternFill ?? relativeFilePath;
53
+ const identifier = relativeFilePath ?? entry.uri;
54
+ if (shouldSplitByKeys) {
55
+ for (const [namespaceKey, namespaceContent] of Object.entries(content)) dictionaries.push({
56
+ key: namespaceKey,
57
+ locale: usedLocale,
58
+ fill,
59
+ format,
60
+ localId: `${namespaceKey}::${location}::${identifier}`,
61
+ location,
62
+ filled,
63
+ content: namespaceContent,
64
+ filePath: relativeFilePath,
65
+ priority
66
+ });
67
+ continue;
68
+ }
69
+ dictionaries.push({
70
+ key: entry.key,
71
+ locale: usedLocale,
72
+ fill,
73
+ format,
74
+ localId: `${entry.key}::${location}::${identifier}`,
75
+ location,
76
+ filled,
77
+ content,
78
+ filePath: relativeFilePath,
79
+ priority
80
+ });
81
+ }
82
+ return dictionaries;
83
+ };
84
+ if (direction === "pull") return {
85
+ name,
86
+ loadDictionaries
87
+ };
88
+ const formatOutput = async ({ dictionary, configuration }) => {
89
+ if (!dictionary.filePath || !dictionary.locale) return dictionary;
90
+ if (await resolveSplitKeys()) return dictionary;
91
+ if (!adapter.resolveUri) return dictionary;
92
+ const canonicalUri = await adapter.resolveUri({
93
+ key: dictionary.key,
94
+ locale: dictionary.locale
95
+ }, { configuration });
96
+ const { baseDir } = configuration.system;
97
+ if (resolve(baseDir, canonicalUri) !== resolve(baseDir, dictionary.filePath)) return dictionary;
98
+ const { formatDictionaryOutput } = await import("../formatDictionary.mjs");
99
+ return formatDictionaryOutput(dictionary, format).content;
100
+ };
101
+ const afterBuild = async ({ dictionaries, configuration }) => {
102
+ const { getPerLocaleDictionary } = await import("@intlayer/core/plugins");
103
+ const { formatDictionaryOutput } = await import("../formatDictionary.mjs");
104
+ const { parallelize } = await import("../utils/parallelize.mjs");
105
+ const context = { configuration };
106
+ const { locales } = configuration.internationalization;
107
+ const ownedDictionaries = Object.entries(dictionaries.mergedDictionaries).map(([key, result]) => ({
108
+ key,
109
+ dictionary: result.dictionary
110
+ })).filter(({ dictionary }) => dictionary.location === location);
111
+ if (await resolveSplitKeys()) {
112
+ const mergedContentByLocale = {};
113
+ const writeKeyByLocale = {};
114
+ for (const { key, dictionary } of ownedDictionaries) for (const locale of locales) {
115
+ const content = toPlainContent(formatDictionaryOutput(getPerLocaleDictionary(dictionary, locale), format).content);
116
+ if (isEmptyContent(content)) continue;
117
+ mergedContentByLocale[locale] ??= {};
118
+ mergedContentByLocale[locale][key] = content;
119
+ writeKeyByLocale[locale] = key;
120
+ }
121
+ await parallelize(Object.keys(mergedContentByLocale), async (locale) => {
122
+ await adapter.write({
123
+ key: writeKeyByLocale[locale],
124
+ locale
125
+ }, mergedContentByLocale[locale], context);
126
+ });
127
+ return;
128
+ }
129
+ await parallelize(ownedDictionaries.flatMap(({ key, dictionary }) => locales.map((locale) => ({
130
+ key,
131
+ dictionary,
132
+ locale
133
+ }))), async ({ key, dictionary, locale }) => {
134
+ const content = toPlainContent(formatDictionaryOutput(getPerLocaleDictionary(dictionary, locale), format).content);
135
+ if (isEmptyContent(content)) return;
136
+ await adapter.write({
137
+ key,
138
+ locale
139
+ }, content, context);
140
+ });
141
+ };
142
+ return {
143
+ name,
144
+ loadDictionaries,
145
+ formatOutput,
146
+ afterBuild
147
+ };
148
+ };
149
+
150
+ //#endregion
151
+ export { createSyncPlugin };
152
+ //# sourceMappingURL=createSyncPlugin.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createSyncPlugin.mjs","names":[],"sources":["../../../src/syncPluginKit/createSyncPlugin.ts"],"sourcesContent":["import { relative, resolve } from 'node:path';\nimport { colorizePath, getAppLogger } from '@intlayer/config/logger';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { Dictionary, LocalDictionaryId } from '@intlayer/types/dictionary';\nimport type { Plugin } from '@intlayer/types/plugin';\nimport type {\n CreateSyncPluginOptions,\n SyncContent,\n SyncPluginContext,\n} from './types';\n\n/**\n * Content that carries nothing to persist: `undefined`, `null`, or an object\n * without any own key. Writing it back would erase the target.\n */\nconst isEmptyContent = (content: unknown): boolean =>\n typeof content === 'undefined' ||\n content === null ||\n (typeof content === 'object' && Object.keys(content).length === 0);\n\n/**\n * Deep-clone formatted output into plain JSON data, stripping prototypes and\n * non-serializable values before the emptiness check and the write.\n */\nconst toPlainContent = (content: unknown): unknown =>\n typeof content === 'undefined'\n ? undefined\n : JSON.parse(JSON.stringify(content));\n\n/**\n * Build an Intlayer {@link Plugin} from a transport adapter.\n *\n * The factory implements the three plugin hooks once — ingestion\n * (`loadDictionaries`), content-declaration reformatting (`formatOutput`) and\n * write-back (`afterBuild`) — so adapters only describe *where* content lives\n * (filesystem, TMS such as Crowdin, extra CMS…) and codecs only describe the\n * payload format. Write-back only processes dictionaries whose `location`\n * matches this plugin instance, so adapters never see foreign content.\n */\nexport const createSyncPlugin = (options: CreateSyncPluginOptions): Plugin => {\n const {\n name,\n adapter,\n direction = 'both',\n location,\n priority = 0,\n format,\n localeOverride,\n } = options;\n\n let splitKeysPromise: Promise<boolean> | undefined;\n\n /**\n * `splitKeys` resolution is asynchronous (adapter auto-detection may parse\n * an async source pattern), so it is resolved lazily and memoized.\n */\n const resolveSplitKeys = (): Promise<boolean> => {\n splitKeysPromise ??= (async () =>\n options.splitKeys ?? (await adapter.detectSplitKeys?.()) ?? false)();\n\n return splitKeysPromise;\n };\n\n const loadDictionaries: Plugin['loadDictionaries'] = async ({\n configuration,\n }) => {\n const context: SyncPluginContext = { configuration };\n const appLogger = getAppLogger(configuration);\n\n const entries = await adapter.list(context);\n\n if (entries.length === 0) {\n const sourceDescription =\n (await adapter.describeSource?.(context)) ?? name;\n\n appLogger(\n `[${name}] No dictionaries found at locations matching source pattern: ${colorizePath(sourceDescription)}`,\n { level: 'warn' }\n );\n }\n\n const shouldSplitByKeys = await resolveSplitKeys();\n const { baseDir } = configuration.system;\n const { defaultLocale } = configuration.internationalization;\n\n // Sync plugins advertise the source pattern (with {{key}}/{{locale}}\n // markers) as fill target; pull-only plugins fill each entry in place.\n let patternFill: string | undefined;\n\n if (direction === 'both' && adapter.getFillPattern) {\n const fillPattern = await adapter.getFillPattern(context);\n patternFill = relative(baseDir, resolve(baseDir, fillPattern));\n }\n\n const dictionaries: Dictionary[] = [];\n\n for (const entry of entries) {\n const content = (await adapter.read(entry, context)) ?? {};\n\n const relativeFilePath = entry.filePath\n ? relative(baseDir, entry.filePath)\n : undefined;\n\n const usedLocale = (localeOverride ?? entry.locale) as Locale;\n const filled = usedLocale !== defaultLocale ? true : undefined;\n const fill = patternFill ?? relativeFilePath;\n const identifier = relativeFilePath ?? entry.uri;\n\n // One entry groups several namespaces by its first-level keys: emit one\n // dictionary per top-level key (e.g. `Hero`, `Nav`, …).\n if (shouldSplitByKeys) {\n for (const [namespaceKey, namespaceContent] of Object.entries(\n content\n )) {\n dictionaries.push({\n key: namespaceKey,\n locale: usedLocale,\n fill,\n format,\n localId:\n `${namespaceKey}::${location}::${identifier}` as LocalDictionaryId,\n location: location as Dictionary['location'],\n filled,\n content: namespaceContent as SyncContent,\n filePath: relativeFilePath,\n priority,\n } as Dictionary);\n }\n continue;\n }\n\n dictionaries.push({\n key: entry.key,\n locale: usedLocale,\n fill,\n format,\n localId:\n `${entry.key}::${location}::${identifier}` as LocalDictionaryId,\n location: location as Dictionary['location'],\n filled,\n content,\n filePath: relativeFilePath,\n priority,\n } as Dictionary);\n }\n\n return dictionaries;\n };\n\n if (direction === 'pull') {\n return { name, loadDictionaries };\n }\n\n const formatOutput: Plugin['formatOutput'] = async ({\n dictionary,\n configuration,\n }) => {\n if (!dictionary.filePath || !dictionary.locale) return dictionary;\n\n // In split mode several namespaces share the same target; the target is\n // re-assembled in `afterBuild`. Skip here to avoid overwriting the whole\n // target with a single namespace.\n if (await resolveSplitKeys()) return dictionary;\n\n // Ownership check: only reformat declarations this adapter can identify\n // as its own canonical target.\n if (!adapter.resolveUri) return dictionary;\n\n const canonicalUri = await adapter.resolveUri(\n { key: dictionary.key, locale: dictionary.locale as Locale },\n { configuration }\n );\n\n const { baseDir } = configuration.system;\n\n if (\n resolve(baseDir, canonicalUri) !== resolve(baseDir, dictionary.filePath)\n ) {\n return dictionary;\n }\n\n // Lazy import to keep the module graph light when configs are transpiled\n const { formatDictionaryOutput } = await import('../formatDictionary');\n\n return formatDictionaryOutput(dictionary as Dictionary, format).content;\n };\n\n const afterBuild: Plugin['afterBuild'] = async ({\n dictionaries,\n configuration,\n }) => {\n // Lazy imports to keep the module graph light when configs are transpiled\n const { getPerLocaleDictionary } = await import('@intlayer/core/plugins');\n const { formatDictionaryOutput } = await import('../formatDictionary');\n const { parallelize } = await import('../utils/parallelize');\n\n const context: SyncPluginContext = { configuration };\n const { locales } = configuration.internationalization;\n\n // Only ever hand the adapter dictionaries owned by THIS plugin instance.\n const ownedDictionaries = Object.entries(dictionaries.mergedDictionaries)\n .map(([key, result]) => ({\n key,\n dictionary: result.dictionary as Dictionary,\n }))\n .filter(({ dictionary }) => dictionary.location === location);\n\n if (await resolveSplitKeys()) {\n // Split mode: every namespace dictionary writes back into the same\n // per-locale target. Re-assemble them under their top-level key and\n // write each target once, instead of one write per key (which would\n // overwrite).\n const mergedContentByLocale = {} as Record<Locale, SyncContent>;\n const writeKeyByLocale = {} as Record<Locale, string>;\n\n for (const { key, dictionary } of ownedDictionaries) {\n for (const locale of locales) {\n const localizedDictionary = getPerLocaleDictionary(\n dictionary,\n locale\n );\n\n const formattedOutput = formatDictionaryOutput(\n localizedDictionary,\n format\n );\n\n const content = toPlainContent(formattedOutput.content);\n\n if (isEmptyContent(content)) continue;\n\n mergedContentByLocale[locale] ??= {};\n mergedContentByLocale[locale][key] = content;\n writeKeyByLocale[locale] = key;\n }\n }\n\n await parallelize(\n Object.keys(mergedContentByLocale) as Locale[],\n async (locale) => {\n await adapter.write(\n { key: writeKeyByLocale[locale], locale },\n mergedContentByLocale[locale],\n context\n );\n }\n );\n\n return;\n }\n\n const writeTasks = ownedDictionaries.flatMap(({ key, dictionary }) =>\n locales.map((locale) => ({ key, dictionary, locale }))\n );\n\n await parallelize(writeTasks, async ({ key, dictionary, locale }) => {\n const localizedDictionary = getPerLocaleDictionary(dictionary, locale);\n\n const formattedOutput = formatDictionaryOutput(\n localizedDictionary,\n format\n );\n\n const content = toPlainContent(formattedOutput.content);\n\n if (isEmptyContent(content)) return;\n\n await adapter.write({ key, locale }, content as SyncContent, context);\n });\n };\n\n return { name, loadDictionaries, formatOutput, afterBuild };\n};\n"],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ //#region src/syncPluginKit/extractKeyAndLocaleFromPath.ts
2
+ const escapeRegex = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3
+ /**
4
+ * Extract the dictionary key and locale of a file path by matching it against
5
+ * a mask pattern where `{{__KEY__}}` and `{{__LOCALE__}}` mark the positions
6
+ * of the key and locale segments.
7
+ *
8
+ * Returns `null` when the path does not match the mask, or when the mask
9
+ * contains no placeholder at all (no named capture group can be produced).
10
+ * Missing placeholders fall back to `'index'` for the key and to
11
+ * `defaultLocale` for the locale.
12
+ */
13
+ const extractKeyAndLocaleFromPath = (filePath, maskPattern, locales, defaultLocale) => {
14
+ const keyPlaceholder = "{{__KEY__}}";
15
+ const localePlaceholder = "{{__LOCALE__}}";
16
+ const normalize = (path) => path.startsWith("./") ? path.slice(2) : path;
17
+ const normalizedFilePath = normalize(filePath);
18
+ const normalizedMask = normalize(maskPattern);
19
+ const localesAlternation = locales.join("|");
20
+ let regexString = `^${escapeRegex(normalizedMask)}$`;
21
+ regexString = regexString.replace(/\\\*\\\*/g, ".*");
22
+ regexString = regexString.replace(/\\\*/g, "[^/]*");
23
+ regexString = regexString.replace(escapeRegex(localePlaceholder), `(?<locale>${localesAlternation})`);
24
+ if (normalizedMask.includes(keyPlaceholder)) regexString = regexString.replace(escapeRegex(keyPlaceholder), "(?<key>.+)");
25
+ const match = new RegExp(regexString).exec(normalizedFilePath);
26
+ if (!match?.groups) return null;
27
+ return {
28
+ key: match.groups.key ?? "index",
29
+ locale: match.groups.locale ?? defaultLocale
30
+ };
31
+ };
32
+
33
+ //#endregion
34
+ export { extractKeyAndLocaleFromPath };
35
+ //# sourceMappingURL=extractKeyAndLocaleFromPath.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractKeyAndLocaleFromPath.mjs","names":[],"sources":["../../../src/syncPluginKit/extractKeyAndLocaleFromPath.ts"],"sourcesContent":["import type { Locale } from '@intlayer/types/allLocales';\n\nconst escapeRegex = (text: string) =>\n text.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n/**\n * Extract the dictionary key and locale of a file path by matching it against\n * a mask pattern where `{{__KEY__}}` and `{{__LOCALE__}}` mark the positions\n * of the key and locale segments.\n *\n * Returns `null` when the path does not match the mask, or when the mask\n * contains no placeholder at all (no named capture group can be produced).\n * Missing placeholders fall back to `'index'` for the key and to\n * `defaultLocale` for the locale.\n */\nexport const extractKeyAndLocaleFromPath = (\n filePath: string,\n maskPattern: string,\n locales: Locale[],\n defaultLocale: Locale\n): { key: string; locale: Locale } | null => {\n const keyPlaceholder = '{{__KEY__}}';\n const localePlaceholder = '{{__LOCALE__}}';\n\n // fast-glob strips leading \"./\" from returned paths; normalize both sides\n const normalize = (path: string) =>\n path.startsWith('./') ? path.slice(2) : path;\n\n const normalizedFilePath = normalize(filePath);\n const normalizedMask = normalize(maskPattern);\n\n const localesAlternation = locales.join('|');\n\n // Escape special regex chars, then convert glob wildcards to regex equivalents.\n // Must replace ** before * to avoid double-replacing.\n let regexString = `^${escapeRegex(normalizedMask)}$`;\n regexString = regexString.replace(/\\\\\\*\\\\\\*/g, '.*'); // ** → match any path segments\n regexString = regexString.replace(/\\\\\\*/g, '[^/]*'); // * → match within a single segment\n\n regexString = regexString.replace(\n escapeRegex(localePlaceholder),\n `(?<locale>${localesAlternation})`\n );\n\n if (normalizedMask.includes(keyPlaceholder)) {\n regexString = regexString.replace(\n escapeRegex(keyPlaceholder),\n '(?<key>.+)'\n );\n }\n\n const maskRegex = new RegExp(regexString);\n const match = maskRegex.exec(normalizedFilePath);\n\n if (!match?.groups) {\n return null;\n }\n\n const key = (match.groups.key as string | undefined) ?? 'index';\n const locale = (match.groups.locale as Locale | undefined) ?? defaultLocale;\n\n return { key, locale };\n};\n"],"mappings":";AAEA,MAAM,eAAe,SACnB,KAAK,QAAQ,uBAAuB,OAAO;;;;;;;;;;;AAY7C,MAAa,+BACX,UACA,aACA,SACA,kBAC2C;CAC3C,MAAM,iBAAiB;CACvB,MAAM,oBAAoB;CAG1B,MAAM,aAAa,SACjB,KAAK,WAAW,KAAK,GAAG,KAAK,MAAM,EAAE,GAAG;CAE1C,MAAM,qBAAqB,UAAU,SAAS;CAC9C,MAAM,iBAAiB,UAAU,YAAY;CAE7C,MAAM,qBAAqB,QAAQ,KAAK,IAAI;CAI5C,IAAI,cAAc,IAAI,YAAY,eAAe,CAAC;AAClD,eAAc,YAAY,QAAQ,aAAa,KAAK;AACpD,eAAc,YAAY,QAAQ,SAAS,QAAQ;AAEnD,eAAc,YAAY,QACxB,YAAY,kBAAkB,EAC9B,aAAa,mBAAmB,GACjC;AAED,KAAI,eAAe,SAAS,eAAe,CACzC,eAAc,YAAY,QACxB,YAAY,eAAe,EAC3B,aACD;CAIH,MAAM,QAAQ,IADQ,OAAO,YACN,CAAC,KAAK,mBAAmB;AAEhD,KAAI,CAAC,OAAO,OACV,QAAO;AAMT,QAAO;EAAE,KAHI,MAAM,OAAO,OAA8B;EAG1C,QAFE,MAAM,OAAO,UAAiC;EAExC"}
@@ -0,0 +1,18 @@
1
+ //#region src/syncPluginKit/filePathPatternHelpers.ts
2
+ /**
3
+ * Build the context handed to `parseFilePathPattern` from the only two
4
+ * variables sync plugins resolve: the dictionary key and the locale.
5
+ *
6
+ * `FilePathPatternContext` declares extra fields (`fileName`, `extension`,
7
+ * `format`…) that only apply to content-declaration paths; sync-plugin
8
+ * patterns never reference them, so this single cast keeps the partial
9
+ * context in one audited place instead of scattering `as any` at call sites.
10
+ */
11
+ const buildFilePathPatternContext = (key, locale) => ({
12
+ key,
13
+ locale
14
+ });
15
+
16
+ //#endregion
17
+ export { buildFilePathPatternContext };
18
+ //# sourceMappingURL=filePathPatternHelpers.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filePathPatternHelpers.mjs","names":[],"sources":["../../../src/syncPluginKit/filePathPatternHelpers.ts"],"sourcesContent":["import type { FilePathPatternContext } from '@intlayer/types/filePathPattern';\n\n/**\n * Build the context handed to `parseFilePathPattern` from the only two\n * variables sync plugins resolve: the dictionary key and the locale.\n *\n * `FilePathPatternContext` declares extra fields (`fileName`, `extension`,\n * `format`…) that only apply to content-declaration paths; sync-plugin\n * patterns never reference them, so this single cast keeps the partial\n * context in one audited place instead of scattering `as any` at call sites.\n */\nexport const buildFilePathPatternContext = (\n key: string,\n locale?: string\n): FilePathPatternContext =>\n ({\n key,\n locale,\n }) as Partial<FilePathPatternContext> as FilePathPatternContext;\n"],"mappings":";;;;;;;;;;AAWA,MAAa,+BACX,KACA,YAEC;CACC;CACA;CACD"}
@@ -0,0 +1,6 @@
1
+ import { createSyncPlugin } from "./createSyncPlugin.mjs";
2
+ import { extractKeyAndLocaleFromPath } from "./extractKeyAndLocaleFromPath.mjs";
3
+ import { buildFilePathPatternContext } from "./filePathPatternHelpers.mjs";
4
+ import { createFileAdapter, createFileReader } from "./createFileAdapter.mjs";
5
+
6
+ export { buildFilePathPatternContext, createFileAdapter, createFileReader, createSyncPlugin, extractKeyAndLocaleFromPath };
File without changes
@@ -64,9 +64,9 @@ const runParallel = (proc) => {
64
64
  }));
65
65
  });
66
66
  });
67
- const kill = () => {
67
+ const kill = (signal = "SIGTERM") => {
68
68
  try {
69
- child.kill("SIGTERM");
69
+ child.kill(signal);
70
70
  } catch {}
71
71
  };
72
72
  const handleExit = () => kill();
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/utils/runParallel/index.ts"],"sourcesContent":["import { delimiter, join } from 'node:path';\nimport { spawnPosix } from './spawnPosix';\nimport { spawnWin32 } from './spawnWin32';\n\nexport type ParallelHandle = {\n kill: () => void;\n result: Promise<any>;\n commandText: string;\n};\n\n/**\n * Start a cross-platform parallel process using npm-run-all approach.\n * Accepts either a single string (e.g., 'next start') or an array of tokens (e.g., ['next', 'start']).\n */\nexport const runParallel = (proc?: string | string[]): ParallelHandle => {\n if (!proc || (Array.isArray(proc) && proc.length === 0))\n throw new Error('Invalid command');\n\n const commandText = Array.isArray(proc) ? proc.join(' ') : proc;\n\n const isArray = Array.isArray(proc);\n const command = isArray ? (proc as string[])[0] : commandText;\n const args = isArray ? (proc as string[]).slice(1) : [];\n\n // Ensure local binaries (node_modules/.bin) are resolvable\n const cwdBin = join(process.cwd(), 'node_modules', '.bin');\n const PATH_KEY =\n Object.keys(process.env).find((key) => key.toLowerCase() === 'path') ??\n 'PATH';\n\n const extendedPath = [cwdBin, process.env[PATH_KEY] ?? '']\n .filter(Boolean)\n .join(delimiter);\n\n const childEnv = {\n ...process.env,\n [PATH_KEY]: extendedPath,\n } as NodeJS.ProcessEnv;\n\n const isWin = process.platform === 'win32';\n const spawnFunc = isWin ? spawnWin32 : spawnPosix;\n\n // Spawn options\n const spawnOptions = {\n cwd: process.cwd(),\n stdio: 'inherit' as const,\n env: childEnv,\n shell: isWin,\n };\n\n // Spawn the child process\n const child = isArray\n ? // If provided as a single string element that includes spaces, treat it like a shell command\n args.length === 0 && /\\s/.test(command)\n ? isWin\n ? spawnFunc(\n process.env.ComSpec ?? 'cmd.exe',\n ['/d', '/s', '/c', command],\n spawnOptions\n )\n : spawnFunc(\n process.env.SHELL ?? '/bin/sh',\n ['-c', command],\n spawnOptions\n )\n : spawnFunc(command, args, spawnOptions)\n : isWin\n ? spawnFunc(\n process.env.ComSpec ?? 'cmd.exe',\n ['/d', '/s', '/c', commandText],\n spawnOptions\n )\n : spawnFunc(\n process.env.SHELL ?? '/bin/sh',\n ['-c', commandText],\n spawnOptions\n );\n\n const result = new Promise<void>((resolve, reject) => {\n child.on('error', (err) => {\n try {\n console.error(\n `[runParallel] Failed to start: ${err?.message ?? String(err)}`\n );\n } catch {}\n reject(err);\n });\n\n child.on('exit', (code, signal) => {\n // Treat common termination signals as graceful exits, not failures\n const gracefulSignals = ['SIGINT', 'SIGTERM', 'SIGQUIT', 'SIGHUP'];\n // Also treat shell-convention exit codes (128 + signal number) as graceful:\n // 129 = SIGHUP, 130 = SIGINT, 131 = SIGQUIT, 143 = SIGTERM\n const gracefulSignalCodes = new Set([129, 130, 131, 143]);\n if (\n code === 0 ||\n gracefulSignalCodes.has(code ?? -1) ||\n (signal && gracefulSignals.includes(signal))\n ) {\n resolve();\n } else {\n reject(\n Object.assign(new Error('Parallel process failed'), { code, signal })\n );\n }\n });\n });\n\n const kill = () => {\n try {\n child.kill('SIGTERM');\n } catch {\n // Best effort\n }\n };\n\n const handleExit = () => kill();\n\n const handleSigInt = () => {\n kill();\n process.off('SIGINT', handleSigInt);\n process.kill(process.pid, 'SIGINT'); // Propagate to allow natural shutdown\n };\n\n const handleSigTerm = () => {\n kill();\n process.off('SIGTERM', handleSigTerm);\n process.kill(process.pid, 'SIGTERM'); // Propagate to allow natural shutdown\n };\n\n process.on('exit', handleExit);\n process.on('SIGINT', handleSigInt);\n process.on('SIGTERM', handleSigTerm);\n\n child.on('exit', () => {\n process.off('exit', handleExit);\n process.off('SIGINT', handleSigInt);\n process.off('SIGTERM', handleSigTerm);\n });\n\n return { kill, result, commandText };\n};\n"],"mappings":";;;;;;;;;AAcA,MAAa,eAAe,SAA6C;AACvE,KAAI,CAAC,QAAS,MAAM,QAAQ,KAAK,IAAI,KAAK,WAAW,EACnD,OAAM,IAAI,MAAM,kBAAkB;CAEpC,MAAM,cAAc,MAAM,QAAQ,KAAK,GAAG,KAAK,KAAK,IAAI,GAAG;CAE3D,MAAM,UAAU,MAAM,QAAQ,KAAK;CACnC,MAAM,UAAU,UAAW,KAAkB,KAAK;CAClD,MAAM,OAAO,UAAW,KAAkB,MAAM,EAAE,GAAG,EAAE;CAGvD,MAAM,SAAS,KAAK,QAAQ,KAAK,EAAE,gBAAgB,OAAO;CAC1D,MAAM,WACJ,OAAO,KAAK,QAAQ,IAAI,CAAC,MAAM,QAAQ,IAAI,aAAa,KAAK,OAAO,IACpE;CAEF,MAAM,eAAe,CAAC,QAAQ,QAAQ,IAAI,aAAa,GAAG,CACvD,OAAO,QAAQ,CACf,KAAK,UAAU;CAElB,MAAM,WAAW;EACf,GAAG,QAAQ;GACV,WAAW;EACb;CAED,MAAM,QAAQ,QAAQ,aAAa;CACnC,MAAM,YAAY,QAAQ,aAAa;CAGvC,MAAM,eAAe;EACnB,KAAK,QAAQ,KAAK;EAClB,OAAO;EACP,KAAK;EACL,OAAO;EACR;CAGD,MAAM,QAAQ,UAEV,KAAK,WAAW,KAAK,KAAK,KAAK,QAAQ,GACrC,QACE,UACE,QAAQ,IAAI,WAAW,WACvB;EAAC;EAAM;EAAM;EAAM;EAAQ,EAC3B,aACD,GACD,UACE,QAAQ,IAAI,SAAS,WACrB,CAAC,MAAM,QAAQ,EACf,aACD,GACH,UAAU,SAAS,MAAM,aAAa,GACxC,QACE,UACE,QAAQ,IAAI,WAAW,WACvB;EAAC;EAAM;EAAM;EAAM;EAAY,EAC/B,aACD,GACD,UACE,QAAQ,IAAI,SAAS,WACrB,CAAC,MAAM,YAAY,EACnB,aACD;CAEP,MAAM,SAAS,IAAI,SAAe,SAAS,WAAW;AACpD,QAAM,GAAG,UAAU,QAAQ;AACzB,OAAI;AACF,YAAQ,MACN,kCAAkC,KAAK,WAAW,OAAO,IAAI,GAC9D;WACK;AACR,UAAO,IAAI;IACX;AAEF,QAAM,GAAG,SAAS,MAAM,WAAW;AAMjC,OACE,SAAS,KACT,IAH8B,IAAI;IAAC;IAAK;IAAK;IAAK;IAAI,CAGnC,CAAC,IAAI,QAAQ,GAAG,IAClC,UAAU;IAPY;IAAU;IAAW;IAAW;IAO7B,CAAC,SAAS,OAAO,CAE3C,UAAS;OAET,QACE,OAAO,uBAAO,IAAI,MAAM,0BAA0B,EAAE;IAAE;IAAM;IAAQ,CAAC,CACtE;IAEH;GACF;CAEF,MAAM,aAAa;AACjB,MAAI;AACF,SAAM,KAAK,UAAU;UACf;;CAKV,MAAM,mBAAmB,MAAM;CAE/B,MAAM,qBAAqB;AACzB,QAAM;AACN,UAAQ,IAAI,UAAU,aAAa;AACnC,UAAQ,KAAK,QAAQ,KAAK,SAAS;;CAGrC,MAAM,sBAAsB;AAC1B,QAAM;AACN,UAAQ,IAAI,WAAW,cAAc;AACrC,UAAQ,KAAK,QAAQ,KAAK,UAAU;;AAGtC,SAAQ,GAAG,QAAQ,WAAW;AAC9B,SAAQ,GAAG,UAAU,aAAa;AAClC,SAAQ,GAAG,WAAW,cAAc;AAEpC,OAAM,GAAG,cAAc;AACrB,UAAQ,IAAI,QAAQ,WAAW;AAC/B,UAAQ,IAAI,UAAU,aAAa;AACnC,UAAQ,IAAI,WAAW,cAAc;GACrC;AAEF,QAAO;EAAE;EAAM;EAAQ;EAAa"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/utils/runParallel/index.ts"],"sourcesContent":["import { delimiter, join } from 'node:path';\nimport { spawnPosix } from './spawnPosix';\nimport { spawnWin32 } from './spawnWin32';\n\nexport type ParallelHandle = {\n /**\n * Terminate the child and its whole process group.\n * @param signal Signal to send (default `SIGTERM`). Pass `SIGKILL` to force-kill\n * a child that ignores graceful termination.\n */\n kill: (signal?: NodeJS.Signals) => void;\n result: Promise<any>;\n commandText: string;\n};\n\n/**\n * Start a cross-platform parallel process using npm-run-all approach.\n * Accepts either a single string (e.g., 'next start') or an array of tokens (e.g., ['next', 'start']).\n */\nexport const runParallel = (proc?: string | string[]): ParallelHandle => {\n if (!proc || (Array.isArray(proc) && proc.length === 0))\n throw new Error('Invalid command');\n\n const commandText = Array.isArray(proc) ? proc.join(' ') : proc;\n\n const isArray = Array.isArray(proc);\n const command = isArray ? (proc as string[])[0] : commandText;\n const args = isArray ? (proc as string[]).slice(1) : [];\n\n // Ensure local binaries (node_modules/.bin) are resolvable\n const cwdBin = join(process.cwd(), 'node_modules', '.bin');\n const PATH_KEY =\n Object.keys(process.env).find((key) => key.toLowerCase() === 'path') ??\n 'PATH';\n\n const extendedPath = [cwdBin, process.env[PATH_KEY] ?? '']\n .filter(Boolean)\n .join(delimiter);\n\n const childEnv = {\n ...process.env,\n [PATH_KEY]: extendedPath,\n } as NodeJS.ProcessEnv;\n\n const isWin = process.platform === 'win32';\n const spawnFunc = isWin ? spawnWin32 : spawnPosix;\n\n // Spawn options\n const spawnOptions = {\n cwd: process.cwd(),\n stdio: 'inherit' as const,\n env: childEnv,\n shell: isWin,\n };\n\n // Spawn the child process\n const child = isArray\n ? // If provided as a single string element that includes spaces, treat it like a shell command\n args.length === 0 && /\\s/.test(command)\n ? isWin\n ? spawnFunc(\n process.env.ComSpec ?? 'cmd.exe',\n ['/d', '/s', '/c', command],\n spawnOptions\n )\n : spawnFunc(\n process.env.SHELL ?? '/bin/sh',\n ['-c', command],\n spawnOptions\n )\n : spawnFunc(command, args, spawnOptions)\n : isWin\n ? spawnFunc(\n process.env.ComSpec ?? 'cmd.exe',\n ['/d', '/s', '/c', commandText],\n spawnOptions\n )\n : spawnFunc(\n process.env.SHELL ?? '/bin/sh',\n ['-c', commandText],\n spawnOptions\n );\n\n const result = new Promise<void>((resolve, reject) => {\n child.on('error', (err) => {\n try {\n console.error(\n `[runParallel] Failed to start: ${err?.message ?? String(err)}`\n );\n } catch {}\n reject(err);\n });\n\n child.on('exit', (code, signal) => {\n // Treat common termination signals as graceful exits, not failures\n const gracefulSignals = ['SIGINT', 'SIGTERM', 'SIGQUIT', 'SIGHUP'];\n // Also treat shell-convention exit codes (128 + signal number) as graceful:\n // 129 = SIGHUP, 130 = SIGINT, 131 = SIGQUIT, 143 = SIGTERM\n const gracefulSignalCodes = new Set([129, 130, 131, 143]);\n if (\n code === 0 ||\n gracefulSignalCodes.has(code ?? -1) ||\n (signal && gracefulSignals.includes(signal))\n ) {\n resolve();\n } else {\n reject(\n Object.assign(new Error('Parallel process failed'), { code, signal })\n );\n }\n });\n });\n\n const kill = (signal: NodeJS.Signals = 'SIGTERM') => {\n try {\n child.kill(signal);\n } catch {\n // Best effort\n }\n };\n\n const handleExit = () => kill();\n\n const handleSigInt = () => {\n kill();\n process.off('SIGINT', handleSigInt);\n process.kill(process.pid, 'SIGINT'); // Propagate to allow natural shutdown\n };\n\n const handleSigTerm = () => {\n kill();\n process.off('SIGTERM', handleSigTerm);\n process.kill(process.pid, 'SIGTERM'); // Propagate to allow natural shutdown\n };\n\n process.on('exit', handleExit);\n process.on('SIGINT', handleSigInt);\n process.on('SIGTERM', handleSigTerm);\n\n child.on('exit', () => {\n process.off('exit', handleExit);\n process.off('SIGINT', handleSigInt);\n process.off('SIGTERM', handleSigTerm);\n });\n\n return { kill, result, commandText };\n};\n"],"mappings":";;;;;;;;;AAmBA,MAAa,eAAe,SAA6C;AACvE,KAAI,CAAC,QAAS,MAAM,QAAQ,KAAK,IAAI,KAAK,WAAW,EACnD,OAAM,IAAI,MAAM,kBAAkB;CAEpC,MAAM,cAAc,MAAM,QAAQ,KAAK,GAAG,KAAK,KAAK,IAAI,GAAG;CAE3D,MAAM,UAAU,MAAM,QAAQ,KAAK;CACnC,MAAM,UAAU,UAAW,KAAkB,KAAK;CAClD,MAAM,OAAO,UAAW,KAAkB,MAAM,EAAE,GAAG,EAAE;CAGvD,MAAM,SAAS,KAAK,QAAQ,KAAK,EAAE,gBAAgB,OAAO;CAC1D,MAAM,WACJ,OAAO,KAAK,QAAQ,IAAI,CAAC,MAAM,QAAQ,IAAI,aAAa,KAAK,OAAO,IACpE;CAEF,MAAM,eAAe,CAAC,QAAQ,QAAQ,IAAI,aAAa,GAAG,CACvD,OAAO,QAAQ,CACf,KAAK,UAAU;CAElB,MAAM,WAAW;EACf,GAAG,QAAQ;GACV,WAAW;EACb;CAED,MAAM,QAAQ,QAAQ,aAAa;CACnC,MAAM,YAAY,QAAQ,aAAa;CAGvC,MAAM,eAAe;EACnB,KAAK,QAAQ,KAAK;EAClB,OAAO;EACP,KAAK;EACL,OAAO;EACR;CAGD,MAAM,QAAQ,UAEV,KAAK,WAAW,KAAK,KAAK,KAAK,QAAQ,GACrC,QACE,UACE,QAAQ,IAAI,WAAW,WACvB;EAAC;EAAM;EAAM;EAAM;EAAQ,EAC3B,aACD,GACD,UACE,QAAQ,IAAI,SAAS,WACrB,CAAC,MAAM,QAAQ,EACf,aACD,GACH,UAAU,SAAS,MAAM,aAAa,GACxC,QACE,UACE,QAAQ,IAAI,WAAW,WACvB;EAAC;EAAM;EAAM;EAAM;EAAY,EAC/B,aACD,GACD,UACE,QAAQ,IAAI,SAAS,WACrB,CAAC,MAAM,YAAY,EACnB,aACD;CAEP,MAAM,SAAS,IAAI,SAAe,SAAS,WAAW;AACpD,QAAM,GAAG,UAAU,QAAQ;AACzB,OAAI;AACF,YAAQ,MACN,kCAAkC,KAAK,WAAW,OAAO,IAAI,GAC9D;WACK;AACR,UAAO,IAAI;IACX;AAEF,QAAM,GAAG,SAAS,MAAM,WAAW;AAMjC,OACE,SAAS,KACT,IAH8B,IAAI;IAAC;IAAK;IAAK;IAAK;IAAI,CAGnC,CAAC,IAAI,QAAQ,GAAG,IAClC,UAAU;IAPY;IAAU;IAAW;IAAW;IAO7B,CAAC,SAAS,OAAO,CAE3C,UAAS;OAET,QACE,OAAO,uBAAO,IAAI,MAAM,0BAA0B,EAAE;IAAE;IAAM;IAAQ,CAAC,CACtE;IAEH;GACF;CAEF,MAAM,QAAQ,SAAyB,cAAc;AACnD,MAAI;AACF,SAAM,KAAK,OAAO;UACZ;;CAKV,MAAM,mBAAmB,MAAM;CAE/B,MAAM,qBAAqB;AACzB,QAAM;AACN,UAAQ,IAAI,UAAU,aAAa;AACnC,UAAQ,KAAK,QAAQ,KAAK,SAAS;;CAGrC,MAAM,sBAAsB;AAC1B,QAAM;AACN,UAAQ,IAAI,WAAW,cAAc;AACrC,UAAQ,KAAK,QAAQ,KAAK,UAAU;;AAGtC,SAAQ,GAAG,QAAQ,WAAW;AAC9B,SAAQ,GAAG,UAAU,aAAa;AAClC,SAAQ,GAAG,WAAW,cAAc;AAEpC,OAAM,GAAG,cAAc;AACrB,UAAQ,IAAI,QAAQ,WAAW;AAC/B,UAAQ,IAAI,UAAU,aAAa;AACnC,UAAQ,IAAI,WAAW,cAAc;GACrC;AAEF,QAAO;EAAE;EAAM;EAAQ;EAAa"}
@@ -12,7 +12,7 @@ import { getConfiguration, getConfigurationAndFilePath } from "@intlayer/config/
12
12
  import { existsSync, watch as watch$1 } from "node:fs";
13
13
  import { clearAllCache, clearDiskCacheMemory, clearModuleCache, normalizePath } from "@intlayer/config/utils";
14
14
  import * as ANSIColor from "@intlayer/config/colors";
15
- import { pathToFileURL } from "node:url";
15
+ import { transpileTSToCJS } from "@intlayer/config/file";
16
16
 
17
17
  //#region src/watcher.ts
18
18
  const pendingUnlinks = /* @__PURE__ */ new Map();
@@ -80,9 +80,8 @@ const watch = async (options) => {
80
80
  if (!rel || rel.includes("/")) continue;
81
81
  if (rel.endsWith(".tmp")) continue;
82
82
  if (event.type === "update") processEvent(async () => {
83
- clearModuleCache(event.path);
84
83
  try {
85
- await import(`${pathToFileURL(event.path).href}?update=${Date.now()}`);
84
+ await transpileTSToCJS(await readFile(event.path, "utf-8"), event.path);
86
85
  } catch {
87
86
  appLogger(`Entry point ${basename(event.path)} failed to load, running clean rebuild...`, { level: "warn" });
88
87
  await prepareIntlayer(configuration, {