projectops 4.6.0 → 4.6.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/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  > 이슈 등록부터 커밋, 보고서, 배포까지. 개발자는 코드만 작성하세요.
8
8
 
9
9
  <!-- AUTO-VERSION-SECTION: DO NOT EDIT MANUALLY -->
10
- ## 최신 버전 : v4.5.0 (2026-09-16)
10
+ ## 최신 버전 : v4.6.0 (2026-09-16)
11
11
 
12
12
  [전체 버전 기록 보기](CHANGELOG.md)
13
13
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "projectops",
3
- "version": "4.6.0",
3
+ "version": "4.6.1",
4
4
  "description": "ProjectOps — 완전 자동화 GitHub 프로젝트 관리 템플릿 통합 CLI",
5
5
  "keywords": [
6
6
  "devops",
@@ -41,6 +41,11 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
41
41
  // finally에서 기록할 때 쓰는 값 — try 안에서 확정되기 전에 취소될 수 있으므로 바깥에 둔다 (#561).
42
42
  let traceFrom = "";
43
43
  let traceTo = "";
44
+ // 기록 파일을 남기는 모드인지 — 모드 선택 전에 취소될 수 있어 기본값은 false.
45
+ let recordArtifacts = false;
46
+ // 기록을 남기지 않고 끝낼 경우(version/issues 모드의 정상 완주)에만 true.
47
+ // 취소·예외는 모드와 무관하게 남긴다 — 끊겼을 때가 "어디까지 갔는지"가 가장 궁금한 순간이다.
48
+ let skipRecord = false;
44
49
  if (io === prompts) trace.mirrorStart();
45
50
  try {
46
51
  // 템플릿 먼저 획득 — 배너에 실제 템플릿 버전을 표시 (.sh는 원격 version.yml fetch L4270~4280 등가)
@@ -378,7 +383,7 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
378
383
 
379
384
  // 마이그레이션 기록 (#493/#494) — Layer 2/3 트레이스 파일 + Layer 1 가이드 엔트리 (full/workflows만)
380
385
  let migrationGuidePath = null;
381
- const recordArtifacts = mode === "full" || mode === "workflows";
386
+ recordArtifacts = mode === "full" || mode === "workflows";
382
387
  // 경로는 먼저 계산하고 실제 쓰기는 완료 화면 뒤로 미룬다 — 요약까지 터미널 미러에 담기 위함 (#561)
383
388
  const files = recordArtifacts
384
389
  ? trace.paths({ fromVersion: existing?.templateVersion || "", toVersion: templateVersion, now })
@@ -406,16 +411,12 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
406
411
  }, cwd);
407
412
  io.outro?.(`통합 완료 — ${mode} 모드로 설치했습니다.`);
408
413
 
409
- // 완료 화면까지 캡처한종료하고 기록한다 (#561)
414
+ // 완료 화면까지 출력한finally에서 닫는다 (#561) — 여기서 닫으면 이 아래 화면이 안 담긴다.
410
415
  trace.event("run", "end", mode || "", {
411
416
  workflowsCopied: result?.workflows?.copied ?? 0,
412
417
  workflowsSkipped: result?.workflows?.skipped ?? 0,
413
418
  });
414
- if (recordArtifacts) {
415
- trace.finalize({ targetRoot: cwd, fromVersion: existing?.templateVersion || "", toVersion: templateVersion, now });
416
- } else {
417
- trace.mirrorStop();
418
- }
419
+ skipRecord = !recordArtifacts;
419
420
  return 0;
420
421
  } catch (err) {
421
422
  trace.event("run", "error", "interactive", {
@@ -424,9 +425,10 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
424
425
  });
425
426
  throw err;
426
427
  } finally {
427
- // 어떤 경로로 빠져나가든 기록을 남긴다 (#561) — 중간 취소·예외 포함.
428
- // finalize는 멱등이라 정상 경로에서 이미 호출됐으면 여기서는 아무 일도 하지 않는다.
429
- trace.finalize({ targetRoot: cwd, fromVersion: traceFrom, toVersion: traceTo, now: clock?.now || "" });
428
+ // 어떤 경로로 빠져나가든 기록을 남긴다 (#561) — 중간 취소·예외·Ctrl+C 포함.
429
+ // 완료 화면 출력이 try 안에 있어야 화면까지 미러에 담긴 채로 여기서 닫힌다.
430
+ if (skipRecord) trace.mirrorStop();
431
+ else trace.finalize({ targetRoot: cwd, fromVersion: traceFrom, toVersion: traceTo, now: clock?.now || "" });
430
432
  disarmSignals();
431
433
  remove(tempDir);
432
434
  }
package/src/index.js CHANGED
@@ -294,6 +294,39 @@ export async function run(argv, { cwd = process.cwd(), source = { type: "git" },
294
294
  // 알 수 없는 모드 → .sh와 동일하게 복사 0건, 에러 아님
295
295
  break;
296
296
  }
297
+ // 마이그레이션 기록 (#493/#494) — Layer 2/3 트레이스 파일 + Layer 1 가이드 엔트리
298
+ let migrationGuidePath = null;
299
+ // 기록 파일 경로는 먼저 계산하고(가이드가 참조), 실제 쓰기는 완료 화면 출력 뒤로 미룬다 —
300
+ // 그래야 터미널 미러에 완료 화면까지 담긴다 (#561).
301
+ const files = recordArtifacts
302
+ ? trace.paths({ fromVersion: existing?.templateVersion || "", toVersion: context.templateVersion, now })
303
+ : null;
304
+ if (recordArtifacts) {
305
+ migrationGuidePath = appendGuideEntry(cwd, {
306
+ now, mode: opts.mode, types, repoName,
307
+ templateFrom: existing?.templateVersion || "", templateTo: context.templateVersion,
308
+ options: { deploy: deployTarget, publish: publishTargets, secretBackup: context.includeSecretBackup, coderabbit: context.codeReviewCoderabbit, changelogProvider: context.changelogProvider, intent, semverAuto: context.semverAuto , appRelease: context.appRelease },
309
+ branches: { defaultBranch: branch, deployBranch: context.deployBranch || "develop", ready: null, created: null },
310
+ breaking: breakingReport, migrations: migrationsResult, orphans: { cleaned: [], pending: orphanPending },
311
+ events: trace.events, counters: { skipped: result?.workflows?.skipped ?? 0 },
312
+ traceFile: files?.traceFile ?? "", logFile: files?.logFile ?? "",
313
+ }).guidePath;
314
+ }
315
+
316
+ // 완료 요약 (.sh print_summary — CLI 모드에서도 출력)
317
+ printSummary({
318
+ mode: opts.mode, types, version, deployBranch: context.deployBranch, migrationGuidePath,
319
+ counters: { workflows: result?.workflows?.copied ?? 0, workflowFiles: result?.workflows?.copiedFiles ?? [], utilModules: 0 },
320
+ verification: result?.verification, // #549 설치 후 검증 결과 (full/workflows 모드에서만 존재)
321
+ logDir: files ? MIGRATION_DIR : null, // #561 기록 위치 안내
322
+ logFile: files?.logFile ?? null,
323
+ traceFile: files?.traceFile ?? null,
324
+ }, cwd);
325
+
326
+ trace.event("run", "end", opts.mode || "", {
327
+ workflowsCopied: result?.workflows?.copied ?? 0,
328
+ workflowsSkipped: result?.workflows?.skipped ?? 0,
329
+ });
297
330
  } catch (err) {
298
331
  // 실패 원인을 로그에 남긴다 (#561) — 서버 로그처럼 사후에 바로 짚을 수 있어야 한다.
299
332
  trace.event("run", "error", opts.mode || "", {
@@ -302,53 +335,16 @@ export async function run(argv, { cwd = process.cwd(), source = { type: "git" },
302
335
  });
303
336
  throw err;
304
337
  } finally {
305
- // 예외로 빠져나가도 기록을 남긴다 (#561). finalize는 멱등 정상 경로에서 이미
306
- // 호출됐으면 여기서는 아무 일도 하지 않는다.
338
+ // 어떤 경로로 빠져나가든 기록을 남긴다 (#561) 조기 return·중간 취소·예외 포함.
339
+ // 완료 화면 출력이 try 안에 있어야 그 화면까지 미러에 담긴 채로 여기서 닫힌다.
307
340
  if (recordArtifacts) {
308
341
  trace.finalize({ targetRoot: cwd, fromVersion: existing?.templateVersion || "", toVersion: context.templateVersion, now });
342
+ } else {
343
+ trace.mirrorStop();
309
344
  }
310
345
  disarmSignals();
311
346
  remove(tempDir);
312
347
  }
313
348
 
314
- // 마이그레이션 기록 (#493/#494) — Layer 2/3 트레이스 파일 + Layer 1 가이드 엔트리
315
- let migrationGuidePath = null;
316
- // 기록 파일 경로는 먼저 계산하고(가이드가 참조), 실제 쓰기는 완료 화면 출력 뒤로 미룬다 —
317
- // 그래야 터미널 미러에 완료 화면까지 담긴다 (#561).
318
- const files = recordArtifacts
319
- ? trace.paths({ fromVersion: existing?.templateVersion || "", toVersion: context.templateVersion, now })
320
- : null;
321
- if (recordArtifacts) {
322
- migrationGuidePath = appendGuideEntry(cwd, {
323
- now, mode: opts.mode, types, repoName,
324
- templateFrom: existing?.templateVersion || "", templateTo: context.templateVersion,
325
- options: { deploy: deployTarget, publish: publishTargets, secretBackup: context.includeSecretBackup, coderabbit: context.codeReviewCoderabbit, changelogProvider: context.changelogProvider, intent, semverAuto: context.semverAuto , appRelease: context.appRelease },
326
- branches: { defaultBranch: branch, deployBranch: context.deployBranch || "develop", ready: null, created: null },
327
- breaking: breakingReport, migrations: migrationsResult, orphans: { cleaned: [], pending: orphanPending },
328
- events: trace.events, counters: { skipped: result?.workflows?.skipped ?? 0 },
329
- traceFile: files?.traceFile ?? "", logFile: files?.logFile ?? "",
330
- }).guidePath;
331
- }
332
-
333
- // 완료 요약 (.sh print_summary — CLI 모드에서도 출력)
334
- printSummary({
335
- mode: opts.mode, types, version, deployBranch: context.deployBranch, migrationGuidePath,
336
- counters: { workflows: result?.workflows?.copied ?? 0, workflowFiles: result?.workflows?.copiedFiles ?? [], utilModules: 0 },
337
- verification: result?.verification, // #549 설치 후 검증 결과 (full/workflows 모드에서만 존재)
338
- logDir: files ? MIGRATION_DIR : null, // #561 기록 위치 안내
339
- logFile: files?.logFile ?? null,
340
- traceFile: files?.traceFile ?? null,
341
- }, cwd);
342
-
343
- // 완료 화면까지 캡처한 뒤 종료하고 기록한다 (#561)
344
- trace.event("run", "end", opts.mode || "", {
345
- workflowsCopied: result?.workflows?.copied ?? 0,
346
- workflowsSkipped: result?.workflows?.skipped ?? 0,
347
- });
348
- if (recordArtifacts) {
349
- trace.finalize({ targetRoot: cwd, fromVersion: existing?.templateVersion || "", toVersion: context.templateVersion, now });
350
- } else {
351
- trace.mirrorStop();
352
- }
353
349
  return 0;
354
350
  }
package/src/ui/summary.js CHANGED
@@ -131,23 +131,6 @@ export function printSummary(ctx, targetRoot = ".") {
131
131
  err(" 📚 워크플로우 가이드: .github/workflows/project-types/README.md");
132
132
  // #493 — 이번 실행의 마이그레이션 기록. "뭐가 남았고 AI에게 어떻게 시키는지"가 바로 보이게 행동 유도형으로 안내.
133
133
  // 실행 기록 위치 (#561) — 무슨 일이 있었는지 나중에 확인할 자리를 알린다.
134
- // 폴더가 아니라 이번 실행의 파일을 정확히 짚어준다 — 실행이 쌓이면 어느 것이 이번 건인지
135
- // 모른다. 붙여넣어 바로 열 수 있는 경로가 목적이다.
136
- if (ctx?.logFile || ctx?.logDir) {
137
- err(` 📁 실행 로그: ${ctx.logFile || `${ctx.logDir}/`}`);
138
- if (ctx.traceFile) err(` 이벤트(JSONL): ${ctx.traceFile}`);
139
- err(" 이번 실행에서 무엇을 어떤 근거로 정했는지 전부 기록돼 있습니다.");
140
- err(" 문제가 생기면 이 파일을 확인하세요 (저장소에 추적되지 않습니다).");
141
- err(" 💡 AI Agent에게 \"실행 로그 확인해줘\"라고 요청하면 원인을 짚어줍니다.");
142
- err("");
143
- }
144
- if (ctx?.migrationGuidePath) {
145
- err(` 🧭 마이그레이션 가이드: ${ctx.migrationGuidePath}`);
146
- err(" 이번 설치에서 바뀐 내용과 직접 확인해야 할 작업이 이 파일에 정리되어 있습니다.");
147
- err(" 💡 AI Agent(Claude, Cursor 등)에게 \"마이그레이션 가이드 확인하고");
148
- err(" 남은 작업 처리해줘\"라고 요청하면 이 문서를 읽고 대신 진행합니다.");
149
- }
150
- err("");
151
134
 
152
135
  // 설치 후 검증 결과 (#549) — 문제가 있을 때만 펼치고, 정상이면 한 줄로 압축한다.
153
136
  const verification = ctx?.verification;
@@ -215,4 +198,17 @@ export function printSummary(ctx, targetRoot = ".") {
215
198
  err(`${CYAN}📖 자세한 설정 방법은 다음 파일을 참고하세요:${NC}`);
216
199
  err(" → PROJECTOPS-SETUP-GUIDE.md");
217
200
  err("");
201
+ // 기록 안내는 완료 화면의 맨 끝에 둔다 (#561) — Secret 목록이 길면 위쪽은 스크롤에 묻혀
202
+ // 사용자가 "로그 얘기가 없다"고 느낀다. 문제가 생겼을 때 가장 먼저 찾는 것이므로 마지막 자리다.
203
+ if (ctx?.logFile || ctx?.logDir || ctx?.migrationGuidePath) {
204
+ err(`${CYAN}📁 이번 실행 기록${NC}`);
205
+ if (ctx?.logFile || ctx?.logDir) {
206
+ err(` 로그: ${ctx.logFile || `${ctx.logDir}/`}`);
207
+ if (ctx.traceFile) err(` 이벤트: ${ctx.traceFile}`);
208
+ }
209
+ if (ctx?.migrationGuidePath) err(` 가이드: ${ctx.migrationGuidePath}`);
210
+ err(" 무엇을 어떤 근거로 정했는지 전부 남아 있습니다 (저장소에 추적되지 않음).");
211
+ err(" 💡 문제가 생기면 AI Agent에게 \"실행 로그 확인해줘\"라고 요청하세요.");
212
+ err("");
213
+ }
218
214
  }