@magentrix-corp/magentrix-cli 1.3.3 → 1.3.5
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 +69 -33
- package/actions/iris/buildStage.js +328 -7
- package/actions/iris/dev.js +15 -97
- package/actions/setup.js +5 -1
- package/bin/magentrix.js +60 -12
- package/package.json +1 -1
- package/utils/iris/builder.js +7 -6
- package/utils/workspaces.js +108 -0
package/README.md
CHANGED
|
@@ -491,32 +491,38 @@ 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
|
|
506
|
-
magentrix
|
|
512
|
+
magentrix run-dev
|
|
507
513
|
```
|
|
508
514
|
**What it does**: Starts the Vue development server with platform assets (CSS, fonts) automatically injected from your Magentrix instance.
|
|
509
515
|
|
|
516
|
+
**Authentication**: Uses `VITE_REFRESH_TOKEN` from `.env.development` (no CLI authentication required). This command can be run inside the Vue project directory.
|
|
517
|
+
|
|
510
518
|
**Options:**
|
|
511
519
|
- `--path <dir>` - Specify Vue project path
|
|
512
520
|
- `--no-inject` - Skip asset injection, just run dev server
|
|
513
|
-
- `--restore` - Restore `.env.development` from backup without running
|
|
514
521
|
|
|
515
522
|
**Process:**
|
|
516
|
-
1. Fetch platform assets from Magentrix
|
|
517
|
-
2.
|
|
523
|
+
1. Fetch platform assets from Magentrix using `.env.development` credentials
|
|
524
|
+
2. Update `VITE_ASSETS` in `.env.development` (changes are kept)
|
|
518
525
|
3. Run `npm run dev`
|
|
519
|
-
4. Restore `.env.development` on exit (Ctrl+C)
|
|
520
526
|
|
|
521
527
|
#### Delete an Iris App
|
|
522
528
|
```bash
|
|
@@ -577,7 +583,7 @@ export const config = {
|
|
|
577
583
|
```bash
|
|
578
584
|
VITE_SITE_URL = https://yourinstance.magentrix.com
|
|
579
585
|
VITE_REFRESH_TOKEN = your-api-key
|
|
580
|
-
VITE_ASSETS = '[]' # Injected automatically by
|
|
586
|
+
VITE_ASSETS = '[]' # Injected automatically by run-dev
|
|
581
587
|
```
|
|
582
588
|
|
|
583
589
|
**Accepted field names in config.ts:**
|
|
@@ -586,28 +592,48 @@ VITE_ASSETS = '[]' # Injected automatically by iris-dev
|
|
|
586
592
|
- Description: `appDescription` or `app_description`
|
|
587
593
|
- Icon: `appIconId` or `app_icon_id`
|
|
588
594
|
|
|
595
|
+
### Command Availability
|
|
596
|
+
|
|
597
|
+
**In Vue project directories** (detected by presence of `config.ts`):
|
|
598
|
+
- ✓ `magentrix iris-link` - Link project to CLI
|
|
599
|
+
- ✓ `magentrix vue-build-stage` - Build and stage (prompts for target workspace)
|
|
600
|
+
- ✓ `magentrix run-dev` - Start dev server (uses `.env.development` credentials)
|
|
601
|
+
- ✓ `magentrix update` - Update CLI to latest version
|
|
602
|
+
|
|
603
|
+
**In Magentrix workspace directories** (has `.magentrix/` folder):
|
|
604
|
+
- ✓ All standard commands (`setup`, `pull`, `publish`, etc.)
|
|
605
|
+
- ✓ All Iris commands (`vue-build-stage`, `iris-delete`, `iris-recover`, etc.)
|
|
606
|
+
|
|
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.
|
|
608
|
+
|
|
589
609
|
### Typical Development Workflow
|
|
590
610
|
|
|
591
611
|
```bash
|
|
592
612
|
# First time setup
|
|
593
613
|
magentrix iris-link # Link your Vue project
|
|
594
614
|
|
|
595
|
-
# Development
|
|
596
|
-
|
|
615
|
+
# Development (run from Vue project folder)
|
|
616
|
+
cd ~/my-vue-app # Work from your Vue project
|
|
617
|
+
magentrix run-dev # Start dev server with platform assets
|
|
597
618
|
# Make changes, test locally
|
|
598
619
|
# Press Ctrl+C to stop
|
|
599
620
|
|
|
600
|
-
# 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
|
+
# Follow prompts, then navigate to workspace to publish
|
|
625
|
+
|
|
626
|
+
# Deployment - Option B (from Magentrix workspace)
|
|
601
627
|
cd ~/magentrix-workspace # Navigate to Magentrix workspace
|
|
602
628
|
magentrix vue-build-stage --path ~/my-vue-app # Build and stage
|
|
603
629
|
# Prompted: "Do you want to publish to Magentrix now?" → Yes/No
|
|
604
630
|
# If autopublish is running, it auto-deploys instead
|
|
605
631
|
|
|
606
|
-
# Deleting an app
|
|
632
|
+
# Deleting an app (from workspace)
|
|
607
633
|
magentrix iris-delete # Select app, confirm, auto-backup created
|
|
608
634
|
magentrix publish # Sync deletion to server
|
|
609
635
|
|
|
610
|
-
# Recovering a deleted app
|
|
636
|
+
# Recovering a deleted app (from workspace)
|
|
611
637
|
magentrix iris-recover # Select backup, restore files
|
|
612
638
|
magentrix publish # Sync recovery to server
|
|
613
639
|
```
|
|
@@ -627,29 +653,31 @@ magentrix vue-build-stage
|
|
|
627
653
|
|
|
628
654
|
### Troubleshooting Iris Apps
|
|
629
655
|
|
|
630
|
-
####
|
|
631
|
-
This warning appears when running `magentrix vue-build-stage` outside your Magentrix CLI workspace.
|
|
656
|
+
#### Running vue-build-stage from different locations
|
|
632
657
|
|
|
633
|
-
|
|
634
|
-
- The command stages build files to `src/iris-apps/<slug>/` in your Magentrix workspace
|
|
635
|
-
- You ran it from your Vue project directory or another location
|
|
658
|
+
The `vue-build-stage` command works from both locations:
|
|
636
659
|
|
|
637
|
-
**
|
|
638
|
-
1. Navigate to your Magentrix CLI workspace (the folder with `.magentrix/` and `src/`)
|
|
639
|
-
2. Run the command from there
|
|
640
|
-
3. Use `--path` to specify your Vue project: `magentrix vue-build-stage --path /path/to/vue-project`
|
|
641
|
-
|
|
642
|
-
**Example:**
|
|
660
|
+
**From Vue project directory:**
|
|
643
661
|
```bash
|
|
644
|
-
# Wrong - running from Vue project
|
|
645
662
|
cd ~/my-vue-app
|
|
646
|
-
magentrix vue-build-stage #
|
|
663
|
+
magentrix vue-build-stage # Prompts for which workspace to stage into
|
|
664
|
+
```
|
|
647
665
|
|
|
648
|
-
|
|
666
|
+
**From Magentrix workspace:**
|
|
667
|
+
```bash
|
|
649
668
|
cd ~/magentrix-workspace
|
|
650
|
-
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
|
|
651
671
|
```
|
|
652
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
|
+
|
|
653
681
|
#### "Missing required field in config.ts: slug (appPath)"
|
|
654
682
|
Your Vue project's `config.ts` is missing the app identifier. Add an `appPath` or `slug` field.
|
|
655
683
|
|
|
@@ -751,16 +779,24 @@ magentrix status # Verify everything is in sync
|
|
|
751
779
|
```
|
|
752
780
|
|
|
753
781
|
### Deploying a Vue.js App
|
|
754
|
-
```bash
|
|
755
|
-
# Important: Run from your Magentrix CLI workspace, NOT the Vue project folder
|
|
756
|
-
cd ~/magentrix-workspace # Navigate to Magentrix workspace
|
|
757
782
|
|
|
783
|
+
**Option A: From Vue project folder**
|
|
784
|
+
```bash
|
|
785
|
+
cd ~/my-vue-app # Navigate to your Vue project
|
|
758
786
|
magentrix iris-link # Link project (first time only)
|
|
787
|
+
magentrix vue-build-stage # Build and select workspace to stage into
|
|
788
|
+
# Navigate to workspace and run: magentrix publish
|
|
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)
|
|
759
795
|
magentrix vue-build-stage --path ~/my-vue-app # Build and stage
|
|
760
796
|
# Prompted: "Do you want to publish to Magentrix now?" (unless autopublish is running)
|
|
761
797
|
```
|
|
762
798
|
|
|
763
|
-
**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.
|
|
764
800
|
|
|
765
801
|
### Deleting and Recovering Apps
|
|
766
802
|
```bash
|
|
@@ -1058,7 +1094,7 @@ magentrix status # Shows sync status
|
|
|
1058
1094
|
- `magentrix update` - Update to latest version
|
|
1059
1095
|
- `magentrix iris-link` - Link Vue.js projects for deployment
|
|
1060
1096
|
- `magentrix vue-build-stage` - Build and stage Vue.js apps
|
|
1061
|
-
- `magentrix
|
|
1097
|
+
- `magentrix run-dev` - Start Vue dev server with platform assets
|
|
1062
1098
|
- `magentrix iris-delete` - Delete an Iris app with recovery backup
|
|
1063
1099
|
- `magentrix iris-recover` - Recover a deleted Iris app
|
|
1064
1100
|
|
|
@@ -1072,6 +1108,6 @@ Once you're comfortable with basic usage:
|
|
|
1072
1108
|
2. **Learn the autopublish workflow** for faster development
|
|
1073
1109
|
3. **Explore conflict resolution** if you work in a team
|
|
1074
1110
|
4. **Check out templates** created by the `create` command to understand best practices
|
|
1075
|
-
5. **Deploy Vue.js apps** using `iris-link`, `vue-build-stage`, and `
|
|
1111
|
+
5. **Deploy Vue.js apps** using `iris-link`, `vue-build-stage`, and `run-dev` commands
|
|
1076
1112
|
|
|
1077
1113
|
Happy coding with MagentrixCLI! 🚀
|
|
@@ -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,295 @@ 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
|
+
// Ask if they want to publish now
|
|
423
|
+
const shouldPublish = await confirm({
|
|
424
|
+
message: 'Do you want to publish to Magentrix now?',
|
|
425
|
+
default: true
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
if (shouldPublish) {
|
|
429
|
+
console.log();
|
|
430
|
+
console.log(chalk.blue('Running publish from workspace...'));
|
|
431
|
+
console.log();
|
|
432
|
+
|
|
433
|
+
const publishSuccess = await runPublishFromWorkspace(workspacePath);
|
|
434
|
+
|
|
435
|
+
if (!publishSuccess) {
|
|
436
|
+
console.log();
|
|
437
|
+
console.log(chalk.cyan('To publish manually:'));
|
|
438
|
+
console.log(chalk.white(` 1. Navigate to workspace: ${chalk.yellow(`cd "${workspacePath}"`)}`));
|
|
439
|
+
console.log(chalk.white(` 2. Run ${chalk.yellow('magentrix publish')}`));
|
|
440
|
+
}
|
|
441
|
+
} else {
|
|
442
|
+
console.log();
|
|
443
|
+
console.log(chalk.cyan('Next steps:'));
|
|
444
|
+
console.log(chalk.white(` 1. Navigate to workspace: ${chalk.yellow(`cd "${workspacePath}"`)}`));
|
|
445
|
+
console.log(chalk.white(` 2. Run ${chalk.yellow('magentrix publish')} to deploy to Magentrix`));
|
|
446
|
+
console.log(chalk.white(` Or use ${chalk.yellow('magentrix autopublish')} for automatic deployment`));
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Run magentrix publish from a specific workspace directory.
|
|
452
|
+
*
|
|
453
|
+
* @param {string} workspacePath - Path to the Magentrix workspace
|
|
454
|
+
* @returns {Promise<boolean>} - True if publish succeeded
|
|
455
|
+
*/
|
|
456
|
+
async function runPublishFromWorkspace(workspacePath) {
|
|
457
|
+
return new Promise((resolvePromise) => {
|
|
458
|
+
const isWindows = process.platform === 'win32';
|
|
459
|
+
const npmCmd = isWindows ? 'npx.cmd' : 'npx';
|
|
460
|
+
|
|
461
|
+
const child = spawn(npmCmd, ['magentrix', 'publish'], {
|
|
462
|
+
cwd: workspacePath,
|
|
463
|
+
stdio: 'inherit',
|
|
464
|
+
shell: isWindows
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
child.on('close', (code) => {
|
|
468
|
+
resolvePromise(code === 0);
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
child.on('error', (err) => {
|
|
472
|
+
console.log(chalk.yellow(`Warning: Could not run publish: ${err.message}`));
|
|
473
|
+
resolvePromise(false);
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Prompt user to select a Magentrix workspace.
|
|
480
|
+
*
|
|
481
|
+
* @returns {Promise<string | null>} - Selected workspace path or null if cancelled
|
|
482
|
+
*/
|
|
483
|
+
async function selectWorkspace() {
|
|
484
|
+
const workspaces = getValidWorkspaces();
|
|
485
|
+
|
|
486
|
+
if (workspaces.length === 0) {
|
|
487
|
+
console.log(chalk.yellow('No Magentrix workspaces found.'));
|
|
488
|
+
console.log();
|
|
489
|
+
console.log(chalk.gray('To register a workspace:'));
|
|
490
|
+
console.log(chalk.white(` • Run ${chalk.cyan('magentrix')} from an existing workspace (auto-registers it)`));
|
|
491
|
+
console.log(chalk.white(` • Or run ${chalk.cyan('magentrix setup')} in a new directory to create one`));
|
|
492
|
+
console.log();
|
|
493
|
+
console.log(chalk.gray('Or specify a workspace path directly:'));
|
|
494
|
+
console.log(chalk.white(` ${chalk.cyan('magentrix vue-build-stage --workspace /path/to/workspace')}`));
|
|
495
|
+
console.log();
|
|
496
|
+
|
|
497
|
+
// Allow manual entry
|
|
498
|
+
const manualPath = await input({
|
|
499
|
+
message: 'Enter the path to your Magentrix workspace (or leave empty to cancel):',
|
|
500
|
+
validate: (value) => {
|
|
501
|
+
if (!value.trim()) return true; // Allow empty for cancel
|
|
502
|
+
const resolved = resolve(value);
|
|
503
|
+
if (!existsSync(resolved)) {
|
|
504
|
+
return `Path does not exist: ${resolved}`;
|
|
505
|
+
}
|
|
506
|
+
const magentrixFolder = join(resolved, '.magentrix');
|
|
507
|
+
if (!existsSync(magentrixFolder)) {
|
|
508
|
+
return `Not a Magentrix workspace (missing .magentrix folder): ${resolved}`;
|
|
509
|
+
}
|
|
510
|
+
return true;
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
if (!manualPath.trim()) {
|
|
515
|
+
console.log(chalk.gray('Cancelled.'));
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return resolve(manualPath);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Build choices from registered workspaces
|
|
523
|
+
const choices = workspaces.map(w => ({
|
|
524
|
+
name: `${w.path}`,
|
|
525
|
+
value: w.path,
|
|
526
|
+
description: chalk.dim(`→ ${w.instanceUrl}`)
|
|
527
|
+
}));
|
|
528
|
+
|
|
529
|
+
choices.push({
|
|
530
|
+
name: 'Enter path manually',
|
|
531
|
+
value: '__manual__',
|
|
532
|
+
description: chalk.dim('→ Specify the full path to a workspace')
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
choices.push({
|
|
536
|
+
name: 'Cancel',
|
|
537
|
+
value: '__cancel__'
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
const choice = await select({
|
|
541
|
+
message: 'Which workspace do you want to stage into?',
|
|
542
|
+
choices
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
if (choice === '__cancel__') {
|
|
546
|
+
console.log(chalk.gray('Cancelled.'));
|
|
547
|
+
return null;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
if (choice === '__manual__') {
|
|
551
|
+
const manualPath = await input({
|
|
552
|
+
message: 'Enter the path to your Magentrix workspace:',
|
|
553
|
+
validate: (value) => {
|
|
554
|
+
if (!value.trim()) {
|
|
555
|
+
return 'Path is required';
|
|
556
|
+
}
|
|
557
|
+
const resolved = resolve(value);
|
|
558
|
+
if (!existsSync(resolved)) {
|
|
559
|
+
return `Path does not exist: ${resolved}`;
|
|
560
|
+
}
|
|
561
|
+
const magentrixFolder = join(resolved, '.magentrix');
|
|
562
|
+
if (!existsSync(magentrixFolder)) {
|
|
563
|
+
return `Not a Magentrix workspace (missing .magentrix folder): ${resolved}`;
|
|
564
|
+
}
|
|
565
|
+
return true;
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
return resolve(manualPath);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
return choice;
|
|
573
|
+
}
|
|
574
|
+
|
|
254
575
|
/**
|
|
255
576
|
* Prompt user to select a project.
|
|
256
577
|
*
|
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';
|
|
@@ -20,26 +18,19 @@ import {
|
|
|
20
18
|
} from '../../utils/iris/linker.js';
|
|
21
19
|
|
|
22
20
|
/**
|
|
23
|
-
*
|
|
21
|
+
* run-dev command - Start Vue dev server with platform assets injected.
|
|
24
22
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
23
|
+
* Uses credentials from .env.development (VITE_REFRESH_TOKEN, VITE_SITE_URL).
|
|
24
|
+
* Assets are updated in .env.development (VITE_ASSETS) and kept between runs.
|
|
27
25
|
*
|
|
28
26
|
* Options:
|
|
29
27
|
* --path <dir> Specify Vue project path
|
|
30
28
|
* --no-inject Skip asset injection, just run dev server
|
|
31
|
-
* --restore Restore .env.development or config.ts from backup
|
|
32
29
|
*/
|
|
33
30
|
export const irisDev = async (options = {}) => {
|
|
34
31
|
process.stdout.write('\x1Bc'); // Clear console
|
|
35
32
|
|
|
36
|
-
const { path: pathOption, inject = true
|
|
37
|
-
|
|
38
|
-
// Handle --restore option
|
|
39
|
-
if (restore) {
|
|
40
|
-
await handleRestore(pathOption);
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
33
|
+
const { path: pathOption, inject = true } = options;
|
|
43
34
|
|
|
44
35
|
// Determine which project to use
|
|
45
36
|
let projectPath = pathOption;
|
|
@@ -93,11 +84,6 @@ export const irisDev = async (options = {}) => {
|
|
|
93
84
|
}
|
|
94
85
|
console.log();
|
|
95
86
|
|
|
96
|
-
let backupPath = null;
|
|
97
|
-
let assetsInjected = false;
|
|
98
|
-
let modifiedFilePath = null;
|
|
99
|
-
let modifiedFileName = null;
|
|
100
|
-
|
|
101
87
|
// Inject assets if enabled
|
|
102
88
|
if (inject && siteUrl) {
|
|
103
89
|
// Check if we have the refresh token for authentication
|
|
@@ -116,27 +102,18 @@ export const irisDev = async (options = {}) => {
|
|
|
116
102
|
console.log(chalk.green(`\u2713 Found ${assetsResult.assets.length} platform assets`));
|
|
117
103
|
|
|
118
104
|
// Determine which file will be modified
|
|
119
|
-
const { targetFile
|
|
105
|
+
const { targetFile } = getInjectionTarget(projectPath);
|
|
120
106
|
|
|
121
107
|
if (!targetFile) {
|
|
122
108
|
console.log(chalk.yellow('Warning: No .env.development file found. Cannot inject assets.'));
|
|
123
109
|
console.log(chalk.gray('Create a .env.development file to enable asset injection.'));
|
|
124
110
|
} else {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
// Backup before modifying
|
|
129
|
-
console.log(chalk.blue(`Backing up ${modifiedFileName}...`));
|
|
130
|
-
backupPath = backupFile(modifiedFilePath);
|
|
131
|
-
console.log(chalk.green(`\u2713 Backup created`));
|
|
132
|
-
|
|
133
|
-
// Inject assets
|
|
134
|
-
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...'));
|
|
135
113
|
const injectResult = injectAssets(projectPath, assetsResult.assets);
|
|
136
114
|
|
|
137
115
|
if (injectResult.success) {
|
|
138
|
-
|
|
139
|
-
console.log(chalk.green(`\u2713 Assets injected into ${injectResult.targetName}`));
|
|
116
|
+
console.log(chalk.green(`\u2713 Assets updated in ${injectResult.targetName}`));
|
|
140
117
|
} else {
|
|
141
118
|
console.log(chalk.yellow('Warning: Could not inject assets. Continuing without injection.'));
|
|
142
119
|
}
|
|
@@ -165,50 +142,34 @@ export const irisDev = async (options = {}) => {
|
|
|
165
142
|
console.log(chalk.gray('Press Ctrl+C to stop'));
|
|
166
143
|
console.log();
|
|
167
144
|
|
|
168
|
-
await runDevServer(projectPath
|
|
145
|
+
await runDevServer(projectPath);
|
|
169
146
|
};
|
|
170
147
|
|
|
171
148
|
/**
|
|
172
149
|
* Run the Vue development server.
|
|
173
150
|
*/
|
|
174
|
-
async function runDevServer(projectPath
|
|
151
|
+
async function runDevServer(projectPath) {
|
|
175
152
|
return new Promise((resolvePromise) => {
|
|
176
|
-
const
|
|
153
|
+
const isWindows = process.platform === 'win32';
|
|
154
|
+
const npmCmd = isWindows ? 'npm.cmd' : 'npm';
|
|
177
155
|
|
|
178
156
|
const child = spawn(npmCmd, ['run', 'dev'], {
|
|
179
157
|
cwd: projectPath,
|
|
180
158
|
stdio: 'inherit',
|
|
181
|
-
shell:
|
|
159
|
+
shell: isWindows // Windows requires shell: true for .cmd files
|
|
182
160
|
});
|
|
183
161
|
|
|
184
|
-
// Handle cleanup on exit
|
|
185
|
-
const cleanup = () => {
|
|
186
|
-
if (assetsInjected && backupPath && modifiedFilePath) {
|
|
187
|
-
console.log();
|
|
188
|
-
console.log(chalk.blue(`Restoring ${modifiedFileName} from backup...`));
|
|
189
|
-
const restored = restoreFile(modifiedFilePath);
|
|
190
|
-
if (restored) {
|
|
191
|
-
console.log(chalk.green(`\u2713 ${modifiedFileName} restored`));
|
|
192
|
-
} else {
|
|
193
|
-
console.log(chalk.yellow(`Warning: Could not restore ${modifiedFileName}`));
|
|
194
|
-
console.log(chalk.gray(`Backup is at: ${backupPath}`));
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
};
|
|
198
|
-
|
|
199
162
|
// Handle process signals
|
|
200
163
|
process.on('SIGINT', () => {
|
|
201
|
-
cleanup();
|
|
202
164
|
child.kill('SIGINT');
|
|
203
165
|
});
|
|
204
166
|
|
|
205
167
|
process.on('SIGTERM', () => {
|
|
206
|
-
cleanup();
|
|
207
168
|
child.kill('SIGTERM');
|
|
208
169
|
});
|
|
209
170
|
|
|
210
171
|
child.on('close', (code) => {
|
|
211
|
-
// If dev server exited with error, show helpful context
|
|
172
|
+
// If dev server exited with error, show helpful context
|
|
212
173
|
if (code !== 0 && code !== null) {
|
|
213
174
|
console.log();
|
|
214
175
|
console.log(chalk.bgYellow.black(' External Process Error '));
|
|
@@ -227,59 +188,16 @@ async function runDevServer(projectPath, modifiedFilePath, modifiedFileName, bac
|
|
|
227
188
|
console.log(chalk.yellow('─'.repeat(48)));
|
|
228
189
|
}
|
|
229
190
|
|
|
230
|
-
// Cleanup after showing error context
|
|
231
|
-
cleanup();
|
|
232
|
-
|
|
233
191
|
resolvePromise(code);
|
|
234
192
|
});
|
|
235
193
|
|
|
236
194
|
child.on('error', (err) => {
|
|
237
195
|
console.log(chalk.red(`Failed to start dev server: ${err.message}`));
|
|
238
|
-
cleanup();
|
|
239
196
|
resolvePromise(1);
|
|
240
197
|
});
|
|
241
198
|
});
|
|
242
199
|
}
|
|
243
200
|
|
|
244
|
-
/**
|
|
245
|
-
* Handle --restore option.
|
|
246
|
-
*/
|
|
247
|
-
async function handleRestore(pathOption) {
|
|
248
|
-
let projectPath = pathOption;
|
|
249
|
-
|
|
250
|
-
if (!projectPath) {
|
|
251
|
-
// Try current directory
|
|
252
|
-
const cwdConfig = readVueConfig(process.cwd());
|
|
253
|
-
if (cwdConfig.found) {
|
|
254
|
-
projectPath = process.cwd();
|
|
255
|
-
} else {
|
|
256
|
-
console.log(chalk.red('No Vue project found in current directory.'));
|
|
257
|
-
console.log(chalk.gray('Use --path to specify the project path.'));
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
projectPath = resolve(projectPath);
|
|
263
|
-
|
|
264
|
-
// Determine which file would be the injection target
|
|
265
|
-
const { targetFile, targetName } = getInjectionTarget(projectPath);
|
|
266
|
-
|
|
267
|
-
if (!targetFile) {
|
|
268
|
-
console.log(chalk.yellow('No config files found to restore.'));
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
console.log(chalk.blue(`Restoring ${targetName} from backup...`));
|
|
273
|
-
const restored = restoreFile(targetFile);
|
|
274
|
-
|
|
275
|
-
if (restored) {
|
|
276
|
-
console.log(chalk.green(`\u2713 ${targetName} restored from backup`));
|
|
277
|
-
} else {
|
|
278
|
-
console.log(chalk.yellow('No backup file found.'));
|
|
279
|
-
console.log(chalk.gray(`Expected backup at: ${targetFile}.bak`));
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
201
|
/**
|
|
284
202
|
* Prompt user to select a project.
|
|
285
203
|
*/
|
|
@@ -316,7 +234,7 @@ async function selectProject() {
|
|
|
316
234
|
console.log();
|
|
317
235
|
console.log(chalk.gray('To get started:'));
|
|
318
236
|
console.log(chalk.white(` 1. Link a Vue project: ${chalk.cyan('magentrix iris-link')}`));
|
|
319
|
-
console.log(chalk.white(` 2. Or specify path: ${chalk.cyan('magentrix
|
|
237
|
+
console.log(chalk.white(` 2. Or specify path: ${chalk.cyan('magentrix run-dev --path /path/to/vue-project')}`));
|
|
320
238
|
console.log();
|
|
321
239
|
}
|
|
322
240
|
|
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'));
|
|
47
|
-
console.error(chalk.gray(' • magentrix
|
|
48
|
-
console.error(chalk.gray(' • magentrix
|
|
51
|
+
console.error(chalk.gray(' • magentrix vue-build-stage'));
|
|
52
|
+
console.error(chalk.gray(' • magentrix run-dev'));
|
|
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}`);
|
|
@@ -105,7 +152,7 @@ program
|
|
|
105
152
|
{ name: 'update', desc: 'Update MagentrixCLI to the latest version', icon: '⬆️ ' },
|
|
106
153
|
{ name: 'iris-link', desc: 'Link a Vue project to the CLI', icon: '🔗 ' },
|
|
107
154
|
{ name: 'vue-build-stage', desc: 'Build Vue project and stage for publish', icon: '🏗️ ' },
|
|
108
|
-
{ name: '
|
|
155
|
+
{ name: 'run-dev', desc: 'Start Vue dev server with platform assets', icon: '🌐 ' },
|
|
109
156
|
{ name: 'iris-delete', desc: 'Delete an Iris app with backup', icon: '🗑️ ' },
|
|
110
157
|
{ name: 'iris-recover', desc: 'Recover a deleted Iris app from backup', icon: '♻️ ' }
|
|
111
158
|
];
|
|
@@ -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,14 +298,14 @@ 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
|
|
257
|
-
.command('
|
|
305
|
+
.command('run-dev')
|
|
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'));
|
|
285
|
-
console.error(chalk.gray(' • magentrix
|
|
286
|
-
console.error(chalk.gray(' • magentrix
|
|
332
|
+
console.error(chalk.gray(' • magentrix vue-build-stage'));
|
|
333
|
+
console.error(chalk.gray(' • magentrix run-dev'));
|
|
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
|
|
@@ -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
|
+
}
|