@mbc-cqrs-serverless/cli 1.0.23 → 1.0.25
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 +58 -0
- package/dist/actions/install-skills.action.js +224 -0
- package/dist/actions/install-skills.action.spec.js +351 -0
- package/dist/actions/new.action.js +1 -1
- package/dist/actions/ui.action.js +3 -3
- package/dist/commands/index.js +2 -0
- package/dist/commands/install-skills.command.js +18 -0
- package/dist/commands/ui.command.js +1 -1
- package/dist/runners/abstract.runner.js +1 -1
- package/dist/schematics/schematic.colection.js +2 -2
- package/dist/utils/formatting.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -156,6 +156,64 @@ Add MBC CQRS UI common components to your project.
|
|
|
156
156
|
mbc ui-common -p ./src/common-ui -c all
|
|
157
157
|
```
|
|
158
158
|
|
|
159
|
+
### `mbc install-skills`
|
|
160
|
+
|
|
161
|
+
Install Claude Code skills for MBC CQRS Serverless development.
|
|
162
|
+
|
|
163
|
+
**Alias**: `skills`
|
|
164
|
+
|
|
165
|
+
**Options**:
|
|
166
|
+
- `-p, --project` - Install to project directory (`.claude/skills/`) instead of personal (`~/.claude/skills/`)
|
|
167
|
+
- `-f, --force` - Overwrite existing skills
|
|
168
|
+
- `-l, --list` - List available skills without installing
|
|
169
|
+
- `-c, --check` - Check if updates are available without installing
|
|
170
|
+
|
|
171
|
+
**Examples**:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
# Install to personal skills directory (available in all projects)
|
|
175
|
+
mbc install-skills
|
|
176
|
+
|
|
177
|
+
# Install to project directory (shared with team via git)
|
|
178
|
+
mbc install-skills --project
|
|
179
|
+
|
|
180
|
+
# List available skills
|
|
181
|
+
mbc install-skills --list
|
|
182
|
+
|
|
183
|
+
# Force overwrite existing skills
|
|
184
|
+
mbc install-skills --force
|
|
185
|
+
|
|
186
|
+
# Check for updates
|
|
187
|
+
mbc install-skills --check
|
|
188
|
+
|
|
189
|
+
# Using alias
|
|
190
|
+
mbc skills -p
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**Upgrading Skills**:
|
|
194
|
+
|
|
195
|
+
Skills do not auto-update. To upgrade to the latest version:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
# Update CLI to latest version
|
|
199
|
+
npm update -g @mbc-cqrs-serverless/cli
|
|
200
|
+
|
|
201
|
+
# Check if updates are available
|
|
202
|
+
mbc install-skills --check
|
|
203
|
+
|
|
204
|
+
# Force reinstall to get latest version
|
|
205
|
+
mbc install-skills --force
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**Available Skills**:
|
|
209
|
+
|
|
210
|
+
| Skill | Description |
|
|
211
|
+
|-------|-------------|
|
|
212
|
+
| `/mbc-generate` | Generate boilerplate code (modules, services, controllers, DTOs, handlers) |
|
|
213
|
+
| `/mbc-review` | Review code for best practices and anti-patterns (20 patterns) |
|
|
214
|
+
| `/mbc-migrate` | Guide version migrations and breaking changes |
|
|
215
|
+
| `/mbc-debug` | Debug and troubleshoot common issues |
|
|
216
|
+
|
|
159
217
|
## Project Structure
|
|
160
218
|
|
|
161
219
|
The CLI creates a standardized project structure optimized for CQRS:
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.VERSION_FILE_NAME = void 0;
|
|
7
|
+
exports.getSkillsSourcePath = getSkillsSourcePath;
|
|
8
|
+
exports.getPersonalSkillsPath = getPersonalSkillsPath;
|
|
9
|
+
exports.getProjectSkillsPath = getProjectSkillsPath;
|
|
10
|
+
exports.copySkills = copySkills;
|
|
11
|
+
exports.getInstalledVersion = getInstalledVersion;
|
|
12
|
+
exports.getPackageVersion = getPackageVersion;
|
|
13
|
+
exports.writeVersionFile = writeVersionFile;
|
|
14
|
+
exports.default = installSkillsAction;
|
|
15
|
+
const fs_1 = require("fs");
|
|
16
|
+
const os_1 = __importDefault(require("os"));
|
|
17
|
+
const path_1 = __importDefault(require("path"));
|
|
18
|
+
const ui_1 = require("../ui");
|
|
19
|
+
/**
|
|
20
|
+
* Version file name for tracking installed skills version
|
|
21
|
+
*/
|
|
22
|
+
exports.VERSION_FILE_NAME = '.mbc-skills-version';
|
|
23
|
+
/**
|
|
24
|
+
* Get the path to the mcp-server skills source directory
|
|
25
|
+
*/
|
|
26
|
+
function getSkillsSourcePath() {
|
|
27
|
+
// Try to find the mcp-server package
|
|
28
|
+
const possiblePaths = [
|
|
29
|
+
// When installed globally or locally via npm
|
|
30
|
+
path_1.default.join(__dirname, '..', '..', '..', 'mcp-server', 'skills'),
|
|
31
|
+
// When running from monorepo
|
|
32
|
+
path_1.default.join(__dirname, '..', '..', '..', '..', 'mcp-server', 'skills'),
|
|
33
|
+
// When installed via npm in node_modules
|
|
34
|
+
path_1.default.join(process.cwd(), 'node_modules', '@mbc-cqrs-serverless', 'mcp-server', 'skills'),
|
|
35
|
+
];
|
|
36
|
+
for (const p of possiblePaths) {
|
|
37
|
+
if ((0, fs_1.existsSync)(p)) {
|
|
38
|
+
return p;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Try to resolve from npm package
|
|
42
|
+
try {
|
|
43
|
+
const mcpServerPath = require.resolve('@mbc-cqrs-serverless/mcp-server');
|
|
44
|
+
return path_1.default.join(path_1.default.dirname(mcpServerPath), '..', 'skills');
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Package not found
|
|
48
|
+
}
|
|
49
|
+
throw new Error('Could not find mcp-server skills directory. Please ensure @mbc-cqrs-serverless/mcp-server is installed.');
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Get the path to personal skills directory (~/.claude/skills/)
|
|
53
|
+
*/
|
|
54
|
+
function getPersonalSkillsPath() {
|
|
55
|
+
return path_1.default.join(os_1.default.homedir(), '.claude', 'skills');
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Get the path to project skills directory (.claude/skills/)
|
|
59
|
+
*/
|
|
60
|
+
function getProjectSkillsPath() {
|
|
61
|
+
return path_1.default.join(process.cwd(), '.claude', 'skills');
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Copy skills from source to destination
|
|
65
|
+
* @returns List of copied skill names
|
|
66
|
+
*/
|
|
67
|
+
function copySkills(sourcePath, destPath) {
|
|
68
|
+
// Create destination directory if it doesn't exist
|
|
69
|
+
if (!(0, fs_1.existsSync)(destPath)) {
|
|
70
|
+
(0, fs_1.mkdirSync)(destPath, { recursive: true });
|
|
71
|
+
}
|
|
72
|
+
// Get all skill directories
|
|
73
|
+
const entries = (0, fs_1.readdirSync)(sourcePath, { withFileTypes: true });
|
|
74
|
+
const skillDirs = entries.filter((entry) => entry.isDirectory());
|
|
75
|
+
const copiedSkills = [];
|
|
76
|
+
for (const skillDir of skillDirs) {
|
|
77
|
+
const skillSourcePath = path_1.default.join(sourcePath, skillDir.name);
|
|
78
|
+
const skillDestPath = path_1.default.join(destPath, skillDir.name);
|
|
79
|
+
(0, fs_1.cpSync)(skillSourcePath, skillDestPath, { recursive: true });
|
|
80
|
+
copiedSkills.push(skillDir.name);
|
|
81
|
+
}
|
|
82
|
+
return copiedSkills;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* List available skills
|
|
86
|
+
*/
|
|
87
|
+
function listSkills(sourcePath) {
|
|
88
|
+
const entries = (0, fs_1.readdirSync)(sourcePath, { withFileTypes: true });
|
|
89
|
+
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Get the installed version of skills from the version file
|
|
93
|
+
* @returns The installed version or null if not found
|
|
94
|
+
*/
|
|
95
|
+
function getInstalledVersion(destPath) {
|
|
96
|
+
const versionFilePath = path_1.default.join(destPath, exports.VERSION_FILE_NAME);
|
|
97
|
+
if (!(0, fs_1.existsSync)(versionFilePath)) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
const version = (0, fs_1.readFileSync)(versionFilePath, 'utf-8');
|
|
102
|
+
return version.trim();
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Get the package version from mcp-server package.json
|
|
110
|
+
* @returns The package version or null if not found
|
|
111
|
+
*/
|
|
112
|
+
function getPackageVersion(sourcePath) {
|
|
113
|
+
// The package.json is one level up from the skills directory
|
|
114
|
+
const packageJsonPath = path_1.default.join(sourcePath, '..', 'package.json');
|
|
115
|
+
if (!(0, fs_1.existsSync)(packageJsonPath)) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
const packageJson = JSON.parse((0, fs_1.readFileSync)(packageJsonPath, 'utf-8'));
|
|
120
|
+
return packageJson.version || null;
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Write the version file to track installed skills version
|
|
128
|
+
*/
|
|
129
|
+
function writeVersionFile(destPath, version) {
|
|
130
|
+
const versionFilePath = path_1.default.join(destPath, exports.VERSION_FILE_NAME);
|
|
131
|
+
(0, fs_1.writeFileSync)(versionFilePath, version, 'utf-8');
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Install Claude Code skills for MBC CQRS Serverless
|
|
135
|
+
*/
|
|
136
|
+
async function installSkillsAction(options, command) {
|
|
137
|
+
if (!command) {
|
|
138
|
+
throw new Error('Command is required');
|
|
139
|
+
}
|
|
140
|
+
ui_1.logger.info(`Executing command '${command.name()}'...`);
|
|
141
|
+
const { project, force, list, check } = options;
|
|
142
|
+
// Get source path
|
|
143
|
+
let sourcePath;
|
|
144
|
+
try {
|
|
145
|
+
sourcePath = getSkillsSourcePath();
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
ui_1.logger.error(error.message);
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
// Verify source exists
|
|
152
|
+
if (!(0, fs_1.existsSync)(sourcePath)) {
|
|
153
|
+
const errorMsg = `Skills source directory not found: ${sourcePath}`;
|
|
154
|
+
ui_1.logger.error(errorMsg);
|
|
155
|
+
throw new Error(errorMsg);
|
|
156
|
+
}
|
|
157
|
+
// List mode
|
|
158
|
+
if (list) {
|
|
159
|
+
const skills = listSkills(sourcePath);
|
|
160
|
+
ui_1.logger.title('skills', 'Available Claude Code Skills:');
|
|
161
|
+
skills.forEach((skill) => {
|
|
162
|
+
ui_1.logger.log(` - ${skill}`);
|
|
163
|
+
});
|
|
164
|
+
ui_1.logger.log('');
|
|
165
|
+
ui_1.logger.info(`Total: ${skills.length} skills`);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
// Determine destination path
|
|
169
|
+
const destPath = project ? getProjectSkillsPath() : getPersonalSkillsPath();
|
|
170
|
+
// Check mode - compare versions without installing
|
|
171
|
+
if (check) {
|
|
172
|
+
const installedVersion = getInstalledVersion(destPath);
|
|
173
|
+
const packageVersion = getPackageVersion(sourcePath);
|
|
174
|
+
if (!installedVersion) {
|
|
175
|
+
ui_1.logger.warn('Skills are not installed.');
|
|
176
|
+
ui_1.logger.info(`Available version: ${packageVersion || 'unknown'}`);
|
|
177
|
+
ui_1.logger.info('Run `mbc install-skills` to install.');
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (!packageVersion) {
|
|
181
|
+
ui_1.logger.warn('Could not determine package version.');
|
|
182
|
+
ui_1.logger.info(`Installed version: ${installedVersion}`);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (installedVersion === packageVersion) {
|
|
186
|
+
ui_1.logger.success(`Skills are up to date (${installedVersion}).`);
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
ui_1.logger.warn(`Update available: ${installedVersion} → ${packageVersion}`);
|
|
190
|
+
ui_1.logger.info('Run `mbc install-skills --force` to update.');
|
|
191
|
+
}
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
// Check if skills already exist
|
|
195
|
+
if ((0, fs_1.existsSync)(destPath) && !force) {
|
|
196
|
+
const existingSkills = (0, fs_1.readdirSync)(destPath, { withFileTypes: true })
|
|
197
|
+
.filter((entry) => entry.isDirectory())
|
|
198
|
+
.map((entry) => entry.name);
|
|
199
|
+
if (existingSkills.length > 0) {
|
|
200
|
+
ui_1.logger.warn(`Skills already exist at ${destPath}. Use --force to overwrite.`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// Copy skills
|
|
204
|
+
ui_1.logger.title('install', `Installing skills to ${destPath}`);
|
|
205
|
+
const copiedSkills = copySkills(sourcePath, destPath);
|
|
206
|
+
// Write version file
|
|
207
|
+
const packageVersion = getPackageVersion(sourcePath);
|
|
208
|
+
if (packageVersion) {
|
|
209
|
+
writeVersionFile(destPath, packageVersion);
|
|
210
|
+
}
|
|
211
|
+
ui_1.logger.success(`Successfully installed ${copiedSkills.length} skills:`);
|
|
212
|
+
copiedSkills.forEach((skill) => {
|
|
213
|
+
ui_1.logger.log(` - ${skill}`);
|
|
214
|
+
});
|
|
215
|
+
if (packageVersion) {
|
|
216
|
+
ui_1.logger.log('');
|
|
217
|
+
ui_1.logger.info(`Version: ${packageVersion}`);
|
|
218
|
+
}
|
|
219
|
+
ui_1.logger.log('');
|
|
220
|
+
ui_1.logger.info('You can now use these skills in Claude Code:');
|
|
221
|
+
copiedSkills.forEach((skill) => {
|
|
222
|
+
ui_1.logger.log(` /${skill}`);
|
|
223
|
+
});
|
|
224
|
+
}
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
const install_skills_action_1 = __importStar(require("./install-skills.action"));
|
|
40
|
+
jest.mock('fs');
|
|
41
|
+
jest.mock('os');
|
|
42
|
+
const fs_1 = require("fs");
|
|
43
|
+
const os_1 = __importDefault(require("os"));
|
|
44
|
+
const mockExistsSync = fs_1.existsSync;
|
|
45
|
+
const mockMkdirSync = fs_1.mkdirSync;
|
|
46
|
+
const mockCpSync = fs_1.cpSync;
|
|
47
|
+
const mockReaddirSync = fs_1.readdirSync;
|
|
48
|
+
const mockReadFileSync = fs_1.readFileSync;
|
|
49
|
+
const mockWriteFileSync = fs_1.writeFileSync;
|
|
50
|
+
describe('Install Skills Action', () => {
|
|
51
|
+
const mockCommand = {
|
|
52
|
+
name: () => 'install-skills',
|
|
53
|
+
opts: () => ({}),
|
|
54
|
+
};
|
|
55
|
+
beforeEach(() => {
|
|
56
|
+
jest.clearAllMocks();
|
|
57
|
+
os_1.default.homedir.mockReturnValue('/home/user');
|
|
58
|
+
});
|
|
59
|
+
describe('getSkillsSourcePath', () => {
|
|
60
|
+
it('should return the path to mcp-server skills directory', () => {
|
|
61
|
+
// Use real path module, mock only existsSync to return true for skills path
|
|
62
|
+
mockExistsSync.mockReturnValue(true);
|
|
63
|
+
const sourcePath = (0, install_skills_action_1.getSkillsSourcePath)();
|
|
64
|
+
expect(sourcePath).toContain('skills');
|
|
65
|
+
});
|
|
66
|
+
it('should try multiple paths to find skills directory', () => {
|
|
67
|
+
// First call returns false, second returns true
|
|
68
|
+
mockExistsSync
|
|
69
|
+
.mockReturnValueOnce(false)
|
|
70
|
+
.mockReturnValueOnce(false)
|
|
71
|
+
.mockReturnValueOnce(true);
|
|
72
|
+
const sourcePath = (0, install_skills_action_1.getSkillsSourcePath)();
|
|
73
|
+
expect(sourcePath).toContain('skills');
|
|
74
|
+
expect(mockExistsSync).toHaveBeenCalledTimes(3);
|
|
75
|
+
});
|
|
76
|
+
it('should fallback to require.resolve when file paths not found', () => {
|
|
77
|
+
// All file paths return false, but require.resolve finds the package
|
|
78
|
+
mockExistsSync.mockReturnValue(false);
|
|
79
|
+
// In monorepo environment, require.resolve will find mcp-server
|
|
80
|
+
// This tests the fallback path (lines 62-64)
|
|
81
|
+
const sourcePath = (0, install_skills_action_1.getSkillsSourcePath)();
|
|
82
|
+
expect(sourcePath).toContain('skills');
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
describe('getPersonalSkillsPath', () => {
|
|
86
|
+
it('should return ~/.claude/skills/ path', () => {
|
|
87
|
+
const personalPath = (0, install_skills_action_1.getPersonalSkillsPath)();
|
|
88
|
+
expect(personalPath).toContain('/home/user');
|
|
89
|
+
expect(personalPath).toContain('.claude');
|
|
90
|
+
expect(personalPath).toContain('skills');
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
describe('getProjectSkillsPath', () => {
|
|
94
|
+
it('should return .claude/skills/ path in current directory', () => {
|
|
95
|
+
const projectPath = (0, install_skills_action_1.getProjectSkillsPath)();
|
|
96
|
+
expect(projectPath).toContain('.claude/skills');
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
describe('copySkills', () => {
|
|
100
|
+
const mockSourcePath = '/source/skills';
|
|
101
|
+
const mockDestPath = '/dest/skills';
|
|
102
|
+
beforeEach(() => {
|
|
103
|
+
mockReaddirSync.mockReturnValue([
|
|
104
|
+
{ name: 'mbc-generate', isDirectory: () => true },
|
|
105
|
+
{ name: 'mbc-review', isDirectory: () => true },
|
|
106
|
+
{ name: 'mbc-migrate', isDirectory: () => true },
|
|
107
|
+
{ name: 'mbc-debug', isDirectory: () => true },
|
|
108
|
+
]);
|
|
109
|
+
});
|
|
110
|
+
it('should create destination directory if it does not exist', () => {
|
|
111
|
+
mockExistsSync.mockReturnValue(false);
|
|
112
|
+
(0, install_skills_action_1.copySkills)(mockSourcePath, mockDestPath);
|
|
113
|
+
expect(mockMkdirSync).toHaveBeenCalledWith(mockDestPath, {
|
|
114
|
+
recursive: true,
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
it('should not create destination directory if it exists', () => {
|
|
118
|
+
mockExistsSync.mockReturnValue(true);
|
|
119
|
+
(0, install_skills_action_1.copySkills)(mockSourcePath, mockDestPath);
|
|
120
|
+
expect(mockMkdirSync).not.toHaveBeenCalled();
|
|
121
|
+
});
|
|
122
|
+
it('should copy all skill directories', () => {
|
|
123
|
+
mockExistsSync.mockReturnValue(true);
|
|
124
|
+
(0, install_skills_action_1.copySkills)(mockSourcePath, mockDestPath);
|
|
125
|
+
expect(mockCpSync).toHaveBeenCalledTimes(4);
|
|
126
|
+
expect(mockCpSync).toHaveBeenCalledWith(expect.stringContaining('mbc-generate'), expect.stringContaining('mbc-generate'), { recursive: true });
|
|
127
|
+
expect(mockCpSync).toHaveBeenCalledWith(expect.stringContaining('mbc-review'), expect.stringContaining('mbc-review'), { recursive: true });
|
|
128
|
+
expect(mockCpSync).toHaveBeenCalledWith(expect.stringContaining('mbc-migrate'), expect.stringContaining('mbc-migrate'), { recursive: true });
|
|
129
|
+
expect(mockCpSync).toHaveBeenCalledWith(expect.stringContaining('mbc-debug'), expect.stringContaining('mbc-debug'), { recursive: true });
|
|
130
|
+
});
|
|
131
|
+
it('should return the list of copied skills', () => {
|
|
132
|
+
mockExistsSync.mockReturnValue(true);
|
|
133
|
+
const copiedSkills = (0, install_skills_action_1.copySkills)(mockSourcePath, mockDestPath);
|
|
134
|
+
expect(copiedSkills).toEqual([
|
|
135
|
+
'mbc-generate',
|
|
136
|
+
'mbc-review',
|
|
137
|
+
'mbc-migrate',
|
|
138
|
+
'mbc-debug',
|
|
139
|
+
]);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
describe('installSkillsAction', () => {
|
|
143
|
+
beforeEach(() => {
|
|
144
|
+
mockExistsSync.mockReturnValue(true);
|
|
145
|
+
mockReaddirSync.mockReturnValue([
|
|
146
|
+
{ name: 'mbc-generate', isDirectory: () => true },
|
|
147
|
+
{ name: 'mbc-review', isDirectory: () => true },
|
|
148
|
+
{ name: 'mbc-migrate', isDirectory: () => true },
|
|
149
|
+
{ name: 'mbc-debug', isDirectory: () => true },
|
|
150
|
+
]);
|
|
151
|
+
});
|
|
152
|
+
describe('Personal installation (default)', () => {
|
|
153
|
+
it('should install skills to personal directory by default', async () => {
|
|
154
|
+
const options = {};
|
|
155
|
+
await (0, install_skills_action_1.default)(options, mockCommand);
|
|
156
|
+
expect(mockCpSync).toHaveBeenCalled();
|
|
157
|
+
});
|
|
158
|
+
it('should install to ~/.claude/skills/', async () => {
|
|
159
|
+
const options = {};
|
|
160
|
+
await (0, install_skills_action_1.default)(options, mockCommand);
|
|
161
|
+
expect(mockCpSync).toHaveBeenCalledWith(expect.any(String), expect.stringContaining('/home/user/.claude/skills'), expect.any(Object));
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
describe('Project installation (--project flag)', () => {
|
|
165
|
+
it('should install skills to project directory when --project is specified', async () => {
|
|
166
|
+
const options = { project: true };
|
|
167
|
+
await (0, install_skills_action_1.default)(options, mockCommand);
|
|
168
|
+
expect(mockCpSync).toHaveBeenCalledWith(expect.any(String), expect.stringContaining('.claude/skills'), expect.any(Object));
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
describe('Error handling', () => {
|
|
172
|
+
it('should handle missing source directory after finding path', async () => {
|
|
173
|
+
// Source path is found, but verification fails
|
|
174
|
+
mockExistsSync
|
|
175
|
+
.mockReturnValueOnce(true) // getSkillsSourcePath finds it
|
|
176
|
+
.mockReturnValueOnce(false); // but verification fails
|
|
177
|
+
await expect((0, install_skills_action_1.default)({}, mockCommand)).rejects.toThrow('Skills source directory not found');
|
|
178
|
+
});
|
|
179
|
+
it('should handle undefined command', async () => {
|
|
180
|
+
mockExistsSync.mockReturnValue(true);
|
|
181
|
+
await expect((0, install_skills_action_1.default)({}, undefined)).rejects.toThrow('Command is required');
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
describe('Force option', () => {
|
|
185
|
+
it('should overwrite existing skills when --force is specified', async () => {
|
|
186
|
+
const options = { force: true };
|
|
187
|
+
mockExistsSync.mockReturnValue(true);
|
|
188
|
+
await (0, install_skills_action_1.default)(options, mockCommand);
|
|
189
|
+
expect(mockCpSync).toHaveBeenCalled();
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
describe('List option', () => {
|
|
193
|
+
it('should list available skills when --list is specified', async () => {
|
|
194
|
+
const options = { list: true };
|
|
195
|
+
await (0, install_skills_action_1.default)(options, mockCommand);
|
|
196
|
+
// Should not copy when just listing
|
|
197
|
+
expect(mockCpSync).not.toHaveBeenCalled();
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
describe('Integration scenarios', () => {
|
|
202
|
+
beforeEach(() => {
|
|
203
|
+
mockExistsSync.mockReturnValue(true);
|
|
204
|
+
mockReaddirSync.mockReturnValue([
|
|
205
|
+
{ name: 'mbc-generate', isDirectory: () => true },
|
|
206
|
+
{ name: 'mbc-review', isDirectory: () => true },
|
|
207
|
+
{ name: 'mbc-migrate', isDirectory: () => true },
|
|
208
|
+
{ name: 'mbc-debug', isDirectory: () => true },
|
|
209
|
+
]);
|
|
210
|
+
});
|
|
211
|
+
it('should handle installation to non-existent directory', async () => {
|
|
212
|
+
mockExistsSync.mockImplementation((p) => {
|
|
213
|
+
if (p.includes('mcp-server/skills'))
|
|
214
|
+
return true;
|
|
215
|
+
return false;
|
|
216
|
+
});
|
|
217
|
+
await (0, install_skills_action_1.default)({}, mockCommand);
|
|
218
|
+
expect(mockMkdirSync).toHaveBeenCalled();
|
|
219
|
+
});
|
|
220
|
+
it('should handle multiple concurrent installations', async () => {
|
|
221
|
+
const promises = [
|
|
222
|
+
(0, install_skills_action_1.default)({}, mockCommand),
|
|
223
|
+
(0, install_skills_action_1.default)({ project: true }, mockCommand),
|
|
224
|
+
];
|
|
225
|
+
await expect(Promise.all(promises)).resolves.not.toThrow();
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
describe('Version management', () => {
|
|
229
|
+
describe('getInstalledVersion', () => {
|
|
230
|
+
it('should return null if version file does not exist', () => {
|
|
231
|
+
mockExistsSync.mockReturnValue(false);
|
|
232
|
+
const version = (0, install_skills_action_1.getInstalledVersion)('/dest/skills');
|
|
233
|
+
expect(version).toBeNull();
|
|
234
|
+
});
|
|
235
|
+
it('should return version from version file', () => {
|
|
236
|
+
mockExistsSync.mockReturnValue(true);
|
|
237
|
+
mockReadFileSync.mockReturnValue('1.0.24');
|
|
238
|
+
const version = (0, install_skills_action_1.getInstalledVersion)('/dest/skills');
|
|
239
|
+
expect(version).toBe('1.0.24');
|
|
240
|
+
});
|
|
241
|
+
it('should trim whitespace from version', () => {
|
|
242
|
+
mockExistsSync.mockReturnValue(true);
|
|
243
|
+
mockReadFileSync.mockReturnValue(' 1.0.24\n ');
|
|
244
|
+
const version = (0, install_skills_action_1.getInstalledVersion)('/dest/skills');
|
|
245
|
+
expect(version).toBe('1.0.24');
|
|
246
|
+
});
|
|
247
|
+
it('should return null on read error', () => {
|
|
248
|
+
mockExistsSync.mockReturnValue(true);
|
|
249
|
+
mockReadFileSync.mockImplementation(() => {
|
|
250
|
+
throw new Error('Read error');
|
|
251
|
+
});
|
|
252
|
+
const version = (0, install_skills_action_1.getInstalledVersion)('/dest/skills');
|
|
253
|
+
expect(version).toBeNull();
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
describe('getPackageVersion', () => {
|
|
257
|
+
it('should return version from package.json', () => {
|
|
258
|
+
mockExistsSync.mockReturnValue(true);
|
|
259
|
+
mockReadFileSync.mockReturnValue(JSON.stringify({ version: '1.0.25' }));
|
|
260
|
+
const version = (0, install_skills_action_1.getPackageVersion)('/source/skills');
|
|
261
|
+
expect(version).toBe('1.0.25');
|
|
262
|
+
});
|
|
263
|
+
it('should return null if package.json does not exist', () => {
|
|
264
|
+
mockExistsSync.mockReturnValue(false);
|
|
265
|
+
const version = (0, install_skills_action_1.getPackageVersion)('/source/skills');
|
|
266
|
+
expect(version).toBeNull();
|
|
267
|
+
});
|
|
268
|
+
it('should return null on parse error', () => {
|
|
269
|
+
mockExistsSync.mockReturnValue(true);
|
|
270
|
+
mockReadFileSync.mockReturnValue('invalid json');
|
|
271
|
+
const version = (0, install_skills_action_1.getPackageVersion)('/source/skills');
|
|
272
|
+
expect(version).toBeNull();
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
describe('writeVersionFile', () => {
|
|
276
|
+
it('should write version to file', () => {
|
|
277
|
+
(0, install_skills_action_1.writeVersionFile)('/dest/skills', '1.0.25');
|
|
278
|
+
expect(mockWriteFileSync).toHaveBeenCalledWith(expect.stringContaining(install_skills_action_1.VERSION_FILE_NAME), '1.0.25', 'utf-8');
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
describe('Check option', () => {
|
|
282
|
+
beforeEach(() => {
|
|
283
|
+
mockReaddirSync.mockReturnValue([
|
|
284
|
+
{ name: 'mbc-generate', isDirectory: () => true },
|
|
285
|
+
{ name: 'mbc-review', isDirectory: () => true },
|
|
286
|
+
]);
|
|
287
|
+
});
|
|
288
|
+
it('should check for updates when --check is specified', async () => {
|
|
289
|
+
const options = { check: true };
|
|
290
|
+
mockExistsSync.mockReturnValue(true);
|
|
291
|
+
mockReadFileSync
|
|
292
|
+
.mockReturnValueOnce('1.0.24') // installed version
|
|
293
|
+
.mockReturnValueOnce(JSON.stringify({ version: '1.0.25' })); // package version
|
|
294
|
+
await (0, install_skills_action_1.default)(options, mockCommand);
|
|
295
|
+
// Should not copy when just checking
|
|
296
|
+
expect(mockCpSync).not.toHaveBeenCalled();
|
|
297
|
+
});
|
|
298
|
+
it('should report up-to-date when versions match', async () => {
|
|
299
|
+
const options = { check: true };
|
|
300
|
+
mockExistsSync.mockReturnValue(true);
|
|
301
|
+
mockReadFileSync
|
|
302
|
+
.mockReturnValueOnce('1.0.25') // installed version
|
|
303
|
+
.mockReturnValueOnce(JSON.stringify({ version: '1.0.25' })); // package version
|
|
304
|
+
await (0, install_skills_action_1.default)(options, mockCommand);
|
|
305
|
+
expect(mockCpSync).not.toHaveBeenCalled();
|
|
306
|
+
});
|
|
307
|
+
it('should report not installed when version file does not exist', async () => {
|
|
308
|
+
const options = { check: true };
|
|
309
|
+
// First call for source path check, second for version file check (false)
|
|
310
|
+
mockExistsSync.mockImplementation((p) => {
|
|
311
|
+
if (p.includes('mcp-server/skills') || p.includes('mcp-server\\skills')) {
|
|
312
|
+
return true;
|
|
313
|
+
}
|
|
314
|
+
if (p.includes(install_skills_action_1.VERSION_FILE_NAME)) {
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
return true;
|
|
318
|
+
});
|
|
319
|
+
mockReadFileSync.mockReturnValue(JSON.stringify({ version: '1.0.25' }));
|
|
320
|
+
await (0, install_skills_action_1.default)(options, mockCommand);
|
|
321
|
+
expect(mockCpSync).not.toHaveBeenCalled();
|
|
322
|
+
});
|
|
323
|
+
it('should handle unknown package version', async () => {
|
|
324
|
+
const options = { check: true };
|
|
325
|
+
mockExistsSync.mockImplementation((p) => {
|
|
326
|
+
// Package.json does not exist
|
|
327
|
+
if (p.includes('package.json')) {
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
return true;
|
|
331
|
+
});
|
|
332
|
+
mockReadFileSync.mockReturnValue('1.0.24'); // installed version only
|
|
333
|
+
await (0, install_skills_action_1.default)(options, mockCommand);
|
|
334
|
+
expect(mockCpSync).not.toHaveBeenCalled();
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
describe('Version file creation during installation', () => {
|
|
338
|
+
beforeEach(() => {
|
|
339
|
+
mockExistsSync.mockReturnValue(true);
|
|
340
|
+
mockReaddirSync.mockReturnValue([
|
|
341
|
+
{ name: 'mbc-generate', isDirectory: () => true },
|
|
342
|
+
]);
|
|
343
|
+
mockReadFileSync.mockReturnValue(JSON.stringify({ version: '1.0.25' }));
|
|
344
|
+
});
|
|
345
|
+
it('should create version file after installation', async () => {
|
|
346
|
+
await (0, install_skills_action_1.default)({}, mockCommand);
|
|
347
|
+
expect(mockWriteFileSync).toHaveBeenCalledWith(expect.stringContaining(install_skills_action_1.VERSION_FILE_NAME), '1.0.25', 'utf-8');
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
});
|
|
@@ -29,7 +29,7 @@ async function newAction(name = '', options, command) {
|
|
|
29
29
|
packageVersion = `^${matchVersions.at(-1)}`; // use the patch and minor versions
|
|
30
30
|
}
|
|
31
31
|
else {
|
|
32
|
-
ui_1.logger.error(`The specified package version does not exist. Please
|
|
32
|
+
ui_1.logger.error(`The specified package version does not exist. Please choose a valid version! ${versions}`);
|
|
33
33
|
return;
|
|
34
34
|
}
|
|
35
35
|
}
|
|
@@ -16,19 +16,19 @@ async function uiAction(options, command) {
|
|
|
16
16
|
ui_1.logger.info(`Executing command '${command.name()}' for application with options '${JSON.stringify(options)}'`);
|
|
17
17
|
const { branch, auth, component, pathDir, token = '' } = options;
|
|
18
18
|
if (componentOptions.findIndex((optionName) => optionName === component) === -1) {
|
|
19
|
-
ui_1.logger.error(`Please choose correct component options: ${componentOptions.join(', ')}`);
|
|
19
|
+
ui_1.logger.error(`Please choose the correct component options: ${componentOptions.join(', ')}`);
|
|
20
20
|
}
|
|
21
21
|
// Check command run in base src
|
|
22
22
|
if (!(0, fs_1.existsSync)(path_1.default.join(process.cwd(), 'tsconfig.json'))) {
|
|
23
23
|
ui_1.logger.error('Please run command in base folder');
|
|
24
24
|
return;
|
|
25
25
|
}
|
|
26
|
-
// Check tsconfig.json
|
|
26
|
+
// Check tsconfig.json contains path @ms
|
|
27
27
|
const tsconfig = JSON.parse((0, fs_1.readFileSync)(path_1.default.join(process.cwd(), 'tsconfig.json'), 'utf8'));
|
|
28
28
|
if (tsconfig?.compilerOptions &&
|
|
29
29
|
tsconfig?.compilerOptions?.paths &&
|
|
30
30
|
tsconfig?.compilerOptions?.paths.hasOwnProperty('@ms/*')) {
|
|
31
|
-
ui_1.logger.error('The project already
|
|
31
|
+
ui_1.logger.error('The project already contains mbc-cqrs-ui-common');
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
34
34
|
// Copy source
|
package/dist/commands/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.default = loadCommands;
|
|
4
4
|
const generate_command_1 = require("./generate.command");
|
|
5
|
+
const install_skills_command_1 = require("./install-skills.command");
|
|
5
6
|
const new_command_1 = require("./new.command");
|
|
6
7
|
const start_command_1 = require("./start.command");
|
|
7
8
|
const ui_command_1 = require("./ui.command");
|
|
@@ -10,6 +11,7 @@ function loadCommands(program) {
|
|
|
10
11
|
(0, start_command_1.startCommand)(program);
|
|
11
12
|
(0, ui_command_1.uiCommand)(program);
|
|
12
13
|
(0, generate_command_1.generateCommand)(program);
|
|
14
|
+
(0, install_skills_command_1.installSkillsCommand)(program);
|
|
13
15
|
// error handling
|
|
14
16
|
program.on('command:*', () => {
|
|
15
17
|
console.error(`\nInvalid command: '${program.args.join(' ')}'`);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.installSkillsCommand = installSkillsCommand;
|
|
7
|
+
const install_skills_action_1 = __importDefault(require("../actions/install-skills.action"));
|
|
8
|
+
function installSkillsCommand(program) {
|
|
9
|
+
program
|
|
10
|
+
.command('install-skills')
|
|
11
|
+
.alias('skills')
|
|
12
|
+
.description('Install Claude Code skills for MBC CQRS Serverless development')
|
|
13
|
+
.option('-p, --project', 'Install to project directory (.claude/skills/) instead of personal (~/.claude/skills/)', false)
|
|
14
|
+
.option('-f, --force', 'Overwrite existing skills', false)
|
|
15
|
+
.option('-l, --list', 'List available skills without installing', false)
|
|
16
|
+
.option('-c, --check', 'Check if updates are available without installing', false)
|
|
17
|
+
.action(install_skills_action_1.default);
|
|
18
|
+
}
|
|
@@ -9,7 +9,7 @@ function uiCommand(program) {
|
|
|
9
9
|
program
|
|
10
10
|
.command('ui-common')
|
|
11
11
|
.alias('ui')
|
|
12
|
-
.description('
|
|
12
|
+
.description('Add mbc-cqrs-ui-common components to your project.')
|
|
13
13
|
.requiredOption('-p, --pathDir <string>', 'The place of common-ui')
|
|
14
14
|
.option('-b, --branch <string>', 'The branch name', 'main')
|
|
15
15
|
.option('--auth <string>', 'The auth method (HTTPS - Token, SSH)', 'SSH')
|
|
@@ -34,7 +34,7 @@ class AbstractRunner {
|
|
|
34
34
|
}
|
|
35
35
|
/**
|
|
36
36
|
* @param command
|
|
37
|
-
* @returns The entire command that will be
|
|
37
|
+
* @returns The entire command that will be run when calling `run(command)`.
|
|
38
38
|
*/
|
|
39
39
|
rawFullCommand(command) {
|
|
40
40
|
const commandArgs = [...this.args, command];
|
|
@@ -26,12 +26,12 @@ exports.schematics = [
|
|
|
26
26
|
{
|
|
27
27
|
name: 'entity',
|
|
28
28
|
alias: 'en',
|
|
29
|
-
description: 'Create
|
|
29
|
+
description: 'Create an entity.',
|
|
30
30
|
},
|
|
31
31
|
{
|
|
32
32
|
name: 'dto',
|
|
33
33
|
alias: 'dto',
|
|
34
|
-
description: 'Create a
|
|
34
|
+
description: 'Create a DTO.',
|
|
35
35
|
},
|
|
36
36
|
];
|
|
37
37
|
function buildSchematicsListAsTable() {
|
package/dist/utils/formatting.js
CHANGED
|
@@ -4,7 +4,7 @@ exports.normalizeToKebabOrSnakeCase = normalizeToKebabOrSnakeCase;
|
|
|
4
4
|
/**
|
|
5
5
|
*
|
|
6
6
|
* @param str
|
|
7
|
-
* @returns
|
|
7
|
+
* @returns formatted string
|
|
8
8
|
* @description normalizes input to supported path and file name format.
|
|
9
9
|
* Changes camelCase strings to kebab-case, replaces spaces with dash and keeps underscores.
|
|
10
10
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mbc-cqrs-serverless/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.25",
|
|
4
4
|
"description": "a CLI to get started with MBC CQRS serverless framework",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mbc",
|
|
@@ -58,5 +58,5 @@
|
|
|
58
58
|
"@faker-js/faker": "^8.3.1",
|
|
59
59
|
"copyfiles": "^2.4.1"
|
|
60
60
|
},
|
|
61
|
-
"gitHead": "
|
|
61
|
+
"gitHead": "26c722916f5c8a71b5c066eca5b8db8c21dc7bcb"
|
|
62
62
|
}
|