bmad-method 6.2.3-next.25 → 6.2.3-next.27
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/package.json +1 -1
- package/tools/installer/commands/install.js +0 -1
- package/tools/installer/core/existing-install.js +2 -8
- package/tools/installer/core/install-paths.js +0 -3
- package/tools/installer/core/installer.js +5 -396
- package/tools/installer/core/manifest-generator.js +0 -9
- package/tools/installer/core/manifest.js +1 -70
- package/tools/installer/ide/_config-driven.js +14 -7
- package/tools/installer/modules/external-manager.js +93 -50
- package/tools/installer/modules/official-modules.js +14 -64
- package/tools/installer/{external-official-modules.yaml → modules/registry-fallback.yaml} +3 -12
- package/tools/installer/ui.js +18 -673
- package/tools/installer/core/custom-module-cache.js +0 -260
- package/tools/installer/custom-handler.js +0 -112
- package/tools/installer/modules/custom-modules.js +0 -302
|
@@ -97,7 +97,6 @@ class Manifest {
|
|
|
97
97
|
lastUpdated: manifestData.installation?.lastUpdated,
|
|
98
98
|
modules: moduleNames, // Simple array of module names for backward compatibility
|
|
99
99
|
modulesDetailed: hasDetailedModules ? modules : null, // New detailed format
|
|
100
|
-
customModules: manifestData.customModules || [], // Keep for backward compatibility
|
|
101
100
|
ides: manifestData.ides || [],
|
|
102
101
|
};
|
|
103
102
|
} catch (error) {
|
|
@@ -254,7 +253,6 @@ class Manifest {
|
|
|
254
253
|
lastUpdated: manifest.installation?.lastUpdated,
|
|
255
254
|
modules: moduleNames,
|
|
256
255
|
modulesDetailed: hasDetailedModules ? modules : null,
|
|
257
|
-
customModules: manifest.customModules || [],
|
|
258
256
|
ides: manifest.ides || [],
|
|
259
257
|
};
|
|
260
258
|
}
|
|
@@ -783,52 +781,6 @@ class Manifest {
|
|
|
783
781
|
|
|
784
782
|
return configs;
|
|
785
783
|
}
|
|
786
|
-
/**
|
|
787
|
-
* Add a custom module to the manifest with its source path
|
|
788
|
-
* @param {string} bmadDir - Path to bmad directory
|
|
789
|
-
* @param {Object} customModule - Custom module info
|
|
790
|
-
*/
|
|
791
|
-
async addCustomModule(bmadDir, customModule) {
|
|
792
|
-
const manifest = await this.read(bmadDir);
|
|
793
|
-
if (!manifest) {
|
|
794
|
-
throw new Error('No manifest found');
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
if (!manifest.customModules) {
|
|
798
|
-
manifest.customModules = [];
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
// Check if custom module already exists
|
|
802
|
-
const existingIndex = manifest.customModules.findIndex((m) => m.id === customModule.id);
|
|
803
|
-
if (existingIndex === -1) {
|
|
804
|
-
// Add new entry
|
|
805
|
-
manifest.customModules.push(customModule);
|
|
806
|
-
} else {
|
|
807
|
-
// Update existing entry
|
|
808
|
-
manifest.customModules[existingIndex] = customModule;
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
await this.update(bmadDir, { customModules: manifest.customModules });
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
/**
|
|
815
|
-
* Remove a custom module from the manifest
|
|
816
|
-
* @param {string} bmadDir - Path to bmad directory
|
|
817
|
-
* @param {string} moduleId - Module ID to remove
|
|
818
|
-
*/
|
|
819
|
-
async removeCustomModule(bmadDir, moduleId) {
|
|
820
|
-
const manifest = await this.read(bmadDir);
|
|
821
|
-
if (!manifest || !manifest.customModules) {
|
|
822
|
-
return;
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
const index = manifest.customModules.findIndex((m) => m.id === moduleId);
|
|
826
|
-
if (index !== -1) {
|
|
827
|
-
manifest.customModules.splice(index, 1);
|
|
828
|
-
await this.update(bmadDir, { customModules: manifest.customModules });
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
|
|
832
784
|
/**
|
|
833
785
|
* Get module version info from source
|
|
834
786
|
* @param {string} moduleName - Module name/code
|
|
@@ -866,29 +818,8 @@ class Manifest {
|
|
|
866
818
|
};
|
|
867
819
|
}
|
|
868
820
|
|
|
869
|
-
// Custom module: resolve path from source or cache before reading version
|
|
870
|
-
const customSourcePath = moduleSourcePath || path.join(bmadDir, '_config', 'custom', moduleName);
|
|
871
|
-
const version = await this._readMarketplaceVersion(moduleName, customSourcePath);
|
|
872
|
-
|
|
873
|
-
const cacheDir = path.join(bmadDir, '_config', 'custom', moduleName);
|
|
874
|
-
const moduleYamlPath = path.join(cacheDir, 'module.yaml');
|
|
875
|
-
|
|
876
|
-
if (await fs.pathExists(moduleYamlPath)) {
|
|
877
|
-
try {
|
|
878
|
-
const yamlContent = await fs.readFile(moduleYamlPath, 'utf8');
|
|
879
|
-
const moduleConfig = yaml.parse(yamlContent);
|
|
880
|
-
return {
|
|
881
|
-
version: version || moduleConfig.version || null,
|
|
882
|
-
source: 'custom',
|
|
883
|
-
npmPackage: moduleConfig.npmPackage || null,
|
|
884
|
-
repoUrl: moduleConfig.repoUrl || null,
|
|
885
|
-
};
|
|
886
|
-
} catch (error) {
|
|
887
|
-
await prompts.log.warn(`Failed to read module.yaml for ${moduleName}: ${error.message}`);
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
|
|
891
821
|
// Unknown module
|
|
822
|
+
const version = await this._readMarketplaceVersion(moduleName, moduleSourcePath);
|
|
892
823
|
return {
|
|
893
824
|
version,
|
|
894
825
|
source: 'unknown',
|
|
@@ -225,13 +225,20 @@ class ConfigDrivenIdeSetup {
|
|
|
225
225
|
// Migrate legacy target directories (e.g. .opencode/agent → .opencode/agents)
|
|
226
226
|
// Legacy dirs are abandoned entirely, so use prefix matching (null removalSet)
|
|
227
227
|
if (this.installerConfig?.legacy_targets) {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
228
|
+
const legacyDirsExist = await Promise.all(
|
|
229
|
+
this.installerConfig.legacy_targets.map((d) =>
|
|
230
|
+
this.isGlobalPath(d) ? fs.pathExists(d.replace(/^~/, os.homedir())) : fs.pathExists(path.join(projectDir, d)),
|
|
231
|
+
),
|
|
232
|
+
);
|
|
233
|
+
if (legacyDirsExist.some(Boolean)) {
|
|
234
|
+
if (!options.silent) await prompts.log.message(' Migrating legacy directories...');
|
|
235
|
+
for (const legacyDir of this.installerConfig.legacy_targets) {
|
|
236
|
+
if (this.isGlobalPath(legacyDir)) {
|
|
237
|
+
await this.warnGlobalLegacy(legacyDir, options);
|
|
238
|
+
} else {
|
|
239
|
+
await this.cleanupTarget(projectDir, legacyDir, options, null);
|
|
240
|
+
await this.removeEmptyParents(projectDir, legacyDir);
|
|
241
|
+
}
|
|
235
242
|
}
|
|
236
243
|
}
|
|
237
244
|
}
|
|
@@ -1,67 +1,128 @@
|
|
|
1
1
|
const fs = require('fs-extra');
|
|
2
2
|
const os = require('node:os');
|
|
3
3
|
const path = require('node:path');
|
|
4
|
+
const https = require('node:https');
|
|
4
5
|
const { execSync } = require('node:child_process');
|
|
5
6
|
const yaml = require('yaml');
|
|
6
7
|
const prompts = require('../prompts');
|
|
7
8
|
|
|
9
|
+
const REGISTRY_RAW_URL = 'https://raw.githubusercontent.com/bmad-code-org/bmad-plugins-marketplace/main/registry/official.yaml';
|
|
10
|
+
const FALLBACK_CONFIG_PATH = path.join(__dirname, 'registry-fallback.yaml');
|
|
11
|
+
|
|
8
12
|
/**
|
|
9
|
-
* Manages
|
|
10
|
-
*
|
|
13
|
+
* Manages official modules from the remote BMad marketplace registry.
|
|
14
|
+
* Fetches registry/official.yaml from GitHub; falls back to the bundled
|
|
15
|
+
* external-official-modules.yaml when the network is unavailable.
|
|
11
16
|
*
|
|
12
17
|
* @class ExternalModuleManager
|
|
13
18
|
*/
|
|
14
19
|
class ExternalModuleManager {
|
|
15
|
-
constructor() {
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
constructor() {}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Fetch a URL and return the response body as a string.
|
|
24
|
+
* @param {string} url - URL to fetch
|
|
25
|
+
* @param {number} timeout - Timeout in ms (default 10s)
|
|
26
|
+
* @returns {Promise<string>} Response body
|
|
27
|
+
*/
|
|
28
|
+
_fetch(url, timeout = 10_000) {
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
const req = https
|
|
31
|
+
.get(url, { timeout }, (res) => {
|
|
32
|
+
// Follow one redirect (GitHub sometimes 301s)
|
|
33
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
34
|
+
return this._fetch(res.headers.location, timeout).then(resolve, reject);
|
|
35
|
+
}
|
|
36
|
+
if (res.statusCode !== 200) {
|
|
37
|
+
return reject(new Error(`HTTP ${res.statusCode}`));
|
|
38
|
+
}
|
|
39
|
+
let data = '';
|
|
40
|
+
res.on('data', (chunk) => (data += chunk));
|
|
41
|
+
res.on('end', () => resolve(data));
|
|
42
|
+
})
|
|
43
|
+
.on('error', reject)
|
|
44
|
+
.on('timeout', () => {
|
|
45
|
+
req.destroy();
|
|
46
|
+
reject(new Error('Request timed out'));
|
|
47
|
+
});
|
|
48
|
+
});
|
|
18
49
|
}
|
|
19
50
|
|
|
20
51
|
/**
|
|
21
|
-
* Load
|
|
22
|
-
*
|
|
52
|
+
* Load the official modules registry from GitHub, falling back to the
|
|
53
|
+
* bundled YAML file if the fetch fails.
|
|
54
|
+
* @returns {Object} Parsed YAML content with modules array
|
|
23
55
|
*/
|
|
24
56
|
async loadExternalModulesConfig() {
|
|
25
57
|
if (this.cachedModules) {
|
|
26
58
|
return this.cachedModules;
|
|
27
59
|
}
|
|
28
60
|
|
|
61
|
+
// Try remote registry first
|
|
29
62
|
try {
|
|
30
|
-
const content = await
|
|
63
|
+
const content = await this._fetch(REGISTRY_RAW_URL);
|
|
64
|
+
const config = yaml.parse(content);
|
|
65
|
+
if (config?.modules?.length) {
|
|
66
|
+
this.cachedModules = config;
|
|
67
|
+
return config;
|
|
68
|
+
}
|
|
69
|
+
} catch {
|
|
70
|
+
// Fall through to local fallback
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Fallback to bundled file
|
|
74
|
+
try {
|
|
75
|
+
const content = await fs.readFile(FALLBACK_CONFIG_PATH, 'utf8');
|
|
31
76
|
const config = yaml.parse(content);
|
|
32
77
|
this.cachedModules = config;
|
|
78
|
+
await prompts.log.warn('Could not reach BMad registry; using bundled module list.');
|
|
33
79
|
return config;
|
|
34
80
|
} catch (error) {
|
|
35
|
-
await prompts.log.warn(`Failed to load
|
|
36
|
-
return { modules:
|
|
81
|
+
await prompts.log.warn(`Failed to load modules config: ${error.message}`);
|
|
82
|
+
return { modules: [] };
|
|
37
83
|
}
|
|
38
84
|
}
|
|
39
85
|
|
|
40
86
|
/**
|
|
41
|
-
*
|
|
87
|
+
* Normalize a module entry from either the remote registry format
|
|
88
|
+
* (snake_case, array) or the legacy bundled format (kebab-case, object map).
|
|
89
|
+
* @param {Object} mod - Raw module config from YAML
|
|
90
|
+
* @param {string} [key] - Key name (only for legacy map format)
|
|
91
|
+
* @returns {Object} Normalized module info
|
|
92
|
+
*/
|
|
93
|
+
_normalizeModule(mod, key) {
|
|
94
|
+
return {
|
|
95
|
+
key: key || mod.name,
|
|
96
|
+
url: mod.repository || mod.url,
|
|
97
|
+
moduleDefinition: mod.module_definition || mod['module-definition'],
|
|
98
|
+
code: mod.code,
|
|
99
|
+
name: mod.display_name || mod.name,
|
|
100
|
+
description: mod.description || '',
|
|
101
|
+
defaultSelected: mod.default_selected === true || mod.defaultSelected === true,
|
|
102
|
+
type: mod.type || 'bmad-org',
|
|
103
|
+
npmPackage: mod.npm_package || mod.npmPackage || null,
|
|
104
|
+
builtIn: mod.built_in === true,
|
|
105
|
+
isExternal: mod.built_in !== true,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Get list of available modules from the registry
|
|
42
111
|
* @returns {Array<Object>} Array of module info objects
|
|
43
112
|
*/
|
|
44
113
|
async listAvailable() {
|
|
45
114
|
const config = await this.loadExternalModulesConfig();
|
|
46
|
-
const modules = [];
|
|
47
115
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
url: moduleConfig.url,
|
|
52
|
-
moduleDefinition: moduleConfig['module-definition'],
|
|
53
|
-
code: moduleConfig.code,
|
|
54
|
-
name: moduleConfig.name,
|
|
55
|
-
header: moduleConfig.header,
|
|
56
|
-
subheader: moduleConfig.subheader,
|
|
57
|
-
description: moduleConfig.description || '',
|
|
58
|
-
defaultSelected: moduleConfig.defaultSelected === true,
|
|
59
|
-
type: moduleConfig.type || 'community', // bmad-org or community
|
|
60
|
-
npmPackage: moduleConfig.npmPackage || null, // Include npm package name
|
|
61
|
-
isExternal: true,
|
|
62
|
-
});
|
|
116
|
+
// Remote format: modules is an array
|
|
117
|
+
if (Array.isArray(config.modules)) {
|
|
118
|
+
return config.modules.map((mod) => this._normalizeModule(mod));
|
|
63
119
|
}
|
|
64
120
|
|
|
121
|
+
// Legacy bundled format: modules is an object map
|
|
122
|
+
const modules = [];
|
|
123
|
+
for (const [key, mod] of Object.entries(config.modules || {})) {
|
|
124
|
+
modules.push(this._normalizeModule(mod, key));
|
|
125
|
+
}
|
|
65
126
|
return modules;
|
|
66
127
|
}
|
|
67
128
|
|
|
@@ -81,27 +142,8 @@ class ExternalModuleManager {
|
|
|
81
142
|
* @returns {Object|null} Module info or null if not found
|
|
82
143
|
*/
|
|
83
144
|
async getModuleByKey(key) {
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (!moduleConfig) {
|
|
88
|
-
return null;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return {
|
|
92
|
-
key,
|
|
93
|
-
url: moduleConfig.url,
|
|
94
|
-
moduleDefinition: moduleConfig['module-definition'],
|
|
95
|
-
code: moduleConfig.code,
|
|
96
|
-
name: moduleConfig.name,
|
|
97
|
-
header: moduleConfig.header,
|
|
98
|
-
subheader: moduleConfig.subheader,
|
|
99
|
-
description: moduleConfig.description || '',
|
|
100
|
-
defaultSelected: moduleConfig.defaultSelected === true,
|
|
101
|
-
type: moduleConfig.type || 'community', // bmad-org or community
|
|
102
|
-
npmPackage: moduleConfig.npmPackage || null, // Include npm package name
|
|
103
|
-
isExternal: true,
|
|
104
|
-
};
|
|
145
|
+
const modules = await this.listAvailable();
|
|
146
|
+
return modules.find((m) => m.key === key) || null;
|
|
105
147
|
}
|
|
106
148
|
|
|
107
149
|
/**
|
|
@@ -154,7 +196,7 @@ class ExternalModuleManager {
|
|
|
154
196
|
const moduleInfo = await this.getModuleByCode(moduleCode);
|
|
155
197
|
|
|
156
198
|
if (!moduleInfo) {
|
|
157
|
-
throw new Error(`External module '${moduleCode}' not found in
|
|
199
|
+
throw new Error(`External module '${moduleCode}' not found in the BMad registry`);
|
|
158
200
|
}
|
|
159
201
|
|
|
160
202
|
const cacheDir = this.getExternalCacheDir();
|
|
@@ -304,7 +346,7 @@ class ExternalModuleManager {
|
|
|
304
346
|
async findExternalModuleSource(moduleCode, options = {}) {
|
|
305
347
|
const moduleInfo = await this.getModuleByCode(moduleCode);
|
|
306
348
|
|
|
307
|
-
if (!moduleInfo) {
|
|
349
|
+
if (!moduleInfo || moduleInfo.builtIn) {
|
|
308
350
|
return null;
|
|
309
351
|
}
|
|
310
352
|
|
|
@@ -349,6 +391,7 @@ class ExternalModuleManager {
|
|
|
349
391
|
// Nothing found: return configured path (preserves old behavior for error messaging)
|
|
350
392
|
return path.dirname(configuredPath);
|
|
351
393
|
}
|
|
394
|
+
cachedModules = null;
|
|
352
395
|
}
|
|
353
396
|
|
|
354
397
|
module.exports = { ExternalModuleManager };
|
|
@@ -98,11 +98,10 @@ class OfficialModules {
|
|
|
98
98
|
/**
|
|
99
99
|
* List all available built-in modules (core and bmm).
|
|
100
100
|
* All other modules come from external-official-modules.yaml
|
|
101
|
-
* @returns {Object} Object with modules array
|
|
101
|
+
* @returns {Object} Object with modules array
|
|
102
102
|
*/
|
|
103
103
|
async listAvailable() {
|
|
104
104
|
const modules = [];
|
|
105
|
-
const customModules = [];
|
|
106
105
|
|
|
107
106
|
// Add built-in core module (directly under src/core-skills)
|
|
108
107
|
const corePath = getSourcePath('core-skills');
|
|
@@ -122,7 +121,7 @@ class OfficialModules {
|
|
|
122
121
|
}
|
|
123
122
|
}
|
|
124
123
|
|
|
125
|
-
return { modules
|
|
124
|
+
return { modules };
|
|
126
125
|
}
|
|
127
126
|
|
|
128
127
|
/**
|
|
@@ -133,25 +132,12 @@ class OfficialModules {
|
|
|
133
132
|
* @returns {Object|null} Module info or null if not a valid module
|
|
134
133
|
*/
|
|
135
134
|
async getModuleInfo(modulePath, defaultName, sourceDescription) {
|
|
136
|
-
// Check for module structure (module.yaml OR custom.yaml)
|
|
137
135
|
const moduleConfigPath = path.join(modulePath, 'module.yaml');
|
|
138
|
-
const rootCustomConfigPath = path.join(modulePath, 'custom.yaml');
|
|
139
|
-
let configPath = null;
|
|
140
136
|
|
|
141
|
-
if (await fs.pathExists(moduleConfigPath)) {
|
|
142
|
-
configPath = moduleConfigPath;
|
|
143
|
-
} else if (await fs.pathExists(rootCustomConfigPath)) {
|
|
144
|
-
configPath = rootCustomConfigPath;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Skip if this doesn't look like a module
|
|
148
|
-
if (!configPath) {
|
|
137
|
+
if (!(await fs.pathExists(moduleConfigPath))) {
|
|
149
138
|
return null;
|
|
150
139
|
}
|
|
151
140
|
|
|
152
|
-
// Mark as custom if it's using custom.yaml OR if it's outside src/bmm or src/core
|
|
153
|
-
const isCustomSource =
|
|
154
|
-
sourceDescription !== 'src/bmm-skills' && sourceDescription !== 'src/core-skills' && sourceDescription !== 'src/modules';
|
|
155
141
|
const moduleInfo = {
|
|
156
142
|
id: defaultName,
|
|
157
143
|
path: modulePath,
|
|
@@ -162,12 +148,11 @@ class OfficialModules {
|
|
|
162
148
|
description: 'BMAD Module',
|
|
163
149
|
version: '5.0.0',
|
|
164
150
|
source: sourceDescription,
|
|
165
|
-
isCustom: configPath === rootCustomConfigPath || isCustomSource,
|
|
166
151
|
};
|
|
167
152
|
|
|
168
153
|
// Read module config for metadata
|
|
169
154
|
try {
|
|
170
|
-
const configContent = await fs.readFile(
|
|
155
|
+
const configContent = await fs.readFile(moduleConfigPath, 'utf8');
|
|
171
156
|
const config = yaml.parse(configContent);
|
|
172
157
|
|
|
173
158
|
// Use the code property as the id if available
|
|
@@ -824,20 +809,15 @@ class OfficialModules {
|
|
|
824
809
|
const results = [];
|
|
825
810
|
|
|
826
811
|
for (const moduleName of modules) {
|
|
827
|
-
// Resolve module.yaml path -
|
|
812
|
+
// Resolve module.yaml path - standard location first, then OfficialModules search
|
|
828
813
|
let moduleConfigPath = null;
|
|
829
|
-
const
|
|
830
|
-
if (
|
|
831
|
-
moduleConfigPath =
|
|
814
|
+
const standardPath = path.join(getModulePath(moduleName), 'module.yaml');
|
|
815
|
+
if (await fs.pathExists(standardPath)) {
|
|
816
|
+
moduleConfigPath = standardPath;
|
|
832
817
|
} else {
|
|
833
|
-
const
|
|
834
|
-
if (
|
|
835
|
-
moduleConfigPath =
|
|
836
|
-
} else {
|
|
837
|
-
const moduleSourcePath = await this.findModuleSource(moduleName, { silent: true });
|
|
838
|
-
if (moduleSourcePath) {
|
|
839
|
-
moduleConfigPath = path.join(moduleSourcePath, 'module.yaml');
|
|
840
|
-
}
|
|
818
|
+
const moduleSourcePath = await this.findModuleSource(moduleName, { silent: true });
|
|
819
|
+
if (moduleSourcePath) {
|
|
820
|
+
moduleConfigPath = path.join(moduleSourcePath, 'module.yaml');
|
|
841
821
|
}
|
|
842
822
|
}
|
|
843
823
|
|
|
@@ -882,12 +862,9 @@ class OfficialModules {
|
|
|
882
862
|
* @param {Array} modules - List of modules to configure (including 'core')
|
|
883
863
|
* @param {string} projectDir - Target project directory
|
|
884
864
|
* @param {Object} options - Additional options
|
|
885
|
-
* @param {Map} options.customModulePaths - Map of module ID to source path for custom modules
|
|
886
865
|
* @param {boolean} options.skipPrompts - Skip prompts and use defaults (for --yes flag)
|
|
887
866
|
*/
|
|
888
867
|
async collectAllConfigurations(modules, projectDir, options = {}) {
|
|
889
|
-
// Store custom module paths for use in collectModuleConfig
|
|
890
|
-
this.customModulePaths = options.customModulePaths || new Map();
|
|
891
868
|
this.skipPrompts = options.skipPrompts || false;
|
|
892
869
|
this.modulesToCustomize = undefined;
|
|
893
870
|
await this.loadExistingConfig(projectDir);
|
|
@@ -1042,25 +1019,7 @@ class OfficialModules {
|
|
|
1042
1019
|
}
|
|
1043
1020
|
}
|
|
1044
1021
|
|
|
1045
|
-
|
|
1046
|
-
let isCustomModule = false;
|
|
1047
|
-
|
|
1048
|
-
if (await fs.pathExists(moduleConfigPath)) {
|
|
1049
|
-
configPath = moduleConfigPath;
|
|
1050
|
-
} else {
|
|
1051
|
-
// Check if this is a custom module with custom.yaml
|
|
1052
|
-
const moduleSourcePath = await this.findModuleSource(moduleName, { silent: true });
|
|
1053
|
-
|
|
1054
|
-
if (moduleSourcePath) {
|
|
1055
|
-
const rootCustomConfigPath = path.join(moduleSourcePath, 'custom.yaml');
|
|
1056
|
-
|
|
1057
|
-
if (await fs.pathExists(rootCustomConfigPath)) {
|
|
1058
|
-
isCustomModule = true;
|
|
1059
|
-
// For custom modules, we don't have an install-config schema, so just use existing values
|
|
1060
|
-
// The custom.yaml values will be loaded and merged during installation
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1022
|
+
if (!(await fs.pathExists(moduleConfigPath))) {
|
|
1064
1023
|
// No config schema for this module - use existing values
|
|
1065
1024
|
if (this._existingConfig && this._existingConfig[moduleName]) {
|
|
1066
1025
|
if (!this.collectedConfig[moduleName]) {
|
|
@@ -1071,7 +1030,7 @@ class OfficialModules {
|
|
|
1071
1030
|
return false;
|
|
1072
1031
|
}
|
|
1073
1032
|
|
|
1074
|
-
const configContent = await fs.readFile(
|
|
1033
|
+
const configContent = await fs.readFile(moduleConfigPath, 'utf8');
|
|
1075
1034
|
const moduleConfig = yaml.parse(configContent);
|
|
1076
1035
|
|
|
1077
1036
|
if (!moduleConfig) {
|
|
@@ -1332,16 +1291,7 @@ class OfficialModules {
|
|
|
1332
1291
|
this.allAnswers = {};
|
|
1333
1292
|
}
|
|
1334
1293
|
// Load module's config
|
|
1335
|
-
|
|
1336
|
-
let moduleConfigPath = null;
|
|
1337
|
-
|
|
1338
|
-
if (this.customModulePaths && this.customModulePaths.has(moduleName)) {
|
|
1339
|
-
const customPath = this.customModulePaths.get(moduleName);
|
|
1340
|
-
moduleConfigPath = path.join(customPath, 'module.yaml');
|
|
1341
|
-
} else {
|
|
1342
|
-
// Try the standard src/modules location
|
|
1343
|
-
moduleConfigPath = path.join(getModulePath(moduleName), 'module.yaml');
|
|
1344
|
-
}
|
|
1294
|
+
let moduleConfigPath = path.join(getModulePath(moduleName), 'module.yaml');
|
|
1345
1295
|
|
|
1346
1296
|
// If not found in src/modules or custom paths, search the project
|
|
1347
1297
|
if (!(await fs.pathExists(moduleConfigPath))) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
#
|
|
2
|
-
#
|
|
1
|
+
# Fallback module registry — used only when the BMad Marketplace repo
|
|
2
|
+
# (bmad-code-org/bmad-plugins-marketplace) is unreachable.
|
|
3
|
+
# The remote registry/official.yaml is the source of truth.
|
|
3
4
|
|
|
4
5
|
modules:
|
|
5
6
|
bmad-builder:
|
|
@@ -41,13 +42,3 @@ modules:
|
|
|
41
42
|
defaultSelected: false
|
|
42
43
|
type: bmad-org
|
|
43
44
|
npmPackage: bmad-method-test-architecture-enterprise
|
|
44
|
-
|
|
45
|
-
whiteport-design-studio:
|
|
46
|
-
url: https://github.com/bmad-code-org/bmad-method-wds-expansion
|
|
47
|
-
module-definition: src/module.yaml
|
|
48
|
-
code: wds
|
|
49
|
-
name: "Whiteport Design Studio (For UX Professionals)"
|
|
50
|
-
description: "Whiteport Design Studio (For UX Professionals)"
|
|
51
|
-
defaultSelected: false
|
|
52
|
-
type: community
|
|
53
|
-
npmPackage: bmad-method-wds-expansion
|