clever-tools 4.5.2 → 4.6.1
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.
- package/README.md +10 -0
- package/bin/clever.js +25 -22
- package/package.json +2 -2
- package/src/clever-client/auth-bridge.js +0 -25
- package/src/commands/README.md +1 -0
- package/src/commands/accesslogs/accesslogs.command.js +4 -1
- package/src/commands/accesslogs/accesslogs.docs.md +1 -1
- package/src/commands/addon/addon.create.command.js +23 -20
- package/src/commands/config/config.set.command.js +3 -3
- package/src/commands/config-provider/config-provider.args.js +8 -0
- package/src/commands/config-provider/config-provider.command.js +10 -0
- package/src/commands/config-provider/config-provider.docs.md +109 -0
- package/src/commands/config-provider/config-provider.get.command.js +36 -0
- package/src/commands/config-provider/config-provider.import.command.js +40 -0
- package/src/commands/config-provider/config-provider.list.command.js +45 -0
- package/src/commands/config-provider/config-provider.open.command.js +17 -0
- package/src/commands/config-provider/config-provider.rm.command.js +25 -0
- package/src/commands/config-provider/config-provider.set.command.js +37 -0
- package/src/commands/create/create.command.js +7 -6
- package/src/commands/curl/curl.command.js +18 -19
- package/src/commands/deploy/deploy.command.js +14 -8
- package/src/commands/deploy/deploy.docs.md +20 -0
- package/src/commands/diag/diag.command.js +52 -39
- package/src/commands/features/features.disable.command.js +1 -2
- package/src/commands/features/features.enable.command.js +1 -2
- package/src/commands/features/features.info.command.js +1 -1
- package/src/commands/features/features.list.command.js +1 -2
- package/src/commands/global.commands.js +22 -0
- package/src/commands/global.options.js +1 -1
- package/src/commands/login/login.command.js +119 -20
- package/src/commands/login/login.docs.md +9 -2
- package/src/commands/logout/logout.command.js +39 -7
- package/src/commands/logout/logout.docs.md +7 -1
- package/src/commands/logs/logs.command.js +7 -8
- package/src/commands/logs/logs.docs.md +1 -1
- package/src/commands/ng/ng.get-config.command.js +3 -3
- package/src/commands/profile/profile.command.js +13 -37
- package/src/commands/profile/profile.docs.md +28 -0
- package/src/commands/profile/profile.list.command.js +44 -0
- package/src/commands/profile/profile.switch.command.js +80 -0
- package/src/commands/restart/restart.command.js +3 -2
- package/src/commands/ssh/ssh.command.js +2 -2
- package/src/commands/tokens/tokens.command.js +3 -3
- package/src/commands/tokens/tokens.create.command.js +4 -4
- package/src/config/cache.js +60 -0
- package/src/config/config.js +233 -0
- package/src/config/features.js +167 -0
- package/src/config/paths.js +23 -0
- package/src/lib/date-utils.js +48 -0
- package/src/lib/fs.js +49 -0
- package/src/lib/operator-commands.js +4 -4
- package/src/lib/profile.js +116 -0
- package/src/logger.js +123 -69
- package/src/logger.types.d.ts +5 -0
- package/src/models/app_configuration.js +42 -39
- package/src/models/application_configuration.js +30 -30
- package/src/models/config-provider.js +34 -0
- package/src/models/git-isomorphic.js +153 -0
- package/src/models/git-system.js +182 -0
- package/src/models/git.js +150 -128
- package/src/models/ids-resolver.js +1 -1
- package/src/models/log.js +141 -90
- package/src/models/send-to-api.js +58 -33
- package/src/models/user.js +0 -11
- package/src/models/utils.js +2 -2
- package/src/experimental-features.js +0 -91
- package/src/lib/format-date.js +0 -3
- package/src/models/configuration.js +0 -140
- package/src/models/log-v4.js +0 -189
|
@@ -1,14 +1,17 @@
|
|
|
1
|
+
import { get as getUser } from '@clevercloud/client/esm/api/v2/organisation.js';
|
|
2
|
+
import dedent from 'dedent';
|
|
1
3
|
import crypto from 'node:crypto';
|
|
2
4
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
3
5
|
import open from 'open';
|
|
4
6
|
import { z } from 'zod';
|
|
5
7
|
import pkg from '../../../package.json' with { type: 'json' };
|
|
8
|
+
import { baseConfig, config, saveProfile } from '../../config/config.js';
|
|
6
9
|
import { defineCommand } from '../../lib/define-command.js';
|
|
7
10
|
import { defineOption } from '../../lib/define-option.js';
|
|
11
|
+
import { formatProfile } from '../../lib/profile.js';
|
|
8
12
|
import { styleText } from '../../lib/style-text.js';
|
|
9
13
|
import { Logger } from '../../logger.js';
|
|
10
|
-
import {
|
|
11
|
-
import * as User from '../../models/user.js';
|
|
14
|
+
import { sendToApiWithConfig } from '../../models/send-to-api.js';
|
|
12
15
|
|
|
13
16
|
function randomToken() {
|
|
14
17
|
return crypto.randomBytes(20).toString('base64').replace(/\//g, '-').replace(/\+/g, '_').replace(/=/g, '');
|
|
@@ -40,24 +43,33 @@ function pollOauthData(url, tryCount = 0) {
|
|
|
40
43
|
});
|
|
41
44
|
}
|
|
42
45
|
|
|
43
|
-
async function loginViaConsole() {
|
|
46
|
+
async function loginViaConsole(apiHost, consoleTokenUrl) {
|
|
44
47
|
const cliToken = randomToken();
|
|
45
48
|
|
|
46
|
-
const consoleUrl = new URL(
|
|
49
|
+
const consoleUrl = new URL(consoleTokenUrl);
|
|
47
50
|
consoleUrl.searchParams.set('cli_version', pkg.version);
|
|
48
51
|
consoleUrl.searchParams.set('cli_token', cliToken);
|
|
49
52
|
|
|
50
|
-
const cliPollUrl = new URL(
|
|
53
|
+
const cliPollUrl = new URL(apiHost);
|
|
51
54
|
cliPollUrl.pathname = '/v2/self/cli_tokens';
|
|
52
55
|
cliPollUrl.searchParams.set('cli_token', cliToken);
|
|
53
56
|
|
|
54
57
|
Logger.debug('Try to login to Clever Cloud…');
|
|
55
|
-
Logger.println(`Opening ${styleText('
|
|
58
|
+
Logger.println(`Opening ${styleText('blue', consoleUrl.toString())} in your browser to log you in…`);
|
|
56
59
|
await open(consoleUrl.toString(), { wait: false });
|
|
57
60
|
|
|
58
61
|
return pollOauthData(cliPollUrl.toString());
|
|
59
62
|
}
|
|
60
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Find an existing profile matching the target alias.
|
|
66
|
+
* @param {string} alias
|
|
67
|
+
* @returns {import('../../config/config.js').Profile | undefined}
|
|
68
|
+
*/
|
|
69
|
+
function getExistingTargetProfile(alias) {
|
|
70
|
+
return config.profiles.find((profile) => profile.alias === alias);
|
|
71
|
+
}
|
|
72
|
+
|
|
61
73
|
export const loginCommand = defineCommand({
|
|
62
74
|
description: 'Login to Clever Cloud',
|
|
63
75
|
since: '0.2.0',
|
|
@@ -65,34 +77,121 @@ export const loginCommand = defineCommand({
|
|
|
65
77
|
token: defineOption({
|
|
66
78
|
name: 'token',
|
|
67
79
|
schema: z.string().optional(),
|
|
68
|
-
description: '
|
|
80
|
+
description: 'Provide an existing token',
|
|
69
81
|
placeholder: 'token',
|
|
70
82
|
}),
|
|
71
83
|
secret: defineOption({
|
|
72
84
|
name: 'secret',
|
|
73
85
|
schema: z.string().optional(),
|
|
74
|
-
description: '
|
|
86
|
+
description: 'Provide an existing secret',
|
|
87
|
+
placeholder: 'secret',
|
|
88
|
+
}),
|
|
89
|
+
alias: defineOption({
|
|
90
|
+
name: 'alias',
|
|
91
|
+
aliases: ['a'],
|
|
92
|
+
schema: z
|
|
93
|
+
.string()
|
|
94
|
+
.min(1, { message: 'Profile alias cannot be empty' })
|
|
95
|
+
.regex(/^[a-zA-Z0-9_-]+$/, { message: 'Alias must only contain letters, numbers, hyphens and underscores' })
|
|
96
|
+
.refine((a) => a !== '$env', { message: '"$env" is reserved auth via environment variables' })
|
|
97
|
+
.default('default'),
|
|
98
|
+
description: 'Profile alias',
|
|
99
|
+
placeholder: 'alias',
|
|
100
|
+
}),
|
|
101
|
+
apiHost: defineOption({
|
|
102
|
+
name: 'api-host',
|
|
103
|
+
schema: z.string().url().optional(),
|
|
104
|
+
description: 'API host URL override',
|
|
105
|
+
placeholder: 'url',
|
|
106
|
+
}),
|
|
107
|
+
consoleUrl: defineOption({
|
|
108
|
+
name: 'console-url',
|
|
109
|
+
schema: z.string().url().optional(),
|
|
110
|
+
description: 'Console URL override',
|
|
111
|
+
placeholder: 'url',
|
|
112
|
+
}),
|
|
113
|
+
authBridgeHost: defineOption({
|
|
114
|
+
name: 'auth-bridge-host',
|
|
115
|
+
schema: z.string().url().optional(),
|
|
116
|
+
description: 'Auth bridge URL override',
|
|
117
|
+
placeholder: 'url',
|
|
118
|
+
}),
|
|
119
|
+
sshGateway: defineOption({
|
|
120
|
+
name: 'ssh-gateway',
|
|
121
|
+
schema: z.string().optional(),
|
|
122
|
+
description: 'SSH gateway override',
|
|
123
|
+
placeholder: 'address',
|
|
124
|
+
}),
|
|
125
|
+
consumerKey: defineOption({
|
|
126
|
+
name: 'oauth-consumer-key',
|
|
127
|
+
schema: z.string().optional(),
|
|
128
|
+
description: 'OAuth consumer key override',
|
|
129
|
+
placeholder: 'key',
|
|
130
|
+
}),
|
|
131
|
+
consumerSecret: defineOption({
|
|
132
|
+
name: 'oauth-consumer-secret',
|
|
133
|
+
schema: z.string().optional(),
|
|
134
|
+
description: 'OAuth consumer secret override',
|
|
75
135
|
placeholder: 'secret',
|
|
76
136
|
}),
|
|
77
137
|
},
|
|
78
|
-
args: [],
|
|
79
138
|
async handler(options) {
|
|
80
139
|
const { token, secret } = options;
|
|
81
|
-
const
|
|
82
|
-
const
|
|
140
|
+
const hasToken = token != null;
|
|
141
|
+
const hasSecret = secret != null;
|
|
142
|
+
const existingTargetProfile = getExistingTargetProfile(options.alias);
|
|
83
143
|
|
|
84
|
-
if (
|
|
85
|
-
|
|
144
|
+
if (hasToken !== hasSecret) {
|
|
145
|
+
throw new Error('Both `--token` and `--secret` must be defined');
|
|
86
146
|
}
|
|
87
147
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
148
|
+
const apiHost = options.apiHost ?? baseConfig.API_HOST;
|
|
149
|
+
const consoleUrl = options.consoleUrl ? `${options.consoleUrl}/cli-oauth` : baseConfig.CONSOLE_TOKEN_URL;
|
|
150
|
+
|
|
151
|
+
if (!hasToken && existingTargetProfile != null) {
|
|
152
|
+
// KO fallback wording (3 lines):
|
|
153
|
+
// Press Ctrl+C to cancel this login if you do not want to overwrite this profile.
|
|
154
|
+
// Then run clever login --alias <another-alias> to login with a different alias.
|
|
155
|
+
Logger.println(dedent`
|
|
156
|
+
You are already logged in with profile ${styleText('gray', formatProfile(existingTargetProfile))}; this login may overwrite it.
|
|
157
|
+
Press ${styleText('gray', 'Ctrl+C')} to cancel, then run ${styleText('gray', 'clever login --alias <another-alias>')}.
|
|
158
|
+
`);
|
|
159
|
+
Logger.println();
|
|
94
160
|
}
|
|
95
161
|
|
|
96
|
-
|
|
162
|
+
const oauthData = hasToken ? { token, secret } : await loginViaConsole(apiHost, consoleUrl);
|
|
163
|
+
|
|
164
|
+
const user = await getUser({}).then(
|
|
165
|
+
sendToApiWithConfig({
|
|
166
|
+
token: oauthData.token,
|
|
167
|
+
secret: oauthData.secret,
|
|
168
|
+
apiHost,
|
|
169
|
+
consumerKey: options.consumerKey ?? baseConfig.OAUTH_CONSUMER_KEY,
|
|
170
|
+
consumerSecret: options.consumerSecret ?? baseConfig.OAUTH_CONSUMER_SECRET,
|
|
171
|
+
}),
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
const overrideEntries = Object.entries({
|
|
175
|
+
API_HOST: options.apiHost,
|
|
176
|
+
CONSOLE_URL: options.consoleUrl,
|
|
177
|
+
AUTH_BRIDGE_HOST: options.authBridgeHost,
|
|
178
|
+
SSH_GATEWAY: options.sshGateway,
|
|
179
|
+
OAUTH_CONSUMER_KEY: options.consumerKey,
|
|
180
|
+
OAUTH_CONSUMER_SECRET: options.consumerSecret,
|
|
181
|
+
}).filter(([, v]) => v != null);
|
|
182
|
+
|
|
183
|
+
const profile = {
|
|
184
|
+
alias: options.alias,
|
|
185
|
+
token: oauthData.token,
|
|
186
|
+
secret: oauthData.secret,
|
|
187
|
+
expirationDate: oauthData.expirationDate,
|
|
188
|
+
userId: user.id,
|
|
189
|
+
email: user.email,
|
|
190
|
+
overrides: overrideEntries.length > 0 ? Object.fromEntries(overrideEntries) : undefined,
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
await saveProfile(profile);
|
|
194
|
+
|
|
195
|
+
Logger.printSuccess(`Login successful as ${styleText('green', formatProfile(profile))}`);
|
|
97
196
|
},
|
|
98
197
|
});
|
|
@@ -12,5 +12,12 @@ clever login [options]
|
|
|
12
12
|
|
|
13
13
|
|Name|Description|
|
|
14
14
|
|---|---|
|
|
15
|
-
|
|
16
|
-
|`--
|
|
15
|
+
|`-a`, `--alias` `<alias>`|Profile alias (default: default)|
|
|
16
|
+
|`--api-host` `<url>`|API host URL override|
|
|
17
|
+
|`--auth-bridge-host` `<url>`|Auth bridge URL override|
|
|
18
|
+
|`--console-url` `<url>`|Console URL override|
|
|
19
|
+
|`--oauth-consumer-key` `<key>`|OAuth consumer key override|
|
|
20
|
+
|`--oauth-consumer-secret` `<secret>`|OAuth consumer secret override|
|
|
21
|
+
|`--secret` `<secret>`|Provide an existing secret|
|
|
22
|
+
|`--ssh-gateway` `<address>`|SSH gateway override|
|
|
23
|
+
|`--token` `<token>`|Provide an existing token|
|
|
@@ -1,15 +1,47 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { config, removeProfile } from '../../config/config.js';
|
|
1
3
|
import { defineCommand } from '../../lib/define-command.js';
|
|
4
|
+
import { defineOption } from '../../lib/define-option.js';
|
|
5
|
+
import { formatProfile } from '../../lib/profile.js';
|
|
6
|
+
import { styleText } from '../../lib/style-text.js';
|
|
2
7
|
import { Logger } from '../../logger.js';
|
|
3
|
-
import { conf, writeOAuthConf } from '../../models/configuration.js';
|
|
4
8
|
|
|
5
9
|
export const logoutCommand = defineCommand({
|
|
6
10
|
description: 'Logout from Clever Cloud',
|
|
7
11
|
since: '1.0.0',
|
|
8
|
-
options: {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
options: {
|
|
13
|
+
alias: defineOption({
|
|
14
|
+
name: 'alias',
|
|
15
|
+
aliases: ['a'],
|
|
16
|
+
schema: z.string().optional(),
|
|
17
|
+
description: 'Alias of the profile to log out',
|
|
18
|
+
placeholder: 'alias',
|
|
19
|
+
}),
|
|
20
|
+
},
|
|
21
|
+
async handler(options) {
|
|
22
|
+
if (config.profiles.length === 0) {
|
|
23
|
+
throw new Error(`No profile found, use ${styleText('red', 'clever login')} command`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const [activeProfile] = config.profiles;
|
|
27
|
+
const aliasToRemove = options.alias ?? activeProfile.alias;
|
|
28
|
+
|
|
29
|
+
if (aliasToRemove === '$env') {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`Cannot logout from environment-based profile, unset ${styleText('red', 'CLEVER_TOKEN')} and ${styleText('red', 'CLEVER_SECRET')} instead`,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const profileToRemove = config.profiles.find((p) => p.alias === aliasToRemove);
|
|
36
|
+
if (profileToRemove == null) {
|
|
37
|
+
throw new Error(`Profile "${aliasToRemove}" not found.`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const newActiveProfile = await removeProfile(aliasToRemove);
|
|
41
|
+
Logger.printSuccess(`Logged out of profile ${styleText('green', formatProfile(profileToRemove))}`);
|
|
42
|
+
|
|
43
|
+
if (newActiveProfile != null && newActiveProfile.alias !== activeProfile.alias) {
|
|
44
|
+
Logger.printSuccess(`Switched active profile to ${styleText('green', formatProfile(newActiveProfile))}`);
|
|
45
|
+
}
|
|
14
46
|
},
|
|
15
47
|
});
|
|
@@ -4,9 +4,8 @@ import { defineOption } from '../../lib/define-option.js';
|
|
|
4
4
|
import { styleText } from '../../lib/style-text.js';
|
|
5
5
|
import { Logger } from '../../logger.js';
|
|
6
6
|
import * as Application from '../../models/application.js';
|
|
7
|
-
import {
|
|
8
|
-
import * as Log from '../../models/log
|
|
9
|
-
import * as LogV2 from '../../models/log.js';
|
|
7
|
+
import { resolveOwnerId, resolveRealId } from '../../models/ids-resolver.js';
|
|
8
|
+
import * as Log from '../../models/log.js';
|
|
10
9
|
import { Deferred } from '../../models/utils.js';
|
|
11
10
|
import {
|
|
12
11
|
addonIdOption,
|
|
@@ -57,15 +56,15 @@ export const logsCommand = defineCommand({
|
|
|
57
56
|
const filter = search !== '' ? search : null;
|
|
58
57
|
const isForHuman = format === 'human';
|
|
59
58
|
|
|
60
|
-
// TODO: drop when addons are migrated to the v4 API
|
|
61
59
|
if (addonIdOrRealId != null) {
|
|
62
|
-
const
|
|
60
|
+
const realId = await resolveRealId(addonIdOrRealId);
|
|
61
|
+
const ownerId = await resolveOwnerId(realId);
|
|
63
62
|
if (isForHuman) {
|
|
64
63
|
Logger.println(styleText('blue', 'Waiting for addon logs…'));
|
|
65
|
-
} else {
|
|
66
|
-
throw new Error(`"${format}" format is not yet available for add-on logs`);
|
|
67
64
|
}
|
|
68
|
-
|
|
65
|
+
const deferred = new Deferred();
|
|
66
|
+
await Log.displayLogs({ ownerId, addonId: realId, since, until, filter, format, deferred });
|
|
67
|
+
return deferred.promise;
|
|
69
68
|
}
|
|
70
69
|
|
|
71
70
|
const { ownerId, appId } = await Application.resolveId(appIdOrName, alias);
|
|
@@ -12,7 +12,7 @@ clever logs [options]
|
|
|
12
12
|
|
|
13
13
|
|Name|Description|
|
|
14
14
|
|---|---|
|
|
15
|
-
|`--addon` `<addon-id>`|Add-on ID|
|
|
15
|
+
|`--addon` `<addon-id>`|Add-on ID or real ID|
|
|
16
16
|
|`--after`, `--since` `<after>`|Fetch logs after this date/time (ISO8601 date, positive number in seconds or duration, e.g.: 1h)|
|
|
17
17
|
|`-a`, `--alias` `<alias>`|Short name for the application|
|
|
18
18
|
|`--app` `<app-id\|app-name>`|Application to manage by its ID (or name, if unambiguous)|
|
|
@@ -15,16 +15,16 @@ export const ngGetConfigCommand = defineCommand({
|
|
|
15
15
|
async handler(options, peerIdOrLabel, ngIdOrLabel) {
|
|
16
16
|
const { org, format } = options;
|
|
17
17
|
|
|
18
|
-
const
|
|
18
|
+
const peerConfig = await networkGroup.getPeerConfig(peerIdOrLabel, ngIdOrLabel, org);
|
|
19
19
|
|
|
20
20
|
switch (format) {
|
|
21
21
|
case 'json': {
|
|
22
|
-
Logger.printJson(
|
|
22
|
+
Logger.printJson(peerConfig);
|
|
23
23
|
break;
|
|
24
24
|
}
|
|
25
25
|
case 'human':
|
|
26
26
|
default: {
|
|
27
|
-
const decodedConfiguration = Buffer.from(
|
|
27
|
+
const decodedConfiguration = Buffer.from(peerConfig.configuration, 'base64').toString('utf8');
|
|
28
28
|
Logger.println(decodedConfiguration);
|
|
29
29
|
}
|
|
30
30
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { config } from '../../config/config.js';
|
|
2
2
|
import { defineCommand } from '../../lib/define-command.js';
|
|
3
|
+
import { formatProfileDetails, getProfileDetails } from '../../lib/profile.js';
|
|
3
4
|
import { styleText } from '../../lib/style-text.js';
|
|
4
5
|
import { Logger } from '../../logger.js';
|
|
5
|
-
import * as User from '../../models/user.js';
|
|
6
6
|
import { humanJsonOutputFormatOption } from '../global.options.js';
|
|
7
7
|
|
|
8
8
|
export const profileCommand = defineCommand({
|
|
@@ -11,49 +11,25 @@ export const profileCommand = defineCommand({
|
|
|
11
11
|
options: {
|
|
12
12
|
format: humanJsonOutputFormatOption,
|
|
13
13
|
},
|
|
14
|
-
args: [],
|
|
15
14
|
async handler(options) {
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const tokenExpiration = new Date(currentToken.expirationDate).toLocaleString('en-US', {
|
|
21
|
-
year: 'numeric',
|
|
22
|
-
month: 'short',
|
|
23
|
-
day: 'numeric',
|
|
24
|
-
hour: 'numeric',
|
|
25
|
-
minute: 'numeric',
|
|
26
|
-
hour12: false,
|
|
27
|
-
timeZone: 'UTC',
|
|
28
|
-
timeZoneName: 'short',
|
|
29
|
-
});
|
|
15
|
+
const [activeProfile] = config.profiles;
|
|
16
|
+
if (activeProfile == null) {
|
|
17
|
+
throw new Error(`No profile found, use ${styleText('red', 'clever login')} command`);
|
|
18
|
+
}
|
|
30
19
|
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
avatar: user.avatar,
|
|
36
|
-
creationDate: new Date(user.creationDate),
|
|
37
|
-
tokenExpiration: new Date(currentToken.expirationDate).toISOString(),
|
|
38
|
-
lang: user.lang,
|
|
39
|
-
has2FA: user.preferredMFA != null && user.preferredMFA !== 'NONE',
|
|
40
|
-
};
|
|
20
|
+
const details = await getProfileDetails({ profile: activeProfile, isActive: true });
|
|
21
|
+
if (!details.isTokenValid) {
|
|
22
|
+
throw new Error(`Your token is invalid or has expired, use ${styleText('red', 'clever login')} command`);
|
|
23
|
+
}
|
|
41
24
|
|
|
42
|
-
switch (format) {
|
|
25
|
+
switch (options.format) {
|
|
43
26
|
case 'json': {
|
|
44
|
-
Logger.printJson(
|
|
27
|
+
Logger.printJson(details);
|
|
45
28
|
break;
|
|
46
29
|
}
|
|
47
30
|
case 'human':
|
|
48
31
|
default: {
|
|
49
|
-
Logger.println(
|
|
50
|
-
You're currently logged in as:
|
|
51
|
-
User id ${formattedUser.id}
|
|
52
|
-
Name ${formattedUser.name ?? styleText(['red', 'bold'], '[not specified]')}
|
|
53
|
-
Email ${formattedUser.email}
|
|
54
|
-
Token expiration ${tokenExpiration}
|
|
55
|
-
Two factor auth ${formattedUser.has2FA ? 'yes' : 'no'}
|
|
56
|
-
`);
|
|
32
|
+
Logger.println(formatProfileDetails(details));
|
|
57
33
|
}
|
|
58
34
|
}
|
|
59
35
|
},
|
|
@@ -14,6 +14,20 @@ clever profile [options]
|
|
|
14
14
|
|---|---|
|
|
15
15
|
|`-F`, `--format` `<format>`|Output format (human, json) (default: human)|
|
|
16
16
|
|
|
17
|
+
## ➡️ `clever profile list` <kbd>Since 4.6.0</kbd>
|
|
18
|
+
|
|
19
|
+
List all configured profiles
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
clever profile list [options]
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### ⚙️ Options
|
|
26
|
+
|
|
27
|
+
|Name|Description|
|
|
28
|
+
|---|---|
|
|
29
|
+
|`-F`, `--format` `<format>`|Output format (human, json) (default: human)|
|
|
30
|
+
|
|
17
31
|
## ➡️ `clever profile open` <kbd>Since 3.11.0</kbd>
|
|
18
32
|
|
|
19
33
|
Open your profile in the Console
|
|
@@ -21,3 +35,17 @@ Open your profile in the Console
|
|
|
21
35
|
```bash
|
|
22
36
|
clever profile open
|
|
23
37
|
```
|
|
38
|
+
|
|
39
|
+
## ➡️ `clever profile switch` <kbd>Since 4.6.0</kbd>
|
|
40
|
+
|
|
41
|
+
Switch to a different profile
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
clever profile switch [options]
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### ⚙️ Options
|
|
48
|
+
|
|
49
|
+
|Name|Description|
|
|
50
|
+
|---|---|
|
|
51
|
+
|`-a`, `--alias` `<alias>`|Alias of the profile to switch to|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { config } from '../../config/config.js';
|
|
2
|
+
import { defineCommand } from '../../lib/define-command.js';
|
|
3
|
+
import { formatProfileDetails, getProfileDetails } from '../../lib/profile.js';
|
|
4
|
+
import { Logger } from '../../logger.js';
|
|
5
|
+
import { humanJsonOutputFormatOption } from '../global.options.js';
|
|
6
|
+
|
|
7
|
+
export const profileListCommand = defineCommand({
|
|
8
|
+
description: 'List all configured profiles',
|
|
9
|
+
since: '4.6.0',
|
|
10
|
+
options: {
|
|
11
|
+
format: humanJsonOutputFormatOption,
|
|
12
|
+
},
|
|
13
|
+
async handler(options) {
|
|
14
|
+
if (config.profiles.length === 0) {
|
|
15
|
+
throw new Error('No profile found. You are not logged in.');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const profilesDetails = await Promise.all(
|
|
19
|
+
config.profiles.map(async (profile, index) => {
|
|
20
|
+
return getProfileDetails({
|
|
21
|
+
profile,
|
|
22
|
+
isActive: index === 0,
|
|
23
|
+
});
|
|
24
|
+
}),
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
switch (options.format) {
|
|
28
|
+
case 'json': {
|
|
29
|
+
Logger.printJson(profilesDetails);
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
case 'human':
|
|
33
|
+
default: {
|
|
34
|
+
Logger.println(
|
|
35
|
+
profilesDetails
|
|
36
|
+
.map((details) => {
|
|
37
|
+
return formatProfileDetails(details);
|
|
38
|
+
})
|
|
39
|
+
.join('\n\n'),
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { config, saveProfile } from '../../config/config.js';
|
|
3
|
+
import { defineCommand } from '../../lib/define-command.js';
|
|
4
|
+
import { defineOption } from '../../lib/define-option.js';
|
|
5
|
+
import { formatProfile } from '../../lib/profile.js';
|
|
6
|
+
import { selectAnswer } from '../../lib/prompts.js';
|
|
7
|
+
import { styleText } from '../../lib/style-text.js';
|
|
8
|
+
import { Logger } from '../../logger.js';
|
|
9
|
+
|
|
10
|
+
/** @typedef {import('../../config/config.js').Profile} Profile */
|
|
11
|
+
|
|
12
|
+
export const profileSwitchCommand = defineCommand({
|
|
13
|
+
description: 'Switch to a different profile',
|
|
14
|
+
since: '4.6.0',
|
|
15
|
+
options: {
|
|
16
|
+
alias: defineOption({
|
|
17
|
+
name: 'alias',
|
|
18
|
+
aliases: ['a'],
|
|
19
|
+
schema: z.string().optional(),
|
|
20
|
+
description: 'Alias of the profile to switch to',
|
|
21
|
+
placeholder: 'alias',
|
|
22
|
+
}),
|
|
23
|
+
},
|
|
24
|
+
async handler(options) {
|
|
25
|
+
if (config.profiles.length === 0) {
|
|
26
|
+
throw new Error('No profile found.');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (config.profiles.length === 1) {
|
|
30
|
+
throw new Error('Only one profile. Use clever login --alias <name> to add another.');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const [activeProfile] = config.profiles;
|
|
34
|
+
if (activeProfile.alias === '$env') {
|
|
35
|
+
throw new Error(
|
|
36
|
+
`Cannot switch profiles while using environment-based authentication, unset ${styleText('red', 'CLEVER_TOKEN')} and ${styleText('red', 'CLEVER_SECRET')} instead`,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const targetProfile = await resolveTargetProfile(config.profiles, options.alias);
|
|
41
|
+
if (targetProfile.alias === activeProfile.alias) {
|
|
42
|
+
Logger.printInfo(`Already on profile ${styleText('blue', formatProfile(activeProfile))}`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
await saveProfile(targetProfile);
|
|
47
|
+
Logger.printSuccess(`Switched to profile ${styleText('green', formatProfile(targetProfile))}`);
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Resolve the target profile to switch to.
|
|
53
|
+
* @param {Profile[]} profiles
|
|
54
|
+
* @param {string | undefined} requestedAlias
|
|
55
|
+
* @returns {Promise<Profile>}
|
|
56
|
+
*/
|
|
57
|
+
async function resolveTargetProfile(profiles, requestedAlias) {
|
|
58
|
+
if (requestedAlias != null) {
|
|
59
|
+
const targetProfile = profiles.find((p) => p.alias === requestedAlias);
|
|
60
|
+
if (targetProfile == null) {
|
|
61
|
+
const availableAliases = profiles.map((p) => p.alias).join(', ');
|
|
62
|
+
throw new Error(
|
|
63
|
+
`Profile "${styleText('red', requestedAlias)}" not found. Available profiles: ${availableAliases}`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
return targetProfile;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (profiles.length === 2) {
|
|
70
|
+
Logger.printInfo(`Switching to ${styleText('blue', formatProfile(profiles[1]))}`);
|
|
71
|
+
return profiles[1];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const choices = profiles.map((profile) => ({
|
|
75
|
+
name: formatProfile(profile),
|
|
76
|
+
value: profile.alias,
|
|
77
|
+
}));
|
|
78
|
+
const selectedAlias = await selectAnswer('Select a profile:', choices);
|
|
79
|
+
return profiles.find((profile) => profile.alias === selectedAlias);
|
|
80
|
+
}
|
|
@@ -5,8 +5,8 @@ import { styleText } from '../../lib/style-text.js';
|
|
|
5
5
|
import { Logger } from '../../logger.js';
|
|
6
6
|
import * as Application from '../../models/application.js';
|
|
7
7
|
import * as ExitStrategy from '../../models/exit-strategy-option.js';
|
|
8
|
-
import
|
|
9
|
-
import * as Log from '../../models/log
|
|
8
|
+
import { Git } from '../../models/git.js';
|
|
9
|
+
import * as Log from '../../models/log.js';
|
|
10
10
|
import {
|
|
11
11
|
aliasOption,
|
|
12
12
|
appIdOrNameOption,
|
|
@@ -43,6 +43,7 @@ export const restartCommand = defineCommand({
|
|
|
43
43
|
const exitStrategy = ExitStrategy.get(follow, exitOnDeploy);
|
|
44
44
|
|
|
45
45
|
const { ownerId, appId } = await Application.resolveId(appIdOrName, alias);
|
|
46
|
+
const git = await Git.get();
|
|
46
47
|
const fullCommitId = await git.resolveFullCommitId(commit);
|
|
47
48
|
const app = await Application.get(ownerId, appId);
|
|
48
49
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
+
import { config } from '../../config/config.js';
|
|
3
4
|
import { defineCommand } from '../../lib/define-command.js';
|
|
4
5
|
import { defineOption } from '../../lib/define-option.js';
|
|
5
6
|
import * as Application from '../../models/application.js';
|
|
6
|
-
import { conf } from '../../models/configuration.js';
|
|
7
7
|
import { aliasOption, appIdOrNameOption } from '../global.options.js';
|
|
8
8
|
|
|
9
9
|
export const sshCommand = defineCommand({
|
|
@@ -25,7 +25,7 @@ export const sshCommand = defineCommand({
|
|
|
25
25
|
const { alias, app: appIdOrName, identityFile } = options;
|
|
26
26
|
|
|
27
27
|
const { appId } = await Application.resolveId(appIdOrName, alias);
|
|
28
|
-
const sshParams = ['-t',
|
|
28
|
+
const sshParams = ['-t', config.SSH_GATEWAY, appId];
|
|
29
29
|
if (identityFile != null) {
|
|
30
30
|
sshParams.push('-i', identityFile);
|
|
31
31
|
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { listApiTokens } from '../../clever-client/auth-bridge.js';
|
|
2
|
+
import { config } from '../../config/config.js';
|
|
3
|
+
import { formatDate } from '../../lib/date-utils.js';
|
|
2
4
|
import { defineCommand } from '../../lib/define-command.js';
|
|
3
|
-
import { formatDate } from '../../lib/format-date.js';
|
|
4
5
|
import { styleText } from '../../lib/style-text.js';
|
|
5
6
|
import { Logger } from '../../logger.js';
|
|
6
|
-
import { conf } from '../../models/configuration.js';
|
|
7
7
|
import { sendToAuthBridge } from '../../models/send-to-api.js';
|
|
8
8
|
import { humanJsonOutputFormatOption } from '../global.options.js';
|
|
9
9
|
|
|
10
10
|
export const tokensCommand = defineCommand({
|
|
11
|
-
description: `Manage API tokens to query Clever Cloud API from ${
|
|
11
|
+
description: `Manage API tokens to query Clever Cloud API from ${config.AUTH_BRIDGE_HOST}`,
|
|
12
12
|
since: '3.12.0',
|
|
13
13
|
options: {
|
|
14
14
|
format: humanJsonOutputFormatOption,
|