clever-tools 3.12.0 → 3.13.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.
Files changed (42) hide show
  1. package/bin/clever.js +280 -6
  2. package/package.json +1 -1
  3. package/src/clever-client/auth-bridge.js +1 -1
  4. package/src/clever-client/operators.js +121 -0
  5. package/src/commands/addon.js +12 -7
  6. package/src/commands/cancel-deploy.js +3 -4
  7. package/src/commands/config.js +16 -20
  8. package/src/commands/console.js +4 -10
  9. package/src/commands/create.js +76 -9
  10. package/src/commands/delete.js +11 -10
  11. package/src/commands/deploy.js +35 -21
  12. package/src/commands/emails.js +174 -0
  13. package/src/commands/keycloak.js +138 -0
  14. package/src/commands/link.js +3 -1
  15. package/src/commands/makeDefault.js +2 -1
  16. package/src/commands/matomo.js +85 -0
  17. package/src/commands/metabase.js +112 -0
  18. package/src/commands/open.js +2 -5
  19. package/src/commands/otoroshi.js +138 -0
  20. package/src/commands/profile.js +2 -4
  21. package/src/commands/restart.js +1 -1
  22. package/src/commands/ssh-keys.js +159 -0
  23. package/src/commands/stop.js +1 -1
  24. package/src/commands/tcp-redirs.js +3 -3
  25. package/src/commands/tokens.js +16 -7
  26. package/src/commands/unlink.js +2 -1
  27. package/src/experimental-features.js +16 -15
  28. package/src/lib/operator-commands.js +281 -0
  29. package/src/lib/prompts.js +30 -0
  30. package/src/lib/slugify.js +10 -0
  31. package/src/models/addon.js +5 -2
  32. package/src/models/app_configuration.js +12 -9
  33. package/src/models/application.js +24 -12
  34. package/src/models/application_configuration.js +72 -72
  35. package/src/models/git.js +29 -1
  36. package/src/models/ids-resolver.js +37 -1
  37. package/src/models/interact.js +0 -24
  38. package/src/models/log-v4.js +13 -4
  39. package/src/models/operator.js +48 -0
  40. package/src/models/utils.js +21 -0
  41. package/src/parsers.js +14 -0
  42. package/src/prompt-password.js +0 -10
@@ -0,0 +1,85 @@
1
+ import {
2
+ operatorList,
3
+ operatorOpen,
4
+ operatorOpenLogs,
5
+ operatorOpenWebUi,
6
+ operatorPrint,
7
+ operatorReboot,
8
+ operatorRebuild,
9
+ } from '../lib/operator-commands.js';
10
+
11
+ /**
12
+ * Get the details of a Matomo operator
13
+ * @param {object} params The command's parameters
14
+ * @param {string} params.args[0] The operator's name or ID
15
+ * @param {string} params.options.format The output format
16
+ * @returns {Promise<void>}
17
+ */
18
+ export async function get (params) {
19
+ const [addonIdOrName] = params.args;
20
+ const { format } = params.options;
21
+ await operatorPrint('matomo', addonIdOrName, format);
22
+ }
23
+
24
+ /**
25
+ * List all Matomo operators
26
+ * @returns {Promise<void>}
27
+ */
28
+ export async function list (params) {
29
+ await operatorList('addon-matomo', params.options.format);
30
+ }
31
+
32
+ /**
33
+ * Open a Matomo operator dashboard in the Clever Cloud Console
34
+ * @param {object} params The command's parameters
35
+ * @param {string} params.args[0] The operator's name or ID
36
+ * @returns {Promise<void>}
37
+ */
38
+ export async function open (params) {
39
+ const [addonIdOrName] = params.args;
40
+ await operatorOpen('matomo', addonIdOrName);
41
+ }
42
+
43
+ /**
44
+ * Open the Logs section of a Matomo Operator application in the Clever Cloud Console
45
+ * @param {object} params The command's parameters
46
+ * @param {string} params.args[0] The operator's name or ID
47
+ * @returns {Promise<void>}
48
+ */
49
+ export async function openLogs (params) {
50
+ const [addonIdOrName] = params.args;
51
+ await operatorOpenLogs('matomo', addonIdOrName);
52
+ }
53
+
54
+ /**
55
+ * Open the Web UI of a Matomo Operator application in the Clever Cloud Console
56
+ * @param {object} params The command's parameters
57
+ * @param {string} params.args[0] The operator's name or ID
58
+ * @returns {Promise<void>}
59
+ */
60
+ export async function openWebUi (params) {
61
+ const [addonIdOrName] = params.args;
62
+ await operatorOpenWebUi('matomo', addonIdOrName);
63
+ }
64
+
65
+ /**
66
+ * Reboot a Matomo operator
67
+ * @param {object} params The command's parameters
68
+ * @param {string} params.args[0] The operator's name or ID
69
+ * @returns {Promise<void>}
70
+ */
71
+ export async function reboot (params) {
72
+ const [addonIdOrName] = params.args;
73
+ await operatorReboot('matomo', addonIdOrName);
74
+ }
75
+
76
+ /**
77
+ * Rebuild a Matomo operator
78
+ * @param {object} params The command's parameters
79
+ * @param {string} params.args[0] The operator's name or ID
80
+ * @returns {Promise<void>}
81
+ */
82
+ export async function rebuild (params) {
83
+ const [addonIdOrName] = params.args;
84
+ await operatorRebuild('matomo', addonIdOrName);
85
+ }
@@ -0,0 +1,112 @@
1
+ import {
2
+ operatorCheckVersion,
3
+ operatorList,
4
+ operatorOpen,
5
+ operatorOpenLogs,
6
+ operatorOpenWebUi,
7
+ operatorPrint,
8
+ operatorReboot,
9
+ operatorRebuild,
10
+ operatorUpdateVersion,
11
+ } from '../lib/operator-commands.js';
12
+
13
+ /**
14
+ * Check the version of a Metabase operator
15
+ * @param {object} params The command's parameters
16
+ * @param {string} params.args[0] The operator's name or ID
17
+ * @param {string} params.options.format The output format
18
+ * @returns {Promise<void>}
19
+ */
20
+ export async function checkVersion (params) {
21
+ const [addonIdOrName] = params.args;
22
+ const { format } = params.options;
23
+ await operatorCheckVersion('metabase', addonIdOrName, format);
24
+ }
25
+
26
+ /**
27
+ * Update the version of a Metabase operator
28
+ * @param {object} params The command's parameters
29
+ * @param {string} params.args[0] The operator's name or ID
30
+ * @returns {Promise<void>}
31
+ */
32
+ export async function updateVersion (params) {
33
+ const [addonIdOrName] = params.args;
34
+ const { target } = params.options;
35
+ await operatorUpdateVersion('metabase', target, addonIdOrName);
36
+ }
37
+
38
+ /**
39
+ * Get the details of a Metabase operator
40
+ * @param {object} params The command's parameters
41
+ * @param {string} params.args[0] The operator's name or ID
42
+ * @param {string} params.options.format The output format
43
+ * @returns {Promise<void>}
44
+ */
45
+ export async function get (params) {
46
+ const [addonIdOrName] = params.args;
47
+ const { format } = params.options;
48
+ await operatorPrint('metabase', addonIdOrName, format);
49
+ }
50
+
51
+ /**
52
+ * List all Metabase operators
53
+ * @returns {Promise<void>}
54
+ */
55
+ export async function list (params) {
56
+ await operatorList('metabase', params.options.format);
57
+ }
58
+
59
+ /**
60
+ * Open a Metabase operator dashboard in the Clever Cloud Console
61
+ * @param {object} params The command's parameters
62
+ * @param {string} params.args[0] The operator's name or ID
63
+ * @returns {Promise<void>}
64
+ */
65
+ export async function open (params) {
66
+ const [addonIdOrName] = params.args;
67
+ await operatorOpen('metabase', addonIdOrName);
68
+ }
69
+
70
+ /**
71
+ * Open the Logs section of a Metabase Operator application in the Clever Cloud Console
72
+ * @param {object} params The command's parameters
73
+ * @param {string} params.args[0] The operator's name or ID
74
+ * @returns {Promise<void>}
75
+ */
76
+ export async function openLogs (params) {
77
+ const [addonIdOrName] = params.args;
78
+ await operatorOpenLogs('metabase', addonIdOrName);
79
+ }
80
+
81
+ /**
82
+ * Open the Web UI of a Metabase Operator application in the Clever Cloud Console
83
+ * @param {object} params The command's parameters
84
+ * @param {string} params.args[0] The operator's name or ID
85
+ * @returns {Promise<void>}
86
+ */
87
+ export async function openWebUi (params) {
88
+ const [addonIdOrName] = params.args;
89
+ await operatorOpenWebUi('metabase', addonIdOrName);
90
+ }
91
+
92
+ /**
93
+ * Reboot a Metabase operator
94
+ * @param {object} params The command's parameters
95
+ * @param {string} params.args[0] The operator's name or ID
96
+ * @returns {Promise<void>}
97
+ */
98
+ export async function reboot (params) {
99
+ const [addonIdOrName] = params.args;
100
+ await operatorReboot('metabase', addonIdOrName);
101
+ }
102
+
103
+ /**
104
+ * Rebuild a Metabase operator
105
+ * @param {object} params The command's parameters
106
+ * @param {string} params.args[0] The operator's name or ID
107
+ * @returns {Promise<void>}
108
+ */
109
+ export async function rebuild (params) {
110
+ const [addonIdOrName] = params.args;
111
+ await operatorRebuild('metabase', addonIdOrName);
112
+ }
@@ -1,8 +1,6 @@
1
- import openPage from 'open';
2
-
3
1
  import * as Application from '../models/application.js';
4
2
  import * as Domain from '../models/domain.js';
5
- import { Logger } from '../logger.js';
3
+ import { openBrowser } from '../models/utils.js';
6
4
 
7
5
  export async function open (params) {
8
6
  const { alias, app: appIdOrName } = params.options;
@@ -11,6 +9,5 @@ export async function open (params) {
11
9
  const vhost = await Domain.getBest(appId, ownerId);
12
10
  const url = 'https://' + vhost.fqdn;
13
11
 
14
- Logger.println('Opening the application in your browser');
15
- await openPage(url, { wait: false });
12
+ await openBrowser(url, 'Opening the application in your browser');
16
13
  }
@@ -0,0 +1,138 @@
1
+ import {
2
+ operatorCheckVersion,
3
+ operatorList,
4
+ operatorNgDisable,
5
+ operatorNgEnable,
6
+ operatorOpen,
7
+ operatorOpenLogs,
8
+ operatorOpenWebUi,
9
+ operatorPrint,
10
+ operatorReboot,
11
+ operatorRebuild,
12
+ operatorUpdateVersion,
13
+ } from '../lib/operator-commands.js';
14
+
15
+ /**
16
+ * Check the version of an Otoroshi operator
17
+ * @param {object} params The command's parameters
18
+ * @param {string} params.args[0] The operator's name or ID
19
+ * @param {string} params.options.format The output format
20
+ * @returns {Promise<void>}
21
+ */
22
+ export async function checkVersion (params) {
23
+ const [addonIdOrName] = params.args;
24
+ const { format } = params.options;
25
+ await operatorCheckVersion('otoroshi', addonIdOrName, format);
26
+ }
27
+
28
+ /**
29
+ * Update the version of an Otoroshi operator
30
+ * @param {object} params The command's parameters
31
+ * @param {string} params.args[0] The operator's name or ID
32
+ * @returns {Promise<void>}
33
+ */
34
+ export async function updateVersion (params) {
35
+ const [addonIdOrName] = params.args;
36
+ const { target } = params.options;
37
+ await operatorUpdateVersion('otoroshi', target, addonIdOrName);
38
+ }
39
+
40
+ /**
41
+ * Get the details of an Otoroshi operator
42
+ * @param {object} params The command's parameters
43
+ * @param {string} params.args[0] The operator's name or ID
44
+ * @param {string} params.options.format The output format
45
+ * @returns {Promise<void>}
46
+ */
47
+ export async function get (params) {
48
+ const [addonIdOrName] = params.args;
49
+ const { format } = params.options;
50
+ await operatorPrint('otoroshi', addonIdOrName, format);
51
+ }
52
+
53
+ /**
54
+ * List all Otoroshi operators
55
+ * @returns {Promise<void>}
56
+ */
57
+ export async function list (params) {
58
+ await operatorList('otoroshi', params.options.format);
59
+ }
60
+
61
+ /**
62
+ * Unlink a Operator from a Network Group
63
+ * @param {object} params The command's parameters
64
+ * @param {string} params.args[0] The operator's name or ID
65
+ * @returns {Promise<void>}
66
+ * @throws {Error} If the Network Group feature is already disabled
67
+ */
68
+ export async function ngDisable (params) {
69
+ const [addonIdOrName] = params.args;
70
+ await operatorNgDisable('otoroshi', addonIdOrName);
71
+ }
72
+
73
+ /**
74
+ * Link a Operator to a Network Group
75
+ * @param {object} params The command's parameters
76
+ * @param {string} params.args[0] The operator's name or ID
77
+ * @returns {Promise<void>}
78
+ * @throws {Error} If the Network Group feature is already enabled
79
+ */
80
+ export async function ngEnable (params) {
81
+ const [addonIdOrName] = params.args;
82
+ await operatorNgEnable('otoroshi', addonIdOrName);
83
+ }
84
+
85
+ /**
86
+ * Open an Otoroshi operator dashboard in the Clever Cloud Console
87
+ * @param {object} params The command's parameters
88
+ * @param {string} params.args[0] The operator's name or ID
89
+ * @returns {Promise<void>}
90
+ */
91
+ export async function open (params) {
92
+ const [addonIdOrName] = params.args;
93
+ await operatorOpen('otoroshi', addonIdOrName);
94
+ }
95
+
96
+ /**
97
+ * Open the Logs section of an Otoroshi Operator application in the Clever Cloud Console
98
+ * @param {object} params The command's parameters
99
+ * @param {string} params.args[0] The operator's name or ID
100
+ * @returns {Promise<void>}
101
+ */
102
+ export async function openLogs (params) {
103
+ const [addonIdOrName] = params.args;
104
+ await operatorOpenLogs('otoroshi', addonIdOrName);
105
+ }
106
+
107
+ /**
108
+ * Open the Web UI of an Otoroshi Operator application in the Clever Cloud Console
109
+ * @param {object} params The command's parameters
110
+ * @param {string} params.args[0] The operator's name or ID
111
+ * @returns {Promise<void>}
112
+ */
113
+ export async function openWebUi (params) {
114
+ const [addonIdOrName] = params.args;
115
+ await operatorOpenWebUi('otoroshi', addonIdOrName);
116
+ }
117
+
118
+ /**
119
+ * Reboot an Otoroshi operator
120
+ * @param {object} params The command's parameters
121
+ * @param {string} params.args[0] The operator's name or ID
122
+ * @returns {Promise<void>}
123
+ */
124
+ export async function reboot (params) {
125
+ const [addonIdOrName] = params.args;
126
+ await operatorReboot('otoroshi', addonIdOrName);
127
+ }
128
+
129
+ /**
130
+ * Rebuild an Otoroshi operator
131
+ * @param {object} params The command's parameters
132
+ * @param {string} params.args[0] The operator's name or ID
133
+ * @returns {Promise<void>}
134
+ */
135
+ export async function rebuild (params) {
136
+ const [addonIdOrName] = params.args;
137
+ await operatorRebuild('otoroshi', addonIdOrName);
138
+ }
@@ -1,5 +1,5 @@
1
1
  import colors from 'colors/safe.js';
2
- import openPage from 'open';
2
+ import { openBrowser } from '../models/utils.js';
3
3
  import { Logger } from '../logger.js';
4
4
  import * as User from '../models/user.js';
5
5
  import dedent from 'dedent';
@@ -51,7 +51,5 @@ export async function profile (params) {
51
51
  };
52
52
 
53
53
  export async function openProfile () {
54
- const URL = 'https://console.clever-cloud.com/users/me/information';
55
- Logger.debug('Opening the profile page in your browser');
56
- await openPage(URL, { wait: false });
54
+ await openBrowser('/users/me/information', 'Opening the profile page in your browser');
57
55
  }
@@ -26,7 +26,7 @@ export async function restart (params) {
26
26
  const commitId = fullCommitId || remoteCommitId;
27
27
  if (commitId != null) {
28
28
  const cacheSuffix = withoutCache ? ' without using cache' : '';
29
- Logger.println(`Restarting ${app.name} on commit ${colors.green(commitId)}${cacheSuffix}`);
29
+ Logger.println(`🔄 Restarting ${colors.bold(app.name)}${cacheSuffix} ${colors.grey(`(${commitId})`)}`);
30
30
  }
31
31
 
32
32
  // This should be handled by the API when a deployment ID is set but we'll do this for now
@@ -0,0 +1,159 @@
1
+ import { confirm } from '../lib/prompts.js';
2
+ import colors from 'colors/safe.js';
3
+ import { Logger } from '../logger.js';
4
+ import fs from 'node:fs';
5
+ import { sendToApi } from '../models/send-to-api.js';
6
+ import { openBrowser } from '../models/utils.js';
7
+ import dedent from 'dedent';
8
+ import {
9
+ todo_addSshKey as addSshKey,
10
+ todo_getSshKeys as getSshKeys,
11
+ todo_removeSshKey as removeSshKey,
12
+ } from '@clevercloud/client/esm/api/v2/user.js';
13
+
14
+ /**
15
+ * List SSH keys of the current user
16
+ * @param {object} params The command parameters
17
+ * @param {string} params.options.format The output format
18
+ */
19
+ export async function list (params) {
20
+ const { format } = params.options;
21
+
22
+ const keys = await getUserSshKeys();
23
+
24
+ switch (format) {
25
+ case 'json': {
26
+ Logger.printJson(keys);
27
+ break;
28
+ }
29
+ case 'human':
30
+ default: {
31
+ if (keys.length === 0) {
32
+ Logger.println(dedent`
33
+ ${colors.blue('🔐 No SSH keys')}
34
+
35
+ To list the SSH keys on your local system, use the following command:
36
+ ${colors.grey('ssh-add -l -E sha256')}
37
+
38
+ To create a new key pair, use the following command:
39
+ ${colors.grey('ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_clever -C "An optional comment"')}
40
+
41
+ Then add the public key to your Clever Cloud account:
42
+ ${colors.grey('clever ssh-keys add myNewKey ~/.ssh/id_ed25519_clever.pub')}
43
+ `);
44
+ return;
45
+ }
46
+
47
+ Logger.println(`🔐 ${keys.length} SSH key(s):`);
48
+ keys.forEach((key) => {
49
+ Logger.println(` • ${colors.blue(key.name)}`, colors.grey(`(${key.fingerprint})`));
50
+ });
51
+ }
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Add a SSH key to the current user
57
+ * @param {object} params The command parameters
58
+ * @param {Array<string>} params.args
59
+ */
60
+ export async function add (params) {
61
+ const [keyName, filePath] = params.args;
62
+
63
+ if (!fs.existsSync(filePath)) {
64
+ throw new Error(`File ${filePath} does not exist`);
65
+ }
66
+
67
+ const pubKeyContent = fs.readFileSync(filePath, 'utf8').trim();
68
+ Logger.debug(`SSH key file content: ${pubKeyContent}`);
69
+
70
+ try {
71
+ await addSshKey({ key: encodeURIComponent(keyName) }, JSON.stringify(pubKeyContent)).then(sendToApi);
72
+ }
73
+ catch (e) {
74
+ console.log(e?.responseBody?.id);
75
+ if (e?.responseBody?.id === 505) {
76
+ throw new Error('This SSH key is not valid, please make sure you\'re pointing to the public key file');
77
+ }
78
+ }
79
+
80
+ Logger.printSuccess(`SSH key ${keyName} added successfully`);
81
+ }
82
+
83
+ /**
84
+ * Remove a SSH key from the current user
85
+ * @param {object} params The command parameters
86
+ * @param {Array<string>} params.args
87
+ */
88
+ export async function remove (params) {
89
+ const [keyName] = params.args;
90
+
91
+ const keys = await getUserSshKeys();
92
+
93
+ if (keys.find((key) => key.name === keyName) == null) {
94
+ throw new Error(`SSH key ${colors.red(keyName)} not found`);
95
+ }
96
+
97
+ const keyNameEncoded = encodeURIComponent(keyName);
98
+ await removeSshKey({ key: keyNameEncoded }).then(sendToApi);
99
+
100
+ Logger.printSuccess(`SSH key ${keyName} removed successfully`);
101
+ }
102
+
103
+ /**
104
+ * Remove all SSH keys from the current user
105
+ * @param {object} params The command parameters
106
+ * @param {object} params.options The command options
107
+ * @param {boolean} params.options.yes The user confirmation
108
+ */
109
+ export async function removeAll (params) {
110
+ if (!params.options.yes) {
111
+ await confirm(
112
+ 'Are you sure you want to remove all your SSH keys?',
113
+ 'No SSH keys removed',
114
+ );
115
+ }
116
+
117
+ const keys = await getUserSshKeys();
118
+
119
+ if (keys.length === 0) {
120
+ Logger.println('No SSH keys to remove');
121
+ return;
122
+ }
123
+
124
+ const results = await Promise.all(
125
+ keys.map((key) => {
126
+ const keyNameEncoded = encodeURIComponent(key.name);
127
+ return removeSshKey({ key: keyNameEncoded }).then(sendToApi)
128
+ .then(() => [true, key.name])
129
+ .catch(() => [false, key.name]);
130
+ }),
131
+ );
132
+
133
+ if (results.every(([isRemoved]) => isRemoved)) {
134
+ Logger.printSuccess('All SSH keys were removed successfully');
135
+ }
136
+ else {
137
+ const keyNamesWithErrors = results
138
+ .filter(([isRemoved]) => !isRemoved)
139
+ .map(([_, keyName]) => keyName)
140
+ .join(', ');
141
+ throw new Error(`Some errors occured while removing these SSH keys: ${keyNamesWithErrors}`);
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Open the SSH keys management page of the Console in your browser
147
+ * @returns {Promise<void>} A promise that resolves when the page is opened
148
+ */
149
+ export function openConsole () {
150
+ return openBrowser('/users/me/ssh-keys', 'Opening the SSH keys management page of the Console in your browser');
151
+ }
152
+
153
+ /**
154
+ * @return {Promise<Array<{ name: string, key: string, fingerprint: string }>>}
155
+ */
156
+ async function getUserSshKeys () {
157
+ const rawKeys = await getSshKeys().then(sendToApi);
158
+ return rawKeys.sort((a, b) => a.name.localeCompare(b.name));
159
+ }
@@ -8,5 +8,5 @@ export async function stop (params) {
8
8
  const { ownerId, appId } = await Application.resolveId(appIdOrName, alias);
9
9
 
10
10
  await stopApplication({ id: ownerId, appId }).then(sendToApi);
11
- Logger.println('App successfully stopped!');
11
+ Logger.printSuccess('Application successfully stopped!');
12
12
  }
@@ -2,7 +2,7 @@ import colors from 'colors/safe.js';
2
2
 
3
3
  import * as Namespaces from '../models/namespaces.js';
4
4
  import { sendToApi } from '../models/send-to-api.js';
5
- import * as Interact from '../models/interact.js';
5
+ import { confirm } from '../lib/prompts.js';
6
6
  import { Logger } from '../logger.js';
7
7
  import * as Application from '../models/application.js';
8
8
  import { addTcpRedir, getTcpRedirs, removeTcpRedir } from '@clevercloud/client/esm/api/v2/application.js';
@@ -72,8 +72,8 @@ async function acceptPayment (result, skipConfirmation) {
72
72
  Logger.println(`Total (without taxes): ${result.totalHT}€`);
73
73
  Logger.println(colors.bold(`Total (with taxes): ${result.totalTTC}€`));
74
74
 
75
- await Interact.confirm(
76
- `You're about to pay ${result.totalTTC}€, confirm? (yes or no) `,
75
+ await confirm(
76
+ `You're about to pay ${result.totalTTC}€, confirm?`,
77
77
  'No confirmation, aborting TCP redirection creation',
78
78
  );
79
79
  }
@@ -5,7 +5,7 @@ import { sendToAuthBridge } from '../models/send-to-api.js';
5
5
  import { getCurrent as getCurrentUser } from '../models/user.js';
6
6
  import { conf } from '../models/configuration.js';
7
7
  import dedent from 'dedent';
8
- import { promptPassword } from '../prompt-password.js';
8
+ import { promptSecret } from '../lib/prompts.js';
9
9
 
10
10
  /**
11
11
  * Create a new API token
@@ -13,10 +13,20 @@ import { promptPassword } from '../prompt-password.js';
13
13
  * @param {[string]} params.args - Command line args
14
14
  * @param {Object} params.options - Command line options
15
15
  * @param {'json'|'human'} params.options.format - Output format
16
+ * @param {number} params.options.expiration - Expiration date as timestamp
16
17
  */
17
18
  export async function create (params) {
18
19
  const [apiTokenName] = params.args;
19
20
  const { expiration, format } = params.options;
21
+ const user = await getCurrentUser();
22
+
23
+ if (!user.hasPassword) {
24
+ const apiTokenListHref = new URL('/users/me/api-tokens', conf.CONSOLE_URL).href;
25
+ throw new Error(dedent`
26
+ ${colors.yellow('!')} Your Clever Cloud account is linked via GitHub and has no password. Setting one is required to create API tokens.
27
+ ${colors.blue('→')} To do so, go to the following URL: ${colors.blue(apiTokenListHref)}
28
+ `);
29
+ }
20
30
 
21
31
  // Expire in 1 year
22
32
  const dateObject = new Date();
@@ -25,22 +35,20 @@ export async function create (params) {
25
35
 
26
36
  let expirationDate;
27
37
  if (expiration != null) {
28
- if (expiration > maxExpirationDate) {
38
+ if (expiration > maxExpirationDate.getTime()) {
29
39
  throw new Error('You cannot set an expiration date greater than 1 year');
30
40
  }
31
- expirationDate = expiration;
41
+ expirationDate = new Date(expiration);
32
42
  }
33
43
  else {
34
44
  expirationDate = maxExpirationDate;
35
45
  }
36
46
 
37
- const user = await getCurrentUser();
38
-
39
- const password = await promptPassword('Enter your password:');
47
+ const password = await promptSecret('Enter your password:');
40
48
 
41
49
  let mfaCode;
42
50
  if (user.preferredMFA === 'TOTP') {
43
- mfaCode = await promptPassword('Enter your 2FA code:');
51
+ mfaCode = await promptSecret('Enter your 2FA code:');
44
52
  }
45
53
 
46
54
  const tokenData = {
@@ -112,6 +120,7 @@ export async function list (params) {
112
120
  'Creation IP address': token.ip,
113
121
  Creation: formatDate(token.creationDate),
114
122
  Expiration: formatDate(token.expirationDate),
123
+ State: token.state,
115
124
  };
116
125
  }));
117
126
  }
@@ -1,11 +1,12 @@
1
1
  import * as AppConfig from '../models/app_configuration.js';
2
2
  import * as Application from '../models/application.js';
3
3
  import { Logger } from '../logger.js';
4
+ import colors from 'colors/safe.js';
4
5
 
5
6
  export async function unlink (params) {
6
7
  const [alias] = params.args;
7
8
  const app = await AppConfig.getAppDetails({ alias });
8
9
 
9
10
  await Application.unlinkRepo(app.alias);
10
- Logger.println('Your application has been successfully unlinked!');
11
+ Logger.printSuccess(`Application ${colors.green(app.appId)} has been successfully unlinked from local alias ${colors.green(app.alias)}!`);
11
12
  };