@magentrix-corp/magentrix-cli 1.0.1 → 1.1.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/README.md +99 -1
- package/actions/autopublish.v2.js +2 -2
- package/actions/create.js +180 -64
- package/actions/publish.js +348 -253
- package/actions/pull.js +53 -8
- package/actions/setup.js +34 -7
- package/bin/magentrix.js +61 -2
- package/package.json +3 -2
- package/utils/assetPaths.js +138 -0
- package/utils/cacher.js +5 -2
- package/utils/cli/helpers/compare.js +4 -2
- package/utils/downloadAssets.js +14 -8
- package/utils/magentrix/api/assets.js +21 -1
- package/utils/magentrix/api/retrieveEntity.js +55 -1
- package/utils/updateFileBase.js +8 -3
- package/vars/global.js +1 -0
package/README.md
CHANGED
|
@@ -63,9 +63,59 @@ You should see the version number displayed.
|
|
|
63
63
|
|
|
64
64
|
---
|
|
65
65
|
|
|
66
|
+
## Updating the Package
|
|
67
|
+
|
|
68
|
+
To update MagentrixCLI to the latest version:
|
|
69
|
+
|
|
70
|
+
### Global Installation Update
|
|
71
|
+
If you installed globally (recommended):
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npm update -g @magentrix-corp/magentrix-cli
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Or to ensure you get the absolute latest version:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npm install -g @magentrix-corp/magentrix-cli@latest
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Local Installation Update
|
|
84
|
+
If you installed locally in your project:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npm update @magentrix-corp/magentrix-cli
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Or for the latest version:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npm install @magentrix-corp/magentrix-cli@latest
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Check for Updates
|
|
97
|
+
To see if you're running the latest version:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
# Check your current version
|
|
101
|
+
magentrix --version
|
|
102
|
+
|
|
103
|
+
# Check the latest available version on npm
|
|
104
|
+
npm view @magentrix-corp/magentrix-cli version
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### After Updating
|
|
108
|
+
Your configuration and local files are preserved during updates. You don't need to run `magentrix setup` again unless there are breaking changes (which will be noted in release notes).
|
|
109
|
+
|
|
110
|
+
**Note**: It's a good practice to check for updates periodically to get the latest features, bug fixes, and performance improvements.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
66
114
|
## First Time Setup
|
|
67
115
|
|
|
68
116
|
### Step 1: Configure Your Credentials
|
|
117
|
+
|
|
118
|
+
#### Interactive Setup
|
|
69
119
|
Run this command to set up your connection to Magentrix:
|
|
70
120
|
|
|
71
121
|
```bash
|
|
@@ -76,6 +126,17 @@ You'll be prompted for:
|
|
|
76
126
|
- **API Key**: Paste your Magentrix API key
|
|
77
127
|
- **Instance URL**: Enter your Magentrix URL (like `https://yourcompany.magentrixcloud.com`)
|
|
78
128
|
|
|
129
|
+
#### Non-Interactive Setup
|
|
130
|
+
For automation or CI/CD, you can provide credentials via command-line flags:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
magentrix setup --api-key YOUR_API_KEY --instance-url https://yourcompany.magentrixcloud.com
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Available flags:**
|
|
137
|
+
- `--api-key <apiKey>` - Your Magentrix API key
|
|
138
|
+
- `--instance-url <instanceUrl>` - Your Magentrix instance URL
|
|
139
|
+
|
|
79
140
|
The tool will test your credentials and save them securely. It will also automatically configure VS Code file associations for syntax highlighting of Magentrix file types (.ac, .ctrl, .trigger, .aspx files).
|
|
80
141
|
|
|
81
142
|
### Editor Support
|
|
@@ -96,7 +157,7 @@ This creates a `src` folder with all your files organized into:
|
|
|
96
157
|
- `Triggers/` - Your trigger code (`.trigger` files)
|
|
97
158
|
- `Pages/` - Your ASPX pages (`.aspx` files)
|
|
98
159
|
- `Templates/` - Your templates (`.aspx` files)
|
|
99
|
-
- `
|
|
160
|
+
- `Assets/` - Your static assets (images, CSS, JavaScript, etc.)
|
|
100
161
|
|
|
101
162
|
---
|
|
102
163
|
|
|
@@ -178,6 +239,43 @@ This starts an interactive wizard:
|
|
|
178
239
|
|
|
179
240
|
The tool creates the file both locally and on your Magentrix server with proper templates.
|
|
180
241
|
|
|
242
|
+
### Non-Interactive File Creation
|
|
243
|
+
For automation or scripting, you can provide all parameters via command-line flags:
|
|
244
|
+
|
|
245
|
+
#### Create a Controller
|
|
246
|
+
```bash
|
|
247
|
+
magentrix create --type class --class-type controller --name UserController --description "Handles user operations"
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
#### Create a Utility Class
|
|
251
|
+
```bash
|
|
252
|
+
magentrix create --type class --class-type utility --name EmailHelper --description "Email utility functions"
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
#### Create a Trigger
|
|
256
|
+
```bash
|
|
257
|
+
magentrix create --type class --class-type trigger --entity-id ENTITY_ID --name AccountTrigger --description "Account trigger logic"
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
#### Create a Page
|
|
261
|
+
```bash
|
|
262
|
+
magentrix create --type page --name Dashboard --description "Main dashboard page"
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
#### Create a Template
|
|
266
|
+
```bash
|
|
267
|
+
magentrix create --type template --name EmailTemplate --description "Email notification template"
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
**Available flags:**
|
|
271
|
+
- `--type <type>` - Entity type: `class`, `page`, or `template`
|
|
272
|
+
- `--class-type <classType>` - For classes: `controller`, `utility`, or `trigger`
|
|
273
|
+
- `--name <name>` - Name of the file to create
|
|
274
|
+
- `--description <description>` - Optional description
|
|
275
|
+
- `--entity-id <entityId>` - Required for triggers: the entity ID to attach the trigger to
|
|
276
|
+
|
|
277
|
+
**Tip**: You can mix interactive and non-interactive modes. For example, provide just the `--name` flag and the tool will prompt you for the rest.
|
|
278
|
+
|
|
181
279
|
---
|
|
182
280
|
|
|
183
281
|
## Working with Files
|
|
@@ -117,11 +117,11 @@ const readFileSafe = (filePath) => {
|
|
|
117
117
|
};
|
|
118
118
|
|
|
119
119
|
/**
|
|
120
|
-
* Checks if file is within
|
|
120
|
+
* Checks if file is within Assets (static asset)
|
|
121
121
|
*/
|
|
122
122
|
const isStaticAsset = (filePath) => {
|
|
123
123
|
const normalizedPath = path.normalize(filePath);
|
|
124
|
-
return normalizedPath.includes(path.join('
|
|
124
|
+
return normalizedPath.includes(path.join('Assets'));
|
|
125
125
|
};
|
|
126
126
|
|
|
127
127
|
/**
|
package/actions/create.js
CHANGED
|
@@ -10,6 +10,7 @@ import fs from 'fs';
|
|
|
10
10
|
import chalk from 'chalk';
|
|
11
11
|
import { setFileTag } from "../utils/filetag.js";
|
|
12
12
|
import { updateBase } from "../utils/updateFileBase.js";
|
|
13
|
+
import { sha256 } from "../utils/hash.js";
|
|
13
14
|
|
|
14
15
|
// Credentials and entity cache (module-scope for re-use during prompt session)
|
|
15
16
|
let credentials = {};
|
|
@@ -57,42 +58,68 @@ const searchEntities = async (term = '') => {
|
|
|
57
58
|
|
|
58
59
|
/**
|
|
59
60
|
* CLI prompt flow for creating an ActiveClass record.
|
|
60
|
-
*
|
|
61
|
+
*
|
|
61
62
|
* - Prompts for type (Controller, Utility, or Trigger)
|
|
62
63
|
* - For Trigger, also prompts for target entity selection (searchable)
|
|
63
64
|
* - Prompts for class name and optional description
|
|
64
|
-
*
|
|
65
|
+
*
|
|
65
66
|
* @async
|
|
67
|
+
* @param {Object} options - CLI options to bypass prompts
|
|
66
68
|
* @returns {Promise<Object>} The creation payload for ActiveClass.
|
|
67
69
|
*/
|
|
68
|
-
const createActiveClass = async () => {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
+
}
|
|
77
94
|
|
|
78
95
|
if (classType === "Trigger") {
|
|
79
|
-
|
|
80
|
-
await loadEntities();
|
|
96
|
+
let entityId = options.entityId;
|
|
81
97
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
pageSize: 8
|
|
86
|
-
});
|
|
98
|
+
if (!entityId) {
|
|
99
|
+
// Ensure entity list is loaded for search prompt
|
|
100
|
+
await loadEntities();
|
|
87
101
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
102
|
+
entityId = await search({
|
|
103
|
+
message: "Search and select entity for this Trigger:",
|
|
104
|
+
source: searchEntities,
|
|
105
|
+
pageSize: 8
|
|
106
|
+
});
|
|
107
|
+
}
|
|
92
108
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
+
}
|
|
96
123
|
|
|
97
124
|
return {
|
|
98
125
|
type: "ActiveClass",
|
|
@@ -106,14 +133,20 @@ const createActiveClass = async () => {
|
|
|
106
133
|
const hint = classType === "Controller" ? "(without controller keyword)" : ""
|
|
107
134
|
|
|
108
135
|
// Flow for Controller or Utility
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
+
}
|
|
113
143
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
144
|
+
let description = options.description;
|
|
145
|
+
if (description === undefined) {
|
|
146
|
+
description = await input({
|
|
147
|
+
message: "Description (optional):"
|
|
148
|
+
});
|
|
149
|
+
}
|
|
117
150
|
|
|
118
151
|
return {
|
|
119
152
|
type: "ActiveClass",
|
|
@@ -125,29 +158,50 @@ const createActiveClass = async () => {
|
|
|
125
158
|
|
|
126
159
|
/**
|
|
127
160
|
* CLI prompt flow for creating an ActivePage record.
|
|
128
|
-
*
|
|
161
|
+
*
|
|
129
162
|
* - Prompts for page name and optional description.
|
|
130
|
-
*
|
|
163
|
+
*
|
|
131
164
|
* @async
|
|
165
|
+
* @param {Object} options - CLI options to bypass prompts
|
|
166
|
+
* @param {string} options.pageType - Pre-determined page type (Page or Template)
|
|
132
167
|
* @returns {Promise<Object>} The creation payload for ActivePage.
|
|
133
168
|
*/
|
|
134
|
-
const createActivePage = async () => {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
+
}
|
|
142
180
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
+
}
|
|
147
190
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
+
}
|
|
151
205
|
|
|
152
206
|
return {
|
|
153
207
|
type: pageType,
|
|
@@ -193,24 +247,63 @@ const saveToFile = async (entityType, formattedData, recordId) => {
|
|
|
193
247
|
|
|
194
248
|
// Add a file tag so we can keep track of file changes
|
|
195
249
|
await setFileTag(filePath, recordId);
|
|
196
|
-
|
|
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
|
+
);
|
|
197
259
|
|
|
198
260
|
return filePath;
|
|
199
261
|
};
|
|
200
262
|
|
|
201
263
|
/**
|
|
202
264
|
* Main CLI handler for `magentrix create`.
|
|
203
|
-
*
|
|
265
|
+
*
|
|
204
266
|
* - Ensures valid Magentrix credentials.
|
|
205
267
|
* - Prompts user to choose entity type (ActiveClass or ActivePage).
|
|
206
268
|
* - Runs the appropriate prompt flow to gather data.
|
|
207
269
|
* - Logs (or sends) the resulting payload.
|
|
208
|
-
*
|
|
270
|
+
*
|
|
209
271
|
* @async
|
|
210
272
|
* @function create
|
|
273
|
+
* @param {Object} cliOptions - Options passed from CLI flags
|
|
211
274
|
* @returns {Promise<void>}
|
|
212
275
|
*/
|
|
213
|
-
export const create = async () => {
|
|
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
|
+
|
|
214
307
|
// Clear the terminal
|
|
215
308
|
process.stdout.write('\x1Bc');
|
|
216
309
|
|
|
@@ -219,21 +312,44 @@ export const create = async () => {
|
|
|
219
312
|
return await ensureValidCredentials();
|
|
220
313
|
});
|
|
221
314
|
|
|
222
|
-
// 2.
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
+
}
|
|
230
346
|
|
|
231
347
|
// 3. Build payload via relevant prompt flow
|
|
232
348
|
let result;
|
|
233
349
|
if (entityType === 'ActiveClass') {
|
|
234
|
-
result = await createActiveClass();
|
|
350
|
+
result = await createActiveClass(cliOptions);
|
|
235
351
|
} else if (entityType === 'ActivePage') {
|
|
236
|
-
result = await createActivePage();
|
|
352
|
+
result = await createActivePage({ ...cliOptions, pageType });
|
|
237
353
|
} else {
|
|
238
354
|
// Unknown
|
|
239
355
|
throw new Error("Unknown type selected.");
|
|
@@ -302,7 +418,7 @@ export const create = async () => {
|
|
|
302
418
|
const errors = creationResponse.errors || [];
|
|
303
419
|
|
|
304
420
|
if (errors.length > 0) {
|
|
305
|
-
errors.forEach((err
|
|
421
|
+
errors.forEach((err) => {
|
|
306
422
|
const code = err.code ? chalk.gray(`[${err.code}] `) : '';
|
|
307
423
|
const status = err.status ? chalk.yellow(`[${err.status}] `) : '';
|
|
308
424
|
const msg = chalk.whiteBright(err.message);
|