@kopynator/cli 1.4.1 → 1.5.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
@@ -25,7 +25,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
25
25
 
26
26
  // src/index.ts
27
27
  var import_commander = require("commander");
28
- var import_chalk5 = __toESM(require("chalk"));
28
+ var import_chalk6 = __toESM(require("chalk"));
29
29
 
30
30
  // src/commands/init.ts
31
31
  var import_inquirer = __toESM(require("inquirer"));
@@ -689,15 +689,95 @@ async function uploadCommand(options) {
689
689
  }
690
690
  }
691
691
 
692
+ // src/commands/limits.ts
693
+ var import_chalk5 = __toESM(require("chalk"));
694
+ var import_fs5 = __toESM(require("fs"));
695
+ var import_path5 = __toESM(require("path"));
696
+ function resolveApiKey() {
697
+ const cwd = process.cwd();
698
+ const fromJson = (p) => {
699
+ try {
700
+ if (import_fs5.default.existsSync(p)) {
701
+ const cfg = JSON.parse(import_fs5.default.readFileSync(p, "utf-8"));
702
+ if (cfg && cfg.apiKey) return { apiKey: cfg.apiKey, baseUrl: cfg.baseUrl };
703
+ }
704
+ } catch {
705
+ }
706
+ return null;
707
+ };
708
+ const fromAppFile = (p) => {
709
+ try {
710
+ if (import_fs5.default.existsSync(p)) {
711
+ const content = import_fs5.default.readFileSync(p, "utf-8");
712
+ const match = content.match(/apiKey:\s*['"]([^'"]+)['"]/);
713
+ if (match) return { apiKey: match[1] };
714
+ }
715
+ } catch {
716
+ }
717
+ return null;
718
+ };
719
+ return fromJson(import_path5.default.join(cwd, "kopynator.config.json")) || fromJson(import_path5.default.join(cwd, "src/kopynator.config.json")) || fromAppFile(import_path5.default.join(cwd, "src/app/app.config.ts")) || fromAppFile(import_path5.default.join(cwd, "src/app/app.module.ts")) || (process.env.KOPYNATOR_API_KEY ? { apiKey: process.env.KOPYNATOR_API_KEY.trim(), baseUrl: process.env.KOPYNATOR_BASE_URL?.trim() } : null);
720
+ }
721
+ function formatLimit(value) {
722
+ return value === -1 ? "Unlimited" : String(value);
723
+ }
724
+ async function limitsCommand() {
725
+ const resolved = resolveApiKey();
726
+ if (!resolved) {
727
+ console.log(import_chalk5.default.red("\n\u2716 No API key found."));
728
+ console.log(import_chalk5.default.gray(" Add it to kopynator.config.json or set KOPYNATOR_API_KEY.\n"));
729
+ process.exit(1);
730
+ return;
731
+ }
732
+ const base = (resolved.baseUrl || process.env.KOPYNATOR_BASE_URL || "https://api.kopynator.com").replace(/\/+$/, "").replace(/\/api$/, "");
733
+ const url = `${base}/tokens/limits?token=${encodeURIComponent(resolved.apiKey)}`;
734
+ try {
735
+ const res = await fetch(url, { headers: { "x-kopynator-version": "1.5.0" } });
736
+ if (!res.ok) {
737
+ const body = await res.text().catch(() => "");
738
+ console.log(import_chalk5.default.red(`
739
+ \u2716 Could not fetch limits (HTTP ${res.status}). ${body}
740
+ `));
741
+ process.exit(1);
742
+ return;
743
+ }
744
+ const data = await res.json();
745
+ const limits = data.limits || {};
746
+ const usage = data.usage || {};
747
+ const row = (label, used, limit) => {
748
+ const usedPart = used === void 0 ? "" : `${used} / `;
749
+ const text = ` ${label.padEnd(9)} ${usedPart}${formatLimit(limit)}`;
750
+ const reached = limit !== -1 && used !== void 0 && used >= limit;
751
+ return reached ? import_chalk5.default.red(`${text} (limit reached \u2014 upgrade your plan)`) : import_chalk5.default.green(text);
752
+ };
753
+ console.log("");
754
+ console.log(import_chalk5.default.bold("\u{1F4CA} Kopynator \u2014 plan limits (per organization)"));
755
+ const planLabel = import_chalk5.default.cyan((data.plan || "free").toUpperCase());
756
+ const statusLabel = data.active ? import_chalk5.default.green("active") : import_chalk5.default.yellow("inactive/expired \u2192 free limits apply");
757
+ console.log(` Plan: ${planLabel} (${statusLabel})`);
758
+ console.log("");
759
+ console.log(row("Projects", usage.projects, limits.projects));
760
+ console.log(row("Keys", usage.keys, limits.keys));
761
+ console.log(row("Members", void 0, limits.members));
762
+ console.log("");
763
+ } catch (error) {
764
+ console.log(import_chalk5.default.red(`
765
+ \u2716 Request failed: ${error?.message || error}
766
+ `));
767
+ process.exit(1);
768
+ }
769
+ }
770
+
692
771
  // src/index.ts
693
772
  var program = new import_commander.Command();
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"));
773
+ program.name("kopynator").description("Kopynator CLI - Manage your i18n workflow").version("1.5.1", "-v, --version").helpOption("-h, --help", "Display help for command").addHelpText("beforeAll", import_chalk6.default.blue("\n\u{1F44B} Welcome to Kopynator CLI!\n"));
695
774
  program.command("init").description("Initialize Kopynator in your project").action(initCommand);
696
775
  program.command("check").description("Validate your local JSON translation files").action(checkCommand);
697
776
  program.command("sync").description("Sync your translations with the Kopynator Cloud").action(syncCommand);
698
777
  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 }));
778
+ program.command("limits").description("Show your plan limits and current usage (projects, keys, members)").action(limitsCommand);
699
779
  program.command("help").description("Show help for all commands").action(() => {
700
- console.log(import_chalk5.default.blue("\u{1F44B} Kopynator CLI - Comandos disponibles:\n"));
780
+ console.log(import_chalk6.default.blue("\u{1F44B} Kopynator CLI - Comandos disponibles:\n"));
701
781
  program.outputHelp();
702
782
  });
703
783
  program.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kopynator/cli",
3
- "version": "1.4.1",
3
+ "version": "1.5.1",
4
4
  "description": "CLI tool for Kopynator - The i18n management solution",
5
5
  "bin": {
6
6
  "kopynator": "dist/index.js"
@@ -23,7 +23,7 @@
23
23
  "author": "Carlos Florio",
24
24
  "license": "ISC",
25
25
  "dependencies": {
26
- "@kopynator/core": "^1.0.1",
26
+ "@kopynator/core": "^2.0.0",
27
27
  "chalk": "^4.1.2",
28
28
  "commander": "^11.1.0",
29
29
  "inquirer": "^8.2.6",
@@ -2,3 +2,4 @@ export * from './init';
2
2
  export * from './check';
3
3
  export * from './sync';
4
4
  export * from './upload';
5
+ export * from './limits';
@@ -0,0 +1,110 @@
1
+ import chalk from 'chalk';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+
5
+ interface ResolvedKey {
6
+ apiKey: string;
7
+ baseUrl?: string;
8
+ }
9
+
10
+ /**
11
+ * Resolve the API key the same way `sync`/`upload` do: config JSON first, then env.
12
+ */
13
+ function resolveApiKey(): ResolvedKey | null {
14
+ const cwd = process.cwd();
15
+
16
+ const fromJson = (p: string): ResolvedKey | null => {
17
+ try {
18
+ if (fs.existsSync(p)) {
19
+ const cfg = JSON.parse(fs.readFileSync(p, 'utf-8'));
20
+ if (cfg && cfg.apiKey) return { apiKey: cfg.apiKey, baseUrl: cfg.baseUrl };
21
+ }
22
+ } catch {
23
+ /* ignore malformed config */
24
+ }
25
+ return null;
26
+ };
27
+
28
+ const fromAppFile = (p: string): ResolvedKey | null => {
29
+ try {
30
+ if (fs.existsSync(p)) {
31
+ const content = fs.readFileSync(p, 'utf-8');
32
+ const match = content.match(/apiKey:\s*['"]([^'"]+)['"]/);
33
+ if (match) return { apiKey: match[1] };
34
+ }
35
+ } catch {
36
+ /* ignore */
37
+ }
38
+ return null;
39
+ };
40
+
41
+ return (
42
+ fromJson(path.join(cwd, 'kopynator.config.json')) ||
43
+ fromJson(path.join(cwd, 'src/kopynator.config.json')) ||
44
+ fromAppFile(path.join(cwd, 'src/app/app.config.ts')) ||
45
+ fromAppFile(path.join(cwd, 'src/app/app.module.ts')) ||
46
+ (process.env.KOPYNATOR_API_KEY
47
+ ? { apiKey: process.env.KOPYNATOR_API_KEY.trim(), baseUrl: process.env.KOPYNATOR_BASE_URL?.trim() }
48
+ : null)
49
+ );
50
+ }
51
+
52
+ function formatLimit(value: number): string {
53
+ return value === -1 ? 'Unlimited' : String(value);
54
+ }
55
+
56
+ /**
57
+ * `kopynator limits` — show the current plan, its per-organization limits and usage.
58
+ */
59
+ export async function limitsCommand(): Promise<void> {
60
+ const resolved = resolveApiKey();
61
+ if (!resolved) {
62
+ console.log(chalk.red('\n✖ No API key found.'));
63
+ console.log(chalk.gray(' Add it to kopynator.config.json or set KOPYNATOR_API_KEY.\n'));
64
+ process.exit(1);
65
+ return;
66
+ }
67
+
68
+ // Endpoint lives outside the /api prefix (like /tokens/fetch), so normalise a trailing /api away.
69
+ const base = (resolved.baseUrl || process.env.KOPYNATOR_BASE_URL || 'https://api.kopynator.com')
70
+ .replace(/\/+$/, '')
71
+ .replace(/\/api$/, '');
72
+ const url = `${base}/tokens/limits?token=${encodeURIComponent(resolved.apiKey)}`;
73
+
74
+ try {
75
+ const res = await fetch(url, { headers: { 'x-kopynator-version': '1.5.0' } });
76
+ if (!res.ok) {
77
+ const body = await res.text().catch(() => '');
78
+ console.log(chalk.red(`\n✖ Could not fetch limits (HTTP ${res.status}). ${body}\n`));
79
+ process.exit(1);
80
+ return;
81
+ }
82
+
83
+ const data: any = await res.json();
84
+ const limits = data.limits || {};
85
+ const usage = data.usage || {};
86
+
87
+ const row = (label: string, used: number | undefined, limit: number): string => {
88
+ const usedPart = used === undefined ? '' : `${used} / `;
89
+ const text = ` ${label.padEnd(9)} ${usedPart}${formatLimit(limit)}`;
90
+ const reached = limit !== -1 && used !== undefined && used >= limit;
91
+ return reached ? chalk.red(`${text} (limit reached — upgrade your plan)`) : chalk.green(text);
92
+ };
93
+
94
+ console.log('');
95
+ console.log(chalk.bold('📊 Kopynator — plan limits (per organization)'));
96
+ const planLabel = chalk.cyan((data.plan || 'free').toUpperCase());
97
+ const statusLabel = data.active
98
+ ? chalk.green('active')
99
+ : chalk.yellow('inactive/expired → free limits apply');
100
+ console.log(` Plan: ${planLabel} (${statusLabel})`);
101
+ console.log('');
102
+ console.log(row('Projects', usage.projects, limits.projects));
103
+ console.log(row('Keys', usage.keys, limits.keys));
104
+ console.log(row('Members', undefined, limits.members));
105
+ console.log('');
106
+ } catch (error: any) {
107
+ console.log(chalk.red(`\n✖ Request failed: ${error?.message || error}\n`));
108
+ process.exit(1);
109
+ }
110
+ }
package/src/index.ts CHANGED
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
3
  import chalk from 'chalk';
4
- import { initCommand, checkCommand, syncCommand, uploadCommand } from './commands';
4
+ import { initCommand, checkCommand, syncCommand, uploadCommand, limitsCommand } from './commands';
5
5
 
6
6
  const program = new Command();
7
7
 
8
8
  program
9
9
  .name('kopynator')
10
10
  .description('Kopynator CLI - Manage your i18n workflow')
11
- .version('1.4.1', '-v, --version')
11
+ .version('1.5.1', '-v, --version')
12
12
  .helpOption('-h, --help', 'Display help for command')
13
13
  .addHelpText('beforeAll', chalk.blue('\n👋 Welcome to Kopynator CLI!\n'));
14
14
 
@@ -34,6 +34,11 @@ program
34
34
  .option('-l, --lang <code>', 'Language code (default: inferred from filename)')
35
35
  .action((opts) => uploadCommand({ file: opts.file, lang: opts.lang }));
36
36
 
37
+ program
38
+ .command('limits')
39
+ .description('Show your plan limits and current usage (projects, keys, members)')
40
+ .action(limitsCommand);
41
+
37
42
  program
38
43
  .command('help')
39
44
  .description('Show help for all commands')