@intlayer/engine 9.5.10 → 9.5.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/docReview/alignBlocks.cjs +53 -36
- package/dist/cjs/docReview/alignBlocks.cjs.map +1 -1
- package/dist/cjs/docReview/rebuildDocument.cjs +3 -1
- package/dist/cjs/docReview/rebuildDocument.cjs.map +1 -1
- package/dist/cjs/docReview/segmentDocument.cjs +8 -5
- package/dist/cjs/docReview/segmentDocument.cjs.map +1 -1
- package/dist/cjs/init/frameworkSetup/nextAppRouter/transforms.cjs +4 -1
- package/dist/cjs/init/frameworkSetup/nextAppRouter/transforms.cjs.map +1 -1
- package/dist/cjs/installMCP/installMCP.cjs +1 -1
- package/dist/cjs/installMCP/installMCP.cjs.map +1 -1
- package/dist/cjs/loadDictionaries/loadLocalDictionaries.cjs +3 -1
- package/dist/cjs/loadDictionaries/loadLocalDictionaries.cjs.map +1 -1
- package/dist/cjs/loadDictionaries/loadRemoteDictionaries.cjs +1 -1
- package/dist/cjs/loadDictionaries/loadRemoteDictionaries.cjs.map +1 -1
- package/dist/cjs/loadDictionaries/log.cjs +1 -1
- package/dist/cjs/loadDictionaries/log.cjs.map +1 -1
- package/dist/cjs/loadDictionaries/logTypeScriptErrors.cjs +19 -12
- package/dist/cjs/loadDictionaries/logTypeScriptErrors.cjs.map +1 -1
- package/dist/cjs/scan/analyzeBundleContent.cjs +26 -12
- package/dist/cjs/scan/analyzeBundleContent.cjs.map +1 -1
- package/dist/cjs/scan/calculateScore.cjs +4 -1
- package/dist/cjs/scan/calculateScore.cjs.map +1 -1
- package/dist/cjs/scan/checks.cjs +394 -221
- package/dist/cjs/scan/checks.cjs.map +1 -1
- package/dist/cjs/scan/detection/checkDetails.cjs +56 -0
- package/dist/cjs/scan/detection/checkDetails.cjs.map +1 -0
- package/dist/cjs/scan/detection/classifyInternalLinks.cjs +47 -0
- package/dist/cjs/scan/detection/classifyInternalLinks.cjs.map +1 -0
- package/dist/cjs/scan/detection/detectRoutingStrategy.cjs +143 -0
- package/dist/cjs/scan/detection/detectRoutingStrategy.cjs.map +1 -0
- package/dist/cjs/scan/detection/detectTechnologies.cjs +89 -0
- package/dist/cjs/scan/detection/detectTechnologies.cjs.map +1 -0
- package/dist/cjs/scan/detection/index.cjs +36 -0
- package/dist/cjs/scan/detection/localeCode.cjs +118 -0
- package/dist/cjs/scan/detection/localeCode.cjs.map +1 -0
- package/dist/cjs/scan/detection/localizedPages.cjs +48 -0
- package/dist/cjs/scan/detection/localizedPages.cjs.map +1 -0
- package/dist/cjs/scan/detection/technologySignatures.cjs +564 -0
- package/dist/cjs/scan/detection/technologySignatures.cjs.map +1 -0
- package/dist/cjs/scan/detection/url.cjs +53 -0
- package/dist/cjs/scan/detection/url.cjs.map +1 -0
- package/dist/cjs/scan/fetchText.cjs +56 -0
- package/dist/cjs/scan/fetchText.cjs.map +1 -0
- package/dist/cjs/scan/index.cjs +59 -3
- package/dist/cjs/scan/parseHtml.cjs +55 -10
- package/dist/cjs/scan/parseHtml.cjs.map +1 -1
- package/dist/cjs/scan/robots.cjs +65 -0
- package/dist/cjs/scan/robots.cjs.map +1 -0
- package/dist/cjs/scan/runScanChecks.cjs +89 -0
- package/dist/cjs/scan/runScanChecks.cjs.map +1 -0
- package/dist/cjs/scan/scanWebsite.cjs +58 -52
- package/dist/cjs/scan/scanWebsite.cjs.map +1 -1
- package/dist/cjs/scan/sitemap.cjs +109 -0
- package/dist/cjs/scan/sitemap.cjs.map +1 -0
- package/dist/cjs/utils/chunkJSON.cjs +24 -9
- package/dist/cjs/utils/chunkJSON.cjs.map +1 -1
- package/dist/cjs/utils/getChunk.cjs +2 -2
- package/dist/cjs/utils/getChunk.cjs.map +1 -1
- package/dist/cjs/utils/runParallel/index.cjs +1 -1
- package/dist/cjs/utils/runParallel/index.cjs.map +1 -1
- package/dist/cjs/utils/runParallel/pidTree.cjs +23 -11
- package/dist/cjs/utils/runParallel/pidTree.cjs.map +1 -1
- package/dist/cjs/utils/runParallel/ps.cjs +8 -3
- package/dist/cjs/utils/runParallel/ps.cjs.map +1 -1
- package/dist/cjs/utils/runParallel/runTask.cjs +2 -1
- package/dist/cjs/utils/runParallel/runTask.cjs.map +1 -1
- package/dist/cjs/utils/runParallel/wmic.cjs +8 -3
- package/dist/cjs/utils/runParallel/wmic.cjs.map +1 -1
- package/dist/cjs/writeContentDeclaration/detectExportedComponentName.cjs +7 -4
- package/dist/cjs/writeContentDeclaration/detectExportedComponentName.cjs.map +1 -1
- package/dist/cjs/writeContentDeclaration/writeMarkdownFile.cjs +2 -2
- package/dist/cjs/writeContentDeclaration/writeMarkdownFile.cjs.map +1 -1
- package/dist/esm/docReview/alignBlocks.mjs +53 -36
- package/dist/esm/docReview/alignBlocks.mjs.map +1 -1
- package/dist/esm/docReview/rebuildDocument.mjs +3 -1
- package/dist/esm/docReview/rebuildDocument.mjs.map +1 -1
- package/dist/esm/docReview/segmentDocument.mjs +8 -5
- package/dist/esm/docReview/segmentDocument.mjs.map +1 -1
- package/dist/esm/init/frameworkSetup/nextAppRouter/transforms.mjs +4 -1
- package/dist/esm/init/frameworkSetup/nextAppRouter/transforms.mjs.map +1 -1
- package/dist/esm/installMCP/installMCP.mjs +1 -1
- package/dist/esm/installMCP/installMCP.mjs.map +1 -1
- package/dist/esm/loadDictionaries/loadLocalDictionaries.mjs +3 -1
- package/dist/esm/loadDictionaries/loadLocalDictionaries.mjs.map +1 -1
- package/dist/esm/loadDictionaries/loadRemoteDictionaries.mjs +1 -1
- package/dist/esm/loadDictionaries/loadRemoteDictionaries.mjs.map +1 -1
- package/dist/esm/loadDictionaries/log.mjs +1 -1
- package/dist/esm/loadDictionaries/log.mjs.map +1 -1
- package/dist/esm/loadDictionaries/logTypeScriptErrors.mjs +19 -12
- package/dist/esm/loadDictionaries/logTypeScriptErrors.mjs.map +1 -1
- package/dist/esm/scan/analyzeBundleContent.mjs +26 -12
- package/dist/esm/scan/analyzeBundleContent.mjs.map +1 -1
- package/dist/esm/scan/calculateScore.mjs +4 -1
- package/dist/esm/scan/calculateScore.mjs.map +1 -1
- package/dist/esm/scan/checks.mjs +387 -220
- package/dist/esm/scan/checks.mjs.map +1 -1
- package/dist/esm/scan/detection/checkDetails.mjs +54 -0
- package/dist/esm/scan/detection/checkDetails.mjs.map +1 -0
- package/dist/esm/scan/detection/classifyInternalLinks.mjs +46 -0
- package/dist/esm/scan/detection/classifyInternalLinks.mjs.map +1 -0
- package/dist/esm/scan/detection/detectRoutingStrategy.mjs +141 -0
- package/dist/esm/scan/detection/detectRoutingStrategy.mjs.map +1 -0
- package/dist/esm/scan/detection/detectTechnologies.mjs +87 -0
- package/dist/esm/scan/detection/detectTechnologies.mjs.map +1 -0
- package/dist/esm/scan/detection/index.mjs +10 -0
- package/dist/esm/scan/detection/localeCode.mjs +108 -0
- package/dist/esm/scan/detection/localeCode.mjs.map +1 -0
- package/dist/esm/scan/detection/localizedPages.mjs +46 -0
- package/dist/esm/scan/detection/localizedPages.mjs.map +1 -0
- package/dist/esm/scan/detection/technologySignatures.mjs +563 -0
- package/dist/esm/scan/detection/technologySignatures.mjs.map +1 -0
- package/dist/esm/scan/detection/url.mjs +47 -0
- package/dist/esm/scan/detection/url.mjs.map +1 -0
- package/dist/esm/scan/fetchText.mjs +55 -0
- package/dist/esm/scan/fetchText.mjs.map +1 -0
- package/dist/esm/scan/index.mjs +15 -3
- package/dist/esm/scan/parseHtml.mjs +52 -11
- package/dist/esm/scan/parseHtml.mjs.map +1 -1
- package/dist/esm/scan/robots.mjs +63 -0
- package/dist/esm/scan/robots.mjs.map +1 -0
- package/dist/esm/scan/runScanChecks.mjs +88 -0
- package/dist/esm/scan/runScanChecks.mjs.map +1 -0
- package/dist/esm/scan/scanWebsite.mjs +59 -53
- package/dist/esm/scan/scanWebsite.mjs.map +1 -1
- package/dist/esm/scan/sitemap.mjs +105 -0
- package/dist/esm/scan/sitemap.mjs.map +1 -0
- package/dist/esm/utils/chunkJSON.mjs +24 -9
- package/dist/esm/utils/chunkJSON.mjs.map +1 -1
- package/dist/esm/utils/getChunk.mjs +2 -2
- package/dist/esm/utils/getChunk.mjs.map +1 -1
- package/dist/esm/utils/runParallel/index.mjs +1 -1
- package/dist/esm/utils/runParallel/index.mjs.map +1 -1
- package/dist/esm/utils/runParallel/pidTree.mjs +23 -11
- package/dist/esm/utils/runParallel/pidTree.mjs.map +1 -1
- package/dist/esm/utils/runParallel/ps.mjs +8 -3
- package/dist/esm/utils/runParallel/ps.mjs.map +1 -1
- package/dist/esm/utils/runParallel/runTask.mjs +2 -1
- package/dist/esm/utils/runParallel/runTask.mjs.map +1 -1
- package/dist/esm/utils/runParallel/wmic.mjs +8 -3
- package/dist/esm/utils/runParallel/wmic.mjs.map +1 -1
- package/dist/esm/writeContentDeclaration/detectExportedComponentName.mjs +7 -4
- package/dist/esm/writeContentDeclaration/detectExportedComponentName.mjs.map +1 -1
- package/dist/esm/writeContentDeclaration/writeMarkdownFile.mjs +2 -2
- package/dist/esm/writeContentDeclaration/writeMarkdownFile.mjs.map +1 -1
- package/dist/types/docReview/rebuildDocument.d.ts.map +1 -1
- package/dist/types/docReview/segmentDocument.d.ts.map +1 -1
- package/dist/types/init/frameworkSetup/nextAppRouter/transforms.d.ts.map +1 -1
- package/dist/types/loadDictionaries/logTypeScriptErrors.d.ts.map +1 -1
- package/dist/types/scan/analyzeBundleContent.d.ts.map +1 -1
- package/dist/types/scan/calculateScore.d.ts +4 -1
- package/dist/types/scan/calculateScore.d.ts.map +1 -1
- package/dist/types/scan/checks.d.ts +114 -21
- package/dist/types/scan/checks.d.ts.map +1 -1
- package/dist/types/scan/detection/checkDetails.d.ts +29 -0
- package/dist/types/scan/detection/checkDetails.d.ts.map +1 -0
- package/dist/types/scan/detection/classifyInternalLinks.d.ts +44 -0
- package/dist/types/scan/detection/classifyInternalLinks.d.ts.map +1 -0
- package/dist/types/scan/detection/detectRoutingStrategy.d.ts +60 -0
- package/dist/types/scan/detection/detectRoutingStrategy.d.ts.map +1 -0
- package/dist/types/scan/detection/detectTechnologies.d.ts +60 -0
- package/dist/types/scan/detection/detectTechnologies.d.ts.map +1 -0
- package/dist/types/scan/detection/index.d.ts +9 -0
- package/dist/types/scan/detection/localeCode.d.ts +49 -0
- package/dist/types/scan/detection/localeCode.d.ts.map +1 -0
- package/dist/types/scan/detection/localizedPages.d.ts +40 -0
- package/dist/types/scan/detection/localizedPages.d.ts.map +1 -0
- package/dist/types/scan/detection/technologySignatures.d.ts +48 -0
- package/dist/types/scan/detection/technologySignatures.d.ts.map +1 -0
- package/dist/types/scan/detection/url.d.ts +27 -0
- package/dist/types/scan/detection/url.d.ts.map +1 -0
- package/dist/types/scan/fetchText.d.ts +30 -0
- package/dist/types/scan/fetchText.d.ts.map +1 -0
- package/dist/types/scan/index.d.ts +16 -4
- package/dist/types/scan/parseHtml.d.ts +17 -0
- package/dist/types/scan/parseHtml.d.ts.map +1 -1
- package/dist/types/scan/robots.d.ts +23 -0
- package/dist/types/scan/robots.d.ts.map +1 -0
- package/dist/types/scan/runScanChecks.d.ts +43 -0
- package/dist/types/scan/runScanChecks.d.ts.map +1 -0
- package/dist/types/scan/scanWebsite.d.ts.map +1 -1
- package/dist/types/scan/sitemap.d.ts +49 -0
- package/dist/types/scan/sitemap.d.ts.map +1 -0
- package/dist/types/scan/types.d.ts +15 -0
- package/dist/types/scan/types.d.ts.map +1 -1
- package/dist/types/utils/chunkJSON.d.ts.map +1 -1
- package/dist/types/utils/runParallel/pidTree.d.ts.map +1 -1
- package/dist/types/writeContentDeclaration/writeMarkdownFile.d.ts.map +1 -1
- package/package.json +15 -7
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
//#region src/docReview/segmentDocument.ts
|
|
2
2
|
const HEADING_PATTERN = /^\s*(#{1,6})\s+/;
|
|
3
|
-
const isBlankLine = (line) => line.trim().length === 0;
|
|
4
|
-
const isFencedCodeDelimiter = (line) => /^\s*```/.test(line);
|
|
3
|
+
const isBlankLine = (line) => !line || line.trim().length === 0;
|
|
4
|
+
const isFencedCodeDelimiter = (line) => Boolean(line && /^\s*```/.test(line));
|
|
5
5
|
/**
|
|
6
6
|
* Read the depth of an ATX markdown heading (`#` → 1, `######` → 6).
|
|
7
7
|
*
|
|
@@ -9,10 +9,11 @@ const isFencedCodeDelimiter = (line) => /^\s*```/.test(line);
|
|
|
9
9
|
* @returns The heading depth, or `null` when the line is not a heading.
|
|
10
10
|
*/
|
|
11
11
|
const parseHeadingDepth = (line) => {
|
|
12
|
+
if (!line) return null;
|
|
12
13
|
return HEADING_PATTERN.exec(line)?.[1]?.length ?? null;
|
|
13
14
|
};
|
|
14
15
|
const isHeading = (line) => parseHeadingDepth(line) !== null;
|
|
15
|
-
const isFrontmatterDelimiter = (line) => /^\s*---\s*$/.test(line);
|
|
16
|
+
const isFrontmatterDelimiter = (line) => Boolean(line && /^\s*---\s*$/.test(line));
|
|
16
17
|
/**
|
|
17
18
|
* Split a markdown document into fine-grained blocks.
|
|
18
19
|
*
|
|
@@ -87,7 +88,8 @@ const segmentDocument = (text) => {
|
|
|
87
88
|
if (units.length === 0) return [];
|
|
88
89
|
return units.map((unit, unitIndex) => {
|
|
89
90
|
const blockStartIndex = unitIndex === 0 ? 0 : unit.startIndex;
|
|
90
|
-
const
|
|
91
|
+
const nextUnit = units[unitIndex + 1];
|
|
92
|
+
const blockEndIndex = unitIndex === units.length - 1 || !nextUnit ? lineCount - 1 : nextUnit.startIndex - 1;
|
|
91
93
|
const blockLines = lines.slice(blockStartIndex, blockEndIndex + 1);
|
|
92
94
|
const content = blockEndIndex < lineCount - 1 ? `${blockLines.join("\n")}\n` : blockLines.join("\n");
|
|
93
95
|
return {
|
|
@@ -123,8 +125,9 @@ const segmentSections = (text) => {
|
|
|
123
125
|
let currentBlocks = [];
|
|
124
126
|
const flushSection = () => {
|
|
125
127
|
if (currentBlocks.length === 0) return;
|
|
126
|
-
const
|
|
128
|
+
const firstBlock = currentBlocks[0];
|
|
127
129
|
const lastBlock = currentBlocks[currentBlocks.length - 1];
|
|
130
|
+
if (!firstBlock || !lastBlock) return;
|
|
128
131
|
sections.push({
|
|
129
132
|
type: firstBlock.type,
|
|
130
133
|
content: currentBlocks.map((block) => block.content).join(""),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"segmentDocument.mjs","names":[],"sources":["../../../src/docReview/segmentDocument.ts"],"sourcesContent":["import type { Block, BlockType } from './types';\n\nconst HEADING_PATTERN = /^\\s*(#{1,6})\\s+/;\n\nconst isBlankLine = (line: string): boolean => line.trim().length === 0;\nconst isFencedCodeDelimiter = (line: string): boolean => /^\\s*```/.test(line);\n\n/**\n * Read the depth of an ATX markdown heading (`#` → 1, `######` → 6).\n *\n * @param line - The line to inspect.\n * @returns The heading depth, or `null` when the line is not a heading.\n */\nconst parseHeadingDepth = (line: string): number | null => {\n const match = HEADING_PATTERN.exec(line);\n\n return match?.[1]?.length ?? null;\n};\n\nconst isHeading = (line: string): boolean => parseHeadingDepth(line) !== null;\nconst isFrontmatterDelimiter = (line: string): boolean =>\n /^\\s*---\\s*$/.test(line);\n\n/**\n * A content unit (heading, paragraph, code block or frontmatter) spanning a\n * 0-based, inclusive line range. Blank-line runs are not units of their own;\n * they are folded into the preceding unit as trailing separators (see\n * {@link segmentDocument}) so that the concatenation of every block's content\n * reproduces the document byte-for-byte.\n */\ntype ContentUnit = {\n type: BlockType;\n /** Depth of the ATX heading opening the unit, `null` when it is not a heading. */\n headingDepth: number | null;\n startIndex: number;\n endIndex: number;\n};\n\n/**\n * Split a markdown document into fine-grained blocks.\n *\n * Boundaries are drawn at headings, blank lines (paragraph breaks) and fenced\n * code blocks, while frontmatter and the inside of code fences are kept intact.\n * Each blank-line run is appended to the block that precedes it, so the blocks\n * form an exact partition of the document: concatenating every `content` (in\n * order) yields the original text unchanged. This is what lets the block-aware\n * review re-translate only the paragraphs/snippets that actually changed instead\n * of the whole heading section.\n *\n * @param text - The full markdown document.\n * @returns The ordered list of blocks with their 1-based line ranges.\n */\nexport const segmentDocument = (text: string): Block[] => {\n const lines = text.split('\\n');\n const lineCount = lines.length;\n\n // 1. Tokenize into content units, skipping blank-line runs (folded in below).\n const units: ContentUnit[] = [];\n let index = 0;\n\n while (index < lineCount) {\n const currentLine = lines[index];\n\n if (isBlankLine(currentLine)) {\n index += 1;\n continue;\n }\n\n // Frontmatter: only when it opens the document.\n if (units.length === 0 && isFrontmatterDelimiter(currentLine)) {\n const startIndex = index;\n index += 1;\n while (index < lineCount && !isFrontmatterDelimiter(lines[index])) {\n index += 1;\n }\n // Include the closing delimiter when present.\n if (index < lineCount) index += 1;\n units.push({\n type: 'unknown',\n headingDepth: null,\n startIndex,\n endIndex: index - 1,\n });\n continue;\n }\n\n // Fenced code block: consumed whole so inner blank lines and `#` lines are\n // never treated as boundaries.\n if (isFencedCodeDelimiter(currentLine)) {\n const startIndex = index;\n index += 1;\n while (index < lineCount && !isFencedCodeDelimiter(lines[index])) {\n index += 1;\n }\n // Include the closing fence when present.\n if (index < lineCount) index += 1;\n units.push({\n type: 'code_block',\n headingDepth: null,\n startIndex,\n endIndex: index - 1,\n });\n continue;\n }\n\n // Heading: a single self-contained line.\n const headingDepth = parseHeadingDepth(currentLine);\n\n if (headingDepth !== null) {\n units.push({\n type: 'heading',\n headingDepth,\n startIndex: index,\n endIndex: index,\n });\n index += 1;\n continue;\n }\n\n // Paragraph: a run of consecutive lines until a blank line, a heading or a\n // code fence. Tables and tight lists stay together (no blank line between\n // their rows/items).\n const startIndex = index;\n while (\n index < lineCount &&\n !isBlankLine(lines[index]) &&\n !isHeading(lines[index]) &&\n !isFencedCodeDelimiter(lines[index])\n ) {\n index += 1;\n }\n units.push({\n type: 'paragraph',\n headingDepth: null,\n startIndex,\n endIndex: index - 1,\n });\n }\n\n if (units.length === 0) return [];\n\n // 2. Turn each unit into a block whose line range extends to just before the\n // next unit, so the trailing blank-line run is owned by it. The first block\n // also absorbs any leading blank lines, and the last block runs to EOF.\n return units.map((unit, unitIndex): Block => {\n const blockStartIndex = unitIndex === 0 ? 0 : unit.startIndex;\n const blockEndIndex =\n unitIndex === units.length - 1\n ? lineCount - 1\n : units[unitIndex + 1].startIndex - 1;\n\n const blockLines = lines.slice(blockStartIndex, blockEndIndex + 1);\n // Re-append the boundary newline dropped by `split` for every block but the\n // one ending at EOF, so concatenating all blocks rebuilds the document.\n const content =\n blockEndIndex < lineCount - 1\n ? `${blockLines.join('\\n')}\\n`\n : blockLines.join('\\n');\n\n return {\n type: unit.type,\n content,\n headingDepth: unit.headingDepth,\n lineStart: blockStartIndex + 1,\n lineEnd: blockEndIndex + 1,\n };\n });\n};\n\n/**\n * Split a markdown document into coarse, heading-anchored sections.\n *\n * Built by grouping the fine blocks of {@link segmentDocument}: frontmatter and\n * each heading open a new section, and the following non-heading blocks are\n * folded into it. Because it only concatenates adjacent fine blocks, the result\n * is still an exact partition of the document (sections concatenate back to the\n * source unchanged).\n *\n * Sections are the robust alignment unit between a base document and its\n * translation — both share the same heading structure, so they align almost\n * perfectly and a translation that splits its prose into a different number of\n * paragraphs never causes a section to be dropped. Fine-grained review happens\n * within a section once it is known to have changed.\n *\n * @param text - The full markdown document.\n * @returns The ordered list of sections with their 1-based line ranges.\n */\nexport const segmentSections = (text: string): Block[] => {\n const fineBlocks = segmentDocument(text);\n const sections: Block[] = [];\n let currentBlocks: Block[] = [];\n\n const flushSection = (): void => {\n if (currentBlocks.length === 0) return;\n\n const [firstBlock] = currentBlocks;\n const lastBlock = currentBlocks[currentBlocks.length - 1];\n\n sections.push({\n type: firstBlock.type,\n content: currentBlocks.map((block) => block.content).join(''),\n // A section is identified by the heading that opens it, so it inherits its\n // depth — this is what keeps a `##` section from aligning with a `###` one.\n headingDepth: firstBlock.headingDepth,\n lineStart: firstBlock.lineStart,\n lineEnd: lastBlock.lineEnd,\n });\n currentBlocks = [];\n };\n\n for (const block of fineBlocks) {\n // Frontmatter (a leading `unknown` block) and every heading open a section.\n const opensSection =\n block.type === 'heading' ||\n (block.type === 'unknown' && sections.length === 0);\n\n if (opensSection) flushSection();\n currentBlocks.push(block);\n if (block.type === 'unknown' && sections.length === 0) flushSection();\n }\n\n flushSection();\n\n return sections;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"segmentDocument.mjs","names":[],"sources":["../../../src/docReview/segmentDocument.ts"],"sourcesContent":["import type { Block, BlockType } from './types';\n\nconst HEADING_PATTERN = /^\\s*(#{1,6})\\s+/;\n\nconst isBlankLine = (line?: string): boolean =>\n !line || line.trim().length === 0;\nconst isFencedCodeDelimiter = (line?: string): boolean =>\n Boolean(line && /^\\s*```/.test(line));\n\n/**\n * Read the depth of an ATX markdown heading (`#` → 1, `######` → 6).\n *\n * @param line - The line to inspect.\n * @returns The heading depth, or `null` when the line is not a heading.\n */\nconst parseHeadingDepth = (line?: string): number | null => {\n if (!line) return null;\n const match = HEADING_PATTERN.exec(line);\n\n return match?.[1]?.length ?? null;\n};\n\nconst isHeading = (line?: string): boolean => parseHeadingDepth(line) !== null;\nconst isFrontmatterDelimiter = (line?: string): boolean =>\n Boolean(line && /^\\s*---\\s*$/.test(line));\n\n/**\n * A content unit (heading, paragraph, code block or frontmatter) spanning a\n * 0-based, inclusive line range. Blank-line runs are not units of their own;\n * they are folded into the preceding unit as trailing separators (see\n * {@link segmentDocument}) so that the concatenation of every block's content\n * reproduces the document byte-for-byte.\n */\ntype ContentUnit = {\n type: BlockType;\n /** Depth of the ATX heading opening the unit, `null` when it is not a heading. */\n headingDepth: number | null;\n startIndex: number;\n endIndex: number;\n};\n\n/**\n * Split a markdown document into fine-grained blocks.\n *\n * Boundaries are drawn at headings, blank lines (paragraph breaks) and fenced\n * code blocks, while frontmatter and the inside of code fences are kept intact.\n * Each blank-line run is appended to the block that precedes it, so the blocks\n * form an exact partition of the document: concatenating every `content` (in\n * order) yields the original text unchanged. This is what lets the block-aware\n * review re-translate only the paragraphs/snippets that actually changed instead\n * of the whole heading section.\n *\n * @param text - The full markdown document.\n * @returns The ordered list of blocks with their 1-based line ranges.\n */\nexport const segmentDocument = (text: string): Block[] => {\n const lines = text.split('\\n');\n const lineCount = lines.length;\n\n // 1. Tokenize into content units, skipping blank-line runs (folded in below).\n const units: ContentUnit[] = [];\n let index = 0;\n\n while (index < lineCount) {\n const currentLine = lines[index];\n\n if (isBlankLine(currentLine)) {\n index += 1;\n continue;\n }\n\n // Frontmatter: only when it opens the document.\n if (units.length === 0 && isFrontmatterDelimiter(currentLine)) {\n const startIndex = index;\n index += 1;\n while (index < lineCount && !isFrontmatterDelimiter(lines[index])) {\n index += 1;\n }\n // Include the closing delimiter when present.\n if (index < lineCount) index += 1;\n units.push({\n type: 'unknown',\n headingDepth: null,\n startIndex,\n endIndex: index - 1,\n });\n continue;\n }\n\n // Fenced code block: consumed whole so inner blank lines and `#` lines are\n // never treated as boundaries.\n if (isFencedCodeDelimiter(currentLine)) {\n const startIndex = index;\n index += 1;\n while (index < lineCount && !isFencedCodeDelimiter(lines[index])) {\n index += 1;\n }\n // Include the closing fence when present.\n if (index < lineCount) index += 1;\n units.push({\n type: 'code_block',\n headingDepth: null,\n startIndex,\n endIndex: index - 1,\n });\n continue;\n }\n\n // Heading: a single self-contained line.\n const headingDepth = parseHeadingDepth(currentLine);\n\n if (headingDepth !== null) {\n units.push({\n type: 'heading',\n headingDepth,\n startIndex: index,\n endIndex: index,\n });\n index += 1;\n continue;\n }\n\n // Paragraph: a run of consecutive lines until a blank line, a heading or a\n // code fence. Tables and tight lists stay together (no blank line between\n // their rows/items).\n const startIndex = index;\n while (\n index < lineCount &&\n !isBlankLine(lines[index]) &&\n !isHeading(lines[index]) &&\n !isFencedCodeDelimiter(lines[index])\n ) {\n index += 1;\n }\n units.push({\n type: 'paragraph',\n headingDepth: null,\n startIndex,\n endIndex: index - 1,\n });\n }\n\n if (units.length === 0) return [];\n\n // 2. Turn each unit into a block whose line range extends to just before the\n // next unit, so the trailing blank-line run is owned by it. The first block\n // also absorbs any leading blank lines, and the last block runs to EOF.\n return units.map((unit, unitIndex): Block => {\n const blockStartIndex = unitIndex === 0 ? 0 : unit.startIndex;\n const nextUnit = units[unitIndex + 1];\n const blockEndIndex =\n unitIndex === units.length - 1 || !nextUnit\n ? lineCount - 1\n : nextUnit.startIndex - 1;\n\n const blockLines = lines.slice(blockStartIndex, blockEndIndex + 1);\n // Re-append the boundary newline dropped by `split` for every block but the\n // one ending at EOF, so concatenating all blocks rebuilds the document.\n const content =\n blockEndIndex < lineCount - 1\n ? `${blockLines.join('\\n')}\\n`\n : blockLines.join('\\n');\n\n return {\n type: unit.type,\n content,\n headingDepth: unit.headingDepth,\n lineStart: blockStartIndex + 1,\n lineEnd: blockEndIndex + 1,\n };\n });\n};\n\n/**\n * Split a markdown document into coarse, heading-anchored sections.\n *\n * Built by grouping the fine blocks of {@link segmentDocument}: frontmatter and\n * each heading open a new section, and the following non-heading blocks are\n * folded into it. Because it only concatenates adjacent fine blocks, the result\n * is still an exact partition of the document (sections concatenate back to the\n * source unchanged).\n *\n * Sections are the robust alignment unit between a base document and its\n * translation — both share the same heading structure, so they align almost\n * perfectly and a translation that splits its prose into a different number of\n * paragraphs never causes a section to be dropped. Fine-grained review happens\n * within a section once it is known to have changed.\n *\n * @param text - The full markdown document.\n * @returns The ordered list of sections with their 1-based line ranges.\n */\nexport const segmentSections = (text: string): Block[] => {\n const fineBlocks = segmentDocument(text);\n const sections: Block[] = [];\n let currentBlocks: Block[] = [];\n\n const flushSection = (): void => {\n if (currentBlocks.length === 0) return;\n\n const firstBlock = currentBlocks[0];\n const lastBlock = currentBlocks[currentBlocks.length - 1];\n\n if (!firstBlock || !lastBlock) return;\n\n sections.push({\n type: firstBlock.type,\n content: currentBlocks.map((block) => block.content).join(''),\n // A section is identified by the heading that opens it, so it inherits its\n // depth — this is what keeps a `##` section from aligning with a `###` one.\n headingDepth: firstBlock.headingDepth,\n lineStart: firstBlock.lineStart,\n lineEnd: lastBlock.lineEnd,\n });\n currentBlocks = [];\n };\n\n for (const block of fineBlocks) {\n // Frontmatter (a leading `unknown` block) and every heading open a section.\n const opensSection =\n block.type === 'heading' ||\n (block.type === 'unknown' && sections.length === 0);\n\n if (opensSection) flushSection();\n currentBlocks.push(block);\n if (block.type === 'unknown' && sections.length === 0) flushSection();\n }\n\n flushSection();\n\n return sections;\n};\n"],"mappings":"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"}
|
|
@@ -71,7 +71,10 @@ const setHtmlLang = (ast) => {
|
|
|
71
71
|
const langAttr = node.attributes?.find((attr) => attr.type === "JSXAttribute" && attr.name?.name === "lang");
|
|
72
72
|
const localeExpression = b.jsxExpressionContainer(b.identifier("locale"));
|
|
73
73
|
if (langAttr) langAttr.value = localeExpression;
|
|
74
|
-
else
|
|
74
|
+
else {
|
|
75
|
+
node.attributes = node.attributes ?? [];
|
|
76
|
+
node.attributes.push(b.jsxAttribute(b.jsxIdentifier("lang"), localeExpression));
|
|
77
|
+
}
|
|
75
78
|
return false;
|
|
76
79
|
}
|
|
77
80
|
this.traverse(path);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transforms.mjs","names":[],"sources":["../../../../../src/init/frameworkSetup/nextAppRouter/transforms.ts"],"sourcesContent":["import * as recast from 'recast';\nimport { babelTsParser } from '../../../utils/babelParser';\nimport { ensureNamedImport, firstInsertIndex } from '../../utils/astImports';\n\nconst { builders: b } = recast.types;\n\n/** babel-ts parser handles TypeScript *and* JSX (the `typescript` parser does not). */\nconst parseTsx = (code: string): any =>\n recast.parse(code, { parser: babelTsParser });\n\n/** Result of a source transform. `code` is unchanged for any non-`wrapped` status. */\nexport type TransformResult = {\n code: string;\n status: 'wrapped' | 'already' | 'skipped-client' | 'skipped';\n};\n\n/** Detects a top-level `'use client'` directive (client components can't be async server providers). */\nconst isClientComponent = (ast: any): boolean => {\n const directives = ast.program.directives ?? [];\n if (\n directives.some((directive: any) => directive.value?.value === 'use client')\n ) {\n return true;\n }\n return ast.program.body.some(\n (stmt: any) =>\n stmt.type === 'ExpressionStatement' &&\n stmt.expression?.type === 'StringLiteral' &&\n stmt.expression.value === 'use client'\n );\n};\n\n/** Finds the function node behind `export default`, following an identifier reference if needed. */\nconst findDefaultExportFunction = (ast: any): any => {\n const body = ast.program.body;\n\n const asFunction = (node: any): any =>\n node &&\n (node.type === 'ArrowFunctionExpression' ||\n node.type === 'FunctionExpression' ||\n node.type === 'FunctionDeclaration')\n ? node\n : null;\n\n for (const stmt of body) {\n if (stmt.type !== 'ExportDefaultDeclaration') continue;\n\n const direct = asFunction(stmt.declaration);\n if (direct) return direct;\n\n if (stmt.declaration?.type === 'Identifier') {\n const name = stmt.declaration.name;\n for (const candidate of body) {\n if (candidate.type === 'VariableDeclaration') {\n for (const declarator of candidate.declarations) {\n if (\n declarator.id?.type === 'Identifier' &&\n declarator.id.name === name\n ) {\n const fn = asFunction(declarator.init);\n if (fn) return fn;\n }\n }\n }\n if (\n candidate.type === 'FunctionDeclaration' &&\n candidate.id?.name === name\n ) {\n return candidate;\n }\n }\n }\n }\n\n return null;\n};\n\n/** Ensures `export { exportedName } from source`, skipping when already declared/re-exported. */\nconst ensureExportFrom = (\n ast: any,\n exportedName: string,\n source: string\n): void => {\n const body = ast.program.body;\n\n const alreadyPresent = body.some((stmt: any) => {\n if (\n stmt.type === 'ExportNamedDeclaration' &&\n stmt.source?.value === source\n ) {\n return stmt.specifiers.some(\n (spec: any) => spec.exported?.name === exportedName\n );\n }\n // Locally declared `export const/function generateStaticParams`\n if (stmt.type === 'ExportNamedDeclaration' && stmt.declaration) {\n const decl = stmt.declaration;\n if (decl.type === 'FunctionDeclaration' && decl.id?.name === exportedName)\n return true;\n if (decl.type === 'VariableDeclaration') {\n return decl.declarations.some(\n (d: any) => d.id?.type === 'Identifier' && d.id.name === exportedName\n );\n }\n }\n return false;\n });\n\n if (alreadyPresent) return;\n\n const exportNode = parseTsx(`export { ${exportedName} } from \"${source}\";`)\n .program.body[0];\n ast.program.body.splice(firstInsertIndex(ast), 0, exportNode);\n};\n\n/** Makes the function async and inserts `const locale = await getLocale();` once, at the top of its body. */\nconst ensureAwaitedLocale = (funcNode: any): void => {\n funcNode.async = true;\n\n if (funcNode.body.type !== 'BlockStatement') {\n funcNode.body = b.blockStatement([b.returnStatement(funcNode.body)]);\n }\n\n const hasLocale = funcNode.body.body.some(\n (stmt: any) =>\n stmt.type === 'VariableDeclaration' &&\n stmt.declarations.some(\n (d: any) =>\n // const locale = ...\n (d.id?.type === 'Identifier' && d.id.name === 'locale') ||\n // const { locale } = ... or const { locale: locale } = ...\n (d.id?.type === 'ObjectPattern' &&\n d.id.properties?.some(\n (prop: any) =>\n prop.value?.type === 'Identifier' &&\n prop.value?.name === 'locale'\n ))\n )\n );\n\n if (!hasLocale) {\n const localeStatement = parseTsx('const locale = await getLocale();')\n .program.body[0];\n funcNode.body.body.unshift(localeStatement);\n }\n};\n\n/** Builds `<providerName locale={locale}>{child}</providerName>` around an existing JSX child node. */\nconst buildProviderElement = (providerName: string, childNode: any): any => {\n const template = parseTsx(\n `const __wrap = <${providerName} locale={locale}>{__child__}</${providerName}>;`\n );\n const providerElement = template.program.body[0].declarations[0].init;\n providerElement.children = [childNode];\n return providerElement;\n};\n\n/** Sets `lang={locale}` on the first `<html>` element, if present. */\nconst setHtmlLang = (ast: any): void => {\n recast.visit(ast, {\n visitJSXOpeningElement(path) {\n const node = path.node;\n if (node.name?.type === 'JSXIdentifier' && node.name.name === 'html') {\n const langAttr = node.attributes?.find(\n (attr: any) =>\n attr.type === 'JSXAttribute' && attr.name?.name === 'lang'\n ) as any;\n const localeExpression = b.jsxExpressionContainer(\n b.identifier('locale')\n );\n if (langAttr) {\n langAttr.value = localeExpression;\n } else {\n node.attributes.push(\n b.jsxAttribute(b.jsxIdentifier('lang'), localeExpression)\n );\n }\n return false;\n }\n this.traverse(path);\n },\n });\n};\n\n/** Options of {@link wrapLayoutWithProvider}. */\nexport type WrapLayoutOptions = {\n /**\n * Re-export `generateStaticParams` from `next-intlayer`. Only meaningful for a\n * layout under a `[locale]` segment; a root layout of an unprefixed app has\n * no locale param to pre-render. Default: `true`.\n */\n exportStaticParams?: boolean;\n};\n\n/**\n * Wraps the `{children}` of a Next.js App Router **layout** with the unified\n * `IntlayerProvider`, deriving the locale via `getLocale()`. Safe and\n * idempotent: bails (returns the original code) for client components, when no\n * `{children}` placeholder is found, or when there is no default export.\n *\n * `IntlayerProvider` seeds both the server context and the client provider in\n * one mount, so this is the only provider a Next.js App Router app needs —\n * pages below the locale layout read locale/variant from it without wrapping\n * themselves individually (see the removed `wrapPageWithProvider`).\n */\nexport const wrapLayoutWithProvider = (\n code: string,\n { exportStaticParams = true }: WrapLayoutOptions = {}\n): TransformResult => {\n const ast = parseTsx(code);\n\n if (isClientComponent(ast)) return { code, status: 'skipped-client' };\n if (code.includes('IntlayerProvider')) return { code, status: 'already' };\n\n const funcNode = findDefaultExportFunction(ast);\n if (!funcNode) return { code, status: 'skipped' };\n\n let wrapped = false;\n recast.visit(funcNode, {\n visitJSXExpressionContainer(path) {\n if (wrapped) return false;\n const expression = path.node.expression;\n if (expression?.type === 'Identifier' && expression.name === 'children') {\n path.replace(buildProviderElement('IntlayerProvider', path.node));\n wrapped = true;\n return false;\n }\n this.traverse(path);\n },\n });\n\n if (!wrapped) return { code, status: 'skipped' };\n\n ensureNamedImport(ast, 'IntlayerProvider', 'next-intlayer/server');\n ensureNamedImport(ast, 'getLocale', 'next-intlayer/server');\n if (exportStaticParams) {\n ensureExportFrom(ast, 'generateStaticParams', 'next-intlayer');\n }\n ensureAwaitedLocale(funcNode);\n setHtmlLang(ast);\n\n return { code: recast.print(ast).code, status: 'wrapped' };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"transforms.mjs","names":[],"sources":["../../../../../src/init/frameworkSetup/nextAppRouter/transforms.ts"],"sourcesContent":["import * as recast from 'recast';\nimport { babelTsParser } from '../../../utils/babelParser';\nimport { ensureNamedImport, firstInsertIndex } from '../../utils/astImports';\n\nconst { builders: b } = recast.types;\n\n/** babel-ts parser handles TypeScript *and* JSX (the `typescript` parser does not). */\nconst parseTsx = (code: string): any =>\n recast.parse(code, { parser: babelTsParser });\n\n/** Result of a source transform. `code` is unchanged for any non-`wrapped` status. */\nexport type TransformResult = {\n code: string;\n status: 'wrapped' | 'already' | 'skipped-client' | 'skipped';\n};\n\n/** Detects a top-level `'use client'` directive (client components can't be async server providers). */\nconst isClientComponent = (ast: any): boolean => {\n const directives = ast.program.directives ?? [];\n if (\n directives.some((directive: any) => directive.value?.value === 'use client')\n ) {\n return true;\n }\n return ast.program.body.some(\n (stmt: any) =>\n stmt.type === 'ExpressionStatement' &&\n stmt.expression?.type === 'StringLiteral' &&\n stmt.expression.value === 'use client'\n );\n};\n\n/** Finds the function node behind `export default`, following an identifier reference if needed. */\nconst findDefaultExportFunction = (ast: any): any => {\n const body = ast.program.body;\n\n const asFunction = (node: any): any =>\n node &&\n (node.type === 'ArrowFunctionExpression' ||\n node.type === 'FunctionExpression' ||\n node.type === 'FunctionDeclaration')\n ? node\n : null;\n\n for (const stmt of body) {\n if (stmt.type !== 'ExportDefaultDeclaration') continue;\n\n const direct = asFunction(stmt.declaration);\n if (direct) return direct;\n\n if (stmt.declaration?.type === 'Identifier') {\n const name = stmt.declaration.name;\n for (const candidate of body) {\n if (candidate.type === 'VariableDeclaration') {\n for (const declarator of candidate.declarations) {\n if (\n declarator.id?.type === 'Identifier' &&\n declarator.id.name === name\n ) {\n const fn = asFunction(declarator.init);\n if (fn) return fn;\n }\n }\n }\n if (\n candidate.type === 'FunctionDeclaration' &&\n candidate.id?.name === name\n ) {\n return candidate;\n }\n }\n }\n }\n\n return null;\n};\n\n/** Ensures `export { exportedName } from source`, skipping when already declared/re-exported. */\nconst ensureExportFrom = (\n ast: any,\n exportedName: string,\n source: string\n): void => {\n const body = ast.program.body;\n\n const alreadyPresent = body.some((stmt: any) => {\n if (\n stmt.type === 'ExportNamedDeclaration' &&\n stmt.source?.value === source\n ) {\n return stmt.specifiers.some(\n (spec: any) => spec.exported?.name === exportedName\n );\n }\n // Locally declared `export const/function generateStaticParams`\n if (stmt.type === 'ExportNamedDeclaration' && stmt.declaration) {\n const decl = stmt.declaration;\n if (decl.type === 'FunctionDeclaration' && decl.id?.name === exportedName)\n return true;\n if (decl.type === 'VariableDeclaration') {\n return decl.declarations.some(\n (d: any) => d.id?.type === 'Identifier' && d.id.name === exportedName\n );\n }\n }\n return false;\n });\n\n if (alreadyPresent) return;\n\n const exportNode = parseTsx(`export { ${exportedName} } from \"${source}\";`)\n .program.body[0];\n ast.program.body.splice(firstInsertIndex(ast), 0, exportNode);\n};\n\n/** Makes the function async and inserts `const locale = await getLocale();` once, at the top of its body. */\nconst ensureAwaitedLocale = (funcNode: any): void => {\n funcNode.async = true;\n\n if (funcNode.body.type !== 'BlockStatement') {\n funcNode.body = b.blockStatement([b.returnStatement(funcNode.body)]);\n }\n\n const hasLocale = funcNode.body.body.some(\n (stmt: any) =>\n stmt.type === 'VariableDeclaration' &&\n stmt.declarations.some(\n (d: any) =>\n // const locale = ...\n (d.id?.type === 'Identifier' && d.id.name === 'locale') ||\n // const { locale } = ... or const { locale: locale } = ...\n (d.id?.type === 'ObjectPattern' &&\n d.id.properties?.some(\n (prop: any) =>\n prop.value?.type === 'Identifier' &&\n prop.value?.name === 'locale'\n ))\n )\n );\n\n if (!hasLocale) {\n const localeStatement = parseTsx('const locale = await getLocale();')\n .program.body[0];\n funcNode.body.body.unshift(localeStatement);\n }\n};\n\n/** Builds `<providerName locale={locale}>{child}</providerName>` around an existing JSX child node. */\nconst buildProviderElement = (providerName: string, childNode: any): any => {\n const template = parseTsx(\n `const __wrap = <${providerName} locale={locale}>{__child__}</${providerName}>;`\n );\n const providerElement = template.program.body[0].declarations[0].init;\n providerElement.children = [childNode];\n return providerElement;\n};\n\n/** Sets `lang={locale}` on the first `<html>` element, if present. */\nconst setHtmlLang = (ast: any): void => {\n recast.visit(ast, {\n visitJSXOpeningElement(path) {\n const node = path.node;\n if (node.name?.type === 'JSXIdentifier' && node.name.name === 'html') {\n const langAttr = node.attributes?.find(\n (attr: any) =>\n attr.type === 'JSXAttribute' && attr.name?.name === 'lang'\n ) as any;\n const localeExpression = b.jsxExpressionContainer(\n b.identifier('locale')\n );\n if (langAttr) {\n langAttr.value = localeExpression;\n } else {\n node.attributes = node.attributes ?? [];\n node.attributes.push(\n b.jsxAttribute(b.jsxIdentifier('lang'), localeExpression)\n );\n }\n return false;\n }\n this.traverse(path);\n },\n });\n};\n\n/** Options of {@link wrapLayoutWithProvider}. */\nexport type WrapLayoutOptions = {\n /**\n * Re-export `generateStaticParams` from `next-intlayer`. Only meaningful for a\n * layout under a `[locale]` segment; a root layout of an unprefixed app has\n * no locale param to pre-render. Default: `true`.\n */\n exportStaticParams?: boolean;\n};\n\n/**\n * Wraps the `{children}` of a Next.js App Router **layout** with the unified\n * `IntlayerProvider`, deriving the locale via `getLocale()`. Safe and\n * idempotent: bails (returns the original code) for client components, when no\n * `{children}` placeholder is found, or when there is no default export.\n *\n * `IntlayerProvider` seeds both the server context and the client provider in\n * one mount, so this is the only provider a Next.js App Router app needs —\n * pages below the locale layout read locale/variant from it without wrapping\n * themselves individually (see the removed `wrapPageWithProvider`).\n */\nexport const wrapLayoutWithProvider = (\n code: string,\n { exportStaticParams = true }: WrapLayoutOptions = {}\n): TransformResult => {\n const ast = parseTsx(code);\n\n if (isClientComponent(ast)) return { code, status: 'skipped-client' };\n if (code.includes('IntlayerProvider')) return { code, status: 'already' };\n\n const funcNode = findDefaultExportFunction(ast);\n if (!funcNode) return { code, status: 'skipped' };\n\n let wrapped = false;\n recast.visit(funcNode, {\n visitJSXExpressionContainer(path) {\n if (wrapped) return false;\n const expression = path.node.expression;\n if (expression?.type === 'Identifier' && expression.name === 'children') {\n path.replace(buildProviderElement('IntlayerProvider', path.node));\n wrapped = true;\n return false;\n }\n this.traverse(path);\n },\n });\n\n if (!wrapped) return { code, status: 'skipped' };\n\n ensureNamedImport(ast, 'IntlayerProvider', 'next-intlayer/server');\n ensureNamedImport(ast, 'getLocale', 'next-intlayer/server');\n if (exportStaticParams) {\n ensureExportFrom(ast, 'generateStaticParams', 'next-intlayer');\n }\n ensureAwaitedLocale(funcNode);\n setHtmlLang(ast);\n\n return { code: recast.print(ast).code, status: 'wrapped' };\n};\n"],"mappings":"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"}
|
|
@@ -14,7 +14,7 @@ const installMCP = async (projectRoot, platform, transport) => {
|
|
|
14
14
|
let configKey = "mcpServers";
|
|
15
15
|
if (platform === "Claude") configPath = CLAUDE_DESKTOP_CONFIG_PATH;
|
|
16
16
|
else {
|
|
17
|
-
const relativeDir = path.dirname(PLATFORMS_METADATA[platform].
|
|
17
|
+
const relativeDir = path.dirname(PLATFORMS_METADATA[platform]?.dir ?? ".");
|
|
18
18
|
configPath = path.join(projectRoot, relativeDir, MCP_CONFIG_FILENAME);
|
|
19
19
|
if (platform === "VSCode") configKey = "servers";
|
|
20
20
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"installMCP.mjs","names":["fs"],"sources":["../../../src/installMCP/installMCP.ts"],"sourcesContent":["import { promises as fs } from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { PLATFORMS_METADATA, type Platform } from '../installSkills';\n\nexport type MCPTransport = 'stdio' | 'sse';\n\nconst MCP_CONFIG_FILENAME = 'mcp.json';\n\nconst CLAUDE_DESKTOP_CONFIG_PATH =\n process.platform === 'win32'\n ? path.join(\n process.env.APPDATA || '',\n 'Claude',\n 'claude_desktop_config.json'\n )\n : path.join(\n os.homedir(),\n 'Library',\n 'Application Support',\n 'Claude',\n 'claude_desktop_config.json'\n );\n\n/**\n * Installs the Intlayer MCP server configuration for a specific platform.\n */\nexport const installMCP = async (\n projectRoot: string,\n platform: Platform,\n transport: MCPTransport\n): Promise<string> => {\n let configPath: string;\n let configKey = 'mcpServers';\n\n if (platform === 'Claude') {\n configPath = CLAUDE_DESKTOP_CONFIG_PATH;\n } else {\n const relativeDir = path.dirname(PLATFORMS_METADATA[platform].
|
|
1
|
+
{"version":3,"file":"installMCP.mjs","names":["fs"],"sources":["../../../src/installMCP/installMCP.ts"],"sourcesContent":["import { promises as fs } from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { PLATFORMS_METADATA, type Platform } from '../installSkills';\n\nexport type MCPTransport = 'stdio' | 'sse';\n\nconst MCP_CONFIG_FILENAME = 'mcp.json';\n\nconst CLAUDE_DESKTOP_CONFIG_PATH =\n process.platform === 'win32'\n ? path.join(\n process.env.APPDATA || '',\n 'Claude',\n 'claude_desktop_config.json'\n )\n : path.join(\n os.homedir(),\n 'Library',\n 'Application Support',\n 'Claude',\n 'claude_desktop_config.json'\n );\n\n/**\n * Installs the Intlayer MCP server configuration for a specific platform.\n */\nexport const installMCP = async (\n projectRoot: string,\n platform: Platform,\n transport: MCPTransport\n): Promise<string> => {\n let configPath: string;\n let configKey = 'mcpServers';\n\n if (platform === 'Claude') {\n configPath = CLAUDE_DESKTOP_CONFIG_PATH;\n } else {\n const relativeDir = path.dirname(PLATFORMS_METADATA[platform]?.dir ?? '.'); // e.g. .cursor or .vscode\n configPath = path.join(projectRoot, relativeDir, MCP_CONFIG_FILENAME);\n\n if (platform === 'VSCode') {\n configKey = 'servers';\n }\n }\n\n // Ensure the configuration directory exists\n await fs.mkdir(path.dirname(configPath), { recursive: true });\n\n let config: any = {};\n try {\n const content = await fs.readFile(configPath, 'utf-8');\n config = JSON.parse(content);\n } catch {\n // File doesn't exist or is invalid JSON, start fresh\n }\n\n if (!config[configKey]) {\n config[configKey] = {};\n }\n\n if (transport === 'stdio') {\n const mcpConfig: any = {\n command: 'npx',\n args: ['-y', '@intlayer/mcp'],\n };\n\n if (platform === 'VSCode') {\n mcpConfig.type = 'stdio';\n }\n\n config[configKey].intlayer = mcpConfig;\n } else {\n const mcpConfig: any = {\n url: 'https://mcp.intlayer.org',\n };\n\n if (platform === 'VSCode') {\n mcpConfig.type = 'sse';\n } else {\n mcpConfig.transport = 'sse';\n }\n\n config[configKey].intlayer = mcpConfig;\n }\n\n await fs.writeFile(configPath, JSON.stringify(config, null, 2), 'utf-8');\n\n return `MCP server configuration updated in ${configPath}`;\n};\n"],"mappings":";;;;;;AAOA,MAAM,sBAAsB;AAE5B,MAAM,6BACJ,QAAQ,aAAa,UACjB,KAAK,KACH,QAAQ,IAAI,WAAW,IACvB,UACA,4BACF,IACA,KAAK,KACH,GAAG,QAAQ,GACX,WACA,uBACA,UACA,4BACF;;;;AAKN,MAAa,aAAa,OACxB,aACA,UACA,cACoB;CACpB,IAAI;CACJ,IAAI,YAAY;CAEhB,IAAI,aAAa,UACf,aAAa;MACR;EACL,MAAM,cAAc,KAAK,QAAQ,mBAAmB,SAAS,EAAE,OAAO,GAAG;EACzE,aAAa,KAAK,KAAK,aAAa,aAAa,mBAAmB;EAEpE,IAAI,aAAa,UACf,YAAY;CAEhB;CAGA,MAAMA,SAAG,MAAM,KAAK,QAAQ,UAAU,GAAG,EAAE,WAAW,KAAK,CAAC;CAE5D,IAAI,SAAc,CAAC;CACnB,IAAI;EACF,MAAM,UAAU,MAAMA,SAAG,SAAS,YAAY,OAAO;EACrD,SAAS,KAAK,MAAM,OAAO;CAC7B,QAAQ,CAER;CAEA,IAAI,CAAC,OAAO,YACV,OAAO,aAAa,CAAC;CAGvB,IAAI,cAAc,SAAS;EACzB,MAAM,YAAiB;GACrB,SAAS;GACT,MAAM,CAAC,MAAM,eAAe;EAC9B;EAEA,IAAI,aAAa,UACf,UAAU,OAAO;EAGnB,OAAO,UAAU,CAAC,WAAW;CAC/B,OAAO;EACL,MAAM,YAAiB,EACrB,KAAK,2BACP;EAEA,IAAI,aAAa,UACf,UAAU,OAAO;OAEjB,UAAU,YAAY;EAGxB,OAAO,UAAU,CAAC,WAAW;CAC/B;CAEA,MAAMA,SAAG,UAAU,YAAY,KAAK,UAAU,QAAQ,MAAM,CAAC,GAAG,OAAO;CAEvE,OAAO,uCAAuC;AAChD"}
|
|
@@ -12,8 +12,10 @@ const loadLocalDictionaries = async (contentDeclarationsPaths, configuration) =>
|
|
|
12
12
|
for await (const contentDeclarationPath of contentDeclarationsPaths) {
|
|
13
13
|
const dictionary = await loadContentDeclarations([contentDeclarationPath], configuration);
|
|
14
14
|
const relativeFilePath = relative(baseDir, contentDeclarationPath);
|
|
15
|
+
const firstDict = dictionary[0];
|
|
16
|
+
if (!firstDict) continue;
|
|
15
17
|
const dictionaryWithPath = {
|
|
16
|
-
...
|
|
18
|
+
...firstDict,
|
|
17
19
|
filePath: relativeFilePath
|
|
18
20
|
};
|
|
19
21
|
result.push(dictionaryWithPath);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loadLocalDictionaries.mjs","names":[],"sources":["../../../src/loadDictionaries/loadLocalDictionaries.ts"],"sourcesContent":["import { mkdir } from 'node:fs/promises';\nimport { relative, resolve } from 'node:path';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { Dictionary } from '@intlayer/types/dictionary';\nimport { loadContentDeclarations } from './loadContentDeclaration';\n\nexport const loadLocalDictionaries = async (\n contentDeclarationsPaths: string[] | string,\n configuration: IntlayerConfig\n): Promise<Dictionary[]> => {\n const { system } = configuration;\n const { dictionariesDir, baseDir } = system;\n\n if (typeof contentDeclarationsPaths === 'string') {\n contentDeclarationsPaths = [contentDeclarationsPaths];\n }\n\n // Create the dictionaries folder if it doesn't exist\n await mkdir(resolve(dictionariesDir), { recursive: true });\n\n const result: Dictionary[] = [];\n\n for await (const contentDeclarationPath of contentDeclarationsPaths) {\n const dictionary = await loadContentDeclarations(\n [contentDeclarationPath],\n configuration\n );\n\n const relativeFilePath = relative(baseDir, contentDeclarationPath);\n\n const dictionaryWithPath: Dictionary = {\n ...
|
|
1
|
+
{"version":3,"file":"loadLocalDictionaries.mjs","names":[],"sources":["../../../src/loadDictionaries/loadLocalDictionaries.ts"],"sourcesContent":["import { mkdir } from 'node:fs/promises';\nimport { relative, resolve } from 'node:path';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { Dictionary } from '@intlayer/types/dictionary';\nimport { loadContentDeclarations } from './loadContentDeclaration';\n\nexport const loadLocalDictionaries = async (\n contentDeclarationsPaths: string[] | string,\n configuration: IntlayerConfig\n): Promise<Dictionary[]> => {\n const { system } = configuration;\n const { dictionariesDir, baseDir } = system;\n\n if (typeof contentDeclarationsPaths === 'string') {\n contentDeclarationsPaths = [contentDeclarationsPaths];\n }\n\n // Create the dictionaries folder if it doesn't exist\n await mkdir(resolve(dictionariesDir), { recursive: true });\n\n const result: Dictionary[] = [];\n\n for await (const contentDeclarationPath of contentDeclarationsPaths) {\n const dictionary = await loadContentDeclarations(\n [contentDeclarationPath],\n configuration\n );\n\n const relativeFilePath = relative(baseDir, contentDeclarationPath);\n\n const firstDict = dictionary[0];\n if (!firstDict) continue;\n\n const dictionaryWithPath: Dictionary = {\n ...firstDict,\n filePath: relativeFilePath,\n };\n\n result.push(dictionaryWithPath);\n }\n\n return result;\n};\n"],"mappings":";;;;;AAMA,MAAa,wBAAwB,OACnC,0BACA,kBAC0B;CAC1B,MAAM,EAAE,WAAW;CACnB,MAAM,EAAE,iBAAiB,YAAY;CAErC,IAAI,OAAO,6BAA6B,UACtC,2BAA2B,CAAC,wBAAwB;CAItD,MAAM,MAAM,QAAQ,eAAe,GAAG,EAAE,WAAW,KAAK,CAAC;CAEzD,MAAM,SAAuB,CAAC;CAE9B,WAAW,MAAM,0BAA0B,0BAA0B;EACnE,MAAM,aAAa,MAAM,wBACvB,CAAC,sBAAsB,GACvB,aACF;EAEA,MAAM,mBAAmB,SAAS,SAAS,sBAAsB;EAEjE,MAAM,YAAY,WAAW;EAC7B,IAAI,CAAC,WAAW;EAEhB,MAAM,qBAAiC;GACrC,GAAG;GACH,UAAU;EACZ;EAEA,OAAO,KAAK,kBAAkB;CAChC;CAEA,OAAO;AACT"}
|
|
@@ -29,7 +29,7 @@ const loadRemoteDictionaries = async (configuration = getConfiguration(), onStat
|
|
|
29
29
|
return data.updatedAt > localUpdatedAt;
|
|
30
30
|
});
|
|
31
31
|
const cachedDictionaries = Object.values(remoteDictionariesRecord).flat().filter((dictionary) => {
|
|
32
|
-
const remoteUpdatedAt = distantDictionaryUpdateTimeStamp[dictionary.id]
|
|
32
|
+
const remoteUpdatedAt = dictionary.id ? distantDictionaryUpdateTimeStamp[dictionary.id]?.updatedAt : void 0;
|
|
33
33
|
const localUpdatedAtRaw = dictionary.updatedAt;
|
|
34
34
|
const localUpdatedAt = typeof localUpdatedAtRaw === "number" ? localUpdatedAtRaw : localUpdatedAtRaw ? new Date(localUpdatedAtRaw).getTime() : void 0;
|
|
35
35
|
return typeof localUpdatedAt === "number" && typeof remoteUpdatedAt === "number" && localUpdatedAt >= remoteUpdatedAt;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loadRemoteDictionaries.mjs","names":[],"sources":["../../../src/loadDictionaries/loadRemoteDictionaries.ts"],"sourcesContent":["import { createIntlayerCMS } from '@intlayer/api';\nimport { dictionaryEndpoint } from '@intlayer/api/dictionary';\n// @ts-ignore @intlayer/backend is not build yet\nimport type { DictionaryAPI } from '@intlayer/backend';\nimport { getConfiguration } from '@intlayer/config/node';\nimport { getRemoteDictionaries } from '@intlayer/dictionaries-entry/remote';\nimport type {\n Dictionary,\n DictionaryId,\n DictionaryKey,\n} from '@intlayer/types/dictionary';\nimport { fetchDistantDictionaries } from '../fetchDistantDictionaries';\nimport { sortAlphabetically } from '../utils/sortAlphabetically';\nimport type { DictionariesStatus } from './loadDictionaries';\n\nexport const formatDistantDictionaries = (\n dictionaries: (DictionaryAPI | Dictionary)[]\n): Dictionary[] =>\n dictionaries.map((dict) => ({\n ...dict,\n localId: `${dict.key}::remote::${dict.id}`,\n location: 'remote' as const,\n }));\n\nexport const loadRemoteDictionaries = async (\n configuration = getConfiguration(),\n onStatusUpdate?: (status: DictionariesStatus[]) => void,\n options?: {\n onStartRemoteCheck?: () => void;\n onStopRemoteCheck?: () => void;\n onError?: (error: Error) => void;\n }\n): Promise<Dictionary[]> => {\n const { editor } = configuration;\n const remoteDictionariesRecord = getRemoteDictionaries(configuration);\n\n const hasRemoteDictionaries = Boolean(editor.clientId && editor.clientSecret);\n\n if (!hasRemoteDictionaries) return [];\n\n try {\n options?.onStartRemoteCheck?.();\n\n const dictionary = dictionaryEndpoint(createIntlayerCMS(configuration));\n\n // Get the list of dictionary keys\n const getDictionariesKeysResult =\n await dictionary.getDictionariesUpdateTimestamp();\n\n const distantDictionaryUpdateTimeStamp: Record<\n DictionaryId,\n { key: DictionaryKey; updatedAt: number }\n > | null = getDictionariesKeysResult.data;\n\n if (!distantDictionaryUpdateTimeStamp) {\n throw new Error('No distant dictionaries found');\n }\n\n const dictionariesIdToFetch = Object.entries(\n distantDictionaryUpdateTimeStamp\n ).filter(([dictionaryId, data]) => {\n // If remote doesn't provide updatedAt, fetch to be safe\n if (!data.updatedAt) return true;\n\n // If no local cache exists, fetch\n const local: Dictionary | undefined = remoteDictionariesRecord[\n data.key\n ]?.find((dictionary) => dictionary.id === dictionaryId);\n if (!local) return true;\n\n const localUpdatedAtRaw = (local as any)?.updatedAt as\n | number\n | string\n | undefined;\n\n const localUpdatedAt =\n typeof localUpdatedAtRaw === 'number'\n ? localUpdatedAtRaw\n : localUpdatedAtRaw\n ? new Date(localUpdatedAtRaw).getTime()\n : undefined;\n\n // If local timestamp missing or older than remote, fetch\n if (typeof localUpdatedAt !== 'number') return true;\n\n return data.updatedAt > localUpdatedAt;\n });\n\n const flatRemoteDictionariesRecord: DictionaryAPI[] = Object.values(\n remoteDictionariesRecord\n ).flat();\n\n const cachedDictionaries: Dictionary[] =\n flatRemoteDictionariesRecord.filter((dictionary) => {\n const remoteUpdatedAt
|
|
1
|
+
{"version":3,"file":"loadRemoteDictionaries.mjs","names":[],"sources":["../../../src/loadDictionaries/loadRemoteDictionaries.ts"],"sourcesContent":["import { createIntlayerCMS } from '@intlayer/api';\nimport { dictionaryEndpoint } from '@intlayer/api/dictionary';\n// @ts-ignore @intlayer/backend is not build yet\nimport type { DictionaryAPI } from '@intlayer/backend';\nimport { getConfiguration } from '@intlayer/config/node';\nimport { getRemoteDictionaries } from '@intlayer/dictionaries-entry/remote';\nimport type {\n Dictionary,\n DictionaryId,\n DictionaryKey,\n} from '@intlayer/types/dictionary';\nimport { fetchDistantDictionaries } from '../fetchDistantDictionaries';\nimport { sortAlphabetically } from '../utils/sortAlphabetically';\nimport type { DictionariesStatus } from './loadDictionaries';\n\nexport const formatDistantDictionaries = (\n dictionaries: (DictionaryAPI | Dictionary)[]\n): Dictionary[] =>\n dictionaries.map((dict) => ({\n ...dict,\n localId: `${dict.key}::remote::${dict.id}`,\n location: 'remote' as const,\n }));\n\nexport const loadRemoteDictionaries = async (\n configuration = getConfiguration(),\n onStatusUpdate?: (status: DictionariesStatus[]) => void,\n options?: {\n onStartRemoteCheck?: () => void;\n onStopRemoteCheck?: () => void;\n onError?: (error: Error) => void;\n }\n): Promise<Dictionary[]> => {\n const { editor } = configuration;\n const remoteDictionariesRecord = getRemoteDictionaries(configuration);\n\n const hasRemoteDictionaries = Boolean(editor.clientId && editor.clientSecret);\n\n if (!hasRemoteDictionaries) return [];\n\n try {\n options?.onStartRemoteCheck?.();\n\n const dictionary = dictionaryEndpoint(createIntlayerCMS(configuration));\n\n // Get the list of dictionary keys\n const getDictionariesKeysResult =\n await dictionary.getDictionariesUpdateTimestamp();\n\n const distantDictionaryUpdateTimeStamp: Record<\n DictionaryId,\n { key: DictionaryKey; updatedAt: number }\n > | null = getDictionariesKeysResult.data;\n\n if (!distantDictionaryUpdateTimeStamp) {\n throw new Error('No distant dictionaries found');\n }\n\n const dictionariesIdToFetch = Object.entries(\n distantDictionaryUpdateTimeStamp\n ).filter(([dictionaryId, data]) => {\n // If remote doesn't provide updatedAt, fetch to be safe\n if (!data.updatedAt) return true;\n\n // If no local cache exists, fetch\n const local: Dictionary | undefined = remoteDictionariesRecord[\n data.key\n ]?.find((dictionary) => dictionary.id === dictionaryId);\n if (!local) return true;\n\n const localUpdatedAtRaw = (local as any)?.updatedAt as\n | number\n | string\n | undefined;\n\n const localUpdatedAt =\n typeof localUpdatedAtRaw === 'number'\n ? localUpdatedAtRaw\n : localUpdatedAtRaw\n ? new Date(localUpdatedAtRaw).getTime()\n : undefined;\n\n // If local timestamp missing or older than remote, fetch\n if (typeof localUpdatedAt !== 'number') return true;\n\n return data.updatedAt > localUpdatedAt;\n });\n\n const flatRemoteDictionariesRecord: DictionaryAPI[] = Object.values(\n remoteDictionariesRecord\n ).flat();\n\n const cachedDictionaries: Dictionary[] =\n flatRemoteDictionariesRecord.filter((dictionary) => {\n const remoteUpdatedAt = dictionary.id\n ? distantDictionaryUpdateTimeStamp[dictionary.id]?.updatedAt\n : undefined;\n\n const localUpdatedAtRaw = dictionary.updatedAt;\n\n const localUpdatedAt =\n typeof localUpdatedAtRaw === 'number'\n ? localUpdatedAtRaw\n : localUpdatedAtRaw\n ? new Date(localUpdatedAtRaw).getTime()\n : undefined;\n\n // Consider as cached/imported when local exists and is up-to-date or newer\n return (\n typeof localUpdatedAt === 'number' &&\n typeof remoteUpdatedAt === 'number' &&\n localUpdatedAt >= remoteUpdatedAt\n );\n });\n\n // Report cached as already imported\n if (cachedDictionaries.length > 0) {\n onStatusUpdate?.(\n cachedDictionaries.map((dictionary) => ({\n dictionaryKey: dictionary.key,\n type: 'remote',\n status: 'imported',\n }))\n );\n }\n\n // Several qualified dictionaries can share a key, so keys are deduplicated:\n // one request per key already returns all of its siblings.\n const orderedDistantDictionaryKeys = [\n ...new Set(dictionariesIdToFetch.map(([, data]) => data.key)),\n ].sort(sortAlphabetically);\n\n // Report pending for keys to be fetched so totals are visible immediately\n if (orderedDistantDictionaryKeys.length > 0) {\n onStatusUpdate?.(\n orderedDistantDictionaryKeys.map((key) => ({\n dictionaryKey: key,\n type: 'remote',\n status: 'pending',\n }))\n );\n }\n\n const distantDictionariesData = await fetchDistantDictionaries(\n {\n dictionaryKeys: orderedDistantDictionaryKeys,\n },\n onStatusUpdate\n );\n\n const distantDictionaries: Dictionary[] = formatDistantDictionaries(\n distantDictionariesData\n );\n\n // Fetching by key also returns the up-to-date siblings of a stale\n // dictionary, so drop the cached copies the remote just sent back.\n const fetchedDictionaryIds = new Set(\n distantDictionaries.map((dictionary) => dictionary.id)\n );\n\n const notRefetchedCachedDictionaries = cachedDictionaries.filter(\n (dictionary) => !fetchedDictionaryIds.has(dictionary.id)\n );\n\n return [...notRefetchedCachedDictionaries, ...distantDictionaries];\n } catch (error) {\n options?.onError?.(error as Error);\n return [];\n } finally {\n options?.onStopRemoteCheck?.();\n }\n};\n"],"mappings":";;;;;;;;AAeA,MAAa,6BACX,iBAEA,aAAa,KAAK,UAAU;CAC1B,GAAG;CACH,SAAS,GAAG,KAAK,IAAI,YAAY,KAAK;CACtC,UAAU;AACZ,EAAE;AAEJ,MAAa,yBAAyB,OACpC,gBAAgB,iBAAiB,GACjC,gBACA,YAK0B;CAC1B,MAAM,EAAE,WAAW;CACnB,MAAM,2BAA2B,sBAAsB,aAAa;CAIpE,IAAI,CAF0B,QAAQ,OAAO,YAAY,OAAO,YAEvC,GAAG,OAAO,CAAC;CAEpC,IAAI;EACF,SAAS,qBAAqB;EAQ9B,MAAM,oCAGK,MATQ,mBAAmB,kBAAkB,aAAa,CAIpD,CAAC,CAAC,+BAA+B,EAKd,CAAC;EAErC,IAAI,CAAC,kCACH,MAAM,IAAI,MAAM,+BAA+B;EAGjD,MAAM,wBAAwB,OAAO,QACnC,gCACF,CAAC,CAAC,QAAQ,CAAC,cAAc,UAAU;GAEjC,IAAI,CAAC,KAAK,WAAW,OAAO;GAG5B,MAAM,QAAgC,yBACpC,KAAK,IACN,EAAE,MAAM,eAAe,WAAW,OAAO,YAAY;GACtD,IAAI,CAAC,OAAO,OAAO;GAEnB,MAAM,oBAAqB,OAAe;GAK1C,MAAM,iBACJ,OAAO,sBAAsB,WACzB,oBACA,oBACE,IAAI,KAAK,iBAAiB,CAAC,CAAC,QAAQ,IACpC;GAGR,IAAI,OAAO,mBAAmB,UAAU,OAAO;GAE/C,OAAO,KAAK,YAAY;EAC1B,CAAC;EAMD,MAAM,qBAJgD,OAAO,OAC3D,wBACF,CAAC,CAAC,KAG2B,CAAC,CAAC,QAAQ,eAAe;GAClD,MAAM,kBAAkB,WAAW,KAC/B,iCAAiC,WAAW,GAAG,EAAE,YACjD;GAEJ,MAAM,oBAAoB,WAAW;GAErC,MAAM,iBACJ,OAAO,sBAAsB,WACzB,oBACA,oBACE,IAAI,KAAK,iBAAiB,CAAC,CAAC,QAAQ,IACpC;GAGR,OACE,OAAO,mBAAmB,YAC1B,OAAO,oBAAoB,YAC3B,kBAAkB;EAEtB,CAAC;EAGH,IAAI,mBAAmB,SAAS,GAC9B,iBACE,mBAAmB,KAAK,gBAAgB;GACtC,eAAe,WAAW;GAC1B,MAAM;GACN,QAAQ;EACV,EAAE,CACJ;EAKF,MAAM,+BAA+B,CACnC,GAAG,IAAI,IAAI,sBAAsB,KAAK,GAAG,UAAU,KAAK,GAAG,CAAC,CAC9D,CAAC,CAAC,KAAK,kBAAkB;EAGzB,IAAI,6BAA6B,SAAS,GACxC,iBACE,6BAA6B,KAAK,SAAS;GACzC,eAAe;GACf,MAAM;GACN,QAAQ;EACV,EAAE,CACJ;EAGF,MAAM,0BAA0B,MAAM,yBACpC,EACE,gBAAgB,6BAClB,GACA,cACF;EAEA,MAAM,sBAAoC,0BACxC,uBACF;EAIA,MAAM,uBAAuB,IAAI,IAC/B,oBAAoB,KAAK,eAAe,WAAW,EAAE,CACvD;EAMA,OAAO,CAAC,GAJ+B,mBAAmB,QACvD,eAAe,CAAC,qBAAqB,IAAI,WAAW,EAAE,CAGjB,GAAG,GAAG,mBAAmB;CACnE,SAAS,OAAO;EACd,SAAS,UAAU,KAAc;EACjC,OAAO,CAAC;CACV,UAAU;EACR,SAAS,oBAAoB;CAC/B;AACF"}
|
|
@@ -106,7 +106,7 @@ var DictionariesLogger = class {
|
|
|
106
106
|
}
|
|
107
107
|
render() {
|
|
108
108
|
const { localTotal, localDone, remoteTotal, remoteDone, pluginTotal, pluginDone } = this.computeProgress();
|
|
109
|
-
const frame = this.spinnerFrames[this.spinnerIndex];
|
|
109
|
+
const frame = this.spinnerFrames[this.spinnerIndex] ?? "";
|
|
110
110
|
const clock = colorize(frame, ANSIColors.BLUE);
|
|
111
111
|
const lines = [];
|
|
112
112
|
const isLocalDone = localDone === localTotal;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"log.mjs","names":[],"sources":["../../../src/loadDictionaries/log.ts"],"sourcesContent":["import { log } from '@intlayer/config/built';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport {\n colorize,\n getPrefix,\n spinnerFrames,\n v,\n x,\n} from '@intlayer/config/logger';\nimport { extractErrorMessage } from '@intlayer/config/utils';\nimport type { DictionariesStatus } from './loadDictionaries';\n\nexport class DictionariesLogger {\n private statuses: DictionariesStatus[] = [];\n private spinnerTimer: NodeJS.Timeout | null = null;\n private spinnerIndex = 0;\n private renderedLines = 0;\n private readonly spinnerFrames = spinnerFrames;\n private isFinished = false;\n private readonly prefix: string;\n private lastRenderedState: string = '';\n private remoteCheckInProgress = false;\n private expectRemote = false;\n private remoteError: string | undefined;\n private pluginTotal = 0;\n private pluginDone = 0;\n private pluginNames: string[] = [];\n private pluginError: string | undefined;\n\n constructor() {\n this.prefix = getPrefix(log?.prefix) ?? '';\n }\n\n setExpectRemote(expect: boolean) {\n this.expectRemote = expect;\n }\n\n startRemoteCheck() {\n if (this.isFinished) return;\n this.remoteCheckInProgress = true;\n this.startSpinner();\n this.render();\n }\n\n stopRemoteCheck() {\n this.remoteCheckInProgress = false;\n }\n\n update(newStatuses: DictionariesStatus[]) {\n if (this.isFinished) return;\n for (const status of newStatuses) {\n const index = this.statuses.findIndex(\n (s) =>\n s.dictionaryKey === status.dictionaryKey && s.type === status.type\n );\n if (index >= 0) {\n this.statuses[index] = status;\n } else {\n this.statuses.push(status);\n }\n }\n\n // If we expect remote fetch later, avoid rendering a local-only line first\n const { remoteTotal } = this.computeProgress();\n if (this.expectRemote && !this.remoteCheckInProgress && remoteTotal === 0) {\n // Do not start spinner or render yet; wait until remote check starts\n return;\n }\n\n this.startSpinner();\n this.render();\n }\n\n finish() {\n this.isFinished = true;\n this.stopSpinner();\n // Render final state and keep it visible\n this.render();\n }\n\n private startSpinner() {\n if (this.spinnerTimer || this.isFinished) return;\n this.spinnerTimer = setInterval(() => {\n this.spinnerIndex = (this.spinnerIndex + 1) % this.spinnerFrames.length;\n this.render();\n }, 100);\n }\n\n private stopSpinner() {\n if (!this.spinnerTimer) return;\n clearInterval(this.spinnerTimer);\n this.spinnerTimer = null;\n }\n\n public setRemoteError = (error?: Error) => {\n this.remoteError = extractErrorMessage(error);\n // Avoid rendering a transient remote-only line while the remote check flag is still true\n // Ensure local + remote are rendered together after a failure\n this.stopRemoteCheck();\n this.render();\n };\n\n /**\n * Register the names of the plugins providing dictionaries.\n * They are displayed next to the plugin progress counters, e.g.\n * `Plugin content: 6/6 (sync-json, load-po)`.\n *\n * @param names - Names of the plugins exposing a `loadDictionaries` hook\n */\n setPluginNames(names: string[]) {\n if (this.isFinished) return;\n // Guard against plugins declared without a name at runtime\n this.pluginNames = names.filter((name) => Boolean(name));\n this.render();\n }\n\n setPluginTotal(total: number) {\n if (this.isFinished) return;\n this.pluginTotal = total;\n if (total > 0) {\n this.startSpinner();\n }\n this.render();\n }\n\n setPluginDone(done: number) {\n if (this.isFinished) return;\n this.pluginDone = done;\n this.render();\n }\n\n /**\n * Flag the plugin line as errored.\n *\n * @param error - Error thrown while loading the plugin dictionaries\n * @param pluginName - Name of the plugin that threw, prefixed to the message\n */\n setPluginError(error?: Error, pluginName?: string) {\n if (this.isFinished) return;\n const errorMessage = extractErrorMessage(error);\n this.pluginError = pluginName\n ? `${pluginName}: ${errorMessage}`\n : errorMessage;\n this.render();\n }\n\n private render() {\n const {\n localTotal,\n localDone,\n remoteTotal,\n remoteDone,\n pluginTotal,\n pluginDone,\n } = this.computeProgress();\n\n const frame = this.spinnerFrames[this.spinnerIndex];\n const clock = colorize(frame, ANSIColors.BLUE);\n const lines: string[] = [];\n\n const isLocalDone = localDone === localTotal;\n const isRemoteDone = remoteDone === remoteTotal;\n const isPluginDone = pluginDone === pluginTotal;\n\n const suppressLocalWhileCheckingRemote =\n this.expectRemote && this.remoteCheckInProgress && remoteTotal === 0;\n\n if (!suppressLocalWhileCheckingRemote) {\n if (isLocalDone) {\n lines.push(\n `${this.prefix} ${v} Local content: ${colorize(`${localDone}`, ANSIColors.GREEN)}${colorize(`/${localTotal}`, ANSIColors.GREY)}`\n );\n } else {\n lines.push(\n `${this.prefix} ${clock} Local content: ${colorize(`${localDone}`, ANSIColors.BLUE)}${colorize(`/${localTotal}`, ANSIColors.GREY)}`\n );\n }\n }\n\n // Single remote line: show error, check, or progress counts\n if (remoteTotal > 0 || this.remoteCheckInProgress || this.remoteError) {\n if (this.remoteError) {\n lines.push(\n `${this.prefix} ${x} Remote content: ${colorize(\n this.remoteError,\n ANSIColors.RED\n )}`\n );\n } else if (remoteTotal === 0) {\n lines.push(\n `${this.prefix} ${clock} Remote content: ${colorize('Check server', ANSIColors.BLUE)}`\n );\n } else if (isRemoteDone) {\n lines.push(\n `${this.prefix} ${v} Remote content: ${colorize(`${remoteDone}`, ANSIColors.GREEN)}${colorize(`/${remoteTotal}`, ANSIColors.GREY)}`\n );\n } else {\n lines.push(\n `${this.prefix} ${clock} Remote content: ${colorize(`${remoteDone}`, ANSIColors.BLUE)}${colorize(`/${remoteTotal}`, ANSIColors.GREY)}`\n );\n }\n }\n\n // Plugin line: show error or progress counts, suffixed by the plugin names\n if (pluginTotal > 0 || this.pluginError) {\n const pluginNamesSuffix =\n this.pluginNames.length > 0\n ? colorize(` (${this.pluginNames.join(', ')})`, ANSIColors.GREY)\n : '';\n\n if (this.pluginError) {\n lines.push(\n `${this.prefix} ${x} Plugin content: ${colorize(\n this.pluginError,\n ANSIColors.RED\n )}`\n );\n } else if (isPluginDone) {\n lines.push(\n `${this.prefix} ${v} Plugin content: ${colorize(`${pluginDone}`, ANSIColors.GREEN)}${colorize(`/${pluginTotal}`, ANSIColors.GREY)}${pluginNamesSuffix}`\n );\n } else {\n lines.push(\n `${this.prefix} ${clock} Plugin content: ${colorize(`${pluginDone}`, ANSIColors.BLUE)}${colorize(`/${pluginTotal}`, ANSIColors.GREY)}${pluginNamesSuffix}`\n );\n }\n }\n\n // Check if the state has changed to avoid duplicate rendering\n const currentState = lines.join('\\n');\n if (currentState === this.lastRenderedState) {\n return;\n }\n this.lastRenderedState = currentState;\n\n if (this.renderedLines > 0) {\n process.stdout.write(`\\x1b[${this.renderedLines}F`);\n }\n\n const totalLinesToClear = Math.max(this.renderedLines, lines.length);\n for (let i = 0; i < totalLinesToClear; i++) {\n process.stdout.write('\\x1b[2K');\n const line = lines[i];\n if (line !== undefined) {\n process.stdout.write(line);\n }\n process.stdout.write('\\n');\n }\n\n this.renderedLines = lines.length;\n }\n\n private computeProgress() {\n const localKeys = new Set(\n this.statuses\n .filter((s) => s.type === 'local')\n .map((s) => s.dictionaryKey)\n );\n\n const localDoneKeys = new Set(\n this.statuses\n .filter(\n (s) =>\n s.type === 'local' && (s.status === 'built' || s.status === 'error')\n )\n .map((s) => s.dictionaryKey)\n );\n\n const remoteKeys = new Set(\n this.statuses\n .filter((s) => s.type === 'remote')\n .map((s) => s.dictionaryKey)\n );\n\n const remoteDoneKeys = new Set(\n this.statuses\n .filter(\n (s) =>\n s.type === 'remote' &&\n (s.status === 'fetched' ||\n s.status === 'imported' ||\n s.status === 'error')\n )\n .map((s) => s.dictionaryKey)\n );\n\n return {\n localTotal: localKeys.size,\n localDone: localDoneKeys.size,\n remoteTotal: remoteKeys.size,\n remoteDone: remoteDoneKeys.size,\n pluginTotal: this.pluginTotal,\n pluginDone: this.pluginDone,\n } as const;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"log.mjs","names":[],"sources":["../../../src/loadDictionaries/log.ts"],"sourcesContent":["import { log } from '@intlayer/config/built';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport {\n colorize,\n getPrefix,\n spinnerFrames,\n v,\n x,\n} from '@intlayer/config/logger';\nimport { extractErrorMessage } from '@intlayer/config/utils';\nimport type { DictionariesStatus } from './loadDictionaries';\n\nexport class DictionariesLogger {\n private statuses: DictionariesStatus[] = [];\n private spinnerTimer: NodeJS.Timeout | null = null;\n private spinnerIndex = 0;\n private renderedLines = 0;\n private readonly spinnerFrames = spinnerFrames;\n private isFinished = false;\n private readonly prefix: string;\n private lastRenderedState: string = '';\n private remoteCheckInProgress = false;\n private expectRemote = false;\n private remoteError: string | undefined;\n private pluginTotal = 0;\n private pluginDone = 0;\n private pluginNames: string[] = [];\n private pluginError: string | undefined;\n\n constructor() {\n this.prefix = getPrefix(log?.prefix) ?? '';\n }\n\n setExpectRemote(expect: boolean) {\n this.expectRemote = expect;\n }\n\n startRemoteCheck() {\n if (this.isFinished) return;\n this.remoteCheckInProgress = true;\n this.startSpinner();\n this.render();\n }\n\n stopRemoteCheck() {\n this.remoteCheckInProgress = false;\n }\n\n update(newStatuses: DictionariesStatus[]) {\n if (this.isFinished) return;\n for (const status of newStatuses) {\n const index = this.statuses.findIndex(\n (s) =>\n s.dictionaryKey === status.dictionaryKey && s.type === status.type\n );\n if (index >= 0) {\n this.statuses[index] = status;\n } else {\n this.statuses.push(status);\n }\n }\n\n // If we expect remote fetch later, avoid rendering a local-only line first\n const { remoteTotal } = this.computeProgress();\n if (this.expectRemote && !this.remoteCheckInProgress && remoteTotal === 0) {\n // Do not start spinner or render yet; wait until remote check starts\n return;\n }\n\n this.startSpinner();\n this.render();\n }\n\n finish() {\n this.isFinished = true;\n this.stopSpinner();\n // Render final state and keep it visible\n this.render();\n }\n\n private startSpinner() {\n if (this.spinnerTimer || this.isFinished) return;\n this.spinnerTimer = setInterval(() => {\n this.spinnerIndex = (this.spinnerIndex + 1) % this.spinnerFrames.length;\n this.render();\n }, 100);\n }\n\n private stopSpinner() {\n if (!this.spinnerTimer) return;\n clearInterval(this.spinnerTimer);\n this.spinnerTimer = null;\n }\n\n public setRemoteError = (error?: Error) => {\n this.remoteError = extractErrorMessage(error);\n // Avoid rendering a transient remote-only line while the remote check flag is still true\n // Ensure local + remote are rendered together after a failure\n this.stopRemoteCheck();\n this.render();\n };\n\n /**\n * Register the names of the plugins providing dictionaries.\n * They are displayed next to the plugin progress counters, e.g.\n * `Plugin content: 6/6 (sync-json, load-po)`.\n *\n * @param names - Names of the plugins exposing a `loadDictionaries` hook\n */\n setPluginNames(names: string[]) {\n if (this.isFinished) return;\n // Guard against plugins declared without a name at runtime\n this.pluginNames = names.filter((name) => Boolean(name));\n this.render();\n }\n\n setPluginTotal(total: number) {\n if (this.isFinished) return;\n this.pluginTotal = total;\n if (total > 0) {\n this.startSpinner();\n }\n this.render();\n }\n\n setPluginDone(done: number) {\n if (this.isFinished) return;\n this.pluginDone = done;\n this.render();\n }\n\n /**\n * Flag the plugin line as errored.\n *\n * @param error - Error thrown while loading the plugin dictionaries\n * @param pluginName - Name of the plugin that threw, prefixed to the message\n */\n setPluginError(error?: Error, pluginName?: string) {\n if (this.isFinished) return;\n const errorMessage = extractErrorMessage(error);\n this.pluginError = pluginName\n ? `${pluginName}: ${errorMessage}`\n : errorMessage;\n this.render();\n }\n\n private render() {\n const {\n localTotal,\n localDone,\n remoteTotal,\n remoteDone,\n pluginTotal,\n pluginDone,\n } = this.computeProgress();\n\n const frame = this.spinnerFrames[this.spinnerIndex] ?? '';\n const clock = colorize(frame, ANSIColors.BLUE);\n const lines: string[] = [];\n\n const isLocalDone = localDone === localTotal;\n const isRemoteDone = remoteDone === remoteTotal;\n const isPluginDone = pluginDone === pluginTotal;\n\n const suppressLocalWhileCheckingRemote =\n this.expectRemote && this.remoteCheckInProgress && remoteTotal === 0;\n\n if (!suppressLocalWhileCheckingRemote) {\n if (isLocalDone) {\n lines.push(\n `${this.prefix} ${v} Local content: ${colorize(`${localDone}`, ANSIColors.GREEN)}${colorize(`/${localTotal}`, ANSIColors.GREY)}`\n );\n } else {\n lines.push(\n `${this.prefix} ${clock} Local content: ${colorize(`${localDone}`, ANSIColors.BLUE)}${colorize(`/${localTotal}`, ANSIColors.GREY)}`\n );\n }\n }\n\n // Single remote line: show error, check, or progress counts\n if (remoteTotal > 0 || this.remoteCheckInProgress || this.remoteError) {\n if (this.remoteError) {\n lines.push(\n `${this.prefix} ${x} Remote content: ${colorize(\n this.remoteError,\n ANSIColors.RED\n )}`\n );\n } else if (remoteTotal === 0) {\n lines.push(\n `${this.prefix} ${clock} Remote content: ${colorize('Check server', ANSIColors.BLUE)}`\n );\n } else if (isRemoteDone) {\n lines.push(\n `${this.prefix} ${v} Remote content: ${colorize(`${remoteDone}`, ANSIColors.GREEN)}${colorize(`/${remoteTotal}`, ANSIColors.GREY)}`\n );\n } else {\n lines.push(\n `${this.prefix} ${clock} Remote content: ${colorize(`${remoteDone}`, ANSIColors.BLUE)}${colorize(`/${remoteTotal}`, ANSIColors.GREY)}`\n );\n }\n }\n\n // Plugin line: show error or progress counts, suffixed by the plugin names\n if (pluginTotal > 0 || this.pluginError) {\n const pluginNamesSuffix =\n this.pluginNames.length > 0\n ? colorize(` (${this.pluginNames.join(', ')})`, ANSIColors.GREY)\n : '';\n\n if (this.pluginError) {\n lines.push(\n `${this.prefix} ${x} Plugin content: ${colorize(\n this.pluginError,\n ANSIColors.RED\n )}`\n );\n } else if (isPluginDone) {\n lines.push(\n `${this.prefix} ${v} Plugin content: ${colorize(`${pluginDone}`, ANSIColors.GREEN)}${colorize(`/${pluginTotal}`, ANSIColors.GREY)}${pluginNamesSuffix}`\n );\n } else {\n lines.push(\n `${this.prefix} ${clock} Plugin content: ${colorize(`${pluginDone}`, ANSIColors.BLUE)}${colorize(`/${pluginTotal}`, ANSIColors.GREY)}${pluginNamesSuffix}`\n );\n }\n }\n\n // Check if the state has changed to avoid duplicate rendering\n const currentState = lines.join('\\n');\n if (currentState === this.lastRenderedState) {\n return;\n }\n this.lastRenderedState = currentState;\n\n if (this.renderedLines > 0) {\n process.stdout.write(`\\x1b[${this.renderedLines}F`);\n }\n\n const totalLinesToClear = Math.max(this.renderedLines, lines.length);\n for (let i = 0; i < totalLinesToClear; i++) {\n process.stdout.write('\\x1b[2K');\n const line = lines[i];\n if (line !== undefined) {\n process.stdout.write(line);\n }\n process.stdout.write('\\n');\n }\n\n this.renderedLines = lines.length;\n }\n\n private computeProgress() {\n const localKeys = new Set(\n this.statuses\n .filter((s) => s.type === 'local')\n .map((s) => s.dictionaryKey)\n );\n\n const localDoneKeys = new Set(\n this.statuses\n .filter(\n (s) =>\n s.type === 'local' && (s.status === 'built' || s.status === 'error')\n )\n .map((s) => s.dictionaryKey)\n );\n\n const remoteKeys = new Set(\n this.statuses\n .filter((s) => s.type === 'remote')\n .map((s) => s.dictionaryKey)\n );\n\n const remoteDoneKeys = new Set(\n this.statuses\n .filter(\n (s) =>\n s.type === 'remote' &&\n (s.status === 'fetched' ||\n s.status === 'imported' ||\n s.status === 'error')\n )\n .map((s) => s.dictionaryKey)\n );\n\n return {\n localTotal: localKeys.size,\n localDone: localDoneKeys.size,\n remoteTotal: remoteKeys.size,\n remoteDone: remoteDoneKeys.size,\n pluginTotal: this.pluginTotal,\n pluginDone: this.pluginDone,\n } as const;\n }\n}\n"],"mappings":"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"}
|
|
@@ -57,16 +57,19 @@ const flattenNativeDiagnosticMessage = (diagnostic, depth = 0) => [`${" ".repea
|
|
|
57
57
|
* export only exposes the version. It spawns the bundled `tsgo` binary, which
|
|
58
58
|
* resolves the closest tsconfig of each file.
|
|
59
59
|
*/
|
|
60
|
-
const collectNativeTypeScriptErrors = (NativeAPI, filePaths, baseDir) => {
|
|
60
|
+
const collectNativeTypeScriptErrors = async (NativeAPI, filePaths, baseDir) => {
|
|
61
61
|
const api = new NativeAPI({ cwd: baseDir });
|
|
62
62
|
try {
|
|
63
|
-
const snapshot = api.updateSnapshot({ openFiles: filePaths });
|
|
63
|
+
const snapshot = await api.updateSnapshot({ openFiles: filePaths });
|
|
64
64
|
try {
|
|
65
|
-
return filePaths.
|
|
66
|
-
const program = snapshot.getDefaultProjectForFile(filePath)?.program;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
return (await Promise.all(filePaths.map(async (filePath) => {
|
|
66
|
+
const program = (await snapshot.getDefaultProjectForFile(filePath))?.program;
|
|
67
|
+
if (!program) return [];
|
|
68
|
+
const sourceFile = await program.getSourceFile(filePath);
|
|
69
|
+
if (!sourceFile) return [];
|
|
70
|
+
const syntacticDiagnostics = await program.getSyntacticDiagnostics(filePath);
|
|
71
|
+
const semanticDiagnostics = await program.getSemanticDiagnostics(filePath);
|
|
72
|
+
return [...syntacticDiagnostics, ...semanticDiagnostics].map((diagnostic) => {
|
|
70
73
|
const { line, character } = sourceFile.getLineAndCharacterOfPosition(diagnostic.pos);
|
|
71
74
|
return {
|
|
72
75
|
fileName: diagnostic.fileName ?? filePath,
|
|
@@ -75,12 +78,12 @@ const collectNativeTypeScriptErrors = (NativeAPI, filePaths, baseDir) => {
|
|
|
75
78
|
message: flattenNativeDiagnosticMessage(diagnostic)
|
|
76
79
|
};
|
|
77
80
|
});
|
|
78
|
-
});
|
|
81
|
+
}))).flat();
|
|
79
82
|
} finally {
|
|
80
|
-
snapshot.dispose();
|
|
83
|
+
await snapshot.dispose();
|
|
81
84
|
}
|
|
82
85
|
} finally {
|
|
83
|
-
api.close();
|
|
86
|
+
await api.close();
|
|
84
87
|
}
|
|
85
88
|
};
|
|
86
89
|
/**
|
|
@@ -98,9 +101,13 @@ const collectTypeScriptErrors = async (filePaths, baseDir) => {
|
|
|
98
101
|
if (classicTypeScript?.createProgram) return collectClassicTypeScriptErrors(classicTypeScript, filePaths, baseDir);
|
|
99
102
|
let nativeModule;
|
|
100
103
|
try {
|
|
101
|
-
nativeModule = await import("typescript/unstable/
|
|
104
|
+
nativeModule = await import("typescript/unstable/async");
|
|
102
105
|
} catch {
|
|
103
|
-
|
|
106
|
+
try {
|
|
107
|
+
nativeModule = await import("typescript/unstable/sync");
|
|
108
|
+
} catch {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
104
111
|
}
|
|
105
112
|
return collectNativeTypeScriptErrors(nativeModule.API, filePaths, baseDir);
|
|
106
113
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logTypeScriptErrors.mjs","names":[],"sources":["../../../src/loadDictionaries/logTypeScriptErrors.ts"],"sourcesContent":["import { dirname, extname, resolve } from 'node:path';\nimport { getAppLogger } from '@intlayer/config/logger';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type {\n API as NativeTypeScriptAPI,\n Diagnostic as NativeTypeScriptDiagnostic,\n} from 'typescript/unstable/sync';\n\n/** Diagnostic of the classic (TS <= 6) compiler API. */\ninterface ClassicTypeScriptDiagnostic {\n file?: {\n fileName: string;\n getLineAndCharacterOfPosition: (position: number) => {\n line: number;\n character: number;\n };\n };\n start?: number;\n messageText: string | { messageText: string };\n}\n\n/** Subset of the classic (TS <= 6) compiler API used here. */\ninterface ClassicTypeScriptCompiler {\n findConfigFile: (\n searchPath: string,\n fileExists: (path: string) => boolean,\n configName?: string\n ) => string | undefined;\n sys: {\n fileExists: (path: string) => boolean;\n readFile: (path: string, encoding?: string) => string | undefined;\n };\n parseConfigFileTextToJson: (\n fileName: string,\n jsonText: string\n ) => { config?: Record<string, unknown>; error?: unknown };\n parseJsonConfigFileContent: (\n json: unknown,\n host: unknown,\n basePath: string\n ) => {\n options: Record<string, unknown>;\n };\n createProgram: (\n rootNames: readonly string[],\n options: Record<string, unknown>\n ) => {\n getSourceFile: (fileName: string) => unknown;\n };\n getPreEmitDiagnostics: (\n program: unknown,\n sourceFile?: unknown\n ) => readonly ClassicTypeScriptDiagnostic[];\n flattenDiagnosticMessageText: (\n diagnostic: string | { messageText: string },\n newLine: string\n ) => string;\n}\n\n/** Root module of `typescript`, possibly wrapped in a CJS `default`. */\ntype ClassicTypeScriptModule = Partial<ClassicTypeScriptCompiler> & {\n default?: Partial<ClassicTypeScriptCompiler>;\n};\n\n/** Engine-agnostic diagnostic, with 0-based line and character. */\ninterface TypeScriptError {\n fileName: string;\n line: number;\n character: number;\n message: string;\n}\n\nconst CHECKABLE_EXTENSIONS = [\n '.ts',\n '.tsx',\n '.js',\n '.jsx',\n '.cjs',\n '.mjs',\n '.json',\n];\n\n/**\n * Checks files with the classic compiler API (`typescript` <= 6), which is\n * exposed from the package root.\n */\nconst collectClassicTypeScriptErrors = (\n typescript: ClassicTypeScriptCompiler,\n filePaths: string[],\n baseDir: string\n): TypeScriptError[] => {\n const configFileName = typescript.findConfigFile(\n baseDir,\n typescript.sys.fileExists,\n 'tsconfig.json'\n );\n\n let compilerOptions: Record<string, unknown> = {\n noEmit: true,\n allowJs: true,\n resolveJsonModule: true,\n };\n\n const configFileText = configFileName\n ? typescript.sys.readFile(configFileName)\n : undefined;\n\n if (configFileName && configFileText) {\n const configJson = typescript.parseConfigFileTextToJson(\n configFileName,\n configFileText\n );\n\n if (!configJson.error) {\n const parsedConfig = typescript.parseJsonConfigFileContent(\n configJson.config,\n typescript.sys,\n dirname(configFileName)\n );\n\n const { incremental, tsBuildInfoFile, ...restOptions } =\n parsedConfig.options;\n\n compilerOptions = { ...compilerOptions, ...restOptions, noEmit: true };\n }\n }\n\n const program = typescript.createProgram(filePaths, compilerOptions);\n\n return filePaths.flatMap((filePath) => {\n const sourceFile = program.getSourceFile(filePath);\n\n if (!sourceFile) return [];\n\n return typescript\n .getPreEmitDiagnostics(program, sourceFile)\n .flatMap((diagnostic) => {\n if (!diagnostic.file || diagnostic.start === undefined) return [];\n\n const { line, character } =\n diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);\n\n return {\n fileName: diagnostic.file.fileName,\n line,\n character,\n message: typescript.flattenDiagnosticMessageText(\n diagnostic.messageText,\n '\\n'\n ),\n };\n });\n });\n};\n\n/** Flattens a native diagnostic message chain, indenting nested messages. */\nconst flattenNativeDiagnosticMessage = (\n diagnostic: NativeTypeScriptDiagnostic,\n depth = 0\n): string =>\n [\n `${' '.repeat(depth)}${diagnostic.text}`,\n ...(diagnostic.messageChain ?? []).map((chainedDiagnostic) =>\n flattenNativeDiagnosticMessage(chainedDiagnostic, depth + 1)\n ),\n ].join('\\n');\n\n/**\n * Checks files with the native API (`typescript` >= 7, Go port), whose root\n * export only exposes the version. It spawns the bundled `tsgo` binary, which\n * resolves the closest tsconfig of each file.\n */\nconst collectNativeTypeScriptErrors = (\n NativeAPI: typeof NativeTypeScriptAPI,\n filePaths: string[],\n baseDir: string\n): TypeScriptError[] => {\n const api = new NativeAPI({ cwd: baseDir });\n\n try {\n const snapshot = api.updateSnapshot({ openFiles: filePaths });\n\n try {\n return filePaths.flatMap((filePath) => {\n const program = snapshot.getDefaultProjectForFile(filePath)?.program;\n const sourceFile = program?.getSourceFile(filePath);\n\n if (!program || !sourceFile) return [];\n\n return [\n ...program.getSyntacticDiagnostics(filePath),\n ...program.getSemanticDiagnostics(filePath),\n ].map((diagnostic) => {\n const { line, character } = sourceFile.getLineAndCharacterOfPosition(\n diagnostic.pos\n );\n\n return {\n fileName: diagnostic.fileName ?? filePath,\n line,\n character,\n message: flattenNativeDiagnosticMessage(diagnostic),\n };\n });\n });\n } finally {\n snapshot.dispose();\n }\n } finally {\n api.close();\n }\n};\n\n/**\n * Resolves the installed TypeScript and returns its errors for the given\n * files, or `undefined` if TypeScript is not installed.\n */\nconst collectTypeScriptErrors = async (\n filePaths: string[],\n baseDir: string\n): Promise<TypeScriptError[] | undefined> => {\n let typescriptModule: ClassicTypeScriptModule;\n\n try {\n // Typed against the installed version, whose root may be version-only (>= 7)\n typescriptModule = (await import(\n 'typescript'\n )) as unknown as ClassicTypeScriptModule;\n } catch {\n return undefined;\n }\n\n const classicTypeScript = typescriptModule.createProgram\n ? typescriptModule\n : typescriptModule.default;\n\n if (classicTypeScript?.createProgram) {\n return collectClassicTypeScriptErrors(\n classicTypeScript as ClassicTypeScriptCompiler,\n filePaths,\n baseDir\n );\n }\n\n let nativeModule: { API: typeof NativeTypeScriptAPI };\n\n try {\n nativeModule = await import('typescript/unstable/sync');\n } catch {\n // Neither the classic nor the native API is available\n return undefined;\n }\n\n return collectNativeTypeScriptErrors(nativeModule.API, filePaths, baseDir);\n};\n\n/**\n * Type-checks content declaration files and logs errors as warnings.\n * Supports both the classic (`typescript` <= 6) and native (>= 7) compilers.\n */\nexport const logTypeScriptErrors = async (\n filePaths: string[],\n configuration: IntlayerConfig\n): Promise<void> => {\n const appLogger = getAppLogger(configuration);\n const baseDir = configuration.system?.baseDir ?? process.cwd();\n\n const filesToCheck = filePaths\n .filter((filePath) => CHECKABLE_EXTENSIONS.includes(extname(filePath)))\n .map((filePath) => resolve(baseDir, filePath));\n\n if (filesToCheck.length === 0) return;\n\n const typeScriptErrors = await collectTypeScriptErrors(filesToCheck, baseDir);\n\n typeScriptErrors?.forEach(({ fileName, line, character, message }) => {\n appLogger(\n `TS Error in ${fileName} (${line + 1},${character + 1}): ${message}`,\n { level: 'warn' }\n );\n });\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"logTypeScriptErrors.mjs","names":[],"sources":["../../../src/loadDictionaries/logTypeScriptErrors.ts"],"sourcesContent":["import { dirname, extname, resolve } from 'node:path';\nimport { getAppLogger } from '@intlayer/config/logger';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type {\n API as NativeTypeScriptAPI,\n Diagnostic as NativeTypeScriptDiagnostic,\n} from 'typescript/unstable/async';\n\n/** Diagnostic of the classic (TS <= 6) compiler API. */\ninterface ClassicTypeScriptDiagnostic {\n file?: {\n fileName: string;\n getLineAndCharacterOfPosition: (position: number) => {\n line: number;\n character: number;\n };\n };\n start?: number;\n messageText: string | { messageText: string };\n}\n\n/** Subset of the classic (TS <= 6) compiler API used here. */\ninterface ClassicTypeScriptCompiler {\n findConfigFile: (\n searchPath: string,\n fileExists: (path: string) => boolean,\n configName?: string\n ) => string | undefined;\n sys: {\n fileExists: (path: string) => boolean;\n readFile: (path: string, encoding?: string) => string | undefined;\n };\n parseConfigFileTextToJson: (\n fileName: string,\n jsonText: string\n ) => { config?: Record<string, unknown>; error?: unknown };\n parseJsonConfigFileContent: (\n json: unknown,\n host: unknown,\n basePath: string\n ) => {\n options: Record<string, unknown>;\n };\n createProgram: (\n rootNames: readonly string[],\n options: Record<string, unknown>\n ) => {\n getSourceFile: (fileName: string) => unknown;\n };\n getPreEmitDiagnostics: (\n program: unknown,\n sourceFile?: unknown\n ) => readonly ClassicTypeScriptDiagnostic[];\n flattenDiagnosticMessageText: (\n diagnostic: string | { messageText: string },\n newLine: string\n ) => string;\n}\n\n/** Root module of `typescript`, possibly wrapped in a CJS `default`. */\ntype ClassicTypeScriptModule = Partial<ClassicTypeScriptCompiler> & {\n default?: Partial<ClassicTypeScriptCompiler>;\n};\n\n/** Engine-agnostic diagnostic, with 0-based line and character. */\ninterface TypeScriptError {\n fileName: string;\n line: number;\n character: number;\n message: string;\n}\n\nconst CHECKABLE_EXTENSIONS = [\n '.ts',\n '.tsx',\n '.js',\n '.jsx',\n '.cjs',\n '.mjs',\n '.json',\n];\n\n/**\n * Checks files with the classic compiler API (`typescript` <= 6), which is\n * exposed from the package root.\n */\nconst collectClassicTypeScriptErrors = (\n typescript: ClassicTypeScriptCompiler,\n filePaths: string[],\n baseDir: string\n): TypeScriptError[] => {\n const configFileName = typescript.findConfigFile(\n baseDir,\n typescript.sys.fileExists,\n 'tsconfig.json'\n );\n\n let compilerOptions: Record<string, unknown> = {\n noEmit: true,\n allowJs: true,\n resolveJsonModule: true,\n };\n\n const configFileText = configFileName\n ? typescript.sys.readFile(configFileName)\n : undefined;\n\n if (configFileName && configFileText) {\n const configJson = typescript.parseConfigFileTextToJson(\n configFileName,\n configFileText\n );\n\n if (!configJson.error) {\n const parsedConfig = typescript.parseJsonConfigFileContent(\n configJson.config,\n typescript.sys,\n dirname(configFileName)\n );\n\n const { incremental, tsBuildInfoFile, ...restOptions } =\n parsedConfig.options;\n\n compilerOptions = { ...compilerOptions, ...restOptions, noEmit: true };\n }\n }\n\n const program = typescript.createProgram(filePaths, compilerOptions);\n\n return filePaths.flatMap((filePath) => {\n const sourceFile = program.getSourceFile(filePath);\n\n if (!sourceFile) return [];\n\n return typescript\n .getPreEmitDiagnostics(program, sourceFile)\n .flatMap((diagnostic) => {\n if (!diagnostic.file || diagnostic.start === undefined) return [];\n\n const { line, character } =\n diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);\n\n return {\n fileName: diagnostic.file.fileName,\n line,\n character,\n message: typescript.flattenDiagnosticMessageText(\n diagnostic.messageText,\n '\\n'\n ),\n };\n });\n });\n};\n\n/** Flattens a native diagnostic message chain, indenting nested messages. */\nconst flattenNativeDiagnosticMessage = (\n diagnostic: NativeTypeScriptDiagnostic,\n depth = 0\n): string =>\n [\n `${' '.repeat(depth)}${diagnostic.text}`,\n ...(diagnostic.messageChain ?? []).map((chainedDiagnostic) =>\n flattenNativeDiagnosticMessage(chainedDiagnostic, depth + 1)\n ),\n ].join('\\n');\n\n/**\n * Checks files with the native API (`typescript` >= 7, Go port), whose root\n * export only exposes the version. It spawns the bundled `tsgo` binary, which\n * resolves the closest tsconfig of each file.\n */\nconst collectNativeTypeScriptErrors = async (\n NativeAPI: typeof NativeTypeScriptAPI,\n filePaths: string[],\n baseDir: string\n): Promise<TypeScriptError[]> => {\n const api = new NativeAPI({ cwd: baseDir });\n\n try {\n const snapshot = await api.updateSnapshot({ openFiles: filePaths });\n\n try {\n const results = await Promise.all(\n filePaths.map(async (filePath) => {\n const defaultProject =\n await snapshot.getDefaultProjectForFile(filePath);\n const program = defaultProject?.program;\n\n if (!program) return [];\n\n const sourceFile = await program.getSourceFile(filePath);\n\n if (!sourceFile) return [];\n\n const syntacticDiagnostics =\n await program.getSyntacticDiagnostics(filePath);\n const semanticDiagnostics =\n await program.getSemanticDiagnostics(filePath);\n\n return [...syntacticDiagnostics, ...semanticDiagnostics].map(\n (diagnostic) => {\n const { line, character } =\n sourceFile.getLineAndCharacterOfPosition(diagnostic.pos);\n\n return {\n fileName: diagnostic.fileName ?? filePath,\n line,\n character,\n message: flattenNativeDiagnosticMessage(diagnostic),\n };\n }\n );\n })\n );\n\n return results.flat();\n } finally {\n await snapshot.dispose();\n }\n } finally {\n await api.close();\n }\n};\n\n/**\n * Resolves the installed TypeScript and returns its errors for the given\n * files, or `undefined` if TypeScript is not installed.\n */\nconst collectTypeScriptErrors = async (\n filePaths: string[],\n baseDir: string\n): Promise<TypeScriptError[] | undefined> => {\n let typescriptModule: ClassicTypeScriptModule;\n\n try {\n // Typed against the installed version, whose root may be version-only (>= 7)\n typescriptModule = (await import(\n 'typescript'\n )) as unknown as ClassicTypeScriptModule;\n } catch {\n return undefined;\n }\n\n const classicTypeScript = typescriptModule.createProgram\n ? typescriptModule\n : typescriptModule.default;\n\n if (classicTypeScript?.createProgram) {\n return collectClassicTypeScriptErrors(\n classicTypeScript as ClassicTypeScriptCompiler,\n filePaths,\n baseDir\n );\n }\n\n let nativeModule: { API: typeof NativeTypeScriptAPI };\n\n try {\n nativeModule = await import('typescript/unstable/async');\n } catch {\n try {\n nativeModule = (await import('typescript/unstable/sync')) as unknown as {\n API: typeof NativeTypeScriptAPI;\n };\n } catch {\n // Neither the classic nor the native API is available\n return undefined;\n }\n }\n\n return collectNativeTypeScriptErrors(nativeModule.API, filePaths, baseDir);\n};\n\n/**\n * Type-checks content declaration files and logs errors as warnings.\n * Supports both the classic (`typescript` <= 6) and native (>= 7) compilers.\n */\nexport const logTypeScriptErrors = async (\n filePaths: string[],\n configuration: IntlayerConfig\n): Promise<void> => {\n const appLogger = getAppLogger(configuration);\n const baseDir = configuration.system?.baseDir ?? process.cwd();\n\n const filesToCheck = filePaths\n .filter((filePath) => CHECKABLE_EXTENSIONS.includes(extname(filePath)))\n .map((filePath) => resolve(baseDir, filePath));\n\n if (filesToCheck.length === 0) return;\n\n const typeScriptErrors = await collectTypeScriptErrors(filesToCheck, baseDir);\n\n typeScriptErrors?.forEach(({ fileName, line, character, message }) => {\n appLogger(\n `TS Error in ${fileName} (${line + 1},${character + 1}): ${message}`,\n { level: 'warn' }\n );\n });\n};\n"],"mappings":"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"}
|
|
@@ -15,18 +15,20 @@ const isLocaleCode = (key) => allLocaleValues.has(key) || /^[a-z]{2}(-[A-Z]{2,4}
|
|
|
15
15
|
/** Find the end index of the value following a `locale:` key. */
|
|
16
16
|
const extractValueEnd = (text, valueStart) => {
|
|
17
17
|
let cursor = valueStart;
|
|
18
|
-
while (cursor < text.length && " \n\r".includes(text[cursor])) cursor++;
|
|
18
|
+
while (cursor < text.length && " \n\r".includes(text[cursor] ?? "")) cursor++;
|
|
19
19
|
if (cursor >= text.length) return valueStart;
|
|
20
20
|
const char = text[cursor];
|
|
21
|
+
if (!char) return valueStart;
|
|
21
22
|
if (char === "{" || char === "[") {
|
|
22
23
|
const endChar = char === "{" ? "}" : "]";
|
|
23
24
|
let depth = 1;
|
|
24
25
|
cursor++;
|
|
25
26
|
while (cursor < text.length && depth > 0) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
else if (
|
|
29
|
-
|
|
27
|
+
const c = text[cursor];
|
|
28
|
+
if (c === char) depth++;
|
|
29
|
+
else if (c === endChar) depth--;
|
|
30
|
+
else if (c === "\"" || c === "`") {
|
|
31
|
+
const quote = c;
|
|
30
32
|
cursor++;
|
|
31
33
|
while (cursor < text.length) {
|
|
32
34
|
if (text[cursor] === "\\") {
|
|
@@ -73,7 +75,7 @@ const analyzeChunkLocaleContent = (text, baseCurrent) => {
|
|
|
73
75
|
let match = regex.exec(text);
|
|
74
76
|
while (match !== null) {
|
|
75
77
|
const locale = match[1] ?? match[2];
|
|
76
|
-
if (isLocaleCode(locale)) {
|
|
78
|
+
if (locale && isLocaleCode(locale)) {
|
|
77
79
|
const valueStart = match.index + match[0].length;
|
|
78
80
|
const valueEnd = extractValueEnd(text, valueStart);
|
|
79
81
|
const valueSize = valueEnd - valueStart;
|
|
@@ -88,12 +90,22 @@ const analyzeChunkLocaleContent = (text, baseCurrent) => {
|
|
|
88
90
|
}
|
|
89
91
|
match = regex.exec(text);
|
|
90
92
|
}
|
|
93
|
+
const languageOf = (candidate) => (candidate.locale.split("-")[0] ?? "").toLowerCase();
|
|
91
94
|
const isI18nMatch = (idx) => {
|
|
92
|
-
const
|
|
95
|
+
const currentCandidate = candidates[idx];
|
|
96
|
+
if (!currentCandidate) return false;
|
|
97
|
+
const base = languageOf(currentCandidate);
|
|
98
|
+
let hasOtherLanguage = false;
|
|
99
|
+
let hasCurrentLanguage = base === baseCurrent;
|
|
93
100
|
for (let j = 0; j < candidates.length; j++) {
|
|
94
101
|
if (j === idx) continue;
|
|
95
|
-
|
|
96
|
-
if (
|
|
102
|
+
const neighbor = candidates[j];
|
|
103
|
+
if (!neighbor) continue;
|
|
104
|
+
if (Math.abs(neighbor.position - currentCandidate.position) > LOCALE_CLUSTER_WINDOW) continue;
|
|
105
|
+
const neighborLanguage = languageOf(neighbor);
|
|
106
|
+
if (neighborLanguage !== base) hasOtherLanguage = true;
|
|
107
|
+
if (neighborLanguage === baseCurrent) hasCurrentLanguage = true;
|
|
108
|
+
if (hasOtherLanguage && hasCurrentLanguage) return true;
|
|
97
109
|
}
|
|
98
110
|
return false;
|
|
99
111
|
};
|
|
@@ -102,9 +114,11 @@ const analyzeChunkLocaleContent = (text, baseCurrent) => {
|
|
|
102
114
|
let dictionariesFound = 0;
|
|
103
115
|
for (let i = 0; i < candidates.length; i++) {
|
|
104
116
|
if (!isI18nMatch(i)) continue;
|
|
105
|
-
const
|
|
117
|
+
const candidate = candidates[i];
|
|
118
|
+
if (!candidate) continue;
|
|
119
|
+
const { locale, valueSize } = candidate;
|
|
106
120
|
dictionariesFound++;
|
|
107
|
-
if (locale.split("-")[0].toLowerCase() === baseCurrent) usedLocaleSize += valueSize;
|
|
121
|
+
if ((locale.split("-")[0] ?? "").toLowerCase() === baseCurrent) usedLocaleSize += valueSize;
|
|
108
122
|
else unusedLocaleSize += valueSize;
|
|
109
123
|
}
|
|
110
124
|
return {
|
|
@@ -140,7 +154,7 @@ const analyzeBundleContent = (chunks, htmlContent, currentLocale, totalPageSize)
|
|
|
140
154
|
pageStrings.forEach((text) => {
|
|
141
155
|
renderedContentSize += byteLength(text);
|
|
142
156
|
});
|
|
143
|
-
const baseCurrent = currentLocale.split("-")[0].toLowerCase();
|
|
157
|
+
const baseCurrent = (currentLocale.split("-")[0] ?? "").toLowerCase();
|
|
144
158
|
const mainBundleChunks = [];
|
|
145
159
|
const lazyBundleChunks = [];
|
|
146
160
|
for (const chunk of chunks) {
|