@magentrix-corp/magentrix-cli 1.3.15 → 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 -45
- 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/iris/link.js
CHANGED
|
@@ -1,375 +1,375 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import { select, input, confirm } from '@inquirer/prompts';
|
|
3
|
-
import { existsSync } from 'node:fs';
|
|
4
|
-
import { resolve } from 'node:path';
|
|
5
|
-
import {
|
|
6
|
-
linkVueProject,
|
|
7
|
-
unlinkVueProject,
|
|
8
|
-
getLinkedProjectsWithStatus,
|
|
9
|
-
formatLinkedProjects,
|
|
10
|
-
cleanupInvalidProjects
|
|
11
|
-
} from '../../utils/iris/linker.js';
|
|
12
|
-
import { formatMissingConfigError, formatConfigErrors, readVueConfig } from '../../utils/iris/config-reader.js';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* iris-app-link command - Link a Vue project to the CLI.
|
|
16
|
-
*
|
|
17
|
-
* Options:
|
|
18
|
-
* --path <dir> Specify Vue project path directly
|
|
19
|
-
* --unlink Remove a linked project
|
|
20
|
-
* --list Show all linked projects
|
|
21
|
-
* --cleanup Remove invalid (non-existent) linked projects
|
|
22
|
-
*/
|
|
23
|
-
export const irisLink = async (options = {}) => {
|
|
24
|
-
process.stdout.write('\x1Bc'); // Clear console
|
|
25
|
-
|
|
26
|
-
const { path: pathOption, unlink, list, cleanup } = options;
|
|
27
|
-
|
|
28
|
-
// Handle --list option
|
|
29
|
-
if (list) {
|
|
30
|
-
const projects = getLinkedProjectsWithStatus();
|
|
31
|
-
console.log(formatLinkedProjects(projects));
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Handle --cleanup option
|
|
36
|
-
if (cleanup) {
|
|
37
|
-
await handleCleanup();
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Handle --unlink option
|
|
42
|
-
if (unlink) {
|
|
43
|
-
await handleUnlink(pathOption);
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// If path provided directly, link it
|
|
48
|
-
if (pathOption) {
|
|
49
|
-
await linkProjectDirect(pathOption);
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Interactive mode - show main menu
|
|
54
|
-
await showMainMenu();
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Show the main menu for managing linked projects.
|
|
59
|
-
*/
|
|
60
|
-
async function showMainMenu() {
|
|
61
|
-
const projects = getLinkedProjectsWithStatus();
|
|
62
|
-
const validCount = projects.filter(p => p.validation.valid).length;
|
|
63
|
-
const invalidCount = projects.filter(p => !p.validation.valid).length;
|
|
64
|
-
|
|
65
|
-
console.log(chalk.blue.bold('Iris Vue Project Manager'));
|
|
66
|
-
console.log(chalk.gray('─'.repeat(48)));
|
|
67
|
-
|
|
68
|
-
if (projects.length > 0) {
|
|
69
|
-
console.log(chalk.white(`Linked projects: ${validCount} valid`));
|
|
70
|
-
if (invalidCount > 0) {
|
|
71
|
-
console.log(chalk.yellow(` ${invalidCount} with issues`));
|
|
72
|
-
}
|
|
73
|
-
} else {
|
|
74
|
-
console.log(chalk.gray('No Vue projects linked yet.'));
|
|
75
|
-
}
|
|
76
|
-
console.log();
|
|
77
|
-
|
|
78
|
-
const choices = [
|
|
79
|
-
{
|
|
80
|
-
name: 'Link a Vue project',
|
|
81
|
-
value: 'link',
|
|
82
|
-
description: chalk.dim('→ Add a Vue project to the CLI')
|
|
83
|
-
}
|
|
84
|
-
];
|
|
85
|
-
|
|
86
|
-
if (projects.length > 0) {
|
|
87
|
-
choices.push({
|
|
88
|
-
name: 'View linked projects',
|
|
89
|
-
value: 'list',
|
|
90
|
-
description: chalk.dim('→ Show all linked Vue projects with status')
|
|
91
|
-
});
|
|
92
|
-
choices.push({
|
|
93
|
-
name: 'Unlink a project',
|
|
94
|
-
value: 'unlink',
|
|
95
|
-
description: chalk.dim('→ Remove a Vue project from the CLI')
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (invalidCount > 0) {
|
|
100
|
-
choices.push({
|
|
101
|
-
name: `Clean up invalid projects (${invalidCount})`,
|
|
102
|
-
value: 'cleanup',
|
|
103
|
-
description: chalk.dim('→ Remove projects with missing paths')
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
choices.push({
|
|
108
|
-
name: 'Exit',
|
|
109
|
-
value: 'exit'
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
const action = await select({
|
|
113
|
-
message: 'What would you like to do?',
|
|
114
|
-
choices
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
switch (action) {
|
|
118
|
-
case 'link':
|
|
119
|
-
await handleLink();
|
|
120
|
-
break;
|
|
121
|
-
case 'list':
|
|
122
|
-
console.log();
|
|
123
|
-
console.log(formatLinkedProjects(projects));
|
|
124
|
-
break;
|
|
125
|
-
case 'unlink':
|
|
126
|
-
await handleUnlink();
|
|
127
|
-
break;
|
|
128
|
-
case 'cleanup':
|
|
129
|
-
await handleCleanup();
|
|
130
|
-
break;
|
|
131
|
-
case 'exit':
|
|
132
|
-
console.log(chalk.gray('Goodbye!'));
|
|
133
|
-
break;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Handle linking a new Vue project.
|
|
139
|
-
*/
|
|
140
|
-
async function handleLink(pathOption) {
|
|
141
|
-
let projectPath = pathOption;
|
|
142
|
-
|
|
143
|
-
// If no path provided, prompt for one
|
|
144
|
-
if (!projectPath) {
|
|
145
|
-
// Check if we're in a Vue project directory
|
|
146
|
-
const cwdConfig = readVueConfig(process.cwd());
|
|
147
|
-
const cwdPath = process.cwd();
|
|
148
|
-
|
|
149
|
-
const choices = [];
|
|
150
|
-
|
|
151
|
-
// Always show current directory option, but disable if not a valid Vue project
|
|
152
|
-
if (cwdConfig.found && cwdConfig.errors.length === 0) {
|
|
153
|
-
// Valid Vue project
|
|
154
|
-
choices.push({
|
|
155
|
-
name: `Current directory (${cwdPath})`,
|
|
156
|
-
value: cwdPath,
|
|
157
|
-
description: chalk.dim(`→ App: "${cwdConfig.appName}" (${cwdConfig.slug})`)
|
|
158
|
-
});
|
|
159
|
-
} else if (cwdConfig.found && cwdConfig.errors.length > 0) {
|
|
160
|
-
// Has config.ts but with errors
|
|
161
|
-
const errorMsg = cwdConfig.errors[0] || 'Invalid config';
|
|
162
|
-
choices.push({
|
|
163
|
-
name: `Current directory (${cwdPath})`,
|
|
164
|
-
value: '__disabled__',
|
|
165
|
-
disabled: `Config error: ${errorMsg}`
|
|
166
|
-
});
|
|
167
|
-
} else {
|
|
168
|
-
// No config.ts found
|
|
169
|
-
choices.push({
|
|
170
|
-
name: `Current directory (${cwdPath})`,
|
|
171
|
-
value: '__disabled__',
|
|
172
|
-
disabled: 'No config.ts found'
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
choices.push({
|
|
177
|
-
name: 'Enter path manually',
|
|
178
|
-
value: '__manual__',
|
|
179
|
-
description: chalk.dim('→ Specify the full path to a Vue project')
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
choices.push({
|
|
183
|
-
name: 'Cancel',
|
|
184
|
-
value: '__cancel__'
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
const choice = await select({
|
|
188
|
-
message: 'Which Vue project do you want to link?',
|
|
189
|
-
choices
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
if (choice === '__cancel__') {
|
|
193
|
-
console.log(chalk.gray('Cancelled.'));
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (choice === '__manual__') {
|
|
198
|
-
projectPath = await input({
|
|
199
|
-
message: 'Enter the path to your Vue project:',
|
|
200
|
-
validate: (value) => {
|
|
201
|
-
if (!value.trim()) {
|
|
202
|
-
return 'Path is required';
|
|
203
|
-
}
|
|
204
|
-
const resolved = resolve(value);
|
|
205
|
-
if (!existsSync(resolved)) {
|
|
206
|
-
return `Path does not exist: ${resolved}`;
|
|
207
|
-
}
|
|
208
|
-
return true;
|
|
209
|
-
}
|
|
210
|
-
});
|
|
211
|
-
} else {
|
|
212
|
-
projectPath = choice;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
await linkProjectDirect(projectPath);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Link a project directly by path.
|
|
221
|
-
*/
|
|
222
|
-
async function linkProjectDirect(projectPath) {
|
|
223
|
-
// Resolve the path
|
|
224
|
-
projectPath = resolve(projectPath);
|
|
225
|
-
|
|
226
|
-
// Validate path exists
|
|
227
|
-
if (!existsSync(projectPath)) {
|
|
228
|
-
console.log(chalk.red(`Error: Path does not exist: ${projectPath}`));
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Check Vue config
|
|
233
|
-
const vueConfig = readVueConfig(projectPath);
|
|
234
|
-
if (!vueConfig.found) {
|
|
235
|
-
console.log(chalk.red(formatMissingConfigError(projectPath)));
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
if (vueConfig.errors.length > 0) {
|
|
240
|
-
console.log(chalk.red(formatConfigErrors(vueConfig)));
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Link the project
|
|
245
|
-
console.log(chalk.blue(`\nLinking Vue project...`));
|
|
246
|
-
console.log(chalk.gray(` Path: ${projectPath}`));
|
|
247
|
-
console.log(chalk.gray(` Slug: ${vueConfig.slug}`));
|
|
248
|
-
console.log(chalk.gray(` Name: ${vueConfig.appName}`));
|
|
249
|
-
if (vueConfig.siteUrl) {
|
|
250
|
-
console.log(chalk.gray(` Site: ${vueConfig.siteUrl}`));
|
|
251
|
-
}
|
|
252
|
-
console.log();
|
|
253
|
-
|
|
254
|
-
const result = linkVueProject(projectPath);
|
|
255
|
-
|
|
256
|
-
if (!result.success) {
|
|
257
|
-
console.log(chalk.red(`Failed to link project: ${result.error}`));
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
if (result.updated) {
|
|
262
|
-
console.log(chalk.green(`\u2713 Project updated successfully!`));
|
|
263
|
-
console.log(chalk.gray(` The linked project configuration has been updated.`));
|
|
264
|
-
} else {
|
|
265
|
-
console.log(chalk.green(`\u2713 Project linked successfully!`));
|
|
266
|
-
console.log();
|
|
267
|
-
console.log(chalk.cyan('Next steps:'));
|
|
268
|
-
console.log(chalk.white(` 1. Build and stage: ${chalk.yellow('magentrix vue-run-build')}`));
|
|
269
|
-
console.log(chalk.white(` 2. Publish to server: ${chalk.yellow('magentrix publish')}`));
|
|
270
|
-
console.log(chalk.white(` Or use ${chalk.yellow('magentrix autopublish')} for automatic publishing`));
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
console.log();
|
|
274
|
-
console.log(chalk.gray('Note: Linked projects are stored globally and available across all Magentrix workspaces.'));
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* Handle unlinking a Vue project.
|
|
279
|
-
*/
|
|
280
|
-
async function handleUnlink(pathOption) {
|
|
281
|
-
const linkedProjects = getLinkedProjectsWithStatus();
|
|
282
|
-
|
|
283
|
-
if (linkedProjects.length === 0) {
|
|
284
|
-
console.log(chalk.yellow('No Vue projects are currently linked.'));
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
let projectToUnlink = pathOption;
|
|
289
|
-
|
|
290
|
-
// If no path provided, prompt user to select
|
|
291
|
-
if (!projectToUnlink) {
|
|
292
|
-
const choices = linkedProjects.map(p => {
|
|
293
|
-
const validation = p.validation;
|
|
294
|
-
let prefix = '';
|
|
295
|
-
if (!validation.valid) {
|
|
296
|
-
prefix = validation.exists ? '⚠ ' : '✗ ';
|
|
297
|
-
}
|
|
298
|
-
const displayName = validation.currentAppName || p.appName;
|
|
299
|
-
const displaySlug = validation.currentSlug || p.slug;
|
|
300
|
-
|
|
301
|
-
return {
|
|
302
|
-
name: `${prefix}${displayName} (${displaySlug})`,
|
|
303
|
-
value: p.slug,
|
|
304
|
-
description: chalk.dim(`→ Path: ${p.path}`)
|
|
305
|
-
};
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
choices.push({
|
|
309
|
-
name: 'Cancel',
|
|
310
|
-
value: '__cancel__'
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
projectToUnlink = await select({
|
|
314
|
-
message: 'Which project do you want to unlink?',
|
|
315
|
-
choices
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
if (projectToUnlink === '__cancel__') {
|
|
319
|
-
console.log(chalk.gray('Cancelled.'));
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
// Unlink the project
|
|
325
|
-
const result = unlinkVueProject(projectToUnlink);
|
|
326
|
-
|
|
327
|
-
if (!result.success) {
|
|
328
|
-
console.log(chalk.red(`Failed to unlink: ${result.error}`));
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
console.log(chalk.green(`\u2713 Project '${result.project.appName}' (${result.project.slug}) has been unlinked.`));
|
|
333
|
-
console.log(chalk.gray(` Path: ${result.project.path}`));
|
|
334
|
-
console.log();
|
|
335
|
-
console.log(chalk.gray('Note: This only removes the link from CLI tracking.'));
|
|
336
|
-
console.log(chalk.gray('The Vue project and any deployed Iris app are unchanged.'));
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
/**
|
|
340
|
-
* Handle cleanup of invalid projects.
|
|
341
|
-
*/
|
|
342
|
-
async function handleCleanup() {
|
|
343
|
-
const projectsWithStatus = getLinkedProjectsWithStatus();
|
|
344
|
-
const invalidProjects = projectsWithStatus.filter(p => !p.validation.exists);
|
|
345
|
-
|
|
346
|
-
if (invalidProjects.length === 0) {
|
|
347
|
-
console.log(chalk.green('All linked projects are valid. No cleanup needed.'));
|
|
348
|
-
return;
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
console.log(chalk.yellow(`Found ${invalidProjects.length} project(s) with invalid paths:\n`));
|
|
352
|
-
|
|
353
|
-
for (const project of invalidProjects) {
|
|
354
|
-
console.log(chalk.red(` ✗ ${project.appName} (${project.slug})`));
|
|
355
|
-
console.log(chalk.gray(` Path: ${project.path}`));
|
|
356
|
-
console.log(chalk.gray(` Error: ${project.validation.errors.join(', ')}`));
|
|
357
|
-
console.log();
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
const shouldCleanup = await confirm({
|
|
361
|
-
message: `Remove these ${invalidProjects.length} invalid project(s)?`,
|
|
362
|
-
default: true
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
if (!shouldCleanup) {
|
|
366
|
-
console.log(chalk.gray('Cleanup cancelled.'));
|
|
367
|
-
return;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
const result = cleanupInvalidProjects();
|
|
371
|
-
|
|
372
|
-
console.log(chalk.green(`\u2713 Removed ${result.removed} invalid project(s).`));
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
export default irisLink;
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { select, input, confirm } from '@inquirer/prompts';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { resolve } from 'node:path';
|
|
5
|
+
import {
|
|
6
|
+
linkVueProject,
|
|
7
|
+
unlinkVueProject,
|
|
8
|
+
getLinkedProjectsWithStatus,
|
|
9
|
+
formatLinkedProjects,
|
|
10
|
+
cleanupInvalidProjects
|
|
11
|
+
} from '../../utils/iris/linker.js';
|
|
12
|
+
import { formatMissingConfigError, formatConfigErrors, readVueConfig } from '../../utils/iris/config-reader.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* iris-app-link command - Link a Vue project to the CLI.
|
|
16
|
+
*
|
|
17
|
+
* Options:
|
|
18
|
+
* --path <dir> Specify Vue project path directly
|
|
19
|
+
* --unlink Remove a linked project
|
|
20
|
+
* --list Show all linked projects
|
|
21
|
+
* --cleanup Remove invalid (non-existent) linked projects
|
|
22
|
+
*/
|
|
23
|
+
export const irisLink = async (options = {}) => {
|
|
24
|
+
process.stdout.write('\x1Bc'); // Clear console
|
|
25
|
+
|
|
26
|
+
const { path: pathOption, unlink, list, cleanup } = options;
|
|
27
|
+
|
|
28
|
+
// Handle --list option
|
|
29
|
+
if (list) {
|
|
30
|
+
const projects = getLinkedProjectsWithStatus();
|
|
31
|
+
console.log(formatLinkedProjects(projects));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Handle --cleanup option
|
|
36
|
+
if (cleanup) {
|
|
37
|
+
await handleCleanup();
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Handle --unlink option
|
|
42
|
+
if (unlink) {
|
|
43
|
+
await handleUnlink(pathOption);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// If path provided directly, link it
|
|
48
|
+
if (pathOption) {
|
|
49
|
+
await linkProjectDirect(pathOption);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Interactive mode - show main menu
|
|
54
|
+
await showMainMenu();
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Show the main menu for managing linked projects.
|
|
59
|
+
*/
|
|
60
|
+
async function showMainMenu() {
|
|
61
|
+
const projects = getLinkedProjectsWithStatus();
|
|
62
|
+
const validCount = projects.filter(p => p.validation.valid).length;
|
|
63
|
+
const invalidCount = projects.filter(p => !p.validation.valid).length;
|
|
64
|
+
|
|
65
|
+
console.log(chalk.blue.bold('Iris Vue Project Manager'));
|
|
66
|
+
console.log(chalk.gray('─'.repeat(48)));
|
|
67
|
+
|
|
68
|
+
if (projects.length > 0) {
|
|
69
|
+
console.log(chalk.white(`Linked projects: ${validCount} valid`));
|
|
70
|
+
if (invalidCount > 0) {
|
|
71
|
+
console.log(chalk.yellow(` ${invalidCount} with issues`));
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
console.log(chalk.gray('No Vue projects linked yet.'));
|
|
75
|
+
}
|
|
76
|
+
console.log();
|
|
77
|
+
|
|
78
|
+
const choices = [
|
|
79
|
+
{
|
|
80
|
+
name: 'Link a Vue project',
|
|
81
|
+
value: 'link',
|
|
82
|
+
description: chalk.dim('→ Add a Vue project to the CLI')
|
|
83
|
+
}
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
if (projects.length > 0) {
|
|
87
|
+
choices.push({
|
|
88
|
+
name: 'View linked projects',
|
|
89
|
+
value: 'list',
|
|
90
|
+
description: chalk.dim('→ Show all linked Vue projects with status')
|
|
91
|
+
});
|
|
92
|
+
choices.push({
|
|
93
|
+
name: 'Unlink a project',
|
|
94
|
+
value: 'unlink',
|
|
95
|
+
description: chalk.dim('→ Remove a Vue project from the CLI')
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (invalidCount > 0) {
|
|
100
|
+
choices.push({
|
|
101
|
+
name: `Clean up invalid projects (${invalidCount})`,
|
|
102
|
+
value: 'cleanup',
|
|
103
|
+
description: chalk.dim('→ Remove projects with missing paths')
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
choices.push({
|
|
108
|
+
name: 'Exit',
|
|
109
|
+
value: 'exit'
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const action = await select({
|
|
113
|
+
message: 'What would you like to do?',
|
|
114
|
+
choices
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
switch (action) {
|
|
118
|
+
case 'link':
|
|
119
|
+
await handleLink();
|
|
120
|
+
break;
|
|
121
|
+
case 'list':
|
|
122
|
+
console.log();
|
|
123
|
+
console.log(formatLinkedProjects(projects));
|
|
124
|
+
break;
|
|
125
|
+
case 'unlink':
|
|
126
|
+
await handleUnlink();
|
|
127
|
+
break;
|
|
128
|
+
case 'cleanup':
|
|
129
|
+
await handleCleanup();
|
|
130
|
+
break;
|
|
131
|
+
case 'exit':
|
|
132
|
+
console.log(chalk.gray('Goodbye!'));
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Handle linking a new Vue project.
|
|
139
|
+
*/
|
|
140
|
+
async function handleLink(pathOption) {
|
|
141
|
+
let projectPath = pathOption;
|
|
142
|
+
|
|
143
|
+
// If no path provided, prompt for one
|
|
144
|
+
if (!projectPath) {
|
|
145
|
+
// Check if we're in a Vue project directory
|
|
146
|
+
const cwdConfig = readVueConfig(process.cwd());
|
|
147
|
+
const cwdPath = process.cwd();
|
|
148
|
+
|
|
149
|
+
const choices = [];
|
|
150
|
+
|
|
151
|
+
// Always show current directory option, but disable if not a valid Vue project
|
|
152
|
+
if (cwdConfig.found && cwdConfig.errors.length === 0) {
|
|
153
|
+
// Valid Vue project
|
|
154
|
+
choices.push({
|
|
155
|
+
name: `Current directory (${cwdPath})`,
|
|
156
|
+
value: cwdPath,
|
|
157
|
+
description: chalk.dim(`→ App: "${cwdConfig.appName}" (${cwdConfig.slug})`)
|
|
158
|
+
});
|
|
159
|
+
} else if (cwdConfig.found && cwdConfig.errors.length > 0) {
|
|
160
|
+
// Has config.ts but with errors
|
|
161
|
+
const errorMsg = cwdConfig.errors[0] || 'Invalid config';
|
|
162
|
+
choices.push({
|
|
163
|
+
name: `Current directory (${cwdPath})`,
|
|
164
|
+
value: '__disabled__',
|
|
165
|
+
disabled: `Config error: ${errorMsg}`
|
|
166
|
+
});
|
|
167
|
+
} else {
|
|
168
|
+
// No config.ts found
|
|
169
|
+
choices.push({
|
|
170
|
+
name: `Current directory (${cwdPath})`,
|
|
171
|
+
value: '__disabled__',
|
|
172
|
+
disabled: 'No config.ts found'
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
choices.push({
|
|
177
|
+
name: 'Enter path manually',
|
|
178
|
+
value: '__manual__',
|
|
179
|
+
description: chalk.dim('→ Specify the full path to a Vue project')
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
choices.push({
|
|
183
|
+
name: 'Cancel',
|
|
184
|
+
value: '__cancel__'
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const choice = await select({
|
|
188
|
+
message: 'Which Vue project do you want to link?',
|
|
189
|
+
choices
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
if (choice === '__cancel__') {
|
|
193
|
+
console.log(chalk.gray('Cancelled.'));
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (choice === '__manual__') {
|
|
198
|
+
projectPath = await input({
|
|
199
|
+
message: 'Enter the path to your Vue project:',
|
|
200
|
+
validate: (value) => {
|
|
201
|
+
if (!value.trim()) {
|
|
202
|
+
return 'Path is required';
|
|
203
|
+
}
|
|
204
|
+
const resolved = resolve(value);
|
|
205
|
+
if (!existsSync(resolved)) {
|
|
206
|
+
return `Path does not exist: ${resolved}`;
|
|
207
|
+
}
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
} else {
|
|
212
|
+
projectPath = choice;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
await linkProjectDirect(projectPath);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Link a project directly by path.
|
|
221
|
+
*/
|
|
222
|
+
async function linkProjectDirect(projectPath) {
|
|
223
|
+
// Resolve the path
|
|
224
|
+
projectPath = resolve(projectPath);
|
|
225
|
+
|
|
226
|
+
// Validate path exists
|
|
227
|
+
if (!existsSync(projectPath)) {
|
|
228
|
+
console.log(chalk.red(`Error: Path does not exist: ${projectPath}`));
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Check Vue config
|
|
233
|
+
const vueConfig = readVueConfig(projectPath);
|
|
234
|
+
if (!vueConfig.found) {
|
|
235
|
+
console.log(chalk.red(formatMissingConfigError(projectPath)));
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (vueConfig.errors.length > 0) {
|
|
240
|
+
console.log(chalk.red(formatConfigErrors(vueConfig)));
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Link the project
|
|
245
|
+
console.log(chalk.blue(`\nLinking Vue project...`));
|
|
246
|
+
console.log(chalk.gray(` Path: ${projectPath}`));
|
|
247
|
+
console.log(chalk.gray(` Slug: ${vueConfig.slug}`));
|
|
248
|
+
console.log(chalk.gray(` Name: ${vueConfig.appName}`));
|
|
249
|
+
if (vueConfig.siteUrl) {
|
|
250
|
+
console.log(chalk.gray(` Site: ${vueConfig.siteUrl}`));
|
|
251
|
+
}
|
|
252
|
+
console.log();
|
|
253
|
+
|
|
254
|
+
const result = linkVueProject(projectPath);
|
|
255
|
+
|
|
256
|
+
if (!result.success) {
|
|
257
|
+
console.log(chalk.red(`Failed to link project: ${result.error}`));
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (result.updated) {
|
|
262
|
+
console.log(chalk.green(`\u2713 Project updated successfully!`));
|
|
263
|
+
console.log(chalk.gray(` The linked project configuration has been updated.`));
|
|
264
|
+
} else {
|
|
265
|
+
console.log(chalk.green(`\u2713 Project linked successfully!`));
|
|
266
|
+
console.log();
|
|
267
|
+
console.log(chalk.cyan('Next steps:'));
|
|
268
|
+
console.log(chalk.white(` 1. Build and stage: ${chalk.yellow('magentrix vue-run-build')}`));
|
|
269
|
+
console.log(chalk.white(` 2. Publish to server: ${chalk.yellow('magentrix publish')}`));
|
|
270
|
+
console.log(chalk.white(` Or use ${chalk.yellow('magentrix autopublish')} for automatic publishing`));
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
console.log();
|
|
274
|
+
console.log(chalk.gray('Note: Linked projects are stored globally and available across all Magentrix workspaces.'));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Handle unlinking a Vue project.
|
|
279
|
+
*/
|
|
280
|
+
async function handleUnlink(pathOption) {
|
|
281
|
+
const linkedProjects = getLinkedProjectsWithStatus();
|
|
282
|
+
|
|
283
|
+
if (linkedProjects.length === 0) {
|
|
284
|
+
console.log(chalk.yellow('No Vue projects are currently linked.'));
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
let projectToUnlink = pathOption;
|
|
289
|
+
|
|
290
|
+
// If no path provided, prompt user to select
|
|
291
|
+
if (!projectToUnlink) {
|
|
292
|
+
const choices = linkedProjects.map(p => {
|
|
293
|
+
const validation = p.validation;
|
|
294
|
+
let prefix = '';
|
|
295
|
+
if (!validation.valid) {
|
|
296
|
+
prefix = validation.exists ? '⚠ ' : '✗ ';
|
|
297
|
+
}
|
|
298
|
+
const displayName = validation.currentAppName || p.appName;
|
|
299
|
+
const displaySlug = validation.currentSlug || p.slug;
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
name: `${prefix}${displayName} (${displaySlug})`,
|
|
303
|
+
value: p.slug,
|
|
304
|
+
description: chalk.dim(`→ Path: ${p.path}`)
|
|
305
|
+
};
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
choices.push({
|
|
309
|
+
name: 'Cancel',
|
|
310
|
+
value: '__cancel__'
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
projectToUnlink = await select({
|
|
314
|
+
message: 'Which project do you want to unlink?',
|
|
315
|
+
choices
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
if (projectToUnlink === '__cancel__') {
|
|
319
|
+
console.log(chalk.gray('Cancelled.'));
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Unlink the project
|
|
325
|
+
const result = unlinkVueProject(projectToUnlink);
|
|
326
|
+
|
|
327
|
+
if (!result.success) {
|
|
328
|
+
console.log(chalk.red(`Failed to unlink: ${result.error}`));
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
console.log(chalk.green(`\u2713 Project '${result.project.appName}' (${result.project.slug}) has been unlinked.`));
|
|
333
|
+
console.log(chalk.gray(` Path: ${result.project.path}`));
|
|
334
|
+
console.log();
|
|
335
|
+
console.log(chalk.gray('Note: This only removes the link from CLI tracking.'));
|
|
336
|
+
console.log(chalk.gray('The Vue project and any deployed Iris app are unchanged.'));
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Handle cleanup of invalid projects.
|
|
341
|
+
*/
|
|
342
|
+
async function handleCleanup() {
|
|
343
|
+
const projectsWithStatus = getLinkedProjectsWithStatus();
|
|
344
|
+
const invalidProjects = projectsWithStatus.filter(p => !p.validation.exists);
|
|
345
|
+
|
|
346
|
+
if (invalidProjects.length === 0) {
|
|
347
|
+
console.log(chalk.green('All linked projects are valid. No cleanup needed.'));
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
console.log(chalk.yellow(`Found ${invalidProjects.length} project(s) with invalid paths:\n`));
|
|
352
|
+
|
|
353
|
+
for (const project of invalidProjects) {
|
|
354
|
+
console.log(chalk.red(` ✗ ${project.appName} (${project.slug})`));
|
|
355
|
+
console.log(chalk.gray(` Path: ${project.path}`));
|
|
356
|
+
console.log(chalk.gray(` Error: ${project.validation.errors.join(', ')}`));
|
|
357
|
+
console.log();
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const shouldCleanup = await confirm({
|
|
361
|
+
message: `Remove these ${invalidProjects.length} invalid project(s)?`,
|
|
362
|
+
default: true
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
if (!shouldCleanup) {
|
|
366
|
+
console.log(chalk.gray('Cleanup cancelled.'));
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const result = cleanupInvalidProjects();
|
|
371
|
+
|
|
372
|
+
console.log(chalk.green(`\u2713 Removed ${result.removed} invalid project(s).`));
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
export default irisLink;
|