mfer 1.4.2 → 2.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 +16 -4
- package/dist/commands/init.js +83 -3
- package/dist/commands/lib/build.js +81 -0
- package/dist/commands/lib/deploy.js +109 -0
- package/dist/commands/lib/index.js +12 -0
- package/dist/commands/lib/list.js +57 -0
- package/dist/commands/lib/publish.js +142 -0
- package/dist/commands/run.js +96 -35
- package/dist/index.js +3 -1
- package/dist/utils/config-utils.js +6 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# mfer (Micro Frontend Runner)
|
|
2
2
|
|
|
3
|
-
[](https://opensource.org/licenses/MIT)
|
|
3
|
+
[](https://opensource.org/licenses/MIT) [](https://www.npmjs.com/package/mfer)
|
|
4
|
+
|
|
4
5
|
|
|
5
6
|
A powerful CLI tool designed to simplify the management and execution of multiple micro frontend applications. mfer helps developers run, update, and organize their micro frontend projects with minimal configuration and maximum efficiency.
|
|
6
7
|
|
|
@@ -123,11 +124,22 @@ Run micro frontend applications concurrently.
|
|
|
123
124
|
|
|
124
125
|
- `group_name`: Name of the group to run (defaults to "all")
|
|
125
126
|
|
|
126
|
-
**
|
|
127
|
+
**Options:**
|
|
128
|
+
|
|
129
|
+
- `-c, --command <command>`: Custom command to run (default: npm start)
|
|
130
|
+
- `-a, --async`: Run custom command concurrently instead of sequentially
|
|
131
|
+
- `-s, --select`: Prompt to select which micro frontends to run
|
|
132
|
+
|
|
133
|
+
**Examples:**
|
|
127
134
|
|
|
128
135
|
```bash
|
|
129
|
-
mfer run
|
|
130
|
-
mfer run frontend
|
|
136
|
+
mfer run # Run all micro frontends with default command (npm start)
|
|
137
|
+
mfer run frontend # Run only frontend group with default command
|
|
138
|
+
mfer run --command "npm ci" home # Run custom command sequentially on home group
|
|
139
|
+
mfer run -c "yarn install" shared # Run yarn install sequentially on shared group
|
|
140
|
+
mfer run --command "npm ci" --async home # Run custom command concurrently on home group
|
|
141
|
+
mfer run -c "yarn install" -a shared # Run yarn install concurrently on shared group
|
|
142
|
+
mfer run --command "npm run build" --select # Select MFEs and run build command sequentially
|
|
131
143
|
```
|
|
132
144
|
|
|
133
145
|
### `mfer pull [group_name]`
|
package/dist/commands/init.js
CHANGED
|
@@ -12,7 +12,7 @@ import { configExists, isConfigValid, saveConfig, configPath, } from "../utils/c
|
|
|
12
12
|
import chalk from "chalk";
|
|
13
13
|
import * as fs from "fs";
|
|
14
14
|
import { input, confirm, checkbox } from "@inquirer/prompts";
|
|
15
|
-
function createAndSaveConfig(githubUsername, mfeDirectory, allGroup = []) {
|
|
15
|
+
function createAndSaveConfig(githubUsername, mfeDirectory, allGroup = [], libDirectory, libs = []) {
|
|
16
16
|
const repositories = allGroup.length > 0 ? allGroup : ["my_mfe_1", "my_mfe_2"];
|
|
17
17
|
const newConfig = {
|
|
18
18
|
base_github_url: `https://github.com/${githubUsername}`,
|
|
@@ -21,6 +21,10 @@ function createAndSaveConfig(githubUsername, mfeDirectory, allGroup = []) {
|
|
|
21
21
|
all: repositories,
|
|
22
22
|
},
|
|
23
23
|
};
|
|
24
|
+
if (libDirectory && libs.length > 0) {
|
|
25
|
+
newConfig.lib_directory = libDirectory;
|
|
26
|
+
newConfig.libs = libs;
|
|
27
|
+
}
|
|
24
28
|
saveConfig(newConfig);
|
|
25
29
|
const successMessage = allGroup.length > 0
|
|
26
30
|
? "Configuration created successfully!"
|
|
@@ -32,6 +36,10 @@ function createAndSaveConfig(githubUsername, mfeDirectory, allGroup = []) {
|
|
|
32
36
|
console.log(chalk.yellow("\nNote: Placeholder repository names have been added to show proper YAML syntax."));
|
|
33
37
|
console.log(chalk.yellow("Please replace 'my_mfe_1' and 'my_mfe_2' with your actual repository names."));
|
|
34
38
|
}
|
|
39
|
+
if (libDirectory && libs.length > 0) {
|
|
40
|
+
console.log(chalk.green(`\nLibrary configuration added: ${libs.length} librar${libs.length === 1 ? 'y' : 'ies'} configured.`));
|
|
41
|
+
console.log(chalk.yellow("You can now use 'mfer lib build', 'mfer lib deploy', and 'mfer lib publish' commands."));
|
|
42
|
+
}
|
|
35
43
|
}
|
|
36
44
|
function getFoldersFromDirectory(directoryPath) {
|
|
37
45
|
try {
|
|
@@ -98,6 +106,44 @@ function promptForFolderSelection(folders) {
|
|
|
98
106
|
}
|
|
99
107
|
});
|
|
100
108
|
}
|
|
109
|
+
function promptForLibDirectory() {
|
|
110
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
111
|
+
try {
|
|
112
|
+
const useLibs = yield confirm({
|
|
113
|
+
message: "Do you have internal npm packages/libraries that need to be built and deployed to your micro frontends?",
|
|
114
|
+
default: false,
|
|
115
|
+
});
|
|
116
|
+
if (!useLibs) {
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
return yield input({
|
|
120
|
+
message: [
|
|
121
|
+
"Enter the path to the folder containing your internal npm packages/libraries.",
|
|
122
|
+
" (Tip: Drag a folder from your file explorer into this terminal to paste its path)",
|
|
123
|
+
" >>>",
|
|
124
|
+
].join("\n"),
|
|
125
|
+
validate: (val) => val && val.trim() !== "" ? true : "Folder path cannot be empty",
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
function promptForLibSelection(libs) {
|
|
134
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
135
|
+
try {
|
|
136
|
+
return yield checkbox({
|
|
137
|
+
message: "Select which libraries to include in the configuration:",
|
|
138
|
+
choices: libs.map((lib) => ({ name: lib, value: lib })),
|
|
139
|
+
validate: (arr) => (arr.length > 0 ? true : "Select at least one library"),
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
101
147
|
const initCommand = new Command("init")
|
|
102
148
|
.description("setup a new configuration")
|
|
103
149
|
.option("-f, --force", "force re-initialization even if config exists and is valid")
|
|
@@ -138,11 +184,45 @@ const initCommand = new Command("init")
|
|
|
138
184
|
if (interrupted) {
|
|
139
185
|
return;
|
|
140
186
|
}
|
|
141
|
-
|
|
187
|
+
const libDirectory = yield promptForLibDirectory();
|
|
188
|
+
if (interrupted) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
let libs = [];
|
|
192
|
+
if (libDirectory) {
|
|
193
|
+
const availableLibs = getFoldersFromDirectory(libDirectory);
|
|
194
|
+
if (availableLibs.length > 0) {
|
|
195
|
+
libs = yield promptForLibSelection(availableLibs);
|
|
196
|
+
if (interrupted) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
console.log(chalk.yellow("No library directories found in the specified path."));
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
createAndSaveConfig(githubUsername, mfeDirectory, selectedFolders, libDirectory, libs);
|
|
142
205
|
}
|
|
143
206
|
else {
|
|
144
207
|
console.log("Add the names of your micro frontends to the 'groups' section.");
|
|
145
|
-
|
|
208
|
+
const libDirectory = yield promptForLibDirectory();
|
|
209
|
+
if (interrupted) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
let libs = [];
|
|
213
|
+
if (libDirectory) {
|
|
214
|
+
const availableLibs = getFoldersFromDirectory(libDirectory);
|
|
215
|
+
if (availableLibs.length > 0) {
|
|
216
|
+
libs = yield promptForLibSelection(availableLibs);
|
|
217
|
+
if (interrupted) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
console.log(chalk.yellow("No library directories found in the specified path."));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
createAndSaveConfig(githubUsername, mfeDirectory, [], libDirectory, libs);
|
|
146
226
|
}
|
|
147
227
|
}
|
|
148
228
|
catch (error) {
|
|
@@ -0,0 +1,81 @@
|
|
|
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 chalk from "chalk";
|
|
12
|
+
import { spawn } from "child_process";
|
|
13
|
+
import * as fs from "fs";
|
|
14
|
+
import * as path from "path";
|
|
15
|
+
import { currentConfig, configExists, warnOfMissingConfig, } from "../../utils/config-utils.js";
|
|
16
|
+
const buildCommand = new Command("build")
|
|
17
|
+
.description("Build internal npm packages")
|
|
18
|
+
.argument("[lib-name]", "Name of the library to build (builds all if not specified)")
|
|
19
|
+
.action((libName) => __awaiter(void 0, void 0, void 0, function* () {
|
|
20
|
+
if (!configExists) {
|
|
21
|
+
warnOfMissingConfig();
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (!currentConfig.lib_directory || !currentConfig.libs) {
|
|
25
|
+
console.log(chalk.red("Error: Library configuration not found in config file."));
|
|
26
|
+
console.log(chalk.yellow("Please run 'mfer init' to configure library settings."));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const libsToBuild = libName
|
|
30
|
+
? [libName].filter(lib => currentConfig.libs.includes(lib))
|
|
31
|
+
: currentConfig.libs;
|
|
32
|
+
if (libName && !currentConfig.libs.includes(libName)) {
|
|
33
|
+
console.log(chalk.red(`Error: Library '${libName}' not found in configuration.`));
|
|
34
|
+
console.log(chalk.yellow(`Available libraries: ${currentConfig.libs.join(", ")}`));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (libsToBuild.length === 0) {
|
|
38
|
+
console.log(chalk.yellow("No libraries to build."));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
console.log(chalk.blue(`Building ${libsToBuild.length} librar${libsToBuild.length === 1 ? 'y' : 'ies'}...`));
|
|
42
|
+
for (const lib of libsToBuild) {
|
|
43
|
+
const libPath = path.join(currentConfig.lib_directory, lib);
|
|
44
|
+
if (!fs.existsSync(libPath)) {
|
|
45
|
+
console.log(chalk.red(`Error: Library directory not found: ${libPath}`));
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
console.log(chalk.blue(`\nBuilding ${chalk.bold(lib)}...`));
|
|
49
|
+
try {
|
|
50
|
+
yield buildLibrary(libPath, lib);
|
|
51
|
+
console.log(chalk.green(`✓ ${lib} built successfully`));
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
console.log(chalk.red(`✗ Failed to build ${lib}: ${error}`));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
console.log(chalk.green("\nBuild process completed!"));
|
|
58
|
+
}));
|
|
59
|
+
function buildLibrary(libPath, libName) {
|
|
60
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
61
|
+
return new Promise((resolve, reject) => {
|
|
62
|
+
const buildProcess = spawn("npm", ["run", "build"], {
|
|
63
|
+
stdio: "inherit",
|
|
64
|
+
cwd: libPath,
|
|
65
|
+
shell: true,
|
|
66
|
+
});
|
|
67
|
+
buildProcess.on("close", (code) => {
|
|
68
|
+
if (code === 0) {
|
|
69
|
+
resolve();
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
reject(new Error(`Build process exited with code ${code}`));
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
buildProcess.on("error", (error) => {
|
|
76
|
+
reject(error);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
export default buildCommand;
|
|
@@ -0,0 +1,109 @@
|
|
|
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 chalk from "chalk";
|
|
12
|
+
import * as fs from "fs";
|
|
13
|
+
import * as path from "path";
|
|
14
|
+
import { currentConfig, configExists, warnOfMissingConfig, } from "../../utils/config-utils.js";
|
|
15
|
+
const deployCommand = new Command("deploy")
|
|
16
|
+
.description("Copy built libraries to micro frontends")
|
|
17
|
+
.argument("[lib-name]", "Name of the library to deploy (deploys all if not specified)")
|
|
18
|
+
.action((libName) => __awaiter(void 0, void 0, void 0, function* () {
|
|
19
|
+
if (!configExists) {
|
|
20
|
+
warnOfMissingConfig();
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (!currentConfig.lib_directory || !currentConfig.libs) {
|
|
24
|
+
console.log(chalk.red("Error: Library configuration not found in config file."));
|
|
25
|
+
console.log(chalk.yellow("Please run 'mfer init' to configure library settings."));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const libsToDeploy = libName
|
|
29
|
+
? [libName].filter(lib => currentConfig.libs.includes(lib))
|
|
30
|
+
: currentConfig.libs;
|
|
31
|
+
if (libName && !currentConfig.libs.includes(libName)) {
|
|
32
|
+
console.log(chalk.red(`Error: Library '${libName}' not found in configuration.`));
|
|
33
|
+
console.log(chalk.yellow(`Available libraries: ${currentConfig.libs.join(", ")}`));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (libsToDeploy.length === 0) {
|
|
37
|
+
console.log(chalk.yellow("No libraries to deploy."));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
console.log(chalk.blue(`Deploying ${libsToDeploy.length} librar${libsToDeploy.length === 1 ? 'y' : 'ies'}...`));
|
|
41
|
+
const mfeDirectories = currentConfig.groups.all.map(mfe => path.join(currentConfig.mfe_directory, mfe));
|
|
42
|
+
for (const lib of libsToDeploy) {
|
|
43
|
+
const libDistPath = path.join(currentConfig.lib_directory, lib, "dist");
|
|
44
|
+
if (!fs.existsSync(libDistPath)) {
|
|
45
|
+
console.log(chalk.red(`Error: Build directory not found for ${lib}: ${libDistPath}`));
|
|
46
|
+
console.log(chalk.yellow(`Please run 'mfer lib build ${lib}' first.`));
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
console.log(chalk.blue(`\nDeploying ${chalk.bold(lib)}...`));
|
|
50
|
+
let deployedCount = 0;
|
|
51
|
+
for (const mfeDir of mfeDirectories) {
|
|
52
|
+
if (!fs.existsSync(mfeDir)) {
|
|
53
|
+
console.log(chalk.yellow(`Warning: MFE directory not found: ${mfeDir}`));
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const targetPath = path.join(mfeDir, "node_modules", lib);
|
|
57
|
+
if (fs.existsSync(targetPath)) {
|
|
58
|
+
try {
|
|
59
|
+
yield copyLibraryToMfe(libDistPath, targetPath, lib);
|
|
60
|
+
console.log(chalk.green(` ✓ Deployed to ${path.basename(mfeDir)}`));
|
|
61
|
+
deployedCount++;
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
console.log(chalk.red(` ✗ Failed to deploy to ${path.basename(mfeDir)}: ${error}`));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
console.log(chalk.gray(` - Skipped ${path.basename(mfeDir)} (not installed)`));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (deployedCount > 0) {
|
|
72
|
+
console.log(chalk.green(`✓ ${lib} deployed to ${deployedCount} MFE${deployedCount === 1 ? '' : 's'}`));
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
console.log(chalk.yellow(`⚠ ${lib} not deployed (not found in any MFE's node_modules)`));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
console.log(chalk.green("\nDeploy process completed!"));
|
|
79
|
+
}));
|
|
80
|
+
function copyLibraryToMfe(sourcePath, targetPath, libName) {
|
|
81
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
if (fs.existsSync(targetPath)) {
|
|
84
|
+
fs.rmSync(targetPath, { recursive: true, force: true });
|
|
85
|
+
}
|
|
86
|
+
fs.mkdirSync(targetPath, { recursive: true });
|
|
87
|
+
copyDirectoryRecursive(sourcePath, targetPath);
|
|
88
|
+
resolve();
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
function copyDirectoryRecursive(source, target) {
|
|
93
|
+
if (!fs.existsSync(target)) {
|
|
94
|
+
fs.mkdirSync(target, { recursive: true });
|
|
95
|
+
}
|
|
96
|
+
const items = fs.readdirSync(source);
|
|
97
|
+
for (const item of items) {
|
|
98
|
+
const sourcePath = path.join(source, item);
|
|
99
|
+
const targetPath = path.join(target, item);
|
|
100
|
+
const stat = fs.statSync(sourcePath);
|
|
101
|
+
if (stat.isDirectory()) {
|
|
102
|
+
copyDirectoryRecursive(sourcePath, targetPath);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
export default deployCommand;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import buildCommand from "./build.js";
|
|
3
|
+
import deployCommand from "./deploy.js";
|
|
4
|
+
import publishCommand from "./publish.js";
|
|
5
|
+
import listCommand from "./list.js";
|
|
6
|
+
const libCommand = new Command("lib")
|
|
7
|
+
.description("Manage internal npm packages and their distribution to micro frontends")
|
|
8
|
+
.addCommand(buildCommand)
|
|
9
|
+
.addCommand(deployCommand)
|
|
10
|
+
.addCommand(publishCommand)
|
|
11
|
+
.addCommand(listCommand);
|
|
12
|
+
export default libCommand;
|
|
@@ -0,0 +1,57 @@
|
|
|
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 chalk from "chalk";
|
|
12
|
+
import * as fs from "fs";
|
|
13
|
+
import * as path from "path";
|
|
14
|
+
import { currentConfig, configExists, warnOfMissingConfig, } from "../../utils/config-utils.js";
|
|
15
|
+
const listCommand = new Command("list")
|
|
16
|
+
.description("List configured libraries and their status")
|
|
17
|
+
.action(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
18
|
+
if (!configExists) {
|
|
19
|
+
warnOfMissingConfig();
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (!currentConfig.lib_directory || !currentConfig.libs) {
|
|
23
|
+
console.log(chalk.red("Error: Library configuration not found in config file."));
|
|
24
|
+
console.log(chalk.yellow("Please run 'mfer init' to configure library settings."));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
console.log(chalk.blue("Configured Libraries:"));
|
|
28
|
+
console.log(chalk.gray("─".repeat(50)));
|
|
29
|
+
if (currentConfig.libs.length === 0) {
|
|
30
|
+
console.log(chalk.yellow("No libraries configured."));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
for (const lib of currentConfig.libs) {
|
|
34
|
+
const libPath = path.join(currentConfig.lib_directory, lib);
|
|
35
|
+
const distPath = path.join(libPath, "dist");
|
|
36
|
+
const libExists = fs.existsSync(libPath);
|
|
37
|
+
const distExists = fs.existsSync(distPath);
|
|
38
|
+
let status = "";
|
|
39
|
+
if (!libExists) {
|
|
40
|
+
status = chalk.red("✗ Directory not found");
|
|
41
|
+
}
|
|
42
|
+
else if (!distExists) {
|
|
43
|
+
status = chalk.yellow("⚠ Not built");
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
status = chalk.green("✓ Built");
|
|
47
|
+
}
|
|
48
|
+
console.log(`${chalk.bold(lib)}`);
|
|
49
|
+
console.log(` Path: ${libPath}`);
|
|
50
|
+
console.log(` Status: ${status}`);
|
|
51
|
+
console.log("");
|
|
52
|
+
}
|
|
53
|
+
console.log(chalk.gray("─".repeat(50)));
|
|
54
|
+
console.log(chalk.blue(`Library Directory: ${currentConfig.lib_directory}`));
|
|
55
|
+
console.log(chalk.blue(`Total Libraries: ${currentConfig.libs.length}`));
|
|
56
|
+
}));
|
|
57
|
+
export default listCommand;
|
|
@@ -0,0 +1,142 @@
|
|
|
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 chalk from "chalk";
|
|
12
|
+
import { spawn } from "child_process";
|
|
13
|
+
import * as fs from "fs";
|
|
14
|
+
import * as path from "path";
|
|
15
|
+
import { currentConfig, configExists, warnOfMissingConfig, } from "../../utils/config-utils.js";
|
|
16
|
+
const publishCommand = new Command("publish")
|
|
17
|
+
.description("Build and deploy libraries to micro frontends")
|
|
18
|
+
.argument("[lib-name]", "Name of the library to publish (publishes all if not specified)")
|
|
19
|
+
.action((libName) => __awaiter(void 0, void 0, void 0, function* () {
|
|
20
|
+
if (!configExists) {
|
|
21
|
+
warnOfMissingConfig();
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (!currentConfig.lib_directory || !currentConfig.libs) {
|
|
25
|
+
console.log(chalk.red("Error: Library configuration not found in config file."));
|
|
26
|
+
console.log(chalk.yellow("Please run 'mfer init' to configure library settings."));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const libsToPublish = libName
|
|
30
|
+
? [libName].filter(lib => currentConfig.libs.includes(lib))
|
|
31
|
+
: currentConfig.libs;
|
|
32
|
+
if (libName && !currentConfig.libs.includes(libName)) {
|
|
33
|
+
console.log(chalk.red(`Error: Library '${libName}' not found in configuration.`));
|
|
34
|
+
console.log(chalk.yellow(`Available libraries: ${currentConfig.libs.join(", ")}`));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (libsToPublish.length === 0) {
|
|
38
|
+
console.log(chalk.yellow("No libraries to publish."));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
console.log(chalk.blue(`Publishing ${libsToPublish.length} librar${libsToPublish.length === 1 ? 'y' : 'ies'}...`));
|
|
42
|
+
const mfeDirectories = currentConfig.groups.all.map(mfe => path.join(currentConfig.mfe_directory, mfe));
|
|
43
|
+
for (const lib of libsToPublish) {
|
|
44
|
+
const libPath = path.join(currentConfig.lib_directory, lib);
|
|
45
|
+
if (!fs.existsSync(libPath)) {
|
|
46
|
+
console.log(chalk.red(`Error: Library directory not found: ${libPath}`));
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
console.log(chalk.blue(`\nPublishing ${chalk.bold(lib)}...`));
|
|
50
|
+
console.log(chalk.blue(` Building ${lib}...`));
|
|
51
|
+
try {
|
|
52
|
+
yield buildLibrary(libPath, lib);
|
|
53
|
+
console.log(chalk.green(` ✓ ${lib} built successfully`));
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
console.log(chalk.red(` ✗ Failed to build ${lib}: ${error}`));
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
console.log(chalk.blue(` Deploying ${lib}...`));
|
|
60
|
+
const libDistPath = path.join(libPath, "dist");
|
|
61
|
+
let deployedCount = 0;
|
|
62
|
+
for (const mfeDir of mfeDirectories) {
|
|
63
|
+
if (!fs.existsSync(mfeDir)) {
|
|
64
|
+
console.log(chalk.yellow(` Warning: MFE directory not found: ${mfeDir}`));
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const targetPath = path.join(mfeDir, "node_modules", lib);
|
|
68
|
+
if (fs.existsSync(targetPath)) {
|
|
69
|
+
try {
|
|
70
|
+
yield copyLibraryToMfe(libDistPath, targetPath, lib);
|
|
71
|
+
console.log(chalk.green(` ✓ Deployed to ${path.basename(mfeDir)}`));
|
|
72
|
+
deployedCount++;
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
console.log(chalk.red(` ✗ Failed to deploy to ${path.basename(mfeDir)}: ${error}`));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
console.log(chalk.gray(` - Skipped ${path.basename(mfeDir)} (not installed)`));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (deployedCount > 0) {
|
|
83
|
+
console.log(chalk.green(` ✓ ${lib} published to ${deployedCount} MFE${deployedCount === 1 ? '' : 's'}`));
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
console.log(chalk.yellow(` ⚠ ${lib} not deployed (not found in any MFE's node_modules)`));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
console.log(chalk.green("\nPublish process completed!"));
|
|
90
|
+
}));
|
|
91
|
+
function buildLibrary(libPath, libName) {
|
|
92
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
93
|
+
return new Promise((resolve, reject) => {
|
|
94
|
+
const buildProcess = spawn("npm", ["run", "build"], {
|
|
95
|
+
stdio: "inherit",
|
|
96
|
+
cwd: libPath,
|
|
97
|
+
shell: true,
|
|
98
|
+
});
|
|
99
|
+
buildProcess.on("close", (code) => {
|
|
100
|
+
if (code === 0) {
|
|
101
|
+
resolve();
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
reject(new Error(`Build process exited with code ${code}`));
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
buildProcess.on("error", (error) => {
|
|
108
|
+
reject(error);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
function copyLibraryToMfe(sourcePath, targetPath, libName) {
|
|
114
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
115
|
+
return new Promise((resolve, reject) => {
|
|
116
|
+
if (fs.existsSync(targetPath)) {
|
|
117
|
+
fs.rmSync(targetPath, { recursive: true, force: true });
|
|
118
|
+
}
|
|
119
|
+
fs.mkdirSync(targetPath, { recursive: true });
|
|
120
|
+
copyDirectoryRecursive(sourcePath, targetPath);
|
|
121
|
+
resolve();
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
function copyDirectoryRecursive(source, target) {
|
|
126
|
+
if (!fs.existsSync(target)) {
|
|
127
|
+
fs.mkdirSync(target, { recursive: true });
|
|
128
|
+
}
|
|
129
|
+
const items = fs.readdirSync(source);
|
|
130
|
+
for (const item of items) {
|
|
131
|
+
const sourcePath = path.join(source, item);
|
|
132
|
+
const targetPath = path.join(target, item);
|
|
133
|
+
const stat = fs.statSync(sourcePath);
|
|
134
|
+
if (stat.isDirectory()) {
|
|
135
|
+
copyDirectoryRecursive(sourcePath, targetPath);
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
export default publishCommand;
|
package/dist/commands/run.js
CHANGED
|
@@ -13,17 +13,32 @@ import concurrently from "concurrently";
|
|
|
13
13
|
import chalk from "chalk";
|
|
14
14
|
import path from "path";
|
|
15
15
|
import { checkbox } from "@inquirer/prompts";
|
|
16
|
-
|
|
16
|
+
import { spawn } from "child_process";
|
|
17
|
+
const DEFAULT_RUN_COMMAND = "npm start";
|
|
17
18
|
const runCommand = new Command("run")
|
|
18
19
|
.description("run micro-frontend applications")
|
|
19
20
|
.argument("[group_name]", "name of the group as specified in the configuration", "all")
|
|
20
21
|
.option("-s, --select", "prompt to select which micro frontends to run")
|
|
22
|
+
.option("-c, --command <command>", "custom command to run (default: npm start)")
|
|
23
|
+
.option("-a, --async", "run custom command concurrently instead of sequentially")
|
|
21
24
|
.action((groupName, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
22
25
|
var _a, _b;
|
|
23
26
|
if (!configExists) {
|
|
24
27
|
warnOfMissingConfig();
|
|
25
28
|
return;
|
|
26
29
|
}
|
|
30
|
+
if (options.command &&
|
|
31
|
+
typeof options.command === "string" &&
|
|
32
|
+
options.command.trim() === "") {
|
|
33
|
+
const messagePrefix = chalk.red("Error");
|
|
34
|
+
console.log(`${messagePrefix}: custom command cannot be empty`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (options.async && !options.command) {
|
|
38
|
+
const messagePrefix = chalk.red("Error");
|
|
39
|
+
console.log(`${messagePrefix}: --async can only be used with --command option`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
27
42
|
const group = currentConfig.groups[groupName];
|
|
28
43
|
if (!group) {
|
|
29
44
|
const messagePrefix = chalk.red("Error");
|
|
@@ -57,45 +72,91 @@ const runCommand = new Command("run")
|
|
|
57
72
|
}
|
|
58
73
|
}
|
|
59
74
|
const mfeDir = currentConfig.mfe_directory;
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
name: mfe,
|
|
63
|
-
cwd: path.join(mfeDir, mfe),
|
|
64
|
-
prefixColor: "blue",
|
|
65
|
-
}));
|
|
75
|
+
const commandToRun = options.command || DEFAULT_RUN_COMMAND;
|
|
76
|
+
const isAsync = options.async && options.command;
|
|
66
77
|
const groupText = options.select
|
|
67
78
|
? `selected MFEs from group '${groupName}'`
|
|
68
79
|
: `group '${groupName}'`;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
+
const commandText = options.command
|
|
81
|
+
? `custom command '${commandToRun}'`
|
|
82
|
+
: "default command";
|
|
83
|
+
const executionMode = isAsync ? "concurrently" : "sequentially";
|
|
84
|
+
console.log(chalk.green(`Running ${commandText} on micro frontends in ${groupText} ${executionMode}...`));
|
|
85
|
+
if (isAsync || !options.command) {
|
|
86
|
+
yield runConcurrently(selectedMFEs, commandToRun, mfeDir);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
yield runSequentially(selectedMFEs, commandToRun, mfeDir);
|
|
90
|
+
}
|
|
91
|
+
}));
|
|
92
|
+
function runSequentially(mfes, command, mfeDir) {
|
|
93
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
94
|
+
for (const mfe of mfes) {
|
|
95
|
+
const cwd = path.join(mfeDir, mfe);
|
|
96
|
+
console.log(chalk.blue(`\n[${mfe}] Running: ${command}`));
|
|
97
|
+
try {
|
|
98
|
+
const result = yield new Promise((resolve) => {
|
|
99
|
+
const child = spawn(command, [], {
|
|
100
|
+
stdio: "inherit",
|
|
101
|
+
cwd,
|
|
102
|
+
shell: true,
|
|
103
|
+
});
|
|
104
|
+
child.on("close", (exitCode) => {
|
|
105
|
+
resolve({ exitCode });
|
|
106
|
+
});
|
|
107
|
+
child.on("error", (error) => {
|
|
108
|
+
console.error(chalk.red(`[${mfe}] Error: ${error.message}`));
|
|
109
|
+
resolve({ exitCode: 1 });
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
if (result.exitCode !== 0) {
|
|
113
|
+
console.error(chalk.red(`[${mfe}] Command failed with exit code ${result.exitCode}`));
|
|
114
|
+
}
|
|
80
115
|
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
console.error(chalk.red(`[${mfe}] Unexpected error: ${error}`));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
function runConcurrently(mfes, command, mfeDir) {
|
|
123
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
124
|
+
const commands = mfes.map((mfe) => ({
|
|
125
|
+
command,
|
|
126
|
+
name: mfe,
|
|
127
|
+
cwd: path.join(mfeDir, mfe),
|
|
128
|
+
prefixColor: "blue",
|
|
129
|
+
}));
|
|
130
|
+
const concurrentlyResult = concurrently(commands, {
|
|
131
|
+
prefix: "{name} |",
|
|
132
|
+
killOthersOn: ["failure", "success"],
|
|
133
|
+
restartTries: 0,
|
|
81
134
|
});
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
err.forEach((fail) => {
|
|
89
|
-
var _a, _b;
|
|
90
|
-
const name = ((_a = fail.command) === null || _a === void 0 ? void 0 : _a.name) || "unknown";
|
|
91
|
-
const exitCode = fail.exitCode;
|
|
92
|
-
const cwd = ((_b = fail.command) === null || _b === void 0 ? void 0 : _b.cwd) || "unknown";
|
|
93
|
-
console.error(chalk.yellow(` MFE ${name} failed to start (cwd: ${cwd}) with exit code ${exitCode}`));
|
|
135
|
+
const handleSigint = () => {
|
|
136
|
+
console.log(chalk.yellow("\nReceived SIGINT. Stopping all micro frontends..."));
|
|
137
|
+
concurrentlyResult.commands.forEach((cmd) => {
|
|
138
|
+
if (cmd && typeof cmd.kill === "function") {
|
|
139
|
+
cmd.kill();
|
|
140
|
+
}
|
|
94
141
|
});
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
142
|
+
process.exit(0);
|
|
143
|
+
};
|
|
144
|
+
process.once("SIGINT", handleSigint);
|
|
145
|
+
concurrentlyResult.result.then(() => { }, (err) => {
|
|
146
|
+
console.error(chalk.red("One or more micro frontends failed to start."));
|
|
147
|
+
if (Array.isArray(err)) {
|
|
148
|
+
err.forEach((fail) => {
|
|
149
|
+
var _a, _b;
|
|
150
|
+
const name = ((_a = fail.command) === null || _a === void 0 ? void 0 : _a.name) || "unknown";
|
|
151
|
+
const exitCode = fail.exitCode;
|
|
152
|
+
const cwd = ((_b = fail.command) === null || _b === void 0 ? void 0 : _b.cwd) || "unknown";
|
|
153
|
+
console.error(chalk.yellow(` MFE ${name} failed to start (cwd: ${cwd}) with exit code ${exitCode}`));
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
else if (err && typeof err === "object" && "message" in err) {
|
|
157
|
+
console.error(err.message);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
99
160
|
});
|
|
100
|
-
}
|
|
161
|
+
}
|
|
101
162
|
export default runCommand;
|
package/dist/index.js
CHANGED
|
@@ -6,11 +6,12 @@ import runCommand from "./commands/run.js";
|
|
|
6
6
|
import installCommand from "./commands/install.js";
|
|
7
7
|
import cloneCommand from "./commands/clone.js";
|
|
8
8
|
import pullCommand from "./commands/pull.js";
|
|
9
|
+
import libCommand from "./commands/lib/index.js";
|
|
9
10
|
import { loadConfig } from "./utils/config-utils.js";
|
|
10
11
|
program
|
|
11
12
|
.name("mfer")
|
|
12
13
|
.description("Micro Frontend Runner (mfer) - A CLI for running your project's micro frontends.")
|
|
13
|
-
.version("
|
|
14
|
+
.version("2.0.0", "-v, --version", "mfer CLI version")
|
|
14
15
|
.hook("preAction", () => {
|
|
15
16
|
console.log();
|
|
16
17
|
})
|
|
@@ -23,5 +24,6 @@ program.addCommand(runCommand);
|
|
|
23
24
|
program.addCommand(installCommand);
|
|
24
25
|
program.addCommand(cloneCommand);
|
|
25
26
|
program.addCommand(pullCommand);
|
|
27
|
+
program.addCommand(libCommand);
|
|
26
28
|
loadConfig();
|
|
27
29
|
program.parse();
|
|
@@ -27,7 +27,7 @@ export const isConfigValid = () => {
|
|
|
27
27
|
try {
|
|
28
28
|
const configFile = fs.readFileSync(configPath, "utf8");
|
|
29
29
|
const config = YAML.parse(configFile);
|
|
30
|
-
|
|
30
|
+
const hasRequiredFields = config &&
|
|
31
31
|
typeof config === "object" &&
|
|
32
32
|
config.base_github_url &&
|
|
33
33
|
config.mfe_directory &&
|
|
@@ -35,7 +35,11 @@ export const isConfigValid = () => {
|
|
|
35
35
|
typeof config.groups === "object" &&
|
|
36
36
|
config.groups.all &&
|
|
37
37
|
Array.isArray(config.groups.all) &&
|
|
38
|
-
config.groups.all.length > 0
|
|
38
|
+
config.groups.all.length > 0;
|
|
39
|
+
if (config.lib_directory && (!config.libs || !Array.isArray(config.libs))) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
return hasRequiredFields;
|
|
39
43
|
}
|
|
40
44
|
catch (_a) {
|
|
41
45
|
return false;
|