gtx-cli 1.2.30 → 1.2.31-alpha.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.
@@ -8,7 +8,7 @@
8
8
  * @param timeoutDuration - The timeout duration for the wait in seconds
9
9
  * @returns True if all translations are deployed, false otherwise
10
10
  */
11
- export declare function checkFileTranslations(apiKey: string, baseUrl: string, data: {
11
+ export declare function checkFileTranslations(projectId: string, apiKey: string, baseUrl: string, data: {
12
12
  [key: string]: {
13
13
  versionId: string;
14
14
  fileName: string;
@@ -3,6 +3,7 @@ import { createOraSpinner, logError } from '../console/logging.js';
3
3
  import { getLocaleProperties } from 'generaltranslation';
4
4
  import { downloadFile } from './downloadFile.js';
5
5
  import { downloadFileBatch } from './downloadFileBatch.js';
6
+ import { getAuthHeaders } from '../utils/headers.js';
6
7
  /**
7
8
  * Checks the status of translations for a given version ID
8
9
  * @param apiKey - The API key for the General Translation API
@@ -13,7 +14,7 @@ import { downloadFileBatch } from './downloadFileBatch.js';
13
14
  * @param timeoutDuration - The timeout duration for the wait in seconds
14
15
  * @returns True if all translations are deployed, false otherwise
15
16
  */
16
- export async function checkFileTranslations(apiKey, baseUrl, data, locales, timeoutDuration, resolveOutputPath, downloadStatus) {
17
+ export async function checkFileTranslations(projectId, apiKey, baseUrl, data, locales, timeoutDuration, resolveOutputPath, downloadStatus) {
17
18
  const startTime = Date.now();
18
19
  console.log();
19
20
  const spinner = await createOraSpinner();
@@ -21,7 +22,7 @@ export async function checkFileTranslations(apiKey, baseUrl, data, locales, time
21
22
  // Initialize the query data
22
23
  const fileQueryData = prepareFileQueryData(data, locales);
23
24
  // Do first check immediately
24
- const initialCheck = await checkTranslationDeployment(baseUrl, apiKey, fileQueryData, downloadStatus, spinner, resolveOutputPath);
25
+ const initialCheck = await checkTranslationDeployment(baseUrl, projectId, apiKey, fileQueryData, downloadStatus, spinner, resolveOutputPath);
25
26
  if (initialCheck) {
26
27
  spinner.succeed(chalk.green('Files translated!'));
27
28
  return true;
@@ -33,7 +34,7 @@ export async function checkFileTranslations(apiKey, baseUrl, data, locales, time
33
34
  // Start the interval aligned with the original request time
34
35
  setTimeout(() => {
35
36
  intervalCheck = setInterval(async () => {
36
- const isDeployed = await checkTranslationDeployment(baseUrl, apiKey, fileQueryData, downloadStatus, spinner, resolveOutputPath);
37
+ const isDeployed = await checkTranslationDeployment(baseUrl, projectId, apiKey, fileQueryData, downloadStatus, spinner, resolveOutputPath);
37
38
  const elapsed = Date.now() - startTime;
38
39
  if (isDeployed || elapsed >= timeoutDuration * 1000) {
39
40
  clearInterval(intervalCheck);
@@ -156,7 +157,7 @@ function generateStatusSuffixText(downloadStatus, fileQueryData) {
156
157
  /**
157
158
  * Checks translation status and downloads ready files
158
159
  */
159
- async function checkTranslationDeployment(baseUrl, apiKey, fileQueryData, downloadStatus, spinner, resolveOutputPath) {
160
+ async function checkTranslationDeployment(baseUrl, projectId, apiKey, fileQueryData, downloadStatus, spinner, resolveOutputPath) {
160
161
  try {
161
162
  // Only query for files that haven't been downloaded yet
162
163
  const currentQueryData = fileQueryData.filter((item) => !downloadStatus.downloaded.has(`${item.fileName}:${item.locale}`) &&
@@ -169,7 +170,7 @@ async function checkTranslationDeployment(baseUrl, apiKey, fileQueryData, downlo
169
170
  method: 'POST',
170
171
  headers: {
171
172
  'Content-Type': 'application/json',
172
- ...(apiKey && { 'x-gt-api-key': apiKey }),
173
+ ...getAuthHeaders(projectId, apiKey),
173
174
  },
174
175
  body: JSON.stringify({ files: currentQueryData }),
175
176
  });
@@ -193,7 +194,7 @@ async function checkTranslationDeployment(baseUrl, apiKey, fileQueryData, downlo
193
194
  });
194
195
  // Use batch download if there are multiple files
195
196
  if (batchFiles.length > 1) {
196
- const batchResult = await downloadFileBatch(baseUrl, apiKey, batchFiles.map(({ translationId, outputPath }) => ({
197
+ const batchResult = await downloadFileBatch(baseUrl, projectId, apiKey, batchFiles.map(({ translationId, outputPath }) => ({
197
198
  translationId,
198
199
  outputPath,
199
200
  })));
@@ -211,7 +212,7 @@ async function checkTranslationDeployment(baseUrl, apiKey, fileQueryData, downlo
211
212
  else if (batchFiles.length === 1) {
212
213
  // For a single file, use the original downloadFile method
213
214
  const file = batchFiles[0];
214
- const result = await downloadFile(baseUrl, apiKey, file.translationId, file.outputPath);
215
+ const result = await downloadFile(baseUrl, projectId, apiKey, file.translationId, file.outputPath);
215
216
  if (result) {
216
217
  downloadStatus.downloaded.add(file.fileLocale);
217
218
  }
@@ -1 +1 @@
1
- export declare function downloadFile(baseUrl: string, apiKey: string, translationId: string, outputPath: string, maxRetries?: number, retryDelay?: number): Promise<boolean>;
1
+ export declare function downloadFile(baseUrl: string, projectId: string, apiKey: string, translationId: string, outputPath: string, maxRetries?: number, retryDelay?: number): Promise<boolean>;
@@ -1,15 +1,16 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import { logError } from '../console/logging.js';
4
+ import { getAuthHeaders } from '../utils/headers.js';
4
5
  // Helper function to download a file
5
- export async function downloadFile(baseUrl, apiKey, translationId, outputPath, maxRetries = 3, retryDelay = 1000) {
6
+ export async function downloadFile(baseUrl, projectId, apiKey, translationId, outputPath, maxRetries = 3, retryDelay = 1000) {
6
7
  let retries = 0;
7
8
  while (retries <= maxRetries) {
8
9
  try {
9
10
  const downloadResponse = await fetch(`${baseUrl}/v1/project/translations/files/${translationId}/download`, {
10
11
  method: 'GET',
11
12
  headers: {
12
- ...(apiKey && { 'x-gt-api-key': apiKey }),
13
+ ...getAuthHeaders(projectId, apiKey),
13
14
  },
14
15
  });
15
16
  if (downloadResponse.ok) {
@@ -7,7 +7,7 @@
7
7
  * @param retryDelay - Delay between retries in milliseconds
8
8
  * @returns Object containing successful and failed file IDs
9
9
  */
10
- export declare function downloadFileBatch(baseUrl: string, apiKey: string, files: Array<{
10
+ export declare function downloadFileBatch(baseUrl: string, projectId: string, apiKey: string, files: Array<{
11
11
  translationId: string;
12
12
  outputPath: string;
13
13
  }>, maxRetries?: number, retryDelay?: number): Promise<{
@@ -1,6 +1,7 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import { logError, logWarning } from '../console/logging.js';
4
+ import { getAuthHeaders } from '../utils/headers.js';
4
5
  /**
5
6
  * Downloads multiple translation files in a single batch request
6
7
  * @param baseUrl - The base URL for the General Translation API
@@ -10,7 +11,7 @@ import { logError, logWarning } from '../console/logging.js';
10
11
  * @param retryDelay - Delay between retries in milliseconds
11
12
  * @returns Object containing successful and failed file IDs
12
13
  */
13
- export async function downloadFileBatch(baseUrl, apiKey, files, maxRetries = 3, retryDelay = 1000) {
14
+ export async function downloadFileBatch(baseUrl, projectId, apiKey, files, maxRetries = 3, retryDelay = 1000) {
14
15
  let retries = 0;
15
16
  const fileIds = files.map((file) => file.translationId);
16
17
  const result = { successful: [], failed: [] };
@@ -22,7 +23,7 @@ export async function downloadFileBatch(baseUrl, apiKey, files, maxRetries = 3,
22
23
  method: 'POST',
23
24
  headers: {
24
25
  'Content-Type': 'application/json',
25
- ...(apiKey && { 'x-gt-api-key': apiKey }),
26
+ ...getAuthHeaders(projectId, apiKey),
26
27
  },
27
28
  body: JSON.stringify({ fileIds }),
28
29
  });
@@ -7,4 +7,4 @@ import { RetrievedTranslations } from '../types/api.js';
7
7
  * @param translationsDir - The directory to save the translations to
8
8
  * @param fileType - The file type to save the translations as (file extension)
9
9
  */
10
- export declare function fetchTranslations(baseUrl: string, apiKey: string, versionId: string): Promise<RetrievedTranslations>;
10
+ export declare function fetchTranslations(baseUrl: string, projectId: string, apiKey: string, versionId: string): Promise<RetrievedTranslations>;
@@ -1,5 +1,6 @@
1
1
  import chalk from 'chalk';
2
2
  import { logError } from '../console/logging.js';
3
+ import { getAuthHeaders } from '../utils/headers.js';
3
4
  /**
4
5
  * Fetches translations from the API and saves them to a local directory
5
6
  * @param baseUrl - The base URL for the API
@@ -8,13 +9,13 @@ import { logError } from '../console/logging.js';
8
9
  * @param translationsDir - The directory to save the translations to
9
10
  * @param fileType - The file type to save the translations as (file extension)
10
11
  */
11
- export async function fetchTranslations(baseUrl, apiKey, versionId) {
12
+ export async function fetchTranslations(baseUrl, projectId, apiKey, versionId) {
12
13
  // First fetch the translations from the API
13
14
  const response = await fetch(`${baseUrl}/v1/project/translations/info/${encodeURIComponent(versionId)}`, {
14
15
  method: 'GET',
15
16
  headers: {
16
17
  'Content-Type': 'application/json',
17
- ...(apiKey && { 'x-gt-api-key': apiKey }),
18
+ ...getAuthHeaders(projectId, apiKey),
18
19
  },
19
20
  });
20
21
  if (response.ok) {
@@ -1,5 +1,6 @@
1
1
  import chalk from 'chalk';
2
2
  import { createSpinner, logMessage, logSuccess } from '../console/logging.js';
3
+ import { getAuthHeaders } from '../utils/headers.js';
3
4
  /**
4
5
  * Sends multiple files for translation to the API
5
6
  * @param files - Array of file objects to translate
@@ -7,7 +8,6 @@ import { createSpinner, logMessage, logSuccess } from '../console/logging.js';
7
8
  * @returns The translated content or version ID
8
9
  */
9
10
  export async function sendFiles(files, options) {
10
- const { apiKey } = options;
11
11
  logMessage(chalk.cyan('Files to translate:') +
12
12
  '\n' +
13
13
  files.map((file) => ` - ${chalk.bold(file.fileName)}`).join('\n'));
@@ -35,7 +35,7 @@ export async function sendFiles(files, options) {
35
35
  const response = await fetch(`${options.baseUrl}/v1/project/translations/files/upload`, {
36
36
  method: 'POST',
37
37
  headers: {
38
- ...(apiKey && { 'x-gt-api-key': apiKey }),
38
+ ...getAuthHeaders(options.projectId, options.apiKey),
39
39
  },
40
40
  body: formData,
41
41
  });
@@ -2,6 +2,7 @@ import chalk from 'chalk';
2
2
  import { createSpinner, logSuccess, logWarning } from '../console/logging.js';
3
3
  import updateConfig from '../fs/config/updateConfig.js';
4
4
  import { isUsingLocalTranslations } from '../config/utils.js';
5
+ import { getAuthHeaders } from '../utils/headers.js';
5
6
  /**
6
7
  * Sends updates to the API
7
8
  * @param updates - The updates to send
@@ -35,7 +36,7 @@ export async function sendUpdates(updates, options, library) {
35
36
  method: 'POST',
36
37
  headers: {
37
38
  'Content-Type': 'application/json',
38
- ...(apiKey && { 'x-gt-api-key': apiKey }),
39
+ ...getAuthHeaders(options.projectId, options.apiKey),
39
40
  },
40
41
  body: JSON.stringify(body),
41
42
  });
@@ -8,4 +8,4 @@
8
8
  * @param timeoutDuration - The timeout duration for the wait
9
9
  * @returns True if all translations are deployed, false otherwise
10
10
  */
11
- export declare const waitForUpdates: (apiKey: string, baseUrl: string, versionId: string, startTime: number, timeoutDuration: number) => Promise<boolean>;
11
+ export declare const waitForUpdates: (projectId: string, apiKey: string, baseUrl: string, versionId: string, startTime: number, timeoutDuration: number) => Promise<boolean>;
@@ -1,6 +1,7 @@
1
1
  import chalk from 'chalk';
2
2
  import { createOraSpinner, logErrorAndExit, } from '../console/logging.js';
3
3
  import { getLocaleProperties } from 'generaltranslation';
4
+ import { getAuthHeaders } from '../utils/headers.js';
4
5
  /**
5
6
  * Waits for translations to be deployed to the General Translation API
6
7
  * @param apiKey - The API key for the General Translation API
@@ -11,7 +12,7 @@ import { getLocaleProperties } from 'generaltranslation';
11
12
  * @param timeoutDuration - The timeout duration for the wait
12
13
  * @returns True if all translations are deployed, false otherwise
13
14
  */
14
- export const waitForUpdates = async (apiKey, baseUrl, versionId, startTime, timeoutDuration) => {
15
+ export const waitForUpdates = async (projectId, apiKey, baseUrl, versionId, startTime, timeoutDuration) => {
15
16
  console.log();
16
17
  const spinner = await createOraSpinner();
17
18
  spinner.start('Waiting for translation...');
@@ -21,7 +22,7 @@ export const waitForUpdates = async (apiKey, baseUrl, versionId, startTime, time
21
22
  method: 'GET',
22
23
  headers: {
23
24
  'Content-Type': 'application/json',
24
- ...(apiKey && { 'x-gt-api-key': apiKey }),
25
+ ...getAuthHeaders(projectId, apiKey),
25
26
  },
26
27
  });
27
28
  if (response.ok) {
@@ -1,5 +1,5 @@
1
- import { displayProjectId, warnApiKeyInConfig } from '../console/logging.js';
2
- import loadConfig from '../fs/config/loadConfig.js';
1
+ import { displayProjectId, logErrorAndExit, warnApiKeyInConfig, } from '../console/logging.js';
2
+ import { loadConfig } from '../fs/config/loadConfig.js';
3
3
  import { defaultBaseUrl, libraryDefaultLocale, } from 'generaltranslation/internal';
4
4
  import fs from 'node:fs';
5
5
  import { createOrUpdateConfig } from '../fs/config/setupConfig.js';
@@ -9,6 +9,8 @@ import { validateSettings } from './validateSettings.js';
9
9
  import { GT_DASHBOARD_URL } from '../utils/constants.js';
10
10
  import { resolveProjectId } from '../fs/utils.js';
11
11
  import path from 'node:path';
12
+ import chalk from 'chalk';
13
+ import { resolveConfig } from './resolveConfig.js';
12
14
  /**
13
15
  * Generates settings from any
14
16
  * @param options - The options to generate settings from
@@ -24,23 +26,33 @@ export async function generateSettings(options, cwd = process.cwd()) {
24
26
  if (options.config) {
25
27
  gtConfig = loadConfig(options.config);
26
28
  }
27
- else if (fs.existsSync(path.join(cwd, 'gt.config.json'))) {
28
- options.config = path.join(cwd, 'gt.config.json');
29
- gtConfig = loadConfig(options.config);
30
- }
31
- else if (fs.existsSync(path.join(cwd, 'src/gt.config.json'))) {
32
- options.config = path.join(cwd, 'src/gt.config.json');
33
- gtConfig = loadConfig(options.config);
34
- }
35
29
  else {
36
- // If neither config exists, use empty config
37
- gtConfig = {};
30
+ const config = resolveConfig(cwd);
31
+ if (config) {
32
+ gtConfig = config.config;
33
+ options.config = config.path;
34
+ }
35
+ else {
36
+ gtConfig = {};
37
+ }
38
38
  }
39
39
  // Warn if apiKey is present in gt.config.json
40
40
  if (gtConfig.apiKey) {
41
41
  warnApiKeyInConfig(options.config);
42
42
  process.exit(1);
43
43
  }
44
+ const projectIdEnv = resolveProjectId();
45
+ // Resolve mismatched projectIds
46
+ if (gtConfig.projectId &&
47
+ options.projectId &&
48
+ gtConfig.projectId !== options.projectId) {
49
+ logErrorAndExit(`Project ID mismatch between ${chalk.green(gtConfig.projectId)} and ${chalk.green(options.projectId)}! Please use the same projectId in all configs.`);
50
+ }
51
+ else if (gtConfig.projectId &&
52
+ projectIdEnv &&
53
+ gtConfig.projectId !== projectIdEnv) {
54
+ logErrorAndExit(`Project ID mismatch between ${chalk.green(gtConfig.projectId)} and ${chalk.green(projectIdEnv)}! Please use the same projectId in all configs.`);
55
+ }
44
56
  // merge options
45
57
  const mergedOptions = { ...gtConfig, ...options };
46
58
  // merge locales
@@ -0,0 +1,4 @@
1
+ export declare function resolveConfig(cwd: string): {
2
+ path: string;
3
+ config: Record<string, any>;
4
+ } | null;
@@ -0,0 +1,19 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { loadConfig } from '../fs/config/loadConfig.js';
4
+ export function resolveConfig(cwd) {
5
+ const configFilepath = 'gt.config.json';
6
+ if (fs.existsSync(path.join(cwd, configFilepath))) {
7
+ return {
8
+ path: path.join(cwd, configFilepath),
9
+ config: loadConfig(path.join(cwd, configFilepath)),
10
+ };
11
+ }
12
+ if (fs.existsSync(path.join(cwd, 'src/gt.config.json'))) {
13
+ return {
14
+ path: path.join(cwd, 'src/gt.config.json'),
15
+ config: loadConfig(path.join(cwd, 'src/gt.config.json')),
16
+ };
17
+ }
18
+ return null;
19
+ }
@@ -99,7 +99,7 @@ export async function translateFiles(filePaths, placeholderPaths, transformPaths
99
99
  // Process any translations that were already completed and returned with the initial response
100
100
  const downloadStatus = await processInitialTranslations(translations, fileMapping, options);
101
101
  // Check for remaining translations
102
- await checkFileTranslations(options.apiKey, options.baseUrl, data, locales, 600, (sourcePath, locale) => fileMapping[locale][sourcePath], downloadStatus // Pass the already downloaded files to avoid duplicate requests
102
+ await checkFileTranslations(options.projectId, options.apiKey, options.baseUrl, data, locales, 600, (sourcePath, locale) => fileMapping[locale][sourcePath], downloadStatus // Pass the already downloaded files to avoid duplicate requests
103
103
  );
104
104
  }
105
105
  catch (error) {
@@ -181,7 +181,7 @@ async function processInitialTranslations(translations = [], fileMapping, option
181
181
  }
182
182
  // Use batch download if there are multiple files
183
183
  if (batchFiles.length > 1) {
184
- const batchResult = await downloadFileBatch(options.baseUrl, options.apiKey, batchFiles.map(({ translationId, outputPath }) => ({
184
+ const batchResult = await downloadFileBatch(options.baseUrl, options.projectId, options.apiKey, batchFiles.map(({ translationId, outputPath }) => ({
185
185
  translationId,
186
186
  outputPath,
187
187
  })));
@@ -199,7 +199,7 @@ async function processInitialTranslations(translations = [], fileMapping, option
199
199
  else if (batchFiles.length === 1) {
200
200
  // For a single file, use the original downloadFile method
201
201
  const file = batchFiles[0];
202
- const result = await downloadFile(options.baseUrl, options.apiKey, file.translationId, file.outputPath);
202
+ const result = await downloadFile(options.baseUrl, options.projectId, options.apiKey, file.translationId, file.outputPath);
203
203
  if (result) {
204
204
  downloadStatus.downloaded.add(file.fileLocale);
205
205
  }
@@ -1 +1 @@
1
- export default function loadConfig(filepath: string): Record<string, any>;
1
+ export declare function loadConfig(filepath: string): Record<string, any>;
@@ -1,5 +1,5 @@
1
1
  import fs from 'node:fs';
2
- export default function loadConfig(filepath) {
2
+ export function loadConfig(filepath) {
3
3
  try {
4
4
  return JSON.parse(fs.readFileSync(filepath, 'utf-8'));
5
5
  }
@@ -6,11 +6,11 @@ export async function translate(settings, versionId) {
6
6
  // timeout was validated earlier
7
7
  const startTime = Date.now();
8
8
  const timeout = parseInt(settings.timeout) * 1000;
9
- const result = await waitForUpdates(settings.apiKey, settings.baseUrl, versionId, startTime, timeout);
9
+ const result = await waitForUpdates(settings.projectId, settings.apiKey, settings.baseUrl, versionId, startTime, timeout);
10
10
  if (!result) {
11
11
  process.exit(1);
12
12
  }
13
- const translations = await fetchTranslations(settings.baseUrl, settings.apiKey, versionId);
13
+ const translations = await fetchTranslations(settings.baseUrl, settings.projectId, settings.apiKey, versionId);
14
14
  // Save translations to local directory if files.gt.output is provided
15
15
  if (settings.files && isUsingLocalTranslations(settings)) {
16
16
  await saveTranslations(translations, settings.files.placeholderPaths, 'JSX');
@@ -0,0 +1 @@
1
+ export declare function getAuthHeaders(projectId: string, apiKey: string): Record<string, string>;
@@ -0,0 +1,14 @@
1
+ export function getAuthHeaders(projectId, apiKey) {
2
+ const authHeaders = {
3
+ 'x-gt-project-id': projectId,
4
+ };
5
+ if (apiKey) {
6
+ if (apiKey.startsWith('gtx-internal-')) {
7
+ authHeaders['x-gt-internal-api-key'] = apiKey;
8
+ }
9
+ else {
10
+ authHeaders['x-gt-api-key'] = apiKey;
11
+ }
12
+ }
13
+ return authHeaders;
14
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gtx-cli",
3
- "version": "1.2.30",
3
+ "version": "1.2.31-alpha.2",
4
4
  "main": "dist/index.js",
5
5
  "bin": "dist/main.js",
6
6
  "files": [
@@ -11,6 +11,7 @@
11
11
  "scripts": {
12
12
  "build": "tsc",
13
13
  "build:clean": "rm -rf dist; npm run build",
14
+ "build:release": "npm run build:clean",
14
15
  "lint": "eslint \"src/**/*.{js,ts}\" \"__tests__/**/*.{js,ts}\"",
15
16
  "lint:fix": "eslint \"src/**/*.{js,ts}\" \"__tests__/**/*.{js,ts}\" --fix",
16
17
  "test": "vitest run",