projectops 4.45.0 → 4.45.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 +1 -1
- package/package.json +1 -1
- package/src/commands/interactive.js +25 -32
- package/src/core/copy/workflows.js +29 -60
- package/src/core/options-ask.js +7 -3
- package/src/core/orphan-workflows.js +7 -22
- package/src/core/resolve-options.js +91 -0
- package/src/core/workflow-groups.js +44 -0
- package/src/index.js +19 -70
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
**Status: actively maintained.** npm `latest` is exactly what is on the `main` branch; work in progress on `develop` is never published. Versions can still ship several times a day. To stay on a version you have tested, run `npx projectops@<version>` ([releases](https://github.com/Cassiiopeia/projectops/releases)).
|
|
10
10
|
|
|
11
11
|
<!-- AUTO-VERSION-SECTION: DO NOT EDIT MANUALLY -->
|
|
12
|
-
## Latest version : v4.
|
|
12
|
+
## Latest version : v4.45.0 (2026-10-10)
|
|
13
13
|
|
|
14
14
|
[View full version history](CHANGELOG.md)
|
|
15
15
|
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// io 주입으로 테스트 가능. 실제 실행은 src/ui/prompts.js 함수를 io로 넘긴다.
|
|
3
3
|
// 새 시각 층(banner/detectionLog/analysisCard/ideStatus/installKind/summary)과 저수준 엔진(engineIo)은
|
|
4
4
|
// io의 "옵셔널 멤버" — 스텁이 생략하면 해당 층만 건너뛰고 실행 계약은 동일하다.
|
|
5
|
-
import {
|
|
5
|
+
import { resolveOptions } from "../core/resolve-options.js";
|
|
6
6
|
import { join } from "node:path";
|
|
7
7
|
import { existsSync, readFileSync } from "node:fs";
|
|
8
8
|
import { PATHS } from "../core/paths.js";
|
|
@@ -14,7 +14,7 @@ import { runBreakingCheck } from "../core/breaking-check.js";
|
|
|
14
14
|
import { runMigrations } from "../core/migrations/index.js";
|
|
15
15
|
import { detectOrphanWorkflows, applyOrphanCleanup } from "../core/orphan-workflows.js";
|
|
16
16
|
import { resolveProjectPaths, filterExcludedTypes } from "../core/paths-resolve.js";
|
|
17
|
-
import { askAllOptionalWorkflows, OPTION_AXES, applicableTargets
|
|
17
|
+
import { askAllOptionalWorkflows, OPTION_AXES, applicableTargets } from "../core/options-ask.js";
|
|
18
18
|
import { createRunTrace, MIGRATION_DIR } from "../core/run-trace.js";
|
|
19
19
|
import { appendGuideEntry } from "../core/migration-guide.js";
|
|
20
20
|
import { promptEnvPlan } from "../ui/env-plan.js";
|
|
@@ -125,35 +125,26 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
|
|
|
125
125
|
let branch = detectDefaultBranch(cwd);
|
|
126
126
|
const repoName = detectRepoName(cwd);
|
|
127
127
|
const versionCode = existing?.versionCode ?? 1; // 기존 빌드번호 보존
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
let
|
|
132
|
-
let
|
|
133
|
-
let
|
|
134
|
-
let
|
|
135
|
-
let
|
|
136
|
-
let
|
|
128
|
+
// 옵션 초기값은 비대화형과 같은 resolve-options.js 에서 받는다 (#851).
|
|
129
|
+
// 질문을 건너뛰는 모드(version 등)도 이 값이 그대로 기록되므로 타입 적용성 정리까지 거친 값이어야 한다.
|
|
130
|
+
const { values: init, sources: initSources } = resolveOptions({ existing, types, cwd });
|
|
131
|
+
let deployTarget = init.deployTarget;
|
|
132
|
+
let publishTargets = init.publishTargets;
|
|
133
|
+
let includeSecretBackup = init.includeSecretBackup;
|
|
134
|
+
let aiPrSummary = init.aiPrSummary;
|
|
135
|
+
let codeReviewCoderabbit = init.codeReviewCoderabbit;
|
|
136
|
+
let changelogProvider = init.changelogProvider;
|
|
137
|
+
let changelogBaseUrl = init.changelogBaseUrl;
|
|
138
|
+
let deployBranch = init.deployBranch; // #456 — 비어 있으면 기록하지 않는다. 질문에서 고른 값만 남긴다
|
|
137
139
|
let deployBranchReady = null; // #490 — 이번 실행에서 개발 브랜치 존재/생성이 확인됐는지
|
|
138
140
|
let deployBranchCreated = null; // #493 — 마법사가 직접 생성했는지 (가이드 기록용)
|
|
139
|
-
let intent =
|
|
140
|
-
//
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const closeOnRelease = existing?.options?.closeOnRelease ?? (existing ? null : true); // #771 묻지 않는다
|
|
147
|
-
const excludedWorkflows = existing?.options?.excludedWorkflows ?? null; // #810 저장값 보존
|
|
148
|
-
const storeLocales = existing?.options?.storeLocales ?? null; // #829 저장값 보존
|
|
149
|
-
const storeLocalesIos = existing?.options?.storeLocalesIos ?? null;
|
|
150
|
-
const storeLocalesPlay = existing?.options?.storeLocalesPlay ?? null;
|
|
151
|
-
// Projects 보드 동기화(#716): 묻지 않는다. 저장값 > 이미 설치돼 있으면 유지 > 신규는 제외 (index.js 와 같은 규칙).
|
|
152
|
-
const projectsSync = existing?.options?.projectsSync
|
|
153
|
-
?? existsSync(join(cwd, ".github/workflows/PROJECT-COMMON-PROJECTS-SYNC-MANAGER.yaml"));
|
|
154
|
-
// 레포 문구 언어(#769): 저장값 > (신규 en / 기존 ko). 기존 레포에는 영문 전환을 한 번만 제안한다.
|
|
155
|
-
let language = resolveRepoLanguage({ flag: null, stored: existing?.options?.language, existing: !!existing });
|
|
156
|
-
let labelStyle = resolveLabelStyle({ flag: null, stored: existing?.options?.labelStyle, existing: !!existing });
|
|
141
|
+
let intent = init.intent; // #485 프로젝트 성격
|
|
142
|
+
// 묻지 않는 값 — 저장값 보존 규칙은 resolve-options.js 에 있다
|
|
143
|
+
const { semverAuto, appRelease, closeOnRelease, excludedWorkflows, storeLocales, storeLocalesIos, storeLocalesPlay, projectsSync } = init;
|
|
144
|
+
const semverAutoNewlyOn = initSources.semverAutoNewlyOn;
|
|
145
|
+
// 레포 문구 언어(#769)·라벨 표기(#776): 기존 레포에는 영문 전환을 한 번만 제안한다(아래).
|
|
146
|
+
let language = init.language;
|
|
147
|
+
let labelStyle = init.labelStyle;
|
|
157
148
|
const showOptional = mode === "full" || mode === "workflows";
|
|
158
149
|
const realTty = process.stdout.isTTY === true;
|
|
159
150
|
// 기존 한글 라벨 레포에는 영문 표준 전환을 한 번만 제안한다(#776). 거절하면 한글을 저장해 다시 묻지 않는다.
|
|
@@ -194,6 +185,7 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
|
|
|
194
185
|
publishTargets = r.publish;
|
|
195
186
|
includeSecretBackup = r.secretBackup;
|
|
196
187
|
codeReviewCoderabbit = r.codeReviewCoderabbit;
|
|
188
|
+
aiPrSummary = r.aiPrSummary; // #851 — 빠져 있어 PR 요약 선택이 버려졌다
|
|
197
189
|
changelogProvider = r.changelogProvider;
|
|
198
190
|
changelogBaseUrl = r.changelogBaseUrl;
|
|
199
191
|
deployBranch = r.deployBranch;
|
|
@@ -256,7 +248,7 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
|
|
|
256
248
|
tempDir, types, targetRoot: cwd, defaultBranch: branch,
|
|
257
249
|
current: {
|
|
258
250
|
deploy: deployTarget, publish: publishTargets, secretBackup: includeSecretBackup,
|
|
259
|
-
codeReviewCoderabbit, changelogProvider, changelogBaseUrl, deployBranch, intent,
|
|
251
|
+
codeReviewCoderabbit, aiPrSummary, changelogProvider, changelogBaseUrl, deployBranch, intent,
|
|
260
252
|
},
|
|
261
253
|
force: false, tty: realTty, forceAsk: true, scope: [what],
|
|
262
254
|
io: {
|
|
@@ -270,6 +262,7 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
|
|
|
270
262
|
publishTargets = r.publish;
|
|
271
263
|
includeSecretBackup = r.secretBackup;
|
|
272
264
|
codeReviewCoderabbit = r.codeReviewCoderabbit;
|
|
265
|
+
aiPrSummary = r.aiPrSummary; // #851
|
|
273
266
|
changelogProvider = r.changelogProvider;
|
|
274
267
|
changelogBaseUrl = r.changelogBaseUrl;
|
|
275
268
|
deployBranch = r.deployBranch;
|
|
@@ -334,10 +327,10 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
|
|
|
334
327
|
const { now, today } = clock || utcNow();
|
|
335
328
|
const ctx = createContext({
|
|
336
329
|
mode, force: true, types, version, versionCode, branch, paths, deployTarget, publishTargets, includeSecretBackup,
|
|
337
|
-
codeReviewCoderabbit, changelogProvider, changelogBaseUrl, deployBranch, intent, semverAuto, appRelease, labelStyle, closeOnRelease, projectsSync, language, excludedWorkflows, storeLocales, storeLocalesIos, storeLocalesPlay,
|
|
330
|
+
aiPrSummary, codeReviewCoderabbit, changelogProvider, changelogBaseUrl, deployBranch, intent, semverAuto, appRelease, labelStyle, closeOnRelease, projectsSync, language, excludedWorkflows, storeLocales, storeLocalesIos, storeLocalesPlay,
|
|
338
331
|
repoName, templateVersion, resolvers, envValues, envUseDefaults, now, today,
|
|
339
332
|
// #502 — version 모드가 기존 full 기록을 강등하지 않도록 (full이 우세)
|
|
340
|
-
recordMode:
|
|
333
|
+
recordMode: init.recordMode,
|
|
341
334
|
});
|
|
342
335
|
ctx.templateVersion = templateVersion;
|
|
343
336
|
|
|
@@ -15,6 +15,7 @@ import { substituteBranches } from "../branch-sub.js";
|
|
|
15
15
|
import { saveIncoming, lineDiffCounts } from "../incoming.js";
|
|
16
16
|
import { branchStatus } from "../git-branch.js";
|
|
17
17
|
import { PUBLISH_TARGETS } from "../constants.js";
|
|
18
|
+
import { commonOptionalGroups, serverDeployEnabled, SERVER_DEPLOY_DIR } from "../workflow-groups.js";
|
|
18
19
|
|
|
19
20
|
// on: 블록의 브랜치 필터가 전부 develop 뿐인가 (#810).
|
|
20
21
|
// 개발 브랜치가 없는 레포에 깔면 영영 실행되지 않는 워크플로우를 가린다.
|
|
@@ -229,78 +230,46 @@ export function copyWorkflows(context, tempDir, targetRoot = ".", hooks = {}) {
|
|
|
229
230
|
if (asks.size) deployValues.set(type, asks);
|
|
230
231
|
}
|
|
231
232
|
|
|
232
|
-
// (4.5) common/
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
233
|
+
// (4.5~5) common/ 아래 선택형 폴더 — deploy/<target>, pr-summary, projects-sync, secret-backup.
|
|
234
|
+
// 어느 폴더를 켜는지는 workflow-groups.js 한 곳이 정한다 (#851). 고아 감지도 같은 표를 쓴다.
|
|
235
|
+
// bak-replace: 템플릿과 같으면 건너뛰고, 다르면 .bak 으로 남기고 교체한다.
|
|
236
|
+
// new-only : 이미 있으면 건드리지 않는다. 새로 깔 때만 레포 이름을 채운다 (#729 — 고정값이면
|
|
237
|
+
// 모든 프로젝트가 서버의 같은 폴더에 Secret 을 덮어쓴다).
|
|
238
|
+
for (const group of commonOptionalGroups(commonDir, { includeSecretBackup, aiPrSummary, projectsSync, deployTarget })) {
|
|
239
|
+
const groupDir = join(commonDir, group.dir);
|
|
240
|
+
if (!group.enabled || !exists(groupDir)) continue;
|
|
241
|
+
for (const filename of listYamlFiles(groupDir)) {
|
|
242
|
+
const src = join(groupDir, filename);
|
|
237
243
|
const dst = join(workflowsDir, filename);
|
|
238
|
-
if (
|
|
239
|
-
|
|
240
|
-
|
|
244
|
+
if (group.policy === "new-only") {
|
|
245
|
+
if (existsSync(dst)) continue;
|
|
246
|
+
if (skipNew(groupDir, filename, group.copyLabel)) continue;
|
|
247
|
+
copyFileSync(src, dst);
|
|
248
|
+
const raw = readFileSync(dst, "utf8");
|
|
249
|
+
const filled = resolveGlobalTokens(raw, repoName || "my-project");
|
|
250
|
+
if (filled !== raw) writeFileSync(dst, filled);
|
|
251
|
+
counters.optionalCopied++;
|
|
252
|
+
counters.copied++;
|
|
253
|
+
counters.copiedFiles.push(filename);
|
|
254
|
+
trace?.event("copy", "copied", filename, { group: group.copyLabel });
|
|
241
255
|
continue;
|
|
242
256
|
}
|
|
243
|
-
if (skipNew(commonDeployDir, filename, "common-deploy")) continue;
|
|
244
|
-
const backedUp = existsSync(dst);
|
|
245
|
-
if (backedUp) renameSync(dst, dst + ".bak");
|
|
246
|
-
copyFileSync(src, dst);
|
|
247
|
-
counters.optionalCopied++;
|
|
248
|
-
counters.copied++;
|
|
249
|
-
counters.copiedFiles.push(filename);
|
|
250
|
-
trace?.event("copy", backedUp ? "replaced-bak" : "copied", filename, { group: "common-deploy" });
|
|
251
|
-
if (backedUp) counters.replacedBak.push(filename); // #673
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// (4.7) common/pr-summary — AI 변경 요약 (#566). 선택했을 때만 복사한다.
|
|
256
|
-
// 종전에는 common 본체에 있어 무조건 복사된 뒤 런타임에 스스로 빠졌고, 그 판단이
|
|
257
|
-
// ".coderabbit.yaml 존재"라 파일만 있고 앱이 없는 저장소에서는 아무도 요약하지 않았다.
|
|
258
|
-
// (4.8) common/projects-sync — Projects 보드 동기화 (#716). 이것도 선택했을 때만 복사한다.
|
|
259
|
-
// Secret(_GITHUB_PAT_TOKEN)과 PROJECT_URL 변수를 등록해야 동작하는데, Projects 를 안 쓰는 레포가
|
|
260
|
-
// 많아 설치만 되고 설정되지 않은 채 남았다. 같은 폴더 방식이라 게이트와 고아 감지(orphan-workflows.js)를 함께 둔다.
|
|
261
|
-
for (const [groupName, groupEnabled] of [["pr-summary", aiPrSummary], ["projects-sync", projectsSync]]) {
|
|
262
|
-
const prSummaryDir = join(commonDir, groupName);
|
|
263
|
-
if (exists(prSummaryDir) && groupEnabled) {
|
|
264
|
-
for (const filename of listYamlFiles(prSummaryDir)) {
|
|
265
|
-
const src = join(prSummaryDir, filename);
|
|
266
|
-
const dst = join(workflowsDir, filename);
|
|
267
257
|
if (existsSync(dst) && isUnchanged(readFileSync(src, "utf8"), readFileSync(dst, "utf8"), envOptsFor("common"))) {
|
|
268
258
|
counters.skipped++;
|
|
269
|
-
trace?.event("copy", "skipped-unchanged", filename, { group:
|
|
259
|
+
trace?.event("copy", "skipped-unchanged", filename, { group: group.copyLabel });
|
|
270
260
|
continue;
|
|
271
261
|
}
|
|
272
|
-
if (skipNew(
|
|
262
|
+
if (skipNew(groupDir, filename, group.copyLabel)) continue;
|
|
273
263
|
const backedUp = existsSync(dst);
|
|
274
264
|
if (backedUp) renameSync(dst, dst + ".bak");
|
|
275
265
|
copyFileSync(src, dst);
|
|
276
266
|
counters.optionalCopied++;
|
|
277
267
|
counters.copied++;
|
|
278
268
|
counters.copiedFiles.push(filename);
|
|
279
|
-
trace?.event("copy", backedUp ? "replaced-bak" : "copied", filename, { group:
|
|
269
|
+
trace?.event("copy", backedUp ? "replaced-bak" : "copied", filename, { group: group.copyLabel });
|
|
280
270
|
if (backedUp) counters.replacedBak.push(filename); // #673
|
|
281
271
|
}
|
|
282
272
|
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// (5) common/secret-backup — 있으면 무조건 스킵/신규만 복사
|
|
286
|
-
const secretDir = join(commonDir, "secret-backup");
|
|
287
|
-
if (exists(secretDir) && includeSecretBackup) {
|
|
288
|
-
for (const filename of listYamlFiles(secretDir)) {
|
|
289
|
-
const dst = join(workflowsDir, filename);
|
|
290
|
-
if (existsSync(dst)) continue; // 이미 존재하면 스킵
|
|
291
|
-
if (skipNew(secretDir, filename, "secret-backup")) continue;
|
|
292
|
-
copyFileSync(join(secretDir, filename), dst);
|
|
293
|
-
// PROJECT_NAME 이 고정값이면 모든 프로젝트가 서버의 같은 폴더에 Secret 을 덮어쓴다 (#729) —
|
|
294
|
-
// 다른 배포 워크플로처럼 레포 이름으로 채운다. 이름을 알 수 없으면 기존 기본값을 쓴다.
|
|
295
|
-
const raw = readFileSync(dst, "utf8");
|
|
296
|
-
const filled = resolveGlobalTokens(raw, repoName || "my-project");
|
|
297
|
-
if (filled !== raw) writeFileSync(dst, filled);
|
|
298
|
-
counters.optionalCopied++;
|
|
299
|
-
counters.copied++;
|
|
300
|
-
counters.copiedFiles.push(filename);
|
|
301
|
-
trace?.event("copy", "copied", filename, { group: "secret-backup" });
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
273
|
|
|
305
274
|
// (6) 브랜치 치환 post-pass (#477) — 표준과 다른 브랜치 전략일 때만 복사된 파일에 적용.
|
|
306
275
|
// isUnchanged 가상 비교에도 같은 branches가 들어가므로 다음 업데이트에서 재복사 churn이 없다.
|
|
@@ -411,8 +380,8 @@ export function listWorkflowConflicts(context, tempDir, targetRoot = ".") {
|
|
|
411
380
|
if (exists(typeDir)) {
|
|
412
381
|
for (const f of classify(typeDir, workflowsDir, envOpts, baseline).changed) conflicts.push({ filename: f, type });
|
|
413
382
|
}
|
|
414
|
-
const serverDeployDir = join(typeDir,
|
|
415
|
-
if (exists(serverDeployDir) && (deployTarget
|
|
383
|
+
const serverDeployDir = join(typeDir, SERVER_DEPLOY_DIR);
|
|
384
|
+
if (exists(serverDeployDir) && serverDeployEnabled(deployTarget)) {
|
|
416
385
|
for (const f of classify(serverDeployDir, workflowsDir, envOpts, baseline).changed) conflicts.push({ filename: f, type });
|
|
417
386
|
}
|
|
418
387
|
}
|
|
@@ -464,8 +433,8 @@ function copyWorkflowsForType(type, projectTypesDir, workflowsDir, ctx, counters
|
|
|
464
433
|
}
|
|
465
434
|
|
|
466
435
|
// server-deploy — deploy=docker-ssh일 때만 포함 (#439)
|
|
467
|
-
const serverDeployDir = join(typeDir,
|
|
468
|
-
if (exists(serverDeployDir) && (deployTarget
|
|
436
|
+
const serverDeployDir = join(typeDir, SERVER_DEPLOY_DIR);
|
|
437
|
+
if (exists(serverDeployDir) && serverDeployEnabled(deployTarget)) {
|
|
469
438
|
const { newFiles, unchanged, changed, upstream } = classify(serverDeployDir, workflowsDir, envOpts, baseline);
|
|
470
439
|
for (const f of unchanged) { counters.skipped++; trace?.event("copy", "skipped-unchanged", f, { group: `${type}/server-deploy` }); }
|
|
471
440
|
for (const f of newFiles) { if (counters.skipNew(serverDeployDir, f, `${type}/server-deploy`)) continue; copyFileSync(join(serverDeployDir, f), join(workflowsDir, f)); counters.copied++; counters.copiedFiles.push(f); trace?.event("copy", "copied", f, { group: `${type}/server-deploy` }); }
|
package/src/core/options-ask.js
CHANGED
|
@@ -234,7 +234,10 @@ export async function askAllOptionalWorkflows({
|
|
|
234
234
|
if (askIntent) {
|
|
235
235
|
if (force || !tty || typeof io.select !== "function") {
|
|
236
236
|
// 비대화형: CLI intent 미지정이면 deploy/publish에서 역추론, 그래도 없으면 manual(기존 동작)
|
|
237
|
-
|
|
237
|
+
// 축이 아직 안 정해졌으면 비대화형(resolve-options.js)과 같은 기본 축으로 추론한다 (#851).
|
|
238
|
+
// 예전에는 null 로 추론해 "manual" 이 기록되어 같은 레포에서 두 경로의 결과가 갈렸다.
|
|
239
|
+
const defaultDeploy = applicable.deploy.includes("docker-ssh") ? "docker-ssh" : "none";
|
|
240
|
+
intent = intent ?? inferIntent(deploy ?? defaultDeploy, publish ?? []) ?? "manual";
|
|
238
241
|
} else {
|
|
239
242
|
say("");
|
|
240
243
|
say(msg("options.intent.intro"));
|
|
@@ -394,7 +397,8 @@ export async function askAllOptionalWorkflows({
|
|
|
394
397
|
// "배포 브랜치"가 아니다 — 배포가 도는 곳은 기본 브랜치(default) 쪽 개념. #482 참조.
|
|
395
398
|
if (ask("release-branch") || deployBranch === null) {
|
|
396
399
|
if (force || !tty || typeof io.text !== "function") {
|
|
397
|
-
|
|
400
|
+
// 묻지 못했으면 값을 지어내지 않는다 (#851) — 비어 있으면 기록하지 않고 스킬이 develop 으로 폴백한다.
|
|
401
|
+
deployBranch = deployBranch ?? "";
|
|
398
402
|
} else {
|
|
399
403
|
const base = defaultBranch || "main"; // git으로 감지된 기본 브랜치 (#481 동적 안내)
|
|
400
404
|
say("");
|
|
@@ -431,7 +435,7 @@ export async function askAllOptionalWorkflows({
|
|
|
431
435
|
aiPrSummary: aiPrSummary !== false,
|
|
432
436
|
changelogProvider: changelogProvider ?? "commit",
|
|
433
437
|
changelogBaseUrl: changelogBaseUrl ?? "",
|
|
434
|
-
deployBranch: deployBranch ?? "
|
|
438
|
+
deployBranch: deployBranch ?? "",
|
|
435
439
|
deployBranchReady, // #490 — true=존재/생성 확인됨, false=거절/실패, null=확인 안 함
|
|
436
440
|
deployBranchCreated, // #493 — true=이번 실행에서 마법사가 생성, null=확인 안 함
|
|
437
441
|
|
|
@@ -6,11 +6,12 @@ import { existsSync, renameSync, rmSync, readdirSync } from "node:fs";
|
|
|
6
6
|
import { join } from "node:path";
|
|
7
7
|
import { PATHS } from "./paths.js";
|
|
8
8
|
import { exists, listYamlFiles } from "./fsutil.js";
|
|
9
|
+
import { commonOptionalGroups, SERVER_DEPLOY_DIR } from "./workflow-groups.js";
|
|
9
10
|
|
|
10
11
|
// 템플릿의 project-types/<type>/ 인벤토리 — 직하위 + server-deploy/ + publish/*/ (copy 엔진과 동일 범위)
|
|
11
12
|
function typeInventory(projectTypesDir, type) {
|
|
12
13
|
const typeDir = join(projectTypesDir, type);
|
|
13
|
-
const dirs = [typeDir, join(typeDir,
|
|
14
|
+
const dirs = [typeDir, join(typeDir, SERVER_DEPLOY_DIR)];
|
|
14
15
|
const pubRoot = join(typeDir, "publish");
|
|
15
16
|
if (exists(pubRoot)) {
|
|
16
17
|
for (const e of readdirSync(pubRoot, { withFileTypes: true })) {
|
|
@@ -28,30 +29,14 @@ function typeInventory(projectTypesDir, type) {
|
|
|
28
29
|
// common/ 아래 "선택했을 때만 복사되는" 폴더들 (#566).
|
|
29
30
|
// 켜면 복사 엔진이 넣어주지만, 끄면 이미 있던 파일이 그대로 남아 계속 실행됐다.
|
|
30
31
|
// 축을 끈 사용자에게는 그게 곧 "껐는데 왜 도나"가 된다 — 여기서 고아로 잡는다.
|
|
31
|
-
//
|
|
32
|
+
// 켜짐/꺼짐은 복사 엔진과 같은 표(workflow-groups.js)에서 읽는다 (#851).
|
|
32
33
|
function commonOptionalOrphans(commonDir, opts) {
|
|
33
|
-
const { includeSecretBackup = false, aiPrSummary = true, projectsSync = true, deployTarget = "docker-ssh" } = opts;
|
|
34
34
|
const out = [];
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
["pr-summary", aiPrSummary, "pr-summary"],
|
|
39
|
-
["projects-sync", projectsSync, "projects-sync"],
|
|
40
|
-
];
|
|
41
|
-
for (const [dir, enabled, label] of gated) {
|
|
42
|
-
if (enabled) continue;
|
|
43
|
-
const d = join(commonDir, dir);
|
|
35
|
+
for (const group of commonOptionalGroups(commonDir, opts)) {
|
|
36
|
+
if (group.enabled) continue;
|
|
37
|
+
const d = join(commonDir, group.dir);
|
|
44
38
|
if (!exists(d)) continue;
|
|
45
|
-
for (const f of listYamlFiles(d)) out.push({ filename: f, type:
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// deploy/<target> — 선택된 타겟 외 폴더는 전부 고아 후보
|
|
49
|
-
const deployRoot = join(commonDir, "deploy");
|
|
50
|
-
if (exists(deployRoot)) {
|
|
51
|
-
for (const e of readdirSync(deployRoot, { withFileTypes: true })) {
|
|
52
|
-
if (!e.isDirectory() || e.name === deployTarget) continue;
|
|
53
|
-
for (const f of listYamlFiles(join(deployRoot, e.name))) out.push({ filename: f, type: `deploy/${e.name}` });
|
|
54
|
-
}
|
|
39
|
+
for (const f of listYamlFiles(d)) out.push({ filename: f, type: group.orphanLabel });
|
|
55
40
|
}
|
|
56
41
|
return out;
|
|
57
42
|
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// 설치 옵션 값을 정하는 단 한 곳 (#851).
|
|
2
|
+
//
|
|
3
|
+
// 예전에는 비대화형(src/index.js)과 대화형(src/commands/interactive.js)이 같은 옵션을
|
|
4
|
+
// 각자 정했다. 그 사이 규칙이 갈라져 대화형에서 PR 요약 선택이 버려지고(aiPrSummary),
|
|
5
|
+
// 질문을 건너뛰는 version 모드가 모바일 단독 레포에 deploy: docker-ssh 를 남겼다.
|
|
6
|
+
// 규칙은 모든 옵션에 같다: CLI 플래그 > version.yml 저장값 > 기본값.
|
|
7
|
+
// 대화형은 이 결과를 초기값으로 쓰고, 사용자가 답한 것만 그 위에 덮어쓴다.
|
|
8
|
+
import { existsSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { applicableTargets, migrateProvider } from "./options-ask.js";
|
|
11
|
+
import { DEFAULT_CODE_REVIEW_CODERABBIT } from "./constants.js";
|
|
12
|
+
import { resolveLabelStyle } from "./label-style.js";
|
|
13
|
+
import { resolveRepoLanguage } from "./repo-language.js";
|
|
14
|
+
|
|
15
|
+
// 이미 설치돼 있으면 Projects 동기화를 켜진 것으로 보는 기준 파일 (#716)
|
|
16
|
+
export const PROJECTS_SYNC_WORKFLOW = ".github/workflows/PROJECT-COMMON-PROJECTS-SYNC-MANAGER.yaml";
|
|
17
|
+
|
|
18
|
+
// 타입에 성립하지 않는 축 값을 정리한다 (#498). 모바일 앱·basic 단독은 두 축이 모두 비어
|
|
19
|
+
// intent 도 none 으로 확정된다. 질문을 거친 값에도 같은 규칙을 다시 적용할 수 있게 따로 둔다.
|
|
20
|
+
export function normalizeAxes({ deploy, publish, intent, types }) {
|
|
21
|
+
const applicable = applicableTargets(types);
|
|
22
|
+
const outDeploy = deploy !== "none" && !applicable.deploy.includes(deploy) ? "none" : deploy;
|
|
23
|
+
const outPublish = publish.filter((t) => applicable.publish.includes(t));
|
|
24
|
+
const noAxes = types.length > 0 && applicable.deploy.length === 0 && applicable.publish.length === 0;
|
|
25
|
+
return { deploy: outDeploy, publish: outPublish, intent: noAxes ? "none" : intent, applicable };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// flags: parseArgs 결과 중 옵션 관련 값 (없으면 {} — 대화형 초기값)
|
|
29
|
+
// existing: parseExisting 결과 또는 null
|
|
30
|
+
// 반환: { values, sources } — sources 는 trace 가 "왜 이 값인가"를 남길 근거다 (#561).
|
|
31
|
+
export function resolveOptions({ flags = {}, existing = null, types = [], cwd = "." }) {
|
|
32
|
+
const stored = existing?.options ?? {};
|
|
33
|
+
const applicable = applicableTargets(types);
|
|
34
|
+
|
|
35
|
+
// 프로젝트 성격(#485): CLI --intent → 저장값. deploy/publish 유도의 기준.
|
|
36
|
+
let intent = flags.intent ?? stored.intent ?? null;
|
|
37
|
+
// 배포/publish 축(#439): 플래그 → 저장값 → 기본값(적용 가능할 때만 docker-ssh — #498).
|
|
38
|
+
let deploy = flags.deployTarget ?? stored.deploy
|
|
39
|
+
?? (applicable.deploy.includes("docker-ssh") ? "docker-ssh" : "none");
|
|
40
|
+
let publish = flags.publishTargets ?? stored.publish ?? [];
|
|
41
|
+
// --intent 를 줬고 해당 축 플래그가 없으면 intent 가 축을 유도한다 (#485 비대화형)
|
|
42
|
+
if (flags.intent != null) {
|
|
43
|
+
if ((intent === "library" || intent === "none") && flags.deployTarget == null) deploy = "none";
|
|
44
|
+
if ((intent === "app" || intent === "none") && flags.publishTargets == null) publish = [];
|
|
45
|
+
}
|
|
46
|
+
const before = { deploy, publish: [...publish] };
|
|
47
|
+
({ deploy, publish, intent } = normalizeAxes({ deploy, publish, intent, types }));
|
|
48
|
+
|
|
49
|
+
const values = {
|
|
50
|
+
intent, deployTarget: deploy, publishTargets: publish,
|
|
51
|
+
includeSecretBackup: flags.includeSecretBackup ?? stored.secretBackup ?? false,
|
|
52
|
+
// #566 — 설정 없이 바로 동작하므로 기본 켜짐
|
|
53
|
+
aiPrSummary: flags.aiPrSummary ?? stored.aiPrSummary ?? true,
|
|
54
|
+
// #716 — 설정(Secret, PROJECT_URL) 없는 신규 레포에 깔면 알림만 쌓인다. 이미 깔린 레포는 유지.
|
|
55
|
+
projectsSync: flags.projectsSync ?? stored.projectsSync ?? existsSync(join(cwd, PROJECTS_SYNC_WORKFLOW)),
|
|
56
|
+
// #502 — version 모드가 기존 full 통합 기록을 강등하지 않게 (full 이 우세)
|
|
57
|
+
recordMode: existing?.templateMode === "full" ? "full" : "version",
|
|
58
|
+
// #456 — 비어 있으면 version.yml 에 쓰지 않는다 (스킬이 develop 으로 폴백). 사용자가 고르거나 플래그로 준 것만 기록.
|
|
59
|
+
deployBranch: flags.deployBranch || stored.deployBranch || "",
|
|
60
|
+
// #455 — null 이 흘러 provider:"null" 로 기록되던 버그 방지
|
|
61
|
+
changelogProvider: migrateProvider(stored.changelogProvider) ?? "commit",
|
|
62
|
+
changelogBaseUrl: stored.changelogBaseUrl ?? "",
|
|
63
|
+
codeReviewCoderabbit: flags.codeReviewCoderabbit ?? stored.codeReviewCoderabbit ?? DEFAULT_CODE_REVIEW_CODERABBIT,
|
|
64
|
+
// #546, #814 — 명시적 false 는 존중, 키가 없으면 신규·기존 모두 켠다
|
|
65
|
+
semverAuto: stored.semverAuto ?? true,
|
|
66
|
+
// 마법사가 묻지 않는 값은 저장값만 보존한다 (전체 재생성이라 안 넘기면 사라진다)
|
|
67
|
+
appRelease: stored.appRelease ?? null, // #553
|
|
68
|
+
excludedWorkflows: stored.excludedWorkflows ?? null, // #810
|
|
69
|
+
storeLocales: stored.storeLocales ?? null, // #829
|
|
70
|
+
storeLocalesIos: stored.storeLocalesIos ?? null,
|
|
71
|
+
storeLocalesPlay: stored.storeLocalesPlay ?? null,
|
|
72
|
+
// #771 — 신규만 켠다. 기존 레포는 키를 만들지 않아 현행 유지
|
|
73
|
+
closeOnRelease: stored.closeOnRelease ?? (existing ? null : true),
|
|
74
|
+
// #769, #776 — 신규 en / 기존 ko. 업데이트만으로 기존 레포 문구·라벨이 바뀌지 않는다
|
|
75
|
+
language: resolveRepoLanguage({ flag: flags.language ?? null, stored: stored.language, existing: !!existing }),
|
|
76
|
+
labelStyle: resolveLabelStyle({ flag: flags.labelStyle ?? null, stored: stored.labelStyle, existing: !!existing }),
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const from = (flagVal, storedVal, flagName) =>
|
|
80
|
+
flagVal != null ? `cli-flag(${flagName})` : (storedVal ? "version.yml(stored value)" : "default");
|
|
81
|
+
const sources = {
|
|
82
|
+
intent: flags.intent != null ? "cli-flag(--intent)"
|
|
83
|
+
: (stored.intent ? "version.yml(stored value)" : "not given, inferred from deploy/publish"),
|
|
84
|
+
deploy: from(flags.deployTarget, stored.deploy, "--deploy"),
|
|
85
|
+
publish: from(flags.publishTargets, stored.publish, "--publish"),
|
|
86
|
+
before, applicable,
|
|
87
|
+
// 기존 레포에서 semver_auto 가 이번에 처음 켜졌는지 — 완료 화면이 알린다
|
|
88
|
+
semverAutoNewlyOn: !!existing && stored.semverAuto == null,
|
|
89
|
+
};
|
|
90
|
+
return { values, sources };
|
|
91
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// 선택형 워크플로 그룹의 켜짐/꺼짐을 정하는 단 한 곳 (#851).
|
|
2
|
+
//
|
|
3
|
+
// 예전에는 복사(copy/workflows.js), 충돌 목록(listWorkflowConflicts), 고아 감지(orphan-workflows.js)가
|
|
4
|
+
// 같은 조건을 각자 들고 있었다. 한쪽만 고치면 "켰는데 안 깔림" 또는 "껐는데 계속 돎"이 된다 (#567).
|
|
5
|
+
// 새 조건부 폴더를 만들면 여기에 한 줄을 넣는다. 세 곳이 함께 따라온다.
|
|
6
|
+
import { readdirSync } from "node:fs";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { exists } from "./fsutil.js";
|
|
9
|
+
|
|
10
|
+
// 서버 배포(<type>/server-deploy/)는 docker-ssh 일 때만 깐다 (#439)
|
|
11
|
+
export const SERVER_DEPLOY_DIR = "server-deploy";
|
|
12
|
+
export function serverDeployEnabled(deployTarget) {
|
|
13
|
+
return (deployTarget || "docker-ssh") === "docker-ssh";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// common/ 아래 선택형 폴더 목록. 순서가 복사 순서다 (기록·요약에 나오는 순서를 바꾸지 않는다).
|
|
17
|
+
// policy: "bak-replace" — 템플릿과 다르면 .bak 으로 남기고 교체
|
|
18
|
+
// "new-only" — 이미 있으면 건드리지 않는다 (secret-backup: 서버 경로를 사용자가 고쳐 쓴다)
|
|
19
|
+
// copyLabel / orphanLabel — trace 그룹 이름과 고아 안내 이름. 기존 기록과 같게 둔다.
|
|
20
|
+
export function commonOptionalGroups(commonDir, opts = {}) {
|
|
21
|
+
const { includeSecretBackup = false, aiPrSummary = true, projectsSync = true, deployTarget = "docker-ssh" } = opts;
|
|
22
|
+
const selectedDeploy = deployTarget || "docker-ssh";
|
|
23
|
+
const groups = [];
|
|
24
|
+
|
|
25
|
+
// deploy/<target> — 타입 비종속 배포 타겟(vercel 등). 고른 타겟 폴더만 켠다.
|
|
26
|
+
const deployRoot = join(commonDir, "deploy");
|
|
27
|
+
const deployNames = exists(deployRoot)
|
|
28
|
+
? readdirSync(deployRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name)
|
|
29
|
+
: [];
|
|
30
|
+
if (!deployNames.includes(selectedDeploy)) deployNames.push(selectedDeploy); // 폴더가 없으면 아래에서 exists 로 걸러진다
|
|
31
|
+
for (const name of deployNames) {
|
|
32
|
+
groups.push({ dir: join("deploy", name), enabled: name === selectedDeploy, policy: "bak-replace",
|
|
33
|
+
copyLabel: "common-deploy", orphanLabel: `deploy/${name}` });
|
|
34
|
+
}
|
|
35
|
+
groups.push(
|
|
36
|
+
// #566 — AI 변경 요약. 설정 없이 바로 동작
|
|
37
|
+
{ dir: "pr-summary", enabled: aiPrSummary === true, policy: "bak-replace", copyLabel: "pr-summary", orphanLabel: "pr-summary" },
|
|
38
|
+
// #716 — Projects 보드 동기화. Secret·변수를 등록해야 동작해 신규는 기본 꺼짐
|
|
39
|
+
{ dir: "projects-sync", enabled: projectsSync === true, policy: "bak-replace", copyLabel: "projects-sync", orphanLabel: "projects-sync" },
|
|
40
|
+
// Secret 서버 백업
|
|
41
|
+
{ dir: "secret-backup", enabled: includeSecretBackup === true, policy: "new-only", copyLabel: "secret-backup", orphanLabel: "secret-backup" },
|
|
42
|
+
);
|
|
43
|
+
return groups;
|
|
44
|
+
}
|
package/src/index.js
CHANGED
|
@@ -19,16 +19,13 @@ import { detectOrphanWorkflows } from "./core/orphan-workflows.js";
|
|
|
19
19
|
import { createRunTrace, MIGRATION_DIR } from "./core/run-trace.js";
|
|
20
20
|
import { appendGuideEntry } from "./core/migration-guide.js";
|
|
21
21
|
import { resolveProjectPaths, markerForType } from "./core/paths-resolve.js";
|
|
22
|
-
import {
|
|
23
|
-
import { DEFAULT_CODE_REVIEW_CODERABBIT } from "./core/constants.js";
|
|
22
|
+
import { resolveOptions } from "./core/resolve-options.js";
|
|
24
23
|
import { printBannerCompact } from "./ui/banner.js";
|
|
25
24
|
import { printSummary } from "./ui/summary.js";
|
|
26
25
|
import { runFull } from "./commands/full.js";
|
|
27
26
|
import { runVersion } from "./commands/version.js";
|
|
28
27
|
import { runWorkflows } from "./commands/workflows.js";
|
|
29
28
|
import { runIssues } from "./commands/issues.js";
|
|
30
|
-
import { resolveLabelStyle } from "./core/label-style.js";
|
|
31
|
-
import { resolveRepoLanguage } from "./core/repo-language.js";
|
|
32
29
|
import { runInteractive } from "./commands/interactive.js";
|
|
33
30
|
import { runSkills } from "./commands/skills.js";
|
|
34
31
|
|
|
@@ -191,86 +188,38 @@ async function runCore(argv, { cwd = process.cwd(), source = { type: "git" }, cl
|
|
|
191
188
|
const { now, today } = clock || utcNow();
|
|
192
189
|
const tempDir = join(cwd, PATHS.tempDir);
|
|
193
190
|
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
// 적용 불가 타겟 조용한 정리 (#498) — 대화형과 동일 규칙. 타입에 성립하지 않는 축 값은
|
|
208
|
-
// 복사 결과가 동일하므로 경고 없이 none/교집합으로 정리한다 (모바일 앱/basic 단독 등).
|
|
209
|
-
const beforeCleanup = { deploy: deployTarget, publish: [...publishTargets] };
|
|
210
|
-
if (deployTarget !== "none" && !applicable.deploy.includes(deployTarget)) deployTarget = "none";
|
|
211
|
-
publishTargets = publishTargets.filter((t) => applicable.publish.includes(t));
|
|
212
|
-
if (types.length > 0 && applicable.deploy.length === 0 && applicable.publish.length === 0) intent = "none";
|
|
191
|
+
// 옵션 값은 resolve-options.js 한 곳에서 정한다 (#851) — 대화형과 같은 규칙.
|
|
192
|
+
const { values: resolved, sources } = resolveOptions({
|
|
193
|
+
flags: {
|
|
194
|
+
intent: opts.intent, deployTarget: opts.deployTarget, publishTargets: opts.publishTargets,
|
|
195
|
+
includeSecretBackup: opts.includeSecretBackup, aiPrSummary: opts.aiPrSummary, projectsSync: opts.projectsSync,
|
|
196
|
+
deployBranch: opts.deployBranch, codeReviewCoderabbit: opts.codeReviewCoderabbit,
|
|
197
|
+
language: opts.language, labelStyle: opts.labelStyle,
|
|
198
|
+
},
|
|
199
|
+
existing, types, cwd,
|
|
200
|
+
});
|
|
201
|
+
const { intent, deployTarget, publishTargets } = resolved;
|
|
202
|
+
const { before: beforeCleanup, applicable } = sources;
|
|
213
203
|
|
|
214
204
|
// 축 확정 근거 (#561) — "왜 이 값인가"가 가장 헷갈리는 자리다.
|
|
215
205
|
// CLI 플래그 / 저장값 / intent 유도 / 타입 적용성 정리 중 무엇이 이겼는지 남긴다.
|
|
216
|
-
trace.event("resolve", "intent", String(intent ?? "(unset)"), {
|
|
217
|
-
source: opts.intent != null ? "cli-flag(--intent)"
|
|
218
|
-
: (existing?.options?.intent ? "version.yml(stored value)" : "not given, inferred from deploy/publish"),
|
|
219
|
-
});
|
|
206
|
+
trace.event("resolve", "intent", String(intent ?? "(unset)"), { source: sources.intent });
|
|
220
207
|
trace.event("resolve", "deploy", deployTarget, {
|
|
221
|
-
source:
|
|
222
|
-
: (existing?.options?.deploy ? "version.yml(stored value)" : "default"),
|
|
208
|
+
source: sources.deploy,
|
|
223
209
|
applicableForTypes: applicable.deploy,
|
|
224
210
|
adjusted: beforeCleanup.deploy !== deployTarget
|
|
225
211
|
? `${beforeCleanup.deploy} → ${deployTarget} (not applicable to the selected types)` : null,
|
|
226
212
|
});
|
|
227
213
|
trace.event("resolve", "publish", publishTargets.join(",") || "(none)", {
|
|
228
|
-
source:
|
|
229
|
-
: (existing?.options?.publish ? "version.yml(stored value)" : "default"),
|
|
214
|
+
source: sources.publish,
|
|
230
215
|
applicableForTypes: applicable.publish,
|
|
231
216
|
adjusted: beforeCleanup.publish.join(",") !== publishTargets.join(",")
|
|
232
217
|
? `${beforeCleanup.publish.join(",") || "(none)"} → ${publishTargets.join(",") || "(none)"} (cleaned up, not applicable)` : null,
|
|
233
218
|
});
|
|
234
219
|
|
|
235
220
|
const context = createContext({
|
|
236
|
-
mode: opts.mode, force: true, types, version, versionCode, branch,
|
|
237
|
-
|
|
238
|
-
deployTarget,
|
|
239
|
-
publishTargets,
|
|
240
|
-
includeSecretBackup: opts.includeSecretBackup ?? existing?.options?.secretBackup ?? false,
|
|
241
|
-
// #566 — CLI 값 우선, 없으면 저장값 보존, 그것도 없으면 true(설정 없이 바로 동작).
|
|
242
|
-
aiPrSummary: opts.aiPrSummary ?? existing?.options?.aiPrSummary ?? true,
|
|
243
|
-
// Projects 보드 동기화(#716): CLI > 저장값 > 이미 설치돼 있으면 유지 > 신규는 제외.
|
|
244
|
-
// 설정(Secret, PROJECT_URL)이 없는 레포에 설치되면 라벨이 바뀔 때마다 알림만 쌓이기 때문이다.
|
|
245
|
-
// 이미 설치된 레포가 갱신만으로 연동을 잃지 않도록 파일이 있으면 켜진 것으로 본다.
|
|
246
|
-
projectsSync: opts.projectsSync ?? existing?.options?.projectsSync
|
|
247
|
-
?? existsSync(join(cwd, ".github/workflows/PROJECT-COMMON-PROJECTS-SYNC-MANAGER.yaml")),
|
|
248
|
-
// #502 — version 모드가 기존 full 통합 기록(mode)을 강등하지 않도록 (full이 우세)
|
|
249
|
-
recordMode: existing?.templateMode === "full" ? "full" : "version",
|
|
250
|
-
// 릴리스 배포 브랜치(#456): CLI 플래그 → version.yml 저장값 → 빈 값(미출력, 스킬이 develop 폴백)
|
|
251
|
-
deployBranch: opts.deployBranch || existing?.options?.deployBranch || "",
|
|
252
|
-
intent,
|
|
253
|
-
// changelog/code_review 축(#455): 비대화형은 저장값 → 기본값. null이 흘러 provider:"null"로 기록되던 버그 수정.
|
|
254
|
-
changelogProvider: migrateProvider(existing?.options?.changelogProvider) ?? "commit",
|
|
255
|
-
changelogBaseUrl: existing?.options?.changelogBaseUrl ?? "",
|
|
256
|
-
// 플래그 > 저장값 > 기본(꺼짐). 쓰겠다고 명시하면 켜지고, 아무 말이 없을 때만 기본값이 적용된다.
|
|
257
|
-
codeReviewCoderabbit: opts.codeReviewCoderabbit ?? existing?.options?.codeReviewCoderabbit ?? DEFAULT_CODE_REVIEW_CODERABBIT,
|
|
258
|
-
// semver 자동 승격(#546, #814): 저장값(명시적 false 포함)은 존중하고, 키가 없으면 신규·기존 모두 켠다.
|
|
259
|
-
// 기존 레포에서 처음 켜질 때는 버전이 예고 없이 오르지 않게 완료 화면이 알린다 (semverAutoNewlyOn).
|
|
260
|
-
semverAuto: existing?.options?.semverAuto ?? true,
|
|
261
|
-
// 앱 심사 배포 레포 여부(#553): 저장값만 보존한다. 마법사가 묻지 않으므로 새로 켜지 않는다
|
|
262
|
-
// (사용자가 version.yml에 직접 쓰거나 스킬이 기록한 값을 그대로 유지).
|
|
263
|
-
appRelease: existing?.options?.appRelease ?? null,
|
|
264
|
-
excludedWorkflows: existing?.options?.excludedWorkflows ?? null, // #810 저장값 보존
|
|
265
|
-
storeLocales: existing?.options?.storeLocales ?? null, // #829 저장값 보존 (전체 재생성이라 안 넘기면 사라진다)
|
|
266
|
-
storeLocalesIos: existing?.options?.storeLocalesIos ?? null,
|
|
267
|
-
storeLocalesPlay: existing?.options?.storeLocalesPlay ?? null,
|
|
268
|
-
// 상태 라벨 표기(#776): 플래그 > 저장값 > (신규 en / 기존 ko). 기존 레포의 라벨 이름은 업데이트만으로 바뀌지 않는다.
|
|
269
|
-
// 릴리스 시 완료 이슈 닫기(#771): 저장값 보존, 없으면 신규만 true. 기존 레포는 키를 만들지 않아 현행 유지.
|
|
270
|
-
closeOnRelease: existing?.options?.closeOnRelease ?? (existing ? null : true),
|
|
271
|
-
// 레포 문구 언어(#769): 플래그 > 저장값 > (신규 en / 기존 ko). 기존 레포의 템플릿 언어는 업데이트만으로 바뀌지 않는다.
|
|
272
|
-
language: resolveRepoLanguage({ flag: opts.language, stored: existing?.options?.language, existing: !!existing }),
|
|
273
|
-
labelStyle: resolveLabelStyle({ flag: opts.labelStyle, stored: existing?.options?.labelStyle, existing: !!existing }),
|
|
221
|
+
mode: opts.mode, force: true, types, version, versionCode, branch, paths,
|
|
222
|
+
...resolved,
|
|
274
223
|
repoName,
|
|
275
224
|
// 실 resolver 4종 (.sh resolve_token 등가 — spring-app-yml 스텁 제거)
|
|
276
225
|
resolvers: makeResolvers(cwd, repoName, paths),
|
|
@@ -382,7 +331,7 @@ async function runCore(argv, { cwd = process.cwd(), source = { type: "git" }, cl
|
|
|
382
331
|
replacedBak: result?.workflows?.replacedBak ?? [], // #673 기준점 없이 교체한 파일 안내
|
|
383
332
|
legacyLeftover: migrationsResult?.confirmPending ?? [], // #809 신·구 세대 동시 실행 안내
|
|
384
333
|
notInstalled: result?.workflows?.notInstalled ?? [], // #810 깔지 않은 워크플로우 안내
|
|
385
|
-
semverAutoNewlyOn:
|
|
334
|
+
semverAutoNewlyOn: sources.semverAutoNewlyOn && context.semverAuto === true, // 업데이트가 켠 사실 안내
|
|
386
335
|
verification: result?.verification, // #549 설치 후 검증 결과 (full/workflows 모드에서만 존재)
|
|
387
336
|
// #569 — 고른 것만 안내하려면 선택값이 필요하다
|
|
388
337
|
aiPrSummary: context.aiPrSummary, codeReviewCoderabbit: context.codeReviewCoderabbit,
|