@intlayer/cli 7.5.9 → 7.5.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -2
- package/dist/cjs/ci.cjs +73 -0
- package/dist/cjs/ci.cjs.map +1 -0
- package/dist/cjs/cli.cjs +37 -2
- package/dist/cjs/cli.cjs.map +1 -1
- package/dist/cjs/editor.cjs +1 -1
- package/dist/cjs/listContentDeclaration.cjs +6 -2
- package/dist/cjs/listContentDeclaration.cjs.map +1 -1
- package/dist/cjs/listProjects.cjs +28 -0
- package/dist/cjs/listProjects.cjs.map +1 -0
- package/dist/cjs/pushConfig.cjs +1 -1
- package/dist/cjs/pushConfig.cjs.map +1 -1
- package/dist/cjs/translateDoc.cjs +41 -10
- package/dist/cjs/translateDoc.cjs.map +1 -1
- package/dist/cjs/utils/checkAccess.cjs +32 -6
- package/dist/cjs/utils/checkAccess.cjs.map +1 -1
- package/dist/cjs/utils/checkConfigConsistency.cjs +24 -0
- package/dist/cjs/utils/checkConfigConsistency.cjs.map +1 -0
- package/dist/cjs/utils/setupAI.cjs +20 -11
- package/dist/cjs/utils/setupAI.cjs.map +1 -1
- package/dist/esm/auth/login.mjs +16 -16
- package/dist/esm/auth/login.mjs.map +1 -1
- package/dist/esm/ci.mjs +72 -0
- package/dist/esm/ci.mjs.map +1 -0
- package/dist/esm/cli.mjs +37 -2
- package/dist/esm/cli.mjs.map +1 -1
- package/dist/esm/editor.mjs +1 -1
- package/dist/esm/listContentDeclaration.mjs +6 -2
- package/dist/esm/listContentDeclaration.mjs.map +1 -1
- package/dist/esm/listProjects.mjs +27 -0
- package/dist/esm/listProjects.mjs.map +1 -0
- package/dist/esm/pull.mjs +6 -6
- package/dist/esm/pull.mjs.map +1 -1
- package/dist/esm/push/push.mjs +7 -7
- 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/translateDoc.mjs +41 -10
- package/dist/esm/translateDoc.mjs.map +1 -1
- package/dist/esm/utils/checkAccess.mjs +32 -6
- package/dist/esm/utils/checkAccess.mjs.map +1 -1
- package/dist/esm/utils/checkConfigConsistency.mjs +23 -0
- package/dist/esm/utils/checkConfigConsistency.mjs.map +1 -0
- package/dist/esm/utils/setupAI.mjs +20 -11
- package/dist/esm/utils/setupAI.mjs.map +1 -1
- package/dist/types/ci.d.ts +5 -0
- package/dist/types/ci.d.ts.map +1 -0
- package/dist/types/cli.d.ts.map +1 -1
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/listContentDeclaration.d.ts +2 -0
- package/dist/types/listContentDeclaration.d.ts.map +1 -1
- package/dist/types/listProjects.d.ts +11 -0
- package/dist/types/listProjects.d.ts.map +1 -0
- package/dist/types/pull.d.ts.map +1 -1
- package/dist/types/translateDoc.d.ts +10 -1
- package/dist/types/translateDoc.d.ts.map +1 -1
- package/dist/types/utils/checkAccess.d.ts +2 -2
- package/dist/types/utils/checkAccess.d.ts.map +1 -1
- package/dist/types/utils/checkConfigConsistency.d.ts +13 -0
- package/dist/types/utils/checkConfigConsistency.d.ts.map +1 -0
- package/dist/types/utils/setupAI.d.ts.map +1 -1
- package/package.json +11 -10
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translateDoc.cjs","names":["readAsset","ANSIColors","chunkText","chunkInference","fixChunkStartEndChars","docList: string[]","setupAI","getOutputFilePath","checkFileModifiedRange"],"sources":["../../src/translateDoc.ts"],"sourcesContent":["import { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { dirname, join, relative } from 'node:path';\nimport { readAsset } from 'utils:asset';\nimport type { AIConfig, AIOptions } from '@intlayer/ai';\nimport {\n formatLocale,\n formatPath,\n getChunk,\n type ListGitFilesOptions,\n listGitFiles,\n parallelize,\n} from '@intlayer/chokidar';\nimport {\n ANSIColors,\n colon,\n colorize,\n colorizeNumber,\n type GetConfigurationOptions,\n getAppLogger,\n getConfiguration,\n retryManager,\n} from '@intlayer/config';\nimport type { IntlayerConfig, Locale } from '@intlayer/types';\nimport fg from 'fast-glob';\nimport { chunkText } from './utils/calculateChunks';\nimport { checkFileModifiedRange } from './utils/checkFileModifiedRange';\nimport { chunkInference } from './utils/chunkInference';\nimport { fixChunkStartEndChars } from './utils/fixChunkStartEndChars';\nimport { getOutputFilePath } from './utils/getOutputFilePath';\nimport { type AIClient, setupAI } from './utils/setupAI';\n\n/**\n * Translate a single file for a given locale\n */\nexport const translateFile = async (\n baseFilePath: string,\n outputFilePath: string,\n locale: Locale,\n baseLocale: Locale,\n configuration: IntlayerConfig,\n aiOptions?: AIOptions,\n customInstructions?: string,\n aiClient?: AIClient,\n aiConfig?: AIConfig\n) => {\n try {\n const appLogger = getAppLogger(configuration, {\n config: {\n prefix: '',\n },\n });\n\n // Determine the target locale file path\n const fileContent = await readFile(baseFilePath, 'utf-8');\n\n let fileResultContent = fileContent;\n\n // Prepare the base prompt for ChatGPT\n const basePrompt = readAsset('./prompts/TRANSLATE_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\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 // 1. Chunk the file by number of lines instead of characters\n const chunks = chunkText(fileContent);\n appLogger(\n `${filePrefix}Base file splitted into ${colorizeNumber(chunks.length)} chunks`\n );\n\n for await (const [i, chunk] of chunks.entries()) {\n const isFirstChunk = i === 0;\n\n // Build the chunk-specific prompt\n const getPrevChunkPrompt = () =>\n `**CHUNK ${i} of ${chunks.length}** that has been translated in ${formatLocale(locale)}:\\n` +\n `///chunkStart///` +\n getChunk(fileResultContent, chunks[i - 1]) +\n `///chunkEnd///`;\n\n const getBaseChunkContextPrompt = () =>\n `**CHUNK ${i + 1} to ${Math.min(i + 3, chunks.length)} of ${chunks.length}** is the base chunk in ${formatLocale(baseLocale, false)} as reference.\\n` +\n `///chunksStart///` +\n (chunks[i - 1]?.content ?? '') +\n chunks[i].content +\n (chunks[i + 1]?.content ?? '') +\n `///chunksEnd///`;\n\n const fileToTranslateCurrentChunk = chunk.content;\n\n // Make the actual translation call\n const chunkTranslation = await retryManager(async () => {\n const result = await chunkInference(\n [\n { role: 'system', content: basePrompt },\n\n { role: 'system', content: getBaseChunkContextPrompt() },\n ...(isFirstChunk\n ? []\n : [{ role: 'system', content: getPrevChunkPrompt() } as const]),\n {\n role: 'system',\n content: `The next user message will be the **CHUNK ${colorizeNumber(i + 1)} of ${colorizeNumber(chunks.length)}** in ${formatLocale(baseLocale, false)} to translate in ${formatLocale(locale, false)}:`,\n },\n { role: 'user', content: fileToTranslateCurrentChunk },\n ],\n aiOptions,\n configuration,\n aiClient,\n aiConfig\n );\n\n appLogger(\n [\n `${prefix}`,\n `${ANSIColors.GREY_DARK}[Chunk `,\n colorizeNumber(i + 1),\n `${ANSIColors.GREY_DARK} of `,\n colorizeNumber(chunks.length),\n `${ANSIColors.GREY_DARK}] →${ANSIColors.RESET} `,\n `${colorizeNumber(result.tokenUsed)} tokens used`,\n ].join('')\n );\n\n const fixedTranslatedChunkResult = fixChunkStartEndChars(\n result?.fileContent,\n fileToTranslateCurrentChunk\n );\n\n return fixedTranslatedChunkResult;\n })();\n\n // Replace the chunk in the file content\n fileResultContent = fileResultContent.replace(\n fileToTranslateCurrentChunk,\n chunkTranslation\n );\n }\n\n // 4. Write the final translation to the appropriate file path\n mkdirSync(dirname(outputFilePath), { recursive: true });\n writeFileSync(outputFilePath, fileResultContent);\n\n const relativePath = relative(\n configuration.content.baseDir,\n outputFilePath\n );\n\n appLogger(\n `${colorize('✔', ANSIColors.GREEN)} File ${formatPath(relativePath)} created/updated successfully.`\n );\n } catch (error) {\n console.error(error);\n }\n};\n\ntype TranslateDocOptions = {\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\n/**\n * Main translate function: scans all .md files in \"en/\" (unless you specified DOC_LIST),\n * then translates them to each locale in LOCALE_LIST.\n */\nexport const translateDoc = 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}: TranslateDocOptions) => {\n const configuration = getConfiguration(configOptions);\n const appLogger = getAppLogger(configuration);\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 const aiResult = await setupAI(configuration, aiOptions);\n\n if (!aiResult?.hasAIAccess) return;\n\n const { aiClient, aiConfig } = aiResult;\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 `Translating ${colorizeNumber(locales.length)} locales: [ ${formatLocale(locales)} ]`\n );\n\n appLogger(`Translating ${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 `Translating file: ${formatPath(docPath)} to ${formatLocale(locale)}`\n );\n\n const absoluteBaseFilePath = join(configuration.content.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.content.baseDir,\n outputFilePath\n );\n appLogger(\n `${colorize('⊘', ANSIColors.YELLOW)} File ${formatPath(relativePath)} already exists, skipping.`\n );\n return;\n }\n\n // check if the file exist, otherwise create it\n if (!existsSync(outputFilePath)) {\n const relativePath = relative(\n configuration.content.baseDir,\n outputFilePath\n );\n appLogger(\n `File ${formatPath(relativePath)} does not exist, creating it...`\n );\n mkdirSync(dirname(outputFilePath), { recursive: true });\n writeFileSync(outputFilePath, '');\n }\n\n const fileModificationData = checkFileModifiedRange(outputFilePath, {\n skipIfModifiedBefore,\n skipIfModifiedAfter,\n });\n\n if (fileModificationData.isSkipped) {\n appLogger(fileModificationData.message);\n return;\n }\n\n await translateFile(\n absoluteBaseFilePath,\n outputFilePath,\n locale as Locale,\n baseLocale,\n configuration,\n aiOptions,\n customInstructions,\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":"translateDoc.cjs","names":["ANSIColors","chunkText","translatedParts: string[]","readAsset","chunkInference","fixChunkStartEndChars","error: any","docList: string[]","setupAI","errorState: ErrorState","getOutputFilePath","checkFileModifiedRange"],"sources":["../../src/translateDoc.ts"],"sourcesContent":["import { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { dirname, join, relative } from 'node:path';\nimport { readAsset } from 'utils:asset';\nimport type { AIConfig, AIOptions } from '@intlayer/ai';\nimport {\n formatLocale,\n formatPath,\n getChunk,\n type ListGitFilesOptions,\n listGitFiles,\n parallelize,\n} from '@intlayer/chokidar';\nimport {\n ANSIColors,\n colon,\n colorize,\n colorizeNumber,\n type GetConfigurationOptions,\n getAppLogger,\n getConfiguration,\n retryManager,\n} from '@intlayer/config';\nimport type { IntlayerConfig, Locale } from '@intlayer/types';\nimport fg from 'fast-glob';\nimport { chunkText } from './utils/calculateChunks';\nimport { checkFileModifiedRange } from './utils/checkFileModifiedRange';\nimport { chunkInference } from './utils/chunkInference';\nimport { fixChunkStartEndChars } from './utils/fixChunkStartEndChars';\nimport { getOutputFilePath } from './utils/getOutputFilePath';\nimport { type AIClient, setupAI } from './utils/setupAI';\n\n/**\n * Shared error state for circuit breaker pattern\n */\ntype ErrorState = {\n count: number;\n maxErrors: number;\n shouldStop: boolean;\n};\n\n/**\n * Translate a single file for a given locale\n * Returns TRUE if successful, FALSE if failed/skipped\n */\nexport const translateFile = async (\n baseFilePath: string,\n outputFilePath: string,\n locale: Locale,\n baseLocale: Locale,\n configuration: IntlayerConfig,\n errorState: ErrorState,\n aiOptions?: AIOptions,\n customInstructions?: string,\n aiClient?: AIClient,\n aiConfig?: AIConfig\n): Promise<boolean> => {\n // Circuit Breaker Check\n if (errorState.shouldStop) {\n return false;\n }\n\n const appLogger = getAppLogger(configuration, {\n config: {\n prefix: '',\n },\n });\n\n try {\n // Read File\n const fileContent = await readFile(baseFilePath, 'utf-8');\n\n let fileResultContent = fileContent;\n\n // Prepare formatting\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\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 // Chunking\n const chunks = chunkText(fileContent);\n appLogger(\n `${filePrefix}Base file splitted into ${colorizeNumber(chunks.length)} chunks`\n );\n\n // Instead of replacing content in a string, we push to an array\n const translatedParts: string[] = [];\n\n // Prepare Base Prompt\n const basePrompt = readAsset('./prompts/TRANSLATE_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 // Iterate and Translate\n for await (const [i, chunk] of chunks.entries()) {\n // Circuit Breaker Check inside the loop (in case error happened elsewhere while processing)\n if (errorState.shouldStop) return false;\n\n const isFirstChunk = i === 0;\n const fileToTranslateCurrentChunk = chunk.content;\n\n // Build the chunk-specific prompt\n const getPrevChunkPrompt = () =>\n `**CHUNK ${i} of ${chunks.length}** that has been translated in ${formatLocale(locale)}:\\n` +\n `///chunkStart///` +\n getChunk(translatedParts.join(''), chunks[i - 1]) +\n `///chunkEnd///`;\n\n const getBaseChunkContextPrompt = () =>\n `**CHUNK ${i + 1} to ${Math.min(i + 3, chunks.length)} of ${chunks.length}** is the base chunk in ${formatLocale(baseLocale, false)} as reference.\\n` +\n `///chunksStart///` +\n (chunks[i - 1]?.content ?? '') +\n chunks[i].content +\n (chunks[i + 1]?.content ?? '') +\n `///chunksEnd///`;\n\n // Make the actual translation call\n const chunkTranslation = await retryManager(async () => {\n const result = await chunkInference(\n [\n { role: 'system', content: basePrompt },\n { role: 'system', content: getBaseChunkContextPrompt() },\n ...(isFirstChunk\n ? []\n : [{ role: 'system', content: getPrevChunkPrompt() } as const]),\n {\n role: 'system',\n content: `The next user message will be the **CHUNK ${colorizeNumber(i + 1)} of ${colorizeNumber(chunks.length)}** in ${formatLocale(baseLocale, false)} to translate in ${formatLocale(locale, false)}.\\n\n STRICT INSTRUCTIONS:\n 1. Translate ONLY the content of this specific chunk. \n 2. Do NOT repeat the content from the previous chunk.\n 3. Start the translation exactly where the previous chunk ended.\n 4. Preserve all code blocks and formatting exactly.`,\n },\n { role: 'user', content: fileToTranslateCurrentChunk },\n ],\n aiOptions,\n configuration,\n aiClient,\n aiConfig\n );\n\n appLogger(\n [\n `${prefix}`,\n `${ANSIColors.GREY_DARK}[Chunk `,\n colorizeNumber(i + 1),\n `${ANSIColors.GREY_DARK} of `,\n colorizeNumber(chunks.length),\n `${ANSIColors.GREY_DARK}] →${ANSIColors.RESET} `,\n `${colorizeNumber(result.tokenUsed)} tokens used`,\n ].join('')\n );\n\n const fixedTranslatedChunkResult = fixChunkStartEndChars(\n result?.fileContent,\n fileToTranslateCurrentChunk\n );\n\n return fixedTranslatedChunkResult;\n })();\n\n // Replace the chunk in the file content\n fileResultContent = fileResultContent.replace(\n fileToTranslateCurrentChunk,\n chunkTranslation\n );\n }\n\n // Write final file by joining parts\n const finalContent = translatedParts.join('');\n\n mkdirSync(dirname(outputFilePath), { recursive: true });\n writeFileSync(outputFilePath, finalContent);\n\n const relativePath = relative(\n configuration.content.baseDir,\n outputFilePath\n );\n\n appLogger(\n `${colorize('✔', ANSIColors.GREEN)} File ${formatPath(relativePath)} created/updated successfully.`\n );\n\n return true; // Success\n } catch (error: any) {\n // Handle Errors and Update State\n\n errorState.count++;\n\n // If it's an Access Denied error, stop immediately (set count to max)\n const errorString = JSON.stringify(error);\n const errorMessage = error?.message ?? '';\n if (\n errorString.includes('AI_ACCESS_DENIED') ||\n errorMessage.includes('Access keys') ||\n errorMessage.includes('Access denied') ||\n errorMessage.includes('Invalid Access keys')\n ) {\n errorState.count = errorState.maxErrors + 1;\n appLogger(\n `${colorize('✖', ANSIColors.RED)} Critical Authentication Error. Aborting all tasks.`\n );\n }\n\n if (errorState.count >= errorState.maxErrors && !errorState.shouldStop) {\n errorState.shouldStop = true;\n appLogger(\n `${colorize('✖', ANSIColors.RED)} Too many errors (${errorState.count}). Stopping process.`\n );\n }\n\n return false; // Failed\n }\n};\n\ntype TranslateDocOptions = {\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\n/**\n * Main translate function: scans all .md files in \"en/\" (unless you specified DOC_LIST),\n * then translates them to each locale in LOCALE_LIST.\n */\nexport const translateDoc = 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}: TranslateDocOptions) => {\n const configuration = getConfiguration(configOptions);\n const appLogger = getAppLogger(configuration);\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 const aiResult = await setupAI(configuration, aiOptions);\n\n if (!aiResult?.hasAIAccess) return;\n\n const { aiClient, aiConfig } = aiResult;\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 `Translating ${colorizeNumber(locales.length)} locales: [ ${formatLocale(locales)} ]`\n );\n\n appLogger(`Translating ${colorizeNumber(docList.length)} files:`);\n docList.forEach((path) => {\n appLogger(` - ${formatPath(path)}`);\n });\n\n // Initialize Error State\n const MAX_ALLOWED_ERRORS = 5;\n const errorState: ErrorState = {\n count: 0,\n maxErrors: MAX_ALLOWED_ERRORS,\n shouldStop: false,\n };\n\n // Create all tasks to be processed\n const allTasks = docList.flatMap((docPath) =>\n locales.map((locale) => async () => {\n // Early exit if too many errors\n if (errorState.shouldStop) return;\n\n appLogger(\n `Translating file: ${formatPath(docPath)} to ${formatLocale(locale)}`\n );\n\n const absoluteBaseFilePath = join(configuration.content.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.content.baseDir,\n outputFilePath\n );\n appLogger(\n `${colorize('⊘', ANSIColors.YELLOW)} File ${formatPath(relativePath)} already exists, skipping.`\n );\n return;\n }\n\n // check if the file exist, otherwise create it\n if (!existsSync(outputFilePath)) {\n const relativePath = relative(\n configuration.content.baseDir,\n outputFilePath\n );\n appLogger(\n `File ${formatPath(relativePath)} does not exist, creating it...`\n );\n mkdirSync(dirname(outputFilePath), { recursive: true });\n writeFileSync(outputFilePath, '');\n }\n\n const fileModificationData = checkFileModifiedRange(outputFilePath, {\n skipIfModifiedBefore,\n skipIfModifiedAfter,\n });\n\n if (fileModificationData.isSkipped) {\n appLogger(fileModificationData.message);\n return;\n }\n\n // Call translateFile with errorState\n await translateFile(\n absoluteBaseFilePath,\n outputFilePath,\n locale as Locale,\n baseLocale,\n configuration,\n errorState,\n aiOptions,\n customInstructions,\n aiClient,\n aiConfig\n );\n })\n );\n\n await parallelize(\n allTasks,\n (task) => task(),\n nbSimultaneousFileProcessed ?? 3\n );\n\n if (errorState.count > 0) {\n appLogger(\n `Process finished with ${colorizeNumber(errorState.count)} error${errorState.count === 1 ? '' : 's'}.`\n );\n }\n};\n"],"mappings":"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"}
|
|
@@ -1,27 +1,53 @@
|
|
|
1
1
|
const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
|
|
2
|
+
const require_utils_checkConfigConsistency = require('./checkConfigConsistency.cjs');
|
|
2
3
|
let _intlayer_api = require("@intlayer/api");
|
|
3
4
|
let _intlayer_config = require("@intlayer/config");
|
|
4
5
|
|
|
5
6
|
//#region src/utils/checkAccess.ts
|
|
6
|
-
const checkCMSAuth = async (configuration) => {
|
|
7
|
+
const checkCMSAuth = async (configuration, shouldCheckConfigConsistency = true) => {
|
|
7
8
|
const appLogger = (0, _intlayer_config.getAppLogger)(configuration, { config: { prefix: "" } });
|
|
8
9
|
if (!(configuration.editor.clientId && configuration.editor.clientSecret)) {
|
|
9
|
-
appLogger(
|
|
10
|
+
appLogger([
|
|
11
|
+
"CMS auth not provided. You can either retreive the CMS access key on",
|
|
12
|
+
(0, _intlayer_config.colorize)("https://intlayer.org/dahboard", _intlayer_config.ANSIColors.GREY),
|
|
13
|
+
(0, _intlayer_config.colorize)("(see doc:", _intlayer_config.ANSIColors.GREY_DARK),
|
|
14
|
+
(0, _intlayer_config.colorize)("https://intlayer.org/doc/concept/cms", _intlayer_config.ANSIColors.GREY),
|
|
15
|
+
(0, _intlayer_config.colorize)(")", _intlayer_config.ANSIColors.GREY_DARK),
|
|
16
|
+
"."
|
|
17
|
+
], { level: "error" });
|
|
10
18
|
return false;
|
|
11
19
|
}
|
|
12
20
|
const intlayerAPI = (0, _intlayer_api.getIntlayerAPIProxy)(void 0, configuration);
|
|
13
21
|
try {
|
|
14
|
-
await intlayerAPI.oAuth.getOAuth2AccessToken();
|
|
22
|
+
const project = (await intlayerAPI.oAuth.getOAuth2AccessToken()).data?.project;
|
|
23
|
+
if (!project) {
|
|
24
|
+
appLogger("Project not found");
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
if (project.configuration && shouldCheckConfigConsistency) try {
|
|
28
|
+
require_utils_checkConfigConsistency.checkConfigConsistency(project.configuration, configuration);
|
|
29
|
+
} catch {
|
|
30
|
+
appLogger([
|
|
31
|
+
"Remote configuration is not up to date. The project configuration does not match the local configuration.",
|
|
32
|
+
"You can push the configuration by running",
|
|
33
|
+
(0, _intlayer_config.colorize)("npx intlayer push", _intlayer_config.ANSIColors.CYAN),
|
|
34
|
+
(0, _intlayer_config.colorize)("(see doc:", _intlayer_config.ANSIColors.GREY_DARK),
|
|
35
|
+
(0, _intlayer_config.colorize)("https://intlayer.org/doc/concept/cli/push", _intlayer_config.ANSIColors.GREY),
|
|
36
|
+
(0, _intlayer_config.colorize)(")", _intlayer_config.ANSIColors.GREY_DARK),
|
|
37
|
+
"."
|
|
38
|
+
], { level: "warn" });
|
|
39
|
+
}
|
|
15
40
|
} catch (error) {
|
|
16
41
|
appLogger((0, _intlayer_config.extractErrorMessage)(error), { level: "error" });
|
|
17
42
|
return false;
|
|
18
43
|
}
|
|
19
44
|
return true;
|
|
20
45
|
};
|
|
21
|
-
const checkAIAccess = async (configuration, aiOptions) => {
|
|
46
|
+
const checkAIAccess = async (configuration, aiOptions, shouldCheckConfigConsistency = true) => {
|
|
22
47
|
const appLogger = (0, _intlayer_config.getAppLogger)(configuration);
|
|
23
48
|
const hasCMSAuth = Boolean(configuration.editor.clientId && configuration.editor.clientSecret);
|
|
24
|
-
|
|
49
|
+
const isOllama = configuration.ai?.provider === "ollama" || aiOptions?.provider === "ollama";
|
|
50
|
+
if (Boolean(configuration.ai?.apiKey || aiOptions?.apiKey) || isOllama) return true;
|
|
25
51
|
if (!hasCMSAuth) {
|
|
26
52
|
appLogger([
|
|
27
53
|
"AI options or API key not provided. You can either retreive the CMS access key on",
|
|
@@ -37,7 +63,7 @@ const checkAIAccess = async (configuration, aiOptions) => {
|
|
|
37
63
|
], { level: "error" });
|
|
38
64
|
return false;
|
|
39
65
|
}
|
|
40
|
-
return await checkCMSAuth(configuration);
|
|
66
|
+
return await checkCMSAuth(configuration, shouldCheckConfigConsistency);
|
|
41
67
|
};
|
|
42
68
|
|
|
43
69
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkAccess.cjs","names":["ANSIColors"],"sources":["../../../src/utils/checkAccess.ts"],"sourcesContent":["import type { AIOptions } from '@intlayer/api';\nimport { getIntlayerAPIProxy } from '@intlayer/api';\nimport {\n ANSIColors,\n colorize,\n extractErrorMessage,\n getAppLogger,\n} from '@intlayer/config';\nimport type { IntlayerConfig } from '@intlayer/types';\n\nexport const checkCMSAuth = async (\n configuration: IntlayerConfig\n): Promise<boolean> => {\n const appLogger = getAppLogger(configuration, {\n config: {\n prefix: '',\n },\n });\n\n const hasCMSAuth =\n configuration.editor.clientId && configuration.editor.clientSecret;\n if (!hasCMSAuth) {\n appLogger('CMS auth not provided.', {\n
|
|
1
|
+
{"version":3,"file":"checkAccess.cjs","names":["ANSIColors"],"sources":["../../../src/utils/checkAccess.ts"],"sourcesContent":["import type { AIOptions } from '@intlayer/api';\nimport { getIntlayerAPIProxy } from '@intlayer/api';\nimport {\n ANSIColors,\n colorize,\n extractErrorMessage,\n getAppLogger,\n} from '@intlayer/config';\nimport type { IntlayerConfig } from '@intlayer/types';\nimport { checkConfigConsistency } from './checkConfigConsistency';\n\nexport const checkCMSAuth = async (\n configuration: IntlayerConfig,\n shouldCheckConfigConsistency: boolean = true\n): Promise<boolean> => {\n const appLogger = getAppLogger(configuration, {\n config: {\n prefix: '',\n },\n });\n\n const hasCMSAuth =\n configuration.editor.clientId && configuration.editor.clientSecret;\n if (!hasCMSAuth) {\n appLogger(\n [\n 'CMS auth not provided. You can either retreive the CMS access key on',\n colorize('https://intlayer.org/dahboard', ANSIColors.GREY),\n colorize('(see doc:', ANSIColors.GREY_DARK),\n colorize('https://intlayer.org/doc/concept/cms', ANSIColors.GREY),\n colorize(')', ANSIColors.GREY_DARK),\n '.',\n ],\n {\n level: 'error',\n }\n );\n\n return false;\n }\n const intlayerAPI = getIntlayerAPIProxy(undefined, configuration);\n\n try {\n const result = await intlayerAPI.oAuth.getOAuth2AccessToken();\n\n const project = result.data?.project;\n\n if (!project) {\n appLogger('Project not found');\n\n return true;\n }\n\n if (project.configuration && shouldCheckConfigConsistency) {\n try {\n // Recursively check if project.configuration (subset) matches configuration (superset)\n checkConfigConsistency(project.configuration, configuration);\n } catch {\n appLogger(\n [\n 'Remote configuration is not up to date. The project configuration does not match the local configuration.',\n 'You can push the configuration by running',\n colorize('npx intlayer push', ANSIColors.CYAN),\n colorize('(see doc:', ANSIColors.GREY_DARK),\n colorize(\n 'https://intlayer.org/doc/concept/cli/push',\n ANSIColors.GREY\n ),\n colorize(')', ANSIColors.GREY_DARK),\n '.',\n ],\n {\n level: 'warn',\n }\n );\n }\n }\n } catch (error) {\n const message = extractErrorMessage(error);\n\n appLogger(message, {\n level: 'error',\n });\n return false;\n }\n\n return true;\n};\n\nexport const checkAIAccess = async (\n configuration: IntlayerConfig,\n aiOptions?: AIOptions,\n shouldCheckConfigConsistency: boolean = true\n): Promise<boolean> => {\n const appLogger = getAppLogger(configuration);\n\n const hasCMSAuth = Boolean(\n configuration.editor.clientId && configuration.editor.clientSecret\n );\n const isOllama =\n configuration.ai?.provider === 'ollama' || aiOptions?.provider === 'ollama';\n const hasHisOwnAIAPIKey = Boolean(\n configuration.ai?.apiKey || aiOptions?.apiKey\n );\n\n if (hasHisOwnAIAPIKey || isOllama) {\n return true;\n }\n\n // User need to provide either his own AI API key or the CMS auth\n if (!hasCMSAuth) {\n appLogger(\n [\n 'AI options or API key not provided. You can either retreive the CMS access key on',\n colorize('https://intlayer.org/dahboard', ANSIColors.GREY),\n colorize('(see doc:', ANSIColors.GREY_DARK),\n colorize('https://intlayer.org/doc/concept/cms', ANSIColors.GREY),\n colorize(')', ANSIColors.GREY_DARK),\n '. Alternatively, you can add your own OpenAI API key in the settings',\n colorize('(see doc:', ANSIColors.GREY_DARK),\n colorize(\n 'https://intlayer.org/doc/concept/configuration',\n ANSIColors.GREY\n ),\n colorize(')', ANSIColors.GREY_DARK),\n '.',\n ],\n {\n level: 'error',\n }\n );\n\n return false;\n }\n\n // If the user do not have his own AI API key, we need to check the CMS auth\n return await checkCMSAuth(configuration, shouldCheckConfigConsistency);\n};\n"],"mappings":";;;;;;AAWA,MAAa,eAAe,OAC1B,eACA,+BAAwC,SACnB;CACrB,MAAM,+CAAyB,eAAe,EAC5C,QAAQ,EACN,QAAQ,IACT,EACF,CAAC;AAIF,KAAI,EADF,cAAc,OAAO,YAAY,cAAc,OAAO,eACvC;AACf,YACE;GACE;kCACS,iCAAiCA,4BAAW,KAAK;kCACjD,aAAaA,4BAAW,UAAU;kCAClC,wCAAwCA,4BAAW,KAAK;kCACxD,KAAKA,4BAAW,UAAU;GACnC;GACD,EACD,EACE,OAAO,SACR,CACF;AAED,SAAO;;CAET,MAAM,qDAAkC,QAAW,cAAc;AAEjE,KAAI;EAGF,MAAM,WAFS,MAAM,YAAY,MAAM,sBAAsB,EAEtC,MAAM;AAE7B,MAAI,CAAC,SAAS;AACZ,aAAU,oBAAoB;AAE9B,UAAO;;AAGT,MAAI,QAAQ,iBAAiB,6BAC3B,KAAI;AAEF,+DAAuB,QAAQ,eAAe,cAAc;UACtD;AACN,aACE;IACE;IACA;mCACS,qBAAqBA,4BAAW,KAAK;mCACrC,aAAaA,4BAAW,UAAU;mCAEzC,6CACAA,4BAAW,KACZ;mCACQ,KAAKA,4BAAW,UAAU;IACnC;IACD,EACD,EACE,OAAO,QACR,CACF;;UAGE,OAAO;AAGd,sDAFoC,MAAM,EAEvB,EACjB,OAAO,SACR,CAAC;AACF,SAAO;;AAGT,QAAO;;AAGT,MAAa,gBAAgB,OAC3B,eACA,WACA,+BAAwC,SACnB;CACrB,MAAM,+CAAyB,cAAc;CAE7C,MAAM,aAAa,QACjB,cAAc,OAAO,YAAY,cAAc,OAAO,aACvD;CACD,MAAM,WACJ,cAAc,IAAI,aAAa,YAAY,WAAW,aAAa;AAKrE,KAJ0B,QACxB,cAAc,IAAI,UAAU,WAAW,OACxC,IAEwB,SACvB,QAAO;AAIT,KAAI,CAAC,YAAY;AACf,YACE;GACE;kCACS,iCAAiCA,4BAAW,KAAK;kCACjD,aAAaA,4BAAW,UAAU;kCAClC,wCAAwCA,4BAAW,KAAK;kCACxD,KAAKA,4BAAW,UAAU;GACnC;kCACS,aAAaA,4BAAW,UAAU;kCAEzC,kDACAA,4BAAW,KACZ;kCACQ,KAAKA,4BAAW,UAAU;GACnC;GACD,EACD,EACE,OAAO,SACR,CACF;AAED,SAAO;;AAIT,QAAO,MAAM,aAAa,eAAe,6BAA6B"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
|
|
2
|
+
let node_util = require("node:util");
|
|
3
|
+
|
|
4
|
+
//#region src/utils/checkConfigConsistency.ts
|
|
5
|
+
/**
|
|
6
|
+
* Recursively checks if a subset configuration matches a superset configuration.
|
|
7
|
+
* Throws an error if any value in the subset doesn't match the corresponding value in the superset.
|
|
8
|
+
*
|
|
9
|
+
* @param subset - The subset configuration (e.g., project.configuration from CMS)
|
|
10
|
+
* @param superset - The superset configuration (e.g., local configuration)
|
|
11
|
+
* @throws Error if any value in subset doesn't match the corresponding value in superset
|
|
12
|
+
*/
|
|
13
|
+
const checkConfigConsistency = (subset, superset) => {
|
|
14
|
+
Object.keys(subset).forEach((key) => {
|
|
15
|
+
const isSubsetObject = typeof subset[key] === "object" && subset[key] !== null && !Array.isArray(subset[key]);
|
|
16
|
+
const isSupersetObject = typeof superset[key] === "object" && superset[key] !== null && !Array.isArray(superset[key]);
|
|
17
|
+
if (isSubsetObject && isSupersetObject) checkConfigConsistency(subset[key], superset[key]);
|
|
18
|
+
else if (!(0, node_util.isDeepStrictEqual)(subset[key], superset[key])) throw new Error(`Configuration mismatch at key "${key}": expected ${JSON.stringify(superset[key])}, got ${JSON.stringify(subset[key])}`);
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
//#endregion
|
|
23
|
+
exports.checkConfigConsistency = checkConfigConsistency;
|
|
24
|
+
//# sourceMappingURL=checkConfigConsistency.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkConfigConsistency.cjs","names":[],"sources":["../../../src/utils/checkConfigConsistency.ts"],"sourcesContent":["import { isDeepStrictEqual } from 'node:util';\n\n/**\n * Recursively checks if a subset configuration matches a superset configuration.\n * Throws an error if any value in the subset doesn't match the corresponding value in the superset.\n *\n * @param subset - The subset configuration (e.g., project.configuration from CMS)\n * @param superset - The superset configuration (e.g., local configuration)\n * @throws Error if any value in subset doesn't match the corresponding value in superset\n */\nexport const checkConfigConsistency = (\n subset: Record<string, any>,\n superset: Record<string, any>\n): void => {\n Object.keys(subset).forEach((key) => {\n const isSubsetObject =\n typeof subset[key] === 'object' &&\n subset[key] !== null &&\n !Array.isArray(subset[key]);\n const isSupersetObject =\n typeof superset[key] === 'object' &&\n superset[key] !== null &&\n !Array.isArray(superset[key]);\n\n if (isSubsetObject && isSupersetObject) {\n checkConfigConsistency(subset[key], superset[key]);\n } else {\n if (!isDeepStrictEqual(subset[key], superset[key])) {\n throw new Error(\n `Configuration mismatch at key \"${key}\": expected ${JSON.stringify(superset[key])}, got ${JSON.stringify(subset[key])}`\n );\n }\n }\n });\n};\n"],"mappings":";;;;;;;;;;;;AAUA,MAAa,0BACX,QACA,aACS;AACT,QAAO,KAAK,OAAO,CAAC,SAAS,QAAQ;EACnC,MAAM,iBACJ,OAAO,OAAO,SAAS,YACvB,OAAO,SAAS,QAChB,CAAC,MAAM,QAAQ,OAAO,KAAK;EAC7B,MAAM,mBACJ,OAAO,SAAS,SAAS,YACzB,SAAS,SAAS,QAClB,CAAC,MAAM,QAAQ,SAAS,KAAK;AAE/B,MAAI,kBAAkB,iBACpB,wBAAuB,OAAO,MAAM,SAAS,KAAK;WAE9C,kCAAmB,OAAO,MAAM,SAAS,KAAK,CAChD,OAAM,IAAI,MACR,kCAAkC,IAAI,cAAc,KAAK,UAAU,SAAS,KAAK,CAAC,QAAQ,KAAK,UAAU,OAAO,KAAK,GACtH;GAGL"}
|
|
@@ -21,28 +21,37 @@ const logAIConfig = (aiOptions, appLogger) => {
|
|
|
21
21
|
*/
|
|
22
22
|
const setupAI = async (configuration, aiOptions) => {
|
|
23
23
|
const appLogger = (0, _intlayer_config.getAppLogger)(configuration);
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
if (aiOptions?.apiKey || aiOptions?.provider === "ollama" || configuration.ai?.apiKey || configuration.ai?.provider === "ollama") {
|
|
25
|
+
let aiClient;
|
|
26
|
+
try {
|
|
27
|
+
aiClient = await import("@intlayer/ai");
|
|
28
|
+
} catch {
|
|
29
|
+
appLogger([
|
|
30
|
+
(0, _intlayer_config.colorize)("Using your API key, you can install the", _intlayer_config.ANSIColors.GREY),
|
|
31
|
+
(0, _intlayer_config.colorize)("@intlayer/ai", _intlayer_config.ANSIColors.GREY_LIGHT),
|
|
32
|
+
(0, _intlayer_config.colorize)("package to run the process locally, with no dependency on the Intlayer server", _intlayer_config.ANSIColors.GREY)
|
|
33
|
+
], { level: "warn" });
|
|
34
|
+
const hasAIAccess$1 = await require_utils_checkAccess.checkAIAccess(configuration, aiOptions);
|
|
35
|
+
logAIConfig(aiOptions ?? {}, appLogger);
|
|
36
|
+
return {
|
|
37
|
+
isCustomAI: false,
|
|
38
|
+
hasAIAccess: hasAIAccess$1
|
|
39
|
+
};
|
|
40
|
+
}
|
|
27
41
|
appLogger([(0, _intlayer_config.colorize)("@intlayer/ai", _intlayer_config.ANSIColors.GREY_LIGHT), (0, _intlayer_config.colorize)("found - Run process locally", _intlayer_config.ANSIColors.GREY_DARK)]);
|
|
28
42
|
const aiConfig = await aiClient.getAIConfig({
|
|
29
43
|
userOptions: aiOptions,
|
|
30
44
|
accessType: ["public"]
|
|
31
45
|
});
|
|
32
|
-
logAIConfig(aiOptions, appLogger);
|
|
46
|
+
logAIConfig(aiOptions ?? {}, appLogger);
|
|
33
47
|
return {
|
|
34
48
|
aiClient,
|
|
35
49
|
aiConfig,
|
|
36
50
|
isCustomAI: true,
|
|
37
|
-
hasAIAccess
|
|
51
|
+
hasAIAccess: true
|
|
38
52
|
};
|
|
39
|
-
} catch {
|
|
40
|
-
appLogger([
|
|
41
|
-
(0, _intlayer_config.colorize)("Using your API key, you can install the", _intlayer_config.ANSIColors.GREY),
|
|
42
|
-
(0, _intlayer_config.colorize)("@intlayer/ai", _intlayer_config.ANSIColors.GREY_LIGHT),
|
|
43
|
-
(0, _intlayer_config.colorize)("package to run the process locally, with no dependency on the Intlayer server", _intlayer_config.ANSIColors.GREY)
|
|
44
|
-
], { level: "warn" });
|
|
45
53
|
}
|
|
54
|
+
const hasAIAccess = await require_utils_checkAccess.checkAIAccess(configuration, aiOptions);
|
|
46
55
|
logAIConfig(aiOptions ?? {}, appLogger);
|
|
47
56
|
return {
|
|
48
57
|
isCustomAI: false,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setupAI.cjs","names":["ANSIColors","checkAIAccess"],"sources":["../../../src/utils/setupAI.ts"],"sourcesContent":["import type { AIConfig, AIOptions } from '@intlayer/ai';\nimport {\n ANSIColors,\n colorize,\n getAppLogger,\n type logger,\n} from '@intlayer/config';\nimport type { IntlayerConfig } from '@intlayer/types';\nimport { checkAIAccess } from './checkAccess';\n\nexport type AIClient = typeof import('@intlayer/ai');\n\ntype SetupAIResult = {\n aiClient?: AIClient;\n aiConfig?: AIConfig;\n isCustomAI: boolean;\n hasAIAccess: boolean;\n};\n\n// Disable warnings from the AI SDK\nglobalThis.AI_SDK_LOG_WARNINGS = false;\n\nconst logAIConfig = (aiOptions: AIOptions, appLogger: typeof logger) => {\n appLogger([\n colorize('Provider:', ANSIColors.GREY_DARK),\n colorize(aiOptions?.provider ?? '(default)', ANSIColors.BLUE),\n colorize('- Model:', ANSIColors.GREY_DARK),\n colorize(aiOptions?.model ?? '(default)', ANSIColors.BLUE),\n colorize('- API Key:', ANSIColors.GREY_DARK),\n colorize(aiOptions?.apiKey ? '✓' : '(not set)', ANSIColors.BLUE),\n ]);\n};\n\n/**\n * Checks if the @intlayer/ai package is available and configured when an API key is provided.\n * If API key is present but package is missing, logs a warning.\n * Also checks if the user has access to AI (either via local key or CMS auth).\n */\nexport const setupAI = async (\n configuration: IntlayerConfig,\n aiOptions?: AIOptions\n): Promise<SetupAIResult | undefined> => {\n const appLogger = getAppLogger(configuration);\n\n const
|
|
1
|
+
{"version":3,"file":"setupAI.cjs","names":["ANSIColors","aiClient: AIClient | undefined","hasAIAccess","checkAIAccess"],"sources":["../../../src/utils/setupAI.ts"],"sourcesContent":["import type { AIConfig, AIOptions } from '@intlayer/ai';\nimport {\n ANSIColors,\n colorize,\n getAppLogger,\n type logger,\n} from '@intlayer/config';\nimport type { IntlayerConfig } from '@intlayer/types';\nimport { checkAIAccess } from './checkAccess';\n\nexport type AIClient = typeof import('@intlayer/ai');\n\ntype SetupAIResult = {\n aiClient?: AIClient;\n aiConfig?: AIConfig;\n isCustomAI: boolean;\n hasAIAccess: boolean;\n};\n\n// Disable warnings from the AI SDK\nglobalThis.AI_SDK_LOG_WARNINGS = false;\n\nconst logAIConfig = (aiOptions: AIOptions, appLogger: typeof logger) => {\n appLogger([\n colorize('Provider:', ANSIColors.GREY_DARK),\n colorize(aiOptions?.provider ?? '(default)', ANSIColors.BLUE),\n colorize('- Model:', ANSIColors.GREY_DARK),\n colorize(aiOptions?.model ?? '(default)', ANSIColors.BLUE),\n colorize('- API Key:', ANSIColors.GREY_DARK),\n colorize(aiOptions?.apiKey ? '✓' : '(not set)', ANSIColors.BLUE),\n ]);\n};\n\n/**\n * Checks if the @intlayer/ai package is available and configured when an API key is provided.\n * If API key is present but package is missing, logs a warning.\n * Also checks if the user has access to AI (either via local key or CMS auth).\n */\nexport const setupAI = async (\n configuration: IntlayerConfig,\n aiOptions?: AIOptions\n): Promise<SetupAIResult | undefined> => {\n const appLogger = getAppLogger(configuration);\n\n const isLocalAI =\n aiOptions?.apiKey ||\n aiOptions?.provider === 'ollama' ||\n configuration.ai?.apiKey ||\n configuration.ai?.provider === 'ollama';\n\n if (isLocalAI) {\n // Try to import the AI package for local AI usage\n let aiClient: AIClient | undefined;\n\n try {\n aiClient = await import('@intlayer/ai');\n } catch {\n // Package not installed - log warning and fall back to backend\n appLogger(\n [\n colorize('Using your API key, you can install the', ANSIColors.GREY),\n colorize('@intlayer/ai', ANSIColors.GREY_LIGHT),\n colorize(\n 'package to run the process locally, with no dependency on the Intlayer server',\n ANSIColors.GREY\n ),\n ],\n {\n level: 'warn',\n }\n );\n\n // Fall back to backend API check\n const hasAIAccess = await checkAIAccess(configuration, aiOptions);\n logAIConfig(aiOptions ?? {}, appLogger);\n return {\n isCustomAI: false,\n hasAIAccess,\n };\n }\n\n // Package found - now configure it (errors here should propagate, not fall back)\n appLogger([\n colorize('@intlayer/ai', ANSIColors.GREY_LIGHT),\n colorize('found - Run process locally', ANSIColors.GREY_DARK),\n ]);\n\n const aiConfig = await aiClient.getAIConfig({\n userOptions: aiOptions,\n accessType: ['public'],\n });\n\n logAIConfig(aiOptions ?? {}, appLogger);\n\n return {\n aiClient,\n aiConfig,\n isCustomAI: true,\n hasAIAccess: true, // Local AI always has access\n };\n }\n\n // No local AI configured - use backend API\n const hasAIAccess = await checkAIAccess(configuration, aiOptions);\n logAIConfig(aiOptions ?? {}, appLogger);\n\n return {\n isCustomAI: false,\n hasAIAccess,\n };\n};\n"],"mappings":";;;;;AAoBA,WAAW,sBAAsB;AAEjC,MAAM,eAAe,WAAsB,cAA6B;AACtE,WAAU;iCACC,aAAaA,4BAAW,UAAU;iCAClC,WAAW,YAAY,aAAaA,4BAAW,KAAK;iCACpD,YAAYA,4BAAW,UAAU;iCACjC,WAAW,SAAS,aAAaA,4BAAW,KAAK;iCACjD,cAAcA,4BAAW,UAAU;iCACnC,WAAW,SAAS,MAAM,aAAaA,4BAAW,KAAK;EACjE,CAAC;;;;;;;AAQJ,MAAa,UAAU,OACrB,eACA,cACuC;CACvC,MAAM,+CAAyB,cAAc;AAQ7C,KALE,WAAW,UACX,WAAW,aAAa,YACxB,cAAc,IAAI,UAClB,cAAc,IAAI,aAAa,UAElB;EAEb,IAAIC;AAEJ,MAAI;AACF,cAAW,MAAM,OAAO;UAClB;AAEN,aACE;mCACW,2CAA2CD,4BAAW,KAAK;mCAC3D,gBAAgBA,4BAAW,WAAW;mCAE7C,iFACAA,4BAAW,KACZ;IACF,EACD,EACE,OAAO,QACR,CACF;GAGD,MAAME,gBAAc,MAAMC,wCAAc,eAAe,UAAU;AACjE,eAAY,aAAa,EAAE,EAAE,UAAU;AACvC,UAAO;IACL,YAAY;IACZ;IACD;;AAIH,YAAU,gCACC,gBAAgBH,4BAAW,WAAW,iCACtC,+BAA+BA,4BAAW,UAAU,CAC9D,CAAC;EAEF,MAAM,WAAW,MAAM,SAAS,YAAY;GAC1C,aAAa;GACb,YAAY,CAAC,SAAS;GACvB,CAAC;AAEF,cAAY,aAAa,EAAE,EAAE,UAAU;AAEvC,SAAO;GACL;GACA;GACA,YAAY;GACZ,aAAa;GACd;;CAIH,MAAM,cAAc,MAAMG,wCAAc,eAAe,UAAU;AACjE,aAAY,aAAa,EAAE,EAAE,UAAU;AAEvC,QAAO;EACL,YAAY;EACZ;EACD"}
|
package/dist/esm/auth/login.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import http from "node:http";
|
|
|
6
6
|
//#region src/auth/login.ts
|
|
7
7
|
const login = async (options) => {
|
|
8
8
|
const configuration = getConfiguration(options.configOptions);
|
|
9
|
-
const logger = getAppLogger(configuration);
|
|
9
|
+
const logger$1 = getAppLogger(configuration);
|
|
10
10
|
const cmsUrl = options.cmsUrl ?? configuration.editor.cmsURL;
|
|
11
11
|
return new Promise((resolve) => {
|
|
12
12
|
const server = http.createServer((req, res) => {
|
|
@@ -23,21 +23,21 @@ const login = async (options) => {
|
|
|
23
23
|
const clientId = url.searchParams.get("clientId");
|
|
24
24
|
const clientSecret = url.searchParams.get("clientSecret");
|
|
25
25
|
if (clientId && clientSecret) {
|
|
26
|
-
logger("");
|
|
27
|
-
logger("Log in successful. Client ID and Client Secret received.");
|
|
28
|
-
logger("");
|
|
29
|
-
logger([
|
|
26
|
+
logger$1("");
|
|
27
|
+
logger$1("Log in successful. Client ID and Client Secret received.");
|
|
28
|
+
logger$1("");
|
|
29
|
+
logger$1([
|
|
30
30
|
"1. Insert the Client ID and Client Secret in your",
|
|
31
31
|
colorizePath(".env"),
|
|
32
32
|
"file:"
|
|
33
33
|
]);
|
|
34
|
-
logger(colorize("--------------------------------", ANSIColors.GREY_DARK));
|
|
35
|
-
logger([colorize("INTLAYER_CLIENT_ID=", ANSIColors.GREY_LIGHT), colorize(clientId, ANSIColors.BLUE)].join(""));
|
|
36
|
-
logger([colorize("INTLAYER_CLIENT_SECRET=", ANSIColors.GREY_LIGHT), colorize(clientSecret, ANSIColors.BLUE)].join(""));
|
|
37
|
-
logger(colorize("--------------------------------", ANSIColors.GREY_DARK));
|
|
38
|
-
logger("");
|
|
39
|
-
logger("2. Insert in your Intlayer configuration file:");
|
|
40
|
-
logger(colorize("--------------------------------", ANSIColors.GREY_DARK));
|
|
34
|
+
logger$1(colorize("--------------------------------", ANSIColors.GREY_DARK));
|
|
35
|
+
logger$1([colorize("INTLAYER_CLIENT_ID=", ANSIColors.GREY_LIGHT), colorize(clientId, ANSIColors.BLUE)].join(""));
|
|
36
|
+
logger$1([colorize("INTLAYER_CLIENT_SECRET=", ANSIColors.GREY_LIGHT), colorize(clientSecret, ANSIColors.BLUE)].join(""));
|
|
37
|
+
logger$1(colorize("--------------------------------", ANSIColors.GREY_DARK));
|
|
38
|
+
logger$1("");
|
|
39
|
+
logger$1("2. Insert in your Intlayer configuration file:");
|
|
40
|
+
logger$1(colorize("--------------------------------", ANSIColors.GREY_DARK));
|
|
41
41
|
[
|
|
42
42
|
`${ANSIColors.GREY_LIGHT}{`,
|
|
43
43
|
` editor: {`,
|
|
@@ -47,9 +47,9 @@ const login = async (options) => {
|
|
|
47
47
|
` },`,
|
|
48
48
|
`}`
|
|
49
49
|
].forEach((line) => {
|
|
50
|
-
logger(line);
|
|
50
|
+
logger$1(line);
|
|
51
51
|
});
|
|
52
|
-
logger(colorize("--------------------------------", ANSIColors.GREY_DARK));
|
|
52
|
+
logger$1(colorize("--------------------------------", ANSIColors.GREY_DARK));
|
|
53
53
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
54
54
|
res.end(`
|
|
55
55
|
<!DOCTYPE html>
|
|
@@ -142,8 +142,8 @@ const login = async (options) => {
|
|
|
142
142
|
const port = typeof address === "object" && address ? address.port : 0;
|
|
143
143
|
const state = Math.random().toString(36).substring(7);
|
|
144
144
|
const loginUrl = `${cmsUrl ?? process.env.INTLAYER_SITE_URL ?? "http://localhost:3000"}/en/auth/cli-login?port=${port}&state=${state}`;
|
|
145
|
-
logger("Opening browser for login...");
|
|
146
|
-
logger(`If browser does not open, visit: ${colorizePath(loginUrl)}`);
|
|
145
|
+
logger$1("Opening browser for login...");
|
|
146
|
+
logger$1(`If browser does not open, visit: ${colorizePath(loginUrl)}`);
|
|
147
147
|
openBrowser(loginUrl);
|
|
148
148
|
});
|
|
149
149
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login.mjs","names":[],"sources":["../../../src/auth/login.ts"],"sourcesContent":["import http from 'node:http';\nimport { URL } from 'node:url';\nimport type { GetConfigurationOptions } from '@intlayer/config';\nimport {\n ANSIColors,\n colorize,\n colorizePath,\n getAppLogger,\n getConfiguration,\n} from '@intlayer/config';\nimport { openBrowser } from '../utils/openBrowser';\n\ntype LoginOptions = {\n cmsUrl?: string;\n configOptions?: GetConfigurationOptions;\n};\n\nexport const login = async (options: LoginOptions) => {\n const configuration = getConfiguration(options.configOptions);\n const logger = getAppLogger(configuration);\n\n const cmsUrl = options.cmsUrl ?? configuration.editor.cmsURL;\n\n return new Promise<void>((resolve) => {\n const server = http.createServer((req, res) => {\n const url = new URL(req.url ?? '', `http://${req.headers.host}`);\n\n // Set CORS headers\n res.setHeader('Access-Control-Allow-Origin', '*');\n res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type');\n\n if (req.method === 'OPTIONS') {\n res.writeHead(204);\n res.end();\n return;\n }\n\n if (url.pathname === '/callback') {\n const clientId = url.searchParams.get('clientId');\n const clientSecret = url.searchParams.get('clientSecret');\n\n if (clientId && clientSecret) {\n logger('');\n logger('Log in successful. Client ID and Client Secret received.');\n\n logger('');\n logger([\n '1. Insert the Client ID and Client Secret in your',\n colorizePath('.env'),\n 'file:',\n ]);\n logger(\n colorize('--------------------------------', ANSIColors.GREY_DARK)\n );\n logger(\n [\n colorize('INTLAYER_CLIENT_ID=', ANSIColors.GREY_LIGHT),\n colorize(clientId, ANSIColors.BLUE),\n ].join('')\n );\n logger(\n [\n colorize('INTLAYER_CLIENT_SECRET=', ANSIColors.GREY_LIGHT),\n colorize(clientSecret, ANSIColors.BLUE),\n ].join('')\n );\n logger(\n colorize('--------------------------------', ANSIColors.GREY_DARK)\n );\n logger('');\n logger('2. Insert in your Intlayer configuration file:');\n logger(\n colorize('--------------------------------', ANSIColors.GREY_DARK)\n );\n [\n `${ANSIColors.GREY_LIGHT}{`,\n ` editor: {`,\n ` cmsURL: '${colorizePath(cmsUrl, undefined, ANSIColors.GREY_LIGHT)}',`,\n ` clientId: '${colorize('process.env.INTLAYER_CLIENT_ID', ANSIColors.BLUE, ANSIColors.GREY_LIGHT)}',`,\n ` clientSecret: '${colorize('process.env.INTLAYER_CLIENT_SECRET', ANSIColors.BLUE, ANSIColors.GREY_LIGHT)}',`,\n ` },`,\n `}`,\n ].forEach((line) => {\n logger(line);\n });\n logger(\n colorize('--------------------------------', ANSIColors.GREY_DARK)\n );\n\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(`\n <!DOCTYPE html>\n <html lang=\"en\" data-theme=\"dark\">\n <head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Intlayer CLI Login</title>\n <style>\n :root {\n --color-background: rgba(23, 23, 23);\n --color-card: rgba(39, 39, 39);\n --color-text: rgba(255, 245, 237);\n --color-neutral: rgba(93, 93, 93);\n --font-sans: \"Inter\", -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;\n }\n \n * {\n box-sizing: border-box;\n }\n \n body {\n font-family: var(--font-sans);\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 100vh;\n margin: 0;\n padding: 1rem;\n background-color: var(--color-background);\n color: var(--color-text);\n }\n \n .container {\n text-align: center;\n padding: 2rem;\n border-radius: 1rem;\n background-color: var(--color-card);\n box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);\n max-width: 400px;\n width: 100%;\n }\n \n h1 {\n margin: 0 0 1rem 0;\n font-size: 1.5rem;\n font-weight: 700;\n color: var(--color-text);\n }\n \n p {\n color: var(--color-neutral);\n margin: 0 0 1.5rem 0;\n line-height: 1.5;\n }\n </style>\n </head>\n <body>\n <div class=\"container\">\n <h1>Login Successful</h1>\n <p>You have successfully logged in to Intlayer CLI. You can now close this tab and return to your terminal.</p>\n </div>\n <script>\n // Attempt to close the window\n window.close();\n \n // Fallback: if window.close() doesn't work, show a message\n setTimeout(() => {\n window.close();\n }, 1000);\n </script>\n </body>\n </html>\n `);\n\n server.close(() => {\n resolve();\n process.exit(0);\n });\n } else {\n res.writeHead(400, { 'Content-Type': 'text/plain' });\n res.end('Missing parameters');\n }\n } else {\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('Not found');\n }\n });\n\n server.listen(0, () => {\n const address = server.address();\n const port = typeof address === 'object' && address ? address.port : 0;\n const state = Math.random().toString(36).substring(7);\n\n const websiteUrl =\n cmsUrl ?? process.env.INTLAYER_SITE_URL ?? 'http://localhost:3000';\n const loginUrl = `${websiteUrl}/en/auth/cli-login?port=${port}&state=${state}`;\n\n logger('Opening browser for login...');\n logger(`If browser does not open, visit: ${colorizePath(loginUrl)}`);\n\n openBrowser(loginUrl);\n });\n });\n};\n"],"mappings":";;;;;;AAiBA,MAAa,QAAQ,OAAO,YAA0B;CACpD,MAAM,gBAAgB,iBAAiB,QAAQ,cAAc;CAC7D,
|
|
1
|
+
{"version":3,"file":"login.mjs","names":["logger"],"sources":["../../../src/auth/login.ts"],"sourcesContent":["import http from 'node:http';\nimport { URL } from 'node:url';\nimport type { GetConfigurationOptions } from '@intlayer/config';\nimport {\n ANSIColors,\n colorize,\n colorizePath,\n getAppLogger,\n getConfiguration,\n} from '@intlayer/config';\nimport { openBrowser } from '../utils/openBrowser';\n\ntype LoginOptions = {\n cmsUrl?: string;\n configOptions?: GetConfigurationOptions;\n};\n\nexport const login = async (options: LoginOptions) => {\n const configuration = getConfiguration(options.configOptions);\n const logger = getAppLogger(configuration);\n\n const cmsUrl = options.cmsUrl ?? configuration.editor.cmsURL;\n\n return new Promise<void>((resolve) => {\n const server = http.createServer((req, res) => {\n const url = new URL(req.url ?? '', `http://${req.headers.host}`);\n\n // Set CORS headers\n res.setHeader('Access-Control-Allow-Origin', '*');\n res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type');\n\n if (req.method === 'OPTIONS') {\n res.writeHead(204);\n res.end();\n return;\n }\n\n if (url.pathname === '/callback') {\n const clientId = url.searchParams.get('clientId');\n const clientSecret = url.searchParams.get('clientSecret');\n\n if (clientId && clientSecret) {\n logger('');\n logger('Log in successful. Client ID and Client Secret received.');\n\n logger('');\n logger([\n '1. Insert the Client ID and Client Secret in your',\n colorizePath('.env'),\n 'file:',\n ]);\n logger(\n colorize('--------------------------------', ANSIColors.GREY_DARK)\n );\n logger(\n [\n colorize('INTLAYER_CLIENT_ID=', ANSIColors.GREY_LIGHT),\n colorize(clientId, ANSIColors.BLUE),\n ].join('')\n );\n logger(\n [\n colorize('INTLAYER_CLIENT_SECRET=', ANSIColors.GREY_LIGHT),\n colorize(clientSecret, ANSIColors.BLUE),\n ].join('')\n );\n logger(\n colorize('--------------------------------', ANSIColors.GREY_DARK)\n );\n logger('');\n logger('2. Insert in your Intlayer configuration file:');\n logger(\n colorize('--------------------------------', ANSIColors.GREY_DARK)\n );\n [\n `${ANSIColors.GREY_LIGHT}{`,\n ` editor: {`,\n ` cmsURL: '${colorizePath(cmsUrl, undefined, ANSIColors.GREY_LIGHT)}',`,\n ` clientId: '${colorize('process.env.INTLAYER_CLIENT_ID', ANSIColors.BLUE, ANSIColors.GREY_LIGHT)}',`,\n ` clientSecret: '${colorize('process.env.INTLAYER_CLIENT_SECRET', ANSIColors.BLUE, ANSIColors.GREY_LIGHT)}',`,\n ` },`,\n `}`,\n ].forEach((line) => {\n logger(line);\n });\n logger(\n colorize('--------------------------------', ANSIColors.GREY_DARK)\n );\n\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(`\n <!DOCTYPE html>\n <html lang=\"en\" data-theme=\"dark\">\n <head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Intlayer CLI Login</title>\n <style>\n :root {\n --color-background: rgba(23, 23, 23);\n --color-card: rgba(39, 39, 39);\n --color-text: rgba(255, 245, 237);\n --color-neutral: rgba(93, 93, 93);\n --font-sans: \"Inter\", -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;\n }\n \n * {\n box-sizing: border-box;\n }\n \n body {\n font-family: var(--font-sans);\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 100vh;\n margin: 0;\n padding: 1rem;\n background-color: var(--color-background);\n color: var(--color-text);\n }\n \n .container {\n text-align: center;\n padding: 2rem;\n border-radius: 1rem;\n background-color: var(--color-card);\n box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);\n max-width: 400px;\n width: 100%;\n }\n \n h1 {\n margin: 0 0 1rem 0;\n font-size: 1.5rem;\n font-weight: 700;\n color: var(--color-text);\n }\n \n p {\n color: var(--color-neutral);\n margin: 0 0 1.5rem 0;\n line-height: 1.5;\n }\n </style>\n </head>\n <body>\n <div class=\"container\">\n <h1>Login Successful</h1>\n <p>You have successfully logged in to Intlayer CLI. You can now close this tab and return to your terminal.</p>\n </div>\n <script>\n // Attempt to close the window\n window.close();\n \n // Fallback: if window.close() doesn't work, show a message\n setTimeout(() => {\n window.close();\n }, 1000);\n </script>\n </body>\n </html>\n `);\n\n server.close(() => {\n resolve();\n process.exit(0);\n });\n } else {\n res.writeHead(400, { 'Content-Type': 'text/plain' });\n res.end('Missing parameters');\n }\n } else {\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('Not found');\n }\n });\n\n server.listen(0, () => {\n const address = server.address();\n const port = typeof address === 'object' && address ? address.port : 0;\n const state = Math.random().toString(36).substring(7);\n\n const websiteUrl =\n cmsUrl ?? process.env.INTLAYER_SITE_URL ?? 'http://localhost:3000';\n const loginUrl = `${websiteUrl}/en/auth/cli-login?port=${port}&state=${state}`;\n\n logger('Opening browser for login...');\n logger(`If browser does not open, visit: ${colorizePath(loginUrl)}`);\n\n openBrowser(loginUrl);\n });\n });\n};\n"],"mappings":";;;;;;AAiBA,MAAa,QAAQ,OAAO,YAA0B;CACpD,MAAM,gBAAgB,iBAAiB,QAAQ,cAAc;CAC7D,MAAMA,WAAS,aAAa,cAAc;CAE1C,MAAM,SAAS,QAAQ,UAAU,cAAc,OAAO;AAEtD,QAAO,IAAI,SAAe,YAAY;EACpC,MAAM,SAAS,KAAK,cAAc,KAAK,QAAQ;GAC7C,MAAM,MAAM,IAAI,IAAI,IAAI,OAAO,IAAI,UAAU,IAAI,QAAQ,OAAO;AAGhE,OAAI,UAAU,+BAA+B,IAAI;AACjD,OAAI,UAAU,gCAAgC,eAAe;AAC7D,OAAI,UAAU,gCAAgC,eAAe;AAE7D,OAAI,IAAI,WAAW,WAAW;AAC5B,QAAI,UAAU,IAAI;AAClB,QAAI,KAAK;AACT;;AAGF,OAAI,IAAI,aAAa,aAAa;IAChC,MAAM,WAAW,IAAI,aAAa,IAAI,WAAW;IACjD,MAAM,eAAe,IAAI,aAAa,IAAI,eAAe;AAEzD,QAAI,YAAY,cAAc;AAC5B,cAAO,GAAG;AACV,cAAO,2DAA2D;AAElE,cAAO,GAAG;AACV,cAAO;MACL;MACA,aAAa,OAAO;MACpB;MACD,CAAC;AACF,cACE,SAAS,oCAAoC,WAAW,UAAU,CACnE;AACD,cACE,CACE,SAAS,uBAAuB,WAAW,WAAW,EACtD,SAAS,UAAU,WAAW,KAAK,CACpC,CAAC,KAAK,GAAG,CACX;AACD,cACE,CACE,SAAS,2BAA2B,WAAW,WAAW,EAC1D,SAAS,cAAc,WAAW,KAAK,CACxC,CAAC,KAAK,GAAG,CACX;AACD,cACE,SAAS,oCAAoC,WAAW,UAAU,CACnE;AACD,cAAO,GAAG;AACV,cAAO,iDAAiD;AACxD,cACE,SAAS,oCAAoC,WAAW,UAAU,CACnE;AACD;MACE,GAAG,WAAW,WAAW;MACzB;MACA,iBAAiB,aAAa,QAAQ,QAAW,WAAW,WAAW,CAAC;MACxE,mBAAmB,SAAS,kCAAkC,WAAW,MAAM,WAAW,WAAW,CAAC;MACtG,uBAAuB,SAAS,sCAAsC,WAAW,MAAM,WAAW,WAAW,CAAC;MAC9G;MACA;MACD,CAAC,SAAS,SAAS;AAClB,eAAO,KAAK;OACZ;AACF,cACE,SAAS,oCAAoC,WAAW,UAAU,CACnE;AAED,SAAI,UAAU,KAAK,EAAE,gBAAgB,aAAa,CAAC;AACnD,SAAI,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAwEN;AAEF,YAAO,YAAY;AACjB,eAAS;AACT,cAAQ,KAAK,EAAE;OACf;WACG;AACL,SAAI,UAAU,KAAK,EAAE,gBAAgB,cAAc,CAAC;AACpD,SAAI,IAAI,qBAAqB;;UAE1B;AACL,QAAI,UAAU,KAAK,EAAE,gBAAgB,cAAc,CAAC;AACpD,QAAI,IAAI,YAAY;;IAEtB;AAEF,SAAO,OAAO,SAAS;GACrB,MAAM,UAAU,OAAO,SAAS;GAChC,MAAM,OAAO,OAAO,YAAY,YAAY,UAAU,QAAQ,OAAO;GACrE,MAAM,QAAQ,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,UAAU,EAAE;GAIrD,MAAM,WAAW,GADf,UAAU,QAAQ,IAAI,qBAAqB,wBACd,0BAA0B,KAAK,SAAS;AAEvE,YAAO,+BAA+B;AACtC,YAAO,oCAAoC,aAAa,SAAS,GAAG;AAEpE,eAAY,SAAS;IACrB;GACF"}
|
package/dist/esm/ci.mjs
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { listProjects } from "@intlayer/chokidar";
|
|
2
|
+
import { logger } from "@intlayer/config";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { normalize, resolve } from "node:path";
|
|
5
|
+
|
|
6
|
+
//#region src/ci.ts
|
|
7
|
+
const getPackageManagerCommand = () => {
|
|
8
|
+
const userAgent = process.env.npm_config_user_agent;
|
|
9
|
+
if (userAgent?.startsWith("bun")) return {
|
|
10
|
+
command: "bun",
|
|
11
|
+
args: ["intlayer"]
|
|
12
|
+
};
|
|
13
|
+
if (userAgent?.startsWith("pnpm")) return {
|
|
14
|
+
command: "pnpm",
|
|
15
|
+
args: ["exec", "intlayer"]
|
|
16
|
+
};
|
|
17
|
+
if (userAgent?.startsWith("yarn")) return {
|
|
18
|
+
command: "yarn",
|
|
19
|
+
args: ["run", "intlayer"]
|
|
20
|
+
};
|
|
21
|
+
return {
|
|
22
|
+
command: "npx",
|
|
23
|
+
args: ["intlayer"]
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
const runCI = async (commands) => {
|
|
27
|
+
const credentialsEnv = process.env.INTLAYER_PROJECT_CREDENTIALS;
|
|
28
|
+
let credentials = {};
|
|
29
|
+
if (credentialsEnv) try {
|
|
30
|
+
credentials = JSON.parse(credentialsEnv);
|
|
31
|
+
} catch {
|
|
32
|
+
logger("INTLAYER_PROJECT_CREDENTIALS is not valid JSON. Proceeding without credentials.", { level: "warn" });
|
|
33
|
+
}
|
|
34
|
+
const cwd = process.cwd();
|
|
35
|
+
const { projectsPath } = await listProjects();
|
|
36
|
+
if (projectsPath.length === 0) {
|
|
37
|
+
logger("No Intlayer projects found.", { level: "warn" });
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const currentProject = projectsPath.find((p) => cwd === p);
|
|
41
|
+
const projectsToRun = currentProject ? [currentProject] : projectsPath;
|
|
42
|
+
const { command, args: pmArgs } = getPackageManagerCommand();
|
|
43
|
+
const finalArgs = [...pmArgs, ...commands];
|
|
44
|
+
logger(`CI: Using package manager: ${command}`, { level: "verbose" });
|
|
45
|
+
if (currentProject) logger(`CI: Detected project context: ${currentProject}`, { level: "info" });
|
|
46
|
+
else logger(`CI: No specific project context detected. Iterating over ${projectsToRun.length} discovered projects...`, { level: "info" });
|
|
47
|
+
let hasError = false;
|
|
48
|
+
for (const projectPath of projectsToRun) {
|
|
49
|
+
const creds = Object.entries(credentials).find(([key]) => {
|
|
50
|
+
return resolve(key) === projectPath || projectPath.endsWith(normalize(key));
|
|
51
|
+
})?.[1];
|
|
52
|
+
logger(`\nCI: Executing for ${projectPath}...`, { level: "info" });
|
|
53
|
+
const envVars = { ...process.env };
|
|
54
|
+
if (creds) {
|
|
55
|
+
envVars.INTLAYER_CLIENT_ID = creds.clientId;
|
|
56
|
+
envVars.INTLAYER_CLIENT_SECRET = creds.clientSecret;
|
|
57
|
+
} else if (credentialsEnv) logger(`CI: No matching credentials found for ${projectPath} in INTLAYER_PROJECT_CREDENTIALS.`, { level: "verbose" });
|
|
58
|
+
if (spawnSync(command, finalArgs, {
|
|
59
|
+
cwd: projectPath,
|
|
60
|
+
stdio: "inherit",
|
|
61
|
+
env: envVars
|
|
62
|
+
}).status !== 0) {
|
|
63
|
+
logger(`CI: Failed for ${projectPath}`, { level: "error" });
|
|
64
|
+
hasError = true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (hasError) process.exit(1);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
//#endregion
|
|
71
|
+
export { runCI };
|
|
72
|
+
//# sourceMappingURL=ci.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ci.mjs","names":["credentials: Record<string, { clientId: string; clientSecret: string }>"],"sources":["../../src/ci.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process';\nimport { normalize, resolve } from 'node:path';\nimport { listProjects } from '@intlayer/chokidar';\nimport { logger } from '@intlayer/config';\n\n// Helper to detect the package manager used to run the command\nconst getPackageManagerCommand = () => {\n const userAgent = process.env.npm_config_user_agent;\n\n if (userAgent?.startsWith('bun')) {\n return { command: 'bun', args: ['intlayer'] }; // Bun runs local bins natively\n }\n if (userAgent?.startsWith('pnpm')) {\n return { command: 'pnpm', args: ['exec', 'intlayer'] }; // pnpm requires 'exec'\n }\n if (userAgent?.startsWith('yarn')) {\n return { command: 'yarn', args: ['run', 'intlayer'] }; // yarn requires 'run' or 'exec'\n }\n\n // Default fallback\n return { command: 'npx', args: ['intlayer'] };\n};\n\nexport const runCI = async (commands: string[]) => {\n const credentialsEnv = process.env.INTLAYER_PROJECT_CREDENTIALS;\n let credentials: Record<string, { clientId: string; clientSecret: string }> =\n {};\n\n // Parse Credentials (Optional now)\n if (credentialsEnv) {\n try {\n credentials = JSON.parse(credentialsEnv);\n } catch {\n logger(\n 'INTLAYER_PROJECT_CREDENTIALS is not valid JSON. Proceeding without credentials.',\n {\n level: 'warn',\n }\n );\n }\n }\n\n const cwd = process.cwd();\n\n // Discover Projects\n const { projectsPath } = await listProjects();\n\n if (projectsPath.length === 0) {\n logger('No Intlayer projects found.', { level: 'warn' });\n return;\n }\n\n // 3. Determine Context: Single Project vs All Projects\n // Check if the current directory matches one of the discovered project paths\n const currentProject = projectsPath.find((p) => cwd === p);\n const projectsToRun = currentProject ? [currentProject] : projectsPath;\n\n const { command, args: pmArgs } = getPackageManagerCommand();\n const finalArgs = [...pmArgs, ...commands];\n\n logger(`CI: Using package manager: ${command}`, { level: 'verbose' });\n\n if (currentProject) {\n logger(`CI: Detected project context: ${currentProject}`, {\n level: 'info',\n });\n } else {\n logger(\n `CI: No specific project context detected. Iterating over ${projectsToRun.length} discovered projects...`,\n {\n level: 'info',\n }\n );\n }\n\n let hasError = false;\n\n // Iterate and Execute\n for (const projectPath of projectsToRun) {\n // Attempt to match credentials to the project path\n // We check if the key (relative or absolute) matches the resolved project path\n const credsEntry = Object.entries(credentials).find(([key]) => {\n const absKey = resolve(key);\n return absKey === projectPath || projectPath.endsWith(normalize(key));\n });\n\n const creds = credsEntry?.[1];\n\n logger(`\\nCI: Executing for ${projectPath}...`, {\n level: 'info',\n });\n\n const envVars = { ...process.env };\n\n if (creds) {\n envVars.INTLAYER_CLIENT_ID = creds.clientId;\n envVars.INTLAYER_CLIENT_SECRET = creds.clientSecret;\n } else if (credentialsEnv) {\n logger(\n `CI: No matching credentials found for ${projectPath} in INTLAYER_PROJECT_CREDENTIALS.`,\n { level: 'verbose' }\n );\n }\n\n const result = spawnSync(command, finalArgs, {\n cwd: projectPath,\n stdio: 'inherit',\n env: envVars,\n });\n\n if (result.status !== 0) {\n logger(`CI: Failed for ${projectPath}`, {\n level: 'error',\n });\n hasError = true;\n }\n }\n\n if (hasError) process.exit(1);\n};\n"],"mappings":";;;;;;AAMA,MAAM,iCAAiC;CACrC,MAAM,YAAY,QAAQ,IAAI;AAE9B,KAAI,WAAW,WAAW,MAAM,CAC9B,QAAO;EAAE,SAAS;EAAO,MAAM,CAAC,WAAW;EAAE;AAE/C,KAAI,WAAW,WAAW,OAAO,CAC/B,QAAO;EAAE,SAAS;EAAQ,MAAM,CAAC,QAAQ,WAAW;EAAE;AAExD,KAAI,WAAW,WAAW,OAAO,CAC/B,QAAO;EAAE,SAAS;EAAQ,MAAM,CAAC,OAAO,WAAW;EAAE;AAIvD,QAAO;EAAE,SAAS;EAAO,MAAM,CAAC,WAAW;EAAE;;AAG/C,MAAa,QAAQ,OAAO,aAAuB;CACjD,MAAM,iBAAiB,QAAQ,IAAI;CACnC,IAAIA,cACF,EAAE;AAGJ,KAAI,eACF,KAAI;AACF,gBAAc,KAAK,MAAM,eAAe;SAClC;AACN,SACE,mFACA,EACE,OAAO,QACR,CACF;;CAIL,MAAM,MAAM,QAAQ,KAAK;CAGzB,MAAM,EAAE,iBAAiB,MAAM,cAAc;AAE7C,KAAI,aAAa,WAAW,GAAG;AAC7B,SAAO,+BAA+B,EAAE,OAAO,QAAQ,CAAC;AACxD;;CAKF,MAAM,iBAAiB,aAAa,MAAM,MAAM,QAAQ,EAAE;CAC1D,MAAM,gBAAgB,iBAAiB,CAAC,eAAe,GAAG;CAE1D,MAAM,EAAE,SAAS,MAAM,WAAW,0BAA0B;CAC5D,MAAM,YAAY,CAAC,GAAG,QAAQ,GAAG,SAAS;AAE1C,QAAO,8BAA8B,WAAW,EAAE,OAAO,WAAW,CAAC;AAErE,KAAI,eACF,QAAO,iCAAiC,kBAAkB,EACxD,OAAO,QACR,CAAC;KAEF,QACE,4DAA4D,cAAc,OAAO,0BACjF,EACE,OAAO,QACR,CACF;CAGH,IAAI,WAAW;AAGf,MAAK,MAAM,eAAe,eAAe;EAQvC,MAAM,QALa,OAAO,QAAQ,YAAY,CAAC,MAAM,CAAC,SAAS;AAE7D,UADe,QAAQ,IAAI,KACT,eAAe,YAAY,SAAS,UAAU,IAAI,CAAC;IACrE,GAEyB;AAE3B,SAAO,uBAAuB,YAAY,MAAM,EAC9C,OAAO,QACR,CAAC;EAEF,MAAM,UAAU,EAAE,GAAG,QAAQ,KAAK;AAElC,MAAI,OAAO;AACT,WAAQ,qBAAqB,MAAM;AACnC,WAAQ,yBAAyB,MAAM;aAC9B,eACT,QACE,yCAAyC,YAAY,oCACrD,EAAE,OAAO,WAAW,CACrB;AASH,MANe,UAAU,SAAS,WAAW;GAC3C,KAAK;GACL,OAAO;GACP,KAAK;GACN,CAAC,CAES,WAAW,GAAG;AACvB,UAAO,kBAAkB,eAAe,EACtC,OAAO,SACR,CAAC;AACF,cAAW;;;AAIf,KAAI,SAAU,SAAQ,KAAK,EAAE"}
|
package/dist/esm/cli.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { build } from "./build.mjs";
|
|
2
|
+
import { runCI } from "./ci.mjs";
|
|
2
3
|
import { login } from "./auth/login.mjs";
|
|
3
4
|
import { getConfig } from "./config.mjs";
|
|
4
5
|
import { startEditor } from "./editor.mjs";
|
|
@@ -6,6 +7,7 @@ import { testMissingTranslations } from "./test/test.mjs";
|
|
|
6
7
|
import { fill } from "./fill/fill.mjs";
|
|
7
8
|
import { init } from "./init.mjs";
|
|
8
9
|
import { listContentDeclaration } from "./listContentDeclaration.mjs";
|
|
10
|
+
import { listProjectsCommand } from "./listProjects.mjs";
|
|
9
11
|
import { liveSync } from "./liveSync.mjs";
|
|
10
12
|
import { pull } from "./pull.mjs";
|
|
11
13
|
import { push } from "./push/push.mjs";
|
|
@@ -300,12 +302,37 @@ const setAPI = () => {
|
|
|
300
302
|
configOptions: extractConfigOptions(options)
|
|
301
303
|
});
|
|
302
304
|
});
|
|
305
|
+
program.command("projects").alias("project").description("List Intlayer projects").command("list").description("List all Intlayer projects in the directory").option("--base-dir [baseDir]", "Base directory to search from").option("--git-root", "Search from the git root directory instead of the base directory").option("--json", "Output the results as JSON").action((options) => {
|
|
306
|
+
listProjectsCommand({
|
|
307
|
+
baseDir: options.baseDir,
|
|
308
|
+
gitRoot: options.gitRoot,
|
|
309
|
+
json: options.json
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
program.command("projects-list").alias("pl").description("List all Intlayer projects in the directory").option("--base-dir [baseDir]", "Base directory to search from").option("--git-root", "Search from the git root directory instead of the base directory").option("--absolute", "Output the results as absolute paths").option("--json", "Output the results as JSON").action((options) => {
|
|
313
|
+
listProjectsCommand({
|
|
314
|
+
baseDir: options.baseDir,
|
|
315
|
+
gitRoot: options.gitRoot,
|
|
316
|
+
json: options.json,
|
|
317
|
+
absolute: options.absolute
|
|
318
|
+
});
|
|
319
|
+
});
|
|
303
320
|
/**
|
|
304
321
|
* CONTENT DECLARATION
|
|
305
322
|
*/
|
|
306
323
|
const contentProgram = program.command("content").description("Content declaration operations");
|
|
307
|
-
contentProgram.command("list").description("List the content declaration files").action(
|
|
308
|
-
|
|
324
|
+
contentProgram.command("list").description("List the content declaration files").option("--json", "Output the results as JSON").option("--absolute", "Output the results as absolute paths").action((options) => {
|
|
325
|
+
listContentDeclaration({
|
|
326
|
+
json: options.json,
|
|
327
|
+
absolute: options.absolute
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
program.command("list").description("List the content declaration files").option("--json", "Output the results as JSON").option("--absolute", "Output the results as absolute paths").action((options) => {
|
|
331
|
+
listContentDeclaration({
|
|
332
|
+
json: options.json,
|
|
333
|
+
absolute: options.absolute
|
|
334
|
+
});
|
|
335
|
+
});
|
|
309
336
|
const testProgram = contentProgram.command("test").description("Test if there are missing translations").option("--build [build]", "Build the dictionaries before testing to ensure the content is up to date. True will force the build, false will skip the build, undefined will allow using the cache of the build");
|
|
310
337
|
applyConfigOptions(testProgram);
|
|
311
338
|
testProgram.action((options) => {
|
|
@@ -424,6 +451,14 @@ const setAPI = () => {
|
|
|
424
451
|
});
|
|
425
452
|
applyConfigOptions(transformProgram);
|
|
426
453
|
program.parse(process.argv);
|
|
454
|
+
/**
|
|
455
|
+
* CI / AUTOMATION
|
|
456
|
+
*
|
|
457
|
+
* Used to iterate over all projects in a monorepo, and help to parse secrets
|
|
458
|
+
*/
|
|
459
|
+
program.command("ci").description("Run Intlayer commands with auto-injected credentials from INTLAYER_PROJECT_CREDENTIALS. Detects current project or iterates over all projects.").argument("<command...>", "The intlayer command to execute (e.g., \"fill\", \"push\")").allowUnknownOption().action((args) => {
|
|
460
|
+
runCI(args);
|
|
461
|
+
});
|
|
427
462
|
return program;
|
|
428
463
|
};
|
|
429
464
|
|