clever-tools 3.11.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 (57) hide show
  1. package/bin/clever.js +416 -3
  2. package/package.json +4 -3
  3. package/src/clever-client/auth-bridge.js +61 -0
  4. package/src/clever-client/ng.js +18 -0
  5. package/src/clever-client/operators.js +121 -0
  6. package/src/commands/addon.js +34 -28
  7. package/src/commands/cancel-deploy.js +3 -4
  8. package/src/commands/config.js +16 -20
  9. package/src/commands/console.js +4 -10
  10. package/src/commands/create.js +76 -9
  11. package/src/commands/curl.js +17 -17
  12. package/src/commands/database.js +9 -9
  13. package/src/commands/delete.js +11 -10
  14. package/src/commands/deploy.js +35 -21
  15. package/src/commands/domain.js +13 -16
  16. package/src/commands/emails.js +174 -0
  17. package/src/commands/env.js +15 -8
  18. package/src/commands/keycloak.js +138 -0
  19. package/src/commands/kv.js +1 -1
  20. package/src/commands/link.js +7 -3
  21. package/src/commands/makeDefault.js +2 -1
  22. package/src/commands/matomo.js +85 -0
  23. package/src/commands/metabase.js +112 -0
  24. package/src/commands/ng.js +202 -0
  25. package/src/commands/open.js +2 -5
  26. package/src/commands/otoroshi.js +138 -0
  27. package/src/commands/profile.js +11 -10
  28. package/src/commands/published-config.js +7 -7
  29. package/src/commands/restart.js +1 -1
  30. package/src/commands/ssh-keys.js +159 -0
  31. package/src/commands/stop.js +3 -3
  32. package/src/commands/tcp-redirs.js +8 -8
  33. package/src/commands/tokens.js +146 -0
  34. package/src/commands/unlink.js +2 -1
  35. package/src/experimental-features.js +47 -2
  36. package/src/lib/ng-print.js +195 -0
  37. package/src/lib/operator-commands.js +281 -0
  38. package/src/lib/prompts.js +30 -0
  39. package/src/lib/slugify.js +10 -0
  40. package/src/logger.js +3 -0
  41. package/src/models/activity.js +2 -3
  42. package/src/models/addon.js +9 -6
  43. package/src/models/app_configuration.js +12 -9
  44. package/src/models/application.js +44 -22
  45. package/src/models/application_configuration.js +72 -72
  46. package/src/models/configuration.js +1 -0
  47. package/src/models/git.js +29 -1
  48. package/src/models/ids-resolver.js +37 -1
  49. package/src/models/interact.js +0 -24
  50. package/src/models/log-v4.js +13 -4
  51. package/src/models/namespaces.js +3 -2
  52. package/src/models/ng-resources.js +270 -0
  53. package/src/models/ng.js +276 -0
  54. package/src/models/operator.js +48 -0
  55. package/src/models/send-to-api.js +18 -0
  56. package/src/models/utils.js +21 -0
  57. package/src/parsers.js +71 -3
@@ -0,0 +1,121 @@
1
+ // TODO: Move this to the Clever Cloud JS Client
2
+
3
+ /**
4
+ * GET /v4/addon-providers/addon-{provider}/addons/{realId}
5
+ * @param {Object} params
6
+ * @param {String} params.provider
7
+ * @param {String} params.realId
8
+ */
9
+ export function getOperator (params) {
10
+ // no multipath for /self or /organisations/{id}
11
+ return Promise.resolve({
12
+ method: 'get',
13
+ url: `/v4/addon-providers/addon-${params.provider}/addons/${params.realId}`,
14
+ headers: { Accept: 'application/json' },
15
+ // no queryParams
16
+ // no body
17
+ });
18
+ }
19
+
20
+ /**
21
+ * POST /v4/addon-providers/addon-{provider}/addons/{realId}/reboot
22
+ * @param {Object} params
23
+ * @param {String} params.provider
24
+ * @param {String} params.realId
25
+ */
26
+ export function rebootOperator (params) {
27
+ // no multipath for /self or /organisations/{id}
28
+ return Promise.resolve({
29
+ method: 'post',
30
+ url: `/v4/addon-providers/addon-${params.provider}/addons/${params.realId}/reboot`,
31
+ headers: { Accept: 'application/json' },
32
+ // no queryParams
33
+ // no body
34
+ });
35
+ }
36
+
37
+ /**
38
+ * POST /v4/addon-providers/addon-{provider}/addons/{realId}/rebuild
39
+ * @param {Object} params
40
+ * @param {String} params.provider
41
+ * @param {String} params.realId
42
+ */
43
+ export function rebuildOperator (params) {
44
+ // no multipath for /self or /organisations/{id}
45
+ return Promise.resolve({
46
+ method: 'post',
47
+ url: `/v4/addon-providers/addon-${params.provider}/addons/${params.realId}/rebuild`,
48
+ headers: { Accept: 'application/json' },
49
+ // no queryParams
50
+ // no body
51
+ });
52
+ }
53
+
54
+ /**
55
+ * POST /v4/addon-providers/addon-{provider}/addons/{realId}/networkgroup
56
+ * @param {Object} params
57
+ * @param {String} params.provider
58
+ * @param {String} params.realId
59
+ */
60
+ export function ngEnableOperator (params) {
61
+ // no multipath for /self or /organisations/{id}
62
+ return Promise.resolve({
63
+ method: 'post',
64
+ url: `/v4/addon-providers/addon-${params.provider}/addons/${params.realId}/networkgroup`,
65
+ headers: { Accept: 'application/json' },
66
+ // no queryParams
67
+ // no body
68
+ });
69
+ }
70
+
71
+ /**
72
+ * POST /v4/addon-providers/addon-{provider}/addons/{realId}/networkgroup
73
+ * @param {Object} params
74
+ * @param {String} params.provider
75
+ * @param {String} params.realId
76
+ */
77
+ export function ngDisableOperator (params) {
78
+ // no multipath for /self or /organisations/{id}
79
+ return Promise.resolve({
80
+ method: 'delete',
81
+ url: `/v4/addon-providers/addon-${params.provider}/addons/${params.realId}/networkgroup`,
82
+ headers: { Accept: 'application/json' },
83
+ // no queryParams
84
+ // no body
85
+ });
86
+ }
87
+
88
+ /**
89
+ * GET /v4/addon-providers/addon-{provider}/addons/{realId}/version/check
90
+ * @param {Object} params
91
+ * @param {String} params.provider
92
+ * @param {String} params.realId
93
+ */
94
+ export function versionCheck (params) {
95
+ // no multipath for /self or /organisations/{id}
96
+ return Promise.resolve({
97
+ method: 'get',
98
+ url: `/v4/addon-providers/addon-${params.provider}/addons/${params.realId}/version/check`,
99
+ headers: { Accept: 'application/json' },
100
+ // no queryParams
101
+ // no body
102
+ });
103
+ }
104
+
105
+ /**
106
+ * POST /v4/addon-providers/addon-{provider}/addons/{realId}/version/update
107
+ * @param {Object} params
108
+ * @param {String} params.provider
109
+ * @param {String} params.realId
110
+ * @param {Object} body
111
+ */
112
+ export function versionUpdate (params, body) {
113
+ // no multipath for /self or /organisations/{id}
114
+ return Promise.resolve({
115
+ method: 'post',
116
+ url: `/v4/addon-providers/addon-${params.provider}/addons/${params.realId}/version/update`,
117
+ headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
118
+ // no queryParams
119
+ body,
120
+ });
121
+ }
@@ -12,6 +12,7 @@ import { sendToApi } from '../models/send-to-api.js';
12
12
  import { toNameEqualsValueString } from '@clevercloud/client/esm/utils/env-vars.js';
13
13
  import { resolveAddonId } from '../models/ids-resolver.js';
14
14
  import { conf } from '../models/configuration.js';
15
+ import dedent from 'dedent';
15
16
 
16
17
  const formatTable = initFormatTable();
17
18
 
@@ -123,49 +124,49 @@ function displayAddon (format, addon, providerName, message) {
123
124
 
124
125
  const WIP_PROVIDERS = {
125
126
  keycloak: {
126
- status: 'beta',
127
- postCreateInstructions: [
128
- `Learn more about Keycloak on Clever Cloud: ${conf.DOC_URL}/addons/keycloak/`,
129
- ].join('\n'),
127
+ status: '',
128
+ postCreateInstructions: dedent`
129
+ Learn more about Keycloak on Clever Cloud: ${conf.DOC_URL}/addons/keycloak/,
130
+ `,
130
131
  },
131
132
  kv: {
132
133
  status: 'alpha',
133
- postCreateInstructions: [
134
- colors.yellow('You can easily use Materia KV with \'redis-cli\', with such commands:'),
135
- colors.blue(`source <(clever addon env ${addon.id} -F shell)`),
136
- colors.blue('redis-cli -h $KV_HOST -p $KV_PORT --tls'),
137
- `Learn more about Materia KV on Clever Cloud: ${conf.DOC_URL}/addons/materia-kv/`,
138
- ].join('\n'),
134
+ postCreateInstructions: dedent`
135
+ ${colors.yellow('You can easily use Materia KV with \'redis-cli\', with such commands:')}
136
+ ${colors.blue(`source <(clever addon env ${addon.id} -F shell)`)}
137
+ ${colors.blue('redis-cli -h $KV_HOST -p $KV_PORT --tls')}
138
+ Learn more about Materia KV on Clever Cloud: ${conf.DOC_URL}/addons/materia-kv/
139
+ `,
139
140
  },
140
141
  'addon-matomo': {
141
- status: 'beta',
142
- postCreateInstructions: [
143
- `Learn more about Matomo on Clever Cloud: ${conf.DOC_URL}/addons/matomo/`,
144
- ].join('\n'),
142
+ status: '',
143
+ postCreateInstructions: dedent`
144
+ Learn more about Matomo on Clever Cloud: ${conf.DOC_URL}/addons/matomo/
145
+ `,
145
146
  },
146
147
  metabase: {
147
- status: 'beta',
148
- postCreateInstructions: [
149
- `Learn more about Metabase on Clever Cloud: ${conf.DOC_URL}/addons/metabase/`,
150
- ].join('\n'),
148
+ status: '',
149
+ postCreateInstructions: dedent`
150
+ Learn more about Metabase on Clever Cloud: ${conf.DOC_URL}/addons/metabase/
151
+ `,
151
152
  },
152
153
  otoroshi: {
153
- status: 'beta',
154
- postCreateInstructions: [
155
- `Learn more about Otoroshi with LLM on Clever Cloud: ${conf.DOC_URL}/addons/otoroshi/`,
156
- ].join('\n'),
154
+ status: '',
155
+ postCreateInstructions: dedent`
156
+ Learn more about Otoroshi with LLM on Clever Cloud: ${conf.DOC_URL}/addons/otoroshi/
157
+ `,
157
158
  },
158
159
  'addon-pulsar': {
159
160
  status: 'beta',
160
- postCreateInstructions: [
161
- `Learn more about Pulsar on Clever Cloud: ${conf.DOC_URL}/addons/pulsar/`,
162
- ].join('\n'),
161
+ postCreateInstructions: dedent`
162
+ Learn more about Pulsar on Clever Cloud: ${conf.DOC_URL}/addons/pulsar/
163
+ `,
163
164
  },
164
165
  };
165
166
 
166
167
  let providerNameToShow = '';
167
168
  let statusMessage = '';
168
- if (providerName in WIP_PROVIDERS) {
169
+ if (providerName in WIP_PROVIDERS && WIP_PROVIDERS[providerName].status !== '') {
169
170
 
170
171
  providerNameToShow = providerName === 'kv'
171
172
  ? 'Materia KV'
@@ -206,11 +207,12 @@ function displayAddon (format, addon, providerName, message) {
206
207
  const provider = PROVIDERS_WITH_URL[providerName];
207
208
  const urlEnv = addon.env.find((entry) => entry.name === provider.urlEnv);
208
209
  const urlToShow = urlEnv.value;
210
+ const urlToShowWithHttps = urlToShow.startsWith('http') ? urlToShow : `https://${urlToShow}`;
209
211
 
210
212
  if (urlEnv) {
211
213
  Logger.println();
212
214
  Logger.println(`Your ${provider.name} is starting:`);
213
- Logger.println(` - Access it: ${urlToShow.startsWith('http') ? urlToShow : `https://${urlToShow}`}`);
215
+ Logger.println(` - Access it: ${urlToShowWithHttps}`);
214
216
  Logger.println(` - Manage it: ${conf.GOTO_URL}/${addon.id}`);
215
217
  }
216
218
 
@@ -232,7 +234,11 @@ function displayAddon (format, addon, providerName, message) {
232
234
  if (providerName in WIP_PROVIDERS) {
233
235
 
234
236
  Logger.println();
235
- Logger.println(colors.yellow(`/!\\ ${statusMessage}`));
237
+
238
+ if (statusMessage !== '') {
239
+ Logger.println(colors.yellow(`/!\\ ${statusMessage}`));
240
+ }
241
+
236
242
  Logger.println(WIP_PROVIDERS[providerName].postCreateInstructions);
237
243
  }
238
244
  }
@@ -2,6 +2,7 @@ import * as Application from '../models/application.js';
2
2
  import { Logger } from '../logger.js';
3
3
  import { getAllDeployments, cancelDeployment } from '@clevercloud/client/esm/api/v2/application.js';
4
4
  import { sendToApi } from '../models/send-to-api.js';
5
+ import colors from 'colors/safe.js';
5
6
 
6
7
  export async function cancelDeploy (params) {
7
8
  const { alias, app: appIdOrName } = params.options;
@@ -13,8 +14,6 @@ export async function cancelDeploy (params) {
13
14
  throw new Error('There is no ongoing deployment for this application');
14
15
  }
15
16
 
16
- const deploymentId = deployments[0].id;
17
- await cancelDeployment({ id: ownerId, appId, deploymentId }).then(sendToApi);
18
-
19
- Logger.println('Deployment cancelled!');
17
+ await cancelDeployment({ id: ownerId, appId, deploymentId: deployments[0].id }).then(sendToApi);
18
+ Logger.printSuccess(`Deployment ${colors.bold.green(deployments[0].uuid)} successfully cancelled!`);
20
19
  };
@@ -1,22 +1,21 @@
1
- import * as application 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 application.update({ 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 application.update({ 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,9 +1,10 @@
1
1
  import { spawn } from 'node:child_process';
2
- import { loadOAuthConf, conf } from '../models/configuration.js';
2
+ import { conf, loadOAuthConf } from '../models/configuration.js';
3
3
  import { addOauthHeader } from '@clevercloud/client/esm/oauth.js';
4
4
  import { Logger } from '../logger.js';
5
5
  import colors from 'colors/safe.js';
6
6
  import curlconverter from 'curlconverter';
7
+ import dedent from 'dedent';
7
8
 
8
9
  async function loadTokens () {
9
10
  const tokens = await loadOAuthConf();
@@ -16,22 +17,21 @@ async function loadTokens () {
16
17
  }
17
18
 
18
19
  function printCleverCurlHelp () {
19
- const apiDocUrlv2 = `${conf.API_DOC_URL}/v2/`;
20
- const apiDocUrlv4 = `${conf.API_DOC_URL}/v4/`;
21
-
22
- Logger.println(`Usage: clever curl
23
- Query Clever Cloud's API using Clever Tools credentials. For example:
24
-
25
- clever curl ${conf.API_HOST}/v2/self
26
- clever curl ${conf.API_HOST}/v2/summary
27
- clever curl ${conf.API_HOST}/v4/products/zones
28
- clever curl ${conf.API_HOST}/v2/organisations/<ORGANISATION_ID>/applications | jq '.[].id'
29
- clever curl ${conf.API_HOST}/v4/billing/organisations/<ORGANISATION_ID>/<INVOICE_NUMBER>.pdf > invoice.pdf
30
-
31
- Our API documentation is available here :
32
-
33
- ${apiDocUrlv2}
34
- ${apiDocUrlv4}`);
20
+ Logger.println(dedent`
21
+ Usage: clever curl
22
+ Query Clever Cloud's API using Clever Tools credentials. For example:
23
+
24
+ clever curl ${conf.API_HOST}/v2/self
25
+ clever curl ${conf.API_HOST}/v2/summary
26
+ clever curl ${conf.API_HOST}/v4/products/zones
27
+ clever curl ${conf.API_HOST}/v2/organisations/<ORGANISATION_ID>/applications | jq '.[].id'
28
+ clever curl ${conf.API_HOST}/v4/billing/organisations/<ORGANISATION_ID>/<INVOICE_NUMBER>.pdf > invoice.pdf
29
+
30
+ Our API documentation is available here :
31
+
32
+ ${conf.API_DOC_URL}/v2/
33
+ ${conf.API_DOC_URL}/v4/
34
+ `);
35
35
  }
36
36
 
37
37
  export async function curl () {
@@ -2,8 +2,8 @@ import { sendToApi } from '../models/send-to-api.js';
2
2
  import { getBackups } from '@clevercloud/client/esm/api/v2/backups.js';
3
3
  import { Logger } from '../logger.js';
4
4
  import { formatTable as initFormatTable } from '../format-table.js';
5
- import superagent from 'superagent';
6
5
  import fs from 'node:fs';
6
+ import { Writable } from 'node:stream';
7
7
  import { findOwnerId } from '../models/addon.js';
8
8
  import { resolveRealId, resolveAddonId } from '../models/ids-resolver.js';
9
9
 
@@ -80,14 +80,14 @@ export async function downloadBackups (params) {
80
80
  throw new Error('no backup with this ID');
81
81
  }
82
82
 
83
- const res = await superagent
84
- .get(backup.download_url)
85
- .responseType('blob');
86
-
87
- if (output) {
88
- fs.writeFileSync(output, res.body);
89
- return;
83
+ const response = await globalThis.fetch(backup.download_url);
84
+ if (!response.ok) {
85
+ throw new Error('Failed to download backup');
90
86
  }
91
87
 
92
- process.stdout.write(res.body);
88
+ await response
89
+ .body
90
+ .pipeTo(
91
+ Writable.toWeb(output ? fs.createWriteStream(output) : process.stdout),
92
+ );
93
93
  }
@@ -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
  };