bmad-method 6.2.3-next.0 → 6.2.3-next.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/package.json +8 -8
- package/src/core-skills/bmad-distillator/resources/distillate-format-reference.md +1 -1
- package/tools/{cli → installer}/bmad-cli.js +3 -1
- package/tools/{cli/lib → installer}/cli-utils.js +3 -4
- package/tools/{cli → installer}/commands/install.js +3 -3
- package/tools/{cli → installer}/commands/status.js +4 -4
- package/tools/{cli → installer}/commands/uninstall.js +5 -5
- package/tools/installer/core/config.js +52 -0
- package/tools/{cli/installers/lib → installer}/core/custom-module-cache.js +1 -1
- package/tools/installer/core/existing-install.js +127 -0
- package/tools/installer/core/install-paths.js +129 -0
- package/tools/installer/core/installer.js +1790 -0
- package/tools/{cli/installers/lib → installer}/core/manifest-generator.js +3 -3
- package/tools/{cli/installers/lib → installer}/core/manifest.js +2 -2
- package/tools/{cli/installers/lib/custom/handler.js → installer/custom-handler.js} +1 -1
- package/tools/{cli/installers/lib → installer}/ide/_config-driven.js +30 -397
- package/tools/{cli/installers/lib → installer}/ide/manager.js +1 -53
- package/tools/installer/ide/platform-codes.js +37 -0
- package/tools/installer/ide/platform-codes.yaml +190 -0
- package/tools/{cli/installers/lib → installer}/ide/shared/module-injections.js +1 -1
- package/tools/{cli/installers/lib → installer}/message-loader.js +2 -2
- package/tools/installer/modules/custom-modules.js +197 -0
- package/tools/installer/modules/external-manager.js +323 -0
- package/tools/{cli/installers/lib/core/config-collector.js → installer/modules/official-modules.js} +714 -43
- package/tools/{cli/lib → installer}/ui.js +65 -299
- package/tools/javascript-conventions.md +5 -0
- package/tools/bmad-npx-wrapper.js +0 -38
- package/tools/cli/installers/lib/core/dependency-resolver.js +0 -743
- package/tools/cli/installers/lib/core/detector.js +0 -223
- package/tools/cli/installers/lib/core/ide-config-manager.js +0 -157
- package/tools/cli/installers/lib/core/installer.js +0 -3002
- package/tools/cli/installers/lib/ide/_base-ide.js +0 -657
- package/tools/cli/installers/lib/ide/platform-codes.js +0 -100
- package/tools/cli/installers/lib/ide/platform-codes.yaml +0 -341
- package/tools/cli/installers/lib/modules/external-manager.js +0 -136
- package/tools/cli/installers/lib/modules/manager.js +0 -928
- package/tools/cli/lib/config.js +0 -213
- package/tools/cli/lib/platform-codes.js +0 -116
- package/tools/lib/xml-utils.js +0 -13
- /package/tools/{cli → installer}/README.md +0 -0
- /package/tools/{cli → installer}/external-official-modules.yaml +0 -0
- /package/tools/{cli/lib → installer}/file-ops.js +0 -0
- /package/tools/{cli/installers/lib → installer}/ide/shared/agent-command-generator.js +0 -0
- /package/tools/{cli/installers/lib → installer}/ide/shared/bmad-artifacts.js +0 -0
- /package/tools/{cli/installers/lib → installer}/ide/shared/path-utils.js +0 -0
- /package/tools/{cli/installers/lib → installer}/ide/shared/skill-manifest.js +0 -0
- /package/tools/{cli/installers/lib → installer}/ide/templates/agent-command-template.md +0 -0
- /package/tools/{cli/installers/lib → installer}/ide/templates/combined/antigravity.md +0 -0
- /package/tools/{cli/installers/lib → installer}/ide/templates/combined/default-agent.md +0 -0
- /package/tools/{cli/installers/lib → installer}/ide/templates/combined/default-task.md +0 -0
- /package/tools/{cli/installers/lib → installer}/ide/templates/combined/default-tool.md +0 -0
- /package/tools/{cli/installers/lib → installer}/ide/templates/combined/default-workflow.md +0 -0
- /package/tools/{cli/installers/lib → installer}/ide/templates/combined/gemini-agent.toml +0 -0
- /package/tools/{cli/installers/lib → installer}/ide/templates/combined/gemini-task.toml +0 -0
- /package/tools/{cli/installers/lib → installer}/ide/templates/combined/gemini-tool.toml +0 -0
- /package/tools/{cli/installers/lib → installer}/ide/templates/combined/gemini-workflow-yaml.toml +0 -0
- /package/tools/{cli/installers/lib → installer}/ide/templates/combined/gemini-workflow.toml +0 -0
- /package/tools/{cli/installers/lib → installer}/ide/templates/combined/kiro-agent.md +0 -0
- /package/tools/{cli/installers/lib → installer}/ide/templates/combined/kiro-task.md +0 -0
- /package/tools/{cli/installers/lib → installer}/ide/templates/combined/kiro-tool.md +0 -0
- /package/tools/{cli/installers/lib → installer}/ide/templates/combined/kiro-workflow.md +0 -0
- /package/tools/{cli/installers/lib → installer}/ide/templates/combined/opencode-agent.md +0 -0
- /package/tools/{cli/installers/lib → installer}/ide/templates/combined/opencode-task.md +0 -0
- /package/tools/{cli/installers/lib → installer}/ide/templates/combined/opencode-tool.md +0 -0
- /package/tools/{cli/installers/lib → installer}/ide/templates/combined/opencode-workflow-yaml.md +0 -0
- /package/tools/{cli/installers/lib → installer}/ide/templates/combined/opencode-workflow.md +0 -0
- /package/tools/{cli/installers/lib → installer}/ide/templates/combined/rovodev.md +0 -0
- /package/tools/{cli/installers/lib → installer}/ide/templates/combined/trae.md +0 -0
- /package/tools/{cli/installers/lib → installer}/ide/templates/combined/windsurf-workflow.md +0 -0
- /package/tools/{cli/installers/lib → installer}/ide/templates/split/.gitkeep +0 -0
- /package/tools/{cli/installers → installer}/install-messages.yaml +0 -0
- /package/tools/{cli/lib → installer}/project-root.js +0 -0
- /package/tools/{cli/lib → installer}/prompts.js +0 -0
- /package/tools/{cli/lib → installer}/yaml-format.js +0 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "bmad-method",
|
|
4
|
-
"version": "6.2.3-next.
|
|
4
|
+
"version": "6.2.3-next.1",
|
|
5
5
|
"description": "Breakthrough Method of Agile AI-driven Development",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"agile",
|
|
@@ -18,14 +18,14 @@
|
|
|
18
18
|
},
|
|
19
19
|
"license": "MIT",
|
|
20
20
|
"author": "Brian (BMad) Madison",
|
|
21
|
-
"main": "tools/
|
|
21
|
+
"main": "tools/installer/bmad-cli.js",
|
|
22
22
|
"bin": {
|
|
23
|
-
"bmad": "tools/bmad-
|
|
24
|
-
"bmad-method": "tools/bmad-
|
|
23
|
+
"bmad": "tools/installer/bmad-cli.js",
|
|
24
|
+
"bmad-method": "tools/installer/bmad-cli.js"
|
|
25
25
|
},
|
|
26
26
|
"scripts": {
|
|
27
|
-
"bmad:install": "node tools/
|
|
28
|
-
"bmad:uninstall": "node tools/
|
|
27
|
+
"bmad:install": "node tools/installer/bmad-cli.js install",
|
|
28
|
+
"bmad:uninstall": "node tools/installer/bmad-cli.js uninstall",
|
|
29
29
|
"docs:build": "node tools/build-docs.mjs",
|
|
30
30
|
"docs:dev": "astro dev --root website",
|
|
31
31
|
"docs:fix-links": "node tools/fix-doc-links.js",
|
|
@@ -34,13 +34,13 @@
|
|
|
34
34
|
"format:check": "prettier --check \"**/*.{js,cjs,mjs,json,yaml}\"",
|
|
35
35
|
"format:fix": "prettier --write \"**/*.{js,cjs,mjs,json,yaml}\"",
|
|
36
36
|
"format:fix:staged": "prettier --write",
|
|
37
|
-
"install:bmad": "node tools/
|
|
37
|
+
"install:bmad": "node tools/installer/bmad-cli.js install",
|
|
38
38
|
"lint": "eslint . --ext .js,.cjs,.mjs,.yaml --max-warnings=0",
|
|
39
39
|
"lint:fix": "eslint . --ext .js,.cjs,.mjs,.yaml --fix",
|
|
40
40
|
"lint:md": "markdownlint-cli2 \"**/*.md\"",
|
|
41
41
|
"prepare": "command -v husky >/dev/null 2>&1 && husky || exit 0",
|
|
42
42
|
"quality": "npm run format:check && npm run lint && npm run lint:md && npm run docs:build && npm run test:install && npm run validate:refs && npm run validate:skills",
|
|
43
|
-
"rebundle": "node tools/
|
|
43
|
+
"rebundle": "node tools/installer/bundlers/bundle-web.js rebundle",
|
|
44
44
|
"test": "npm run test:refs && npm run test:install && npm run lint && npm run lint:md && npm run format:check",
|
|
45
45
|
"test:install": "node test/test-installation-components.js",
|
|
46
46
|
"test:refs": "node test/test-file-refs-csv.js",
|
|
@@ -172,7 +172,7 @@ parts: 1
|
|
|
172
172
|
- Deferred: CI/CD integration, telemetry for module authors, air-gapped enterprise install, zip bundle integrity verification (checksums/signing), deeper non-technical platform integrations
|
|
173
173
|
|
|
174
174
|
## Current Installer (migration context)
|
|
175
|
-
- Entry: `tools/
|
|
175
|
+
- Entry: `tools/installer/bmad-cli.js` (Commander.js) → `tools/installer/core/installer.js`
|
|
176
176
|
- Platforms: `platform-codes.yaml` (~20 platforms with target dirs, legacy dirs, template types, special flags)
|
|
177
177
|
- Manifests: CSV files (skill/workflow/agent-manifest.csv) are current source of truth, not JSON
|
|
178
178
|
- External modules: `external-official-modules.yaml` (CIS, GDS, TEA, WDS) from npm with semver
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
1
3
|
const { program } = require('commander');
|
|
2
4
|
const path = require('node:path');
|
|
3
5
|
const fs = require('node:fs');
|
|
4
6
|
const { execSync } = require('node:child_process');
|
|
5
7
|
const semver = require('semver');
|
|
6
|
-
const prompts = require('./
|
|
8
|
+
const prompts = require('./prompts');
|
|
7
9
|
|
|
8
10
|
// The installer flow uses many sequential @clack/prompts, each adding keypress
|
|
9
11
|
// listeners to stdin. Raise the limit to avoid spurious EventEmitter warnings.
|
|
@@ -8,7 +8,7 @@ const CLIUtils = {
|
|
|
8
8
|
*/
|
|
9
9
|
getVersion() {
|
|
10
10
|
try {
|
|
11
|
-
const packageJson = require(path.join(__dirname, '..', '..', '
|
|
11
|
+
const packageJson = require(path.join(__dirname, '..', '..', 'package.json'));
|
|
12
12
|
return packageJson.version || 'Unknown';
|
|
13
13
|
} catch {
|
|
14
14
|
return 'Unknown';
|
|
@@ -16,10 +16,9 @@ const CLIUtils = {
|
|
|
16
16
|
},
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
* Display BMAD logo using @clack intro + box
|
|
20
|
-
* @param {boolean} _clearScreen - Deprecated, ignored (no longer clears screen)
|
|
19
|
+
* Display BMAD logo and version using @clack intro + box
|
|
21
20
|
*/
|
|
22
|
-
async displayLogo(
|
|
21
|
+
async displayLogo() {
|
|
23
22
|
const version = this.getVersion();
|
|
24
23
|
const color = await prompts.getColor();
|
|
25
24
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const path = require('node:path');
|
|
2
|
-
const prompts = require('../
|
|
3
|
-
const { Installer } = require('../
|
|
4
|
-
const { UI } = require('../
|
|
2
|
+
const prompts = require('../prompts');
|
|
3
|
+
const { Installer } = require('../core/installer');
|
|
4
|
+
const { UI } = require('../ui');
|
|
5
5
|
|
|
6
6
|
const installer = new Installer();
|
|
7
7
|
const ui = new UI();
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
const path = require('node:path');
|
|
2
|
-
const prompts = require('../
|
|
3
|
-
const { Installer } = require('../
|
|
4
|
-
const { Manifest } = require('../
|
|
5
|
-
const { UI } = require('../
|
|
2
|
+
const prompts = require('../prompts');
|
|
3
|
+
const { Installer } = require('../core/installer');
|
|
4
|
+
const { Manifest } = require('../core/manifest');
|
|
5
|
+
const { UI } = require('../ui');
|
|
6
6
|
|
|
7
7
|
const installer = new Installer();
|
|
8
8
|
const manifest = new Manifest();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const path = require('node:path');
|
|
2
2
|
const fs = require('fs-extra');
|
|
3
|
-
const prompts = require('../
|
|
4
|
-
const { Installer } = require('../
|
|
3
|
+
const prompts = require('../prompts');
|
|
4
|
+
const { Installer } = require('../core/installer');
|
|
5
5
|
|
|
6
6
|
const installer = new Installer();
|
|
7
7
|
|
|
@@ -62,9 +62,9 @@ module.exports = {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
const existingInstall = await installer.getStatus(projectDir);
|
|
65
|
-
const version = existingInstall.version
|
|
66
|
-
const modules =
|
|
67
|
-
const ides =
|
|
65
|
+
const version = existingInstall.installed ? existingInstall.version : 'unknown';
|
|
66
|
+
const modules = existingInstall.moduleIds.join(', ');
|
|
67
|
+
const ides = existingInstall.ides.join(', ');
|
|
68
68
|
|
|
69
69
|
const outputFolder = await installer.getOutputFolder(projectDir);
|
|
70
70
|
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clean install configuration built from user input.
|
|
3
|
+
* User input comes from either UI answers or headless CLI flags.
|
|
4
|
+
*/
|
|
5
|
+
class Config {
|
|
6
|
+
constructor({ directory, modules, ides, skipPrompts, verbose, actionType, coreConfig, moduleConfigs, quickUpdate }) {
|
|
7
|
+
this.directory = directory;
|
|
8
|
+
this.modules = Object.freeze([...modules]);
|
|
9
|
+
this.ides = Object.freeze([...ides]);
|
|
10
|
+
this.skipPrompts = skipPrompts;
|
|
11
|
+
this.verbose = verbose;
|
|
12
|
+
this.actionType = actionType;
|
|
13
|
+
this.coreConfig = coreConfig;
|
|
14
|
+
this.moduleConfigs = moduleConfigs;
|
|
15
|
+
this._quickUpdate = quickUpdate;
|
|
16
|
+
Object.freeze(this);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Build a clean install config from raw user input.
|
|
21
|
+
* @param {Object} userInput - UI answers or CLI flags
|
|
22
|
+
* @returns {Config}
|
|
23
|
+
*/
|
|
24
|
+
static build(userInput) {
|
|
25
|
+
const modules = [...(userInput.modules || [])];
|
|
26
|
+
if (userInput.installCore && !modules.includes('core')) {
|
|
27
|
+
modules.unshift('core');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return new Config({
|
|
31
|
+
directory: userInput.directory,
|
|
32
|
+
modules,
|
|
33
|
+
ides: userInput.skipIde ? [] : [...(userInput.ides || [])],
|
|
34
|
+
skipPrompts: userInput.skipPrompts || false,
|
|
35
|
+
verbose: userInput.verbose || false,
|
|
36
|
+
actionType: userInput.actionType,
|
|
37
|
+
coreConfig: userInput.coreConfig || {},
|
|
38
|
+
moduleConfigs: userInput.moduleConfigs || null,
|
|
39
|
+
quickUpdate: userInput._quickUpdate || false,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
hasCoreConfig() {
|
|
44
|
+
return this.coreConfig && Object.keys(this.coreConfig).length > 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
isQuickUpdate() {
|
|
48
|
+
return this._quickUpdate;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = { Config };
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
const path = require('node:path');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const yaml = require('yaml');
|
|
4
|
+
const { Manifest } = require('./manifest');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Immutable snapshot of an existing BMAD installation.
|
|
8
|
+
* Pure query object — no filesystem operations after construction.
|
|
9
|
+
*/
|
|
10
|
+
class ExistingInstall {
|
|
11
|
+
#version;
|
|
12
|
+
|
|
13
|
+
constructor({ installed, version, hasCore, modules, ides, customModules }) {
|
|
14
|
+
this.installed = installed;
|
|
15
|
+
this.#version = version;
|
|
16
|
+
this.hasCore = hasCore;
|
|
17
|
+
this.modules = Object.freeze(modules.map((m) => Object.freeze({ ...m })));
|
|
18
|
+
this.moduleIds = Object.freeze(this.modules.map((m) => m.id));
|
|
19
|
+
this.ides = Object.freeze([...ides]);
|
|
20
|
+
this.customModules = Object.freeze([...customModules]);
|
|
21
|
+
Object.freeze(this);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get version() {
|
|
25
|
+
if (!this.installed) {
|
|
26
|
+
throw new Error('version is not available when nothing is installed');
|
|
27
|
+
}
|
|
28
|
+
return this.#version;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static empty() {
|
|
32
|
+
return new ExistingInstall({
|
|
33
|
+
installed: false,
|
|
34
|
+
version: null,
|
|
35
|
+
hasCore: false,
|
|
36
|
+
modules: [],
|
|
37
|
+
ides: [],
|
|
38
|
+
customModules: [],
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Scan a bmad directory and return an immutable snapshot of what's installed.
|
|
44
|
+
* @param {string} bmadDir - Path to bmad directory
|
|
45
|
+
* @returns {Promise<ExistingInstall>}
|
|
46
|
+
*/
|
|
47
|
+
static async detect(bmadDir) {
|
|
48
|
+
if (!(await fs.pathExists(bmadDir))) {
|
|
49
|
+
return ExistingInstall.empty();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let version = null;
|
|
53
|
+
let hasCore = false;
|
|
54
|
+
const modules = [];
|
|
55
|
+
let ides = [];
|
|
56
|
+
let customModules = [];
|
|
57
|
+
|
|
58
|
+
const manifest = new Manifest();
|
|
59
|
+
const manifestData = await manifest.read(bmadDir);
|
|
60
|
+
if (manifestData) {
|
|
61
|
+
version = manifestData.version;
|
|
62
|
+
if (manifestData.customModules) {
|
|
63
|
+
customModules = manifestData.customModules;
|
|
64
|
+
}
|
|
65
|
+
if (manifestData.ides) {
|
|
66
|
+
ides = manifestData.ides.filter((ide) => ide && typeof ide === 'string');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const corePath = path.join(bmadDir, 'core');
|
|
71
|
+
if (await fs.pathExists(corePath)) {
|
|
72
|
+
hasCore = true;
|
|
73
|
+
|
|
74
|
+
if (!version) {
|
|
75
|
+
const coreConfigPath = path.join(corePath, 'config.yaml');
|
|
76
|
+
if (await fs.pathExists(coreConfigPath)) {
|
|
77
|
+
try {
|
|
78
|
+
const configContent = await fs.readFile(coreConfigPath, 'utf8');
|
|
79
|
+
const config = yaml.parse(configContent);
|
|
80
|
+
if (config.version) {
|
|
81
|
+
version = config.version;
|
|
82
|
+
}
|
|
83
|
+
} catch {
|
|
84
|
+
// Ignore config read errors
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (manifestData && manifestData.modules && manifestData.modules.length > 0) {
|
|
91
|
+
for (const moduleId of manifestData.modules) {
|
|
92
|
+
const modulePath = path.join(bmadDir, moduleId);
|
|
93
|
+
const moduleConfigPath = path.join(modulePath, 'config.yaml');
|
|
94
|
+
|
|
95
|
+
const moduleInfo = {
|
|
96
|
+
id: moduleId,
|
|
97
|
+
path: modulePath,
|
|
98
|
+
version: 'unknown',
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
if (await fs.pathExists(moduleConfigPath)) {
|
|
102
|
+
try {
|
|
103
|
+
const configContent = await fs.readFile(moduleConfigPath, 'utf8');
|
|
104
|
+
const config = yaml.parse(configContent);
|
|
105
|
+
moduleInfo.version = config.version || 'unknown';
|
|
106
|
+
moduleInfo.name = config.name || moduleId;
|
|
107
|
+
moduleInfo.description = config.description;
|
|
108
|
+
} catch {
|
|
109
|
+
// Ignore config read errors
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
modules.push(moduleInfo);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const installed = hasCore || modules.length > 0 || !!manifestData;
|
|
118
|
+
|
|
119
|
+
if (!installed) {
|
|
120
|
+
return ExistingInstall.empty();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return new ExistingInstall({ installed, version, hasCore, modules, ides, customModules });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
module.exports = { ExistingInstall };
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
const path = require('node:path');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const { getProjectRoot } = require('../project-root');
|
|
4
|
+
const { BMAD_FOLDER_NAME } = require('../ide/shared/path-utils');
|
|
5
|
+
|
|
6
|
+
class InstallPaths {
|
|
7
|
+
static async create(config) {
|
|
8
|
+
const srcDir = getProjectRoot();
|
|
9
|
+
await assertReadableDir(srcDir, 'BMAD source root');
|
|
10
|
+
|
|
11
|
+
const pkgPath = path.join(srcDir, 'package.json');
|
|
12
|
+
await assertReadableFile(pkgPath, 'package.json');
|
|
13
|
+
const version = require(pkgPath).version;
|
|
14
|
+
|
|
15
|
+
const projectRoot = path.resolve(config.directory);
|
|
16
|
+
await ensureWritableDir(projectRoot, 'project root');
|
|
17
|
+
|
|
18
|
+
const bmadDir = path.join(projectRoot, BMAD_FOLDER_NAME);
|
|
19
|
+
const isUpdate = await fs.pathExists(bmadDir);
|
|
20
|
+
|
|
21
|
+
const configDir = path.join(bmadDir, '_config');
|
|
22
|
+
const agentsDir = path.join(configDir, 'agents');
|
|
23
|
+
const customCacheDir = path.join(configDir, 'custom');
|
|
24
|
+
const coreDir = path.join(bmadDir, 'core');
|
|
25
|
+
|
|
26
|
+
for (const [dir, label] of [
|
|
27
|
+
[bmadDir, 'bmad directory'],
|
|
28
|
+
[configDir, 'config directory'],
|
|
29
|
+
[agentsDir, 'agents config directory'],
|
|
30
|
+
[customCacheDir, 'custom modules cache'],
|
|
31
|
+
[coreDir, 'core module directory'],
|
|
32
|
+
]) {
|
|
33
|
+
await ensureWritableDir(dir, label);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return new InstallPaths({
|
|
37
|
+
srcDir,
|
|
38
|
+
version,
|
|
39
|
+
projectRoot,
|
|
40
|
+
bmadDir,
|
|
41
|
+
configDir,
|
|
42
|
+
agentsDir,
|
|
43
|
+
customCacheDir,
|
|
44
|
+
coreDir,
|
|
45
|
+
isUpdate,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
constructor(props) {
|
|
50
|
+
Object.assign(this, props);
|
|
51
|
+
Object.freeze(this);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
manifestFile() {
|
|
55
|
+
return path.join(this.configDir, 'manifest.yaml');
|
|
56
|
+
}
|
|
57
|
+
agentManifest() {
|
|
58
|
+
return path.join(this.configDir, 'agent-manifest.csv');
|
|
59
|
+
}
|
|
60
|
+
filesManifest() {
|
|
61
|
+
return path.join(this.configDir, 'files-manifest.csv');
|
|
62
|
+
}
|
|
63
|
+
helpCatalog() {
|
|
64
|
+
return path.join(this.configDir, 'bmad-help.csv');
|
|
65
|
+
}
|
|
66
|
+
moduleDir(name) {
|
|
67
|
+
return path.join(this.bmadDir, name);
|
|
68
|
+
}
|
|
69
|
+
moduleConfig(name) {
|
|
70
|
+
return path.join(this.bmadDir, name, 'config.yaml');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function assertReadableDir(dirPath, label) {
|
|
75
|
+
const stat = await fs.stat(dirPath).catch(() => null);
|
|
76
|
+
if (!stat) {
|
|
77
|
+
throw new Error(`${label} does not exist: ${dirPath}`);
|
|
78
|
+
}
|
|
79
|
+
if (!stat.isDirectory()) {
|
|
80
|
+
throw new Error(`${label} is not a directory: ${dirPath}`);
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
await fs.access(dirPath, fs.constants.R_OK);
|
|
84
|
+
} catch {
|
|
85
|
+
throw new Error(`${label} is not readable: ${dirPath}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function assertReadableFile(filePath, label) {
|
|
90
|
+
const stat = await fs.stat(filePath).catch(() => null);
|
|
91
|
+
if (!stat) {
|
|
92
|
+
throw new Error(`${label} does not exist: ${filePath}`);
|
|
93
|
+
}
|
|
94
|
+
if (!stat.isFile()) {
|
|
95
|
+
throw new Error(`${label} is not a file: ${filePath}`);
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
await fs.access(filePath, fs.constants.R_OK);
|
|
99
|
+
} catch {
|
|
100
|
+
throw new Error(`${label} is not readable: ${filePath}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function ensureWritableDir(dirPath, label) {
|
|
105
|
+
const stat = await fs.stat(dirPath).catch(() => null);
|
|
106
|
+
if (stat && !stat.isDirectory()) {
|
|
107
|
+
throw new Error(`${label} exists but is not a directory: ${dirPath}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
await fs.ensureDir(dirPath);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
if (error.code === 'EACCES') {
|
|
114
|
+
throw new Error(`${label}: permission denied creating directory: ${dirPath}`);
|
|
115
|
+
}
|
|
116
|
+
if (error.code === 'ENOSPC') {
|
|
117
|
+
throw new Error(`${label}: no space left on device: ${dirPath}`);
|
|
118
|
+
}
|
|
119
|
+
throw new Error(`${label}: cannot create directory: ${dirPath} (${error.message})`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
await fs.access(dirPath, fs.constants.R_OK | fs.constants.W_OK);
|
|
124
|
+
} catch {
|
|
125
|
+
throw new Error(`${label} is not writable: ${dirPath}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
module.exports = { InstallPaths };
|