@tolgee/cli 2.8.1 → 2.8.3

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/cli.js CHANGED
@@ -16,6 +16,7 @@ import TagCommand from './commands/tag.js';
16
16
  import { getSingleOption } from './utils/getSingleOption.js';
17
17
  import { createTolgeeClient } from './client/TolgeeClient.js';
18
18
  import { projectIdFromKey } from './client/ApiClient.js';
19
+ import { printApiKeyLists } from './utils/apiKeyList.js';
19
20
  const NO_KEY_COMMANDS = ['login', 'logout', 'extract'];
20
21
  ansi.enabled = process.stdout.isTTY;
21
22
  function topLevelName(command) {
@@ -58,16 +59,21 @@ function loadProjectId(cmd) {
58
59
  }
59
60
  }
60
61
  }
61
- function validateOptions(cmd) {
62
+ async function validateOptions(cmd) {
62
63
  const opts = cmd.optsWithGlobals();
63
- if (!opts.apiKey) {
64
- exitWithError('No API key has been provided. You must either provide one via --api-key, or login via `tolgee login`.');
65
- }
66
64
  if (opts.projectId === -1) {
67
65
  error('No Project ID have been specified. You must either provide one via --project-id, or by setting up a `.tolgeerc` file.');
66
+ info('If you provide Project Api Key (PAK) via `--api-key`, Project ID is derived automatically.');
68
67
  info('Learn more about configuring the CLI here: https://tolgee.io/tolgee-cli/project-configuration');
69
68
  process.exit(1);
70
69
  }
70
+ if (!opts.apiKey) {
71
+ error(`Not authenticated for host ${ansi.blue(opts.apiUrl.hostname)} and project ${ansi.blue(opts.projectId)}.`);
72
+ info(`You must either provide api key via --api-key or login via \`tolgee login\` (for correct api url and project)`);
73
+ console.log('\nYou are logged into these projects:');
74
+ await printApiKeyLists();
75
+ process.exit(1);
76
+ }
71
77
  }
72
78
  const preHandler = (config) => async function (prog, cmd) {
73
79
  if (!NO_KEY_COMMANDS.includes(topLevelName(cmd))) {
@@ -1,17 +1,26 @@
1
1
  import { Command } from 'commander';
2
+ import ansi from 'ansi-colors';
2
3
  import { saveApiKey, removeApiKeys, clearAuthStore, } from '../config/credentials.js';
3
- import { success } from '../utils/logger.js';
4
+ import { exitWithError, success } from '../utils/logger.js';
4
5
  import { createTolgeeClient } from '../client/TolgeeClient.js';
6
+ import { printApiKeyLists } from '../utils/apiKeyList.js';
5
7
  async function loginHandler(key) {
6
8
  const opts = this.optsWithGlobals();
9
+ if (opts.list) {
10
+ printApiKeyLists();
11
+ return;
12
+ }
13
+ else if (!key) {
14
+ exitWithError('Missing argument [API Key]');
15
+ }
7
16
  const keyInfo = await createTolgeeClient({
8
17
  baseUrl: opts.apiUrl.toString(),
9
18
  apiKey: key,
10
19
  }).getApiKeyInfo();
11
20
  await saveApiKey(opts.apiUrl, keyInfo);
12
21
  success(keyInfo.type === 'PAK'
13
- ? `Logged in as ${keyInfo.username} on ${opts.apiUrl.hostname} for project ${keyInfo.project.name} (#${keyInfo.project.id}). Welcome back!`
14
- : `Logged in as ${keyInfo.username} on ${opts.apiUrl.hostname}. Welcome back!`);
22
+ ? `Logged in as ${keyInfo.username} on ${ansi.blue(opts.apiUrl.hostname)} for project ${ansi.blue(String(keyInfo.project.id))} (${keyInfo.project.name}). Welcome back!`
23
+ : `Logged in as ${keyInfo.username} on ${ansi.blue(opts.apiUrl.hostname)}. Welcome back!`);
15
24
  }
16
25
  async function logoutHandler() {
17
26
  const opts = this.optsWithGlobals();
@@ -26,7 +35,8 @@ async function logoutHandler() {
26
35
  export const Login = new Command()
27
36
  .name('login')
28
37
  .description('Login to Tolgee with an API key. You can be logged into multiple Tolgee instances at the same time by using --api-url')
29
- .argument('<API Key>', 'The API key. Can be either a personal access token, or a project key')
38
+ .option('-l, --list', 'List existing api keys')
39
+ .argument('[API Key]', 'The API key. Can be either a personal access token, or a project key')
30
40
  .action(loginHandler);
31
41
  export const Logout = new Command()
32
42
  .name('logout')
@@ -13,7 +13,7 @@ async function ensureConfigPath() {
13
13
  }
14
14
  }
15
15
  }
16
- async function loadStore() {
16
+ export async function loadStore() {
17
17
  try {
18
18
  await ensureConfigPath();
19
19
  const storeData = await readFile(API_TOKENS_FILE, 'utf8');
@@ -42,25 +42,34 @@ async function storePat(store, instance, pat) {
42
42
  },
43
43
  });
44
44
  }
45
- async function storePak(store, instance, projectId, pak) {
45
+ async function storePak(store, instance, project, pak) {
46
46
  return saveStore({
47
47
  ...store,
48
48
  [instance.hostname]: {
49
49
  ...(store[instance.hostname] || {}),
50
50
  projects: {
51
51
  ...(store[instance.hostname]?.projects || {}),
52
- [projectId.toString(10)]: pak,
52
+ [project.id.toString(10)]: pak,
53
+ },
54
+ projectDetails: {
55
+ ...(store[instance.hostname]?.projectDetails || {}),
56
+ [project.id.toString(10)]: { name: project.name },
53
57
  },
54
58
  },
55
59
  });
56
60
  }
61
+ async function removePak(store, instance, projectId) {
62
+ delete store[instance.hostname].projects?.[projectId.toString(10)];
63
+ delete store[instance.hostname].projectDetails?.[projectId.toString(10)];
64
+ return saveStore(store);
65
+ }
57
66
  export async function savePat(instance, pat) {
58
67
  const store = await loadStore();
59
68
  return storePat(store, instance, pat);
60
69
  }
61
- export async function savePak(instance, projectId, pak) {
70
+ export async function savePak(instance, project, pak) {
62
71
  const store = await loadStore();
63
- return storePak(store, instance, projectId, pak);
72
+ return storePak(store, instance, project, pak);
64
73
  }
65
74
  export async function getApiKey(instance, projectId) {
66
75
  const store = await loadStore();
@@ -84,7 +93,7 @@ export async function getApiKey(instance, projectId) {
84
93
  if (pak) {
85
94
  if (pak.expires !== 0 && Date.now() > pak.expires) {
86
95
  warn(`Your project API key for project #${projectId} on ${instance.hostname} expired.`);
87
- await storePak(store, instance, projectId, undefined);
96
+ await removePak(store, instance, projectId);
88
97
  return null;
89
98
  }
90
99
  return pak.token;
@@ -99,17 +108,15 @@ export async function saveApiKey(instance, token) {
99
108
  expires: token.expires,
100
109
  });
101
110
  }
102
- return storePak(store, instance, token.project.id, {
111
+ return storePak(store, instance, token.project, {
103
112
  token: token.key,
104
113
  expires: token.expires,
105
114
  });
106
115
  }
107
116
  export async function removeApiKeys(api) {
108
117
  const store = await loadStore();
109
- return saveStore({
110
- ...store,
111
- [api.hostname]: {},
112
- });
118
+ delete store[api.hostname];
119
+ return saveStore(store);
113
120
  }
114
121
  export async function clearAuthStore() {
115
122
  return saveStore({});
@@ -0,0 +1,46 @@
1
+ import ansi from 'ansi-colors';
2
+ import { loadStore } from '../config/credentials.js';
3
+ function getProjectName(projectId, projectDetails) {
4
+ return projectDetails?.[projectId]?.name;
5
+ }
6
+ function printToken(type, token, projectId, projectDetails) {
7
+ let result = type === 'PAK' ? ansi.green('PAK') : ansi.blue('PAT');
8
+ if (projectId !== undefined) {
9
+ const projectName = getProjectName(projectId, projectDetails);
10
+ result += '\t ' + ansi.red(`#${projectId}` + ' ' + (projectName ?? ''));
11
+ }
12
+ else {
13
+ result += '\t ' + ansi.yellow('<all projects>');
14
+ }
15
+ if (token.expires) {
16
+ result +=
17
+ '\t ' +
18
+ ansi.grey('expires ' + new Date(token.expires).toLocaleDateString());
19
+ }
20
+ else {
21
+ result += '\t ' + ansi.grey('never expires');
22
+ }
23
+ console.log(result);
24
+ }
25
+ export async function printApiKeyLists() {
26
+ const store = await loadStore();
27
+ const list = Object.entries(store);
28
+ if (list.length === 0) {
29
+ console.log(ansi.gray('No records\n'));
30
+ }
31
+ for (const [origin, server] of list) {
32
+ console.log(ansi.white('[') + ansi.red(origin) + ansi.white(']'));
33
+ if (server.user) {
34
+ printToken('PAT', server.user);
35
+ }
36
+ if (server.projects) {
37
+ for (const [project, token] of Object.entries(server.projects)) {
38
+ if (token) {
39
+ printToken('PAK', token, project, server.projectDetails);
40
+ }
41
+ }
42
+ }
43
+ console.log('\n');
44
+ }
45
+ return;
46
+ }
@@ -1,3 +1,4 @@
1
+ import { stdout } from 'process';
1
2
  import { getStackTrace } from './getStackTrace.js';
2
3
  const SYMBOLS = [' 🐁', ' 🐁 ', ' 🐁 ', '🐁 '];
3
4
  let debugEnabled = false;
@@ -59,21 +60,47 @@ export function warn(msg) {
59
60
  export function error(msg) {
60
61
  console.log(`🔴 ${msg}`);
61
62
  }
62
- export function exitWithError(err) {
63
+ export function printError(err, level = 0) {
63
64
  let message;
64
65
  let stack;
66
+ let cause;
65
67
  if (err instanceof Error) {
66
68
  message = err.message;
67
69
  stack = err.stack;
70
+ cause = err.cause;
68
71
  }
69
72
  else {
70
73
  message = err;
71
- stack = getStackTrace();
72
74
  }
73
- error(message);
75
+ if (level === 0) {
76
+ error(message);
77
+ }
78
+ else {
79
+ stdout.write('[cause] ');
80
+ }
74
81
  if (debugEnabled && stack) {
75
82
  console.log(stack);
76
83
  }
84
+ else if (level !== 0) {
85
+ console.log(message);
86
+ }
87
+ if (cause && level < 3) {
88
+ printError(cause, level + 1);
89
+ }
90
+ }
91
+ export function exitWithError(err) {
92
+ if (err instanceof Error) {
93
+ printError(err);
94
+ }
95
+ else {
96
+ error(err);
97
+ if (debugEnabled) {
98
+ console.log(getStackTrace());
99
+ }
100
+ }
101
+ if (!isDebugEnabled()) {
102
+ console.log('\nHINT: Use `--verbose` parameter to get full stack trace');
103
+ }
77
104
  process.exit(1);
78
105
  }
79
106
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tolgee/cli",
3
- "version": "2.8.1",
3
+ "version": "2.8.3",
4
4
  "type": "module",
5
5
  "description": "A tool to interact with the Tolgee Platform through CLI",
6
6
  "repository": {