clever-tools 3.14.0 → 4.0.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 +130 -37
- package/bin/clever.js +1427 -718
- package/package.json +61 -77
- package/src/clever-client/auth-bridge.js +9 -12
- package/src/clever-client/ng.js +1 -1
- package/src/clever-client/operators.js +7 -7
- package/src/command-options.js +6 -6
- package/src/command-promise-handler.js +6 -6
- package/src/commands/accesslogs.js +38 -29
- package/src/commands/activity.js +62 -40
- package/src/commands/addon.js +65 -69
- package/src/commands/applications.js +33 -31
- package/src/commands/cancel-deploy.js +7 -7
- package/src/commands/config.js +9 -7
- package/src/commands/console.js +3 -3
- package/src/commands/create.js +32 -31
- package/src/commands/curl.js +14 -36
- package/src/commands/database.js +10 -30
- package/src/commands/delete.js +8 -8
- package/src/commands/deploy.js +60 -46
- package/src/commands/diag.js +27 -25
- package/src/commands/domain.js +89 -91
- package/src/commands/drain.js +26 -14
- package/src/commands/emails.js +24 -28
- package/src/commands/env.js +18 -18
- package/src/commands/features.js +12 -18
- package/src/commands/keycloak.js +11 -11
- package/src/commands/kv.js +13 -13
- package/src/commands/link.js +9 -8
- package/src/commands/login.js +19 -27
- package/src/commands/logout.js +1 -1
- package/src/commands/logs.js +20 -12
- package/src/commands/makeDefault.js +5 -5
- package/src/commands/matomo.js +7 -7
- package/src/commands/metabase.js +9 -9
- package/src/commands/ng.js +32 -32
- package/src/commands/notify-email.js +11 -15
- package/src/commands/open.js +1 -1
- package/src/commands/otoroshi.js +11 -11
- package/src/commands/profile.js +8 -8
- package/src/commands/published-config.js +11 -11
- package/src/commands/restart.js +15 -8
- package/src/commands/scale.js +25 -16
- package/src/commands/service.js +18 -9
- package/src/commands/ssh-keys.js +25 -29
- package/src/commands/ssh.js +1 -2
- package/src/commands/status.js +31 -22
- package/src/commands/stop.js +3 -3
- package/src/commands/tcp-redirs.js +27 -26
- package/src/commands/tokens.js +44 -43
- package/src/commands/unlink.js +7 -5
- package/src/commands/version.js +2 -4
- package/src/commands/webhooks.js +8 -10
- package/src/experimental-features.js +7 -5
- package/src/format-table.js +46 -15
- package/src/initial-setup.js +2 -32
- package/src/initial-update-notifier.js +18 -0
- package/src/lib/has-param.js +16 -0
- package/src/lib/ng-print.js +21 -24
- package/src/lib/operator-commands.js +58 -53
- package/src/lib/prompts.js +5 -5
- package/src/lib/slugify.js +1 -1
- package/src/logger.js +24 -22
- package/src/models/activity.js +3 -3
- package/src/models/addon.js +52 -53
- package/src/models/app_configuration.js +34 -32
- package/src/models/application.js +90 -77
- package/src/models/application_configuration.js +19 -27
- package/src/models/configuration.js +30 -32
- package/src/models/deployments.js +9 -19
- package/src/models/domain.js +10 -9
- package/src/models/drain.js +16 -15
- package/src/models/exit-strategy-option.js +15 -5
- package/src/models/fs-utils.js +4 -3
- package/src/models/git.js +26 -32
- package/src/models/ids-resolver.js +37 -41
- package/src/models/isomorphic-http-with-agent.js +3 -3
- package/src/models/json-array.js +5 -6
- package/src/models/log-v4.js +44 -52
- package/src/models/log.js +40 -45
- package/src/models/namespaces.js +4 -4
- package/src/models/ng-resources.js +49 -39
- package/src/models/ng.js +41 -40
- package/src/models/node-dns-resolver.js +3 -3
- package/src/models/notification.js +7 -10
- package/src/models/operator.js +8 -10
- package/src/models/organisation.js +5 -8
- package/src/models/send-to-api.js +18 -11
- package/src/models/user.js +6 -5
- package/src/models/utils.js +5 -8
- package/src/models/variables.js +18 -20
- package/src/parsers.js +34 -34
- package/src/load-package-json.cjs +0 -5
package/src/models/ng.js
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import {
|
|
2
|
+
createNetworkGroup,
|
|
3
|
+
deleteNetworkGroup,
|
|
4
|
+
getNetworkGroup,
|
|
5
|
+
getNetworkGroupWireGuardConfiguration,
|
|
6
|
+
listNetworkGroups,
|
|
7
|
+
} from '@clevercloud/client/esm/api/v4/network-group.js';
|
|
5
8
|
import crypto from 'node:crypto';
|
|
9
|
+
import { styleText } from 'node:util';
|
|
10
|
+
import { searchNetworkGroupOrResource } from '../clever-client/ng.js';
|
|
6
11
|
import { Logger } from '../logger.js';
|
|
7
|
-
import { sendToApi } from './send-to-api.js';
|
|
8
12
|
import { checkMembersToLink } from './ng-resources.js';
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
13
|
+
import * as Organisation from './organisation.js';
|
|
14
|
+
import { sendToApi } from './send-to-api.js';
|
|
15
|
+
import * as User from './user.js';
|
|
11
16
|
|
|
12
17
|
export const POLLING_TIMEOUT_MS = 30_000;
|
|
13
18
|
export const POLLING_INTERVAL_MS = 1000;
|
|
@@ -27,7 +32,7 @@ const TYPE_PREFIXES = {
|
|
|
27
32
|
* @param {string} orgaIdOrName The owner ID or name
|
|
28
33
|
* @throws {Error} If the Network Group label is missing
|
|
29
34
|
*/
|
|
30
|
-
export async function create
|
|
35
|
+
export async function create(label, description, tags, membersIds, orgaIdOrName) {
|
|
31
36
|
const id = `ng_${crypto.randomUUID()}`;
|
|
32
37
|
const ownerId = await getOwnerIdFromOrgaIdOrName(orgaIdOrName);
|
|
33
38
|
|
|
@@ -53,11 +58,11 @@ export async function create (label, description, tags, membersIds, orgaIdOrName
|
|
|
53
58
|
* @param {object} orgaIdOrName The owner ID or name
|
|
54
59
|
* @throws {Error} If the Network Group is not found
|
|
55
60
|
*/
|
|
56
|
-
export async function destroy
|
|
61
|
+
export async function destroy(ngIdOrLabel, orgaIdOrName) {
|
|
57
62
|
const [found] = await searchNgOrResource(ngIdOrLabel, orgaIdOrName, 'NetworkGroup');
|
|
58
63
|
|
|
59
64
|
if (!found) {
|
|
60
|
-
throw new Error(`Network Group ${
|
|
65
|
+
throw new Error(`Network Group ${styleText('red', ngIdOrLabel.ngId ?? ngIdOrLabel.ngResourceLabel)} not found`);
|
|
61
66
|
}
|
|
62
67
|
|
|
63
68
|
await deleteNetworkGroup({ ownerId: found.ownerId, networkGroupId: found.id }).then(sendToApi);
|
|
@@ -76,22 +81,27 @@ export async function destroy (ngIdOrLabel, orgaIdOrName) {
|
|
|
76
81
|
* @throws {Error} If the Network Group is not found
|
|
77
82
|
* @throws {Error} If the Peer is not in the Network Group
|
|
78
83
|
*/
|
|
79
|
-
export async function getPeerConfig
|
|
84
|
+
export async function getPeerConfig(peerIdOrLabel, ngIdOrLabel, orgaIdOrName) {
|
|
80
85
|
const [parentNg] = await searchNgOrResource(ngIdOrLabel, orgaIdOrName, 'NetworkGroup');
|
|
81
86
|
|
|
82
87
|
if (!parentNg) {
|
|
83
|
-
throw new Error(`Network Group ${
|
|
88
|
+
throw new Error(`Network Group ${styleText('red', ngIdOrLabel.ngId ?? ngIdOrLabel.ngResourceLabel)} not found`);
|
|
84
89
|
}
|
|
85
90
|
|
|
86
91
|
const [peer] = await searchNgOrResource(peerIdOrLabel, orgaIdOrName, 'Peer');
|
|
87
92
|
|
|
88
93
|
// peer.id is catched as a ngResourceLabel as it's a string with no distinctive prefix for now, it will change from API
|
|
89
|
-
if (
|
|
90
|
-
|
|
94
|
+
if (
|
|
95
|
+
!peer ||
|
|
96
|
+
(peerIdOrLabel.ngResourceLabel &&
|
|
97
|
+
peerIdOrLabel.ngResourceLabel !== peer.label &&
|
|
98
|
+
peerIdOrLabel.ngResourceLabel !== peer.id)
|
|
99
|
+
) {
|
|
100
|
+
throw new Error(`Peer ${styleText('red', peerIdOrLabel.ngResourceLabel ?? peerIdOrLabel.member)} not found`);
|
|
91
101
|
}
|
|
92
102
|
|
|
93
103
|
if (!parentNg.peers.find((p) => p.id === peer.id)) {
|
|
94
|
-
throw new Error(`Peer ${
|
|
104
|
+
throw new Error(`Peer ${styleText('red', peer.id)} is not in Network Group ${styleText('red', parentNg.id)}`);
|
|
95
105
|
}
|
|
96
106
|
|
|
97
107
|
Logger.debug(`Getting configuration for Peer ${peer.id}`);
|
|
@@ -111,7 +121,7 @@ export async function getPeerConfig (peerIdOrLabel, ngIdOrLabel, orgaIdOrName) {
|
|
|
111
121
|
* @param {string} orgaIdOrName The owner ID or name
|
|
112
122
|
* @returns {Promise<Array<Object>>} The Network Groups
|
|
113
123
|
*/
|
|
114
|
-
export async function getNG
|
|
124
|
+
export async function getNG(networkGroupId, orgaIdOrName) {
|
|
115
125
|
const ownerId = await getOwnerIdFromOrgaIdOrName(orgaIdOrName);
|
|
116
126
|
|
|
117
127
|
Logger.info(`Get Network Group ${networkGroupId} for owner ${ownerId}`);
|
|
@@ -126,7 +136,7 @@ export async function getNG (networkGroupId, orgaIdOrName) {
|
|
|
126
136
|
* @param {string} orgaIdOrName The owner ID or name
|
|
127
137
|
* @returns {Promise<Array<Object>>} The Network Groups
|
|
128
138
|
*/
|
|
129
|
-
export async function getAllNGs
|
|
139
|
+
export async function getAllNGs(orgaIdOrName) {
|
|
130
140
|
const ownerId = await getOwnerIdFromOrgaIdOrName(orgaIdOrName);
|
|
131
141
|
|
|
132
142
|
Logger.info(`Listing Network Groups from owner ${ownerId}`);
|
|
@@ -144,17 +154,12 @@ export async function getAllNGs (orgaIdOrName) {
|
|
|
144
154
|
* @throws {Error} If multiple Network Groups or member/peer are found in single_result mode
|
|
145
155
|
* @returns {Promise<Object>} Found results
|
|
146
156
|
*/
|
|
147
|
-
export async function searchNgOrResource
|
|
157
|
+
export async function searchNgOrResource(idOrLabel, orgaIdOrName, type = 'all', exactMatch = true) {
|
|
148
158
|
const ownerId = await getOwnerIdFromOrgaIdOrName(orgaIdOrName);
|
|
149
159
|
|
|
150
160
|
// If idOrLabel is a string we use it, or we look through multiple keys
|
|
151
|
-
const query =
|
|
152
|
-
? idOrLabel
|
|
153
|
-
: (
|
|
154
|
-
idOrLabel.ngId
|
|
155
|
-
?? idOrLabel.memberId
|
|
156
|
-
?? idOrLabel.ngResourceLabel
|
|
157
|
-
);
|
|
161
|
+
const query =
|
|
162
|
+
typeof idOrLabel === 'string' ? idOrLabel : (idOrLabel.ngId ?? idOrLabel.memberId ?? idOrLabel.ngResourceLabel);
|
|
158
163
|
|
|
159
164
|
const found = await searchNetworkGroupOrResource({ ownerId, query }).then(sendToApi);
|
|
160
165
|
|
|
@@ -181,12 +186,12 @@ export async function searchNgOrResource (idOrLabel, orgaIdOrName, type = 'all',
|
|
|
181
186
|
}
|
|
182
187
|
|
|
183
188
|
if (filtered.length > 1 && type !== 'all') {
|
|
184
|
-
throw new Error(`Multiple resources found for ${
|
|
185
|
-
${filtered.map((f) => ` • ${f.id} ${
|
|
189
|
+
throw new Error(`Multiple resources found for ${styleText('red', query)}, use ID instead:
|
|
190
|
+
${filtered.map((f) => ` • ${f.id} ${styleText('grey', `(${f.label} - ${f.type})`)}`).join('\n')}`);
|
|
186
191
|
}
|
|
187
192
|
|
|
188
193
|
// Deduplicate results
|
|
189
|
-
return filtered.filter((item, index, array) => array.findIndex((element) =>
|
|
194
|
+
return filtered.filter((item, index, array) => array.findIndex((element) => element.id === item.id) === index);
|
|
190
195
|
}
|
|
191
196
|
|
|
192
197
|
/**
|
|
@@ -195,7 +200,7 @@ ${filtered.map((f) => ` • ${f.id} ${colors.grey(`(${f.label} - ${f.type})`)}`)
|
|
|
195
200
|
* @param {Array<string>} membersIds The members IDs
|
|
196
201
|
* @returns {Array<Object>} Array of members with id, domainName and kind
|
|
197
202
|
*/
|
|
198
|
-
export function constructMembers
|
|
203
|
+
export function constructMembers(ngId, membersIds) {
|
|
199
204
|
return membersIds.map((id) => {
|
|
200
205
|
const domainName = `${id}.m.${ngId}.${DOMAIN}`;
|
|
201
206
|
const prefixToType = TYPE_PREFIXES;
|
|
@@ -204,8 +209,7 @@ export function constructMembers (ngId, membersIds) {
|
|
|
204
209
|
id,
|
|
205
210
|
domainName,
|
|
206
211
|
// Get kind from prefix match in id (app_*, addon_*, external_*) or default to 'APPLICATION'
|
|
207
|
-
kind: prefixToType[Object.keys(prefixToType).find((p) => id.startsWith(p))]
|
|
208
|
-
?? TYPE_PREFIXES.app_,
|
|
212
|
+
kind: prefixToType[Object.keys(prefixToType).find((p) => id.startsWith(p))] ?? TYPE_PREFIXES.app_,
|
|
209
213
|
};
|
|
210
214
|
});
|
|
211
215
|
}
|
|
@@ -219,12 +223,12 @@ export function constructMembers (ngId, membersIds) {
|
|
|
219
223
|
* @throws {Error} When timeout is reached
|
|
220
224
|
* @returns {Promise<void>}
|
|
221
225
|
*/
|
|
222
|
-
async function pollNetworkGroup
|
|
226
|
+
async function pollNetworkGroup(ownerId, ngId, { waitForMembers = null, waitForDeletion = false } = {}) {
|
|
223
227
|
return new Promise((resolve, reject) => {
|
|
224
228
|
Logger.info(`Polling Network Groups from owner ${ownerId}`);
|
|
225
|
-
const timeoutTime = Date.now() +
|
|
229
|
+
const timeoutTime = Date.now() + POLLING_TIMEOUT_MS;
|
|
226
230
|
|
|
227
|
-
async function pollOnce
|
|
231
|
+
async function pollOnce() {
|
|
228
232
|
if (Date.now() > timeoutTime) {
|
|
229
233
|
const action = waitForDeletion ? 'deletion of' : 'creation of';
|
|
230
234
|
reject(new Error(`Timeout while checking ${action} Network Group ${ngId}`));
|
|
@@ -254,8 +258,7 @@ async function pollNetworkGroup (ownerId, ngId, { waitForMembers = null, waitFor
|
|
|
254
258
|
}
|
|
255
259
|
|
|
256
260
|
setTimeout(pollOnce, POLLING_INTERVAL_MS);
|
|
257
|
-
}
|
|
258
|
-
catch (error) {
|
|
261
|
+
} catch (error) {
|
|
259
262
|
reject(error);
|
|
260
263
|
}
|
|
261
264
|
}
|
|
@@ -269,8 +272,6 @@ async function pollNetworkGroup (ownerId, ngId, { waitForMembers = null, waitFor
|
|
|
269
272
|
* @param {object} orgaIdOrName The Organisation ID or name
|
|
270
273
|
* @returns {Promise<string>} The owner ID
|
|
271
274
|
*/
|
|
272
|
-
async function getOwnerIdFromOrgaIdOrName
|
|
273
|
-
return orgaIdOrName != null
|
|
274
|
-
? Organisation.getId(orgaIdOrName)
|
|
275
|
-
: User.getCurrentId();
|
|
275
|
+
async function getOwnerIdFromOrgaIdOrName(orgaIdOrName) {
|
|
276
|
+
return orgaIdOrName != null ? Organisation.getId(orgaIdOrName) : User.getCurrentId();
|
|
276
277
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Resolver } from 'node:dns/promises';
|
|
2
2
|
|
|
3
3
|
export class DnsResolver {
|
|
4
|
-
constructor
|
|
4
|
+
constructor() {
|
|
5
5
|
this._resolver = new Resolver();
|
|
6
6
|
}
|
|
7
7
|
|
|
@@ -12,7 +12,7 @@ export class DnsResolver {
|
|
|
12
12
|
* @param {string} hostname - The hostname to resolve A records for.
|
|
13
13
|
* @returns {Promise<string[]>} A promise that resolves to an array of IP addresses, or an empty array if not found or an error occurs.
|
|
14
14
|
*/
|
|
15
|
-
resolveA
|
|
15
|
+
resolveA(hostname) {
|
|
16
16
|
return this._resolver.resolve4(hostname).catch((error) => {
|
|
17
17
|
switch (error.code) {
|
|
18
18
|
case 'ENOTFOUND':
|
|
@@ -30,7 +30,7 @@ export class DnsResolver {
|
|
|
30
30
|
* @param {string} hostname - The hostname to resolve CNAME records for.
|
|
31
31
|
* @returns {Promise<string|null>} A promise that resolves to the CNAME record if found, or null if not found or an error occurs.
|
|
32
32
|
*/
|
|
33
|
-
resolveCname
|
|
33
|
+
resolveCname(hostname) {
|
|
34
34
|
return this._resolver.resolveCname(hostname).catch((error) => {
|
|
35
35
|
switch (error.code) {
|
|
36
36
|
case 'ENOTFOUND':
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import cliparse from 'cliparse';
|
|
2
|
-
import * as AppConfig from '
|
|
3
|
-
import * as Organisation from '
|
|
4
|
-
import * as User from '
|
|
2
|
+
import * as AppConfig from './app_configuration.js';
|
|
3
|
+
import * as Organisation from './organisation.js';
|
|
4
|
+
import * as User from './user.js';
|
|
5
5
|
|
|
6
|
-
export function listMetaEvents
|
|
6
|
+
export function listMetaEvents() {
|
|
7
7
|
return cliparse.autocomplete.words([
|
|
8
8
|
'META_SERVICE_LIFECYCLE',
|
|
9
9
|
'META_DEPLOYMENT_RESULT',
|
|
@@ -12,14 +12,11 @@ export function listMetaEvents () {
|
|
|
12
12
|
]);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export function getOrgaIdOrUserId
|
|
16
|
-
return
|
|
17
|
-
? User.getCurrentId()
|
|
18
|
-
: Organisation.getId(orgIdOrName);
|
|
15
|
+
export function getOrgaIdOrUserId(orgIdOrName) {
|
|
16
|
+
return orgIdOrName == null ? User.getCurrentId() : Organisation.getId(orgIdOrName);
|
|
19
17
|
}
|
|
20
18
|
|
|
21
|
-
export async function getOwnerAndApp
|
|
22
|
-
|
|
19
|
+
export async function getOwnerAndApp(alias, org, useLinkedApp) {
|
|
23
20
|
if (!useLinkedApp) {
|
|
24
21
|
const ownerId = await getOrgaIdOrUserId(org);
|
|
25
22
|
return { ownerId };
|
package/src/models/operator.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import colors from 'colors/safe.js';
|
|
2
1
|
import dedent from 'dedent';
|
|
3
|
-
|
|
4
|
-
import { sendToApi } from './send-to-api.js';
|
|
5
|
-
import { findAddonsByNameOrId } from './ids-resolver.js';
|
|
2
|
+
import { styleText } from 'node:util';
|
|
6
3
|
import { getOperator } from '../clever-client/operators.js';
|
|
4
|
+
import { findAddonsByNameOrId } from './ids-resolver.js';
|
|
5
|
+
import { sendToApi } from './send-to-api.js';
|
|
7
6
|
|
|
8
7
|
/**
|
|
9
8
|
* Get the details of an operator from its name or ID
|
|
@@ -12,7 +11,7 @@ import { getOperator } from '../clever-client/operators.js';
|
|
|
12
11
|
* @returns {Promise<object>} The operator's details
|
|
13
12
|
* @throws {Error} If the operator provider is unknown
|
|
14
13
|
*/
|
|
15
|
-
export async function getDetails
|
|
14
|
+
export async function getDetails(provider, operatorIdOrName) {
|
|
16
15
|
const realId = await getSingleRealId(operatorIdOrName);
|
|
17
16
|
return getOperator({ provider, realId }).then(sendToApi);
|
|
18
17
|
}
|
|
@@ -24,8 +23,7 @@ export async function getDetails (provider, operatorIdOrName) {
|
|
|
24
23
|
* @throws {Error} If the operator is not found
|
|
25
24
|
* @throws {Error} If the operator name is ambiguous
|
|
26
25
|
*/
|
|
27
|
-
export async function getSingleRealId
|
|
28
|
-
|
|
26
|
+
export async function getSingleRealId(operatorIdOrName) {
|
|
29
27
|
if (operatorIdOrName.operator_id != null) {
|
|
30
28
|
return operatorIdOrName.operator_id;
|
|
31
29
|
}
|
|
@@ -34,13 +32,13 @@ export async function getSingleRealId (operatorIdOrName) {
|
|
|
34
32
|
const operators = await findAddonsByNameOrId(name);
|
|
35
33
|
|
|
36
34
|
if (operators.length === 0) {
|
|
37
|
-
throw new Error(`Could not find ${
|
|
35
|
+
throw new Error(`Could not find ${styleText('red', name)}`);
|
|
38
36
|
}
|
|
39
37
|
|
|
40
38
|
if (operators.length > 1) {
|
|
41
39
|
throw new Error(dedent`
|
|
42
|
-
Ambiguous name ${
|
|
43
|
-
${
|
|
40
|
+
Ambiguous name ${styleText('red', name)}, use the real ID instead:
|
|
41
|
+
${styleText('grey', operators.map((otoroshi) => `- ${otoroshi.name} (${otoroshi.realId})`).join('\n'))}
|
|
44
42
|
`);
|
|
45
43
|
}
|
|
46
44
|
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import _ from 'lodash';
|
|
2
|
-
|
|
3
1
|
import { getSummary } from '@clevercloud/client/esm/api/v2/user.js';
|
|
4
|
-
import
|
|
2
|
+
import _ from 'lodash';
|
|
3
|
+
import { sendToApi } from './send-to-api.js';
|
|
5
4
|
|
|
6
|
-
export async function getId
|
|
5
|
+
export async function getId(orgaIdOrName) {
|
|
7
6
|
if (orgaIdOrName == null) {
|
|
8
7
|
return null;
|
|
9
8
|
}
|
|
@@ -12,12 +11,10 @@ export async function getId (orgaIdOrName) {
|
|
|
12
11
|
return orgaIdOrName.orga_id;
|
|
13
12
|
}
|
|
14
13
|
|
|
15
|
-
return getByName(orgaIdOrName.orga_name)
|
|
16
|
-
.then((orga) => orga.id);
|
|
14
|
+
return getByName(orgaIdOrName.orga_name).then((orga) => orga.id);
|
|
17
15
|
}
|
|
18
16
|
|
|
19
|
-
async function getByName
|
|
20
|
-
|
|
17
|
+
async function getByName(name) {
|
|
21
18
|
const fullSummary = await getSummary({}).then(sendToApi);
|
|
22
19
|
const filteredOrgs = _.filter(fullSummary.organisations, { name });
|
|
23
20
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { Logger } from '../logger.js';
|
|
2
1
|
import { addOauthHeader } from '@clevercloud/client/esm/oauth.js';
|
|
3
|
-
import { conf, loadOAuthConf } from './configuration.js';
|
|
4
2
|
import { prefixUrl } from '@clevercloud/client/esm/prefix-url.js';
|
|
5
3
|
import { request } from '@clevercloud/client/esm/request.fetch.js';
|
|
6
4
|
import { subtle as cryptoSuble } from 'node:crypto';
|
|
5
|
+
import { styleText } from 'node:util';
|
|
7
6
|
import { addOauthHeaderPlaintext } from '../clever-client/auth-bridge.js';
|
|
8
|
-
import
|
|
7
|
+
import { Logger } from '../logger.js';
|
|
8
|
+
import { conf, loadOAuthConf } from './configuration.js';
|
|
9
9
|
|
|
10
10
|
// Required for @clevercloud/client with "old" Node.js
|
|
11
11
|
if (globalThis.crypto == null) {
|
|
@@ -14,7 +14,7 @@ if (globalThis.crypto == null) {
|
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
async function loadTokens
|
|
17
|
+
async function loadTokens() {
|
|
18
18
|
const tokens = await loadOAuthConf();
|
|
19
19
|
return {
|
|
20
20
|
OAUTH_CONSUMER_KEY: conf.OAUTH_CONSUMER_KEY,
|
|
@@ -24,33 +24,37 @@ async function loadTokens () {
|
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
export async function sendToApi
|
|
27
|
+
export async function sendToApi(requestParams) {
|
|
28
28
|
const tokens = await loadTokens();
|
|
29
29
|
return Promise.resolve(requestParams)
|
|
30
30
|
.then(prefixUrl(conf.API_HOST))
|
|
31
31
|
.then(addOauthHeader(tokens))
|
|
32
32
|
.then((requestParams) => {
|
|
33
|
-
Logger.debug(
|
|
33
|
+
Logger.debug(
|
|
34
|
+
`${requestParams.method.toUpperCase()} ${requestParams.url} ? ${JSON.stringify(requestParams.queryParams)}`,
|
|
35
|
+
);
|
|
34
36
|
return requestParams;
|
|
35
37
|
})
|
|
36
38
|
.then(request)
|
|
37
39
|
.catch(processError);
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
export async function sendToAuthBridge
|
|
42
|
+
export async function sendToAuthBridge(requestParams) {
|
|
41
43
|
const tokens = await loadTokens();
|
|
42
44
|
return Promise.resolve(requestParams)
|
|
43
45
|
.then(prefixUrl(conf.AUTH_BRIDGE_HOST))
|
|
44
46
|
.then(addOauthHeaderPlaintext(tokens))
|
|
45
47
|
.then((requestParams) => {
|
|
46
|
-
Logger.debug(
|
|
48
|
+
Logger.debug(
|
|
49
|
+
`${requestParams.method.toUpperCase()} ${requestParams.url} ? ${JSON.stringify(requestParams.queryParams)}`,
|
|
50
|
+
);
|
|
47
51
|
return requestParams;
|
|
48
52
|
})
|
|
49
53
|
.then(request)
|
|
50
54
|
.catch(processError);
|
|
51
55
|
}
|
|
52
56
|
|
|
53
|
-
export function processError
|
|
57
|
+
export function processError(error) {
|
|
54
58
|
const code = error.code ?? error?.cause?.code;
|
|
55
59
|
if (code === 'EAI_AGAIN') {
|
|
56
60
|
throw new Error('Cannot reach the Clever Cloud API, please check your internet connection.', { cause: error });
|
|
@@ -59,12 +63,15 @@ export function processError (error) {
|
|
|
59
63
|
throw new Error('The connection to the Clever Cloud API was closed abruptly, please try again.', { cause: error });
|
|
60
64
|
}
|
|
61
65
|
if (error?.response?.status === 401) {
|
|
62
|
-
throw new Error(
|
|
66
|
+
throw new Error(
|
|
67
|
+
`You're not logged in, use ${styleText('red', 'clever login')} command to connect to your Clever Cloud account`,
|
|
68
|
+
{ cause: error },
|
|
69
|
+
);
|
|
63
70
|
}
|
|
64
71
|
throw error;
|
|
65
72
|
}
|
|
66
73
|
|
|
67
|
-
export async function getHostAndTokens
|
|
74
|
+
export async function getHostAndTokens() {
|
|
68
75
|
const tokens = await loadTokens();
|
|
69
76
|
return {
|
|
70
77
|
apiHost: conf.API_HOST,
|
package/src/models/user.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { get } from '@clevercloud/client/esm/api/v2/organisation.js';
|
|
2
|
-
import { sendToApi } from '
|
|
2
|
+
import { sendToApi } from './send-to-api.js';
|
|
3
3
|
|
|
4
|
-
export function getCurrent
|
|
4
|
+
export function getCurrent() {
|
|
5
5
|
return get({}).then(sendToApi);
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
export function getCurrentId
|
|
9
|
-
return get({})
|
|
8
|
+
export function getCurrentId() {
|
|
9
|
+
return get({})
|
|
10
|
+
.then(sendToApi)
|
|
10
11
|
.then(({ id }) => id);
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
// TODO move to clever client
|
|
14
|
-
export function getCurrentToken
|
|
15
|
+
export function getCurrentToken() {
|
|
15
16
|
return Promise.resolve({
|
|
16
17
|
method: 'get',
|
|
17
18
|
url: '/v2/self/tokens/current',
|
package/src/models/utils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import openPage from 'open';
|
|
1
2
|
import { Logger } from '../logger.js';
|
|
2
3
|
import { conf } from './configuration.js';
|
|
3
|
-
import openPage from 'open';
|
|
4
4
|
|
|
5
5
|
// Inspirations:
|
|
6
6
|
// https://github.com/sindresorhus/p-defer/blob/master/index.js
|
|
@@ -8,8 +8,7 @@ import openPage from 'open';
|
|
|
8
8
|
|
|
9
9
|
// When you mix async/await APIs with event emitters callbacks, it's hard to keep a proper error flow without a good old deferred.
|
|
10
10
|
export class Deferred {
|
|
11
|
-
|
|
12
|
-
constructor () {
|
|
11
|
+
constructor() {
|
|
13
12
|
this.promise = new Promise((resolve, reject) => {
|
|
14
13
|
this.resolve = resolve;
|
|
15
14
|
this.reject = reject;
|
|
@@ -23,10 +22,8 @@ export class Deferred {
|
|
|
23
22
|
* @param {string} message The message to display before opening the URL
|
|
24
23
|
* @returns {Promise<void>} A promise that resolves when the URL is opened
|
|
25
24
|
*/
|
|
26
|
-
export function openBrowser
|
|
27
|
-
const url = urlOrPath.startsWith('/')
|
|
28
|
-
? `${conf.CONSOLE_URL}${urlOrPath}`
|
|
29
|
-
: urlOrPath;
|
|
25
|
+
export function openBrowser(urlOrPath, message) {
|
|
26
|
+
const url = urlOrPath.startsWith('/') ? `${conf.CONSOLE_URL}${urlOrPath}` : urlOrPath;
|
|
30
27
|
|
|
31
28
|
Logger.debug(`Opening URL "${url}" in browser`);
|
|
32
29
|
Logger.println(message);
|
|
@@ -34,7 +31,7 @@ export function openBrowser (urlOrPath, message) {
|
|
|
34
31
|
return openPage(url, { wait: false });
|
|
35
32
|
}
|
|
36
33
|
|
|
37
|
-
export function truncateWithEllipsis
|
|
34
|
+
export function truncateWithEllipsis(length, string) {
|
|
38
35
|
if (string.length > length - 1) {
|
|
39
36
|
return string.substring(0, length - 1) + '…';
|
|
40
37
|
}
|
package/src/models/variables.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
+
import { ERROR_TYPES, parseRaw, toNameValueObject, validateName } from '@clevercloud/client/esm/utils/env-vars.js';
|
|
1
2
|
import _countBy from 'lodash/countBy.js';
|
|
2
3
|
import readline from 'node:readline';
|
|
3
|
-
import { ERROR_TYPES, parseRaw, toNameValueObject, validateName } from '@clevercloud/client/esm/utils/env-vars.js';
|
|
4
|
-
|
|
5
|
-
function readStdin () {
|
|
6
4
|
|
|
5
|
+
function readStdin() {
|
|
7
6
|
return new Promise((resolve, reject) => {
|
|
8
|
-
|
|
9
7
|
const rl = readline.createInterface({
|
|
10
8
|
input: process.stdin,
|
|
11
9
|
output: process.stdout,
|
|
@@ -31,31 +29,32 @@ function readStdin () {
|
|
|
31
29
|
// a "name" property with a string and value
|
|
32
30
|
// a "value" property with a string and value
|
|
33
31
|
// TODO: This should be moved and unit tested in the clever-client repo
|
|
34
|
-
function parseFromJson
|
|
35
|
-
|
|
32
|
+
function parseFromJson(rawStdin) {
|
|
36
33
|
let variables;
|
|
37
34
|
try {
|
|
38
35
|
variables = JSON.parse(rawStdin);
|
|
39
|
-
}
|
|
40
|
-
catch (e) {
|
|
36
|
+
} catch (e) {
|
|
41
37
|
throw new Error(`Error when parsing JSON input: ${e.message}`);
|
|
42
38
|
}
|
|
43
39
|
|
|
44
40
|
if (!Array.isArray(variables) || variables.some((entry) => typeof entry !== 'object')) {
|
|
45
|
-
throw new Error(
|
|
41
|
+
throw new Error(
|
|
42
|
+
'The input was valid JSON but it does not follow the correct format. It must be an array of objects.',
|
|
43
|
+
);
|
|
46
44
|
}
|
|
47
45
|
|
|
48
46
|
const someEntriesDontHaveNameAndValueAsString = variables.some(({ name, value }) => {
|
|
49
|
-
return
|
|
47
|
+
return typeof name !== 'string' || typeof value !== 'string';
|
|
50
48
|
});
|
|
51
49
|
if (someEntriesDontHaveNameAndValueAsString) {
|
|
52
|
-
throw new Error(
|
|
50
|
+
throw new Error(
|
|
51
|
+
'The input was a valid JSON array of objects but all entries must have properties "name" and "value" of type string. Ex: { "name": "THE_NAME", "value": "the value" }',
|
|
52
|
+
);
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
const namesOccurences = _countBy(variables, 'name');
|
|
56
|
-
const duplicatedNames = Object
|
|
57
|
-
.
|
|
58
|
-
.filter(([name, count]) => count > 1)
|
|
56
|
+
const duplicatedNames = Object.entries(namesOccurences)
|
|
57
|
+
.filter(([_name, count]) => count > 1)
|
|
59
58
|
.map(([name]) => `"${name}"`)
|
|
60
59
|
.join(', ');
|
|
61
60
|
|
|
@@ -75,11 +74,10 @@ function parseFromJson (rawStdin) {
|
|
|
75
74
|
return toNameValueObject(variables);
|
|
76
75
|
}
|
|
77
76
|
|
|
78
|
-
function parseFromNameEqualsValue
|
|
77
|
+
function parseFromNameEqualsValue(rawStdin) {
|
|
79
78
|
const { variables, errors } = parseRaw(rawStdin);
|
|
80
79
|
|
|
81
80
|
if (errors.length !== 0) {
|
|
82
|
-
|
|
83
81
|
const formattedErrors = errors
|
|
84
82
|
.map(({ type, name, pos }) => {
|
|
85
83
|
if (type === ERROR_TYPES.INVALID_NAME) {
|
|
@@ -95,7 +93,8 @@ function parseFromNameEqualsValue (rawStdin) {
|
|
|
95
93
|
return `line ${pos.line}: the value is not valid, if you use quotes, you need to escape them like this: \\" or quote the whole value.`;
|
|
96
94
|
}
|
|
97
95
|
return 'Unknown error in your input';
|
|
98
|
-
})
|
|
96
|
+
})
|
|
97
|
+
.join('\n');
|
|
99
98
|
|
|
100
99
|
throw new Error(formattedErrors);
|
|
101
100
|
}
|
|
@@ -103,8 +102,7 @@ function parseFromNameEqualsValue (rawStdin) {
|
|
|
103
102
|
return toNameValueObject(variables);
|
|
104
103
|
}
|
|
105
104
|
|
|
106
|
-
export async function readVariablesFromStdin
|
|
107
|
-
|
|
105
|
+
export async function readVariablesFromStdin(format) {
|
|
108
106
|
const rawStdin = await readStdin();
|
|
109
107
|
|
|
110
108
|
switch (format) {
|
|
@@ -113,6 +111,6 @@ export async function readVariablesFromStdin (format) {
|
|
|
113
111
|
case 'json':
|
|
114
112
|
return parseFromJson(rawStdin);
|
|
115
113
|
default:
|
|
116
|
-
throw new Error(
|
|
114
|
+
throw new Error("Unrecognized environment input format. Available formats are 'name-equals-value' and 'json'");
|
|
117
115
|
}
|
|
118
116
|
}
|