mfer 0.0.0-alpha.1 → 0.0.0-alpha.3

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
@@ -1,19 +1,25 @@
1
- # Micro Frontend Runner (mfer)
1
+ # mfer (Micro-frontend Runner)
2
2
 
3
3
  A CLI tool that helps manage and run multiple micro frontends.
4
4
 
5
- ## Development
5
+ Created with:
6
+ - inquirer, for prompting
7
+ - commander, for cli
8
+ - chalk, for colors
9
+ - concurrently, for running commands
6
10
 
7
- ### Getting Started
11
+ ```
12
+ Usage: mfer [options] [command]
8
13
 
9
- 1. Run `npm i` to install dependencies.
10
- 2. Run `npm run build` to build the project.
11
- 3. Run `npm install -g .` to link the project globally.
12
- 4. Now you can run the CLI as `mfer` for locally development anywhere on your system. Try `mfer --help` to begin.
13
- 5. Updating the code will require another `npm run build`.
14
+ Micro Frontend Runner (mfer) - A CLI for running your project's micro frontends.
14
15
 
15
- ### Troubleshooting
16
+ Options:
17
+ -v, --version mfer CLI version
18
+ -h, --help display help for command
16
19
 
17
- If you run into problems first check `npm list -g` if the project is successfully linked globally.
18
-
19
- Sometimes you may need to run `npm uninstall -g run-mfs` and then follow up with another `npm install -g .` within the project's root directory.
20
+ Commands:
21
+ config configuration settings
22
+ init setup a new configuration
23
+ run [group_name] run micro-frontend applications
24
+ help [command] display help for command
25
+ ```
@@ -1,24 +1,21 @@
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
1
  import { Command } from "commander";
11
- import YAML from "yaml";
12
- import { configExists, currentConfig, editConfigAsync, saveConfig, warnOfMissingConfig, } from "../../../utils/config-utils.js";
2
+ import { configExists, configPath, warnOfMissingConfig } from "../../../utils/config-utils.js";
3
+ import chalk from "chalk";
4
+ import { spawn } from "child_process";
5
+ import * as os from "os";
13
6
  export const editConfigCommand = new Command("edit")
14
- .description("edit configuration")
15
- .action(() => __awaiter(void 0, void 0, void 0, function* () {
16
- if (configExists) {
17
- const answer = yield editConfigAsync("edit configuration file, this will overwrite your existing configuration", currentConfig);
18
- const newConfig = YAML.parse(answer);
19
- saveConfig(newConfig);
20
- }
21
- else {
7
+ .description("edit configuration in your preferred editor")
8
+ .action(() => {
9
+ if (!configExists) {
22
10
  warnOfMissingConfig();
11
+ return;
23
12
  }
24
- }));
13
+ const editor = process.env.EDITOR || process.env.VISUAL || (os.platform() === "win32" ? "notepad" : "vi");
14
+ console.log(chalk.green(`Opening config file in editor: ${editor}\n`));
15
+ spawn(editor, [configPath], {
16
+ stdio: "ignore",
17
+ detached: true,
18
+ shell: true
19
+ }).unref();
20
+ process.exit(0);
21
+ });
@@ -1,7 +1,12 @@
1
1
  import { Command } from "commander";
2
- import { currentConfig } from "../../../utils/config-utils.js";
2
+ import { configExists, currentConfig, warnOfMissingConfig, } from "../../../utils/config-utils.js";
3
3
  export const listConfigCommand = new Command("list")
4
4
  .description("display the current configuration settings")
5
5
  .action(() => {
6
- console.log(currentConfig);
6
+ if (configExists) {
7
+ console.log(currentConfig);
8
+ }
9
+ else {
10
+ warnOfMissingConfig();
11
+ }
7
12
  });
@@ -8,9 +8,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { Command } from "commander";
11
- import { configExists, editConfigAsync, saveConfig, } from "../utils/config-utils.js";
12
- import YAML from "yaml";
11
+ import { configExists, editConfig, } from "../utils/config-utils.js";
12
+ import chalk from "chalk";
13
+ import * as fs from "fs";
14
+ import { input, confirm, checkbox } from "@inquirer/prompts";
13
15
  const templateConfig = {
16
+ base_github_url: "https://github.com/your-username",
14
17
  mfe_directory: "path/to/folder/containing/microfrontends",
15
18
  groups: {
16
19
  all: ["repo_name_1", "repo_name_2", "repo_name_3"],
@@ -21,12 +24,53 @@ const initCommand = new Command("init")
21
24
  .description("setup a new configuration")
22
25
  .action((options) => __awaiter(void 0, void 0, void 0, function* () {
23
26
  if (!configExists) {
24
- const answer = yield editConfigAsync("create a new configuration file", templateConfig);
25
- const newConfig = YAML.parse(answer);
26
- saveConfig(newConfig);
27
+ const usesGithub = yield confirm({
28
+ message: "Do you use GitHub to host your repositories?",
29
+ default: true,
30
+ });
31
+ if (!usesGithub) {
32
+ console.log(chalk.red("Currently, only GitHub is supported for repository hosting. Other providers are not yet supported."));
33
+ return;
34
+ }
35
+ const githubUsername = yield input({
36
+ message: "What is your GitHub username?",
37
+ validate: (val) => val && val.trim() !== "" ? true : "Username cannot be empty"
38
+ });
39
+ const mfeDirectory = yield input({
40
+ message: [
41
+ "Enter the path to the folder containing all your micro frontends.",
42
+ " (Tip: Drag a folder from your file explorer into this terminal to paste its path)",
43
+ " >>>"
44
+ ].join("\n"),
45
+ validate: (val) => val && val.trim() !== "" ? true : "Folder path cannot be empty"
46
+ });
47
+ let allGroup = [];
48
+ let promptMessage = "Add the names of your micro frontends to the 'groups' section.";
49
+ try {
50
+ if (fs.existsSync(mfeDirectory) && fs.statSync(mfeDirectory).isDirectory()) {
51
+ const entries = fs.readdirSync(mfeDirectory, { withFileTypes: true });
52
+ const folders = entries.filter(e => e.isDirectory()).map(e => e.name);
53
+ if (folders.length > 0) {
54
+ allGroup = yield checkbox({
55
+ message: "Select which folders to include in the default 'all' group:",
56
+ choices: folders.map(f => ({ name: f, value: f })),
57
+ validate: (arr) => arr.length > 0 ? true : "Select at least one folder"
58
+ });
59
+ promptMessage = "";
60
+ }
61
+ }
62
+ }
63
+ catch (e) {
64
+ }
65
+ if (promptMessage) {
66
+ console.log(promptMessage);
67
+ editConfig();
68
+ }
27
69
  }
28
70
  else {
29
- console.log("config already exists, you can edit it with 'mfer config edit'");
71
+ const messagePrefix = chalk.red("Error");
72
+ const mferCommandHint = chalk.blue("mfer config edit");
73
+ console.log(`${messagePrefix}: config already exists, you can edit it with ${mferCommandHint}`);
30
74
  }
31
75
  }));
32
76
  export default initCommand;
@@ -0,0 +1,73 @@
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 { configExists, currentConfig, warnOfMissingConfig, } from "../utils/config-utils.js";
12
+ import chalk from "chalk";
13
+ import path from "path";
14
+ import { spawnSync } from "child_process";
15
+ const installCommand = new Command("install")
16
+ .description("run 'npm install' in micro-frontend applications synchronously")
17
+ .argument("[group_name]", "name of the group as specified in the configuration", "all")
18
+ .action((groupName) => __awaiter(void 0, void 0, void 0, function* () {
19
+ if (!configExists) {
20
+ warnOfMissingConfig();
21
+ return;
22
+ }
23
+ const group = currentConfig.groups[groupName];
24
+ if (!group) {
25
+ const messagePrefix = chalk.red("Error");
26
+ console.log(`${messagePrefix}: no group found with name '${groupName}'`);
27
+ console.log(`Available groups: ${chalk.green(Object.keys(currentConfig.groups).join(" "))}`);
28
+ return;
29
+ }
30
+ if (!Array.isArray(group) || group.length === 0) {
31
+ const messagePrefix = chalk.red("Error");
32
+ console.log(`${messagePrefix}: group '${groupName}' has no micro frontends defined.`);
33
+ return;
34
+ }
35
+ const mfeDir = currentConfig.mfe_directory;
36
+ let hadError = false;
37
+ console.log(chalk.green(`Running 'npm install' in group: ${groupName}`));
38
+ let interrupted = false;
39
+ const handleSigint = () => {
40
+ interrupted = true;
41
+ console.log(chalk.yellow("\nReceived SIGINT. Stopping installs..."));
42
+ };
43
+ process.once('SIGINT', handleSigint);
44
+ for (const mfe of group) {
45
+ if (interrupted)
46
+ break;
47
+ const cwd = path.join(mfeDir, mfe);
48
+ console.log(chalk.blue(`[${mfe}] Running 'npm install' in ${cwd}`));
49
+ const result = spawnSync("npm", ["install"], {
50
+ cwd,
51
+ stdio: "inherit",
52
+ shell: true
53
+ });
54
+ if (result.status !== 0) {
55
+ hadError = true;
56
+ console.error(chalk.red(` MFE ${mfe} failed to install (cwd: ${cwd}) with exit code ${result.status}`));
57
+ }
58
+ else {
59
+ console.log(chalk.green(` MFE ${mfe} installed successfully (cwd: ${cwd})`));
60
+ }
61
+ }
62
+ if (hadError) {
63
+ console.error(chalk.red("One or more installs failed."));
64
+ process.exitCode = 1;
65
+ }
66
+ else if (interrupted) {
67
+ process.exitCode = 130;
68
+ }
69
+ else {
70
+ console.log(chalk.green("All installs completed successfully."));
71
+ }
72
+ }));
73
+ export default installCommand;
@@ -1,15 +1,65 @@
1
1
  import { Command } from "commander";
2
2
  import { configExists, currentConfig, warnOfMissingConfig, } from "../utils/config-utils.js";
3
+ import concurrently from "concurrently";
4
+ import chalk from "chalk";
5
+ import path from "path";
3
6
  const runCommand = new Command("run")
4
7
  .description("run micro-frontend applications")
5
8
  .argument("[group_name]", "name of the group as specified in the configuration", "all")
6
- .action((options) => {
9
+ .action((groupName) => {
7
10
  if (!configExists) {
8
11
  warnOfMissingConfig();
9
12
  return;
10
13
  }
11
- console.log("options", options);
12
- const runGroup = currentConfig.groups[options];
13
- console.log("Running the following micro frontends:\n", runGroup);
14
+ const group = currentConfig.groups[groupName];
15
+ if (!group) {
16
+ const messagePrefix = chalk.red("Error");
17
+ console.log(`${messagePrefix}: no group found with name '${groupName}'`);
18
+ console.log(`Available groups: ${chalk.green(Object.keys(currentConfig.groups).join(" "))}`);
19
+ return;
20
+ }
21
+ if (!Array.isArray(group) || group.length === 0) {
22
+ const messagePrefix = chalk.red("Error");
23
+ console.log(`${messagePrefix}: group '${groupName}' has no micro frontends defined.`);
24
+ return;
25
+ }
26
+ const mfeDir = currentConfig.mfe_directory;
27
+ const commands = group.map((mfe) => ({
28
+ command: "npm start",
29
+ name: mfe,
30
+ cwd: path.join(mfeDir, mfe),
31
+ prefixColor: "blue"
32
+ }));
33
+ console.log(chalk.green(`Running micro frontends in group: ${groupName}...`));
34
+ const concurrentlyResult = concurrently(commands, {
35
+ prefix: "{name} |",
36
+ killOthersOn: ["failure", "success"],
37
+ restartTries: 0,
38
+ });
39
+ const handleSigint = () => {
40
+ console.log(chalk.yellow("\nReceived SIGINT. Stopping all micro frontends..."));
41
+ concurrentlyResult.commands.forEach(cmd => {
42
+ if (cmd && typeof cmd.kill === 'function') {
43
+ cmd.kill();
44
+ }
45
+ });
46
+ process.exit(0);
47
+ };
48
+ process.once('SIGINT', handleSigint);
49
+ concurrentlyResult.result.then(() => { }, (err) => {
50
+ console.error(chalk.red("One or more micro frontends failed to start."));
51
+ if (Array.isArray(err)) {
52
+ err.forEach((fail) => {
53
+ var _a, _b;
54
+ const name = ((_a = fail.command) === null || _a === void 0 ? void 0 : _a.name) || "unknown";
55
+ const exitCode = fail.exitCode;
56
+ const cwd = ((_b = fail.command) === null || _b === void 0 ? void 0 : _b.cwd) || "unknown";
57
+ console.error(chalk.yellow(` MFE ${name} failed to start (cwd: ${cwd}) with exit code ${exitCode}`));
58
+ });
59
+ }
60
+ else if (err && err.message) {
61
+ console.error(err.message);
62
+ }
63
+ });
14
64
  });
15
65
  export default runCommand;
package/dist/index.js CHANGED
@@ -3,11 +3,12 @@ import { program } from "commander";
3
3
  import configCommand from "./commands/config/index.js";
4
4
  import initCommand from "./commands/init.js";
5
5
  import runCommand from "./commands/run.js";
6
+ import installCommand from "./commands/install.js";
6
7
  import { loadConfig } from "./utils/config-utils.js";
7
8
  program
8
9
  .name("mfer")
9
10
  .description("Micro Frontend Runner (mfer) - A CLI for running your project's micro frontends.")
10
- .version("0.0.0-alpha.1", "-v, --version", "mfer CLI version")
11
+ .version("0.0.0-alpha.2", "-v, --version", "mfer CLI version")
11
12
  .hook("preAction", (thisCommand, actionCommand) => {
12
13
  console.log();
13
14
  })
@@ -17,5 +18,6 @@ program
17
18
  program.addCommand(configCommand);
18
19
  program.addCommand(initCommand);
19
20
  program.addCommand(runCommand);
21
+ program.addCommand(installCommand);
20
22
  loadConfig();
21
23
  program.parse();
@@ -1,18 +1,9 @@
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
1
  import * as fs from "fs";
11
2
  import * as path from "path";
12
3
  import * as os from "os";
13
4
  import YAML from "yaml";
14
5
  import chalk from "chalk";
15
- import { editor } from "@inquirer/prompts";
6
+ import { spawn } from "child_process";
16
7
  export const configPath = path.join(os.homedir(), ".mfer/config.yaml");
17
8
  export const configExists = fs.existsSync(configPath);
18
9
  export let currentConfig;
@@ -24,7 +15,7 @@ export const loadConfig = () => {
24
15
  };
25
16
  export const warnOfMissingConfig = () => {
26
17
  if (!configExists) {
27
- console.log(`${chalk.red("Error")}: No configuration file detected.\n Please run ${chalk.blue.bold("mfer init")} to create one.`);
18
+ console.log(`${chalk.red("Error")}: No configuration file detected\n Please run ${chalk.blue.bold("mfer init")} to create one`);
28
19
  }
29
20
  };
30
21
  export const saveConfig = (newConfig) => {
@@ -39,12 +30,12 @@ export const saveConfig = (newConfig) => {
39
30
  console.log(`Error writing config file!\n\n${e}`);
40
31
  }
41
32
  };
42
- export const editConfigAsync = (message, config) => __awaiter(void 0, void 0, void 0, function* () {
43
- const prefixMessage = "# This file is whitespace sensitive. Tabs are two spaces, and file must be valid YAML.";
44
- const answer = yield editor({
45
- message,
46
- default: `${prefixMessage}\n${YAML.stringify(config)}`,
47
- postfix: ".yaml",
48
- });
49
- return answer;
50
- });
33
+ export const editConfig = () => {
34
+ const editor = process.env.EDITOR || process.env.VISUAL || (os.platform() === "win32" ? "notepad" : "vi");
35
+ console.log(chalk.green(`Opening config file in editor: ${editor}\n`));
36
+ spawn(editor, [configPath], {
37
+ stdio: "ignore",
38
+ detached: true,
39
+ shell: true
40
+ }).unref();
41
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mfer",
3
- "version": "0.0.0-alpha.1",
3
+ "version": "0.0.0-alpha.3",
4
4
  "description": "CLI tool designed to sensibly run micro-frontends from the terminal.",
5
5
  "bin": {
6
6
  "mfer": "dist/index.js"
@@ -13,6 +13,11 @@
13
13
  },
14
14
  "keywords": [
15
15
  "micro frontends",
16
+ "micro frontend",
17
+ "microfrontend",
18
+ "micro-frontend",
19
+ "micro-frontends",
20
+ "mfe",
16
21
  "CLI",
17
22
  "runner"
18
23
  ],