@localheroai/cli 0.0.3 → 0.0.6
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 +1 -1
- package/dist/api/auth.d.ts +2 -0
- package/dist/api/auth.js +28 -0
- package/dist/api/auth.js.map +1 -0
- package/dist/api/client.d.ts +3 -0
- package/dist/api/client.js +80 -0
- package/dist/api/client.js.map +1 -0
- package/dist/api/imports.d.ts +5 -0
- package/dist/api/imports.js +43 -0
- package/dist/api/imports.js.map +1 -0
- package/dist/api/projects.d.ts +2 -0
- package/dist/api/projects.js +42 -0
- package/dist/api/projects.js.map +1 -0
- package/dist/api/translations.d.ts +15 -0
- package/dist/api/translations.js +71 -0
- package/dist/api/translations.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +79 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/_sync.js +22 -0
- package/dist/commands/_sync.js.map +1 -0
- package/dist/commands/_translate.js +3 -0
- package/dist/commands/_translate.js.map +1 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +439 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/login.d.ts +16 -0
- package/dist/commands/login.js +58 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/pull.js +22 -0
- package/dist/commands/pull.js.map +1 -0
- package/dist/commands/push.js +56 -0
- package/dist/commands/push.js.map +1 -0
- package/dist/commands/sync.d.ts +20 -0
- package/dist/commands/sync.js +22 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/commands/translate.d.ts +14 -0
- package/dist/commands/translate.js +145 -0
- package/dist/commands/translate.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +75 -0
- package/dist/types/index.js +17 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/translate/index.js +2 -0
- package/dist/types/translate/index.js.map +1 -0
- package/dist/utils/auth.d.ts +2 -0
- package/dist/utils/auth.js +29 -0
- package/dist/utils/auth.js.map +1 -0
- package/dist/utils/common.js +9 -0
- package/dist/utils/common.js.map +1 -0
- package/dist/utils/config.d.ts +23 -0
- package/dist/utils/config.js +137 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/errors.js +37 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/files.d.ts +32 -0
- package/dist/utils/files.js +347 -0
- package/dist/utils/files.js.map +1 -0
- package/dist/utils/git.d.ts +21 -0
- package/dist/utils/git.js +87 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/utils/github.d.ts +241 -0
- package/dist/utils/github.js +161 -0
- package/dist/utils/github.js.map +1 -0
- package/dist/utils/import-service.d.ts +4 -0
- package/dist/utils/import-service.js +218 -0
- package/dist/utils/import-service.js.map +1 -0
- package/dist/utils/prompt-service.d.ts +44 -0
- package/dist/utils/prompt-service.js +104 -0
- package/dist/utils/prompt-service.js.map +1 -0
- package/dist/utils/sync-service.d.ts +58 -0
- package/dist/utils/sync-service.js +159 -0
- package/dist/utils/sync-service.js.map +1 -0
- package/dist/utils/translation-processor.js +197 -0
- package/dist/utils/translation-processor.js.map +1 -0
- package/dist/utils/translation-updater/common.d.ts +6 -0
- package/{src → dist}/utils/translation-updater/common.js +16 -10
- package/dist/utils/translation-updater/common.js.map +1 -0
- package/dist/utils/translation-updater/index.d.ts +5 -0
- package/{src → dist}/utils/translation-updater/index.js +21 -9
- package/dist/utils/translation-updater/index.js.map +1 -0
- package/dist/utils/translation-updater/json-handler.d.ts +5 -0
- package/{src → dist}/utils/translation-updater/json-handler.js +42 -31
- package/dist/utils/translation-updater/json-handler.js.map +1 -0
- package/dist/utils/translation-updater/yaml-handler.d.ts +5 -0
- package/{src → dist}/utils/translation-updater/yaml-handler.js +40 -41
- package/dist/utils/translation-updater/yaml-handler.js.map +1 -0
- package/dist/utils/translation-utils.d.ts +30 -0
- package/dist/utils/translation-utils.js +324 -0
- package/dist/utils/translation-utils.js.map +1 -0
- package/dist/utils/updater.js +38 -0
- package/dist/utils/updater.js.map +1 -0
- package/package.json +33 -28
- package/src/api/auth.js +0 -24
- package/src/api/client.js +0 -83
- package/src/api/imports.js +0 -22
- package/src/api/projects.js +0 -24
- package/src/api/translations.js +0 -58
- package/src/cli.js +0 -78
- package/src/commands/init.js +0 -485
- package/src/commands/login.js +0 -80
- package/src/commands/sync.js +0 -28
- package/src/commands/translate.js +0 -262
- package/src/utils/auth.js +0 -23
- package/src/utils/config.js +0 -125
- package/src/utils/files.js +0 -361
- package/src/utils/git.js +0 -72
- package/src/utils/github.js +0 -122
- package/src/utils/import-service.js +0 -129
- package/src/utils/prompt-service.js +0 -67
- package/src/utils/sync-service.js +0 -147
- package/src/utils/translation-utils.js +0 -237
package/src/commands/sync.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { syncService as defaultSyncService } from '../utils/sync-service.js';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
|
|
4
|
-
export async function sync({ verbose = false } = {}, deps = { syncService: defaultSyncService }) {
|
|
5
|
-
const { syncService } = deps;
|
|
6
|
-
const { hasUpdates, updates } = await syncService.checkForUpdates({ verbose });
|
|
7
|
-
|
|
8
|
-
if (!hasUpdates) {
|
|
9
|
-
console.log(chalk.green('✓ All translations are up to date'));
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const result = await syncService.applyUpdates(updates, { verbose });
|
|
14
|
-
|
|
15
|
-
const { totalUpdates = 0, totalDeleted = 0 } = result;
|
|
16
|
-
|
|
17
|
-
if (!verbose) {
|
|
18
|
-
if (totalUpdates > 0) {
|
|
19
|
-
console.log(chalk.green(`✓ Updated ${totalUpdates} translations`));
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (totalDeleted > 0) {
|
|
23
|
-
console.log(chalk.green(`✓ Deleted ${totalDeleted} keys`));
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return result;
|
|
28
|
-
}
|
|
@@ -1,262 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import { configService } from '../utils/config.js';
|
|
3
|
-
import { findTranslationFiles, parseFile, flattenTranslations } from '../utils/files.js';
|
|
4
|
-
import { createTranslationJob, checkJobStatus } from '../api/translations.js';
|
|
5
|
-
import { updateTranslationFile } from '../utils/translation-updater/index.js';
|
|
6
|
-
import { checkAuth } from '../utils/auth.js';
|
|
7
|
-
import { findMissingTranslations, batchKeysWithMissing, processLocaleTranslations } from '../utils/translation-utils.js';
|
|
8
|
-
import { syncService } from '../utils/sync-service.js';
|
|
9
|
-
import { autoCommitChanges } from '../utils/github.js';
|
|
10
|
-
|
|
11
|
-
const BATCH_SIZE = 50;
|
|
12
|
-
|
|
13
|
-
const defaultDeps = {
|
|
14
|
-
console,
|
|
15
|
-
configUtils: configService,
|
|
16
|
-
authUtils: { checkAuth },
|
|
17
|
-
fileUtils: { findTranslationFiles },
|
|
18
|
-
translationUtils: {
|
|
19
|
-
createTranslationJob,
|
|
20
|
-
checkJobStatus,
|
|
21
|
-
updateTranslationFile,
|
|
22
|
-
findMissingTranslations,
|
|
23
|
-
batchKeysWithMissing
|
|
24
|
-
},
|
|
25
|
-
syncService,
|
|
26
|
-
gitUtils: { autoCommitChanges }
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export async function translate(options = {}, deps = defaultDeps) {
|
|
30
|
-
const { console, configUtils, authUtils, fileUtils, translationUtils, syncService, gitUtils } = deps;
|
|
31
|
-
const { verbose } = options;
|
|
32
|
-
|
|
33
|
-
const isAuthenticated = await authUtils.checkAuth();
|
|
34
|
-
if (!isAuthenticated) {
|
|
35
|
-
console.error(chalk.red('\n✖ Your API key is invalid. Please run `npx @localheroai/cli login` to authenticate.\n'));
|
|
36
|
-
process.exit(1);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const config = await configUtils.getProjectConfig();
|
|
40
|
-
if (!config) {
|
|
41
|
-
console.error(chalk.red('\n✖ No configuration found. Please run `npx @localheroai/cli init` first.\n'));
|
|
42
|
-
process.exit(1);
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (!config.translationFiles?.paths) {
|
|
47
|
-
console.error(chalk.red('\n✖ Invalid configuration: missing translationFiles.paths. Please run `npx @localheroai/cli init` to set up your configuration.\n'));
|
|
48
|
-
process.exit(1);
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const { hasUpdates, updates } = await syncService.checkForUpdates({ verbose });
|
|
53
|
-
if (hasUpdates) {
|
|
54
|
-
await syncService.applyUpdates(updates, { verbose });
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (verbose) {
|
|
58
|
-
console.log(chalk.blue('\nℹ Using configuration:'));
|
|
59
|
-
console.log(chalk.gray(` Project ID: ${config.projectId}`));
|
|
60
|
-
console.log(chalk.gray(` Source locale: ${config.sourceLocale}`));
|
|
61
|
-
console.log(chalk.gray(` Output locales: ${config.outputLocales.join(', ')}`));
|
|
62
|
-
console.log(chalk.gray(` Translation files: ${config.translationFiles.paths.join(', ')}`));
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const result = await fileUtils.findTranslationFiles(config, { verbose, returnFullResult: true });
|
|
66
|
-
const { sourceFiles, targetFilesByLocale, allFiles } = result;
|
|
67
|
-
|
|
68
|
-
if (!allFiles || allFiles.length === 0) {
|
|
69
|
-
console.error(chalk.red('\n✖ No translation files found in the specified paths.\n'));
|
|
70
|
-
process.exit(1);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (verbose) {
|
|
74
|
-
console.log(chalk.blue(`\nℹ Found ${allFiles.length} translation files`));
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (sourceFiles.length === 0) {
|
|
78
|
-
console.error(chalk.red(`\n✖ No source files found for locale ${config.sourceLocale}\n`));
|
|
79
|
-
process.exit(1);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (verbose) {
|
|
83
|
-
console.log(chalk.blue(`ℹ Found ${sourceFiles.length} source files for locale ${config.sourceLocale}`));
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const missingByLocale = {};
|
|
87
|
-
|
|
88
|
-
for (const sourceFile of sourceFiles) {
|
|
89
|
-
const sourceContentRaw = Buffer.from(sourceFile.content, 'base64').toString();
|
|
90
|
-
const sourceContent = parseFile(sourceContentRaw, sourceFile.format);
|
|
91
|
-
const sourceKeys = flattenTranslations(sourceContent[config.sourceLocale] || sourceContent);
|
|
92
|
-
|
|
93
|
-
for (const targetLocale of config.outputLocales) {
|
|
94
|
-
const targetFiles = targetFilesByLocale[targetLocale] || [];
|
|
95
|
-
const result = processLocaleTranslations(sourceKeys, targetLocale, targetFiles, sourceFile, config.sourceLocale);
|
|
96
|
-
|
|
97
|
-
if (Object.keys(result.missingKeys).length > 0) {
|
|
98
|
-
if (!missingByLocale[targetLocale]) {
|
|
99
|
-
missingByLocale[targetLocale] = {
|
|
100
|
-
path: sourceFile.path,
|
|
101
|
-
targetPath: result.targetPath,
|
|
102
|
-
keys: {},
|
|
103
|
-
keyCount: 0
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
missingByLocale[targetLocale].keys = {
|
|
108
|
-
...missingByLocale[targetLocale].keys,
|
|
109
|
-
...result.missingKeys
|
|
110
|
-
};
|
|
111
|
-
missingByLocale[targetLocale].keyCount += Object.keys(result.missingKeys).length;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (verbose && Object.keys(result.skippedKeys).length > 0) {
|
|
115
|
-
console.log(chalk.yellow(`\nℹ Skipped ${Object.keys(result.skippedKeys).length} keys marked as WIP in ${sourceFile.path}`));
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const missingLocales = Object.keys(missingByLocale);
|
|
121
|
-
if (missingLocales.length === 0) {
|
|
122
|
-
console.log(chalk.green('✓ All translations are up to date!'));
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (verbose) {
|
|
127
|
-
console.log(chalk.blue('\nℹ Missing translations:'));
|
|
128
|
-
for (const [locale, data] of Object.entries(missingByLocale)) {
|
|
129
|
-
console.log(chalk.gray(` ${locale}: ${data.keyCount} keys`));
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const { batches, errors } = translationUtils.batchKeysWithMissing(sourceFiles, missingByLocale, BATCH_SIZE);
|
|
134
|
-
|
|
135
|
-
if (errors.length > 0) {
|
|
136
|
-
console.error(chalk.red('\n✖ Errors occurred while preparing translation jobs:'));
|
|
137
|
-
for (const error of errors) {
|
|
138
|
-
console.error(chalk.red(` ${error.message}`));
|
|
139
|
-
}
|
|
140
|
-
process.exit(1);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
let totalTranslated = 0;
|
|
144
|
-
let totalLanguages = 0;
|
|
145
|
-
const processedLocales = new Set();
|
|
146
|
-
const allJobIds = [];
|
|
147
|
-
let resultsBaseUrl = null;
|
|
148
|
-
|
|
149
|
-
for (const batch of batches) {
|
|
150
|
-
const targetPaths = Object.entries(missingByLocale).reduce((acc, [locale, data]) => {
|
|
151
|
-
acc[locale] = data.targetPath;
|
|
152
|
-
return acc;
|
|
153
|
-
}, {});
|
|
154
|
-
|
|
155
|
-
const jobRequest = {
|
|
156
|
-
projectId: config.projectId,
|
|
157
|
-
sourceFiles: batch.files,
|
|
158
|
-
targetLocales: batch.locales,
|
|
159
|
-
targetPaths
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
try {
|
|
163
|
-
const response = await translationUtils.createTranslationJob(jobRequest);
|
|
164
|
-
const { jobs } = response;
|
|
165
|
-
const batchJobIds = jobs.map(job => job.id);
|
|
166
|
-
|
|
167
|
-
allJobIds.push(...batchJobIds);
|
|
168
|
-
|
|
169
|
-
const pendingJobs = new Set(batchJobIds);
|
|
170
|
-
|
|
171
|
-
while (pendingJobs.size > 0) {
|
|
172
|
-
const jobPromises = Array.from(pendingJobs).map(async jobId => {
|
|
173
|
-
if (verbose) {
|
|
174
|
-
console.log(chalk.blue(`\nℹ Checking job ${jobId}`));
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
let status;
|
|
178
|
-
let retries = 0;
|
|
179
|
-
const MAX_WAIT_MINUTES = 10;
|
|
180
|
-
const startTime = Date.now();
|
|
181
|
-
|
|
182
|
-
do {
|
|
183
|
-
status = await translationUtils.checkJobStatus(jobId, true);
|
|
184
|
-
|
|
185
|
-
if (status.status === 'failed') {
|
|
186
|
-
throw new Error(`Translation job failed: ${status.error_details || 'Unknown error'}`);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
if (status.status === 'pending' || status.status === 'processing') {
|
|
190
|
-
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
|
191
|
-
if (elapsed > MAX_WAIT_MINUTES * 60) {
|
|
192
|
-
throw new Error(`Translation timed out after ${MAX_WAIT_MINUTES} minutes`);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const waitSeconds = Math.min(2 ** retries, 30);
|
|
196
|
-
if (verbose) {
|
|
197
|
-
console.log(chalk.blue(` Job ${jobId} is ${status.status}, checking again in ${waitSeconds}s...`));
|
|
198
|
-
}
|
|
199
|
-
await new Promise(resolve => setTimeout(resolve, waitSeconds * 1000));
|
|
200
|
-
retries = Math.min(retries + 1, 5);
|
|
201
|
-
return { jobId, status: 'pending' };
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (status.status === 'completed' && status.translations?.data && status.language?.code) {
|
|
205
|
-
if (status.results_url && !resultsBaseUrl) {
|
|
206
|
-
resultsBaseUrl = status.results_url.split('?')[0];
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const languageCode = status.language.code;
|
|
210
|
-
if (!processedLocales.has(languageCode)) {
|
|
211
|
-
const targetPath = missingByLocale[languageCode].targetPath;
|
|
212
|
-
if (verbose) {
|
|
213
|
-
console.log(chalk.blue(` Updating translations for ${languageCode} in ${targetPath}`));
|
|
214
|
-
}
|
|
215
|
-
await translationUtils.updateTranslationFile(targetPath, status.translations.data, languageCode);
|
|
216
|
-
totalTranslated += missingByLocale[languageCode].keyCount;
|
|
217
|
-
totalLanguages++;
|
|
218
|
-
processedLocales.add(languageCode);
|
|
219
|
-
}
|
|
220
|
-
return { jobId, status: 'completed' };
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
return { jobId, status: status.status };
|
|
224
|
-
} while (status.status === 'pending' || status.status === 'processing');
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
const results = await Promise.all(jobPromises);
|
|
228
|
-
results.forEach(result => {
|
|
229
|
-
if (result.status === 'completed') {
|
|
230
|
-
pendingJobs.delete(result.jobId);
|
|
231
|
-
}
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
if (pendingJobs.size > 0) {
|
|
235
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
} catch (error) {
|
|
239
|
-
console.error(chalk.red(`\n✖ Error processing translation jobs: ${error.message}\n`));
|
|
240
|
-
process.exit(1);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
await configUtils.updateLastSyncedAt();
|
|
245
|
-
|
|
246
|
-
console.log(chalk.green('✓ Translations complete!'));
|
|
247
|
-
console.log(`Updated ${totalTranslated} keys in ${totalLanguages} languages`);
|
|
248
|
-
|
|
249
|
-
// Auto-commit changes if we're running in GitHub Actions
|
|
250
|
-
if (totalTranslated > 0) {
|
|
251
|
-
try {
|
|
252
|
-
gitUtils.autoCommitChanges(config.translationFiles.paths.join(' '));
|
|
253
|
-
} catch (error) {
|
|
254
|
-
console.warn(chalk.yellow(`\nℹ Could not auto-commit changes: ${error.message}`));
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
if (resultsBaseUrl && allJobIds.length > 0) {
|
|
259
|
-
const jobIdsParam = allJobIds.join(',');
|
|
260
|
-
console.log(`View job results at: ${resultsBaseUrl}?job_ids=${jobIdsParam}`);
|
|
261
|
-
}
|
|
262
|
-
}
|
package/src/utils/auth.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { configService } from './config.js';
|
|
2
|
-
|
|
3
|
-
export async function getApiKey() {
|
|
4
|
-
const envKey = process.env.LOCALHERO_API_KEY;
|
|
5
|
-
if (typeof envKey === 'string' && envKey.trim() !== '') {
|
|
6
|
-
return envKey;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const config = await configService.getAuthConfig();
|
|
10
|
-
return config?.api_key;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export async function checkAuth() {
|
|
14
|
-
try {
|
|
15
|
-
const apiKey = await getApiKey();
|
|
16
|
-
const isValidFormat = typeof apiKey === 'string' &&
|
|
17
|
-
/^tk_[a-f0-9]+$/.test(apiKey);
|
|
18
|
-
|
|
19
|
-
return isValidFormat;
|
|
20
|
-
} catch {
|
|
21
|
-
return false;
|
|
22
|
-
}
|
|
23
|
-
}
|
package/src/utils/config.js
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import { promises as fs } from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
|
|
4
|
-
const AUTH_CONFIG_FILE = '.localhero_key';
|
|
5
|
-
const PROJECT_CONFIG_FILE = 'localhero.json';
|
|
6
|
-
const DEFAULT_PROJECT_CONFIG = {
|
|
7
|
-
schemaVersion: '1.0',
|
|
8
|
-
projectId: '',
|
|
9
|
-
sourceLocale: 'en',
|
|
10
|
-
outputLocales: [],
|
|
11
|
-
translationFiles: {
|
|
12
|
-
paths: [],
|
|
13
|
-
ignore: []
|
|
14
|
-
},
|
|
15
|
-
lastSyncedAt: null
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const defaultDeps = {
|
|
19
|
-
fs,
|
|
20
|
-
path,
|
|
21
|
-
process,
|
|
22
|
-
cwd: process.cwd
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export const configService = {
|
|
26
|
-
deps: { ...defaultDeps },
|
|
27
|
-
|
|
28
|
-
setDependencies(customDeps = {}) {
|
|
29
|
-
this.deps = { ...defaultDeps, ...customDeps };
|
|
30
|
-
return this;
|
|
31
|
-
},
|
|
32
|
-
|
|
33
|
-
configFilePath(basePath) {
|
|
34
|
-
const { path } = this.deps;
|
|
35
|
-
const baseDir = basePath || this.deps.cwd();
|
|
36
|
-
return path.join(baseDir, PROJECT_CONFIG_FILE);
|
|
37
|
-
},
|
|
38
|
-
|
|
39
|
-
async getAuthConfig(basePath) {
|
|
40
|
-
const { fs, path } = this.deps;
|
|
41
|
-
const baseDir = basePath || this.deps.cwd();
|
|
42
|
-
try {
|
|
43
|
-
const configPath = path.join(baseDir, AUTH_CONFIG_FILE);
|
|
44
|
-
const content = await fs.readFile(configPath, 'utf8');
|
|
45
|
-
return JSON.parse(content);
|
|
46
|
-
} catch {
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
|
-
|
|
51
|
-
async saveAuthConfig(config, basePath) {
|
|
52
|
-
const { fs, path } = this.deps;
|
|
53
|
-
const baseDir = basePath || this.deps.cwd();
|
|
54
|
-
const configPath = path.join(baseDir, AUTH_CONFIG_FILE);
|
|
55
|
-
await fs.writeFile(configPath, JSON.stringify(config, null, 2), {
|
|
56
|
-
mode: 0o600
|
|
57
|
-
});
|
|
58
|
-
},
|
|
59
|
-
|
|
60
|
-
async getProjectConfig(basePath) {
|
|
61
|
-
const { fs } = this.deps;
|
|
62
|
-
try {
|
|
63
|
-
const configPath = this.configFilePath(basePath);
|
|
64
|
-
const content = await fs.readFile(configPath, 'utf8');
|
|
65
|
-
const config = JSON.parse(content);
|
|
66
|
-
|
|
67
|
-
if (config.schemaVersion !== DEFAULT_PROJECT_CONFIG.schemaVersion) {
|
|
68
|
-
throw new Error(`Unsupported config schema version: ${config.schemaVersion}`);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return config;
|
|
72
|
-
} catch (error) {
|
|
73
|
-
if (error.code === 'ENOENT') {
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
76
|
-
throw error;
|
|
77
|
-
}
|
|
78
|
-
},
|
|
79
|
-
|
|
80
|
-
async saveProjectConfig(config, basePath) {
|
|
81
|
-
const { fs } = this.deps;
|
|
82
|
-
const configPath = this.configFilePath(basePath);
|
|
83
|
-
const configWithSchema = {
|
|
84
|
-
...DEFAULT_PROJECT_CONFIG,
|
|
85
|
-
...config,
|
|
86
|
-
schemaVersion: DEFAULT_PROJECT_CONFIG.schemaVersion
|
|
87
|
-
};
|
|
88
|
-
await fs.writeFile(configPath, JSON.stringify(configWithSchema, null, 2));
|
|
89
|
-
},
|
|
90
|
-
|
|
91
|
-
async validateProjectConfig(config) {
|
|
92
|
-
const required = ['projectId', 'sourceLocale', 'outputLocales', 'translationFiles'];
|
|
93
|
-
const missing = required.filter(key => !config[key]);
|
|
94
|
-
|
|
95
|
-
if (missing.length) {
|
|
96
|
-
throw new Error(`Missing required config: ${missing.join(', ')}. Run 'npx @localheroai/cli init' to set up your project.`);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (!Array.isArray(config.outputLocales) || config.outputLocales.length === 0) {
|
|
100
|
-
throw new Error('outputLocales must be an array with at least one locale');
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (!config.translationFiles.paths || !Array.isArray(config.translationFiles.paths)) {
|
|
104
|
-
throw new Error('translationFiles.paths must be an array of paths');
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return true;
|
|
108
|
-
},
|
|
109
|
-
|
|
110
|
-
async getValidProjectConfig(basePath) {
|
|
111
|
-
const config = await this.getProjectConfig(basePath);
|
|
112
|
-
if (!config) {
|
|
113
|
-
throw new Error('No project config found. Run `npx @localheroai/cli init` first');
|
|
114
|
-
}
|
|
115
|
-
await this.validateProjectConfig(config);
|
|
116
|
-
return config;
|
|
117
|
-
},
|
|
118
|
-
|
|
119
|
-
async updateLastSyncedAt(basePath) {
|
|
120
|
-
const config = await this.getValidProjectConfig(basePath);
|
|
121
|
-
config.lastSyncedAt = new Date().toISOString();
|
|
122
|
-
await this.saveProjectConfig(config, basePath);
|
|
123
|
-
return config;
|
|
124
|
-
}
|
|
125
|
-
};
|