@intlayer/engine 9.4.3 → 9.5.0
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 +0 -2
- package/dist/cjs/createDictionaryEntryPoint/generateDictionaryListContent.cjs +1 -2
- package/dist/cjs/createDictionaryEntryPoint/generateDictionaryListContent.cjs.map +1 -1
- package/dist/cjs/init/index.cjs +14 -11
- package/dist/cjs/init/index.cjs.map +1 -1
- package/dist/cjs/init/utils/devScript.cjs +57 -0
- package/dist/cjs/init/utils/devScript.cjs.map +1 -0
- package/dist/cjs/init/utils/index.cjs +3 -0
- package/dist/cjs/loadDictionaries/loadContentDeclaration.cjs +1 -1
- package/dist/cjs/loadDictionaries/loadContentDeclaration.cjs.map +1 -1
- package/dist/cjs/loadDictionaries/loadMarkdownContentDeclaration.cjs +6 -5
- package/dist/cjs/loadDictionaries/loadMarkdownContentDeclaration.cjs.map +1 -1
- package/dist/cjs/syncPluginKit/createSyncPlugin.cjs +51 -3
- package/dist/cjs/syncPluginKit/createSyncPlugin.cjs.map +1 -1
- package/dist/cjs/utils/contentWatcherLock.cjs +173 -0
- package/dist/cjs/utils/contentWatcherLock.cjs.map +1 -0
- package/dist/cjs/utils/index.cjs +13 -1
- package/dist/cjs/utils/parseContentDeclarationFileName.cjs +35 -0
- package/dist/cjs/utils/parseContentDeclarationFileName.cjs.map +1 -0
- package/dist/cjs/utils/withFileLock.cjs +133 -0
- package/dist/cjs/utils/withFileLock.cjs.map +1 -0
- package/dist/cjs/watcher.cjs +7 -3
- package/dist/cjs/watcher.cjs.map +1 -1
- package/dist/esm/createDictionaryEntryPoint/generateDictionaryListContent.mjs +1 -2
- package/dist/esm/createDictionaryEntryPoint/generateDictionaryListContent.mjs.map +1 -1
- package/dist/esm/init/index.mjs +14 -11
- package/dist/esm/init/index.mjs.map +1 -1
- package/dist/esm/init/utils/devScript.mjs +55 -0
- package/dist/esm/init/utils/devScript.mjs.map +1 -0
- package/dist/esm/init/utils/index.mjs +2 -1
- package/dist/esm/loadDictionaries/loadContentDeclaration.mjs +1 -1
- package/dist/esm/loadDictionaries/loadContentDeclaration.mjs.map +1 -1
- package/dist/esm/loadDictionaries/loadMarkdownContentDeclaration.mjs +6 -5
- package/dist/esm/loadDictionaries/loadMarkdownContentDeclaration.mjs.map +1 -1
- package/dist/esm/syncPluginKit/createSyncPlugin.mjs +51 -3
- package/dist/esm/syncPluginKit/createSyncPlugin.mjs.map +1 -1
- package/dist/esm/utils/contentWatcherLock.mjs +164 -0
- package/dist/esm/utils/contentWatcherLock.mjs.map +1 -0
- package/dist/esm/utils/index.mjs +4 -1
- package/dist/esm/utils/parseContentDeclarationFileName.mjs +34 -0
- package/dist/esm/utils/parseContentDeclarationFileName.mjs.map +1 -0
- package/dist/esm/utils/withFileLock.mjs +132 -0
- package/dist/esm/utils/withFileLock.mjs.map +1 -0
- package/dist/esm/watcher.mjs +8 -4
- package/dist/esm/watcher.mjs.map +1 -1
- package/dist/types/buildIntlayerDictionary/buildIntlayerDictionary.d.ts +2 -3
- package/dist/types/buildIntlayerDictionary/buildIntlayerDictionary.d.ts.map +1 -1
- package/dist/types/buildIntlayerDictionary/processContentDeclaration.d.ts +1 -2
- package/dist/types/buildIntlayerDictionary/processContentDeclaration.d.ts.map +1 -1
- package/dist/types/buildIntlayerDictionary/writeDynamicDictionary.d.ts +10 -11
- package/dist/types/buildIntlayerDictionary/writeDynamicDictionary.d.ts.map +1 -1
- package/dist/types/buildIntlayerDictionary/writeFetchDictionary.d.ts +2 -3
- package/dist/types/buildIntlayerDictionary/writeFetchDictionary.d.ts.map +1 -1
- package/dist/types/buildIntlayerDictionary/writeMergedDictionary.d.ts +5 -6
- package/dist/types/buildIntlayerDictionary/writeMergedDictionary.d.ts.map +1 -1
- package/dist/types/buildIntlayerDictionary/writeNestedDictionary.d.ts +4 -5
- package/dist/types/buildIntlayerDictionary/writeNestedDictionary.d.ts.map +1 -1
- package/dist/types/buildIntlayerDictionary/writeRemoteDictionary.d.ts +3 -4
- package/dist/types/buildIntlayerDictionary/writeRemoteDictionary.d.ts.map +1 -1
- package/dist/types/buildIntlayerDictionary/writeUnmergedDictionary.d.ts +4 -5
- package/dist/types/buildIntlayerDictionary/writeUnmergedDictionary.d.ts.map +1 -1
- package/dist/types/cleanOutputDir.d.ts +1 -2
- package/dist/types/cleanOutputDir.d.ts.map +1 -1
- package/dist/types/cleanRemovedContentDeclaration.d.ts +1 -2
- package/dist/types/cleanRemovedContentDeclaration.d.ts.map +1 -1
- package/dist/types/createDictionaryEntryPoint/createDictionaryEntryPoint.d.ts +2 -3
- package/dist/types/createDictionaryEntryPoint/createDictionaryEntryPoint.d.ts.map +1 -1
- package/dist/types/createDictionaryEntryPoint/generateDictionaryListContent.d.ts +1 -2
- package/dist/types/createDictionaryEntryPoint/generateDictionaryListContent.d.ts.map +1 -1
- package/dist/types/createDictionaryEntryPoint/getBuiltDictionariesPath.d.ts +1 -2
- package/dist/types/createDictionaryEntryPoint/getBuiltDictionariesPath.d.ts.map +1 -1
- package/dist/types/createDictionaryEntryPoint/getBuiltDynamicDictionariesPath.d.ts +1 -2
- package/dist/types/createDictionaryEntryPoint/getBuiltDynamicDictionariesPath.d.ts.map +1 -1
- package/dist/types/createDictionaryEntryPoint/getBuiltFetchDictionariesPath.d.ts +1 -2
- package/dist/types/createDictionaryEntryPoint/getBuiltFetchDictionariesPath.d.ts.map +1 -1
- package/dist/types/createDictionaryEntryPoint/getBuiltRemoteDictionariesPath.d.ts +1 -2
- package/dist/types/createDictionaryEntryPoint/getBuiltRemoteDictionariesPath.d.ts.map +1 -1
- package/dist/types/createDictionaryEntryPoint/getBuiltUnmergedDictionariesPath.d.ts +1 -2
- package/dist/types/createDictionaryEntryPoint/getBuiltUnmergedDictionariesPath.d.ts.map +1 -1
- package/dist/types/createType/createModuleAugmentation.d.ts +4 -5
- package/dist/types/createType/createModuleAugmentation.d.ts.map +1 -1
- package/dist/types/createType/createType.d.ts +2 -3
- package/dist/types/createType/createType.d.ts.map +1 -1
- package/dist/types/detectFormatCommand.d.ts +1 -2
- package/dist/types/detectFormatCommand.d.ts.map +1 -1
- package/dist/types/docReview/alignBlocks.d.ts +1 -2
- package/dist/types/docReview/alignBlocks.d.ts.map +1 -1
- package/dist/types/docReview/computeSimilarity.d.ts +2 -3
- package/dist/types/docReview/computeSimilarity.d.ts.map +1 -1
- package/dist/types/docReview/fingerprintBlock.d.ts +1 -2
- package/dist/types/docReview/fingerprintBlock.d.ts.map +1 -1
- package/dist/types/docReview/mapChangedLinesToBlocks.d.ts +1 -2
- package/dist/types/docReview/mapChangedLinesToBlocks.d.ts.map +1 -1
- package/dist/types/docReview/normalizeBlock.d.ts +1 -2
- package/dist/types/docReview/normalizeBlock.d.ts.map +1 -1
- package/dist/types/docReview/pipeline.d.ts +4 -4
- package/dist/types/docReview/pipeline.d.ts.map +1 -1
- package/dist/types/docReview/planActions.d.ts +1 -2
- package/dist/types/docReview/planActions.d.ts.map +1 -1
- package/dist/types/docReview/rebuildDocument.d.ts +5 -6
- package/dist/types/docReview/rebuildDocument.d.ts.map +1 -1
- package/dist/types/docReview/reviewReport.d.ts +8 -9
- package/dist/types/docReview/reviewReport.d.ts.map +1 -1
- package/dist/types/docReview/segmentDocument.d.ts +2 -3
- package/dist/types/docReview/segmentDocument.d.ts.map +1 -1
- package/dist/types/docReview/types.d.ts +9 -10
- package/dist/types/docReview/types.d.ts.map +1 -1
- package/dist/types/fetchDistantDictionaries.d.ts +1 -2
- package/dist/types/fetchDistantDictionaries.d.ts.map +1 -1
- package/dist/types/filterInvalidDictionaries.d.ts +2 -3
- package/dist/types/filterInvalidDictionaries.d.ts.map +1 -1
- package/dist/types/formatDictionary.d.ts +6 -8
- package/dist/types/formatDictionary.d.ts.map +1 -1
- package/dist/types/getContentDeclarationFileTemplate/getContentDeclarationFileTemplate.d.ts +1 -2
- package/dist/types/getContentDeclarationFileTemplate/getContentDeclarationFileTemplate.d.ts.map +1 -1
- package/dist/types/handleAdditionalContentDeclarationFile.d.ts +1 -2
- package/dist/types/handleAdditionalContentDeclarationFile.d.ts.map +1 -1
- package/dist/types/handleContentDeclarationFileChange.d.ts +1 -2
- package/dist/types/handleContentDeclarationFileChange.d.ts.map +1 -1
- package/dist/types/handleContentDeclarationFileMoved.d.ts +1 -2
- package/dist/types/handleContentDeclarationFileMoved.d.ts.map +1 -1
- package/dist/types/handleUnlinkedContentDeclarationFile.d.ts +1 -2
- package/dist/types/handleUnlinkedContentDeclarationFile.d.ts.map +1 -1
- package/dist/types/init/cms.d.ts +8 -9
- package/dist/types/init/cms.d.ts.map +1 -1
- package/dist/types/init/frameworkSetup/index.d.ts +2 -2
- package/dist/types/init/frameworkSetup/index.d.ts.map +1 -1
- package/dist/types/init/frameworkSetup/nextAppRouter/detect.d.ts +4 -4
- package/dist/types/init/frameworkSetup/nextAppRouter/detect.d.ts.map +1 -1
- package/dist/types/init/frameworkSetup/nextAppRouter/index.d.ts +1 -2
- package/dist/types/init/frameworkSetup/nextAppRouter/index.d.ts.map +1 -1
- package/dist/types/init/frameworkSetup/nextAppRouter/restructure.d.ts +5 -6
- package/dist/types/init/frameworkSetup/nextAppRouter/restructure.d.ts.map +1 -1
- package/dist/types/init/frameworkSetup/nextAppRouter/templates.d.ts +8 -9
- package/dist/types/init/frameworkSetup/nextAppRouter/templates.d.ts.map +1 -1
- package/dist/types/init/frameworkSetup/nextAppRouter/transforms.d.ts +2 -3
- package/dist/types/init/frameworkSetup/nextAppRouter/transforms.d.ts.map +1 -1
- package/dist/types/init/frameworkSetup/tanstackStart/detect.d.ts +3 -4
- package/dist/types/init/frameworkSetup/tanstackStart/detect.d.ts.map +1 -1
- package/dist/types/init/frameworkSetup/tanstackStart/index.d.ts +1 -2
- package/dist/types/init/frameworkSetup/tanstackStart/index.d.ts.map +1 -1
- package/dist/types/init/frameworkSetup/tanstackStart/restructure.d.ts +7 -8
- package/dist/types/init/frameworkSetup/tanstackStart/restructure.d.ts.map +1 -1
- package/dist/types/init/frameworkSetup/tanstackStart/templates.d.ts +3 -4
- package/dist/types/init/frameworkSetup/tanstackStart/templates.d.ts.map +1 -1
- package/dist/types/init/frameworkSetup/tanstackStart/transforms.d.ts +2 -3
- package/dist/types/init/frameworkSetup/tanstackStart/transforms.d.ts.map +1 -1
- package/dist/types/init/frameworkSetup/types.d.ts +2 -3
- package/dist/types/init/frameworkSetup/types.d.ts.map +1 -1
- package/dist/types/init/index.d.ts +4 -5
- package/dist/types/init/index.d.ts.map +1 -1
- package/dist/types/init/utils/astImports.d.ts +3 -4
- package/dist/types/init/utils/astImports.d.ts.map +1 -1
- package/dist/types/init/utils/configManipulation.d.ts +19 -20
- package/dist/types/init/utils/configManipulation.d.ts.map +1 -1
- package/dist/types/init/utils/devScript.d.ts +49 -0
- package/dist/types/init/utils/devScript.d.ts.map +1 -0
- package/dist/types/init/utils/fileSystem.d.ts +11 -12
- package/dist/types/init/utils/fileSystem.d.ts.map +1 -1
- package/dist/types/init/utils/githubActions.d.ts +7 -8
- package/dist/types/init/utils/githubActions.d.ts.map +1 -1
- package/dist/types/init/utils/index.d.ts +2 -1
- package/dist/types/init/utils/jsonParser.d.ts +1 -2
- package/dist/types/init/utils/jsonParser.d.ts.map +1 -1
- package/dist/types/init/utils/nextCompilerBabel.d.ts +2 -3
- package/dist/types/init/utils/nextCompilerBabel.d.ts.map +1 -1
- package/dist/types/init/utils/packageManager.d.ts +17 -18
- package/dist/types/init/utils/packageManager.d.ts.map +1 -1
- package/dist/types/init/utils/tsConfig.d.ts +1 -2
- package/dist/types/init/utils/tsConfig.d.ts.map +1 -1
- package/dist/types/init/utils/version.d.ts +1 -2
- package/dist/types/init/utils/version.d.ts.map +1 -1
- package/dist/types/initConfig/index.d.ts +2 -3
- package/dist/types/initConfig/index.d.ts.map +1 -1
- package/dist/types/installLSP.d.ts +1 -2
- package/dist/types/installLSP.d.ts.map +1 -1
- package/dist/types/installMCP/installMCP.d.ts +2 -3
- package/dist/types/installMCP/installMCP.d.ts.map +1 -1
- package/dist/types/installSkills/index.d.ts +9 -10
- package/dist/types/installSkills/index.d.ts.map +1 -1
- package/dist/types/listDictionariesPath.d.ts +2 -3
- package/dist/types/listDictionariesPath.d.ts.map +1 -1
- package/dist/types/listGitFiles.d.ts +5 -6
- package/dist/types/listGitFiles.d.ts.map +1 -1
- package/dist/types/listProjects.d.ts +3 -4
- package/dist/types/listProjects.d.ts.map +1 -1
- package/dist/types/loadDictionaries/getIntlayerBundle.d.ts +1 -2
- package/dist/types/loadDictionaries/getIntlayerBundle.d.ts.map +1 -1
- package/dist/types/loadDictionaries/loadContentDeclaration.d.ts +4 -5
- package/dist/types/loadDictionaries/loadContentDeclaration.d.ts.map +1 -1
- package/dist/types/loadDictionaries/loadDictionaries.d.ts +2 -3
- package/dist/types/loadDictionaries/loadDictionaries.d.ts.map +1 -1
- package/dist/types/loadDictionaries/loadLocalDictionaries.d.ts +1 -2
- package/dist/types/loadDictionaries/loadLocalDictionaries.d.ts.map +1 -1
- package/dist/types/loadDictionaries/loadMarkdownContentDeclaration.d.ts +2 -2
- package/dist/types/loadDictionaries/loadMarkdownContentDeclaration.d.ts.map +1 -1
- package/dist/types/loadDictionaries/loadRemoteDictionaries.d.ts +2 -3
- package/dist/types/loadDictionaries/loadRemoteDictionaries.d.ts.map +1 -1
- package/dist/types/loadDictionaries/loadYamlContentDeclaration.d.ts +1 -2
- package/dist/types/loadDictionaries/loadYamlContentDeclaration.d.ts.map +1 -1
- package/dist/types/loadDictionaries/log.d.ts +1 -2
- package/dist/types/loadDictionaries/log.d.ts.map +1 -1
- package/dist/types/loadDictionaries/logTypeScriptErrors.d.ts +1 -2
- package/dist/types/loadDictionaries/logTypeScriptErrors.d.ts.map +1 -1
- package/dist/types/logConfigDetails.d.ts +1 -2
- package/dist/types/logConfigDetails.d.ts.map +1 -1
- package/dist/types/prepareIntlayer.d.ts +1 -2
- package/dist/types/prepareIntlayer.d.ts.map +1 -1
- package/dist/types/reduceDictionaryContent/applyMask.d.ts +1 -2
- package/dist/types/reduceDictionaryContent/applyMask.d.ts.map +1 -1
- package/dist/types/reduceDictionaryContent/reduceDictionaryContent.d.ts +1 -2
- package/dist/types/reduceDictionaryContent/reduceDictionaryContent.d.ts.map +1 -1
- package/dist/types/scan/analyzeBundleContent.d.ts +1 -2
- package/dist/types/scan/analyzeBundleContent.d.ts.map +1 -1
- package/dist/types/scan/calculateScore.d.ts +6 -7
- package/dist/types/scan/calculateScore.d.ts.map +1 -1
- package/dist/types/scan/checks.d.ts +8 -9
- package/dist/types/scan/checks.d.ts.map +1 -1
- package/dist/types/scan/parseHtml.d.ts +13 -14
- package/dist/types/scan/parseHtml.d.ts.map +1 -1
- package/dist/types/scan/scanWebsite.d.ts +1 -2
- package/dist/types/scan/scanWebsite.d.ts.map +1 -1
- package/dist/types/scan/types.d.ts +9 -10
- package/dist/types/scan/types.d.ts.map +1 -1
- package/dist/types/syncPluginKit/createFileAdapter.d.ts +4 -5
- package/dist/types/syncPluginKit/createFileAdapter.d.ts.map +1 -1
- package/dist/types/syncPluginKit/createSyncPlugin.d.ts +1 -2
- package/dist/types/syncPluginKit/createSyncPlugin.d.ts.map +1 -1
- package/dist/types/syncPluginKit/extractKeyAndLocaleFromPath.d.ts +1 -2
- package/dist/types/syncPluginKit/extractKeyAndLocaleFromPath.d.ts.map +1 -1
- package/dist/types/syncPluginKit/filePathPatternHelpers.d.ts +1 -2
- package/dist/types/syncPluginKit/filePathPatternHelpers.d.ts.map +1 -1
- package/dist/types/syncPluginKit/index.d.ts +2 -2
- package/dist/types/syncPluginKit/types.d.ts +28 -15
- package/dist/types/syncPluginKit/types.d.ts.map +1 -1
- package/dist/types/utils/autoDecorateContent.d.ts +1 -2
- package/dist/types/utils/autoDecorateContent.d.ts.map +1 -1
- package/dist/types/utils/babelParser.d.ts +3 -3
- package/dist/types/utils/babelParser.d.ts.map +1 -1
- package/dist/types/utils/buildComponentFilesList.d.ts +1 -2
- package/dist/types/utils/buildComponentFilesList.d.ts.map +1 -1
- package/dist/types/utils/buildFilesList.d.ts +3 -4
- package/dist/types/utils/buildFilesList.d.ts.map +1 -1
- package/dist/types/utils/chunkArray.d.ts +1 -2
- package/dist/types/utils/chunkArray.d.ts.map +1 -1
- package/dist/types/utils/chunkJSON.d.ts +5 -6
- package/dist/types/utils/chunkJSON.d.ts.map +1 -1
- package/dist/types/utils/contentWatcherLock.d.ts +113 -0
- package/dist/types/utils/contentWatcherLock.d.ts.map +1 -0
- package/dist/types/utils/formatter.d.ts +2 -3
- package/dist/types/utils/formatter.d.ts.map +1 -1
- package/dist/types/utils/getChunk.d.ts +1 -2
- package/dist/types/utils/getChunk.d.ts.map +1 -1
- package/dist/types/utils/getContentExtension.d.ts +1 -2
- package/dist/types/utils/getContentExtension.d.ts.map +1 -1
- package/dist/types/utils/getFormatFromExtension.d.ts +4 -5
- package/dist/types/utils/getFormatFromExtension.d.ts.map +1 -1
- package/dist/types/utils/getPathHash.d.ts +1 -2
- package/dist/types/utils/getPathHash.d.ts.map +1 -1
- package/dist/types/utils/index.d.ts +4 -1
- package/dist/types/utils/mergeChunks.d.ts +1 -2
- package/dist/types/utils/mergeChunks.d.ts.map +1 -1
- package/dist/types/utils/pLimit.d.ts +2 -3
- package/dist/types/utils/pLimit.d.ts.map +1 -1
- package/dist/types/utils/parallelize.d.ts +1 -2
- package/dist/types/utils/parallelize.d.ts.map +1 -1
- package/dist/types/utils/parallelizeGlobal.d.ts +3 -4
- package/dist/types/utils/parallelizeGlobal.d.ts.map +1 -1
- package/dist/types/utils/parseContentDeclarationFileName.d.ts +23 -0
- package/dist/types/utils/parseContentDeclarationFileName.d.ts.map +1 -0
- package/dist/types/utils/readDictionariesFromDisk.d.ts +1 -2
- package/dist/types/utils/readDictionariesFromDisk.d.ts.map +1 -1
- package/dist/types/utils/reduceObjectFormat.d.ts +4 -5
- package/dist/types/utils/reduceObjectFormat.d.ts.map +1 -1
- package/dist/types/utils/resolveObjectPromises.d.ts +1 -2
- package/dist/types/utils/resolveObjectPromises.d.ts.map +1 -1
- package/dist/types/utils/resolveRelativePath.d.ts +1 -2
- package/dist/types/utils/resolveRelativePath.d.ts.map +1 -1
- package/dist/types/utils/runOnce.d.ts +2 -3
- package/dist/types/utils/runOnce.d.ts.map +1 -1
- package/dist/types/utils/runParallel/bin.d.ts +1 -2
- package/dist/types/utils/runParallel/bin.d.ts.map +1 -1
- package/dist/types/utils/runParallel/bootstrap.d.ts +1 -2
- package/dist/types/utils/runParallel/bootstrap.d.ts.map +1 -1
- package/dist/types/utils/runParallel/index.d.ts +2 -3
- package/dist/types/utils/runParallel/index.d.ts.map +1 -1
- package/dist/types/utils/runParallel/pidTree.d.ts +1 -2
- package/dist/types/utils/runParallel/pidTree.d.ts.map +1 -1
- package/dist/types/utils/runParallel/ps.d.ts +1 -2
- package/dist/types/utils/runParallel/ps.d.ts.map +1 -1
- package/dist/types/utils/runParallel/runTask.d.ts +1 -2
- package/dist/types/utils/runParallel/runTask.d.ts.map +1 -1
- package/dist/types/utils/runParallel/spawnPosix.d.ts +1 -2
- package/dist/types/utils/runParallel/spawnPosix.d.ts.map +1 -1
- package/dist/types/utils/runParallel/spawnWin32.d.ts +1 -2
- package/dist/types/utils/runParallel/spawnWin32.d.ts.map +1 -1
- package/dist/types/utils/runParallel/wmic.d.ts +1 -2
- package/dist/types/utils/runParallel/wmic.d.ts.map +1 -1
- package/dist/types/utils/sortAlphabetically.d.ts +1 -2
- package/dist/types/utils/sortAlphabetically.d.ts.map +1 -1
- package/dist/types/utils/splitTextByLine.d.ts +1 -2
- package/dist/types/utils/splitTextByLine.d.ts.map +1 -1
- package/dist/types/utils/verifyIdenticObjectFormat.d.ts +1 -2
- package/dist/types/utils/verifyIdenticObjectFormat.d.ts.map +1 -1
- package/dist/types/utils/withFileLock.d.ts +44 -0
- package/dist/types/utils/withFileLock.d.ts.map +1 -0
- package/dist/types/watcher.d.ts +2 -3
- package/dist/types/watcher.d.ts.map +1 -1
- package/dist/types/writeConfiguration/generateConfigurationContent.d.ts +1 -2
- package/dist/types/writeConfiguration/generateConfigurationContent.d.ts.map +1 -1
- package/dist/types/writeConfiguration/index.d.ts +2 -3
- package/dist/types/writeConfiguration/index.d.ts.map +1 -1
- package/dist/types/writeContentDeclaration/detectExportedComponentName.d.ts +1 -2
- package/dist/types/writeContentDeclaration/detectExportedComponentName.d.ts.map +1 -1
- package/dist/types/writeContentDeclaration/dictionaryStatus.d.ts +1 -2
- package/dist/types/writeContentDeclaration/dictionaryStatus.d.ts.map +1 -1
- package/dist/types/writeContentDeclaration/processContentDeclarationContent.d.ts +1 -2
- package/dist/types/writeContentDeclaration/processContentDeclarationContent.d.ts.map +1 -1
- package/dist/types/writeContentDeclaration/transformJSFile.d.ts +1 -2
- package/dist/types/writeContentDeclaration/transformJSFile.d.ts.map +1 -1
- package/dist/types/writeContentDeclaration/transformJSONFile.d.ts +1 -2
- package/dist/types/writeContentDeclaration/transformJSONFile.d.ts.map +1 -1
- package/dist/types/writeContentDeclaration/writeContentDeclaration.d.ts +1 -2
- package/dist/types/writeContentDeclaration/writeContentDeclaration.d.ts.map +1 -1
- package/dist/types/writeContentDeclaration/writeJSFile.d.ts +1 -2
- package/dist/types/writeContentDeclaration/writeJSFile.d.ts.map +1 -1
- package/dist/types/writeContentDeclaration/writeMarkdownFile.d.ts +1 -2
- package/dist/types/writeContentDeclaration/writeMarkdownFile.d.ts.map +1 -1
- package/dist/types/writeContentDeclaration/writeYamlFile.d.ts +1 -2
- package/dist/types/writeContentDeclaration/writeYamlFile.d.ts.map +1 -1
- package/dist/types/writeFileIfChanged.d.ts +2 -3
- package/dist/types/writeFileIfChanged.d.ts.map +1 -1
- package/dist/types/writeJsonIfChanged.d.ts +1 -2
- package/dist/types/writeJsonIfChanged.d.ts.map +1 -1
- package/package.json +12 -12
- package/dist/types/intlayer/dist/types/index.d.ts +0 -14
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
const require_utils_parseContentDeclarationFileName = require('../utils/parseContentDeclarationFileName.cjs');
|
|
2
3
|
let node_fs_promises = require("node:fs/promises");
|
|
3
|
-
let node_path = require("node:path");
|
|
4
4
|
let _intlayer_core_markdown = require("@intlayer/core/markdown");
|
|
5
5
|
let _intlayer_types_nodeType = require("@intlayer/types/nodeType");
|
|
6
6
|
|
|
7
7
|
//#region src/loadDictionaries/loadMarkdownContentDeclaration.ts
|
|
8
|
-
const loadMarkdownContentDeclaration = async (path) => {
|
|
8
|
+
const loadMarkdownContentDeclaration = async (path, configuration) => {
|
|
9
9
|
try {
|
|
10
10
|
const fileContent = await (0, node_fs_promises.readFile)(path, "utf-8");
|
|
11
11
|
const frontmatter = (0, _intlayer_core_markdown.getMarkdownMetadata)(fileContent);
|
|
12
|
-
const
|
|
13
|
-
const key = frontmatter.key ??
|
|
12
|
+
const fileNameDeclaration = require_utils_parseContentDeclarationFileName.parseContentDeclarationFileName(path, configuration);
|
|
13
|
+
const key = frontmatter.key ?? fileNameDeclaration.key;
|
|
14
14
|
if (!key) {
|
|
15
15
|
console.error(`[intlayer] Missing key in markdown content declaration: ${path}`);
|
|
16
16
|
return;
|
|
17
17
|
}
|
|
18
|
-
const { key: _key, locale, title, description, tags, fill, importMode, location, priority, version } = frontmatter;
|
|
18
|
+
const { key: _key, locale: frontmatterLocale, title, description, tags, fill, importMode, location, priority, version } = frontmatter;
|
|
19
|
+
const locale = frontmatterLocale ?? fileNameDeclaration.locale;
|
|
19
20
|
return {
|
|
20
21
|
key,
|
|
21
22
|
...locale !== void 0 && { locale },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loadMarkdownContentDeclaration.cjs","names":["readFile","getMarkdownMetadata","
|
|
1
|
+
{"version":3,"file":"loadMarkdownContentDeclaration.cjs","names":["readFile","getMarkdownMetadata","parseContentDeclarationFileName","MARKDOWN"],"sources":["../../../src/loadDictionaries/loadMarkdownContentDeclaration.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { getMarkdownMetadata } from '@intlayer/core/markdown';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { Dictionary } from '@intlayer/types/dictionary';\nimport { MARKDOWN } from '@intlayer/types/nodeType';\nimport { parseContentDeclarationFileName } from '../utils/parseContentDeclarationFileName';\n\ntype MarkdownFrontmatter = {\n key?: string;\n locale?: string;\n title?: string;\n description?: string;\n tags?: string[];\n fill?: any;\n importMode?: string;\n location?: string;\n priority?: number;\n version?: string;\n [key: string]: any;\n};\n\nexport const loadMarkdownContentDeclaration = async (\n path: string,\n configuration: IntlayerConfig\n): Promise<Dictionary | undefined> => {\n try {\n const fileContent = await readFile(path, 'utf-8');\n const frontmatter = getMarkdownMetadata<MarkdownFrontmatter>(fileContent);\n\n // Derive key and locale from the file name (e.g. \"my-doc.fr.content.md\" →\n // key \"my-doc\", locale \"fr\") for whatever the frontmatter does not declare\n const fileNameDeclaration = parseContentDeclarationFileName(\n path,\n configuration\n );\n const key = frontmatter.key ?? fileNameDeclaration.key;\n\n if (!key) {\n console.error(\n `[intlayer] Missing key in markdown content declaration: ${path}`\n );\n return undefined;\n }\n\n const {\n key: _key,\n locale: frontmatterLocale,\n title,\n description,\n tags,\n fill,\n importMode,\n location,\n priority,\n version,\n } = frontmatter;\n\n const locale = frontmatterLocale ?? fileNameDeclaration.locale;\n\n return {\n key,\n ...(locale !== undefined && { locale }),\n ...(title !== undefined && { title }),\n ...(description !== undefined && { description }),\n ...(tags !== undefined && { tags }),\n ...(fill !== undefined && { fill }),\n ...(importMode !== undefined && { importMode }),\n ...(location !== undefined && { location }),\n ...(priority !== undefined && { priority }),\n ...(version !== undefined && { version }),\n content: {\n nodeType: MARKDOWN,\n [MARKDOWN]: fileContent,\n metadata: frontmatter,\n },\n } as Dictionary;\n } catch (error) {\n console.error(\n `Error loading markdown content declaration at ${path}:`,\n error\n );\n return undefined;\n }\n};\n"],"mappings":";;;;;;;AAqBA,MAAa,iCAAiC,OAC5C,MACA,kBACoC;CACpC,IAAI;EACF,MAAM,cAAc,UAAMA,2BAAS,MAAM,OAAO;EAChD,MAAM,kBAAcC,6CAAyC,WAAW;EAIxE,MAAM,sBAAsBC,8EAC1B,MACA,aACF;EACA,MAAM,MAAM,YAAY,OAAO,oBAAoB;EAEnD,IAAI,CAAC,KAAK;GACR,QAAQ,MACN,2DAA2D,MAC7D;GACA;EACF;EAEA,MAAM,EACJ,KAAK,MACL,QAAQ,mBACR,OACA,aACA,MACA,MACA,YACA,UACA,UACA,YACE;EAEJ,MAAM,SAAS,qBAAqB,oBAAoB;EAExD,OAAO;GACL;GACA,GAAI,WAAW,UAAa,EAAE,OAAO;GACrC,GAAI,UAAU,UAAa,EAAE,MAAM;GACnC,GAAI,gBAAgB,UAAa,EAAE,YAAY;GAC/C,GAAI,SAAS,UAAa,EAAE,KAAK;GACjC,GAAI,SAAS,UAAa,EAAE,KAAK;GACjC,GAAI,eAAe,UAAa,EAAE,WAAW;GAC7C,GAAI,aAAa,UAAa,EAAE,SAAS;GACzC,GAAI,aAAa,UAAa,EAAE,SAAS;GACzC,GAAI,YAAY,UAAa,EAAE,QAAQ;GACvC,SAAS;IACP,UAAUC;KACTA,oCAAW;IACZ,UAAU;GACZ;EACF;CACF,SAAS,OAAO;EACd,QAAQ,MACN,iDAAiD,KAAK,IACtD,KACF;EACA;CACF;AACF"}
|
|
@@ -3,6 +3,51 @@ let node_path = require("node:path");
|
|
|
3
3
|
let _intlayer_config_logger = require("@intlayer/config/logger");
|
|
4
4
|
|
|
5
5
|
//#region src/syncPluginKit/createSyncPlugin.ts
|
|
6
|
+
/** Separator between the dictionary segment and the key remainder of a flat id. */
|
|
7
|
+
const KEY_PREFIX_SEPARATOR = ".";
|
|
8
|
+
/**
|
|
9
|
+
* Groups a flat map of dotted ids by their first segment.
|
|
10
|
+
*
|
|
11
|
+
* `{ 'footer.github': 'GitHub', 'footer.contact': 'Contact', 'banner': '!' }`
|
|
12
|
+
* becomes `{ footer: { github: 'GitHub', contact: 'Contact' }, banner: '!' }`.
|
|
13
|
+
*
|
|
14
|
+
* An id without a separator names a dictionary whose content is the value
|
|
15
|
+
* itself — the shape the optimize pass binds with an empty key remainder.
|
|
16
|
+
* Nested (non-flat) values are passed through under their own key, so a
|
|
17
|
+
* catalog mixing both shapes still round-trips.
|
|
18
|
+
*/
|
|
19
|
+
const groupByKeyPrefix = (content) => {
|
|
20
|
+
const grouped = {};
|
|
21
|
+
for (const [id, value] of Object.entries(content)) {
|
|
22
|
+
const separatorIndex = id.indexOf(KEY_PREFIX_SEPARATOR);
|
|
23
|
+
if (separatorIndex === -1) {
|
|
24
|
+
grouped[id] = value;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const prefix = id.slice(0, separatorIndex);
|
|
28
|
+
const remainder = id.slice(separatorIndex + 1);
|
|
29
|
+
const bucket = grouped[prefix];
|
|
30
|
+
if (bucket !== void 0 && typeof bucket !== "object") {
|
|
31
|
+
grouped[id] = value;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
grouped[prefix] = {
|
|
35
|
+
...bucket ?? {},
|
|
36
|
+
[remainder]: value
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
return grouped;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Inverse of {@link groupByKeyPrefix} for one dictionary: re-joins a grouped
|
|
43
|
+
* bucket back into the flat dotted ids the source file stores.
|
|
44
|
+
*/
|
|
45
|
+
const flattenKeyPrefix = (prefix, content) => {
|
|
46
|
+
if (content === null || typeof content !== "object" || Array.isArray(content)) return { [prefix]: content };
|
|
47
|
+
const flattened = {};
|
|
48
|
+
for (const [key, value] of Object.entries(content)) flattened[`${prefix}${KEY_PREFIX_SEPARATOR}${key}`] = value;
|
|
49
|
+
return flattened;
|
|
50
|
+
};
|
|
6
51
|
/**
|
|
7
52
|
* Content that carries nothing to persist: `undefined`, `null`, or an object
|
|
8
53
|
* without any own key. Writing it back would erase the target.
|
|
@@ -59,7 +104,8 @@ const createSyncPlugin = (options) => {
|
|
|
59
104
|
const fill = patternFill ?? relativeFilePath;
|
|
60
105
|
const identifier = relativeFilePath ?? entry.uri;
|
|
61
106
|
if (shouldSplitByKeys) {
|
|
62
|
-
|
|
107
|
+
const splitContent = shouldSplitByKeys === "key-prefix" ? groupByKeyPrefix(content) : content;
|
|
108
|
+
for (const [namespaceKey, namespaceContent] of Object.entries(splitContent)) dictionaries.push({
|
|
63
109
|
key: namespaceKey,
|
|
64
110
|
locale: usedLocale,
|
|
65
111
|
fill,
|
|
@@ -115,7 +161,8 @@ const createSyncPlugin = (options) => {
|
|
|
115
161
|
key,
|
|
116
162
|
dictionary: result.dictionary
|
|
117
163
|
})).filter(({ dictionary }) => dictionary.location === location);
|
|
118
|
-
|
|
164
|
+
const splitMode = await resolveSplitKeys();
|
|
165
|
+
if (splitMode) {
|
|
119
166
|
const mergedContentByLocale = {};
|
|
120
167
|
const writeKeyByLocale = {};
|
|
121
168
|
for (const { key, dictionary } of ownedDictionaries) for (const locale of locales) {
|
|
@@ -123,7 +170,8 @@ const createSyncPlugin = (options) => {
|
|
|
123
170
|
const content = toPlainContent(formattedOutput.content);
|
|
124
171
|
if (isEmptyContent(content)) continue;
|
|
125
172
|
mergedContentByLocale[locale] ??= {};
|
|
126
|
-
mergedContentByLocale[locale]
|
|
173
|
+
if (splitMode === "key-prefix") Object.assign(mergedContentByLocale[locale], flattenKeyPrefix(key, content));
|
|
174
|
+
else mergedContentByLocale[locale][key] = content;
|
|
127
175
|
writeKeyByLocale[locale] = key;
|
|
128
176
|
}
|
|
129
177
|
await parallelize(Object.keys(mergedContentByLocale), async (locale) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createSyncPlugin.cjs","names":["getAppLogger","colorizePath","relative","resolve"],"sources":["../../../src/syncPluginKit/createSyncPlugin.ts"],"sourcesContent":["import { relative, resolve } from 'node:path';\nimport { colorizePath, getAppLogger } from '@intlayer/config/logger';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { Dictionary, LocalDictionaryId } from '@intlayer/types/dictionary';\nimport type { Plugin } from '@intlayer/types/plugin';\nimport type {\n CreateSyncPluginOptions,\n SyncContent,\n SyncPluginContext,\n} from './types';\n\n/**\n * Content that carries nothing to persist: `undefined`, `null`, or an object\n * without any own key. Writing it back would erase the target.\n */\nconst isEmptyContent = (content: unknown): boolean =>\n typeof content === 'undefined' ||\n content === null ||\n (typeof content === 'object' && Object.keys(content).length === 0);\n\n/**\n * Deep-clone formatted output into plain JSON data, stripping prototypes and\n * non-serializable values before the emptiness check and the write.\n */\nconst toPlainContent = (content: unknown): unknown =>\n typeof content === 'undefined'\n ? undefined\n : JSON.parse(JSON.stringify(content));\n\n/**\n * Build an Intlayer {@link Plugin} from a transport adapter.\n *\n * The factory implements the three plugin hooks once — ingestion\n * (`loadDictionaries`), content-declaration reformatting (`formatOutput`) and\n * write-back (`afterBuild`) — so adapters only describe *where* content lives\n * (filesystem, TMS such as Crowdin, extra CMS…) and codecs only describe the\n * payload format. Write-back only processes dictionaries whose `location`\n * matches this plugin instance, so adapters never see foreign content.\n */\nexport const createSyncPlugin = (options: CreateSyncPluginOptions): Plugin => {\n const {\n name,\n adapter,\n direction = 'both',\n location,\n priority = 0,\n format,\n localeOverride,\n } = options;\n\n let splitKeysPromise: Promise<boolean> | undefined;\n\n /**\n * `splitKeys` resolution is asynchronous (adapter auto-detection may parse\n * an async source pattern), so it is resolved lazily and memoized.\n */\n const resolveSplitKeys = (): Promise<boolean> => {\n splitKeysPromise ??= (async () =>\n options.splitKeys ?? (await adapter.detectSplitKeys?.()) ?? false)();\n\n return splitKeysPromise;\n };\n\n const loadDictionaries: Plugin['loadDictionaries'] = async ({\n configuration,\n }) => {\n const context: SyncPluginContext = { configuration };\n const appLogger = getAppLogger(configuration);\n\n const entries = await adapter.list(context);\n\n if (entries.length === 0) {\n const sourceDescription =\n (await adapter.describeSource?.(context)) ?? name;\n\n appLogger(\n `[${name}] No dictionaries found at locations matching source pattern: ${colorizePath(sourceDescription)}`,\n { level: 'warn' }\n );\n }\n\n const shouldSplitByKeys = await resolveSplitKeys();\n const { baseDir } = configuration.system;\n const { defaultLocale } = configuration.internationalization;\n\n // Sync plugins advertise the source pattern (with {{key}}/{{locale}}\n // markers) as fill target; pull-only plugins fill each entry in place.\n let patternFill: string | undefined;\n\n if (direction === 'both' && adapter.getFillPattern) {\n const fillPattern = await adapter.getFillPattern(context);\n patternFill = relative(baseDir, resolve(baseDir, fillPattern));\n }\n\n const dictionaries: Dictionary[] = [];\n\n for (const entry of entries) {\n const content = (await adapter.read(entry, context)) ?? {};\n\n const relativeFilePath = entry.filePath\n ? relative(baseDir, entry.filePath)\n : undefined;\n\n const usedLocale = (localeOverride ?? entry.locale) as Locale;\n const filled = usedLocale !== defaultLocale ? true : undefined;\n const fill = patternFill ?? relativeFilePath;\n const identifier = relativeFilePath ?? entry.uri;\n\n // One entry groups several namespaces by its first-level keys: emit one\n // dictionary per top-level key (e.g. `Hero`, `Nav`, …).\n if (shouldSplitByKeys) {\n for (const [namespaceKey, namespaceContent] of Object.entries(\n content\n )) {\n dictionaries.push({\n key: namespaceKey,\n locale: usedLocale,\n fill,\n format,\n localId:\n `${namespaceKey}::${location}::${identifier}` as LocalDictionaryId,\n location: location as Dictionary['location'],\n filled,\n content: namespaceContent as SyncContent,\n filePath: relativeFilePath,\n priority,\n } as Dictionary);\n }\n continue;\n }\n\n dictionaries.push({\n key: entry.key,\n locale: usedLocale,\n fill,\n format,\n localId:\n `${entry.key}::${location}::${identifier}` as LocalDictionaryId,\n location: location as Dictionary['location'],\n filled,\n content,\n filePath: relativeFilePath,\n priority,\n } as Dictionary);\n }\n\n return dictionaries;\n };\n\n if (direction === 'pull') {\n return { name, loadDictionaries };\n }\n\n const formatOutput: Plugin['formatOutput'] = async ({\n dictionary,\n configuration,\n }) => {\n if (!dictionary.filePath || !dictionary.locale) return dictionary;\n\n // In split mode several namespaces share the same target; the target is\n // re-assembled in `afterBuild`. Skip here to avoid overwriting the whole\n // target with a single namespace.\n if (await resolveSplitKeys()) return dictionary;\n\n // Ownership check: only reformat declarations this adapter can identify\n // as its own canonical target.\n if (!adapter.resolveUri) return dictionary;\n\n const canonicalUri = await adapter.resolveUri(\n { key: dictionary.key, locale: dictionary.locale as Locale },\n { configuration }\n );\n\n const { baseDir } = configuration.system;\n\n if (\n resolve(baseDir, canonicalUri) !== resolve(baseDir, dictionary.filePath)\n ) {\n return dictionary;\n }\n\n // Lazy import to keep the module graph light when configs are transpiled\n const { formatDictionaryOutput } = await import('../formatDictionary');\n\n return formatDictionaryOutput(dictionary as Dictionary, format).content;\n };\n\n const afterBuild: Plugin['afterBuild'] = async ({\n dictionaries,\n configuration,\n }) => {\n // Lazy imports to keep the module graph light when configs are transpiled\n const { getPerLocaleDictionary } = await import('@intlayer/core/plugins');\n const { formatDictionaryOutput } = await import('../formatDictionary');\n const { parallelize } = await import('../utils/parallelize');\n\n const context: SyncPluginContext = { configuration };\n const { locales } = configuration.internationalization;\n\n // Only ever hand the adapter dictionaries owned by THIS plugin instance.\n const ownedDictionaries = Object.entries(dictionaries.mergedDictionaries)\n .map(([key, result]) => ({\n key,\n dictionary: result.dictionary as Dictionary,\n }))\n .filter(({ dictionary }) => dictionary.location === location);\n\n if (await resolveSplitKeys()) {\n // Split mode: every namespace dictionary writes back into the same\n // per-locale target. Re-assemble them under their top-level key and\n // write each target once, instead of one write per key (which would\n // overwrite).\n const mergedContentByLocale = {} as Record<Locale, SyncContent>;\n const writeKeyByLocale = {} as Record<Locale, string>;\n\n for (const { key, dictionary } of ownedDictionaries) {\n for (const locale of locales) {\n const localizedDictionary = getPerLocaleDictionary(\n dictionary,\n locale\n );\n\n const formattedOutput = formatDictionaryOutput(\n localizedDictionary,\n format\n );\n\n const content = toPlainContent(formattedOutput.content);\n\n if (isEmptyContent(content)) continue;\n\n mergedContentByLocale[locale] ??= {};\n mergedContentByLocale[locale][key] = content;\n writeKeyByLocale[locale] = key;\n }\n }\n\n await parallelize(\n Object.keys(mergedContentByLocale) as Locale[],\n async (locale) => {\n await adapter.write(\n { key: writeKeyByLocale[locale], locale },\n mergedContentByLocale[locale],\n context\n );\n }\n );\n\n return;\n }\n\n const writeTasks = ownedDictionaries.flatMap(({ key, dictionary }) =>\n locales.map((locale) => ({ key, dictionary, locale }))\n );\n\n await parallelize(writeTasks, async ({ key, dictionary, locale }) => {\n const localizedDictionary = getPerLocaleDictionary(dictionary, locale);\n\n const formattedOutput = formatDictionaryOutput(\n localizedDictionary,\n format\n );\n\n const content = toPlainContent(formattedOutput.content);\n\n if (isEmptyContent(content)) return;\n\n await adapter.write({ key, locale }, content as SyncContent, context);\n });\n };\n\n return { name, loadDictionaries, formatOutput, afterBuild };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"createSyncPlugin.cjs","names":["getAppLogger","colorizePath","relative","resolve"],"sources":["../../../src/syncPluginKit/createSyncPlugin.ts"],"sourcesContent":["import { relative, resolve } from 'node:path';\nimport { colorizePath, getAppLogger } from '@intlayer/config/logger';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { Dictionary, LocalDictionaryId } from '@intlayer/types/dictionary';\nimport type { Plugin } from '@intlayer/types/plugin';\nimport type {\n CreateSyncPluginOptions,\n SplitKeysMode,\n SyncContent,\n SyncPluginContext,\n} from './types';\n\n/** Separator between the dictionary segment and the key remainder of a flat id. */\nconst KEY_PREFIX_SEPARATOR = '.';\n\n/**\n * Groups a flat map of dotted ids by their first segment.\n *\n * `{ 'footer.github': 'GitHub', 'footer.contact': 'Contact', 'banner': '!' }`\n * becomes `{ footer: { github: 'GitHub', contact: 'Contact' }, banner: '!' }`.\n *\n * An id without a separator names a dictionary whose content is the value\n * itself — the shape the optimize pass binds with an empty key remainder.\n * Nested (non-flat) values are passed through under their own key, so a\n * catalog mixing both shapes still round-trips.\n */\nconst groupByKeyPrefix = (\n content: SyncContent\n): Record<string, SyncContent | unknown> => {\n const grouped: Record<string, SyncContent | unknown> = {};\n\n for (const [id, value] of Object.entries(content)) {\n const separatorIndex = id.indexOf(KEY_PREFIX_SEPARATOR);\n\n if (separatorIndex === -1) {\n grouped[id] = value;\n continue;\n }\n\n const prefix = id.slice(0, separatorIndex);\n const remainder = id.slice(separatorIndex + 1);\n const bucket = grouped[prefix];\n\n // A scalar already sits here (`'a'` seen before `'a.b'`): keep the scalar\n // and leave the dotted id whole, rather than silently dropping either.\n if (bucket !== undefined && typeof bucket !== 'object') {\n grouped[id] = value;\n continue;\n }\n\n grouped[prefix] = { ...((bucket ?? {}) as object), [remainder]: value };\n }\n\n return grouped;\n};\n\n/**\n * Inverse of {@link groupByKeyPrefix} for one dictionary: re-joins a grouped\n * bucket back into the flat dotted ids the source file stores.\n */\nconst flattenKeyPrefix = (\n prefix: string,\n content: unknown\n): Record<string, unknown> => {\n if (\n content === null ||\n typeof content !== 'object' ||\n Array.isArray(content)\n ) {\n // Dot-less id — the dictionary content *is* the message.\n return { [prefix]: content };\n }\n\n const flattened: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(content)) {\n flattened[`${prefix}${KEY_PREFIX_SEPARATOR}${key}`] = value;\n }\n return flattened;\n};\n\n/**\n * Content that carries nothing to persist: `undefined`, `null`, or an object\n * without any own key. Writing it back would erase the target.\n */\nconst isEmptyContent = (content: unknown): boolean =>\n typeof content === 'undefined' ||\n content === null ||\n (typeof content === 'object' && Object.keys(content).length === 0);\n\n/**\n * Deep-clone formatted output into plain JSON data, stripping prototypes and\n * non-serializable values before the emptiness check and the write.\n */\nconst toPlainContent = (content: unknown): unknown =>\n typeof content === 'undefined'\n ? undefined\n : JSON.parse(JSON.stringify(content));\n\n/**\n * Build an Intlayer {@link Plugin} from a transport adapter.\n *\n * The factory implements the three plugin hooks once — ingestion\n * (`loadDictionaries`), content-declaration reformatting (`formatOutput`) and\n * write-back (`afterBuild`) — so adapters only describe *where* content lives\n * (filesystem, TMS such as Crowdin, extra CMS…) and codecs only describe the\n * payload format. Write-back only processes dictionaries whose `location`\n * matches this plugin instance, so adapters never see foreign content.\n */\nexport const createSyncPlugin = (options: CreateSyncPluginOptions): Plugin => {\n const {\n name,\n adapter,\n direction = 'both',\n location,\n priority = 0,\n format,\n localeOverride,\n } = options;\n\n let splitKeysPromise: Promise<SplitKeysMode> | undefined;\n\n /**\n * `splitKeys` resolution is asynchronous (adapter auto-detection may parse\n * an async source pattern), so it is resolved lazily and memoized.\n */\n const resolveSplitKeys = (): Promise<SplitKeysMode> => {\n splitKeysPromise ??= (async () =>\n options.splitKeys ?? (await adapter.detectSplitKeys?.()) ?? false)();\n\n return splitKeysPromise;\n };\n\n const loadDictionaries: Plugin['loadDictionaries'] = async ({\n configuration,\n }) => {\n const context: SyncPluginContext = { configuration };\n const appLogger = getAppLogger(configuration);\n\n const entries = await adapter.list(context);\n\n if (entries.length === 0) {\n const sourceDescription =\n (await adapter.describeSource?.(context)) ?? name;\n\n appLogger(\n `[${name}] No dictionaries found at locations matching source pattern: ${colorizePath(sourceDescription)}`,\n { level: 'warn' }\n );\n }\n\n const shouldSplitByKeys = await resolveSplitKeys();\n const { baseDir } = configuration.system;\n const { defaultLocale } = configuration.internationalization;\n\n // Sync plugins advertise the source pattern (with {{key}}/{{locale}}\n // markers) as fill target; pull-only plugins fill each entry in place.\n let patternFill: string | undefined;\n\n if (direction === 'both' && adapter.getFillPattern) {\n const fillPattern = await adapter.getFillPattern(context);\n patternFill = relative(baseDir, resolve(baseDir, fillPattern));\n }\n\n const dictionaries: Dictionary[] = [];\n\n for (const entry of entries) {\n const content = (await adapter.read(entry, context)) ?? {};\n\n const relativeFilePath = entry.filePath\n ? relative(baseDir, entry.filePath)\n : undefined;\n\n const usedLocale = (localeOverride ?? entry.locale) as Locale;\n const filled = usedLocale !== defaultLocale ? true : undefined;\n const fill = patternFill ?? relativeFilePath;\n const identifier = relativeFilePath ?? entry.uri;\n\n // One entry groups several namespaces: emit one dictionary per\n // namespace. `true` reads them from the first-level keys (`Hero`,\n // `Nav`, …); `'key-prefix'` derives them from the first dot-segment of\n // a flat catalog's dotted ids (`footer.github` → `footer`).\n if (shouldSplitByKeys) {\n const splitContent =\n shouldSplitByKeys === 'key-prefix'\n ? groupByKeyPrefix(content)\n : content;\n\n for (const [namespaceKey, namespaceContent] of Object.entries(\n splitContent\n )) {\n dictionaries.push({\n key: namespaceKey,\n locale: usedLocale,\n fill,\n format,\n localId:\n `${namespaceKey}::${location}::${identifier}` as LocalDictionaryId,\n location: location as Dictionary['location'],\n filled,\n content: namespaceContent as SyncContent,\n filePath: relativeFilePath,\n priority,\n } as Dictionary);\n }\n continue;\n }\n\n dictionaries.push({\n key: entry.key,\n locale: usedLocale,\n fill,\n format,\n localId:\n `${entry.key}::${location}::${identifier}` as LocalDictionaryId,\n location: location as Dictionary['location'],\n filled,\n content,\n filePath: relativeFilePath,\n priority,\n } as Dictionary);\n }\n\n return dictionaries;\n };\n\n if (direction === 'pull') {\n return { name, loadDictionaries };\n }\n\n const formatOutput: Plugin['formatOutput'] = async ({\n dictionary,\n configuration,\n }) => {\n if (!dictionary.filePath || !dictionary.locale) return dictionary;\n\n // In split mode several namespaces share the same target; the target is\n // re-assembled in `afterBuild`. Skip here to avoid overwriting the whole\n // target with a single namespace.\n if (await resolveSplitKeys()) return dictionary;\n\n // Ownership check: only reformat declarations this adapter can identify\n // as its own canonical target.\n if (!adapter.resolveUri) return dictionary;\n\n const canonicalUri = await adapter.resolveUri(\n { key: dictionary.key, locale: dictionary.locale as Locale },\n { configuration }\n );\n\n const { baseDir } = configuration.system;\n\n if (\n resolve(baseDir, canonicalUri) !== resolve(baseDir, dictionary.filePath)\n ) {\n return dictionary;\n }\n\n // Lazy import to keep the module graph light when configs are transpiled\n const { formatDictionaryOutput } = await import('../formatDictionary');\n\n return formatDictionaryOutput(dictionary as Dictionary, format).content;\n };\n\n const afterBuild: Plugin['afterBuild'] = async ({\n dictionaries,\n configuration,\n }) => {\n // Lazy imports to keep the module graph light when configs are transpiled\n const { getPerLocaleDictionary } = await import('@intlayer/core/plugins');\n const { formatDictionaryOutput } = await import('../formatDictionary');\n const { parallelize } = await import('../utils/parallelize');\n\n const context: SyncPluginContext = { configuration };\n const { locales } = configuration.internationalization;\n\n // Only ever hand the adapter dictionaries owned by THIS plugin instance.\n const ownedDictionaries = Object.entries(dictionaries.mergedDictionaries)\n .map(([key, result]) => ({\n key,\n dictionary: result.dictionary as Dictionary,\n }))\n .filter(({ dictionary }) => dictionary.location === location);\n\n const splitMode = await resolveSplitKeys();\n\n if (splitMode) {\n // Split mode: every namespace dictionary writes back into the same\n // per-locale target. Re-assemble them under their top-level key and\n // write each target once, instead of one write per key (which would\n // overwrite).\n const mergedContentByLocale = {} as Record<Locale, SyncContent>;\n const writeKeyByLocale = {} as Record<Locale, string>;\n\n for (const { key, dictionary } of ownedDictionaries) {\n for (const locale of locales) {\n const localizedDictionary = getPerLocaleDictionary(\n dictionary,\n locale\n );\n\n const formattedOutput = formatDictionaryOutput(\n localizedDictionary,\n format\n );\n\n const content = toPlainContent(formattedOutput.content);\n\n if (isEmptyContent(content)) continue;\n\n mergedContentByLocale[locale] ??= {};\n\n // `'key-prefix'` split the flat dotted ids apart on read; restore\n // them so the source file keeps the shape its library expects,\n // instead of gaining a nested object per prefix.\n if (splitMode === 'key-prefix') {\n Object.assign(\n mergedContentByLocale[locale],\n flattenKeyPrefix(key, content)\n );\n } else {\n mergedContentByLocale[locale][key] = content;\n }\n\n writeKeyByLocale[locale] = key;\n }\n }\n\n await parallelize(\n Object.keys(mergedContentByLocale) as Locale[],\n async (locale) => {\n await adapter.write(\n { key: writeKeyByLocale[locale], locale },\n mergedContentByLocale[locale],\n context\n );\n }\n );\n\n return;\n }\n\n const writeTasks = ownedDictionaries.flatMap(({ key, dictionary }) =>\n locales.map((locale) => ({ key, dictionary, locale }))\n );\n\n await parallelize(writeTasks, async ({ key, dictionary, locale }) => {\n const localizedDictionary = getPerLocaleDictionary(dictionary, locale);\n\n const formattedOutput = formatDictionaryOutput(\n localizedDictionary,\n format\n );\n\n const content = toPlainContent(formattedOutput.content);\n\n if (isEmptyContent(content)) return;\n\n await adapter.write({ key, locale }, content as SyncContent, context);\n });\n };\n\n return { name, loadDictionaries, formatOutput, afterBuild };\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
const require_runtime = require('../_virtual/_rolldown/runtime.cjs');
|
|
3
|
+
let node_fs_promises = require("node:fs/promises");
|
|
4
|
+
let node_path = require("node:path");
|
|
5
|
+
let _intlayer_config_logger = require("@intlayer/config/logger");
|
|
6
|
+
let node_fs = require("node:fs");
|
|
7
|
+
let _intlayer_config_colors = require("@intlayer/config/colors");
|
|
8
|
+
_intlayer_config_colors = require_runtime.__toESM(_intlayer_config_colors);
|
|
9
|
+
|
|
10
|
+
//#region src/utils/contentWatcherLock.ts
|
|
11
|
+
/**
|
|
12
|
+
* Set on every process spawned by `intlayer watch --with` (and
|
|
13
|
+
* `intlayer build --watch --with`), so a bundler plugin running as that child
|
|
14
|
+
* knows a CLI watcher is already covering the project — without having to
|
|
15
|
+
* depend on the two processes reaching the lock file in a given order.
|
|
16
|
+
*
|
|
17
|
+
* Holds the label of the command that set it.
|
|
18
|
+
*/
|
|
19
|
+
const CLI_CONTENT_WATCHER_ENV_VAR = "INTLAYER_CLI_CONTENT_WATCHER";
|
|
20
|
+
/** Name of the file materialising the ownership of the content watcher. */
|
|
21
|
+
const CONTENT_WATCHER_LOCK_FILE_NAME = "intlayer-content-watcher.lock";
|
|
22
|
+
/** How many times taking the lock may follow up on a reclaimed stale one. */
|
|
23
|
+
const ACQUIRE_ATTEMPTS = 3;
|
|
24
|
+
/** Lock owned by this process, released on exit. */
|
|
25
|
+
let ownedLockFilePath;
|
|
26
|
+
process.on("exit", () => {
|
|
27
|
+
if (!ownedLockFilePath) return;
|
|
28
|
+
try {
|
|
29
|
+
(0, node_fs.rmSync)(ownedLockFilePath, { force: true });
|
|
30
|
+
} catch {}
|
|
31
|
+
});
|
|
32
|
+
/**
|
|
33
|
+
* Path of the lock coordinating every content watcher of one project.
|
|
34
|
+
*
|
|
35
|
+
* Deliberately at the root of `.intlayer` rather than in its `cache`
|
|
36
|
+
* subdirectory: `cleanOutputDir` wipes every subdirectory of `.intlayer`, so a
|
|
37
|
+
* lock kept in `cache` would be erased by any `prepareIntlayer` that cleans —
|
|
38
|
+
* after which a bundler plugin would see no owner and start a second watcher
|
|
39
|
+
* next to the `intlayer watch` still running. The root itself is never removed.
|
|
40
|
+
*
|
|
41
|
+
* @param configuration - The resolved Intlayer configuration.
|
|
42
|
+
*/
|
|
43
|
+
const getContentWatcherLockFilePath = (configuration) => (0, node_path.join)(configuration.system.tempDir, CONTENT_WATCHER_LOCK_FILE_NAME);
|
|
44
|
+
/**
|
|
45
|
+
* Whether a PID belongs to a process that is still running.
|
|
46
|
+
*/
|
|
47
|
+
const getIsProcessAlive = (pid) => {
|
|
48
|
+
if (pid === process.pid) return true;
|
|
49
|
+
try {
|
|
50
|
+
process.kill(pid, 0);
|
|
51
|
+
return true;
|
|
52
|
+
} catch (error) {
|
|
53
|
+
return error.code === "EPERM";
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Reads the lock, removing it when it describes a process that has since died.
|
|
58
|
+
*
|
|
59
|
+
* An unreadable or malformed lock is treated as abandoned: a corrupted file
|
|
60
|
+
* must never block the watcher for a whole dev session.
|
|
61
|
+
*
|
|
62
|
+
* @returns The live owner, or `null` when nothing is watching.
|
|
63
|
+
*/
|
|
64
|
+
const getContentWatcherOwner = async (configuration) => {
|
|
65
|
+
const lockFilePath = getContentWatcherLockFilePath(configuration);
|
|
66
|
+
let owner;
|
|
67
|
+
try {
|
|
68
|
+
owner = JSON.parse(await (0, node_fs_promises.readFile)(lockFilePath, "utf8"));
|
|
69
|
+
} catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
if (owner.pid && getIsProcessAlive(owner.pid)) return {
|
|
73
|
+
pid: owner.pid,
|
|
74
|
+
source: owner.source ?? "bundler",
|
|
75
|
+
label: owner.label ?? "Intlayer",
|
|
76
|
+
since: owner.since
|
|
77
|
+
};
|
|
78
|
+
try {
|
|
79
|
+
await (0, node_fs_promises.rm)(lockFilePath, { force: true });
|
|
80
|
+
} catch {}
|
|
81
|
+
return null;
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Tries to become the process that watches this project's content
|
|
85
|
+
* declarations.
|
|
86
|
+
*
|
|
87
|
+
* Creating the file with `wx` is atomic, so exactly one process wins even when
|
|
88
|
+
* several try at the same moment — which is the normal case on Next.js, where
|
|
89
|
+
* `next.config.*` is evaluated in more than one process.
|
|
90
|
+
*
|
|
91
|
+
* @param configuration - The resolved Intlayer configuration.
|
|
92
|
+
* @param owner - How this process should describe itself to the others.
|
|
93
|
+
* @returns `true` when this process now owns the watcher.
|
|
94
|
+
*/
|
|
95
|
+
const acquireContentWatcherLock = async (configuration, owner) => {
|
|
96
|
+
const lockFilePath = getContentWatcherLockFilePath(configuration);
|
|
97
|
+
const data = JSON.stringify({
|
|
98
|
+
pid: process.pid,
|
|
99
|
+
...owner
|
|
100
|
+
});
|
|
101
|
+
for (let attempt = 0; attempt < ACQUIRE_ATTEMPTS; attempt++) try {
|
|
102
|
+
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(lockFilePath), { recursive: true });
|
|
103
|
+
await (0, node_fs_promises.writeFile)(lockFilePath, data, { flag: "wx" });
|
|
104
|
+
ownedLockFilePath = lockFilePath;
|
|
105
|
+
return true;
|
|
106
|
+
} catch (error) {
|
|
107
|
+
if (error.code !== "EEXIST") return false;
|
|
108
|
+
if (await getContentWatcherOwner(configuration)) return false;
|
|
109
|
+
}
|
|
110
|
+
return false;
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* The CLI watcher command this process was spawned by, or `null` when it was
|
|
114
|
+
* not started through `intlayer watch --with`.
|
|
115
|
+
*/
|
|
116
|
+
const getCliContentWatcherLabel = () => process.env["INTLAYER_CLI_CONTENT_WATCHER"] || null;
|
|
117
|
+
/**
|
|
118
|
+
* Registers a CLI command as the content watcher of this project.
|
|
119
|
+
*
|
|
120
|
+
* Call it *before* spawning a `--with` child: taking the lock first means the
|
|
121
|
+
* bundler plugin inside that child never races the command for it, and the
|
|
122
|
+
* environment marker makes the child's decision independent of the filesystem
|
|
123
|
+
* altogether.
|
|
124
|
+
*
|
|
125
|
+
* @param configuration - The resolved Intlayer configuration.
|
|
126
|
+
* @param label - How the command should be named in a message.
|
|
127
|
+
* @returns The bundler already watching this project, or `null` when the
|
|
128
|
+
* command took the watcher for itself. A CLI watcher never stands down on that
|
|
129
|
+
* answer — watching is what it was asked to do — but reporting it lets the user
|
|
130
|
+
* know the parallel command has become redundant.
|
|
131
|
+
*/
|
|
132
|
+
const claimCliContentWatcher = async (configuration, label) => {
|
|
133
|
+
process.env[CLI_CONTENT_WATCHER_ENV_VAR] = label;
|
|
134
|
+
if (await acquireContentWatcherLock(configuration, {
|
|
135
|
+
source: "cli",
|
|
136
|
+
label
|
|
137
|
+
})) return null;
|
|
138
|
+
const owner = await getContentWatcherOwner(configuration);
|
|
139
|
+
return owner?.source === "bundler" ? owner : null;
|
|
140
|
+
};
|
|
141
|
+
/** Keeps the redundant-watcher notice to one per process. */
|
|
142
|
+
let hasReportedRedundantWatcher = false;
|
|
143
|
+
/**
|
|
144
|
+
* Reports a CLI watcher running alongside a bundler integration that watches
|
|
145
|
+
* on its own, and points at the parallel command as the part to drop.
|
|
146
|
+
*
|
|
147
|
+
* Emitted at most once per process: a bundler config is evaluated several times
|
|
148
|
+
* per command, and callers may re-enter this on a retry loop.
|
|
149
|
+
*
|
|
150
|
+
* @param configuration - The resolved Intlayer configuration.
|
|
151
|
+
* @param report - Who is watching, and from which release it became redundant.
|
|
152
|
+
*/
|
|
153
|
+
const reportRedundantContentWatcher = (configuration, { cliLabel, bundlerLabel, since }) => {
|
|
154
|
+
if (hasReportedRedundantWatcher) return;
|
|
155
|
+
hasReportedRedundantWatcher = true;
|
|
156
|
+
(0, _intlayer_config_logger.getAppLogger)(configuration)([
|
|
157
|
+
(0, _intlayer_config_logger.colorize)(cliLabel, _intlayer_config_colors.BLUE, _intlayer_config_colors.BEIGE),
|
|
158
|
+
"is watching your content declarations in parallel with",
|
|
159
|
+
`${(0, _intlayer_config_logger.colorize)(bundlerLabel, _intlayer_config_colors.BLUE, _intlayer_config_colors.BEIGE)},`,
|
|
160
|
+
since ? `which watches them on its own since ${(0, _intlayer_config_logger.colorize)(`${bundlerLabel}@${since}`, _intlayer_config_colors.GREY_LIGHT, _intlayer_config_colors.BEIGE)}.` : "which watches them on its own.",
|
|
161
|
+
"The parallel watch command is no longer needed — running your dev server on its own is enough."
|
|
162
|
+
], { level: "warn" });
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
//#endregion
|
|
166
|
+
exports.CLI_CONTENT_WATCHER_ENV_VAR = CLI_CONTENT_WATCHER_ENV_VAR;
|
|
167
|
+
exports.acquireContentWatcherLock = acquireContentWatcherLock;
|
|
168
|
+
exports.claimCliContentWatcher = claimCliContentWatcher;
|
|
169
|
+
exports.getCliContentWatcherLabel = getCliContentWatcherLabel;
|
|
170
|
+
exports.getContentWatcherLockFilePath = getContentWatcherLockFilePath;
|
|
171
|
+
exports.getContentWatcherOwner = getContentWatcherOwner;
|
|
172
|
+
exports.reportRedundantContentWatcher = reportRedundantContentWatcher;
|
|
173
|
+
//# sourceMappingURL=contentWatcherLock.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contentWatcherLock.cjs","names":["join","readFile","rm","mkdir","dirname","writeFile","colorize","ANSIColors"],"sources":["../../../src/utils/contentWatcherLock.ts"],"sourcesContent":["import { rmSync } from 'node:fs';\nimport { mkdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, getAppLogger } from '@intlayer/config/logger';\nimport type { IntlayerConfig } from '@intlayer/types/config';\n\n/**\n * What kind of process is watching the content declarations.\n *\n * - `cli` — the `intlayer watch` / `intlayer build --watch` command.\n * - `bundler` — an Intlayer bundler plugin (e.g. `withIntlayer` on Next.js).\n */\nexport type ContentWatcherSource = 'cli' | 'bundler';\n\nexport type ContentWatcherOwner = {\n /** PID of the process holding the watcher. */\n pid: number;\n source: ContentWatcherSource;\n /**\n * How the owner should be named in a message, e.g. `intlayer watch` or\n * `next-intlayer`.\n */\n label: string;\n /**\n * Release from which a `bundler` owner watches the content declarations on\n * its own, so a CLI watcher finding it can name the version that made the\n * parallel command redundant without keeping a table of its own.\n */\n since?: string;\n};\n\n/**\n * Set on every process spawned by `intlayer watch --with` (and\n * `intlayer build --watch --with`), so a bundler plugin running as that child\n * knows a CLI watcher is already covering the project — without having to\n * depend on the two processes reaching the lock file in a given order.\n *\n * Holds the label of the command that set it.\n */\nexport const CLI_CONTENT_WATCHER_ENV_VAR = 'INTLAYER_CLI_CONTENT_WATCHER';\n\n/** Name of the file materialising the ownership of the content watcher. */\nconst CONTENT_WATCHER_LOCK_FILE_NAME = 'intlayer-content-watcher.lock';\n\n/** How many times taking the lock may follow up on a reclaimed stale one. */\nconst ACQUIRE_ATTEMPTS = 3;\n\n/** Lock owned by this process, released on exit. */\nlet ownedLockFilePath: string | undefined;\n\n// The one place that has to stay synchronous: Node runs `exit` listeners to\n// completion without an event loop turn, so a promise scheduled here would\n// never settle and the lock would outlive the process — stalling the next dev\n// session until its PID check reclaims it.\nprocess.on('exit', () => {\n if (!ownedLockFilePath) return;\n\n try {\n rmSync(ownedLockFilePath, { force: true });\n } catch {}\n});\n\n/**\n * Path of the lock coordinating every content watcher of one project.\n *\n * Deliberately at the root of `.intlayer` rather than in its `cache`\n * subdirectory: `cleanOutputDir` wipes every subdirectory of `.intlayer`, so a\n * lock kept in `cache` would be erased by any `prepareIntlayer` that cleans —\n * after which a bundler plugin would see no owner and start a second watcher\n * next to the `intlayer watch` still running. The root itself is never removed.\n *\n * @param configuration - The resolved Intlayer configuration.\n */\nexport const getContentWatcherLockFilePath = (\n configuration: IntlayerConfig\n): string => join(configuration.system.tempDir, CONTENT_WATCHER_LOCK_FILE_NAME);\n\n/**\n * Whether a PID belongs to a process that is still running.\n */\nconst getIsProcessAlive = (pid: number): boolean => {\n if (pid === process.pid) return true;\n\n try {\n // Signal 0 checks for existence without delivering a signal.\n process.kill(pid, 0);\n return true;\n } catch (error) {\n // EPERM means the process exists but belongs to another user.\n return (error as NodeJS.ErrnoException).code === 'EPERM';\n }\n};\n\n/**\n * Reads the lock, removing it when it describes a process that has since died.\n *\n * An unreadable or malformed lock is treated as abandoned: a corrupted file\n * must never block the watcher for a whole dev session.\n *\n * @returns The live owner, or `null` when nothing is watching.\n */\nexport const getContentWatcherOwner = async (\n configuration: IntlayerConfig\n): Promise<ContentWatcherOwner | null> => {\n const lockFilePath = getContentWatcherLockFilePath(configuration);\n\n let owner: Partial<ContentWatcherOwner>;\n\n try {\n owner = JSON.parse(await readFile(lockFilePath, 'utf8'));\n } catch {\n return null;\n }\n\n if (owner.pid && getIsProcessAlive(owner.pid)) {\n return {\n pid: owner.pid,\n source: owner.source ?? 'bundler',\n label: owner.label ?? 'Intlayer',\n since: owner.since,\n };\n }\n\n try {\n await rm(lockFilePath, { force: true });\n } catch {}\n\n return null;\n};\n\n/**\n * Tries to become the process that watches this project's content\n * declarations.\n *\n * Creating the file with `wx` is atomic, so exactly one process wins even when\n * several try at the same moment — which is the normal case on Next.js, where\n * `next.config.*` is evaluated in more than one process.\n *\n * @param configuration - The resolved Intlayer configuration.\n * @param owner - How this process should describe itself to the others.\n * @returns `true` when this process now owns the watcher.\n */\nexport const acquireContentWatcherLock = async (\n configuration: IntlayerConfig,\n owner: Omit<ContentWatcherOwner, 'pid'>\n): Promise<boolean> => {\n const lockFilePath = getContentWatcherLockFilePath(configuration);\n const data = JSON.stringify({\n pid: process.pid,\n ...owner,\n } satisfies ContentWatcherOwner);\n\n // Bounded rather than recursive: the retry only exists to follow up on a lock\n // `getContentWatcherOwner` has just reclaimed, and two processes reclaiming\n // and re-taking it in turn must not spin forever.\n for (let attempt = 0; attempt < ACQUIRE_ATTEMPTS; attempt++) {\n try {\n await mkdir(dirname(lockFilePath), { recursive: true });\n await writeFile(lockFilePath, data, { flag: 'wx' });\n\n ownedLockFilePath = lockFilePath;\n\n return true;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'EEXIST') return false;\n\n // A lock left behind by a dead process is reclaimed by\n // `getContentWatcherOwner`, leaving the next attempt free to take it.\n if (await getContentWatcherOwner(configuration)) return false;\n }\n }\n\n return false;\n};\n\n/**\n * The CLI watcher command this process was spawned by, or `null` when it was\n * not started through `intlayer watch --with`.\n */\nexport const getCliContentWatcherLabel = (): string | null =>\n process.env[CLI_CONTENT_WATCHER_ENV_VAR] || null;\n\n/**\n * Registers a CLI command as the content watcher of this project.\n *\n * Call it *before* spawning a `--with` child: taking the lock first means the\n * bundler plugin inside that child never races the command for it, and the\n * environment marker makes the child's decision independent of the filesystem\n * altogether.\n *\n * @param configuration - The resolved Intlayer configuration.\n * @param label - How the command should be named in a message.\n * @returns The bundler already watching this project, or `null` when the\n * command took the watcher for itself. A CLI watcher never stands down on that\n * answer — watching is what it was asked to do — but reporting it lets the user\n * know the parallel command has become redundant.\n */\nexport const claimCliContentWatcher = async (\n configuration: IntlayerConfig,\n label: string\n): Promise<ContentWatcherOwner | null> => {\n // Set before the first `await`: a `--with` child spawned while the lock is\n // still being written must already see the marker in its environment.\n process.env[CLI_CONTENT_WATCHER_ENV_VAR] = label;\n\n const hasAcquiredLock = await acquireContentWatcherLock(configuration, {\n source: 'cli',\n label,\n });\n\n if (hasAcquiredLock) return null;\n\n const owner = await getContentWatcherOwner(configuration);\n\n return owner?.source === 'bundler' ? owner : null;\n};\n\n/** Keeps the redundant-watcher notice to one per process. */\nlet hasReportedRedundantWatcher = false;\n\ntype RedundantWatcherReport = {\n /** Name of the CLI command watching, e.g. `intlayer watch`. */\n cliLabel: string;\n /** Name of the bundler integration watching, e.g. `next-intlayer`. */\n bundlerLabel: string;\n /**\n * Release from which the bundler integration watches on its own. Omitted when\n * the integration did not report one.\n */\n since?: string;\n};\n\n/**\n * Reports a CLI watcher running alongside a bundler integration that watches\n * on its own, and points at the parallel command as the part to drop.\n *\n * Emitted at most once per process: a bundler config is evaluated several times\n * per command, and callers may re-enter this on a retry loop.\n *\n * @param configuration - The resolved Intlayer configuration.\n * @param report - Who is watching, and from which release it became redundant.\n */\nexport const reportRedundantContentWatcher = (\n configuration: IntlayerConfig,\n { cliLabel, bundlerLabel, since }: RedundantWatcherReport\n): void => {\n if (hasReportedRedundantWatcher) return;\n hasReportedRedundantWatcher = true;\n\n getAppLogger(configuration)(\n [\n colorize(cliLabel, ANSIColors.BLUE, ANSIColors.BEIGE),\n 'is watching your content declarations in parallel with',\n `${colorize(bundlerLabel, ANSIColors.BLUE, ANSIColors.BEIGE)},`,\n since\n ? `which watches them on its own since ${colorize(`${bundlerLabel}@${since}`, ANSIColors.GREY_LIGHT, ANSIColors.BEIGE)}.`\n : 'which watches them on its own.',\n 'The parallel watch command is no longer needed — running your dev server on its own is enough.',\n ],\n { level: 'warn' }\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAwCA,MAAa,8BAA8B;;AAG3C,MAAM,iCAAiC;;AAGvC,MAAM,mBAAmB;;AAGzB,IAAI;AAMJ,QAAQ,GAAG,cAAc;CACvB,IAAI,CAAC,mBAAmB;CAExB,IAAI;EACF,oBAAO,mBAAmB,EAAE,OAAO,KAAK,CAAC;CAC3C,QAAQ,CAAC;AACX,CAAC;;;;;;;;;;;;AAaD,MAAa,iCACX,sBACWA,gBAAK,cAAc,OAAO,SAAS,8BAA8B;;;;AAK9E,MAAM,qBAAqB,QAAyB;CAClD,IAAI,QAAQ,QAAQ,KAAK,OAAO;CAEhC,IAAI;EAEF,QAAQ,KAAK,KAAK,CAAC;EACnB,OAAO;CACT,SAAS,OAAO;EAEd,OAAQ,MAAgC,SAAS;CACnD;AACF;;;;;;;;;AAUA,MAAa,yBAAyB,OACpC,kBACwC;CACxC,MAAM,eAAe,8BAA8B,aAAa;CAEhE,IAAI;CAEJ,IAAI;EACF,QAAQ,KAAK,MAAM,UAAMC,2BAAS,cAAc,MAAM,CAAC;CACzD,QAAQ;EACN,OAAO;CACT;CAEA,IAAI,MAAM,OAAO,kBAAkB,MAAM,GAAG,GAC1C,OAAO;EACL,KAAK,MAAM;EACX,QAAQ,MAAM,UAAU;EACxB,OAAO,MAAM,SAAS;EACtB,OAAO,MAAM;CACf;CAGF,IAAI;EACF,UAAMC,qBAAG,cAAc,EAAE,OAAO,KAAK,CAAC;CACxC,QAAQ,CAAC;CAET,OAAO;AACT;;;;;;;;;;;;;AAcA,MAAa,4BAA4B,OACvC,eACA,UACqB;CACrB,MAAM,eAAe,8BAA8B,aAAa;CAChE,MAAM,OAAO,KAAK,UAAU;EAC1B,KAAK,QAAQ;EACb,GAAG;CACL,CAA+B;CAK/B,KAAK,IAAI,UAAU,GAAG,UAAU,kBAAkB,WAChD,IAAI;EACF,UAAMC,4BAAMC,mBAAQ,YAAY,GAAG,EAAE,WAAW,KAAK,CAAC;EACtD,UAAMC,4BAAU,cAAc,MAAM,EAAE,MAAM,KAAK,CAAC;EAElD,oBAAoB;EAEpB,OAAO;CACT,SAAS,OAAO;EACd,IAAK,MAAgC,SAAS,UAAU,OAAO;EAI/D,IAAI,MAAM,uBAAuB,aAAa,GAAG,OAAO;CAC1D;CAGF,OAAO;AACT;;;;;AAMA,MAAa,kCACX,QAAQ,uCAAoC;;;;;;;;;;;;;;;;AAiB9C,MAAa,yBAAyB,OACpC,eACA,UACwC;CAGxC,QAAQ,IAAI,+BAA+B;CAO3C,IAAI,MAL0B,0BAA0B,eAAe;EACrE,QAAQ;EACR;CACF,CAAC,GAEoB,OAAO;CAE5B,MAAM,QAAQ,MAAM,uBAAuB,aAAa;CAExD,OAAO,OAAO,WAAW,YAAY,QAAQ;AAC/C;;AAGA,IAAI,8BAA8B;;;;;;;;;;;AAwBlC,MAAa,iCACX,eACA,EAAE,UAAU,cAAc,YACjB;CACT,IAAI,6BAA6B;CACjC,8BAA8B;CAE9B,0CAAa,aAAa,CAAC,CACzB;MACEC,kCAAS,UAAUC,wBAAW,MAAMA,wBAAW,KAAK;EACpD;EACA,OAAGD,kCAAS,cAAcC,wBAAW,MAAMA,wBAAW,KAAK,EAAE;EAC7D,QACI,2CAAuCD,kCAAS,GAAG,aAAa,GAAG,SAASC,wBAAW,YAAYA,wBAAW,KAAK,EAAE,KACrH;EACJ;CACF,GACA,EAAE,OAAO,OAAO,CAClB;AACF"}
|
package/dist/cjs/utils/index.cjs
CHANGED
|
@@ -9,10 +9,12 @@ const require_utils_chunkArray = require('./chunkArray.cjs');
|
|
|
9
9
|
const require_utils_splitTextByLine = require('./splitTextByLine.cjs');
|
|
10
10
|
const require_utils_getChunk = require('./getChunk.cjs');
|
|
11
11
|
const require_utils_chunkJSON = require('./chunkJSON.cjs');
|
|
12
|
+
const require_utils_contentWatcherLock = require('./contentWatcherLock.cjs');
|
|
12
13
|
const require_utils_getContentExtension = require('./getContentExtension.cjs');
|
|
13
14
|
const require_utils_getFormatFromExtension = require('./getFormatFromExtension.cjs');
|
|
14
15
|
const require_utils_mergeChunks = require('./mergeChunks.cjs');
|
|
15
16
|
const require_utils_parallelizeGlobal = require('./parallelizeGlobal.cjs');
|
|
17
|
+
const require_utils_parseContentDeclarationFileName = require('./parseContentDeclarationFileName.cjs');
|
|
16
18
|
const require_utils_reduceObjectFormat = require('./reduceObjectFormat.cjs');
|
|
17
19
|
const require_utils_resolveObjectPromises = require('./resolveObjectPromises.cjs');
|
|
18
20
|
const require_utils_resolveRelativePath = require('./resolveRelativePath.cjs');
|
|
@@ -20,18 +22,25 @@ const require_utils_runOnce = require('./runOnce.cjs');
|
|
|
20
22
|
const require_utils_runParallel_index = require('./runParallel/index.cjs');
|
|
21
23
|
const require_utils_sortAlphabetically = require('./sortAlphabetically.cjs');
|
|
22
24
|
const require_utils_verifyIdenticObjectFormat = require('./verifyIdenticObjectFormat.cjs');
|
|
25
|
+
const require_utils_withFileLock = require('./withFileLock.cjs');
|
|
23
26
|
|
|
27
|
+
exports.CLI_CONTENT_WATCHER_ENV_VAR = require_utils_contentWatcherLock.CLI_CONTENT_WATCHER_ENV_VAR;
|
|
24
28
|
exports.Queue = require_utils_pLimit.Queue;
|
|
29
|
+
exports.acquireContentWatcherLock = require_utils_contentWatcherLock.acquireContentWatcherLock;
|
|
25
30
|
exports.assembleJSON = require_utils_chunkJSON.assembleJSON;
|
|
26
31
|
exports.autoDecorateContent = require_utils_autoDecorateContent.autoDecorateContent;
|
|
27
32
|
exports.buildComponentFilesList = require_utils_buildComponentFilesList.buildComponentFilesList;
|
|
28
33
|
exports.chunkArray = require_utils_chunkArray.chunkArray;
|
|
29
34
|
exports.chunkJSON = require_utils_chunkJSON.chunkJSON;
|
|
35
|
+
exports.claimCliContentWatcher = require_utils_contentWatcherLock.claimCliContentWatcher;
|
|
30
36
|
exports.excludeObjectFormat = require_utils_reduceObjectFormat.excludeObjectFormat;
|
|
31
37
|
exports.formatLocale = require_utils_formatter.formatLocale;
|
|
32
38
|
exports.formatPath = require_utils_formatter.formatPath;
|
|
33
39
|
exports.getChunk = require_utils_getChunk.getChunk;
|
|
40
|
+
exports.getCliContentWatcherLabel = require_utils_contentWatcherLock.getCliContentWatcherLabel;
|
|
34
41
|
exports.getContentExtension = require_utils_getContentExtension.getContentExtension;
|
|
42
|
+
exports.getContentWatcherLockFilePath = require_utils_contentWatcherLock.getContentWatcherLockFilePath;
|
|
43
|
+
exports.getContentWatcherOwner = require_utils_contentWatcherLock.getContentWatcherOwner;
|
|
35
44
|
exports.getExtensionFromFormat = require_utils_getFormatFromExtension.getExtensionFromFormat;
|
|
36
45
|
exports.getFormatFromExtension = require_utils_getFormatFromExtension.getFormatFromExtension;
|
|
37
46
|
exports.getGlobalLimiter = require_utils_parallelizeGlobal.getGlobalLimiter;
|
|
@@ -41,12 +50,15 @@ exports.mergeChunks = require_utils_mergeChunks.mergeChunks;
|
|
|
41
50
|
exports.pLimit = require_utils_pLimit.pLimit;
|
|
42
51
|
exports.parallelize = require_utils_parallelize.parallelize;
|
|
43
52
|
exports.parallelizeGlobal = require_utils_parallelizeGlobal.parallelizeGlobal;
|
|
53
|
+
exports.parseContentDeclarationFileName = require_utils_parseContentDeclarationFileName.parseContentDeclarationFileName;
|
|
44
54
|
exports.reconstructFromSingleChunk = require_utils_chunkJSON.reconstructFromSingleChunk;
|
|
45
55
|
exports.reduceObjectFormat = require_utils_reduceObjectFormat.reduceObjectFormat;
|
|
56
|
+
exports.reportRedundantContentWatcher = require_utils_contentWatcherLock.reportRedundantContentWatcher;
|
|
46
57
|
exports.resolveObjectPromises = require_utils_resolveObjectPromises.resolveObjectPromises;
|
|
47
58
|
exports.resolveRelativePath = require_utils_resolveRelativePath.resolveRelativePath;
|
|
48
59
|
exports.runOnce = require_utils_runOnce.runOnce;
|
|
49
60
|
exports.runParallel = require_utils_runParallel_index.runParallel;
|
|
50
61
|
exports.sortAlphabetically = require_utils_sortAlphabetically.sortAlphabetically;
|
|
51
62
|
exports.splitTextByLines = require_utils_splitTextByLine.splitTextByLines;
|
|
52
|
-
exports.verifyIdenticObjectFormat = require_utils_verifyIdenticObjectFormat.verifyIdenticObjectFormat;
|
|
63
|
+
exports.verifyIdenticObjectFormat = require_utils_verifyIdenticObjectFormat.verifyIdenticObjectFormat;
|
|
64
|
+
exports.withFileLock = require_utils_withFileLock.withFileLock;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
let node_path = require("node:path");
|
|
3
|
+
|
|
4
|
+
//#region src/utils/parseContentDeclarationFileName.ts
|
|
5
|
+
/** Fallback used when the file name matches no configured content extension */
|
|
6
|
+
const CONTENT_EXTENSION_PATTERN = /\.content\.[^.]+$/;
|
|
7
|
+
/**
|
|
8
|
+
* Splits a content declaration file name into its dictionary key and its
|
|
9
|
+
* optional locale suffix.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* parseContentDeclarationFileName('src/about.content.ts', configuration)
|
|
13
|
+
* // { key: 'about' }
|
|
14
|
+
*
|
|
15
|
+
* parseContentDeclarationFileName('src/about.fr.content.md', configuration)
|
|
16
|
+
* // { key: 'about', locale: 'fr' }
|
|
17
|
+
*/
|
|
18
|
+
const parseContentDeclarationFileName = (filePath, configuration) => {
|
|
19
|
+
const fileName = (0, node_path.basename)(filePath);
|
|
20
|
+
const matchedExtension = configuration.content.fileExtensions.filter((extension) => fileName.endsWith(extension)).sort((a, b) => b.length - a.length)[0];
|
|
21
|
+
const nameWithoutExtension = matchedExtension ? fileName.slice(0, -matchedExtension.length) : fileName.replace(CONTENT_EXTENSION_PATTERN, "");
|
|
22
|
+
const lastSegmentIndex = nameWithoutExtension.lastIndexOf(".");
|
|
23
|
+
if (lastSegmentIndex <= 0) return { key: nameWithoutExtension };
|
|
24
|
+
const lastSegment = nameWithoutExtension.slice(lastSegmentIndex + 1);
|
|
25
|
+
const matchedLocale = configuration.internationalization.locales.find((locale) => locale.toLowerCase() === lastSegment.toLowerCase());
|
|
26
|
+
if (!matchedLocale) return { key: nameWithoutExtension };
|
|
27
|
+
return {
|
|
28
|
+
key: nameWithoutExtension.slice(0, lastSegmentIndex),
|
|
29
|
+
locale: matchedLocale
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
//#endregion
|
|
34
|
+
exports.parseContentDeclarationFileName = parseContentDeclarationFileName;
|
|
35
|
+
//# sourceMappingURL=parseContentDeclarationFileName.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parseContentDeclarationFileName.cjs","names":["basename"],"sources":["../../../src/utils/parseContentDeclarationFileName.ts"],"sourcesContent":["import { basename } from 'node:path';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { LocalesValues } from '@intlayer/types/module_augmentation';\n\nexport type ContentDeclarationFileName = {\n /** Dictionary key derived from the file name, locale suffix excluded */\n key: string;\n /** Locale declared as a file name suffix, e.g. `about.fr.content.md` -> `fr` */\n locale?: LocalesValues;\n};\n\n/** Fallback used when the file name matches no configured content extension */\nconst CONTENT_EXTENSION_PATTERN = /\\.content\\.[^.]+$/;\n\n/**\n * Splits a content declaration file name into its dictionary key and its\n * optional locale suffix.\n *\n * @example\n * parseContentDeclarationFileName('src/about.content.ts', configuration)\n * // { key: 'about' }\n *\n * parseContentDeclarationFileName('src/about.fr.content.md', configuration)\n * // { key: 'about', locale: 'fr' }\n */\nexport const parseContentDeclarationFileName = (\n filePath: string,\n configuration: IntlayerConfig\n): ContentDeclarationFileName => {\n const fileName = basename(filePath);\n\n // Longest match first so `.content.mdx` wins over a hypothetical `.mdx`\n const matchedExtension = configuration.content.fileExtensions\n .filter((extension) => fileName.endsWith(extension))\n .sort((a, b) => b.length - a.length)[0];\n\n const nameWithoutExtension = matchedExtension\n ? fileName.slice(0, -matchedExtension.length)\n : fileName.replace(CONTENT_EXTENSION_PATTERN, '');\n\n const lastSegmentIndex = nameWithoutExtension.lastIndexOf('.');\n\n // A name made of a single segment is the key itself, never a locale\n if (lastSegmentIndex <= 0) return { key: nameWithoutExtension };\n\n const lastSegment = nameWithoutExtension.slice(lastSegmentIndex + 1);\n const matchedLocale = configuration.internationalization.locales.find(\n (locale) => locale.toLowerCase() === lastSegment.toLowerCase()\n );\n\n if (!matchedLocale) return { key: nameWithoutExtension };\n\n return {\n key: nameWithoutExtension.slice(0, lastSegmentIndex),\n locale: matchedLocale,\n };\n};\n"],"mappings":";;;;;AAYA,MAAM,4BAA4B;;;;;;;;;;;;AAalC,MAAa,mCACX,UACA,kBAC+B;CAC/B,MAAM,eAAWA,oBAAS,QAAQ;CAGlC,MAAM,mBAAmB,cAAc,QAAQ,eAC5C,QAAQ,cAAc,SAAS,SAAS,SAAS,CAAC,CAAC,CACnD,MAAM,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;CAEvC,MAAM,uBAAuB,mBACzB,SAAS,MAAM,GAAG,CAAC,iBAAiB,MAAM,IAC1C,SAAS,QAAQ,2BAA2B,EAAE;CAElD,MAAM,mBAAmB,qBAAqB,YAAY,GAAG;CAG7D,IAAI,oBAAoB,GAAG,OAAO,EAAE,KAAK,qBAAqB;CAE9D,MAAM,cAAc,qBAAqB,MAAM,mBAAmB,CAAC;CACnE,MAAM,gBAAgB,cAAc,qBAAqB,QAAQ,MAC9D,WAAW,OAAO,YAAY,MAAM,YAAY,YAAY,CAC/D;CAEA,IAAI,CAAC,eAAe,OAAO,EAAE,KAAK,qBAAqB;CAEvD,OAAO;EACL,KAAK,qBAAqB,MAAM,GAAG,gBAAgB;EACnD,QAAQ;CACV;AACF"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
let node_fs_promises = require("node:fs/promises");
|
|
3
|
+
let node_path = require("node:path");
|
|
4
|
+
let node_fs = require("node:fs");
|
|
5
|
+
|
|
6
|
+
//#region src/utils/withFileLock.ts
|
|
7
|
+
const DEFAULT_OPTIONS = {
|
|
8
|
+
staleTimeoutMs: 3e4,
|
|
9
|
+
acquireTimeoutMs: 6e4
|
|
10
|
+
};
|
|
11
|
+
/** Delay between two attempts to take a lock held by someone else. */
|
|
12
|
+
const POLL_INTERVAL_MS = 25;
|
|
13
|
+
const delay = (durationMs) => new Promise((resolve) => setTimeout(resolve, durationMs));
|
|
14
|
+
/**
|
|
15
|
+
* Locks held by this process, released synchronously on exit so a crash never
|
|
16
|
+
* leaves a lock file that would stall every later run for `staleTimeoutMs`.
|
|
17
|
+
*/
|
|
18
|
+
const ownedLockFilePaths = /* @__PURE__ */ new Set();
|
|
19
|
+
process.on("exit", () => {
|
|
20
|
+
for (const lockFilePath of ownedLockFilePaths) try {
|
|
21
|
+
(0, node_fs.rmSync)(lockFilePath, { force: true });
|
|
22
|
+
} catch {}
|
|
23
|
+
});
|
|
24
|
+
/**
|
|
25
|
+
* Whether the process holding the lock is still alive. An unreadable PID is
|
|
26
|
+
* reported as alive so {@link WithFileLockOptions.staleTimeoutMs} stays the
|
|
27
|
+
* only way to reclaim the lock.
|
|
28
|
+
*/
|
|
29
|
+
const getIsOwnerAlive = (pid) => {
|
|
30
|
+
if (!pid || pid === process.pid) return true;
|
|
31
|
+
try {
|
|
32
|
+
process.kill(pid, 0);
|
|
33
|
+
return true;
|
|
34
|
+
} catch (error) {
|
|
35
|
+
return error.code === "EPERM";
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const readLockOwner = async (lockFilePath) => {
|
|
39
|
+
try {
|
|
40
|
+
const raw = await (0, node_fs_promises.readFile)(lockFilePath, "utf8");
|
|
41
|
+
const parsed = JSON.parse(raw);
|
|
42
|
+
return {
|
|
43
|
+
pid: parsed.pid ?? 0,
|
|
44
|
+
acquiredAt: parsed.acquiredAt ?? 0
|
|
45
|
+
};
|
|
46
|
+
} catch {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const releaseLock = async (lockFilePath) => {
|
|
51
|
+
try {
|
|
52
|
+
await (0, node_fs_promises.unlink)(lockFilePath);
|
|
53
|
+
} catch {}
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Attempts to create the lock file. `wx` makes the creation atomic, so exactly
|
|
57
|
+
* one process wins even when several try at the same moment.
|
|
58
|
+
*/
|
|
59
|
+
const tryAcquireLock = async (lockFilePath) => {
|
|
60
|
+
const data = JSON.stringify({
|
|
61
|
+
pid: process.pid,
|
|
62
|
+
acquiredAt: Date.now()
|
|
63
|
+
});
|
|
64
|
+
for (let attempt = 0; attempt < 2; attempt++) try {
|
|
65
|
+
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(lockFilePath), { recursive: true });
|
|
66
|
+
await (0, node_fs_promises.writeFile)(lockFilePath, data, { flag: "wx" });
|
|
67
|
+
return true;
|
|
68
|
+
} catch (error) {
|
|
69
|
+
const code = error.code;
|
|
70
|
+
if (code === "EEXIST") return false;
|
|
71
|
+
if (code === "ENOENT" && attempt === 0) continue;
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Runs `callback` under a cross-process mutex materialised by a lock file.
|
|
78
|
+
*
|
|
79
|
+
* Unlike `runOnce`, which lets one process do the work and the others skip it,
|
|
80
|
+
* every caller here runs the callback — just never at the same time. This is
|
|
81
|
+
* what shared, read-modify-write outputs need (e.g. building the `.intlayer`
|
|
82
|
+
* dictionaries, which merges every dictionary on disk): concurrent runs would
|
|
83
|
+
* each read a half-written state.
|
|
84
|
+
*
|
|
85
|
+
* The lock is released even when the callback throws, and its error is
|
|
86
|
+
* re-thrown to the caller unchanged.
|
|
87
|
+
*
|
|
88
|
+
* @param lockFilePath - Path of the lock file coordinating the callers.
|
|
89
|
+
* @param callback - The critical section.
|
|
90
|
+
* @param options - Staleness and acquisition timeouts.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```typescript
|
|
94
|
+
* await withFileLock(join(cacheDir, 'build-dictionary.lock'), () =>
|
|
95
|
+
* buildDictionary([dictionary], configuration)
|
|
96
|
+
* );
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
const withFileLock = async (lockFilePath, callback, options) => {
|
|
100
|
+
const { staleTimeoutMs, acquireTimeoutMs } = {
|
|
101
|
+
...DEFAULT_OPTIONS,
|
|
102
|
+
...options ?? {}
|
|
103
|
+
};
|
|
104
|
+
const acquireDeadline = Date.now() + acquireTimeoutMs;
|
|
105
|
+
let hasAcquiredLock = false;
|
|
106
|
+
while (Date.now() < acquireDeadline) {
|
|
107
|
+
if (await tryAcquireLock(lockFilePath)) {
|
|
108
|
+
hasAcquiredLock = true;
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
const owner = await readLockOwner(lockFilePath);
|
|
112
|
+
if (owner) {
|
|
113
|
+
if (!getIsOwnerAlive(owner.pid) || Date.now() - owner.acquiredAt > staleTimeoutMs) {
|
|
114
|
+
await releaseLock(lockFilePath);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
await delay(POLL_INTERVAL_MS);
|
|
119
|
+
}
|
|
120
|
+
if (hasAcquiredLock) ownedLockFilePaths.add(lockFilePath);
|
|
121
|
+
try {
|
|
122
|
+
return await callback();
|
|
123
|
+
} finally {
|
|
124
|
+
if (hasAcquiredLock) {
|
|
125
|
+
ownedLockFilePaths.delete(lockFilePath);
|
|
126
|
+
await releaseLock(lockFilePath);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
//#endregion
|
|
132
|
+
exports.withFileLock = withFileLock;
|
|
133
|
+
//# sourceMappingURL=withFileLock.cjs.map
|