@localheroai/cli 0.0.2 → 0.0.3
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/README +98 -0
- package/package.json +57 -57
- package/src/api/auth.js +20 -11
- package/src/api/client.js +70 -28
- package/src/api/imports.js +15 -13
- package/src/api/projects.js +17 -17
- package/src/api/translations.js +50 -29
- package/src/cli.js +49 -42
- package/src/commands/init.js +436 -236
- package/src/commands/login.js +59 -48
- package/src/commands/sync.js +28 -0
- package/src/commands/translate.js +227 -247
- package/src/utils/auth.js +15 -15
- package/src/utils/config.js +115 -86
- package/src/utils/files.js +338 -116
- package/src/utils/git.js +64 -8
- package/src/utils/github.js +75 -45
- package/src/utils/import-service.js +112 -129
- package/src/utils/prompt-service.js +66 -50
- package/src/utils/sync-service.js +147 -0
- package/src/utils/translation-updater/common.js +44 -0
- package/src/utils/translation-updater/index.js +36 -0
- package/src/utils/translation-updater/json-handler.js +112 -0
- package/src/utils/translation-updater/yaml-handler.js +181 -0
- package/src/utils/translation-utils.js +237 -0
- package/src/utils/defaults.js +0 -7
- package/src/utils/helpers.js +0 -3
- package/src/utils/project-service.js +0 -11
- package/src/utils/translation-updater.js +0 -154
package/src/utils/files.js
CHANGED
|
@@ -1,139 +1,361 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
1
2
|
import { glob } from 'glob';
|
|
2
3
|
import { readFile } from 'fs/promises';
|
|
3
4
|
import path from 'path';
|
|
4
5
|
import yaml from 'yaml';
|
|
6
|
+
import { promises as fs } from 'fs';
|
|
5
7
|
|
|
6
|
-
function parseFile(content, format) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
export function parseFile(content, format) {
|
|
9
|
+
try {
|
|
10
|
+
if (format === 'json') {
|
|
11
|
+
return JSON.parse(content);
|
|
12
|
+
}
|
|
13
|
+
return yaml.parse(content);
|
|
14
|
+
} catch (error) {
|
|
15
|
+
throw new Error(`Failed to parse ${format} file: ${error.message}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function extractLocaleFromPath(filePath, localeRegex, knownLocales = []) {
|
|
20
|
+
if (knownLocales && knownLocales.length > 0) {
|
|
21
|
+
const basename = path.basename(filePath, path.extname(filePath));
|
|
22
|
+
const foundLocaleInFilename = knownLocales.find(locale =>
|
|
23
|
+
locale && basename.toLowerCase() === locale.toLowerCase()
|
|
24
|
+
);
|
|
25
|
+
if (foundLocaleInFilename) {
|
|
26
|
+
return foundLocaleInFilename.toLowerCase();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Then try to match in the path
|
|
30
|
+
const pathParts = filePath.toLowerCase().split(path.sep);
|
|
31
|
+
const foundLocaleInPath = knownLocales.find(locale =>
|
|
32
|
+
locale && pathParts.includes(locale.toLowerCase())
|
|
33
|
+
);
|
|
34
|
+
if (foundLocaleInPath) {
|
|
35
|
+
return foundLocaleInPath.toLowerCase();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const dirName = path.basename(path.dirname(filePath));
|
|
40
|
+
if (dirName && isValidLocale(dirName)) {
|
|
41
|
+
return dirName.toLowerCase();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (localeRegex) {
|
|
45
|
+
const filename = path.basename(filePath);
|
|
46
|
+
const regexPattern = new RegExp(localeRegex);
|
|
47
|
+
const regexMatch = filename.match(regexPattern);
|
|
48
|
+
if (regexMatch && regexMatch[1]) {
|
|
49
|
+
const locale = regexMatch[1].toLowerCase();
|
|
50
|
+
if (isValidLocale(locale)) {
|
|
51
|
+
return locale;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
throw new Error(`Could not extract locale from path: ${filePath}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function isValidLocale(locale) {
|
|
60
|
+
// Basic validation for language code (2 letters) or language-region code (e.g., en-US)
|
|
61
|
+
return /^[a-z]{2}(?:-[A-Z]{2})?$/.test(locale);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function flattenTranslations(obj, parentKey = '') {
|
|
65
|
+
const result = {};
|
|
66
|
+
|
|
67
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
68
|
+
const newKey = parentKey ? `${parentKey}.${key}` : key;
|
|
69
|
+
|
|
70
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
71
|
+
Object.assign(result, flattenTranslations(value, newKey));
|
|
72
|
+
} else {
|
|
73
|
+
result[newKey] = value;
|
|
14
74
|
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return result;
|
|
15
78
|
}
|
|
16
79
|
|
|
17
|
-
function
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
.forEach(([k, v]) => {
|
|
33
|
-
siblings[`${parentKeys.join('.')}.${k}`] = v;
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
result[fullKey] = {
|
|
37
|
-
value: value,
|
|
38
|
-
context: {
|
|
39
|
-
parent_keys: parentKeys,
|
|
40
|
-
sibling_keys: siblings
|
|
41
|
-
}
|
|
42
|
-
};
|
|
80
|
+
function detectJsonFormat(obj) {
|
|
81
|
+
let hasNested = false;
|
|
82
|
+
let hasDotNotation = false;
|
|
83
|
+
|
|
84
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
85
|
+
if (key.includes('.')) {
|
|
86
|
+
hasDotNotation = true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
90
|
+
hasNested = true;
|
|
91
|
+
|
|
92
|
+
for (const [, nestedValue] of Object.entries(value)) {
|
|
93
|
+
if (nestedValue && typeof nestedValue === 'object' && !Array.isArray(nestedValue)) {
|
|
94
|
+
return 'nested';
|
|
43
95
|
}
|
|
96
|
+
}
|
|
44
97
|
}
|
|
45
|
-
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (hasNested && hasDotNotation) {
|
|
101
|
+
return 'mixed';
|
|
102
|
+
} else if (hasNested) {
|
|
103
|
+
return 'nested';
|
|
104
|
+
} else if (hasDotNotation) {
|
|
105
|
+
return 'flat';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return 'flat';
|
|
46
109
|
}
|
|
47
110
|
|
|
48
|
-
function
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
111
|
+
function unflattenTranslations(flatObj) {
|
|
112
|
+
const result = {};
|
|
113
|
+
|
|
114
|
+
for (const [key, value] of Object.entries(flatObj)) {
|
|
115
|
+
const keys = key.split('.');
|
|
116
|
+
let current = result;
|
|
117
|
+
|
|
118
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
119
|
+
const k = keys[i];
|
|
120
|
+
current[k] = current[k] || {};
|
|
121
|
+
current = current[k];
|
|
52
122
|
}
|
|
53
|
-
|
|
123
|
+
|
|
124
|
+
current[keys[keys.length - 1]] = value;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return result;
|
|
54
128
|
}
|
|
55
129
|
|
|
56
|
-
function
|
|
57
|
-
|
|
130
|
+
function preserveJsonStructure(originalObj, newTranslations, format) {
|
|
131
|
+
if (format === 'flat') {
|
|
132
|
+
return { ...originalObj, ...newTranslations };
|
|
133
|
+
}
|
|
58
134
|
|
|
59
|
-
|
|
60
|
-
|
|
135
|
+
if (format === 'nested') {
|
|
136
|
+
const merged = { ...originalObj };
|
|
137
|
+
const unflattenedNew = unflattenTranslations(newTranslations);
|
|
138
|
+
return deepMerge(merged, unflattenedNew);
|
|
139
|
+
}
|
|
140
|
+
const result = { ...originalObj };
|
|
61
141
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
142
|
+
for (const [key, value] of Object.entries(newTranslations)) {
|
|
143
|
+
if (key.includes('.')) {
|
|
144
|
+
const keys = key.split('.');
|
|
145
|
+
if (originalObj[key] !== undefined) {
|
|
146
|
+
result[key] = value;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
let current = result;
|
|
151
|
+
|
|
152
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
153
|
+
const k = keys[i];
|
|
154
|
+
current[k] = current[k] || {};
|
|
155
|
+
if (typeof current[k] !== 'object' || Array.isArray(current[k])) {
|
|
156
|
+
current[k] = {};
|
|
66
157
|
}
|
|
158
|
+
|
|
159
|
+
current = current[k];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
current[keys[keys.length - 1]] = value;
|
|
163
|
+
} else {
|
|
164
|
+
result[key] = value;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function deepMerge(target, source) {
|
|
172
|
+
const result = { ...target };
|
|
173
|
+
|
|
174
|
+
for (const [key, value] of Object.entries(source)) {
|
|
175
|
+
if (value && typeof value === 'object' &&
|
|
176
|
+
result[key] && typeof result[key] === 'object' &&
|
|
177
|
+
!Array.isArray(value) && !Array.isArray(result[key])) {
|
|
178
|
+
result[key] = deepMerge(result[key], value);
|
|
179
|
+
} else {
|
|
180
|
+
result[key] = value;
|
|
67
181
|
}
|
|
182
|
+
}
|
|
68
183
|
|
|
69
|
-
|
|
184
|
+
return result;
|
|
70
185
|
}
|
|
71
186
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
187
|
+
function extractNamespace(filePath) {
|
|
188
|
+
const fileName = path.basename(filePath, path.extname(filePath));
|
|
189
|
+
const dirName = path.basename(path.dirname(filePath));
|
|
190
|
+
|
|
191
|
+
// Pattern 1: /path/to/en/common.json -> namespace = common
|
|
192
|
+
if (/^[a-z]{2}(-[A-Z]{2})?$/.test(dirName)) {
|
|
193
|
+
return fileName;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Pattern 2: /path/to/messages.en.json -> namespace = messages
|
|
197
|
+
const dotMatch = fileName.match(/^(.+)\.([a-z]{2}(?:-[A-Z]{2})?)$/);
|
|
198
|
+
if (dotMatch) {
|
|
199
|
+
return dotMatch[1];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Pattern 3: /path/to/common-en.json -> namespace = common
|
|
203
|
+
const dashMatch = fileName.match(/^(.+)-([a-z]{2}(?:-[A-Z]{2})?)$/);
|
|
204
|
+
if (dashMatch) {
|
|
205
|
+
return dashMatch[1];
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return '';
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export async function findTranslationFiles(config, options = {}) {
|
|
212
|
+
const {
|
|
213
|
+
parseContent = true,
|
|
214
|
+
includeContent = true,
|
|
215
|
+
extractKeys = true,
|
|
216
|
+
basePath = process.cwd(),
|
|
217
|
+
sourceLocale = config.sourceLocale,
|
|
218
|
+
targetLocales = config.outputLocales || [],
|
|
219
|
+
includeNamespace = false,
|
|
220
|
+
verbose = false,
|
|
221
|
+
returnFullResult = false
|
|
222
|
+
} = options;
|
|
223
|
+
const knownLocales = [sourceLocale, ...targetLocales];
|
|
224
|
+
const { translationFiles } = config;
|
|
225
|
+
const {
|
|
226
|
+
paths = [],
|
|
227
|
+
pattern = '**/*.{json,yml,yaml}',
|
|
228
|
+
ignore = [],
|
|
229
|
+
localeRegex = '.*?([a-z]{2}(?:-[A-Z]{2})?)\\.(?:yml|yaml|json)$'
|
|
230
|
+
} = translationFiles || {};
|
|
231
|
+
|
|
232
|
+
const processedFiles = [];
|
|
233
|
+
|
|
234
|
+
for (const translationPath of paths) {
|
|
235
|
+
const fullPath = path.join(basePath, translationPath);
|
|
236
|
+
const globPattern = path.join(fullPath, pattern);
|
|
237
|
+
|
|
238
|
+
if (verbose) {
|
|
239
|
+
console.log(chalk.blue(`Searching for translation files in ${globPattern}`));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
let files;
|
|
243
|
+
try {
|
|
244
|
+
files = await glob(globPattern, {
|
|
245
|
+
ignore: ignore.map(i => path.join(basePath, i)),
|
|
246
|
+
absolute: false
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
if (verbose) {
|
|
250
|
+
console.log(chalk.blue(`Found ${files.length} files in ${translationPath}`));
|
|
251
|
+
}
|
|
252
|
+
} catch (error) {
|
|
253
|
+
if (verbose) {
|
|
254
|
+
console.error(chalk.red(`Error searching for files in ${translationPath}: ${error.message}`));
|
|
255
|
+
}
|
|
256
|
+
files = [];
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
for (const file of files) {
|
|
260
|
+
try {
|
|
261
|
+
const filePath = file;
|
|
262
|
+
const format = path.extname(file).slice(1);
|
|
263
|
+
const locale = extractLocaleFromPath(file, localeRegex, knownLocales);
|
|
264
|
+
|
|
265
|
+
const result = {
|
|
266
|
+
path: filePath,
|
|
267
|
+
format,
|
|
268
|
+
locale
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
if (parseContent) {
|
|
272
|
+
const content = await readFile(filePath, 'utf8');
|
|
273
|
+
const parsedContent = parseFile(content, format);
|
|
274
|
+
|
|
275
|
+
if (includeContent) {
|
|
276
|
+
result.content = Buffer.from(content).toString('base64');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (extractKeys) {
|
|
280
|
+
const hasLanguageWrapper = parsedContent[locale] !== undefined;
|
|
281
|
+
result.hasLanguageWrapper = hasLanguageWrapper;
|
|
282
|
+
const translationData = hasLanguageWrapper ? parsedContent[locale] : parsedContent;
|
|
283
|
+
result.translations = translationData;
|
|
284
|
+
result.keys = flattenTranslations(translationData);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (includeNamespace) {
|
|
289
|
+
result.namespace = extractNamespace(filePath);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
processedFiles.push(result);
|
|
293
|
+
} catch (error) {
|
|
294
|
+
if (verbose) {
|
|
295
|
+
console.warn(chalk.yellow(`Warning: ${error.message}`));
|
|
137
296
|
}
|
|
138
|
-
|
|
139
|
-
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (!returnFullResult) {
|
|
302
|
+
return processedFiles;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const allFiles = processedFiles;
|
|
306
|
+
const sourceFiles = allFiles.filter(file => file.locale === sourceLocale);
|
|
307
|
+
const targetFilesByLocale = {};
|
|
308
|
+
|
|
309
|
+
for (const locale of targetLocales) {
|
|
310
|
+
targetFilesByLocale[locale] = allFiles.filter(file => file.locale === locale);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return {
|
|
314
|
+
allFiles,
|
|
315
|
+
sourceFiles,
|
|
316
|
+
targetFilesByLocale
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export {
|
|
321
|
+
unflattenTranslations,
|
|
322
|
+
detectJsonFormat,
|
|
323
|
+
preserveJsonStructure,
|
|
324
|
+
directoryExists,
|
|
325
|
+
findFirstExistingPath,
|
|
326
|
+
getDirectoryContents
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
async function directoryExists(path, fsModule = fs) {
|
|
330
|
+
try {
|
|
331
|
+
const stats = await fsModule.stat(path);
|
|
332
|
+
return stats.isDirectory();
|
|
333
|
+
} catch (error) {
|
|
334
|
+
if (error.code === 'ENOENT') {
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
throw error;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
async function findFirstExistingPath(paths, fsModule = fs) {
|
|
342
|
+
for (const path of paths) {
|
|
343
|
+
if (await directoryExists(path, fsModule)) {
|
|
344
|
+
return path;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async function getDirectoryContents(dir, fsModule = fs) {
|
|
351
|
+
try {
|
|
352
|
+
const files = await fsModule.readdir(dir);
|
|
353
|
+
return {
|
|
354
|
+
files,
|
|
355
|
+
jsonFiles: files.filter(f => f.endsWith('.json')),
|
|
356
|
+
yamlFiles: files.filter(f => f.endsWith('.yml') || f.endsWith('.yaml'))
|
|
357
|
+
};
|
|
358
|
+
} catch {
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
}
|
package/src/utils/git.js
CHANGED
|
@@ -1,16 +1,72 @@
|
|
|
1
1
|
import { promises as fs } from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { promisify } from 'util';
|
|
4
|
+
import { execFile } from 'child_process';
|
|
3
5
|
|
|
4
|
-
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
|
|
8
|
+
const defaultDeps = {
|
|
9
|
+
fs,
|
|
10
|
+
path,
|
|
11
|
+
exec: execFileAsync,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const gitService = {
|
|
15
|
+
deps: { ...defaultDeps },
|
|
16
|
+
|
|
17
|
+
setDependencies(customDeps = {}) {
|
|
18
|
+
this.deps = { ...defaultDeps, ...customDeps };
|
|
19
|
+
return this;
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
async updateGitignore(basePath) {
|
|
23
|
+
const { fs, path } = this.deps;
|
|
5
24
|
const gitignorePath = path.join(basePath, '.gitignore');
|
|
25
|
+
let content = '';
|
|
26
|
+
|
|
6
27
|
try {
|
|
7
|
-
|
|
8
|
-
if (!content.includes('.localhero_key')) {
|
|
9
|
-
await fs.appendFile(gitignorePath, '\n.localhero_key\n');
|
|
10
|
-
return true;
|
|
11
|
-
}
|
|
12
|
-
return false;
|
|
28
|
+
content = await fs.readFile(gitignorePath, 'utf8');
|
|
13
29
|
} catch (error) {
|
|
30
|
+
if (error.code !== 'ENOENT') {
|
|
14
31
|
return false;
|
|
32
|
+
}
|
|
15
33
|
}
|
|
16
|
-
|
|
34
|
+
|
|
35
|
+
if (content.includes('.localhero_key')) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
await fs.appendFile(gitignorePath, '\n.localhero_key\n');
|
|
41
|
+
return true;
|
|
42
|
+
} catch (error) {
|
|
43
|
+
if (error.code === 'ENOENT') {
|
|
44
|
+
try {
|
|
45
|
+
await fs.writeFile(gitignorePath, '.localhero_key\n');
|
|
46
|
+
return true;
|
|
47
|
+
} catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
async getCurrentBranch() {
|
|
56
|
+
try {
|
|
57
|
+
const { exec } = this.deps;
|
|
58
|
+
const { stdout } = await exec('git', ['rev-parse', '--abbrev-ref', 'HEAD']);
|
|
59
|
+
return stdout.trim();
|
|
60
|
+
} catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export async function updateGitignore(basePath) {
|
|
67
|
+
return gitService.updateGitignore(basePath);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function getCurrentBranch() {
|
|
71
|
+
return gitService.getCurrentBranch();
|
|
72
|
+
}
|