mage-remote-run 0.17.0 → 0.19.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/commands/company.js +6 -4
- package/lib/commands/console.js +8 -6
- package/lib/commands/webhooks.js +461 -22
- package/lib/utils.js +27 -1
- package/package.json +2 -1
package/lib/commands/company.js
CHANGED
|
@@ -226,14 +226,16 @@ Examples:
|
|
|
226
226
|
const answers = await inquirer.prompt([
|
|
227
227
|
{ name: 'company_name', message: 'Company Name:', default: current.company_name },
|
|
228
228
|
{ name: 'company_email', message: 'Company Email:', default: current.company_email },
|
|
229
|
-
{ name: 'sales_representative_id', message: 'Sales Rep ID:', default: current.sales_representative_id },
|
|
230
|
-
{ name: 'customer_group_id', message: 'Customer Group ID:', default: current.customer_group_id }
|
|
229
|
+
{ name: 'sales_representative_id', message: 'Sales Rep ID:', default: String(current.sales_representative_id) },
|
|
230
|
+
{ name: 'customer_group_id', message: 'Customer Group ID:', default: String(current.customer_group_id) }
|
|
231
231
|
]);
|
|
232
232
|
|
|
233
233
|
// Merge
|
|
234
234
|
const payload = {
|
|
235
|
-
|
|
236
|
-
|
|
235
|
+
company: {
|
|
236
|
+
...current,
|
|
237
|
+
...answers
|
|
238
|
+
}
|
|
237
239
|
};
|
|
238
240
|
|
|
239
241
|
await client.put(`V1/company/${companyId}`, payload);
|
package/lib/commands/console.js
CHANGED
|
@@ -18,12 +18,14 @@ export function registerConsoleCommand(program) {
|
|
|
18
18
|
console.log(chalk.gray('Debug mode enabled'));
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
22
|
+
console.log(chalk.bold.blue('Mage Remote Run Interactive Console'));
|
|
23
|
+
console.log(chalk.gray('Type your commands directly or write JS code.'));
|
|
24
|
+
console.log(chalk.gray('Global variables available: client (async factory), config, chalk'));
|
|
25
|
+
console.log(chalk.gray('Example JS: await (await client()).get("V1/store/websites")'));
|
|
26
|
+
console.log(chalk.gray('Type "list" to see available commands.'));
|
|
27
|
+
console.log(chalk.gray('Type .exit to quit.\n'));
|
|
28
|
+
}
|
|
27
29
|
|
|
28
30
|
// State for the REPL
|
|
29
31
|
let localProgram;
|
package/lib/commands/webhooks.js
CHANGED
|
@@ -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
|
-
|
|
30
|
-
|
|
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
|
|
46
|
-
w.
|
|
47
|
-
w.
|
|
48
|
-
w.
|
|
49
|
-
w.
|
|
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: ${
|
|
54
|
-
printTable(['
|
|
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
|
@@ -11,7 +11,33 @@ export function printTable(headers, data) {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export function handleError(error) {
|
|
14
|
-
|
|
14
|
+
let message = error.message;
|
|
15
|
+
|
|
16
|
+
// specific handling for Magento API Errors which are often JSON stringified
|
|
17
|
+
// Format: "API Error 404: {...}"
|
|
18
|
+
const apiErrorMatch = message.match(/^API Error (\d+): (.+)$/);
|
|
19
|
+
if (apiErrorMatch) {
|
|
20
|
+
const statusCode = apiErrorMatch[1];
|
|
21
|
+
const jsonPart = apiErrorMatch[2];
|
|
22
|
+
try {
|
|
23
|
+
const parsed = JSON.parse(jsonPart);
|
|
24
|
+
if (parsed.message) {
|
|
25
|
+
let prettyMessage = parsed.message;
|
|
26
|
+
if (parsed.parameters) {
|
|
27
|
+
// Substitute %fieldName with values
|
|
28
|
+
Object.keys(parsed.parameters).forEach(key => {
|
|
29
|
+
prettyMessage = prettyMessage.replace(new RegExp(`%${key}`, 'g'), parsed.parameters[key]);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
message = `${prettyMessage}`;
|
|
33
|
+
// Optional: append status code if not 200? The user request just showed the clean message.
|
|
34
|
+
}
|
|
35
|
+
} catch (e) {
|
|
36
|
+
// If parsing fails, keep original message
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
console.error(chalk.red('Error:'), message);
|
|
15
41
|
if (process.env.DEBUG) {
|
|
16
42
|
console.error(error);
|
|
17
43
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mage-remote-run",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.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,6 +32,7 @@
|
|
|
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",
|