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
@@ -1,22 +1,21 @@
1
- import { update as updateApplication } from '@clevercloud/client/esm/api/v2/application.js';
2
-
3
1
  import * as Application from '../models/application.js';
4
2
  import * as ApplicationConfiguration from '../models/application_configuration.js';
3
+ import { Logger } from '../logger.js';
4
+ import colors from 'colors/safe.js';
5
5
 
6
- import { sendToApi } from '../models/send-to-api.js';
6
+ export async function list (params) {
7
+ const { alias, app: appIdOrName } = params.options;
8
+ const { ownerId, appId } = await Application.resolveId(appIdOrName, alias);
9
+ const app = await Application.get(ownerId, appId);
10
+ ApplicationConfiguration.printAllValues(app);
11
+ }
7
12
 
8
13
  export async function get (params) {
9
14
  const [configurationName] = params.args;
10
15
  const { alias, app: appIdOrName } = params.options;
11
16
  const { ownerId, appId } = await Application.resolveId(appIdOrName, alias);
12
17
  const app = await Application.get(ownerId, appId);
13
-
14
- if (configurationName == null) {
15
- ApplicationConfiguration.print(app);
16
- }
17
- else {
18
- ApplicationConfiguration.printById(app, configurationName);
19
- }
18
+ ApplicationConfiguration.printValue(app, configurationName);
20
19
  }
21
20
 
22
21
  export async function set (params) {
@@ -24,12 +23,11 @@ export async function set (params) {
24
23
  const { alias, app: appIdOrName } = params.options;
25
24
  const { ownerId, appId } = await Application.resolveId(appIdOrName, alias);
26
25
  const config = ApplicationConfiguration.getById(configurationName);
27
-
28
- if (config != null) {
29
- const app = await updateApplication({ id: ownerId, appId }, { [config.name]: ApplicationConfiguration.parse(config, configurationValue) }).then(sendToApi);
30
-
31
- ApplicationConfiguration.printById(app, configurationName);
32
- }
26
+ const options = {
27
+ [config.name]: ApplicationConfiguration.parse(config, configurationValue),
28
+ };
29
+ const app = await Application.updateOptions(ownerId, appId, options);
30
+ Logger.printSuccess(`Config ${colors.green(config.id)} successfully updated to ${colors.green(ApplicationConfiguration.formatValue(config, app[config.name]))}!`);
33
31
  }
34
32
 
35
33
  export async function update (params) {
@@ -41,9 +39,7 @@ export async function update (params) {
41
39
  throw new Error('No configuration to update');
42
40
  }
43
41
 
44
- const app = await updateApplication({ id: ownerId, appId }, options).then(sendToApi);
42
+ const app = await Application.updateOptions(ownerId, appId, options);
45
43
 
46
- for (const configName of Object.keys(options)) {
47
- ApplicationConfiguration.printByName(app, configName);
48
- }
44
+ ApplicationConfiguration.printAllValues(app);
49
45
  }
@@ -1,8 +1,6 @@
1
1
  import * as Application from '../models/application.js';
2
2
  import * as AppConfig from '../models/app_configuration.js';
3
- import { Logger } from '../logger.js';
4
- import openPage from 'open';
5
- import { conf } from '../models/configuration.js';
3
+ import { openBrowser } from '../models/utils.js';
6
4
 
7
5
  export async function openConsole (params) {
8
6
  const { alias, app: appIdOrName } = params.options;
@@ -10,17 +8,13 @@ export async function openConsole (params) {
10
8
  const { apps } = await AppConfig.loadApplicationConf();
11
9
  // If no app is linked or asked, open the Console without any context
12
10
  if (apps.length === 0 && !appIdOrName) {
13
- Logger.println('Opening the Console in your browser');
14
- await openPage(conf.CONSOLE_URL, { wait: false });
11
+ await openBrowser('/', 'Opening the Console in your browser');
15
12
  return;
16
13
  }
17
14
 
18
15
  const { ownerId, appId } = await Application.resolveId(appIdOrName, alias);
19
16
  const prefixPath = (ownerId.startsWith('user_')) ? 'users/me' : `organisations/${ownerId}`;
20
- const url = `${conf.CONSOLE_URL}/${prefixPath}/applications/${appId}`;
17
+ const consolePath = `/${prefixPath}/applications/${appId}`;
21
18
 
22
- Logger.debug(`URL: ${url}`);
23
- Logger.println(`Opening the Console in your browser for application ${appId}`);
24
-
25
- await openPage(url, { wait: false });
19
+ await openBrowser(consolePath, `Opening the Console in your browser for application ${appId}`);
26
20
  }
@@ -1,7 +1,10 @@
1
1
  import path from 'node:path';
2
+ import colors from 'colors/safe.js';
2
3
  import * as Application from '../models/application.js';
3
4
  import * as AppConfig from '../models/app_configuration.js';
4
5
  import { Logger } from '../logger.js';
6
+ import { isGitWorkingDirectoryClean, isInsideGitRepo } from '../models/git.js';
7
+ import { conf } from '../models/configuration.js';
5
8
 
6
9
  export async function create (params) {
7
10
  const { type: typeName } = params.options;
@@ -37,15 +40,7 @@ export async function create (params) {
37
40
 
38
41
  case 'human':
39
42
  default:
40
- if (isTask) {
41
- Logger.println('Your application has been successfully created as a task!');
42
- Logger.println(`The "CC_RUN_COMMAND" environment variable has been set to "${taskCommand}"`);
43
- }
44
- else {
45
- Logger.println('Your application has been successfully created!');
46
- }
47
- Logger.println(`ID: ${app.id}`);
48
- Logger.println(`Name: ${name}`);
43
+ await displayAppCreation(app, alias, github, taskCommand);
49
44
  }
50
45
  };
51
46
 
@@ -59,3 +54,75 @@ function getGithubDetails (githubOwnerRepo) {
59
54
  function getCurrentDirectoryName () {
60
55
  return path.basename(process.cwd());
61
56
  }
57
+
58
+ /**
59
+ * Display the application creation message in a human-readable format
60
+ * @param {Object} app - The application object
61
+ * @param {string} alias - The alias of the application
62
+ * @param {Object} github - The GitHub details
63
+ * @param {string} taskCommand - The task command
64
+ */
65
+ async function displayAppCreation (app, alias, github, taskCommand) {
66
+
67
+ Logger.printSuccess(`Application ${colors.green(app.name)} successfully created!`);
68
+
69
+ const hasDistinctAlias = alias != null && alias !== app.name;
70
+ const isTask = app.instance.lifetime === 'TASK';
71
+
72
+ Logger.println();
73
+ printFieldsAsTable(2, {
74
+ Type: colors.blue(`⬢ ${app.instance.variant.name}`),
75
+ ID: app.id,
76
+ 'Org ID': app.ownerId,
77
+ Name: app.name,
78
+ GitHub: github != null && `${github.owner}/${github.name}`,
79
+ Alias: hasDistinctAlias && alias,
80
+ Zone: app.zone,
81
+ Task: isTask && `"${taskCommand}"`,
82
+ });
83
+
84
+ Logger.println();
85
+ Logger.println(' ' + colors.bold('Next steps:'));
86
+
87
+ if (!github) {
88
+ const isInsideGit = await isInsideGitRepo();
89
+ if (!isInsideGit) {
90
+ Logger.println(` ${colors.yellow('!')} Initialize a git repository first, for example:`);
91
+ Logger.println(` ${shellCommand('git init')}`);
92
+ Logger.println(` ${shellCommand('git add .')}`);
93
+ Logger.println(` ${shellCommand('git commit -m "Initial commit"')}`);
94
+ Logger.println();
95
+ }
96
+ else {
97
+ const isClean = await isGitWorkingDirectoryClean();
98
+ if (!isClean) {
99
+ Logger.println(` ${colors.yellow('!')} Commit your changes first:`);
100
+ Logger.println(` ${shellCommand('git add .')}`);
101
+ Logger.println(` ${shellCommand('git commit -m "My changes"')}`);
102
+ Logger.println();
103
+ }
104
+ }
105
+ }
106
+
107
+ if (github) {
108
+ Logger.println(` ${colors.blue('→')} Push changes to ${colors.blue(`${github.owner}/${github.name}`)} GitHub repository to trigger a deployment, or ${colors.blue('clever restart')} the latest pushed commit`);
109
+ }
110
+ else {
111
+ Logger.println(` ${colors.blue('→')} Run ${colors.blue('clever deploy')} ${isTask ? 'to execute your task' : 'to deploy your application'}`);
112
+ }
113
+
114
+ Logger.println(` ${colors.blue('→')} Manage your application at: ${colors.underline(`${conf.GOTO_URL}/${app.id}`)}`);
115
+ Logger.println('');
116
+ }
117
+
118
+ function shellCommand (command) {
119
+ return `${colors.grey('$')} ${colors.yellow(command)}`;
120
+ }
121
+
122
+ function printFieldsAsTable (indent, fields) {
123
+ const fieldsWithValues = Object.fromEntries(Object.entries(fields).filter(([_, value]) => typeof value === 'string'));
124
+ const labelMaxWidth = Math.max(...Object.keys(fieldsWithValues).map((label) => label.length));
125
+ return Object.entries(fieldsWithValues).forEach(([label, value]) => {
126
+ Logger.println(`${' '.repeat(indent)}${label.padEnd(labelMaxWidth, ' ')} ${colors.grey(value)}`);
127
+ });
128
+ }
@@ -1,6 +1,7 @@
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 deleteApp (params) {
6
7
  const { alias, app: appIdOrName, yes: skipConfirmation } = params.options;
@@ -8,16 +9,16 @@ export async function deleteApp (params) {
8
9
 
9
10
  const app = await Application.get(ownerId, appId);
10
11
  if (app == null) {
11
- Logger.println('The application doesn\'t exist');
12
+ throw new Error('The application doesn\'t exist');
12
13
  }
13
- else {
14
- // delete app
15
- await Application.deleteApp(app, skipConfirmation);
16
- Logger.println('The application has been deleted');
17
- // unlink app
18
- const wasUnlinked = await AppConfig.removeLinkedApplication({ appId, alias });
19
- if (wasUnlinked) {
20
- Logger.println('The application has been unlinked');
21
- }
14
+
15
+ // delete app
16
+ await Application.deleteApp(app, skipConfirmation);
17
+ Logger.printSuccess(`Application ${colors.green(colors.bold(`${app.name}`))} successfully deleted!`);
18
+ Logger.println(` ${colors.grey('•')} Application ID: ${colors.grey(app.id)}`);
19
+
20
+ const wasUnlinked = await AppConfig.removeLinkedApplication({ appId, alias });
21
+ if (wasUnlinked) {
22
+ Logger.println(` ${colors.blue('→')} Local alias ${colors.blue(alias || app.name)} unlinked`);
22
23
  }
23
24
  };
@@ -1,4 +1,5 @@
1
1
  import colors from 'colors/safe.js';
2
+ import dedent from 'dedent';
2
3
 
3
4
  import * as AppConfig from '../models/app_configuration.js';
4
5
  import * as Application from '../models/application.js';
@@ -27,14 +28,10 @@ export async function deploy (params) {
27
28
 
28
29
  await git.addRemote(appData.alias, appData.deployUrl);
29
30
 
30
- Logger.println(colors.bold.blue(`Remote application is app_id=${appId}, alias=${appData.alias}, name=${appData.name}`));
31
-
32
- Logger.println(colors.bold.blue(`Remote application belongs to ${ownerId}`));
33
-
34
31
  if (commitIdToPush === remoteHeadCommitId) {
35
32
  switch (sameCommitPolicy) {
36
33
  case 'ignore':
37
- Logger.println(`The clever-cloud application is up-to-date (${colors.green(remoteHeadCommitId)})`);
34
+ Logger.printSuccess(`The application is up-to-date (${colors.grey(remoteHeadCommitId)})`);
38
35
  return;
39
36
  case 'restart':
40
37
  return restartOnSameCommit(ownerId, appId, commitIdToPush, quiet, false, exitStrategy);
@@ -42,31 +39,44 @@ export async function deploy (params) {
42
39
  return restartOnSameCommit(ownerId, appId, commitIdToPush, quiet, true, exitStrategy);
43
40
  case 'error':
44
41
  default: {
45
- const upToDateMessage = `The clever-cloud application is up-to-date (${colors.green(remoteHeadCommitId)}).\nYou can set a policy with 'same-commit-policy' to handle differently when remote HEAD has the same commit as the one to push.\nOr try this command to restart the application:`;
46
- if (commitIdToPush !== deployedCommitId) {
47
- const restartWithId = `clever restart --commit ${commitIdToPush}`;
48
- throw new Error(`${upToDateMessage}\n${colors.yellow(restartWithId)}`);
49
- }
50
- throw new Error(`${upToDateMessage}\n${colors.yellow('clever restart')}`);
42
+ const restartCommand = commitIdToPush !== deployedCommitId ? `clever restart --commit ${commitIdToPush}` : 'clever restart';
43
+ throw new Error(dedent`
44
+ Remote HEAD has the same commit as the one to push ${colors.grey(`(${remoteHeadCommitId})`)}, your application is up-to-date.
45
+ Create a new commit, use ${colors.blue(restartCommand)} or the ${colors.blue('--same-commit-policy')} option.
46
+ `);
51
47
  }
52
48
  }
53
49
  }
54
50
 
51
+ // It's sometimes tricky to figure out the deployment ID for the current git push.
52
+ // We on have the commit ID but there in a situation where the last deployment was cancelled, it may have the same commit ID.
53
+ // So before pushing, we get the last deployments so we can after the push figure out which deployment is new…
54
+ const knownDeployments = await getAllDeployments({ id: ownerId, appId, limit: 5 }).then(sendToApi);
55
+
56
+ Logger.println(dedent`
57
+ ${colors.bold(`🚀 Deploying ${colors.green(appData.name)}`)}
58
+ Application ID ${colors.grey(`${appId}`)}
59
+ Org ID ${colors.grey(`${ownerId}`)}
60
+ `);
61
+
62
+ Logger.println();
63
+
64
+ Logger.println(colors.bold('🔀 Git information'));
55
65
  if (remoteHeadCommitId == null || deployedCommitId == null) {
56
- Logger.println('App is brand new, no commits on remote yet');
66
+ Logger.println(` ${colors.yellow('!')} App is brand new, no commits on remote yet`);
57
67
  }
58
68
  else {
59
- Logger.println(`Remote git head commit is ${colors.green(remoteHeadCommitId)}`);
60
- Logger.println(`Current deployed commit is ${colors.green(deployedCommitId)}`);
69
+ Logger.println(` Remote head ${colors.yellow(remoteHeadCommitId)} (${branchRefspec})`);
70
+ Logger.println(` Deployed commit ${colors.yellow(deployedCommitId)}`);
61
71
  }
62
- Logger.println(`New local commit to push is ${colors.green(commitIdToPush)} (from ${colors.green(branchRefspec)})`);
72
+ Logger.println(` Local commit ${colors.yellow(commitIdToPush)} ${colors.blue('[will be deployed]')}`);
63
73
 
64
- // It's sometimes tricky to figure out the deployment ID for the current git push.
65
- // We on have the commit ID but there in a situation where the last deployment was cancelled, it may have the same commit ID.
66
- // So before pushing, we get the last deployments so we can after the push figure out which deployment is new…
67
- const knownDeployments = await getAllDeployments({ id: ownerId, appId, limit: 5 }).then(sendToApi);
74
+ Logger.println();
68
75
 
69
- Logger.println('Pushing source code to Clever Cloud…');
76
+ Logger.println(dedent`
77
+ ${colors.bold('🔄 Deployment progress')}
78
+ ${colors.blue('→ Pushing source code to Clever Cloud…')}
79
+ `);
70
80
 
71
81
  await git.push(appData.deployUrl, commitIdToPush, force)
72
82
  .catch(async (e) => {
@@ -78,12 +88,16 @@ export async function deploy (params) {
78
88
  throw e;
79
89
  }
80
90
  });
81
- Logger.println(colors.bold.green('Your source code has been pushed to Clever Cloud.'));
91
+
92
+ await Logger.println(` ${colors.green('✓ Code pushed to Clever Cloud')}`);
82
93
 
83
94
  return Log.watchDeploymentAndDisplayLogs({ ownerId, appId, commitId: commitIdToPush, knownDeployments, quiet, exitStrategy });
84
95
  }
85
96
 
86
97
  async function restartOnSameCommit (ownerId, appId, commitIdToPush, quiet, withoutCache, exitStrategy) {
98
+ const cacheSuffix = withoutCache ? ' without using cache' : '';
99
+ Logger.println(`🔄 Restarting ${colors.bold(appId)}${cacheSuffix} ${colors.grey(`(${commitIdToPush})`)}`);
100
+
87
101
  const restart = await Application.redeploy(ownerId, appId, commitIdToPush, withoutCache);
88
102
  return Log.watchDeploymentAndDisplayLogs({ ownerId, appId, deploymentId: restart.deploymentId, quiet, exitStrategy });
89
103
  }
@@ -0,0 +1,174 @@
1
+ import colors from 'colors/safe.js';
2
+ import * as User from '../models/user.js';
3
+ import { Logger } from '../logger.js';
4
+ import { openBrowser } from '../models/utils.js';
5
+ import { sendToApi } from '../models/send-to-api.js';
6
+ import { confirm } from '../lib/prompts.js';
7
+ import {
8
+ todo_addEmailAddress as addEmailAddress,
9
+ todo_getEmailAddresses as getEmailAddresses,
10
+ todo_removeEmailAddress as removeEmailAddress,
11
+ } from '@clevercloud/client/esm/api/v2/user.js';
12
+
13
+ /**
14
+ * Show primary and secondary email addresses of the current user
15
+ * @param {object} params The command parameters
16
+ * @param {string} params.options.format The output format
17
+ */
18
+ export async function list (params) {
19
+ const { format } = params.options;
20
+ const addresses = await getUserEmailAddresses();
21
+
22
+ switch (format) {
23
+ case 'json': {
24
+ Logger.printJson(addresses);
25
+ break;
26
+ }
27
+ case 'human':
28
+ default: {
29
+ Logger.println('✉️ Primary email address:');
30
+ Logger.println(` • ${colors.green(addresses.primary)}`);
31
+ if (addresses.secondary.length > 0) {
32
+ Logger.println();
33
+ Logger.println(`✉️ ${addresses.secondary.length} secondary email address(es):`);
34
+ addresses.secondary.forEach((address) =>
35
+ Logger.println(` • ${colors.blue(address)}`),
36
+ );
37
+ }
38
+ }
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Add a secondary email address to the current user
44
+ * @param {object} params The command parameters
45
+ * @param {Array<string>} params.args
46
+ */
47
+ export async function addSecondary (params) {
48
+ const [secondaryAddress] = params.args;
49
+
50
+ const secondaryAddressEncoded = encodeURIComponent(secondaryAddress);
51
+ try {
52
+ await addEmailAddress({ email: secondaryAddressEncoded }).then(sendToApi);
53
+ Logger.printSuccess(`The server sent a confirmation email to ${secondaryAddress} to validate your secondary address`);
54
+ }
55
+ catch (e) {
56
+ switch (e?.responseBody?.id) {
57
+ case 101:
58
+ throw new Error('This address already belongs to your account');
59
+ case 550:
60
+ throw new Error('The format of this address is invalid');
61
+ case 1004:
62
+ throw new Error('This address belongs to another account');
63
+ default:
64
+ throw e;
65
+ }
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Set the primary email address of the current user
71
+ * @param {object} params The command parameters
72
+ * @param {Array<string>} params.args
73
+ */
74
+ export async function setPrimary (params) {
75
+ const [newPrimaryAddress] = params.args;
76
+
77
+ const addresses = await getUserEmailAddresses();
78
+
79
+ if (addresses.primary === newPrimaryAddress) {
80
+ throw new Error('This address is already the primary one');
81
+ }
82
+
83
+ if (!addresses.secondary.includes(newPrimaryAddress)) {
84
+ throw new Error('This address must be added as a secondary address before marking it as primary');
85
+ }
86
+
87
+ const newPrimaryEmailEncoded = encodeURIComponent(newPrimaryAddress);
88
+ await addEmailAddress({ email: newPrimaryEmailEncoded }, { make_primary: true }).then(sendToApi);
89
+
90
+ Logger.printSuccess(`Primary address updated to ${newPrimaryAddress} successfully`);
91
+ }
92
+
93
+ /**
94
+ * Remove a secondary email address from the current user
95
+ * @param {object} params The command parameters
96
+ * @param {Array<string>} params.args
97
+ */
98
+ export async function removeSecondary (params) {
99
+ const [addressToRemove] = params.args;
100
+
101
+ const addresses = await getUserEmailAddresses();
102
+
103
+ if (!addresses.secondary.includes(addressToRemove)) {
104
+ throw new Error('This address is not part of the secondary addresses of the current user, it can\'t be removed');
105
+ }
106
+
107
+ const addressToRemoveEncoded = encodeURIComponent(addressToRemove);
108
+ await removeEmailAddress({ email: addressToRemoveEncoded }).then(sendToApi);
109
+
110
+ Logger.printSuccess(`Secondary address ${addressToRemove} removed successfully`);
111
+ }
112
+
113
+ /**
114
+ * Remove all secondary email addresses from the current user
115
+ * @param {object} params The command parameters
116
+ * @param {object} params.options The command options
117
+ * @param {boolean} params.options.yes The user confirmation
118
+ */
119
+ export async function removeAllSecondary (params) {
120
+ if (!params.options.yes) {
121
+ await confirm(
122
+ 'Are you sure you want to remove all your secondary addresses?',
123
+ 'No secondary addresses removed',
124
+ );
125
+ }
126
+
127
+ const addresses = await getUserEmailAddresses();
128
+
129
+ if (addresses.secondary.length === 0) {
130
+ Logger.println('No secondary address to remove');
131
+ return;
132
+ }
133
+
134
+ const results = await Promise.all(
135
+ addresses.secondary.map((addressToRemove) => {
136
+ const addressToRemoveEncoded = encodeURIComponent(addressToRemove);
137
+ return removeEmailAddress({ email: addressToRemoveEncoded }).then(sendToApi)
138
+ .then(() => [true, addressToRemove])
139
+ .catch(() => [false, addressToRemove]);
140
+ }),
141
+ );
142
+
143
+ if (results.every(([isRemoved]) => isRemoved)) {
144
+ Logger.printSuccess('All secondary addresses were removed successfully');
145
+ }
146
+ else {
147
+ const addressesWithErrors = results
148
+ .filter(([isRemoved]) => !isRemoved)
149
+ .map(([_, address]) => address)
150
+ .join(', ');
151
+ throw new Error(`Some errors occured while removing these addresses: ${addressesWithErrors}`);
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Open the email addresses management page of the Console in your browser
157
+ * @returns {Promise<void>} A promise that resolves when the page is opened
158
+ */
159
+ export function openConsole () {
160
+ return openBrowser('/users/me/emails', 'Opening the email addresses management page of the Console in your browser');
161
+ }
162
+
163
+ /**
164
+ * Get the primary and secondary email addresses of the current user
165
+ * @returns {Promise<{ primary: string, secondary: string[] }>} The primary and secondary email addresses of the current user
166
+ */
167
+ async function getUserEmailAddresses () {
168
+ const currentUser = await User.getCurrent();
169
+ const secondaryAddresses = await getEmailAddresses().then(sendToApi);
170
+ return {
171
+ primary: currentUser.email,
172
+ secondary: secondaryAddresses.sort(),
173
+ };
174
+ }
@@ -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 a Keycloak 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('keycloak', addonIdOrName, format);
26
+ }
27
+
28
+ /**
29
+ * Update the version of a Keycloak 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('keycloak', target, addonIdOrName);
38
+ }
39
+
40
+ /**
41
+ * Get the details of a Keycloak 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('keycloak', addonIdOrName, format);
51
+ }
52
+
53
+ /**
54
+ * List all Keycloak operators
55
+ * @returns {Promise<void>}
56
+ */
57
+ export async function list (params) {
58
+ await operatorList('keycloak', params.options.format);
59
+ }
60
+
61
+ /**
62
+ * Unlink a Keycloak 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('keycloak', addonIdOrName);
71
+ }
72
+
73
+ /**
74
+ * Link a Keycloak 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('keycloak', addonIdOrName);
83
+ }
84
+
85
+ /**
86
+ * Open a Keycloak 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('keycloak', addonIdOrName);
94
+ }
95
+
96
+ /**
97
+ * Open the Logs section of a Keycloak 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('keycloak', addonIdOrName);
105
+ }
106
+
107
+ /**
108
+ * Open the Web UI of a Keycloak 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('keycloak', addonIdOrName);
116
+ }
117
+
118
+ /**
119
+ * Restart a Keycloak 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('keycloak', addonIdOrName);
127
+ }
128
+
129
+ /**
130
+ * Rebuild a Keycloak 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('keycloak', addonIdOrName);
138
+ }
@@ -1,5 +1,6 @@
1
1
  import * as Application from '../models/application.js';
2
2
  import { Logger } from '../logger.js';
3
+ import colors from 'colors/safe.js';
3
4
 
4
5
  export async function link (params) {
5
6
  const [app] = params.args;
@@ -13,5 +14,6 @@ export async function link (params) {
13
14
  await Application.linkRepo(app, orgaIdOrName, alias);
14
15
  }
15
16
 
16
- Logger.println('Your application has been successfully linked!');
17
+ const linkedMessage = alias ? ` to local alias ${colors.green(alias)}` : '';
18
+ Logger.printSuccess(`Application ${colors.green(app.app_name || app.app_id)} has been successfully linked${linkedMessage}!`);
17
19
  }
@@ -1,10 +1,11 @@
1
1
  import * as AppConfig from '../models/app_configuration.js';
2
2
  import { Logger } from '../logger.js';
3
+ import colors from 'colors/safe.js';
3
4
 
4
5
  export async function makeDefault (params) {
5
6
  const [alias] = params.args;
6
7
 
7
8
  await AppConfig.setDefault(alias);
8
9
 
9
- Logger.println(`The application ${alias} has been set as default`);
10
+ Logger.printSuccess(`The application ${colors.green(alias)} has been set as default`);
10
11
  };