gtx-cli 2.5.8 → 2.5.9

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,14 @@
1
1
  # gtx-cli
2
2
 
3
+ ## 2.5.9
4
+
5
+ ### Patch Changes
6
+
7
+ - [#823](https://github.com/generaltranslation/gt/pull/823) [`afbd29a`](https://github.com/generaltranslation/gt/commit/afbd29a34b051c76fce387269c4eb4a2e00a5831) Thanks [@brian-lou](https://github.com/brian-lou)! - Deprecate old 'setup' command -> Use 'init' instead. New 'setup' command runs project setup
8
+
9
+ - Updated dependencies [[`afbd29a`](https://github.com/generaltranslation/gt/commit/afbd29a34b051c76fce387269c4eb4a2e00a5831)]:
10
+ - generaltranslation@8.0.3
11
+
3
12
  ## 2.5.8
4
13
 
5
14
  ### Patch Changes
@@ -16,16 +16,17 @@ export declare class BaseCLI {
16
16
  constructor(program: Command, library: SupportedLibraries, additionalModules?: SupportedLibraries[]);
17
17
  init(): void;
18
18
  execute(): void;
19
+ protected setupSetupProjectCommand(): void;
19
20
  protected setupStageCommand(): void;
20
21
  protected setupTranslateCommand(): void;
21
22
  protected setupSendDiffsCommand(): void;
23
+ protected handleSetupProject(initOptions: TranslateFlags): Promise<void>;
22
24
  protected handleStage(initOptions: TranslateFlags): Promise<void>;
23
25
  protected handleTranslate(initOptions: TranslateFlags): Promise<void>;
24
26
  protected setupUploadCommand(): void;
25
27
  protected setupLoginCommand(): void;
26
28
  protected setupInitCommand(): void;
27
29
  protected setupConfigureCommand(): void;
28
- protected setupSetupCommand(): void;
29
30
  protected handleUploadCommand(settings: Settings & UploadOptions): Promise<void>;
30
31
  protected handleSetupReactCommand(options: SetupOptions): Promise<void>;
31
32
  protected handleInitCommand(ranReactSetup: boolean): Promise<void>;
package/dist/cli/base.js CHANGED
@@ -17,6 +17,7 @@ import { areCredentialsSet } from '../utils/credentials.js';
17
17
  import { upload } from '../formats/files/upload.js';
18
18
  import { attachTranslateFlags } from './flags.js';
19
19
  import { handleStage } from './commands/stage.js';
20
+ import { handleSetupProject } from './commands/setupProject.js';
20
21
  import { handleDownload, handleTranslate, postProcessTranslations, } from './commands/translate.js';
21
22
  import { getDownloaded, clearDownloaded } from '../state/recentDownloads.js';
22
23
  import updateConfig from '../fs/config/updateConfig.js';
@@ -34,13 +35,14 @@ export class BaseCLI {
34
35
  this.additionalModules = additionalModules || [];
35
36
  this.setupInitCommand();
36
37
  this.setupConfigureCommand();
37
- this.setupSetupCommand();
38
38
  this.setupUploadCommand();
39
39
  this.setupLoginCommand();
40
40
  this.setupSendDiffsCommand();
41
41
  }
42
42
  // Init is never called in a child class
43
43
  init() {
44
+ this.setupSetupProjectCommand();
45
+ this.setupStageCommand();
44
46
  this.setupTranslateCommand();
45
47
  }
46
48
  // Execute is called by the main program
@@ -50,6 +52,15 @@ export class BaseCLI {
50
52
  process.argv.push('init');
51
53
  }
52
54
  }
55
+ setupSetupProjectCommand() {
56
+ attachTranslateFlags(this.program
57
+ .command('setup')
58
+ .description('Upload source files and setup the project for translation')).action(async (initOptions) => {
59
+ displayHeader('Uploading source files and setting up project...');
60
+ await this.handleSetupProject(initOptions);
61
+ logger.endCommand('Done!');
62
+ });
63
+ }
53
64
  setupStageCommand() {
54
65
  attachTranslateFlags(this.program
55
66
  .command('stage')
@@ -80,6 +91,12 @@ export class BaseCLI {
80
91
  logger.endCommand('Saved local edits');
81
92
  });
82
93
  }
94
+ async handleSetupProject(initOptions) {
95
+ const settings = await generateSettings(initOptions);
96
+ // Preprocess shared static assets if configured (move + rewrite sources)
97
+ await processSharedStaticAssets(settings);
98
+ await handleSetupProject(initOptions, settings, this.library);
99
+ }
83
100
  async handleStage(initOptions) {
84
101
  const settings = await generateSettings(initOptions);
85
102
  // Preprocess shared static assets if configured (move + rewrite sources)
@@ -208,18 +225,6 @@ See the docs for more information: https://generaltranslation.com/docs/react/tut
208
225
  logger.endCommand('Done! Make sure you have an API key and project ID to use General Translation. Get them on the dashboard: https://generaltranslation.com/dashboard');
209
226
  });
210
227
  }
211
- setupSetupCommand() {
212
- this.program
213
- .command('setup')
214
- .description('Run the setup to configure your Next.js or React project for General Translation')
215
- .option('--src <paths...>', "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}'")
216
- .option('-c, --config <path>', 'Filepath to config file, by default gt.config.json', findFilepath(['gt.config.json']))
217
- .action(async (options) => {
218
- displayHeader('Running React setup wizard...');
219
- await this.handleSetupReactCommand(options);
220
- logger.endCommand("Done! Take advantage of all of General Translation's features by signing up for a free account! https://generaltranslation.com/signup");
221
- });
222
- }
223
228
  async handleUploadCommand(settings) {
224
229
  // dataFormat for JSONs
225
230
  let dataFormat;
@@ -265,7 +270,7 @@ See the docs for more information: https://generaltranslation.com/docs/react/tut
265
270
  message: `Auto-detected that you're using gt-next or gt-react. Would you like to use the General Translation CDN to store your translations?\nSee ${isUsingGTNext
266
271
  ? 'https://generaltranslation.com/en/docs/next/guides/local-tx'
267
272
  : 'https://generaltranslation.com/en/docs/react/guides/local-tx'} for more information.\nIf you answer no, we'll configure the CLI tool to download completed translations.`,
268
- defaultValue: true,
273
+ defaultValue: false,
269
274
  })
270
275
  : false;
271
276
  // Ask where the translations are stored
@@ -279,7 +284,7 @@ See the docs for more information: https://generaltranslation.com/docs/react/tut
279
284
  const finalTranslationsDir = translationsDir?.trim() || './public/_gt';
280
285
  if (isUsingGT && !usingCDN) {
281
286
  // Create loadTranslations.js file for local translations
282
- await createLoadTranslationsFile(process.cwd(), finalTranslationsDir);
287
+ await createLoadTranslationsFile(process.cwd(), finalTranslationsDir, locales);
283
288
  logger.message(`Created ${chalk.cyan('loadTranslations.js')} file for local translations.
284
289
  Make sure to add this function to your app configuration.
285
290
  See https://generaltranslation.com/en/docs/next/guides/local-tx`);
@@ -0,0 +1,7 @@
1
+ import { Settings, SupportedLibraries, TranslateFlags } from '../../types/index.js';
2
+ import { FileTranslationData } from '../../workflow/download.js';
3
+ import { BranchData } from '../../types/branch.js';
4
+ export declare function handleSetupProject(options: TranslateFlags, settings: Settings, library: SupportedLibraries): Promise<{
5
+ fileVersionData: FileTranslationData | undefined;
6
+ branchData: BranchData | undefined;
7
+ } | null>;
@@ -0,0 +1,50 @@
1
+ import { logger } from '../../console/logger.js';
2
+ import { logCollectedFiles, logErrorAndExit } from '../../console/logging.js';
3
+ import { noLocalesError, noDefaultLocaleError, noApiKeyError, noProjectIdError, devApiKeyError, } from '../../console/index.js';
4
+ import { collectFiles } from '../../formats/files/collectFiles.js';
5
+ import { setupProject } from '../../workflow/setupProject.js';
6
+ export async function handleSetupProject(options, settings, library) {
7
+ // Validate required settings are present if not in dry run
8
+ if (!options.dryRun) {
9
+ if (!settings.locales) {
10
+ return logErrorAndExit(noLocalesError);
11
+ }
12
+ if (!settings.defaultLocale) {
13
+ return logErrorAndExit(noDefaultLocaleError);
14
+ }
15
+ if (!settings.apiKey) {
16
+ return logErrorAndExit(noApiKeyError);
17
+ }
18
+ if (settings.apiKey.startsWith('gtx-dev-')) {
19
+ return logErrorAndExit(devApiKeyError);
20
+ }
21
+ if (!settings.projectId) {
22
+ return logErrorAndExit(noProjectIdError);
23
+ }
24
+ }
25
+ const { files: allFiles, reactComponents } = await collectFiles(options, settings, library);
26
+ // Dry run
27
+ if (options.dryRun) {
28
+ logger.success(`Dry run: No files were uploaded to General Translation.`);
29
+ logCollectedFiles(allFiles, reactComponents);
30
+ return null;
31
+ }
32
+ // Upload files and run setup step
33
+ let fileVersionData;
34
+ let branchData;
35
+ if (allFiles.length > 0) {
36
+ const { branchData: branchDataResult } = await setupProject(allFiles, options, settings);
37
+ branchData = branchDataResult;
38
+ fileVersionData = Object.fromEntries(allFiles.map((file) => [
39
+ file.fileId,
40
+ {
41
+ fileName: file.fileName,
42
+ versionId: file.versionId,
43
+ },
44
+ ]));
45
+ }
46
+ return {
47
+ fileVersionData,
48
+ branchData,
49
+ };
50
+ }
@@ -2,8 +2,6 @@ import { Settings, SupportedLibraries, TranslateFlags } from '../../types/index.
2
2
  import type { EnqueueFilesResult } from 'generaltranslation/types';
3
3
  import { FileTranslationData } from '../../workflow/download.js';
4
4
  import { BranchData } from '../../types/branch.js';
5
- export declare const TEMPLATE_FILE_NAME = "__INTERNAL_GT_TEMPLATE_NAME__";
6
- export declare const TEMPLATE_FILE_ID: string;
7
5
  export declare function handleStage(options: TranslateFlags, settings: Settings, library: SupportedLibraries, stage: boolean): Promise<{
8
6
  fileVersionData: FileTranslationData | undefined;
9
7
  jobData: EnqueueFilesResult | undefined;
@@ -1,14 +1,11 @@
1
1
  import { logger } from '../../console/logger.js';
2
- import { logErrorAndExit } from '../../console/logging.js';
3
- import { noLocalesError, noDefaultLocaleError, noApiKeyError, noProjectIdError, devApiKeyError, invalidConfigurationError, } from '../../console/index.js';
4
- import { aggregateFiles } from '../../formats/files/translate.js';
5
- import { aggregateReactTranslations } from '../../translation/stage.js';
2
+ import { logCollectedFiles, logErrorAndExit } from '../../console/logging.js';
3
+ import { noLocalesError, noDefaultLocaleError, noApiKeyError, noProjectIdError, devApiKeyError, } from '../../console/index.js';
6
4
  import { stageFiles } from '../../workflow/stage.js';
7
5
  import { updateVersions } from '../../fs/config/updateVersions.js';
8
6
  import updateConfig from '../../fs/config/updateConfig.js';
9
- import { hashStringSync } from '../../utils/hash.js';
10
- export const TEMPLATE_FILE_NAME = '__INTERNAL_GT_TEMPLATE_NAME__';
11
- export const TEMPLATE_FILE_ID = hashStringSync(TEMPLATE_FILE_NAME);
7
+ import { TEMPLATE_FILE_ID } from '../../utils/constants.js';
8
+ import { collectFiles } from '../../formats/files/collectFiles.js';
12
9
  export async function handleStage(options, settings, library, stage) {
13
10
  // Validate required settings are present if not in dry run
14
11
  if (!options.dryRun) {
@@ -28,57 +25,11 @@ export async function handleStage(options, settings, library, stage) {
28
25
  return logErrorAndExit(noProjectIdError);
29
26
  }
30
27
  }
31
- // Aggregate files
32
- const allFiles = await aggregateFiles(settings);
33
- // Parse for React components
34
- let reactComponents = 0;
35
- if (library === 'gt-react' || library === 'gt-next') {
36
- const updates = await aggregateReactTranslations(options, settings, library);
37
- if (updates.length > 0) {
38
- if (!options.dryRun &&
39
- !settings.publish &&
40
- !settings.files?.placeholderPaths.gt) {
41
- logErrorAndExit(invalidConfigurationError);
42
- }
43
- reactComponents = updates.length;
44
- // Convert updates to a file object
45
- const fileData = {};
46
- const fileMetadata = {};
47
- // Convert updates to the proper data format
48
- for (const update of updates) {
49
- const { source, metadata, dataFormat } = update;
50
- metadata.dataFormat = dataFormat; // add the data format to the metadata
51
- const { hash, id } = metadata;
52
- if (id) {
53
- fileData[id] = source;
54
- fileMetadata[id] = metadata;
55
- }
56
- else {
57
- fileData[hash] = source;
58
- fileMetadata[hash] = metadata;
59
- }
60
- }
61
- allFiles.push({
62
- fileName: TEMPLATE_FILE_NAME,
63
- content: JSON.stringify(fileData),
64
- fileFormat: 'GTJSON',
65
- formatMetadata: fileMetadata,
66
- fileId: TEMPLATE_FILE_ID,
67
- versionId: hashStringSync(JSON.stringify(fileData)),
68
- });
69
- }
70
- }
28
+ const { files: allFiles, reactComponents } = await collectFiles(options, settings, library);
71
29
  // Dry run
72
30
  if (options.dryRun) {
73
- const fileNames = allFiles
74
- .map((file) => {
75
- if (file.fileName === TEMPLATE_FILE_NAME) {
76
- return `- <React Elements> (${reactComponents})`;
77
- }
78
- return `- ${file.fileName}`;
79
- })
80
- .join('\n');
81
- logger.success(`Dry run: No files were sent to General Translation. Found files:\n${fileNames}`);
31
+ logger.success(`Dry run: No files were sent to General Translation.`);
32
+ logCollectedFiles(allFiles, reactComponents);
82
33
  return null;
83
34
  }
84
35
  // Send translations to General Translation
package/dist/cli/next.js CHANGED
@@ -6,6 +6,7 @@ export class NextCLI extends ReactCLI {
6
6
  super(command, library, additionalModules);
7
7
  }
8
8
  init() {
9
+ this.setupSetupProjectCommand();
9
10
  this.setupStageCommand();
10
11
  this.setupTranslateCommand();
11
12
  this.setupGenerateSourceCommand();
@@ -8,6 +8,7 @@ export declare class ReactCLI extends BaseCLI {
8
8
  protected wrapContent(options: WrapOptions, framework: SupportedFrameworks, errors: string[], warnings: string[]): Promise<{
9
9
  filesUpdated: string[];
10
10
  }>;
11
+ protected setupSetupProjectCommand(): void;
11
12
  protected setupStageCommand(): void;
12
13
  protected setupTranslateCommand(): void;
13
14
  protected setupValidateCommand(): void;
package/dist/cli/react.js CHANGED
@@ -21,6 +21,7 @@ export class ReactCLI extends BaseCLI {
21
21
  super(command, library, additionalModules);
22
22
  }
23
23
  init() {
24
+ this.setupSetupProjectCommand();
24
25
  this.setupStageCommand();
25
26
  this.setupTranslateCommand();
26
27
  this.setupGenerateSourceCommand();
@@ -32,6 +33,15 @@ export class ReactCLI extends BaseCLI {
32
33
  wrapContent(options, framework, errors, warnings) {
33
34
  return wrapContentReact(options, pkg, framework, errors, warnings);
34
35
  }
36
+ setupSetupProjectCommand() {
37
+ attachAdditionalReactTranslateFlags(attachTranslateFlags(this.program
38
+ .command('setup')
39
+ .description('Upload source files and setup the project for translation'))).action(async (options) => {
40
+ displayHeader('Uploading source files and setting up project...');
41
+ await this.handleSetupProject(options);
42
+ logger.endCommand('Done!');
43
+ });
44
+ }
35
45
  setupStageCommand() {
36
46
  attachAdditionalReactTranslateFlags(attachTranslateFlags(this.program
37
47
  .command('stage')
@@ -1,5 +1,13 @@
1
1
  import type { SpinnerResult, ProgressResult } from '@clack/prompts';
2
2
  export type LogFormat = 'default' | 'json';
3
+ /**
4
+ * GT_LOG_FORMAT: default | json.
5
+ * - If default, logs will be pretty-printed using @clack/prompts.
6
+ * - If json, logs will be written in JSON format to the console.
7
+ * GT_LOG_FILE: If specified, logs will be written to the file.
8
+ * GT_LOG_LEVEL: The level of logs to write. If not specified, defaults to 'info'.
9
+ * - Valid levels: debug, info, warn, error.
10
+ */
3
11
  declare class Logger {
4
12
  private static instance;
5
13
  private pinoLogger;
@@ -59,6 +59,14 @@ class MockProgress {
59
59
  this.logger.info(`[Progress] ${msg} (${this.current}/${this.max})`);
60
60
  }
61
61
  }
62
+ /**
63
+ * GT_LOG_FORMAT: default | json.
64
+ * - If default, logs will be pretty-printed using @clack/prompts.
65
+ * - If json, logs will be written in JSON format to the console.
66
+ * GT_LOG_FILE: If specified, logs will be written to the file.
67
+ * GT_LOG_LEVEL: The level of logs to write. If not specified, defaults to 'info'.
68
+ * - Valid levels: debug, info, warn, error.
69
+ */
62
70
  class Logger {
63
71
  static instance;
64
72
  pinoLogger = null;
@@ -67,12 +75,19 @@ class Logger {
67
75
  constructor() {
68
76
  // Read configuration from environment variables
69
77
  const format = (process.env.GT_LOG_FORMAT || 'default').toLowerCase();
78
+ const logFile = process.env.GT_LOG_FILE;
79
+ const logLevel = process.env.GT_LOG_LEVEL || 'info';
70
80
  if (format !== 'default' && format !== 'json') {
71
81
  console.error('Invalid log format');
72
82
  process.exit(1);
73
83
  }
74
- const logFile = process.env.GT_LOG_FILE;
75
- const logLevel = process.env.GT_LOG_LEVEL || 'info';
84
+ if (logLevel !== 'debug' &&
85
+ logLevel !== 'info' &&
86
+ logLevel !== 'warn' &&
87
+ logLevel !== 'error') {
88
+ console.error('Invalid log level');
89
+ process.exit(1);
90
+ }
76
91
  this.logFormat = format;
77
92
  const transports = [];
78
93
  // Console output (stdout)
@@ -1,3 +1,4 @@
1
+ import { FileToUpload } from 'generaltranslation/types';
1
2
  export declare function logErrorAndExit(message: string): never;
2
3
  export declare function exitSync(code: number): never;
3
4
  export declare function displayHeader(introString?: string): void;
@@ -41,3 +42,7 @@ export declare function warnHasUnwrappedExpression(file: string, id: string, unw
41
42
  export declare function warnNonStaticExpression(file: string, attrName: string, value: string): void;
42
43
  export declare function warnTemplateLiteral(file: string, value: string): void;
43
44
  export declare function warnTernary(file: string): void;
45
+ /**
46
+ * Helper: Log all collected files
47
+ */
48
+ export declare function logCollectedFiles(files: FileToUpload[], reactComponents?: number): void;
@@ -2,6 +2,7 @@ import { text, select, confirm, isCancel, cancel, multiselect, } from '@clack/pr
2
2
  import chalk from 'chalk';
3
3
  import { getCLIVersion } from '../utils/packageJson.js';
4
4
  import { logger } from './logger.js';
5
+ import { TEMPLATE_FILE_NAME } from '../utils/constants.js';
5
6
  export function logErrorAndExit(message) {
6
7
  logger.error(message);
7
8
  return exitSync(1);
@@ -151,3 +152,18 @@ export function warnTernary(file) {
151
152
  logger.warn(`Found ternary expression in ${chalk.cyan(file)}. ` +
152
153
  chalk.white('A Branch component may be more appropriate here.'));
153
154
  }
155
+ /**
156
+ * Helper: Log all collected files
157
+ */
158
+ export function logCollectedFiles(files, reactComponents) {
159
+ logger.message(chalk.cyan('Files found in project:') +
160
+ '\n' +
161
+ files
162
+ .map((file) => {
163
+ if (file.fileName === TEMPLATE_FILE_NAME) {
164
+ return `- <React Elements>${reactComponents ? ` (${reactComponents})` : ''}`;
165
+ }
166
+ return `- ${file.fileName}`;
167
+ })
168
+ .join('\n'));
169
+ }
@@ -0,0 +1,6 @@
1
+ import { Settings, SupportedLibraries, TranslateFlags } from '../../types/index.js';
2
+ import type { FileToUpload } from 'generaltranslation/types';
3
+ export declare function collectFiles(options: TranslateFlags, settings: Settings, library: SupportedLibraries): Promise<{
4
+ files: FileToUpload[];
5
+ reactComponents: number;
6
+ }>;
@@ -0,0 +1,49 @@
1
+ import { logErrorAndExit } from '../../console/logging.js';
2
+ import { invalidConfigurationError } from '../../console/index.js';
3
+ import { aggregateFiles } from '../../formats/files/translate.js';
4
+ import { aggregateReactTranslations } from '../../translation/stage.js';
5
+ import { hashStringSync } from '../../utils/hash.js';
6
+ import { TEMPLATE_FILE_NAME, TEMPLATE_FILE_ID } from '../../utils/constants.js';
7
+ export async function collectFiles(options, settings, library) {
8
+ // Aggregate files
9
+ const allFiles = await aggregateFiles(settings);
10
+ // Parse for React components
11
+ let reactComponents = 0;
12
+ if (library === 'gt-react' || library === 'gt-next') {
13
+ const updates = await aggregateReactTranslations(options, settings, library);
14
+ if (updates.length > 0) {
15
+ if (!options.dryRun &&
16
+ !settings.publish &&
17
+ !settings.files?.placeholderPaths.gt) {
18
+ logErrorAndExit(invalidConfigurationError);
19
+ }
20
+ // Convert updates to a file object
21
+ const fileData = {};
22
+ const fileMetadata = {};
23
+ // Convert updates to the proper data format
24
+ for (const update of updates) {
25
+ const { source, metadata, dataFormat } = update;
26
+ metadata.dataFormat = dataFormat; // add the data format to the metadata
27
+ const { hash, id } = metadata;
28
+ if (id) {
29
+ fileData[id] = source;
30
+ fileMetadata[id] = metadata;
31
+ }
32
+ else {
33
+ fileData[hash] = source;
34
+ fileMetadata[hash] = metadata;
35
+ }
36
+ }
37
+ reactComponents = updates.length;
38
+ allFiles.push({
39
+ fileName: TEMPLATE_FILE_NAME,
40
+ content: JSON.stringify(fileData),
41
+ fileFormat: 'GTJSON',
42
+ formatMetadata: fileMetadata,
43
+ fileId: TEMPLATE_FILE_ID,
44
+ versionId: hashStringSync(JSON.stringify(fileData)),
45
+ });
46
+ }
47
+ }
48
+ return { files: allFiles, reactComponents };
49
+ }
@@ -4,7 +4,7 @@ import path from 'node:path';
4
4
  import { getRelative } from '../../fs/findFilepath.js';
5
5
  import { getLocaleProperties } from 'generaltranslation';
6
6
  import { replaceLocalePlaceholders } from '../utils.js';
7
- import { TEMPLATE_FILE_NAME } from '../../cli/commands/stage.js';
7
+ import { TEMPLATE_FILE_NAME } from '../../utils/constants.js';
8
8
  /**
9
9
  * Creates a mapping between source files and their translated counterparts for each locale
10
10
  * @param filePaths - Resolved file paths for different file types
@@ -1 +1 @@
1
- export declare function createLoadTranslationsFile(appDirectory: string, translationsDir?: string): Promise<void>;
1
+ export declare function createLoadTranslationsFile(appDirectory: string, translationsDir: string | undefined, locales: string[]): Promise<void>;
@@ -2,7 +2,7 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { logger } from '../console/logger.js';
4
4
  import chalk from 'chalk';
5
- export async function createLoadTranslationsFile(appDirectory, translationsDir = './public/_gt') {
5
+ export async function createLoadTranslationsFile(appDirectory, translationsDir = './public/_gt', locales) {
6
6
  const usingSrcDirectory = fs.existsSync(path.join(appDirectory, 'src'));
7
7
  // Calculate the relative path from the loadTranslations.js location to the translations directory
8
8
  const loadTranslationsDir = usingSrcDirectory
@@ -29,6 +29,13 @@ export default async function loadTranslations(locale) {
29
29
  `;
30
30
  await fs.promises.writeFile(filePath, loadTranslationsContent);
31
31
  logger.info(`Created ${chalk.cyan('loadTranslations.js')} file at ${chalk.cyan(filePath)}.`);
32
+ // Create empty JSON files
33
+ for (const locale of locales) {
34
+ if (fs.existsSync(path.join(translationsDir, `${locale}.json`))) {
35
+ continue;
36
+ }
37
+ await fs.promises.writeFile(path.join(translationsDir, `${locale}.json`), '{}');
38
+ }
32
39
  }
33
40
  else {
34
41
  logger.info(`Found ${chalk.cyan('loadTranslations.js')} file at ${chalk.cyan(filePath)}. Skipping creation...`);
@@ -41,6 +41,7 @@ export type TranslateFlags = {
41
41
  dryRun: boolean;
42
42
  saveLocal?: boolean;
43
43
  stageTranslations?: boolean;
44
+ setupProject?: boolean;
44
45
  publish?: boolean;
45
46
  force?: boolean;
46
47
  forceDownload?: boolean;
@@ -1,2 +1,4 @@
1
1
  export declare const GT_DASHBOARD_URL = "https://dash.generaltranslation.com";
2
2
  export declare const GT_CONFIG_SCHEMA_URL = "https://assets.gtx.dev/config-schema.json";
3
+ export declare const TEMPLATE_FILE_NAME = "__INTERNAL_GT_TEMPLATE_NAME__";
4
+ export declare const TEMPLATE_FILE_ID: string;
@@ -1,2 +1,5 @@
1
+ import { hashStringSync } from './hash.js';
1
2
  export const GT_DASHBOARD_URL = 'https://dash.generaltranslation.com';
2
3
  export const GT_CONFIG_SCHEMA_URL = 'https://assets.gtx.dev/config-schema.json';
4
+ export const TEMPLATE_FILE_NAME = '__INTERNAL_GT_TEMPLATE_NAME__';
5
+ export const TEMPLATE_FILE_ID = hashStringSync(TEMPLATE_FILE_NAME);
@@ -1,7 +1,7 @@
1
1
  import chalk from 'chalk';
2
2
  import { WorkflowStep } from './Workflow.js';
3
3
  import { logger } from '../console/logger.js';
4
- import { TEMPLATE_FILE_NAME } from '../cli/commands/stage.js';
4
+ import { TEMPLATE_FILE_NAME } from '../utils/constants.js';
5
5
  export class PollTranslationJobsStep extends WorkflowStep {
6
6
  gt;
7
7
  spinner = null;
@@ -11,6 +11,6 @@ export declare class SetupStep extends WorkflowStep<FileReference[], FileReferen
11
11
  private files;
12
12
  private completed;
13
13
  constructor(gt: GT, settings: Settings, timeoutMs: number);
14
- run(files: FileReference[]): Promise<FileReference[]>;
14
+ run(files: FileReference[], force?: boolean): Promise<FileReference[]>;
15
15
  wait(): Promise<void>;
16
16
  }
@@ -15,7 +15,7 @@ export class SetupStep extends WorkflowStep {
15
15
  this.settings = settings;
16
16
  this.timeoutMs = timeoutMs;
17
17
  }
18
- async run(files) {
18
+ async run(files, force = false) {
19
19
  this.files = files;
20
20
  this.spinner.start('Setting up project...');
21
21
  if (files.length === 0) {
@@ -24,6 +24,7 @@ export class SetupStep extends WorkflowStep {
24
24
  }
25
25
  const result = await this.gt.setupProject(files, {
26
26
  locales: this.settings.locales,
27
+ force,
27
28
  });
28
29
  if (result.status === 'completed') {
29
30
  this.completed = true;
@@ -0,0 +1,13 @@
1
+ import { Settings, TranslateFlags } from '../types/index.js';
2
+ import { FileToUpload } from 'generaltranslation/types';
3
+ import { BranchData } from '../types/branch.js';
4
+ /**
5
+ * Sets up a project by uploading files running the setup step
6
+ * @param files - Array of file objects to upload
7
+ * @param options - The options for the API call
8
+ * @param settings - Settings configuration
9
+ * @returns The branch data
10
+ */
11
+ export declare function setupProject(files: FileToUpload[], options: TranslateFlags, settings: Settings): Promise<{
12
+ branchData: BranchData;
13
+ }>;
@@ -0,0 +1,48 @@
1
+ import { logErrorAndExit } from '../console/logging.js';
2
+ import { gt } from '../utils/gt.js';
3
+ import { UploadStep } from './UploadStep.js';
4
+ import { SetupStep } from './SetupStep.js';
5
+ import { BranchStep } from './BranchStep.js';
6
+ import { logCollectedFiles } from '../console/logging.js';
7
+ /**
8
+ * Helper: Calculate timeout with validation
9
+ */
10
+ function calculateTimeout(timeout) {
11
+ const value = timeout !== undefined ? Number(timeout) : 600;
12
+ return (Number.isFinite(value) ? value : 600) * 1000;
13
+ }
14
+ /**
15
+ * Sets up a project by uploading files running the setup step
16
+ * @param files - Array of file objects to upload
17
+ * @param options - The options for the API call
18
+ * @param settings - Settings configuration
19
+ * @returns The branch data
20
+ */
21
+ export async function setupProject(files, options, settings) {
22
+ try {
23
+ // Log files to be translated
24
+ logCollectedFiles(files);
25
+ // Calculate timeout for setup step
26
+ const timeoutMs = calculateTimeout(options.timeout);
27
+ // Create workflow with steps
28
+ const branchStep = new BranchStep(gt, settings);
29
+ const uploadStep = new UploadStep(gt, settings);
30
+ const setupStep = new SetupStep(gt, settings, timeoutMs);
31
+ // first run the branch step
32
+ const branchData = await branchStep.run();
33
+ await branchStep.wait();
34
+ if (!branchData) {
35
+ return logErrorAndExit('Failed to resolve git branch information.');
36
+ }
37
+ // then run the upload step
38
+ const uploadedFiles = await uploadStep.run({ files, branchData });
39
+ await uploadStep.wait();
40
+ // then run the setup step
41
+ await setupStep.run(uploadedFiles, options.force ?? false);
42
+ await setupStep.wait();
43
+ return { branchData };
44
+ }
45
+ catch (error) {
46
+ return logErrorAndExit('Failed to run project setup. ' + error);
47
+ }
48
+ }
@@ -1,8 +1,5 @@
1
- import chalk from 'chalk';
2
- import { logErrorAndExit } from '../console/logging.js';
3
- import { logger } from '../console/logger.js';
1
+ import { logCollectedFiles, logErrorAndExit } from '../console/logging.js';
4
2
  import { gt } from '../utils/gt.js';
5
- import { TEMPLATE_FILE_NAME } from '../cli/commands/stage.js';
6
3
  import { UploadStep } from './UploadStep.js';
7
4
  import { SetupStep } from './SetupStep.js';
8
5
  import { EnqueueStep } from './EnqueueStep.js';
@@ -15,21 +12,6 @@ function calculateTimeout(timeout) {
15
12
  const value = timeout !== undefined ? Number(timeout) : 600;
16
13
  return (Number.isFinite(value) ? value : 600) * 1000;
17
14
  }
18
- /**
19
- * Helper: Log files to be translated
20
- */
21
- function logFilesToTranslate(files) {
22
- logger.message(chalk.cyan('Files found in project:') +
23
- '\n' +
24
- files
25
- .map((file) => {
26
- if (file.fileName === TEMPLATE_FILE_NAME) {
27
- return `- <React Elements>`;
28
- }
29
- return `- ${file.fileName}`;
30
- })
31
- .join('\n'));
32
- }
33
15
  /**
34
16
  * Sends multiple files for translation to the API using a workflow pattern
35
17
  * @param files - Array of file objects to translate
@@ -40,7 +22,7 @@ function logFilesToTranslate(files) {
40
22
  export async function stageFiles(files, options, settings) {
41
23
  try {
42
24
  // Log files to be translated
43
- logFilesToTranslate(files);
25
+ logCollectedFiles(files);
44
26
  // Calculate timeout for setup step
45
27
  const timeoutMs = calculateTimeout(options.timeout);
46
28
  // Create workflow with steps
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gtx-cli",
3
- "version": "2.5.8",
3
+ "version": "2.5.9",
4
4
  "main": "dist/index.js",
5
5
  "bin": "dist/main.js",
6
6
  "files": [
@@ -93,7 +93,7 @@
93
93
  "unified": "^11.0.5",
94
94
  "unist-util-visit": "^5.0.0",
95
95
  "yaml": "^2.8.0",
96
- "generaltranslation": "8.0.2"
96
+ "generaltranslation": "8.0.3"
97
97
  },
98
98
  "devDependencies": {
99
99
  "@babel/types": "^7.28.4",