langctl 0.1.1 → 0.2.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/.gitattributes +2 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +26 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +19 -0
- package/CONTRIBUTING.md +33 -0
- package/LICENSE +1 -1
- package/README.md +1018 -113
- package/dist/api.d.ts +20 -0
- package/dist/api.js +75 -0
- package/dist/auth.d.ts +7 -2
- package/dist/auth.js +27 -27
- package/dist/commands/auth.js +2 -10
- package/dist/commands/config.js +1 -0
- package/dist/commands/debug.js +45 -41
- package/dist/commands/export.d.ts +13 -0
- package/dist/commands/export.js +125 -0
- package/dist/commands/import.d.ts +12 -0
- package/dist/commands/import.js +150 -0
- package/dist/commands/init.js +0 -10
- package/dist/commands/keys.d.ts +25 -0
- package/dist/commands/keys.js +294 -0
- package/dist/commands/org.d.ts +13 -0
- package/dist/commands/org.js +118 -0
- package/dist/commands/projects.d.ts +31 -0
- package/dist/commands/projects.js +295 -24
- package/dist/commands/pull.js +40 -95
- package/dist/commands/team.d.ts +29 -0
- package/dist/commands/team.js +270 -0
- package/dist/config.d.ts +1 -0
- package/dist/index.js +357 -6
- package/package.json +53 -7
- package/NPM_PUBLISH_GUIDE.md +0 -293
- package/QUICK_PUBLISH.md +0 -64
- package/SECURITY_FIX.md +0 -167
- package/dist/supabase.d.ts +0 -14
- package/dist/supabase.js +0 -30
|
@@ -1,54 +1,325 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import ora from 'ora';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { isAuthenticated } from '../auth.js';
|
|
4
|
+
import { getApiClient } from '../api.js';
|
|
5
|
+
import { config } from '../config.js';
|
|
6
|
+
/**
|
|
7
|
+
* Resolve a project slug to a full project object
|
|
8
|
+
*/
|
|
9
|
+
async function resolveProject(orgId, slug) {
|
|
10
|
+
const api = getApiClient();
|
|
11
|
+
return api.get(`/orgs/${orgId}/projects/by-slug/${slug}`);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* List all projects
|
|
15
|
+
*/
|
|
5
16
|
export async function projectsListCommand() {
|
|
6
|
-
// Check authentication
|
|
7
17
|
if (!isAuthenticated()) {
|
|
8
18
|
console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
|
|
19
|
+
process.exitCode = 1;
|
|
9
20
|
return;
|
|
10
21
|
}
|
|
11
|
-
const
|
|
12
|
-
if (!
|
|
13
|
-
console.log(chalk.red('✗ Organization ID not found. Please run "langctl
|
|
22
|
+
const orgId = config.get('organizationId');
|
|
23
|
+
if (!orgId) {
|
|
24
|
+
console.log(chalk.red('✗ Organization ID not found. Please run "langctl auth <api-key>" again.\n'));
|
|
25
|
+
process.exitCode = 1;
|
|
14
26
|
return;
|
|
15
27
|
}
|
|
16
28
|
const spinner = ora('Fetching projects...').start();
|
|
17
29
|
try {
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
.from('projects')
|
|
21
|
-
.select('*')
|
|
22
|
-
.eq('organization_id', organizationId)
|
|
23
|
-
.is('deleted_at', null)
|
|
24
|
-
.order('created_at', { ascending: false });
|
|
25
|
-
if (error) {
|
|
26
|
-
spinner.fail(chalk.red('Failed to fetch projects'));
|
|
27
|
-
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
+
const api = getApiClient();
|
|
31
|
+
const projects = await api.get(`/orgs/${orgId}/projects`);
|
|
30
32
|
spinner.stop();
|
|
31
|
-
if (!
|
|
33
|
+
if (!projects || projects.length === 0) {
|
|
32
34
|
console.log(chalk.yellow('\nNo projects found. Create one in the dashboard first.\n'));
|
|
33
35
|
return;
|
|
34
36
|
}
|
|
35
|
-
console.log(chalk.blue.bold(`\n📦 Projects (${
|
|
36
|
-
|
|
37
|
+
console.log(chalk.blue.bold(`\n📦 Projects (${projects.length})\n`));
|
|
38
|
+
projects.forEach((project) => {
|
|
37
39
|
console.log(chalk.white.bold(project.name));
|
|
38
40
|
console.log(chalk.gray(` Slug: ${project.slug}`));
|
|
39
41
|
if (project.description) {
|
|
40
42
|
console.log(chalk.gray(` Description: ${project.description}`));
|
|
41
43
|
}
|
|
42
44
|
console.log(chalk.gray(` Languages: ${project.languages.join(', ')}`));
|
|
43
|
-
console.log(chalk.gray(` Default: ${project.
|
|
45
|
+
console.log(chalk.gray(` Default: ${project.defaultLanguage}`));
|
|
46
|
+
if (project.modules && project.modules.length > 0) {
|
|
47
|
+
console.log(chalk.gray(` Modules: ${project.modules.join(', ')}`));
|
|
48
|
+
}
|
|
44
49
|
console.log('');
|
|
45
50
|
});
|
|
46
|
-
console.log(chalk.blue('To
|
|
47
|
-
console.log(chalk.white(` langctl
|
|
51
|
+
console.log(chalk.blue('To export translations, run:'));
|
|
52
|
+
console.log(chalk.white(` langctl export ${projects[0].slug} -l en\n`));
|
|
48
53
|
}
|
|
49
54
|
catch (error) {
|
|
50
55
|
spinner.fail(chalk.red('Failed to fetch projects'));
|
|
51
56
|
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
57
|
+
process.exitCode = 1;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Create new project
|
|
62
|
+
*/
|
|
63
|
+
export async function projectsCreateCommand(name, options) {
|
|
64
|
+
if (!isAuthenticated()) {
|
|
65
|
+
console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
|
|
66
|
+
process.exitCode = 1;
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const orgId = config.get('organizationId');
|
|
70
|
+
if (!orgId) {
|
|
71
|
+
console.log(chalk.red('✗ Organization ID not found. Please run "langctl auth <api-key>" again.\n'));
|
|
72
|
+
process.exitCode = 1;
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const spinner = ora('Creating project...').start();
|
|
76
|
+
try {
|
|
77
|
+
const api = getApiClient();
|
|
78
|
+
const languages = options.languages ? options.languages.split(',') : ['en'];
|
|
79
|
+
const defaultLanguage = options.defaultLanguage || languages[0];
|
|
80
|
+
const project = await api.post(`/orgs/${orgId}/projects`, {
|
|
81
|
+
name,
|
|
82
|
+
description: options.description,
|
|
83
|
+
languages,
|
|
84
|
+
defaultLanguage
|
|
85
|
+
});
|
|
86
|
+
spinner.succeed(chalk.green(`Created project: ${name} (${project.slug})`));
|
|
87
|
+
console.log('');
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
spinner.fail(chalk.red('Failed to create project'));
|
|
91
|
+
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
92
|
+
process.exitCode = 1;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Get project details
|
|
97
|
+
*/
|
|
98
|
+
export async function projectsGetCommand(slug) {
|
|
99
|
+
if (!isAuthenticated()) {
|
|
100
|
+
console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
|
|
101
|
+
process.exitCode = 1;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const orgId = config.get('organizationId');
|
|
105
|
+
if (!orgId) {
|
|
106
|
+
console.log(chalk.red('✗ Organization ID not found. Please run "langctl auth <api-key>" again.\n'));
|
|
107
|
+
process.exitCode = 1;
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const spinner = ora('Fetching project...').start();
|
|
111
|
+
try {
|
|
112
|
+
const project = await resolveProject(orgId, slug);
|
|
113
|
+
spinner.stop();
|
|
114
|
+
console.log(chalk.blue.bold('\n📦 Project Details\n'));
|
|
115
|
+
console.log(chalk.white.bold(project.name));
|
|
116
|
+
console.log(chalk.gray(`Slug: ${project.slug}`));
|
|
117
|
+
console.log(chalk.gray(`ID: ${project.id}`));
|
|
118
|
+
if (project.description) {
|
|
119
|
+
console.log(chalk.gray(`Description: ${project.description}`));
|
|
120
|
+
}
|
|
121
|
+
console.log(chalk.gray(`Languages: ${project.languages.join(', ')}`));
|
|
122
|
+
console.log(chalk.gray(`Default Language: ${project.defaultLanguage}`));
|
|
123
|
+
if (project.modules && project.modules.length > 0) {
|
|
124
|
+
console.log(chalk.gray(`Modules: ${project.modules.join(', ')}`));
|
|
125
|
+
}
|
|
126
|
+
console.log('');
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
spinner.fail(chalk.red(`Failed to fetch project "${slug}"`));
|
|
130
|
+
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
131
|
+
process.exitCode = 1;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Update project
|
|
136
|
+
*/
|
|
137
|
+
export async function projectsUpdateCommand(slug, options) {
|
|
138
|
+
if (!isAuthenticated()) {
|
|
139
|
+
console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
|
|
140
|
+
process.exitCode = 1;
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const orgId = config.get('organizationId');
|
|
144
|
+
if (!orgId) {
|
|
145
|
+
console.log(chalk.red('✗ Organization ID not found. Please run "langctl auth <api-key>" again.\n'));
|
|
146
|
+
process.exitCode = 1;
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const spinner = ora('Updating project...').start();
|
|
150
|
+
try {
|
|
151
|
+
const project = await resolveProject(orgId, slug);
|
|
152
|
+
const api = getApiClient();
|
|
153
|
+
const updateData = {};
|
|
154
|
+
if (options.name)
|
|
155
|
+
updateData.name = options.name;
|
|
156
|
+
if (options.description !== undefined)
|
|
157
|
+
updateData.description = options.description;
|
|
158
|
+
if (options.defaultLanguage)
|
|
159
|
+
updateData.defaultLanguage = options.defaultLanguage;
|
|
160
|
+
// Keep support for --languages by reconciling desired list through add/remove-language APIs.
|
|
161
|
+
let targetLanguages = null;
|
|
162
|
+
if (options.languages) {
|
|
163
|
+
const parsedLanguages = options.languages
|
|
164
|
+
.split(',')
|
|
165
|
+
.map((lang) => lang.trim())
|
|
166
|
+
.filter((lang) => lang.length > 0);
|
|
167
|
+
if (parsedLanguages.length === 0) {
|
|
168
|
+
throw new Error('At least one language must be provided when using --languages');
|
|
169
|
+
}
|
|
170
|
+
const effectiveDefaultLanguage = options.defaultLanguage || project.defaultLanguage;
|
|
171
|
+
if (!parsedLanguages.includes(effectiveDefaultLanguage)) {
|
|
172
|
+
throw new Error(`Default language "${effectiveDefaultLanguage}" must be included in --languages`);
|
|
173
|
+
}
|
|
174
|
+
targetLanguages = parsedLanguages;
|
|
175
|
+
}
|
|
176
|
+
if (Object.keys(updateData).length > 0) {
|
|
177
|
+
await api.patch(`/orgs/${orgId}/projects/${project.id}`, updateData);
|
|
178
|
+
}
|
|
179
|
+
if (targetLanguages) {
|
|
180
|
+
const desiredLanguages = targetLanguages;
|
|
181
|
+
const currentLanguages = project.languages || [];
|
|
182
|
+
const toAdd = desiredLanguages.filter((lang) => !currentLanguages.includes(lang));
|
|
183
|
+
const toRemove = currentLanguages.filter((lang) => !desiredLanguages.includes(lang));
|
|
184
|
+
for (const lang of toAdd) {
|
|
185
|
+
await api.post(`/orgs/${orgId}/projects/${project.id}/languages`, { code: lang });
|
|
186
|
+
}
|
|
187
|
+
for (const lang of toRemove) {
|
|
188
|
+
await api.delete(`/orgs/${orgId}/projects/${project.id}/languages/${lang}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
spinner.succeed(chalk.green(`Updated project: ${slug}`));
|
|
192
|
+
console.log('');
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
spinner.fail(chalk.red('Failed to update project'));
|
|
196
|
+
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
197
|
+
process.exitCode = 1;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Delete project
|
|
202
|
+
*/
|
|
203
|
+
export async function projectsDeleteCommand(slug) {
|
|
204
|
+
if (!isAuthenticated()) {
|
|
205
|
+
console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
|
|
206
|
+
process.exitCode = 1;
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
const orgId = config.get('organizationId');
|
|
210
|
+
if (!orgId) {
|
|
211
|
+
console.log(chalk.red('✗ Organization ID not found. Please run "langctl auth <api-key>" again.\n'));
|
|
212
|
+
process.exitCode = 1;
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const spinner = ora('Deleting project...').start();
|
|
216
|
+
try {
|
|
217
|
+
const project = await resolveProject(orgId, slug);
|
|
218
|
+
const api = getApiClient();
|
|
219
|
+
await api.delete(`/orgs/${orgId}/projects/${project.id}`);
|
|
220
|
+
spinner.succeed(chalk.green(`Deleted project: ${slug}`));
|
|
221
|
+
console.log('');
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
spinner.fail(chalk.red('Failed to delete project'));
|
|
225
|
+
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
226
|
+
process.exitCode = 1;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Add language to project
|
|
231
|
+
*/
|
|
232
|
+
export async function projectsAddLanguageCommand(slug, language) {
|
|
233
|
+
if (!isAuthenticated()) {
|
|
234
|
+
console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
|
|
235
|
+
process.exitCode = 1;
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const orgId = config.get('organizationId');
|
|
239
|
+
if (!orgId) {
|
|
240
|
+
console.log(chalk.red('✗ Organization ID not found. Please run "langctl auth <api-key>" again.\n'));
|
|
241
|
+
process.exitCode = 1;
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const spinner = ora(`Adding language ${language}...`).start();
|
|
245
|
+
try {
|
|
246
|
+
const project = await resolveProject(orgId, slug);
|
|
247
|
+
const api = getApiClient();
|
|
248
|
+
await api.post(`/orgs/${orgId}/projects/${project.id}/languages`, { code: language });
|
|
249
|
+
spinner.succeed(chalk.green(`Added language "${language}" to project "${slug}"`));
|
|
250
|
+
console.log('');
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
spinner.fail(chalk.red('Failed to add language'));
|
|
254
|
+
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
255
|
+
process.exitCode = 1;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Remove language from project
|
|
260
|
+
*/
|
|
261
|
+
export async function projectsRemoveLanguageCommand(slug, language) {
|
|
262
|
+
if (!isAuthenticated()) {
|
|
263
|
+
console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
|
|
264
|
+
process.exitCode = 1;
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const orgId = config.get('organizationId');
|
|
268
|
+
if (!orgId) {
|
|
269
|
+
console.log(chalk.red('✗ Organization ID not found. Please run "langctl auth <api-key>" again.\n'));
|
|
270
|
+
process.exitCode = 1;
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
const spinner = ora(`Removing language ${language}...`).start();
|
|
274
|
+
try {
|
|
275
|
+
const project = await resolveProject(orgId, slug);
|
|
276
|
+
const api = getApiClient();
|
|
277
|
+
await api.delete(`/orgs/${orgId}/projects/${project.id}/languages/${language}`);
|
|
278
|
+
spinner.succeed(chalk.green(`Removed language "${language}" from project "${slug}"`));
|
|
279
|
+
console.log('');
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
spinner.fail(chalk.red('Failed to remove language'));
|
|
283
|
+
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
284
|
+
process.exitCode = 1;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Get project statistics
|
|
289
|
+
*/
|
|
290
|
+
export async function projectsStatsCommand(slug) {
|
|
291
|
+
if (!isAuthenticated()) {
|
|
292
|
+
console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
|
|
293
|
+
process.exitCode = 1;
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
const orgId = config.get('organizationId');
|
|
297
|
+
if (!orgId) {
|
|
298
|
+
console.log(chalk.red('✗ Organization ID not found. Please run "langctl auth <api-key>" again.\n'));
|
|
299
|
+
process.exitCode = 1;
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
const spinner = ora('Fetching statistics...').start();
|
|
303
|
+
try {
|
|
304
|
+
const project = await resolveProject(orgId, slug);
|
|
305
|
+
const api = getApiClient();
|
|
306
|
+
const stats = await api.get(`/orgs/${orgId}/projects/${project.id}/stats`);
|
|
307
|
+
spinner.stop();
|
|
308
|
+
console.log(chalk.blue.bold('\n📊 Project Statistics\n'));
|
|
309
|
+
console.log(chalk.white(`Total Keys: ${stats.totalKeys}`));
|
|
310
|
+
console.log(chalk.green(`Published Keys: ${stats.publishedKeys}`));
|
|
311
|
+
console.log(chalk.yellow(`Unpublished Keys: ${stats.unpublishedKeys}`));
|
|
312
|
+
console.log(chalk.white(`Language Count: ${stats.languageCount}`));
|
|
313
|
+
if (stats.modules && stats.modules.length > 0) {
|
|
314
|
+
console.log(chalk.white(`Modules: ${stats.modules.length}`));
|
|
315
|
+
console.log(chalk.gray(` ${stats.modules.join(', ')}`));
|
|
316
|
+
}
|
|
317
|
+
console.log('');
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
spinner.fail(chalk.red('Failed to fetch statistics'));
|
|
321
|
+
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
322
|
+
process.exitCode = 1;
|
|
52
323
|
}
|
|
53
324
|
}
|
|
54
325
|
//# sourceMappingURL=projects.js.map
|
package/dist/commands/pull.js
CHANGED
|
@@ -2,8 +2,9 @@ import chalk from 'chalk';
|
|
|
2
2
|
import ora from 'ora';
|
|
3
3
|
import { writeFileSync, mkdirSync } from 'fs';
|
|
4
4
|
import { dirname, join } from 'path';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { isAuthenticated } from '../auth.js';
|
|
6
|
+
import { getApiClient } from '../api.js';
|
|
7
|
+
import { config } from '../config.js';
|
|
7
8
|
import { exportTranslations } from '../exporters/index.js';
|
|
8
9
|
/**
|
|
9
10
|
* Get platform-specific output directory
|
|
@@ -18,64 +19,24 @@ function getPlatformDirectory(format) {
|
|
|
18
19
|
};
|
|
19
20
|
return platformDirs[format] || 'i18n';
|
|
20
21
|
}
|
|
21
|
-
/**
|
|
22
|
-
* Resolve project identifier (name, slug, or UUID) to project UUID
|
|
23
|
-
*/
|
|
24
|
-
async function resolveProjectId(identifier, organizationId) {
|
|
25
|
-
const supabase = getSupabase();
|
|
26
|
-
// Check if it's already a UUID (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
|
|
27
|
-
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
28
|
-
if (uuidRegex.test(identifier)) {
|
|
29
|
-
// Verify it exists
|
|
30
|
-
const { data } = await supabase
|
|
31
|
-
.from('projects')
|
|
32
|
-
.select('id')
|
|
33
|
-
.eq('id', identifier)
|
|
34
|
-
.eq('organization_id', organizationId)
|
|
35
|
-
.is('deleted_at', null)
|
|
36
|
-
.single();
|
|
37
|
-
return data ? identifier : null;
|
|
38
|
-
}
|
|
39
|
-
// Try to match by slug (case-insensitive)
|
|
40
|
-
const { data: bySlug } = await supabase
|
|
41
|
-
.from('projects')
|
|
42
|
-
.select('id')
|
|
43
|
-
.eq('organization_id', organizationId)
|
|
44
|
-
.ilike('slug', identifier)
|
|
45
|
-
.is('deleted_at', null)
|
|
46
|
-
.single();
|
|
47
|
-
if (bySlug) {
|
|
48
|
-
return bySlug.id;
|
|
49
|
-
}
|
|
50
|
-
// Try to match by name (case-insensitive)
|
|
51
|
-
const { data: byName } = await supabase
|
|
52
|
-
.from('projects')
|
|
53
|
-
.select('id')
|
|
54
|
-
.eq('organization_id', organizationId)
|
|
55
|
-
.ilike('name', identifier)
|
|
56
|
-
.is('deleted_at', null)
|
|
57
|
-
.single();
|
|
58
|
-
if (byName) {
|
|
59
|
-
return byName.id;
|
|
60
|
-
}
|
|
61
|
-
return null;
|
|
62
|
-
}
|
|
63
22
|
export async function pullCommand(projectIdentifier, options) {
|
|
64
23
|
// Check authentication
|
|
65
24
|
if (!isAuthenticated()) {
|
|
66
25
|
console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
|
|
26
|
+
process.exitCode = 1;
|
|
67
27
|
return;
|
|
68
28
|
}
|
|
69
|
-
const
|
|
70
|
-
if (!
|
|
71
|
-
console.log(chalk.red('✗ Organization ID not found. Please run "langctl
|
|
29
|
+
const orgId = config.get('organizationId');
|
|
30
|
+
if (!orgId) {
|
|
31
|
+
console.log(chalk.red('✗ Organization ID not found. Please run "langctl auth <api-key>" again.\n'));
|
|
32
|
+
process.exitCode = 1;
|
|
72
33
|
return;
|
|
73
34
|
}
|
|
74
35
|
// Set defaults
|
|
75
36
|
const format = (options.format || 'json');
|
|
76
37
|
const publishedOnly = options.publishedOnly !== false; // Default to true
|
|
77
|
-
const specificLanguage = options.language;
|
|
78
|
-
const baseDir = options.dir || './translations';
|
|
38
|
+
const specificLanguage = options.language;
|
|
39
|
+
const baseDir = options.dir || './translations';
|
|
79
40
|
// Validate format
|
|
80
41
|
const validFormats = ['json', 'json-nested', 'ios', 'android', 'flutter'];
|
|
81
42
|
if (!validFormats.includes(format)) {
|
|
@@ -85,31 +46,17 @@ export async function pullCommand(projectIdentifier, options) {
|
|
|
85
46
|
}
|
|
86
47
|
const spinner = ora('Resolving project...').start();
|
|
87
48
|
try {
|
|
88
|
-
const
|
|
89
|
-
// Resolve project
|
|
90
|
-
const
|
|
91
|
-
if (!
|
|
49
|
+
const api = getApiClient();
|
|
50
|
+
// Resolve project by slug
|
|
51
|
+
const project = await api.get(`/orgs/${orgId}/projects/by-slug/${projectIdentifier}`);
|
|
52
|
+
if (!project) {
|
|
92
53
|
spinner.fail(chalk.red(`Project "${projectIdentifier}" not found`));
|
|
93
54
|
console.log(chalk.yellow('\nRun "langctl projects list" to see available projects.\n'));
|
|
94
55
|
return;
|
|
95
56
|
}
|
|
96
|
-
// Fetch project details
|
|
97
|
-
spinner.text = 'Fetching project...';
|
|
98
|
-
const { data: project, error: projectError } = await supabase
|
|
99
|
-
.from('projects')
|
|
100
|
-
.select('id, name, slug, languages, default_language')
|
|
101
|
-
.eq('id', projectId)
|
|
102
|
-
.eq('organization_id', organizationId)
|
|
103
|
-
.is('deleted_at', null)
|
|
104
|
-
.single();
|
|
105
|
-
if (projectError || !project) {
|
|
106
|
-
spinner.fail(chalk.red('Project not found or access denied'));
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
57
|
// Determine which languages to pull
|
|
110
58
|
let languagesToPull;
|
|
111
59
|
if (specificLanguage) {
|
|
112
|
-
// Check if specified language exists in project
|
|
113
60
|
if (!project.languages.includes(specificLanguage)) {
|
|
114
61
|
spinner.fail(chalk.red(`Language "${specificLanguage}" not found in project`));
|
|
115
62
|
console.log(chalk.yellow(`Available languages: ${project.languages.join(', ')}\n`));
|
|
@@ -118,44 +65,37 @@ export async function pullCommand(projectIdentifier, options) {
|
|
|
118
65
|
languagesToPull = [specificLanguage];
|
|
119
66
|
}
|
|
120
67
|
else {
|
|
121
|
-
// Pull all languages
|
|
122
68
|
languagesToPull = project.languages;
|
|
123
69
|
}
|
|
124
70
|
spinner.text = `Fetching translations for "${project.name}"...`;
|
|
125
|
-
// Fetch translation keys
|
|
126
|
-
let query = supabase
|
|
127
|
-
.from('translation_keys')
|
|
128
|
-
.select('key, translations, description, module')
|
|
129
|
-
.eq('project_id', projectId)
|
|
130
|
-
.is('deleted_at', null);
|
|
131
|
-
if (publishedOnly) {
|
|
132
|
-
query = query.eq('published', true);
|
|
133
|
-
}
|
|
134
|
-
const { data: keys, error: keysError } = await query;
|
|
135
|
-
if (keysError) {
|
|
136
|
-
spinner.fail(chalk.red('Failed to fetch translations'));
|
|
137
|
-
console.error(chalk.red(`Error: ${keysError.message}\n`));
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
if (!keys || keys.length === 0) {
|
|
141
|
-
spinner.fail(chalk.yellow('No translations found'));
|
|
142
|
-
console.log(chalk.yellow(`\nNo ${publishedOnly ? 'published ' : ''}translations found for this project.\n`));
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
spinner.succeed(chalk.green(`Found ${keys.length} translation keys`));
|
|
146
71
|
// Get platform directory
|
|
147
72
|
const platformDir = getPlatformDirectory(format);
|
|
148
73
|
// Export translations for each language
|
|
149
74
|
const exportedFiles = [];
|
|
75
|
+
let totalKeyCount = 0;
|
|
150
76
|
for (const language of languagesToPull) {
|
|
151
|
-
spinner.
|
|
152
|
-
|
|
77
|
+
spinner.text = `Exporting ${language}...`;
|
|
78
|
+
// Fetch flat translations from the API
|
|
79
|
+
const result = await api.get(`/orgs/${orgId}/projects/${project.id}/export`, {
|
|
80
|
+
language,
|
|
81
|
+
publishedOnly: publishedOnly ? 'true' : 'false'
|
|
82
|
+
});
|
|
83
|
+
// Convert flat translations to TranslationKey array for the exporter
|
|
84
|
+
const flatTranslations = result.translations || {};
|
|
85
|
+
const translationKeys = Object.entries(flatTranslations).map(([key, value]) => ({
|
|
86
|
+
key,
|
|
87
|
+
translations: { [language]: value }
|
|
88
|
+
}));
|
|
89
|
+
if (totalKeyCount === 0) {
|
|
90
|
+
totalKeyCount = translationKeys.length;
|
|
91
|
+
}
|
|
92
|
+
const exportResult = exportTranslations(translationKeys, language, format);
|
|
153
93
|
// Determine output path
|
|
154
94
|
let outputPath;
|
|
155
95
|
if (options.output) {
|
|
156
96
|
// If custom output is specified and multiple languages, append language code
|
|
157
97
|
if (languagesToPull.length > 1) {
|
|
158
|
-
const ext =
|
|
98
|
+
const ext = exportResult.filename.split('.').pop();
|
|
159
99
|
const base = options.output.replace(/\.[^.]+$/, '');
|
|
160
100
|
outputPath = `${base}-${language}.${ext}`;
|
|
161
101
|
}
|
|
@@ -165,15 +105,19 @@ export async function pullCommand(projectIdentifier, options) {
|
|
|
165
105
|
}
|
|
166
106
|
else {
|
|
167
107
|
// Default output path: <baseDir>/<platform>/<language>.<ext>
|
|
168
|
-
const ext =
|
|
108
|
+
const ext = exportResult.filename.split('.').pop();
|
|
169
109
|
outputPath = join(baseDir, platformDir, `${language}.${ext}`);
|
|
170
110
|
}
|
|
171
111
|
// Create directory if it doesn't exist
|
|
172
112
|
const dir = dirname(outputPath);
|
|
173
113
|
mkdirSync(dir, { recursive: true });
|
|
174
|
-
writeFileSync(outputPath,
|
|
114
|
+
writeFileSync(outputPath, exportResult.content, 'utf-8');
|
|
175
115
|
exportedFiles.push(outputPath);
|
|
176
116
|
spinner.succeed(chalk.green(`Exported ${language} → ${outputPath}`));
|
|
117
|
+
// Restart spinner if more languages remain
|
|
118
|
+
if (languagesToPull.indexOf(language) < languagesToPull.length - 1) {
|
|
119
|
+
spinner.start();
|
|
120
|
+
}
|
|
177
121
|
}
|
|
178
122
|
// Summary
|
|
179
123
|
console.log(chalk.hex('#10b981').bold('\n📦 Export Summary:'));
|
|
@@ -181,7 +125,7 @@ export async function pullCommand(projectIdentifier, options) {
|
|
|
181
125
|
console.log(chalk.white(` Format: ${format}`));
|
|
182
126
|
console.log(chalk.white(` Platform: ${platformDir}`));
|
|
183
127
|
console.log(chalk.white(` Output: ${baseDir}`));
|
|
184
|
-
console.log(chalk.white(` Keys: ${
|
|
128
|
+
console.log(chalk.white(` Keys: ${totalKeyCount}`));
|
|
185
129
|
console.log(chalk.white(` Languages: ${languagesToPull.join(', ')}`));
|
|
186
130
|
console.log(chalk.white(` Files: ${exportedFiles.length}`));
|
|
187
131
|
console.log('');
|
|
@@ -189,6 +133,7 @@ export async function pullCommand(projectIdentifier, options) {
|
|
|
189
133
|
catch (error) {
|
|
190
134
|
spinner.fail(chalk.red('Failed to pull translations'));
|
|
191
135
|
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
136
|
+
process.exitCode = 1;
|
|
192
137
|
}
|
|
193
138
|
}
|
|
194
139
|
//# sourceMappingURL=pull.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List all team members
|
|
3
|
+
*/
|
|
4
|
+
export declare function listTeamCommand(): Promise<void>;
|
|
5
|
+
/**
|
|
6
|
+
* Get team member details
|
|
7
|
+
*/
|
|
8
|
+
export declare function getTeamMemberCommand(email: string): Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* Invite team member
|
|
11
|
+
*/
|
|
12
|
+
export declare function inviteTeamMemberCommand(email: string, options: any): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Remove team member
|
|
15
|
+
*/
|
|
16
|
+
export declare function removeTeamMemberCommand(email: string): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Update team member role
|
|
19
|
+
*/
|
|
20
|
+
export declare function updateTeamRoleCommand(email: string, role: string): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* List invitations
|
|
23
|
+
*/
|
|
24
|
+
export declare function listInvitationsCommand(options: any): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Revoke invitation
|
|
27
|
+
*/
|
|
28
|
+
export declare function revokeInvitationCommand(email: string): Promise<void>;
|
|
29
|
+
//# sourceMappingURL=team.d.ts.map
|