gtx-cli 2.5.0-alpha.0 → 2.5.0-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.
Files changed (93) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/api/collectUserEditDiffs.d.ts +2 -7
  3. package/dist/api/collectUserEditDiffs.js +33 -78
  4. package/dist/api/downloadFileBatch.d.ts +11 -10
  5. package/dist/api/downloadFileBatch.js +120 -127
  6. package/dist/api/saveLocalEdits.js +18 -15
  7. package/dist/cli/base.js +1 -1
  8. package/dist/cli/commands/stage.d.ts +8 -2
  9. package/dist/cli/commands/stage.js +25 -7
  10. package/dist/cli/commands/translate.d.ts +4 -2
  11. package/dist/cli/commands/translate.js +5 -6
  12. package/dist/cli/flags.js +4 -1
  13. package/dist/config/generateSettings.js +10 -0
  14. package/dist/console/colors.d.ts +0 -1
  15. package/dist/console/colors.js +0 -3
  16. package/dist/console/index.d.ts +0 -6
  17. package/dist/console/index.js +2 -13
  18. package/dist/console/logging.d.ts +1 -1
  19. package/dist/console/logging.js +3 -4
  20. package/dist/formats/files/translate.d.ts +2 -2
  21. package/dist/formats/files/translate.js +31 -5
  22. package/dist/fs/config/downloadedVersions.d.ts +10 -3
  23. package/dist/fs/config/downloadedVersions.js +8 -0
  24. package/dist/fs/config/updateVersions.d.ts +2 -1
  25. package/dist/git/branches.d.ts +7 -0
  26. package/dist/git/branches.js +88 -0
  27. package/dist/react/{jsx/utils/jsxParsing → data-_gt}/addGTIdentifierToSyntaxTree.d.ts +1 -2
  28. package/dist/react/{jsx/utils/jsxParsing → data-_gt}/addGTIdentifierToSyntaxTree.js +6 -30
  29. package/dist/react/jsx/evaluateJsx.d.ts +6 -5
  30. package/dist/react/jsx/evaluateJsx.js +4 -32
  31. package/dist/react/jsx/trimJsxStringChildren.d.ts +7 -0
  32. package/dist/react/jsx/trimJsxStringChildren.js +122 -0
  33. package/dist/react/jsx/utils/constants.d.ts +0 -2
  34. package/dist/react/jsx/utils/constants.js +2 -11
  35. package/dist/react/jsx/utils/parseJsx.d.ts +21 -0
  36. package/dist/react/jsx/utils/parseJsx.js +259 -0
  37. package/dist/react/jsx/utils/parseStringFunction.js +141 -4
  38. package/dist/react/parse/createInlineUpdates.js +70 -19
  39. package/dist/types/branch.d.ts +14 -0
  40. package/dist/types/branch.js +1 -0
  41. package/dist/types/data.d.ts +1 -1
  42. package/dist/types/files.d.ts +7 -0
  43. package/dist/types/index.d.ts +7 -0
  44. package/dist/utils/SpinnerManager.d.ts +30 -0
  45. package/dist/utils/SpinnerManager.js +73 -0
  46. package/dist/utils/gitDiff.js +18 -16
  47. package/dist/workflow/BranchStep.d.ts +13 -0
  48. package/dist/workflow/BranchStep.js +131 -0
  49. package/dist/workflow/DownloadStep.d.ts +19 -0
  50. package/dist/workflow/DownloadStep.js +127 -0
  51. package/dist/workflow/EnqueueStep.d.ts +15 -0
  52. package/dist/workflow/EnqueueStep.js +33 -0
  53. package/dist/workflow/PollJobsStep.d.ts +31 -0
  54. package/dist/workflow/PollJobsStep.js +286 -0
  55. package/dist/workflow/SetupStep.d.ts +16 -0
  56. package/dist/workflow/SetupStep.js +72 -0
  57. package/dist/workflow/UploadStep.d.ts +21 -0
  58. package/dist/workflow/UploadStep.js +72 -0
  59. package/dist/workflow/UserEditDiffsStep.d.ts +11 -0
  60. package/dist/workflow/UserEditDiffsStep.js +30 -0
  61. package/dist/workflow/Workflow.d.ts +4 -0
  62. package/dist/workflow/Workflow.js +2 -0
  63. package/dist/workflow/download.d.ts +22 -0
  64. package/dist/workflow/download.js +104 -0
  65. package/dist/workflow/stage.d.ts +14 -0
  66. package/dist/workflow/stage.js +76 -0
  67. package/package.json +3 -4
  68. package/dist/api/checkFileTranslations.d.ts +0 -23
  69. package/dist/api/checkFileTranslations.js +0 -281
  70. package/dist/api/sendFiles.d.ts +0 -17
  71. package/dist/api/sendFiles.js +0 -127
  72. package/dist/api/sendUserEdits.d.ts +0 -19
  73. package/dist/api/sendUserEdits.js +0 -15
  74. package/dist/cli/commands/edits.d.ts +0 -8
  75. package/dist/cli/commands/edits.js +0 -32
  76. package/dist/react/jsx/utils/buildImportMap.d.ts +0 -9
  77. package/dist/react/jsx/utils/buildImportMap.js +0 -30
  78. package/dist/react/jsx/utils/getPathsAndAliases.d.ts +0 -17
  79. package/dist/react/jsx/utils/getPathsAndAliases.js +0 -89
  80. package/dist/react/jsx/utils/jsxParsing/handleChildrenWhitespace.d.ts +0 -6
  81. package/dist/react/jsx/utils/jsxParsing/handleChildrenWhitespace.js +0 -199
  82. package/dist/react/jsx/utils/jsxParsing/multiplication/findMultiplicationNode.d.ts +0 -13
  83. package/dist/react/jsx/utils/jsxParsing/multiplication/findMultiplicationNode.js +0 -42
  84. package/dist/react/jsx/utils/jsxParsing/multiplication/multiplyJsxTree.d.ts +0 -5
  85. package/dist/react/jsx/utils/jsxParsing/multiplication/multiplyJsxTree.js +0 -69
  86. package/dist/react/jsx/utils/jsxParsing/parseJsx.d.ts +0 -60
  87. package/dist/react/jsx/utils/jsxParsing/parseJsx.js +0 -949
  88. package/dist/react/jsx/utils/jsxParsing/parseTProps.d.ts +0 -8
  89. package/dist/react/jsx/utils/jsxParsing/parseTProps.js +0 -47
  90. package/dist/react/jsx/utils/jsxParsing/types.d.ts +0 -48
  91. package/dist/react/jsx/utils/jsxParsing/types.js +0 -34
  92. package/dist/react/jsx/utils/resolveImportPath.d.ts +0 -11
  93. package/dist/react/jsx/utils/resolveImportPath.js +0 -111
@@ -2,7 +2,7 @@ import { logErrorAndExit, logSuccess } from '../../console/logging.js';
2
2
  import { noLocalesError, noDefaultLocaleError, noApiKeyError, noProjectIdError, devApiKeyError, invalidConfigurationError, } from '../../console/index.js';
3
3
  import { aggregateFiles } from '../../formats/files/translate.js';
4
4
  import { aggregateReactTranslations } from '../../translation/stage.js';
5
- import { sendFiles } from '../../api/sendFiles.js';
5
+ import { stageFiles } from '../../workflow/stage.js';
6
6
  import { updateVersions } from '../../fs/config/updateVersions.js';
7
7
  import updateConfig from '../../fs/config/updateConfig.js';
8
8
  import { hashStringSync } from '../../utils/hash.js';
@@ -62,6 +62,8 @@ export async function handleStage(options, settings, library, stage) {
62
62
  content: JSON.stringify(fileData),
63
63
  fileFormat: 'GTJSON',
64
64
  formatMetadata: fileMetadata,
65
+ fileId: TEMPLATE_FILE_ID,
66
+ versionId: hashStringSync(JSON.stringify(fileData)),
65
67
  });
66
68
  }
67
69
  }
@@ -76,19 +78,31 @@ export async function handleStage(options, settings, library, stage) {
76
78
  })
77
79
  .join('\n');
78
80
  logSuccess(`Dry run: No files were sent to General Translation. Found files:\n${fileNames}`);
79
- return undefined;
81
+ return null;
80
82
  }
81
83
  // Send translations to General Translation
82
- let filesTranslationResponse;
84
+ let fileVersionData;
85
+ let jobData;
86
+ let branchData;
83
87
  if (allFiles.length > 0) {
84
- filesTranslationResponse = await sendFiles(allFiles, options, settings);
88
+ const { branchData: branchDataResult, enqueueResult } = await stageFiles(allFiles, options, settings);
89
+ jobData = enqueueResult;
90
+ branchData = branchDataResult;
91
+ fileVersionData = Object.fromEntries(allFiles.map((file) => [
92
+ file.fileId,
93
+ {
94
+ fileName: file.fileName,
95
+ versionId: file.versionId,
96
+ },
97
+ ]));
98
+ // This logic is a little scuffed because stage is async from the API
85
99
  if (stage) {
86
100
  await updateVersions({
87
101
  configDirectory: settings.configDirectory,
88
- versionData: filesTranslationResponse.data,
102
+ versionData: fileVersionData,
89
103
  });
90
104
  }
91
- const templateData = filesTranslationResponse.data[TEMPLATE_FILE_ID];
105
+ const templateData = allFiles.find((file) => file.fileId === TEMPLATE_FILE_ID);
92
106
  if (templateData?.versionId) {
93
107
  await updateConfig({
94
108
  configFilepath: settings.config,
@@ -96,5 +110,9 @@ export async function handleStage(options, settings, library, stage) {
96
110
  });
97
111
  }
98
112
  }
99
- return filesTranslationResponse;
113
+ return {
114
+ fileVersionData,
115
+ jobData,
116
+ branchData,
117
+ };
100
118
  }
@@ -1,6 +1,8 @@
1
- import { SendFilesResult } from '../../api/sendFiles.js';
1
+ import { EnqueueFilesResult } from 'generaltranslation/types';
2
2
  import { TranslateFlags } from '../../types/index.js';
3
3
  import { Settings } from '../../types/index.js';
4
- export declare function handleTranslate(options: TranslateFlags, settings: Settings, filesTranslationResponse: SendFilesResult | undefined): Promise<void>;
4
+ import { FileTranslationData } from '../../workflow/download.js';
5
+ import { BranchData } from '../../types/branch.js';
6
+ export declare function handleTranslate(options: TranslateFlags, settings: Settings, fileVersionData: FileTranslationData | undefined, jobData: EnqueueFilesResult | undefined, branchData: BranchData | undefined): Promise<void>;
5
7
  export declare function handleDownload(options: TranslateFlags, settings: Settings): Promise<void>;
6
8
  export declare function postProcessTranslations(settings: Settings, includeFiles?: Set<string>): Promise<void>;
@@ -1,4 +1,4 @@
1
- import { checkFileTranslations } from '../../api/checkFileTranslations.js';
1
+ import { downloadTranslations, } from '../../workflow/download.js';
2
2
  import { createFileMapping } from '../../formats/files/fileMapping.js';
3
3
  import { logError } from '../../console/logging.js';
4
4
  import { getStagedVersions } from '../../fs/config/updateVersions.js';
@@ -9,13 +9,12 @@ import processAnchorIds from '../../utils/processAnchorIds.js';
9
9
  import { noFilesError, noVersionIdError } from '../../console/index.js';
10
10
  import localizeStaticImports from '../../utils/localizeStaticImports.js';
11
11
  // Downloads translations that were completed
12
- export async function handleTranslate(options, settings, filesTranslationResponse) {
13
- if (filesTranslationResponse) {
12
+ export async function handleTranslate(options, settings, fileVersionData, jobData, branchData) {
13
+ if (fileVersionData) {
14
14
  const { resolvedPaths, placeholderPaths, transformPaths } = settings.files;
15
15
  const fileMapping = createFileMapping(resolvedPaths, placeholderPaths, transformPaths, settings.locales, settings.defaultLocale);
16
- const { data } = filesTranslationResponse;
17
16
  // Check for remaining translations
18
- await checkFileTranslations(data, settings.locales, options.timeout, (sourcePath, locale) => fileMapping[locale]?.[sourcePath] ?? null, settings, options.force, options.forceDownload);
17
+ await downloadTranslations(fileVersionData, jobData, branchData, settings.locales, options.timeout, (sourcePath, locale) => fileMapping[locale]?.[sourcePath] ?? null, settings, options.force, options.forceDownload);
19
18
  }
20
19
  }
21
20
  // Downloads translations that were originally staged
@@ -33,7 +32,7 @@ export async function handleDownload(options, settings) {
33
32
  const fileMapping = createFileMapping(resolvedPaths, placeholderPaths, transformPaths, settings.locales, settings.defaultLocale);
34
33
  const stagedVersionData = await getStagedVersions(settings.configDirectory);
35
34
  // Check for remaining translations
36
- await checkFileTranslations(stagedVersionData, settings.locales, options.timeout, (sourcePath, locale) => fileMapping[locale][sourcePath] ?? null, settings, false, // force is not applicable for downloading staged translations
35
+ await downloadTranslations(stagedVersionData, undefined, undefined, settings.locales, options.timeout, (sourcePath, locale) => fileMapping[locale][sourcePath] ?? null, settings, false, // force is not applicable for downloading staged translations
37
36
  options.forceDownload);
38
37
  }
39
38
  export async function postProcessTranslations(settings, includeFiles) {
package/dist/cli/flags.js CHANGED
@@ -28,7 +28,10 @@ export function attachTranslateFlags(command) {
28
28
  .option('--experimental-localize-static-imports', 'Triggering this will run a script after the cli tool that localizes all static imports in content files. Currently only supported for md and mdx files.', false)
29
29
  .option('--force', 'Force a retranslation, invalidating all existing cached translations if they exist.', false)
30
30
  .option('--force-download', 'Force download and overwrite local files, bypassing gt-lock.json checks.', false)
31
- .option('--experimental-clear-locale-dirs', 'Clear locale directories before downloading new translations', false);
31
+ .option('--experimental-clear-locale-dirs', 'Clear locale directories before downloading new translations', false)
32
+ .option('--branch <branch>', 'Specify a custom branch to use for translations')
33
+ .option('--disable-branch-detection', 'Disable additional branch detection and optimizations and use the manually specified branch', false)
34
+ .option('--enable-branching', 'Enable branching for the project', false); // disabled by default for now
32
35
  return command;
33
36
  }
34
37
  export function attachAdditionalReactTranslateFlags(command) {
@@ -159,6 +159,16 @@ export async function generateSettings(flags, cwd = process.cwd()) {
159
159
  mergedOptions.parsingOptions = mergedOptions.parsingOptions || {};
160
160
  mergedOptions.parsingOptions.conditionNames = mergedOptions.parsingOptions
161
161
  .conditionNames || ['browser', 'module', 'import', 'require', 'default'];
162
+ // Add branch options if not provided
163
+ const branchOptions = mergedOptions.branchOptions || {};
164
+ branchOptions.enabled = flags.enableBranching ?? false;
165
+ branchOptions.currentBranch =
166
+ flags.branch ?? gtConfig.branchOptions?.currentBranch ?? undefined;
167
+ branchOptions.autoDetectBranches = flags.disableBranchDetection
168
+ ? false
169
+ : true;
170
+ branchOptions.remoteName = gtConfig.branchOptions?.remoteName ?? 'origin';
171
+ mergedOptions.branchOptions = branchOptions;
162
172
  // if there's no existing config file, creates one
163
173
  // does not include the API key to avoid exposing it
164
174
  if (!fs.existsSync(mergedOptions.config)) {
@@ -1,6 +1,5 @@
1
1
  export declare function colorizeFilepath(filepath: string): string;
2
2
  export declare function colorizeComponent(component: string): string;
3
- export declare function colorizeFunctionName(functionName: string): string;
4
3
  export declare function colorizeIdString(id: string): string;
5
4
  export declare function colorizeContent(content: string): string;
6
5
  export declare function colorizeLine(line: string): string;
@@ -5,9 +5,6 @@ export function colorizeFilepath(filepath) {
5
5
  export function colorizeComponent(component) {
6
6
  return chalk.yellow(component);
7
7
  }
8
- export function colorizeFunctionName(functionName) {
9
- return chalk.yellow(functionName);
10
- }
11
8
  export function colorizeIdString(id) {
12
9
  return chalk.yellow(id);
13
10
  }
@@ -1,7 +1,5 @@
1
1
  export declare const warnApiKeyInConfigSync: (optionsFilepath: string) => string;
2
2
  export declare const warnVariablePropSync: (file: string, attrName: string, value: string, location?: string) => string;
3
- export declare const warnInvalidReturnSync: (file: string, functionName: string, expression: string, location?: string) => string;
4
- export declare const warnMissingReturnSync: (file: string, functionName: string, location?: string) => string;
5
3
  export declare const warnHasUnwrappedExpressionSync: (file: string, unwrappedExpressions: string[], id?: string, location?: string) => string;
6
4
  export declare const warnNestedTComponent: (file: string, location?: string) => string;
7
5
  export declare const warnNonStaticExpressionSync: (file: string, attrName: string, value: string, location?: string) => string;
@@ -12,10 +10,6 @@ export declare const warnAsyncUseGT: (file: string, location?: string) => string
12
10
  export declare const warnSyncGetGT: (file: string, location?: string) => string;
13
11
  export declare const warnTernarySync: (file: string, location?: string) => string;
14
12
  export declare const withLocation: (file: string, message: string, location?: string) => string;
15
- export declare const warnInvalidStaticChildSync: (file: string, location?: string) => string;
16
- export declare const warnFunctionNotFoundSync: (file: string, functionName: string, location?: string) => string;
17
- export declare const warnDuplicateFunctionDefinitionSync: (file: string, functionName: string, location?: string) => string;
18
- export declare const warnInvalidStaticInitSync: (file: string, functionName: string, location?: string) => string;
19
13
  export declare const noLocalesError = "No locales found! Please provide a list of locales to translate to, or specify them in your gt.config.json file.";
20
14
  export declare const noDefaultLocaleError = "No default locale found! Please provide a default locale, or specify it in your gt.config.json file.";
21
15
  export declare const noFilesError = "Incorrect or missing files configuration! Please make sure your files are configured correctly in your gt.config.json file.";
@@ -1,13 +1,7 @@
1
- import { colorizeFilepath, colorizeComponent, colorizeIdString, colorizeContent, colorizeLine, colorizeFunctionName, } from './colors.js';
2
- const withWillErrorInNextVersion = (message) => `${message} (This will become an error in the next major version of the CLI.)`;
3
- // Static function related errors
4
- const withStaticError = (message) => `<Static> rules violation: ${message}`;
1
+ import { colorizeFilepath, colorizeComponent, colorizeIdString, colorizeContent, colorizeLine, } from './colors.js';
5
2
  // Synchronous wrappers for backward compatibility
6
3
  export const warnApiKeyInConfigSync = (optionsFilepath) => `${colorizeFilepath(optionsFilepath)}: Your API key is exposed! Please remove it from the file and include it as an environment variable.`;
7
4
  export const warnVariablePropSync = (file, attrName, value, location) => withLocation(file, `${colorizeComponent('<T>')} component has dynamic attribute ${colorizeIdString(attrName)} with value: ${colorizeContent(value)}. Change ${colorizeIdString(attrName)} to ensure this content is translated.`, location);
8
- export const warnInvalidReturnSync = (file, functionName, expression, location) => withLocation(file, withStaticError(`Function ${colorizeFunctionName(functionName)} does not return a static expression. ${colorizeFunctionName(functionName)} must return either (1) a static string literal, (2) another static function invocation, (3) static JSX content, or (4) a ternary expression. Instead got:\n${colorizeContent(expression)}`), location);
9
- // TODO: this is temporary until we handle implicit returns
10
- export const warnMissingReturnSync = (file, functionName, location) => withLocation(file, `Function ${colorizeFunctionName(functionName)} is wrapped in ${colorizeComponent('<Static>')} tags but does have an explicit return statement. Static functions must have an explicit return statment.`, location);
11
5
  export const warnHasUnwrappedExpressionSync = (file, unwrappedExpressions, id, location) => withLocation(file, `${colorizeComponent('<T>')} component${id ? ` with id ${colorizeIdString(id)}` : ''} has children that could change at runtime. Use a variable component like ${colorizeComponent('<Var>')} to ensure this content is translated.\n${colorizeContent(unwrappedExpressions.join('\n'))}`, location);
12
6
  export const warnNestedTComponent = (file, location) => withLocation(file, `Found nested <T> component. <T> components cannot be directly nested.`, location);
13
7
  export const warnNonStaticExpressionSync = (file, attrName, value, location) => withLocation(file, `Found non-static expression for attribute ${colorizeIdString(attrName)}: ${colorizeContent(value)}. Change "${colorizeIdString(attrName)}" to ensure this content is translated.`, location);
@@ -18,12 +12,7 @@ export const warnAsyncUseGT = (file, location) => withLocation(file, `Found useG
18
12
  export const warnSyncGetGT = (file, location) => withLocation(file, `Found getGT() in a synchronous function. Use useGT() instead, or make the function async.`, location);
19
13
  export const warnTernarySync = (file, location) => withLocation(file, 'Found ternary expression. A Branch component may be more appropriate here.', location);
20
14
  export const withLocation = (file, message, location) => `${colorizeFilepath(file)}${location ? ` (${colorizeLine(location)})` : ''}: ${message}`;
21
- export const warnInvalidStaticChildSync = (file, location) => withLocation(file, 'Found invalid <Static> invocation. Children must be an expression container with a function invocation. Callee must be a single identifier. (Example: <T> <Static> {getSubject()} </Static> </T>)', location);
22
- export const warnFunctionNotFoundSync = (file, functionName, location) => withLocation(file, `Function ${colorizeFunctionName(functionName)} definition could not be resolved. This might affect translation resolution for this ${colorizeComponent('<T>')} component.`, location);
23
- export const warnDuplicateFunctionDefinitionSync = (file, functionName, location) => withLocation(file, `Function ${colorizeFunctionName(functionName)} is defined multiple times. Only the first definition will be used.`, location);
24
- export const warnInvalidStaticInitSync = (file, functionName, location) => withLocation(file, withStaticError(`The definition for ${colorizeFunctionName(functionName)} could not be resolved. When using arrow syntax to define a static function, the right hand side or the assignment MUST only contain the arrow function itself and no other expressions.
25
- Example: ${colorizeContent(`const ${colorizeFunctionName(functionName)} = () => { ... }`)}
26
- Invalid: ${colorizeContent(`const ${colorizeFunctionName(functionName)} = [() => { ... }][0]`)}`), location);
15
+ const withWillErrorInNextVersion = (message) => `${message} (This will become an error in the next major version of the CLI.)`;
27
16
  // Re-export error messages
28
17
  export const noLocalesError = `No locales found! Please provide a list of locales to translate to, or specify them in your gt.config.json file.`;
29
18
  export const noDefaultLocaleError = `No default locale found! Please provide a default locale, or specify it in your gt.config.json file.`;
@@ -15,7 +15,7 @@ export declare function displayCreatedConfigFile(configFilepath: string): void;
15
15
  export declare function displayUpdatedConfigFile(configFilepath: string): void;
16
16
  export declare function displayUpdatedVersionsFile(versionFilepath: string): void;
17
17
  export declare function createSpinner(indicator?: 'dots' | 'timer'): import("@clack/prompts").SpinnerResult;
18
- export declare function createOraSpinner(indicator?: 'dots' | 'circleHalves'): Promise<import("ora").Ora>;
18
+ export declare function createProgressBar(total: number): import("@clack/prompts").ProgressResult;
19
19
  export declare function promptText({ message, defaultValue, validate, }: {
20
20
  message: string;
21
21
  defaultValue?: string;
@@ -1,4 +1,4 @@
1
- import { log, spinner, intro, outro, text, select, confirm, isCancel, cancel, multiselect, } from '@clack/prompts';
1
+ import { log, spinner, intro, outro, text, select, confirm, isCancel, cancel, multiselect, progress, } from '@clack/prompts';
2
2
  import chalk from 'chalk';
3
3
  import { getCLIVersion } from '../utils/packageJson.js';
4
4
  // Basic logging functions
@@ -81,9 +81,8 @@ export function createSpinner(indicator = 'timer') {
81
81
  return spinner({ indicator });
82
82
  }
83
83
  // Spinner functionality
84
- export async function createOraSpinner(indicator = 'circleHalves') {
85
- const ora = await import('ora');
86
- return ora.default({ spinner: indicator });
84
+ export function createProgressBar(total) {
85
+ return progress({ max: total });
87
86
  }
88
87
  // Input prompts
89
88
  export async function promptText({ message, defaultValue, validate, }) {
@@ -1,4 +1,4 @@
1
1
  import { Settings } from '../../types/index.js';
2
- import { FileToTranslate } from '../../types/data.js';
2
+ import type { FileToUpload } from '../../types/data.js';
3
3
  export declare const SUPPORTED_DATA_FORMATS: string[];
4
- export declare function aggregateFiles(settings: Settings): Promise<FileToTranslate[]>;
4
+ export declare function aggregateFiles(settings: Settings): Promise<FileToUpload[]>;
@@ -4,8 +4,10 @@ import { SUPPORTED_FILE_EXTENSIONS } from './supportedFiles.js';
4
4
  import sanitizeFileContent from '../../utils/sanitizeFileContent.js';
5
5
  import { parseJson } from '../json/parseJson.js';
6
6
  import parseYaml from '../yaml/parseYaml.js';
7
+ import YAML from 'yaml';
7
8
  import { determineLibrary } from '../../fs/determineFramework.js';
8
9
  import { isValidMdx } from '../../utils/validateMdx.js';
10
+ import { hashStringSync } from '../../utils/hash.js';
9
11
  export const SUPPORTED_DATA_FORMATS = ['JSX', 'ICU', 'I18NEXT'];
10
12
  export async function aggregateFiles(settings) {
11
13
  // Aggregate all files to translate
@@ -39,8 +41,18 @@ export async function aggregateFiles(settings) {
39
41
  .map((filePath) => {
40
42
  const content = readFile(filePath);
41
43
  const relativePath = getRelative(filePath);
44
+ // Pre-validate JSON parseability
45
+ try {
46
+ JSON.parse(content);
47
+ }
48
+ catch (e) {
49
+ logWarning(`Skipping ${relativePath}: JSON file is not parsable`);
50
+ return null;
51
+ }
42
52
  const parsedJson = parseJson(content, filePath, settings.options || {}, settings.defaultLocale);
43
53
  return {
54
+ fileId: hashStringSync(relativePath),
55
+ versionId: hashStringSync(parsedJson),
44
56
  content: parsedJson,
45
57
  fileName: relativePath,
46
58
  fileFormat: 'JSON',
@@ -48,13 +60,15 @@ export async function aggregateFiles(settings) {
48
60
  };
49
61
  })
50
62
  .filter((file) => {
51
- if (!file || typeof file.content !== 'string' || !file.content.trim()) {
52
- logWarning(`Skipping ${file?.fileName ?? 'unknown'}: JSON file is empty`);
63
+ if (!file)
64
+ return false;
65
+ if (typeof file.content !== 'string' || !file.content.trim()) {
66
+ logWarning(`Skipping ${file.fileName}: JSON file is empty`);
53
67
  return false;
54
68
  }
55
69
  return true;
56
70
  });
57
- allFiles.push(...jsonFiles);
71
+ allFiles.push(...jsonFiles.filter((file) => file !== null));
58
72
  }
59
73
  // Process YAML files
60
74
  if (filePaths.yaml) {
@@ -62,11 +76,21 @@ export async function aggregateFiles(settings) {
62
76
  .map((filePath) => {
63
77
  const content = readFile(filePath);
64
78
  const relativePath = getRelative(filePath);
79
+ // Pre-validate YAML parseability
80
+ try {
81
+ YAML.parse(content);
82
+ }
83
+ catch (e) {
84
+ logWarning(`Skipping ${relativePath}: YAML file is not parsable`);
85
+ return null;
86
+ }
65
87
  const { content: parsedYaml, fileFormat } = parseYaml(content, filePath, settings.options || {});
66
88
  return {
67
89
  content: parsedYaml,
68
90
  fileName: relativePath,
69
91
  fileFormat,
92
+ fileId: hashStringSync(relativePath),
93
+ versionId: hashStringSync(parsedYaml),
70
94
  };
71
95
  })
72
96
  .filter((file) => {
@@ -76,7 +100,7 @@ export async function aggregateFiles(settings) {
76
100
  }
77
101
  return true;
78
102
  });
79
- allFiles.push(...yamlFiles);
103
+ allFiles.push(...yamlFiles.filter((file) => file !== null));
80
104
  }
81
105
  for (const fileType of SUPPORTED_FILE_EXTENSIONS) {
82
106
  if (fileType === 'json' || fileType === 'yaml')
@@ -98,6 +122,8 @@ export async function aggregateFiles(settings) {
98
122
  content: sanitizedContent,
99
123
  fileName: relativePath,
100
124
  fileFormat: fileType.toUpperCase(),
125
+ fileId: hashStringSync(relativePath),
126
+ versionId: hashStringSync(content),
101
127
  };
102
128
  })
103
129
  .filter((file) => {
@@ -109,7 +135,7 @@ export async function aggregateFiles(settings) {
109
135
  }
110
136
  return true;
111
137
  });
112
- allFiles.push(...files);
138
+ allFiles.push(...files.filter((file) => file !== null));
113
139
  }
114
140
  }
115
141
  if (allFiles.length === 0 && !settings.publish) {
@@ -1,12 +1,19 @@
1
1
  export type DownloadedVersionEntry = {
2
- versionId: string;
3
- fileId?: string;
4
2
  fileName?: string;
5
3
  updatedAt?: string;
6
4
  };
7
5
  export type DownloadedVersions = {
8
6
  version: number;
9
- entries: Record<string, DownloadedVersionEntry>;
7
+ entries: {
8
+ [branchId: string]: {
9
+ [fileId: string]: {
10
+ [versionId: string]: {
11
+ [locale: string]: DownloadedVersionEntry;
12
+ };
13
+ };
14
+ };
15
+ };
10
16
  };
11
17
  export declare function getDownloadedVersions(configDirectory: string): DownloadedVersions;
12
18
  export declare function saveDownloadedVersions(configDirectory: string, lock: DownloadedVersions): void;
19
+ export declare function ensureNestedObject(obj: any, path: string[]): any;
@@ -40,3 +40,11 @@ export function saveDownloadedVersions(configDirectory, lock) {
40
40
  logError(`An error occurred while updating ${GT_LOCK_FILE}: ${error}`);
41
41
  }
42
42
  }
43
+ export function ensureNestedObject(obj, path) {
44
+ return path.reduce((current, key, index) => {
45
+ if (index === path.length - 1)
46
+ return current;
47
+ current[key] = current[key] || {};
48
+ return current[key];
49
+ }, obj);
50
+ }
@@ -1,4 +1,5 @@
1
- type StagedVersionData = Record<string, {
1
+ type StagedVersionData = Record<string, // fileId
2
+ {
2
3
  fileName: string;
3
4
  versionId: string;
4
5
  }>;
@@ -0,0 +1,7 @@
1
+ export declare function getCurrentBranch(remoteName: string): Promise<{
2
+ currentBranchName: string;
3
+ defaultBranch: boolean;
4
+ defaultBranchName: string;
5
+ } | null>;
6
+ export declare function getIncomingBranches(remoteName: string): Promise<string[]>;
7
+ export declare function getCheckedOutBranches(remoteName: string): Promise<string[]>;
@@ -0,0 +1,88 @@
1
+ import { execFile } from 'child_process';
2
+ import { promisify } from 'util';
3
+ const execAsync = promisify(execFile);
4
+ const MAX_BRANCHES = 5;
5
+ export async function getCurrentBranch(remoteName) {
6
+ try {
7
+ const { stdout } = await execAsync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
8
+ encoding: 'utf8',
9
+ windowsHide: true,
10
+ });
11
+ const currentBranchName = stdout.trim();
12
+ // Get the default branch (usually main or master)
13
+ const { stdout: defaultBranchRef } = await execAsync('git', ['symbolic-ref', `refs/remotes/${remoteName}/HEAD`], { encoding: 'utf8', windowsHide: true });
14
+ const defaultBranchName = defaultBranchRef
15
+ .trim()
16
+ .replace(`refs/remotes/${remoteName}/`, '');
17
+ const defaultBranch = currentBranchName === defaultBranchName;
18
+ return { currentBranchName, defaultBranch, defaultBranchName };
19
+ }
20
+ catch {
21
+ return null;
22
+ }
23
+ }
24
+ export async function getIncomingBranches(remoteName) {
25
+ try {
26
+ // Get merge commits into the current branch
27
+ const { stdout } = await execAsync('git', [
28
+ 'log',
29
+ '--merges',
30
+ '--first-parent',
31
+ '--pretty=format:%s',
32
+ `-${MAX_BRANCHES}`,
33
+ ], {
34
+ encoding: 'utf8',
35
+ windowsHide: true,
36
+ });
37
+ if (!stdout.trim()) {
38
+ return [];
39
+ }
40
+ const branches = [];
41
+ const lines = stdout.trim().split('\n');
42
+ for (const line of lines) {
43
+ // Parse merge commit messages:
44
+ // - "Merge branch 'feature-name'" or "Merge branch 'feature-name' into main"
45
+ // - "Merge pull request #123 from user/branch-name"
46
+ const branchMatch = line.match(/Merge branch '([^']+)'/);
47
+ const prMatch = line.match(/Merge pull request #\d+ from [^/]+\/(.+)/);
48
+ if (branchMatch && branchMatch[1]) {
49
+ branches.push(branchMatch[1]);
50
+ }
51
+ else if (prMatch && prMatch[1]) {
52
+ branches.push(prMatch[1]);
53
+ }
54
+ }
55
+ return branches.slice(0, MAX_BRANCHES);
56
+ }
57
+ catch {
58
+ // If log fails or no merges found, return empty array
59
+ return [];
60
+ }
61
+ }
62
+ export async function getCheckedOutBranches(remoteName) {
63
+ try {
64
+ // Get current branch
65
+ const currentBranchResult = await getCurrentBranch(remoteName);
66
+ if (!currentBranchResult) {
67
+ return [];
68
+ }
69
+ // If we're already on the default branch, return empty
70
+ if (currentBranchResult.defaultBranch) {
71
+ return [];
72
+ }
73
+ // Check if there's a merge-base (common ancestor) between default branch and current
74
+ // This means the branch was at some point checked out from the default branch
75
+ try {
76
+ await execAsync('git', ['merge-base', currentBranchResult.defaultBranchName, 'HEAD'], { encoding: 'utf8', windowsHide: true });
77
+ // If merge-base exists, the branch shares history with default branch
78
+ return [currentBranchResult.defaultBranchName];
79
+ }
80
+ catch {
81
+ // No common ancestor found
82
+ return [];
83
+ }
84
+ }
85
+ catch {
86
+ return [];
87
+ }
88
+ }
@@ -1,9 +1,8 @@
1
1
  import { JsxChildren } from 'generaltranslation/types';
2
- import { MultipliedTreeNode } from './types.js';
3
2
  /**
4
3
  * Add GT Identifier and minify the tree (recreates addGTIdentifier and writeChildrenAsObjects)
5
4
  * @param tree - The tree to add GT identifiers to
6
5
  * @param startingIndex - The starting index for GT IDs
7
6
  * @returns The tree with GT identifiers added
8
7
  */
9
- export default function addGTIdentifierToSyntaxTree(tree: MultipliedTreeNode, startingIndex?: number): JsxChildren;
8
+ export default function addGTIdentifierToSyntaxTree(tree: any, startingIndex?: number): JsxChildren;
@@ -1,5 +1,5 @@
1
1
  import { HTML_CONTENT_PROPS, } from 'generaltranslation/types';
2
- import { defaultVariableNames, getVariableName, minifyVariableType, } from '../../../utils/getVariableName.js';
2
+ import { defaultVariableNames, getVariableName, minifyVariableType, } from '../utils/getVariableName.js';
3
3
  import { isAcceptedPluralForm } from 'generaltranslation/internal';
4
4
  /**
5
5
  * Construct the data-_gt prop
@@ -55,10 +55,6 @@ function constructGTProp(type, props, id) {
55
55
  * @returns The tree with GT identifiers added
56
56
  */
57
57
  export default function addGTIdentifierToSyntaxTree(tree, startingIndex = 0) {
58
- // Edge case: boolean or null, just return the tree
59
- if (typeof tree === 'boolean' || tree === null) {
60
- return tree;
61
- }
62
58
  // Object to keep track of the current index for GT IDs
63
59
  const indexObject = { index: startingIndex };
64
60
  /**
@@ -87,23 +83,12 @@ export default function addGTIdentifierToSyntaxTree(tree, startingIndex = 0) {
87
83
  };
88
84
  }
89
85
  // Construct the data-_gt prop
90
- const generaltranslation = constructGTProp(type, (props || {}), indexObject.index);
91
- // Save current index and recurse
92
- const currentIndex = indexObject.index;
93
- const children = handleChildren(props?.children === undefined ? null : props?.children);
94
- // Enforce boolean rendering behavior
95
- // <T>{true}</T> -> true <- this is a boolean value, not a string
96
- // <T>{false}</T> -> nothing
97
- let includeChildren = true;
98
- if (typeof children === 'boolean') {
99
- // So, technically JsxChildren do include boolean values, but the type is incorrect
100
- includeChildren = children !== false;
101
- }
86
+ const generaltranslation = constructGTProp(type, props, indexObject.index);
102
87
  // Return the result
103
88
  return {
104
- t: type || `C${currentIndex}`,
105
- i: currentIndex,
106
- ...(includeChildren && { c: children }),
89
+ t: type || `C${indexObject.index}`,
90
+ i: indexObject.index,
91
+ c: handleChildren(props.children),
107
92
  ...(generaltranslation && { d: generaltranslation }),
108
93
  };
109
94
  }
@@ -119,16 +104,7 @@ export default function addGTIdentifierToSyntaxTree(tree, startingIndex = 0) {
119
104
  */
120
105
  const handleChildren = (children) => {
121
106
  return Array.isArray(children)
122
- ? children.map(handleSingleChild).filter((child) => {
123
- // Enforce boolean rendering behavior
124
- // <T>{true}{false}{null}</T> -> []
125
- if (typeof child === 'boolean' ||
126
- child === null ||
127
- child === undefined) {
128
- return false;
129
- }
130
- return true;
131
- })
107
+ ? children.map(handleSingleChild)
132
108
  : handleSingleChild(children);
133
109
  };
134
110
  return handleChildren(tree);
@@ -5,14 +5,15 @@ import * as t from '@babel/types';
5
5
  * @returns Whether the node is meaningful
6
6
  */
7
7
  export declare function isMeaningful(node: t.Node): boolean;
8
- export declare function isStaticExpression(expr: t.Expression | t.JSXEmptyExpression, jsxStatic?: false): {
8
+ /**
9
+ * Checks if an expression is static (does not contain any variables which could change at runtime).
10
+ * @param expr - The expression to check
11
+ * @returns An object containing the result of the static check
12
+ */
13
+ export declare function isStaticExpression(expr: t.Expression | t.JSXEmptyExpression): {
9
14
  isStatic: boolean;
10
15
  value?: string;
11
16
  };
12
- export declare function isStaticExpression(expr: t.Expression | t.JSXEmptyExpression, jsxStatic?: true): {
13
- isStatic: boolean;
14
- value?: string | boolean | null;
15
- };
16
17
  export declare function isStaticValue(expr: t.Expression | t.JSXEmptyExpression): boolean;
17
18
  export declare function isValidIcu(string: string): {
18
19
  isValid: boolean;