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 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 YAML-based configuration
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 # Run all micro frontends with default command (npm start)
138
- mfer run frontend # Run only frontend group with default command
139
- mfer run --command "npm ci" home # Run custom command sequentially on home group
140
- mfer run -c "yarn install" shared # Run yarn install sequentially on shared group
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 -c "yarn install" -a shared # Run yarn install concurrently on shared group
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 YAML configuration file located at `~/.mfer/config.yaml`. Here's an example structure:
359
-
360
- ```yaml
361
- base_github_url: "https://github.com/your-username"
362
- mfe_directory: "/path/to/your/micro-frontends"
363
- lib_directory: "/path/to/your/internal-libs"
364
- libs:
365
- - my-shared-utils
366
- - my-design-system
367
- - my-common-components
368
- groups:
369
- all:
370
- - my-main-app
371
- - my-admin-panel
372
- - my-shared-components
373
- main:
374
- - my-main-app
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.yaml
439
+ nano ~/.mfer/config.toml
406
440
 
407
441
  # On Windows
408
- notepad %USERPROFILE%\.mfer\config.yaml
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
- ```yaml
429
- groups:
430
- all:
431
- - main-app
432
- - admin-panel
433
- - user-dashboard
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
- Adding configurable custom start commands is something I plan on adding in the near future.
463
- I also welcome anyone to open a PR for that!
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
- - [YAML](https://github.com/eemeli/yaml) - Configuration parsing
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
+ }));
@@ -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 YAML syntax."));
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) {
@@ -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 '${commandToRun}'`
65
- : "default command";
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(selectedMFEs, commandToRun, mfeDir);
89
+ yield runConcurrently(mfeCommands, mfeDir);
69
90
  }
70
91
  else {
71
- yield runSequentially(selectedMFEs, commandToRun, mfeDir);
92
+ yield runSequentially(mfeCommands, mfeDir);
72
93
  }
73
94
  }));
74
- function runSequentially(mfes, command, mfeDir) {
95
+ function runSequentially(mfeCommands, mfeDir) {
75
96
  return __awaiter(this, void 0, void 0, function* () {
76
- for (const mfe of mfes) {
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(mfes, command, mfeDir) {
125
+ function runConcurrently(mfeCommands, mfeDir) {
105
126
  return __awaiter(this, void 0, void 0, function* () {
106
- const commands = mfes.map((mfe) => ({
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("3.2.4", "-v, --version", "mfer CLI 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 YAML from "yaml";
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.yaml");
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 = YAML.parse(configFile);
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
- const config = YAML.parse(configFile);
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, YAML.stringify(newConfig));
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.2.4",
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": [