project-auto-wizard 0.12.0 → 0.12.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 +3 -7
- package/package.json +1 -1
- package/payload/config/breaking-changes.json +1 -1
- 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/scripts/changelog_manager.py +1 -1
- package/payload/scripts/version_manager.py +2 -2
- package/payload/version.yml.template +0 -2
- package/payload/workflows/common/PROJECT-COMMON-AI-PR-SUMMARY.yaml +0 -3
- package/payload/workflows/common/PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml +1 -4
- package/payload/workflows/common/PROJECT-COMMON-ISSUE-HELPER.yaml +3 -6
- package/payload/workflows/common/PROJECT-COMMON-README-VERSION-UPDATE.yaml +0 -12
- package/payload/workflows/common/PROJECT-COMMON-RELEASE-PUBLISH.yaml +2 -5
- package/payload/workflows/common/PROJECT-COMMON-VERSION-CONTROL.yaml +3 -7
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-FIREBASE-CICD.yaml +7 -34
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml +2 -25
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD.yaml +2 -20
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-TEST-APK.yaml +5 -30
- package/payload/workflows/flutter/PROJECT-FLUTTER-APP-BUILD-TRIGGER.yaml +0 -4
- package/payload/workflows/flutter/PROJECT-FLUTTER-CI.yaml +7 -51
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml +5 -29
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml +9 -29
- package/payload/workflows/go/PROJECT-GO-CI.yaml +5 -28
- package/payload/workflows/go/PROJECT-GO-PR-PREVIEW.yaml +19 -99
- package/payload/workflows/go/PROJECT-GO-SIMPLE-CICD.yaml +18 -89
- package/payload/workflows/next/PROJECT-NEXT-CI.yaml +11 -41
- package/payload/workflows/next/PROJECT-NEXT-CICD.yaml +9 -45
- package/payload/workflows/python/PROJECT-PYTHON-CI.yaml +1 -27
- package/payload/workflows/python/PROJECT-PYTHON-PR-PREVIEW.yaml +14 -97
- package/payload/workflows/python/PROJECT-PYTHON-SIMPLE-CICD.yaml +14 -92
- package/payload/workflows/react/PROJECT-REACT-CI.yaml +11 -40
- package/payload/workflows/react/PROJECT-REACT-CICD.yaml +9 -43
- package/payload/workflows/spring/{nexus/PROJECT-SPRING-NEXUS-CI.yml → PROJECT-SPRING-CI.yml} +2 -25
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-NGINX-CICD.yaml +19 -63
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-TRAEFIK-CICD.yaml +19 -59
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-PR-PREVIEW.yaml +23 -86
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-SIMPLE-CICD.yaml +20 -80
- package/src/cli/args.js +1 -15
- package/src/cli/help.js +0 -2
- package/src/commands/doctor.js +5 -5
- package/src/commands/dry-run.js +1 -1
- package/src/commands/full.js +9 -10
- package/src/commands/interactive-flutter.js +1 -1
- package/src/commands/interactive.js +25 -46
- package/src/commands/purge.js +2 -2
- package/src/commands/status.js +4 -6
- package/src/commands/uninstall.js +3 -4
- package/src/context.js +4 -6
- package/src/core/assets.js +2 -2
- package/src/core/baseline.js +1 -1
- package/src/core/branches.js +3 -3
- package/src/core/breaking-check.js +1 -1
- package/src/core/breaking.js +1 -1
- package/src/core/copy/flutter-app.js +2 -2
- package/src/core/copy/gitignore.js +8 -8
- package/src/core/copy/workflows.js +24 -69
- package/src/core/deploy-style.js +2 -2
- package/src/core/detect-fs.js +11 -11
- package/src/core/detect.js +7 -7
- package/src/core/flutter-options.js +1 -1
- package/src/core/installed-stores.js +1 -1
- package/src/core/logger.js +1 -1
- package/src/core/paths-resolve.js +1 -1
- package/src/core/removal-plan.js +4 -4
- package/src/core/verify.js +3 -3
- package/src/core/version-yml.js +17 -33
- package/src/core/wizard-env.js +4 -4
- package/src/index.js +12 -14
- package/src/ui/banner.js +2 -2
- package/src/ui/env-plan.js +10 -16
- package/src/ui/prompts.js +9 -14
- package/src/ui/status-cards.js +10 -12
- package/src/ui/summary.js +9 -9
- package/payload/workflows/common/secret-backup/PROJECT-COMMON-SECRET-FILE-UPLOAD.yaml +0 -210
- package/payload/workflows/spring/nexus/PROJECT-SPRING-GITHUB-PACKAGES-PUBLISH.yml +0 -92
- package/payload/workflows/spring/nexus/PROJECT-SPRING-NEXUS-PUBLISH.yml +0 -70
- package/src/core/options-ask.js +0 -102
|
@@ -59,7 +59,7 @@ function renderVirtual(templateContent, envOpts) {
|
|
|
59
59
|
|
|
60
60
|
// 분류 — 대상 워크플로우 디렉토리 기준. srcText: 브랜치 치환이 적용된 원본 로더 (makeSrcText).
|
|
61
61
|
//
|
|
62
|
-
// baseline이 있으면 3-way로
|
|
62
|
+
// baseline이 있으면 3-way로 가른다. base가 없던 시절에는 업스트림이 한 글자만 고쳐도
|
|
63
63
|
// 사용자가 손대지 않은 파일이 changed로 떨어져, "전부 skip" 아니면 "전부 backup" 둘 중 하나만
|
|
64
64
|
// 고를 수 있었다.
|
|
65
65
|
//
|
|
@@ -101,7 +101,7 @@ function classify(srcDir, workflowsDir, envOpts, srcText, baseline = null, filte
|
|
|
101
101
|
// 한 원본 디렉토리를 분류 결과대로 처리한다. common·타입별·server-deploy가 같은 규칙을 쓴다.
|
|
102
102
|
// filter: trunk-based 제외 같은 파일 단위 필터.
|
|
103
103
|
//
|
|
104
|
-
// 자동 처리되는 두 버킷이 이 함수의
|
|
104
|
+
// 자동 처리되는 두 버킷이 이 함수의 핵심이다:
|
|
105
105
|
// upstreamOnly — 사용자가 손대지 않았으니 그냥 최신으로 교체한다. 물어볼 이유가 없다.
|
|
106
106
|
// localOnly — 업스트림이 그대로니 사용자 수정본을 그대로 둔다. 역시 물어볼 이유가 없다.
|
|
107
107
|
function processDir(srcDir, workflowsDir, envOpts, ctx, counters, filter = () => true) {
|
|
@@ -153,25 +153,25 @@ function processDir(srcDir, workflowsDir, envOpts, ctx, counters, filter = () =>
|
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
// copy_workflows 본체 (동기 — 기존 호출부 무변경).
|
|
156
|
-
// context: { types:[], paths:Map,
|
|
156
|
+
// context: { types:[], paths:Map, force, repoName, resolvers,
|
|
157
157
|
// envValues?:Map<key,value>, envUseDefaults?:boolean } ← env 계획(promptEnvPlan) 결과 주입점
|
|
158
158
|
// flutterStore?:string[]|null } ← Flutter 스토어 대상 (null=필터 없음, 배열=선택된 플랫폼만)
|
|
159
159
|
// hooks: { decisions?: Map<filename, 'skip'|'backup'|'template'>, — 진짜 충돌(changed) 결정
|
|
160
160
|
// restoreRemoved?: Set<filename> } — 사용자가 지운 파일 중 복원할 것
|
|
161
161
|
// 미지정 파일은 'skip'(현행 force 동작 100% 유지). 대화형 수집은 copyWorkflowsInteractive 참조.
|
|
162
|
-
// 반환: {copied, skipped, templateAdded,
|
|
162
|
+
// 반환: {copied, skipped, templateAdded, backupAdded, autoUpdated, keptLocal, removedKept, restoredFiles}
|
|
163
163
|
export function copyWorkflows(context, payloadRoot, targetRoot = ".", hooks = {}) {
|
|
164
|
-
const { types = [], paths = new Map(),
|
|
164
|
+
const { types = [], paths = new Map(), repoName = "", resolvers = {}, envValues = new Map(), envUseDefaults = true } = context;
|
|
165
165
|
const decisions = hooks.decisions instanceof Map ? hooks.decisions : new Map();
|
|
166
166
|
const restoreRemoved = hooks.restoreRemoved instanceof Set ? hooks.restoreRemoved : new Set();
|
|
167
167
|
const workflowsDir = join(targetRoot, PATHS.workflowsDir);
|
|
168
168
|
const projectTypesDir = join(payloadRoot, PAYLOAD.workflowsDir);
|
|
169
169
|
if (!exists(projectTypesDir)) throw new Error("패키지 구조 오류 — payload/workflows 폴더를 찾지 못했습니다.");
|
|
170
170
|
|
|
171
|
-
const counters = { copied: 0, skipped: 0, templateAdded: 0,
|
|
171
|
+
const counters = { copied: 0, skipped: 0, templateAdded: 0, backupAdded: 0 };
|
|
172
172
|
const deployValues = new Map(); // Map<type, Map<key,value>> — deploy 블록용 ask 값
|
|
173
173
|
counters.deployValues = deployValues;
|
|
174
|
-
counters.copiedFiles = []; // 이번 실행에서 실제로 새로 쓰여진 파일명 (
|
|
174
|
+
counters.copiedFiles = []; // 이번 실행에서 실제로 새로 쓰여진 파일명 (printSummary 정확성용)
|
|
175
175
|
counters.unchangedFiles = []; // skip(unchanged) 대상 — 로그에서 "왜 안 바뀌었나"의 근거
|
|
176
176
|
counters.autoUpdated = []; // 질문 없이 최신으로 교체된 파일 (사용자 미수정)
|
|
177
177
|
counters.keptLocal = []; // 질문 없이 사용자 수정본을 유지한 파일 (업스트림 무변경)
|
|
@@ -185,7 +185,7 @@ export function copyWorkflows(context, payloadRoot, targetRoot = ".", hooks = {}
|
|
|
185
185
|
const envOptsFor = (type) => ({ type, projectPath: paths.get(type) || ".", repoName, resolvers, values: envValues, useDefaults: envUseDefaults });
|
|
186
186
|
const dirCtx = { srcText, baseline, decisions, restoreRemoved, baselineTargets };
|
|
187
187
|
|
|
188
|
-
// (1) common — 타입별과 동일 규칙 (README
|
|
188
|
+
// (1) common — 타입별과 동일 규칙 (README 계약).
|
|
189
189
|
// trunk-based 모드는 VERSION-CONTROL·AUTO-CHANGELOG 미설치 (RELEASE-PUBLISH 단독).
|
|
190
190
|
const branchMode = context.branches?.mode || "pr-flow";
|
|
191
191
|
const commonDir = join(projectTypesDir, "common");
|
|
@@ -194,7 +194,7 @@ export function copyWorkflows(context, payloadRoot, targetRoot = ".", hooks = {}
|
|
|
194
194
|
const c = processDir(commonDir, workflowsDir, envOptsFor("common"), dirCtx, counters, notExcluded);
|
|
195
195
|
// env 치환 — 타입별 폴더(copyWorkflowsForType)와 동일하게, 손대지 않기로 한 파일(unchanged/localOnly)은
|
|
196
196
|
// 건너뛴다. common 최상위는 지금까지 @wizard 마커가 없어 이 루프가 없어도 드러나지 않았지만,
|
|
197
|
-
// ISSUE_HELPER_CREATE_BRANCH
|
|
197
|
+
// ISSUE_HELPER_CREATE_BRANCH부터는 실제로 값이 반영돼야 한다.
|
|
198
198
|
const untouched = [...c.unchanged, ...c.localOnly];
|
|
199
199
|
for (const filename of listYamlFiles(commonDir)) {
|
|
200
200
|
if (!notExcluded(filename)) continue;
|
|
@@ -208,27 +208,10 @@ export function copyWorkflows(context, payloadRoot, targetRoot = ".", hooks = {}
|
|
|
208
208
|
// (2~4) 타입별
|
|
209
209
|
for (const type of types) {
|
|
210
210
|
const asks = new Map();
|
|
211
|
-
copyWorkflowsForType(type, projectTypesDir, workflowsDir, {
|
|
211
|
+
copyWorkflowsForType(type, projectTypesDir, workflowsDir, { ...context, deployStyle, envOptsFor, collectAsks: asks, dirCtx }, counters);
|
|
212
212
|
if (asks.size) deployValues.set(type, asks);
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
-
// (5) common/secret-backup — 있으면 무조건 스킵/신규만 복사
|
|
216
|
-
const secretDir = join(commonDir, "secret-backup");
|
|
217
|
-
if (exists(secretDir) && includeSecretBackup) {
|
|
218
|
-
for (const filename of listYamlFiles(secretDir)) {
|
|
219
|
-
const dst = join(workflowsDir, filename);
|
|
220
|
-
if (existsSync(dst)) continue; // 이미 존재하면 스킵
|
|
221
|
-
writeText(dst, srcText(join(secretDir, filename)));
|
|
222
|
-
// 이 경로는 타입별 복사 루프 밖이라 env 치환 루프가 닿지 않는다. 여기서 직접 걸어주지
|
|
223
|
-
// 않으면 이 파일의 @wizard 마커가 통째로 무시돼 __PROJECT_NAME__ 같은 값이 그대로 설치된다.
|
|
224
|
-
configureEnv(dst, envOptsFor("common"));
|
|
225
|
-
counters.optionalCopied++;
|
|
226
|
-
counters.copied++;
|
|
227
|
-
counters.copiedFiles.push(filename);
|
|
228
|
-
baselineTargets.set(filename, { srcPath: join(secretDir, filename), envOpts: envOptsFor("common"), wrote: true });
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
215
|
counters.baselineTargets = baselineTargets; // 호출부(runFull)가 env 치환 완료 후 baseline을 기록한다
|
|
233
216
|
return counters;
|
|
234
217
|
}
|
|
@@ -276,14 +259,14 @@ function applyDecision(decision, srcDir, workflowsDir, filename, counters, srcTe
|
|
|
276
259
|
log.info("copy", "skip", `${filename} (사용자 결정: 기존 유지)`);
|
|
277
260
|
}
|
|
278
261
|
|
|
279
|
-
// 대화형 사전 조사 — 사람이 답해야 하는 것만
|
|
262
|
+
// 대화형 사전 조사 — 사람이 답해야 하는 것만 뽑는다.
|
|
280
263
|
// copyWorkflows 본체와 동일한 classify 기준을 써야 결정 Map이 실제 처리 대상과 1:1로 맞는다.
|
|
281
|
-
// common도 타입별과 동일하게 스캔한다 (
|
|
264
|
+
// common도 타입별과 동일하게 스캔한다 (이전에는 common 충돌이 질문조차 되지 않았다).
|
|
282
265
|
// 반환: { conflicts: [{filename,type}], removed: [{filename,type}] }
|
|
283
266
|
// conflicts — 양쪽이 다 바뀐 진짜 충돌. upstreamOnly/localOnly는 자동 처리되므로 여기 없다.
|
|
284
267
|
// removed — 우리가 깔았는데 사용자가 지운 파일. 되살리기 전에 물어봐야 한다.
|
|
285
268
|
export function surveyWorkflows(context, payloadRoot, targetRoot = ".") {
|
|
286
|
-
const { types = [], paths = new Map(),
|
|
269
|
+
const { types = [], paths = new Map(), repoName = "", resolvers = {}, flutterStore = null } = context;
|
|
287
270
|
const workflowsDir = join(targetRoot, PATHS.workflowsDir);
|
|
288
271
|
const projectTypesDir = join(payloadRoot, PAYLOAD.workflowsDir);
|
|
289
272
|
const deployStyle = context.deployStyle || DEFAULT_DEPLOY_STYLE;
|
|
@@ -313,7 +296,7 @@ export function surveyWorkflows(context, payloadRoot, targetRoot = ".") {
|
|
|
313
296
|
collect(typeDir, envOpts, type, () => false, buildTypeRootFilter(type, deployStyle, flutterStore));
|
|
314
297
|
}
|
|
315
298
|
const serverDeployDir = join(typeDir, "server-deploy");
|
|
316
|
-
if (exists(serverDeployDir) &&
|
|
299
|
+
if (exists(serverDeployDir) && deployStyle !== NO_DEPLOY_STYLE) {
|
|
317
300
|
collect(serverDeployDir, envOpts, type, () => false, keepDeploy);
|
|
318
301
|
}
|
|
319
302
|
}
|
|
@@ -341,7 +324,7 @@ export async function copyWorkflowsInteractive(context, payloadRoot, targetRoot
|
|
|
341
324
|
}
|
|
342
325
|
|
|
343
326
|
function copyWorkflowsForType(type, projectTypesDir, workflowsDir, ctx, counters) {
|
|
344
|
-
const {
|
|
327
|
+
const { deployStyle = "", flutterStore = null, envOptsFor, collectAsks = null, dirCtx } = ctx;
|
|
345
328
|
const keepDeploy = deployFilter(deployStyle);
|
|
346
329
|
const keepTypeRoot = buildTypeRootFilter(type, deployStyle, flutterStore);
|
|
347
330
|
const { srcText, baselineTargets } = dirCtx;
|
|
@@ -361,29 +344,16 @@ function copyWorkflowsForType(type, projectTypesDir, workflowsDir, ctx, counters
|
|
|
361
344
|
|
|
362
345
|
// server-deploy
|
|
363
346
|
const serverDeployDir = join(typeDir, "server-deploy");
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
} else {
|
|
368
|
-
const c = processDir(serverDeployDir, workflowsDir, envOpts, dirCtx, counters, keepDeploy);
|
|
369
|
-
untouched.push(...c.unchanged, ...c.localOnly);
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
// nexus (opt-in) — 라이브러리 publish 계열(Nexus + GitHub Packages).
|
|
374
|
-
// 다른 폴더와 같은 processDir을 쓴다. 종전에는 이 경로만 별도 로직으로 "존재하면 무조건
|
|
375
|
-
// .bak 후 교체"였는데, 그러면 사용자가 손댄 publish 워크플로우가 재실행마다 묻지도 않고
|
|
376
|
-
// 밀린다. baseline 3-way·충돌 3지선을 다른 워크플로우와 동일하게 적용한다.
|
|
377
|
-
const nexusDir = join(typeDir, "nexus");
|
|
378
|
-
if (exists(nexusDir) && includeNexus) {
|
|
379
|
-
const c = processDir(nexusDir, workflowsDir, envOpts, dirCtx, counters);
|
|
347
|
+
// "배포 안 함"이면 폴더째 제외 (복사 안 함)
|
|
348
|
+
if (exists(serverDeployDir) && deployStyle !== NO_DEPLOY_STYLE) {
|
|
349
|
+
const c = processDir(serverDeployDir, workflowsDir, envOpts, dirCtx, counters, keepDeploy);
|
|
380
350
|
untouched.push(...c.unchanged, ...c.localOnly);
|
|
381
351
|
}
|
|
382
352
|
|
|
383
353
|
// env 치환 — 이 타입의 원본 디렉토리들에서 복사돼 존재하고, 손대지 않기로 한 것이 아닌 파일만
|
|
384
|
-
for (const srcDir of [typeDir, serverDeployDir
|
|
354
|
+
for (const srcDir of [typeDir, serverDeployDir]) {
|
|
385
355
|
if (!exists(srcDir)) continue;
|
|
386
|
-
if (srcDir === serverDeployDir &&
|
|
356
|
+
if (srcDir === serverDeployDir && deployStyle === NO_DEPLOY_STYLE) continue; // 폴더째 제외
|
|
387
357
|
for (const filename of listYamlFiles(srcDir)) {
|
|
388
358
|
const target = join(workflowsDir, filename);
|
|
389
359
|
if (srcDir === serverDeployDir && !keepDeploy(filename)) continue; // 안 고른 배포 방식
|
|
@@ -395,18 +365,18 @@ function copyWorkflowsForType(type, projectTypesDir, workflowsDir, ctx, counters
|
|
|
395
365
|
}
|
|
396
366
|
}
|
|
397
367
|
|
|
398
|
-
// 전체 워크플로우 분류(common + 타입별 + server-deploy
|
|
368
|
+
// 전체 워크플로우 분류(common + 타입별 + server-deploy) — status/dry-run 공용.
|
|
399
369
|
// changed뿐 아니라 newFiles/unchanged까지 전부 반환한다는 점이 listWorkflowConflicts(changed만
|
|
400
370
|
// 반환)와 다르다(읽기 전용 — 실제로 아무 파일도 쓰지 않는다).
|
|
401
371
|
export function planWorkflows(context, payloadRoot, targetRoot = ".") {
|
|
402
|
-
const { types = [], paths = new Map(),
|
|
372
|
+
const { types = [], paths = new Map(), repoName = "", resolvers = {}, flutterStore = null } = context;
|
|
403
373
|
const workflowsDir = join(targetRoot, PATHS.workflowsDir);
|
|
404
374
|
const projectTypesDir = join(payloadRoot, PAYLOAD.workflowsDir);
|
|
405
375
|
const deployStyle = context.deployStyle || DEFAULT_DEPLOY_STYLE;
|
|
406
376
|
const srcText = makeSrcText(context.branches || null, deployStyle);
|
|
407
377
|
const baseline = readBaseline(targetRoot);
|
|
408
378
|
const branchMode = context.branches?.mode || "pr-flow";
|
|
409
|
-
// upstreamOnly/localOnly/removed는 baseline이 있을 때만
|
|
379
|
+
// upstreamOnly/localOnly/removed는 baseline이 있을 때만 채워진다.
|
|
410
380
|
const plan = { newFiles: [], unchanged: [], changed: [], upstreamOnly: [], localOnly: [], removed: [] };
|
|
411
381
|
const BUCKETS = ["newFiles", "unchanged", "changed", "upstreamOnly", "localOnly", "removed"];
|
|
412
382
|
|
|
@@ -426,16 +396,6 @@ export function planWorkflows(context, payloadRoot, targetRoot = ".") {
|
|
|
426
396
|
branchMode === "trunk-based" ? TRUNK_BASED_EXCLUDED : null);
|
|
427
397
|
}
|
|
428
398
|
|
|
429
|
-
// secret-backup은 copyWorkflows처럼 신규 파일만 대상(기존 파일은 절대 덮어쓰지 않는 규약) —
|
|
430
|
-
// classify()의 changed 판정과 무관하게, 여기서도 존재 여부만으로 new/unchanged를 가른다.
|
|
431
|
-
const secretDir = join(commonDir, "secret-backup");
|
|
432
|
-
if (exists(secretDir) && includeSecretBackup) {
|
|
433
|
-
for (const filename of listYamlFiles(secretDir)) {
|
|
434
|
-
const dst = join(workflowsDir, filename);
|
|
435
|
-
plan[existsSync(dst) ? "unchanged" : "newFiles"].push({ filename, type: "common" });
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
|
|
439
399
|
for (const type of types) {
|
|
440
400
|
const envOpts = { type, projectPath: paths.get(type) || ".", repoName, resolvers };
|
|
441
401
|
const typeDir = join(projectTypesDir, type);
|
|
@@ -444,14 +404,9 @@ export function planWorkflows(context, payloadRoot, targetRoot = ".") {
|
|
|
444
404
|
}
|
|
445
405
|
|
|
446
406
|
const serverDeployDir = join(typeDir, "server-deploy");
|
|
447
|
-
if (exists(serverDeployDir) &&
|
|
407
|
+
if (exists(serverDeployDir) && deployStyle !== NO_DEPLOY_STYLE) {
|
|
448
408
|
merge(classify(serverDeployDir, workflowsDir, envOpts, srcText, baseline, deployFilter(deployStyle)), type);
|
|
449
409
|
}
|
|
450
|
-
|
|
451
|
-
const nexusDir = join(typeDir, "nexus");
|
|
452
|
-
if (exists(nexusDir) && includeNexus) {
|
|
453
|
-
merge(classify(nexusDir, workflowsDir, envOpts, srcText, baseline), type);
|
|
454
|
-
}
|
|
455
410
|
}
|
|
456
411
|
|
|
457
412
|
return plan;
|
package/src/core/deploy-style.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// 배포 방식
|
|
1
|
+
// 배포 방식 — 서버 배포 CD 워크플로우는 서로 대체재다.
|
|
2
2
|
// Nginx 무중단과 Traefik 무중단을 동시에 쓰는 경우는 없으므로 하나만 설치한다.
|
|
3
3
|
// 고른 것은 push 트리거까지 켜서 설치한다 — 설치했는데 안 도는 상태를 만들지 않는다.
|
|
4
4
|
import { join } from "node:path";
|
|
@@ -17,7 +17,7 @@ export const DEFAULT_DEPLOY_STYLE = "simple";
|
|
|
17
17
|
// 서버 배포 자체를 하지 않는 프로젝트(프론트엔드 전용, 라이브러리 등)를 위한 값.
|
|
18
18
|
// DEPLOY_STYLES에는 넣지 않는다 — isDeployWorkflow/suffixOf가 이 배열을 순회하는데, 빈 접미사를
|
|
19
19
|
// 돌려주면 endsWith("")가 항상 참이라 모든 파일이 CD로 오판되어 cleanupOtherDeployWorkflows가
|
|
20
|
-
// 설치된 워크플로우 전체를 지우는 회귀가
|
|
20
|
+
// 설치된 워크플로우 전체를 지우는 회귀가 생긴다.
|
|
21
21
|
export const NO_DEPLOY_STYLE = "none";
|
|
22
22
|
|
|
23
23
|
export const isDeployStyle = (v) => v === NO_DEPLOY_STYLE || DEPLOY_STYLES.some((s) => s.value === v);
|
package/src/core/detect-fs.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// 실 파일시스템 프로젝트 감지 (.sh detect_* 실행부 등가).
|
|
2
|
-
//
|
|
2
|
+
// detect.js 순수 함수를 fs/git으로 구동한다.
|
|
3
3
|
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
4
4
|
import { join, basename } from "node:path";
|
|
5
5
|
import { execFileSync } from "node:child_process";
|
|
@@ -27,8 +27,8 @@ export function detectTypes(root) {
|
|
|
27
27
|
return detectTypesFromMarkers({ has: hasFile(root), read: readFile(root) });
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
// 버전 감지 — .sh detect_version 순서. jq는 package.json 파싱에 쓰인 적이 없어 게이트를
|
|
31
|
-
// hint: 폴백 경고에 붙일 해결 방법 안내 (대화형/CLI가 다르다
|
|
30
|
+
// 버전 감지 — .sh detect_version 순서. jq는 package.json 파싱에 쓰인 적이 없어 게이트를 제거했다.
|
|
31
|
+
// hint: 폴백 경고에 붙일 해결 방법 안내 (대화형/CLI가 다르다).
|
|
32
32
|
export function detectVersion(root, { warn = (m) => console.error(m), hint } = {}) {
|
|
33
33
|
const read = readFile(root);
|
|
34
34
|
const readJson = (rel) => { const c = read(rel); try { return c ? JSON.parse(c) : null; } catch { return null; } };
|
|
@@ -36,12 +36,12 @@ export function detectVersion(root, { warn = (m) => console.error(m), hint } = {
|
|
|
36
36
|
return detectVersionFromFiles({ read, readJson, gitTag, warn, hint });
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
// 타입별 실제 마커 파일
|
|
39
|
+
// 타입별 실제 마커 파일 — 감지 로그·설치 로그가 같은 근거 파일을 인용하도록.
|
|
40
40
|
export function detectMarkers(root, types = []) {
|
|
41
41
|
return resolveMarkers(types, hasFile(root));
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
// 빌드 JDK 감지
|
|
44
|
+
// 빌드 JDK 감지 — 배포 워크플로우 JAVA_VERSION 기본값에 실측값을 쓰기 위해.
|
|
45
45
|
// base: 모노레포에서 spring 프로젝트 루트 (레포 루트 기준 상대경로).
|
|
46
46
|
export function detectJdk(root, base = ".") {
|
|
47
47
|
const rel = base && base !== "." ? (r) => `${base}/${r}` : (r) => r;
|
|
@@ -49,7 +49,7 @@ export function detectJdk(root, base = ".") {
|
|
|
49
49
|
return detectJdkFromFiles({ read: (r) => read(rel(r)) });
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
// 빌드 번호 감지 — 신규 통합 시 pubspec.yaml/build.gradle/app.json에서 실제 빌드 번호를
|
|
52
|
+
// 빌드 번호 감지 — 신규 통합 시 pubspec.yaml/build.gradle/app.json에서 실제 빌드 번호를 읽는다.
|
|
53
53
|
export function detectBuildNumber(root, { types = [], warn = (m) => console.error(m) } = {}) {
|
|
54
54
|
const read = readFile(root);
|
|
55
55
|
const readJson = (rel) => { const c = read(rel); try { return c ? JSON.parse(c) : null; } catch { return null; } };
|
|
@@ -80,7 +80,7 @@ export function detectRepoName(root) {
|
|
|
80
80
|
// find {base} -path "*/src/main/resources/application*.yml" | head -1 의 fs 재귀 구현.
|
|
81
81
|
// 반환: root 기준 상대경로 (예: "server/src/main/resources/application.yml") 또는 "".
|
|
82
82
|
//
|
|
83
|
-
// .yaml도
|
|
83
|
+
// .yaml도 인정한다. Spring은 .yml/.yaml을 모두 공식 지원하는데 종전 정규식이
|
|
84
84
|
// .yml만 봐서, application.yaml을 쓰는 프로젝트는 이 값이 빈 문자열이 되고 그 결과
|
|
85
85
|
// __APPLICATION_YML_DIR__ 가 치환되지 않은 채 설치됐다.
|
|
86
86
|
//
|
|
@@ -116,15 +116,15 @@ export function findSpringAppYml(root, base = ".") {
|
|
|
116
116
|
|
|
117
117
|
// 실 resolver 세트 생성 (.sh resolve_token 4종 등가) — index/interactive 공용.
|
|
118
118
|
// paths: Map<type, path> (모노레포 경로).
|
|
119
|
-
// flutterOptions: resolveFlutterOptions 결과 또는 같은 필드를 가진 context
|
|
119
|
+
// flutterOptions: resolveFlutterOptions 결과 또는 같은 필드를 가진 context. null이면 Flutter 토큰이
|
|
120
120
|
// 빈 값이라 템플릿 기본값(dart-define, store_only)이 그대로 남는다.
|
|
121
121
|
export function makeResolvers(root, repoName, paths, flutterOptions = null) {
|
|
122
122
|
const springBase = (t) => paths.get(t || "spring") || paths.get("spring") || ".";
|
|
123
123
|
return {
|
|
124
124
|
repo: () => repoName,
|
|
125
|
-
// 빌드 JDK
|
|
125
|
+
// 빌드 JDK — 배포 워크플로우 JAVA_VERSION의 기본값. 프로젝트 툴체인을 실측한다.
|
|
126
126
|
// ⚠️ 빈 문자열을 돌려주면 setEnvLine이 그 줄을 건너뛰어 __JAVA_VERSION__이 그대로 남는다
|
|
127
|
-
// (
|
|
127
|
+
// (같은 실패 형태). 감지 실패 시 반드시 종전 기본값 21로 폴백한다.
|
|
128
128
|
jdk: (t) => detectJdk(root, springBase(t)) || "21",
|
|
129
129
|
"spring-app-yml-dir": (t) => {
|
|
130
130
|
const f = findSpringAppYml(root, springBase(t));
|
|
@@ -132,7 +132,7 @@ export function makeResolvers(root, repoName, paths, flutterOptions = null) {
|
|
|
132
132
|
},
|
|
133
133
|
"spring-app-yml-path": (t) => findSpringAppYml(root, springBase(t)) || "",
|
|
134
134
|
"flutter-root": () => paths.get("flutter") || ".",
|
|
135
|
-
// CI changes job의 경로 필터
|
|
135
|
+
// CI changes job의 경로 필터 — 타입별 프로젝트 루트. 단일 레포·common은 "."(항상 변경됨으로 판정).
|
|
136
136
|
"project-path": (t) => paths.get(t) || ".",
|
|
137
137
|
// 빈 문자열이면 setEnvLine/setFallbackLine이 줄을 건너뛰어 템플릿 기본값이 남는다.
|
|
138
138
|
"flutter-env-mode": () => flutterOptions?.envMode || "",
|
package/src/core/detect.js
CHANGED
|
@@ -36,9 +36,9 @@ export function detectTypesFromMarkers({ has, read }) {
|
|
|
36
36
|
const VERSION_RE = /^\d+\.\d+\.\d+$/;
|
|
37
37
|
|
|
38
38
|
// 버전 감지 (동작명세 §3.3) — 순서대로 첫 성공. read(relpath)=>string|null 주입.
|
|
39
|
-
// package.json은 이미 Node JSON.parse로 파싱을 마친 값이므로 jq 설치 여부와 무관하게 항상
|
|
39
|
+
// package.json은 이미 Node JSON.parse로 파싱을 마친 값이므로 jq 설치 여부와 무관하게 항상 사용한다.
|
|
40
40
|
// hint: 폴백 경고 뒤에 붙일 "그럼 어떻게 고치나" 한 줄. 대화형과 CLI가 서로 다른 방법을
|
|
41
|
-
// 안내해야 하므로
|
|
41
|
+
// 안내해야 하므로 호출부가 정한다. 미지정 시 CLI 문구를 쓴다.
|
|
42
42
|
export function detectVersionFromFiles({ read, readJson, gitTag, warn, hint }) {
|
|
43
43
|
const pkg = readJson?.("package.json");
|
|
44
44
|
if (pkg?.version && VERSION_RE.test(pkg.version)) return pkg.version;
|
|
@@ -52,7 +52,7 @@ export function detectVersionFromFiles({ read, readJson, gitTag, warn, hint }) {
|
|
|
52
52
|
let v;
|
|
53
53
|
const gradleRe = /version\s*=\s*["']?(\d+\.\d+\.\d+)/;
|
|
54
54
|
// Groovy DSL과 Kotlin DSL은 같은 문법(`version = "x.y.z"`)이라 정규식을 공유한다.
|
|
55
|
-
// .kts를 빼먹으면 Kotlin DSL Spring 프로젝트가 전부 0.0.1로
|
|
55
|
+
// .kts를 빼먹으면 Kotlin DSL Spring 프로젝트가 전부 0.0.1로 초기화된다.
|
|
56
56
|
if ((v = grab(read("build.gradle"), gradleRe))) return v;
|
|
57
57
|
if ((v = grab(read("build.gradle.kts"), gradleRe))) return v;
|
|
58
58
|
if ((v = versionFromPom(read("pom.xml")))) return v;
|
|
@@ -64,7 +64,7 @@ export function detectVersionFromFiles({ read, readJson, gitTag, warn, hint }) {
|
|
|
64
64
|
return "0.0.1";
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
// Maven pom.xml의 프로젝트
|
|
67
|
+
// Maven pom.xml의 프로젝트 버전. <parent> 블록 안의 버전은 스프링 부트 BOM 버전이라
|
|
68
68
|
// 프로젝트 버전이 아니다 — 그 구간을 지운 뒤 첫 <version>을 읽는다.
|
|
69
69
|
export function versionFromPom(content) {
|
|
70
70
|
if (!content) return null;
|
|
@@ -81,7 +81,7 @@ export function extraMarkers(type) {
|
|
|
81
81
|
return { python: ["setup.py", "requirements.txt"], spring: ["build.gradle.kts", "pom.xml"] }[type] || [];
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
// 그 타입을 감지하는 데 실제로 쓰인
|
|
84
|
+
// 그 타입을 감지하는 데 실제로 쓰인 파일. markerForType은 타입당 대표 파일 하나를
|
|
85
85
|
// 고정 반환하므로, build.gradle.kts만 있는 레포에서도 "build.gradle 발견"이라고 출력돼
|
|
86
86
|
// 같은 설치 로그 안에서 경로 확정 화면과 파일명이 어긋났다. has()로 실재하는 것을 고른다.
|
|
87
87
|
// 실재하는 후보가 없으면(감지 전 화면 등) 대표 파일을 쓴다.
|
|
@@ -90,7 +90,7 @@ export function resolveMarker(type, has) {
|
|
|
90
90
|
return candidates.find(has) ?? candidates[0];
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
// 빌드 JDK 감지
|
|
93
|
+
// 빌드 JDK 감지 — 배포 워크플로우의 JAVA_VERSION 기본값이 21로 고정돼 있어
|
|
94
94
|
// toolchain이 다른 프로젝트(예: 25)는 그대로 Enter를 누르면 러너 JDK와 어긋나 빌드가 깨진다.
|
|
95
95
|
// 빌드 번호를 프로젝트 파일에서 읽는 detectBuildNumberFromFiles와 같은 방식으로 실측한다.
|
|
96
96
|
// 반환: "21" 같은 메이저 버전 문자열, 못 찾으면 null.
|
|
@@ -129,7 +129,7 @@ export function resolveMarkers(types = [], has) {
|
|
|
129
129
|
return out;
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
// 빌드 번호 감지
|
|
132
|
+
// 빌드 번호 감지 — 신규 통합 시 pubspec.yaml/build.gradle/app.json에 이미 기록된
|
|
133
133
|
// 빌드 번호를 읽어 version_code가 항상 1로 초기화되는 걸 막는다. types 배열에서 먼저 매칭되는
|
|
134
134
|
// 첫 타입만 사용한다(다른 감지 로직의 types[0]=primary 관례와 동일). read(rel)=>string|null,
|
|
135
135
|
// readJson(rel)=>object|null 로 주입.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Flutter 옵션
|
|
1
|
+
// Flutter 옵션 — 환경변수 방식·스토어 배포 대상·배포 모드의 단일 진실.
|
|
2
2
|
// 스토어 배포 대상은 deploy-style.js(배포 방식)와 같은 구조 — 값 목록, 파일 필터, 선택 해제 정리 — 를 따른다.
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { existsSync, readFileSync, renameSync, rmSync } from "node:fs";
|
package/src/core/logger.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// 쓰는 구조는 예외가 나면 아무것도 남기지 못한다(구 install-log.js가 그랬다).
|
|
5
5
|
//
|
|
6
6
|
// 왜 로컬 전용인가: 상세도를 제약하지 않기 위해서다. 로그 디렉토리에 .gitignore를 직접
|
|
7
|
-
// 두어 그 폴더만 추적에서 뺀다 — 루트 .gitignore는 건드리지
|
|
7
|
+
// 두어 그 폴더만 추적에서 뺀다 — 루트 .gitignore는 건드리지 않는다.
|
|
8
8
|
import { appendFileSync, existsSync, mkdirSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
|
|
@@ -166,7 +166,7 @@ export async function resolveProjectPaths({
|
|
|
166
166
|
const candidates = findTypePathCandidates(root, t);
|
|
167
167
|
let chosen = "";
|
|
168
168
|
|
|
169
|
-
// ── ⑤-a 비대화형 (--force 또는 TTY 없음, .sh L1476~1489 —
|
|
169
|
+
// ── ⑤-a 비대화형 (--force 또는 TTY 없음, .sh L1476~1489 — root 폴백 의도적 불포함) ──
|
|
170
170
|
if (force || !tty) {
|
|
171
171
|
if (existing) {
|
|
172
172
|
chosen = existing;
|
package/src/core/removal-plan.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// 원칙: (a) 현재 payload에 존재하는 파일명과 정확히 일치하는 것, (b) 설치된 워크플로우 파일 중
|
|
5
5
|
// 마법사 관리 마커(MANAGED_WORKFLOW_MARKER)로 시작하는 것 — 이 둘의 합집합을 제거 대상으로 삼는다.
|
|
6
6
|
// (a)만으로는 과거 버전에서 설치된 뒤 이후 릴리스에서 payload 파일명이 바뀌거나 삭제된 파일을
|
|
7
|
-
// 인식하지 못했다 (
|
|
7
|
+
// 인식하지 못했다 (원래 버그). 반대로 (b)만으로는 이 수정 이전에 설치되어 마커가
|
|
8
8
|
// 없는(그러나 파일명은 여전히 현재 payload와 일치하는) 기존 설치 전체를 인식하지 못하는 회귀가
|
|
9
9
|
// 생긴다 — 그래서 두 방식을 합집합으로 병행한다. 마커는 이 수정 이후 배포되는 payload 템플릿부터
|
|
10
10
|
// 포함되므로, (b) 경로가 실제로 새로 잡아내는 것은 "이름이 바뀌거나 삭제된, 마커가 있는" 파일뿐이다.
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// (version.yml은 사용자 버전 데이터 — 제거 대상이 아니라 산출물이다).
|
|
13
13
|
//
|
|
14
14
|
// 이 파일은 원래 src/commands/revert.js였다. revert 모드는 uninstall의 부분집합이라 제거됐고
|
|
15
|
-
|
|
15
|
+
//, 판별 로직만 남아 commands가 아닌 core로 옮겨졌다.
|
|
16
16
|
import { join } from "node:path";
|
|
17
17
|
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
18
18
|
import { PATHS, PAYLOAD } from "./paths.js";
|
|
@@ -34,7 +34,7 @@ function payloadWorkflowNames(payloadRoot) {
|
|
|
34
34
|
|
|
35
35
|
// 설치된 워크플로우 디렉토리(평면 구조)에서 관리 마커로 시작하는 파일명 집합.
|
|
36
36
|
// .bak/.template.yaml 백업본도 원본 텍스트를 그대로 복사한 것이라 마커를 그대로 갖고 있어
|
|
37
|
-
// 확장자나 파일명 유추 없이 동일한 방식으로
|
|
37
|
+
// 확장자나 파일명 유추 없이 동일한 방식으로 인식된다.
|
|
38
38
|
function markedWorkflowNames(wfDir) {
|
|
39
39
|
const names = new Set();
|
|
40
40
|
if (!existsSync(wfDir)) return names;
|
|
@@ -60,7 +60,7 @@ export function planRemoval(payloadRoot, targetRoot = ".") {
|
|
|
60
60
|
if (existsSync(join(wfDir, templateName))) removedWf.add(templateName);
|
|
61
61
|
if (existsSync(p + ".bak")) removedWf.add(name + ".bak");
|
|
62
62
|
}
|
|
63
|
-
// (b) 관리 마커로 시작하는 것 — payload에서 이름이 바뀌거나 삭제된 파일도
|
|
63
|
+
// (b) 관리 마커로 시작하는 것 — payload에서 이름이 바뀌거나 삭제된 파일도 인식.
|
|
64
64
|
for (const name of markedWorkflowNames(wfDir)) removedWf.add(name);
|
|
65
65
|
}
|
|
66
66
|
for (const s of ["version_manager.py", "changelog_manager.py", "truncate_release_notes.py", "issue_helper.py"]) {
|
package/src/core/verify.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// 설치 후 검증
|
|
1
|
+
// 설치 후 검증 — 설치된 워크플로우를 다시 읽어 "이대로 돌아가는가"를 본다.
|
|
2
2
|
//
|
|
3
3
|
// 왜 설치 전이 아니라 후인가: 치환은 파일 단위로 흩어져 일어나고 auto 토큰은 resolver 결과에
|
|
4
4
|
// 의존한다. 최종 디스크 내용을 보는 것이 실제로 배포될 것과 같은 것을 보는 유일한 방법이다.
|
|
@@ -15,7 +15,7 @@ const PLACEHOLDER_RE = /__[A-Z][A-Z0-9_]*__/g;
|
|
|
15
15
|
// 쓰지도 않는 Secret을 "등록하세요"라고 안내하게 된다.
|
|
16
16
|
const isCommented = (line) => /^\s*#/.test(line);
|
|
17
17
|
|
|
18
|
-
// 미치환 플레이스홀더
|
|
18
|
+
// 미치환 플레이스홀더 스캔.
|
|
19
19
|
// 종전에는 auto 토큰 계산이 실패해도(예: application.yaml을 못 찾아 경로가 빈 문자열) 그 줄을
|
|
20
20
|
// 건드리지 않고 넘어가, __APPLICATION_YML_DIR__ 이 그대로 남은 워크플로우가 "설치 성공"으로
|
|
21
21
|
// 끝났다. 배포 시점에야 그 이름의 디렉토리가 만들어지며 문제가 드러난다.
|
|
@@ -48,7 +48,7 @@ const AUTO_SECRETS = new Set(["GITHUB_TOKEN"]);
|
|
|
48
48
|
export const OPTIONAL_SECRETS = new Set(["AI_API_KEY", "WORKFLOW_PAT"]);
|
|
49
49
|
const SECRET_RE = /secrets\.([A-Z][A-Z0-9_]*)/g;
|
|
50
50
|
|
|
51
|
-
// 설치된 워크플로우가 요구하는 GitHub Secret
|
|
51
|
+
// 설치된 워크플로우가 요구하는 GitHub Secret 목록.
|
|
52
52
|
// 완료 화면이 WORKFLOW_PAT와 권한만 안내하는 바람에, 배포 워크플로우가 실제로 필요로 하는
|
|
53
53
|
// SERVER_HOST·SSH_KEY 같은 값이 하나도 안내되지 않았다. 설치 직후 상태로는 배포가 돌지 않는데
|
|
54
54
|
// 그 사실이 어디에도 드러나지 않는다.
|
package/src/core/version-yml.js
CHANGED
|
@@ -4,12 +4,12 @@ import {
|
|
|
4
4
|
ENV_MODES, DEPLOY_MODES, DEFAULT_ENV_MODE, DEFAULT_DEPLOY_MODE, STORE_PLATFORMS, formatStoreList,
|
|
5
5
|
} from "./flutter-options.js";
|
|
6
6
|
|
|
7
|
-
// version.yml 파싱·생성 (.sh create_version_yml 등가, 전체 재생성 전략
|
|
7
|
+
// version.yml 파싱·생성 (.sh create_version_yml 등가, 전체 재생성 전략).
|
|
8
8
|
// ⚠️ YAML 재직렬화 금지 — 주석이 데이터.
|
|
9
9
|
// 레이아웃 단일 진실 = payload/version.yml.template (호출부가 templateText로 주입).
|
|
10
10
|
|
|
11
11
|
// version.yml.template이 아는 최상위 키 — 이 밖의 최상위 키는 사용자가 직접 추가한 것으로 간주한다.
|
|
12
|
-
// "project_type"(단수)은 더 이상 렌더하지 않는 레거시 키지만 이 집합에는
|
|
12
|
+
// "project_type"(단수)은 더 이상 렌더하지 않는 레거시 키지만 이 집합에는 남겨둔다:
|
|
13
13
|
// 빼면 기존 파일의 단수 줄이 "사용자가 추가한 필드"로 오인돼 재생성 때 되살아난다. 아는 키로
|
|
14
14
|
// 둬야 재통합 시 흡수되어 사라진다.
|
|
15
15
|
const KNOWN_TOP_LEVEL_KEYS = new Set([
|
|
@@ -17,14 +17,14 @@ const KNOWN_TOP_LEVEL_KEYS = new Set([
|
|
|
17
17
|
]);
|
|
18
18
|
|
|
19
19
|
// 최상위 레벨의 알려지지 않은 필드(사용자가 직접 추가한 임의 필드)를 원본 그대로 보존한다
|
|
20
|
-
|
|
20
|
+
//. 각 알려지지 않은 최상위 키부터 다음 최상위 키 직전까지를 통째로 한 블록으로
|
|
21
21
|
// 캡처한다(중첩 구조가 있어도 유효한 YAML로 남기기 위함). metadata/project_paths/deploy처럼
|
|
22
|
-
// 이 모듈이 이미 아는 블록 "내부"의 알려지지 않은 하위 키는 대상이 아니다(범위 밖
|
|
22
|
+
// 이 모듈이 이미 아는 블록 "내부"의 알려지지 않은 하위 키는 대상이 아니다(범위 밖).
|
|
23
23
|
export function parseExtraTopLevel(content) {
|
|
24
24
|
const blocks = [];
|
|
25
25
|
let current = null;
|
|
26
26
|
for (const line of String(content || "").split("\n")) {
|
|
27
|
-
// 최상위 키는 하이픈을 포함할 수 있다(YAML 관례)
|
|
27
|
+
// 최상위 키는 하이픈을 포함할 수 있다(YAML 관례).
|
|
28
28
|
const m = line.match(/^([a-zA-Z_][a-zA-Z0-9_-]*):/);
|
|
29
29
|
if (m) {
|
|
30
30
|
if (current) blocks.push(current.join("\n"));
|
|
@@ -37,20 +37,19 @@ export function parseExtraTopLevel(content) {
|
|
|
37
37
|
return blocks;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
// Flutter 옵션 키
|
|
40
|
+
// Flutter 옵션 키 → 반환 필드. 값은 원문 문자열로 돌려주고, 유효성 판정은 resolveFlutterOptions 몫이다.
|
|
41
41
|
const FLUTTER_OPTION_KEYS = {
|
|
42
42
|
env_mode: "envMode", flutter_store: "flutterStore",
|
|
43
43
|
android_deploy_mode: "androidDeployMode", ios_deploy_mode: "iosDeployMode",
|
|
44
44
|
};
|
|
45
45
|
|
|
46
46
|
// metadata.template.options 상태머신 파싱 (.sh read_template_options L2361~2416 등가).
|
|
47
|
-
// 반환: {
|
|
47
|
+
// 반환: { semverAuto: bool|null, copilotAi: bool|null, deployStyle: string|null,
|
|
48
48
|
// envMode/flutterStore/androidDeployMode/iosDeployMode: string|null } — null=미기재.
|
|
49
49
|
// 구 synology·coderabbit 키 등 다른 키는 어느 분기에도 안 걸려 자연히 무시된다(파싱 에러 없음).
|
|
50
|
-
// (options-ask.js가 이 함수를 import한다 — 순환 방지 위해 여기(version-yml)에 정의.)
|
|
51
50
|
export function parseTemplateOptions(content) {
|
|
52
51
|
const out = {
|
|
53
|
-
|
|
52
|
+
semverAuto: null, copilotAi: null, deployStyle: null,
|
|
54
53
|
envMode: null, flutterStore: null, androidDeployMode: null, iosDeployMode: null,
|
|
55
54
|
};
|
|
56
55
|
// 값 정규화: 따옴표 제거 + 트림 (.sh tr -d '"' | tr -d "'" | xargs 등가)
|
|
@@ -63,21 +62,7 @@ export function parseTemplateOptions(content) {
|
|
|
63
62
|
if (/^\s*template:/.test(line)) { inTemplate = true; continue; }
|
|
64
63
|
if (inTemplate && /^\s+options:/.test(line)) { inOptions = true; continue; }
|
|
65
64
|
if (inTemplate && inOptions) {
|
|
66
|
-
let m = line.match(/^\s+
|
|
67
|
-
if (m) {
|
|
68
|
-
const v = strip(m[1]);
|
|
69
|
-
if (v === "true") out.nexus = true;
|
|
70
|
-
if (v === "false") out.nexus = false;
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
m = line.match(/^\s+secret_backup:\s*(.+)/);
|
|
74
|
-
if (m) {
|
|
75
|
-
const v = strip(m[1]);
|
|
76
|
-
if (v === "true") out.secretBackup = true;
|
|
77
|
-
if (v === "false") out.secretBackup = false;
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
m = line.match(/^\s+deploy_style:\s*(.+)/);
|
|
65
|
+
let m = line.match(/^\s+deploy_style:\s*(.+)/);
|
|
81
66
|
if (m) { const v = strip(m[1]); if (v) out.deployStyle = v; continue; }
|
|
82
67
|
m = line.match(/^\s+(env_mode|flutter_store|android_deploy_mode|ios_deploy_mode):\s*(.+)/);
|
|
83
68
|
if (m) { const v = strip(m[2]); if (v) out[FLUTTER_OPTION_KEYS[m[1]]] = v; continue; }
|
|
@@ -146,7 +131,7 @@ export function parseExisting(content) {
|
|
|
146
131
|
if (/^\S/.test(l)) break;
|
|
147
132
|
}
|
|
148
133
|
}
|
|
149
|
-
// 선택 워크플로우 옵션 (metadata.template.options
|
|
134
|
+
// 선택 워크플로우 옵션 (metadata.template.options)
|
|
150
135
|
const options = parseTemplateOptions(text);
|
|
151
136
|
// metadata.template.branches — main/develop/mode (업데이트 모드 재질문 생략용)
|
|
152
137
|
const branches = parseTemplateBranches(text);
|
|
@@ -194,13 +179,13 @@ function buildFlutterOptionsBlock({ envMode, stores, androidDeployMode, iosDeplo
|
|
|
194
179
|
// version.yml 전체 생성 — payload/version.yml.template 렌더링.
|
|
195
180
|
// opts: { templateText, version, types:[], paths:Map, pathMarkers?:Map,
|
|
196
181
|
// branch, branches?, versionCode, now, today, templateOptions?, deployValues?,
|
|
197
|
-
// extraTopLevel?:string[], ← 기존 version.yml의 알려지지 않은 최상위 필드 보존
|
|
198
|
-
// flutterOptions?:{ envMode, stores, androidDeployMode, iosDeployMode } } ← Flutter 타입일 때만 렌더
|
|
182
|
+
// extraTopLevel?:string[], ← 기존 version.yml의 알려지지 않은 최상위 필드 보존
|
|
183
|
+
// flutterOptions?:{ envMode, stores, androidDeployMode, iosDeployMode } } ← Flutter 타입일 때만 렌더
|
|
199
184
|
// templateText = payload/version.yml.template 원문 (readVersionYmlTemplate — 필수)
|
|
200
185
|
// now = "YYYY-MM-DD HH:MM:SS" (UTC) — 결정성 위해 주입 / today = "YYYY-MM-DD"
|
|
201
186
|
// branches = { main, develop, mode } (resolveBranchConfig 결과. 없으면 branch 기반 기본값)
|
|
202
187
|
// pathMarkers = Map<type, markerFilename> (project_paths 주석용)
|
|
203
|
-
// templateOptions = { templateVersion,
|
|
188
|
+
// templateOptions = { templateVersion, optionsDate }
|
|
204
189
|
export function buildVersionYml({
|
|
205
190
|
templateText, version, types = [], paths = new Map(), pathMarkers = new Map(),
|
|
206
191
|
branch = "main", branches = null, versionCode = 1, now, today,
|
|
@@ -210,7 +195,7 @@ export function buildVersionYml({
|
|
|
210
195
|
const typesJson = types.length ? `[${types.map((t) => `"${t}"`).join(", ")}]` : `["basic"]`;
|
|
211
196
|
const b = branches || { main: branch || "main", develop: "develop", mode: "pr-flow" };
|
|
212
197
|
const {
|
|
213
|
-
templateVersion = "unknown",
|
|
198
|
+
templateVersion = "unknown",
|
|
214
199
|
includeSemverAuto = true, includeCopilotAi = false, deployStyle = "", optionsDate = today,
|
|
215
200
|
} = templateOptions || {};
|
|
216
201
|
|
|
@@ -234,7 +219,7 @@ export function buildVersionYml({
|
|
|
234
219
|
for (const t of deployTypes) {
|
|
235
220
|
rows.push(` ${t}:`);
|
|
236
221
|
// 동일한 이스케이프를 재사용 — deploy 값도 @wizard ask 값과 같은 경로로 들어오므로
|
|
237
|
-
// 큰따옴표가 섞이면 setEnvLine과 동일하게 YAML이 깨진다 (
|
|
222
|
+
// 큰따옴표가 섞이면 setEnvLine과 동일하게 YAML이 깨진다 (두 번째 지점).
|
|
238
223
|
for (const [k, v] of deployValues.get(t)) rows.push(` ${k}: "${escapeYamlDoubleQuoted(v)}"`);
|
|
239
224
|
}
|
|
240
225
|
deployBlock = rows.join("\n");
|
|
@@ -249,7 +234,6 @@ export function buildVersionYml({
|
|
|
249
234
|
NOW: now, TODAY: today || optionsDate, DEFAULT_BRANCH: branch,
|
|
250
235
|
TEMPLATE_VERSION: templateVersion,
|
|
251
236
|
MAIN_BRANCH: b.main, DEVELOP_BRANCH: b.develop, BRANCH_MODE: b.mode,
|
|
252
|
-
OPT_NEXUS: String(includeNexus), OPT_SECRET_BACKUP: String(includeSecretBackup),
|
|
253
237
|
OPT_SEMVER_AUTO: String(includeSemverAuto),
|
|
254
238
|
OPT_COPILOT_AI: String(includeCopilotAi),
|
|
255
239
|
OPT_DEPLOY_STYLE: String(deployStyle || ""),
|
|
@@ -281,14 +265,14 @@ export function buildVersionYml({
|
|
|
281
265
|
export function renderVersionYml(context, templateText, { pathMarkers, deployValues = new Map(), extraTopLevel = [] }) {
|
|
282
266
|
const { version, types = [], paths = new Map(), branch = "main", versionCode = 1,
|
|
283
267
|
now, today, templateVersion = "unknown", branches = null,
|
|
284
|
-
|
|
268
|
+
includeSemverAuto, includeCopilotAi, deployStyle,
|
|
285
269
|
envMode, flutterStore, androidDeployMode, iosDeployMode } = context;
|
|
286
270
|
return buildVersionYml({
|
|
287
271
|
templateText, version, types, paths, pathMarkers, branch, branches, versionCode, now, today,
|
|
288
272
|
deployValues, extraTopLevel,
|
|
289
273
|
flutterOptions: { envMode, stores: flutterStore, androidDeployMode, iosDeployMode },
|
|
290
274
|
templateOptions: {
|
|
291
|
-
templateVersion,
|
|
275
|
+
templateVersion,
|
|
292
276
|
includeSemverAuto: includeSemverAuto !== false,
|
|
293
277
|
includeCopilotAi: includeCopilotAi === true,
|
|
294
278
|
deployStyle: deployStyle || DEFAULT_DEPLOY_STYLE,
|