bmad-method 4.20.0 ā 4.21.1
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/CHANGELOG.md +19 -0
- package/CONTRIBUTING.md +38 -4
- package/README.md +42 -18
- package/bmad-core/core-config.yml +1 -0
- package/dist/agents/dev.txt +1 -1
- package/dist/teams/team-all.txt +1 -1
- package/dist/teams/team-ide-minimal.txt +1 -1
- package/docs/how-to-contribute-with-pull-requests.md +24 -7
- package/expansion-packs/bmad-2d-phaser-game-dev/config.yml +4 -2
- package/expansion-packs/bmad-creator-tools/config.yml +1 -1
- package/expansion-packs/bmad-infrastructure-devops/config.yml +5 -2
- package/package.json +15 -1
- package/tools/bump-all-versions.js +107 -0
- package/tools/bump-core-version.js +57 -0
- package/tools/bump-expansion-version.js +78 -0
- package/tools/installer/bin/bmad.js +79 -158
- package/tools/installer/lib/file-manager.js +90 -2
- package/tools/installer/lib/installer.js +515 -64
- package/tools/installer/package.json +1 -1
- package/tools/update-expansion-version.js +54 -0
- package/test-ide-paths.js +0 -41
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
const { program } = require('commander');
|
|
4
4
|
const path = require('path');
|
|
5
|
+
const fs = require('fs').promises;
|
|
6
|
+
const yaml = require('js-yaml');
|
|
5
7
|
|
|
6
8
|
// Dynamic imports for ES modules
|
|
7
9
|
let chalk, inquirer;
|
|
@@ -45,17 +47,15 @@ program
|
|
|
45
47
|
program
|
|
46
48
|
.command('install')
|
|
47
49
|
.description('Install BMAD Method agents and tools')
|
|
48
|
-
.option('-f, --full', 'Install complete
|
|
49
|
-
.option('-a, --agent <agent>', 'Install specific agent with dependencies')
|
|
50
|
-
.option('-t, --team <team>', 'Install specific team with required agents and dependencies')
|
|
50
|
+
.option('-f, --full', 'Install complete BMAD Method')
|
|
51
51
|
.option('-x, --expansion-only', 'Install only expansion packs (no bmad-core)')
|
|
52
|
-
.option('-d, --directory <path>', 'Installation directory
|
|
52
|
+
.option('-d, --directory <path>', 'Installation directory')
|
|
53
53
|
.option('-i, --ide <ide...>', 'Configure for specific IDE(s) - can specify multiple (cursor, claude-code, windsurf, roo, cline, gemini, other)')
|
|
54
54
|
.option('-e, --expansion-packs <packs...>', 'Install specific expansion packs (can specify multiple)')
|
|
55
55
|
.action(async (options) => {
|
|
56
56
|
try {
|
|
57
57
|
await initializeModules();
|
|
58
|
-
if (!options.full && !options.
|
|
58
|
+
if (!options.full && !options.expansionOnly) {
|
|
59
59
|
// Interactive mode
|
|
60
60
|
const answers = await promptInstallation();
|
|
61
61
|
if (!answers._alreadyInstalled) {
|
|
@@ -64,15 +64,11 @@ program
|
|
|
64
64
|
} else {
|
|
65
65
|
// Direct mode
|
|
66
66
|
let installType = 'full';
|
|
67
|
-
if (options.
|
|
68
|
-
else if (options.team) installType = 'team';
|
|
69
|
-
else if (options.expansionOnly) installType = 'expansion-only';
|
|
67
|
+
if (options.expansionOnly) installType = 'expansion-only';
|
|
70
68
|
|
|
71
69
|
const config = {
|
|
72
70
|
installType,
|
|
73
|
-
|
|
74
|
-
team: options.team,
|
|
75
|
-
directory: options.directory || '.bmad-core',
|
|
71
|
+
directory: options.directory || '.',
|
|
76
72
|
ides: (options.ide || []).filter(ide => ide !== 'other'),
|
|
77
73
|
expansionPacks: options.expansionPacks || []
|
|
78
74
|
};
|
|
@@ -100,19 +96,6 @@ program
|
|
|
100
96
|
}
|
|
101
97
|
});
|
|
102
98
|
|
|
103
|
-
program
|
|
104
|
-
.command('list')
|
|
105
|
-
.description('List available agents')
|
|
106
|
-
.action(async () => {
|
|
107
|
-
try {
|
|
108
|
-
await installer.listAgents();
|
|
109
|
-
} catch (error) {
|
|
110
|
-
if (!chalk) await initializeModules();
|
|
111
|
-
console.error(chalk.red('Error:'), error.message);
|
|
112
|
-
process.exit(1);
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
|
|
116
99
|
program
|
|
117
100
|
.command('list:expansions')
|
|
118
101
|
.description('List available expansion packs')
|
|
@@ -145,7 +128,7 @@ async function promptInstallation() {
|
|
|
145
128
|
|
|
146
129
|
const answers = {};
|
|
147
130
|
|
|
148
|
-
// Ask for installation directory
|
|
131
|
+
// Ask for installation directory first
|
|
149
132
|
const { directory } = await inquirer.prompt([
|
|
150
133
|
{
|
|
151
134
|
type: 'input',
|
|
@@ -161,147 +144,85 @@ async function promptInstallation() {
|
|
|
161
144
|
]);
|
|
162
145
|
answers.directory = directory;
|
|
163
146
|
|
|
164
|
-
//
|
|
165
|
-
const installDir = path.resolve(
|
|
147
|
+
// Detect existing installations
|
|
148
|
+
const installDir = path.resolve(directory);
|
|
166
149
|
const state = await installer.detectInstallationState(installDir);
|
|
167
|
-
|
|
150
|
+
|
|
151
|
+
// Check for existing expansion packs
|
|
152
|
+
const existingExpansionPacks = state.expansionPacks || {};
|
|
153
|
+
|
|
154
|
+
// Get available expansion packs
|
|
155
|
+
const availableExpansionPacks = await installer.getAvailableExpansionPacks();
|
|
156
|
+
|
|
157
|
+
// Build choices list
|
|
158
|
+
const choices = [];
|
|
159
|
+
|
|
160
|
+
// Load core config to get short-title
|
|
161
|
+
const coreConfigPath = path.join(__dirname, '..', '..', '..', 'bmad-core', 'core-config.yml');
|
|
162
|
+
const coreConfig = yaml.load(await fs.readFile(coreConfigPath, 'utf8'));
|
|
163
|
+
const coreShortTitle = coreConfig['short-title'] || 'BMad Agile Core System';
|
|
164
|
+
|
|
165
|
+
// Add BMAD core option
|
|
166
|
+
let bmadOptionText;
|
|
168
167
|
if (state.type === 'v4_existing') {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
name: 'shouldUpdate',
|
|
178
|
-
message: 'Would you like to update your existing BMAD v4 installation?',
|
|
179
|
-
default: true
|
|
180
|
-
}
|
|
181
|
-
]);
|
|
182
|
-
|
|
183
|
-
if (shouldUpdate) {
|
|
184
|
-
// Skip other prompts and go directly to update
|
|
185
|
-
answers.installType = 'update';
|
|
186
|
-
answers._alreadyInstalled = true; // Flag to prevent double installation
|
|
187
|
-
await installer.install(answers);
|
|
188
|
-
return answers; // Return the answers object
|
|
189
|
-
}
|
|
190
|
-
// If user doesn't want to update, continue with normal flow
|
|
168
|
+
const currentVersion = state.manifest?.version || 'unknown';
|
|
169
|
+
const newVersion = coreConfig.version || 'unknown'; // Use version from core-config.yml
|
|
170
|
+
const versionInfo = currentVersion === newVersion
|
|
171
|
+
? `(v${currentVersion} - reinstall)`
|
|
172
|
+
: `(v${currentVersion} ā v${newVersion})`;
|
|
173
|
+
bmadOptionText = `Update ${coreShortTitle} ${versionInfo} .bmad-core`;
|
|
174
|
+
} else {
|
|
175
|
+
bmadOptionText = `Install ${coreShortTitle} (v${coreConfig.version || version}) .bmad-core`;
|
|
191
176
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
{
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
name: 'Expansion packs only - Install expansion packs without bmad-core',
|
|
214
|
-
value: 'expansion-only'
|
|
215
|
-
}
|
|
216
|
-
]
|
|
177
|
+
|
|
178
|
+
choices.push({
|
|
179
|
+
name: bmadOptionText,
|
|
180
|
+
value: 'bmad-core',
|
|
181
|
+
checked: true
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Add expansion pack options
|
|
185
|
+
for (const pack of availableExpansionPacks) {
|
|
186
|
+
const existing = existingExpansionPacks[pack.id];
|
|
187
|
+
let packOptionText;
|
|
188
|
+
|
|
189
|
+
if (existing) {
|
|
190
|
+
const currentVersion = existing.manifest?.version || 'unknown';
|
|
191
|
+
const newVersion = pack.version;
|
|
192
|
+
const versionInfo = currentVersion === newVersion
|
|
193
|
+
? `(v${currentVersion} - reinstall)`
|
|
194
|
+
: `(v${currentVersion} ā v${newVersion})`;
|
|
195
|
+
packOptionText = `Update ${pack.description} ${versionInfo} .${pack.id}`;
|
|
196
|
+
} else {
|
|
197
|
+
packOptionText = `Install ${pack.description} (v${pack.version}) .${pack.id}`;
|
|
217
198
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const { agent } = await inquirer.prompt([
|
|
225
|
-
{
|
|
226
|
-
type: 'list',
|
|
227
|
-
name: 'agent',
|
|
228
|
-
message: 'Select an agent to install:',
|
|
229
|
-
choices: agents.map(a => ({
|
|
230
|
-
name: `${a.id} - ${a.name} (${a.description})`,
|
|
231
|
-
value: a.id
|
|
232
|
-
}))
|
|
233
|
-
}
|
|
234
|
-
]);
|
|
235
|
-
answers.agent = agent;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// If team installation, ask which team
|
|
239
|
-
if (installType === 'team') {
|
|
240
|
-
const teams = await installer.getAvailableTeams();
|
|
241
|
-
const { team } = await inquirer.prompt([
|
|
242
|
-
{
|
|
243
|
-
type: 'list',
|
|
244
|
-
name: 'team',
|
|
245
|
-
message: 'Select a team to install in your IDE project folder:',
|
|
246
|
-
choices: teams.map(t => ({
|
|
247
|
-
name: `${t.icon || 'š'} ${t.name}: ${t.description}`,
|
|
248
|
-
value: t.id
|
|
249
|
-
}))
|
|
250
|
-
}
|
|
251
|
-
]);
|
|
252
|
-
answers.team = team;
|
|
199
|
+
|
|
200
|
+
choices.push({
|
|
201
|
+
name: packOptionText,
|
|
202
|
+
value: pack.id,
|
|
203
|
+
checked: false
|
|
204
|
+
});
|
|
253
205
|
}
|
|
254
|
-
|
|
255
|
-
// Ask
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
message = 'Select expansion packs to install (required):'
|
|
266
|
-
choices = availableExpansionPacks.map(pack => ({
|
|
267
|
-
name: `${pack.name} - ${pack.description}`,
|
|
268
|
-
value: pack.id
|
|
269
|
-
}));
|
|
270
|
-
} else {
|
|
271
|
-
message = 'Select expansion packs to install (press Enter to skip, or check any to install):';
|
|
272
|
-
choices = availableExpansionPacks.map(pack => ({
|
|
273
|
-
name: `${pack.name} - ${pack.description}`,
|
|
274
|
-
value: pack.id
|
|
275
|
-
}));
|
|
206
|
+
|
|
207
|
+
// Ask what to install
|
|
208
|
+
const { selectedItems } = await inquirer.prompt([
|
|
209
|
+
{
|
|
210
|
+
type: 'checkbox',
|
|
211
|
+
name: 'selectedItems',
|
|
212
|
+
message: 'Select what to install/update (use space to select, enter to continue):',
|
|
213
|
+
choices: choices,
|
|
214
|
+
validate: (selected) => {
|
|
215
|
+
if (selected.length === 0) {
|
|
216
|
+
return 'Please select at least one item to install';
|
|
276
217
|
}
|
|
277
|
-
|
|
278
|
-
const { expansionPacks } = await inquirer.prompt([
|
|
279
|
-
{
|
|
280
|
-
type: 'checkbox',
|
|
281
|
-
name: 'expansionPacks',
|
|
282
|
-
message,
|
|
283
|
-
choices,
|
|
284
|
-
validate: installType === 'expansion-only' ? (answer) => {
|
|
285
|
-
if (answer.length < 1) {
|
|
286
|
-
return 'You must select at least one expansion pack for expansion-only installation.';
|
|
287
|
-
}
|
|
288
|
-
return true;
|
|
289
|
-
} : undefined
|
|
290
|
-
}
|
|
291
|
-
]);
|
|
292
|
-
|
|
293
|
-
// Use selected expansion packs directly
|
|
294
|
-
answers.expansionPacks = expansionPacks;
|
|
295
|
-
} else {
|
|
296
|
-
answers.expansionPacks = [];
|
|
218
|
+
return true;
|
|
297
219
|
}
|
|
298
|
-
} catch (error) {
|
|
299
|
-
console.warn(chalk.yellow('Warning: Could not load expansion packs. Continuing without them.'));
|
|
300
|
-
answers.expansionPacks = [];
|
|
301
220
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
221
|
+
]);
|
|
222
|
+
|
|
223
|
+
// Process selections
|
|
224
|
+
answers.installType = selectedItems.includes('bmad-core') ? 'full' : 'expansion-only';
|
|
225
|
+
answers.expansionPacks = selectedItems.filter(item => item !== 'bmad-core');
|
|
305
226
|
|
|
306
227
|
// Ask for IDE configuration
|
|
307
228
|
const { ides } = await inquirer.prompt([
|
|
@@ -83,12 +83,22 @@ class FileManager {
|
|
|
83
83
|
this.manifestFile
|
|
84
84
|
);
|
|
85
85
|
|
|
86
|
+
// Read version from core-config.yml
|
|
87
|
+
const coreConfigPath = path.join(__dirname, "../../../bmad-core/core-config.yml");
|
|
88
|
+
let coreVersion = "unknown";
|
|
89
|
+
try {
|
|
90
|
+
const coreConfigContent = await fs.readFile(coreConfigPath, "utf8");
|
|
91
|
+
const coreConfig = yaml.load(coreConfigContent);
|
|
92
|
+
coreVersion = coreConfig.version || "unknown";
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.warn("Could not read version from core-config.yml, using 'unknown'");
|
|
95
|
+
}
|
|
96
|
+
|
|
86
97
|
const manifest = {
|
|
87
|
-
version:
|
|
98
|
+
version: coreVersion,
|
|
88
99
|
installed_at: new Date().toISOString(),
|
|
89
100
|
install_type: config.installType,
|
|
90
101
|
agent: config.agent || null,
|
|
91
|
-
ide_setup: config.ide || null,
|
|
92
102
|
ides_setup: config.ides || [],
|
|
93
103
|
expansion_packs: config.expansionPacks || [],
|
|
94
104
|
files: [],
|
|
@@ -128,6 +138,21 @@ class FileManager {
|
|
|
128
138
|
}
|
|
129
139
|
}
|
|
130
140
|
|
|
141
|
+
async readExpansionPackManifest(installDir, packId) {
|
|
142
|
+
const manifestPath = path.join(
|
|
143
|
+
installDir,
|
|
144
|
+
`.${packId}`,
|
|
145
|
+
this.manifestFile
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
const content = await fs.readFile(manifestPath, "utf8");
|
|
150
|
+
return yaml.load(content);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
131
156
|
async checkModifiedFiles(installDir, manifest) {
|
|
132
157
|
const modified = [];
|
|
133
158
|
|
|
@@ -143,6 +168,33 @@ class FileManager {
|
|
|
143
168
|
return modified;
|
|
144
169
|
}
|
|
145
170
|
|
|
171
|
+
async checkFileIntegrity(installDir, manifest) {
|
|
172
|
+
const result = {
|
|
173
|
+
missing: [],
|
|
174
|
+
modified: []
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
for (const file of manifest.files) {
|
|
178
|
+
const filePath = path.join(installDir, file.path);
|
|
179
|
+
|
|
180
|
+
// Skip checking the manifest file itself - it will always be different due to timestamps
|
|
181
|
+
if (file.path.endsWith('install-manifest.yml')) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (!(await this.pathExists(filePath))) {
|
|
186
|
+
result.missing.push(file.path);
|
|
187
|
+
} else {
|
|
188
|
+
const currentHash = await this.calculateFileHash(filePath);
|
|
189
|
+
if (currentHash && currentHash !== file.hash) {
|
|
190
|
+
result.modified.push(file.path);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return result;
|
|
196
|
+
}
|
|
197
|
+
|
|
146
198
|
async backupFile(filePath) {
|
|
147
199
|
const backupPath = filePath + ".bak";
|
|
148
200
|
let counter = 1;
|
|
@@ -183,6 +235,42 @@ class FileManager {
|
|
|
183
235
|
async removeDirectory(dirPath) {
|
|
184
236
|
await fs.remove(dirPath);
|
|
185
237
|
}
|
|
238
|
+
|
|
239
|
+
async createExpansionPackManifest(installDir, packId, config, files) {
|
|
240
|
+
const manifestPath = path.join(
|
|
241
|
+
installDir,
|
|
242
|
+
`.${packId}`,
|
|
243
|
+
this.manifestFile
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
const manifest = {
|
|
247
|
+
version: config.expansionPackVersion || require("../../../package.json").version,
|
|
248
|
+
installed_at: new Date().toISOString(),
|
|
249
|
+
install_type: config.installType,
|
|
250
|
+
expansion_pack_id: config.expansionPackId,
|
|
251
|
+
expansion_pack_name: config.expansionPackName,
|
|
252
|
+
ides_setup: config.ides || [],
|
|
253
|
+
files: [],
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
// Add file information
|
|
257
|
+
for (const file of files) {
|
|
258
|
+
const filePath = path.join(installDir, file);
|
|
259
|
+
const hash = await this.calculateFileHash(filePath);
|
|
260
|
+
|
|
261
|
+
manifest.files.push({
|
|
262
|
+
path: file,
|
|
263
|
+
hash: hash,
|
|
264
|
+
modified: false,
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Write manifest
|
|
269
|
+
await fs.ensureDir(path.dirname(manifestPath));
|
|
270
|
+
await fs.writeFile(manifestPath, yaml.dump(manifest, { indent: 2 }));
|
|
271
|
+
|
|
272
|
+
return manifest;
|
|
273
|
+
}
|
|
186
274
|
}
|
|
187
275
|
|
|
188
276
|
module.exports = new FileManager();
|