clever-tools 4.7.0 → 4.8.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 (69) hide show
  1. package/bin/clever.js +11 -1
  2. package/package.json +2 -2
  3. package/src/clever-client/drains.js +18 -18
  4. package/src/commands/README.md +1 -0
  5. package/src/commands/accesslogs/accesslogs.command.js +2 -2
  6. package/src/commands/activity/activity.command.js +1 -1
  7. package/src/commands/addon/addon.create.command.js +2 -3
  8. package/src/commands/addon/addon.delete.command.js +2 -2
  9. package/src/commands/addon/addon.docs.md +2 -2
  10. package/src/commands/addon/addon.env.command.js +4 -6
  11. package/src/commands/config-provider/config-provider.command.js +0 -4
  12. package/src/commands/config-provider/config-provider.import.command.js +1 -1
  13. package/src/commands/config-provider/config-provider.list.command.js +8 -19
  14. package/src/commands/config-provider/config-provider.open.command.js +1 -1
  15. package/src/commands/console/console.command.js +3 -2
  16. package/src/commands/curl/curl.command.js +0 -1
  17. package/src/commands/database/database.backups.command.js +4 -7
  18. package/src/commands/database/database.backups.download.command.js +5 -7
  19. package/src/commands/database/database.command.js +0 -4
  20. package/src/commands/database/database.docs.md +2 -2
  21. package/src/commands/deploy/deploy.command.js +1 -1
  22. package/src/commands/deploy/deploy.docs.md +1 -1
  23. package/src/commands/drain/drain.command.js +14 -9
  24. package/src/commands/drain/drain.create.command.js +9 -13
  25. package/src/commands/drain/drain.disable.command.js +7 -7
  26. package/src/commands/drain/drain.docs.md +7 -1
  27. package/src/commands/drain/drain.enable.command.js +7 -7
  28. package/src/commands/drain/drain.get.command.js +12 -8
  29. package/src/commands/drain/drain.remove.command.js +7 -7
  30. package/src/commands/emails/emails.open.command.js +1 -4
  31. package/src/commands/global.commands.js +18 -0
  32. package/src/commands/global.options.js +7 -7
  33. package/src/commands/help/help.command.js +0 -3
  34. package/src/commands/k8s/k8s.command.js +0 -4
  35. package/src/commands/k8s/k8s.delete.command.js +2 -2
  36. package/src/commands/k8s/k8s.docs.md +1 -1
  37. package/src/commands/logs/logs.command.js +4 -5
  38. package/src/commands/ng/ng.docs.md +0 -1
  39. package/src/commands/ng/ng.get-config.command.js +3 -14
  40. package/src/commands/ng/ng.options.js +1 -1
  41. package/src/commands/notify-email/notify-email.remove.command.js +2 -2
  42. package/src/commands/oauth-consumers/oauth-consumers.args.js +8 -0
  43. package/src/commands/oauth-consumers/oauth-consumers.command.js +6 -0
  44. package/src/commands/oauth-consumers/oauth-consumers.create.command.js +64 -0
  45. package/src/commands/oauth-consumers/oauth-consumers.delete.command.js +33 -0
  46. package/src/commands/oauth-consumers/oauth-consumers.docs.md +130 -0
  47. package/src/commands/oauth-consumers/oauth-consumers.get.command.js +67 -0
  48. package/src/commands/oauth-consumers/oauth-consumers.list.command.js +34 -0
  49. package/src/commands/oauth-consumers/oauth-consumers.open.command.js +18 -0
  50. package/src/commands/oauth-consumers/oauth-consumers.options.js +48 -0
  51. package/src/commands/oauth-consumers/oauth-consumers.update.command.js +83 -0
  52. package/src/commands/open/open.command.js +1 -1
  53. package/src/commands/profile/profile.open.command.js +1 -1
  54. package/src/commands/restart/restart.docs.md +1 -1
  55. package/src/commands/ssh-keys/ssh-keys.open.command.js +1 -1
  56. package/src/commands/webhooks/webhooks.remove.command.js +2 -2
  57. package/src/lib/cliparse-patched.js +2 -0
  58. package/src/lib/get-command-info.js +3 -1
  59. package/src/lib/get-command-info.types.d.ts +4 -0
  60. package/src/lib/operator-commands.js +11 -25
  61. package/src/lib/print-items-by-owner.js +38 -0
  62. package/src/lib/prompts.js +23 -1
  63. package/src/lib/zod-utils.js +51 -0
  64. package/src/models/drain.js +17 -0
  65. package/src/models/ids-resolver.js +38 -20
  66. package/src/models/ng.js +2 -2
  67. package/src/models/notification.js +0 -4
  68. package/src/models/oauth-consumer.js +77 -0
  69. package/src/models/utils.js +1 -1
@@ -0,0 +1,130 @@
1
+ # 📖 `clever oauth-consumers` command reference
2
+
3
+ ## ➡️ `clever oauth-consumers` <kbd>Since 4.8.0</kbd>
4
+
5
+ Manage OAuth consumers used with a Clever Cloud login
6
+
7
+ ```bash
8
+ clever oauth-consumers
9
+ ```
10
+
11
+ ## ➡️ `clever oauth-consumers create` <kbd>Since 4.8.0</kbd>
12
+
13
+ Create an OAuth consumer
14
+
15
+ ```bash
16
+ clever oauth-consumers create <name> [options]
17
+ ```
18
+
19
+ ### 📥 Arguments
20
+
21
+ |Name|Description|
22
+ |---|---|
23
+ |`name`|Consumer name|
24
+
25
+ ### ⚙️ Options
26
+
27
+ |Name|Description|
28
+ |---|---|
29
+ |`--base-url` `<url>`|OAuth callback base URL|
30
+ |`-d`, `--description` `<description>`|Consumer description|
31
+ |`-F`, `--format` `<format>`|Output format (human, json) (default: human)|
32
+ |`-o`, `--org`, `--owner` `<org-id\|org-name>`|Organisation to target by its ID (or name, if unambiguous)|
33
+ |`--picture` `<url>`|Application logo URL|
34
+ |`--rights` `<rights>`|Comma-separated list of rights (access-organisations, access-organisations-bills, access-organisations-consumption-statistics, access-organisations-credit-count, access-personal-information, manage-organisations, manage-organisations-applications, manage-organisations-members, manage-organisations-services, manage-personal-information, manage-ssh-keys, all)|
35
+ |`--url` `<url>`|Application home URL|
36
+
37
+ ## ➡️ `clever oauth-consumers delete` <kbd>Since 4.8.0</kbd>
38
+
39
+ Delete an OAuth consumer
40
+
41
+ ```bash
42
+ clever oauth-consumers delete <consumer-key|consumer-name> [options]
43
+ ```
44
+
45
+ ### 📥 Arguments
46
+
47
+ |Name|Description|
48
+ |---|---|
49
+ |`consumer-key|consumer-name`|OAuth consumer key (or name, if unambiguous)|
50
+
51
+ ### ⚙️ Options
52
+
53
+ |Name|Description|
54
+ |---|---|
55
+ |`-y`, `--yes`|Skip confirmation and proceed with deletion directly|
56
+
57
+ ## ➡️ `clever oauth-consumers get` <kbd>Since 4.8.0</kbd>
58
+
59
+ Get details of an OAuth consumer
60
+
61
+ ```bash
62
+ clever oauth-consumers get <consumer-key|consumer-name> [options]
63
+ ```
64
+
65
+ ### 📥 Arguments
66
+
67
+ |Name|Description|
68
+ |---|---|
69
+ |`consumer-key|consumer-name`|OAuth consumer key (or name, if unambiguous)|
70
+
71
+ ### ⚙️ Options
72
+
73
+ |Name|Description|
74
+ |---|---|
75
+ |`-F`, `--format` `<format>`|Output format (human, json) (default: human)|
76
+ |`--with-secret`|Include the consumer secret in the output|
77
+
78
+ ## ➡️ `clever oauth-consumers list` <kbd>Since 4.8.0</kbd>
79
+
80
+ List OAuth consumers
81
+
82
+ ```bash
83
+ clever oauth-consumers list [options]
84
+ ```
85
+
86
+ ### ⚙️ Options
87
+
88
+ |Name|Description|
89
+ |---|---|
90
+ |`-F`, `--format` `<format>`|Output format (human, json) (default: human)|
91
+
92
+ ## ➡️ `clever oauth-consumers open` <kbd>Since 4.8.0</kbd>
93
+
94
+ Open an OAuth consumer in the Clever Cloud Console
95
+
96
+ ```bash
97
+ clever oauth-consumers open <consumer-key|consumer-name>
98
+ ```
99
+
100
+ ### 📥 Arguments
101
+
102
+ |Name|Description|
103
+ |---|---|
104
+ |`consumer-key|consumer-name`|OAuth consumer key (or name, if unambiguous)|
105
+
106
+ ## ➡️ `clever oauth-consumers update` <kbd>Since 4.8.0</kbd>
107
+
108
+ Update an OAuth consumer
109
+
110
+ ```bash
111
+ clever oauth-consumers update <consumer-key|consumer-name> [options]
112
+ ```
113
+
114
+ ### 📥 Arguments
115
+
116
+ |Name|Description|
117
+ |---|---|
118
+ |`consumer-key|consumer-name`|OAuth consumer key (or name, if unambiguous)|
119
+
120
+ ### ⚙️ Options
121
+
122
+ |Name|Description|
123
+ |---|---|
124
+ |`--base-url` `<url>`|OAuth callback base URL|
125
+ |`-d`, `--description` `<description>`|Consumer description|
126
+ |`-F`, `--format` `<format>`|Output format (human, json) (default: human)|
127
+ |`-n`, `--name` `<name>`|Consumer name|
128
+ |`--picture` `<url>`|Application logo URL|
129
+ |`--rights` `<rights>`|Comma-separated list of rights (access-organisations, access-organisations-bills, access-organisations-consumption-statistics, access-organisations-credit-count, access-personal-information, manage-organisations, manage-organisations-applications, manage-organisations-members, manage-organisations-services, manage-personal-information, manage-ssh-keys, all)|
130
+ |`--url` `<url>`|Application home URL|
@@ -0,0 +1,67 @@
1
+ import { getSecret as getOauthConsumerSecret } from '@clevercloud/client/esm/api/v2/oauth-consumer.js';
2
+ import { z } from 'zod';
3
+ import { defineCommand } from '../../lib/define-command.js';
4
+ import { defineOption } from '../../lib/define-option.js';
5
+ import { Logger } from '../../logger.js';
6
+ import { OAUTH_RIGHTS, resolveOauthConsumer } from '../../models/oauth-consumer.js';
7
+ import { sendToApi } from '../../models/send-to-api.js';
8
+ import { humanJsonOutputFormatOption } from '../global.options.js';
9
+ import { consumerKeyOrNameArg } from './oauth-consumers.args.js';
10
+
11
+ export const oauthConsumersGetCommand = defineCommand({
12
+ description: 'Get details of an OAuth consumer',
13
+ since: '4.8.0',
14
+ options: {
15
+ format: humanJsonOutputFormatOption,
16
+ withSecret: defineOption({
17
+ name: 'with-secret',
18
+ schema: z.boolean().default(false),
19
+ description: 'Include the consumer secret in the output',
20
+ }),
21
+ },
22
+ args: [consumerKeyOrNameArg],
23
+ async handler(options, keyOrName) {
24
+ const { format, withSecret } = options;
25
+
26
+ const oauthConsumer = await resolveOauthConsumer(keyOrName);
27
+
28
+ const secret = withSecret
29
+ ? await getOauthConsumerSecret({ id: oauthConsumer.ownerId, key: oauthConsumer.key })
30
+ .then(sendToApi)
31
+ .then((c) => c.secret)
32
+ : null;
33
+
34
+ switch (format) {
35
+ case 'json': {
36
+ Logger.printJson({ ...oauthConsumer, ...(secret != null && { secret }) });
37
+ break;
38
+ }
39
+ case 'human':
40
+ default: {
41
+ const dataToPrint = {
42
+ Key: oauthConsumer.key,
43
+ Name: oauthConsumer.name || '(unnamed)',
44
+ Description: oauthConsumer.description || '',
45
+ URL: oauthConsumer.url || '',
46
+ Picture: oauthConsumer.picture || '',
47
+ 'Base URL': oauthConsumer.baseUrl || '',
48
+ };
49
+
50
+ if (secret != null) {
51
+ dataToPrint.Secret = secret;
52
+ }
53
+
54
+ console.table(dataToPrint);
55
+
56
+ if (oauthConsumer.rights) {
57
+ Logger.println('');
58
+ const rightsData = {};
59
+ for (const [apiName, cliName] of Object.entries(OAUTH_RIGHTS)) {
60
+ rightsData[cliName] = oauthConsumer.rights[apiName] ?? false;
61
+ }
62
+ console.table(rightsData);
63
+ }
64
+ }
65
+ }
66
+ },
67
+ });
@@ -0,0 +1,34 @@
1
+ import { defineCommand } from '../../lib/define-command.js';
2
+ import { printItemsByOwner } from '../../lib/print-items-by-owner.js';
3
+ import { Logger } from '../../logger.js';
4
+ import { getAllConsumers } from '../../models/oauth-consumer.js';
5
+ import { humanJsonOutputFormatOption } from '../global.options.js';
6
+
7
+ export const oauthConsumersListCommand = defineCommand({
8
+ description: 'List OAuth consumers',
9
+ since: '4.8.0',
10
+ options: {
11
+ format: humanJsonOutputFormatOption,
12
+ },
13
+ async handler(options) {
14
+ const { format } = options;
15
+ const oauthConsumers = await getAllConsumers();
16
+
17
+ switch (format) {
18
+ case 'json': {
19
+ const consumersPerOwner = Object.groupBy(oauthConsumers, (c) => c.ownerId);
20
+ Logger.printJson(consumersPerOwner);
21
+ break;
22
+ }
23
+ case 'human':
24
+ default: {
25
+ printItemsByOwner(oauthConsumers, {
26
+ itemName: 'OAuth consumer',
27
+ emptyCommand: 'clever oauth-consumers create',
28
+ getItemId: (c) => c.key,
29
+ getExtraLines: (c) => (c.url ? `URL: ${c.url}` : null),
30
+ });
31
+ }
32
+ }
33
+ },
34
+ });
@@ -0,0 +1,18 @@
1
+ import { defineCommand } from '../../lib/define-command.js';
2
+ import { styleText } from '../../lib/style-text.js';
3
+ import { resolveOauthConsumer } from '../../models/oauth-consumer.js';
4
+ import { openBrowser } from '../../models/utils.js';
5
+ import { consumerKeyOrNameArg } from './oauth-consumers.args.js';
6
+
7
+ export const oauthConsumersOpenCommand = defineCommand({
8
+ description: 'Open an OAuth consumer in the Clever Cloud Console',
9
+ since: '4.8.0',
10
+ args: [consumerKeyOrNameArg],
11
+ async handler(options, keyOrName) {
12
+ const oauthConsumer = await resolveOauthConsumer(keyOrName);
13
+ await openBrowser(
14
+ `/organisations/${oauthConsumer.ownerId}/oauth-consumers/${oauthConsumer.key}`,
15
+ `Opening OAuth consumer ${styleText('blue', oauthConsumer.key)} in the browser…`,
16
+ );
17
+ },
18
+ });
@@ -0,0 +1,48 @@
1
+ import { z } from 'zod';
2
+ import { defineOption } from '../../lib/define-option.js';
3
+ import { OAUTH_RIGHTS } from '../../models/oauth-consumer.js';
4
+
5
+ export const descriptionOption = defineOption({
6
+ name: 'description',
7
+ schema: z.string().trim().min(1).optional(),
8
+ description: 'Consumer description',
9
+ aliases: ['d'],
10
+ placeholder: 'description',
11
+ });
12
+
13
+ export const urlOption = defineOption({
14
+ name: 'url',
15
+ schema: z.string().url().optional(),
16
+ description: 'Application home URL',
17
+ placeholder: 'url',
18
+ });
19
+
20
+ export const pictureOption = defineOption({
21
+ name: 'picture',
22
+ schema: z.string().url().optional(),
23
+ description: 'Application logo URL',
24
+ placeholder: 'url',
25
+ });
26
+
27
+ export const baseUrlOption = defineOption({
28
+ name: 'base-url',
29
+ schema: z.string().url().optional(),
30
+ description: 'OAuth callback base URL',
31
+ placeholder: 'url',
32
+ });
33
+
34
+ /** @type {[string, ...string[]]} */
35
+ const OAUTH_RIGHTS_WITH_ALL = [...Object.values(OAUTH_RIGHTS), 'all'];
36
+
37
+ const rightsSchema = z
38
+ .string()
39
+ .transform((s) => s.split(',').map((r) => r.trim()))
40
+ .pipe(z.array(z.enum(OAUTH_RIGHTS_WITH_ALL)))
41
+ .optional();
42
+
43
+ export const rightsOption = defineOption({
44
+ name: 'rights',
45
+ schema: rightsSchema,
46
+ description: `Comma-separated list of rights (${OAUTH_RIGHTS_WITH_ALL.join(', ')})`,
47
+ placeholder: 'rights',
48
+ });
@@ -0,0 +1,83 @@
1
+ import { update as updateOauthConsumer } from '@clevercloud/client/esm/api/v2/oauth-consumer.js';
2
+ import { z } from 'zod';
3
+ import { defineCommand } from '../../lib/define-command.js';
4
+ import { defineOption } from '../../lib/define-option.js';
5
+ import { promptTextOption } from '../../lib/prompts.js';
6
+ import { styleText } from '../../lib/style-text.js';
7
+ import { Logger } from '../../logger.js';
8
+ import {
9
+ promptRights,
10
+ removeReadonlyRights,
11
+ resolveOauthConsumer,
12
+ rightsFromList,
13
+ } from '../../models/oauth-consumer.js';
14
+ import { sendToApi } from '../../models/send-to-api.js';
15
+ import { humanJsonOutputFormatOption } from '../global.options.js';
16
+ import { consumerKeyOrNameArg } from './oauth-consumers.args.js';
17
+ import { baseUrlOption, descriptionOption, pictureOption, rightsOption, urlOption } from './oauth-consumers.options.js';
18
+
19
+ const nameOption = defineOption({
20
+ name: 'name',
21
+ schema: z.string().trim().min(1).optional(),
22
+ description: 'Consumer name',
23
+ aliases: ['n'],
24
+ placeholder: 'name',
25
+ });
26
+
27
+ export const oauthConsumersUpdateCommand = defineCommand({
28
+ description: 'Update an OAuth consumer',
29
+ since: '4.8.0',
30
+ options: {
31
+ name: nameOption,
32
+ description: descriptionOption,
33
+ url: urlOption,
34
+ picture: pictureOption,
35
+ baseUrl: baseUrlOption,
36
+ rights: rightsOption,
37
+ format: humanJsonOutputFormatOption,
38
+ },
39
+ args: [consumerKeyOrNameArg],
40
+ async handler(options, keyOrName) {
41
+ const { name, description, url, picture, baseUrl, rights, format } = options;
42
+
43
+ const oauthConsumer = await resolveOauthConsumer(keyOrName);
44
+
45
+ const hasAnyOption = [name, description, url, picture, baseUrl, rights].some((v) => v != null);
46
+
47
+ const body = hasAnyOption
48
+ ? {
49
+ name: name ?? oauthConsumer.name,
50
+ description: description ?? oauthConsumer.description,
51
+ url: url ?? oauthConsumer.url,
52
+ picture: picture ?? oauthConsumer.picture,
53
+ baseUrl: baseUrl ?? oauthConsumer.baseUrl,
54
+ rights: rights != null ? rightsFromList(rights) : removeReadonlyRights(oauthConsumer.rights),
55
+ }
56
+ : {
57
+ name: await promptTextOption(nameOption, oauthConsumer.name),
58
+ description: await promptTextOption(descriptionOption, oauthConsumer.description),
59
+ url: await promptTextOption(urlOption, oauthConsumer.url),
60
+ picture: await promptTextOption(pictureOption, oauthConsumer.picture),
61
+ baseUrl: await promptTextOption(baseUrlOption, oauthConsumer.baseUrl),
62
+ rights: await promptRights(oauthConsumer.rights),
63
+ };
64
+
65
+ const updatedOauthConsumer = await updateOauthConsumer(
66
+ { id: oauthConsumer.ownerId, key: oauthConsumer.key },
67
+ body,
68
+ ).then(sendToApi);
69
+
70
+ switch (format) {
71
+ case 'json': {
72
+ Logger.printJson(updatedOauthConsumer);
73
+ break;
74
+ }
75
+ case 'human':
76
+ default: {
77
+ Logger.printSuccess(
78
+ `OAuth consumer ${styleText(['bold', 'green'], updatedOauthConsumer.key)} has been updated!`,
79
+ );
80
+ }
81
+ }
82
+ },
83
+ });
@@ -19,6 +19,6 @@ export const openCommand = defineCommand({
19
19
  const vhost = await Domain.getBest(appId, ownerId);
20
20
  const url = 'https://' + vhost.fqdn;
21
21
 
22
- await openBrowser(url, 'Opening the application in your browser');
22
+ await openBrowser(url, 'Opening the application in the browser');
23
23
  },
24
24
  });
@@ -7,6 +7,6 @@ export const profileOpenCommand = defineCommand({
7
7
  options: {},
8
8
  args: [],
9
9
  async handler() {
10
- await openBrowser('/users/me/information', 'Opening the profile page in your browser');
10
+ await openBrowser('/users/me/information', 'Opening the profile page in the browser');
11
11
  },
12
12
  });
@@ -15,7 +15,7 @@ clever restart [options]
15
15
  |`-a`, `--alias` `<alias>`|Short name for the application|
16
16
  |`--app` `<app-id\|app-name>`|Application to manage by its ID (or name, if unambiguous)|
17
17
  |`--commit` `<commit-id>`|Restart the application with a specific commit ID|
18
- |`-e`, `--exit-on` `<step>`|Step at which the logs streaming is ended, steps are: deploy-start, deploy-end, never (default: deploy-end)|
18
+ |`-e`, `--exit-on` `<step>`|Step at which the logs streaming is ended (deploy-start, deploy-end, never) (default: deploy-end)|
19
19
  |`--follow`|Continue to follow logs after deployment has ended *(deprecated, use `--exit-on never` instead)*|
20
20
  |`-q`, `--quiet`|Don't show logs during deployment|
21
21
  |`--without-cache`|Restart the application without using cache|
@@ -7,6 +7,6 @@ export const sshKeysOpenCommand = defineCommand({
7
7
  options: {},
8
8
  args: [],
9
9
  handler() {
10
- return openBrowser('/users/me/ssh-keys', 'Opening the SSH keys management page of the Console in your browser');
10
+ return openBrowser('/users/me/ssh-keys', 'Opening the SSH keys page in the browser');
11
11
  },
12
12
  });
@@ -1,7 +1,7 @@
1
1
  import { deleteWebhook } from '@clevercloud/client/esm/api/v2/notification.js';
2
2
  import { defineCommand } from '../../lib/define-command.js';
3
3
  import { Logger } from '../../logger.js';
4
- import { getOrgaIdOrUserId } from '../../models/notification.js';
4
+ import { getOwnerIdFromOrgIdOrName } from '../../models/ids-resolver.js';
5
5
  import { sendToApi } from '../../models/send-to-api.js';
6
6
  import { notificationIdArg } from '../global.args.js';
7
7
  import { orgaIdOrNameOption } from '../global.options.js';
@@ -16,7 +16,7 @@ export const webhooksRemoveCommand = defineCommand({
16
16
  async handler(options, notificationId) {
17
17
  const { org } = options;
18
18
 
19
- const ownerId = await getOrgaIdOrUserId(org);
19
+ const ownerId = await getOwnerIdFromOrgIdOrName(org);
20
20
  await deleteWebhook({ ownerId, id: notificationId }).then(sendToApi);
21
21
 
22
22
  Logger.println('The notification has been successfully removed');
@@ -80,6 +80,7 @@ cliparseCommandModule.help = function (context) {
80
80
  if (commandInfo.args) {
81
81
  argumentsRows = commandInfo.args.map((arg) => {
82
82
  let description = arg.description;
83
+ if (arg.enumValues) description += ` (${arg.enumValues.join(', ')})`;
83
84
  if (arg.optional) description += ` ${styleText('dim', arg.optional)}`;
84
85
  return [arg.name, description];
85
86
  });
@@ -93,6 +94,7 @@ cliparseCommandModule.help = function (context) {
93
94
  const aliasesPadding = opt.aliases[0].length === 2 ? '' : ' ';
94
95
  const aliases = aliasesPadding + opt.aliases.join(', ') + placeholder;
95
96
  let description = opt.description;
97
+ if (opt.enumValues) description += ` (${opt.enumValues.join(', ')})`;
96
98
  if (opt.deprecated) description += ` ${styleText('dim', opt.deprecated)}`;
97
99
  if (opt.required) description += ` ${styleText('dim', opt.required)}`;
98
100
  if (opt.default) description += ` ${styleText('dim', opt.default)}`;
@@ -1,4 +1,4 @@
1
- import { getDefault, isBoolean, isRequired } from './zod-utils.js';
1
+ import { getDefault, getEnumValues, isBoolean, isRequired } from './zod-utils.js';
2
2
 
3
3
  /**
4
4
  * @typedef {import('./get-command-info.types.d.ts').ArgumentInfo} ArgumentInfo
@@ -45,6 +45,7 @@ export function getCommandInfo(path, definition) {
45
45
  name: arg.placeholder,
46
46
  description: arg.description,
47
47
  optional: isRequired(arg.schema) ? null : '(optional)',
48
+ enumValues: getEnumValues(arg.schema) ?? null,
48
49
  };
49
50
  })
50
51
  : null;
@@ -78,6 +79,7 @@ export function getCommandInfo(path, definition) {
78
79
  : null,
79
80
  required: isRequired(option.schema) ? '(required)' : null,
80
81
  default: defaultValue ? `(default: ${defaultValue})` : null,
82
+ enumValues: getEnumValues(option.schema) ?? null,
81
83
  };
82
84
  })
83
85
  : null;
@@ -13,6 +13,8 @@ export interface ArgumentInfo {
13
13
  description: string;
14
14
  /** '(optional)' or null */
15
15
  optional: string | null;
16
+ /** Enum values if the schema is a z.enum(), or null */
17
+ enumValues: string[] | null;
16
18
  }
17
19
 
18
20
  export interface OptionInfo {
@@ -30,6 +32,8 @@ export interface OptionInfo {
30
32
  required: string | null;
31
33
  /** '(default: value)' or null */
32
34
  default: string | null;
35
+ /** Enum values if the schema is a z.enum(), or null */
36
+ enumValues: string[] | null;
33
37
  }
34
38
 
35
39
  export interface CommandInfo {
@@ -15,6 +15,7 @@ import { findAddonsByAddonProvider } from '../models/ids-resolver.js';
15
15
  import * as Operator from '../models/operator.js';
16
16
  import { sendToApi } from '../models/send-to-api.js';
17
17
  import { openBrowser } from '../models/utils.js';
18
+ import { printItemsByOwner } from './print-items-by-owner.js';
18
19
  import { confirm, selectAnswer } from './prompts.js';
19
20
  import { styleText } from './style-text.js';
20
21
 
@@ -141,30 +142,18 @@ export async function operatorNgEnable(provider, addonIdOrName) {
141
142
  export async function operatorList(provider, format) {
142
143
  const deployed = await findAddonsByAddonProvider(provider);
143
144
  const providerName = _.capitalize(provider.replace('addon-', ''));
144
- const operatorsPerOwner = _.groupBy(deployed, 'ownerId');
145
-
146
145
  switch (format) {
147
- case 'json':
146
+ case 'json': {
147
+ const operatorsPerOwner = Object.groupBy(deployed, (o) => o.ownerId);
148
148
  Logger.printJson(operatorsPerOwner);
149
149
  break;
150
+ }
150
151
  case 'human':
151
152
  default:
152
- if (deployed.length === 0) {
153
- Logger.println(
154
- `🔎 No ${providerName} found, create one with ${styleText('blue', `clever addon create ${providerName.toLocaleLowerCase()}`)} command`,
155
- );
156
- return;
157
- }
158
-
159
- Logger.println(`🔎 Found ${deployed.length} ${providerName} operator${deployed.length > 1 ? 's' : ''}:`);
160
- Logger.println();
161
-
162
- Object.values(operatorsPerOwner).forEach((operators) => {
163
- Logger.println(`• ${styleText('bold', `${operators[0].ownerId} (${operators[0].ownerName})`)}`);
164
- operators.forEach((operator) => {
165
- Logger.println(` • ${operator.name} ${styleText('grey', `(${operator.realId})`)}`);
166
- });
167
- Logger.println();
153
+ printItemsByOwner(deployed, {
154
+ itemName: `${providerName} operator`,
155
+ emptyCommand: `clever addon create ${providerName.toLocaleLowerCase()}`,
156
+ getItemId: (o) => o.realId,
168
157
  });
169
158
  break;
170
159
  }
@@ -180,7 +169,7 @@ export async function operatorOpen(provider, addonIdOrName) {
180
169
  const operator = await Operator.getDetails(provider, addonIdOrName);
181
170
  await openBrowser(
182
171
  `${config.GOTO_URL}/${operator.addonId}`,
183
- `🌐 Opening ${styleText('blue', operator.addonId)} in the browser…`,
172
+ `Opening ${styleText('blue', operator.addonId)} in the browser…`,
184
173
  );
185
174
  }
186
175
 
@@ -194,7 +183,7 @@ export async function operatorOpenLogs(provider, addonIdOrName) {
194
183
  const operator = await Operator.getDetails(provider, addonIdOrName);
195
184
  await openBrowser(
196
185
  `/organisations/${operator.ownerId}/applications/${operator.resources.entrypoint}/logs`,
197
- `🌐 Opening ${styleText('blue', operator.addonId)} logs in the Clever Cloud Console…`,
186
+ `Opening ${styleText('blue', operator.addonId)} logs in the browser…`,
198
187
  );
199
188
  }
200
189
 
@@ -206,10 +195,7 @@ export async function operatorOpenLogs(provider, addonIdOrName) {
206
195
  */
207
196
  export async function operatorOpenWebUi(provider, addonIdOrName) {
208
197
  const operator = await Operator.getDetails(provider, addonIdOrName);
209
- await openBrowser(
210
- operator.accessUrl,
211
- `🌐 Opening ${styleText('blue', operator.addonId)} Management interface in the browser…`,
212
- );
198
+ await openBrowser(operator.accessUrl, `Opening ${styleText('blue', operator.addonId)} web UI in the browser…`);
213
199
  }
214
200
 
215
201
  /**
@@ -0,0 +1,38 @@
1
+ import { Logger } from '../logger.js';
2
+ import { styleText } from './style-text.js';
3
+
4
+ /**
5
+ * Print a list of items grouped by owner with consistent formatting.
6
+ * Items must have ownerId, ownerName and name properties.
7
+ * @param {Array} items - The items to display
8
+ * @param {Object} options
9
+ * @param {string} options.itemName - Singular name for the items (e.g. "configuration provider")
10
+ * @param {string} options.emptyCommand - Command suggestion shown when no items are found
11
+ * @param {Function} options.getItemId - Function to get the item ID from an item
12
+ * @param {Function} [options.getExtraLines] - Function returning extra detail lines to display below the item (nulls filtered)
13
+ */
14
+ export function printItemsByOwner(items, { itemName, emptyCommand, getItemId, getExtraLines }) {
15
+ if (items.length === 0) {
16
+ Logger.println(`🔎 No ${itemName} found, create one with ${styleText('blue', emptyCommand)} command`);
17
+ return;
18
+ }
19
+
20
+ const plural = items.length > 1 ? 's' : '';
21
+ Logger.println(`🔎 Found ${items.length} ${itemName}${plural}:`);
22
+ Logger.println();
23
+
24
+ const groups = Object.groupBy(items, (item) => item.ownerId);
25
+ for (const group of Object.values(groups)) {
26
+ const { ownerId, ownerName } = group[0];
27
+ Logger.println(`• ${styleText('bold', `${ownerId} (${ownerName})`)}`);
28
+ for (const item of group) {
29
+ Logger.println(` • ${item.name || '(unnamed)'} ${styleText('grey', `(${getItemId(item)})`)}`);
30
+ if (getExtraLines != null) {
31
+ for (const line of [getExtraLines(item)].flat().filter(Boolean)) {
32
+ Logger.println(` ${line}`);
33
+ }
34
+ }
35
+ }
36
+ Logger.println();
37
+ }
38
+ }
@@ -1,4 +1,5 @@
1
- import { confirm as confirmPrompt, input, password, select } from '@inquirer/prompts';
1
+ import { checkbox, confirm as confirmPrompt, input, password, select } from '@inquirer/prompts';
2
+ import { unwrapSchema } from './zod-utils.js';
2
3
 
3
4
  export function promptSecret(message) {
4
5
  return password({ message, mask: true }).catch(exitOnPromptError);
@@ -24,6 +25,27 @@ export function selectAnswer(message, choices) {
24
25
  return select({ message, choices }).catch(exitOnPromptError);
25
26
  }
26
27
 
28
+ export function promptCheckbox(message, choices) {
29
+ return checkbox({ message, choices }).catch(exitOnPromptError);
30
+ }
31
+
32
+ /**
33
+ * Prompt the user for a text value based on an option definition.
34
+ * Derives the prompt message from option.description and validation from option.schema.
35
+ * @param {import('./define-option.types.js').OptionDefinition} option
36
+ * @param {string} [defaultValue]
37
+ * @returns {Promise<string>}
38
+ */
39
+ export function promptTextOption(option, defaultValue) {
40
+ const message = `${option.description}:`;
41
+ const innerSchema = unwrapSchema(option.schema);
42
+ const validate = (value) => {
43
+ const result = innerSchema.safeParse(value);
44
+ return result.success || result.error.issues[0]?.message || 'Invalid value';
45
+ };
46
+ return input({ message, default: defaultValue, validate }).catch(exitOnPromptError);
47
+ }
48
+
27
49
  function exitOnPromptError(error) {
28
50
  if (error instanceof Error && error.name === 'ExitPromptError') {
29
51
  process.exit(1);