mage-remote-run 0.21.0 → 0.22.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.
@@ -6,6 +6,7 @@ import { registerOrdersCommands } from './commands/orders.js';
6
6
  import { registerEavCommands } from './commands/eav.js';
7
7
  import { registerProductsCommands } from './commands/products.js';
8
8
  import { registerCompanyCommands } from './commands/company.js';
9
+ import { registerCartCommands } from './commands/cart.js';
9
10
  import { registerTaxCommands } from './commands/tax.js';
10
11
  import { registerInventoryCommands } from './commands/inventory.js';
11
12
  import { registerEventsCommands } from './commands/events.js';
@@ -24,6 +25,7 @@ const GROUPS = {
24
25
  registerOrdersCommands,
25
26
  registerEavCommands,
26
27
  registerProductsCommands,
28
+ registerCartCommands,
27
29
  registerTaxCommands,
28
30
  registerInventoryCommands,
29
31
  registerConsoleCommand
@@ -0,0 +1,238 @@
1
+ import { createClient } from '../api/factory.js';
2
+ import { printTable, handleError } from '../utils.js';
3
+ import chalk from 'chalk';
4
+
5
+ export function registerCartCommands(program) {
6
+ const carts = program.command('cart').description('Manage carts');
7
+
8
+ //-------------------------------------------------------
9
+ // "cart show" Command
10
+ //-------------------------------------------------------
11
+ carts.command('show <cartId>')
12
+ .description('Show detailed cart information')
13
+ .option('-f, --format <type>', 'Output format (text, json, xml)', 'text')
14
+ .addHelpText('after', `
15
+ Examples:
16
+ $ mage-remote-run cart show 123
17
+ $ mage-remote-run cart show 123 --format json
18
+ `)
19
+ .action(async (cartId, options) => {
20
+ try {
21
+ const client = await createClient();
22
+ const headers = {};
23
+ if (options.format === 'json') headers['Accept'] = 'application/json';
24
+ else if (options.format === 'xml') headers['Accept'] = 'application/xml';
25
+
26
+ const data = await client.get(`V1/carts/${cartId}`, {}, { headers });
27
+
28
+ if (options.format === 'json') {
29
+ console.log(JSON.stringify(data, null, 2));
30
+ return;
31
+ }
32
+ if (options.format === 'xml') {
33
+ console.log(data);
34
+ return;
35
+ }
36
+
37
+ console.log(chalk.bold.blue('\nšŸ›’ Cart Information'));
38
+ console.log(chalk.gray('━'.repeat(50)));
39
+
40
+ // General Info
41
+ console.log(`${chalk.bold('ID:')} ${data.id}`);
42
+ console.log(`${chalk.bold('Status:')} ${data.is_active ? chalk.green('Active') : chalk.gray('Inactive')}`);
43
+ console.log(`${chalk.bold('Created At:')} ${data.created_at || 'N/A'}`);
44
+ console.log(`${chalk.bold('Updated At:')} ${data.updated_at || 'N/A'}`);
45
+ console.log(`${chalk.bold('Items Count:')} ${data.items_count || 0}`);
46
+ console.log(`${chalk.bold('Items Qty:')} ${data.items_qty || 0}`);
47
+ console.log(`${chalk.bold('Virtual:')} ${data.is_virtual ? 'Yes' : 'No'}`);
48
+
49
+ // Customer Info
50
+ if (data.customer && data.customer.email) {
51
+ const customerName = `${data.customer.firstname || ''} ${data.customer.lastname || ''}`.trim();
52
+ console.log(`${chalk.bold('Customer:')} ${customerName} <${data.customer.email}>`);
53
+ } else {
54
+ console.log(`${chalk.bold('Customer:')} Guest`);
55
+ }
56
+
57
+ // Totals (if available) - Fetching just for display
58
+ let totals = null;
59
+ try {
60
+ totals = await client.get(`V1/carts/${cartId}/totals`);
61
+ const currency = totals.quote_currency_code || '';
62
+ console.log(`${chalk.bold('Grand Total:')} ${chalk.green(totals.grand_total + ' ' + currency)}`);
63
+ } catch (e) {
64
+ // Ignore missing totals
65
+ }
66
+
67
+ console.log(chalk.gray('━'.repeat(50)));
68
+
69
+ // Items
70
+ if (data.items && data.items.length > 0) {
71
+ console.log(chalk.bold('\nšŸ“¦ Items'));
72
+ const itemRows = data.items.map(item => [
73
+ item.sku,
74
+ item.name,
75
+ item.qty,
76
+ item.price,
77
+ (item.qty * item.price).toFixed(2) // Approximation as row total might not be in item directly? actually cart item usually has price.
78
+ ]);
79
+ printTable(['SKU', 'Name', 'Qty', 'Price', 'Row Total'], itemRows);
80
+ }
81
+
82
+ // Totals Breakdown
83
+ if (totals) {
84
+ // We displayed Grand Total above.
85
+ // Order show doesn't list full breakdown usually, just items and grand total and addresses.
86
+ // But user asked for "Add ... totals there" previously.
87
+ // I'll keep the breakdown but make it compact or skip if Order doesn't have it.
88
+ // Order show has "Grand Total" in header.
89
+ // I will stick to the requested structure "similar to order show".
90
+ // Order show DOES NOT show subtotal/tax explicitly in the "Order Information" block usually, just Grand Total.
91
+ // But I will keep the full breakdown nicely formatted if requested.
92
+ // Actually, let's append it after items or before addresses.
93
+ // Or just skip it to strict "match order show".
94
+ // User said "Add the address information... and the totals there". So I MUST include totals.
95
+ console.log(chalk.bold('\nšŸ’° Totals Breakdown'));
96
+ console.log(` Subtotal: ${totals.subtotal}`);
97
+ if (totals.tax_amount > 0) console.log(` Tax: ${totals.tax_amount}`);
98
+ if (totals.shipping_amount > 0) console.log(` Shipping: ${totals.shipping_amount}`);
99
+ if (totals.discount_amount < 0) console.log(` Discount: ${totals.discount_amount}`);
100
+ console.log(chalk.bold(` Grand Total: ${totals.grand_total} ${totals.quote_currency_code}`));
101
+ }
102
+
103
+ // Addresses
104
+ if (data.billing_address || (data.extension_attributes && data.extension_attributes.shipping_assignments)) {
105
+ console.log(chalk.bold('\nšŸ“ Addresses'));
106
+
107
+ if (data.billing_address) {
108
+ console.log(chalk.bold.underline('Billing Address:'));
109
+ printAddress(data.billing_address);
110
+ }
111
+
112
+ if (data.extension_attributes && data.extension_attributes.shipping_assignments) {
113
+ data.extension_attributes.shipping_assignments.forEach((assignment, idx) => {
114
+ if (assignment.shipping && assignment.shipping.address) {
115
+ console.log(chalk.bold.underline(idx === 0 ? '\nShipping Address:' : `\nShipping Address (${idx + 1}):`));
116
+ printAddress(assignment.shipping.address);
117
+ if (assignment.shipping.method) {
118
+ console.log(chalk.gray(`Method: ${assignment.shipping.method}`));
119
+ }
120
+ }
121
+ });
122
+ }
123
+ }
124
+
125
+ // Helper for address printing (inline to avoid messing with utils exports for now or duplicate)
126
+ function printAddress(addr) {
127
+ if (!addr) return;
128
+
129
+ const name = `${addr.firstname || ''} ${addr.lastname || ''}`.trim();
130
+ const street = Array.isArray(addr.street) ? addr.street : (addr.street ? [addr.street] : []);
131
+
132
+ // Handle region: could be string, or object with region_code/region
133
+ let region = addr.region || addr.region_code || '';
134
+ if (typeof region === 'object') {
135
+ region = region.region_code || region.region || '';
136
+ }
137
+
138
+ const cityParts = [
139
+ addr.city,
140
+ region,
141
+ addr.postcode
142
+ ].filter(Boolean);
143
+
144
+ const lines = [
145
+ name,
146
+ ...street,
147
+ cityParts.length > 0 ? cityParts.join(', ') : null,
148
+ addr.country_id,
149
+ addr.telephone ? `T: ${addr.telephone}` : null
150
+ ].filter(Boolean);
151
+
152
+ if (lines.length === 0) console.log(chalk.gray(' (Empty Address)'));
153
+ else lines.forEach(l => console.log(` ${l}`));
154
+ }
155
+
156
+ } catch (e) { handleError(e); }
157
+ });
158
+
159
+ //-------------------------------------------------------
160
+ // "cart list" Command (Renamed from search)
161
+ //-------------------------------------------------------
162
+ carts.command('list')
163
+ .description('List carts')
164
+ .option('-p, --page <number>', 'Page number', '1')
165
+ .option('-s, --size <number>', 'Page size', '20')
166
+ .option('-f, --format <type>', 'Output format (text, json, xml)', 'text')
167
+ .option('--filter <filter...>', 'Filter options (e.g. "field:value:condition_type" or "field:value")', [])
168
+ .option('--sort <sort...>', 'Sort options (e.g. "field:direction")', [])
169
+ .addHelpText('after', `
170
+ Examples:
171
+ $ mage-remote-run cart list
172
+ $ mage-remote-run cart list --page 2 --size 50
173
+ $ mage-remote-run cart list --filter "is_active:1"
174
+ $ mage-remote-run cart list --sort "created_at:DESC"
175
+ `)
176
+ .action(async (options) => {
177
+ try {
178
+ const client = await createClient();
179
+ const headers = {};
180
+ if (options.format === 'json') headers['Accept'] = 'application/json';
181
+ else if (options.format === 'xml') headers['Accept'] = 'application/xml';
182
+
183
+ let params = {
184
+ 'searchCriteria[currentPage]': options.page,
185
+ 'searchCriteria[pageSize]': options.size
186
+ };
187
+
188
+ // Process filters
189
+ if (options.filter && options.filter.length > 0) {
190
+ options.filter.forEach((f, idx) => {
191
+ const parts = f.split(':');
192
+ const field = parts[0];
193
+ const value = parts[1];
194
+ const condition = parts[2] || 'eq';
195
+
196
+ params[`searchCriteria[filter_groups][${idx}][filters][0][field]`] = field;
197
+ params[`searchCriteria[filter_groups][${idx}][filters][0][value]`] = value;
198
+ params[`searchCriteria[filter_groups][${idx}][filters][0][condition_type]`] = condition;
199
+ });
200
+ }
201
+
202
+ // Process sorting
203
+ if (options.sort && options.sort.length > 0) {
204
+ options.sort.forEach((s, idx) => {
205
+ const parts = s.split(':');
206
+ const field = parts[0];
207
+ const direction = parts[1] || 'ASC';
208
+
209
+ params[`searchCriteria[sortOrders][${idx}][field]`] = field;
210
+ params[`searchCriteria[sortOrders][${idx}][direction]`] = direction;
211
+ });
212
+ }
213
+
214
+ const data = await client.get('V1/carts/search', params, { headers });
215
+
216
+ if (options.format === 'json') {
217
+ console.log(JSON.stringify(data, null, 2));
218
+ return;
219
+ }
220
+ if (options.format === 'xml') {
221
+ console.log(data);
222
+ return;
223
+ }
224
+
225
+ const rows = (data.items || []).map(c => [
226
+ c.id,
227
+ c.customer ? c.customer.email : 'Guest',
228
+ c.is_active ? 'Yes' : 'No',
229
+ c.items_qty || 0,
230
+ c.created_at
231
+ ]);
232
+
233
+ console.log(chalk.bold(`Total: ${data.total_count}, Page: ${options.page}, Size: ${options.size}`));
234
+ printTable(['ID', 'Customer', 'Active', 'Qty', 'Created'], rows);
235
+
236
+ } catch (e) { handleError(e); }
237
+ });
238
+ }
@@ -357,4 +357,41 @@ Examples:
357
357
  printTable(['Value', 'Label'], rows);
358
358
  } catch (e) { handleError(e); }
359
359
  });
360
+
361
+ const linkTypes = products.command('link-type').description('Manage product link types');
362
+
363
+
364
+ //-------------------------------------------------------
365
+ // "product link-type list" Command
366
+ //-------------------------------------------------------
367
+ linkTypes.command('list')
368
+ .description('List available product link types')
369
+ .option('-f, --format <type>', 'Output format (text, json, xml)', 'text')
370
+ .addHelpText('after', `
371
+ Examples:
372
+ $ mage-remote-run product link-type list
373
+ $ mage-remote-run product link-type list --format json
374
+ `)
375
+ .action(async (options) => {
376
+ try {
377
+ const client = await createClient();
378
+ const headers = {};
379
+ if (options.format === 'json') headers['Accept'] = 'application/json';
380
+ else if (options.format === 'xml') headers['Accept'] = 'application/xml';
381
+
382
+ const data = await client.get('V1/products/links/types', {}, { headers });
383
+
384
+ if (options.format === 'json') {
385
+ console.log(JSON.stringify(data, null, 2));
386
+ return;
387
+ }
388
+ if (options.format === 'xml') {
389
+ console.log(data);
390
+ return;
391
+ }
392
+
393
+ const rows = (data || []).map(t => [t.code, t.name]);
394
+ printTable(['Code', 'Name'], rows);
395
+ } catch (e) { handleError(e); }
396
+ });
360
397
  }
package/lib/config.js CHANGED
@@ -3,14 +3,43 @@ import path from 'path';
3
3
  import envPaths from 'env-paths';
4
4
  import { mkdirp } from 'mkdirp';
5
5
 
6
+ import os from 'os';
7
+
6
8
  const paths = envPaths('mage-remote-run', { suffix: '' });
7
- const CONFIG_DIR = paths.config;
9
+ const CONFIG_DIR = process.platform === 'darwin'
10
+ ? path.join(os.homedir(), '.config', 'mage-remote-run')
11
+ : paths.config;
8
12
  const CACHE_DIR = paths.cache;
9
13
  const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
10
14
  const TOKEN_CACHE_FILE = path.join(CACHE_DIR, 'token-cache.json');
11
15
 
16
+
17
+ const OLD_CONFIG_DIR = paths.config;
18
+ const OLD_CONFIG_FILE = path.join(OLD_CONFIG_DIR, 'config.json');
19
+
20
+ async function migrateOldConfig() {
21
+ if (process.platform !== 'darwin') {
22
+ return;
23
+ }
24
+
25
+ if (fs.existsSync(CONFIG_FILE)) {
26
+ return;
27
+ }
28
+
29
+ if (fs.existsSync(OLD_CONFIG_FILE)) {
30
+ try {
31
+ console.log(`Migrating config from ${OLD_CONFIG_FILE} to ${CONFIG_FILE}`);
32
+ await mkdirp(CONFIG_DIR);
33
+ fs.copyFileSync(OLD_CONFIG_FILE, CONFIG_FILE);
34
+ } catch (e) {
35
+ console.error("Error migrating config:", e.message);
36
+ }
37
+ }
38
+ }
39
+
12
40
  export async function loadConfig() {
13
41
  try {
42
+ await migrateOldConfig();
14
43
  if (!fs.existsSync(CONFIG_FILE)) {
15
44
  return { profiles: {}, activeProfile: null };
16
45
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mage-remote-run",
3
- "version": "0.21.0",
3
+ "version": "0.22.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": {