mfer 1.0.1 → 1.0.2
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 +64 -5
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/commands/config/add-config.js +0 -1
- package/dist/commands/config/config.js +0 -16
- package/dist/commands/config/sub-commands/add-config.js +0 -8
- package/dist/commands/config.js +0 -16
- package/dist/utils/__tests__/config-utils.test.js +0 -250
package/README.md
CHANGED
|
@@ -4,6 +4,29 @@
|
|
|
4
4
|
|
|
5
5
|
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
6
|
|
|
7
|
+
## 📋 Table of Contents
|
|
8
|
+
|
|
9
|
+
- [🚀 Features](#-features)
|
|
10
|
+
- [📦 Installation](#-installation)
|
|
11
|
+
- [🛠️ Quick Start](#️-quick-start)
|
|
12
|
+
- [📋 Commands](#-commands)
|
|
13
|
+
- [mfer init](#mfer-init)
|
|
14
|
+
- [mfer run](#mfer-run)
|
|
15
|
+
- [mfer pull](#mfer-pull)
|
|
16
|
+
- [mfer install](#mfer-install)
|
|
17
|
+
- [mfer clone](#mfer-clone)
|
|
18
|
+
- [mfer config](#mfer-config)
|
|
19
|
+
- [mfer config list](#mfer-config-list)
|
|
20
|
+
- [mfer config edit](#mfer-config-edit)
|
|
21
|
+
- [mfer help](#mfer-help)
|
|
22
|
+
- [⚙️ Configuration](#️-configuration)
|
|
23
|
+
- [🎯 Use Cases](#-use-cases)
|
|
24
|
+
- [🔧 Advanced Usage](#-advanced-usage)
|
|
25
|
+
- [🐛 Troubleshooting](#-troubleshooting)
|
|
26
|
+
- [🤝 Contributing](#-contributing)
|
|
27
|
+
- [📄 License](#-license)
|
|
28
|
+
- [🙏 Acknowledgments](#-acknowledgments)
|
|
29
|
+
|
|
7
30
|
## 🚀 Features
|
|
8
31
|
|
|
9
32
|
- **Concurrent Execution**: Run multiple micro frontends simultaneously with organized output
|
|
@@ -105,18 +128,52 @@ mfer pull # Pull from all repositories
|
|
|
105
128
|
mfer pull shared # Pull from shared components group only
|
|
106
129
|
```
|
|
107
130
|
|
|
131
|
+
### `mfer install [group_name]`
|
|
132
|
+
Install dependencies for all micro frontends in a group.
|
|
133
|
+
|
|
134
|
+
**Arguments:**
|
|
135
|
+
- `group_name`: Name of the group to install dependencies for (defaults to "all")
|
|
136
|
+
|
|
137
|
+
**Example:**
|
|
138
|
+
```bash
|
|
139
|
+
mfer install # Install dependencies for all micro frontends
|
|
140
|
+
mfer install frontend # Install dependencies for frontend group only
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### `mfer clone [group_name]`
|
|
144
|
+
Clone repositories that don't exist locally.
|
|
145
|
+
|
|
146
|
+
**Arguments:**
|
|
147
|
+
- `group_name`: Name of the group to clone repositories from (defaults to "all")
|
|
148
|
+
|
|
149
|
+
**Example:**
|
|
150
|
+
```bash
|
|
151
|
+
mfer clone # Clone all repositories
|
|
152
|
+
mfer clone shared # Clone repositories in shared group only
|
|
153
|
+
```
|
|
154
|
+
|
|
108
155
|
### `mfer config`
|
|
109
156
|
Manage your configuration settings.
|
|
110
157
|
|
|
111
158
|
**Subcommands:**
|
|
112
|
-
- `mfer config edit`: Open configuration file in your default editor
|
|
113
159
|
- `mfer config list`: Display current configuration
|
|
160
|
+
- `mfer config edit`: Open configuration file in your default editor
|
|
114
161
|
|
|
115
|
-
|
|
116
|
-
|
|
162
|
+
**Example:**
|
|
163
|
+
```bash
|
|
164
|
+
mfer config list # Show current configuration
|
|
165
|
+
mfer config edit # Edit configuration in your editor
|
|
166
|
+
```
|
|
117
167
|
|
|
118
|
-
### `mfer
|
|
119
|
-
|
|
168
|
+
### `mfer help`
|
|
169
|
+
Display help information for mfer commands.
|
|
170
|
+
|
|
171
|
+
**Example:**
|
|
172
|
+
```bash
|
|
173
|
+
mfer help # Show general help
|
|
174
|
+
mfer help run # Show help for run command
|
|
175
|
+
mfer help config # Show help for config command
|
|
176
|
+
```
|
|
120
177
|
|
|
121
178
|
## ⚙️ Configuration
|
|
122
179
|
|
|
@@ -257,6 +314,8 @@ npm run build
|
|
|
257
314
|
npm install -g .
|
|
258
315
|
```
|
|
259
316
|
|
|
317
|
+
Refer to [local development](./docs/local-development.md) docs for more information.
|
|
318
|
+
|
|
260
319
|
## 🤝 Contributing
|
|
261
320
|
|
|
262
321
|
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import { loadConfig } from "./utils/config-utils.js";
|
|
|
10
10
|
program
|
|
11
11
|
.name("mfer")
|
|
12
12
|
.description("Micro Frontend Runner (mfer) - A CLI for running your project's micro frontends.")
|
|
13
|
-
.version("1.0.
|
|
13
|
+
.version("1.0.2", "-v, --version", "mfer CLI version")
|
|
14
14
|
.hook("preAction", (thisCommand, actionCommand) => {
|
|
15
15
|
console.log();
|
|
16
16
|
})
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { currentConfig } from "../../utils/config-utils.js";
|
|
3
|
-
const configCommand = new Command("config")
|
|
4
|
-
.description("prism configuration settings")
|
|
5
|
-
.option("-l, --list", "list the current configuration")
|
|
6
|
-
.action((options) => {
|
|
7
|
-
const { list, path } = options;
|
|
8
|
-
if (list) {
|
|
9
|
-
console.log(`\nCurrent configuration:\n`);
|
|
10
|
-
console.log(currentConfig);
|
|
11
|
-
console.log("");
|
|
12
|
-
}
|
|
13
|
-
if (path) {
|
|
14
|
-
}
|
|
15
|
-
});
|
|
16
|
-
export default configCommand;
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { currentConfig } from "../../../utils/config-utils.js";
|
|
3
|
-
export const listConfigCommand = new Command("list")
|
|
4
|
-
.description("display the current configuration settings")
|
|
5
|
-
.action(() => {
|
|
6
|
-
console.log("current configuration!");
|
|
7
|
-
console.log(currentConfig);
|
|
8
|
-
});
|
package/dist/commands/config.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { currentConfig } from "../utils/config-utils.js";
|
|
3
|
-
const configCommand = new Command("config")
|
|
4
|
-
.description("prism configuration settings")
|
|
5
|
-
.option("-l, --list", "list the current configuration")
|
|
6
|
-
.action((options) => {
|
|
7
|
-
const { list, path } = options;
|
|
8
|
-
if (list) {
|
|
9
|
-
console.log(`\nCurrent configuration:\n`);
|
|
10
|
-
console.log(currentConfig);
|
|
11
|
-
console.log("");
|
|
12
|
-
}
|
|
13
|
-
if (path) {
|
|
14
|
-
}
|
|
15
|
-
});
|
|
16
|
-
export default configCommand;
|
|
@@ -1,250 +0,0 @@
|
|
|
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 { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
11
|
-
import * as fs from 'fs';
|
|
12
|
-
import * as path from 'path';
|
|
13
|
-
import * as os from 'os';
|
|
14
|
-
import { spawn } from 'child_process';
|
|
15
|
-
import YAML from 'yaml';
|
|
16
|
-
vi.mock('fs');
|
|
17
|
-
vi.mock('path');
|
|
18
|
-
vi.mock('os');
|
|
19
|
-
vi.mock('child_process');
|
|
20
|
-
vi.mock('yaml');
|
|
21
|
-
vi.mock('chalk', () => {
|
|
22
|
-
const mockChalk = {
|
|
23
|
-
red: vi.fn((text) => text),
|
|
24
|
-
blue: { bold: vi.fn((text) => text) },
|
|
25
|
-
green: vi.fn((text) => text)
|
|
26
|
-
};
|
|
27
|
-
return Object.assign({ default: mockChalk }, mockChalk);
|
|
28
|
-
});
|
|
29
|
-
describe('config-utils', () => {
|
|
30
|
-
const mockFs = vi.mocked(fs);
|
|
31
|
-
const mockPath = vi.mocked(path);
|
|
32
|
-
const mockOs = vi.mocked(os);
|
|
33
|
-
const mockSpawn = vi.mocked(spawn);
|
|
34
|
-
const mockYaml = vi.mocked(YAML);
|
|
35
|
-
beforeEach(() => {
|
|
36
|
-
vi.clearAllMocks();
|
|
37
|
-
mockOs.homedir.mockReturnValue('/mock/home');
|
|
38
|
-
mockPath.join.mockReturnValue('/mock/home/.mfer/config.yaml');
|
|
39
|
-
mockPath.dirname.mockReturnValue('/mock/home/.mfer');
|
|
40
|
-
});
|
|
41
|
-
describe('loadConfig', () => {
|
|
42
|
-
it('should load config when file exists', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
43
|
-
const mockConfig = {
|
|
44
|
-
base_github_url: 'https://github.com',
|
|
45
|
-
mfe_directory: '/path/to/mfe',
|
|
46
|
-
groups: {
|
|
47
|
-
all: ['app1', 'app2']
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
mockFs.existsSync.mockReturnValue(true);
|
|
51
|
-
mockFs.readFileSync.mockReturnValue('mock yaml content');
|
|
52
|
-
mockYaml.parse.mockReturnValue(mockConfig);
|
|
53
|
-
const { loadConfig } = yield import('../config-utils.js');
|
|
54
|
-
loadConfig();
|
|
55
|
-
expect(mockFs.readFileSync).toHaveBeenCalledWith('/mock/home/.mfer/config.yaml', 'utf8');
|
|
56
|
-
expect(mockYaml.parse).toHaveBeenCalledWith('mock yaml content');
|
|
57
|
-
}));
|
|
58
|
-
it('should not load config when file does not exist', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
59
|
-
mockFs.existsSync.mockReturnValue(false);
|
|
60
|
-
const { loadConfig } = yield import('../config-utils.js');
|
|
61
|
-
loadConfig();
|
|
62
|
-
expect(loadConfig).toBeDefined();
|
|
63
|
-
}));
|
|
64
|
-
});
|
|
65
|
-
describe('warnOfMissingConfig', () => {
|
|
66
|
-
it('should display warning when config does not exist', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
67
|
-
mockFs.existsSync.mockReturnValue(false);
|
|
68
|
-
const { warnOfMissingConfig } = yield import('../config-utils.js');
|
|
69
|
-
warnOfMissingConfig();
|
|
70
|
-
expect(warnOfMissingConfig).toBeDefined();
|
|
71
|
-
}));
|
|
72
|
-
it('should not display warning when config exists', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
73
|
-
mockFs.existsSync.mockReturnValue(true);
|
|
74
|
-
const { warnOfMissingConfig } = yield import('../config-utils.js');
|
|
75
|
-
warnOfMissingConfig();
|
|
76
|
-
expect(warnOfMissingConfig).toBeDefined();
|
|
77
|
-
}));
|
|
78
|
-
});
|
|
79
|
-
describe('isConfigValid', () => {
|
|
80
|
-
it('should return false when config file does not exist', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
81
|
-
mockFs.existsSync.mockReturnValue(false);
|
|
82
|
-
const { isConfigValid } = yield import('../config-utils.js');
|
|
83
|
-
const result = isConfigValid();
|
|
84
|
-
expect(typeof result).toBe('boolean');
|
|
85
|
-
}));
|
|
86
|
-
it('should return false when YAML parsing fails', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
87
|
-
mockFs.existsSync.mockReturnValue(true);
|
|
88
|
-
mockFs.readFileSync.mockReturnValue('invalid yaml');
|
|
89
|
-
mockYaml.parse.mockImplementation(() => {
|
|
90
|
-
throw new Error('Invalid YAML');
|
|
91
|
-
});
|
|
92
|
-
const { isConfigValid } = yield import('../config-utils.js');
|
|
93
|
-
const result = isConfigValid();
|
|
94
|
-
expect(result).toBe(false);
|
|
95
|
-
}));
|
|
96
|
-
it('should return false when config is missing required fields', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
97
|
-
const invalidConfigs = [
|
|
98
|
-
null,
|
|
99
|
-
undefined,
|
|
100
|
-
{},
|
|
101
|
-
{ base_github_url: 'https://github.com' },
|
|
102
|
-
{ base_github_url: 'https://github.com', mfe_directory: '/path' },
|
|
103
|
-
{ base_github_url: 'https://github.com', mfe_directory: '/path', groups: {} },
|
|
104
|
-
{ base_github_url: 'https://github.com', mfe_directory: '/path', groups: { all: [] } }
|
|
105
|
-
];
|
|
106
|
-
mockFs.existsSync.mockReturnValue(true);
|
|
107
|
-
mockFs.readFileSync.mockReturnValue('mock yaml content');
|
|
108
|
-
const { isConfigValid } = yield import('../config-utils.js');
|
|
109
|
-
for (const config of invalidConfigs) {
|
|
110
|
-
mockYaml.parse.mockReturnValue(config);
|
|
111
|
-
const result = isConfigValid();
|
|
112
|
-
expect(result).toBeFalsy();
|
|
113
|
-
mockYaml.parse.mockClear();
|
|
114
|
-
}
|
|
115
|
-
}));
|
|
116
|
-
it('should return true when config has all required fields', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
117
|
-
const validConfig = {
|
|
118
|
-
base_github_url: 'https://github.com',
|
|
119
|
-
mfe_directory: '/path/to/mfe',
|
|
120
|
-
groups: {
|
|
121
|
-
all: ['app1', 'app2'],
|
|
122
|
-
frontend: ['app1'],
|
|
123
|
-
backend: ['app2']
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
mockFs.existsSync.mockReturnValue(true);
|
|
127
|
-
mockFs.readFileSync.mockReturnValue('mock yaml content');
|
|
128
|
-
mockYaml.parse.mockReturnValue(validConfig);
|
|
129
|
-
const { isConfigValid } = yield import('../config-utils.js');
|
|
130
|
-
const result = isConfigValid();
|
|
131
|
-
expect(result).toBe(true);
|
|
132
|
-
}));
|
|
133
|
-
});
|
|
134
|
-
describe('saveConfig', () => {
|
|
135
|
-
it('should save config successfully', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
136
|
-
const mockConfig = {
|
|
137
|
-
base_github_url: 'https://github.com',
|
|
138
|
-
mfe_directory: '/path/to/mfe',
|
|
139
|
-
groups: {
|
|
140
|
-
all: ['app1', 'app2']
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
mockFs.existsSync.mockReturnValue(false);
|
|
144
|
-
mockYaml.stringify.mockReturnValue('mock yaml string');
|
|
145
|
-
const { saveConfig } = yield import('../config-utils.js');
|
|
146
|
-
saveConfig(mockConfig);
|
|
147
|
-
expect(mockPath.dirname).toHaveBeenCalledWith('/mock/home/.mfer/config.yaml');
|
|
148
|
-
expect(mockFs.mkdirSync).toHaveBeenCalledWith('/mock/home/.mfer', { recursive: true });
|
|
149
|
-
expect(mockYaml.stringify).toHaveBeenCalledWith(mockConfig);
|
|
150
|
-
expect(mockFs.writeFileSync).toHaveBeenCalledWith('/mock/home/.mfer/config.yaml', 'mock yaml string');
|
|
151
|
-
}));
|
|
152
|
-
it('should create directory if it does not exist', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
153
|
-
const mockConfig = {
|
|
154
|
-
base_github_url: 'https://github.com',
|
|
155
|
-
mfe_directory: '/path/to/mfe',
|
|
156
|
-
groups: {
|
|
157
|
-
all: ['app1', 'app2']
|
|
158
|
-
}
|
|
159
|
-
};
|
|
160
|
-
mockFs.existsSync.mockReturnValue(false);
|
|
161
|
-
mockYaml.stringify.mockReturnValue('mock yaml string');
|
|
162
|
-
const { saveConfig } = yield import('../config-utils.js');
|
|
163
|
-
saveConfig(mockConfig);
|
|
164
|
-
expect(mockFs.mkdirSync).toHaveBeenCalledWith('/mock/home/.mfer', { recursive: true });
|
|
165
|
-
}));
|
|
166
|
-
it('should handle errors when saving config', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
167
|
-
const mockConfig = {
|
|
168
|
-
base_github_url: 'https://github.com',
|
|
169
|
-
mfe_directory: '/path/to/mfe',
|
|
170
|
-
groups: {
|
|
171
|
-
all: ['app1', 'app2']
|
|
172
|
-
}
|
|
173
|
-
};
|
|
174
|
-
const mockError = new Error('Write error');
|
|
175
|
-
mockFs.writeFileSync.mockImplementation(() => {
|
|
176
|
-
throw mockError;
|
|
177
|
-
});
|
|
178
|
-
const { saveConfig } = yield import('../config-utils.js');
|
|
179
|
-
saveConfig(mockConfig);
|
|
180
|
-
expect(saveConfig).toBeDefined();
|
|
181
|
-
}));
|
|
182
|
-
});
|
|
183
|
-
describe('editConfig', () => {
|
|
184
|
-
it('should open config file with default editor on Windows', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
185
|
-
mockOs.platform.mockReturnValue('win32');
|
|
186
|
-
const mockProcess = { env: {} };
|
|
187
|
-
vi.stubGlobal('process', mockProcess);
|
|
188
|
-
const mockSpawnInstance = {
|
|
189
|
-
unref: vi.fn()
|
|
190
|
-
};
|
|
191
|
-
mockSpawn.mockReturnValue(mockSpawnInstance);
|
|
192
|
-
const { editConfig } = yield import('../config-utils.js');
|
|
193
|
-
editConfig();
|
|
194
|
-
expect(mockSpawn).toHaveBeenCalledWith('notepad', ['/mock/home/.mfer/config.yaml'], {
|
|
195
|
-
stdio: 'ignore',
|
|
196
|
-
detached: true,
|
|
197
|
-
shell: true
|
|
198
|
-
});
|
|
199
|
-
expect(mockSpawnInstance.unref).toHaveBeenCalled();
|
|
200
|
-
}));
|
|
201
|
-
it('should use EDITOR environment variable when available', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
202
|
-
mockOs.platform.mockReturnValue('linux');
|
|
203
|
-
const mockProcess = { env: { EDITOR: 'vim' } };
|
|
204
|
-
vi.stubGlobal('process', mockProcess);
|
|
205
|
-
const mockSpawnInstance = {
|
|
206
|
-
unref: vi.fn()
|
|
207
|
-
};
|
|
208
|
-
mockSpawn.mockReturnValue(mockSpawnInstance);
|
|
209
|
-
const { editConfig } = yield import('../config-utils.js');
|
|
210
|
-
editConfig();
|
|
211
|
-
expect(mockSpawn).toHaveBeenCalledWith('vim', ['/mock/home/.mfer/config.yaml'], {
|
|
212
|
-
stdio: 'ignore',
|
|
213
|
-
detached: true,
|
|
214
|
-
shell: true
|
|
215
|
-
});
|
|
216
|
-
}));
|
|
217
|
-
it('should use VISUAL environment variable when EDITOR is not available', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
218
|
-
mockOs.platform.mockReturnValue('linux');
|
|
219
|
-
const mockProcess = { env: { VISUAL: 'code' } };
|
|
220
|
-
vi.stubGlobal('process', mockProcess);
|
|
221
|
-
const mockSpawnInstance = {
|
|
222
|
-
unref: vi.fn()
|
|
223
|
-
};
|
|
224
|
-
mockSpawn.mockReturnValue(mockSpawnInstance);
|
|
225
|
-
const { editConfig } = yield import('../config-utils.js');
|
|
226
|
-
editConfig();
|
|
227
|
-
expect(mockSpawn).toHaveBeenCalledWith('code', ['/mock/home/.mfer/config.yaml'], {
|
|
228
|
-
stdio: 'ignore',
|
|
229
|
-
detached: true,
|
|
230
|
-
shell: true
|
|
231
|
-
});
|
|
232
|
-
}));
|
|
233
|
-
it('should use vi as fallback on non-Windows platforms', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
234
|
-
mockOs.platform.mockReturnValue('linux');
|
|
235
|
-
const mockProcess = { env: {} };
|
|
236
|
-
vi.stubGlobal('process', mockProcess);
|
|
237
|
-
const mockSpawnInstance = {
|
|
238
|
-
unref: vi.fn()
|
|
239
|
-
};
|
|
240
|
-
mockSpawn.mockReturnValue(mockSpawnInstance);
|
|
241
|
-
const { editConfig } = yield import('../config-utils.js');
|
|
242
|
-
editConfig();
|
|
243
|
-
expect(mockSpawn).toHaveBeenCalledWith('vi', ['/mock/home/.mfer/config.yaml'], {
|
|
244
|
-
stdio: 'ignore',
|
|
245
|
-
detached: true,
|
|
246
|
-
shell: true
|
|
247
|
-
});
|
|
248
|
-
}));
|
|
249
|
-
});
|
|
250
|
-
});
|