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