clever-tools 4.5.2 → 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.
- 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
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { get as getUser } from '@clevercloud/client/esm/api/v2/organisation.js';
|
|
2
|
+
import { getCurrentTokenInfo } from '@clevercloud/client/esm/api/v2/self.js';
|
|
3
|
+
import { baseConfig } from '../config/config.js';
|
|
4
|
+
import { sendToApiWithConfig } from '../models/send-to-api.js';
|
|
5
|
+
import { formatDateLocalized, toDate } from './date-utils.js';
|
|
6
|
+
import { styleText } from './style-text.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {import('../config/config.js').Profile} Profile
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {object} ProfileDetails
|
|
14
|
+
* @property {string} alias
|
|
15
|
+
* @property {string} email
|
|
16
|
+
* @property {string | null | undefined} name
|
|
17
|
+
* @property {string} [id]
|
|
18
|
+
* @property {Date} [tokenExpiration]
|
|
19
|
+
* @property {boolean} has2FA
|
|
20
|
+
* @property {string} [avatar]
|
|
21
|
+
* @property {Date} [creationDate]
|
|
22
|
+
* @property {string} [lang]
|
|
23
|
+
* @property {boolean} isProfileActive
|
|
24
|
+
* @property {boolean} isTokenValid
|
|
25
|
+
* @property {Record<string, string>} [overrides]
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Formats a profile for display.
|
|
30
|
+
* @param {Profile | ProfileDetails} profile
|
|
31
|
+
* @returns {string}
|
|
32
|
+
*/
|
|
33
|
+
export function formatProfile(profile) {
|
|
34
|
+
return [
|
|
35
|
+
profile.alias,
|
|
36
|
+
profile.email != null ? `(${profile.email})` : null,
|
|
37
|
+
profile.isProfileActive ? styleText('green', '[active]') : null,
|
|
38
|
+
]
|
|
39
|
+
.filter(Boolean)
|
|
40
|
+
.join(' ');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Fetch full profile details using the profile's credentials.
|
|
45
|
+
* @param {object} params
|
|
46
|
+
* @param {Profile} params.profile
|
|
47
|
+
* @param {boolean} params.isActive
|
|
48
|
+
* @returns {Promise<ProfileDetails>}
|
|
49
|
+
*/
|
|
50
|
+
export async function getProfileDetails({ profile, isActive }) {
|
|
51
|
+
const sendWithCredentials = sendToApiWithConfig({
|
|
52
|
+
token: profile.token,
|
|
53
|
+
secret: profile.secret,
|
|
54
|
+
apiHost: profile.overrides?.API_HOST ?? baseConfig.API_HOST,
|
|
55
|
+
consumerKey: profile.overrides?.OAUTH_CONSUMER_KEY ?? baseConfig.OAUTH_CONSUMER_KEY,
|
|
56
|
+
consumerSecret: profile.overrides?.OAUTH_CONSUMER_SECRET ?? baseConfig.OAUTH_CONSUMER_SECRET,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const [user, token] = await Promise.all([
|
|
60
|
+
getUser({}).then(sendWithCredentials),
|
|
61
|
+
getCurrentTokenInfo().then(sendWithCredentials),
|
|
62
|
+
]).catch(() => [null, null]);
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
id: user?.id ?? profile.userId,
|
|
66
|
+
email: user?.email ?? profile.email,
|
|
67
|
+
name: user?.name,
|
|
68
|
+
avatar: user?.avatar,
|
|
69
|
+
creationDate: toDate(user?.creationDate),
|
|
70
|
+
tokenExpiration: toDate(token?.expirationDate ?? profile.expirationDate),
|
|
71
|
+
lang: user?.lang,
|
|
72
|
+
has2FA: user != null ? user.preferredMFA != null && user.preferredMFA !== 'NONE' : undefined,
|
|
73
|
+
alias: profile.alias,
|
|
74
|
+
isProfileActive: isActive,
|
|
75
|
+
isTokenValid: user != null,
|
|
76
|
+
overrides: profile.overrides,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Formats profile details for CLI display.
|
|
82
|
+
* @param {ProfileDetails} profile
|
|
83
|
+
*/
|
|
84
|
+
export function formatProfileDetails(profile) {
|
|
85
|
+
const lines = [];
|
|
86
|
+
|
|
87
|
+
lines.push(styleText('bold', formatProfile(profile)));
|
|
88
|
+
lines.push(profile.id);
|
|
89
|
+
|
|
90
|
+
if (!profile.isTokenValid) {
|
|
91
|
+
lines.push(styleText('red', 'Invalid or expired token'));
|
|
92
|
+
return lines.map((line, index) => (index === 0 ? line : ` ${line}`)).join('\n');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
lines.push(profile.name ? profile.name : '[unknown]');
|
|
96
|
+
|
|
97
|
+
const expiresAtFormatted = formatDateLocalized(profile.tokenExpiration);
|
|
98
|
+
if (expiresAtFormatted) {
|
|
99
|
+
lines.push(`Expires on ${styleText('gray', expiresAtFormatted)}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
lines.push(`2FA ${profile.has2FA ? styleText('green', 'enabled ✓') : styleText('red', 'disabled ✗')}`);
|
|
103
|
+
|
|
104
|
+
if (profile.overrides != null) {
|
|
105
|
+
const overrideEntries = Object.entries(profile.overrides).filter(([, v]) => v != null);
|
|
106
|
+
for (const [key, value] of overrideEntries) {
|
|
107
|
+
lines.push(`${key}: ${styleText('gray', value)}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return lines
|
|
112
|
+
.map((line, index) => {
|
|
113
|
+
return index === 0 ? line : ` ${line}`;
|
|
114
|
+
})
|
|
115
|
+
.join('\n');
|
|
116
|
+
}
|
package/src/logger.js
CHANGED
|
@@ -1,85 +1,139 @@
|
|
|
1
|
-
import _ from 'lodash';
|
|
2
1
|
import { format } from 'node:util';
|
|
3
2
|
import { styleText } from './lib/style-text.js';
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
if (severity === 'error') {
|
|
9
|
-
return { prefix: styleText(['bold', 'red'], prefix), prefixLength };
|
|
10
|
-
}
|
|
11
|
-
return { prefix, prefixLength };
|
|
12
|
-
}
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {import('./logger.types.js').ApiError} ApiError
|
|
6
|
+
*/
|
|
13
7
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return error;
|
|
17
|
-
}
|
|
18
|
-
const fields = _.map(error.fields, (msg, field) => `${field}: ${msg}`);
|
|
19
|
-
return [`${error.message} [${error.id}]`, ...fields].join('\n');
|
|
20
|
-
}
|
|
8
|
+
const IS_QUIET = Boolean(process.env.CLEVER_QUIET);
|
|
9
|
+
const IS_VERBOSE = Boolean(process.env.CLEVER_VERBOSE);
|
|
21
10
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
11
|
+
export const Logger = {
|
|
12
|
+
/**
|
|
13
|
+
* @param {string} message
|
|
14
|
+
*/
|
|
15
|
+
debug(message) {
|
|
16
|
+
consoleLog('debug', message);
|
|
17
|
+
},
|
|
29
18
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
19
|
+
/**
|
|
20
|
+
* @param {string} message
|
|
21
|
+
*/
|
|
22
|
+
info(message) {
|
|
23
|
+
consoleLog('info', message);
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {string} message
|
|
28
|
+
*/
|
|
29
|
+
warn(message) {
|
|
30
|
+
consoleLog('warn', message);
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {Error|string} error
|
|
35
|
+
*/
|
|
36
|
+
error(error) {
|
|
37
|
+
if (IS_QUIET) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const prefix = '[ERROR] ';
|
|
42
|
+
const styledPrefix = styleText(['bold', 'red'], prefix);
|
|
43
|
+
const message = error instanceof Error ? error.message : error;
|
|
44
|
+
const formatted = formatLines(prefix.length, processApiError(message));
|
|
33
45
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
46
|
+
if (IS_VERBOSE) {
|
|
47
|
+
writeStderr('[STACKTRACE]');
|
|
48
|
+
writeStderr(error);
|
|
49
|
+
writeStderr('[/STACKTRACE]');
|
|
38
50
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
51
|
+
writeStderr(`${styledPrefix}${formatted}`);
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
println: console.log,
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {string} text
|
|
58
|
+
* @param {number} indentLevel
|
|
59
|
+
*/
|
|
60
|
+
printlnWithIndent(text, indentLevel) {
|
|
61
|
+
console.log(' '.repeat(indentLevel) + text);
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
/** @param {string} message */
|
|
65
|
+
printSuccess(message) {
|
|
66
|
+
console.log(`${styleText(['bold', 'green'], '✓')} ${message}`);
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
/** @param {string} message */
|
|
70
|
+
printInfo(message) {
|
|
71
|
+
console.log(`${styleText('blue', 'i')} ${message}`);
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
/** @param {unknown} obj */
|
|
75
|
+
printJson(obj) {
|
|
76
|
+
console.log(JSON.stringify(obj, null, 2));
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
printErrorLine: writeStderr,
|
|
80
|
+
};
|
|
60
81
|
|
|
61
82
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* @param {string}
|
|
65
|
-
* @
|
|
83
|
+
* Logs a message to the console with severity prefix.
|
|
84
|
+
* @param {'debug'|'info'|'warn'} severity
|
|
85
|
+
* @param {string} message
|
|
86
|
+
* @returns {void}
|
|
66
87
|
*/
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
88
|
+
function consoleLog(severity, message) {
|
|
89
|
+
if (IS_QUIET) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (!IS_VERBOSE && severity !== 'warn') {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const prefix = `[${severity.toUpperCase()}] `;
|
|
96
|
+
console.log(`${prefix}${formatLines(prefix.length, message)}`);
|
|
97
|
+
}
|
|
70
98
|
|
|
71
|
-
|
|
72
|
-
|
|
99
|
+
/**
|
|
100
|
+
* Writes a formatted line to stderr.
|
|
101
|
+
* @param {Error|string} value
|
|
102
|
+
* @returns {void}
|
|
103
|
+
*/
|
|
104
|
+
function writeStderr(value) {
|
|
105
|
+
process.stderr.write(format(value) + '\n');
|
|
106
|
+
}
|
|
73
107
|
|
|
74
|
-
|
|
75
|
-
|
|
108
|
+
/**
|
|
109
|
+
* Formats a multiline message with indentation for continuation lines.
|
|
110
|
+
* @param {number} prefixLength
|
|
111
|
+
* @param {string} message
|
|
112
|
+
* @returns {string}
|
|
113
|
+
*/
|
|
114
|
+
function formatLines(prefixLength, message) {
|
|
115
|
+
const indent = ' '.repeat(prefixLength);
|
|
116
|
+
return message
|
|
117
|
+
.split('\n')
|
|
118
|
+
.map((line, i) => (i === 0 ? line : indent + line))
|
|
119
|
+
.join('\n');
|
|
120
|
+
}
|
|
76
121
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
122
|
+
/**
|
|
123
|
+
* Transforms an API error object into a formatted message string.
|
|
124
|
+
* @param {ApiError|string} error
|
|
125
|
+
* @returns {string}
|
|
126
|
+
*/
|
|
127
|
+
function processApiError(error) {
|
|
128
|
+
if (typeof error === 'string') {
|
|
129
|
+
return error;
|
|
130
|
+
}
|
|
81
131
|
|
|
82
|
-
|
|
132
|
+
const { id, message, fields } = error;
|
|
133
|
+
if (id == null || message == null) {
|
|
134
|
+
return String(error);
|
|
135
|
+
}
|
|
83
136
|
|
|
84
|
-
|
|
85
|
-
|
|
137
|
+
const fieldLines = Object.entries(fields ?? {}).map(([name, msg]) => `${name}: ${msg}`);
|
|
138
|
+
return [`${message} [${id}]`, ...fieldLines].join('\n');
|
|
139
|
+
}
|
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
import _ from 'lodash';
|
|
2
|
-
import { promises as fs } from 'node:fs';
|
|
3
2
|
import path from 'node:path';
|
|
3
|
+
import { config } from '../config/config.js';
|
|
4
|
+
import { readJson, writeJson } from '../lib/fs.js';
|
|
4
5
|
import { slugify } from '../lib/slugify.js';
|
|
5
6
|
import { styleText } from '../lib/style-text.js';
|
|
6
7
|
import { Logger } from '../logger.js';
|
|
7
|
-
import { conf } from './configuration.js';
|
|
8
8
|
import * as User from './user.js';
|
|
9
9
|
|
|
10
10
|
// TODO: Maybe use fs-utils findPath()
|
|
11
11
|
export async function loadApplicationConf(ignoreParentConfig = false, pathToFolder) {
|
|
12
12
|
if (pathToFolder == null) {
|
|
13
|
-
pathToFolder = path.dirname(
|
|
13
|
+
pathToFolder = path.dirname(config.APP_CONFIGURATION_FILE);
|
|
14
14
|
}
|
|
15
|
-
const fileName = path.basename(
|
|
15
|
+
const fileName = path.basename(config.APP_CONFIGURATION_FILE);
|
|
16
16
|
const fullPath = path.join(pathToFolder, fileName);
|
|
17
17
|
Logger.debug('Loading app configuration from ' + fullPath);
|
|
18
18
|
try {
|
|
19
|
-
|
|
20
|
-
return JSON.parse(contents);
|
|
19
|
+
return await readJson(fullPath);
|
|
21
20
|
} catch (error) {
|
|
22
|
-
Logger.info('Cannot load app configuration from ' +
|
|
21
|
+
Logger.info('Cannot load app configuration from ' + config.APP_CONFIGURATION_FILE + ' (' + error + ')');
|
|
23
22
|
if (ignoreParentConfig || path.parse(pathToFolder).root === pathToFolder) {
|
|
24
23
|
return { apps: [] };
|
|
25
24
|
}
|
|
@@ -30,25 +29,34 @@ export async function loadApplicationConf(ignoreParentConfig = false, pathToFold
|
|
|
30
29
|
export async function addLinkedApplication(appData, alias, ignoreParentConfig) {
|
|
31
30
|
const currentConfig = await loadApplicationConf(ignoreParentConfig);
|
|
32
31
|
|
|
32
|
+
const generatedAlias = alias || slugify(appData.name);
|
|
33
|
+
|
|
34
|
+
const existingApp = currentConfig.apps.find((app) => app.app_id === appData.id);
|
|
35
|
+
if (existingApp != null) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`Application ${styleText('red', appData.id)} is already linked with alias ${styleText('red', existingApp.alias)}`,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const aliasConflict = currentConfig.apps.find((app) => app.alias === generatedAlias);
|
|
42
|
+
if (aliasConflict != null) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`An application with alias ${styleText('red', generatedAlias)} is already linked. Please specify a different alias with ${styleText('blue', '--alias')}.`,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
33
48
|
const appEntry = {
|
|
34
49
|
app_id: appData.id,
|
|
35
50
|
org_id: appData.ownerId,
|
|
36
51
|
deploy_url: appData.deployment.httpUrl || appData.deployment.url,
|
|
37
52
|
git_ssh_url: appData.deployment.url,
|
|
38
53
|
name: appData.name,
|
|
39
|
-
alias:
|
|
54
|
+
alias: generatedAlias,
|
|
40
55
|
};
|
|
41
56
|
|
|
42
|
-
const isPresent = currentConfig.apps.find((app) => app.app_id === appEntry.app_id) != null;
|
|
43
|
-
if (isPresent) {
|
|
44
|
-
throw new Error(
|
|
45
|
-
`Application ${styleText('red', appEntry.app_id)} is already linked with alias ${styleText('red', appEntry.alias)}`,
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
57
|
currentConfig.apps.push(appEntry);
|
|
50
58
|
|
|
51
|
-
return
|
|
59
|
+
return writeJson(config.APP_CONFIGURATION_FILE, currentConfig).then(() => {
|
|
52
60
|
return appEntry;
|
|
53
61
|
});
|
|
54
62
|
}
|
|
@@ -69,17 +77,17 @@ export async function removeLinkedApplication({ appId, alias }) {
|
|
|
69
77
|
delete newConfig.default;
|
|
70
78
|
}
|
|
71
79
|
|
|
72
|
-
await
|
|
80
|
+
await writeJson(config.APP_CONFIGURATION_FILE, newConfig);
|
|
73
81
|
return true;
|
|
74
82
|
}
|
|
75
83
|
|
|
76
|
-
export function findApp(
|
|
77
|
-
if (_.isEmpty(
|
|
84
|
+
export function findApp(appConfig, alias) {
|
|
85
|
+
if (_.isEmpty(appConfig.apps)) {
|
|
78
86
|
throw new Error('There is no linked or targeted application. Use `--app` option or `clever link` command.');
|
|
79
87
|
}
|
|
80
88
|
|
|
81
89
|
if (alias != null) {
|
|
82
|
-
const [appByAlias, secondAppByAlias] = _.filter(
|
|
90
|
+
const [appByAlias, secondAppByAlias] = _.filter(appConfig.apps, { alias });
|
|
83
91
|
if (appByAlias == null) {
|
|
84
92
|
throw new Error(`There are no applications matching alias ${alias}`);
|
|
85
93
|
}
|
|
@@ -91,7 +99,7 @@ export function findApp(config, alias) {
|
|
|
91
99
|
return appByAlias;
|
|
92
100
|
}
|
|
93
101
|
|
|
94
|
-
return findDefaultApp(
|
|
102
|
+
return findDefaultApp(appConfig);
|
|
95
103
|
}
|
|
96
104
|
|
|
97
105
|
export function checkAlreadyLinked(apps, name, alias) {
|
|
@@ -106,13 +114,13 @@ export function checkAlreadyLinked(apps, name, alias) {
|
|
|
106
114
|
}
|
|
107
115
|
}
|
|
108
116
|
|
|
109
|
-
function findDefaultApp(
|
|
110
|
-
if (_.isEmpty(
|
|
117
|
+
function findDefaultApp(appConfig) {
|
|
118
|
+
if (_.isEmpty(appConfig.apps)) {
|
|
111
119
|
throw new Error('There is no linked or targeted application. Use `--app` option or `clever link` command.');
|
|
112
120
|
}
|
|
113
121
|
|
|
114
|
-
if (
|
|
115
|
-
const defaultApp = _.find(
|
|
122
|
+
if (appConfig.default != null) {
|
|
123
|
+
const defaultApp = _.find(appConfig.apps, { app_id: appConfig.default });
|
|
116
124
|
if (defaultApp == null) {
|
|
117
125
|
throw new Error(
|
|
118
126
|
'The default application is not listed anymore. This should not happen, your `.clever.json` should be fixed.',
|
|
@@ -121,19 +129,19 @@ function findDefaultApp(config) {
|
|
|
121
129
|
return defaultApp;
|
|
122
130
|
}
|
|
123
131
|
|
|
124
|
-
if (
|
|
125
|
-
return
|
|
132
|
+
if (appConfig.apps.length === 1) {
|
|
133
|
+
return appConfig.apps[0];
|
|
126
134
|
}
|
|
127
135
|
|
|
128
|
-
const aliases = _.map(
|
|
136
|
+
const aliases = _.map(appConfig.apps, 'alias').join(', ');
|
|
129
137
|
throw new Error(
|
|
130
138
|
`Several applications are linked. You can specify one with the "--alias" option. Run "clever applications" to list linked applications. Available aliases: ${aliases}`,
|
|
131
139
|
);
|
|
132
140
|
}
|
|
133
141
|
|
|
134
142
|
export async function getAppDetails({ alias }) {
|
|
135
|
-
const
|
|
136
|
-
const app = findApp(
|
|
143
|
+
const appConfig = await loadApplicationConf();
|
|
144
|
+
const app = findApp(appConfig, alias);
|
|
137
145
|
const ownerId = app.org_id != null ? app.org_id : await User.getCurrentId();
|
|
138
146
|
return {
|
|
139
147
|
appId: app.app_id,
|
|
@@ -144,14 +152,9 @@ export async function getAppDetails({ alias }) {
|
|
|
144
152
|
};
|
|
145
153
|
}
|
|
146
154
|
|
|
147
|
-
function persistConfig(modifiedConfig) {
|
|
148
|
-
const jsonContents = JSON.stringify(modifiedConfig, null, 2);
|
|
149
|
-
return fs.writeFile(conf.APP_CONFIGURATION_FILE, jsonContents);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
155
|
export async function setDefault(alias) {
|
|
153
|
-
const
|
|
154
|
-
const app = findApp(
|
|
155
|
-
const newConfig = { ...
|
|
156
|
-
return
|
|
156
|
+
const appConfig = await loadApplicationConf();
|
|
157
|
+
const app = findApp(appConfig, alias);
|
|
158
|
+
const newConfig = { ...appConfig, default: app.app_id };
|
|
159
|
+
return writeJson(config.APP_CONFIGURATION_FILE, newConfig);
|
|
157
160
|
}
|
|
@@ -12,7 +12,7 @@ const CONFIG_KEYS = [
|
|
|
12
12
|
];
|
|
13
13
|
|
|
14
14
|
export function listAvailableIds(asText = false) {
|
|
15
|
-
const ids = CONFIG_KEYS.map((
|
|
15
|
+
const ids = CONFIG_KEYS.map((configKey) => configKey.id);
|
|
16
16
|
if (asText) {
|
|
17
17
|
return new Intl.ListFormat('en', { style: 'short', type: 'disjunction' }).format(ids);
|
|
18
18
|
}
|
|
@@ -20,9 +20,9 @@ export function listAvailableIds(asText = false) {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export function getById(id) {
|
|
23
|
-
const
|
|
24
|
-
if (
|
|
25
|
-
return
|
|
23
|
+
const configKey = CONFIG_KEYS.find((ck) => ck.id === id);
|
|
24
|
+
if (configKey != null) {
|
|
25
|
+
return configKey;
|
|
26
26
|
}
|
|
27
27
|
throw new Error(dedent`
|
|
28
28
|
Invalid configuration name: ${id}.
|
|
@@ -30,8 +30,8 @@ export function getById(id) {
|
|
|
30
30
|
`);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
export function formatValue(
|
|
34
|
-
switch (
|
|
33
|
+
export function formatValue(configKey, value) {
|
|
34
|
+
switch (configKey.kind) {
|
|
35
35
|
case 'bool': {
|
|
36
36
|
return value;
|
|
37
37
|
}
|
|
@@ -50,8 +50,8 @@ export function formatValue(config, value) {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
export function parse(
|
|
54
|
-
switch (
|
|
53
|
+
export function parse(configKey, value) {
|
|
54
|
+
switch (configKey.kind) {
|
|
55
55
|
case 'bool':
|
|
56
56
|
case 'inverted-bool':
|
|
57
57
|
case 'force-https':
|
|
@@ -59,16 +59,16 @@ export function parse(config, value) {
|
|
|
59
59
|
if (value !== 'true' && value !== 'false') {
|
|
60
60
|
throw new Error('Invalid configuration value, it must be a boolean (true or false)');
|
|
61
61
|
}
|
|
62
|
-
if (
|
|
62
|
+
if (configKey.kind === 'bool') {
|
|
63
63
|
return value === 'true';
|
|
64
64
|
}
|
|
65
|
-
if (
|
|
65
|
+
if (configKey.kind === 'inverted-bool') {
|
|
66
66
|
return value === 'false';
|
|
67
67
|
}
|
|
68
|
-
if (
|
|
68
|
+
if (configKey.kind === 'force-https') {
|
|
69
69
|
return value === 'true' ? 'ENABLED' : 'DISABLED';
|
|
70
70
|
}
|
|
71
|
-
if (
|
|
71
|
+
if (configKey.kind === 'task') {
|
|
72
72
|
return value === 'false' ? 'REGULAR' : 'TASK';
|
|
73
73
|
}
|
|
74
74
|
return;
|
|
@@ -80,54 +80,54 @@ export function parse(config, value) {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
export function parseOptions(options) {
|
|
83
|
-
const newOptions = CONFIG_KEYS.map((
|
|
84
|
-
return parseConfigOption(
|
|
83
|
+
const newOptions = CONFIG_KEYS.map((configKey) => {
|
|
84
|
+
return parseConfigOption(configKey, options);
|
|
85
85
|
}).filter((a) => {
|
|
86
86
|
return a != null && a[1] != null;
|
|
87
87
|
});
|
|
88
88
|
return Object.fromEntries(newOptions);
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
function parseConfigOption(
|
|
92
|
-
switch (
|
|
91
|
+
function parseConfigOption(configKey, options) {
|
|
92
|
+
switch (configKey.kind) {
|
|
93
93
|
case 'bool':
|
|
94
94
|
case 'inverted-bool':
|
|
95
95
|
case 'force-https':
|
|
96
96
|
case 'task': {
|
|
97
|
-
const enable = options[`enable-${
|
|
98
|
-
const disable = options[`disable-${
|
|
97
|
+
const enable = options[`enable-${configKey.id}`];
|
|
98
|
+
const disable = options[`disable-${configKey.id}`];
|
|
99
99
|
if (enable && disable) {
|
|
100
|
-
throw new Error(`You cannot use both --enable-${
|
|
100
|
+
throw new Error(`You cannot use both --enable-${configKey.id} and --disable-${configKey.id} at the same time`);
|
|
101
101
|
}
|
|
102
102
|
if (enable || disable) {
|
|
103
|
-
if (
|
|
104
|
-
return [
|
|
103
|
+
if (configKey.kind === 'bool') {
|
|
104
|
+
return [configKey.name, enable];
|
|
105
105
|
}
|
|
106
|
-
if (
|
|
107
|
-
return [
|
|
106
|
+
if (configKey.kind === 'inverted-bool') {
|
|
107
|
+
return [configKey.name, disable];
|
|
108
108
|
}
|
|
109
|
-
if (
|
|
110
|
-
return [
|
|
109
|
+
if (configKey.kind === 'force-https' || configKey.kind === 'task') {
|
|
110
|
+
return [configKey.name, parse(configKey, String(enable))];
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
return;
|
|
114
114
|
}
|
|
115
115
|
default: {
|
|
116
|
-
return [
|
|
116
|
+
return [configKey.name, options[configKey.id]];
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
export function printValue(app, id) {
|
|
122
|
-
const
|
|
123
|
-
Logger.println(formatValue(
|
|
122
|
+
const configKey = getById(id);
|
|
123
|
+
Logger.println(formatValue(configKey, app[configKey.name]));
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
export function printAllValues(app) {
|
|
127
127
|
console.table(
|
|
128
128
|
Object.fromEntries(
|
|
129
|
-
CONFIG_KEYS.map((
|
|
130
|
-
return [
|
|
129
|
+
CONFIG_KEYS.map((configKey) => {
|
|
130
|
+
return [configKey.id, formatValue(configKey, app[configKey.name])];
|
|
131
131
|
}),
|
|
132
132
|
),
|
|
133
133
|
);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { get as getAddon } from '@clevercloud/client/esm/api/v2/addon.js';
|
|
2
|
+
import { findAddonsByNameOrId } from './ids-resolver.js';
|
|
3
|
+
import { sendToApi } from './send-to-api.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Resolve a config provider ID from a name, ID or real ID
|
|
7
|
+
* @param {string} addonIdOrRealIdOrName The add-on ID (addon_xxx), real ID (config_xxx) or name
|
|
8
|
+
* @returns {Promise<{ ownerId: string, realId: string, addonId: string }>} The owner ID, real ID and addon ID
|
|
9
|
+
* @throws {Error} If the add-on is not found
|
|
10
|
+
* @throws {Error} If multiple add-ons are found with the same name
|
|
11
|
+
* @throws {Error} If the add-on is not a configuration provider
|
|
12
|
+
*/
|
|
13
|
+
export async function resolveConfigProviderId(addonIdOrRealIdOrName) {
|
|
14
|
+
const candidates = await findAddonsByNameOrId(addonIdOrRealIdOrName);
|
|
15
|
+
|
|
16
|
+
if (candidates.length === 0) {
|
|
17
|
+
throw new Error(`Config provider not found: ${addonIdOrRealIdOrName}`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (candidates.length > 1) {
|
|
21
|
+
throw new Error(`Ambiguous config provider name '${addonIdOrRealIdOrName}', please use the ID`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const { ownerId, addonId, realId } = candidates[0];
|
|
25
|
+
|
|
26
|
+
// Verify that the addon is a config provider
|
|
27
|
+
const addon = await getAddon({ id: ownerId, addonId }).then(sendToApi);
|
|
28
|
+
|
|
29
|
+
if (addon.provider.id !== 'config-provider') {
|
|
30
|
+
throw new Error(`The add-on '${addonIdOrRealIdOrName}' is not a configuration provider`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return { ownerId, realId, addonId };
|
|
34
|
+
}
|