mfer 1.4.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -6
- package/dist/commands/clone.js +7 -7
- package/dist/commands/config/sub-commands/edit-config.js +5 -3
- package/dist/commands/init.js +13 -30
- package/dist/commands/install.js +2 -2
- package/dist/commands/pull.js +9 -9
- package/dist/commands/run.js +104 -39
- package/dist/index.js +3 -3
- package/dist/utils/config-utils.js +11 -7
- package/package.json +11 -2
package/README.md
CHANGED
|
@@ -30,15 +30,18 @@ A powerful CLI tool designed to simplify the management and execution of multipl
|
|
|
30
30
|
## 📦 Installation
|
|
31
31
|
|
|
32
32
|
### Prerequisites
|
|
33
|
+
|
|
33
34
|
- Node.js 18 or higher
|
|
34
35
|
- Git (for repository management)
|
|
35
36
|
|
|
36
37
|
### Install from npm
|
|
38
|
+
|
|
37
39
|
```bash
|
|
38
40
|
npm install -g mfer
|
|
39
41
|
```
|
|
40
42
|
|
|
41
43
|
### Install from source
|
|
44
|
+
|
|
42
45
|
```bash
|
|
43
46
|
git clone https://github.com/srimel/mfer.git
|
|
44
47
|
cd mfer
|
|
@@ -50,6 +53,7 @@ npm install -g .
|
|
|
50
53
|
## 🛠️ Quick Start
|
|
51
54
|
|
|
52
55
|
### 1. Initialize Configuration
|
|
56
|
+
|
|
53
57
|
Start by setting up your mfer configuration:
|
|
54
58
|
|
|
55
59
|
```bash
|
|
@@ -57,11 +61,13 @@ mfer init
|
|
|
57
61
|
```
|
|
58
62
|
|
|
59
63
|
This interactive wizard will guide you through:
|
|
64
|
+
|
|
60
65
|
- Setting up your GitHub username
|
|
61
66
|
- Specifying the directory containing your micro frontends
|
|
62
67
|
- Selecting which projects to include in your default group
|
|
63
68
|
|
|
64
69
|
### 2. Run Your Micro Frontends
|
|
70
|
+
|
|
65
71
|
```bash
|
|
66
72
|
# Run all micro frontends
|
|
67
73
|
mfer run
|
|
@@ -74,6 +80,7 @@ mfer run shared
|
|
|
74
80
|
```
|
|
75
81
|
|
|
76
82
|
### 3. Update Your Repositories
|
|
83
|
+
|
|
77
84
|
```bash
|
|
78
85
|
# Pull latest changes from all repositories
|
|
79
86
|
mfer pull
|
|
@@ -85,6 +92,7 @@ mfer pull frontend
|
|
|
85
92
|
## 📋 Commands
|
|
86
93
|
|
|
87
94
|
### Quick Reference
|
|
95
|
+
|
|
88
96
|
- [`mfer init`](#mfer-init) - Interactive setup wizard
|
|
89
97
|
- [`mfer run`](#mfer-run-group_name) - Run micro frontend applications
|
|
90
98
|
- [`mfer pull`](#mfer-pull-group_name) - Pull latest changes from git repositories
|
|
@@ -94,81 +102,112 @@ mfer pull frontend
|
|
|
94
102
|
- [`mfer help`](#mfer-help) - Display help information
|
|
95
103
|
|
|
96
104
|
### `mfer init`
|
|
105
|
+
|
|
97
106
|
Interactive setup wizard to create your configuration file.
|
|
98
107
|
|
|
99
108
|
**Options:**
|
|
109
|
+
|
|
100
110
|
- `-f, --force`: Force re-initialization even if config exists
|
|
101
111
|
|
|
102
112
|
**Example:**
|
|
113
|
+
|
|
103
114
|
```bash
|
|
104
115
|
mfer init --force
|
|
105
116
|
```
|
|
106
117
|
|
|
107
118
|
### `mfer run [group_name]`
|
|
119
|
+
|
|
108
120
|
Run micro frontend applications concurrently.
|
|
109
121
|
|
|
110
122
|
**Arguments:**
|
|
123
|
+
|
|
111
124
|
- `group_name`: Name of the group to run (defaults to "all")
|
|
112
125
|
|
|
113
|
-
**
|
|
126
|
+
**Options:**
|
|
127
|
+
|
|
128
|
+
- `-c, --command <command>`: Custom command to run (default: npm start)
|
|
129
|
+
- `-a, --async`: Run custom command concurrently instead of sequentially
|
|
130
|
+
- `-s, --select`: Prompt to select which micro frontends to run
|
|
131
|
+
|
|
132
|
+
**Examples:**
|
|
133
|
+
|
|
114
134
|
```bash
|
|
115
|
-
mfer run
|
|
116
|
-
mfer run frontend
|
|
135
|
+
mfer run # Run all micro frontends with default command (npm start)
|
|
136
|
+
mfer run frontend # Run only frontend group with default command
|
|
137
|
+
mfer run --command "npm ci" home # Run custom command sequentially on home group
|
|
138
|
+
mfer run -c "yarn install" shared # Run yarn install sequentially on shared group
|
|
139
|
+
mfer run --command "npm ci" --async home # Run custom command concurrently on home group
|
|
140
|
+
mfer run -c "yarn install" -a shared # Run yarn install concurrently on shared group
|
|
141
|
+
mfer run --command "npm run build" --select # Select MFEs and run build command sequentially
|
|
117
142
|
```
|
|
118
143
|
|
|
119
144
|
### `mfer pull [group_name]`
|
|
145
|
+
|
|
120
146
|
Pull latest changes from git repositories.
|
|
121
147
|
|
|
122
148
|
**Arguments:**
|
|
149
|
+
|
|
123
150
|
- `group_name`: Name of the group to pull from (defaults to "all")
|
|
124
151
|
|
|
125
152
|
**Example:**
|
|
153
|
+
|
|
126
154
|
```bash
|
|
127
155
|
mfer pull # Pull from all repositories
|
|
128
156
|
mfer pull shared # Pull from shared components group only
|
|
129
157
|
```
|
|
130
158
|
|
|
131
159
|
### `mfer install [group_name]`
|
|
160
|
+
|
|
132
161
|
Install dependencies for all micro frontends in a group.
|
|
133
162
|
|
|
134
163
|
**Arguments:**
|
|
164
|
+
|
|
135
165
|
- `group_name`: Name of the group to install dependencies for (defaults to "all")
|
|
136
166
|
|
|
137
167
|
**Example:**
|
|
168
|
+
|
|
138
169
|
```bash
|
|
139
170
|
mfer install # Install dependencies for all micro frontends
|
|
140
171
|
mfer install frontend # Install dependencies for frontend group only
|
|
141
172
|
```
|
|
142
173
|
|
|
143
174
|
### `mfer clone [group_name]`
|
|
175
|
+
|
|
144
176
|
Clone repositories that don't exist locally.
|
|
145
177
|
|
|
146
178
|
**Arguments:**
|
|
179
|
+
|
|
147
180
|
- `group_name`: Name of the group to clone repositories from (defaults to "all")
|
|
148
181
|
|
|
149
182
|
**Example:**
|
|
183
|
+
|
|
150
184
|
```bash
|
|
151
185
|
mfer clone # Clone all repositories
|
|
152
186
|
mfer clone shared # Clone repositories in shared group only
|
|
153
187
|
```
|
|
154
188
|
|
|
155
189
|
### `mfer config`
|
|
190
|
+
|
|
156
191
|
Manage your configuration settings.
|
|
157
192
|
|
|
158
193
|
**Subcommands:**
|
|
194
|
+
|
|
159
195
|
- `mfer config list`: Display current configuration
|
|
160
196
|
- `mfer config edit`: Open configuration file in your default editor
|
|
161
197
|
|
|
162
198
|
**Example:**
|
|
199
|
+
|
|
163
200
|
```bash
|
|
164
201
|
mfer config list # Show current configuration
|
|
165
202
|
mfer config edit # Edit configuration in your editor
|
|
166
203
|
```
|
|
167
204
|
|
|
168
205
|
### `mfer help`
|
|
206
|
+
|
|
169
207
|
Display help information for mfer commands.
|
|
170
208
|
|
|
171
209
|
**Example:**
|
|
210
|
+
|
|
172
211
|
```bash
|
|
173
212
|
mfer help # Show general help
|
|
174
213
|
mfer help run # Show help for run command
|
|
@@ -208,15 +247,17 @@ groups:
|
|
|
208
247
|
You can edit your configuration in several ways:
|
|
209
248
|
|
|
210
249
|
1. **Interactive editor** (recommended):
|
|
250
|
+
|
|
211
251
|
```bash
|
|
212
252
|
mfer config edit
|
|
213
253
|
```
|
|
214
254
|
|
|
215
255
|
2. **Direct file editing**:
|
|
256
|
+
|
|
216
257
|
```bash
|
|
217
258
|
# On macOS/Linux
|
|
218
259
|
nano ~/.mfer/config.yaml
|
|
219
|
-
|
|
260
|
+
|
|
220
261
|
# On Windows
|
|
221
262
|
notepad %USERPROFILE%\.mfer\config.yaml
|
|
222
263
|
```
|
|
@@ -224,6 +265,7 @@ You can edit your configuration in several ways:
|
|
|
224
265
|
## 🎯 Use Cases
|
|
225
266
|
|
|
226
267
|
### Development Workflow
|
|
268
|
+
|
|
227
269
|
```bash
|
|
228
270
|
# Start your day
|
|
229
271
|
mfer pull # Get latest changes
|
|
@@ -234,6 +276,7 @@ mfer run admin # Start admin panel
|
|
|
234
276
|
```
|
|
235
277
|
|
|
236
278
|
### Project Organization
|
|
279
|
+
|
|
237
280
|
Organize your micro frontends into logical groups:
|
|
238
281
|
|
|
239
282
|
```yaml
|
|
@@ -258,6 +301,7 @@ groups:
|
|
|
258
301
|
```
|
|
259
302
|
|
|
260
303
|
### Team Collaboration
|
|
304
|
+
|
|
261
305
|
- Share configuration files with your team
|
|
262
306
|
- Standardize development environment setup
|
|
263
307
|
- Ensure everyone runs the same services
|
|
@@ -265,16 +309,19 @@ groups:
|
|
|
265
309
|
## 🔧 Advanced Usage
|
|
266
310
|
|
|
267
311
|
### Custom Start Commands
|
|
268
|
-
|
|
312
|
+
|
|
313
|
+
By default, mfer runs `npm start` in each project directory.
|
|
269
314
|
You can currently only customize this by modifying the run command in the source code.
|
|
270
315
|
|
|
271
|
-
Adding configurable custom start commands is something I plan on adding in the near future.
|
|
316
|
+
Adding configurable custom start commands is something I plan on adding in the near future.
|
|
272
317
|
I also welcome anyone to open a PR for that!
|
|
273
318
|
|
|
274
319
|
### Environment Variables
|
|
320
|
+
|
|
275
321
|
mfer respects your existing environment setup and will use the same Node.js and npm versions you have configured.
|
|
276
322
|
|
|
277
323
|
### Process Management
|
|
324
|
+
|
|
278
325
|
- All processes are managed concurrently with organized output
|
|
279
326
|
- Use Ctrl+C to gracefully shut down all running services
|
|
280
327
|
- Failed processes are reported with detailed error information
|
|
@@ -284,12 +331,14 @@ mfer respects your existing environment setup and will use the same Node.js and
|
|
|
284
331
|
### Common Issues
|
|
285
332
|
|
|
286
333
|
**"No configuration file detected"**
|
|
334
|
+
|
|
287
335
|
```bash
|
|
288
336
|
# Run the initialization wizard
|
|
289
337
|
mfer init
|
|
290
338
|
```
|
|
291
339
|
|
|
292
340
|
**"Group not found"**
|
|
341
|
+
|
|
293
342
|
```bash
|
|
294
343
|
# Check available groups
|
|
295
344
|
mfer config list
|
|
@@ -299,15 +348,18 @@ mfer config edit
|
|
|
299
348
|
```
|
|
300
349
|
|
|
301
350
|
**"Directory does not exist"**
|
|
351
|
+
|
|
302
352
|
- Ensure the `mfe_directory` path in your configuration is correct
|
|
303
353
|
- Use absolute paths for better reliability
|
|
304
354
|
- Check that the directory exists and is accessible
|
|
305
355
|
|
|
306
356
|
**"Not a git repository"**
|
|
357
|
+
|
|
307
358
|
- Ensure all projects in your configuration are valid git repositories
|
|
308
359
|
- Run `mfer clone` to clone missing repositories
|
|
309
360
|
|
|
310
361
|
### Development Mode
|
|
362
|
+
|
|
311
363
|
For local development of mfer itself:
|
|
312
364
|
|
|
313
365
|
```bash
|
|
@@ -331,6 +383,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|
|
331
383
|
## 🙏 Acknowledgments
|
|
332
384
|
|
|
333
385
|
Built with:
|
|
386
|
+
|
|
334
387
|
- [Commander.js](https://github.com/tj/commander.js) - CLI framework
|
|
335
388
|
- [Inquirer](https://github.com/SBoudrias/Inquirer.js) - Interactive prompts
|
|
336
389
|
- [Concurrently](https://github.com/open-cli-tools/concurrently) - Process management
|
package/dist/commands/clone.js
CHANGED
|
@@ -36,7 +36,7 @@ const cloneCommand = new Command("clone")
|
|
|
36
36
|
const gitResult = spawnSync("git", ["rev-parse", "--git-dir"], {
|
|
37
37
|
cwd: repoPath,
|
|
38
38
|
stdio: "pipe",
|
|
39
|
-
shell: true
|
|
39
|
+
shell: true,
|
|
40
40
|
});
|
|
41
41
|
if (gitResult.status === 0) {
|
|
42
42
|
existingRepos.push(repo);
|
|
@@ -50,7 +50,7 @@ const cloneCommand = new Command("clone")
|
|
|
50
50
|
}
|
|
51
51
|
if (existingRepos.length > 0) {
|
|
52
52
|
console.log(chalk.green(`\nRepositories already exist (${existingRepos.length}):`));
|
|
53
|
-
existingRepos.forEach(repo => {
|
|
53
|
+
existingRepos.forEach((repo) => {
|
|
54
54
|
console.log(chalk.green(` ✓ ${repo}`));
|
|
55
55
|
});
|
|
56
56
|
console.log();
|
|
@@ -73,7 +73,7 @@ const cloneCommand = new Command("clone")
|
|
|
73
73
|
command: `git clone ${baseUrl}/${repo}.git`,
|
|
74
74
|
name: repo,
|
|
75
75
|
cwd: mfeDir,
|
|
76
|
-
prefixColor: "green"
|
|
76
|
+
prefixColor: "green",
|
|
77
77
|
}));
|
|
78
78
|
console.log(chalk.green(`Cloning ${reposToClone.length} repositories in group: ${groupName}...`));
|
|
79
79
|
const concurrentlyResult = concurrently(commands, {
|
|
@@ -83,14 +83,14 @@ const cloneCommand = new Command("clone")
|
|
|
83
83
|
});
|
|
84
84
|
const handleSigint = () => {
|
|
85
85
|
console.log(chalk.yellow("\nReceived SIGINT. Stopping all clone operations..."));
|
|
86
|
-
concurrentlyResult.commands.forEach(cmd => {
|
|
87
|
-
if (cmd && typeof cmd.kill ===
|
|
86
|
+
concurrentlyResult.commands.forEach((cmd) => {
|
|
87
|
+
if (cmd && typeof cmd.kill === "function") {
|
|
88
88
|
cmd.kill();
|
|
89
89
|
}
|
|
90
90
|
});
|
|
91
91
|
process.exit(0);
|
|
92
92
|
};
|
|
93
|
-
process.once(
|
|
93
|
+
process.once("SIGINT", handleSigint);
|
|
94
94
|
concurrentlyResult.result.then(() => {
|
|
95
95
|
console.log(chalk.green(`\nSuccessfully cloned all repositories in group: ${groupName}`));
|
|
96
96
|
console.log(chalk.blue(`Repositories are located in: ${mfeDir}`));
|
|
@@ -105,7 +105,7 @@ const cloneCommand = new Command("clone")
|
|
|
105
105
|
console.error(chalk.yellow(` Repository ${name} failed to clone (cwd: ${cwd}) with exit code ${exitCode}`));
|
|
106
106
|
});
|
|
107
107
|
}
|
|
108
|
-
else if (err && err
|
|
108
|
+
else if (err && typeof err === "object" && "message" in err) {
|
|
109
109
|
console.error(err.message);
|
|
110
110
|
}
|
|
111
111
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import { configExists, configPath, warnOfMissingConfig } from "../../../utils/config-utils.js";
|
|
2
|
+
import { configExists, configPath, warnOfMissingConfig, } from "../../../utils/config-utils.js";
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import { spawn } from "child_process";
|
|
5
5
|
import * as os from "os";
|
|
@@ -10,12 +10,14 @@ export const editConfigCommand = new Command("edit")
|
|
|
10
10
|
warnOfMissingConfig();
|
|
11
11
|
return;
|
|
12
12
|
}
|
|
13
|
-
const editor = process.env.EDITOR ||
|
|
13
|
+
const editor = process.env.EDITOR ||
|
|
14
|
+
process.env.VISUAL ||
|
|
15
|
+
(os.platform() === "win32" ? "notepad" : "vi");
|
|
14
16
|
console.log(chalk.green(`Opening config file in editor: ${editor}\n`));
|
|
15
17
|
spawn(editor, [configPath], {
|
|
16
18
|
stdio: "ignore",
|
|
17
19
|
detached: true,
|
|
18
|
-
shell: true
|
|
20
|
+
shell: true,
|
|
19
21
|
}).unref();
|
|
20
22
|
process.exit(0);
|
|
21
23
|
});
|
package/dist/commands/init.js
CHANGED
|
@@ -12,14 +12,6 @@ import { configExists, isConfigValid, saveConfig, configPath, } from "../utils/c
|
|
|
12
12
|
import chalk from "chalk";
|
|
13
13
|
import * as fs from "fs";
|
|
14
14
|
import { input, confirm, checkbox } from "@inquirer/prompts";
|
|
15
|
-
const templateConfig = {
|
|
16
|
-
base_github_url: "https://github.com/your-username",
|
|
17
|
-
mfe_directory: "path/to/folder/containing/microfrontends",
|
|
18
|
-
groups: {
|
|
19
|
-
all: ["repo_name_1", "repo_name_2", "repo_name_3"],
|
|
20
|
-
customGroup1: ["repo_name2", "repo_name_3"],
|
|
21
|
-
},
|
|
22
|
-
};
|
|
23
15
|
function createAndSaveConfig(githubUsername, mfeDirectory, allGroup = []) {
|
|
24
16
|
const repositories = allGroup.length > 0 ? allGroup : ["my_mfe_1", "my_mfe_2"];
|
|
25
17
|
const newConfig = {
|
|
@@ -43,18 +35,18 @@ function createAndSaveConfig(githubUsername, mfeDirectory, allGroup = []) {
|
|
|
43
35
|
}
|
|
44
36
|
function getFoldersFromDirectory(directoryPath) {
|
|
45
37
|
try {
|
|
46
|
-
if (fs.existsSync(directoryPath) &&
|
|
38
|
+
if (fs.existsSync(directoryPath) &&
|
|
39
|
+
fs.statSync(directoryPath).isDirectory()) {
|
|
47
40
|
const entries = fs.readdirSync(directoryPath, { withFileTypes: true });
|
|
48
|
-
return entries.filter(e => e.isDirectory()).map(e => e.name);
|
|
41
|
+
return entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
49
42
|
}
|
|
50
43
|
}
|
|
51
|
-
catch (
|
|
44
|
+
catch (_a) {
|
|
52
45
|
}
|
|
53
46
|
return [];
|
|
54
47
|
}
|
|
55
48
|
function promptForGitHubInfo() {
|
|
56
49
|
return __awaiter(this, void 0, void 0, function* () {
|
|
57
|
-
var _a, _b;
|
|
58
50
|
try {
|
|
59
51
|
const usesGithub = yield confirm({
|
|
60
52
|
message: "Do you use GitHub to host your repositories?",
|
|
@@ -66,53 +58,42 @@ function promptForGitHubInfo() {
|
|
|
66
58
|
}
|
|
67
59
|
const githubUsername = yield input({
|
|
68
60
|
message: "What is your GitHub username?",
|
|
69
|
-
validate: (val) => val && val.trim() !== "" ? true : "Username cannot be empty"
|
|
61
|
+
validate: (val) => val && val.trim() !== "" ? true : "Username cannot be empty",
|
|
70
62
|
});
|
|
71
63
|
return { usesGithub: true, githubUsername };
|
|
72
64
|
}
|
|
73
65
|
catch (error) {
|
|
74
|
-
if (error instanceof Error && (((_a = error.message) === null || _a === void 0 ? void 0 : _a.includes('SIGINT')) || ((_b = error.message) === null || _b === void 0 ? void 0 : _b.includes('User force closed')))) {
|
|
75
|
-
throw error;
|
|
76
|
-
}
|
|
77
66
|
throw error;
|
|
78
67
|
}
|
|
79
68
|
});
|
|
80
69
|
}
|
|
81
70
|
function promptForMFEDirectory() {
|
|
82
71
|
return __awaiter(this, void 0, void 0, function* () {
|
|
83
|
-
var _a, _b;
|
|
84
72
|
try {
|
|
85
73
|
return yield input({
|
|
86
74
|
message: [
|
|
87
75
|
"Enter the path to the folder containing all your micro frontends.",
|
|
88
76
|
" (Tip: Drag a folder from your file explorer into this terminal to paste its path)",
|
|
89
|
-
" >>>"
|
|
77
|
+
" >>>",
|
|
90
78
|
].join("\n"),
|
|
91
|
-
validate: (val) => val && val.trim() !== "" ? true : "Folder path cannot be empty"
|
|
79
|
+
validate: (val) => val && val.trim() !== "" ? true : "Folder path cannot be empty",
|
|
92
80
|
});
|
|
93
81
|
}
|
|
94
82
|
catch (error) {
|
|
95
|
-
if (error instanceof Error && (((_a = error.message) === null || _a === void 0 ? void 0 : _a.includes('SIGINT')) || ((_b = error.message) === null || _b === void 0 ? void 0 : _b.includes('User force closed')))) {
|
|
96
|
-
throw error;
|
|
97
|
-
}
|
|
98
83
|
throw error;
|
|
99
84
|
}
|
|
100
85
|
});
|
|
101
86
|
}
|
|
102
87
|
function promptForFolderSelection(folders) {
|
|
103
88
|
return __awaiter(this, void 0, void 0, function* () {
|
|
104
|
-
var _a, _b;
|
|
105
89
|
try {
|
|
106
90
|
return yield checkbox({
|
|
107
91
|
message: "Select which folders to include in the default 'all' group:",
|
|
108
|
-
choices: folders.map(f => ({ name: f, value: f })),
|
|
109
|
-
validate: (arr) => arr.length > 0 ? true : "Select at least one folder"
|
|
92
|
+
choices: folders.map((f) => ({ name: f, value: f })),
|
|
93
|
+
validate: (arr) => (arr.length > 0 ? true : "Select at least one folder"),
|
|
110
94
|
});
|
|
111
95
|
}
|
|
112
96
|
catch (error) {
|
|
113
|
-
if (error instanceof Error && (((_a = error.message) === null || _a === void 0 ? void 0 : _a.includes('SIGINT')) || ((_b = error.message) === null || _b === void 0 ? void 0 : _b.includes('User force closed')))) {
|
|
114
|
-
throw error;
|
|
115
|
-
}
|
|
116
97
|
throw error;
|
|
117
98
|
}
|
|
118
99
|
});
|
|
@@ -128,7 +109,7 @@ const initCommand = new Command("init")
|
|
|
128
109
|
console.log(chalk.yellow("\nReceived SIGINT. Stopping initialization..."));
|
|
129
110
|
process.exit(130);
|
|
130
111
|
};
|
|
131
|
-
process.once(
|
|
112
|
+
process.once("SIGINT", handleSigint);
|
|
132
113
|
if (configExists && isConfigValid() && !options.force) {
|
|
133
114
|
const messagePrefix = chalk.red("Error");
|
|
134
115
|
const mferCommandHint = chalk.blue("mfer config edit");
|
|
@@ -165,7 +146,9 @@ const initCommand = new Command("init")
|
|
|
165
146
|
}
|
|
166
147
|
}
|
|
167
148
|
catch (error) {
|
|
168
|
-
if (error instanceof Error &&
|
|
149
|
+
if (error instanceof Error &&
|
|
150
|
+
(((_a = error.message) === null || _a === void 0 ? void 0 : _a.includes("SIGINT")) ||
|
|
151
|
+
((_b = error.message) === null || _b === void 0 ? void 0 : _b.includes("User force closed")))) {
|
|
169
152
|
console.log(chalk.yellow("\nReceived SIGINT. Stopping initialization..."));
|
|
170
153
|
process.exit(130);
|
|
171
154
|
}
|
package/dist/commands/install.js
CHANGED
|
@@ -40,7 +40,7 @@ const installCommand = new Command("install")
|
|
|
40
40
|
interrupted = true;
|
|
41
41
|
console.log(chalk.yellow("\nReceived SIGINT. Stopping installs..."));
|
|
42
42
|
};
|
|
43
|
-
process.once(
|
|
43
|
+
process.once("SIGINT", handleSigint);
|
|
44
44
|
for (const mfe of group) {
|
|
45
45
|
if (interrupted)
|
|
46
46
|
break;
|
|
@@ -49,7 +49,7 @@ const installCommand = new Command("install")
|
|
|
49
49
|
const result = spawnSync("npm", ["install"], {
|
|
50
50
|
cwd,
|
|
51
51
|
stdio: "inherit",
|
|
52
|
-
shell: true
|
|
52
|
+
shell: true,
|
|
53
53
|
});
|
|
54
54
|
if (result.status !== 0) {
|
|
55
55
|
hadError = true;
|
package/dist/commands/pull.js
CHANGED
|
@@ -34,19 +34,19 @@ const pullCommand = new Command("pull")
|
|
|
34
34
|
if (!fs.existsSync(repoPath)) {
|
|
35
35
|
invalidRepos.push({
|
|
36
36
|
name: repo,
|
|
37
|
-
reason: `Directory does not exist: ${repoPath}
|
|
37
|
+
reason: `Directory does not exist: ${repoPath}`,
|
|
38
38
|
});
|
|
39
39
|
continue;
|
|
40
40
|
}
|
|
41
41
|
const gitResult = spawnSync("git", ["rev-parse", "--git-dir"], {
|
|
42
42
|
cwd: repoPath,
|
|
43
43
|
stdio: "pipe",
|
|
44
|
-
shell: true
|
|
44
|
+
shell: true,
|
|
45
45
|
});
|
|
46
46
|
if (gitResult.status !== 0) {
|
|
47
47
|
invalidRepos.push({
|
|
48
48
|
name: repo,
|
|
49
|
-
reason: `Not a git repository: ${repoPath}
|
|
49
|
+
reason: `Not a git repository: ${repoPath}`,
|
|
50
50
|
});
|
|
51
51
|
continue;
|
|
52
52
|
}
|
|
@@ -61,7 +61,7 @@ const pullCommand = new Command("pull")
|
|
|
61
61
|
}
|
|
62
62
|
if (validRepos.length === 0) {
|
|
63
63
|
console.log(chalk.red("No valid git repositories found to pull from."));
|
|
64
|
-
if (invalidRepos.some(repo => repo.reason.includes("Directory does not exist"))) {
|
|
64
|
+
if (invalidRepos.some((repo) => repo.reason.includes("Directory does not exist"))) {
|
|
65
65
|
console.log(chalk.blue("\nTip: Run 'mfer init' to clone repositories that don't exist yet."));
|
|
66
66
|
}
|
|
67
67
|
return;
|
|
@@ -70,7 +70,7 @@ const pullCommand = new Command("pull")
|
|
|
70
70
|
command: "git pull",
|
|
71
71
|
name: repo,
|
|
72
72
|
cwd: path.join(mfeDir, repo),
|
|
73
|
-
prefixColor: "green"
|
|
73
|
+
prefixColor: "green",
|
|
74
74
|
}));
|
|
75
75
|
console.log(chalk.green(`Pulling latest changes for ${validRepos.length} repositories in group: ${groupName}...`));
|
|
76
76
|
const concurrentlyResult = concurrently(commands, {
|
|
@@ -80,14 +80,14 @@ const pullCommand = new Command("pull")
|
|
|
80
80
|
});
|
|
81
81
|
const handleSigint = () => {
|
|
82
82
|
console.log(chalk.yellow("\nReceived SIGINT. Stopping all git pull operations..."));
|
|
83
|
-
concurrentlyResult.commands.forEach(cmd => {
|
|
84
|
-
if (cmd && typeof cmd.kill ===
|
|
83
|
+
concurrentlyResult.commands.forEach((cmd) => {
|
|
84
|
+
if (cmd && typeof cmd.kill === "function") {
|
|
85
85
|
cmd.kill();
|
|
86
86
|
}
|
|
87
87
|
});
|
|
88
88
|
process.exit(0);
|
|
89
89
|
};
|
|
90
|
-
process.once(
|
|
90
|
+
process.once("SIGINT", handleSigint);
|
|
91
91
|
concurrentlyResult.result.then(() => {
|
|
92
92
|
console.log(chalk.green(`\nSuccessfully pulled latest changes for all repositories in group: ${groupName}`));
|
|
93
93
|
}, (err) => {
|
|
@@ -101,7 +101,7 @@ const pullCommand = new Command("pull")
|
|
|
101
101
|
console.error(chalk.yellow(` Repository ${name} failed to pull (cwd: ${cwd}) with exit code ${exitCode}`));
|
|
102
102
|
});
|
|
103
103
|
}
|
|
104
|
-
else if (err && err
|
|
104
|
+
else if (err && typeof err === "object" && "message" in err) {
|
|
105
105
|
console.error(err.message);
|
|
106
106
|
}
|
|
107
107
|
});
|
package/dist/commands/run.js
CHANGED
|
@@ -13,17 +13,32 @@ import concurrently from "concurrently";
|
|
|
13
13
|
import chalk from "chalk";
|
|
14
14
|
import path from "path";
|
|
15
15
|
import { checkbox } from "@inquirer/prompts";
|
|
16
|
-
|
|
16
|
+
import { spawn } from "child_process";
|
|
17
|
+
const DEFAULT_RUN_COMMAND = "npm start";
|
|
17
18
|
const runCommand = new Command("run")
|
|
18
19
|
.description("run micro-frontend applications")
|
|
19
20
|
.argument("[group_name]", "name of the group as specified in the configuration", "all")
|
|
20
21
|
.option("-s, --select", "prompt to select which micro frontends to run")
|
|
22
|
+
.option("-c, --command <command>", "custom command to run (default: npm start)")
|
|
23
|
+
.option("-a, --async", "run custom command concurrently instead of sequentially")
|
|
21
24
|
.action((groupName, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
22
25
|
var _a, _b;
|
|
23
26
|
if (!configExists) {
|
|
24
27
|
warnOfMissingConfig();
|
|
25
28
|
return;
|
|
26
29
|
}
|
|
30
|
+
if (options.command &&
|
|
31
|
+
typeof options.command === "string" &&
|
|
32
|
+
options.command.trim() === "") {
|
|
33
|
+
const messagePrefix = chalk.red("Error");
|
|
34
|
+
console.log(`${messagePrefix}: custom command cannot be empty`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (options.async && !options.command) {
|
|
38
|
+
const messagePrefix = chalk.red("Error");
|
|
39
|
+
console.log(`${messagePrefix}: --async can only be used with --command option`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
27
42
|
const group = currentConfig.groups[groupName];
|
|
28
43
|
if (!group) {
|
|
29
44
|
const messagePrefix = chalk.red("Error");
|
|
@@ -42,12 +57,14 @@ const runCommand = new Command("run")
|
|
|
42
57
|
console.log(chalk.blue(`Select micro frontends to run from group '${groupName}':`));
|
|
43
58
|
selectedMFEs = yield checkbox({
|
|
44
59
|
message: "Choose which micro frontends to run:",
|
|
45
|
-
choices: group.map(mfe => ({ name: mfe, value: mfe })),
|
|
46
|
-
validate: (arr) => arr.length > 0 ? true : "Select at least one micro frontend"
|
|
60
|
+
choices: group.map((mfe) => ({ name: mfe, value: mfe })),
|
|
61
|
+
validate: (arr) => arr.length > 0 ? true : "Select at least one micro frontend",
|
|
47
62
|
});
|
|
48
63
|
}
|
|
49
64
|
catch (error) {
|
|
50
|
-
if (error instanceof Error &&
|
|
65
|
+
if (error instanceof Error &&
|
|
66
|
+
(((_a = error.message) === null || _a === void 0 ? void 0 : _a.includes("SIGINT")) ||
|
|
67
|
+
((_b = error.message) === null || _b === void 0 ? void 0 : _b.includes("User force closed")))) {
|
|
51
68
|
console.log(chalk.yellow("\nReceived SIGINT. Stopping..."));
|
|
52
69
|
process.exit(130);
|
|
53
70
|
}
|
|
@@ -55,43 +72,91 @@ const runCommand = new Command("run")
|
|
|
55
72
|
}
|
|
56
73
|
}
|
|
57
74
|
const mfeDir = currentConfig.mfe_directory;
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
75
|
+
const commandToRun = options.command || DEFAULT_RUN_COMMAND;
|
|
76
|
+
const isAsync = options.async && options.command;
|
|
77
|
+
const groupText = options.select
|
|
78
|
+
? `selected MFEs from group '${groupName}'`
|
|
79
|
+
: `group '${groupName}'`;
|
|
80
|
+
const commandText = options.command
|
|
81
|
+
? `custom command '${commandToRun}'`
|
|
82
|
+
: "default command";
|
|
83
|
+
const executionMode = isAsync ? "concurrently" : "sequentially";
|
|
84
|
+
console.log(chalk.green(`Running ${commandText} on micro frontends in ${groupText} ${executionMode}...`));
|
|
85
|
+
if (isAsync || !options.command) {
|
|
86
|
+
yield runConcurrently(selectedMFEs, commandToRun, mfeDir);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
yield runSequentially(selectedMFEs, commandToRun, mfeDir);
|
|
90
|
+
}
|
|
91
|
+
}));
|
|
92
|
+
function runSequentially(mfes, command, mfeDir) {
|
|
93
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
94
|
+
for (const mfe of mfes) {
|
|
95
|
+
const cwd = path.join(mfeDir, mfe);
|
|
96
|
+
console.log(chalk.blue(`\n[${mfe}] Running: ${command}`));
|
|
97
|
+
try {
|
|
98
|
+
const result = yield new Promise((resolve) => {
|
|
99
|
+
const child = spawn(command, [], {
|
|
100
|
+
stdio: "inherit",
|
|
101
|
+
cwd,
|
|
102
|
+
shell: true,
|
|
103
|
+
});
|
|
104
|
+
child.on("close", (exitCode) => {
|
|
105
|
+
resolve({ exitCode });
|
|
106
|
+
});
|
|
107
|
+
child.on("error", (error) => {
|
|
108
|
+
console.error(chalk.red(`[${mfe}] Error: ${error.message}`));
|
|
109
|
+
resolve({ exitCode: 1 });
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
if (result.exitCode !== 0) {
|
|
113
|
+
console.error(chalk.red(`[${mfe}] Command failed with exit code ${result.exitCode}`));
|
|
114
|
+
}
|
|
76
115
|
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
console.error(chalk.red(`[${mfe}] Unexpected error: ${error}`));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
function runConcurrently(mfes, command, mfeDir) {
|
|
123
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
124
|
+
const commands = mfes.map((mfe) => ({
|
|
125
|
+
command,
|
|
126
|
+
name: mfe,
|
|
127
|
+
cwd: path.join(mfeDir, mfe),
|
|
128
|
+
prefixColor: "blue",
|
|
129
|
+
}));
|
|
130
|
+
const concurrentlyResult = concurrently(commands, {
|
|
131
|
+
prefix: "{name} |",
|
|
132
|
+
killOthersOn: ["failure", "success"],
|
|
133
|
+
restartTries: 0,
|
|
77
134
|
});
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
err.forEach((fail) => {
|
|
85
|
-
var _a, _b;
|
|
86
|
-
const name = ((_a = fail.command) === null || _a === void 0 ? void 0 : _a.name) || "unknown";
|
|
87
|
-
const exitCode = fail.exitCode;
|
|
88
|
-
const cwd = ((_b = fail.command) === null || _b === void 0 ? void 0 : _b.cwd) || "unknown";
|
|
89
|
-
console.error(chalk.yellow(` MFE ${name} failed to start (cwd: ${cwd}) with exit code ${exitCode}`));
|
|
135
|
+
const handleSigint = () => {
|
|
136
|
+
console.log(chalk.yellow("\nReceived SIGINT. Stopping all micro frontends..."));
|
|
137
|
+
concurrentlyResult.commands.forEach((cmd) => {
|
|
138
|
+
if (cmd && typeof cmd.kill === "function") {
|
|
139
|
+
cmd.kill();
|
|
140
|
+
}
|
|
90
141
|
});
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
142
|
+
process.exit(0);
|
|
143
|
+
};
|
|
144
|
+
process.once("SIGINT", handleSigint);
|
|
145
|
+
concurrentlyResult.result.then(() => { }, (err) => {
|
|
146
|
+
console.error(chalk.red("One or more micro frontends failed to start."));
|
|
147
|
+
if (Array.isArray(err)) {
|
|
148
|
+
err.forEach((fail) => {
|
|
149
|
+
var _a, _b;
|
|
150
|
+
const name = ((_a = fail.command) === null || _a === void 0 ? void 0 : _a.name) || "unknown";
|
|
151
|
+
const exitCode = fail.exitCode;
|
|
152
|
+
const cwd = ((_b = fail.command) === null || _b === void 0 ? void 0 : _b.cwd) || "unknown";
|
|
153
|
+
console.error(chalk.yellow(` MFE ${name} failed to start (cwd: ${cwd}) with exit code ${exitCode}`));
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
else if (err && typeof err === "object" && "message" in err) {
|
|
157
|
+
console.error(err.message);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
95
160
|
});
|
|
96
|
-
}
|
|
161
|
+
}
|
|
97
162
|
export default runCommand;
|
package/dist/index.js
CHANGED
|
@@ -10,11 +10,11 @@ import { loadConfig } from "./utils/config-utils.js";
|
|
|
10
10
|
program
|
|
11
11
|
.name("mfer")
|
|
12
12
|
.description("Micro Frontend Runner (mfer) - A CLI for running your project's micro frontends.")
|
|
13
|
-
.version("1.
|
|
14
|
-
.hook("preAction", (
|
|
13
|
+
.version("1.5.0", "-v, --version", "mfer CLI version")
|
|
14
|
+
.hook("preAction", () => {
|
|
15
15
|
console.log();
|
|
16
16
|
})
|
|
17
|
-
.hook("postAction", (
|
|
17
|
+
.hook("postAction", () => {
|
|
18
18
|
console.log();
|
|
19
19
|
});
|
|
20
20
|
program.addCommand(configCommand);
|
|
@@ -11,7 +11,9 @@ export const loadConfig = () => {
|
|
|
11
11
|
if (configExists) {
|
|
12
12
|
const configFile = fs.readFileSync(configPath, "utf8");
|
|
13
13
|
currentConfig = YAML.parse(configFile);
|
|
14
|
+
return currentConfig;
|
|
14
15
|
}
|
|
16
|
+
return undefined;
|
|
15
17
|
};
|
|
16
18
|
export const warnOfMissingConfig = () => {
|
|
17
19
|
if (!configExists) {
|
|
@@ -26,16 +28,16 @@ export const isConfigValid = () => {
|
|
|
26
28
|
const configFile = fs.readFileSync(configPath, "utf8");
|
|
27
29
|
const config = YAML.parse(configFile);
|
|
28
30
|
return (config &&
|
|
29
|
-
typeof config ===
|
|
31
|
+
typeof config === "object" &&
|
|
30
32
|
config.base_github_url &&
|
|
31
33
|
config.mfe_directory &&
|
|
32
34
|
config.groups &&
|
|
33
|
-
typeof config.groups ===
|
|
35
|
+
typeof config.groups === "object" &&
|
|
34
36
|
config.groups.all &&
|
|
35
37
|
Array.isArray(config.groups.all) &&
|
|
36
38
|
config.groups.all.length > 0);
|
|
37
39
|
}
|
|
38
|
-
catch (
|
|
40
|
+
catch (_a) {
|
|
39
41
|
return false;
|
|
40
42
|
}
|
|
41
43
|
};
|
|
@@ -47,16 +49,18 @@ export const saveConfig = (newConfig) => {
|
|
|
47
49
|
}
|
|
48
50
|
fs.writeFileSync(configPath, YAML.stringify(newConfig));
|
|
49
51
|
}
|
|
50
|
-
catch (
|
|
51
|
-
console.log(`Error writing config file!\n\n${
|
|
52
|
+
catch (error) {
|
|
53
|
+
console.log(`Error writing config file!\n\n${error}`);
|
|
52
54
|
}
|
|
53
55
|
};
|
|
54
56
|
export const editConfig = () => {
|
|
55
|
-
const editor = process.env.EDITOR ||
|
|
57
|
+
const editor = process.env.EDITOR ||
|
|
58
|
+
process.env.VISUAL ||
|
|
59
|
+
(os.platform() === "win32" ? "notepad" : "vi");
|
|
56
60
|
console.log(chalk.green(`Opening config file in editor: ${editor}\n`));
|
|
57
61
|
spawn(editor, [configPath], {
|
|
58
62
|
stdio: "ignore",
|
|
59
63
|
detached: true,
|
|
60
|
-
shell: true
|
|
64
|
+
shell: true,
|
|
61
65
|
}).unref();
|
|
62
66
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mfer",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "CLI tool designed to sensibly run micro-frontends from the terminal.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"mfer": "dist/index.js"
|
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
"clean": "rimraf dist",
|
|
12
12
|
"watch": "tsc --watch",
|
|
13
13
|
"test": "vitest run",
|
|
14
|
-
"test:
|
|
14
|
+
"test:coverage": "vitest run --coverage",
|
|
15
|
+
"lint": "eslint . --ext .ts && prettier --check .",
|
|
16
|
+
"lint:fix": "eslint . --ext .ts --fix && prettier --write ."
|
|
15
17
|
},
|
|
16
18
|
"keywords": [
|
|
17
19
|
"micro frontends",
|
|
@@ -39,8 +41,15 @@
|
|
|
39
41
|
},
|
|
40
42
|
"devDependencies": {
|
|
41
43
|
"@types/node": "^24.0.3",
|
|
44
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
45
|
+
"eslint": "^9.33.0",
|
|
46
|
+
"eslint-config-prettier": "^10.1.8",
|
|
47
|
+
"globals": "^16.3.0",
|
|
48
|
+
"jiti": "^2.5.1",
|
|
49
|
+
"prettier": "3.6.2",
|
|
42
50
|
"rimraf": "^6.0.1",
|
|
43
51
|
"typescript": "^5.8.3",
|
|
52
|
+
"typescript-eslint": "^8.40.0",
|
|
44
53
|
"vitest": "^3.2.4"
|
|
45
54
|
},
|
|
46
55
|
"dependencies": {
|