clever-tools 4.0.2 → 4.1.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/bin/clever.js +3 -54
- package/package.json +2 -2
- package/src/commands/accesslogs.js +1 -1
- package/src/commands/domain.js +67 -46
- package/src/commands/emails.js +1 -15
- package/src/commands/link.js +4 -4
- package/src/commands/ssh-keys.js +2 -13
- package/src/commands/status.js +5 -0
- package/src/commands/tcp-redirs.js +2 -31
- package/src/commands/tokens.js +1 -4
- package/src/lib/format-date.js +3 -0
- package/src/logger.js +10 -0
- package/src/models/app_configuration.js +10 -6
- package/src/models/emails.js +16 -0
- package/src/models/log-v4.js +2 -1
- package/src/models/ssh-keys.js +10 -0
package/bin/clever.js
CHANGED
|
@@ -103,10 +103,6 @@ async function run() {
|
|
|
103
103
|
}),
|
|
104
104
|
apiTokenId: cliparse.argument('api-token-id', { description: 'API token ID' }),
|
|
105
105
|
apiTokenName: cliparse.argument('api-token-name', { description: 'API token name' }),
|
|
106
|
-
ngId: cliparse.argument('id', {
|
|
107
|
-
description: 'Network Group ID',
|
|
108
|
-
parser: Parsers.ngResourceType,
|
|
109
|
-
}),
|
|
110
106
|
ngLabel: cliparse.argument('ng-label', {
|
|
111
107
|
description: 'Network Group label',
|
|
112
108
|
parser: Parsers.ngResourceType,
|
|
@@ -115,9 +111,6 @@ async function run() {
|
|
|
115
111
|
description: 'Network Group ID or label',
|
|
116
112
|
parser: Parsers.ngResourceType,
|
|
117
113
|
}),
|
|
118
|
-
ngDescription: cliparse.argument('ng-description', {
|
|
119
|
-
description: 'Network Group description',
|
|
120
|
-
}),
|
|
121
114
|
ngExternalPeerLabel: cliparse.argument('external-peer-label', {
|
|
122
115
|
description: 'External peer label',
|
|
123
116
|
parser: Parsers.ngResourceType,
|
|
@@ -216,15 +209,6 @@ async function run() {
|
|
|
216
209
|
"Comma separated list of members IDs to link to a Network Group ('app_xxx', 'addon_xxx', 'external_xxx')",
|
|
217
210
|
parser: Parsers.commaSeparated,
|
|
218
211
|
}),
|
|
219
|
-
ngMemberLabel: cliparse.option('label', {
|
|
220
|
-
required: false,
|
|
221
|
-
metavar: 'member_label',
|
|
222
|
-
description: 'The member label',
|
|
223
|
-
parser: Parsers.ngResourceType,
|
|
224
|
-
}),
|
|
225
|
-
ngPeerGetConfig: cliparse.flag('config', {
|
|
226
|
-
description: 'Get the Wireguard configuration of an external peer',
|
|
227
|
-
}),
|
|
228
212
|
ngResourceType: cliparse.option('type', {
|
|
229
213
|
metavar: 'type',
|
|
230
214
|
description: 'Type of resource to look for (NetworkGroup, Member, CleverPeer, ExternalPeer)',
|
|
@@ -265,10 +249,6 @@ async function run() {
|
|
|
265
249
|
metavar: 'TEXT',
|
|
266
250
|
description: 'Get only domains containing the provided text',
|
|
267
251
|
}),
|
|
268
|
-
naturalName: cliparse.flag('natural-name', {
|
|
269
|
-
aliases: ['n'],
|
|
270
|
-
description: 'Show the application names or aliases if possible',
|
|
271
|
-
}),
|
|
272
252
|
before: cliparse.option('before', {
|
|
273
253
|
metavar: 'before',
|
|
274
254
|
aliases: ['until'],
|
|
@@ -294,10 +274,6 @@ async function run() {
|
|
|
294
274
|
metavar: 'tag',
|
|
295
275
|
description: 'Tag to push (none by default)',
|
|
296
276
|
}),
|
|
297
|
-
databaseId: cliparse.option('database-id', {
|
|
298
|
-
metavar: 'database_id',
|
|
299
|
-
description: 'The Database ID (e.g.: postgresql_xxx)',
|
|
300
|
-
}),
|
|
301
277
|
deploymentId: cliparse.option('deployment-id', {
|
|
302
278
|
metavar: 'deployment_id',
|
|
303
279
|
description: 'Fetch logs for a given deployment',
|
|
@@ -534,45 +510,17 @@ async function run() {
|
|
|
534
510
|
aliases: ['y'],
|
|
535
511
|
description: 'Skip confirmation and remove all SSH keys directly',
|
|
536
512
|
}),
|
|
537
|
-
confirmTcpRedirCreation: cliparse.flag('yes', {
|
|
538
|
-
aliases: ['y'],
|
|
539
|
-
description: 'Skip confirmation even if the TCP redirection is not free',
|
|
540
|
-
}),
|
|
541
513
|
yes: cliparse.flag('yes', {
|
|
542
514
|
aliases: ['y'],
|
|
543
515
|
description: 'Skip confirmation',
|
|
544
516
|
}),
|
|
545
517
|
jsonFormat: cliparse.flag('json', { aliases: ['j'], description: 'Show result in JSON format' }),
|
|
546
518
|
humanJsonOutputFormat: getOutputFormatOption(),
|
|
547
|
-
tag: cliparse.option('tag', {
|
|
548
|
-
required: true,
|
|
549
|
-
metavar: 'tag',
|
|
550
|
-
description: 'A tag',
|
|
551
|
-
parser: Parsers.tag,
|
|
552
|
-
}),
|
|
553
|
-
tags: cliparse.option('tags', {
|
|
554
|
-
required: true,
|
|
555
|
-
metavar: 'tags',
|
|
556
|
-
description: 'List of tags, separated by a comma',
|
|
557
|
-
parser: Parsers.tags,
|
|
558
|
-
}),
|
|
559
519
|
optTags: cliparse.option('tags', {
|
|
560
520
|
metavar: 'tags',
|
|
561
521
|
description: 'List of tags, separated by a comma',
|
|
562
522
|
parser: Parsers.tags,
|
|
563
523
|
}),
|
|
564
|
-
optIpAddress: cliparse.option('ip', {
|
|
565
|
-
required: false,
|
|
566
|
-
metavar: 'ip_address',
|
|
567
|
-
description: 'An IP address',
|
|
568
|
-
parser: Parsers.ipAddress,
|
|
569
|
-
}),
|
|
570
|
-
optPortNumber: cliparse.option('port', {
|
|
571
|
-
required: false,
|
|
572
|
-
metavar: 'port_number',
|
|
573
|
-
description: 'A port number',
|
|
574
|
-
parser: Parsers.portNumber,
|
|
575
|
-
}),
|
|
576
524
|
};
|
|
577
525
|
|
|
578
526
|
// ACCESSLOGS COMMAND
|
|
@@ -855,6 +803,7 @@ async function run() {
|
|
|
855
803
|
{
|
|
856
804
|
description: 'Manage the favourite domain name for an application',
|
|
857
805
|
options: [opts.alias, opts.appIdOrName],
|
|
806
|
+
privateOptions: [opts.humanJsonOutputFormat],
|
|
858
807
|
commands: [domainSetFavouriteCommand, domainUnsetFavouriteCommand],
|
|
859
808
|
},
|
|
860
809
|
domain.getFavourite,
|
|
@@ -879,7 +828,7 @@ async function run() {
|
|
|
879
828
|
'domain',
|
|
880
829
|
{
|
|
881
830
|
description: 'Manage domain names for an application',
|
|
882
|
-
privateOptions: [opts.alias, opts.appIdOrName],
|
|
831
|
+
privateOptions: [opts.alias, opts.appIdOrName, opts.humanJsonOutputFormat],
|
|
883
832
|
commands: [
|
|
884
833
|
domainCreateCommand,
|
|
885
834
|
domainFavouriteCommands,
|
|
@@ -1907,7 +1856,7 @@ async function run() {
|
|
|
1907
1856
|
'add',
|
|
1908
1857
|
{
|
|
1909
1858
|
description: 'Add a new TCP redirection to the application',
|
|
1910
|
-
options: [opts.namespace
|
|
1859
|
+
options: [opts.namespace],
|
|
1911
1860
|
},
|
|
1912
1861
|
tcpRedirs.add,
|
|
1913
1862
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clever-tools",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "Command Line Interface for Clever Cloud.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"typecheck": "tsc -p tsconfig.json"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@clevercloud/client": "11.0
|
|
50
|
+
"@clevercloud/client": "11.2.0",
|
|
51
51
|
"@inquirer/prompts": "7.8.4",
|
|
52
52
|
"char-regex": "2.0.2",
|
|
53
53
|
"cliparse": "0.5.0",
|
|
@@ -34,7 +34,7 @@ export async function accessLogs(params) {
|
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
if (format === 'human') {
|
|
37
|
-
Logger.
|
|
37
|
+
Logger.println(styleText('yellow', '/!\\ This feature is in Beta testing phase'));
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
if (format === 'json' && !until) {
|
package/src/commands/domain.js
CHANGED
|
@@ -39,15 +39,24 @@ function getFavouriteDomain({ ownerId, appId }) {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
export async function list(params) {
|
|
42
|
-
const { alias, app: appIdOrName } = params.options;
|
|
42
|
+
const { alias, app: appIdOrName, format } = params.options;
|
|
43
43
|
const { ownerId, appId } = await Application.resolveId(appIdOrName, alias);
|
|
44
44
|
|
|
45
45
|
const app = await getApp({ id: ownerId, appId }).then(sendToApi);
|
|
46
46
|
const favouriteDomain = await getFavouriteDomain({ ownerId, appId });
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
|
|
48
|
+
const domains = app.vhosts.map((vhost) => getDomainObject(vhost.fqdn, favouriteDomain));
|
|
49
|
+
|
|
50
|
+
switch (format) {
|
|
51
|
+
case 'json':
|
|
52
|
+
Logger.printJson(domains);
|
|
53
|
+
break;
|
|
54
|
+
default:
|
|
55
|
+
domains.forEach((domain) => {
|
|
56
|
+
Logger.println(`${domain.isFavourite ? '* ' : ' '}${domain.domainWithPathPrefix}`);
|
|
57
|
+
});
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
51
60
|
}
|
|
52
61
|
|
|
53
62
|
export async function add(params) {
|
|
@@ -61,16 +70,23 @@ export async function add(params) {
|
|
|
61
70
|
}
|
|
62
71
|
|
|
63
72
|
export async function getFavourite(params) {
|
|
64
|
-
const { alias, app: appIdOrName } = params.options;
|
|
73
|
+
const { alias, app: appIdOrName, format } = params.options;
|
|
65
74
|
const { ownerId, appId } = await Application.resolveId(appIdOrName, alias);
|
|
66
75
|
|
|
67
76
|
const favouriteDomain = await getFavouriteDomain({ ownerId, appId });
|
|
68
77
|
|
|
69
|
-
|
|
70
|
-
|
|
78
|
+
switch (format) {
|
|
79
|
+
case 'json':
|
|
80
|
+
const domain = getDomainObject(favouriteDomain, favouriteDomain);
|
|
81
|
+
Logger.printJson(domain);
|
|
82
|
+
break;
|
|
83
|
+
default:
|
|
84
|
+
if (favouriteDomain == null) {
|
|
85
|
+
return Logger.println('No favourite domain set');
|
|
86
|
+
}
|
|
87
|
+
Logger.println(favouriteDomain);
|
|
88
|
+
break;
|
|
71
89
|
}
|
|
72
|
-
|
|
73
|
-
return Logger.println(favouriteDomain);
|
|
74
90
|
}
|
|
75
91
|
|
|
76
92
|
export async function setFavourite(params) {
|
|
@@ -258,6 +274,21 @@ export async function overview(params) {
|
|
|
258
274
|
}
|
|
259
275
|
}
|
|
260
276
|
|
|
277
|
+
function getDomainObject(domainWithPathPrefix, favouriteDomain) {
|
|
278
|
+
const parsed = parseDomain(domainWithPathPrefix, { validateHostname: false });
|
|
279
|
+
return {
|
|
280
|
+
domainWithPathPrefix,
|
|
281
|
+
domain: parsed.domain,
|
|
282
|
+
domainWithoutSuffix: parsed.domainWithoutSuffix,
|
|
283
|
+
hostname: parsed.hostname,
|
|
284
|
+
publicSuffix: parsed.publicSuffix,
|
|
285
|
+
subdomain: parsed.subdomain,
|
|
286
|
+
isApex: parsed.subdomain === '',
|
|
287
|
+
pathPrefix: new URL('https://' + domainWithPathPrefix).pathname,
|
|
288
|
+
isFavourite: domainWithPathPrefix === favouriteDomain,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
261
292
|
/** @param {DomainDiag & { resolvedDnsConfig: ResolveDnsResult }} domainDiag */
|
|
262
293
|
function reportDomainDiagnostics({ hostname, pathPrefix, resolvedDnsConfig, diagDetails, diagSummary }) {
|
|
263
294
|
const validDiags = diagDetails.filter((diag) => diag.code === 'valid-a');
|
|
@@ -274,20 +305,20 @@ function reportDomainDiagnostics({ hostname, pathPrefix, resolvedDnsConfig, diag
|
|
|
274
305
|
|
|
275
306
|
switch (diagSummary) {
|
|
276
307
|
case 'managed':
|
|
277
|
-
printlnWithIndent(styleText('green', '✔ Managed by Clever Cloud'), 2);
|
|
278
|
-
printlnWithIndent('ⓘ cleverapps.io domains should only be used for testing purposes', 2);
|
|
308
|
+
Logger.printlnWithIndent(styleText('green', '✔ Managed by Clever Cloud'), 2);
|
|
309
|
+
Logger.printlnWithIndent('ⓘ cleverapps.io domains should only be used for testing purposes', 2);
|
|
279
310
|
break;
|
|
280
311
|
case 'no-config':
|
|
281
|
-
printlnWithIndent(styleText('red', '✘ No DNS configuration found'), 2);
|
|
312
|
+
Logger.printlnWithIndent(styleText('red', '✘ No DNS configuration found'), 2);
|
|
282
313
|
break;
|
|
283
314
|
case 'valid':
|
|
284
|
-
printlnWithIndent(styleText('green', '✔ Your configuration is valid'), 2);
|
|
315
|
+
Logger.printlnWithIndent(styleText('green', '✔ Your configuration is valid'), 2);
|
|
285
316
|
break;
|
|
286
317
|
case 'invalid':
|
|
287
|
-
printlnWithIndent(styleText('red', '✘ Something is wrong with your configuration'), 2);
|
|
318
|
+
Logger.printlnWithIndent(styleText('red', '✘ Something is wrong with your configuration'), 2);
|
|
288
319
|
break;
|
|
289
320
|
case 'incomplete':
|
|
290
|
-
printlnWithIndent(styleText('yellow', '⚠ Your configuration is incomplete'), 2);
|
|
321
|
+
Logger.printlnWithIndent(styleText('yellow', '⚠ Your configuration is incomplete'), 2);
|
|
291
322
|
break;
|
|
292
323
|
}
|
|
293
324
|
|
|
@@ -296,15 +327,15 @@ function reportDomainDiagnostics({ hostname, pathPrefix, resolvedDnsConfig, diag
|
|
|
296
327
|
Logger.println('');
|
|
297
328
|
validDiags.forEach((diag) => {
|
|
298
329
|
const source = hasCnameRecord ? `(from CNAME ${resolvedDnsConfig.cnameRecords[0]}.)` : '';
|
|
299
|
-
printlnWithIndent(`${diag.record.value} ${styleText('green', '✔ A Record OK')} ${source}`, 2);
|
|
330
|
+
Logger.printlnWithIndent(`${diag.record.value} ${styleText('green', '✔ A Record OK')} ${source}`, 2);
|
|
300
331
|
});
|
|
301
332
|
}
|
|
302
333
|
|
|
303
334
|
// Replace A with CNAME
|
|
304
335
|
if (diagSummary === 'valid' && suggestedCname != null) {
|
|
305
336
|
Logger.println('');
|
|
306
|
-
printlnWithIndent('ⓘ You can replace your A records with this CNAME:', 2);
|
|
307
|
-
printlnWithIndent(suggestedCname.record.value, 6);
|
|
337
|
+
Logger.printlnWithIndent('ⓘ You can replace your A records with this CNAME:', 2);
|
|
338
|
+
Logger.printlnWithIndent(suggestedCname.record.value, 6);
|
|
308
339
|
}
|
|
309
340
|
|
|
310
341
|
// Replace A Records with CNAME
|
|
@@ -312,51 +343,51 @@ function reportDomainDiagnostics({ hostname, pathPrefix, resolvedDnsConfig, diag
|
|
|
312
343
|
const cnameToUse = suggestedCname != null ? suggestedCname.record.value : missingCname.record.value;
|
|
313
344
|
|
|
314
345
|
Logger.println('');
|
|
315
|
-
printlnWithIndent('⇄ Replace your A records with this CNAME:', 2);
|
|
316
|
-
printlnWithIndent(cnameToUse, 6);
|
|
346
|
+
Logger.printlnWithIndent('⇄ Replace your A records with this CNAME:', 2);
|
|
347
|
+
Logger.printlnWithIndent(cnameToUse, 6);
|
|
317
348
|
Logger.println('');
|
|
318
|
-
printlnWithIndent('or:', 2);
|
|
349
|
+
Logger.printlnWithIndent('or:', 2);
|
|
319
350
|
}
|
|
320
351
|
|
|
321
352
|
// Replace unknown CNAME with missing CNAME
|
|
322
353
|
if (diagSummary === 'invalid' && missingCname != null && unknownCname != null) {
|
|
323
354
|
Logger.println('');
|
|
324
|
-
printlnWithIndent('➖Remove this CNAME record:', 2);
|
|
325
|
-
printlnWithIndent(unknownCname.record.value + '. ', 6);
|
|
355
|
+
Logger.printlnWithIndent('➖Remove this CNAME record:', 2);
|
|
356
|
+
Logger.printlnWithIndent(unknownCname.record.value + '. ', 6);
|
|
326
357
|
Logger.println('');
|
|
327
|
-
printlnWithIndent('➕Add this CNAME record instead:', 2);
|
|
328
|
-
printlnWithIndent(missingCname.record.value, 6);
|
|
358
|
+
Logger.printlnWithIndent('➕Add this CNAME record instead:', 2);
|
|
359
|
+
Logger.printlnWithIndent(missingCname.record.value, 6);
|
|
329
360
|
|
|
330
361
|
if (hasARecords) {
|
|
331
362
|
Logger.println('');
|
|
332
|
-
printlnWithIndent('or:', 2);
|
|
363
|
+
Logger.printlnWithIndent('or:', 2);
|
|
333
364
|
}
|
|
334
365
|
}
|
|
335
366
|
|
|
336
367
|
// Add CNAME
|
|
337
368
|
if (diagSummary === 'no-config' && missingCname != null) {
|
|
338
369
|
Logger.println('');
|
|
339
|
-
printlnWithIndent('➕Add this CNAME record:', 2);
|
|
340
|
-
printlnWithIndent(missingCname.record.value, 6);
|
|
370
|
+
Logger.printlnWithIndent('➕Add this CNAME record:', 2);
|
|
371
|
+
Logger.printlnWithIndent(missingCname.record.value, 6);
|
|
341
372
|
}
|
|
342
373
|
|
|
343
374
|
// Remove Unknown records
|
|
344
375
|
if (unknownDiags.length > 0) {
|
|
345
376
|
Logger.println('');
|
|
346
|
-
printlnWithIndent('➖Remove these A records:', 2);
|
|
377
|
+
Logger.printlnWithIndent('➖Remove these A records:', 2);
|
|
347
378
|
|
|
348
379
|
unknownDiags.forEach((diag) => {
|
|
349
380
|
const source = hasCnameRecord ? `(from CNAME ${resolvedDnsConfig.cnameRecords[0]}.)` : '';
|
|
350
|
-
printlnWithIndent(`${diag.record.value} ${source}`, 6);
|
|
381
|
+
Logger.printlnWithIndent(`${diag.record.value} ${source}`, 6);
|
|
351
382
|
});
|
|
352
383
|
}
|
|
353
384
|
|
|
354
385
|
// Add missing A records
|
|
355
386
|
if (missingDiags.length > 0) {
|
|
356
387
|
Logger.println('');
|
|
357
|
-
printlnWithIndent('➕Add these A records:', 2);
|
|
388
|
+
Logger.printlnWithIndent('➕Add these A records:', 2);
|
|
358
389
|
|
|
359
|
-
missingDiags.forEach((missingDiag) => printlnWithIndent(missingDiag.record.value, 6));
|
|
390
|
+
missingDiags.forEach((missingDiag) => Logger.printlnWithIndent(missingDiag.record.value, 6));
|
|
360
391
|
}
|
|
361
392
|
}
|
|
362
393
|
|
|
@@ -375,16 +406,6 @@ function getParsedDomains(vhosts) {
|
|
|
375
406
|
});
|
|
376
407
|
}
|
|
377
408
|
|
|
378
|
-
/**
|
|
379
|
-
* Prints a line of text with specified indentation.
|
|
380
|
-
*
|
|
381
|
-
* @param {string} text - The text to be printed.
|
|
382
|
-
* @param {number} indentLevel - The number of spaces to indent the text.
|
|
383
|
-
*/
|
|
384
|
-
function printlnWithIndent(text, indentLevel) {
|
|
385
|
-
Logger.println(' '.repeat(indentLevel) + text);
|
|
386
|
-
}
|
|
387
|
-
|
|
388
409
|
function recursiveSort(obj) {
|
|
389
410
|
if (typeof obj === 'object' && obj.appId != null) {
|
|
390
411
|
return obj;
|
|
@@ -407,15 +428,15 @@ function recursiveSort(obj) {
|
|
|
407
428
|
|
|
408
429
|
function recursiveDisplay(obj, indentLevel = 0) {
|
|
409
430
|
if (typeof obj === 'object' && obj.appId != null) {
|
|
410
|
-
printlnWithIndent(`${obj.ownerName} | ${obj.appName} (${obj.appVariantSlug})`, indentLevel);
|
|
411
|
-
printlnWithIndent(styleText('blue', obj.appConsoleUrl), indentLevel);
|
|
431
|
+
Logger.printlnWithIndent(`${obj.ownerName} | ${obj.appName} (${obj.appVariantSlug})`, indentLevel);
|
|
432
|
+
Logger.printlnWithIndent(styleText('blue', obj.appConsoleUrl), indentLevel);
|
|
412
433
|
return;
|
|
413
434
|
}
|
|
414
435
|
|
|
415
436
|
for (const [propertyPath, subObj] of Object.entries(obj)) {
|
|
416
437
|
if (propertyPath !== '/') {
|
|
417
438
|
Logger.println('');
|
|
418
|
-
printlnWithIndent(styleText('yellow', propertyPath), indentLevel);
|
|
439
|
+
Logger.printlnWithIndent(styleText('yellow', propertyPath), indentLevel);
|
|
419
440
|
recursiveDisplay(subObj, indentLevel + 2);
|
|
420
441
|
} else {
|
|
421
442
|
recursiveDisplay(subObj, indentLevel);
|
package/src/commands/emails.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
todo_addEmailAddress as addEmailAddress,
|
|
3
|
-
todo_getEmailAddresses as getEmailAddresses,
|
|
4
3
|
todo_removeEmailAddress as removeEmailAddress,
|
|
5
4
|
} from '@clevercloud/client/esm/api/v2/user.js';
|
|
6
5
|
import { confirm } from '../lib/prompts.js';
|
|
7
6
|
import { styleText } from '../lib/style-text.js';
|
|
8
7
|
import { Logger } from '../logger.js';
|
|
8
|
+
import { getUserEmailAddresses } from '../models/emails.js';
|
|
9
9
|
import { sendToApi } from '../models/send-to-api.js';
|
|
10
|
-
import * as User from '../models/user.js';
|
|
11
10
|
import { openBrowser } from '../models/utils.js';
|
|
12
11
|
|
|
13
12
|
/**
|
|
@@ -155,16 +154,3 @@ export async function removeAllSecondary(params) {
|
|
|
155
154
|
export function openConsole() {
|
|
156
155
|
return openBrowser('/users/me/emails', 'Opening the email addresses management page of the Console in your browser');
|
|
157
156
|
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Get the primary and secondary email addresses of the current user
|
|
161
|
-
* @returns {Promise<{ primary: string, secondary: string[] }>} The primary and secondary email addresses of the current user
|
|
162
|
-
*/
|
|
163
|
-
async function getUserEmailAddresses() {
|
|
164
|
-
const currentUser = await User.getCurrent();
|
|
165
|
-
const secondaryAddresses = await getEmailAddresses().then(sendToApi);
|
|
166
|
-
return {
|
|
167
|
-
primary: currentUser.email,
|
|
168
|
-
secondary: secondaryAddresses.sort(),
|
|
169
|
-
};
|
|
170
|
-
}
|
package/src/commands/link.js
CHANGED
|
@@ -6,15 +6,15 @@ export async function link(params) {
|
|
|
6
6
|
const [app] = params.args;
|
|
7
7
|
const { org: orgaIdOrName, alias } = params.options;
|
|
8
8
|
|
|
9
|
+
let appConfigEntry;
|
|
9
10
|
if (app.app_id != null && orgaIdOrName != null) {
|
|
10
11
|
Logger.warn("You've specified a unique application ID, organisation option will be ignored");
|
|
11
|
-
await Application.linkRepo(app, null, alias);
|
|
12
|
+
appConfigEntry = await Application.linkRepo(app, null, alias);
|
|
12
13
|
} else {
|
|
13
|
-
await Application.linkRepo(app, orgaIdOrName, alias);
|
|
14
|
+
appConfigEntry = await Application.linkRepo(app, orgaIdOrName, alias);
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
const linkedMessage = alias ? ` to local alias ${styleText('green', alias)}` : '';
|
|
17
17
|
Logger.printSuccess(
|
|
18
|
-
`Application ${styleText('green',
|
|
18
|
+
`Application ${styleText('green', appConfigEntry.app_id)} has been successfully linked to local alias ${styleText('green', appConfigEntry.alias)}!`,
|
|
19
19
|
);
|
|
20
20
|
}
|
package/src/commands/ssh-keys.js
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
todo_addSshKey as addSshKey,
|
|
3
|
-
todo_getSshKeys as getSshKeys,
|
|
4
|
-
todo_removeSshKey as removeSshKey,
|
|
5
|
-
} from '@clevercloud/client/esm/api/v2/user.js';
|
|
1
|
+
import { todo_addSshKey as addSshKey, todo_removeSshKey as removeSshKey } from '@clevercloud/client/esm/api/v2/user.js';
|
|
6
2
|
import dedent from 'dedent';
|
|
7
3
|
import fs from 'node:fs';
|
|
8
4
|
import { confirm } from '../lib/prompts.js';
|
|
9
5
|
import { styleText } from '../lib/style-text.js';
|
|
10
6
|
import { Logger } from '../logger.js';
|
|
11
7
|
import { sendToApi } from '../models/send-to-api.js';
|
|
8
|
+
import { getUserSshKeys } from '../models/ssh-keys.js';
|
|
12
9
|
import { openBrowser } from '../models/utils.js';
|
|
13
10
|
|
|
14
11
|
/**
|
|
@@ -145,11 +142,3 @@ export async function removeAll(params) {
|
|
|
145
142
|
export function openConsole() {
|
|
146
143
|
return openBrowser('/users/me/ssh-keys', 'Opening the SSH keys management page of the Console in your browser');
|
|
147
144
|
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* @return {Promise<Array<{ name: string, key: string, fingerprint: string }>>}
|
|
151
|
-
*/
|
|
152
|
-
async function getUserSshKeys() {
|
|
153
|
-
const rawKeys = await getSshKeys().then(sendToApi);
|
|
154
|
-
return rawKeys.sort((a, b) => a.name.localeCompare(b.name));
|
|
155
|
-
}
|
package/src/commands/status.js
CHANGED
|
@@ -27,6 +27,7 @@ export async function status(params) {
|
|
|
27
27
|
: styleText(['bold', 'red'], 'stopped');
|
|
28
28
|
|
|
29
29
|
Logger.println(`${status.name}: ${statusMessage}`);
|
|
30
|
+
Logger.println(`Type: ${status.type.name}`);
|
|
30
31
|
Logger.println(`Executed as: ${styleText('bold', status.lifetime)}`);
|
|
31
32
|
if (status.deploymentInProgress) {
|
|
32
33
|
Logger.println(
|
|
@@ -67,6 +68,10 @@ function computeStatus(instances, app) {
|
|
|
67
68
|
const status = {
|
|
68
69
|
id: app.id,
|
|
69
70
|
name: app.name,
|
|
71
|
+
type: {
|
|
72
|
+
name: app.instance.variant.name,
|
|
73
|
+
slug: app.instance.variant.slug,
|
|
74
|
+
},
|
|
70
75
|
lifetime: app.instance.lifetime,
|
|
71
76
|
status: isUp ? 'running' : 'stopped',
|
|
72
77
|
commit: upCommit,
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { addTcpRedir, getTcpRedirs, removeTcpRedir } from '@clevercloud/client/esm/api/v2/application.js';
|
|
2
|
-
import { confirm } from '../lib/prompts.js';
|
|
3
|
-
import { styleText } from '../lib/style-text.js';
|
|
4
2
|
import { Logger } from '../logger.js';
|
|
5
3
|
import * as Application from '../models/application.js';
|
|
6
4
|
import * as Namespaces from '../models/namespaces.js';
|
|
@@ -64,37 +62,10 @@ export async function list(params) {
|
|
|
64
62
|
}
|
|
65
63
|
}
|
|
66
64
|
|
|
67
|
-
async function acceptPayment(result, skipConfirmation) {
|
|
68
|
-
if (!skipConfirmation) {
|
|
69
|
-
result.lines.forEach(({ description, VAT, price }) =>
|
|
70
|
-
Logger.println(`${description}\tVAT: ${VAT}%\tPrice: ${price}€`),
|
|
71
|
-
);
|
|
72
|
-
Logger.println(`Total (without taxes): ${result.totalHT}€`);
|
|
73
|
-
Logger.println(styleText('bold', `Total (with taxes): ${result.totalTTC}€`));
|
|
74
|
-
|
|
75
|
-
await confirm(
|
|
76
|
-
`You're about to pay ${result.totalTTC}€, confirm?`,
|
|
77
|
-
'No confirmation, aborting TCP redirection creation',
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
65
|
export async function add(params) {
|
|
83
|
-
const { alias, app: appIdOrName, namespace
|
|
66
|
+
const { alias, app: appIdOrName, namespace } = params.options;
|
|
84
67
|
const { ownerId, appId } = await Application.resolveId(appIdOrName, alias);
|
|
85
|
-
|
|
86
|
-
const { port } = await addTcpRedir({ id: ownerId, appId }, { namespace })
|
|
87
|
-
.then(sendToApi)
|
|
88
|
-
.catch((error) => {
|
|
89
|
-
if (error.status === 402) {
|
|
90
|
-
return acceptPayment(error.response.body, skipConfirmation).then(() => {
|
|
91
|
-
return addTcpRedir({ id: ownerId, appId, payment: 'accepted' }, { namespace }).then(sendToApi);
|
|
92
|
-
});
|
|
93
|
-
} else {
|
|
94
|
-
throw error;
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
|
|
68
|
+
const { port } = await addTcpRedir({ id: ownerId, appId }, { namespace }).then(sendToApi);
|
|
98
69
|
Logger.println('Successfully added tcp redirection on port: ' + port);
|
|
99
70
|
}
|
|
100
71
|
|
package/src/commands/tokens.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import dedent from 'dedent';
|
|
2
2
|
import { createApiToken, deleteApiToken, listApiTokens } from '../clever-client/auth-bridge.js';
|
|
3
|
+
import { formatDate } from '../lib/format-date.js';
|
|
3
4
|
import { promptSecret } from '../lib/prompts.js';
|
|
4
5
|
import { styleText } from '../lib/style-text.js';
|
|
5
6
|
import { Logger } from '../logger.js';
|
|
@@ -141,7 +142,3 @@ export async function revoke(params) {
|
|
|
141
142
|
|
|
142
143
|
Logger.println(styleText('green', '✔'), 'API token successfully revoked!');
|
|
143
144
|
}
|
|
144
|
-
|
|
145
|
-
function formatDate(dateInput) {
|
|
146
|
-
return new Date(dateInput).toISOString().substring(0, 16).replace('T', ' ');
|
|
147
|
-
}
|
package/src/logger.js
CHANGED
|
@@ -58,6 +58,16 @@ export const Logger = _(['debug', 'info', 'warn', 'error'])
|
|
|
58
58
|
// No decoration for Logger.println
|
|
59
59
|
Logger.println = console.log;
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Prints a line of text with specified indentation.
|
|
63
|
+
*
|
|
64
|
+
* @param {string} text - The text to be printed.
|
|
65
|
+
* @param {number} indentLevel - The number of spaces to indent the text.
|
|
66
|
+
*/
|
|
67
|
+
Logger.printlnWithIndent = (text, indentLevel) => {
|
|
68
|
+
Logger.println(' '.repeat(indentLevel) + text);
|
|
69
|
+
};
|
|
70
|
+
|
|
61
71
|
// Logger for success with a green check before the message
|
|
62
72
|
Logger.printSuccess = (message) => console.log(`${styleText(['bold', 'green'], '✓')} ${message}`);
|
|
63
73
|
|
|
@@ -2,6 +2,7 @@ import _ from 'lodash';
|
|
|
2
2
|
import { promises as fs } from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { slugify } from '../lib/slugify.js';
|
|
5
|
+
import { styleText } from '../lib/style-text.js';
|
|
5
6
|
import { Logger } from '../logger.js';
|
|
6
7
|
import { conf } from './configuration.js';
|
|
7
8
|
import * as User from './user.js';
|
|
@@ -39,14 +40,17 @@ export async function addLinkedApplication(appData, alias, ignoreParentConfig) {
|
|
|
39
40
|
};
|
|
40
41
|
|
|
41
42
|
const isPresent = currentConfig.apps.find((app) => app.app_id === appEntry.app_id) != null;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
currentConfig.apps.push(appEntry);
|
|
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
47
|
}
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
currentConfig.apps.push(appEntry);
|
|
50
|
+
|
|
51
|
+
return persistConfig(currentConfig).then(() => {
|
|
52
|
+
return appEntry;
|
|
53
|
+
});
|
|
50
54
|
}
|
|
51
55
|
|
|
52
56
|
export async function removeLinkedApplication({ appId, alias }) {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { todo_getEmailAddresses as getEmailAddresses } from '@clevercloud/client/esm/api/v2/user.js';
|
|
2
|
+
import { sendToApi } from './send-to-api.js';
|
|
3
|
+
import * as User from './user.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Get the primary and secondary email addresses of the current user
|
|
7
|
+
* @returns {Promise<{ primary: string, secondary: string[] }>} The primary and secondary email addresses of the current user
|
|
8
|
+
*/
|
|
9
|
+
export async function getUserEmailAddresses() {
|
|
10
|
+
const currentUser = await User.getCurrent();
|
|
11
|
+
const secondaryAddresses = await getEmailAddresses().then(sendToApi);
|
|
12
|
+
return {
|
|
13
|
+
primary: currentUser.email,
|
|
14
|
+
secondary: secondaryAddresses.sort(),
|
|
15
|
+
};
|
|
16
|
+
}
|
package/src/models/log-v4.js
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { todo_getSshKeys as getSshKeys } from '@clevercloud/client/esm/api/v2/user.js';
|
|
2
|
+
import { sendToApi } from './send-to-api.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @return {Promise<Array<{ name: string, key: string, fingerprint: string }>>}
|
|
6
|
+
*/
|
|
7
|
+
export async function getUserSshKeys() {
|
|
8
|
+
const rawKeys = await getSshKeys().then(sendToApi);
|
|
9
|
+
return rawKeys.sort((a, b) => a.name.localeCompare(b.name));
|
|
10
|
+
}
|