@intlayer/engine 9.3.0 → 9.3.1
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.
- package/README.md +24 -24
- package/dist/assets/initConfig/templates/cjs.txt +1 -1
- package/dist/assets/initConfig/templates/mjs.txt +1 -1
- package/dist/assets/initConfig/templates/ts.txt +1 -1
- package/dist/cjs/_virtual/_rolldown/runtime.cjs +1 -2
- package/dist/cjs/_virtual/_utils_asset.cjs +2 -1
- package/dist/cjs/buildIntlayerDictionary/buildIntlayerDictionary.cjs.map +1 -1
- package/dist/cjs/buildIntlayerDictionary/writeDynamicDictionary.cjs +2 -1
- package/dist/cjs/buildIntlayerDictionary/writeDynamicDictionary.cjs.map +1 -1
- package/dist/cjs/buildIntlayerDictionary/writeFetchDictionary.cjs.map +1 -1
- package/dist/cjs/buildIntlayerDictionary/writeMergedDictionary.cjs +2 -1
- package/dist/cjs/buildIntlayerDictionary/writeMergedDictionary.cjs.map +1 -1
- package/dist/cjs/buildIntlayerDictionary/writeNestedDictionary.cjs.map +1 -1
- package/dist/cjs/buildIntlayerDictionary/writeRemoteDictionary.cjs +4 -2
- package/dist/cjs/buildIntlayerDictionary/writeRemoteDictionary.cjs.map +1 -1
- package/dist/cjs/buildIntlayerDictionary/writeUnmergedDictionary.cjs.map +1 -1
- package/dist/cjs/cleanOutputDir.cjs.map +1 -1
- package/dist/cjs/cleanRemovedContentDeclaration.cjs +7 -4
- package/dist/cjs/cleanRemovedContentDeclaration.cjs.map +1 -1
- package/dist/cjs/createDictionaryEntryPoint/createDictionaryEntryPoint.cjs +5 -3
- package/dist/cjs/createDictionaryEntryPoint/createDictionaryEntryPoint.cjs.map +1 -1
- package/dist/cjs/createDictionaryEntryPoint/generateDictionaryListContent.cjs.map +1 -1
- package/dist/cjs/createDictionaryEntryPoint/getBuiltDictionariesPath.cjs.map +1 -1
- package/dist/cjs/createDictionaryEntryPoint/getBuiltDynamicDictionariesPath.cjs.map +1 -1
- package/dist/cjs/createDictionaryEntryPoint/getBuiltFetchDictionariesPath.cjs.map +1 -1
- package/dist/cjs/createDictionaryEntryPoint/getBuiltRemoteDictionariesPath.cjs.map +1 -1
- package/dist/cjs/createDictionaryEntryPoint/getBuiltUnmergedDictionariesPath.cjs.map +1 -1
- package/dist/cjs/createType/createModuleAugmentation.cjs +2 -1
- package/dist/cjs/createType/createModuleAugmentation.cjs.map +1 -1
- package/dist/cjs/createType/createType.cjs.map +1 -1
- package/dist/cjs/detectFormatCommand.cjs.map +1 -1
- package/dist/cjs/docReview/alignBlocks.cjs +1 -1
- package/dist/cjs/docReview/alignBlocks.cjs.map +1 -1
- package/dist/cjs/docReview/computeSimilarity.cjs +0 -1
- package/dist/cjs/docReview/computeSimilarity.cjs.map +1 -1
- package/dist/cjs/docReview/mapChangedLinesToBlocks.cjs +0 -1
- package/dist/cjs/docReview/mapChangedLinesToBlocks.cjs.map +1 -1
- package/dist/cjs/docReview/normalizeBlock.cjs +0 -1
- package/dist/cjs/docReview/normalizeBlock.cjs.map +1 -1
- package/dist/cjs/docReview/planActions.cjs +0 -1
- package/dist/cjs/docReview/planActions.cjs.map +1 -1
- package/dist/cjs/docReview/rebuildDocument.cjs +0 -1
- package/dist/cjs/docReview/rebuildDocument.cjs.map +1 -1
- package/dist/cjs/docReview/reviewReport.cjs +2 -1
- package/dist/cjs/docReview/reviewReport.cjs.map +1 -1
- package/dist/cjs/docReview/segmentDocument.cjs +0 -1
- package/dist/cjs/docReview/segmentDocument.cjs.map +1 -1
- package/dist/cjs/fetchDistantDictionaries.cjs.map +1 -1
- package/dist/cjs/filterInvalidDictionaries.cjs.map +1 -1
- package/dist/cjs/formatDictionary.cjs.map +1 -1
- package/dist/cjs/getContentDeclarationFileTemplate/getContentDeclarationFileTemplate.cjs.map +1 -1
- package/dist/cjs/handleAdditionalContentDeclarationFile.cjs +4 -2
- package/dist/cjs/handleAdditionalContentDeclarationFile.cjs.map +1 -1
- package/dist/cjs/handleContentDeclarationFileChange.cjs +6 -2
- package/dist/cjs/handleContentDeclarationFileChange.cjs.map +1 -1
- package/dist/cjs/handleContentDeclarationFileMoved.cjs.map +1 -1
- package/dist/cjs/handleUnlinkedContentDeclarationFile.cjs +4 -2
- package/dist/cjs/handleUnlinkedContentDeclarationFile.cjs.map +1 -1
- package/dist/cjs/init/cms.cjs.map +1 -1
- package/dist/cjs/init/frameworkSetup/nextAppRouter/detect.cjs.map +1 -1
- package/dist/cjs/init/frameworkSetup/nextAppRouter/index.cjs +1 -3
- package/dist/cjs/init/frameworkSetup/nextAppRouter/index.cjs.map +1 -1
- package/dist/cjs/init/frameworkSetup/nextAppRouter/restructure.cjs +2 -1
- package/dist/cjs/init/frameworkSetup/nextAppRouter/restructure.cjs.map +1 -1
- package/dist/cjs/init/frameworkSetup/nextAppRouter/templates.cjs +0 -1
- package/dist/cjs/init/frameworkSetup/nextAppRouter/templates.cjs.map +1 -1
- package/dist/cjs/init/frameworkSetup/tanstackStart/detect.cjs.map +1 -1
- package/dist/cjs/init/frameworkSetup/tanstackStart/index.cjs +3 -5
- package/dist/cjs/init/frameworkSetup/tanstackStart/index.cjs.map +1 -1
- package/dist/cjs/init/frameworkSetup/tanstackStart/restructure.cjs +2 -1
- package/dist/cjs/init/frameworkSetup/tanstackStart/restructure.cjs.map +1 -1
- package/dist/cjs/init/frameworkSetup/tanstackStart/templates.cjs +18 -12
- package/dist/cjs/init/frameworkSetup/tanstackStart/templates.cjs.map +1 -1
- package/dist/cjs/init/frameworkSetup/tanstackStart/transforms.cjs +57 -14
- package/dist/cjs/init/frameworkSetup/tanstackStart/transforms.cjs.map +1 -1
- package/dist/cjs/init/index.cjs +66 -38
- package/dist/cjs/init/index.cjs.map +1 -1
- package/dist/cjs/init/utils/configManipulation.cjs.map +1 -1
- package/dist/cjs/init/utils/fileSystem.cjs +3 -1
- package/dist/cjs/init/utils/fileSystem.cjs.map +1 -1
- package/dist/cjs/init/utils/githubActions.cjs.map +1 -1
- package/dist/cjs/init/utils/jsonParser.cjs +0 -1
- package/dist/cjs/init/utils/jsonParser.cjs.map +1 -1
- package/dist/cjs/init/utils/nextCompilerBabel.cjs.map +1 -1
- package/dist/cjs/init/utils/packageManager.cjs +4 -2
- package/dist/cjs/init/utils/packageManager.cjs.map +1 -1
- package/dist/cjs/init/utils/tsConfig.cjs.map +1 -1
- package/dist/cjs/init/utils/version.cjs +0 -1
- package/dist/cjs/init/utils/version.cjs.map +1 -1
- package/dist/cjs/initConfig/index.cjs +12 -8
- package/dist/cjs/initConfig/index.cjs.map +1 -1
- package/dist/cjs/installSkills/index.cjs.map +1 -1
- package/dist/cjs/listDictionariesPath.cjs +6 -5
- package/dist/cjs/listDictionariesPath.cjs.map +1 -1
- package/dist/cjs/listGitFiles.cjs.map +1 -1
- package/dist/cjs/listProjects.cjs +2 -1
- package/dist/cjs/listProjects.cjs.map +1 -1
- package/dist/cjs/loadDictionaries/getIntlayerBundle.cjs +2 -1
- package/dist/cjs/loadDictionaries/getIntlayerBundle.cjs.map +1 -1
- package/dist/cjs/loadDictionaries/loadContentDeclaration.cjs +8 -5
- package/dist/cjs/loadDictionaries/loadContentDeclaration.cjs.map +1 -1
- package/dist/cjs/loadDictionaries/loadDictionaries.cjs.map +1 -1
- package/dist/cjs/loadDictionaries/loadLocalDictionaries.cjs.map +1 -1
- package/dist/cjs/loadDictionaries/loadMarkdownContentDeclaration.cjs.map +1 -1
- package/dist/cjs/loadDictionaries/loadRemoteDictionaries.cjs.map +1 -1
- package/dist/cjs/loadDictionaries/loadYamlContentDeclaration.cjs +2 -1
- package/dist/cjs/loadDictionaries/loadYamlContentDeclaration.cjs.map +1 -1
- package/dist/cjs/loadDictionaries/log.cjs +10 -6
- package/dist/cjs/loadDictionaries/log.cjs.map +1 -1
- package/dist/cjs/loadDictionaries/logTypeScriptErrors.cjs.map +1 -1
- package/dist/cjs/logConfigDetails.cjs +4 -3
- package/dist/cjs/logConfigDetails.cjs.map +1 -1
- package/dist/cjs/prepareIntlayer.cjs +5 -3
- package/dist/cjs/prepareIntlayer.cjs.map +1 -1
- package/dist/cjs/reduceDictionaryContent/applyMask.cjs +0 -1
- package/dist/cjs/reduceDictionaryContent/applyMask.cjs.map +1 -1
- package/dist/cjs/reduceDictionaryContent/reduceDictionaryContent.cjs +2 -1
- package/dist/cjs/reduceDictionaryContent/reduceDictionaryContent.cjs.map +1 -1
- package/dist/cjs/scan/calculateScore.cjs +0 -1
- package/dist/cjs/scan/calculateScore.cjs.map +1 -1
- package/dist/cjs/scan/checks.cjs +1 -1
- package/dist/cjs/scan/checks.cjs.map +1 -1
- package/dist/cjs/scan/parseHtml.cjs +0 -1
- package/dist/cjs/scan/parseHtml.cjs.map +1 -1
- package/dist/cjs/scan/scanWebsite.cjs.map +1 -1
- package/dist/cjs/syncPluginKit/createFileAdapter.cjs +2 -1
- package/dist/cjs/syncPluginKit/createFileAdapter.cjs.map +1 -1
- package/dist/cjs/syncPluginKit/createSyncPlugin.cjs +4 -1
- package/dist/cjs/syncPluginKit/createSyncPlugin.cjs.map +1 -1
- package/dist/cjs/syncPluginKit/extractKeyAndLocaleFromPath.cjs +0 -1
- package/dist/cjs/syncPluginKit/extractKeyAndLocaleFromPath.cjs.map +1 -1
- package/dist/cjs/syncPluginKit/filePathPatternHelpers.cjs +0 -1
- package/dist/cjs/syncPluginKit/filePathPatternHelpers.cjs.map +1 -1
- package/dist/cjs/utils/autoDecorateContent.cjs.map +1 -1
- package/dist/cjs/utils/babelParser.cjs.map +1 -1
- package/dist/cjs/utils/buildComponentFilesList.cjs.map +1 -1
- package/dist/cjs/utils/buildFilesList.cjs.map +1 -1
- package/dist/cjs/utils/chunkArray.cjs +0 -1
- package/dist/cjs/utils/chunkArray.cjs.map +1 -1
- package/dist/cjs/utils/formatter.cjs.map +1 -1
- package/dist/cjs/utils/getContentExtension.cjs +1 -4
- package/dist/cjs/utils/getContentExtension.cjs.map +1 -1
- package/dist/cjs/utils/getFormatFromExtension.cjs +0 -1
- package/dist/cjs/utils/getFormatFromExtension.cjs.map +1 -1
- package/dist/cjs/utils/getPathHash.cjs +0 -1
- package/dist/cjs/utils/getPathHash.cjs.map +1 -1
- package/dist/cjs/utils/mergeChunks.cjs +0 -1
- package/dist/cjs/utils/mergeChunks.cjs.map +1 -1
- package/dist/cjs/utils/pLimit.cjs +0 -1
- package/dist/cjs/utils/pLimit.cjs.map +1 -1
- package/dist/cjs/utils/readDictionariesFromDisk.cjs.map +1 -1
- package/dist/cjs/utils/reduceObjectFormat.cjs +0 -1
- package/dist/cjs/utils/reduceObjectFormat.cjs.map +1 -1
- package/dist/cjs/utils/resolveObjectPromises.cjs +0 -1
- package/dist/cjs/utils/resolveObjectPromises.cjs.map +1 -1
- package/dist/cjs/utils/resolveRelativePath.cjs +4 -1
- package/dist/cjs/utils/resolveRelativePath.cjs.map +1 -1
- package/dist/cjs/utils/runOnce.cjs +2 -2
- package/dist/cjs/utils/runOnce.cjs.map +1 -1
- package/dist/cjs/utils/runParallel/bin.cjs.map +1 -1
- package/dist/cjs/utils/runParallel/bootstrap.cjs +0 -1
- package/dist/cjs/utils/runParallel/bootstrap.cjs.map +1 -1
- package/dist/cjs/utils/runParallel/index.cjs.map +1 -1
- package/dist/cjs/utils/runParallel/spawnPosix.cjs.map +1 -1
- package/dist/cjs/utils/runParallel/spawnWin32.cjs.map +1 -1
- package/dist/cjs/utils/sortAlphabetically.cjs +0 -1
- package/dist/cjs/utils/sortAlphabetically.cjs.map +1 -1
- package/dist/cjs/utils/splitTextByLine.cjs +0 -1
- package/dist/cjs/utils/splitTextByLine.cjs.map +1 -1
- package/dist/cjs/utils/verifyIdenticObjectFormat.cjs +0 -1
- package/dist/cjs/utils/verifyIdenticObjectFormat.cjs.map +1 -1
- package/dist/cjs/watcher.cjs +4 -2
- package/dist/cjs/watcher.cjs.map +1 -1
- package/dist/cjs/writeConfiguration/index.cjs +2 -1
- package/dist/cjs/writeConfiguration/index.cjs.map +1 -1
- package/dist/cjs/writeContentDeclaration/detectExportedComponentName.cjs +0 -1
- package/dist/cjs/writeContentDeclaration/detectExportedComponentName.cjs.map +1 -1
- package/dist/cjs/writeContentDeclaration/processContentDeclarationContent.cjs.map +1 -1
- package/dist/cjs/writeContentDeclaration/transformJSFile.cjs +26 -23
- package/dist/cjs/writeContentDeclaration/transformJSFile.cjs.map +1 -1
- package/dist/cjs/writeContentDeclaration/transformJSONFile.cjs +6 -5
- package/dist/cjs/writeContentDeclaration/transformJSONFile.cjs.map +1 -1
- package/dist/cjs/writeContentDeclaration/writeContentDeclaration.cjs +9 -3
- package/dist/cjs/writeContentDeclaration/writeContentDeclaration.cjs.map +1 -1
- package/dist/cjs/writeContentDeclaration/writeJSFile.cjs +2 -1
- package/dist/cjs/writeContentDeclaration/writeJSFile.cjs.map +1 -1
- package/dist/cjs/writeContentDeclaration/writeMarkdownFile.cjs +2 -1
- package/dist/cjs/writeContentDeclaration/writeMarkdownFile.cjs.map +1 -1
- package/dist/cjs/writeContentDeclaration/writeYamlFile.cjs +4 -2
- package/dist/cjs/writeContentDeclaration/writeYamlFile.cjs.map +1 -1
- package/dist/cjs/writeFileIfChanged.cjs.map +1 -1
- package/dist/esm/_virtual/_utils_asset.mjs +2 -1
- package/dist/esm/buildIntlayerDictionary/writeDynamicDictionary.mjs +2 -1
- package/dist/esm/buildIntlayerDictionary/writeDynamicDictionary.mjs.map +1 -1
- package/dist/esm/buildIntlayerDictionary/writeMergedDictionary.mjs +2 -1
- package/dist/esm/buildIntlayerDictionary/writeMergedDictionary.mjs.map +1 -1
- package/dist/esm/buildIntlayerDictionary/writeRemoteDictionary.mjs +4 -2
- package/dist/esm/buildIntlayerDictionary/writeRemoteDictionary.mjs.map +1 -1
- package/dist/esm/cleanRemovedContentDeclaration.mjs +7 -4
- package/dist/esm/cleanRemovedContentDeclaration.mjs.map +1 -1
- package/dist/esm/createDictionaryEntryPoint/createDictionaryEntryPoint.mjs +5 -3
- package/dist/esm/createDictionaryEntryPoint/createDictionaryEntryPoint.mjs.map +1 -1
- package/dist/esm/createType/createModuleAugmentation.mjs +2 -1
- package/dist/esm/createType/createModuleAugmentation.mjs.map +1 -1
- package/dist/esm/docReview/alignBlocks.mjs +1 -1
- package/dist/esm/docReview/alignBlocks.mjs.map +1 -1
- package/dist/esm/docReview/reviewReport.mjs +2 -1
- package/dist/esm/docReview/reviewReport.mjs.map +1 -1
- package/dist/esm/handleAdditionalContentDeclarationFile.mjs +4 -2
- package/dist/esm/handleAdditionalContentDeclarationFile.mjs.map +1 -1
- package/dist/esm/handleContentDeclarationFileChange.mjs +6 -2
- package/dist/esm/handleContentDeclarationFileChange.mjs.map +1 -1
- package/dist/esm/handleUnlinkedContentDeclarationFile.mjs +4 -2
- package/dist/esm/handleUnlinkedContentDeclarationFile.mjs.map +1 -1
- package/dist/esm/init/frameworkSetup/nextAppRouter/index.mjs +1 -3
- package/dist/esm/init/frameworkSetup/nextAppRouter/index.mjs.map +1 -1
- package/dist/esm/init/frameworkSetup/nextAppRouter/restructure.mjs +2 -1
- package/dist/esm/init/frameworkSetup/nextAppRouter/restructure.mjs.map +1 -1
- package/dist/esm/init/frameworkSetup/tanstackStart/index.mjs +4 -6
- package/dist/esm/init/frameworkSetup/tanstackStart/index.mjs.map +1 -1
- package/dist/esm/init/frameworkSetup/tanstackStart/restructure.mjs +2 -1
- package/dist/esm/init/frameworkSetup/tanstackStart/restructure.mjs.map +1 -1
- package/dist/esm/init/frameworkSetup/tanstackStart/templates.mjs +17 -10
- package/dist/esm/init/frameworkSetup/tanstackStart/templates.mjs.map +1 -1
- package/dist/esm/init/frameworkSetup/tanstackStart/transforms.mjs +58 -15
- package/dist/esm/init/frameworkSetup/tanstackStart/transforms.mjs.map +1 -1
- package/dist/esm/init/index.mjs +66 -38
- package/dist/esm/init/index.mjs.map +1 -1
- package/dist/esm/init/utils/configManipulation.mjs.map +1 -1
- package/dist/esm/init/utils/fileSystem.mjs +3 -1
- package/dist/esm/init/utils/fileSystem.mjs.map +1 -1
- package/dist/esm/init/utils/packageManager.mjs +4 -2
- package/dist/esm/init/utils/packageManager.mjs.map +1 -1
- package/dist/esm/initConfig/index.mjs +12 -8
- package/dist/esm/initConfig/index.mjs.map +1 -1
- package/dist/esm/listDictionariesPath.mjs +6 -5
- package/dist/esm/listDictionariesPath.mjs.map +1 -1
- package/dist/esm/listProjects.mjs +2 -1
- package/dist/esm/listProjects.mjs.map +1 -1
- package/dist/esm/loadDictionaries/getIntlayerBundle.mjs +2 -1
- package/dist/esm/loadDictionaries/getIntlayerBundle.mjs.map +1 -1
- package/dist/esm/loadDictionaries/loadContentDeclaration.mjs +8 -5
- package/dist/esm/loadDictionaries/loadContentDeclaration.mjs.map +1 -1
- package/dist/esm/loadDictionaries/loadYamlContentDeclaration.mjs +2 -1
- package/dist/esm/loadDictionaries/loadYamlContentDeclaration.mjs.map +1 -1
- package/dist/esm/loadDictionaries/log.mjs +10 -6
- package/dist/esm/loadDictionaries/log.mjs.map +1 -1
- package/dist/esm/logConfigDetails.mjs +4 -3
- package/dist/esm/logConfigDetails.mjs.map +1 -1
- package/dist/esm/prepareIntlayer.mjs +5 -3
- package/dist/esm/prepareIntlayer.mjs.map +1 -1
- package/dist/esm/reduceDictionaryContent/reduceDictionaryContent.mjs +2 -1
- package/dist/esm/reduceDictionaryContent/reduceDictionaryContent.mjs.map +1 -1
- package/dist/esm/scan/checks.mjs +1 -1
- package/dist/esm/scan/checks.mjs.map +1 -1
- package/dist/esm/syncPluginKit/createFileAdapter.mjs +2 -1
- package/dist/esm/syncPluginKit/createFileAdapter.mjs.map +1 -1
- package/dist/esm/syncPluginKit/createSyncPlugin.mjs +4 -1
- package/dist/esm/syncPluginKit/createSyncPlugin.mjs.map +1 -1
- package/dist/esm/utils/babelParser.mjs.map +1 -1
- package/dist/esm/utils/getContentExtension.mjs +1 -3
- package/dist/esm/utils/getContentExtension.mjs.map +1 -1
- package/dist/esm/utils/resolveRelativePath.mjs +4 -1
- package/dist/esm/utils/resolveRelativePath.mjs.map +1 -1
- package/dist/esm/utils/runOnce.mjs +2 -2
- package/dist/esm/utils/runOnce.mjs.map +1 -1
- package/dist/esm/watcher.mjs +4 -2
- package/dist/esm/watcher.mjs.map +1 -1
- package/dist/esm/writeConfiguration/index.mjs +2 -1
- package/dist/esm/writeConfiguration/index.mjs.map +1 -1
- package/dist/esm/writeContentDeclaration/transformJSFile.mjs +26 -23
- package/dist/esm/writeContentDeclaration/transformJSFile.mjs.map +1 -1
- package/dist/esm/writeContentDeclaration/transformJSONFile.mjs +6 -5
- package/dist/esm/writeContentDeclaration/transformJSONFile.mjs.map +1 -1
- package/dist/esm/writeContentDeclaration/writeContentDeclaration.mjs +9 -3
- package/dist/esm/writeContentDeclaration/writeContentDeclaration.mjs.map +1 -1
- package/dist/esm/writeContentDeclaration/writeJSFile.mjs +2 -1
- package/dist/esm/writeContentDeclaration/writeJSFile.mjs.map +1 -1
- package/dist/esm/writeContentDeclaration/writeMarkdownFile.mjs +2 -1
- package/dist/esm/writeContentDeclaration/writeMarkdownFile.mjs.map +1 -1
- package/dist/esm/writeContentDeclaration/writeYamlFile.mjs +4 -2
- package/dist/esm/writeContentDeclaration/writeYamlFile.mjs.map +1 -1
- package/dist/types/init/frameworkSetup/tanstackStart/templates.d.ts +9 -4
- package/dist/types/init/frameworkSetup/tanstackStart/templates.d.ts.map +1 -1
- package/dist/types/init/frameworkSetup/tanstackStart/transforms.d.ts +6 -2
- package/dist/types/init/frameworkSetup/tanstackStart/transforms.d.ts.map +1 -1
- package/package.json +10 -10
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reviewReport.cjs","names":["buildAlignmentPlan","ANSIColors"],"sources":["../../../src/docReview/reviewReport.ts"],"sourcesContent":["import * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, colorizeNumber } from '@intlayer/config/logger';\nimport { buildAlignmentPlan } from './pipeline';\n\n/**\n * The kind of change detected for a block when reviewing a translation against\n * its base document.\n *\n * - `review`: the base block changed and its translation must be updated.\n * - `insert_new`: the base block has no translation yet and must be added.\n * - `delete`: the target block no longer exists in the base and should be removed.\n */\nexport type ReviewBlockAction = 'review' | 'insert_new' | 'delete';\n\n/** A 1-based, inclusive line range within a document. */\nexport type LineRange = {\n start: number;\n end: number;\n};\n\n/**\n * A single block that diverges between the base document and its translation.\n *\n * This is the unit an external translator (AI client, human, or agent) needs to\n * act on: it carries the base content to translate, the existing translation to\n * update, and where each lives so the change can be located in the file.\n */\nexport type ReviewReportBlock = {\n /** What should happen to this block. */\n action: ReviewBlockAction;\n /** Line range of the block in the base document (omitted for pure deletions). */\n baseLineRange?: LineRange;\n /** Line range of the block in the target document (omitted for new insertions). */\n targetLineRange?: LineRange;\n /** Raw markdown of the base block (omitted for pure deletions). */\n baseContent?: string;\n /** Existing translation of the block (omitted for new insertions). */\n targetContent?: string;\n};\n\nexport type ReviewReportSummary = {\n /** Number of blocks reused as-is (unchanged translation). */\n reuse: number;\n /** Number of blocks whose translation must be updated. */\n review: number;\n /** Number of blocks missing a translation. */\n insertNew: number;\n /** Number of stale target blocks to delete. */\n delete: number;\n};\n\nexport type ReviewReport = {\n /** Only the blocks that diverge (review, insert_new, delete). */\n blocks: ReviewReportBlock[];\n /** Counts per action over the whole document, including reused blocks. */\n summary: ReviewReportSummary;\n};\n\nexport type BuildReviewReportInput = {\n /** The base (source) document, used as the translation reference. */\n baseText: string;\n /** The existing target (translated) document, possibly empty. */\n targetText: string;\n /**\n * 1-based line numbers that changed in the base document. When omitted, only\n * inserted and deleted blocks are reported (no aligned block is flagged for\n * review since there is no way to know which ones changed).\n */\n changedLines?: number[];\n};\n\n/**\n * Compare a base markdown document with its translation and report only the\n * blocks that need attention, with their line ranges and content.\n *\n * Reusable across the CLI (`doc review --log`), the backend (comparing two\n * translation contents stored in database), and agents that generate the\n * missing translations.\n *\n * @param input - The base/target texts and optional changed lines.\n * @returns The divergent blocks and a per-action summary.\n */\nexport const buildReviewReport = ({\n baseText,\n targetText,\n changedLines,\n}: BuildReviewReportInput): ReviewReport => {\n const { baseBlocks, targetBlocks, plan } = buildAlignmentPlan({\n baseText,\n targetText,\n changedLines,\n });\n\n const summary: ReviewReportSummary = {\n reuse: 0,\n review: 0,\n insertNew: 0,\n delete: 0,\n };\n\n const blocks: ReviewReportBlock[] = [];\n\n for (const [actionIndex, action] of plan.actions.entries()) {\n if (action.kind === 'reuse') {\n summary.reuse += 1;\n continue;\n }\n\n if (action.kind === 'review') {\n summary.review += 1;\n const baseBlock = baseBlocks[action.baseIndex];\n\n if (!baseBlock) continue;\n\n const targetBlock =\n action.targetIndex !== null ? targetBlocks[action.targetIndex] : null;\n\n blocks.push({\n action: 'review',\n baseLineRange: { start: baseBlock.lineStart, end: baseBlock.lineEnd },\n targetLineRange: targetBlock\n ? { start: targetBlock.lineStart, end: targetBlock.lineEnd }\n : undefined,\n baseContent: baseBlock.content,\n targetContent: targetBlock?.content,\n });\n continue;\n }\n\n if (action.kind === 'insert_new') {\n summary.insertNew += 1;\n const baseBlock = baseBlocks[action.baseIndex];\n\n if (!baseBlock) continue;\n\n // Find the target line after which the new block should be inserted:\n // scan backwards for the last action that produced a target block.\n let insertAfterLine: number | undefined;\n for (let prevIndex = actionIndex - 1; prevIndex >= 0; prevIndex--) {\n const prevAction = plan.actions[prevIndex];\n if (!prevAction) continue;\n\n let targetIdx: number | null = null;\n if (prevAction.kind === 'reuse') targetIdx = prevAction.targetIndex;\n else if (\n prevAction.kind === 'review' &&\n prevAction.targetIndex !== null\n )\n targetIdx = prevAction.targetIndex;\n else if (prevAction.kind === 'delete')\n targetIdx = prevAction.targetIndex;\n\n if (targetIdx !== null) {\n const prevTargetBlock = targetBlocks[targetIdx];\n if (prevTargetBlock) {\n insertAfterLine = prevTargetBlock.lineEnd + 1;\n break;\n }\n }\n }\n\n blocks.push({\n action: 'insert_new',\n baseLineRange: { start: baseBlock.lineStart, end: baseBlock.lineEnd },\n targetLineRange:\n insertAfterLine !== undefined\n ? { start: insertAfterLine, end: insertAfterLine }\n : undefined,\n baseContent: baseBlock.content,\n });\n continue;\n }\n\n if (action.kind === 'delete') {\n summary.delete += 1;\n const targetBlock = targetBlocks[action.targetIndex];\n\n if (!targetBlock) continue;\n\n blocks.push({\n action: 'delete',\n targetLineRange: {\n start: targetBlock.lineStart,\n end: targetBlock.lineEnd,\n },\n targetContent: targetBlock.content,\n });\n }\n }\n\n return { blocks, summary };\n};\n\n/** Render a line range as a blue colored string, or an orange dash when absent. */\nconst colorizeLineRange = (range?: LineRange): string => {\n if (!range) return colorize('—', ANSIColors.ORANGE);\n const rangeStr =\n range.start === range.end\n ? `L${range.start}`\n : `L${range.start}-${range.end}`;\n return colorize(rangeStr, ANSIColors.BLUE);\n};\n\n/**\n * Colorize a count with green when zero (nothing to do) and a caller-supplied\n * non-zero color (orange for warnings, red for destructive actions).\n */\nconst colorizeCount = (\n count: number,\n nonZeroColor: (typeof ANSIColors)[keyof typeof ANSIColors]\n): string =>\n colorizeNumber(count, {\n zero: ANSIColors.GREEN,\n one: nonZeroColor,\n two: nonZeroColor,\n few: nonZeroColor,\n many: nonZeroColor,\n other: nonZeroColor,\n });\n\n/**\n * Render a {@link ReviewReport} as a human and agent readable log.\n *\n * Each divergent block is printed with its action, the base/target line ranges,\n * the base content to translate, and the existing translation to update.\n *\n * @param report - The report to format.\n * @param options - Optional labels for the base and target locales.\n * @returns A multi-line string describing every block that needs attention.\n */\nexport const formatReviewReport = (\n report: ReviewReport,\n options?: { baseLabel?: string; targetLabel?: string }\n): string => {\n const baseLabel = options?.baseLabel ?? 'base';\n const targetLabel = options?.targetLabel ?? 'target';\n\n const { summary, blocks } = report;\n\n // Build the summary header piece-by-piece so each count uses its own color.\n const header = [\n colorize('Review report: ', ANSIColors.ORANGE),\n colorizeCount(blocks.length, ANSIColors.ORANGE),\n colorize(' block(s) need attention (review=', ANSIColors.ORANGE),\n colorizeCount(summary.review, ANSIColors.ORANGE),\n colorize(', new=', ANSIColors.ORANGE),\n colorizeCount(summary.insertNew, ANSIColors.ORANGE),\n colorize(', delete=', ANSIColors.ORANGE),\n colorizeCount(summary.delete, ANSIColors.RED),\n colorize(', reuse=', ANSIColors.ORANGE),\n colorizeNumber(summary.reuse),\n colorize(').', ANSIColors.ORANGE),\n ].join('');\n\n if (blocks.length === 0) {\n return `${header}\\n${colorize('No changes needed.', ANSIColors.GREEN)}`;\n }\n\n const sections = blocks.map((block, index) => {\n const lines: string[] = [];\n\n // Block header: each token colored individually.\n const blockHeader = [\n colorize('--- Block ', ANSIColors.ORANGE),\n colorizeNumber(index + 1),\n colorize('/', ANSIColors.ORANGE),\n colorizeNumber(blocks.length),\n colorize(` [${block.action}] `, ANSIColors.ORANGE),\n baseLabel,\n colorize(' ', ANSIColors.ORANGE),\n colorizeLineRange(block.baseLineRange),\n colorize(' → ', ANSIColors.ORANGE),\n targetLabel,\n colorize(' ', ANSIColors.ORANGE),\n colorizeLineRange(block.targetLineRange),\n colorize(' ---', ANSIColors.ORANGE),\n ].join('');\n\n lines.push(blockHeader);\n\n if (block.baseContent !== undefined) {\n lines.push(colorize(`[${baseLabel}]`, ANSIColors.BEIGE));\n lines.push(colorize(block.baseContent.trimEnd(), ANSIColors.GREY));\n }\n\n if (block.targetContent !== undefined) {\n lines.push(colorize(`[${targetLabel}]`, ANSIColors.BEIGE));\n lines.push(colorize(block.targetContent.trimEnd(), ANSIColors.GREY));\n } else if (block.action === 'insert_new') {\n lines.push(colorize(`[${targetLabel}]`, ANSIColors.BEIGE));\n lines.push(colorize('(missing — to be translated)', ANSIColors.ORANGE));\n }\n\n return lines.join('\\n');\n });\n\n const editingNote = colorize(\n 'Tip: start editing from the last block — working bottom-up keeps earlier line numbers accurate.',\n ANSIColors.GREY\n );\n\n return [header, '', ...sections, '', editingNote].join('\\n');\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"reviewReport.cjs","names":["buildAlignmentPlan","colorize","ANSIColors","colorizeNumber"],"sources":["../../../src/docReview/reviewReport.ts"],"sourcesContent":["import * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, colorizeNumber } from '@intlayer/config/logger';\nimport { buildAlignmentPlan } from './pipeline';\n\n/**\n * The kind of change detected for a block when reviewing a translation against\n * its base document.\n *\n * - `review`: the base block changed and its translation must be updated.\n * - `insert_new`: the base block has no translation yet and must be added.\n * - `delete`: the target block no longer exists in the base and should be removed.\n */\nexport type ReviewBlockAction = 'review' | 'insert_new' | 'delete';\n\n/** A 1-based, inclusive line range within a document. */\nexport type LineRange = {\n start: number;\n end: number;\n};\n\n/**\n * A single block that diverges between the base document and its translation.\n *\n * This is the unit an external translator (AI client, human, or agent) needs to\n * act on: it carries the base content to translate, the existing translation to\n * update, and where each lives so the change can be located in the file.\n */\nexport type ReviewReportBlock = {\n /** What should happen to this block. */\n action: ReviewBlockAction;\n /** Line range of the block in the base document (omitted for pure deletions). */\n baseLineRange?: LineRange;\n /** Line range of the block in the target document (omitted for new insertions). */\n targetLineRange?: LineRange;\n /** Raw markdown of the base block (omitted for pure deletions). */\n baseContent?: string;\n /** Existing translation of the block (omitted for new insertions). */\n targetContent?: string;\n};\n\nexport type ReviewReportSummary = {\n /** Number of blocks reused as-is (unchanged translation). */\n reuse: number;\n /** Number of blocks whose translation must be updated. */\n review: number;\n /** Number of blocks missing a translation. */\n insertNew: number;\n /** Number of stale target blocks to delete. */\n delete: number;\n};\n\nexport type ReviewReport = {\n /** Only the blocks that diverge (review, insert_new, delete). */\n blocks: ReviewReportBlock[];\n /** Counts per action over the whole document, including reused blocks. */\n summary: ReviewReportSummary;\n};\n\nexport type BuildReviewReportInput = {\n /** The base (source) document, used as the translation reference. */\n baseText: string;\n /** The existing target (translated) document, possibly empty. */\n targetText: string;\n /**\n * 1-based line numbers that changed in the base document. When omitted, only\n * inserted and deleted blocks are reported (no aligned block is flagged for\n * review since there is no way to know which ones changed).\n */\n changedLines?: number[];\n};\n\n/**\n * Compare a base markdown document with its translation and report only the\n * blocks that need attention, with their line ranges and content.\n *\n * Reusable across the CLI (`doc review --log`), the backend (comparing two\n * translation contents stored in database), and agents that generate the\n * missing translations.\n *\n * @param input - The base/target texts and optional changed lines.\n * @returns The divergent blocks and a per-action summary.\n */\nexport const buildReviewReport = ({\n baseText,\n targetText,\n changedLines,\n}: BuildReviewReportInput): ReviewReport => {\n const { baseBlocks, targetBlocks, plan } = buildAlignmentPlan({\n baseText,\n targetText,\n changedLines,\n });\n\n const summary: ReviewReportSummary = {\n reuse: 0,\n review: 0,\n insertNew: 0,\n delete: 0,\n };\n\n const blocks: ReviewReportBlock[] = [];\n\n for (const [actionIndex, action] of plan.actions.entries()) {\n if (action.kind === 'reuse') {\n summary.reuse += 1;\n continue;\n }\n\n if (action.kind === 'review') {\n summary.review += 1;\n const baseBlock = baseBlocks[action.baseIndex];\n\n if (!baseBlock) continue;\n\n const targetBlock =\n action.targetIndex !== null ? targetBlocks[action.targetIndex] : null;\n\n blocks.push({\n action: 'review',\n baseLineRange: { start: baseBlock.lineStart, end: baseBlock.lineEnd },\n targetLineRange: targetBlock\n ? { start: targetBlock.lineStart, end: targetBlock.lineEnd }\n : undefined,\n baseContent: baseBlock.content,\n targetContent: targetBlock?.content,\n });\n continue;\n }\n\n if (action.kind === 'insert_new') {\n summary.insertNew += 1;\n const baseBlock = baseBlocks[action.baseIndex];\n\n if (!baseBlock) continue;\n\n // Find the target line after which the new block should be inserted:\n // scan backwards for the last action that produced a target block.\n let insertAfterLine: number | undefined;\n for (let prevIndex = actionIndex - 1; prevIndex >= 0; prevIndex--) {\n const prevAction = plan.actions[prevIndex];\n if (!prevAction) continue;\n\n let targetIdx: number | null = null;\n if (prevAction.kind === 'reuse') targetIdx = prevAction.targetIndex;\n else if (\n prevAction.kind === 'review' &&\n prevAction.targetIndex !== null\n )\n targetIdx = prevAction.targetIndex;\n else if (prevAction.kind === 'delete')\n targetIdx = prevAction.targetIndex;\n\n if (targetIdx !== null) {\n const prevTargetBlock = targetBlocks[targetIdx];\n if (prevTargetBlock) {\n insertAfterLine = prevTargetBlock.lineEnd + 1;\n break;\n }\n }\n }\n\n blocks.push({\n action: 'insert_new',\n baseLineRange: { start: baseBlock.lineStart, end: baseBlock.lineEnd },\n targetLineRange:\n insertAfterLine !== undefined\n ? { start: insertAfterLine, end: insertAfterLine }\n : undefined,\n baseContent: baseBlock.content,\n });\n continue;\n }\n\n if (action.kind === 'delete') {\n summary.delete += 1;\n const targetBlock = targetBlocks[action.targetIndex];\n\n if (!targetBlock) continue;\n\n blocks.push({\n action: 'delete',\n targetLineRange: {\n start: targetBlock.lineStart,\n end: targetBlock.lineEnd,\n },\n targetContent: targetBlock.content,\n });\n }\n }\n\n return { blocks, summary };\n};\n\n/** Render a line range as a blue colored string, or an orange dash when absent. */\nconst colorizeLineRange = (range?: LineRange): string => {\n if (!range) return colorize('—', ANSIColors.ORANGE);\n const rangeStr =\n range.start === range.end\n ? `L${range.start}`\n : `L${range.start}-${range.end}`;\n return colorize(rangeStr, ANSIColors.BLUE);\n};\n\n/**\n * Colorize a count with green when zero (nothing to do) and a caller-supplied\n * non-zero color (orange for warnings, red for destructive actions).\n */\nconst colorizeCount = (\n count: number,\n nonZeroColor: (typeof ANSIColors)[keyof typeof ANSIColors]\n): string =>\n colorizeNumber(count, {\n zero: ANSIColors.GREEN,\n one: nonZeroColor,\n two: nonZeroColor,\n few: nonZeroColor,\n many: nonZeroColor,\n other: nonZeroColor,\n });\n\n/**\n * Render a {@link ReviewReport} as a human and agent readable log.\n *\n * Each divergent block is printed with its action, the base/target line ranges,\n * the base content to translate, and the existing translation to update.\n *\n * @param report - The report to format.\n * @param options - Optional labels for the base and target locales.\n * @returns A multi-line string describing every block that needs attention.\n */\nexport const formatReviewReport = (\n report: ReviewReport,\n options?: { baseLabel?: string; targetLabel?: string }\n): string => {\n const baseLabel = options?.baseLabel ?? 'base';\n const targetLabel = options?.targetLabel ?? 'target';\n\n const { summary, blocks } = report;\n\n // Build the summary header piece-by-piece so each count uses its own color.\n const header = [\n colorize('Review report: ', ANSIColors.ORANGE),\n colorizeCount(blocks.length, ANSIColors.ORANGE),\n colorize(' block(s) need attention (review=', ANSIColors.ORANGE),\n colorizeCount(summary.review, ANSIColors.ORANGE),\n colorize(', new=', ANSIColors.ORANGE),\n colorizeCount(summary.insertNew, ANSIColors.ORANGE),\n colorize(', delete=', ANSIColors.ORANGE),\n colorizeCount(summary.delete, ANSIColors.RED),\n colorize(', reuse=', ANSIColors.ORANGE),\n colorizeNumber(summary.reuse),\n colorize(').', ANSIColors.ORANGE),\n ].join('');\n\n if (blocks.length === 0) {\n return `${header}\\n${colorize('No changes needed.', ANSIColors.GREEN)}`;\n }\n\n const sections = blocks.map((block, index) => {\n const lines: string[] = [];\n\n // Block header: each token colored individually.\n const blockHeader = [\n colorize('--- Block ', ANSIColors.ORANGE),\n colorizeNumber(index + 1),\n colorize('/', ANSIColors.ORANGE),\n colorizeNumber(blocks.length),\n colorize(` [${block.action}] `, ANSIColors.ORANGE),\n baseLabel,\n colorize(' ', ANSIColors.ORANGE),\n colorizeLineRange(block.baseLineRange),\n colorize(' → ', ANSIColors.ORANGE),\n targetLabel,\n colorize(' ', ANSIColors.ORANGE),\n colorizeLineRange(block.targetLineRange),\n colorize(' ---', ANSIColors.ORANGE),\n ].join('');\n\n lines.push(blockHeader);\n\n if (block.baseContent !== undefined) {\n lines.push(colorize(`[${baseLabel}]`, ANSIColors.BEIGE));\n lines.push(colorize(block.baseContent.trimEnd(), ANSIColors.GREY));\n }\n\n if (block.targetContent !== undefined) {\n lines.push(colorize(`[${targetLabel}]`, ANSIColors.BEIGE));\n lines.push(colorize(block.targetContent.trimEnd(), ANSIColors.GREY));\n } else if (block.action === 'insert_new') {\n lines.push(colorize(`[${targetLabel}]`, ANSIColors.BEIGE));\n lines.push(colorize('(missing — to be translated)', ANSIColors.ORANGE));\n }\n\n return lines.join('\\n');\n });\n\n const editingNote = colorize(\n 'Tip: start editing from the last block — working bottom-up keeps earlier line numbers accurate.',\n ANSIColors.GREY\n );\n\n return [header, '', ...sections, '', editingNote].join('\\n');\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"segmentDocument.cjs","names":[],"sources":["../../../src/docReview/segmentDocument.ts"],"sourcesContent":["import type { Block, BlockType } from './types';\n\nconst HEADING_PATTERN = /^\\s*(#{1,6})\\s+/;\n\nconst isBlankLine = (line: string): boolean => line.trim().length === 0;\nconst isFencedCodeDelimiter = (line: string): boolean => /^\\s*```/.test(line);\n\n/**\n * Read the depth of an ATX markdown heading (`#` → 1, `######` → 6).\n *\n * @param line - The line to inspect.\n * @returns The heading depth, or `null` when the line is not a heading.\n */\nconst parseHeadingDepth = (line: string): number | null => {\n const match = HEADING_PATTERN.exec(line);\n\n return match?.[1]?.length ?? null;\n};\n\nconst isHeading = (line: string): boolean => parseHeadingDepth(line) !== null;\nconst isFrontmatterDelimiter = (line: string): boolean =>\n /^\\s*---\\s*$/.test(line);\n\n/**\n * A content unit (heading, paragraph, code block or frontmatter) spanning a\n * 0-based, inclusive line range. Blank-line runs are not units of their own;\n * they are folded into the preceding unit as trailing separators (see\n * {@link segmentDocument}) so that the concatenation of every block's content\n * reproduces the document byte-for-byte.\n */\ntype ContentUnit = {\n type: BlockType;\n /** Depth of the ATX heading opening the unit, `null` when it is not a heading. */\n headingDepth: number | null;\n startIndex: number;\n endIndex: number;\n};\n\n/**\n * Split a markdown document into fine-grained blocks.\n *\n * Boundaries are drawn at headings, blank lines (paragraph breaks) and fenced\n * code blocks, while frontmatter and the inside of code fences are kept intact.\n * Each blank-line run is appended to the block that precedes it, so the blocks\n * form an exact partition of the document: concatenating every `content` (in\n * order) yields the original text unchanged. This is what lets the block-aware\n * review re-translate only the paragraphs/snippets that actually changed instead\n * of the whole heading section.\n *\n * @param text - The full markdown document.\n * @returns The ordered list of blocks with their 1-based line ranges.\n */\nexport const segmentDocument = (text: string): Block[] => {\n const lines = text.split('\\n');\n const lineCount = lines.length;\n\n // 1. Tokenize into content units, skipping blank-line runs (folded in below).\n const units: ContentUnit[] = [];\n let index = 0;\n\n while (index < lineCount) {\n const currentLine = lines[index];\n\n if (isBlankLine(currentLine)) {\n index += 1;\n continue;\n }\n\n // Frontmatter: only when it opens the document.\n if (units.length === 0 && isFrontmatterDelimiter(currentLine)) {\n const startIndex = index;\n index += 1;\n while (index < lineCount && !isFrontmatterDelimiter(lines[index])) {\n index += 1;\n }\n // Include the closing delimiter when present.\n if (index < lineCount) index += 1;\n units.push({\n type: 'unknown',\n headingDepth: null,\n startIndex,\n endIndex: index - 1,\n });\n continue;\n }\n\n // Fenced code block: consumed whole so inner blank lines and `#` lines are\n // never treated as boundaries.\n if (isFencedCodeDelimiter(currentLine)) {\n const startIndex = index;\n index += 1;\n while (index < lineCount && !isFencedCodeDelimiter(lines[index])) {\n index += 1;\n }\n // Include the closing fence when present.\n if (index < lineCount) index += 1;\n units.push({\n type: 'code_block',\n headingDepth: null,\n startIndex,\n endIndex: index - 1,\n });\n continue;\n }\n\n // Heading: a single self-contained line.\n const headingDepth = parseHeadingDepth(currentLine);\n\n if (headingDepth !== null) {\n units.push({\n type: 'heading',\n headingDepth,\n startIndex: index,\n endIndex: index,\n });\n index += 1;\n continue;\n }\n\n // Paragraph: a run of consecutive lines until a blank line, a heading or a\n // code fence. Tables and tight lists stay together (no blank line between\n // their rows/items).\n const startIndex = index;\n while (\n index < lineCount &&\n !isBlankLine(lines[index]) &&\n !isHeading(lines[index]) &&\n !isFencedCodeDelimiter(lines[index])\n ) {\n index += 1;\n }\n units.push({\n type: 'paragraph',\n headingDepth: null,\n startIndex,\n endIndex: index - 1,\n });\n }\n\n if (units.length === 0) return [];\n\n // 2. Turn each unit into a block whose line range extends to just before the\n // next unit, so the trailing blank-line run is owned by it. The first block\n // also absorbs any leading blank lines, and the last block runs to EOF.\n return units.map((unit, unitIndex): Block => {\n const blockStartIndex = unitIndex === 0 ? 0 : unit.startIndex;\n const blockEndIndex =\n unitIndex === units.length - 1\n ? lineCount - 1\n : units[unitIndex + 1].startIndex - 1;\n\n const blockLines = lines.slice(blockStartIndex, blockEndIndex + 1);\n // Re-append the boundary newline dropped by `split` for every block but the\n // one ending at EOF, so concatenating all blocks rebuilds the document.\n const content =\n blockEndIndex < lineCount - 1\n ? `${blockLines.join('\\n')}\\n`\n : blockLines.join('\\n');\n\n return {\n type: unit.type,\n content,\n headingDepth: unit.headingDepth,\n lineStart: blockStartIndex + 1,\n lineEnd: blockEndIndex + 1,\n };\n });\n};\n\n/**\n * Split a markdown document into coarse, heading-anchored sections.\n *\n * Built by grouping the fine blocks of {@link segmentDocument}: frontmatter and\n * each heading open a new section, and the following non-heading blocks are\n * folded into it. Because it only concatenates adjacent fine blocks, the result\n * is still an exact partition of the document (sections concatenate back to the\n * source unchanged).\n *\n * Sections are the robust alignment unit between a base document and its\n * translation — both share the same heading structure, so they align almost\n * perfectly and a translation that splits its prose into a different number of\n * paragraphs never causes a section to be dropped. Fine-grained review happens\n * within a section once it is known to have changed.\n *\n * @param text - The full markdown document.\n * @returns The ordered list of sections with their 1-based line ranges.\n */\nexport const segmentSections = (text: string): Block[] => {\n const fineBlocks = segmentDocument(text);\n const sections: Block[] = [];\n let currentBlocks: Block[] = [];\n\n const flushSection = (): void => {\n if (currentBlocks.length === 0) return;\n\n const [firstBlock] = currentBlocks;\n const lastBlock = currentBlocks[currentBlocks.length - 1];\n\n sections.push({\n type: firstBlock.type,\n content: currentBlocks.map((block) => block.content).join(''),\n // A section is identified by the heading that opens it, so it inherits its\n // depth — this is what keeps a `##` section from aligning with a `###` one.\n headingDepth: firstBlock.headingDepth,\n lineStart: firstBlock.lineStart,\n lineEnd: lastBlock.lineEnd,\n });\n currentBlocks = [];\n };\n\n for (const block of fineBlocks) {\n // Frontmatter (a leading `unknown` block) and every heading open a section.\n const opensSection =\n block.type === 'heading' ||\n (block.type === 'unknown' && sections.length === 0);\n\n if (opensSection) flushSection();\n currentBlocks.push(block);\n if (block.type === 'unknown' && sections.length === 0) flushSection();\n }\n\n flushSection();\n\n return sections;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"segmentDocument.cjs","names":[],"sources":["../../../src/docReview/segmentDocument.ts"],"sourcesContent":["import type { Block, BlockType } from './types';\n\nconst HEADING_PATTERN = /^\\s*(#{1,6})\\s+/;\n\nconst isBlankLine = (line: string): boolean => line.trim().length === 0;\nconst isFencedCodeDelimiter = (line: string): boolean => /^\\s*```/.test(line);\n\n/**\n * Read the depth of an ATX markdown heading (`#` → 1, `######` → 6).\n *\n * @param line - The line to inspect.\n * @returns The heading depth, or `null` when the line is not a heading.\n */\nconst parseHeadingDepth = (line: string): number | null => {\n const match = HEADING_PATTERN.exec(line);\n\n return match?.[1]?.length ?? null;\n};\n\nconst isHeading = (line: string): boolean => parseHeadingDepth(line) !== null;\nconst isFrontmatterDelimiter = (line: string): boolean =>\n /^\\s*---\\s*$/.test(line);\n\n/**\n * A content unit (heading, paragraph, code block or frontmatter) spanning a\n * 0-based, inclusive line range. Blank-line runs are not units of their own;\n * they are folded into the preceding unit as trailing separators (see\n * {@link segmentDocument}) so that the concatenation of every block's content\n * reproduces the document byte-for-byte.\n */\ntype ContentUnit = {\n type: BlockType;\n /** Depth of the ATX heading opening the unit, `null` when it is not a heading. */\n headingDepth: number | null;\n startIndex: number;\n endIndex: number;\n};\n\n/**\n * Split a markdown document into fine-grained blocks.\n *\n * Boundaries are drawn at headings, blank lines (paragraph breaks) and fenced\n * code blocks, while frontmatter and the inside of code fences are kept intact.\n * Each blank-line run is appended to the block that precedes it, so the blocks\n * form an exact partition of the document: concatenating every `content` (in\n * order) yields the original text unchanged. This is what lets the block-aware\n * review re-translate only the paragraphs/snippets that actually changed instead\n * of the whole heading section.\n *\n * @param text - The full markdown document.\n * @returns The ordered list of blocks with their 1-based line ranges.\n */\nexport const segmentDocument = (text: string): Block[] => {\n const lines = text.split('\\n');\n const lineCount = lines.length;\n\n // 1. Tokenize into content units, skipping blank-line runs (folded in below).\n const units: ContentUnit[] = [];\n let index = 0;\n\n while (index < lineCount) {\n const currentLine = lines[index];\n\n if (isBlankLine(currentLine)) {\n index += 1;\n continue;\n }\n\n // Frontmatter: only when it opens the document.\n if (units.length === 0 && isFrontmatterDelimiter(currentLine)) {\n const startIndex = index;\n index += 1;\n while (index < lineCount && !isFrontmatterDelimiter(lines[index])) {\n index += 1;\n }\n // Include the closing delimiter when present.\n if (index < lineCount) index += 1;\n units.push({\n type: 'unknown',\n headingDepth: null,\n startIndex,\n endIndex: index - 1,\n });\n continue;\n }\n\n // Fenced code block: consumed whole so inner blank lines and `#` lines are\n // never treated as boundaries.\n if (isFencedCodeDelimiter(currentLine)) {\n const startIndex = index;\n index += 1;\n while (index < lineCount && !isFencedCodeDelimiter(lines[index])) {\n index += 1;\n }\n // Include the closing fence when present.\n if (index < lineCount) index += 1;\n units.push({\n type: 'code_block',\n headingDepth: null,\n startIndex,\n endIndex: index - 1,\n });\n continue;\n }\n\n // Heading: a single self-contained line.\n const headingDepth = parseHeadingDepth(currentLine);\n\n if (headingDepth !== null) {\n units.push({\n type: 'heading',\n headingDepth,\n startIndex: index,\n endIndex: index,\n });\n index += 1;\n continue;\n }\n\n // Paragraph: a run of consecutive lines until a blank line, a heading or a\n // code fence. Tables and tight lists stay together (no blank line between\n // their rows/items).\n const startIndex = index;\n while (\n index < lineCount &&\n !isBlankLine(lines[index]) &&\n !isHeading(lines[index]) &&\n !isFencedCodeDelimiter(lines[index])\n ) {\n index += 1;\n }\n units.push({\n type: 'paragraph',\n headingDepth: null,\n startIndex,\n endIndex: index - 1,\n });\n }\n\n if (units.length === 0) return [];\n\n // 2. Turn each unit into a block whose line range extends to just before the\n // next unit, so the trailing blank-line run is owned by it. The first block\n // also absorbs any leading blank lines, and the last block runs to EOF.\n return units.map((unit, unitIndex): Block => {\n const blockStartIndex = unitIndex === 0 ? 0 : unit.startIndex;\n const blockEndIndex =\n unitIndex === units.length - 1\n ? lineCount - 1\n : units[unitIndex + 1].startIndex - 1;\n\n const blockLines = lines.slice(blockStartIndex, blockEndIndex + 1);\n // Re-append the boundary newline dropped by `split` for every block but the\n // one ending at EOF, so concatenating all blocks rebuilds the document.\n const content =\n blockEndIndex < lineCount - 1\n ? `${blockLines.join('\\n')}\\n`\n : blockLines.join('\\n');\n\n return {\n type: unit.type,\n content,\n headingDepth: unit.headingDepth,\n lineStart: blockStartIndex + 1,\n lineEnd: blockEndIndex + 1,\n };\n });\n};\n\n/**\n * Split a markdown document into coarse, heading-anchored sections.\n *\n * Built by grouping the fine blocks of {@link segmentDocument}: frontmatter and\n * each heading open a new section, and the following non-heading blocks are\n * folded into it. Because it only concatenates adjacent fine blocks, the result\n * is still an exact partition of the document (sections concatenate back to the\n * source unchanged).\n *\n * Sections are the robust alignment unit between a base document and its\n * translation — both share the same heading structure, so they align almost\n * perfectly and a translation that splits its prose into a different number of\n * paragraphs never causes a section to be dropped. Fine-grained review happens\n * within a section once it is known to have changed.\n *\n * @param text - The full markdown document.\n * @returns The ordered list of sections with their 1-based line ranges.\n */\nexport const segmentSections = (text: string): Block[] => {\n const fineBlocks = segmentDocument(text);\n const sections: Block[] = [];\n let currentBlocks: Block[] = [];\n\n const flushSection = (): void => {\n if (currentBlocks.length === 0) return;\n\n const [firstBlock] = currentBlocks;\n const lastBlock = currentBlocks[currentBlocks.length - 1];\n\n sections.push({\n type: firstBlock.type,\n content: currentBlocks.map((block) => block.content).join(''),\n // A section is identified by the heading that opens it, so it inherits its\n // depth — this is what keeps a `##` section from aligning with a `###` one.\n headingDepth: firstBlock.headingDepth,\n lineStart: firstBlock.lineStart,\n lineEnd: lastBlock.lineEnd,\n });\n currentBlocks = [];\n };\n\n for (const block of fineBlocks) {\n // Frontmatter (a leading `unknown` block) and every heading open a section.\n const opensSection =\n block.type === 'heading' ||\n (block.type === 'unknown' && sections.length === 0);\n\n if (opensSection) flushSection();\n currentBlocks.push(block);\n if (block.type === 'unknown' && sections.length === 0) flushSection();\n }\n\n flushSection();\n\n return sections;\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetchDistantDictionaries.cjs","names":["chunkArray","parallelize","x"],"sources":["../../src/fetchDistantDictionaries.ts"],"sourcesContent":["import { createIntlayerCMS } from '@intlayer/api';\nimport { dictionaryEndpoint } from '@intlayer/api/dictionary';\n// @ts-ignore @intlayer/backend is not build yet\nimport type { DictionaryAPI } from '@intlayer/backend';\nimport { getAppLogger, x } from '@intlayer/config/logger';\nimport { getConfiguration } from '@intlayer/config/node';\nimport { retryManager } from '@intlayer/config/utils';\nimport type { DictionariesStatus } from './loadDictionaries';\nimport { chunkArray } from './utils/chunkArray';\nimport { parallelize } from './utils/parallelize';\n\n/**\n * Number of dictionaries requested per HTTP call. Must stay under the batch\n * limit enforced by the backend (`MAX_DICTIONARY_KEYS_PER_REQUEST`).\n */\nconst DEFAULT_DICTIONARIES_PER_REQUEST = 3;\n\n/** Number of batch requests kept in flight at the same time. */\nconst DEFAULT_PARALLEL_REQUESTS = 5;\n\n/** Number of retries performed on a failing batch request. */\nconst DEFAULT_MAX_RETRY = 2;\n\n/** Delay between two attempts of a failing batch request, in milliseconds. */\nconst DEFAULT_RETRY_DELAY = 250;\n\n/** HTTP statuses worth another attempt despite being client errors. */\nconst RETRYABLE_CLIENT_ERROR_STATUSES = new Set([408, 425, 429]);\n\n/**\n * A request is worth retrying when it failed for a reason another attempt could\n * fix: a network error (no status at all) or a server-side / throttling status.\n * Replaying a `404` or a `401` only multiplies the noise.\n */\nconst isRetryableError = (error: unknown): boolean => {\n const status = (error as { status?: number })?.status;\n\n if (typeof status !== 'number') return true;\n\n if (status >= 500) return true;\n\n return RETRYABLE_CLIENT_ERROR_STATUSES.has(status);\n};\n\n/**\n * A backend that predates the batch endpoint routes `/dictionary/by-keys` to\n * its `/dictionary/:dictionaryKey` handler, which answers `404` - either as an\n * unknown dictionary or as an unknown route. Both mean the same thing here:\n * this backend cannot serve batches, so fall back to one request per key.\n */\nconst isBatchEndpointUnsupported = (error: unknown): boolean =>\n (error as { status?: number })?.status === 404;\n\ntype FetchDistantDictionariesOptions = {\n dictionaryKeys: string[];\n newDictionariesPath?: string;\n logPrefix?: string;\n /**\n * Number of dictionaries fetched by a single request.\n * @default 3\n */\n dictionariesPerRequest?: number;\n /**\n * Number of requests kept in flight at the same time.\n * @default 5\n */\n parallelRequests?: number;\n /**\n * Number of retries performed when a batch request fails.\n * @default 2\n */\n maxRetry?: number;\n /**\n * Delay between two attempts of a failing batch request, in milliseconds.\n * @default 250\n */\n retryDelay?: number;\n};\n\n/**\n * Fetch distant dictionaries and update the logger with their statuses.\n *\n * Keys are grouped into batches fetched by a single request each, and those\n * requests are kept in flight together — by default 5 concurrent requests of 3\n * dictionaries, so 15 dictionaries travel at any given time. A slot is refilled\n * as soon as its request settles, so no batch waits for the whole wave.\n * Failing requests are retried before their keys are reported as errors.\n */\nexport const fetchDistantDictionaries = async (\n options: FetchDistantDictionariesOptions,\n onStatusUpdate?: (status: DictionariesStatus[]) => void\n): Promise<DictionaryAPI[]> => {\n const config = getConfiguration();\n const appLogger = getAppLogger(config);\n try {\n const dictionary = dictionaryEndpoint(createIntlayerCMS(config));\n\n const {\n dictionariesPerRequest = DEFAULT_DICTIONARIES_PER_REQUEST,\n parallelRequests = DEFAULT_PARALLEL_REQUESTS,\n maxRetry = DEFAULT_MAX_RETRY,\n retryDelay = DEFAULT_RETRY_DELAY,\n } = options;\n\n // A same key can be listed twice when several qualified dictionaries share\n // it - one request per key is enough to retrieve all of its siblings.\n const distantDictionariesKeys = [...new Set(options.dictionaryKeys)];\n\n if (distantDictionariesKeys.length === 0) return [];\n\n const batches = chunkArray(distantDictionariesKeys, dictionariesPerRequest);\n\n // Flipped once for the whole run as soon as a backend proves unable to\n // serve batches, so the remaining batches skip the doomed request.\n let isBatchEndpointSupported = true;\n let hasLoggedBatchFallback = false;\n\n /**\n * Fetch each key of a batch with its own request. Used against backends\n * without the batch endpoint. A key missing on the remote only discards\n * itself, never its neighbours.\n */\n const requestDictionariesOneByOne = async (\n batchKeys: string[]\n ): Promise<DictionaryAPI[]> => {\n const results = await parallelize(\n batchKeys,\n async (dictionaryKey) => {\n try {\n const getDictionaryResult =\n await dictionary.getDictionary(dictionaryKey);\n\n return getDictionaryResult.data;\n } catch (_error) {\n return undefined;\n }\n },\n batchKeys.length\n );\n\n return results.filter(\n (distantDictionary): distantDictionary is DictionaryAPI =>\n distantDictionary !== undefined\n );\n };\n\n /**\n * Request one batch of dictionaries, retrying the whole request when it\n * fails for a transient reason - a batch carries several dictionaries, so\n * a network hiccup would otherwise discard all of them at once.\n */\n const requestBatch = retryManager(\n async (batchKeys: string[]): Promise<DictionaryAPI[]> => {\n if (isBatchEndpointSupported) {\n try {\n const getDictionariesResult =\n await dictionary.getDictionariesByKeys(batchKeys);\n\n return getDictionariesResult.data ?? [];\n } catch (error) {\n if (!isBatchEndpointUnsupported(error)) throw error;\n\n isBatchEndpointSupported = false;\n\n if (!hasLoggedBatchFallback) {\n hasLoggedBatchFallback = true;\n appLogger(\n 'This backend does not provide the batch dictionary endpoint - falling back to one request per dictionary.',\n { level: 'warn' }\n );\n }\n }\n }\n\n return await requestDictionariesOneByOne(batchKeys);\n },\n {\n maxRetry,\n delay: retryDelay,\n shouldRetry: isRetryableError,\n onError: ({ error, attempt }) =>\n appLogger(\n `Retrying distant dictionaries request (attempt ${attempt + 1}/${maxRetry}): ${error}`,\n { level: 'warn' }\n ),\n }\n );\n\n /**\n * Fetch one batch of dictionaries within a single request, and report the\n * resulting status of every key it contains.\n */\n const processBatch = async (\n batchKeys: string[]\n ): Promise<DictionaryAPI[]> => {\n onStatusUpdate?.(\n batchKeys.map((dictionaryKey) => ({\n dictionaryKey,\n type: 'remote',\n status: 'fetching',\n }))\n );\n\n try {\n const distantDictionaries = await requestBatch(batchKeys);\n\n const fetchedKeys = new Set(\n distantDictionaries.map(\n (distantDictionary: DictionaryAPI) => distantDictionary.key\n )\n );\n\n onStatusUpdate?.(\n batchKeys.map(\n (dictionaryKey): DictionariesStatus =>\n fetchedKeys.has(dictionaryKey)\n ? {\n dictionaryKey,\n type: 'remote',\n status: 'fetched',\n }\n : {\n dictionaryKey,\n type: 'remote',\n status: 'error',\n error: `Dictionary ${dictionaryKey} not found on remote`,\n }\n )\n );\n\n return distantDictionaries;\n } catch (error) {\n onStatusUpdate?.(\n batchKeys.map((dictionaryKey) => ({\n dictionaryKey,\n type: 'remote',\n status: 'error',\n error: `Error fetching dictionary ${dictionaryKey}: ${error}`,\n }))\n );\n return [];\n }\n };\n\n const batchResults = await parallelize(\n batches,\n processBatch,\n parallelRequests\n );\n\n return batchResults.flat();\n } catch (_error) {\n appLogger(`${x} Failed to fetch distant dictionaries`, { level: 'error' });\n return [];\n }\n};\n"],"mappings":";;;;;;;;;;;;;;AAeA,MAAM,mCAAmC;;AAGzC,MAAM,4BAA4B;;AAGlC,MAAM,oBAAoB;;AAG1B,MAAM,sBAAsB;;AAG5B,MAAM,kDAAkC,IAAI,IAAI;CAAC;CAAK;CAAK;AAAG,CAAC;;;;;;AAO/D,MAAM,oBAAoB,UAA4B;CACpD,MAAM,SAAU,OAA+B;CAE/C,IAAI,OAAO,WAAW,UAAU,OAAO;CAEvC,IAAI,UAAU,KAAK,OAAO;CAE1B,OAAO,gCAAgC,IAAI,MAAM;AACnD;;;;;;;AAQA,MAAM,8BAA8B,UACjC,OAA+B,WAAW;;;;;;;;;;AAqC7C,MAAa,2BAA2B,OACtC,SACA,mBAC6B;CAC7B,MAAM,qDAA0B;CAChC,MAAM,sDAAyB,MAAM;CACrC,IAAI;EACF,MAAM,mGAAkD,MAAM,CAAC;EAE/D,MAAM,EACJ,yBAAyB,kCACzB,mBAAmB,2BACnB,WAAW,mBACX,aAAa,wBACX;EAIJ,MAAM,0BAA0B,CAAC,GAAG,IAAI,IAAI,QAAQ,cAAc,CAAC;EAEnE,IAAI,wBAAwB,WAAW,GAAG,OAAO,CAAC;EAElD,MAAM,UAAUA,oCAAW,yBAAyB,sBAAsB;EAI1E,IAAI,2BAA2B;EAC/B,IAAI,yBAAyB;;;;;;EAO7B,MAAM,8BAA8B,OAClC,cAC6B;GAgB7B,QAAO,MAfeC,sCACpB,WACA,OAAO,kBAAkB;IACvB,IAAI;KAIF,QAAO,MAFC,WAAW,cAAc,aAAa,EAEpB,CAAC;IAC7B,SAAS,QAAQ;KACf;IACF;GACF,GACA,UAAU,MACZ,EAEc,CAAC,QACZ,sBACC,sBAAsB,MAC1B;EACF;;;;;;EAOA,MAAM,wDACJ,OAAO,cAAkD;GACvD,IAAI,0BACF,IAAI;IAIF,QAAO,MAFC,WAAW,sBAAsB,SAAS,EAEtB,CAAC,QAAQ,CAAC;GACxC,SAAS,OAAO;IACd,IAAI,CAAC,2BAA2B,KAAK,GAAG,MAAM;IAE9C,2BAA2B;IAE3B,IAAI,CAAC,wBAAwB;KAC3B,yBAAyB;KACzB,UACE,6GACA,EAAE,OAAO,OAAO,CAClB;IACF;GACF;GAGF,OAAO,MAAM,4BAA4B,SAAS;EACpD,GACA;GACE;GACA,OAAO;GACP,aAAa;GACb,UAAU,EAAE,OAAO,cACjB,UACE,kDAAkD,UAAU,EAAE,GAAG,SAAS,KAAK,SAC/E,EAAE,OAAO,OAAO,CAClB;EACJ,CACF;;;;;EAMA,MAAM,eAAe,OACnB,cAC6B;GAC7B,iBACE,UAAU,KAAK,mBAAmB;IAChC;IACA,MAAM;IACN,QAAQ;GACV,EAAE,CACJ;GAEA,IAAI;IACF,MAAM,sBAAsB,MAAM,aAAa,SAAS;IAExD,MAAM,cAAc,IAAI,IACtB,oBAAoB,KACjB,sBAAqC,kBAAkB,GAC1D,CACF;IAEA,iBACE,UAAU,KACP,kBACC,YAAY,IAAI,aAAa,IACzB;KACE;KACA,MAAM;KACN,QAAQ;IACV,IACA;KACE;KACA,MAAM;KACN,QAAQ;KACR,OAAO,cAAc,cAAc;IACrC,CACR,CACF;IAEA,OAAO;GACT,SAAS,OAAO;IACd,iBACE,UAAU,KAAK,mBAAmB;KAChC;KACA,MAAM;KACN,QAAQ;KACR,OAAO,6BAA6B,cAAc,IAAI;IACxD,EAAE,CACJ;IACA,OAAO,CAAC;GACV;EACF;EAQA,QAAO,MANoBA,sCACzB,SACA,cACA,gBACF,EAEmB,CAAC,KAAK;CAC3B,SAAS,QAAQ;EACf,UAAU,GAAGC,0BAAE,wCAAwC,EAAE,OAAO,QAAQ,CAAC;EACzE,OAAO,CAAC;CACV;AACF"}
|
|
1
|
+
{"version":3,"file":"fetchDistantDictionaries.cjs","names":["getConfiguration","getAppLogger","dictionaryEndpoint","createIntlayerCMS","chunkArray","parallelize","retryManager","x"],"sources":["../../src/fetchDistantDictionaries.ts"],"sourcesContent":["import { createIntlayerCMS } from '@intlayer/api';\nimport { dictionaryEndpoint } from '@intlayer/api/dictionary';\n// @ts-ignore @intlayer/backend is not build yet\nimport type { DictionaryAPI } from '@intlayer/backend';\nimport { getAppLogger, x } from '@intlayer/config/logger';\nimport { getConfiguration } from '@intlayer/config/node';\nimport { retryManager } from '@intlayer/config/utils';\nimport type { DictionariesStatus } from './loadDictionaries';\nimport { chunkArray } from './utils/chunkArray';\nimport { parallelize } from './utils/parallelize';\n\n/**\n * Number of dictionaries requested per HTTP call. Must stay under the batch\n * limit enforced by the backend (`MAX_DICTIONARY_KEYS_PER_REQUEST`).\n */\nconst DEFAULT_DICTIONARIES_PER_REQUEST = 3;\n\n/** Number of batch requests kept in flight at the same time. */\nconst DEFAULT_PARALLEL_REQUESTS = 5;\n\n/** Number of retries performed on a failing batch request. */\nconst DEFAULT_MAX_RETRY = 2;\n\n/** Delay between two attempts of a failing batch request, in milliseconds. */\nconst DEFAULT_RETRY_DELAY = 250;\n\n/** HTTP statuses worth another attempt despite being client errors. */\nconst RETRYABLE_CLIENT_ERROR_STATUSES = new Set([408, 425, 429]);\n\n/**\n * A request is worth retrying when it failed for a reason another attempt could\n * fix: a network error (no status at all) or a server-side / throttling status.\n * Replaying a `404` or a `401` only multiplies the noise.\n */\nconst isRetryableError = (error: unknown): boolean => {\n const status = (error as { status?: number })?.status;\n\n if (typeof status !== 'number') return true;\n\n if (status >= 500) return true;\n\n return RETRYABLE_CLIENT_ERROR_STATUSES.has(status);\n};\n\n/**\n * A backend that predates the batch endpoint routes `/dictionary/by-keys` to\n * its `/dictionary/:dictionaryKey` handler, which answers `404` - either as an\n * unknown dictionary or as an unknown route. Both mean the same thing here:\n * this backend cannot serve batches, so fall back to one request per key.\n */\nconst isBatchEndpointUnsupported = (error: unknown): boolean =>\n (error as { status?: number })?.status === 404;\n\ntype FetchDistantDictionariesOptions = {\n dictionaryKeys: string[];\n newDictionariesPath?: string;\n logPrefix?: string;\n /**\n * Number of dictionaries fetched by a single request.\n * @default 3\n */\n dictionariesPerRequest?: number;\n /**\n * Number of requests kept in flight at the same time.\n * @default 5\n */\n parallelRequests?: number;\n /**\n * Number of retries performed when a batch request fails.\n * @default 2\n */\n maxRetry?: number;\n /**\n * Delay between two attempts of a failing batch request, in milliseconds.\n * @default 250\n */\n retryDelay?: number;\n};\n\n/**\n * Fetch distant dictionaries and update the logger with their statuses.\n *\n * Keys are grouped into batches fetched by a single request each, and those\n * requests are kept in flight together — by default 5 concurrent requests of 3\n * dictionaries, so 15 dictionaries travel at any given time. A slot is refilled\n * as soon as its request settles, so no batch waits for the whole wave.\n * Failing requests are retried before their keys are reported as errors.\n */\nexport const fetchDistantDictionaries = async (\n options: FetchDistantDictionariesOptions,\n onStatusUpdate?: (status: DictionariesStatus[]) => void\n): Promise<DictionaryAPI[]> => {\n const config = getConfiguration();\n const appLogger = getAppLogger(config);\n try {\n const dictionary = dictionaryEndpoint(createIntlayerCMS(config));\n\n const {\n dictionariesPerRequest = DEFAULT_DICTIONARIES_PER_REQUEST,\n parallelRequests = DEFAULT_PARALLEL_REQUESTS,\n maxRetry = DEFAULT_MAX_RETRY,\n retryDelay = DEFAULT_RETRY_DELAY,\n } = options;\n\n // A same key can be listed twice when several qualified dictionaries share\n // it - one request per key is enough to retrieve all of its siblings.\n const distantDictionariesKeys = [...new Set(options.dictionaryKeys)];\n\n if (distantDictionariesKeys.length === 0) return [];\n\n const batches = chunkArray(distantDictionariesKeys, dictionariesPerRequest);\n\n // Flipped once for the whole run as soon as a backend proves unable to\n // serve batches, so the remaining batches skip the doomed request.\n let isBatchEndpointSupported = true;\n let hasLoggedBatchFallback = false;\n\n /**\n * Fetch each key of a batch with its own request. Used against backends\n * without the batch endpoint. A key missing on the remote only discards\n * itself, never its neighbours.\n */\n const requestDictionariesOneByOne = async (\n batchKeys: string[]\n ): Promise<DictionaryAPI[]> => {\n const results = await parallelize(\n batchKeys,\n async (dictionaryKey) => {\n try {\n const getDictionaryResult =\n await dictionary.getDictionary(dictionaryKey);\n\n return getDictionaryResult.data;\n } catch (_error) {\n return undefined;\n }\n },\n batchKeys.length\n );\n\n return results.filter(\n (distantDictionary): distantDictionary is DictionaryAPI =>\n distantDictionary !== undefined\n );\n };\n\n /**\n * Request one batch of dictionaries, retrying the whole request when it\n * fails for a transient reason - a batch carries several dictionaries, so\n * a network hiccup would otherwise discard all of them at once.\n */\n const requestBatch = retryManager(\n async (batchKeys: string[]): Promise<DictionaryAPI[]> => {\n if (isBatchEndpointSupported) {\n try {\n const getDictionariesResult =\n await dictionary.getDictionariesByKeys(batchKeys);\n\n return getDictionariesResult.data ?? [];\n } catch (error) {\n if (!isBatchEndpointUnsupported(error)) throw error;\n\n isBatchEndpointSupported = false;\n\n if (!hasLoggedBatchFallback) {\n hasLoggedBatchFallback = true;\n appLogger(\n 'This backend does not provide the batch dictionary endpoint - falling back to one request per dictionary.',\n { level: 'warn' }\n );\n }\n }\n }\n\n return await requestDictionariesOneByOne(batchKeys);\n },\n {\n maxRetry,\n delay: retryDelay,\n shouldRetry: isRetryableError,\n onError: ({ error, attempt }) =>\n appLogger(\n `Retrying distant dictionaries request (attempt ${attempt + 1}/${maxRetry}): ${error}`,\n { level: 'warn' }\n ),\n }\n );\n\n /**\n * Fetch one batch of dictionaries within a single request, and report the\n * resulting status of every key it contains.\n */\n const processBatch = async (\n batchKeys: string[]\n ): Promise<DictionaryAPI[]> => {\n onStatusUpdate?.(\n batchKeys.map((dictionaryKey) => ({\n dictionaryKey,\n type: 'remote',\n status: 'fetching',\n }))\n );\n\n try {\n const distantDictionaries = await requestBatch(batchKeys);\n\n const fetchedKeys = new Set(\n distantDictionaries.map(\n (distantDictionary: DictionaryAPI) => distantDictionary.key\n )\n );\n\n onStatusUpdate?.(\n batchKeys.map(\n (dictionaryKey): DictionariesStatus =>\n fetchedKeys.has(dictionaryKey)\n ? {\n dictionaryKey,\n type: 'remote',\n status: 'fetched',\n }\n : {\n dictionaryKey,\n type: 'remote',\n status: 'error',\n error: `Dictionary ${dictionaryKey} not found on remote`,\n }\n )\n );\n\n return distantDictionaries;\n } catch (error) {\n onStatusUpdate?.(\n batchKeys.map((dictionaryKey) => ({\n dictionaryKey,\n type: 'remote',\n status: 'error',\n error: `Error fetching dictionary ${dictionaryKey}: ${error}`,\n }))\n );\n return [];\n }\n };\n\n const batchResults = await parallelize(\n batches,\n processBatch,\n parallelRequests\n );\n\n return batchResults.flat();\n } catch (_error) {\n appLogger(`${x} Failed to fetch distant dictionaries`, { level: 'error' });\n return [];\n }\n};\n"],"mappings":";;;;;;;;;;;;;;AAeA,MAAM,mCAAmC;;AAGzC,MAAM,4BAA4B;;AAGlC,MAAM,oBAAoB;;AAG1B,MAAM,sBAAsB;;AAG5B,MAAM,kDAAkC,IAAI,IAAI;CAAC;CAAK;CAAK;AAAG,CAAC;;;;;;AAO/D,MAAM,oBAAoB,UAA4B;CACpD,MAAM,SAAU,OAA+B;CAE/C,IAAI,OAAO,WAAW,UAAU,OAAO;CAEvC,IAAI,UAAU,KAAK,OAAO;CAE1B,OAAO,gCAAgC,IAAI,MAAM;AACnD;;;;;;;AAQA,MAAM,8BAA8B,UACjC,OAA+B,WAAW;;;;;;;;;;AAqC7C,MAAa,2BAA2B,OACtC,SACA,mBAC6B;CAC7B,MAAM,aAASA,wCAAiB;CAChC,MAAM,gBAAYC,sCAAa,MAAM;CACrC,IAAI;EACF,MAAM,iBAAaC,iDAAmBC,iCAAkB,MAAM,CAAC;EAE/D,MAAM,EACJ,yBAAyB,kCACzB,mBAAmB,2BACnB,WAAW,mBACX,aAAa,wBACX;EAIJ,MAAM,0BAA0B,CAAC,GAAG,IAAI,IAAI,QAAQ,cAAc,CAAC;EAEnE,IAAI,wBAAwB,WAAW,GAAG,OAAO,CAAC;EAElD,MAAM,UAAUC,oCAAW,yBAAyB,sBAAsB;EAI1E,IAAI,2BAA2B;EAC/B,IAAI,yBAAyB;;;;;;EAO7B,MAAM,8BAA8B,OAClC,cAC6B;GAgB7B,QAAO,MAfeC,sCACpB,WACA,OAAO,kBAAkB;IACvB,IAAI;KAIF,QAAO,MAFC,WAAW,cAAc,aAAa,EAEpB,CAAC;IAC7B,SAAS,QAAQ;KACf;IACF;GACF,GACA,UAAU,MACZ,EAEc,CAAC,QACZ,sBACC,sBAAsB,MAC1B;EACF;;;;;;EAOA,MAAM,mBAAeC,qCACnB,OAAO,cAAkD;GACvD,IAAI,0BACF,IAAI;IAIF,QAAO,MAFC,WAAW,sBAAsB,SAAS,EAEtB,CAAC,QAAQ,CAAC;GACxC,SAAS,OAAO;IACd,IAAI,CAAC,2BAA2B,KAAK,GAAG,MAAM;IAE9C,2BAA2B;IAE3B,IAAI,CAAC,wBAAwB;KAC3B,yBAAyB;KACzB,UACE,6GACA,EAAE,OAAO,OAAO,CAClB;IACF;GACF;GAGF,OAAO,MAAM,4BAA4B,SAAS;EACpD,GACA;GACE;GACA,OAAO;GACP,aAAa;GACb,UAAU,EAAE,OAAO,cACjB,UACE,kDAAkD,UAAU,EAAE,GAAG,SAAS,KAAK,SAC/E,EAAE,OAAO,OAAO,CAClB;EACJ,CACF;;;;;EAMA,MAAM,eAAe,OACnB,cAC6B;GAC7B,iBACE,UAAU,KAAK,mBAAmB;IAChC;IACA,MAAM;IACN,QAAQ;GACV,EAAE,CACJ;GAEA,IAAI;IACF,MAAM,sBAAsB,MAAM,aAAa,SAAS;IAExD,MAAM,cAAc,IAAI,IACtB,oBAAoB,KACjB,sBAAqC,kBAAkB,GAC1D,CACF;IAEA,iBACE,UAAU,KACP,kBACC,YAAY,IAAI,aAAa,IACzB;KACE;KACA,MAAM;KACN,QAAQ;IACV,IACA;KACE;KACA,MAAM;KACN,QAAQ;KACR,OAAO,cAAc,cAAc;IACrC,CACR,CACF;IAEA,OAAO;GACT,SAAS,OAAO;IACd,iBACE,UAAU,KAAK,mBAAmB;KAChC;KACA,MAAM;KACN,QAAQ;KACR,OAAO,6BAA6B,cAAc,IAAI;IACxD,EAAE,CACJ;IACA,OAAO,CAAC;GACV;EACF;EAQA,QAAO,MANoBD,sCACzB,SACA,cACA,gBACF,EAEmB,CAAC,KAAK;CAC3B,SAAS,QAAQ;EACf,UAAU,GAAGE,0BAAE,wCAAwC,EAAE,OAAO,QAAQ,CAAC;EACzE,OAAO,CAAC;CACV;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filterInvalidDictionaries.cjs","names":["formatPath","ANSIColors","formatLocale","x"],"sources":["../../src/filterInvalidDictionaries.ts"],"sourcesContent":["import * as ANSIColors from '@intlayer/config/colors';\nimport {\n colorize,\n colorizeKey,\n colorizePath,\n getAppLogger,\n x,\n} from '@intlayer/config/logger';\nimport { getBasePlugins, getContent } from '@intlayer/core/interpreter';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { Dictionary } from '@intlayer/types/dictionary';\nimport { formatLocale, formatPath } from './utils/formatter';\n\ntype IsInvalidDictionaryOptions = { checkSchema: boolean };\n\nexport const isInvalidDictionary = (\n dictionary: Dictionary | undefined,\n configuration?: IntlayerConfig,\n options?: IsInvalidDictionaryOptions\n): boolean => {\n const appLogger = getAppLogger(configuration);\n\n if (!dictionary) return false;\n\n const isLocal = Boolean(\n dictionary.location === 'local' || typeof dictionary.filePath === 'string'\n );\n const location = isLocal ? 'Local' : 'Remote';\n const hasKey = Boolean(dictionary.key);\n const hasContent = Boolean(dictionary.content);\n\n if (!hasKey) {\n appLogger(`${location} dictionary has no key`, {\n level: 'error',\n });\n\n return false;\n }\n\n // Reject keys that contain path-unsafe characters.\n // A key is used as a filename component ({key}.json), so slashes, backslashes,\n // null bytes, or pure dot-segments can escape the output directory or break\n // dictionary resolution.\n // Allows alphanumeric characters, dashes, and underscores.\n // Allows dots, but not as the first or last character, and no consecutive dots.\n // Allows chinese or russian characters e.g. 你好\n const isInsecureKey = !/^[\\p{L}\\p{N}]+([._-][\\p{L}\\p{N}]+)*$/u.test(\n dictionary.key\n );\n\n if (isInsecureKey) {\n appLogger(\n `Insecure key ${colorizeKey(dictionary.key)} at ${dictionary.filePath ? formatPath(dictionary.filePath) : colorizePath('Remote')} - dictionary filtered out`,\n { level: 'error' }\n );\n return false;\n }\n\n if (!hasContent) {\n appLogger(\n `${location} dictionary ${colorizeKey(dictionary.key)} has no content - ${dictionary.filePath ? formatPath(dictionary.filePath) : colorizePath('Remote')}`,\n {\n level: 'error',\n }\n );\n return false;\n }\n\n if (dictionary.schema && options?.checkSchema) {\n const isAsync =\n typeof dictionary.content === 'function' ||\n (typeof dictionary.content === 'object' &&\n dictionary.content !== null &&\n typeof (dictionary.content as any).then === 'function');\n\n if (!isAsync) {\n const locales = configuration?.internationalization?.locales ?? [];\n const isStrict =\n configuration?.internationalization.strictMode === 'strict';\n\n const schema =\n typeof dictionary.schema === 'string'\n ? configuration?.schemas?.[dictionary.schema]\n : undefined;\n\n if (schema && typeof schema.safeParse === 'function') {\n for (const locale of locales) {\n const resolvedContent = getContent(\n dictionary.content,\n {\n dictionaryKey: dictionary.key,\n keyPath: [],\n locale,\n },\n getBasePlugins(locale, !isStrict)\n );\n const result = (schema as any).safeParse(resolvedContent);\n\n if (!result.success) {\n appLogger(\n `${location} dictionary ${colorizeKey(dictionary.key)} has invalid content according to schema ${colorize(dictionary.schema as string, ANSIColors.ORANGE)} for locale ${formatLocale(locale)} - ${dictionary.filePath ? formatPath(dictionary.filePath) : colorizePath('Remote')}`,\n {\n level: 'error',\n }\n );\n\n result.error.issues.forEach((issue: any) => {\n appLogger(\n `${x} Error: ${colorizeKey(dictionary.key)} - ${formatLocale(locale)} - ${colorize(`${issue.path.join('.')}:`, ANSIColors.BLUE)} ${colorize(issue.message, ANSIColors.GREY)}`,\n {\n level: 'error',\n }\n );\n });\n\n return false;\n }\n }\n }\n }\n }\n\n return true;\n};\n\nexport const filterInvalidDictionaries = (\n dictionaries: (Dictionary | undefined)[] | undefined,\n configuration: IntlayerConfig,\n options?: IsInvalidDictionaryOptions\n): Dictionary[] =>\n (dictionaries ?? [])?.filter((dictionary) =>\n isInvalidDictionary(dictionary, configuration, options)\n ) as Dictionary[];\n"],"mappings":";;;;;;;;;AAeA,MAAa,uBACX,YACA,eACA,YACY;CACZ,MAAM,
|
|
1
|
+
{"version":3,"file":"filterInvalidDictionaries.cjs","names":["getAppLogger","colorizeKey","formatPath","colorizePath","getContent","getBasePlugins","colorize","ANSIColors","formatLocale","x"],"sources":["../../src/filterInvalidDictionaries.ts"],"sourcesContent":["import * as ANSIColors from '@intlayer/config/colors';\nimport {\n colorize,\n colorizeKey,\n colorizePath,\n getAppLogger,\n x,\n} from '@intlayer/config/logger';\nimport { getBasePlugins, getContent } from '@intlayer/core/interpreter';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { Dictionary } from '@intlayer/types/dictionary';\nimport { formatLocale, formatPath } from './utils/formatter';\n\ntype IsInvalidDictionaryOptions = { checkSchema: boolean };\n\nexport const isInvalidDictionary = (\n dictionary: Dictionary | undefined,\n configuration?: IntlayerConfig,\n options?: IsInvalidDictionaryOptions\n): boolean => {\n const appLogger = getAppLogger(configuration);\n\n if (!dictionary) return false;\n\n const isLocal = Boolean(\n dictionary.location === 'local' || typeof dictionary.filePath === 'string'\n );\n const location = isLocal ? 'Local' : 'Remote';\n const hasKey = Boolean(dictionary.key);\n const hasContent = Boolean(dictionary.content);\n\n if (!hasKey) {\n appLogger(`${location} dictionary has no key`, {\n level: 'error',\n });\n\n return false;\n }\n\n // Reject keys that contain path-unsafe characters.\n // A key is used as a filename component ({key}.json), so slashes, backslashes,\n // null bytes, or pure dot-segments can escape the output directory or break\n // dictionary resolution.\n // Allows alphanumeric characters, dashes, and underscores.\n // Allows dots, but not as the first or last character, and no consecutive dots.\n // Allows chinese or russian characters e.g. 你好\n const isInsecureKey = !/^[\\p{L}\\p{N}]+([._-][\\p{L}\\p{N}]+)*$/u.test(\n dictionary.key\n );\n\n if (isInsecureKey) {\n appLogger(\n `Insecure key ${colorizeKey(dictionary.key)} at ${dictionary.filePath ? formatPath(dictionary.filePath) : colorizePath('Remote')} - dictionary filtered out`,\n { level: 'error' }\n );\n return false;\n }\n\n if (!hasContent) {\n appLogger(\n `${location} dictionary ${colorizeKey(dictionary.key)} has no content - ${dictionary.filePath ? formatPath(dictionary.filePath) : colorizePath('Remote')}`,\n {\n level: 'error',\n }\n );\n return false;\n }\n\n if (dictionary.schema && options?.checkSchema) {\n const isAsync =\n typeof dictionary.content === 'function' ||\n (typeof dictionary.content === 'object' &&\n dictionary.content !== null &&\n typeof (dictionary.content as any).then === 'function');\n\n if (!isAsync) {\n const locales = configuration?.internationalization?.locales ?? [];\n const isStrict =\n configuration?.internationalization.strictMode === 'strict';\n\n const schema =\n typeof dictionary.schema === 'string'\n ? configuration?.schemas?.[dictionary.schema]\n : undefined;\n\n if (schema && typeof schema.safeParse === 'function') {\n for (const locale of locales) {\n const resolvedContent = getContent(\n dictionary.content,\n {\n dictionaryKey: dictionary.key,\n keyPath: [],\n locale,\n },\n getBasePlugins(locale, !isStrict)\n );\n const result = (schema as any).safeParse(resolvedContent);\n\n if (!result.success) {\n appLogger(\n `${location} dictionary ${colorizeKey(dictionary.key)} has invalid content according to schema ${colorize(dictionary.schema as string, ANSIColors.ORANGE)} for locale ${formatLocale(locale)} - ${dictionary.filePath ? formatPath(dictionary.filePath) : colorizePath('Remote')}`,\n {\n level: 'error',\n }\n );\n\n result.error.issues.forEach((issue: any) => {\n appLogger(\n `${x} Error: ${colorizeKey(dictionary.key)} - ${formatLocale(locale)} - ${colorize(`${issue.path.join('.')}:`, ANSIColors.BLUE)} ${colorize(issue.message, ANSIColors.GREY)}`,\n {\n level: 'error',\n }\n );\n });\n\n return false;\n }\n }\n }\n }\n }\n\n return true;\n};\n\nexport const filterInvalidDictionaries = (\n dictionaries: (Dictionary | undefined)[] | undefined,\n configuration: IntlayerConfig,\n options?: IsInvalidDictionaryOptions\n): Dictionary[] =>\n (dictionaries ?? [])?.filter((dictionary) =>\n isInvalidDictionary(dictionary, configuration, options)\n ) as Dictionary[];\n"],"mappings":";;;;;;;;;AAeA,MAAa,uBACX,YACA,eACA,YACY;CACZ,MAAM,gBAAYA,sCAAa,aAAa;CAE5C,IAAI,CAAC,YAAY,OAAO;CAKxB,MAAM,WAHU,QACd,WAAW,aAAa,WAAW,OAAO,WAAW,aAAa,QAE7C,IAAI,UAAU;CACrC,MAAM,SAAS,QAAQ,WAAW,GAAG;CACrC,MAAM,aAAa,QAAQ,WAAW,OAAO;CAE7C,IAAI,CAAC,QAAQ;EACX,UAAU,GAAG,SAAS,yBAAyB,EAC7C,OAAO,QACT,CAAC;EAED,OAAO;CACT;CAaA,IAAI,CAJmB,wCAAwC,KAC7D,WAAW,GACb,GAEmB;EACjB,UACE,oBAAgBC,qCAAY,WAAW,GAAG,EAAE,MAAM,WAAW,WAAWC,mCAAW,WAAW,QAAQ,QAAIC,sCAAa,QAAQ,EAAE,6BACjI,EAAE,OAAO,QAAQ,CACnB;EACA,OAAO;CACT;CAEA,IAAI,CAAC,YAAY;EACf,UACE,GAAG,SAAS,kBAAcF,qCAAY,WAAW,GAAG,EAAE,oBAAoB,WAAW,WAAWC,mCAAW,WAAW,QAAQ,QAAIC,sCAAa,QAAQ,KACvJ,EACE,OAAO,QACT,CACF;EACA,OAAO;CACT;CAEA,IAAI,WAAW,UAAU,SAAS,aAOhC;MAAI,EALF,OAAO,WAAW,YAAY,cAC7B,OAAO,WAAW,YAAY,YAC7B,WAAW,YAAY,QACvB,OAAQ,WAAW,QAAgB,SAAS,aAElC;GACZ,MAAM,UAAU,eAAe,sBAAsB,WAAW,CAAC;GACjE,MAAM,WACJ,eAAe,qBAAqB,eAAe;GAErD,MAAM,SACJ,OAAO,WAAW,WAAW,WACzB,eAAe,UAAU,WAAW,UACpC;GAEN,IAAI,UAAU,OAAO,OAAO,cAAc,YACxC,KAAK,MAAM,UAAU,SAAS;IAC5B,MAAM,sBAAkBC,uCACtB,WAAW,SACX;KACE,eAAe,WAAW;KAC1B,SAAS,CAAC;KACV;IACF,OACAC,2CAAe,QAAQ,CAAC,QAAQ,CAClC;IACA,MAAM,SAAU,OAAe,UAAU,eAAe;IAExD,IAAI,CAAC,OAAO,SAAS;KACnB,UACE,GAAG,SAAS,kBAAcJ,qCAAY,WAAW,GAAG,EAAE,+CAA2CK,kCAAS,WAAW,QAAkBC,wBAAW,MAAM,EAAE,cAAcC,qCAAa,MAAM,EAAE,KAAK,WAAW,WAAWN,mCAAW,WAAW,QAAQ,QAAIC,sCAAa,QAAQ,KAC/Q,EACE,OAAO,QACT,CACF;KAEA,OAAO,MAAM,OAAO,SAAS,UAAe;MAC1C,UACE,GAAGM,0BAAE,cAAUR,qCAAY,WAAW,GAAG,EAAE,KAAKO,qCAAa,MAAM,EAAE,SAAKF,kCAAS,GAAG,MAAM,KAAK,KAAK,GAAG,EAAE,IAAIC,wBAAW,IAAI,EAAE,OAAGD,kCAAS,MAAM,SAASC,wBAAW,IAAI,KAC1K,EACE,OAAO,QACT,CACF;KACF,CAAC;KAED,OAAO;IACT;GACF;EAEJ;;CAGF,OAAO;AACT;AAEA,MAAa,6BACX,cACA,eACA,aAEC,gBAAgB,CAAC,EAAC,EAAG,QAAQ,eAC5B,oBAAoB,YAAY,eAAe,OAAO,CACxD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formatDictionary.cjs","names":[],"sources":["../../src/formatDictionary.ts"],"sourcesContent":["import {\n i18nextToIntlayerFormatter,\n icuToIntlayerFormatter,\n intlayerToI18nextFormatter,\n intlayerToICUFormatter,\n intlayerToPortableObjectFormatter,\n intlayerToVueI18nFormatter,\n portableObjectToIntlayerFormatter,\n vueI18nToIntlayerFormatter,\n} from '@intlayer/core/messageFormat';\nimport type { Dictionary } from '@intlayer/types/dictionary';\n\nexport const formatDictionary = (dictionary: Dictionary): Dictionary => {\n if (dictionary.format === 'icu') {\n return {\n ...dictionary,\n format: 'intlayer',\n content: icuToIntlayerFormatter(dictionary.content),\n };\n }\n\n if (dictionary.format === 'i18next') {\n return {\n ...dictionary,\n format: 'intlayer',\n content: i18nextToIntlayerFormatter(dictionary.content),\n };\n }\n\n if (dictionary.format === 'vue-i18n') {\n return {\n ...dictionary,\n format: 'intlayer',\n content: vueI18nToIntlayerFormatter(dictionary.content),\n };\n }\n\n if (dictionary.format === 'po') {\n return {\n ...dictionary,\n format: 'intlayer',\n content: portableObjectToIntlayerFormatter(dictionary.content),\n };\n }\n\n return dictionary;\n};\n\nexport const formatDictionaries = async (\n dictionaries: Dictionary[]\n): Promise<Dictionary[]> => Promise.all(dictionaries.map(formatDictionary));\n\nexport const formatDictionaryOutput = (\n dictionary: Dictionary,\n format: Dictionary['format']\n) => {\n if (format === 'icu') {\n return {\n ...dictionary,\n format: 'icu',\n content: intlayerToICUFormatter(dictionary.content),\n };\n }\n\n if (format === 'i18next') {\n return {\n ...dictionary,\n format: 'i18next',\n content: intlayerToI18nextFormatter(dictionary.content),\n };\n }\n\n if (format === 'vue-i18n') {\n return {\n ...dictionary,\n format: 'vue-i18n',\n content: intlayerToVueI18nFormatter(dictionary.content),\n };\n }\n\n if (dictionary.format === 'po') {\n return {\n ...dictionary,\n format: 'po',\n content: intlayerToPortableObjectFormatter(dictionary.content),\n };\n }\n\n return dictionary;\n};\n\nexport const formatDictionariesOutput = (\n dictionaries: Dictionary[],\n format: Dictionary['format']\n) =>\n dictionaries.map((dictionary) => formatDictionaryOutput(dictionary, format));\n"],"mappings":";;;;AAYA,MAAa,oBAAoB,eAAuC;CACtE,IAAI,WAAW,WAAW,OACxB,OAAO;EACL,GAAG;EACH,QAAQ;EACR,
|
|
1
|
+
{"version":3,"file":"formatDictionary.cjs","names":["icuToIntlayerFormatter","i18nextToIntlayerFormatter","vueI18nToIntlayerFormatter","portableObjectToIntlayerFormatter","intlayerToICUFormatter","intlayerToI18nextFormatter","intlayerToVueI18nFormatter","intlayerToPortableObjectFormatter"],"sources":["../../src/formatDictionary.ts"],"sourcesContent":["import {\n i18nextToIntlayerFormatter,\n icuToIntlayerFormatter,\n intlayerToI18nextFormatter,\n intlayerToICUFormatter,\n intlayerToPortableObjectFormatter,\n intlayerToVueI18nFormatter,\n portableObjectToIntlayerFormatter,\n vueI18nToIntlayerFormatter,\n} from '@intlayer/core/messageFormat';\nimport type { Dictionary } from '@intlayer/types/dictionary';\n\nexport const formatDictionary = (dictionary: Dictionary): Dictionary => {\n if (dictionary.format === 'icu') {\n return {\n ...dictionary,\n format: 'intlayer',\n content: icuToIntlayerFormatter(dictionary.content),\n };\n }\n\n if (dictionary.format === 'i18next') {\n return {\n ...dictionary,\n format: 'intlayer',\n content: i18nextToIntlayerFormatter(dictionary.content),\n };\n }\n\n if (dictionary.format === 'vue-i18n') {\n return {\n ...dictionary,\n format: 'intlayer',\n content: vueI18nToIntlayerFormatter(dictionary.content),\n };\n }\n\n if (dictionary.format === 'po') {\n return {\n ...dictionary,\n format: 'intlayer',\n content: portableObjectToIntlayerFormatter(dictionary.content),\n };\n }\n\n return dictionary;\n};\n\nexport const formatDictionaries = async (\n dictionaries: Dictionary[]\n): Promise<Dictionary[]> => Promise.all(dictionaries.map(formatDictionary));\n\nexport const formatDictionaryOutput = (\n dictionary: Dictionary,\n format: Dictionary['format']\n) => {\n if (format === 'icu') {\n return {\n ...dictionary,\n format: 'icu',\n content: intlayerToICUFormatter(dictionary.content),\n };\n }\n\n if (format === 'i18next') {\n return {\n ...dictionary,\n format: 'i18next',\n content: intlayerToI18nextFormatter(dictionary.content),\n };\n }\n\n if (format === 'vue-i18n') {\n return {\n ...dictionary,\n format: 'vue-i18n',\n content: intlayerToVueI18nFormatter(dictionary.content),\n };\n }\n\n if (dictionary.format === 'po') {\n return {\n ...dictionary,\n format: 'po',\n content: intlayerToPortableObjectFormatter(dictionary.content),\n };\n }\n\n return dictionary;\n};\n\nexport const formatDictionariesOutput = (\n dictionaries: Dictionary[],\n format: Dictionary['format']\n) =>\n dictionaries.map((dictionary) => formatDictionaryOutput(dictionary, format));\n"],"mappings":";;;;AAYA,MAAa,oBAAoB,eAAuC;CACtE,IAAI,WAAW,WAAW,OACxB,OAAO;EACL,GAAG;EACH,QAAQ;EACR,aAASA,qDAAuB,WAAW,OAAO;CACpD;CAGF,IAAI,WAAW,WAAW,WACxB,OAAO;EACL,GAAG;EACH,QAAQ;EACR,aAASC,yDAA2B,WAAW,OAAO;CACxD;CAGF,IAAI,WAAW,WAAW,YACxB,OAAO;EACL,GAAG;EACH,QAAQ;EACR,aAASC,yDAA2B,WAAW,OAAO;CACxD;CAGF,IAAI,WAAW,WAAW,MACxB,OAAO;EACL,GAAG;EACH,QAAQ;EACR,aAASC,gEAAkC,WAAW,OAAO;CAC/D;CAGF,OAAO;AACT;AAEA,MAAa,qBAAqB,OAChC,iBAC0B,QAAQ,IAAI,aAAa,IAAI,gBAAgB,CAAC;AAE1E,MAAa,0BACX,YACA,WACG;CACH,IAAI,WAAW,OACb,OAAO;EACL,GAAG;EACH,QAAQ;EACR,aAASC,qDAAuB,WAAW,OAAO;CACpD;CAGF,IAAI,WAAW,WACb,OAAO;EACL,GAAG;EACH,QAAQ;EACR,aAASC,yDAA2B,WAAW,OAAO;CACxD;CAGF,IAAI,WAAW,YACb,OAAO;EACL,GAAG;EACH,QAAQ;EACR,aAASC,yDAA2B,WAAW,OAAO;CACxD;CAGF,IAAI,WAAW,WAAW,MACxB,OAAO;EACL,GAAG;EACH,QAAQ;EACR,aAASC,gEAAkC,WAAW,OAAO;CAC/D;CAGF,OAAO;AACT;AAEA,MAAa,4BACX,cACA,WAEA,aAAa,KAAK,eAAe,uBAAuB,YAAY,MAAM,CAAC"}
|
package/dist/cjs/getContentDeclarationFileTemplate/getContentDeclarationFileTemplate.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getContentDeclarationFileTemplate.cjs","names":[],"sources":["../../../src/getContentDeclarationFileTemplate/getContentDeclarationFileTemplate.ts"],"sourcesContent":["import { kebabCaseToCamelCase } from '@intlayer/config/utils';\nimport type { Format } from '../utils/getFormatFromExtension';\n\nexport const getContentDeclarationFileTemplate = async (\n key: string,\n format: Format,\n fileParams: Record<string, any> = {},\n noMetadata?: boolean\n) => {\n const camelCaseKey = kebabCaseToCamelCase(key);\n const name = camelCaseKey.charAt(0).toLowerCase() + camelCaseKey.slice(1);\n\n const fileParamsString = Object.entries(fileParams)\n .filter(([, value]) => value !== undefined)\n .map(([paramKey, value]) => {\n const formattedKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(paramKey)\n ? paramKey\n : JSON.stringify(paramKey);\n\n const formattedValue =\n typeof value === 'object' || typeof value === 'string'\n ? JSON.stringify(value)\n : value;\n\n return `\\n ${formattedKey}: ${formattedValue},`;\n })\n .join('');\n\n let content: string;\n\n if (noMetadata) {\n content = '{}';\n } else if (format === 'json' || format === 'jsonc' || format === 'json5') {\n content = [\n '{',\n ' \"$schema\": \"https://intlayer.org/schema.json\",',\n ` \"key\": \"${key}\",${fileParamsString}`,\n ' \"content\": {',\n ' }',\n '}',\n ].join('\\n');\n } else {\n content = [\n '{',\n ` key: '${key}',${fileParamsString}`,\n ' content: {',\n ' },',\n '}',\n ].join('\\n');\n }\n\n const jsdoc = `/** @type {import('intlayer').Dictionary${noMetadata ? \"['content']\" : ''}} **/`;\n const satisfiesType = noMetadata ? \"Dictionary['content']\" : 'Dictionary';\n\n switch (format) {\n case 'yaml': {\n const yamlLines = [\n `key: ${key}`,\n ...(fileParams.locale ? [`locale: ${fileParams.locale}`] : []),\n ...(fileParams.title\n ? [`title: ${JSON.stringify(fileParams.title)}`]\n : []),\n ...(fileParams.description\n ? [`description: ${JSON.stringify(fileParams.description)}`]\n : []),\n ...(fileParams.tags?.length\n ? [\n 'tags:',\n ...(fileParams.tags as string[]).map(\n (tag) => ` - ${JSON.stringify(tag)}`\n ),\n ]\n : []),\n 'content: {}',\n ];\n return `${yamlLines.join('\\n')}\\n`;\n }\n\n case 'md': {\n const mdFrontmatterLines = [\n `key: ${key}`,\n ...(fileParams.locale ? [`locale: ${fileParams.locale}`] : []),\n ...(fileParams.title\n ? [`title: ${JSON.stringify(fileParams.title)}`]\n : []),\n ...(fileParams.description\n ? [`description: ${JSON.stringify(fileParams.description)}`]\n : []),\n ...(fileParams.tags?.length\n ? [\n 'tags:',\n ...(fileParams.tags as string[]).map(\n (t) => ` - ${JSON.stringify(t)}`\n ),\n ]\n : []),\n ];\n return ['---', ...mdFrontmatterLines, '---', '', ''].join('\\n');\n }\n\n case 'ts':\n return [\n \"import { type Dictionary } from 'intlayer';\",\n '',\n `const ${name}Content = ${content} satisfies ${satisfiesType};`,\n '',\n `export default ${name}Content;`,\n '',\n ].join('\\n');\n\n case 'cjs':\n return [\n jsdoc,\n `const ${name}Content = ${content};`,\n '',\n `module.exports = ${name}Content;`,\n '',\n ].join('\\n');\n\n case 'json':\n case 'jsonc':\n case 'json5':\n return [content, ''].join('\\n');\n\n default: // esm\n return [\n jsdoc,\n `const ${name}Content = ${content};`,\n '',\n `export default ${name}Content;`,\n '',\n ].join('\\n');\n }\n};\n"],"mappings":";;;;AAGA,MAAa,oCAAoC,OAC/C,KACA,QACA,aAAkC,CAAC,GACnC,eACG;CACH,MAAM,
|
|
1
|
+
{"version":3,"file":"getContentDeclarationFileTemplate.cjs","names":["kebabCaseToCamelCase"],"sources":["../../../src/getContentDeclarationFileTemplate/getContentDeclarationFileTemplate.ts"],"sourcesContent":["import { kebabCaseToCamelCase } from '@intlayer/config/utils';\nimport type { Format } from '../utils/getFormatFromExtension';\n\nexport const getContentDeclarationFileTemplate = async (\n key: string,\n format: Format,\n fileParams: Record<string, any> = {},\n noMetadata?: boolean\n) => {\n const camelCaseKey = kebabCaseToCamelCase(key);\n const name = camelCaseKey.charAt(0).toLowerCase() + camelCaseKey.slice(1);\n\n const fileParamsString = Object.entries(fileParams)\n .filter(([, value]) => value !== undefined)\n .map(([paramKey, value]) => {\n const formattedKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(paramKey)\n ? paramKey\n : JSON.stringify(paramKey);\n\n const formattedValue =\n typeof value === 'object' || typeof value === 'string'\n ? JSON.stringify(value)\n : value;\n\n return `\\n ${formattedKey}: ${formattedValue},`;\n })\n .join('');\n\n let content: string;\n\n if (noMetadata) {\n content = '{}';\n } else if (format === 'json' || format === 'jsonc' || format === 'json5') {\n content = [\n '{',\n ' \"$schema\": \"https://intlayer.org/schema.json\",',\n ` \"key\": \"${key}\",${fileParamsString}`,\n ' \"content\": {',\n ' }',\n '}',\n ].join('\\n');\n } else {\n content = [\n '{',\n ` key: '${key}',${fileParamsString}`,\n ' content: {',\n ' },',\n '}',\n ].join('\\n');\n }\n\n const jsdoc = `/** @type {import('intlayer').Dictionary${noMetadata ? \"['content']\" : ''}} **/`;\n const satisfiesType = noMetadata ? \"Dictionary['content']\" : 'Dictionary';\n\n switch (format) {\n case 'yaml': {\n const yamlLines = [\n `key: ${key}`,\n ...(fileParams.locale ? [`locale: ${fileParams.locale}`] : []),\n ...(fileParams.title\n ? [`title: ${JSON.stringify(fileParams.title)}`]\n : []),\n ...(fileParams.description\n ? [`description: ${JSON.stringify(fileParams.description)}`]\n : []),\n ...(fileParams.tags?.length\n ? [\n 'tags:',\n ...(fileParams.tags as string[]).map(\n (tag) => ` - ${JSON.stringify(tag)}`\n ),\n ]\n : []),\n 'content: {}',\n ];\n return `${yamlLines.join('\\n')}\\n`;\n }\n\n case 'md': {\n const mdFrontmatterLines = [\n `key: ${key}`,\n ...(fileParams.locale ? [`locale: ${fileParams.locale}`] : []),\n ...(fileParams.title\n ? [`title: ${JSON.stringify(fileParams.title)}`]\n : []),\n ...(fileParams.description\n ? [`description: ${JSON.stringify(fileParams.description)}`]\n : []),\n ...(fileParams.tags?.length\n ? [\n 'tags:',\n ...(fileParams.tags as string[]).map(\n (t) => ` - ${JSON.stringify(t)}`\n ),\n ]\n : []),\n ];\n return ['---', ...mdFrontmatterLines, '---', '', ''].join('\\n');\n }\n\n case 'ts':\n return [\n \"import { type Dictionary } from 'intlayer';\",\n '',\n `const ${name}Content = ${content} satisfies ${satisfiesType};`,\n '',\n `export default ${name}Content;`,\n '',\n ].join('\\n');\n\n case 'cjs':\n return [\n jsdoc,\n `const ${name}Content = ${content};`,\n '',\n `module.exports = ${name}Content;`,\n '',\n ].join('\\n');\n\n case 'json':\n case 'jsonc':\n case 'json5':\n return [content, ''].join('\\n');\n\n default: // esm\n return [\n jsdoc,\n `const ${name}Content = ${content};`,\n '',\n `export default ${name}Content;`,\n '',\n ].join('\\n');\n }\n};\n"],"mappings":";;;;AAGA,MAAa,oCAAoC,OAC/C,KACA,QACA,aAAkC,CAAC,GACnC,eACG;CACH,MAAM,mBAAeA,6CAAqB,GAAG;CAC7C,MAAM,OAAO,aAAa,OAAO,CAAC,CAAC,CAAC,YAAY,IAAI,aAAa,MAAM,CAAC;CAExE,MAAM,mBAAmB,OAAO,QAAQ,UAAU,CAAC,CAChD,QAAQ,GAAG,WAAW,UAAU,MAAS,CAAC,CAC1C,KAAK,CAAC,UAAU,WAAW;EAU1B,OAAO,OATc,6BAA6B,KAAK,QAAQ,IAC3D,WACA,KAAK,UAAU,QAAQ,EAOA,IAJzB,OAAO,UAAU,YAAY,OAAO,UAAU,WAC1C,KAAK,UAAU,KAAK,IACpB,MAEwC;CAChD,CAAC,CAAC,CACD,KAAK,EAAE;CAEV,IAAI;CAEJ,IAAI,YACF,UAAU;MACL,IAAI,WAAW,UAAU,WAAW,WAAW,WAAW,SAC/D,UAAU;EACR;EACA;EACA,aAAa,IAAI,IAAI;EACrB;EACA;EACA;CACF,CAAC,CAAC,KAAK,IAAI;MAEX,UAAU;EACR;EACA,WAAW,IAAI,IAAI;EACnB;EACA;EACA;CACF,CAAC,CAAC,KAAK,IAAI;CAGb,MAAM,QAAQ,2CAA2C,aAAa,gBAAgB,GAAG;CACzF,MAAM,gBAAgB,aAAa,0BAA0B;CAE7D,QAAQ,QAAR;EACE,KAAK,QAoBH,OAAO,GAAG;GAlBR,QAAQ;GACR,GAAI,WAAW,SAAS,CAAC,WAAW,WAAW,QAAQ,IAAI,CAAC;GAC5D,GAAI,WAAW,QACX,CAAC,UAAU,KAAK,UAAU,WAAW,KAAK,GAAG,IAC7C,CAAC;GACL,GAAI,WAAW,cACX,CAAC,gBAAgB,KAAK,UAAU,WAAW,WAAW,GAAG,IACzD,CAAC;GACL,GAAI,WAAW,MAAM,SACjB,CACE,SACA,GAAI,WAAW,KAAkB,KAC9B,QAAQ,OAAO,KAAK,UAAU,GAAG,GACpC,CACF,IACA,CAAC;GACL;EAEgB,CAAC,CAAC,KAAK,IAAI,EAAE;EAGjC,KAAK,MAmBH,OAAO;GAAC;GAAO,GAAG;IAjBhB,QAAQ;IACR,GAAI,WAAW,SAAS,CAAC,WAAW,WAAW,QAAQ,IAAI,CAAC;IAC5D,GAAI,WAAW,QACX,CAAC,UAAU,KAAK,UAAU,WAAW,KAAK,GAAG,IAC7C,CAAC;IACL,GAAI,WAAW,cACX,CAAC,gBAAgB,KAAK,UAAU,WAAW,WAAW,GAAG,IACzD,CAAC;IACL,GAAI,WAAW,MAAM,SACjB,CACE,SACA,GAAI,WAAW,KAAkB,KAC9B,MAAM,OAAO,KAAK,UAAU,CAAC,GAChC,CACF,IACA,CAAC;GAE4B;GAAG;GAAO;GAAI;EAAE,CAAC,CAAC,KAAK,IAAI;EAGhE,KAAK,MACH,OAAO;GACL;GACA;GACA,SAAS,KAAK,YAAY,QAAQ,aAAa,cAAc;GAC7D;GACA,kBAAkB,KAAK;GACvB;EACF,CAAC,CAAC,KAAK,IAAI;EAEb,KAAK,OACH,OAAO;GACL;GACA,SAAS,KAAK,YAAY,QAAQ;GAClC;GACA,oBAAoB,KAAK;GACzB;EACF,CAAC,CAAC,KAAK,IAAI;EAEb,KAAK;EACL,KAAK;EACL,KAAK,SACH,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,KAAK,IAAI;EAEhC,SACE,OAAO;GACL;GACA,SAAS,KAAK,YAAY,QAAQ;GAClC;GACA,kBAAkB,KAAK;GACvB;EACF,CAAC,CAAC,KAAK,IAAI;CACf;AACF"}
|
|
@@ -11,8 +11,10 @@ let _intlayer_config_logger = require("@intlayer/config/logger");
|
|
|
11
11
|
const handleAdditionalContentDeclarationFile = async (filePath, config) => {
|
|
12
12
|
const appLogger = (0, _intlayer_config_logger.getAppLogger)(config);
|
|
13
13
|
appLogger(`Additional file detected: ${require_utils_formatter.formatPath(filePath)}`, { isVerbose: true });
|
|
14
|
-
const
|
|
15
|
-
|
|
14
|
+
const localeDictionaries = await require_loadDictionaries_loadLocalDictionaries.loadLocalDictionaries(filePath, config);
|
|
15
|
+
const dictionariesOutput = await require_buildIntlayerDictionary_buildIntlayerDictionary.buildDictionary(localeDictionaries, config);
|
|
16
|
+
const dictionariesToBuild = Object.values(dictionariesOutput?.mergedDictionaries ?? {}).map((dictionary) => dictionary.dictionary);
|
|
17
|
+
await require_createType_createType.createTypes(dictionariesToBuild, config);
|
|
16
18
|
await require_createDictionaryEntryPoint_createDictionaryEntryPoint.createDictionaryEntryPoint(config);
|
|
17
19
|
appLogger("Dictionaries built", { isVerbose: true });
|
|
18
20
|
await require_createType_createModuleAugmentation.createModuleAugmentation(config);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handleAdditionalContentDeclarationFile.cjs","names":["
|
|
1
|
+
{"version":3,"file":"handleAdditionalContentDeclarationFile.cjs","names":["getAppLogger","formatPath","loadLocalDictionaries","buildDictionary","createTypes","createDictionaryEntryPoint","createModuleAugmentation"],"sources":["../../src/handleAdditionalContentDeclarationFile.ts"],"sourcesContent":["import { getAppLogger } from '@intlayer/config/logger';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { buildDictionary } from './buildIntlayerDictionary/buildIntlayerDictionary';\nimport { createDictionaryEntryPoint } from './createDictionaryEntryPoint/createDictionaryEntryPoint';\nimport { createModuleAugmentation, createTypes } from './createType/index';\nimport { loadLocalDictionaries } from './loadDictionaries/loadLocalDictionaries';\nimport { formatPath } from './utils/formatter';\n\nexport const handleAdditionalContentDeclarationFile = async (\n filePath: string,\n config: IntlayerConfig\n) => {\n const appLogger = getAppLogger(config);\n\n // Process the file with the functionToRun\n appLogger(`Additional file detected: ${formatPath(filePath)}`, {\n isVerbose: true,\n });\n\n const localeDictionaries = await loadLocalDictionaries(filePath, config);\n\n const dictionariesOutput = await buildDictionary(localeDictionaries, config);\n\n const dictionariesToBuild = Object.values(\n dictionariesOutput?.mergedDictionaries ?? {}\n ).map((dictionary) => dictionary.dictionary);\n\n await createTypes(dictionariesToBuild, config);\n\n await createDictionaryEntryPoint(config);\n\n appLogger('Dictionaries built', {\n isVerbose: true,\n });\n\n await createModuleAugmentation(config);\n\n appLogger('Module augmentation built', {\n isVerbose: true,\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 config.plugins ?? []) {\n const { unmergedDictionaries, mergedDictionaries } = dictionariesOutput;\n\n await plugin.afterBuild?.({\n dictionaries: {\n unmergedDictionaries,\n mergedDictionaries,\n },\n configuration: config,\n });\n }\n};\n"],"mappings":";;;;;;;;;;AAQA,MAAa,yCAAyC,OACpD,UACA,WACG;CACH,MAAM,gBAAYA,sCAAa,MAAM;CAGrC,UAAU,6BAA6BC,mCAAW,QAAQ,KAAK,EAC7D,WAAW,KACb,CAAC;CAED,MAAM,qBAAqB,MAAMC,qEAAsB,UAAU,MAAM;CAEvE,MAAM,qBAAqB,MAAMC,wEAAgB,oBAAoB,MAAM;CAE3E,MAAM,sBAAsB,OAAO,OACjC,oBAAoB,sBAAsB,CAAC,CAC7C,CAAC,CAAC,KAAK,eAAe,WAAW,UAAU;CAE3C,MAAMC,0CAAY,qBAAqB,MAAM;CAE7C,MAAMC,yFAA2B,MAAM;CAEvC,UAAU,sBAAsB,EAC9B,WAAW,KACb,CAAC;CAED,MAAMC,qEAAyB,MAAM;CAErC,UAAU,6BAA6B,EACrC,WAAW,KACb,CAAC;CAID,WAAW,MAAM,UAAU,OAAO,WAAW,CAAC,GAAG;EAC/C,MAAM,EAAE,sBAAsB,uBAAuB;EAErD,MAAM,OAAO,aAAa;GACxB,cAAc;IACZ;IACA;GACF;GACA,eAAe;EACjB,CAAC;CACH;AACF"}
|
|
@@ -23,7 +23,8 @@ const handleContentDeclarationFileChange = async (filePath, config) => {
|
|
|
23
23
|
const updatedDictionariesPaths = updatedDictionaries.map((dictionary) => dictionary.dictionaryPath);
|
|
24
24
|
const { excludeKeys, hasRebuilt } = await require_cleanRemovedContentDeclaration.cleanRemovedContentDeclaration(filePath, localeDictionaries.map((dictionary) => dictionary.key), config);
|
|
25
25
|
const hasNewDictionaries = updatedDictionariesPaths.some((updatedDictionaryPath) => !allDictionariesPaths.includes(updatedDictionaryPath));
|
|
26
|
-
|
|
26
|
+
const dictionariesToBuild = updatedDictionaries.map((dictionary) => dictionary.dictionary);
|
|
27
|
+
await require_createType_createType.createTypes(dictionariesToBuild, config);
|
|
27
28
|
appLogger("TypeScript types built", { isVerbose: true });
|
|
28
29
|
await require_createType_createModuleAugmentation.createModuleAugmentation(config);
|
|
29
30
|
appLogger("Module augmentation built", { isVerbose: true });
|
|
@@ -36,7 +37,10 @@ const handleContentDeclarationFileChange = async (filePath, config) => {
|
|
|
36
37
|
const { mainDir } = config.system;
|
|
37
38
|
for (const extension of config.build.outputFormat) {
|
|
38
39
|
const entryPointPath = (0, node_path.join)(mainDir, `dictionaries.${extension}`);
|
|
39
|
-
if ((0, node_fs.existsSync)(entryPointPath))
|
|
40
|
+
if ((0, node_fs.existsSync)(entryPointPath)) {
|
|
41
|
+
const content = await (0, node_fs_promises.readFile)(entryPointPath, "utf-8");
|
|
42
|
+
await (0, node_fs_promises.writeFile)(entryPointPath, content);
|
|
43
|
+
}
|
|
40
44
|
}
|
|
41
45
|
for await (const plugin of config.plugins ?? []) {
|
|
42
46
|
const { unmergedDictionaries, mergedDictionaries } = dictionariesOutput;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handleContentDeclarationFileChange.cjs","names":["formatPath","getBuiltDictionariesPath","loadLocalDictionaries","buildDictionary","cleanRemovedContentDeclaration","createTypes","createModuleAugmentation","createDictionaryEntryPoint"],"sources":["../../src/handleContentDeclarationFileChange.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { getAppLogger } from '@intlayer/config/logger';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { buildDictionary } from './buildIntlayerDictionary/buildIntlayerDictionary';\nimport { cleanRemovedContentDeclaration } from './cleanRemovedContentDeclaration';\nimport { createDictionaryEntryPoint } from './createDictionaryEntryPoint/createDictionaryEntryPoint';\nimport { getBuiltDictionariesPath } from './createDictionaryEntryPoint/getBuiltDictionariesPath';\nimport { createTypes } from './createType';\nimport { createModuleAugmentation } from './createType/createModuleAugmentation';\nimport { loadLocalDictionaries } from './loadDictionaries/loadLocalDictionaries';\nimport { formatPath } from './utils/formatter';\n\nexport const handleContentDeclarationFileChange = async (\n filePath: string,\n config: IntlayerConfig\n) => {\n const appLogger = getAppLogger(config);\n\n // Process the file with the functionToRun\n appLogger(`Change detected: ${formatPath(filePath)}`, {\n isVerbose: true,\n });\n\n const allDictionariesPaths: string[] = await getBuiltDictionariesPath(config);\n\n const localeDictionaries = await loadLocalDictionaries(filePath, config);\n\n const dictionariesOutput = await buildDictionary(localeDictionaries, config);\n const updatedDictionaries = Object.values(\n dictionariesOutput?.mergedDictionaries ?? {}\n );\n const updatedDictionariesPaths = updatedDictionaries.map(\n (dictionary) => dictionary.dictionaryPath\n );\n\n const { excludeKeys, hasRebuilt } = await cleanRemovedContentDeclaration(\n filePath,\n localeDictionaries.map((dictionary) => dictionary.key),\n config\n );\n\n const hasNewDictionaries = updatedDictionariesPaths.some(\n (updatedDictionaryPath) =>\n !allDictionariesPaths.includes(updatedDictionaryPath)\n );\n\n // Rebuild Types first — types must be ready before anything triggers a bundler rebuild.\n // esbuild (used by Angular, Vite, etc.) only watches files in its JS bundle graph; it does\n // NOT watch .intlayer/types/*.ts (TypeScript-only files). Generating types here, before\n // updating the entry point, guarantees that when the entry point write below fires esbuild's\n // file watcher the types are already up-to-date.\n const dictionariesToBuild = updatedDictionaries.map(\n (dictionary) => dictionary.dictionary\n );\n\n await createTypes(dictionariesToBuild, config);\n appLogger('TypeScript types built', {\n isVerbose: true,\n });\n\n // Always regenerate the module augmentation so that structural changes to existing\n // dictionaries (new content keys, type changes) are reflected in intlayer.d.ts.\n await createModuleAugmentation(config);\n appLogger('Module augmentation built', {\n isVerbose: true,\n });\n\n // Rebuild Entry Point\n // Only needed when the *list* of dictionaries changed (Add/Remove/Rename).\n if (hasRebuilt || hasNewDictionaries) {\n // If hasRebuilt is true, cleanRemovedContentDeclaration has already updated the entry point\n // to remove the old keys (and it likely included the new ones if they were already on disk).\n // If NOT hasRebuilt, we explicitly need to update the entry point to include the new dictionaries.\n if (!hasRebuilt) {\n await createDictionaryEntryPoint(config, { excludeKeys });\n appLogger('Dictionary list built', {\n isVerbose: true,\n });\n }\n }\n\n // Force-write the entry point files so esbuild detects a change and triggers a fresh rebuild\n // now that all types are ready. Without this, the only rebuild esbuild sees is the one\n // triggered by buildDictionary's JSON writes above — which races against type generation\n // and often runs before the new .intlayer/types/*.ts files are on disk.\n const { mainDir } = config.system;\n\n for (const extension of config.build.outputFormat) {\n const entryPointPath = join(mainDir, `dictionaries.${extension}`);\n\n if (existsSync(entryPointPath)) {\n const content = await readFile(entryPointPath, 'utf-8');\n await writeFile(entryPointPath, content);\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 config.plugins ?? []) {\n const { unmergedDictionaries, mergedDictionaries } = dictionariesOutput;\n\n await plugin.afterBuild?.({\n dictionaries: {\n unmergedDictionaries,\n mergedDictionaries,\n },\n configuration: config,\n });\n }\n};\n"],"mappings":";;;;;;;;;;;;;;;AAcA,MAAa,qCAAqC,OAChD,UACA,WACG;CACH,MAAM,
|
|
1
|
+
{"version":3,"file":"handleContentDeclarationFileChange.cjs","names":["getAppLogger","formatPath","getBuiltDictionariesPath","loadLocalDictionaries","buildDictionary","cleanRemovedContentDeclaration","createTypes","createModuleAugmentation","createDictionaryEntryPoint","join","existsSync","readFile","writeFile"],"sources":["../../src/handleContentDeclarationFileChange.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { getAppLogger } from '@intlayer/config/logger';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { buildDictionary } from './buildIntlayerDictionary/buildIntlayerDictionary';\nimport { cleanRemovedContentDeclaration } from './cleanRemovedContentDeclaration';\nimport { createDictionaryEntryPoint } from './createDictionaryEntryPoint/createDictionaryEntryPoint';\nimport { getBuiltDictionariesPath } from './createDictionaryEntryPoint/getBuiltDictionariesPath';\nimport { createTypes } from './createType';\nimport { createModuleAugmentation } from './createType/createModuleAugmentation';\nimport { loadLocalDictionaries } from './loadDictionaries/loadLocalDictionaries';\nimport { formatPath } from './utils/formatter';\n\nexport const handleContentDeclarationFileChange = async (\n filePath: string,\n config: IntlayerConfig\n) => {\n const appLogger = getAppLogger(config);\n\n // Process the file with the functionToRun\n appLogger(`Change detected: ${formatPath(filePath)}`, {\n isVerbose: true,\n });\n\n const allDictionariesPaths: string[] = await getBuiltDictionariesPath(config);\n\n const localeDictionaries = await loadLocalDictionaries(filePath, config);\n\n const dictionariesOutput = await buildDictionary(localeDictionaries, config);\n const updatedDictionaries = Object.values(\n dictionariesOutput?.mergedDictionaries ?? {}\n );\n const updatedDictionariesPaths = updatedDictionaries.map(\n (dictionary) => dictionary.dictionaryPath\n );\n\n const { excludeKeys, hasRebuilt } = await cleanRemovedContentDeclaration(\n filePath,\n localeDictionaries.map((dictionary) => dictionary.key),\n config\n );\n\n const hasNewDictionaries = updatedDictionariesPaths.some(\n (updatedDictionaryPath) =>\n !allDictionariesPaths.includes(updatedDictionaryPath)\n );\n\n // Rebuild Types first — types must be ready before anything triggers a bundler rebuild.\n // esbuild (used by Angular, Vite, etc.) only watches files in its JS bundle graph; it does\n // NOT watch .intlayer/types/*.ts (TypeScript-only files). Generating types here, before\n // updating the entry point, guarantees that when the entry point write below fires esbuild's\n // file watcher the types are already up-to-date.\n const dictionariesToBuild = updatedDictionaries.map(\n (dictionary) => dictionary.dictionary\n );\n\n await createTypes(dictionariesToBuild, config);\n appLogger('TypeScript types built', {\n isVerbose: true,\n });\n\n // Always regenerate the module augmentation so that structural changes to existing\n // dictionaries (new content keys, type changes) are reflected in intlayer.d.ts.\n await createModuleAugmentation(config);\n appLogger('Module augmentation built', {\n isVerbose: true,\n });\n\n // Rebuild Entry Point\n // Only needed when the *list* of dictionaries changed (Add/Remove/Rename).\n if (hasRebuilt || hasNewDictionaries) {\n // If hasRebuilt is true, cleanRemovedContentDeclaration has already updated the entry point\n // to remove the old keys (and it likely included the new ones if they were already on disk).\n // If NOT hasRebuilt, we explicitly need to update the entry point to include the new dictionaries.\n if (!hasRebuilt) {\n await createDictionaryEntryPoint(config, { excludeKeys });\n appLogger('Dictionary list built', {\n isVerbose: true,\n });\n }\n }\n\n // Force-write the entry point files so esbuild detects a change and triggers a fresh rebuild\n // now that all types are ready. Without this, the only rebuild esbuild sees is the one\n // triggered by buildDictionary's JSON writes above — which races against type generation\n // and often runs before the new .intlayer/types/*.ts files are on disk.\n const { mainDir } = config.system;\n\n for (const extension of config.build.outputFormat) {\n const entryPointPath = join(mainDir, `dictionaries.${extension}`);\n\n if (existsSync(entryPointPath)) {\n const content = await readFile(entryPointPath, 'utf-8');\n await writeFile(entryPointPath, content);\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 config.plugins ?? []) {\n const { unmergedDictionaries, mergedDictionaries } = dictionariesOutput;\n\n await plugin.afterBuild?.({\n dictionaries: {\n unmergedDictionaries,\n mergedDictionaries,\n },\n configuration: config,\n });\n }\n};\n"],"mappings":";;;;;;;;;;;;;;;AAcA,MAAa,qCAAqC,OAChD,UACA,WACG;CACH,MAAM,gBAAYA,sCAAa,MAAM;CAGrC,UAAU,oBAAoBC,mCAAW,QAAQ,KAAK,EACpD,WAAW,KACb,CAAC;CAED,MAAM,uBAAiC,MAAMC,qFAAyB,MAAM;CAE5E,MAAM,qBAAqB,MAAMC,qEAAsB,UAAU,MAAM;CAEvE,MAAM,qBAAqB,MAAMC,wEAAgB,oBAAoB,MAAM;CAC3E,MAAM,sBAAsB,OAAO,OACjC,oBAAoB,sBAAsB,CAAC,CAC7C;CACA,MAAM,2BAA2B,oBAAoB,KAClD,eAAe,WAAW,cAC7B;CAEA,MAAM,EAAE,aAAa,eAAe,MAAMC,sEACxC,UACA,mBAAmB,KAAK,eAAe,WAAW,GAAG,GACrD,MACF;CAEA,MAAM,qBAAqB,yBAAyB,MACjD,0BACC,CAAC,qBAAqB,SAAS,qBAAqB,CACxD;CAOA,MAAM,sBAAsB,oBAAoB,KAC7C,eAAe,WAAW,UAC7B;CAEA,MAAMC,0CAAY,qBAAqB,MAAM;CAC7C,UAAU,0BAA0B,EAClC,WAAW,KACb,CAAC;CAID,MAAMC,qEAAyB,MAAM;CACrC,UAAU,6BAA6B,EACrC,WAAW,KACb,CAAC;CAID,IAAI,cAAc,oBAIhB;MAAI,CAAC,YAAY;GACf,MAAMC,yFAA2B,QAAQ,EAAE,YAAY,CAAC;GACxD,UAAU,yBAAyB,EACjC,WAAW,KACb,CAAC;EACH;;CAOF,MAAM,EAAE,YAAY,OAAO;CAE3B,KAAK,MAAM,aAAa,OAAO,MAAM,cAAc;EACjD,MAAM,qBAAiBC,gBAAK,SAAS,gBAAgB,WAAW;EAEhE,QAAIC,oBAAW,cAAc,GAAG;GAC9B,MAAM,UAAU,UAAMC,2BAAS,gBAAgB,OAAO;GACtD,UAAMC,4BAAU,gBAAgB,OAAO;EACzC;CACF;CAIA,WAAW,MAAM,UAAU,OAAO,WAAW,CAAC,GAAG;EAC/C,MAAM,EAAE,sBAAsB,uBAAuB;EAErD,MAAM,OAAO,aAAa;GACxB,cAAc;IACZ;IACA;GACF;GACA,eAAe;EACjB,CAAC;CACH;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handleContentDeclarationFileMoved.cjs","names":["formatPath","loadLocalDictionaries","cleanRemovedContentDeclaration","handleContentDeclarationFileChange"],"sources":["../../src/handleContentDeclarationFileMoved.ts"],"sourcesContent":["import { getAppLogger } from '@intlayer/config/logger';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { cleanRemovedContentDeclaration } from './cleanRemovedContentDeclaration';\nimport { handleContentDeclarationFileChange } from './handleContentDeclarationFileChange';\nimport { loadLocalDictionaries } from './loadDictionaries/loadLocalDictionaries';\nimport { formatPath } from './utils/formatter';\n\nexport const handleContentDeclarationFileMoved = async (\n oldFilePath: string,\n newFilePath: string,\n config: IntlayerConfig\n) => {\n const appLogger = getAppLogger(config);\n\n appLogger(\n `File moved from ${formatPath(oldFilePath)} to ${formatPath(newFilePath)}`,\n {\n isVerbose: true,\n }\n );\n\n let keysToKeep: string[] = [];\n\n try {\n // Pre-load the new file to identify the keys inside it.\n // We need to pass these keys to the cleaner so it knows NOT to completely delete\n // the dictionary artifacts (types/json) if the key has simply moved to a new file.\n const newLocaleDictionaries = await loadLocalDictionaries(\n newFilePath,\n config\n );\n keysToKeep = newLocaleDictionaries.map((d) => d.key);\n } catch {\n appLogger(\n `Error parsing new file during move operation: ${formatPath(newFilePath)}`,\n {\n isVerbose: true,\n }\n );\n // Proceed with empty keysToKeep; this will result in the old dictionary being cleaned up\n // entirely, and the new one being re-created in the next step.\n }\n\n // Clean up the artifacts associated with the old file path.\n // By passing 'keysToKeep', we instruct the cleaner to remove the specific file association\n // (filePath reference) from the dictionaries, but preserve the dictionary Entry Point and Types\n // if the key is still present in the new file.\n await cleanRemovedContentDeclaration(oldFilePath, keysToKeep, config);\n\n // Process the new file.\n // This will add the new file association, rebuild the dictionary JSONs with the new path,\n // and ensure types and module augmentation are up to date.\n await handleContentDeclarationFileChange(newFilePath, config);\n};\n"],"mappings":";;;;;;;;AAOA,MAAa,oCAAoC,OAC/C,aACA,aACA,WACG;CACH,MAAM,
|
|
1
|
+
{"version":3,"file":"handleContentDeclarationFileMoved.cjs","names":["getAppLogger","formatPath","loadLocalDictionaries","cleanRemovedContentDeclaration","handleContentDeclarationFileChange"],"sources":["../../src/handleContentDeclarationFileMoved.ts"],"sourcesContent":["import { getAppLogger } from '@intlayer/config/logger';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { cleanRemovedContentDeclaration } from './cleanRemovedContentDeclaration';\nimport { handleContentDeclarationFileChange } from './handleContentDeclarationFileChange';\nimport { loadLocalDictionaries } from './loadDictionaries/loadLocalDictionaries';\nimport { formatPath } from './utils/formatter';\n\nexport const handleContentDeclarationFileMoved = async (\n oldFilePath: string,\n newFilePath: string,\n config: IntlayerConfig\n) => {\n const appLogger = getAppLogger(config);\n\n appLogger(\n `File moved from ${formatPath(oldFilePath)} to ${formatPath(newFilePath)}`,\n {\n isVerbose: true,\n }\n );\n\n let keysToKeep: string[] = [];\n\n try {\n // Pre-load the new file to identify the keys inside it.\n // We need to pass these keys to the cleaner so it knows NOT to completely delete\n // the dictionary artifacts (types/json) if the key has simply moved to a new file.\n const newLocaleDictionaries = await loadLocalDictionaries(\n newFilePath,\n config\n );\n keysToKeep = newLocaleDictionaries.map((d) => d.key);\n } catch {\n appLogger(\n `Error parsing new file during move operation: ${formatPath(newFilePath)}`,\n {\n isVerbose: true,\n }\n );\n // Proceed with empty keysToKeep; this will result in the old dictionary being cleaned up\n // entirely, and the new one being re-created in the next step.\n }\n\n // Clean up the artifacts associated with the old file path.\n // By passing 'keysToKeep', we instruct the cleaner to remove the specific file association\n // (filePath reference) from the dictionaries, but preserve the dictionary Entry Point and Types\n // if the key is still present in the new file.\n await cleanRemovedContentDeclaration(oldFilePath, keysToKeep, config);\n\n // Process the new file.\n // This will add the new file association, rebuild the dictionary JSONs with the new path,\n // and ensure types and module augmentation are up to date.\n await handleContentDeclarationFileChange(newFilePath, config);\n};\n"],"mappings":";;;;;;;;AAOA,MAAa,oCAAoC,OAC/C,aACA,aACA,WACG;CACH,MAAM,gBAAYA,sCAAa,MAAM;CAErC,UACE,mBAAmBC,mCAAW,WAAW,EAAE,MAAMA,mCAAW,WAAW,KACvE,EACE,WAAW,KACb,CACF;CAEA,IAAI,aAAuB,CAAC;CAE5B,IAAI;EAQF,cAAa,MAJuBC,qEAClC,aACA,MACF,EACkC,CAAC,KAAK,MAAM,EAAE,GAAG;CACrD,QAAQ;EACN,UACE,iDAAiDD,mCAAW,WAAW,KACvE,EACE,WAAW,KACb,CACF;CAGF;CAMA,MAAME,sEAA+B,aAAa,YAAY,MAAM;CAKpE,MAAMC,8EAAmC,aAAa,MAAM;AAC9D"}
|
|
@@ -13,10 +13,12 @@ let _intlayer_config_logger = require("@intlayer/config/logger");
|
|
|
13
13
|
const handleUnlinkedContentDeclarationFile = async (filePath, config) => {
|
|
14
14
|
const appLogger = (0, _intlayer_config_logger.getAppLogger)(config);
|
|
15
15
|
appLogger(`Unlinked detected: ${require_utils_formatter.formatPath(filePath)}`, { isVerbose: true });
|
|
16
|
-
const
|
|
16
|
+
const existingFiles = (await require_listDictionariesPath.listDictionaries(config)).filter((file) => (0, node_path.normalize)(file) !== (0, node_path.normalize)(filePath));
|
|
17
|
+
const localeDictionaries = await require_loadDictionaries_loadLocalDictionaries.loadLocalDictionaries(existingFiles, config);
|
|
17
18
|
await require_cleanRemovedContentDeclaration.cleanRemovedContentDeclaration(filePath, [], config);
|
|
18
19
|
const dictionariesOutput = await require_buildIntlayerDictionary_buildIntlayerDictionary.buildDictionary(localeDictionaries, config);
|
|
19
|
-
|
|
20
|
+
const dictionariesToBuild = Object.values(dictionariesOutput?.mergedDictionaries ?? {}).map((dictionary) => dictionary.dictionary);
|
|
21
|
+
await require_createType_createType.createTypes(dictionariesToBuild, config);
|
|
20
22
|
appLogger("Dictionaries rebuilt", { isVerbose: true });
|
|
21
23
|
await require_createType_createModuleAugmentation.createModuleAugmentation(config);
|
|
22
24
|
appLogger("Module augmentation built", { isVerbose: true });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handleUnlinkedContentDeclarationFile.cjs","names":["
|
|
1
|
+
{"version":3,"file":"handleUnlinkedContentDeclarationFile.cjs","names":["getAppLogger","formatPath","listDictionaries","normalize","loadLocalDictionaries","cleanRemovedContentDeclaration","buildDictionary","createTypes","createModuleAugmentation"],"sources":["../../src/handleUnlinkedContentDeclarationFile.ts"],"sourcesContent":["import { normalize } from 'node:path';\nimport { getAppLogger } from '@intlayer/config/logger';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { buildDictionary } from './buildIntlayerDictionary/buildIntlayerDictionary';\nimport { cleanRemovedContentDeclaration } from './cleanRemovedContentDeclaration';\nimport { createTypes } from './createType';\nimport { createModuleAugmentation } from './createType/createModuleAugmentation';\nimport { listDictionaries } from './listDictionariesPath';\nimport { loadLocalDictionaries } from './loadDictionaries/loadLocalDictionaries';\nimport { formatPath } from './utils/formatter';\n\nexport const handleUnlinkedContentDeclarationFile = async (\n filePath: string,\n config: IntlayerConfig\n) => {\n const appLogger = getAppLogger(config);\n\n // Process the file with the functionToRun\n appLogger(`Unlinked detected: ${formatPath(filePath)}`, {\n isVerbose: true,\n });\n\n const files: string[] = await listDictionaries(config);\n\n const existingFiles = files.filter(\n (file) => normalize(file) !== normalize(filePath)\n );\n\n const localeDictionaries = await loadLocalDictionaries(existingFiles, config);\n\n await cleanRemovedContentDeclaration(filePath, [], config);\n\n const dictionariesOutput = await buildDictionary(localeDictionaries, config);\n\n const dictionariesToBuild = Object.values(\n dictionariesOutput?.mergedDictionaries ?? {}\n ).map((dictionary) => dictionary.dictionary);\n\n await createTypes(dictionariesToBuild, config);\n\n appLogger('Dictionaries rebuilt', {\n isVerbose: true,\n });\n\n await createModuleAugmentation(config);\n\n appLogger('Module augmentation built', {\n isVerbose: true,\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 config.plugins ?? []) {\n const { unmergedDictionaries, mergedDictionaries } = dictionariesOutput;\n\n await plugin.afterBuild?.({\n dictionaries: {\n unmergedDictionaries,\n mergedDictionaries,\n },\n configuration: config,\n });\n }\n};\n"],"mappings":";;;;;;;;;;;;AAWA,MAAa,uCAAuC,OAClD,UACA,WACG;CACH,MAAM,gBAAYA,sCAAa,MAAM;CAGrC,UAAU,sBAAsBC,mCAAW,QAAQ,KAAK,EACtD,WAAW,KACb,CAAC;CAID,MAAM,iBAAgB,MAFQC,8CAAiB,MAAM,EAE1B,CAAC,QACzB,aAASC,qBAAU,IAAI,UAAMA,qBAAU,QAAQ,CAClD;CAEA,MAAM,qBAAqB,MAAMC,qEAAsB,eAAe,MAAM;CAE5E,MAAMC,sEAA+B,UAAU,CAAC,GAAG,MAAM;CAEzD,MAAM,qBAAqB,MAAMC,wEAAgB,oBAAoB,MAAM;CAE3E,MAAM,sBAAsB,OAAO,OACjC,oBAAoB,sBAAsB,CAAC,CAC7C,CAAC,CAAC,KAAK,eAAe,WAAW,UAAU;CAE3C,MAAMC,0CAAY,qBAAqB,MAAM;CAE7C,UAAU,wBAAwB,EAChC,WAAW,KACb,CAAC;CAED,MAAMC,qEAAyB,MAAM;CAErC,UAAU,6BAA6B,EACrC,WAAW,KACb,CAAC;CAID,WAAW,MAAM,UAAU,OAAO,WAAW,CAAC,GAAG;EAC/C,MAAM,EAAE,sBAAsB,uBAAuB;EAErD,MAAM,OAAO,aAAa;GACxB,cAAc;IACZ;IACA;GACF;GACA,eAAe;EACjB,CAAC;CACH;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cms.cjs","names":["exists","readFileFromRoot","writeFileToRoot","v","ANSIColors","x","enableIntlayerEditorConfig","setIntlayerConfigRoutingMode","setIntlayerConfigCompilerOutput"],"sources":["../../../src/init/cms.ts"],"sourcesContent":["import * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, colorizePath, logger, v, x } from '@intlayer/config/logger';\nimport fg from 'fast-glob';\nimport {\n enableIntlayerEditorConfig,\n type RoutingMode,\n setIntlayerConfigCompilerOutput,\n setIntlayerConfigRoutingMode,\n} from './utils/configManipulation';\nimport { exists, readFileFromRoot, writeFileToRoot } from './utils/fileSystem';\n\n/**\n * Intlayer configuration file candidates, ordered by the precedence used when\n * resolving which file to mutate.\n */\nexport const INTLAYER_CONFIG_FILE_CANDIDATES = [\n 'intlayer.config.ts',\n 'intlayer.config.mjs',\n 'intlayer.config.js',\n 'intlayer.config.cjs',\n 'intlayer.config.json',\n] as const;\n\n/**\n * Environment files searched (most-preferred first) when persisting the CMS\n * credentials. The first existing match is reused; otherwise a `.env` file is\n * created at the project root.\n */\nconst ENV_FILE_CANDIDATES = ['.env', '.env.local', '.env.development'] as const;\n\n/** Credentials returned by the CMS login flow (access key pair). */\nexport type CmsCredentials = {\n clientId: string;\n clientSecret: string;\n};\n\n/**\n * Resolves the first existing Intlayer configuration file at the project root,\n * or `undefined` when none is present.\n */\nconst findIntlayerConfigFile = async (\n rootDir: string\n): Promise<(typeof INTLAYER_CONFIG_FILE_CANDIDATES)[number] | undefined> => {\n for (const candidate of INTLAYER_CONFIG_FILE_CANDIDATES) {\n if (await exists(rootDir, candidate)) {\n return candidate;\n }\n }\n\n return undefined;\n};\n\n/**\n * Locates the environment file the CMS credentials should be written to. Uses\n * `fast-glob` to detect an existing dotenv file at the project root, preferring\n * a plain `.env`; falls back to `.env` (to be created) when none exist.\n */\nexport const findEnvFile = async (rootDir: string): Promise<string> => {\n const matches = await fg([...ENV_FILE_CANDIDATES], {\n cwd: rootDir,\n dot: true,\n deep: 1,\n onlyFiles: true,\n });\n\n return (\n ENV_FILE_CANDIDATES.find((candidate) => matches.includes(candidate)) ??\n '.env'\n );\n};\n\n/**\n * Inserts or updates a `KEY=value` line in dotenv file content. An existing\n * assignment of the same key is replaced in place; otherwise the line is\n * appended with a trailing newline.\n */\nconst upsertEnvVariable = (\n content: string,\n key: string,\n value: string\n): string => {\n const line = `${key}=${value}`;\n const keyMatcher = new RegExp(`^${key}=.*$`, 'm');\n\n if (keyMatcher.test(content)) {\n return content.replace(keyMatcher, line);\n }\n\n const needsLeadingNewline = content.length > 0 && !content.endsWith('\\n');\n\n return `${content}${needsLeadingNewline ? '\\n' : ''}${line}\\n`;\n};\n\n/**\n * Persists the CMS access-key credentials to the project's environment file as\n * `INTLAYER_CLIENT_ID` / `INTLAYER_CLIENT_SECRET`. The target file is detected\n * with {@link findEnvFile} and created when missing. Returns the relative path\n * of the file that was written.\n */\nexport const writeCmsCredentialsToEnv = async (\n rootDir: string,\n { clientId, clientSecret }: CmsCredentials\n): Promise<string> => {\n const envFile = await findEnvFile(rootDir);\n\n let content = '';\n if (await exists(rootDir, envFile)) {\n content = await readFileFromRoot(rootDir, envFile);\n }\n\n let updatedContent = content;\n updatedContent = upsertEnvVariable(\n updatedContent,\n 'INTLAYER_CLIENT_ID',\n clientId\n );\n updatedContent = upsertEnvVariable(\n updatedContent,\n 'INTLAYER_CLIENT_SECRET',\n clientSecret\n );\n\n await writeFileToRoot(rootDir, envFile, updatedContent);\n logger(\n `${v} Saved Intlayer CMS credentials to ${colorizePath(envFile)} (${colorize('INTLAYER_CLIENT_ID', ANSIColors.GREY_LIGHT)}, ${colorize('INTLAYER_CLIENT_SECRET', ANSIColors.GREY_LIGHT)})`\n );\n\n return envFile;\n};\n\n/**\n * Enables the Intlayer visual editor in the project's configuration file:\n * flips `editor.enabled` to `true` and wires `clientId` / `clientSecret` to the\n * matching environment variables. JSON configs cannot reference `process.env`,\n * so they are skipped with a warning. Returns the config file that was updated,\n * or `undefined` when no editable config was found.\n */\nexport const enableEditorInConfig = async (\n rootDir: string\n): Promise<string | undefined> => {\n const configFile = await findIntlayerConfigFile(rootDir);\n\n if (!configFile) {\n logger(\n `${x} Could not find an Intlayer configuration file to enable the editor.`,\n { level: 'warn' }\n );\n return undefined;\n }\n\n const extension = configFile.split('.').pop()!;\n\n if (extension === 'json') {\n logger(\n `${x} ${colorizePath(configFile)} is a JSON config and cannot reference environment variables. Enable the editor and set clientId/clientSecret manually.`,\n { level: 'warn' }\n );\n return undefined;\n }\n\n const content = await readFileFromRoot(rootDir, configFile);\n const updatedContent = enableIntlayerEditorConfig(content);\n\n if (updatedContent !== content) {\n await writeFileToRoot(rootDir, configFile, updatedContent);\n logger(`${v} Enabled the Intlayer editor in ${colorizePath(configFile)}`);\n } else {\n logger(\n `${v} ${colorizePath(configFile)} already has the Intlayer editor enabled`\n );\n }\n\n return configFile;\n};\n\n/**\n * Sets `routing.mode` in the project's Intlayer configuration file. Returns the\n * config file that was updated, or `undefined` when none was found.\n */\nexport const setRoutingModeInConfig = async (\n rootDir: string,\n mode: RoutingMode\n): Promise<string | undefined> => {\n const configFile = await findIntlayerConfigFile(rootDir);\n\n if (!configFile) return undefined;\n\n const extension = configFile.split('.').pop()!;\n const content = await readFileFromRoot(rootDir, configFile);\n const updatedContent = setIntlayerConfigRoutingMode(content, extension, mode);\n\n if (updatedContent !== content) {\n await writeFileToRoot(rootDir, configFile, updatedContent);\n logger(\n `${v} Set ${colorize(`routing.mode = '${mode}'`, ANSIColors.GREY_LIGHT)} in ${colorizePath(configFile)}`\n );\n }\n\n return configFile;\n};\n\n/**\n * Sets `compiler.output` in the project's Intlayer configuration file to the\n * given `{{variable}}` path template. Returns the config file that was updated,\n * or `undefined` when none was found.\n */\nexport const setCompilerOutputInConfig = async (\n rootDir: string,\n outputTemplate: string\n): Promise<string | undefined> => {\n const configFile = await findIntlayerConfigFile(rootDir);\n\n if (!configFile) return undefined;\n\n const extension = configFile.split('.').pop()!;\n const content = await readFileFromRoot(rootDir, configFile);\n const updatedContent = setIntlayerConfigCompilerOutput(\n content,\n extension,\n outputTemplate\n );\n\n if (updatedContent !== content) {\n await writeFileToRoot(rootDir, configFile, updatedContent);\n logger(\n `${v} Set ${colorize(`compiler.output = '${outputTemplate}'`, ANSIColors.GREY_LIGHT)} in ${colorizePath(configFile)}`\n );\n }\n\n return configFile;\n};\n\n/**\n * Completes the CMS setup once credentials are received from the login flow:\n * writes them to the environment file and enables the editor in the Intlayer\n * configuration file.\n */\nexport const setupCmsCredentials = async (\n rootDir: string,\n credentials: CmsCredentials\n): Promise<void> => {\n await writeCmsCredentialsToEnv(rootDir, credentials);\n await enableEditorInConfig(rootDir);\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cms.cjs","names":["exists","fg","readFileFromRoot","writeFileToRoot","v","colorizePath","colorize","ANSIColors","x","enableIntlayerEditorConfig","setIntlayerConfigRoutingMode","setIntlayerConfigCompilerOutput"],"sources":["../../../src/init/cms.ts"],"sourcesContent":["import * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, colorizePath, logger, v, x } from '@intlayer/config/logger';\nimport fg from 'fast-glob';\nimport {\n enableIntlayerEditorConfig,\n type RoutingMode,\n setIntlayerConfigCompilerOutput,\n setIntlayerConfigRoutingMode,\n} from './utils/configManipulation';\nimport { exists, readFileFromRoot, writeFileToRoot } from './utils/fileSystem';\n\n/**\n * Intlayer configuration file candidates, ordered by the precedence used when\n * resolving which file to mutate.\n */\nexport const INTLAYER_CONFIG_FILE_CANDIDATES = [\n 'intlayer.config.ts',\n 'intlayer.config.mjs',\n 'intlayer.config.js',\n 'intlayer.config.cjs',\n 'intlayer.config.json',\n] as const;\n\n/**\n * Environment files searched (most-preferred first) when persisting the CMS\n * credentials. The first existing match is reused; otherwise a `.env` file is\n * created at the project root.\n */\nconst ENV_FILE_CANDIDATES = ['.env', '.env.local', '.env.development'] as const;\n\n/** Credentials returned by the CMS login flow (access key pair). */\nexport type CmsCredentials = {\n clientId: string;\n clientSecret: string;\n};\n\n/**\n * Resolves the first existing Intlayer configuration file at the project root,\n * or `undefined` when none is present.\n */\nconst findIntlayerConfigFile = async (\n rootDir: string\n): Promise<(typeof INTLAYER_CONFIG_FILE_CANDIDATES)[number] | undefined> => {\n for (const candidate of INTLAYER_CONFIG_FILE_CANDIDATES) {\n if (await exists(rootDir, candidate)) {\n return candidate;\n }\n }\n\n return undefined;\n};\n\n/**\n * Locates the environment file the CMS credentials should be written to. Uses\n * `fast-glob` to detect an existing dotenv file at the project root, preferring\n * a plain `.env`; falls back to `.env` (to be created) when none exist.\n */\nexport const findEnvFile = async (rootDir: string): Promise<string> => {\n const matches = await fg([...ENV_FILE_CANDIDATES], {\n cwd: rootDir,\n dot: true,\n deep: 1,\n onlyFiles: true,\n });\n\n return (\n ENV_FILE_CANDIDATES.find((candidate) => matches.includes(candidate)) ??\n '.env'\n );\n};\n\n/**\n * Inserts or updates a `KEY=value` line in dotenv file content. An existing\n * assignment of the same key is replaced in place; otherwise the line is\n * appended with a trailing newline.\n */\nconst upsertEnvVariable = (\n content: string,\n key: string,\n value: string\n): string => {\n const line = `${key}=${value}`;\n const keyMatcher = new RegExp(`^${key}=.*$`, 'm');\n\n if (keyMatcher.test(content)) {\n return content.replace(keyMatcher, line);\n }\n\n const needsLeadingNewline = content.length > 0 && !content.endsWith('\\n');\n\n return `${content}${needsLeadingNewline ? '\\n' : ''}${line}\\n`;\n};\n\n/**\n * Persists the CMS access-key credentials to the project's environment file as\n * `INTLAYER_CLIENT_ID` / `INTLAYER_CLIENT_SECRET`. The target file is detected\n * with {@link findEnvFile} and created when missing. Returns the relative path\n * of the file that was written.\n */\nexport const writeCmsCredentialsToEnv = async (\n rootDir: string,\n { clientId, clientSecret }: CmsCredentials\n): Promise<string> => {\n const envFile = await findEnvFile(rootDir);\n\n let content = '';\n if (await exists(rootDir, envFile)) {\n content = await readFileFromRoot(rootDir, envFile);\n }\n\n let updatedContent = content;\n updatedContent = upsertEnvVariable(\n updatedContent,\n 'INTLAYER_CLIENT_ID',\n clientId\n );\n updatedContent = upsertEnvVariable(\n updatedContent,\n 'INTLAYER_CLIENT_SECRET',\n clientSecret\n );\n\n await writeFileToRoot(rootDir, envFile, updatedContent);\n logger(\n `${v} Saved Intlayer CMS credentials to ${colorizePath(envFile)} (${colorize('INTLAYER_CLIENT_ID', ANSIColors.GREY_LIGHT)}, ${colorize('INTLAYER_CLIENT_SECRET', ANSIColors.GREY_LIGHT)})`\n );\n\n return envFile;\n};\n\n/**\n * Enables the Intlayer visual editor in the project's configuration file:\n * flips `editor.enabled` to `true` and wires `clientId` / `clientSecret` to the\n * matching environment variables. JSON configs cannot reference `process.env`,\n * so they are skipped with a warning. Returns the config file that was updated,\n * or `undefined` when no editable config was found.\n */\nexport const enableEditorInConfig = async (\n rootDir: string\n): Promise<string | undefined> => {\n const configFile = await findIntlayerConfigFile(rootDir);\n\n if (!configFile) {\n logger(\n `${x} Could not find an Intlayer configuration file to enable the editor.`,\n { level: 'warn' }\n );\n return undefined;\n }\n\n const extension = configFile.split('.').pop()!;\n\n if (extension === 'json') {\n logger(\n `${x} ${colorizePath(configFile)} is a JSON config and cannot reference environment variables. Enable the editor and set clientId/clientSecret manually.`,\n { level: 'warn' }\n );\n return undefined;\n }\n\n const content = await readFileFromRoot(rootDir, configFile);\n const updatedContent = enableIntlayerEditorConfig(content);\n\n if (updatedContent !== content) {\n await writeFileToRoot(rootDir, configFile, updatedContent);\n logger(`${v} Enabled the Intlayer editor in ${colorizePath(configFile)}`);\n } else {\n logger(\n `${v} ${colorizePath(configFile)} already has the Intlayer editor enabled`\n );\n }\n\n return configFile;\n};\n\n/**\n * Sets `routing.mode` in the project's Intlayer configuration file. Returns the\n * config file that was updated, or `undefined` when none was found.\n */\nexport const setRoutingModeInConfig = async (\n rootDir: string,\n mode: RoutingMode\n): Promise<string | undefined> => {\n const configFile = await findIntlayerConfigFile(rootDir);\n\n if (!configFile) return undefined;\n\n const extension = configFile.split('.').pop()!;\n const content = await readFileFromRoot(rootDir, configFile);\n const updatedContent = setIntlayerConfigRoutingMode(content, extension, mode);\n\n if (updatedContent !== content) {\n await writeFileToRoot(rootDir, configFile, updatedContent);\n logger(\n `${v} Set ${colorize(`routing.mode = '${mode}'`, ANSIColors.GREY_LIGHT)} in ${colorizePath(configFile)}`\n );\n }\n\n return configFile;\n};\n\n/**\n * Sets `compiler.output` in the project's Intlayer configuration file to the\n * given `{{variable}}` path template. Returns the config file that was updated,\n * or `undefined` when none was found.\n */\nexport const setCompilerOutputInConfig = async (\n rootDir: string,\n outputTemplate: string\n): Promise<string | undefined> => {\n const configFile = await findIntlayerConfigFile(rootDir);\n\n if (!configFile) return undefined;\n\n const extension = configFile.split('.').pop()!;\n const content = await readFileFromRoot(rootDir, configFile);\n const updatedContent = setIntlayerConfigCompilerOutput(\n content,\n extension,\n outputTemplate\n );\n\n if (updatedContent !== content) {\n await writeFileToRoot(rootDir, configFile, updatedContent);\n logger(\n `${v} Set ${colorize(`compiler.output = '${outputTemplate}'`, ANSIColors.GREY_LIGHT)} in ${colorizePath(configFile)}`\n );\n }\n\n return configFile;\n};\n\n/**\n * Completes the CMS setup once credentials are received from the login flow:\n * writes them to the environment file and enables the editor in the Intlayer\n * configuration file.\n */\nexport const setupCmsCredentials = async (\n rootDir: string,\n credentials: CmsCredentials\n): Promise<void> => {\n await writeCmsCredentialsToEnv(rootDir, credentials);\n await enableEditorInConfig(rootDir);\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detect.cjs","names":["exists"],"sources":["../../../../../src/init/frameworkSetup/nextAppRouter/detect.ts"],"sourcesContent":["import { join } from 'node:path';\nimport { exists } from '../../utils/fileSystem';\n\n/** Script extensions probed when locating an App Router convention file. */\nconst SCRIPT_EXTENSIONS = ['tsx', 'jsx', 'ts', 'js'] as const;\n\n/** Location of the Next.js App Router directory within the project. */\nexport type NextAppDirInfo = {\n /** App Router directory relative to the root, e.g. `src/app` or `app`. */\n appDir: string;\n /** `src` when the project uses a `src/` directory, otherwise `''`. */\n srcDir: string;\n};\n\n/**\n * Detects the Next.js App Router directory, preferring `src/app` over `app`.\n * Returns `null` when neither exists (e.g. a Pages Router project).\n */\nexport const detectNextAppDir = async (\n rootDir: string\n): Promise<NextAppDirInfo | null> => {\n if (await exists(rootDir, join('src', 'app'))) {\n return { appDir: join('src', 'app'), srcDir: 'src' };\n }\n if (await exists(rootDir, 'app')) {\n return { appDir: 'app', srcDir: '' };\n }\n return null;\n};\n\n/**\n * Finds an existing App Router convention file (e.g. `layout`, `page`) within\n * `dir`, trying each known script extension. Returns the matching relative path\n * or `null`.\n */\nexport const findAppFile = async (\n rootDir: string,\n dir: string,\n baseName: string\n): Promise<string | null> => {\n for (const extension of SCRIPT_EXTENSIONS) {\n const candidate = join(dir, `${baseName}.${extension}`);\n if (await exists(rootDir, candidate)) {\n return candidate;\n }\n }\n return null;\n};\n\nexport { isVersionAtLeast } from '../../utils/version';\n"],"mappings":";;;;;;;AAIA,MAAM,oBAAoB;CAAC;CAAO;CAAO;CAAM;AAAI;;;;;AAcnD,MAAa,mBAAmB,OAC9B,YACmC;CACnC,IAAI,MAAMA,qCAAO,
|
|
1
|
+
{"version":3,"file":"detect.cjs","names":["exists","join"],"sources":["../../../../../src/init/frameworkSetup/nextAppRouter/detect.ts"],"sourcesContent":["import { join } from 'node:path';\nimport { exists } from '../../utils/fileSystem';\n\n/** Script extensions probed when locating an App Router convention file. */\nconst SCRIPT_EXTENSIONS = ['tsx', 'jsx', 'ts', 'js'] as const;\n\n/** Location of the Next.js App Router directory within the project. */\nexport type NextAppDirInfo = {\n /** App Router directory relative to the root, e.g. `src/app` or `app`. */\n appDir: string;\n /** `src` when the project uses a `src/` directory, otherwise `''`. */\n srcDir: string;\n};\n\n/**\n * Detects the Next.js App Router directory, preferring `src/app` over `app`.\n * Returns `null` when neither exists (e.g. a Pages Router project).\n */\nexport const detectNextAppDir = async (\n rootDir: string\n): Promise<NextAppDirInfo | null> => {\n if (await exists(rootDir, join('src', 'app'))) {\n return { appDir: join('src', 'app'), srcDir: 'src' };\n }\n if (await exists(rootDir, 'app')) {\n return { appDir: 'app', srcDir: '' };\n }\n return null;\n};\n\n/**\n * Finds an existing App Router convention file (e.g. `layout`, `page`) within\n * `dir`, trying each known script extension. Returns the matching relative path\n * or `null`.\n */\nexport const findAppFile = async (\n rootDir: string,\n dir: string,\n baseName: string\n): Promise<string | null> => {\n for (const extension of SCRIPT_EXTENSIONS) {\n const candidate = join(dir, `${baseName}.${extension}`);\n if (await exists(rootDir, candidate)) {\n return candidate;\n }\n }\n return null;\n};\n\nexport { isVersionAtLeast } from '../../utils/version';\n"],"mappings":";;;;;;;AAIA,MAAM,oBAAoB;CAAC;CAAO;CAAO;CAAM;AAAI;;;;;AAcnD,MAAa,mBAAmB,OAC9B,YACmC;CACnC,IAAI,MAAMA,qCAAO,aAASC,gBAAK,OAAO,KAAK,CAAC,GAC1C,OAAO;EAAE,YAAQA,gBAAK,OAAO,KAAK;EAAG,QAAQ;CAAM;CAErD,IAAI,MAAMD,qCAAO,SAAS,KAAK,GAC7B,OAAO;EAAE,QAAQ;EAAO,QAAQ;CAAG;CAErC,OAAO;AACT;;;;;;AAOA,MAAa,cAAc,OACzB,SACA,KACA,aAC2B;CAC3B,KAAK,MAAM,aAAa,mBAAmB;EACzC,MAAM,gBAAYC,gBAAK,KAAK,GAAG,SAAS,GAAG,WAAW;EACtD,IAAI,MAAMD,qCAAO,SAAS,SAAS,GACjC,OAAO;CAEX;CACA,OAAO;AACT"}
|
|
@@ -47,9 +47,7 @@ const logTransformOutcome = (filePath, result) => {
|
|
|
47
47
|
case "skipped-client":
|
|
48
48
|
(0, _intlayer_config_logger.logger)(`${_intlayer_config_logger.x} ${(0, _intlayer_config_logger.colorizePath)(filePath)} is a client component — add the Intlayer provider manually.`, { level: "warn" });
|
|
49
49
|
break;
|
|
50
|
-
case "skipped":
|
|
51
|
-
(0, _intlayer_config_logger.logger)(`${_intlayer_config_logger.x} Could not safely add the Intlayer provider to ${(0, _intlayer_config_logger.colorizePath)(filePath)} — please wire it manually.`, { level: "warn" });
|
|
52
|
-
break;
|
|
50
|
+
case "skipped": (0, _intlayer_config_logger.logger)(`${_intlayer_config_logger.x} Could not safely add the Intlayer provider to ${(0, _intlayer_config_logger.colorizePath)(filePath)} — please wire it manually.`, { level: "warn" });
|
|
53
51
|
}
|
|
54
52
|
};
|
|
55
53
|
/** Transforms an existing file in place, writing it back only when changed. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["exists","v","PROXY_TEMPLATE","MIDDLEWARE_TEMPLATE","ensureDirectory","writeFileToRoot","x","readFileFromRoot","detectNextAppDir","isVersionAtLeast","ANSIColors","restructureAppIntoLocale","ROOT_LAYOUT_TEMPLATE_TS","ROOT_LAYOUT_TEMPLATE_JS","findAppFile","wrapLayoutWithProvider","LOCALE_LAYOUT_TEMPLATE_TS","LOCALE_LAYOUT_TEMPLATE_JS","wrapPageWithProvider","LOCALE_PAGE_TEMPLATE_TS","LOCALE_PAGE_TEMPLATE_JS"],"sources":["../../../../../src/init/frameworkSetup/nextAppRouter/index.ts"],"sourcesContent":["import { join } from 'node:path';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, colorizePath, logger, v, x } from '@intlayer/config/logger';\nimport {\n ensureDirectory,\n exists,\n readFileFromRoot,\n writeFileToRoot,\n} from '../../utils/fileSystem';\nimport type { FrameworkAdapter, FrameworkSetupContext } from '../types';\nimport { detectNextAppDir, findAppFile, isVersionAtLeast } from './detect';\nimport { restructureAppIntoLocale } from './restructure';\nimport {\n LOCALE_LAYOUT_TEMPLATE_JS,\n LOCALE_LAYOUT_TEMPLATE_TS,\n LOCALE_PAGE_TEMPLATE_JS,\n LOCALE_PAGE_TEMPLATE_TS,\n MIDDLEWARE_TEMPLATE,\n PROXY_TEMPLATE,\n ROOT_LAYOUT_TEMPLATE_JS,\n ROOT_LAYOUT_TEMPLATE_TS,\n} from './templates';\nimport {\n type TransformResult,\n wrapLayoutWithProvider,\n wrapPageWithProvider,\n} from './transforms';\n\n/**\n * Creates the locale-detection proxy (Next >= 16) or middleware (Next < 16),\n * but only when neither already exists, so a user's custom file is never\n * touched. Placed next to the app directory's parent (`src/` when present).\n */\nconst ensureProxyOrMiddleware = async (\n rootDir: string,\n srcDir: string,\n isNext16: boolean\n): Promise<void> => {\n const baseDir = srcDir || '.';\n const proxyCandidates = [\n 'proxy.ts',\n 'proxy.js',\n 'middleware.ts',\n 'middleware.js',\n ];\n\n for (const candidate of proxyCandidates) {\n if (await exists(rootDir, join(baseDir, candidate))) {\n logger(\n `${v} ${colorizePath(join(baseDir, candidate))} already exists, leaving it untouched`\n );\n return;\n }\n }\n\n const fileName = isNext16 ? 'proxy.ts' : 'middleware.ts';\n const content = isNext16 ? PROXY_TEMPLATE : MIDDLEWARE_TEMPLATE;\n const targetPath = join(baseDir, fileName);\n\n if (srcDir) await ensureDirectory(rootDir, srcDir);\n await writeFileToRoot(rootDir, targetPath, content);\n logger(`${v} Created ${colorizePath(targetPath)} for locale detection`);\n};\n\n/** Logs the outcome of a layout/page provider-wrap transform. */\nconst logTransformOutcome = (\n filePath: string,\n result: TransformResult\n): void => {\n switch (result.status) {\n case 'wrapped':\n logger(`${v} Added Intlayer provider to ${colorizePath(filePath)}`);\n break;\n case 'already':\n logger(\n `${v} ${colorizePath(filePath)} already wraps the Intlayer provider`\n );\n break;\n case 'skipped-client':\n logger(\n `${x} ${colorizePath(filePath)} is a client component — add the Intlayer provider manually.`,\n { level: 'warn' }\n );\n break;\n case 'skipped':\n logger(\n `${x} Could not safely add the Intlayer provider to ${colorizePath(filePath)} — please wire it manually.`,\n { level: 'warn' }\n );\n break;\n }\n};\n\n/** Transforms an existing file in place, writing it back only when changed. */\nconst transformExistingFile = async (\n rootDir: string,\n filePath: string,\n transform: (code: string) => TransformResult\n): Promise<void> => {\n const code = await readFileFromRoot(rootDir, filePath);\n const result = transform(code);\n if (result.status === 'wrapped') {\n await writeFileToRoot(rootDir, filePath, result.code);\n }\n logTransformOutcome(filePath, result);\n};\n\n/** Creates a file from a template only when it does not already exist. */\nconst createIfMissing = async (\n rootDir: string,\n filePath: string,\n content: string,\n description: string\n): Promise<boolean> => {\n if (await exists(rootDir, filePath)) return false;\n await writeFileToRoot(rootDir, filePath, content);\n logger(`${v} Created ${description} ${colorizePath(filePath)}`);\n return true;\n};\n\n/**\n * Next.js App Router adapter. Scaffolds the locale proxy/middleware, restructures\n * the app into a `[locale]` segment, and wraps the layout/page with the Intlayer\n * providers — all idempotently and without overwriting recognizable user code.\n */\nexport const nextAppRouterAdapter: FrameworkAdapter = {\n name: 'Next.js App Router',\n\n detect: async ({ rootDir, allDeps }) => {\n if (!allDeps.next) return false;\n return (await detectNextAppDir(rootDir)) !== null;\n },\n\n setup: async ({ rootDir, allDeps, useTypeScript }: FrameworkSetupContext) => {\n const appDirInfo = await detectNextAppDir(rootDir);\n if (!appDirInfo) return;\n\n const { appDir, srcDir } = appDirInfo;\n const isNext16 = isVersionAtLeast(allDeps.next, 16);\n const scriptExtension = useTypeScript ? 'tsx' : 'jsx';\n\n logger(\n colorize('Setting up Next.js App Router integration...', ANSIColors.CYAN)\n );\n\n // 1. Locale detection proxy / middleware (create only when absent).\n await ensureProxyOrMiddleware(rootDir, srcDir, isNext16);\n\n // 2. Move routable files under a `[locale]` segment (idempotent).\n const restructureResult = await restructureAppIntoLocale(rootDir, appDir);\n if (restructureResult.status === 'moved') {\n logger(\n `${v} Restructured app routes under ${colorizePath(join(appDir, '[locale]'))}: ${restructureResult.movedEntries\n .map((entry) => colorize(entry, ANSIColors.MAGENTA))\n .join(', ')}`\n );\n } else if (restructureResult.status === 'already-structured') {\n logger(\n `${v} ${colorizePath(join(appDir, restructureResult.localeSegment))} already exists, skipping restructure`\n );\n }\n\n // When the app already uses a different locale-prefix scheme (e.g. an\n // `[...locale]` / `[[...locale]]` catch-all), respect the user's routing:\n // don't scaffold a second, conflicting `[locale]` segment.\n if (\n restructureResult.status === 'already-structured' &&\n restructureResult.localeSegment !== '[locale]'\n ) {\n logger(\n `${v} Detected existing locale routing (${colorize(restructureResult.localeSegment, ANSIColors.MAGENTA)}), skipping locale layout/page scaffold`\n );\n return;\n }\n\n const localeDir = join(appDir, '[locale]');\n await ensureDirectory(rootDir, localeDir);\n\n // 3. Minimal pass-through root layout (the `[locale]` layout owns `<html>`).\n await createIfMissing(\n rootDir,\n join(appDir, `layout.${scriptExtension}`),\n useTypeScript ? ROOT_LAYOUT_TEMPLATE_TS : ROOT_LAYOUT_TEMPLATE_JS,\n 'root layout'\n );\n\n // 4. Locale layout — transform an existing one, else scaffold from template.\n const existingLayout = await findAppFile(rootDir, localeDir, 'layout');\n if (existingLayout) {\n await transformExistingFile(\n rootDir,\n existingLayout,\n wrapLayoutWithProvider\n );\n } else {\n await createIfMissing(\n rootDir,\n join(localeDir, `layout.${scriptExtension}`),\n useTypeScript ? LOCALE_LAYOUT_TEMPLATE_TS : LOCALE_LAYOUT_TEMPLATE_JS,\n 'locale layout'\n );\n }\n\n // 5. Locale page — transform an existing one, else scaffold from template.\n const existingPage = await findAppFile(rootDir, localeDir, 'page');\n if (existingPage) {\n await transformExistingFile(rootDir, existingPage, wrapPageWithProvider);\n } else {\n await createIfMissing(\n rootDir,\n join(localeDir, `page.${scriptExtension}`),\n useTypeScript ? LOCALE_PAGE_TEMPLATE_TS : LOCALE_PAGE_TEMPLATE_JS,\n 'locale page'\n );\n }\n },\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["exists","join","v","colorizePath","PROXY_TEMPLATE","MIDDLEWARE_TEMPLATE","ensureDirectory","writeFileToRoot","x","readFileFromRoot","detectNextAppDir","isVersionAtLeast","colorize","ANSIColors","restructureAppIntoLocale","ROOT_LAYOUT_TEMPLATE_TS","ROOT_LAYOUT_TEMPLATE_JS","findAppFile","wrapLayoutWithProvider","LOCALE_LAYOUT_TEMPLATE_TS","LOCALE_LAYOUT_TEMPLATE_JS","wrapPageWithProvider","LOCALE_PAGE_TEMPLATE_TS","LOCALE_PAGE_TEMPLATE_JS"],"sources":["../../../../../src/init/frameworkSetup/nextAppRouter/index.ts"],"sourcesContent":["import { join } from 'node:path';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, colorizePath, logger, v, x } from '@intlayer/config/logger';\nimport {\n ensureDirectory,\n exists,\n readFileFromRoot,\n writeFileToRoot,\n} from '../../utils/fileSystem';\nimport type { FrameworkAdapter, FrameworkSetupContext } from '../types';\nimport { detectNextAppDir, findAppFile, isVersionAtLeast } from './detect';\nimport { restructureAppIntoLocale } from './restructure';\nimport {\n LOCALE_LAYOUT_TEMPLATE_JS,\n LOCALE_LAYOUT_TEMPLATE_TS,\n LOCALE_PAGE_TEMPLATE_JS,\n LOCALE_PAGE_TEMPLATE_TS,\n MIDDLEWARE_TEMPLATE,\n PROXY_TEMPLATE,\n ROOT_LAYOUT_TEMPLATE_JS,\n ROOT_LAYOUT_TEMPLATE_TS,\n} from './templates';\nimport {\n type TransformResult,\n wrapLayoutWithProvider,\n wrapPageWithProvider,\n} from './transforms';\n\n/**\n * Creates the locale-detection proxy (Next >= 16) or middleware (Next < 16),\n * but only when neither already exists, so a user's custom file is never\n * touched. Placed next to the app directory's parent (`src/` when present).\n */\nconst ensureProxyOrMiddleware = async (\n rootDir: string,\n srcDir: string,\n isNext16: boolean\n): Promise<void> => {\n const baseDir = srcDir || '.';\n const proxyCandidates = [\n 'proxy.ts',\n 'proxy.js',\n 'middleware.ts',\n 'middleware.js',\n ];\n\n for (const candidate of proxyCandidates) {\n if (await exists(rootDir, join(baseDir, candidate))) {\n logger(\n `${v} ${colorizePath(join(baseDir, candidate))} already exists, leaving it untouched`\n );\n return;\n }\n }\n\n const fileName = isNext16 ? 'proxy.ts' : 'middleware.ts';\n const content = isNext16 ? PROXY_TEMPLATE : MIDDLEWARE_TEMPLATE;\n const targetPath = join(baseDir, fileName);\n\n if (srcDir) await ensureDirectory(rootDir, srcDir);\n await writeFileToRoot(rootDir, targetPath, content);\n logger(`${v} Created ${colorizePath(targetPath)} for locale detection`);\n};\n\n/** Logs the outcome of a layout/page provider-wrap transform. */\nconst logTransformOutcome = (\n filePath: string,\n result: TransformResult\n): void => {\n switch (result.status) {\n case 'wrapped':\n logger(`${v} Added Intlayer provider to ${colorizePath(filePath)}`);\n break;\n case 'already':\n logger(\n `${v} ${colorizePath(filePath)} already wraps the Intlayer provider`\n );\n break;\n case 'skipped-client':\n logger(\n `${x} ${colorizePath(filePath)} is a client component — add the Intlayer provider manually.`,\n { level: 'warn' }\n );\n break;\n case 'skipped':\n logger(\n `${x} Could not safely add the Intlayer provider to ${colorizePath(filePath)} — please wire it manually.`,\n { level: 'warn' }\n );\n break;\n }\n};\n\n/** Transforms an existing file in place, writing it back only when changed. */\nconst transformExistingFile = async (\n rootDir: string,\n filePath: string,\n transform: (code: string) => TransformResult\n): Promise<void> => {\n const code = await readFileFromRoot(rootDir, filePath);\n const result = transform(code);\n if (result.status === 'wrapped') {\n await writeFileToRoot(rootDir, filePath, result.code);\n }\n logTransformOutcome(filePath, result);\n};\n\n/** Creates a file from a template only when it does not already exist. */\nconst createIfMissing = async (\n rootDir: string,\n filePath: string,\n content: string,\n description: string\n): Promise<boolean> => {\n if (await exists(rootDir, filePath)) return false;\n await writeFileToRoot(rootDir, filePath, content);\n logger(`${v} Created ${description} ${colorizePath(filePath)}`);\n return true;\n};\n\n/**\n * Next.js App Router adapter. Scaffolds the locale proxy/middleware, restructures\n * the app into a `[locale]` segment, and wraps the layout/page with the Intlayer\n * providers — all idempotently and without overwriting recognizable user code.\n */\nexport const nextAppRouterAdapter: FrameworkAdapter = {\n name: 'Next.js App Router',\n\n detect: async ({ rootDir, allDeps }) => {\n if (!allDeps.next) return false;\n return (await detectNextAppDir(rootDir)) !== null;\n },\n\n setup: async ({ rootDir, allDeps, useTypeScript }: FrameworkSetupContext) => {\n const appDirInfo = await detectNextAppDir(rootDir);\n if (!appDirInfo) return;\n\n const { appDir, srcDir } = appDirInfo;\n const isNext16 = isVersionAtLeast(allDeps.next, 16);\n const scriptExtension = useTypeScript ? 'tsx' : 'jsx';\n\n logger(\n colorize('Setting up Next.js App Router integration...', ANSIColors.CYAN)\n );\n\n // 1. Locale detection proxy / middleware (create only when absent).\n await ensureProxyOrMiddleware(rootDir, srcDir, isNext16);\n\n // 2. Move routable files under a `[locale]` segment (idempotent).\n const restructureResult = await restructureAppIntoLocale(rootDir, appDir);\n if (restructureResult.status === 'moved') {\n logger(\n `${v} Restructured app routes under ${colorizePath(join(appDir, '[locale]'))}: ${restructureResult.movedEntries\n .map((entry) => colorize(entry, ANSIColors.MAGENTA))\n .join(', ')}`\n );\n } else if (restructureResult.status === 'already-structured') {\n logger(\n `${v} ${colorizePath(join(appDir, restructureResult.localeSegment))} already exists, skipping restructure`\n );\n }\n\n // When the app already uses a different locale-prefix scheme (e.g. an\n // `[...locale]` / `[[...locale]]` catch-all), respect the user's routing:\n // don't scaffold a second, conflicting `[locale]` segment.\n if (\n restructureResult.status === 'already-structured' &&\n restructureResult.localeSegment !== '[locale]'\n ) {\n logger(\n `${v} Detected existing locale routing (${colorize(restructureResult.localeSegment, ANSIColors.MAGENTA)}), skipping locale layout/page scaffold`\n );\n return;\n }\n\n const localeDir = join(appDir, '[locale]');\n await ensureDirectory(rootDir, localeDir);\n\n // 3. Minimal pass-through root layout (the `[locale]` layout owns `<html>`).\n await createIfMissing(\n rootDir,\n join(appDir, `layout.${scriptExtension}`),\n useTypeScript ? ROOT_LAYOUT_TEMPLATE_TS : ROOT_LAYOUT_TEMPLATE_JS,\n 'root layout'\n );\n\n // 4. Locale layout — transform an existing one, else scaffold from template.\n const existingLayout = await findAppFile(rootDir, localeDir, 'layout');\n if (existingLayout) {\n await transformExistingFile(\n rootDir,\n existingLayout,\n wrapLayoutWithProvider\n );\n } else {\n await createIfMissing(\n rootDir,\n join(localeDir, `layout.${scriptExtension}`),\n useTypeScript ? LOCALE_LAYOUT_TEMPLATE_TS : LOCALE_LAYOUT_TEMPLATE_JS,\n 'locale layout'\n );\n }\n\n // 5. Locale page — transform an existing one, else scaffold from template.\n const existingPage = await findAppFile(rootDir, localeDir, 'page');\n if (existingPage) {\n await transformExistingFile(rootDir, existingPage, wrapPageWithProvider);\n } else {\n await createIfMissing(\n rootDir,\n join(localeDir, `page.${scriptExtension}`),\n useTypeScript ? LOCALE_PAGE_TEMPLATE_TS : LOCALE_PAGE_TEMPLATE_JS,\n 'locale page'\n );\n }\n },\n};\n"],"mappings":"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"}
|
|
@@ -146,7 +146,8 @@ const restructureAppIntoLocale = async (rootDir, appDir) => {
|
|
|
146
146
|
movedTopLevelNames
|
|
147
147
|
};
|
|
148
148
|
for (const newAbs of movedFiles) {
|
|
149
|
-
const
|
|
149
|
+
const relFromLocale = (0, node_path.relative)(localeDirAbs, newAbs);
|
|
150
|
+
const oldAbs = (0, node_path.join)(appDirAbs, relFromLocale);
|
|
150
151
|
const code = await (0, node_fs_promises.readFile)(newAbs, "utf8");
|
|
151
152
|
const rewritten = rewriteRelativeImports(code, oldAbs, newAbs, rewriteContext);
|
|
152
153
|
if (rewritten !== code) await (0, node_fs_promises.writeFile)(newAbs, rewritten, "utf8");
|