@kopynator/cli 1.3.1 → 1.4.1

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/dist/index.js CHANGED
@@ -341,10 +341,32 @@ function getTranslationDir(framework) {
341
341
  return import_path3.default.join(process.cwd(), "locales");
342
342
  }
343
343
  }
344
+ function loadJsonConfig(jsonPath) {
345
+ if (!import_fs3.default.existsSync(jsonPath)) return null;
346
+ try {
347
+ const config = JSON.parse(import_fs3.default.readFileSync(jsonPath, "utf-8"));
348
+ if (config.apiKey) return { apiKey: config.apiKey, baseUrl: config.baseUrl };
349
+ } catch (e) {
350
+ if (jsonPath.includes("kopynator.config.json")) {
351
+ console.log(import_chalk3.default.red("\u274C Error parsing kopynator.config.json"));
352
+ }
353
+ }
354
+ return null;
355
+ }
344
356
  function extractApiKey() {
345
- const appConfigPath = import_path3.default.join(process.cwd(), "src/app/app.config.ts");
346
- const appModulePath = import_path3.default.join(process.cwd(), "src/app/app.module.ts");
347
- const jsonConfigPath = import_path3.default.join(process.cwd(), "kopynator.config.json");
357
+ const cwd = process.cwd();
358
+ const configFromRoot = loadJsonConfig(import_path3.default.join(cwd, "kopynator.config.json"));
359
+ if (configFromRoot) {
360
+ console.log(import_chalk3.default.blue("\u2139\uFE0F Found API key in kopynator.config.json"));
361
+ return configFromRoot;
362
+ }
363
+ const configFromSrc = loadJsonConfig(import_path3.default.join(cwd, "src/kopynator.config.json"));
364
+ if (configFromSrc) {
365
+ console.log(import_chalk3.default.blue("\u2139\uFE0F Found API key in src/kopynator.config.json"));
366
+ return configFromSrc;
367
+ }
368
+ const appConfigPath = import_path3.default.join(cwd, "src/app/app.config.ts");
369
+ const appModulePath = import_path3.default.join(cwd, "src/app/app.module.ts");
348
370
  if (import_fs3.default.existsSync(appConfigPath)) {
349
371
  const content = import_fs3.default.readFileSync(appConfigPath, "utf-8");
350
372
  const apiKeyMatch = content.match(/apiKey:\s*['"]([^'"]+)['"]/);
@@ -361,16 +383,13 @@ function extractApiKey() {
361
383
  return { apiKey: apiKeyMatch[1] };
362
384
  }
363
385
  }
364
- if (import_fs3.default.existsSync(jsonConfigPath)) {
365
- try {
366
- const config = JSON.parse(import_fs3.default.readFileSync(jsonConfigPath, "utf-8"));
367
- if (config.apiKey) {
368
- console.log(import_chalk3.default.blue("\u2139\uFE0F Found API key in kopynator.config.json"));
369
- return { apiKey: config.apiKey, baseUrl: config.baseUrl };
370
- }
371
- } catch (e) {
372
- console.log(import_chalk3.default.red("\u274C Error parsing kopynator.config.json"));
373
- }
386
+ const envKey = process.env.KOPYNATOR_API_KEY?.trim();
387
+ if (envKey) {
388
+ console.log(import_chalk3.default.blue("\u2139\uFE0F Using API key from KOPYNATOR_API_KEY"));
389
+ return {
390
+ apiKey: envKey,
391
+ baseUrl: process.env.KOPYNATOR_BASE_URL?.trim() || void 0
392
+ };
374
393
  }
375
394
  return null;
376
395
  }
@@ -446,7 +465,7 @@ async function syncCommand() {
446
465
  const languagesUrl = `${baseUrl}/languages?token=${token}`;
447
466
  const languagesResponse = await fetch(languagesUrl, {
448
467
  headers: {
449
- "x-kopynator-version": "1.3.1"
468
+ "x-kopynator-version": "1.4.1"
450
469
  }
451
470
  });
452
471
  if (!languagesResponse.ok) {
@@ -466,7 +485,7 @@ async function syncCommand() {
466
485
  try {
467
486
  const translationResponse = await fetch(fetchUrl, {
468
487
  headers: {
469
- "x-kopynator-version": "1.3.1"
488
+ "x-kopynator-version": "1.4.1"
470
489
  }
471
490
  });
472
491
  if (!translationResponse.ok) {
@@ -513,10 +532,24 @@ function flatten(data, prefix = "") {
513
532
  }
514
533
  return result;
515
534
  }
535
+ function loadJsonConfig2(jsonPath) {
536
+ if (!import_fs4.default.existsSync(jsonPath)) return null;
537
+ try {
538
+ const config = JSON.parse(import_fs4.default.readFileSync(jsonPath, "utf-8"));
539
+ const key = config.apiKey ?? config.api_key;
540
+ if (key && typeof key === "string") return { apiKey: key, baseUrl: config.baseUrl };
541
+ } catch {
542
+ }
543
+ return null;
544
+ }
516
545
  function extractApiKey2() {
517
- const appConfigPath = import_path4.default.join(process.cwd(), "src/app/app.config.ts");
518
- const appModulePath = import_path4.default.join(process.cwd(), "src/app/app.module.ts");
519
- const jsonConfigPath = import_path4.default.join(process.cwd(), "kopynator.config.json");
546
+ const cwd = process.cwd();
547
+ const appConfigPath = import_path4.default.join(cwd, "src/app/app.config.ts");
548
+ const appModulePath = import_path4.default.join(cwd, "src/app/app.module.ts");
549
+ const configFromRoot = loadJsonConfig2(import_path4.default.join(cwd, "kopynator.config.json"));
550
+ if (configFromRoot) return configFromRoot;
551
+ const configFromSrc = loadJsonConfig2(import_path4.default.join(cwd, "src/kopynator.config.json"));
552
+ if (configFromSrc) return configFromSrc;
520
553
  if (import_fs4.default.existsSync(appConfigPath)) {
521
554
  const content = import_fs4.default.readFileSync(appConfigPath, "utf-8");
522
555
  const apiKeyMatch = content.match(/apiKey:\s*['"]([^'"]+)['"]/);
@@ -527,12 +560,12 @@ function extractApiKey2() {
527
560
  const apiKeyMatch = content.match(/apiKey:\s*['"]([^'"]+)['"]/);
528
561
  if (apiKeyMatch) return { apiKey: apiKeyMatch[1] };
529
562
  }
530
- if (import_fs4.default.existsSync(jsonConfigPath)) {
531
- try {
532
- const config = JSON.parse(import_fs4.default.readFileSync(jsonConfigPath, "utf-8"));
533
- if (config.apiKey) return { apiKey: config.apiKey, baseUrl: config.baseUrl };
534
- } catch {
535
- }
563
+ const envKey = process.env.KOPYNATOR_API_KEY?.trim();
564
+ if (envKey) {
565
+ return {
566
+ apiKey: envKey,
567
+ baseUrl: process.env.KOPYNATOR_BASE_URL?.trim() || void 0
568
+ };
536
569
  }
537
570
  return null;
538
571
  }
@@ -544,7 +577,18 @@ async function uploadCommand(options) {
544
577
  console.log(import_chalk4.default.bold.blue("\n\u{1F4E4} Upload translations to Kopynator Cloud...\n"));
545
578
  const config = extractApiKey2();
546
579
  if (!config) {
547
- console.log(import_chalk4.default.red("\u274C Could not find API key. Run `npx kopynator init` or set apiKey in kopynator.config.json / app.config.ts"));
580
+ const cwd = process.cwd();
581
+ const rootConfig = import_path4.default.join(cwd, "kopynator.config.json");
582
+ const srcConfig = import_path4.default.join(cwd, "src/kopynator.config.json");
583
+ console.log(import_chalk4.default.red("\u274C Could not find API key. Set it in kopynator.config.json, app.config.ts, or KOPYNATOR_API_KEY. Run `npx kopynator init` to create config."));
584
+ console.log(import_chalk4.default.gray(` Directorio actual: ${cwd}`));
585
+ console.log(import_chalk4.default.gray(` Comprobado: ${rootConfig} (${import_fs4.default.existsSync(rootConfig) ? "existe" : "no existe"})`));
586
+ console.log(import_chalk4.default.gray(` Comprobado: ${srcConfig} (${import_fs4.default.existsSync(srcConfig) ? "existe" : "no existe"})`));
587
+ if (import_fs4.default.existsSync(rootConfig) || import_fs4.default.existsSync(srcConfig)) {
588
+ console.log(import_chalk4.default.yellow('\u{1F4A1} Si el archivo existe, comprueba que tenga la propiedad "apiKey" (o "api_key") con un valor no vac\xEDo.'));
589
+ } else {
590
+ console.log(import_chalk4.default.yellow("\u{1F4A1} Ejecuta el comando desde la ra\xEDz del proyecto (donde est\xE1 package.json)."));
591
+ }
548
592
  return;
549
593
  }
550
594
  const fileOption = options.file;
@@ -558,7 +602,7 @@ async function uploadCommand(options) {
558
602
  return;
559
603
  }
560
604
  const lang = options.lang || inferLangFromFile(resolvedPath);
561
- const baseUrl = config.baseUrl || "https://api.kopynator.com/api/tokens";
605
+ const baseUrl = config.baseUrl || "https://api.kopynator.com/tokens";
562
606
  const token = config.apiKey;
563
607
  let raw;
564
608
  try {
@@ -583,43 +627,80 @@ async function uploadCommand(options) {
583
627
  batches.push(Object.fromEntries(entries.slice(i, i + BATCH_SIZE)));
584
628
  }
585
629
  const spinner = (0, import_ora2.default)(`Uploading ${total} keys (${lang})...`).start();
586
- try {
630
+ const DEFAULT_API_BASE = "https://api.kopynator.com/tokens";
631
+ const FALLBACK_API_BASE = "https://api.kopynator.com/api/tokens";
632
+ const doUpload = async (urlBase) => {
587
633
  let imported = 0;
588
634
  for (let i = 0; i < batches.length; i++) {
589
635
  spinner.text = `Uploading batch ${i + 1}/${batches.length}...`;
590
- const res = await fetch(`${baseUrl}/import`, {
636
+ const res = await fetch(`${urlBase}/import`, {
591
637
  method: "POST",
592
638
  headers: {
593
639
  "Content-Type": "application/json",
594
640
  "x-api-token": token,
595
- "x-kopynator-version": "1.3.1"
641
+ "x-kopynator-version": "1.4.1"
596
642
  },
597
643
  body: JSON.stringify({ lang, data: batches[i] })
598
644
  });
599
645
  if (!res.ok) {
600
646
  const errText = await res.text();
601
- spinner.fail(`Upload failed: ${res.status} ${errText}`);
602
- return;
647
+ let errMsg;
648
+ try {
649
+ const errJson = JSON.parse(errText);
650
+ errMsg = errJson.message || errJson.error || errText;
651
+ } catch {
652
+ errMsg = errText || res.statusText;
653
+ }
654
+ return { ok: false, status: res.status, errMsg };
603
655
  }
604
656
  const result = await res.json();
605
- imported += result.importedCount ?? batches[i].length;
657
+ imported += Number(result.importedCount ?? Object.keys(batches[i]).length);
606
658
  }
607
- spinner.succeed(import_chalk4.default.green(`Uploaded ${imported} keys for language "${lang}".`));
608
- console.log(import_chalk4.default.bold.green("\n\u{1F389} Upload completed. Run `npx kopynator sync` to pull latest from cloud.\n"));
659
+ return { ok: true, imported };
660
+ };
661
+ try {
662
+ let result = await doUpload(baseUrl);
663
+ let triedFallback = false;
664
+ if (!result.ok && result.status === 404 && baseUrl === DEFAULT_API_BASE) {
665
+ spinner.text = "Reintentando con URL alternativa...";
666
+ result = await doUpload(FALLBACK_API_BASE);
667
+ triedFallback = true;
668
+ }
669
+ if (result.ok) {
670
+ spinner.succeed(import_chalk4.default.green(`Uploaded ${result.imported} keys for language "${lang}".`));
671
+ console.log(import_chalk4.default.bold.green("\n\u{1F389} Upload completed. Run `npx kopynator sync` to pull latest from cloud.\n"));
672
+ return;
673
+ }
674
+ spinner.fail(`Upload failed: ${result.status} ${result.errMsg}`);
675
+ if (result.status === 401) {
676
+ console.log(import_chalk4.default.yellow("\u{1F4A1} Comprueba que el API key sea correcto (Dashboard \u2192 Settings \u2192 Tokens)."));
677
+ }
678
+ if (result.status === 404) {
679
+ console.log(import_chalk4.default.yellow("\u{1F4A1} Comprueba que la API est\xE9 desplegada y que la URL sea correcta (KOPYNATOR_BASE_URL si usas entorno propio)."));
680
+ }
681
+ console.log(import_chalk4.default.gray(` URL usada: ${baseUrl}/import`));
682
+ if (triedFallback) console.log(import_chalk4.default.gray(` (tambi\xE9n probada: ${FALLBACK_API_BASE}/import)`));
609
683
  } catch (error) {
610
- spinner.fail(`Upload failed: ${error instanceof Error ? error.message : "Unknown error"}`);
684
+ const msg = error instanceof Error ? error.message : String(error);
685
+ spinner.fail(`Upload failed: ${msg}`);
686
+ if (msg.includes("fetch") || msg.includes("ECONNREFUSED") || msg.includes("ENOTFOUND")) {
687
+ console.log(import_chalk4.default.yellow("\u{1F4A1} Comprueba conexi\xF3n y que la API est\xE9 disponible. Para entorno local: KOPYNATOR_BASE_URL=http://localhost:7300/api/tokens"));
688
+ }
611
689
  }
612
690
  }
613
691
 
614
692
  // src/index.ts
615
693
  var program = new import_commander.Command();
616
- program.name("kopynator").description("Kopynator CLI - Manage your i18n workflow").version("1.3.1");
694
+ program.name("kopynator").description("Kopynator CLI - Manage your i18n workflow").version("1.4.1", "-v, --version").helpOption("-h, --help", "Display help for command").addHelpText("beforeAll", import_chalk5.default.blue("\n\u{1F44B} Welcome to Kopynator CLI!\n"));
617
695
  program.command("init").description("Initialize Kopynator in your project").action(initCommand);
618
696
  program.command("check").description("Validate your local JSON translation files").action(checkCommand);
619
697
  program.command("sync").description("Sync your translations with the Kopynator Cloud").action(syncCommand);
620
698
  program.command("upload").description("Upload a JSON translation file to Kopynator Cloud").option("-f, --file <path>", "Path to the JSON file (e.g. es.json)").option("-l, --lang <code>", "Language code (default: inferred from filename)").action((opts) => uploadCommand({ file: opts.file, lang: opts.lang }));
699
+ program.command("help").description("Show help for all commands").action(() => {
700
+ console.log(import_chalk5.default.blue("\u{1F44B} Kopynator CLI - Comandos disponibles:\n"));
701
+ program.outputHelp();
702
+ });
621
703
  program.parse(process.argv);
622
704
  if (!process.argv.slice(2).length) {
623
- console.log(import_chalk5.default.blue("\u{1F44B} Welcome to Kopynator CLI!"));
624
705
  program.outputHelp();
625
706
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kopynator/cli",
3
- "version": "1.3.1",
3
+ "version": "1.4.1",
4
4
  "description": "CLI tool for Kopynator - The i18n management solution",
5
5
  "bin": {
6
6
  "kopynator": "dist/index.js"
@@ -65,15 +65,40 @@ function getTranslationDir(framework: string): string {
65
65
  }
66
66
 
67
67
  /**
68
- * Extract API key from Angular config files or JSON config
69
- * Tries: app.config.ts -> app.module.ts -> kopynator.config.json
68
+ * API key del proyecto primero (no hace falta en el comando), luego env para CI.
69
+ * Order: app.config.ts -> app.module.ts -> kopynator.config.json -> KOPYNATOR_API_KEY
70
70
  */
71
+ function loadJsonConfig(jsonPath: string): KopyConfig | null {
72
+ if (!fs.existsSync(jsonPath)) return null;
73
+ try {
74
+ const config = JSON.parse(fs.readFileSync(jsonPath, 'utf-8'));
75
+ if (config.apiKey) return { apiKey: config.apiKey, baseUrl: config.baseUrl };
76
+ } catch (e) {
77
+ if (jsonPath.includes('kopynator.config.json')) {
78
+ console.log(chalk.red('❌ Error parsing kopynator.config.json'));
79
+ }
80
+ }
81
+ return null;
82
+ }
83
+
71
84
  function extractApiKey(): KopyConfig | null {
72
- const appConfigPath = path.join(process.cwd(), 'src/app/app.config.ts');
73
- const appModulePath = path.join(process.cwd(), 'src/app/app.module.ts');
74
- const jsonConfigPath = path.join(process.cwd(), 'kopynator.config.json');
85
+ const cwd = process.cwd();
75
86
 
76
- // Try app.config.ts
87
+ // 1) kopynator.config.json (raíz o src/) — funciona aunque en app.config uses apiKey: kopynatorConfig.apiKey
88
+ const configFromRoot = loadJsonConfig(path.join(cwd, 'kopynator.config.json'));
89
+ if (configFromRoot) {
90
+ console.log(chalk.blue('ℹ️ Found API key in kopynator.config.json'));
91
+ return configFromRoot;
92
+ }
93
+ const configFromSrc = loadJsonConfig(path.join(cwd, 'src/kopynator.config.json'));
94
+ if (configFromSrc) {
95
+ console.log(chalk.blue('ℹ️ Found API key in src/kopynator.config.json'));
96
+ return configFromSrc;
97
+ }
98
+
99
+ // 2) Literales en app.config.ts / app.module.ts
100
+ const appConfigPath = path.join(cwd, 'src/app/app.config.ts');
101
+ const appModulePath = path.join(cwd, 'src/app/app.module.ts');
77
102
  if (fs.existsSync(appConfigPath)) {
78
103
  const content = fs.readFileSync(appConfigPath, 'utf-8');
79
104
  const apiKeyMatch = content.match(/apiKey:\s*['"]([^'"]+)['"]/);
@@ -82,8 +107,6 @@ function extractApiKey(): KopyConfig | null {
82
107
  return { apiKey: apiKeyMatch[1] };
83
108
  }
84
109
  }
85
-
86
- // Try app.module.ts
87
110
  if (fs.existsSync(appModulePath)) {
88
111
  const content = fs.readFileSync(appModulePath, 'utf-8');
89
112
  const apiKeyMatch = content.match(/apiKey:\s*['"]([^'"]+)['"]/);
@@ -93,19 +116,15 @@ function extractApiKey(): KopyConfig | null {
93
116
  }
94
117
  }
95
118
 
96
- // Try kopynator.config.json
97
- if (fs.existsSync(jsonConfigPath)) {
98
- try {
99
- const config = JSON.parse(fs.readFileSync(jsonConfigPath, 'utf-8'));
100
- if (config.apiKey) {
101
- console.log(chalk.blue('ℹ️ Found API key in kopynator.config.json'));
102
- return { apiKey: config.apiKey, baseUrl: config.baseUrl };
103
- }
104
- } catch (e) {
105
- console.log(chalk.red('❌ Error parsing kopynator.config.json'));
106
- }
119
+ // Env (CI)
120
+ const envKey = process.env.KOPYNATOR_API_KEY?.trim();
121
+ if (envKey) {
122
+ console.log(chalk.blue('ℹ️ Using API key from KOPYNATOR_API_KEY'));
123
+ return {
124
+ apiKey: envKey,
125
+ baseUrl: process.env.KOPYNATOR_BASE_URL?.trim() || undefined,
126
+ };
107
127
  }
108
-
109
128
  return null;
110
129
  }
111
130
 
@@ -204,7 +223,7 @@ export async function syncCommand() {
204
223
  const languagesUrl = `${baseUrl}/languages?token=${token}`;
205
224
  const languagesResponse = await fetch(languagesUrl, {
206
225
  headers: {
207
- 'x-kopynator-version': '1.3.1'
226
+ 'x-kopynator-version': '1.4.1'
208
227
  }
209
228
  });
210
229
  if (!languagesResponse.ok) {
@@ -230,7 +249,7 @@ export async function syncCommand() {
230
249
  try {
231
250
  const translationResponse = await fetch(fetchUrl, {
232
251
  headers: {
233
- 'x-kopynator-version': '1.3.1'
252
+ 'x-kopynator-version': '1.4.1'
234
253
  }
235
254
  });
236
255
 
@@ -27,11 +27,35 @@ function flatten(data: Record<string, unknown>, prefix = ''): Record<string, str
27
27
  return result;
28
28
  }
29
29
 
30
+ /**
31
+ * Obtiene el API key y baseUrl del proyecto para enviarlos en la petición.
32
+ * Prioridad: archivos del proyecto (no hace falta poner el key en el comando) → env para CI.
33
+ * El backend identifica el proyecto por el token en el header x-api-token.
34
+ */
35
+ function loadJsonConfig(jsonPath: string): KopyConfig | null {
36
+ if (!fs.existsSync(jsonPath)) return null;
37
+ try {
38
+ const config = JSON.parse(fs.readFileSync(jsonPath, 'utf-8'));
39
+ const key = config.apiKey ?? config.api_key;
40
+ if (key && typeof key === 'string') return { apiKey: key, baseUrl: config.baseUrl };
41
+ } catch {
42
+ // ignore
43
+ }
44
+ return null;
45
+ }
46
+
30
47
  function extractApiKey(): KopyConfig | null {
31
- const appConfigPath = path.join(process.cwd(), 'src/app/app.config.ts');
32
- const appModulePath = path.join(process.cwd(), 'src/app/app.module.ts');
33
- const jsonConfigPath = path.join(process.cwd(), 'kopynator.config.json');
48
+ const cwd = process.cwd();
49
+ const appConfigPath = path.join(cwd, 'src/app/app.config.ts');
50
+ const appModulePath = path.join(cwd, 'src/app/app.module.ts');
51
+
52
+ // 1) kopynator.config.json (raíz o src/) — así funciona aunque en app.config uses apiKey: kopynatorConfig.apiKey
53
+ const configFromRoot = loadJsonConfig(path.join(cwd, 'kopynator.config.json'));
54
+ if (configFromRoot) return configFromRoot;
55
+ const configFromSrc = loadJsonConfig(path.join(cwd, 'src/kopynator.config.json'));
56
+ if (configFromSrc) return configFromSrc;
34
57
 
58
+ // 2) Literales en app.config.ts / app.module.ts
35
59
  if (fs.existsSync(appConfigPath)) {
36
60
  const content = fs.readFileSync(appConfigPath, 'utf-8');
37
61
  const apiKeyMatch = content.match(/apiKey:\s*['"]([^'"]+)['"]/);
@@ -42,13 +66,14 @@ function extractApiKey(): KopyConfig | null {
42
66
  const apiKeyMatch = content.match(/apiKey:\s*['"]([^'"]+)['"]/);
43
67
  if (apiKeyMatch) return { apiKey: apiKeyMatch[1] };
44
68
  }
45
- if (fs.existsSync(jsonConfigPath)) {
46
- try {
47
- const config = JSON.parse(fs.readFileSync(jsonConfigPath, 'utf-8'));
48
- if (config.apiKey) return { apiKey: config.apiKey, baseUrl: config.baseUrl };
49
- } catch {
50
- // ignore
51
- }
69
+
70
+ // 2) Env (útil en CI donde no hay app.config/kopynator.config.json)
71
+ const envKey = process.env.KOPYNATOR_API_KEY?.trim();
72
+ if (envKey) {
73
+ return {
74
+ apiKey: envKey,
75
+ baseUrl: process.env.KOPYNATOR_BASE_URL?.trim() || undefined,
76
+ };
52
77
  }
53
78
  return null;
54
79
  }
@@ -66,7 +91,18 @@ export async function uploadCommand(options: { file?: string; lang?: string }) {
66
91
 
67
92
  const config = extractApiKey();
68
93
  if (!config) {
69
- console.log(chalk.red('❌ Could not find API key. Run `npx kopynator init` or set apiKey in kopynator.config.json / app.config.ts'));
94
+ const cwd = process.cwd();
95
+ const rootConfig = path.join(cwd, 'kopynator.config.json');
96
+ const srcConfig = path.join(cwd, 'src/kopynator.config.json');
97
+ console.log(chalk.red('❌ Could not find API key. Set it in kopynator.config.json, app.config.ts, or KOPYNATOR_API_KEY. Run `npx kopynator init` to create config.'));
98
+ console.log(chalk.gray(` Directorio actual: ${cwd}`));
99
+ console.log(chalk.gray(` Comprobado: ${rootConfig} (${fs.existsSync(rootConfig) ? 'existe' : 'no existe'})`));
100
+ console.log(chalk.gray(` Comprobado: ${srcConfig} (${fs.existsSync(srcConfig) ? 'existe' : 'no existe'})`));
101
+ if (fs.existsSync(rootConfig) || fs.existsSync(srcConfig)) {
102
+ console.log(chalk.yellow('💡 Si el archivo existe, comprueba que tenga la propiedad "apiKey" (o "api_key") con un valor no vacío.'));
103
+ } else {
104
+ console.log(chalk.yellow('💡 Ejecuta el comando desde la raíz del proyecto (donde está package.json).'));
105
+ }
70
106
  return;
71
107
  }
72
108
 
@@ -83,7 +119,9 @@ export async function uploadCommand(options: { file?: string; lang?: string }) {
83
119
  }
84
120
 
85
121
  const lang = options.lang || inferLangFromFile(resolvedPath);
86
- const baseUrl = config.baseUrl || 'https://api.kopynator.com/api/tokens';
122
+ // Backend expone /tokens/import sin prefijo /api (exclude en setGlobalPrefix)
123
+ const baseUrl = config.baseUrl || 'https://api.kopynator.com/tokens';
124
+ // Token enviado en la petición: el backend lo usa para saber a qué proyecto importar (como en la web)
87
125
  const token = config.apiKey;
88
126
 
89
127
  let raw: unknown;
@@ -114,32 +152,69 @@ export async function uploadCommand(options: { file?: string; lang?: string }) {
114
152
 
115
153
  const spinner = ora(`Uploading ${total} keys (${lang})...`).start();
116
154
 
117
- try {
155
+ const DEFAULT_API_BASE = 'https://api.kopynator.com/tokens';
156
+ const FALLBACK_API_BASE = 'https://api.kopynator.com/api/tokens';
157
+
158
+ const doUpload = async (urlBase: string): Promise<{ ok: true; imported: number } | { ok: false; status: number; errMsg: string }> => {
118
159
  let imported = 0;
119
160
  for (let i = 0; i < batches.length; i++) {
120
161
  spinner.text = `Uploading batch ${i + 1}/${batches.length}...`;
121
- const res = await fetch(`${baseUrl}/import`, {
162
+ const res = await fetch(`${urlBase}/import`, {
122
163
  method: 'POST',
123
164
  headers: {
124
165
  'Content-Type': 'application/json',
125
166
  'x-api-token': token,
126
- 'x-kopynator-version': '1.3.1',
167
+ 'x-kopynator-version': '1.4.1',
127
168
  },
128
169
  body: JSON.stringify({ lang, data: batches[i] }),
129
170
  });
130
-
131
171
  if (!res.ok) {
132
172
  const errText = await res.text();
133
- spinner.fail(`Upload failed: ${res.status} ${errText}`);
134
- return;
173
+ let errMsg: string;
174
+ try {
175
+ const errJson = JSON.parse(errText) as { message?: string; error?: string };
176
+ errMsg = errJson.message || errJson.error || errText;
177
+ } catch {
178
+ errMsg = errText || res.statusText;
179
+ }
180
+ return { ok: false, status: res.status, errMsg };
135
181
  }
136
182
  const result = (await res.json()) as { importedCount?: number };
137
- imported += result.importedCount ?? batches[i].length;
183
+ imported += Number(result.importedCount ?? Object.keys(batches[i]).length);
184
+ }
185
+ return { ok: true, imported };
186
+ };
187
+
188
+ try {
189
+ let result = await doUpload(baseUrl);
190
+ let triedFallback = false;
191
+
192
+ if (!result.ok && result.status === 404 && baseUrl === DEFAULT_API_BASE) {
193
+ spinner.text = 'Reintentando con URL alternativa...';
194
+ result = await doUpload(FALLBACK_API_BASE);
195
+ triedFallback = true;
196
+ }
197
+
198
+ if (result.ok) {
199
+ spinner.succeed(chalk.green(`Uploaded ${result.imported} keys for language "${lang}".`));
200
+ console.log(chalk.bold.green('\n🎉 Upload completed. Run `npx kopynator sync` to pull latest from cloud.\n'));
201
+ return;
138
202
  }
139
203
 
140
- spinner.succeed(chalk.green(`Uploaded ${imported} keys for language "${lang}".`));
141
- console.log(chalk.bold.green('\n🎉 Upload completed. Run `npx kopynator sync` to pull latest from cloud.\n'));
204
+ spinner.fail(`Upload failed: ${result.status} ${result.errMsg}`);
205
+ if (result.status === 401) {
206
+ console.log(chalk.yellow('💡 Comprueba que el API key sea correcto (Dashboard → Settings → Tokens).'));
207
+ }
208
+ if (result.status === 404) {
209
+ console.log(chalk.yellow('💡 Comprueba que la API esté desplegada y que la URL sea correcta (KOPYNATOR_BASE_URL si usas entorno propio).'));
210
+ }
211
+ console.log(chalk.gray(` URL usada: ${baseUrl}/import`));
212
+ if (triedFallback) console.log(chalk.gray(` (también probada: ${FALLBACK_API_BASE}/import)`));
142
213
  } catch (error) {
143
- spinner.fail(`Upload failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
214
+ const msg = error instanceof Error ? error.message : String(error);
215
+ spinner.fail(`Upload failed: ${msg}`);
216
+ if (msg.includes('fetch') || msg.includes('ECONNREFUSED') || msg.includes('ENOTFOUND')) {
217
+ console.log(chalk.yellow('💡 Comprueba conexión y que la API esté disponible. Para entorno local: KOPYNATOR_BASE_URL=http://localhost:7300/api/tokens'));
218
+ }
144
219
  }
145
220
  }
package/src/index.ts CHANGED
@@ -8,7 +8,9 @@ const program = new Command();
8
8
  program
9
9
  .name('kopynator')
10
10
  .description('Kopynator CLI - Manage your i18n workflow')
11
- .version('1.3.1');
11
+ .version('1.4.1', '-v, --version')
12
+ .helpOption('-h, --help', 'Display help for command')
13
+ .addHelpText('beforeAll', chalk.blue('\n👋 Welcome to Kopynator CLI!\n'));
12
14
 
13
15
  program
14
16
  .command('init')
@@ -32,9 +34,16 @@ program
32
34
  .option('-l, --lang <code>', 'Language code (default: inferred from filename)')
33
35
  .action((opts) => uploadCommand({ file: opts.file, lang: opts.lang }));
34
36
 
37
+ program
38
+ .command('help')
39
+ .description('Show help for all commands')
40
+ .action(() => {
41
+ console.log(chalk.blue('👋 Kopynator CLI - Comandos disponibles:\n'));
42
+ program.outputHelp();
43
+ });
44
+
35
45
  program.parse(process.argv);
36
46
 
37
47
  if (!process.argv.slice(2).length) {
38
- console.log(chalk.blue('👋 Welcome to Kopynator CLI!'));
39
48
  program.outputHelp();
40
49
  }