bmad-cybersec 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +483 -0
- package/cli.js +77 -0
- package/commands/install.js +301 -0
- package/commands/update.js +417 -0
- package/commands/version.js +18 -0
- package/index.js +2 -0
- package/lib/config.js +21 -0
- package/lib/downloader.js +297 -0
- package/lib/extractor.js +353 -0
- package/lib/git-clone.js +207 -0
- package/lib/logger.js +34 -0
- package/lib/package-merger.js +480 -0
- package/lib/url-validator.js +109 -0
- package/lib/utils.js +44 -0
- package/package.json +55 -0
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import { existsSync, promises as fs } from 'fs';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { promisify } from 'util';
|
|
4
|
+
import { exec } from 'child_process';
|
|
5
|
+
import inquirer from 'inquirer';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import ora from 'ora';
|
|
8
|
+
import { downloadRelease } from '../lib/downloader.js';
|
|
9
|
+
import { extractFramework } from '../lib/extractor.js';
|
|
10
|
+
import { logger } from '../lib/logger.js';
|
|
11
|
+
import { CONFIG } from '../lib/config.js';
|
|
12
|
+
|
|
13
|
+
const execAsync = promisify(exec);
|
|
14
|
+
|
|
15
|
+
// Files to preserve during update
|
|
16
|
+
const PRESERVE_FILES = [
|
|
17
|
+
'_bmad/core/config.yaml',
|
|
18
|
+
'_bmad/_config/',
|
|
19
|
+
'.claude/settings.local.json',
|
|
20
|
+
'.env',
|
|
21
|
+
'.env.local'
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
export async function updateCommand(options) {
|
|
25
|
+
const spinner = ora();
|
|
26
|
+
const targetDir = process.cwd();
|
|
27
|
+
let backupDir = null;
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
// 1. Detect current version (AC-007.1)
|
|
31
|
+
spinner.start('Checking current version...');
|
|
32
|
+
const currentVersion = await getCurrentVersion(targetDir);
|
|
33
|
+
|
|
34
|
+
if (!currentVersion) {
|
|
35
|
+
spinner.fail('BMAD-CYBER not detected in current directory');
|
|
36
|
+
logger.error('Run this command from a directory with BMAD-CYBER installed.');
|
|
37
|
+
logger.info('You can install BMAD-CYBER with: npx bmad-cyber install');
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
spinner.succeed(`Current version: ${currentVersion}`);
|
|
42
|
+
|
|
43
|
+
// Security: Validate version format if specific version requested
|
|
44
|
+
if (options.version && options.version !== 'latest') {
|
|
45
|
+
if (!isValidVersionFormat(options.version)) {
|
|
46
|
+
spinner.fail('Invalid version format');
|
|
47
|
+
logger.error('Version must be in format: v1.2.3 or 1.2.3');
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 2. Check for updates (AC-007.2)
|
|
53
|
+
spinner.start('Checking for updates...');
|
|
54
|
+
const latestRelease = await getLatestVersion(options.version);
|
|
55
|
+
spinner.succeed(`Latest version: ${latestRelease.tag}`);
|
|
56
|
+
|
|
57
|
+
// Security: Prevent downgrade attacks (GH-093-001)
|
|
58
|
+
if (options.version && options.version !== 'latest' && currentVersion !== 'unknown') {
|
|
59
|
+
if (isDowngrade(latestRelease.tag, currentVersion)) {
|
|
60
|
+
logger.warn('\n⚠️ SECURITY WARNING: Downgrade detected!');
|
|
61
|
+
logger.warn(`You are attempting to install ${latestRelease.tag} which is OLDER than your current version ${currentVersion}.`);
|
|
62
|
+
logger.warn('Downgrading to older versions may expose you to known security vulnerabilities.\n');
|
|
63
|
+
|
|
64
|
+
if (!options.force) {
|
|
65
|
+
const { confirmDowngrade } = await inquirer.prompt([
|
|
66
|
+
{
|
|
67
|
+
type: 'confirm',
|
|
68
|
+
name: 'confirmDowngrade',
|
|
69
|
+
message: 'Are you SURE you want to downgrade? (This is not recommended)',
|
|
70
|
+
default: false
|
|
71
|
+
}
|
|
72
|
+
]);
|
|
73
|
+
|
|
74
|
+
if (!confirmDowngrade) {
|
|
75
|
+
logger.info('Downgrade cancelled for security reasons.');
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
logger.warn('Proceeding with downgrade at user request...');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 3. --check flag only checks without installing (AC-007.3)
|
|
85
|
+
if (options.check) {
|
|
86
|
+
showVersionComparison(currentVersion, latestRelease);
|
|
87
|
+
|
|
88
|
+
if (isNewerVersion(latestRelease.tag, currentVersion)) {
|
|
89
|
+
logger.info('\nRun `npx bmad-cyber update` to update.');
|
|
90
|
+
} else {
|
|
91
|
+
logger.success('\nYou are on the latest version!');
|
|
92
|
+
}
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 4. Show version comparison and changelog (AC-007.4)
|
|
97
|
+
showVersionComparison(currentVersion, latestRelease);
|
|
98
|
+
|
|
99
|
+
// Check if update needed
|
|
100
|
+
if (!isNewerVersion(latestRelease.tag, currentVersion) && !options.force) {
|
|
101
|
+
logger.success('You are already on the latest version!');
|
|
102
|
+
|
|
103
|
+
const { forceUpdate } = await inquirer.prompt([
|
|
104
|
+
{
|
|
105
|
+
type: 'confirm',
|
|
106
|
+
name: 'forceUpdate',
|
|
107
|
+
message: 'Would you like to reinstall anyway?',
|
|
108
|
+
default: false
|
|
109
|
+
}
|
|
110
|
+
]);
|
|
111
|
+
|
|
112
|
+
if (!forceUpdate) return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Show changelog
|
|
116
|
+
await showChangelog(currentVersion, latestRelease);
|
|
117
|
+
|
|
118
|
+
// Confirm update
|
|
119
|
+
const { proceed } = await inquirer.prompt([
|
|
120
|
+
{
|
|
121
|
+
type: 'confirm',
|
|
122
|
+
name: 'proceed',
|
|
123
|
+
message: `Update from ${currentVersion} to ${latestRelease.tag}?`,
|
|
124
|
+
default: true
|
|
125
|
+
}
|
|
126
|
+
]);
|
|
127
|
+
|
|
128
|
+
if (!proceed) {
|
|
129
|
+
logger.info('Update cancelled.');
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 5. Backup configurations (AC-007.5)
|
|
134
|
+
spinner.start('Backing up configurations...');
|
|
135
|
+
backupDir = await backupConfigurations(targetDir);
|
|
136
|
+
spinner.succeed(`Configurations backed up to: ${backupDir}`);
|
|
137
|
+
|
|
138
|
+
// 6. Download new version
|
|
139
|
+
spinner.start('Downloading new version...');
|
|
140
|
+
const tarballPath = await downloadRelease({
|
|
141
|
+
version: options.version || 'latest'
|
|
142
|
+
});
|
|
143
|
+
spinner.succeed('Download complete');
|
|
144
|
+
|
|
145
|
+
// 7. Extract with force overwrite (AC-007.7)
|
|
146
|
+
spinner.start('Installing update...');
|
|
147
|
+
await extractFramework(tarballPath, targetDir, {
|
|
148
|
+
force: true,
|
|
149
|
+
withDocs: options.withDocs,
|
|
150
|
+
withDev: options.withDev
|
|
151
|
+
});
|
|
152
|
+
spinner.succeed('Update installed');
|
|
153
|
+
|
|
154
|
+
// 8. Restore configurations (AC-007.6)
|
|
155
|
+
spinner.start('Restoring configurations...');
|
|
156
|
+
await restoreConfigurations(backupDir, targetDir);
|
|
157
|
+
spinner.succeed('Configurations restored');
|
|
158
|
+
|
|
159
|
+
// 9. Run npm install if needed
|
|
160
|
+
spinner.start('Updating dependencies...');
|
|
161
|
+
try {
|
|
162
|
+
await execAsync('npm install', { cwd: targetDir });
|
|
163
|
+
spinner.succeed('Dependencies updated');
|
|
164
|
+
} catch {
|
|
165
|
+
spinner.warn('Dependencies may need manual update');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// 10. Show what changed (AC-007.10)
|
|
169
|
+
console.log('\n');
|
|
170
|
+
logger.success(`Successfully updated to ${latestRelease.tag}!`);
|
|
171
|
+
await showWhatChanged(currentVersion, latestRelease.tag);
|
|
172
|
+
|
|
173
|
+
} catch (error) {
|
|
174
|
+
spinner.fail(`Update failed: ${error.message}`);
|
|
175
|
+
|
|
176
|
+
// AC-007.9: Rollback on failure
|
|
177
|
+
if (backupDir) {
|
|
178
|
+
logger.info('Attempting to restore previous configuration...');
|
|
179
|
+
try {
|
|
180
|
+
await restoreConfigurations(backupDir, targetDir);
|
|
181
|
+
logger.success('Previous configuration restored.');
|
|
182
|
+
} catch (restoreError) {
|
|
183
|
+
logger.error(`Failed to restore backup: ${restoreError.message}`);
|
|
184
|
+
logger.info(`Backup files are at: ${backupDir}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
logger.error('\nYour previous installation should be intact.');
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function getCurrentVersion(targetDir) {
|
|
194
|
+
// Check _bmad/version.json first
|
|
195
|
+
const versionPath = join(targetDir, '_bmad', 'version.json');
|
|
196
|
+
|
|
197
|
+
if (existsSync(versionPath)) {
|
|
198
|
+
try {
|
|
199
|
+
const version = JSON.parse(await fs.readFile(versionPath, 'utf-8'));
|
|
200
|
+
return version.version;
|
|
201
|
+
} catch {
|
|
202
|
+
// Fall through to package.json check
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Try package.json
|
|
207
|
+
const packagePath = join(targetDir, 'package.json');
|
|
208
|
+
if (existsSync(packagePath)) {
|
|
209
|
+
try {
|
|
210
|
+
const pkg = JSON.parse(await fs.readFile(packagePath, 'utf-8'));
|
|
211
|
+
if (pkg.bmadVersion) return pkg.bmadVersion;
|
|
212
|
+
} catch {
|
|
213
|
+
// Ignore parsing errors
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Check for _bmad directory as fallback (means BMAD is installed but version unknown)
|
|
218
|
+
const bmadDir = join(targetDir, '_bmad');
|
|
219
|
+
if (existsSync(bmadDir)) {
|
|
220
|
+
return 'unknown';
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function getLatestVersion(specificVersion) {
|
|
227
|
+
const GITHUB_API = 'https://api.github.com';
|
|
228
|
+
const endpoint = specificVersion && specificVersion !== 'latest'
|
|
229
|
+
? `${GITHUB_API}/repos/${CONFIG.GITHUB_OWNER}/${CONFIG.GITHUB_REPO}/releases/tags/${specificVersion}`
|
|
230
|
+
: `${GITHUB_API}/repos/${CONFIG.GITHUB_OWNER}/${CONFIG.GITHUB_REPO}/releases/latest`;
|
|
231
|
+
|
|
232
|
+
const headers = {
|
|
233
|
+
'Accept': 'application/vnd.github.v3+json',
|
|
234
|
+
'User-Agent': 'bmad-cyber-installer'
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
if (process.env.GITHUB_TOKEN) {
|
|
238
|
+
headers['Authorization'] = `token ${process.env.GITHUB_TOKEN}`;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const response = await fetch(endpoint, { headers });
|
|
242
|
+
|
|
243
|
+
if (!response.ok) {
|
|
244
|
+
if (response.status === 404) {
|
|
245
|
+
throw new Error(`Release ${specificVersion || 'latest'} not found`);
|
|
246
|
+
}
|
|
247
|
+
if (response.status === 403) {
|
|
248
|
+
throw new Error('GitHub API rate limit exceeded. Set GITHUB_TOKEN or try again later.');
|
|
249
|
+
}
|
|
250
|
+
throw new Error(`GitHub API error: ${response.status}`);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const release = await response.json();
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
tag: release.tag_name,
|
|
257
|
+
publishedAt: release.published_at,
|
|
258
|
+
body: release.body
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Compares two semver versions
|
|
264
|
+
* @param {string} latest - Latest version string
|
|
265
|
+
* @param {string} current - Current version string
|
|
266
|
+
* @returns {boolean} True if latest is newer than current
|
|
267
|
+
*/
|
|
268
|
+
function isNewerVersion(latest, current) {
|
|
269
|
+
if (current === 'unknown') return true;
|
|
270
|
+
|
|
271
|
+
// Simple semver comparison
|
|
272
|
+
const latestParts = latest.replace('v', '').split('.').map(Number);
|
|
273
|
+
const currentParts = current.replace('v', '').split('.').map(Number);
|
|
274
|
+
|
|
275
|
+
for (let i = 0; i < 3; i++) {
|
|
276
|
+
const latestPart = latestParts[i] || 0;
|
|
277
|
+
const currentPart = currentParts[i] || 0;
|
|
278
|
+
if (latestPart > currentPart) return true;
|
|
279
|
+
if (latestPart < currentPart) return false;
|
|
280
|
+
}
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Security: Checks if a version would be a downgrade
|
|
286
|
+
* Prevents downgrade attacks where attacker tricks user into installing older vulnerable version
|
|
287
|
+
* @param {string} targetVersion - Version to install
|
|
288
|
+
* @param {string} currentVersion - Currently installed version
|
|
289
|
+
* @returns {boolean} True if this would be a downgrade
|
|
290
|
+
*/
|
|
291
|
+
function isDowngrade(targetVersion, currentVersion) {
|
|
292
|
+
if (currentVersion === 'unknown') return false;
|
|
293
|
+
|
|
294
|
+
const targetParts = targetVersion.replace('v', '').split('.').map(Number);
|
|
295
|
+
const currentParts = currentVersion.replace('v', '').split('.').map(Number);
|
|
296
|
+
|
|
297
|
+
for (let i = 0; i < 3; i++) {
|
|
298
|
+
const targetPart = targetParts[i] || 0;
|
|
299
|
+
const currentPart = currentParts[i] || 0;
|
|
300
|
+
if (targetPart < currentPart) return true;
|
|
301
|
+
if (targetPart > currentPart) return false;
|
|
302
|
+
}
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Security: Validates that the version format is legitimate
|
|
308
|
+
* Prevents injection via malformed version strings
|
|
309
|
+
* @param {string} version - Version string to validate
|
|
310
|
+
* @returns {boolean} True if version format is valid
|
|
311
|
+
*/
|
|
312
|
+
function isValidVersionFormat(version) {
|
|
313
|
+
if (!version || typeof version !== 'string') return false;
|
|
314
|
+
|
|
315
|
+
// Must be either 'latest' or a valid semver-like format (v1.2.3 or 1.2.3)
|
|
316
|
+
if (version === 'latest') return true;
|
|
317
|
+
|
|
318
|
+
// Check for valid semver pattern (with optional 'v' prefix)
|
|
319
|
+
const semverPattern = /^v?\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?$/;
|
|
320
|
+
return semverPattern.test(version);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function showVersionComparison(current, latest) {
|
|
324
|
+
console.log('\n');
|
|
325
|
+
console.log(chalk.bold('Version Comparison:'));
|
|
326
|
+
console.log(` Current: ${chalk.yellow(current)}`);
|
|
327
|
+
console.log(` Latest: ${chalk.green(latest.tag)}`);
|
|
328
|
+
console.log(` Released: ${new Date(latest.publishedAt).toLocaleDateString()}`);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async function showChangelog(fromVersion, release) {
|
|
332
|
+
console.log('\n');
|
|
333
|
+
console.log(chalk.bold("What's New:"));
|
|
334
|
+
console.log(chalk.dim('-'.repeat(40)));
|
|
335
|
+
|
|
336
|
+
// Parse and display release notes
|
|
337
|
+
if (release.body) {
|
|
338
|
+
const lines = release.body.split('\n').slice(0, 15);
|
|
339
|
+
lines.forEach(line => console.log(' ' + line));
|
|
340
|
+
if (release.body.split('\n').length > 15) {
|
|
341
|
+
console.log(chalk.dim(' ... (see full changelog on GitHub)'));
|
|
342
|
+
}
|
|
343
|
+
} else {
|
|
344
|
+
console.log(' No changelog available.');
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
console.log('');
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async function backupConfigurations(targetDir) {
|
|
351
|
+
const timestamp = Date.now();
|
|
352
|
+
const backupDir = join(targetDir, '.bmad-backup', `backup-${timestamp}`);
|
|
353
|
+
await fs.mkdir(backupDir, { recursive: true });
|
|
354
|
+
|
|
355
|
+
for (const file of PRESERVE_FILES) {
|
|
356
|
+
const sourcePath = join(targetDir, file);
|
|
357
|
+
|
|
358
|
+
if (existsSync(sourcePath)) {
|
|
359
|
+
const destPath = join(backupDir, file);
|
|
360
|
+
await fs.mkdir(dirname(destPath), { recursive: true });
|
|
361
|
+
|
|
362
|
+
// Check if it's a directory
|
|
363
|
+
const stat = await fs.stat(sourcePath);
|
|
364
|
+
if (stat.isDirectory()) {
|
|
365
|
+
await copyDirectory(sourcePath, destPath);
|
|
366
|
+
} else {
|
|
367
|
+
await fs.copyFile(sourcePath, destPath);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return backupDir;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
async function copyDirectory(src, dest) {
|
|
376
|
+
await fs.mkdir(dest, { recursive: true });
|
|
377
|
+
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
378
|
+
|
|
379
|
+
for (const entry of entries) {
|
|
380
|
+
const srcPath = join(src, entry.name);
|
|
381
|
+
const destPath = join(dest, entry.name);
|
|
382
|
+
|
|
383
|
+
if (entry.isDirectory()) {
|
|
384
|
+
await copyDirectory(srcPath, destPath);
|
|
385
|
+
} else {
|
|
386
|
+
await fs.copyFile(srcPath, destPath);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
async function restoreConfigurations(backupDir, targetDir) {
|
|
392
|
+
for (const file of PRESERVE_FILES) {
|
|
393
|
+
const backupPath = join(backupDir, file);
|
|
394
|
+
|
|
395
|
+
if (existsSync(backupPath)) {
|
|
396
|
+
const destPath = join(targetDir, file);
|
|
397
|
+
await fs.mkdir(dirname(destPath), { recursive: true });
|
|
398
|
+
|
|
399
|
+
// Check if it's a directory
|
|
400
|
+
const stat = await fs.stat(backupPath);
|
|
401
|
+
if (stat.isDirectory()) {
|
|
402
|
+
await copyDirectory(backupPath, destPath);
|
|
403
|
+
} else {
|
|
404
|
+
await fs.copyFile(backupPath, destPath);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
async function showWhatChanged(_fromVersion, toVersion) {
|
|
411
|
+
console.log(chalk.bold('\nUpdate Summary:'));
|
|
412
|
+
console.log(` Updated to ${chalk.green(toVersion)}`);
|
|
413
|
+
console.log('');
|
|
414
|
+
console.log(' Your configurations have been preserved.');
|
|
415
|
+
console.log(' Run `npm run bmad:health` to verify installation.');
|
|
416
|
+
console.log('');
|
|
417
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { CONFIG } from '../lib/config.js';
|
|
2
|
+
import { logger } from '../lib/logger.js';
|
|
3
|
+
import { isBmadInstalled, getInstalledVersion } from '../lib/utils.js';
|
|
4
|
+
|
|
5
|
+
export function versionCommand() {
|
|
6
|
+
console.log(`bmad-cyber CLI v${CONFIG.VERSION}`);
|
|
7
|
+
|
|
8
|
+
if (isBmadInstalled()) {
|
|
9
|
+
const installedVersion = getInstalledVersion();
|
|
10
|
+
if (installedVersion) {
|
|
11
|
+
console.log(`Installed framework: v${installedVersion}`);
|
|
12
|
+
} else {
|
|
13
|
+
console.log('Installed framework: version unknown');
|
|
14
|
+
}
|
|
15
|
+
} else {
|
|
16
|
+
console.log('No BMAD-CYBER installation detected in current directory');
|
|
17
|
+
}
|
|
18
|
+
}
|
package/index.js
ADDED
package/lib/config.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration constants for the BMAD installer
|
|
3
|
+
* @description Contains version information, GitHub repository details,
|
|
4
|
+
* and other configuration values used throughout the installer.
|
|
5
|
+
* @type {Object}
|
|
6
|
+
* @property {string} VERSION - Current version of the installer
|
|
7
|
+
* @property {string} GITHUB_OWNER - GitHub organization/owner name
|
|
8
|
+
* @property {string} GITHUB_REPO - GitHub repository name
|
|
9
|
+
* @property {number} MIN_NODE_VERSION - Minimum required Node.js major version
|
|
10
|
+
* @property {string} TEMP_DIR_PREFIX - Prefix for temporary directory names
|
|
11
|
+
* @example
|
|
12
|
+
* import { CONFIG } from './config.js';
|
|
13
|
+
* console.log(`Installing from ${CONFIG.GITHUB_OWNER}/${CONFIG.GITHUB_REPO}`);
|
|
14
|
+
*/
|
|
15
|
+
export const CONFIG = {
|
|
16
|
+
VERSION: '2.0.0',
|
|
17
|
+
GITHUB_OWNER: 'SchenLong',
|
|
18
|
+
GITHUB_REPO: 'BMAD-CYBERSEC',
|
|
19
|
+
MIN_NODE_VERSION: 18,
|
|
20
|
+
TEMP_DIR_PREFIX: 'bmad-cyber-install'
|
|
21
|
+
};
|