@intlayer/cli 9.6.0 → 9.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist/cjs/IntlayerEventListener.cjs.map +1 -1
  2. package/dist/cjs/auth/login.cjs +11 -0
  3. package/dist/cjs/auth/login.cjs.map +1 -1
  4. package/dist/cjs/auth/setupRepository.cjs +95 -0
  5. package/dist/cjs/auth/setupRepository.cjs.map +1 -0
  6. package/dist/cjs/cli.cjs +12 -6
  7. package/dist/cjs/cli.cjs.map +1 -1
  8. package/dist/cjs/editor.cjs +61 -32
  9. package/dist/cjs/editor.cjs.map +1 -1
  10. package/dist/cjs/fill/fill.cjs.map +1 -1
  11. package/dist/cjs/fill/translateDictionary.cjs.map +1 -1
  12. package/dist/cjs/index.cjs +14 -0
  13. package/dist/cjs/init.cjs +165 -11
  14. package/dist/cjs/init.cjs.map +1 -1
  15. package/dist/cjs/liveSync.cjs.map +1 -1
  16. package/dist/cjs/reviewDoc/reviewDoc.cjs.map +1 -1
  17. package/dist/cjs/reviewDoc/reviewDocBlockAware.cjs.map +1 -1
  18. package/dist/cjs/utils/checkAccess.cjs.map +1 -1
  19. package/dist/cjs/utils/detectGitRepository.cjs +87 -0
  20. package/dist/cjs/utils/detectGitRepository.cjs.map +1 -0
  21. package/dist/esm/IntlayerEventListener.mjs.map +1 -1
  22. package/dist/esm/auth/login.mjs +11 -0
  23. package/dist/esm/auth/login.mjs.map +1 -1
  24. package/dist/esm/auth/setupRepository.mjs +90 -0
  25. package/dist/esm/auth/setupRepository.mjs.map +1 -0
  26. package/dist/esm/cli.mjs +12 -6
  27. package/dist/esm/cli.mjs.map +1 -1
  28. package/dist/esm/editor.mjs +62 -34
  29. package/dist/esm/editor.mjs.map +1 -1
  30. package/dist/esm/fill/fill.mjs.map +1 -1
  31. package/dist/esm/fill/translateDictionary.mjs.map +1 -1
  32. package/dist/esm/index.mjs +7 -3
  33. package/dist/esm/init.mjs +163 -13
  34. package/dist/esm/init.mjs.map +1 -1
  35. package/dist/esm/liveSync.mjs.map +1 -1
  36. package/dist/esm/reviewDoc/reviewDoc.mjs.map +1 -1
  37. package/dist/esm/reviewDoc/reviewDocBlockAware.mjs.map +1 -1
  38. package/dist/esm/utils/checkAccess.mjs.map +1 -1
  39. package/dist/esm/utils/detectGitRepository.mjs +85 -0
  40. package/dist/esm/utils/detectGitRepository.mjs.map +1 -0
  41. package/dist/types/IntlayerEventListener.d.ts +1 -1
  42. package/dist/types/IntlayerEventListener.d.ts.map +1 -1
  43. package/dist/types/auth/login.d.ts +5 -0
  44. package/dist/types/auth/login.d.ts.map +1 -1
  45. package/dist/types/auth/setupRepository.d.ts +25 -0
  46. package/dist/types/auth/setupRepository.d.ts.map +1 -0
  47. package/dist/types/editor.d.ts +10 -0
  48. package/dist/types/editor.d.ts.map +1 -1
  49. package/dist/types/fill/fill.d.ts +1 -1
  50. package/dist/types/fill/translateDictionary.d.ts +1 -2
  51. package/dist/types/fill/translateDictionary.d.ts.map +1 -1
  52. package/dist/types/index.d.ts +7 -3
  53. package/dist/types/init.d.ts +19 -2
  54. package/dist/types/init.d.ts.map +1 -1
  55. package/dist/types/liveSync.d.ts.map +1 -1
  56. package/dist/types/reviewDoc/reviewDoc.d.ts +1 -1
  57. package/dist/types/reviewDoc/reviewDoc.d.ts.map +1 -1
  58. package/dist/types/reviewDoc/reviewDocBlockAware.d.ts +1 -2
  59. package/dist/types/reviewDoc/reviewDocBlockAware.d.ts.map +1 -1
  60. package/dist/types/translateDoc/types.d.ts +1 -1
  61. package/dist/types/utils/checkAccess.d.ts +2 -1
  62. package/dist/types/utils/checkAccess.d.ts.map +1 -1
  63. package/dist/types/utils/detectGitRepository.d.ts +34 -0
  64. package/dist/types/utils/detectGitRepository.d.ts.map +1 -0
  65. package/dist/types/utils/selectCmsEnvironment.d.ts +1 -1
  66. package/package.json +11 -11
@@ -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 * 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 {\n buildReviewReport,\n type ReviewReportSummary,\n} 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 { buildFileReviewReport, logReviewFileBlocks } from './reviewDocLog';\nimport { createReviewProgressLogger } from './reviewDocProgress';\nimport {\n formatReviewSynthesis,\n type ReviewFileResult,\n type ReviewOutcome,\n} from './reviewDocSynthesis';\n\n/**\n * How the review should be run.\n *\n * - `apply`: translate the diverging blocks with AI and write the target files.\n * - `report`: no AI. Log every block that needs attention (line numbers and\n * content, for both locales) so another agent can generate the translations.\n * - `synthesis`: no AI, no per-block output. Only log the final synthesis of the\n * documents that are up to date and the ones that still have blocks to edit.\n */\nexport type ReviewDocMode = 'apply' | 'report' | 'synthesis';\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 /** How the review should be run. Defaults to `apply`. See {@link ReviewDocMode}. */\n mode?: ReviewDocMode;\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 mode = 'apply',\n}: ReviewDocOptions) => {\n const configuration = getConfiguration(configOptions);\n logConfigDetails(configOptions);\n\n const appLogger = getAppLogger({ log: { ...configuration.log, prefix: '' } });\n\n // The `report` and `synthesis` modes never call an AI, so the AI access\n // checks are skipped.\n let aiClient: AIClient | undefined;\n let aiConfig: AIConfig | undefined;\n\n if (mode === 'apply') {\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 // In `synthesis` mode only the final recap is printed, so the per-file\n // progress logs are muted.\n const logProgress: typeof appLogger = (...content) => {\n if (mode === 'synthesis') return;\n appLogger(...content);\n };\n\n // Filled by every task, then rendered as the end-of-run synthesis.\n const reviewResults: ReviewFileResult[] = [];\n\n /** A document is up to date when no block has to be added, reviewed or removed. */\n const getOutcome = (summary: ReviewReportSummary): ReviewOutcome =>\n summary.review + summary.insertNew + summary.delete > 0\n ? 'toEdit'\n : 'upToDate';\n\n // Create all tasks to be processed\n const allTasks = docList.flatMap((docPath) =>\n locales.map((locale) => async () => {\n logProgress(\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 logProgress(\n `${colorize('⊘', ANSIColors.YELLOW)} File ${formatPath(relativePath)} already exists, skipping.`\n );\n reviewResults.push({ docPath, locale, outcome: 'skipped' });\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 logProgress(fileModificationData.message);\n reviewResults.push({ docPath, locale, outcome: 'skipped' });\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 logProgress(\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 logProgress(\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 logProgress(\n `Corresponding block (${formatLocale(locale)}): ${formatLineRanges(correspondingTargetLines)}`\n );\n }\n\n if (mode !== 'apply') {\n const report = await buildFileReviewReport(\n absoluteBaseFilePath,\n outputFilePath,\n changedLines\n );\n\n if (mode === 'report') {\n logReviewFileBlocks(\n report,\n absoluteBaseFilePath,\n locale as Locale,\n baseLocale,\n configOptions\n );\n }\n\n reviewResults.push({\n docPath,\n locale,\n outcome: getOutcome(report.summary),\n summary: report.summary,\n });\n return;\n }\n\n const reviewSummary = 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 reviewResults.push({\n docPath,\n locale,\n outcome: getOutcome(reviewSummary),\n summary: reviewSummary,\n });\n })\n );\n\n // The per-file logs are muted in `synthesis` mode, so a progress line keeps\n // the run readable while every document is compared.\n const progressLogger = createReviewProgressLogger(allTasks.length);\n\n if (mode === 'synthesis') progressLogger.start();\n\n await parallelize(\n allTasks,\n async (task) => {\n await task();\n progressLogger.increment();\n },\n nbSimultaneousFileProcessed ?? 3\n );\n\n progressLogger.stop();\n\n const synthesis = formatReviewSynthesis(reviewResults, {\n hasAppliedChanges: mode === 'apply',\n });\n\n for (const line of synthesis.split('\\n')) {\n appLogger(line);\n }\n\n return reviewResults;\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, AIOptions } from '@intlayer/ai';\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 {\n buildReviewReport,\n type ReviewReportSummary,\n} 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 { buildFileReviewReport, logReviewFileBlocks } from './reviewDocLog';\nimport { createReviewProgressLogger } from './reviewDocProgress';\nimport {\n formatReviewSynthesis,\n type ReviewFileResult,\n type ReviewOutcome,\n} from './reviewDocSynthesis';\n\n/**\n * How the review should be run.\n *\n * - `apply`: translate the diverging blocks with AI and write the target files.\n * - `report`: no AI. Log every block that needs attention (line numbers and\n * content, for both locales) so another agent can generate the translations.\n * - `synthesis`: no AI, no per-block output. Only log the final synthesis of the\n * documents that are up to date and the ones that still have blocks to edit.\n */\nexport type ReviewDocMode = 'apply' | 'report' | 'synthesis';\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 /** How the review should be run. Defaults to `apply`. See {@link ReviewDocMode}. */\n mode?: ReviewDocMode;\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 mode = 'apply',\n}: ReviewDocOptions) => {\n const configuration = getConfiguration(configOptions);\n logConfigDetails(configOptions);\n\n const appLogger = getAppLogger({ log: { ...configuration.log, prefix: '' } });\n\n // The `report` and `synthesis` modes never call an AI, so the AI access\n // checks are skipped.\n let aiClient: AIClient | undefined;\n let aiConfig: AIConfig | undefined;\n\n if (mode === 'apply') {\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 // In `synthesis` mode only the final recap is printed, so the per-file\n // progress logs are muted.\n const logProgress: typeof appLogger = (...content) => {\n if (mode === 'synthesis') return;\n appLogger(...content);\n };\n\n // Filled by every task, then rendered as the end-of-run synthesis.\n const reviewResults: ReviewFileResult[] = [];\n\n /** A document is up to date when no block has to be added, reviewed or removed. */\n const getOutcome = (summary: ReviewReportSummary): ReviewOutcome =>\n summary.review + summary.insertNew + summary.delete > 0\n ? 'toEdit'\n : 'upToDate';\n\n // Create all tasks to be processed\n const allTasks = docList.flatMap((docPath) =>\n locales.map((locale) => async () => {\n logProgress(\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 logProgress(\n `${colorize('⊘', ANSIColors.YELLOW)} File ${formatPath(relativePath)} already exists, skipping.`\n );\n reviewResults.push({ docPath, locale, outcome: 'skipped' });\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 logProgress(fileModificationData.message);\n reviewResults.push({ docPath, locale, outcome: 'skipped' });\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 logProgress(\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 logProgress(\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 logProgress(\n `Corresponding block (${formatLocale(locale)}): ${formatLineRanges(correspondingTargetLines)}`\n );\n }\n\n if (mode !== 'apply') {\n const report = await buildFileReviewReport(\n absoluteBaseFilePath,\n outputFilePath,\n changedLines\n );\n\n if (mode === 'report') {\n logReviewFileBlocks(\n report,\n absoluteBaseFilePath,\n locale as Locale,\n baseLocale,\n configOptions\n );\n }\n\n reviewResults.push({\n docPath,\n locale,\n outcome: getOutcome(report.summary),\n summary: report.summary,\n });\n return;\n }\n\n const reviewSummary = 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 reviewResults.push({\n docPath,\n locale,\n outcome: getOutcome(reviewSummary),\n summary: reviewSummary,\n });\n })\n );\n\n // The per-file logs are muted in `synthesis` mode, so a progress line keeps\n // the run readable while every document is compared.\n const progressLogger = createReviewProgressLogger(allTasks.length);\n\n if (mode === 'synthesis') progressLogger.start();\n\n await parallelize(\n allTasks,\n async (task) => {\n await task();\n progressLogger.increment();\n },\n nbSimultaneousFileProcessed ?? 3\n );\n\n progressLogger.stop();\n\n const synthesis = formatReviewSynthesis(reviewResults, {\n hasAppliedChanges: mode === 'apply',\n });\n\n for (const line of synthesis.split('\\n')) {\n appLogger(line);\n }\n\n return reviewResults;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAyEA,MAAa,YAAY,OAAO,EAC9B,YACA,SACA,qBACA,YACA,WACA,6BACA,eACA,oBACA,sBACA,qBACA,cACA,YACA,OAAO,cACe;CACtB,MAAM,gBAAgB,iBAAiB,aAAa;CACpD,iBAAiB,aAAa;CAE9B,MAAM,YAAY,aAAa,EAAE,KAAK;EAAE,GAAG,cAAc;EAAK,QAAQ;CAAG,EAAE,CAAC;CAI5E,IAAI;CACJ,IAAI;CAEJ,IAAI,SAAS,SAAS;EACpB,MAAM,WAAW,MAAM,QAAQ,eAAe,SAAS;EAEvD,IAAI,CAAC,UAAU,aAAa,OAAO,CAAC;EAEpC,WAAW,SAAS;EACpB,WAAW,SAAS;EAEpB,IAAI,SAAS,cAAc,YAAY,UAAU;GAC/C,MAAM,EAAE,aAAa,UAAU,MAAM,SAAS,iBAAiB,QAAQ;GACvE,IAAI,CAAC,aAAa;IAChB,UAAU,GAAG,EAAE,GAAG,OAAO;IACzB,OAAO,CAAC;GACV;EACF;CACF;CAEA,IAAI,+BAA+B,8BAA8B,IAAI;EACnE,UACE,kDAAkD,4BAA4B,8CAChF;EACA,8BAA8B;CAChC;CAEA,IAAI,UAAoB,MAAM,GAAG,YAAY,EAC3C,QAAQ,oBACV,CAAC;CAED,IAAI,YAAY;EACd,MAAM,kBAAkB,MAAM,aAAa,UAAU;EAErD,IAAI,iBAIF,UAAU,QAAQ,QAAQ,SACxB,gBAAgB,MAAM,YAAY,KAAK,QAAQ,IAAI,GAAG,IAAI,MAAM,OAAO,CACzE;CAEJ;CAIA,UAAU,kBAAkB,aAAa,UAAU,GAAG;CACtD,UACE,aAAa,eAAe,QAAQ,MAAM,EAAE,cAAc,aAAa,OAAO,EAAE,GAClF;CAEA,UAAU,aAAa,eAAe,QAAQ,MAAM,EAAE,QAAQ;CAC9D,UAAU,QAAQ,KAAK,SAAS,MAAM,WAAW,IAAI,EAAE,GAAG,CAAC;CAI3D,MAAM,eAAiC,GAAG,YAAY;EACpD,IAAI,SAAS,aAAa;EAC1B,UAAU,GAAG,OAAO;CACtB;CAGA,MAAM,gBAAoC,CAAC;;CAG3C,MAAM,cAAc,YAClB,QAAQ,SAAS,QAAQ,YAAY,QAAQ,SAAS,IAClD,WACA;CAGN,MAAM,WAAW,QAAQ,SAAS,YAChC,QAAQ,KAAK,WAAW,YAAY;EAClC,YACE,mBAAmB,WAAW,OAAO,EAAE,MAAM,aAAa,MAAM,GAClE;EAEA,MAAM,uBAAuB,KAAK,cAAc,OAAO,SAAS,OAAO;EACvE,MAAM,iBAAiB,kBACrB,sBACA,QACA,UACF;EAGA,IAAI,gBAAgB,WAAW,cAAc,GAAG;GAC9C,MAAM,eAAe,SACnB,cAAc,OAAO,SACrB,cACF;GACA,YACE,GAAG,SAAS,KAAK,WAAW,MAAM,EAAE,QAAQ,WAAW,YAAY,EAAE,2BACvE;GACA,cAAc,KAAK;IAAE;IAAS;IAAQ,SAAS;GAAU,CAAC;GAC1D;EACF;EAGA,IAAI,WAAW,cAAc,GAAG;GAC9B,MAAM,uBAAuB,uBAAuB,gBAAgB;IAClE;IACA;GACF,CAAC;GAED,IAAI,qBAAqB,WAAW;IAClC,YAAY,qBAAqB,OAAO;IACxC,cAAc,KAAK;KAAE;KAAS;KAAQ,SAAS;IAAU,CAAC;IAC1D;GACF;EACF,OAAO,IAAI,wBAAwB,qBAEjC,YACE,GAAG,SAAS,KAAK,WAAW,MAAM,EAAE,QAAQ,WAAW,cAAc,EAAE,mDACzE;EAGF,IAAI;EAEJ,IAAI,YAAY;GACd,MAAM,kBAAkB,MAAM,aAC5B,sBACA,UACF;GACA,eAAe;GAOf,YACE,kBAAkB,aAAa,UAAU,EAAE,KAAK,iBAAiB,eAAe,GAClF;GAEA,MAAM,WAAW,MAAM,SAAS,sBAAsB,OAAO,CAAC,CAAC,YACvD,EACR;GACA,MAAM,aAAa,WAAW,cAAc,IACxC,MAAM,SAAS,gBAAgB,OAAO,CAAC,CAAC,YAAY,EAAE,IACtD;GAEJ,MAAM,EAAE,WAAW,kBAAkB;IACnC;IACA;IACA,cAAc;GAChB,CAAC;GAED,MAAM,2BAA2B,OAAO,SAAS,UAAU;IACzD,IAAI,MAAM,WAAW,YAAY,CAAC,MAAM,iBAAiB,OAAO,CAAC;IAEjE,MAAM,EAAE,OAAO,QAAQ,MAAM;IAC7B,OAAO,MAAM,KACX,EAAE,QAAQ,MAAM,QAAQ,EAAE,IACzB,SAAS,WAAW,QAAQ,MAC/B;GACF,CAAC;GAED,YACE,wBAAwB,aAAa,MAAM,EAAE,KAAK,iBAAiB,wBAAwB,GAC7F;EACF;EAEA,IAAI,SAAS,SAAS;GACpB,MAAM,SAAS,MAAM,sBACnB,sBACA,gBACA,YACF;GAEA,IAAI,SAAS,UACX,oBACE,QACA,sBACA,QACA,YACA,aACF;GAGF,cAAc,KAAK;IACjB;IACA;IACA,SAAS,WAAW,OAAO,OAAO;IAClC,SAAS,OAAO;GAClB,CAAC;GACD;EACF;EAEA,MAAM,gBAAgB,MAAM,qBAC1B,sBACA,gBACA,QACA,YACA,WACA,eACA,oBACA,cACA,UACA,QACF;EAEA,cAAc,KAAK;GACjB;GACA;GACA,SAAS,WAAW,aAAa;GACjC,SAAS;EACX,CAAC;CACH,CAAC,CACH;CAIA,MAAM,iBAAiB,2BAA2B,SAAS,MAAM;CAEjE,IAAI,SAAS,aAAa,eAAe,MAAM;CAE/C,MAAM,YACJ,UACA,OAAO,SAAS;EACd,MAAM,KAAK;EACX,eAAe,UAAU;CAC3B,GACA,+BAA+B,CACjC;CAEA,eAAe,KAAK;CAEpB,MAAM,YAAY,sBAAsB,eAAe,EACrD,mBAAmB,SAAS,QAC9B,CAAC;CAED,KAAK,MAAM,QAAQ,UAAU,MAAM,IAAI,GACrC,UAAU,IAAI;CAGhB,OAAO;AACT"}
@@ -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 * 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 type PlannedAction,\n type ReviewReportSummary,\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 *\n * @returns The per-action block counts of the alignment plan, so the caller can\n * synthesize what was reused and what had to be edited.\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): Promise<ReviewReportSummary> => {\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 countActions = (kind: PlannedAction['kind']): number =>\n plan.actions.filter((action: PlannedAction) => action.kind === kind).length;\n\n const reviewSummary: ReviewReportSummary = {\n reuse: countActions('reuse'),\n review: countActions('review'),\n insertNew: countActions('insert_new'),\n delete: countActions('delete'),\n };\n\n const deleteCount = reviewSummary.delete;\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(reviewSummary.reuse)}, review=${colorizeNumber(reviewSummary.review)}, new=${colorizeNumber(reviewSummary.insertNew)}, 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 changes needed).`\n );\n return reviewSummary;\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 return reviewSummary;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+CA,MAAa,uBAAuB,OAClC,cACA,gBACA,QACA,YACA,WACA,eACA,oBACA,cACA,UACA,aACiC;CACjC,MAAM,gBAAgB,iBAAiB,aAAa;CACpD,MAAM,oBAAoB,aAAa,EACrC,KAAK;EAAE,GAAG,cAAc;EAAK,QAAQ;CAAG,EAC1C,CAAC;CAED,MAAM,WAAW,MAAM,SAAS,cAAc,OAAO;CACrD,MAAM,aAAa,MAAM,SAAS,gBAAgB,OAAO,CAAC,CAAC,YAAY,EAAE;CAEzE,MAAM,aAAa,UAAU,8BAA8B,OAAO,CAAC,CAChE,WAAW,kBAAkB,GAAG,aAAa,QAAQ,KAAK,GAAG,CAAC,CAC9D,WAAW,sBAAsB,GAAG,aAAa,YAAY,KAAK,GAAG,CAAC,CACtE,QAAQ,0BAA0B,WAAW,sBAAsB,GAAG,CAAC,CACvE,QAAQ,0BAA0B,sBAAsB,GAAG;CAE9D,MAAM,iBAAiB,GAAG,WAAW,UAAU,GAAG,WAAW,YAAY,IAAI,WAAW,UAAU;CAClG,MAAM,aAAa,CACjB,MAAM,gBAAgB,EAAE,SAAS,GAAG,CAAC,GACrC,KAAK,WAAW,OAClB,CAAC,CAAC,KAAK,EAAE;CACT,MAAM,aAAa,GAAG,WAAW,UAAU,GAAG,WAAW,YAAY,IAAI,WAAW,UAAU,IAAI,aAAa,MAAM,IAAI,WAAW,UAAU;CAC9I,MAAM,SAAS,CACb,MAAM,YAAY,EAAE,SAAS,GAAG,CAAC,GACjC,KAAK,WAAW,OAClB,CAAC,CAAC,KAAK,EAAE;CAGT,MAAM,EAAE,YAAY,cAAc,MAAM,qBACtC,mBAAmB;EACjB;EACA;EACA;CACF,CAAC;CAEH,MAAM,gBAAgB,SACpB,KAAK,QAAQ,QAAQ,WAA0B,OAAO,SAAS,IAAI,CAAC,CAAC;CAEvE,MAAM,gBAAqC;EACzC,OAAO,aAAa,OAAO;EAC3B,QAAQ,aAAa,QAAQ;EAC7B,WAAW,aAAa,YAAY;EACpC,QAAQ,aAAa,QAAQ;CAC/B;CAEA,MAAM,cAAc,cAAc;CAElC,kBACE,GAAG,WAAW,qDAAqD,eAAe,WAAW,MAAM,EAAE,WAAW,eAAe,aAAa,MAAM,GACpJ;CACA,kBACE,GAAG,WAAW,iBAAiB,eAAe,cAAc,KAAK,EAAE,WAAW,eAAe,cAAc,MAAM,EAAE,QAAQ,eAAe,cAAc,SAAS,EAAE,WAAW,eAAe,WAAW,GAC1M;CAIA,MAAM,sCAAsB,IAAI,IAAoB;CAGpD,KAAK,MAAM,CAAC,aAAa,WAAW,KAAK,QAAQ,QAAQ,GACvD,IAAI,OAAO,SAAS,UAClB,oBAAoB,IAAI,aAAa,EAAE;CAI3C,MAAM,0BAAgC;EACpC,MAAM,SAAS,sBACb,MACA,cACA,mBACF;EACA,UAAU,QAAQ,cAAc,GAAG,EAAE,WAAW,KAAK,CAAC;EACtD,cAAc,gBAAgB,MAAM;CACtC;CAEA,IAAI,cAAc,GAAG;EACnB,kBAAkB;EAClB,kBACE,GAAG,aAAa,eAAe,WAAW,EAAE,8BAC9C;CACF;CAEA,IAAI,iBAAiB,WAAW,GAAG;EACjC,IAAI,gBAAgB,GAAG;GACrB,kBACE,GAAG,WAAW,sDAChB;GACA,kBAAkB;EACpB;EACA,kBACE,GAAG,SAAS,KAAK,WAAW,KAAK,EAAE,QAAQ,WAAW,cAAc,EAAE,2CACxE;EACA,OAAO;CACT;CAEA,kBACE,GAAG,WAAW,sBAAsB,eAAe,iBAAiB,MAAM,EAAE,wBAC9E;CAKA,MAAM,mBAAmB,iBACtB,KAAK,SAAS,mBAAmB;EAChC;EACA,eAAe,gBAAgB;CACjC,EAAE,CAAC,CACF,QAAQ;CAEX,KAAK,MAAM,EAAE,SAAS,mBAAmB,kBAAkB;EACzD,MAAM,YAAY,QAAQ;EAE1B,MAAM,kCACJ,WAAW,cAAc,MAAM,iBAAiB,OAAO,0BAA0B,aAAa,YAAY,KAAK,EAAE,uCAEjH,UAAU,UACV;EAEF,MAAM,6BACJ,WAAW,cAAc,MAAM,iBAAiB,OAAO,uCAAuC,aAAa,QAAQ,KAAK,EAAE,2BAEzH,QAAQ,mBAAmB,MAC5B;EAEF,MAAM,sBAAsB,MAAM,aAAa,YAAY;GACzD,MAAM,SAAS,MAAM,eACnB;IACE;KAAE,MAAM;KAAU,SAAS;IAAW;IACtC;KAAE,MAAM;KAAU,SAAS,0BAA0B;IAAE;IACvD;KAAE,MAAM;KAAU,SAAS,qBAAqB;IAAE;IAClD;KACE,MAAM;KACN,SAAS,6CAA6C,eAAe,aAAa,EAAE,MAAM,eAAe,iBAAiB,MAAM,EAAE,kCAAkC,cAAc,QAAQ,OAAO,EAAE,IAAI,OAAO;IAChN;GACF,GACA,CAAC;IAAE,MAAM;IAAQ,SAAS,UAAU;GAAQ,CAAC,GAC7C,WACA,eACA,UACA,QACF;GAEA,kBACE,GAAG,SAAS,eAAe,OAAO,SAAS,EAAE,uBAAuB,eAAe,aAAa,EAAE,MAAM,eAAe,iBAAiB,MAAM,GAChJ;GAEA,IAAI,iBAAiB,cACnB,QAAQ,aACR,UAAU,OACZ;GACA,iBAAiB,sBAAsB,gBAAgB,UAAU,OAAO;GAQxE,IAAI,CANY,oBACd,UAAU,SACV,gBACA,iBAGS,GACT,MAAM,IAAI,MACR,yEACF;GAGF,OAAO;EACT,CAAC,CAAC,CAAC;EAEH,oBAAoB,IAAI,QAAQ,aAAa,mBAAmB;EAGhE,kBAAkB;CACpB;CAEA,kBACE,GAAG,SAAS,KAAK,WAAW,KAAK,EAAE,QAAQ,WAAW,cAAc,EAAE,+BACxE;CAEA,OAAO;AACT"}
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, AIOptions } from '@intlayer/ai';\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 type PlannedAction,\n type ReviewReportSummary,\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 *\n * @returns The per-action block counts of the alignment plan, so the caller can\n * synthesize what was reused and what had to be edited.\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): Promise<ReviewReportSummary> => {\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 countActions = (kind: PlannedAction['kind']): number =>\n plan.actions.filter((action: PlannedAction) => action.kind === kind).length;\n\n const reviewSummary: ReviewReportSummary = {\n reuse: countActions('reuse'),\n review: countActions('review'),\n insertNew: countActions('insert_new'),\n delete: countActions('delete'),\n };\n\n const deleteCount = reviewSummary.delete;\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(reviewSummary.reuse)}, review=${colorizeNumber(reviewSummary.review)}, new=${colorizeNumber(reviewSummary.insertNew)}, 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 changes needed).`\n );\n return reviewSummary;\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 return reviewSummary;\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"checkAccess.mjs","names":[],"sources":["../../../src/utils/checkAccess.ts"],"sourcesContent":["import type { AIOptions, IntlayerAPIProxy } from '@intlayer/api';\nimport { getIntlayerAPIProxy } from '@intlayer/api';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, getAppLogger } from '@intlayer/config/logger';\nimport { extractErrorMessage } from '@intlayer/config/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { login } from '../auth/login';\nimport { type CliSessionData, readCliSessionToken } from '../auth/sessionToken';\nimport { checkConfigConsistency } from './checkConfigConsistency';\n\nexport const getAuthenticatedAPI = async (\n configuration?: IntlayerConfig\n): Promise<IntlayerAPIProxy> => {\n let sessionData: CliSessionData | null = null;\n\n // First use session data if it exists\n if (configuration) {\n sessionData = await readCliSessionToken(configuration);\n }\n\n return getIntlayerAPIProxy(undefined, configuration, sessionData?.token);\n};\n\nconst checkProjectConfigConsistency = (\n project: { configuration?: any } | null | undefined,\n configuration: IntlayerConfig,\n appLogger: ReturnType<typeof getAppLogger>\n): void => {\n if (!project?.configuration) return;\n\n try {\n let remoteConfigToCheck = project.configuration;\n\n if (\n remoteConfigToCheck.ai &&\n 'apiKeyConfigured' in remoteConfigToCheck.ai\n ) {\n const { apiKeyConfigured, ...restAi } = remoteConfigToCheck.ai as any;\n remoteConfigToCheck = { ...remoteConfigToCheck, ai: restAi };\n }\n\n checkConfigConsistency(remoteConfigToCheck, 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 configuration push', ANSIColors.CYAN),\n colorize('(see doc:', ANSIColors.GREY_LIGHT),\n colorize(\n 'https://intlayer.org/doc/concept/cli/push',\n ANSIColors.GREY_DARK\n ),\n colorize(')', ANSIColors.GREY_LIGHT),\n '.',\n ],\n { level: 'warn' }\n );\n }\n};\n\nexport const checkCMSAuth = async (\n configuration: IntlayerConfig,\n shouldCheckConfigConsistency: boolean = true,\n shouldAutoLogin: boolean = true\n): Promise<boolean> => {\n const appLogger = getAppLogger(configuration);\n\n // Try CLI session token first (short-lived 2h token stored in tempDir)\n const sessionData = await readCliSessionToken(configuration);\n if (sessionData) {\n const intlayerAPI = getIntlayerAPIProxy(\n undefined,\n configuration,\n sessionData.token\n );\n\n try {\n const result = await intlayerAPI.oAuth.getCliSessionMe();\n const project = result.data?.project;\n\n if (project && shouldCheckConfigConsistency) {\n checkProjectConfigConsistency(project, configuration, appLogger);\n }\n\n return true;\n } catch (error) {\n const message = extractErrorMessage(error);\n appLogger(message, { level: 'error' });\n appLogger(\n [\n 'Run',\n colorize('npx intlayer login', ANSIColors.CYAN),\n 'to set up a new session.',\n ],\n { level: 'error' }\n );\n return false;\n }\n }\n\n // Fall back to client_credentials (access key)\n const hasCMSAuth =\n configuration.editor.clientId && configuration.editor.clientSecret;\n if (!hasCMSAuth) {\n if (shouldAutoLogin) {\n appLogger(\n [\n 'No credentials found. Starting login...',\n colorize('( Set', ANSIColors.GREY_LIGHT),\n colorize('INTLAYER_CLIENT_ID', ANSIColors.BLUE),\n colorize('and', ANSIColors.GREY_LIGHT),\n colorize('INTLAYER_CLIENT_SECRET', ANSIColors.BLUE),\n colorize('in your .env to skip this step)', ANSIColors.GREY_LIGHT),\n ],\n { level: 'warn' }\n );\n await login({ exitAfter: false });\n return checkCMSAuth(configuration, shouldCheckConfigConsistency, false);\n }\n\n appLogger(\n [\n 'CMS auth not provided. Run',\n colorize('npx intlayer login', ANSIColors.CYAN),\n 'to authenticate, or set',\n colorize('INTLAYER_CLIENT_ID', ANSIColors.BLUE),\n 'and',\n colorize('INTLAYER_CLIENT_SECRET', ANSIColors.BLUE),\n 'in your .env file',\n colorize('(see doc:', ANSIColors.GREY_LIGHT),\n colorize('https://intlayer.org/doc/concept/cms', ANSIColors.GREY_DARK),\n colorize(')', ANSIColors.GREY_LIGHT),\n '.',\n ],\n { level: 'error' }\n );\n\n return false;\n }\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 return true;\n }\n\n if (shouldCheckConfigConsistency) {\n checkProjectConfigConsistency(project, configuration, appLogger);\n }\n } catch (error) {\n const message = extractErrorMessage(error);\n appLogger(message, { level: 'error' });\n appLogger(\n [\n 'Your access keys appear to be invalid. Run',\n colorize('npx intlayer login', ANSIColors.CYAN),\n 'to set up new access keys.',\n ],\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 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 // No local AI key — delegate to CMS auth check (which will auto-login if needed)\n return await checkCMSAuth(configuration, shouldCheckConfigConsistency);\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"checkAccess.mjs","names":[],"sources":["../../../src/utils/checkAccess.ts"],"sourcesContent":["import type { AIOptions } from '@intlayer/ai';\nimport type { IntlayerAPIProxy } from '@intlayer/api';\nimport { getIntlayerAPIProxy } from '@intlayer/api';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, getAppLogger } from '@intlayer/config/logger';\nimport { extractErrorMessage } from '@intlayer/config/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { login } from '../auth/login';\nimport { type CliSessionData, readCliSessionToken } from '../auth/sessionToken';\nimport { checkConfigConsistency } from './checkConfigConsistency';\n\nexport const getAuthenticatedAPI = async (\n configuration?: IntlayerConfig\n): Promise<IntlayerAPIProxy> => {\n let sessionData: CliSessionData | null = null;\n\n // First use session data if it exists\n if (configuration) {\n sessionData = await readCliSessionToken(configuration);\n }\n\n return getIntlayerAPIProxy(undefined, configuration, sessionData?.token);\n};\n\nconst checkProjectConfigConsistency = (\n project: { configuration?: any } | null | undefined,\n configuration: IntlayerConfig,\n appLogger: ReturnType<typeof getAppLogger>\n): void => {\n if (!project?.configuration) return;\n\n try {\n let remoteConfigToCheck = project.configuration;\n\n if (\n remoteConfigToCheck.ai &&\n 'apiKeyConfigured' in remoteConfigToCheck.ai\n ) {\n const { apiKeyConfigured, ...restAi } = remoteConfigToCheck.ai as any;\n remoteConfigToCheck = { ...remoteConfigToCheck, ai: restAi };\n }\n\n checkConfigConsistency(remoteConfigToCheck, 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 configuration push', ANSIColors.CYAN),\n colorize('(see doc:', ANSIColors.GREY_LIGHT),\n colorize(\n 'https://intlayer.org/doc/concept/cli/push',\n ANSIColors.GREY_DARK\n ),\n colorize(')', ANSIColors.GREY_LIGHT),\n '.',\n ],\n { level: 'warn' }\n );\n }\n};\n\nexport const checkCMSAuth = async (\n configuration: IntlayerConfig,\n shouldCheckConfigConsistency: boolean = true,\n shouldAutoLogin: boolean = true\n): Promise<boolean> => {\n const appLogger = getAppLogger(configuration);\n\n // Try CLI session token first (short-lived 2h token stored in tempDir)\n const sessionData = await readCliSessionToken(configuration);\n if (sessionData) {\n const intlayerAPI = getIntlayerAPIProxy(\n undefined,\n configuration,\n sessionData.token\n );\n\n try {\n const result = await intlayerAPI.oAuth.getCliSessionMe();\n const project = result.data?.project;\n\n if (project && shouldCheckConfigConsistency) {\n checkProjectConfigConsistency(project, configuration, appLogger);\n }\n\n return true;\n } catch (error) {\n const message = extractErrorMessage(error);\n appLogger(message, { level: 'error' });\n appLogger(\n [\n 'Run',\n colorize('npx intlayer login', ANSIColors.CYAN),\n 'to set up a new session.',\n ],\n { level: 'error' }\n );\n return false;\n }\n }\n\n // Fall back to client_credentials (access key)\n const hasCMSAuth =\n configuration.editor.clientId && configuration.editor.clientSecret;\n if (!hasCMSAuth) {\n if (shouldAutoLogin) {\n appLogger(\n [\n 'No credentials found. Starting login...',\n colorize('( Set', ANSIColors.GREY_LIGHT),\n colorize('INTLAYER_CLIENT_ID', ANSIColors.BLUE),\n colorize('and', ANSIColors.GREY_LIGHT),\n colorize('INTLAYER_CLIENT_SECRET', ANSIColors.BLUE),\n colorize('in your .env to skip this step)', ANSIColors.GREY_LIGHT),\n ],\n { level: 'warn' }\n );\n await login({ exitAfter: false });\n return checkCMSAuth(configuration, shouldCheckConfigConsistency, false);\n }\n\n appLogger(\n [\n 'CMS auth not provided. Run',\n colorize('npx intlayer login', ANSIColors.CYAN),\n 'to authenticate, or set',\n colorize('INTLAYER_CLIENT_ID', ANSIColors.BLUE),\n 'and',\n colorize('INTLAYER_CLIENT_SECRET', ANSIColors.BLUE),\n 'in your .env file',\n colorize('(see doc:', ANSIColors.GREY_LIGHT),\n colorize('https://intlayer.org/doc/concept/cms', ANSIColors.GREY_DARK),\n colorize(')', ANSIColors.GREY_LIGHT),\n '.',\n ],\n { level: 'error' }\n );\n\n return false;\n }\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 return true;\n }\n\n if (shouldCheckConfigConsistency) {\n checkProjectConfigConsistency(project, configuration, appLogger);\n }\n } catch (error) {\n const message = extractErrorMessage(error);\n appLogger(message, { level: 'error' });\n appLogger(\n [\n 'Your access keys appear to be invalid. Run',\n colorize('npx intlayer login', ANSIColors.CYAN),\n 'to set up new access keys.',\n ],\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 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 // No local AI key — delegate to CMS auth check (which will auto-login if needed)\n return await checkCMSAuth(configuration, shouldCheckConfigConsistency);\n};\n"],"mappings":"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"}
@@ -0,0 +1,85 @@
1
+ import { relative, sep } from "node:path";
2
+ import { searchConfigurationFile } from "@intlayer/config/node";
3
+ import { spawnSync } from "node:child_process";
4
+
5
+ //#region src/utils/detectGitRepository.ts
6
+ const HOST_PROVIDERS = {
7
+ "github.com": "github",
8
+ "gitlab.com": "gitlab",
9
+ "bitbucket.org": "bitbucket",
10
+ "codeberg.org": "codeberg",
11
+ "gitee.com": "gitee"
12
+ };
13
+ /** Provider of a host; any host naming GitLab is taken as self-managed. */
14
+ const getHostProvider = (host) => HOST_PROVIDERS[host] ?? (host.includes("gitlab") ? "gitlab" : void 0);
15
+ /**
16
+ * Parses an HTTPS, SSH (`ssh://`) or scp-like (`git@host:owner/repo.git`)
17
+ * remote URL. Returns `null` for an unknown host or a malformed URL.
18
+ */
19
+ const parseGitRemoteUrl = (remoteUrl) => {
20
+ const trimmedUrl = remoteUrl.trim();
21
+ const scpLikeMatch = /^(?:[^@/\s]+@)?([^:/\s]+):(?!\/)(.+)$/.exec(trimmedUrl);
22
+ let host;
23
+ let path;
24
+ if (scpLikeMatch && !trimmedUrl.includes("://")) [, host, path] = scpLikeMatch;
25
+ else try {
26
+ const url = new URL(trimmedUrl);
27
+ host = url.hostname;
28
+ path = url.pathname;
29
+ } catch {
30
+ return null;
31
+ }
32
+ const provider = getHostProvider(host.toLowerCase());
33
+ const segments = path.replace(/^\/+|\/+$/g, "").replace(/\.git$/, "").split("/").filter(Boolean);
34
+ if (!provider || segments.length < 2) return null;
35
+ if (provider !== "gitlab" && segments.length !== 2) return null;
36
+ const repository = segments[segments.length - 1];
37
+ const owner = segments.slice(0, -1).join("/");
38
+ const isSelfManagedGitLab = provider === "gitlab" && host !== "gitlab.com";
39
+ return {
40
+ provider,
41
+ owner,
42
+ repository,
43
+ url: `https://${host}/${owner}/${repository}`,
44
+ ...isSelfManagedGitLab && { instanceUrl: `https://${host}` }
45
+ };
46
+ };
47
+ /** Output of a git command, `undefined` when it fails. */
48
+ const runGit = (cwd, args) => {
49
+ const result = spawnSync("git", args, {
50
+ cwd,
51
+ encoding: "utf-8"
52
+ });
53
+ if (result.status !== 0) return void 0;
54
+ return result.stdout.trim() || void 0;
55
+ };
56
+ /**
57
+ * Detects the hosted repository of the project from its `origin` remote,
58
+ * with the checked-out branch and the config file path in the repository.
59
+ * Returns `null` outside a git repository or for an unsupported host.
60
+ */
61
+ const detectGitRepository = (projectRoot) => {
62
+ const remoteUrl = runGit(projectRoot, [
63
+ "remote",
64
+ "get-url",
65
+ "origin"
66
+ ]);
67
+ const remote = remoteUrl ? parseGitRemoteUrl(remoteUrl) : null;
68
+ if (!remote) return null;
69
+ const branch = runGit(projectRoot, [
70
+ "rev-parse",
71
+ "--abbrev-ref",
72
+ "HEAD"
73
+ ]);
74
+ const gitRoot = runGit(projectRoot, ["rev-parse", "--show-toplevel"]);
75
+ const { configurationFilePath } = searchConfigurationFile(projectRoot);
76
+ return {
77
+ ...remote,
78
+ branch: branch && branch !== "HEAD" ? branch : void 0,
79
+ configFilePath: gitRoot && configurationFilePath ? relative(gitRoot, configurationFilePath).split(sep).join("/") : void 0
80
+ };
81
+ };
82
+
83
+ //#endregion
84
+ export { detectGitRepository, parseGitRemoteUrl };
85
+ //# sourceMappingURL=detectGitRepository.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detectGitRepository.mjs","names":[],"sources":["../../../src/utils/detectGitRepository.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process';\nimport { relative, sep } from 'node:path';\nimport { searchConfigurationFile } from '@intlayer/config/node';\n\n/** Git providers the Intlayer CMS can connect a repository from. */\nexport type GitRepositoryProvider =\n | 'github'\n | 'gitlab'\n | 'bitbucket'\n | 'codeberg'\n | 'gitee';\n\n/** Repository a git remote points to. */\nexport type GitRemoteRepository = {\n provider: GitRepositoryProvider;\n /** Owner, group path (GitLab sub-groups included) or workspace */\n owner: string;\n repository: string;\n /** Web URL of the repository */\n url: string;\n /** Self-managed GitLab instance (ex: https://gitlab.company.com) */\n instanceUrl?: string;\n};\n\n/** Local repository of the project, as sent to the CMS login page. */\nexport type DetectedGitRepository = GitRemoteRepository & {\n /** Checked-out branch */\n branch?: string;\n /** Intlayer configuration file, relative to the repository root (posix) */\n configFilePath?: string;\n};\n\nconst HOST_PROVIDERS: Record<string, GitRepositoryProvider> = {\n 'github.com': 'github',\n 'gitlab.com': 'gitlab',\n 'bitbucket.org': 'bitbucket',\n 'codeberg.org': 'codeberg',\n 'gitee.com': 'gitee',\n};\n\n/** Provider of a host; any host naming GitLab is taken as self-managed. */\nconst getHostProvider = (host: string): GitRepositoryProvider | undefined =>\n HOST_PROVIDERS[host] ?? (host.includes('gitlab') ? 'gitlab' : undefined);\n\n/**\n * Parses an HTTPS, SSH (`ssh://`) or scp-like (`git@host:owner/repo.git`)\n * remote URL. Returns `null` for an unknown host or a malformed URL.\n */\nexport const parseGitRemoteUrl = (\n remoteUrl: string\n): GitRemoteRepository | null => {\n const trimmedUrl = remoteUrl.trim();\n const scpLikeMatch = /^(?:[^@/\\s]+@)?([^:/\\s]+):(?!\\/)(.+)$/.exec(trimmedUrl);\n\n let host: string;\n let path: string;\n\n if (scpLikeMatch && !trimmedUrl.includes('://')) {\n [, host, path] = scpLikeMatch as unknown as [string, string, string];\n } else {\n try {\n const url = new URL(trimmedUrl);\n host = url.hostname;\n path = url.pathname;\n } catch {\n return null;\n }\n }\n\n const provider = getHostProvider(host.toLowerCase());\n const segments = path\n .replace(/^\\/+|\\/+$/g, '')\n .replace(/\\.git$/, '')\n .split('/')\n .filter(Boolean);\n\n if (!provider || segments.length < 2) return null;\n\n // Only GitLab nests groups: elsewhere the path is exactly `owner/repo`\n if (provider !== 'gitlab' && segments.length !== 2) return null;\n\n const repository = segments[segments.length - 1] as string;\n const owner = segments.slice(0, -1).join('/');\n const isSelfManagedGitLab = provider === 'gitlab' && host !== 'gitlab.com';\n\n return {\n provider,\n owner,\n repository,\n url: `https://${host}/${owner}/${repository}`,\n ...(isSelfManagedGitLab && { instanceUrl: `https://${host}` }),\n };\n};\n\n/** Output of a git command, `undefined` when it fails. */\nconst runGit = (cwd: string, args: string[]): string | undefined => {\n const result = spawnSync('git', args, { cwd, encoding: 'utf-8' });\n\n if (result.status !== 0) return undefined;\n\n return result.stdout.trim() || undefined;\n};\n\n/**\n * Detects the hosted repository of the project from its `origin` remote,\n * with the checked-out branch and the config file path in the repository.\n * Returns `null` outside a git repository or for an unsupported host.\n */\nexport const detectGitRepository = (\n projectRoot: string\n): DetectedGitRepository | null => {\n const remoteUrl = runGit(projectRoot, ['remote', 'get-url', 'origin']);\n const remote = remoteUrl ? parseGitRemoteUrl(remoteUrl) : null;\n\n if (!remote) return null;\n\n const branch = runGit(projectRoot, ['rev-parse', '--abbrev-ref', 'HEAD']);\n const gitRoot = runGit(projectRoot, ['rev-parse', '--show-toplevel']);\n const { configurationFilePath } = searchConfigurationFile(projectRoot);\n\n return {\n ...remote,\n // A detached HEAD has no branch to connect\n branch: branch && branch !== 'HEAD' ? branch : undefined,\n configFilePath:\n gitRoot && configurationFilePath\n ? relative(gitRoot, configurationFilePath).split(sep).join('/')\n : undefined,\n };\n};\n"],"mappings":";;;;;AAgCA,MAAM,iBAAwD;CAC5D,cAAc;CACd,cAAc;CACd,iBAAiB;CACjB,gBAAgB;CAChB,aAAa;AACf;;AAGA,MAAM,mBAAmB,SACvB,eAAe,UAAU,KAAK,SAAS,QAAQ,IAAI,WAAW;;;;;AAMhE,MAAa,qBACX,cAC+B;CAC/B,MAAM,aAAa,UAAU,KAAK;CAClC,MAAM,eAAe,wCAAwC,KAAK,UAAU;CAE5E,IAAI;CACJ,IAAI;CAEJ,IAAI,gBAAgB,CAAC,WAAW,SAAS,KAAK,GAC5C,GAAG,MAAM,QAAQ;MAEjB,IAAI;EACF,MAAM,MAAM,IAAI,IAAI,UAAU;EAC9B,OAAO,IAAI;EACX,OAAO,IAAI;CACb,QAAQ;EACN,OAAO;CACT;CAGF,MAAM,WAAW,gBAAgB,KAAK,YAAY,CAAC;CACnD,MAAM,WAAW,KACd,QAAQ,cAAc,EAAE,CAAC,CACzB,QAAQ,UAAU,EAAE,CAAC,CACrB,MAAM,GAAG,CAAC,CACV,OAAO,OAAO;CAEjB,IAAI,CAAC,YAAY,SAAS,SAAS,GAAG,OAAO;CAG7C,IAAI,aAAa,YAAY,SAAS,WAAW,GAAG,OAAO;CAE3D,MAAM,aAAa,SAAS,SAAS,SAAS;CAC9C,MAAM,QAAQ,SAAS,MAAM,GAAG,EAAE,CAAC,CAAC,KAAK,GAAG;CAC5C,MAAM,sBAAsB,aAAa,YAAY,SAAS;CAE9D,OAAO;EACL;EACA;EACA;EACA,KAAK,WAAW,KAAK,GAAG,MAAM,GAAG;EACjC,GAAI,uBAAuB,EAAE,aAAa,WAAW,OAAO;CAC9D;AACF;;AAGA,MAAM,UAAU,KAAa,SAAuC;CAClE,MAAM,SAAS,UAAU,OAAO,MAAM;EAAE;EAAK,UAAU;CAAQ,CAAC;CAEhE,IAAI,OAAO,WAAW,GAAG,OAAO;CAEhC,OAAO,OAAO,OAAO,KAAK,KAAK;AACjC;;;;;;AAOA,MAAa,uBACX,gBACiC;CACjC,MAAM,YAAY,OAAO,aAAa;EAAC;EAAU;EAAW;CAAQ,CAAC;CACrE,MAAM,SAAS,YAAY,kBAAkB,SAAS,IAAI;CAE1D,IAAI,CAAC,QAAQ,OAAO;CAEpB,MAAM,SAAS,OAAO,aAAa;EAAC;EAAa;EAAgB;CAAM,CAAC;CACxE,MAAM,UAAU,OAAO,aAAa,CAAC,aAAa,iBAAiB,CAAC;CACpE,MAAM,EAAE,0BAA0B,wBAAwB,WAAW;CAErE,OAAO;EACL,GAAG;EAEH,QAAQ,UAAU,WAAW,SAAS,SAAS;EAC/C,gBACE,WAAW,wBACP,SAAS,SAAS,qBAAqB,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,GAAG,IAC5D;CACR;AACF"}
@@ -1,4 +1,4 @@
1
- import { DictionaryAPI } from "@intlayer/backend";
1
+ import { DictionaryAPI } from "@intlayer/api";
2
2
  import { IntlayerConfig } from "@intlayer/types/config";
3
3
  //#region src/IntlayerEventListener.d.ts
4
4
  export type IntlayerMessageEvent = MessageEvent;
@@ -1 +1 @@
1
- {"version":3,"file":"IntlayerEventListener.d.ts","names":[],"sources":["../../src/IntlayerEventListener.ts"],"mappings":";;;YAQY,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;qBA0BtB;UAoCD;UAnCF;UAEA;UACA;UACA;UACA;UACA;UACA;;;;EAKD,qBAAqB,YAAY;;;;EAKjC,sBAAsB,YAAY;;;;EAKlC,uBAAuB,YAAY;;;;EAKnC;;;;EAKA,qBAAqB,OAAO;EAEnC,YACU,iBAAgB,KAAK;;;;;EAYlB,cAAc;;;;UAQb;;;;EAmDP;;;;UAiBC;;;;;UAoCM;;;;UA8CN"}
1
+ {"version":3,"file":"IntlayerEventListener.d.ts","names":[],"sources":["../../src/IntlayerEventListener.ts"],"mappings":";;;YAOY,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;qBA0BtB;UAoCD;UAnCF;UAEA;UACA;UACA;UACA;UACA;UACA;;;;EAKD,qBAAqB,YAAY;;;;EAKjC,sBAAsB,YAAY;;;;EAKlC,uBAAuB,YAAY;;;;EAKnC;;;;EAKA,qBAAqB,OAAO;EAEnC,YACU,iBAAgB,KAAK;;;;;EAYlB,cAAc;;;;UAQb;;;;EAmDP;;;;UAiBC;;;;;UAoCM;;;;UA8CN"}
@@ -19,6 +19,11 @@ type LoginOptions = {
19
19
  clientId: string;
20
20
  clientSecret: string;
21
21
  }) => void | Promise<void>;
22
+ /**
23
+ * Aborts a login still waiting for the browser callback: the callback
24
+ * server is shut down and the returned promise resolves.
25
+ */
26
+ signal?: AbortSignal;
22
27
  };
23
28
  export declare const login: (options?: LoginOptions) => Promise<void>;
24
29
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"login.d.ts","names":[],"sources":["../../../src/auth/login.ts"],"mappings":";;KAiEK;EACH;EACA,gBAAgB;;;;;;EAMhB;;;;;;;EAOA,iBAAiB;IACf;IACA;eACW;;qBAGF,QAAK,UAAmB,iBAAY"}
1
+ {"version":3,"file":"login.d.ts","names":[],"sources":["../../../src/auth/login.ts"],"mappings":";;KAiEK;EACH;EACA,gBAAgB;;;;;;EAMhB;;;;;;;EAOA,iBAAiB;IACf;IACA;eACW;;;;;EAKb,SAAS;;qBAGE,QAAK,UAAmB,iBAAY"}
@@ -0,0 +1,25 @@
1
+ import { DetectedGitRepository } from "../utils/detectGitRepository.js";
2
+ import { GetConfigurationOptions } from "@intlayer/config/node";
3
+ //#region src/auth/setupRepository.d.ts
4
+ /**
5
+ * Search params pre-filling the repository page with the local repository.
6
+ * Empty when none was detected: the user then picks it in the browser.
7
+ */
8
+ export declare const getRepositorySearchParams: (detectedRepository: DetectedGitRepository | null | undefined) => URLSearchParams;
9
+ type SetupRepositoryOptions = {
10
+ cmsUrl?: string;
11
+ configOptions?: GetConfigurationOptions;
12
+ /** Local repository pre-filling the page */
13
+ detectedRepository?: DetectedGitRepository | null;
14
+ };
15
+ /** Outcome of the browser step: `provider:owner/repository`, or skipped. */
16
+ export type SetupRepositoryResult = {
17
+ linkedRepository: string | null;
18
+ };
19
+ /**
20
+ * Opens the CMS repository page (`/auth/cli-repository`) and waits for it to
21
+ * call back the local server once the repository and build settings are set.
22
+ */
23
+ export declare const setupRepository: (options?: SetupRepositoryOptions) => Promise<SetupRepositoryResult>;
24
+ //#endregion
25
+ //# sourceMappingURL=setupRepository.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setupRepository.d.ts","names":[],"sources":["../../../src/auth/setupRepository.ts"],"mappings":";;;;;;;qBAqCa,4BAAyB,oBAChB,6CACnB;KAkBE;EACH;EACA,gBAAgB;;EAEhB,qBAAqB;;;YAIX;EACV;;;;;;qBAOW,kBAAe,UACjB,2BACR,QAAQ"}
@@ -3,6 +3,16 @@ type StartEditorOptions = {
3
3
  env?: string;
4
4
  envFile?: string;
5
5
  };
6
+ /**
7
+ * Package runners trying `intlayer-editor` without it being installed. The
8
+ * editor is pinned to the CLI version, so both speak the same protocol.
9
+ */
10
+ export declare const getRemoteEditorRunners: (cliVersion: string | undefined) => [command: string, args: string[]][];
11
+ /**
12
+ * Starts the visual editor: the installed `intlayer-editor`, else one of the
13
+ * CLI version run by `bun x` or `npx` (the next runner is tried when one is
14
+ * not installed).
15
+ */
6
16
  export declare const startEditor: (options: StartEditorOptions) => void;
7
17
  //#endregion
8
18
  //# sourceMappingURL=editor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"editor.d.ts","names":[],"sources":["../../src/editor.ts"],"mappings":";KAKK;EACH;EACA;;qBAGW,cAAW,SAAa"}
1
+ {"version":3,"file":"editor.d.ts","names":[],"sources":["../../src/editor.ts"],"mappings":";KAYK;EACH;EACA;;;;;;qBAaW,yBAAsB,oCAE/B,iBAAiB;;;;;;qBA8DR,cAAW,SAAa"}
@@ -1,5 +1,5 @@
1
1
  import { GetTargetDictionaryOptions } from "../getTargetDictionary.js";
2
- import { AIOptions } from "@intlayer/api";
2
+ import { AIOptions } from "@intlayer/ai";
3
3
  import { ListGitFilesOptions } from "@intlayer/engine/cli";
4
4
  import { Locale } from "@intlayer/types/allLocales";
5
5
  //#region src/fill/fill.d.ts
@@ -1,9 +1,8 @@
1
1
  import { TranslationTask } from "./listTranslationsTasks.js";
2
2
  import { AIClient } from "../utils/setupAI.js";
3
3
  import { IntlayerConfig } from "@intlayer/types/config";
4
- import { AIOptions } from "@intlayer/api";
4
+ import { AIConfig, AIOptions } from "@intlayer/ai";
5
5
  import { Dictionary } from "@intlayer/types/dictionary";
6
- import { AIConfig } from "@intlayer/ai";
7
6
  //#region src/fill/translateDictionary.d.ts
8
7
  type TranslateDictionaryResult = TranslationTask & {
9
8
  dictionaryOutput: Dictionary | null;
@@ -1 +1 @@
1
- {"version":3,"file":"translateDictionary.d.ts","names":[],"sources":["../../../src/fill/translateDictionary.ts"],"mappings":";;;;;;;KAwCK,4BAA4B;EAC/B,kBAAkB;;KAGf;EACH;EACA,YAAY;EACZ;EACA,WAAW,mDAC+B;EAE1C;EACA,WAAW;EACX,sBAAsB;EACtB,WAAW;EACX,WAAW;;qBAyCA,sBAAmB,MACxB,iBAAe,eACN,gBAAc,UACnB,+BACT,QAAQ"}
1
+ {"version":3,"file":"translateDictionary.d.ts","names":[],"sources":["../../../src/fill/translateDictionary.ts"],"mappings":";;;;;;KAuCK,4BAA4B;EAC/B,kBAAkB;;KAGf;EACH;EACA,YAAY;EACZ;EACA,WAAW,mDAC+B;EAE1C;EACA,WAAW;EACX,sBAAsB;EACtB,WAAW;EACX,WAAW;;qBAyCA,sBAAmB,MACxB,iBAAe,eACN,gBAAc,UACnB,+BACT,QAAQ"}
@@ -1,10 +1,13 @@
1
+ import { IntlayerEventListener, IntlayerMessageEvent } from "./IntlayerEventListener.js";
2
+ import { login } from "./auth/login.js";
3
+ import { clearCliSessionToken, readCliSessionToken } from "./auth/sessionToken.js";
1
4
  import { build } from "./build.js";
2
5
  import { bundle } from "./bundle.js";
3
6
  import { ConfigurationOptions, dirname, setAPI } from "./cli.js";
4
- import { startEditor } from "./editor.js";
7
+ import { getRemoteEditorRunners, startEditor } from "./editor.js";
5
8
  import { extract } from "./extract.js";
6
9
  import { FillOptions, FillResult, fill } from "./fill/fill.js";
7
- import { DEFAULT_INIT_STEPS, ESLINT_CONFIG_FILES, INIT_STEP_COMMANDS, INIT_STEP_GROUPS, InitStep, InitStepOption, LOCALE_ROUTING_CHOICES, LocaleRoutingChoice, OPT_IN_INIT_STEPS, findProjectRoot, getInitOptionsForSteps, getInitialInitSteps, getProjectDependencies, getRoutingInitOptions, init, initCms, initSteps, isEslintInstalled, parseLocaleRoutingChoice, resolveProjectRoot } from "./init.js";
10
+ import { CONTENT_LAYOUT_CHOICES, DEFAULT_INIT_STEPS, ESLINT_CONFIG_FILES, INIT_STEP_COMMANDS, INIT_STEP_GROUPS, InitStep, InitStepOption, LOCALE_ROUTING_CHOICES, LocaleRoutingChoice, OPT_IN_INIT_STEPS, findProjectRoot, getContentInitOptions, getInitOptionsForSteps, getInitialInitSteps, getProjectDependencies, getRoutingInitOptions, init, initCms, initRepository, initSteps, isEslintInstalled, parseContentLayoutChoice, parseLocaleRoutingChoice, resolveProjectRoot } from "./init.js";
8
11
  import { BROWSER_EXTENSION_OPTIONS, CHROME_EXTENSION_URL, EXTENSION_BROWSERS, EXTENSION_URL_BY_BROWSER, ExtensionBrowser, InitChromeExtensionOptions, MOZILLA_EXTENSION_URL, initChromeExtension, parseExtensionBrowsers } from "./initChromeExtension.js";
9
12
  import { INFRA_MODES, INSTALL_SCRIPT_URL, INSTALL_SCRIPT_URL_WINDOWS, InfraMode, InitInfraOptions, initInfra, parseInfraMode } from "./initInfra.js";
10
13
  import { InitSkillsOptions, PLATFORM_OPTIONS, initSkills, parsePlatform, parseSkills, promptPlatform, resolvePlatformWithoutPrompt } from "./initSkills.js";
@@ -22,8 +25,9 @@ import { testMissingTranslations } from "./test/test.js";
22
25
  import "./test/index.js";
23
26
  import { translateDoc } from "./translateDoc/translateDoc.js";
24
27
  import { checkCMSAuth, getAuthenticatedAPI } from "./utils/checkAccess.js";
28
+ import { checkConfigConsistency } from "./utils/checkConfigConsistency.js";
25
29
  export type * from "@intlayer/engine/cli";
26
30
  export type * from "@intlayer/engine/build";
27
31
  export type * from "@intlayer/engine/utils";
28
32
  export type * from "@intlayer/engine/watcher";
29
- export { BROWSER_EXTENSION_OPTIONS, CHROME_EXTENSION_URL, ConfigurationOptions, DEFAULT_INIT_STEPS, ESLINT_CONFIG_FILES, EXTENSION_BROWSERS, EXTENSION_URL_BY_BROWSER, ExtensionBrowser, FillOptions, FillResult, INFRA_MODES, INIT_STEP_COMMANDS, INIT_STEP_GROUPS, INSTALL_SCRIPT_URL, INSTALL_SCRIPT_URL_WINDOWS, InfraMode, InitChromeExtensionOptions, InitInfraOptions, InitSkillsOptions, InitStep, InitStepOption, LOCALE_ROUTING_CHOICES, ListProjectsCommandOptions, LocaleRoutingChoice, MOZILLA_EXTENSION_URL, OPT_IN_INIT_STEPS, PLATFORM_OPTIONS, ReviewDocMode, ScanCommandOptions, build, bundle, checkCMSAuth, dirname, extract, fill, findProjectRoot, getAuthenticatedAPI, getInitOptionsForSteps, getInitialInitSteps, getProjectDependencies, getRoutingInitOptions, init, initChromeExtension, initCms, initInfra, initSkills, initSteps, isEslintInstalled, listContentDeclaration, listContentDeclarationRows, listMissingTranslations, listMissingTranslationsWithConfig, listProjectsCommand, liveSync, parseExtensionBrowsers, parseInfraMode, parseLocaleRoutingChoice, parsePlatform, parseSkills, promptPlatform, pull, push, pushConfig, resolvePlatformWithoutPrompt, resolveProjectRoot, reviewDoc, scan, searchDoc, setAPI, startEditor, testMissingTranslations, translateDoc };
33
+ export { BROWSER_EXTENSION_OPTIONS, CHROME_EXTENSION_URL, CONTENT_LAYOUT_CHOICES, ConfigurationOptions, DEFAULT_INIT_STEPS, ESLINT_CONFIG_FILES, EXTENSION_BROWSERS, EXTENSION_URL_BY_BROWSER, ExtensionBrowser, FillOptions, FillResult, INFRA_MODES, INIT_STEP_COMMANDS, INIT_STEP_GROUPS, INSTALL_SCRIPT_URL, INSTALL_SCRIPT_URL_WINDOWS, InfraMode, InitChromeExtensionOptions, InitInfraOptions, InitSkillsOptions, InitStep, InitStepOption, IntlayerEventListener, IntlayerMessageEvent, LOCALE_ROUTING_CHOICES, ListProjectsCommandOptions, LocaleRoutingChoice, MOZILLA_EXTENSION_URL, OPT_IN_INIT_STEPS, PLATFORM_OPTIONS, ReviewDocMode, ScanCommandOptions, build, bundle, checkCMSAuth, checkConfigConsistency, clearCliSessionToken, dirname, extract, fill, findProjectRoot, getAuthenticatedAPI, getContentInitOptions, getInitOptionsForSteps, getInitialInitSteps, getProjectDependencies, getRemoteEditorRunners, getRoutingInitOptions, init, initChromeExtension, initCms, initInfra, initRepository, initSkills, initSteps, isEslintInstalled, listContentDeclaration, listContentDeclarationRows, listMissingTranslations, listMissingTranslationsWithConfig, listProjectsCommand, liveSync, login, parseContentLayoutChoice, parseExtensionBrowsers, parseInfraMode, parseLocaleRoutingChoice, parsePlatform, parseSkills, promptPlatform, pull, push, pushConfig, readCliSessionToken, resolvePlatformWithoutPrompt, resolveProjectRoot, reviewDoc, scan, searchDoc, setAPI, startEditor, testMissingTranslations, translateDoc };
@@ -1,10 +1,10 @@
1
- import { InitOptions, RoutingMode } from "@intlayer/engine/cli";
1
+ import { ContentLayout, InitOptions, RoutingMode } from "@intlayer/engine/cli";
2
2
  //#region src/init.d.ts
3
3
  export declare const findProjectRoot: (startDir: string) => string;
4
4
  /** Resolves the project root from `--project-root`, else the working directory. */
5
5
  export declare const resolveProjectRoot: (projectRoot?: string) => string;
6
6
  /** Individually selectable setup steps exposed by the interactive init flow. */
7
- export type InitStep = 'packages' | 'githubActions' | 'projectSetup' | 'vscodeExtension' | 'lsp' | 'eslint' | 'skills' | 'mcp' | 'infra' | 'cms' | 'chromeExtension';
7
+ export type InitStep = 'packages' | 'githubActions' | 'projectSetup' | 'vscodeExtension' | 'lsp' | 'eslint' | 'skills' | 'mcp' | 'infra' | 'cms' | 'repository' | 'chromeExtension';
8
8
  /** A checkbox entry of the interactive init flow. */
9
9
  export type InitStepOption = {
10
10
  value: InitStep;
@@ -46,6 +46,11 @@ export declare const initSteps: (projectRoot: string | undefined, steps: InitSte
46
46
  * credentials in `.env` and enables the editor in the config file.
47
47
  */
48
48
  export declare const initCms: (projectRoot?: string) => Promise<void>;
49
+ /**
50
+ * Connects the git repository of the project to its CMS project (and sets
51
+ * its build settings) in the browser, pre-filled with the `origin` remote.
52
+ */
53
+ export declare const initRepository: (projectRoot?: string) => Promise<void>;
49
54
  /** Grouped checkbox entries of the interactive init flow, in display order. */
50
55
  export declare const INIT_STEP_GROUPS: Record<string, InitStepOption[]>;
51
56
  /**
@@ -59,6 +64,18 @@ export declare const LOCALE_ROUTING_CHOICES: LocaleRoutingChoice[];
59
64
  export declare const parseLocaleRoutingChoice: (value: string) => LocaleRoutingChoice;
60
65
  /** Maps a routing choice to the init options it sets. */
61
66
  export declare const getRoutingInitOptions: (choice: LocaleRoutingChoice) => Pick<InitOptions, 'routingMode' | 'enableProxy'>;
67
+ /** Content layouts accepted by `--content`. */
68
+ export declare const CONTENT_LAYOUT_CHOICES: ContentLayout[];
69
+ /** Validates a `--content` value, throwing on an unknown layout. */
70
+ export declare const parseContentLayoutChoice: (value: string) => ContentLayout;
71
+ /** Init options set by the content layout questions / flags. */
72
+ type ContentInitOptions = Pick<InitOptions, 'contentLayout' | 'contentFormat' | 'contentMessageFormat'>;
73
+ /**
74
+ * Validates `--content` / `--content-format` / `--message-format` and maps
75
+ * them to the init options. The format must be one the layout accepts, and the
76
+ * message format only applies to JSON catalogs.
77
+ */
78
+ export declare const getContentInitOptions: (layoutValue?: string, formatValue?: string, messageFormatValue?: string) => ContentInitOptions;
62
79
  /** Known ESLint and oxlint configuration file names. */
63
80
  export declare const ESLINT_CONFIG_FILES: string[];
64
81
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","names":[],"sources":["../../src/init.ts"],"mappings":";;qBAqBa,kBAAe;;qBAgBf,qBAAkB;;YAInB;;YAcA;EACV,OAAO;EACP;EACA;;;;;;;qBAQW,mBAAmB;;;;;;;qBAQnB,oBAAoB;;;;;qBAMpB,oBAAoB,OAAO;;;;;;qBAmB3B,yBAAsB,OAC1B,YAAU,cACH,gBACb;;;;;;qBAwBU,YAAS,iCACW,OACxB,YAAU,UACP,gBACT;;;;;qBAWU,UAAO,yBAAiC;;qBAuBxC,kBAAkB,eAAe;;;;;YAsElC,sBAAsB;;qBAoCrB,wBAAsB;;qBAKtB,2BAAwB,kBAAoB;;qBAI5C,wBAAqB,QACxB,wBACP,KAAK;;qBAMK;;;;;qBAoBA,oBAAiB,cACd,wBAAsB;;;;;;qBAkBzB,sBAAmB,cAChB,wBAAsB,kBAEnC;;qBAcU,yBAAsB,iBAEhC;;;;;;qBAkIU,OAAI,sBACK,UACV,aAAW,0BACA"}
1
+ {"version":3,"file":"init.d.ts","names":[],"sources":["../../src/init.ts"],"mappings":";;qBA4Ba,kBAAe;;qBAgBf,qBAAkB;;YAInB;;YAeA;EACV,OAAO;EACP;EACA;;;;;;;qBAQW,mBAAmB;;;;;;;qBAQnB,oBAAoB;;;;;qBAMpB,oBAAoB,OAAO;;;;;;qBAqB3B,yBAAsB,OAC1B,YAAU,cACH,gBACb;;;;;;qBAwBU,YAAS,iCACW,OACxB,YAAU,UACP,gBACT;;;;;qBAWU,UAAO,yBAAiC;;;;;qBAiBxC,iBAAc,yBAAiC;;qBAyB/C,kBAAkB,eAAe;;;;;YA2ElC,sBAAsB;;qBAoCrB,wBAAsB;;qBAKtB,2BAAwB,kBAAoB;;qBAI5C,wBAAqB,QACxB,wBACP,KAAK;;qBAoDK,wBAAsB;;qBAKtB,2BAAwB,kBAAoB;;KAIpD,qBAAqB,KACxB;;;;;;qBAaW,wBAAqB,sBACZ,sBACA,gCAEnB;;qBAwCU;;;;;qBAoBA,oBAAiB,cACd,wBAAsB;;;;;;qBAkBzB,sBAAmB,cAChB,wBAAsB,kBAEnC;;qBAcU,yBAAsB,iBAEhC;;;;;;qBA0MU,OAAI,sBACK,UACV,aAAW,0BACA"}
@@ -1 +1 @@
1
- {"version":3,"file":"liveSync.d.ts","names":[],"sources":["../../src/liveSync.ts"],"mappings":";;KAkBK;EACH;EACA,gBAAgB;;qBAYL,WAAQ,UAAoB,oBAAe"}
1
+ {"version":3,"file":"liveSync.d.ts","names":[],"sources":["../../src/liveSync.ts"],"mappings":";;KAiBK;EACH;EACA,gBAAgB;;qBAYL,WAAQ,UAAoB,oBAAe"}
@@ -1,6 +1,6 @@
1
1
  import { ReviewFileResult } from "./reviewDocSynthesis.js";
2
2
  import { GetConfigurationOptions } from "@intlayer/config/node";
3
- import { AIOptions } from "@intlayer/api";
3
+ import { AIOptions } from "@intlayer/ai";
4
4
  import { ListGitFilesOptions } from "@intlayer/engine/cli";
5
5
  import { Locale } from "@intlayer/types/allLocales";
6
6
  //#region src/reviewDoc/reviewDoc.d.ts
@@ -1 +1 @@
1
- {"version":3,"file":"reviewDoc.d.ts","names":[],"sources":["../../../src/reviewDoc/reviewDoc.ts"],"mappings":";;;;;;;;;;;;;;;YAmDY;KAEP;EACH;EACA,SAAS;EACT;EACA,YAAY;EACZ,YAAY;EACZ;EACA,gBAAgB;EAChB;EACA,yCAAyC;EACzC,wCAAwC;EACxC;EACA,aAAa;;EAEb,OAAO;;;;;;qBAOI,cAAS,YAAA,SAAA,qBAAA,YAAA,WAAA,6BAAA,eAAA,oBAAA,sBAAA,qBAAA,cAAA,YAAA,QAcnB,qBAAgB,QAAA"}
1
+ {"version":3,"file":"reviewDoc.d.ts","names":[],"sources":["../../../src/reviewDoc/reviewDoc.ts"],"mappings":";;;;;;;;;;;;;;;YAkDY;KAEP;EACH;EACA,SAAS;EACT;EACA,YAAY;EACZ,YAAY;EACZ;EACA,gBAAgB;EAChB;EACA,yCAAyC;EACzC,wCAAwC;EACxC;EACA,aAAa;;EAEb,OAAO;;;;;;qBAOI,cAAS,YAAA,SAAA,qBAAA,YAAA,WAAA,6BAAA,eAAA,oBAAA,sBAAA,qBAAA,cAAA,YAAA,QAcnB,qBAAgB,QAAA"}
@@ -1,8 +1,7 @@
1
1
  import { AIClient } from "../utils/setupAI.js";
2
2
  import { GetConfigurationOptions } from "@intlayer/config/node";
3
- import { AIOptions } from "@intlayer/api";
3
+ import { AIConfig, AIOptions } from "@intlayer/ai";
4
4
  import { Locale } from "@intlayer/types/allLocales";
5
- import { AIConfig } from "@intlayer/ai";
6
5
  import { ReviewReportSummary } from "@intlayer/engine/docReview";
7
6
  //#region src/reviewDoc/reviewDocBlockAware.d.ts
8
7
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"reviewDocBlockAware.d.ts","names":[],"sources":["../../../src/reviewDoc/reviewDocBlockAware.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;qBA+Ca,uBAAoB,sBACX,wBACE,QACd,QAAM,YACF,QAAM,YACN,WAAS,gBACL,yBAAuB,6BACZ,yBACJ,WACZ,UAAQ,WACR,aACV,QAAQ"}
1
+ {"version":3,"file":"reviewDocBlockAware.d.ts","names":[],"sources":["../../../src/reviewDoc/reviewDocBlockAware.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;qBA8Ca,uBAAoB,sBACX,wBACE,QACd,QAAM,YACF,QAAM,YACN,WAAS,gBACL,yBAAuB,6BACZ,yBACJ,WACZ,UAAQ,WACR,aACV,QAAQ"}
@@ -1,9 +1,9 @@
1
1
  import { AIClient } from "../utils/setupAI.js";
2
2
  import { IntlayerConfig } from "@intlayer/types/config";
3
3
  import { GetConfigurationOptions } from "@intlayer/config/node";
4
+ import { AIConfig, AIOptions } from "@intlayer/ai";
4
5
  import { ListGitFilesOptions } from "@intlayer/engine/cli";
5
6
  import { Locale } from "@intlayer/types/allLocales";
6
- import { AIConfig, AIOptions } from "@intlayer/ai";
7
7
  //#region src/translateDoc/types.d.ts
8
8
  export type ErrorState = {
9
9
  count: number;
@@ -1,5 +1,6 @@
1
+ import { IntlayerAPIProxy } from "@intlayer/api";
1
2
  import { IntlayerConfig } from "@intlayer/types/config";
2
- import { AIOptions, IntlayerAPIProxy } from "@intlayer/api";
3
+ import { AIOptions } from "@intlayer/ai";
3
4
  //#region src/utils/checkAccess.d.ts
4
5
  export declare const getAuthenticatedAPI: (configuration?: IntlayerConfig) => Promise<IntlayerAPIProxy>;
5
6
  export declare const checkCMSAuth: (configuration: IntlayerConfig, shouldCheckConfigConsistency?: boolean, shouldAutoLogin?: boolean) => Promise<boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"checkAccess.d.ts","names":[],"sources":["../../../src/utils/checkAccess.ts"],"mappings":";;;qBAUa,sBAAmB,gBACd,mBACf,QAAQ;qBAiDE,eAAY,eACR,gBAAc,wCACQ,8BAEpC;qBA4GU,gBAAa,eACT,gBAAc,YACjB,WAAS,2CAEpB"}
1
+ {"version":3,"file":"checkAccess.d.ts","names":[],"sources":["../../../src/utils/checkAccess.ts"],"mappings":";;;;qBAWa,sBAAmB,gBACd,mBACf,QAAQ;qBAiDE,eAAY,eACR,gBAAc,wCACQ,8BAEpC;qBA4GU,gBAAa,eACT,gBAAc,YACjB,WAAS,2CAEpB"}
@@ -0,0 +1,34 @@
1
+ //#region src/utils/detectGitRepository.d.ts
2
+ /** Git providers the Intlayer CMS can connect a repository from. */
3
+ export type GitRepositoryProvider = 'github' | 'gitlab' | 'bitbucket' | 'codeberg' | 'gitee';
4
+ /** Repository a git remote points to. */
5
+ export type GitRemoteRepository = {
6
+ provider: GitRepositoryProvider;
7
+ /** Owner, group path (GitLab sub-groups included) or workspace */
8
+ owner: string;
9
+ repository: string;
10
+ /** Web URL of the repository */
11
+ url: string;
12
+ /** Self-managed GitLab instance (ex: https://gitlab.company.com) */
13
+ instanceUrl?: string;
14
+ };
15
+ /** Local repository of the project, as sent to the CMS login page. */
16
+ export type DetectedGitRepository = GitRemoteRepository & {
17
+ /** Checked-out branch */
18
+ branch?: string;
19
+ /** Intlayer configuration file, relative to the repository root (posix) */
20
+ configFilePath?: string;
21
+ };
22
+ /**
23
+ * Parses an HTTPS, SSH (`ssh://`) or scp-like (`git@host:owner/repo.git`)
24
+ * remote URL. Returns `null` for an unknown host or a malformed URL.
25
+ */
26
+ export declare const parseGitRemoteUrl: (remoteUrl: string) => GitRemoteRepository | null;
27
+ /**
28
+ * Detects the hosted repository of the project from its `origin` remote,
29
+ * with the checked-out branch and the config file path in the repository.
30
+ * Returns `null` outside a git repository or for an unsupported host.
31
+ */
32
+ export declare const detectGitRepository: (projectRoot: string) => DetectedGitRepository | null;
33
+ //#endregion
34
+ //# sourceMappingURL=detectGitRepository.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detectGitRepository.d.ts","names":[],"sources":["../../../src/utils/detectGitRepository.ts"],"mappings":";;YAKY;;YAQA;EACV,UAAU;;EAEV;EACA;;EAEA;;EAEA;;;YAIU,wBAAwB;;EAElC;;EAEA;;;;;;qBAmBW,oBAAiB,sBAE3B;;;;;;qBA0DU,sBAAmB,wBAE7B"}
@@ -1,5 +1,5 @@
1
- import { IntlayerConfig } from "@intlayer/types/config";
2
1
  import { IntlayerAPIProxy } from "@intlayer/api";
2
+ import { IntlayerConfig } from "@intlayer/types/config";
3
3
  //#region src/utils/selectCmsEnvironment.d.ts
4
4
  /**
5
5
  * Selects the CMS project environment that matches `environmentName`.