mfer 4.0.1 → 4.1.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 +30 -1
- package/dist/commands/run.js +12 -1
- package/dist/index.js +1 -0
- package/dist/utils/command-utils.js +16 -0
- package/dist/utils/config-utils.js +46 -3
- package/dist/utils/file-utils.js +11 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -131,6 +131,7 @@ Run micro frontend applications concurrently.
|
|
|
131
131
|
- `-a, --async`: Run custom command concurrently instead of sequentially (only with `--command`)
|
|
132
132
|
- `-m, --mode <mode_name>`: Run MFEs using a named mode from config (see [Per-MFE Run Modes](#per-mfe-run-modes))
|
|
133
133
|
- `-s, --select`: Prompt to select which micro frontends to run
|
|
134
|
+
- `--config <path>`: Use a specific config file path (overrides default config location)
|
|
134
135
|
|
|
135
136
|
> **Note:** `--mode` and `--command` are mutually exclusive.
|
|
136
137
|
|
|
@@ -145,6 +146,7 @@ mfer run --command "npm ci" home # Run custom command sequentially on home grou
|
|
|
145
146
|
mfer run -c "yarn install" shared # Run yarn install sequentially on shared group
|
|
146
147
|
mfer run --command "npm ci" --async home # Run custom command concurrently on home group
|
|
147
148
|
mfer run --command "npm run build" --select # Select MFEs and run build sequentially
|
|
149
|
+
mfer run --config ./mfer.config.toml --select # Use project-local config and select MFEs
|
|
148
150
|
```
|
|
149
151
|
|
|
150
152
|
### `mfer pull [group_name]`
|
|
@@ -359,7 +361,15 @@ mfer lib install my-design-system --select # Select libraries from specific libr
|
|
|
359
361
|
|
|
360
362
|
## ⚙️ Configuration
|
|
361
363
|
|
|
362
|
-
mfer uses a TOML configuration file
|
|
364
|
+
mfer uses a TOML configuration file. Default location: `~/.mfer/config.toml`
|
|
365
|
+
|
|
366
|
+
Override options (highest precedence first):
|
|
367
|
+
|
|
368
|
+
- CLI: `mfer --config ./path/to/config.toml ...`
|
|
369
|
+
- Env: `MFER_CONFIG=./path/to/config.toml`
|
|
370
|
+
- Default: `~/.mfer/config.toml`
|
|
371
|
+
|
|
372
|
+
Here's an example structure:
|
|
363
373
|
|
|
364
374
|
```toml
|
|
365
375
|
base_github_url = "https://github.com/your-username"
|
|
@@ -376,6 +386,11 @@ admin = ["my-admin-panel", "my-shared-components"]
|
|
|
376
386
|
[[mfes.my-main-app.modes]]
|
|
377
387
|
mode_name = "mock"
|
|
378
388
|
command = "npm run start:mocked"
|
|
389
|
+
|
|
390
|
+
# optional lifecycle hooks for `mfer run`
|
|
391
|
+
[hooks]
|
|
392
|
+
pre_run = "echo preparing run for $MFER_GROUP_NAME"
|
|
393
|
+
post_run = "echo completed run for $MFER_GROUP_NAME"
|
|
379
394
|
```
|
|
380
395
|
|
|
381
396
|
> **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.
|
|
@@ -393,6 +408,9 @@ command = "npm run start:mocked"
|
|
|
393
408
|
- **`modes`**: Array of named run modes for the MFE. Each mode has:
|
|
394
409
|
- **`mode_name`**: Name used with `mfer run --mode <name>`
|
|
395
410
|
- **`command`**: The command to run for this MFE when the mode is active
|
|
411
|
+
- **`hooks`**: Optional run lifecycle hooks
|
|
412
|
+
- **`pre_run`**: Command executed before `mfer run` starts MFE processes
|
|
413
|
+
- **`post_run`**: Command executed after `mfer run` finishes successfully
|
|
396
414
|
|
|
397
415
|
### Per-MFE Run Modes
|
|
398
416
|
|
|
@@ -422,6 +440,17 @@ mfer run --mode mock
|
|
|
422
440
|
- 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
441
|
- `--mode` cannot be combined with `--command`; they are mutually exclusive.
|
|
424
442
|
|
|
443
|
+
### Run Lifecycle Hooks
|
|
444
|
+
|
|
445
|
+
When configured, `pre_run` and `post_run` hooks run in the configured `mfe_directory`.
|
|
446
|
+
|
|
447
|
+
Environment variables provided to hooks:
|
|
448
|
+
|
|
449
|
+
- `MFER_HOOK_NAME` (`pre_run` or `post_run`)
|
|
450
|
+
- `MFER_GROUP_NAME` (resolved group name)
|
|
451
|
+
- `MFER_MFE_DIRECTORY` (configured mfe root directory)
|
|
452
|
+
- `MFER_SELECTED_MFES` (comma-separated selected MFEs)
|
|
453
|
+
|
|
425
454
|
### Editing Configuration
|
|
426
455
|
|
|
427
456
|
You can edit your configuration in several ways:
|
package/dist/commands/run.js
CHANGED
|
@@ -12,7 +12,7 @@ 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, resolveRunCommand, } from "../utils/command-utils.js";
|
|
15
|
+
import { promptForMFESelection, resolveRunCommand, runLifecycleHook, } from "../utils/command-utils.js";
|
|
16
16
|
import { spawn } from "child_process";
|
|
17
17
|
const runCommand = new Command("run")
|
|
18
18
|
.description("run micro-frontend applications")
|
|
@@ -22,6 +22,7 @@ const runCommand = new Command("run")
|
|
|
22
22
|
.option("-a, --async", "run custom command concurrently instead of sequentially (only works with --command option)")
|
|
23
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
|
+
var _a, _b;
|
|
25
26
|
if (!configExists) {
|
|
26
27
|
warnOfMissingConfig();
|
|
27
28
|
return;
|
|
@@ -85,12 +86,22 @@ const runCommand = new Command("run")
|
|
|
85
86
|
? `mode '${options.mode}'`
|
|
86
87
|
: "default command";
|
|
87
88
|
console.log(chalk.green(`Running ${commandText} on micro frontends in ${groupText}...`));
|
|
89
|
+
yield runLifecycleHook((_a = currentConfig.hooks) === null || _a === void 0 ? void 0 : _a.pre_run, "pre_run", {
|
|
90
|
+
groupName,
|
|
91
|
+
mfeDirectory: mfeDir,
|
|
92
|
+
selectedMfes: selectedMFEs,
|
|
93
|
+
});
|
|
88
94
|
if (isAsync || !options.command) {
|
|
89
95
|
yield runConcurrently(mfeCommands, mfeDir);
|
|
90
96
|
}
|
|
91
97
|
else {
|
|
92
98
|
yield runSequentially(mfeCommands, mfeDir);
|
|
93
99
|
}
|
|
100
|
+
yield runLifecycleHook((_b = currentConfig.hooks) === null || _b === void 0 ? void 0 : _b.post_run, "post_run", {
|
|
101
|
+
groupName,
|
|
102
|
+
mfeDirectory: mfeDir,
|
|
103
|
+
selectedMfes: selectedMFEs,
|
|
104
|
+
});
|
|
94
105
|
}));
|
|
95
106
|
function runSequentially(mfeCommands, mfeDir) {
|
|
96
107
|
return __awaiter(this, void 0, void 0, function* () {
|
package/dist/index.js
CHANGED
|
@@ -13,6 +13,7 @@ import { checkForUpdateNotification, getInstalledVersion, } from "./utils/versio
|
|
|
13
13
|
program
|
|
14
14
|
.name("mfer")
|
|
15
15
|
.description("Micro Frontend Runner (mfer) - A CLI for running your project's micro frontends.")
|
|
16
|
+
.option("--config <path>", "path to config.toml (overrides ~/.mfer/config.toml)")
|
|
16
17
|
.version(getInstalledVersion(), "-v, --version", "mfer CLI version")
|
|
17
18
|
.hook("preAction", () => {
|
|
18
19
|
console.log();
|
|
@@ -14,6 +14,22 @@ import concurrently from "concurrently";
|
|
|
14
14
|
import path from "path";
|
|
15
15
|
import fs from "fs";
|
|
16
16
|
export const DEFAULT_RUN_COMMAND = "npm start";
|
|
17
|
+
export function runLifecycleHook(hookCommand, hookName, options) {
|
|
18
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
19
|
+
if (!hookCommand) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const result = spawnSync(hookCommand, {
|
|
23
|
+
cwd: options.mfeDirectory,
|
|
24
|
+
env: Object.assign(Object.assign({}, process.env), { MFER_GROUP_NAME: options.groupName, MFER_HOOK_NAME: hookName, MFER_MFE_DIRECTORY: options.mfeDirectory, MFER_SELECTED_MFES: options.selectedMfes.join(",") }),
|
|
25
|
+
shell: true,
|
|
26
|
+
stdio: "inherit",
|
|
27
|
+
});
|
|
28
|
+
if (result.status !== 0) {
|
|
29
|
+
throw new Error(`Hook '${hookName}' failed with exit code ${result.status}`);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
17
33
|
export function resolveRunCommand(mfe, modeName, config) {
|
|
18
34
|
var _a, _b, _c, _d;
|
|
19
35
|
if (!modeName)
|
|
@@ -4,15 +4,44 @@ import * as os from "os";
|
|
|
4
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
|
-
|
|
8
|
-
|
|
7
|
+
import { expandHomePath } from "./file-utils.js";
|
|
8
|
+
const getCliOptionValue = (optionName) => {
|
|
9
|
+
const exactMatchIndex = process.argv.findIndex((arg) => arg === optionName);
|
|
10
|
+
if (exactMatchIndex >= 0) {
|
|
11
|
+
return process.argv[exactMatchIndex + 1];
|
|
12
|
+
}
|
|
13
|
+
const inlineMatch = process.argv.find((arg) => arg.startsWith(`${optionName}=`));
|
|
14
|
+
return inlineMatch === null || inlineMatch === void 0 ? void 0 : inlineMatch.split("=")[1];
|
|
15
|
+
};
|
|
16
|
+
const resolvedConfigPath = () => {
|
|
17
|
+
const pathFromCli = getCliOptionValue("--config");
|
|
18
|
+
const pathFromEnv = process.env.MFER_CONFIG;
|
|
19
|
+
const configPathOverride = pathFromCli || pathFromEnv;
|
|
20
|
+
if (!configPathOverride) {
|
|
21
|
+
return path.join(os.homedir(), ".mfer/config.toml");
|
|
22
|
+
}
|
|
23
|
+
const expandedConfigPath = expandHomePath(configPathOverride);
|
|
24
|
+
return path.isAbsolute(expandedConfigPath)
|
|
25
|
+
? expandedConfigPath
|
|
26
|
+
: path.resolve(expandedConfigPath);
|
|
27
|
+
};
|
|
28
|
+
export const configPath = resolvedConfigPath();
|
|
29
|
+
export const legacyYamlConfigPath = path.join(path.dirname(configPath), "config.yaml");
|
|
9
30
|
export const configExists = fs.existsSync(configPath);
|
|
10
31
|
export const legacyYamlConfigExists = fs.existsSync(legacyYamlConfigPath);
|
|
11
32
|
export let currentConfig;
|
|
33
|
+
const normalizeConfigPaths = (config) => {
|
|
34
|
+
const normalizedConfig = Object.assign(Object.assign({}, config), { mfe_directory: expandHomePath(config.mfe_directory) });
|
|
35
|
+
if (normalizedConfig.lib_directory) {
|
|
36
|
+
normalizedConfig.lib_directory = expandHomePath(normalizedConfig.lib_directory);
|
|
37
|
+
}
|
|
38
|
+
return normalizedConfig;
|
|
39
|
+
};
|
|
12
40
|
export const loadConfig = () => {
|
|
13
41
|
if (configExists) {
|
|
14
42
|
const configFile = fs.readFileSync(configPath, "utf8");
|
|
15
|
-
|
|
43
|
+
const parsedConfig = parseToml(configFile);
|
|
44
|
+
currentConfig = normalizeConfigPaths(parsedConfig);
|
|
16
45
|
return currentConfig;
|
|
17
46
|
}
|
|
18
47
|
return undefined;
|
|
@@ -59,6 +88,20 @@ export const isParsedConfigValid = (parsed) => {
|
|
|
59
88
|
}
|
|
60
89
|
}
|
|
61
90
|
}
|
|
91
|
+
if (config.hooks !== undefined) {
|
|
92
|
+
if (!config.hooks || typeof config.hooks !== "object") {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
const hooksConfig = config.hooks;
|
|
96
|
+
for (const [hookName, hookCommand] of Object.entries(hooksConfig)) {
|
|
97
|
+
if (typeof hookCommand !== "string" || hookCommand.trim() === "") {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
if (!["pre_run", "post_run"].includes(hookName)) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
62
105
|
return true;
|
|
63
106
|
};
|
|
64
107
|
export const isConfigValid = () => {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as os from "os";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
export const expandHomePath = (targetPath) => {
|
|
4
|
+
if (targetPath === "~") {
|
|
5
|
+
return os.homedir();
|
|
6
|
+
}
|
|
7
|
+
if (!targetPath.startsWith("~/")) {
|
|
8
|
+
return targetPath;
|
|
9
|
+
}
|
|
10
|
+
return path.join(os.homedir(), targetPath.slice(2));
|
|
11
|
+
};
|