projectops 4.2.45 → 4.2.47
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 +4 -4
- package/package.json +1 -1
- package/src/commands/full.js +2 -2
- package/src/commands/interactive.js +5 -2
- package/src/commands/version.js +2 -2
- package/src/context.js +1 -0
- package/src/core/migration-guide.js +1 -1
- package/src/core/version-yml.js +21 -5
- package/src/index.js +4 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
> 이슈 등록부터 커밋, 보고서, 배포까지. 개발자는 코드만 작성하세요.
|
|
8
8
|
|
|
9
9
|
<!-- AUTO-VERSION-SECTION: DO NOT EDIT MANUALLY -->
|
|
10
|
-
## 최신 버전 : v4.2.
|
|
10
|
+
## 최신 버전 : v4.2.46 (2026-09-15)
|
|
11
11
|
|
|
12
12
|
[전체 버전 기록 보기](CHANGELOG.md)
|
|
13
13
|
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
| 기존 방식 | Projectops |
|
|
26
26
|
|----------|---------------------|
|
|
27
|
-
| 버전 수동 관리, 태그 직접 생성 | 릴리스
|
|
27
|
+
| 버전 수동 관리, 태그 직접 생성 | 릴리스 시 커밋 내용에 맞는 버전 자동 증가 + 태그 생성 |
|
|
28
28
|
| 체인지로그 직접 작성 (30분+) | CodeRabbit AI가 PR마다 자동 생성 |
|
|
29
29
|
| CI/CD 처음부터 설정 | 프로젝트 타입별 워크플로우 즉시 구성 |
|
|
30
30
|
| 이슈 매번 형식 맞춰 작성 (5분+) | `/pro-github` 한 번에 표준 템플릿 생성 + 등록 |
|
|
@@ -67,7 +67,7 @@ flowchart TD
|
|
|
67
67
|
flowchart TD
|
|
68
68
|
A([develop 푸시]) --> B[개발 통합]
|
|
69
69
|
B --> C[develop→main 릴리스 PR]
|
|
70
|
-
C --> D["PR 내 버전 확정<br
|
|
70
|
+
C --> D["PR 내 버전 확정<br/>커밋 기반 승격 + 태그 + AI 체인지로그"]
|
|
71
71
|
D --> E[자동 머지]
|
|
72
72
|
E --> F["main push → CI/CD 배포<br/>Flutter / Spring / React 등"]
|
|
73
73
|
F --> G([완료])
|
|
@@ -131,7 +131,7 @@ npx projectops --mode skills
|
|
|
131
131
|
| 기능 | 설명 | 문서 |
|
|
132
132
|
|------|------|------|
|
|
133
133
|
| **Agent Skills** | Claude Code, Cursor, Gemini CLI, Codex CLI에서 쓰는 17종 AI DevOps Skills | [상세](docs/SKILLS.md) |
|
|
134
|
-
| **버전 자동화** | 릴리스
|
|
134
|
+
| **버전 자동화** | 릴리스 시 커밋 제목으로 major/minor/patch 판정 + Git 태그 | [상세](docs/VERSION-CONTROL.md) |
|
|
135
135
|
| **AI 체인지로그** | provider 사다리(CodeRabbit/GitHub Models/OpenAI 계열/commit) 기반 CHANGELOG 자동 생성 | [상세](docs/CHANGELOG-AUTOMATION.md) |
|
|
136
136
|
| **PR Preview** | 댓글 한 줄로 임시 서버 배포, 닫으면 자동 삭제 | [상세](docs/PR-PREVIEW.md) |
|
|
137
137
|
| **이슈 자동화** | 브랜치명/커밋 메시지 자동 제안, QA 이슈 생성 | [상세](docs/ISSUE-AUTOMATION.md) |
|
package/package.json
CHANGED
package/src/commands/full.js
CHANGED
|
@@ -24,7 +24,7 @@ export function runFull(context, tempDir, targetRoot = ".", hooks = {}) {
|
|
|
24
24
|
force = true, now, today, templateVersion = "unknown",
|
|
25
25
|
deployTarget = "docker-ssh", publishTargets = [], includeSecretBackup = false,
|
|
26
26
|
changelogProvider = "github-ai", changelogBaseUrl = "", codeReviewCoderabbit = true,
|
|
27
|
-
deployBranch = "", intent = null } = context;
|
|
27
|
+
deployBranch = "", intent = null, semverAuto = true } = context;
|
|
28
28
|
|
|
29
29
|
// project_paths 마커 계산 (.sh existing_marker_in_dir 등가 — 대표 마커명)
|
|
30
30
|
const pathMarkers = new Map();
|
|
@@ -41,7 +41,7 @@ export function runFull(context, tempDir, targetRoot = ".", hooks = {}) {
|
|
|
41
41
|
version, types, paths, pathMarkers, branch, deployBranch, versionCode, now, today,
|
|
42
42
|
deployValues,
|
|
43
43
|
templateOptions: { templateVersion, deployTarget, publishTargets, includeSecretBackup, optionsDate: today,
|
|
44
|
-
changelogProvider, changelogBaseUrl, codeReviewCoderabbit, intent, mode: "full" },
|
|
44
|
+
changelogProvider, changelogBaseUrl, codeReviewCoderabbit, intent, mode: "full", semverAuto },
|
|
45
45
|
}));
|
|
46
46
|
|
|
47
47
|
// 2. README 버전 섹션
|
|
@@ -110,6 +110,9 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
|
|
|
110
110
|
let deployBranchReady = null; // #490 — 이번 실행에서 개발 브랜치 존재/생성이 확인됐는지
|
|
111
111
|
let deployBranchCreated = null; // #493 — 마법사가 직접 생성했는지 (가이드 기록용)
|
|
112
112
|
let intent = existing?.options?.intent ?? null; // #485 프로젝트 성격
|
|
113
|
+
// semver 자동 승격(#546) — 질문하지 않는다(#485 질문 부담 축소 방향 유지).
|
|
114
|
+
// 저장값이 있으면 보존, 없으면 신규 통합만 ON. 기존 레포는 업데이트만으로 버전이 튀지 않는다.
|
|
115
|
+
const semverAuto = existing?.options?.semverAuto ?? (existing ? false : true);
|
|
113
116
|
const showOptional = mode === "full" || mode === "workflows";
|
|
114
117
|
const realTty = process.stdout.isTTY === true;
|
|
115
118
|
|
|
@@ -278,7 +281,7 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
|
|
|
278
281
|
const { now, today } = clock || utcNow();
|
|
279
282
|
const ctx = createContext({
|
|
280
283
|
mode, force: true, types, version, versionCode, branch, paths, deployTarget, publishTargets, includeSecretBackup,
|
|
281
|
-
codeReviewCoderabbit, changelogProvider, changelogBaseUrl, deployBranch, intent,
|
|
284
|
+
codeReviewCoderabbit, changelogProvider, changelogBaseUrl, deployBranch, intent, semverAuto,
|
|
282
285
|
repoName, templateVersion, resolvers, envValues, envUseDefaults, now, today,
|
|
283
286
|
// #502 — version 모드가 기존 full 기록을 강등하지 않도록 (full이 우세)
|
|
284
287
|
recordMode: existing?.templateMode === "full" ? "full" : "version",
|
|
@@ -366,7 +369,7 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
|
|
|
366
369
|
migrationGuidePath = appendGuideEntry(cwd, {
|
|
367
370
|
now, mode, types, repoName,
|
|
368
371
|
templateFrom: existing?.templateVersion || "", templateTo: templateVersion,
|
|
369
|
-
options: { deploy: deployTarget, publish: publishTargets, secretBackup: includeSecretBackup, coderabbit: codeReviewCoderabbit, changelogProvider, intent },
|
|
372
|
+
options: { deploy: deployTarget, publish: publishTargets, secretBackup: includeSecretBackup, coderabbit: codeReviewCoderabbit, changelogProvider, intent, semverAuto },
|
|
370
373
|
branches: { defaultBranch: branch, deployBranch, ready: deployBranchReady, created: deployBranchCreated },
|
|
371
374
|
breaking: breakingReport, migrations: migrationsResult, orphans: orphanReport,
|
|
372
375
|
events: trace.events, counters: { skipped: result?.workflows?.skipped ?? 0 },
|
package/src/commands/version.js
CHANGED
|
@@ -14,7 +14,7 @@ export function runVersion(context, tempDir, targetRoot = ".") {
|
|
|
14
14
|
const { version, types = [], paths = new Map(), branch = "main", versionCode = 1,
|
|
15
15
|
now, today, templateVersion = "unknown", deployTarget = "docker-ssh", publishTargets = [], includeSecretBackup = false,
|
|
16
16
|
changelogProvider = "github-ai", changelogBaseUrl = "", codeReviewCoderabbit = true,
|
|
17
|
-
deployBranch = "", recordMode = "version" } = context;
|
|
17
|
+
deployBranch = "", recordMode = "version", semverAuto = true } = context;
|
|
18
18
|
|
|
19
19
|
const pathMarkers = new Map();
|
|
20
20
|
for (const [t] of paths) pathMarkers.set(t, markerForType(t));
|
|
@@ -25,7 +25,7 @@ export function runVersion(context, tempDir, targetRoot = ".") {
|
|
|
25
25
|
// mode(#502): version 모드가 기존 full 통합 기록을 "version"으로 강등하지 않도록
|
|
26
26
|
// 호출부가 recordMode로 기존 값을 넘긴다 (full이 우세 — 업데이트 재실행 범위 축소 방지).
|
|
27
27
|
templateOptions: { templateVersion, deployTarget, publishTargets, includeSecretBackup, optionsDate: today,
|
|
28
|
-
changelogProvider, changelogBaseUrl, codeReviewCoderabbit, mode: recordMode },
|
|
28
|
+
changelogProvider, changelogBaseUrl, codeReviewCoderabbit, mode: recordMode, semverAuto },
|
|
29
29
|
}));
|
|
30
30
|
addVersionSectionToReadme(version, targetRoot);
|
|
31
31
|
copyScripts(tempDir, targetRoot);
|
package/src/context.js
CHANGED
|
@@ -25,6 +25,7 @@ export function createContext(overrides = {}) {
|
|
|
25
25
|
codeReviewCoderabbit: null,
|
|
26
26
|
deployBranch: "", // 릴리스 PR head 브랜치 (#456). 빈 값=metadata.deploy_branch 미출력
|
|
27
27
|
intent: null, // 프로젝트 성격 (#485 — app/library/both/none/manual). null=미설정(역추론)
|
|
28
|
+
semverAuto: null, // semver 자동 승격 (#546). null=미설정(신규 통합 true / 기존 레포 false)
|
|
28
29
|
templateVersion: "",
|
|
29
30
|
tempDir: "",
|
|
30
31
|
deployValues: new Map(), // "type.KEY" -> value
|
|
@@ -156,7 +156,7 @@ export function renderGuideEntry(report) {
|
|
|
156
156
|
L.push(`template: { from: ${yq(from)}, to: ${yq(to)} }`);
|
|
157
157
|
L.push(`mode: ${r.mode || "full"}`);
|
|
158
158
|
L.push(`types: ${ylist(r.types)}`);
|
|
159
|
-
L.push(`options: { deploy: ${yq(r.options?.deploy ?? "")}, publish: ${ylist(r.options?.publish)}, secret_backup: ${r.options?.secretBackup === true}, coderabbit: ${r.options?.coderabbit === true}, changelog_provider: ${yq(r.options?.changelogProvider ?? "")}, intent: ${yq(r.options?.intent ?? "")} }`);
|
|
159
|
+
L.push(`options: { deploy: ${yq(r.options?.deploy ?? "")}, publish: ${ylist(r.options?.publish)}, secret_backup: ${r.options?.secretBackup === true}, coderabbit: ${r.options?.coderabbit === true}, changelog_provider: ${yq(r.options?.changelogProvider ?? "")}, intent: ${yq(r.options?.intent ?? "")}, semver_auto: ${r.options?.semverAuto === true} }`);
|
|
160
160
|
L.push(`branches: { default: ${yq(r.branches?.defaultBranch ?? "main")}, deploy: ${yq(r.branches?.deployBranch ?? "develop")}, deploy_branch_created: ${r.branches?.created === true} }`);
|
|
161
161
|
L.push("workflows:");
|
|
162
162
|
L.push(` added: ${ylist(wf.added)}`);
|
package/src/core/version-yml.js
CHANGED
|
@@ -16,9 +16,14 @@ const HEADER = `# ==============================================================
|
|
|
16
16
|
# 4. project_paths: 타입별 프로젝트 폴더 (레포 루트 기준 상대경로, 모노레포용)
|
|
17
17
|
#
|
|
18
18
|
# 자동 버전 업데이트:
|
|
19
|
-
# -
|
|
20
|
-
# -
|
|
21
|
-
#
|
|
19
|
+
# - version_code: 매 빌드마다 자동으로 1씩 증가 (아래 판정과 무관)
|
|
20
|
+
# - 버전 승격 폭은 metadata.template.options.semver_auto 가 정합니다
|
|
21
|
+
# semver_auto: true -> 릴리스 구간 커밋 제목으로 판정
|
|
22
|
+
# "제목 : feat! : 내용" 또는 "feat!:" -> major (x+1.0.0)
|
|
23
|
+
# "제목 : feat : 내용" 또는 "feat:" -> minor (x.y+1.0)
|
|
24
|
+
# 그 외(fix/docs/chore/refactor/test) -> patch (x.y.z+1)
|
|
25
|
+
# 키 없음 / false -> 항상 patch +1
|
|
26
|
+
# - 위 판정과 무관하게 version 값을 직접 수정해도 됩니다
|
|
22
27
|
#
|
|
23
28
|
# 프로젝트 타입별 동기화 파일:
|
|
24
29
|
# - spring: build.gradle (version = "x.y.z")
|
|
@@ -52,7 +57,7 @@ const HEADER = `# ==============================================================
|
|
|
52
57
|
export function parseTemplateOptions(content) {
|
|
53
58
|
const out = { deploy: null, publish: null, secretBackup: null,
|
|
54
59
|
changelogProvider: null, changelogBaseUrl: null, codeReviewCoderabbit: null,
|
|
55
|
-
deployBranch: null, intent: null };
|
|
60
|
+
deployBranch: null, intent: null, semverAuto: null };
|
|
56
61
|
// deploy_branch는 metadata 직속(#456) — template.options 밖이라 별도로 스캔한다.
|
|
57
62
|
for (const line of String(content || "").split("\n")) {
|
|
58
63
|
if (line.startsWith("#")) continue;
|
|
@@ -118,6 +123,14 @@ export function parseTemplateOptions(content) {
|
|
|
118
123
|
if (v === "false") out.secretBackup = false;
|
|
119
124
|
continue;
|
|
120
125
|
}
|
|
126
|
+
// semver 자동 승격(#546). null(미기재)은 "기존 통합 레포" 신호 — 호출부가 OFF로 해석한다.
|
|
127
|
+
// 캡처를 true|false로 한정한다(intent와 같은 방식) — 값 뒤 인라인 주석이 값에 딸려오면
|
|
128
|
+
// strip()은 따옴표·공백만 걷어내므로 "true # ..."가 되어 매칭이 조용히 실패한다.
|
|
129
|
+
m = line.match(/^\s+semver_auto:\s*["']?(true|false)["']?/);
|
|
130
|
+
if (m) {
|
|
131
|
+
out.semverAuto = m[1] === "true";
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
121
134
|
m = line.match(/^\s+npm_publish:\s*(.+)/);
|
|
122
135
|
if (m) {
|
|
123
136
|
const v = strip(m[1]);
|
|
@@ -294,7 +307,8 @@ export function buildVersionYml({ version, types = [], paths = new Map(), pathMa
|
|
|
294
307
|
// template 옵션 블록 (.sh save_template_options 신규 추가 케이스). templateOptions 지정 시.
|
|
295
308
|
if (templateOptions) {
|
|
296
309
|
const { templateVersion = "unknown", deployTarget = "docker-ssh", publishTargets = [], includeSecretBackup = false, optionsDate = today,
|
|
297
|
-
changelogProvider = "github-ai", changelogBaseUrl = "", codeReviewCoderabbit = true, intent = null, mode = null
|
|
310
|
+
changelogProvider = "github-ai", changelogBaseUrl = "", codeReviewCoderabbit = true, intent = null, mode = null,
|
|
311
|
+
semverAuto = true } = templateOptions;
|
|
298
312
|
const publishJson = `[${publishTargets.map((t) => `"${t}"`).join(",")}]`;
|
|
299
313
|
// intent(프로젝트 성격, #485) — 미지정이면 deploy/publish에서 역추론해 기록 (재통합 시 진입 질문 생략용)
|
|
300
314
|
const intentVal = intent || inferIntent(deployTarget, publishTargets) || "manual";
|
|
@@ -310,6 +324,8 @@ export function buildVersionYml({ version, types = [], paths = new Map(), pathMa
|
|
|
310
324
|
out += ` deploy: "${deployTarget}"\n`;
|
|
311
325
|
out += ` publish: ${publishJson}\n`;
|
|
312
326
|
out += ` secret_backup: ${includeSecretBackup}\n`;
|
|
327
|
+
// semver 자동 승격(#546) — 릴리스 시 커밋 제목으로 major/minor/patch 결정. false면 항상 patch.
|
|
328
|
+
out += ` semver_auto: ${semverAuto} # 커밋 제목으로 버전 승격 폭 결정 (false면 항상 patch)\n`;
|
|
313
329
|
out += ` code_review:\n`;
|
|
314
330
|
out += ` coderabbit: ${codeReviewCoderabbit}\n`;
|
|
315
331
|
out += ` changelog:\n`;
|
package/src/index.js
CHANGED
|
@@ -141,6 +141,9 @@ export async function run(argv, { cwd = process.cwd(), source = { type: "git" },
|
|
|
141
141
|
changelogProvider: existing?.options?.changelogProvider ?? "github-ai",
|
|
142
142
|
changelogBaseUrl: existing?.options?.changelogBaseUrl ?? "",
|
|
143
143
|
codeReviewCoderabbit: existing?.options?.codeReviewCoderabbit ?? false,
|
|
144
|
+
// semver 자동 승격(#546): 저장값 → (기존 통합 레포면 false / 신규면 true).
|
|
145
|
+
// 이미 통합된 레포의 버전이 업데이트만으로 예고 없이 minor로 튀지 않게 하는 안전장치다.
|
|
146
|
+
semverAuto: existing?.options?.semverAuto ?? (existing ? false : true),
|
|
144
147
|
repoName,
|
|
145
148
|
// 실 resolver 4종 (.sh resolve_token 등가 — spring-app-yml 스텁 제거)
|
|
146
149
|
resolvers: makeResolvers(cwd, repoName, paths),
|
|
@@ -206,7 +209,7 @@ export async function run(argv, { cwd = process.cwd(), source = { type: "git" },
|
|
|
206
209
|
migrationGuidePath = appendGuideEntry(cwd, {
|
|
207
210
|
now, mode: opts.mode, types, repoName,
|
|
208
211
|
templateFrom: existing?.templateVersion || "", templateTo: context.templateVersion,
|
|
209
|
-
options: { deploy: deployTarget, publish: publishTargets, secretBackup: context.includeSecretBackup, coderabbit: context.codeReviewCoderabbit, changelogProvider: context.changelogProvider, intent },
|
|
212
|
+
options: { deploy: deployTarget, publish: publishTargets, secretBackup: context.includeSecretBackup, coderabbit: context.codeReviewCoderabbit, changelogProvider: context.changelogProvider, intent, semverAuto: context.semverAuto },
|
|
210
213
|
branches: { defaultBranch: branch, deployBranch: context.deployBranch || "develop", ready: null, created: null },
|
|
211
214
|
breaking: breakingReport, migrations: migrationsResult, orphans: { cleaned: [], pending: orphanPending },
|
|
212
215
|
events: trace.events, counters: { skipped: result?.workflows?.skipped ?? 0 },
|