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.
Files changed (63) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/LICENSE +1 -1
  3. package/README.md +159 -1151
  4. package/dist/commands/auth.js +82 -24
  5. package/dist/commands/config.js +36 -25
  6. package/dist/commands/export.js +11 -112
  7. package/dist/commands/import.js +32 -164
  8. package/dist/commands/init.js +59 -80
  9. package/dist/commands/keys.js +145 -381
  10. package/dist/commands/org.js +45 -140
  11. package/dist/commands/projects.js +125 -390
  12. package/dist/commands/pull.js +78 -183
  13. package/dist/commands/push.js +125 -0
  14. package/dist/commands/team.js +68 -272
  15. package/dist/core/config.js +130 -0
  16. package/dist/core/errors.js +67 -0
  17. package/dist/core/files.js +38 -0
  18. package/dist/core/http.js +121 -0
  19. package/dist/core/output.js +96 -0
  20. package/dist/core/project.js +58 -0
  21. package/dist/core/prompts.js +56 -0
  22. package/dist/formats/index.js +285 -0
  23. package/dist/index.js +261 -508
  24. package/dist/version.js +4 -0
  25. package/package.json +22 -16
  26. package/.gitattributes +0 -2
  27. package/NEXT_PHASE.md +0 -618
  28. package/WORKING_INSTRUCTIONS.md +0 -1389
  29. package/dist/auth.d.ts +0 -37
  30. package/dist/auth.js +0 -112
  31. package/dist/commands/auth.d.ts +0 -3
  32. package/dist/commands/config.d.ts +0 -2
  33. package/dist/commands/debug.d.ts +0 -3
  34. package/dist/commands/debug.js +0 -57
  35. package/dist/commands/export.d.ts +0 -14
  36. package/dist/commands/import.d.ts +0 -11
  37. package/dist/commands/init.d.ts +0 -2
  38. package/dist/commands/keys.d.ts +0 -25
  39. package/dist/commands/org.d.ts +0 -13
  40. package/dist/commands/projects.d.ts +0 -30
  41. package/dist/commands/pull.d.ts +0 -9
  42. package/dist/commands/team.d.ts +0 -29
  43. package/dist/config.d.ts +0 -47
  44. package/dist/config.js +0 -69
  45. package/dist/exporters/index.d.ts +0 -36
  46. package/dist/exporters/index.js +0 -214
  47. package/dist/index.d.ts +0 -3
  48. package/dist/supabase.d.ts +0 -14
  49. package/dist/supabase.js +0 -30
  50. package/dist/utils/banner.d.ts +0 -9
  51. package/dist/utils/banner.js +0 -24
  52. package/translations/en.json +0 -5
  53. package/translations/en.xml +0 -9
  54. package/translations/fr.json +0 -4
  55. package/translations/fr.xml +0 -7
  56. package/translations/hi.json +0 -4
  57. package/translations/hi.xml +0 -7
  58. package/translations/ios/en.strings +0 -9
  59. package/translations/ios/fr.strings +0 -6
  60. package/translations/ios/hi.strings +0 -6
  61. package/translations/ios/ja.strings +0 -6
  62. package/translations/ja.json +0 -4
  63. package/translations/ja.xml +0 -7
@@ -1,402 +1,137 @@
1
1
  import chalk from 'chalk';
2
- import ora from 'ora';
3
- import { isAuthenticated, getApiKey } from '../auth.js';
4
- const LIST_PROJECTS_URL = 'https://bcgnmvkgkbhbxzzflwdb.supabase.co/functions/v1/list-projects';
5
- const MANAGE_PROJECTS_URL = 'https://bcgnmvkgkbhbxzzflwdb.supabase.co/functions/v1/manage-projects';
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
- // Check authentication
8
- if (!isAuthenticated()) {
9
- console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
10
- return;
11
- }
12
- const apiKey = getApiKey();
13
- if (!apiKey) {
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
- if (!isAuthenticated()) {
106
- console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
107
- return;
108
- }
109
- const apiKey = getApiKey();
110
- const spinner = ora('Fetching project...').start();
111
- try {
112
- // Get project from list first
113
- const listResponse = await fetch(LIST_PROJECTS_URL, {
114
- method: 'POST',
115
- headers: {
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
- * Update project
148
- */
149
- export async function projectsUpdateCommand(slug, options) {
150
- if (!isAuthenticated()) {
151
- console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
152
- return;
153
- }
154
- const apiKey = getApiKey();
155
- const spinner = ora('Updating project...').start();
156
- try {
157
- // Get project ID from slug
158
- const listResponse = await fetch(LIST_PROJECTS_URL, {
159
- method: 'POST',
160
- headers: {
161
- 'Content-Type': 'application/json',
162
- 'X-API-Key': apiKey
163
- }
164
- });
165
- const listData = await listResponse.json();
166
- const project = listData.projects?.find((p) => p.slug === slug);
167
- if (!project) {
168
- spinner.fail(chalk.red(`Project "${slug}" not found`));
169
- return;
170
- }
171
- const updateData = { projectId: project.id, action: 'update' };
172
- if (options.name)
173
- updateData.name = options.name;
174
- if (options.description !== undefined)
175
- updateData.description = options.description;
176
- if (options.languages)
177
- updateData.languages = options.languages.split(',');
178
- if (options.defaultLanguage)
179
- updateData.defaultLanguage = options.defaultLanguage;
180
- const response = await fetch(MANAGE_PROJECTS_URL, {
181
- method: 'POST',
182
- headers: {
183
- 'Content-Type': 'application/json',
184
- 'X-API-Key': apiKey
185
- },
186
- body: JSON.stringify(updateData)
187
- });
188
- const data = await response.json();
189
- if (!data.success) {
190
- throw new Error(data.error);
191
- }
192
- spinner.succeed(chalk.green(`Updated project: ${slug}`));
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
- if (!isAuthenticated()) {
205
- console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
206
- return;
207
- }
208
- const apiKey = getApiKey();
209
- const spinner = ora('Deleting project...').start();
210
- try {
211
- // Get project ID from slug
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
- * Add language to project
250
- */
251
- export async function projectsAddLanguageCommand(slug, language) {
252
- if (!isAuthenticated()) {
253
- console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
254
- return;
255
- }
256
- const apiKey = getApiKey();
257
- const spinner = ora(`Adding language ${language}...`).start();
258
- try {
259
- // Get project ID from slug
260
- const listResponse = await fetch(LIST_PROJECTS_URL, {
261
- method: 'POST',
262
- headers: {
263
- 'Content-Type': 'application/json',
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
- * Remove language from project
299
- */
300
- export async function projectsRemoveLanguageCommand(slug, language) {
301
- if (!isAuthenticated()) {
302
- console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
303
- return;
304
- }
305
- const apiKey = getApiKey();
306
- const spinner = ora(`Removing language ${language}...`).start();
307
- try {
308
- // Get project ID from slug
309
- const listResponse = await fetch(LIST_PROJECTS_URL, {
310
- method: 'POST',
311
- headers: {
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
- if (!isAuthenticated()) {
351
- console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
352
- return;
353
- }
354
- const apiKey = getApiKey();
355
- const spinner = ora('Fetching statistics...').start();
356
- try {
357
- // Get project ID from slug
358
- const listResponse = await fetch(LIST_PROJECTS_URL, {
359
- method: 'POST',
360
- headers: {
361
- 'Content-Type': 'application/json',
362
- 'X-API-Key': apiKey
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