@intlayer/cli 8.9.7 → 8.10.0-canary.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 +14 -0
- package/dist/cjs/IntlayerEventListener.cjs.map +1 -1
- package/dist/cjs/auth/login.cjs +3 -1
- package/dist/cjs/auth/login.cjs.map +1 -1
- package/dist/cjs/auth/sessionToken.cjs.map +1 -1
- package/dist/cjs/build.cjs.map +1 -1
- package/dist/cjs/bundle.cjs.map +1 -1
- package/dist/cjs/ci.cjs.map +1 -1
- package/dist/cjs/cli.cjs.map +1 -1
- package/dist/cjs/config.cjs +1 -1
- package/dist/cjs/config.cjs.map +1 -1
- package/dist/cjs/editor.cjs.map +1 -1
- package/dist/cjs/extract.cjs.map +1 -1
- package/dist/cjs/fill/deepMergeContent.cjs.map +1 -1
- package/dist/cjs/fill/extractTranslatableContent.cjs.map +1 -1
- package/dist/cjs/fill/fill.cjs.map +1 -1
- package/dist/cjs/fill/formatAutoFilledFilePath.cjs.map +1 -1
- package/dist/cjs/fill/formatFillData.cjs.map +1 -1
- package/dist/cjs/fill/getAvailableLocalesInDictionary.cjs.map +1 -1
- package/dist/cjs/fill/getFilterMissingContentPerLocale.cjs.map +1 -1
- package/dist/cjs/fill/listTranslationsTasks.cjs.map +1 -1
- package/dist/cjs/fill/translateDictionary.cjs +2 -2
- package/dist/cjs/fill/translateDictionary.cjs.map +1 -1
- package/dist/cjs/fill/writeFill.cjs.map +1 -1
- package/dist/cjs/getTargetDictionary.cjs.map +1 -1
- package/dist/cjs/init.cjs.map +1 -1
- package/dist/cjs/initMCP.cjs.map +1 -1
- package/dist/cjs/initSkills.cjs.map +1 -1
- package/dist/cjs/listContentDeclaration.cjs.map +1 -1
- package/dist/cjs/listProjects.cjs.map +1 -1
- package/dist/cjs/liveSync.cjs.map +1 -1
- package/dist/cjs/pull.cjs +3 -4
- package/dist/cjs/pull.cjs.map +1 -1
- package/dist/cjs/push/pullLog.cjs.map +1 -1
- package/dist/cjs/push/push.cjs +1 -2
- package/dist/cjs/push/push.cjs.map +1 -1
- package/dist/cjs/pushConfig.cjs +6 -4
- package/dist/cjs/pushConfig.cjs.map +1 -1
- package/dist/cjs/pushLog.cjs.map +1 -1
- package/dist/cjs/reviewDoc/reviewDoc.cjs.map +1 -1
- package/dist/cjs/reviewDoc/reviewDocBlockAware.cjs.map +1 -1
- package/dist/cjs/searchDoc.cjs.map +1 -1
- package/dist/cjs/test/listMissingTranslations.cjs.map +1 -1
- package/dist/cjs/test/test.cjs.map +1 -1
- package/dist/cjs/translateDoc/translateDoc.cjs.map +1 -1
- package/dist/cjs/translateDoc/translateFile.cjs.map +1 -1
- package/dist/cjs/translateDoc/validation.cjs.map +1 -1
- package/dist/cjs/translation-alignment/alignBlocks.cjs.map +1 -1
- package/dist/cjs/translation-alignment/computeSimilarity.cjs.map +1 -1
- package/dist/cjs/translation-alignment/fingerprintBlock.cjs.map +1 -1
- package/dist/cjs/translation-alignment/mapChangedLinesToBlocks.cjs.map +1 -1
- package/dist/cjs/translation-alignment/normalizeBlock.cjs.map +1 -1
- package/dist/cjs/translation-alignment/pipeline.cjs.map +1 -1
- package/dist/cjs/translation-alignment/planActions.cjs.map +1 -1
- package/dist/cjs/translation-alignment/rebuildDocument.cjs.map +1 -1
- package/dist/cjs/translation-alignment/segmentDocument.cjs.map +1 -1
- package/dist/cjs/utils/calculateChunks.cjs.map +1 -1
- package/dist/cjs/utils/checkAccess.cjs +6 -0
- package/dist/cjs/utils/checkAccess.cjs.map +1 -1
- package/dist/cjs/utils/checkConfigConsistency.cjs.map +1 -1
- package/dist/cjs/utils/checkFileModifiedRange.cjs.map +1 -1
- package/dist/cjs/utils/checkLastUpdateTime.cjs.map +1 -1
- package/dist/cjs/utils/chunkInference.cjs +2 -2
- package/dist/cjs/utils/chunkInference.cjs.map +1 -1
- package/dist/cjs/utils/fixChunkStartEndChars.cjs.map +1 -1
- package/dist/cjs/utils/formatTimeDiff.cjs.map +1 -1
- package/dist/cjs/utils/getIsFileUpdatedRecently.cjs.map +1 -1
- package/dist/cjs/utils/getOutputFilePath.cjs.map +1 -1
- package/dist/cjs/utils/getParentPackageJSON.cjs.map +1 -1
- package/dist/cjs/utils/listSpecialChars.cjs.map +1 -1
- package/dist/cjs/utils/mapChunksBetweenFiles.cjs.map +1 -1
- package/dist/cjs/utils/openBrowser.cjs.map +1 -1
- package/dist/cjs/utils/reorderParagraphs.cjs.map +1 -1
- package/dist/cjs/utils/setupAI.cjs.map +1 -1
- package/dist/cjs/watch.cjs.map +1 -1
- package/dist/esm/IntlayerEventListener.mjs.map +1 -1
- package/dist/esm/auth/login.mjs +3 -1
- package/dist/esm/auth/login.mjs.map +1 -1
- package/dist/esm/auth/sessionToken.mjs.map +1 -1
- package/dist/esm/build.mjs.map +1 -1
- package/dist/esm/bundle.mjs.map +1 -1
- package/dist/esm/ci.mjs.map +1 -1
- package/dist/esm/cli.mjs.map +1 -1
- package/dist/esm/config.mjs +2 -2
- package/dist/esm/config.mjs.map +1 -1
- package/dist/esm/editor.mjs.map +1 -1
- package/dist/esm/extract.mjs.map +1 -1
- package/dist/esm/fill/deepMergeContent.mjs.map +1 -1
- package/dist/esm/fill/extractTranslatableContent.mjs.map +1 -1
- package/dist/esm/fill/fill.mjs.map +1 -1
- package/dist/esm/fill/formatAutoFilledFilePath.mjs.map +1 -1
- package/dist/esm/fill/formatFillData.mjs.map +1 -1
- package/dist/esm/fill/getAvailableLocalesInDictionary.mjs.map +1 -1
- package/dist/esm/fill/getFilterMissingContentPerLocale.mjs.map +1 -1
- package/dist/esm/fill/listTranslationsTasks.mjs.map +1 -1
- package/dist/esm/fill/translateDictionary.mjs +2 -2
- package/dist/esm/fill/translateDictionary.mjs.map +1 -1
- package/dist/esm/fill/writeFill.mjs.map +1 -1
- package/dist/esm/getTargetDictionary.mjs.map +1 -1
- package/dist/esm/init.mjs.map +1 -1
- package/dist/esm/initMCP.mjs.map +1 -1
- package/dist/esm/initSkills.mjs.map +1 -1
- package/dist/esm/listContentDeclaration.mjs.map +1 -1
- package/dist/esm/listProjects.mjs.map +1 -1
- package/dist/esm/liveSync.mjs.map +1 -1
- package/dist/esm/pull.mjs +4 -5
- package/dist/esm/pull.mjs.map +1 -1
- package/dist/esm/push/pullLog.mjs.map +1 -1
- package/dist/esm/push/push.mjs +2 -3
- package/dist/esm/push/push.mjs.map +1 -1
- package/dist/esm/pushConfig.mjs +8 -6
- package/dist/esm/pushConfig.mjs.map +1 -1
- package/dist/esm/pushLog.mjs.map +1 -1
- package/dist/esm/reviewDoc/reviewDoc.mjs.map +1 -1
- package/dist/esm/reviewDoc/reviewDocBlockAware.mjs.map +1 -1
- package/dist/esm/searchDoc.mjs.map +1 -1
- package/dist/esm/test/listMissingTranslations.mjs.map +1 -1
- package/dist/esm/test/test.mjs.map +1 -1
- package/dist/esm/translateDoc/translateDoc.mjs.map +1 -1
- package/dist/esm/translateDoc/translateFile.mjs.map +1 -1
- package/dist/esm/translateDoc/validation.mjs.map +1 -1
- package/dist/esm/translation-alignment/alignBlocks.mjs.map +1 -1
- package/dist/esm/translation-alignment/computeSimilarity.mjs.map +1 -1
- package/dist/esm/translation-alignment/fingerprintBlock.mjs.map +1 -1
- package/dist/esm/translation-alignment/mapChangedLinesToBlocks.mjs.map +1 -1
- package/dist/esm/translation-alignment/normalizeBlock.mjs.map +1 -1
- package/dist/esm/translation-alignment/pipeline.mjs.map +1 -1
- package/dist/esm/translation-alignment/planActions.mjs.map +1 -1
- package/dist/esm/translation-alignment/rebuildDocument.mjs.map +1 -1
- package/dist/esm/translation-alignment/segmentDocument.mjs.map +1 -1
- package/dist/esm/utils/calculateChunks.mjs.map +1 -1
- package/dist/esm/utils/checkAccess.mjs +6 -1
- package/dist/esm/utils/checkAccess.mjs.map +1 -1
- package/dist/esm/utils/checkConfigConsistency.mjs.map +1 -1
- package/dist/esm/utils/checkFileModifiedRange.mjs.map +1 -1
- package/dist/esm/utils/checkLastUpdateTime.mjs.map +1 -1
- package/dist/esm/utils/chunkInference.mjs +2 -2
- package/dist/esm/utils/chunkInference.mjs.map +1 -1
- package/dist/esm/utils/fixChunkStartEndChars.mjs.map +1 -1
- package/dist/esm/utils/formatTimeDiff.mjs.map +1 -1
- package/dist/esm/utils/getIsFileUpdatedRecently.mjs.map +1 -1
- package/dist/esm/utils/getOutputFilePath.mjs.map +1 -1
- package/dist/esm/utils/getParentPackageJSON.mjs.map +1 -1
- package/dist/esm/utils/listSpecialChars.mjs.map +1 -1
- package/dist/esm/utils/mapChunksBetweenFiles.mjs.map +1 -1
- package/dist/esm/utils/openBrowser.mjs.map +1 -1
- package/dist/esm/utils/reorderParagraphs.mjs.map +1 -1
- package/dist/esm/utils/setupAI.mjs.map +1 -1
- package/dist/esm/watch.mjs.map +1 -1
- package/dist/types/IntlayerEventListener.d.ts.map +1 -1
- package/dist/types/auth/login.d.ts.map +1 -1
- package/dist/types/auth/sessionToken.d.ts +1 -1
- package/dist/types/auth/sessionToken.d.ts.map +1 -1
- package/dist/types/build.d.ts.map +1 -1
- package/dist/types/bundle.d.ts.map +1 -1
- package/dist/types/cli.d.ts.map +1 -1
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/editor.d.ts.map +1 -1
- package/dist/types/extract.d.ts.map +1 -1
- package/dist/types/fill/extractTranslatableContent.d.ts.map +1 -1
- package/dist/types/fill/fill.d.ts.map +1 -1
- package/dist/types/fill/formatFillData.d.ts.map +1 -1
- package/dist/types/fill/getAvailableLocalesInDictionary.d.ts.map +1 -1
- package/dist/types/fill/translateDictionary.d.ts +1 -2
- package/dist/types/fill/translateDictionary.d.ts.map +1 -1
- package/dist/types/index.d.ts +4 -4
- package/dist/types/init.d.ts.map +1 -1
- package/dist/types/initSkills.d.ts.map +1 -1
- package/dist/types/listContentDeclaration.d.ts.map +1 -1
- package/dist/types/listProjects.d.ts.map +1 -1
- package/dist/types/liveSync.d.ts.map +1 -1
- package/dist/types/pull.d.ts.map +1 -1
- package/dist/types/push/pullLog.d.ts.map +1 -1
- package/dist/types/push/push.d.ts.map +1 -1
- package/dist/types/pushConfig.d.ts.map +1 -1
- package/dist/types/pushLog.d.ts.map +1 -1
- package/dist/types/searchDoc.d.ts.map +1 -1
- package/dist/types/test/test.d.ts.map +1 -1
- package/dist/types/translateDoc/types.d.ts.map +1 -1
- package/dist/types/translateDoc/validation.d.ts.map +1 -1
- package/dist/types/translation-alignment/computeSimilarity.d.ts.map +1 -1
- package/dist/types/translation-alignment/normalizeBlock.d.ts.map +1 -1
- package/dist/types/translation-alignment/pipeline.d.ts.map +1 -1
- package/dist/types/translation-alignment/rebuildDocument.d.ts.map +1 -1
- package/dist/types/translation-alignment/segmentDocument.d.ts.map +1 -1
- package/dist/types/translation-alignment/types.d.ts.map +1 -1
- package/dist/types/utils/calculateChunks.d.ts.map +1 -1
- package/dist/types/utils/checkAccess.d.ts +3 -2
- package/dist/types/utils/checkAccess.d.ts.map +1 -1
- package/dist/types/utils/checkConfigConsistency.d.ts.map +1 -1
- package/dist/types/utils/checkFileModifiedRange.d.ts.map +1 -1
- package/dist/types/utils/checkLastUpdateTime.d.ts.map +1 -1
- package/dist/types/utils/chunkInference.d.ts.map +1 -1
- package/dist/types/utils/fixChunkStartEndChars.d.ts.map +1 -1
- package/dist/types/utils/formatTimeDiff.d.ts.map +1 -1
- package/dist/types/utils/getIsFileUpdatedRecently.d.ts.map +1 -1
- package/dist/types/utils/getOutputFilePath.d.ts.map +1 -1
- package/dist/types/utils/getParentPackageJSON.d.ts.map +1 -1
- package/dist/types/utils/listSpecialChars.d.ts.map +1 -1
- package/dist/types/utils/mapChunksBetweenFiles.d.ts.map +1 -1
- package/dist/types/utils/openBrowser.d.ts.map +1 -1
- package/dist/types/utils/reorderParagraphs.d.ts.map +1 -1
- package/dist/types/utils/setupAI.d.ts +1 -2
- package/dist/types/utils/setupAI.d.ts.map +1 -1
- package/dist/types/watch.d.ts.map +1 -1
- package/package.json +13 -13
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translateDictionary.mjs","names":[],"sources":["../../../src/fill/translateDictionary.ts"],"sourcesContent":["import { basename } from 'node:path';\nimport type { AIConfig } from '@intlayer/ai';\nimport { type AIOptions, getIntlayerAPIProxy } from '@intlayer/api';\nimport {\n chunkJSON,\n excludeObjectFormat,\n formatLocale,\n type JsonChunk,\n mergeChunks,\n reconstructFromSingleChunk,\n verifyIdenticObjectFormat,\n} from '@intlayer/chokidar/utils';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport {\n colon,\n colorize,\n colorizeNumber,\n colorizePath,\n getAppLogger,\n} from '@intlayer/config/logger';\nimport { retryManager } from '@intlayer/config/utils';\nimport {\n getFilterMissingTranslationsDictionary,\n getMultilingualDictionary,\n getPerLocaleDictionary,\n insertContentInDictionary,\n} from '@intlayer/core/plugins';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { Dictionary } from '@intlayer/types/dictionary';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\nimport type { AIClient } from '../utils/setupAI';\nimport { deepMergeContent } from './deepMergeContent';\nimport {\n extractTranslatableContent,\n reinsertTranslatedContent,\n} from './extractTranslatableContent';\nimport type { TranslationTask } from './listTranslationsTasks';\n\ntype TranslateDictionaryResult = TranslationTask & {\n dictionaryOutput: Dictionary | null;\n};\n\ntype TranslateDictionaryOptions = {\n mode: 'complete' | 'review';\n aiOptions?: AIOptions;\n fillMetadata?: boolean;\n onHandle?: ReturnType<\n typeof import('@intlayer/chokidar/utils').getGlobalLimiter\n >;\n onSuccess?: () => void;\n onError?: (error: unknown) => void;\n getAbortError?: () => Error | null;\n aiClient?: AIClient;\n aiConfig?: AIConfig;\n};\n\nconst createChunkPreset = (chunkIndex: number, totalChunks: number) => {\n if (totalChunks <= 1) return '';\n return colon(\n [\n colorize('[', ANSIColors.GREY_DARK),\n colorizeNumber(chunkIndex + 1),\n colorize(`/${totalChunks}`, ANSIColors.GREY_DARK),\n colorize(']', ANSIColors.GREY_DARK),\n ].join(''),\n { colSize: 5 }\n );\n};\n\nconst hasMissingMetadata = (dictionary: Dictionary) =>\n !dictionary.description || !dictionary.title || !dictionary.tags;\n\nconst serializeError = (error: unknown): string => {\n if (error instanceof Error) {\n return error.cause\n ? `${error.message} (cause: ${String(error.cause)})`\n : error.message;\n }\n if (typeof error === 'string') return error;\n try {\n return JSON.stringify(error);\n } catch {\n return String(error);\n }\n};\n\nconst CHUNK_SIZE = 4000;\nconst GROUP_MAX_RETRY = 2;\nconst MAX_RETRY = 3;\nconst RETRY_DELAY = 1000 * 10; // 10 seconds\n\nconst MAX_FOLLOWING_ERRORS = 10; // 10 errors in a row, hard exit the process\nlet followingErrors = 0;\n\nexport const translateDictionary = async (\n task: TranslationTask,\n configuration: IntlayerConfig,\n options?: TranslateDictionaryOptions\n): Promise<TranslateDictionaryResult> => {\n const appLogger = getAppLogger(configuration);\n const intlayerAPI = getIntlayerAPIProxy(undefined, configuration);\n\n const { mode, aiOptions, fillMetadata, aiClient, aiConfig } = {\n mode: 'complete',\n fillMetadata: true,\n ...options,\n } as const;\n\n const notifySuccess = () => {\n followingErrors = 0;\n options?.onSuccess?.();\n };\n\n const result = await retryManager(\n async () => {\n const unmergedDictionariesRecord = getUnmergedDictionaries(configuration);\n\n const baseUnmergedDictionary: Dictionary | undefined =\n unmergedDictionariesRecord[task.dictionaryKey].find(\n (dict) => dict.localId === task.dictionaryLocalId\n );\n\n if (!baseUnmergedDictionary) {\n appLogger(\n `${task.dictionaryPreset}Dictionary not found in unmergedDictionariesRecord. Skipping.`,\n {\n level: 'warn',\n }\n );\n return { ...task, dictionaryOutput: null };\n }\n\n let metadata:\n | Pick<Dictionary, 'description' | 'title' | 'tags'>\n | undefined;\n\n if (\n fillMetadata &&\n (hasMissingMetadata(baseUnmergedDictionary) || mode === 'review')\n ) {\n const defaultLocaleDictionary = getPerLocaleDictionary(\n baseUnmergedDictionary,\n configuration.internationalization.defaultLocale\n );\n\n appLogger(\n `${task.dictionaryPreset} Filling missing metadata for ${colorizePath(basename(baseUnmergedDictionary.filePath!))}`,\n {\n level: 'info',\n }\n );\n\n const runAudit = async () => {\n if (aiClient && aiConfig) {\n const result = await aiClient.auditDictionaryMetadata({\n fileContent: JSON.stringify(defaultLocaleDictionary),\n aiConfig,\n });\n\n return {\n data: result,\n };\n }\n\n return await intlayerAPI.ai.auditContentDeclarationMetadata({\n fileContent: JSON.stringify(defaultLocaleDictionary),\n aiOptions,\n });\n };\n\n const metadataResult = options?.onHandle\n ? await options.onHandle(runAudit)\n : await runAudit();\n\n metadata = metadataResult.data?.fileContent;\n }\n\n const translatedContentResults = await Promise.all(\n task.targetLocales.map(async (targetLocale) => {\n /**\n * In complete mode, for large dictionaries, we want to filter all content that is already translated\n *\n * targetLocale: fr\n *\n * { test1: t({ ar: 'Hello', en: 'Hello', fr: 'Bonjour' } }) -> {}\n * { test2: t({ ar: 'Hello', en: 'Hello' }) } -> { test2: t({ ar: 'Hello', en: 'Hello' }) }\n *\n */\n // Reset to base dictionary for each locale to ensure we filter from the original\n let dictionaryToProcess = structuredClone(baseUnmergedDictionary);\n\n let targetLocaleDictionary: Dictionary;\n\n if (typeof baseUnmergedDictionary.locale === 'string') {\n // For per-locale files, the content is already in simple JSON format (not translation nodes)\n // The base dictionary is already the source locale content\n\n // Look up the target locale dictionary by its locale property.\n // This handles both directory-based paths (/en/file.json → /es/file.json)\n // AND filename-based paths (messages_ICU/en.json → messages_ICU/es.json).\n // Plugins like syncJSON set `dict.locale` correctly, so matching by locale\n // is the only reliable strategy — path string-replacement fails when the\n // locale appears in the filename rather than a directory segment.\n const targetUnmergedDictionary = unmergedDictionariesRecord[\n task.dictionaryKey\n ]?.find(\n (dict) =>\n dict.locale === targetLocale &&\n dict.filePath !== baseUnmergedDictionary.filePath\n );\n\n // Derive the target file path for the fallback (when the file doesn't exist yet).\n // Try directory-style first (/en/ → /es/), then filename-style (en.json → es.json).\n const sourceFilePath = baseUnmergedDictionary.filePath ?? '';\n const derivedTargetFilePath = sourceFilePath.includes(\n `/${task.sourceLocale}/`\n )\n ? sourceFilePath.replace(\n new RegExp(`/${task.sourceLocale}/`, 'g'),\n `/${targetLocale}/`\n )\n : sourceFilePath.replace(\n new RegExp(`(^|/)${task.sourceLocale}(\\\\.[^/]+)$`),\n `$1${targetLocale}$2`\n );\n\n targetLocaleDictionary = targetUnmergedDictionary ?? {\n key: baseUnmergedDictionary.key,\n content: {},\n filePath: derivedTargetFilePath || undefined,\n locale: targetLocale,\n };\n } else {\n // For multilingual dictionaries\n if (mode === 'complete') {\n // Remove all nodes that don't have any content to translate\n dictionaryToProcess = getFilterMissingTranslationsDictionary(\n dictionaryToProcess,\n targetLocale\n );\n }\n\n dictionaryToProcess = getPerLocaleDictionary(\n dictionaryToProcess,\n task.sourceLocale\n );\n\n targetLocaleDictionary = getPerLocaleDictionary(\n baseUnmergedDictionary,\n targetLocale\n );\n }\n\n // Filter to only untranslated fields, preserving explicit null values as\n // default-locale fallback markers. Applied after both paths converge so\n // the same logic covers per-locale and multilingual dictionaries.\n if (mode === 'complete') {\n dictionaryToProcess = {\n ...dictionaryToProcess,\n content:\n excludeObjectFormat(\n dictionaryToProcess.content,\n targetLocaleDictionary.content\n ) ?? {},\n };\n }\n\n const localePreset = colon(\n [\n colorize('[', ANSIColors.GREY_DARK),\n formatLocale(targetLocale),\n colorize(']', ANSIColors.GREY_DARK),\n ].join(''),\n { colSize: 18 }\n );\n\n appLogger(\n `${task.dictionaryPreset}${localePreset} Preparing ${colorizePath(basename(targetLocaleDictionary.filePath!))}`,\n {\n level: 'info',\n }\n );\n\n const chunkedJsonContent: JsonChunk[] = chunkJSON(\n dictionaryToProcess.content as unknown as Record<string, any>,\n CHUNK_SIZE\n );\n\n const nbOfChunks = chunkedJsonContent.length;\n\n if (nbOfChunks > 1) {\n appLogger(\n `${task.dictionaryPreset}${localePreset} Split into ${colorizeNumber(nbOfChunks)} chunks for translation`,\n {\n level: 'info',\n }\n );\n }\n\n const chunkResult: JsonChunk[] = [];\n\n // Process chunks in parallel (globally throttled) to allow concurrent translation\n const chunkPromises = chunkedJsonContent.map(async (chunk) => {\n const chunkPreset = createChunkPreset(chunk.index, chunk.total);\n\n if (nbOfChunks > 1) {\n appLogger(\n `${task.dictionaryPreset}${localePreset}${chunkPreset} Translating chunk`,\n {\n level: 'info',\n }\n );\n }\n\n const reconstructedChunk = reconstructFromSingleChunk(chunk);\n const {\n extractedContent: chunkExtractedContent,\n translatableDictionary: chunkTranslatableDictionary,\n } = extractTranslatableContent(reconstructedChunk);\n\n const executeTranslation = async () => {\n return await retryManager(\n async () => {\n let translationResult: any;\n\n if (aiClient && aiConfig) {\n translationResult = await aiClient.translateJSON({\n entryFileContent:\n chunkTranslatableDictionary as unknown as JSON,\n presetOutputContent: chunkTranslatableDictionary,\n dictionaryDescription:\n dictionaryToProcess.description ??\n metadata?.description ??\n '',\n entryLocale: task.sourceLocale,\n outputLocale: targetLocale,\n mode,\n aiConfig,\n });\n } else {\n translationResult = await intlayerAPI.ai\n .translateJSON({\n entryFileContent:\n chunkTranslatableDictionary as unknown as JSON,\n presetOutputContent: chunkTranslatableDictionary,\n dictionaryDescription:\n dictionaryToProcess.description ??\n metadata?.description ??\n '',\n entryLocale: task.sourceLocale,\n outputLocale: targetLocale,\n mode,\n aiOptions,\n })\n .then((result) => result.data);\n }\n\n if (!translationResult?.fileContent) {\n throw new Error('No content result');\n }\n\n const { isIdentic, error } = verifyIdenticObjectFormat(\n translationResult.fileContent,\n chunkTranslatableDictionary\n );\n\n if (!isIdentic) {\n throw new Error(\n `Translation result does not match expected format: ${error}`\n );\n }\n\n notifySuccess();\n return reinsertTranslatedContent(\n reconstructedChunk,\n chunkExtractedContent,\n translationResult.fileContent as Record<number, string>\n );\n },\n {\n maxRetry: MAX_RETRY,\n delay: RETRY_DELAY,\n onError: ({ error, attempt, maxRetry }) => {\n const chunkPreset = createChunkPreset(\n chunk.index,\n chunk.total\n );\n appLogger(\n `${task.dictionaryPreset}${localePreset}${chunkPreset} ${colorize('Error filling:', ANSIColors.RED)} ${colorize(serializeError(error), ANSIColors.GREY_DARK)} - Attempt ${colorizeNumber(attempt + 1)} of ${colorizeNumber(maxRetry)}`,\n {\n level: 'error',\n }\n );\n\n followingErrors += 1;\n\n if (followingErrors >= MAX_FOLLOWING_ERRORS) {\n appLogger(`There is something wrong.`, {\n level: 'error',\n });\n process.exit(1); // 1 for error\n }\n },\n }\n )();\n };\n\n const wrapped = options?.onHandle\n ? options.onHandle(executeTranslation) // queued in global limiter\n : executeTranslation(); // no global limiter\n\n return wrapped.then((result) => ({ chunk, result }));\n });\n\n // Wait for all chunks for this locale in parallel (still capped by global limiter)\n const chunkResults = await Promise.all(chunkPromises);\n\n // Maintain order\n chunkResults\n .sort((chunkA, chunkB) => chunkA.chunk.index - chunkB.chunk.index)\n .forEach(({ result }) => {\n chunkResult.push(result);\n });\n\n // Merge translated chunk contents back into a single content object\n const reinsertedContent = mergeChunks(chunkResult);\n\n const merged = {\n ...dictionaryToProcess,\n content: reinsertedContent,\n };\n\n // Merge newly translated content (including explicit null fallbacks) back\n // into the existing target locale content. Applies to both per-locale and\n // multilingual paths so the target always retains previously translated\n // fields and receives null markers where the source has no translation.\n const finalContent = deepMergeContent(\n targetLocaleDictionary.content ?? {},\n merged.content\n );\n\n return [targetLocale, finalContent] as const;\n })\n );\n\n const translatedContent: Partial<Record<Locale, Dictionary['content']>> =\n Object.fromEntries(translatedContentResults);\n\n const baseDictionary = baseUnmergedDictionary.locale\n ? {\n ...baseUnmergedDictionary,\n key: baseUnmergedDictionary.key!,\n content: {},\n }\n : baseUnmergedDictionary;\n\n let dictionaryOutput: Dictionary = {\n ...getMultilingualDictionary(baseDictionary),\n locale: undefined, // Ensure the dictionary is multilingual\n ...metadata,\n };\n\n for (const targetLocale of task.targetLocales) {\n if (translatedContent[targetLocale]) {\n dictionaryOutput = insertContentInDictionary(\n dictionaryOutput,\n translatedContent[targetLocale],\n targetLocale\n );\n }\n }\n\n appLogger(\n `${task.dictionaryPreset} ${colorize('Translation completed successfully', ANSIColors.GREEN)} for ${colorizePath(basename(dictionaryOutput.filePath!))}`,\n {\n level: 'info',\n }\n );\n\n // The dict-level `fill` value may have been lost during JSON serialisation\n // (functions can't be serialised to JSON). Fall back to the config-level\n // fill so that an explicit fill in intlayer.config.ts is honoured.\n const effectiveFillForCheck =\n baseUnmergedDictionary.fill ?? configuration.dictionary?.fill;\n\n if (\n baseUnmergedDictionary.locale &&\n (effectiveFillForCheck === true ||\n effectiveFillForCheck === undefined) &&\n baseUnmergedDictionary.location === 'local'\n ) {\n const dictionaryFilePath = baseUnmergedDictionary\n .filePath!.split('.')\n .slice(0, -1);\n\n const contentIndex = dictionaryFilePath[dictionaryFilePath.length - 1];\n\n return JSON.parse(\n JSON.stringify({\n ...task,\n dictionaryOutput: {\n ...dictionaryOutput,\n fill: undefined,\n filled: true,\n },\n }).replaceAll(\n new RegExp(`\\\\.${contentIndex}\\\\.[a-zA-Z0-9]+`, 'g'),\n `.filled.${contentIndex}.json`\n )\n );\n }\n\n return {\n ...task,\n dictionaryOutput,\n };\n },\n {\n maxRetry: GROUP_MAX_RETRY,\n delay: RETRY_DELAY,\n onError: ({ error, attempt, maxRetry }) =>\n appLogger(\n `${task.dictionaryPreset} ${colorize('Error:', ANSIColors.RED)} ${colorize(serializeError(error), ANSIColors.GREY_DARK)} - Attempt ${colorizeNumber(attempt + 1)} of ${colorizeNumber(maxRetry)}`,\n {\n level: 'error',\n }\n ),\n onMaxTryReached: ({ error }) =>\n appLogger(\n `${task.dictionaryPreset} ${colorize('Maximum number of retries reached:', ANSIColors.RED)} ${colorize(serializeError(error), ANSIColors.GREY_DARK)}`,\n {\n level: 'error',\n }\n ),\n }\n )();\n\n return result as TranslateDictionaryResult;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"translateDictionary.mjs","names":[],"sources":["../../../src/fill/translateDictionary.ts"],"sourcesContent":["import { basename } from 'node:path';\nimport type { AIConfig } from '@intlayer/ai';\nimport type { AIOptions } from '@intlayer/api';\nimport {\n chunkJSON,\n excludeObjectFormat,\n formatLocale,\n type JsonChunk,\n mergeChunks,\n reconstructFromSingleChunk,\n verifyIdenticObjectFormat,\n} from '@intlayer/chokidar/utils';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport {\n colon,\n colorize,\n colorizeNumber,\n colorizePath,\n getAppLogger,\n} from '@intlayer/config/logger';\nimport { retryManager } from '@intlayer/config/utils';\nimport {\n getFilterMissingTranslationsDictionary,\n getMultilingualDictionary,\n getPerLocaleDictionary,\n insertContentInDictionary,\n} from '@intlayer/core/plugins';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { Dictionary } from '@intlayer/types/dictionary';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\nimport { getAuthenticatedAPI } from '../utils/checkAccess';\nimport type { AIClient } from '../utils/setupAI';\nimport { deepMergeContent } from './deepMergeContent';\nimport {\n extractTranslatableContent,\n reinsertTranslatedContent,\n} from './extractTranslatableContent';\nimport type { TranslationTask } from './listTranslationsTasks';\n\ntype TranslateDictionaryResult = TranslationTask & {\n dictionaryOutput: Dictionary | null;\n};\n\ntype TranslateDictionaryOptions = {\n mode: 'complete' | 'review';\n aiOptions?: AIOptions;\n fillMetadata?: boolean;\n onHandle?: ReturnType<\n typeof import('@intlayer/chokidar/utils').getGlobalLimiter\n >;\n onSuccess?: () => void;\n onError?: (error: unknown) => void;\n getAbortError?: () => Error | null;\n aiClient?: AIClient;\n aiConfig?: AIConfig;\n};\n\nconst createChunkPreset = (chunkIndex: number, totalChunks: number) => {\n if (totalChunks <= 1) return '';\n return colon(\n [\n colorize('[', ANSIColors.GREY_DARK),\n colorizeNumber(chunkIndex + 1),\n colorize(`/${totalChunks}`, ANSIColors.GREY_DARK),\n colorize(']', ANSIColors.GREY_DARK),\n ].join(''),\n { colSize: 5 }\n );\n};\n\nconst hasMissingMetadata = (dictionary: Dictionary) =>\n !dictionary.description || !dictionary.title || !dictionary.tags;\n\nconst serializeError = (error: unknown): string => {\n if (error instanceof Error) {\n return error.cause\n ? `${error.message} (cause: ${String(error.cause)})`\n : error.message;\n }\n if (typeof error === 'string') return error;\n try {\n return JSON.stringify(error);\n } catch {\n return String(error);\n }\n};\n\nconst CHUNK_SIZE = 4000;\nconst GROUP_MAX_RETRY = 2;\nconst MAX_RETRY = 3;\nconst RETRY_DELAY = 1000 * 10; // 10 seconds\n\nconst MAX_FOLLOWING_ERRORS = 10; // 10 errors in a row, hard exit the process\nlet followingErrors = 0;\n\nexport const translateDictionary = async (\n task: TranslationTask,\n configuration: IntlayerConfig,\n options?: TranslateDictionaryOptions\n): Promise<TranslateDictionaryResult> => {\n const appLogger = getAppLogger(configuration);\n const intlayerAPI = await getAuthenticatedAPI(configuration);\n\n const { mode, aiOptions, fillMetadata, aiClient, aiConfig } = {\n mode: 'complete',\n fillMetadata: true,\n ...options,\n } as const;\n\n const notifySuccess = () => {\n followingErrors = 0;\n options?.onSuccess?.();\n };\n\n const result = await retryManager(\n async () => {\n const unmergedDictionariesRecord = getUnmergedDictionaries(configuration);\n\n const baseUnmergedDictionary: Dictionary | undefined =\n unmergedDictionariesRecord[task.dictionaryKey].find(\n (dict) => dict.localId === task.dictionaryLocalId\n );\n\n if (!baseUnmergedDictionary) {\n appLogger(\n `${task.dictionaryPreset}Dictionary not found in unmergedDictionariesRecord. Skipping.`,\n {\n level: 'warn',\n }\n );\n return { ...task, dictionaryOutput: null };\n }\n\n let metadata:\n | Pick<Dictionary, 'description' | 'title' | 'tags'>\n | undefined;\n\n if (\n fillMetadata &&\n (hasMissingMetadata(baseUnmergedDictionary) || mode === 'review')\n ) {\n const defaultLocaleDictionary = getPerLocaleDictionary(\n baseUnmergedDictionary,\n configuration.internationalization.defaultLocale\n );\n\n appLogger(\n `${task.dictionaryPreset} Filling missing metadata for ${colorizePath(basename(baseUnmergedDictionary.filePath!))}`,\n {\n level: 'info',\n }\n );\n\n const runAudit = async () => {\n if (aiClient && aiConfig) {\n const result = await aiClient.auditDictionaryMetadata({\n fileContent: JSON.stringify(defaultLocaleDictionary),\n aiConfig,\n });\n\n return {\n data: result,\n };\n }\n\n return await intlayerAPI.ai.auditContentDeclarationMetadata({\n fileContent: JSON.stringify(defaultLocaleDictionary),\n aiOptions,\n });\n };\n\n const metadataResult = options?.onHandle\n ? await options.onHandle(runAudit)\n : await runAudit();\n\n metadata = metadataResult.data?.fileContent;\n }\n\n const translatedContentResults = await Promise.all(\n task.targetLocales.map(async (targetLocale) => {\n /**\n * In complete mode, for large dictionaries, we want to filter all content that is already translated\n *\n * targetLocale: fr\n *\n * { test1: t({ ar: 'Hello', en: 'Hello', fr: 'Bonjour' } }) -> {}\n * { test2: t({ ar: 'Hello', en: 'Hello' }) } -> { test2: t({ ar: 'Hello', en: 'Hello' }) }\n *\n */\n // Reset to base dictionary for each locale to ensure we filter from the original\n let dictionaryToProcess = structuredClone(baseUnmergedDictionary);\n\n let targetLocaleDictionary: Dictionary;\n\n if (typeof baseUnmergedDictionary.locale === 'string') {\n // For per-locale files, the content is already in simple JSON format (not translation nodes)\n // The base dictionary is already the source locale content\n\n // Look up the target locale dictionary by its locale property.\n // This handles both directory-based paths (/en/file.json → /es/file.json)\n // AND filename-based paths (messages_ICU/en.json → messages_ICU/es.json).\n // Plugins like syncJSON set `dict.locale` correctly, so matching by locale\n // is the only reliable strategy — path string-replacement fails when the\n // locale appears in the filename rather than a directory segment.\n const targetUnmergedDictionary = unmergedDictionariesRecord[\n task.dictionaryKey\n ]?.find(\n (dict) =>\n dict.locale === targetLocale &&\n dict.filePath !== baseUnmergedDictionary.filePath\n );\n\n // Derive the target file path for the fallback (when the file doesn't exist yet).\n // Try directory-style first (/en/ → /es/), then filename-style (en.json → es.json).\n const sourceFilePath = baseUnmergedDictionary.filePath ?? '';\n const derivedTargetFilePath = sourceFilePath.includes(\n `/${task.sourceLocale}/`\n )\n ? sourceFilePath.replace(\n new RegExp(`/${task.sourceLocale}/`, 'g'),\n `/${targetLocale}/`\n )\n : sourceFilePath.replace(\n new RegExp(`(^|/)${task.sourceLocale}(\\\\.[^/]+)$`),\n `$1${targetLocale}$2`\n );\n\n targetLocaleDictionary = targetUnmergedDictionary ?? {\n key: baseUnmergedDictionary.key,\n content: {},\n filePath: derivedTargetFilePath || undefined,\n locale: targetLocale,\n };\n } else {\n // For multilingual dictionaries\n if (mode === 'complete') {\n // Remove all nodes that don't have any content to translate\n dictionaryToProcess = getFilterMissingTranslationsDictionary(\n dictionaryToProcess,\n targetLocale\n );\n }\n\n dictionaryToProcess = getPerLocaleDictionary(\n dictionaryToProcess,\n task.sourceLocale\n );\n\n targetLocaleDictionary = getPerLocaleDictionary(\n baseUnmergedDictionary,\n targetLocale\n );\n }\n\n // Filter to only untranslated fields, preserving explicit null values as\n // default-locale fallback markers. Applied after both paths converge so\n // the same logic covers per-locale and multilingual dictionaries.\n if (mode === 'complete') {\n dictionaryToProcess = {\n ...dictionaryToProcess,\n content:\n excludeObjectFormat(\n dictionaryToProcess.content,\n targetLocaleDictionary.content\n ) ?? {},\n };\n }\n\n const localePreset = colon(\n [\n colorize('[', ANSIColors.GREY_DARK),\n formatLocale(targetLocale),\n colorize(']', ANSIColors.GREY_DARK),\n ].join(''),\n { colSize: 18 }\n );\n\n appLogger(\n `${task.dictionaryPreset}${localePreset} Preparing ${colorizePath(basename(targetLocaleDictionary.filePath!))}`,\n {\n level: 'info',\n }\n );\n\n const chunkedJsonContent: JsonChunk[] = chunkJSON(\n dictionaryToProcess.content as unknown as Record<string, any>,\n CHUNK_SIZE\n );\n\n const nbOfChunks = chunkedJsonContent.length;\n\n if (nbOfChunks > 1) {\n appLogger(\n `${task.dictionaryPreset}${localePreset} Split into ${colorizeNumber(nbOfChunks)} chunks for translation`,\n {\n level: 'info',\n }\n );\n }\n\n const chunkResult: JsonChunk[] = [];\n\n // Process chunks in parallel (globally throttled) to allow concurrent translation\n const chunkPromises = chunkedJsonContent.map(async (chunk) => {\n const chunkPreset = createChunkPreset(chunk.index, chunk.total);\n\n if (nbOfChunks > 1) {\n appLogger(\n `${task.dictionaryPreset}${localePreset}${chunkPreset} Translating chunk`,\n {\n level: 'info',\n }\n );\n }\n\n const reconstructedChunk = reconstructFromSingleChunk(chunk);\n const {\n extractedContent: chunkExtractedContent,\n translatableDictionary: chunkTranslatableDictionary,\n } = extractTranslatableContent(reconstructedChunk);\n\n const executeTranslation = async () => {\n return await retryManager(\n async () => {\n let translationResult: any;\n\n if (aiClient && aiConfig) {\n translationResult = await aiClient.translateJSON({\n entryFileContent:\n chunkTranslatableDictionary as unknown as JSON,\n presetOutputContent: chunkTranslatableDictionary,\n dictionaryDescription:\n dictionaryToProcess.description ??\n metadata?.description ??\n '',\n entryLocale: task.sourceLocale,\n outputLocale: targetLocale,\n mode,\n aiConfig,\n });\n } else {\n translationResult = await intlayerAPI.ai\n .translateJSON({\n entryFileContent:\n chunkTranslatableDictionary as unknown as JSON,\n presetOutputContent: chunkTranslatableDictionary,\n dictionaryDescription:\n dictionaryToProcess.description ??\n metadata?.description ??\n '',\n entryLocale: task.sourceLocale,\n outputLocale: targetLocale,\n mode,\n aiOptions,\n })\n .then((result) => result.data);\n }\n\n if (!translationResult?.fileContent) {\n throw new Error('No content result');\n }\n\n const { isIdentic, error } = verifyIdenticObjectFormat(\n translationResult.fileContent,\n chunkTranslatableDictionary\n );\n\n if (!isIdentic) {\n throw new Error(\n `Translation result does not match expected format: ${error}`\n );\n }\n\n notifySuccess();\n return reinsertTranslatedContent(\n reconstructedChunk,\n chunkExtractedContent,\n translationResult.fileContent as Record<number, string>\n );\n },\n {\n maxRetry: MAX_RETRY,\n delay: RETRY_DELAY,\n onError: ({ error, attempt, maxRetry }) => {\n const chunkPreset = createChunkPreset(\n chunk.index,\n chunk.total\n );\n appLogger(\n `${task.dictionaryPreset}${localePreset}${chunkPreset} ${colorize('Error filling:', ANSIColors.RED)} ${colorize(serializeError(error), ANSIColors.GREY_DARK)} - Attempt ${colorizeNumber(attempt + 1)} of ${colorizeNumber(maxRetry)}`,\n {\n level: 'error',\n }\n );\n\n followingErrors += 1;\n\n if (followingErrors >= MAX_FOLLOWING_ERRORS) {\n appLogger(`There is something wrong.`, {\n level: 'error',\n });\n process.exit(1); // 1 for error\n }\n },\n }\n )();\n };\n\n const wrapped = options?.onHandle\n ? options.onHandle(executeTranslation) // queued in global limiter\n : executeTranslation(); // no global limiter\n\n return wrapped.then((result) => ({ chunk, result }));\n });\n\n // Wait for all chunks for this locale in parallel (still capped by global limiter)\n const chunkResults = await Promise.all(chunkPromises);\n\n // Maintain order\n chunkResults\n .sort((chunkA, chunkB) => chunkA.chunk.index - chunkB.chunk.index)\n .forEach(({ result }) => {\n chunkResult.push(result);\n });\n\n // Merge translated chunk contents back into a single content object\n const reinsertedContent = mergeChunks(chunkResult);\n\n const merged = {\n ...dictionaryToProcess,\n content: reinsertedContent,\n };\n\n // Merge newly translated content (including explicit null fallbacks) back\n // into the existing target locale content. Applies to both per-locale and\n // multilingual paths so the target always retains previously translated\n // fields and receives null markers where the source has no translation.\n const finalContent = deepMergeContent(\n targetLocaleDictionary.content ?? {},\n merged.content\n );\n\n return [targetLocale, finalContent] as const;\n })\n );\n\n const translatedContent: Partial<Record<Locale, Dictionary['content']>> =\n Object.fromEntries(translatedContentResults);\n\n const baseDictionary = baseUnmergedDictionary.locale\n ? {\n ...baseUnmergedDictionary,\n key: baseUnmergedDictionary.key!,\n content: {},\n }\n : baseUnmergedDictionary;\n\n let dictionaryOutput: Dictionary = {\n ...getMultilingualDictionary(baseDictionary),\n locale: undefined, // Ensure the dictionary is multilingual\n ...metadata,\n };\n\n for (const targetLocale of task.targetLocales) {\n if (translatedContent[targetLocale]) {\n dictionaryOutput = insertContentInDictionary(\n dictionaryOutput,\n translatedContent[targetLocale],\n targetLocale\n );\n }\n }\n\n appLogger(\n `${task.dictionaryPreset} ${colorize('Translation completed successfully', ANSIColors.GREEN)} for ${colorizePath(basename(dictionaryOutput.filePath!))}`,\n {\n level: 'info',\n }\n );\n\n // The dict-level `fill` value may have been lost during JSON serialisation\n // (functions can't be serialised to JSON). Fall back to the config-level\n // fill so that an explicit fill in intlayer.config.ts is honoured.\n const effectiveFillForCheck =\n baseUnmergedDictionary.fill ?? configuration.dictionary?.fill;\n\n if (\n baseUnmergedDictionary.locale &&\n (effectiveFillForCheck === true ||\n effectiveFillForCheck === undefined) &&\n baseUnmergedDictionary.location === 'local'\n ) {\n const dictionaryFilePath = baseUnmergedDictionary\n .filePath!.split('.')\n .slice(0, -1);\n\n const contentIndex = dictionaryFilePath[dictionaryFilePath.length - 1];\n\n return JSON.parse(\n JSON.stringify({\n ...task,\n dictionaryOutput: {\n ...dictionaryOutput,\n fill: undefined,\n filled: true,\n },\n }).replaceAll(\n new RegExp(`\\\\.${contentIndex}\\\\.[a-zA-Z0-9]+`, 'g'),\n `.filled.${contentIndex}.json`\n )\n );\n }\n\n return {\n ...task,\n dictionaryOutput,\n };\n },\n {\n maxRetry: GROUP_MAX_RETRY,\n delay: RETRY_DELAY,\n onError: ({ error, attempt, maxRetry }) =>\n appLogger(\n `${task.dictionaryPreset} ${colorize('Error:', ANSIColors.RED)} ${colorize(serializeError(error), ANSIColors.GREY_DARK)} - Attempt ${colorizeNumber(attempt + 1)} of ${colorizeNumber(maxRetry)}`,\n {\n level: 'error',\n }\n ),\n onMaxTryReached: ({ error }) =>\n appLogger(\n `${task.dictionaryPreset} ${colorize('Maximum number of retries reached:', ANSIColors.RED)} ${colorize(serializeError(error), ANSIColors.GREY_DARK)}`,\n {\n level: 'error',\n }\n ),\n }\n )();\n\n return result as TranslateDictionaryResult;\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"writeFill.mjs","names":[],"sources":["../../../src/fill/writeFill.ts"],"sourcesContent":["import { resolve } from 'node:path';\nimport { writeContentDeclaration } from '@intlayer/chokidar/build';\nimport { formatLocale, formatPath } from '@intlayer/chokidar/utils';\nimport { colorizeKey, getAppLogger } from '@intlayer/config/logger';\nimport { getDictionaries } from '@intlayer/dictionaries-entry';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { Dictionary, Fill } from '@intlayer/types/dictionary';\nimport { type FillData, formatFillData } from './formatFillData';\nimport { getAvailableLocalesInDictionary } from './getAvailableLocalesInDictionary';\n\nexport const writeFill = async (\n contentDeclarationFile: Dictionary,\n outputLocales: Locale[],\n parentLocales: Locale[],\n configuration: IntlayerConfig\n) => {\n const appLogger = getAppLogger(configuration);\n const dictionaries = getDictionaries(configuration);\n\n const fullDictionary = dictionaries[contentDeclarationFile.key];\n\n const { filePath } = contentDeclarationFile;\n\n if (!filePath) {\n appLogger('No file path found for dictionary', {\n level: 'error',\n });\n return;\n }\n\n const fillOptions: Fill | undefined =\n contentDeclarationFile.fill ?? configuration.dictionary?.fill ?? true;\n\n if ((fillOptions as boolean) === false) {\n appLogger(\n `Auto fill is disabled for '${colorizeKey(fullDictionary.key)}'`,\n {\n level: 'info',\n }\n );\n return;\n }\n\n const requestedLocales: Locale[] = (\n outputLocales ?? configuration.internationalization.locales\n ).filter((locale) => !parentLocales?.includes(locale));\n\n // Get locales that actually have translations in the content\n const availableLocales = getAvailableLocalesInDictionary(\n contentDeclarationFile\n );\n\n // Only write files for locales that have actual translations\n const localeList = requestedLocales.filter((locale) =>\n availableLocales.includes(locale)\n );\n\n if (localeList.length === 0) {\n appLogger(\n `No translations available for dictionary '${colorizeKey(fullDictionary.key)}'`,\n {\n level: 'info',\n }\n );\n return;\n }\n\n const fillData: FillData[] = await formatFillData(\n fillOptions as Fill,\n localeList,\n filePath,\n fullDictionary.key,\n configuration\n );\n\n for await (const output of fillData) {\n if (!output.filePath) {\n appLogger(\n `No file path found for auto filled content declaration for '${colorizeKey(fullDictionary.key)}'`,\n {\n level: 'error',\n }\n );\n continue;\n }\n\n const { fill, ...rest } = contentDeclarationFile;\n\n await writeContentDeclaration(\n {\n ...rest,\n filled: true,\n locale: output.isPerLocale ? output.localeList[0] : undefined,\n localId: `${contentDeclarationFile.key}::local::${output.filePath}`,\n filePath: resolve(configuration.system.baseDir, output.filePath), // Use absolute path for vscode extension\n },\n configuration,\n {\n // Per-locale files: write only the specific locale.\n // Multilingual files (string/function fill, single output): include all\n // output locales (including source) so the file is complete.\n localeList: output.isPerLocale\n ? output.localeList\n : (outputLocales ?? configuration.internationalization.locales),\n }\n );\n\n if (output.isPerLocale) {\n appLogger(\n `Auto filled per-locale content declaration for '${colorizeKey(fullDictionary.key)}' written to ${formatPath(output.filePath)} for locale ${formatLocale(output.localeList[0])}`,\n { level: 'info' }\n );\n } else {\n appLogger(\n `Auto filled content declaration for '${colorizeKey(fullDictionary.key)}' written to ${formatPath(output.filePath)}`,\n { level: 'info' }\n );\n }\n }\n};\n"],"mappings":";;;;;;;;;AAWA,MAAa,YAAY,OACvB,wBACA,eACA,eACA,kBACG;CACH,MAAM,YAAY,aAAa,
|
|
1
|
+
{"version":3,"file":"writeFill.mjs","names":[],"sources":["../../../src/fill/writeFill.ts"],"sourcesContent":["import { resolve } from 'node:path';\nimport { writeContentDeclaration } from '@intlayer/chokidar/build';\nimport { formatLocale, formatPath } from '@intlayer/chokidar/utils';\nimport { colorizeKey, getAppLogger } from '@intlayer/config/logger';\nimport { getDictionaries } from '@intlayer/dictionaries-entry';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { Dictionary, Fill } from '@intlayer/types/dictionary';\nimport { type FillData, formatFillData } from './formatFillData';\nimport { getAvailableLocalesInDictionary } from './getAvailableLocalesInDictionary';\n\nexport const writeFill = async (\n contentDeclarationFile: Dictionary,\n outputLocales: Locale[],\n parentLocales: Locale[],\n configuration: IntlayerConfig\n) => {\n const appLogger = getAppLogger(configuration);\n const dictionaries = getDictionaries(configuration);\n\n const fullDictionary = dictionaries[contentDeclarationFile.key];\n\n const { filePath } = contentDeclarationFile;\n\n if (!filePath) {\n appLogger('No file path found for dictionary', {\n level: 'error',\n });\n return;\n }\n\n const fillOptions: Fill | undefined =\n contentDeclarationFile.fill ?? configuration.dictionary?.fill ?? true;\n\n if ((fillOptions as boolean) === false) {\n appLogger(\n `Auto fill is disabled for '${colorizeKey(fullDictionary.key)}'`,\n {\n level: 'info',\n }\n );\n return;\n }\n\n const requestedLocales: Locale[] = (\n outputLocales ?? configuration.internationalization.locales\n ).filter((locale) => !parentLocales?.includes(locale));\n\n // Get locales that actually have translations in the content\n const availableLocales = getAvailableLocalesInDictionary(\n contentDeclarationFile\n );\n\n // Only write files for locales that have actual translations\n const localeList = requestedLocales.filter((locale) =>\n availableLocales.includes(locale)\n );\n\n if (localeList.length === 0) {\n appLogger(\n `No translations available for dictionary '${colorizeKey(fullDictionary.key)}'`,\n {\n level: 'info',\n }\n );\n return;\n }\n\n const fillData: FillData[] = await formatFillData(\n fillOptions as Fill,\n localeList,\n filePath,\n fullDictionary.key,\n configuration\n );\n\n for await (const output of fillData) {\n if (!output.filePath) {\n appLogger(\n `No file path found for auto filled content declaration for '${colorizeKey(fullDictionary.key)}'`,\n {\n level: 'error',\n }\n );\n continue;\n }\n\n const { fill, ...rest } = contentDeclarationFile;\n\n await writeContentDeclaration(\n {\n ...rest,\n filled: true,\n locale: output.isPerLocale ? output.localeList[0] : undefined,\n localId: `${contentDeclarationFile.key}::local::${output.filePath}`,\n filePath: resolve(configuration.system.baseDir, output.filePath), // Use absolute path for vscode extension\n },\n configuration,\n {\n // Per-locale files: write only the specific locale.\n // Multilingual files (string/function fill, single output): include all\n // output locales (including source) so the file is complete.\n localeList: output.isPerLocale\n ? output.localeList\n : (outputLocales ?? configuration.internationalization.locales),\n }\n );\n\n if (output.isPerLocale) {\n appLogger(\n `Auto filled per-locale content declaration for '${colorizeKey(fullDictionary.key)}' written to ${formatPath(output.filePath)} for locale ${formatLocale(output.localeList[0])}`,\n { level: 'info' }\n );\n } else {\n appLogger(\n `Auto filled content declaration for '${colorizeKey(fullDictionary.key)}' written to ${formatPath(output.filePath)}`,\n { level: 'info' }\n );\n }\n }\n};\n"],"mappings":";;;;;;;;;AAWA,MAAa,YAAY,OACvB,wBACA,eACA,eACA,kBACG;CACH,MAAM,YAAY,aAAa,aAAa;CAG5C,MAAM,iBAFe,gBAAgB,aAEH,EAAE,uBAAuB;CAE3D,MAAM,EAAE,aAAa;CAErB,IAAI,CAAC,UAAU;EACb,UAAU,qCAAqC,EAC7C,OAAO,QACT,CAAC;EACD;CACF;CAEA,MAAM,cACJ,uBAAuB,QAAQ,cAAc,YAAY,QAAQ;CAEnE,IAAK,gBAA4B,OAAO;EACtC,UACE,8BAA8B,YAAY,eAAe,GAAG,EAAE,IAC9D,EACE,OAAO,OACT,CACF;EACA;CACF;CAEA,MAAM,oBACJ,iBAAiB,cAAc,qBAAqB,SACpD,QAAQ,WAAW,CAAC,eAAe,SAAS,MAAM,CAAC;CAGrD,MAAM,mBAAmB,gCACvB,sBACF;CAGA,MAAM,aAAa,iBAAiB,QAAQ,WAC1C,iBAAiB,SAAS,MAAM,CAClC;CAEA,IAAI,WAAW,WAAW,GAAG;EAC3B,UACE,6CAA6C,YAAY,eAAe,GAAG,EAAE,IAC7E,EACE,OAAO,OACT,CACF;EACA;CACF;CAEA,MAAM,WAAuB,MAAM,eACjC,aACA,YACA,UACA,eAAe,KACf,aACF;CAEA,WAAW,MAAM,UAAU,UAAU;EACnC,IAAI,CAAC,OAAO,UAAU;GACpB,UACE,+DAA+D,YAAY,eAAe,GAAG,EAAE,IAC/F,EACE,OAAO,QACT,CACF;GACA;EACF;EAEA,MAAM,EAAE,MAAM,GAAG,SAAS;EAE1B,MAAM,wBACJ;GACE,GAAG;GACH,QAAQ;GACR,QAAQ,OAAO,cAAc,OAAO,WAAW,KAAK;GACpD,SAAS,GAAG,uBAAuB,IAAI,WAAW,OAAO;GACzD,UAAU,QAAQ,cAAc,OAAO,SAAS,OAAO,QAAQ;EACjE,GACA,eACA,EAIE,YAAY,OAAO,cACf,OAAO,aACN,iBAAiB,cAAc,qBAAqB,QAC3D,CACF;EAEA,IAAI,OAAO,aACT,UACE,mDAAmD,YAAY,eAAe,GAAG,EAAE,eAAe,WAAW,OAAO,QAAQ,EAAE,cAAc,aAAa,OAAO,WAAW,EAAE,KAC7K,EAAE,OAAO,OAAO,CAClB;OAEA,UACE,wCAAwC,YAAY,eAAe,GAAG,EAAE,eAAe,WAAW,OAAO,QAAQ,KACjH,EAAE,OAAO,OAAO,CAClB;CAEJ;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getTargetDictionary.mjs","names":[],"sources":["../../src/getTargetDictionary.ts"],"sourcesContent":["import { join, relative } from 'node:path';\nimport { type ListGitFilesOptions, listGitFiles } from '@intlayer/chokidar/cli';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport type { Dictionary } from '@intlayer/types/dictionary';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\n\nexport const ensureArray = <T>(value: T | T[]): T[] => [value].flat() as T[];\n\n// Arguments for the fill function\nexport type GetTargetDictionaryOptions = {\n file?: string | string[];\n keys?: string | string[];\n excludedKeys?: string | string[];\n filter?: (entry: Dictionary) => boolean; // DictionaryEntry needs to be defined\n pathFilter?: string | string[];\n gitOptions?: ListGitFilesOptions;\n configOptions?: GetConfigurationOptions;\n};\n\nexport const getTargetUnmergedDictionaries = async (\n options?: GetTargetDictionaryOptions\n): Promise<Dictionary[]> => {\n const configuration = getConfiguration(options?.configOptions);\n\n const { baseDir } = configuration.system;\n\n const unmergedDictionariesRecord = getUnmergedDictionaries(configuration);\n let result = Object.values(unmergedDictionariesRecord).flat();\n\n // 1. if filePath not defined, list all content declaration files based on unmerged dictionaries list\n if (typeof options?.file !== 'undefined') {\n const fileArray = ensureArray(options?.file);\n const relativeFilePaths = fileArray.map((file) =>\n file.startsWith('/') ? relative(baseDir, file) : join('./', file)\n );\n\n result = result.filter(\n (dict) =>\n dict.filePath &&\n // Check for absolute path\n relativeFilePaths.includes(dict.filePath)\n );\n }\n\n if (typeof options?.keys !== 'undefined') {\n result = result.filter((dict) =>\n ensureArray(options?.keys)?.includes(dict.key)\n );\n }\n\n if (typeof options?.excludedKeys !== 'undefined') {\n result = result.filter(\n (dict) => !ensureArray(options?.excludedKeys)?.includes(dict.key)\n );\n }\n\n if (typeof options?.pathFilter !== 'undefined') {\n result = result.filter((dict) =>\n ensureArray(options?.pathFilter)?.includes(dict.filePath ?? '')\n );\n }\n\n if (typeof options?.filter !== 'undefined') {\n result = result.filter(options?.filter);\n }\n\n const gitOptions = options?.gitOptions;\n if (gitOptions) {\n const gitChangedFiles = await listGitFiles(gitOptions);\n\n if (gitChangedFiles) {\n // Convert dictionary file paths to be relative to git root for comparison\n\n // Filter dictionaries based on git changed files\n result = result.filter((dict) => {\n if (!dict.filePath) return false;\n\n return gitChangedFiles.some((gitFile) => dict.filePath === gitFile);\n });\n }\n }\n\n return result;\n};\n"],"mappings":";;;;;;AASA,MAAa,eAAkB,UAAwB,CAAC,
|
|
1
|
+
{"version":3,"file":"getTargetDictionary.mjs","names":[],"sources":["../../src/getTargetDictionary.ts"],"sourcesContent":["import { join, relative } from 'node:path';\nimport { type ListGitFilesOptions, listGitFiles } from '@intlayer/chokidar/cli';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport type { Dictionary } from '@intlayer/types/dictionary';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\n\nexport const ensureArray = <T>(value: T | T[]): T[] => [value].flat() as T[];\n\n// Arguments for the fill function\nexport type GetTargetDictionaryOptions = {\n file?: string | string[];\n keys?: string | string[];\n excludedKeys?: string | string[];\n filter?: (entry: Dictionary) => boolean; // DictionaryEntry needs to be defined\n pathFilter?: string | string[];\n gitOptions?: ListGitFilesOptions;\n configOptions?: GetConfigurationOptions;\n};\n\nexport const getTargetUnmergedDictionaries = async (\n options?: GetTargetDictionaryOptions\n): Promise<Dictionary[]> => {\n const configuration = getConfiguration(options?.configOptions);\n\n const { baseDir } = configuration.system;\n\n const unmergedDictionariesRecord = getUnmergedDictionaries(configuration);\n let result = Object.values(unmergedDictionariesRecord).flat();\n\n // 1. if filePath not defined, list all content declaration files based on unmerged dictionaries list\n if (typeof options?.file !== 'undefined') {\n const fileArray = ensureArray(options?.file);\n const relativeFilePaths = fileArray.map((file) =>\n file.startsWith('/') ? relative(baseDir, file) : join('./', file)\n );\n\n result = result.filter(\n (dict) =>\n dict.filePath &&\n // Check for absolute path\n relativeFilePaths.includes(dict.filePath)\n );\n }\n\n if (typeof options?.keys !== 'undefined') {\n result = result.filter((dict) =>\n ensureArray(options?.keys)?.includes(dict.key)\n );\n }\n\n if (typeof options?.excludedKeys !== 'undefined') {\n result = result.filter(\n (dict) => !ensureArray(options?.excludedKeys)?.includes(dict.key)\n );\n }\n\n if (typeof options?.pathFilter !== 'undefined') {\n result = result.filter((dict) =>\n ensureArray(options?.pathFilter)?.includes(dict.filePath ?? '')\n );\n }\n\n if (typeof options?.filter !== 'undefined') {\n result = result.filter(options?.filter);\n }\n\n const gitOptions = options?.gitOptions;\n if (gitOptions) {\n const gitChangedFiles = await listGitFiles(gitOptions);\n\n if (gitChangedFiles) {\n // Convert dictionary file paths to be relative to git root for comparison\n\n // Filter dictionaries based on git changed files\n result = result.filter((dict) => {\n if (!dict.filePath) return false;\n\n return gitChangedFiles.some((gitFile) => dict.filePath === gitFile);\n });\n }\n }\n\n return result;\n};\n"],"mappings":";;;;;;AASA,MAAa,eAAkB,UAAwB,CAAC,KAAK,EAAE,KAAK;AAapE,MAAa,gCAAgC,OAC3C,YAC0B;CAC1B,MAAM,gBAAgB,iBAAiB,SAAS,aAAa;CAE7D,MAAM,EAAE,YAAY,cAAc;CAElC,MAAM,6BAA6B,wBAAwB,aAAa;CACxE,IAAI,SAAS,OAAO,OAAO,0BAA0B,EAAE,KAAK;CAG5D,IAAI,OAAO,SAAS,SAAS,aAAa;EAExC,MAAM,oBADY,YAAY,SAAS,IACL,EAAE,KAAK,SACvC,KAAK,WAAW,GAAG,IAAI,SAAS,SAAS,IAAI,IAAI,KAAK,MAAM,IAAI,CAClE;EAEA,SAAS,OAAO,QACb,SACC,KAAK,YAEL,kBAAkB,SAAS,KAAK,QAAQ,CAC5C;CACF;CAEA,IAAI,OAAO,SAAS,SAAS,aAC3B,SAAS,OAAO,QAAQ,SACtB,YAAY,SAAS,IAAI,GAAG,SAAS,KAAK,GAAG,CAC/C;CAGF,IAAI,OAAO,SAAS,iBAAiB,aACnC,SAAS,OAAO,QACb,SAAS,CAAC,YAAY,SAAS,YAAY,GAAG,SAAS,KAAK,GAAG,CAClE;CAGF,IAAI,OAAO,SAAS,eAAe,aACjC,SAAS,OAAO,QAAQ,SACtB,YAAY,SAAS,UAAU,GAAG,SAAS,KAAK,YAAY,EAAE,CAChE;CAGF,IAAI,OAAO,SAAS,WAAW,aAC7B,SAAS,OAAO,OAAO,SAAS,MAAM;CAGxC,MAAM,aAAa,SAAS;CAC5B,IAAI,YAAY;EACd,MAAM,kBAAkB,MAAM,aAAa,UAAU;EAErD,IAAI,iBAIF,SAAS,OAAO,QAAQ,SAAS;GAC/B,IAAI,CAAC,KAAK,UAAU,OAAO;GAE3B,OAAO,gBAAgB,MAAM,YAAY,KAAK,aAAa,OAAO;EACpE,CAAC;CAEL;CAEA,OAAO;AACT"}
|
package/dist/esm/init.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.mjs","names":[],"sources":["../../src/init.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport { type InitOptions, initIntlayer } from '@intlayer/chokidar/cli';\n\nexport const findProjectRoot = (startDir: string) => {\n let currentDir = startDir;\n\n while (currentDir !== resolve(currentDir, '..')) {\n if (existsSync(join(currentDir, 'package.json'))) {\n return currentDir;\n }\n currentDir = resolve(currentDir, '..');\n }\n\n // If no package.json is found, return the start directory.\n // The initIntlayer function will handle the missing package.json error.\n return startDir;\n};\n\nexport const init = async (projectRoot?: string, options?: InitOptions) => {\n const root = projectRoot\n ? findProjectRoot(resolve(projectRoot))\n : findProjectRoot(process.cwd());\n\n await initIntlayer(root, options);\n};\n"],"mappings":";;;;;AAIA,MAAa,mBAAmB,aAAqB;CACnD,IAAI,aAAa;CAEjB,OAAO,eAAe,QAAQ,YAAY,
|
|
1
|
+
{"version":3,"file":"init.mjs","names":[],"sources":["../../src/init.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport { type InitOptions, initIntlayer } from '@intlayer/chokidar/cli';\n\nexport const findProjectRoot = (startDir: string) => {\n let currentDir = startDir;\n\n while (currentDir !== resolve(currentDir, '..')) {\n if (existsSync(join(currentDir, 'package.json'))) {\n return currentDir;\n }\n currentDir = resolve(currentDir, '..');\n }\n\n // If no package.json is found, return the start directory.\n // The initIntlayer function will handle the missing package.json error.\n return startDir;\n};\n\nexport const init = async (projectRoot?: string, options?: InitOptions) => {\n const root = projectRoot\n ? findProjectRoot(resolve(projectRoot))\n : findProjectRoot(process.cwd());\n\n await initIntlayer(root, options);\n};\n"],"mappings":";;;;;AAIA,MAAa,mBAAmB,aAAqB;CACnD,IAAI,aAAa;CAEjB,OAAO,eAAe,QAAQ,YAAY,IAAI,GAAG;EAC/C,IAAI,WAAW,KAAK,YAAY,cAAc,CAAC,GAC7C,OAAO;EAET,aAAa,QAAQ,YAAY,IAAI;CACvC;CAIA,OAAO;AACT;AAEA,MAAa,OAAO,OAAO,aAAsB,YAA0B;CAKzE,MAAM,aAJO,cACT,gBAAgB,QAAQ,WAAW,CAAC,IACpC,gBAAgB,QAAQ,IAAI,CAAC,GAER,OAAO;AAClC"}
|
package/dist/esm/initMCP.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"initMCP.mjs","names":[],"sources":["../../src/initMCP.ts"],"sourcesContent":["import { resolve } from 'node:path';\nimport * as p from '@clack/prompts';\nimport {\n installMCP,\n type MCPTransport,\n PLATFORMS,\n} from '@intlayer/chokidar/cli';\nimport enquirer from 'enquirer';\nimport { findProjectRoot } from './init';\nimport { getDetectedPlatform, PLATFORM_OPTIONS } from './initSkills';\n\nexport const initMCP = async (projectRoot?: string) => {\n const root = findProjectRoot(\n projectRoot ? resolve(projectRoot) : process.cwd()\n );\n\n p.intro('Initializing Intlayer MCP Server');\n\n const detectedPlatform = getDetectedPlatform();\n\n let platform: any;\n try {\n const response = await enquirer.prompt<{ platforms: any }>({\n type: 'autocomplete',\n name: 'platforms',\n message: 'Which platform are you using? (Type to search)',\n multiple: false,\n initial: detectedPlatform\n ? PLATFORMS.indexOf(detectedPlatform)\n : undefined,\n choices: PLATFORM_OPTIONS.map((opt) => ({\n name: opt.value,\n message: opt.label,\n hint: opt.hint,\n })),\n });\n platform = response.platforms;\n } catch {\n p.cancel('Operation cancelled.');\n return;\n }\n\n if (!platform) {\n p.cancel('Operation cancelled. No platform selected.');\n return;\n }\n\n const transport = (await p.select({\n message: 'Which transport method do you want to use?',\n options: [\n {\n value: 'stdio',\n label: 'Local server (stdio)',\n hint: 'Recommended. Integrates all features including CLI tools.',\n },\n {\n value: 'sse',\n label: 'Remote server (SSE)',\n hint: 'Hosted by Intlayer. Documentation only.',\n },\n ],\n })) as MCPTransport;\n\n if (p.isCancel(transport) || !transport) {\n p.cancel('Operation cancelled.');\n return;\n }\n\n const s = p.spinner();\n s.start('Configuring MCP Server...');\n\n try {\n const result = await installMCP(root, platform, transport);\n\n s.stop('MCP Server configured successfully');\n\n p.note(result, 'Success');\n } catch (error) {\n s.stop('Failed to configure MCP Server');\n p.log.error(error instanceof Error ? error.message : String(error));\n }\n\n p.outro('Intlayer MCP Server initialization complete');\n};\n"],"mappings":";;;;;;;;AAWA,MAAa,UAAU,OAAO,gBAAyB;CACrD,MAAM,OAAO,gBACX,cAAc,QAAQ,
|
|
1
|
+
{"version":3,"file":"initMCP.mjs","names":[],"sources":["../../src/initMCP.ts"],"sourcesContent":["import { resolve } from 'node:path';\nimport * as p from '@clack/prompts';\nimport {\n installMCP,\n type MCPTransport,\n PLATFORMS,\n} from '@intlayer/chokidar/cli';\nimport enquirer from 'enquirer';\nimport { findProjectRoot } from './init';\nimport { getDetectedPlatform, PLATFORM_OPTIONS } from './initSkills';\n\nexport const initMCP = async (projectRoot?: string) => {\n const root = findProjectRoot(\n projectRoot ? resolve(projectRoot) : process.cwd()\n );\n\n p.intro('Initializing Intlayer MCP Server');\n\n const detectedPlatform = getDetectedPlatform();\n\n let platform: any;\n try {\n const response = await enquirer.prompt<{ platforms: any }>({\n type: 'autocomplete',\n name: 'platforms',\n message: 'Which platform are you using? (Type to search)',\n multiple: false,\n initial: detectedPlatform\n ? PLATFORMS.indexOf(detectedPlatform)\n : undefined,\n choices: PLATFORM_OPTIONS.map((opt) => ({\n name: opt.value,\n message: opt.label,\n hint: opt.hint,\n })),\n });\n platform = response.platforms;\n } catch {\n p.cancel('Operation cancelled.');\n return;\n }\n\n if (!platform) {\n p.cancel('Operation cancelled. No platform selected.');\n return;\n }\n\n const transport = (await p.select({\n message: 'Which transport method do you want to use?',\n options: [\n {\n value: 'stdio',\n label: 'Local server (stdio)',\n hint: 'Recommended. Integrates all features including CLI tools.',\n },\n {\n value: 'sse',\n label: 'Remote server (SSE)',\n hint: 'Hosted by Intlayer. Documentation only.',\n },\n ],\n })) as MCPTransport;\n\n if (p.isCancel(transport) || !transport) {\n p.cancel('Operation cancelled.');\n return;\n }\n\n const s = p.spinner();\n s.start('Configuring MCP Server...');\n\n try {\n const result = await installMCP(root, platform, transport);\n\n s.stop('MCP Server configured successfully');\n\n p.note(result, 'Success');\n } catch (error) {\n s.stop('Failed to configure MCP Server');\n p.log.error(error instanceof Error ? error.message : String(error));\n }\n\n p.outro('Intlayer MCP Server initialization complete');\n};\n"],"mappings":";;;;;;;;AAWA,MAAa,UAAU,OAAO,gBAAyB;CACrD,MAAM,OAAO,gBACX,cAAc,QAAQ,WAAW,IAAI,QAAQ,IAAI,CACnD;CAEA,EAAE,MAAM,kCAAkC;CAE1C,MAAM,mBAAmB,oBAAoB;CAE7C,IAAI;CACJ,IAAI;EAeF,YAAW,MAdY,SAAS,OAA2B;GACzD,MAAM;GACN,MAAM;GACN,SAAS;GACT,UAAU;GACV,SAAS,mBACL,UAAU,QAAQ,gBAAgB,IAClC;GACJ,SAAS,iBAAiB,KAAK,SAAS;IACtC,MAAM,IAAI;IACV,SAAS,IAAI;IACb,MAAM,IAAI;GACZ,EAAE;EACJ,CAAC,GACmB;CACtB,QAAQ;EACN,EAAE,OAAO,sBAAsB;EAC/B;CACF;CAEA,IAAI,CAAC,UAAU;EACb,EAAE,OAAO,4CAA4C;EACrD;CACF;CAEA,MAAM,YAAa,MAAM,EAAE,OAAO;EAChC,SAAS;EACT,SAAS,CACP;GACE,OAAO;GACP,OAAO;GACP,MAAM;EACR,GACA;GACE,OAAO;GACP,OAAO;GACP,MAAM;EACR,CACF;CACF,CAAC;CAED,IAAI,EAAE,SAAS,SAAS,KAAK,CAAC,WAAW;EACvC,EAAE,OAAO,sBAAsB;EAC/B;CACF;CAEA,MAAM,IAAI,EAAE,QAAQ;CACpB,EAAE,MAAM,2BAA2B;CAEnC,IAAI;EACF,MAAM,SAAS,MAAM,WAAW,MAAM,UAAU,SAAS;EAEzD,EAAE,KAAK,oCAAoC;EAE3C,EAAE,KAAK,QAAQ,SAAS;CAC1B,SAAS,OAAO;EACd,EAAE,KAAK,gCAAgC;EACvC,EAAE,IAAI,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;CACpE;CAEA,EAAE,MAAM,6CAA6C;AACvD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"initSkills.mjs","names":[],"sources":["../../src/initSkills.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport * as p from '@clack/prompts';\nimport {\n getInitialSkills,\n installSkills,\n PLATFORMS,\n PLATFORMS_METADATA,\n type Platform,\n SKILLS,\n SKILLS_METADATA,\n} from '@intlayer/chokidar/cli';\nimport enquirer from 'enquirer';\nimport { findProjectRoot } from './init';\n\nconst PLATFORM_CHECKS: Array<{ check: () => boolean; platform: Platform }> =\n PLATFORMS.filter((platform) => PLATFORMS_METADATA[platform].check).map(\n (platform) => ({\n check: PLATFORMS_METADATA[platform].check ?? (() => false),\n platform,\n })\n );\n\nexport const PLATFORM_OPTIONS: Array<{\n value: Platform;\n label: string;\n hint: string;\n}> = PLATFORMS.map((platform) => ({\n value: platform,\n label: PLATFORMS_METADATA[platform].label,\n hint: `(${PLATFORMS_METADATA[platform].dir})`,\n}));\n\nexport const getDetectedPlatform = (): Platform | undefined =>\n PLATFORM_CHECKS.find(({ check }) => check())?.platform;\n\nconst getDependencies = (root: string): Record<string, string> => {\n try {\n const packageJsonPath = join(root, 'package.json');\n if (!existsSync(packageJsonPath)) return {};\n\n const { dependencies = {}, devDependencies = {} } = JSON.parse(\n readFileSync(packageJsonPath, 'utf-8')\n );\n return { ...dependencies, ...devDependencies };\n } catch {\n return {};\n }\n};\n\nexport const initSkills = async (projectRoot?: string) => {\n const root = findProjectRoot(\n projectRoot ? resolve(projectRoot) : process.cwd()\n );\n\n p.intro('Initializing Intlayer skills');\n\n const detectedPlatform = getDetectedPlatform();\n\n let platform: Platform;\n try {\n const response = await enquirer.prompt<{ platforms: Platform }>({\n type: 'autocomplete',\n name: 'platforms',\n message: 'Which platforms are you using? (Type to search)',\n multiple: false,\n initial: detectedPlatform\n ? PLATFORMS.indexOf(detectedPlatform)\n : undefined,\n choices: PLATFORM_OPTIONS.map((opt) => ({\n name: opt.value,\n message: opt.label,\n hint: opt.hint,\n })),\n });\n platform = response.platforms;\n } catch {\n p.cancel('Operation cancelled.');\n return;\n }\n\n if (!platform) {\n p.log.warn('No platform selected. Nothing to install.');\n return;\n }\n\n const dependencies = getDependencies(root);\n const initialValues = getInitialSkills(dependencies);\n\n const selectedSkills = await p.multiselect({\n message: 'Select the documentation skills to provide to your AI:',\n initialValues,\n options: SKILLS.map((skill) => ({\n value: skill,\n label: skill,\n hint: SKILLS_METADATA[skill],\n })),\n required: false,\n });\n\n if (\n p.isCancel(selectedSkills) ||\n !selectedSkills ||\n (selectedSkills as string[]).length === 0\n ) {\n p.cancel('Operation cancelled. No skills selected.');\n return;\n }\n\n const s = p.spinner();\n s.start('Installing skills...');\n\n try {\n const result = await installSkills(root, platform, selectedSkills);\n\n s.stop('Skills installed successfully');\n\n p.note(result, 'Success');\n } catch (error) {\n s.stop('Failed to install skills');\n p.log.error(error instanceof Error ? error.message : String(error));\n }\n\n p.outro('Intlayer skills initialization complete');\n};\n"],"mappings":";;;;;;;;AAeA,MAAM,kBACJ,UAAU,QAAQ,aAAa,mBAAmB,UAAU,
|
|
1
|
+
{"version":3,"file":"initSkills.mjs","names":[],"sources":["../../src/initSkills.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport * as p from '@clack/prompts';\nimport {\n getInitialSkills,\n installSkills,\n PLATFORMS,\n PLATFORMS_METADATA,\n type Platform,\n SKILLS,\n SKILLS_METADATA,\n} from '@intlayer/chokidar/cli';\nimport enquirer from 'enquirer';\nimport { findProjectRoot } from './init';\n\nconst PLATFORM_CHECKS: Array<{ check: () => boolean; platform: Platform }> =\n PLATFORMS.filter((platform) => PLATFORMS_METADATA[platform].check).map(\n (platform) => ({\n check: PLATFORMS_METADATA[platform].check ?? (() => false),\n platform,\n })\n );\n\nexport const PLATFORM_OPTIONS: Array<{\n value: Platform;\n label: string;\n hint: string;\n}> = PLATFORMS.map((platform) => ({\n value: platform,\n label: PLATFORMS_METADATA[platform].label,\n hint: `(${PLATFORMS_METADATA[platform].dir})`,\n}));\n\nexport const getDetectedPlatform = (): Platform | undefined =>\n PLATFORM_CHECKS.find(({ check }) => check())?.platform;\n\nconst getDependencies = (root: string): Record<string, string> => {\n try {\n const packageJsonPath = join(root, 'package.json');\n if (!existsSync(packageJsonPath)) return {};\n\n const { dependencies = {}, devDependencies = {} } = JSON.parse(\n readFileSync(packageJsonPath, 'utf-8')\n );\n return { ...dependencies, ...devDependencies };\n } catch {\n return {};\n }\n};\n\nexport const initSkills = async (projectRoot?: string) => {\n const root = findProjectRoot(\n projectRoot ? resolve(projectRoot) : process.cwd()\n );\n\n p.intro('Initializing Intlayer skills');\n\n const detectedPlatform = getDetectedPlatform();\n\n let platform: Platform;\n try {\n const response = await enquirer.prompt<{ platforms: Platform }>({\n type: 'autocomplete',\n name: 'platforms',\n message: 'Which platforms are you using? (Type to search)',\n multiple: false,\n initial: detectedPlatform\n ? PLATFORMS.indexOf(detectedPlatform)\n : undefined,\n choices: PLATFORM_OPTIONS.map((opt) => ({\n name: opt.value,\n message: opt.label,\n hint: opt.hint,\n })),\n });\n platform = response.platforms;\n } catch {\n p.cancel('Operation cancelled.');\n return;\n }\n\n if (!platform) {\n p.log.warn('No platform selected. Nothing to install.');\n return;\n }\n\n const dependencies = getDependencies(root);\n const initialValues = getInitialSkills(dependencies);\n\n const selectedSkills = await p.multiselect({\n message: 'Select the documentation skills to provide to your AI:',\n initialValues,\n options: SKILLS.map((skill) => ({\n value: skill,\n label: skill,\n hint: SKILLS_METADATA[skill],\n })),\n required: false,\n });\n\n if (\n p.isCancel(selectedSkills) ||\n !selectedSkills ||\n (selectedSkills as string[]).length === 0\n ) {\n p.cancel('Operation cancelled. No skills selected.');\n return;\n }\n\n const s = p.spinner();\n s.start('Installing skills...');\n\n try {\n const result = await installSkills(root, platform, selectedSkills);\n\n s.stop('Skills installed successfully');\n\n p.note(result, 'Success');\n } catch (error) {\n s.stop('Failed to install skills');\n p.log.error(error instanceof Error ? error.message : String(error));\n }\n\n p.outro('Intlayer skills initialization complete');\n};\n"],"mappings":";;;;;;;;AAeA,MAAM,kBACJ,UAAU,QAAQ,aAAa,mBAAmB,UAAU,KAAK,EAAE,KAChE,cAAc;CACb,OAAO,mBAAmB,UAAU,gBAAgB;CACpD;AACF,EACF;AAEF,MAAa,mBAIR,UAAU,KAAK,cAAc;CAChC,OAAO;CACP,OAAO,mBAAmB,UAAU;CACpC,MAAM,IAAI,mBAAmB,UAAU,IAAI;AAC7C,EAAE;AAEF,MAAa,4BACX,gBAAgB,MAAM,EAAE,YAAY,MAAM,CAAC,GAAG;AAEhD,MAAM,mBAAmB,SAAyC;CAChE,IAAI;EACF,MAAM,kBAAkB,KAAK,MAAM,cAAc;EACjD,IAAI,CAAC,WAAW,eAAe,GAAG,OAAO,CAAC;EAE1C,MAAM,EAAE,eAAe,CAAC,GAAG,kBAAkB,CAAC,MAAM,KAAK,MACvD,aAAa,iBAAiB,OAAO,CACvC;EACA,OAAO;GAAE,GAAG;GAAc,GAAG;EAAgB;CAC/C,QAAQ;EACN,OAAO,CAAC;CACV;AACF;AAEA,MAAa,aAAa,OAAO,gBAAyB;CACxD,MAAM,OAAO,gBACX,cAAc,QAAQ,WAAW,IAAI,QAAQ,IAAI,CACnD;CAEA,EAAE,MAAM,8BAA8B;CAEtC,MAAM,mBAAmB,oBAAoB;CAE7C,IAAI;CACJ,IAAI;EAeF,YAAW,MAdY,SAAS,OAAgC;GAC9D,MAAM;GACN,MAAM;GACN,SAAS;GACT,UAAU;GACV,SAAS,mBACL,UAAU,QAAQ,gBAAgB,IAClC;GACJ,SAAS,iBAAiB,KAAK,SAAS;IACtC,MAAM,IAAI;IACV,SAAS,IAAI;IACb,MAAM,IAAI;GACZ,EAAE;EACJ,CAAC,GACmB;CACtB,QAAQ;EACN,EAAE,OAAO,sBAAsB;EAC/B;CACF;CAEA,IAAI,CAAC,UAAU;EACb,EAAE,IAAI,KAAK,2CAA2C;EACtD;CACF;CAGA,MAAM,gBAAgB,iBADD,gBAAgB,IACa,CAAC;CAEnD,MAAM,iBAAiB,MAAM,EAAE,YAAY;EACzC,SAAS;EACT;EACA,SAAS,OAAO,KAAK,WAAW;GAC9B,OAAO;GACP,OAAO;GACP,MAAM,gBAAgB;EACxB,EAAE;EACF,UAAU;CACZ,CAAC;CAED,IACE,EAAE,SAAS,cAAc,KACzB,CAAC,kBACA,eAA4B,WAAW,GACxC;EACA,EAAE,OAAO,0CAA0C;EACnD;CACF;CAEA,MAAM,IAAI,EAAE,QAAQ;CACpB,EAAE,MAAM,sBAAsB;CAE9B,IAAI;EACF,MAAM,SAAS,MAAM,cAAc,MAAM,UAAU,cAAc;EAEjE,EAAE,KAAK,+BAA+B;EAEtC,EAAE,KAAK,QAAQ,SAAS;CAC1B,SAAS,OAAO;EACd,EAAE,KAAK,0BAA0B;EACjC,EAAE,IAAI,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;CACpE;CAEA,EAAE,MAAM,yCAAyC;AACnD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"listContentDeclaration.mjs","names":[],"sources":["../../src/listContentDeclaration.ts"],"sourcesContent":["import { relative } from 'node:path';\nimport { prepareIntlayer } from '@intlayer/chokidar/cli';\nimport { formatPath } from '@intlayer/chokidar/utils';\nimport {\n colon,\n colorizeKey,\n colorizeNumber,\n getAppLogger,\n} from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\n\ntype ListContentDeclarationOptions = {\n configOptions?: GetConfigurationOptions;\n json?: boolean;\n absolute?: boolean;\n};\n\nexport const listContentDeclarationRows = async (\n options?: ListContentDeclarationOptions\n) => {\n const config = getConfiguration(options?.configOptions);\n\n await prepareIntlayer(config);\n\n const unmergedDictionariesRecord = getUnmergedDictionaries(config);\n\n const rows = Object.values(unmergedDictionariesRecord)\n .flat()\n .map((dictionary) => ({\n key: dictionary.key ?? '',\n path: options?.absolute\n ? (dictionary.filePath ?? 'Remote')\n : relative(config.system.baseDir, dictionary.filePath ?? 'Remote'),\n }));\n return rows;\n};\n\nexport const listContentDeclaration = async (\n options?: ListContentDeclarationOptions\n) => {\n const rows = await listContentDeclarationRows(options);\n\n if (options?.json) {\n console.log(JSON.stringify(rows));\n return;\n }\n\n const config = getConfiguration(options?.configOptions);\n const appLogger = getAppLogger(config);\n\n const lines = rows.map((row) =>\n [\n colon(` - ${colorizeKey(row.key)}`, {\n colSize: rows.map((row) => row.key.length),\n maxSize: 60,\n }),\n ' - ',\n formatPath(row.path),\n ].join('')\n );\n\n appLogger(`Content declaration files:`);\n\n lines.forEach((line) => {\n appLogger(line, {\n level: 'info',\n });\n });\n\n appLogger(`Total content declaration files: ${colorizeNumber(rows.length)}`);\n};\n"],"mappings":";;;;;;;;AAqBA,MAAa,6BAA6B,OACxC,YACG;CACH,MAAM,SAAS,iBAAiB,SAAS,
|
|
1
|
+
{"version":3,"file":"listContentDeclaration.mjs","names":[],"sources":["../../src/listContentDeclaration.ts"],"sourcesContent":["import { relative } from 'node:path';\nimport { prepareIntlayer } from '@intlayer/chokidar/cli';\nimport { formatPath } from '@intlayer/chokidar/utils';\nimport {\n colon,\n colorizeKey,\n colorizeNumber,\n getAppLogger,\n} from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\n\ntype ListContentDeclarationOptions = {\n configOptions?: GetConfigurationOptions;\n json?: boolean;\n absolute?: boolean;\n};\n\nexport const listContentDeclarationRows = async (\n options?: ListContentDeclarationOptions\n) => {\n const config = getConfiguration(options?.configOptions);\n\n await prepareIntlayer(config);\n\n const unmergedDictionariesRecord = getUnmergedDictionaries(config);\n\n const rows = Object.values(unmergedDictionariesRecord)\n .flat()\n .map((dictionary) => ({\n key: dictionary.key ?? '',\n path: options?.absolute\n ? (dictionary.filePath ?? 'Remote')\n : relative(config.system.baseDir, dictionary.filePath ?? 'Remote'),\n }));\n return rows;\n};\n\nexport const listContentDeclaration = async (\n options?: ListContentDeclarationOptions\n) => {\n const rows = await listContentDeclarationRows(options);\n\n if (options?.json) {\n console.log(JSON.stringify(rows));\n return;\n }\n\n const config = getConfiguration(options?.configOptions);\n const appLogger = getAppLogger(config);\n\n const lines = rows.map((row) =>\n [\n colon(` - ${colorizeKey(row.key)}`, {\n colSize: rows.map((row) => row.key.length),\n maxSize: 60,\n }),\n ' - ',\n formatPath(row.path),\n ].join('')\n );\n\n appLogger(`Content declaration files:`);\n\n lines.forEach((line) => {\n appLogger(line, {\n level: 'info',\n });\n });\n\n appLogger(`Total content declaration files: ${colorizeNumber(rows.length)}`);\n};\n"],"mappings":";;;;;;;;AAqBA,MAAa,6BAA6B,OACxC,YACG;CACH,MAAM,SAAS,iBAAiB,SAAS,aAAa;CAEtD,MAAM,gBAAgB,MAAM;CAE5B,MAAM,6BAA6B,wBAAwB,MAAM;CAUjE,OARa,OAAO,OAAO,0BAA0B,EAClD,KAAK,EACL,KAAK,gBAAgB;EACpB,KAAK,WAAW,OAAO;EACvB,MAAM,SAAS,WACV,WAAW,YAAY,WACxB,SAAS,OAAO,OAAO,SAAS,WAAW,YAAY,QAAQ;CACrE,EACQ;AACZ;AAEA,MAAa,yBAAyB,OACpC,YACG;CACH,MAAM,OAAO,MAAM,2BAA2B,OAAO;CAErD,IAAI,SAAS,MAAM;EACjB,QAAQ,IAAI,KAAK,UAAU,IAAI,CAAC;EAChC;CACF;CAGA,MAAM,YAAY,aADH,iBAAiB,SAAS,aACL,CAAC;CAErC,MAAM,QAAQ,KAAK,KAAK,QACtB;EACE,MAAM,MAAM,YAAY,IAAI,GAAG,KAAK;GAClC,SAAS,KAAK,KAAK,QAAQ,IAAI,IAAI,MAAM;GACzC,SAAS;EACX,CAAC;EACD;EACA,WAAW,IAAI,IAAI;CACrB,EAAE,KAAK,EAAE,CACX;CAEA,UAAU,4BAA4B;CAEtC,MAAM,SAAS,SAAS;EACtB,UAAU,MAAM,EACd,OAAO,OACT,CAAC;CACH,CAAC;CAED,UAAU,oCAAoC,eAAe,KAAK,MAAM,GAAG;AAC7E"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"listProjects.mjs","names":[],"sources":["../../src/listProjects.ts"],"sourcesContent":["import { relative } from 'node:path';\nimport { type ListProjectsOptions, listProjects } from '@intlayer/chokidar/cli';\n\nexport type ListProjectsCommandOptions = ListProjectsOptions & {\n json?: boolean;\n absolute?: boolean;\n};\n\nexport const listProjectsCommand = async (\n options?: ListProjectsCommandOptions\n) => {\n const { searchDir, projectsPath } = await listProjects(options);\n\n const projectsRelativePath = projectsPath\n .map((projectPath) =>\n options?.absolute ? projectPath : relative(searchDir, projectPath)\n )\n .map((projectPath) => (projectPath === '' ? '.' : projectPath));\n\n if (options?.json) {\n console.dir(projectsRelativePath, { depth: null, arrayLimit: null });\n return;\n }\n\n if (projectsPath.length === 0) {\n console.log('No Intlayer projects found.');\n return;\n }\n\n console.log(`Found ${projectsPath.length} Intlayer project(s):\\n`);\n projectsPath.forEach((project) => {\n console.log(` - ${project}`);\n });\n};\n"],"mappings":";;;;AAQA,MAAa,sBAAsB,OACjC,YACG;CACH,MAAM,EAAE,WAAW,iBAAiB,MAAM,aAAa,
|
|
1
|
+
{"version":3,"file":"listProjects.mjs","names":[],"sources":["../../src/listProjects.ts"],"sourcesContent":["import { relative } from 'node:path';\nimport { type ListProjectsOptions, listProjects } from '@intlayer/chokidar/cli';\n\nexport type ListProjectsCommandOptions = ListProjectsOptions & {\n json?: boolean;\n absolute?: boolean;\n};\n\nexport const listProjectsCommand = async (\n options?: ListProjectsCommandOptions\n) => {\n const { searchDir, projectsPath } = await listProjects(options);\n\n const projectsRelativePath = projectsPath\n .map((projectPath) =>\n options?.absolute ? projectPath : relative(searchDir, projectPath)\n )\n .map((projectPath) => (projectPath === '' ? '.' : projectPath));\n\n if (options?.json) {\n console.dir(projectsRelativePath, { depth: null, arrayLimit: null });\n return;\n }\n\n if (projectsPath.length === 0) {\n console.log('No Intlayer projects found.');\n return;\n }\n\n console.log(`Found ${projectsPath.length} Intlayer project(s):\\n`);\n projectsPath.forEach((project) => {\n console.log(` - ${project}`);\n });\n};\n"],"mappings":";;;;AAQA,MAAa,sBAAsB,OACjC,YACG;CACH,MAAM,EAAE,WAAW,iBAAiB,MAAM,aAAa,OAAO;CAE9D,MAAM,uBAAuB,aAC1B,KAAK,gBACJ,SAAS,WAAW,cAAc,SAAS,WAAW,WAAW,CACnE,EACC,KAAK,gBAAiB,gBAAgB,KAAK,MAAM,WAAY;CAEhE,IAAI,SAAS,MAAM;EACjB,QAAQ,IAAI,sBAAsB;GAAE,OAAO;GAAM,YAAY;EAAK,CAAC;EACnE;CACF;CAEA,IAAI,aAAa,WAAW,GAAG;EAC7B,QAAQ,IAAI,6BAA6B;EACzC;CACF;CAEA,QAAQ,IAAI,SAAS,aAAa,OAAO,wBAAwB;CACjE,aAAa,SAAS,YAAY;EAChC,QAAQ,IAAI,OAAO,SAAS;CAC9B,CAAC;AACH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"liveSync.mjs","names":[],"sources":["../../src/liveSync.ts"],"sourcesContent":["import { createServer } from 'node:http';\n// @ts-ignore: @intlayer/backend is not built yet\nimport type { DictionaryAPI } from '@intlayer/backend';\nimport { buildDictionary } from '@intlayer/chokidar/build';\nimport { type ParallelHandle, runParallel } from '@intlayer/chokidar/utils';\nimport { getAppLogger } from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport packageJson from '@intlayer/config/package.json' with { type: 'json' };\nimport { getLocalizedContent } from '@intlayer/core/plugins';\nimport { getDictionaries } from '@intlayer/dictionaries-entry';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\nimport { IntlayerEventListener } from './IntlayerEventListener';\n\ntype LiveSyncOptions = {\n with?: string | string[];\n configOptions?: GetConfigurationOptions;\n};\n\nconst writeDictionary = async (\n dictionary: DictionaryAPI,\n configuration: IntlayerConfig\n) => {\n const appLogger = getAppLogger(configuration);\n appLogger(`Writing dictionary ${dictionary.key}`);\n await buildDictionary([dictionary], configuration);\n};\n\nexport const liveSync = async (options?: LiveSyncOptions) => {\n const configuration = getConfiguration(options?.configOptions);\n const appLogger = getAppLogger(configuration);\n\n const { liveSyncPort, liveSyncURL } = configuration.editor;\n\n let parallelProcess: ParallelHandle | null = null;\n let eventListener: IntlayerEventListener | null = null;\n let _isHotReloadConnected = false;\n let _connectionStatus = 'disconnected'; // 'connected', 'connecting', 'reconnecting', 'disconnected', 'error'\n\n // Start the parallel process if provided\n if (options?.with) {\n parallelProcess = runParallel(options.with);\n // Handle the promise to avoid unhandled rejection\n parallelProcess.result.catch(() => {\n // Parallel process failed or was terminated\n });\n }\n\n // Initialize the event listener for hot reload if configured\n if (\n configuration.editor.liveSync &&\n configuration.editor.backendURL &&\n configuration.editor.clientId &&\n configuration.editor.clientSecret\n ) {\n eventListener = new IntlayerEventListener(configuration);\n _connectionStatus = 'connecting';\n\n // Set up connection callbacks\n eventListener.onConnectionOpen = () => {\n _connectionStatus = 'connected';\n _isHotReloadConnected = true;\n appLogger('Live sync connection established');\n };\n\n eventListener.onConnectionError = (error) => {\n _connectionStatus = 'error';\n _isHotReloadConnected = false;\n const errorEvent = error as any;\n appLogger(\n `Live sync connection error: ${errorEvent.message ?? 'Unknown error'}`,\n {\n level: 'warn',\n }\n );\n\n // If this is a \"terminated: other side closed\" error, it's likely a server restart\n if (\n errorEvent.message?.includes('terminated') ||\n errorEvent.message?.includes('closed')\n ) {\n appLogger(\n 'Server connection was terminated, automatic reconnection will be attempted...',\n {\n level: 'info',\n }\n );\n _connectionStatus = 'reconnecting';\n }\n };\n\n // Set up dictionary change callbacks\n eventListener.onDictionaryAdded = (dictionary) =>\n writeDictionary(dictionary, configuration);\n eventListener.onDictionaryChange = (dictionary) =>\n writeDictionary(dictionary, configuration);\n eventListener.onDictionaryDeleted = (dictionary) =>\n writeDictionary(dictionary, configuration);\n\n try {\n await eventListener.initialize();\n } catch (error) {\n _connectionStatus = 'error';\n _isHotReloadConnected = false;\n appLogger('Failed to initialize IntlayerEventListener:', {\n level: 'error',\n });\n appLogger(\n `Error: ${error instanceof Error ? error.message : String(error)}`,\n {\n level: 'error',\n }\n );\n }\n } else if (!configuration.editor.liveSync) {\n appLogger(\n 'Hot reload is disabled. Please enable it in the configuration (editor.liveSync).'\n );\n } else if (\n !configuration.editor.clientId ||\n !configuration.editor.clientSecret\n ) {\n appLogger(\n 'Missing client credentials for hot reload. Please configure clientId and clientSecret'\n );\n }\n\n const server = createServer(async (req, res) => {\n // Handle CORS preflight requests\n if (req.method === 'OPTIONS') {\n res.writeHead(200, {\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type, Authorization',\n });\n\n res.end();\n return;\n }\n\n if (req.url?.startsWith('/dictionaries')) {\n res.writeHead(200, {\n 'Content-Type': 'application/json; charset=utf-8',\n 'Cache-Control': 'no-store',\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type, Authorization',\n });\n const dictionaries = getDictionaries(configuration);\n\n const prefix = '/dictionaries/';\n if (req.url.startsWith(prefix)) {\n const [key, locale] = decodeURIComponent(req.url)\n .slice(prefix.length)\n .split('/');\n\n const dictionary = dictionaries[key] ?? null;\n\n if (locale) {\n // @ts-ignore Type instantiation is excessively deep and possibly infinite\n const sourceLocaleContent = getLocalizedContent(\n dictionary.content,\n locale,\n {\n dictionaryKey: key,\n keyPath: [],\n }\n );\n\n res.end(JSON.stringify(sourceLocaleContent));\n return;\n }\n\n res.end(JSON.stringify(dictionary));\n return;\n }\n\n res.end(JSON.stringify(dictionaries));\n return;\n }\n\n if (req.url?.startsWith('/unmerged_dictionaries')) {\n res.writeHead(200, {\n 'Content-Type': 'application/json; charset=utf-8',\n 'Cache-Control': 'no-store',\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type, Authorization',\n });\n const unmergedDictionaries = getUnmergedDictionaries(configuration);\n\n const prefix = '/unmerged_dictionaries/';\n if (req.url.startsWith(prefix)) {\n const key = decodeURIComponent(req.url.slice(prefix.length));\n const one = unmergedDictionaries[key] ?? null;\n\n res.end(JSON.stringify(one));\n return;\n }\n\n res.end(JSON.stringify(unmergedDictionaries));\n return;\n }\n\n if (req.url === '/configuration') {\n res.writeHead(200, {\n 'Content-Type': 'application/json; charset=utf-8',\n 'Cache-Control': 'no-store',\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type, Authorization',\n });\n res.end(JSON.stringify(configuration));\n return;\n }\n\n if (req.url === '/health') {\n res.writeHead(200, {\n 'Content-Type': 'application/json; charset=utf-8',\n });\n res.end(JSON.stringify({ status: 'ok' }));\n return;\n }\n\n res.end('Not found');\n return;\n });\n\n const getLiveSyncParam = () => {\n if (!configuration.editor.liveSync) return '\\x1b[31m✗ Disabled\\x1b[0m';\n\n return '\\x1b[32m✓ Enabled\\x1b[0m';\n };\n server.listen(liveSyncPort, () => {\n console.log(`\n \\x1b[1;90mINTLAYER v${packageJson.version}\\x1b[0m\n \n Live server running at: \\x1b[90m${liveSyncURL}\\x1b[0m\n - Backend URL: \\x1b[90m${configuration.editor.backendURL ?? '-'}\\x1b[0m\n - Live sync: ${getLiveSyncParam()}\n - Parallel process: ${options?.with ? `\\x1b[90m${Array.isArray(options.with) ? options.with.join(' ') : options.with}\\x1b[0m` : '-'}\n - Access key: ${configuration.editor.clientId ?? '-'}\n `);\n });\n\n // Cleanup function to terminate child process and event listener when the main process exits\n const cleanup = () => {\n // Clean up event listener\n if (eventListener) {\n appLogger('Closing SSE connection...');\n eventListener.cleanup();\n }\n\n if (parallelProcess) {\n parallelProcess.kill();\n }\n\n server.close(() => {\n appLogger('Live sync server stopped');\n process.exit(0);\n });\n };\n\n // Handle process termination signals\n process.on('SIGINT', cleanup);\n process.on('SIGTERM', cleanup);\n process.on('exit', cleanup);\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"liveSync.mjs","names":[],"sources":["../../src/liveSync.ts"],"sourcesContent":["import { createServer } from 'node:http';\n// @ts-ignore: @intlayer/backend is not built yet\nimport type { DictionaryAPI } from '@intlayer/backend';\nimport { buildDictionary } from '@intlayer/chokidar/build';\nimport { type ParallelHandle, runParallel } from '@intlayer/chokidar/utils';\nimport { getAppLogger } from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport packageJson from '@intlayer/config/package.json' with { type: 'json' };\nimport { getLocalizedContent } from '@intlayer/core/plugins';\nimport { getDictionaries } from '@intlayer/dictionaries-entry';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\nimport { IntlayerEventListener } from './IntlayerEventListener';\n\ntype LiveSyncOptions = {\n with?: string | string[];\n configOptions?: GetConfigurationOptions;\n};\n\nconst writeDictionary = async (\n dictionary: DictionaryAPI,\n configuration: IntlayerConfig\n) => {\n const appLogger = getAppLogger(configuration);\n appLogger(`Writing dictionary ${dictionary.key}`);\n await buildDictionary([dictionary], configuration);\n};\n\nexport const liveSync = async (options?: LiveSyncOptions) => {\n const configuration = getConfiguration(options?.configOptions);\n const appLogger = getAppLogger(configuration);\n\n const { liveSyncPort, liveSyncURL } = configuration.editor;\n\n let parallelProcess: ParallelHandle | null = null;\n let eventListener: IntlayerEventListener | null = null;\n let _isHotReloadConnected = false;\n let _connectionStatus = 'disconnected'; // 'connected', 'connecting', 'reconnecting', 'disconnected', 'error'\n\n // Start the parallel process if provided\n if (options?.with) {\n parallelProcess = runParallel(options.with);\n // Handle the promise to avoid unhandled rejection\n parallelProcess.result.catch(() => {\n // Parallel process failed or was terminated\n });\n }\n\n // Initialize the event listener for hot reload if configured\n if (\n configuration.editor.liveSync &&\n configuration.editor.backendURL &&\n configuration.editor.clientId &&\n configuration.editor.clientSecret\n ) {\n eventListener = new IntlayerEventListener(configuration);\n _connectionStatus = 'connecting';\n\n // Set up connection callbacks\n eventListener.onConnectionOpen = () => {\n _connectionStatus = 'connected';\n _isHotReloadConnected = true;\n appLogger('Live sync connection established');\n };\n\n eventListener.onConnectionError = (error) => {\n _connectionStatus = 'error';\n _isHotReloadConnected = false;\n const errorEvent = error as any;\n appLogger(\n `Live sync connection error: ${errorEvent.message ?? 'Unknown error'}`,\n {\n level: 'warn',\n }\n );\n\n // If this is a \"terminated: other side closed\" error, it's likely a server restart\n if (\n errorEvent.message?.includes('terminated') ||\n errorEvent.message?.includes('closed')\n ) {\n appLogger(\n 'Server connection was terminated, automatic reconnection will be attempted...',\n {\n level: 'info',\n }\n );\n _connectionStatus = 'reconnecting';\n }\n };\n\n // Set up dictionary change callbacks\n eventListener.onDictionaryAdded = (dictionary) =>\n writeDictionary(dictionary, configuration);\n eventListener.onDictionaryChange = (dictionary) =>\n writeDictionary(dictionary, configuration);\n eventListener.onDictionaryDeleted = (dictionary) =>\n writeDictionary(dictionary, configuration);\n\n try {\n await eventListener.initialize();\n } catch (error) {\n _connectionStatus = 'error';\n _isHotReloadConnected = false;\n appLogger('Failed to initialize IntlayerEventListener:', {\n level: 'error',\n });\n appLogger(\n `Error: ${error instanceof Error ? error.message : String(error)}`,\n {\n level: 'error',\n }\n );\n }\n } else if (!configuration.editor.liveSync) {\n appLogger(\n 'Hot reload is disabled. Please enable it in the configuration (editor.liveSync).'\n );\n } else if (\n !configuration.editor.clientId ||\n !configuration.editor.clientSecret\n ) {\n appLogger(\n 'Missing client credentials for hot reload. Please configure clientId and clientSecret'\n );\n }\n\n const server = createServer(async (req, res) => {\n // Handle CORS preflight requests\n if (req.method === 'OPTIONS') {\n res.writeHead(200, {\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type, Authorization',\n });\n\n res.end();\n return;\n }\n\n if (req.url?.startsWith('/dictionaries')) {\n res.writeHead(200, {\n 'Content-Type': 'application/json; charset=utf-8',\n 'Cache-Control': 'no-store',\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type, Authorization',\n });\n const dictionaries = getDictionaries(configuration);\n\n const prefix = '/dictionaries/';\n if (req.url.startsWith(prefix)) {\n const [key, locale] = decodeURIComponent(req.url)\n .slice(prefix.length)\n .split('/');\n\n const dictionary = dictionaries[key] ?? null;\n\n if (locale) {\n // @ts-ignore Type instantiation is excessively deep and possibly infinite\n const sourceLocaleContent = getLocalizedContent(\n dictionary.content,\n locale,\n {\n dictionaryKey: key,\n keyPath: [],\n }\n );\n\n res.end(JSON.stringify(sourceLocaleContent));\n return;\n }\n\n res.end(JSON.stringify(dictionary));\n return;\n }\n\n res.end(JSON.stringify(dictionaries));\n return;\n }\n\n if (req.url?.startsWith('/unmerged_dictionaries')) {\n res.writeHead(200, {\n 'Content-Type': 'application/json; charset=utf-8',\n 'Cache-Control': 'no-store',\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type, Authorization',\n });\n const unmergedDictionaries = getUnmergedDictionaries(configuration);\n\n const prefix = '/unmerged_dictionaries/';\n if (req.url.startsWith(prefix)) {\n const key = decodeURIComponent(req.url.slice(prefix.length));\n const one = unmergedDictionaries[key] ?? null;\n\n res.end(JSON.stringify(one));\n return;\n }\n\n res.end(JSON.stringify(unmergedDictionaries));\n return;\n }\n\n if (req.url === '/configuration') {\n res.writeHead(200, {\n 'Content-Type': 'application/json; charset=utf-8',\n 'Cache-Control': 'no-store',\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type, Authorization',\n });\n res.end(JSON.stringify(configuration));\n return;\n }\n\n if (req.url === '/health') {\n res.writeHead(200, {\n 'Content-Type': 'application/json; charset=utf-8',\n });\n res.end(JSON.stringify({ status: 'ok' }));\n return;\n }\n\n res.end('Not found');\n return;\n });\n\n const getLiveSyncParam = () => {\n if (!configuration.editor.liveSync) return '\\x1b[31m✗ Disabled\\x1b[0m';\n\n return '\\x1b[32m✓ Enabled\\x1b[0m';\n };\n server.listen(liveSyncPort, () => {\n console.log(`\n \\x1b[1;90mINTLAYER v${packageJson.version}\\x1b[0m\n \n Live server running at: \\x1b[90m${liveSyncURL}\\x1b[0m\n - Backend URL: \\x1b[90m${configuration.editor.backendURL ?? '-'}\\x1b[0m\n - Live sync: ${getLiveSyncParam()}\n - Parallel process: ${options?.with ? `\\x1b[90m${Array.isArray(options.with) ? options.with.join(' ') : options.with}\\x1b[0m` : '-'}\n - Access key: ${configuration.editor.clientId ?? '-'}\n `);\n });\n\n // Cleanup function to terminate child process and event listener when the main process exits\n const cleanup = () => {\n // Clean up event listener\n if (eventListener) {\n appLogger('Closing SSE connection...');\n eventListener.cleanup();\n }\n\n if (parallelProcess) {\n parallelProcess.kill();\n }\n\n server.close(() => {\n appLogger('Live sync server stopped');\n process.exit(0);\n });\n };\n\n // Handle process termination signals\n process.on('SIGINT', cleanup);\n process.on('SIGTERM', cleanup);\n process.on('exit', cleanup);\n};\n"],"mappings":"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"}
|
package/dist/esm/pull.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { checkCMSAuth } from "./utils/checkAccess.mjs";
|
|
1
|
+
import { checkCMSAuth, getAuthenticatedAPI } from "./utils/checkAccess.mjs";
|
|
2
2
|
import { PullLogger } from "./push/pullLog.mjs";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
@@ -9,7 +9,6 @@ import * as ANSIColors from "@intlayer/config/colors";
|
|
|
9
9
|
import { getAppLogger } from "@intlayer/config/logger";
|
|
10
10
|
import { getConfiguration } from "@intlayer/config/node";
|
|
11
11
|
import { getUnmergedDictionaries } from "@intlayer/unmerged-dictionaries-entry";
|
|
12
|
-
import { getIntlayerAPIProxy } from "@intlayer/api";
|
|
13
12
|
import { getProjectRequire } from "@intlayer/config/utils";
|
|
14
13
|
|
|
15
14
|
//#region src/pull.ts
|
|
@@ -18,12 +17,12 @@ import { getProjectRequire } from "@intlayer/config/utils";
|
|
|
18
17
|
* with progress indicators and concurrency control.
|
|
19
18
|
*/
|
|
20
19
|
const pull = async (options) => {
|
|
21
|
-
const
|
|
20
|
+
const config = getConfiguration(options?.configOptions);
|
|
21
|
+
const appLogger = getAppLogger(config);
|
|
22
22
|
try {
|
|
23
|
-
const config = getConfiguration(options?.configOptions);
|
|
24
23
|
logConfigDetails(options?.configOptions);
|
|
25
24
|
if (!await checkCMSAuth(config)) return;
|
|
26
|
-
const intlayerAPI =
|
|
25
|
+
const intlayerAPI = await getAuthenticatedAPI(config);
|
|
27
26
|
const unmergedDictionariesRecord = getUnmergedDictionaries(config);
|
|
28
27
|
const getDictionariesUpdateTimestampResult = await intlayerAPI.dictionary.getDictionariesUpdateTimestamp();
|
|
29
28
|
if (!getDictionariesUpdateTimestampResult.data) throw new Error("No distant dictionaries found");
|
package/dist/esm/pull.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pull.mjs","names":[],"sources":["../../src/pull.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { getIntlayerAPIProxy } from '@intlayer/api';\nimport {\n type DictionaryStatus,\n writeContentDeclaration,\n} from '@intlayer/chokidar/build';\nimport { logConfigDetails } from '@intlayer/chokidar/cli';\nimport { parallelize } from '@intlayer/chokidar/utils';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { getAppLogger } from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport { getProjectRequire } from '@intlayer/config/utils';\nimport type { Dictionary } from '@intlayer/types/dictionary';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\nimport { PullLogger, type PullStatus } from './push/pullLog';\nimport { checkCMSAuth } from './utils/checkAccess';\n\ntype PullOptions = {\n dictionaries?: string[];\n newDictionariesPath?: string;\n configOptions?: GetConfigurationOptions;\n};\n\ntype DictionariesStatus = {\n dictionaryKey: string;\n status: DictionaryStatus | 'pending' | 'fetching' | 'error';\n error?: Error;\n errorMessage?: string;\n};\n\n/**\n * Fetch distant dictionaries and write them locally,\n * with progress indicators and concurrency control.\n */\nexport const pull = async (options?: PullOptions): Promise<void> => {\n const appLogger = getAppLogger(options?.configOptions?.override);\n\n try {\n const config = getConfiguration(options?.configOptions);\n logConfigDetails(options?.configOptions);\n\n const hasCMSAuth = await checkCMSAuth(config);\n\n if (!hasCMSAuth) return;\n\n const intlayerAPI = getIntlayerAPIProxy(undefined, config);\n\n const unmergedDictionariesRecord = getUnmergedDictionaries(config);\n\n // Get remote update timestamps map\n const getDictionariesUpdateTimestampResult =\n await intlayerAPI.dictionary.getDictionariesUpdateTimestamp();\n\n if (!getDictionariesUpdateTimestampResult.data) {\n throw new Error('No distant dictionaries found');\n }\n\n let distantDictionariesUpdateTimeStamp: Record<string, any> =\n getDictionariesUpdateTimestampResult.data;\n\n // Optional filtering by requested dictionaries\n if (options?.dictionaries) {\n distantDictionariesUpdateTimeStamp = Object.fromEntries(\n Object.entries(distantDictionariesUpdateTimeStamp).filter(([key]) =>\n options.dictionaries?.includes(key)\n )\n );\n }\n\n // Filter by location\n distantDictionariesUpdateTimeStamp = Object.fromEntries(\n Object.entries(distantDictionariesUpdateTimeStamp).filter(([key]) => {\n const localDictionaries = unmergedDictionariesRecord[key];\n const location =\n localDictionaries?.[0]?.location ??\n config.dictionary?.location ??\n 'remote';\n\n return location === 'remote' || location === 'hybrid';\n })\n );\n\n // Load local cached remote dictionaries (if any)\n const remoteDictionariesPath = join(\n config.system.mainDir,\n 'remote_dictionaries.cjs'\n );\n const requireFunction = config.build?.require ?? getProjectRequire();\n const remoteDictionariesRecord: Record<string, any> = existsSync(\n remoteDictionariesPath\n )\n ? (requireFunction(remoteDictionariesPath) as any)\n : {};\n\n // Determine which keys need fetching by comparing updatedAt with local cache\n const entries = Object.entries(distantDictionariesUpdateTimeStamp);\n const keysToFetch = entries\n .filter(([key, remoteUpdatedAtValue]) => {\n if (!remoteUpdatedAtValue) return true;\n\n const remoteUpdatedAt =\n typeof remoteUpdatedAtValue === 'object'\n ? (remoteUpdatedAtValue as any).updatedAt\n : remoteUpdatedAtValue;\n\n const local = (remoteDictionariesRecord as any)[key];\n if (!local) return true;\n const localUpdatedAtRaw = (local as any)?.updatedAt as\n | number\n | string\n | undefined;\n const localUpdatedAt =\n typeof localUpdatedAtRaw === 'number'\n ? localUpdatedAtRaw\n : localUpdatedAtRaw\n ? new Date(localUpdatedAtRaw).getTime()\n : undefined;\n if (typeof localUpdatedAt !== 'number') return true;\n return remoteUpdatedAt > localUpdatedAt;\n })\n .map(([key]) => key);\n\n const cachedKeys = entries\n .filter(([key, remoteUpdatedAtValue]) => {\n const remoteUpdatedAt =\n typeof remoteUpdatedAtValue === 'object'\n ? (remoteUpdatedAtValue as any).updatedAt\n : remoteUpdatedAtValue;\n\n const local = (remoteDictionariesRecord as any)[key];\n const localUpdatedAtRaw = (local as any)?.updatedAt as\n | number\n | string\n | undefined;\n const localUpdatedAt =\n typeof localUpdatedAtRaw === 'number'\n ? localUpdatedAtRaw\n : localUpdatedAtRaw\n ? new Date(localUpdatedAtRaw).getTime()\n : undefined;\n return (\n typeof localUpdatedAt === 'number' &&\n typeof remoteUpdatedAt === 'number' &&\n localUpdatedAt >= remoteUpdatedAt\n );\n })\n .map(([key]) => key);\n\n // Check if dictionaries list is empty\n if (entries.length === 0) {\n appLogger('No dictionaries to fetch', {\n level: 'error',\n });\n return;\n }\n\n appLogger('Fetching dictionaries:');\n\n // Prepare dictionaries statuses\n const dictionariesStatuses: DictionariesStatus[] = [\n ...cachedKeys.map((dictionaryKey) => ({\n dictionaryKey,\n status: 'imported' as DictionaryStatus,\n })),\n ...keysToFetch.map((dictionaryKey) => ({\n dictionaryKey,\n status: 'pending' as const,\n })),\n ];\n\n // Initialize aggregated logger\n const logger = new PullLogger();\n logger.update(\n dictionariesStatuses.map<PullStatus>((s) => ({\n dictionaryKey: s.dictionaryKey,\n status: s.status,\n }))\n );\n\n const successfullyFetchedDictionaries: Dictionary[] = [];\n\n const processDictionary = async (\n statusObj: DictionariesStatus\n ): Promise<void> => {\n const isCached =\n statusObj.status === 'imported' || statusObj.status === 'up-to-date';\n\n if (!isCached) {\n statusObj.status = 'fetching';\n logger.update([\n { dictionaryKey: statusObj.dictionaryKey, status: 'fetching' },\n ]);\n }\n\n try {\n let sourceDictionary: Dictionary | undefined;\n\n if (isCached) {\n sourceDictionary = remoteDictionariesRecord[\n statusObj.dictionaryKey\n ] as Dictionary | undefined;\n }\n\n if (!sourceDictionary) {\n // Fetch the dictionary\n const getDictionaryResult =\n await intlayerAPI.dictionary.getDictionary(statusObj.dictionaryKey);\n\n sourceDictionary = getDictionaryResult.data as Dictionary | undefined;\n }\n\n if (!sourceDictionary) {\n throw new Error(\n `Dictionary ${statusObj.dictionaryKey} not found on remote`\n );\n }\n\n // Check if there is a local version of this dictionary that is hybrid\n const localDictionaries =\n unmergedDictionariesRecord[statusObj.dictionaryKey];\n const localAndRemoteDictionary = localDictionaries?.find(\n (d) => d.location === 'hybrid'\n );\n\n if (localAndRemoteDictionary) {\n // We want to preserve the local properties but use the remote content\n sourceDictionary = {\n ...sourceDictionary,\n location: 'hybrid',\n filePath: localAndRemoteDictionary.filePath,\n localId: localAndRemoteDictionary.localId,\n };\n }\n\n // Now, write the dictionary to local file\n const { status } = await writeContentDeclaration(\n sourceDictionary,\n config,\n options\n );\n\n statusObj.status = status;\n logger.update([{ dictionaryKey: statusObj.dictionaryKey, status }]);\n\n successfullyFetchedDictionaries.push(sourceDictionary);\n } catch (error) {\n statusObj.status = 'error';\n statusObj.error = error as Error;\n statusObj.errorMessage = `Error fetching dictionary ${statusObj.dictionaryKey}: ${error}`;\n logger.update([\n { dictionaryKey: statusObj.dictionaryKey, status: 'error' },\n ]);\n }\n };\n\n // Process dictionaries in parallel with concurrency limit\n await parallelize(dictionariesStatuses, processDictionary, 5);\n\n // Stop the logger and render final state\n logger.finish();\n\n // Per-dictionary summary\n const iconFor = (status: DictionariesStatus['status']) => {\n switch (status) {\n case 'fetched':\n case 'imported':\n case 'updated':\n case 'up-to-date':\n case 'reimported in JSON':\n case 'new content file':\n return '✔';\n case 'error':\n return '✖';\n default:\n return '⏲';\n }\n };\n\n const colorFor = (status: DictionariesStatus['status']) => {\n switch (status) {\n case 'fetched':\n case 'imported':\n case 'updated':\n case 'up-to-date':\n return ANSIColors.GREEN;\n case 'reimported in JSON':\n case 'new content file':\n return ANSIColors.YELLOW;\n case 'error':\n return ANSIColors.RED;\n default:\n return ANSIColors.BLUE;\n }\n };\n\n for (const s of dictionariesStatuses) {\n const icon = iconFor(s.status);\n const color = colorFor(s.status);\n appLogger(\n ` - ${s.dictionaryKey} ${ANSIColors.GREY}[${color}${icon} ${s.status}${ANSIColors.GREY}]${ANSIColors.RESET}`\n );\n }\n\n // Output any error messages\n for (const statusObj of dictionariesStatuses) {\n if (statusObj.errorMessage) {\n appLogger(statusObj.errorMessage, {\n level: 'error',\n });\n }\n }\n } catch (error) {\n appLogger(error, {\n level: 'error',\n });\n }\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"pull.mjs","names":[],"sources":["../../src/pull.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport {\n type DictionaryStatus,\n writeContentDeclaration,\n} from '@intlayer/chokidar/build';\nimport { logConfigDetails } from '@intlayer/chokidar/cli';\nimport { parallelize } from '@intlayer/chokidar/utils';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { getAppLogger } from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport { getProjectRequire } from '@intlayer/config/utils';\nimport type { Dictionary } from '@intlayer/types/dictionary';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\nimport { PullLogger, type PullStatus } from './push/pullLog';\nimport { checkCMSAuth, getAuthenticatedAPI } from './utils/checkAccess';\n\ntype PullOptions = {\n dictionaries?: string[];\n newDictionariesPath?: string;\n configOptions?: GetConfigurationOptions;\n};\n\ntype DictionariesStatus = {\n dictionaryKey: string;\n status: DictionaryStatus | 'pending' | 'fetching' | 'error';\n error?: Error;\n errorMessage?: string;\n};\n\n/**\n * Fetch distant dictionaries and write them locally,\n * with progress indicators and concurrency control.\n */\nexport const pull = async (options?: PullOptions): Promise<void> => {\n const config = getConfiguration(options?.configOptions);\n const appLogger = getAppLogger(config);\n\n try {\n logConfigDetails(options?.configOptions);\n\n const hasCMSAuth = await checkCMSAuth(config);\n\n if (!hasCMSAuth) return;\n\n const intlayerAPI = await getAuthenticatedAPI(config);\n\n const unmergedDictionariesRecord = getUnmergedDictionaries(config);\n\n // Get remote update timestamps map\n const getDictionariesUpdateTimestampResult =\n await intlayerAPI.dictionary.getDictionariesUpdateTimestamp();\n\n if (!getDictionariesUpdateTimestampResult.data) {\n throw new Error('No distant dictionaries found');\n }\n\n let distantDictionariesUpdateTimeStamp: Record<string, any> =\n getDictionariesUpdateTimestampResult.data;\n\n // Optional filtering by requested dictionaries\n if (options?.dictionaries) {\n distantDictionariesUpdateTimeStamp = Object.fromEntries(\n Object.entries(distantDictionariesUpdateTimeStamp).filter(([key]) =>\n options.dictionaries?.includes(key)\n )\n );\n }\n\n // Filter by location\n distantDictionariesUpdateTimeStamp = Object.fromEntries(\n Object.entries(distantDictionariesUpdateTimeStamp).filter(([key]) => {\n const localDictionaries = unmergedDictionariesRecord[key];\n const location =\n localDictionaries?.[0]?.location ??\n config.dictionary?.location ??\n 'remote';\n\n return location === 'remote' || location === 'hybrid';\n })\n );\n\n // Load local cached remote dictionaries (if any)\n const remoteDictionariesPath = join(\n config.system.mainDir,\n 'remote_dictionaries.cjs'\n );\n const requireFunction = config.build?.require ?? getProjectRequire();\n const remoteDictionariesRecord: Record<string, any> = existsSync(\n remoteDictionariesPath\n )\n ? (requireFunction(remoteDictionariesPath) as any)\n : {};\n\n // Determine which keys need fetching by comparing updatedAt with local cache\n const entries = Object.entries(distantDictionariesUpdateTimeStamp);\n const keysToFetch = entries\n .filter(([key, remoteUpdatedAtValue]) => {\n if (!remoteUpdatedAtValue) return true;\n\n const remoteUpdatedAt =\n typeof remoteUpdatedAtValue === 'object'\n ? (remoteUpdatedAtValue as any).updatedAt\n : remoteUpdatedAtValue;\n\n const local = (remoteDictionariesRecord as any)[key];\n if (!local) return true;\n const localUpdatedAtRaw = (local as any)?.updatedAt as\n | number\n | string\n | undefined;\n const localUpdatedAt =\n typeof localUpdatedAtRaw === 'number'\n ? localUpdatedAtRaw\n : localUpdatedAtRaw\n ? new Date(localUpdatedAtRaw).getTime()\n : undefined;\n if (typeof localUpdatedAt !== 'number') return true;\n return remoteUpdatedAt > localUpdatedAt;\n })\n .map(([key]) => key);\n\n const cachedKeys = entries\n .filter(([key, remoteUpdatedAtValue]) => {\n const remoteUpdatedAt =\n typeof remoteUpdatedAtValue === 'object'\n ? (remoteUpdatedAtValue as any).updatedAt\n : remoteUpdatedAtValue;\n\n const local = (remoteDictionariesRecord as any)[key];\n const localUpdatedAtRaw = (local as any)?.updatedAt as\n | number\n | string\n | undefined;\n const localUpdatedAt =\n typeof localUpdatedAtRaw === 'number'\n ? localUpdatedAtRaw\n : localUpdatedAtRaw\n ? new Date(localUpdatedAtRaw).getTime()\n : undefined;\n return (\n typeof localUpdatedAt === 'number' &&\n typeof remoteUpdatedAt === 'number' &&\n localUpdatedAt >= remoteUpdatedAt\n );\n })\n .map(([key]) => key);\n\n // Check if dictionaries list is empty\n if (entries.length === 0) {\n appLogger('No dictionaries to fetch', {\n level: 'error',\n });\n return;\n }\n\n appLogger('Fetching dictionaries:');\n\n // Prepare dictionaries statuses\n const dictionariesStatuses: DictionariesStatus[] = [\n ...cachedKeys.map((dictionaryKey) => ({\n dictionaryKey,\n status: 'imported' as DictionaryStatus,\n })),\n ...keysToFetch.map((dictionaryKey) => ({\n dictionaryKey,\n status: 'pending' as const,\n })),\n ];\n\n // Initialize aggregated logger\n const logger = new PullLogger();\n logger.update(\n dictionariesStatuses.map<PullStatus>((s) => ({\n dictionaryKey: s.dictionaryKey,\n status: s.status,\n }))\n );\n\n const successfullyFetchedDictionaries: Dictionary[] = [];\n\n const processDictionary = async (\n statusObj: DictionariesStatus\n ): Promise<void> => {\n const isCached =\n statusObj.status === 'imported' || statusObj.status === 'up-to-date';\n\n if (!isCached) {\n statusObj.status = 'fetching';\n logger.update([\n { dictionaryKey: statusObj.dictionaryKey, status: 'fetching' },\n ]);\n }\n\n try {\n let sourceDictionary: Dictionary | undefined;\n\n if (isCached) {\n sourceDictionary = remoteDictionariesRecord[\n statusObj.dictionaryKey\n ] as Dictionary | undefined;\n }\n\n if (!sourceDictionary) {\n // Fetch the dictionary\n const getDictionaryResult =\n await intlayerAPI.dictionary.getDictionary(statusObj.dictionaryKey);\n\n sourceDictionary = getDictionaryResult.data as Dictionary | undefined;\n }\n\n if (!sourceDictionary) {\n throw new Error(\n `Dictionary ${statusObj.dictionaryKey} not found on remote`\n );\n }\n\n // Check if there is a local version of this dictionary that is hybrid\n const localDictionaries =\n unmergedDictionariesRecord[statusObj.dictionaryKey];\n const localAndRemoteDictionary = localDictionaries?.find(\n (d) => d.location === 'hybrid'\n );\n\n if (localAndRemoteDictionary) {\n // We want to preserve the local properties but use the remote content\n sourceDictionary = {\n ...sourceDictionary,\n location: 'hybrid',\n filePath: localAndRemoteDictionary.filePath,\n localId: localAndRemoteDictionary.localId,\n };\n }\n\n // Now, write the dictionary to local file\n const { status } = await writeContentDeclaration(\n sourceDictionary,\n config,\n options\n );\n\n statusObj.status = status;\n logger.update([{ dictionaryKey: statusObj.dictionaryKey, status }]);\n\n successfullyFetchedDictionaries.push(sourceDictionary);\n } catch (error) {\n statusObj.status = 'error';\n statusObj.error = error as Error;\n statusObj.errorMessage = `Error fetching dictionary ${statusObj.dictionaryKey}: ${error}`;\n logger.update([\n { dictionaryKey: statusObj.dictionaryKey, status: 'error' },\n ]);\n }\n };\n\n // Process dictionaries in parallel with concurrency limit\n await parallelize(dictionariesStatuses, processDictionary, 5);\n\n // Stop the logger and render final state\n logger.finish();\n\n // Per-dictionary summary\n const iconFor = (status: DictionariesStatus['status']) => {\n switch (status) {\n case 'fetched':\n case 'imported':\n case 'updated':\n case 'up-to-date':\n case 'reimported in JSON':\n case 'new content file':\n return '✔';\n case 'error':\n return '✖';\n default:\n return '⏲';\n }\n };\n\n const colorFor = (status: DictionariesStatus['status']) => {\n switch (status) {\n case 'fetched':\n case 'imported':\n case 'updated':\n case 'up-to-date':\n return ANSIColors.GREEN;\n case 'reimported in JSON':\n case 'new content file':\n return ANSIColors.YELLOW;\n case 'error':\n return ANSIColors.RED;\n default:\n return ANSIColors.BLUE;\n }\n };\n\n for (const s of dictionariesStatuses) {\n const icon = iconFor(s.status);\n const color = colorFor(s.status);\n appLogger(\n ` - ${s.dictionaryKey} ${ANSIColors.GREY}[${color}${icon} ${s.status}${ANSIColors.GREY}]${ANSIColors.RESET}`\n );\n }\n\n // Output any error messages\n for (const statusObj of dictionariesStatuses) {\n if (statusObj.errorMessage) {\n appLogger(statusObj.errorMessage, {\n level: 'error',\n });\n }\n }\n } catch (error) {\n appLogger(error, {\n level: 'error',\n });\n }\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAqCA,MAAa,OAAO,OAAO,YAAyC;CAClE,MAAM,SAAS,iBAAiB,SAAS,aAAa;CACtD,MAAM,YAAY,aAAa,MAAM;CAErC,IAAI;EACF,iBAAiB,SAAS,aAAa;EAIvC,IAAI,CAAC,MAFoB,aAAa,MAAM,GAE3B;EAEjB,MAAM,cAAc,MAAM,oBAAoB,MAAM;EAEpD,MAAM,6BAA6B,wBAAwB,MAAM;EAGjE,MAAM,uCACJ,MAAM,YAAY,WAAW,+BAA+B;EAE9D,IAAI,CAAC,qCAAqC,MACxC,MAAM,IAAI,MAAM,+BAA+B;EAGjD,IAAI,qCACF,qCAAqC;EAGvC,IAAI,SAAS,cACX,qCAAqC,OAAO,YAC1C,OAAO,QAAQ,kCAAkC,EAAE,QAAQ,CAAC,SAC1D,QAAQ,cAAc,SAAS,GAAG,CACpC,CACF;EAIF,qCAAqC,OAAO,YAC1C,OAAO,QAAQ,kCAAkC,EAAE,QAAQ,CAAC,SAAS;GAEnE,MAAM,WADoB,2BAA2B,OAE/B,IAAI,YACxB,OAAO,YAAY,YACnB;GAEF,OAAO,aAAa,YAAY,aAAa;EAC/C,CAAC,CACH;EAGA,MAAM,yBAAyB,KAC7B,OAAO,OAAO,SACd,yBACF;EACA,MAAM,kBAAkB,OAAO,OAAO,WAAW,kBAAkB;EACnE,MAAM,2BAAgD,WACpD,sBACF,IACK,gBAAgB,sBAAsB,IACvC,CAAC;EAGL,MAAM,UAAU,OAAO,QAAQ,kCAAkC;EACjE,MAAM,cAAc,QACjB,QAAQ,CAAC,KAAK,0BAA0B;GACvC,IAAI,CAAC,sBAAsB,OAAO;GAElC,MAAM,kBACJ,OAAO,yBAAyB,WAC3B,qBAA6B,YAC9B;GAEN,MAAM,QAAS,yBAAiC;GAChD,IAAI,CAAC,OAAO,OAAO;GACnB,MAAM,oBAAqB,OAAe;GAI1C,MAAM,iBACJ,OAAO,sBAAsB,WACzB,oBACA,oBACE,IAAI,KAAK,iBAAiB,EAAE,QAAQ,IACpC;GACR,IAAI,OAAO,mBAAmB,UAAU,OAAO;GAC/C,OAAO,kBAAkB;EAC3B,CAAC,EACA,KAAK,CAAC,SAAS,GAAG;EAErB,MAAM,aAAa,QAChB,QAAQ,CAAC,KAAK,0BAA0B;GACvC,MAAM,kBACJ,OAAO,yBAAyB,WAC3B,qBAA6B,YAC9B;GAGN,MAAM,oBADS,yBAAiC,MACN;GAI1C,MAAM,iBACJ,OAAO,sBAAsB,WACzB,oBACA,oBACE,IAAI,KAAK,iBAAiB,EAAE,QAAQ,IACpC;GACR,OACE,OAAO,mBAAmB,YAC1B,OAAO,oBAAoB,YAC3B,kBAAkB;EAEtB,CAAC,EACA,KAAK,CAAC,SAAS,GAAG;EAGrB,IAAI,QAAQ,WAAW,GAAG;GACxB,UAAU,4BAA4B,EACpC,OAAO,QACT,CAAC;GACD;EACF;EAEA,UAAU,wBAAwB;EAGlC,MAAM,uBAA6C,CACjD,GAAG,WAAW,KAAK,mBAAmB;GACpC;GACA,QAAQ;EACV,EAAE,GACF,GAAG,YAAY,KAAK,mBAAmB;GACrC;GACA,QAAQ;EACV,EAAE,CACJ;EAGA,MAAM,SAAS,IAAI,WAAW;EAC9B,OAAO,OACL,qBAAqB,KAAiB,OAAO;GAC3C,eAAe,EAAE;GACjB,QAAQ,EAAE;EACZ,EAAE,CACJ;EAEA,MAAM,kCAAgD,CAAC;EAEvD,MAAM,oBAAoB,OACxB,cACkB;GAClB,MAAM,WACJ,UAAU,WAAW,cAAc,UAAU,WAAW;GAE1D,IAAI,CAAC,UAAU;IACb,UAAU,SAAS;IACnB,OAAO,OAAO,CACZ;KAAE,eAAe,UAAU;KAAe,QAAQ;IAAW,CAC/D,CAAC;GACH;GAEA,IAAI;IACF,IAAI;IAEJ,IAAI,UACF,mBAAmB,yBACjB,UAAU;IAId,IAAI,CAAC,kBAKH,oBAAmB,MAFX,YAAY,WAAW,cAAc,UAAU,aAAa,GAE7B;IAGzC,IAAI,CAAC,kBACH,MAAM,IAAI,MACR,cAAc,UAAU,cAAc,qBACxC;IAMF,MAAM,2BADJ,2BAA2B,UAAU,gBACa,MACjD,MAAM,EAAE,aAAa,QACxB;IAEA,IAAI,0BAEF,mBAAmB;KACjB,GAAG;KACH,UAAU;KACV,UAAU,yBAAyB;KACnC,SAAS,yBAAyB;IACpC;IAIF,MAAM,EAAE,WAAW,MAAM,wBACvB,kBACA,QACA,OACF;IAEA,UAAU,SAAS;IACnB,OAAO,OAAO,CAAC;KAAE,eAAe,UAAU;KAAe;IAAO,CAAC,CAAC;IAElE,gCAAgC,KAAK,gBAAgB;GACvD,SAAS,OAAO;IACd,UAAU,SAAS;IACnB,UAAU,QAAQ;IAClB,UAAU,eAAe,6BAA6B,UAAU,cAAc,IAAI;IAClF,OAAO,OAAO,CACZ;KAAE,eAAe,UAAU;KAAe,QAAQ;IAAQ,CAC5D,CAAC;GACH;EACF;EAGA,MAAM,YAAY,sBAAsB,mBAAmB,CAAC;EAG5D,OAAO,OAAO;EAGd,MAAM,WAAW,WAAyC;GACxD,QAAQ,QAAR;IACE,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK,oBACH,OAAO;IACT,KAAK,SACH,OAAO;IACT,SACE,OAAO;GACX;EACF;EAEA,MAAM,YAAY,WAAyC;GACzD,QAAQ,QAAR;IACE,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK,cACH,OAAO,WAAW;IACpB,KAAK;IACL,KAAK,oBACH,OAAO,WAAW;IACpB,KAAK,SACH,OAAO,WAAW;IACpB,SACE,OAAO,WAAW;GACtB;EACF;EAEA,KAAK,MAAM,KAAK,sBAAsB;GACpC,MAAM,OAAO,QAAQ,EAAE,MAAM;GAC7B,MAAM,QAAQ,SAAS,EAAE,MAAM;GAC/B,UACE,MAAM,EAAE,cAAc,GAAG,WAAW,KAAK,GAAG,QAAQ,KAAK,GAAG,EAAE,SAAS,WAAW,KAAK,GAAG,WAAW,OACvG;EACF;EAGA,KAAK,MAAM,aAAa,sBACtB,IAAI,UAAU,cACZ,UAAU,UAAU,cAAc,EAChC,OAAO,QACT,CAAC;CAGP,SAAS,OAAO;EACd,UAAU,OAAO,EACf,OAAO,QACT,CAAC;CACH;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pullLog.mjs","names":[],"sources":["../../../src/push/pullLog.ts"],"sourcesContent":["import type { DictionaryStatus } from '@intlayer/chokidar/build';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, spinnerFrames } from '@intlayer/config/logger';\nimport { getConfiguration } from '@intlayer/config/node';\n\nexport type PullStatus = {\n dictionaryKey: string;\n status: DictionaryStatus | 'pending' | 'fetching';\n errorMessage?: string;\n};\n\nexport class PullLogger {\n private statuses: PullStatus[] = [];\n private spinnerTimer: NodeJS.Timeout | null = null;\n private spinnerIndex = 0;\n private renderedLines = 0;\n private readonly spinnerFrames = spinnerFrames;\n private isFinished = false;\n private readonly prefix: string;\n private lastRenderedState: string = '';\n\n constructor() {\n const configuration = getConfiguration();\n this.prefix = configuration.log.prefix;\n }\n\n update(newStatuses: PullStatus[]) {\n if (this.isFinished) return;\n for (const status of newStatuses) {\n const index = this.statuses.findIndex(\n (s) => s.dictionaryKey === status.dictionaryKey\n );\n if (index >= 0) {\n this.statuses[index] = status;\n } else {\n this.statuses.push(status);\n }\n }\n\n this.startSpinner();\n this.render();\n }\n\n finish() {\n this.isFinished = true;\n this.stopSpinner();\n this.render();\n }\n\n private startSpinner() {\n if (this.spinnerTimer || this.isFinished) return;\n this.spinnerTimer = setInterval(() => {\n this.spinnerIndex = (this.spinnerIndex + 1) % this.spinnerFrames.length;\n this.render();\n }, 100);\n }\n\n private stopSpinner() {\n if (!this.spinnerTimer) return;\n clearInterval(this.spinnerTimer);\n this.spinnerTimer = null;\n }\n\n private render() {\n const { total, done, success, errors } = this.computeProgress();\n\n const frame = this.spinnerFrames[this.spinnerIndex];\n const lines: string[] = [];\n\n const isDone = done === total;\n const progressLabel = `dictionaries: ${done}/${total}`;\n const details: string[] = [];\n if (success > 0) details.push(`ok: ${success}`);\n if (errors > 0) details.push(colorize(`errors: ${errors}`, ANSIColors.RED));\n\n const suffix = details.length > 0 ? ` (${details.join(', ')})` : '';\n\n if (isDone) {\n lines.push(\n `${this.prefix} ${colorize('✔', ANSIColors.GREEN)} fetched ${progressLabel}${suffix}`\n );\n } else {\n lines.push(\n `${this.prefix} ${colorize(frame, ANSIColors.BLUE)} fetching ${progressLabel}${suffix}`\n );\n }\n\n const currentState = lines.join('\\n');\n if (currentState === this.lastRenderedState) {\n return;\n }\n this.lastRenderedState = currentState;\n\n if (this.renderedLines > 0) {\n process.stdout.write(`\\x1b[${this.renderedLines}F`);\n }\n\n const totalLinesToClear = Math.max(this.renderedLines, lines.length);\n for (let i = 0; i < totalLinesToClear; i++) {\n process.stdout.write('\\x1b[2K');\n const line = lines[i];\n if (line !== undefined) {\n process.stdout.write(line);\n }\n process.stdout.write('\\n');\n }\n\n this.renderedLines = lines.length;\n }\n\n private computeProgress() {\n const keys = new Set(this.statuses.map((s) => s.dictionaryKey));\n\n const doneSet = new Set<DictionaryStatus | 'error'>([\n 'fetched',\n 'imported',\n 'updated',\n 'up-to-date',\n 'reimported in JSON',\n 'new content file',\n 'error',\n ] as const);\n\n const successesSet = new Set<DictionaryStatus>([\n 'fetched',\n 'imported',\n 'updated',\n 'up-to-date',\n 'reimported in JSON',\n 'new content file',\n ] as const);\n\n const done = this.statuses.filter((s) =>\n doneSet.has(s.status as any)\n ).length;\n const success = this.statuses.filter((s) =>\n successesSet.has(s.status as any)\n ).length;\n const errors = this.statuses.filter((s) => s.status === 'error').length;\n\n return {\n total: keys.size,\n done,\n success,\n errors,\n } as const;\n }\n}\n"],"mappings":";;;;;AAWA,IAAa,aAAb,MAAwB;CACtB,AAAQ,WAAyB,
|
|
1
|
+
{"version":3,"file":"pullLog.mjs","names":[],"sources":["../../../src/push/pullLog.ts"],"sourcesContent":["import type { DictionaryStatus } from '@intlayer/chokidar/build';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, spinnerFrames } from '@intlayer/config/logger';\nimport { getConfiguration } from '@intlayer/config/node';\n\nexport type PullStatus = {\n dictionaryKey: string;\n status: DictionaryStatus | 'pending' | 'fetching';\n errorMessage?: string;\n};\n\nexport class PullLogger {\n private statuses: PullStatus[] = [];\n private spinnerTimer: NodeJS.Timeout | null = null;\n private spinnerIndex = 0;\n private renderedLines = 0;\n private readonly spinnerFrames = spinnerFrames;\n private isFinished = false;\n private readonly prefix: string;\n private lastRenderedState: string = '';\n\n constructor() {\n const configuration = getConfiguration();\n this.prefix = configuration.log.prefix;\n }\n\n update(newStatuses: PullStatus[]) {\n if (this.isFinished) return;\n for (const status of newStatuses) {\n const index = this.statuses.findIndex(\n (s) => s.dictionaryKey === status.dictionaryKey\n );\n if (index >= 0) {\n this.statuses[index] = status;\n } else {\n this.statuses.push(status);\n }\n }\n\n this.startSpinner();\n this.render();\n }\n\n finish() {\n this.isFinished = true;\n this.stopSpinner();\n this.render();\n }\n\n private startSpinner() {\n if (this.spinnerTimer || this.isFinished) return;\n this.spinnerTimer = setInterval(() => {\n this.spinnerIndex = (this.spinnerIndex + 1) % this.spinnerFrames.length;\n this.render();\n }, 100);\n }\n\n private stopSpinner() {\n if (!this.spinnerTimer) return;\n clearInterval(this.spinnerTimer);\n this.spinnerTimer = null;\n }\n\n private render() {\n const { total, done, success, errors } = this.computeProgress();\n\n const frame = this.spinnerFrames[this.spinnerIndex];\n const lines: string[] = [];\n\n const isDone = done === total;\n const progressLabel = `dictionaries: ${done}/${total}`;\n const details: string[] = [];\n if (success > 0) details.push(`ok: ${success}`);\n if (errors > 0) details.push(colorize(`errors: ${errors}`, ANSIColors.RED));\n\n const suffix = details.length > 0 ? ` (${details.join(', ')})` : '';\n\n if (isDone) {\n lines.push(\n `${this.prefix} ${colorize('✔', ANSIColors.GREEN)} fetched ${progressLabel}${suffix}`\n );\n } else {\n lines.push(\n `${this.prefix} ${colorize(frame, ANSIColors.BLUE)} fetching ${progressLabel}${suffix}`\n );\n }\n\n const currentState = lines.join('\\n');\n if (currentState === this.lastRenderedState) {\n return;\n }\n this.lastRenderedState = currentState;\n\n if (this.renderedLines > 0) {\n process.stdout.write(`\\x1b[${this.renderedLines}F`);\n }\n\n const totalLinesToClear = Math.max(this.renderedLines, lines.length);\n for (let i = 0; i < totalLinesToClear; i++) {\n process.stdout.write('\\x1b[2K');\n const line = lines[i];\n if (line !== undefined) {\n process.stdout.write(line);\n }\n process.stdout.write('\\n');\n }\n\n this.renderedLines = lines.length;\n }\n\n private computeProgress() {\n const keys = new Set(this.statuses.map((s) => s.dictionaryKey));\n\n const doneSet = new Set<DictionaryStatus | 'error'>([\n 'fetched',\n 'imported',\n 'updated',\n 'up-to-date',\n 'reimported in JSON',\n 'new content file',\n 'error',\n ] as const);\n\n const successesSet = new Set<DictionaryStatus>([\n 'fetched',\n 'imported',\n 'updated',\n 'up-to-date',\n 'reimported in JSON',\n 'new content file',\n ] as const);\n\n const done = this.statuses.filter((s) =>\n doneSet.has(s.status as any)\n ).length;\n const success = this.statuses.filter((s) =>\n successesSet.has(s.status as any)\n ).length;\n const errors = this.statuses.filter((s) => s.status === 'error').length;\n\n return {\n total: keys.size,\n done,\n success,\n errors,\n } as const;\n }\n}\n"],"mappings":"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"}
|
package/dist/esm/push/push.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { checkCMSAuth } from "../utils/checkAccess.mjs";
|
|
1
|
+
import { checkCMSAuth, getAuthenticatedAPI } from "../utils/checkAccess.mjs";
|
|
2
2
|
import { PushLogger } from "../pushLog.mjs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { prepareIntlayer, writeContentDeclaration } from "@intlayer/chokidar/build";
|
|
@@ -8,7 +8,6 @@ import * as ANSIColors from "@intlayer/config/colors";
|
|
|
8
8
|
import { colorize, colorizeKey, getAppLogger } from "@intlayer/config/logger";
|
|
9
9
|
import { getConfiguration } from "@intlayer/config/node";
|
|
10
10
|
import { getUnmergedDictionaries } from "@intlayer/unmerged-dictionaries-entry";
|
|
11
|
-
import { getIntlayerAPIProxy } from "@intlayer/api";
|
|
12
11
|
import * as fsPromises from "node:fs/promises";
|
|
13
12
|
|
|
14
13
|
//#region src/push/push.ts
|
|
@@ -44,7 +43,7 @@ const push = async (options) => {
|
|
|
44
43
|
else if (typeof options?.build === "undefined") await prepareIntlayer(config);
|
|
45
44
|
try {
|
|
46
45
|
if (!await checkCMSAuth(config)) return;
|
|
47
|
-
const intlayerAPI =
|
|
46
|
+
const intlayerAPI = await getAuthenticatedAPI(config);
|
|
48
47
|
const unmergedDictionariesRecord = getUnmergedDictionaries(config);
|
|
49
48
|
const allDictionaries = Object.values(unmergedDictionariesRecord).flat();
|
|
50
49
|
const customLocations = Array.from(new Set(allDictionaries.map((dictionary) => dictionary.location).filter((location) => location && ![
|