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
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { readFileSync } from 'fs';
|
|
4
|
+
import { resolve } from 'path';
|
|
5
|
+
import { isAuthenticated } from '../auth.js';
|
|
6
|
+
import { getApiClient } from '../api.js';
|
|
7
|
+
import { config } from '../config.js';
|
|
8
|
+
/**
|
|
9
|
+
* Parse translation file based on extension
|
|
10
|
+
*/
|
|
11
|
+
function parseTranslationFile(filePath) {
|
|
12
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
13
|
+
const ext = filePath.split('.').pop()?.toLowerCase();
|
|
14
|
+
if (ext === 'json') {
|
|
15
|
+
const parsed = JSON.parse(content);
|
|
16
|
+
// Check if it's nested JSON and flatten it
|
|
17
|
+
if (isNestedObject(parsed)) {
|
|
18
|
+
return flattenObject(parsed);
|
|
19
|
+
}
|
|
20
|
+
return parsed;
|
|
21
|
+
}
|
|
22
|
+
throw new Error(`Unsupported file format: .${ext}. Only JSON files are currently supported.`);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Check if object has nested objects
|
|
26
|
+
*/
|
|
27
|
+
function isNestedObject(obj) {
|
|
28
|
+
return Object.values(obj).some(value => typeof value === 'object' && value !== null && !Array.isArray(value));
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Flatten nested object to dot-notation keys
|
|
32
|
+
*/
|
|
33
|
+
function flattenObject(obj, prefix = '') {
|
|
34
|
+
const result = {};
|
|
35
|
+
Object.entries(obj).forEach(([key, value]) => {
|
|
36
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
37
|
+
if (typeof value === 'string') {
|
|
38
|
+
result[fullKey] = value;
|
|
39
|
+
}
|
|
40
|
+
else if (typeof value === 'object' && value !== null) {
|
|
41
|
+
Object.assign(result, flattenObject(value, fullKey));
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Import translations command
|
|
48
|
+
*/
|
|
49
|
+
export async function importCommand(projectSlug, filePath, options) {
|
|
50
|
+
if (!isAuthenticated()) {
|
|
51
|
+
console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
|
|
52
|
+
process.exitCode = 1;
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const orgId = config.get('organizationId');
|
|
56
|
+
if (!orgId) {
|
|
57
|
+
console.log(chalk.red('✗ Organization ID not found. Please run "langctl auth <api-key>" again.\n'));
|
|
58
|
+
process.exitCode = 1;
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const spinner = ora('Loading file...').start();
|
|
62
|
+
try {
|
|
63
|
+
// Resolve file path
|
|
64
|
+
const resolvedPath = resolve(filePath);
|
|
65
|
+
// Parse translation file
|
|
66
|
+
spinner.text = 'Parsing translation file...';
|
|
67
|
+
const translations = parseTranslationFile(resolvedPath);
|
|
68
|
+
const keyCount = Object.keys(translations).length;
|
|
69
|
+
if (keyCount === 0) {
|
|
70
|
+
spinner.warn(chalk.yellow('No translations found in file'));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
spinner.text = 'Fetching project...';
|
|
74
|
+
// Get project by slug
|
|
75
|
+
const api = getApiClient();
|
|
76
|
+
const project = await api.get(`/orgs/${orgId}/projects/by-slug/${projectSlug}`);
|
|
77
|
+
// Validate language
|
|
78
|
+
if (!project.languages.includes(options.language)) {
|
|
79
|
+
spinner.fail(chalk.red(`Language "${options.language}" not found in project`));
|
|
80
|
+
console.log(chalk.yellow(`Available languages: ${project.languages.join(', ')}\n`));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
spinner.text = `Importing ${keyCount} translations...`;
|
|
84
|
+
// Push translations via REST API
|
|
85
|
+
const body = {
|
|
86
|
+
language: options.language,
|
|
87
|
+
translations,
|
|
88
|
+
// Keep import safe by default; only overwrite when explicitly requested.
|
|
89
|
+
overwriteExisting: options.overwrite === true
|
|
90
|
+
};
|
|
91
|
+
if (options.module)
|
|
92
|
+
body.module = options.module;
|
|
93
|
+
const data = await api.post(`/orgs/${orgId}/projects/${project.id}/import`, body);
|
|
94
|
+
let publishedCount = 0;
|
|
95
|
+
if (options.publish) {
|
|
96
|
+
spinner.text = 'Publishing imported keys...';
|
|
97
|
+
const keyNames = new Set(Object.keys(translations));
|
|
98
|
+
const keyIds = [];
|
|
99
|
+
let page = 1;
|
|
100
|
+
const pageSize = 100;
|
|
101
|
+
while (true) {
|
|
102
|
+
const keysPage = await api.get(`/orgs/${orgId}/projects/${project.id}/keys`, {
|
|
103
|
+
page: String(page),
|
|
104
|
+
pageSize: String(pageSize)
|
|
105
|
+
});
|
|
106
|
+
for (const key of (keysPage.data || [])) {
|
|
107
|
+
if (keyNames.has(key.key)) {
|
|
108
|
+
keyIds.push(key.id);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const totalPages = keysPage.pagination?.totalPages || 1;
|
|
112
|
+
if (page >= totalPages)
|
|
113
|
+
break;
|
|
114
|
+
page += 1;
|
|
115
|
+
}
|
|
116
|
+
if (keyIds.length > 0) {
|
|
117
|
+
await api.post(`/orgs/${orgId}/projects/${project.id}/keys/bulk-publish`, {
|
|
118
|
+
keyIds,
|
|
119
|
+
published: true
|
|
120
|
+
});
|
|
121
|
+
publishedCount = keyIds.length;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
spinner.succeed(chalk.green('Import completed successfully'));
|
|
125
|
+
console.log(chalk.green(`\n✓ Created: ${data.created}`));
|
|
126
|
+
console.log(chalk.blue(`✓ Updated: ${data.updated}`));
|
|
127
|
+
console.log(chalk.yellow(`⊘ Skipped: ${data.skipped}`));
|
|
128
|
+
if (options.publish) {
|
|
129
|
+
console.log(chalk.magenta(`✓ Published: ${publishedCount}`));
|
|
130
|
+
}
|
|
131
|
+
console.log(chalk.white(` Total: ${data.total}`));
|
|
132
|
+
console.log('');
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
spinner.fail(chalk.red('Failed to import translations'));
|
|
136
|
+
if (error.code === 'ENOENT') {
|
|
137
|
+
console.error(chalk.red(`Error: File not found: ${filePath}\n`));
|
|
138
|
+
process.exitCode = 1;
|
|
139
|
+
}
|
|
140
|
+
else if (error instanceof SyntaxError) {
|
|
141
|
+
console.error(chalk.red(`Error: Invalid JSON file\n`));
|
|
142
|
+
process.exitCode = 1;
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
146
|
+
process.exitCode = 1;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=import.js.map
|
package/dist/commands/init.js
CHANGED
|
@@ -3,7 +3,6 @@ import chalk from 'chalk';
|
|
|
3
3
|
import ora from 'ora';
|
|
4
4
|
import { config } from '../config.js';
|
|
5
5
|
import { authenticate } from '../auth.js';
|
|
6
|
-
import { initializeSupabase } from '../supabase.js';
|
|
7
6
|
import { showLogo } from '../utils/banner.js';
|
|
8
7
|
export async function initCommand() {
|
|
9
8
|
// Show logo
|
|
@@ -25,15 +24,6 @@ export async function initCommand() {
|
|
|
25
24
|
return;
|
|
26
25
|
}
|
|
27
26
|
}
|
|
28
|
-
// Initialize Supabase client (with hardcoded credentials - user doesn't need to know)
|
|
29
|
-
try {
|
|
30
|
-
initializeSupabase();
|
|
31
|
-
}
|
|
32
|
-
catch (error) {
|
|
33
|
-
console.error(chalk.red('✗ Failed to connect to Langctl servers'));
|
|
34
|
-
console.error(chalk.red('Please check your internet connection and try again.\n'));
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
27
|
// Ask for API key
|
|
38
28
|
const { apiKey } = await inquirer.prompt([
|
|
39
29
|
{
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List translation keys
|
|
3
|
+
*/
|
|
4
|
+
export declare function listKeysCommand(projectSlug: string, options: any): Promise<void>;
|
|
5
|
+
/**
|
|
6
|
+
* Get single key
|
|
7
|
+
*/
|
|
8
|
+
export declare function getKeyCommand(projectSlug: string, keyName: string): Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* Create translation key
|
|
11
|
+
*/
|
|
12
|
+
export declare function createKeyCommand(projectSlug: string, keyName: string, options: any): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Delete translation key
|
|
15
|
+
*/
|
|
16
|
+
export declare function deleteKeyCommand(projectSlug: string, keyName: string): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Update single translation
|
|
19
|
+
*/
|
|
20
|
+
export declare function translateKeyCommand(projectSlug: string, keyName: string, options: any): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Publish/unpublish keys
|
|
23
|
+
*/
|
|
24
|
+
export declare function publishKeysCommand(projectSlug: string, keys: string[], options: any): Promise<void>;
|
|
25
|
+
//# sourceMappingURL=keys.d.ts.map
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
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
|
+
* 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();
|
|
41
|
+
try {
|
|
42
|
+
const project = await resolveProject(orgId, projectSlug);
|
|
43
|
+
const limit = Math.max(1, parseInt(options.limit || '100', 10) || 100);
|
|
44
|
+
const offset = Math.max(0, parseInt(options.offset || '0', 10) || 0);
|
|
45
|
+
const skipWithinPage = offset % limit;
|
|
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('');
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
spinner.fail(chalk.red('Failed to list keys'));
|
|
90
|
+
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
91
|
+
process.exitCode = 1;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Get single key
|
|
96
|
+
*/
|
|
97
|
+
export async function getKeyCommand(projectSlug, keyName) {
|
|
98
|
+
if (!isAuthenticated()) {
|
|
99
|
+
console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
|
|
100
|
+
process.exitCode = 1;
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const orgId = config.get('organizationId');
|
|
104
|
+
if (!orgId) {
|
|
105
|
+
console.log(chalk.red('✗ Organization ID not found. Please run "langctl auth <api-key>" again.\n'));
|
|
106
|
+
process.exitCode = 1;
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const spinner = ora('Fetching key...').start();
|
|
110
|
+
try {
|
|
111
|
+
const project = await resolveProject(orgId, projectSlug);
|
|
112
|
+
const key = await findKeyByName(orgId, project.id, keyName);
|
|
113
|
+
if (!key) {
|
|
114
|
+
spinner.fail(chalk.red(`Key "${keyName}" not found`));
|
|
115
|
+
return;
|
|
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
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Create translation key
|
|
142
|
+
*/
|
|
143
|
+
export async function createKeyCommand(projectSlug, keyName, options) {
|
|
144
|
+
if (!isAuthenticated()) {
|
|
145
|
+
console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
|
|
146
|
+
process.exitCode = 1;
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const orgId = config.get('organizationId');
|
|
150
|
+
if (!orgId) {
|
|
151
|
+
console.log(chalk.red('✗ Organization ID not found. Please run "langctl auth <api-key>" again.\n'));
|
|
152
|
+
process.exitCode = 1;
|
|
153
|
+
return;
|
|
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;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Delete translation key
|
|
187
|
+
*/
|
|
188
|
+
export async function deleteKeyCommand(projectSlug, keyName) {
|
|
189
|
+
if (!isAuthenticated()) {
|
|
190
|
+
console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
|
|
191
|
+
process.exitCode = 1;
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const orgId = config.get('organizationId');
|
|
195
|
+
if (!orgId) {
|
|
196
|
+
console.log(chalk.red('✗ Organization ID not found. Please run "langctl auth <api-key>" again.\n'));
|
|
197
|
+
process.exitCode = 1;
|
|
198
|
+
return;
|
|
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
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Update single translation
|
|
221
|
+
*/
|
|
222
|
+
export async function translateKeyCommand(projectSlug, keyName, options) {
|
|
223
|
+
if (!isAuthenticated()) {
|
|
224
|
+
console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
|
|
225
|
+
process.exitCode = 1;
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
const orgId = config.get('organizationId');
|
|
229
|
+
if (!orgId) {
|
|
230
|
+
console.log(chalk.red('✗ Organization ID not found. Please run "langctl auth <api-key>" again.\n'));
|
|
231
|
+
process.exitCode = 1;
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const spinner = ora('Updating translation...').start();
|
|
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
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Publish/unpublish keys
|
|
255
|
+
*/
|
|
256
|
+
export async function publishKeysCommand(projectSlug, keys, options) {
|
|
257
|
+
if (!isAuthenticated()) {
|
|
258
|
+
console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
|
|
259
|
+
process.exitCode = 1;
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const orgId = config.get('organizationId');
|
|
263
|
+
if (!orgId) {
|
|
264
|
+
console.log(chalk.red('✗ Organization ID not found. Please run "langctl auth <api-key>" again.\n'));
|
|
265
|
+
process.exitCode = 1;
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
const action = options.unpublish ? 'unpublish' : 'publish';
|
|
269
|
+
const spinner = ora(`${action === 'publish' ? 'Publishing' : 'Unpublishing'} keys...`).start();
|
|
270
|
+
try {
|
|
271
|
+
const project = await resolveProject(orgId, projectSlug);
|
|
272
|
+
// List all keys to match names to IDs
|
|
273
|
+
const api = getApiClient();
|
|
274
|
+
const result = await api.get(`/orgs/${orgId}/projects/${project.id}/keys`, {
|
|
275
|
+
pageSize: '1000'
|
|
276
|
+
});
|
|
277
|
+
const keyIds = result.data
|
|
278
|
+
?.filter((k) => keys.includes(k.key))
|
|
279
|
+
.map((k) => k.id) || [];
|
|
280
|
+
if (keyIds.length === 0) {
|
|
281
|
+
spinner.fail(chalk.red('No matching keys found'));
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const response = await api.post(`/orgs/${orgId}/projects/${project.id}/keys/bulk-publish`, { keyIds, published: !options.unpublish });
|
|
285
|
+
spinner.succeed(chalk.green(response.message || `${action === 'publish' ? 'Published' : 'Unpublished'} ${response.count} key(s)`));
|
|
286
|
+
console.log('');
|
|
287
|
+
}
|
|
288
|
+
catch (error) {
|
|
289
|
+
spinner.fail(chalk.red(`Failed to ${action} keys`));
|
|
290
|
+
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
291
|
+
process.exitCode = 1;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
//# sourceMappingURL=keys.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get organization information
|
|
3
|
+
*/
|
|
4
|
+
export declare function orgInfoCommand(): Promise<void>;
|
|
5
|
+
/**
|
|
6
|
+
* Get organization statistics
|
|
7
|
+
*/
|
|
8
|
+
export declare function orgStatsCommand(): Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* Get organization plan and limits
|
|
11
|
+
*/
|
|
12
|
+
export declare function orgPlanCommand(): Promise<void>;
|
|
13
|
+
//# sourceMappingURL=org.d.ts.map
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { isAuthenticated } from '../auth.js';
|
|
4
|
+
import { getApiClient } from '../api.js';
|
|
5
|
+
import { config } from '../config.js';
|
|
6
|
+
/**
|
|
7
|
+
* Get organization information
|
|
8
|
+
*/
|
|
9
|
+
export async function orgInfoCommand() {
|
|
10
|
+
if (!isAuthenticated()) {
|
|
11
|
+
console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
|
|
12
|
+
process.exitCode = 1;
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const orgId = config.get('organizationId');
|
|
16
|
+
if (!orgId) {
|
|
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
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Get organization statistics
|
|
42
|
+
*/
|
|
43
|
+
export async function orgStatsCommand() {
|
|
44
|
+
if (!isAuthenticated()) {
|
|
45
|
+
console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
|
|
46
|
+
process.exitCode = 1;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const orgId = config.get('organizationId');
|
|
50
|
+
if (!orgId) {
|
|
51
|
+
console.log(chalk.red('✗ No organization configured. Please run "langctl init" first.\n'));
|
|
52
|
+
process.exitCode = 1;
|
|
53
|
+
return;
|
|
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
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Get organization plan and limits
|
|
82
|
+
*/
|
|
83
|
+
export async function orgPlanCommand() {
|
|
84
|
+
if (!isAuthenticated()) {
|
|
85
|
+
console.log(chalk.red('✗ Not authenticated. Please run "langctl auth <api-key>" first.\n'));
|
|
86
|
+
process.exitCode = 1;
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const orgId = config.get('organizationId');
|
|
90
|
+
if (!orgId) {
|
|
91
|
+
console.log(chalk.red('✗ No organization configured. Please run "langctl init" first.\n'));
|
|
92
|
+
process.exitCode = 1;
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const spinner = ora('Fetching plan information...').start();
|
|
96
|
+
try {
|
|
97
|
+
const api = getApiClient();
|
|
98
|
+
const org = await api.get(`/orgs/${orgId}`);
|
|
99
|
+
spinner.stop();
|
|
100
|
+
console.log(chalk.blue.bold('\n💎 Subscription Plan\n'));
|
|
101
|
+
console.log(chalk.white.bold(`Current Plan: ${org.plan.toUpperCase()}`));
|
|
102
|
+
console.log('');
|
|
103
|
+
console.log(chalk.white.bold('Plan Limits'));
|
|
104
|
+
// Display limits with "Unlimited" for -1 or null values
|
|
105
|
+
const formatLimit = (value) => (value === -1 || value === null) ? chalk.green('Unlimited') : chalk.gray(value.toString());
|
|
106
|
+
console.log(chalk.gray(` Max Members: ${formatLimit(org.maxMembers)}`));
|
|
107
|
+
console.log(chalk.gray(` Max Projects: ${formatLimit(org.maxProjects)}`));
|
|
108
|
+
console.log(chalk.gray(` Max Keys per Project: ${formatLimit(org.maxKeysPerProject)}`));
|
|
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
|
+
}
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=org.js.map
|
|
@@ -1,2 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List all projects
|
|
3
|
+
*/
|
|
1
4
|
export declare function projectsListCommand(): Promise<void>;
|
|
5
|
+
/**
|
|
6
|
+
* Create new project
|
|
7
|
+
*/
|
|
8
|
+
export declare function projectsCreateCommand(name: string, options: any): Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* Get project details
|
|
11
|
+
*/
|
|
12
|
+
export declare function projectsGetCommand(slug: string): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Update project
|
|
15
|
+
*/
|
|
16
|
+
export declare function projectsUpdateCommand(slug: string, options: any): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Delete project
|
|
19
|
+
*/
|
|
20
|
+
export declare function projectsDeleteCommand(slug: string): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Add language to project
|
|
23
|
+
*/
|
|
24
|
+
export declare function projectsAddLanguageCommand(slug: string, language: string): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Remove language from project
|
|
27
|
+
*/
|
|
28
|
+
export declare function projectsRemoveLanguageCommand(slug: string, language: string): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Get project statistics
|
|
31
|
+
*/
|
|
32
|
+
export declare function projectsStatsCommand(slug: string): Promise<void>;
|
|
2
33
|
//# sourceMappingURL=projects.d.ts.map
|