@magentrix-corp/magentrix-cli 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +282 -2
- package/actions/autopublish.js +9 -48
- package/actions/iris/buildStage.js +330 -0
- package/actions/iris/delete.js +211 -0
- package/actions/iris/dev.js +338 -0
- package/actions/iris/index.js +6 -0
- package/actions/iris/link.js +377 -0
- package/actions/iris/recover.js +228 -0
- package/actions/publish.js +258 -15
- package/actions/pull.js +520 -327
- package/actions/setup.js +62 -15
- package/bin/magentrix.js +43 -1
- package/package.json +2 -1
- package/utils/autopublishLock.js +77 -0
- package/utils/cli/helpers/compare.js +4 -5
- package/utils/cli/helpers/ensureApiKey.js +28 -22
- package/utils/cli/helpers/ensureInstanceUrl.js +35 -27
- package/utils/cli/writeRecords.js +13 -2
- package/utils/config.js +76 -0
- package/utils/iris/backup.js +201 -0
- package/utils/iris/builder.js +304 -0
- package/utils/iris/config-reader.js +296 -0
- package/utils/iris/deleteHelper.js +102 -0
- package/utils/iris/linker.js +490 -0
- package/utils/iris/validator.js +281 -0
- package/utils/iris/zipper.js +239 -0
- package/utils/logger.js +13 -5
- package/utils/magentrix/api/auth.js +45 -6
- package/utils/magentrix/api/iris.js +235 -0
- package/utils/permissionError.js +70 -0
- package/utils/progress.js +87 -1
- package/utils/updateFileBase.js +14 -2
- package/vars/global.js +1 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { select, input } from '@inquirer/prompts';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import { existsSync } from 'node:fs';
|
|
5
|
+
import { resolve } from 'node:path';
|
|
6
|
+
import {
|
|
7
|
+
readVueConfig,
|
|
8
|
+
formatMissingConfigError,
|
|
9
|
+
formatConfigErrors,
|
|
10
|
+
findConfigFile,
|
|
11
|
+
backupConfigFile,
|
|
12
|
+
restoreConfigFile,
|
|
13
|
+
injectAssetsIntoConfig
|
|
14
|
+
} from '../../utils/iris/config-reader.js';
|
|
15
|
+
import { getIrisAssets } from '../../utils/magentrix/api/iris.js';
|
|
16
|
+
import {
|
|
17
|
+
getLinkedProjectsWithStatus,
|
|
18
|
+
buildProjectChoices
|
|
19
|
+
} from '../../utils/iris/linker.js';
|
|
20
|
+
import { ensureValidCredentials } from '../../utils/cli/helpers/ensureCredentials.js';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* iris-dev command - Start Vue dev server with platform assets injected.
|
|
24
|
+
*
|
|
25
|
+
* Options:
|
|
26
|
+
* --path <dir> Specify Vue project path
|
|
27
|
+
* --no-inject Skip asset injection, just run dev server
|
|
28
|
+
* --restore Restore config.ts from backup without running
|
|
29
|
+
*/
|
|
30
|
+
export const irisDev = async (options = {}) => {
|
|
31
|
+
process.stdout.write('\x1Bc'); // Clear console
|
|
32
|
+
|
|
33
|
+
const { path: pathOption, inject = true, restore } = options;
|
|
34
|
+
|
|
35
|
+
// Handle --restore option
|
|
36
|
+
if (restore) {
|
|
37
|
+
await handleRestore(pathOption);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Determine which project to use
|
|
42
|
+
let projectPath = pathOption;
|
|
43
|
+
let vueConfig = null;
|
|
44
|
+
|
|
45
|
+
if (projectPath) {
|
|
46
|
+
projectPath = resolve(projectPath);
|
|
47
|
+
|
|
48
|
+
if (!existsSync(projectPath)) {
|
|
49
|
+
console.log(chalk.red(`Error: Path does not exist: ${projectPath}`));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
vueConfig = readVueConfig(projectPath);
|
|
54
|
+
} else {
|
|
55
|
+
// Try current directory first
|
|
56
|
+
const cwdConfig = readVueConfig(process.cwd());
|
|
57
|
+
if (cwdConfig.found && cwdConfig.errors.length === 0) {
|
|
58
|
+
projectPath = process.cwd();
|
|
59
|
+
vueConfig = cwdConfig;
|
|
60
|
+
console.log(chalk.gray(`Using current directory: ${projectPath}`));
|
|
61
|
+
} else {
|
|
62
|
+
// Prompt user to select a project
|
|
63
|
+
const result = await selectProject();
|
|
64
|
+
if (!result) return;
|
|
65
|
+
|
|
66
|
+
projectPath = result.path;
|
|
67
|
+
vueConfig = result.config;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Validate Vue config
|
|
72
|
+
if (!vueConfig.found) {
|
|
73
|
+
console.log(chalk.red(formatMissingConfigError(projectPath)));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (vueConfig.errors.length > 0) {
|
|
78
|
+
console.log(chalk.red(formatConfigErrors(vueConfig)));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const { slug, appName, siteUrl } = vueConfig;
|
|
83
|
+
const configPath = findConfigFile(projectPath);
|
|
84
|
+
|
|
85
|
+
console.log(chalk.blue('\nIris Development Server'));
|
|
86
|
+
console.log(chalk.gray('─'.repeat(48)));
|
|
87
|
+
console.log(chalk.white(` Project: ${chalk.cyan(appName)} (${slug})`));
|
|
88
|
+
console.log(chalk.white(` Path: ${chalk.gray(projectPath)}`));
|
|
89
|
+
if (siteUrl) {
|
|
90
|
+
console.log(chalk.white(` Site: ${chalk.gray(siteUrl)}`));
|
|
91
|
+
}
|
|
92
|
+
console.log();
|
|
93
|
+
|
|
94
|
+
let backupPath = null;
|
|
95
|
+
let assetsInjected = false;
|
|
96
|
+
|
|
97
|
+
// Inject assets if enabled
|
|
98
|
+
if (inject && siteUrl) {
|
|
99
|
+
console.log(chalk.blue('Fetching platform assets...'));
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
// Get credentials for API call
|
|
103
|
+
const { instanceUrl, token } = await ensureValidCredentials();
|
|
104
|
+
|
|
105
|
+
// Use siteUrl from config if different from instanceUrl
|
|
106
|
+
const targetUrl = siteUrl || instanceUrl;
|
|
107
|
+
|
|
108
|
+
const assetsResult = await getIrisAssets(targetUrl, token.value);
|
|
109
|
+
|
|
110
|
+
if (assetsResult.success && assetsResult.assets?.length > 0) {
|
|
111
|
+
console.log(chalk.green(`\u2713 Found ${assetsResult.assets.length} platform assets`));
|
|
112
|
+
|
|
113
|
+
// Backup config file
|
|
114
|
+
console.log(chalk.blue('Backing up config.ts...'));
|
|
115
|
+
backupPath = backupConfigFile(configPath);
|
|
116
|
+
console.log(chalk.green(`\u2713 Backup created: ${backupPath}`));
|
|
117
|
+
|
|
118
|
+
// Inject assets
|
|
119
|
+
console.log(chalk.blue('Injecting assets into config.ts...'));
|
|
120
|
+
const injected = injectAssetsIntoConfig(configPath, assetsResult.assets);
|
|
121
|
+
|
|
122
|
+
if (injected) {
|
|
123
|
+
assetsInjected = true;
|
|
124
|
+
console.log(chalk.green('\u2713 Assets injected'));
|
|
125
|
+
} else {
|
|
126
|
+
console.log(chalk.yellow('Warning: Could not inject assets. Continuing without injection.'));
|
|
127
|
+
}
|
|
128
|
+
} else if (assetsResult.error) {
|
|
129
|
+
console.log(chalk.yellow(`Warning: Could not fetch assets: ${assetsResult.error}`));
|
|
130
|
+
console.log(chalk.gray('Continuing without asset injection.'));
|
|
131
|
+
} else {
|
|
132
|
+
console.log(chalk.yellow('No platform assets found.'));
|
|
133
|
+
}
|
|
134
|
+
} catch (err) {
|
|
135
|
+
console.log(chalk.yellow(`Warning: Error fetching assets: ${err.message}`));
|
|
136
|
+
console.log(chalk.gray('Continuing without asset injection.'));
|
|
137
|
+
}
|
|
138
|
+
} else if (!inject) {
|
|
139
|
+
console.log(chalk.gray('Skipping asset injection (--no-inject)'));
|
|
140
|
+
} else if (!siteUrl) {
|
|
141
|
+
console.log(chalk.yellow('Warning: No siteUrl in config.ts. Cannot fetch platform assets.'));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Start dev server
|
|
145
|
+
console.log();
|
|
146
|
+
console.log(chalk.blue('Starting development server...'));
|
|
147
|
+
console.log(chalk.gray('Press Ctrl+C to stop'));
|
|
148
|
+
console.log();
|
|
149
|
+
|
|
150
|
+
await runDevServer(projectPath, configPath, backupPath, assetsInjected);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Run the Vue development server.
|
|
155
|
+
*/
|
|
156
|
+
async function runDevServer(projectPath, configPath, backupPath, assetsInjected) {
|
|
157
|
+
return new Promise((resolvePromise) => {
|
|
158
|
+
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
159
|
+
|
|
160
|
+
const child = spawn(npmCmd, ['run', 'dev'], {
|
|
161
|
+
cwd: projectPath,
|
|
162
|
+
stdio: 'inherit',
|
|
163
|
+
shell: false
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Handle cleanup on exit
|
|
167
|
+
const cleanup = () => {
|
|
168
|
+
if (assetsInjected && backupPath) {
|
|
169
|
+
console.log();
|
|
170
|
+
console.log(chalk.blue('Restoring config.ts from backup...'));
|
|
171
|
+
const restored = restoreConfigFile(configPath);
|
|
172
|
+
if (restored) {
|
|
173
|
+
console.log(chalk.green('\u2713 config.ts restored'));
|
|
174
|
+
} else {
|
|
175
|
+
console.log(chalk.yellow('Warning: Could not restore config.ts'));
|
|
176
|
+
console.log(chalk.gray(`Backup is at: ${backupPath}`));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// Handle process signals
|
|
182
|
+
process.on('SIGINT', () => {
|
|
183
|
+
cleanup();
|
|
184
|
+
child.kill('SIGINT');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
process.on('SIGTERM', () => {
|
|
188
|
+
cleanup();
|
|
189
|
+
child.kill('SIGTERM');
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
child.on('close', (code) => {
|
|
193
|
+
// If dev server exited with error, show helpful context first
|
|
194
|
+
if (code !== 0 && code !== null) {
|
|
195
|
+
console.log();
|
|
196
|
+
console.log(chalk.bgYellow.black(' External Process Error '));
|
|
197
|
+
console.log(chalk.yellow('─'.repeat(48)));
|
|
198
|
+
console.log(chalk.white('The Vue dev server exited with an error.'));
|
|
199
|
+
console.log(chalk.gray('This is NOT a MagentrixCLI issue.'));
|
|
200
|
+
console.log();
|
|
201
|
+
console.log(chalk.white('Common causes:'));
|
|
202
|
+
console.log(chalk.gray(' • Permission issues in node_modules/'));
|
|
203
|
+
console.log(chalk.gray(' • Missing dependencies (run npm install)'));
|
|
204
|
+
console.log(chalk.gray(' • Port already in use'));
|
|
205
|
+
console.log(chalk.gray(' • Syntax errors in project files'));
|
|
206
|
+
console.log();
|
|
207
|
+
console.log(chalk.white('If you see EACCES/permission errors, try:'));
|
|
208
|
+
console.log(chalk.cyan(` sudo chown -R $(whoami) "${projectPath}/node_modules"`));
|
|
209
|
+
console.log(chalk.yellow('─'.repeat(48)));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Cleanup after showing error context
|
|
213
|
+
cleanup();
|
|
214
|
+
|
|
215
|
+
resolvePromise(code);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
child.on('error', (err) => {
|
|
219
|
+
console.log(chalk.red(`Failed to start dev server: ${err.message}`));
|
|
220
|
+
cleanup();
|
|
221
|
+
resolvePromise(1);
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Handle --restore option.
|
|
228
|
+
*/
|
|
229
|
+
async function handleRestore(pathOption) {
|
|
230
|
+
let projectPath = pathOption;
|
|
231
|
+
|
|
232
|
+
if (!projectPath) {
|
|
233
|
+
// Try current directory
|
|
234
|
+
const cwdConfig = readVueConfig(process.cwd());
|
|
235
|
+
if (cwdConfig.found) {
|
|
236
|
+
projectPath = process.cwd();
|
|
237
|
+
} else {
|
|
238
|
+
console.log(chalk.red('No Vue project found in current directory.'));
|
|
239
|
+
console.log(chalk.gray('Use --path to specify the project path.'));
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
projectPath = resolve(projectPath);
|
|
245
|
+
const configPath = findConfigFile(projectPath);
|
|
246
|
+
|
|
247
|
+
if (!configPath) {
|
|
248
|
+
console.log(chalk.red('No config.ts found in project.'));
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
console.log(chalk.blue('Restoring config.ts from backup...'));
|
|
253
|
+
|
|
254
|
+
const restored = restoreConfigFile(configPath);
|
|
255
|
+
|
|
256
|
+
if (restored) {
|
|
257
|
+
console.log(chalk.green('\u2713 config.ts restored from backup'));
|
|
258
|
+
} else {
|
|
259
|
+
console.log(chalk.yellow('No backup file found.'));
|
|
260
|
+
console.log(chalk.gray(`Expected backup at: ${configPath}.bak`));
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Prompt user to select a project.
|
|
266
|
+
*/
|
|
267
|
+
async function selectProject() {
|
|
268
|
+
const projectsWithStatus = getLinkedProjectsWithStatus();
|
|
269
|
+
|
|
270
|
+
// Check if CWD is a Vue project (and not already linked)
|
|
271
|
+
const cwdConfig = readVueConfig(process.cwd());
|
|
272
|
+
const inVueProject = cwdConfig.found && cwdConfig.errors.length === 0;
|
|
273
|
+
const cwdAlreadyLinked = projectsWithStatus.some(p =>
|
|
274
|
+
resolve(p.path) === resolve(process.cwd())
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
// Build choices using the helper
|
|
278
|
+
const choices = buildProjectChoices({
|
|
279
|
+
includeManual: true,
|
|
280
|
+
includeCancel: true,
|
|
281
|
+
showInvalid: true
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// If CWD is a valid Vue project and not linked, add it at the top
|
|
285
|
+
if (inVueProject && !cwdAlreadyLinked) {
|
|
286
|
+
choices.unshift({
|
|
287
|
+
name: `${cwdConfig.appName} (${cwdConfig.slug}) - Current directory (not linked)`,
|
|
288
|
+
value: { type: 'cwd', path: process.cwd() }
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Check if we have any projects to show
|
|
293
|
+
const hasProjects = choices.some(c => c.value?.type === 'linked' || c.value?.type === 'cwd');
|
|
294
|
+
|
|
295
|
+
if (!hasProjects) {
|
|
296
|
+
console.log(chalk.yellow('No linked Vue projects found.'));
|
|
297
|
+
console.log();
|
|
298
|
+
console.log(chalk.gray('To get started:'));
|
|
299
|
+
console.log(chalk.white(` 1. Link a Vue project: ${chalk.cyan('magentrix iris-link')}`));
|
|
300
|
+
console.log(chalk.white(` 2. Or specify path: ${chalk.cyan('magentrix iris-dev --path /path/to/vue-project')}`));
|
|
301
|
+
console.log();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const choice = await select({
|
|
305
|
+
message: 'Which project do you want to run?',
|
|
306
|
+
choices
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
if (choice.type === 'cancel') {
|
|
310
|
+
console.log(chalk.gray('Cancelled.'));
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (choice.type === 'manual') {
|
|
315
|
+
const manualPath = await input({
|
|
316
|
+
message: 'Enter the path to your Vue project:',
|
|
317
|
+
validate: (value) => {
|
|
318
|
+
if (!value.trim()) {
|
|
319
|
+
return 'Path is required';
|
|
320
|
+
}
|
|
321
|
+
const resolved = resolve(value);
|
|
322
|
+
if (!existsSync(resolved)) {
|
|
323
|
+
return `Path does not exist: ${resolved}`;
|
|
324
|
+
}
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
const config = readVueConfig(resolve(manualPath));
|
|
330
|
+
return { path: resolve(manualPath), config };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Linked project or CWD
|
|
334
|
+
const config = readVueConfig(choice.path);
|
|
335
|
+
return { path: choice.path, config };
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export default irisDev;
|