project-auto-wizard 0.10.0 → 0.11.0
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 +83 -4
- package/package.json +1 -1
- package/payload/config/breaking-changes.json +24 -1
- package/payload/flutter-app/android/fastlane/Fastfile.playstore +86 -0
- package/payload/flutter-app/ios/ExportOptions.plist +31 -0
- package/payload/flutter-app/ios/fastlane/Fastfile +117 -0
- package/payload/scripts/__pycache__/changelog_manager.cpython-314.pyc +0 -0
- package/payload/scripts/__pycache__/issue_helper.cpython-314.pyc +0 -0
- package/payload/scripts/__pycache__/version_manager.cpython-314.pyc +0 -0
- package/payload/version.yml.template +1 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-FIREBASE-CICD.yaml +48 -20
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml +44 -19
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD.yaml +28 -36
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-TEST-APK.yaml +32 -45
- package/payload/workflows/flutter/PROJECT-FLUTTER-CI.yaml +123 -32
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml +56 -41
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml +61 -40
- package/payload/workflows/go/PROJECT-GO-CI.yaml +47 -0
- package/payload/workflows/next/PROJECT-NEXT-CI.yaml +47 -0
- package/payload/workflows/python/PROJECT-PYTHON-CI.yaml +47 -0
- package/payload/workflows/react/PROJECT-REACT-CI.yaml +47 -0
- package/payload/workflows/spring/nexus/PROJECT-SPRING-GITHUB-PACKAGES-PUBLISH.yml +1 -0
- package/payload/workflows/spring/nexus/PROJECT-SPRING-NEXUS-CI.yml +47 -0
- package/payload/workflows/spring/nexus/PROJECT-SPRING-NEXUS-PUBLISH.yml +1 -0
- package/src/cli/args.js +37 -0
- package/src/cli/help.js +4 -0
- package/src/commands/doctor.js +55 -2
- package/src/commands/dry-run.js +10 -0
- package/src/commands/full.js +28 -3
- package/src/commands/interactive-flutter.js +95 -0
- package/src/commands/interactive.js +51 -5
- package/src/commands/status.js +20 -2
- package/src/context.js +6 -0
- package/src/core/breaking.js +7 -3
- package/src/core/copy/flutter-app.js +42 -0
- package/src/core/copy/workflows.js +21 -9
- package/src/core/detect-fs.js +9 -1
- package/src/core/flutter-options.js +118 -0
- package/src/core/installed-stores.js +12 -0
- package/src/core/version-yml.js +40 -5
- package/src/core/wizard-env.js +26 -4
- package/src/index.js +32 -1
- package/src/ui/env-plan.js +8 -5
- package/src/ui/prompts.js +88 -3
- package/src/ui/status-cards.js +13 -1
- package/src/ui/summary.js +24 -5
package/src/ui/prompts.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// 취소(ESC/Ctrl+C)는 각 함수가 CANCEL 심볼을 반환 → 호출부가 정상 종료(exit 0) 처리.
|
|
4
4
|
import * as engine from "./readline-engine.js";
|
|
5
5
|
import { DEPLOY_STYLES, NO_DEPLOY_STYLE } from "../core/deploy-style.js";
|
|
6
|
+
import { ENV_MODES, DEFAULT_ENV_MODE, STORE_PLATFORMS, DEPLOY_MODES, DEFAULT_DEPLOY_MODE } from "../core/flutter-options.js";
|
|
6
7
|
|
|
7
8
|
export const CANCEL = engine.CANCEL;
|
|
8
9
|
|
|
@@ -33,8 +34,10 @@ export async function confirmProjectMenu() {
|
|
|
33
34
|
});
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
// 수정 메뉴 —
|
|
37
|
-
|
|
37
|
+
// 수정 메뉴 항목 — showOptional=full/workflows에서만 nexus/secret 노출,
|
|
38
|
+
// showFlutter=Flutter 타입일 때만 환경변수 방식/스토어 배포 대상/배포 모드 노출 (이슈 #131).
|
|
39
|
+
// 라벨·순서를 테스트할 수 있도록 순수 함수로 분리했다.
|
|
40
|
+
export function editMenuOptions({ showOptional = false, showFlutter = false } = {}) {
|
|
38
41
|
const options = [
|
|
39
42
|
{ value: "type", label: "프로젝트 타입" },
|
|
40
43
|
{ value: "version", label: "버전" },
|
|
@@ -44,8 +47,18 @@ export async function editMenu({ showOptional = false } = {}) {
|
|
|
44
47
|
options.push({ value: "nexus", label: "Nexus publish 포함 여부" });
|
|
45
48
|
options.push({ value: "secret", label: "Secret 백업 포함 여부" });
|
|
46
49
|
}
|
|
50
|
+
if (showFlutter) {
|
|
51
|
+
options.push({ value: "envMode", label: "환경변수 방식" });
|
|
52
|
+
options.push({ value: "flutterStore", label: "스토어 배포 대상" });
|
|
53
|
+
options.push({ value: "deployMode", label: "배포 모드" });
|
|
54
|
+
}
|
|
47
55
|
options.push({ value: "done", label: "모두 맞음, 계속" });
|
|
48
|
-
return
|
|
56
|
+
return options;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 수정 메뉴 — 어떤 항목을 고칠지.
|
|
60
|
+
export async function editMenu({ showOptional = false, showFlutter = false } = {}) {
|
|
61
|
+
return engine.select({ message: "어떤 항목을 수정할까요?", options: editMenuOptions({ showOptional, showFlutter }) });
|
|
49
62
|
}
|
|
50
63
|
|
|
51
64
|
const ALL_TYPES = ["spring", "flutter", "next", "react", "react-native", "react-native-expo", "node", "python", "basic", "go"];
|
|
@@ -103,6 +116,78 @@ export async function selectDeployStyle() {
|
|
|
103
116
|
});
|
|
104
117
|
}
|
|
105
118
|
|
|
119
|
+
// ── Flutter 옵션 (이슈 #131) ─────────────────────────────────────────
|
|
120
|
+
// 프로젝트 타입에 flutter가 포함된 경우에만 interactive.js가 묻는다. 세 함수 모두 취소(ESC) 시 CANCEL을
|
|
121
|
+
// 그대로 돌려주고, "ESC = 기본값" 처리는 호출부가 한다 (selectDeployStyle과 같은 규약).
|
|
122
|
+
const ENV_MODE_LABELS = {
|
|
123
|
+
"dart-define": "dart-define (신규 설치 기본) — 시크릿을 --dart-define-from-file로 빌드에 전달, 프로젝트에 .env를 만들지 않음",
|
|
124
|
+
dotenv: "dotenv — Flutter 루트에 .env를 만들어 flutter_dotenv·envied가 읽음 (기존 설치 유지값)",
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const STORE_LABELS = {
|
|
128
|
+
android: "Android — Google Play Store (fastlane)",
|
|
129
|
+
ios: "iOS — TestFlight / App Store Connect (fastlane)",
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const DEPLOY_MODE_LABELS = {
|
|
133
|
+
android: {
|
|
134
|
+
store_only: "store_only — internal 트랙에 업로드 (기본)",
|
|
135
|
+
store_prepare: "store_prepare — production 트랙에 draft로 업로드 (Play Console에서 직접 출시)",
|
|
136
|
+
store_submit: "store_submit — production 심사 제출까지",
|
|
137
|
+
},
|
|
138
|
+
ios: {
|
|
139
|
+
store_only: "store_only — TestFlight 업로드 (기본)",
|
|
140
|
+
store_prepare: "store_prepare — 앱 버전·메타데이터 준비까지 (심사 제출 안 함)",
|
|
141
|
+
store_submit: "store_submit — 심사 제출까지",
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const PLATFORM_TITLES = { android: "Android (Play Store)", ios: "iOS (TestFlight)" };
|
|
146
|
+
|
|
147
|
+
// 환경변수 방식 — 시크릿 ENV_FILE(.env 형식)을 Flutter 빌드에 넘기는 방법.
|
|
148
|
+
export async function selectEnvMode({ initialValue = DEFAULT_ENV_MODE } = {}) {
|
|
149
|
+
engine.note(
|
|
150
|
+
"시크릿 ENV_FILE(.env 형식)을 Flutter 빌드에 넘기는 방식입니다.\n" +
|
|
151
|
+
"dart-define은 String.fromEnvironment로 읽고, dotenv는 flutter_dotenv·envied가 .env 파일을 읽습니다.\n" +
|
|
152
|
+
"나중에 확인 화면의 '수정하기 > 환경변수 방식'에서 바꿀 수 있습니다.",
|
|
153
|
+
"환경변수 방식",
|
|
154
|
+
);
|
|
155
|
+
return engine.select({
|
|
156
|
+
message: "Flutter 환경변수는 어떤 방식으로 넘길까요?",
|
|
157
|
+
options: ENV_MODES.map((value) => ({ value, label: ENV_MODE_LABELS[value] })),
|
|
158
|
+
initialIndex: Math.max(0, ENV_MODES.indexOf(initialValue)),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// 스토어 배포 대상 — 고른 플랫폼의 워크플로우와 fastlane 템플릿만 설치한다. 아무것도 안 고르면 스토어 배포 없이 설치.
|
|
163
|
+
export async function selectFlutterStores({ initialValues = [] } = {}) {
|
|
164
|
+
engine.note(
|
|
165
|
+
"고른 플랫폼의 스토어 배포 워크플로우와 fastlane 파일(Fastfile 등)만 설치합니다.\n" +
|
|
166
|
+
"아무것도 고르지 않으면 스토어 배포 없이 설치합니다 (Firebase·Selfhosted·Test APK·CI는 항상 설치).",
|
|
167
|
+
"스토어 배포 대상",
|
|
168
|
+
);
|
|
169
|
+
return engine.multiselect({
|
|
170
|
+
message: "스토어에 배포할 플랫폼을 선택하세요 (Space 토글, Enter 확정)",
|
|
171
|
+
options: STORE_PLATFORMS.map((value) => ({ value, label: STORE_LABELS[value] })),
|
|
172
|
+
initialValues,
|
|
173
|
+
required: false,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// 배포 모드 — 플랫폼별로 한 번씩 묻는다. 런타임의 저장소 변수와 workflow_dispatch 입력이 항상 이 값보다 우선한다.
|
|
178
|
+
export async function selectDeployMode({ platform, initialValue = DEFAULT_DEPLOY_MODE }) {
|
|
179
|
+
return engine.select({
|
|
180
|
+
message: `${PLATFORM_TITLES[platform]} 배포 모드를 선택하세요`,
|
|
181
|
+
options: DEPLOY_MODES.map((value) => ({ value, label: DEPLOY_MODE_LABELS[platform][value] })),
|
|
182
|
+
initialIndex: Math.max(0, DEPLOY_MODES.indexOf(initialValue)),
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// store_submit은 main push마다 심사를 제출하므로 고른 직후 한 줄로 알린다. 호출부(interactive)가 note로 출력한다.
|
|
187
|
+
export function deployModeWarning(mode) {
|
|
188
|
+
return mode === "store_submit" ? "store_submit을 고르면 main push마다 심사가 자동 제출됩니다." : "";
|
|
189
|
+
}
|
|
190
|
+
|
|
106
191
|
// 브랜치 전략 선택 (이슈 #93). 종전에는 "릴리스 브랜치"/"개발 브랜치" 두 질문에
|
|
107
192
|
// 같은 이름을 입력해야만 trunk-based가 됐는데, 그 규칙이 사전에 안내되지 않아
|
|
108
193
|
// 의도치 않게 pr-flow로 흘러갔다. 전략을 먼저 명시적으로 고르게 해 이를 없앤다.
|
package/src/ui/status-cards.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// (층5의 Breaking Changes 박스는 core/breaking-check.js가 담당.
|
|
3
3
|
// 원본의 층4 IDE Skills 상태는 project-auto-wizard 스코프 제외 — Agent Skills 미포함)
|
|
4
4
|
import { A, paint } from "./ansi.js";
|
|
5
|
+
import { DEFAULT_DEPLOY_MODE } from "../core/flutter-options.js";
|
|
5
6
|
|
|
6
7
|
const GUT = paint("│", A.gray);
|
|
7
8
|
const HEAD = paint("◆", A.cyan);
|
|
@@ -29,7 +30,8 @@ export function printDetectionLog({ types = [], version = "", branch = "", marke
|
|
|
29
30
|
|
|
30
31
|
// 층3 — 프로젝트 분석 개요 카드 (.ps1 Print-ProjectAnalysis 등가+)
|
|
31
32
|
export function printAnalysisCard({ mode = "", modeLabel = "", types = [], version = "", branch = "",
|
|
32
|
-
includeNexus = null, includeSecretBackup = null, paths = new Map(), showOptional = false
|
|
33
|
+
includeNexus = null, includeSecretBackup = null, paths = new Map(), showOptional = false,
|
|
34
|
+
flutter = null, envModeDefault = "" },
|
|
33
35
|
out = (s) => process.stderr.write(s)) {
|
|
34
36
|
out(`${HEAD} ${paint("프로젝트 분석 결과", A.bold)}\n`);
|
|
35
37
|
const row = (icon, label, value) => out(`${GUT} ${icon} ${label.padEnd(10)} ${value}\n`);
|
|
@@ -40,6 +42,16 @@ export function printAnalysisCard({ mode = "", modeLabel = "", types = [], versi
|
|
|
40
42
|
if (showOptional) {
|
|
41
43
|
row("📦", "Nexus", includeNexus === true ? paint("포함", A.green) : paint("제외", A.dim));
|
|
42
44
|
row("🔐", "Secret백업", includeSecretBackup === true ? paint("포함", A.green) : paint("제외", A.dim));
|
|
45
|
+
// Flutter 옵션 (이슈 #131 fable5.1 리뷰 Important #1) — 확정 직전 화면에서도 선택값을 보여준다.
|
|
46
|
+
if (flutter && types.includes("flutter")) {
|
|
47
|
+
const stores = flutter.stores ?? [];
|
|
48
|
+
const modeParts = stores.map(
|
|
49
|
+
(p) => `${p}=${(p === "android" ? flutter.androidDeployMode : flutter.iosDeployMode) || DEFAULT_DEPLOY_MODE}`,
|
|
50
|
+
);
|
|
51
|
+
row("⚙️", "환경변수", flutter.envMode || envModeDefault);
|
|
52
|
+
row("🏬", "스토어", stores.length ? stores.join(", ") : "없음");
|
|
53
|
+
row("🚀", "배포모드", modeParts.length ? modeParts.join(" ") : "없음");
|
|
54
|
+
}
|
|
43
55
|
}
|
|
44
56
|
// 모노레포 경로 — 루트가 아닌 항목이 하나라도 있으면 표시
|
|
45
57
|
const nonRoot = [...paths.entries()].filter(([, p]) => p && p !== ".");
|
package/src/ui/summary.js
CHANGED
|
@@ -8,7 +8,9 @@ const SEPARATOR = "────────────────────
|
|
|
8
8
|
export function printSummary(ctx) {
|
|
9
9
|
const { mode, types = [], version = "", versionCode = null, copiedFiles = [], branches = null, gitignoreUpdated = false,
|
|
10
10
|
// 설치 후 검증·기록 (#79, #80, #81)
|
|
11
|
-
answers = [], unresolved = [], secrets = new Map(), logPath = "", legacyMdLogs = false, cleanup = null
|
|
11
|
+
answers = [], unresolved = [], secrets = new Map(), logPath = "", legacyMdLogs = false, cleanup = null,
|
|
12
|
+
// Flutter 스토어 배포 (이슈 #131) — 앱 파일 생성/유지와 스토어 선택 해제 정리 결과
|
|
13
|
+
flutterApp = null, storeCleanup = null } = ctx || {};
|
|
12
14
|
const err = (s = "") => process.stderr.write(`${s}\n`);
|
|
13
15
|
// 색상은 ansi.js의 공용 가드로 통일 (NO_COLOR + stderr TTY 여부)
|
|
14
16
|
const enabled = colorEnabled(process.stderr);
|
|
@@ -105,10 +107,18 @@ export function printSummary(ctx) {
|
|
|
105
107
|
err("");
|
|
106
108
|
}
|
|
107
109
|
// 배포 방식을 바꿔 재설치한 경우, 이전 CD를 어떻게 처리했는지 알린다 (#80).
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
printCleanup(err, enabled, "이전 배포 방식 정리", cleanup);
|
|
111
|
+
// 스토어 배포 대상을 해제한 경우도 같은 규칙으로 정리한 결과를 알린다 (#131).
|
|
112
|
+
printCleanup(err, enabled, "선택 해제한 스토어 배포 정리", storeCleanup);
|
|
113
|
+
// Fastfile·ExportOptions.plist는 사용자 소유라 없을 때만 만든다 — 만든 것과 그대로 둔 것을 나눠 보여준다 (#131).
|
|
114
|
+
const { created: appCreated = [], kept: appKept = [] } = flutterApp || {};
|
|
115
|
+
if (appCreated.length || appKept.length) {
|
|
116
|
+
err(" 📱 Flutter 스토어 배포 파일:");
|
|
117
|
+
for (const f of appCreated) err(` • ${f} ${paint("새로 생성", A.dim, enabled)}`);
|
|
118
|
+
for (const f of appKept) err(` • ${f} ${paint("기존 파일 유지", A.dim, enabled)}`);
|
|
119
|
+
if (appCreated.some((f) => f.endsWith("ExportOptions.plist"))) {
|
|
120
|
+
err(" → ExportOptions.plist의 __TEAM_ID__ · __BUNDLE_ID__ · __PROVISIONING_PROFILE_NAME__ 을 실제 값으로 채워야 iOS 배포가 동작합니다");
|
|
121
|
+
}
|
|
112
122
|
err("");
|
|
113
123
|
}
|
|
114
124
|
if (logPath) {
|
|
@@ -175,3 +185,12 @@ export function printSummary(ctx) {
|
|
|
175
185
|
err(paint("📖 워크플로우 구성과 릴리스 흐름은 README를 참고하세요.", A.cyan, enabled));
|
|
176
186
|
err("");
|
|
177
187
|
}
|
|
188
|
+
|
|
189
|
+
// 삭제·백업 정리 결과 한 블록 — 이전 배포 방식 정리와 스토어 선택 해제 정리가 같은 형식을 쓴다.
|
|
190
|
+
function printCleanup(err, enabled, title, cleanup) {
|
|
191
|
+
if (!cleanup?.removed?.length && !cleanup?.backedUp?.length) return;
|
|
192
|
+
err(` 🧹 ${title}:`);
|
|
193
|
+
for (const f of cleanup.removed || []) err(` • ${f} ${paint("삭제 (손대지 않은 파일)", A.dim, enabled)}`);
|
|
194
|
+
for (const f of cleanup.backedUp || []) err(` • ${f} → ${f}.bak ${paint("수정하신 내용이 있어 백업", A.dim, enabled)}`);
|
|
195
|
+
err("");
|
|
196
|
+
}
|