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.
Files changed (2) hide show
  1. package/bin/cli.js +193 -37
  2. 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. .gitignore 패치
59
- 6. package.json 스크립트 패치
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 update --init --recursive
63
- 2. git -C bmad-submodule pull origin master
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 updateSubmodule() {
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('git submodule update --init --recursive', 'Submodule 동기화');
98
- log(' \u2714', 'Submodule 동기화 완료');
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
- function pullLatest() {
103
- logStep(2, 3, 'Submodule 최신화 (pull origin master)');
104
- runSafe(`git -C ${SUBMODULE_DIR} pull origin master`, 'Submodule pull');
105
- log(' \u2714', '최신 버전으로 업데이트 완료');
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
- runSafe(`bash ${scriptPath}`, 'install.sh 실행');
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
- runSafe(`bash ${scriptPath}`, 'install.sh 실행');
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 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',
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: 'install.sh 실행', fn: runInstallScript },
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 (const step of steps) {
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
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bmad-setup",
3
- "version": "1.7.2",
3
+ "version": "1.8.1",
4
4
  "description": "BMAD Framework submodule installer - one command setup",
5
5
  "bin": {
6
6
  "bmad-setup": "bin/cli.js"