clever-tools 4.0.2 → 4.2.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 CHANGED
@@ -79,6 +79,10 @@ process.stdout.on('error', (error) => {
79
79
  const cliparseCommand = cliparse.command;
80
80
 
81
81
  cliparse.command = function (name, options, commandFunction) {
82
+ if (commandFunction == null) {
83
+ return cliparseCommand(name, options);
84
+ }
85
+
82
86
  return cliparseCommand(name, options, (...args) => {
83
87
  const promise = commandFunction(...args);
84
88
  handleCommandPromise(promise);
@@ -103,10 +107,6 @@ async function run() {
103
107
  }),
104
108
  apiTokenId: cliparse.argument('api-token-id', { description: 'API token ID' }),
105
109
  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
110
  ngLabel: cliparse.argument('ng-label', {
111
111
  description: 'Network Group label',
112
112
  parser: Parsers.ngResourceType,
@@ -115,9 +115,6 @@ async function run() {
115
115
  description: 'Network Group ID or label',
116
116
  parser: Parsers.ngResourceType,
117
117
  }),
118
- ngDescription: cliparse.argument('ng-description', {
119
- description: 'Network Group description',
120
- }),
121
118
  ngExternalPeerLabel: cliparse.argument('external-peer-label', {
122
119
  description: 'External peer label',
123
120
  parser: Parsers.ngResourceType,
@@ -127,12 +124,16 @@ async function run() {
127
124
  parser: Parsers.ngResourceType,
128
125
  }),
129
126
  ngAnyIdOrLabel: cliparse.argument('id-or-label', {
130
- description: 'ID or Label of a Network group, a member or an (external) peer',
127
+ description: 'ID or Label of a Network Group, a member or an (external) peer',
128
+ parser: Parsers.ngResourceType,
129
+ }),
130
+ ngResourceId: cliparse.argument('id', {
131
+ description: 'ID of a resource to (un)link to a Network Group',
131
132
  parser: Parsers.ngResourceType,
132
133
  }),
133
134
  wgPublicKey: cliparse.argument('public-key', {
134
135
  metavar: 'public_key',
135
- description: 'Wireguard public key of the external peer to link to a Network Group',
136
+ description: 'WireGuard public key of the external peer to link to a Network Group',
136
137
  }),
137
138
  email: cliparse.argument('email', {
138
139
  description: 'Email address',
@@ -213,18 +214,9 @@ async function run() {
213
214
  ngMembersIdsToLink: cliparse.option('link', {
214
215
  metavar: 'members_ids',
215
216
  description:
216
- "Comma separated list of members IDs to link to a Network Group ('app_xxx', 'addon_xxx', 'external_xxx')",
217
+ 'Comma separated list of members IDs to link to a Network Group (app_xxx, external_xxx, mysql_xxx, postgresql_xxx, redis_xxx, etc.)',
217
218
  parser: Parsers.commaSeparated,
218
219
  }),
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
220
  ngResourceType: cliparse.option('type', {
229
221
  metavar: 'type',
230
222
  description: 'Type of resource to look for (NetworkGroup, Member, CleverPeer, ExternalPeer)',
@@ -265,10 +257,6 @@ async function run() {
265
257
  metavar: 'TEXT',
266
258
  description: 'Get only domains containing the provided text',
267
259
  }),
268
- naturalName: cliparse.flag('natural-name', {
269
- aliases: ['n'],
270
- description: 'Show the application names or aliases if possible',
271
- }),
272
260
  before: cliparse.option('before', {
273
261
  metavar: 'before',
274
262
  aliases: ['until'],
@@ -294,10 +282,6 @@ async function run() {
294
282
  metavar: 'tag',
295
283
  description: 'Tag to push (none by default)',
296
284
  }),
297
- databaseId: cliparse.option('database-id', {
298
- metavar: 'database_id',
299
- description: 'The Database ID (e.g.: postgresql_xxx)',
300
- }),
301
285
  deploymentId: cliparse.option('deployment-id', {
302
286
  metavar: 'deployment_id',
303
287
  description: 'Fetch logs for a given deployment',
@@ -534,45 +518,17 @@ async function run() {
534
518
  aliases: ['y'],
535
519
  description: 'Skip confirmation and remove all SSH keys directly',
536
520
  }),
537
- confirmTcpRedirCreation: cliparse.flag('yes', {
538
- aliases: ['y'],
539
- description: 'Skip confirmation even if the TCP redirection is not free',
540
- }),
541
521
  yes: cliparse.flag('yes', {
542
522
  aliases: ['y'],
543
523
  description: 'Skip confirmation',
544
524
  }),
545
525
  jsonFormat: cliparse.flag('json', { aliases: ['j'], description: 'Show result in JSON format' }),
546
526
  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
527
  optTags: cliparse.option('tags', {
560
528
  metavar: 'tags',
561
529
  description: 'List of tags, separated by a comma',
562
530
  parser: Parsers.tags,
563
531
  }),
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
532
  };
577
533
 
578
534
  // ACCESSLOGS COMMAND
@@ -855,6 +811,7 @@ async function run() {
855
811
  {
856
812
  description: 'Manage the favourite domain name for an application',
857
813
  options: [opts.alias, opts.appIdOrName],
814
+ privateOptions: [opts.humanJsonOutputFormat],
858
815
  commands: [domainSetFavouriteCommand, domainUnsetFavouriteCommand],
859
816
  },
860
817
  domain.getFavourite,
@@ -879,7 +836,7 @@ async function run() {
879
836
  'domain',
880
837
  {
881
838
  description: 'Manage domain names for an application',
882
- privateOptions: [opts.alias, opts.appIdOrName],
839
+ privateOptions: [opts.alias, opts.appIdOrName, opts.humanJsonOutputFormat],
883
840
  commands: [
884
841
  domainCreateCommand,
885
842
  domainFavouriteCommands,
@@ -1464,16 +1421,18 @@ async function run() {
1464
1421
  const ngLinkCommand = cliparse.command(
1465
1422
  'link',
1466
1423
  {
1467
- description: 'Link an application or a database add-on by its ID to a Network Group',
1468
- args: [args.ngAnyIdOrLabel, args.ngIdOrLabel],
1424
+ description:
1425
+ 'Link a resource by its ID (app_xxx, external_xxx, mysql_xxx, postgresql_xxx, redis_xxx, etc.) to a Network Group',
1426
+ args: [args.ngResourceId, args.ngIdOrLabel],
1469
1427
  },
1470
1428
  ng.linkToNg,
1471
1429
  );
1472
1430
  const ngUnlinkCommand = cliparse.command(
1473
1431
  'unlink',
1474
1432
  {
1475
- description: 'Unlink an application or a database add-on by its ID from a Network Group',
1476
- args: [args.ngAnyIdOrLabel, args.ngIdOrLabel],
1433
+ description:
1434
+ 'Unlink a resource by its ID (app_xxx, external_xxx, mysql_xxx, postgresql_xxx, redis_xxx, etc.) from a Network Group',
1435
+ args: [args.ngResourceId, args.ngIdOrLabel],
1477
1436
  },
1478
1437
  ng.unlinkFromNg,
1479
1438
  );
@@ -1489,7 +1448,7 @@ async function run() {
1489
1448
  const ngGetConfigCommand = cliparse.command(
1490
1449
  'get-config',
1491
1450
  {
1492
- description: 'Get the Wireguard configuration of a peer in a Network Group',
1451
+ description: 'Get the WireGuard configuration of a peer in a Network Group',
1493
1452
  args: [args.ngExternalIdOrLabel, args.ngIdOrLabel],
1494
1453
  options: [opts.humanJsonOutputFormat],
1495
1454
  },
@@ -1907,7 +1866,7 @@ async function run() {
1907
1866
  'add',
1908
1867
  {
1909
1868
  description: 'Add a new TCP redirection to the application',
1910
- options: [opts.namespace, opts.confirmTcpRedirCreation],
1869
+ options: [opts.namespace],
1911
1870
  },
1912
1871
  tcpRedirs.add,
1913
1872
  );
@@ -2028,18 +1987,10 @@ async function run() {
2028
1987
  },
2029
1988
  database.listBackups,
2030
1989
  );
2031
- const databaseCommand = cliparse.command(
2032
- 'database',
2033
- {
2034
- description: 'List available databases',
2035
- commands: [backupsCommand],
2036
- },
2037
- async () => {
2038
- console.info('This command is not available, you can try the following commands:');
2039
- console.info('clever database backups');
2040
- console.info('clever database backups download');
2041
- },
2042
- );
1990
+ const databaseCommand = cliparse.command('database', {
1991
+ description: 'Manage databases and backups',
1992
+ commands: [backupsCommand],
1993
+ });
2043
1994
 
2044
1995
  // Patch help command description
2045
1996
  cliparseCommands.helpCommand.description = 'Display help about the Clever Cloud CLI';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clever-tools",
3
- "version": "4.0.2",
3
+ "version": "4.2.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.1",
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.warn(styleText('yellow', '/!\\ Access Logs feature is in Alpha testing phase'));
37
+ Logger.println(styleText('yellow', '/!\\ This feature is in Beta testing phase'));
38
38
  }
39
39
 
40
40
  if (format === 'json' && !until) {
@@ -3,6 +3,7 @@ import { styleText } from '../lib/style-text.js';
3
3
  import { getAllEnvVars } from '@clevercloud/client/esm/api/v2/addon.js';
4
4
  import { toNameEqualsValueString } from '@clevercloud/client/esm/utils/env-vars.js';
5
5
  import dedent from 'dedent';
6
+ import { getOperator } from '../clever-client/operators.js';
6
7
  import { formatTable } from '../format-table.js';
7
8
  import { Logger } from '../logger.js';
8
9
  import * as Addon from '../models/addon.js';
@@ -53,6 +54,45 @@ export async function list(params) {
53
54
  }
54
55
  }
55
56
 
57
+ const ADDON_PROVIDERS = {
58
+ keycloak: {
59
+ name: 'Keycloak',
60
+ isOperator: true,
61
+ postCreateInstructions: `Learn more about Keycloak on Clever Cloud: ${conf.DOC_URL}/addons/keycloak/`,
62
+ },
63
+ kv: {
64
+ name: 'Materia KV',
65
+ isOperator: false,
66
+ status: 'beta',
67
+ postCreateInstructions: (addonId) => dedent`
68
+ ${styleText('yellow', "You can easily use Materia KV with 'redis-cli', with such commands:")}
69
+ ${styleText('blue', `source <(clever addon env ${addonId} -F shell)`)}
70
+ ${styleText('blue', 'redis-cli -h $KV_HOST -p $KV_PORT --tls')}
71
+ Learn more about Materia KV on Clever Cloud: ${conf.DOC_URL}/addons/materia-kv/
72
+ `,
73
+ },
74
+ 'addon-matomo': {
75
+ name: 'Matomo',
76
+ isOperator: true,
77
+ postCreateInstructions: `Learn more about Matomo on Clever Cloud: ${conf.DOC_URL}/addons/matomo/`,
78
+ },
79
+ metabase: {
80
+ name: 'Metabase',
81
+ isOperator: true,
82
+ postCreateInstructions: `Learn more about Metabase on Clever Cloud: ${conf.DOC_URL}/addons/metabase/`,
83
+ },
84
+ otoroshi: {
85
+ name: 'Otoroshi with LLM',
86
+ isOperator: true,
87
+ postCreateInstructions: `Learn more about Otoroshi with LLM on Clever Cloud: ${conf.DOC_URL}/addons/otoroshi/`,
88
+ },
89
+ 'addon-pulsar': {
90
+ name: 'Pulsar',
91
+ isOperator: false,
92
+ postCreateInstructions: `Learn more about Pulsar on Clever Cloud: ${conf.DOC_URL}/addons/pulsar/`,
93
+ },
94
+ };
95
+
56
96
  export async function create(params) {
57
97
  const [providerName, name] = params.args;
58
98
  const {
@@ -62,23 +102,21 @@ export async function create(params) {
62
102
  yes: skipConfirmation,
63
103
  org: orgaIdOrName,
64
104
  format,
105
+ 'addon-version': version,
106
+ option: addonOptions,
65
107
  } = params.options;
66
- const version = params.options['addon-version'];
67
- const addonOptions = parseAddonOptions(params.options.option);
68
-
69
- const ownerId = orgaIdOrName != null ? await Organisation.getId(orgaIdOrName) : await User.getCurrentId();
70
108
 
71
109
  const addonToCreate = {
72
- ownerId,
73
110
  name,
74
111
  providerName,
75
112
  planName,
76
113
  region,
77
114
  skipConfirmation,
78
115
  version,
79
- addonOptions,
116
+ addonOptions: parseAddonOptions(addonOptions),
80
117
  };
81
118
 
119
+ const ownerId = orgaIdOrName != null ? await Organisation.getId(orgaIdOrName) : await User.getCurrentId();
82
120
  if (linkedAppAlias != null) {
83
121
  const linkedAppData = await AppConfig.getAppDetails({ alias: linkedAppAlias });
84
122
  if (orgaIdOrName != null && linkedAppData.ownerId !== ownerId && format === 'human') {
@@ -86,163 +124,98 @@ export async function create(params) {
86
124
  'The specified application does not belong to the specified organisation. Ignoring the `--org` option',
87
125
  );
88
126
  }
89
- const newAddon = await Addon.create({
90
- ...addonToCreate,
91
- ownerId: linkedAppData.ownerId,
92
- });
93
- await Addon.link(linkedAppData.ownerId, linkedAppData.appId, { addon_id: newAddon.id });
94
- displayAddon(
95
- format,
96
- newAddon,
97
- providerName,
98
- `Add-on created and linked to application ${linkedAppAlias} successfully!`,
99
- );
127
+ addonToCreate.ownerId = linkedAppData.ownerId;
100
128
  } else {
101
- const newAddon = await Addon.create(addonToCreate);
102
- displayAddon(format, newAddon, providerName, 'Add-on created successfully!');
129
+ addonToCreate.ownerId = ownerId;
103
130
  }
104
- }
105
131
 
106
- function displayAddon(format, addon, providerName, message) {
107
- const PROVIDERS_WITH_URL = {
108
- keycloak: {
109
- name: 'Keycloak',
110
- urlEnv: 'CC_KEYCLOAK_URL',
111
- },
112
- 'addon-matomo': {
113
- name: 'Matomo',
114
- urlEnv: 'MATOMO_URL',
115
- },
116
- metabase: {
117
- name: 'Metabase',
118
- urlEnv: 'METABASE_URL',
119
- },
120
- otoroshi: {
121
- name: 'Otoroshi with LLM',
122
- urlEnv: 'CC_OTOROSHI_URL',
123
- },
124
- };
132
+ const newAddon = await Addon.create(addonToCreate);
125
133
 
126
- const WIP_PROVIDERS = {
127
- keycloak: {
128
- status: '',
129
- postCreateInstructions: dedent`
130
- Learn more about Keycloak on Clever Cloud: ${conf.DOC_URL}/addons/keycloak/,
131
- `,
132
- },
133
- kv: {
134
- status: 'alpha',
135
- postCreateInstructions: dedent`
136
- ${styleText('yellow', "You can easily use Materia KV with 'redis-cli', with such commands:")}
137
- ${styleText('blue', `source <(clever addon env ${addon.id} -F shell)`)}
138
- ${styleText('blue', 'redis-cli -h $KV_HOST -p $KV_PORT --tls')}
139
- Learn more about Materia KV on Clever Cloud: ${conf.DOC_URL}/addons/materia-kv/
140
- `,
141
- },
142
- 'addon-matomo': {
143
- status: '',
144
- postCreateInstructions: dedent`
145
- Learn more about Matomo on Clever Cloud: ${conf.DOC_URL}/addons/matomo/
146
- `,
147
- },
148
- metabase: {
149
- status: '',
150
- postCreateInstructions: dedent`
151
- Learn more about Metabase on Clever Cloud: ${conf.DOC_URL}/addons/metabase/
152
- `,
153
- },
154
- otoroshi: {
155
- status: '',
156
- postCreateInstructions: dedent`
157
- Learn more about Otoroshi with LLM on Clever Cloud: ${conf.DOC_URL}/addons/otoroshi/
158
- `,
159
- },
160
- 'addon-pulsar': {
161
- status: 'beta',
162
- postCreateInstructions: dedent`
163
- Learn more about Pulsar on Clever Cloud: ${conf.DOC_URL}/addons/pulsar/
164
- `,
165
- },
166
- };
134
+ if (linkedAppAlias != null) {
135
+ const linkedAppData = await AppConfig.getAppDetails({ alias: linkedAppAlias });
136
+ await Addon.link(linkedAppData.ownerId, linkedAppData.appId, { addon_id: newAddon.id });
137
+ }
167
138
 
168
- let providerNameToShow = '';
139
+ const provider = ADDON_PROVIDERS[providerName];
169
140
  let statusMessage = '';
170
- if (providerName in WIP_PROVIDERS && WIP_PROVIDERS[providerName].status !== '') {
171
- providerNameToShow = providerName === 'kv' ? 'Materia KV' : providerName;
141
+ if (provider?.status) {
142
+ statusMessage = `The ${provider.name} provider is in ${provider.status} testing phase`;
143
+ if (provider.status === 'alpha') {
144
+ statusMessage += ". Don't store sensitive or production grade data.";
145
+ }
146
+ }
147
+
148
+ // JSON format
149
+ if (format === 'json') {
150
+ const jsonAddon = {
151
+ id: newAddon.id,
152
+ realId: newAddon.realId,
153
+ name: newAddon.name,
154
+ env: newAddon.env,
155
+ };
156
+
157
+ if (provider?.status) {
158
+ jsonAddon.availability = provider.status;
159
+ jsonAddon.message = statusMessage;
160
+ }
172
161
 
173
- statusMessage = `The ${providerNameToShow} provider is in ${WIP_PROVIDERS[providerName].status} testing phase`;
174
- statusMessage +=
175
- WIP_PROVIDERS[providerName].status === 'alpha' ? ". Don't store sensitive or production grade data." : '';
162
+ Logger.printJson(jsonAddon);
163
+ return;
176
164
  }
177
165
 
178
- switch (format) {
179
- case 'json': {
180
- const jsonAddon = {
181
- id: addon.id,
182
- realId: addon.realId,
183
- name: addon.name,
184
- env: addon.env,
185
- };
166
+ // Human format
167
+ if (linkedAppAlias != null) {
168
+ Logger.println(`Add-on created and linked to application ${linkedAppAlias} successfully!`);
169
+ } else {
170
+ Logger.println('Add-on created successfully!');
171
+ }
186
172
 
187
- Logger.printJson(
188
- WIP_PROVIDERS[providerName] != null
189
- ? { ...jsonAddon, availability: WIP_PROVIDERS[providerName].status, message: statusMessage }
190
- : jsonAddon,
191
- );
192
- break;
193
- }
173
+ Logger.println(`ID: ${newAddon.id}`);
174
+ Logger.println(`Real ID: ${newAddon.realId}`);
175
+ Logger.println(`Name: ${newAddon.name}`);
194
176
 
195
- case 'human':
196
- default:
197
- Logger.println([message, `ID: ${addon.id}`, `Real ID: ${addon.realId}`, `Name: ${addon.name}`].join('\n'));
198
-
199
- if (providerName in PROVIDERS_WITH_URL) {
200
- const provider = PROVIDERS_WITH_URL[providerName];
201
- const urlEnv = addon.env.find((entry) => entry.name === provider.urlEnv);
202
- const urlToShow = urlEnv.value;
203
- const urlToShowWithHttps = urlToShow.startsWith('http') ? urlToShow : `https://${urlToShow}`;
204
-
205
- if (urlEnv) {
206
- Logger.println();
207
- Logger.println(`Your ${provider.name} is starting:`);
208
- Logger.println(` - Access it: ${urlToShowWithHttps}`);
209
- Logger.println(` - Manage it: ${conf.GOTO_URL}/${addon.id}`);
210
- }
177
+ const operator = ADDON_PROVIDERS[providerName]?.isOperator
178
+ ? await getOperator({ provider: providerName, realId: newAddon.realId }).then(sendToApi)
179
+ : null;
211
180
 
212
- if (providerName === 'keycloak') {
213
- Logger.println();
214
- Logger.println(
215
- "An initial account has been created, you'll be invited to change the password at first login:",
216
- );
217
- Logger.println(` - Admin user name: ${addon.env.find((e) => e.name === 'CC_KEYCLOAK_ADMIN').value}`);
218
- Logger.println(
219
- ` - Temporary password: ${addon.env.find((e) => e.name === 'CC_KEYCLOAK_ADMIN_DEFAULT_PASSWORD').value}`,
220
- );
221
- }
181
+ if (operator) {
182
+ if (operator.accessUrl) {
183
+ Logger.println();
184
+ Logger.println(`Your ${ADDON_PROVIDERS[providerName].name} is starting:`);
185
+ Logger.println(` - Access it: ${operator.accessUrl}`);
186
+ Logger.println(` - Manage it: ${conf.GOTO_URL}/${newAddon.id}`);
187
+ }
222
188
 
223
- if (providerName === 'otoroshi') {
224
- Logger.println();
225
- Logger.println(
226
- 'An initial account has been created, change the password at first login (Security -> Administrators -> Edit user):',
227
- );
228
- Logger.println(
229
- ` - Admin user name: ${addon.env.find((e) => e.name === 'CC_OTOROSHI_INITIAL_ADMIN_LOGIN').value}`,
230
- );
231
- Logger.println(
232
- ` - Initial password: ${addon.env.find((e) => e.name === 'CC_OTOROSHI_INITIAL_ADMIN_PASSWORD').value}`,
233
- );
234
- }
189
+ if (operator.initialCredentials) {
190
+ if (providerName === 'keycloak') {
191
+ Logger.println();
192
+ Logger.println("An initial account has been created, you'll be invited to change the password at first login:");
193
+ Logger.println(` - Admin user name: ${operator.initialCredentials.user}`);
194
+ Logger.println(` - Temporary password: ${operator.initialCredentials.password}`);
235
195
  }
236
-
237
- if (providerName in WIP_PROVIDERS) {
196
+ if (providerName === 'otoroshi') {
238
197
  Logger.println();
198
+ Logger.println(
199
+ 'An initial account has been created, change the password at first login (Security -> Administrators -> Edit user):',
200
+ );
201
+ Logger.println(` - Admin user name: ${operator.initialCredentials.user}`);
202
+ Logger.println(` - Password: ${operator.initialCredentials.password}`);
203
+ }
204
+ }
205
+ }
239
206
 
240
- if (statusMessage !== '') {
241
- Logger.println(styleText('yellow', `/!\\ ${statusMessage}`));
242
- }
207
+ if (provider?.postCreateInstructions) {
208
+ Logger.println();
243
209
 
244
- Logger.println(WIP_PROVIDERS[providerName].postCreateInstructions);
245
- }
210
+ if (statusMessage !== '') {
211
+ Logger.println(styleText('yellow', `/!\\ ${statusMessage}`));
212
+ }
213
+
214
+ if (typeof provider.postCreateInstructions === 'function') {
215
+ Logger.println(provider.postCreateInstructions(newAddon.id));
216
+ } else {
217
+ Logger.println(provider.postCreateInstructions);
218
+ }
246
219
  }
247
220
  }
248
221
 
@@ -17,11 +17,9 @@ export async function diag(params) {
17
17
  if (authDetails.token == null) {
18
18
  return 'not connected';
19
19
  }
20
-
21
20
  if (userId == null) {
22
21
  return 'authentication failed';
23
22
  }
24
-
25
23
  return 'authenticated';
26
24
  }
27
25
 
@@ -32,7 +30,8 @@ export async function diag(params) {
32
30
  platform: os.platform(),
33
31
  release: os.release(),
34
32
  arch: process.arch,
35
- shell: process.env.SHELL,
33
+ shell: getShell(),
34
+ terminal: getTerminal(),
36
35
  isPackaged: process.pkg != null,
37
36
  execPath: process.execPath,
38
37
  configFile: conf.CONFIGURATION_FILE,
@@ -69,7 +68,8 @@ export async function diag(params) {
69
68
  Logger.println('Linux ' + styleText('green', formattedDiag.linuxInfos));
70
69
  }
71
70
  Logger.println('Shell ' + styleText('green', formattedDiag.shell));
72
- Logger.println('Packaged ' + styleText('green', String(formattedDiag.isPackaged)));
71
+ Logger.println('Terminal ' + styleText('green', formattedDiag.terminal));
72
+ Logger.println('Packaged ' + styleText('green', formattedDiag.isPackaged));
73
73
  Logger.println('Exec path ' + styleText('green', formattedDiag.execPath));
74
74
  Logger.println('Config file ' + styleText('green', formattedDiag.configFile));
75
75
 
@@ -95,3 +95,32 @@ export async function diag(params) {
95
95
  }
96
96
  }
97
97
  }
98
+
99
+ function getShell() {
100
+ const platform = os.platform();
101
+
102
+ if (platform === 'win32') {
103
+ if (process.env.PSModulePath) {
104
+ if (process.env.WT_SESSION) {
105
+ return 'PowerShell (Windows Terminal)';
106
+ }
107
+ if (process.env.PSVersionTable || process.env.POWERSHELL_DISTRIBUTION_CHANNEL) {
108
+ return 'PowerShell Core';
109
+ }
110
+ return 'Windows PowerShell';
111
+ }
112
+ if (process.env.WT_SESSION) {
113
+ return 'Windows Terminal (cmd)';
114
+ }
115
+ return process.env.ComSpec || 'cmd.exe';
116
+ }
117
+
118
+ return process.env.SHELL || 'unknown';
119
+ }
120
+
121
+ function getTerminal() {
122
+ if (process.env.WT_SESSION) {
123
+ return 'Windows Terminal';
124
+ }
125
+ return process.env.TERM_PROGRAM || process.env.TERMINAL_EMULATOR || process.env.TERM;
126
+ }