mage-remote-run 0.18.0 → 0.20.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.
@@ -1,24 +1,18 @@
1
1
  import { createClient } from '../api/factory.js';
2
2
  import { printTable, handleError } from '../utils.js';
3
3
  import chalk from 'chalk';
4
+ import { input, select, confirm } from '@inquirer/prompts';
5
+ import search from '@inquirer/search';
4
6
 
5
7
  export function registerWebhooksCommands(program) {
6
- const webhooks = program.command('webhook').description('Manage webhooks');
7
-
8
+ const webhooks = program.command('webhook').description('Manage webhooks (SaaS)');
8
9
 
9
10
  //-------------------------------------------------------
10
11
  // "webhook list" Command
11
12
  //-------------------------------------------------------
12
13
  webhooks.command('list')
13
14
  .description('List available webhooks')
14
- .option('-p, --page <number>', 'Page number', '1')
15
- .option('-s, --size <number>', 'Page size', '20')
16
15
  .option('-f, --format <type>', 'Output format (text, json, xml)', 'text')
17
- .addHelpText('after', `
18
- Examples:
19
- $ mage-remote-run webhook list
20
- $ mage-remote-run webhook list --format json
21
- `)
22
16
  .action(async (options) => {
23
17
  try {
24
18
  const client = await createClient();
@@ -26,12 +20,8 @@ Examples:
26
20
  if (options.format === 'json') headers['Accept'] = 'application/json';
27
21
  else if (options.format === 'xml') headers['Accept'] = 'application/xml';
28
22
 
29
- const params = {
30
- 'searchCriteria[currentPage]': options.page,
31
- 'searchCriteria[pageSize]': options.size
32
- };
33
-
34
- const data = await client.get('V1/webhooks/list', params, { headers });
23
+ // SaaS API returns an array directly
24
+ const data = await client.get('V1/webhooks/list', {}, { headers });
35
25
 
36
26
  if (options.format === 'json') {
37
27
  console.log(JSON.stringify(data, null, 2));
@@ -42,16 +32,465 @@ Examples:
42
32
  return;
43
33
  }
44
34
 
45
- const rows = (data.items || []).map(w => [
46
- w.hook_id,
47
- w.name,
48
- w.status,
49
- w.store_ids ? w.store_ids.join(',') : '-',
35
+ const rows = (Array.isArray(data) ? data : []).map(w => [
36
+ w.hook_name,
37
+ w.webhook_type,
38
+ w.method,
39
+ w.priority !== undefined ? w.priority : '-',
50
40
  w.url
51
41
  ]);
52
42
 
53
- console.log(chalk.bold(`Total: ${data.total_count}, Page: ${options.page}, Size: ${options.size}`));
54
- printTable(['ID', 'Name', 'Status', 'Store IDs', 'URL'], rows);
43
+ console.log(chalk.bold(`Total: ${rows.length}`));
44
+ printTable(['Name', 'Type', 'Method', 'Priority', 'URL'], rows);
55
45
  } catch (e) { handleError(e); }
56
46
  });
47
+
48
+ //-------------------------------------------------------
49
+ // "webhook supported-list" Command
50
+ //-------------------------------------------------------
51
+ webhooks.command('supported-list')
52
+ .description('List supported webhook types')
53
+ .option('--format <format>', 'Output format (table/json)', 'table')
54
+ .action(async (options) => {
55
+ try {
56
+ const client = await createClient();
57
+
58
+ const headers = {};
59
+ if (options.format === 'json') headers['Accept'] = 'application/json';
60
+
61
+ const data = await client.get('V1/webhooks/supportedList', {}, { headers });
62
+
63
+ if (options.format === 'json') {
64
+ console.log(JSON.stringify(data, null, 2));
65
+ return;
66
+ }
67
+
68
+ // Extract webhook names from the response
69
+ const rows = (Array.isArray(data) ? data : []).map(w => [w.name]);
70
+
71
+ console.log(chalk.bold(`Total: ${rows.length}`));
72
+ printTable(['Webhook Method'], rows);
73
+ } catch (e) { handleError(e); }
74
+ });
75
+
76
+ //-------------------------------------------------------
77
+ // "webhook show" Command
78
+ //-------------------------------------------------------
79
+ webhooks.command('show')
80
+ .description('Get webhook details')
81
+ .argument('[name]', 'Webhook Name')
82
+ .option('-f, --format <type>', 'Output format (text, json, xml)', 'text')
83
+ .action(async (name, options) => {
84
+ try {
85
+ const client = await createClient();
86
+
87
+ // Always fetch list first as there is no direct "get by name" endpoint in swagger
88
+ const listData = await client.get('V1/webhooks/list');
89
+ const items = Array.isArray(listData) ? listData : [];
90
+
91
+ if (!name) {
92
+ if (items.length === 0) {
93
+ console.log(chalk.yellow('No webhooks found.'));
94
+ return;
95
+ }
96
+ name = await select({
97
+ message: 'Select Webhook:',
98
+ choices: items.map(w => ({
99
+ name: `${w.hook_name} (${w.webhook_type})`,
100
+ value: w.hook_name
101
+ }))
102
+ });
103
+ }
104
+
105
+ const webhook = items.find(w => w.hook_name === name);
106
+
107
+ if (!webhook) {
108
+ throw new Error(`Webhook "${name}" not found.`);
109
+ }
110
+
111
+ if (options.format === 'json') {
112
+ console.log(JSON.stringify(webhook, null, 2));
113
+ return;
114
+ }
115
+ if (options.format === 'xml') {
116
+ // Primitive XML conversion if needed, but JSON is standard
117
+ console.log(webhook);
118
+ return;
119
+ }
120
+
121
+ console.log(chalk.bold.blue('\nā„¹ļø Webhook Details'));
122
+ console.log(chalk.gray('━'.repeat(60)));
123
+
124
+ const fields = [
125
+ ['Name', webhook.hook_name],
126
+ ['Type', webhook.webhook_type],
127
+ ['URL', webhook.url],
128
+ ['Method', webhook.method],
129
+ ['Priority', webhook.priority],
130
+ ['Batch Name', webhook.batch_name],
131
+ ['Batch Order', webhook.batch_order],
132
+ ['Timeout', webhook.timeout],
133
+ ['Soft Timeout', webhook.soft_timeout],
134
+ ['Required', webhook.required ? 'Yes' : 'No'],
135
+ ['Active', webhook.active === false ? 'No' : 'Yes'] // Assuming default true if undefined
136
+ ];
137
+
138
+ fields.forEach(([label, value]) => {
139
+ console.log(` ${chalk.bold((label + ':').padEnd(20))} ${value !== undefined && value !== null ? value : '-'}`);
140
+ });
141
+ console.log('');
142
+
143
+ } catch (e) {
144
+ if (e.name === 'ExitPromptError') return;
145
+ handleError(e);
146
+ }
147
+ });
148
+
149
+ //-------------------------------------------------------
150
+ // Helper Functions
151
+ //-------------------------------------------------------
152
+
153
+ /**
154
+ * Parse JSON option string
155
+ */
156
+ function parseJsonOption(jsonString, fieldName) {
157
+ if (!jsonString) return undefined;
158
+ try {
159
+ return JSON.parse(jsonString);
160
+ } catch (e) {
161
+ throw new Error(`Invalid JSON for ${fieldName}: ${e.message}`);
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Collect fields interactively
167
+ */
168
+ async function collectFields() {
169
+ const fields = [];
170
+ const addFields = await confirm({ message: 'Add webhook fields?', default: false });
171
+ if (!addFields) return fields;
172
+
173
+ let addMore = true;
174
+ while (addMore) {
175
+ const name = await input({ message: 'Field name:', validate: v => v ? true : 'Required' });
176
+ const source = await input({ message: 'Field source:', validate: v => v ? true : 'Required' });
177
+ fields.push({ name, source });
178
+ addMore = await confirm({ message: 'Add another field?', default: false });
179
+ }
180
+ return fields;
181
+ }
182
+
183
+ /**
184
+ * Collect headers interactively
185
+ */
186
+ async function collectHeaders() {
187
+ const headers = [];
188
+ const addHeaders = await confirm({ message: 'Add webhook headers?', default: false });
189
+ if (!addHeaders) return headers;
190
+
191
+ let addMore = true;
192
+ while (addMore) {
193
+ const name = await input({ message: 'Header name:', validate: v => v ? true : 'Required' });
194
+ const value = await input({ message: 'Header value:', validate: v => v ? true : 'Required' });
195
+ headers.push({ name, value });
196
+ addMore = await confirm({ message: 'Add another header?', default: false });
197
+ }
198
+ return headers;
199
+ }
200
+
201
+ /**
202
+ * Collect rules interactively
203
+ */
204
+ async function collectRules() {
205
+ const rules = [];
206
+ const addRules = await confirm({ message: 'Add webhook rules?', default: false });
207
+ if (!addRules) return rules;
208
+
209
+ let addMore = true;
210
+ while (addMore) {
211
+ const field = await input({ message: 'Rule field:', validate: v => v ? true : 'Required' });
212
+ const operator = await select({
213
+ message: 'Operator:',
214
+ choices: [
215
+ { name: 'Equals (eq)', value: 'eq' },
216
+ { name: 'Not Equals (neq)', value: 'neq' },
217
+ { name: 'Greater Than (gt)', value: 'gt' },
218
+ { name: 'Less Than (lt)', value: 'lt' },
219
+ { name: 'Greater or Equal (gte)', value: 'gte' },
220
+ { name: 'Less or Equal (lte)', value: 'lte' }
221
+ ]
222
+ });
223
+ const value = await input({ message: 'Rule value:', validate: v => v ? true : 'Required' });
224
+ rules.push({ field, operator, value });
225
+ addMore = await confirm({ message: 'Add another rule?', default: false });
226
+ }
227
+ return rules;
228
+ }
229
+
230
+ //-------------------------------------------------------
231
+ // "webhook create" Command
232
+ //-------------------------------------------------------
233
+ webhooks.command('create')
234
+ .description('Subscribe to a new webhook')
235
+ .option('--name <name>', 'Hook Name')
236
+ .option('--webhook-method <method>', 'Webhook Method (e.g., observer.catalog_product_save_after)')
237
+ .option('--webhook-type <type>', 'Webhook Type (before/after)')
238
+ .option('--url <url>', 'Webhook URL')
239
+ .option('--method <method>', 'HTTP Method (POST/PUT/DELETE)')
240
+ .option('--batch-name <name>', 'Batch Name')
241
+ .option('--batch-order <n>', 'Batch Order')
242
+ .option('--priority <n>', 'Priority')
243
+ .option('--timeout <n>', 'Timeout in milliseconds')
244
+ .option('--soft-timeout <n>', 'Soft Timeout in milliseconds')
245
+ .option('--ttl <n>', 'Cache TTL')
246
+ .option('--fallback-error-message <msg>', 'Fallback Error Message')
247
+ .option('--required', 'Required')
248
+ .option('--fields <json>', 'Fields as JSON string')
249
+ .option('--headers <json>', 'Headers as JSON string')
250
+ .option('--rules <json>', 'Rules as JSON string')
251
+ .addHelpText('after', `
252
+ Examples:
253
+ # Interactive mode
254
+ $ mage-remote-run webhook create
255
+
256
+ # Create with all options
257
+ $ mage-remote-run webhook create \\
258
+ --name "Product Save Hook" \\
259
+ --webhook-method "observer.catalog_product_save_after" \\
260
+ --webhook-type "after" \\
261
+ --url https://example.com/webhook \\
262
+ --method POST \\
263
+ --timeout 5000 \\
264
+ --soft-timeout 3000 \\
265
+ --ttl 3600
266
+
267
+ # Create with fields, headers, and rules
268
+ $ mage-remote-run webhook create \\
269
+ --name "Order Complete" \\
270
+ --webhook-method "observer.sales_order_save_after" \\
271
+ --webhook-type "after" \\
272
+ --url https://example.com/orders \\
273
+ --fields '[{"name":"order_id","source":"order.entity_id"}]' \\
274
+ --headers '[{"name":"X-Auth","value":"token123"}]' \\
275
+ --rules '[{"field":"order.status","operator":"eq","value":"complete"}]'
276
+ `)
277
+ .action(async (options) => {
278
+ try {
279
+ const client = await createClient();
280
+
281
+ const name = options.name || await input({ message: 'Hook Name:', validate: v => v ? true : 'Required' });
282
+
283
+ let webhookMethod = options.webhookMethod;
284
+ if (!webhookMethod) {
285
+ // Fetch supported webhook types for interactive selection
286
+ let supportedTypes = [];
287
+ try {
288
+ const supportedData = await client.get('V1/webhooks/supportedList');
289
+ supportedTypes = Array.isArray(supportedData) ? supportedData.map(w => w.name) : [];
290
+ } catch (e) {
291
+ // If fetching fails, continue without suggestions
292
+ console.log(chalk.yellow('Warning: Could not fetch supported webhook types.'));
293
+ }
294
+
295
+ if (supportedTypes.length > 0) {
296
+ // Add custom option at the top
297
+ const CUSTOM_OPTION = '-- Enter custom webhook method --';
298
+
299
+ webhookMethod = await search({
300
+ message: 'Webhook Method:',
301
+ source: async (term) => {
302
+ const filtered = supportedTypes.filter(type =>
303
+ type.toLowerCase().includes((term || '').toLowerCase())
304
+ );
305
+ return [
306
+ { name: CUSTOM_OPTION, value: CUSTOM_OPTION },
307
+ ...filtered.map(type => ({ name: type, value: type }))
308
+ ];
309
+ }
310
+ });
311
+
312
+ // If custom option selected, prompt for manual input
313
+ if (webhookMethod === CUSTOM_OPTION) {
314
+ webhookMethod = await input({
315
+ message: 'Enter custom webhook method:',
316
+ validate: v => v ? true : 'Required'
317
+ });
318
+ }
319
+ } else {
320
+ // Fallback to regular input if no supported types available
321
+ webhookMethod = await input({
322
+ message: 'Webhook Method (e.g., observer.catalog_product_save_after):',
323
+ validate: v => v ? true : 'Required'
324
+ });
325
+ }
326
+ }
327
+
328
+ const webhookType = options.webhookType || await select({
329
+ message: 'Webhook Type:',
330
+ choices: [
331
+ { name: 'After', value: 'after' },
332
+ { name: 'Before', value: 'before' }
333
+ ],
334
+ default: 'after'
335
+ });
336
+
337
+ const url = options.url || await input({ message: 'Webhook URL:', validate: v => v ? true : 'Required' });
338
+
339
+ const method = options.method || await select({
340
+ message: 'HTTP Method:',
341
+ choices: [
342
+ { name: 'POST', value: 'POST' },
343
+ { name: 'PUT', value: 'PUT' },
344
+ { name: 'DELETE', value: 'DELETE' }
345
+ ],
346
+ default: 'POST'
347
+ });
348
+
349
+ // Batch name with default
350
+ const batchName = options.batchName || await input({
351
+ message: 'Batch Name:',
352
+ default: 'default'
353
+ });
354
+
355
+ // Batch order with default
356
+ const batchOrder = options.batchOrder || await input({
357
+ message: 'Batch Order:',
358
+ default: '0',
359
+ validate: v => !isNaN(parseInt(v)) || 'Must be a number'
360
+ });
361
+
362
+ // Priority with default
363
+ const priority = options.priority || await input({
364
+ message: 'Priority:',
365
+ default: '0',
366
+ validate: v => !isNaN(parseInt(v)) || 'Must be a number'
367
+ });
368
+
369
+ // Timeout with default
370
+ const timeout = options.timeout || await input({
371
+ message: 'Timeout (ms):',
372
+ default: '5000',
373
+ validate: v => !isNaN(parseInt(v)) || 'Must be a number'
374
+ });
375
+
376
+ // Soft timeout with default
377
+ const softTimeout = options.softTimeout || await input({
378
+ message: 'Soft Timeout (ms):',
379
+ default: '3000',
380
+ validate: v => !isNaN(parseInt(v)) || 'Must be a number'
381
+ });
382
+
383
+ // TTL with default
384
+ const ttl = options.ttl || await input({
385
+ message: 'Cache TTL:',
386
+ default: '3600',
387
+ validate: v => !isNaN(parseInt(v)) || 'Must be a number'
388
+ });
389
+
390
+ // Fallback error message
391
+ const fallbackErrorMessage = options.fallbackErrorMessage || await input({
392
+ message: 'Fallback Error Message:',
393
+ default: 'Webhook execution failed'
394
+ });
395
+
396
+ // Check if 'required' is explicitly passed
397
+ let isRequired = options.required;
398
+ if (isRequired === undefined) {
399
+ isRequired = await select({
400
+ message: 'Is Required?',
401
+ choices: [
402
+ { name: 'No', value: false },
403
+ { name: 'Yes', value: true }
404
+ ],
405
+ default: false
406
+ });
407
+ }
408
+
409
+ // Parse or collect fields, headers, rules
410
+ let fields = parseJsonOption(options.fields, 'fields');
411
+ let headers = parseJsonOption(options.headers, 'headers');
412
+ let rules = parseJsonOption(options.rules, 'rules');
413
+
414
+ // If not provided via options and we're in interactive mode, collect them
415
+ const isInteractive = !options.name || !options.webhookMethod || !options.url;
416
+ if (isInteractive) {
417
+ if (!fields) fields = await collectFields();
418
+ if (!headers) headers = await collectHeaders();
419
+ if (!rules) rules = await collectRules();
420
+ }
421
+
422
+ const payload = {
423
+ webhook: {
424
+ hook_name: name,
425
+ webhook_method: webhookMethod,
426
+ webhook_type: webhookType,
427
+ url: url,
428
+ method: method,
429
+ batch_name: batchName,
430
+ batch_order: parseInt(batchOrder),
431
+ priority: parseInt(priority),
432
+ timeout: parseInt(timeout),
433
+ soft_timeout: parseInt(softTimeout),
434
+ ttl: parseInt(ttl),
435
+ fallback_error_message: fallbackErrorMessage,
436
+ required: isRequired,
437
+ fields: fields || [],
438
+ headers: headers || [],
439
+ rules: rules || []
440
+ }
441
+ };
442
+
443
+ // Filter undefined
444
+ Object.keys(payload.webhook).forEach(key => payload.webhook[key] === undefined && delete payload.webhook[key]);
445
+
446
+ await client.post('V1/webhooks/subscribe', payload);
447
+
448
+ console.log(chalk.green(`\nāœ… Webhook "${name}" created (subscribed) successfully!`));
449
+ } catch (e) {
450
+ if (e.name === 'ExitPromptError') return;
451
+ handleError(e);
452
+ }
453
+ });
454
+
455
+ //-------------------------------------------------------
456
+ // "webhook delete" Command
457
+ //-------------------------------------------------------
458
+ webhooks.command('delete')
459
+ .description('Unsubscribe from a webhook')
460
+ .argument('[name]', 'Webhook Name')
461
+ .action(async (name) => {
462
+ try {
463
+ const client = await createClient();
464
+
465
+ const listData = await client.get('V1/webhooks/list');
466
+ const items = Array.isArray(listData) ? listData : [];
467
+
468
+ if (!name) {
469
+ if (items.length === 0) {
470
+ console.log(chalk.yellow('No webhooks found.'));
471
+ return;
472
+ }
473
+ name = await select({
474
+ message: 'Select Webhook to Delete:',
475
+ choices: items.map(w => ({
476
+ name: `${w.hook_name} (${w.webhook_type})`,
477
+ value: w.hook_name
478
+ }))
479
+ });
480
+ }
481
+
482
+ const webhook = items.find(w => w.hook_name === name);
483
+ if (!webhook) throw new Error(`Webhook "${name}" not found.`);
484
+
485
+ const shouldDelete = await confirm({ message: `Are you sure you want to delete webhook "${name}"?`, default: false });
486
+ if (!shouldDelete) return;
487
+
488
+ // Unsubscribe requires sending the webhook object wrapper
489
+ await client.post('V1/webhooks/unsubscribe', { webhook });
490
+ console.log(chalk.green(`\nāœ… Webhook "${name}" deleted (unsubscribed) successfully.`));
491
+ } catch (e) {
492
+ if (e.name === 'ExitPromptError') return;
493
+ handleError(e);
494
+ }
495
+ });
57
496
  }
package/lib/utils.js CHANGED
@@ -1,5 +1,8 @@
1
1
  import chalk from 'chalk';
2
2
  import Table from 'cli-table3';
3
+ import fs from 'fs';
4
+ import os from 'os';
5
+ import { loadConfig } from './config.js';
3
6
 
4
7
  export function printTable(headers, data) {
5
8
  const table = new Table({
@@ -42,3 +45,44 @@ export function handleError(error) {
42
45
  console.error(error);
43
46
  }
44
47
  }
48
+
49
+ export async function readInput(filePath) {
50
+ if (filePath) {
51
+ if (filePath.startsWith('~/') || filePath === '~') {
52
+ filePath = filePath.replace(/^~/, os.homedir());
53
+ }
54
+
55
+ if (!fs.existsSync(filePath)) {
56
+ throw new Error(`File not found: ${filePath}`);
57
+ }
58
+ return fs.readFileSync(filePath, 'utf8');
59
+ }
60
+
61
+ if (!process.stdin.isTTY) {
62
+ let data = '';
63
+ for await (const chunk of process.stdin) {
64
+ data += chunk;
65
+ }
66
+ return data;
67
+ }
68
+
69
+ return null;
70
+ }
71
+
72
+ export async function validateAdobeCommerce() {
73
+ const config = await loadConfig();
74
+ const profile = config.profiles[config.activeProfile];
75
+ const allowed = ['ac-cloud-paas', 'ac-saas', 'ac-on-prem'];
76
+ if (!profile || !allowed.includes(profile.type)) {
77
+ throw new Error('This command is only available for Adobe Commerce (Cloud, SaaS, On-Premise).');
78
+ }
79
+ }
80
+
81
+ export async function validatePaaSOrOnPrem() {
82
+ const config = await loadConfig();
83
+ const profile = config.profiles[config.activeProfile];
84
+ const allowed = ['ac-cloud-paas', 'ac-on-prem'];
85
+ if (!profile || !allowed.includes(profile.type)) {
86
+ throw new Error('This command is only available for Adobe Commerce (Cloud/On-Premise).');
87
+ }
88
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mage-remote-run",
3
- "version": "0.18.0",
3
+ "version": "0.20.0",
4
4
  "description": "The remote swiss army knife for Magento Open Source, Mage-OS, Adobe Commerce",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -32,11 +32,13 @@
32
32
  ],
33
33
  "dependencies": {
34
34
  "@inquirer/prompts": "^8.1.0",
35
+ "@inquirer/search": "^4.0.3",
35
36
  "@modelcontextprotocol/sdk": "^1.25.1",
36
37
  "axios": "^1.13.2",
37
38
  "chalk": "^5.6.2",
38
39
  "cli-table3": "^0.6.5",
39
40
  "commander": "^14.0.2",
41
+ "csv-parse": "^6.1.0",
40
42
  "env-paths": "^3.0.0",
41
43
  "html-to-text": "^9.0.5",
42
44
  "inquirer": "^13.1.0",
@@ -1,70 +0,0 @@
1
- import { createClient } from '../api/factory.js';
2
- import { printTable, handleError } from '../utils.js';
3
- import chalk from 'chalk';
4
-
5
- export function registerAdobeIoEventsCommands(program) {
6
- const adobeIoEvents = program.command('adobe-io-event').description('Manage Adobe I/O Events');
7
-
8
-
9
- //-------------------------------------------------------
10
- // "adobe-io-event check-configuration" Command
11
- //-------------------------------------------------------
12
- adobeIoEvents.command('check-configuration')
13
- .description('Check Adobe I/O Event configuration')
14
- .option('-f, --format <type>', 'Output format (text, json, xml)', 'text')
15
- .addHelpText('after', `
16
- Examples:
17
- $ mage-remote-run adobe-io-event check-configuration
18
- $ mage-remote-run adobe-io-event check-configuration --format json
19
- `)
20
- .action(async (options) => {
21
- try {
22
- const client = await createClient();
23
- const headers = {};
24
- if (options.format === 'json') headers['Accept'] = 'application/json';
25
- else if (options.format === 'xml') headers['Accept'] = 'application/xml';
26
-
27
- // The endpoint is likely returning a simple boolean/string or a small object.
28
- // Based on standard Magento APIs, let's assume it returns a boolean or object.
29
- // We'll inspect the data structure.
30
- const data = await client.get('V1/adobe_io_events/check_configuration', {}, { headers });
31
-
32
- if (options.format === 'json') {
33
- console.log(JSON.stringify(data, null, 2));
34
- return;
35
- }
36
- if (options.format === 'xml') {
37
- console.log(data);
38
- return;
39
- }
40
-
41
- console.log(chalk.bold.blue('\nšŸ” Configuration Check Result'));
42
- console.log(chalk.gray('━'.repeat(60)));
43
-
44
- if (typeof data === 'object' && data !== null) {
45
- if (Array.isArray(data)) {
46
- console.log(JSON.stringify(data, null, 2));
47
- } else {
48
- Object.entries(data).forEach(([key, value]) => {
49
- const label = key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
50
-
51
- let displayValue = value;
52
- if (typeof value === 'boolean') {
53
- displayValue = value ? chalk.green('Yes') : chalk.red('No');
54
- } else if (value === null) {
55
- displayValue = chalk.gray('null');
56
- } else if (typeof value === 'object') {
57
- displayValue = JSON.stringify(value);
58
- }
59
-
60
- console.log(` ${chalk.bold(label + ':').padEnd(35)} ${displayValue}`);
61
- });
62
- }
63
- } else {
64
- console.log(` ${data}`);
65
- }
66
- console.log('');
67
-
68
- } catch (e) { handleError(e); }
69
- });
70
- }