gtx-cli 2.2.0 → 2.3.0

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,16 @@
1
1
  # gtx-cli
2
2
 
3
+ ## 2.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#651](https://github.com/generaltranslation/gt/pull/651) [`3e5705b`](https://github.com/generaltranslation/gt/commit/3e5705bc96005441798619fec97574fa15a5a2bd) Thanks [@fernando-aviles](https://github.com/fernando-aviles)! - Split up file upload into source/translation specific uploads; added project setup visibility
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [[`3e5705b`](https://github.com/generaltranslation/gt/commit/3e5705bc96005441798619fec97574fa15a5a2bd)]:
12
+ - generaltranslation@7.6.0
13
+
3
14
  ## 2.2.0
4
15
 
5
16
  ### Minor Changes
@@ -1,5 +1,5 @@
1
1
  import chalk from 'chalk';
2
- import { createSpinner, logMessage, logSuccess } from '../console/logging.js';
2
+ import { createSpinner, logError, logMessage, logSuccess, } from '../console/logging.js';
3
3
  import { gt } from '../utils/gt.js';
4
4
  import { TEMPLATE_FILE_NAME } from '../cli/commands/stage.js';
5
5
  /**
@@ -9,6 +9,8 @@ import { TEMPLATE_FILE_NAME } from '../cli/commands/stage.js';
9
9
  * @returns The translated content or version ID
10
10
  */
11
11
  export async function sendFiles(files, options, settings) {
12
+ // Keep track of the most recent spinner so we can stop it on error
13
+ let currentSpinner = null;
12
14
  logMessage(chalk.cyan('Files to translate:') +
13
15
  '\n' +
14
16
  files
@@ -19,26 +21,95 @@ export async function sendFiles(files, options, settings) {
19
21
  return `- ${file.fileName}`;
20
22
  })
21
23
  .join('\n'));
22
- const spinner = createSpinner('dots');
23
- spinner.start(`Sending ${files.length} file${files.length !== 1 ? 's' : ''} to General Translation API...`);
24
24
  try {
25
- // Send the files to the API
26
- const responseData = await gt.enqueueFiles(files, {
27
- publish: settings.publish,
25
+ // Step 1: Upload files (get references)
26
+ const uploadSpinner = createSpinner('dots');
27
+ currentSpinner = uploadSpinner;
28
+ uploadSpinner.start(`Uploading ${files.length} file${files.length !== 1 ? 's' : ''} to General Translation API...`);
29
+ const sourceLocale = settings.defaultLocale;
30
+ if (!sourceLocale) {
31
+ uploadSpinner.stop(chalk.red('Missing default source locale'));
32
+ throw new Error('sendFiles: settings.defaultLocale is required to upload source files');
33
+ }
34
+ // Convert FileToTranslate[] -> { source: FileUpload }[]
35
+ const uploads = files.map(({ content, fileName, fileFormat, dataFormat }) => ({
36
+ source: {
37
+ content,
38
+ fileName,
39
+ fileFormat,
40
+ dataFormat,
41
+ locale: sourceLocale,
42
+ },
43
+ }));
44
+ const upload = await gt.uploadSourceFiles(uploads, {
45
+ sourceLocale,
46
+ modelProvider: settings.modelProvider,
47
+ });
48
+ uploadSpinner.stop(chalk.green('Files uploaded successfully'));
49
+ // Check if setup is needed
50
+ const setupDecision = await Promise.resolve(gt.shouldSetupProject?.())
51
+ .then((v) => v)
52
+ .catch(() => ({ shouldSetupProject: false }));
53
+ const shouldSetupProject = Boolean(setupDecision?.shouldSetupProject);
54
+ // Step 2: Setup if needed and poll until complete
55
+ if (shouldSetupProject) {
56
+ // Calculate timeout once for setup fetching
57
+ // Accept number or numeric string, default to 600s
58
+ const timeoutVal = options?.timeout !== undefined ? Number(options.timeout) : 600;
59
+ const setupTimeoutMs = (Number.isFinite(timeoutVal) ? timeoutVal : 600) * 1000;
60
+ const { setupJobId } = await gt.setupProject(upload.uploadedFiles);
61
+ const setupSpinner = createSpinner('dots');
62
+ currentSpinner = setupSpinner;
63
+ setupSpinner.start('Setting up project...');
64
+ const start = Date.now();
65
+ const pollInterval = 2000;
66
+ let setupCompleted = false;
67
+ let setupFailedMessage = null;
68
+ while (true) {
69
+ const status = await gt.checkSetupStatus(setupJobId);
70
+ if (status.status === 'completed') {
71
+ setupCompleted = true;
72
+ break;
73
+ }
74
+ if (status.status === 'failed') {
75
+ setupFailedMessage = status.error?.message || 'Unknown error';
76
+ break;
77
+ }
78
+ if (Date.now() - start > setupTimeoutMs) {
79
+ setupFailedMessage = 'Timed out while waiting for setup generation';
80
+ break;
81
+ }
82
+ await new Promise((r) => setTimeout(r, pollInterval));
83
+ }
84
+ if (setupCompleted) {
85
+ setupSpinner.stop(chalk.green('Setup successfully completed'));
86
+ }
87
+ else {
88
+ setupSpinner.stop(chalk.yellow(`Setup ${setupFailedMessage ? 'failed' : 'timed out'} — proceeding without setup${setupFailedMessage ? ` (${setupFailedMessage})` : ''}`));
89
+ }
90
+ }
91
+ // Step 3: Enqueue translations by reference
92
+ const enqueueSpinner = createSpinner('dots');
93
+ currentSpinner = enqueueSpinner;
94
+ enqueueSpinner.start('Enqueuing translations...');
95
+ const enqueueResult = await gt.enqueueFiles(upload.uploadedFiles, {
28
96
  sourceLocale: settings.defaultLocale,
29
97
  targetLocales: settings.locales,
30
- version: settings.version, // not set ATM
98
+ publish: settings.publish,
99
+ requireApproval: settings.stageTranslations,
31
100
  modelProvider: settings.modelProvider,
32
101
  force: options?.force,
33
102
  });
34
- // Handle version ID response (for async processing)
35
- const { data, message, locales, translations } = responseData;
36
- spinner.stop(chalk.green('Files for translation uploaded successfully'));
103
+ const { data, message, locales, translations } = enqueueResult;
104
+ enqueueSpinner.stop(chalk.green('Files for translation uploaded successfully'));
37
105
  logSuccess(message);
38
106
  return { data, locales, translations };
39
107
  }
40
108
  catch (error) {
41
- spinner.stop(chalk.red('Failed to send files for translation'));
109
+ if (currentSpinner) {
110
+ currentSpinner.stop(chalk.red('Failed to send files for translation'));
111
+ }
112
+ logError('Failed to send files for translation');
42
113
  throw error;
43
114
  }
44
115
  }
@@ -24,4 +24,4 @@ export type UploadData = {
24
24
  export declare function uploadFiles(files: {
25
25
  source: FileUpload;
26
26
  translations: FileUpload[];
27
- }[], options: Settings): Promise<any>;
27
+ }[], options: Settings): Promise<void>;
@@ -18,12 +18,20 @@ export async function uploadFiles(files, options) {
18
18
  const spinner = createSpinner('dots');
19
19
  spinner.start(`Uploading ${files.length} file${files.length !== 1 ? 's' : ''} to General Translation...`);
20
20
  try {
21
- const result = gt.uploadFiles(files, {
21
+ // Upload sources
22
+ await gt.uploadSourceFiles(files, {
22
23
  ...options,
23
24
  sourceLocale: options.defaultLocale,
24
25
  });
26
+ // Upload translations (if any exist)
27
+ const withTranslations = files.filter((f) => f.translations.length > 0);
28
+ if (withTranslations.length > 0) {
29
+ await gt.uploadTranslations(withTranslations, {
30
+ ...options,
31
+ sourceLocale: options.defaultLocale, // optional, safe to include
32
+ });
33
+ }
25
34
  spinner.stop(chalk.green('Files uploaded successfully'));
26
- return result;
27
35
  }
28
36
  catch {
29
37
  spinner.stop(chalk.red('An unexpected error occurred while uploading files'));
@@ -100,9 +100,8 @@ export async function upload(filePaths, placeholderPaths, transformPaths, dataFo
100
100
  const fileMapping = createFileMapping(filePaths, placeholderPaths, transformPaths, locales, options.defaultLocale);
101
101
  // construct object
102
102
  const uploadData = allFiles.map((file) => {
103
- const encodedContent = Buffer.from(file.content).toString('base64');
104
103
  const sourceFile = {
105
- content: encodedContent,
104
+ content: file.content,
106
105
  fileName: file.fileName,
107
106
  fileFormat: file.fileFormat,
108
107
  dataFormat: file.dataFormat,
@@ -113,9 +112,8 @@ export async function upload(filePaths, placeholderPaths, transformPaths, dataFo
113
112
  const translatedFileName = fileMapping[locale][file.fileName];
114
113
  if (translatedFileName && existsSync(translatedFileName)) {
115
114
  const translatedContent = readFileSync(translatedFileName, 'utf8');
116
- const encodedTranslatedContent = Buffer.from(translatedContent).toString('base64');
117
115
  translations.push({
118
- content: encodedTranslatedContent,
116
+ content: translatedContent,
119
117
  fileName: translatedFileName,
120
118
  fileFormat: file.fileFormat,
121
119
  dataFormat: file.dataFormat,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gtx-cli",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "main": "dist/index.js",
5
5
  "bin": "dist/main.js",
6
6
  "files": [
@@ -87,7 +87,7 @@
87
87
  "esbuild": "^0.25.4",
88
88
  "fast-glob": "^3.3.3",
89
89
  "form-data": "^4.0.4",
90
- "generaltranslation": "^7.5.0",
90
+ "generaltranslation": "^7.6.0",
91
91
  "json-pointer": "^0.6.2",
92
92
  "jsonpath-plus": "^10.3.0",
93
93
  "jsonpointer": "^5.0.1",