langctl 0.1.2 ā 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -0
- package/LICENSE +1 -1
- package/README.md +159 -1151
- package/dist/commands/auth.js +82 -24
- package/dist/commands/config.js +36 -25
- package/dist/commands/export.js +11 -112
- package/dist/commands/import.js +32 -164
- package/dist/commands/init.js +59 -80
- package/dist/commands/keys.js +145 -381
- package/dist/commands/org.js +45 -140
- package/dist/commands/projects.js +125 -390
- package/dist/commands/pull.js +78 -183
- package/dist/commands/push.js +125 -0
- package/dist/commands/team.js +68 -272
- package/dist/core/config.js +130 -0
- package/dist/core/errors.js +67 -0
- package/dist/core/files.js +38 -0
- package/dist/core/http.js +121 -0
- package/dist/core/output.js +96 -0
- package/dist/core/project.js +58 -0
- package/dist/core/prompts.js +56 -0
- package/dist/formats/index.js +285 -0
- package/dist/index.js +261 -508
- package/dist/version.js +4 -0
- package/package.json +22 -16
- package/.gitattributes +0 -2
- package/NEXT_PHASE.md +0 -618
- package/WORKING_INSTRUCTIONS.md +0 -1389
- package/dist/auth.d.ts +0 -37
- package/dist/auth.js +0 -112
- package/dist/commands/auth.d.ts +0 -3
- package/dist/commands/config.d.ts +0 -2
- package/dist/commands/debug.d.ts +0 -3
- package/dist/commands/debug.js +0 -57
- package/dist/commands/export.d.ts +0 -14
- package/dist/commands/import.d.ts +0 -11
- package/dist/commands/init.d.ts +0 -2
- package/dist/commands/keys.d.ts +0 -25
- package/dist/commands/org.d.ts +0 -13
- package/dist/commands/projects.d.ts +0 -30
- package/dist/commands/pull.d.ts +0 -9
- package/dist/commands/team.d.ts +0 -29
- package/dist/config.d.ts +0 -47
- package/dist/config.js +0 -69
- package/dist/exporters/index.d.ts +0 -36
- package/dist/exporters/index.js +0 -214
- package/dist/index.d.ts +0 -3
- package/dist/supabase.d.ts +0 -14
- package/dist/supabase.js +0 -30
- package/dist/utils/banner.d.ts +0 -9
- package/dist/utils/banner.js +0 -24
- package/translations/en.json +0 -5
- package/translations/en.xml +0 -9
- package/translations/fr.json +0 -4
- package/translations/fr.xml +0 -7
- package/translations/hi.json +0 -4
- package/translations/hi.xml +0 -7
- package/translations/ios/en.strings +0 -9
- package/translations/ios/fr.strings +0 -6
- package/translations/ios/hi.strings +0 -6
- package/translations/ios/ja.strings +0 -6
- package/translations/ja.json +0 -4
- package/translations/ja.xml +0 -7
package/dist/commands/init.js
CHANGED
|
@@ -1,87 +1,66 @@
|
|
|
1
|
-
import inquirer from 'inquirer';
|
|
2
1
|
import chalk from 'chalk';
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
]);
|
|
23
|
-
if (!confirm) {
|
|
24
|
-
console.log(chalk.yellow('\nSetup cancelled.'));
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { PROJECT_CONFIG_FILE, resolveCredentials, writeProjectConfig } from '../core/config.js';
|
|
5
|
+
import { usageError } from '../core/errors.js';
|
|
6
|
+
import { getSession } from '../core/http.js';
|
|
7
|
+
import { isInteractive, log, printJson, runtime } from '../core/output.js';
|
|
8
|
+
import { input, password } from '../core/prompts.js';
|
|
9
|
+
import { FORMATS, getFormat } from '../formats/index.js';
|
|
10
|
+
import { saveApiKey } from './auth.js';
|
|
11
|
+
/**
|
|
12
|
+
* Set up a repository: authenticate if needed, then write langctl.json so that
|
|
13
|
+
* `langctl pull` / `langctl push` need no arguments (locally and in CI).
|
|
14
|
+
*/
|
|
15
|
+
export async function initCommand(opts) {
|
|
16
|
+
const interactive = isInteractive();
|
|
17
|
+
const target = join(process.cwd(), PROJECT_CONFIG_FILE);
|
|
18
|
+
if (existsSync(target) && !opts.force) {
|
|
19
|
+
throw usageError(`${PROJECT_CONFIG_FILE} already exists here.`, 'Edit it directly, or re-run with --force to replace it.');
|
|
27
20
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
21
|
+
if (!resolveCredentials()) {
|
|
22
|
+
if (!interactive)
|
|
23
|
+
throw usageError('Not authenticated.', 'Set LANGCTL_API_KEY, or run "langctl auth --stdin" first.');
|
|
24
|
+
log.info(`Create an API key at ${chalk.cyan('https://app.langctl.com/organization/api-keys')}`);
|
|
25
|
+
const org = await saveApiKey(await password('Paste your API key'));
|
|
26
|
+
log.success(`Authenticated to ${chalk.bold(org.name)}`);
|
|
31
27
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
28
|
+
const session = await getSession();
|
|
29
|
+
const projects = await session.api.get(`/orgs/${session.orgId}/projects`);
|
|
30
|
+
if (projects.length === 0) {
|
|
31
|
+
throw usageError('This organization has no projects yet.', 'Create one with "langctl projects create <name>" or at https://app.langctl.com.');
|
|
36
32
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
{
|
|
40
|
-
type: 'password',
|
|
41
|
-
name: 'apiKey',
|
|
42
|
-
message: 'Enter your API key:',
|
|
43
|
-
mask: '*',
|
|
44
|
-
filter: (input) => input?.trim(),
|
|
45
|
-
validate: (input) => {
|
|
46
|
-
const value = (input || '').trim();
|
|
47
|
-
if (!value || !value.startsWith('lc_')) {
|
|
48
|
-
return 'Please enter a valid API key (starts with lc_)';
|
|
49
|
-
}
|
|
50
|
-
if (value.length !== 67) {
|
|
51
|
-
return 'Invalid API key format (should be 67 characters)';
|
|
52
|
-
}
|
|
53
|
-
return true;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
]);
|
|
57
|
-
// Authenticate
|
|
58
|
-
const spinner = ora('Verifying API key...').start();
|
|
59
|
-
const result = await authenticate(apiKey);
|
|
60
|
-
if (result.success) {
|
|
61
|
-
spinner.succeed(chalk.green(`Authenticated as ${result.data?.organization.name}`));
|
|
33
|
+
let project = opts.project ? projects.find(p => p.slug === opts.project) : undefined;
|
|
34
|
+
if (opts.project && !project) {
|
|
35
|
+
throw usageError(`Project "${opts.project}" not found.`, `Available: ${projects.map(p => p.slug).join(', ')}`);
|
|
62
36
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
37
|
+
if (!project) {
|
|
38
|
+
if (projects.length === 1)
|
|
39
|
+
project = projects[0];
|
|
40
|
+
else if (!interactive)
|
|
41
|
+
throw usageError('Several projects exist ā choose one with --project <slug>.', `Available: ${projects.map(p => p.slug).join(', ')}`);
|
|
42
|
+
else
|
|
43
|
+
project = await choose('Project', projects.map(p => ({ label: `${p.name} ${chalk.dim(`(${p.slug})`)}`, value: p })));
|
|
44
|
+
}
|
|
45
|
+
let format = opts.format ? getFormat(opts.format) : undefined;
|
|
46
|
+
if (!format) {
|
|
47
|
+
format = interactive ? await choose('Format', FORMATS.map(f => ({ label: f.label, value: f }))) : getFormat('json');
|
|
48
|
+
}
|
|
49
|
+
const output = opts.output ?? (interactive ? await input('Where should translation files go', format.defaultOutput) : format.defaultOutput);
|
|
50
|
+
const config = { project: project.slug, format: format.id, output };
|
|
51
|
+
writeProjectConfig(target, config);
|
|
52
|
+
if (runtime.json)
|
|
53
|
+
return printJson({ created: target, config });
|
|
54
|
+
log.success(`Wrote ${PROJECT_CONFIG_FILE} for ${chalk.bold(project.name)} (${project.languages.join(', ')})`);
|
|
55
|
+
log.info(`\nNext:\n ${chalk.cyan('langctl pull')} download translations into ${output}\n ${chalk.cyan('langctl push')} upload new ${project.defaultLanguage} strings\n ${chalk.cyan('langctl pull --check')} fail CI when committed files are out of date`);
|
|
56
|
+
}
|
|
57
|
+
async function choose(label, options) {
|
|
58
|
+
options.forEach((o, i) => log.info(` ${chalk.cyan(String(i + 1).padStart(2))} ${o.label}`));
|
|
59
|
+
for (;;) {
|
|
60
|
+
const answer = await input(`${label} [1-${options.length}]`, '1');
|
|
61
|
+
const n = Number(answer);
|
|
62
|
+
if (Number.isInteger(n) && n >= 1 && n <= options.length)
|
|
63
|
+
return options[n - 1].value;
|
|
64
|
+
log.warn(`Enter a number between 1 and ${options.length}.`);
|
|
70
65
|
}
|
|
71
|
-
// Ask for default settings
|
|
72
|
-
const { defaultLanguage } = await inquirer.prompt([
|
|
73
|
-
{
|
|
74
|
-
type: 'input',
|
|
75
|
-
name: 'defaultLanguage',
|
|
76
|
-
message: 'Default language code:',
|
|
77
|
-
default: 'en'
|
|
78
|
-
}
|
|
79
|
-
]);
|
|
80
|
-
config.set('defaultLanguage', defaultLanguage);
|
|
81
|
-
console.log(chalk.green.bold('\nā Setup complete!\n'));
|
|
82
|
-
console.log(chalk.gray(`Configuration saved to: ${config.getConfigPath()}\n`));
|
|
83
|
-
console.log(chalk.blue('Next steps:'));
|
|
84
|
-
console.log(chalk.white(' 1. Run "langctl projects list" to see your projects'));
|
|
85
|
-
console.log(chalk.white(' 2. Run "langctl pull <project-slug>" to download translations\n'));
|
|
86
66
|
}
|
|
87
|
-
//# sourceMappingURL=init.js.map
|