gt 2.20.2 → 2.20.4
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 +12 -0
- package/dist/cli/commands/translate.js.map +1 -1
- package/dist/cli/flags.js +1 -1
- package/dist/cli/flags.js.map +1 -1
- package/dist/config/generateSettings.js +1 -1
- package/dist/config/generateSettings.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 +0 -8
- package/dist/utils/addExplicitAnchorIds.js +19 -45
- package/dist/utils/addExplicitAnchorIds.js.map +1 -1
- package/dist/workflows/stage.js +1 -1
- package/dist/workflows/stage.js.map +1 -1
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# gtx-cli
|
|
2
2
|
|
|
3
|
+
## 2.20.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#2262](https://github.com/generaltranslation/gt/pull/2262) [`c2c9048`](https://github.com/generaltranslation/gt/commit/c2c9048ffbe98b7aa77b0dee034e5e324bbb6395) Thanks [@fernando-aviles](https://github.com/fernando-aviles)! - fix(cli): with `experimentalAddHeaderAnchorIds: 'mintlify'`, write Mintlify's native `{#id}` on every translated heading, using the source heading's ID so anchors match across locales and the heading hover link points at the same target. Mintlify only reads `{#id}` on headings indented up to three spaces, and the MDX serializer indents JSX children two spaces per level, so headings nested in JSX are moved back to the margin. Default mode is unchanged.
|
|
8
|
+
|
|
9
|
+
## 2.20.3
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [#2260](https://github.com/generaltranslation/gt/pull/2260) [`8a3c49c`](https://github.com/generaltranslation/gt/commit/8a3c49cbf48b09d1687eb3e5202bd12c4221d3ad) Thanks [@fernando-aviles](https://github.com/fernando-aviles)! - `gt translate` no longer saves local edits to translated output by default. Saving local edits is now opt-in via `--save-local` or `options.saveLocal: true` in `gt.config.json`, so running the CLI locally does not overwrite production translations.
|
|
14
|
+
|
|
3
15
|
## 2.20.2
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translate.js","names":[],"sources":["../../../src/cli/commands/translate.ts"],"sourcesContent":["import { EnqueueFilesResult } from 'generaltranslation/types';\nimport { TranslateFlags } from '../../types/index.js';\nimport { Settings } from '../../types/index.js';\nimport {\n FileTranslationData,\n runDownloadWorkflow,\n} from '../../workflows/download.js';\nimport { createFileMapping } from '../../formats/files/fileMapping.js';\nimport copyFile from '../../fs/copyFile.js';\nimport flattenJsonFiles from '../../utils/flattenJsonFiles.js';\nimport localizeStaticUrls from '../../utils/localizeStaticUrls.js';\nimport localizeRelativeAssets from '../../utils/localizeRelativeAssets.js';\nimport processAnchorIds from '../../utils/processAnchorIds.js';\nimport localizeStaticImports from '../../utils/localizeStaticImports.js';\nimport { postprocessMintlify } from '../../formats/files/postprocess/mintlify.js';\nimport { BranchData } from '../../types/branch.js';\nimport { getDownloadedMeta } from '../../state/recentDownloads.js';\nimport { persistPostProcessHashes } from '../../utils/persistPostprocessHashes.js';\nimport { runPublishWorkflow } from '../../workflows/publish.js';\nimport { SUPPORTED_FILE_EXTENSIONS } from '../../formats/files/supportedFiles.js';\nimport { hasNonIdentityFileFormatTransformForType } from '../../formats/files/transformFormat.js';\nimport { getRelative } from '../../fs/findFilepath.js';\nimport type { InlineLibrary } from '../../types/libraries.js';\n\n// Downloads translations that were completed\nexport async function handleTranslate(\n options: TranslateFlags,\n settings: Settings,\n fileVersionData: FileTranslationData | undefined,\n jobData: EnqueueFilesResult | undefined,\n branchData: BranchData | undefined,\n publishMap?: Map<string, boolean>,\n inlineLibrary?: InlineLibrary\n) {\n if (fileVersionData) {\n const {\n resolvedPaths,\n placeholderPaths,\n transformPaths,\n transformFormats,\n } = settings.files;\n\n const fileMapping = createFileMapping(\n resolvedPaths,\n placeholderPaths,\n transformPaths,\n transformFormats,\n settings.locales,\n settings.defaultLocale\n );\n // Check for remaining translations\n await runDownloadWorkflow({\n fileVersionData: fileVersionData,\n jobData: jobData,\n branchData: branchData,\n locales: settings.locales,\n timeoutDuration: options.timeout,\n resolveOutputPath: (sourcePath, locale) =>\n fileMapping[locale]?.[sourcePath] ?? null,\n options: settings,\n inlineLibrary,\n forceRetranslation: options.force,\n forceDownload: options.forceDownload || options.force, // if force is true should also force download\n });\n\n // Publish/unpublish files after translations are downloaded\n if (publishMap && branchData?.currentBranch.id) {\n const files = Object.entries(fileVersionData).map(([fileId, data]) => ({\n fileId,\n versionId: data.versionId,\n fileName: data.fileName,\n }));\n await runPublishWorkflow(\n files,\n publishMap,\n branchData.currentBranch.id,\n settings\n );\n }\n }\n}\n\nexport async function postProcessTranslations(\n settings: Settings,\n includeFiles?: Set<string>\n) {\n const postProcessIncludes = filterPostProcessIncludesForFormatTransforms(\n settings,\n includeFiles\n );\n if (includeFiles && postProcessIncludes?.size === 0) return;\n\n await postprocessMintlify(settings, postProcessIncludes);\n\n // Localize static urls (/docs -> /[locale]/docs) and preserve anchor IDs for non-default locales\n // Default locale is processed earlier in the flow in base.ts\n if (settings.options?.experimentalLocalizeStaticUrls) {\n const nonDefaultLocales = settings.locales.filter(\n (locale) => locale !== settings.defaultLocale\n );\n if (nonDefaultLocales.length > 0) {\n await localizeStaticUrls(\n settings,\n nonDefaultLocales,\n postProcessIncludes\n );\n }\n }\n\n // Rewrite relative asset URLs in translated md/mdx files\n if (settings.options?.experimentalLocalizeRelativeAssets) {\n const nonDefaultLocales = settings.locales.filter(\n (locale) => locale !== settings.defaultLocale\n );\n if (nonDefaultLocales.length > 0) {\n await localizeRelativeAssets(\n settings,\n nonDefaultLocales,\n postProcessIncludes\n );\n }\n }\n\n // Localize static imports (import Snippet from /snippets/file.mdx -> import Snippet from /snippets/[locale]/file.mdx)\n if (settings.options?.experimentalLocalizeStaticImports) {\n await localizeStaticImports(settings, postProcessIncludes);\n }\n\n const shouldProcessAnchorIds =\n settings.options?.experimentalLocalizeStaticUrls ||\n settings.options?.experimentalAddHeaderAnchorIds;\n\n // Add explicit anchor IDs to translated MDX/MD files to preserve navigation.\n // Uses inline {#id}
|
|
1
|
+
{"version":3,"file":"translate.js","names":[],"sources":["../../../src/cli/commands/translate.ts"],"sourcesContent":["import { EnqueueFilesResult } from 'generaltranslation/types';\nimport { TranslateFlags } from '../../types/index.js';\nimport { Settings } from '../../types/index.js';\nimport {\n FileTranslationData,\n runDownloadWorkflow,\n} from '../../workflows/download.js';\nimport { createFileMapping } from '../../formats/files/fileMapping.js';\nimport copyFile from '../../fs/copyFile.js';\nimport flattenJsonFiles from '../../utils/flattenJsonFiles.js';\nimport localizeStaticUrls from '../../utils/localizeStaticUrls.js';\nimport localizeRelativeAssets from '../../utils/localizeRelativeAssets.js';\nimport processAnchorIds from '../../utils/processAnchorIds.js';\nimport localizeStaticImports from '../../utils/localizeStaticImports.js';\nimport { postprocessMintlify } from '../../formats/files/postprocess/mintlify.js';\nimport { BranchData } from '../../types/branch.js';\nimport { getDownloadedMeta } from '../../state/recentDownloads.js';\nimport { persistPostProcessHashes } from '../../utils/persistPostprocessHashes.js';\nimport { runPublishWorkflow } from '../../workflows/publish.js';\nimport { SUPPORTED_FILE_EXTENSIONS } from '../../formats/files/supportedFiles.js';\nimport { hasNonIdentityFileFormatTransformForType } from '../../formats/files/transformFormat.js';\nimport { getRelative } from '../../fs/findFilepath.js';\nimport type { InlineLibrary } from '../../types/libraries.js';\n\n// Downloads translations that were completed\nexport async function handleTranslate(\n options: TranslateFlags,\n settings: Settings,\n fileVersionData: FileTranslationData | undefined,\n jobData: EnqueueFilesResult | undefined,\n branchData: BranchData | undefined,\n publishMap?: Map<string, boolean>,\n inlineLibrary?: InlineLibrary\n) {\n if (fileVersionData) {\n const {\n resolvedPaths,\n placeholderPaths,\n transformPaths,\n transformFormats,\n } = settings.files;\n\n const fileMapping = createFileMapping(\n resolvedPaths,\n placeholderPaths,\n transformPaths,\n transformFormats,\n settings.locales,\n settings.defaultLocale\n );\n // Check for remaining translations\n await runDownloadWorkflow({\n fileVersionData: fileVersionData,\n jobData: jobData,\n branchData: branchData,\n locales: settings.locales,\n timeoutDuration: options.timeout,\n resolveOutputPath: (sourcePath, locale) =>\n fileMapping[locale]?.[sourcePath] ?? null,\n options: settings,\n inlineLibrary,\n forceRetranslation: options.force,\n forceDownload: options.forceDownload || options.force, // if force is true should also force download\n });\n\n // Publish/unpublish files after translations are downloaded\n if (publishMap && branchData?.currentBranch.id) {\n const files = Object.entries(fileVersionData).map(([fileId, data]) => ({\n fileId,\n versionId: data.versionId,\n fileName: data.fileName,\n }));\n await runPublishWorkflow(\n files,\n publishMap,\n branchData.currentBranch.id,\n settings\n );\n }\n }\n}\n\nexport async function postProcessTranslations(\n settings: Settings,\n includeFiles?: Set<string>\n) {\n const postProcessIncludes = filterPostProcessIncludesForFormatTransforms(\n settings,\n includeFiles\n );\n if (includeFiles && postProcessIncludes?.size === 0) return;\n\n await postprocessMintlify(settings, postProcessIncludes);\n\n // Localize static urls (/docs -> /[locale]/docs) and preserve anchor IDs for non-default locales\n // Default locale is processed earlier in the flow in base.ts\n if (settings.options?.experimentalLocalizeStaticUrls) {\n const nonDefaultLocales = settings.locales.filter(\n (locale) => locale !== settings.defaultLocale\n );\n if (nonDefaultLocales.length > 0) {\n await localizeStaticUrls(\n settings,\n nonDefaultLocales,\n postProcessIncludes\n );\n }\n }\n\n // Rewrite relative asset URLs in translated md/mdx files\n if (settings.options?.experimentalLocalizeRelativeAssets) {\n const nonDefaultLocales = settings.locales.filter(\n (locale) => locale !== settings.defaultLocale\n );\n if (nonDefaultLocales.length > 0) {\n await localizeRelativeAssets(\n settings,\n nonDefaultLocales,\n postProcessIncludes\n );\n }\n }\n\n // Localize static imports (import Snippet from /snippets/file.mdx -> import Snippet from /snippets/[locale]/file.mdx)\n if (settings.options?.experimentalLocalizeStaticImports) {\n await localizeStaticImports(settings, postProcessIncludes);\n }\n\n const shouldProcessAnchorIds =\n settings.options?.experimentalLocalizeStaticUrls ||\n settings.options?.experimentalAddHeaderAnchorIds;\n\n // Add explicit anchor IDs to translated MDX/MD files to preserve navigation.\n // Uses escaped inline \\{#id\\} by default, or Mintlify's native {#id} if\n // experimentalAddHeaderAnchorIds is 'mintlify'.\n //\n // Runs last of the md/mdx passes: the others re-stringify the document, which\n // re-indents headings nested in JSX and would otherwise move them out from\n // under the anchors placed here.\n if (shouldProcessAnchorIds) {\n await processAnchorIds(settings, postProcessIncludes);\n }\n\n // Flatten json files into a single file\n if (settings.options?.experimentalFlattenJsonFiles) {\n await flattenJsonFiles(settings, postProcessIncludes);\n }\n\n // Copy files to the target locale\n if (settings.options?.copyFiles) {\n await copyFile(settings);\n }\n\n // Record postprocessed content hashes for newly downloaded files\n persistPostProcessHashes(settings, postProcessIncludes, getDownloadedMeta());\n}\n\n/**\n * Exclude only outputs whose source file was translated into a different format.\n * @param settings - The settings for the project\n * @param includeFiles - The files to include in the post-processing\n * @returns The files to exclude in the post-processing\n */\nfunction filterPostProcessIncludesForFormatTransforms(\n settings: Settings,\n includeFiles?: Set<string>\n): Set<string> | undefined {\n if (!includeFiles) return includeFiles;\n\n const transformedSourcePaths = new Set<string>();\n for (const fileType of SUPPORTED_FILE_EXTENSIONS) {\n if (!hasNonIdentityFileFormatTransformForType(settings, fileType)) continue;\n\n for (const sourcePath of settings.files.resolvedPaths[fileType] || []) {\n transformedSourcePaths.add(getRelative(sourcePath));\n }\n }\n if (transformedSourcePaths.size === 0) return includeFiles;\n\n const { resolvedPaths, placeholderPaths, transformPaths, transformFormats } =\n settings.files;\n const fileMapping = createFileMapping(\n resolvedPaths,\n placeholderPaths,\n transformPaths,\n transformFormats,\n settings.locales,\n settings.defaultLocale\n );\n\n const transformedOutputPaths = new Set<string>();\n for (const localeMapping of Object.values(fileMapping)) {\n for (const [sourcePath, outputPath] of Object.entries(localeMapping)) {\n if (transformedSourcePaths.has(sourcePath)) {\n transformedOutputPaths.add(outputPath);\n }\n }\n }\n\n return new Set(\n [...includeFiles].filter(\n (filePath) => !transformedOutputPaths.has(filePath)\n )\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAyBA,eAAsB,gBACpB,SACA,UACA,iBACA,SACA,YACA,YACA,eACA;AACA,KAAI,iBAAiB;EACnB,MAAM,EACJ,eACA,kBACA,gBACA,qBACE,SAAS;EAEb,MAAM,cAAc,kBAClB,eACA,kBACA,gBACA,kBACA,SAAS,SACT,SAAS,cACV;AAED,QAAM,oBAAoB;GACP;GACR;GACG;GACZ,SAAS,SAAS;GAClB,iBAAiB,QAAQ;GACzB,oBAAoB,YAAY,WAC9B,YAAY,UAAU,eAAe;GACvC,SAAS;GACT;GACA,oBAAoB,QAAQ;GAC5B,eAAe,QAAQ,iBAAiB,QAAQ;GACjD,CAAC;AAGF,MAAI,cAAc,YAAY,cAAc,GAM1C,OAAM,mBALQ,OAAO,QAAQ,gBAAgB,CAAC,KAAK,CAAC,QAAQ,WAAW;GACrE;GACA,WAAW,KAAK;GAChB,UAAU,KAAK;GAChB,EAEM,EACL,YACA,WAAW,cAAc,IACzB,SACD;;;AAKP,eAAsB,wBACpB,UACA,cACA;CACA,MAAM,sBAAsB,6CAC1B,UACA,aACD;AACD,KAAI,gBAAgB,qBAAqB,SAAS,EAAG;AAErD,OAAM,oBAAoB,UAAU,oBAAoB;AAIxD,KAAI,SAAS,SAAS,gCAAgC;EACpD,MAAM,oBAAoB,SAAS,QAAQ,QACxC,WAAW,WAAW,SAAS,cACjC;AACD,MAAI,kBAAkB,SAAS,EAC7B,OAAM,mBACJ,UACA,mBACA,oBACD;;AAKL,KAAI,SAAS,SAAS,oCAAoC;EACxD,MAAM,oBAAoB,SAAS,QAAQ,QACxC,WAAW,WAAW,SAAS,cACjC;AACD,MAAI,kBAAkB,SAAS,EAC7B,OAAM,uBACJ,UACA,mBACA,oBACD;;AAKL,KAAI,SAAS,SAAS,kCACpB,OAAM,sBAAsB,UAAU,oBAAoB;AAc5D,KAVE,SAAS,SAAS,kCAClB,SAAS,SAAS,+BAUlB,OAAM,iBAAiB,UAAU,oBAAoB;AAIvD,KAAI,SAAS,SAAS,6BACpB,OAAM,iBAAiB,UAAU,oBAAoB;AAIvD,KAAI,SAAS,SAAS,UACpB,OAAM,SAAS,SAAS;AAI1B,0BAAyB,UAAU,qBAAqB,mBAAmB,CAAC;;;;;;;;AAS9E,SAAS,6CACP,UACA,cACyB;AACzB,KAAI,CAAC,aAAc,QAAO;CAE1B,MAAM,yCAAyB,IAAI,KAAa;AAChD,MAAK,MAAM,YAAY,2BAA2B;AAChD,MAAI,CAAC,yCAAyC,UAAU,SAAS,CAAE;AAEnE,OAAK,MAAM,cAAc,SAAS,MAAM,cAAc,aAAa,EAAE,CACnE,wBAAuB,IAAI,YAAY,WAAW,CAAC;;AAGvD,KAAI,uBAAuB,SAAS,EAAG,QAAO;CAE9C,MAAM,EAAE,eAAe,kBAAkB,gBAAgB,qBACvD,SAAS;CACX,MAAM,cAAc,kBAClB,eACA,kBACA,gBACA,kBACA,SAAS,SACT,SAAS,cACV;CAED,MAAM,yCAAyB,IAAI,KAAa;AAChD,MAAK,MAAM,iBAAiB,OAAO,OAAO,YAAY,CACpD,MAAK,MAAM,CAAC,YAAY,eAAe,OAAO,QAAQ,cAAc,CAClE,KAAI,uBAAuB,IAAI,WAAW,CACxC,wBAAuB,IAAI,WAAW;AAK5C,QAAO,IAAI,IACT,CAAC,GAAG,aAAa,CAAC,QACf,aAAa,CAAC,uBAAuB,IAAI,SAAS,CACpD,CACF"}
|
package/dist/cli/flags.js
CHANGED
|
@@ -12,7 +12,7 @@ function attachTranslateFlags(command) {
|
|
|
12
12
|
if (isNaN(parsedValue)) throw new Error("Invalid timeout: not a number.");
|
|
13
13
|
if (parsedValue < 0) throw new Error("Invalid timeout: must be a positive number.");
|
|
14
14
|
return parsedValue;
|
|
15
|
-
}, DEFAULT_TIMEOUT).option("--save-local", "Detect and save local edits before enqueuing translations (default:
|
|
15
|
+
}, DEFAULT_TIMEOUT).option("--save-local", "Detect and save local edits before enqueuing translations (default: false; configurable via options.saveLocal in gt.config.json)").option("--no-save-local", "Skip detecting and saving local edits before enqueuing translations").option("--publish", "Publish translations to the CDN", false).option("--experimental-localize-static-urls", "Triggering this will run a script after the cli tool that localizes all urls in content files. Currently only supported for md and mdx files.", false).option("--experimental-hide-default-locale", "When localizing static locales, hide the default locale from the path", false).option("--experimental-flatten-json-files", "Triggering this will flatten the json files into a single file. This is useful for projects that have a lot of json files.", false).option("--experimental-localize-static-imports", "Triggering this will run a script after the cli tool that localizes all static imports in content files. Currently only supported for md and mdx files.", false).option("--experimental-localize-relative-assets", "Triggering this will rewrite relative image asset URLs in translated md/mdx files to valid paths.", false).option("--force", "Force a retranslation, invalidating all existing cached translations if they exist.", false).option("--force-download", "Force download and overwrite local files, bypassing gt-lock.json checks.", false).option("--omit-config-ids", "Do not write _versionId or _branchId to gt.config.json").option("--experimental-clear-locale-dirs", "Clear locale directories before downloading new translations", false).option("--branch <branch>", "Specify a custom branch to use for translations").option("--disable-branch-detection", "Disable additional branch detection and optimizations and use the manually specified branch", false).option("--enable-branching", "Enable branching for the project").option("--remote-name <name>", "Specify a custom remote name to use for branch detection", DEFAULT_GIT_REMOTE_NAME).option("--tag [value]", "Tag this translation run (auto-resolves from git if no value provided)").option("-m, --message <message>", "Message to attach to the translation tag");
|
|
16
16
|
return command;
|
|
17
17
|
}
|
|
18
18
|
/**
|
package/dist/cli/flags.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flags.js","names":[],"sources":["../../src/cli/flags.ts"],"sourcesContent":["import { Command } from 'commander';\nimport findFilepath from '../fs/findFilepath.js';\nimport { DEFAULT_GIT_REMOTE_NAME } from '../utils/constants.js';\n\nconst DEFAULT_TIMEOUT = 900;\n\nexport function attachSharedFlags(command: Command) {\n command\n .option(\n '-c, --config <path>',\n 'Filepath to config file, by default gt.config.json',\n findFilepath(['gt.config.json'])\n )\n .option('--api-key <key>', 'API key for General Translation cloud service')\n .option('--project-id <id>', 'General Translation project ID');\n return command;\n}\n\nexport function attachTranslateFlags(command: Command) {\n attachSharedFlags(command)\n .option('--version-id <id>', 'General Translation version ID')\n .option(\n '--default-language, --default-locale <locale>',\n 'Default locale (e.g., en)'\n )\n .option(\n '--new, --locales <locales...>',\n 'Space-separated list of locales (e.g., en fr es)'\n )\n .option(\n '--dry-run',\n 'Dry run, do not send updates to the General Translation API',\n false\n )\n .option(\n '--timeout <seconds>',\n 'Translation wait timeout in seconds',\n (value) => {\n const parsedValue = parseInt(value, 10);\n if (isNaN(parsedValue)) {\n throw new Error('Invalid timeout: not a number.');\n }\n if (parsedValue < 0) {\n throw new Error('Invalid timeout: must be a positive number.');\n }\n return parsedValue;\n },\n DEFAULT_TIMEOUT\n )\n .option(\n '--save-local',\n 'Detect and save local edits before enqueuing translations (default:
|
|
1
|
+
{"version":3,"file":"flags.js","names":[],"sources":["../../src/cli/flags.ts"],"sourcesContent":["import { Command } from 'commander';\nimport findFilepath from '../fs/findFilepath.js';\nimport { DEFAULT_GIT_REMOTE_NAME } from '../utils/constants.js';\n\nconst DEFAULT_TIMEOUT = 900;\n\nexport function attachSharedFlags(command: Command) {\n command\n .option(\n '-c, --config <path>',\n 'Filepath to config file, by default gt.config.json',\n findFilepath(['gt.config.json'])\n )\n .option('--api-key <key>', 'API key for General Translation cloud service')\n .option('--project-id <id>', 'General Translation project ID');\n return command;\n}\n\nexport function attachTranslateFlags(command: Command) {\n attachSharedFlags(command)\n .option('--version-id <id>', 'General Translation version ID')\n .option(\n '--default-language, --default-locale <locale>',\n 'Default locale (e.g., en)'\n )\n .option(\n '--new, --locales <locales...>',\n 'Space-separated list of locales (e.g., en fr es)'\n )\n .option(\n '--dry-run',\n 'Dry run, do not send updates to the General Translation API',\n false\n )\n .option(\n '--timeout <seconds>',\n 'Translation wait timeout in seconds',\n (value) => {\n const parsedValue = parseInt(value, 10);\n if (isNaN(parsedValue)) {\n throw new Error('Invalid timeout: not a number.');\n }\n if (parsedValue < 0) {\n throw new Error('Invalid timeout: must be a positive number.');\n }\n return parsedValue;\n },\n DEFAULT_TIMEOUT\n )\n .option(\n '--save-local',\n 'Detect and save local edits before enqueuing translations (default: false; configurable via options.saveLocal in gt.config.json)'\n )\n .option(\n '--no-save-local',\n 'Skip detecting and saving local edits before enqueuing translations'\n )\n .option('--publish', 'Publish translations to the CDN', false)\n .option(\n '--experimental-localize-static-urls',\n 'Triggering this will run a script after the cli tool that localizes all urls in content files. Currently only supported for md and mdx files.',\n false\n )\n .option(\n '--experimental-hide-default-locale',\n 'When localizing static locales, hide the default locale from the path',\n false\n )\n .option(\n '--experimental-flatten-json-files',\n 'Triggering this will flatten the json files into a single file. This is useful for projects that have a lot of json files.',\n false\n )\n .option(\n '--experimental-localize-static-imports',\n 'Triggering this will run a script after the cli tool that localizes all static imports in content files. Currently only supported for md and mdx files.',\n false\n )\n .option(\n '--experimental-localize-relative-assets',\n 'Triggering this will rewrite relative image asset URLs in translated md/mdx files to valid paths.',\n false\n )\n .option(\n '--force',\n 'Force a retranslation, invalidating all existing cached translations if they exist.',\n false\n )\n .option(\n '--force-download',\n 'Force download and overwrite local files, bypassing gt-lock.json checks.',\n false\n )\n .option(\n '--omit-config-ids',\n 'Do not write _versionId or _branchId to gt.config.json'\n )\n .option(\n '--experimental-clear-locale-dirs',\n 'Clear locale directories before downloading new translations',\n false\n )\n .option(\n '--branch <branch>',\n 'Specify a custom branch to use for translations'\n )\n .option(\n '--disable-branch-detection',\n 'Disable additional branch detection and optimizations and use the manually specified branch',\n false\n )\n .option('--enable-branching', 'Enable branching for the project')\n .option(\n '--remote-name <name>',\n 'Specify a custom remote name to use for branch detection',\n DEFAULT_GIT_REMOTE_NAME\n )\n .option(\n '--tag [value]',\n 'Tag this translation run (auto-resolves from git if no value provided)'\n )\n .option(\n '-m, --message <message>',\n 'Message to attach to the translation tag'\n );\n return command;\n}\n\n/**\n * Attaches flags necessary for parsing inline content\n * @param command - The command to attach the flags to\n * @returns The command with the inline content parsing flags attached\n */\nfunction attachInlineContentParsingFlags(\n command: Command,\n sourceHelp?: string\n) {\n return command\n .option(\n '--tsconfig, --jsconfig <path>',\n 'Path to custom jsconfig or tsconfig file',\n findFilepath(['./tsconfig.json', './jsconfig.json'])\n )\n .option('--dictionary <path>', 'Path to dictionary file')\n .option(\n '--src <paths...>',\n sourceHelp ??\n \"Space-separated list of glob patterns containing the app's source code, by default 'src/**/*.{js,jsx,ts,tsx}' 'app/**/*.{js,jsx,ts,tsx}' 'pages/**/*.{js,jsx,ts,tsx}' 'components/**/*.{js,jsx,ts,tsx}'\"\n )\n .option(\n '--inline',\n 'Include inline content in translations (e.g., inline jsx translations, inline string translations, etc.)',\n true\n );\n}\n\n/**\n * Attaches flags necessary for validating a project\n * @param command\n * @returns The command with the validate flags attached\n */\nexport function attachValidateFlags(command: Command, sourceHelp?: string) {\n return attachInlineContentParsingFlags(\n command.option(\n '-c, --config <path>',\n 'Filepath to config file, by default gt.config.json',\n findFilepath(['gt.config.json'])\n ),\n sourceHelp\n );\n}\n\n/**\n * Attaches flags necessary for translating a project\n * @param command\n * @returns The command with the translate flags attached\n */\nexport function attachInlineTranslateFlags(\n command: Command,\n sourceHelp?: string\n) {\n return attachInlineContentParsingFlags(\n command.option(\n '--ignore-errors',\n 'Ignore errors encountered while scanning for inline content',\n false\n ),\n sourceHelp\n );\n}\n"],"mappings":";;;AAIA,MAAM,kBAAkB;AAExB,SAAgB,kBAAkB,SAAkB;AAClD,SACG,OACC,uBACA,sDACA,aAAa,CAAC,iBAAiB,CAAC,CACjC,CACA,OAAO,mBAAmB,gDAAgD,CAC1E,OAAO,qBAAqB,iCAAiC;AAChE,QAAO;;AAGT,SAAgB,qBAAqB,SAAkB;AACrD,mBAAkB,QAAQ,CACvB,OAAO,qBAAqB,iCAAiC,CAC7D,OACC,iDACA,4BACD,CACA,OACC,iCACA,mDACD,CACA,OACC,aACA,+DACA,MACD,CACA,OACC,uBACA,wCACC,UAAU;EACT,MAAM,cAAc,SAAS,OAAO,GAAG;AACvC,MAAI,MAAM,YAAY,CACpB,OAAM,IAAI,MAAM,iCAAiC;AAEnD,MAAI,cAAc,EAChB,OAAM,IAAI,MAAM,8CAA8C;AAEhE,SAAO;IAET,gBACD,CACA,OACC,gBACA,mIACD,CACA,OACC,mBACA,sEACD,CACA,OAAO,aAAa,mCAAmC,MAAM,CAC7D,OACC,uCACA,iJACA,MACD,CACA,OACC,sCACA,yEACA,MACD,CACA,OACC,qCACA,8HACA,MACD,CACA,OACC,0CACA,2JACA,MACD,CACA,OACC,2CACA,qGACA,MACD,CACA,OACC,WACA,uFACA,MACD,CACA,OACC,oBACA,4EACA,MACD,CACA,OACC,qBACA,yDACD,CACA,OACC,oCACA,gEACA,MACD,CACA,OACC,qBACA,kDACD,CACA,OACC,8BACA,+FACA,MACD,CACA,OAAO,sBAAsB,mCAAmC,CAChE,OACC,wBACA,4DACA,wBACD,CACA,OACC,iBACA,yEACD,CACA,OACC,2BACA,2CACD;AACH,QAAO;;;;;;;AAQT,SAAS,gCACP,SACA,YACA;AACA,QAAO,QACJ,OACC,iCACA,4CACA,aAAa,CAAC,mBAAmB,kBAAkB,CAAC,CACrD,CACA,OAAO,uBAAuB,0BAA0B,CACxD,OACC,oBACA,cACE,0MACH,CACA,OACC,YACA,4GACA,KACD;;;;;;;AAQL,SAAgB,oBAAoB,SAAkB,YAAqB;AACzE,QAAO,gCACL,QAAQ,OACN,uBACA,sDACA,aAAa,CAAC,iBAAiB,CAAC,CACjC,EACD,WACD;;;;;;;AAQH,SAAgB,2BACd,SACA,YACA;AACA,QAAO,gCACL,QAAQ,OACN,mBACA,+DACA,MACD,EACD,WACD"}
|
|
@@ -120,7 +120,7 @@ async function generateSettings(flags, cwd = process.cwd(), options) {
|
|
|
120
120
|
experimentalFlattenJsonFiles: gtConfig.options?.experimentalFlattenJsonFiles || flags.experimentalFlattenJsonFiles,
|
|
121
121
|
experimentalClearLocaleDirs: gtConfig.options?.experimentalClearLocaleDirs || flags.experimentalClearLocaleDirs,
|
|
122
122
|
clearLocaleDirsExclude: gtConfig.options?.clearLocaleDirsExclude || flags.clearLocaleDirsExclude,
|
|
123
|
-
saveLocal: flags.saveLocal ?? gtConfig.options?.saveLocal ??
|
|
123
|
+
saveLocal: flags.saveLocal ?? gtConfig.options?.saveLocal ?? false
|
|
124
124
|
};
|
|
125
125
|
if (mergedOptions.omitConfigIds && (mergedOptions.publish === true || mergedOptions.files.gtJson.publish === true)) logger.warn(chalk.yellow("Config IDs will be omitted even though CDN publishing is enabled. Remote cache/CDN consumers may load the latest available translations instead of a pinned version."));
|
|
126
126
|
if (mergedOptions.options) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generateSettings.js","names":[],"sources":["../../src/config/generateSettings.ts"],"sourcesContent":["import {\n displayProjectId,\n exitSync,\n logErrorAndExit,\n warnApiKeyInConfig,\n warnDeprecatedField,\n} from '../console/logging.js';\nimport { loadConfig } from '../fs/config/loadConfig.js';\nimport { FilesOptions, Settings } from '../types/index.js';\nimport {\n defaultBaseUrl,\n libraryDefaultLocale,\n} from 'generaltranslation/internal';\nimport { resolveFiles } from '../fs/config/parseFilesConfig.js';\nimport { validateSettings } from './validateSettings.js';\nimport {\n DEFAULT_GIT_REMOTE_NAME,\n GT_DASHBOARD_URL,\n} from '../utils/constants.js';\nimport { resolveProjectId } from '../fs/utils.js';\nimport crypto from 'node:crypto';\nimport { execSync } from 'node:child_process';\nimport path from 'node:path';\nimport chalk from 'chalk';\nimport { resolveConfig } from './resolveConfig.js';\nimport { configureApiClient } from '../utils/api.js';\nimport { gt } from '../utils/gt.js';\nimport { generatePreset } from './optionPresets.js';\nimport { GT_PARSING_FLAGS_DEFAULT } from './defaults.js';\nimport { normalizeFilesOptions } from '../formats/files/transformFormat.js';\nimport { determineLibrary } from '../fs/determineFramework/index.js';\nimport { logger } from '../console/logger.js';\n\nexport const DEFAULT_SRC_PATTERNS = [\n 'src/**/*.{js,jsx,ts,tsx}',\n 'app/**/*.{js,jsx,ts,tsx}',\n 'pages/**/*.{js,jsx,ts,tsx}',\n 'components/**/*.{js,jsx,ts,tsx}',\n];\n\nexport const DEFAULT_PYTHON_SRC_PATTERNS = ['**/*.py'];\nexport const DEFAULT_PYTHON_SRC_EXCLUDES = [\n 'venv/**',\n '.venv/**',\n '__pycache__/**',\n '**/migrations/**',\n '**/tests/**',\n '**/test_*.py',\n '**/*_test.py',\n];\n\ntype GenerateSettingsInput = Partial<Omit<Settings, 'tag'>> & {\n config?: string;\n projectId?: string;\n locales?: string[];\n options?: Settings['options'];\n files?: unknown;\n publish?: boolean;\n omitConfigIds?: boolean;\n saveLocal?: boolean;\n tag?: string | boolean;\n message?: string;\n branch?: string;\n enableBranching?: boolean;\n disableBranchDetection?: boolean;\n remoteName?: string;\n experimentalLocalizeStaticImports?: boolean;\n experimentalLocalizeStaticUrls?: boolean;\n experimentalLocalizeRelativeAssets?: boolean;\n experimentalHideDefaultLocale?: boolean;\n experimentalFlattenJsonFiles?: boolean;\n experimentalClearLocaleDirs?: boolean;\n clearLocaleDirsExclude?: string[];\n [key: string]: unknown;\n};\n\nfunction hasConfiguredTranslationFiles(files: unknown): boolean {\n if (!files || typeof files !== 'object' || Array.isArray(files)) {\n return false;\n }\n return Object.keys(files).some((key) => key !== 'gt');\n}\n\n/**\n * Generates settings from any\n * @param flags - The CLI flags to generate settings from\n * @param cwd - The current working directory\n * @param options - Additional options\n * @param options.requireConfig - If true, exit with an error when no config file is found\n * @returns The generated settings\n */\nexport async function generateSettings(\n flags: GenerateSettingsInput,\n cwd: string = process.cwd(),\n options?: { requireConfig?: boolean }\n): Promise<Settings> {\n // Load config file\n let gtConfig: GenerateSettingsInput = {};\n\n if (flags.config && !flags.config.endsWith('.json')) {\n flags.config = `${flags.config}.json`;\n }\n if (flags.config) {\n gtConfig = loadConfig(flags.config);\n } else {\n const config = resolveConfig(cwd);\n if (config) {\n gtConfig = config.config as GenerateSettingsInput;\n flags.config = config.path;\n } else {\n if (options?.requireConfig) {\n return logErrorAndExit(\n 'No gt.config.json file was found. Run `npx gt init` to create one, pass --config, or run this command from your project root.'\n );\n }\n gtConfig = {};\n }\n }\n\n // Warn if apiKey is present in gt.config.json\n if (gtConfig.apiKey) {\n warnApiKeyInConfig(flags.config ?? 'gt.config.json');\n exitSync(1);\n }\n const projectIdEnv = resolveProjectId();\n // Resolve mismatched projectIds\n if (\n gtConfig.projectId &&\n flags.projectId &&\n gtConfig.projectId !== flags.projectId\n ) {\n logErrorAndExit(\n `Project ID mismatch: gt.config.json uses ${chalk.green(gtConfig.projectId)}, but the CLI flag uses ${chalk.green(flags.projectId)}. Use the same projectId in all configs.`\n );\n } else if (\n gtConfig.projectId &&\n projectIdEnv &&\n gtConfig.projectId !== projectIdEnv\n ) {\n logErrorAndExit(\n `Project ID mismatch: gt.config.json uses ${chalk.green(gtConfig.projectId)}, but GT_PROJECT_ID uses ${chalk.green(projectIdEnv)}. Use the same projectId in all configs.`\n );\n }\n\n if (\n flags.options?.docsUrlPattern &&\n !flags.options?.docsUrlPattern.includes('[locale]')\n ) {\n logErrorAndExit(\n 'Static URLs could not be localized because the URL pattern is missing \"[locale]\". Add \"[locale]\" where the locale should appear in the generated URL.'\n );\n }\n\n if (\n flags.options?.docsImportPattern &&\n !flags.options?.docsImportPattern.includes('[locale]')\n ) {\n logErrorAndExit(\n 'Static imports could not be localized because the import pattern is missing \"[locale]\". Add \"[locale]\" where the locale should appear in the generated import path.'\n );\n }\n\n if (flags.options?.copyFiles) {\n for (const file of flags.options.copyFiles) {\n if (!file.includes('[locale]')) {\n logErrorAndExit(\n 'Files could not be copied because the file path is missing \"[locale]\". Add \"[locale]\" where the locale should appear in the copied path.'\n );\n }\n }\n }\n\n // Warn on deprecated includeSourceCodeContext\n const configuredFiles = gtConfig.files as FilesOptions | undefined;\n if (configuredFiles?.gt?.includeSourceCodeContext != null) {\n warnDeprecatedField(\n 'files.gt.includeSourceCodeContext',\n 'files.gt.parsingFlags.includeSourceCodeContext'\n );\n }\n\n // merge options\n const mergedOptions: Settings = { ...gtConfig, ...flags } as Settings;\n\n if (\n determineLibrary().library === 'base' &&\n !hasConfiguredTranslationFiles(mergedOptions.files)\n ) {\n logger.warn(\n chalk.yellow(\n 'No package.json or Python project file found in the current directory. Run this command from the root of your project.'\n )\n );\n }\n\n // Add defaultLocale if not provided\n mergedOptions.defaultLocale =\n mergedOptions.defaultLocale || libraryDefaultLocale;\n\n // merge locales\n mergedOptions.locales = Array.from(\n new Set([...(gtConfig.locales || []), ...(flags.locales || [])])\n );\n // Separate defaultLocale from locales\n mergedOptions.locales = mergedOptions.locales.filter(\n (locale) => locale !== mergedOptions.defaultLocale\n );\n\n // Add apiKey if not provided\n mergedOptions.apiKey = mergedOptions.apiKey || process.env.GT_API_KEY;\n\n // Add projectId if not provided\n mergedOptions.projectId = mergedOptions.projectId || resolveProjectId();\n\n // Add baseUrl if not provided\n mergedOptions.baseUrl = mergedOptions.baseUrl || defaultBaseUrl;\n\n // Add dashboardUrl if not provided\n mergedOptions.dashboardUrl = mergedOptions.dashboardUrl || GT_DASHBOARD_URL;\n\n // Add locales if not provided\n mergedOptions.locales = mergedOptions.locales || [];\n\n // Only set config path if one was actually found or explicitly provided.\n // Do not default to a phantom path — that would cause downstream writes\n // (e.g. updateConfig) to silently create a config file.\n if (!mergedOptions.config) {\n mergedOptions.config = '';\n }\n\n // Display projectId if present\n if (mergedOptions.projectId) {\n displayProjectId(mergedOptions.projectId);\n }\n\n // Add stageTranslations if not provided\n // For human review, always stage the project\n mergedOptions.stageTranslations = mergedOptions.stageTranslations ?? false;\n\n // Top-level default for whether translated files require approval before use.\n // Effective policy is hash-changing, so reject anything but a real boolean.\n if (\n mergedOptions.requiresReview !== undefined &&\n typeof mergedOptions.requiresReview !== 'boolean'\n ) {\n logErrorAndExit(\n 'requiresReview in gt.config.json must be a boolean. Use files.<type>.requiresReview for glob-scoped overrides.'\n );\n }\n mergedOptions.requiresReview = mergedOptions.requiresReview ?? false;\n\n // Add publish — only set if explicitly configured or passed via flag.\n // When neither is set, leave undefined so the publish step knows\n // there is no global publish intent.\n if (flags.publish) {\n mergedOptions.publish = true;\n } else if (gtConfig.publish !== undefined) {\n mergedOptions.publish = gtConfig.publish;\n } else {\n mergedOptions.publish = undefined;\n }\n\n mergedOptions.omitConfigIds =\n flags.omitConfigIds === true || gtConfig.omitConfigIds === true;\n\n // Don't default src here — each pipeline (JS/Python) has its own defaults.\n // Only set src if the user explicitly provided it via flags or config.\n\n // Resolve all glob patterns in the files object\n const compositePatterns = Object.entries(\n mergedOptions.options?.jsonSchema || {}\n )\n .filter(([, schema]) => schema.composite)\n .map(([key]) => key);\n mergedOptions.files = mergedOptions.files\n ? resolveFiles(\n normalizeFilesOptions(mergedOptions.files as FilesOptions),\n mergedOptions.defaultLocale,\n mergedOptions.locales,\n cwd,\n compositePatterns,\n mergedOptions.requiresReview\n )\n : {\n resolvedPaths: {},\n placeholderPaths: {},\n transformPaths: {},\n transformFormats: {},\n publishPaths: new Set<string>(),\n unpublishPaths: new Set<string>(),\n requiresReviewPaths: new Set<string>(),\n parsingFlags: {},\n gtJson: {\n parsingFlags: GT_PARSING_FLAGS_DEFAULT,\n },\n };\n\n mergedOptions.options = {\n ...mergedOptions.options,\n mintlify: {\n ...mergedOptions.options?.mintlify,\n inferTitleFromFilename:\n gtConfig.options?.mintlify?.inferTitleFromFilename ||\n mergedOptions.options?.mintlify?.inferTitleFromFilename,\n },\n experimentalLocalizeStaticImports:\n gtConfig.options?.experimentalLocalizeStaticImports ||\n flags.experimentalLocalizeStaticImports,\n experimentalLocalizeStaticUrls:\n gtConfig.options?.experimentalLocalizeStaticUrls ||\n flags.experimentalLocalizeStaticUrls,\n experimentalLocalizeRelativeAssets:\n gtConfig.options?.experimentalLocalizeRelativeAssets ||\n flags.experimentalLocalizeRelativeAssets,\n experimentalHideDefaultLocale:\n gtConfig.options?.experimentalHideDefaultLocale ||\n flags.experimentalHideDefaultLocale,\n experimentalFlattenJsonFiles:\n gtConfig.options?.experimentalFlattenJsonFiles ||\n flags.experimentalFlattenJsonFiles,\n experimentalClearLocaleDirs:\n gtConfig.options?.experimentalClearLocaleDirs ||\n flags.experimentalClearLocaleDirs,\n clearLocaleDirsExclude:\n gtConfig.options?.clearLocaleDirsExclude || flags.clearLocaleDirsExclude,\n saveLocal: flags.saveLocal ?? gtConfig.options?.saveLocal ?? true,\n };\n\n if (\n mergedOptions.omitConfigIds &&\n (mergedOptions.publish === true ||\n mergedOptions.files.gtJson.publish === true)\n ) {\n logger.warn(\n chalk.yellow(\n 'Config IDs will be omitted even though CDN publishing is enabled. Remote cache/CDN consumers may load the latest available translations instead of a pinned version.'\n )\n );\n }\n\n // Add additional options if provided\n if (mergedOptions.options) {\n if (mergedOptions.options.jsonSchema) {\n for (const fileGlob of Object.keys(mergedOptions.options.jsonSchema)) {\n const jsonSchema = mergedOptions.options.jsonSchema[fileGlob];\n if (jsonSchema.preset) {\n mergedOptions.options.jsonSchema[fileGlob] = {\n ...generatePreset(jsonSchema.preset, 'json'),\n ...jsonSchema,\n };\n }\n }\n }\n if (mergedOptions.options.yamlSchema) {\n for (const fileGlob of Object.keys(mergedOptions.options.yamlSchema)) {\n const yamlSchema = mergedOptions.options.yamlSchema[fileGlob];\n if (yamlSchema.preset) {\n mergedOptions.options.yamlSchema[fileGlob] = {\n ...generatePreset(yamlSchema.preset, 'yaml'),\n ...yamlSchema,\n };\n }\n }\n }\n }\n\n // Add parsing options if not provided\n mergedOptions.parsingOptions = mergedOptions.parsingOptions || {};\n mergedOptions.parsingOptions.conditionNames = mergedOptions.parsingOptions\n .conditionNames || [\n 'development',\n 'browser',\n 'module',\n 'import',\n 'require',\n 'default',\n ];\n\n // Add branch options if not provided\n const branchOptions = mergedOptions.branchOptions || {};\n // If --branch is set, enable branching\n branchOptions.enabled =\n flags.enableBranching ??\n gtConfig.branchOptions?.enabled ??\n (flags.branch ? true : false);\n branchOptions.currentBranch =\n flags.branch ?? gtConfig.branchOptions?.currentBranch ?? undefined;\n branchOptions.autoDetectBranches = flags.disableBranchDetection\n ? false\n : (gtConfig.branchOptions?.autoDetectBranches ?? true);\n branchOptions.remoteName =\n flags.remoteName ??\n gtConfig.branchOptions?.remoteName ??\n DEFAULT_GIT_REMOTE_NAME;\n mergedOptions.branchOptions = branchOptions;\n\n // Map -m/--message flag to tagMessage\n if (flags.message) {\n mergedOptions.tagMessage = flags.message;\n }\n\n // Resolve tag:\n // --tag (bare) or -m without --tag: try git SHA, fall back to random hex\n // --tag <value>: use as-is\n // No flags: no tag\n if (flags.tag === true || (!flags.tag && mergedOptions.tagMessage)) {\n try {\n mergedOptions.tag = execSync('git rev-parse --short HEAD', {\n encoding: 'utf-8',\n }).trim();\n // If no message provided, use git commit message\n if (!mergedOptions.tagMessage) {\n mergedOptions.tagMessage = execSync('git log -1 --format=%s', {\n encoding: 'utf-8',\n }).trim();\n }\n } catch {\n // Not in a git repo or git unavailable — fall back to random hex\n mergedOptions.tag = crypto.randomBytes(4).toString('hex');\n }\n }\n\n mergedOptions.configDirectory = path.join(cwd, '.gt');\n\n validateSettings(mergedOptions);\n\n // Keep both clients on the same resolved credentials while consumers migrate.\n configureApiClient({\n projectId: mergedOptions.projectId,\n apiKey: mergedOptions.apiKey,\n baseUrl: mergedOptions.baseUrl,\n customMapping: mergedOptions.customMapping,\n });\n gt.setConfig({\n projectId: mergedOptions.projectId,\n apiKey: mergedOptions.apiKey,\n baseUrl: mergedOptions.baseUrl,\n sourceLocale: mergedOptions.defaultLocale,\n customMapping: mergedOptions.customMapping,\n });\n\n return mergedOptions;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"generateSettings.js","names":[],"sources":["../../src/config/generateSettings.ts"],"sourcesContent":["import {\n displayProjectId,\n exitSync,\n logErrorAndExit,\n warnApiKeyInConfig,\n warnDeprecatedField,\n} from '../console/logging.js';\nimport { loadConfig } from '../fs/config/loadConfig.js';\nimport { FilesOptions, Settings } from '../types/index.js';\nimport {\n defaultBaseUrl,\n libraryDefaultLocale,\n} from 'generaltranslation/internal';\nimport { resolveFiles } from '../fs/config/parseFilesConfig.js';\nimport { validateSettings } from './validateSettings.js';\nimport {\n DEFAULT_GIT_REMOTE_NAME,\n GT_DASHBOARD_URL,\n} from '../utils/constants.js';\nimport { resolveProjectId } from '../fs/utils.js';\nimport crypto from 'node:crypto';\nimport { execSync } from 'node:child_process';\nimport path from 'node:path';\nimport chalk from 'chalk';\nimport { resolveConfig } from './resolveConfig.js';\nimport { configureApiClient } from '../utils/api.js';\nimport { gt } from '../utils/gt.js';\nimport { generatePreset } from './optionPresets.js';\nimport { GT_PARSING_FLAGS_DEFAULT } from './defaults.js';\nimport { normalizeFilesOptions } from '../formats/files/transformFormat.js';\nimport { determineLibrary } from '../fs/determineFramework/index.js';\nimport { logger } from '../console/logger.js';\n\nexport const DEFAULT_SRC_PATTERNS = [\n 'src/**/*.{js,jsx,ts,tsx}',\n 'app/**/*.{js,jsx,ts,tsx}',\n 'pages/**/*.{js,jsx,ts,tsx}',\n 'components/**/*.{js,jsx,ts,tsx}',\n];\n\nexport const DEFAULT_PYTHON_SRC_PATTERNS = ['**/*.py'];\nexport const DEFAULT_PYTHON_SRC_EXCLUDES = [\n 'venv/**',\n '.venv/**',\n '__pycache__/**',\n '**/migrations/**',\n '**/tests/**',\n '**/test_*.py',\n '**/*_test.py',\n];\n\ntype GenerateSettingsInput = Partial<Omit<Settings, 'tag'>> & {\n config?: string;\n projectId?: string;\n locales?: string[];\n options?: Settings['options'];\n files?: unknown;\n publish?: boolean;\n omitConfigIds?: boolean;\n saveLocal?: boolean;\n tag?: string | boolean;\n message?: string;\n branch?: string;\n enableBranching?: boolean;\n disableBranchDetection?: boolean;\n remoteName?: string;\n experimentalLocalizeStaticImports?: boolean;\n experimentalLocalizeStaticUrls?: boolean;\n experimentalLocalizeRelativeAssets?: boolean;\n experimentalHideDefaultLocale?: boolean;\n experimentalFlattenJsonFiles?: boolean;\n experimentalClearLocaleDirs?: boolean;\n clearLocaleDirsExclude?: string[];\n [key: string]: unknown;\n};\n\nfunction hasConfiguredTranslationFiles(files: unknown): boolean {\n if (!files || typeof files !== 'object' || Array.isArray(files)) {\n return false;\n }\n return Object.keys(files).some((key) => key !== 'gt');\n}\n\n/**\n * Generates settings from any\n * @param flags - The CLI flags to generate settings from\n * @param cwd - The current working directory\n * @param options - Additional options\n * @param options.requireConfig - If true, exit with an error when no config file is found\n * @returns The generated settings\n */\nexport async function generateSettings(\n flags: GenerateSettingsInput,\n cwd: string = process.cwd(),\n options?: { requireConfig?: boolean }\n): Promise<Settings> {\n // Load config file\n let gtConfig: GenerateSettingsInput = {};\n\n if (flags.config && !flags.config.endsWith('.json')) {\n flags.config = `${flags.config}.json`;\n }\n if (flags.config) {\n gtConfig = loadConfig(flags.config);\n } else {\n const config = resolveConfig(cwd);\n if (config) {\n gtConfig = config.config as GenerateSettingsInput;\n flags.config = config.path;\n } else {\n if (options?.requireConfig) {\n return logErrorAndExit(\n 'No gt.config.json file was found. Run `npx gt init` to create one, pass --config, or run this command from your project root.'\n );\n }\n gtConfig = {};\n }\n }\n\n // Warn if apiKey is present in gt.config.json\n if (gtConfig.apiKey) {\n warnApiKeyInConfig(flags.config ?? 'gt.config.json');\n exitSync(1);\n }\n const projectIdEnv = resolveProjectId();\n // Resolve mismatched projectIds\n if (\n gtConfig.projectId &&\n flags.projectId &&\n gtConfig.projectId !== flags.projectId\n ) {\n logErrorAndExit(\n `Project ID mismatch: gt.config.json uses ${chalk.green(gtConfig.projectId)}, but the CLI flag uses ${chalk.green(flags.projectId)}. Use the same projectId in all configs.`\n );\n } else if (\n gtConfig.projectId &&\n projectIdEnv &&\n gtConfig.projectId !== projectIdEnv\n ) {\n logErrorAndExit(\n `Project ID mismatch: gt.config.json uses ${chalk.green(gtConfig.projectId)}, but GT_PROJECT_ID uses ${chalk.green(projectIdEnv)}. Use the same projectId in all configs.`\n );\n }\n\n if (\n flags.options?.docsUrlPattern &&\n !flags.options?.docsUrlPattern.includes('[locale]')\n ) {\n logErrorAndExit(\n 'Static URLs could not be localized because the URL pattern is missing \"[locale]\". Add \"[locale]\" where the locale should appear in the generated URL.'\n );\n }\n\n if (\n flags.options?.docsImportPattern &&\n !flags.options?.docsImportPattern.includes('[locale]')\n ) {\n logErrorAndExit(\n 'Static imports could not be localized because the import pattern is missing \"[locale]\". Add \"[locale]\" where the locale should appear in the generated import path.'\n );\n }\n\n if (flags.options?.copyFiles) {\n for (const file of flags.options.copyFiles) {\n if (!file.includes('[locale]')) {\n logErrorAndExit(\n 'Files could not be copied because the file path is missing \"[locale]\". Add \"[locale]\" where the locale should appear in the copied path.'\n );\n }\n }\n }\n\n // Warn on deprecated includeSourceCodeContext\n const configuredFiles = gtConfig.files as FilesOptions | undefined;\n if (configuredFiles?.gt?.includeSourceCodeContext != null) {\n warnDeprecatedField(\n 'files.gt.includeSourceCodeContext',\n 'files.gt.parsingFlags.includeSourceCodeContext'\n );\n }\n\n // merge options\n const mergedOptions: Settings = { ...gtConfig, ...flags } as Settings;\n\n if (\n determineLibrary().library === 'base' &&\n !hasConfiguredTranslationFiles(mergedOptions.files)\n ) {\n logger.warn(\n chalk.yellow(\n 'No package.json or Python project file found in the current directory. Run this command from the root of your project.'\n )\n );\n }\n\n // Add defaultLocale if not provided\n mergedOptions.defaultLocale =\n mergedOptions.defaultLocale || libraryDefaultLocale;\n\n // merge locales\n mergedOptions.locales = Array.from(\n new Set([...(gtConfig.locales || []), ...(flags.locales || [])])\n );\n // Separate defaultLocale from locales\n mergedOptions.locales = mergedOptions.locales.filter(\n (locale) => locale !== mergedOptions.defaultLocale\n );\n\n // Add apiKey if not provided\n mergedOptions.apiKey = mergedOptions.apiKey || process.env.GT_API_KEY;\n\n // Add projectId if not provided\n mergedOptions.projectId = mergedOptions.projectId || resolveProjectId();\n\n // Add baseUrl if not provided\n mergedOptions.baseUrl = mergedOptions.baseUrl || defaultBaseUrl;\n\n // Add dashboardUrl if not provided\n mergedOptions.dashboardUrl = mergedOptions.dashboardUrl || GT_DASHBOARD_URL;\n\n // Add locales if not provided\n mergedOptions.locales = mergedOptions.locales || [];\n\n // Only set config path if one was actually found or explicitly provided.\n // Do not default to a phantom path — that would cause downstream writes\n // (e.g. updateConfig) to silently create a config file.\n if (!mergedOptions.config) {\n mergedOptions.config = '';\n }\n\n // Display projectId if present\n if (mergedOptions.projectId) {\n displayProjectId(mergedOptions.projectId);\n }\n\n // Add stageTranslations if not provided\n // For human review, always stage the project\n mergedOptions.stageTranslations = mergedOptions.stageTranslations ?? false;\n\n // Top-level default for whether translated files require approval before use.\n // Effective policy is hash-changing, so reject anything but a real boolean.\n if (\n mergedOptions.requiresReview !== undefined &&\n typeof mergedOptions.requiresReview !== 'boolean'\n ) {\n logErrorAndExit(\n 'requiresReview in gt.config.json must be a boolean. Use files.<type>.requiresReview for glob-scoped overrides.'\n );\n }\n mergedOptions.requiresReview = mergedOptions.requiresReview ?? false;\n\n // Add publish — only set if explicitly configured or passed via flag.\n // When neither is set, leave undefined so the publish step knows\n // there is no global publish intent.\n if (flags.publish) {\n mergedOptions.publish = true;\n } else if (gtConfig.publish !== undefined) {\n mergedOptions.publish = gtConfig.publish;\n } else {\n mergedOptions.publish = undefined;\n }\n\n mergedOptions.omitConfigIds =\n flags.omitConfigIds === true || gtConfig.omitConfigIds === true;\n\n // Don't default src here — each pipeline (JS/Python) has its own defaults.\n // Only set src if the user explicitly provided it via flags or config.\n\n // Resolve all glob patterns in the files object\n const compositePatterns = Object.entries(\n mergedOptions.options?.jsonSchema || {}\n )\n .filter(([, schema]) => schema.composite)\n .map(([key]) => key);\n mergedOptions.files = mergedOptions.files\n ? resolveFiles(\n normalizeFilesOptions(mergedOptions.files as FilesOptions),\n mergedOptions.defaultLocale,\n mergedOptions.locales,\n cwd,\n compositePatterns,\n mergedOptions.requiresReview\n )\n : {\n resolvedPaths: {},\n placeholderPaths: {},\n transformPaths: {},\n transformFormats: {},\n publishPaths: new Set<string>(),\n unpublishPaths: new Set<string>(),\n requiresReviewPaths: new Set<string>(),\n parsingFlags: {},\n gtJson: {\n parsingFlags: GT_PARSING_FLAGS_DEFAULT,\n },\n };\n\n mergedOptions.options = {\n ...mergedOptions.options,\n mintlify: {\n ...mergedOptions.options?.mintlify,\n inferTitleFromFilename:\n gtConfig.options?.mintlify?.inferTitleFromFilename ||\n mergedOptions.options?.mintlify?.inferTitleFromFilename,\n },\n experimentalLocalizeStaticImports:\n gtConfig.options?.experimentalLocalizeStaticImports ||\n flags.experimentalLocalizeStaticImports,\n experimentalLocalizeStaticUrls:\n gtConfig.options?.experimentalLocalizeStaticUrls ||\n flags.experimentalLocalizeStaticUrls,\n experimentalLocalizeRelativeAssets:\n gtConfig.options?.experimentalLocalizeRelativeAssets ||\n flags.experimentalLocalizeRelativeAssets,\n experimentalHideDefaultLocale:\n gtConfig.options?.experimentalHideDefaultLocale ||\n flags.experimentalHideDefaultLocale,\n experimentalFlattenJsonFiles:\n gtConfig.options?.experimentalFlattenJsonFiles ||\n flags.experimentalFlattenJsonFiles,\n experimentalClearLocaleDirs:\n gtConfig.options?.experimentalClearLocaleDirs ||\n flags.experimentalClearLocaleDirs,\n clearLocaleDirsExclude:\n gtConfig.options?.clearLocaleDirsExclude || flags.clearLocaleDirsExclude,\n saveLocal: flags.saveLocal ?? gtConfig.options?.saveLocal ?? false,\n };\n\n if (\n mergedOptions.omitConfigIds &&\n (mergedOptions.publish === true ||\n mergedOptions.files.gtJson.publish === true)\n ) {\n logger.warn(\n chalk.yellow(\n 'Config IDs will be omitted even though CDN publishing is enabled. Remote cache/CDN consumers may load the latest available translations instead of a pinned version.'\n )\n );\n }\n\n // Add additional options if provided\n if (mergedOptions.options) {\n if (mergedOptions.options.jsonSchema) {\n for (const fileGlob of Object.keys(mergedOptions.options.jsonSchema)) {\n const jsonSchema = mergedOptions.options.jsonSchema[fileGlob];\n if (jsonSchema.preset) {\n mergedOptions.options.jsonSchema[fileGlob] = {\n ...generatePreset(jsonSchema.preset, 'json'),\n ...jsonSchema,\n };\n }\n }\n }\n if (mergedOptions.options.yamlSchema) {\n for (const fileGlob of Object.keys(mergedOptions.options.yamlSchema)) {\n const yamlSchema = mergedOptions.options.yamlSchema[fileGlob];\n if (yamlSchema.preset) {\n mergedOptions.options.yamlSchema[fileGlob] = {\n ...generatePreset(yamlSchema.preset, 'yaml'),\n ...yamlSchema,\n };\n }\n }\n }\n }\n\n // Add parsing options if not provided\n mergedOptions.parsingOptions = mergedOptions.parsingOptions || {};\n mergedOptions.parsingOptions.conditionNames = mergedOptions.parsingOptions\n .conditionNames || [\n 'development',\n 'browser',\n 'module',\n 'import',\n 'require',\n 'default',\n ];\n\n // Add branch options if not provided\n const branchOptions = mergedOptions.branchOptions || {};\n // If --branch is set, enable branching\n branchOptions.enabled =\n flags.enableBranching ??\n gtConfig.branchOptions?.enabled ??\n (flags.branch ? true : false);\n branchOptions.currentBranch =\n flags.branch ?? gtConfig.branchOptions?.currentBranch ?? undefined;\n branchOptions.autoDetectBranches = flags.disableBranchDetection\n ? false\n : (gtConfig.branchOptions?.autoDetectBranches ?? true);\n branchOptions.remoteName =\n flags.remoteName ??\n gtConfig.branchOptions?.remoteName ??\n DEFAULT_GIT_REMOTE_NAME;\n mergedOptions.branchOptions = branchOptions;\n\n // Map -m/--message flag to tagMessage\n if (flags.message) {\n mergedOptions.tagMessage = flags.message;\n }\n\n // Resolve tag:\n // --tag (bare) or -m without --tag: try git SHA, fall back to random hex\n // --tag <value>: use as-is\n // No flags: no tag\n if (flags.tag === true || (!flags.tag && mergedOptions.tagMessage)) {\n try {\n mergedOptions.tag = execSync('git rev-parse --short HEAD', {\n encoding: 'utf-8',\n }).trim();\n // If no message provided, use git commit message\n if (!mergedOptions.tagMessage) {\n mergedOptions.tagMessage = execSync('git log -1 --format=%s', {\n encoding: 'utf-8',\n }).trim();\n }\n } catch {\n // Not in a git repo or git unavailable — fall back to random hex\n mergedOptions.tag = crypto.randomBytes(4).toString('hex');\n }\n }\n\n mergedOptions.configDirectory = path.join(cwd, '.gt');\n\n validateSettings(mergedOptions);\n\n // Keep both clients on the same resolved credentials while consumers migrate.\n configureApiClient({\n projectId: mergedOptions.projectId,\n apiKey: mergedOptions.apiKey,\n baseUrl: mergedOptions.baseUrl,\n customMapping: mergedOptions.customMapping,\n });\n gt.setConfig({\n projectId: mergedOptions.projectId,\n apiKey: mergedOptions.apiKey,\n baseUrl: mergedOptions.baseUrl,\n sourceLocale: mergedOptions.defaultLocale,\n customMapping: mergedOptions.customMapping,\n });\n\n return mergedOptions;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const PACKAGE_VERSION = "2.20.
|
|
1
|
+
export declare const PACKAGE_VERSION = "2.20.4";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.js","names":[],"sources":["../../src/generated/version.ts"],"sourcesContent":["// This file is auto-generated. Do not edit manually.\nexport const PACKAGE_VERSION = '2.20.
|
|
1
|
+
{"version":3,"file":"version.js","names":[],"sources":["../../src/generated/version.ts"],"sourcesContent":["// This file is auto-generated. Do not edit manually.\nexport const PACKAGE_VERSION = '2.20.4';\n"],"mappings":";AACA,MAAa,kBAAkB"}
|
|
@@ -2,12 +2,6 @@ import type { AdditionalOptions } from '../types/index.js';
|
|
|
2
2
|
type AnchorIdSettings = {
|
|
3
3
|
options?: Pick<AdditionalOptions, 'experimentalAddHeaderAnchorIds'>;
|
|
4
4
|
};
|
|
5
|
-
/** A source range on a single line, as 1-based inclusive/exclusive columns. */
|
|
6
|
-
interface ColumnRange {
|
|
7
|
-
line: number;
|
|
8
|
-
startColumn: number;
|
|
9
|
-
endColumn: number;
|
|
10
|
-
}
|
|
11
5
|
/**
|
|
12
6
|
* Represents a heading with its position and metadata
|
|
13
7
|
*/
|
|
@@ -24,8 +18,6 @@ export interface HeadingInfo {
|
|
|
24
18
|
startColumn: number;
|
|
25
19
|
/** 1-based column just past the text, before any closing `##`; -1 if unknown. */
|
|
26
20
|
textEndColumn: number;
|
|
27
|
-
/** `id` attribute of a wrapper element already anchoring this heading. */
|
|
28
|
-
wrapperId: ColumnRange | null;
|
|
29
21
|
/** Whether the author wrote an explicit `{#id}`. */
|
|
30
22
|
explicit: boolean;
|
|
31
23
|
}
|
|
@@ -7,6 +7,12 @@ const ATX_HEADING = /^([ \t]*)(#{1,6}[ \t]+)(.*)$/;
|
|
|
7
7
|
/** A trailing custom anchor ID, in either the plain or MDX-escaped form. */
|
|
8
8
|
const TRAILING_ANCHOR = /\s*(?:\\\{#[^}]+\\\}|\{#[^}]+\})\s*$/;
|
|
9
9
|
/**
|
|
10
|
+
* Deepest indentation at which Mintlify still reads `## Heading {#id}`. It is
|
|
11
|
+
* the CommonMark limit for a heading; MDX itself accepts deeper headings, so
|
|
12
|
+
* past it the `{#id}` reaches the expression parser and fails to compile.
|
|
13
|
+
*/
|
|
14
|
+
const MAX_MINTLIFY_HEADING_INDENT = 3;
|
|
15
|
+
/**
|
|
10
16
|
* Generates a slug from heading text
|
|
11
17
|
*/
|
|
12
18
|
function generateSlug(text) {
|
|
@@ -43,7 +49,6 @@ function extractHeadingsWithFallback(mdxContent) {
|
|
|
43
49
|
endLine: index + 1,
|
|
44
50
|
startColumn: indent.length + 1,
|
|
45
51
|
textEndColumn: line.length + 1,
|
|
46
|
-
wrapperId: null,
|
|
47
52
|
explicit: explicitId !== void 0
|
|
48
53
|
});
|
|
49
54
|
});
|
|
@@ -79,22 +84,6 @@ function assignUniqueSlugs(headings) {
|
|
|
79
84
|
}
|
|
80
85
|
}
|
|
81
86
|
/**
|
|
82
|
-
* Finds the `id` of a wrapper element already anchoring this heading. Requiring
|
|
83
|
-
* the heading to be its only child rules out containers like `<Tab>`.
|
|
84
|
-
*/
|
|
85
|
-
function findWrapperId(heading, parent) {
|
|
86
|
-
if (!parent || parent.type !== "mdxJsxFlowElement") return null;
|
|
87
|
-
const element = parent;
|
|
88
|
-
if (element.children.length !== 1 || element.children[0] !== heading) return null;
|
|
89
|
-
const position = element.attributes.find((attribute) => attribute.type === "mdxJsxAttribute" && attribute.name === "id")?.position;
|
|
90
|
-
if (!position || position.start.line !== position.end.line) return null;
|
|
91
|
-
return {
|
|
92
|
-
line: position.start.line,
|
|
93
|
-
startColumn: position.start.column,
|
|
94
|
-
endColumn: position.end.column
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
87
|
* Extracts heading information from content (read-only, no modifications).
|
|
99
88
|
* Source and translation are matched by position, so both must parse the same
|
|
100
89
|
* way — the fallback extractor misses headings nested in JSX.
|
|
@@ -108,7 +97,7 @@ function extractHeadingInfo(mdxContent) {
|
|
|
108
97
|
}
|
|
109
98
|
const headings = [];
|
|
110
99
|
let position = 0;
|
|
111
|
-
visit(ast, "heading", (heading
|
|
100
|
+
visit(ast, "heading", (heading) => {
|
|
112
101
|
const { cleanedText, explicitId } = parseHeadingContent(extractHeadingText(heading));
|
|
113
102
|
if (!cleanedText && !explicitId) return;
|
|
114
103
|
const lastChild = heading.children[heading.children.length - 1];
|
|
@@ -121,7 +110,6 @@ function extractHeadingInfo(mdxContent) {
|
|
|
121
110
|
endLine: heading.position?.end.line ?? -1,
|
|
122
111
|
startColumn: heading.position?.start.column ?? 1,
|
|
123
112
|
textEndColumn: lastChild?.position?.end.column ?? heading.position?.end.column ?? -1,
|
|
124
|
-
wrapperId: findWrapperId(heading, parent),
|
|
125
113
|
explicit: explicitId !== void 0
|
|
126
114
|
});
|
|
127
115
|
});
|
|
@@ -133,7 +121,7 @@ function extractHeadingInfo(mdxContent) {
|
|
|
133
121
|
*/
|
|
134
122
|
function addExplicitAnchorIds(translatedContent, sourceHeadingMap, settings, sourcePath, translatedPath, fileTypeHint) {
|
|
135
123
|
const addedIds = [];
|
|
136
|
-
const
|
|
124
|
+
const mintlifyMode = settings?.options?.experimentalAddHeaderAnchorIds === "mintlify";
|
|
137
125
|
const translatedHeadings = extractHeadingInfo(translatedContent);
|
|
138
126
|
if (sourceHeadingMap.length !== translatedHeadings.length) {
|
|
139
127
|
const sourceFile = sourcePath ? `Source file: ${sourcePath}` : "Source file";
|
|
@@ -155,17 +143,16 @@ function addExplicitAnchorIds(translatedContent, sourceHeadingMap, settings, sou
|
|
|
155
143
|
}
|
|
156
144
|
});
|
|
157
145
|
const translatedIsMdx = translatedPath ? translatedPath.toLowerCase().endsWith(".mdx") : true;
|
|
158
|
-
const shouldEscapeAnchors = fileTypeHint === "mdx" ? true : fileTypeHint === "md" ? false : translatedIsMdx;
|
|
146
|
+
const shouldEscapeAnchors = mintlifyMode ? false : fileTypeHint === "mdx" ? true : fileTypeHint === "md" ? false : translatedIsMdx;
|
|
159
147
|
if (idMappings.size === 0) {
|
|
160
|
-
const content =
|
|
148
|
+
const content = normalizeInlineAnchors(translatedContent, shouldEscapeAnchors);
|
|
161
149
|
return {
|
|
162
150
|
content,
|
|
163
151
|
hasChanges: content !== translatedContent,
|
|
164
152
|
addedIds: []
|
|
165
153
|
};
|
|
166
154
|
}
|
|
167
|
-
|
|
168
|
-
if (!useDivWrapping) content = normalizeInlineAnchors(content, shouldEscapeAnchors);
|
|
155
|
+
const content = normalizeInlineAnchors(applyAnchorIds(translatedContent, translatedHeadings, idMappings, mintlifyMode, shouldEscapeAnchors), shouldEscapeAnchors);
|
|
169
156
|
return {
|
|
170
157
|
content,
|
|
171
158
|
hasChanges: content !== translatedContent,
|
|
@@ -176,7 +163,7 @@ function addExplicitAnchorIds(translatedContent, sourceHeadingMap, settings, sou
|
|
|
176
163
|
* Writes anchor IDs onto the translated document, locating headings by parser
|
|
177
164
|
* line position rather than by text. Edits run bottom-up to keep lines valid.
|
|
178
165
|
*/
|
|
179
|
-
function applyAnchorIds(translatedContent, translatedHeadings, idMappings,
|
|
166
|
+
function applyAnchorIds(translatedContent, translatedHeadings, idMappings, mintlifyMode, escapeAnchors) {
|
|
180
167
|
const lines = translatedContent.split("\n");
|
|
181
168
|
const ordered = [...translatedHeadings].sort((a, b) => b.startLine - a.startLine);
|
|
182
169
|
for (const heading of ordered) {
|
|
@@ -184,26 +171,13 @@ function applyAnchorIds(translatedContent, translatedHeadings, idMappings, useDi
|
|
|
184
171
|
if (!mapping) continue;
|
|
185
172
|
if (heading.startLine < 1 || heading.endLine > lines.length) continue;
|
|
186
173
|
const index = heading.startLine - 1;
|
|
187
|
-
if (
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if (heading.textEndColumn >= 1) {
|
|
195
|
-
const { text, trailer } = splitHeadingLine(lines[index], heading);
|
|
196
|
-
lines[index] = `${text}${trailer}`;
|
|
197
|
-
}
|
|
198
|
-
if (heading.wrapperId) {
|
|
199
|
-
const { line, startColumn, endColumn } = heading.wrapperId;
|
|
200
|
-
const wrapper = lines[line - 1];
|
|
201
|
-
lines[line - 1] = wrapper.slice(0, startColumn - 1) + `id="${mapping.id}"` + wrapper.slice(endColumn - 1);
|
|
202
|
-
continue;
|
|
203
|
-
}
|
|
204
|
-
const indent = lines[index].slice(0, Math.max(0, heading.startColumn - 1));
|
|
205
|
-
const body = lines.slice(index, heading.endLine).map((line) => ` ${line}`);
|
|
206
|
-
lines.splice(index, heading.endLine - heading.startLine + 1, `${indent}<div id="${mapping.id}">`, ...body, `${indent}</div>`);
|
|
174
|
+
if (heading.textEndColumn < 1) continue;
|
|
175
|
+
if (mintlifyMode && heading.endLine > heading.startLine) continue;
|
|
176
|
+
const anchor = escapeAnchors && !mapping.explicit ? `\\{#${mapping.id}\\}` : `{#${mapping.id}}`;
|
|
177
|
+
const { text, trailer } = splitHeadingLine(lines[index], heading);
|
|
178
|
+
const line = `${text} ${anchor}${trailer}`;
|
|
179
|
+
const indent = line.match(/^[ \t]*/)?.[0].length ?? 0;
|
|
180
|
+
lines[index] = mintlifyMode && indent > MAX_MINTLIFY_HEADING_INDENT ? line.trimStart() : line;
|
|
207
181
|
}
|
|
208
182
|
return lines.join("\n");
|
|
209
183
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"addExplicitAnchorIds.js","names":[],"sources":["../../src/utils/addExplicitAnchorIds.ts"],"sourcesContent":["import { visit } from 'unist-util-visit';\nimport type { Heading, Node } from 'mdast';\nimport type { MdxJsxFlowElement } from 'mdast-util-mdx-jsx';\nimport { logger } from '../console/logger.js';\nimport type { AdditionalOptions } from '../types/index.js';\nimport {\n forEachLineOutsideCodeFences,\n mapLinesOutsideCodeFences,\n parseMdxTolerantly,\n} from './mdxAnchorSyntax.js';\n\ntype AnchorIdSettings = {\n options?: Pick<AdditionalOptions, 'experimentalAddHeaderAnchorIds'>;\n};\n\n/** An ATX heading line, split into indentation, marker and text. */\nconst ATX_HEADING = /^([ \\t]*)(#{1,6}[ \\t]+)(.*)$/;\n\n/** A trailing custom anchor ID, in either the plain or MDX-escaped form. */\nconst TRAILING_ANCHOR = /\\s*(?:\\\\\\{#[^}]+\\\\\\}|\\{#[^}]+\\})\\s*$/;\n\n/**\n * Generates a slug from heading text\n */\nfunction generateSlug(text: string): string {\n return text\n .toLowerCase()\n .replace(/[^\\w\\s-]/g, '') // Remove special chars except spaces and hyphens\n .trim()\n .replace(/\\s+/g, '-') // Replace spaces with hyphens\n .replace(/-+/g, '-') // Replace multiple hyphens with single hyphen\n .replace(/^-|-$/g, ''); // Remove leading/trailing hyphens\n}\n\n/**\n * Extracts text content from heading nodes\n */\nfunction extractHeadingText(heading: Heading): string {\n let text = '';\n\n visit(heading, ['text', 'inlineCode'], (node: Node) => {\n if ('value' in node && typeof node.value === 'string') {\n text += node.value;\n }\n });\n\n return text;\n}\n\n/**\n * Line-by-line heading extractor used when MDX parsing fails outright.\n */\nfunction extractHeadingsWithFallback(mdxContent: string): HeadingInfo[] {\n const headings: HeadingInfo[] = [];\n let position = 0;\n\n forEachLineOutsideCodeFences(mdxContent, (line, index) => {\n const headingMatch = line.match(ATX_HEADING);\n if (!headingMatch) return;\n\n const [, indent, marker, rawText] = headingMatch;\n const { cleanedText, explicitId } = parseHeadingContent(rawText);\n if (!cleanedText && !explicitId) return;\n\n headings.push({\n text: cleanedText,\n level: marker.trim().length,\n slug: explicitId ?? generateSlug(cleanedText),\n position: position++,\n startLine: index + 1,\n endLine: index + 1,\n startColumn: indent.length + 1,\n // Without a parser there is nothing finer to go on than end of line.\n textEndColumn: line.length + 1,\n wrapperId: null,\n explicit: explicitId !== undefined,\n });\n });\n\n assignUniqueSlugs(headings);\n return headings;\n}\n\nfunction parseHeadingContent(text: string): {\n cleanedText: string;\n explicitId?: string;\n} {\n // Support both {#id} and escaped \\{#id\\} forms\n const anchorMatch = text.match(/(\\\\\\{#([^}]+)\\\\\\}|\\{#([^}]+)\\})\\s*$/);\n\n if (!anchorMatch) {\n return { cleanedText: text };\n }\n\n const explicitId = anchorMatch[2] || anchorMatch[3];\n const cleanedText = text.replace(anchorMatch[0], '').trimEnd();\n\n return { cleanedText, explicitId };\n}\n\n/**\n * Suffixes repeated slugs `-2`, `-3`, ... as Mintlify does. Author-written IDs\n * are never renumbered, only reserved.\n */\nfunction assignUniqueSlugs(headings: HeadingInfo[]): void {\n // Reserve every explicit ID up front, including ones later in the document,\n // so a generated slug never claims an ID an author asked for.\n const used = new Set(\n headings\n .filter((heading) => heading.explicit)\n .map((heading) => heading.slug)\n );\n\n for (const heading of headings) {\n if (heading.explicit) continue;\n\n // Headings with no slug-able characters would produce id=\"\".\n const base = heading.slug || 'section';\n let slug = base;\n let suffix = 1;\n while (used.has(slug)) {\n suffix += 1;\n slug = `${base}-${suffix}`;\n }\n\n heading.slug = slug;\n used.add(slug);\n }\n}\n\n/** A source range on a single line, as 1-based inclusive/exclusive columns. */\ninterface ColumnRange {\n line: number;\n startColumn: number;\n endColumn: number;\n}\n\n/**\n * Represents a heading with its position and metadata\n */\nexport interface HeadingInfo {\n text: string;\n level: number;\n slug: string;\n position: number;\n /** 1-based line the heading starts on. */\n startLine: number;\n /** 1-based line the heading ends on (differs from startLine for setext). */\n endLine: number;\n /** 1-based column of the heading marker; anything left of it is indentation. */\n startColumn: number;\n /** 1-based column just past the text, before any closing `##`; -1 if unknown. */\n textEndColumn: number;\n /** `id` attribute of a wrapper element already anchoring this heading. */\n wrapperId: ColumnRange | null;\n /** Whether the author wrote an explicit `{#id}`. */\n explicit: boolean;\n}\n\n/**\n * Finds the `id` of a wrapper element already anchoring this heading. Requiring\n * the heading to be its only child rules out containers like `<Tab>`.\n */\nfunction findWrapperId(\n heading: Heading,\n parent: Node | undefined\n): ColumnRange | null {\n if (!parent || parent.type !== 'mdxJsxFlowElement') return null;\n\n const element = parent as MdxJsxFlowElement;\n if (element.children.length !== 1 || element.children[0] !== heading) {\n return null;\n }\n\n const id = element.attributes.find(\n (attribute) =>\n attribute.type === 'mdxJsxAttribute' && attribute.name === 'id'\n );\n const position = id?.position;\n if (!position || position.start.line !== position.end.line) return null;\n\n return {\n line: position.start.line,\n startColumn: position.start.column,\n endColumn: position.end.column,\n };\n}\n\n/**\n * Extracts heading information from content (read-only, no modifications).\n * Source and translation are matched by position, so both must parse the same\n * way — the fallback extractor misses headings nested in JSX.\n */\nexport function extractHeadingInfo(mdxContent: string): HeadingInfo[] {\n let ast;\n try {\n ast = parseMdxTolerantly(mdxContent);\n } catch {\n // Fallback: line-by-line extraction skipping fenced code blocks\n return extractHeadingsWithFallback(mdxContent);\n }\n\n const headings: HeadingInfo[] = [];\n let position = 0;\n\n visit(ast, 'heading', (heading: Heading, _index, parent) => {\n const headingText = extractHeadingText(heading);\n const { cleanedText, explicitId } = parseHeadingContent(headingText);\n if (!cleanedText && !explicitId) return;\n\n const lastChild = heading.children[heading.children.length - 1];\n\n headings.push({\n text: cleanedText,\n level: heading.depth,\n slug: explicitId ?? generateSlug(cleanedText),\n position: position++,\n startLine: heading.position?.start.line ?? -1,\n endLine: heading.position?.end.line ?? -1,\n startColumn: heading.position?.start.column ?? 1,\n textEndColumn:\n lastChild?.position?.end.column ?? heading.position?.end.column ?? -1,\n wrapperId: findWrapperId(heading, parent),\n explicit: explicitId !== undefined,\n });\n });\n\n assignUniqueSlugs(headings);\n return headings;\n}\n\n/**\n * Applies anchor IDs to translated content based on source heading mapping\n */\nexport function addExplicitAnchorIds(\n translatedContent: string,\n sourceHeadingMap: HeadingInfo[],\n settings?: AnchorIdSettings,\n sourcePath?: string,\n translatedPath?: string,\n fileTypeHint?: 'md' | 'mdx'\n): {\n content: string;\n hasChanges: boolean;\n addedIds: Array<{ heading: string; id: string }>;\n} {\n const addedIds: Array<{ heading: string; id: string }> = [];\n const useDivWrapping =\n settings?.options?.experimentalAddHeaderAnchorIds === 'mintlify';\n\n // Extract headings from translated content\n const translatedHeadings = extractHeadingInfo(translatedContent);\n\n // Pre-processing validation: check if header counts match\n if (sourceHeadingMap.length !== translatedHeadings.length) {\n const sourceFile = sourcePath\n ? `Source file: ${sourcePath}`\n : 'Source file';\n const translatedFile = translatedPath\n ? `translated file: ${translatedPath}`\n : 'translated file';\n\n logger.warn(\n `Header count mismatch detected! ${sourceFile} has ${sourceHeadingMap.length} headers but ${translatedFile} has ${translatedHeadings.length} headers. ` +\n `This likely means your source file was edited after translation was requested, causing a mismatch between ` +\n `the number of headers in your source file vs the translated file. Re-translate this file to resolve the issue.`\n );\n }\n\n // Create ID mapping based on positional matching\n const idMappings = new Map<number, { id: string; explicit: boolean }>();\n sourceHeadingMap.forEach((sourceHeading, index) => {\n const translatedHeading = translatedHeadings[index];\n // Match by position and level for safety\n if (translatedHeading && translatedHeading.level === sourceHeading.level) {\n idMappings.set(index, {\n id: sourceHeading.slug,\n explicit: sourceHeading.explicit,\n });\n addedIds.push({\n heading: translatedHeading.text,\n id: sourceHeading.slug,\n });\n }\n });\n\n const translatedIsMdx = translatedPath\n ? translatedPath.toLowerCase().endsWith('.mdx')\n : true; // default to mdx-style escaping when unknown\n const shouldEscapeAnchors =\n fileTypeHint === 'mdx'\n ? true\n : fileTypeHint === 'md'\n ? false\n : translatedIsMdx;\n\n if (idMappings.size === 0) {\n // Normalize anchors the translation carried over.\n const content = useDivWrapping\n ? translatedContent\n : normalizeInlineAnchors(translatedContent, shouldEscapeAnchors);\n return {\n content,\n hasChanges: content !== translatedContent,\n addedIds: [],\n };\n }\n\n let content = applyAnchorIds(\n translatedContent,\n translatedHeadings,\n idMappings,\n useDivWrapping,\n shouldEscapeAnchors\n );\n\n if (!useDivWrapping) {\n content = normalizeInlineAnchors(content, shouldEscapeAnchors);\n }\n\n return {\n content,\n hasChanges: content !== translatedContent,\n addedIds,\n };\n}\n\n/**\n * Writes anchor IDs onto the translated document, locating headings by parser\n * line position rather than by text. Edits run bottom-up to keep lines valid.\n */\nfunction applyAnchorIds(\n translatedContent: string,\n translatedHeadings: HeadingInfo[],\n idMappings: Map<number, { id: string; explicit: boolean }>,\n useDivWrapping: boolean,\n escapeAnchors: boolean\n): string {\n const lines = translatedContent.split('\\n');\n\n const ordered = [...translatedHeadings].sort(\n (a, b) => b.startLine - a.startLine\n );\n\n for (const heading of ordered) {\n const mapping = idMappings.get(heading.position);\n if (!mapping) continue;\n if (heading.startLine < 1 || heading.endLine > lines.length) continue;\n\n const index = heading.startLine - 1;\n\n if (!useDivWrapping) {\n // Setext headings have no column to append to.\n if (heading.textEndColumn < 1) continue;\n\n const escape = escapeAnchors && !mapping.explicit;\n const anchor = escape ? `\\\\{#${mapping.id}\\\\}` : `{#${mapping.id}}`;\n const { text, trailer } = splitHeadingLine(lines[index], heading);\n\n lines[index] = `${text} ${anchor}${trailer}`;\n continue;\n }\n\n // In wrapper mode every heading gets a wrapper, including ones whose ID\n // the author wrote inline. Mintlify's `{#id}` pre-pass does not recognize\n // headings indented four or more spaces, which the MDX serializer produces\n // for headings nested in JSX, so a re-attached inline ID fails to compile.\n // A wrapper anchors the heading at any indentation. Drop any inline ID the\n // translation carried over so the two forms never appear together.\n if (heading.textEndColumn >= 1) {\n const { text, trailer } = splitHeadingLine(lines[index], heading);\n lines[index] = `${text}${trailer}`;\n }\n\n if (heading.wrapperId) {\n // Already wrapped: fix the ID in place rather than nesting another.\n const { line, startColumn, endColumn } = heading.wrapperId;\n const wrapper = lines[line - 1];\n lines[line - 1] =\n wrapper.slice(0, startColumn - 1) +\n `id=\"${mapping.id}\"` +\n wrapper.slice(endColumn - 1);\n continue;\n }\n\n const indent = lines[index].slice(0, Math.max(0, heading.startColumn - 1));\n const body = lines.slice(index, heading.endLine).map((line) => ` ${line}`);\n\n lines.splice(\n index,\n heading.endLine - heading.startLine + 1,\n `${indent}<div id=\"${mapping.id}\">`,\n ...body,\n `${indent}</div>`\n );\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Splits a heading line into its text, minus any trailing inline anchor, and\n * whatever follows the text (a closing `##` sequence, for instance).\n */\nfunction splitHeadingLine(\n line: string,\n heading: HeadingInfo\n): { text: string; trailer: string } {\n return {\n text: line.slice(0, heading.textEndColumn - 1).replace(TRAILING_ANCHOR, ''),\n trailer: line.slice(heading.textEndColumn - 1),\n };\n}\n\n/**\n * Normalizes every inline anchor: escaped for MDX, bare for Markdown.\n */\nfunction normalizeInlineAnchors(\n content: string,\n escapeAnchors: boolean\n): string {\n return mapLinesOutsideCodeFences(content, (line) => {\n const atx = line.match(ATX_HEADING);\n if (!atx) return line;\n\n const escaped = atx[3].match(/\\\\\\{#([A-Za-z0-9_-]+)\\\\\\}\\s*$/);\n const bare = atx[3].match(/(?<!\\\\)\\{#([A-Za-z0-9_-]+)\\}\\s*$/);\n\n if (escapeAnchors && bare) {\n const text = atx[3].replace(TRAILING_ANCHOR, '');\n return `${atx[1]}${atx[2]}${text} \\\\{#${bare[1]}\\\\}`;\n }\n if (!escapeAnchors && escaped) {\n const text = atx[3].replace(TRAILING_ANCHOR, '');\n return `${atx[1]}${atx[2]}${text} {#${escaped[1]}}`;\n }\n return line;\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"addExplicitAnchorIds.js","names":[],"sources":["../../src/utils/addExplicitAnchorIds.ts"],"sourcesContent":["import { visit } from 'unist-util-visit';\nimport type { Heading, Node } from 'mdast';\nimport { logger } from '../console/logger.js';\nimport type { AdditionalOptions } from '../types/index.js';\nimport {\n forEachLineOutsideCodeFences,\n mapLinesOutsideCodeFences,\n parseMdxTolerantly,\n} from './mdxAnchorSyntax.js';\n\ntype AnchorIdSettings = {\n options?: Pick<AdditionalOptions, 'experimentalAddHeaderAnchorIds'>;\n};\n\n/** An ATX heading line, split into indentation, marker and text. */\nconst ATX_HEADING = /^([ \\t]*)(#{1,6}[ \\t]+)(.*)$/;\n\n/** A trailing custom anchor ID, in either the plain or MDX-escaped form. */\nconst TRAILING_ANCHOR = /\\s*(?:\\\\\\{#[^}]+\\\\\\}|\\{#[^}]+\\})\\s*$/;\n\n/**\n * Deepest indentation at which Mintlify still reads `## Heading {#id}`. It is\n * the CommonMark limit for a heading; MDX itself accepts deeper headings, so\n * past it the `{#id}` reaches the expression parser and fails to compile.\n */\nconst MAX_MINTLIFY_HEADING_INDENT = 3;\n\n/**\n * Generates a slug from heading text\n */\nfunction generateSlug(text: string): string {\n return text\n .toLowerCase()\n .replace(/[^\\w\\s-]/g, '') // Remove special chars except spaces and hyphens\n .trim()\n .replace(/\\s+/g, '-') // Replace spaces with hyphens\n .replace(/-+/g, '-') // Replace multiple hyphens with single hyphen\n .replace(/^-|-$/g, ''); // Remove leading/trailing hyphens\n}\n\n/**\n * Extracts text content from heading nodes\n */\nfunction extractHeadingText(heading: Heading): string {\n let text = '';\n\n visit(heading, ['text', 'inlineCode'], (node: Node) => {\n if ('value' in node && typeof node.value === 'string') {\n text += node.value;\n }\n });\n\n return text;\n}\n\n/**\n * Line-by-line heading extractor used when MDX parsing fails outright.\n */\nfunction extractHeadingsWithFallback(mdxContent: string): HeadingInfo[] {\n const headings: HeadingInfo[] = [];\n let position = 0;\n\n forEachLineOutsideCodeFences(mdxContent, (line, index) => {\n const headingMatch = line.match(ATX_HEADING);\n if (!headingMatch) return;\n\n const [, indent, marker, rawText] = headingMatch;\n const { cleanedText, explicitId } = parseHeadingContent(rawText);\n if (!cleanedText && !explicitId) return;\n\n headings.push({\n text: cleanedText,\n level: marker.trim().length,\n slug: explicitId ?? generateSlug(cleanedText),\n position: position++,\n startLine: index + 1,\n endLine: index + 1,\n startColumn: indent.length + 1,\n // Without a parser there is nothing finer to go on than end of line.\n textEndColumn: line.length + 1,\n explicit: explicitId !== undefined,\n });\n });\n\n assignUniqueSlugs(headings);\n return headings;\n}\n\nfunction parseHeadingContent(text: string): {\n cleanedText: string;\n explicitId?: string;\n} {\n // Support both {#id} and escaped \\{#id\\} forms\n const anchorMatch = text.match(/(\\\\\\{#([^}]+)\\\\\\}|\\{#([^}]+)\\})\\s*$/);\n\n if (!anchorMatch) {\n return { cleanedText: text };\n }\n\n const explicitId = anchorMatch[2] || anchorMatch[3];\n const cleanedText = text.replace(anchorMatch[0], '').trimEnd();\n\n return { cleanedText, explicitId };\n}\n\n/**\n * Suffixes repeated slugs `-2`, `-3`, ... as Mintlify does. Author-written IDs\n * are never renumbered, only reserved.\n */\nfunction assignUniqueSlugs(headings: HeadingInfo[]): void {\n // Reserve every explicit ID up front, including ones later in the document,\n // so a generated slug never claims an ID an author asked for.\n const used = new Set(\n headings\n .filter((heading) => heading.explicit)\n .map((heading) => heading.slug)\n );\n\n for (const heading of headings) {\n if (heading.explicit) continue;\n\n // Headings with no slug-able characters would produce id=\"\".\n const base = heading.slug || 'section';\n let slug = base;\n let suffix = 1;\n while (used.has(slug)) {\n suffix += 1;\n slug = `${base}-${suffix}`;\n }\n\n heading.slug = slug;\n used.add(slug);\n }\n}\n\n/**\n * Represents a heading with its position and metadata\n */\nexport interface HeadingInfo {\n text: string;\n level: number;\n slug: string;\n position: number;\n /** 1-based line the heading starts on. */\n startLine: number;\n /** 1-based line the heading ends on (differs from startLine for setext). */\n endLine: number;\n /** 1-based column of the heading marker; anything left of it is indentation. */\n startColumn: number;\n /** 1-based column just past the text, before any closing `##`; -1 if unknown. */\n textEndColumn: number;\n /** Whether the author wrote an explicit `{#id}`. */\n explicit: boolean;\n}\n\n/**\n * Extracts heading information from content (read-only, no modifications).\n * Source and translation are matched by position, so both must parse the same\n * way — the fallback extractor misses headings nested in JSX.\n */\nexport function extractHeadingInfo(mdxContent: string): HeadingInfo[] {\n let ast;\n try {\n ast = parseMdxTolerantly(mdxContent);\n } catch {\n // Fallback: line-by-line extraction skipping fenced code blocks\n return extractHeadingsWithFallback(mdxContent);\n }\n\n const headings: HeadingInfo[] = [];\n let position = 0;\n\n visit(ast, 'heading', (heading: Heading) => {\n const headingText = extractHeadingText(heading);\n const { cleanedText, explicitId } = parseHeadingContent(headingText);\n if (!cleanedText && !explicitId) return;\n\n const lastChild = heading.children[heading.children.length - 1];\n\n headings.push({\n text: cleanedText,\n level: heading.depth,\n slug: explicitId ?? generateSlug(cleanedText),\n position: position++,\n startLine: heading.position?.start.line ?? -1,\n endLine: heading.position?.end.line ?? -1,\n startColumn: heading.position?.start.column ?? 1,\n textEndColumn:\n lastChild?.position?.end.column ?? heading.position?.end.column ?? -1,\n explicit: explicitId !== undefined,\n });\n });\n\n assignUniqueSlugs(headings);\n return headings;\n}\n\n/**\n * Applies anchor IDs to translated content based on source heading mapping\n */\nexport function addExplicitAnchorIds(\n translatedContent: string,\n sourceHeadingMap: HeadingInfo[],\n settings?: AnchorIdSettings,\n sourcePath?: string,\n translatedPath?: string,\n fileTypeHint?: 'md' | 'mdx'\n): {\n content: string;\n hasChanges: boolean;\n addedIds: Array<{ heading: string; id: string }>;\n} {\n const addedIds: Array<{ heading: string; id: string }> = [];\n // Mintlify mode writes Mintlify's native `{#id}` on every heading.\n const mintlifyMode =\n settings?.options?.experimentalAddHeaderAnchorIds === 'mintlify';\n\n // Extract headings from translated content\n const translatedHeadings = extractHeadingInfo(translatedContent);\n\n // Pre-processing validation: check if header counts match\n if (sourceHeadingMap.length !== translatedHeadings.length) {\n const sourceFile = sourcePath\n ? `Source file: ${sourcePath}`\n : 'Source file';\n const translatedFile = translatedPath\n ? `translated file: ${translatedPath}`\n : 'translated file';\n\n logger.warn(\n `Header count mismatch detected! ${sourceFile} has ${sourceHeadingMap.length} headers but ${translatedFile} has ${translatedHeadings.length} headers. ` +\n `This likely means your source file was edited after translation was requested, causing a mismatch between ` +\n `the number of headers in your source file vs the translated file. Re-translate this file to resolve the issue.`\n );\n }\n\n // Create ID mapping based on positional matching\n const idMappings = new Map<number, { id: string; explicit: boolean }>();\n sourceHeadingMap.forEach((sourceHeading, index) => {\n const translatedHeading = translatedHeadings[index];\n // Match by position and level for safety\n if (translatedHeading && translatedHeading.level === sourceHeading.level) {\n idMappings.set(index, {\n id: sourceHeading.slug,\n explicit: sourceHeading.explicit,\n });\n addedIds.push({\n heading: translatedHeading.text,\n id: sourceHeading.slug,\n });\n }\n });\n\n const translatedIsMdx = translatedPath\n ? translatedPath.toLowerCase().endsWith('.mdx')\n : true; // default to mdx-style escaping when unknown\n const shouldEscapeAnchors = mintlifyMode\n ? false\n : fileTypeHint === 'mdx'\n ? true\n : fileTypeHint === 'md'\n ? false\n : translatedIsMdx;\n\n if (idMappings.size === 0) {\n // Normalize anchors the translation carried over.\n const content = normalizeInlineAnchors(\n translatedContent,\n shouldEscapeAnchors\n );\n return {\n content,\n hasChanges: content !== translatedContent,\n addedIds: [],\n };\n }\n\n const content = normalizeInlineAnchors(\n applyAnchorIds(\n translatedContent,\n translatedHeadings,\n idMappings,\n mintlifyMode,\n shouldEscapeAnchors\n ),\n shouldEscapeAnchors\n );\n\n return {\n content,\n hasChanges: content !== translatedContent,\n addedIds,\n };\n}\n\n/**\n * Writes anchor IDs onto the translated document, locating headings by parser\n * line position rather than by text. Edits run bottom-up to keep lines valid.\n */\nfunction applyAnchorIds(\n translatedContent: string,\n translatedHeadings: HeadingInfo[],\n idMappings: Map<number, { id: string; explicit: boolean }>,\n mintlifyMode: boolean,\n escapeAnchors: boolean\n): string {\n const lines = translatedContent.split('\\n');\n\n const ordered = [...translatedHeadings].sort(\n (a, b) => b.startLine - a.startLine\n );\n\n for (const heading of ordered) {\n const mapping = idMappings.get(heading.position);\n if (!mapping) continue;\n if (heading.startLine < 1 || heading.endLine > lines.length) continue;\n\n const index = heading.startLine - 1;\n\n if (heading.textEndColumn < 1) continue;\n\n // Mintlify reads `{#id}` on ATX headings only, so a setext heading is left\n // alone. Other modes keep the anchor on its text line as before.\n if (mintlifyMode && heading.endLine > heading.startLine) continue;\n\n const escape = escapeAnchors && !mapping.explicit;\n const anchor = escape ? `\\\\{#${mapping.id}\\\\}` : `{#${mapping.id}}`;\n const { text, trailer } = splitHeadingLine(lines[index], heading);\n const line = `${text} ${anchor}${trailer}`;\n\n // The MDX serializer indents JSX children two spaces per level, so a\n // heading nested in JSX can sit deeper than Mintlify reads `{#id}`. Move it\n // to the margin; mixed indentation inside a JSX element is valid MDX.\n const indent = line.match(/^[ \\t]*/)?.[0].length ?? 0;\n lines[index] =\n mintlifyMode && indent > MAX_MINTLIFY_HEADING_INDENT\n ? line.trimStart()\n : line;\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Splits a heading line into its text, minus any trailing inline anchor, and\n * whatever follows the text (a closing `##` sequence, for instance).\n */\nfunction splitHeadingLine(\n line: string,\n heading: HeadingInfo\n): { text: string; trailer: string } {\n return {\n text: line.slice(0, heading.textEndColumn - 1).replace(TRAILING_ANCHOR, ''),\n trailer: line.slice(heading.textEndColumn - 1),\n };\n}\n\n/**\n * Normalizes every inline anchor: escaped for MDX, bare for Markdown.\n */\nfunction normalizeInlineAnchors(\n content: string,\n escapeAnchors: boolean\n): string {\n return mapLinesOutsideCodeFences(content, (line) => {\n const atx = line.match(ATX_HEADING);\n if (!atx) return line;\n\n const escaped = atx[3].match(/\\\\\\{#([A-Za-z0-9_-]+)\\\\\\}\\s*$/);\n const bare = atx[3].match(/(?<!\\\\)\\{#([A-Za-z0-9_-]+)\\}\\s*$/);\n\n if (escapeAnchors && bare) {\n const text = atx[3].replace(TRAILING_ANCHOR, '');\n return `${atx[1]}${atx[2]}${text} \\\\{#${bare[1]}\\\\}`;\n }\n if (!escapeAnchors && escaped) {\n const text = atx[3].replace(TRAILING_ANCHOR, '');\n return `${atx[1]}${atx[2]}${text} {#${escaped[1]}}`;\n }\n return line;\n });\n}\n"],"mappings":"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"}
|
package/dist/workflows/stage.js
CHANGED
|
@@ -35,7 +35,7 @@ async function runStageFilesWorkflow({ files, options, settings, inlineLibrary }
|
|
|
35
35
|
files,
|
|
36
36
|
branchData
|
|
37
37
|
});
|
|
38
|
-
if (settings.options?.saveLocal
|
|
38
|
+
if (settings.options?.saveLocal === true) await userEditDiffsStep.run(uploadedFiles);
|
|
39
39
|
if (settings.tag) try {
|
|
40
40
|
await new TagStep(api, settings, !!options.tag).run(uploadedFiles);
|
|
41
41
|
} catch {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stage.js","names":[],"sources":["../../src/workflows/stage.ts"],"sourcesContent":["import { logCollectedFiles, logErrorAndExit } from '../console/logging.js';\nimport { branchResolutionError, withOriginalError } from '../console/index.js';\nimport { logger } from '../console/logger.js';\nimport { Settings, TranslateFlags } from '../types/index.js';\nimport { api } from '../utils/api.js';\nimport { EnqueueFilesResult, FileToUpload } from 'generaltranslation/types';\nimport { UploadSourcesStep } from './steps/UploadSourcesStep.js';\nimport { SetupStep } from './steps/SetupStep.js';\nimport { EnqueueStep } from './steps/EnqueueStep.js';\nimport { BranchStep } from './steps/BranchStep.js';\nimport { TagStep } from './steps/TagStep.js';\nimport { UserEditDiffsStep } from './steps/UserEditDiffsStep.js';\nimport { BranchData } from '../types/branch.js';\nimport { calculateTimeoutMs } from '../utils/calculateTimeoutMs.js';\nimport { filterFilesForEnqueue } from './utils/filterFilesForEnqueue.js';\nimport { syncFonts } from './utils/syncFonts.js';\nimport type { InlineLibrary } from '../types/libraries.js';\n\n/**\n * Sends multiple files for translation to the API using a workflow pattern\n * @param files - Array of file objects to translate\n * @param options - The options for the API call\n * @param settings - Settings configuration\n * @returns The translated content or version ID\n */\nexport async function runStageFilesWorkflow({\n files,\n options,\n settings,\n inlineLibrary,\n}: {\n files: FileToUpload[];\n options: TranslateFlags;\n settings: Settings;\n inlineLibrary?: InlineLibrary;\n}): Promise<{\n branchData: BranchData;\n enqueueResult: EnqueueFilesResult;\n}> {\n try {\n // Log files to be translated\n logCollectedFiles(files, undefined, inlineLibrary);\n\n // Sync fonts before enqueueing so the translation jobs (e.g. Lottie\n // layout refinement) can use them instead of fallback fonts.\n await syncFonts(settings);\n\n // Calculate timeout for setup step\n const timeoutMs = calculateTimeoutMs(options.timeout);\n\n // Create workflow with steps\n const branchStep = new BranchStep(api, settings);\n const uploadStep = new UploadSourcesStep(api, settings);\n const userEditDiffsStep = new UserEditDiffsStep(settings);\n const setupStep = new SetupStep(api, settings, timeoutMs);\n const enqueueStep = new EnqueueStep(api, settings, options.force);\n\n // first run the branch step\n const branchData = await branchStep.run();\n if (!branchData) {\n return logErrorAndExit(branchResolutionError);\n }\n\n // then run the upload step\n const uploadedFiles = await uploadStep.run({ files, branchData });\n\n // optionally run the user edit diffs step\n if (settings.options?.saveLocal
|
|
1
|
+
{"version":3,"file":"stage.js","names":[],"sources":["../../src/workflows/stage.ts"],"sourcesContent":["import { logCollectedFiles, logErrorAndExit } from '../console/logging.js';\nimport { branchResolutionError, withOriginalError } from '../console/index.js';\nimport { logger } from '../console/logger.js';\nimport { Settings, TranslateFlags } from '../types/index.js';\nimport { api } from '../utils/api.js';\nimport { EnqueueFilesResult, FileToUpload } from 'generaltranslation/types';\nimport { UploadSourcesStep } from './steps/UploadSourcesStep.js';\nimport { SetupStep } from './steps/SetupStep.js';\nimport { EnqueueStep } from './steps/EnqueueStep.js';\nimport { BranchStep } from './steps/BranchStep.js';\nimport { TagStep } from './steps/TagStep.js';\nimport { UserEditDiffsStep } from './steps/UserEditDiffsStep.js';\nimport { BranchData } from '../types/branch.js';\nimport { calculateTimeoutMs } from '../utils/calculateTimeoutMs.js';\nimport { filterFilesForEnqueue } from './utils/filterFilesForEnqueue.js';\nimport { syncFonts } from './utils/syncFonts.js';\nimport type { InlineLibrary } from '../types/libraries.js';\n\n/**\n * Sends multiple files for translation to the API using a workflow pattern\n * @param files - Array of file objects to translate\n * @param options - The options for the API call\n * @param settings - Settings configuration\n * @returns The translated content or version ID\n */\nexport async function runStageFilesWorkflow({\n files,\n options,\n settings,\n inlineLibrary,\n}: {\n files: FileToUpload[];\n options: TranslateFlags;\n settings: Settings;\n inlineLibrary?: InlineLibrary;\n}): Promise<{\n branchData: BranchData;\n enqueueResult: EnqueueFilesResult;\n}> {\n try {\n // Log files to be translated\n logCollectedFiles(files, undefined, inlineLibrary);\n\n // Sync fonts before enqueueing so the translation jobs (e.g. Lottie\n // layout refinement) can use them instead of fallback fonts.\n await syncFonts(settings);\n\n // Calculate timeout for setup step\n const timeoutMs = calculateTimeoutMs(options.timeout);\n\n // Create workflow with steps\n const branchStep = new BranchStep(api, settings);\n const uploadStep = new UploadSourcesStep(api, settings);\n const userEditDiffsStep = new UserEditDiffsStep(settings);\n const setupStep = new SetupStep(api, settings, timeoutMs);\n const enqueueStep = new EnqueueStep(api, settings, options.force);\n\n // first run the branch step\n const branchData = await branchStep.run();\n if (!branchData) {\n return logErrorAndExit(branchResolutionError);\n }\n\n // then run the upload step\n const uploadedFiles = await uploadStep.run({ files, branchData });\n\n // optionally run the user edit diffs step (opt-in via --save-local or options.saveLocal)\n if (settings.options?.saveLocal === true) {\n await userEditDiffsStep.run(uploadedFiles);\n }\n\n // then run the tag step (non-fatal — tagging failure should not block translations)\n if (settings.tag) {\n try {\n const userProvidedTag = !!options.tag;\n const tagStep = new TagStep(api, settings, userProvidedTag);\n await tagStep.run(uploadedFiles);\n } catch {\n logger.warn('Failed to create translation tag. Continuing...');\n }\n }\n\n // then run the setup step\n await setupStep.run(uploadedFiles);\n\n // then run the enqueue step\n const { filesToEnqueue, skippedFiles } = await filterFilesForEnqueue({\n gt: api,\n files: uploadedFiles,\n locales: settings.locales,\n force: options.force,\n });\n if (skippedFiles.length > 0) {\n logger.info(\n `Skipped enqueue for ${skippedFiles.length} already translated file${skippedFiles.length === 1 ? '' : 's'}`\n );\n }\n\n const enqueueResult = await enqueueStep.run(filesToEnqueue);\n\n return { branchData, enqueueResult };\n } catch (error) {\n return logErrorAndExit(\n withOriginalError(\n 'Files could not be sent for translation. Check the files, branch configuration, and API credentials, then try again.',\n error\n )\n );\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAyBA,eAAsB,sBAAsB,EAC1C,OACA,SACA,UACA,iBASC;AACD,KAAI;AAEF,oBAAkB,OAAO,KAAA,GAAW,cAAc;AAIlD,QAAM,UAAU,SAAS;EAGzB,MAAM,YAAY,mBAAmB,QAAQ,QAAQ;EAGrD,MAAM,aAAa,IAAI,WAAW,KAAK,SAAS;EAChD,MAAM,aAAa,IAAI,kBAAkB,KAAK,SAAS;EACvD,MAAM,oBAAoB,IAAI,kBAAkB,SAAS;EACzD,MAAM,YAAY,IAAI,UAAU,KAAK,UAAU,UAAU;EACzD,MAAM,cAAc,IAAI,YAAY,KAAK,UAAU,QAAQ,MAAM;EAGjE,MAAM,aAAa,MAAM,WAAW,KAAK;AACzC,MAAI,CAAC,WACH,QAAO,gBAAgB,sBAAsB;EAI/C,MAAM,gBAAgB,MAAM,WAAW,IAAI;GAAE;GAAO;GAAY,CAAC;AAGjE,MAAI,SAAS,SAAS,cAAc,KAClC,OAAM,kBAAkB,IAAI,cAAc;AAI5C,MAAI,SAAS,IACX,KAAI;AAGF,SAAM,IADc,QAAQ,KAAK,UAAU,CADlB,CAAC,QAAQ,IAErB,CAAC,IAAI,cAAc;UAC1B;AACN,UAAO,KAAK,kDAAkD;;AAKlE,QAAM,UAAU,IAAI,cAAc;EAGlC,MAAM,EAAE,gBAAgB,iBAAiB,MAAM,sBAAsB;GACnE,IAAI;GACJ,OAAO;GACP,SAAS,SAAS;GAClB,OAAO,QAAQ;GAChB,CAAC;AACF,MAAI,aAAa,SAAS,EACxB,QAAO,KACL,uBAAuB,aAAa,OAAO,0BAA0B,aAAa,WAAW,IAAI,KAAK,MACvG;AAKH,SAAO;GAAE;GAAY,eAAA,MAFO,YAAY,IAAI,eAAe;GAEvB;UAC7B,OAAO;AACd,SAAO,gBACL,kBACE,wHACA,MACD,CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gt",
|
|
3
|
-
"version": "2.20.
|
|
3
|
+
"version": "2.20.4",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"bin": "bin/main.js",
|
|
6
6
|
"files": [
|
|
@@ -115,13 +115,13 @@
|
|
|
115
115
|
"unified": "^11.0.5",
|
|
116
116
|
"unist-util-visit": "^5.0.0",
|
|
117
117
|
"yaml": "^2.8.0",
|
|
118
|
-
"@generaltranslation/icu": "0.1.2",
|
|
119
118
|
"@generaltranslation/format": "0.1.8",
|
|
120
|
-
"@generaltranslation/python-extractor": "0.2.47",
|
|
121
119
|
"@generaltranslation/supported-locales": "2.1.27",
|
|
122
|
-
"@generaltranslation/vue-extractor": "0.1.7",
|
|
123
120
|
"generaltranslation": "9.2.0",
|
|
124
|
-
"
|
|
121
|
+
"@generaltranslation/vue-extractor": "0.1.7",
|
|
122
|
+
"gt-remark": "1.0.12",
|
|
123
|
+
"@generaltranslation/python-extractor": "0.2.47",
|
|
124
|
+
"@generaltranslation/icu": "0.1.2"
|
|
125
125
|
},
|
|
126
126
|
"devDependencies": {
|
|
127
127
|
"@types/babel__generator": "^7.27.0",
|