project-auto-wizard 0.1.31 → 0.1.32

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-auto-wizard",
3
- "version": "0.1.31",
3
+ "version": "0.1.32",
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",
@@ -433,12 +433,9 @@ def cmd_classify_bump(commits_file: str) -> int:
433
433
  def cmd_update_from_summary() -> int:
434
434
  """pr_body.md에서 Markdown을 파싱하여 CHANGELOG.json 갱신."""
435
435
  version = os.environ.get('VERSION')
436
- project_type = os.environ.get('PROJECT_TYPE')
437
- # 멀티타입 — PROJECT_TYPES(csv) env가 있으면 배열로, 없으면 단수 키 fallback
436
+ # PROJECT_TYPES(csv)가 유일한 입력 — 단수 PROJECT_TYPE 폴백은 제거됐다 (issue #62)
438
437
  project_types_csv = os.environ.get('PROJECT_TYPES', '')
439
438
  project_types = [t.strip() for t in project_types_csv.split(',') if t.strip()]
440
- if not project_types and project_type:
441
- project_types = [project_type]
442
439
  today = os.environ.get('TODAY')
443
440
  pr_number_raw = os.environ.get('PR_NUMBER')
444
441
  timestamp = os.environ.get('TIMESTAMP')
@@ -482,8 +479,7 @@ def cmd_update_from_summary() -> int:
482
479
  # 릴리즈 데이터 생성
483
480
  new_release = {
484
481
  "version": version,
485
- "project_type": project_type, # 기존 단수 키 — 유지 (하위 호환)
486
- "project_types": project_types, # 신규 멀티타입 배열
482
+ "project_types": project_types,
487
483
  "date": today,
488
484
  "pr_number": pr_number,
489
485
  "raw_summary": raw_summary,
@@ -510,7 +506,6 @@ def cmd_update_from_summary() -> int:
510
506
  "metadata": {
511
507
  "lastUpdated": timestamp,
512
508
  "currentVersion": version,
513
- "projectType": project_type,
514
509
  "projectTypes": project_types,
515
510
  "totalReleases": 0,
516
511
  },
@@ -525,7 +520,6 @@ def cmd_update_from_summary() -> int:
525
520
 
526
521
  changelog_data["metadata"]["lastUpdated"] = timestamp
527
522
  changelog_data["metadata"]["currentVersion"] = version
528
- changelog_data["metadata"]["projectType"] = project_type
529
523
  changelog_data["metadata"]["projectTypes"] = project_types
530
524
  changelog_data["metadata"]["totalReleases"] = len(changelog_data.get("releases", [])) + 1
531
525
  changelog_data.setdefault("releases", []).insert(0, new_release)
@@ -146,21 +146,22 @@ def get_current_version():
146
146
  return read_scalar_key("version", "0.0.0")
147
147
 
148
148
 
149
- def get_project_type():
150
- return read_scalar_key("project_type", "basic")
151
-
152
-
153
149
  def get_project_types_csv():
154
150
  """Return project_types as a list. Supports both:
155
151
  project_types: ["a", "b"]
156
152
  project_types:
157
153
  - "a"
158
154
  - "b"
159
- Returns [] if key absent (legacy single-type mode)."""
155
+ Returns [] if the key is absent project_types is the single source of
156
+ truth (issue #62), so callers must treat [] as a hard error rather than
157
+ falling back to a singular key."""
160
158
  text = read_text()
161
159
 
162
- # Inline array form: project_types: ["a", "b"]
163
- m = re.search(r'^project_types:[ \t]*\[(.*?)\][ \t]*$', text, re.MULTILINE)
160
+ # Inline array form: project_types: ["a", "b"] # trailing comment allowed
161
+ # The template always appends "# first entry is primary", so anchoring at
162
+ # end-of-line made this branch never match — every install silently fell
163
+ # through to the singular key instead (issue #62).
164
+ m = re.search(r'^project_types:[ \t]*\[(.*?)\][ \t]*(?:#.*)?$', text, re.MULTILINE)
164
165
  if m:
165
166
  inner = m.group(1)
166
167
  items = re.findall(r'"([^"]*)"|\'([^\']*)\'', inner)
@@ -174,7 +175,8 @@ def get_project_types_csv():
174
175
  m = re.search(r'^project_types:[ \t]*\n((?:[ \t]+-[ \t]*.*\n?)+)', text, re.MULTILINE)
175
176
  if m:
176
177
  block = m.group(1)
177
- types = re.findall(r'-\s*["\']?([^"\'\n]+?)["\']?\s*$', block, re.MULTILINE)
178
+ # trailing comments are allowed on list items too
179
+ types = re.findall(r'-[ \t]*["\']?([^"\'#\n]+?)["\']?[ \t]*(?:#.*)?$', block, re.MULTILINE)
178
180
  return [t.strip() for t in types if t.strip()]
179
181
 
180
182
  return []
@@ -430,7 +432,9 @@ def sync_for_type(project_type, new_version, version_code_getter):
430
432
  def sync_all_project_files(new_version):
431
433
  types = get_project_types_csv()
432
434
  if not types:
433
- types = [get_project_type()]
435
+ # No silent fallback: an unreadable project_types used to degrade to
436
+ # "basic" and skip every sync without a word (issue #62).
437
+ raise SystemExit("ERROR: version.yml has no readable project_types — cannot sync project files")
434
438
  for t in types:
435
439
  sync_for_type(t, new_version, get_version_code)
436
440
 
@@ -510,7 +514,10 @@ def get_project_file_version(project_type):
510
514
 
511
515
  def sync_versions():
512
516
  yml_version = get_current_version()
513
- primary_type = get_project_type()
517
+ types = get_project_types_csv()
518
+ if not types:
519
+ raise SystemExit("ERROR: version.yml has no readable project_types — cannot sync versions")
520
+ primary_type = types[0]
514
521
  project_version = get_project_file_version(primary_type)
515
522
 
516
523
  log("Version sync check")
@@ -10,7 +10,7 @@
10
10
  # Supported project types: spring, flutter, next, react, react-native,
11
11
  # react-native-expo, node, python, basic
12
12
  # Multi-type projects: list every type in the project_types array
13
- # -> project_type (singular) always mirrors project_types[0] do not edit it directly
13
+ # -> the first entry is the primary type (project_types is the single source of truth)
14
14
  # Monorepo paths: project_paths maps each type to its subdir relative to the repo root
15
15
  # (e.g., flutter: "app", react: "client"). Omitted -> repo root.
16
16
  #
@@ -27,7 +27,6 @@
27
27
  version: "{{VERSION}}"
28
28
  version_code: {{VERSION_CODE}} # app build number
29
29
  project_types: {{PROJECT_TYPES}} # first entry is primary
30
- project_type: "{{PROJECT_TYPE}}" # auto-mirror of project_types[0] — do not edit directly
31
30
  {{PROJECT_PATHS}}
32
31
  metadata:
33
32
  last_updated: "{{NOW}}"
@@ -156,13 +156,11 @@ jobs:
156
156
  run: |
157
157
  # note: `grep | sed || echo default` never fires the default (the
158
158
  # pipeline exits with sed's 0) — default via ${var:-} instead
159
- PROJECT_TYPE=$(grep "^project_type:" version.yml 2>/dev/null | sed 's/project_type: *"\([^"]*\)".*/\1/')
160
- PROJECT_TYPE=${PROJECT_TYPE:-unknown}
161
159
  PROJECT_TYPES=$(grep "^project_types:" version.yml 2>/dev/null | sed -E 's/.*\[([^]]*)\].*/\1/' | tr -d '" ')
162
- PROJECT_TYPES=${PROJECT_TYPES:-$PROJECT_TYPE}
160
+ PROJECT_TYPES=${PROJECT_TYPES:-unknown}
163
161
 
164
162
  export VERSION="${{ steps.bump.outputs.new_version }}"
165
- export PROJECT_TYPE PROJECT_TYPES
163
+ export PROJECT_TYPES
166
164
  export TODAY=$(date '+%Y-%m-%d')
167
165
  export PR_NUMBER="${{ github.event.pull_request.number }}"
168
166
  export TIMESTAMP=$(date '+%Y-%m-%dT%H:%M:%SZ')
@@ -192,13 +192,11 @@ jobs:
192
192
 
193
193
  # note: `grep | sed || echo default` never fires the default (the
194
194
  # pipeline exits with sed's 0) — default via ${var:-} instead
195
- PROJECT_TYPE=$(grep "^project_type:" version.yml 2>/dev/null | sed 's/project_type: *"\([^"]*\)".*/\1/')
196
- PROJECT_TYPE=${PROJECT_TYPE:-unknown}
197
195
  PROJECT_TYPES=$(grep "^project_types:" version.yml 2>/dev/null | sed -E 's/.*\[([^]]*)\].*/\1/' | tr -d '" ')
198
- PROJECT_TYPES=${PROJECT_TYPES:-$PROJECT_TYPE}
196
+ PROJECT_TYPES=${PROJECT_TYPES:-unknown}
199
197
 
200
198
  export VERSION="$NEW_VERSION"
201
- export PROJECT_TYPE PROJECT_TYPES
199
+ export PROJECT_TYPES
202
200
  export TODAY=$(date '+%Y-%m-%d')
203
201
  export TIMESTAMP=$(date '+%Y-%m-%dT%H:%M:%SZ')
204
202
 
@@ -114,21 +114,16 @@ jobs:
114
114
  echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
115
115
  echo "new version: $NEW_VERSION"
116
116
 
117
- - name: Read project type
117
+ - name: Read project types
118
118
  id: project_info
119
119
  if: steps.release_guard.outputs.skip != 'true'
120
120
  run: |
121
121
  if [ -f "version.yml" ]; then
122
- PROJECT_TYPE=$(grep "^project_type:" version.yml | sed 's/project_type: *"\([^"]*\)".*/\1/' || echo "unknown")
123
- PROJECT_TYPES=$(grep "^project_types:" version.yml 2>/dev/null | sed -E 's/.*\[([^]]*)\].*/\1/' | tr -d '" ' || echo "")
124
- if [ -z "$PROJECT_TYPES" ]; then
125
- PROJECT_TYPES="$PROJECT_TYPE"
126
- fi
127
- echo "project_type=$PROJECT_TYPE" >> $GITHUB_OUTPUT
122
+ PROJECT_TYPES=$(grep "^project_types:" version.yml 2>/dev/null | sed -E 's/.*\[([^]]*)\].*/\1/' | tr -d '" ')
123
+ PROJECT_TYPES=${PROJECT_TYPES:-unknown}
128
124
  echo "project_types=$PROJECT_TYPES" >> $GITHUB_OUTPUT
129
- echo "project type: $PROJECT_TYPE (types: $PROJECT_TYPES)"
125
+ echo "project types: $PROJECT_TYPES"
130
126
  else
131
- echo "project_type=unknown" >> $GITHUB_OUTPUT
132
127
  echo "project_types=unknown" >> $GITHUB_OUTPUT
133
128
  echo "version.yml not found"
134
129
  fi
@@ -5,6 +5,9 @@ import { escapeYamlDoubleQuoted } from "./wizard-env.js";
5
5
  // 레이아웃 단일 진실 = payload/version.yml.template (호출부가 templateText로 주입).
6
6
 
7
7
  // version.yml.template이 아는 최상위 키 — 이 밖의 최상위 키는 사용자가 직접 추가한 것으로 간주한다.
8
+ // "project_type"(단수)은 더 이상 렌더하지 않는 레거시 키지만 이 집합에는 남겨둔다 (issue #62):
9
+ // 빼면 기존 파일의 단수 줄이 "사용자가 추가한 필드"로 오인돼 재생성 때 되살아난다. 아는 키로
10
+ // 둬야 재통합 시 흡수되어 사라진다.
8
11
  const KNOWN_TOP_LEVEL_KEYS = new Set([
9
12
  "version", "version_code", "project_types", "project_type", "project_paths", "metadata", "deploy",
10
13
  ]);
@@ -147,7 +150,7 @@ export function parseTemplateBranches(content) {
147
150
  }
148
151
 
149
152
  // version.yml 전체 생성 — payload/version.yml.template 렌더링.
150
- // opts: { templateText, version, types:[], primaryType?, paths:Map, pathMarkers?:Map,
153
+ // opts: { templateText, version, types:[], paths:Map, pathMarkers?:Map,
151
154
  // branch, branches?, versionCode, now, today, templateOptions?, deployValues?,
152
155
  // extraTopLevel?:string[] } ← 기존 version.yml의 알려지지 않은 최상위 필드 보존 (issue #20 M8)
153
156
  // templateText = payload/version.yml.template 원문 (readVersionYmlTemplate — 필수)
@@ -156,13 +159,12 @@ export function parseTemplateBranches(content) {
156
159
  // pathMarkers = Map<type, markerFilename> (project_paths 주석용)
157
160
  // templateOptions = { templateVersion, includeNexus, includeSecretBackup, optionsDate }
158
161
  export function buildVersionYml({
159
- templateText, version, types = [], primaryType, paths = new Map(), pathMarkers = new Map(),
162
+ templateText, version, types = [], paths = new Map(), pathMarkers = new Map(),
160
163
  branch = "main", branches = null, versionCode = 1, now, today,
161
164
  templateOptions = null, deployValues = new Map(), extraTopLevel = [],
162
165
  }) {
163
166
  if (!templateText) throw new Error("version.yml.template 원문이 필요합니다 (payload/version.yml.template 누락?)");
164
167
  const typesJson = types.length ? `[${types.map((t) => `"${t}"`).join(", ")}]` : `["basic"]`;
165
- const primary = primaryType || types[0] || "basic";
166
168
  const b = branches || { main: branch || "main", develop: "develop", mode: "pr-flow" };
167
169
  const {
168
170
  templateVersion = "unknown", includeNexus = false, includeSecretBackup = false,
@@ -197,7 +199,7 @@ export function buildVersionYml({
197
199
 
198
200
  const scalars = {
199
201
  VERSION: version, VERSION_CODE: String(versionCode),
200
- PROJECT_TYPES: typesJson, PROJECT_TYPE: primary,
202
+ PROJECT_TYPES: typesJson,
201
203
  NOW: now, TODAY: today || optionsDate, DEFAULT_BRANCH: branch,
202
204
  TEMPLATE_VERSION: templateVersion,
203
205
  MAIN_BRANCH: b.main, DEVELOP_BRANCH: b.develop, BRANCH_MODE: b.mode,