project-auto-wizard 0.1.20 → 0.1.23

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
@@ -11,12 +11,13 @@ npx project-auto-wizard
11
11
  <!-- TODO: 30초 데모 GIF (docs/assets/demo.gif) -->
12
12
  <!-- TODO: 3분 데모 YouTube 링크 -->
13
13
 
14
+ [![CI](https://github.com/Twin-Fang/project-auto-wizard/actions/workflows/CI.yaml/badge.svg)](https://github.com/Twin-Fang/project-auto-wizard/actions/workflows/CI.yaml)
14
15
  [![npm](https://img.shields.io/npm/v/project-auto-wizard)](https://www.npmjs.com/package/project-auto-wizard)
15
16
  [![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
16
17
  [![node](https://img.shields.io/badge/node-%3E%3D20.12-brightgreen)](package.json)
17
18
 
18
19
  <!-- AUTO-VERSION-SECTION: DO NOT EDIT MANUALLY -->
19
- ## 최신 버전 : v0.1.18 (2026-08-06)
20
+ ## 최신 버전 : v0.1.23 (2026-08-06)
20
21
 
21
22
  [전체 버전 기록 보기](CHANGELOG.md)
22
23
 
@@ -163,13 +164,17 @@ npx project-auto-wizard --mode doctor # 환경 진단 (읽기 전용, 규칙
163
164
  [✓] GitHub 로그인 — 레포 설정 조회 권한 인증됨
164
165
  [✓] merge commit 허용 — 릴리스 PR 자동 머지 조건 허용됨
165
166
 
166
- [!] Workflow permissions 버전 커밋 자동 push
167
- 현재 read 입니다.
168
- 워크플로가 버전 올림 커밋을 push하지 못해 릴리스가 중단됩니다.
169
- 레포 Settings Actions General Workflow permissions
170
- "Read and write permissions" 선택 Save
167
+ [!] WORKFLOW_PAT자동 태그·Release 발행
168
+ secret이 등록되어 있지 않습니다.
169
+ PR 자동 머지 태그·Release 워크플로가 이어지지 않습니다.
170
+ (GitHub 정책상 기본 토큰으로 만든 커밋은 다음 워크플로를 깨우지 못합니다)
171
+ 개인 액세스 토큰 발급 (scopes: repo, workflow)
172
+ → 레포 Settings → Secrets and variables → Actions
173
+ → New repository secret · 이름은 WORKFLOW_PAT
171
174
  → 자세히: https://github.com/Twin-Fang/project-auto-wizard#post-install
172
175
 
176
+ [i] Workflow permissions — 직접 추가한 워크플로우의 기본 권한
177
+ 현재 read 입니다 — 마법사가 설치한 워크플로우는 각자 권한을 선언하므로 그대로 동작합니다.
173
178
  [i] GitHub Models — AI 릴리스 노트 생성
174
179
  조직 정책으로 차단됐는지는 자동으로 확인할 수 없습니다 (Settings → Models).
175
180
  차단돼 있어도 규칙 기반 요약으로 자동 전환되므로 그대로 두셔도 됩니다.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-auto-wizard",
3
- "version": "0.1.20",
3
+ "version": "0.1.23",
4
4
  "description": "One command DevOps: npx wizard that installs GitHub-native AI Release Automation into any project",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -17,7 +17,7 @@
17
17
  ],
18
18
  "scripts": {
19
19
  "test": "npm run test:node && npm run test:py",
20
- "test:node": "node --test \"tests/node/**/*.test.js\"",
20
+ "test:node": "node --test --test-concurrency=1",
21
21
  "test:py": "node scripts/run-py-tests.mjs"
22
22
  },
23
23
  "repository": {
@@ -322,7 +322,10 @@ jobs:
322
322
  - name: Create GitHub Release
323
323
  if: steps.gate.outputs.proceed == 'true' && steps.version.outputs.release_exists != 'true'
324
324
  env:
325
- GH_TOKEN: ${{ github.token }}
325
+ # WORKFLOW_PAT으로 발행해야 release 이벤트가 후속 워크플로우(npm 배포 등)를 트리거한다.
326
+ # GITHUB_TOKEN이 만든 이벤트는 GitHub 정책상 다른 워크플로우를 깨우지 못한다.
327
+ # PAT이 없는 레포에서도 릴리스 자체는 계속 동작해야 하므로 기본 토큰으로 폴백한다.
328
+ GH_TOKEN: ${{ secrets.WORKFLOW_PAT || github.token }}
326
329
  run: |
327
330
  VERSION="${{ steps.version.outputs.version }}"
328
331
  gh release create "v$VERSION" --title "v$VERSION" --notes-file notes.md
@@ -83,26 +83,31 @@ export function runDoctor(cwd = process.cwd(), { exec = defaultExec } = {}) {
83
83
 
84
84
  const perm = exec("gh", ["api", `repos/${owner}/${repo}/actions/permissions/workflow`, "--jq", ".default_workflow_permissions"]);
85
85
  const permValue = (perm.stdout || "").trim();
86
+ // 이 값은 "워크플로우가 permissions를 생략했을 때 적용되는 기본값"이지 상한이 아니다(#34).
87
+ // 마법사가 설치하는 워크플로우는 전부 자체 permissions를 선언하므로(회귀 가드:
88
+ // tests/node/payload-workflow-permissions.test.js) read여도 정상 동작한다 — 이 레포 자체가
89
+ // read인데 VERSION-CONTROL이 버전 커밋 push에 성공하는 것이 그 증거다.
90
+ const PERM_PURPOSE = "직접 추가한 워크플로우의 기본 권한";
86
91
  if (perm.status !== 0) {
92
+ // 조회 실패는 오진이 아니라 실제로 정보를 얻지 못한 상태다 — 관리자 확인이라는 조치가 있다.
87
93
  add({
88
- name: "Workflow permissions", purpose: "버전 커밋 자동 push", status: "WARN",
94
+ name: "Workflow permissions", purpose: PERM_PURPOSE, status: "WARN",
89
95
  value: "설정을 조회하지 못했습니다.",
90
96
  impact: ["레포 관리자 권한이 없으면 이 설정은 조회되지 않습니다 — 값 자체는 정상일 수 있습니다."],
91
97
  actions: ["레포 관리자에게 Settings → Actions → General → Workflow permissions 값을 확인하세요"],
92
98
  doc: DOC.postInstall,
93
99
  });
94
100
  } else if (permValue === "write") {
95
- add({ name: "Workflow permissions", purpose: "버전 커밋 자동 push", status: "OK", value: "Read and write" });
101
+ add({ name: "Workflow permissions", purpose: PERM_PURPOSE, status: "OK", value: "Read and write" });
96
102
  } else {
103
+ // 조치가 필요 없으므로 WARN이 아니라 INFO다. 경고로 띄우면 없는 장애를 알리고
104
+ // 불필요한 권한 상향을 유도해 최소 권한 원칙에 역행한다.
97
105
  add({
98
- name: "Workflow permissions", purpose: "버전 커밋 자동 push", status: "WARN",
99
- value: `현재 ${permValue || "확인불가"} 입니다.`,
100
- impact: ["워크플로가 버전 올림 커밋을 push하지 못해 릴리스가 중단됩니다."],
101
- actions: [
102
- "레포 Settings → Actions → General → Workflow permissions",
103
- '"Read and write permissions" 선택 후 Save',
106
+ name: "Workflow permissions", purpose: PERM_PURPOSE, status: "INFO",
107
+ note: [
108
+ `현재 ${permValue || "확인불가"} 입니다 마법사가 설치한 워크플로우는 각자 권한을 선언하므로 그대로 동작합니다.`,
109
+ "직접 추가한 워크플로우에서 permissions를 생략했다면 이 기본값을 따르므로, 그때만 Read and write로 올리세요.",
104
110
  ],
105
- doc: DOC.postInstall,
106
111
  });
107
112
  }
108
113
 
package/src/ui/banner.js CHANGED
@@ -10,7 +10,7 @@ function boxLine(out, content = "") {
10
10
  }
11
11
 
12
12
  // 대화형 첫 화면 배너 — 박스 타이틀 + 메타 4줄 (.ps1 Print-Banner 등가, #446 확정 시안 A)
13
- export function printBanner({ version, modeLabel }, out = (s) => process.stdout.write(s)) {
13
+ export function printBanner({ version, modeLabel }, out = (s) => process.stderr.write(s)) {
14
14
  out("\n");
15
15
  out(paint(`╔${"═".repeat(INNER)}╗`, A.cyan) + "\n");
16
16
  boxLine(out);
@@ -24,6 +24,6 @@ export function printBanner({ version, modeLabel }, out = (s) => process.stdout.
24
24
  }
25
25
 
26
26
  // 비대화형(--force/CI) 축약 배너 — 1줄 (사용자 확정: 로그 오염 최소 + 버전 추적)
27
- export function printBannerCompact({ version, mode }, out = (s) => process.stdout.write(s)) {
27
+ export function printBannerCompact({ version, mode }, out = (s) => process.stderr.write(s)) {
28
28
  out(`${paint("✦", A.yellow)} ${paint("project-auto-wizard", A.bold)} v${version} — ${mode} 모드 (--force)\n`);
29
29
  }
@@ -9,7 +9,7 @@ const HEAD = paint("◆", A.cyan);
9
9
  const OK = paint("✓", A.green);
10
10
 
11
11
  // 층2 — 감지 로그 (.ps1 감지 진행 표시 등가)
12
- export function printDetectionLog({ types = [], version = "", branch = "" }, out = (s) => process.stdout.write(s)) {
12
+ export function printDetectionLog({ types = [], version = "", branch = "" }, out = (s) => process.stderr.write(s)) {
13
13
  out(`${paint("┌", A.gray)} 🔍 프로젝트를 살펴보는 중...\n`);
14
14
  if (types.length && !(types.length === 1 && types[0] === "basic")) {
15
15
  for (const t of types) {
@@ -26,7 +26,7 @@ export function printDetectionLog({ types = [], version = "", branch = "" }, out
26
26
  // 층3 — 프로젝트 분석 개요 카드 (.ps1 Print-ProjectAnalysis 등가+)
27
27
  export function printAnalysisCard({ mode = "", modeLabel = "", types = [], version = "", branch = "",
28
28
  includeNexus = null, includeSecretBackup = null, paths = new Map(), showOptional = false },
29
- out = (s) => process.stdout.write(s)) {
29
+ out = (s) => process.stderr.write(s)) {
30
30
  out(`${HEAD} ${paint("프로젝트 분석 결과", A.bold)}\n`);
31
31
  const row = (icon, label, value) => out(`${GUT} ${icon} ${label.padEnd(10)} ${value}\n`);
32
32
  row("📂", types.length > 1 ? "타입(멀티)" : "타입", paint(types.join(", ") || "basic", A.bold));
@@ -46,7 +46,7 @@ export function printAnalysisCard({ mode = "", modeLabel = "", types = [], versi
46
46
  }
47
47
 
48
48
  // 층5 — 신규 통합 vs 업데이트 판별 라인 (Breaking 박스는 breaking-check.js)
49
- export function printInstallKind({ currentTemplateVersion = "", templateVersion = "" }, out = (s) => process.stdout.write(s)) {
49
+ export function printInstallKind({ currentTemplateVersion = "", templateVersion = "" }, out = (s) => process.stderr.write(s)) {
50
50
  if (currentTemplateVersion) {
51
51
  out(`${GUT} ♻️ ${paint("업데이트", A.bold)} — 템플릿 ${paint(`v${currentTemplateVersion}`, A.dim)} → ${paint(`v${templateVersion}`, A.green)}\n`);
52
52
  } else {