projectops 4.2.19 → 4.2.21
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 +1 -1
- package/package.json +1 -1
- package/src/commands/interactive.js +3 -2
- package/src/core/copy/util.js +2 -0
- package/src/core/migrations/index.js +1 -0
- package/src/core/migrations/registry.js +43 -1
- package/src/core/options-ask.js +69 -17
- package/src/index.js +13 -4
- package/src/ui/prompts.js +9 -5
- package/src/ui/summary.js +3 -2
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -13,7 +13,7 @@ import { runBreakingCheck } from "../core/breaking-check.js";
|
|
|
13
13
|
import { runMigrations } from "../core/migrations/index.js";
|
|
14
14
|
import { detectOrphanWorkflows, applyOrphanCleanup } from "../core/orphan-workflows.js";
|
|
15
15
|
import { resolveProjectPaths, filterExcludedTypes } from "../core/paths-resolve.js";
|
|
16
|
-
import { askAllOptionalWorkflows, OPTION_AXES } from "../core/options-ask.js";
|
|
16
|
+
import { askAllOptionalWorkflows, OPTION_AXES, applicableTargets } from "../core/options-ask.js";
|
|
17
17
|
import { createRunTrace } from "../core/run-trace.js";
|
|
18
18
|
import { appendGuideEntry } from "../core/migration-guide.js";
|
|
19
19
|
import { promptEnvPlan } from "../ui/env-plan.js";
|
|
@@ -154,7 +154,8 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
|
|
|
154
154
|
// edit 루프
|
|
155
155
|
let editing = true;
|
|
156
156
|
while (editing) {
|
|
157
|
-
|
|
157
|
+
// #498 — 타입에 적용 불가한 배포 축 항목은 메뉴에서 숨김 (타입을 바꾸면 다음 진입부터 다시 노출)
|
|
158
|
+
const what = await io.editMenu({ showOptional, axes: applicableTargets(types) });
|
|
158
159
|
if (isCancel(what) || what === "done") { editing = false; break; }
|
|
159
160
|
if (what === "type") {
|
|
160
161
|
const t = await io.selectTypes(types);
|
package/src/core/copy/util.js
CHANGED
|
@@ -5,6 +5,8 @@ import { readdirSync } from "node:fs";
|
|
|
5
5
|
import { exists, copyDirSync } from "../fsutil.js";
|
|
6
6
|
|
|
7
7
|
// 반환: {copied:bool, moduleCount:number}. force가 아니면(비TTY) 스킵.
|
|
8
|
+
// ⚠️ overlay 복사다 — 템플릿에서 삭제/리네임된 구 파일은 여기서 지우지 않는다.
|
|
9
|
+
// 구 util 파일 정리는 migrations registry(util-file 카테고리)가 담당한다 (#500).
|
|
8
10
|
export function copyUtilModules(tempDir, type, { force = false } = {}, targetRoot = ".") {
|
|
9
11
|
const src = join(tempDir, ".github", "util", type);
|
|
10
12
|
if (!exists(src)) return { copied: false, moduleCount: 0 };
|
|
@@ -10,6 +10,7 @@ const RULES = {
|
|
|
10
10
|
workflow: workflowRule,
|
|
11
11
|
"root-file": rootFileRule,
|
|
12
12
|
"legacy-dir": legacyDirRule,
|
|
13
|
+
"util-file": rootFileRule, // #500 — util 모듈 내 폐기 파일: 정확 경로 삭제 (root-file rule 재사용)
|
|
13
14
|
};
|
|
14
15
|
|
|
15
16
|
// 대상 레포에서 레거시 잔재 감지. 반환: { safe: [entry], confirm: [entry], ask: [entry] }
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
//
|
|
6
6
|
// 항목 스키마:
|
|
7
7
|
// id - 고유 식별자 (kebab-case)
|
|
8
|
-
// category - "workflow" | "root-file" | "legacy-dir" (rules/ 폴더의 구현이 detect/apply 담당)
|
|
8
|
+
// category - "workflow" | "root-file" | "legacy-dir" | "util-file" (rules/ 폴더의 구현이 detect/apply 담당)
|
|
9
|
+
// util-file: .github/util/ 모듈 안의 폐기 파일 — root-file rule 재사용(정확 경로 삭제).
|
|
10
|
+
// util 복사(copy/util.js)는 overlay라 구 파일을 지우지 않는다 — 정리는 여기가 유일 경로.
|
|
9
11
|
// tier - "safe" : 신형이 같은 기능을 대체(순수 리네임). 공존 시 이중 트리거 실해
|
|
10
12
|
// → 확인 1회 후 자동 무해화(.bak) / 삭제
|
|
11
13
|
// "confirm" : 배포 파이프라인일 수 있음(그 레포의 유일한 현역 배포 가능성)
|
|
@@ -161,4 +163,44 @@ export const MIGRATIONS = [
|
|
|
161
163
|
{ id: "dir-docs-suh-template", category: "legacy-dir", tier: "ask",
|
|
162
164
|
file: "docs/suh-template", replacedBy: "docs/projectops",
|
|
163
165
|
since: "4.2.9", reason: "리브랜딩 — 스킬 산출물(이슈·보고서) 폴더 구명칭. 신 스킬은 docs/projectops/에 저장" },
|
|
166
|
+
|
|
167
|
+
// ── util-file / safe — Flutter 마법사 스크립트 Python 단일화 (#500) ──────────
|
|
168
|
+
// 로컬 실행 도구라 CI 중복 실행 위험은 없지만, 신형 py와 공존 시 사용자가 구 스크립트를
|
|
169
|
+
// 실행해 갈라진 동작을 볼 수 있다. 마법사 폴더에 사용자 상태가 없어 삭제가 기대 동작.
|
|
170
|
+
{ id: "util-playstore-setup-sh", category: "util-file", tier: "safe",
|
|
171
|
+
file: ".github/util/flutter/playstore-wizard/playstore-wizard-setup.sh", replacedBy: "playstore-wizard.py",
|
|
172
|
+
since: "4.3.0", reason: "마법사 스크립트 Python 단일화 — setup 서브커맨드로 대체" },
|
|
173
|
+
{ id: "util-playstore-setup-ps1", category: "util-file", tier: "safe",
|
|
174
|
+
file: ".github/util/flutter/playstore-wizard/playstore-wizard-setup.ps1", replacedBy: "playstore-wizard.py",
|
|
175
|
+
since: "4.3.0", reason: "마법사 스크립트 Python 단일화 — setup 서브커맨드로 대체" },
|
|
176
|
+
{ id: "util-playstore-apply-sh", category: "util-file", tier: "safe",
|
|
177
|
+
file: ".github/util/flutter/playstore-wizard/playstore-wizard-apply.sh", replacedBy: "playstore-wizard.py",
|
|
178
|
+
since: "4.3.0", reason: "마법사 스크립트 Python 단일화 — apply 서브커맨드로 대체" },
|
|
179
|
+
{ id: "util-playstore-apply-ps1", category: "util-file", tier: "safe",
|
|
180
|
+
file: ".github/util/flutter/playstore-wizard/playstore-wizard-apply.ps1", replacedBy: "playstore-wizard.py",
|
|
181
|
+
since: "4.3.0", reason: "마법사 스크립트 Python 단일화 — apply 서브커맨드로 대체" },
|
|
182
|
+
{ id: "util-playstore-detect-sh", category: "util-file", tier: "safe",
|
|
183
|
+
file: ".github/util/flutter/playstore-wizard/detect-application-id.sh", replacedBy: "playstore-wizard.py",
|
|
184
|
+
since: "4.3.0", reason: "마법사 스크립트 Python 단일화 — detect-app-id 서브커맨드로 대체" },
|
|
185
|
+
{ id: "util-playstore-detect-ps1", category: "util-file", tier: "safe",
|
|
186
|
+
file: ".github/util/flutter/playstore-wizard/detect-application-id.ps1", replacedBy: "playstore-wizard.py",
|
|
187
|
+
since: "4.3.0", reason: "마법사 스크립트 Python 단일화 — detect-app-id 서브커맨드로 대체" },
|
|
188
|
+
{ id: "util-playstore-patch-py", category: "util-file", tier: "safe",
|
|
189
|
+
file: ".github/util/flutter/playstore-wizard/patch-build-gradle.py", replacedBy: "playstore-wizard.py",
|
|
190
|
+
since: "4.3.0", reason: "마법사 스크립트 Python 단일화 — 내부 함수로 흡수" },
|
|
191
|
+
{ id: "util-firebase-setup-sh", category: "util-file", tier: "safe",
|
|
192
|
+
file: ".github/util/flutter/firebase-wizard/firebase-wizard-setup.sh", replacedBy: "firebase-wizard.py",
|
|
193
|
+
since: "4.3.0", reason: "마법사 스크립트 Python 단일화 — setup 서브커맨드로 대체" },
|
|
194
|
+
{ id: "util-firebase-setup-ps1", category: "util-file", tier: "safe",
|
|
195
|
+
file: ".github/util/flutter/firebase-wizard/firebase-wizard-setup.ps1", replacedBy: "firebase-wizard.py",
|
|
196
|
+
since: "4.3.0", reason: "마법사 스크립트 Python 단일화 — setup 서브커맨드로 대체" },
|
|
197
|
+
{ id: "util-firebase-test-sh", category: "util-file", tier: "safe",
|
|
198
|
+
file: ".github/util/flutter/firebase-wizard/test/setup-script-test.sh", replacedBy: "test/setup-script-test.py",
|
|
199
|
+
since: "4.3.0", reason: "마법사 테스트 Python 단일화" },
|
|
200
|
+
{ id: "util-firebase-test-ps1", category: "util-file", tier: "safe",
|
|
201
|
+
file: ".github/util/flutter/firebase-wizard/test/setup-script-test.ps1", replacedBy: "test/setup-script-test.py",
|
|
202
|
+
since: "4.3.0", reason: "마법사 테스트 Python 단일화" },
|
|
203
|
+
{ id: "util-testflight-setup-sh", category: "util-file", tier: "safe",
|
|
204
|
+
file: ".github/util/flutter/testflight-wizard/testflight-wizard-setup.sh", replacedBy: "testflight-wizard.py",
|
|
205
|
+
since: "4.3.0", reason: "마법사 스크립트 Python 단일화 — setup 서브커맨드로 대체" },
|
|
164
206
|
];
|
package/src/core/options-ask.js
CHANGED
|
@@ -56,6 +56,42 @@ export { parseTemplateOptions };
|
|
|
56
56
|
|
|
57
57
|
export const DEPLOY_TARGETS = ["docker-ssh", "vercel", "none"];
|
|
58
58
|
export const PUBLISH_TARGETS = ["nexus", "npm", "github-packages"];
|
|
59
|
+
|
|
60
|
+
// #498 — 타입별 적용 가능 deploy/publish 타겟 선언.
|
|
61
|
+
// 빈 배열 = 그 축이 개념상 성립하지 않는 타입. 모바일 앱(flutter/react-native/expo)은 스토어 배포
|
|
62
|
+
// (Play Store/TestFlight/Firebase 등) 워크플로우가 타입 자체에 항상 포함되므로 서버 deploy 축과
|
|
63
|
+
// 무관하고, 레지스트리 publish 개념도 없다. 두 축이 모두 빈 타입만 선택되면 질문 자체를 스킵한다.
|
|
64
|
+
// 서버형 타입은 현행 선택지 전체를 유지한다 (동작 무변경 — 타입별 세분화는 후속 과제).
|
|
65
|
+
export const TYPE_DEPLOY_TARGETS = {
|
|
66
|
+
spring: ["docker-ssh", "vercel"],
|
|
67
|
+
react: ["docker-ssh", "vercel"],
|
|
68
|
+
node: ["docker-ssh", "vercel"],
|
|
69
|
+
python: ["docker-ssh", "vercel"],
|
|
70
|
+
flutter: [],
|
|
71
|
+
"react-native": [],
|
|
72
|
+
"react-native-expo": [],
|
|
73
|
+
basic: [],
|
|
74
|
+
};
|
|
75
|
+
export const TYPE_PUBLISH_TARGETS = {
|
|
76
|
+
spring: ["nexus", "npm", "github-packages"],
|
|
77
|
+
react: ["nexus", "npm", "github-packages"],
|
|
78
|
+
node: ["nexus", "npm", "github-packages"],
|
|
79
|
+
python: ["nexus", "npm", "github-packages"],
|
|
80
|
+
flutter: [],
|
|
81
|
+
"react-native": [],
|
|
82
|
+
"react-native-expo": [],
|
|
83
|
+
basic: [],
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// 선택된 타입 집합의 합집합으로 적용 가능 타겟 산출 (#498). 'none'은 항상 허용이라 목록에서 제외.
|
|
87
|
+
// 미선언 타입은 보수적으로 전체 허용(질문 유지) — 새 타입 추가 시 위 선언도 함께 넣는 게 원칙.
|
|
88
|
+
export function applicableTargets(types = []) {
|
|
89
|
+
const deployAll = DEPLOY_TARGETS.filter((t) => t !== "none");
|
|
90
|
+
return {
|
|
91
|
+
deploy: deployAll.filter((t) => types.some((ty) => (TYPE_DEPLOY_TARGETS[ty] ?? deployAll).includes(t))),
|
|
92
|
+
publish: PUBLISH_TARGETS.filter((t) => types.some((ty) => (TYPE_PUBLISH_TARGETS[ty] ?? PUBLISH_TARGETS).includes(t))),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
59
95
|
// #455 — changelog 생성기 provider. github-ai가 기본(설정 제로). openai/gemini/claude/ollama는 openai 호환 한 갈래.
|
|
60
96
|
export const CHANGELOG_PROVIDERS = ["github-ai", "coderabbit", "openai", "gemini", "claude", "ollama", "commit"];
|
|
61
97
|
|
|
@@ -124,10 +160,12 @@ export async function askAllOptionalWorkflows({
|
|
|
124
160
|
let deployBranchCreated = null; // #493 — 이번 실행에서 마법사가 직접 생성했는지 (가이드 기록용)
|
|
125
161
|
let intent = current.intent ?? null; // #485 프로젝트 성격 (app/library/both/none/manual)
|
|
126
162
|
|
|
127
|
-
// basic
|
|
128
|
-
//
|
|
129
|
-
// (
|
|
130
|
-
|
|
163
|
+
// 두 축이 모두 빈 타입 조합(#498 — basic 단독, flutter 등 모바일 앱 단독)은 서버 배포도
|
|
164
|
+
// 라이브러리 publish도 개념상 성립하지 않는다. 질문을 전부 건너뛰고 none·[]로 조용히 확정한다
|
|
165
|
+
// (모바일 스토어 배포는 타입 워크플로우가 항상 포함되므로 안내조차 불필요 — 타입 변경 시 재질문됨).
|
|
166
|
+
// 구 isBasicOnly 특례를 일반화한 판정이다.
|
|
167
|
+
const applicable = applicableTargets(types);
|
|
168
|
+
const noAxes = types.length > 0 && applicable.deploy.length === 0 && applicable.publish.length === 0;
|
|
131
169
|
|
|
132
170
|
// ① --force-ask가 아니면 version.yml 저장값을 먼저 읽어 재질문을 건너뛴다.
|
|
133
171
|
// CLI 명시값(current)이 이미 있으면 그쪽이 우선 — 저장값은 빈 자리만 채운다.
|
|
@@ -158,9 +196,14 @@ export async function askAllOptionalWorkflows({
|
|
|
158
196
|
}
|
|
159
197
|
}
|
|
160
198
|
|
|
199
|
+
// 적용 불가 타겟 조용한 정리 (#498) — 구버전에서 저장된 무의미 값(예: flutter 단독 + docker-ssh)은
|
|
200
|
+
// 어차피 복사 결과가 동일하므로 경고 없이 none/교집합으로 정리한다 (SSOT — 무의미 값 잔존 금지).
|
|
201
|
+
if (deploy !== null && deploy !== "none" && !applicable.deploy.includes(deploy)) deploy = "none";
|
|
202
|
+
if (Array.isArray(publish)) publish = publish.filter((t) => applicable.publish.includes(t));
|
|
203
|
+
|
|
161
204
|
// ── ② intent(프로젝트 성격) 우선 분기 → 배포 축 유도 (#485) ──
|
|
162
|
-
//
|
|
163
|
-
if (
|
|
205
|
+
// 두 축 모두 빈 타입 조합이면 intent 개념 자체가 없어 질문 스킵, none/[]로 확정 (#498).
|
|
206
|
+
if (noAxes) {
|
|
164
207
|
if (deploy === null) deploy = "none";
|
|
165
208
|
if (publish === null) publish = [];
|
|
166
209
|
intent = "none";
|
|
@@ -216,22 +259,28 @@ export async function askAllOptionalWorkflows({
|
|
|
216
259
|
say(" 1) 실행물 배포 — 서버/호스팅에 올려 돌리는 것 (Docker, Vercel …)");
|
|
217
260
|
say(" 2) 라이브러리 배포(publish) — 남이 가져다 쓰게 레지스트리에 내는 것 (Nexus, npm …)");
|
|
218
261
|
}
|
|
262
|
+
// 비대화형/폴백 기본값 (#498) — docker-ssh가 적용 가능할 때만 기본, 아니면 none.
|
|
263
|
+
const deployDefault = applicable.deploy.includes("docker-ssh") ? "docker-ssh" : "none";
|
|
219
264
|
if (willAskDeploy) {
|
|
220
265
|
if (force || !tty || typeof io.select !== "function") {
|
|
221
|
-
deploy = deploy ??
|
|
266
|
+
deploy = deploy ?? deployDefault;
|
|
222
267
|
} else {
|
|
223
268
|
say("");
|
|
224
269
|
say("🚀 (1) 실행물(서버/앱)을 어디에 올리나요?");
|
|
225
270
|
say(" 서버·호스팅에 올릴 계획이 있으면 고르고, 없으면 '서버에 올리지 않음'으로 두세요.");
|
|
271
|
+
// 선택지는 적용 가능 타겟으로 필터링 (#498) — 'none'은 항상 노출.
|
|
272
|
+
const deployLabels = [
|
|
273
|
+
{ value: "docker-ssh", label: "Docker + SSH 서버 배포 (기본)" },
|
|
274
|
+
{ value: "vercel", label: "Vercel" },
|
|
275
|
+
];
|
|
226
276
|
const ans = await io.select({
|
|
227
277
|
message: "실행물 배포 방식을 선택하세요",
|
|
228
278
|
options: [
|
|
229
|
-
|
|
230
|
-
{ value: "vercel", label: "Vercel" },
|
|
279
|
+
...deployLabels.filter((o) => applicable.deploy.includes(o.value)),
|
|
231
280
|
{ value: "none", label: "서버에 올리지 않음 (빌드 검증만 · 라이브러리는 다음에서 선택)" },
|
|
232
281
|
],
|
|
233
282
|
});
|
|
234
|
-
deploy = (!isCancel(ans) &&
|
|
283
|
+
deploy = (!isCancel(ans) && (ans === "none" || applicable.deploy.includes(ans))) ? ans : (deploy ?? deployDefault);
|
|
235
284
|
say(`실행물 배포: ${deploy}`);
|
|
236
285
|
}
|
|
237
286
|
}
|
|
@@ -244,18 +293,20 @@ export async function askAllOptionalWorkflows({
|
|
|
244
293
|
say("");
|
|
245
294
|
say("📦 (2) 이 프로젝트를 남이 가져다 쓰는 라이브러리로도 배포(publish)하나요?");
|
|
246
295
|
say(" (1) 실행물 배포와 별개입니다. 해당되는 걸 고르고, 라이브러리 배포를 안 하면 아무것도 고르지 말고 Enter.");
|
|
296
|
+
// 선택지는 적용 가능 타겟으로 필터링 (#498)
|
|
297
|
+
const publishLabels = [
|
|
298
|
+
{ value: "nexus", label: "사내 Maven(Nexus) 라이브러리 배포" },
|
|
299
|
+
{ value: "npm", label: "공개 npmjs 패키지 배포 (NPM_TOKEN)" },
|
|
300
|
+
{ value: "github-packages", label: "GitHub Packages 라이브러리 배포" },
|
|
301
|
+
];
|
|
247
302
|
const ans = await io.multiselect({
|
|
248
303
|
message: "라이브러리 배포 타겟 (없으면 선택 없이 Enter = 배포 안 함)",
|
|
249
|
-
options:
|
|
250
|
-
{ value: "nexus", label: "사내 Maven(Nexus) 라이브러리 배포" },
|
|
251
|
-
{ value: "npm", label: "공개 npmjs 패키지 배포 (NPM_TOKEN)" },
|
|
252
|
-
{ value: "github-packages", label: "GitHub Packages 라이브러리 배포" },
|
|
253
|
-
],
|
|
304
|
+
options: publishLabels.filter((o) => applicable.publish.includes(o.value)),
|
|
254
305
|
initialValues: publish ?? [],
|
|
255
306
|
required: false,
|
|
256
307
|
});
|
|
257
308
|
publish = (!isCancel(ans) && Array.isArray(ans))
|
|
258
|
-
? ans.filter((t) =>
|
|
309
|
+
? ans.filter((t) => applicable.publish.includes(t))
|
|
259
310
|
: (publish ?? []);
|
|
260
311
|
say(publish.length ? `라이브러리 배포: ${publish.join(", ")}` : "라이브러리 배포: 안 함");
|
|
261
312
|
}
|
|
@@ -351,7 +402,8 @@ export async function askAllOptionalWorkflows({
|
|
|
351
402
|
current: secretBackup, force, tty, io, forceAsk: ask("secret"), say,
|
|
352
403
|
});
|
|
353
404
|
|
|
354
|
-
|
|
405
|
+
// 최종 폴백도 적용 가능 타겟 기준 (#498) — 모바일/basic 조합에서 docker-ssh가 새어 나가지 않게.
|
|
406
|
+
const finalDeploy = deploy ?? (applicable.deploy.includes("docker-ssh") ? "docker-ssh" : "none");
|
|
355
407
|
const finalPublish = publish ?? [];
|
|
356
408
|
return {
|
|
357
409
|
deploy: finalDeploy, publish: finalPublish, secretBackup: secretBackup === true,
|
package/src/index.js
CHANGED
|
@@ -17,6 +17,7 @@ import { detectOrphanWorkflows } from "./core/orphan-workflows.js";
|
|
|
17
17
|
import { createRunTrace } from "./core/run-trace.js";
|
|
18
18
|
import { appendGuideEntry } from "./core/migration-guide.js";
|
|
19
19
|
import { resolveProjectPaths } from "./core/paths-resolve.js";
|
|
20
|
+
import { applicableTargets } from "./core/options-ask.js";
|
|
20
21
|
import { printBannerCompact } from "./ui/banner.js";
|
|
21
22
|
import { printSummary } from "./ui/summary.js";
|
|
22
23
|
import { runFull } from "./commands/full.js";
|
|
@@ -107,15 +108,23 @@ export async function run(argv, { cwd = process.cwd(), source = { type: "git" },
|
|
|
107
108
|
const tempDir = join(cwd, PATHS.tempDir);
|
|
108
109
|
|
|
109
110
|
// 프로젝트 성격(#485): CLI --intent → version.yml 저장값. deploy/publish 유도의 기준.
|
|
110
|
-
|
|
111
|
-
// 배포/publish 축(#439): CLI 플래그 최우선 → 저장값 →
|
|
112
|
-
// 없으면 intent가 유도한다 (#485 비대화형):
|
|
113
|
-
|
|
111
|
+
let intent = opts.intent ?? existing?.options?.intent ?? null;
|
|
112
|
+
// 배포/publish 축(#439): CLI 플래그 최우선 → 저장값 → 기본값(적용 가능할 때만 docker-ssh — #498).
|
|
113
|
+
// 단 --intent가 명시됐고 해당 축 플래그가 없으면 intent가 유도한다 (#485 비대화형):
|
|
114
|
+
// library/none이면 deploy=none, app/none이면 publish=[].
|
|
115
|
+
const applicable = applicableTargets(types);
|
|
116
|
+
let deployTarget = opts.deployTarget ?? existing?.options?.deploy
|
|
117
|
+
?? (applicable.deploy.includes("docker-ssh") ? "docker-ssh" : "none");
|
|
114
118
|
let publishTargets = opts.publishTargets ?? existing?.options?.publish ?? [];
|
|
115
119
|
if (opts.intent != null) {
|
|
116
120
|
if ((intent === "library" || intent === "none") && opts.deployTarget == null) deployTarget = "none";
|
|
117
121
|
if ((intent === "app" || intent === "none") && opts.publishTargets == null) publishTargets = [];
|
|
118
122
|
}
|
|
123
|
+
// 적용 불가 타겟 조용한 정리 (#498) — 대화형과 동일 규칙. 타입에 성립하지 않는 축 값은
|
|
124
|
+
// 복사 결과가 동일하므로 경고 없이 none/교집합으로 정리한다 (모바일 앱/basic 단독 등).
|
|
125
|
+
if (deployTarget !== "none" && !applicable.deploy.includes(deployTarget)) deployTarget = "none";
|
|
126
|
+
publishTargets = publishTargets.filter((t) => applicable.publish.includes(t));
|
|
127
|
+
if (types.length > 0 && applicable.deploy.length === 0 && applicable.publish.length === 0) intent = "none";
|
|
119
128
|
|
|
120
129
|
const context = createContext({
|
|
121
130
|
mode: opts.mode, force: true, types, version, versionCode, branch,
|
package/src/ui/prompts.js
CHANGED
|
@@ -32,18 +32,22 @@ export async function confirmProjectMenu() {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
// 수정 메뉴 — 어떤 항목을 고칠지. showOptional=full/workflows에서만 nexus/secret 노출.
|
|
35
|
-
|
|
35
|
+
// axes(#498): applicableTargets(types) 결과 — 적용 불가 축의 항목은 숨긴다 (모바일 앱/basic 단독 등).
|
|
36
|
+
// null이면 기존처럼 전부 노출 (테스트 스텁 하위호환).
|
|
37
|
+
export async function editMenu({ showOptional = false, axes = null } = {}) {
|
|
36
38
|
const options = [
|
|
37
39
|
{ value: "type", label: "프로젝트 타입" },
|
|
38
40
|
{ value: "version", label: "버전" },
|
|
39
41
|
{ value: "branch", label: "기본 브랜치" },
|
|
40
42
|
];
|
|
41
43
|
if (showOptional) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
const hasDeploy = axes == null || axes.deploy.length > 0;
|
|
45
|
+
const hasPublish = axes == null || axes.publish.length > 0;
|
|
46
|
+
// #485 — 프로젝트 성격(intent): 재선택 시 배포/publish 축을 재유도한다. 축이 하나도 없으면 무의미 → 숨김.
|
|
47
|
+
if (hasDeploy || hasPublish) options.push({ value: "intent", label: "프로젝트 성격 (배포 유형)" });
|
|
44
48
|
// #483 — 항목별 격리: 한 축만 골라 그 축만 재질문한다 (통짜 "배포/Publish 방식" 분해)
|
|
45
|
-
options.push({ value: "deploy", label: "배포 방식 (서버 실행물)" });
|
|
46
|
-
options.push({ value: "publish", label: "라이브러리 배포(publish) 타겟" });
|
|
49
|
+
if (hasDeploy) options.push({ value: "deploy", label: "배포 방식 (서버 실행물)" });
|
|
50
|
+
if (hasPublish) options.push({ value: "publish", label: "라이브러리 배포(publish) 타겟" });
|
|
47
51
|
options.push({ value: "code-review", label: "CodeRabbit 코드 리뷰" });
|
|
48
52
|
options.push({ value: "changelog", label: "릴리스 노트(changelog) 생성기" });
|
|
49
53
|
options.push({ value: "release-branch", label: "릴리스 소스(개발) 브랜치" });
|
package/src/ui/summary.js
CHANGED
|
@@ -120,8 +120,9 @@ export function printSummary(ctx, targetRoot = ".") {
|
|
|
120
120
|
}
|
|
121
121
|
if (types.includes("flutter") && utilModulesCopied > 0) {
|
|
122
122
|
err(" 💡 Flutter 배포 마법사 사용법:");
|
|
123
|
-
err(" • iOS TestFlight: .github/util/flutter/
|
|
124
|
-
err(" • Android Play Store: .github/util/flutter/
|
|
123
|
+
err(" • iOS TestFlight: .github/util/flutter/testflight-wizard/testflight-wizard.html");
|
|
124
|
+
err(" • Android Play Store: .github/util/flutter/playstore-wizard/playstore-wizard.html");
|
|
125
|
+
err(" • Firebase App Distribution: .github/util/flutter/firebase-wizard/firebase-wizard.html");
|
|
125
126
|
err(" • 브라우저에서 열어 필요한 정보 입력 후 파일 생성");
|
|
126
127
|
err("");
|
|
127
128
|
}
|