bmad-method 5.0.0-beta.2 → 5.0.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/.github/ISSUE_TEMPLATE/bug_report.md +3 -3
- package/.github/ISSUE_TEMPLATE/feature_request.md +3 -3
- package/.github/workflows/discord.yaml +11 -2
- package/.github/workflows/format-check.yaml +42 -0
- package/.github/workflows/manual-release.yaml +173 -0
- package/.husky/pre-commit +3 -0
- package/.vscode/settings.json +26 -1
- package/CHANGELOG.md +0 -11
- package/README.md +2 -0
- package/bmad-core/agent-teams/team-all.yaml +1 -1
- package/bmad-core/agents/bmad-orchestrator.md +1 -1
- package/bmad-core/agents/dev.md +4 -4
- package/bmad-core/data/bmad-kb.md +1 -1
- package/bmad-core/data/test-levels-framework.md +12 -12
- package/bmad-core/tasks/facilitate-brainstorming-session.md +1 -1
- package/bmad-core/tasks/nfr-assess.md +10 -10
- package/bmad-core/tasks/qa-gate.md +23 -23
- package/bmad-core/tasks/review-story.md +18 -18
- package/bmad-core/tasks/risk-profile.md +25 -25
- package/bmad-core/tasks/test-design.md +9 -9
- package/bmad-core/tasks/trace-requirements.md +21 -21
- package/bmad-core/templates/architecture-tmpl.yaml +49 -49
- package/bmad-core/templates/brainstorming-output-tmpl.yaml +5 -5
- package/bmad-core/templates/brownfield-architecture-tmpl.yaml +31 -31
- package/bmad-core/templates/brownfield-prd-tmpl.yaml +13 -13
- package/bmad-core/templates/competitor-analysis-tmpl.yaml +19 -6
- package/bmad-core/templates/front-end-architecture-tmpl.yaml +21 -9
- package/bmad-core/templates/front-end-spec-tmpl.yaml +24 -24
- package/bmad-core/templates/fullstack-architecture-tmpl.yaml +122 -104
- package/bmad-core/templates/market-research-tmpl.yaml +2 -2
- package/bmad-core/templates/prd-tmpl.yaml +9 -9
- package/bmad-core/templates/project-brief-tmpl.yaml +4 -4
- package/bmad-core/templates/qa-gate-tmpl.yaml +9 -9
- package/bmad-core/templates/story-tmpl.yaml +12 -12
- package/bmad-core/workflows/brownfield-fullstack.yaml +9 -9
- package/bmad-core/workflows/brownfield-service.yaml +1 -1
- package/bmad-core/workflows/brownfield-ui.yaml +1 -1
- package/bmad-core/workflows/greenfield-fullstack.yaml +1 -1
- package/bmad-core/workflows/greenfield-service.yaml +1 -1
- package/bmad-core/workflows/greenfield-ui.yaml +1 -1
- package/common/utils/bmad-doc-template.md +5 -5
- package/dist/agents/analyst.txt +28 -15
- package/dist/agents/architect.txt +220 -190
- package/dist/agents/bmad-master.txt +298 -255
- package/dist/agents/bmad-orchestrator.txt +1 -1
- package/dist/agents/pm.txt +20 -20
- package/dist/agents/po.txt +11 -11
- package/dist/agents/qa.txt +275 -618
- package/dist/agents/sm.txt +11 -11
- package/dist/agents/ux-expert.txt +23 -23
- package/dist/expansion-packs/bmad-2d-phaser-game-dev/agents/game-designer.txt +109 -109
- package/dist/expansion-packs/bmad-2d-phaser-game-dev/agents/game-developer.txt +75 -77
- package/dist/expansion-packs/bmad-2d-phaser-game-dev/agents/game-sm.txt +41 -41
- package/dist/expansion-packs/bmad-2d-phaser-game-dev/teams/phaser-2d-nodejs-game-team.txt +483 -474
- package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-architect.txt +1 -1
- package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-designer.txt +149 -149
- package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-sm.txt +20 -20
- package/dist/expansion-packs/bmad-2d-unity-game-dev/teams/unity-2d-game-team.txt +371 -358
- package/dist/expansion-packs/bmad-infrastructure-devops/agents/infra-devops-platform.txt +25 -25
- package/dist/teams/team-all.txt +581 -881
- package/dist/teams/team-fullstack.txt +316 -273
- package/dist/teams/team-ide-minimal.txt +276 -619
- package/dist/teams/team-no-ui.txt +281 -238
- package/docs/versioning-and-releases.md +114 -44
- package/eslint.config.mjs +119 -0
- package/expansion-packs/Complete AI Agent System - Blank Templates & Google Cloud Setup/PART 1 - Google Cloud Vertex AI Setup Documentation/1.4 Deployment Configuration/1.4.2 - cloudbuild.yaml +26 -26
- package/expansion-packs/bmad-2d-phaser-game-dev/agents/game-developer.md +4 -4
- package/expansion-packs/bmad-2d-phaser-game-dev/agents/game-sm.md +1 -1
- package/expansion-packs/bmad-2d-phaser-game-dev/data/development-guidelines.md +26 -28
- package/expansion-packs/bmad-2d-phaser-game-dev/templates/game-architecture-tmpl.yaml +50 -50
- package/expansion-packs/bmad-2d-phaser-game-dev/templates/game-brief-tmpl.yaml +23 -23
- package/expansion-packs/bmad-2d-phaser-game-dev/templates/game-design-doc-tmpl.yaml +24 -24
- package/expansion-packs/bmad-2d-phaser-game-dev/templates/game-story-tmpl.yaml +42 -42
- package/expansion-packs/bmad-2d-phaser-game-dev/templates/level-design-doc-tmpl.yaml +65 -65
- package/expansion-packs/bmad-2d-phaser-game-dev/workflows/game-dev-greenfield.yaml +5 -5
- package/expansion-packs/bmad-2d-phaser-game-dev/workflows/game-prototype.yaml +1 -1
- package/expansion-packs/bmad-2d-unity-game-dev/agents/game-developer.md +3 -3
- package/expansion-packs/bmad-2d-unity-game-dev/data/bmad-kb.md +1 -1
- package/expansion-packs/bmad-2d-unity-game-dev/templates/game-brief-tmpl.yaml +23 -23
- package/expansion-packs/bmad-2d-unity-game-dev/templates/game-design-doc-tmpl.yaml +63 -63
- package/expansion-packs/bmad-2d-unity-game-dev/templates/game-story-tmpl.yaml +20 -20
- package/expansion-packs/bmad-2d-unity-game-dev/templates/level-design-doc-tmpl.yaml +65 -65
- package/expansion-packs/bmad-2d-unity-game-dev/workflows/game-dev-greenfield.yaml +5 -5
- package/expansion-packs/bmad-2d-unity-game-dev/workflows/game-prototype.yaml +1 -1
- package/expansion-packs/bmad-infrastructure-devops/templates/infrastructure-architecture-tmpl.yaml +20 -20
- package/expansion-packs/bmad-infrastructure-devops/templates/infrastructure-platform-from-arch-tmpl.yaml +7 -7
- package/package.json +62 -39
- package/prettier.config.mjs +32 -0
- package/release_notes.md +30 -0
- package/tools/bmad-npx-wrapper.js +10 -10
- package/tools/builders/web-builder.js +124 -130
- package/tools/bump-all-versions.js +42 -33
- package/tools/bump-expansion-version.js +23 -16
- package/tools/cli.js +10 -12
- package/tools/flattener/aggregate.js +10 -10
- package/tools/flattener/binary.js +44 -17
- package/tools/flattener/discovery.js +19 -18
- package/tools/flattener/files.js +6 -6
- package/tools/flattener/ignoreRules.js +125 -125
- package/tools/flattener/main.js +201 -304
- package/tools/flattener/projectRoot.js +75 -73
- package/tools/flattener/prompts.js +9 -9
- package/tools/flattener/stats.helpers.js +131 -67
- package/tools/flattener/stats.js +3 -3
- package/tools/flattener/test-matrix.js +201 -193
- package/tools/flattener/xml.js +33 -31
- package/tools/installer/bin/bmad.js +130 -89
- package/tools/installer/config/ide-agent-config.yaml +1 -1
- package/tools/installer/config/install.config.yaml +2 -2
- package/tools/installer/lib/config-loader.js +46 -42
- package/tools/installer/lib/file-manager.js +91 -113
- package/tools/installer/lib/ide-base-setup.js +57 -56
- package/tools/installer/lib/ide-setup.js +375 -343
- package/tools/installer/lib/installer.js +875 -714
- package/tools/installer/lib/memory-profiler.js +54 -53
- package/tools/installer/lib/module-manager.js +19 -15
- package/tools/installer/lib/resource-locator.js +26 -28
- package/tools/installer/package.json +19 -19
- package/tools/lib/dependency-resolver.js +26 -30
- package/tools/lib/yaml-utils.js +7 -7
- package/tools/preview-release-notes.js +66 -0
- package/tools/shared/bannerArt.js +3 -3
- package/tools/sync-installer-version.js +7 -9
- package/tools/update-expansion-version.js +14 -15
- package/tools/upgraders/v3-to-v4-upgrader.js +203 -294
- package/tools/version-bump.js +41 -26
- package/tools/yaml-format.js +56 -43
- package/.github/workflows/promote-to-stable.yml +0 -144
- package/.github/workflows/release.yaml +0 -60
- package/.releaserc.json +0 -21
- package/tools/semantic-release-sync-installer.js +0 -30
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const fs = require('fs-extra');
|
|
2
|
-
const path = require('path');
|
|
2
|
+
const path = require('node:path');
|
|
3
3
|
const yaml = require('js-yaml');
|
|
4
4
|
const { extractYamlFromAgent } = require('../../lib/yaml-utils');
|
|
5
5
|
|
|
@@ -11,7 +11,7 @@ class ConfigLoader {
|
|
|
11
11
|
|
|
12
12
|
async load() {
|
|
13
13
|
if (this.config) return this.config;
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
try {
|
|
16
16
|
const configContent = await fs.readFile(this.configPath, 'utf8');
|
|
17
17
|
this.config = yaml.load(configContent);
|
|
@@ -28,30 +28,30 @@ class ConfigLoader {
|
|
|
28
28
|
|
|
29
29
|
async getAvailableAgents() {
|
|
30
30
|
const agentsDir = path.join(this.getBmadCorePath(), 'agents');
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
try {
|
|
33
33
|
const entries = await fs.readdir(agentsDir, { withFileTypes: true });
|
|
34
34
|
const agents = [];
|
|
35
|
-
|
|
35
|
+
|
|
36
36
|
for (const entry of entries) {
|
|
37
37
|
if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
38
38
|
const agentPath = path.join(agentsDir, entry.name);
|
|
39
39
|
const agentId = path.basename(entry.name, '.md');
|
|
40
|
-
|
|
40
|
+
|
|
41
41
|
try {
|
|
42
42
|
const agentContent = await fs.readFile(agentPath, 'utf8');
|
|
43
|
-
|
|
43
|
+
|
|
44
44
|
// Extract YAML block from agent file
|
|
45
45
|
const yamlContentText = extractYamlFromAgent(agentContent);
|
|
46
46
|
if (yamlContentText) {
|
|
47
47
|
const yamlContent = yaml.load(yamlContentText);
|
|
48
48
|
const agentConfig = yamlContent.agent || {};
|
|
49
|
-
|
|
49
|
+
|
|
50
50
|
agents.push({
|
|
51
51
|
id: agentId,
|
|
52
52
|
name: agentConfig.title || agentConfig.name || agentId,
|
|
53
53
|
file: `bmad-core/agents/${entry.name}`,
|
|
54
|
-
description: agentConfig.whenToUse || 'No description available'
|
|
54
|
+
description: agentConfig.whenToUse || 'No description available',
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
57
|
} catch (error) {
|
|
@@ -59,10 +59,10 @@ class ConfigLoader {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
-
|
|
62
|
+
|
|
63
63
|
// Sort agents by name for consistent display
|
|
64
64
|
agents.sort((a, b) => a.name.localeCompare(b.name));
|
|
65
|
-
|
|
65
|
+
|
|
66
66
|
return agents;
|
|
67
67
|
} catch (error) {
|
|
68
68
|
console.warn(`Failed to read agents directory: ${error.message}`);
|
|
@@ -72,41 +72,45 @@ class ConfigLoader {
|
|
|
72
72
|
|
|
73
73
|
async getAvailableExpansionPacks() {
|
|
74
74
|
const expansionPacksDir = path.join(this.getBmadCorePath(), '..', 'expansion-packs');
|
|
75
|
-
|
|
75
|
+
|
|
76
76
|
try {
|
|
77
77
|
const entries = await fs.readdir(expansionPacksDir, { withFileTypes: true });
|
|
78
78
|
const expansionPacks = [];
|
|
79
|
-
|
|
79
|
+
|
|
80
80
|
for (const entry of entries) {
|
|
81
81
|
if (entry.isDirectory() && !entry.name.startsWith('.')) {
|
|
82
82
|
const packPath = path.join(expansionPacksDir, entry.name);
|
|
83
83
|
const configPath = path.join(packPath, 'config.yaml');
|
|
84
|
-
|
|
84
|
+
|
|
85
85
|
try {
|
|
86
86
|
// Read config.yaml
|
|
87
87
|
const configContent = await fs.readFile(configPath, 'utf8');
|
|
88
88
|
const config = yaml.load(configContent);
|
|
89
|
-
|
|
89
|
+
|
|
90
90
|
expansionPacks.push({
|
|
91
91
|
id: entry.name,
|
|
92
92
|
name: config.name || entry.name,
|
|
93
|
-
description:
|
|
94
|
-
|
|
93
|
+
description:
|
|
94
|
+
config['short-title'] || config.description || 'No description available',
|
|
95
|
+
fullDescription:
|
|
96
|
+
config.description || config['short-title'] || 'No description available',
|
|
95
97
|
version: config.version || '1.0.0',
|
|
96
98
|
author: config.author || 'BMad Team',
|
|
97
99
|
packPath: packPath,
|
|
98
|
-
dependencies: config.dependencies?.agents || []
|
|
100
|
+
dependencies: config.dependencies?.agents || [],
|
|
99
101
|
});
|
|
100
102
|
} catch (error) {
|
|
101
103
|
// Fallback if config.yaml doesn't exist or can't be read
|
|
102
|
-
console.warn(
|
|
103
|
-
|
|
104
|
+
console.warn(
|
|
105
|
+
`Failed to read config for expansion pack ${entry.name}: ${error.message}`,
|
|
106
|
+
);
|
|
107
|
+
|
|
104
108
|
// Try to derive info from directory name as fallback
|
|
105
109
|
const name = entry.name
|
|
106
110
|
.split('-')
|
|
107
|
-
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
111
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
108
112
|
.join(' ');
|
|
109
|
-
|
|
113
|
+
|
|
110
114
|
expansionPacks.push({
|
|
111
115
|
id: entry.name,
|
|
112
116
|
name: name,
|
|
@@ -115,12 +119,12 @@ class ConfigLoader {
|
|
|
115
119
|
version: '1.0.0',
|
|
116
120
|
author: 'BMad Team',
|
|
117
121
|
packPath: packPath,
|
|
118
|
-
dependencies: []
|
|
122
|
+
dependencies: [],
|
|
119
123
|
});
|
|
120
124
|
}
|
|
121
125
|
}
|
|
122
126
|
}
|
|
123
|
-
|
|
127
|
+
|
|
124
128
|
return expansionPacks;
|
|
125
129
|
} catch (error) {
|
|
126
130
|
console.warn(`Failed to read expansion packs directory: ${error.message}`);
|
|
@@ -132,16 +136,16 @@ class ConfigLoader {
|
|
|
132
136
|
// Use DependencyResolver to dynamically parse agent dependencies
|
|
133
137
|
const DependencyResolver = require('../../lib/dependency-resolver');
|
|
134
138
|
const resolver = new DependencyResolver(path.join(__dirname, '..', '..', '..'));
|
|
135
|
-
|
|
139
|
+
|
|
136
140
|
const agentDeps = await resolver.resolveAgentDependencies(agentId);
|
|
137
|
-
|
|
141
|
+
|
|
138
142
|
// Convert to flat list of file paths
|
|
139
143
|
const depPaths = [];
|
|
140
|
-
|
|
144
|
+
|
|
141
145
|
// Core files and utilities are included automatically by DependencyResolver
|
|
142
|
-
|
|
146
|
+
|
|
143
147
|
// Add agent file itself is already handled by installer
|
|
144
|
-
|
|
148
|
+
|
|
145
149
|
// Add all resolved resources
|
|
146
150
|
for (const resource of agentDeps.resources) {
|
|
147
151
|
const filePath = `.bmad-core/${resource.type}/${resource.id}.md`;
|
|
@@ -149,7 +153,7 @@ class ConfigLoader {
|
|
|
149
153
|
depPaths.push(filePath);
|
|
150
154
|
}
|
|
151
155
|
}
|
|
152
|
-
|
|
156
|
+
|
|
153
157
|
return depPaths;
|
|
154
158
|
}
|
|
155
159
|
|
|
@@ -175,25 +179,25 @@ class ConfigLoader {
|
|
|
175
179
|
|
|
176
180
|
async getAvailableTeams() {
|
|
177
181
|
const teamsDir = path.join(this.getBmadCorePath(), 'agent-teams');
|
|
178
|
-
|
|
182
|
+
|
|
179
183
|
try {
|
|
180
184
|
const entries = await fs.readdir(teamsDir, { withFileTypes: true });
|
|
181
185
|
const teams = [];
|
|
182
|
-
|
|
186
|
+
|
|
183
187
|
for (const entry of entries) {
|
|
184
188
|
if (entry.isFile() && entry.name.endsWith('.yaml')) {
|
|
185
189
|
const teamPath = path.join(teamsDir, entry.name);
|
|
186
|
-
|
|
190
|
+
|
|
187
191
|
try {
|
|
188
192
|
const teamContent = await fs.readFile(teamPath, 'utf8');
|
|
189
193
|
const teamConfig = yaml.load(teamContent);
|
|
190
|
-
|
|
194
|
+
|
|
191
195
|
if (teamConfig.bundle) {
|
|
192
196
|
teams.push({
|
|
193
197
|
id: path.basename(entry.name, '.yaml'),
|
|
194
198
|
name: teamConfig.bundle.name || entry.name,
|
|
195
199
|
description: teamConfig.bundle.description || 'Team configuration',
|
|
196
|
-
icon: teamConfig.bundle.icon || '📋'
|
|
200
|
+
icon: teamConfig.bundle.icon || '📋',
|
|
197
201
|
});
|
|
198
202
|
}
|
|
199
203
|
} catch (error) {
|
|
@@ -201,7 +205,7 @@ class ConfigLoader {
|
|
|
201
205
|
}
|
|
202
206
|
}
|
|
203
207
|
}
|
|
204
|
-
|
|
208
|
+
|
|
205
209
|
return teams;
|
|
206
210
|
} catch (error) {
|
|
207
211
|
console.warn(`Warning: Could not scan teams directory: ${error.message}`);
|
|
@@ -217,16 +221,16 @@ class ConfigLoader {
|
|
|
217
221
|
// Use DependencyResolver to dynamically parse team dependencies
|
|
218
222
|
const DependencyResolver = require('../../lib/dependency-resolver');
|
|
219
223
|
const resolver = new DependencyResolver(path.join(__dirname, '..', '..', '..'));
|
|
220
|
-
|
|
224
|
+
|
|
221
225
|
try {
|
|
222
226
|
const teamDeps = await resolver.resolveTeamDependencies(teamId);
|
|
223
|
-
|
|
227
|
+
|
|
224
228
|
// Convert to flat list of file paths
|
|
225
229
|
const depPaths = [];
|
|
226
|
-
|
|
230
|
+
|
|
227
231
|
// Add team config file
|
|
228
232
|
depPaths.push(`.bmad-core/agent-teams/${teamId}.yaml`);
|
|
229
|
-
|
|
233
|
+
|
|
230
234
|
// Add all agents
|
|
231
235
|
for (const agent of teamDeps.agents) {
|
|
232
236
|
const filePath = `.bmad-core/agents/${agent.id}.md`;
|
|
@@ -234,7 +238,7 @@ class ConfigLoader {
|
|
|
234
238
|
depPaths.push(filePath);
|
|
235
239
|
}
|
|
236
240
|
}
|
|
237
|
-
|
|
241
|
+
|
|
238
242
|
// Add all resolved resources
|
|
239
243
|
for (const resource of teamDeps.resources) {
|
|
240
244
|
const filePath = `.bmad-core/${resource.type}/${resource.id}.${resource.type === 'workflows' ? 'yaml' : 'md'}`;
|
|
@@ -242,7 +246,7 @@ class ConfigLoader {
|
|
|
242
246
|
depPaths.push(filePath);
|
|
243
247
|
}
|
|
244
248
|
}
|
|
245
|
-
|
|
249
|
+
|
|
246
250
|
return depPaths;
|
|
247
251
|
} catch (error) {
|
|
248
252
|
throw new Error(`Failed to resolve team dependencies for ${teamId}: ${error.message}`);
|
|
@@ -250,4 +254,4 @@ class ConfigLoader {
|
|
|
250
254
|
}
|
|
251
255
|
}
|
|
252
256
|
|
|
253
|
-
module.exports = new ConfigLoader();
|
|
257
|
+
module.exports = new ConfigLoader();
|
|
@@ -1,32 +1,24 @@
|
|
|
1
|
-
const fs = require(
|
|
2
|
-
const path = require(
|
|
3
|
-
const crypto = require(
|
|
4
|
-
const yaml = require(
|
|
5
|
-
const chalk = require(
|
|
6
|
-
const { createReadStream, createWriteStream, promises: fsPromises } = require('fs');
|
|
7
|
-
const { pipeline } = require('stream/promises');
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
const crypto = require('node:crypto');
|
|
4
|
+
const yaml = require('js-yaml');
|
|
5
|
+
const chalk = require('chalk');
|
|
6
|
+
const { createReadStream, createWriteStream, promises: fsPromises } = require('node:fs');
|
|
7
|
+
const { pipeline } = require('node:stream/promises');
|
|
8
8
|
const resourceLocator = require('./resource-locator');
|
|
9
9
|
|
|
10
10
|
class FileManager {
|
|
11
|
-
constructor() {
|
|
12
|
-
this.manifestDir = ".bmad-core";
|
|
13
|
-
this.manifestFile = "install-manifest.yaml";
|
|
14
|
-
}
|
|
11
|
+
constructor() {}
|
|
15
12
|
|
|
16
13
|
async copyFile(source, destination) {
|
|
17
14
|
try {
|
|
18
15
|
await fs.ensureDir(path.dirname(destination));
|
|
19
|
-
|
|
16
|
+
|
|
20
17
|
// Use streaming for large files (> 10MB)
|
|
21
18
|
const stats = await fs.stat(source);
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
createWriteStream(destination)
|
|
26
|
-
);
|
|
27
|
-
} else {
|
|
28
|
-
await fs.copy(source, destination);
|
|
29
|
-
}
|
|
19
|
+
await (stats.size > 10 * 1024 * 1024
|
|
20
|
+
? pipeline(createReadStream(source), createWriteStream(destination))
|
|
21
|
+
: fs.copy(source, destination));
|
|
30
22
|
return true;
|
|
31
23
|
} catch (error) {
|
|
32
24
|
console.error(chalk.red(`Failed to copy ${source}:`), error.message);
|
|
@@ -37,32 +29,24 @@ class FileManager {
|
|
|
37
29
|
async copyDirectory(source, destination) {
|
|
38
30
|
try {
|
|
39
31
|
await fs.ensureDir(destination);
|
|
40
|
-
|
|
32
|
+
|
|
41
33
|
// Use streaming copy for large directories
|
|
42
34
|
const files = await resourceLocator.findFiles('**/*', {
|
|
43
35
|
cwd: source,
|
|
44
|
-
nodir: true
|
|
36
|
+
nodir: true,
|
|
45
37
|
});
|
|
46
|
-
|
|
38
|
+
|
|
47
39
|
// Process files in batches to avoid memory issues
|
|
48
40
|
const batchSize = 50;
|
|
49
|
-
for (let
|
|
50
|
-
const batch = files.slice(
|
|
41
|
+
for (let index = 0; index < files.length; index += batchSize) {
|
|
42
|
+
const batch = files.slice(index, index + batchSize);
|
|
51
43
|
await Promise.all(
|
|
52
|
-
batch.map(file =>
|
|
53
|
-
this.copyFile(
|
|
54
|
-
path.join(source, file),
|
|
55
|
-
path.join(destination, file)
|
|
56
|
-
)
|
|
57
|
-
)
|
|
44
|
+
batch.map((file) => this.copyFile(path.join(source, file), path.join(destination, file))),
|
|
58
45
|
);
|
|
59
46
|
}
|
|
60
47
|
return true;
|
|
61
48
|
} catch (error) {
|
|
62
|
-
console.error(
|
|
63
|
-
chalk.red(`Failed to copy directory ${source}:`),
|
|
64
|
-
error.message
|
|
65
|
-
);
|
|
49
|
+
console.error(chalk.red(`Failed to copy directory ${source}:`), error.message);
|
|
66
50
|
return false;
|
|
67
51
|
}
|
|
68
52
|
}
|
|
@@ -73,17 +57,16 @@ class FileManager {
|
|
|
73
57
|
|
|
74
58
|
for (const file of files) {
|
|
75
59
|
const sourcePath = path.join(sourceDir, file);
|
|
76
|
-
const
|
|
60
|
+
const destinationPath = path.join(destDir, file);
|
|
77
61
|
|
|
78
62
|
// Use root replacement if rootValue is provided and file needs it
|
|
79
|
-
const needsRootReplacement =
|
|
80
|
-
|
|
63
|
+
const needsRootReplacement =
|
|
64
|
+
rootValue && (file.endsWith('.md') || file.endsWith('.yaml') || file.endsWith('.yml'));
|
|
65
|
+
|
|
81
66
|
let success = false;
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
success = await this.copyFile(sourcePath, destPath);
|
|
86
|
-
}
|
|
67
|
+
success = await (needsRootReplacement
|
|
68
|
+
? this.copyFileWithRootReplacement(sourcePath, destinationPath, rootValue)
|
|
69
|
+
: this.copyFile(sourcePath, destinationPath));
|
|
87
70
|
|
|
88
71
|
if (success) {
|
|
89
72
|
copied.push(file);
|
|
@@ -97,32 +80,28 @@ class FileManager {
|
|
|
97
80
|
try {
|
|
98
81
|
// Use streaming for hash calculation to reduce memory usage
|
|
99
82
|
const stream = createReadStream(filePath);
|
|
100
|
-
const hash = crypto.createHash(
|
|
101
|
-
|
|
83
|
+
const hash = crypto.createHash('sha256');
|
|
84
|
+
|
|
102
85
|
for await (const chunk of stream) {
|
|
103
86
|
hash.update(chunk);
|
|
104
87
|
}
|
|
105
|
-
|
|
106
|
-
return hash.digest(
|
|
107
|
-
} catch
|
|
88
|
+
|
|
89
|
+
return hash.digest('hex').slice(0, 16);
|
|
90
|
+
} catch {
|
|
108
91
|
return null;
|
|
109
92
|
}
|
|
110
93
|
}
|
|
111
94
|
|
|
112
95
|
async createManifest(installDir, config, files) {
|
|
113
|
-
const manifestPath = path.join(
|
|
114
|
-
installDir,
|
|
115
|
-
this.manifestDir,
|
|
116
|
-
this.manifestFile
|
|
117
|
-
);
|
|
96
|
+
const manifestPath = path.join(installDir, this.manifestDir, this.manifestFile);
|
|
118
97
|
|
|
119
98
|
// Read version from package.json
|
|
120
|
-
let coreVersion =
|
|
99
|
+
let coreVersion = 'unknown';
|
|
121
100
|
try {
|
|
122
101
|
const packagePath = path.join(__dirname, '..', '..', '..', 'package.json');
|
|
123
102
|
const packageJson = require(packagePath);
|
|
124
103
|
coreVersion = packageJson.version;
|
|
125
|
-
} catch
|
|
104
|
+
} catch {
|
|
126
105
|
console.warn("Could not read version from package.json, using 'unknown'");
|
|
127
106
|
}
|
|
128
107
|
|
|
@@ -156,31 +135,23 @@ class FileManager {
|
|
|
156
135
|
}
|
|
157
136
|
|
|
158
137
|
async readManifest(installDir) {
|
|
159
|
-
const manifestPath = path.join(
|
|
160
|
-
installDir,
|
|
161
|
-
this.manifestDir,
|
|
162
|
-
this.manifestFile
|
|
163
|
-
);
|
|
138
|
+
const manifestPath = path.join(installDir, this.manifestDir, this.manifestFile);
|
|
164
139
|
|
|
165
140
|
try {
|
|
166
|
-
const content = await fs.readFile(manifestPath,
|
|
141
|
+
const content = await fs.readFile(manifestPath, 'utf8');
|
|
167
142
|
return yaml.load(content);
|
|
168
|
-
} catch
|
|
143
|
+
} catch {
|
|
169
144
|
return null;
|
|
170
145
|
}
|
|
171
146
|
}
|
|
172
147
|
|
|
173
148
|
async readExpansionPackManifest(installDir, packId) {
|
|
174
|
-
const manifestPath = path.join(
|
|
175
|
-
installDir,
|
|
176
|
-
`.${packId}`,
|
|
177
|
-
this.manifestFile
|
|
178
|
-
);
|
|
149
|
+
const manifestPath = path.join(installDir, `.${packId}`, this.manifestFile);
|
|
179
150
|
|
|
180
151
|
try {
|
|
181
|
-
const content = await fs.readFile(manifestPath,
|
|
152
|
+
const content = await fs.readFile(manifestPath, 'utf8');
|
|
182
153
|
return yaml.load(content);
|
|
183
|
-
} catch
|
|
154
|
+
} catch {
|
|
184
155
|
return null;
|
|
185
156
|
}
|
|
186
157
|
}
|
|
@@ -203,24 +174,24 @@ class FileManager {
|
|
|
203
174
|
async checkFileIntegrity(installDir, manifest) {
|
|
204
175
|
const result = {
|
|
205
176
|
missing: [],
|
|
206
|
-
modified: []
|
|
177
|
+
modified: [],
|
|
207
178
|
};
|
|
208
179
|
|
|
209
180
|
for (const file of manifest.files) {
|
|
210
181
|
const filePath = path.join(installDir, file.path);
|
|
211
|
-
|
|
182
|
+
|
|
212
183
|
// Skip checking the manifest file itself - it will always be different due to timestamps
|
|
213
184
|
if (file.path.endsWith('install-manifest.yaml')) {
|
|
214
185
|
continue;
|
|
215
186
|
}
|
|
216
|
-
|
|
217
|
-
if (
|
|
218
|
-
result.missing.push(file.path);
|
|
219
|
-
} else {
|
|
187
|
+
|
|
188
|
+
if (await this.pathExists(filePath)) {
|
|
220
189
|
const currentHash = await this.calculateFileHash(filePath);
|
|
221
190
|
if (currentHash && currentHash !== file.hash) {
|
|
222
191
|
result.modified.push(file.path);
|
|
223
192
|
}
|
|
193
|
+
} else {
|
|
194
|
+
result.missing.push(file.path);
|
|
224
195
|
}
|
|
225
196
|
}
|
|
226
197
|
|
|
@@ -228,7 +199,7 @@ class FileManager {
|
|
|
228
199
|
}
|
|
229
200
|
|
|
230
201
|
async backupFile(filePath) {
|
|
231
|
-
const backupPath = filePath +
|
|
202
|
+
const backupPath = filePath + '.bak';
|
|
232
203
|
let counter = 1;
|
|
233
204
|
let finalBackupPath = backupPath;
|
|
234
205
|
|
|
@@ -256,7 +227,7 @@ class FileManager {
|
|
|
256
227
|
}
|
|
257
228
|
|
|
258
229
|
async readFile(filePath) {
|
|
259
|
-
return fs.readFile(filePath,
|
|
230
|
+
return fs.readFile(filePath, 'utf8');
|
|
260
231
|
}
|
|
261
232
|
|
|
262
233
|
async writeFile(filePath, content) {
|
|
@@ -269,14 +240,10 @@ class FileManager {
|
|
|
269
240
|
}
|
|
270
241
|
|
|
271
242
|
async createExpansionPackManifest(installDir, packId, config, files) {
|
|
272
|
-
const manifestPath = path.join(
|
|
273
|
-
installDir,
|
|
274
|
-
`.${packId}`,
|
|
275
|
-
this.manifestFile
|
|
276
|
-
);
|
|
243
|
+
const manifestPath = path.join(installDir, `.${packId}`, this.manifestFile);
|
|
277
244
|
|
|
278
245
|
const manifest = {
|
|
279
|
-
version: config.expansionPackVersion || require(
|
|
246
|
+
version: config.expansionPackVersion || require('../../../package.json').version,
|
|
280
247
|
installed_at: new Date().toISOString(),
|
|
281
248
|
install_type: config.installType,
|
|
282
249
|
expansion_pack_id: config.expansionPackId,
|
|
@@ -306,24 +273,24 @@ class FileManager {
|
|
|
306
273
|
|
|
307
274
|
async modifyCoreConfig(installDir, config) {
|
|
308
275
|
const coreConfigPath = path.join(installDir, '.bmad-core', 'core-config.yaml');
|
|
309
|
-
|
|
276
|
+
|
|
310
277
|
try {
|
|
311
278
|
// Read the existing core-config.yaml
|
|
312
279
|
const coreConfigContent = await fs.readFile(coreConfigPath, 'utf8');
|
|
313
280
|
const coreConfig = yaml.load(coreConfigContent);
|
|
314
|
-
|
|
281
|
+
|
|
315
282
|
// Modify sharding settings if provided
|
|
316
283
|
if (config.prdSharded !== undefined) {
|
|
317
284
|
coreConfig.prd.prdSharded = config.prdSharded;
|
|
318
285
|
}
|
|
319
|
-
|
|
286
|
+
|
|
320
287
|
if (config.architectureSharded !== undefined) {
|
|
321
288
|
coreConfig.architecture.architectureSharded = config.architectureSharded;
|
|
322
289
|
}
|
|
323
|
-
|
|
290
|
+
|
|
324
291
|
// Write back the modified config
|
|
325
292
|
await fs.writeFile(coreConfigPath, yaml.dump(coreConfig, { indent: 2 }));
|
|
326
|
-
|
|
293
|
+
|
|
327
294
|
return true;
|
|
328
295
|
} catch (error) {
|
|
329
296
|
console.error(chalk.red(`Failed to modify core-config.yaml:`), error.message);
|
|
@@ -335,31 +302,32 @@ class FileManager {
|
|
|
335
302
|
try {
|
|
336
303
|
// Check file size to determine if we should stream
|
|
337
304
|
const stats = await fs.stat(source);
|
|
338
|
-
|
|
339
|
-
if (stats.size > 5 * 1024 * 1024) {
|
|
305
|
+
|
|
306
|
+
if (stats.size > 5 * 1024 * 1024) {
|
|
307
|
+
// 5MB threshold
|
|
340
308
|
// Use streaming for large files
|
|
341
|
-
const { Transform } = require('stream');
|
|
309
|
+
const { Transform } = require('node:stream');
|
|
342
310
|
const replaceStream = new Transform({
|
|
343
311
|
transform(chunk, encoding, callback) {
|
|
344
|
-
const modified = chunk.toString().
|
|
312
|
+
const modified = chunk.toString().replaceAll('{root}', rootValue);
|
|
345
313
|
callback(null, modified);
|
|
346
|
-
}
|
|
314
|
+
},
|
|
347
315
|
});
|
|
348
|
-
|
|
316
|
+
|
|
349
317
|
await this.ensureDirectory(path.dirname(destination));
|
|
350
318
|
await pipeline(
|
|
351
319
|
createReadStream(source, { encoding: 'utf8' }),
|
|
352
320
|
replaceStream,
|
|
353
|
-
createWriteStream(destination, { encoding: 'utf8' })
|
|
321
|
+
createWriteStream(destination, { encoding: 'utf8' }),
|
|
354
322
|
);
|
|
355
323
|
} else {
|
|
356
324
|
// Regular approach for smaller files
|
|
357
325
|
const content = await fsPromises.readFile(source, 'utf8');
|
|
358
|
-
const updatedContent = content.
|
|
326
|
+
const updatedContent = content.replaceAll('{root}', rootValue);
|
|
359
327
|
await this.ensureDirectory(path.dirname(destination));
|
|
360
328
|
await fsPromises.writeFile(destination, updatedContent, 'utf8');
|
|
361
329
|
}
|
|
362
|
-
|
|
330
|
+
|
|
363
331
|
return true;
|
|
364
332
|
} catch (error) {
|
|
365
333
|
console.error(chalk.red(`Failed to copy ${source} with root replacement:`), error.message);
|
|
@@ -367,45 +335,55 @@ class FileManager {
|
|
|
367
335
|
}
|
|
368
336
|
}
|
|
369
337
|
|
|
370
|
-
async copyDirectoryWithRootReplacement(
|
|
338
|
+
async copyDirectoryWithRootReplacement(
|
|
339
|
+
source,
|
|
340
|
+
destination,
|
|
341
|
+
rootValue,
|
|
342
|
+
fileExtensions = ['.md', '.yaml', '.yml'],
|
|
343
|
+
) {
|
|
371
344
|
try {
|
|
372
345
|
await this.ensureDirectory(destination);
|
|
373
|
-
|
|
346
|
+
|
|
374
347
|
// Get all files in source directory
|
|
375
|
-
const files = await resourceLocator.findFiles('**/*', {
|
|
376
|
-
cwd: source,
|
|
377
|
-
nodir: true
|
|
348
|
+
const files = await resourceLocator.findFiles('**/*', {
|
|
349
|
+
cwd: source,
|
|
350
|
+
nodir: true,
|
|
378
351
|
});
|
|
379
|
-
|
|
352
|
+
|
|
380
353
|
let replacedCount = 0;
|
|
381
|
-
|
|
354
|
+
|
|
382
355
|
for (const file of files) {
|
|
383
356
|
const sourcePath = path.join(source, file);
|
|
384
|
-
const
|
|
385
|
-
|
|
357
|
+
const destinationPath = path.join(destination, file);
|
|
358
|
+
|
|
386
359
|
// Check if this file type should have {root} replacement
|
|
387
|
-
const shouldReplace = fileExtensions.some(
|
|
388
|
-
|
|
360
|
+
const shouldReplace = fileExtensions.some((extension) => file.endsWith(extension));
|
|
361
|
+
|
|
389
362
|
if (shouldReplace) {
|
|
390
|
-
if (await this.copyFileWithRootReplacement(sourcePath,
|
|
363
|
+
if (await this.copyFileWithRootReplacement(sourcePath, destinationPath, rootValue)) {
|
|
391
364
|
replacedCount++;
|
|
392
365
|
}
|
|
393
366
|
} else {
|
|
394
367
|
// Regular copy for files that don't need replacement
|
|
395
|
-
await this.copyFile(sourcePath,
|
|
368
|
+
await this.copyFile(sourcePath, destinationPath);
|
|
396
369
|
}
|
|
397
370
|
}
|
|
398
|
-
|
|
371
|
+
|
|
399
372
|
if (replacedCount > 0) {
|
|
400
373
|
console.log(chalk.dim(` Processed ${replacedCount} files with {root} replacement`));
|
|
401
374
|
}
|
|
402
|
-
|
|
375
|
+
|
|
403
376
|
return true;
|
|
404
377
|
} catch (error) {
|
|
405
|
-
console.error(
|
|
378
|
+
console.error(
|
|
379
|
+
chalk.red(`Failed to copy directory ${source} with root replacement:`),
|
|
380
|
+
error.message,
|
|
381
|
+
);
|
|
406
382
|
return false;
|
|
407
383
|
}
|
|
408
384
|
}
|
|
385
|
+
manifestDir = '.bmad-core';
|
|
386
|
+
manifestFile = 'install-manifest.yaml';
|
|
409
387
|
}
|
|
410
388
|
|
|
411
389
|
module.exports = new FileManager();
|