mage-remote-run 0.23.1 → 0.25.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/lib/b2b.js ADDED
@@ -0,0 +1,19 @@
1
+ export const B2B_MODULES = [
2
+ 'Magento_Company',
3
+ 'Magento_CompanyCredit',
4
+ 'Magento_CompanyPayment',
5
+ 'Magento_CompanyShipping',
6
+ 'Magento_NegotiableQuote',
7
+ 'Magento_PurchaseOrder',
8
+ 'Magento_RequisitionList',
9
+ 'Magento_SharedCatalog'
10
+ ];
11
+
12
+ export function getMissingB2BModules(modules = []) {
13
+ const installed = new Set(modules);
14
+ return B2B_MODULES.filter(moduleName => !installed.has(moduleName));
15
+ }
16
+
17
+ export function hasB2BModules(modules = []) {
18
+ return getMissingB2BModules(modules).length === 0;
19
+ }
@@ -16,8 +16,9 @@ import { registerImportCommands } from './commands/import.js';
16
16
  import { registerModulesCommands } from './commands/modules.js';
17
17
  import { registerConsoleCommand } from './commands/console.js';
18
18
  import { registerShipmentCommands } from './commands/shipments.js';
19
+ import { registerRestCommands } from './commands/rest.js';
19
20
 
20
- export { registerConnectionCommands, registerConsoleCommand, registerShipmentCommands };
21
+ export { registerConnectionCommands, registerConsoleCommand, registerShipmentCommands, registerRestCommands };
21
22
 
22
23
  const GROUPS = {
23
24
  CORE: [
@@ -31,7 +32,8 @@ const GROUPS = {
31
32
  registerTaxCommands,
32
33
  registerInventoryCommands,
33
34
  registerShipmentCommands,
34
- registerConsoleCommand
35
+ registerConsoleCommand,
36
+ registerRestCommands
35
37
  ],
36
38
  COMMERCE: [
37
39
  registerCompanyCommands,
@@ -57,14 +59,32 @@ const TYPE_MAPPINGS = {
57
59
  'ac-saas': [...GROUPS.CORE, ...GROUPS.COMMERCE, ...GROUPS.CLOUD, ...GROUPS.IMPORT] // Assuming SaaS has same feature set as PaaS for CLI
58
60
  };
59
61
 
62
+ const B2B_COMMANDS = new Set([registerCompanyCommands, registerPurchaseOrderCartCommands]);
63
+ const B2B_CHECK_TYPES = new Set(['ac-on-prem', 'ac-cloud-paas']);
64
+
65
+ function shouldRegisterCommandForProfile(registrar, profile) {
66
+ if (!B2B_COMMANDS.has(registrar)) {
67
+ return true;
68
+ }
69
+ if (!profile || !profile.type) {
70
+ return false;
71
+ }
72
+ if (!B2B_CHECK_TYPES.has(profile.type)) {
73
+ return true;
74
+ }
75
+ return profile.b2bModulesAvailable === true;
76
+ }
77
+
60
78
  export function registerCoreCommands(program) {
61
79
  // Backward compatibility: Register CORE group
62
80
  GROUPS.CORE.forEach(registrar => registrar(program));
63
81
  }
64
82
 
65
- export function registerCloudCommands(program) {
83
+ export function registerCloudCommands(program, profile = null) {
66
84
  // Backward compatibility
67
- [...GROUPS.COMMERCE, ...GROUPS.CLOUD, ...GROUPS.IMPORT].forEach(registrar => registrar(program));
85
+ [...GROUPS.COMMERCE, ...GROUPS.CLOUD, ...GROUPS.IMPORT]
86
+ .filter(registrar => shouldRegisterCommandForProfile(registrar, profile))
87
+ .forEach(registrar => registrar(program, profile));
68
88
  }
69
89
 
70
90
  // Deprecated: Use registerCommands instead
@@ -80,7 +100,9 @@ export const registerCommands = (program, profile) => {
80
100
  if (profile && profile.type) {
81
101
  const registrars = TYPE_MAPPINGS[profile.type];
82
102
  if (registrars) {
83
- registrars.forEach(registrar => registrar(program, profile));
103
+ registrars
104
+ .filter(registrar => shouldRegisterCommandForProfile(registrar, profile))
105
+ .forEach(registrar => registrar(program, profile));
84
106
  } else {
85
107
  // Fallback for unknown types
86
108
  GROUPS.CORE.forEach(registrar => registrar(program));
@@ -5,10 +5,12 @@ import { createClient } from '../api/factory.js';
5
5
  import { input, confirm, select } from '@inquirer/prompts';
6
6
  import inquirer from 'inquirer';
7
7
  import chalk from 'chalk';
8
+ import { getMissingB2BModules } from '../b2b.js';
8
9
 
9
10
  // Helper to handle interactive connection configuration and testing
10
11
  async function configureAndTestConnection(name, initialSettings = {}) {
11
12
  let settings = await askForProfileSettings(initialSettings);
13
+ let lastTestError = null;
12
14
 
13
15
  while (true) {
14
16
  const shouldTest = await confirm({
@@ -24,9 +26,11 @@ async function configureAndTestConnection(name, initialSettings = {}) {
24
26
  await client.get('V1/store/storeViews');
25
27
  const duration = Date.now() - start;
26
28
  console.log(chalk.green(`✔ Connection successful! (${duration}ms)`));
29
+ lastTestError = null;
27
30
  break; // Test passed, proceed to save
28
31
  } catch (e) {
29
32
  console.error(chalk.red(`✖ Connection failed: ${e.message}`));
33
+ lastTestError = e;
30
34
  const shouldEdit = await confirm({
31
35
  message: 'Connection failed. Do you want to change settings?',
32
36
  default: true
@@ -53,20 +57,160 @@ async function configureAndTestConnection(name, initialSettings = {}) {
53
57
  break;
54
58
  }
55
59
  }
60
+ settings = await updateProfileCapabilities(settings, lastTestError);
56
61
  return settings;
57
62
  }
58
63
 
64
+ function shouldCheckB2BModules(settings) {
65
+ return settings && ['ac-cloud-paas', 'ac-on-prem'].includes(settings.type);
66
+ }
67
+
68
+ function shouldCheckHyvaModules(settings) {
69
+ return settings && ['magento-os', 'mage-os', 'ac-on-prem', 'ac-cloud-paas'].includes(settings.type);
70
+ }
71
+
72
+ async function updateProfileCapabilities(settings, lastTestError) {
73
+ if (settings && settings.type === 'ac-saas') {
74
+ settings.b2bModulesAvailable = true;
75
+ settings.hyvaCommerceAvailable = false;
76
+ settings.hyvaThemeAvailable = false;
77
+ return settings;
78
+ }
79
+
80
+ if (!shouldCheckB2BModules(settings) && !shouldCheckHyvaModules(settings)) {
81
+ if (settings && 'b2bModulesAvailable' in settings) delete settings.b2bModulesAvailable;
82
+ if (settings && 'hyvaCommerceAvailable' in settings) delete settings.hyvaCommerceAvailable;
83
+ if (settings && 'hyvaThemeAvailable' in settings) delete settings.hyvaThemeAvailable;
84
+ if (settings && 'hyvaModulesAvailable' in settings) delete settings.hyvaModulesAvailable; // Cleanup legacy
85
+ return settings;
86
+ }
87
+
88
+ try {
89
+ const client = await createClient(settings);
90
+ const data = await client.get('V1/modules');
91
+ const modules = Array.isArray(data) ? data : (Array.isArray(data?.items) ? data.items : []);
92
+
93
+ if (shouldCheckB2BModules(settings)) {
94
+ const missing = getMissingB2BModules(modules);
95
+ settings.b2bModulesAvailable = missing.length === 0;
96
+ if (process.env.DEBUG) {
97
+ if (settings.b2bModulesAvailable) {
98
+ console.log(chalk.gray('DEBUG: B2B modules detected.'));
99
+ } else {
100
+ console.log(chalk.gray(`DEBUG: Missing B2B modules: ${missing.join(', ')}`));
101
+ }
102
+ }
103
+ }
104
+
105
+ if (shouldCheckHyvaModules(settings)) {
106
+ settings.hyvaCommerceAvailable = modules.includes('Hyva_Commerce');
107
+ settings.hyvaThemeAvailable = modules.includes('Hyva_Theme');
108
+
109
+ // Cleanup old property if exists
110
+ if ('hyvaModulesAvailable' in settings) delete settings.hyvaModulesAvailable;
111
+
112
+ if (process.env.DEBUG) {
113
+ if (settings.hyvaCommerceAvailable) console.log(chalk.gray('DEBUG: Hyvä Commerce detected.'));
114
+ if (settings.hyvaThemeAvailable) console.log(chalk.gray('DEBUG: Hyvä Theme detected.'));
115
+ }
116
+ }
117
+
118
+ } catch (e) {
119
+ if (process.env.DEBUG) {
120
+ const suffix = lastTestError ? ' (connection test failed)' : '';
121
+ console.log(chalk.gray(`DEBUG: Unable to detect modules${suffix}: ${e.message}`));
122
+ }
123
+ if (shouldCheckB2BModules(settings)) settings.b2bModulesAvailable = null;
124
+ if (shouldCheckHyvaModules(settings)) {
125
+ settings.hyvaCommerceAvailable = null;
126
+ settings.hyvaThemeAvailable = null;
127
+ }
128
+ }
129
+
130
+ return settings;
131
+ }
132
+
133
+ async function ensureProfileCapabilities(profileName, profile, config) {
134
+ if (!profile || !profile.type) {
135
+ return false;
136
+ }
137
+
138
+ let updated = false;
139
+
140
+ // Migrate old property if exists
141
+ if ('hyvaModulesAvailable' in profile) {
142
+ profile.hyvaCommerceAvailable = profile.hyvaModulesAvailable;
143
+ delete profile.hyvaModulesAvailable;
144
+ updated = true;
145
+ }
146
+
147
+ if (profile.type === 'ac-saas') {
148
+ if (profile.b2bModulesAvailable !== true) {
149
+ profile.b2bModulesAvailable = true;
150
+ updated = true;
151
+ }
152
+ if (profile.hyvaCommerceAvailable !== false) {
153
+ profile.hyvaCommerceAvailable = false;
154
+ updated = true;
155
+ }
156
+ if (profile.hyvaThemeAvailable !== false) {
157
+ profile.hyvaThemeAvailable = false;
158
+ updated = true;
159
+ }
160
+ if (updated) config.profiles[profileName] = profile;
161
+ return updated;
162
+ }
163
+
164
+ const checkB2B = shouldCheckB2BModules(profile);
165
+ const checkHyva = shouldCheckHyvaModules(profile);
166
+
167
+ if (!checkB2B && !checkHyva) return false;
168
+
169
+ // Check if we need to detect
170
+ const needsB2B = checkB2B && profile.b2bModulesAvailable === undefined;
171
+ const needsHyva = checkHyva && (profile.hyvaCommerceAvailable === undefined || profile.hyvaThemeAvailable === undefined);
172
+
173
+ if (needsB2B || needsHyva) {
174
+ const newSettings = await updateProfileCapabilities({ ...profile });
175
+
176
+ if (checkB2B) {
177
+ profile.b2bModulesAvailable = newSettings.b2bModulesAvailable;
178
+ }
179
+ if (checkHyva) {
180
+ profile.hyvaCommerceAvailable = newSettings.hyvaCommerceAvailable;
181
+ profile.hyvaThemeAvailable = newSettings.hyvaThemeAvailable;
182
+ }
183
+
184
+ config.profiles[profileName] = profile;
185
+ return true;
186
+ }
187
+
188
+ if (updated) config.profiles[profileName] = profile;
189
+ return updated;
190
+ }
59
191
 
60
192
  // Helper to print connection status
61
- async function printConnectionStatus(config) {
193
+ async function printConnectionStatus(config, options = {}) {
62
194
  if (!config.activeProfile) {
63
- console.log(chalk.yellow('No active profile configured. Run "connection add" or "connection select".'));
195
+ if (options.format === 'json') {
196
+ console.log(JSON.stringify({ error: 'No active profile configured' }));
197
+ } else {
198
+ console.log(chalk.yellow('No active profile configured. Run "connection add" or "connection select".'));
199
+ }
64
200
  return;
65
201
  }
66
202
 
67
203
  const profile = config.profiles[config.activeProfile];
68
204
 
69
205
  if (profile) {
206
+ if (options.format === 'json') {
207
+ console.log(JSON.stringify({
208
+ activeProfile: config.activeProfile,
209
+ ...profile
210
+ }, null, 2));
211
+ return;
212
+ }
213
+
70
214
  // ASCII Logos
71
215
  const logos = {
72
216
  adobe: chalk.red(`
@@ -149,9 +293,43 @@ async function printConnectionStatus(config) {
149
293
  console.log(logo);
150
294
  }
151
295
 
152
- console.log(chalk.bold('Active Profile:'), chalk.green(config.activeProfile));
153
- console.log(`Type: ${profile.type}`);
154
- console.log(`URL: ${profile.url}`);
296
+ const rows = [
297
+ ['Active Profile', chalk.green(config.activeProfile)],
298
+ ['Type', profile.type],
299
+ ['URL', profile.url]
300
+ ];
301
+
302
+ if (['ac-cloud-paas', 'ac-on-prem', 'ac-saas'].includes(profile.type)) {
303
+ let b2bStatus = '?';
304
+ if (profile.type === 'ac-saas') {
305
+ b2bStatus = chalk.green('Yes');
306
+ } else if (profile.b2bModulesAvailable === true) {
307
+ b2bStatus = chalk.green('Yes');
308
+ } else if (profile.b2bModulesAvailable === false) {
309
+ b2bStatus = chalk.yellow('No');
310
+ }
311
+ rows.push(['B2B Modules', b2bStatus]);
312
+ }
313
+
314
+ if (['magento-os', 'mage-os', 'ac-on-prem', 'ac-cloud-paas'].includes(profile.type)) {
315
+ let hyvaCommerceStatus = '?';
316
+ if (profile.hyvaCommerceAvailable === true) {
317
+ hyvaCommerceStatus = chalk.green('Yes');
318
+ } else if (profile.hyvaCommerceAvailable === false) {
319
+ hyvaCommerceStatus = chalk.yellow('No');
320
+ }
321
+ rows.push(['Hyvä Commerce', hyvaCommerceStatus]);
322
+
323
+ let hyvaThemeStatus = '?';
324
+ if (profile.hyvaThemeAvailable === true) {
325
+ hyvaThemeStatus = chalk.green('Yes');
326
+ } else if (profile.hyvaThemeAvailable === false) {
327
+ hyvaThemeStatus = chalk.yellow('No');
328
+ }
329
+ rows.push(['Hyvä Theme', hyvaThemeStatus]);
330
+ }
331
+
332
+ printTable(['Configuration', 'Value'], rows);
155
333
  } else {
156
334
  console.log(chalk.red('Profile not found in configuration!'));
157
335
  }
@@ -217,20 +395,117 @@ Examples:
217
395
  //-------------------------------------------------------
218
396
  connections.command('list')
219
397
  .description('List connection profiles')
398
+ .option('--format <format>', 'Output format (table, json, csv)', 'table')
220
399
  .addHelpText('after', `
221
400
  Examples:
222
401
  $ mage-remote-run connection list
402
+ $ mage-remote-run connection list --format json
403
+ $ mage-remote-run connection list --format csv
223
404
  `)
224
- .action(async () => {
405
+ .action(async (options) => {
225
406
  try {
226
407
  const config = await loadConfig();
408
+ let updated = false;
409
+ for (const [name, profile] of Object.entries(config.profiles || {})) {
410
+ updated = (await ensureProfileCapabilities(name, profile, config)) || updated;
411
+ }
412
+ if (updated) {
413
+ await saveConfig(config);
414
+ }
415
+
416
+ if (options.format === 'json') {
417
+ console.log(JSON.stringify(config.profiles || {}, null, 2));
418
+ return;
419
+ }
420
+
421
+ if (options.format === 'csv') {
422
+ const { stringify } = await import('csv-stringify/sync');
423
+ const rows = Object.entries(config.profiles || {}).map(([name, p]) => ({
424
+ name,
425
+ type: p.type,
426
+ url: p.url,
427
+ b2b_modules_available: p.b2bModulesAvailable,
428
+ hyva_theme_available: p.hyvaThemeAvailable,
429
+ hyva_commerce_available: p.hyvaCommerceAvailable,
430
+ active: name === config.activeProfile
431
+ }));
432
+ console.log(stringify(rows, { header: true }));
433
+ return;
434
+ }
435
+
227
436
  const rows = Object.entries(config.profiles || {}).map(([name, p]) => [
228
437
  name,
229
438
  p.type,
230
439
  p.url,
440
+ ['ac-cloud-paas', 'ac-on-prem', 'ac-saas'].includes(p.type)
441
+ ? (p.type === 'ac-saas'
442
+ ? 'Yes'
443
+ : (p.b2bModulesAvailable === true ? 'Yes' : (p.b2bModulesAvailable === false ? 'No' : '?')))
444
+ : '',
445
+ (p.hyvaThemeAvailable === true ? 'Yes' : (p.hyvaThemeAvailable === false ? 'No' : '?')),
446
+ (p.hyvaCommerceAvailable === true ? 'Yes' : (p.hyvaCommerceAvailable === false ? 'No' : '?')),
231
447
  name === config.activeProfile ? chalk.green('Yes') : 'No'
232
448
  ]);
233
- printTable(['Name', 'Type', 'URL', 'Active'], rows);
449
+
450
+ const headers = ['Name', 'Type', 'URL', 'B2B', 'Hyvä Theme', 'Hyvä Comm.', 'Active'];
451
+ const termWidth = process.stdout.columns;
452
+
453
+ if (termWidth) {
454
+ const visibleLength = (str) => {
455
+ return ('' + str).replace(/\u001b\[[0-9;]*m/g, '').length;
456
+ };
457
+
458
+ const colWidths = headers.map((h, i) => {
459
+ return Math.max(h.length, ...rows.map(r => visibleLength(r[i])));
460
+ });
461
+
462
+ const overhead = (headers.length * 3) + 1;
463
+ const totalWidth = colWidths.reduce((a, b) => a + b, 0) + overhead;
464
+
465
+ if (totalWidth > termWidth) {
466
+ const available = termWidth - overhead;
467
+ // Indices: 0=Name, 1=Type, 2=URL, 3=B2B, 4=HT, 5=HC, 6=Active
468
+ const fixedIndices = [1, 3, 4, 5, 6];
469
+ const fixedWidth = fixedIndices.reduce((sum, i) => sum + colWidths[i], 0);
470
+
471
+ let fluidWidth = available - fixedWidth;
472
+ if (fluidWidth < 20) fluidWidth = 20;
473
+
474
+ const nameWidth = colWidths[0];
475
+ const urlWidth = colWidths[2];
476
+ let targetNameWidth = nameWidth;
477
+ let targetUrlWidth = urlWidth;
478
+
479
+ // Try to preserve Name, sacrifice URL
480
+ if (nameWidth + urlWidth > fluidWidth) {
481
+ // Keep Name as is if possible (assuming min URL width of 20)
482
+ if (nameWidth + 20 <= fluidWidth) {
483
+ targetUrlWidth = fluidWidth - nameWidth;
484
+ targetNameWidth = nameWidth;
485
+ } else {
486
+ // We have to cut Name too
487
+ // Give Name 30% or min 15
488
+ let w = Math.floor(fluidWidth * 0.3);
489
+ if (w < 15) w = 15;
490
+ targetNameWidth = Math.min(nameWidth, w);
491
+ targetUrlWidth = fluidWidth - targetNameWidth;
492
+ }
493
+ }
494
+
495
+ const truncate = (str, len) => {
496
+ if (!str) return str;
497
+ if (visibleLength(str) <= len) return str;
498
+ return str.substring(0, len - 3) + '...';
499
+ };
500
+
501
+ rows.forEach(row => {
502
+ row[0] = truncate(row[0], targetNameWidth);
503
+ row[2] = truncate(row[2], targetUrlWidth);
504
+ });
505
+ }
506
+ }
507
+
508
+ printTable(headers, rows);
234
509
  } catch (e) { handleError(e); }
235
510
  });
236
511
 
@@ -401,14 +676,21 @@ Examples:
401
676
  //-------------------------------------------------------
402
677
  connections.command('status')
403
678
  .description('Show current configuration status')
679
+ .option('--format <format>', 'Output format (text, json)', 'text')
404
680
  .addHelpText('after', `
405
681
  Examples:
406
682
  $ mage-remote-run connection status
683
+ $ mage-remote-run connection status --format json
407
684
  `)
408
- .action(async () => {
685
+ .action(async (options) => {
409
686
  try {
410
687
  const config = await loadConfig();
411
- await printConnectionStatus(config);
688
+ const activeProfileName = config.activeProfile;
689
+ const activeProfile = activeProfileName ? config.profiles[activeProfileName] : null;
690
+ if (activeProfile && await ensureProfileCapabilities(activeProfileName, activeProfile, config)) {
691
+ await saveConfig(config);
692
+ }
693
+ await printConnectionStatus(config, options);
412
694
  } catch (e) { handleError(e); }
413
695
  });
414
696
 
@@ -0,0 +1,136 @@
1
+ import { createClient } from '../api/factory.js';
2
+ import { handleError } from '../utils.js';
3
+ import chalk from 'chalk';
4
+ import { input, select, editor } from '@inquirer/prompts';
5
+
6
+ export function registerRestCommands(program) {
7
+ program.command('rest [path]')
8
+ .description('Execute a manual REST API request')
9
+ .option('-m, --method <method>', 'HTTP Method (GET, POST, PUT, DELETE)')
10
+ .option('-d, --data <data>', 'Request body data (JSON)')
11
+ .option('-q, --query <string>', 'Query parameters (e.g. "a=1&b=2")')
12
+ .option('--page-size <number>', 'Search Criteria Page Size')
13
+ .option('--current-page <number>', 'Search Criteria Current Page')
14
+ .option('-c, --content-type <type>', 'Content-Type', 'application/json')
15
+ .option('-f, --format <type>', 'Output format (json, xml)')
16
+ .addHelpText('after', `
17
+ Examples:
18
+ $ mage-remote-run rest V1/store/websites
19
+ $ mage-remote-run rest V1/customers/1 -m GET
20
+ $ mage-remote-run rest V1/customers -m POST -d '{"customer": {"email": "test@example.com", ...}}'
21
+ $ mage-remote-run rest V1/products -m GET -q "searchCriteria[pageSize]=10&fields=items[sku,name]"
22
+ $ mage-remote-run rest V1/products -m GET --page-size 10 --current-page 1
23
+ `)
24
+ .action(async (path, options) => {
25
+ try {
26
+ const client = await createClient();
27
+
28
+ // 1. Path
29
+ let requestPath = path;
30
+ if (!requestPath) {
31
+ requestPath = await input({
32
+ message: 'Enter the endpoint path (relative to base URL):',
33
+ validate: (value) => value.length > 0 ? true : 'Path is required'
34
+ });
35
+ }
36
+
37
+ // 2. Method
38
+ let method = options.method;
39
+ if (!method) {
40
+ method = await select({
41
+ message: 'Select HTTP Method:',
42
+ choices: [
43
+ { name: 'GET', value: 'GET' },
44
+ { name: 'POST', value: 'POST' },
45
+ { name: 'PUT', value: 'PUT' },
46
+ { name: 'DELETE', value: 'DELETE' }
47
+ ]
48
+ });
49
+ }
50
+ method = method.toUpperCase();
51
+
52
+ // 3. Data (Body)
53
+ let data = options.data;
54
+ const contentType = options.contentType || 'application/json';
55
+
56
+ if ((method === 'POST' || method === 'PUT') && data === undefined) {
57
+ data = await editor({
58
+ message: 'Enter request body:',
59
+ default: contentType === 'application/json' ? '{\n \n}' : ''
60
+ });
61
+ }
62
+
63
+ // Validate and Parse JSON
64
+ let parsedData = data;
65
+ if (contentType === 'application/json' && data) {
66
+ if (typeof data === 'string') {
67
+ try {
68
+ parsedData = JSON.parse(data);
69
+ } catch (e) {
70
+ throw new Error('Invalid JSON data provided.');
71
+ }
72
+ }
73
+ }
74
+
75
+ // 4. Execution
76
+ if (!options.format) {
77
+ console.log(chalk.gray(`Executing ${method} ${requestPath}...`));
78
+ }
79
+
80
+ const config = {
81
+ headers: {
82
+ 'Content-Type': contentType
83
+ }
84
+ };
85
+
86
+ if (options.format === 'json') {
87
+ config.headers['Accept'] = 'application/json';
88
+ } else if (options.format === 'xml') {
89
+ config.headers['Accept'] = 'application/xml';
90
+ }
91
+
92
+ // Parse query options
93
+ let params = {};
94
+ if (options.query) {
95
+ const searchParams = new URLSearchParams(options.query);
96
+ for (const [key, value] of searchParams) {
97
+ params[key] = value;
98
+ }
99
+ }
100
+
101
+ if (options.pageSize) {
102
+ params['searchCriteria[pageSize]'] = options.pageSize;
103
+ }
104
+
105
+ if (options.currentPage) {
106
+ params['searchCriteria[currentPage]'] = options.currentPage;
107
+ }
108
+
109
+ const response = await client.request(method, requestPath, parsedData, params, config);
110
+
111
+ // 5. Output
112
+ if (options.format === 'json') {
113
+ // Ensure we output valid JSON even if response is already an object
114
+ if (typeof response === 'object') {
115
+ console.log(JSON.stringify(response, null, 2));
116
+ } else {
117
+ // Attempt to parse if string, otherwise output as is (or error?)
118
+ // Usually response.data is parsed by axios if json.
119
+ console.log(response);
120
+ }
121
+ } else if (options.format === 'xml') {
122
+ console.log(response);
123
+ } else {
124
+ // Default behavior
125
+ if (typeof response === 'object') {
126
+ console.log(JSON.stringify(response, null, 2));
127
+ } else {
128
+ console.log(response);
129
+ }
130
+ }
131
+
132
+ } catch (e) {
133
+ handleError(e);
134
+ }
135
+ });
136
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mage-remote-run",
3
- "version": "0.23.1",
3
+ "version": "0.25.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": {
@@ -39,6 +39,7 @@
39
39
  "cli-table3": "^0.6.5",
40
40
  "commander": "^14.0.2",
41
41
  "csv-parse": "^6.1.0",
42
+ "csv-stringify": "^6.6.0",
42
43
  "env-paths": "^3.0.0",
43
44
  "html-to-text": "^9.0.5",
44
45
  "inquirer": "^13.1.0",