langctl 0.1.2 → 0.3.0
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 +42 -0
- package/LICENSE +1 -1
- package/README.md +159 -1151
- package/dist/commands/auth.js +82 -24
- package/dist/commands/config.js +36 -25
- package/dist/commands/export.js +11 -112
- package/dist/commands/import.js +32 -164
- package/dist/commands/init.js +59 -80
- package/dist/commands/keys.js +145 -381
- package/dist/commands/org.js +45 -140
- package/dist/commands/projects.js +125 -390
- package/dist/commands/pull.js +78 -183
- package/dist/commands/push.js +125 -0
- package/dist/commands/team.js +68 -272
- package/dist/core/config.js +130 -0
- package/dist/core/errors.js +67 -0
- package/dist/core/files.js +38 -0
- package/dist/core/http.js +121 -0
- package/dist/core/output.js +96 -0
- package/dist/core/project.js +58 -0
- package/dist/core/prompts.js +56 -0
- package/dist/formats/index.js +285 -0
- package/dist/index.js +261 -508
- package/dist/version.js +4 -0
- package/package.json +22 -16
- package/.gitattributes +0 -2
- package/NEXT_PHASE.md +0 -618
- package/WORKING_INSTRUCTIONS.md +0 -1389
- package/dist/auth.d.ts +0 -37
- package/dist/auth.js +0 -112
- package/dist/commands/auth.d.ts +0 -3
- package/dist/commands/config.d.ts +0 -2
- package/dist/commands/debug.d.ts +0 -3
- package/dist/commands/debug.js +0 -57
- package/dist/commands/export.d.ts +0 -14
- package/dist/commands/import.d.ts +0 -11
- package/dist/commands/init.d.ts +0 -2
- package/dist/commands/keys.d.ts +0 -25
- package/dist/commands/org.d.ts +0 -13
- package/dist/commands/projects.d.ts +0 -30
- package/dist/commands/pull.d.ts +0 -9
- package/dist/commands/team.d.ts +0 -29
- package/dist/config.d.ts +0 -47
- package/dist/config.js +0 -69
- package/dist/exporters/index.d.ts +0 -36
- package/dist/exporters/index.js +0 -214
- package/dist/index.d.ts +0 -3
- package/dist/supabase.d.ts +0 -14
- package/dist/supabase.js +0 -30
- package/dist/utils/banner.d.ts +0 -9
- package/dist/utils/banner.js +0 -24
- package/translations/en.json +0 -5
- package/translations/en.xml +0 -9
- package/translations/fr.json +0 -4
- package/translations/fr.xml +0 -7
- package/translations/hi.json +0 -4
- package/translations/hi.xml +0 -7
- package/translations/ios/en.strings +0 -9
- package/translations/ios/fr.strings +0 -6
- package/translations/ios/hi.strings +0 -6
- package/translations/ios/ja.strings +0 -6
- package/translations/ja.json +0 -4
- package/translations/ja.xml +0 -7
|
@@ -1,402 +1,137 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
import { CliError, ExitCode, usageError } from '../core/errors.js';
|
|
3
|
+
import { getSession } from '../core/http.js';
|
|
4
|
+
import { log, printJson, runtime, table } from '../core/output.js';
|
|
5
|
+
import { confirm } from '../core/prompts.js';
|
|
6
|
+
import { getProject } from '../core/project.js';
|
|
7
|
+
import { splitList } from './pull.js';
|
|
6
8
|
export async function projectsListCommand() {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
return;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
console.log(chalk.red('✗ API key not found. Please run "langctl auth <api-key>" again.\n'));
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
const spinner = ora('Fetching projects...').start();
|
|
18
|
-
try {
|
|
19
|
-
// Call list-projects Edge Function
|
|
20
|
-
const response = await fetch(LIST_PROJECTS_URL, {
|
|
21
|
-
method: 'POST',
|
|
22
|
-
headers: {
|
|
23
|
-
'Content-Type': 'application/json',
|
|
24
|
-
'X-API-Key': apiKey
|
|
25
|
-
}
|
|
26
|
-
});
|
|
27
|
-
if (!response.ok) {
|
|
28
|
-
const errorData = await response.json().catch(() => ({}));
|
|
29
|
-
throw new Error(errorData.error || `HTTP ${response.status}: ${response.statusText}`);
|
|
30
|
-
}
|
|
31
|
-
const data = await response.json();
|
|
32
|
-
if (!data.success) {
|
|
33
|
-
throw new Error(data.error || 'Failed to fetch projects');
|
|
34
|
-
}
|
|
35
|
-
spinner.stop();
|
|
36
|
-
if (!data.projects || data.projects.length === 0) {
|
|
37
|
-
console.log(chalk.yellow('\nNo projects found. Create one in the dashboard first.\n'));
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
console.log(chalk.blue.bold(`\n📦 Projects (${data.projects.length})\n`));
|
|
41
|
-
data.projects.forEach((project) => {
|
|
42
|
-
console.log(chalk.white.bold(project.name));
|
|
43
|
-
console.log(chalk.gray(` Slug: ${project.slug}`));
|
|
44
|
-
if (project.description) {
|
|
45
|
-
console.log(chalk.gray(` Description: ${project.description}`));
|
|
46
|
-
}
|
|
47
|
-
console.log(chalk.gray(` Languages: ${project.languages.join(', ')}`));
|
|
48
|
-
console.log(chalk.gray(` Default: ${project.default_language}`));
|
|
49
|
-
if (project.modules && project.modules.length > 0) {
|
|
50
|
-
console.log(chalk.gray(` Modules: ${project.modules.join(', ')}`));
|
|
51
|
-
}
|
|
52
|
-
console.log('');
|
|
53
|
-
});
|
|
54
|
-
console.log(chalk.blue('To export translations, run:'));
|
|
55
|
-
console.log(chalk.white(` langctl export ${data.projects[0].slug} -l en\n`));
|
|
56
|
-
}
|
|
57
|
-
catch (error) {
|
|
58
|
-
spinner.fail(chalk.red('Failed to fetch projects'));
|
|
59
|
-
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Create new project
|
|
64
|
-
*/
|
|
65
|
-
export async function projectsCreateCommand(name, options) {
|
|
66
|
-
if (!isAuthenticated()) {
|
|
67
|
-
console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
const apiKey = getApiKey();
|
|
71
|
-
const spinner = ora('Creating project...').start();
|
|
72
|
-
try {
|
|
73
|
-
const languages = options.languages ? options.languages.split(',') : ['en'];
|
|
74
|
-
const defaultLanguage = options.defaultLanguage || languages[0];
|
|
75
|
-
const response = await fetch(MANAGE_PROJECTS_URL, {
|
|
76
|
-
method: 'POST',
|
|
77
|
-
headers: {
|
|
78
|
-
'Content-Type': 'application/json',
|
|
79
|
-
'X-API-Key': apiKey
|
|
80
|
-
},
|
|
81
|
-
body: JSON.stringify({
|
|
82
|
-
action: 'create',
|
|
83
|
-
name,
|
|
84
|
-
description: options.description,
|
|
85
|
-
languages,
|
|
86
|
-
defaultLanguage
|
|
87
|
-
})
|
|
88
|
-
});
|
|
89
|
-
const data = await response.json();
|
|
90
|
-
if (!data.success) {
|
|
91
|
-
throw new Error(data.error);
|
|
92
|
-
}
|
|
93
|
-
spinner.succeed(chalk.green(`Created project: ${name} (${data.project.slug})`));
|
|
94
|
-
console.log('');
|
|
95
|
-
}
|
|
96
|
-
catch (error) {
|
|
97
|
-
spinner.fail(chalk.red('Failed to create project'));
|
|
98
|
-
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
99
|
-
}
|
|
9
|
+
const session = await getSession();
|
|
10
|
+
const projects = await session.api.get(`/orgs/${session.orgId}/projects`);
|
|
11
|
+
if (runtime.json)
|
|
12
|
+
return printJson(projects);
|
|
13
|
+
if (projects.length === 0)
|
|
14
|
+
return log.info('No projects yet. Create one with "langctl projects create <name>".');
|
|
15
|
+
table(projects.map(p => [p.slug, p.name, p.languages.join(','), p.defaultLanguage]), ['SLUG', 'NAME', 'LANGUAGES', 'DEFAULT']);
|
|
100
16
|
}
|
|
101
|
-
/**
|
|
102
|
-
* Get project details
|
|
103
|
-
*/
|
|
104
17
|
export async function projectsGetCommand(slug) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
'Content-Type': 'application/json',
|
|
117
|
-
'X-API-Key': apiKey
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
const listData = await listResponse.json();
|
|
121
|
-
const project = listData.projects?.find((p) => p.slug === slug);
|
|
122
|
-
if (!project) {
|
|
123
|
-
spinner.fail(chalk.red(`Project "${slug}" not found`));
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
spinner.stop();
|
|
127
|
-
console.log(chalk.blue.bold('\n📦 Project Details\n'));
|
|
128
|
-
console.log(chalk.white.bold(project.name));
|
|
129
|
-
console.log(chalk.gray(`Slug: ${project.slug}`));
|
|
130
|
-
console.log(chalk.gray(`ID: ${project.id}`));
|
|
131
|
-
if (project.description) {
|
|
132
|
-
console.log(chalk.gray(`Description: ${project.description}`));
|
|
133
|
-
}
|
|
134
|
-
console.log(chalk.gray(`Languages: ${project.languages.join(', ')}`));
|
|
135
|
-
console.log(chalk.gray(`Default Language: ${project.default_language}`));
|
|
136
|
-
if (project.modules && project.modules.length > 0) {
|
|
137
|
-
console.log(chalk.gray(`Modules: ${project.modules.join(', ')}`));
|
|
138
|
-
}
|
|
139
|
-
console.log('');
|
|
140
|
-
}
|
|
141
|
-
catch (error) {
|
|
142
|
-
spinner.fail(chalk.red('Failed to fetch project'));
|
|
143
|
-
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
144
|
-
}
|
|
18
|
+
const session = await getSession();
|
|
19
|
+
const project = await getProject(session, slug);
|
|
20
|
+
if (runtime.json)
|
|
21
|
+
return printJson(project);
|
|
22
|
+
log.out(`${chalk.bold(project.name)} ${chalk.dim(`(${project.slug})`)}`);
|
|
23
|
+
if (project.description)
|
|
24
|
+
log.out(chalk.dim(project.description));
|
|
25
|
+
log.out(` languages ${project.languages.join(', ')} ${chalk.dim(`default ${project.defaultLanguage}`)}`);
|
|
26
|
+
if (project.modules?.length)
|
|
27
|
+
log.out(` modules ${project.modules.join(', ')}`);
|
|
28
|
+
log.out(` id ${project.id}`);
|
|
145
29
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if (!
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const
|
|
172
|
-
if (
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
console.log('');
|
|
194
|
-
}
|
|
195
|
-
catch (error) {
|
|
196
|
-
spinner.fail(chalk.red('Failed to update project'));
|
|
197
|
-
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
198
|
-
}
|
|
30
|
+
export async function projectsCreateCommand(name, opts) {
|
|
31
|
+
const session = await getSession();
|
|
32
|
+
const languages = splitList(opts.languages ?? 'en');
|
|
33
|
+
const defaultLanguage = opts.defaultLanguage ?? languages[0];
|
|
34
|
+
if (!languages.includes(defaultLanguage))
|
|
35
|
+
throw usageError(`Default language "${defaultLanguage}" must be one of --languages (${languages.join(',')}).`);
|
|
36
|
+
const project = await session.api.post(`/orgs/${session.orgId}/projects`, { name, description: opts.description, languages, defaultLanguage });
|
|
37
|
+
if (runtime.json)
|
|
38
|
+
return printJson(project);
|
|
39
|
+
log.success(`Created ${chalk.bold(project.name)} — slug ${chalk.cyan(project.slug)}`);
|
|
40
|
+
}
|
|
41
|
+
export async function projectsUpdateCommand(slug, opts) {
|
|
42
|
+
const session = await getSession();
|
|
43
|
+
const project = await getProject(session, slug);
|
|
44
|
+
const body = {};
|
|
45
|
+
if (opts.name)
|
|
46
|
+
body.name = opts.name;
|
|
47
|
+
if (opts.description !== undefined)
|
|
48
|
+
body.description = opts.description;
|
|
49
|
+
if (opts.defaultLanguage)
|
|
50
|
+
body.defaultLanguage = opts.defaultLanguage;
|
|
51
|
+
let toAdd = [];
|
|
52
|
+
let toRemove = [];
|
|
53
|
+
if (opts.languages) {
|
|
54
|
+
const desired = splitList(opts.languages);
|
|
55
|
+
const effectiveDefault = opts.defaultLanguage ?? project.defaultLanguage;
|
|
56
|
+
if (!desired.includes(effectiveDefault))
|
|
57
|
+
throw usageError(`--languages must include the default language "${effectiveDefault}".`);
|
|
58
|
+
toAdd = desired.filter(l => !project.languages.includes(l));
|
|
59
|
+
toRemove = project.languages.filter(l => !desired.includes(l));
|
|
60
|
+
}
|
|
61
|
+
if (Object.keys(body).length === 0 && !toAdd.length && !toRemove.length)
|
|
62
|
+
throw usageError('Nothing to update.');
|
|
63
|
+
if (toRemove.length && !(await confirm(`Remove ${toRemove.join(', ')} from ${project.slug} and delete all of their translations?`))) {
|
|
64
|
+
throw new CliError('Cancelled.', ExitCode.Error);
|
|
65
|
+
}
|
|
66
|
+
// Add new languages first so a new default language exists before it is set
|
|
67
|
+
for (const code of toAdd)
|
|
68
|
+
await session.api.post(`/orgs/${session.orgId}/projects/${project.id}/languages`, { code });
|
|
69
|
+
if (Object.keys(body).length)
|
|
70
|
+
await session.api.patch(`/orgs/${session.orgId}/projects/${project.id}`, body);
|
|
71
|
+
for (const code of toRemove)
|
|
72
|
+
await session.api.delete(`/orgs/${session.orgId}/projects/${project.id}/languages/${encodeURIComponent(code)}`);
|
|
73
|
+
const updated = await getProject(session, slug);
|
|
74
|
+
if (runtime.json)
|
|
75
|
+
return printJson(updated);
|
|
76
|
+
log.success(`Updated ${chalk.bold(updated.slug)}${toAdd.length ? ` +${toAdd.join(',')}` : ''}${toRemove.length ? ` -${toRemove.join(',')}` : ''}`);
|
|
199
77
|
}
|
|
200
|
-
/**
|
|
201
|
-
* Delete project
|
|
202
|
-
*/
|
|
203
78
|
export async function projectsDeleteCommand(slug) {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const listResponse = await fetch(LIST_PROJECTS_URL, {
|
|
213
|
-
method: 'POST',
|
|
214
|
-
headers: {
|
|
215
|
-
'Content-Type': 'application/json',
|
|
216
|
-
'X-API-Key': apiKey
|
|
217
|
-
}
|
|
218
|
-
});
|
|
219
|
-
const listData = await listResponse.json();
|
|
220
|
-
const project = listData.projects?.find((p) => p.slug === slug);
|
|
221
|
-
if (!project) {
|
|
222
|
-
spinner.fail(chalk.red(`Project "${slug}" not found`));
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
const response = await fetch(MANAGE_PROJECTS_URL, {
|
|
226
|
-
method: 'POST',
|
|
227
|
-
headers: {
|
|
228
|
-
'Content-Type': 'application/json',
|
|
229
|
-
'X-API-Key': apiKey
|
|
230
|
-
},
|
|
231
|
-
body: JSON.stringify({
|
|
232
|
-
action: 'delete',
|
|
233
|
-
projectId: project.id
|
|
234
|
-
})
|
|
235
|
-
});
|
|
236
|
-
const data = await response.json();
|
|
237
|
-
if (!data.success) {
|
|
238
|
-
throw new Error(data.error);
|
|
239
|
-
}
|
|
240
|
-
spinner.succeed(chalk.green(`Deleted project: ${slug}`));
|
|
241
|
-
console.log('');
|
|
242
|
-
}
|
|
243
|
-
catch (error) {
|
|
244
|
-
spinner.fail(chalk.red('Failed to delete project'));
|
|
245
|
-
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
246
|
-
}
|
|
79
|
+
const session = await getSession();
|
|
80
|
+
const project = await getProject(session, slug);
|
|
81
|
+
if (!(await confirm(`Delete project "${project.slug}" and all of its translation keys?`)))
|
|
82
|
+
throw new CliError('Cancelled.', ExitCode.Error);
|
|
83
|
+
await session.api.delete(`/orgs/${session.orgId}/projects/${project.id}`);
|
|
84
|
+
if (runtime.json)
|
|
85
|
+
return printJson({ deleted: project.slug });
|
|
86
|
+
log.success(`Deleted project ${chalk.bold(project.slug)}`);
|
|
247
87
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
'X-API-Key': apiKey
|
|
265
|
-
}
|
|
266
|
-
});
|
|
267
|
-
const listData = await listResponse.json();
|
|
268
|
-
const project = listData.projects?.find((p) => p.slug === slug);
|
|
269
|
-
if (!project) {
|
|
270
|
-
spinner.fail(chalk.red(`Project "${slug}" not found`));
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
const response = await fetch(MANAGE_PROJECTS_URL, {
|
|
274
|
-
method: 'POST',
|
|
275
|
-
headers: {
|
|
276
|
-
'Content-Type': 'application/json',
|
|
277
|
-
'X-API-Key': apiKey
|
|
278
|
-
},
|
|
279
|
-
body: JSON.stringify({
|
|
280
|
-
action: 'add-language',
|
|
281
|
-
projectId: project.id,
|
|
282
|
-
language
|
|
283
|
-
})
|
|
284
|
-
});
|
|
285
|
-
const data = await response.json();
|
|
286
|
-
if (!data.success) {
|
|
287
|
-
throw new Error(data.error);
|
|
288
|
-
}
|
|
289
|
-
spinner.succeed(chalk.green(data.message));
|
|
290
|
-
console.log('');
|
|
291
|
-
}
|
|
292
|
-
catch (error) {
|
|
293
|
-
spinner.fail(chalk.red('Failed to add language'));
|
|
294
|
-
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
295
|
-
}
|
|
88
|
+
export async function projectsAddLanguageCommand(slug, codes) {
|
|
89
|
+
const session = await getSession();
|
|
90
|
+
const project = await getProject(session, slug);
|
|
91
|
+
const added = [];
|
|
92
|
+
for (const code of codes) {
|
|
93
|
+
if (project.languages.includes(code)) {
|
|
94
|
+
log.info(`${code} is already in ${project.slug}`);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
await session.api.post(`/orgs/${session.orgId}/projects/${project.id}/languages`, { code });
|
|
98
|
+
added.push(code);
|
|
99
|
+
}
|
|
100
|
+
if (runtime.json)
|
|
101
|
+
return printJson({ project: project.slug, added });
|
|
102
|
+
if (added.length)
|
|
103
|
+
log.success(`Added ${added.join(', ')} to ${chalk.bold(project.slug)}`);
|
|
296
104
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
if (
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
'Content-Type': 'application/json',
|
|
313
|
-
'X-API-Key': apiKey
|
|
314
|
-
}
|
|
315
|
-
});
|
|
316
|
-
const listData = await listResponse.json();
|
|
317
|
-
const project = listData.projects?.find((p) => p.slug === slug);
|
|
318
|
-
if (!project) {
|
|
319
|
-
spinner.fail(chalk.red(`Project "${slug}" not found`));
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
322
|
-
const response = await fetch(MANAGE_PROJECTS_URL, {
|
|
323
|
-
method: 'POST',
|
|
324
|
-
headers: {
|
|
325
|
-
'Content-Type': 'application/json',
|
|
326
|
-
'X-API-Key': apiKey
|
|
327
|
-
},
|
|
328
|
-
body: JSON.stringify({
|
|
329
|
-
action: 'remove-language',
|
|
330
|
-
projectId: project.id,
|
|
331
|
-
language
|
|
332
|
-
})
|
|
333
|
-
});
|
|
334
|
-
const data = await response.json();
|
|
335
|
-
if (!data.success) {
|
|
336
|
-
throw new Error(data.error);
|
|
337
|
-
}
|
|
338
|
-
spinner.succeed(chalk.green(data.message));
|
|
339
|
-
console.log('');
|
|
340
|
-
}
|
|
341
|
-
catch (error) {
|
|
342
|
-
spinner.fail(chalk.red('Failed to remove language'));
|
|
343
|
-
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
344
|
-
}
|
|
105
|
+
export async function projectsRemoveLanguageCommand(slug, codes) {
|
|
106
|
+
const session = await getSession();
|
|
107
|
+
const project = await getProject(session, slug);
|
|
108
|
+
const missing = codes.filter(c => !project.languages.includes(c));
|
|
109
|
+
if (missing.length)
|
|
110
|
+
throw usageError(`Not in ${project.slug}: ${missing.join(', ')}`);
|
|
111
|
+
if (codes.includes(project.defaultLanguage))
|
|
112
|
+
throw usageError(`"${project.defaultLanguage}" is the default language and can't be removed.`);
|
|
113
|
+
if (!(await confirm(`Remove ${codes.join(', ')} from ${project.slug} and delete all of their translations?`)))
|
|
114
|
+
throw new CliError('Cancelled.', ExitCode.Error);
|
|
115
|
+
for (const code of codes)
|
|
116
|
+
await session.api.delete(`/orgs/${session.orgId}/projects/${project.id}/languages/${encodeURIComponent(code)}`);
|
|
117
|
+
if (runtime.json)
|
|
118
|
+
return printJson({ project: project.slug, removed: codes });
|
|
119
|
+
log.success(`Removed ${codes.join(', ')} from ${chalk.bold(project.slug)}`);
|
|
345
120
|
}
|
|
346
|
-
/**
|
|
347
|
-
* Get project statistics
|
|
348
|
-
*/
|
|
349
121
|
export async function projectsStatsCommand(slug) {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
const listData = await listResponse.json();
|
|
366
|
-
const project = listData.projects?.find((p) => p.slug === slug);
|
|
367
|
-
if (!project) {
|
|
368
|
-
spinner.fail(chalk.red(`Project "${slug}" not found`));
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
371
|
-
const response = await fetch(MANAGE_PROJECTS_URL, {
|
|
372
|
-
method: 'POST',
|
|
373
|
-
headers: {
|
|
374
|
-
'Content-Type': 'application/json',
|
|
375
|
-
'X-API-Key': apiKey
|
|
376
|
-
},
|
|
377
|
-
body: JSON.stringify({
|
|
378
|
-
action: 'stats',
|
|
379
|
-
projectId: project.id
|
|
380
|
-
})
|
|
381
|
-
});
|
|
382
|
-
const data = await response.json();
|
|
383
|
-
if (!data.success) {
|
|
384
|
-
throw new Error(data.error);
|
|
385
|
-
}
|
|
386
|
-
spinner.stop();
|
|
387
|
-
console.log(chalk.blue.bold('\n📊 Project Statistics\n'));
|
|
388
|
-
console.log(chalk.white(`Total Keys: ${data.stats.total_keys}`));
|
|
389
|
-
console.log(chalk.green(`Published Keys: ${data.stats.published_keys}`));
|
|
390
|
-
console.log(chalk.yellow(`Unpublished Keys: ${data.stats.unpublished_keys}`));
|
|
391
|
-
console.log(chalk.white(`Modules: ${data.stats.modules}`));
|
|
392
|
-
if (data.stats.module_names && data.stats.module_names.length > 0) {
|
|
393
|
-
console.log(chalk.gray(` ${data.stats.module_names.join(', ')}`));
|
|
394
|
-
}
|
|
395
|
-
console.log('');
|
|
396
|
-
}
|
|
397
|
-
catch (error) {
|
|
398
|
-
spinner.fail(chalk.red('Failed to fetch statistics'));
|
|
399
|
-
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
400
|
-
}
|
|
122
|
+
const session = await getSession();
|
|
123
|
+
const project = await getProject(session, slug);
|
|
124
|
+
const [stats, snapshot] = await Promise.all([
|
|
125
|
+
session.api.get(`/orgs/${session.orgId}/projects/${project.id}/stats`),
|
|
126
|
+
session.api.get(`/orgs/${session.orgId}/projects/${project.id}/export`, { publishedOnly: 'false' }),
|
|
127
|
+
]);
|
|
128
|
+
const total = snapshot.keys.length;
|
|
129
|
+
const coverage = project.languages.map(lang => {
|
|
130
|
+
const translated = snapshot.keys.filter(k => k.translations[lang]).length;
|
|
131
|
+
return { language: lang, translated, missing: total - translated, percent: total ? Math.round((translated / total) * 100) : 100 };
|
|
132
|
+
});
|
|
133
|
+
if (runtime.json)
|
|
134
|
+
return printJson({ ...stats, coverage });
|
|
135
|
+
log.out(`${chalk.bold(project.slug)} ${stats.totalKeys} keys ${chalk.green(`${stats.publishedKeys} published`)} ${chalk.yellow(`${stats.unpublishedKeys} draft`)}`);
|
|
136
|
+
table(coverage.map(c => [c.language, `${c.translated}/${total}`, c.missing ? chalk.yellow(`${c.percent}%`) : chalk.green('100%')]), ['LANG', 'TRANSLATED', 'DONE']);
|
|
401
137
|
}
|
|
402
|
-
//# sourceMappingURL=projects.js.map
|