@rockcarver/frodo-cli 0.11.1-2 → 0.12.2-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,11 +7,44 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.12.2-1] - 2022-08-27
11
+
12
+ ## [0.12.2-0] - 2022-08-27
13
+
14
+ ## [0.12.1] - 2022-08-27
15
+
16
+ ### Changed
17
+
18
+ - \#3: `frodo-cli` now uses the new callback based progress indicator and message display framework in `frodo-lib 0.12.0`
19
+
20
+ ## [0.12.0] - 2022-08-27 [YANKED]
21
+
10
22
  ## [0.11.1-2] - 2022-08-21
11
23
 
24
+ ### Fixed
25
+
26
+ - rockcarver/frodo#389: Exporting of empty scripts now works properly
27
+
12
28
  ## [0.11.1-1] - 2022-08-21
13
29
 
14
- ## [0.11.1-0] - 2022-08-19
30
+ ### Added
31
+
32
+ - Frodo CLI is now effectively using Frodo Library for all functionality except CLI.
33
+ - This changes has no effect on users using frodo binaries except for the download location of those binaries, which has now shifted to the [frodo-cli](https://github.com/rockcarver/frodo-cli) repo [release section](https://github.com/rockcarver/frodo-cli/releases).
34
+ - This change does affect users who run Frodo in `Developer Mode`. The exact effects and required actions are not yet fully documented and understood.
35
+ - This change does not effect the installation/update/usage process of users running the Frodo CLI NPM package. However, under the surface there is a big change in that the [Frodo CLI (@rockcarver/frodo-cli)](https://www.npmjs.com/package/@rockcarver/frodo-cli) package is now built on the new [Frodo Library (@rockcarver/frodo-lib)](https://www.npmjs.com/package/@rockcarver/frodo-lib).
36
+
37
+ ### Changed
38
+
39
+ - The output of `frodo -v` has changed to include all three versions: cli, lib, and node:
40
+ ```console
41
+ % frodo -v
42
+ cli: v0.11.1-1
43
+ lib: v0.11.1-6
44
+ node: v18.7.0
45
+ ```
46
+
47
+ ## [0.11.1-0] - 2022-08-19 [YANKED]
15
48
 
16
49
  ## [0.10.4] - 2022-08-13
17
50
 
@@ -408,7 +441,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
408
441
  - Fixed problem with adding connection profiles
409
442
  - Miscellaneous bug fixes
410
443
 
411
- [Unreleased]: https://github.com/rockcarver/frodo-cli/compare/v0.11.1-2...HEAD
444
+ [Unreleased]: https://github.com/rockcarver/frodo-cli/compare/v0.12.2-1...HEAD
445
+
446
+ [0.12.2-1]: https://github.com/rockcarver/frodo-cli/compare/v0.12.2-0...v0.12.2-1
447
+
448
+ [0.12.2-0]: https://github.com/rockcarver/frodo-cli/compare/v0.12.1...v0.12.2-0
449
+
450
+ [0.12.1]: https://github.com/rockcarver/frodo-cli/compare/v0.12.0...v0.12.1
451
+
452
+ [0.12.0]: https://github.com/rockcarver/frodo-cli/compare/v0.11.1-2...v0.12.0
412
453
 
413
454
  [0.11.1-2]: https://github.com/rockcarver/frodo-cli/compare/v0.11.1-1...v0.11.1-2
414
455
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rockcarver/frodo-cli",
3
- "version": "0.11.1-2",
3
+ "version": "0.12.2-1",
4
4
  "type": "module",
5
5
  "description": "A command line interface to manage ForgeRock Identity Cloud tenants, ForgeOps deployments, and classic deployments.",
6
6
  "keywords": [
@@ -80,24 +80,12 @@
80
80
  },
81
81
  "dependencies": {
82
82
  "@colors/colors": "^1.5.0",
83
- "@rockcarver/frodo-lib": "^0.11.1-7",
84
- "axios": "^0.27.2",
85
- "axios-retry": "^3.3.1",
83
+ "@rockcarver/frodo-lib": "~0.12.1",
86
84
  "cli-progress": "^3.11.2",
87
85
  "cli-table3": "^0.6.2",
88
86
  "commander": "^9.4.0",
89
- "fs-extra": "^10.0.0",
90
- "https-proxy-agent": "^5.0.1",
91
- "lodash": "^4.17.21",
92
87
  "nanospinner": "^1.1.0",
93
- "properties-reader": "^2.2.0",
94
- "qs": "^6.10.3",
95
- "readline-sync": "^1.4.10",
96
- "replaceall": "^0.1.6",
97
- "slugify": "^1.6.5",
98
- "url": "^0.11.0",
99
- "uuid": "^8.3.2",
100
- "yesno": "^0.4.0"
88
+ "uuid": "^8.3.2"
101
89
  },
102
90
  "devDependencies": {
103
91
  "@babel/eslint-parser": "^7.18.9",
@@ -1,5 +1,18 @@
1
1
  import { Argument, Option } from 'commander';
2
+ import { state } from '@rockcarver/frodo-lib';
3
+
2
4
  import * as global from '../storage/StaticStorage.js';
5
+ import {
6
+ printMessage,
7
+ createProgressIndicator,
8
+ updateProgressIndicator,
9
+ stopProgressIndicator,
10
+ } from '../utils/Console.js';
11
+
12
+ state.default.session.setPrintHandler(printMessage);
13
+ state.default.session.setCreateProgressHandler(createProgressIndicator);
14
+ state.default.session.setUpdateProgressHandler(updateProgressIndicator);
15
+ state.default.session.setStopProgressHandler(stopProgressIndicator);
3
16
 
4
17
  const hostArgumentDescription =
5
18
  'Access Management base URL, e.g.: https://cdk.iam.example.com/am. To use a connection profile, just specify a unique substring.';
@@ -0,0 +1,480 @@
1
+ import { SingleBar, Presets } from 'cli-progress';
2
+ import { createSpinner } from 'nanospinner';
3
+ import Table from 'cli-table3';
4
+ // eslint-disable-next-line no-unused-vars
5
+ import * as colors from '@colors/colors';
6
+ import { state } from '@rockcarver/frodo-lib';
7
+
8
+ /**
9
+ * Output a data message
10
+ * @param {Object} message the message
11
+ */
12
+ function data(message) {
13
+ switch (typeof message) {
14
+ case 'object':
15
+ console.dir(message, { depth: 3 });
16
+ break;
17
+ default:
18
+ console.log(message);
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Output a text message
24
+ * @param {Object} message the message
25
+ */
26
+ function text(message) {
27
+ switch (typeof message) {
28
+ case 'object':
29
+ console.dir(message, { depth: 3 });
30
+ break;
31
+ default:
32
+ console.error(message);
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Output an info message
38
+ * @param {Object} message the message
39
+ */
40
+ function info(message) {
41
+ switch (typeof message) {
42
+ case 'object':
43
+ console.dir(message, { depth: 3 });
44
+ break;
45
+ default:
46
+ console.error(message.brightCyan);
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Output a warn message
52
+ * @param {Object} message the message
53
+ */
54
+ function warn(message) {
55
+ switch (typeof message) {
56
+ case 'object':
57
+ console.dir(message, { depth: 3 });
58
+ break;
59
+ default:
60
+ console.error(message.yellow);
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Output an error message
66
+ * @param {Object} message the message
67
+ */
68
+ function error(message) {
69
+ switch (typeof message) {
70
+ case 'object':
71
+ console.dir(message, { depth: 4 });
72
+ break;
73
+ default:
74
+ console.error(message.brightRed);
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Prints a string message to console
80
+ *
81
+ * @param {string} message The string message to print
82
+ * @param {string} [type=text] "text", "info", "warn", "error" or "data". All but
83
+ * type="data" will be written to stderr.
84
+ * @param {boolean} [newline=true] Whether to add a new at the end of message
85
+ *
86
+ */
87
+ export function printMessage(message, type = 'text', newline = true) {
88
+ // if (state.default.session.getItem('scriptFriendly')) {
89
+ switch (type) {
90
+ case 'data':
91
+ if (newline) {
92
+ data(message);
93
+ } else {
94
+ process.stdout.write(message);
95
+ }
96
+ break;
97
+ case 'text':
98
+ if (newline) {
99
+ text(message);
100
+ } else {
101
+ process.stderr.write(message);
102
+ }
103
+ break;
104
+ case 'info':
105
+ if (newline) {
106
+ info(message);
107
+ } else {
108
+ process.stderr.write(message.brightCyan);
109
+ }
110
+ break;
111
+ case 'warn':
112
+ if (newline) {
113
+ warn(message);
114
+ } else {
115
+ process.stderr.write(message.yellow);
116
+ }
117
+ break;
118
+ case 'error':
119
+ if (newline) {
120
+ error(message);
121
+ } else {
122
+ process.stderr.write(message.brightRed);
123
+ }
124
+ break;
125
+ default:
126
+ if (newline) {
127
+ error(message);
128
+ } else {
129
+ process.stderr.write(message.bgBrightRed);
130
+ }
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Creates a progress bar on stderr
136
+ *
137
+ * Example:
138
+ * [========================================] 100% | 49/49 | Analyzing journey - transactional_auth
139
+ *
140
+ * @param {Number} total The total number of entries to track progress for
141
+ * @param {String} message optional progress bar message
142
+ * @param {Object} options progress bar configuration options
143
+ *
144
+ */
145
+ export function createProgressBar(
146
+ total,
147
+ message = null,
148
+ options = {
149
+ format: '[{bar}] {percentage}% | {value}/{total} | {data}',
150
+ noTTYOutput: true,
151
+ }
152
+ ) {
153
+ let opt = options;
154
+ if (message == null) {
155
+ opt = {
156
+ format: '[{bar}] {percentage}% | {value}/{total}',
157
+ noTTYOutput: true,
158
+ };
159
+ }
160
+ let pBar = state.default.session.getItem('progressBar');
161
+ if (!pBar) pBar = new SingleBar(opt, Presets.legacy); // create only when needed
162
+ pBar.start(total, 0, {
163
+ data: message,
164
+ });
165
+ state.default.session.setItem('progressBar', pBar);
166
+ }
167
+
168
+ /**
169
+ * Updates the progress bar by 1
170
+ * @param {string} message optional message to update the progress bar
171
+ *
172
+ */
173
+ export function updateProgressBar(message = null) {
174
+ const pBar = state.default.session.getItem('progressBar');
175
+ if (message)
176
+ pBar.increment({
177
+ data: message,
178
+ });
179
+ else pBar.increment();
180
+ }
181
+
182
+ /**
183
+ * Stop and hide the progress bar
184
+ * @param {*} message optional message to update the progress bar
185
+ */
186
+ export function stopProgressBar(message = null) {
187
+ const pBar = state.default.session.getItem('progressBar');
188
+ if (message)
189
+ pBar.update({
190
+ data: message,
191
+ });
192
+ pBar.stop();
193
+ }
194
+
195
+ /**
196
+ * Create the spinner
197
+ * @param {String} message optional spinner message
198
+ */
199
+ export function showSpinner(message) {
200
+ const spinner = createSpinner(message).start();
201
+ state.default.session.setItem('Spinner', spinner);
202
+ }
203
+
204
+ /**
205
+ * Stop the spinner
206
+ * @param {String} message optional message to update the spinner
207
+ */
208
+ export function stopSpinner(message = null) {
209
+ const spinner = state.default.session.getItem('Spinner');
210
+ if (spinner) {
211
+ let options = {};
212
+ if (message) options = { text: message };
213
+ spinner.stop(options);
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Succeed the spinner. Stop and render success checkmark with optional message.
219
+ * @param {String} message optional message to update the spinner
220
+ */
221
+ export function succeedSpinner(message = null) {
222
+ const spinner = state.default.session.getItem('Spinner');
223
+ if (spinner) {
224
+ let options = {};
225
+ if (message) options = { text: message };
226
+ spinner.success(options);
227
+ }
228
+ }
229
+
230
+ /**
231
+ * Warn the spinner
232
+ * @param {String} message optional message to update the spinner
233
+ */
234
+ export function warnSpinner(message = null) {
235
+ const spinner = state.default.session.getItem('Spinner');
236
+ if (spinner) {
237
+ let options = {};
238
+ if (message) options = { text: message };
239
+ spinner.warn(options);
240
+ }
241
+ }
242
+
243
+ /**
244
+ * Fail the spinner
245
+ * @param {String} message optional message to update the spinner
246
+ */
247
+ export function failSpinner(message = null) {
248
+ const spinner = state.default.session.getItem('Spinner');
249
+ if (spinner) {
250
+ let options = {};
251
+ if (message) options = { text: message };
252
+ spinner.error(options);
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Spin the spinner
258
+ * @param {String} message optional message to update the spinner
259
+ */
260
+ export function spinSpinner(message = null) {
261
+ const spinner = state.default.session.getItem('Spinner');
262
+ if (spinner) {
263
+ let options = {};
264
+ if (message) options = { text: message };
265
+ spinner.update(options);
266
+ spinner.spin();
267
+ }
268
+ }
269
+
270
+ export function createProgressIndicator(
271
+ type = 'determinate',
272
+ total = 0,
273
+ message = null
274
+ ) {
275
+ if (type === 'determinate') {
276
+ createProgressBar(total, message);
277
+ } else {
278
+ showSpinner(message);
279
+ }
280
+ }
281
+
282
+ export function updateProgressIndicator(message) {
283
+ const progressBar = state.default.session.getItem('progressBar');
284
+ if (!progressBar) {
285
+ spinSpinner(message);
286
+ } else {
287
+ updateProgressBar(message);
288
+ }
289
+ }
290
+
291
+ export function stopProgressIndicator(message, status = 'none') {
292
+ const progressBar = state.default.session.getItem('progressBar');
293
+ if (!progressBar) {
294
+ switch (status) {
295
+ case 'none':
296
+ stopSpinner(message);
297
+ break;
298
+ case 'success':
299
+ succeedSpinner(message);
300
+ break;
301
+ case 'warn':
302
+ warnSpinner(message);
303
+ break;
304
+ case 'fail':
305
+ failSpinner(message);
306
+ break;
307
+ default:
308
+ stopSpinner(message);
309
+ break;
310
+ }
311
+ } else {
312
+ stopProgressBar(message);
313
+ }
314
+ }
315
+
316
+ /**
317
+ * Create an empty table
318
+ * @param {[String]} head header row as an array of strings
319
+ * @returns {CliTable3} an empty table
320
+ */
321
+ export function createTable(head) {
322
+ return new Table({
323
+ head,
324
+ chars: {
325
+ top: '',
326
+ 'top-mid': '',
327
+ 'top-left': '',
328
+ 'top-right': '',
329
+ bottom: '',
330
+ 'bottom-mid': '',
331
+ 'bottom-left': '',
332
+ 'bottom-right': '',
333
+ left: '',
334
+ 'left-mid': '',
335
+ mid: '',
336
+ 'mid-mid': '',
337
+ right: '',
338
+ 'right-mid': '',
339
+ },
340
+ style: { 'padding-left': 0, 'padding-right': 0, head: ['brightCyan'] },
341
+ });
342
+ }
343
+
344
+ /**
345
+ * Create a new key/value table
346
+ * @returns {CliTable3} an empty key/value table
347
+ */
348
+ export function createKeyValueTable() {
349
+ return new Table({
350
+ chars: {
351
+ top: '',
352
+ 'top-mid': '',
353
+ 'top-left': '',
354
+ 'top-right': '',
355
+ bottom: '',
356
+ 'bottom-mid': '',
357
+ 'bottom-left': '',
358
+ 'bottom-right': '',
359
+ left: '',
360
+ 'left-mid': '',
361
+ mid: '',
362
+ 'mid-mid': '',
363
+ right: '',
364
+ 'right-mid': '',
365
+ },
366
+ style: { 'padding-left': 0, 'padding-right': 0 },
367
+ wordWrap: true,
368
+ });
369
+ }
370
+
371
+ /**
372
+ * Helper function to determine the total depth of an object
373
+ * @param {Object} object input object
374
+ * @returns {Number} total depth of the input object
375
+ */
376
+ function getObjectDepth(object) {
377
+ return Object(object) === object
378
+ ? 1 + Math.max(-1, ...Object.values(object).map(getObjectDepth))
379
+ : 0;
380
+ }
381
+
382
+ /**
383
+ * Helper function to determine if an object has values
384
+ * @param {Object} object input object
385
+ * @returns {boolean} true of the object or any of its sub-objects contain values, false otherwise
386
+ */
387
+ function hasValues(object) {
388
+ let has = false;
389
+ const keys = Object.keys(object);
390
+ for (const key of keys) {
391
+ if (Object(object[key]) !== object[key]) {
392
+ return true;
393
+ }
394
+ has = has || hasValues(object[key]);
395
+ }
396
+ return has;
397
+ }
398
+
399
+ /**
400
+ * Helper function (recursive) to add rows to an object table
401
+ * @param {Object} object object to render
402
+ * @param {Number} depth total depth of initial object
403
+ * @param {Number} level current level
404
+ * @param {CliTable3} table the object table to add the rows to
405
+ * @returns the updated object table
406
+ */
407
+ function addRows(object, depth, level, table, keyMap) {
408
+ const space = ' ';
409
+ const keys = Object.keys(object);
410
+ for (const key of keys) {
411
+ if (Object(object[key]) !== object[key]) {
412
+ if (level === 1) {
413
+ table.push([
414
+ keyMap[key] ? keyMap[key].brightCyan : key.brightCyan,
415
+ object[key],
416
+ ]);
417
+ } else {
418
+ table.push([
419
+ {
420
+ hAlign: 'right',
421
+ content: keyMap[key] ? keyMap[key].gray : key.gray,
422
+ },
423
+ object[key],
424
+ ]);
425
+ }
426
+ }
427
+ }
428
+ for (const key of keys) {
429
+ if (Object(object[key]) === object[key]) {
430
+ // only print header if there are any values below
431
+ if (hasValues(object[key])) {
432
+ let indention = new Array(level).fill(space).join('');
433
+ if (level < 3) indention = `\n${indention}`;
434
+ table.push([
435
+ indention.concat(
436
+ keyMap[key] ? keyMap[key].brightCyan : key.brightCyan
437
+ ),
438
+ '',
439
+ ]);
440
+ }
441
+ // eslint-disable-next-line no-param-reassign
442
+ table = addRows(object[key], depth, level + 1, table, keyMap);
443
+ }
444
+ }
445
+ return table;
446
+ }
447
+
448
+ /**
449
+ * Create and populate an object table from any JSON object. Use for describe commands.
450
+ * @param {Object} object JSON object to create
451
+ * @returns {CliTable3} a table that can be printed to the console
452
+ */
453
+ export function createObjectTable(object, keyMap = {}) {
454
+ // eslint-disable-next-line no-param-reassign
455
+ const depth = getObjectDepth(object);
456
+ // eslint-disable-next-line no-param-reassign
457
+ const level = 0;
458
+ // eslint-disable-next-line no-param-reassign
459
+ const table = new Table({
460
+ chars: {
461
+ top: '',
462
+ 'top-mid': '',
463
+ 'top-left': '',
464
+ 'top-right': '',
465
+ bottom: '',
466
+ 'bottom-mid': '',
467
+ 'bottom-left': '',
468
+ 'bottom-right': '',
469
+ left: '',
470
+ 'left-mid': '',
471
+ mid: '',
472
+ 'mid-mid': '',
473
+ right: '',
474
+ 'right-mid': '',
475
+ },
476
+ style: { 'padding-left': 0, 'padding-right': 0, head: ['brightCyan'] },
477
+ });
478
+ addRows(object, depth, level + 1, table, keyMap);
479
+ return table;
480
+ }