mage-remote-run 0.18.0 → 0.20.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 +5 -12
- package/lib/command-registry.js +49 -38
- package/lib/commands/events.js +277 -0
- package/lib/commands/import.js +275 -0
- package/lib/commands/webhooks.js +461 -22
- package/lib/utils.js +44 -0
- package/package.json +3 -1
- package/lib/commands/adobe-io-events.js +0 -70
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
|
|
2
|
+
import { createClient } from '../api/factory.js';
|
|
3
|
+
import { handleError, readInput, validateAdobeCommerce, validatePaaSOrOnPrem } from '../utils.js';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import { input, select, confirm, editor } from '@inquirer/prompts';
|
|
7
|
+
import search from '@inquirer/search';
|
|
8
|
+
import os from 'os';
|
|
9
|
+
|
|
10
|
+
async function getImportBehaviorAndEntity(options) {
|
|
11
|
+
let entityType = options.entityType;
|
|
12
|
+
if (!entityType) {
|
|
13
|
+
const ENTITY_TYPES = [
|
|
14
|
+
{ name: 'Advanced Pricing', value: 'advanced_pricing' },
|
|
15
|
+
{ name: 'Products', value: 'catalog_product' },
|
|
16
|
+
{ name: 'Customers and Addresses (single file)', value: 'customer_composite' },
|
|
17
|
+
{ name: 'Customers Main File', value: 'customer' },
|
|
18
|
+
{ name: 'Customer Addresses', value: 'customer_address' },
|
|
19
|
+
{ name: 'Customer Finances', value: 'customer_finance' },
|
|
20
|
+
{ name: 'Stock Sources', value: 'stock_sources' },
|
|
21
|
+
{ name: 'Custom...', value: 'custom' }
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
entityType = await search({
|
|
25
|
+
message: 'Select Entity Type:',
|
|
26
|
+
source: async (term) => {
|
|
27
|
+
if (!term) {
|
|
28
|
+
return ENTITY_TYPES;
|
|
29
|
+
}
|
|
30
|
+
return ENTITY_TYPES.filter((type) =>
|
|
31
|
+
type.name.toLowerCase().includes(term.toLowerCase()) ||
|
|
32
|
+
type.value.toLowerCase().includes(term.toLowerCase())
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
if (entityType === 'custom') {
|
|
38
|
+
entityType = await input({
|
|
39
|
+
message: 'Enter Custom Entity Type:',
|
|
40
|
+
validate: value => value ? true : 'Entity Type is required'
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let behavior = options.behavior;
|
|
46
|
+
if (!behavior) {
|
|
47
|
+
behavior = await select({
|
|
48
|
+
message: 'Import Behavior:',
|
|
49
|
+
choices: [
|
|
50
|
+
{ name: 'Append/Update', value: 'append' },
|
|
51
|
+
{ name: 'Replace', value: 'replace' },
|
|
52
|
+
{ name: 'Delete', value: 'delete_entity' }
|
|
53
|
+
],
|
|
54
|
+
default: 'append'
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return { entityType, behavior };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function getValidationOptions(options) {
|
|
62
|
+
let validationStrategy = options.validationStrategy;
|
|
63
|
+
if (!validationStrategy) {
|
|
64
|
+
validationStrategy = await select({
|
|
65
|
+
message: 'Validation Strategy:',
|
|
66
|
+
choices: [
|
|
67
|
+
{ name: 'Stop on Error', value: 'validation-stop-on-errors' },
|
|
68
|
+
{ name: 'Skip error entries', value: 'validation-skip-errors' }
|
|
69
|
+
],
|
|
70
|
+
default: 'validation-stop-on-errors'
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let allowedErrorCount = options.allowedErrorCount;
|
|
75
|
+
if (!allowedErrorCount) {
|
|
76
|
+
allowedErrorCount = await input({
|
|
77
|
+
message: 'Allowed Error Count:',
|
|
78
|
+
default: '10',
|
|
79
|
+
validate: (value) => {
|
|
80
|
+
const num = parseInt(value, 10);
|
|
81
|
+
return !isNaN(num) && num >= 0 ? true : 'Please enter a valid number';
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
validationStrategy,
|
|
88
|
+
allowedErrorCount: parseInt(allowedErrorCount, 10)
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function registerImportCommands(program, profile) {
|
|
93
|
+
const importCmd = program.command('import').description('Import data');
|
|
94
|
+
|
|
95
|
+
//-------------------------------------------------------
|
|
96
|
+
// "import json" Command
|
|
97
|
+
//-------------------------------------------------------
|
|
98
|
+
importCmd.command('json [file]')
|
|
99
|
+
.description('Import data from JSON (Adobe Commerce)')
|
|
100
|
+
.option('--entity-type <type>', 'Entity Type (e.g. catalog_product, customer)')
|
|
101
|
+
.option('--behavior <behavior>', 'Import Behavior (append, replace, delete_entity)')
|
|
102
|
+
.option('--validation-strategy <strategy>', 'Validation Strategy (validation-stop-on-errors, validation-skip-errors)')
|
|
103
|
+
.option('--allowed-error-count <count>', 'Allowed Error Count')
|
|
104
|
+
.addHelpText('after', `
|
|
105
|
+
Examples:
|
|
106
|
+
Product from file:
|
|
107
|
+
$ mage-remote-run import json products.json --entity-type=catalog_product --allowed-error-count=10 --validation-strategy=validation-skip-errors
|
|
108
|
+
|
|
109
|
+
From STDIN (piped):
|
|
110
|
+
$ cat data.json | mage-remote-run import json
|
|
111
|
+
`)
|
|
112
|
+
.action(async (file, options) => {
|
|
113
|
+
try {
|
|
114
|
+
await validateAdobeCommerce();
|
|
115
|
+
|
|
116
|
+
let content = await readInput(file);
|
|
117
|
+
if (!content) {
|
|
118
|
+
const fromFile = await confirm({
|
|
119
|
+
message: 'Do you want to import a file?',
|
|
120
|
+
default: true
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
if (fromFile) {
|
|
124
|
+
const filePath = await input({
|
|
125
|
+
message: 'Path to JSON file:',
|
|
126
|
+
validate: value => {
|
|
127
|
+
let p = value;
|
|
128
|
+
if (p.startsWith('~/') || p === '~') p = p.replace(/^~/, os.homedir());
|
|
129
|
+
return fs.existsSync(p) ? true : 'File not found';
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
content = await readInput(filePath);
|
|
133
|
+
} else {
|
|
134
|
+
content = await editor({
|
|
135
|
+
message: 'Enter JSON content',
|
|
136
|
+
postfix: '.json'
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let data;
|
|
142
|
+
try {
|
|
143
|
+
data = JSON.parse(content);
|
|
144
|
+
} catch (e) {
|
|
145
|
+
throw new Error('Invalid JSON data');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const { entityType, behavior } = await getImportBehaviorAndEntity(options);
|
|
149
|
+
const { validationStrategy, allowedErrorCount } = await getValidationOptions(options);
|
|
150
|
+
|
|
151
|
+
const payload = {
|
|
152
|
+
source: {
|
|
153
|
+
entity: entityType,
|
|
154
|
+
behavior: behavior,
|
|
155
|
+
validation_strategy: validationStrategy,
|
|
156
|
+
allowed_error_count: allowedErrorCount,
|
|
157
|
+
items: Array.isArray(data) ? data : [data]
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const client = await createClient();
|
|
162
|
+
const endpoint = 'V1/import/json';
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
const result = await client.post(endpoint, payload);
|
|
166
|
+
console.log(chalk.green('Import submitted successfully.'));
|
|
167
|
+
if (result) console.log(JSON.stringify(result, null, 2));
|
|
168
|
+
} catch (apiError) {
|
|
169
|
+
if (apiError.message && (apiError.message.includes('Route') || apiError.message.includes('404'))) {
|
|
170
|
+
throw new Error(`The endpoint "${endpoint}" was not found on the server.\nEnsure your Magento instance has the required Import API module installed.`);
|
|
171
|
+
}
|
|
172
|
+
throw apiError;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
} catch (e) {
|
|
176
|
+
if (e.name === 'ExitPromptError') return;
|
|
177
|
+
handleError(e);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
//-------------------------------------------------------
|
|
182
|
+
// "import csv" Command
|
|
183
|
+
//-------------------------------------------------------
|
|
184
|
+
// Only register/show if profile is allowed (PaaS or On-Prem)
|
|
185
|
+
if (profile && (profile.type === 'ac-cloud-paas' || profile.type === 'ac-on-prem')) {
|
|
186
|
+
importCmd.command('csv [file]')
|
|
187
|
+
.description('Import data from CSV (Adobe Commerce PaaS/On-Prem)')
|
|
188
|
+
.option('--entity-type <type>', 'Entity Type (e.g. catalog_product, customer)')
|
|
189
|
+
.option('--behavior <behavior>', 'Import Behavior (append, replace, delete_entity)')
|
|
190
|
+
.option('--validation-strategy <strategy>', 'Validation Strategy (validation-stop-on-errors, validation-skip-errors)')
|
|
191
|
+
.option('--allowed-error-count <count>', 'Allowed Error Count')
|
|
192
|
+
.option('--field-separator <char>', 'Field Separator', ',')
|
|
193
|
+
.option('--multi-value-separator <char>', 'Multiple Value Separator', ',')
|
|
194
|
+
.option('--empty-value-constant <string>', 'Empty Attribute Value Constant', '__EMPTY__VALUE__')
|
|
195
|
+
.option('--images-file-dir <dir>', 'Images File Directory', 'var/import/images')
|
|
196
|
+
.addHelpText('after', `
|
|
197
|
+
Examples:
|
|
198
|
+
Interactive:
|
|
199
|
+
$ mage-remote-run import csv products.csv
|
|
200
|
+
|
|
201
|
+
Non-Interactive:
|
|
202
|
+
$ mage-remote-run import csv products.csv --entity-type catalog_product --behavior append
|
|
203
|
+
|
|
204
|
+
Custom Separators:
|
|
205
|
+
$ mage-remote-run import csv products.csv --field-separator ";" --multi-value-separator "|"
|
|
206
|
+
`)
|
|
207
|
+
.action(async (file, options) => {
|
|
208
|
+
try {
|
|
209
|
+
await validatePaaSOrOnPrem();
|
|
210
|
+
|
|
211
|
+
let content = await readInput(file);
|
|
212
|
+
if (!content) {
|
|
213
|
+
const fromFile = await confirm({
|
|
214
|
+
message: 'Do you want to import a file?',
|
|
215
|
+
default: true
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
if (fromFile) {
|
|
219
|
+
const filePath = await input({
|
|
220
|
+
message: 'Path to CSV file:',
|
|
221
|
+
validate: value => {
|
|
222
|
+
let p = value;
|
|
223
|
+
if (p.startsWith('~/') || p === '~') p = p.replace(/^~/, os.homedir());
|
|
224
|
+
return fs.existsSync(p) ? true : 'File not found';
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
content = await readInput(filePath);
|
|
228
|
+
} else {
|
|
229
|
+
content = await editor({
|
|
230
|
+
message: 'Enter CSV content',
|
|
231
|
+
postfix: '.csv'
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
console.log(chalk.blue(`Size: ${content.length} characters.`));
|
|
237
|
+
|
|
238
|
+
const { entityType, behavior } = await getImportBehaviorAndEntity(options);
|
|
239
|
+
const { validationStrategy, allowedErrorCount } = await getValidationOptions(options);
|
|
240
|
+
|
|
241
|
+
const payload = {
|
|
242
|
+
source: {
|
|
243
|
+
entity: entityType,
|
|
244
|
+
behavior: behavior,
|
|
245
|
+
csv_data: Buffer.from(content).toString('base64'),
|
|
246
|
+
import_field_separator: options.fieldSeparator,
|
|
247
|
+
import_multiple_value_separator: options.multiValueSeparator,
|
|
248
|
+
import_empty_attribute_value_constant: options.emptyValueConstant,
|
|
249
|
+
import_images_file_dir: options.imagesFileDir,
|
|
250
|
+
validation_strategy: validationStrategy,
|
|
251
|
+
allowed_error_count: allowedErrorCount
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
const client = await createClient();
|
|
256
|
+
const endpoint = 'V1/import/csv';
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
const result = await client.post(endpoint, payload);
|
|
260
|
+
console.log(chalk.green('Import submitted successfully.'));
|
|
261
|
+
if (result) console.log(JSON.stringify(result, null, 2));
|
|
262
|
+
} catch (apiError) {
|
|
263
|
+
if (apiError.message && (apiError.message.includes('Route') || apiError.message.includes('404'))) {
|
|
264
|
+
throw new Error(`The endpoint "${endpoint}" was not found on the server.\nEnsure your Magento instance has the required Import API module installed.`);
|
|
265
|
+
}
|
|
266
|
+
throw apiError;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
} catch (e) {
|
|
270
|
+
if (e.name === 'ExitPromptError') return;
|
|
271
|
+
handleError(e);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
}
|