gtx-cli 2.4.0 → 2.4.2

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,20 @@
1
1
  # gtx-cli
2
2
 
3
+ ## 2.4.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [#753](https://github.com/generaltranslation/gt/pull/753) [`bd0bc26`](https://github.com/generaltranslation/gt/commit/bd0bc265192d5b51618a537a92122cd6eeae6e4d) Thanks [@ErnestM1234](https://github.com/ErnestM1234)! - fix: avoid downloading files when using --publish flag
8
+
9
+ ## 2.4.1
10
+
11
+ ### Patch Changes
12
+
13
+ - [#751](https://github.com/generaltranslation/gt/pull/751) [`7114780`](https://github.com/generaltranslation/gt/commit/71147803bf3e4cf21556ffb9b5f77756e283a32a) Thanks [@SamEggert](https://github.com/SamEggert)! - transform for yaml files -- retrieve file format in downloadFileBatch
14
+
15
+ - Updated dependencies [[`7114780`](https://github.com/generaltranslation/gt/commit/71147803bf3e4cf21556ffb9b5f77756e283a32a)]:
16
+ - generaltranslation@7.7.1
17
+
3
18
  ## 2.4.0
4
19
 
5
20
  ### Minor Changes
@@ -103,7 +103,9 @@ function prepareFileQueryData(data, locales) {
103
103
  */
104
104
  function generateStatusSuffixText(downloadStatus, fileQueryData) {
105
105
  // Simple progress indicator
106
- const progressText = chalk.green(`[${downloadStatus.downloaded.size + downloadStatus.failed.size}/${fileQueryData.length}]`) + ` translations completed`;
106
+ const progressText = chalk.green(`[${downloadStatus.downloaded.size +
107
+ downloadStatus.failed.size +
108
+ downloadStatus.skipped.size}/${fileQueryData.length}]`) + ` translations completed`;
107
109
  // Get terminal height to adapt our output
108
110
  const terminalHeight = process.stdout.rows || 24; // Default to 24 if undefined
109
111
  // If terminal is very small, just show the basic progress
@@ -120,6 +122,7 @@ function generateStatusSuffixText(downloadStatus, fileQueryData) {
120
122
  completed: new Set(),
121
123
  pending: new Set([item.locale]),
122
124
  failed: new Set(),
125
+ skipped: new Set(),
123
126
  });
124
127
  }
125
128
  else {
@@ -143,6 +146,14 @@ function generateStatusSuffixText(downloadStatus, fileQueryData) {
143
146
  status.failed.add(locale);
144
147
  }
145
148
  }
149
+ for (const fileLocale of downloadStatus.skipped) {
150
+ const [fileName, locale] = fileLocale.split(':');
151
+ const status = fileStatus.get(fileName);
152
+ if (status) {
153
+ status.pending.delete(locale);
154
+ status.skipped.add(locale);
155
+ }
156
+ }
146
157
  // Calculate how many files we can show based on terminal height
147
158
  const filesArray = Array.from(fileStatus.entries());
148
159
  const maxFilesToShow = Math.min(filesArray.length, terminalHeight - 3 // Header + progress + buffer
@@ -159,6 +170,13 @@ function generateStatusSuffixText(downloadStatus, fileQueryData) {
159
170
  .join(', ');
160
171
  localeStatuses.push(chalk.green(`${completedCodes}`));
161
172
  }
173
+ // Add (translated but not downloaded) skipped locales
174
+ if (status.skipped.size > 0) {
175
+ const skippedCodes = Array.from(status.skipped)
176
+ .map((locale) => getLocaleProperties(locale).code)
177
+ .join(', ');
178
+ localeStatuses.push(chalk.green(`${skippedCodes}`));
179
+ }
162
180
  // Add failed locales
163
181
  if (status.failed.size > 0) {
164
182
  const failedCodes = Array.from(status.failed)
@@ -230,17 +248,19 @@ async function checkTranslationDeployment(fileQueryData, downloadStatus, spinner
230
248
  };
231
249
  })
232
250
  .filter((file) => file !== null);
233
- const batchResult = await downloadFileBatch(batchFiles, options, 3, 1000, Boolean(forceDownload));
234
- // Process results
235
- batchFiles.forEach((file) => {
236
- const { translationId, fileLocale } = file;
237
- if (batchResult.successful.includes(translationId)) {
238
- downloadStatus.downloaded.add(fileLocale);
239
- }
240
- else if (batchResult.failed.includes(translationId)) {
241
- downloadStatus.failed.add(fileLocale);
242
- }
243
- });
251
+ if (batchFiles.length > 0) {
252
+ const batchResult = await downloadFileBatch(batchFiles, options, 3, 1000, Boolean(forceDownload));
253
+ // Process results
254
+ batchFiles.forEach((file) => {
255
+ const { translationId, fileLocale } = file;
256
+ if (batchResult.successful.includes(translationId)) {
257
+ downloadStatus.downloaded.add(fileLocale);
258
+ }
259
+ else if (batchResult.failed.includes(translationId)) {
260
+ downloadStatus.failed.add(fileLocale);
261
+ }
262
+ });
263
+ }
244
264
  }
245
265
  // Force a refresh of the spinner display
246
266
  const statusText = generateStatusSuffixText(downloadStatus, fileQueryData);
@@ -8,6 +8,7 @@ import { mergeJson } from '../formats/json/mergeJson.js';
8
8
  import mergeYaml from '../formats/yaml/mergeYaml.js';
9
9
  import { getDownloadedVersions, saveDownloadedVersions, } from '../fs/config/downloadedVersions.js';
10
10
  import { recordDownloaded } from '../state/recentDownloads.js';
11
+ import stringify from 'fast-json-stable-stringify';
11
12
  /**
12
13
  * Downloads multiple translation files in a single batch request
13
14
  * @param files - Array of files to download with their output paths
@@ -92,10 +93,22 @@ export async function downloadFileBatch(files, options, maxRetries = 3, retryDel
92
93
  translatedContent: file.data,
93
94
  targetLocale: locale,
94
95
  },
95
- ])[0];
96
+ ], options.defaultLocale)[0];
96
97
  }
97
98
  }
98
99
  }
100
+ // If the file is a GTJSON file, stable sort the order and format the data
101
+ if (file.fileFormat === 'GTJSON') {
102
+ try {
103
+ const jsonData = JSON.parse(data);
104
+ const sortedData = stringify(jsonData); // stably sort with fast-json-stable-stringify
105
+ const sortedJsonData = JSON.parse(sortedData);
106
+ data = JSON.stringify(sortedJsonData, null, 2); // format the data
107
+ }
108
+ catch (error) {
109
+ logWarning(`Failed to sort GTJson file: ${file.id}: ` + error);
110
+ }
111
+ }
99
112
  // Write the file to disk
100
113
  await fs.promises.writeFile(outputPath, data);
101
114
  // Track as downloaded
@@ -10,12 +10,12 @@ import { noFilesError, noVersionIdError } from '../../console/index.js';
10
10
  import localizeStaticImports from '../../utils/localizeStaticImports.js';
11
11
  // Downloads translations that were completed
12
12
  export async function handleTranslate(options, settings, filesTranslationResponse) {
13
- if (filesTranslationResponse && settings.files) {
13
+ if (filesTranslationResponse) {
14
14
  const { resolvedPaths, placeholderPaths, transformPaths } = settings.files;
15
15
  const fileMapping = createFileMapping(resolvedPaths, placeholderPaths, transformPaths, settings.locales, settings.defaultLocale);
16
16
  const { data } = filesTranslationResponse;
17
17
  // Check for remaining translations
18
- await checkFileTranslations(data, settings.locales, options.timeout, (sourcePath, locale) => fileMapping[locale][sourcePath] ?? null, settings, options.force, options.forceDownload);
18
+ await checkFileTranslations(data, settings.locales, options.timeout, (sourcePath, locale) => fileMapping[locale]?.[sourcePath] ?? null, settings, options.force, options.forceDownload);
19
19
  }
20
20
  }
21
21
  // Downloads translations that were originally staged
@@ -2,8 +2,8 @@ import { Settings } from '../types/index.js';
2
2
  export declare const DEFAULT_SRC_PATTERNS: string[];
3
3
  /**
4
4
  * Generates settings from any
5
- * @param options - The options to generate settings from
5
+ * @param flags - The CLI flags to generate settings from
6
6
  * @param cwd - The current working directory
7
7
  * @returns The generated settings
8
8
  */
9
- export declare function generateSettings(options: any, cwd?: string): Promise<Settings>;
9
+ export declare function generateSettings(flags: Record<string, any>, cwd?: string): Promise<Settings>;
@@ -20,24 +20,24 @@ export const DEFAULT_SRC_PATTERNS = [
20
20
  ];
21
21
  /**
22
22
  * Generates settings from any
23
- * @param options - The options to generate settings from
23
+ * @param flags - The CLI flags to generate settings from
24
24
  * @param cwd - The current working directory
25
25
  * @returns The generated settings
26
26
  */
27
- export async function generateSettings(options, cwd = process.cwd()) {
27
+ export async function generateSettings(flags, cwd = process.cwd()) {
28
28
  // Load config file
29
29
  let gtConfig = {};
30
- if (options.config && !options.config.endsWith('.json')) {
31
- options.config = `${options.config}.json`;
30
+ if (flags.config && !flags.config.endsWith('.json')) {
31
+ flags.config = `${flags.config}.json`;
32
32
  }
33
- if (options.config) {
34
- gtConfig = loadConfig(options.config);
33
+ if (flags.config) {
34
+ gtConfig = loadConfig(flags.config);
35
35
  }
36
36
  else {
37
37
  const config = resolveConfig(cwd);
38
38
  if (config) {
39
39
  gtConfig = config.config;
40
- options.config = config.path;
40
+ flags.config = config.path;
41
41
  }
42
42
  else {
43
43
  gtConfig = {};
@@ -45,43 +45,43 @@ export async function generateSettings(options, cwd = process.cwd()) {
45
45
  }
46
46
  // Warn if apiKey is present in gt.config.json
47
47
  if (gtConfig.apiKey) {
48
- warnApiKeyInConfig(options.config);
48
+ warnApiKeyInConfig(flags.config);
49
49
  process.exit(1);
50
50
  }
51
51
  const projectIdEnv = resolveProjectId();
52
52
  // Resolve mismatched projectIds
53
53
  if (gtConfig.projectId &&
54
- options.projectId &&
55
- gtConfig.projectId !== options.projectId) {
56
- logErrorAndExit(`Project ID mismatch between ${chalk.green(gtConfig.projectId)} and ${chalk.green(options.projectId)}! Please use the same projectId in all configs.`);
54
+ flags.projectId &&
55
+ gtConfig.projectId !== flags.projectId) {
56
+ logErrorAndExit(`Project ID mismatch between ${chalk.green(gtConfig.projectId)} and ${chalk.green(flags.projectId)}! Please use the same projectId in all configs.`);
57
57
  }
58
58
  else if (gtConfig.projectId &&
59
59
  projectIdEnv &&
60
60
  gtConfig.projectId !== projectIdEnv) {
61
61
  logErrorAndExit(`Project ID mismatch between ${chalk.green(gtConfig.projectId)} and ${chalk.green(projectIdEnv)}! Please use the same projectId in all configs.`);
62
62
  }
63
- if (options.options?.docsUrlPattern &&
64
- !options.options?.docsUrlPattern.includes('[locale]')) {
63
+ if (flags.options?.docsUrlPattern &&
64
+ !flags.options?.docsUrlPattern.includes('[locale]')) {
65
65
  logErrorAndExit('Failed to localize static urls: URL pattern must include "[locale]" to denote the location of the locale');
66
66
  }
67
- if (options.options?.docsImportPattern &&
68
- !options.options?.docsImportPattern.includes('[locale]')) {
67
+ if (flags.options?.docsImportPattern &&
68
+ !flags.options?.docsImportPattern.includes('[locale]')) {
69
69
  logErrorAndExit('Failed to localize static imports: Import pattern must include "[locale]" to denote the location of the locale');
70
70
  }
71
- if (options.options?.copyFiles) {
72
- for (const file of options.options.copyFiles) {
71
+ if (flags.options?.copyFiles) {
72
+ for (const file of flags.options.copyFiles) {
73
73
  if (!file.includes('[locale]')) {
74
74
  logErrorAndExit('Failed to copy files: File path must include "[locale]" to denote the location of the locale');
75
75
  }
76
76
  }
77
77
  }
78
78
  // merge options
79
- const mergedOptions = { ...gtConfig, ...options };
79
+ const mergedOptions = { ...gtConfig, ...flags };
80
80
  // Add defaultLocale if not provided
81
81
  mergedOptions.defaultLocale =
82
82
  mergedOptions.defaultLocale || libraryDefaultLocale;
83
83
  // merge locales
84
- mergedOptions.locales = Array.from(new Set([...(gtConfig.locales || []), ...(options.locales || [])]));
84
+ mergedOptions.locales = Array.from(new Set([...(gtConfig.locales || []), ...(flags.locales || [])]));
85
85
  // Separate defaultLocale from locales
86
86
  mergedOptions.locales = mergedOptions.locales.filter((locale) => locale !== mergedOptions.defaultLocale);
87
87
  // Add apiKey if not provided
@@ -104,7 +104,7 @@ export async function generateSettings(options, cwd = process.cwd()) {
104
104
  // For human review, always stage the project
105
105
  mergedOptions.stageTranslations = mergedOptions.stageTranslations ?? false;
106
106
  // Add publish if not provided
107
- mergedOptions.publish = (gtConfig.publish || options.publish) ?? false;
107
+ mergedOptions.publish = (gtConfig.publish || flags.publish) ?? false;
108
108
  // Populate src if not provided
109
109
  mergedOptions.src = mergedOptions.src || DEFAULT_SRC_PATTERNS;
110
110
  // Resolve all glob patterns in the files object
@@ -115,21 +115,20 @@ export async function generateSettings(options, cwd = process.cwd()) {
115
115
  .map(([key]) => key);
116
116
  mergedOptions.files = mergedOptions.files
117
117
  ? resolveFiles(mergedOptions.files, mergedOptions.defaultLocale, mergedOptions.locales, cwd, compositePatterns)
118
- : undefined;
118
+ : { resolvedPaths: {}, placeholderPaths: {}, transformPaths: {} };
119
119
  mergedOptions.options = {
120
120
  ...(mergedOptions.options || {}),
121
121
  experimentalLocalizeStaticImports: gtConfig.options?.experimentalLocalizeStaticImports ||
122
- options.experimentalLocalizeStaticImports,
122
+ flags.experimentalLocalizeStaticImports,
123
123
  experimentalLocalizeStaticUrls: gtConfig.options?.experimentalLocalizeStaticUrls ||
124
- options.experimentalLocalizeStaticUrls,
124
+ flags.experimentalLocalizeStaticUrls,
125
125
  experimentalHideDefaultLocale: gtConfig.options?.experimentalHideDefaultLocale ||
126
- options.experimentalHideDefaultLocale,
126
+ flags.experimentalHideDefaultLocale,
127
127
  experimentalFlattenJsonFiles: gtConfig.options?.experimentalFlattenJsonFiles ||
128
- options.experimentalFlattenJsonFiles,
128
+ flags.experimentalFlattenJsonFiles,
129
129
  experimentalClearLocaleDirs: gtConfig.options?.experimentalClearLocaleDirs ||
130
- options.experimentalClearLocaleDirs,
131
- clearLocaleDirsExclude: gtConfig.options?.clearLocaleDirsExclude ||
132
- options.clearLocaleDirsExclude,
130
+ flags.experimentalClearLocaleDirs,
131
+ clearLocaleDirsExclude: gtConfig.options?.clearLocaleDirsExclude || flags.clearLocaleDirsExclude,
133
132
  };
134
133
  // Add additional options if provided
135
134
  if (mergedOptions.options) {
@@ -112,7 +112,7 @@ export async function aggregateFiles(settings) {
112
112
  allFiles.push(...files);
113
113
  }
114
114
  }
115
- if (allFiles.length === 0) {
115
+ if (allFiles.length === 0 && !settings.publish) {
116
116
  logError('No files to translate were found. Please check your configuration and try again.');
117
117
  }
118
118
  return allFiles;
@@ -2,4 +2,4 @@ import { AdditionalOptions } from '../../types/index.js';
2
2
  export default function mergeYaml(originalContent: string, inputPath: string, options: AdditionalOptions, targets: {
3
3
  translatedContent: string;
4
4
  targetLocale: string;
5
- }[]): string[];
5
+ }[], defaultLocale: string): string[];
@@ -2,7 +2,8 @@ import JSONPointer from 'jsonpointer';
2
2
  import { exit, logError } from '../../console/logging.js';
3
3
  import { validateYamlSchema } from './utils.js';
4
4
  import YAML from 'yaml';
5
- export default function mergeYaml(originalContent, inputPath, options, targets) {
5
+ import { applyTransformations } from '../json/mergeJson.js';
6
+ export default function mergeYaml(originalContent, inputPath, options, targets, defaultLocale) {
6
7
  const yamlSchema = validateYamlSchema(options, inputPath);
7
8
  if (!yamlSchema) {
8
9
  return targets.map((target) => target.translatedContent);
@@ -46,6 +47,10 @@ export default function mergeYaml(originalContent, inputPath, options, targets)
46
47
  // Silently ignore invalid or non-existent JSON pointers
47
48
  }
48
49
  }
50
+ // Apply transformations if they exist
51
+ if (yamlSchema.transform) {
52
+ applyTransformations(mergedYaml, yamlSchema.transform, target.targetLocale, defaultLocale);
53
+ }
49
54
  output.push(YAML.stringify(mergedYaml));
50
55
  }
51
56
  if (!output.length) {
@@ -120,7 +120,7 @@ export type Settings = {
120
120
  resolvedPaths: ResolvedFiles;
121
121
  placeholderPaths: ResolvedFiles;
122
122
  transformPaths: TransformFiles;
123
- } | undefined;
123
+ };
124
124
  stageTranslations: boolean;
125
125
  publish: boolean;
126
126
  _versionId?: string;
@@ -163,6 +163,7 @@ export type JsonSchema = {
163
163
  export type YamlSchema = {
164
164
  preset?: 'mintlify';
165
165
  include?: string[];
166
+ transform?: TransformOptions;
166
167
  };
167
168
  export type SourceObjectOptions = {
168
169
  type: 'array' | 'object';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gtx-cli",
3
- "version": "2.4.0",
3
+ "version": "2.4.2",
4
4
  "main": "dist/index.js",
5
5
  "bin": "dist/main.js",
6
6
  "files": [
@@ -73,6 +73,7 @@
73
73
  "dotenv": "^16.4.5",
74
74
  "esbuild": "^0.25.4",
75
75
  "fast-glob": "^3.3.3",
76
+ "fast-json-stable-stringify": "^2.1.0",
76
77
  "form-data": "^4.0.4",
77
78
  "gt-remark": "^1.0.1",
78
79
  "json-pointer": "^0.6.2",
@@ -91,7 +92,7 @@
91
92
  "unified": "^11.0.5",
92
93
  "unist-util-visit": "^5.0.0",
93
94
  "yaml": "^2.8.0",
94
- "generaltranslation": "7.7.0"
95
+ "generaltranslation": "7.7.1"
95
96
  },
96
97
  "devDependencies": {
97
98
  "@babel/types": "^7.28.4",