langctl 0.2.0 → 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/README.md +155 -1188
- package/dist/commands/auth.js +85 -19
- package/dist/commands/config.js +36 -26
- package/dist/commands/export.js +11 -122
- package/dist/commands/import.js +32 -143
- package/dist/commands/init.js +61 -72
- package/dist/commands/keys.js +145 -279
- package/dist/commands/org.js +45 -111
- package/dist/commands/projects.js +124 -312
- package/dist/commands/pull.js +80 -130
- package/dist/commands/push.js +125 -0
- package/dist/commands/team.js +68 -257
- 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 -15
- package/.gitattributes +0 -2
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -26
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
- package/CONTRIBUTING.md +0 -33
- package/dist/api.d.ts +0 -20
- package/dist/api.js +0 -75
- package/dist/auth.d.ts +0 -38
- package/dist/auth.js +0 -106
- 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 -61
- package/dist/commands/export.d.ts +0 -13
- package/dist/commands/import.d.ts +0 -12
- 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 -33
- package/dist/commands/pull.d.ts +0 -9
- package/dist/commands/team.d.ts +0 -29
- package/dist/config.d.ts +0 -48
- 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/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
package/dist/commands/keys.js
CHANGED
|
@@ -1,294 +1,160 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Find a key by exact name within a project (search + exact match)
|
|
15
|
-
*/
|
|
16
|
-
async function findKeyByName(orgId, projectId, keyName) {
|
|
17
|
-
const api = getApiClient();
|
|
18
|
-
const result = await api.get(`/orgs/${orgId}/projects/${projectId}/keys`, {
|
|
19
|
-
search: keyName,
|
|
20
|
-
pageSize: '100'
|
|
21
|
-
});
|
|
22
|
-
const key = result.data?.find((k) => k.key === keyName);
|
|
23
|
-
return key || null;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* List translation keys
|
|
27
|
-
*/
|
|
28
|
-
export async function listKeysCommand(projectSlug, options) {
|
|
29
|
-
if (!isAuthenticated()) {
|
|
30
|
-
console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
|
|
31
|
-
process.exitCode = 1;
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
const orgId = config.get('organizationId');
|
|
35
|
-
if (!orgId) {
|
|
36
|
-
console.log(chalk.red('✗ Organization ID not found. Please run "langctl auth <api-key>" again.\n'));
|
|
37
|
-
process.exitCode = 1;
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
const spinner = ora('Fetching keys...').start();
|
|
2
|
+
import { CliError, ExitCode, usageError } from '../core/errors.js';
|
|
3
|
+
import { getSession } from '../core/http.js';
|
|
4
|
+
import { log, printJson, runtime, spinner, table } from '../core/output.js';
|
|
5
|
+
import { confirm } from '../core/prompts.js';
|
|
6
|
+
import { assertLanguages, getProject, listAllKeys, projectSlugFrom, requireKey } from '../core/project.js';
|
|
7
|
+
export async function listKeysCommand(projectArg, opts) {
|
|
8
|
+
const session = await getSession();
|
|
9
|
+
const project = await getProject(session, projectSlugFrom(projectArg));
|
|
10
|
+
const spin = spinner('Fetching keys…');
|
|
11
|
+
let keys;
|
|
41
12
|
try {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const page = Math.floor(offset / limit) + 1;
|
|
47
|
-
const pageSize = Math.min(100, limit + skipWithinPage);
|
|
48
|
-
const api = getApiClient();
|
|
49
|
-
const params = {
|
|
50
|
-
page: String(page),
|
|
51
|
-
pageSize: String(pageSize)
|
|
52
|
-
};
|
|
53
|
-
if (options.module)
|
|
54
|
-
params.module = options.module;
|
|
55
|
-
if (options.search)
|
|
56
|
-
params.search = options.search;
|
|
57
|
-
if (options.published)
|
|
58
|
-
params.published = 'true';
|
|
59
|
-
const result = await api.get(`/orgs/${orgId}/projects/${project.id}/keys`, params);
|
|
60
|
-
spinner.stop();
|
|
61
|
-
let keys = result.data || [];
|
|
62
|
-
if (skipWithinPage > 0) {
|
|
63
|
-
keys = keys.slice(skipWithinPage);
|
|
64
|
-
}
|
|
65
|
-
keys = keys.slice(0, limit);
|
|
66
|
-
const total = result.pagination?.total;
|
|
67
|
-
if (!keys || keys.length === 0) {
|
|
68
|
-
console.log(chalk.yellow('\nNo keys found\n'));
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
console.log(chalk.blue.bold(`\n🔑 Translation Keys (${keys.length}${total ? ` of ${total}` : ''})\n`));
|
|
72
|
-
keys.forEach((key) => {
|
|
73
|
-
console.log(chalk.white.bold(key.key));
|
|
74
|
-
if (key.description) {
|
|
75
|
-
console.log(chalk.gray(` Description: ${key.description}`));
|
|
76
|
-
}
|
|
77
|
-
if (key.module) {
|
|
78
|
-
console.log(chalk.gray(` Module: ${key.module}`));
|
|
79
|
-
}
|
|
80
|
-
console.log(chalk.gray(` Published: ${key.published ? chalk.green('Yes') : chalk.yellow('No')}`));
|
|
81
|
-
const langs = Object.keys(key.translations || {});
|
|
82
|
-
if (langs.length > 0) {
|
|
83
|
-
console.log(chalk.gray(` Languages: ${langs.join(', ')}`));
|
|
84
|
-
}
|
|
85
|
-
console.log('');
|
|
13
|
+
keys = await listAllKeys(session, project, {
|
|
14
|
+
module: opts.module,
|
|
15
|
+
search: opts.search,
|
|
16
|
+
published: opts.published ? true : opts.drafts ? false : undefined,
|
|
86
17
|
});
|
|
87
18
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
91
|
-
process.exitCode = 1;
|
|
19
|
+
finally {
|
|
20
|
+
spin.stop();
|
|
92
21
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
return;
|
|
22
|
+
const limit = opts.limit ? Number(opts.limit) : undefined;
|
|
23
|
+
if (limit !== undefined && (!Number.isInteger(limit) || limit < 1))
|
|
24
|
+
throw usageError('--limit must be a positive integer');
|
|
25
|
+
const shown = limit ? keys.slice(0, limit) : keys;
|
|
26
|
+
if (runtime.json) {
|
|
27
|
+
return printJson(shown.map(k => ({
|
|
28
|
+
key: k.key, published: k.published, module: k.module ?? null, description: k.description ?? null, translations: k.translations,
|
|
29
|
+
})));
|
|
102
30
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
console.log(chalk.red('✗ Organization ID not found. Please run "langctl auth <api-key>" again.\n'));
|
|
106
|
-
process.exitCode = 1;
|
|
31
|
+
if (shown.length === 0) {
|
|
32
|
+
log.info('No keys match.');
|
|
107
33
|
return;
|
|
108
34
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
spinner.stop();
|
|
118
|
-
console.log(chalk.blue.bold('\n🔑 Translation Key Details\n'));
|
|
119
|
-
console.log(chalk.white.bold(`Key: ${key.key}`));
|
|
120
|
-
console.log(chalk.gray(`ID: ${key.id}`));
|
|
121
|
-
if (key.description) {
|
|
122
|
-
console.log(chalk.gray(`Description: ${key.description}`));
|
|
123
|
-
}
|
|
124
|
-
if (key.module) {
|
|
125
|
-
console.log(chalk.gray(`Module: ${key.module}`));
|
|
126
|
-
}
|
|
127
|
-
console.log(chalk.gray(`Published: ${key.published ? chalk.green('Yes') : chalk.yellow('No')}`));
|
|
128
|
-
console.log(chalk.blue.bold('\nTranslations:'));
|
|
129
|
-
Object.entries(key.translations || {}).forEach(([lang, value]) => {
|
|
130
|
-
console.log(chalk.white(` ${lang}: ${value}`));
|
|
131
|
-
});
|
|
132
|
-
console.log('');
|
|
133
|
-
}
|
|
134
|
-
catch (error) {
|
|
135
|
-
spinner.fail(chalk.red('Failed to get key'));
|
|
136
|
-
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
137
|
-
process.exitCode = 1;
|
|
138
|
-
}
|
|
35
|
+
table(shown.map(k => [
|
|
36
|
+
k.key,
|
|
37
|
+
k.published ? chalk.green('published') : chalk.yellow('draft'),
|
|
38
|
+
k.module ?? '',
|
|
39
|
+
`${project.languages.filter(l => k.translations?.[l]).length}/${project.languages.length}`,
|
|
40
|
+
]), ['KEY', 'STATUS', 'MODULE', 'LANGS']);
|
|
41
|
+
if (limit && keys.length > limit)
|
|
42
|
+
log.info(chalk.dim(`Showing ${limit} of ${keys.length}. Use --json for scripting.`));
|
|
139
43
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
const spinner = ora('Creating key...').start();
|
|
156
|
-
try {
|
|
157
|
-
const project = await resolveProject(orgId, projectSlug);
|
|
158
|
-
// Build translations object from --value-* options
|
|
159
|
-
const translations = {};
|
|
160
|
-
Object.keys(options).forEach(opt => {
|
|
161
|
-
if (opt.startsWith('value')) {
|
|
162
|
-
const lang = opt.replace('value', '').toLowerCase();
|
|
163
|
-
if (lang) {
|
|
164
|
-
translations[lang] = options[opt];
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
const api = getApiClient();
|
|
169
|
-
await api.post(`/orgs/${orgId}/projects/${project.id}/keys`, {
|
|
170
|
-
key: keyName,
|
|
171
|
-
translations: Object.keys(translations).length > 0 ? translations : undefined,
|
|
172
|
-
description: options.description,
|
|
173
|
-
module: options.module,
|
|
174
|
-
tags: options.tags ? options.tags.split(',') : undefined
|
|
175
|
-
});
|
|
176
|
-
spinner.succeed(chalk.green(`Created key: ${keyName}`));
|
|
177
|
-
console.log('');
|
|
178
|
-
}
|
|
179
|
-
catch (error) {
|
|
180
|
-
spinner.fail(chalk.red('Failed to create key'));
|
|
181
|
-
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
182
|
-
process.exitCode = 1;
|
|
44
|
+
export async function getKeyCommand(projectArg, keyName) {
|
|
45
|
+
const session = await getSession();
|
|
46
|
+
const project = await getProject(session, projectArg);
|
|
47
|
+
const key = await requireKey(session, project, keyName);
|
|
48
|
+
if (runtime.json)
|
|
49
|
+
return printJson(key);
|
|
50
|
+
log.out(chalk.bold(key.key) + ' ' + (key.published ? chalk.green('published') : chalk.yellow('draft')));
|
|
51
|
+
if (key.description)
|
|
52
|
+
log.out(chalk.dim(key.description));
|
|
53
|
+
if (key.module)
|
|
54
|
+
log.out(chalk.dim(`module: ${key.module}`));
|
|
55
|
+
for (const lang of project.languages) {
|
|
56
|
+
const v = key.translations?.[lang];
|
|
57
|
+
log.out(` ${lang.padEnd(6)} ${v === undefined || v === '' ? chalk.dim('— missing') : v}`);
|
|
183
58
|
}
|
|
184
59
|
}
|
|
185
|
-
/**
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const spinner = ora('Deleting key...').start();
|
|
201
|
-
try {
|
|
202
|
-
const project = await resolveProject(orgId, projectSlug);
|
|
203
|
-
const key = await findKeyByName(orgId, project.id, keyName);
|
|
204
|
-
if (!key) {
|
|
205
|
-
spinner.fail(chalk.red(`Key "${keyName}" not found`));
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
const api = getApiClient();
|
|
209
|
-
await api.delete(`/orgs/${orgId}/projects/${project.id}/keys/${key.id}`);
|
|
210
|
-
spinner.succeed(chalk.green(`Deleted key: ${keyName}`));
|
|
211
|
-
console.log('');
|
|
212
|
-
}
|
|
213
|
-
catch (error) {
|
|
214
|
-
spinner.fail(chalk.red('Failed to delete key'));
|
|
215
|
-
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
216
|
-
process.exitCode = 1;
|
|
217
|
-
}
|
|
60
|
+
/** Collect `--value en=Hello --value es=Hola` plus the legacy `--value-en` style flags. */
|
|
61
|
+
function collectValues(opts) {
|
|
62
|
+
const out = {};
|
|
63
|
+
for (const pair of opts.value ?? []) {
|
|
64
|
+
const i = pair.indexOf('=');
|
|
65
|
+
if (i < 1)
|
|
66
|
+
throw usageError(`--value expects LANG=TEXT, got "${pair}"`, 'Example: --value en="Welcome" --value es="Bienvenido"');
|
|
67
|
+
out[pair.slice(0, i).trim()] = pair.slice(i + 1);
|
|
68
|
+
}
|
|
69
|
+
for (const lang of ['en', 'es', 'fr', 'de']) {
|
|
70
|
+
const legacy = opts[`value${lang[0].toUpperCase()}${lang[1]}`];
|
|
71
|
+
if (typeof legacy === 'string')
|
|
72
|
+
out[lang] = legacy;
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
218
75
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
try {
|
|
236
|
-
const project = await resolveProject(orgId, projectSlug);
|
|
237
|
-
const key = await findKeyByName(orgId, project.id, keyName);
|
|
238
|
-
if (!key) {
|
|
239
|
-
spinner.fail(chalk.red(`Key "${keyName}" not found`));
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
const api = getApiClient();
|
|
243
|
-
await api.patch(`/orgs/${orgId}/projects/${project.id}/keys/${key.id}/translations/${options.language}`, { value: options.value });
|
|
244
|
-
spinner.succeed(chalk.green(`Updated ${options.language} translation for ${keyName}`));
|
|
245
|
-
console.log('');
|
|
246
|
-
}
|
|
247
|
-
catch (error) {
|
|
248
|
-
spinner.fail(chalk.red('Failed to update translation'));
|
|
249
|
-
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
250
|
-
process.exitCode = 1;
|
|
251
|
-
}
|
|
76
|
+
export async function createKeyCommand(projectArg, keyName, opts) {
|
|
77
|
+
const session = await getSession();
|
|
78
|
+
const project = await getProject(session, projectArg);
|
|
79
|
+
const translations = collectValues(opts);
|
|
80
|
+
assertLanguages(project, Object.keys(translations));
|
|
81
|
+
const key = await session.api.post(`/orgs/${session.orgId}/projects/${project.id}/keys`, {
|
|
82
|
+
key: keyName,
|
|
83
|
+
translations,
|
|
84
|
+
description: opts.description,
|
|
85
|
+
module: opts.module,
|
|
86
|
+
tags: typeof opts.tags === 'string' ? opts.tags.split(',').map(t => t.trim()).filter(Boolean) : undefined,
|
|
87
|
+
published: Boolean(opts.publish),
|
|
88
|
+
});
|
|
89
|
+
if (runtime.json)
|
|
90
|
+
return printJson(key);
|
|
91
|
+
log.success(`Created ${chalk.bold(keyName)}${opts.publish ? ' (published)' : ' (draft — publish with "langctl keys publish")'}`);
|
|
252
92
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
93
|
+
export async function updateKeyCommand(projectArg, keyName, opts) {
|
|
94
|
+
const session = await getSession();
|
|
95
|
+
const project = await getProject(session, projectArg);
|
|
96
|
+
const key = await requireKey(session, project, keyName);
|
|
97
|
+
const translations = collectValues(opts);
|
|
98
|
+
// legacy: keys translate <project> <key> -l es -v "Hola"
|
|
99
|
+
if (typeof opts.language === 'string') {
|
|
100
|
+
if (typeof opts.text !== 'string')
|
|
101
|
+
throw usageError('--language needs --text (or use --value LANG=TEXT)');
|
|
102
|
+
translations[opts.language] = opts.text;
|
|
103
|
+
}
|
|
104
|
+
assertLanguages(project, Object.keys(translations));
|
|
105
|
+
const body = {};
|
|
106
|
+
if (Object.keys(translations).length)
|
|
107
|
+
body.translations = { ...key.translations, ...translations };
|
|
108
|
+
if (opts.description !== undefined)
|
|
109
|
+
body.description = opts.description;
|
|
110
|
+
if (opts.module !== undefined)
|
|
111
|
+
body.module = opts.module;
|
|
112
|
+
if (Object.keys(body).length === 0)
|
|
113
|
+
throw usageError('Nothing to update.', 'Pass --value LANG=TEXT, --description or --module.');
|
|
114
|
+
const updated = await session.api.patch(`/orgs/${session.orgId}/projects/${project.id}/keys/${key.id}`, body);
|
|
115
|
+
if (runtime.json)
|
|
116
|
+
return printJson(updated);
|
|
117
|
+
log.success(`Updated ${chalk.bold(keyName)}${Object.keys(translations).length ? ` (${Object.keys(translations).join(', ')})` : ''}`);
|
|
118
|
+
}
|
|
119
|
+
export async function deleteKeyCommand(projectArg, keyNames) {
|
|
120
|
+
const session = await getSession();
|
|
121
|
+
const project = await getProject(session, projectArg);
|
|
122
|
+
const keys = [];
|
|
123
|
+
for (const name of keyNames)
|
|
124
|
+
keys.push(await requireKey(session, project, name));
|
|
125
|
+
if (!(await confirm(`Delete ${keys.length === 1 ? `key "${keys[0].key}"` : `${keys.length} keys`} from ${project.slug}?`))) {
|
|
126
|
+
throw new CliError('Cancelled.', ExitCode.Error);
|
|
127
|
+
}
|
|
128
|
+
for (const k of keys)
|
|
129
|
+
await session.api.delete(`/orgs/${session.orgId}/projects/${project.id}/keys/${k.id}`);
|
|
130
|
+
if (runtime.json)
|
|
131
|
+
return printJson({ deleted: keys.map(k => k.key) });
|
|
132
|
+
log.success(`Deleted ${keys.map(k => chalk.bold(k.key)).join(', ')}`);
|
|
133
|
+
}
|
|
134
|
+
export async function publishKeysCommand(projectArg, keyNames, opts, publish = true) {
|
|
135
|
+
const published = publish && !opts.unpublish;
|
|
136
|
+
const session = await getSession();
|
|
137
|
+
const project = await getProject(session, projectSlugFrom(projectArg));
|
|
138
|
+
let names = keyNames;
|
|
139
|
+
if (opts.all || opts.module) {
|
|
140
|
+
if (names.length)
|
|
141
|
+
throw usageError('Pass key names or --all/--module, not both.');
|
|
142
|
+
names = (await listAllKeys(session, project, { module: opts.module, published: !published })).map(k => k.key);
|
|
143
|
+
}
|
|
144
|
+
else if (names.length === 0) {
|
|
145
|
+
throw usageError('No keys given.', `Example: langctl keys ${published ? 'publish' : 'unpublish'} ${project.slug} home.title home.subtitle (or --all / --module <name>)`);
|
|
146
|
+
}
|
|
147
|
+
if (names.length === 0) {
|
|
148
|
+
if (runtime.json)
|
|
149
|
+
return printJson({ count: 0, notFound: [] });
|
|
150
|
+
return log.info(`Nothing to ${published ? 'publish' : 'unpublish'}.`);
|
|
151
|
+
}
|
|
152
|
+
const res = await session.api.post(`/orgs/${session.orgId}/projects/${project.id}/keys/bulk-publish`, { keys: names, published }, true);
|
|
153
|
+
if (runtime.json)
|
|
154
|
+
printJson(res);
|
|
155
|
+
else
|
|
156
|
+
log.success(`${published ? 'Published' : 'Unpublished'} ${res.count} key(s)`);
|
|
157
|
+
if (res.notFound?.length) {
|
|
158
|
+
throw new CliError(`Not found: ${res.notFound.join(', ')}`, ExitCode.NotFound);
|
|
292
159
|
}
|
|
293
160
|
}
|
|
294
|
-
//# sourceMappingURL=keys.js.map
|
package/dist/commands/org.js
CHANGED
|
@@ -1,118 +1,52 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
import { config } from '../config.js';
|
|
6
|
-
/**
|
|
7
|
-
* Get organization information
|
|
8
|
-
*/
|
|
2
|
+
import { getSession } from '../core/http.js';
|
|
3
|
+
import { log, printJson, runtime, table } from '../core/output.js';
|
|
4
|
+
const limit = (n) => (n === null || n === undefined ? 'unlimited' : String(n));
|
|
9
5
|
export async function orgInfoCommand() {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
console.log(chalk.red('✗ No organization configured. Please run "langctl init" first.\n'));
|
|
18
|
-
process.exitCode = 1;
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
const spinner = ora('Fetching organization info...').start();
|
|
22
|
-
try {
|
|
23
|
-
const api = getApiClient();
|
|
24
|
-
const org = await api.get(`/orgs/${orgId}`);
|
|
25
|
-
spinner.stop();
|
|
26
|
-
console.log(chalk.blue.bold('\n🏢 Organization Information\n'));
|
|
27
|
-
console.log(chalk.white.bold(org.name));
|
|
28
|
-
console.log(chalk.gray(`ID: ${org.id}`));
|
|
29
|
-
console.log(chalk.gray(`Slug: ${org.slug}`));
|
|
30
|
-
console.log(chalk.gray(`Plan: ${org.plan}`));
|
|
31
|
-
console.log(chalk.gray(`Created: ${new Date(org.createdAt).toLocaleDateString()}`));
|
|
32
|
-
console.log('');
|
|
33
|
-
}
|
|
34
|
-
catch (error) {
|
|
35
|
-
spinner.fail(chalk.red('Failed to fetch organization info'));
|
|
36
|
-
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
37
|
-
process.exitCode = 1;
|
|
38
|
-
}
|
|
6
|
+
const session = await getSession();
|
|
7
|
+
const org = await session.api.get(`/orgs/${session.orgId}`);
|
|
8
|
+
const { id, name, slug, plan, createdAt } = org;
|
|
9
|
+
if (runtime.json)
|
|
10
|
+
return printJson({ id, name, slug, plan, createdAt });
|
|
11
|
+
log.out(`${chalk.bold(name)} ${chalk.dim(`(${slug})`)} ${plan} plan`);
|
|
12
|
+
log.out(chalk.dim(`id ${id} · created ${createdAt.slice(0, 10)}`));
|
|
39
13
|
}
|
|
40
|
-
/**
|
|
41
|
-
* Get organization statistics
|
|
42
|
-
*/
|
|
43
14
|
export async function orgStatsCommand() {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
const spinner = ora('Fetching organization statistics...').start();
|
|
56
|
-
try {
|
|
57
|
-
const api = getApiClient();
|
|
58
|
-
const stats = await api.get(`/orgs/${orgId}/stats`);
|
|
59
|
-
spinner.stop();
|
|
60
|
-
console.log(chalk.blue.bold('\n📊 Organization Statistics\n'));
|
|
61
|
-
console.log(chalk.white.bold('Team'));
|
|
62
|
-
console.log(chalk.gray(` Members: ${stats.memberCount}`));
|
|
63
|
-
console.log('');
|
|
64
|
-
console.log(chalk.white.bold('Projects'));
|
|
65
|
-
console.log(chalk.gray(` Total Projects: ${stats.projectCount}`));
|
|
66
|
-
console.log('');
|
|
67
|
-
console.log(chalk.white.bold('Translation Keys'));
|
|
68
|
-
console.log(chalk.gray(` Total Keys: ${stats.totalKeys}`));
|
|
69
|
-
console.log('');
|
|
70
|
-
console.log(chalk.white.bold('Resources'));
|
|
71
|
-
console.log(chalk.gray(` API Keys: ${stats.apiKeyCount}`));
|
|
72
|
-
console.log('');
|
|
73
|
-
}
|
|
74
|
-
catch (error) {
|
|
75
|
-
spinner.fail(chalk.red('Failed to fetch statistics'));
|
|
76
|
-
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
77
|
-
process.exitCode = 1;
|
|
78
|
-
}
|
|
15
|
+
const session = await getSession();
|
|
16
|
+
const stats = await session.api.get(`/orgs/${session.orgId}/stats`);
|
|
17
|
+
if (runtime.json)
|
|
18
|
+
return printJson(stats);
|
|
19
|
+
table([
|
|
20
|
+
['Members', String(stats.memberCount)],
|
|
21
|
+
['Projects', String(stats.projectCount)],
|
|
22
|
+
['Translation keys', String(stats.totalKeys)],
|
|
23
|
+
['Active API keys', String(stats.apiKeyCount)],
|
|
24
|
+
]);
|
|
79
25
|
}
|
|
80
|
-
/**
|
|
81
|
-
* Get organization plan and limits
|
|
82
|
-
*/
|
|
83
26
|
export async function orgPlanCommand() {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
console.log(chalk.gray(` Max API Keys: ${formatLimit(org.maxApiKeys)}`));
|
|
110
|
-
console.log('');
|
|
111
|
-
}
|
|
112
|
-
catch (error) {
|
|
113
|
-
spinner.fail(chalk.red('Failed to fetch plan information'));
|
|
114
|
-
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
115
|
-
process.exitCode = 1;
|
|
116
|
-
}
|
|
27
|
+
const session = await getSession();
|
|
28
|
+
const [org, stats] = await Promise.all([
|
|
29
|
+
session.api.get(`/orgs/${session.orgId}`),
|
|
30
|
+
session.api.get(`/orgs/${session.orgId}/stats`),
|
|
31
|
+
]);
|
|
32
|
+
const rows = {
|
|
33
|
+
plan: org.plan,
|
|
34
|
+
members: { used: stats.memberCount, limit: org.maxMembers ?? null },
|
|
35
|
+
projects: { used: stats.projectCount, limit: org.maxProjects ?? null },
|
|
36
|
+
apiKeys: { used: stats.apiKeyCount, limit: org.maxApiKeys ?? null },
|
|
37
|
+
keysPerProject: { limit: org.maxKeysPerProject ?? null },
|
|
38
|
+
languagesPerProject: { limit: org.maxLanguagesPerProject ?? null },
|
|
39
|
+
aiTranslationsPerMonth: { used: org.aiTranslationsUsedThisMonth ?? 0, limit: org.aiTranslationsLimit ?? null },
|
|
40
|
+
};
|
|
41
|
+
if (runtime.json)
|
|
42
|
+
return printJson(rows);
|
|
43
|
+
log.out(`${chalk.bold(org.plan.toUpperCase())} plan`);
|
|
44
|
+
table([
|
|
45
|
+
['Members', `${stats.memberCount} / ${limit(org.maxMembers)}`],
|
|
46
|
+
['Projects', `${stats.projectCount} / ${limit(org.maxProjects)}`],
|
|
47
|
+
['API keys', `${stats.apiKeyCount} / ${limit(org.maxApiKeys)}`],
|
|
48
|
+
['Keys per project', limit(org.maxKeysPerProject)],
|
|
49
|
+
['Languages per project', limit(org.maxLanguagesPerProject)],
|
|
50
|
+
['AI translations / month', `${org.aiTranslationsUsedThisMonth ?? 0} / ${limit(org.aiTranslationsLimit)}`],
|
|
51
|
+
]);
|
|
117
52
|
}
|
|
118
|
-
//# sourceMappingURL=org.js.map
|