@intlayer/engine 9.4.0-canary.0 → 9.4.1

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 (57) hide show
  1. package/README.md +3 -0
  2. package/dist/cjs/build.cjs +1 -0
  3. package/dist/cjs/createType/createModuleAugmentation.cjs +22 -1
  4. package/dist/cjs/createType/createModuleAugmentation.cjs.map +1 -1
  5. package/dist/cjs/createType/index.cjs +1 -0
  6. package/dist/cjs/docReview/pipeline.cjs +21 -3
  7. package/dist/cjs/docReview/pipeline.cjs.map +1 -1
  8. package/dist/cjs/docReview/reviewReport.cjs.map +1 -1
  9. package/dist/cjs/init/frameworkSetup/nextAppRouter/index.cjs +6 -6
  10. package/dist/cjs/init/frameworkSetup/nextAppRouter/index.cjs.map +1 -1
  11. package/dist/cjs/init/frameworkSetup/nextAppRouter/templates.cjs +11 -9
  12. package/dist/cjs/init/frameworkSetup/nextAppRouter/templates.cjs.map +1 -1
  13. package/dist/cjs/init/frameworkSetup/nextAppRouter/transforms.cjs +10 -70
  14. package/dist/cjs/init/frameworkSetup/nextAppRouter/transforms.cjs.map +1 -1
  15. package/dist/cjs/init/index.cjs +5 -3
  16. package/dist/cjs/init/index.cjs.map +1 -1
  17. package/dist/cjs/installSkills/index.cjs +37 -6
  18. package/dist/cjs/installSkills/index.cjs.map +1 -1
  19. package/dist/cjs/writeConfiguration/generateConfigurationContent.cjs +21 -1
  20. package/dist/cjs/writeConfiguration/generateConfigurationContent.cjs.map +1 -1
  21. package/dist/esm/build.mjs +2 -2
  22. package/dist/esm/createType/createModuleAugmentation.mjs +23 -3
  23. package/dist/esm/createType/createModuleAugmentation.mjs.map +1 -1
  24. package/dist/esm/createType/index.mjs +2 -2
  25. package/dist/esm/docReview/pipeline.mjs +21 -3
  26. package/dist/esm/docReview/pipeline.mjs.map +1 -1
  27. package/dist/esm/docReview/reviewReport.mjs.map +1 -1
  28. package/dist/esm/init/frameworkSetup/nextAppRouter/index.mjs +7 -7
  29. package/dist/esm/init/frameworkSetup/nextAppRouter/index.mjs.map +1 -1
  30. package/dist/esm/init/frameworkSetup/nextAppRouter/templates.mjs +11 -9
  31. package/dist/esm/init/frameworkSetup/nextAppRouter/templates.mjs.map +1 -1
  32. package/dist/esm/init/frameworkSetup/nextAppRouter/transforms.mjs +11 -70
  33. package/dist/esm/init/frameworkSetup/nextAppRouter/transforms.mjs.map +1 -1
  34. package/dist/esm/init/index.mjs +5 -3
  35. package/dist/esm/init/index.mjs.map +1 -1
  36. package/dist/esm/installSkills/index.mjs +37 -6
  37. package/dist/esm/installSkills/index.mjs.map +1 -1
  38. package/dist/esm/writeConfiguration/generateConfigurationContent.mjs +21 -1
  39. package/dist/esm/writeConfiguration/generateConfigurationContent.mjs.map +1 -1
  40. package/dist/types/build.d.ts +2 -2
  41. package/dist/types/createType/createModuleAugmentation.d.ts +16 -1
  42. package/dist/types/createType/createModuleAugmentation.d.ts.map +1 -1
  43. package/dist/types/createType/index.d.ts +2 -2
  44. package/dist/types/docReview/pipeline.d.ts +14 -2
  45. package/dist/types/docReview/pipeline.d.ts.map +1 -1
  46. package/dist/types/docReview/reviewReport.d.ts +6 -3
  47. package/dist/types/docReview/reviewReport.d.ts.map +1 -1
  48. package/dist/types/init/frameworkSetup/nextAppRouter/index.d.ts +4 -2
  49. package/dist/types/init/frameworkSetup/nextAppRouter/index.d.ts.map +1 -1
  50. package/dist/types/init/frameworkSetup/nextAppRouter/templates.d.ts +8 -3
  51. package/dist/types/init/frameworkSetup/nextAppRouter/templates.d.ts.map +1 -1
  52. package/dist/types/init/frameworkSetup/nextAppRouter/transforms.d.ts +8 -10
  53. package/dist/types/init/frameworkSetup/nextAppRouter/transforms.d.ts.map +1 -1
  54. package/dist/types/installSkills/index.d.ts.map +1 -1
  55. package/dist/types/writeConfiguration/generateConfigurationContent.d.ts +15 -0
  56. package/dist/types/writeConfiguration/generateConfigurationContent.d.ts.map +1 -1
  57. package/package.json +10 -10
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["readAsset","path","fs","getMarkdownMetadata","v"],"sources":["../../../src/installSkills/index.ts"],"sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { readAsset } from 'utils:asset';\nimport { v } from '@intlayer/config/logger';\nimport { getMarkdownMetadata } from '@intlayer/core/markdown';\n\n/**\n * Metadata for each available documentation skill.\n */\nexport const SKILLS_METADATA = {\n Config: 'Intlayer configuration documentation',\n Content: 'Reference for all Intlayer content node types (t, enu, etc.)',\n Usage: 'How to use Intlayer in your project',\n CLI: 'Intlayer CLI commands and usage',\n Compiler:\n 'Intlayer Compiler setup and usage for automatic content extraction without .content files',\n RemoteContent: 'How to use Intlayer with Remote/CMS/Server-side content',\n NextJS: 'Next.js-specific usage (Server & Client components)',\n React: 'React-specific syntax and hooks usage',\n Vue: 'Vue-specific composables and syntax',\n Svelte: 'Svelte-specific stores and syntax',\n Angular: 'Angular-specific syntax and Injectable Function usage',\n Preact: 'Preact-specific syntax and hooks usage',\n Solid:\n 'Integrates Intlayer internationalization with SolidJS components. Use when the user asks to \"setup SolidJS i18n\", use the \"useIntlayer\" hook in Solid, or manage locales in a SolidJS application.',\n Astro: 'Astro-specific usage and getIntlayer',\n} as const;\n\nexport type Skill = keyof typeof SKILLS_METADATA;\n\nexport const SKILLS = Object.keys(SKILLS_METADATA) as Skill[];\n\nexport const getInitialSkills = (\n deps: Record<string, string>\n): (keyof typeof SKILLS_METADATA)[] => {\n const skills: (keyof typeof SKILLS_METADATA)[] = [\n 'Usage',\n 'Content',\n 'Config',\n 'CLI',\n 'Compiler',\n ];\n\n if (deps.next) skills.push('NextJS');\n if (deps.react || !deps.next) skills.push('React');\n if (deps.preact) skills.push('Preact');\n if (deps['solid-js']) skills.push('Solid');\n if (deps.vue || deps.nuxt) skills.push('Vue');\n if (deps.svelte || deps['@sveltejs/kit']) skills.push('Svelte');\n if (deps.astro) skills.push('Astro');\n\n return skills;\n};\n\nexport interface PlatformMetadata {\n label: string;\n dir: string;\n check?: () => boolean;\n}\n\n/**\n * Metadata and configuration for each supported platform.\n */\nexport const PLATFORMS_METADATA: Record<string, PlatformMetadata> = {\n Cursor: {\n label: 'Cursor',\n dir: '.cursor/skills',\n check: () =>\n process.env.CURSOR === 'true' || process.env.TERM_PROGRAM === 'cursor',\n },\n Windsurf: {\n label: 'Windsurf',\n dir: '.windsurf/skills',\n check: () =>\n process.env.WINDSURF === 'true' ||\n process.env.TERM_PROGRAM === 'windsurf',\n },\n Trae: {\n label: 'Trae',\n dir: '.trae/skills',\n check: () =>\n process.env.TRAE === 'true' || process.env.TERM_PROGRAM === 'trae',\n },\n TraeCN: {\n label: 'Trae CN',\n dir: '.trae/skills',\n check: () => process.env.TRAE_CN === 'true',\n },\n VSCode: {\n label: 'VS Code',\n dir: '.vscode/skills',\n check: () =>\n process.env.VSCODE === 'true' || process.env.TERM_PROGRAM === 'vscode',\n },\n OpenCode: {\n label: 'OpenCode',\n dir: '.opencode/skills',\n check: () => process.env.OPENCODE === 'true',\n },\n Claude: {\n label: 'Claude Code',\n dir: '.claude/skills',\n check: () => process.env.CLAUDE === 'true',\n },\n GitHub: {\n label: 'GitHub Copilot Workspace',\n dir: '.github/skills',\n check: () =>\n process.env.GITHUB_ACTIONS === 'true' || !!process.env.GITHUB_WORKSPACE,\n },\n Antigravity: {\n label: 'Antigravity',\n dir: '.agent/skills',\n },\n Augment: {\n label: 'Augment',\n dir: '.augment/skills',\n },\n OpenClaw: {\n label: 'OpenClaw',\n dir: 'skills',\n },\n Cline: {\n label: 'Cline',\n dir: '.cline/skills',\n },\n CodeBuddy: {\n label: 'CodeBuddy',\n dir: '.codebuddy/skills',\n },\n CommandCode: {\n label: 'Command Code',\n dir: '.commandcode/skills',\n },\n Continue: {\n label: 'Continue',\n dir: '.continue/skills',\n },\n Crush: {\n label: 'Crush',\n dir: '.crush/skills',\n },\n Droid: {\n label: 'Droid',\n dir: '.factory/skills',\n },\n Goose: {\n label: 'Goose',\n dir: '.goose/skills',\n },\n IFlow: {\n label: 'iFlow CLI',\n dir: '.iflow/skills',\n },\n Junie: {\n label: 'Junie',\n dir: '.junie/skills',\n },\n KiloCode: {\n label: 'Kilo Code',\n dir: '.kilocode/skills',\n },\n Kiro: {\n label: 'Kiro CLI',\n dir: '.kiro/skills',\n },\n Kode: {\n label: 'Kode',\n dir: '.kode/skills',\n },\n MCPJam: {\n label: 'MCPJam',\n dir: '.mcpjam/skills',\n },\n MistralVibe: {\n label: 'Mistral Vibe',\n dir: '.vibe/skills',\n },\n Mux: {\n label: 'Mux',\n dir: '.mux/skills',\n },\n OpenHands: {\n label: 'OpenHands',\n dir: '.openhands/skills',\n },\n Pi: {\n label: 'Pi',\n dir: '.pi/skills',\n },\n Qoder: {\n label: 'Qoder',\n dir: '.qoder/skills',\n },\n Qwen: {\n label: 'Qwen Code',\n dir: '.qwen/skills',\n },\n RooCode: {\n label: 'Roo Code',\n dir: '.roo/skills',\n },\n Zencoder: {\n label: 'Zencoder',\n dir: '.zencoder/skills',\n },\n Neovate: {\n label: 'Neovate',\n dir: '.neovate/skills',\n },\n Pochi: {\n label: 'Pochi',\n dir: '.pochi/skills',\n },\n Other: {\n label: 'Other',\n dir: 'skills',\n },\n} as const;\n\nexport type Platform = keyof typeof PLATFORMS_METADATA;\n\nexport const PLATFORMS = Object.keys(PLATFORMS_METADATA) as Platform[];\n\n/**\n * Maps specific skill keys to special filenames if they differ from standard snake_case.\n */\nconst SKILL_FILENAME_MAP: Partial<Record<Skill, string>> = {};\n\n/**\n * Helper to convert CamelCase to kebab-case for directory naming\n */\nconst camelToKebabCase = (str: string) =>\n str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();\n\n/**\n * Reads the raw markdown content for a specific skill from the assets folder.\n */\nconst getSkillContent = (skill: Skill): string => {\n const baseName = SKILL_FILENAME_MAP[skill] ?? camelToKebabCase(skill);\n const fileName = `./skills/${baseName}.md`;\n\n try {\n return readAsset(fileName);\n } catch {\n console.warn(\n `Warning: Could not read asset for skill: ${skill} at ${fileName}`\n );\n return '';\n }\n};\n\n/**\n * Reads the licence content from the assets folder.\n */\nconst getLicenceContent = (): string => {\n try {\n return readAsset('./LICENCE.md');\n } catch {\n console.warn('Warning: Could not read LICENCE.md asset');\n return '';\n }\n};\n\nconst ALLOWED_FETCH_HOSTS = new Set(['intlayer.org']);\n\n/**\n * Fetches the content of a URL using native fetch.\n */\nconst fetchUrlContent = async (url: string): Promise<string> => {\n const parsed = new URL(url);\n if (\n parsed.protocol !== 'https:' ||\n !ALLOWED_FETCH_HOSTS.has(parsed.hostname)\n ) {\n throw new Error(`Blocked fetch to disallowed host: ${parsed.hostname}`);\n }\n const response = await fetch(url, { redirect: 'error' });\n if (!response.ok) {\n throw new Error(`Failed to fetch ${url}: ${response.statusText}`);\n }\n return response.text();\n};\n\n/**\n * Installs skills using the \"Agent Skills\" directory standard.\n * Standard: <PROJECT_ROOT>/<CONFIG_DIR>/skills/<SKILL_NAME>/SKILL.md\n */\nexport const installSkills = async (\n projectRoot: string,\n platform: Platform,\n skills: Skill[]\n): Promise<string> => {\n // Determine destination directory\n const relativeDir = PLATFORMS_METADATA[platform].dir ?? 'skills';\n const skillsBaseDir = path.join(projectRoot, relativeDir);\n\n // Ensure the base directory exists\n await fs.mkdir(skillsBaseDir, { recursive: true });\n\n const createdSkills: string[] = [];\n const licenceContent = getLicenceContent();\n\n for (const skill of skills) {\n const baseName = SKILL_FILENAME_MAP[skill] ?? camelToKebabCase(skill);\n const skillName = `intlayer-${baseName}`;\n const skillContent = getSkillContent(skill);\n\n if (!skillContent) continue;\n\n // Extract unique URLs\n const urls = Array.from(\n new Set(skillContent.match(/https:\\/\\/intlayer\\.org\\/[^\\s)]+\\.md/g) || [])\n );\n\n // Agent Standard: .../skills/<skill-name>/SKILL.md\n const skillDir = path.join(skillsBaseDir, skillName);\n const referenceDir = path.join(skillDir, 'references');\n\n // Ensure directories exist\n await fs.mkdir(referenceDir, { recursive: true });\n\n // Write License\n if (licenceContent) {\n await fs.writeFile(\n path.join(skillDir, 'LICENCE.md'),\n licenceContent,\n 'utf-8'\n );\n }\n\n let updatedSkillContent = skillContent;\n\n // Parallel download of references\n const downloadPromises = urls.map(async (url) => {\n try {\n const content = await fetchUrlContent(url);\n const metadata = getMarkdownMetadata<{ slugs?: string[] }>(content);\n\n let fileName = '';\n\n // Determine filename from slugs or URL path\n if (Array.isArray(metadata.slugs)) {\n fileName = metadata.slugs.filter((slug) => slug !== 'doc').join('_');\n } else {\n const urlPath = new URL(url).pathname;\n fileName = urlPath\n .split('/')\n .filter((part) => part && part !== 'doc')\n .map((part) => part.replace('.md', '')) // Clean extension for joining\n .join('_');\n }\n\n // Ensure it ends with .md\n fileName = fileName ? `${fileName}.md` : 'index.md';\n const localRefPath = `references/${fileName}`;\n\n return {\n url,\n localRefPath,\n fileName,\n content,\n success: true,\n };\n } catch (error) {\n console.warn(\n `Warning: Failed to download ref ${url} for skill ${skill}`,\n error\n );\n return { url, success: false };\n }\n });\n\n const results = await Promise.all(downloadPromises);\n\n // Process results: Write files and update content string\n for (const res of results) {\n if (res.success && res.fileName && res.content && res.localRefPath) {\n // Write the reference file\n await fs.writeFile(\n path.join(referenceDir, res.fileName),\n res.content,\n 'utf-8'\n );\n\n // Update the main content to point to local file\n updatedSkillContent = updatedSkillContent.replaceAll(\n res.url,\n res.localRefPath\n );\n }\n }\n\n // Write the main SKILL.md\n const filePath = path.join(skillDir, 'SKILL.md');\n await fs.writeFile(filePath, updatedSkillContent, 'utf-8');\n createdSkills.push(`${skillName}/SKILL.md`);\n }\n\n if (createdSkills.length === 0) {\n return `No skill files were created. Check your asset paths.`;\n }\n\n return `${v} Created ${createdSkills.length} skills in ${skillsBaseDir}`;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["readAsset","path","fs","getMarkdownMetadata","v"],"sources":["../../../src/installSkills/index.ts"],"sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { readAsset } from 'utils:asset';\nimport { v } from '@intlayer/config/logger';\nimport { getMarkdownMetadata } from '@intlayer/core/markdown';\n\n/**\n * Metadata for each available documentation skill.\n */\nexport const SKILLS_METADATA = {\n Config: 'Intlayer configuration documentation',\n Content: 'Reference for all Intlayer content node types (t, enu, etc.)',\n Usage: 'How to use Intlayer in your project',\n CLI: 'Intlayer CLI commands and usage',\n Compiler:\n 'Intlayer Compiler setup and usage for automatic content extraction without .content files',\n RemoteContent: 'How to use Intlayer with Remote/CMS/Server-side content',\n NextJS: 'Next.js-specific usage (Server & Client components)',\n React: 'React-specific syntax and hooks usage',\n Vue: 'Vue-specific composables and syntax',\n Svelte: 'Svelte-specific stores and syntax',\n Angular: 'Angular-specific syntax and Injectable Function usage',\n Preact: 'Preact-specific syntax and hooks usage',\n Solid:\n 'Integrates Intlayer internationalization with SolidJS components. Use when the user asks to \"setup SolidJS i18n\", use the \"useIntlayer\" hook in Solid, or manage locales in a SolidJS application.',\n Astro: 'Astro-specific usage and getIntlayer',\n} as const;\n\nexport type Skill = keyof typeof SKILLS_METADATA;\n\nexport const SKILLS = Object.keys(SKILLS_METADATA) as Skill[];\n\nexport const getInitialSkills = (\n deps: Record<string, string>\n): (keyof typeof SKILLS_METADATA)[] => {\n const skills: (keyof typeof SKILLS_METADATA)[] = [\n 'Usage',\n 'Content',\n 'Config',\n 'CLI',\n 'Compiler',\n ];\n\n if (deps.next) skills.push('NextJS');\n if (deps.react || !deps.next) skills.push('React');\n if (deps.preact) skills.push('Preact');\n if (deps['solid-js']) skills.push('Solid');\n if (deps.vue || deps.nuxt) skills.push('Vue');\n if (deps.svelte || deps['@sveltejs/kit']) skills.push('Svelte');\n if (deps.astro) skills.push('Astro');\n\n return skills;\n};\n\nexport interface PlatformMetadata {\n label: string;\n dir: string;\n check?: () => boolean;\n}\n\n/**\n * Metadata and configuration for each supported platform.\n */\nexport const PLATFORMS_METADATA: Record<string, PlatformMetadata> = {\n Cursor: {\n label: 'Cursor',\n dir: '.cursor/skills',\n check: () =>\n process.env.CURSOR === 'true' || process.env.TERM_PROGRAM === 'cursor',\n },\n Windsurf: {\n label: 'Windsurf',\n dir: '.windsurf/skills',\n check: () =>\n process.env.WINDSURF === 'true' ||\n process.env.TERM_PROGRAM === 'windsurf',\n },\n Trae: {\n label: 'Trae',\n dir: '.trae/skills',\n check: () =>\n process.env.TRAE === 'true' || process.env.TERM_PROGRAM === 'trae',\n },\n TraeCN: {\n label: 'Trae CN',\n dir: '.trae/skills',\n check: () => process.env.TRAE_CN === 'true',\n },\n VSCode: {\n label: 'VS Code',\n dir: '.vscode/skills',\n check: () =>\n process.env.VSCODE === 'true' || process.env.TERM_PROGRAM === 'vscode',\n },\n OpenCode: {\n label: 'OpenCode',\n dir: '.opencode/skills',\n check: () => process.env.OPENCODE === 'true',\n },\n Claude: {\n label: 'Claude Code',\n dir: '.claude/skills',\n check: () => process.env.CLAUDE === 'true',\n },\n GitHub: {\n label: 'GitHub Copilot Workspace',\n dir: '.github/skills',\n check: () =>\n process.env.GITHUB_ACTIONS === 'true' || !!process.env.GITHUB_WORKSPACE,\n },\n Antigravity: {\n label: 'Antigravity',\n dir: '.agent/skills',\n },\n Augment: {\n label: 'Augment',\n dir: '.augment/skills',\n },\n OpenClaw: {\n label: 'OpenClaw',\n dir: 'skills',\n },\n Cline: {\n label: 'Cline',\n dir: '.cline/skills',\n },\n CodeBuddy: {\n label: 'CodeBuddy',\n dir: '.codebuddy/skills',\n },\n CommandCode: {\n label: 'Command Code',\n dir: '.commandcode/skills',\n },\n Continue: {\n label: 'Continue',\n dir: '.continue/skills',\n },\n Crush: {\n label: 'Crush',\n dir: '.crush/skills',\n },\n Droid: {\n label: 'Droid',\n dir: '.factory/skills',\n },\n Goose: {\n label: 'Goose',\n dir: '.goose/skills',\n },\n IFlow: {\n label: 'iFlow CLI',\n dir: '.iflow/skills',\n },\n Junie: {\n label: 'Junie',\n dir: '.junie/skills',\n },\n KiloCode: {\n label: 'Kilo Code',\n dir: '.kilocode/skills',\n },\n Kiro: {\n label: 'Kiro CLI',\n dir: '.kiro/skills',\n },\n Kode: {\n label: 'Kode',\n dir: '.kode/skills',\n },\n MCPJam: {\n label: 'MCPJam',\n dir: '.mcpjam/skills',\n },\n MistralVibe: {\n label: 'Mistral Vibe',\n dir: '.vibe/skills',\n },\n Mux: {\n label: 'Mux',\n dir: '.mux/skills',\n },\n OpenHands: {\n label: 'OpenHands',\n dir: '.openhands/skills',\n },\n Pi: {\n label: 'Pi',\n dir: '.pi/skills',\n },\n Qoder: {\n label: 'Qoder',\n dir: '.qoder/skills',\n },\n Qwen: {\n label: 'Qwen Code',\n dir: '.qwen/skills',\n },\n RooCode: {\n label: 'Roo Code',\n dir: '.roo/skills',\n },\n Zencoder: {\n label: 'Zencoder',\n dir: '.zencoder/skills',\n },\n Neovate: {\n label: 'Neovate',\n dir: '.neovate/skills',\n },\n Pochi: {\n label: 'Pochi',\n dir: '.pochi/skills',\n },\n Other: {\n label: 'Other',\n dir: 'skills',\n },\n} as const;\n\nexport type Platform = keyof typeof PLATFORMS_METADATA;\n\nexport const PLATFORMS = Object.keys(PLATFORMS_METADATA) as Platform[];\n\n/**\n * Maps specific skill keys to special filenames if they differ from standard snake_case.\n */\nconst SKILL_FILENAME_MAP: Partial<Record<Skill, string>> = {};\n\n/**\n * Helper to convert CamelCase to kebab-case for directory naming\n */\nconst camelToKebabCase = (str: string) =>\n str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();\n\n/**\n * Reads the raw markdown content for a specific skill from the assets folder.\n */\nconst getSkillContent = (skill: Skill): string => {\n const baseName = SKILL_FILENAME_MAP[skill] ?? camelToKebabCase(skill);\n const fileName = `./skills/${baseName}.md`;\n\n try {\n return readAsset(fileName);\n } catch {\n console.warn(\n `Warning: Could not read asset for skill: ${skill} at ${fileName}`\n );\n return '';\n }\n};\n\n/**\n * Reads the licence content from the assets folder.\n */\nconst getLicenceContent = (): string => {\n try {\n return readAsset('./LICENCE.md');\n } catch {\n console.warn('Warning: Could not read LICENCE.md asset');\n return '';\n }\n};\n\nconst ALLOWED_FETCH_HOSTS = new Set(['intlayer.org']);\n\n/** Upper bound on the redirect chain a reference URL may take. */\nconst MAX_FETCH_REDIRECTS = 5;\n\n/**\n * Parses a URL and rejects it unless it is an HTTPS address on an allowed host.\n *\n * @param url - Absolute URL to validate.\n * @returns The parsed URL.\n * @throws When the protocol or host is not allowed.\n */\nconst parseAllowedUrl = (url: string | URL): URL => {\n const parsed = new URL(url);\n\n if (\n parsed.protocol !== 'https:' ||\n !ALLOWED_FETCH_HOSTS.has(parsed.hostname)\n ) {\n throw new Error(`Blocked fetch to disallowed host: ${parsed.hostname}`);\n }\n\n return parsed;\n};\n\n/**\n * Fetches the content of a URL using native fetch.\n *\n * Redirects are followed manually so every hop is re-checked against the host\n * allowlist — a redirect off `intlayer.org` is refused rather than silently\n * followed. Letting `fetch` refuse them outright (`redirect: 'error'`) instead\n * reported an unhelpful `TypeError: fetch failed` for a documentation URL that\n * merely moved.\n *\n * @param url - Absolute URL to read.\n * @returns The response body as text.\n */\nconst fetchUrlContent = async (url: string): Promise<string> => {\n let target = parseAllowedUrl(url);\n\n for (let hop = 0; hop <= MAX_FETCH_REDIRECTS; hop++) {\n const response = await fetch(target, { redirect: 'manual' });\n\n if (response.status >= 300 && response.status < 400) {\n const location = response.headers.get('location');\n\n if (!location) {\n throw new Error(`Failed to fetch ${url}: redirect without a location`);\n }\n\n target = parseAllowedUrl(new URL(location, target));\n continue;\n }\n\n if (!response.ok) {\n throw new Error(`Failed to fetch ${url}: ${response.statusText}`);\n }\n\n return response.text();\n }\n\n throw new Error(`Failed to fetch ${url}: too many redirects`);\n};\n\n/**\n * Installs skills using the \"Agent Skills\" directory standard.\n * Standard: <PROJECT_ROOT>/<CONFIG_DIR>/skills/<SKILL_NAME>/SKILL.md\n */\nexport const installSkills = async (\n projectRoot: string,\n platform: Platform,\n skills: Skill[]\n): Promise<string> => {\n // Determine destination directory\n const relativeDir = PLATFORMS_METADATA[platform]?.dir ?? 'skills';\n const skillsBaseDir = path.join(projectRoot, relativeDir);\n\n // Ensure the base directory exists\n await fs.mkdir(skillsBaseDir, { recursive: true });\n\n const createdSkills: string[] = [];\n const licenceContent = getLicenceContent();\n\n for (const skill of skills) {\n const baseName = SKILL_FILENAME_MAP[skill] ?? camelToKebabCase(skill);\n const skillName = `intlayer-${baseName}`;\n const skillContent = getSkillContent(skill);\n\n if (!skillContent) continue;\n\n // Extract unique URLs\n const urls = Array.from(\n new Set(skillContent.match(/https:\\/\\/intlayer\\.org\\/[^\\s)]+\\.md/g) || [])\n );\n\n // Agent Standard: .../skills/<skill-name>/SKILL.md\n const skillDir = path.join(skillsBaseDir, skillName);\n const referenceDir = path.join(skillDir, 'references');\n\n // Ensure directories exist\n await fs.mkdir(referenceDir, { recursive: true });\n\n // Write License\n if (licenceContent) {\n await fs.writeFile(\n path.join(skillDir, 'LICENCE.md'),\n licenceContent,\n 'utf-8'\n );\n }\n\n let updatedSkillContent = skillContent;\n\n // Parallel download of references\n const downloadPromises = urls.map(async (url) => {\n try {\n const content = await fetchUrlContent(url);\n const metadata = getMarkdownMetadata<{ slugs?: string[] }>(content);\n\n let fileName = '';\n\n // Determine filename from slugs or URL path\n if (Array.isArray(metadata.slugs)) {\n fileName = metadata.slugs.filter((slug) => slug !== 'doc').join('_');\n } else {\n const urlPath = new URL(url).pathname;\n fileName = urlPath\n .split('/')\n .filter((part) => part && part !== 'doc')\n .map((part) => part.replace('.md', '')) // Clean extension for joining\n .join('_');\n }\n\n // Ensure it ends with .md\n fileName = fileName ? `${fileName}.md` : 'index.md';\n const localRefPath = `references/${fileName}`;\n\n return {\n url,\n localRefPath,\n fileName,\n content,\n success: true,\n };\n } catch (error) {\n console.warn(\n `Warning: Failed to download ref ${url} for skill ${skill}`,\n error\n );\n return { url, success: false };\n }\n });\n\n const results = await Promise.all(downloadPromises);\n\n // Process results: Write files and update content string\n for (const res of results) {\n if (res.success && res.fileName && res.content && res.localRefPath) {\n // Write the reference file\n await fs.writeFile(\n path.join(referenceDir, res.fileName),\n res.content,\n 'utf-8'\n );\n\n // Update the main content to point to local file\n updatedSkillContent = updatedSkillContent.replaceAll(\n res.url,\n res.localRefPath\n );\n }\n }\n\n // Write the main SKILL.md\n const filePath = path.join(skillDir, 'SKILL.md');\n await fs.writeFile(filePath, updatedSkillContent, 'utf-8');\n createdSkills.push(`${skillName}/SKILL.md`);\n }\n\n if (createdSkills.length === 0) {\n return `No skill files were created. Check your asset paths.`;\n }\n\n return `${v} Created ${createdSkills.length} skills in ${skillsBaseDir}`;\n};\n"],"mappings":"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"}
@@ -11,14 +11,34 @@ let _intlayer_config_utils = require("@intlayer/config/utils");
11
11
  * exports consumers import — preventing `MISSING_EXPORT` errors when a new
12
12
  * configuration section (e.g. `analytics`) is added.
13
13
  *
14
+ * ## Why `editor.clientSecret` is never emitted
15
+ *
16
+ * This file is what a bundle *inlines*. Every framework integration installs the
17
+ * same alias for the client and the server build, so anything written here that
18
+ * a client module imports ends up in a public asset — and `editor` is imported
19
+ * by browser code (the provider, the analytics SDK, the visual editor).
20
+ *
21
+ * `clientSecret` grants full project-scoped API access, so it is stripped here
22
+ * rather than in each integration: a per-bundler fix only protects the bundlers
23
+ * we patched, while stripping it at the source protects every consumer,
24
+ * including ones that build their own alias.
25
+ *
26
+ * Server-side code reads the secret from `@intlayer/config/secrets`, whose
27
+ * `browser` export condition resolves to an empty stub in browser builds.
28
+ *
14
29
  * @param configuration - The fully resolved Intlayer configuration.
15
30
  * @param format - Module format to emit (`esm` for `.mjs`, `cjs` for `.cjs`).
16
31
  * @returns The file content as a string.
17
32
  */
18
33
  const generateConfigurationContent = (configuration, format) => {
19
34
  let fileContent = "";
35
+ const { clientSecret: _clientSecret, ...editorWithoutSecret } = configuration.editor;
36
+ const values = {
37
+ ...configuration,
38
+ editor: editorWithoutSecret
39
+ };
20
40
  for (const key of _intlayer_config_utils.BUILT_CONFIG_KEYS) {
21
- const value = configuration[key];
41
+ const value = values[key];
22
42
  fileContent += `const ${key} = ${JSON.stringify(value, null, 2)};\n`;
23
43
  }
24
44
  if (format === "esm") fileContent += `\nexport { ${_intlayer_config_utils.BUILT_CONFIG_KEYS.join(", ")} };\n`;
@@ -1 +1 @@
1
- {"version":3,"file":"generateConfigurationContent.cjs","names":["BUILT_CONFIG_KEYS"],"sources":["../../../src/writeConfiguration/generateConfigurationContent.ts"],"sourcesContent":["import { BUILT_CONFIG_KEYS } from '@intlayer/config/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\n\n/**\n * Generates the source of the `.intlayer/config/configuration.{mjs,cjs}` file\n * that bundler integrations alias `@intlayer/config/built` to.\n *\n * The emitted named exports are derived from {@link BUILT_CONFIG_KEYS}, the same\n * canonical list `built.ts` uses, so the aliased file always exposes exactly the\n * exports consumers import — preventing `MISSING_EXPORT` errors when a new\n * configuration section (e.g. `analytics`) is added.\n *\n * @param configuration - The fully resolved Intlayer configuration.\n * @param format - Module format to emit (`esm` for `.mjs`, `cjs` for `.cjs`).\n * @returns The file content as a string.\n */\nexport const generateConfigurationContent = (\n configuration: IntlayerConfig,\n format: 'cjs' | 'esm'\n): string => {\n let fileContent = '';\n\n for (const key of BUILT_CONFIG_KEYS) {\n const value = configuration[key];\n fileContent += `const ${key} = ${JSON.stringify(value, null, 2)};\\n`;\n }\n\n if (format === 'esm') {\n fileContent += `\\nexport { ${BUILT_CONFIG_KEYS.join(', ')} };\\n`;\n } else {\n fileContent += '\\n';\n for (const key of BUILT_CONFIG_KEYS) {\n fileContent += `module.exports.${key} = ${key};\\n`;\n }\n }\n\n return fileContent;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AAgBA,MAAa,gCACX,eACA,WACW;CACX,IAAI,cAAc;CAElB,KAAK,MAAM,OAAOA,0CAAmB;EACnC,MAAM,QAAQ,cAAc;EAC5B,eAAe,SAAS,IAAI,KAAK,KAAK,UAAU,OAAO,MAAM,CAAC,EAAE;CAClE;CAEA,IAAI,WAAW,OACb,eAAe,cAAcA,yCAAkB,KAAK,IAAI,EAAE;MACrD;EACL,eAAe;EACf,KAAK,MAAM,OAAOA,0CAChB,eAAe,kBAAkB,IAAI,KAAK,IAAI;CAElD;CAEA,OAAO;AACT"}
1
+ {"version":3,"file":"generateConfigurationContent.cjs","names":["BUILT_CONFIG_KEYS"],"sources":["../../../src/writeConfiguration/generateConfigurationContent.ts"],"sourcesContent":["import { BUILT_CONFIG_KEYS } from '@intlayer/config/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\n\n/**\n * Generates the source of the `.intlayer/config/configuration.{mjs,cjs}` file\n * that bundler integrations alias `@intlayer/config/built` to.\n *\n * The emitted named exports are derived from {@link BUILT_CONFIG_KEYS}, the same\n * canonical list `built.ts` uses, so the aliased file always exposes exactly the\n * exports consumers import — preventing `MISSING_EXPORT` errors when a new\n * configuration section (e.g. `analytics`) is added.\n *\n * ## Why `editor.clientSecret` is never emitted\n *\n * This file is what a bundle *inlines*. Every framework integration installs the\n * same alias for the client and the server build, so anything written here that\n * a client module imports ends up in a public asset — and `editor` is imported\n * by browser code (the provider, the analytics SDK, the visual editor).\n *\n * `clientSecret` grants full project-scoped API access, so it is stripped here\n * rather than in each integration: a per-bundler fix only protects the bundlers\n * we patched, while stripping it at the source protects every consumer,\n * including ones that build their own alias.\n *\n * Server-side code reads the secret from `@intlayer/config/secrets`, whose\n * `browser` export condition resolves to an empty stub in browser builds.\n *\n * @param configuration - The fully resolved Intlayer configuration.\n * @param format - Module format to emit (`esm` for `.mjs`, `cjs` for `.cjs`).\n * @returns The file content as a string.\n */\nexport const generateConfigurationContent = (\n configuration: IntlayerConfig,\n format: 'cjs' | 'esm'\n): string => {\n let fileContent = '';\n\n const { clientSecret: _clientSecret, ...editorWithoutSecret } =\n configuration.editor;\n\n const values: Record<string, unknown> = {\n ...configuration,\n editor: editorWithoutSecret,\n };\n\n for (const key of BUILT_CONFIG_KEYS) {\n const value = values[key];\n fileContent += `const ${key} = ${JSON.stringify(value, null, 2)};\\n`;\n }\n\n if (format === 'esm') {\n fileContent += `\\nexport { ${BUILT_CONFIG_KEYS.join(', ')} };\\n`;\n } else {\n fileContent += '\\n';\n for (const key of BUILT_CONFIG_KEYS) {\n fileContent += `module.exports.${key} = ${key};\\n`;\n }\n }\n\n return fileContent;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,MAAa,gCACX,eACA,WACW;CACX,IAAI,cAAc;CAElB,MAAM,EAAE,cAAc,eAAe,GAAG,wBACtC,cAAc;CAEhB,MAAM,SAAkC;EACtC,GAAG;EACH,QAAQ;CACV;CAEA,KAAK,MAAM,OAAOA,0CAAmB;EACnC,MAAM,QAAQ,OAAO;EACrB,eAAe,SAAS,IAAI,KAAK,KAAK,UAAU,OAAO,MAAM,CAAC,EAAE;CAClE;CAEA,IAAI,WAAW,OACb,eAAe,cAAcA,yCAAkB,KAAK,IAAI,EAAE;MACrD;EACL,eAAe;EACf,KAAK,MAAM,OAAOA,0CAChB,eAAe,kBAAkB,IAAI,KAAK,IAAI;CAElD;CAEA,OAAO;AACT"}
@@ -7,7 +7,7 @@ import { getBuiltUnmergedDictionariesPath } from "./createDictionaryEntryPoint/g
7
7
  import { createDictionaryEntryPoint } from "./createDictionaryEntryPoint/createDictionaryEntryPoint.mjs";
8
8
  import { readDictionariesFromDisk } from "./utils/readDictionariesFromDisk.mjs";
9
9
  import { buildDictionary } from "./buildIntlayerDictionary/buildIntlayerDictionary.mjs";
10
- import { createModuleAugmentation, formatRewriteRules, getTypeName } from "./createType/createModuleAugmentation.mjs";
10
+ import { createModuleAugmentation, formatDomains, formatRewriteRules, getTypeName } from "./createType/createModuleAugmentation.mjs";
11
11
  import { createTypes, generateTypeScriptType } from "./createType/createType.mjs";
12
12
  import { processContentDeclaration } from "./buildIntlayerDictionary/processContentDeclaration.mjs";
13
13
  import { ensureIntlayerBundle, formatLocalDictionaries, loadContentDeclaration, loadContentDeclarations } from "./loadDictionaries/loadContentDeclaration.mjs";
@@ -23,4 +23,4 @@ import { transformJSFile } from "./writeContentDeclaration/transformJSFile.mjs";
23
23
  import { writeJSFile } from "./writeContentDeclaration/writeJSFile.mjs";
24
24
  import { writeContentDeclaration } from "./writeContentDeclaration/writeContentDeclaration.mjs";
25
25
 
26
- export { buildDictionary, cleanOutputDir, createDictionaryEntryPoint, createModuleAugmentation, createTypes, detectExportedComponentName, ensureIntlayerBundle, formatDictionaries, formatDictionariesOutput, formatDictionary, formatDictionaryOutput, formatDistantDictionaries, formatLocalDictionaries, formatRewriteRules, generateDictionaryListContent, generateTypeScriptType, getBuiltDictionariesPath, getBuiltDynamicDictionariesPath, getBuiltFetchDictionariesPath, getBuiltRemoteDictionariesPath, getBuiltUnmergedDictionariesPath, getTypeName, isCachedConfigurationUpToDate, loadContentDeclaration, loadContentDeclarations, loadDictionaries, loadLocalDictionaries, loadRemoteDictionaries, prepareIntlayer, processContentDeclaration, readDictionariesFromDisk, transformJSFile, writeConfiguration, writeContentDeclaration, writeJSFile };
26
+ export { buildDictionary, cleanOutputDir, createDictionaryEntryPoint, createModuleAugmentation, createTypes, detectExportedComponentName, ensureIntlayerBundle, formatDictionaries, formatDictionariesOutput, formatDictionary, formatDictionaryOutput, formatDistantDictionaries, formatDomains, formatLocalDictionaries, formatRewriteRules, generateDictionaryListContent, generateTypeScriptType, getBuiltDictionariesPath, getBuiltDynamicDictionariesPath, getBuiltFetchDictionariesPath, getBuiltRemoteDictionariesPath, getBuiltUnmergedDictionariesPath, getTypeName, isCachedConfigurationUpToDate, loadContentDeclaration, loadContentDeclarations, loadDictionaries, loadLocalDictionaries, loadRemoteDictionaries, prepareIntlayer, processContentDeclaration, readDictionariesFromDisk, transformJSFile, writeConfiguration, writeContentDeclaration, writeJSFile };
@@ -4,7 +4,7 @@ import { mkdir } from "node:fs/promises";
4
4
  import { basename, extname, join, relative } from "node:path";
5
5
  import fg from "fast-glob";
6
6
  import { kebabCaseToCamelCase, normalizePath } from "@intlayer/config/utils";
7
- import { getRewriteRules } from "@intlayer/core/localization";
7
+ import { getDomainOrigin, getRewriteRules, isLocaleExclusiveOnDomain } from "@intlayer/core/localization";
8
8
 
9
9
  //#region src/createType/createModuleAugmentation.ts
10
10
  const getTypeName = (key) => `${kebabCaseToCamelCase(key)}Content`;
@@ -26,6 +26,25 @@ const formatRewriteRules = (rewrite) => {
26
26
  return `'${canonical}': { ${Object.entries(localized).filter(([, pattern]) => typeof pattern === "string").map(([locale, pattern]) => `'${locale}': '${pattern}'`).join("; ")} }`;
27
27
  }).join("; ")} }`;
28
28
  };
29
+ /**
30
+ * Serializes the `routing.domains` map as a type literal, so `getLocalizedUrl`
31
+ * and `getLocalizedPath` can resolve the origin of a locale — and the
32
+ * suppression of its path prefix — at the type level. Returns an empty string
33
+ * when the project maps no locale to a domain.
34
+ *
35
+ * Each entry carries the two facts the consuming types need, both computed with
36
+ * the very helpers the runtime uses:
37
+ * - `origin`: the normalized origin ({@link getDomainOrigin}), prepended to the
38
+ * localized path when the target locale lives on another domain.
39
+ * - `exclusive`: whether the locale is the only one on that hostname
40
+ * ({@link isLocaleExclusiveOnDomain}), in which case the domain identifies the
41
+ * locale and no path prefix is emitted.
42
+ */
43
+ const formatDomains = (domains) => {
44
+ const domainEntries = Object.entries(domains ?? {}).filter(([, domain]) => typeof domain === "string" && domain.length > 0);
45
+ if (!domainEntries.length) return "";
46
+ return `; domains: { ${domainEntries.map(([locale, domain]) => `'${locale}': { origin: '${getDomainOrigin(domain)}'; exclusive: ${isLocaleExclusiveOnDomain(locale, domains)} }`).join("; ")} }`;
47
+ };
29
48
  const zodToTsString = (schema) => {
30
49
  if (!schema) return "any";
31
50
  const def = schema._def ?? schema.def ?? schema;
@@ -113,7 +132,8 @@ const generateTypeIndexContent = (typeFiles, configuration, zodToTsFns) => {
113
132
  fileContent += ` interface __EditorRegistry { enabled : ${enabled} }\n\n`;
114
133
  const routingMode = routing?.mode ?? "prefix-no-default";
115
134
  const formattedRewriteRules = formatRewriteRules(routing?.rewrite);
116
- fileContent += ` interface __RoutingRegistry { mode: '${routingMode}'; defaultLocale: '${defaultLocale}'${formattedRewriteRules} }\n`;
135
+ const formattedDomains = formatDomains(routing?.domains);
136
+ fileContent += ` interface __RoutingRegistry { mode: '${routingMode}'; defaultLocale: '${defaultLocale}'${formattedRewriteRules}${formattedDomains} }\n`;
117
137
  fileContent += `}\n`;
118
138
  return fileContent;
119
139
  };
@@ -137,5 +157,5 @@ const createModuleAugmentation = async (configuration) => {
137
157
  };
138
158
 
139
159
  //#endregion
140
- export { createModuleAugmentation, formatRewriteRules, getTypeName };
160
+ export { createModuleAugmentation, formatDomains, formatRewriteRules, getTypeName };
141
161
  //# sourceMappingURL=createModuleAugmentation.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"createModuleAugmentation.mjs","names":[],"sources":["../../../src/createType/createModuleAugmentation.ts"],"sourcesContent":["import { mkdir } from 'node:fs/promises';\nimport { basename, extname, join, relative } from 'node:path';\nimport { kebabCaseToCamelCase, normalizePath } from '@intlayer/config/utils';\nimport { getRewriteRules } from '@intlayer/core/localization';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { IntlayerConfig, RoutingConfig } from '@intlayer/types/config';\nimport fg from 'fast-glob';\nimport { getPathHash } from '../utils';\nimport { writeFileIfChanged } from '../writeFileIfChanged';\n\nexport const getTypeName = (key: string): string =>\n `${kebabCaseToCamelCase(key)}Content`;\n\n/** Returns lines like: [Locales.FRENCH]: 1; */\nconst formatLocales = (locales: Locale[]) =>\n locales.map((locale) => ` \"${locale}\": 1;`).join('\\n');\n\n/**\n * Serializes the `url` rewrite rules as a type literal, so `getLocalizedPath`\n * and `getLocalizedUrl` can resolve a canonical path to its localized literal at\n * the type level. Returns an empty string when the project declares no rewrite —\n * the consuming types then localize every path to itself.\n *\n * Rules are emitted in their normalized form (`:param`, no locale prefix), which\n * is exactly what the runtime matches against.\n */\nexport const formatRewriteRules = (\n rewrite: RoutingConfig['rewrite']\n): string => {\n const rewriteRules = getRewriteRules(rewrite, 'url');\n\n if (!rewriteRules?.rules?.length) return '';\n\n const formattedRules = rewriteRules.rules\n .map(({ canonical, localized }) => {\n const formattedLocalized = Object.entries(localized)\n .filter(([, pattern]) => typeof pattern === 'string')\n .map(([locale, pattern]) => `'${locale}': '${pattern}'`)\n .join('; ');\n\n return `'${canonical}': { ${formattedLocalized} }`;\n })\n .join('; ');\n\n return `; rewrite: { ${formattedRules} }`;\n};\n\nconst zodToTsString = (schema: any): string => {\n if (!schema) return 'any';\n\n // Support both real Zod objects (_def) and serialized versions (def or nested)\n const def = schema._def ?? schema.def ?? schema;\n\n // Handle serialized type names (sometimes 'type' instead of 'typeName')\n const typeName = def.typeName ?? def.type;\n\n switch (typeName) {\n case 'ZodString':\n case 'string':\n return 'string';\n case 'ZodNumber':\n case 'number':\n return 'number';\n case 'ZodBoolean':\n case 'boolean':\n return 'boolean';\n case 'ZodNull':\n case 'null':\n return 'null';\n case 'ZodUndefined':\n case 'undefined':\n return 'undefined';\n case 'ZodArray':\n case 'array':\n return `${zodToTsString(def.type ?? def.element)}[]`;\n case 'ZodObject':\n case 'object': {\n const shape = typeof def.shape === 'function' ? def.shape() : def.shape;\n if (!shape) return 'Record<string, any>';\n\n const entries = Object.entries(shape)\n .map(([k, v]) => ` \"${k}\": ${zodToTsString(v)};`)\n .join('\\n');\n return `{\\n${entries}\\n }`;\n }\n case 'ZodOptional':\n case 'optional':\n return `${zodToTsString(def.innerType ?? def.wrapped)} | undefined`;\n case 'ZodNullable':\n case 'nullable':\n return `${zodToTsString(def.innerType ?? def.wrapped)} | null`;\n case 'ZodUnion':\n case 'union': {\n const options = def.options ?? [];\n return options.map(zodToTsString).join(' | ');\n }\n case 'ZodIntersection':\n case 'intersection':\n return `${zodToTsString(def.left)} & ${zodToTsString(def.right)}`;\n case 'ZodEnum':\n case 'enum': {\n const values = def.values ?? [];\n return values.map((v: string) => `\"${v}\"`).join(' | ');\n }\n case 'ZodLiteral':\n case 'literal': {\n const value = def.value;\n return typeof value === 'string' ? `\"${value}\"` : String(value);\n }\n default:\n return 'any';\n }\n};\n\ntype ZodToTsFns = {\n zodToTs: (schema: any, opts?: any) => { node: any };\n printNode: (node: any) => string;\n createAuxiliaryTypeStore: () => any;\n};\n\n/** Generate the content of the module augmentation file */\nconst generateTypeIndexContent = (\n typeFiles: string[],\n configuration: IntlayerConfig,\n zodToTsFns: ZodToTsFns | null\n): string => {\n const { internationalization, system, editor, routing } = configuration;\n const { moduleAugmentationDir } = system;\n const { enabled } = editor;\n const { locales, requiredLocales, strictMode, defaultLocale } =\n internationalization;\n\n let fileContent = 'import \"intlayer\";\\n';\n\n // Build dictionary refs\n const dictionariesRef = typeFiles.map((dictionaryPath) => ({\n relativePath: `./${normalizePath(relative(moduleAugmentationDir, dictionaryPath))}`,\n id: basename(dictionaryPath, extname(dictionaryPath)),\n hash: `_${getPathHash(dictionaryPath)}`,\n }));\n\n // Import all dictionaries\n for (const dictionary of dictionariesRef) {\n fileContent += `import ${dictionary.hash} from '${dictionary.relativePath}';\\n`;\n }\n fileContent += '\\n';\n\n // Dictionary map entries (id: typeof <hash>)\n const formattedDictionaryMap: string = dictionariesRef\n .map((dictionary) => ` \"${dictionary.id}\": typeof ${dictionary.hash};`)\n .join('\\n');\n\n // Ensure required ⊆ declared; if empty, default required = declared\n const declared = locales;\n const requiredSanitized = requiredLocales?.length\n ? requiredLocales.filter((requiredLocales) =>\n declared.includes(requiredLocales)\n )\n : declared;\n\n const formattedDeclaredLocales = formatLocales(declared);\n const formattedRequiredLocales = formatLocales(requiredSanitized);\n\n // Build schema registry\n const schemas = configuration.schemas ?? {};\n const formattedSchemas = Object.entries(schemas)\n .map(([key, schema]) => {\n let typeStr = 'any';\n\n if (schema) {\n try {\n if (zodToTsFns) {\n const { node } = zodToTsFns.zodToTs(schema, {\n auxiliaryTypeStore: zodToTsFns.createAuxiliaryTypeStore(),\n });\n // 133 is the kind for AnyKeyword in TypeScript\n if ((node as any).kind !== 133) {\n typeStr = zodToTsFns.printNode(node);\n } else {\n typeStr = zodToTsString(schema);\n }\n } else {\n typeStr = zodToTsString(schema);\n }\n } catch (_e) {\n // Fallback to custom string generator\n typeStr = zodToTsString(schema);\n }\n }\n return ` \"${key}\": ${typeStr};`;\n })\n .join('\\n');\n\n // Choose strict mode registry key\n const strictKey =\n strictMode === 'strict'\n ? 'strict'\n : strictMode === 'inclusive'\n ? 'inclusive'\n : 'loose';\n\n /**\n * Module augmentation that ONLY adds keys to registries.\n * No types/aliases redefined here—avoids merge conflicts.\n */\n fileContent += `declare module 'intlayer' {\\n`;\n // Dictionaries registry\n fileContent += ` interface __DictionaryRegistry {\\n${formattedDictionaryMap}\\n }\\n\\n`;\n // Locales registries\n fileContent += ` interface __DeclaredLocalesRegistry {\\n${formattedDeclaredLocales}\\n }\\n\\n`;\n fileContent += ` interface __RequiredLocalesRegistry {\\n${formattedRequiredLocales}\\n }\\n\\n`;\n // Schema registry\n fileContent += ` interface __SchemaRegistry {\\n${formattedSchemas}\\n }\\n\\n`;\n // Resolved strict mode (narrow the literal at build time)\n fileContent += ` interface __StrictModeRegistry { mode: '${strictKey}' }\\n\\n`;\n // Editor registry\n fileContent += ` interface __EditorRegistry { enabled : ${enabled} }\\n\\n`;\n // Routing registry (mode + defaultLocale narrowed to literals)\n const routingMode = routing?.mode ?? 'prefix-no-default';\n const formattedRewriteRules = formatRewriteRules(routing?.rewrite);\n fileContent += ` interface __RoutingRegistry { mode: '${routingMode}'; defaultLocale: '${defaultLocale}'${formattedRewriteRules} }\\n`;\n fileContent += `}\\n`;\n\n return fileContent;\n};\n\n/** Generate the index file merging all the types */\nexport const createModuleAugmentation = async (\n configuration: IntlayerConfig\n) => {\n const { moduleAugmentationDir, typesDir } = configuration.system;\n\n await mkdir(moduleAugmentationDir, { recursive: true });\n\n const dictionariesTypesDefinitions: string[] = await fg(\n normalizePath(`${typesDir}/*.ts`),\n { ignore: ['**/*.d.ts'] }\n );\n\n let zodToTsFns: ZodToTsFns | null = null;\n\n const hasSchemas = Object.keys(configuration.schemas ?? {}).length > 0;\n\n // zod-to-ts drags in the whole `typescript` package (~300ms import), so\n // only load it when there are schemas to convert.\n if (hasSchemas) {\n try {\n const mod = await import('zod-to-ts');\n zodToTsFns = {\n zodToTs: mod.zodToTs,\n printNode: mod.printNode,\n createAuxiliaryTypeStore: mod.createAuxiliaryTypeStore,\n };\n } catch {\n // typescript peer dep not installed (plain JS project), use fallback\n }\n }\n\n const tsContent = generateTypeIndexContent(\n dictionariesTypesDefinitions,\n configuration,\n zodToTsFns\n );\n\n const tsFilePath = join(moduleAugmentationDir, 'intlayer.d.ts');\n await writeFileIfChanged(tsFilePath, tsContent);\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"createModuleAugmentation.mjs","names":[],"sources":["../../../src/createType/createModuleAugmentation.ts"],"sourcesContent":["import { mkdir } from 'node:fs/promises';\nimport { basename, extname, join, relative } from 'node:path';\nimport { kebabCaseToCamelCase, normalizePath } from '@intlayer/config/utils';\nimport {\n getDomainOrigin,\n getRewriteRules,\n isLocaleExclusiveOnDomain,\n} from '@intlayer/core/localization';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { IntlayerConfig, RoutingConfig } from '@intlayer/types/config';\nimport fg from 'fast-glob';\nimport { getPathHash } from '../utils';\nimport { writeFileIfChanged } from '../writeFileIfChanged';\n\nexport const getTypeName = (key: string): string =>\n `${kebabCaseToCamelCase(key)}Content`;\n\n/** Returns lines like: [Locales.FRENCH]: 1; */\nconst formatLocales = (locales: Locale[]) =>\n locales.map((locale) => ` \"${locale}\": 1;`).join('\\n');\n\n/**\n * Serializes the `url` rewrite rules as a type literal, so `getLocalizedPath`\n * and `getLocalizedUrl` can resolve a canonical path to its localized literal at\n * the type level. Returns an empty string when the project declares no rewrite —\n * the consuming types then localize every path to itself.\n *\n * Rules are emitted in their normalized form (`:param`, no locale prefix), which\n * is exactly what the runtime matches against.\n */\nexport const formatRewriteRules = (\n rewrite: RoutingConfig['rewrite']\n): string => {\n const rewriteRules = getRewriteRules(rewrite, 'url');\n\n if (!rewriteRules?.rules?.length) return '';\n\n const formattedRules = rewriteRules.rules\n .map(({ canonical, localized }) => {\n const formattedLocalized = Object.entries(localized)\n .filter(([, pattern]) => typeof pattern === 'string')\n .map(([locale, pattern]) => `'${locale}': '${pattern}'`)\n .join('; ');\n\n return `'${canonical}': { ${formattedLocalized} }`;\n })\n .join('; ');\n\n return `; rewrite: { ${formattedRules} }`;\n};\n\n/**\n * Serializes the `routing.domains` map as a type literal, so `getLocalizedUrl`\n * and `getLocalizedPath` can resolve the origin of a locale — and the\n * suppression of its path prefix — at the type level. Returns an empty string\n * when the project maps no locale to a domain.\n *\n * Each entry carries the two facts the consuming types need, both computed with\n * the very helpers the runtime uses:\n * - `origin`: the normalized origin ({@link getDomainOrigin}), prepended to the\n * localized path when the target locale lives on another domain.\n * - `exclusive`: whether the locale is the only one on that hostname\n * ({@link isLocaleExclusiveOnDomain}), in which case the domain identifies the\n * locale and no path prefix is emitted.\n */\nexport const formatDomains = (domains: RoutingConfig['domains']): string => {\n const domainEntries = Object.entries(domains ?? {}).filter(\n ([, domain]) => typeof domain === 'string' && domain.length > 0\n ) as [string, string][];\n\n if (!domainEntries.length) return '';\n\n const formattedDomains = domainEntries\n .map(\n ([locale, domain]) =>\n `'${locale}': { origin: '${getDomainOrigin(domain)}'; exclusive: ${isLocaleExclusiveOnDomain(locale, domains)} }`\n )\n .join('; ');\n\n return `; domains: { ${formattedDomains} }`;\n};\n\nconst zodToTsString = (schema: any): string => {\n if (!schema) return 'any';\n\n // Support both real Zod objects (_def) and serialized versions (def or nested)\n const def = schema._def ?? schema.def ?? schema;\n\n // Handle serialized type names (sometimes 'type' instead of 'typeName')\n const typeName = def.typeName ?? def.type;\n\n switch (typeName) {\n case 'ZodString':\n case 'string':\n return 'string';\n case 'ZodNumber':\n case 'number':\n return 'number';\n case 'ZodBoolean':\n case 'boolean':\n return 'boolean';\n case 'ZodNull':\n case 'null':\n return 'null';\n case 'ZodUndefined':\n case 'undefined':\n return 'undefined';\n case 'ZodArray':\n case 'array':\n return `${zodToTsString(def.type ?? def.element)}[]`;\n case 'ZodObject':\n case 'object': {\n const shape = typeof def.shape === 'function' ? def.shape() : def.shape;\n if (!shape) return 'Record<string, any>';\n\n const entries = Object.entries(shape)\n .map(([k, v]) => ` \"${k}\": ${zodToTsString(v)};`)\n .join('\\n');\n return `{\\n${entries}\\n }`;\n }\n case 'ZodOptional':\n case 'optional':\n return `${zodToTsString(def.innerType ?? def.wrapped)} | undefined`;\n case 'ZodNullable':\n case 'nullable':\n return `${zodToTsString(def.innerType ?? def.wrapped)} | null`;\n case 'ZodUnion':\n case 'union': {\n const options = def.options ?? [];\n return options.map(zodToTsString).join(' | ');\n }\n case 'ZodIntersection':\n case 'intersection':\n return `${zodToTsString(def.left)} & ${zodToTsString(def.right)}`;\n case 'ZodEnum':\n case 'enum': {\n const values = def.values ?? [];\n return values.map((v: string) => `\"${v}\"`).join(' | ');\n }\n case 'ZodLiteral':\n case 'literal': {\n const value = def.value;\n return typeof value === 'string' ? `\"${value}\"` : String(value);\n }\n default:\n return 'any';\n }\n};\n\ntype ZodToTsFns = {\n zodToTs: (schema: any, opts?: any) => { node: any };\n printNode: (node: any) => string;\n createAuxiliaryTypeStore: () => any;\n};\n\n/** Generate the content of the module augmentation file */\nconst generateTypeIndexContent = (\n typeFiles: string[],\n configuration: IntlayerConfig,\n zodToTsFns: ZodToTsFns | null\n): string => {\n const { internationalization, system, editor, routing } = configuration;\n const { moduleAugmentationDir } = system;\n const { enabled } = editor;\n const { locales, requiredLocales, strictMode, defaultLocale } =\n internationalization;\n\n let fileContent = 'import \"intlayer\";\\n';\n\n // Build dictionary refs\n const dictionariesRef = typeFiles.map((dictionaryPath) => ({\n relativePath: `./${normalizePath(relative(moduleAugmentationDir, dictionaryPath))}`,\n id: basename(dictionaryPath, extname(dictionaryPath)),\n hash: `_${getPathHash(dictionaryPath)}`,\n }));\n\n // Import all dictionaries\n for (const dictionary of dictionariesRef) {\n fileContent += `import ${dictionary.hash} from '${dictionary.relativePath}';\\n`;\n }\n fileContent += '\\n';\n\n // Dictionary map entries (id: typeof <hash>)\n const formattedDictionaryMap: string = dictionariesRef\n .map((dictionary) => ` \"${dictionary.id}\": typeof ${dictionary.hash};`)\n .join('\\n');\n\n // Ensure required ⊆ declared; if empty, default required = declared\n const declared = locales;\n const requiredSanitized = requiredLocales?.length\n ? requiredLocales.filter((requiredLocales) =>\n declared.includes(requiredLocales)\n )\n : declared;\n\n const formattedDeclaredLocales = formatLocales(declared);\n const formattedRequiredLocales = formatLocales(requiredSanitized);\n\n // Build schema registry\n const schemas = configuration.schemas ?? {};\n const formattedSchemas = Object.entries(schemas)\n .map(([key, schema]) => {\n let typeStr = 'any';\n\n if (schema) {\n try {\n if (zodToTsFns) {\n const { node } = zodToTsFns.zodToTs(schema, {\n auxiliaryTypeStore: zodToTsFns.createAuxiliaryTypeStore(),\n });\n // 133 is the kind for AnyKeyword in TypeScript\n if ((node as any).kind !== 133) {\n typeStr = zodToTsFns.printNode(node);\n } else {\n typeStr = zodToTsString(schema);\n }\n } else {\n typeStr = zodToTsString(schema);\n }\n } catch (_e) {\n // Fallback to custom string generator\n typeStr = zodToTsString(schema);\n }\n }\n return ` \"${key}\": ${typeStr};`;\n })\n .join('\\n');\n\n // Choose strict mode registry key\n const strictKey =\n strictMode === 'strict'\n ? 'strict'\n : strictMode === 'inclusive'\n ? 'inclusive'\n : 'loose';\n\n /**\n * Module augmentation that ONLY adds keys to registries.\n * No types/aliases redefined here—avoids merge conflicts.\n */\n fileContent += `declare module 'intlayer' {\\n`;\n // Dictionaries registry\n fileContent += ` interface __DictionaryRegistry {\\n${formattedDictionaryMap}\\n }\\n\\n`;\n // Locales registries\n fileContent += ` interface __DeclaredLocalesRegistry {\\n${formattedDeclaredLocales}\\n }\\n\\n`;\n fileContent += ` interface __RequiredLocalesRegistry {\\n${formattedRequiredLocales}\\n }\\n\\n`;\n // Schema registry\n fileContent += ` interface __SchemaRegistry {\\n${formattedSchemas}\\n }\\n\\n`;\n // Resolved strict mode (narrow the literal at build time)\n fileContent += ` interface __StrictModeRegistry { mode: '${strictKey}' }\\n\\n`;\n // Editor registry\n fileContent += ` interface __EditorRegistry { enabled : ${enabled} }\\n\\n`;\n // Routing registry (mode + defaultLocale narrowed to literals, rewrite rules\n // and domain map serialized as type literals)\n const routingMode = routing?.mode ?? 'prefix-no-default';\n const formattedRewriteRules = formatRewriteRules(routing?.rewrite);\n const formattedDomains = formatDomains(routing?.domains);\n fileContent += ` interface __RoutingRegistry { mode: '${routingMode}'; defaultLocale: '${defaultLocale}'${formattedRewriteRules}${formattedDomains} }\\n`;\n fileContent += `}\\n`;\n\n return fileContent;\n};\n\n/** Generate the index file merging all the types */\nexport const createModuleAugmentation = async (\n configuration: IntlayerConfig\n) => {\n const { moduleAugmentationDir, typesDir } = configuration.system;\n\n await mkdir(moduleAugmentationDir, { recursive: true });\n\n const dictionariesTypesDefinitions: string[] = await fg(\n normalizePath(`${typesDir}/*.ts`),\n { ignore: ['**/*.d.ts'] }\n );\n\n let zodToTsFns: ZodToTsFns | null = null;\n\n const hasSchemas = Object.keys(configuration.schemas ?? {}).length > 0;\n\n // zod-to-ts drags in the whole `typescript` package (~300ms import), so\n // only load it when there are schemas to convert.\n if (hasSchemas) {\n try {\n const mod = await import('zod-to-ts');\n zodToTsFns = {\n zodToTs: mod.zodToTs,\n printNode: mod.printNode,\n createAuxiliaryTypeStore: mod.createAuxiliaryTypeStore,\n };\n } catch {\n // typescript peer dep not installed (plain JS project), use fallback\n }\n }\n\n const tsContent = generateTypeIndexContent(\n dictionariesTypesDefinitions,\n configuration,\n zodToTsFns\n );\n\n const tsFilePath = join(moduleAugmentationDir, 'intlayer.d.ts');\n await writeFileIfChanged(tsFilePath, tsContent);\n};\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { createModuleAugmentation, formatRewriteRules, getTypeName } from "./createModuleAugmentation.mjs";
1
+ import { createModuleAugmentation, formatDomains, formatRewriteRules, getTypeName } from "./createModuleAugmentation.mjs";
2
2
  import { createTypes, generateTypeScriptType } from "./createType.mjs";
3
3
 
4
- export { createModuleAugmentation, createTypes, formatRewriteRules, generateTypeScriptType, getTypeName };
4
+ export { createModuleAugmentation, createTypes, formatDomains, formatRewriteRules, generateTypeScriptType, getTypeName };
@@ -38,6 +38,12 @@ const fingerprintSectionFineBlocks = (section) => {
38
38
  * whole section. Within a changed section a target block with no base
39
39
  * counterpart is **kept as-is** (reused) rather than deleted, so a translation
40
40
  * that has extra paragraphs never loses content.
41
+ * When `changedLines` is `undefined` the changed lines are simply unknown, so
42
+ * **every** section is inspected instead of none: aligned blocks are still
43
+ * reused (there is no way to tell which translation went stale), but blocks
44
+ * living on one side only are reported as `insert_new` / `delete`. This is
45
+ * what makes a plain "compare this document with its translation" run — one
46
+ * with no git history behind it — report anything at all.
41
47
  *
42
48
  * Section-level insertions and deletions stay whole: a brand-new section is
43
49
  * translated as one unit, and a target section with no base counterpart is
@@ -48,11 +54,20 @@ const fingerprintSectionFineBlocks = (section) => {
48
54
  * @returns The (flattened) blocks, the plan, and the segments that need translation.
49
55
  */
50
56
  const buildAlignmentPlan = ({ baseText, targetText, changedLines }) => {
51
- const changedLineNumbers = Array.isArray(changedLines) ? changedLines : [];
57
+ const hasChangedLineInformation = Array.isArray(changedLines);
58
+ const changedLineNumbers = changedLines ?? [];
52
59
  const baseSections = fingerprintBlockList(segmentSections(baseText));
53
60
  const targetSections = fingerprintBlockList(segmentSections(targetText));
54
61
  const sectionAlignment = alignBaseAndTargetBlocks(baseSections, targetSections);
55
62
  const changedSectionIndexes = mapChangedLinesToBlocks(baseSections, changedLineNumbers);
63
+ /**
64
+ * Whether a section must be opened and inspected block by block.
65
+ *
66
+ * Without changed-line information every section is inspected, so comparing a
67
+ * document with its translation from scratch still reports the blocks that
68
+ * exist on one side only. With changed lines, only the touched sections are.
69
+ */
70
+ const isSectionToInspect = (sectionIndex) => !hasChangedLineInformation || changedSectionIndexes.has(sectionIndex);
56
71
  const baseBlocks = [];
57
72
  const targetBlocks = [];
58
73
  const actions = [];
@@ -78,7 +93,7 @@ const buildAlignmentPlan = ({ baseText, targetText, changedLines }) => {
78
93
  if (pair.baseIndex < 0 || pair.targetIndex === null) continue;
79
94
  const baseSection = baseSections[pair.baseIndex];
80
95
  const targetSection = targetSections[pair.targetIndex];
81
- if (!changedSectionIndexes.has(pair.baseIndex)) {
96
+ if (!isSectionToInspect(pair.baseIndex)) {
82
97
  const baseIndex = pushBaseBlock(baseSection);
83
98
  const targetIndex = pushTargetBlock(targetSection);
84
99
  actions.push({
@@ -95,10 +110,13 @@ const buildAlignmentPlan = ({ baseText, targetText, changedLines }) => {
95
110
  for (const finePair of fineAlignment) {
96
111
  if (finePair.baseIndex === -1 && finePair.targetIndex !== null) {
97
112
  const targetIndex = pushTargetBlock(targetFineBlocks[finePair.targetIndex]);
98
- actions.push({
113
+ actions.push(hasChangedLineInformation ? {
99
114
  kind: "reuse",
100
115
  baseIndex: -1,
101
116
  targetIndex
117
+ } : {
118
+ kind: "delete",
119
+ targetIndex
102
120
  });
103
121
  continue;
104
122
  }
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline.mjs","names":[],"sources":["../../../src/docReview/pipeline.ts"],"sourcesContent":["import { alignBaseAndTargetBlocks } from './alignBlocks';\nimport { fingerprintBlock } from './fingerprintBlock';\nimport { mapChangedLinesToBlocks } from './mapChangedLinesToBlocks';\nimport { normalizeBlock } from './normalizeBlock';\nimport {\n identifySegmentsToReview,\n mergeReviewedSegments,\n type SegmentToReview,\n} from './rebuildDocument';\nimport { segmentDocument, segmentSections } from './segmentDocument';\nimport type { Block, FingerprintedBlock, PlannedAction } from './types';\n\nexport type BuildAlignmentPlanInput = {\n /** The base (source) document, used as the translation reference. */\n baseText: string;\n /** The existing target (translated) document, possibly empty. */\n targetText: string;\n /** 1-based line numbers that changed in the base document, when known. */\n changedLines: number[] | undefined;\n};\n\nexport type BuildAlignmentPlanOutput = {\n baseBlocks: FingerprintedBlock[];\n targetBlocks: FingerprintedBlock[];\n plan: AlignmentPlan;\n segmentsToReview: SegmentToReview[];\n};\n\n/** Fingerprint a list of already-segmented blocks (context = neighbours). */\nconst fingerprintBlockList = (blocks: Block[]): FingerprintedBlock[] => {\n const normalized = blocks.map(normalizeBlock);\n\n return normalized.map((block, index, array) =>\n fingerprintBlock(block, array[index - 1] ?? null, array[index + 1] ?? null)\n );\n};\n\n/**\n * Re-segment a section's own text into fine blocks, shifting their relative line\n * numbers back to absolute document positions so changed-line mapping and the\n * review report keep reporting real file coordinates.\n */\nconst fingerprintSectionFineBlocks = (section: Block): FingerprintedBlock[] => {\n const lineOffset = section.lineStart - 1;\n\n const fineBlocks = segmentDocument(section.content).map(\n (block): Block => ({\n ...block,\n lineStart: block.lineStart + lineOffset,\n lineEnd: block.lineEnd + lineOffset,\n })\n );\n\n return fingerprintBlockList(fineBlocks);\n};\n\n/**\n * Build the block-aware alignment plan between a base document and its\n * translation, in two levels.\n *\n * 1. **Sections** (heading-anchored) are aligned first. Because a document and\n * its translation share the same heading structure, this alignment is robust\n * and never drops a section just because the prose was split into a different\n * number of paragraphs.\n * 2. Only the sections **touched by a changed line** are then re-segmented into\n * fine blocks (paragraphs, code fences) and aligned within the section, so a\n * small edit re-translates only the affected paragraph(s) instead of the\n * whole section. Within a changed section a target block with no base\n * counterpart is **kept as-is** (reused) rather than deleted, so a translation\n * that has extra paragraphs never loses content.\n *\n * Section-level insertions and deletions stay whole: a brand-new section is\n * translated as one unit, and a target section with no base counterpart is\n * reported as `delete` for visibility but never dropped by the merge (see\n * {@link mergeReviewedSegments}), so a review can never lose translated content.\n *\n * @param input - The base/target texts and optional changed lines.\n * @returns The (flattened) blocks, the plan, and the segments that need translation.\n */\nexport const buildAlignmentPlan = ({\n baseText,\n targetText,\n changedLines,\n}: BuildAlignmentPlanInput): BuildAlignmentPlanOutput => {\n const changedLineNumbers = Array.isArray(changedLines) ? changedLines : [];\n\n const baseSections = fingerprintBlockList(segmentSections(baseText));\n const targetSections = fingerprintBlockList(segmentSections(targetText));\n\n const sectionAlignment = alignBaseAndTargetBlocks(\n baseSections,\n targetSections\n );\n const changedSectionIndexes = mapChangedLinesToBlocks(\n baseSections,\n changedLineNumbers\n );\n\n // Flattened blocks the plan refers to by index. Reused/deleted/inserted\n // sections contribute their whole-section block; changed sections contribute\n // their fine sub-blocks.\n const baseBlocks: FingerprintedBlock[] = [];\n const targetBlocks: FingerprintedBlock[] = [];\n const actions: PlannedAction[] = [];\n\n const pushBaseBlock = (block: FingerprintedBlock): number =>\n baseBlocks.push(block) - 1;\n const pushTargetBlock = (block: FingerprintedBlock): number =>\n targetBlocks.push(block) - 1;\n\n for (const pair of sectionAlignment) {\n // Section present only in the target → reported as `delete` for visibility,\n // but kept verbatim by the merge (the aligner may simply have failed to\n // follow a reordered section), never silently dropped.\n if (pair.baseIndex === -1 && pair.targetIndex !== null) {\n const targetIndex = pushTargetBlock(targetSections[pair.targetIndex]!);\n actions.push({ kind: 'delete', targetIndex });\n continue;\n }\n\n // Section present only in the base → brand new, translate as one unit.\n if (pair.baseIndex >= 0 && pair.targetIndex === null) {\n const baseIndex = pushBaseBlock(baseSections[pair.baseIndex]!);\n actions.push({ kind: 'insert_new', baseIndex });\n continue;\n }\n\n if (pair.baseIndex < 0 || pair.targetIndex === null) continue;\n\n const baseSection = baseSections[pair.baseIndex]!;\n const targetSection = targetSections[pair.targetIndex]!;\n\n // Unchanged section → reuse the existing translation verbatim.\n if (!changedSectionIndexes.has(pair.baseIndex)) {\n const baseIndex = pushBaseBlock(baseSection);\n const targetIndex = pushTargetBlock(targetSection);\n actions.push({ kind: 'reuse', baseIndex, targetIndex });\n continue;\n }\n\n // Changed section → align its fine blocks and review only what changed.\n const baseFineBlocks = fingerprintSectionFineBlocks(baseSection);\n const targetFineBlocks = fingerprintSectionFineBlocks(targetSection);\n const fineAlignment = alignBaseAndTargetBlocks(\n baseFineBlocks,\n targetFineBlocks\n );\n const changedFineIndexes = mapChangedLinesToBlocks(\n baseFineBlocks,\n changedLineNumbers\n );\n\n for (const finePair of fineAlignment) {\n // Target-only fine block: keep it (no data loss), do not delete.\n if (finePair.baseIndex === -1 && finePair.targetIndex !== null) {\n const targetIndex = pushTargetBlock(\n targetFineBlocks[finePair.targetIndex]!\n );\n actions.push({ kind: 'reuse', baseIndex: -1, targetIndex });\n continue;\n }\n\n // Base-only fine block: a new paragraph inside the section, translate it.\n if (finePair.baseIndex >= 0 && finePair.targetIndex === null) {\n const baseIndex = pushBaseBlock(baseFineBlocks[finePair.baseIndex]!);\n actions.push({ kind: 'insert_new', baseIndex });\n continue;\n }\n\n if (finePair.baseIndex < 0 || finePair.targetIndex === null) continue;\n\n const baseIndex = pushBaseBlock(baseFineBlocks[finePair.baseIndex]!);\n const targetIndex = pushTargetBlock(\n targetFineBlocks[finePair.targetIndex]!\n );\n\n actions.push(\n changedFineIndexes.has(finePair.baseIndex)\n ? { kind: 'review', baseIndex, targetIndex }\n : { kind: 'reuse', baseIndex, targetIndex }\n );\n }\n }\n\n const plan = { actions };\n\n const { segmentsToReview } = identifySegmentsToReview({\n baseBlocks,\n targetBlocks,\n plan,\n });\n\n return { baseBlocks, targetBlocks, plan, segmentsToReview };\n};\n\nexport type { SegmentToReview };\nexport { mergeReviewedSegments };\n"],"mappings":"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"}
1
+ {"version":3,"file":"pipeline.mjs","names":[],"sources":["../../../src/docReview/pipeline.ts"],"sourcesContent":["import { alignBaseAndTargetBlocks } from './alignBlocks';\nimport { fingerprintBlock } from './fingerprintBlock';\nimport { mapChangedLinesToBlocks } from './mapChangedLinesToBlocks';\nimport { normalizeBlock } from './normalizeBlock';\nimport {\n identifySegmentsToReview,\n mergeReviewedSegments,\n type SegmentToReview,\n} from './rebuildDocument';\nimport { segmentDocument, segmentSections } from './segmentDocument';\nimport type {\n AlignmentPlan,\n Block,\n FingerprintedBlock,\n PlannedAction,\n} from './types';\n\nexport type BuildAlignmentPlanInput = {\n /** The base (source) document, used as the translation reference. */\n baseText: string;\n /** The existing target (translated) document, possibly empty. */\n targetText: string;\n /**\n * 1-based line numbers that changed in the base document.\n *\n * `undefined` means the changed lines are unknown (no git history available):\n * every section is then compared block by block. An empty array means nothing\n * changed, and every aligned block is reused.\n */\n changedLines: number[] | undefined;\n};\n\nexport type BuildAlignmentPlanOutput = {\n baseBlocks: FingerprintedBlock[];\n targetBlocks: FingerprintedBlock[];\n plan: AlignmentPlan;\n segmentsToReview: SegmentToReview[];\n};\n\n/** Fingerprint a list of already-segmented blocks (context = neighbours). */\nconst fingerprintBlockList = (blocks: Block[]): FingerprintedBlock[] => {\n const normalized = blocks.map(normalizeBlock);\n\n return normalized.map((block, index, array) =>\n fingerprintBlock(block, array[index - 1] ?? null, array[index + 1] ?? null)\n );\n};\n\n/**\n * Re-segment a section's own text into fine blocks, shifting their relative line\n * numbers back to absolute document positions so changed-line mapping and the\n * review report keep reporting real file coordinates.\n */\nconst fingerprintSectionFineBlocks = (section: Block): FingerprintedBlock[] => {\n const lineOffset = section.lineStart - 1;\n\n const fineBlocks = segmentDocument(section.content).map(\n (block): Block => ({\n ...block,\n lineStart: block.lineStart + lineOffset,\n lineEnd: block.lineEnd + lineOffset,\n })\n );\n\n return fingerprintBlockList(fineBlocks);\n};\n\n/**\n * Build the block-aware alignment plan between a base document and its\n * translation, in two levels.\n *\n * 1. **Sections** (heading-anchored) are aligned first. Because a document and\n * its translation share the same heading structure, this alignment is robust\n * and never drops a section just because the prose was split into a different\n * number of paragraphs.\n * 2. Only the sections **touched by a changed line** are then re-segmented into\n * fine blocks (paragraphs, code fences) and aligned within the section, so a\n * small edit re-translates only the affected paragraph(s) instead of the\n * whole section. Within a changed section a target block with no base\n * counterpart is **kept as-is** (reused) rather than deleted, so a translation\n * that has extra paragraphs never loses content.\n * When `changedLines` is `undefined` the changed lines are simply unknown, so\n * **every** section is inspected instead of none: aligned blocks are still\n * reused (there is no way to tell which translation went stale), but blocks\n * living on one side only are reported as `insert_new` / `delete`. This is\n * what makes a plain \"compare this document with its translation\" run — one\n * with no git history behind it — report anything at all.\n *\n * Section-level insertions and deletions stay whole: a brand-new section is\n * translated as one unit, and a target section with no base counterpart is\n * reported as `delete` for visibility but never dropped by the merge (see\n * {@link mergeReviewedSegments}), so a review can never lose translated content.\n *\n * @param input - The base/target texts and optional changed lines.\n * @returns The (flattened) blocks, the plan, and the segments that need translation.\n */\nexport const buildAlignmentPlan = ({\n baseText,\n targetText,\n changedLines,\n}: BuildAlignmentPlanInput): BuildAlignmentPlanOutput => {\n // `undefined` means \"which lines changed is unknown\" (no git history to read),\n // which is not the same as \"no line changed\": the former must still compare the\n // two documents, the latter must reuse everything.\n const hasChangedLineInformation = Array.isArray(changedLines);\n const changedLineNumbers = changedLines ?? [];\n\n const baseSections = fingerprintBlockList(segmentSections(baseText));\n const targetSections = fingerprintBlockList(segmentSections(targetText));\n\n const sectionAlignment = alignBaseAndTargetBlocks(\n baseSections,\n targetSections\n );\n const changedSectionIndexes = mapChangedLinesToBlocks(\n baseSections,\n changedLineNumbers\n );\n\n /**\n * Whether a section must be opened and inspected block by block.\n *\n * Without changed-line information every section is inspected, so comparing a\n * document with its translation from scratch still reports the blocks that\n * exist on one side only. With changed lines, only the touched sections are.\n */\n const isSectionToInspect = (sectionIndex: number): boolean =>\n !hasChangedLineInformation || changedSectionIndexes.has(sectionIndex);\n\n // Flattened blocks the plan refers to by index. Reused/deleted/inserted\n // sections contribute their whole-section block; changed sections contribute\n // their fine sub-blocks.\n const baseBlocks: FingerprintedBlock[] = [];\n const targetBlocks: FingerprintedBlock[] = [];\n const actions: PlannedAction[] = [];\n\n const pushBaseBlock = (block: FingerprintedBlock): number =>\n baseBlocks.push(block) - 1;\n const pushTargetBlock = (block: FingerprintedBlock): number =>\n targetBlocks.push(block) - 1;\n\n for (const pair of sectionAlignment) {\n // Section present only in the target → reported as `delete` for visibility,\n // but kept verbatim by the merge (the aligner may simply have failed to\n // follow a reordered section), never silently dropped.\n if (pair.baseIndex === -1 && pair.targetIndex !== null) {\n const targetIndex = pushTargetBlock(targetSections[pair.targetIndex]!);\n actions.push({ kind: 'delete', targetIndex });\n continue;\n }\n\n // Section present only in the base → brand new, translate as one unit.\n if (pair.baseIndex >= 0 && pair.targetIndex === null) {\n const baseIndex = pushBaseBlock(baseSections[pair.baseIndex]!);\n actions.push({ kind: 'insert_new', baseIndex });\n continue;\n }\n\n if (pair.baseIndex < 0 || pair.targetIndex === null) continue;\n\n const baseSection = baseSections[pair.baseIndex]!;\n const targetSection = targetSections[pair.targetIndex]!;\n\n // Unchanged section → reuse the existing translation verbatim.\n if (!isSectionToInspect(pair.baseIndex)) {\n const baseIndex = pushBaseBlock(baseSection);\n const targetIndex = pushTargetBlock(targetSection);\n actions.push({ kind: 'reuse', baseIndex, targetIndex });\n continue;\n }\n\n // Inspected section → align its fine blocks and review only what changed.\n const baseFineBlocks = fingerprintSectionFineBlocks(baseSection);\n const targetFineBlocks = fingerprintSectionFineBlocks(targetSection);\n const fineAlignment = alignBaseAndTargetBlocks(\n baseFineBlocks,\n targetFineBlocks\n );\n const changedFineIndexes = mapChangedLinesToBlocks(\n baseFineBlocks,\n changedLineNumbers\n );\n\n for (const finePair of fineAlignment) {\n // Target-only fine block. Its content is kept either way — `delete` is\n // reported for visibility only and the merge keeps it verbatim (see\n // {@link mergeReviewedSegments}).\n // With changed lines the author's edit is known precisely, so an unmatched\n // target block is most likely the translator's own prose split and stays\n // silent. Without them the whole document is being compared, and such a\n // block is exactly the divergence the report exists to surface.\n if (finePair.baseIndex === -1 && finePair.targetIndex !== null) {\n const targetIndex = pushTargetBlock(\n targetFineBlocks[finePair.targetIndex]!\n );\n actions.push(\n hasChangedLineInformation\n ? { kind: 'reuse', baseIndex: -1, targetIndex }\n : { kind: 'delete', targetIndex }\n );\n continue;\n }\n\n // Base-only fine block: a new paragraph inside the section, translate it.\n if (finePair.baseIndex >= 0 && finePair.targetIndex === null) {\n const baseIndex = pushBaseBlock(baseFineBlocks[finePair.baseIndex]!);\n actions.push({ kind: 'insert_new', baseIndex });\n continue;\n }\n\n if (finePair.baseIndex < 0 || finePair.targetIndex === null) continue;\n\n const baseIndex = pushBaseBlock(baseFineBlocks[finePair.baseIndex]!);\n const targetIndex = pushTargetBlock(\n targetFineBlocks[finePair.targetIndex]!\n );\n\n actions.push(\n changedFineIndexes.has(finePair.baseIndex)\n ? { kind: 'review', baseIndex, targetIndex }\n : { kind: 'reuse', baseIndex, targetIndex }\n );\n }\n }\n\n const plan = { actions };\n\n const { segmentsToReview } = identifySegmentsToReview({\n baseBlocks,\n targetBlocks,\n plan,\n });\n\n return { baseBlocks, targetBlocks, plan, segmentsToReview };\n};\n\nexport type { SegmentToReview };\nexport { mergeReviewedSegments };\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"reviewReport.mjs","names":[],"sources":["../../../src/docReview/reviewReport.ts"],"sourcesContent":["import * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, colorizeNumber } from '@intlayer/config/logger';\nimport { buildAlignmentPlan } from './pipeline';\n\n/**\n * The kind of change detected for a block when reviewing a translation against\n * its base document.\n *\n * - `review`: the base block changed and its translation must be updated.\n * - `insert_new`: the base block has no translation yet and must be added.\n * - `delete`: the target block no longer exists in the base and should be removed.\n */\nexport type ReviewBlockAction = 'review' | 'insert_new' | 'delete';\n\n/** A 1-based, inclusive line range within a document. */\nexport type LineRange = {\n start: number;\n end: number;\n};\n\n/**\n * A single block that diverges between the base document and its translation.\n *\n * This is the unit an external translator (AI client, human, or agent) needs to\n * act on: it carries the base content to translate, the existing translation to\n * update, and where each lives so the change can be located in the file.\n */\nexport type ReviewReportBlock = {\n /** What should happen to this block. */\n action: ReviewBlockAction;\n /** Line range of the block in the base document (omitted for pure deletions). */\n baseLineRange?: LineRange;\n /** Line range of the block in the target document (omitted for new insertions). */\n targetLineRange?: LineRange;\n /** Raw markdown of the base block (omitted for pure deletions). */\n baseContent?: string;\n /** Existing translation of the block (omitted for new insertions). */\n targetContent?: string;\n};\n\nexport type ReviewReportSummary = {\n /** Number of blocks reused as-is (unchanged translation). */\n reuse: number;\n /** Number of blocks whose translation must be updated. */\n review: number;\n /** Number of blocks missing a translation. */\n insertNew: number;\n /** Number of stale target blocks to delete. */\n delete: number;\n};\n\nexport type ReviewReport = {\n /** Only the blocks that diverge (review, insert_new, delete). */\n blocks: ReviewReportBlock[];\n /** Counts per action over the whole document, including reused blocks. */\n summary: ReviewReportSummary;\n};\n\nexport type BuildReviewReportInput = {\n /** The base (source) document, used as the translation reference. */\n baseText: string;\n /** The existing target (translated) document, possibly empty. */\n targetText: string;\n /**\n * 1-based line numbers that changed in the base document. When omitted, only\n * inserted and deleted blocks are reported (no aligned block is flagged for\n * review since there is no way to know which ones changed).\n */\n changedLines?: number[];\n};\n\n/**\n * Compare a base markdown document with its translation and report only the\n * blocks that need attention, with their line ranges and content.\n *\n * Reusable across the CLI (`doc review --log`), the backend (comparing two\n * translation contents stored in database), and agents that generate the\n * missing translations.\n *\n * @param input - The base/target texts and optional changed lines.\n * @returns The divergent blocks and a per-action summary.\n */\nexport const buildReviewReport = ({\n baseText,\n targetText,\n changedLines,\n}: BuildReviewReportInput): ReviewReport => {\n const { baseBlocks, targetBlocks, plan } = buildAlignmentPlan({\n baseText,\n targetText,\n changedLines,\n });\n\n const summary: ReviewReportSummary = {\n reuse: 0,\n review: 0,\n insertNew: 0,\n delete: 0,\n };\n\n const blocks: ReviewReportBlock[] = [];\n\n for (const [actionIndex, action] of plan.actions.entries()) {\n if (action.kind === 'reuse') {\n summary.reuse += 1;\n continue;\n }\n\n if (action.kind === 'review') {\n summary.review += 1;\n const baseBlock = baseBlocks[action.baseIndex];\n\n if (!baseBlock) continue;\n\n const targetBlock =\n action.targetIndex !== null ? targetBlocks[action.targetIndex] : null;\n\n blocks.push({\n action: 'review',\n baseLineRange: { start: baseBlock.lineStart, end: baseBlock.lineEnd },\n targetLineRange: targetBlock\n ? { start: targetBlock.lineStart, end: targetBlock.lineEnd }\n : undefined,\n baseContent: baseBlock.content,\n targetContent: targetBlock?.content,\n });\n continue;\n }\n\n if (action.kind === 'insert_new') {\n summary.insertNew += 1;\n const baseBlock = baseBlocks[action.baseIndex];\n\n if (!baseBlock) continue;\n\n // Find the target line after which the new block should be inserted:\n // scan backwards for the last action that produced a target block.\n let insertAfterLine: number | undefined;\n for (let prevIndex = actionIndex - 1; prevIndex >= 0; prevIndex--) {\n const prevAction = plan.actions[prevIndex];\n if (!prevAction) continue;\n\n let targetIdx: number | null = null;\n if (prevAction.kind === 'reuse') targetIdx = prevAction.targetIndex;\n else if (\n prevAction.kind === 'review' &&\n prevAction.targetIndex !== null\n )\n targetIdx = prevAction.targetIndex;\n else if (prevAction.kind === 'delete')\n targetIdx = prevAction.targetIndex;\n\n if (targetIdx !== null) {\n const prevTargetBlock = targetBlocks[targetIdx];\n if (prevTargetBlock) {\n insertAfterLine = prevTargetBlock.lineEnd + 1;\n break;\n }\n }\n }\n\n blocks.push({\n action: 'insert_new',\n baseLineRange: { start: baseBlock.lineStart, end: baseBlock.lineEnd },\n targetLineRange:\n insertAfterLine !== undefined\n ? { start: insertAfterLine, end: insertAfterLine }\n : undefined,\n baseContent: baseBlock.content,\n });\n continue;\n }\n\n if (action.kind === 'delete') {\n summary.delete += 1;\n const targetBlock = targetBlocks[action.targetIndex];\n\n if (!targetBlock) continue;\n\n blocks.push({\n action: 'delete',\n targetLineRange: {\n start: targetBlock.lineStart,\n end: targetBlock.lineEnd,\n },\n targetContent: targetBlock.content,\n });\n }\n }\n\n return { blocks, summary };\n};\n\n/** Render a line range as a blue colored string, or an orange dash when absent. */\nconst colorizeLineRange = (range?: LineRange): string => {\n if (!range) return colorize('—', ANSIColors.ORANGE);\n const rangeStr =\n range.start === range.end\n ? `L${range.start}`\n : `L${range.start}-${range.end}`;\n return colorize(rangeStr, ANSIColors.BLUE);\n};\n\n/**\n * Colorize a count with green when zero (nothing to do) and a caller-supplied\n * non-zero color (orange for warnings, red for destructive actions).\n */\nconst colorizeCount = (\n count: number,\n nonZeroColor: (typeof ANSIColors)[keyof typeof ANSIColors]\n): string =>\n colorizeNumber(count, {\n zero: ANSIColors.GREEN,\n one: nonZeroColor,\n two: nonZeroColor,\n few: nonZeroColor,\n many: nonZeroColor,\n other: nonZeroColor,\n });\n\n/**\n * Render a {@link ReviewReport} as a human and agent readable log.\n *\n * Each divergent block is printed with its action, the base/target line ranges,\n * the base content to translate, and the existing translation to update.\n *\n * @param report - The report to format.\n * @param options - Optional labels for the base and target locales.\n * @returns A multi-line string describing every block that needs attention.\n */\nexport const formatReviewReport = (\n report: ReviewReport,\n options?: { baseLabel?: string; targetLabel?: string }\n): string => {\n const baseLabel = options?.baseLabel ?? 'base';\n const targetLabel = options?.targetLabel ?? 'target';\n\n const { summary, blocks } = report;\n\n // Build the summary header piece-by-piece so each count uses its own color.\n const header = [\n colorize('Review report: ', ANSIColors.ORANGE),\n colorizeCount(blocks.length, ANSIColors.ORANGE),\n colorize(' block(s) need attention (review=', ANSIColors.ORANGE),\n colorizeCount(summary.review, ANSIColors.ORANGE),\n colorize(', new=', ANSIColors.ORANGE),\n colorizeCount(summary.insertNew, ANSIColors.ORANGE),\n colorize(', delete=', ANSIColors.ORANGE),\n colorizeCount(summary.delete, ANSIColors.RED),\n colorize(', reuse=', ANSIColors.ORANGE),\n colorizeNumber(summary.reuse),\n colorize(').', ANSIColors.ORANGE),\n ].join('');\n\n if (blocks.length === 0) {\n return `${header}\\n${colorize('No changes needed.', ANSIColors.GREEN)}`;\n }\n\n const sections = blocks.map((block, index) => {\n const lines: string[] = [];\n\n // Block header: each token colored individually.\n const blockHeader = [\n colorize('--- Block ', ANSIColors.ORANGE),\n colorizeNumber(index + 1),\n colorize('/', ANSIColors.ORANGE),\n colorizeNumber(blocks.length),\n colorize(` [${block.action}] `, ANSIColors.ORANGE),\n baseLabel,\n colorize(' ', ANSIColors.ORANGE),\n colorizeLineRange(block.baseLineRange),\n colorize(' → ', ANSIColors.ORANGE),\n targetLabel,\n colorize(' ', ANSIColors.ORANGE),\n colorizeLineRange(block.targetLineRange),\n colorize(' ---', ANSIColors.ORANGE),\n ].join('');\n\n lines.push(blockHeader);\n\n if (block.baseContent !== undefined) {\n lines.push(colorize(`[${baseLabel}]`, ANSIColors.BEIGE));\n lines.push(colorize(block.baseContent.trimEnd(), ANSIColors.GREY));\n }\n\n if (block.targetContent !== undefined) {\n lines.push(colorize(`[${targetLabel}]`, ANSIColors.BEIGE));\n lines.push(colorize(block.targetContent.trimEnd(), ANSIColors.GREY));\n } else if (block.action === 'insert_new') {\n lines.push(colorize(`[${targetLabel}]`, ANSIColors.BEIGE));\n lines.push(colorize('(missing — to be translated)', ANSIColors.ORANGE));\n }\n\n return lines.join('\\n');\n });\n\n const editingNote = colorize(\n 'Tip: start editing from the last block — working bottom-up keeps earlier line numbers accurate.',\n ANSIColors.GREY\n );\n\n return [header, '', ...sections, '', editingNote].join('\\n');\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"reviewReport.mjs","names":[],"sources":["../../../src/docReview/reviewReport.ts"],"sourcesContent":["import * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, colorizeNumber } from '@intlayer/config/logger';\nimport { buildAlignmentPlan } from './pipeline';\n\n/**\n * The kind of change detected for a block when reviewing a translation against\n * its base document.\n *\n * - `review`: the base block changed and its translation must be updated.\n * - `insert_new`: the base block has no translation yet and must be added.\n * - `delete`: the target block no longer exists in the base and should be removed.\n */\nexport type ReviewBlockAction = 'review' | 'insert_new' | 'delete';\n\n/** A 1-based, inclusive line range within a document. */\nexport type LineRange = {\n start: number;\n end: number;\n};\n\n/**\n * A single block that diverges between the base document and its translation.\n *\n * This is the unit an external translator (AI client, human, or agent) needs to\n * act on: it carries the base content to translate, the existing translation to\n * update, and where each lives so the change can be located in the file.\n */\nexport type ReviewReportBlock = {\n /** What should happen to this block. */\n action: ReviewBlockAction;\n /** Line range of the block in the base document (omitted for pure deletions). */\n baseLineRange?: LineRange;\n /** Line range of the block in the target document (omitted for new insertions). */\n targetLineRange?: LineRange;\n /** Raw markdown of the base block (omitted for pure deletions). */\n baseContent?: string;\n /** Existing translation of the block (omitted for new insertions). */\n targetContent?: string;\n};\n\nexport type ReviewReportSummary = {\n /** Number of blocks reused as-is (unchanged translation). */\n reuse: number;\n /** Number of blocks whose translation must be updated. */\n review: number;\n /** Number of blocks missing a translation. */\n insertNew: number;\n /** Number of stale target blocks to delete. */\n delete: number;\n};\n\nexport type ReviewReport = {\n /** Only the blocks that diverge (review, insert_new, delete). */\n blocks: ReviewReportBlock[];\n /** Counts per action over the whole document, including reused blocks. */\n summary: ReviewReportSummary;\n};\n\nexport type BuildReviewReportInput = {\n /** The base (source) document, used as the translation reference. */\n baseText: string;\n /** The existing target (translated) document, possibly empty. */\n targetText: string;\n /**\n * 1-based line numbers that changed in the base document.\n *\n * When omitted the whole document is compared, and only inserted and deleted\n * blocks are reported (no aligned block is flagged for review since there is\n * no way to know which ones changed). An empty array means nothing changed,\n * and no block at all is reported.\n */\n changedLines?: number[];\n};\n\n/**\n * Compare a base markdown document with its translation and report only the\n * blocks that need attention, with their line ranges and content.\n *\n * Reusable across the CLI (`doc review --log`), the backend (comparing two\n * translation contents stored in database), and agents that generate the\n * missing translations.\n *\n * @param input - The base/target texts and optional changed lines.\n * @returns The divergent blocks and a per-action summary.\n */\nexport const buildReviewReport = ({\n baseText,\n targetText,\n changedLines,\n}: BuildReviewReportInput): ReviewReport => {\n const { baseBlocks, targetBlocks, plan } = buildAlignmentPlan({\n baseText,\n targetText,\n changedLines,\n });\n\n const summary: ReviewReportSummary = {\n reuse: 0,\n review: 0,\n insertNew: 0,\n delete: 0,\n };\n\n const blocks: ReviewReportBlock[] = [];\n\n for (const [actionIndex, action] of plan.actions.entries()) {\n if (action.kind === 'reuse') {\n summary.reuse += 1;\n continue;\n }\n\n if (action.kind === 'review') {\n summary.review += 1;\n const baseBlock = baseBlocks[action.baseIndex];\n\n if (!baseBlock) continue;\n\n const targetBlock =\n action.targetIndex !== null ? targetBlocks[action.targetIndex] : null;\n\n blocks.push({\n action: 'review',\n baseLineRange: { start: baseBlock.lineStart, end: baseBlock.lineEnd },\n targetLineRange: targetBlock\n ? { start: targetBlock.lineStart, end: targetBlock.lineEnd }\n : undefined,\n baseContent: baseBlock.content,\n targetContent: targetBlock?.content,\n });\n continue;\n }\n\n if (action.kind === 'insert_new') {\n summary.insertNew += 1;\n const baseBlock = baseBlocks[action.baseIndex];\n\n if (!baseBlock) continue;\n\n // Find the target line after which the new block should be inserted:\n // scan backwards for the last action that produced a target block.\n let insertAfterLine: number | undefined;\n for (let prevIndex = actionIndex - 1; prevIndex >= 0; prevIndex--) {\n const prevAction = plan.actions[prevIndex];\n if (!prevAction) continue;\n\n let targetIdx: number | null = null;\n if (prevAction.kind === 'reuse') targetIdx = prevAction.targetIndex;\n else if (\n prevAction.kind === 'review' &&\n prevAction.targetIndex !== null\n )\n targetIdx = prevAction.targetIndex;\n else if (prevAction.kind === 'delete')\n targetIdx = prevAction.targetIndex;\n\n if (targetIdx !== null) {\n const prevTargetBlock = targetBlocks[targetIdx];\n if (prevTargetBlock) {\n insertAfterLine = prevTargetBlock.lineEnd + 1;\n break;\n }\n }\n }\n\n blocks.push({\n action: 'insert_new',\n baseLineRange: { start: baseBlock.lineStart, end: baseBlock.lineEnd },\n targetLineRange:\n insertAfterLine !== undefined\n ? { start: insertAfterLine, end: insertAfterLine }\n : undefined,\n baseContent: baseBlock.content,\n });\n continue;\n }\n\n if (action.kind === 'delete') {\n summary.delete += 1;\n const targetBlock = targetBlocks[action.targetIndex];\n\n if (!targetBlock) continue;\n\n blocks.push({\n action: 'delete',\n targetLineRange: {\n start: targetBlock.lineStart,\n end: targetBlock.lineEnd,\n },\n targetContent: targetBlock.content,\n });\n }\n }\n\n return { blocks, summary };\n};\n\n/** Render a line range as a blue colored string, or an orange dash when absent. */\nconst colorizeLineRange = (range?: LineRange): string => {\n if (!range) return colorize('—', ANSIColors.ORANGE);\n const rangeStr =\n range.start === range.end\n ? `L${range.start}`\n : `L${range.start}-${range.end}`;\n return colorize(rangeStr, ANSIColors.BLUE);\n};\n\n/**\n * Colorize a count with green when zero (nothing to do) and a caller-supplied\n * non-zero color (orange for warnings, red for destructive actions).\n */\nconst colorizeCount = (\n count: number,\n nonZeroColor: (typeof ANSIColors)[keyof typeof ANSIColors]\n): string =>\n colorizeNumber(count, {\n zero: ANSIColors.GREEN,\n one: nonZeroColor,\n two: nonZeroColor,\n few: nonZeroColor,\n many: nonZeroColor,\n other: nonZeroColor,\n });\n\n/**\n * Render a {@link ReviewReport} as a human and agent readable log.\n *\n * Each divergent block is printed with its action, the base/target line ranges,\n * the base content to translate, and the existing translation to update.\n *\n * @param report - The report to format.\n * @param options - Optional labels for the base and target locales.\n * @returns A multi-line string describing every block that needs attention.\n */\nexport const formatReviewReport = (\n report: ReviewReport,\n options?: { baseLabel?: string; targetLabel?: string }\n): string => {\n const baseLabel = options?.baseLabel ?? 'base';\n const targetLabel = options?.targetLabel ?? 'target';\n\n const { summary, blocks } = report;\n\n // Build the summary header piece-by-piece so each count uses its own color.\n const header = [\n colorize('Review report: ', ANSIColors.ORANGE),\n colorizeCount(blocks.length, ANSIColors.ORANGE),\n colorize(' block(s) need attention (review=', ANSIColors.ORANGE),\n colorizeCount(summary.review, ANSIColors.ORANGE),\n colorize(', new=', ANSIColors.ORANGE),\n colorizeCount(summary.insertNew, ANSIColors.ORANGE),\n colorize(', delete=', ANSIColors.ORANGE),\n colorizeCount(summary.delete, ANSIColors.RED),\n colorize(', reuse=', ANSIColors.ORANGE),\n colorizeNumber(summary.reuse),\n colorize(').', ANSIColors.ORANGE),\n ].join('');\n\n if (blocks.length === 0) {\n return `${header}\\n${colorize('No changes needed.', ANSIColors.GREEN)}`;\n }\n\n const sections = blocks.map((block, index) => {\n const lines: string[] = [];\n\n // Block header: each token colored individually.\n const blockHeader = [\n colorize('--- Block ', ANSIColors.ORANGE),\n colorizeNumber(index + 1),\n colorize('/', ANSIColors.ORANGE),\n colorizeNumber(blocks.length),\n colorize(` [${block.action}] `, ANSIColors.ORANGE),\n baseLabel,\n colorize(' ', ANSIColors.ORANGE),\n colorizeLineRange(block.baseLineRange),\n colorize(' → ', ANSIColors.ORANGE),\n targetLabel,\n colorize(' ', ANSIColors.ORANGE),\n colorizeLineRange(block.targetLineRange),\n colorize(' ---', ANSIColors.ORANGE),\n ].join('');\n\n lines.push(blockHeader);\n\n if (block.baseContent !== undefined) {\n lines.push(colorize(`[${baseLabel}]`, ANSIColors.BEIGE));\n lines.push(colorize(block.baseContent.trimEnd(), ANSIColors.GREY));\n }\n\n if (block.targetContent !== undefined) {\n lines.push(colorize(`[${targetLabel}]`, ANSIColors.BEIGE));\n lines.push(colorize(block.targetContent.trimEnd(), ANSIColors.GREY));\n } else if (block.action === 'insert_new') {\n lines.push(colorize(`[${targetLabel}]`, ANSIColors.BEIGE));\n lines.push(colorize('(missing — to be translated)', ANSIColors.ORANGE));\n }\n\n return lines.join('\\n');\n });\n\n const editingNote = colorize(\n 'Tip: start editing from the last block — working bottom-up keeps earlier line numbers accurate.',\n ANSIColors.GREY\n );\n\n return [header, '', ...sections, '', editingNote].join('\\n');\n};\n"],"mappings":"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"}
@@ -3,7 +3,7 @@ import { isVersionAtLeast } from "../../utils/version.mjs";
3
3
  import { detectNextAppDir, findAppFile } from "./detect.mjs";
4
4
  import { restructureAppIntoLocale } from "./restructure.mjs";
5
5
  import { LOCALE_LAYOUT_TEMPLATE_JS, LOCALE_LAYOUT_TEMPLATE_TS, LOCALE_PAGE_TEMPLATE_JS, LOCALE_PAGE_TEMPLATE_TS, MIDDLEWARE_TEMPLATE, PROXY_TEMPLATE, ROOT_LAYOUT_TEMPLATE_JS, ROOT_LAYOUT_TEMPLATE_TS } from "./templates.mjs";
6
- import { wrapLayoutWithProvider, wrapPageWithProvider } from "./transforms.mjs";
6
+ import { wrapLayoutWithProvider } from "./transforms.mjs";
7
7
  import { join } from "node:path";
8
8
  import { colorize, colorizePath, logger, v, x } from "@intlayer/config/logger";
9
9
  import * as ANSIColors from "@intlayer/config/colors";
@@ -32,7 +32,7 @@ const ensureProxyOrMiddleware = async (rootDir, srcDir, isNext16) => {
32
32
  await writeFileToRoot(rootDir, targetPath, content);
33
33
  logger(`${v} Created ${colorizePath(targetPath)} for locale detection`);
34
34
  };
35
- /** Logs the outcome of a layout/page provider-wrap transform. */
35
+ /** Logs the outcome of a layout provider-wrap transform. */
36
36
  const logTransformOutcome = (filePath, result) => {
37
37
  switch (result.status) {
38
38
  case "wrapped":
@@ -62,8 +62,10 @@ const createIfMissing = async (rootDir, filePath, content, description) => {
62
62
  };
63
63
  /**
64
64
  * Next.js App Router adapter. Scaffolds the locale proxy/middleware, restructures
65
- * the app into a `[locale]` segment, and wraps the layout/page with the Intlayer
66
- * providers — all idempotently and without overwriting recognizable user code.
65
+ * the app into a `[locale]` segment, and wraps the layout with the unified
66
+ * Intlayer provider — all idempotently and without overwriting recognizable
67
+ * user code. Pages are never wrapped: `IntlayerProvider` in the locale layout
68
+ * covers them.
67
69
  */
68
70
  const nextAppRouterAdapter = {
69
71
  name: "Next.js App Router",
@@ -92,9 +94,7 @@ const nextAppRouterAdapter = {
92
94
  const existingLayout = await findAppFile(rootDir, localeDir, "layout");
93
95
  if (existingLayout) await transformExistingFile(rootDir, existingLayout, wrapLayoutWithProvider);
94
96
  else await createIfMissing(rootDir, join(localeDir, `layout.${scriptExtension}`), useTypeScript ? LOCALE_LAYOUT_TEMPLATE_TS : LOCALE_LAYOUT_TEMPLATE_JS, "locale layout");
95
- const existingPage = await findAppFile(rootDir, localeDir, "page");
96
- if (existingPage) await transformExistingFile(rootDir, existingPage, wrapPageWithProvider);
97
- else await createIfMissing(rootDir, join(localeDir, `page.${scriptExtension}`), useTypeScript ? LOCALE_PAGE_TEMPLATE_TS : LOCALE_PAGE_TEMPLATE_JS, "locale page");
97
+ if (!await findAppFile(rootDir, localeDir, "page")) await createIfMissing(rootDir, join(localeDir, `page.${scriptExtension}`), useTypeScript ? LOCALE_PAGE_TEMPLATE_TS : LOCALE_PAGE_TEMPLATE_JS, "locale page");
98
98
  }
99
99
  };
100
100
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../../../src/init/frameworkSetup/nextAppRouter/index.ts"],"sourcesContent":["import { join } from 'node:path';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, colorizePath, logger, v, x } from '@intlayer/config/logger';\nimport {\n ensureDirectory,\n exists,\n readFileFromRoot,\n writeFileToRoot,\n} from '../../utils/fileSystem';\nimport type { FrameworkAdapter, FrameworkSetupContext } from '../types';\nimport { detectNextAppDir, findAppFile, isVersionAtLeast } from './detect';\nimport { restructureAppIntoLocale } from './restructure';\nimport {\n LOCALE_LAYOUT_TEMPLATE_JS,\n LOCALE_LAYOUT_TEMPLATE_TS,\n LOCALE_PAGE_TEMPLATE_JS,\n LOCALE_PAGE_TEMPLATE_TS,\n MIDDLEWARE_TEMPLATE,\n PROXY_TEMPLATE,\n ROOT_LAYOUT_TEMPLATE_JS,\n ROOT_LAYOUT_TEMPLATE_TS,\n} from './templates';\nimport {\n type TransformResult,\n wrapLayoutWithProvider,\n wrapPageWithProvider,\n} from './transforms';\n\n/**\n * Creates the locale-detection proxy (Next >= 16) or middleware (Next < 16),\n * but only when neither already exists, so a user's custom file is never\n * touched. Placed next to the app directory's parent (`src/` when present).\n */\nconst ensureProxyOrMiddleware = async (\n rootDir: string,\n srcDir: string,\n isNext16: boolean\n): Promise<void> => {\n const baseDir = srcDir || '.';\n const proxyCandidates = [\n 'proxy.ts',\n 'proxy.js',\n 'middleware.ts',\n 'middleware.js',\n ];\n\n for (const candidate of proxyCandidates) {\n if (await exists(rootDir, join(baseDir, candidate))) {\n logger(\n `${v} ${colorizePath(join(baseDir, candidate))} already exists, leaving it untouched`\n );\n return;\n }\n }\n\n const fileName = isNext16 ? 'proxy.ts' : 'middleware.ts';\n const content = isNext16 ? PROXY_TEMPLATE : MIDDLEWARE_TEMPLATE;\n const targetPath = join(baseDir, fileName);\n\n if (srcDir) await ensureDirectory(rootDir, srcDir);\n await writeFileToRoot(rootDir, targetPath, content);\n logger(`${v} Created ${colorizePath(targetPath)} for locale detection`);\n};\n\n/** Logs the outcome of a layout/page provider-wrap transform. */\nconst logTransformOutcome = (\n filePath: string,\n result: TransformResult\n): void => {\n switch (result.status) {\n case 'wrapped':\n logger(`${v} Added Intlayer provider to ${colorizePath(filePath)}`);\n break;\n case 'already':\n logger(\n `${v} ${colorizePath(filePath)} already wraps the Intlayer provider`\n );\n break;\n case 'skipped-client':\n logger(\n `${x} ${colorizePath(filePath)} is a client component — add the Intlayer provider manually.`,\n { level: 'warn' }\n );\n break;\n case 'skipped':\n logger(\n `${x} Could not safely add the Intlayer provider to ${colorizePath(filePath)} — please wire it manually.`,\n { level: 'warn' }\n );\n break;\n }\n};\n\n/** Transforms an existing file in place, writing it back only when changed. */\nconst transformExistingFile = async (\n rootDir: string,\n filePath: string,\n transform: (code: string) => TransformResult\n): Promise<void> => {\n const code = await readFileFromRoot(rootDir, filePath);\n const result = transform(code);\n if (result.status === 'wrapped') {\n await writeFileToRoot(rootDir, filePath, result.code);\n }\n logTransformOutcome(filePath, result);\n};\n\n/** Creates a file from a template only when it does not already exist. */\nconst createIfMissing = async (\n rootDir: string,\n filePath: string,\n content: string,\n description: string\n): Promise<boolean> => {\n if (await exists(rootDir, filePath)) return false;\n await writeFileToRoot(rootDir, filePath, content);\n logger(`${v} Created ${description} ${colorizePath(filePath)}`);\n return true;\n};\n\n/**\n * Next.js App Router adapter. Scaffolds the locale proxy/middleware, restructures\n * the app into a `[locale]` segment, and wraps the layout/page with the Intlayer\n * providers — all idempotently and without overwriting recognizable user code.\n */\nexport const nextAppRouterAdapter: FrameworkAdapter = {\n name: 'Next.js App Router',\n\n detect: async ({ rootDir, allDeps }) => {\n if (!allDeps.next) return false;\n return (await detectNextAppDir(rootDir)) !== null;\n },\n\n setup: async ({ rootDir, allDeps, useTypeScript }: FrameworkSetupContext) => {\n const appDirInfo = await detectNextAppDir(rootDir);\n if (!appDirInfo) return;\n\n const { appDir, srcDir } = appDirInfo;\n const isNext16 = isVersionAtLeast(allDeps.next, 16);\n const scriptExtension = useTypeScript ? 'tsx' : 'jsx';\n\n logger(\n colorize('Setting up Next.js App Router integration...', ANSIColors.CYAN)\n );\n\n // 1. Locale detection proxy / middleware (create only when absent).\n await ensureProxyOrMiddleware(rootDir, srcDir, isNext16);\n\n // 2. Move routable files under a `[locale]` segment (idempotent).\n const restructureResult = await restructureAppIntoLocale(rootDir, appDir);\n if (restructureResult.status === 'moved') {\n logger(\n `${v} Restructured app routes under ${colorizePath(join(appDir, '[locale]'))}: ${restructureResult.movedEntries\n .map((entry) => colorize(entry, ANSIColors.MAGENTA))\n .join(', ')}`\n );\n } else if (restructureResult.status === 'already-structured') {\n logger(\n `${v} ${colorizePath(join(appDir, restructureResult.localeSegment))} already exists, skipping restructure`\n );\n }\n\n // When the app already uses a different locale-prefix scheme (e.g. an\n // `[...locale]` / `[[...locale]]` catch-all), respect the user's routing:\n // don't scaffold a second, conflicting `[locale]` segment.\n if (\n restructureResult.status === 'already-structured' &&\n restructureResult.localeSegment !== '[locale]'\n ) {\n logger(\n `${v} Detected existing locale routing (${colorize(restructureResult.localeSegment, ANSIColors.MAGENTA)}), skipping locale layout/page scaffold`\n );\n return;\n }\n\n const localeDir = join(appDir, '[locale]');\n await ensureDirectory(rootDir, localeDir);\n\n // 3. Minimal pass-through root layout (the `[locale]` layout owns `<html>`).\n await createIfMissing(\n rootDir,\n join(appDir, `layout.${scriptExtension}`),\n useTypeScript ? ROOT_LAYOUT_TEMPLATE_TS : ROOT_LAYOUT_TEMPLATE_JS,\n 'root layout'\n );\n\n // 4. Locale layout — transform an existing one, else scaffold from template.\n const existingLayout = await findAppFile(rootDir, localeDir, 'layout');\n if (existingLayout) {\n await transformExistingFile(\n rootDir,\n existingLayout,\n wrapLayoutWithProvider\n );\n } else {\n await createIfMissing(\n rootDir,\n join(localeDir, `layout.${scriptExtension}`),\n useTypeScript ? LOCALE_LAYOUT_TEMPLATE_TS : LOCALE_LAYOUT_TEMPLATE_JS,\n 'locale layout'\n );\n }\n\n // 5. Locale page — transform an existing one, else scaffold from template.\n const existingPage = await findAppFile(rootDir, localeDir, 'page');\n if (existingPage) {\n await transformExistingFile(rootDir, existingPage, wrapPageWithProvider);\n } else {\n await createIfMissing(\n rootDir,\n join(localeDir, `page.${scriptExtension}`),\n useTypeScript ? LOCALE_PAGE_TEMPLATE_TS : LOCALE_PAGE_TEMPLATE_JS,\n 'locale page'\n );\n }\n },\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../../../src/init/frameworkSetup/nextAppRouter/index.ts"],"sourcesContent":["import { join } from 'node:path';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, colorizePath, logger, v, x } from '@intlayer/config/logger';\nimport {\n ensureDirectory,\n exists,\n readFileFromRoot,\n writeFileToRoot,\n} from '../../utils/fileSystem';\nimport type { FrameworkAdapter, FrameworkSetupContext } from '../types';\nimport { detectNextAppDir, findAppFile, isVersionAtLeast } from './detect';\nimport { restructureAppIntoLocale } from './restructure';\nimport {\n LOCALE_LAYOUT_TEMPLATE_JS,\n LOCALE_LAYOUT_TEMPLATE_TS,\n LOCALE_PAGE_TEMPLATE_JS,\n LOCALE_PAGE_TEMPLATE_TS,\n MIDDLEWARE_TEMPLATE,\n PROXY_TEMPLATE,\n ROOT_LAYOUT_TEMPLATE_JS,\n ROOT_LAYOUT_TEMPLATE_TS,\n} from './templates';\nimport { type TransformResult, wrapLayoutWithProvider } from './transforms';\n\n/**\n * Creates the locale-detection proxy (Next >= 16) or middleware (Next < 16),\n * but only when neither already exists, so a user's custom file is never\n * touched. Placed next to the app directory's parent (`src/` when present).\n */\nconst ensureProxyOrMiddleware = async (\n rootDir: string,\n srcDir: string,\n isNext16: boolean\n): Promise<void> => {\n const baseDir = srcDir || '.';\n const proxyCandidates = [\n 'proxy.ts',\n 'proxy.js',\n 'middleware.ts',\n 'middleware.js',\n ];\n\n for (const candidate of proxyCandidates) {\n if (await exists(rootDir, join(baseDir, candidate))) {\n logger(\n `${v} ${colorizePath(join(baseDir, candidate))} already exists, leaving it untouched`\n );\n return;\n }\n }\n\n const fileName = isNext16 ? 'proxy.ts' : 'middleware.ts';\n const content = isNext16 ? PROXY_TEMPLATE : MIDDLEWARE_TEMPLATE;\n const targetPath = join(baseDir, fileName);\n\n if (srcDir) await ensureDirectory(rootDir, srcDir);\n await writeFileToRoot(rootDir, targetPath, content);\n logger(`${v} Created ${colorizePath(targetPath)} for locale detection`);\n};\n\n/** Logs the outcome of a layout provider-wrap transform. */\nconst logTransformOutcome = (\n filePath: string,\n result: TransformResult\n): void => {\n switch (result.status) {\n case 'wrapped':\n logger(`${v} Added Intlayer provider to ${colorizePath(filePath)}`);\n break;\n case 'already':\n logger(\n `${v} ${colorizePath(filePath)} already wraps the Intlayer provider`\n );\n break;\n case 'skipped-client':\n logger(\n `${x} ${colorizePath(filePath)} is a client component — add the Intlayer provider manually.`,\n { level: 'warn' }\n );\n break;\n case 'skipped':\n logger(\n `${x} Could not safely add the Intlayer provider to ${colorizePath(filePath)} — please wire it manually.`,\n { level: 'warn' }\n );\n break;\n }\n};\n\n/** Transforms an existing file in place, writing it back only when changed. */\nconst transformExistingFile = async (\n rootDir: string,\n filePath: string,\n transform: (code: string) => TransformResult\n): Promise<void> => {\n const code = await readFileFromRoot(rootDir, filePath);\n const result = transform(code);\n if (result.status === 'wrapped') {\n await writeFileToRoot(rootDir, filePath, result.code);\n }\n logTransformOutcome(filePath, result);\n};\n\n/** Creates a file from a template only when it does not already exist. */\nconst createIfMissing = async (\n rootDir: string,\n filePath: string,\n content: string,\n description: string\n): Promise<boolean> => {\n if (await exists(rootDir, filePath)) return false;\n await writeFileToRoot(rootDir, filePath, content);\n logger(`${v} Created ${description} ${colorizePath(filePath)}`);\n return true;\n};\n\n/**\n * Next.js App Router adapter. Scaffolds the locale proxy/middleware, restructures\n * the app into a `[locale]` segment, and wraps the layout with the unified\n * Intlayer provider — all idempotently and without overwriting recognizable\n * user code. Pages are never wrapped: `IntlayerProvider` in the locale layout\n * covers them.\n */\nexport const nextAppRouterAdapter: FrameworkAdapter = {\n name: 'Next.js App Router',\n\n detect: async ({ rootDir, allDeps }) => {\n if (!allDeps.next) return false;\n return (await detectNextAppDir(rootDir)) !== null;\n },\n\n setup: async ({ rootDir, allDeps, useTypeScript }: FrameworkSetupContext) => {\n const appDirInfo = await detectNextAppDir(rootDir);\n if (!appDirInfo) return;\n\n const { appDir, srcDir } = appDirInfo;\n const isNext16 = isVersionAtLeast(allDeps.next, 16);\n const scriptExtension = useTypeScript ? 'tsx' : 'jsx';\n\n logger(\n colorize('Setting up Next.js App Router integration...', ANSIColors.CYAN)\n );\n\n // 1. Locale detection proxy / middleware (create only when absent).\n await ensureProxyOrMiddleware(rootDir, srcDir, isNext16);\n\n // 2. Move routable files under a `[locale]` segment (idempotent).\n const restructureResult = await restructureAppIntoLocale(rootDir, appDir);\n if (restructureResult.status === 'moved') {\n logger(\n `${v} Restructured app routes under ${colorizePath(join(appDir, '[locale]'))}: ${restructureResult.movedEntries\n .map((entry) => colorize(entry, ANSIColors.MAGENTA))\n .join(', ')}`\n );\n } else if (restructureResult.status === 'already-structured') {\n logger(\n `${v} ${colorizePath(join(appDir, restructureResult.localeSegment))} already exists, skipping restructure`\n );\n }\n\n // When the app already uses a different locale-prefix scheme (e.g. an\n // `[...locale]` / `[[...locale]]` catch-all), respect the user's routing:\n // don't scaffold a second, conflicting `[locale]` segment.\n if (\n restructureResult.status === 'already-structured' &&\n restructureResult.localeSegment !== '[locale]'\n ) {\n logger(\n `${v} Detected existing locale routing (${colorize(restructureResult.localeSegment, ANSIColors.MAGENTA)}), skipping locale layout/page scaffold`\n );\n return;\n }\n\n const localeDir = join(appDir, '[locale]');\n await ensureDirectory(rootDir, localeDir);\n\n // 3. Minimal pass-through root layout (the `[locale]` layout owns `<html>`).\n await createIfMissing(\n rootDir,\n join(appDir, `layout.${scriptExtension}`),\n useTypeScript ? ROOT_LAYOUT_TEMPLATE_TS : ROOT_LAYOUT_TEMPLATE_JS,\n 'root layout'\n );\n\n // 4. Locale layout — transform an existing one, else scaffold from template.\n const existingLayout = await findAppFile(rootDir, localeDir, 'layout');\n if (existingLayout) {\n await transformExistingFile(\n rootDir,\n existingLayout,\n wrapLayoutWithProvider\n );\n } else {\n await createIfMissing(\n rootDir,\n join(localeDir, `layout.${scriptExtension}`),\n useTypeScript ? LOCALE_LAYOUT_TEMPLATE_TS : LOCALE_LAYOUT_TEMPLATE_JS,\n 'locale layout'\n );\n }\n\n // 5. Locale page — only scaffolded when absent. An existing page needs no\n // transform: the locale layout's `IntlayerProvider` already covers it.\n const existingPage = await findAppFile(rootDir, localeDir, 'page');\n if (!existingPage) {\n await createIfMissing(\n rootDir,\n join(localeDir, `page.${scriptExtension}`),\n useTypeScript ? LOCALE_PAGE_TEMPLATE_TS : LOCALE_PAGE_TEMPLATE_JS,\n 'locale page'\n );\n }\n },\n};\n"],"mappings":"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"}