@intlayer/cli 9.0.0-canary.11 → 9.0.0-canary.13
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/dist/cjs/auth/login.cjs +2 -2
- package/dist/cjs/auth/login.cjs.map +1 -1
- package/dist/cjs/build.cjs +6 -6
- package/dist/cjs/build.cjs.map +1 -1
- package/dist/cjs/ci.cjs +2 -2
- package/dist/cjs/ci.cjs.map +1 -1
- package/dist/cjs/cli.cjs.map +1 -1
- package/dist/cjs/config.cjs +2 -2
- package/dist/cjs/config.cjs.map +1 -1
- package/dist/cjs/extract.cjs +7 -7
- package/dist/cjs/extract.cjs.map +1 -1
- package/dist/cjs/fill/fill.cjs +11 -11
- package/dist/cjs/fill/fill.cjs.map +1 -1
- package/dist/cjs/fill/formatAutoFilledFilePath.cjs +2 -2
- package/dist/cjs/fill/formatAutoFilledFilePath.cjs.map +1 -1
- package/dist/cjs/fill/formatFillData.cjs +3 -3
- package/dist/cjs/fill/formatFillData.cjs.map +1 -1
- package/dist/cjs/fill/listTranslationsTasks.cjs +2 -2
- package/dist/cjs/fill/listTranslationsTasks.cjs.map +1 -1
- package/dist/cjs/fill/translateDictionary.cjs +7 -7
- package/dist/cjs/fill/translateDictionary.cjs.map +1 -1
- package/dist/cjs/fill/writeFill.cjs +5 -5
- package/dist/cjs/fill/writeFill.cjs.map +1 -1
- package/dist/cjs/getTargetDictionary.cjs +2 -2
- package/dist/cjs/getTargetDictionary.cjs.map +1 -1
- package/dist/cjs/init.cjs +41 -5
- package/dist/cjs/init.cjs.map +1 -1
- package/dist/cjs/initBuildOptimization.cjs +6 -6
- package/dist/cjs/initBuildOptimization.cjs.map +1 -1
- package/dist/cjs/initCompiler.cjs +3 -3
- package/dist/cjs/initCompiler.cjs.map +1 -1
- package/dist/cjs/initMCP.cjs +3 -3
- package/dist/cjs/initMCP.cjs.map +1 -1
- package/dist/cjs/initSkills.cjs +11 -11
- package/dist/cjs/initSkills.cjs.map +1 -1
- package/dist/cjs/listContentDeclaration.cjs +4 -4
- package/dist/cjs/listContentDeclaration.cjs.map +1 -1
- package/dist/cjs/listProjects.cjs +2 -2
- package/dist/cjs/listProjects.cjs.map +1 -1
- package/dist/cjs/liveSync.cjs +4 -4
- package/dist/cjs/liveSync.cjs.map +1 -1
- package/dist/cjs/pull.cjs +6 -6
- package/dist/cjs/pull.cjs.map +1 -1
- package/dist/cjs/push/pullLog.cjs.map +1 -1
- package/dist/cjs/push/push.cjs +13 -13
- package/dist/cjs/push/push.cjs.map +1 -1
- package/dist/cjs/pushConfig.cjs +2 -2
- package/dist/cjs/pushConfig.cjs.map +1 -1
- package/dist/cjs/reviewDoc/reviewDoc.cjs +16 -16
- package/dist/cjs/reviewDoc/reviewDoc.cjs.map +1 -1
- package/dist/cjs/reviewDoc/reviewDocBlockAware.cjs +11 -11
- package/dist/cjs/reviewDoc/reviewDocBlockAware.cjs.map +1 -1
- package/dist/cjs/reviewDoc/reviewDocLog.cjs +7 -7
- package/dist/cjs/reviewDoc/reviewDocLog.cjs.map +1 -1
- package/dist/cjs/scan.cjs +5 -5
- package/dist/cjs/scan.cjs.map +1 -1
- package/dist/cjs/searchDoc.cjs +2 -2
- package/dist/cjs/searchDoc.cjs.map +1 -1
- package/dist/cjs/test/listMissingTranslations.cjs +2 -2
- package/dist/cjs/test/listMissingTranslations.cjs.map +1 -1
- package/dist/cjs/test/test.cjs +11 -11
- package/dist/cjs/test/test.cjs.map +1 -1
- package/dist/cjs/translateDoc/translateDoc.cjs +6 -6
- package/dist/cjs/translateDoc/translateDoc.cjs.map +1 -1
- package/dist/cjs/translateDoc/translateFile.cjs +5 -5
- package/dist/cjs/translateDoc/translateFile.cjs.map +1 -1
- package/dist/cjs/utils/calculateChunks.cjs +4 -4
- package/dist/cjs/utils/calculateChunks.cjs.map +1 -1
- package/dist/cjs/utils/getOutputFilePath.cjs +2 -2
- package/dist/cjs/utils/getOutputFilePath.cjs.map +1 -1
- package/dist/cjs/utils/mapChunksBetweenFiles.cjs +4 -4
- package/dist/cjs/utils/mapChunksBetweenFiles.cjs.map +1 -1
- package/dist/cjs/watch.cjs +6 -6
- package/dist/cjs/watch.cjs.map +1 -1
- package/dist/esm/auth/login.mjs +1 -1
- package/dist/esm/auth/login.mjs.map +1 -1
- package/dist/esm/build.mjs +3 -3
- package/dist/esm/build.mjs.map +1 -1
- package/dist/esm/ci.mjs +1 -1
- package/dist/esm/ci.mjs.map +1 -1
- package/dist/esm/cli.mjs.map +1 -1
- package/dist/esm/config.mjs +1 -1
- package/dist/esm/config.mjs.map +1 -1
- package/dist/esm/extract.mjs +3 -3
- package/dist/esm/extract.mjs.map +1 -1
- package/dist/esm/fill/fill.mjs +3 -3
- package/dist/esm/fill/fill.mjs.map +1 -1
- package/dist/esm/fill/formatAutoFilledFilePath.mjs +1 -1
- package/dist/esm/fill/formatAutoFilledFilePath.mjs.map +1 -1
- package/dist/esm/fill/formatFillData.mjs +1 -1
- package/dist/esm/fill/formatFillData.mjs.map +1 -1
- package/dist/esm/fill/listTranslationsTasks.mjs +1 -1
- package/dist/esm/fill/listTranslationsTasks.mjs.map +1 -1
- package/dist/esm/fill/translateDictionary.mjs +1 -1
- package/dist/esm/fill/translateDictionary.mjs.map +1 -1
- package/dist/esm/fill/writeFill.mjs +2 -2
- package/dist/esm/fill/writeFill.mjs.map +1 -1
- package/dist/esm/getTargetDictionary.mjs +1 -1
- package/dist/esm/getTargetDictionary.mjs.map +1 -1
- package/dist/esm/init.mjs +37 -1
- package/dist/esm/init.mjs.map +1 -1
- package/dist/esm/initBuildOptimization.mjs +1 -1
- package/dist/esm/initBuildOptimization.mjs.map +1 -1
- package/dist/esm/initCompiler.mjs +1 -1
- package/dist/esm/initCompiler.mjs.map +1 -1
- package/dist/esm/initMCP.mjs +1 -1
- package/dist/esm/initMCP.mjs.map +1 -1
- package/dist/esm/initSkills.mjs +1 -1
- package/dist/esm/initSkills.mjs.map +1 -1
- package/dist/esm/listContentDeclaration.mjs +2 -2
- package/dist/esm/listContentDeclaration.mjs.map +1 -1
- package/dist/esm/listProjects.mjs +1 -1
- package/dist/esm/listProjects.mjs.map +1 -1
- package/dist/esm/liveSync.mjs +2 -2
- package/dist/esm/liveSync.mjs.map +1 -1
- package/dist/esm/pull.mjs +3 -3
- package/dist/esm/pull.mjs.map +1 -1
- package/dist/esm/push/pullLog.mjs.map +1 -1
- package/dist/esm/push/push.mjs +3 -3
- package/dist/esm/push/push.mjs.map +1 -1
- package/dist/esm/pushConfig.mjs +1 -1
- package/dist/esm/pushConfig.mjs.map +1 -1
- package/dist/esm/reviewDoc/reviewDoc.mjs +3 -3
- package/dist/esm/reviewDoc/reviewDoc.mjs.map +1 -1
- package/dist/esm/reviewDoc/reviewDocBlockAware.mjs +2 -2
- package/dist/esm/reviewDoc/reviewDocBlockAware.mjs.map +1 -1
- package/dist/esm/reviewDoc/reviewDocLog.mjs +2 -2
- package/dist/esm/reviewDoc/reviewDocLog.mjs.map +1 -1
- package/dist/esm/scan.mjs +1 -1
- package/dist/esm/scan.mjs.map +1 -1
- package/dist/esm/searchDoc.mjs +1 -1
- package/dist/esm/searchDoc.mjs.map +1 -1
- package/dist/esm/test/listMissingTranslations.mjs +1 -1
- package/dist/esm/test/listMissingTranslations.mjs.map +1 -1
- package/dist/esm/test/test.mjs +2 -2
- package/dist/esm/test/test.mjs.map +1 -1
- package/dist/esm/translateDoc/translateDoc.mjs +2 -2
- package/dist/esm/translateDoc/translateDoc.mjs.map +1 -1
- package/dist/esm/translateDoc/translateFile.mjs +1 -1
- package/dist/esm/translateDoc/translateFile.mjs.map +1 -1
- package/dist/esm/utils/calculateChunks.mjs +1 -1
- package/dist/esm/utils/calculateChunks.mjs.map +1 -1
- package/dist/esm/utils/getOutputFilePath.mjs +1 -1
- package/dist/esm/utils/getOutputFilePath.mjs.map +1 -1
- package/dist/esm/utils/mapChunksBetweenFiles.mjs +1 -1
- package/dist/esm/utils/mapChunksBetweenFiles.mjs.map +1 -1
- package/dist/esm/watch.mjs +3 -3
- package/dist/esm/watch.mjs.map +1 -1
- package/dist/types/fill/fill.d.ts +1 -1
- package/dist/types/fill/translateDictionary.d.ts +2 -2
- package/dist/types/fill/translateDictionary.d.ts.map +1 -1
- package/dist/types/getTargetDictionary.d.ts +1 -1
- package/dist/types/index.d.ts +4 -4
- package/dist/types/init.d.ts +1 -1
- package/dist/types/init.d.ts.map +1 -1
- package/dist/types/initMCP.d.ts +1 -1
- package/dist/types/initSkills.d.ts +1 -1
- package/dist/types/listProjects.d.ts +1 -1
- package/dist/types/push/pullLog.d.ts +1 -1
- package/dist/types/push/push.d.ts +1 -1
- package/dist/types/reviewDoc/reviewDoc.d.ts +1 -1
- package/dist/types/reviewDoc/reviewDoc.d.ts.map +1 -1
- package/dist/types/reviewDoc/reviewDocLog.d.ts +1 -1
- package/dist/types/scan.d.ts.map +1 -1
- package/dist/types/translateDoc/translateDoc.d.ts.map +1 -1
- package/dist/types/translateDoc/types.d.ts +1 -1
- package/package.json +13 -13
package/dist/esm/liveSync.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { IntlayerEventListener } from "./IntlayerEventListener.mjs";
|
|
2
|
-
import { buildDictionary } from "@intlayer/chokidar/build";
|
|
3
|
-
import { runParallel } from "@intlayer/chokidar/utils";
|
|
4
2
|
import * as ANSIColors from "@intlayer/config/colors";
|
|
5
3
|
import { colorize, getAppLogger } from "@intlayer/config/logger";
|
|
6
4
|
import { getConfiguration } from "@intlayer/config/node";
|
|
5
|
+
import { buildDictionary } from "@intlayer/engine/build";
|
|
6
|
+
import { runParallel } from "@intlayer/engine/utils";
|
|
7
7
|
import { getUnmergedDictionaries } from "@intlayer/unmerged-dictionaries-entry";
|
|
8
8
|
import { createServer } from "node:http";
|
|
9
9
|
import packageJson from "@intlayer/config/package.json" with { type: "json" };
|
|
@@ -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 * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, 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 ${colorize('disabled', ANSIColors.RED)}. 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 if (!key) {\n res.writeHead(404);\n res.end('Not Found');\n return;\n }\n\n const dictionary = dictionaries[key] ?? null;\n\n if (!dictionary) {\n res.writeHead(404);\n res.end('Not Found');\n return;\n }\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 * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, 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 { buildDictionary } from '@intlayer/engine/build';\nimport { type ParallelHandle, runParallel } from '@intlayer/engine/utils';\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 ${colorize('disabled', ANSIColors.RED)}. 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 if (!key) {\n res.writeHead(404);\n res.end('Not Found');\n return;\n }\n\n const dictionary = dictionaries[key] ?? null;\n\n if (!dictionary) {\n res.writeHead(404);\n res.end('Not Found');\n return;\n }\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
|
@@ -3,12 +3,12 @@ import { PullLogger } from "./push/pullLog.mjs";
|
|
|
3
3
|
import { selectCmsEnvironment } from "./utils/selectCmsEnvironment.mjs";
|
|
4
4
|
import { existsSync } from "node:fs";
|
|
5
5
|
import { join } from "node:path";
|
|
6
|
-
import { writeContentDeclaration } from "@intlayer/chokidar/build";
|
|
7
|
-
import { logConfigDetails } from "@intlayer/chokidar/cli";
|
|
8
|
-
import { parallelize } from "@intlayer/chokidar/utils";
|
|
9
6
|
import * as ANSIColors from "@intlayer/config/colors";
|
|
10
7
|
import { getAppLogger } from "@intlayer/config/logger";
|
|
11
8
|
import { getConfiguration } from "@intlayer/config/node";
|
|
9
|
+
import { writeContentDeclaration } from "@intlayer/engine/build";
|
|
10
|
+
import { logConfigDetails } from "@intlayer/engine/cli";
|
|
11
|
+
import { parallelize } from "@intlayer/engine/utils";
|
|
12
12
|
import { getUnmergedDictionaries } from "@intlayer/unmerged-dictionaries-entry";
|
|
13
13
|
import { getProjectRequire } from "@intlayer/config/utils";
|
|
14
14
|
|
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 {\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';\nimport { selectCmsEnvironment } from './utils/selectCmsEnvironment';\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 await selectCmsEnvironment(\n options?.configOptions?.env,\n intlayerAPI,\n config\n );\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 * 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 {\n type DictionaryStatus,\n writeContentDeclaration,\n} from '@intlayer/engine/build';\nimport { logConfigDetails } from '@intlayer/engine/cli';\nimport { parallelize } from '@intlayer/engine/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';\nimport { selectCmsEnvironment } from './utils/selectCmsEnvironment';\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 await selectCmsEnvironment(\n options?.configOptions?.env,\n intlayerAPI,\n config\n );\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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pullLog.mjs","names":[],"sources":["../../../src/push/pullLog.ts"],"sourcesContent":["import
|
|
1
|
+
{"version":3,"file":"pullLog.mjs","names":[],"sources":["../../../src/push/pullLog.ts"],"sourcesContent":["import * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, spinnerFrames } from '@intlayer/config/logger';\nimport { getConfiguration } from '@intlayer/config/node';\nimport type { DictionaryStatus } from '@intlayer/engine/build';\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
|
@@ -2,12 +2,12 @@ import { checkCMSAuth, getAuthenticatedAPI } from "../utils/checkAccess.mjs";
|
|
|
2
2
|
import { selectCmsEnvironment } from "../utils/selectCmsEnvironment.mjs";
|
|
3
3
|
import { PushLogger } from "../pushLog.mjs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
-
import { prepareIntlayer, writeContentDeclaration } from "@intlayer/chokidar/build";
|
|
6
|
-
import { listGitFiles, logConfigDetails } from "@intlayer/chokidar/cli";
|
|
7
|
-
import { formatPath, parallelize } from "@intlayer/chokidar/utils";
|
|
8
5
|
import * as ANSIColors from "@intlayer/config/colors";
|
|
9
6
|
import { colorize, colorizeKey, getAppLogger } from "@intlayer/config/logger";
|
|
10
7
|
import { getConfiguration } from "@intlayer/config/node";
|
|
8
|
+
import { prepareIntlayer, writeContentDeclaration } from "@intlayer/engine/build";
|
|
9
|
+
import { listGitFiles, logConfigDetails } from "@intlayer/engine/cli";
|
|
10
|
+
import { formatPath, parallelize } from "@intlayer/engine/utils";
|
|
11
11
|
import { getUnmergedDictionaries } from "@intlayer/unmerged-dictionaries-entry";
|
|
12
12
|
import * as fsPromises from "node:fs/promises";
|
|
13
13
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"push.mjs","names":[],"sources":["../../../src/push/push.ts"],"sourcesContent":["import * as fsPromises from 'node:fs/promises';\nimport { join } from 'node:path';\nimport {\n prepareIntlayer,\n writeContentDeclaration,\n} from '@intlayer/chokidar/build';\nimport {\n type ListGitFilesOptions,\n listGitFiles,\n logConfigDetails,\n} from '@intlayer/chokidar/cli';\nimport { formatPath, parallelize } from '@intlayer/chokidar/utils';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, colorizeKey, getAppLogger } from '@intlayer/config/logger';\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';\nimport { PushLogger, type PushStatus } from '../pushLog';\nimport { checkCMSAuth, getAuthenticatedAPI } from '../utils/checkAccess';\nimport { selectCmsEnvironment } from '../utils/selectCmsEnvironment';\n\ntype PushOptions = {\n deleteLocaleDictionary?: boolean;\n keepLocaleDictionary?: boolean;\n dictionaries?: string[];\n gitOptions?: ListGitFilesOptions;\n configOptions?: GetConfigurationOptions;\n build?: boolean;\n};\n\ntype DictionariesStatus = {\n dictionary: Dictionary;\n status:\n | 'pending'\n | 'pushing'\n | 'modified'\n | 'pushed'\n | 'up-to-date'\n | 'unknown'\n | 'error';\n error?: Error;\n errorMessage?: string;\n};\n\n// Print per-dictionary summary similar to loadDictionaries\nconst statusIconsAndColors = {\n pushed: { icon: '✔', color: ANSIColors.GREEN },\n modified: { icon: '✔', color: ANSIColors.GREEN },\n 'up-to-date': { icon: '=', color: ANSIColors.GREY },\n error: { icon: '✖', color: ANSIColors.RED },\n default: { icon: '⏲', color: ANSIColors.BLUE },\n};\n\nconst getIconAndColor = (status: DictionariesStatus['status']) => {\n return (\n statusIconsAndColors[status as keyof typeof statusIconsAndColors] ??\n statusIconsAndColors.default\n );\n};\n\n/**\n * Get all local dictionaries and push them simultaneously.\n */\nexport const push = async (options?: PushOptions): Promise<void> => {\n const config = getConfiguration(options?.configOptions);\n logConfigDetails(options?.configOptions);\n\n const appLogger = getAppLogger(config);\n\n if (options?.build === true) {\n await prepareIntlayer(config, { forceRun: true });\n } else if (typeof options?.build === 'undefined') {\n await prepareIntlayer(config);\n }\n\n try {\n const hasCMSAuth = await checkCMSAuth(config);\n\n if (!hasCMSAuth) return;\n\n const intlayerAPI = await getAuthenticatedAPI(config);\n\n await selectCmsEnvironment(\n options?.configOptions?.env,\n intlayerAPI,\n config\n );\n\n const unmergedDictionariesRecord = getUnmergedDictionaries(config);\n const allDictionaries = Object.values(unmergedDictionariesRecord).flat();\n\n const customLocations = Array.from(\n new Set(\n allDictionaries\n .map((dictionary) => dictionary.location)\n .filter(\n (location) =>\n location && !['remote', 'local', 'hybrid'].includes(location)\n )\n )\n ) as string[];\n\n // Include all custom locations automatically — no interactive prompt needed.\n const selectedCustomLocations: string[] = customLocations;\n\n let dictionaries: Dictionary[] = allDictionaries.filter((dictionary) => {\n const location =\n dictionary.location ?? config.dictionary?.location ?? 'local';\n\n return (\n location === 'remote' ||\n location === 'hybrid' ||\n selectedCustomLocations.includes(location)\n );\n });\n\n // Check if the dictionaries list is empty after filtering by location\n if (dictionaries.length === 0) {\n appLogger(\n `No dictionaries found to push. Only dictionaries with location ${colorize('remote', ANSIColors.BLUE, ANSIColors.RESET)}, ${colorize('hybrid', ANSIColors.BLUE, ANSIColors.RESET)} or selected custom locations are pushed.`,\n { level: 'warn' }\n );\n appLogger(\n `You can set the location in your dictionary file (e.g. ${colorize(\"{ key: 'my-key', location: 'hybrid', ... }\", ANSIColors.BLUE, ANSIColors.RESET)} or globally in your intlayer.config.ts file (e.g. ${colorize(\"{ dictionary: { location: 'hybrid' } }\", ANSIColors.BLUE, ANSIColors.RESET)}).`,\n { level: 'info' }\n );\n return;\n }\n\n const existingDictionariesKeys: string[] = Object.keys(\n unmergedDictionariesRecord\n );\n\n if (options?.dictionaries) {\n // Check if the provided dictionaries exist\n const noneExistingDictionariesOption = options.dictionaries.filter(\n (dictionaryId) => !existingDictionariesKeys.includes(dictionaryId)\n );\n\n if (noneExistingDictionariesOption.length > 0) {\n appLogger(\n `The following dictionaries do not exist: ${noneExistingDictionariesOption.join(\n ', '\n )} and have been ignored.`,\n {\n level: 'error',\n }\n );\n }\n\n // Filter the dictionaries from the provided list of IDs\n dictionaries = dictionaries.filter((dictionary) =>\n options.dictionaries?.includes(dictionary.key)\n );\n }\n\n if (options?.gitOptions) {\n const gitFiles = await listGitFiles(options.gitOptions);\n\n dictionaries = dictionaries.filter((dictionary) =>\n gitFiles.includes(\n join(config.system.baseDir, dictionary.filePath ?? '')\n )\n );\n }\n\n // Check if the dictionaries list is empty\n if (dictionaries.length === 0) {\n appLogger('No local dictionaries found', {\n level: 'error',\n });\n return;\n }\n\n appLogger('Pushing dictionaries:');\n\n // Prepare dictionaries statuses\n const dictionariesStatuses: DictionariesStatus[] = dictionaries.map(\n (dictionary) => ({\n dictionary,\n status: 'pending',\n })\n );\n\n // Initialize aggregated logger similar to loadDictionaries\n const logger = new PushLogger();\n logger.update(\n dictionariesStatuses.map<PushStatus>((s) => ({\n dictionaryKey: s.dictionary.key,\n status: 'pending',\n }))\n );\n\n const successfullyPushedDictionaries: Dictionary[] = [];\n\n const processDictionary = async (\n statusObj: DictionariesStatus\n ): Promise<void> => {\n statusObj.status = 'pushing';\n logger.update([\n { dictionaryKey: statusObj.dictionary.key, status: 'pushing' },\n ]);\n\n try {\n const pushResult = await intlayerAPI.dictionary.pushDictionaries([\n statusObj.dictionary,\n ]);\n\n const updatedDictionaries = pushResult.data?.updatedDictionaries ?? [];\n const newDictionaries = pushResult.data?.newDictionaries ?? [];\n const upToDateDictionaries =\n pushResult.data?.upToDateDictionaries ?? [];\n\n const allDictionaries = [\n ...updatedDictionaries,\n ...newDictionaries,\n ...upToDateDictionaries,\n ];\n\n for (const remoteDictionaryData of allDictionaries) {\n const localDictionary = unmergedDictionariesRecord[\n remoteDictionaryData.key\n ]?.find(\n (dictionary) => dictionary.localId === remoteDictionaryData.localId\n );\n\n if (!localDictionary) continue;\n\n await writeContentDeclaration(\n { ...localDictionary, id: remoteDictionaryData.id },\n config\n );\n }\n\n if (\n updatedDictionaries.some(\n (dictionary) => dictionary.key === statusObj.dictionary.key\n )\n ) {\n statusObj.status = 'modified';\n successfullyPushedDictionaries.push(statusObj.dictionary);\n logger.update([\n { dictionaryKey: statusObj.dictionary.key, status: 'modified' },\n ]);\n } else if (\n newDictionaries.some(\n (dictionary) => dictionary.key === statusObj.dictionary.key\n )\n ) {\n statusObj.status = 'pushed';\n successfullyPushedDictionaries.push(statusObj.dictionary);\n logger.update([\n { dictionaryKey: statusObj.dictionary.key, status: 'pushed' },\n ]);\n } else if (\n upToDateDictionaries.some(\n (dictionary) => dictionary.key === statusObj.dictionary.key\n )\n ) {\n statusObj.status = 'up-to-date';\n logger.update([\n { dictionaryKey: statusObj.dictionary.key, status: 'up-to-date' },\n ]);\n } else {\n statusObj.status = 'unknown';\n }\n } catch (error) {\n statusObj.status = 'error';\n statusObj.error = error as Error;\n statusObj.errorMessage = `Error pushing dictionary ${statusObj.dictionary.key}: ${error}`;\n logger.update([\n { dictionaryKey: statusObj.dictionary.key, status: 'error' },\n ]);\n }\n };\n\n // Process dictionaries in parallel with a concurrency limit (reuse parallelize)\n await parallelize(dictionariesStatuses, processDictionary, 5);\n\n // Stop the logger and render final state\n logger.finish();\n\n for (const dictionaryStatus of dictionariesStatuses) {\n const { icon, color } = getIconAndColor(dictionaryStatus.status);\n appLogger(\n ` - ${colorizeKey(dictionaryStatus.dictionary.key)} ${ANSIColors.GREY}[${color}${icon} ${dictionaryStatus.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\n // Handle delete or keep options\n const deleteOption = options?.deleteLocaleDictionary;\n const keepOption = options?.keepLocaleDictionary;\n\n if (deleteOption && keepOption) {\n throw new Error(\n 'Cannot specify both --deleteLocaleDictionary and --keepLocaleDictionary options.'\n );\n }\n\n if (deleteOption) {\n // Delete only the successfully pushed dictionaries\n await deleteLocalDictionaries(successfullyPushedDictionaries, options);\n } else if (keepOption) {\n // Do nothing, keep the local dictionaries\n } else {\n // Ask the user\n const remoteDictionaries = successfullyPushedDictionaries.filter(\n (dictionary) => dictionary.location === 'remote'\n );\n const remoteDictionariesKeys = remoteDictionaries.map(\n (dictionary) => dictionary.key\n );\n\n if (remoteDictionaries.length > 0) {\n const { confirm, isCancel } = await import('@clack/prompts');\n\n const shouldDelete = await confirm({\n message: `Do you want to delete the local dictionaries that were successfully pushed? ${colorize('(Dictionaries:', ANSIColors.GREY, ANSIColors.RESET)} ${colorizeKey(remoteDictionariesKeys)}${colorize(')', ANSIColors.GREY, ANSIColors.RESET)}`,\n initialValue: false,\n });\n\n if (isCancel(shouldDelete)) {\n return;\n }\n\n if (shouldDelete) {\n await deleteLocalDictionaries(remoteDictionaries, options);\n }\n }\n }\n } catch (error) {\n appLogger(error, {\n level: 'error',\n });\n }\n};\n\nconst deleteLocalDictionaries = async (\n dictionariesToDelete: Dictionary[],\n options?: PushOptions\n): Promise<void> => {\n const config = getConfiguration(options?.configOptions);\n const appLogger = getAppLogger(config);\n\n // Use a Set to collect all unique file paths\n const filePathsSet: Set<string> = new Set();\n\n for (const dictionary of dictionariesToDelete) {\n const { filePath } = dictionary;\n\n if (!filePath) {\n appLogger(\n `Dictionary ${colorizeKey(dictionary.key)} does not have a file path`,\n {\n level: 'error',\n }\n );\n continue;\n }\n\n filePathsSet.add(filePath);\n }\n\n for (const filePath of filePathsSet) {\n try {\n const stats = await fsPromises.lstat(filePath);\n\n if (stats.isFile()) {\n await fsPromises.unlink(filePath);\n appLogger(`Deleted file ${formatPath(filePath)}`, {});\n } else if (stats.isDirectory()) {\n appLogger(`Path is a directory ${formatPath(filePath)}, skipping.`, {});\n } else {\n appLogger(\n `Unknown file type for ${formatPath(filePath)}, skipping.`,\n {}\n );\n }\n } catch (err) {\n appLogger(`Error deleting ${formatPath(filePath)}: ${err}`, {\n level: 'error',\n });\n }\n }\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"push.mjs","names":[],"sources":["../../../src/push/push.ts"],"sourcesContent":["import * as fsPromises from 'node:fs/promises';\nimport { join } from 'node:path';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, colorizeKey, getAppLogger } from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport {\n prepareIntlayer,\n writeContentDeclaration,\n} from '@intlayer/engine/build';\nimport {\n type ListGitFilesOptions,\n listGitFiles,\n logConfigDetails,\n} from '@intlayer/engine/cli';\nimport { formatPath, parallelize } from '@intlayer/engine/utils';\nimport type { Dictionary } from '@intlayer/types/dictionary';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\nimport { PushLogger, type PushStatus } from '../pushLog';\nimport { checkCMSAuth, getAuthenticatedAPI } from '../utils/checkAccess';\nimport { selectCmsEnvironment } from '../utils/selectCmsEnvironment';\n\ntype PushOptions = {\n deleteLocaleDictionary?: boolean;\n keepLocaleDictionary?: boolean;\n dictionaries?: string[];\n gitOptions?: ListGitFilesOptions;\n configOptions?: GetConfigurationOptions;\n build?: boolean;\n};\n\ntype DictionariesStatus = {\n dictionary: Dictionary;\n status:\n | 'pending'\n | 'pushing'\n | 'modified'\n | 'pushed'\n | 'up-to-date'\n | 'unknown'\n | 'error';\n error?: Error;\n errorMessage?: string;\n};\n\n// Print per-dictionary summary similar to loadDictionaries\nconst statusIconsAndColors = {\n pushed: { icon: '✔', color: ANSIColors.GREEN },\n modified: { icon: '✔', color: ANSIColors.GREEN },\n 'up-to-date': { icon: '=', color: ANSIColors.GREY },\n error: { icon: '✖', color: ANSIColors.RED },\n default: { icon: '⏲', color: ANSIColors.BLUE },\n};\n\nconst getIconAndColor = (status: DictionariesStatus['status']) => {\n return (\n statusIconsAndColors[status as keyof typeof statusIconsAndColors] ??\n statusIconsAndColors.default\n );\n};\n\n/**\n * Get all local dictionaries and push them simultaneously.\n */\nexport const push = async (options?: PushOptions): Promise<void> => {\n const config = getConfiguration(options?.configOptions);\n logConfigDetails(options?.configOptions);\n\n const appLogger = getAppLogger(config);\n\n if (options?.build === true) {\n await prepareIntlayer(config, { forceRun: true });\n } else if (typeof options?.build === 'undefined') {\n await prepareIntlayer(config);\n }\n\n try {\n const hasCMSAuth = await checkCMSAuth(config);\n\n if (!hasCMSAuth) return;\n\n const intlayerAPI = await getAuthenticatedAPI(config);\n\n await selectCmsEnvironment(\n options?.configOptions?.env,\n intlayerAPI,\n config\n );\n\n const unmergedDictionariesRecord = getUnmergedDictionaries(config);\n const allDictionaries = Object.values(unmergedDictionariesRecord).flat();\n\n const customLocations = Array.from(\n new Set(\n allDictionaries\n .map((dictionary) => dictionary.location)\n .filter(\n (location) =>\n location && !['remote', 'local', 'hybrid'].includes(location)\n )\n )\n ) as string[];\n\n // Include all custom locations automatically — no interactive prompt needed.\n const selectedCustomLocations: string[] = customLocations;\n\n let dictionaries: Dictionary[] = allDictionaries.filter((dictionary) => {\n const location =\n dictionary.location ?? config.dictionary?.location ?? 'local';\n\n return (\n location === 'remote' ||\n location === 'hybrid' ||\n selectedCustomLocations.includes(location)\n );\n });\n\n // Check if the dictionaries list is empty after filtering by location\n if (dictionaries.length === 0) {\n appLogger(\n `No dictionaries found to push. Only dictionaries with location ${colorize('remote', ANSIColors.BLUE, ANSIColors.RESET)}, ${colorize('hybrid', ANSIColors.BLUE, ANSIColors.RESET)} or selected custom locations are pushed.`,\n { level: 'warn' }\n );\n appLogger(\n `You can set the location in your dictionary file (e.g. ${colorize(\"{ key: 'my-key', location: 'hybrid', ... }\", ANSIColors.BLUE, ANSIColors.RESET)} or globally in your intlayer.config.ts file (e.g. ${colorize(\"{ dictionary: { location: 'hybrid' } }\", ANSIColors.BLUE, ANSIColors.RESET)}).`,\n { level: 'info' }\n );\n return;\n }\n\n const existingDictionariesKeys: string[] = Object.keys(\n unmergedDictionariesRecord\n );\n\n if (options?.dictionaries) {\n // Check if the provided dictionaries exist\n const noneExistingDictionariesOption = options.dictionaries.filter(\n (dictionaryId) => !existingDictionariesKeys.includes(dictionaryId)\n );\n\n if (noneExistingDictionariesOption.length > 0) {\n appLogger(\n `The following dictionaries do not exist: ${noneExistingDictionariesOption.join(\n ', '\n )} and have been ignored.`,\n {\n level: 'error',\n }\n );\n }\n\n // Filter the dictionaries from the provided list of IDs\n dictionaries = dictionaries.filter((dictionary) =>\n options.dictionaries?.includes(dictionary.key)\n );\n }\n\n if (options?.gitOptions) {\n const gitFiles = await listGitFiles(options.gitOptions);\n\n dictionaries = dictionaries.filter((dictionary) =>\n gitFiles.includes(\n join(config.system.baseDir, dictionary.filePath ?? '')\n )\n );\n }\n\n // Check if the dictionaries list is empty\n if (dictionaries.length === 0) {\n appLogger('No local dictionaries found', {\n level: 'error',\n });\n return;\n }\n\n appLogger('Pushing dictionaries:');\n\n // Prepare dictionaries statuses\n const dictionariesStatuses: DictionariesStatus[] = dictionaries.map(\n (dictionary) => ({\n dictionary,\n status: 'pending',\n })\n );\n\n // Initialize aggregated logger similar to loadDictionaries\n const logger = new PushLogger();\n logger.update(\n dictionariesStatuses.map<PushStatus>((s) => ({\n dictionaryKey: s.dictionary.key,\n status: 'pending',\n }))\n );\n\n const successfullyPushedDictionaries: Dictionary[] = [];\n\n const processDictionary = async (\n statusObj: DictionariesStatus\n ): Promise<void> => {\n statusObj.status = 'pushing';\n logger.update([\n { dictionaryKey: statusObj.dictionary.key, status: 'pushing' },\n ]);\n\n try {\n const pushResult = await intlayerAPI.dictionary.pushDictionaries([\n statusObj.dictionary,\n ]);\n\n const updatedDictionaries = pushResult.data?.updatedDictionaries ?? [];\n const newDictionaries = pushResult.data?.newDictionaries ?? [];\n const upToDateDictionaries =\n pushResult.data?.upToDateDictionaries ?? [];\n\n const allDictionaries = [\n ...updatedDictionaries,\n ...newDictionaries,\n ...upToDateDictionaries,\n ];\n\n for (const remoteDictionaryData of allDictionaries) {\n const localDictionary = unmergedDictionariesRecord[\n remoteDictionaryData.key\n ]?.find(\n (dictionary) => dictionary.localId === remoteDictionaryData.localId\n );\n\n if (!localDictionary) continue;\n\n await writeContentDeclaration(\n { ...localDictionary, id: remoteDictionaryData.id },\n config\n );\n }\n\n if (\n updatedDictionaries.some(\n (dictionary) => dictionary.key === statusObj.dictionary.key\n )\n ) {\n statusObj.status = 'modified';\n successfullyPushedDictionaries.push(statusObj.dictionary);\n logger.update([\n { dictionaryKey: statusObj.dictionary.key, status: 'modified' },\n ]);\n } else if (\n newDictionaries.some(\n (dictionary) => dictionary.key === statusObj.dictionary.key\n )\n ) {\n statusObj.status = 'pushed';\n successfullyPushedDictionaries.push(statusObj.dictionary);\n logger.update([\n { dictionaryKey: statusObj.dictionary.key, status: 'pushed' },\n ]);\n } else if (\n upToDateDictionaries.some(\n (dictionary) => dictionary.key === statusObj.dictionary.key\n )\n ) {\n statusObj.status = 'up-to-date';\n logger.update([\n { dictionaryKey: statusObj.dictionary.key, status: 'up-to-date' },\n ]);\n } else {\n statusObj.status = 'unknown';\n }\n } catch (error) {\n statusObj.status = 'error';\n statusObj.error = error as Error;\n statusObj.errorMessage = `Error pushing dictionary ${statusObj.dictionary.key}: ${error}`;\n logger.update([\n { dictionaryKey: statusObj.dictionary.key, status: 'error' },\n ]);\n }\n };\n\n // Process dictionaries in parallel with a concurrency limit (reuse parallelize)\n await parallelize(dictionariesStatuses, processDictionary, 5);\n\n // Stop the logger and render final state\n logger.finish();\n\n for (const dictionaryStatus of dictionariesStatuses) {\n const { icon, color } = getIconAndColor(dictionaryStatus.status);\n appLogger(\n ` - ${colorizeKey(dictionaryStatus.dictionary.key)} ${ANSIColors.GREY}[${color}${icon} ${dictionaryStatus.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\n // Handle delete or keep options\n const deleteOption = options?.deleteLocaleDictionary;\n const keepOption = options?.keepLocaleDictionary;\n\n if (deleteOption && keepOption) {\n throw new Error(\n 'Cannot specify both --deleteLocaleDictionary and --keepLocaleDictionary options.'\n );\n }\n\n if (deleteOption) {\n // Delete only the successfully pushed dictionaries\n await deleteLocalDictionaries(successfullyPushedDictionaries, options);\n } else if (keepOption) {\n // Do nothing, keep the local dictionaries\n } else {\n // Ask the user\n const remoteDictionaries = successfullyPushedDictionaries.filter(\n (dictionary) => dictionary.location === 'remote'\n );\n const remoteDictionariesKeys = remoteDictionaries.map(\n (dictionary) => dictionary.key\n );\n\n if (remoteDictionaries.length > 0) {\n const { confirm, isCancel } = await import('@clack/prompts');\n\n const shouldDelete = await confirm({\n message: `Do you want to delete the local dictionaries that were successfully pushed? ${colorize('(Dictionaries:', ANSIColors.GREY, ANSIColors.RESET)} ${colorizeKey(remoteDictionariesKeys)}${colorize(')', ANSIColors.GREY, ANSIColors.RESET)}`,\n initialValue: false,\n });\n\n if (isCancel(shouldDelete)) {\n return;\n }\n\n if (shouldDelete) {\n await deleteLocalDictionaries(remoteDictionaries, options);\n }\n }\n }\n } catch (error) {\n appLogger(error, {\n level: 'error',\n });\n }\n};\n\nconst deleteLocalDictionaries = async (\n dictionariesToDelete: Dictionary[],\n options?: PushOptions\n): Promise<void> => {\n const config = getConfiguration(options?.configOptions);\n const appLogger = getAppLogger(config);\n\n // Use a Set to collect all unique file paths\n const filePathsSet: Set<string> = new Set();\n\n for (const dictionary of dictionariesToDelete) {\n const { filePath } = dictionary;\n\n if (!filePath) {\n appLogger(\n `Dictionary ${colorizeKey(dictionary.key)} does not have a file path`,\n {\n level: 'error',\n }\n );\n continue;\n }\n\n filePathsSet.add(filePath);\n }\n\n for (const filePath of filePathsSet) {\n try {\n const stats = await fsPromises.lstat(filePath);\n\n if (stats.isFile()) {\n await fsPromises.unlink(filePath);\n appLogger(`Deleted file ${formatPath(filePath)}`, {});\n } else if (stats.isDirectory()) {\n appLogger(`Path is a directory ${formatPath(filePath)}, skipping.`, {});\n } else {\n appLogger(\n `Unknown file type for ${formatPath(filePath)}, skipping.`,\n {}\n );\n }\n } catch (err) {\n appLogger(`Error deleting ${formatPath(filePath)}: ${err}`, {\n level: 'error',\n });\n }\n }\n};\n"],"mappings":"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"}
|
package/dist/esm/pushConfig.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { checkCMSAuth, getAuthenticatedAPI } from "./utils/checkAccess.mjs";
|
|
2
2
|
import { selectCmsEnvironment } from "./utils/selectCmsEnvironment.mjs";
|
|
3
|
-
import { logConfigDetails } from "@intlayer/chokidar/cli";
|
|
4
3
|
import { CYAN, GREY_DARK } from "@intlayer/config/colors";
|
|
5
4
|
import { colorize, colorizeObject, getAppLogger } from "@intlayer/config/logger";
|
|
6
5
|
import { getConfiguration } from "@intlayer/config/node";
|
|
6
|
+
import { logConfigDetails } from "@intlayer/engine/cli";
|
|
7
7
|
|
|
8
8
|
//#region src/pushConfig.ts
|
|
9
9
|
const pushConfig = async (options) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pushConfig.mjs","names":[],"sources":["../../src/pushConfig.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"pushConfig.mjs","names":[],"sources":["../../src/pushConfig.ts"],"sourcesContent":["import { CYAN, GREY_DARK } from '@intlayer/config/colors';\nimport {\n colorize,\n colorizeObject,\n getAppLogger,\n} from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport { logConfigDetails } from '@intlayer/engine/cli';\nimport { checkCMSAuth, getAuthenticatedAPI } from './utils/checkAccess';\nimport { selectCmsEnvironment } from './utils/selectCmsEnvironment';\n\ntype PushOptions = {\n configOptions?: GetConfigurationOptions;\n};\n\nexport const pushConfig = async (options?: PushOptions) => {\n const config = getConfiguration(options?.configOptions);\n logConfigDetails(options?.configOptions);\n\n const appLogger = getAppLogger(config);\n\n const hasCMSAuth = await checkCMSAuth(config, false);\n\n if (!hasCMSAuth) return;\n\n const intlayerAPI = await getAuthenticatedAPI(config);\n\n await selectCmsEnvironment(options?.configOptions?.env, intlayerAPI, config);\n\n // Push the project configuration\n const getDictionariesKeysResult =\n await intlayerAPI.project.pushProjectConfiguration(config);\n\n if (!getDictionariesKeysResult.data) {\n appLogger(\n `Error pushing project configuration. Run ${colorize('npx intlayer login', CYAN)} command to authenticate.`,\n {\n level: 'error',\n }\n );\n throw new Error('Error pushing project configuration');\n }\n\n appLogger('Project configuration pushed successfully');\n\n appLogger(colorize('--------------------------------', GREY_DARK));\n appLogger(colorizeObject(getDictionariesKeysResult.data.configuration));\n appLogger(colorize('--------------------------------', GREY_DARK));\n};\n"],"mappings":";;;;;;;;AAkBA,MAAa,aAAa,OAAO,YAA0B;CACzD,MAAM,SAAS,iBAAiB,SAAS,cAAc;AACvD,kBAAiB,SAAS,cAAc;CAExC,MAAM,YAAY,aAAa,OAAO;AAItC,KAAI,CAAC,MAFoB,aAAa,QAAQ,MAAM,CAEnC;CAEjB,MAAM,cAAc,MAAM,oBAAoB,OAAO;AAErD,OAAM,qBAAqB,SAAS,eAAe,KAAK,aAAa,OAAO;CAG5E,MAAM,4BACJ,MAAM,YAAY,QAAQ,yBAAyB,OAAO;AAE5D,KAAI,CAAC,0BAA0B,MAAM;AACnC,YACE,4CAA4C,SAAS,sBAAsB,KAAK,CAAC,4BACjF,EACE,OAAO,SACR,CACF;AACD,QAAM,IAAI,MAAM,sCAAsC;;AAGxD,WAAU,4CAA4C;AAEtD,WAAU,SAAS,oCAAoC,UAAU,CAAC;AAClE,WAAU,eAAe,0BAA0B,KAAK,cAAc,CAAC;AACvE,WAAU,SAAS,oCAAoC,UAAU,CAAC"}
|
|
@@ -6,13 +6,13 @@ import { reviewFileBlockAware } from "./reviewDocBlockAware.mjs";
|
|
|
6
6
|
import { logReviewFileBlocks } from "./reviewDocLog.mjs";
|
|
7
7
|
import { existsSync } from "node:fs";
|
|
8
8
|
import { join, relative } from "node:path";
|
|
9
|
-
import { listGitFiles, listGitLines, logConfigDetails } from "@intlayer/chokidar/cli";
|
|
10
|
-
import { formatLocale, formatPath, parallelize } from "@intlayer/chokidar/utils";
|
|
11
9
|
import * as ANSIColors from "@intlayer/config/colors";
|
|
12
10
|
import { colorize, colorizeNumber, getAppLogger, x } from "@intlayer/config/logger";
|
|
13
11
|
import { getConfiguration } from "@intlayer/config/node";
|
|
12
|
+
import { listGitFiles, listGitLines, logConfigDetails } from "@intlayer/engine/cli";
|
|
13
|
+
import { formatLocale, formatPath, parallelize } from "@intlayer/engine/utils";
|
|
14
14
|
import { readFile } from "node:fs/promises";
|
|
15
|
-
import { buildReviewReport } from "@intlayer/
|
|
15
|
+
import { buildReviewReport } from "@intlayer/engine/docReview";
|
|
16
16
|
import fg from "fast-glob";
|
|
17
17
|
|
|
18
18
|
//#region src/reviewDoc/reviewDoc.ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reviewDoc.mjs","names":[],"sources":["../../../src/reviewDoc/reviewDoc.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { join, relative } from 'node:path';\nimport type { AIConfig } from '@intlayer/ai';\nimport type { AIOptions } from '@intlayer/api';\nimport {\n type ListGitFilesOptions,\n listGitFiles,\n listGitLines,\n logConfigDetails,\n} from '@intlayer/chokidar/cli';\nimport { buildReviewReport } from '@intlayer/chokidar/docReview';\nimport {\n formatLocale,\n formatPath,\n parallelize,\n} from '@intlayer/chokidar/utils';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport {\n colorize,\n colorizeNumber,\n getAppLogger,\n x,\n} from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport fg from 'fast-glob';\nimport { checkFileModifiedRange } from '../utils/checkFileModifiedRange';\nimport { formatLineRanges } from '../utils/formatLineRanges';\nimport { getOutputFilePath } from '../utils/getOutputFilePath';\nimport { type AIClient, setupAI } from '../utils/setupAI';\nimport { reviewFileBlockAware } from './reviewDocBlockAware';\nimport { logReviewFileBlocks } from './reviewDocLog';\n\ntype ReviewDocOptions = {\n docPattern: string[];\n locales: Locale[];\n excludedGlobPattern: string[];\n baseLocale: Locale;\n aiOptions?: AIOptions;\n nbSimultaneousFileProcessed?: number;\n configOptions?: GetConfigurationOptions;\n customInstructions?: string;\n skipIfModifiedBefore?: number | string | Date;\n skipIfModifiedAfter?: number | string | Date;\n skipIfExists?: boolean;\n gitOptions?: ListGitFilesOptions;\n /**\n * Log-only mode. Instead of translating the changed blocks with AI, log the\n * blocks that need attention (with line numbers and content) for the base and\n * target locales, so another agent can generate the translations.\n */\n log?: boolean;\n};\n\n/**\n * Main audit function: scans all .md files in \"en/\" (unless you specified DOC_LIST),\n * then audits them to each locale in LOCALE_LIST.\n */\nexport const reviewDoc = async ({\n docPattern,\n locales,\n excludedGlobPattern,\n baseLocale,\n aiOptions,\n nbSimultaneousFileProcessed,\n configOptions,\n customInstructions,\n skipIfModifiedBefore,\n skipIfModifiedAfter,\n skipIfExists,\n gitOptions,\n log,\n}: ReviewDocOptions) => {\n const configuration = getConfiguration(configOptions);\n logConfigDetails(configOptions);\n\n const appLogger = getAppLogger({ log: { ...configuration.log, prefix: '' } });\n\n // Log-only mode does not call any AI, so the AI access checks are skipped.\n let aiClient: AIClient | undefined;\n let aiConfig: AIConfig | undefined;\n\n if (!log) {\n const aiResult = await setupAI(configuration, aiOptions);\n\n if (!aiResult?.hasAIAccess) return;\n\n aiClient = aiResult.aiClient;\n aiConfig = aiResult.aiConfig;\n\n if (aiResult.isCustomAI && aiClient && aiConfig) {\n const { hasAIAccess, error } = await aiClient.checkAISDKAccess(aiConfig);\n if (!hasAIAccess) {\n appLogger(`${x} ${error}`);\n return;\n }\n }\n }\n\n if (nbSimultaneousFileProcessed && nbSimultaneousFileProcessed > 10) {\n appLogger(\n `Warning: nbSimultaneousFileProcessed is set to ${nbSimultaneousFileProcessed}, which is greater than 10. Setting it to 10.`\n );\n nbSimultaneousFileProcessed = 10; // Limit the number of simultaneous file processed to 10\n }\n\n let docList: string[] = await fg(docPattern, {\n ignore: excludedGlobPattern,\n });\n\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 docList = docList.filter((path) =>\n gitChangedFiles.some((gitFile) => join(process.cwd(), path) === gitFile)\n );\n }\n }\n\n // OAuth handled by API proxy internally\n\n appLogger(`Base locale is ${formatLocale(baseLocale)}`);\n appLogger(\n `Reviewing ${colorizeNumber(locales.length)} locales: [ ${formatLocale(locales)} ]`\n );\n\n appLogger(`Reviewing ${colorizeNumber(docList.length)} files:`);\n appLogger(docList.map((path) => ` - ${formatPath(path)}\\n`));\n\n // Create all tasks to be processed\n const allTasks = docList.flatMap((docPath) =>\n locales.map((locale) => async () => {\n appLogger(\n `Reviewing file: ${formatPath(docPath)} to ${formatLocale(locale)}`\n );\n\n const absoluteBaseFilePath = join(configuration.system.baseDir, docPath);\n const outputFilePath = getOutputFilePath(\n absoluteBaseFilePath,\n locale,\n baseLocale\n );\n\n // Skip if file exists and skipIfExists option is enabled\n if (skipIfExists && existsSync(outputFilePath)) {\n const relativePath = relative(\n configuration.system.baseDir,\n outputFilePath\n );\n appLogger(\n `${colorize('⊘', ANSIColors.YELLOW)} File ${formatPath(relativePath)} already exists, skipping.`\n );\n return;\n }\n\n // Check modification range only if the file exists\n if (existsSync(outputFilePath)) {\n const fileModificationData = checkFileModifiedRange(outputFilePath, {\n skipIfModifiedBefore,\n skipIfModifiedAfter,\n });\n\n if (fileModificationData.isSkipped) {\n appLogger(fileModificationData.message);\n return;\n }\n } else if (skipIfModifiedBefore || skipIfModifiedAfter) {\n // Log if we intended to check modification time but couldn't because the file doesn't exist\n appLogger(\n `${colorize('!', ANSIColors.YELLOW)} File ${formatPath(outputFilePath)} does not exist, skipping modification date check.`\n );\n }\n\n let changedLines: number[] | undefined;\n // FIXED: Enable git optimization that was previously commented out\n if (gitOptions) {\n const gitChangedLines = await listGitLines(\n absoluteBaseFilePath,\n gitOptions\n );\n changedLines = gitChangedLines;\n\n // Report the base lines that changed, then the corresponding line span\n // in the target document — i.e. the blocks the alignment will actually\n // re-translate. `review` blocks are exactly the aligned base blocks the\n // changed lines touched, so their `targetLineRange` is the matching span\n // in the existing translation.\n appLogger(\n `Changed lines (${formatLocale(baseLocale)}): ${formatLineRanges(gitChangedLines)}`\n );\n\n const baseText = await readFile(absoluteBaseFilePath, 'utf-8').catch(\n () => ''\n );\n const targetText = existsSync(outputFilePath)\n ? await readFile(outputFilePath, 'utf-8').catch(() => '')\n : '';\n\n const { blocks } = buildReviewReport({\n baseText,\n targetText,\n changedLines: gitChangedLines,\n });\n\n const correspondingTargetLines = blocks.flatMap((block) => {\n if (block.action !== 'review' || !block.targetLineRange) return [];\n\n const { start, end } = block.targetLineRange;\n return Array.from(\n { length: end - start + 1 },\n (_unused, offset) => start + offset\n );\n });\n\n appLogger(\n `Corresponding block (${formatLocale(locale)}): ${formatLineRanges(correspondingTargetLines)}`\n );\n }\n\n if (log) {\n await logReviewFileBlocks(\n absoluteBaseFilePath,\n outputFilePath,\n locale as Locale,\n baseLocale,\n configOptions,\n changedLines\n );\n return;\n }\n\n await reviewFileBlockAware(\n absoluteBaseFilePath,\n outputFilePath,\n locale as Locale,\n baseLocale,\n aiOptions,\n configOptions,\n customInstructions,\n changedLines,\n aiClient,\n aiConfig\n );\n })\n );\n\n await parallelize(\n allTasks,\n (task) => task(),\n nbSimultaneousFileProcessed ?? 3\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"reviewDoc.mjs","names":[],"sources":["../../../src/reviewDoc/reviewDoc.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { join, relative } from 'node:path';\nimport type { AIConfig } from '@intlayer/ai';\nimport type { AIOptions } from '@intlayer/api';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport {\n colorize,\n colorizeNumber,\n getAppLogger,\n x,\n} from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport {\n type ListGitFilesOptions,\n listGitFiles,\n listGitLines,\n logConfigDetails,\n} from '@intlayer/engine/cli';\nimport { buildReviewReport } from '@intlayer/engine/docReview';\nimport { formatLocale, formatPath, parallelize } from '@intlayer/engine/utils';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport fg from 'fast-glob';\nimport { checkFileModifiedRange } from '../utils/checkFileModifiedRange';\nimport { formatLineRanges } from '../utils/formatLineRanges';\nimport { getOutputFilePath } from '../utils/getOutputFilePath';\nimport { type AIClient, setupAI } from '../utils/setupAI';\nimport { reviewFileBlockAware } from './reviewDocBlockAware';\nimport { logReviewFileBlocks } from './reviewDocLog';\n\ntype ReviewDocOptions = {\n docPattern: string[];\n locales: Locale[];\n excludedGlobPattern: string[];\n baseLocale: Locale;\n aiOptions?: AIOptions;\n nbSimultaneousFileProcessed?: number;\n configOptions?: GetConfigurationOptions;\n customInstructions?: string;\n skipIfModifiedBefore?: number | string | Date;\n skipIfModifiedAfter?: number | string | Date;\n skipIfExists?: boolean;\n gitOptions?: ListGitFilesOptions;\n /**\n * Log-only mode. Instead of translating the changed blocks with AI, log the\n * blocks that need attention (with line numbers and content) for the base and\n * target locales, so another agent can generate the translations.\n */\n log?: boolean;\n};\n\n/**\n * Main audit function: scans all .md files in \"en/\" (unless you specified DOC_LIST),\n * then audits them to each locale in LOCALE_LIST.\n */\nexport const reviewDoc = async ({\n docPattern,\n locales,\n excludedGlobPattern,\n baseLocale,\n aiOptions,\n nbSimultaneousFileProcessed,\n configOptions,\n customInstructions,\n skipIfModifiedBefore,\n skipIfModifiedAfter,\n skipIfExists,\n gitOptions,\n log,\n}: ReviewDocOptions) => {\n const configuration = getConfiguration(configOptions);\n logConfigDetails(configOptions);\n\n const appLogger = getAppLogger({ log: { ...configuration.log, prefix: '' } });\n\n // Log-only mode does not call any AI, so the AI access checks are skipped.\n let aiClient: AIClient | undefined;\n let aiConfig: AIConfig | undefined;\n\n if (!log) {\n const aiResult = await setupAI(configuration, aiOptions);\n\n if (!aiResult?.hasAIAccess) return;\n\n aiClient = aiResult.aiClient;\n aiConfig = aiResult.aiConfig;\n\n if (aiResult.isCustomAI && aiClient && aiConfig) {\n const { hasAIAccess, error } = await aiClient.checkAISDKAccess(aiConfig);\n if (!hasAIAccess) {\n appLogger(`${x} ${error}`);\n return;\n }\n }\n }\n\n if (nbSimultaneousFileProcessed && nbSimultaneousFileProcessed > 10) {\n appLogger(\n `Warning: nbSimultaneousFileProcessed is set to ${nbSimultaneousFileProcessed}, which is greater than 10. Setting it to 10.`\n );\n nbSimultaneousFileProcessed = 10; // Limit the number of simultaneous file processed to 10\n }\n\n let docList: string[] = await fg(docPattern, {\n ignore: excludedGlobPattern,\n });\n\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 docList = docList.filter((path) =>\n gitChangedFiles.some((gitFile) => join(process.cwd(), path) === gitFile)\n );\n }\n }\n\n // OAuth handled by API proxy internally\n\n appLogger(`Base locale is ${formatLocale(baseLocale)}`);\n appLogger(\n `Reviewing ${colorizeNumber(locales.length)} locales: [ ${formatLocale(locales)} ]`\n );\n\n appLogger(`Reviewing ${colorizeNumber(docList.length)} files:`);\n appLogger(docList.map((path) => ` - ${formatPath(path)}\\n`));\n\n // Create all tasks to be processed\n const allTasks = docList.flatMap((docPath) =>\n locales.map((locale) => async () => {\n appLogger(\n `Reviewing file: ${formatPath(docPath)} to ${formatLocale(locale)}`\n );\n\n const absoluteBaseFilePath = join(configuration.system.baseDir, docPath);\n const outputFilePath = getOutputFilePath(\n absoluteBaseFilePath,\n locale,\n baseLocale\n );\n\n // Skip if file exists and skipIfExists option is enabled\n if (skipIfExists && existsSync(outputFilePath)) {\n const relativePath = relative(\n configuration.system.baseDir,\n outputFilePath\n );\n appLogger(\n `${colorize('⊘', ANSIColors.YELLOW)} File ${formatPath(relativePath)} already exists, skipping.`\n );\n return;\n }\n\n // Check modification range only if the file exists\n if (existsSync(outputFilePath)) {\n const fileModificationData = checkFileModifiedRange(outputFilePath, {\n skipIfModifiedBefore,\n skipIfModifiedAfter,\n });\n\n if (fileModificationData.isSkipped) {\n appLogger(fileModificationData.message);\n return;\n }\n } else if (skipIfModifiedBefore || skipIfModifiedAfter) {\n // Log if we intended to check modification time but couldn't because the file doesn't exist\n appLogger(\n `${colorize('!', ANSIColors.YELLOW)} File ${formatPath(outputFilePath)} does not exist, skipping modification date check.`\n );\n }\n\n let changedLines: number[] | undefined;\n // FIXED: Enable git optimization that was previously commented out\n if (gitOptions) {\n const gitChangedLines = await listGitLines(\n absoluteBaseFilePath,\n gitOptions\n );\n changedLines = gitChangedLines;\n\n // Report the base lines that changed, then the corresponding line span\n // in the target document — i.e. the blocks the alignment will actually\n // re-translate. `review` blocks are exactly the aligned base blocks the\n // changed lines touched, so their `targetLineRange` is the matching span\n // in the existing translation.\n appLogger(\n `Changed lines (${formatLocale(baseLocale)}): ${formatLineRanges(gitChangedLines)}`\n );\n\n const baseText = await readFile(absoluteBaseFilePath, 'utf-8').catch(\n () => ''\n );\n const targetText = existsSync(outputFilePath)\n ? await readFile(outputFilePath, 'utf-8').catch(() => '')\n : '';\n\n const { blocks } = buildReviewReport({\n baseText,\n targetText,\n changedLines: gitChangedLines,\n });\n\n const correspondingTargetLines = blocks.flatMap((block) => {\n if (block.action !== 'review' || !block.targetLineRange) return [];\n\n const { start, end } = block.targetLineRange;\n return Array.from(\n { length: end - start + 1 },\n (_unused, offset) => start + offset\n );\n });\n\n appLogger(\n `Corresponding block (${formatLocale(locale)}): ${formatLineRanges(correspondingTargetLines)}`\n );\n }\n\n if (log) {\n await logReviewFileBlocks(\n absoluteBaseFilePath,\n outputFilePath,\n locale as Locale,\n baseLocale,\n configOptions,\n changedLines\n );\n return;\n }\n\n await reviewFileBlockAware(\n absoluteBaseFilePath,\n outputFilePath,\n locale as Locale,\n baseLocale,\n aiOptions,\n configOptions,\n customInstructions,\n changedLines,\n aiClient,\n aiConfig\n );\n })\n );\n\n await parallelize(\n allTasks,\n (task) => task(),\n nbSimultaneousFileProcessed ?? 3\n );\n};\n"],"mappings":"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"}
|
|
@@ -4,13 +4,13 @@ import { chunkInference } from "../utils/chunkInference.mjs";
|
|
|
4
4
|
import { fixChunkStartEndChars } from "../utils/fixChunkStartEndChars.mjs";
|
|
5
5
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { dirname } from "node:path";
|
|
7
|
-
import { formatLocale, formatPath } from "@intlayer/chokidar/utils";
|
|
8
7
|
import * as ANSIColors from "@intlayer/config/colors";
|
|
9
8
|
import { colon, colorize, colorizeNumber, getAppLogger } from "@intlayer/config/logger";
|
|
10
9
|
import { getConfiguration } from "@intlayer/config/node";
|
|
10
|
+
import { formatLocale, formatPath } from "@intlayer/engine/utils";
|
|
11
11
|
import { readFile } from "node:fs/promises";
|
|
12
12
|
import { retryManager } from "@intlayer/config/utils";
|
|
13
|
-
import { buildAlignmentPlan, mergeReviewedSegments } from "@intlayer/
|
|
13
|
+
import { buildAlignmentPlan, mergeReviewedSegments } from "@intlayer/engine/docReview";
|
|
14
14
|
import { getLocaleName } from "@intlayer/core/localization";
|
|
15
15
|
import { ENGLISH } from "@intlayer/types/locales";
|
|
16
16
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reviewDocBlockAware.mjs","names":[],"sources":["../../../src/reviewDoc/reviewDocBlockAware.ts"],"sourcesContent":["import { mkdirSync, writeFileSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { dirname } from 'node:path';\nimport { readAsset } from 'utils:asset';\nimport type { AIConfig } from '@intlayer/ai';\nimport type { AIOptions } from '@intlayer/api';\nimport {\n buildAlignmentPlan,\n mergeReviewedSegments,\n} from '@intlayer/chokidar/docReview';\nimport { formatLocale, formatPath } from '@intlayer/chokidar/utils';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport {\n colon,\n colorize,\n colorizeNumber,\n getAppLogger,\n} from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport { retryManager } from '@intlayer/config/utils';\nimport { getLocaleName } from '@intlayer/core/localization';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport { ENGLISH } from '@intlayer/types/locales';\nimport { sanitizeChunk, validateTranslation } from '../translateDoc/validation';\nimport { chunkInference } from '../utils/chunkInference';\nimport { fixChunkStartEndChars } from '../utils/fixChunkStartEndChars';\nimport type { AIClient } from '../utils/setupAI';\n\n/**\n * Review a file using block-aware alignment.\n *\n * 1. Segments both base and target documents into semantic blocks.\n * 2. Aligns blocks using structure (special chars, numbers) and context.\n * 3. Detects which blocks changed, were added, or deleted.\n * 4. Applies deletions immediately without AI.\n * 5. Sends changed/new blocks to AI in bottom-up order (last block first), so\n * line numbers of earlier blocks are not shifted by edits below them.\n * 6. Rewrites the file after each block so progress is persisted incrementally.\n */\nexport const reviewFileBlockAware = async (\n baseFilePath: string,\n outputFilePath: string,\n locale: Locale,\n baseLocale: Locale,\n aiOptions?: AIOptions,\n configOptions?: GetConfigurationOptions,\n customInstructions?: string,\n changedLines?: number[],\n aiClient?: AIClient,\n aiConfig?: AIConfig\n) => {\n const configuration = getConfiguration(configOptions);\n const applicationLogger = getAppLogger({\n log: { ...configuration.log, prefix: '' },\n });\n\n const baseText = await readFile(baseFilePath, 'utf-8');\n const targetText = await readFile(outputFilePath, 'utf-8').catch(() => '');\n\n const basePrompt = readAsset('./prompts/REVIEW_PROMPT.md', 'utf-8')\n .replaceAll('{{localeName}}', `${formatLocale(locale, false)}`)\n .replaceAll('{{baseLocaleName}}', `${formatLocale(baseLocale, false)}`)\n .replace('{{applicationContext}}', aiOptions?.applicationContext ?? '-')\n .replace('{{customInstructions}}', customInstructions ?? '-');\n\n const filePrefixText = `${ANSIColors.GREY_DARK}[${formatPath(baseFilePath)}${ANSIColors.GREY_DARK}] `;\n const filePrefix = [\n colon(filePrefixText, { colSize: 40 }),\n `→ ${ANSIColors.RESET}`,\n ].join('');\n const prefixText = `${ANSIColors.GREY_DARK}[${formatPath(baseFilePath)}${ANSIColors.GREY_DARK}][${formatLocale(locale)}${ANSIColors.GREY_DARK}] `;\n const prefix = [\n colon(prefixText, { colSize: 40 }),\n `→ ${ANSIColors.RESET}`,\n ].join('');\n\n // Build block-aware alignment and plan\n const { baseBlocks, targetBlocks, plan, segmentsToReview } =\n buildAlignmentPlan({\n baseText,\n targetText,\n changedLines,\n });\n\n const deleteCount = plan.actions.filter((a) => a.kind === 'delete').length;\n\n applicationLogger(\n `${filePrefix}Block-aware alignment complete. Total blocks: base=${colorizeNumber(baseBlocks.length)}, target=${colorizeNumber(targetBlocks.length)}`\n );\n applicationLogger(\n `${filePrefix}Actions: reuse=${colorizeNumber(plan.actions.filter((a) => a.kind === 'reuse').length)}, review=${colorizeNumber(plan.actions.filter((a) => a.kind === 'review').length)}, new=${colorizeNumber(plan.actions.filter((a) => a.kind === 'insert_new').length)}, delete=${colorizeNumber(deleteCount)}`\n );\n\n // Map shared across the entire run: each entry overrides the default behavior\n // of mergeReviewedSegments for that action index.\n const reviewedSegmentsMap = new Map<number, string>();\n\n // --- Step 1: apply deletions immediately (no AI needed) ---\n for (const [actionIndex, action] of plan.actions.entries()) {\n if (action.kind === 'delete') {\n reviewedSegmentsMap.set(actionIndex, '');\n }\n }\n\n const writeCurrentState = (): void => {\n const output = mergeReviewedSegments(\n plan,\n targetBlocks,\n reviewedSegmentsMap\n );\n mkdirSync(dirname(outputFilePath), { recursive: true });\n writeFileSync(outputFilePath, output);\n };\n\n if (deleteCount > 0) {\n writeCurrentState();\n applicationLogger(\n `${filePrefix}${colorizeNumber(deleteCount)} block(s) deleted without AI.`\n );\n }\n\n if (segmentsToReview.length === 0) {\n if (deleteCount === 0) {\n applicationLogger(\n `${filePrefix}No segments need review, reusing existing translation`\n );\n writeCurrentState();\n }\n applicationLogger(\n `${colorize('✔', ANSIColors.GREEN)} File ${formatPath(outputFilePath)} updated successfully (no AI changes needed).`\n );\n return;\n }\n\n applicationLogger(\n `${filePrefix}Segments to review: ${colorizeNumber(segmentsToReview.length)} (processing bottom-up)`\n );\n\n // --- Step 2: process AI segments in bottom-up order ---\n // Reversing ensures edits near the end of the file don't shift line numbers\n // that matter for blocks higher up, and each intermediate file write is valid.\n const segmentsBottomUp = segmentsToReview\n .map((segment, originalIndex) => ({\n segment,\n displayNumber: originalIndex + 1,\n }))\n .reverse();\n\n for (const { segment, displayNumber } of segmentsBottomUp) {\n const baseBlock = segment.baseBlock;\n\n const getBaseChunkContextPrompt = () =>\n `**BLOCK ${displayNumber} of ${segmentsToReview.length}** is the base block in ${formatLocale(baseLocale, false)} as reference.\\n` +\n `///chunksStart///\\n` +\n baseBlock.content +\n `///chunksEnd///`;\n\n const getTargetChunkPrompt = () =>\n `**BLOCK ${displayNumber} of ${segmentsToReview.length}** is the current block to review in ${formatLocale(locale, false)}.\\n` +\n `///chunksStart///\\n` +\n (segment.targetBlockText ?? '') +\n `///chunksEnd///`;\n\n const reviewedChunkResult = await retryManager(async () => {\n const result = await chunkInference(\n [\n { role: 'system', content: basePrompt },\n { role: 'system', content: getBaseChunkContextPrompt() },\n { role: 'system', content: getTargetChunkPrompt() },\n {\n role: 'system',\n content: `The next user message will be the **BLOCK ${colorizeNumber(displayNumber)} of ${colorizeNumber(segmentsToReview.length)}** that should be translated in ${getLocaleName(locale, ENGLISH)} (${locale}).`,\n },\n ],\n [{ role: 'user', content: baseBlock.content }],\n aiOptions,\n configuration,\n aiClient,\n aiConfig\n );\n\n applicationLogger(\n `${prefix}${colorizeNumber(result.tokenUsed)} tokens used - Block ${colorizeNumber(displayNumber)} of ${colorizeNumber(segmentsToReview.length)}`\n );\n\n let processedChunk = sanitizeChunk(\n result?.fileContent,\n baseBlock.content\n );\n processedChunk = fixChunkStartEndChars(processedChunk, baseBlock.content);\n\n const isValid = validateTranslation(\n baseBlock.content,\n processedChunk,\n applicationLogger\n );\n\n if (!isValid) {\n throw new Error(\n 'Validation failed for chunk (structure or length mismatch). Retrying...'\n );\n }\n\n return processedChunk;\n })();\n\n reviewedSegmentsMap.set(segment.actionIndex, reviewedChunkResult);\n\n // Rewrite the file after every block so progress is never lost.\n writeCurrentState();\n }\n\n applicationLogger(\n `${colorize('✔', ANSIColors.GREEN)} File ${formatPath(outputFilePath)} created/updated successfully.`\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CA,MAAa,uBAAuB,OAClC,cACA,gBACA,QACA,YACA,WACA,eACA,oBACA,cACA,UACA,aACG;CACH,MAAM,gBAAgB,iBAAiB,cAAc;CACrD,MAAM,oBAAoB,aAAa,EACrC,KAAK;EAAE,GAAG,cAAc;EAAK,QAAQ;EAAI,EAC1C,CAAC;CAEF,MAAM,WAAW,MAAM,SAAS,cAAc,QAAQ;CACtD,MAAM,aAAa,MAAM,SAAS,gBAAgB,QAAQ,CAAC,YAAY,GAAG;CAE1E,MAAM,aAAa,UAAU,8BAA8B,QAAQ,CAChE,WAAW,kBAAkB,GAAG,aAAa,QAAQ,MAAM,GAAG,CAC9D,WAAW,sBAAsB,GAAG,aAAa,YAAY,MAAM,GAAG,CACtE,QAAQ,0BAA0B,WAAW,sBAAsB,IAAI,CACvE,QAAQ,0BAA0B,sBAAsB,IAAI;CAG/D,MAAM,aAAa,CACjB,MAAM,GAFkB,WAAW,UAAU,GAAG,WAAW,aAAa,GAAG,WAAW,UAAU,KAE1E,EAAE,SAAS,IAAI,CAAC,EACtC,KAAK,WAAW,QACjB,CAAC,KAAK,GAAG;CAEV,MAAM,SAAS,CACb,MAAM,GAFc,WAAW,UAAU,GAAG,WAAW,aAAa,GAAG,WAAW,UAAU,IAAI,aAAa,OAAO,GAAG,WAAW,UAAU,KAE1H,EAAE,SAAS,IAAI,CAAC,EAClC,KAAK,WAAW,QACjB,CAAC,KAAK,GAAG;CAGV,MAAM,EAAE,YAAY,cAAc,MAAM,qBACtC,mBAAmB;EACjB;EACA;EACA;EACD,CAAC;CAEJ,MAAM,cAAc,KAAK,QAAQ,QAAQ,MAAM,EAAE,SAAS,SAAS,CAAC;AAEpE,mBACE,GAAG,WAAW,qDAAqD,eAAe,WAAW,OAAO,CAAC,WAAW,eAAe,aAAa,OAAO,GACpJ;AACD,mBACE,GAAG,WAAW,iBAAiB,eAAe,KAAK,QAAQ,QAAQ,MAAM,EAAE,SAAS,QAAQ,CAAC,OAAO,CAAC,WAAW,eAAe,KAAK,QAAQ,QAAQ,MAAM,EAAE,SAAS,SAAS,CAAC,OAAO,CAAC,QAAQ,eAAe,KAAK,QAAQ,QAAQ,MAAM,EAAE,SAAS,aAAa,CAAC,OAAO,CAAC,WAAW,eAAe,YAAY,GACjT;CAID,MAAM,sCAAsB,IAAI,KAAqB;AAGrD,MAAK,MAAM,CAAC,aAAa,WAAW,KAAK,QAAQ,SAAS,CACxD,KAAI,OAAO,SAAS,SAClB,qBAAoB,IAAI,aAAa,GAAG;CAI5C,MAAM,0BAAgC;EACpC,MAAM,SAAS,sBACb,MACA,cACA,oBACD;AACD,YAAU,QAAQ,eAAe,EAAE,EAAE,WAAW,MAAM,CAAC;AACvD,gBAAc,gBAAgB,OAAO;;AAGvC,KAAI,cAAc,GAAG;AACnB,qBAAmB;AACnB,oBACE,GAAG,aAAa,eAAe,YAAY,CAAC,+BAC7C;;AAGH,KAAI,iBAAiB,WAAW,GAAG;AACjC,MAAI,gBAAgB,GAAG;AACrB,qBACE,GAAG,WAAW,uDACf;AACD,sBAAmB;;AAErB,oBACE,GAAG,SAAS,KAAK,WAAW,MAAM,CAAC,QAAQ,WAAW,eAAe,CAAC,+CACvE;AACD;;AAGF,mBACE,GAAG,WAAW,sBAAsB,eAAe,iBAAiB,OAAO,CAAC,yBAC7E;CAKD,MAAM,mBAAmB,iBACtB,KAAK,SAAS,mBAAmB;EAChC;EACA,eAAe,gBAAgB;EAChC,EAAE,CACF,SAAS;AAEZ,MAAK,MAAM,EAAE,SAAS,mBAAmB,kBAAkB;EACzD,MAAM,YAAY,QAAQ;EAE1B,MAAM,kCACJ,WAAW,cAAc,MAAM,iBAAiB,OAAO,0BAA0B,aAAa,YAAY,MAAM,CAAC,uCAEjH,UAAU,UACV;EAEF,MAAM,6BACJ,WAAW,cAAc,MAAM,iBAAiB,OAAO,uCAAuC,aAAa,QAAQ,MAAM,CAAC,2BAEzH,QAAQ,mBAAmB,MAC5B;EAEF,MAAM,sBAAsB,MAAM,aAAa,YAAY;GACzD,MAAM,SAAS,MAAM,eACnB;IACE;KAAE,MAAM;KAAU,SAAS;KAAY;IACvC;KAAE,MAAM;KAAU,SAAS,2BAA2B;KAAE;IACxD;KAAE,MAAM;KAAU,SAAS,sBAAsB;KAAE;IACnD;KACE,MAAM;KACN,SAAS,6CAA6C,eAAe,cAAc,CAAC,MAAM,eAAe,iBAAiB,OAAO,CAAC,kCAAkC,cAAc,QAAQ,QAAQ,CAAC,IAAI,OAAO;KAC/M;IACF,EACD,CAAC;IAAE,MAAM;IAAQ,SAAS,UAAU;IAAS,CAAC,EAC9C,WACA,eACA,UACA,SACD;AAED,qBACE,GAAG,SAAS,eAAe,OAAO,UAAU,CAAC,uBAAuB,eAAe,cAAc,CAAC,MAAM,eAAe,iBAAiB,OAAO,GAChJ;GAED,IAAI,iBAAiB,cACnB,QAAQ,aACR,UAAU,QACX;AACD,oBAAiB,sBAAsB,gBAAgB,UAAU,QAAQ;AAQzE,OAAI,CANY,oBACd,UAAU,SACV,gBACA,kBAGU,CACV,OAAM,IAAI,MACR,0EACD;AAGH,UAAO;IACP,EAAE;AAEJ,sBAAoB,IAAI,QAAQ,aAAa,oBAAoB;AAGjE,qBAAmB;;AAGrB,mBACE,GAAG,SAAS,KAAK,WAAW,MAAM,CAAC,QAAQ,WAAW,eAAe,CAAC,gCACvE"}
|
|
1
|
+
{"version":3,"file":"reviewDocBlockAware.mjs","names":[],"sources":["../../../src/reviewDoc/reviewDocBlockAware.ts"],"sourcesContent":["import { mkdirSync, writeFileSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { dirname } from 'node:path';\nimport { readAsset } from 'utils:asset';\nimport type { AIConfig } from '@intlayer/ai';\nimport type { AIOptions } from '@intlayer/api';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport {\n colon,\n colorize,\n colorizeNumber,\n getAppLogger,\n} from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport { retryManager } from '@intlayer/config/utils';\nimport { getLocaleName } from '@intlayer/core/localization';\nimport {\n buildAlignmentPlan,\n mergeReviewedSegments,\n} from '@intlayer/engine/docReview';\nimport { formatLocale, formatPath } from '@intlayer/engine/utils';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport { ENGLISH } from '@intlayer/types/locales';\nimport { sanitizeChunk, validateTranslation } from '../translateDoc/validation';\nimport { chunkInference } from '../utils/chunkInference';\nimport { fixChunkStartEndChars } from '../utils/fixChunkStartEndChars';\nimport type { AIClient } from '../utils/setupAI';\n\n/**\n * Review a file using block-aware alignment.\n *\n * 1. Segments both base and target documents into semantic blocks.\n * 2. Aligns blocks using structure (special chars, numbers) and context.\n * 3. Detects which blocks changed, were added, or deleted.\n * 4. Applies deletions immediately without AI.\n * 5. Sends changed/new blocks to AI in bottom-up order (last block first), so\n * line numbers of earlier blocks are not shifted by edits below them.\n * 6. Rewrites the file after each block so progress is persisted incrementally.\n */\nexport const reviewFileBlockAware = async (\n baseFilePath: string,\n outputFilePath: string,\n locale: Locale,\n baseLocale: Locale,\n aiOptions?: AIOptions,\n configOptions?: GetConfigurationOptions,\n customInstructions?: string,\n changedLines?: number[],\n aiClient?: AIClient,\n aiConfig?: AIConfig\n) => {\n const configuration = getConfiguration(configOptions);\n const applicationLogger = getAppLogger({\n log: { ...configuration.log, prefix: '' },\n });\n\n const baseText = await readFile(baseFilePath, 'utf-8');\n const targetText = await readFile(outputFilePath, 'utf-8').catch(() => '');\n\n const basePrompt = readAsset('./prompts/REVIEW_PROMPT.md', 'utf-8')\n .replaceAll('{{localeName}}', `${formatLocale(locale, false)}`)\n .replaceAll('{{baseLocaleName}}', `${formatLocale(baseLocale, false)}`)\n .replace('{{applicationContext}}', aiOptions?.applicationContext ?? '-')\n .replace('{{customInstructions}}', customInstructions ?? '-');\n\n const filePrefixText = `${ANSIColors.GREY_DARK}[${formatPath(baseFilePath)}${ANSIColors.GREY_DARK}] `;\n const filePrefix = [\n colon(filePrefixText, { colSize: 40 }),\n `→ ${ANSIColors.RESET}`,\n ].join('');\n const prefixText = `${ANSIColors.GREY_DARK}[${formatPath(baseFilePath)}${ANSIColors.GREY_DARK}][${formatLocale(locale)}${ANSIColors.GREY_DARK}] `;\n const prefix = [\n colon(prefixText, { colSize: 40 }),\n `→ ${ANSIColors.RESET}`,\n ].join('');\n\n // Build block-aware alignment and plan\n const { baseBlocks, targetBlocks, plan, segmentsToReview } =\n buildAlignmentPlan({\n baseText,\n targetText,\n changedLines,\n });\n\n const deleteCount = plan.actions.filter((a) => a.kind === 'delete').length;\n\n applicationLogger(\n `${filePrefix}Block-aware alignment complete. Total blocks: base=${colorizeNumber(baseBlocks.length)}, target=${colorizeNumber(targetBlocks.length)}`\n );\n applicationLogger(\n `${filePrefix}Actions: reuse=${colorizeNumber(plan.actions.filter((a) => a.kind === 'reuse').length)}, review=${colorizeNumber(plan.actions.filter((a) => a.kind === 'review').length)}, new=${colorizeNumber(plan.actions.filter((a) => a.kind === 'insert_new').length)}, delete=${colorizeNumber(deleteCount)}`\n );\n\n // Map shared across the entire run: each entry overrides the default behavior\n // of mergeReviewedSegments for that action index.\n const reviewedSegmentsMap = new Map<number, string>();\n\n // --- Step 1: apply deletions immediately (no AI needed) ---\n for (const [actionIndex, action] of plan.actions.entries()) {\n if (action.kind === 'delete') {\n reviewedSegmentsMap.set(actionIndex, '');\n }\n }\n\n const writeCurrentState = (): void => {\n const output = mergeReviewedSegments(\n plan,\n targetBlocks,\n reviewedSegmentsMap\n );\n mkdirSync(dirname(outputFilePath), { recursive: true });\n writeFileSync(outputFilePath, output);\n };\n\n if (deleteCount > 0) {\n writeCurrentState();\n applicationLogger(\n `${filePrefix}${colorizeNumber(deleteCount)} block(s) deleted without AI.`\n );\n }\n\n if (segmentsToReview.length === 0) {\n if (deleteCount === 0) {\n applicationLogger(\n `${filePrefix}No segments need review, reusing existing translation`\n );\n writeCurrentState();\n }\n applicationLogger(\n `${colorize('✔', ANSIColors.GREEN)} File ${formatPath(outputFilePath)} updated successfully (no AI changes needed).`\n );\n return;\n }\n\n applicationLogger(\n `${filePrefix}Segments to review: ${colorizeNumber(segmentsToReview.length)} (processing bottom-up)`\n );\n\n // --- Step 2: process AI segments in bottom-up order ---\n // Reversing ensures edits near the end of the file don't shift line numbers\n // that matter for blocks higher up, and each intermediate file write is valid.\n const segmentsBottomUp = segmentsToReview\n .map((segment, originalIndex) => ({\n segment,\n displayNumber: originalIndex + 1,\n }))\n .reverse();\n\n for (const { segment, displayNumber } of segmentsBottomUp) {\n const baseBlock = segment.baseBlock;\n\n const getBaseChunkContextPrompt = () =>\n `**BLOCK ${displayNumber} of ${segmentsToReview.length}** is the base block in ${formatLocale(baseLocale, false)} as reference.\\n` +\n `///chunksStart///\\n` +\n baseBlock.content +\n `///chunksEnd///`;\n\n const getTargetChunkPrompt = () =>\n `**BLOCK ${displayNumber} of ${segmentsToReview.length}** is the current block to review in ${formatLocale(locale, false)}.\\n` +\n `///chunksStart///\\n` +\n (segment.targetBlockText ?? '') +\n `///chunksEnd///`;\n\n const reviewedChunkResult = await retryManager(async () => {\n const result = await chunkInference(\n [\n { role: 'system', content: basePrompt },\n { role: 'system', content: getBaseChunkContextPrompt() },\n { role: 'system', content: getTargetChunkPrompt() },\n {\n role: 'system',\n content: `The next user message will be the **BLOCK ${colorizeNumber(displayNumber)} of ${colorizeNumber(segmentsToReview.length)}** that should be translated in ${getLocaleName(locale, ENGLISH)} (${locale}).`,\n },\n ],\n [{ role: 'user', content: baseBlock.content }],\n aiOptions,\n configuration,\n aiClient,\n aiConfig\n );\n\n applicationLogger(\n `${prefix}${colorizeNumber(result.tokenUsed)} tokens used - Block ${colorizeNumber(displayNumber)} of ${colorizeNumber(segmentsToReview.length)}`\n );\n\n let processedChunk = sanitizeChunk(\n result?.fileContent,\n baseBlock.content\n );\n processedChunk = fixChunkStartEndChars(processedChunk, baseBlock.content);\n\n const isValid = validateTranslation(\n baseBlock.content,\n processedChunk,\n applicationLogger\n );\n\n if (!isValid) {\n throw new Error(\n 'Validation failed for chunk (structure or length mismatch). Retrying...'\n );\n }\n\n return processedChunk;\n })();\n\n reviewedSegmentsMap.set(segment.actionIndex, reviewedChunkResult);\n\n // Rewrite the file after every block so progress is never lost.\n writeCurrentState();\n }\n\n applicationLogger(\n `${colorize('✔', ANSIColors.GREEN)} File ${formatPath(outputFilePath)} created/updated successfully.`\n );\n};\n"],"mappings":"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"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
-
import { formatLocale, formatPath } from "@intlayer/chokidar/utils";
|
|
3
2
|
import { getAppLogger } from "@intlayer/config/logger";
|
|
4
3
|
import { getConfiguration } from "@intlayer/config/node";
|
|
4
|
+
import { formatLocale, formatPath } from "@intlayer/engine/utils";
|
|
5
5
|
import { readFile } from "node:fs/promises";
|
|
6
|
-
import { buildReviewReport, formatReviewReport } from "@intlayer/
|
|
6
|
+
import { buildReviewReport, formatReviewReport } from "@intlayer/engine/docReview";
|
|
7
7
|
|
|
8
8
|
//#region src/reviewDoc/reviewDocLog.ts
|
|
9
9
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reviewDocLog.mjs","names":[],"sources":["../../../src/reviewDoc/reviewDocLog.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport {
|
|
1
|
+
{"version":3,"file":"reviewDocLog.mjs","names":[],"sources":["../../../src/reviewDoc/reviewDocLog.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { getAppLogger } from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport {\n buildReviewReport,\n formatReviewReport,\n type ReviewReport,\n} from '@intlayer/engine/docReview';\nimport { formatLocale, formatPath } from '@intlayer/engine/utils';\nimport type { Locale } from '@intlayer/types/allLocales';\n\n/**\n * Log-only review of a single file/locale pair.\n *\n * Instead of calling an AI to translate the changed blocks, this compares the\n * base document with its translation and logs the blocks that need attention\n * (with their line ranges and content) so another agent or a human can generate\n * the missing translations.\n *\n * @param baseFilePath - Absolute path of the base (source) document.\n * @param outputFilePath - Absolute path of the target (translated) document.\n * @param locale - The target locale being reviewed.\n * @param baseLocale - The base locale used as reference.\n * @param configOptions - Optional Intlayer configuration overrides.\n * @param changedLines - 1-based base line numbers that changed (from git), if any.\n * @returns The structured review report.\n */\nexport const logReviewFileBlocks = async (\n baseFilePath: string,\n outputFilePath: string,\n locale: Locale,\n baseLocale: Locale,\n configOptions?: GetConfigurationOptions,\n changedLines?: number[]\n): Promise<ReviewReport> => {\n const configuration = getConfiguration(configOptions);\n const appLogger = getAppLogger({ log: { ...configuration.log, prefix: '' } });\n\n const baseText = await readFile(baseFilePath, 'utf-8');\n const targetText = existsSync(outputFilePath)\n ? await readFile(outputFilePath, 'utf-8').catch(() => '')\n : '';\n\n const report = buildReviewReport({ baseText, targetText, changedLines });\n\n const formatted = formatReviewReport(report, {\n baseLabel: formatLocale(baseLocale),\n targetLabel: formatLocale(locale),\n });\n\n appLogger(`${formatPath(baseFilePath)} → ${formatLocale(locale)}`);\n for (const line of formatted.split('\\n')) {\n appLogger(line);\n }\n\n return report;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA+BA,MAAa,sBAAsB,OACjC,cACA,gBACA,QACA,YACA,eACA,iBAC0B;CAE1B,MAAM,YAAY,aAAa,EAAE,KAAK;EAAE,GADlB,iBAAiB,cACiB,CAAC;EAAK,QAAQ;EAAI,EAAE,CAAC;CAO7E,MAAM,SAAS,kBAAkB;EAAE,gBALZ,SAAS,cAAc,QAAQ;EAKT,YAJ1B,WAAW,eAAe,GACzC,MAAM,SAAS,gBAAgB,QAAQ,CAAC,YAAY,GAAG,GACvD;EAEqD;EAAc,CAAC;CAExE,MAAM,YAAY,mBAAmB,QAAQ;EAC3C,WAAW,aAAa,WAAW;EACnC,aAAa,aAAa,OAAO;EAClC,CAAC;AAEF,WAAU,GAAG,WAAW,aAAa,CAAC,KAAK,aAAa,OAAO,GAAG;AAClE,MAAK,MAAM,QAAQ,UAAU,MAAM,KAAK,CACtC,WAAU,KAAK;AAGjB,QAAO"}
|
package/dist/esm/scan.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as ANSIColors from "@intlayer/config/colors";
|
|
2
2
|
import { colorize, getAppLogger } from "@intlayer/config/logger";
|
|
3
3
|
import { getConfiguration } from "@intlayer/config/node";
|
|
4
|
-
import { formatSize, scanWebsite } from "@intlayer/
|
|
4
|
+
import { formatSize, scanWebsite } from "@intlayer/engine/scan";
|
|
5
5
|
|
|
6
6
|
//#region src/scan.ts
|
|
7
7
|
/** Human-readable labels for each scorable check type. */
|
package/dist/esm/scan.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scan.mjs","names":[],"sources":["../../src/scan.ts"],"sourcesContent":["import
|
|
1
|
+
{"version":3,"file":"scan.mjs","names":[],"sources":["../../src/scan.ts"],"sourcesContent":["import * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, getAppLogger } from '@intlayer/config/logger';\nimport { getConfiguration } from '@intlayer/config/node';\nimport { formatSize, type ScanEvent, scanWebsite } from '@intlayer/engine/scan';\nimport type { ConfigurationOptions } from './cli';\n\n/** Options accepted by the {@link scan} command. */\nexport type ScanCommandOptions = {\n /** Disable the deeper puppeteer-based render scan. */\n deep?: boolean;\n /** Output the raw {@link ScanResult} as JSON instead of formatted text. */\n json?: boolean;\n configOptions?: ConfigurationOptions;\n};\n\n/** Human-readable labels for each scorable check type. */\nconst checkLabels: Record<string, string> = {\n robots_robotsPresent: 'robots.txt present',\n robots_noLocalizedUrlsForgotten: 'robots.txt keeps locale paths crawlable',\n sitemap_sitemapPresent: 'sitemap.xml present',\n sitemap_noLocalizedUrlsForgotten: 'sitemap lists every locale',\n sitemap_hasAlternates: 'sitemap has alternate links',\n sitemap_hasXDefault: 'sitemap has x-default',\n url_htmlLang: 'html lang attribute',\n url_htmlDir: 'html dir attribute',\n url_hasCanonical: 'canonical link',\n url_hreflang: 'hreflang tags',\n url_hasLocalizedLinks: 'localized internal links',\n url_hasXDefault: 'x-default hreflang',\n url_allAnchorsLocalized: 'all internal links localized',\n url_currentLocale: 'current locale detected',\n url_unusedBundleContent: 'unused bundle locale content',\n};\n\nconst statusIcon = (status: ScanEvent['status']): string => {\n if (status === 'success') return colorize('✓', ANSIColors.GREEN);\n if (status === 'warning') return colorize('⚠', ANSIColors.YELLOW);\n return colorize('✗', ANSIColors.RED);\n};\n\nconst scoreColor = (score: number) => {\n if (score >= 80) return ANSIColors.GREEN;\n if (score >= 50) return ANSIColors.YELLOW;\n return ANSIColors.RED;\n};\n\n/**\n * Scan a website for i18n/SEO health and bundle weight, printing a formatted\n * report (or JSON with `--json`).\n *\n * @example\n * ```sh\n * npx intlayer scan https://intlayer.org\n * ```\n */\nexport const scan = async (\n url: string,\n options: ScanCommandOptions = {}\n): Promise<void> => {\n const configuration = getConfiguration(options.configOptions);\n const appLogger = getAppLogger(configuration);\n\n const result = await scanWebsite(url, { deep: options.deep });\n\n if (options.json) {\n appLogger(JSON.stringify(result, null, 2));\n return;\n }\n\n appLogger(\n `\\n🔍 Scanned ${colorize(result.url, ANSIColors.GREY_LIGHT)} ${colorize(\n `(${result.mode} mode)`,\n ANSIColors.GREY\n )}\\n`\n );\n\n appLogger(\n `Score: ${colorize(`${result.score}/100`, scoreColor(result.score))}`\n );\n appLogger(\n `Page size: ${colorize(formatSize(result.totalPageSize), ANSIColors.BLUE)} ${colorize(\n `(HTML ${formatSize(result.htmlSize)})`,\n ANSIColors.GREY\n )}`\n );\n if (result.locales.length > 0) {\n appLogger(\n `Locales: ${colorize(result.locales.join(', '), ANSIColors.GREEN)}`\n );\n }\n\n appLogger('\\nChecks:');\n for (const event of result.events) {\n const type = event.type.split('\\\\')[0];\n\n if (!type) continue;\n\n const label = checkLabels[type] ?? type;\n appLogger(` ${statusIcon(event.status)} ${label}`);\n }\n\n if (result.bundle && result.bundle.totalLocaleSize > 0) {\n const { bundle } = result;\n appLogger('\\nBundle locale weight:');\n appLogger(\n ` Translations shipped: ${colorize(formatSize(bundle.totalLocaleSize), ANSIColors.BLUE)}`\n );\n appLogger(\n ` Unused (other locales): ${colorize(\n `${formatSize(bundle.totalUnusedLocaleSize)} (${bundle.unusedPercentOfLocale}%)`,\n bundle.unusedPercentOfLocale > 30\n ? ANSIColors.RED\n : bundle.unusedPercentOfLocale > 0\n ? ANSIColors.YELLOW\n : ANSIColors.GREEN\n )}`\n );\n }\n};\n"],"mappings":";;;;;;;AAgBA,MAAM,cAAsC;CAC1C,sBAAsB;CACtB,iCAAiC;CACjC,wBAAwB;CACxB,kCAAkC;CAClC,uBAAuB;CACvB,qBAAqB;CACrB,cAAc;CACd,aAAa;CACb,kBAAkB;CAClB,cAAc;CACd,uBAAuB;CACvB,iBAAiB;CACjB,yBAAyB;CACzB,mBAAmB;CACnB,yBAAyB;CAC1B;AAED,MAAM,cAAc,WAAwC;AAC1D,KAAI,WAAW,UAAW,QAAO,SAAS,KAAK,WAAW,MAAM;AAChE,KAAI,WAAW,UAAW,QAAO,SAAS,KAAK,WAAW,OAAO;AACjE,QAAO,SAAS,KAAK,WAAW,IAAI;;AAGtC,MAAM,cAAc,UAAkB;AACpC,KAAI,SAAS,GAAI,QAAO,WAAW;AACnC,KAAI,SAAS,GAAI,QAAO,WAAW;AACnC,QAAO,WAAW;;;;;;;;;;;AAYpB,MAAa,OAAO,OAClB,KACA,UAA8B,EAAE,KACd;CAElB,MAAM,YAAY,aADI,iBAAiB,QAAQ,cACH,CAAC;CAE7C,MAAM,SAAS,MAAM,YAAY,KAAK,EAAE,MAAM,QAAQ,MAAM,CAAC;AAE7D,KAAI,QAAQ,MAAM;AAChB,YAAU,KAAK,UAAU,QAAQ,MAAM,EAAE,CAAC;AAC1C;;AAGF,WACE,gBAAgB,SAAS,OAAO,KAAK,WAAW,WAAW,CAAC,GAAG,SAC7D,IAAI,OAAO,KAAK,SAChB,WAAW,KACZ,CAAC,IACH;AAED,WACE,UAAU,SAAS,GAAG,OAAO,MAAM,OAAO,WAAW,OAAO,MAAM,CAAC,GACpE;AACD,WACE,cAAc,SAAS,WAAW,OAAO,cAAc,EAAE,WAAW,KAAK,CAAC,GAAG,SAC3E,SAAS,WAAW,OAAO,SAAS,CAAC,IACrC,WAAW,KACZ,GACF;AACD,KAAI,OAAO,QAAQ,SAAS,EAC1B,WACE,YAAY,SAAS,OAAO,QAAQ,KAAK,KAAK,EAAE,WAAW,MAAM,GAClE;AAGH,WAAU,YAAY;AACtB,MAAK,MAAM,SAAS,OAAO,QAAQ;EACjC,MAAM,OAAO,MAAM,KAAK,MAAM,KAAK,CAAC;AAEpC,MAAI,CAAC,KAAM;EAEX,MAAM,QAAQ,YAAY,SAAS;AACnC,YAAU,KAAK,WAAW,MAAM,OAAO,CAAC,GAAG,QAAQ;;AAGrD,KAAI,OAAO,UAAU,OAAO,OAAO,kBAAkB,GAAG;EACtD,MAAM,EAAE,WAAW;AACnB,YAAU,0BAA0B;AACpC,YACE,2BAA2B,SAAS,WAAW,OAAO,gBAAgB,EAAE,WAAW,KAAK,GACzF;AACD,YACE,6BAA6B,SAC3B,GAAG,WAAW,OAAO,sBAAsB,CAAC,IAAI,OAAO,sBAAsB,KAC7E,OAAO,wBAAwB,KAC3B,WAAW,MACX,OAAO,wBAAwB,IAC7B,WAAW,SACX,WAAW,MAClB,GACF"}
|
package/dist/esm/searchDoc.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { logConfigDetails } from "@intlayer/chokidar/cli";
|
|
2
1
|
import { colorizeKey, colorizeNumber, getAppLogger } from "@intlayer/config/logger";
|
|
3
2
|
import { getConfiguration } from "@intlayer/config/node";
|
|
3
|
+
import { logConfigDetails } from "@intlayer/engine/cli";
|
|
4
4
|
import { getSearchAPI } from "@intlayer/api";
|
|
5
5
|
|
|
6
6
|
//#region src/searchDoc.ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"searchDoc.mjs","names":[],"sources":["../../src/searchDoc.ts"],"sourcesContent":["import { getSearchAPI } from '@intlayer/api';\nimport {
|
|
1
|
+
{"version":3,"file":"searchDoc.mjs","names":[],"sources":["../../src/searchDoc.ts"],"sourcesContent":["import { getSearchAPI } from '@intlayer/api';\nimport {\n colorizeKey,\n colorizeNumber,\n getAppLogger,\n} from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport { logConfigDetails } from '@intlayer/engine/cli';\n\ninterface SearchDocOptions {\n query: string;\n limit?: number;\n configOptions?: GetConfigurationOptions;\n}\n\nexport const searchDoc = async ({\n query,\n limit = 10,\n configOptions,\n}: SearchDocOptions) => {\n const config = getConfiguration(configOptions);\n logConfigDetails(configOptions);\n\n const appLogger = getAppLogger(config);\n\n try {\n const { searchDoc } = getSearchAPI(undefined, config);\n const response = await searchDoc({\n input: query,\n limit: limit.toString(),\n returnContent: 'true',\n });\n\n if (!response.data || !Array.isArray(response.data)) {\n appLogger('No relevant chunks found.');\n return;\n }\n\n const chunks = response.data;\n\n appLogger(`Found ${colorizeNumber(chunks.length)} relevant chunks:`);\n\n chunks.forEach((chunk: any) => {\n appLogger('---');\n appLogger(`${colorizeKey('File')}: ${chunk.fileKey}`);\n appLogger(`${colorizeKey('Title')}: ${chunk.docName}`);\n appLogger(`${colorizeKey('URL')}: ${chunk.docUrl}`);\n appLogger(`${colorizeKey('Chunk')}: ${chunk.chunkNumber}`);\n appLogger(`${colorizeKey('Content')}:`);\n appLogger(chunk.content);\n });\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : 'An unknown error occurred';\n appLogger(`Search failed: ${errorMessage}`, { level: 'error' });\n }\n};\n"],"mappings":";;;;;;AAkBA,MAAa,YAAY,OAAO,EAC9B,OACA,QAAQ,IACR,oBACsB;CACtB,MAAM,SAAS,iBAAiB,cAAc;AAC9C,kBAAiB,cAAc;CAE/B,MAAM,YAAY,aAAa,OAAO;AAEtC,KAAI;EACF,MAAM,EAAE,cAAc,aAAa,QAAW,OAAO;EACrD,MAAM,WAAW,MAAM,UAAU;GAC/B,OAAO;GACP,OAAO,MAAM,UAAU;GACvB,eAAe;GAChB,CAAC;AAEF,MAAI,CAAC,SAAS,QAAQ,CAAC,MAAM,QAAQ,SAAS,KAAK,EAAE;AACnD,aAAU,4BAA4B;AACtC;;EAGF,MAAM,SAAS,SAAS;AAExB,YAAU,SAAS,eAAe,OAAO,OAAO,CAAC,mBAAmB;AAEpE,SAAO,SAAS,UAAe;AAC7B,aAAU,MAAM;AAChB,aAAU,GAAG,YAAY,OAAO,CAAC,IAAI,MAAM,UAAU;AACrD,aAAU,GAAG,YAAY,QAAQ,CAAC,IAAI,MAAM,UAAU;AACtD,aAAU,GAAG,YAAY,MAAM,CAAC,IAAI,MAAM,SAAS;AACnD,aAAU,GAAG,YAAY,QAAQ,CAAC,IAAI,MAAM,cAAc;AAC1D,aAAU,GAAG,YAAY,UAAU,CAAC,GAAG;AACvC,aAAU,MAAM,QAAQ;IACxB;UACK,OAAO;AAGd,YAAU,kBADR,iBAAiB,QAAQ,MAAM,UAAU,+BACC,EAAE,OAAO,SAAS,CAAC"}
|