mfer 3.2.4 → 3.3.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
@@ -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]`
@@ -376,6 +380,11 @@ groups:
376
380
  admin:
377
381
  - my-admin-panel
378
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
379
388
  ```
380
389
 
381
390
  ### Configuration Options
@@ -387,6 +396,42 @@ groups:
387
396
  - **`groups`**: Named collections of micro frontend projects
388
397
  - **`all`**: Default group containing all projects (required)
389
398
  - **Custom groups**: Any additional groups you want to create
399
+ - **`mfes`**: Per-MFE configuration (optional). Each key is an MFE name matching one in `groups`.
400
+ - **`modes`**: Array of named run modes for the MFE. Each mode has:
401
+ - **`mode_name`**: Name used with `mfer run --mode <name>`
402
+ - **`command`**: The command to run for this MFE when the mode is active
403
+
404
+ ### Per-MFE Run Modes
405
+
406
+ 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.
407
+
408
+ **Config example** — `root-config` runs with a mocked API, everything else runs normally:
409
+
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
421
+ ```
422
+
423
+ **Usage:**
424
+
425
+ ```bash
426
+ mfer run --mode mock
427
+ # root-config → npm run start:mocked
428
+ # mfe-nav → npm start (no mock mode defined, falls back to default)
429
+ # mfe-dashboard → npm start
430
+ ```
431
+
432
+ - MFEs that **do not** have the mode defined silently fall back to `npm start`.
433
+ - If **no MFE** in the group has the requested mode, a warning is printed but the command still runs (all MFEs use `npm start`).
434
+ - `--mode` cannot be combined with `--command`; they are mutually exclusive.
390
435
 
391
436
  ### Editing Configuration
392
437
 
@@ -456,11 +501,10 @@ groups:
456
501
 
457
502
  ### Custom Start Commands
458
503
 
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.
504
+ By default, mfer runs `npm start` in each project directory. You can customize this in two ways:
461
505
 
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!
506
+ - **Same command for all MFEs**: use `--command` (e.g. `mfer run --command "yarn start"`)
507
+ - **Per-MFE commands via modes**: define `mfes[name].modes` in config and use `--mode` (see [Per-MFE Run Modes](#per-mfe-run-modes))
464
508
 
465
509
  ### Environment Variables
466
510
 
@@ -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;
@@ -39,6 +39,24 @@ export const isConfigValid = () => {
39
39
  if (config.lib_directory && (!config.libs || !Array.isArray(config.libs))) {
40
40
  return false;
41
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))
48
+ 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
+ }
58
+ }
59
+ }
42
60
  return hasRequiredFields;
43
61
  }
44
62
  catch (_a) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mfer",
3
- "version": "3.2.4",
3
+ "version": "3.3.0",
4
4
  "description": "CLI tool designed to sensibly run micro-frontends from the terminal.",
5
5
  "bin": {
6
6
  "mfer": "dist/index.js"