@magentrix-corp/magentrix-cli 1.3.4 → 1.3.6
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 +51 -30
- package/actions/iris/buildStage.js +419 -7
- package/actions/iris/dev.js +9 -93
- package/actions/setup.js +5 -1
- package/bin/magentrix.js +56 -8
- package/package.json +1 -1
- package/utils/iris/builder.js +18 -8
- package/utils/workspaces.js +108 -0
package/README.md
CHANGED
|
@@ -491,15 +491,21 @@ magentrix vue-build-stage
|
|
|
491
491
|
```
|
|
492
492
|
**What it does**: Builds a linked Vue project and copies the output to `src/iris-apps/<slug>/` for publishing.
|
|
493
493
|
|
|
494
|
+
**Two modes of operation:**
|
|
495
|
+
|
|
496
|
+
1. **From Magentrix workspace**: Prompts for which Vue project to build, stages to current workspace
|
|
497
|
+
2. **From Vue project directory**: Prompts for which Magentrix workspace to stage into
|
|
498
|
+
|
|
494
499
|
**Options:**
|
|
495
500
|
- `--path <dir>` - Specify Vue project path directly
|
|
496
501
|
- `--skip-build` - Use existing `dist/` folder without rebuilding
|
|
502
|
+
- `--workspace <dir>` - Specify Magentrix workspace path directly (when running from Vue project)
|
|
497
503
|
|
|
498
504
|
**Process:**
|
|
499
505
|
1. Select from linked projects or enter path manually
|
|
500
506
|
2. Run `npm run build` in the Vue project
|
|
501
507
|
3. Validate the build output
|
|
502
|
-
4. Copy to
|
|
508
|
+
4. Copy to workspace `src/iris-apps/<app-slug>/`
|
|
503
509
|
|
|
504
510
|
#### Vue Development Server
|
|
505
511
|
```bash
|
|
@@ -512,13 +518,11 @@ magentrix run-dev
|
|
|
512
518
|
**Options:**
|
|
513
519
|
- `--path <dir>` - Specify Vue project path
|
|
514
520
|
- `--no-inject` - Skip asset injection, just run dev server
|
|
515
|
-
- `--restore` - Restore `.env.development` from backup without running
|
|
516
521
|
|
|
517
522
|
**Process:**
|
|
518
523
|
1. Fetch platform assets from Magentrix using `.env.development` credentials
|
|
519
|
-
2.
|
|
524
|
+
2. Update `VITE_ASSETS` in `.env.development` (changes are kept)
|
|
520
525
|
3. Run `npm run dev`
|
|
521
|
-
4. Restore `.env.development` on exit (Ctrl+C)
|
|
522
526
|
|
|
523
527
|
#### Delete an Iris App
|
|
524
528
|
```bash
|
|
@@ -592,14 +596,15 @@ VITE_ASSETS = '[]' # Injected automatically by run-dev
|
|
|
592
596
|
|
|
593
597
|
**In Vue project directories** (detected by presence of `config.ts`):
|
|
594
598
|
- ✓ `magentrix iris-link` - Link project to CLI
|
|
599
|
+
- ✓ `magentrix vue-build-stage` - Build and stage (prompts for target workspace)
|
|
595
600
|
- ✓ `magentrix run-dev` - Start dev server (uses `.env.development` credentials)
|
|
596
|
-
- ✓ `magentrix
|
|
601
|
+
- ✓ `magentrix update` - Update CLI to latest version
|
|
597
602
|
|
|
598
603
|
**In Magentrix workspace directories** (has `.magentrix/` folder):
|
|
599
604
|
- ✓ All standard commands (`setup`, `pull`, `publish`, etc.)
|
|
600
|
-
- ✓ All Iris commands (`iris-delete`, `iris-recover`, etc.)
|
|
605
|
+
- ✓ All Iris commands (`vue-build-stage`, `iris-delete`, `iris-recover`, etc.)
|
|
601
606
|
|
|
602
|
-
**Note**: Commands like `pull`, `publish`, `autopublish` require a Magentrix workspace
|
|
607
|
+
**Note**: The `setup` command cannot be run inside a Vue project directory - run it from your Magentrix workspace. Commands like `pull`, `publish`, `autopublish` require a Magentrix workspace.
|
|
603
608
|
|
|
604
609
|
### Typical Development Workflow
|
|
605
610
|
|
|
@@ -607,22 +612,28 @@ VITE_ASSETS = '[]' # Injected automatically by run-dev
|
|
|
607
612
|
# First time setup
|
|
608
613
|
magentrix iris-link # Link your Vue project
|
|
609
614
|
|
|
610
|
-
# Development
|
|
615
|
+
# Development (run from Vue project folder)
|
|
616
|
+
cd ~/my-vue-app # Work from your Vue project
|
|
611
617
|
magentrix run-dev # Start dev server with platform assets
|
|
612
618
|
# Make changes, test locally
|
|
613
619
|
# Press Ctrl+C to stop
|
|
614
620
|
|
|
615
|
-
# Deployment
|
|
621
|
+
# Deployment - Option A (from Vue project folder)
|
|
622
|
+
cd ~/my-vue-app # Work from your Vue project
|
|
623
|
+
magentrix vue-build-stage # Build and select workspace to stage into
|
|
624
|
+
# Prompted: "Do you want to publish to Magentrix now?" → Yes/No
|
|
625
|
+
|
|
626
|
+
# Deployment - Option B (from Magentrix workspace)
|
|
616
627
|
cd ~/magentrix-workspace # Navigate to Magentrix workspace
|
|
617
628
|
magentrix vue-build-stage --path ~/my-vue-app # Build and stage
|
|
618
629
|
# Prompted: "Do you want to publish to Magentrix now?" → Yes/No
|
|
619
630
|
# If autopublish is running, it auto-deploys instead
|
|
620
631
|
|
|
621
|
-
# Deleting an app
|
|
632
|
+
# Deleting an app (from workspace)
|
|
622
633
|
magentrix iris-delete # Select app, confirm, auto-backup created
|
|
623
634
|
magentrix publish # Sync deletion to server
|
|
624
635
|
|
|
625
|
-
# Recovering a deleted app
|
|
636
|
+
# Recovering a deleted app (from workspace)
|
|
626
637
|
magentrix iris-recover # Select backup, restore files
|
|
627
638
|
magentrix publish # Sync recovery to server
|
|
628
639
|
```
|
|
@@ -642,29 +653,31 @@ magentrix vue-build-stage
|
|
|
642
653
|
|
|
643
654
|
### Troubleshooting Iris Apps
|
|
644
655
|
|
|
645
|
-
####
|
|
646
|
-
This warning appears when running `magentrix vue-build-stage` outside your Magentrix CLI workspace.
|
|
647
|
-
|
|
648
|
-
**Why it happens:**
|
|
649
|
-
- The command stages build files to `src/iris-apps/<slug>/` in your Magentrix workspace
|
|
650
|
-
- You ran it from your Vue project directory or another location
|
|
656
|
+
#### Running vue-build-stage from different locations
|
|
651
657
|
|
|
652
|
-
|
|
653
|
-
1. Navigate to your Magentrix CLI workspace (the folder with `.magentrix/` and `src/`)
|
|
654
|
-
2. Run the command from there
|
|
655
|
-
3. Use `--path` to specify your Vue project: `magentrix vue-build-stage --path /path/to/vue-project`
|
|
658
|
+
The `vue-build-stage` command works from both locations:
|
|
656
659
|
|
|
657
|
-
**
|
|
660
|
+
**From Vue project directory:**
|
|
658
661
|
```bash
|
|
659
|
-
# Wrong - running from Vue project
|
|
660
662
|
cd ~/my-vue-app
|
|
661
|
-
magentrix vue-build-stage #
|
|
663
|
+
magentrix vue-build-stage # Prompts for which workspace to stage into
|
|
664
|
+
```
|
|
662
665
|
|
|
663
|
-
|
|
666
|
+
**From Magentrix workspace:**
|
|
667
|
+
```bash
|
|
664
668
|
cd ~/magentrix-workspace
|
|
665
|
-
magentrix vue-build-stage
|
|
669
|
+
magentrix vue-build-stage # Prompts for which Vue project to build
|
|
670
|
+
# Or with path: magentrix vue-build-stage --path ~/my-vue-app
|
|
666
671
|
```
|
|
667
672
|
|
|
673
|
+
#### "No Magentrix workspaces found"
|
|
674
|
+
When running from a Vue project, the command looks for registered workspaces in the global config. To register a workspace:
|
|
675
|
+
1. Navigate to your Magentrix workspace
|
|
676
|
+
2. Run `magentrix setup` (this automatically registers it)
|
|
677
|
+
3. Or specify workspace manually: `magentrix vue-build-stage --workspace /path/to/workspace`
|
|
678
|
+
|
|
679
|
+
**Note**: Existing workspaces are auto-registered when you run any command from them.
|
|
680
|
+
|
|
668
681
|
#### "Missing required field in config.ts: slug (appPath)"
|
|
669
682
|
Your Vue project's `config.ts` is missing the app identifier. Add an `appPath` or `slug` field.
|
|
670
683
|
|
|
@@ -766,16 +779,24 @@ magentrix status # Verify everything is in sync
|
|
|
766
779
|
```
|
|
767
780
|
|
|
768
781
|
### Deploying a Vue.js App
|
|
769
|
-
```bash
|
|
770
|
-
# Important: Run from your Magentrix CLI workspace, NOT the Vue project folder
|
|
771
|
-
cd ~/magentrix-workspace # Navigate to Magentrix workspace
|
|
772
782
|
|
|
783
|
+
**Option A: From Vue project folder**
|
|
784
|
+
```bash
|
|
785
|
+
cd ~/my-vue-app # Navigate to your Vue project
|
|
773
786
|
magentrix iris-link # Link project (first time only)
|
|
787
|
+
magentrix vue-build-stage # Build and select workspace to stage into
|
|
788
|
+
# Prompted: "Do you want to publish to Magentrix now?" → Yes/No
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
**Option B: From Magentrix workspace**
|
|
792
|
+
```bash
|
|
793
|
+
cd ~/magentrix-workspace # Navigate to Magentrix workspace
|
|
794
|
+
magentrix iris-link --path ~/my-vue-app # Link project (first time only)
|
|
774
795
|
magentrix vue-build-stage --path ~/my-vue-app # Build and stage
|
|
775
796
|
# Prompted: "Do you want to publish to Magentrix now?" (unless autopublish is running)
|
|
776
797
|
```
|
|
777
798
|
|
|
778
|
-
**Note:**
|
|
799
|
+
**Note:** When running from a Vue project, the command prompts you to select a registered workspace. Workspaces are auto-registered when you run any command from them.
|
|
779
800
|
|
|
780
801
|
### Deleting and Recovering Apps
|
|
781
802
|
```bash
|
|
@@ -2,12 +2,13 @@ import chalk from 'chalk';
|
|
|
2
2
|
import { select, input, confirm } from '@inquirer/prompts';
|
|
3
3
|
import { existsSync } from 'node:fs';
|
|
4
4
|
import { resolve, join } from 'node:path';
|
|
5
|
+
import { spawn } from 'node:child_process';
|
|
5
6
|
import Config from '../../utils/config.js';
|
|
6
7
|
import { runPublish } from '../publish.js';
|
|
7
8
|
import { isAutopublishRunning } from '../../utils/autopublishLock.js';
|
|
8
9
|
import {
|
|
9
10
|
buildVueProject,
|
|
10
|
-
|
|
11
|
+
stageToWorkspace,
|
|
11
12
|
findDistDirectory,
|
|
12
13
|
formatBuildError,
|
|
13
14
|
formatValidationError
|
|
@@ -25,9 +26,24 @@ import {
|
|
|
25
26
|
buildProjectChoices
|
|
26
27
|
} from '../../utils/iris/linker.js';
|
|
27
28
|
import { EXPORT_ROOT, IRIS_APPS_DIR, HASHED_CWD } from '../../vars/global.js';
|
|
29
|
+
import { getValidWorkspaces } from '../../utils/workspaces.js';
|
|
28
30
|
|
|
29
31
|
const config = new Config();
|
|
30
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Check if the current directory is a Vue project (has config.ts).
|
|
35
|
+
* @returns {boolean}
|
|
36
|
+
*/
|
|
37
|
+
function isInVueProject() {
|
|
38
|
+
const configLocations = [
|
|
39
|
+
'src/config.ts',
|
|
40
|
+
'config.ts',
|
|
41
|
+
'src/iris-config.ts',
|
|
42
|
+
'iris-config.ts'
|
|
43
|
+
];
|
|
44
|
+
return configLocations.some(loc => existsSync(join(process.cwd(), loc)));
|
|
45
|
+
}
|
|
46
|
+
|
|
31
47
|
/**
|
|
32
48
|
* Check if the current directory appears to be a Magentrix workspace.
|
|
33
49
|
* @returns {boolean} - True if in a Magentrix workspace
|
|
@@ -52,17 +68,33 @@ function isMagentrixWorkspace() {
|
|
|
52
68
|
/**
|
|
53
69
|
* vue-build-stage command - Build a Vue project and stage to CLI workspace.
|
|
54
70
|
*
|
|
71
|
+
* Two modes of operation:
|
|
72
|
+
* 1. Run from Magentrix workspace: prompts for which Vue project to build
|
|
73
|
+
* 2. Run from Vue project: prompts for which workspace to stage into
|
|
74
|
+
*
|
|
55
75
|
* Options:
|
|
56
|
-
* --path <dir>
|
|
57
|
-
* --skip-build
|
|
76
|
+
* --path <dir> Specify Vue project path directly
|
|
77
|
+
* --skip-build Use existing dist/ without rebuilding
|
|
78
|
+
* --workspace <dir> Specify Magentrix workspace path directly
|
|
58
79
|
*/
|
|
59
80
|
export const vueBuildStage = async (options = {}) => {
|
|
60
81
|
process.stdout.write('\x1Bc'); // Clear console
|
|
61
82
|
|
|
62
|
-
const { path: pathOption, skipBuild } = options;
|
|
83
|
+
const { path: pathOption, skipBuild, workspace: workspaceOption } = options;
|
|
84
|
+
|
|
85
|
+
// Detect which mode we're in
|
|
86
|
+
const inVueProject = isInVueProject();
|
|
87
|
+
const inWorkspace = isMagentrixWorkspace();
|
|
63
88
|
|
|
89
|
+
// If run from a Vue project, use reversed logic
|
|
90
|
+
if (inVueProject && !inWorkspace) {
|
|
91
|
+
await buildFromVueProject(options);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Standard mode: run from workspace, select Vue project
|
|
64
96
|
// Warn if not in a Magentrix workspace
|
|
65
|
-
if (!
|
|
97
|
+
if (!inWorkspace) {
|
|
66
98
|
console.log(chalk.yellow('⚠ Warning: Magentrix Workspace Not Detected'));
|
|
67
99
|
console.log(chalk.gray('─'.repeat(48)));
|
|
68
100
|
console.log(chalk.white('\nThis command should be run from your Magentrix CLI workspace directory.'));
|
|
@@ -200,11 +232,11 @@ export const vueBuildStage = async (options = {}) => {
|
|
|
200
232
|
console.log(chalk.green('\u2713 Build output validated'));
|
|
201
233
|
}
|
|
202
234
|
|
|
203
|
-
// Stage to CLI project
|
|
235
|
+
// Stage to CLI project (current workspace)
|
|
204
236
|
console.log();
|
|
205
237
|
console.log(chalk.blue('Staging to CLI workspace...'));
|
|
206
238
|
|
|
207
|
-
const stageResult =
|
|
239
|
+
const stageResult = stageToWorkspace(distPath, slug, process.cwd());
|
|
208
240
|
|
|
209
241
|
if (!stageResult.success) {
|
|
210
242
|
console.log(chalk.red(`Failed to stage: ${stageResult.error}`));
|
|
@@ -251,6 +283,386 @@ export const vueBuildStage = async (options = {}) => {
|
|
|
251
283
|
}
|
|
252
284
|
};
|
|
253
285
|
|
|
286
|
+
/**
|
|
287
|
+
* Build and stage when running from inside a Vue project.
|
|
288
|
+
* Prompts user to select which workspace to stage into.
|
|
289
|
+
*/
|
|
290
|
+
async function buildFromVueProject(options) {
|
|
291
|
+
const { skipBuild, workspace: workspaceOption } = options;
|
|
292
|
+
|
|
293
|
+
// Use current directory as Vue project
|
|
294
|
+
const projectPath = process.cwd();
|
|
295
|
+
const vueConfig = readVueConfig(projectPath);
|
|
296
|
+
|
|
297
|
+
// Validate Vue config
|
|
298
|
+
if (!vueConfig.found) {
|
|
299
|
+
console.log(chalk.red(formatMissingConfigError(projectPath)));
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (vueConfig.errors.length > 0) {
|
|
304
|
+
console.log(chalk.red(formatConfigErrors(vueConfig)));
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const { slug, appName } = vueConfig;
|
|
309
|
+
|
|
310
|
+
console.log(chalk.blue('\nVue Build & Stage'));
|
|
311
|
+
console.log(chalk.gray('─'.repeat(48)));
|
|
312
|
+
console.log(chalk.white(` Project: ${chalk.cyan(appName)} (${slug})`));
|
|
313
|
+
console.log(chalk.white(` Path: ${chalk.gray(projectPath)}`));
|
|
314
|
+
console.log();
|
|
315
|
+
|
|
316
|
+
// Determine which workspace to stage into
|
|
317
|
+
let workspacePath = workspaceOption;
|
|
318
|
+
|
|
319
|
+
if (workspacePath) {
|
|
320
|
+
workspacePath = resolve(workspacePath);
|
|
321
|
+
if (!existsSync(workspacePath)) {
|
|
322
|
+
console.log(chalk.red(`Error: Workspace path does not exist: ${workspacePath}`));
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
} else {
|
|
326
|
+
// Prompt user to select a workspace
|
|
327
|
+
const result = await selectWorkspace();
|
|
328
|
+
if (!result) return; // User cancelled
|
|
329
|
+
workspacePath = result;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Ensure project is linked
|
|
333
|
+
const linked = findLinkedProjectByPath(projectPath);
|
|
334
|
+
if (!linked) {
|
|
335
|
+
const shouldLink = await confirm({
|
|
336
|
+
message: 'This project is not linked. Link it now?',
|
|
337
|
+
default: true
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
if (shouldLink) {
|
|
341
|
+
const linkResult = linkVueProject(projectPath);
|
|
342
|
+
if (!linkResult.success) {
|
|
343
|
+
console.log(chalk.red(`Failed to link project: ${linkResult.error}`));
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
console.log(chalk.green(`\u2713 Project linked`));
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
let distPath;
|
|
351
|
+
|
|
352
|
+
if (skipBuild) {
|
|
353
|
+
// Use existing dist
|
|
354
|
+
distPath = findDistDirectory(projectPath);
|
|
355
|
+
|
|
356
|
+
if (!distPath) {
|
|
357
|
+
console.log(chalk.red('No existing dist/ directory found.'));
|
|
358
|
+
console.log(chalk.gray('Run without --skip-build to build the project.'));
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
console.log(chalk.yellow(`Using existing dist: ${distPath}`));
|
|
363
|
+
|
|
364
|
+
// Validate the existing build
|
|
365
|
+
const validation = validateIrisBuild(distPath);
|
|
366
|
+
if (!validation.valid) {
|
|
367
|
+
console.log(chalk.red(formatValidationError(distPath, validation.errors)));
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
console.log(chalk.green('\u2713 Existing build is valid'));
|
|
372
|
+
} else {
|
|
373
|
+
// Build the project
|
|
374
|
+
console.log(chalk.blue('Building project...'));
|
|
375
|
+
console.log();
|
|
376
|
+
|
|
377
|
+
const buildResult = await buildVueProject(projectPath, { silent: false });
|
|
378
|
+
|
|
379
|
+
if (!buildResult.success) {
|
|
380
|
+
console.log();
|
|
381
|
+
console.log(chalk.red(formatBuildError(projectPath, buildResult.error)));
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
distPath = buildResult.distPath;
|
|
386
|
+
console.log();
|
|
387
|
+
console.log(chalk.green(`\u2713 Build completed successfully`));
|
|
388
|
+
console.log(chalk.gray(` Output: ${distPath}`));
|
|
389
|
+
|
|
390
|
+
// Validate build output
|
|
391
|
+
const validation = validateIrisBuild(distPath);
|
|
392
|
+
if (!validation.valid) {
|
|
393
|
+
console.log();
|
|
394
|
+
console.log(chalk.red(formatValidationError(distPath, validation.errors)));
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
console.log(chalk.green('\u2713 Build output validated'));
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Stage to selected workspace
|
|
402
|
+
console.log();
|
|
403
|
+
console.log(chalk.blue(`Staging to workspace: ${workspacePath}`));
|
|
404
|
+
|
|
405
|
+
const stageResult = stageToWorkspace(distPath, slug, workspacePath);
|
|
406
|
+
|
|
407
|
+
if (!stageResult.success) {
|
|
408
|
+
console.log(chalk.red(`Failed to stage: ${stageResult.error}`));
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
console.log(chalk.green(`\u2713 Staged ${stageResult.fileCount} files to ${stageResult.stagedPath}`));
|
|
413
|
+
|
|
414
|
+
// Summary
|
|
415
|
+
console.log();
|
|
416
|
+
console.log(chalk.green('─'.repeat(48)));
|
|
417
|
+
console.log(chalk.green.bold('\u2713 Build & Stage Complete!'));
|
|
418
|
+
console.log();
|
|
419
|
+
console.log(chalk.gray(`Staged to: ${stageResult.stagedPath}`));
|
|
420
|
+
console.log();
|
|
421
|
+
|
|
422
|
+
// Check if workspace might be out of sync and offer to pull first
|
|
423
|
+
console.log(chalk.gray('Checking workspace sync status...'));
|
|
424
|
+
const syncStatus = await checkWorkspaceSyncStatus(workspacePath);
|
|
425
|
+
|
|
426
|
+
if (syncStatus.needsPull) {
|
|
427
|
+
console.log();
|
|
428
|
+
console.log(chalk.yellow('⚠ Your workspace may be out of sync with the server.'));
|
|
429
|
+
|
|
430
|
+
const shouldPull = await confirm({
|
|
431
|
+
message: 'Would you like to pull latest changes first?',
|
|
432
|
+
default: true
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
if (shouldPull) {
|
|
436
|
+
console.log();
|
|
437
|
+
console.log(chalk.blue('Running pull from workspace...'));
|
|
438
|
+
console.log();
|
|
439
|
+
|
|
440
|
+
const pullSuccess = await runCommandFromWorkspace(workspacePath, 'pull');
|
|
441
|
+
|
|
442
|
+
if (!pullSuccess) {
|
|
443
|
+
console.log();
|
|
444
|
+
console.log(chalk.yellow('Pull encountered issues. You may want to resolve them manually.'));
|
|
445
|
+
|
|
446
|
+
const continueAnyway = await confirm({
|
|
447
|
+
message: 'Do you still want to continue with publishing?',
|
|
448
|
+
default: false
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
if (!continueAnyway) {
|
|
452
|
+
console.log();
|
|
453
|
+
console.log(chalk.cyan('To continue manually:'));
|
|
454
|
+
console.log(chalk.white(` 1. Navigate to workspace: ${chalk.yellow(`cd "${workspacePath}"`)}`));
|
|
455
|
+
console.log(chalk.white(` 2. Run ${chalk.yellow('magentrix pull')} to resolve conflicts`));
|
|
456
|
+
console.log(chalk.white(` 3. Run ${chalk.yellow('magentrix publish')} to deploy`));
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
console.log();
|
|
461
|
+
}
|
|
462
|
+
} else if (syncStatus.checked) {
|
|
463
|
+
console.log(chalk.green('✓ Workspace is in sync'));
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Ask if they want to publish now
|
|
467
|
+
const shouldPublish = await confirm({
|
|
468
|
+
message: 'Do you want to publish to Magentrix now?',
|
|
469
|
+
default: true
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
if (shouldPublish) {
|
|
473
|
+
console.log();
|
|
474
|
+
console.log(chalk.blue('Running publish from workspace...'));
|
|
475
|
+
console.log();
|
|
476
|
+
|
|
477
|
+
const publishSuccess = await runCommandFromWorkspace(workspacePath, 'publish');
|
|
478
|
+
|
|
479
|
+
if (!publishSuccess) {
|
|
480
|
+
console.log();
|
|
481
|
+
console.log(chalk.cyan('To publish manually:'));
|
|
482
|
+
console.log(chalk.white(` 1. Navigate to workspace: ${chalk.yellow(`cd "${workspacePath}"`)}`));
|
|
483
|
+
console.log(chalk.white(` 2. Run ${chalk.yellow('magentrix publish')}`));
|
|
484
|
+
}
|
|
485
|
+
} else {
|
|
486
|
+
console.log();
|
|
487
|
+
console.log(chalk.cyan('Next steps:'));
|
|
488
|
+
console.log(chalk.white(` 1. Navigate to workspace: ${chalk.yellow(`cd "${workspacePath}"`)}`));
|
|
489
|
+
console.log(chalk.white(` 2. Run ${chalk.yellow('magentrix publish')} to deploy to Magentrix`));
|
|
490
|
+
console.log(chalk.white(` Or use ${chalk.yellow('magentrix autopublish')} for automatic deployment`));
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Check if a workspace needs to pull (has remote changes or conflicts).
|
|
496
|
+
*
|
|
497
|
+
* @param {string} workspacePath - Path to the Magentrix workspace
|
|
498
|
+
* @returns {Promise<{checked: boolean, needsPull: boolean}>}
|
|
499
|
+
*/
|
|
500
|
+
async function checkWorkspaceSyncStatus(workspacePath) {
|
|
501
|
+
return new Promise((resolvePromise) => {
|
|
502
|
+
const isWindows = process.platform === 'win32';
|
|
503
|
+
const npmCmd = isWindows ? 'npx.cmd' : 'npx';
|
|
504
|
+
|
|
505
|
+
let output = '';
|
|
506
|
+
|
|
507
|
+
const child = spawn(npmCmd, ['magentrix', 'status'], {
|
|
508
|
+
cwd: workspacePath,
|
|
509
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
510
|
+
shell: isWindows
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
child.stdout.on('data', (data) => {
|
|
514
|
+
output += data.toString();
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
child.stderr.on('data', (data) => {
|
|
518
|
+
output += data.toString();
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
child.on('close', (code) => {
|
|
522
|
+
// Check output for signs of remote changes or conflicts
|
|
523
|
+
const lowerOutput = output.toLowerCase();
|
|
524
|
+
const needsPull = lowerOutput.includes('conflict') ||
|
|
525
|
+
lowerOutput.includes('remote') ||
|
|
526
|
+
lowerOutput.includes('server has changes') ||
|
|
527
|
+
lowerOutput.includes('out of sync') ||
|
|
528
|
+
lowerOutput.includes('modified on server');
|
|
529
|
+
|
|
530
|
+
resolvePromise({ checked: code === 0, needsPull });
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
child.on('error', () => {
|
|
534
|
+
// If we can't check, assume it's fine and let them proceed
|
|
535
|
+
resolvePromise({ checked: false, needsPull: false });
|
|
536
|
+
});
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Run a magentrix command from a specific workspace directory.
|
|
542
|
+
*
|
|
543
|
+
* @param {string} workspacePath - Path to the Magentrix workspace
|
|
544
|
+
* @param {string} command - The magentrix command to run (e.g., 'pull', 'publish')
|
|
545
|
+
* @returns {Promise<boolean>} - True if command succeeded
|
|
546
|
+
*/
|
|
547
|
+
async function runCommandFromWorkspace(workspacePath, command) {
|
|
548
|
+
return new Promise((resolvePromise) => {
|
|
549
|
+
const isWindows = process.platform === 'win32';
|
|
550
|
+
const npmCmd = isWindows ? 'npx.cmd' : 'npx';
|
|
551
|
+
|
|
552
|
+
const child = spawn(npmCmd, ['magentrix', command], {
|
|
553
|
+
cwd: workspacePath,
|
|
554
|
+
stdio: 'inherit',
|
|
555
|
+
shell: isWindows
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
child.on('close', (code) => {
|
|
559
|
+
resolvePromise(code === 0);
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
child.on('error', (err) => {
|
|
563
|
+
console.log(chalk.yellow(`Warning: Could not run ${command}: ${err.message}`));
|
|
564
|
+
resolvePromise(false);
|
|
565
|
+
});
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Prompt user to select a Magentrix workspace.
|
|
571
|
+
*
|
|
572
|
+
* @returns {Promise<string | null>} - Selected workspace path or null if cancelled
|
|
573
|
+
*/
|
|
574
|
+
async function selectWorkspace() {
|
|
575
|
+
const workspaces = getValidWorkspaces();
|
|
576
|
+
|
|
577
|
+
if (workspaces.length === 0) {
|
|
578
|
+
console.log(chalk.yellow('No Magentrix workspaces found.'));
|
|
579
|
+
console.log();
|
|
580
|
+
console.log(chalk.gray('To register a workspace:'));
|
|
581
|
+
console.log(chalk.white(` • Run ${chalk.cyan('magentrix')} from an existing workspace (auto-registers it)`));
|
|
582
|
+
console.log(chalk.white(` • Or run ${chalk.cyan('magentrix setup')} in a new directory to create one`));
|
|
583
|
+
console.log();
|
|
584
|
+
console.log(chalk.gray('Or specify a workspace path directly:'));
|
|
585
|
+
console.log(chalk.white(` ${chalk.cyan('magentrix vue-build-stage --workspace /path/to/workspace')}`));
|
|
586
|
+
console.log();
|
|
587
|
+
|
|
588
|
+
// Allow manual entry
|
|
589
|
+
const manualPath = await input({
|
|
590
|
+
message: 'Enter the path to your Magentrix workspace (or leave empty to cancel):',
|
|
591
|
+
validate: (value) => {
|
|
592
|
+
if (!value.trim()) return true; // Allow empty for cancel
|
|
593
|
+
const resolved = resolve(value);
|
|
594
|
+
if (!existsSync(resolved)) {
|
|
595
|
+
return `Path does not exist: ${resolved}`;
|
|
596
|
+
}
|
|
597
|
+
const magentrixFolder = join(resolved, '.magentrix');
|
|
598
|
+
if (!existsSync(magentrixFolder)) {
|
|
599
|
+
return `Not a Magentrix workspace (missing .magentrix folder): ${resolved}`;
|
|
600
|
+
}
|
|
601
|
+
return true;
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
if (!manualPath.trim()) {
|
|
606
|
+
console.log(chalk.gray('Cancelled.'));
|
|
607
|
+
return null;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
return resolve(manualPath);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Build choices from registered workspaces
|
|
614
|
+
const choices = workspaces.map(w => ({
|
|
615
|
+
name: `${w.path}`,
|
|
616
|
+
value: w.path,
|
|
617
|
+
description: chalk.dim(`→ ${w.instanceUrl}`)
|
|
618
|
+
}));
|
|
619
|
+
|
|
620
|
+
choices.push({
|
|
621
|
+
name: 'Enter path manually',
|
|
622
|
+
value: '__manual__',
|
|
623
|
+
description: chalk.dim('→ Specify the full path to a workspace')
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
choices.push({
|
|
627
|
+
name: 'Cancel',
|
|
628
|
+
value: '__cancel__'
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
const choice = await select({
|
|
632
|
+
message: 'Which workspace do you want to stage into?',
|
|
633
|
+
choices
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
if (choice === '__cancel__') {
|
|
637
|
+
console.log(chalk.gray('Cancelled.'));
|
|
638
|
+
return null;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
if (choice === '__manual__') {
|
|
642
|
+
const manualPath = await input({
|
|
643
|
+
message: 'Enter the path to your Magentrix workspace:',
|
|
644
|
+
validate: (value) => {
|
|
645
|
+
if (!value.trim()) {
|
|
646
|
+
return 'Path is required';
|
|
647
|
+
}
|
|
648
|
+
const resolved = resolve(value);
|
|
649
|
+
if (!existsSync(resolved)) {
|
|
650
|
+
return `Path does not exist: ${resolved}`;
|
|
651
|
+
}
|
|
652
|
+
const magentrixFolder = join(resolved, '.magentrix');
|
|
653
|
+
if (!existsSync(magentrixFolder)) {
|
|
654
|
+
return `Not a Magentrix workspace (missing .magentrix folder): ${resolved}`;
|
|
655
|
+
}
|
|
656
|
+
return true;
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
return resolve(manualPath);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
return choice;
|
|
664
|
+
}
|
|
665
|
+
|
|
254
666
|
/**
|
|
255
667
|
* Prompt user to select a project.
|
|
256
668
|
*
|
package/actions/iris/dev.js
CHANGED
|
@@ -7,8 +7,6 @@ import {
|
|
|
7
7
|
readVueConfig,
|
|
8
8
|
formatMissingConfigError,
|
|
9
9
|
formatConfigErrors,
|
|
10
|
-
backupFile,
|
|
11
|
-
restoreFile,
|
|
12
10
|
injectAssets,
|
|
13
11
|
getInjectionTarget
|
|
14
12
|
} from '../../utils/iris/config-reader.js';
|
|
@@ -23,24 +21,16 @@ import {
|
|
|
23
21
|
* run-dev command - Start Vue dev server with platform assets injected.
|
|
24
22
|
*
|
|
25
23
|
* Uses credentials from .env.development (VITE_REFRESH_TOKEN, VITE_SITE_URL).
|
|
26
|
-
* Assets are
|
|
27
|
-
* The .env.development file is backed up and restored when the dev server exits.
|
|
24
|
+
* Assets are updated in .env.development (VITE_ASSETS) and kept between runs.
|
|
28
25
|
*
|
|
29
26
|
* Options:
|
|
30
27
|
* --path <dir> Specify Vue project path
|
|
31
28
|
* --no-inject Skip asset injection, just run dev server
|
|
32
|
-
* --restore Restore .env.development from backup
|
|
33
29
|
*/
|
|
34
30
|
export const irisDev = async (options = {}) => {
|
|
35
31
|
process.stdout.write('\x1Bc'); // Clear console
|
|
36
32
|
|
|
37
|
-
const { path: pathOption, inject = true
|
|
38
|
-
|
|
39
|
-
// Handle --restore option
|
|
40
|
-
if (restore) {
|
|
41
|
-
await handleRestore(pathOption);
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
33
|
+
const { path: pathOption, inject = true } = options;
|
|
44
34
|
|
|
45
35
|
// Determine which project to use
|
|
46
36
|
let projectPath = pathOption;
|
|
@@ -94,11 +84,6 @@ export const irisDev = async (options = {}) => {
|
|
|
94
84
|
}
|
|
95
85
|
console.log();
|
|
96
86
|
|
|
97
|
-
let backupPath = null;
|
|
98
|
-
let assetsInjected = false;
|
|
99
|
-
let modifiedFilePath = null;
|
|
100
|
-
let modifiedFileName = null;
|
|
101
|
-
|
|
102
87
|
// Inject assets if enabled
|
|
103
88
|
if (inject && siteUrl) {
|
|
104
89
|
// Check if we have the refresh token for authentication
|
|
@@ -117,27 +102,18 @@ export const irisDev = async (options = {}) => {
|
|
|
117
102
|
console.log(chalk.green(`\u2713 Found ${assetsResult.assets.length} platform assets`));
|
|
118
103
|
|
|
119
104
|
// Determine which file will be modified
|
|
120
|
-
const { targetFile
|
|
105
|
+
const { targetFile } = getInjectionTarget(projectPath);
|
|
121
106
|
|
|
122
107
|
if (!targetFile) {
|
|
123
108
|
console.log(chalk.yellow('Warning: No .env.development file found. Cannot inject assets.'));
|
|
124
109
|
console.log(chalk.gray('Create a .env.development file to enable asset injection.'));
|
|
125
110
|
} else {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
// Backup before modifying
|
|
130
|
-
console.log(chalk.blue(`Backing up ${modifiedFileName}...`));
|
|
131
|
-
backupPath = backupFile(modifiedFilePath);
|
|
132
|
-
console.log(chalk.green(`\u2713 Backup created`));
|
|
133
|
-
|
|
134
|
-
// Inject assets
|
|
135
|
-
console.log(chalk.blue('Injecting assets...'));
|
|
111
|
+
// Inject assets (no backup needed - we keep the changes)
|
|
112
|
+
console.log(chalk.blue('Updating assets in .env.development...'));
|
|
136
113
|
const injectResult = injectAssets(projectPath, assetsResult.assets);
|
|
137
114
|
|
|
138
115
|
if (injectResult.success) {
|
|
139
|
-
|
|
140
|
-
console.log(chalk.green(`\u2713 Assets injected into ${injectResult.targetName}`));
|
|
116
|
+
console.log(chalk.green(`\u2713 Assets updated in ${injectResult.targetName}`));
|
|
141
117
|
} else {
|
|
142
118
|
console.log(chalk.yellow('Warning: Could not inject assets. Continuing without injection.'));
|
|
143
119
|
}
|
|
@@ -166,13 +142,13 @@ export const irisDev = async (options = {}) => {
|
|
|
166
142
|
console.log(chalk.gray('Press Ctrl+C to stop'));
|
|
167
143
|
console.log();
|
|
168
144
|
|
|
169
|
-
await runDevServer(projectPath
|
|
145
|
+
await runDevServer(projectPath);
|
|
170
146
|
};
|
|
171
147
|
|
|
172
148
|
/**
|
|
173
149
|
* Run the Vue development server.
|
|
174
150
|
*/
|
|
175
|
-
async function runDevServer(projectPath
|
|
151
|
+
async function runDevServer(projectPath) {
|
|
176
152
|
return new Promise((resolvePromise) => {
|
|
177
153
|
const isWindows = process.platform === 'win32';
|
|
178
154
|
const npmCmd = isWindows ? 'npm.cmd' : 'npm';
|
|
@@ -183,34 +159,17 @@ async function runDevServer(projectPath, modifiedFilePath, modifiedFileName, bac
|
|
|
183
159
|
shell: isWindows // Windows requires shell: true for .cmd files
|
|
184
160
|
});
|
|
185
161
|
|
|
186
|
-
// Handle cleanup on exit
|
|
187
|
-
const cleanup = () => {
|
|
188
|
-
if (assetsInjected && backupPath && modifiedFilePath) {
|
|
189
|
-
console.log();
|
|
190
|
-
console.log(chalk.blue(`Restoring ${modifiedFileName} from backup...`));
|
|
191
|
-
const restored = restoreFile(modifiedFilePath);
|
|
192
|
-
if (restored) {
|
|
193
|
-
console.log(chalk.green(`\u2713 ${modifiedFileName} restored`));
|
|
194
|
-
} else {
|
|
195
|
-
console.log(chalk.yellow(`Warning: Could not restore ${modifiedFileName}`));
|
|
196
|
-
console.log(chalk.gray(`Backup is at: ${backupPath}`));
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
};
|
|
200
|
-
|
|
201
162
|
// Handle process signals
|
|
202
163
|
process.on('SIGINT', () => {
|
|
203
|
-
cleanup();
|
|
204
164
|
child.kill('SIGINT');
|
|
205
165
|
});
|
|
206
166
|
|
|
207
167
|
process.on('SIGTERM', () => {
|
|
208
|
-
cleanup();
|
|
209
168
|
child.kill('SIGTERM');
|
|
210
169
|
});
|
|
211
170
|
|
|
212
171
|
child.on('close', (code) => {
|
|
213
|
-
// If dev server exited with error, show helpful context
|
|
172
|
+
// If dev server exited with error, show helpful context
|
|
214
173
|
if (code !== 0 && code !== null) {
|
|
215
174
|
console.log();
|
|
216
175
|
console.log(chalk.bgYellow.black(' External Process Error '));
|
|
@@ -229,59 +188,16 @@ async function runDevServer(projectPath, modifiedFilePath, modifiedFileName, bac
|
|
|
229
188
|
console.log(chalk.yellow('─'.repeat(48)));
|
|
230
189
|
}
|
|
231
190
|
|
|
232
|
-
// Cleanup after showing error context
|
|
233
|
-
cleanup();
|
|
234
|
-
|
|
235
191
|
resolvePromise(code);
|
|
236
192
|
});
|
|
237
193
|
|
|
238
194
|
child.on('error', (err) => {
|
|
239
195
|
console.log(chalk.red(`Failed to start dev server: ${err.message}`));
|
|
240
|
-
cleanup();
|
|
241
196
|
resolvePromise(1);
|
|
242
197
|
});
|
|
243
198
|
});
|
|
244
199
|
}
|
|
245
200
|
|
|
246
|
-
/**
|
|
247
|
-
* Handle --restore option.
|
|
248
|
-
*/
|
|
249
|
-
async function handleRestore(pathOption) {
|
|
250
|
-
let projectPath = pathOption;
|
|
251
|
-
|
|
252
|
-
if (!projectPath) {
|
|
253
|
-
// Try current directory
|
|
254
|
-
const cwdConfig = readVueConfig(process.cwd());
|
|
255
|
-
if (cwdConfig.found) {
|
|
256
|
-
projectPath = process.cwd();
|
|
257
|
-
} else {
|
|
258
|
-
console.log(chalk.red('No Vue project found in current directory.'));
|
|
259
|
-
console.log(chalk.gray('Use --path to specify the project path.'));
|
|
260
|
-
return;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
projectPath = resolve(projectPath);
|
|
265
|
-
|
|
266
|
-
// Determine which file would be the injection target
|
|
267
|
-
const { targetFile, targetName } = getInjectionTarget(projectPath);
|
|
268
|
-
|
|
269
|
-
if (!targetFile) {
|
|
270
|
-
console.log(chalk.yellow('No config files found to restore.'));
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
console.log(chalk.blue(`Restoring ${targetName} from backup...`));
|
|
275
|
-
const restored = restoreFile(targetFile);
|
|
276
|
-
|
|
277
|
-
if (restored) {
|
|
278
|
-
console.log(chalk.green(`\u2713 ${targetName} restored from backup`));
|
|
279
|
-
} else {
|
|
280
|
-
console.log(chalk.yellow('No backup file found.'));
|
|
281
|
-
console.log(chalk.gray(`Expected backup at: ${targetFile}.bak`));
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
201
|
/**
|
|
286
202
|
* Prompt user to select a project.
|
|
287
203
|
*/
|
package/actions/setup.js
CHANGED
|
@@ -3,8 +3,9 @@ import { ensureInstanceUrl } from "../utils/cli/helpers/ensureInstanceUrl.js";
|
|
|
3
3
|
import Config from "../utils/config.js";
|
|
4
4
|
import { getAccessToken, tryAuthenticate } from "../utils/magentrix/api/auth.js";
|
|
5
5
|
import { ensureVSCodeFileAssociation } from "../utils/preferences.js";
|
|
6
|
-
import { EXPORT_ROOT, HASHED_CWD } from "../vars/global.js";
|
|
6
|
+
import { CWD, EXPORT_ROOT, HASHED_CWD } from "../vars/global.js";
|
|
7
7
|
import { select } from "@inquirer/prompts";
|
|
8
|
+
import { registerWorkspace } from "../utils/workspaces.js";
|
|
8
9
|
|
|
9
10
|
const config = new Config();
|
|
10
11
|
|
|
@@ -116,6 +117,9 @@ export const setup = async (cliOptions = {}) => {
|
|
|
116
117
|
{ global: true, pathHash: HASHED_CWD }
|
|
117
118
|
);
|
|
118
119
|
|
|
120
|
+
// Register this workspace in the global registry
|
|
121
|
+
registerWorkspace(CWD, instanceUrl);
|
|
122
|
+
|
|
119
123
|
// Set up the editor
|
|
120
124
|
await ensureVSCodeFileAssociation('./');
|
|
121
125
|
|
package/bin/magentrix.js
CHANGED
|
@@ -13,11 +13,15 @@ import { create } from '../actions/create.js';
|
|
|
13
13
|
import { autoPublish } from '../actions/autopublish.js';
|
|
14
14
|
import { status } from '../actions/status.js';
|
|
15
15
|
import { cacheDir, recacheFileIdIndex } from '../utils/cacher.js';
|
|
16
|
-
import { EXPORT_ROOT } from '../vars/global.js';
|
|
16
|
+
import { CWD, EXPORT_ROOT, HASHED_CWD } from '../vars/global.js';
|
|
17
17
|
import { publish } from '../actions/publish.js';
|
|
18
18
|
import { update } from '../actions/update.js';
|
|
19
19
|
import { configWizard } from '../actions/config.js';
|
|
20
20
|
import { irisLink, irisDev, irisDelete, irisRecover, vueBuildStage } from '../actions/iris/index.js';
|
|
21
|
+
import Config from '../utils/config.js';
|
|
22
|
+
import { registerWorkspace, getRegisteredWorkspaces } from '../utils/workspaces.js';
|
|
23
|
+
|
|
24
|
+
const config = new Config();
|
|
21
25
|
|
|
22
26
|
// ── Vue Project Detection ────────────────────────────────
|
|
23
27
|
/**
|
|
@@ -44,8 +48,25 @@ function requireMagentrixWorkspace(fn) {
|
|
|
44
48
|
console.error(chalk.gray('This command requires a Magentrix workspace with global API key and instance URL.\n'));
|
|
45
49
|
console.error(chalk.cyan('Available commands in Vue projects:'));
|
|
46
50
|
console.error(chalk.gray(' • magentrix iris-link'));
|
|
51
|
+
console.error(chalk.gray(' • magentrix vue-build-stage'));
|
|
47
52
|
console.error(chalk.gray(' • magentrix run-dev'));
|
|
48
|
-
console.error(chalk.gray(' • magentrix
|
|
53
|
+
console.error(chalk.gray(' • magentrix update\n'));
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
// Execute the command
|
|
57
|
+
await fn(...args);
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Block commands that should not run in Vue projects (but don't require a workspace)
|
|
63
|
+
*/
|
|
64
|
+
function blockInVueProject(fn) {
|
|
65
|
+
return async (...args) => {
|
|
66
|
+
if (isInVueProject()) {
|
|
67
|
+
console.error(`\n${chalk.bgRed.white.bold(' ERROR ')} ${chalk.redBright('This command cannot be run in a Vue project')}\n`);
|
|
68
|
+
console.error(chalk.yellow('It looks like you\'re in a Vue project directory.'));
|
|
69
|
+
console.error(chalk.gray('Please run this command from a Magentrix workspace or an empty directory.\n'));
|
|
49
70
|
process.exit(1);
|
|
50
71
|
}
|
|
51
72
|
// Execute the command
|
|
@@ -54,7 +75,33 @@ function requireMagentrixWorkspace(fn) {
|
|
|
54
75
|
}
|
|
55
76
|
|
|
56
77
|
// ── Middleware ────────────────────────────────
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Auto-register existing workspaces for backwards compatibility.
|
|
81
|
+
* If the current directory has credentials configured but isn't in the
|
|
82
|
+
* global workspace registry, register it automatically.
|
|
83
|
+
*/
|
|
84
|
+
function ensureWorkspaceRegistered() {
|
|
85
|
+
// Check if current directory has credentials configured
|
|
86
|
+
const instanceUrl = config.read('instanceUrl', { global: true, pathHash: HASHED_CWD });
|
|
87
|
+
const apiKey = config.read('apiKey', { global: true, pathHash: HASHED_CWD });
|
|
88
|
+
|
|
89
|
+
if (!instanceUrl || !apiKey) {
|
|
90
|
+
return; // Not a configured workspace
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Check if already registered
|
|
94
|
+
const workspaces = getRegisteredWorkspaces();
|
|
95
|
+
const alreadyRegistered = workspaces.some(w => w.path === CWD);
|
|
96
|
+
|
|
97
|
+
if (!alreadyRegistered) {
|
|
98
|
+
// Auto-register for backwards compatibility
|
|
99
|
+
registerWorkspace(CWD, instanceUrl);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
57
103
|
async function preMiddleware() {
|
|
104
|
+
ensureWorkspaceRegistered();
|
|
58
105
|
await recacheFileIdIndex(EXPORT_ROOT);
|
|
59
106
|
await cacheDir(EXPORT_ROOT);
|
|
60
107
|
}
|
|
@@ -80,7 +127,7 @@ program
|
|
|
80
127
|
.description('Manage Magentrix assets and automation')
|
|
81
128
|
.version(VERSION)
|
|
82
129
|
.configureHelp({
|
|
83
|
-
formatHelp: (
|
|
130
|
+
formatHelp: (_cmd, _helper) => {
|
|
84
131
|
const divider = chalk.gray('━'.repeat(60));
|
|
85
132
|
const titleBar = chalk.bold.bgBlue.white(' Magentrix CLI ');
|
|
86
133
|
const version = chalk.dim(`v${VERSION}`);
|
|
@@ -137,7 +184,7 @@ program
|
|
|
137
184
|
// ── Error Handlers ───────────────────────────
|
|
138
185
|
program.showHelpAfterError(false);
|
|
139
186
|
program.configureOutput({
|
|
140
|
-
outputError: (str,
|
|
187
|
+
outputError: (str, _write) => {
|
|
141
188
|
// Custom error message for unknown options
|
|
142
189
|
if (str.includes('unknown option')) {
|
|
143
190
|
const match = str.match(/'([^']+)'/);
|
|
@@ -167,7 +214,7 @@ program
|
|
|
167
214
|
.description('Configure your Magentrix API key')
|
|
168
215
|
.option('--api-key <apiKey>', 'Magentrix API key')
|
|
169
216
|
.option('--instance-url <instanceUrl>', 'Magentrix instance URL (e.g., https://example.magentrixcloud.com)')
|
|
170
|
-
.action(
|
|
217
|
+
.action(blockInVueProject(setup));
|
|
171
218
|
program.command('pull').description('Pull files from the remote server').action(withWorkspaceCheck(pull));
|
|
172
219
|
const createCommand = program
|
|
173
220
|
.command('create')
|
|
@@ -228,7 +275,7 @@ program.command('status').description('Show file conflicts').action(withWorkspac
|
|
|
228
275
|
program.command('autopublish').description('Watch & sync changes in real time').action(withWorkspaceCheck(autoPublish));
|
|
229
276
|
// Publish does its own comprehensive file scanning, so skip the pre-cache middleware
|
|
230
277
|
program.command('publish').description('Publish pending changes to the remote server').action(withWorkspaceCheck(publish));
|
|
231
|
-
program.command('update').description('Update MagentrixCLI to the latest version').action(
|
|
278
|
+
program.command('update').description('Update MagentrixCLI to the latest version').action(update);
|
|
232
279
|
|
|
233
280
|
// Config command - interactive wizard
|
|
234
281
|
program
|
|
@@ -251,6 +298,7 @@ program
|
|
|
251
298
|
.description('Build a Vue project and stage it for publishing')
|
|
252
299
|
.option('--path <path>', 'Path to the Vue project')
|
|
253
300
|
.option('--skip-build', 'Skip build step and use existing dist/')
|
|
301
|
+
.option('--workspace <workspace>', 'Path to Magentrix workspace to stage into')
|
|
254
302
|
.action(vueBuildStage);
|
|
255
303
|
|
|
256
304
|
program
|
|
@@ -258,7 +306,6 @@ program
|
|
|
258
306
|
.description('Start Vue dev server with platform assets injected')
|
|
259
307
|
.option('--path <path>', 'Path to the Vue project')
|
|
260
308
|
.option('--no-inject', 'Skip asset injection')
|
|
261
|
-
.option('--restore', 'Restore config.ts from backup')
|
|
262
309
|
.action(irisDev);
|
|
263
310
|
|
|
264
311
|
program
|
|
@@ -282,8 +329,9 @@ program.argument('[command]', 'command to run').action((cmd) => {
|
|
|
282
329
|
console.error(chalk.gray('This command requires a Magentrix workspace with global API key and instance URL.\n'));
|
|
283
330
|
console.error(chalk.cyan('Available commands in Vue projects:'));
|
|
284
331
|
console.error(chalk.gray(' • magentrix iris-link'));
|
|
332
|
+
console.error(chalk.gray(' • magentrix vue-build-stage'));
|
|
285
333
|
console.error(chalk.gray(' • magentrix run-dev'));
|
|
286
|
-
console.error(chalk.gray(' • magentrix
|
|
334
|
+
console.error(chalk.gray(' • magentrix update\n'));
|
|
287
335
|
process.exit(1);
|
|
288
336
|
}
|
|
289
337
|
|
package/package.json
CHANGED
package/utils/iris/builder.js
CHANGED
|
@@ -47,11 +47,12 @@ export async function buildVueProject(projectPath, options = {}) {
|
|
|
47
47
|
const errorOutput = [];
|
|
48
48
|
|
|
49
49
|
// Determine npm command based on platform
|
|
50
|
-
const
|
|
50
|
+
const isWindows = process.platform === 'win32';
|
|
51
|
+
const npmCmd = isWindows ? 'npm.cmd' : 'npm';
|
|
51
52
|
|
|
52
53
|
const child = spawn(npmCmd, ['run', 'build'], {
|
|
53
54
|
cwd: resolvedPath,
|
|
54
|
-
shell:
|
|
55
|
+
shell: isWindows, // Windows requires shell: true for .cmd files
|
|
55
56
|
env: { ...process.env, FORCE_COLOR: '1' }
|
|
56
57
|
});
|
|
57
58
|
|
|
@@ -127,11 +128,11 @@ export async function buildVueProject(projectPath, options = {}) {
|
|
|
127
128
|
}
|
|
128
129
|
|
|
129
130
|
/**
|
|
130
|
-
* Stage build output to
|
|
131
|
+
* Stage build output to a Magentrix workspace's iris-apps directory.
|
|
131
132
|
*
|
|
132
133
|
* @param {string} distPath - Path to the build output (dist directory)
|
|
133
134
|
* @param {string} slug - The app slug (folder name)
|
|
134
|
-
* @param {string}
|
|
135
|
+
* @param {string} workspacePath - Path to the Magentrix workspace (defaults to CWD)
|
|
135
136
|
* @returns {{
|
|
136
137
|
* success: boolean,
|
|
137
138
|
* stagedPath: string | null,
|
|
@@ -139,9 +140,9 @@ export async function buildVueProject(projectPath, options = {}) {
|
|
|
139
140
|
* fileCount: number
|
|
140
141
|
* }}
|
|
141
142
|
*/
|
|
142
|
-
export function
|
|
143
|
+
export function stageToWorkspace(distPath, slug, workspacePath = process.cwd()) {
|
|
143
144
|
const resolvedDistPath = resolve(distPath);
|
|
144
|
-
const irisAppsDir = join(
|
|
145
|
+
const irisAppsDir = join(workspacePath, EXPORT_ROOT, IRIS_APPS_DIR);
|
|
145
146
|
const targetDir = join(irisAppsDir, slug);
|
|
146
147
|
|
|
147
148
|
// Validate dist path exists
|
|
@@ -175,9 +176,18 @@ export function stageToCliProject(distPath, slug, cliProjectPath = process.cwd()
|
|
|
175
176
|
rmSync(targetDir, { recursive: true, force: true });
|
|
176
177
|
}
|
|
177
178
|
|
|
178
|
-
//
|
|
179
|
+
// Files to exclude from staging (not needed for Iris apps)
|
|
180
|
+
const excludeFiles = new Set(['index.html', 'favicon.ico']);
|
|
181
|
+
|
|
182
|
+
// Copy dist contents to target, excluding unnecessary files
|
|
179
183
|
try {
|
|
180
|
-
cpSync(resolvedDistPath, targetDir, {
|
|
184
|
+
cpSync(resolvedDistPath, targetDir, {
|
|
185
|
+
recursive: true,
|
|
186
|
+
filter: (src) => {
|
|
187
|
+
const filename = src.split(/[/\\]/).pop();
|
|
188
|
+
return !excludeFiles.has(filename);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
181
191
|
} catch (err) {
|
|
182
192
|
return {
|
|
183
193
|
success: false,
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import Config from './config.js';
|
|
4
|
+
|
|
5
|
+
const config = new Config();
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Register a workspace in the global registry.
|
|
9
|
+
* This stores the workspace path so it can be discovered later.
|
|
10
|
+
*
|
|
11
|
+
* @param {string} workspacePath - Full path to the workspace
|
|
12
|
+
* @param {string} instanceUrl - The Magentrix instance URL for this workspace
|
|
13
|
+
*/
|
|
14
|
+
export function registerWorkspace(workspacePath, instanceUrl) {
|
|
15
|
+
const workspaces = config.read('workspaces', { global: true }) || [];
|
|
16
|
+
|
|
17
|
+
// Check if already registered
|
|
18
|
+
const existingIndex = workspaces.findIndex(w => w.path === workspacePath);
|
|
19
|
+
|
|
20
|
+
if (existingIndex >= 0) {
|
|
21
|
+
// Update existing entry
|
|
22
|
+
workspaces[existingIndex] = {
|
|
23
|
+
path: workspacePath,
|
|
24
|
+
instanceUrl,
|
|
25
|
+
lastUsed: new Date().toISOString()
|
|
26
|
+
};
|
|
27
|
+
} else {
|
|
28
|
+
// Add new entry
|
|
29
|
+
workspaces.push({
|
|
30
|
+
path: workspacePath,
|
|
31
|
+
instanceUrl,
|
|
32
|
+
lastUsed: new Date().toISOString()
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
config.save('workspaces', workspaces, { global: true });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get all registered workspaces from the global registry.
|
|
41
|
+
* Validates that each workspace still exists on disk.
|
|
42
|
+
*
|
|
43
|
+
* @returns {Array<{path: string, instanceUrl: string, lastUsed: string, valid: boolean}>}
|
|
44
|
+
*/
|
|
45
|
+
export function getRegisteredWorkspaces() {
|
|
46
|
+
const workspaces = config.read('workspaces', { global: true }) || [];
|
|
47
|
+
|
|
48
|
+
return workspaces.map(workspace => {
|
|
49
|
+
// Check if the workspace still exists and is valid
|
|
50
|
+
const magentrixFolder = join(workspace.path, '.magentrix');
|
|
51
|
+
const srcFolder = join(workspace.path, 'src');
|
|
52
|
+
|
|
53
|
+
const valid = existsSync(magentrixFolder) && existsSync(srcFolder);
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
...workspace,
|
|
57
|
+
valid
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get all valid registered workspaces (those that still exist on disk).
|
|
64
|
+
*
|
|
65
|
+
* @returns {Array<{path: string, instanceUrl: string, lastUsed: string, valid: boolean}>}
|
|
66
|
+
*/
|
|
67
|
+
export function getValidWorkspaces() {
|
|
68
|
+
return getRegisteredWorkspaces().filter(w => w.valid);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Remove a workspace from the global registry.
|
|
73
|
+
*
|
|
74
|
+
* @param {string} workspacePath - Path to the workspace to remove
|
|
75
|
+
* @returns {boolean} True if removed, false if not found
|
|
76
|
+
*/
|
|
77
|
+
export function unregisterWorkspace(workspacePath) {
|
|
78
|
+
const workspaces = config.read('workspaces', { global: true }) || [];
|
|
79
|
+
const initialLength = workspaces.length;
|
|
80
|
+
|
|
81
|
+
const filtered = workspaces.filter(w => w.path !== workspacePath);
|
|
82
|
+
|
|
83
|
+
if (filtered.length < initialLength) {
|
|
84
|
+
config.save('workspaces', filtered, { global: true });
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Clean up invalid workspaces (those that no longer exist on disk).
|
|
93
|
+
*
|
|
94
|
+
* @returns {number} Number of workspaces removed
|
|
95
|
+
*/
|
|
96
|
+
export function cleanupInvalidWorkspaces() {
|
|
97
|
+
const workspaces = getRegisteredWorkspaces();
|
|
98
|
+
const validWorkspaces = workspaces.filter(w => w.valid);
|
|
99
|
+
const removedCount = workspaces.length - validWorkspaces.length;
|
|
100
|
+
|
|
101
|
+
if (removedCount > 0) {
|
|
102
|
+
// Remove the 'valid' property before saving
|
|
103
|
+
const toSave = validWorkspaces.map(({ valid, ...rest }) => rest);
|
|
104
|
+
config.save('workspaces', toSave, { global: true });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return removedCount;
|
|
108
|
+
}
|