clever-tools 4.5.1 → 4.6.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 (77) hide show
  1. package/README.md +10 -0
  2. package/bin/clever.js +25 -22
  3. package/package.json +2 -2
  4. package/src/clever-client/auth-bridge.js +0 -25
  5. package/src/commands/README.md +1 -0
  6. package/src/commands/accesslogs/accesslogs.command.js +4 -1
  7. package/src/commands/accesslogs/accesslogs.docs.md +1 -1
  8. package/src/commands/addon/addon.create.command.js +26 -23
  9. package/src/commands/config/config.set.command.js +3 -3
  10. package/src/commands/config-provider/config-provider.args.js +8 -0
  11. package/src/commands/config-provider/config-provider.command.js +10 -0
  12. package/src/commands/config-provider/config-provider.docs.md +109 -0
  13. package/src/commands/config-provider/config-provider.get.command.js +36 -0
  14. package/src/commands/config-provider/config-provider.import.command.js +40 -0
  15. package/src/commands/config-provider/config-provider.list.command.js +45 -0
  16. package/src/commands/config-provider/config-provider.open.command.js +17 -0
  17. package/src/commands/config-provider/config-provider.rm.command.js +25 -0
  18. package/src/commands/config-provider/config-provider.set.command.js +37 -0
  19. package/src/commands/create/create.command.js +7 -6
  20. package/src/commands/curl/curl.command.js +18 -19
  21. package/src/commands/deploy/deploy.command.js +14 -8
  22. package/src/commands/deploy/deploy.docs.md +20 -0
  23. package/src/commands/diag/diag.command.js +52 -39
  24. package/src/commands/features/features.disable.command.js +1 -2
  25. package/src/commands/features/features.enable.command.js +1 -2
  26. package/src/commands/features/features.info.command.js +1 -1
  27. package/src/commands/features/features.list.command.js +1 -2
  28. package/src/commands/global.commands.js +22 -0
  29. package/src/commands/global.options.js +2 -2
  30. package/src/commands/login/login.command.js +119 -20
  31. package/src/commands/login/login.docs.md +9 -2
  32. package/src/commands/logout/logout.command.js +39 -7
  33. package/src/commands/logout/logout.docs.md +7 -1
  34. package/src/commands/logs/logs.command.js +7 -8
  35. package/src/commands/logs/logs.docs.md +1 -1
  36. package/src/commands/ng/ng.get-config.command.js +3 -3
  37. package/src/commands/notify-email/notify-email.add.command.js +5 -2
  38. package/src/commands/notify-email/notify-email.command.js +1 -2
  39. package/src/commands/notify-email/notify-email.docs.md +1 -1
  40. package/src/commands/profile/profile.command.js +13 -37
  41. package/src/commands/profile/profile.docs.md +28 -0
  42. package/src/commands/profile/profile.list.command.js +44 -0
  43. package/src/commands/profile/profile.switch.command.js +80 -0
  44. package/src/commands/restart/restart.command.js +3 -2
  45. package/src/commands/ssh/ssh.command.js +2 -2
  46. package/src/commands/tokens/tokens.command.js +3 -3
  47. package/src/commands/tokens/tokens.create.command.js +4 -4
  48. package/src/commands/webhooks/webhooks.add.command.js +5 -2
  49. package/src/commands/webhooks/webhooks.command.js +1 -2
  50. package/src/commands/webhooks/webhooks.docs.md +1 -1
  51. package/src/config/cache.js +60 -0
  52. package/src/config/config.js +233 -0
  53. package/src/config/features.js +167 -0
  54. package/src/config/paths.js +23 -0
  55. package/src/lib/date-utils.js +48 -0
  56. package/src/lib/fs.js +49 -0
  57. package/src/lib/operator-commands.js +4 -4
  58. package/src/lib/profile.js +116 -0
  59. package/src/logger.js +123 -69
  60. package/src/logger.types.d.ts +5 -0
  61. package/src/models/addon.js +1 -3
  62. package/src/models/app_configuration.js +42 -39
  63. package/src/models/application_configuration.js +30 -30
  64. package/src/models/config-provider.js +34 -0
  65. package/src/models/git-isomorphic.js +153 -0
  66. package/src/models/git-system.js +182 -0
  67. package/src/models/git.js +150 -128
  68. package/src/models/ids-resolver.js +1 -1
  69. package/src/models/log.js +141 -90
  70. package/src/models/notification.js +7 -6
  71. package/src/models/send-to-api.js +58 -33
  72. package/src/models/user.js +0 -11
  73. package/src/models/utils.js +2 -2
  74. package/src/experimental-features.js +0 -91
  75. package/src/lib/format-date.js +0 -3
  76. package/src/models/configuration.js +0 -140
  77. package/src/models/log-v4.js +0 -189
@@ -1,140 +0,0 @@
1
- import { promises as fs } from 'node:fs';
2
- import path from 'node:path';
3
- import xdg from 'xdg';
4
- import { Logger } from '../logger.js';
5
-
6
- const CONFIG_FILES = {
7
- MAIN: 'clever-tools.json',
8
- IDS_CACHE: 'ids-cache.json',
9
- EXPERIMENTAL_FEATURES_FILE: 'clever-tools-experimental-features.json',
10
- };
11
-
12
- function getConfigDir() {
13
- return process.platform === 'win32'
14
- ? path.resolve(process.env.APPDATA, 'clever-cloud')
15
- : xdg.basedir.configPath('clever-cloud');
16
- }
17
-
18
- function getConfigPath(configFile) {
19
- return path.resolve(getConfigDir(), configFile);
20
- }
21
-
22
- // Every function which need 'clever-cloud' directory, need to call it before
23
- async function ensureConfigDirExists() {
24
- await fs.mkdir(getConfigDir(), { mode: 0o700, recursive: true });
25
- }
26
-
27
- export async function loadOAuthConf() {
28
- Logger.debug('Load configuration from environment variables');
29
- if (process.env.CLEVER_TOKEN != null && process.env.CLEVER_SECRET != null) {
30
- return {
31
- source: 'environment variables',
32
- token: process.env.CLEVER_TOKEN,
33
- secret: process.env.CLEVER_SECRET,
34
- };
35
- }
36
- Logger.debug('Load configuration from ' + conf.CONFIGURATION_FILE);
37
- try {
38
- const rawFile = await fs.readFile(conf.CONFIGURATION_FILE);
39
- const { token, secret } = JSON.parse(rawFile);
40
- return {
41
- source: 'configuration file',
42
- token,
43
- secret,
44
- };
45
- } catch (error) {
46
- Logger.info(`Cannot load configuration from ${conf.CONFIGURATION_FILE}\n${error.message}`);
47
- return {
48
- source: 'none',
49
- };
50
- }
51
- }
52
-
53
- export async function writeOAuthConf(oauthData) {
54
- Logger.debug('Write the tokens in the configuration file…');
55
- try {
56
- await ensureConfigDirExists();
57
- await fs.writeFile(conf.CONFIGURATION_FILE, JSON.stringify(oauthData));
58
- } catch (error) {
59
- throw new Error(`Cannot write configuration to ${conf.CONFIGURATION_FILE}\n${error.message}`);
60
- }
61
- }
62
-
63
- export async function loadIdsCache() {
64
- const cachePath = getConfigPath(CONFIG_FILES.IDS_CACHE);
65
- try {
66
- const rawFile = await fs.readFile(cachePath);
67
- return JSON.parse(rawFile);
68
- } catch (error) {
69
- Logger.info(`Cannot load IDs cache from ${cachePath}\n${error.message}`);
70
- return {
71
- owners: {},
72
- addons: {},
73
- };
74
- }
75
- }
76
-
77
- export async function writeIdsCache(ids) {
78
- const cachePath = getConfigPath(CONFIG_FILES.IDS_CACHE);
79
- const idsJson = JSON.stringify(ids);
80
- try {
81
- await ensureConfigDirExists();
82
- await fs.writeFile(cachePath, idsJson);
83
- } catch (error) {
84
- throw new Error(`Cannot write IDs cache to ${cachePath}\n${error.message}`);
85
- }
86
- }
87
-
88
- export async function getFeatures() {
89
- Logger.debug('Get features configuration from ' + conf.EXPERIMENTAL_FEATURES_FILE);
90
- try {
91
- const rawFile = await fs.readFile(conf.EXPERIMENTAL_FEATURES_FILE);
92
- return JSON.parse(rawFile);
93
- } catch (error) {
94
- if (error.code !== 'ENOENT') {
95
- throw new Error(`Cannot get experimental features configuration from ${conf.EXPERIMENTAL_FEATURES_FILE}`);
96
- }
97
- return {};
98
- }
99
- }
100
-
101
- export async function setFeature(feature, value) {
102
- const currentFeatures = await getFeatures();
103
- const newFeatures = { ...currentFeatures, ...{ [feature]: value } };
104
-
105
- try {
106
- await ensureConfigDirExists();
107
- await fs.writeFile(conf.EXPERIMENTAL_FEATURES_FILE, JSON.stringify(newFeatures, null, 2));
108
- } catch {
109
- throw new Error(`Cannot write experimental features configuration to ${conf.EXPERIMENTAL_FEATURES_FILE}`);
110
- }
111
- }
112
-
113
- const defaultConf = {
114
- API_HOST: 'https://api.clever-cloud.com',
115
- AUTH_BRIDGE_HOST: 'https://api-bridge.clever-cloud.com',
116
- SSH_GATEWAY: 'ssh@sshgateway-clevercloud-customers.services.clever-cloud.com',
117
-
118
- // the disclosure of these tokens is not considered as a vulnerability. Do not report this to our security service.
119
- OAUTH_CONSUMER_KEY: 'T5nFjKeHH4AIlEveuGhB5S3xg8T19e',
120
- OAUTH_CONSUMER_SECRET: 'MgVMqTr6fWlf2M0tkC2MXOnhfqBWDT',
121
-
122
- APP_CONFIGURATION_FILE: path.resolve('.', '.clever.json'),
123
- CONFIGURATION_FILE: getConfigPath(CONFIG_FILES.MAIN),
124
- EXPERIMENTAL_FEATURES_FILE: getConfigPath(CONFIG_FILES.EXPERIMENTAL_FEATURES_FILE),
125
-
126
- API_DOC_URL: 'https://www.clever.cloud/developers/api',
127
- DOC_URL: 'https://www.clever.cloud/developers/doc',
128
- CONSOLE_URL: 'https://console.clever-cloud.com',
129
- CONSOLE_TOKEN_URL: 'https://console.clever-cloud.com/cli-oauth',
130
- GOTO_URL: 'https://console.clever-cloud.com/goto',
131
- };
132
-
133
- export const conf = Object.fromEntries(
134
- Object.entries(defaultConf).map(([name, value]) => {
135
- if (process.env[name] != null) {
136
- return [name, process.env[name]];
137
- }
138
- return [name, value];
139
- }),
140
- );
@@ -1,189 +0,0 @@
1
- import { ApplicationLogStream } from '@clevercloud/client/esm/streams/application-logs.js';
2
- import { styleText } from '../lib/style-text.js';
3
- import { Logger } from '../logger.js';
4
- import { conf } from './configuration.js';
5
- import { waitForDeploymentEnd, waitForDeploymentStart } from './deployments.js';
6
- import { getBest } from './domain.js';
7
- import * as ExitStrategy from './exit-strategy-option.js';
8
- import { JsonArray } from './json-array.js';
9
- import { getHostAndTokens, processError } from './send-to-api.js';
10
- import { Deferred } from './utils.js';
11
-
12
- const RESET_COLOR = '\x1B[0m';
13
-
14
- // 2000 logs per 100ms maximum
15
- const THROTTLE_ELEMENTS = 2000;
16
- const THROTTLE_PER_IN_MILLISECONDS = 100;
17
-
18
- const retryConfiguration = {
19
- enabled: true,
20
- initRetryTimeout: 3000,
21
- maxRetryCount: 10,
22
- };
23
-
24
- export async function displayLogs(params) {
25
- const deferred = params.deferred || new Deferred();
26
- const { apiHost, tokens } = await getHostAndTokens();
27
- const { ownerId, appId, filter, since, until, deploymentId, format } = params;
28
-
29
- if (format === 'json' && until == null) {
30
- throw new Error('"json" format is only applicable with a limiting parameter such as `--until`');
31
- }
32
-
33
- const logStream = new ApplicationLogStream({
34
- apiHost,
35
- tokens,
36
- ownerId,
37
- appId,
38
- connectionTimeout: 10_000,
39
- retryConfiguration,
40
- since,
41
- until,
42
- deploymentId,
43
- filter,
44
- throttleElements: THROTTLE_ELEMENTS,
45
- throttlePerInMilliseconds: THROTTLE_PER_IN_MILLISECONDS,
46
- });
47
-
48
- // Properly close the stream
49
- process.once('SIGINT', (signal) => logStream.close(signal));
50
- const jsonArray = new JsonArray();
51
-
52
- logStream
53
- .on('open', () => {
54
- Logger.debug(styleText('blue', `Logs stream (open) ${JSON.stringify({ appId, filter, deploymentId })}`));
55
- if (format === 'json') {
56
- jsonArray.open();
57
- }
58
- })
59
- .on('error', (event) => {
60
- Logger.debug(styleText('red', `Logs stream (error) ${event.error.message}`));
61
- })
62
- .onLog((log) => {
63
- switch (format) {
64
- case 'json':
65
- jsonArray.push(log);
66
- return;
67
- case 'json-stream':
68
- Logger.println(JSON.stringify(log));
69
- return;
70
- case 'human':
71
- default:
72
- if (log.message === RESET_COLOR) {
73
- return;
74
- }
75
- Logger.println(formatLogLine(log));
76
- }
77
- });
78
-
79
- // start() is blocking until end of stream
80
- logStream
81
- .start()
82
- .then(() => {
83
- if (format === 'json') {
84
- jsonArray.close();
85
- }
86
- return deferred.resolve();
87
- })
88
- .catch(processError)
89
- .catch((error) => deferred.reject(error));
90
-
91
- return logStream;
92
- }
93
-
94
- export async function watchDeploymentAndDisplayLogs(options) {
95
- const { ownerId, appId, deploymentId, commitId, knownDeployments, quiet, redeployDate, exitStrategy } = options;
96
-
97
- ExitStrategy.plotQuietWarning(exitStrategy, quiet);
98
- // If in quiet mode, we only log start/finished deployment messages
99
- if (!quiet) {
100
- Logger.println(` ${styleText('blue', '→ Waiting for deployment to start…')}`);
101
- }
102
- const deployment = await waitForDeploymentStart({ ownerId, appId, deploymentId, commitId, knownDeployments });
103
- Logger.println(` ${styleText('green', `✓ Deployment started ${styleText('grey', `(${deployment.uuid})`)}`)}`);
104
-
105
- if (exitStrategy === 'deploy-start') {
106
- return;
107
- }
108
-
109
- const deferred = new Deferred();
110
- let logsStream;
111
-
112
- if (!quiet) {
113
- // About the deferred…
114
- // If displayLogs() throws an error,
115
- // the async function we're in (watchDeploymentAndDisplayLogs) will stop here and the error will be passed to the parent.
116
- // displayLogs() defines callback listeners so if it catches error in those callbacks,
117
- // it has no proper way to bubble up the error here.
118
- // Using the deferred enables this.
119
- logsStream = await displayLogs({ ownerId, appId, deploymentId: deployment.uuid, since: redeployDate, deferred });
120
- }
121
-
122
- if (!quiet) {
123
- Logger.println(` ${styleText('blue', '→ Waiting for application logs…')}`);
124
- }
125
-
126
- // Wait for deployment end (or an error thrown by logs with the deferred)
127
- const deploymentEnded = await Promise.race([
128
- waitForDeploymentEnd({ ownerId, appId, deploymentId: deployment.uuid }),
129
- deferred.promise,
130
- ]);
131
-
132
- if (!quiet && exitStrategy !== 'never') {
133
- logsStream.close(quiet ? 'quiet' : 'follow');
134
- }
135
-
136
- // deploymentEnded can be undefined if deferred resolved (e.g., stream closed via SIGINT)
137
- if (deploymentEnded == null) {
138
- return;
139
- }
140
-
141
- if (deploymentEnded.state === 'OK') {
142
- Logger.println('');
143
-
144
- // There can be applications without any domain, so we don't fail if we can't get one
145
- const favouriteDomain = await getBest(appId, ownerId).catch(() => null);
146
-
147
- if (favouriteDomain) {
148
- Logger.println(
149
- `${styleText(['bold', 'green'], '✓ Access your application:')} ${styleText(['underline', 'bold'], `https://${favouriteDomain.fqdn}`)}`,
150
- );
151
- }
152
-
153
- Logger.println(
154
- `${styleText(['bold', 'blue'], '→ Manage your application:')} ${styleText(['underline', 'bold'], `${conf.GOTO_URL}/${appId}`)}`,
155
- );
156
- } else if (deploymentEnded.state === 'CANCELLED') {
157
- throw new Error('Deployment was cancelled. Please check the activity');
158
- } else {
159
- throw new Error('Deployment failed. Please check the logs');
160
- }
161
- }
162
-
163
- function formatLogLine(log) {
164
- const { date, message } = log;
165
- if (isDeploymentSuccessMessage(log)) {
166
- return `${date.toISOString()}: ${styleText(['bold', 'green'], message)}`;
167
- } else if (isDeploymentFailedMessage(log)) {
168
- return `${date.toISOString()}: ${styleText(['bold', 'red'], message)}`;
169
- } else if (isBuildSucessMessage(log)) {
170
- return `${date.toISOString()}: ${styleText(['bold', 'blue'], message)}`;
171
- }
172
- return `${date.toISOString()}: ${message}${RESET_COLOR}`;
173
- }
174
-
175
- function isCleverMessage(log) {
176
- return log.service !== 'bas-deploy.service';
177
- }
178
-
179
- function isDeploymentSuccessMessage(log) {
180
- return isCleverMessage(log) && log.message.toLowerCase().startsWith('successfully deployed in');
181
- }
182
-
183
- function isDeploymentFailedMessage(log) {
184
- return isCleverMessage(log) && log.message.toLowerCase().startsWith('deploy failed in');
185
- }
186
-
187
- function isBuildSucessMessage(log) {
188
- return isCleverMessage(log) && log.message.toLowerCase().startsWith('build succeeded in');
189
- }