@rockcarver/frodo-lib 0.11.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.
Files changed (112) hide show
  1. package/.eslintrc +32 -0
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +30 -0
  3. package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  4. package/.github/README.md +121 -0
  5. package/.github/workflows/pipeline.yml +287 -0
  6. package/.prettierrc +6 -0
  7. package/CHANGELOG.md +512 -0
  8. package/CODE_OF_CONDUCT.md +128 -0
  9. package/LICENSE +21 -0
  10. package/README.md +8 -0
  11. package/docs/CONTRIBUTE.md +96 -0
  12. package/docs/PIPELINE.md +169 -0
  13. package/docs/images/npm_versioning_guidelines.png +0 -0
  14. package/docs/images/release_pipeline.png +0 -0
  15. package/jsconfig.json +6 -0
  16. package/package.json +95 -0
  17. package/resources/sampleEntitiesFile.json +8 -0
  18. package/resources/sampleEnvFile.env +2 -0
  19. package/src/api/AuthenticateApi.js +33 -0
  20. package/src/api/BaseApi.js +242 -0
  21. package/src/api/CirclesOfTrustApi.js +87 -0
  22. package/src/api/EmailTemplateApi.js +37 -0
  23. package/src/api/IdmConfigApi.js +88 -0
  24. package/src/api/LogApi.js +45 -0
  25. package/src/api/ManagedObjectApi.js +62 -0
  26. package/src/api/OAuth2ClientApi.js +69 -0
  27. package/src/api/OAuth2OIDCApi.js +73 -0
  28. package/src/api/OAuth2ProviderApi.js +32 -0
  29. package/src/api/RealmApi.js +99 -0
  30. package/src/api/Saml2Api.js +176 -0
  31. package/src/api/ScriptApi.js +84 -0
  32. package/src/api/SecretsApi.js +151 -0
  33. package/src/api/ServerInfoApi.js +41 -0
  34. package/src/api/SocialIdentityProvidersApi.js +114 -0
  35. package/src/api/StartupApi.js +45 -0
  36. package/src/api/ThemeApi.js +181 -0
  37. package/src/api/TreeApi.js +207 -0
  38. package/src/api/VariablesApi.js +104 -0
  39. package/src/api/utils/ApiUtils.js +77 -0
  40. package/src/api/utils/ApiUtils.test.js +96 -0
  41. package/src/api/utils/Base64.js +62 -0
  42. package/src/index.js +32 -0
  43. package/src/index.test.js +13 -0
  44. package/src/ops/AdminOps.js +901 -0
  45. package/src/ops/AuthenticateOps.js +342 -0
  46. package/src/ops/CirclesOfTrustOps.js +350 -0
  47. package/src/ops/ConnectionProfileOps.js +254 -0
  48. package/src/ops/EmailTemplateOps.js +326 -0
  49. package/src/ops/IdmOps.js +227 -0
  50. package/src/ops/IdpOps.js +342 -0
  51. package/src/ops/JourneyOps.js +2026 -0
  52. package/src/ops/LogOps.js +357 -0
  53. package/src/ops/ManagedObjectOps.js +34 -0
  54. package/src/ops/OAuth2ClientOps.js +151 -0
  55. package/src/ops/OrganizationOps.js +85 -0
  56. package/src/ops/RealmOps.js +139 -0
  57. package/src/ops/SamlOps.js +541 -0
  58. package/src/ops/ScriptOps.js +211 -0
  59. package/src/ops/SecretsOps.js +288 -0
  60. package/src/ops/StartupOps.js +114 -0
  61. package/src/ops/ThemeOps.js +379 -0
  62. package/src/ops/VariablesOps.js +185 -0
  63. package/src/ops/templates/OAuth2ClientTemplate.json +270 -0
  64. package/src/ops/templates/OrgModelUserAttributesTemplate.json +149 -0
  65. package/src/ops/templates/cloud/GenericExtensionAttributesTemplate.json +392 -0
  66. package/src/ops/templates/cloud/managed.json +4119 -0
  67. package/src/ops/utils/Console.js +434 -0
  68. package/src/ops/utils/DataProtection.js +92 -0
  69. package/src/ops/utils/DataProtection.test.js +28 -0
  70. package/src/ops/utils/ExportImportUtils.js +146 -0
  71. package/src/ops/utils/ExportImportUtils.test.js +119 -0
  72. package/src/ops/utils/OpsUtils.js +76 -0
  73. package/src/ops/utils/Wordwrap.js +11 -0
  74. package/src/storage/SessionStorage.js +45 -0
  75. package/src/storage/StaticStorage.js +15 -0
  76. package/test/e2e/journey/baseline/ForgottenUsername.journey.json +216 -0
  77. package/test/e2e/journey/baseline/Login.journey.json +205 -0
  78. package/test/e2e/journey/baseline/PasswordGrant.journey.json +139 -0
  79. package/test/e2e/journey/baseline/ProgressiveProfile.journey.json +198 -0
  80. package/test/e2e/journey/baseline/Registration.journey.json +249 -0
  81. package/test/e2e/journey/baseline/ResetPassword.journey.json +268 -0
  82. package/test/e2e/journey/baseline/UpdatePassword.journey.json +323 -0
  83. package/test/e2e/journey/baseline/allAlphaJourneys.journeys.json +1520 -0
  84. package/test/e2e/journey/delete/ForgottenUsername.journey.json +216 -0
  85. package/test/e2e/journey/delete/Login.journey.json +205 -0
  86. package/test/e2e/journey/delete/PasswordGrant.journey.json +139 -0
  87. package/test/e2e/journey/delete/ProgressiveProfile.journey.json +198 -0
  88. package/test/e2e/journey/delete/Registration.journey.json +249 -0
  89. package/test/e2e/journey/delete/ResetPassword.journey.json +268 -0
  90. package/test/e2e/journey/delete/UpdatePassword.journey.json +323 -0
  91. package/test/e2e/journey/delete/deleteMe.journey.json +230 -0
  92. package/test/e2e/journey/list/Disabled.journey.json +43 -0
  93. package/test/e2e/journey/list/ForgottenUsername.journey.json +216 -0
  94. package/test/e2e/journey/list/Login.journey.json +205 -0
  95. package/test/e2e/journey/list/PasswordGrant.journey.json +139 -0
  96. package/test/e2e/journey/list/ProgressiveProfile.journey.json +198 -0
  97. package/test/e2e/journey/list/Registration.journey.json +249 -0
  98. package/test/e2e/journey/list/ResetPassword.journey.json +268 -0
  99. package/test/e2e/journey/list/UpdatePassword.journey.json +323 -0
  100. package/test/e2e/setup.js +107 -0
  101. package/test/e2e/theme/baseline/Contrast.theme.json +95 -0
  102. package/test/e2e/theme/baseline/Highlander.theme.json +95 -0
  103. package/test/e2e/theme/baseline/Robroy.theme.json +95 -0
  104. package/test/e2e/theme/baseline/Starter-Theme.theme.json +94 -0
  105. package/test/e2e/theme/baseline/Zardoz.theme.json +95 -0
  106. package/test/e2e/theme/import/Contrast.theme.json +95 -0
  107. package/test/e2e/theme/import/Highlander.theme.json +95 -0
  108. package/test/e2e/theme/import/Robroy.theme.json +95 -0
  109. package/test/e2e/theme/import/Starter-Theme.theme.json +94 -0
  110. package/test/e2e/theme/import/Zardoz.default.theme.json +95 -0
  111. package/test/fs_tmp/.gitkeep +2 -0
  112. package/test/global/setup.js +65 -0
@@ -0,0 +1,379 @@
1
+ import fs from 'fs';
2
+ import {
3
+ deleteTheme,
4
+ deleteThemeByName,
5
+ deleteThemes,
6
+ getTheme,
7
+ getThemeByName,
8
+ getThemes,
9
+ putTheme,
10
+ putThemeByName,
11
+ putThemes,
12
+ } from '../api/ThemeApi.js';
13
+ import {
14
+ createProgressBar,
15
+ createTable,
16
+ failSpinner,
17
+ printMessage,
18
+ showSpinner,
19
+ stopProgressBar,
20
+ succeedSpinner,
21
+ updateProgressBar,
22
+ } from './utils/Console.js';
23
+ import {
24
+ getRealmString,
25
+ getTypedFilename,
26
+ saveToFile,
27
+ validateImport,
28
+ } from './utils/ExportImportUtils.js';
29
+
30
+ /**
31
+ * List all the themes
32
+ * @param {boolean} long Long version, more fields
33
+ */
34
+ export async function listThemes(long = false) {
35
+ const themeList = await getThemes();
36
+ themeList.sort((a, b) => a.name.localeCompare(b.name));
37
+ if (!long) {
38
+ themeList.forEach((theme) => {
39
+ printMessage(
40
+ `${theme.isDefault ? theme.name.brightCyan : theme.name}`,
41
+ 'data'
42
+ );
43
+ });
44
+ } else {
45
+ const table = createTable([
46
+ 'Name'.brightCyan,
47
+ 'Id'.brightCyan,
48
+ 'Default'.brightCyan,
49
+ ]);
50
+ themeList.forEach((theme) => {
51
+ table.push([
52
+ `${theme.name}`,
53
+ `${theme._id}`,
54
+ `${theme.isDefault ? 'Yes'.brightGreen : ''}`,
55
+ ]);
56
+ });
57
+ printMessage(table.toString(), 'data');
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Export theme by name to file
63
+ * @param {String} name theme name
64
+ * @param {String} file optional export file name
65
+ */
66
+ export async function exportThemeByName(name, file) {
67
+ let fileName = getTypedFilename(name, 'theme');
68
+ if (file) {
69
+ fileName = file;
70
+ }
71
+ createProgressBar(1, `Exporting ${name}`);
72
+ const themeData = await getThemeByName(name);
73
+ if (themeData.length === 0) {
74
+ stopProgressBar(`Theme ${name} not found!`);
75
+ printMessage(`Theme ${name} not found!`, 'error');
76
+ } else {
77
+ updateProgressBar(`Writing file ${fileName}`);
78
+ saveToFile('theme', themeData, '_id', fileName);
79
+ stopProgressBar(`Successfully exported theme ${name}.`);
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Export theme by uuid to file
85
+ * @param {String} id theme uuid
86
+ * @param {String} file optional export file name
87
+ */
88
+ export async function exportThemeById(id, file) {
89
+ let fileName = getTypedFilename(id, 'theme');
90
+ if (file) {
91
+ fileName = file;
92
+ }
93
+ createProgressBar(1, `Exporting ${id}`);
94
+ const themeData = await getTheme(id);
95
+ if (themeData.length === 0) {
96
+ stopProgressBar(`Theme ${id} not found!`);
97
+ printMessage(`Theme ${id} not found!`, 'error');
98
+ } else {
99
+ updateProgressBar(`Writing file ${fileName}`);
100
+ saveToFile('theme', themeData, '_id', fileName);
101
+ stopProgressBar(`Successfully exported theme ${id}.`);
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Export all themes to file
107
+ * @param {String} file optional export file name
108
+ */
109
+ export async function exportThemesToFile(file) {
110
+ let fileName = getTypedFilename(`all${getRealmString()}Themes`, 'theme');
111
+ if (file) {
112
+ fileName = file;
113
+ }
114
+ const allThemesData = await getThemes();
115
+ createProgressBar(allThemesData.length, 'Exporting themes');
116
+ for (const themeData of allThemesData) {
117
+ updateProgressBar(`Exporting theme ${themeData.name}`);
118
+ }
119
+ saveToFile('theme', allThemesData, '_id', fileName);
120
+ stopProgressBar(`${allThemesData.length} themes exported to ${fileName}.`);
121
+ }
122
+
123
+ /**
124
+ * Export all themes to separate files
125
+ */
126
+ export async function exportThemesToFiles() {
127
+ const allThemesData = await getThemes();
128
+ createProgressBar(allThemesData.length, 'Exporting themes');
129
+ for (const themeData of allThemesData) {
130
+ updateProgressBar(`Writing theme ${themeData.name}`);
131
+ const fileName = getTypedFilename(themeData.name, 'theme');
132
+ saveToFile('theme', themeData, '_id', fileName);
133
+ }
134
+ stopProgressBar(`${allThemesData.length} themes exported.`);
135
+ }
136
+
137
+ /**
138
+ * Import theme by name from file
139
+ * @param {String} name theme name
140
+ * @param {String} file import file name
141
+ */
142
+ export async function importThemeByName(name, file) {
143
+ fs.readFile(file, 'utf8', (err, data) => {
144
+ if (err) throw err;
145
+ const themeData = JSON.parse(data);
146
+ if (validateImport(themeData.meta)) {
147
+ createProgressBar(1, 'Importing theme...');
148
+ let found = false;
149
+ for (const id in themeData.theme) {
150
+ if ({}.hasOwnProperty.call(themeData.theme, id)) {
151
+ if (themeData.theme[id].name === name) {
152
+ found = true;
153
+ updateProgressBar(`Importing ${themeData.theme[id].name}`);
154
+ putThemeByName(name, themeData.theme[id]).then((result) => {
155
+ if (result == null) {
156
+ stopProgressBar(
157
+ `Error importing theme ${themeData.theme[id].name}`
158
+ );
159
+ printMessage(
160
+ `Error importing theme ${themeData.theme[id].name}`,
161
+ 'error'
162
+ );
163
+ } else {
164
+ stopProgressBar(`Successfully imported theme ${name}.`);
165
+ }
166
+ });
167
+ break;
168
+ }
169
+ }
170
+ }
171
+ if (!found) {
172
+ stopProgressBar(`Theme ${name} not found!`);
173
+ }
174
+ } else {
175
+ printMessage('Import validation failed...', 'error');
176
+ }
177
+ });
178
+ }
179
+
180
+ /**
181
+ * Import theme by uuid from file
182
+ * @param {String} id theme uuid
183
+ * @param {String} file import file name
184
+ */
185
+ export async function importThemeById(id, file) {
186
+ fs.readFile(file, 'utf8', (err, data) => {
187
+ if (err) throw err;
188
+ const themeData = JSON.parse(data);
189
+ if (validateImport(themeData.meta)) {
190
+ createProgressBar(1, 'Importing theme...');
191
+ let found = false;
192
+ for (const themeId in themeData.theme) {
193
+ if ({}.hasOwnProperty.call(themeData.theme, themeId)) {
194
+ if (themeId === id) {
195
+ found = true;
196
+ updateProgressBar(`Importing ${themeData.theme[themeId]._id}`);
197
+ putTheme(themeId, themeData.theme[themeId]).then((result) => {
198
+ if (result == null) {
199
+ stopProgressBar(
200
+ `Error importing theme ${themeData.theme[themeId]._id}`
201
+ );
202
+ printMessage(
203
+ `Error importing theme ${themeData.theme[themeId]._id}`,
204
+ 'error'
205
+ );
206
+ } else {
207
+ stopProgressBar(`Successfully imported theme ${id}.`);
208
+ }
209
+ });
210
+ break;
211
+ }
212
+ }
213
+ }
214
+ if (!found) {
215
+ stopProgressBar(`Theme ${id} not found!`);
216
+ }
217
+ } else {
218
+ printMessage('Import validation failed...', 'error');
219
+ }
220
+ });
221
+ }
222
+
223
+ /**
224
+ * Import all themes from single file
225
+ * @param {String} file import file name
226
+ */
227
+ export async function importThemesFromFile(file) {
228
+ fs.readFile(file, 'utf8', (err, data) => {
229
+ if (err) throw err;
230
+ const fileData = JSON.parse(data);
231
+ if (validateImport(fileData.meta)) {
232
+ createProgressBar(
233
+ Object.keys(fileData.theme).length,
234
+ 'Importing themes...'
235
+ );
236
+ for (const id in fileData.theme) {
237
+ if ({}.hasOwnProperty.call(fileData.theme, id)) {
238
+ updateProgressBar(`Importing ${fileData.theme[id].name}`);
239
+ }
240
+ }
241
+ putThemes(fileData.theme).then((result) => {
242
+ if (result == null) {
243
+ stopProgressBar(
244
+ `Error importing ${Object.keys(fileData.theme).length} themes!`
245
+ );
246
+ printMessage(
247
+ `Error importing ${
248
+ Object.keys(fileData.theme).length
249
+ } themes from ${file}`,
250
+ 'error'
251
+ );
252
+ } else {
253
+ stopProgressBar(
254
+ `Successfully imported ${
255
+ Object.keys(fileData.theme).length
256
+ } themes.`
257
+ );
258
+ }
259
+ });
260
+ } else {
261
+ printMessage('Import validation failed...', 'error');
262
+ }
263
+ });
264
+ }
265
+
266
+ /**
267
+ * Import themes from separate files
268
+ */
269
+ export async function importThemesFromFiles() {
270
+ const names = fs.readdirSync('.');
271
+ const jsonFiles = names.filter((name) =>
272
+ name.toLowerCase().endsWith('.theme.json')
273
+ );
274
+
275
+ createProgressBar(jsonFiles.length, 'Importing themes...');
276
+ let fileData = null;
277
+ let count = 0;
278
+ let total = 0;
279
+ let files = 0;
280
+ for (const file of jsonFiles) {
281
+ const data = fs.readFileSync(file, 'utf8');
282
+ fileData = JSON.parse(data);
283
+ if (validateImport(fileData.meta)) {
284
+ count = Object.keys(fileData.theme).length;
285
+ // eslint-disable-next-line no-await-in-loop
286
+ const result = await putThemes(fileData.theme);
287
+ if (result == null) {
288
+ printMessage(`Error importing ${count} themes from ${file}`, 'error');
289
+ } else {
290
+ files += 1;
291
+ total += count;
292
+ updateProgressBar(`Imported ${count} theme(s) from ${file}`);
293
+ }
294
+ } else {
295
+ printMessage(`Validation of ${file} failed!`, 'error');
296
+ }
297
+ }
298
+ stopProgressBar(
299
+ `Finished importing ${total} theme(s) from ${files} file(s).`
300
+ );
301
+ }
302
+
303
+ /**
304
+ * Import first theme from file
305
+ * @param {String} file import file name
306
+ */
307
+ export async function importFirstThemeFromFile(file) {
308
+ fs.readFile(file, 'utf8', (err, data) => {
309
+ if (err) throw err;
310
+ const themeData = JSON.parse(data);
311
+ if (validateImport(themeData.meta)) {
312
+ createProgressBar(1, 'Importing theme...');
313
+ for (const id in themeData.theme) {
314
+ if ({}.hasOwnProperty.call(themeData.theme, id)) {
315
+ updateProgressBar(`Importing ${themeData.theme[id].name}`);
316
+ putTheme(id, themeData.theme[id]).then((result) => {
317
+ if (result == null) {
318
+ stopProgressBar(
319
+ `Error importing theme ${themeData.theme[id].name}`
320
+ );
321
+ printMessage(
322
+ `Error importing theme ${themeData.theme[id].name}`,
323
+ 'error'
324
+ );
325
+ } else {
326
+ stopProgressBar(
327
+ `Successfully imported theme ${themeData.theme[id].name}`
328
+ );
329
+ }
330
+ });
331
+ break;
332
+ }
333
+ }
334
+ } else {
335
+ printMessage('Import validation failed...', 'error');
336
+ }
337
+ });
338
+ }
339
+
340
+ /**
341
+ * Delete theme by id
342
+ * @param {String} id theme id
343
+ */
344
+ export async function deleteThemeCmd(id) {
345
+ showSpinner(`Deleting ${id}...`);
346
+ try {
347
+ await deleteTheme(id);
348
+ succeedSpinner(`Deleted ${id}.`);
349
+ } catch (error) {
350
+ failSpinner(`Error: ${error.message}`);
351
+ }
352
+ }
353
+
354
+ /**
355
+ * Delete theme by name
356
+ * @param {String} name theme name
357
+ */
358
+ export async function deleteThemeByNameCmd(name) {
359
+ showSpinner(`Deleting ${name}...`);
360
+ try {
361
+ await deleteThemeByName(name);
362
+ succeedSpinner(`Deleted ${name}.`);
363
+ } catch (error) {
364
+ failSpinner(`Error: ${error.message}`);
365
+ }
366
+ }
367
+
368
+ /**
369
+ * Delete all themes
370
+ */
371
+ export async function deleteThemesCmd() {
372
+ showSpinner(`Deleting all realm themes...`);
373
+ try {
374
+ await deleteThemes();
375
+ succeedSpinner(`Deleted all realm themes.`);
376
+ } catch (error) {
377
+ failSpinner(`Error: ${error.message}`);
378
+ }
379
+ }
@@ -0,0 +1,185 @@
1
+ import {
2
+ createKeyValueTable,
3
+ createProgressBar,
4
+ createTable,
5
+ failSpinner,
6
+ printMessage,
7
+ showSpinner,
8
+ stopProgressBar,
9
+ succeedSpinner,
10
+ updateProgressBar,
11
+ } from './utils/Console.js';
12
+ import {
13
+ deleteVariable,
14
+ getVariable,
15
+ getVariables,
16
+ putVariable,
17
+ setVariableDescription,
18
+ } from '../api/VariablesApi.js';
19
+ import wordwrap from './utils/Wordwrap.js';
20
+ import { resolveUserName } from './ManagedObjectOps.js';
21
+ import { decode } from '../api/utils/Base64.js';
22
+
23
+ /**
24
+ * List variables
25
+ * @param {boolean} long Long version, all the fields
26
+ */
27
+ export async function listVariables(long) {
28
+ let variables = [];
29
+ try {
30
+ variables = (await getVariables()).data.result;
31
+ } catch (error) {
32
+ printMessage(`${error.message}`, 'error');
33
+ printMessage(error.response.data, 'error');
34
+ }
35
+ if (long) {
36
+ const table = createTable([
37
+ 'Id'.brightCyan,
38
+ 'Value'.brightCyan,
39
+ 'Status'.brightCyan,
40
+ 'Description'.brightCyan,
41
+ 'Modifier'.brightCyan,
42
+ 'Modified'.brightCyan,
43
+ ]);
44
+ variables.sort((a, b) => a._id.localeCompare(b._id));
45
+ for (const variable of variables) {
46
+ table.push([
47
+ variable._id,
48
+ wordwrap(decode(variable.valueBase64), 40),
49
+ variable.loaded ? 'loaded'.brightGreen : 'unloaded'.brightRed,
50
+ wordwrap(variable.description, 40),
51
+ // eslint-disable-next-line no-await-in-loop
52
+ await resolveUserName('teammember', variable.lastChangedBy),
53
+ new Date(variable.lastChangeDate).toLocaleString(),
54
+ ]);
55
+ }
56
+ printMessage(table.toString());
57
+ } else {
58
+ variables.forEach((secret) => {
59
+ printMessage(secret._id);
60
+ });
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Create variable
66
+ * @param {String} variableId variable id
67
+ * @param {String} value variable value
68
+ * @param {String} description variable description
69
+ */
70
+ export async function createVariable(variableId, value, description) {
71
+ showSpinner(`Creating variable ${variableId}...`);
72
+ try {
73
+ await putVariable(variableId, value, description);
74
+ succeedSpinner(`Created variable ${variableId}`);
75
+ } catch (error) {
76
+ failSpinner(
77
+ `Error: ${error.response.data.code} - ${error.response.data.message}`
78
+ );
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Update variable
84
+ * @param {String} variableId variable id
85
+ * @param {String} value variable value
86
+ * @param {String} description variable description
87
+ */
88
+ export async function updateVariable(variableId, value, description) {
89
+ showSpinner(`Updating variable ${variableId}...`);
90
+ try {
91
+ await putVariable(variableId, value, description);
92
+ succeedSpinner(`Updated variable ${variableId}`);
93
+ } catch (error) {
94
+ failSpinner(
95
+ `Error: ${error.response.data.code} - ${error.response.data.message}`
96
+ );
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Set description of variable
102
+ * @param {String} variableId variable id
103
+ * @param {String} description variable description
104
+ */
105
+ export async function setDescriptionOfVariable(variableId, description) {
106
+ showSpinner(`Setting description of variable ${variableId}...`);
107
+ try {
108
+ await setVariableDescription(variableId, description);
109
+ succeedSpinner(`Set description of variable ${variableId}`);
110
+ } catch (error) {
111
+ failSpinner(
112
+ `Error: ${error.response.data.code} - ${error.response.data.message}`
113
+ );
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Delete a variable
119
+ * @param {String} variableId variable id
120
+ */
121
+ export async function deleteVariableCmd(variableId) {
122
+ showSpinner(`Deleting variable ${variableId}...`);
123
+ try {
124
+ await deleteVariable(variableId);
125
+ succeedSpinner(`Deleted variable ${variableId}`);
126
+ } catch (error) {
127
+ failSpinner(
128
+ `Error: ${error.response.data.code} - ${error.response.data.message}`
129
+ );
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Delete all variables
135
+ */
136
+ export async function deleteVariablesCmd() {
137
+ try {
138
+ const variables = (await getVariables()).data.result;
139
+ createProgressBar(variables.length, `Deleting variable...`);
140
+ for (const variable of variables) {
141
+ try {
142
+ // eslint-disable-next-line no-await-in-loop
143
+ await deleteVariable(variable._id);
144
+ updateProgressBar(`Deleted variable ${variable._id}`);
145
+ } catch (error) {
146
+ printMessage(
147
+ `Error: ${error.response.data.code} - ${error.response.data.message}`,
148
+ 'error'
149
+ );
150
+ }
151
+ }
152
+ stopProgressBar(`Variables deleted.`);
153
+ } catch (error) {
154
+ printMessage(
155
+ `Error: ${error.response.data.code} - ${error.response.data.message}`,
156
+ 'error'
157
+ );
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Describe a variable
163
+ * @param {String} variableId variable id
164
+ */
165
+ export async function describeVariable(variableId) {
166
+ const variable = (await getVariable(variableId)).data;
167
+ const table = createKeyValueTable();
168
+ table.push(['Name'.brightCyan, variable._id]);
169
+ table.push(['Value'.brightCyan, wordwrap(decode(variable.valueBase64), 40)]);
170
+ table.push([
171
+ 'Status'.brightCyan,
172
+ variable.loaded ? 'loaded'.brightGreen : 'unloaded'.brightRed,
173
+ ]);
174
+ table.push(['Description'.brightCyan, wordwrap(variable.description, 60)]);
175
+ table.push([
176
+ 'Modified'.brightCyan,
177
+ new Date(variable.lastChangeDate).toLocaleString(),
178
+ ]);
179
+ table.push([
180
+ 'Modifier'.brightCyan,
181
+ await resolveUserName('teammember', variable.lastChangedBy),
182
+ ]);
183
+ table.push(['Modifier UUID'.brightCyan, variable.lastChangedBy]);
184
+ printMessage(table.toString());
185
+ }