mfer 3.0.0 → 3.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 CHANGED
@@ -151,11 +151,17 @@ Pull latest changes from git repositories.
151
151
 
152
152
  - `group_name`: Name of the group to pull from (defaults to "all")
153
153
 
154
- **Example:**
154
+ **Options:**
155
+
156
+ - `-s, --select`: Prompt to select which repositories to pull
157
+
158
+ **Examples:**
155
159
 
156
160
  ```bash
157
161
  mfer pull # Pull from all repositories
158
162
  mfer pull shared # Pull from shared components group only
163
+ mfer pull --select # Select repositories to pull interactively
164
+ mfer pull frontend --select # Select repositories from frontend group
159
165
  ```
160
166
 
161
167
  ### `mfer install [group_name]`
@@ -166,11 +172,17 @@ Install dependencies for all micro frontends in a group.
166
172
 
167
173
  - `group_name`: Name of the group to install dependencies for (defaults to "all")
168
174
 
169
- **Example:**
175
+ **Options:**
176
+
177
+ - `-s, --select`: Prompt to select which micro frontends to install
178
+
179
+ **Examples:**
170
180
 
171
181
  ```bash
172
182
  mfer install # Install dependencies for all micro frontends
173
183
  mfer install frontend # Install dependencies for frontend group only
184
+ mfer install --select # Select micro frontends to install interactively
185
+ mfer install shared --select # Select micro frontends from shared group
174
186
  ```
175
187
 
176
188
  ### `mfer clone [group_name]`
@@ -226,6 +238,8 @@ Manage internal npm packages and their distribution to micro frontends.
226
238
  - [`mfer lib deploy`](#mfer-lib-deploy-lib-name) - Copy built libraries to micro frontends
227
239
  - [`mfer lib publish`](#mfer-lib-publish-lib-name) - Build and deploy libraries to micro frontends
228
240
  - [`mfer lib list`](#mfer-lib-list) - List configured libraries and their status
241
+ - [`mfer lib pull`](#mfer-lib-pull-lib-name) - Pull latest changes from library git repositories
242
+ - [`mfer lib install`](#mfer-lib-install-lib-name) - Install dependencies for libraries
229
243
 
230
244
  ### `mfer lib build [lib-name]`
231
245
 
@@ -297,6 +311,48 @@ List configured libraries and their status.
297
311
  mfer lib list # Show all configured libraries and their build status
298
312
  ```
299
313
 
314
+ ### `mfer lib pull [lib-name]`
315
+
316
+ Pull latest changes from library git repositories.
317
+
318
+ **Arguments:**
319
+
320
+ - `lib-name`: Name of the library to pull from (pulls all if not specified)
321
+
322
+ **Options:**
323
+
324
+ - `-s, --select`: Prompt to select which libraries to pull
325
+
326
+ **Examples:**
327
+
328
+ ```bash
329
+ mfer lib pull # Pull from all libraries
330
+ mfer lib pull my-shared-utils # Pull from specific library only
331
+ mfer lib pull --select # Select libraries to pull interactively
332
+ mfer lib pull my-design-system --select # Select libraries from specific library
333
+ ```
334
+
335
+ ### `mfer lib install [lib-name]`
336
+
337
+ Install dependencies for libraries.
338
+
339
+ **Arguments:**
340
+
341
+ - `lib-name`: Name of the library to install dependencies for (installs all if not specified)
342
+
343
+ **Options:**
344
+
345
+ - `-s, --select`: Prompt to select which libraries to install
346
+
347
+ **Examples:**
348
+
349
+ ```bash
350
+ mfer lib install # Install dependencies for all libraries
351
+ mfer lib install my-shared-utils # Install dependencies for specific library only
352
+ mfer lib install --select # Select libraries to install interactively
353
+ mfer lib install my-design-system --select # Select libraries from specific library
354
+ ```
355
+
300
356
  ## ⚙️ Configuration
301
357
 
302
358
  mfer uses a YAML configuration file located at `~/.mfer/config.yaml`. Here's an example structure:
@@ -10,12 +10,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  import { Command } from "commander";
11
11
  import { configExists, currentConfig, warnOfMissingConfig, } from "../utils/config-utils.js";
12
12
  import chalk from "chalk";
13
- import path from "path";
14
- import { spawnSync } from "child_process";
13
+ import { runNpmInstallSequentially, promptForMFESelection, } from "../utils/command-utils.js";
15
14
  const installCommand = new Command("install")
16
15
  .description("run 'npm install' in micro-frontend applications synchronously")
17
16
  .argument("[group_name]", "name of the group as specified in the configuration", "all")
18
- .action((groupName) => __awaiter(void 0, void 0, void 0, function* () {
17
+ .option("-s, --select", "prompt to select which micro frontends to install")
18
+ .action((groupName, options) => __awaiter(void 0, void 0, void 0, function* () {
19
19
  if (!configExists) {
20
20
  warnOfMissingConfig();
21
21
  return;
@@ -32,42 +32,12 @@ const installCommand = new Command("install")
32
32
  console.log(`${messagePrefix}: group '${groupName}' has no micro frontends defined.`);
33
33
  return;
34
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."));
35
+ let selectedMFEs = group;
36
+ if (options.select) {
37
+ selectedMFEs = yield promptForMFESelection(groupName, group);
71
38
  }
39
+ const mfeDir = currentConfig.mfe_directory;
40
+ const contextName = `group: ${groupName}`;
41
+ yield runNpmInstallSequentially(selectedMFEs, mfeDir, contextName, options);
72
42
  }));
73
43
  export default installCommand;
@@ -3,10 +3,14 @@ import buildCommand from "./build.js";
3
3
  import deployCommand from "./deploy.js";
4
4
  import publishCommand from "./publish.js";
5
5
  import listCommand from "./list.js";
6
+ import pullCommand from "./pull.js";
7
+ import installCommand from "./install.js";
6
8
  const libCommand = new Command("lib")
7
9
  .description("Manage internal npm packages and their distribution to micro frontends")
8
10
  .addCommand(buildCommand)
9
11
  .addCommand(deployCommand)
10
12
  .addCommand(publishCommand)
11
- .addCommand(listCommand);
13
+ .addCommand(listCommand)
14
+ .addCommand(pullCommand)
15
+ .addCommand(installCommand);
12
16
  export default libCommand;
@@ -0,0 +1,52 @@
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 { runNpmInstallSequentially, promptForLibrarySelection, } from "../../utils/command-utils.js";
14
+ const installCommand = new Command("install")
15
+ .description("run 'npm install' in library directories")
16
+ .argument("[lib-name]", "name of the library to install dependencies for (installs all if not specified)")
17
+ .option("-s, --select", "prompt to select which libraries to install")
18
+ .action((libName, options) => __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
+ if (!Array.isArray(currentConfig.libs) || currentConfig.libs.length === 0) {
29
+ console.log(chalk.red("Error: No libraries configured in config file."));
30
+ return;
31
+ }
32
+ let targetLibs;
33
+ if (libName) {
34
+ if (!currentConfig.libs.includes(libName)) {
35
+ console.log(chalk.red(`Error: Library '${libName}' not found in configuration.`));
36
+ console.log(`Available libraries: ${chalk.green(currentConfig.libs.join(" "))}`);
37
+ return;
38
+ }
39
+ targetLibs = [libName];
40
+ }
41
+ else {
42
+ targetLibs = currentConfig.libs;
43
+ }
44
+ let selectedLibs = targetLibs;
45
+ if (options.select) {
46
+ selectedLibs = yield promptForLibrarySelection(targetLibs);
47
+ }
48
+ const libDir = currentConfig.lib_directory;
49
+ const contextName = libName ? `library '${libName}'` : "all libraries";
50
+ yield runNpmInstallSequentially(selectedLibs, libDir, contextName, options);
51
+ }));
52
+ export default installCommand;
@@ -0,0 +1,68 @@
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 { validateGitRepositories, runGitPullConcurrently, promptForLibrarySelection, } from "../../utils/command-utils.js";
14
+ const pullCommand = new Command("pull")
15
+ .description("pull latest changes from library git repositories")
16
+ .argument("[lib-name]", "name of the library to pull from (pulls all if not specified)")
17
+ .option("-s, --select", "prompt to select which libraries to pull")
18
+ .action((libName, options) => __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
+ if (!Array.isArray(currentConfig.libs) || currentConfig.libs.length === 0) {
29
+ console.log(chalk.red("Error: No libraries configured in config file."));
30
+ return;
31
+ }
32
+ let targetLibs;
33
+ if (libName) {
34
+ if (!currentConfig.libs.includes(libName)) {
35
+ console.log(chalk.red(`Error: Library '${libName}' not found in configuration.`));
36
+ console.log(`Available libraries: ${chalk.green(currentConfig.libs.join(" "))}`);
37
+ return;
38
+ }
39
+ targetLibs = [libName];
40
+ }
41
+ else {
42
+ targetLibs = currentConfig.libs;
43
+ }
44
+ const libDir = currentConfig.lib_directory;
45
+ console.log(chalk.blue(`Validating library repositories...`));
46
+ const { validRepos, invalidRepos } = validateGitRepositories(targetLibs, libDir);
47
+ if (invalidRepos.length > 0) {
48
+ console.log(chalk.yellow("\nSkipping invalid repositories:"));
49
+ invalidRepos.forEach(({ name, reason }) => {
50
+ console.log(chalk.yellow(` ${name}: ${reason}`));
51
+ });
52
+ console.log();
53
+ }
54
+ if (validRepos.length === 0) {
55
+ console.log(chalk.red("No valid git repositories found to pull from."));
56
+ if (invalidRepos.some((repo) => repo.reason.includes("Directory does not exist"))) {
57
+ console.log(chalk.blue("\nTip: Make sure your libraries are cloned to the configured directory."));
58
+ }
59
+ return;
60
+ }
61
+ let selectedLibs = validRepos;
62
+ if (options.select) {
63
+ selectedLibs = yield promptForLibrarySelection(validRepos);
64
+ }
65
+ const contextName = libName ? `library '${libName}'` : "all libraries";
66
+ yield runGitPullConcurrently(selectedLibs, libDir, contextName, options);
67
+ }));
68
+ export default pullCommand;
@@ -1,14 +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
+ };
1
10
  import { Command } from "commander";
2
11
  import { configExists, currentConfig, warnOfMissingConfig, } from "../utils/config-utils.js";
3
- import concurrently from "concurrently";
4
12
  import chalk from "chalk";
5
- import path from "path";
6
- import fs from "fs";
7
- import { spawnSync } from "child_process";
13
+ import { validateGitRepositories, runGitPullConcurrently, promptForMFESelection, } from "../utils/command-utils.js";
8
14
  const pullCommand = new Command("pull")
9
15
  .description("pull latest changes from git repositories")
10
16
  .argument("[group_name]", "name of the group as specified in the configuration", "all")
11
- .action((groupName) => {
17
+ .option("-s, --select", "prompt to select which repositories to pull")
18
+ .action((groupName, options) => __awaiter(void 0, void 0, void 0, function* () {
12
19
  if (!configExists) {
13
20
  warnOfMissingConfig();
14
21
  return;
@@ -26,32 +33,8 @@ const pullCommand = new Command("pull")
26
33
  return;
27
34
  }
28
35
  const mfeDir = currentConfig.mfe_directory;
29
- const validRepos = [];
30
- const invalidRepos = [];
31
36
  console.log(chalk.blue(`Validating repositories in group: ${groupName}...`));
32
- for (const repo of group) {
33
- const repoPath = path.join(mfeDir, repo);
34
- if (!fs.existsSync(repoPath)) {
35
- invalidRepos.push({
36
- name: repo,
37
- reason: `Directory does not exist: ${repoPath}`,
38
- });
39
- continue;
40
- }
41
- const gitResult = spawnSync("git", ["rev-parse", "--git-dir"], {
42
- cwd: repoPath,
43
- stdio: "pipe",
44
- shell: true,
45
- });
46
- if (gitResult.status !== 0) {
47
- invalidRepos.push({
48
- name: repo,
49
- reason: `Not a git repository: ${repoPath}`,
50
- });
51
- continue;
52
- }
53
- validRepos.push(repo);
54
- }
37
+ const { validRepos, invalidRepos } = validateGitRepositories(group, mfeDir);
55
38
  if (invalidRepos.length > 0) {
56
39
  console.log(chalk.yellow("\nSkipping invalid repositories:"));
57
40
  invalidRepos.forEach(({ name, reason }) => {
@@ -66,44 +49,11 @@ const pullCommand = new Command("pull")
66
49
  }
67
50
  return;
68
51
  }
69
- const commands = validRepos.map((repo) => ({
70
- command: "git pull",
71
- name: repo,
72
- cwd: path.join(mfeDir, repo),
73
- prefixColor: "green",
74
- }));
75
- console.log(chalk.green(`Pulling latest changes for ${validRepos.length} repositories in group: ${groupName}...`));
76
- const concurrentlyResult = concurrently(commands, {
77
- prefix: "{name} |",
78
- killOthersOn: ["failure"],
79
- restartTries: 0,
80
- });
81
- const handleSigint = () => {
82
- console.log(chalk.yellow("\nReceived SIGINT. Stopping all git pull operations..."));
83
- concurrentlyResult.commands.forEach((cmd) => {
84
- if (cmd && typeof cmd.kill === "function") {
85
- cmd.kill();
86
- }
87
- });
88
- process.exit(0);
89
- };
90
- process.once("SIGINT", handleSigint);
91
- concurrentlyResult.result.then(() => {
92
- console.log(chalk.green(`\nSuccessfully pulled latest changes for all repositories in group: ${groupName}`));
93
- }, (err) => {
94
- console.error(chalk.red("One or more repositories failed to pull."));
95
- if (Array.isArray(err)) {
96
- err.forEach((fail) => {
97
- var _a, _b;
98
- const name = ((_a = fail.command) === null || _a === void 0 ? void 0 : _a.name) || "unknown";
99
- const exitCode = fail.exitCode;
100
- const cwd = ((_b = fail.command) === null || _b === void 0 ? void 0 : _b.cwd) || "unknown";
101
- console.error(chalk.yellow(` Repository ${name} failed to pull (cwd: ${cwd}) with exit code ${exitCode}`));
102
- });
103
- }
104
- else if (err && typeof err === "object" && "message" in err) {
105
- console.error(err.message);
106
- }
107
- });
108
- });
52
+ let selectedRepos = validRepos;
53
+ if (options.select) {
54
+ selectedRepos = yield promptForMFESelection(groupName, validRepos);
55
+ }
56
+ const contextName = `group: ${groupName}`;
57
+ yield runGitPullConcurrently(selectedRepos, mfeDir, contextName, options);
58
+ }));
109
59
  export default pullCommand;
@@ -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 { checkbox } from "@inquirer/prompts";
15
+ import { promptForMFESelection } from "../utils/command-utils.js";
16
16
  import { spawn } from "child_process";
17
17
  const DEFAULT_RUN_COMMAND = "npm start";
18
18
  const runCommand = new Command("run")
@@ -20,9 +20,8 @@ const runCommand = new Command("run")
20
20
  .argument("[group_name]", "name of the group as specified in the configuration", "all")
21
21
  .option("-s, --select", "prompt to select which micro frontends to run")
22
22
  .option("-c, --command <command>", "custom command to run (default: npm start)")
23
- .option("-a, --async", "run custom command concurrently instead of sequentially")
23
+ .option("-a, --async", "run custom command concurrently instead of sequentially (only works with --command option)")
24
24
  .action((groupName, options) => __awaiter(void 0, void 0, void 0, function* () {
25
- var _a, _b;
26
25
  if (!configExists) {
27
26
  warnOfMissingConfig();
28
27
  return;
@@ -53,23 +52,7 @@ const runCommand = new Command("run")
53
52
  }
54
53
  let selectedMFEs = group;
55
54
  if (options.select) {
56
- try {
57
- console.log(chalk.blue(`Select micro frontends to run from group '${groupName}':`));
58
- selectedMFEs = yield checkbox({
59
- message: "Choose which micro frontends to run:",
60
- choices: group.map((mfe) => ({ name: mfe, value: mfe })),
61
- validate: (arr) => arr.length > 0 ? true : "Select at least one micro frontend",
62
- });
63
- }
64
- catch (error) {
65
- if (error instanceof Error &&
66
- (((_a = error.message) === null || _a === void 0 ? void 0 : _a.includes("SIGINT")) ||
67
- ((_b = error.message) === null || _b === void 0 ? void 0 : _b.includes("User force closed")))) {
68
- console.log(chalk.yellow("\nReceived SIGINT. Stopping..."));
69
- process.exit(130);
70
- }
71
- throw error;
72
- }
55
+ selectedMFEs = yield promptForMFESelection(groupName, group);
73
56
  }
74
57
  const mfeDir = currentConfig.mfe_directory;
75
58
  const commandToRun = options.command || DEFAULT_RUN_COMMAND;
@@ -80,8 +63,7 @@ const runCommand = new Command("run")
80
63
  const commandText = options.command
81
64
  ? `custom command '${commandToRun}'`
82
65
  : "default command";
83
- const executionMode = isAsync ? "concurrently" : "sequentially";
84
- console.log(chalk.green(`Running ${commandText} on micro frontends in ${groupText} ${executionMode}...`));
66
+ console.log(chalk.green(`Running ${commandText} on micro frontends in ${groupText}...`));
85
67
  if (isAsync || !options.command) {
86
68
  yield runConcurrently(selectedMFEs, commandToRun, mfeDir);
87
69
  }
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ import { loadConfig } from "./utils/config-utils.js";
11
11
  program
12
12
  .name("mfer")
13
13
  .description("Micro Frontend Runner (mfer) - A CLI for running your project's micro frontends.")
14
- .version("3.0.0", "-v, --version", "mfer CLI version")
14
+ .version("3.1.0", "-v, --version", "mfer CLI version")
15
15
  .hook("preAction", () => {
16
16
  console.log();
17
17
  })
@@ -0,0 +1,181 @@
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 chalk from "chalk";
11
+ import { checkbox } from "@inquirer/prompts";
12
+ import { spawnSync } from "child_process";
13
+ import concurrently from "concurrently";
14
+ import path from "path";
15
+ import fs from "fs";
16
+ export function promptForMFESelection(groupName, mfes) {
17
+ return __awaiter(this, void 0, void 0, function* () {
18
+ var _a, _b;
19
+ try {
20
+ console.log(chalk.blue(`Select micro frontends to operate on from group '${groupName}':`));
21
+ const selectedMFEs = yield checkbox({
22
+ message: "Choose which micro frontends to operate on:",
23
+ choices: mfes.map((mfe) => ({ name: mfe, value: mfe })),
24
+ validate: (arr) => arr.length > 0 ? true : "Select at least one micro frontend",
25
+ });
26
+ return selectedMFEs;
27
+ }
28
+ catch (error) {
29
+ if (error instanceof Error &&
30
+ (((_a = error.message) === null || _a === void 0 ? void 0 : _a.includes("SIGINT")) ||
31
+ ((_b = error.message) === null || _b === void 0 ? void 0 : _b.includes("User force closed")))) {
32
+ console.log(chalk.yellow("\nReceived SIGINT. Stopping..."));
33
+ process.exit(130);
34
+ }
35
+ throw error;
36
+ }
37
+ });
38
+ }
39
+ export function promptForLibrarySelection(libs) {
40
+ return __awaiter(this, void 0, void 0, function* () {
41
+ var _a, _b;
42
+ try {
43
+ console.log(chalk.blue("Select libraries to operate on:"));
44
+ const selectedLibs = yield checkbox({
45
+ message: "Choose which libraries to operate on:",
46
+ choices: libs.map((lib) => ({ name: lib, value: lib })),
47
+ validate: (arr) => arr.length > 0 ? true : "Select at least one library",
48
+ });
49
+ return selectedLibs;
50
+ }
51
+ catch (error) {
52
+ if (error instanceof Error &&
53
+ (((_a = error.message) === null || _a === void 0 ? void 0 : _a.includes("SIGINT")) ||
54
+ ((_b = error.message) === null || _b === void 0 ? void 0 : _b.includes("User force closed")))) {
55
+ console.log(chalk.yellow("\nReceived SIGINT. Stopping..."));
56
+ process.exit(130);
57
+ }
58
+ throw error;
59
+ }
60
+ });
61
+ }
62
+ export function validateGitRepositories(repos, baseDir) {
63
+ const validRepos = [];
64
+ const invalidRepos = [];
65
+ for (const repo of repos) {
66
+ const repoPath = path.join(baseDir, repo);
67
+ if (!fs.existsSync(repoPath)) {
68
+ invalidRepos.push({
69
+ name: repo,
70
+ reason: `Directory does not exist: ${repoPath}`,
71
+ });
72
+ continue;
73
+ }
74
+ const gitResult = spawnSync("git", ["rev-parse", "--git-dir"], {
75
+ cwd: repoPath,
76
+ stdio: "pipe",
77
+ shell: true,
78
+ });
79
+ if (gitResult.status !== 0) {
80
+ invalidRepos.push({
81
+ name: repo,
82
+ reason: `Not a git repository: ${repoPath}`,
83
+ });
84
+ continue;
85
+ }
86
+ validRepos.push(repo);
87
+ }
88
+ return { validRepos, invalidRepos };
89
+ }
90
+ export function runGitPullConcurrently(repos, baseDir, contextName, options) {
91
+ return __awaiter(this, void 0, void 0, function* () {
92
+ const commands = repos.map((repo) => ({
93
+ command: "git pull",
94
+ name: repo,
95
+ cwd: path.join(baseDir, repo),
96
+ prefixColor: "green",
97
+ }));
98
+ const repoText = options.select
99
+ ? `selected repositories from ${contextName}`
100
+ : `${repos.length} repositories in ${contextName}`;
101
+ console.log(chalk.green(`Pulling latest changes for ${repoText}...`));
102
+ const concurrentlyResult = concurrently(commands, {
103
+ prefix: "{name} |",
104
+ killOthersOn: ["failure"],
105
+ restartTries: 0,
106
+ });
107
+ const handleSigint = () => {
108
+ console.log(chalk.yellow("\nReceived SIGINT. Stopping all git pull operations..."));
109
+ concurrentlyResult.commands.forEach((cmd) => {
110
+ if (cmd && typeof cmd.kill === "function") {
111
+ cmd.kill();
112
+ }
113
+ });
114
+ process.exit(0);
115
+ };
116
+ process.once("SIGINT", handleSigint);
117
+ return concurrentlyResult.result.then(() => {
118
+ const successText = options.select
119
+ ? `selected repositories from ${contextName}`
120
+ : `all repositories in ${contextName}`;
121
+ console.log(chalk.green(`\nSuccessfully pulled latest changes for ${successText}`));
122
+ }, (err) => {
123
+ console.error(chalk.red("One or more repositories failed to pull."));
124
+ if (Array.isArray(err)) {
125
+ err.forEach((fail) => {
126
+ var _a, _b;
127
+ const name = ((_a = fail.command) === null || _a === void 0 ? void 0 : _a.name) || "unknown";
128
+ const exitCode = fail.exitCode;
129
+ const cwd = ((_b = fail.command) === null || _b === void 0 ? void 0 : _b.cwd) || "unknown";
130
+ console.error(chalk.yellow(` Repository ${name} failed to pull (cwd: ${cwd}) with exit code ${exitCode}`));
131
+ });
132
+ }
133
+ else if (err && typeof err === "object" && "message" in err) {
134
+ console.error(err.message);
135
+ }
136
+ });
137
+ });
138
+ }
139
+ export function runNpmInstallSequentially(items, baseDir, contextName, options) {
140
+ return __awaiter(this, void 0, void 0, function* () {
141
+ let hadError = false;
142
+ const groupText = options.select
143
+ ? `selected items from ${contextName}`
144
+ : `${contextName}`;
145
+ console.log(chalk.green(`Running 'npm install' in ${groupText}`));
146
+ let interrupted = false;
147
+ const handleSigint = () => {
148
+ interrupted = true;
149
+ console.log(chalk.yellow("\nReceived SIGINT. Stopping installs..."));
150
+ };
151
+ process.once("SIGINT", handleSigint);
152
+ for (const item of items) {
153
+ if (interrupted)
154
+ break;
155
+ const cwd = path.join(baseDir, item);
156
+ console.log(chalk.blue(`[${item}] Running 'npm install' in ${cwd}`));
157
+ const result = spawnSync("npm", ["install", "--no-fund"], {
158
+ cwd,
159
+ stdio: "inherit",
160
+ shell: true,
161
+ });
162
+ if (result.status !== 0) {
163
+ hadError = true;
164
+ console.error(chalk.red(` ${item} failed to install (cwd: ${cwd}) with exit code ${result.status}`));
165
+ }
166
+ else {
167
+ console.log(chalk.green(` ${item} installed successfully (cwd: ${cwd})\n`));
168
+ }
169
+ }
170
+ if (hadError) {
171
+ console.error(chalk.red("One or more installs failed."));
172
+ process.exitCode = 1;
173
+ }
174
+ else if (interrupted) {
175
+ process.exitCode = 130;
176
+ }
177
+ else {
178
+ console.log(chalk.green("All installs completed successfully."));
179
+ }
180
+ });
181
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mfer",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
4
4
  "description": "CLI tool designed to sensibly run micro-frontends from the terminal.",
5
5
  "bin": {
6
6
  "mfer": "dist/index.js"
@@ -54,7 +54,7 @@
54
54
  },
55
55
  "dependencies": {
56
56
  "@inquirer/prompts": "^7.5.3",
57
- "chalk": "^5.4.1",
57
+ "chalk": "^5.6.2",
58
58
  "commander": "^14.0.0",
59
59
  "concurrently": "^9.2.0",
60
60
  "yaml": "^2.8.0"