gt 2.14.17 → 2.14.18

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # gtx-cli
2
2
 
3
+ ## 2.14.18
4
+
5
+ ### Patch Changes
6
+
7
+ - [#1243](https://github.com/generaltranslation/gt/pull/1243) [`a0e19f6`](https://github.com/generaltranslation/gt/commit/a0e19f64a17d1a439d2352a6bc3ca7390c4ed401) Thanks [@fernando-aviles](https://github.com/fernando-aviles)! - Handling Mintlify $ref
8
+
3
9
  ## 2.14.17
4
10
 
5
11
  ### Patch Changes
@@ -8,6 +8,7 @@ import { mergeJson } from '../formats/json/mergeJson.js';
8
8
  import { extractJson } from '../formats/json/extractJson.js';
9
9
  import mergeYaml from '../formats/yaml/mergeYaml.js';
10
10
  import { extractYaml } from '../formats/yaml/extractYaml.js';
11
+ import { resolveMintlifyRefs, shouldResolveRefs, } from '../utils/resolveMintlifyRefs.js';
11
12
  import { readLockfile, writeLockfile, findOrCreateEntry, } from '../fs/config/downloadedVersions.js';
12
13
  import { recordDownloaded, recordRemerged } from '../state/recentDownloads.js';
13
14
  import { recordWarning } from '../state/translateWarnings.js';
@@ -30,7 +31,19 @@ function mergeWithSource(translatedContent, locale, inputPath, options) {
30
31
  if (!sourceContent)
31
32
  return translatedContent;
32
33
  if (jsonSchema) {
33
- return mergeJson(sourceContent, inputPath, options.options, [{ translatedContent, targetLocale: locale }], options.defaultLocale, options.locales)[0];
34
+ // Resolve $ref before merging if configured
35
+ let resolvedSourceContent = sourceContent;
36
+ if (shouldResolveRefs(inputPath, options.options)) {
37
+ try {
38
+ const json = JSON.parse(sourceContent);
39
+ const { resolved } = resolveMintlifyRefs(json, inputPath);
40
+ resolvedSourceContent = JSON.stringify(resolved, null, 2);
41
+ }
42
+ catch {
43
+ // Fall through with original content
44
+ }
45
+ }
46
+ return mergeJson(resolvedSourceContent, inputPath, options.options, [{ translatedContent, targetLocale: locale }], options.defaultLocale, options.locales)[0];
34
47
  }
35
48
  else {
36
49
  return mergeYaml(sourceContent, inputPath, options.options, [{ translatedContent, targetLocale: locale }], options.defaultLocale)[0];
@@ -103,7 +116,18 @@ export async function downloadFileBatch(fileTracker, files, options, forceDownlo
103
116
  // For schema-based files, re-merge with current source in case
104
117
  // non-translatable fields changed (skip the API download, not the merge)
105
118
  try {
106
- const existingContent = fs.readFileSync(outputPath, 'utf8');
119
+ let existingContent = fs.readFileSync(outputPath, 'utf8');
120
+ // Resolve $ref before extraction if configured
121
+ if (shouldResolveRefs(outputPath, options.options)) {
122
+ try {
123
+ const json = JSON.parse(existingContent);
124
+ const { resolved } = resolveMintlifyRefs(json, outputPath);
125
+ existingContent = JSON.stringify(resolved, null, 2);
126
+ }
127
+ catch {
128
+ // Fall through with original content
129
+ }
130
+ }
107
131
  const jsonExtracted = options.options?.jsonSchema
108
132
  ? extractJson(existingContent, inputPath, options.options, locale, options.defaultLocale)
109
133
  : null;
package/dist/cli/base.js CHANGED
@@ -33,6 +33,7 @@ import { detectFramework } from '../setup/detectFramework.js';
33
33
  import { getFrameworkDisplayName, getReactFrameworkLibrary, } from '../setup/frameworkUtils.js';
34
34
  import { INLINE_LIBRARIES } from '../types/libraries.js';
35
35
  import { handleEnqueue } from './commands/enqueue.js';
36
+ import { splitMintlifyLanguageRefs } from '../utils/splitMintlifyLanguageRefs.js';
36
37
  export class BaseCLI {
37
38
  library;
38
39
  additionalModules;
@@ -198,6 +199,8 @@ export class BaseCLI {
198
199
  if (include.size > 0) {
199
200
  await postProcessTranslations(settings, include);
200
201
  }
202
+ // Split Mintlify language entries into $ref files to keep docs.json small
203
+ await splitMintlifyLanguageRefs(settings);
201
204
  // Mirror assets after translations are downloaded and locale dirs are populated
202
205
  await mirrorAssetsToLocales(settings);
203
206
  clearDownloaded();
@@ -2,12 +2,12 @@ import { noSupportedFormatError, noDefaultLocaleError, } from '../../console/ind
2
2
  import { exitSync, logErrorAndExit } from '../../console/logging.js';
3
3
  import { logger } from '../../console/logger.js';
4
4
  import { getRelative, readFile } from '../../fs/findFilepath.js';
5
- import path from 'node:path';
6
5
  import { SUPPORTED_FILE_EXTENSIONS } from '../../formats/files/supportedFiles.js';
7
6
  import sanitizeFileContent from '../../utils/sanitizeFileContent.js';
8
7
  import { parseJson } from '../../formats/json/parseJson.js';
9
8
  import { extractJson } from '../../formats/json/extractJson.js';
10
- import { validateJsonSchema, detectMintlifyUnsupportedFields, } from '../../formats/json/utils.js';
9
+ import { validateJsonSchema } from '../../formats/json/utils.js';
10
+ import { resolveMintlifyRefs, shouldResolveRefs, } from '../../utils/resolveMintlifyRefs.js';
11
11
  import { runUploadFilesWorkflow } from '../../workflows/upload.js';
12
12
  import { existsSync, readFileSync } from 'node:fs';
13
13
  import { createFileMapping } from '../../formats/files/fileMapping.js';
@@ -38,17 +38,19 @@ export async function upload(filePaths, placeholderPaths, transformPaths, dataFo
38
38
  }
39
39
  const jsonFiles = filePaths.json.map((filePath) => {
40
40
  const content = readFile(filePath);
41
- // Detect unsupported fields in Mintlify docs.json
42
- if (settings.framework === 'mintlify' &&
43
- path.basename(filePath) === 'docs.json') {
41
+ // Resolve $ref before parsing if configured
42
+ let contentForParsing = content;
43
+ if (shouldResolveRefs(filePath, additionalOptions)) {
44
44
  try {
45
- detectMintlifyUnsupportedFields(JSON.parse(content), filePath);
45
+ const json = JSON.parse(content);
46
+ const { resolved } = resolveMintlifyRefs(json, filePath);
47
+ contentForParsing = JSON.stringify(resolved, null, 2);
46
48
  }
47
49
  catch {
48
50
  // JSON parse errors are handled below by parseJson
49
51
  }
50
52
  }
51
- const parsedJson = parseJson(content, filePath, additionalOptions, settings.defaultLocale);
53
+ const parsedJson = parseJson(contentForParsing, filePath, additionalOptions, settings.defaultLocale);
52
54
  const relativePath = getRelative(filePath);
53
55
  const jsonSchema = validateJsonSchema(additionalOptions, filePath);
54
56
  if (jsonSchema?.composite) {
@@ -3,8 +3,8 @@ import { recordWarning } from '../../state/translateWarnings.js';
3
3
  import { getRelative, readFile } from '../../fs/findFilepath.js';
4
4
  import { SUPPORTED_FILE_EXTENSIONS } from './supportedFiles.js';
5
5
  import { parseJson } from '../json/parseJson.js';
6
- import { detectMintlifyUnsupportedFields } from '../json/utils.js';
7
- import path from 'node:path';
6
+ import { resolveMintlifyRefs, shouldResolveRefs, } from '../../utils/resolveMintlifyRefs.js';
7
+ import { storeRefMap } from '../../state/mintlifyRefMap.js';
8
8
  import parseYaml from '../yaml/parseYaml.js';
9
9
  import { validateYamlSchema } from '../yaml/utils.js';
10
10
  import { flattenJson } from '../json/flattenJson.js';
@@ -75,17 +75,20 @@ export async function aggregateFiles(settings) {
75
75
  return null;
76
76
  }
77
77
  }
78
- // Detect unsupported fields in Mintlify docs.json
79
- if (settings.framework === 'mintlify' &&
80
- path.basename(filePath) === 'docs.json') {
78
+ // Resolve $ref before parsing if configured
79
+ let contentForParsing = content;
80
+ if (shouldResolveRefs(filePath, settings.options)) {
81
81
  try {
82
- detectMintlifyUnsupportedFields(JSON.parse(content), filePath);
82
+ const json = JSON.parse(content);
83
+ const { resolved, refMap } = resolveMintlifyRefs(json, filePath);
84
+ storeRefMap(refMap);
85
+ contentForParsing = JSON.stringify(resolved, null, 2);
83
86
  }
84
87
  catch {
85
88
  // JSON parse errors are handled below by parseJson
86
89
  }
87
90
  }
88
- const parsedJson = parseJson(content, filePath, settings.options || {}, settings.defaultLocale);
91
+ const parsedJson = parseJson(contentForParsing, filePath, settings.options || {}, settings.defaultLocale);
89
92
  // Detect companion metadata file
90
93
  let keyedMetadata;
91
94
  let parsedContent;
@@ -1 +1 @@
1
- export declare const PACKAGE_VERSION = "2.14.17";
1
+ export declare const PACKAGE_VERSION = "2.14.18";
@@ -1,2 +1,2 @@
1
1
  // This file is auto-generated. Do not edit manually.
2
- export const PACKAGE_VERSION = '2.14.17';
2
+ export const PACKAGE_VERSION = '2.14.18';
@@ -0,0 +1,4 @@
1
+ import type { RefMap } from '../utils/resolveMintlifyRefs.js';
2
+ export declare function storeRefMap(refMap: RefMap): void;
3
+ export declare function getStoredRefMap(): RefMap | null;
4
+ export declare function clearStoredRefMap(): void;
@@ -0,0 +1,12 @@
1
+ let storedRefMap = new Map();
2
+ export function storeRefMap(refMap) {
3
+ for (const [key, value] of refMap.entries()) {
4
+ storedRefMap.set(key, value);
5
+ }
6
+ }
7
+ export function getStoredRefMap() {
8
+ return storedRefMap.size > 0 ? storedRefMap : null;
9
+ }
10
+ export function clearStoredRefMap() {
11
+ storedRefMap = new Map();
12
+ }
@@ -0,0 +1,28 @@
1
+ export type RefMapEntry = {
2
+ sourceFile: string;
3
+ refPath: string;
4
+ containingDir: string;
5
+ originalContent: unknown;
6
+ };
7
+ export type RefMap = Map<string, RefMapEntry>;
8
+ export type ResolvedRefs = {
9
+ resolved: unknown;
10
+ refMap: RefMap;
11
+ };
12
+ /**
13
+ * Resolve all Mintlify $ref references in a parsed JSON object.
14
+ *
15
+ * Returns the fully-expanded JSON and a refMap that tracks which subtrees
16
+ * came from which files (used later to split translated output back into
17
+ * the same file topology).
18
+ */
19
+ export declare function resolveMintlifyRefs(json: unknown, filePath: string): ResolvedRefs;
20
+ /**
21
+ * Check if a file should have $ref resolution applied based on the settings.
22
+ * Returns true if the file has mintlify options configured AND matches a
23
+ * composite jsonSchema entry.
24
+ */
25
+ export declare function shouldResolveRefs(filePath: string, options?: {
26
+ mintlify?: any;
27
+ jsonSchema?: Record<string, any>;
28
+ }): boolean;
@@ -0,0 +1,117 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { logger } from '../console/logger.js';
4
+ import chalk from 'chalk';
5
+ import micromatch from 'micromatch';
6
+ /**
7
+ * Resolve all Mintlify $ref references in a parsed JSON object.
8
+ *
9
+ * Returns the fully-expanded JSON and a refMap that tracks which subtrees
10
+ * came from which files (used later to split translated output back into
11
+ * the same file topology).
12
+ */
13
+ export function resolveMintlifyRefs(json, filePath) {
14
+ const refMap = new Map();
15
+ const resolved = resolveNode(json, path.dirname(path.resolve(filePath)), '', new Set(), refMap);
16
+ return { resolved, refMap };
17
+ }
18
+ function resolveNode(node, baseDir, pointer, visiting, refMap) {
19
+ if (node === null || typeof node !== 'object')
20
+ return node;
21
+ if (Array.isArray(node)) {
22
+ return node.map((item, i) => resolveNode(item, baseDir, `${pointer}/${i}`, visiting, refMap));
23
+ }
24
+ const obj = node;
25
+ if (typeof obj['$ref'] === 'string') {
26
+ return resolveRef(obj, baseDir, pointer, visiting, refMap);
27
+ }
28
+ const result = {};
29
+ for (const [key, value] of Object.entries(obj)) {
30
+ result[key] = resolveNode(value, baseDir, `${pointer}/${key}`, visiting, refMap);
31
+ }
32
+ return result;
33
+ }
34
+ function resolveRef(obj, baseDir, pointer, visiting, refMap) {
35
+ const refPath = obj['$ref'];
36
+ if (!isRelativePath(refPath)) {
37
+ logger.warn(chalk.yellow(`Skipping non-relative $ref at ${pointer || '/'}: ${refPath}`));
38
+ const { $ref: _, ...rest } = obj;
39
+ return rest;
40
+ }
41
+ const resolvedFilePath = path.resolve(baseDir, refPath);
42
+ if (visiting.has(resolvedFilePath)) {
43
+ logger.warn(chalk.yellow(`Circular $ref detected at ${pointer || '/'}: ${refPath}`));
44
+ const { $ref: _, ...rest } = obj;
45
+ return rest;
46
+ }
47
+ if (!fs.existsSync(resolvedFilePath)) {
48
+ logger.warn(chalk.yellow(`$ref file not found at ${pointer || '/'}: ${refPath} (resolved to ${resolvedFilePath})`));
49
+ const { $ref: _, ...rest } = obj;
50
+ return rest;
51
+ }
52
+ let fileContent;
53
+ try {
54
+ fileContent = fs.readFileSync(resolvedFilePath, 'utf-8');
55
+ }
56
+ catch {
57
+ logger.warn(chalk.yellow(`Failed to read $ref file: ${resolvedFilePath}`));
58
+ const { $ref: _, ...rest } = obj;
59
+ return rest;
60
+ }
61
+ let parsed;
62
+ try {
63
+ parsed = JSON.parse(fileContent);
64
+ }
65
+ catch {
66
+ logger.warn(chalk.yellow(`$ref file is not valid JSON: ${resolvedFilePath}`));
67
+ const { $ref: _, ...rest } = obj;
68
+ return rest;
69
+ }
70
+ // Record provenance before recursive resolution
71
+ refMap.set(pointer, {
72
+ sourceFile: resolvedFilePath,
73
+ refPath,
74
+ containingDir: baseDir,
75
+ originalContent: parsed,
76
+ });
77
+ // Recursively resolve nested $ref in the referenced file
78
+ const refFileDir = path.dirname(resolvedFilePath);
79
+ const nextVisiting = new Set(visiting);
80
+ nextVisiting.add(resolvedFilePath);
81
+ const resolvedContent = resolveNode(parsed, refFileDir, pointer, nextVisiting, refMap);
82
+ // Apply Mintlify merge rules
83
+ const { $ref: _, ...siblings } = obj;
84
+ if (resolvedContent !== null &&
85
+ typeof resolvedContent === 'object' &&
86
+ !Array.isArray(resolvedContent)) {
87
+ // Object: merge siblings on top (siblings take precedence)
88
+ return { ...resolvedContent, ...siblings };
89
+ }
90
+ // Non-object (array, string, number, etc.): replace entirely, drop siblings
91
+ return resolvedContent;
92
+ }
93
+ /**
94
+ * Check if a file should have $ref resolution applied based on the settings.
95
+ * Returns true if the file has mintlify options configured AND matches a
96
+ * composite jsonSchema entry.
97
+ */
98
+ export function shouldResolveRefs(filePath, options) {
99
+ if (!options?.mintlify)
100
+ return false;
101
+ if (!options.jsonSchema)
102
+ return false;
103
+ const relative = path.relative(process.cwd(), filePath);
104
+ for (const [glob, schema] of Object.entries(options.jsonSchema)) {
105
+ if (schema?.composite && micromatch.isMatch(relative, glob)) {
106
+ return true;
107
+ }
108
+ }
109
+ return false;
110
+ }
111
+ function isRelativePath(refPath) {
112
+ if (path.isAbsolute(refPath))
113
+ return false;
114
+ if (/^[a-z]+:\/\//i.test(refPath))
115
+ return false;
116
+ return true;
117
+ }
@@ -0,0 +1,13 @@
1
+ import { Settings } from '../types/index.js';
2
+ /**
3
+ * Post-processing step for Mintlify docs.json.
4
+ *
5
+ * After mergeJson writes a fully-inlined docs.json, this function restores
6
+ * the original $ref structure:
7
+ *
8
+ * - Default locale: restores original $ref paths
9
+ * - Non-default locales: prefixes ref paths with {locale}/, writes translated
10
+ * content to the prefixed paths
11
+ * - Top-level refs (navigation, navbar): restored in docs.json
12
+ */
13
+ export declare function splitMintlifyLanguageRefs(settings: Settings): Promise<void>;
@@ -0,0 +1,190 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { logger } from '../console/logger.js';
4
+ import { shouldResolveRefs } from './resolveMintlifyRefs.js';
5
+ import { getStoredRefMap, clearStoredRefMap } from '../state/mintlifyRefMap.js';
6
+ /**
7
+ * Post-processing step for Mintlify docs.json.
8
+ *
9
+ * After mergeJson writes a fully-inlined docs.json, this function restores
10
+ * the original $ref structure:
11
+ *
12
+ * - Default locale: restores original $ref paths
13
+ * - Non-default locales: prefixes ref paths with {locale}/, writes translated
14
+ * content to the prefixed paths
15
+ * - Top-level refs (navigation, navbar): restored in docs.json
16
+ */
17
+ export async function splitMintlifyLanguageRefs(settings) {
18
+ const isMintlify = settings.framework === 'mintlify' || !!settings.options?.mintlify;
19
+ if (!isMintlify)
20
+ return;
21
+ const refMap = getStoredRefMap();
22
+ if (!refMap || refMap.size === 0)
23
+ return;
24
+ try {
25
+ const resolvedJsonPaths = settings.files?.resolvedPaths?.json;
26
+ if (!resolvedJsonPaths)
27
+ return;
28
+ const docsJsonPath = resolvedJsonPaths.find((p) => shouldResolveRefs(p, settings.options));
29
+ if (!docsJsonPath)
30
+ return;
31
+ if (!fs.existsSync(docsJsonPath))
32
+ return;
33
+ let docsJson;
34
+ try {
35
+ docsJson = JSON.parse(fs.readFileSync(docsJsonPath, 'utf-8'));
36
+ }
37
+ catch {
38
+ return;
39
+ }
40
+ const defaultLocale = settings.defaultLocale;
41
+ const navRefEntry = refMap.get('/navigation');
42
+ const navContent = navRefEntry
43
+ ? getAtPointer(docsJson, '/navigation')
44
+ : docsJson?.navigation;
45
+ const languages = navContent?.languages;
46
+ if (!Array.isArray(languages) || languages.length <= 1) {
47
+ restoreTopLevelRefs(docsJson, refMap, docsJsonPath);
48
+ return;
49
+ }
50
+ const defaultIndex = languages.findIndex((e) => e?.language === defaultLocale);
51
+ if (defaultIndex < 0) {
52
+ restoreTopLevelRefs(docsJson, refMap, docsJsonPath);
53
+ return;
54
+ }
55
+ const navDir = navRefEntry
56
+ ? path.dirname(navRefEntry.sourceFile)
57
+ : path.dirname(docsJsonPath);
58
+ const defaultPointerPrefix = `/navigation/languages/${defaultIndex}`;
59
+ const internalRefs = collectInternalRefs(refMap, defaultPointerPrefix);
60
+ if (internalRefs.length > 0) {
61
+ const defaultEntry = languages[defaultIndex];
62
+ for (const ref of internalRefs) {
63
+ setAtPointer(defaultEntry, ref.relativePointer, {
64
+ $ref: ref.refPath,
65
+ });
66
+ }
67
+ for (const entry of languages) {
68
+ if (!entry || entry.language === defaultLocale)
69
+ continue;
70
+ const locale = entry.language;
71
+ if (!locale)
72
+ continue;
73
+ for (const ref of internalRefs) {
74
+ const subtree = getAtPointer(entry, ref.relativePointer);
75
+ if (subtree === undefined)
76
+ continue;
77
+ const originalAbsPath = path.resolve(ref.resolvedDir, ref.refPath);
78
+ const relToNavDir = path.relative(navDir, originalAbsPath);
79
+ const localeRelPath = path.join(locale, relToNavDir);
80
+ const outputPath = path.resolve(navDir, localeRelPath);
81
+ writeJsonFile(outputPath, subtree);
82
+ // All refs inside the locale's files use original paths — the locale
83
+ // directory mirrors the source structure, so relative resolution works.
84
+ // The locale prefix only appears in the parent navigation.json entry.
85
+ setAtPointer(entry, ref.relativePointer, { $ref: ref.refPath });
86
+ }
87
+ }
88
+ logger.info(`Restored $ref structure for source locale navigation`);
89
+ }
90
+ const navFileName = navRefEntry
91
+ ? path.basename(navRefEntry.sourceFile)
92
+ : 'navigation.json';
93
+ for (let i = 0; i < languages.length; i++) {
94
+ const entry = languages[i];
95
+ if (!entry || entry.language === defaultLocale)
96
+ continue;
97
+ const locale = entry.language;
98
+ if (!locale)
99
+ continue;
100
+ const { language, ...contentWithoutLanguage } = entry;
101
+ const entryFileName = `${locale}/${navFileName}`;
102
+ const entryFilePath = path.resolve(navDir, entryFileName);
103
+ writeJsonFile(entryFilePath, contentWithoutLanguage);
104
+ languages[i] = { language: locale, $ref: `./${entryFileName}` };
105
+ }
106
+ logger.info(`Split locale navigation entries into ref files`);
107
+ restoreTopLevelRefs(docsJson, refMap, docsJsonPath);
108
+ }
109
+ finally {
110
+ clearStoredRefMap();
111
+ }
112
+ }
113
+ /**
114
+ * Restore top-level $ref pointers in docs.json.
115
+ * Writes each resolved subtree to its original source file and replaces
116
+ * the subtree in docs.json with the $ref pointer.
117
+ */
118
+ function restoreTopLevelRefs(docsJson, refMap, docsJsonPath) {
119
+ let changed = false;
120
+ // Sort deepest-first so nested refs are written before their parent
121
+ // replaces the ancestor subtree with a $ref pointer
122
+ const entries = [...refMap.entries()]
123
+ .filter(([pointer]) => !pointer.match(/^\/navigation\/languages\/\d+/))
124
+ .sort(([a], [b]) => b.length - a.length);
125
+ for (const [pointer, entry] of entries) {
126
+ const subtree = getAtPointer(docsJson, pointer);
127
+ if (subtree === undefined)
128
+ continue;
129
+ writeJsonFile(entry.sourceFile, subtree);
130
+ setAtPointer(docsJson, pointer, { $ref: entry.refPath });
131
+ changed = true;
132
+ }
133
+ if (changed) {
134
+ fs.writeFileSync(docsJsonPath, JSON.stringify(docsJson, null, 2), 'utf-8');
135
+ }
136
+ }
137
+ /**
138
+ * Collect refMap entries that describe a language entry's internal $ref chain.
139
+ * Sorted deepest-first so nested content is extracted before parents.
140
+ */
141
+ function collectInternalRefs(refMap, entryPointerPrefix) {
142
+ const refs = [];
143
+ for (const [pointer, entry] of refMap.entries()) {
144
+ if (!pointer.startsWith(entryPointerPrefix + '/'))
145
+ continue;
146
+ refs.push({
147
+ relativePointer: pointer.slice(entryPointerPrefix.length),
148
+ refPath: entry.refPath,
149
+ // The directory from which this $ref path should be resolved
150
+ resolvedDir: entry.containingDir,
151
+ });
152
+ }
153
+ refs.sort((a, b) => b.relativePointer.length - a.relativePointer.length);
154
+ return refs;
155
+ }
156
+ function writeJsonFile(filePath, data) {
157
+ const dir = path.dirname(filePath);
158
+ if (!fs.existsSync(dir)) {
159
+ fs.mkdirSync(dir, { recursive: true });
160
+ }
161
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
162
+ }
163
+ function getAtPointer(obj, pointer) {
164
+ if (!pointer || pointer === '/')
165
+ return obj;
166
+ const parts = pointer.split('/').filter(Boolean);
167
+ let current = obj;
168
+ for (const part of parts) {
169
+ if (current === null || current === undefined)
170
+ return undefined;
171
+ const index = /^\d+$/.test(part) ? parseInt(part) : part;
172
+ current = current[index];
173
+ }
174
+ return current;
175
+ }
176
+ function setAtPointer(obj, pointer, value) {
177
+ if (!pointer || pointer === '/')
178
+ return;
179
+ const parts = pointer.split('/').filter(Boolean);
180
+ let current = obj;
181
+ for (let i = 0; i < parts.length - 1; i++) {
182
+ const index = /^\d+$/.test(parts[i]) ? parseInt(parts[i]) : parts[i];
183
+ if (current[index] === undefined)
184
+ return;
185
+ current = current[index];
186
+ }
187
+ const lastPart = parts[parts.length - 1];
188
+ const lastIndex = /^\d+$/.test(lastPart) ? parseInt(lastPart) : lastPart;
189
+ current[lastIndex] = value;
190
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gt",
3
- "version": "2.14.17",
3
+ "version": "2.14.18",
4
4
  "main": "dist/index.js",
5
5
  "bin": "dist/main.js",
6
6
  "files": [
@@ -110,9 +110,9 @@
110
110
  "unified": "^11.0.5",
111
111
  "unist-util-visit": "^5.0.0",
112
112
  "yaml": "^2.8.0",
113
- "@generaltranslation/python-extractor": "0.2.10",
114
113
  "generaltranslation": "8.2.5",
115
- "gt-remark": "1.0.7"
114
+ "gt-remark": "1.0.7",
115
+ "@generaltranslation/python-extractor": "0.2.10"
116
116
  },
117
117
  "devDependencies": {
118
118
  "@babel/types": "^7.28.4",