bmad-setup 1.7.2 → 1.8.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/bin/cli.js +193 -37
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
5
|
-
const { execSync } = require('child_process');
|
|
5
|
+
const { execSync, execFileSync } = require('child_process');
|
|
6
|
+
const https = require('https');
|
|
6
7
|
const fs = require('fs');
|
|
7
8
|
const path = require('path');
|
|
8
9
|
|
|
@@ -32,6 +33,14 @@ function runSafe(cmd, stepName) {
|
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
function runFile(file, args, stepName) {
|
|
37
|
+
try {
|
|
38
|
+
execFileSync(file, args, { stdio: 'inherit' });
|
|
39
|
+
} catch (e) {
|
|
40
|
+
throw new Error(`${stepName} 실패: ${e.message}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
35
44
|
// --- --help / --version ---
|
|
36
45
|
function handleFlags() {
|
|
37
46
|
if (args.includes('--version') || args.includes('-v')) {
|
|
@@ -45,28 +54,69 @@ bmad-setup v${VERSION}
|
|
|
45
54
|
BMAD Framework 서브모듈을 한 줄로 설치합니다.
|
|
46
55
|
|
|
47
56
|
Usage:
|
|
48
|
-
npx bmad-setup 전체 설치 실행
|
|
49
|
-
npx bmad-setup --update 서브모듈 최신화 + 심링크 재생성
|
|
50
|
-
npx bmad-setup --help 도움말 표시
|
|
51
|
-
npx bmad-setup --version 버전 표시
|
|
57
|
+
npx bmad-setup@latest 전체 설치 실행 (worktree 자동 감지)
|
|
58
|
+
npx bmad-setup@latest --update 서브모듈 최신화 + 심링크 재생성 + 부모 참조 갱신
|
|
59
|
+
npx bmad-setup@latest --help 도움말 표시
|
|
60
|
+
npx bmad-setup@latest --version 버전 표시
|
|
52
61
|
|
|
53
62
|
Install steps:
|
|
54
63
|
1. git submodule add (bmad-submodule)
|
|
55
64
|
2. git submodule init & update
|
|
56
65
|
3. .gitmodules ignore=dirty 설정
|
|
57
66
|
4. install.sh 실행 (심볼릭 링크)
|
|
58
|
-
5.
|
|
59
|
-
6.
|
|
67
|
+
5. post-checkout hook 설치 (worktree 자동 지원)
|
|
68
|
+
6. .gitignore 패치
|
|
69
|
+
7. package.json 스크립트 패치
|
|
70
|
+
|
|
71
|
+
Worktree 감지 시 (자동):
|
|
72
|
+
1. git submodule init & update
|
|
73
|
+
2. install.sh 실행 (심볼릭 링크)
|
|
60
74
|
|
|
61
75
|
Update steps (--update):
|
|
62
|
-
1. git submodule
|
|
63
|
-
2.
|
|
76
|
+
1. git -C bmad-submodule fetch + checkout origin/master
|
|
77
|
+
2. 부모 repo submodule 참조 갱신 (git add)
|
|
64
78
|
3. install.sh 재실행 (심링크 갱신)
|
|
79
|
+
|
|
80
|
+
Requirements:
|
|
81
|
+
- git 2.13+
|
|
65
82
|
`);
|
|
66
83
|
process.exit(0);
|
|
67
84
|
}
|
|
68
85
|
}
|
|
69
86
|
|
|
87
|
+
// --- Version check against npm registry ---
|
|
88
|
+
function checkLatestVersion() {
|
|
89
|
+
return new Promise((resolve) => {
|
|
90
|
+
const req = https.get(
|
|
91
|
+
'https://registry.npmjs.org/bmad-setup/latest',
|
|
92
|
+
{ timeout: 3000 },
|
|
93
|
+
(res) => {
|
|
94
|
+
let data = '';
|
|
95
|
+
res.on('data', (chunk) => (data += chunk));
|
|
96
|
+
res.on('end', () => {
|
|
97
|
+
try {
|
|
98
|
+
const latest = JSON.parse(data).version;
|
|
99
|
+
if (latest && latest !== VERSION) {
|
|
100
|
+
console.log('');
|
|
101
|
+
log('\u26a0', `새 버전이 있습니다: v${latest} (현재: v${VERSION})`);
|
|
102
|
+
log('', ' 최신 버전으로 실행하세요: npx bmad-setup@latest');
|
|
103
|
+
console.log('');
|
|
104
|
+
}
|
|
105
|
+
} catch (e) {
|
|
106
|
+
// ignore parse errors
|
|
107
|
+
}
|
|
108
|
+
resolve();
|
|
109
|
+
});
|
|
110
|
+
},
|
|
111
|
+
);
|
|
112
|
+
req.on('error', () => resolve());
|
|
113
|
+
req.on('timeout', () => {
|
|
114
|
+
req.destroy();
|
|
115
|
+
resolve();
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
70
120
|
// --- Step 0: Pre-validation ---
|
|
71
121
|
function validateGitRepo() {
|
|
72
122
|
try {
|
|
@@ -87,40 +137,72 @@ function validateGitRepo() {
|
|
|
87
137
|
}
|
|
88
138
|
}
|
|
89
139
|
|
|
140
|
+
// --- Task 1: git version check ---
|
|
141
|
+
function validateGitVersion() {
|
|
142
|
+
try {
|
|
143
|
+
const versionOutput = runCapture('git --version');
|
|
144
|
+
const match = versionOutput.match(/(\d+)\.(\d+)/);
|
|
145
|
+
if (!match) {
|
|
146
|
+
log('\u26a0', `Warning: git 버전을 파싱할 수 없습니다. (출력: ${versionOutput})`);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const major = parseInt(match[1], 10);
|
|
150
|
+
const minor = parseInt(match[2], 10);
|
|
151
|
+
if (major < 2 || (major === 2 && minor < 13)) {
|
|
152
|
+
log('\u274c', `Error: git 2.13 이상이 필요합니다. (현재: ${versionOutput})`);
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
} catch (e) {
|
|
156
|
+
log('\u274c', 'Error: git 버전을 확인할 수 없습니다.');
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// --- Task 2: worktree detection ---
|
|
162
|
+
function isWorktree() {
|
|
163
|
+
try {
|
|
164
|
+
return fs.existsSync('.git') && fs.statSync('.git').isFile();
|
|
165
|
+
} catch (e) {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
90
170
|
// --- Update mode ---
|
|
91
|
-
function
|
|
92
|
-
logStep(1, 3, 'Submodule 동기화');
|
|
171
|
+
function pullLatest() {
|
|
93
172
|
if (!fs.existsSync(SUBMODULE_DIR)) {
|
|
94
|
-
log(' \u274c', `${SUBMODULE_DIR}/ 디렉토리가 없습니다. 먼저 \`npx bmad-setup\`으로 설치하세요.`);
|
|
173
|
+
log(' \u274c', `${SUBMODULE_DIR}/ 디렉토리가 없습니다. 먼저 \`npx bmad-setup@latest\`으로 설치하세요.`);
|
|
95
174
|
process.exit(1);
|
|
96
175
|
}
|
|
97
|
-
runSafe(
|
|
98
|
-
|
|
176
|
+
runSafe(`git -C ${SUBMODULE_DIR} fetch origin master`, 'Submodule fetch');
|
|
177
|
+
runSafe(`git -C ${SUBMODULE_DIR} checkout origin/master`, 'Submodule checkout');
|
|
178
|
+
log(' \u2714', '최신 버전으로 업데이트 완료');
|
|
99
179
|
return 'done';
|
|
100
180
|
}
|
|
101
181
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
182
|
+
// --- Task 4: update parent ref ---
|
|
183
|
+
function updateParentRef() {
|
|
184
|
+
if (isWorktree()) {
|
|
185
|
+
log(' \u2139', 'Worktree 환경에서는 부모 참조 갱신을 건너뜁니다. 메인 worktree에서 --update를 실행하세요.');
|
|
186
|
+
return 'skipped';
|
|
187
|
+
}
|
|
188
|
+
runSafe(`git add ${SUBMODULE_DIR}`, '부모 repo 참조 갱신');
|
|
189
|
+
log(' \u2714', '부모 repo의 submodule 참조가 staged 되었습니다. 필요 시 commit 하세요.');
|
|
106
190
|
return 'done';
|
|
107
191
|
}
|
|
108
192
|
|
|
109
193
|
function reinstallSymlinks() {
|
|
110
|
-
logStep(3, 3, 'install.sh 재실행 (심링크 갱신)');
|
|
111
194
|
const scriptPath = `./${SUBMODULE_DIR}/install.sh`;
|
|
112
195
|
if (!fs.existsSync(scriptPath)) {
|
|
113
196
|
log(' \u26a0', `${scriptPath} 파일을 찾을 수 없습니다. 스킵합니다.`);
|
|
114
197
|
return 'skipped';
|
|
115
198
|
}
|
|
116
|
-
|
|
199
|
+
runFile('bash', [scriptPath, process.cwd()], 'install.sh 실행');
|
|
117
200
|
log(' \u2714', '심링크 갱신 완료');
|
|
118
201
|
return 'done';
|
|
119
202
|
}
|
|
120
203
|
|
|
121
204
|
// --- Step 1: Submodule 추가 ---
|
|
122
205
|
function addSubmodule() {
|
|
123
|
-
logStep(1, 6, 'Submodule 추가');
|
|
124
206
|
if (fs.existsSync(SUBMODULE_DIR)) {
|
|
125
207
|
log(' \u2714', `${SUBMODULE_DIR}/ 이미 존재합니다. 스킵합니다.`);
|
|
126
208
|
return 'skipped';
|
|
@@ -132,7 +214,6 @@ function addSubmodule() {
|
|
|
132
214
|
|
|
133
215
|
// --- Step 2: Submodule 초기화 ---
|
|
134
216
|
function initSubmodule() {
|
|
135
|
-
logStep(2, 6, 'Submodule 초기화');
|
|
136
217
|
runSafe('git submodule init && git submodule update', 'Submodule 초기화');
|
|
137
218
|
log(' \u2714', '초기화 완료');
|
|
138
219
|
return 'done';
|
|
@@ -140,7 +221,6 @@ function initSubmodule() {
|
|
|
140
221
|
|
|
141
222
|
// --- Step 3: dirty ignore 설정 ---
|
|
142
223
|
function configureDirtyIgnore() {
|
|
143
|
-
logStep(3, 6, 'dirty ignore 설정');
|
|
144
224
|
runSafe(
|
|
145
225
|
`git config -f .gitmodules submodule.${SUBMODULE_DIR}.ignore dirty`,
|
|
146
226
|
'dirty ignore 설정',
|
|
@@ -151,21 +231,74 @@ function configureDirtyIgnore() {
|
|
|
151
231
|
|
|
152
232
|
// --- Step 4: install.sh 실행 ---
|
|
153
233
|
function runInstallScript() {
|
|
154
|
-
logStep(4, 6, 'install.sh 실행 (심볼릭 링크 생성)');
|
|
155
234
|
const scriptPath = `./${SUBMODULE_DIR}/install.sh`;
|
|
156
235
|
if (!fs.existsSync(scriptPath)) {
|
|
157
236
|
log(' \u26a0', `${scriptPath} 파일을 찾을 수 없습니다. 스킵합니다.`);
|
|
158
237
|
return 'skipped';
|
|
159
238
|
}
|
|
160
|
-
|
|
239
|
+
runFile('bash', [scriptPath, process.cwd()], 'install.sh 실행');
|
|
161
240
|
log(' \u2714', '심볼릭 링크 생성 완료');
|
|
162
241
|
return 'done';
|
|
163
242
|
}
|
|
164
243
|
|
|
244
|
+
// --- Task 8: post-checkout hook 설치 ---
|
|
245
|
+
function installPostCheckoutHook() {
|
|
246
|
+
const MARKER_START = '# BMAD-POST-CHECKOUT-START';
|
|
247
|
+
const MARKER_END = '# BMAD-POST-CHECKOUT-END';
|
|
248
|
+
|
|
249
|
+
let hooksDir;
|
|
250
|
+
try {
|
|
251
|
+
const gitCommonDir = runCapture('git rev-parse --git-common-dir');
|
|
252
|
+
hooksDir = path.join(gitCommonDir, 'hooks');
|
|
253
|
+
} catch (e) {
|
|
254
|
+
log(' \u26a0', 'git hooks 디렉토리를 찾을 수 없습니다. 스킵합니다.');
|
|
255
|
+
return 'skipped';
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (!fs.existsSync(hooksDir)) {
|
|
259
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const hookPath = path.join(hooksDir, 'post-checkout');
|
|
263
|
+
let content = '';
|
|
264
|
+
|
|
265
|
+
if (fs.existsSync(hookPath)) {
|
|
266
|
+
content = fs.readFileSync(hookPath, 'utf8');
|
|
267
|
+
if (content.includes(MARKER_START)) {
|
|
268
|
+
log(' \u2714', 'post-checkout hook에 BMAD 섹션이 이미 존재합니다. 스킵합니다.');
|
|
269
|
+
return 'skipped';
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const bmadSection = `
|
|
274
|
+
${MARKER_START}
|
|
275
|
+
# Auto-generated by bmad-setup. Do not edit this section.
|
|
276
|
+
if [ "$3" = "1" ] && [ -d "${SUBMODULE_DIR}" ]; then
|
|
277
|
+
# Branch checkout (including worktree creation)
|
|
278
|
+
git submodule update --init --recursive 2>/dev/null
|
|
279
|
+
if [ -f "${SUBMODULE_DIR}/install.sh" ]; then
|
|
280
|
+
bash ${SUBMODULE_DIR}/install.sh "$(pwd)" 2>/dev/null
|
|
281
|
+
fi
|
|
282
|
+
fi
|
|
283
|
+
${MARKER_END}
|
|
284
|
+
`;
|
|
285
|
+
|
|
286
|
+
if (content) {
|
|
287
|
+
// Append to existing hook
|
|
288
|
+
fs.writeFileSync(hookPath, content.trimEnd() + '\n' + bmadSection, 'utf8');
|
|
289
|
+
} else {
|
|
290
|
+
// Create new hook
|
|
291
|
+
fs.writeFileSync(hookPath, '#!/bin/bash\n' + bmadSection, 'utf8');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Make executable
|
|
295
|
+
fs.chmodSync(hookPath, 0o755);
|
|
296
|
+
log(' \u2714', 'post-checkout hook 설치 완료');
|
|
297
|
+
return 'done';
|
|
298
|
+
}
|
|
299
|
+
|
|
165
300
|
// --- Step 5: .gitignore 패치 ---
|
|
166
301
|
function patchGitignore() {
|
|
167
|
-
logStep(5, 6, '.gitignore 패치');
|
|
168
|
-
|
|
169
302
|
const MARKER_START = '# BMAD symlinks (auto-generated)';
|
|
170
303
|
const MARKER_END = '# End BMAD';
|
|
171
304
|
const entries = ['_bmad', '.claude/commands/bmad-*'];
|
|
@@ -198,8 +331,6 @@ function patchGitignore() {
|
|
|
198
331
|
|
|
199
332
|
// --- Step 6: package.json 패치 ---
|
|
200
333
|
function patchPackageJson() {
|
|
201
|
-
logStep(6, 6, 'package.json 패치');
|
|
202
|
-
|
|
203
334
|
const pkgPath = 'package.json';
|
|
204
335
|
if (!fs.existsSync(pkgPath)) {
|
|
205
336
|
log(' \u26a0', 'package.json이 없습니다. 이 단계를 스킵합니다.');
|
|
@@ -226,9 +357,9 @@ function patchPackageJson() {
|
|
|
226
357
|
|
|
227
358
|
const scriptsToAdd = {
|
|
228
359
|
postinstall:
|
|
229
|
-
'[ -z "$CI" ] && git submodule
|
|
230
|
-
'bmad:install': './bmad-submodule/install.sh',
|
|
231
|
-
'bmad:uninstall': './bmad-submodule/uninstall.sh',
|
|
360
|
+
'[ -z "$CI" ] && git -C bmad-submodule fetch origin master && git -C bmad-submodule checkout origin/master && git add bmad-submodule && ./bmad-submodule/install.sh "$(pwd)" || true',
|
|
361
|
+
'bmad:install': './bmad-submodule/install.sh "$(pwd)"',
|
|
362
|
+
'bmad:uninstall': './bmad-submodule/uninstall.sh "$(pwd)"',
|
|
232
363
|
};
|
|
233
364
|
|
|
234
365
|
let added = 0;
|
|
@@ -257,10 +388,13 @@ function patchPackageJson() {
|
|
|
257
388
|
}
|
|
258
389
|
|
|
259
390
|
// --- Main ---
|
|
260
|
-
function main() {
|
|
391
|
+
async function main() {
|
|
261
392
|
handleFlags();
|
|
262
393
|
|
|
394
|
+
await checkLatestVersion();
|
|
395
|
+
|
|
263
396
|
validateGitRepo();
|
|
397
|
+
validateGitVersion();
|
|
264
398
|
|
|
265
399
|
if (isUpdate) {
|
|
266
400
|
console.log('');
|
|
@@ -268,14 +402,29 @@ function main() {
|
|
|
268
402
|
console.log('');
|
|
269
403
|
|
|
270
404
|
const steps = [
|
|
271
|
-
{ key: 'sync', label: 'Submodule 동기화', fn: updateSubmodule },
|
|
272
405
|
{ key: 'pull', label: 'Submodule 최신화', fn: pullLatest },
|
|
406
|
+
{ key: 'parentRef', label: '부모 참조 갱신', fn: updateParentRef },
|
|
273
407
|
{ key: 'reinstall', label: '심링크 갱신', fn: reinstallSymlinks },
|
|
274
408
|
];
|
|
275
409
|
|
|
276
410
|
return runSteps(steps, 'BMAD 업데이트가 완료되었습니다!');
|
|
277
411
|
}
|
|
278
412
|
|
|
413
|
+
// Task 7: worktree auto-detect
|
|
414
|
+
if (isWorktree()) {
|
|
415
|
+
console.log('');
|
|
416
|
+
console.log('=== BMAD Worktree Setup ===');
|
|
417
|
+
console.log('\u2139 Git worktree 환경이 감지되었습니다.');
|
|
418
|
+
console.log('');
|
|
419
|
+
|
|
420
|
+
const steps = [
|
|
421
|
+
{ key: 'init', label: 'Submodule 초기화', fn: initSubmodule },
|
|
422
|
+
{ key: 'installSh', label: '심볼릭 링크 생성', fn: runInstallScript },
|
|
423
|
+
];
|
|
424
|
+
|
|
425
|
+
return runSteps(steps, 'Worktree BMAD 설정이 완료되었습니다!');
|
|
426
|
+
}
|
|
427
|
+
|
|
279
428
|
console.log('');
|
|
280
429
|
console.log('=== BMAD Submodule Setup ===');
|
|
281
430
|
console.log('');
|
|
@@ -284,7 +433,8 @@ function main() {
|
|
|
284
433
|
{ key: 'submodule', label: 'Submodule 추가', fn: addSubmodule },
|
|
285
434
|
{ key: 'init', label: 'Submodule 초기화', fn: initSubmodule },
|
|
286
435
|
{ key: 'dirtyIgnore', label: 'dirty ignore 설정', fn: configureDirtyIgnore },
|
|
287
|
-
{ key: 'installSh', label: '
|
|
436
|
+
{ key: 'installSh', label: '심볼릭 링크 생성', fn: runInstallScript },
|
|
437
|
+
{ key: 'hook', label: 'post-checkout hook 설치', fn: installPostCheckoutHook },
|
|
288
438
|
{ key: 'gitignore', label: '.gitignore 패치', fn: patchGitignore },
|
|
289
439
|
{ key: 'packageJson', label: 'package.json 패치', fn: patchPackageJson },
|
|
290
440
|
];
|
|
@@ -292,9 +442,12 @@ function main() {
|
|
|
292
442
|
runSteps(steps, 'BMAD 설치가 완료되었습니다!');
|
|
293
443
|
}
|
|
294
444
|
|
|
445
|
+
// Task 3: dynamic logStep in runSteps
|
|
295
446
|
function runSteps(steps, doneMessage) {
|
|
296
447
|
const results = {};
|
|
297
|
-
for (
|
|
448
|
+
for (let i = 0; i < steps.length; i++) {
|
|
449
|
+
const step = steps[i];
|
|
450
|
+
logStep(i + 1, steps.length, step.label);
|
|
298
451
|
try {
|
|
299
452
|
results[step.key] = step.fn();
|
|
300
453
|
} catch (e) {
|
|
@@ -324,4 +477,7 @@ function runSteps(steps, doneMessage) {
|
|
|
324
477
|
console.log('');
|
|
325
478
|
}
|
|
326
479
|
|
|
327
|
-
main()
|
|
480
|
+
main().catch((e) => {
|
|
481
|
+
log('\u274c', e.message);
|
|
482
|
+
process.exit(1);
|
|
483
|
+
});
|