mage-remote-run 0.2.5 ā 0.3.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/bin/mage-remote-run.js +2 -0
- package/lib/api/spec-loader.js +3 -1
- package/lib/commands/customers.js +19 -0
- package/lib/commands/inventory.js +91 -0
- package/lib/commands/products.js +135 -1
- package/lib/config.js +3 -2
- package/package.json +2 -1
package/bin/mage-remote-run.js
CHANGED
|
@@ -25,6 +25,7 @@ import { registerOrdersCommands } from '../lib/commands/orders.js';
|
|
|
25
25
|
import { registerEavCommands } from '../lib/commands/eav.js';
|
|
26
26
|
import { registerProductsCommands } from '../lib/commands/products.js';
|
|
27
27
|
import { registerTaxCommands } from '../lib/commands/tax.js';
|
|
28
|
+
import { registerInventoryCommands } from '../lib/commands/inventory.js';
|
|
28
29
|
|
|
29
30
|
registerConnectionCommands(program);
|
|
30
31
|
registerWebsitesCommands(program);
|
|
@@ -34,6 +35,7 @@ registerOrdersCommands(program);
|
|
|
34
35
|
registerEavCommands(program);
|
|
35
36
|
registerProductsCommands(program);
|
|
36
37
|
registerTaxCommands(program);
|
|
38
|
+
registerInventoryCommands(program);
|
|
37
39
|
|
|
38
40
|
function resolveCommandMatch(parent, token) {
|
|
39
41
|
const tokenLower = token.toLowerCase();
|
package/lib/api/spec-loader.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { OpenAPIClientAxios } from 'openapi-client-axios';
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
4
5
|
|
|
5
6
|
// Locate specs relative to project root (assuming we are in lib/api)
|
|
6
|
-
const
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const SPEC_DIR = path.resolve(__dirname, '../../api-specs/2.4.8');
|
|
7
9
|
|
|
8
10
|
export function loadSpec(type) {
|
|
9
11
|
let specFile;
|
|
@@ -193,4 +193,23 @@ export function registerCustomersCommands(program) {
|
|
|
193
193
|
}
|
|
194
194
|
}
|
|
195
195
|
});
|
|
196
|
+
const groups = customers.command('group').description('Manage customer groups');
|
|
197
|
+
|
|
198
|
+
groups.command('list')
|
|
199
|
+
.description('List customer groups')
|
|
200
|
+
.option('-p, --page <number>', 'Page number', '1')
|
|
201
|
+
.option('-s, --size <number>', 'Page size', '20')
|
|
202
|
+
.action(async (options) => {
|
|
203
|
+
try {
|
|
204
|
+
const client = await createClient();
|
|
205
|
+
const params = {
|
|
206
|
+
'searchCriteria[currentPage]': options.page,
|
|
207
|
+
'searchCriteria[pageSize]': options.size
|
|
208
|
+
};
|
|
209
|
+
const data = await client.get('V1/customerGroups/search', params);
|
|
210
|
+
const rows = (data.items || []).map(g => [g.id, g.code, g.tax_class_id]);
|
|
211
|
+
console.log(chalk.bold(`Total: ${data.total_count}, Page: ${options.page}, Size: ${options.size}`));
|
|
212
|
+
printTable(['ID', 'Code', 'Tax Class ID'], rows);
|
|
213
|
+
} catch (e) { handleError(e); }
|
|
214
|
+
});
|
|
196
215
|
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
|
|
2
|
+
import { createClient } from '../api/factory.js';
|
|
3
|
+
import { printTable, handleError } from '../utils.js';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
|
|
6
|
+
export function registerInventoryCommands(program) {
|
|
7
|
+
const inventory = program.command('inventory').description('Manage inventory');
|
|
8
|
+
|
|
9
|
+
const stock = inventory.command('stock').description('Manage inventory stocks');
|
|
10
|
+
|
|
11
|
+
stock.command('list')
|
|
12
|
+
.description('List inventory stocks')
|
|
13
|
+
.action(async () => {
|
|
14
|
+
try {
|
|
15
|
+
const client = await createClient();
|
|
16
|
+
const data = await client.get('V1/inventory/stocks', {
|
|
17
|
+
'searchCriteria[currentPage]': 1,
|
|
18
|
+
'searchCriteria[pageSize]': 50
|
|
19
|
+
});
|
|
20
|
+
// Check if result is items array or object with items
|
|
21
|
+
const items = data.items ? data.items : data;
|
|
22
|
+
const rows = (Array.isArray(items) ? items : []).map(s => [s.stock_id, s.name]);
|
|
23
|
+
printTable(['ID', 'Name'], rows);
|
|
24
|
+
} catch (e) { handleError(e); }
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
stock.command('show <stockId>')
|
|
28
|
+
.description('Show stock details')
|
|
29
|
+
.action(async (stockId) => {
|
|
30
|
+
try {
|
|
31
|
+
const client = await createClient();
|
|
32
|
+
const data = await client.get(`V1/inventory/stocks/${stockId}`);
|
|
33
|
+
console.log(chalk.bold.blue('\nš¦ Stock Information'));
|
|
34
|
+
console.log(chalk.gray('ā'.repeat(60)));
|
|
35
|
+
console.log(` ${chalk.bold('ID:')} ${data.stock_id}`);
|
|
36
|
+
console.log(` ${chalk.bold('Name:')} ${data.name}`);
|
|
37
|
+
if (data.extension_attributes && data.extension_attributes.sales_channels) {
|
|
38
|
+
console.log(chalk.bold('\nš¢ Sales Channels'));
|
|
39
|
+
data.extension_attributes.sales_channels.forEach(sc => {
|
|
40
|
+
console.log(` Type: ${sc.type}, Code: ${sc.code}`);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
console.log('');
|
|
44
|
+
} catch (e) { handleError(e); }
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
inventory.command('resolve-stock <type> <code>')
|
|
48
|
+
.description('Resolve stock for a sales channel')
|
|
49
|
+
.action(async (type, code) => {
|
|
50
|
+
try {
|
|
51
|
+
const client = await createClient();
|
|
52
|
+
const stock = await client.get(`V1/inventory/stock-resolver/${type}/${code}`);
|
|
53
|
+
console.log(chalk.bold(`Resolved Stock ID: ${stock.stock_id}`));
|
|
54
|
+
} catch (e) { handleError(e); }
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const source = inventory.command('source').description('Manage inventory sources');
|
|
58
|
+
|
|
59
|
+
source.command('list')
|
|
60
|
+
.description('List inventory sources')
|
|
61
|
+
.option('-p, --page <number>', 'Page number', '1')
|
|
62
|
+
.option('-s, --size <number>', 'Page size', '20')
|
|
63
|
+
.action(async (options) => {
|
|
64
|
+
try {
|
|
65
|
+
const client = await createClient();
|
|
66
|
+
const params = {
|
|
67
|
+
'searchCriteria[currentPage]': options.page,
|
|
68
|
+
'searchCriteria[pageSize]': options.size
|
|
69
|
+
};
|
|
70
|
+
const data = await client.get('V1/inventory/sources', params);
|
|
71
|
+
const items = data.items ? data.items : [];
|
|
72
|
+
const rows = (Array.isArray(items) ? items : []).map(s => [s.source_code, s.name, s.enabled ? 'Yes' : 'No', s.postcode]);
|
|
73
|
+
console.log(chalk.bold(`Total: ${data.total_count}, Page: ${options.page}, Size: ${options.size}`));
|
|
74
|
+
printTable(['Code', 'Name', 'Enabled', 'Postcode'], rows);
|
|
75
|
+
} catch (e) { handleError(e); }
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const ssa = source.command('selection-algorithm').description('Manage source selection algorithms');
|
|
79
|
+
|
|
80
|
+
ssa.command('list')
|
|
81
|
+
.description('List available source selection algorithms')
|
|
82
|
+
.action(async () => {
|
|
83
|
+
try {
|
|
84
|
+
const client = await createClient();
|
|
85
|
+
const data = await client.get('V1/inventory/source-selection-algorithm-list');
|
|
86
|
+
const items = data.items ? data.items : data;
|
|
87
|
+
const rows = (Array.isArray(items) ? items : []).map(a => [a.code, a.title, a.description]);
|
|
88
|
+
printTable(['Code', 'Title', 'Description'], rows);
|
|
89
|
+
} catch (e) { handleError(e); }
|
|
90
|
+
});
|
|
91
|
+
}
|
package/lib/commands/products.js
CHANGED
|
@@ -5,7 +5,141 @@ import chalk from 'chalk';
|
|
|
5
5
|
export function registerProductsCommands(program) {
|
|
6
6
|
const products = program.command('product').description('Manage products');
|
|
7
7
|
|
|
8
|
-
products.command('
|
|
8
|
+
products.command('list')
|
|
9
|
+
.description('List products')
|
|
10
|
+
.option('-p, --page <number>', 'Page number', '1')
|
|
11
|
+
.option('-s, --size <number>', 'Page size', '20')
|
|
12
|
+
.option('--sort-by <attribute>', 'Attribute to sort by', 'id')
|
|
13
|
+
.option('--sort-order <order>', 'Sort order (ASC or DESC)', 'ASC')
|
|
14
|
+
.action(async (options) => {
|
|
15
|
+
try {
|
|
16
|
+
const client = await createClient();
|
|
17
|
+
const params = {
|
|
18
|
+
'searchCriteria[currentPage]': options.page,
|
|
19
|
+
'searchCriteria[pageSize]': options.size,
|
|
20
|
+
'searchCriteria[sortOrders][0][field]': options.sortBy,
|
|
21
|
+
'searchCriteria[sortOrders][0][direction]': options.sortOrder
|
|
22
|
+
};
|
|
23
|
+
const data = await client.get('V1/products', params);
|
|
24
|
+
const rows = (data.items || []).map(p => [p.id, p.sku, p.name, p.type_id, p.price]);
|
|
25
|
+
console.log(chalk.bold(`Total: ${data.total_count}, Page: ${options.page}, Size: ${options.size}`));
|
|
26
|
+
printTable(['ID', 'SKU', 'Name', 'Type', 'Price'], rows);
|
|
27
|
+
} catch (e) { handleError(e); }
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
products.command('show <sku>')
|
|
31
|
+
.description('Show product details')
|
|
32
|
+
.option('-f, --format <type>', 'Output format (text, json, xml)', 'text')
|
|
33
|
+
.action(async (sku, options) => {
|
|
34
|
+
try {
|
|
35
|
+
const client = await createClient();
|
|
36
|
+
let headers = {};
|
|
37
|
+
if (options.format === 'json') headers['Accept'] = 'application/json';
|
|
38
|
+
else if (options.format === 'xml') headers['Accept'] = 'application/xml';
|
|
39
|
+
|
|
40
|
+
let data;
|
|
41
|
+
try {
|
|
42
|
+
// SKU in URL needs to be encoded, but typically client libraries handle it if properly used.
|
|
43
|
+
// However, we are constructing URL manually. encodeURIComponent is safer.
|
|
44
|
+
data = await client.get(`V1/products/${encodeURIComponent(sku)}`, {}, { headers });
|
|
45
|
+
} catch (e) {
|
|
46
|
+
throw new Error(`Product '${sku}' not found.`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (options.format === 'json') {
|
|
50
|
+
console.log(JSON.stringify(data, null, 2));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (options.format === 'xml') {
|
|
54
|
+
console.log(data);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
console.log(chalk.bold.blue('\nš¦ Product Information'));
|
|
59
|
+
console.log(chalk.gray('ā'.repeat(60)));
|
|
60
|
+
|
|
61
|
+
// General
|
|
62
|
+
console.log(chalk.bold('\nā¹ļø General Information'));
|
|
63
|
+
console.log(` ${chalk.bold('ID:')} ${data.id}`);
|
|
64
|
+
console.log(` ${chalk.bold('SKU:')} ${data.sku}`);
|
|
65
|
+
console.log(` ${chalk.bold('Name:')} ${data.name}`);
|
|
66
|
+
console.log(` ${chalk.bold('Type:')} ${data.type_id}`);
|
|
67
|
+
console.log(` ${chalk.bold('Set ID:')} ${data.attribute_set_id}`);
|
|
68
|
+
console.log(` ${chalk.bold('Created At:')} ${data.created_at}`);
|
|
69
|
+
console.log(` ${chalk.bold('Updated At:')} ${data.updated_at}`);
|
|
70
|
+
|
|
71
|
+
// Pricing
|
|
72
|
+
console.log(chalk.bold('\nš° Pricing'));
|
|
73
|
+
console.log(` ${chalk.bold('Price:')} ${data.price}`);
|
|
74
|
+
// Simple checks for special price if available in standard fields or attributes
|
|
75
|
+
const specialPrice = data.custom_attributes && data.custom_attributes.find(a => a.attribute_code === 'special_price');
|
|
76
|
+
if (specialPrice) {
|
|
77
|
+
console.log(` ${chalk.bold('Special Price:')} ${specialPrice.value}`);
|
|
78
|
+
}
|
|
79
|
+
if (data.tier_prices && data.tier_prices.length > 0) {
|
|
80
|
+
console.log(` ${chalk.bold('Tier Prices:')} ${data.tier_prices.length} defined`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Stock (Simple check via extension attributes usually)
|
|
84
|
+
if (data.extension_attributes && data.extension_attributes.stock_item) {
|
|
85
|
+
const stock = data.extension_attributes.stock_item;
|
|
86
|
+
console.log(chalk.bold('\nš¦ Stock'));
|
|
87
|
+
console.log(` ${chalk.bold('In Stock:')} ${stock.is_in_stock ? chalk.green('Yes') : chalk.red('No')}`);
|
|
88
|
+
console.log(` ${chalk.bold('Quantity:')} ${stock.qty}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Content
|
|
92
|
+
const { convert } = await import('html-to-text');
|
|
93
|
+
const description = data.custom_attributes && data.custom_attributes.find(a => a.attribute_code === 'description');
|
|
94
|
+
const shortDescription = data.custom_attributes && data.custom_attributes.find(a => a.attribute_code === 'short_description');
|
|
95
|
+
|
|
96
|
+
if (description || shortDescription) {
|
|
97
|
+
console.log(chalk.bold('\nš Content'));
|
|
98
|
+
if (shortDescription) {
|
|
99
|
+
console.log(chalk.bold.underline('Short Description:'));
|
|
100
|
+
console.log(convert(shortDescription.value, { wordwrap: 80 }));
|
|
101
|
+
console.log('');
|
|
102
|
+
}
|
|
103
|
+
if (description) {
|
|
104
|
+
console.log(chalk.bold.underline('Description:'));
|
|
105
|
+
console.log(convert(description.value, { wordwrap: 80 }));
|
|
106
|
+
console.log('');
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Custom Attributes
|
|
111
|
+
if (data.custom_attributes && data.custom_attributes.length > 0) {
|
|
112
|
+
const ignoredAttributes = ['description', 'short_description', 'special_price', 'category_ids', 'url_key'];
|
|
113
|
+
const visibleAttributes = data.custom_attributes.filter(a => !ignoredAttributes.includes(a.attribute_code));
|
|
114
|
+
|
|
115
|
+
if (visibleAttributes.length > 0) {
|
|
116
|
+
console.log(chalk.bold('\nš Additional Attributes'));
|
|
117
|
+
const attrRows = visibleAttributes
|
|
118
|
+
.filter(a => typeof a.value !== 'object') // Skip complex objects for now
|
|
119
|
+
.map(a => [a.attribute_code, a.value]);
|
|
120
|
+
|
|
121
|
+
if (attrRows.length > 0) {
|
|
122
|
+
printTable(['Attribute', 'Value'], attrRows);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Media
|
|
128
|
+
if (data.media_gallery_entries && data.media_gallery_entries.length > 0) {
|
|
129
|
+
console.log(chalk.bold('\nš¼ļø Media'));
|
|
130
|
+
data.media_gallery_entries.forEach(entry => {
|
|
131
|
+
console.log(` [${entry.media_type}] ${entry.file} (${entry.label || 'No Label'})`);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
console.log(chalk.gray('ā'.repeat(60)));
|
|
136
|
+
|
|
137
|
+
} catch (e) { handleError(e); }
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const types = products.command('type').description('Manage product types');
|
|
141
|
+
|
|
142
|
+
types.command('list')
|
|
9
143
|
.description('List available product types')
|
|
10
144
|
.action(async () => {
|
|
11
145
|
try {
|
package/lib/config.js
CHANGED
|
@@ -5,8 +5,9 @@ import { mkdirp } from 'mkdirp';
|
|
|
5
5
|
|
|
6
6
|
const paths = envPaths('mage-remote-run', { suffix: '' });
|
|
7
7
|
const CONFIG_DIR = paths.config;
|
|
8
|
+
const CACHE_DIR = paths.cache;
|
|
8
9
|
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
9
|
-
const TOKEN_CACHE_FILE = path.join(
|
|
10
|
+
const TOKEN_CACHE_FILE = path.join(CACHE_DIR, 'token-cache.json');
|
|
10
11
|
|
|
11
12
|
export async function loadConfig() {
|
|
12
13
|
try {
|
|
@@ -69,7 +70,7 @@ export async function loadTokenCache() {
|
|
|
69
70
|
|
|
70
71
|
export async function saveTokenCache(cache) {
|
|
71
72
|
try {
|
|
72
|
-
await mkdirp(
|
|
73
|
+
await mkdirp(CACHE_DIR);
|
|
73
74
|
fs.writeFileSync(TOKEN_CACHE_FILE, JSON.stringify(cache, null, 2));
|
|
74
75
|
} catch (e) {
|
|
75
76
|
console.error("Error saving token cache:", e.message);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mage-remote-run",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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": {
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"cli-table3": "^0.6.5",
|
|
37
37
|
"commander": "^14.0.2",
|
|
38
38
|
"env-paths": "^3.0.0",
|
|
39
|
+
"html-to-text": "^9.0.5",
|
|
39
40
|
"inquirer": "^13.1.0",
|
|
40
41
|
"mkdirp": "^3.0.1",
|
|
41
42
|
"oauth-1.0a": "^2.2.6",
|