gt 2.17.2 → 2.17.3
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/CHANGELOG.md +43 -0
- package/dist/cli/commands/translate.js +1 -1
- package/dist/cli/commands/translate.js.map +1 -1
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/generated/version.js.map +1 -1
- package/dist/utils/addExplicitAnchorIds.d.ts +21 -1
- package/dist/utils/addExplicitAnchorIds.js +130 -198
- package/dist/utils/addExplicitAnchorIds.js.map +1 -1
- package/dist/utils/localizeRelativeAssets.js +6 -3
- package/dist/utils/localizeRelativeAssets.js.map +1 -1
- package/dist/utils/localizeStaticImports.js +2 -7
- package/dist/utils/localizeStaticImports.js.map +1 -1
- package/dist/utils/localizeStaticUrls.js +6 -4
- package/dist/utils/localizeStaticUrls.js.map +1 -1
- package/dist/utils/mdxAnchorSyntax.d.ts +30 -0
- package/dist/utils/mdxAnchorSyntax.js +113 -0
- package/dist/utils/mdxAnchorSyntax.js.map +1 -0
- package/dist/utils/validateMdx.d.ts +5 -0
- package/dist/utils/validateMdx.js +7 -7
- package/dist/utils/validateMdx.js.map +1 -1
- package/package.json +5 -5
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { unified } from "unified";
|
|
2
|
+
import remarkParse from "remark-parse";
|
|
3
|
+
import remarkMdx from "remark-mdx";
|
|
4
|
+
import remarkFrontmatter from "remark-frontmatter";
|
|
5
|
+
//#region src/utils/mdxAnchorSyntax.ts
|
|
6
|
+
/**
|
|
7
|
+
* Custom heading IDs (`## Heading {#id}`) break MDX parsing: remark-mdx passes
|
|
8
|
+
* `{#id}` to acorn. Escaping the braces makes the document parse without
|
|
9
|
+
* changing its line count, so mdast line positions still map 1:1 (columns do
|
|
10
|
+
* not). https://mintlify.com/docs/create/headers#custom-heading-ids
|
|
11
|
+
*/
|
|
12
|
+
/** A heading line ending in an unescaped `{#id}`. */
|
|
13
|
+
const UNESCAPED_ANCHOR = /^([ \t]*#{1,6}[ \t]+.*?)[ \t]*\{#([A-Za-z0-9_-]+)\}[ \t]*$/;
|
|
14
|
+
/** A heading line ending in an escaped `\{#id\}`. */
|
|
15
|
+
const ESCAPED_ANCHOR = /^([ \t]*#{1,6}[ \t]+.*?)[ \t]*\\\{#([A-Za-z0-9_-]+)\\\}[ \t]*$/;
|
|
16
|
+
/** Matches an opening or closing fenced-code-block marker. */
|
|
17
|
+
const CODE_FENCE = /^\s*(`{3,}|~{3,})/;
|
|
18
|
+
/** Returns a predicate telling whether a line sits outside a code fence. */
|
|
19
|
+
function createFenceTracker() {
|
|
20
|
+
let inFence = false;
|
|
21
|
+
let fence = null;
|
|
22
|
+
return (line) => {
|
|
23
|
+
const match = line.match(CODE_FENCE);
|
|
24
|
+
if (!match) return !inFence;
|
|
25
|
+
const marker = match[1];
|
|
26
|
+
if (!inFence) {
|
|
27
|
+
inFence = true;
|
|
28
|
+
fence = marker;
|
|
29
|
+
} else if (fence && marker[0] === fence[0] && marker.length >= fence.length) {
|
|
30
|
+
inFence = false;
|
|
31
|
+
fence = null;
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/** Maps over a document's lines, leaving fenced code blocks untouched. */
|
|
37
|
+
function mapLinesOutsideCodeFences(content, mapLine) {
|
|
38
|
+
const isContent = createFenceTracker();
|
|
39
|
+
return content.split("\n").map((line) => isContent(line) ? mapLine(line) : line).join("\n");
|
|
40
|
+
}
|
|
41
|
+
/** Visits a document's lines with their 0-based index, skipping code fences. */
|
|
42
|
+
function forEachLineOutsideCodeFences(content, visitLine) {
|
|
43
|
+
const isContent = createFenceTracker();
|
|
44
|
+
content.split("\n").forEach((line, index) => {
|
|
45
|
+
if (isContent(line)) visitLine(line, index);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Escapes `## Heading {#id}` to `## Heading \{#id\}` so MDX can parse it.
|
|
50
|
+
*/
|
|
51
|
+
function neutralizeAnchorIds(content) {
|
|
52
|
+
let changed = false;
|
|
53
|
+
return {
|
|
54
|
+
content: mapLinesOutsideCodeFences(content, (line) => {
|
|
55
|
+
const match = line.match(UNESCAPED_ANCHOR);
|
|
56
|
+
if (!match) return line;
|
|
57
|
+
changed = true;
|
|
58
|
+
return `${match[1]} \\{#${match[2]}\\}`;
|
|
59
|
+
}),
|
|
60
|
+
changed
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Unescapes anchors back to `{#id}`; Mintlify renders `\{` as a literal brace.
|
|
65
|
+
*/
|
|
66
|
+
function restoreAnchorIds(content) {
|
|
67
|
+
return mapLinesOutsideCodeFences(content, (line) => {
|
|
68
|
+
const match = line.match(ESCAPED_ANCHOR);
|
|
69
|
+
if (!match) return line;
|
|
70
|
+
return `${match[1]} {#${match[2]}}`;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/** Builds the parse-only MDX processor shared by every parse site. */
|
|
74
|
+
function createMdxParseProcessor() {
|
|
75
|
+
return unified().use(remarkParse).use(remarkFrontmatter, ["yaml", "toml"]).use(remarkMdx);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Parses MDX, retrying with anchors escaped; rethrows any other parse error.
|
|
79
|
+
*/
|
|
80
|
+
function parseMdxTolerantly(content) {
|
|
81
|
+
const processor = createMdxParseProcessor();
|
|
82
|
+
try {
|
|
83
|
+
return processor.runSync(processor.parse(content));
|
|
84
|
+
} catch (error) {
|
|
85
|
+
const { content: neutralized, changed } = neutralizeAnchorIds(content);
|
|
86
|
+
if (!changed) throw error;
|
|
87
|
+
return processor.runSync(processor.parse(neutralized));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Parses MDX for a pass that stringifies the tree back out. `neutralized` tells
|
|
92
|
+
* the caller whether to run {@link restoreAnchorIds} on its output.
|
|
93
|
+
*/
|
|
94
|
+
function parseMdxForRoundTrip(content) {
|
|
95
|
+
const processor = createMdxParseProcessor();
|
|
96
|
+
try {
|
|
97
|
+
return {
|
|
98
|
+
ast: processor.runSync(processor.parse(content)),
|
|
99
|
+
neutralized: false
|
|
100
|
+
};
|
|
101
|
+
} catch (error) {
|
|
102
|
+
const { content: neutralized, changed } = neutralizeAnchorIds(content);
|
|
103
|
+
if (!changed) throw error;
|
|
104
|
+
return {
|
|
105
|
+
ast: processor.runSync(processor.parse(neutralized)),
|
|
106
|
+
neutralized: true
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
//#endregion
|
|
111
|
+
export { createMdxParseProcessor, forEachLineOutsideCodeFences, mapLinesOutsideCodeFences, neutralizeAnchorIds, parseMdxForRoundTrip, parseMdxTolerantly, restoreAnchorIds };
|
|
112
|
+
|
|
113
|
+
//# sourceMappingURL=mdxAnchorSyntax.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mdxAnchorSyntax.js","names":[],"sources":["../../src/utils/mdxAnchorSyntax.ts"],"sourcesContent":["import { unified } from 'unified';\nimport remarkParse from 'remark-parse';\nimport remarkMdx from 'remark-mdx';\nimport remarkFrontmatter from 'remark-frontmatter';\nimport type { Root } from 'mdast';\n\n/**\n * Custom heading IDs (`## Heading {#id}`) break MDX parsing: remark-mdx passes\n * `{#id}` to acorn. Escaping the braces makes the document parse without\n * changing its line count, so mdast line positions still map 1:1 (columns do\n * not). https://mintlify.com/docs/create/headers#custom-heading-ids\n */\n\n/** A heading line ending in an unescaped `{#id}`. */\nconst UNESCAPED_ANCHOR =\n /^([ \\t]*#{1,6}[ \\t]+.*?)[ \\t]*\\{#([A-Za-z0-9_-]+)\\}[ \\t]*$/;\n\n/** A heading line ending in an escaped `\\{#id\\}`. */\nconst ESCAPED_ANCHOR =\n /^([ \\t]*#{1,6}[ \\t]+.*?)[ \\t]*\\\\\\{#([A-Za-z0-9_-]+)\\\\\\}[ \\t]*$/;\n\n/** Matches an opening or closing fenced-code-block marker. */\nconst CODE_FENCE = /^\\s*(`{3,}|~{3,})/;\n\n/** Returns a predicate telling whether a line sits outside a code fence. */\nfunction createFenceTracker(): (line: string) => boolean {\n let inFence = false;\n let fence: string | null = null;\n\n return (line: string): boolean => {\n const match = line.match(CODE_FENCE);\n if (!match) return !inFence;\n\n const marker = match[1];\n if (!inFence) {\n inFence = true;\n fence = marker;\n } else if (\n fence &&\n marker[0] === fence[0] &&\n marker.length >= fence.length\n ) {\n inFence = false;\n fence = null;\n }\n return false;\n };\n}\n\n/** Maps over a document's lines, leaving fenced code blocks untouched. */\nexport function mapLinesOutsideCodeFences(\n content: string,\n mapLine: (line: string) => string\n): string {\n const isContent = createFenceTracker();\n return content\n .split('\\n')\n .map((line) => (isContent(line) ? mapLine(line) : line))\n .join('\\n');\n}\n\n/** Visits a document's lines with their 0-based index, skipping code fences. */\nexport function forEachLineOutsideCodeFences(\n content: string,\n visitLine: (line: string, index: number) => void\n): void {\n const isContent = createFenceTracker();\n content.split('\\n').forEach((line, index) => {\n if (isContent(line)) visitLine(line, index);\n });\n}\n\n/**\n * Escapes `## Heading {#id}` to `## Heading \\{#id\\}` so MDX can parse it.\n */\nexport function neutralizeAnchorIds(content: string): {\n content: string;\n changed: boolean;\n} {\n let changed = false;\n\n const next = mapLinesOutsideCodeFences(content, (line) => {\n const match = line.match(UNESCAPED_ANCHOR);\n if (!match) return line;\n changed = true;\n return `${match[1]} \\\\{#${match[2]}\\\\}`;\n });\n\n return { content: next, changed };\n}\n\n/**\n * Unescapes anchors back to `{#id}`; Mintlify renders `\\{` as a literal brace.\n */\nexport function restoreAnchorIds(content: string): string {\n return mapLinesOutsideCodeFences(content, (line) => {\n const match = line.match(ESCAPED_ANCHOR);\n if (!match) return line;\n return `${match[1]} {#${match[2]}}`;\n });\n}\n\n/** Builds the parse-only MDX processor shared by every parse site. */\nexport function createMdxParseProcessor() {\n return unified()\n .use(remarkParse)\n .use(remarkFrontmatter, ['yaml', 'toml'])\n .use(remarkMdx);\n}\n\n/**\n * Parses MDX, retrying with anchors escaped; rethrows any other parse error.\n */\nexport function parseMdxTolerantly(content: string): Root {\n const processor = createMdxParseProcessor();\n try {\n return processor.runSync(processor.parse(content)) as Root;\n } catch (error) {\n const { content: neutralized, changed } = neutralizeAnchorIds(content);\n if (!changed) throw error;\n return processor.runSync(processor.parse(neutralized)) as Root;\n }\n}\n\n/**\n * Parses MDX for a pass that stringifies the tree back out. `neutralized` tells\n * the caller whether to run {@link restoreAnchorIds} on its output.\n */\nexport function parseMdxForRoundTrip(content: string): {\n ast: Root;\n neutralized: boolean;\n} {\n const processor = createMdxParseProcessor();\n try {\n return {\n ast: processor.runSync(processor.parse(content)) as Root,\n neutralized: false,\n };\n } catch (error) {\n const { content: neutralized, changed } = neutralizeAnchorIds(content);\n if (!changed) throw error;\n return {\n ast: processor.runSync(processor.parse(neutralized)) as Root,\n neutralized: true,\n };\n }\n}\n"],"mappings":";;;;;;;;;;;;AAcA,MAAM,mBACJ;;AAGF,MAAM,iBACJ;;AAGF,MAAM,aAAa;;AAGnB,SAAS,qBAAgD;CACvD,IAAI,UAAU;CACd,IAAI,QAAuB;AAE3B,SAAQ,SAA0B;EAChC,MAAM,QAAQ,KAAK,MAAM,WAAW;AACpC,MAAI,CAAC,MAAO,QAAO,CAAC;EAEpB,MAAM,SAAS,MAAM;AACrB,MAAI,CAAC,SAAS;AACZ,aAAU;AACV,WAAQ;aAER,SACA,OAAO,OAAO,MAAM,MACpB,OAAO,UAAU,MAAM,QACvB;AACA,aAAU;AACV,WAAQ;;AAEV,SAAO;;;;AAKX,SAAgB,0BACd,SACA,SACQ;CACR,MAAM,YAAY,oBAAoB;AACtC,QAAO,QACJ,MAAM,KAAK,CACX,KAAK,SAAU,UAAU,KAAK,GAAG,QAAQ,KAAK,GAAG,KAAM,CACvD,KAAK,KAAK;;;AAIf,SAAgB,6BACd,SACA,WACM;CACN,MAAM,YAAY,oBAAoB;AACtC,SAAQ,MAAM,KAAK,CAAC,SAAS,MAAM,UAAU;AAC3C,MAAI,UAAU,KAAK,CAAE,WAAU,MAAM,MAAM;GAC3C;;;;;AAMJ,SAAgB,oBAAoB,SAGlC;CACA,IAAI,UAAU;AASd,QAAO;EAAE,SAPI,0BAA0B,UAAU,SAAS;GACxD,MAAM,QAAQ,KAAK,MAAM,iBAAiB;AAC1C,OAAI,CAAC,MAAO,QAAO;AACnB,aAAU;AACV,UAAO,GAAG,MAAM,GAAG,OAAO,MAAM,GAAG;IAGf;EAAE;EAAS;;;;;AAMnC,SAAgB,iBAAiB,SAAyB;AACxD,QAAO,0BAA0B,UAAU,SAAS;EAClD,MAAM,QAAQ,KAAK,MAAM,eAAe;AACxC,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,GAAG,MAAM,GAAG,KAAK,MAAM,GAAG;GACjC;;;AAIJ,SAAgB,0BAA0B;AACxC,QAAO,SAAS,CACb,IAAI,YAAY,CAChB,IAAI,mBAAmB,CAAC,QAAQ,OAAO,CAAC,CACxC,IAAI,UAAU;;;;;AAMnB,SAAgB,mBAAmB,SAAuB;CACxD,MAAM,YAAY,yBAAyB;AAC3C,KAAI;AACF,SAAO,UAAU,QAAQ,UAAU,MAAM,QAAQ,CAAC;UAC3C,OAAO;EACd,MAAM,EAAE,SAAS,aAAa,YAAY,oBAAoB,QAAQ;AACtE,MAAI,CAAC,QAAS,OAAM;AACpB,SAAO,UAAU,QAAQ,UAAU,MAAM,YAAY,CAAC;;;;;;;AAQ1D,SAAgB,qBAAqB,SAGnC;CACA,MAAM,YAAY,yBAAyB;AAC3C,KAAI;AACF,SAAO;GACL,KAAK,UAAU,QAAQ,UAAU,MAAM,QAAQ,CAAC;GAChD,aAAa;GACd;UACM,OAAO;EACd,MAAM,EAAE,SAAS,aAAa,YAAY,oBAAoB,QAAQ;AACtE,MAAI,CAAC,QAAS,OAAM;AACpB,SAAO;GACL,KAAK,UAAU,QAAQ,UAAU,MAAM,YAAY,CAAC;GACpD,aAAa;GACd"}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Validates if an MDX file content can be parsed as a valid AST
|
|
3
|
+
*
|
|
4
|
+
* Mintlify-style custom heading IDs (`## Heading {#id}`) are tolerated: they are
|
|
5
|
+
* not valid MDX expressions, but the CLI supports them end-to-end, so a document
|
|
6
|
+
* whose only parse error comes from them is considered valid.
|
|
7
|
+
*
|
|
3
8
|
* @param content - The MDX file content to validate
|
|
4
9
|
* @param filePath - The file path for error reporting
|
|
5
10
|
* @returns object with isValid boolean and optional error message
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import remarkParse from "remark-parse";
|
|
3
|
-
import remarkMdx from "remark-mdx";
|
|
4
|
-
import remarkFrontmatter from "remark-frontmatter";
|
|
1
|
+
import { parseMdxTolerantly } from "./mdxAnchorSyntax.js";
|
|
5
2
|
//#region src/utils/validateMdx.ts
|
|
6
3
|
/**
|
|
7
4
|
* Validates if an MDX file content can be parsed as a valid AST
|
|
5
|
+
*
|
|
6
|
+
* Mintlify-style custom heading IDs (`## Heading {#id}`) are tolerated: they are
|
|
7
|
+
* not valid MDX expressions, but the CLI supports them end-to-end, so a document
|
|
8
|
+
* whose only parse error comes from them is considered valid.
|
|
9
|
+
*
|
|
8
10
|
* @param content - The MDX file content to validate
|
|
9
11
|
* @param filePath - The file path for error reporting
|
|
10
12
|
* @returns object with isValid boolean and optional error message
|
|
11
13
|
*/
|
|
12
14
|
function isValidMdx(content, _filePath) {
|
|
13
15
|
try {
|
|
14
|
-
|
|
15
|
-
const ast = parseProcessor.parse(content);
|
|
16
|
-
parseProcessor.runSync(ast);
|
|
16
|
+
parseMdxTolerantly(content);
|
|
17
17
|
return { isValid: true };
|
|
18
18
|
} catch (error) {
|
|
19
19
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validateMdx.js","names":[],"sources":["../../src/utils/validateMdx.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"validateMdx.js","names":[],"sources":["../../src/utils/validateMdx.ts"],"sourcesContent":["import { parseMdxTolerantly } from './mdxAnchorSyntax.js';\n\n/**\n * Validates if an MDX file content can be parsed as a valid AST\n *\n * Mintlify-style custom heading IDs (`## Heading {#id}`) are tolerated: they are\n * not valid MDX expressions, but the CLI supports them end-to-end, so a document\n * whose only parse error comes from them is considered valid.\n *\n * @param content - The MDX file content to validate\n * @param filePath - The file path for error reporting\n * @returns object with isValid boolean and optional error message\n */\nexport function isValidMdx(\n content: string,\n _filePath: string\n): {\n isValid: boolean;\n error?: string;\n} {\n try {\n parseMdxTolerantly(content);\n return { isValid: true };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n return { isValid: false, error: errorMessage };\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAaA,SAAgB,WACd,SACA,WAIA;AACA,KAAI;AACF,qBAAmB,QAAQ;AAC3B,SAAO,EAAE,SAAS,MAAM;UACjB,OAAO;AAEd,SAAO;GAAE,SAAS;GAAO,OADJ,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;GAC7B"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gt",
|
|
3
|
-
"version": "2.17.
|
|
3
|
+
"version": "2.17.3",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"bin": "bin/main.js",
|
|
6
6
|
"files": [
|
|
@@ -117,10 +117,10 @@
|
|
|
117
117
|
"yaml": "^2.8.0",
|
|
118
118
|
"@generaltranslation/icu": "0.1.2",
|
|
119
119
|
"@generaltranslation/format": "0.1.8",
|
|
120
|
-
"@generaltranslation/python-extractor": "0.2.
|
|
121
|
-
"@generaltranslation/supported-locales": "2.1.
|
|
122
|
-
"@generaltranslation/vue-extractor": "0.1.
|
|
123
|
-
"generaltranslation": "9.1.
|
|
120
|
+
"@generaltranslation/python-extractor": "0.2.43",
|
|
121
|
+
"@generaltranslation/supported-locales": "2.1.23",
|
|
122
|
+
"@generaltranslation/vue-extractor": "0.1.3",
|
|
123
|
+
"generaltranslation": "9.1.10",
|
|
124
124
|
"gt-remark": "1.0.12"
|
|
125
125
|
},
|
|
126
126
|
"devDependencies": {
|