gtx-cli 2.5.0-alpha.0 → 2.5.0-alpha.1
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 +13 -0
- package/dist/api/collectUserEditDiffs.d.ts +2 -7
- package/dist/api/collectUserEditDiffs.js +33 -78
- package/dist/api/downloadFileBatch.d.ts +11 -10
- package/dist/api/downloadFileBatch.js +120 -127
- package/dist/api/saveLocalEdits.js +18 -15
- package/dist/cli/base.js +1 -1
- package/dist/cli/commands/stage.d.ts +8 -2
- package/dist/cli/commands/stage.js +25 -7
- package/dist/cli/commands/translate.d.ts +4 -2
- package/dist/cli/commands/translate.js +5 -6
- package/dist/cli/flags.js +4 -1
- package/dist/config/generateSettings.js +10 -0
- package/dist/console/colors.d.ts +0 -1
- package/dist/console/colors.js +0 -3
- package/dist/console/index.d.ts +0 -6
- package/dist/console/index.js +2 -13
- package/dist/console/logging.d.ts +1 -1
- package/dist/console/logging.js +3 -4
- package/dist/formats/files/translate.d.ts +2 -2
- package/dist/formats/files/translate.js +31 -5
- package/dist/fs/config/downloadedVersions.d.ts +10 -3
- package/dist/fs/config/downloadedVersions.js +8 -0
- package/dist/fs/config/updateVersions.d.ts +2 -1
- package/dist/git/branches.d.ts +7 -0
- package/dist/git/branches.js +88 -0
- package/dist/react/{jsx/utils/jsxParsing → data-_gt}/addGTIdentifierToSyntaxTree.d.ts +1 -2
- package/dist/react/{jsx/utils/jsxParsing → data-_gt}/addGTIdentifierToSyntaxTree.js +6 -30
- package/dist/react/jsx/evaluateJsx.d.ts +6 -5
- package/dist/react/jsx/evaluateJsx.js +4 -32
- package/dist/react/jsx/trimJsxStringChildren.d.ts +7 -0
- package/dist/react/jsx/trimJsxStringChildren.js +122 -0
- package/dist/react/jsx/utils/constants.d.ts +0 -2
- package/dist/react/jsx/utils/constants.js +2 -11
- package/dist/react/jsx/utils/parseJsx.d.ts +21 -0
- package/dist/react/jsx/utils/parseJsx.js +259 -0
- package/dist/react/jsx/utils/parseStringFunction.js +141 -4
- package/dist/react/parse/createInlineUpdates.js +70 -19
- package/dist/types/branch.d.ts +14 -0
- package/dist/types/branch.js +1 -0
- package/dist/types/data.d.ts +1 -1
- package/dist/types/files.d.ts +7 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/utils/SpinnerManager.d.ts +30 -0
- package/dist/utils/SpinnerManager.js +73 -0
- package/dist/utils/gitDiff.js +18 -16
- package/dist/workflow/BranchStep.d.ts +13 -0
- package/dist/workflow/BranchStep.js +131 -0
- package/dist/workflow/DownloadStep.d.ts +19 -0
- package/dist/workflow/DownloadStep.js +127 -0
- package/dist/workflow/EnqueueStep.d.ts +15 -0
- package/dist/workflow/EnqueueStep.js +33 -0
- package/dist/workflow/PollJobsStep.d.ts +31 -0
- package/dist/workflow/PollJobsStep.js +284 -0
- package/dist/workflow/SetupStep.d.ts +16 -0
- package/dist/workflow/SetupStep.js +71 -0
- package/dist/workflow/UploadStep.d.ts +21 -0
- package/dist/workflow/UploadStep.js +72 -0
- package/dist/workflow/UserEditDiffsStep.d.ts +11 -0
- package/dist/workflow/UserEditDiffsStep.js +30 -0
- package/dist/workflow/Workflow.d.ts +4 -0
- package/dist/workflow/Workflow.js +2 -0
- package/dist/workflow/download.d.ts +22 -0
- package/dist/workflow/download.js +104 -0
- package/dist/workflow/stage.d.ts +14 -0
- package/dist/workflow/stage.js +76 -0
- package/package.json +4 -5
- package/dist/api/checkFileTranslations.d.ts +0 -23
- package/dist/api/checkFileTranslations.js +0 -281
- package/dist/api/sendFiles.d.ts +0 -17
- package/dist/api/sendFiles.js +0 -127
- package/dist/api/sendUserEdits.d.ts +0 -19
- package/dist/api/sendUserEdits.js +0 -15
- package/dist/cli/commands/edits.d.ts +0 -8
- package/dist/cli/commands/edits.js +0 -32
- package/dist/react/jsx/utils/buildImportMap.d.ts +0 -9
- package/dist/react/jsx/utils/buildImportMap.js +0 -30
- package/dist/react/jsx/utils/getPathsAndAliases.d.ts +0 -17
- package/dist/react/jsx/utils/getPathsAndAliases.js +0 -89
- package/dist/react/jsx/utils/jsxParsing/handleChildrenWhitespace.d.ts +0 -6
- package/dist/react/jsx/utils/jsxParsing/handleChildrenWhitespace.js +0 -199
- package/dist/react/jsx/utils/jsxParsing/multiplication/findMultiplicationNode.d.ts +0 -13
- package/dist/react/jsx/utils/jsxParsing/multiplication/findMultiplicationNode.js +0 -42
- package/dist/react/jsx/utils/jsxParsing/multiplication/multiplyJsxTree.d.ts +0 -5
- package/dist/react/jsx/utils/jsxParsing/multiplication/multiplyJsxTree.js +0 -69
- package/dist/react/jsx/utils/jsxParsing/parseJsx.d.ts +0 -60
- package/dist/react/jsx/utils/jsxParsing/parseJsx.js +0 -949
- package/dist/react/jsx/utils/jsxParsing/parseTProps.d.ts +0 -8
- package/dist/react/jsx/utils/jsxParsing/parseTProps.js +0 -47
- package/dist/react/jsx/utils/jsxParsing/types.d.ts +0 -48
- package/dist/react/jsx/utils/jsxParsing/types.js +0 -34
- package/dist/react/jsx/utils/resolveImportPath.d.ts +0 -11
- package/dist/react/jsx/utils/resolveImportPath.js +0 -111
|
@@ -8,9 +8,12 @@ import traverseModule from '@babel/traverse';
|
|
|
8
8
|
const generate = generateModule.default || generateModule;
|
|
9
9
|
const traverse = traverseModule.default || traverseModule;
|
|
10
10
|
import fs from 'node:fs';
|
|
11
|
+
import path from 'node:path';
|
|
11
12
|
import { parse } from '@babel/parser';
|
|
12
|
-
import {
|
|
13
|
-
import
|
|
13
|
+
import { createMatchPath, loadConfig } from 'tsconfig-paths';
|
|
14
|
+
import resolve from 'resolve';
|
|
15
|
+
import enhancedResolve from 'enhanced-resolve';
|
|
16
|
+
const { ResolverFactory } = enhancedResolve;
|
|
14
17
|
/**
|
|
15
18
|
* Cache for resolved import paths to avoid redundant I/O operations.
|
|
16
19
|
* Key: `${currentFile}::${importPath}`
|
|
@@ -109,6 +112,35 @@ function extractParameterName(param) {
|
|
|
109
112
|
}
|
|
110
113
|
return null;
|
|
111
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* Builds a map of imported function names to their import paths from a given program path.
|
|
117
|
+
* Handles both named imports and default imports.
|
|
118
|
+
*
|
|
119
|
+
* Example: import { getInfo } from './constants' -> Map { 'getInfo' => './constants' }
|
|
120
|
+
* Example: import utils from './utils' -> Map { 'utils' => './utils' }
|
|
121
|
+
*/
|
|
122
|
+
function buildImportMap(programPath) {
|
|
123
|
+
const importMap = new Map();
|
|
124
|
+
programPath.traverse({
|
|
125
|
+
ImportDeclaration(importPath) {
|
|
126
|
+
if (t.isStringLiteral(importPath.node.source)) {
|
|
127
|
+
const importSource = importPath.node.source.value;
|
|
128
|
+
importPath.node.specifiers.forEach((spec) => {
|
|
129
|
+
if (t.isImportSpecifier(spec) &&
|
|
130
|
+
t.isIdentifier(spec.imported) &&
|
|
131
|
+
t.isIdentifier(spec.local)) {
|
|
132
|
+
importMap.set(spec.local.name, importSource);
|
|
133
|
+
}
|
|
134
|
+
else if (t.isImportDefaultSpecifier(spec) &&
|
|
135
|
+
t.isIdentifier(spec.local)) {
|
|
136
|
+
importMap.set(spec.local.name, importSource);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
return importMap;
|
|
143
|
+
}
|
|
112
144
|
/**
|
|
113
145
|
* Recursively resolves variable assignments to find all aliases of a translation callback parameter.
|
|
114
146
|
* Handles cases like: const t = translate; const a = translate; const b = a; const c = b;
|
|
@@ -180,7 +212,7 @@ function handleFunctionCall(tPath, updates, errors, warnings, file, importMap, i
|
|
|
180
212
|
// If not found locally, check if it's an imported function
|
|
181
213
|
else if (importMap.has(callee.name)) {
|
|
182
214
|
const importPath = importMap.get(callee.name);
|
|
183
|
-
const resolvedPath = resolveImportPath(file, importPath, parsingOptions
|
|
215
|
+
const resolvedPath = resolveImportPath(file, importPath, parsingOptions);
|
|
184
216
|
if (resolvedPath) {
|
|
185
217
|
processFunctionInFile(resolvedPath, callee.name, argIndex, updates, errors, warnings, ignoreAdditionalData, ignoreDynamicContent, ignoreInvalidIcu, parsingOptions);
|
|
186
218
|
}
|
|
@@ -231,6 +263,111 @@ function findFunctionParameterUsage(functionPath, parameterName, updates, errors
|
|
|
231
263
|
});
|
|
232
264
|
}
|
|
233
265
|
}
|
|
266
|
+
/**
|
|
267
|
+
* Resolves import paths to absolute file paths using battle-tested libraries.
|
|
268
|
+
* Handles relative paths, TypeScript paths, and node module resolution.
|
|
269
|
+
*
|
|
270
|
+
* Examples:
|
|
271
|
+
* - './constants' -> '/full/path/to/constants.ts'
|
|
272
|
+
* - '@/components/ui/button' -> '/full/path/to/src/components/ui/button.tsx'
|
|
273
|
+
* - '@shared/utils' -> '/full/path/to/packages/utils/index.ts'
|
|
274
|
+
*/
|
|
275
|
+
function resolveImportPath(currentFile, importPath, parsingOptions) {
|
|
276
|
+
// Check cache first
|
|
277
|
+
const cacheKey = `${currentFile}::${importPath}`;
|
|
278
|
+
if (resolveImportPathCache.has(cacheKey)) {
|
|
279
|
+
return resolveImportPathCache.get(cacheKey);
|
|
280
|
+
}
|
|
281
|
+
const basedir = path.dirname(currentFile);
|
|
282
|
+
const extensions = ['.tsx', '.ts', '.jsx', '.js'];
|
|
283
|
+
const mainFields = ['module', 'main'];
|
|
284
|
+
let result = null;
|
|
285
|
+
// 1. Try tsconfig-paths resolution first (handles TypeScript path mapping)
|
|
286
|
+
const tsConfigResult = loadConfig(basedir);
|
|
287
|
+
if (tsConfigResult.resultType === 'success') {
|
|
288
|
+
const matchPath = createMatchPath(tsConfigResult.absoluteBaseUrl, tsConfigResult.paths, mainFields);
|
|
289
|
+
// First try without any extension
|
|
290
|
+
let tsResolved = matchPath(importPath);
|
|
291
|
+
if (tsResolved && fs.existsSync(tsResolved)) {
|
|
292
|
+
result = tsResolved;
|
|
293
|
+
resolveImportPathCache.set(cacheKey, result);
|
|
294
|
+
return result;
|
|
295
|
+
}
|
|
296
|
+
// Then try with each extension
|
|
297
|
+
for (const ext of extensions) {
|
|
298
|
+
tsResolved = matchPath(importPath + ext);
|
|
299
|
+
if (tsResolved && fs.existsSync(tsResolved)) {
|
|
300
|
+
result = tsResolved;
|
|
301
|
+
resolveImportPathCache.set(cacheKey, result);
|
|
302
|
+
return result;
|
|
303
|
+
}
|
|
304
|
+
// Also try the resolved path with extension
|
|
305
|
+
tsResolved = matchPath(importPath);
|
|
306
|
+
if (tsResolved) {
|
|
307
|
+
const resolvedWithExt = tsResolved + ext;
|
|
308
|
+
if (fs.existsSync(resolvedWithExt)) {
|
|
309
|
+
result = resolvedWithExt;
|
|
310
|
+
resolveImportPathCache.set(cacheKey, result);
|
|
311
|
+
return result;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
// 2. Try enhanced-resolve (handles package.json exports field and modern resolution)
|
|
317
|
+
try {
|
|
318
|
+
const resolver = ResolverFactory.createResolver({
|
|
319
|
+
useSyncFileSystemCalls: true,
|
|
320
|
+
fileSystem: fs,
|
|
321
|
+
extensions,
|
|
322
|
+
// Include 'development' condition to resolve to source files in monorepos
|
|
323
|
+
conditionNames: parsingOptions.conditionNames, // defaults to ['browser', 'module', 'import', 'require', 'default']. See generateSettings.ts for more details
|
|
324
|
+
exportsFields: ['exports'],
|
|
325
|
+
mainFields,
|
|
326
|
+
});
|
|
327
|
+
const resolved = resolver.resolveSync({}, basedir, importPath);
|
|
328
|
+
if (resolved) {
|
|
329
|
+
result = resolved;
|
|
330
|
+
resolveImportPathCache.set(cacheKey, result);
|
|
331
|
+
return result;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
catch {
|
|
335
|
+
// Fall through to next resolution strategy
|
|
336
|
+
}
|
|
337
|
+
// 3. Fallback to Node.js resolution (handles relative paths and node_modules)
|
|
338
|
+
try {
|
|
339
|
+
result = resolve.sync(importPath, { basedir, extensions });
|
|
340
|
+
resolveImportPathCache.set(cacheKey, result);
|
|
341
|
+
return result;
|
|
342
|
+
}
|
|
343
|
+
catch {
|
|
344
|
+
// If resolution fails, try to manually replace .js/.jsx with .ts/.tsx for source files
|
|
345
|
+
if (importPath.endsWith('.js')) {
|
|
346
|
+
const tsPath = importPath.replace(/\.js$/, '.ts');
|
|
347
|
+
try {
|
|
348
|
+
result = resolve.sync(tsPath, { basedir, extensions });
|
|
349
|
+
resolveImportPathCache.set(cacheKey, result);
|
|
350
|
+
return result;
|
|
351
|
+
}
|
|
352
|
+
catch {
|
|
353
|
+
// Continue to return null
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
else if (importPath.endsWith('.jsx')) {
|
|
357
|
+
const tsxPath = importPath.replace(/\.jsx$/, '.tsx');
|
|
358
|
+
try {
|
|
359
|
+
result = resolve.sync(tsxPath, { basedir, extensions });
|
|
360
|
+
resolveImportPathCache.set(cacheKey, result);
|
|
361
|
+
return result;
|
|
362
|
+
}
|
|
363
|
+
catch {
|
|
364
|
+
// Continue to return null
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
resolveImportPathCache.set(cacheKey, null);
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
234
371
|
/**
|
|
235
372
|
* Searches for a specific user-defined function in a file and analyzes how a translation callback
|
|
236
373
|
* parameter (at argIndex position) is used within that function.
|
|
@@ -309,7 +446,7 @@ function processFunctionInFile(filePath, functionName, argIndex, updates, errors
|
|
|
309
446
|
// If function not found, follow re-exports
|
|
310
447
|
if (!found && reExports.length > 0) {
|
|
311
448
|
for (const reExportPath of reExports) {
|
|
312
|
-
const resolvedPath = resolveImportPath(filePath, reExportPath, parsingOptions
|
|
449
|
+
const resolvedPath = resolveImportPath(filePath, reExportPath, parsingOptions);
|
|
313
450
|
if (resolvedPath) {
|
|
314
451
|
processFunctionInFile(resolvedPath, functionName, argIndex, updates, errors, warnings, ignoreAdditionalData, ignoreDynamicContent, ignoreInvalidIcu, parsingOptions, visited);
|
|
315
452
|
}
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import { parse } from '@babel/parser';
|
|
3
|
+
import traverseModule from '@babel/traverse';
|
|
4
|
+
// Handle CommonJS/ESM interop
|
|
5
|
+
const traverse = traverseModule.default || traverseModule;
|
|
3
6
|
import { hashSource } from 'generaltranslation/id';
|
|
4
|
-
import {
|
|
7
|
+
import { parseJSXElement } from '../jsx/utils/parseJsx.js';
|
|
5
8
|
import { parseStrings } from '../jsx/utils/parseStringFunction.js';
|
|
9
|
+
import { extractImportName } from '../jsx/utils/parseAst.js';
|
|
6
10
|
import { logError } from '../../console/logging.js';
|
|
11
|
+
import { GT_TRANSLATION_FUNCS, INLINE_TRANSLATION_HOOK, INLINE_TRANSLATION_HOOK_ASYNC, INLINE_MESSAGE_HOOK, INLINE_MESSAGE_HOOK_ASYNC, MSG_TRANSLATION_HOOK, } from '../jsx/utils/constants.js';
|
|
7
12
|
import { matchFiles } from '../../fs/matchFiles.js';
|
|
8
13
|
import { DEFAULT_SRC_PATTERNS } from '../../config/generateSettings.js';
|
|
9
|
-
import { getPathsAndAliases } from '../jsx/utils/getPathsAndAliases.js';
|
|
10
14
|
export async function createInlineUpdates(pkg, validate, filePatterns, parsingOptions) {
|
|
11
15
|
const updates = [];
|
|
12
16
|
const errors = [];
|
|
@@ -26,28 +30,75 @@ export async function createInlineUpdates(pkg, validate, filePatterns, parsingOp
|
|
|
26
30
|
logError(`Error parsing file ${file}: ${error}`);
|
|
27
31
|
continue;
|
|
28
32
|
}
|
|
33
|
+
const importAliases = {};
|
|
29
34
|
// First pass: collect imports and process translation functions
|
|
30
|
-
const
|
|
35
|
+
const translationPaths = [];
|
|
36
|
+
traverse(ast, {
|
|
37
|
+
ImportDeclaration(path) {
|
|
38
|
+
if (path.node.source.value.startsWith(pkg)) {
|
|
39
|
+
const importName = extractImportName(path.node, pkg, GT_TRANSLATION_FUNCS);
|
|
40
|
+
for (const name of importName) {
|
|
41
|
+
if (name.original === INLINE_TRANSLATION_HOOK ||
|
|
42
|
+
name.original === INLINE_TRANSLATION_HOOK_ASYNC ||
|
|
43
|
+
name.original === INLINE_MESSAGE_HOOK ||
|
|
44
|
+
name.original === INLINE_MESSAGE_HOOK_ASYNC ||
|
|
45
|
+
name.original === MSG_TRANSLATION_HOOK) {
|
|
46
|
+
translationPaths.push({
|
|
47
|
+
localName: name.local,
|
|
48
|
+
path,
|
|
49
|
+
originalName: name.original,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
importAliases[name.local] = name.original;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
VariableDeclarator(path) {
|
|
59
|
+
// Check if the init is a require call
|
|
60
|
+
if (path.node.init?.type === 'CallExpression' &&
|
|
61
|
+
path.node.init.callee.type === 'Identifier' &&
|
|
62
|
+
path.node.init.callee.name === 'require') {
|
|
63
|
+
// Check if it's requiring our package
|
|
64
|
+
const args = path.node.init.arguments;
|
|
65
|
+
if (args.length === 1 &&
|
|
66
|
+
args[0].type === 'StringLiteral' &&
|
|
67
|
+
args[0].value.startsWith(pkg)) {
|
|
68
|
+
const parentPath = path.parentPath;
|
|
69
|
+
if (parentPath.isVariableDeclaration()) {
|
|
70
|
+
const importName = extractImportName(parentPath.node, pkg, GT_TRANSLATION_FUNCS);
|
|
71
|
+
for (const name of importName) {
|
|
72
|
+
if (name.original === INLINE_TRANSLATION_HOOK ||
|
|
73
|
+
name.original === INLINE_TRANSLATION_HOOK_ASYNC ||
|
|
74
|
+
name.original === INLINE_MESSAGE_HOOK ||
|
|
75
|
+
name.original === INLINE_MESSAGE_HOOK_ASYNC ||
|
|
76
|
+
name.original === MSG_TRANSLATION_HOOK) {
|
|
77
|
+
translationPaths.push({
|
|
78
|
+
localName: name.local,
|
|
79
|
+
path: parentPath,
|
|
80
|
+
originalName: name.original,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
importAliases[name.local] = name.original;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
});
|
|
31
92
|
// Process translation functions asynchronously
|
|
32
|
-
for (const { localName: name, originalName, path
|
|
93
|
+
for (const { localName: name, originalName, path } of translationPaths) {
|
|
33
94
|
parseStrings(name, originalName, path, updates, errors, warnings, file, parsingOptions);
|
|
34
95
|
}
|
|
35
96
|
// Parse <T> components
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
importAliases
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
ast,
|
|
42
|
-
pkg,
|
|
43
|
-
path,
|
|
44
|
-
updates,
|
|
45
|
-
errors,
|
|
46
|
-
warnings,
|
|
47
|
-
file,
|
|
48
|
-
parsingOptions,
|
|
49
|
-
});
|
|
50
|
-
}
|
|
97
|
+
traverse(ast, {
|
|
98
|
+
JSXElement(path) {
|
|
99
|
+
parseJSXElement(importAliases, path.node, updates, errors, warnings, file);
|
|
100
|
+
},
|
|
101
|
+
});
|
|
51
102
|
// Extra validation (for Locadex)
|
|
52
103
|
// Done in parseStrings() atm
|
|
53
104
|
// if (validate) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/types/data.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export type JSONDictionary = {
|
|
|
17
17
|
export type FlattenedJSONDictionary = {
|
|
18
18
|
[key: string]: string;
|
|
19
19
|
};
|
|
20
|
-
export type { FileFormat, DataFormat,
|
|
20
|
+
export type { FileFormat, DataFormat, FileToUpload, } from 'generaltranslation/types';
|
|
21
21
|
export type JsxChildren = string | string[] | any;
|
|
22
22
|
export type Translations = {
|
|
23
23
|
[key: string]: JsxChildren;
|
package/dist/types/files.d.ts
CHANGED
package/dist/types/index.d.ts
CHANGED
|
@@ -133,6 +133,13 @@ export type Settings = {
|
|
|
133
133
|
options?: AdditionalOptions;
|
|
134
134
|
modelProvider?: string;
|
|
135
135
|
parsingOptions: ParsingConfigOptions;
|
|
136
|
+
branchOptions: BranchOptions;
|
|
137
|
+
};
|
|
138
|
+
export type BranchOptions = {
|
|
139
|
+
currentBranch?: string;
|
|
140
|
+
autoDetectBranches?: boolean;
|
|
141
|
+
remoteName: string;
|
|
142
|
+
enabled: boolean;
|
|
136
143
|
};
|
|
137
144
|
export type AdditionalOptions = {
|
|
138
145
|
jsonSchema?: {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized spinner management for tracking multiple async operations
|
|
3
|
+
*/
|
|
4
|
+
export declare class SpinnerManager {
|
|
5
|
+
private spinners;
|
|
6
|
+
/**
|
|
7
|
+
* Run an async operation with a spinner
|
|
8
|
+
*/
|
|
9
|
+
run<T>(id: string, message: string, fn: () => Promise<T>): Promise<T>;
|
|
10
|
+
/**
|
|
11
|
+
* Mark a spinner as successful
|
|
12
|
+
*/
|
|
13
|
+
succeed(id: string, message: string): void;
|
|
14
|
+
/**
|
|
15
|
+
* Mark a spinner as warning
|
|
16
|
+
*/
|
|
17
|
+
warn(id: string, message: string): void;
|
|
18
|
+
/**
|
|
19
|
+
* Start a new spinner
|
|
20
|
+
*/
|
|
21
|
+
start(id: string, message: string): void;
|
|
22
|
+
/**
|
|
23
|
+
* Stop a specific spinner
|
|
24
|
+
*/
|
|
25
|
+
stop(id: string, message?: string): void;
|
|
26
|
+
/**
|
|
27
|
+
* Stop all running spinners
|
|
28
|
+
*/
|
|
29
|
+
stopAll(): void;
|
|
30
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { createSpinner } from '../console/logging.js';
|
|
3
|
+
/**
|
|
4
|
+
* Centralized spinner management for tracking multiple async operations
|
|
5
|
+
*/
|
|
6
|
+
export class SpinnerManager {
|
|
7
|
+
spinners = new Map();
|
|
8
|
+
/**
|
|
9
|
+
* Run an async operation with a spinner
|
|
10
|
+
*/
|
|
11
|
+
async run(id, message, fn) {
|
|
12
|
+
const spinner = createSpinner('dots');
|
|
13
|
+
this.spinners.set(id, spinner);
|
|
14
|
+
spinner.start(message);
|
|
15
|
+
try {
|
|
16
|
+
const result = await fn();
|
|
17
|
+
spinner.stop(chalk.green('✓'));
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
spinner.stop(chalk.red('✗'));
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
finally {
|
|
25
|
+
this.spinners.delete(id);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Mark a spinner as successful
|
|
30
|
+
*/
|
|
31
|
+
succeed(id, message) {
|
|
32
|
+
const spinner = this.spinners.get(id);
|
|
33
|
+
if (spinner) {
|
|
34
|
+
spinner.stop(chalk.green(message));
|
|
35
|
+
this.spinners.delete(id);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Mark a spinner as warning
|
|
40
|
+
*/
|
|
41
|
+
warn(id, message) {
|
|
42
|
+
const spinner = this.spinners.get(id);
|
|
43
|
+
if (spinner) {
|
|
44
|
+
spinner.stop(chalk.yellow(message));
|
|
45
|
+
this.spinners.delete(id);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Start a new spinner
|
|
50
|
+
*/
|
|
51
|
+
start(id, message) {
|
|
52
|
+
const spinner = createSpinner('dots');
|
|
53
|
+
this.spinners.set(id, spinner);
|
|
54
|
+
spinner.start(message);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Stop a specific spinner
|
|
58
|
+
*/
|
|
59
|
+
stop(id, message) {
|
|
60
|
+
const spinner = this.spinners.get(id);
|
|
61
|
+
if (spinner) {
|
|
62
|
+
spinner.stop(message);
|
|
63
|
+
this.spinners.delete(id);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Stop all running spinners
|
|
68
|
+
*/
|
|
69
|
+
stopAll() {
|
|
70
|
+
this.spinners.forEach((s) => s.stop());
|
|
71
|
+
this.spinners.clear();
|
|
72
|
+
}
|
|
73
|
+
}
|
package/dist/utils/gitDiff.js
CHANGED
|
@@ -9,24 +9,26 @@ const execFileAsync = promisify(execFile);
|
|
|
9
9
|
* Throws if git is unavailable or another error occurs.
|
|
10
10
|
*/
|
|
11
11
|
export async function getGitUnifiedDiff(oldPath, newPath) {
|
|
12
|
-
|
|
13
|
-
'
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
12
|
+
try {
|
|
13
|
+
const res = await execFileAsync('git', [
|
|
14
|
+
'diff',
|
|
15
|
+
'--no-index',
|
|
16
|
+
'--text',
|
|
17
|
+
'--unified=3',
|
|
18
|
+
'--no-color',
|
|
19
|
+
'--',
|
|
20
|
+
oldPath,
|
|
21
|
+
newPath,
|
|
22
|
+
], {
|
|
23
|
+
windowsHide: true,
|
|
24
|
+
});
|
|
25
|
+
return res.stdout || '';
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
24
28
|
// Exit code 1 means differences found; stdout contains the diff
|
|
25
29
|
if (error && error.code === 1 && typeof error.stdout === 'string') {
|
|
26
|
-
return
|
|
30
|
+
return error.stdout;
|
|
27
31
|
}
|
|
28
32
|
throw error;
|
|
29
|
-
}
|
|
30
|
-
// When there are no changes, stdout is empty string and exit code 0
|
|
31
|
-
return res.stdout || '';
|
|
33
|
+
}
|
|
32
34
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { WorkflowStep } from './Workflow.js';
|
|
2
|
+
import { GT } from 'generaltranslation';
|
|
3
|
+
import { Settings } from '../types/index.js';
|
|
4
|
+
import { BranchData } from '../types/branch.js';
|
|
5
|
+
export declare class BranchStep extends WorkflowStep<null, BranchData | null> {
|
|
6
|
+
private spinner;
|
|
7
|
+
private branchData;
|
|
8
|
+
private settings;
|
|
9
|
+
private gt;
|
|
10
|
+
constructor(gt: GT, settings: Settings);
|
|
11
|
+
run(): Promise<BranchData | null>;
|
|
12
|
+
wait(): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { WorkflowStep } from './Workflow.js';
|
|
2
|
+
import { createSpinner, logError, logErrorAndExit, } from '../console/logging.js';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { getCurrentBranch, getIncomingBranches, getCheckedOutBranches, } from '../git/branches.js';
|
|
5
|
+
import { ApiError } from 'generaltranslation/errors';
|
|
6
|
+
// Step 1: Resolve the current branch id & update API with branch information
|
|
7
|
+
export class BranchStep extends WorkflowStep {
|
|
8
|
+
spinner = createSpinner('dots');
|
|
9
|
+
branchData;
|
|
10
|
+
settings;
|
|
11
|
+
gt;
|
|
12
|
+
constructor(gt, settings) {
|
|
13
|
+
super();
|
|
14
|
+
this.gt = gt;
|
|
15
|
+
this.settings = settings;
|
|
16
|
+
this.branchData = {
|
|
17
|
+
currentBranch: {
|
|
18
|
+
id: '',
|
|
19
|
+
name: '',
|
|
20
|
+
},
|
|
21
|
+
incomingBranch: null,
|
|
22
|
+
checkedOutBranch: null,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
async run() {
|
|
26
|
+
this.spinner.start(`Resolving branch information...`);
|
|
27
|
+
// First get some info about the branches we're working with
|
|
28
|
+
let current = null;
|
|
29
|
+
let incoming = [];
|
|
30
|
+
let checkedOut = [];
|
|
31
|
+
let useDefaultBranch = true;
|
|
32
|
+
if (this.settings.branchOptions.enabled &&
|
|
33
|
+
this.settings.branchOptions.autoDetectBranches) {
|
|
34
|
+
const [currentResult, incomingResult, checkedOutResult] = await Promise.all([
|
|
35
|
+
getCurrentBranch(this.settings.branchOptions.remoteName),
|
|
36
|
+
getIncomingBranches(this.settings.branchOptions.remoteName),
|
|
37
|
+
getCheckedOutBranches(this.settings.branchOptions.remoteName),
|
|
38
|
+
]);
|
|
39
|
+
current = currentResult;
|
|
40
|
+
incoming = incomingResult;
|
|
41
|
+
checkedOut = checkedOutResult;
|
|
42
|
+
useDefaultBranch = false;
|
|
43
|
+
}
|
|
44
|
+
if (this.settings.branchOptions.enabled &&
|
|
45
|
+
this.settings.branchOptions.currentBranch) {
|
|
46
|
+
current = {
|
|
47
|
+
currentBranchName: this.settings.branchOptions.currentBranch,
|
|
48
|
+
defaultBranch: current?.defaultBranch ?? false, // we have no way of knowing if this is the default branch without using the auto-detection logic
|
|
49
|
+
};
|
|
50
|
+
useDefaultBranch = false;
|
|
51
|
+
}
|
|
52
|
+
const branchData = await this.gt.queryBranchData({
|
|
53
|
+
branchNames: [
|
|
54
|
+
...(current ? [current.currentBranchName] : []),
|
|
55
|
+
...incoming,
|
|
56
|
+
...checkedOut,
|
|
57
|
+
],
|
|
58
|
+
});
|
|
59
|
+
if (useDefaultBranch) {
|
|
60
|
+
if (!branchData.defaultBranch) {
|
|
61
|
+
const createBranchResult = await this.gt.createBranch({
|
|
62
|
+
branchName: 'main', // name doesn't matter for default branch
|
|
63
|
+
defaultBranch: true,
|
|
64
|
+
});
|
|
65
|
+
this.branchData.currentBranch = createBranchResult.branch;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
this.branchData.currentBranch = branchData.defaultBranch;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
if (!current) {
|
|
73
|
+
logErrorAndExit('Failed to determine the current branch. Please specify a custom branch or enable automatic branch detection.');
|
|
74
|
+
}
|
|
75
|
+
const currentBranch = branchData.branches.find((b) => b.name === current.currentBranchName);
|
|
76
|
+
if (!currentBranch) {
|
|
77
|
+
try {
|
|
78
|
+
const createBranchResult = await this.gt.createBranch({
|
|
79
|
+
branchName: current.currentBranchName,
|
|
80
|
+
defaultBranch: current.defaultBranch,
|
|
81
|
+
});
|
|
82
|
+
this.branchData.currentBranch = createBranchResult.branch;
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
if (error instanceof ApiError && error.code === 403) {
|
|
86
|
+
logError('Failed to create branch. To enable branching, please upgrade your plan.');
|
|
87
|
+
// retry with default branch
|
|
88
|
+
const createBranchResult = await this.gt.createBranch({
|
|
89
|
+
branchName: 'main', // name doesn't matter for default branch
|
|
90
|
+
defaultBranch: true,
|
|
91
|
+
});
|
|
92
|
+
this.branchData.currentBranch = createBranchResult.branch;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
this.branchData.currentBranch = currentBranch;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Now set the incoming and checked out branches (first one that exists)
|
|
101
|
+
this.branchData.incomingBranch =
|
|
102
|
+
incoming
|
|
103
|
+
.map((b) => {
|
|
104
|
+
const branch = branchData.branches.find((bb) => bb.name === b);
|
|
105
|
+
if (branch) {
|
|
106
|
+
return branch;
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
.filter((b) => b !== null)[0] ?? null;
|
|
113
|
+
this.branchData.checkedOutBranch =
|
|
114
|
+
checkedOut
|
|
115
|
+
.map((b) => {
|
|
116
|
+
const branch = branchData.branches.find((bb) => bb.name === b);
|
|
117
|
+
if (branch) {
|
|
118
|
+
return branch;
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
.filter((b) => b !== null)[0] ?? null;
|
|
125
|
+
this.spinner.stop(chalk.green('Branch information resolved successfully'));
|
|
126
|
+
return this.branchData;
|
|
127
|
+
}
|
|
128
|
+
async wait() {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { WorkflowStep } from './Workflow.js';
|
|
2
|
+
import { GT } from 'generaltranslation';
|
|
3
|
+
import { Settings } from '../types/index.js';
|
|
4
|
+
import { FileStatusTracker } from './PollJobsStep.js';
|
|
5
|
+
export type DownloadTranslationsInput = {
|
|
6
|
+
fileTracker: FileStatusTracker;
|
|
7
|
+
resolveOutputPath: (sourcePath: string, locale: string) => string | null;
|
|
8
|
+
forceDownload?: boolean;
|
|
9
|
+
};
|
|
10
|
+
export declare class DownloadTranslationsStep extends WorkflowStep<DownloadTranslationsInput, boolean> {
|
|
11
|
+
private gt;
|
|
12
|
+
private settings;
|
|
13
|
+
private spinner;
|
|
14
|
+
constructor(gt: GT, settings: Settings);
|
|
15
|
+
run({ fileTracker, resolveOutputPath, forceDownload, }: DownloadTranslationsInput): Promise<boolean>;
|
|
16
|
+
private downloadFiles;
|
|
17
|
+
private downloadFilesWithRetry;
|
|
18
|
+
wait(): Promise<void>;
|
|
19
|
+
}
|