mfer 3.2.4 → 4.0.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 +86 -57
- package/dist/commands/config/index.js +2 -0
- package/dist/commands/config/sub-commands/migrate.js +86 -0
- package/dist/commands/init.js +1 -1
- package/dist/commands/run.js +33 -12
- package/dist/index.js +2 -2
- package/dist/utils/command-utils.js +8 -0
- package/dist/utils/config-utils.js +46 -18
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ A powerful CLI tool designed to simplify the management and execution of multipl
|
|
|
24
24
|
- **Concurrent Execution**: Run multiple micro frontends simultaneously with organized output
|
|
25
25
|
- **Group Management**: Organize micro frontends into logical groups for selective execution
|
|
26
26
|
- **Git Integration**: Pull latest changes from all repositories with a single command
|
|
27
|
-
- **Smart Configuration**: Interactive setup wizard with
|
|
27
|
+
- **Smart Configuration**: Interactive setup wizard with TOML-based configuration
|
|
28
28
|
- **Cross-Platform**: Works on Windows, macOS, and Linux
|
|
29
29
|
- **Graceful Shutdown**: Clean termination of all processes with Ctrl+C
|
|
30
30
|
|
|
@@ -95,7 +95,7 @@ mfer pull frontend
|
|
|
95
95
|
### Quick Reference
|
|
96
96
|
|
|
97
97
|
- [`mfer init`](#mfer-init) - Interactive setup wizard
|
|
98
|
-
- [`mfer run`](#mfer-run-group_name) - Run micro frontend applications
|
|
98
|
+
- [`mfer run`](#mfer-run-group_name) - Run micro frontend applications (supports per-MFE run modes)
|
|
99
99
|
- [`mfer pull`](#mfer-pull-group_name) - Pull latest changes from git repositories
|
|
100
100
|
- [`mfer install`](#mfer-install-group_name) - Install dependencies for micro frontends
|
|
101
101
|
- [`mfer clone`](#mfer-clone-group_name) - Clone repositories that don't exist locally
|
|
@@ -127,20 +127,24 @@ Run micro frontend applications concurrently.
|
|
|
127
127
|
|
|
128
128
|
**Options:**
|
|
129
129
|
|
|
130
|
-
- `-c, --command <command>`: Custom command to run (default: npm start)
|
|
131
|
-
- `-a, --async`: Run custom command concurrently instead of sequentially
|
|
130
|
+
- `-c, --command <command>`: Custom command to run for all MFEs (default: npm start)
|
|
131
|
+
- `-a, --async`: Run custom command concurrently instead of sequentially (only with `--command`)
|
|
132
|
+
- `-m, --mode <mode_name>`: Run MFEs using a named mode from config (see [Per-MFE Run Modes](#per-mfe-run-modes))
|
|
132
133
|
- `-s, --select`: Prompt to select which micro frontends to run
|
|
133
134
|
|
|
135
|
+
> **Note:** `--mode` and `--command` are mutually exclusive.
|
|
136
|
+
|
|
134
137
|
**Examples:**
|
|
135
138
|
|
|
136
139
|
```bash
|
|
137
|
-
mfer run
|
|
138
|
-
mfer run frontend
|
|
139
|
-
mfer run --
|
|
140
|
-
mfer run
|
|
140
|
+
mfer run # Run all MFEs with default command (npm start)
|
|
141
|
+
mfer run frontend # Run frontend group with default command
|
|
142
|
+
mfer run --mode mock # Run all MFEs in mock mode (per-MFE commands from config)
|
|
143
|
+
mfer run frontend --mode mock # Run frontend group in mock mode
|
|
144
|
+
mfer run --command "npm ci" home # Run custom command sequentially on home group
|
|
145
|
+
mfer run -c "yarn install" shared # Run yarn install sequentially on shared group
|
|
141
146
|
mfer run --command "npm ci" --async home # Run custom command concurrently on home group
|
|
142
|
-
mfer run
|
|
143
|
-
mfer run --command "npm run build" --select # Select MFEs and run build command sequentially
|
|
147
|
+
mfer run --command "npm run build" --select # Select MFEs and run build sequentially
|
|
144
148
|
```
|
|
145
149
|
|
|
146
150
|
### `mfer pull [group_name]`
|
|
@@ -355,29 +359,27 @@ mfer lib install my-design-system --select # Select libraries from specific libr
|
|
|
355
359
|
|
|
356
360
|
## ⚙️ Configuration
|
|
357
361
|
|
|
358
|
-
mfer uses a
|
|
359
|
-
|
|
360
|
-
```
|
|
361
|
-
base_github_url
|
|
362
|
-
mfe_directory
|
|
363
|
-
lib_directory
|
|
364
|
-
libs
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
- my-shared-components
|
|
376
|
-
admin:
|
|
377
|
-
- my-admin-panel
|
|
378
|
-
- my-shared-components
|
|
362
|
+
mfer uses a TOML configuration file located at `~/.mfer/config.toml`. Here's an example structure:
|
|
363
|
+
|
|
364
|
+
```toml
|
|
365
|
+
base_github_url = "https://github.com/your-username"
|
|
366
|
+
mfe_directory = "/path/to/your/micro-frontends"
|
|
367
|
+
lib_directory = "/path/to/your/internal-libs"
|
|
368
|
+
libs = ["my-shared-utils", "my-design-system", "my-common-components"]
|
|
369
|
+
|
|
370
|
+
[groups]
|
|
371
|
+
all = ["my-main-app", "my-admin-panel", "my-shared-components"]
|
|
372
|
+
main = ["my-main-app", "my-shared-components"]
|
|
373
|
+
admin = ["my-admin-panel", "my-shared-components"]
|
|
374
|
+
|
|
375
|
+
# optional, per-MFE configuration
|
|
376
|
+
[[mfes.my-main-app.modes]]
|
|
377
|
+
mode_name = "mock"
|
|
378
|
+
command = "npm run start:mocked"
|
|
379
379
|
```
|
|
380
380
|
|
|
381
|
+
> **Migrating from v3.x?** v4.0.0 is a breaking change: YAML config is no longer supported. Run `mfer config migrate` to convert your existing `~/.mfer/config.yaml` to TOML automatically.
|
|
382
|
+
|
|
381
383
|
### Configuration Options
|
|
382
384
|
|
|
383
385
|
- **`base_github_url`**: Your GitHub base URL for repository operations
|
|
@@ -387,6 +389,38 @@ groups:
|
|
|
387
389
|
- **`groups`**: Named collections of micro frontend projects
|
|
388
390
|
- **`all`**: Default group containing all projects (required)
|
|
389
391
|
- **Custom groups**: Any additional groups you want to create
|
|
392
|
+
- **`mfes`**: Per-MFE configuration (optional). Each key is an MFE name matching one in `groups`.
|
|
393
|
+
- **`modes`**: Array of named run modes for the MFE. Each mode has:
|
|
394
|
+
- **`mode_name`**: Name used with `mfer run --mode <name>`
|
|
395
|
+
- **`command`**: The command to run for this MFE when the mode is active
|
|
396
|
+
|
|
397
|
+
### Per-MFE Run Modes
|
|
398
|
+
|
|
399
|
+
Run modes let individual MFEs use a different start command while the rest of the group continue using `npm start`. This is useful when some MFEs support a mocked/stubbed backend while others don't need it.
|
|
400
|
+
|
|
401
|
+
**Config example** — `root-config` runs with a mocked API, everything else runs normally:
|
|
402
|
+
|
|
403
|
+
```toml
|
|
404
|
+
[groups]
|
|
405
|
+
all = ["root-config", "mfe-nav", "mfe-dashboard"]
|
|
406
|
+
|
|
407
|
+
[[mfes.root-config.modes]]
|
|
408
|
+
mode_name = "mock"
|
|
409
|
+
command = "npm run start:mocked"
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
**Usage:**
|
|
413
|
+
|
|
414
|
+
```bash
|
|
415
|
+
mfer run --mode mock
|
|
416
|
+
# root-config → npm run start:mocked
|
|
417
|
+
# mfe-nav → npm start (no mock mode defined, falls back to default)
|
|
418
|
+
# mfe-dashboard → npm start
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
- MFEs that **do not** have the mode defined silently fall back to `npm start`.
|
|
422
|
+
- If **no MFE** in the group has the requested mode, a warning is printed but the command still runs (all MFEs use `npm start`).
|
|
423
|
+
- `--mode` cannot be combined with `--command`; they are mutually exclusive.
|
|
390
424
|
|
|
391
425
|
### Editing Configuration
|
|
392
426
|
|
|
@@ -402,12 +436,20 @@ You can edit your configuration in several ways:
|
|
|
402
436
|
|
|
403
437
|
```bash
|
|
404
438
|
# On macOS/Linux
|
|
405
|
-
nano ~/.mfer/config.
|
|
439
|
+
nano ~/.mfer/config.toml
|
|
406
440
|
|
|
407
441
|
# On Windows
|
|
408
|
-
notepad %USERPROFILE%\.mfer\config.
|
|
442
|
+
notepad %USERPROFILE%\.mfer\config.toml
|
|
409
443
|
```
|
|
410
444
|
|
|
445
|
+
3. **Migrate a legacy YAML config**:
|
|
446
|
+
|
|
447
|
+
```bash
|
|
448
|
+
mfer config migrate
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
Converts `~/.mfer/config.yaml` → `~/.mfer/config.toml`. Prompts before overwriting an existing TOML file and optionally removes the legacy YAML afterward.
|
|
452
|
+
|
|
411
453
|
## 🎯 Use Cases
|
|
412
454
|
|
|
413
455
|
### Development Workflow
|
|
@@ -425,25 +467,12 @@ mfer run admin # Start admin panel
|
|
|
425
467
|
|
|
426
468
|
Organize your micro frontends into logical groups:
|
|
427
469
|
|
|
428
|
-
```
|
|
429
|
-
groups
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
- shared-components
|
|
435
|
-
- design-system
|
|
436
|
-
core:
|
|
437
|
-
- main-app
|
|
438
|
-
- shared-components
|
|
439
|
-
- design-system
|
|
440
|
-
admin:
|
|
441
|
-
- admin-panel
|
|
442
|
-
- user-dashboard
|
|
443
|
-
- shared-components
|
|
444
|
-
ui:
|
|
445
|
-
- shared-components
|
|
446
|
-
- design-system
|
|
470
|
+
```toml
|
|
471
|
+
[groups]
|
|
472
|
+
all = ["main-app", "admin-panel", "user-dashboard", "shared-components", "design-system"]
|
|
473
|
+
core = ["main-app", "shared-components", "design-system"]
|
|
474
|
+
admin = ["admin-panel", "user-dashboard", "shared-components"]
|
|
475
|
+
ui = ["shared-components", "design-system"]
|
|
447
476
|
```
|
|
448
477
|
|
|
449
478
|
### Team Collaboration
|
|
@@ -456,11 +485,10 @@ groups:
|
|
|
456
485
|
|
|
457
486
|
### Custom Start Commands
|
|
458
487
|
|
|
459
|
-
By default, mfer runs `npm start` in each project directory.
|
|
460
|
-
You can currently only customize this by modifying the run command in the source code.
|
|
488
|
+
By default, mfer runs `npm start` in each project directory. You can customize this in two ways:
|
|
461
489
|
|
|
462
|
-
|
|
463
|
-
|
|
490
|
+
- **Same command for all MFEs**: use `--command` (e.g. `mfer run --command "yarn start"`)
|
|
491
|
+
- **Per-MFE commands via modes**: define `mfes[name].modes` in config and use `--mode` (see [Per-MFE Run Modes](#per-mfe-run-modes))
|
|
464
492
|
|
|
465
493
|
### Environment Variables
|
|
466
494
|
|
|
@@ -544,4 +572,5 @@ Built with:
|
|
|
544
572
|
- [Inquirer](https://github.com/SBoudrias/Inquirer.js) - Interactive prompts
|
|
545
573
|
- [Concurrently](https://github.com/open-cli-tools/concurrently) - Process management
|
|
546
574
|
- [Chalk](https://github.com/chalk/chalk) - Terminal styling
|
|
547
|
-
- [
|
|
575
|
+
- [smol-toml](https://github.com/squirrelchat/smol-toml) - Configuration parsing
|
|
576
|
+
- [YAML](https://github.com/eemeli/yaml) - Legacy config parsing for `mfer config migrate`
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { listConfigCommand } from "./sub-commands/list-config.js";
|
|
3
3
|
import { editConfigCommand } from "./sub-commands/edit-config.js";
|
|
4
|
+
import { migrateConfigCommand } from "./sub-commands/migrate.js";
|
|
4
5
|
const configCommand = new Command("config").description("configuration settings");
|
|
5
6
|
configCommand.addCommand(listConfigCommand);
|
|
6
7
|
configCommand.addCommand(editConfigCommand);
|
|
8
|
+
configCommand.addCommand(migrateConfigCommand);
|
|
7
9
|
export default configCommand;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { Command } from "commander";
|
|
11
|
+
import * as fs from "fs";
|
|
12
|
+
import * as path from "path";
|
|
13
|
+
import * as os from "os";
|
|
14
|
+
import chalk from "chalk";
|
|
15
|
+
import YAML from "yaml";
|
|
16
|
+
import { stringify as stringifyToml } from "smol-toml";
|
|
17
|
+
import { confirm } from "@inquirer/prompts";
|
|
18
|
+
import { isParsedConfigValid, } from "../../../utils/config-utils.js";
|
|
19
|
+
const yamlConfigPath = path.join(os.homedir(), ".mfer/config.yaml");
|
|
20
|
+
const tomlConfigPath = path.join(os.homedir(), ".mfer/config.toml");
|
|
21
|
+
export const migrateConfigCommand = new Command("migrate")
|
|
22
|
+
.description("migrate a legacy YAML config to TOML")
|
|
23
|
+
.action(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
24
|
+
if (!fs.existsSync(yamlConfigPath)) {
|
|
25
|
+
console.log(`${chalk.red("Error")}: No YAML config found at ${yamlConfigPath}`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
let parsed;
|
|
29
|
+
try {
|
|
30
|
+
parsed = YAML.parse(fs.readFileSync(yamlConfigPath, "utf8"));
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
console.log(`${chalk.red("Error")}: Failed to parse YAML config\n${error}`);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (!parsed || typeof parsed !== "object") {
|
|
37
|
+
console.log(`${chalk.red("Error")}: YAML config is empty or invalid`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (!isParsedConfigValid(parsed)) {
|
|
41
|
+
console.log(`${chalk.red("Error")}: YAML config is missing required fields or is structurally invalid. Migration aborted.`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (fs.existsSync(tomlConfigPath)) {
|
|
45
|
+
try {
|
|
46
|
+
const overwrite = yield confirm({
|
|
47
|
+
message: `${tomlConfigPath} already exists. Overwrite?`,
|
|
48
|
+
default: false,
|
|
49
|
+
});
|
|
50
|
+
if (!overwrite) {
|
|
51
|
+
console.log(chalk.yellow("Migration cancelled."));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
console.log(`${chalk.red("Error")}: Unable to prompt for overwrite\n${error}`);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
fs.writeFileSync(tomlConfigPath, stringifyToml(parsed));
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
console.log(`${chalk.red("Error")}: Failed to write TOML config\n${error}`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
console.log(chalk.green(`Migrated config written to ${tomlConfigPath}`));
|
|
68
|
+
let removeOld = false;
|
|
69
|
+
try {
|
|
70
|
+
removeOld = yield confirm({
|
|
71
|
+
message: `Delete the legacy ${yamlConfigPath}?`,
|
|
72
|
+
default: false,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
console.log(`${chalk.yellow("Warning")}: Unable to prompt for legacy deletion — leaving legacy config in place\n${error}`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (removeOld) {
|
|
80
|
+
fs.unlinkSync(yamlConfigPath);
|
|
81
|
+
console.log(chalk.green(`Removed ${yamlConfigPath}`));
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
console.log(chalk.yellow(`Legacy config left in place at ${yamlConfigPath}. You can delete it manually.`));
|
|
85
|
+
}
|
|
86
|
+
}));
|
package/dist/commands/init.js
CHANGED
|
@@ -33,7 +33,7 @@ function createAndSaveConfig(githubUsername, mfeDirectory, allGroup = [], libDir
|
|
|
33
33
|
console.log(chalk.blue(`Config file saved to: ${configPath}`));
|
|
34
34
|
console.log(chalk.yellow("You can edit the config file later using: mfer config edit"));
|
|
35
35
|
if (allGroup.length === 0) {
|
|
36
|
-
console.log(chalk.yellow("\nNote: Placeholder repository names have been added to show proper
|
|
36
|
+
console.log(chalk.yellow("\nNote: Placeholder repository names have been added to show proper TOML syntax."));
|
|
37
37
|
console.log(chalk.yellow("Please replace 'my_mfe_1' and 'my_mfe_2' with your actual repository names."));
|
|
38
38
|
}
|
|
39
39
|
if (libDirectory && libs.length > 0) {
|
package/dist/commands/run.js
CHANGED
|
@@ -12,15 +12,15 @@ import { configExists, currentConfig, warnOfMissingConfig, } from "../utils/conf
|
|
|
12
12
|
import concurrently from "concurrently";
|
|
13
13
|
import chalk from "chalk";
|
|
14
14
|
import path from "path";
|
|
15
|
-
import { promptForMFESelection } from "../utils/command-utils.js";
|
|
15
|
+
import { promptForMFESelection, resolveRunCommand, } from "../utils/command-utils.js";
|
|
16
16
|
import { spawn } from "child_process";
|
|
17
|
-
const DEFAULT_RUN_COMMAND = "npm start";
|
|
18
17
|
const runCommand = new Command("run")
|
|
19
18
|
.description("run micro-frontend applications")
|
|
20
19
|
.argument("[group_name]", "name of the group as specified in the configuration", "all")
|
|
21
20
|
.option("-s, --select", "prompt to select which micro frontends to run")
|
|
22
21
|
.option("-c, --command <command>", "custom command to run (default: npm start)")
|
|
23
22
|
.option("-a, --async", "run custom command concurrently instead of sequentially (only works with --command option)")
|
|
23
|
+
.option("-m, --mode <mode_name>", "run MFEs using a named mode from config")
|
|
24
24
|
.action((groupName, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
25
25
|
if (!configExists) {
|
|
26
26
|
warnOfMissingConfig();
|
|
@@ -33,11 +33,16 @@ const runCommand = new Command("run")
|
|
|
33
33
|
console.log(`${messagePrefix}: custom command cannot be empty`);
|
|
34
34
|
return;
|
|
35
35
|
}
|
|
36
|
-
if (options.async && !options.command) {
|
|
36
|
+
if (options.async && !options.command && !options.mode) {
|
|
37
37
|
const messagePrefix = chalk.red("Error");
|
|
38
38
|
console.log(`${messagePrefix}: --async can only be used with --command option`);
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
41
|
+
if (options.mode && options.command) {
|
|
42
|
+
const messagePrefix = chalk.red("Error");
|
|
43
|
+
console.log(`${messagePrefix}: --mode and --command cannot be used together`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
41
46
|
const group = currentConfig.groups[groupName];
|
|
42
47
|
if (!group) {
|
|
43
48
|
const messagePrefix = chalk.red("Error");
|
|
@@ -54,26 +59,42 @@ const runCommand = new Command("run")
|
|
|
54
59
|
if (options.select) {
|
|
55
60
|
selectedMFEs = yield promptForMFESelection(groupName, group);
|
|
56
61
|
}
|
|
62
|
+
if (options.mode) {
|
|
63
|
+
const hasAnyMfeWithMode = selectedMFEs.some((mfe) => {
|
|
64
|
+
var _a, _b, _c;
|
|
65
|
+
return (_c = (_b = (_a = currentConfig.mfes) === null || _a === void 0 ? void 0 : _a[mfe]) === null || _b === void 0 ? void 0 : _b.modes) === null || _c === void 0 ? void 0 : _c.some((m) => m.mode_name === options.mode);
|
|
66
|
+
});
|
|
67
|
+
if (!hasAnyMfeWithMode) {
|
|
68
|
+
console.log(chalk.yellow(`Warning: no MFE in the selected group has a '${options.mode}' mode defined. All MFEs will use the default command.`));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
57
71
|
const mfeDir = currentConfig.mfe_directory;
|
|
58
|
-
const commandToRun = options.command || DEFAULT_RUN_COMMAND;
|
|
59
72
|
const isAsync = options.async && options.command;
|
|
73
|
+
const mfeCommands = selectedMFEs.map((mfe) => ({
|
|
74
|
+
mfe,
|
|
75
|
+
command: options.command
|
|
76
|
+
? options.command
|
|
77
|
+
: resolveRunCommand(mfe, options.mode, currentConfig),
|
|
78
|
+
}));
|
|
60
79
|
const groupText = options.select
|
|
61
80
|
? `selected MFEs from group '${groupName}'`
|
|
62
81
|
: `group '${groupName}'`;
|
|
63
82
|
const commandText = options.command
|
|
64
|
-
? `custom command '${
|
|
65
|
-
:
|
|
83
|
+
? `custom command '${options.command}'`
|
|
84
|
+
: options.mode
|
|
85
|
+
? `mode '${options.mode}'`
|
|
86
|
+
: "default command";
|
|
66
87
|
console.log(chalk.green(`Running ${commandText} on micro frontends in ${groupText}...`));
|
|
67
88
|
if (isAsync || !options.command) {
|
|
68
|
-
yield runConcurrently(
|
|
89
|
+
yield runConcurrently(mfeCommands, mfeDir);
|
|
69
90
|
}
|
|
70
91
|
else {
|
|
71
|
-
yield runSequentially(
|
|
92
|
+
yield runSequentially(mfeCommands, mfeDir);
|
|
72
93
|
}
|
|
73
94
|
}));
|
|
74
|
-
function runSequentially(
|
|
95
|
+
function runSequentially(mfeCommands, mfeDir) {
|
|
75
96
|
return __awaiter(this, void 0, void 0, function* () {
|
|
76
|
-
for (const mfe of
|
|
97
|
+
for (const { mfe, command } of mfeCommands) {
|
|
77
98
|
const cwd = path.join(mfeDir, mfe);
|
|
78
99
|
console.log(chalk.blue(`\n[${mfe}] Running: ${command}`));
|
|
79
100
|
try {
|
|
@@ -101,9 +122,9 @@ function runSequentially(mfes, command, mfeDir) {
|
|
|
101
122
|
}
|
|
102
123
|
});
|
|
103
124
|
}
|
|
104
|
-
function runConcurrently(
|
|
125
|
+
function runConcurrently(mfeCommands, mfeDir) {
|
|
105
126
|
return __awaiter(this, void 0, void 0, function* () {
|
|
106
|
-
const commands =
|
|
127
|
+
const commands = mfeCommands.map(({ mfe, command }) => ({
|
|
107
128
|
command,
|
|
108
129
|
name: mfe,
|
|
109
130
|
cwd: path.join(mfeDir, mfe),
|
package/dist/index.js
CHANGED
|
@@ -9,11 +9,11 @@ import pullCommand from "./commands/pull.js";
|
|
|
9
9
|
import libCommand from "./commands/lib/index.js";
|
|
10
10
|
import updateCommand from "./commands/update.js";
|
|
11
11
|
import { loadConfig } from "./utils/config-utils.js";
|
|
12
|
-
import { checkForUpdateNotification } from "./utils/version-utils.js";
|
|
12
|
+
import { checkForUpdateNotification, getInstalledVersion, } from "./utils/version-utils.js";
|
|
13
13
|
program
|
|
14
14
|
.name("mfer")
|
|
15
15
|
.description("Micro Frontend Runner (mfer) - A CLI for running your project's micro frontends.")
|
|
16
|
-
.version(
|
|
16
|
+
.version(getInstalledVersion(), "-v, --version", "mfer CLI version")
|
|
17
17
|
.hook("preAction", () => {
|
|
18
18
|
console.log();
|
|
19
19
|
})
|
|
@@ -13,6 +13,14 @@ import { spawnSync } from "child_process";
|
|
|
13
13
|
import concurrently from "concurrently";
|
|
14
14
|
import path from "path";
|
|
15
15
|
import fs from "fs";
|
|
16
|
+
export const DEFAULT_RUN_COMMAND = "npm start";
|
|
17
|
+
export function resolveRunCommand(mfe, modeName, config) {
|
|
18
|
+
var _a, _b, _c, _d;
|
|
19
|
+
if (!modeName)
|
|
20
|
+
return DEFAULT_RUN_COMMAND;
|
|
21
|
+
const mode = (_c = (_b = (_a = config.mfes) === null || _a === void 0 ? void 0 : _a[mfe]) === null || _b === void 0 ? void 0 : _b.modes) === null || _c === void 0 ? void 0 : _c.find((m) => m.mode_name === modeName);
|
|
22
|
+
return (_d = mode === null || mode === void 0 ? void 0 : mode.command) !== null && _d !== void 0 ? _d : DEFAULT_RUN_COMMAND;
|
|
23
|
+
}
|
|
16
24
|
export function promptForMFESelection(groupName, mfes) {
|
|
17
25
|
return __awaiter(this, void 0, void 0, function* () {
|
|
18
26
|
var _a, _b;
|
|
@@ -1,45 +1,73 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
import * as os from "os";
|
|
4
|
-
import
|
|
4
|
+
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
5
5
|
import chalk from "chalk";
|
|
6
6
|
import { spawn } from "child_process";
|
|
7
|
-
export const configPath = path.join(os.homedir(), ".mfer/config.
|
|
7
|
+
export const configPath = path.join(os.homedir(), ".mfer/config.toml");
|
|
8
|
+
export const legacyYamlConfigPath = path.join(os.homedir(), ".mfer/config.yaml");
|
|
8
9
|
export const configExists = fs.existsSync(configPath);
|
|
10
|
+
export const legacyYamlConfigExists = fs.existsSync(legacyYamlConfigPath);
|
|
9
11
|
export let currentConfig;
|
|
10
12
|
export const loadConfig = () => {
|
|
11
13
|
if (configExists) {
|
|
12
14
|
const configFile = fs.readFileSync(configPath, "utf8");
|
|
13
|
-
currentConfig =
|
|
15
|
+
currentConfig = parseToml(configFile);
|
|
14
16
|
return currentConfig;
|
|
15
17
|
}
|
|
16
18
|
return undefined;
|
|
17
19
|
};
|
|
18
20
|
export const warnOfMissingConfig = () => {
|
|
19
21
|
if (!configExists) {
|
|
22
|
+
if (legacyYamlConfigExists) {
|
|
23
|
+
console.log(`${chalk.red("Error")}: No TOML configuration file detected, but a legacy YAML config was found at ${legacyYamlConfigPath}\n Please run ${chalk.blue.bold("mfer config migrate")} to convert it to TOML`);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
20
26
|
console.log(`${chalk.red("Error")}: No configuration file detected\n Please run ${chalk.blue.bold("mfer init")} to create one`);
|
|
21
27
|
}
|
|
22
28
|
};
|
|
29
|
+
export const isParsedConfigValid = (parsed) => {
|
|
30
|
+
const config = parsed;
|
|
31
|
+
const hasRequiredFields = Boolean(config &&
|
|
32
|
+
typeof config === "object" &&
|
|
33
|
+
config.base_github_url &&
|
|
34
|
+
config.mfe_directory &&
|
|
35
|
+
config.groups &&
|
|
36
|
+
typeof config.groups === "object" &&
|
|
37
|
+
config.groups.all &&
|
|
38
|
+
Array.isArray(config.groups.all) &&
|
|
39
|
+
config.groups.all.length > 0);
|
|
40
|
+
if (!hasRequiredFields)
|
|
41
|
+
return false;
|
|
42
|
+
if (config.lib_directory && (!config.libs || !Array.isArray(config.libs))) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
if (config.mfes && typeof config.mfes === "object") {
|
|
46
|
+
for (const mfeConfig of Object.values(config.mfes)) {
|
|
47
|
+
if (mfeConfig && mfeConfig.modes !== undefined) {
|
|
48
|
+
const modes = mfeConfig.modes;
|
|
49
|
+
if (!Array.isArray(modes))
|
|
50
|
+
return false;
|
|
51
|
+
for (const mode of modes) {
|
|
52
|
+
if (!mode ||
|
|
53
|
+
typeof mode !== "object" ||
|
|
54
|
+
!mode.mode_name ||
|
|
55
|
+
!mode.command) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return true;
|
|
63
|
+
};
|
|
23
64
|
export const isConfigValid = () => {
|
|
24
65
|
if (!configExists) {
|
|
25
66
|
return false;
|
|
26
67
|
}
|
|
27
68
|
try {
|
|
28
69
|
const configFile = fs.readFileSync(configPath, "utf8");
|
|
29
|
-
|
|
30
|
-
const hasRequiredFields = config &&
|
|
31
|
-
typeof config === "object" &&
|
|
32
|
-
config.base_github_url &&
|
|
33
|
-
config.mfe_directory &&
|
|
34
|
-
config.groups &&
|
|
35
|
-
typeof config.groups === "object" &&
|
|
36
|
-
config.groups.all &&
|
|
37
|
-
Array.isArray(config.groups.all) &&
|
|
38
|
-
config.groups.all.length > 0;
|
|
39
|
-
if (config.lib_directory && (!config.libs || !Array.isArray(config.libs))) {
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
42
|
-
return hasRequiredFields;
|
|
70
|
+
return isParsedConfigValid(parseToml(configFile));
|
|
43
71
|
}
|
|
44
72
|
catch (_a) {
|
|
45
73
|
return false;
|
|
@@ -51,7 +79,7 @@ export const saveConfig = (newConfig) => {
|
|
|
51
79
|
if (!fs.existsSync(configDir)) {
|
|
52
80
|
fs.mkdirSync(configDir, { recursive: true });
|
|
53
81
|
}
|
|
54
|
-
fs.writeFileSync(configPath,
|
|
82
|
+
fs.writeFileSync(configPath, stringifyToml(newConfig));
|
|
55
83
|
}
|
|
56
84
|
catch (error) {
|
|
57
85
|
console.log(`Error writing config file!\n\n${error}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mfer",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "CLI tool designed to sensibly run micro-frontends from the terminal.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"mfer": "dist/index.js"
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"chalk": "^5.6.2",
|
|
58
58
|
"commander": "^14.0.0",
|
|
59
59
|
"concurrently": "^9.2.0",
|
|
60
|
+
"smol-toml": "^1.6.1",
|
|
60
61
|
"yaml": "^2.8.0"
|
|
61
62
|
},
|
|
62
63
|
"files": [
|