@intlayer/engine 9.2.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 +14 -2
- package/dist/cjs/buildIntlayerDictionary/buildIntlayerDictionary.cjs.map +1 -1
- package/dist/cjs/buildIntlayerDictionary/writeDynamicDictionary.cjs +26 -5
- package/dist/cjs/buildIntlayerDictionary/writeDynamicDictionary.cjs.map +1 -1
- package/dist/cjs/buildIntlayerDictionary/writeFetchDictionary.cjs +43 -6
- 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 +96 -0
- package/dist/cjs/buildIntlayerDictionary/writeNestedDictionary.cjs.map +1 -0
- 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 +107 -13
- 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 +58 -15
- 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 +4 -2
- 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 +3 -2
- 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 +38 -11
- 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 +23 -0
- package/dist/cjs/utils/chunkArray.cjs.map +1 -0
- 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/index.cjs +2 -0
- 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 +173 -109
- 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/buildIntlayerDictionary.mjs +15 -3
- package/dist/esm/buildIntlayerDictionary/buildIntlayerDictionary.mjs.map +1 -1
- package/dist/esm/buildIntlayerDictionary/writeDynamicDictionary.mjs +26 -5
- package/dist/esm/buildIntlayerDictionary/writeDynamicDictionary.mjs.map +1 -1
- package/dist/esm/buildIntlayerDictionary/writeFetchDictionary.mjs +43 -6
- package/dist/esm/buildIntlayerDictionary/writeFetchDictionary.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/writeNestedDictionary.mjs +92 -0
- package/dist/esm/buildIntlayerDictionary/writeNestedDictionary.mjs.map +1 -0
- 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/fetchDistantDictionaries.mjs +107 -13
- package/dist/esm/fetchDistantDictionaries.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 +59 -16
- 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/loadDictionaries.mjs +4 -2
- package/dist/esm/loadDictionaries/loadDictionaries.mjs.map +1 -1
- package/dist/esm/loadDictionaries/loadRemoteDictionaries.mjs +3 -2
- package/dist/esm/loadDictionaries/loadRemoteDictionaries.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 +38 -11
- 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/chunkArray.mjs +22 -0
- package/dist/esm/utils/chunkArray.mjs.map +1 -0
- package/dist/esm/utils/getContentExtension.mjs +1 -3
- package/dist/esm/utils/getContentExtension.mjs.map +1 -1
- package/dist/esm/utils/index.mjs +2 -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 +173 -109
- 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/buildIntlayerDictionary/buildIntlayerDictionary.d.ts.map +1 -1
- package/dist/types/buildIntlayerDictionary/writeDynamicDictionary.d.ts +14 -2
- package/dist/types/buildIntlayerDictionary/writeDynamicDictionary.d.ts.map +1 -1
- package/dist/types/buildIntlayerDictionary/writeFetchDictionary.d.ts +16 -2
- package/dist/types/buildIntlayerDictionary/writeFetchDictionary.d.ts.map +1 -1
- package/dist/types/buildIntlayerDictionary/writeNestedDictionary.d.ts +54 -0
- package/dist/types/buildIntlayerDictionary/writeNestedDictionary.d.ts.map +1 -0
- package/dist/types/fetchDistantDictionaries.d.ts +26 -0
- package/dist/types/fetchDistantDictionaries.d.ts.map +1 -1
- package/dist/types/formatDictionary.d.ts +2 -0
- package/dist/types/formatDictionary.d.ts.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/dist/types/loadDictionaries/log.d.ts +16 -1
- package/dist/types/loadDictionaries/log.d.ts.map +1 -1
- package/dist/types/utils/chunkArray.d.ts +16 -0
- package/dist/types/utils/chunkArray.d.ts.map +1 -0
- package/dist/types/utils/index.d.ts +2 -1
- package/dist/types/writeContentDeclaration/transformJSFile.d.ts.map +1 -1
- package/package.json +11 -11
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logTypeScriptErrors.cjs","names":[],"sources":["../../../src/loadDictionaries/logTypeScriptErrors.ts"],"sourcesContent":["import { dirname, extname } from 'node:path';\nimport { getAppLogger } from '@intlayer/config/logger';\nimport type { IntlayerConfig } from '@intlayer/types/config';\n\nexport const logTypeScriptErrors = async (\n filePaths: string[],\n configuration: IntlayerConfig\n) => {\n const appLogger = getAppLogger(configuration);\n\n const filesToCheck = filePaths.filter((path) => {\n const ext = extname(path);\n\n return ['.ts', '.tsx', '.js', '.jsx', '.cjs', '.mjs', '.json'].includes(\n ext\n );\n });\n\n if (filesToCheck.length === 0) return;\n\n let ts: typeof import('typescript');\n\n try {\n ts = (await import('typescript')).default || (await import('typescript'));\n } catch {\n // TypeScript not installed, skip type checking\n\n return;\n }\n\n const configFileName = ts.findConfigFile(\n configuration.system?.baseDir ?? process.cwd(),\n ts.sys.fileExists,\n 'tsconfig.json'\n );\n\n let compilerOptions: any = {\n noEmit: true,\n\n allowJs: true,\n\n resolveJsonModule: true,\n };\n\n if (configFileName) {\n const configFileText = ts.sys.readFile(configFileName);\n\n if (configFileText) {\n const configJson = ts.parseConfigFileTextToJson(\n configFileName,\n\n configFileText\n );\n\n if (!configJson.error) {\n const parsedConfig = ts.parseJsonConfigFileContent(\n configJson.config,\n\n ts.sys,\n\n dirname(configFileName)\n );\n\n const { incremental, tsBuildInfoFile, ...restOptions } =\n parsedConfig.options;\n\n compilerOptions = { ...compilerOptions, ...restOptions, noEmit: true };\n }\n }\n }\n\n const program = ts.createProgram(filesToCheck, compilerOptions);\n\n filesToCheck.forEach((filePath) => {\n const sourceFile = program.getSourceFile(filePath);\n\n if (!sourceFile) return;\n\n const diagnostics = ts.getPreEmitDiagnostics(program, sourceFile);\n\n diagnostics.forEach((diagnostic) => {\n const message = ts.flattenDiagnosticMessageText(\n diagnostic.messageText,\n\n '\\n'\n );\n\n if (diagnostic.file && diagnostic.start !== undefined) {\n const { line, character } =\n diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);\n\n appLogger(\n `TS Error in ${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`,\n\n { level: 'warn' }\n );\n }\n });\n });\n};\n"],"mappings":";;;;;AAIA,MAAa,sBAAsB,OACjC,WACA,kBACG;CACH,MAAM,
|
|
1
|
+
{"version":3,"file":"logTypeScriptErrors.cjs","names":["getAppLogger","extname","dirname"],"sources":["../../../src/loadDictionaries/logTypeScriptErrors.ts"],"sourcesContent":["import { dirname, extname } from 'node:path';\nimport { getAppLogger } from '@intlayer/config/logger';\nimport type { IntlayerConfig } from '@intlayer/types/config';\n\nexport const logTypeScriptErrors = async (\n filePaths: string[],\n configuration: IntlayerConfig\n) => {\n const appLogger = getAppLogger(configuration);\n\n const filesToCheck = filePaths.filter((path) => {\n const ext = extname(path);\n\n return ['.ts', '.tsx', '.js', '.jsx', '.cjs', '.mjs', '.json'].includes(\n ext\n );\n });\n\n if (filesToCheck.length === 0) return;\n\n let ts: typeof import('typescript');\n\n try {\n ts = (await import('typescript')).default || (await import('typescript'));\n } catch {\n // TypeScript not installed, skip type checking\n\n return;\n }\n\n const configFileName = ts.findConfigFile(\n configuration.system?.baseDir ?? process.cwd(),\n ts.sys.fileExists,\n 'tsconfig.json'\n );\n\n let compilerOptions: any = {\n noEmit: true,\n\n allowJs: true,\n\n resolveJsonModule: true,\n };\n\n if (configFileName) {\n const configFileText = ts.sys.readFile(configFileName);\n\n if (configFileText) {\n const configJson = ts.parseConfigFileTextToJson(\n configFileName,\n\n configFileText\n );\n\n if (!configJson.error) {\n const parsedConfig = ts.parseJsonConfigFileContent(\n configJson.config,\n\n ts.sys,\n\n dirname(configFileName)\n );\n\n const { incremental, tsBuildInfoFile, ...restOptions } =\n parsedConfig.options;\n\n compilerOptions = { ...compilerOptions, ...restOptions, noEmit: true };\n }\n }\n }\n\n const program = ts.createProgram(filesToCheck, compilerOptions);\n\n filesToCheck.forEach((filePath) => {\n const sourceFile = program.getSourceFile(filePath);\n\n if (!sourceFile) return;\n\n const diagnostics = ts.getPreEmitDiagnostics(program, sourceFile);\n\n diagnostics.forEach((diagnostic) => {\n const message = ts.flattenDiagnosticMessageText(\n diagnostic.messageText,\n\n '\\n'\n );\n\n if (diagnostic.file && diagnostic.start !== undefined) {\n const { line, character } =\n diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);\n\n appLogger(\n `TS Error in ${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`,\n\n { level: 'warn' }\n );\n }\n });\n });\n};\n"],"mappings":";;;;;AAIA,MAAa,sBAAsB,OACjC,WACA,kBACG;CACH,MAAM,gBAAYA,sCAAa,aAAa;CAE5C,MAAM,eAAe,UAAU,QAAQ,SAAS;EAC9C,MAAM,UAAMC,mBAAQ,IAAI;EAExB,OAAO;GAAC;GAAO;GAAQ;GAAO;GAAQ;GAAQ;GAAQ;EAAO,CAAC,CAAC,SAC7D,GACF;CACF,CAAC;CAED,IAAI,aAAa,WAAW,GAAG;CAE/B,IAAI;CAEJ,IAAI;EACF,MAAM,MAAM,OAAO,cAAa,CAAE,WAAY,MAAM,OAAO;CAC7D,QAAQ;EAGN;CACF;CAEA,MAAM,iBAAiB,GAAG,eACxB,cAAc,QAAQ,WAAW,QAAQ,IAAI,GAC7C,GAAG,IAAI,YACP,eACF;CAEA,IAAI,kBAAuB;EACzB,QAAQ;EAER,SAAS;EAET,mBAAmB;CACrB;CAEA,IAAI,gBAAgB;EAClB,MAAM,iBAAiB,GAAG,IAAI,SAAS,cAAc;EAErD,IAAI,gBAAgB;GAClB,MAAM,aAAa,GAAG,0BACpB,gBAEA,cACF;GAEA,IAAI,CAAC,WAAW,OAAO;IASrB,MAAM,EAAE,aAAa,iBAAiB,GAAG,gBARpB,GAAG,2BACtB,WAAW,QAEX,GAAG,SAEHC,mBAAQ,cAAc,CAIX,CAAC,CAAC;IAEf,kBAAkB;KAAE,GAAG;KAAiB,GAAG;KAAa,QAAQ;IAAK;GACvE;EACF;CACF;CAEA,MAAM,UAAU,GAAG,cAAc,cAAc,eAAe;CAE9D,aAAa,SAAS,aAAa;EACjC,MAAM,aAAa,QAAQ,cAAc,QAAQ;EAEjD,IAAI,CAAC,YAAY;EAIjB,AAFoB,GAAG,sBAAsB,SAAS,UAE5C,CAAC,CAAC,SAAS,eAAe;GAClC,MAAM,UAAU,GAAG,6BACjB,WAAW,aAEX,IACF;GAEA,IAAI,WAAW,QAAQ,WAAW,UAAU,QAAW;IACrD,MAAM,EAAE,MAAM,cACZ,WAAW,KAAK,8BAA8B,WAAW,KAAK;IAEhE,UACE,eAAe,WAAW,KAAK,SAAS,IAAI,OAAO,EAAE,GAAG,YAAY,EAAE,KAAK,WAE3E,EAAE,OAAO,OAAO,CAClB;GACF;EACF,CAAC;CACH,CAAC;AACH"}
|
|
@@ -26,16 +26,17 @@ const logConfigDetails = (options) => {
|
|
|
26
26
|
if (customConfiguration) {
|
|
27
27
|
const validation = _intlayer_config_node.intlayerConfigSchema.safeParse(customConfiguration);
|
|
28
28
|
if (!validation.success) {
|
|
29
|
-
const
|
|
29
|
+
const errorMessages = validation.error.issues.map((error) => {
|
|
30
30
|
return `${(0, _intlayer_config_logger.colorizePath)(` - ${error.path.join(".")}:`)} ${(0, _intlayer_config_logger.colorize)(error.message, _intlayer_config_colors.GREY_DARK)}`;
|
|
31
|
-
}).join("\n")
|
|
31
|
+
}).join("\n");
|
|
32
|
+
const errorMessage = `${_intlayer_config_logger.x} Invalid configuration:\n${errorMessages}`;
|
|
32
33
|
appLogger(errorMessage);
|
|
33
34
|
}
|
|
34
35
|
if (customConfiguration.build?.importMode) appLogger(`${(0, _intlayer_config_logger.colorize)("build.importMode", _intlayer_config_colors.BLUE)} is deprecated, use ${(0, _intlayer_config_logger.colorize)("dictionary.importMode", _intlayer_config_colors.BLUE)} instead`);
|
|
35
36
|
if (customConfiguration.compiler?.transformPattern) appLogger(`${(0, _intlayer_config_logger.colorize)("compiler.transformPattern", _intlayer_config_colors.BLUE)} is deprecated, use ${(0, _intlayer_config_logger.colorize)("build.traversePattern", _intlayer_config_colors.BLUE)} instead`);
|
|
36
37
|
if (customConfiguration.compiler?.excludePattern) appLogger(`${(0, _intlayer_config_logger.colorize)("compiler.excludePattern", _intlayer_config_colors.BLUE)} is deprecated, use ${(0, _intlayer_config_logger.colorize)("build.traversePattern", _intlayer_config_colors.BLUE)} instead`);
|
|
37
38
|
}
|
|
38
|
-
}, { cacheTimeoutMs:
|
|
39
|
+
}, { cacheTimeoutMs: 6e4 });
|
|
39
40
|
};
|
|
40
41
|
|
|
41
42
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logConfigDetails.cjs","names":["formatPath","intlayerConfigSchema","
|
|
1
|
+
{"version":3,"file":"logConfigDetails.cjs","names":["getConfigurationAndFilePath","getAppLogger","join","relative","getEnvFilePath","formatPath","intlayerConfigSchema","colorizePath","colorize","ANSIColors","x"],"sources":["../../src/logConfigDetails.ts"],"sourcesContent":["import { join, relative } from 'node:path';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { getEnvFilePath } from '@intlayer/config/env';\nimport {\n colorize,\n colorizePath,\n getAppLogger,\n x,\n} from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfigurationAndFilePath,\n intlayerConfigSchema,\n} from '@intlayer/config/node';\nimport { formatPath, runOnce } from './utils';\n\nexport const logConfigDetails = (options?: GetConfigurationOptions) => {\n const {\n configuration,\n customConfiguration,\n numCustomConfiguration,\n configurationFilePath,\n } = getConfigurationAndFilePath(options);\n const appLogger = getAppLogger(configuration);\n\n runOnce(\n join(\n configuration.system.baseDir,\n '.intlayer',\n 'cache',\n 'intlayer-config-locaded.lock'\n ),\n () => {\n if (numCustomConfiguration === 0) {\n appLogger(\n 'Configuration file not found, using default configuration.',\n {\n isVerbose: true,\n }\n );\n } else {\n const baseDir = configuration.system.baseDir;\n const relativeOutputPath = relative(baseDir, configurationFilePath!);\n\n if (numCustomConfiguration === 1) {\n const dotEnvFilePath = getEnvFilePath(options?.env, options?.envFile);\n\n appLogger(\n `Configuration loaded ${formatPath(relativeOutputPath)}${dotEnvFilePath ? ` - Env: ${formatPath(dotEnvFilePath)}` : ''}`,\n {\n isVerbose: true,\n }\n );\n } else {\n appLogger(\n `Multiple configuration files found, using ${formatPath(relativeOutputPath)}.`,\n {\n isVerbose: true,\n }\n );\n }\n }\n\n if (customConfiguration) {\n const validation = intlayerConfigSchema.safeParse(customConfiguration);\n\n if (!validation.success) {\n const errorMessages = validation.error.issues\n .map((error) => {\n const path = colorizePath(` - ${error.path.join('.')}:`);\n const message = colorize(error.message, ANSIColors.GREY_DARK);\n return `${path} ${message}`;\n })\n .join('\\n');\n const errorMessage = `${x} Invalid configuration:\\n${errorMessages}`;\n\n appLogger(errorMessage);\n }\n\n if (customConfiguration.build?.importMode) {\n appLogger(\n `${colorize('build.importMode', ANSIColors.BLUE)} is deprecated, use ${colorize('dictionary.importMode', ANSIColors.BLUE)} instead`\n );\n }\n if (customConfiguration.compiler?.transformPattern) {\n appLogger(\n `${colorize('compiler.transformPattern', ANSIColors.BLUE)} is deprecated, use ${colorize('build.traversePattern', ANSIColors.BLUE)} instead`\n );\n }\n if (customConfiguration.compiler?.excludePattern) {\n appLogger(\n `${colorize('compiler.excludePattern', ANSIColors.BLUE)} is deprecated, use ${colorize('build.traversePattern', ANSIColors.BLUE)} instead`\n );\n }\n }\n },\n {\n cacheTimeoutMs: 1000 * 60, // 1 minute\n }\n );\n};\n"],"mappings":";;;;;;;;;;;;AAgBA,MAAa,oBAAoB,YAAsC;CACrE,MAAM,EACJ,eACA,qBACA,wBACA,8BACEA,mDAA4B,OAAO;CACvC,MAAM,gBAAYC,sCAAa,aAAa;CAE5C,kCACEC,gBACE,cAAc,OAAO,SACrB,aACA,SACA,8BACF,SACM;EACJ,IAAI,2BAA2B,GAC7B,UACE,8DACA,EACE,WAAW,KACb,CACF;OACK;GACL,MAAM,UAAU,cAAc,OAAO;GACrC,MAAM,yBAAqBC,oBAAS,SAAS,qBAAsB;GAEnE,IAAI,2BAA2B,GAAG;IAChC,MAAM,qBAAiBC,qCAAe,SAAS,KAAK,SAAS,OAAO;IAEpE,UACE,wBAAwBC,mCAAW,kBAAkB,IAAI,iBAAiB,WAAWA,mCAAW,cAAc,MAAM,MACpH,EACE,WAAW,KACb,CACF;GACF,OACE,UACE,6CAA6CA,mCAAW,kBAAkB,EAAE,IAC5E,EACE,WAAW,KACb,CACF;EAEJ;EAEA,IAAI,qBAAqB;GACvB,MAAM,aAAaC,2CAAqB,UAAU,mBAAmB;GAErE,IAAI,CAAC,WAAW,SAAS;IACvB,MAAM,gBAAgB,WAAW,MAAM,OACpC,KAAK,UAAU;KAGd,OAAO,OAFMC,sCAAa,MAAM,MAAM,KAAK,KAAK,GAAG,EAAE,EAExC,EAAE,OADCC,kCAAS,MAAM,SAASC,wBAAW,SAC3B;IAC1B,CAAC,CAAC,CACD,KAAK,IAAI;IACZ,MAAM,eAAe,GAAGC,0BAAE,2BAA2B;IAErD,UAAU,YAAY;GACxB;GAEA,IAAI,oBAAoB,OAAO,YAC7B,UACE,OAAGF,kCAAS,oBAAoBC,wBAAW,IAAI,EAAE,0BAAsBD,kCAAS,yBAAyBC,wBAAW,IAAI,EAAE,SAC5H;GAEF,IAAI,oBAAoB,UAAU,kBAChC,UACE,OAAGD,kCAAS,6BAA6BC,wBAAW,IAAI,EAAE,0BAAsBD,kCAAS,yBAAyBC,wBAAW,IAAI,EAAE,SACrI;GAEF,IAAI,oBAAoB,UAAU,gBAChC,UACE,OAAGD,kCAAS,2BAA2BC,wBAAW,IAAI,EAAE,0BAAsBD,kCAAS,yBAAyBC,wBAAW,IAAI,EAAE,SACnI;EAEJ;CACF,GACA,EACE,gBAAgB,IAClB,CACF;AACF"}
|
|
@@ -24,7 +24,7 @@ const DEFAULT_PREPARE_INTLAYER_OPTIONS = {
|
|
|
24
24
|
clean: false,
|
|
25
25
|
env: "dev",
|
|
26
26
|
format: ["cjs", "esm"],
|
|
27
|
-
cacheTimeoutMs:
|
|
27
|
+
cacheTimeoutMs: 36e5
|
|
28
28
|
};
|
|
29
29
|
const prepareIntlayer = async (configuration, options) => {
|
|
30
30
|
const appLogger = (0, _intlayer_config_logger.getAppLogger)(configuration);
|
|
@@ -57,7 +57,8 @@ const prepareIntlayer = async (configuration, options) => {
|
|
|
57
57
|
await require_writeConfiguration_index.writeConfiguration(configuration);
|
|
58
58
|
const configurationWrittenTime = Date.now();
|
|
59
59
|
appLogger(["Configuration written", (0, _intlayer_config_logger.colorize)(`(${configurationWrittenTime - preparationStartMs}ms)`, _intlayer_config_colors.GREY_DARK)], { isVerbose: true });
|
|
60
|
-
const
|
|
60
|
+
const contentDeclarationPaths = dictionariesWithStats.map((dictionary) => dictionary.path);
|
|
61
|
+
const dictionaries = await require_loadDictionaries_loadDictionaries.loadDictionaries(contentDeclarationPaths, configuration);
|
|
61
62
|
const dictionariesLoadedTime = Date.now();
|
|
62
63
|
appLogger(["Content loaded", (0, _intlayer_config_logger.colorize)([dictionaries.remoteDictionaries.length + dictionaries.pluginDictionaries.length > 0 ? [
|
|
63
64
|
`(Total: ${dictionariesLoadedTime - configurationWrittenTime}ms`,
|
|
@@ -76,7 +77,8 @@ const prepareIntlayer = async (configuration, options) => {
|
|
|
76
77
|
env
|
|
77
78
|
});
|
|
78
79
|
await require_buildIntlayerDictionary_writeRemoteDictionary.writeRemoteDictionary(dictionaries.remoteDictionaries, configuration);
|
|
79
|
-
|
|
80
|
+
const dictionariesToBuild = Object.values(dictionariesOutput?.mergedDictionaries ?? {}).map((dictionary) => dictionary.dictionary);
|
|
81
|
+
await require_createType_createType.createTypes(dictionariesToBuild, configuration);
|
|
80
82
|
await require_createDictionaryEntryPoint_createDictionaryEntryPoint.createDictionaryEntryPoint(configuration, { formats: options?.format });
|
|
81
83
|
const dictionariesBuiltTime = Date.now();
|
|
82
84
|
appLogger(["Dictionaries built", (0, _intlayer_config_logger.colorize)(`(${dictionariesBuiltTime - preparationStartMs}ms)`, _intlayer_config_colors.GREY_DARK)]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prepareIntlayer.cjs","names":["packageJson","isCachedConfigurationUpToDate","listDictionariesWithStats","runOnce","cleanOutputDir","ANSIColors","writeConfiguration","loadDictionaries","buildDictionary","writeRemoteDictionary","createTypes","createDictionaryEntryPoint","createModuleAugmentation"],"sources":["../../src/prepareIntlayer.ts"],"sourcesContent":["import { stat } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, getAppLogger } from '@intlayer/config/logger';\nimport packageJson from '@intlayer/config/package.json' with { type: 'json' };\nimport { cacheDisk } from '@intlayer/config/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { buildDictionary } from './buildIntlayerDictionary/buildIntlayerDictionary';\nimport { writeRemoteDictionary } from './buildIntlayerDictionary/writeRemoteDictionary';\nimport { cleanOutputDir } from './cleanOutputDir';\nimport { createDictionaryEntryPoint } from './createDictionaryEntryPoint/createDictionaryEntryPoint';\nimport { createModuleAugmentation, createTypes } from './createType/index';\nimport { listDictionariesWithStats } from './listDictionariesPath';\nimport { loadDictionaries } from './loadDictionaries/loadDictionaries';\nimport { runOnce } from './utils/runOnce';\nimport {\n isCachedConfigurationUpToDate,\n writeConfiguration,\n} from './writeConfiguration';\n\ntype PrepareIntlayerOptions = {\n clean?: boolean;\n env?: 'prod' | 'dev';\n format?: ('cjs' | 'esm')[];\n forceRun?: boolean;\n cacheTimeoutMs?: number;\n onIsCached?: () => void | Promise<void>;\n};\n\nconst DEFAULT_PREPARE_INTLAYER_OPTIONS = {\n clean: false,\n env: 'dev',\n format: ['cjs', 'esm'],\n cacheTimeoutMs: 1000 * 60 * 60, // 1 hour\n} satisfies PrepareIntlayerOptions;\n\nexport const prepareIntlayer = async (\n configuration: IntlayerConfig,\n options?: PrepareIntlayerOptions\n) => {\n const appLogger = getAppLogger(configuration);\n\n const sentinelPath = join(\n configuration.system.cacheDir,\n 'intlayer-prepared.lock'\n );\n // Clean output dir if the intlayer version has changed\n const versionCache = cacheDisk(configuration, ['intlayer-version']);\n const intlayerCacheVersion = await versionCache.get();\n const isCorrectVersion = Boolean(\n intlayerCacheVersion === undefined ||\n intlayerCacheVersion === packageJson.version\n );\n\n const isConfigSimilar = await isCachedConfigurationUpToDate(configuration);\n\n // Check if any dictionary has been changed to force a new rebuild\n const dictionariesWithStats = await listDictionariesWithStats(configuration);\n let isDictionaryChanged = false;\n try {\n // Try catch as sentinel file may not exist yet\n const sentinelStats = await stat(sentinelPath);\n isDictionaryChanged = dictionariesWithStats.some(\n (dictionary) =>\n dictionary.stats.mtime.getTime() > sentinelStats.mtime.getTime()\n );\n } catch {}\n\n const resolvedPlugins = await Promise.all(configuration.plugins ?? []);\n const hasPluginLoadDictionaries = resolvedPlugins.some((plugin) =>\n Boolean(plugin.loadDictionaries)\n ); // Disable cache if any plugin because it can have custom behavior\n\n const { clean, format, forceRun, onIsCached, cacheTimeoutMs, env } = {\n ...DEFAULT_PREPARE_INTLAYER_OPTIONS,\n forceRun:\n !isCorrectVersion ||\n !isConfigSimilar ||\n isDictionaryChanged ||\n hasPluginLoadDictionaries,\n ...(options ?? {}),\n };\n\n // Skip preparation if it has already been done recently\n await runOnce(\n sentinelPath,\n async ({ renewLock }) => {\n // comment because of issue with next and webpack\n // await checkVersionsConsistency(configuration);\n\n // Re-read the cached version now that the lock is held: another process\n // may have upgraded the output directory while this call was queued, in\n // which case cleaning it would throw away a build that just completed.\n const isCorrectVersionUnderLock =\n (await versionCache.get()) === packageJson.version;\n\n if (clean || !isCorrectVersionUnderLock) {\n await cleanOutputDir(configuration);\n // `cleanOutputDir` wipes the cache directory the sentinel lives in.\n // Re-create it immediately, otherwise a concurrent process (e.g. the\n // `intlayer watch` CLI next to a dev server) sees no lock, starts its\n // own run, and cleans the output directory while this one writes.\n await renewLock();\n }\n\n await versionCache.set(packageJson.version);\n\n const preparationStartMs = Date.now();\n\n appLogger([\n 'Preparing Intlayer',\n colorize(`(v${packageJson.version})`, ANSIColors.GREY_DARK),\n ]);\n\n await writeConfiguration(configuration);\n\n const configurationWrittenTime = Date.now();\n\n appLogger(\n [\n 'Configuration written',\n colorize(\n `(${configurationWrittenTime - preparationStartMs}ms)`,\n ANSIColors.GREY_DARK\n ),\n ],\n {\n isVerbose: true,\n }\n );\n\n const contentDeclarationPaths = dictionariesWithStats.map(\n (dictionary) => dictionary.path\n );\n\n const dictionaries = await loadDictionaries(\n contentDeclarationPaths,\n configuration\n );\n\n const dictionariesLoadedTime = Date.now();\n\n appLogger(\n [\n 'Content loaded',\n colorize(\n [\n dictionaries.remoteDictionaries.length +\n dictionaries.pluginDictionaries.length >\n 0\n ? [\n `(Total: ${dictionariesLoadedTime - configurationWrittenTime}ms`,\n dictionaries.localDictionaries.length > 0\n ? ` - Local: ${dictionaries.time.localDictionaries}ms`\n : '',\n dictionaries.remoteDictionaries.length > 0\n ? ` - Remote: ${dictionaries.time.remoteDictionaries}ms`\n : '',\n dictionaries.pluginDictionaries.length > 0\n ? ` - Plugin: ${dictionaries.time.pluginDictionaries}ms`\n : '',\n `)`,\n ].join('')\n : `(${dictionariesLoadedTime - configurationWrittenTime}ms)`,\n ].join(''),\n ANSIColors.GREY_DARK\n ),\n ],\n {\n isVerbose: true,\n }\n );\n\n // Build local dictionaries\n const dictionariesOutput = await buildDictionary(\n [\n ...dictionaries.localDictionaries,\n ...dictionaries.remoteDictionaries,\n ...dictionaries.pluginDictionaries,\n ],\n configuration,\n { formats: format, importOtherDictionaries: false, env }\n );\n\n // Write remote dictionaries\n // Used as cache for next fetch\n await writeRemoteDictionary(\n dictionaries.remoteDictionaries,\n configuration\n );\n\n const dictionariesToBuild = Object.values(\n dictionariesOutput?.mergedDictionaries ?? {}\n ).map((dictionary) => dictionary.dictionary);\n\n await createTypes(dictionariesToBuild, configuration);\n\n await createDictionaryEntryPoint(configuration, {\n formats: options?.format,\n });\n\n const dictionariesBuiltTime = Date.now();\n\n appLogger([\n 'Dictionaries built',\n colorize(\n `(${dictionariesBuiltTime - preparationStartMs}ms)`,\n ANSIColors.GREY_DARK\n ),\n ]);\n\n await createModuleAugmentation(configuration);\n\n const moduleAugmentationBuiltTime = Date.now();\n\n appLogger(\n [\n 'Module augmentation built',\n colorize(\n `(${moduleAugmentationBuiltTime - dictionariesBuiltTime}ms)`,\n ANSIColors.GREY_DARK\n ),\n ],\n {\n isVerbose: true,\n }\n );\n\n // Plugin transformation\n // Allow plugins to post-process the final build output (e.g., write back ICU JSON)\n for await (const plugin of configuration.plugins ?? []) {\n const { unmergedDictionaries, mergedDictionaries } = dictionariesOutput;\n\n try {\n await plugin.afterBuild?.({\n dictionaries: {\n unmergedDictionaries,\n mergedDictionaries,\n },\n configuration,\n });\n } catch (error) {\n // A failing write-back (e.g. transient network error on a remote\n // target) must not invalidate the dictionaries already built.\n appLogger(\n [\n `Plugin ${plugin.name} afterBuild failed:`,\n (error as Error).message,\n ],\n { level: 'error' }\n );\n }\n }\n\n const preparationElapsedMs = Date.now() - preparationStartMs;\n appLogger(\n [`Done`, colorize(`${preparationElapsedMs}ms`, ANSIColors.GREEN)],\n {\n level: 'info',\n isVerbose: true,\n }\n );\n },\n {\n forceRun,\n onIsCached,\n cacheTimeoutMs,\n }\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"prepareIntlayer.cjs","names":["getAppLogger","join","cacheDisk","packageJson","isCachedConfigurationUpToDate","listDictionariesWithStats","stat","runOnce","cleanOutputDir","colorize","ANSIColors","writeConfiguration","loadDictionaries","buildDictionary","writeRemoteDictionary","createTypes","createDictionaryEntryPoint","createModuleAugmentation"],"sources":["../../src/prepareIntlayer.ts"],"sourcesContent":["import { stat } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, getAppLogger } from '@intlayer/config/logger';\nimport packageJson from '@intlayer/config/package.json' with { type: 'json' };\nimport { cacheDisk } from '@intlayer/config/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { buildDictionary } from './buildIntlayerDictionary/buildIntlayerDictionary';\nimport { writeRemoteDictionary } from './buildIntlayerDictionary/writeRemoteDictionary';\nimport { cleanOutputDir } from './cleanOutputDir';\nimport { createDictionaryEntryPoint } from './createDictionaryEntryPoint/createDictionaryEntryPoint';\nimport { createModuleAugmentation, createTypes } from './createType/index';\nimport { listDictionariesWithStats } from './listDictionariesPath';\nimport { loadDictionaries } from './loadDictionaries/loadDictionaries';\nimport { runOnce } from './utils/runOnce';\nimport {\n isCachedConfigurationUpToDate,\n writeConfiguration,\n} from './writeConfiguration';\n\ntype PrepareIntlayerOptions = {\n clean?: boolean;\n env?: 'prod' | 'dev';\n format?: ('cjs' | 'esm')[];\n forceRun?: boolean;\n cacheTimeoutMs?: number;\n onIsCached?: () => void | Promise<void>;\n};\n\nconst DEFAULT_PREPARE_INTLAYER_OPTIONS = {\n clean: false,\n env: 'dev',\n format: ['cjs', 'esm'],\n cacheTimeoutMs: 1000 * 60 * 60, // 1 hour\n} satisfies PrepareIntlayerOptions;\n\nexport const prepareIntlayer = async (\n configuration: IntlayerConfig,\n options?: PrepareIntlayerOptions\n) => {\n const appLogger = getAppLogger(configuration);\n\n const sentinelPath = join(\n configuration.system.cacheDir,\n 'intlayer-prepared.lock'\n );\n // Clean output dir if the intlayer version has changed\n const versionCache = cacheDisk(configuration, ['intlayer-version']);\n const intlayerCacheVersion = await versionCache.get();\n const isCorrectVersion = Boolean(\n intlayerCacheVersion === undefined ||\n intlayerCacheVersion === packageJson.version\n );\n\n const isConfigSimilar = await isCachedConfigurationUpToDate(configuration);\n\n // Check if any dictionary has been changed to force a new rebuild\n const dictionariesWithStats = await listDictionariesWithStats(configuration);\n let isDictionaryChanged = false;\n try {\n // Try catch as sentinel file may not exist yet\n const sentinelStats = await stat(sentinelPath);\n isDictionaryChanged = dictionariesWithStats.some(\n (dictionary) =>\n dictionary.stats.mtime.getTime() > sentinelStats.mtime.getTime()\n );\n } catch {}\n\n const resolvedPlugins = await Promise.all(configuration.plugins ?? []);\n const hasPluginLoadDictionaries = resolvedPlugins.some((plugin) =>\n Boolean(plugin.loadDictionaries)\n ); // Disable cache if any plugin because it can have custom behavior\n\n const { clean, format, forceRun, onIsCached, cacheTimeoutMs, env } = {\n ...DEFAULT_PREPARE_INTLAYER_OPTIONS,\n forceRun:\n !isCorrectVersion ||\n !isConfigSimilar ||\n isDictionaryChanged ||\n hasPluginLoadDictionaries,\n ...(options ?? {}),\n };\n\n // Skip preparation if it has already been done recently\n await runOnce(\n sentinelPath,\n async ({ renewLock }) => {\n // comment because of issue with next and webpack\n // await checkVersionsConsistency(configuration);\n\n // Re-read the cached version now that the lock is held: another process\n // may have upgraded the output directory while this call was queued, in\n // which case cleaning it would throw away a build that just completed.\n const isCorrectVersionUnderLock =\n (await versionCache.get()) === packageJson.version;\n\n if (clean || !isCorrectVersionUnderLock) {\n await cleanOutputDir(configuration);\n // `cleanOutputDir` wipes the cache directory the sentinel lives in.\n // Re-create it immediately, otherwise a concurrent process (e.g. the\n // `intlayer watch` CLI next to a dev server) sees no lock, starts its\n // own run, and cleans the output directory while this one writes.\n await renewLock();\n }\n\n await versionCache.set(packageJson.version);\n\n const preparationStartMs = Date.now();\n\n appLogger([\n 'Preparing Intlayer',\n colorize(`(v${packageJson.version})`, ANSIColors.GREY_DARK),\n ]);\n\n await writeConfiguration(configuration);\n\n const configurationWrittenTime = Date.now();\n\n appLogger(\n [\n 'Configuration written',\n colorize(\n `(${configurationWrittenTime - preparationStartMs}ms)`,\n ANSIColors.GREY_DARK\n ),\n ],\n {\n isVerbose: true,\n }\n );\n\n const contentDeclarationPaths = dictionariesWithStats.map(\n (dictionary) => dictionary.path\n );\n\n const dictionaries = await loadDictionaries(\n contentDeclarationPaths,\n configuration\n );\n\n const dictionariesLoadedTime = Date.now();\n\n appLogger(\n [\n 'Content loaded',\n colorize(\n [\n dictionaries.remoteDictionaries.length +\n dictionaries.pluginDictionaries.length >\n 0\n ? [\n `(Total: ${dictionariesLoadedTime - configurationWrittenTime}ms`,\n dictionaries.localDictionaries.length > 0\n ? ` - Local: ${dictionaries.time.localDictionaries}ms`\n : '',\n dictionaries.remoteDictionaries.length > 0\n ? ` - Remote: ${dictionaries.time.remoteDictionaries}ms`\n : '',\n dictionaries.pluginDictionaries.length > 0\n ? ` - Plugin: ${dictionaries.time.pluginDictionaries}ms`\n : '',\n `)`,\n ].join('')\n : `(${dictionariesLoadedTime - configurationWrittenTime}ms)`,\n ].join(''),\n ANSIColors.GREY_DARK\n ),\n ],\n {\n isVerbose: true,\n }\n );\n\n // Build local dictionaries\n const dictionariesOutput = await buildDictionary(\n [\n ...dictionaries.localDictionaries,\n ...dictionaries.remoteDictionaries,\n ...dictionaries.pluginDictionaries,\n ],\n configuration,\n { formats: format, importOtherDictionaries: false, env }\n );\n\n // Write remote dictionaries\n // Used as cache for next fetch\n await writeRemoteDictionary(\n dictionaries.remoteDictionaries,\n configuration\n );\n\n const dictionariesToBuild = Object.values(\n dictionariesOutput?.mergedDictionaries ?? {}\n ).map((dictionary) => dictionary.dictionary);\n\n await createTypes(dictionariesToBuild, configuration);\n\n await createDictionaryEntryPoint(configuration, {\n formats: options?.format,\n });\n\n const dictionariesBuiltTime = Date.now();\n\n appLogger([\n 'Dictionaries built',\n colorize(\n `(${dictionariesBuiltTime - preparationStartMs}ms)`,\n ANSIColors.GREY_DARK\n ),\n ]);\n\n await createModuleAugmentation(configuration);\n\n const moduleAugmentationBuiltTime = Date.now();\n\n appLogger(\n [\n 'Module augmentation built',\n colorize(\n `(${moduleAugmentationBuiltTime - dictionariesBuiltTime}ms)`,\n ANSIColors.GREY_DARK\n ),\n ],\n {\n isVerbose: true,\n }\n );\n\n // Plugin transformation\n // Allow plugins to post-process the final build output (e.g., write back ICU JSON)\n for await (const plugin of configuration.plugins ?? []) {\n const { unmergedDictionaries, mergedDictionaries } = dictionariesOutput;\n\n try {\n await plugin.afterBuild?.({\n dictionaries: {\n unmergedDictionaries,\n mergedDictionaries,\n },\n configuration,\n });\n } catch (error) {\n // A failing write-back (e.g. transient network error on a remote\n // target) must not invalidate the dictionaries already built.\n appLogger(\n [\n `Plugin ${plugin.name} afterBuild failed:`,\n (error as Error).message,\n ],\n { level: 'error' }\n );\n }\n }\n\n const preparationElapsedMs = Date.now() - preparationStartMs;\n appLogger(\n [`Done`, colorize(`${preparationElapsedMs}ms`, ANSIColors.GREEN)],\n {\n level: 'info',\n isVerbose: true,\n }\n );\n },\n {\n forceRun,\n onIsCached,\n cacheTimeoutMs,\n }\n );\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"applyMask.cjs","names":[],"sources":["../../../src/reduceDictionaryContent/applyMask.ts"],"sourcesContent":["import type { Dictionary } from '@intlayer/types/dictionary';\n\nexport const applyMask = (full: Dictionary, mask: any): Dictionary => {\n // the mask \"true\" → we don't filter\n if (mask === true) {\n return full;\n }\n\n // arrays\n if (Array.isArray(mask) && Array.isArray(full)) {\n return mask.map((m, i) => applyMask(full[i], m)) as any;\n }\n\n // handle node with nodeType property\n if (full && typeof full === 'object' && 'nodeType' in full) {\n if (mask && typeof mask === 'object') {\n return full; // Keep the full object with nodeType intact\n }\n return full;\n }\n\n // generic object\n if (mask && typeof mask === 'object' && full && typeof full === 'object') {\n const out: any = {};\n const maskEntries = Object.entries(mask);\n const allChildrenAreArrays = maskEntries.every(([, value]) =>\n Array.isArray(value)\n );\n\n for (const [k, m] of maskEntries) {\n const fullValue = (full as any)[k];\n\n // If this child is an array, decide preservation rules.\n // - Preserve when all children at this level are arrays in the mask\n // - Also preserve when the array is an array of translation nodes\n if (Array.isArray(m) && Array.isArray(fullValue)) {\n const isTranslationNode = (val: unknown): boolean =>\n !!val && typeof val === 'object' && 'nodeType' in (val as any);\n const isArrayOfTranslationNodes = fullValue.every((item: any) =>\n isTranslationNode(item)\n );\n\n if (!allChildrenAreArrays && !isArrayOfTranslationNodes) {\n continue; // skip incidental arrays when mixed with non-arrays\n }\n }\n\n out[k] = applyMask(fullValue, m);\n }\n return out;\n }\n\n // unexpected case: we return the original value\n return full;\n};\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"applyMask.cjs","names":[],"sources":["../../../src/reduceDictionaryContent/applyMask.ts"],"sourcesContent":["import type { Dictionary } from '@intlayer/types/dictionary';\n\nexport const applyMask = (full: Dictionary, mask: any): Dictionary => {\n // the mask \"true\" → we don't filter\n if (mask === true) {\n return full;\n }\n\n // arrays\n if (Array.isArray(mask) && Array.isArray(full)) {\n return mask.map((m, i) => applyMask(full[i], m)) as any;\n }\n\n // handle node with nodeType property\n if (full && typeof full === 'object' && 'nodeType' in full) {\n if (mask && typeof mask === 'object') {\n return full; // Keep the full object with nodeType intact\n }\n return full;\n }\n\n // generic object\n if (mask && typeof mask === 'object' && full && typeof full === 'object') {\n const out: any = {};\n const maskEntries = Object.entries(mask);\n const allChildrenAreArrays = maskEntries.every(([, value]) =>\n Array.isArray(value)\n );\n\n for (const [k, m] of maskEntries) {\n const fullValue = (full as any)[k];\n\n // If this child is an array, decide preservation rules.\n // - Preserve when all children at this level are arrays in the mask\n // - Also preserve when the array is an array of translation nodes\n if (Array.isArray(m) && Array.isArray(fullValue)) {\n const isTranslationNode = (val: unknown): boolean =>\n !!val && typeof val === 'object' && 'nodeType' in (val as any);\n const isArrayOfTranslationNodes = fullValue.every((item: any) =>\n isTranslationNode(item)\n );\n\n if (!allChildrenAreArrays && !isArrayOfTranslationNodes) {\n continue; // skip incidental arrays when mixed with non-arrays\n }\n }\n\n out[k] = applyMask(fullValue, m);\n }\n return out;\n }\n\n // unexpected case: we return the original value\n return full;\n};\n"],"mappings":";;AAEA,MAAa,aAAa,MAAkB,SAA0B;CAEpE,IAAI,SAAS,MACX,OAAO;CAIT,IAAI,MAAM,QAAQ,IAAI,KAAK,MAAM,QAAQ,IAAI,GAC3C,OAAO,KAAK,KAAK,GAAG,MAAM,UAAU,KAAK,IAAI,CAAC,CAAC;CAIjD,IAAI,QAAQ,OAAO,SAAS,YAAY,cAAc,MAAM;EAC1D,IAAI,QAAQ,OAAO,SAAS,UAC1B,OAAO;EAET,OAAO;CACT;CAGA,IAAI,QAAQ,OAAO,SAAS,YAAY,QAAQ,OAAO,SAAS,UAAU;EACxE,MAAM,MAAW,CAAC;EAClB,MAAM,cAAc,OAAO,QAAQ,IAAI;EACvC,MAAM,uBAAuB,YAAY,OAAO,GAAG,WACjD,MAAM,QAAQ,KAAK,CACrB;EAEA,KAAK,MAAM,CAAC,GAAG,MAAM,aAAa;GAChC,MAAM,YAAa,KAAa;GAKhC,IAAI,MAAM,QAAQ,CAAC,KAAK,MAAM,QAAQ,SAAS,GAAG;IAChD,MAAM,qBAAqB,QACzB,CAAC,CAAC,OAAO,OAAO,QAAQ,YAAY,cAAe;IACrD,MAAM,4BAA4B,UAAU,OAAO,SACjD,kBAAkB,IAAI,CACxB;IAEA,IAAI,CAAC,wBAAwB,CAAC,2BAC5B;GAEJ;GAEA,IAAI,KAAK,UAAU,WAAW,CAAC;EACjC;EACA,OAAO;CACT;CAGA,OAAO;AACT"}
|
|
@@ -4,7 +4,8 @@ let _intlayer_core_plugins = require("@intlayer/core/plugins");
|
|
|
4
4
|
|
|
5
5
|
//#region src/reduceDictionaryContent/reduceDictionaryContent.ts
|
|
6
6
|
const reduceDictionaryContent = (fullDictionary, partialDictionary) => {
|
|
7
|
-
|
|
7
|
+
const mask = (0, _intlayer_core_plugins.getMaskContent)(partialDictionary);
|
|
8
|
+
return require_reduceDictionaryContent_applyMask.applyMask(fullDictionary, mask);
|
|
8
9
|
};
|
|
9
10
|
|
|
10
11
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reduceDictionaryContent.cjs","names":["applyMask"],"sources":["../../../src/reduceDictionaryContent/reduceDictionaryContent.ts"],"sourcesContent":["import { getMaskContent } from '@intlayer/core/plugins';\nimport type { Dictionary } from '@intlayer/types/dictionary';\nimport { applyMask } from './applyMask';\n\nexport const reduceDictionaryContent = (\n fullDictionary: Dictionary,\n partialDictionary: Dictionary\n) => {\n const mask = getMaskContent(partialDictionary);\n const result = applyMask(fullDictionary, mask);\n\n return result;\n};\n"],"mappings":";;;;;AAIA,MAAa,2BACX,gBACA,sBACG;
|
|
1
|
+
{"version":3,"file":"reduceDictionaryContent.cjs","names":["getMaskContent","applyMask"],"sources":["../../../src/reduceDictionaryContent/reduceDictionaryContent.ts"],"sourcesContent":["import { getMaskContent } from '@intlayer/core/plugins';\nimport type { Dictionary } from '@intlayer/types/dictionary';\nimport { applyMask } from './applyMask';\n\nexport const reduceDictionaryContent = (\n fullDictionary: Dictionary,\n partialDictionary: Dictionary\n) => {\n const mask = getMaskContent(partialDictionary);\n const result = applyMask(fullDictionary, mask);\n\n return result;\n};\n"],"mappings":";;;;;AAIA,MAAa,2BACX,gBACA,sBACG;CACH,MAAM,WAAOA,uCAAe,iBAAiB;CAG7C,OAFeC,oDAAU,gBAAgB,IAE7B;AACd"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"calculateScore.cjs","names":[],"sources":["../../../src/scan/calculateScore.ts"],"sourcesContent":["/**\n * Mutualized SEO/i18n scoring logic.\n *\n * This module is the single source of truth for turning audit/scan events into\n * a weighted score. It is consumed both by the hosted backend SEO audit\n * (`apps/backend`) and by the `intlayer scan` CLI command, so it must stay free\n * of any server-only dependency (no Fastify, no Cheerio, no logger coupling).\n */\n\n/** Accumulated score across all checks that ran. */\nexport type Score = {\n /** Sum of points obtained (success = full, warning = half, error = none). */\n score: number;\n /** Maximum achievable points for the checks that ran. */\n totalScore: number;\n};\n\n/**\n * Minimal shape of an event needed to contribute to the score.\n *\n * The `type` is the check identifier. URL-scoped checks are suffixed with the\n * URL using a backslash separator (e.g. `url_htmlLang\\https://example.com`), so\n * only the part before the first backslash is used to look up the weight.\n */\nexport type ScorableEvent = {\n type?: string;\n status?: string;\n};\n\n/**\n * Resolve how many points an event contributes based on its status.\n * - `warning` → half of the weight\n * - `error` → none\n * - anything else (`success`, `started`, `done`, …) → full weight\n */\nconst scoreCheck = (score: number, event: ScorableEvent): number => {\n if (event.status === 'warning') return score / 2;\n if (event.status === 'error') return 0;\n return score;\n};\n\n/** Weight (in points) of every scorable check. */\nexport const scoreRecord = {\n robots_robotsPresent: 10,\n robots_noLocalizedUrlsForgotten: 8,\n sitemap_sitemapPresent: 10,\n sitemap_noLocalizedUrlsForgotten: 9,\n sitemap_hasAlternates: 8,\n sitemap_hasXDefault: 7,\n url_htmlLang: 9,\n url_htmlDir: 3,\n url_hasCanonical: 10,\n url_hreflang: 9,\n url_hasLocalizedLinks: 8,\n url_hasXDefault: 7,\n url_allAnchorsLocalized: 6,\n url_currentLocale: 3,\n url_unusedBundleContent: 8,\n} as const;\n\n/** Identifier of a scorable check (without the URL suffix). */\nexport type ScoreCheckType = keyof typeof scoreRecord;\n\n/**\n * Apply a single event to the running score, returning a new {@link Score}.\n * Unknown check types are ignored so the function is safe to call on every\n * emitted event.\n *\n * @param score - The current accumulated score.\n * @param event - The event to fold into the score.\n * @returns A new score with the event applied.\n */\nexport const mutateScore = (score: Score, event: ScorableEvent): Score => {\n const newScore: Score = { ...score };\n\n const typeWithoutUrl = event.type?.split('\\\\')[0];\n\n if (!typeWithoutUrl) {\n return newScore;\n }\n\n const scoreValue = scoreRecord[typeWithoutUrl as ScoreCheckType];\n\n if (typeof scoreValue === 'number') {\n newScore.score += scoreCheck(scoreValue, event);\n newScore.totalScore += scoreValue;\n }\n\n return newScore;\n};\n\n/**\n * Convert a raw {@link Score} into a 0–100 percentage.\n *\n * @param score - The accumulated score.\n * @returns The rounded percentage, or 0 when no check ran.\n */\nexport const toScorePercent = (score: Score): number =>\n Math.round(score.totalScore > 0 ? (score.score / score.totalScore) * 100 : 0);\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"calculateScore.cjs","names":[],"sources":["../../../src/scan/calculateScore.ts"],"sourcesContent":["/**\n * Mutualized SEO/i18n scoring logic.\n *\n * This module is the single source of truth for turning audit/scan events into\n * a weighted score. It is consumed both by the hosted backend SEO audit\n * (`apps/backend`) and by the `intlayer scan` CLI command, so it must stay free\n * of any server-only dependency (no Fastify, no Cheerio, no logger coupling).\n */\n\n/** Accumulated score across all checks that ran. */\nexport type Score = {\n /** Sum of points obtained (success = full, warning = half, error = none). */\n score: number;\n /** Maximum achievable points for the checks that ran. */\n totalScore: number;\n};\n\n/**\n * Minimal shape of an event needed to contribute to the score.\n *\n * The `type` is the check identifier. URL-scoped checks are suffixed with the\n * URL using a backslash separator (e.g. `url_htmlLang\\https://example.com`), so\n * only the part before the first backslash is used to look up the weight.\n */\nexport type ScorableEvent = {\n type?: string;\n status?: string;\n};\n\n/**\n * Resolve how many points an event contributes based on its status.\n * - `warning` → half of the weight\n * - `error` → none\n * - anything else (`success`, `started`, `done`, …) → full weight\n */\nconst scoreCheck = (score: number, event: ScorableEvent): number => {\n if (event.status === 'warning') return score / 2;\n if (event.status === 'error') return 0;\n return score;\n};\n\n/** Weight (in points) of every scorable check. */\nexport const scoreRecord = {\n robots_robotsPresent: 10,\n robots_noLocalizedUrlsForgotten: 8,\n sitemap_sitemapPresent: 10,\n sitemap_noLocalizedUrlsForgotten: 9,\n sitemap_hasAlternates: 8,\n sitemap_hasXDefault: 7,\n url_htmlLang: 9,\n url_htmlDir: 3,\n url_hasCanonical: 10,\n url_hreflang: 9,\n url_hasLocalizedLinks: 8,\n url_hasXDefault: 7,\n url_allAnchorsLocalized: 6,\n url_currentLocale: 3,\n url_unusedBundleContent: 8,\n} as const;\n\n/** Identifier of a scorable check (without the URL suffix). */\nexport type ScoreCheckType = keyof typeof scoreRecord;\n\n/**\n * Apply a single event to the running score, returning a new {@link Score}.\n * Unknown check types are ignored so the function is safe to call on every\n * emitted event.\n *\n * @param score - The current accumulated score.\n * @param event - The event to fold into the score.\n * @returns A new score with the event applied.\n */\nexport const mutateScore = (score: Score, event: ScorableEvent): Score => {\n const newScore: Score = { ...score };\n\n const typeWithoutUrl = event.type?.split('\\\\')[0];\n\n if (!typeWithoutUrl) {\n return newScore;\n }\n\n const scoreValue = scoreRecord[typeWithoutUrl as ScoreCheckType];\n\n if (typeof scoreValue === 'number') {\n newScore.score += scoreCheck(scoreValue, event);\n newScore.totalScore += scoreValue;\n }\n\n return newScore;\n};\n\n/**\n * Convert a raw {@link Score} into a 0–100 percentage.\n *\n * @param score - The accumulated score.\n * @returns The rounded percentage, or 0 when no check ran.\n */\nexport const toScorePercent = (score: Score): number =>\n Math.round(score.totalScore > 0 ? (score.score / score.totalScore) * 100 : 0);\n"],"mappings":";;;;;;;;AAmCA,MAAM,cAAc,OAAe,UAAiC;CAClE,IAAI,MAAM,WAAW,WAAW,OAAO,QAAQ;CAC/C,IAAI,MAAM,WAAW,SAAS,OAAO;CACrC,OAAO;AACT;;AAGA,MAAa,cAAc;CACzB,sBAAsB;CACtB,iCAAiC;CACjC,wBAAwB;CACxB,kCAAkC;CAClC,uBAAuB;CACvB,qBAAqB;CACrB,cAAc;CACd,aAAa;CACb,kBAAkB;CAClB,cAAc;CACd,uBAAuB;CACvB,iBAAiB;CACjB,yBAAyB;CACzB,mBAAmB;CACnB,yBAAyB;AAC3B;;;;;;;;;;AAcA,MAAa,eAAe,OAAc,UAAgC;CACxE,MAAM,WAAkB,EAAE,GAAG,MAAM;CAEnC,MAAM,iBAAiB,MAAM,MAAM,MAAM,IAAI,CAAC,CAAC;CAE/C,IAAI,CAAC,gBACH,OAAO;CAGT,MAAM,aAAa,YAAY;CAE/B,IAAI,OAAO,eAAe,UAAU;EAClC,SAAS,SAAS,WAAW,YAAY,KAAK;EAC9C,SAAS,cAAc;CACzB;CAEA,OAAO;AACT;;;;;;;AAQA,MAAa,kBAAkB,UAC7B,KAAK,MAAM,MAAM,aAAa,IAAK,MAAM,QAAQ,MAAM,aAAc,MAAM,CAAC"}
|
package/dist/cjs/scan/checks.cjs
CHANGED
|
@@ -6,7 +6,7 @@ let _intlayer_types_allLocales = require("@intlayer/types/allLocales");
|
|
|
6
6
|
//#region src/scan/checks.ts
|
|
7
7
|
/** Format a byte count as a human-readable size. */
|
|
8
8
|
const formatSize = (bytes) => {
|
|
9
|
-
if (bytes >=
|
|
9
|
+
if (bytes >= 1048576) return `${(bytes / 1048576).toFixed(2)} MB`;
|
|
10
10
|
if (bytes >= 1024) return `${(bytes / 1024).toFixed(2)} KB`;
|
|
11
11
|
return `${bytes} B`;
|
|
12
12
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checks.cjs","names":["extractHtmlLang","extractHtmlDir","hasCanonical","extractHreflangs","ALL_LOCALES","analyzeBundleContent"],"sources":["../../../src/scan/checks.ts"],"sourcesContent":["import { ALL_LOCALES } from '@intlayer/types/allLocales';\nimport { analyzeBundleContent } from './analyzeBundleContent';\nimport {\n type Anchor,\n extractHreflangs,\n extractHtmlDir,\n extractHtmlLang,\n hasCanonical,\n} from './parseHtml';\nimport type {\n BundleChunkInput,\n BundleContentAnalysis,\n ScanEvent,\n} from './types';\n\n/** Format a byte count as a human-readable size. */\nexport const formatSize = (bytes: number): string => {\n if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;\n if (bytes >= 1024) return `${(bytes / 1024).toFixed(2)} KB`;\n return `${bytes} B`;\n};\n\n/**\n * Check the `<html>` element attributes (`lang`, `dir`) and the resulting\n * current-locale signal. Returns the detected language tag.\n */\nexport const checkHtmlAttributes = (\n html: string,\n targetUrl: string,\n events: ScanEvent[]\n): { langTag: string | undefined } => {\n const langTag = extractHtmlLang(html);\n const dirTag = extractHtmlDir(html);\n\n events.push({\n type: `url_htmlLang\\\\${targetUrl}`,\n status: langTag ? 'success' : 'error',\n details: {\n success: langTag,\n error: langTag ? undefined : 'Missing html lang attribute',\n },\n });\n\n events.push({\n type: `url_currentLocale\\\\${targetUrl}`,\n status: langTag ? 'success' : 'warning',\n details: {\n success: langTag,\n warning: langTag ? undefined : 'No locale detected',\n },\n });\n\n events.push({\n type: `url_htmlDir\\\\${targetUrl}`,\n status: dirTag ? 'success' : 'warning',\n details: {\n success: dirTag,\n warning: dirTag ? undefined : 'Missing html dir attribute',\n },\n });\n\n return { langTag };\n};\n\n/** Check the presence of a canonical link. */\nexport const checkCanonical = (\n html: string,\n targetUrl: string,\n events: ScanEvent[]\n): void => {\n const present = hasCanonical(html);\n events.push({\n type: `url_hasCanonical\\\\${targetUrl}`,\n status: present ? 'success' : 'warning',\n details: { warning: present ? undefined : 'Missing canonical link' },\n });\n};\n\n/**\n * Check the page's hreflang structure and collect discovered locales into\n * `localesSet`.\n */\nexport const checkLinguisticStructure = (\n html: string,\n targetUrl: string,\n localesSet: Set<string>,\n events: ScanEvent[]\n): void => {\n const langTag = extractHtmlLang(html);\n if (langTag) localesSet.add(langTag);\n\n const hreflangs = extractHreflangs(html);\n for (const { hreflang } of hreflangs) {\n if (hreflang !== 'x-default') localesSet.add(hreflang);\n }\n\n const hasXDefault = hreflangs.some((h) => h.hreflang === 'x-default');\n\n events.push({\n type: `url_hreflang\\\\${targetUrl}`,\n status: hreflangs.length > 0 ? 'success' : 'warning',\n details: {\n success: hreflangs.length > 0 ? hreflangs : undefined,\n warning: hreflangs.length === 0 ? 'No hreflang tags found' : undefined,\n },\n });\n\n events.push({\n type: `url_hasXDefault\\\\${targetUrl}`,\n status: hasXDefault ? 'success' : 'error',\n details: {\n error: hasXDefault ? undefined : 'Missing x-default hreflang link',\n },\n });\n};\n\nconst normalizeHost = (host: string): string =>\n host.startsWith('www.') ? host.slice(4) : host;\n\nconst localeValues = Object.values(ALL_LOCALES) as string[];\n\n/**\n * Check whether internal links carry a locale segment, mirroring the hosted\n * audit's URL-structure analysis but operating on parsed anchors instead of a\n * live DOM.\n */\nexport const checkUrlStructure = (\n anchors: Anchor[],\n origin: string,\n targetUrl: string,\n events: ScanEvent[]\n): void => {\n const targetHostname = normalizeHost(new URL(origin).hostname);\n\n let localizedCount = 0;\n let totalInternalCount = 0;\n const nonLocalizedLinks: string[] = [];\n\n for (const { href } of anchors) {\n if (!href || href.startsWith('#') || href.startsWith('javascript:'))\n continue;\n\n try {\n const url = new URL(href, origin);\n if (normalizeHost(url.hostname) !== targetHostname) continue;\n\n totalInternalCount++;\n const path = url.pathname.toLowerCase();\n const hostname = url.hostname.toLowerCase();\n\n const hasLocaleInPath = localeValues.some((locale) => {\n const l = locale.toLowerCase();\n return path === `/${l}` || path.includes(`/${l}/`);\n });\n const hasLocaleInSubdomain = localeValues.some((locale) => {\n const l = locale.toLowerCase();\n return hostname.startsWith(`${l}.`) || hostname.includes(`.${l}.`);\n });\n\n if (hasLocaleInPath || hasLocaleInSubdomain) {\n localizedCount++;\n } else {\n nonLocalizedLinks.push(href);\n }\n } catch {\n /* ignore malformed URLs */\n }\n }\n\n const hasLocalizedLinks = localizedCount > 0;\n const allAnchorsLocalized =\n totalInternalCount === 0 || localizedCount === totalInternalCount;\n\n events.push({\n type: `url_hasLocalizedLinks\\\\${targetUrl}`,\n status: hasLocalizedLinks ? 'success' : 'warning',\n details: {\n success: hasLocalizedLinks\n ? `${localizedCount} localized links found out of ${totalInternalCount} internal links`\n : undefined,\n warning: hasLocalizedLinks ? undefined : 'No localized links found',\n },\n });\n\n events.push({\n type: `url_allAnchorsLocalized\\\\${targetUrl}`,\n status: allAnchorsLocalized ? 'success' : 'warning',\n details: {\n warning: allAnchorsLocalized\n ? undefined\n : {\n message: 'Some internal links are not localized',\n links: nonLocalizedLinks,\n },\n },\n });\n};\n\n/**\n * Analyze the JavaScript bundles for unused locale content and emit the\n * corresponding event. Returns the analysis so callers can report on it.\n */\nexport const checkBundleContent = (\n chunks: BundleChunkInput[],\n html: string,\n currentLocale: string | undefined,\n targetUrl: string,\n totalPageSize: number,\n events: ScanEvent[]\n): BundleContentAnalysis | undefined => {\n if (!currentLocale) {\n events.push({\n type: `url_unusedBundleContent\\\\${targetUrl}`,\n status: 'warning',\n details: {\n warning: 'Cannot analyse bundle content: page locale not detected',\n },\n });\n return undefined;\n }\n\n const analysis = analyzeBundleContent(\n chunks,\n html,\n currentLocale,\n totalPageSize\n );\n\n // Status is driven by the main bundle — lazy chunks with unused content are expected.\n const mainBundleMaxUnused = analysis.mainBundleChunks.reduce(\n (max, c) => Math.max(max, c.unusedPercent),\n 0\n );\n\n const status =\n mainBundleMaxUnused === 0\n ? 'success'\n : mainBundleMaxUnused <= 30\n ? 'warning'\n : 'error';\n\n events.push({\n type: `url_unusedBundleContent\\\\${targetUrl}`,\n status,\n details: { [status]: analysis },\n });\n\n return analysis;\n};\n\n/** Fetch and check `robots.txt`, emitting robots-related events. */\nexport const checkRobots = async (\n origin: string,\n discoveredLocales: Set<string>,\n userAgent: string,\n events: ScanEvent[]\n): Promise<void> => {\n let robotsPresent = false;\n let noLocalizedUrlsForgotten = true;\n const errors: string[] = [];\n\n try {\n const response = await fetch(`${origin}/robots.txt`, {\n headers: { 'User-Agent': userAgent },\n });\n\n if (response.ok) {\n robotsPresent = true;\n const content = await response.text();\n\n if (content && discoveredLocales.size > 0) {\n const disallowedPaths = content\n .split('\\n')\n .map((line) => line.trim().toLowerCase())\n .filter((line) => line.startsWith('disallow:'))\n .map((line) => line.slice('disallow:'.length).trim());\n\n for (const locale of discoveredLocales) {\n for (const path of disallowedPaths) {\n if (path === `/${locale}` || path === `/${locale}/`) {\n noLocalizedUrlsForgotten = false;\n errors.push(\n `Locale path \"${locale}\" appears to be blocked in robots.txt: ${path}`\n );\n }\n }\n }\n }\n }\n } catch (error) {\n errors.push(\n `Failed to fetch robots.txt: ${error instanceof Error ? error.message : 'Unknown error'}`\n );\n }\n\n events.push({\n type: 'robots_robotsPresent',\n status: robotsPresent ? 'success' : 'warning',\n details: {\n warning: robotsPresent ? undefined : 'No robots.txt found',\n error: errors.length > 0 ? errors : undefined,\n },\n });\n\n if (robotsPresent) {\n events.push({\n type: 'robots_noLocalizedUrlsForgotten',\n status: noLocalizedUrlsForgotten ? 'success' : 'error',\n details: { error: noLocalizedUrlsForgotten ? undefined : errors },\n });\n }\n};\n\n/** Fetch and check `sitemap.xml`, emitting sitemap-related events. */\nexport const checkSitemap = async (\n origin: string,\n discoveredLocales: Set<string>,\n userAgent: string,\n events: ScanEvent[]\n): Promise<void> => {\n let sitemapPresent = false;\n let hasXDefault = false;\n let hasAlternates = false;\n let noLocalizedUrlsForgotten = true;\n const errors: string[] = [];\n\n try {\n const response = await fetch(`${origin}/sitemap.xml`, {\n headers: { 'User-Agent': userAgent },\n });\n\n if (response.ok) {\n sitemapPresent = true;\n const content = await response.text();\n\n const hreflangMatches = content.match(/hreflang\\s*=\\s*\"([^\"]+)\"/gi) ?? [];\n const hreflangs = hreflangMatches.map((m) =>\n m.replace(/hreflang\\s*=\\s*\"/i, '').replace(/\"$/, '')\n );\n hasAlternates = hreflangs.length > 0;\n hasXDefault = hreflangs.includes('x-default');\n\n if (discoveredLocales.size > 0) {\n const urlBlocks = content.match(/<url\\b[\\s\\S]*?<\\/url>/gi) ?? [];\n const allFoundLocales = new Set<string>();\n let anyUrlMissingLocale = false;\n\n for (const block of urlBlocks) {\n const localesInUrl = new Set<string>();\n\n for (const hreflang of block.match(/hreflang\\s*=\\s*\"([^\"]+)\"/gi) ??\n []) {\n const value = hreflang\n .replace(/hreflang\\s*=\\s*\"/i, '')\n .replace(/\"$/, '');\n if (value !== 'x-default') {\n localesInUrl.add(value);\n allFoundLocales.add(value);\n }\n }\n\n const loc = block.match(/<loc>([\\s\\S]*?)<\\/loc>/i)?.[1]?.trim();\n if (loc) {\n try {\n const firstSegment = new URL(loc).pathname\n .split('/')\n .filter(Boolean)[0];\n if (firstSegment && discoveredLocales.has(firstSegment)) {\n localesInUrl.add(firstSegment);\n allFoundLocales.add(firstSegment);\n }\n } catch {\n /* invalid loc URL, skip */\n }\n }\n\n const missing = [...discoveredLocales].filter(\n (locale) => !localesInUrl.has(locale)\n );\n if (missing.length > 0 && missing.length < discoveredLocales.size) {\n anyUrlMissingLocale = true;\n }\n }\n\n const completelyMissing = [...discoveredLocales].filter(\n (locale) => !allFoundLocales.has(locale)\n );\n if (anyUrlMissingLocale || completelyMissing.length > 0) {\n noLocalizedUrlsForgotten = false;\n if (completelyMissing.length > 0) {\n errors.push(\n `The following locales are completely missing from the sitemap: ${completelyMissing.join(', ')}`\n );\n }\n }\n }\n }\n } catch (error) {\n errors.push(\n `Failed to fetch sitemap.xml: ${error instanceof Error ? error.message : 'Unknown error'}`\n );\n }\n\n events.push({\n type: 'sitemap_sitemapPresent',\n status: sitemapPresent ? 'success' : 'warning',\n details: {\n warning: sitemapPresent ? undefined : 'No sitemap.xml found',\n error: errors.length > 0 ? errors : undefined,\n },\n });\n\n if (sitemapPresent) {\n events.push({\n type: 'sitemap_noLocalizedUrlsForgotten',\n status: noLocalizedUrlsForgotten ? 'success' : 'warning',\n details: { warning: noLocalizedUrlsForgotten ? undefined : errors },\n });\n\n events.push({\n type: 'sitemap_hasXDefault',\n status: hasXDefault ? 'success' : 'warning',\n details: {\n warning: hasXDefault ? undefined : 'No x-default hreflang in sitemap',\n },\n });\n\n events.push({\n type: 'sitemap_hasAlternates',\n status: hasAlternates ? 'success' : 'warning',\n details: {\n warning: hasAlternates\n ? undefined\n : 'No alternate language links found in sitemap',\n },\n });\n }\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"checks.cjs","names":["extractHtmlLang","extractHtmlDir","hasCanonical","extractHreflangs","ALL_LOCALES","analyzeBundleContent"],"sources":["../../../src/scan/checks.ts"],"sourcesContent":["import { ALL_LOCALES } from '@intlayer/types/allLocales';\nimport { analyzeBundleContent } from './analyzeBundleContent';\nimport {\n type Anchor,\n extractHreflangs,\n extractHtmlDir,\n extractHtmlLang,\n hasCanonical,\n} from './parseHtml';\nimport type {\n BundleChunkInput,\n BundleContentAnalysis,\n ScanEvent,\n} from './types';\n\n/** Format a byte count as a human-readable size. */\nexport const formatSize = (bytes: number): string => {\n if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;\n if (bytes >= 1024) return `${(bytes / 1024).toFixed(2)} KB`;\n return `${bytes} B`;\n};\n\n/**\n * Check the `<html>` element attributes (`lang`, `dir`) and the resulting\n * current-locale signal. Returns the detected language tag.\n */\nexport const checkHtmlAttributes = (\n html: string,\n targetUrl: string,\n events: ScanEvent[]\n): { langTag: string | undefined } => {\n const langTag = extractHtmlLang(html);\n const dirTag = extractHtmlDir(html);\n\n events.push({\n type: `url_htmlLang\\\\${targetUrl}`,\n status: langTag ? 'success' : 'error',\n details: {\n success: langTag,\n error: langTag ? undefined : 'Missing html lang attribute',\n },\n });\n\n events.push({\n type: `url_currentLocale\\\\${targetUrl}`,\n status: langTag ? 'success' : 'warning',\n details: {\n success: langTag,\n warning: langTag ? undefined : 'No locale detected',\n },\n });\n\n events.push({\n type: `url_htmlDir\\\\${targetUrl}`,\n status: dirTag ? 'success' : 'warning',\n details: {\n success: dirTag,\n warning: dirTag ? undefined : 'Missing html dir attribute',\n },\n });\n\n return { langTag };\n};\n\n/** Check the presence of a canonical link. */\nexport const checkCanonical = (\n html: string,\n targetUrl: string,\n events: ScanEvent[]\n): void => {\n const present = hasCanonical(html);\n events.push({\n type: `url_hasCanonical\\\\${targetUrl}`,\n status: present ? 'success' : 'warning',\n details: { warning: present ? undefined : 'Missing canonical link' },\n });\n};\n\n/**\n * Check the page's hreflang structure and collect discovered locales into\n * `localesSet`.\n */\nexport const checkLinguisticStructure = (\n html: string,\n targetUrl: string,\n localesSet: Set<string>,\n events: ScanEvent[]\n): void => {\n const langTag = extractHtmlLang(html);\n if (langTag) localesSet.add(langTag);\n\n const hreflangs = extractHreflangs(html);\n for (const { hreflang } of hreflangs) {\n if (hreflang !== 'x-default') localesSet.add(hreflang);\n }\n\n const hasXDefault = hreflangs.some((h) => h.hreflang === 'x-default');\n\n events.push({\n type: `url_hreflang\\\\${targetUrl}`,\n status: hreflangs.length > 0 ? 'success' : 'warning',\n details: {\n success: hreflangs.length > 0 ? hreflangs : undefined,\n warning: hreflangs.length === 0 ? 'No hreflang tags found' : undefined,\n },\n });\n\n events.push({\n type: `url_hasXDefault\\\\${targetUrl}`,\n status: hasXDefault ? 'success' : 'error',\n details: {\n error: hasXDefault ? undefined : 'Missing x-default hreflang link',\n },\n });\n};\n\nconst normalizeHost = (host: string): string =>\n host.startsWith('www.') ? host.slice(4) : host;\n\nconst localeValues = Object.values(ALL_LOCALES) as string[];\n\n/**\n * Check whether internal links carry a locale segment, mirroring the hosted\n * audit's URL-structure analysis but operating on parsed anchors instead of a\n * live DOM.\n */\nexport const checkUrlStructure = (\n anchors: Anchor[],\n origin: string,\n targetUrl: string,\n events: ScanEvent[]\n): void => {\n const targetHostname = normalizeHost(new URL(origin).hostname);\n\n let localizedCount = 0;\n let totalInternalCount = 0;\n const nonLocalizedLinks: string[] = [];\n\n for (const { href } of anchors) {\n if (!href || href.startsWith('#') || href.startsWith('javascript:'))\n continue;\n\n try {\n const url = new URL(href, origin);\n if (normalizeHost(url.hostname) !== targetHostname) continue;\n\n totalInternalCount++;\n const path = url.pathname.toLowerCase();\n const hostname = url.hostname.toLowerCase();\n\n const hasLocaleInPath = localeValues.some((locale) => {\n const l = locale.toLowerCase();\n return path === `/${l}` || path.includes(`/${l}/`);\n });\n const hasLocaleInSubdomain = localeValues.some((locale) => {\n const l = locale.toLowerCase();\n return hostname.startsWith(`${l}.`) || hostname.includes(`.${l}.`);\n });\n\n if (hasLocaleInPath || hasLocaleInSubdomain) {\n localizedCount++;\n } else {\n nonLocalizedLinks.push(href);\n }\n } catch {\n /* ignore malformed URLs */\n }\n }\n\n const hasLocalizedLinks = localizedCount > 0;\n const allAnchorsLocalized =\n totalInternalCount === 0 || localizedCount === totalInternalCount;\n\n events.push({\n type: `url_hasLocalizedLinks\\\\${targetUrl}`,\n status: hasLocalizedLinks ? 'success' : 'warning',\n details: {\n success: hasLocalizedLinks\n ? `${localizedCount} localized links found out of ${totalInternalCount} internal links`\n : undefined,\n warning: hasLocalizedLinks ? undefined : 'No localized links found',\n },\n });\n\n events.push({\n type: `url_allAnchorsLocalized\\\\${targetUrl}`,\n status: allAnchorsLocalized ? 'success' : 'warning',\n details: {\n warning: allAnchorsLocalized\n ? undefined\n : {\n message: 'Some internal links are not localized',\n links: nonLocalizedLinks,\n },\n },\n });\n};\n\n/**\n * Analyze the JavaScript bundles for unused locale content and emit the\n * corresponding event. Returns the analysis so callers can report on it.\n */\nexport const checkBundleContent = (\n chunks: BundleChunkInput[],\n html: string,\n currentLocale: string | undefined,\n targetUrl: string,\n totalPageSize: number,\n events: ScanEvent[]\n): BundleContentAnalysis | undefined => {\n if (!currentLocale) {\n events.push({\n type: `url_unusedBundleContent\\\\${targetUrl}`,\n status: 'warning',\n details: {\n warning: 'Cannot analyse bundle content: page locale not detected',\n },\n });\n return undefined;\n }\n\n const analysis = analyzeBundleContent(\n chunks,\n html,\n currentLocale,\n totalPageSize\n );\n\n // Status is driven by the main bundle — lazy chunks with unused content are expected.\n const mainBundleMaxUnused = analysis.mainBundleChunks.reduce(\n (max, c) => Math.max(max, c.unusedPercent),\n 0\n );\n\n const status =\n mainBundleMaxUnused === 0\n ? 'success'\n : mainBundleMaxUnused <= 30\n ? 'warning'\n : 'error';\n\n events.push({\n type: `url_unusedBundleContent\\\\${targetUrl}`,\n status,\n details: { [status]: analysis },\n });\n\n return analysis;\n};\n\n/** Fetch and check `robots.txt`, emitting robots-related events. */\nexport const checkRobots = async (\n origin: string,\n discoveredLocales: Set<string>,\n userAgent: string,\n events: ScanEvent[]\n): Promise<void> => {\n let robotsPresent = false;\n let noLocalizedUrlsForgotten = true;\n const errors: string[] = [];\n\n try {\n const response = await fetch(`${origin}/robots.txt`, {\n headers: { 'User-Agent': userAgent },\n });\n\n if (response.ok) {\n robotsPresent = true;\n const content = await response.text();\n\n if (content && discoveredLocales.size > 0) {\n const disallowedPaths = content\n .split('\\n')\n .map((line) => line.trim().toLowerCase())\n .filter((line) => line.startsWith('disallow:'))\n .map((line) => line.slice('disallow:'.length).trim());\n\n for (const locale of discoveredLocales) {\n for (const path of disallowedPaths) {\n if (path === `/${locale}` || path === `/${locale}/`) {\n noLocalizedUrlsForgotten = false;\n errors.push(\n `Locale path \"${locale}\" appears to be blocked in robots.txt: ${path}`\n );\n }\n }\n }\n }\n }\n } catch (error) {\n errors.push(\n `Failed to fetch robots.txt: ${error instanceof Error ? error.message : 'Unknown error'}`\n );\n }\n\n events.push({\n type: 'robots_robotsPresent',\n status: robotsPresent ? 'success' : 'warning',\n details: {\n warning: robotsPresent ? undefined : 'No robots.txt found',\n error: errors.length > 0 ? errors : undefined,\n },\n });\n\n if (robotsPresent) {\n events.push({\n type: 'robots_noLocalizedUrlsForgotten',\n status: noLocalizedUrlsForgotten ? 'success' : 'error',\n details: { error: noLocalizedUrlsForgotten ? undefined : errors },\n });\n }\n};\n\n/** Fetch and check `sitemap.xml`, emitting sitemap-related events. */\nexport const checkSitemap = async (\n origin: string,\n discoveredLocales: Set<string>,\n userAgent: string,\n events: ScanEvent[]\n): Promise<void> => {\n let sitemapPresent = false;\n let hasXDefault = false;\n let hasAlternates = false;\n let noLocalizedUrlsForgotten = true;\n const errors: string[] = [];\n\n try {\n const response = await fetch(`${origin}/sitemap.xml`, {\n headers: { 'User-Agent': userAgent },\n });\n\n if (response.ok) {\n sitemapPresent = true;\n const content = await response.text();\n\n const hreflangMatches = content.match(/hreflang\\s*=\\s*\"([^\"]+)\"/gi) ?? [];\n const hreflangs = hreflangMatches.map((m) =>\n m.replace(/hreflang\\s*=\\s*\"/i, '').replace(/\"$/, '')\n );\n hasAlternates = hreflangs.length > 0;\n hasXDefault = hreflangs.includes('x-default');\n\n if (discoveredLocales.size > 0) {\n const urlBlocks = content.match(/<url\\b[\\s\\S]*?<\\/url>/gi) ?? [];\n const allFoundLocales = new Set<string>();\n let anyUrlMissingLocale = false;\n\n for (const block of urlBlocks) {\n const localesInUrl = new Set<string>();\n\n for (const hreflang of block.match(/hreflang\\s*=\\s*\"([^\"]+)\"/gi) ??\n []) {\n const value = hreflang\n .replace(/hreflang\\s*=\\s*\"/i, '')\n .replace(/\"$/, '');\n if (value !== 'x-default') {\n localesInUrl.add(value);\n allFoundLocales.add(value);\n }\n }\n\n const loc = block.match(/<loc>([\\s\\S]*?)<\\/loc>/i)?.[1]?.trim();\n if (loc) {\n try {\n const firstSegment = new URL(loc).pathname\n .split('/')\n .filter(Boolean)[0];\n if (firstSegment && discoveredLocales.has(firstSegment)) {\n localesInUrl.add(firstSegment);\n allFoundLocales.add(firstSegment);\n }\n } catch {\n /* invalid loc URL, skip */\n }\n }\n\n const missing = [...discoveredLocales].filter(\n (locale) => !localesInUrl.has(locale)\n );\n if (missing.length > 0 && missing.length < discoveredLocales.size) {\n anyUrlMissingLocale = true;\n }\n }\n\n const completelyMissing = [...discoveredLocales].filter(\n (locale) => !allFoundLocales.has(locale)\n );\n if (anyUrlMissingLocale || completelyMissing.length > 0) {\n noLocalizedUrlsForgotten = false;\n if (completelyMissing.length > 0) {\n errors.push(\n `The following locales are completely missing from the sitemap: ${completelyMissing.join(', ')}`\n );\n }\n }\n }\n }\n } catch (error) {\n errors.push(\n `Failed to fetch sitemap.xml: ${error instanceof Error ? error.message : 'Unknown error'}`\n );\n }\n\n events.push({\n type: 'sitemap_sitemapPresent',\n status: sitemapPresent ? 'success' : 'warning',\n details: {\n warning: sitemapPresent ? undefined : 'No sitemap.xml found',\n error: errors.length > 0 ? errors : undefined,\n },\n });\n\n if (sitemapPresent) {\n events.push({\n type: 'sitemap_noLocalizedUrlsForgotten',\n status: noLocalizedUrlsForgotten ? 'success' : 'warning',\n details: { warning: noLocalizedUrlsForgotten ? undefined : errors },\n });\n\n events.push({\n type: 'sitemap_hasXDefault',\n status: hasXDefault ? 'success' : 'warning',\n details: {\n warning: hasXDefault ? undefined : 'No x-default hreflang in sitemap',\n },\n });\n\n events.push({\n type: 'sitemap_hasAlternates',\n status: hasAlternates ? 'success' : 'warning',\n details: {\n warning: hasAlternates\n ? undefined\n : 'No alternate language links found in sitemap',\n },\n });\n }\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parseHtml.cjs","names":[],"sources":["../../../src/scan/parseHtml.ts"],"sourcesContent":["/**\n * Tiny dependency-free HTML extraction helpers.\n *\n * The hosted backend audit relies on Cheerio + a real browser, but the CLI scan\n * must stay dependency-light. These regex-based helpers cover the handful of\n * head/anchor signals the score needs. They are intentionally forgiving: when a\n * tag can't be parsed it is simply skipped rather than throwing.\n */\n\n/** Compute the UTF-8 byte length of a string in both Node and browser builds. */\nexport const byteLength = (text: string): number =>\n typeof Buffer !== 'undefined'\n ? Buffer.byteLength(text, 'utf-8')\n : new TextEncoder().encode(text).length;\n\n/** Read an attribute value off a single tag's attribute string. */\nconst readAttribute = (\n attributes: string,\n attributeName: string\n): string | undefined => {\n const match = attributes.match(\n new RegExp(`${attributeName}\\\\s*=\\\\s*(\"([^\"]*)\"|'([^']*)'|([^\\\\s>]+))`, 'i')\n );\n if (!match) return undefined;\n return match[2] ?? match[3] ?? match[4];\n};\n\n/** Extract the `lang` attribute of the `<html>` element, if present. */\nexport const extractHtmlLang = (html: string): string | undefined => {\n const htmlTag = html.match(/<html\\b([^>]*)>/i);\n return htmlTag ? readAttribute(htmlTag[1], 'lang') : undefined;\n};\n\n/** Extract the `dir` attribute of the `<html>` element, if present. */\nexport const extractHtmlDir = (html: string): string | undefined => {\n const htmlTag = html.match(/<html\\b([^>]*)>/i);\n return htmlTag ? readAttribute(htmlTag[1], 'dir') : undefined;\n};\n\n/** Extract the document `<title>` text. */\nexport const extractTitle = (html: string): string => {\n const match = html.match(/<title[^>]*>([\\s\\S]*?)<\\/title>/i);\n return match ? match[1].trim() : '';\n};\n\n/** Extract the `<meta name=\"description\">` content. */\nexport const extractMetaDescription = (html: string): string => {\n const metas = html.match(/<meta\\b[^>]*>/gi) ?? [];\n for (const meta of metas) {\n if (/name\\s*=\\s*(\"|')?description\\1?/i.test(meta)) {\n return readAttribute(meta, 'content') ?? '';\n }\n }\n return '';\n};\n\n/** Extract the `<meta property=\"og:image\">` content. */\nexport const extractOgImage = (html: string): string | undefined => {\n const metas = html.match(/<meta\\b[^>]*>/gi) ?? [];\n for (const meta of metas) {\n if (/property\\s*=\\s*(\"|')?og:image\\1?/i.test(meta)) {\n return readAttribute(meta, 'content');\n }\n }\n return undefined;\n};\n\n/** Whether a `<link rel=\"canonical\">` element is present. */\nexport const hasCanonical = (html: string): boolean => {\n const links = html.match(/<link\\b[^>]*>/gi) ?? [];\n return links.some((link) => /rel\\s*=\\s*(\"|')?canonical\\1?/i.test(link));\n};\n\n/** A parsed `<link rel=\"alternate\" hreflang>` element. */\nexport type HreflangLink = { hreflang: string; href: string };\n\n/** Extract every `<link rel=\"alternate\" hreflang=\"…\" href=\"…\">` element. */\nexport const extractHreflangs = (html: string): HreflangLink[] => {\n const links = html.match(/<link\\b[^>]*>/gi) ?? [];\n const result: HreflangLink[] = [];\n for (const link of links) {\n if (!/rel\\s*=\\s*(\"|')?alternate\\1?/i.test(link)) continue;\n const hreflang = readAttribute(link, 'hreflang');\n const href = readAttribute(link, 'href');\n if (hreflang && href) result.push({ hreflang, href });\n }\n return result;\n};\n\n/**\n * Extract every eagerly-loaded script URL: `<script src>`,\n * `<link rel=\"modulepreload\">` and `<link rel=\"preload\" as=\"script\">`.\n *\n * @param html - The raw HTML document.\n * @param baseUrl - Base URL used to resolve relative script URLs.\n * @returns Absolute, de-duplicated script URLs.\n */\nexport const extractScriptUrls = (html: string, baseUrl: string): string[] => {\n const urls = new Set<string>();\n\n const add = (raw: string | undefined) => {\n if (!raw) return;\n try {\n urls.add(new URL(raw, baseUrl).href);\n } catch {\n /* ignore malformed URLs */\n }\n };\n\n for (const script of html.match(/<script\\b[^>]*>/gi) ?? []) {\n add(readAttribute(script, 'src'));\n }\n\n for (const link of html.match(/<link\\b[^>]*>/gi) ?? []) {\n const rel = readAttribute(link, 'rel')?.toLowerCase();\n const as = readAttribute(link, 'as')?.toLowerCase();\n if (rel === 'modulepreload' || (rel === 'preload' && as === 'script')) {\n add(readAttribute(link, 'href'));\n }\n }\n\n return Array.from(urls);\n};\n\n/** A parsed `<a href>` anchor. */\nexport type Anchor = { href: string; text: string };\n\n/** Extract every `<a href=\"…\">text</a>` anchor from the document. */\nexport const extractAnchors = (html: string): Anchor[] => {\n const anchors: Anchor[] = [];\n const anchorPattern = /<a\\b([^>]*)>([\\s\\S]*?)<\\/a>/gi;\n let match = anchorPattern.exec(html);\n while (match !== null) {\n const href = readAttribute(match[1], 'href');\n if (href) {\n const text = match[2]\n .replace(/<[^>]+>/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n anchors.push({ href, text });\n }\n match = anchorPattern.exec(html);\n }\n return anchors;\n};\n\n/**\n * Extract visible text snippets from an HTML document (scripts, styles and\n * tags stripped). Used to approximate the rendered content size without a DOM.\n */\nexport const extractVisibleTextStrings = (html: string): string[] => {\n const withoutNonVisible = html\n .replace(/<script[\\s\\S]*?<\\/script>/gi, ' ')\n .replace(/<style[\\s\\S]*?<\\/style>/gi, ' ')\n .replace(/<noscript[\\s\\S]*?<\\/noscript>/gi, ' ')\n .replace(/<!--[\\s\\S]*?-->/g, ' ');\n\n return withoutNonVisible\n .replace(/<[^>]+>/g, '\\n')\n .split('\\n')\n .map((line) => line.replace(/\\s+/g, ' ').trim())\n .filter((line) => line.length > 1);\n};\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"parseHtml.cjs","names":[],"sources":["../../../src/scan/parseHtml.ts"],"sourcesContent":["/**\n * Tiny dependency-free HTML extraction helpers.\n *\n * The hosted backend audit relies on Cheerio + a real browser, but the CLI scan\n * must stay dependency-light. These regex-based helpers cover the handful of\n * head/anchor signals the score needs. They are intentionally forgiving: when a\n * tag can't be parsed it is simply skipped rather than throwing.\n */\n\n/** Compute the UTF-8 byte length of a string in both Node and browser builds. */\nexport const byteLength = (text: string): number =>\n typeof Buffer !== 'undefined'\n ? Buffer.byteLength(text, 'utf-8')\n : new TextEncoder().encode(text).length;\n\n/** Read an attribute value off a single tag's attribute string. */\nconst readAttribute = (\n attributes: string,\n attributeName: string\n): string | undefined => {\n const match = attributes.match(\n new RegExp(`${attributeName}\\\\s*=\\\\s*(\"([^\"]*)\"|'([^']*)'|([^\\\\s>]+))`, 'i')\n );\n if (!match) return undefined;\n return match[2] ?? match[3] ?? match[4];\n};\n\n/** Extract the `lang` attribute of the `<html>` element, if present. */\nexport const extractHtmlLang = (html: string): string | undefined => {\n const htmlTag = html.match(/<html\\b([^>]*)>/i);\n return htmlTag ? readAttribute(htmlTag[1], 'lang') : undefined;\n};\n\n/** Extract the `dir` attribute of the `<html>` element, if present. */\nexport const extractHtmlDir = (html: string): string | undefined => {\n const htmlTag = html.match(/<html\\b([^>]*)>/i);\n return htmlTag ? readAttribute(htmlTag[1], 'dir') : undefined;\n};\n\n/** Extract the document `<title>` text. */\nexport const extractTitle = (html: string): string => {\n const match = html.match(/<title[^>]*>([\\s\\S]*?)<\\/title>/i);\n return match ? match[1].trim() : '';\n};\n\n/** Extract the `<meta name=\"description\">` content. */\nexport const extractMetaDescription = (html: string): string => {\n const metas = html.match(/<meta\\b[^>]*>/gi) ?? [];\n for (const meta of metas) {\n if (/name\\s*=\\s*(\"|')?description\\1?/i.test(meta)) {\n return readAttribute(meta, 'content') ?? '';\n }\n }\n return '';\n};\n\n/** Extract the `<meta property=\"og:image\">` content. */\nexport const extractOgImage = (html: string): string | undefined => {\n const metas = html.match(/<meta\\b[^>]*>/gi) ?? [];\n for (const meta of metas) {\n if (/property\\s*=\\s*(\"|')?og:image\\1?/i.test(meta)) {\n return readAttribute(meta, 'content');\n }\n }\n return undefined;\n};\n\n/** Whether a `<link rel=\"canonical\">` element is present. */\nexport const hasCanonical = (html: string): boolean => {\n const links = html.match(/<link\\b[^>]*>/gi) ?? [];\n return links.some((link) => /rel\\s*=\\s*(\"|')?canonical\\1?/i.test(link));\n};\n\n/** A parsed `<link rel=\"alternate\" hreflang>` element. */\nexport type HreflangLink = { hreflang: string; href: string };\n\n/** Extract every `<link rel=\"alternate\" hreflang=\"…\" href=\"…\">` element. */\nexport const extractHreflangs = (html: string): HreflangLink[] => {\n const links = html.match(/<link\\b[^>]*>/gi) ?? [];\n const result: HreflangLink[] = [];\n for (const link of links) {\n if (!/rel\\s*=\\s*(\"|')?alternate\\1?/i.test(link)) continue;\n const hreflang = readAttribute(link, 'hreflang');\n const href = readAttribute(link, 'href');\n if (hreflang && href) result.push({ hreflang, href });\n }\n return result;\n};\n\n/**\n * Extract every eagerly-loaded script URL: `<script src>`,\n * `<link rel=\"modulepreload\">` and `<link rel=\"preload\" as=\"script\">`.\n *\n * @param html - The raw HTML document.\n * @param baseUrl - Base URL used to resolve relative script URLs.\n * @returns Absolute, de-duplicated script URLs.\n */\nexport const extractScriptUrls = (html: string, baseUrl: string): string[] => {\n const urls = new Set<string>();\n\n const add = (raw: string | undefined) => {\n if (!raw) return;\n try {\n urls.add(new URL(raw, baseUrl).href);\n } catch {\n /* ignore malformed URLs */\n }\n };\n\n for (const script of html.match(/<script\\b[^>]*>/gi) ?? []) {\n add(readAttribute(script, 'src'));\n }\n\n for (const link of html.match(/<link\\b[^>]*>/gi) ?? []) {\n const rel = readAttribute(link, 'rel')?.toLowerCase();\n const as = readAttribute(link, 'as')?.toLowerCase();\n if (rel === 'modulepreload' || (rel === 'preload' && as === 'script')) {\n add(readAttribute(link, 'href'));\n }\n }\n\n return Array.from(urls);\n};\n\n/** A parsed `<a href>` anchor. */\nexport type Anchor = { href: string; text: string };\n\n/** Extract every `<a href=\"…\">text</a>` anchor from the document. */\nexport const extractAnchors = (html: string): Anchor[] => {\n const anchors: Anchor[] = [];\n const anchorPattern = /<a\\b([^>]*)>([\\s\\S]*?)<\\/a>/gi;\n let match = anchorPattern.exec(html);\n while (match !== null) {\n const href = readAttribute(match[1], 'href');\n if (href) {\n const text = match[2]\n .replace(/<[^>]+>/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n anchors.push({ href, text });\n }\n match = anchorPattern.exec(html);\n }\n return anchors;\n};\n\n/**\n * Extract visible text snippets from an HTML document (scripts, styles and\n * tags stripped). Used to approximate the rendered content size without a DOM.\n */\nexport const extractVisibleTextStrings = (html: string): string[] => {\n const withoutNonVisible = html\n .replace(/<script[\\s\\S]*?<\\/script>/gi, ' ')\n .replace(/<style[\\s\\S]*?<\\/style>/gi, ' ')\n .replace(/<noscript[\\s\\S]*?<\\/noscript>/gi, ' ')\n .replace(/<!--[\\s\\S]*?-->/g, ' ');\n\n return withoutNonVisible\n .replace(/<[^>]+>/g, '\\n')\n .split('\\n')\n .map((line) => line.replace(/\\s+/g, ' ').trim())\n .filter((line) => line.length > 1);\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scanWebsite.cjs","names":["GREY","GREY_LIGHT","extractScriptUrls","byteLength","checkHtmlAttributes","extractAnchors","checkBundleContent","checkRobots","checkSitemap","mutateScore","toScorePercent"],"sources":["../../../src/scan/scanWebsite.ts"],"sourcesContent":["import { GREY, GREY_LIGHT } from '@intlayer/config/colors';\nimport { colorize, logger } from '@intlayer/config/logger';\nimport { mutateScore, type Score, toScorePercent } from './calculateScore';\nimport {\n checkBundleContent,\n checkCanonical,\n checkHtmlAttributes,\n checkLinguisticStructure,\n checkRobots,\n checkSitemap,\n checkUrlStructure,\n} from './checks';\nimport { byteLength, extractAnchors, extractScriptUrls } from './parseHtml';\nimport type {\n BundleChunkInput,\n ScanEvent,\n ScanOptions,\n ScanResult,\n} from './types';\n\nconst DEFAULT_USER_AGENT =\n 'Mozilla/5.0 (compatible; IntlayerScanBot/1.0; +https://intlayer.org)';\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\n/**\n * Log a recommendation to install `puppeteer` for a deeper scan. Mirrors the\n * style used by other optional-dependency hints across the CLI.\n */\nconst logDeepScanRecommendation = (): void => {\n logger([\n colorize('Recommended: Install', GREY),\n colorize('puppeteer', GREY_LIGHT),\n colorize(\n 'package to enable a deeper scan (renders client-side content & lazy-loaded chunks). See documentation:',\n GREY\n ),\n colorize('https://intlayer.org/doc/concept/cli#scan', GREY_LIGHT),\n ]);\n};\n\n/** Outcome of a render-based deep scan. */\ntype DeepScanResult = {\n html: string;\n totalPageSize: number;\n chunks: BundleChunkInput[];\n};\n\n/**\n * Render the page with a locally installed `puppeteer` to capture\n * client-rendered content, the accurate transfer size, and lazy-loaded chunks.\n *\n * `puppeteer` is imported dynamically through a non-literal specifier so it is\n * never bundled and stays an optional dependency: when it is absent the import\n * rejects and the caller falls back to the basic scan.\n *\n * @returns The deep-scan result, or `null` when `puppeteer` is unavailable.\n */\nconst runDeepScan = async (\n targetUrl: string,\n userAgent: string,\n timeoutMs: number\n): Promise<DeepScanResult | null> => {\n const moduleName = 'puppeteer';\n // Optional dependency resolved at runtime — typed as `any` because it is not\n // a declared dependency of this package.\n let puppeteer: any;\n try {\n const mod: any = await import(moduleName);\n puppeteer = mod.default ?? mod;\n } catch {\n return null;\n }\n\n let browser: any;\n try {\n browser = await puppeteer.launch({\n headless: true,\n executablePath: process.env.PUPPETEER_EXECUTABLE_PATH,\n args: [\n '--no-sandbox',\n '--disable-setuid-sandbox',\n '--disable-dev-shm-usage',\n '--disable-gpu',\n ],\n });\n\n const page = await browser.newPage();\n await page.setUserAgent(userAgent);\n await page.setExtraHTTPHeaders({ 'Accept-Language': 'en-US,en;q=0.9' });\n\n const origin = new URL(targetUrl).origin;\n const jsResponseMap = new Map<string, string>();\n let totalPageSize = 0;\n const pendingResponses: Promise<void>[] = [];\n\n page.on('response', (response: any) => {\n pendingResponses.push(\n (async () => {\n try {\n if (response.status() !== 200) return;\n const buffer = await response.buffer();\n totalPageSize += buffer.length;\n\n const responseUrl: string = response.url();\n const contentType: string =\n response.headers()['content-type'] ?? '';\n const isJavaScript =\n contentType.includes('javascript') ||\n /\\.(js|mjs|cjs)(\\?|$)/.test(responseUrl);\n if (isJavaScript && responseUrl.startsWith(origin)) {\n jsResponseMap.set(responseUrl, buffer.toString('utf-8'));\n }\n } catch {\n /* response already consumed or aborted */\n }\n })()\n );\n });\n\n await page.goto(targetUrl, {\n waitUntil: 'domcontentloaded',\n timeout: timeoutMs,\n });\n await page\n .waitForNetworkIdle({ idleTime: 1000, timeout: 10_000 })\n .catch(() => {\n /* ok if it never fully idles */\n });\n await Promise.allSettled(pendingResponses);\n\n const html: string = await page.content();\n const mainBundleUrls = new Set(extractScriptUrls(html, targetUrl));\n const chunks: BundleChunkInput[] = Array.from(jsResponseMap.entries()).map(\n ([url, content]) => ({\n url,\n isMainBundle: mainBundleUrls.has(url),\n content,\n })\n );\n\n return { html, totalPageSize, chunks };\n } finally {\n if (browser) await browser.close();\n }\n};\n\n/** Fetch the raw HTML document, measuring its byte size. */\nconst fetchHtml = async (\n url: string,\n userAgent: string,\n timeoutMs: number\n): Promise<{ html: string; finalUrl: string }> => {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const response = await fetch(url, {\n headers: { 'User-Agent': userAgent, 'Accept-Language': 'en-US,en;q=0.9' },\n signal: controller.signal,\n });\n const html = await response.text();\n return { html, finalUrl: response.url || url };\n } finally {\n clearTimeout(timer);\n }\n};\n\n/**\n * Fetch every eagerly-loaded script. Same-origin scripts keep their content so\n * their locale weight can be analyzed; third-party scripts only contribute to\n * the measured page size (their locale-like keys cause false positives).\n */\nconst fetchScripts = async (\n scriptUrls: string[],\n origin: string,\n userAgent: string\n): Promise<{ chunks: BundleChunkInput[]; scriptBytes: number }> => {\n const chunks: BundleChunkInput[] = [];\n let scriptBytes = 0;\n\n await Promise.all(\n scriptUrls.map(async (scriptUrl) => {\n try {\n const response = await fetch(scriptUrl, {\n headers: { 'User-Agent': userAgent },\n });\n if (!response.ok) return;\n const content = await response.text();\n scriptBytes += byteLength(content);\n if (scriptUrl.startsWith(origin)) {\n chunks.push({ url: scriptUrl, isMainBundle: true, content });\n }\n } catch {\n /* ignore unreachable scripts */\n }\n })\n );\n\n return { chunks, scriptBytes };\n};\n\n/**\n * Scan a single web page for i18n/SEO health and bundle weight.\n *\n * In `deep` mode (default) the page is rendered with a locally installed\n * `puppeteer`; when `puppeteer` is missing the scan transparently falls back to\n * a `basic` fetch-based pass and logs a recommendation to install it.\n *\n * @param targetUrl - The absolute URL to scan.\n * @param options - {@link ScanOptions} controlling depth, timeout and UA.\n * @returns The {@link ScanResult} including score, page size and per-check events.\n */\nexport const scanWebsite = async (\n targetUrl: string,\n options: ScanOptions = {}\n): Promise<ScanResult> => {\n const {\n deep = true,\n timeoutMs = DEFAULT_TIMEOUT_MS,\n userAgent = DEFAULT_USER_AGENT,\n } = options;\n\n const origin = new URL(targetUrl).origin;\n\n let mode: ScanResult['mode'] = 'basic';\n let html: string;\n let totalPageSize: number;\n let chunks: BundleChunkInput[];\n\n const deepResult = deep\n ? await runDeepScan(targetUrl, userAgent, timeoutMs)\n : null;\n\n if (deepResult) {\n mode = 'deep';\n html = deepResult.html;\n chunks = deepResult.chunks;\n totalPageSize = deepResult.totalPageSize;\n } else {\n if (deep) logDeepScanRecommendation();\n const { html: fetchedHtml, finalUrl } = await fetchHtml(\n targetUrl,\n userAgent,\n timeoutMs\n );\n html = fetchedHtml;\n const scriptUrls = extractScriptUrls(fetchedHtml, finalUrl);\n const { chunks: fetchedChunks, scriptBytes } = await fetchScripts(\n scriptUrls,\n origin,\n userAgent\n );\n chunks = fetchedChunks;\n totalPageSize = byteLength(fetchedHtml) + scriptBytes;\n }\n\n const htmlSize = byteLength(html);\n\n const events: ScanEvent[] = [];\n const localesSet = new Set<string>();\n\n const { langTag } = checkHtmlAttributes(html, targetUrl, events);\n checkCanonical(html, targetUrl, events);\n checkLinguisticStructure(html, targetUrl, localesSet, events);\n checkUrlStructure(extractAnchors(html), origin, targetUrl, events);\n const bundle = checkBundleContent(\n chunks,\n html,\n langTag,\n targetUrl,\n totalPageSize,\n events\n );\n await checkRobots(origin, localesSet, userAgent, events);\n await checkSitemap(origin, localesSet, userAgent, events);\n\n const rawScore = events.reduce<Score>(\n (score, event) => mutateScore(score, event),\n { score: 0, totalScore: 0 }\n );\n\n return {\n url: targetUrl,\n mode,\n totalPageSize,\n htmlSize,\n score: toScorePercent(rawScore),\n rawScore,\n events,\n locales: Array.from(localesSet),\n bundle,\n };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"scanWebsite.cjs","names":["colorize","GREY","GREY_LIGHT","extractScriptUrls","byteLength","checkHtmlAttributes","extractAnchors","checkBundleContent","checkRobots","checkSitemap","mutateScore","toScorePercent"],"sources":["../../../src/scan/scanWebsite.ts"],"sourcesContent":["import { GREY, GREY_LIGHT } from '@intlayer/config/colors';\nimport { colorize, logger } from '@intlayer/config/logger';\nimport { mutateScore, type Score, toScorePercent } from './calculateScore';\nimport {\n checkBundleContent,\n checkCanonical,\n checkHtmlAttributes,\n checkLinguisticStructure,\n checkRobots,\n checkSitemap,\n checkUrlStructure,\n} from './checks';\nimport { byteLength, extractAnchors, extractScriptUrls } from './parseHtml';\nimport type {\n BundleChunkInput,\n ScanEvent,\n ScanOptions,\n ScanResult,\n} from './types';\n\nconst DEFAULT_USER_AGENT =\n 'Mozilla/5.0 (compatible; IntlayerScanBot/1.0; +https://intlayer.org)';\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\n/**\n * Log a recommendation to install `puppeteer` for a deeper scan. Mirrors the\n * style used by other optional-dependency hints across the CLI.\n */\nconst logDeepScanRecommendation = (): void => {\n logger([\n colorize('Recommended: Install', GREY),\n colorize('puppeteer', GREY_LIGHT),\n colorize(\n 'package to enable a deeper scan (renders client-side content & lazy-loaded chunks). See documentation:',\n GREY\n ),\n colorize('https://intlayer.org/doc/concept/cli#scan', GREY_LIGHT),\n ]);\n};\n\n/** Outcome of a render-based deep scan. */\ntype DeepScanResult = {\n html: string;\n totalPageSize: number;\n chunks: BundleChunkInput[];\n};\n\n/**\n * Render the page with a locally installed `puppeteer` to capture\n * client-rendered content, the accurate transfer size, and lazy-loaded chunks.\n *\n * `puppeteer` is imported dynamically through a non-literal specifier so it is\n * never bundled and stays an optional dependency: when it is absent the import\n * rejects and the caller falls back to the basic scan.\n *\n * @returns The deep-scan result, or `null` when `puppeteer` is unavailable.\n */\nconst runDeepScan = async (\n targetUrl: string,\n userAgent: string,\n timeoutMs: number\n): Promise<DeepScanResult | null> => {\n const moduleName = 'puppeteer';\n // Optional dependency resolved at runtime — typed as `any` because it is not\n // a declared dependency of this package.\n let puppeteer: any;\n try {\n const mod: any = await import(moduleName);\n puppeteer = mod.default ?? mod;\n } catch {\n return null;\n }\n\n let browser: any;\n try {\n browser = await puppeteer.launch({\n headless: true,\n executablePath: process.env.PUPPETEER_EXECUTABLE_PATH,\n args: [\n '--no-sandbox',\n '--disable-setuid-sandbox',\n '--disable-dev-shm-usage',\n '--disable-gpu',\n ],\n });\n\n const page = await browser.newPage();\n await page.setUserAgent(userAgent);\n await page.setExtraHTTPHeaders({ 'Accept-Language': 'en-US,en;q=0.9' });\n\n const origin = new URL(targetUrl).origin;\n const jsResponseMap = new Map<string, string>();\n let totalPageSize = 0;\n const pendingResponses: Promise<void>[] = [];\n\n page.on('response', (response: any) => {\n pendingResponses.push(\n (async () => {\n try {\n if (response.status() !== 200) return;\n const buffer = await response.buffer();\n totalPageSize += buffer.length;\n\n const responseUrl: string = response.url();\n const contentType: string =\n response.headers()['content-type'] ?? '';\n const isJavaScript =\n contentType.includes('javascript') ||\n /\\.(js|mjs|cjs)(\\?|$)/.test(responseUrl);\n if (isJavaScript && responseUrl.startsWith(origin)) {\n jsResponseMap.set(responseUrl, buffer.toString('utf-8'));\n }\n } catch {\n /* response already consumed or aborted */\n }\n })()\n );\n });\n\n await page.goto(targetUrl, {\n waitUntil: 'domcontentloaded',\n timeout: timeoutMs,\n });\n await page\n .waitForNetworkIdle({ idleTime: 1000, timeout: 10_000 })\n .catch(() => {\n /* ok if it never fully idles */\n });\n await Promise.allSettled(pendingResponses);\n\n const html: string = await page.content();\n const mainBundleUrls = new Set(extractScriptUrls(html, targetUrl));\n const chunks: BundleChunkInput[] = Array.from(jsResponseMap.entries()).map(\n ([url, content]) => ({\n url,\n isMainBundle: mainBundleUrls.has(url),\n content,\n })\n );\n\n return { html, totalPageSize, chunks };\n } finally {\n if (browser) await browser.close();\n }\n};\n\n/** Fetch the raw HTML document, measuring its byte size. */\nconst fetchHtml = async (\n url: string,\n userAgent: string,\n timeoutMs: number\n): Promise<{ html: string; finalUrl: string }> => {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const response = await fetch(url, {\n headers: { 'User-Agent': userAgent, 'Accept-Language': 'en-US,en;q=0.9' },\n signal: controller.signal,\n });\n const html = await response.text();\n return { html, finalUrl: response.url || url };\n } finally {\n clearTimeout(timer);\n }\n};\n\n/**\n * Fetch every eagerly-loaded script. Same-origin scripts keep their content so\n * their locale weight can be analyzed; third-party scripts only contribute to\n * the measured page size (their locale-like keys cause false positives).\n */\nconst fetchScripts = async (\n scriptUrls: string[],\n origin: string,\n userAgent: string\n): Promise<{ chunks: BundleChunkInput[]; scriptBytes: number }> => {\n const chunks: BundleChunkInput[] = [];\n let scriptBytes = 0;\n\n await Promise.all(\n scriptUrls.map(async (scriptUrl) => {\n try {\n const response = await fetch(scriptUrl, {\n headers: { 'User-Agent': userAgent },\n });\n if (!response.ok) return;\n const content = await response.text();\n scriptBytes += byteLength(content);\n if (scriptUrl.startsWith(origin)) {\n chunks.push({ url: scriptUrl, isMainBundle: true, content });\n }\n } catch {\n /* ignore unreachable scripts */\n }\n })\n );\n\n return { chunks, scriptBytes };\n};\n\n/**\n * Scan a single web page for i18n/SEO health and bundle weight.\n *\n * In `deep` mode (default) the page is rendered with a locally installed\n * `puppeteer`; when `puppeteer` is missing the scan transparently falls back to\n * a `basic` fetch-based pass and logs a recommendation to install it.\n *\n * @param targetUrl - The absolute URL to scan.\n * @param options - {@link ScanOptions} controlling depth, timeout and UA.\n * @returns The {@link ScanResult} including score, page size and per-check events.\n */\nexport const scanWebsite = async (\n targetUrl: string,\n options: ScanOptions = {}\n): Promise<ScanResult> => {\n const {\n deep = true,\n timeoutMs = DEFAULT_TIMEOUT_MS,\n userAgent = DEFAULT_USER_AGENT,\n } = options;\n\n const origin = new URL(targetUrl).origin;\n\n let mode: ScanResult['mode'] = 'basic';\n let html: string;\n let totalPageSize: number;\n let chunks: BundleChunkInput[];\n\n const deepResult = deep\n ? await runDeepScan(targetUrl, userAgent, timeoutMs)\n : null;\n\n if (deepResult) {\n mode = 'deep';\n html = deepResult.html;\n chunks = deepResult.chunks;\n totalPageSize = deepResult.totalPageSize;\n } else {\n if (deep) logDeepScanRecommendation();\n const { html: fetchedHtml, finalUrl } = await fetchHtml(\n targetUrl,\n userAgent,\n timeoutMs\n );\n html = fetchedHtml;\n const scriptUrls = extractScriptUrls(fetchedHtml, finalUrl);\n const { chunks: fetchedChunks, scriptBytes } = await fetchScripts(\n scriptUrls,\n origin,\n userAgent\n );\n chunks = fetchedChunks;\n totalPageSize = byteLength(fetchedHtml) + scriptBytes;\n }\n\n const htmlSize = byteLength(html);\n\n const events: ScanEvent[] = [];\n const localesSet = new Set<string>();\n\n const { langTag } = checkHtmlAttributes(html, targetUrl, events);\n checkCanonical(html, targetUrl, events);\n checkLinguisticStructure(html, targetUrl, localesSet, events);\n checkUrlStructure(extractAnchors(html), origin, targetUrl, events);\n const bundle = checkBundleContent(\n chunks,\n html,\n langTag,\n targetUrl,\n totalPageSize,\n events\n );\n await checkRobots(origin, localesSet, userAgent, events);\n await checkSitemap(origin, localesSet, userAgent, events);\n\n const rawScore = events.reduce<Score>(\n (score, event) => mutateScore(score, event),\n { score: 0, totalScore: 0 }\n );\n\n return {\n url: targetUrl,\n mode,\n totalPageSize,\n htmlSize,\n score: toScorePercent(rawScore),\n rawScore,\n events,\n locales: Array.from(localesSet),\n bundle,\n };\n};\n"],"mappings":"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"}
|
|
@@ -37,7 +37,8 @@ const createFileAdapter = (options) => {
|
|
|
37
37
|
const globPatternLocale = await (0, _intlayer_config_utils.parseFilePathPattern)(source, require_syncPluginKit_filePathPatternHelpers.buildFilePathPatternContext("**", locale));
|
|
38
38
|
const maskPatternLocale = await (0, _intlayer_config_utils.parseFilePathPattern)(source, require_syncPluginKit_filePathPatternHelpers.buildFilePathPatternContext("{{__KEY__}}", locale));
|
|
39
39
|
if (!globPatternLocale || !maskPatternLocale) continue;
|
|
40
|
-
const
|
|
40
|
+
const normalizedGlobPattern = globPatternLocale.startsWith("./") ? globPatternLocale.slice(2) : globPatternLocale;
|
|
41
|
+
const files = await (0, fast_glob.default)(normalizedGlobPattern, { cwd: baseDir });
|
|
41
42
|
const hasLocaleInMask = maskPatternLocale.includes("{{__LOCALE__}}");
|
|
42
43
|
const hasKeyInMask = maskPatternLocale.includes("{{__KEY__}}");
|
|
43
44
|
for (const file of files) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createFileAdapter.cjs","names":["buildFilePathPatternContext","extractKeyAndLocaleFromPath","writeFileIfChanged"],"sources":["../../../src/syncPluginKit/createFileAdapter.ts"],"sourcesContent":["import { mkdir, readFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, resolve } from 'node:path';\nimport { parseFilePathPattern } from '@intlayer/config/utils';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { FilePathPattern } from '@intlayer/types/filePathPattern';\nimport fg from 'fast-glob';\nimport { writeFileIfChanged } from '../writeFileIfChanged';\nimport { extractKeyAndLocaleFromPath } from './extractKeyAndLocaleFromPath';\nimport { buildFilePathPatternContext } from './filePathPatternHelpers';\nimport type {\n ContentAdapter,\n ContentEntry,\n FormatCodec,\n SyncContent,\n SyncPluginContext,\n} from './types';\n\n/**\n * Discovery strategy of the file adapter:\n * - `strict`: keep only files whose path rebuilds identically from the source\n * pattern, and fabricate entries for missing locales/keys so every declared\n * locale has a write-back target (sync plugins).\n * - `inclusive`: keep every glob match without fabricating missing entries\n * (read-only load plugins, whose patterns may contain free `**` globs).\n */\nexport type FileAdapterDiscovery = 'strict' | 'inclusive';\n\nexport type CreateFileAdapterOptions = {\n /**\n * Location of the files, as a static string, a templated string\n * (e.g. `./messages/{{locale}}/{{key}}.json`) or a function building the\n * path from the key and locale.\n */\n source: FilePathPattern;\n\n /**\n * Format of the files: string payload ↔ structured content.\n */\n codec: FormatCodec;\n\n /**\n * Optional override of how one file is read and parsed. Defaults to a plain\n * utf-8 read followed by `codec.parse`, returning `undefined` for missing or\n * unreadable files. Adapters needing richer loading (e.g. JSON5 or\n * transpiled TS sources) provide their own implementation here.\n */\n readEntry?: (\n absoluteFilePath: string,\n context: SyncPluginContext\n ) => Promise<SyncContent | undefined>;\n\n /**\n * Discovery strategy. Defaults to `'strict'`.\n */\n discovery?: FileAdapterDiscovery;\n};\n\ntype FilePath = string;\ntype MessagesRecord = Record<Locale, Record<string, FilePath>>;\n\n/**\n * Create a filesystem {@link ContentAdapter} for `createSyncPlugin`.\n *\n * Handles glob discovery of the source pattern, key/locale extraction from\n * paths, base-directory resolution, and change-detected atomic writes.\n */\nexport const createFileAdapter = (\n options: CreateFileAdapterOptions\n): ContentAdapter => {\n const { source, codec, discovery = 'strict' } = options;\n const readEntry = options.readEntry ?? createFileReader(codec.parse);\n\n let patternMarkerPromise: Promise<string> | undefined;\n\n /**\n * Source pattern with `{{key}}` / `{{locale}}` markers kept verbatim,\n * memoized because pattern functions can be asynchronous.\n */\n const getPatternMarker = () => {\n patternMarkerPromise ??= parseFilePathPattern(\n source,\n buildFilePathPatternContext('{{key}}', '{{locale}}')\n );\n\n return patternMarkerPromise;\n };\n\n const listMessages = async (\n context: SyncPluginContext\n ): Promise<MessagesRecord> => {\n const { system, internationalization } = context.configuration;\n const { baseDir } = system;\n const { locales } = internationalization;\n\n const result: MessagesRecord = {} as MessagesRecord;\n\n for (const locale of locales) {\n const globPatternLocale = await parseFilePathPattern(\n source,\n buildFilePathPatternContext('**', locale)\n );\n\n const maskPatternLocale = await parseFilePathPattern(\n source,\n buildFilePathPatternContext('{{__KEY__}}', locale)\n );\n\n if (!globPatternLocale || !maskPatternLocale) {\n continue;\n }\n\n const normalizedGlobPattern = globPatternLocale.startsWith('./')\n ? globPatternLocale.slice(2)\n : globPatternLocale;\n\n const files = await fg(normalizedGlobPattern, {\n cwd: baseDir,\n });\n\n const hasLocaleInMask = maskPatternLocale.includes('{{__LOCALE__}}');\n const hasKeyInMask = maskPatternLocale.includes('{{__KEY__}}');\n\n for (const file of files) {\n let key: string;\n let extractedLocale: Locale;\n\n if (hasLocaleInMask || hasKeyInMask) {\n const extraction = extractKeyAndLocaleFromPath(\n file,\n maskPatternLocale,\n locales,\n locale\n );\n\n if (!extraction) {\n continue;\n }\n\n key = extraction.key;\n extractedLocale = extraction.locale;\n } else if (discovery === 'inclusive') {\n // Mask has no placeholders — the file was found via a concrete\n // locale glob. Attribute it directly to the current loop locale.\n key = 'index';\n extractedLocale = locale;\n } else {\n // Strict mode relies on the fabrication step below to attribute\n // placeholder-free sources to their canonical paths.\n continue;\n }\n\n const absoluteFoundPath = isAbsolute(file)\n ? file\n : resolve(baseDir, file);\n\n if (discovery === 'strict') {\n // Rebuild what the path SHOULD be for this key/locale. A mismatch\n // means the file belongs to another plugin/structure.\n const expectedPath = await parseFilePathPattern(\n source,\n buildFilePathPatternContext(key, extractedLocale)\n );\n\n const absoluteExpectedPath = isAbsolute(expectedPath)\n ? expectedPath\n : resolve(baseDir, expectedPath);\n\n if (absoluteFoundPath !== absoluteExpectedPath) {\n continue;\n }\n }\n\n result[extractedLocale] ??= {};\n result[extractedLocale][key] = absoluteFoundPath;\n }\n }\n\n if (discovery === 'inclusive') {\n // Read-only mode only uses actual discovered files; do not fabricate\n // missing locales or keys, since no outputs are written.\n return result;\n }\n\n // Ensure all declared locales are present even if the file doesn't exist yet\n const maskWithKey = await parseFilePathPattern(\n source,\n buildFilePathPatternContext('{{__KEY__}}', locales[0])\n );\n\n const hasKeyInMask = maskWithKey.includes('{{__KEY__}}');\n const discoveredKeys = new Set<string>();\n\n for (const locale of Object.keys(result) as Locale[]) {\n for (const key of Object.keys(result[locale] ?? {})) {\n discoveredKeys.add(key);\n }\n }\n\n if (!hasKeyInMask) {\n discoveredKeys.add('index');\n }\n\n for (const locale of locales) {\n result[locale] ??= {};\n\n for (const key of discoveredKeys) {\n if (!result[locale][key]) {\n const builtPath = await parseFilePathPattern(\n source,\n buildFilePathPatternContext(key, locale)\n );\n\n result[locale][key] = isAbsolute(builtPath)\n ? builtPath\n : resolve(baseDir, builtPath);\n }\n }\n }\n\n return result;\n };\n\n return {\n list: async (context) => {\n const messages = await listMessages(context);\n\n const entries: ContentEntry[] = (\n Object.entries(messages) as [Locale, Record<string, FilePath>][]\n ).flatMap(([locale, keysRecord]) =>\n Object.entries(keysRecord).map(([key, absoluteFilePath]) => ({\n key,\n locale,\n uri: absoluteFilePath,\n filePath: absoluteFilePath,\n }))\n );\n\n return entries;\n },\n\n read: async (entry, context) => await readEntry(entry.uri, context),\n\n write: async ({ key, locale }, content, { configuration }) => {\n const builderPath = await parseFilePathPattern(\n source,\n buildFilePathPatternContext(key, locale)\n );\n\n const absoluteFilePath = resolve(\n configuration.system.baseDir,\n builderPath\n );\n\n await mkdir(dirname(absoluteFilePath), { recursive: true });\n\n await writeFileIfChanged(\n absoluteFilePath,\n codec.serialize(content, { locale }),\n { tempDir: configuration.system?.tempDir }\n );\n },\n\n resolveUri: async ({ key, locale }, { configuration }) => {\n const builderPath = await parseFilePathPattern(\n source,\n buildFilePathPatternContext(key, locale)\n );\n\n return resolve(configuration.system.baseDir, builderPath);\n },\n\n getFillPattern: async () => await getPatternMarker(),\n\n describeSource: async () => await getPatternMarker(),\n\n detectSplitKeys: async () =>\n !(await getPatternMarker()).includes('{{key}}'),\n };\n};\n\n/**\n * Default `readEntry` implementation: plain utf-8 read followed by the given\n * parser. Returns `undefined` for missing or unreadable files.\n */\nexport const createFileReader =\n (parse: (raw: string) => SyncContent) =>\n async (absoluteFilePath: string): Promise<SyncContent | undefined> => {\n try {\n const raw = await readFile(absoluteFilePath, 'utf-8');\n\n return parse(raw);\n } catch {\n return undefined;\n }\n };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"createFileAdapter.cjs","names":["parseFilePathPattern","buildFilePathPatternContext","fg","extractKeyAndLocaleFromPath","isAbsolute","resolve","mkdir","dirname","writeFileIfChanged","readFile"],"sources":["../../../src/syncPluginKit/createFileAdapter.ts"],"sourcesContent":["import { mkdir, readFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, resolve } from 'node:path';\nimport { parseFilePathPattern } from '@intlayer/config/utils';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { FilePathPattern } from '@intlayer/types/filePathPattern';\nimport fg from 'fast-glob';\nimport { writeFileIfChanged } from '../writeFileIfChanged';\nimport { extractKeyAndLocaleFromPath } from './extractKeyAndLocaleFromPath';\nimport { buildFilePathPatternContext } from './filePathPatternHelpers';\nimport type {\n ContentAdapter,\n ContentEntry,\n FormatCodec,\n SyncContent,\n SyncPluginContext,\n} from './types';\n\n/**\n * Discovery strategy of the file adapter:\n * - `strict`: keep only files whose path rebuilds identically from the source\n * pattern, and fabricate entries for missing locales/keys so every declared\n * locale has a write-back target (sync plugins).\n * - `inclusive`: keep every glob match without fabricating missing entries\n * (read-only load plugins, whose patterns may contain free `**` globs).\n */\nexport type FileAdapterDiscovery = 'strict' | 'inclusive';\n\nexport type CreateFileAdapterOptions = {\n /**\n * Location of the files, as a static string, a templated string\n * (e.g. `./messages/{{locale}}/{{key}}.json`) or a function building the\n * path from the key and locale.\n */\n source: FilePathPattern;\n\n /**\n * Format of the files: string payload ↔ structured content.\n */\n codec: FormatCodec;\n\n /**\n * Optional override of how one file is read and parsed. Defaults to a plain\n * utf-8 read followed by `codec.parse`, returning `undefined` for missing or\n * unreadable files. Adapters needing richer loading (e.g. JSON5 or\n * transpiled TS sources) provide their own implementation here.\n */\n readEntry?: (\n absoluteFilePath: string,\n context: SyncPluginContext\n ) => Promise<SyncContent | undefined>;\n\n /**\n * Discovery strategy. Defaults to `'strict'`.\n */\n discovery?: FileAdapterDiscovery;\n};\n\ntype FilePath = string;\ntype MessagesRecord = Record<Locale, Record<string, FilePath>>;\n\n/**\n * Create a filesystem {@link ContentAdapter} for `createSyncPlugin`.\n *\n * Handles glob discovery of the source pattern, key/locale extraction from\n * paths, base-directory resolution, and change-detected atomic writes.\n */\nexport const createFileAdapter = (\n options: CreateFileAdapterOptions\n): ContentAdapter => {\n const { source, codec, discovery = 'strict' } = options;\n const readEntry = options.readEntry ?? createFileReader(codec.parse);\n\n let patternMarkerPromise: Promise<string> | undefined;\n\n /**\n * Source pattern with `{{key}}` / `{{locale}}` markers kept verbatim,\n * memoized because pattern functions can be asynchronous.\n */\n const getPatternMarker = () => {\n patternMarkerPromise ??= parseFilePathPattern(\n source,\n buildFilePathPatternContext('{{key}}', '{{locale}}')\n );\n\n return patternMarkerPromise;\n };\n\n const listMessages = async (\n context: SyncPluginContext\n ): Promise<MessagesRecord> => {\n const { system, internationalization } = context.configuration;\n const { baseDir } = system;\n const { locales } = internationalization;\n\n const result: MessagesRecord = {} as MessagesRecord;\n\n for (const locale of locales) {\n const globPatternLocale = await parseFilePathPattern(\n source,\n buildFilePathPatternContext('**', locale)\n );\n\n const maskPatternLocale = await parseFilePathPattern(\n source,\n buildFilePathPatternContext('{{__KEY__}}', locale)\n );\n\n if (!globPatternLocale || !maskPatternLocale) {\n continue;\n }\n\n const normalizedGlobPattern = globPatternLocale.startsWith('./')\n ? globPatternLocale.slice(2)\n : globPatternLocale;\n\n const files = await fg(normalizedGlobPattern, {\n cwd: baseDir,\n });\n\n const hasLocaleInMask = maskPatternLocale.includes('{{__LOCALE__}}');\n const hasKeyInMask = maskPatternLocale.includes('{{__KEY__}}');\n\n for (const file of files) {\n let key: string;\n let extractedLocale: Locale;\n\n if (hasLocaleInMask || hasKeyInMask) {\n const extraction = extractKeyAndLocaleFromPath(\n file,\n maskPatternLocale,\n locales,\n locale\n );\n\n if (!extraction) {\n continue;\n }\n\n key = extraction.key;\n extractedLocale = extraction.locale;\n } else if (discovery === 'inclusive') {\n // Mask has no placeholders — the file was found via a concrete\n // locale glob. Attribute it directly to the current loop locale.\n key = 'index';\n extractedLocale = locale;\n } else {\n // Strict mode relies on the fabrication step below to attribute\n // placeholder-free sources to their canonical paths.\n continue;\n }\n\n const absoluteFoundPath = isAbsolute(file)\n ? file\n : resolve(baseDir, file);\n\n if (discovery === 'strict') {\n // Rebuild what the path SHOULD be for this key/locale. A mismatch\n // means the file belongs to another plugin/structure.\n const expectedPath = await parseFilePathPattern(\n source,\n buildFilePathPatternContext(key, extractedLocale)\n );\n\n const absoluteExpectedPath = isAbsolute(expectedPath)\n ? expectedPath\n : resolve(baseDir, expectedPath);\n\n if (absoluteFoundPath !== absoluteExpectedPath) {\n continue;\n }\n }\n\n result[extractedLocale] ??= {};\n result[extractedLocale][key] = absoluteFoundPath;\n }\n }\n\n if (discovery === 'inclusive') {\n // Read-only mode only uses actual discovered files; do not fabricate\n // missing locales or keys, since no outputs are written.\n return result;\n }\n\n // Ensure all declared locales are present even if the file doesn't exist yet\n const maskWithKey = await parseFilePathPattern(\n source,\n buildFilePathPatternContext('{{__KEY__}}', locales[0])\n );\n\n const hasKeyInMask = maskWithKey.includes('{{__KEY__}}');\n const discoveredKeys = new Set<string>();\n\n for (const locale of Object.keys(result) as Locale[]) {\n for (const key of Object.keys(result[locale] ?? {})) {\n discoveredKeys.add(key);\n }\n }\n\n if (!hasKeyInMask) {\n discoveredKeys.add('index');\n }\n\n for (const locale of locales) {\n result[locale] ??= {};\n\n for (const key of discoveredKeys) {\n if (!result[locale][key]) {\n const builtPath = await parseFilePathPattern(\n source,\n buildFilePathPatternContext(key, locale)\n );\n\n result[locale][key] = isAbsolute(builtPath)\n ? builtPath\n : resolve(baseDir, builtPath);\n }\n }\n }\n\n return result;\n };\n\n return {\n list: async (context) => {\n const messages = await listMessages(context);\n\n const entries: ContentEntry[] = (\n Object.entries(messages) as [Locale, Record<string, FilePath>][]\n ).flatMap(([locale, keysRecord]) =>\n Object.entries(keysRecord).map(([key, absoluteFilePath]) => ({\n key,\n locale,\n uri: absoluteFilePath,\n filePath: absoluteFilePath,\n }))\n );\n\n return entries;\n },\n\n read: async (entry, context) => await readEntry(entry.uri, context),\n\n write: async ({ key, locale }, content, { configuration }) => {\n const builderPath = await parseFilePathPattern(\n source,\n buildFilePathPatternContext(key, locale)\n );\n\n const absoluteFilePath = resolve(\n configuration.system.baseDir,\n builderPath\n );\n\n await mkdir(dirname(absoluteFilePath), { recursive: true });\n\n await writeFileIfChanged(\n absoluteFilePath,\n codec.serialize(content, { locale }),\n { tempDir: configuration.system?.tempDir }\n );\n },\n\n resolveUri: async ({ key, locale }, { configuration }) => {\n const builderPath = await parseFilePathPattern(\n source,\n buildFilePathPatternContext(key, locale)\n );\n\n return resolve(configuration.system.baseDir, builderPath);\n },\n\n getFillPattern: async () => await getPatternMarker(),\n\n describeSource: async () => await getPatternMarker(),\n\n detectSplitKeys: async () =>\n !(await getPatternMarker()).includes('{{key}}'),\n };\n};\n\n/**\n * Default `readEntry` implementation: plain utf-8 read followed by the given\n * parser. Returns `undefined` for missing or unreadable files.\n */\nexport const createFileReader =\n (parse: (raw: string) => SyncContent) =>\n async (absoluteFilePath: string): Promise<SyncContent | undefined> => {\n try {\n const raw = await readFile(absoluteFilePath, 'utf-8');\n\n return parse(raw);\n } catch {\n return undefined;\n }\n };\n"],"mappings":"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"}
|
|
@@ -46,7 +46,10 @@ const createSyncPlugin = (options) => {
|
|
|
46
46
|
const { baseDir } = configuration.system;
|
|
47
47
|
const { defaultLocale } = configuration.internationalization;
|
|
48
48
|
let patternFill;
|
|
49
|
-
if (direction === "both" && adapter.getFillPattern)
|
|
49
|
+
if (direction === "both" && adapter.getFillPattern) {
|
|
50
|
+
const fillPattern = await adapter.getFillPattern(context);
|
|
51
|
+
patternFill = (0, node_path.relative)(baseDir, (0, node_path.resolve)(baseDir, fillPattern));
|
|
52
|
+
}
|
|
50
53
|
const dictionaries = [];
|
|
51
54
|
for (const entry of entries) {
|
|
52
55
|
const content = await adapter.read(entry, context) ?? {};
|