@magentrix-corp/magentrix-cli 1.3.16 → 1.3.17
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/LICENSE +25 -25
- package/README.md +1166 -1166
- package/actions/autopublish.old.js +293 -293
- package/actions/config.js +182 -182
- package/actions/create.js +466 -466
- package/actions/help.js +164 -164
- package/actions/iris/buildStage.js +874 -874
- package/actions/iris/delete.js +256 -256
- package/actions/iris/dev.js +391 -391
- package/actions/iris/index.js +6 -6
- package/actions/iris/link.js +375 -375
- package/actions/iris/recover.js +268 -268
- package/actions/main.js +80 -80
- package/actions/publish.js +1420 -1420
- package/actions/pull.js +684 -684
- package/actions/setup.js +148 -148
- package/actions/status.js +17 -17
- package/actions/update.js +248 -248
- package/bin/magentrix.js +393 -393
- package/package.json +55 -55
- package/utils/assetPaths.js +158 -158
- package/utils/autopublishLock.js +77 -77
- package/utils/cacher.js +206 -206
- package/utils/cli/checkInstanceUrl.js +76 -74
- package/utils/cli/helpers/compare.js +282 -282
- package/utils/cli/helpers/ensureApiKey.js +63 -63
- package/utils/cli/helpers/ensureCredentials.js +68 -68
- package/utils/cli/helpers/ensureInstanceUrl.js +75 -75
- package/utils/cli/writeRecords.js +262 -262
- package/utils/compare.js +135 -135
- package/utils/compress.js +17 -17
- package/utils/config.js +527 -527
- package/utils/debug.js +144 -144
- package/utils/diagnostics/testPublishLogic.js +96 -96
- package/utils/diff.js +49 -49
- package/utils/downloadAssets.js +291 -291
- package/utils/filetag.js +115 -115
- package/utils/hash.js +14 -14
- package/utils/iris/backup.js +411 -411
- package/utils/iris/builder.js +541 -541
- package/utils/iris/config-reader.js +664 -664
- package/utils/iris/deleteHelper.js +150 -150
- package/utils/iris/errors.js +537 -537
- package/utils/iris/linker.js +601 -601
- package/utils/iris/lock.js +360 -360
- package/utils/iris/validation.js +360 -360
- package/utils/iris/validator.js +281 -281
- package/utils/iris/zipper.js +248 -248
- package/utils/logger.js +291 -291
- package/utils/magentrix/api/assets.js +220 -220
- package/utils/magentrix/api/auth.js +107 -107
- package/utils/magentrix/api/createEntity.js +61 -61
- package/utils/magentrix/api/deleteEntity.js +55 -55
- package/utils/magentrix/api/iris.js +251 -251
- package/utils/magentrix/api/meqlQuery.js +36 -36
- package/utils/magentrix/api/retrieveEntity.js +86 -86
- package/utils/magentrix/api/updateEntity.js +66 -66
- package/utils/magentrix/fetch.js +168 -168
- package/utils/merge.js +22 -22
- package/utils/permissionError.js +70 -70
- package/utils/preferences.js +40 -40
- package/utils/progress.js +469 -469
- package/utils/spinner.js +43 -43
- package/utils/template.js +52 -52
- package/utils/updateFileBase.js +121 -121
- package/utils/workspaces.js +108 -108
- package/vars/config.js +11 -11
- package/vars/global.js +50 -50
package/actions/create.js
CHANGED
|
@@ -1,466 +1,466 @@
|
|
|
1
|
-
import { ensureValidCredentials } from "../utils/cli/helpers/ensureCredentials.js";
|
|
2
|
-
import { createEntity } from "../utils/magentrix/api/createEntity.js";
|
|
3
|
-
import { search, select, input } from "@inquirer/prompts";
|
|
4
|
-
import { listEntities } from "../utils/magentrix/api/retrieveEntity.js";
|
|
5
|
-
import { getClassTemplate, getControllerTemplate, getPageTemplate, getTriggerTemplate } from "../utils/template.js";
|
|
6
|
-
import { withSpinner } from "../utils/spinner.js";
|
|
7
|
-
import { EXPORT_ROOT, TYPE_DIR_MAP } from "../vars/global.js";
|
|
8
|
-
import path from "path";
|
|
9
|
-
import fs from 'fs';
|
|
10
|
-
import chalk from 'chalk';
|
|
11
|
-
import { setFileTag } from "../utils/filetag.js";
|
|
12
|
-
import { updateBase } from "../utils/updateFileBase.js";
|
|
13
|
-
import { sha256 } from "../utils/hash.js";
|
|
14
|
-
|
|
15
|
-
// Credentials and entity cache (module-scope for re-use during prompt session)
|
|
16
|
-
let credentials = {};
|
|
17
|
-
let triggerEntities = [];
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Loads all entities from the Magentrix API and prepares them
|
|
21
|
-
* for searchable selection in the CLI.
|
|
22
|
-
* Populates the `triggerEntities` array.
|
|
23
|
-
*
|
|
24
|
-
* @async
|
|
25
|
-
* @returns {Promise<void>}
|
|
26
|
-
*/
|
|
27
|
-
const loadEntities = async () => {
|
|
28
|
-
const entities = await listEntities(credentials.instanceUrl, credentials.token.value);
|
|
29
|
-
// Map to inquirer-friendly objects: { name, value }
|
|
30
|
-
triggerEntities = entities.map(entity => ({
|
|
31
|
-
label: entity.Label,
|
|
32
|
-
value: entity.Id,
|
|
33
|
-
name: entity.Name
|
|
34
|
-
}));
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Search function for entity selection using @inquirer/prompts.
|
|
39
|
-
* Filters `triggerEntities` array based on the user's input term.
|
|
40
|
-
*
|
|
41
|
-
* @async
|
|
42
|
-
* @param {string} term - User's search input.
|
|
43
|
-
* @returns {Promise<Array>} Filtered entity choices for prompt.
|
|
44
|
-
*/
|
|
45
|
-
const searchEntities = async (term = '') => {
|
|
46
|
-
// Case-insensitive substring match against entity names
|
|
47
|
-
const filteredEntities = triggerEntities.filter(entity =>
|
|
48
|
-
entity.name.toLowerCase().includes(term.toLowerCase())
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
// Format for inquirer searchable prompt
|
|
52
|
-
return filteredEntities.map(entity => ({
|
|
53
|
-
name: `${entity.label} (${entity.value})`,
|
|
54
|
-
value: entity.value,
|
|
55
|
-
// Optionally: description: `Entity ID ${entity.value}`
|
|
56
|
-
}));
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* CLI prompt flow for creating an ActiveClass record.
|
|
61
|
-
*
|
|
62
|
-
* - Prompts for type (Controller, Utility, or Trigger)
|
|
63
|
-
* - For Trigger, also prompts for target entity selection (searchable)
|
|
64
|
-
* - Prompts for class name and optional description
|
|
65
|
-
*
|
|
66
|
-
* @async
|
|
67
|
-
* @param {Object} options - CLI options to bypass prompts
|
|
68
|
-
* @returns {Promise<Object>} The creation payload for ActiveClass.
|
|
69
|
-
*/
|
|
70
|
-
const createActiveClass = async (options = {}) => {
|
|
71
|
-
let classType = options.classType;
|
|
72
|
-
|
|
73
|
-
// Normalize class type from CLI format to internal format
|
|
74
|
-
if (classType) {
|
|
75
|
-
const typeMap = {
|
|
76
|
-
'controller': 'Controller',
|
|
77
|
-
'utility': 'Utility',
|
|
78
|
-
'class': 'Utility',
|
|
79
|
-
'trigger': 'Trigger'
|
|
80
|
-
};
|
|
81
|
-
classType = typeMap[classType.toLowerCase()] || classType;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (!classType) {
|
|
85
|
-
classType = await select({
|
|
86
|
-
message: "Select ActiveClass type:",
|
|
87
|
-
choices: [
|
|
88
|
-
{ name: "Controller", value: "Controller" },
|
|
89
|
-
{ name: "Class", value: "Utility" },
|
|
90
|
-
{ name: "Trigger", value: "Trigger" }
|
|
91
|
-
]
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (classType === "Trigger") {
|
|
96
|
-
let entityId = options.entityId;
|
|
97
|
-
|
|
98
|
-
if (!entityId) {
|
|
99
|
-
// Ensure entity list is loaded for search prompt
|
|
100
|
-
await loadEntities();
|
|
101
|
-
|
|
102
|
-
entityId = await search({
|
|
103
|
-
message: "Search and select entity for this Trigger:",
|
|
104
|
-
source: searchEntities,
|
|
105
|
-
pageSize: 8
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
let className = options.name;
|
|
110
|
-
if (!className) {
|
|
111
|
-
className = await input({
|
|
112
|
-
message: "Trigger class name:",
|
|
113
|
-
validate: (input) => input.length > 0 || "Please enter a class name"
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
let description = options.description;
|
|
118
|
-
if (description === undefined) {
|
|
119
|
-
description = await input({
|
|
120
|
-
message: "Description (optional):"
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return {
|
|
125
|
-
type: "ActiveClass",
|
|
126
|
-
classType,
|
|
127
|
-
triggerEntityId: entityId,
|
|
128
|
-
className,
|
|
129
|
-
description,
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const hint = classType === "Controller" ? "(without controller keyword)" : ""
|
|
134
|
-
|
|
135
|
-
// Flow for Controller or Utility
|
|
136
|
-
let className = options.name;
|
|
137
|
-
if (!className) {
|
|
138
|
-
className = await input({
|
|
139
|
-
message: `${classType} class name ${hint}:`,
|
|
140
|
-
validate: (input) => input.length > 0 || "Please enter a class name"
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
let description = options.description;
|
|
145
|
-
if (description === undefined) {
|
|
146
|
-
description = await input({
|
|
147
|
-
message: "Description (optional):"
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return {
|
|
152
|
-
type: "ActiveClass",
|
|
153
|
-
classType,
|
|
154
|
-
className,
|
|
155
|
-
description,
|
|
156
|
-
};
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* CLI prompt flow for creating an ActivePage record.
|
|
161
|
-
*
|
|
162
|
-
* - Prompts for page name and optional description.
|
|
163
|
-
*
|
|
164
|
-
* @async
|
|
165
|
-
* @param {Object} options - CLI options to bypass prompts
|
|
166
|
-
* @param {string} options.pageType - Pre-determined page type (Page or Template)
|
|
167
|
-
* @returns {Promise<Object>} The creation payload for ActivePage.
|
|
168
|
-
*/
|
|
169
|
-
const createActivePage = async (options = {}) => {
|
|
170
|
-
let pageType = options.pageType;
|
|
171
|
-
|
|
172
|
-
// Normalize page type from CLI format to internal format
|
|
173
|
-
if (pageType) {
|
|
174
|
-
const typeMap = {
|
|
175
|
-
'page': 'Page',
|
|
176
|
-
'template': 'Template'
|
|
177
|
-
};
|
|
178
|
-
pageType = typeMap[pageType.toLowerCase()] || pageType;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (!pageType) {
|
|
182
|
-
pageType = await select({
|
|
183
|
-
message: "Select ActivePage type:",
|
|
184
|
-
choices: [
|
|
185
|
-
{ name: "Page", value: "Page" },
|
|
186
|
-
{ name: "Template", value: "Template" }
|
|
187
|
-
]
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
let pageName = options.name;
|
|
192
|
-
if (!pageName) {
|
|
193
|
-
pageName = await input({
|
|
194
|
-
message: `${pageType} name:`,
|
|
195
|
-
validate: (input) => input.length > 0 || `Please enter a ${pageType.toLowerCase()} name`
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
let description = options.description;
|
|
200
|
-
if (description === undefined) {
|
|
201
|
-
description = await input({
|
|
202
|
-
message: "Description (optional):"
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return {
|
|
207
|
-
type: pageType,
|
|
208
|
-
pageName,
|
|
209
|
-
description,
|
|
210
|
-
};
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Saves the generated file content to the appropriate local folder.
|
|
215
|
-
* @param {string} entityType
|
|
216
|
-
* @param {object} formattedData
|
|
217
|
-
* @param {string} recordId
|
|
218
|
-
* @returns {Promise<string>} Full path of created file.
|
|
219
|
-
*/
|
|
220
|
-
const saveToFile = async (entityType, formattedData, recordId) => {
|
|
221
|
-
let fileDir, fileExt, fileName, fileContent;
|
|
222
|
-
|
|
223
|
-
let mapKey; // fallback to Page
|
|
224
|
-
if (entityType === "ActivePage") {
|
|
225
|
-
mapKey = formattedData.Type || "Active Page";
|
|
226
|
-
fileDir = TYPE_DIR_MAP[mapKey].directory;
|
|
227
|
-
fileExt = TYPE_DIR_MAP[mapKey].extension;
|
|
228
|
-
fileName = `${formattedData.Name}.${fileExt}`;
|
|
229
|
-
fileContent = formattedData.Content || "";
|
|
230
|
-
} else {
|
|
231
|
-
mapKey = formattedData.Type || "Class"; // fallback to Class
|
|
232
|
-
fileDir = TYPE_DIR_MAP[mapKey].directory;
|
|
233
|
-
fileExt = TYPE_DIR_MAP[mapKey].extension;
|
|
234
|
-
fileName = `${formattedData.Name}.${fileExt}`;
|
|
235
|
-
fileContent = formattedData.Body || "";
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Ensure directory exists
|
|
239
|
-
const outputDir = path.join(process.cwd(), EXPORT_ROOT, fileDir);
|
|
240
|
-
fs.mkdirSync(outputDir, { recursive: true });
|
|
241
|
-
|
|
242
|
-
// Full file path
|
|
243
|
-
const filePath = path.join(outputDir, fileName);
|
|
244
|
-
|
|
245
|
-
// Write file
|
|
246
|
-
fs.writeFileSync(filePath, fileContent);
|
|
247
|
-
|
|
248
|
-
// Add a file tag so we can keep track of file changes
|
|
249
|
-
await setFileTag(filePath, recordId);
|
|
250
|
-
|
|
251
|
-
// Update base with content snapshot to ensure cache is in sync
|
|
252
|
-
const contentHash = sha256(fileContent);
|
|
253
|
-
updateBase(
|
|
254
|
-
filePath,
|
|
255
|
-
{ Id: recordId, Type: mapKey },
|
|
256
|
-
'',
|
|
257
|
-
{ content: fileContent, hash: contentHash }
|
|
258
|
-
);
|
|
259
|
-
|
|
260
|
-
return filePath;
|
|
261
|
-
};
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* Main CLI handler for `magentrix create`.
|
|
265
|
-
*
|
|
266
|
-
* - Ensures valid Magentrix credentials.
|
|
267
|
-
* - Prompts user to choose entity type (ActiveClass or ActivePage).
|
|
268
|
-
* - Runs the appropriate prompt flow to gather data.
|
|
269
|
-
* - Logs (or sends) the resulting payload.
|
|
270
|
-
*
|
|
271
|
-
* @async
|
|
272
|
-
* @function create
|
|
273
|
-
* @param {Object} cliOptions - Options passed from CLI flags
|
|
274
|
-
* @returns {Promise<void>}
|
|
275
|
-
*/
|
|
276
|
-
export const create = async (cliOptions = {}) => {
|
|
277
|
-
// Validate CLI options
|
|
278
|
-
if (cliOptions.type) {
|
|
279
|
-
const validTypes = ['class', 'page', 'template'];
|
|
280
|
-
if (!validTypes.includes(cliOptions.type.toLowerCase())) {
|
|
281
|
-
throw new Error(`Invalid --type: "${cliOptions.type}". Valid options are: ${validTypes.join(', ')}`);
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
if (cliOptions.classType) {
|
|
286
|
-
const validClassTypes = ['controller', 'utility', 'class', 'trigger'];
|
|
287
|
-
if (!validClassTypes.includes(cliOptions.classType.toLowerCase())) {
|
|
288
|
-
throw new Error(`Invalid --class-type: "${cliOptions.classType}". Valid options are: ${validClassTypes.join(', ')}`);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Ensure --class-type is only used with --type class
|
|
292
|
-
if (cliOptions.type && cliOptions.type.toLowerCase() !== 'class') {
|
|
293
|
-
throw new Error('--class-type can only be used with --type class');
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
if (cliOptions.entityId) {
|
|
298
|
-
// Ensure --entity-id is only used with triggers
|
|
299
|
-
if (cliOptions.classType && cliOptions.classType.toLowerCase() !== 'trigger') {
|
|
300
|
-
throw new Error('--entity-id can only be used with --class-type trigger');
|
|
301
|
-
}
|
|
302
|
-
if (!cliOptions.classType) {
|
|
303
|
-
console.log(chalk.yellow('⚠️ Warning: --entity-id provided without --class-type trigger. It will be ignored unless you select Trigger interactively.'));
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// Clear the terminal
|
|
308
|
-
process.stdout.write('\x1Bc');
|
|
309
|
-
|
|
310
|
-
// 1. Prompt for and validate Magentrix credentials
|
|
311
|
-
credentials = await withSpinner('Authenticating...', async () => {
|
|
312
|
-
return await ensureValidCredentials();
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
// 2. Determine entity type (from CLI or prompt)
|
|
316
|
-
let entityType;
|
|
317
|
-
let pageType;
|
|
318
|
-
|
|
319
|
-
if (cliOptions.type) {
|
|
320
|
-
const typeMap = {
|
|
321
|
-
'class': 'ActiveClass',
|
|
322
|
-
'page': 'ActivePage',
|
|
323
|
-
'template': 'ActivePage'
|
|
324
|
-
};
|
|
325
|
-
entityType = typeMap[cliOptions.type.toLowerCase()];
|
|
326
|
-
|
|
327
|
-
// If template was specified, set the pageType
|
|
328
|
-
if (cliOptions.type.toLowerCase() === 'template') {
|
|
329
|
-
pageType = 'Template';
|
|
330
|
-
} else if (cliOptions.type.toLowerCase() === 'page') {
|
|
331
|
-
pageType = 'Page';
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
if (!entityType) {
|
|
335
|
-
throw new Error(`Invalid type: ${cliOptions.type}. Valid types are: class, page, template`);
|
|
336
|
-
}
|
|
337
|
-
} else {
|
|
338
|
-
entityType = await select({
|
|
339
|
-
message: "What would you like to create?",
|
|
340
|
-
choices: [
|
|
341
|
-
{ name: "ActiveClass (Controller, Class, Trigger)", value: "ActiveClass" },
|
|
342
|
-
{ name: "ActivePage (ASPX Page)", value: "ActivePage" },
|
|
343
|
-
]
|
|
344
|
-
});
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// 3. Build payload via relevant prompt flow
|
|
348
|
-
let result;
|
|
349
|
-
if (entityType === 'ActiveClass') {
|
|
350
|
-
result = await createActiveClass(cliOptions);
|
|
351
|
-
} else if (entityType === 'ActivePage') {
|
|
352
|
-
result = await createActivePage({ ...cliOptions, pageType });
|
|
353
|
-
} else {
|
|
354
|
-
// Unknown
|
|
355
|
-
throw new Error("Unknown type selected.");
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// 4. Display or send payload to Magentrix API
|
|
359
|
-
let formattedData;
|
|
360
|
-
|
|
361
|
-
if (entityType === "ActivePage") {
|
|
362
|
-
formattedData = {
|
|
363
|
-
Label: result.pageName,
|
|
364
|
-
Name: result.pageName,
|
|
365
|
-
Description: result.description || "",
|
|
366
|
-
Type: `Active ${result.type}`,
|
|
367
|
-
Content: getPageTemplate(result.pageName, result.pageName)
|
|
368
|
-
};
|
|
369
|
-
} else if (entityType === "ActiveClass") {
|
|
370
|
-
let name = result.className;
|
|
371
|
-
|
|
372
|
-
if (result.classType === "Controller") {
|
|
373
|
-
if (!name.endsWith("Controller")) name += "Controller";
|
|
374
|
-
formattedData = {
|
|
375
|
-
Name: name,
|
|
376
|
-
Body: getControllerTemplate(name),
|
|
377
|
-
Description: result.description || "",
|
|
378
|
-
Type: "Controller"
|
|
379
|
-
};
|
|
380
|
-
} else if (result.classType === "Utility" || result.classType === "Class") {
|
|
381
|
-
formattedData = {
|
|
382
|
-
Name: name,
|
|
383
|
-
Body: getClassTemplate(name),
|
|
384
|
-
Description: result.description || "",
|
|
385
|
-
Type: "Class"
|
|
386
|
-
};
|
|
387
|
-
} else if (result.classType === "Trigger") {
|
|
388
|
-
const selectedTriggerEntity = triggerEntities.find(entity => entity.value === result.triggerEntityId);
|
|
389
|
-
|
|
390
|
-
formattedData = {
|
|
391
|
-
Name: name,
|
|
392
|
-
Body: getTriggerTemplate(name, selectedTriggerEntity.name),
|
|
393
|
-
Description: result.description || "",
|
|
394
|
-
Type: "Trigger",
|
|
395
|
-
EntityId: result.triggerEntityId
|
|
396
|
-
};
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
console.log();
|
|
401
|
-
|
|
402
|
-
// Uncomment to perform creation via API:
|
|
403
|
-
const creationResponse = await withSpinner('Creating file...', async () => {
|
|
404
|
-
return await createEntity(
|
|
405
|
-
credentials.instanceUrl,
|
|
406
|
-
credentials.token.value,
|
|
407
|
-
entityType,
|
|
408
|
-
formattedData
|
|
409
|
-
).catch(err => {
|
|
410
|
-
// The error object structure from fetchMagentrix:
|
|
411
|
-
// - err.type: 'network' | 'http' | 'api'
|
|
412
|
-
// - err.message: formatted error message
|
|
413
|
-
// - err.response: the API response data (if available)
|
|
414
|
-
// - err.status: HTTP status code (for http errors)
|
|
415
|
-
return {
|
|
416
|
-
hasErrors: true,
|
|
417
|
-
errorType: err.type,
|
|
418
|
-
errorMessage: err.message,
|
|
419
|
-
status: err.status,
|
|
420
|
-
statusText: err.statusText,
|
|
421
|
-
errors: err.response?.errors || err.response?.Errors || [],
|
|
422
|
-
rawResponse: err.response
|
|
423
|
-
};
|
|
424
|
-
});
|
|
425
|
-
});
|
|
426
|
-
|
|
427
|
-
if (creationResponse?.hasErrors) {
|
|
428
|
-
console.log();
|
|
429
|
-
console.log(chalk.bgRed.bold.white(' ✖ Creation Failed '));
|
|
430
|
-
console.log(chalk.redBright('─'.repeat(48)));
|
|
431
|
-
|
|
432
|
-
// Display HTTP status if available
|
|
433
|
-
if (creationResponse.status) {
|
|
434
|
-
console.log(chalk.yellow(` Status: ${creationResponse.status} ${creationResponse.statusText || ''}`));
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
const errors = creationResponse.errors || [];
|
|
438
|
-
|
|
439
|
-
if (errors.length > 0) {
|
|
440
|
-
errors.forEach((err) => {
|
|
441
|
-
const code = err.code ? chalk.gray(`[${err.code}] `) : '';
|
|
442
|
-
const msg = chalk.whiteBright(err.message || err);
|
|
443
|
-
console.log(`${chalk.redBright(' •')} ${code}${msg}`);
|
|
444
|
-
});
|
|
445
|
-
} else if (creationResponse.errorMessage) {
|
|
446
|
-
// Show the formatted error message from fetchMagentrix
|
|
447
|
-
console.log(chalk.red(creationResponse.errorMessage));
|
|
448
|
-
} else {
|
|
449
|
-
console.log(chalk.red('An unknown error occurred during creation.'));
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
return;
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
console.log(`✅ File successfully created on Magentrix server.`);
|
|
456
|
-
|
|
457
|
-
try {
|
|
458
|
-
const filePath = await saveToFile(entityType, formattedData, creationResponse.id);
|
|
459
|
-
console.log(`📄 Local copy saved at: ${filePath}`);
|
|
460
|
-
console.log('✨ You can now edit this file locally. Don\'t forget to push changes when ready!');
|
|
461
|
-
} catch (err) {
|
|
462
|
-
console.error(`🚨 Error: Unable to save file locally (${err.message}).`);
|
|
463
|
-
console.error('You may need to check directory permissions or disk space.');
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
};
|
|
1
|
+
import { ensureValidCredentials } from "../utils/cli/helpers/ensureCredentials.js";
|
|
2
|
+
import { createEntity } from "../utils/magentrix/api/createEntity.js";
|
|
3
|
+
import { search, select, input } from "@inquirer/prompts";
|
|
4
|
+
import { listEntities } from "../utils/magentrix/api/retrieveEntity.js";
|
|
5
|
+
import { getClassTemplate, getControllerTemplate, getPageTemplate, getTriggerTemplate } from "../utils/template.js";
|
|
6
|
+
import { withSpinner } from "../utils/spinner.js";
|
|
7
|
+
import { EXPORT_ROOT, TYPE_DIR_MAP } from "../vars/global.js";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
import { setFileTag } from "../utils/filetag.js";
|
|
12
|
+
import { updateBase } from "../utils/updateFileBase.js";
|
|
13
|
+
import { sha256 } from "../utils/hash.js";
|
|
14
|
+
|
|
15
|
+
// Credentials and entity cache (module-scope for re-use during prompt session)
|
|
16
|
+
let credentials = {};
|
|
17
|
+
let triggerEntities = [];
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Loads all entities from the Magentrix API and prepares them
|
|
21
|
+
* for searchable selection in the CLI.
|
|
22
|
+
* Populates the `triggerEntities` array.
|
|
23
|
+
*
|
|
24
|
+
* @async
|
|
25
|
+
* @returns {Promise<void>}
|
|
26
|
+
*/
|
|
27
|
+
const loadEntities = async () => {
|
|
28
|
+
const entities = await listEntities(credentials.instanceUrl, credentials.token.value);
|
|
29
|
+
// Map to inquirer-friendly objects: { name, value }
|
|
30
|
+
triggerEntities = entities.map(entity => ({
|
|
31
|
+
label: entity.Label,
|
|
32
|
+
value: entity.Id,
|
|
33
|
+
name: entity.Name
|
|
34
|
+
}));
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Search function for entity selection using @inquirer/prompts.
|
|
39
|
+
* Filters `triggerEntities` array based on the user's input term.
|
|
40
|
+
*
|
|
41
|
+
* @async
|
|
42
|
+
* @param {string} term - User's search input.
|
|
43
|
+
* @returns {Promise<Array>} Filtered entity choices for prompt.
|
|
44
|
+
*/
|
|
45
|
+
const searchEntities = async (term = '') => {
|
|
46
|
+
// Case-insensitive substring match against entity names
|
|
47
|
+
const filteredEntities = triggerEntities.filter(entity =>
|
|
48
|
+
entity.name.toLowerCase().includes(term.toLowerCase())
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// Format for inquirer searchable prompt
|
|
52
|
+
return filteredEntities.map(entity => ({
|
|
53
|
+
name: `${entity.label} (${entity.value})`,
|
|
54
|
+
value: entity.value,
|
|
55
|
+
// Optionally: description: `Entity ID ${entity.value}`
|
|
56
|
+
}));
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* CLI prompt flow for creating an ActiveClass record.
|
|
61
|
+
*
|
|
62
|
+
* - Prompts for type (Controller, Utility, or Trigger)
|
|
63
|
+
* - For Trigger, also prompts for target entity selection (searchable)
|
|
64
|
+
* - Prompts for class name and optional description
|
|
65
|
+
*
|
|
66
|
+
* @async
|
|
67
|
+
* @param {Object} options - CLI options to bypass prompts
|
|
68
|
+
* @returns {Promise<Object>} The creation payload for ActiveClass.
|
|
69
|
+
*/
|
|
70
|
+
const createActiveClass = async (options = {}) => {
|
|
71
|
+
let classType = options.classType;
|
|
72
|
+
|
|
73
|
+
// Normalize class type from CLI format to internal format
|
|
74
|
+
if (classType) {
|
|
75
|
+
const typeMap = {
|
|
76
|
+
'controller': 'Controller',
|
|
77
|
+
'utility': 'Utility',
|
|
78
|
+
'class': 'Utility',
|
|
79
|
+
'trigger': 'Trigger'
|
|
80
|
+
};
|
|
81
|
+
classType = typeMap[classType.toLowerCase()] || classType;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!classType) {
|
|
85
|
+
classType = await select({
|
|
86
|
+
message: "Select ActiveClass type:",
|
|
87
|
+
choices: [
|
|
88
|
+
{ name: "Controller", value: "Controller" },
|
|
89
|
+
{ name: "Class", value: "Utility" },
|
|
90
|
+
{ name: "Trigger", value: "Trigger" }
|
|
91
|
+
]
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (classType === "Trigger") {
|
|
96
|
+
let entityId = options.entityId;
|
|
97
|
+
|
|
98
|
+
if (!entityId) {
|
|
99
|
+
// Ensure entity list is loaded for search prompt
|
|
100
|
+
await loadEntities();
|
|
101
|
+
|
|
102
|
+
entityId = await search({
|
|
103
|
+
message: "Search and select entity for this Trigger:",
|
|
104
|
+
source: searchEntities,
|
|
105
|
+
pageSize: 8
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let className = options.name;
|
|
110
|
+
if (!className) {
|
|
111
|
+
className = await input({
|
|
112
|
+
message: "Trigger class name:",
|
|
113
|
+
validate: (input) => input.length > 0 || "Please enter a class name"
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let description = options.description;
|
|
118
|
+
if (description === undefined) {
|
|
119
|
+
description = await input({
|
|
120
|
+
message: "Description (optional):"
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
type: "ActiveClass",
|
|
126
|
+
classType,
|
|
127
|
+
triggerEntityId: entityId,
|
|
128
|
+
className,
|
|
129
|
+
description,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const hint = classType === "Controller" ? "(without controller keyword)" : ""
|
|
134
|
+
|
|
135
|
+
// Flow for Controller or Utility
|
|
136
|
+
let className = options.name;
|
|
137
|
+
if (!className) {
|
|
138
|
+
className = await input({
|
|
139
|
+
message: `${classType} class name ${hint}:`,
|
|
140
|
+
validate: (input) => input.length > 0 || "Please enter a class name"
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let description = options.description;
|
|
145
|
+
if (description === undefined) {
|
|
146
|
+
description = await input({
|
|
147
|
+
message: "Description (optional):"
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
type: "ActiveClass",
|
|
153
|
+
classType,
|
|
154
|
+
className,
|
|
155
|
+
description,
|
|
156
|
+
};
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* CLI prompt flow for creating an ActivePage record.
|
|
161
|
+
*
|
|
162
|
+
* - Prompts for page name and optional description.
|
|
163
|
+
*
|
|
164
|
+
* @async
|
|
165
|
+
* @param {Object} options - CLI options to bypass prompts
|
|
166
|
+
* @param {string} options.pageType - Pre-determined page type (Page or Template)
|
|
167
|
+
* @returns {Promise<Object>} The creation payload for ActivePage.
|
|
168
|
+
*/
|
|
169
|
+
const createActivePage = async (options = {}) => {
|
|
170
|
+
let pageType = options.pageType;
|
|
171
|
+
|
|
172
|
+
// Normalize page type from CLI format to internal format
|
|
173
|
+
if (pageType) {
|
|
174
|
+
const typeMap = {
|
|
175
|
+
'page': 'Page',
|
|
176
|
+
'template': 'Template'
|
|
177
|
+
};
|
|
178
|
+
pageType = typeMap[pageType.toLowerCase()] || pageType;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (!pageType) {
|
|
182
|
+
pageType = await select({
|
|
183
|
+
message: "Select ActivePage type:",
|
|
184
|
+
choices: [
|
|
185
|
+
{ name: "Page", value: "Page" },
|
|
186
|
+
{ name: "Template", value: "Template" }
|
|
187
|
+
]
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
let pageName = options.name;
|
|
192
|
+
if (!pageName) {
|
|
193
|
+
pageName = await input({
|
|
194
|
+
message: `${pageType} name:`,
|
|
195
|
+
validate: (input) => input.length > 0 || `Please enter a ${pageType.toLowerCase()} name`
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
let description = options.description;
|
|
200
|
+
if (description === undefined) {
|
|
201
|
+
description = await input({
|
|
202
|
+
message: "Description (optional):"
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
type: pageType,
|
|
208
|
+
pageName,
|
|
209
|
+
description,
|
|
210
|
+
};
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Saves the generated file content to the appropriate local folder.
|
|
215
|
+
* @param {string} entityType
|
|
216
|
+
* @param {object} formattedData
|
|
217
|
+
* @param {string} recordId
|
|
218
|
+
* @returns {Promise<string>} Full path of created file.
|
|
219
|
+
*/
|
|
220
|
+
const saveToFile = async (entityType, formattedData, recordId) => {
|
|
221
|
+
let fileDir, fileExt, fileName, fileContent;
|
|
222
|
+
|
|
223
|
+
let mapKey; // fallback to Page
|
|
224
|
+
if (entityType === "ActivePage") {
|
|
225
|
+
mapKey = formattedData.Type || "Active Page";
|
|
226
|
+
fileDir = TYPE_DIR_MAP[mapKey].directory;
|
|
227
|
+
fileExt = TYPE_DIR_MAP[mapKey].extension;
|
|
228
|
+
fileName = `${formattedData.Name}.${fileExt}`;
|
|
229
|
+
fileContent = formattedData.Content || "";
|
|
230
|
+
} else {
|
|
231
|
+
mapKey = formattedData.Type || "Class"; // fallback to Class
|
|
232
|
+
fileDir = TYPE_DIR_MAP[mapKey].directory;
|
|
233
|
+
fileExt = TYPE_DIR_MAP[mapKey].extension;
|
|
234
|
+
fileName = `${formattedData.Name}.${fileExt}`;
|
|
235
|
+
fileContent = formattedData.Body || "";
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Ensure directory exists
|
|
239
|
+
const outputDir = path.join(process.cwd(), EXPORT_ROOT, fileDir);
|
|
240
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
241
|
+
|
|
242
|
+
// Full file path
|
|
243
|
+
const filePath = path.join(outputDir, fileName);
|
|
244
|
+
|
|
245
|
+
// Write file
|
|
246
|
+
fs.writeFileSync(filePath, fileContent);
|
|
247
|
+
|
|
248
|
+
// Add a file tag so we can keep track of file changes
|
|
249
|
+
await setFileTag(filePath, recordId);
|
|
250
|
+
|
|
251
|
+
// Update base with content snapshot to ensure cache is in sync
|
|
252
|
+
const contentHash = sha256(fileContent);
|
|
253
|
+
updateBase(
|
|
254
|
+
filePath,
|
|
255
|
+
{ Id: recordId, Type: mapKey },
|
|
256
|
+
'',
|
|
257
|
+
{ content: fileContent, hash: contentHash }
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
return filePath;
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Main CLI handler for `magentrix create`.
|
|
265
|
+
*
|
|
266
|
+
* - Ensures valid Magentrix credentials.
|
|
267
|
+
* - Prompts user to choose entity type (ActiveClass or ActivePage).
|
|
268
|
+
* - Runs the appropriate prompt flow to gather data.
|
|
269
|
+
* - Logs (or sends) the resulting payload.
|
|
270
|
+
*
|
|
271
|
+
* @async
|
|
272
|
+
* @function create
|
|
273
|
+
* @param {Object} cliOptions - Options passed from CLI flags
|
|
274
|
+
* @returns {Promise<void>}
|
|
275
|
+
*/
|
|
276
|
+
export const create = async (cliOptions = {}) => {
|
|
277
|
+
// Validate CLI options
|
|
278
|
+
if (cliOptions.type) {
|
|
279
|
+
const validTypes = ['class', 'page', 'template'];
|
|
280
|
+
if (!validTypes.includes(cliOptions.type.toLowerCase())) {
|
|
281
|
+
throw new Error(`Invalid --type: "${cliOptions.type}". Valid options are: ${validTypes.join(', ')}`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (cliOptions.classType) {
|
|
286
|
+
const validClassTypes = ['controller', 'utility', 'class', 'trigger'];
|
|
287
|
+
if (!validClassTypes.includes(cliOptions.classType.toLowerCase())) {
|
|
288
|
+
throw new Error(`Invalid --class-type: "${cliOptions.classType}". Valid options are: ${validClassTypes.join(', ')}`);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Ensure --class-type is only used with --type class
|
|
292
|
+
if (cliOptions.type && cliOptions.type.toLowerCase() !== 'class') {
|
|
293
|
+
throw new Error('--class-type can only be used with --type class');
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (cliOptions.entityId) {
|
|
298
|
+
// Ensure --entity-id is only used with triggers
|
|
299
|
+
if (cliOptions.classType && cliOptions.classType.toLowerCase() !== 'trigger') {
|
|
300
|
+
throw new Error('--entity-id can only be used with --class-type trigger');
|
|
301
|
+
}
|
|
302
|
+
if (!cliOptions.classType) {
|
|
303
|
+
console.log(chalk.yellow('⚠️ Warning: --entity-id provided without --class-type trigger. It will be ignored unless you select Trigger interactively.'));
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Clear the terminal
|
|
308
|
+
process.stdout.write('\x1Bc');
|
|
309
|
+
|
|
310
|
+
// 1. Prompt for and validate Magentrix credentials
|
|
311
|
+
credentials = await withSpinner('Authenticating...', async () => {
|
|
312
|
+
return await ensureValidCredentials();
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// 2. Determine entity type (from CLI or prompt)
|
|
316
|
+
let entityType;
|
|
317
|
+
let pageType;
|
|
318
|
+
|
|
319
|
+
if (cliOptions.type) {
|
|
320
|
+
const typeMap = {
|
|
321
|
+
'class': 'ActiveClass',
|
|
322
|
+
'page': 'ActivePage',
|
|
323
|
+
'template': 'ActivePage'
|
|
324
|
+
};
|
|
325
|
+
entityType = typeMap[cliOptions.type.toLowerCase()];
|
|
326
|
+
|
|
327
|
+
// If template was specified, set the pageType
|
|
328
|
+
if (cliOptions.type.toLowerCase() === 'template') {
|
|
329
|
+
pageType = 'Template';
|
|
330
|
+
} else if (cliOptions.type.toLowerCase() === 'page') {
|
|
331
|
+
pageType = 'Page';
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (!entityType) {
|
|
335
|
+
throw new Error(`Invalid type: ${cliOptions.type}. Valid types are: class, page, template`);
|
|
336
|
+
}
|
|
337
|
+
} else {
|
|
338
|
+
entityType = await select({
|
|
339
|
+
message: "What would you like to create?",
|
|
340
|
+
choices: [
|
|
341
|
+
{ name: "ActiveClass (Controller, Class, Trigger)", value: "ActiveClass" },
|
|
342
|
+
{ name: "ActivePage (ASPX Page)", value: "ActivePage" },
|
|
343
|
+
]
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// 3. Build payload via relevant prompt flow
|
|
348
|
+
let result;
|
|
349
|
+
if (entityType === 'ActiveClass') {
|
|
350
|
+
result = await createActiveClass(cliOptions);
|
|
351
|
+
} else if (entityType === 'ActivePage') {
|
|
352
|
+
result = await createActivePage({ ...cliOptions, pageType });
|
|
353
|
+
} else {
|
|
354
|
+
// Unknown
|
|
355
|
+
throw new Error("Unknown type selected.");
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// 4. Display or send payload to Magentrix API
|
|
359
|
+
let formattedData;
|
|
360
|
+
|
|
361
|
+
if (entityType === "ActivePage") {
|
|
362
|
+
formattedData = {
|
|
363
|
+
Label: result.pageName,
|
|
364
|
+
Name: result.pageName,
|
|
365
|
+
Description: result.description || "",
|
|
366
|
+
Type: `Active ${result.type}`,
|
|
367
|
+
Content: getPageTemplate(result.pageName, result.pageName)
|
|
368
|
+
};
|
|
369
|
+
} else if (entityType === "ActiveClass") {
|
|
370
|
+
let name = result.className;
|
|
371
|
+
|
|
372
|
+
if (result.classType === "Controller") {
|
|
373
|
+
if (!name.endsWith("Controller")) name += "Controller";
|
|
374
|
+
formattedData = {
|
|
375
|
+
Name: name,
|
|
376
|
+
Body: getControllerTemplate(name),
|
|
377
|
+
Description: result.description || "",
|
|
378
|
+
Type: "Controller"
|
|
379
|
+
};
|
|
380
|
+
} else if (result.classType === "Utility" || result.classType === "Class") {
|
|
381
|
+
formattedData = {
|
|
382
|
+
Name: name,
|
|
383
|
+
Body: getClassTemplate(name),
|
|
384
|
+
Description: result.description || "",
|
|
385
|
+
Type: "Class"
|
|
386
|
+
};
|
|
387
|
+
} else if (result.classType === "Trigger") {
|
|
388
|
+
const selectedTriggerEntity = triggerEntities.find(entity => entity.value === result.triggerEntityId);
|
|
389
|
+
|
|
390
|
+
formattedData = {
|
|
391
|
+
Name: name,
|
|
392
|
+
Body: getTriggerTemplate(name, selectedTriggerEntity.name),
|
|
393
|
+
Description: result.description || "",
|
|
394
|
+
Type: "Trigger",
|
|
395
|
+
EntityId: result.triggerEntityId
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
console.log();
|
|
401
|
+
|
|
402
|
+
// Uncomment to perform creation via API:
|
|
403
|
+
const creationResponse = await withSpinner('Creating file...', async () => {
|
|
404
|
+
return await createEntity(
|
|
405
|
+
credentials.instanceUrl,
|
|
406
|
+
credentials.token.value,
|
|
407
|
+
entityType,
|
|
408
|
+
formattedData
|
|
409
|
+
).catch(err => {
|
|
410
|
+
// The error object structure from fetchMagentrix:
|
|
411
|
+
// - err.type: 'network' | 'http' | 'api'
|
|
412
|
+
// - err.message: formatted error message
|
|
413
|
+
// - err.response: the API response data (if available)
|
|
414
|
+
// - err.status: HTTP status code (for http errors)
|
|
415
|
+
return {
|
|
416
|
+
hasErrors: true,
|
|
417
|
+
errorType: err.type,
|
|
418
|
+
errorMessage: err.message,
|
|
419
|
+
status: err.status,
|
|
420
|
+
statusText: err.statusText,
|
|
421
|
+
errors: err.response?.errors || err.response?.Errors || [],
|
|
422
|
+
rawResponse: err.response
|
|
423
|
+
};
|
|
424
|
+
});
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
if (creationResponse?.hasErrors) {
|
|
428
|
+
console.log();
|
|
429
|
+
console.log(chalk.bgRed.bold.white(' ✖ Creation Failed '));
|
|
430
|
+
console.log(chalk.redBright('─'.repeat(48)));
|
|
431
|
+
|
|
432
|
+
// Display HTTP status if available
|
|
433
|
+
if (creationResponse.status) {
|
|
434
|
+
console.log(chalk.yellow(` Status: ${creationResponse.status} ${creationResponse.statusText || ''}`));
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const errors = creationResponse.errors || [];
|
|
438
|
+
|
|
439
|
+
if (errors.length > 0) {
|
|
440
|
+
errors.forEach((err) => {
|
|
441
|
+
const code = err.code ? chalk.gray(`[${err.code}] `) : '';
|
|
442
|
+
const msg = chalk.whiteBright(err.message || err);
|
|
443
|
+
console.log(`${chalk.redBright(' •')} ${code}${msg}`);
|
|
444
|
+
});
|
|
445
|
+
} else if (creationResponse.errorMessage) {
|
|
446
|
+
// Show the formatted error message from fetchMagentrix
|
|
447
|
+
console.log(chalk.red(creationResponse.errorMessage));
|
|
448
|
+
} else {
|
|
449
|
+
console.log(chalk.red('An unknown error occurred during creation.'));
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
console.log(`✅ File successfully created on Magentrix server.`);
|
|
456
|
+
|
|
457
|
+
try {
|
|
458
|
+
const filePath = await saveToFile(entityType, formattedData, creationResponse.id);
|
|
459
|
+
console.log(`📄 Local copy saved at: ${filePath}`);
|
|
460
|
+
console.log('✨ You can now edit this file locally. Don\'t forget to push changes when ready!');
|
|
461
|
+
} catch (err) {
|
|
462
|
+
console.error(`🚨 Error: Unable to save file locally (${err.message}).`);
|
|
463
|
+
console.error('You may need to check directory permissions or disk space.');
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
};
|