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 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
 
@@ -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 YAML configuration file located at `~/.mfer/config.yaml`. Here's an example structure:
363
-
364
- ```yaml
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:
369
- - my-shared-utils
370
- - my-design-system
371
- - my-common-components
372
- groups:
373
- all:
374
- - my-main-app
375
- - my-admin-panel
376
- - my-shared-components
377
- main:
378
- - my-main-app
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
- ```yaml
411
- groups:
412
- all:
413
- - root-config
414
- - mfe-nav
415
- - mfe-dashboard
416
- mfes:
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.yaml
439
+ nano ~/.mfer/config.toml
451
440
 
452
441
  # On Windows
453
- notepad %USERPROFILE%\.mfer\config.yaml
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
- ```yaml
474
- groups:
475
- all:
476
- - main-app
477
- - admin-panel
478
- - user-dashboard
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
- - [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;
@@ -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
+ }));
@@ -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) {
@@ -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 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
  };
23
- export const isConfigValid = () => {
24
- if (!configExists) {
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
- try {
28
- 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
- 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
- return hasRequiredFields;
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, YAML.stringify(newConfig));
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(new URL(import.meta.url).pathname), "../../package.json");
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.3.0",
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": [