gtx-cli 2.4.15 → 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.
Files changed (58) hide show
  1. package/dist/api/collectUserEditDiffs.d.ts +2 -7
  2. package/dist/api/collectUserEditDiffs.js +33 -78
  3. package/dist/api/downloadFileBatch.d.ts +11 -10
  4. package/dist/api/downloadFileBatch.js +120 -127
  5. package/dist/api/saveLocalEdits.js +18 -15
  6. package/dist/cli/base.js +1 -1
  7. package/dist/cli/commands/stage.d.ts +8 -2
  8. package/dist/cli/commands/stage.js +25 -7
  9. package/dist/cli/commands/translate.d.ts +4 -2
  10. package/dist/cli/commands/translate.js +5 -6
  11. package/dist/cli/flags.js +4 -1
  12. package/dist/config/generateSettings.js +10 -0
  13. package/dist/console/logging.d.ts +1 -1
  14. package/dist/console/logging.js +3 -4
  15. package/dist/formats/files/translate.d.ts +2 -2
  16. package/dist/formats/files/translate.js +12 -7
  17. package/dist/fs/config/downloadedVersions.d.ts +10 -3
  18. package/dist/fs/config/downloadedVersions.js +8 -0
  19. package/dist/fs/config/updateVersions.d.ts +2 -1
  20. package/dist/git/branches.d.ts +7 -0
  21. package/dist/git/branches.js +88 -0
  22. package/dist/types/branch.d.ts +14 -0
  23. package/dist/types/branch.js +1 -0
  24. package/dist/types/data.d.ts +1 -1
  25. package/dist/types/files.d.ts +7 -0
  26. package/dist/types/index.d.ts +7 -0
  27. package/dist/utils/SpinnerManager.d.ts +30 -0
  28. package/dist/utils/SpinnerManager.js +73 -0
  29. package/dist/utils/gitDiff.js +18 -16
  30. package/dist/workflow/BranchStep.d.ts +13 -0
  31. package/dist/workflow/BranchStep.js +131 -0
  32. package/dist/workflow/DownloadStep.d.ts +19 -0
  33. package/dist/workflow/DownloadStep.js +127 -0
  34. package/dist/workflow/EnqueueStep.d.ts +15 -0
  35. package/dist/workflow/EnqueueStep.js +33 -0
  36. package/dist/workflow/PollJobsStep.d.ts +31 -0
  37. package/dist/workflow/PollJobsStep.js +284 -0
  38. package/dist/workflow/SetupStep.d.ts +16 -0
  39. package/dist/workflow/SetupStep.js +71 -0
  40. package/dist/workflow/UploadStep.d.ts +21 -0
  41. package/dist/workflow/UploadStep.js +72 -0
  42. package/dist/workflow/UserEditDiffsStep.d.ts +11 -0
  43. package/dist/workflow/UserEditDiffsStep.js +30 -0
  44. package/dist/workflow/Workflow.d.ts +4 -0
  45. package/dist/workflow/Workflow.js +2 -0
  46. package/dist/workflow/download.d.ts +22 -0
  47. package/dist/workflow/download.js +104 -0
  48. package/dist/workflow/stage.d.ts +14 -0
  49. package/dist/workflow/stage.js +76 -0
  50. package/package.json +4 -5
  51. package/dist/api/checkFileTranslations.d.ts +0 -23
  52. package/dist/api/checkFileTranslations.js +0 -281
  53. package/dist/api/sendFiles.d.ts +0 -17
  54. package/dist/api/sendFiles.js +0 -127
  55. package/dist/api/sendUserEdits.d.ts +0 -19
  56. package/dist/api/sendUserEdits.js +0 -15
  57. package/dist/cli/commands/edits.d.ts +0 -8
  58. package/dist/cli/commands/edits.js +0 -32
@@ -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)) {
@@ -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[]>;
@@ -7,6 +7,7 @@ import parseYaml from '../yaml/parseYaml.js';
7
7
  import YAML from 'yaml';
8
8
  import { determineLibrary } from '../../fs/determineFramework.js';
9
9
  import { isValidMdx } from '../../utils/validateMdx.js';
10
+ import { hashStringSync } from '../../utils/hash.js';
10
11
  export const SUPPORTED_DATA_FORMATS = ['JSX', 'ICU', 'I18NEXT'];
11
12
  export async function aggregateFiles(settings) {
12
13
  // Aggregate all files to translate
@@ -50,6 +51,8 @@ export async function aggregateFiles(settings) {
50
51
  }
51
52
  const parsedJson = parseJson(content, filePath, settings.options || {}, settings.defaultLocale);
52
53
  return {
54
+ fileId: hashStringSync(relativePath),
55
+ versionId: hashStringSync(parsedJson),
53
56
  content: parsedJson,
54
57
  fileName: relativePath,
55
58
  fileFormat: 'JSON',
@@ -65,7 +68,7 @@ export async function aggregateFiles(settings) {
65
68
  }
66
69
  return true;
67
70
  });
68
- allFiles.push(...jsonFiles);
71
+ allFiles.push(...jsonFiles.filter((file) => file !== null));
69
72
  }
70
73
  // Process YAML files
71
74
  if (filePaths.yaml) {
@@ -86,18 +89,18 @@ export async function aggregateFiles(settings) {
86
89
  content: parsedYaml,
87
90
  fileName: relativePath,
88
91
  fileFormat,
92
+ fileId: hashStringSync(relativePath),
93
+ versionId: hashStringSync(parsedYaml),
89
94
  };
90
95
  })
91
96
  .filter((file) => {
92
- if (!file)
93
- return false;
94
- if (typeof file.content !== 'string' || !file.content.trim()) {
95
- logWarning(`Skipping ${file.fileName}: YAML file is empty`);
97
+ if (!file || typeof file.content !== 'string' || !file.content.trim()) {
98
+ logWarning(`Skipping ${file?.fileName ?? 'unknown'}: YAML file is empty`);
96
99
  return false;
97
100
  }
98
101
  return true;
99
102
  });
100
- allFiles.push(...yamlFiles);
103
+ allFiles.push(...yamlFiles.filter((file) => file !== null));
101
104
  }
102
105
  for (const fileType of SUPPORTED_FILE_EXTENSIONS) {
103
106
  if (fileType === 'json' || fileType === 'yaml')
@@ -119,6 +122,8 @@ export async function aggregateFiles(settings) {
119
122
  content: sanitizedContent,
120
123
  fileName: relativePath,
121
124
  fileFormat: fileType.toUpperCase(),
125
+ fileId: hashStringSync(relativePath),
126
+ versionId: hashStringSync(content),
122
127
  };
123
128
  })
124
129
  .filter((file) => {
@@ -130,7 +135,7 @@ export async function aggregateFiles(settings) {
130
135
  }
131
136
  return true;
132
137
  });
133
- allFiles.push(...files);
138
+ allFiles.push(...files.filter((file) => file !== null));
134
139
  }
135
140
  }
136
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
+ }
@@ -0,0 +1,14 @@
1
+ export type BranchData = {
2
+ currentBranch: {
3
+ id: string;
4
+ name: string;
5
+ };
6
+ incomingBranch: {
7
+ id: string;
8
+ name: string;
9
+ } | null;
10
+ checkedOutBranch: {
11
+ id: string;
12
+ name: string;
13
+ } | null;
14
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -17,7 +17,7 @@ export type JSONDictionary = {
17
17
  export type FlattenedJSONDictionary = {
18
18
  [key: string]: string;
19
19
  };
20
- export type { FileFormat, DataFormat, FileToTranslate, } from 'generaltranslation/types';
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;
@@ -1 +1,8 @@
1
1
  export type FileMapping = Record<string, Record<string, string>>;
2
+ export type FileProperties = {
3
+ versionId: string;
4
+ fileName: string;
5
+ fileId: string;
6
+ locale: string;
7
+ branchId: string;
8
+ };
@@ -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
+ }
@@ -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
- const res = await execFileAsync('git', [
13
- 'diff',
14
- '--no-index',
15
- '--text',
16
- '--unified=3',
17
- '--no-color',
18
- '--',
19
- oldPath,
20
- newPath,
21
- ], {
22
- windowsHide: true,
23
- }).catch((error) => {
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 { stdout: error.stdout };
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
+ }