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.
- package/lib/command-registry.js +2 -0
- package/lib/commands/cart.js +238 -0
- package/lib/commands/products.js +37 -0
- package/lib/config.js +30 -1
- package/package.json +1 -1
package/lib/command-registry.js
CHANGED
|
@@ -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
|
+
}
|
package/lib/commands/products.js
CHANGED
|
@@ -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 =
|
|
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
|
}
|