mfer 3.3.0 → 4.0.1
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 +45 -60
- package/dist/commands/config/index.js +2 -0
- package/dist/commands/config/sub-commands/list-config.js +2 -1
- package/dist/commands/config/sub-commands/migrate.js +86 -0
- package/dist/commands/init.js +1 -1
- package/dist/utils/config-utils.js +45 -35
- package/dist/utils/version-utils.js +2 -1
- 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
|
|
|
@@ -359,34 +359,27 @@ mfer lib install my-design-system --select # Select libraries from specific libr
|
|
|
359
359
|
|
|
360
360
|
## ⚙️ Configuration
|
|
361
361
|
|
|
362
|
-
mfer uses a
|
|
363
|
-
|
|
364
|
-
```
|
|
365
|
-
base_github_url
|
|
366
|
-
mfe_directory
|
|
367
|
-
lib_directory
|
|
368
|
-
libs
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
- my-shared-components
|
|
380
|
-
admin:
|
|
381
|
-
- my-admin-panel
|
|
382
|
-
- my-shared-components
|
|
383
|
-
mfes: # optional, per-MFE configuration
|
|
384
|
-
my-main-app:
|
|
385
|
-
modes:
|
|
386
|
-
- mode_name: mock
|
|
387
|
-
command: npm run start:mocked
|
|
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"
|
|
388
379
|
```
|
|
389
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
|
+
|
|
390
383
|
### Configuration Options
|
|
391
384
|
|
|
392
385
|
- **`base_github_url`**: Your GitHub base URL for repository operations
|
|
@@ -407,17 +400,13 @@ Run modes let individual MFEs use a different start command while the rest of th
|
|
|
407
400
|
|
|
408
401
|
**Config example** — `root-config` runs with a mocked API, everything else runs normally:
|
|
409
402
|
|
|
410
|
-
```
|
|
411
|
-
groups
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
root-config:
|
|
418
|
-
modes:
|
|
419
|
-
- mode_name: mock
|
|
420
|
-
command: npm run start:mocked
|
|
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"
|
|
421
410
|
```
|
|
422
411
|
|
|
423
412
|
**Usage:**
|
|
@@ -447,12 +436,20 @@ You can edit your configuration in several ways:
|
|
|
447
436
|
|
|
448
437
|
```bash
|
|
449
438
|
# On macOS/Linux
|
|
450
|
-
nano ~/.mfer/config.
|
|
439
|
+
nano ~/.mfer/config.toml
|
|
451
440
|
|
|
452
441
|
# On Windows
|
|
453
|
-
notepad %USERPROFILE%\.mfer\config.
|
|
442
|
+
notepad %USERPROFILE%\.mfer\config.toml
|
|
454
443
|
```
|
|
455
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
|
+
|
|
456
453
|
## 🎯 Use Cases
|
|
457
454
|
|
|
458
455
|
### Development Workflow
|
|
@@ -470,25 +467,12 @@ mfer run admin # Start admin panel
|
|
|
470
467
|
|
|
471
468
|
Organize your micro frontends into logical groups:
|
|
472
469
|
|
|
473
|
-
```
|
|
474
|
-
groups
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
- shared-components
|
|
480
|
-
- design-system
|
|
481
|
-
core:
|
|
482
|
-
- main-app
|
|
483
|
-
- shared-components
|
|
484
|
-
- design-system
|
|
485
|
-
admin:
|
|
486
|
-
- admin-panel
|
|
487
|
-
- user-dashboard
|
|
488
|
-
- shared-components
|
|
489
|
-
ui:
|
|
490
|
-
- shared-components
|
|
491
|
-
- 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"]
|
|
492
476
|
```
|
|
493
477
|
|
|
494
478
|
### Team Collaboration
|
|
@@ -588,4 +572,5 @@ Built with:
|
|
|
588
572
|
- [Inquirer](https://github.com/SBoudrias/Inquirer.js) - Interactive prompts
|
|
589
573
|
- [Concurrently](https://github.com/open-cli-tools/concurrently) - Process management
|
|
590
574
|
- [Chalk](https://github.com/chalk/chalk) - Terminal styling
|
|
591
|
-
- [
|
|
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;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { inspect } from "util";
|
|
1
2
|
import { Command } from "commander";
|
|
2
3
|
import { configExists, currentConfig, warnOfMissingConfig, } from "../../../utils/config-utils.js";
|
|
3
4
|
export const listConfigCommand = new Command("list")
|
|
4
5
|
.description("display the current configuration settings")
|
|
5
6
|
.action(() => {
|
|
6
7
|
if (configExists) {
|
|
7
|
-
console.log(currentConfig);
|
|
8
|
+
console.log(inspect(currentConfig, { depth: null, colors: true }));
|
|
8
9
|
}
|
|
9
10
|
else {
|
|
10
11
|
warnOfMissingConfig();
|
|
@@ -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) {
|
|
@@ -1,63 +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
|
};
|
|
23
|
-
export const
|
|
24
|
-
|
|
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))) {
|
|
25
43
|
return false;
|
|
26
44
|
}
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
config.groups.all.length > 0;
|
|
39
|
-
if (config.lib_directory && (!config.libs || !Array.isArray(config.libs))) {
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
42
|
-
if (config.mfes && typeof config.mfes === "object") {
|
|
43
|
-
for (const mfeConfig of Object.values(config.mfes)) {
|
|
44
|
-
if (mfeConfig &&
|
|
45
|
-
mfeConfig.modes !== undefined) {
|
|
46
|
-
const modes = mfeConfig.modes;
|
|
47
|
-
if (!Array.isArray(modes))
|
|
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) {
|
|
48
56
|
return false;
|
|
49
|
-
for (const mode of modes) {
|
|
50
|
-
if (!mode ||
|
|
51
|
-
typeof mode !== "object" ||
|
|
52
|
-
!mode.mode_name ||
|
|
53
|
-
!mode.command) {
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
57
|
}
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
|
-
|
|
61
|
+
}
|
|
62
|
+
return true;
|
|
63
|
+
};
|
|
64
|
+
export const isConfigValid = () => {
|
|
65
|
+
if (!configExists) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
const configFile = fs.readFileSync(configPath, "utf8");
|
|
70
|
+
return isParsedConfigValid(parseToml(configFile));
|
|
61
71
|
}
|
|
62
72
|
catch (_a) {
|
|
63
73
|
return false;
|
|
@@ -69,7 +79,7 @@ export const saveConfig = (newConfig) => {
|
|
|
69
79
|
if (!fs.existsSync(configDir)) {
|
|
70
80
|
fs.mkdirSync(configDir, { recursive: true });
|
|
71
81
|
}
|
|
72
|
-
fs.writeFileSync(configPath,
|
|
82
|
+
fs.writeFileSync(configPath, stringifyToml(newConfig));
|
|
73
83
|
}
|
|
74
84
|
catch (error) {
|
|
75
85
|
console.log(`Error writing config file!\n\n${error}`);
|
|
@@ -10,11 +10,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
import * as fs from "fs";
|
|
11
11
|
import * as path from "path";
|
|
12
12
|
import * as os from "os";
|
|
13
|
+
import { fileURLToPath } from "url";
|
|
13
14
|
import chalk from "chalk";
|
|
14
15
|
import { spawnSync } from "child_process";
|
|
15
16
|
const NOTIFIED_VERSION_FILE = path.join(os.homedir(), ".mfer", ".last-notified-version");
|
|
16
17
|
export function getInstalledVersion() {
|
|
17
|
-
const packageJsonPath = path.join(path.dirname(
|
|
18
|
+
const packageJsonPath = path.join(path.dirname(fileURLToPath(import.meta.url)), "../../package.json");
|
|
18
19
|
try {
|
|
19
20
|
const raw = fs.readFileSync(packageJsonPath, "utf8");
|
|
20
21
|
return JSON.parse(raw).version;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mfer",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.1",
|
|
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": [
|