projectops 4.40.0 → 4.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/package.json +1 -1
- package/src/cli/args.js +3 -0
- package/src/cli/help.js +141 -60
- package/src/commands/doctor.js +4 -1
- package/src/commands/full.js +5 -3
- package/src/commands/interactive.js +5 -3
- package/src/commands/version.js +5 -3
- package/src/context.js +1 -0
- package/src/core/constants.js +7 -0
- package/src/core/copy/simple.js +2 -0
- package/src/core/copy/workflows.js +8 -5
- package/src/core/exclusions.js +1 -1
- package/src/core/migrations/registry.js +2 -1
- package/src/core/options-ask.js +2 -2
- package/src/core/options-schema.js +218 -2
- package/src/core/version-yml.js +98 -3
- package/src/i18n/doctor.en.js +1 -0
- package/src/i18n/doctor.ko.js +1 -0
- package/src/index.js +5 -2
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.41.0 (2026-10-10)
|
|
13
13
|
|
|
14
14
|
[View full version history](CHANGELOG.md)
|
|
15
15
|
|
|
@@ -327,6 +327,7 @@ Browse and search everything on the **[docs site](https://cassiiopeia.github.io/
|
|
|
327
327
|
- [Issues](https://github.com/Cassiiopeia/projectops/issues): bug reports and feature requests
|
|
328
328
|
- Finished issues are marked with the `status: done` label and closed automatically when a release is merged (`close_on_release`).
|
|
329
329
|
- [CONTRIBUTING.md](CONTRIBUTING.md): contribution guide
|
|
330
|
+
- [ARCHITECTURE.md](ARCHITECTURE.md): how the code fits together and how to add a flag, option, provider, language or skill
|
|
330
331
|
- [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md): how we expect everyone to behave, and where to report problems
|
|
331
332
|
- [SECURITY.md](SECURITY.md): please report security vulnerabilities privately, not in a public issue
|
|
332
333
|
|
package/package.json
CHANGED
package/src/cli/args.js
CHANGED
|
@@ -33,6 +33,7 @@ export function parseArgs(argv) {
|
|
|
33
33
|
intent: null, // 프로젝트 성격 (#485): --intent app|library|both|none|manual, null=미설정(역추론)
|
|
34
34
|
includeSecretBackup: null,
|
|
35
35
|
aiPrSummary: null, // #566 — AI 변경 요약 워크플로우 포함 여부
|
|
36
|
+
codeReviewCoderabbit: null, // --coderabbit: CodeRabbit 코드 리뷰 설정(.coderabbit.yaml) 설치 여부 (null=미지정 → 저장값, 없으면 꺼짐)
|
|
36
37
|
projectsSync: null, // #716 — Projects 보드 동기화 워크플로우 포함 여부 (null=미지정)
|
|
37
38
|
json: false, // --json: 기계가 읽는 출력 (--mode options)
|
|
38
39
|
removeLegacy: false, // #809 — 구세대 배포 워크플로우(confirm 티어)도 .bak 으로 치운다
|
|
@@ -169,6 +170,8 @@ export function parseArgs(argv) {
|
|
|
169
170
|
case "--secret-backup": result.includeSecretBackup = true; break;
|
|
170
171
|
case "--no-secret-backup": result.includeSecretBackup = false; break;
|
|
171
172
|
// #566 — 비대화형에서도 켜고 끌 수 있어야 한다. 없으면 자동화 환경은 선택권이 없다.
|
|
173
|
+
case "--coderabbit": result.codeReviewCoderabbit = true; break;
|
|
174
|
+
case "--no-coderabbit": result.codeReviewCoderabbit = false; break;
|
|
172
175
|
case "--ai-summary": result.aiPrSummary = true; break;
|
|
173
176
|
case "--no-ai-summary": result.aiPrSummary = false; break;
|
|
174
177
|
case "--projects-sync": result.projectsSync = true; break;
|
package/src/cli/help.js
CHANGED
|
@@ -1,86 +1,167 @@
|
|
|
1
1
|
// --help text in each supported language. English is the default (see src/i18n).
|
|
2
|
+
//
|
|
3
|
+
// 이 화면은 사람보다 AI agent 가 더 많이 읽는다. 그래서 "무엇을 하는가"만이 아니라
|
|
4
|
+
// 언제 질문이 나오는지 · 안 주면 무엇이 되는지 · 레포에서 무엇이 바뀌는지를 적는다.
|
|
5
|
+
// 값 목록과 플래그별 상세는 src/core/options-schema.js 가 정본이고 `--mode options --json` 이 그대로 출력한다.
|
|
6
|
+
// test/cli-docs-sync.test.js 가 args.js 의 모든 플래그가 영·한 두 본문에 있는지 검사한다 — 새 플래그는 둘 다에 적는다.
|
|
2
7
|
import { getLang } from "../i18n/index.js";
|
|
3
8
|
|
|
4
|
-
const HELP_EN = `projectops — GitHub
|
|
9
|
+
const HELP_EN = `projectops — installs and updates GitHub automation (versioning, release notes, CI/CD, issue templates, agent skills)
|
|
5
10
|
|
|
6
11
|
Usage:
|
|
7
12
|
npx projectops [options]
|
|
8
13
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
-
|
|
14
|
+
An AI agent or CI job should always run: npx projectops --mode <mode> --force [flags]
|
|
15
|
+
and read the full option table first: npx projectops --mode options --json
|
|
16
|
+
|
|
17
|
+
How it decides (read this before scripting)
|
|
18
|
+
- No --mode, or --mode interactive: asks questions in the terminal. Needs a TTY; fails without one.
|
|
19
|
+
- Any other --mode: asks NOTHING. Every value comes from the flags you pass, then the values stored in version.yml,
|
|
20
|
+
then the defaults. Without a TTY, --force is also required.
|
|
21
|
+
- --force: skips every confirmation (including the breaking-changes warning). It never overwrites a file you changed:
|
|
22
|
+
that file is kept and a copy of the new template is saved in .github/.projectops/incoming/.
|
|
23
|
+
- Running the same command twice changes nothing that is already current.
|
|
24
|
+
|
|
25
|
+
Modes (-m, --mode MODE)
|
|
26
|
+
full everything: version.yml, workflows, scripts, issue/PR templates, setup guide (.coderabbit.yaml only with --coderabbit)
|
|
27
|
+
workflows only .github/workflows plus the scripts, config and util they use. version.yml is not rewritten
|
|
28
|
+
version only version.yml, the README version section and the version scripts
|
|
29
|
+
issues only issue and discussion templates
|
|
30
|
+
skills install or update agent skills for supported IDEs
|
|
31
|
+
doctor read-only diagnosis of the install and repository settings. Writes nothing. Exit code 0 even when it finds problems
|
|
32
|
+
options prints every flag and every version.yml key with when-to-use, effect and default (add --json). Writes nothing
|
|
33
|
+
interactive asks questions (default when no --mode is given)
|
|
34
|
+
|
|
35
|
+
Options
|
|
36
|
+
-t, --type CSV project types, comma separated, first = primary (e.g. spring,react,python)
|
|
15
37
|
supported: spring flutter react react-native
|
|
16
38
|
react-native-expo node python basic
|
|
39
|
+
omitted: auto-detected from files in the repo root (nothing found = basic)
|
|
17
40
|
(next was merged into react: use react for Next.js)
|
|
18
|
-
--project-version V
|
|
19
|
-
--paths "t=p,..."
|
|
20
|
-
--intent KIND
|
|
21
|
-
|
|
22
|
-
--label-style STYLE
|
|
23
|
-
--language LANG Language of
|
|
24
|
-
|
|
25
|
-
--
|
|
26
|
-
|
|
27
|
-
--
|
|
28
|
-
--
|
|
29
|
-
|
|
30
|
-
--
|
|
41
|
+
--project-version V initial version, only when version.yml does not exist yet (an existing value is always kept)
|
|
42
|
+
--paths "t=p,..." per-type project folders for monorepos, e.g. flutter=app,react=client. Folders must exist inside the repo
|
|
43
|
+
--intent KIND app | library | both | none | manual. Decides which deploy/publish questions exist.
|
|
44
|
+
omitted: inferred from --deploy/--publish
|
|
45
|
+
--label-style STYLE en = 'status: todo' labels, ko = legacy Korean names. New installs en, existing installs keep ko
|
|
46
|
+
--language LANG en | ko. Language of issue/PR templates, bot comments and version.yml comments written into the repo
|
|
47
|
+
(not the CLI screen — that is --lang). New installs en, existing installs keep the stored value
|
|
48
|
+
--deploy TARGET where the app is deployed, pick one: docker-ssh (SSH + Docker server workflows) | vercel | none
|
|
49
|
+
omitted: docker-ssh for server stacks; mobile types ignore it
|
|
50
|
+
--publish CSV library registries: nexus,npm,github-packages. omitted: none
|
|
51
|
+
--deploy-branch NAME the DEVELOPMENT branch (head of the release PR), default develop. It is NOT the branch that deploys;
|
|
52
|
+
that is the repository default branch. This flag does not create the branch
|
|
53
|
+
--secret-backup / --no-secret-backup upload GitHub Secrets to your server over SSH (needs SERVER_* secrets). omitted: excluded
|
|
54
|
+
--ai-summary / --no-ai-summary AI change summary comment on work PRs. Works with no extra setup. omitted: included
|
|
55
|
+
--coderabbit / --no-coderabbit install .coderabbit.yaml (only --mode full). Needs the CodeRabbit GitHub app. An existing
|
|
56
|
+
file is overwritten and saved as .bak. omitted: off, unless version.yml already stores on.
|
|
57
|
+
This is PR code review only; release notes do not depend on it
|
|
58
|
+
--projects-sync / --no-projects-sync keep a GitHub Projects board's Status in step with issue labels. Needs the secret
|
|
59
|
+
_GITHUB_PAT_TOKEN and variable PROJECT_URL. omitted: off for new installs, kept if installed
|
|
60
|
+
--remove-legacy rename retired old-generation workflows to .bak so they stop running next to their replacements.
|
|
61
|
+
omitted: they stay and are only listed (an old deploy workflow may be your only live deployment)
|
|
31
62
|
--nexus / --npm-publish (deprecated: use --publish nexus / --publish npm)
|
|
32
|
-
--
|
|
33
|
-
--
|
|
34
|
-
--lang en|ko
|
|
35
|
-
-v, --version
|
|
36
|
-
-h, --help
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
63
|
+
--force, -y, --yes skip every confirmation, use defaults for anything not given
|
|
64
|
+
--json with --mode options: JSON instead of text
|
|
65
|
+
--lang en|ko language of the CLI screen (default: system language, en in CI and with --force)
|
|
66
|
+
-v, --version print the projectops version
|
|
67
|
+
-h, --help show this help
|
|
68
|
+
|
|
69
|
+
Where results go
|
|
70
|
+
stderr a completion summary: files kept, retired workflows still installed, workflows not installed and why,
|
|
71
|
+
placeholders still to fill (__NAME__), secrets to register, next steps
|
|
72
|
+
.github/.projectops/logs/ why each decision was made (.log for people, .jsonl for programs)
|
|
73
|
+
.github/.projectops/incoming/ new template copies of files that were kept or not installed
|
|
74
|
+
exit code 0 = completed, 1 = invalid arguments or a failed run (the message says which)
|
|
75
|
+
|
|
76
|
+
Examples
|
|
77
|
+
npx projectops --mode options --json # every option, value, default and effect (read-only)
|
|
78
|
+
npx projectops --mode doctor # diagnose this install (read-only)
|
|
79
|
+
npx projectops --mode full --force # install or update everything, stack auto-detected
|
|
80
|
+
npx projectops --mode full --force --type spring,react # explicit stacks
|
|
81
|
+
npx projectops --mode workflows --force # refresh only workflows, scripts and config
|
|
82
|
+
npx projectops --mode workflows --force --remove-legacy # also retire old-generation workflows
|
|
83
|
+
npx projectops --mode full --force --coderabbit # also install the CodeRabbit review config
|
|
84
|
+
npx projectops --mode workflows --type flutter --paths "flutter=app" --force
|
|
85
|
+
GITHUB_TOKEN=ghp_... npx projectops --mode doctor # include repository settings
|
|
43
86
|
`;
|
|
44
87
|
|
|
45
|
-
const HELP_KO = `projectops — GitHub
|
|
88
|
+
const HELP_KO = `projectops — GitHub 자동화(버전 관리, 릴리스 노트, CI/CD, 이슈 템플릿, agent 스킬)를 설치·업데이트하는 CLI
|
|
46
89
|
|
|
47
90
|
사용법:
|
|
48
91
|
npx projectops [옵션]
|
|
49
92
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
-
|
|
93
|
+
AI agent 나 CI 는 항상 이렇게 실행한다: npx projectops --mode <모드> --force [플래그]
|
|
94
|
+
그리고 먼저 전체 옵션 표를 읽는다: npx projectops --mode options --json
|
|
95
|
+
|
|
96
|
+
어떻게 정해지는가 (스크립트를 짜기 전에 읽는다)
|
|
97
|
+
- --mode 가 없거나 --mode interactive: 터미널에서 질문한다. TTY 가 필요하고 없으면 실패한다.
|
|
98
|
+
- 그 밖의 --mode: **아무것도 묻지 않는다.** 모든 값은 넘긴 플래그 → version.yml 에 저장된 값 → 기본값 순으로 정해진다.
|
|
99
|
+
TTY 가 없으면 --force 도 필요하다.
|
|
100
|
+
- --force: 모든 확인(호환성 변경 경고 포함)을 건너뛴다. 직접 고친 파일은 **덮어쓰지 않는다** —
|
|
101
|
+
그 파일은 그대로 두고 새 템플릿 사본을 .github/.projectops/incoming/ 에 남긴다.
|
|
102
|
+
- 같은 명령을 두 번 돌려도 이미 최신인 것은 바꾸지 않는다.
|
|
103
|
+
|
|
104
|
+
모드 (-m, --mode MODE)
|
|
105
|
+
full 전부: version.yml, 워크플로우, 스크립트, 이슈/PR 템플릿, 설정 안내서 (.coderabbit.yaml 은 --coderabbit 일 때만)
|
|
106
|
+
workflows .github/workflows 와 그것이 쓰는 스크립트·config·util 만. version.yml 은 다시 쓰지 않는다
|
|
107
|
+
version version.yml, README 버전 섹션, 버전 스크립트만
|
|
108
|
+
issues 이슈·디스커션 템플릿만
|
|
109
|
+
skills 지원 IDE 의 agent 스킬 설치·갱신
|
|
110
|
+
doctor 통합 상태와 저장소 설정을 읽기 전용으로 진단. 아무것도 쓰지 않는다. 문제를 찾아도 종료 코드는 0
|
|
111
|
+
options 모든 플래그와 version.yml 키를 언제 쓰는지·효과·기본값과 함께 출력 (--json 추가). 아무것도 쓰지 않는다
|
|
112
|
+
interactive 질문하며 진행 (--mode 를 안 주면 기본)
|
|
113
|
+
|
|
114
|
+
옵션
|
|
115
|
+
-t, --type CSV 프로젝트 타입 csv, 첫 항목이 primary (예: spring,react,python)
|
|
56
116
|
지원: spring flutter react react-native
|
|
57
117
|
react-native-expo node python basic
|
|
118
|
+
생략: 레포 루트의 파일로 자동 감지 (못 찾으면 basic)
|
|
58
119
|
(next는 react로 흡수됨 — Next.js 프로젝트는 react 사용)
|
|
59
|
-
--project-version V
|
|
60
|
-
--paths "t=p,..." 타입별 프로젝트
|
|
61
|
-
--intent KIND
|
|
62
|
-
|
|
63
|
-
--label-style STYLE
|
|
64
|
-
--language LANG 레포에 쓰이는 이슈/PR
|
|
65
|
-
|
|
66
|
-
--
|
|
67
|
-
|
|
68
|
-
--
|
|
69
|
-
--
|
|
70
|
-
|
|
71
|
-
--
|
|
120
|
+
--project-version V 초기 버전. version.yml 이 아직 없을 때만 쓰인다 (기존 값은 항상 유지)
|
|
121
|
+
--paths "t=p,..." 타입별 프로젝트 폴더(모노레포). 예: flutter=app,react=client. 폴더는 레포 안에 있어야 한다
|
|
122
|
+
--intent KIND app | library | both | none | manual. 배포·publish 질문을 어떻게 할지 정한다.
|
|
123
|
+
생략: --deploy/--publish 에서 추정
|
|
124
|
+
--label-style STYLE en = 'status: todo' 라벨, ko = 기존 한글 이름. 신규 en, 기존 설치는 ko 유지
|
|
125
|
+
--language LANG en | ko. 레포에 쓰이는 이슈/PR 템플릿·봇 댓글·version.yml 주석의 언어
|
|
126
|
+
(CLI 화면 언어는 --lang 이다). 신규 en, 기존 설치는 저장값 유지
|
|
127
|
+
--deploy TARGET 앱을 어디에 배포하나, 택1: docker-ssh(SSH + Docker 서버 워크플로우) | vercel | none
|
|
128
|
+
생략: 서버 스택은 docker-ssh, 모바일 타입은 무시
|
|
129
|
+
--publish CSV 라이브러리 레지스트리: nexus,npm,github-packages. 생략: 없음
|
|
130
|
+
--deploy-branch NAME **개발** 브랜치(릴리스 PR 의 head), 기본 develop. 배포가 도는 브랜치가 아니다 —
|
|
131
|
+
그것은 레포 기본 브랜치다. 이 플래그는 브랜치를 만들지 않는다
|
|
132
|
+
--secret-backup / --no-secret-backup GitHub Secret 을 SSH 로 서버에 올린다 (SERVER_* secret 필요). 생략: 제외
|
|
133
|
+
--ai-summary / --no-ai-summary 작업 PR 에 AI 변경 요약 댓글. 추가 설정 없이 동작. 생략: 포함
|
|
134
|
+
--coderabbit / --no-coderabbit .coderabbit.yaml 설치 (--mode full 에서만). CodeRabbit GitHub 앱이 필요하다.
|
|
135
|
+
이미 있으면 덮어쓰고 .bak 으로 백업. 생략: 꺼짐 (version.yml 에 켜짐이 저장돼 있으면 유지).
|
|
136
|
+
PR 코드 리뷰 전용이며 릴리스 노트와 무관하다
|
|
137
|
+
--projects-sync / --no-projects-sync GitHub Projects 보드의 Status 를 이슈 라벨과 맞춘다. secret _GITHUB_PAT_TOKEN 과
|
|
138
|
+
변수 PROJECT_URL 이 필요. 생략: 신규는 제외, 이미 설치돼 있으면 유지
|
|
139
|
+
--remove-legacy 은퇴한 구세대 워크플로우를 .bak 으로 바꿔 신형과 함께 돌지 않게 한다.
|
|
140
|
+
생략: 그대로 두고 목록만 안내한다 (옛 배포 워크플로우가 유일한 현역 배포일 수 있다)
|
|
72
141
|
--nexus / --npm-publish (deprecated — --publish nexus / --publish npm 사용)
|
|
73
|
-
--
|
|
74
|
-
--
|
|
75
|
-
--lang en|ko 화면 언어 (기본: 시스템 언어, CI/--force 는 en
|
|
142
|
+
--force, -y, --yes 모든 확인을 건너뛰고 주지 않은 값은 기본값 사용
|
|
143
|
+
--json --mode options 와 함께: JSON 출력
|
|
144
|
+
--lang en|ko CLI 화면 언어 (기본: 시스템 언어, CI/--force 는 en)
|
|
76
145
|
-v, --version projectops 버전 출력
|
|
77
|
-
-h, --help
|
|
146
|
+
-h, --help 도움말
|
|
147
|
+
|
|
148
|
+
결과는 어디에 남나
|
|
149
|
+
stderr 완료 요약: 유지한 파일, 아직 설치된 은퇴 워크플로우, 설치하지 않은 워크플로우와 이유,
|
|
150
|
+
채워야 할 자리표시자(__NAME__), 등록할 secret, 다음 할 일
|
|
151
|
+
.github/.projectops/logs/ 각 결정의 이유 (.log 사람용, .jsonl 프로그램용)
|
|
152
|
+
.github/.projectops/incoming/ 유지했거나 설치하지 않은 파일의 새 템플릿 사본
|
|
153
|
+
종료 코드 0 = 완료, 1 = 잘못된 인자 또는 실패한 실행 (메시지가 어느 쪽인지 알려 준다)
|
|
78
154
|
|
|
79
|
-
|
|
80
|
-
npx projectops --mode
|
|
81
|
-
npx projectops --mode
|
|
82
|
-
npx projectops --mode
|
|
83
|
-
|
|
155
|
+
예시
|
|
156
|
+
npx projectops --mode options --json # 모든 옵션·값·기본값·효과 (읽기 전용)
|
|
157
|
+
npx projectops --mode doctor # 이 설치를 진단 (읽기 전용)
|
|
158
|
+
npx projectops --mode full --force # 전부 설치·업데이트, 스택 자동 감지
|
|
159
|
+
npx projectops --mode full --force --type spring,react # 스택을 명시
|
|
160
|
+
npx projectops --mode workflows --force # 워크플로우·스크립트·config 만 갱신
|
|
161
|
+
npx projectops --mode workflows --force --remove-legacy # 구세대 워크플로우도 정리
|
|
162
|
+
npx projectops --mode full --force --coderabbit # CodeRabbit 리뷰 설정도 설치
|
|
163
|
+
npx projectops --mode workflows --type flutter --paths "flutter=app" --force
|
|
164
|
+
GITHUB_TOKEN=ghp_... npx projectops --mode doctor # 저장소 설정까지 진단
|
|
84
165
|
`;
|
|
85
166
|
|
|
86
167
|
export function helpText(lang = getLang()) {
|
package/src/commands/doctor.js
CHANGED
|
@@ -68,7 +68,10 @@ export function localChecks(cwd = ".") {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
add({ name: t("doctor.integ.name"), purpose: t("doctor.integ.purpose"), status: "OK",
|
|
71
|
-
|
|
71
|
+
// 템플릿 버전이 기록돼 있지 않으면 "vunknown" 이 아니라 사람이 읽는 문구로 — 옛 설치는 version.yml 에 이 값이 없다
|
|
72
|
+
value: existing.templateVersion
|
|
73
|
+
? t("doctor.integ.versions", { tpl: existing.templateVersion, proj: existing.version })
|
|
74
|
+
: t("doctor.integ.versionsNoTemplate", { proj: existing.version }) });
|
|
72
75
|
|
|
73
76
|
const wfDir = join(cwd, PATHS.workflowsDir);
|
|
74
77
|
const files = existsSync(wfDir)
|
package/src/commands/full.js
CHANGED
|
@@ -6,7 +6,7 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
6
6
|
import { writeText } from "../core/fsutil.js";
|
|
7
7
|
import { PATHS } from "../core/paths.js";
|
|
8
8
|
import { installAgentGuide } from "../core/agent-guide.js";
|
|
9
|
-
import { buildVersionYml, mergeDeployValues, resolveUpdatedBy } from "../core/version-yml.js";
|
|
9
|
+
import { buildVersionYml, mergeDeployValues, resolveUpdatedBy, customYml } from "../core/version-yml.js";
|
|
10
10
|
import { markerForType } from "../core/detect.js";
|
|
11
11
|
import { addVersionSectionToReadme } from "../core/copy/readme.js";
|
|
12
12
|
import { copyWorkflows } from "../core/copy/workflows.js";
|
|
@@ -30,7 +30,7 @@ export function runFull(context, tempDir, targetRoot = ".", hooks = {}) {
|
|
|
30
30
|
force = true, now, today, templateVersion = "unknown",
|
|
31
31
|
deployTarget = "docker-ssh", publishTargets = [], includeSecretBackup = false, aiPrSummary = true,
|
|
32
32
|
changelogProvider = "commit", changelogBaseUrl = "", codeReviewCoderabbit = true,
|
|
33
|
-
deployBranch = "", intent = null, semverAuto = true , appRelease = null, labelStyle = "en", closeOnRelease = null, projectsSync = null, language = "en", excludedWorkflows = null } = context;
|
|
33
|
+
deployBranch = "", intent = null, semverAuto = true , appRelease = null, labelStyle = "en", closeOnRelease = null, projectsSync = null, language = "en", excludedWorkflows = null, storeLocales = null } = context;
|
|
34
34
|
|
|
35
35
|
// project_paths 마커 계산 (.sh existing_marker_in_dir 등가 — 대표 마커명)
|
|
36
36
|
const pathMarkers = new Map();
|
|
@@ -51,9 +51,11 @@ export function runFull(context, tempDir, targetRoot = ".", hooks = {}) {
|
|
|
51
51
|
buildVersionYml({
|
|
52
52
|
version, types, paths, pathMarkers, branch, deployBranch, versionCode, now, today,
|
|
53
53
|
deployValues,
|
|
54
|
+
// 생성기가 모르는 키(issue_helper 등)를 지킨다 — version.yml 을 매번 다시 쓰므로 안 그러면 업데이트마다 사라진다 (#835)
|
|
55
|
+
...customYml(vyFile),
|
|
54
56
|
updatedBy: resolveUpdatedBy(existsSync(vyFile) ? readFileSync(vyFile, "utf8") : "", targetRoot), // #811
|
|
55
57
|
templateOptions: { templateVersion, deployTarget, publishTargets, includeSecretBackup, aiPrSummary, optionsDate: today,
|
|
56
|
-
changelogProvider, changelogBaseUrl, codeReviewCoderabbit, intent, mode: "full", semverAuto, appRelease, labelStyle, closeOnRelease, projectsSync, language, excludedWorkflows },
|
|
58
|
+
changelogProvider, changelogBaseUrl, codeReviewCoderabbit, intent, mode: "full", semverAuto, appRelease, labelStyle, closeOnRelease, projectsSync, language, excludedWorkflows, storeLocales },
|
|
57
59
|
})), { version, versionCode });
|
|
58
60
|
|
|
59
61
|
// 2. README 버전 섹션
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// io 주입으로 테스트 가능. 실제 실행은 src/ui/prompts.js 함수를 io로 넘긴다.
|
|
3
3
|
// 새 시각 층(banner/detectionLog/analysisCard/ideStatus/installKind/summary)과 저수준 엔진(engineIo)은
|
|
4
4
|
// io의 "옵셔널 멤버" — 스텁이 생략하면 해당 층만 건너뛰고 실행 계약은 동일하다.
|
|
5
|
+
import { DEFAULT_CODE_REVIEW_CODERABBIT } from "../core/constants.js";
|
|
5
6
|
import { join } from "node:path";
|
|
6
7
|
import { existsSync, readFileSync } from "node:fs";
|
|
7
8
|
import { PATHS } from "../core/paths.js";
|
|
@@ -129,7 +130,7 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
|
|
|
129
130
|
let publishTargets = existing?.options?.publish ?? [];
|
|
130
131
|
let includeSecretBackup = existing?.options?.secretBackup ?? false;
|
|
131
132
|
let aiPrSummary = existing?.options?.aiPrSummary ?? null; // null = 아직 안 물음 (#566)
|
|
132
|
-
let codeReviewCoderabbit = existing?.options?.codeReviewCoderabbit ??
|
|
133
|
+
let codeReviewCoderabbit = existing?.options?.codeReviewCoderabbit ?? DEFAULT_CODE_REVIEW_CODERABBIT;
|
|
133
134
|
let changelogProvider = migrateProvider(existing?.options?.changelogProvider) ?? "commit";
|
|
134
135
|
let changelogBaseUrl = existing?.options?.changelogBaseUrl ?? "";
|
|
135
136
|
let deployBranch = existing?.options?.deployBranch ?? "develop"; // #456
|
|
@@ -144,6 +145,7 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
|
|
|
144
145
|
// 상태 라벨 표기(#776): 저장값 > (신규 en / 기존 ko). 기존 한글 레포에는 영문 전환을 한 번 제안한다(아래).
|
|
145
146
|
const closeOnRelease = existing?.options?.closeOnRelease ?? (existing ? null : true); // #771 묻지 않는다
|
|
146
147
|
const excludedWorkflows = existing?.options?.excludedWorkflows ?? null; // #810 저장값 보존
|
|
148
|
+
const storeLocales = existing?.options?.storeLocales ?? null; // #829 저장값 보존
|
|
147
149
|
// Projects 보드 동기화(#716): 묻지 않는다. 저장값 > 이미 설치돼 있으면 유지 > 신규는 제외 (index.js 와 같은 규칙).
|
|
148
150
|
const projectsSync = existing?.options?.projectsSync
|
|
149
151
|
?? existsSync(join(cwd, ".github/workflows/PROJECT-COMMON-PROJECTS-SYNC-MANAGER.yaml"));
|
|
@@ -330,7 +332,7 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
|
|
|
330
332
|
const { now, today } = clock || utcNow();
|
|
331
333
|
const ctx = createContext({
|
|
332
334
|
mode, force: true, types, version, versionCode, branch, paths, deployTarget, publishTargets, includeSecretBackup,
|
|
333
|
-
codeReviewCoderabbit, changelogProvider, changelogBaseUrl, deployBranch, intent, semverAuto, appRelease, labelStyle, closeOnRelease, projectsSync, language, excludedWorkflows,
|
|
335
|
+
codeReviewCoderabbit, changelogProvider, changelogBaseUrl, deployBranch, intent, semverAuto, appRelease, labelStyle, closeOnRelease, projectsSync, language, excludedWorkflows, storeLocales,
|
|
334
336
|
repoName, templateVersion, resolvers, envValues, envUseDefaults, now, today,
|
|
335
337
|
// #502 — version 모드가 기존 full 기록을 강등하지 않도록 (full이 우세)
|
|
336
338
|
recordMode: existing?.templateMode === "full" ? "full" : "version",
|
|
@@ -425,7 +427,7 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
|
|
|
425
427
|
migrationGuidePath = appendGuideEntry(cwd, {
|
|
426
428
|
now, mode, types, repoName,
|
|
427
429
|
templateFrom: existing?.templateVersion || "", templateTo: templateVersion,
|
|
428
|
-
options: { deploy: deployTarget, publish: publishTargets, secretBackup: includeSecretBackup, coderabbit: codeReviewCoderabbit, changelogProvider, intent, semverAuto , appRelease, labelStyle, closeOnRelease, projectsSync, language, excludedWorkflows },
|
|
430
|
+
options: { deploy: deployTarget, publish: publishTargets, secretBackup: includeSecretBackup, coderabbit: codeReviewCoderabbit, changelogProvider, intent, semverAuto , appRelease, labelStyle, closeOnRelease, projectsSync, language, excludedWorkflows, storeLocales },
|
|
429
431
|
branches: { defaultBranch: branch, deployBranch, ready: deployBranchReady, created: deployBranchCreated },
|
|
430
432
|
breaking: breakingReport, migrations: migrationsResult, orphans: orphanReport,
|
|
431
433
|
events: trace.events, counters: { skipped: result?.workflows?.skipped ?? 0 },
|
package/src/commands/version.js
CHANGED
|
@@ -6,7 +6,7 @@ import { join } from "node:path";
|
|
|
6
6
|
import { writeText } from "../core/fsutil.js";
|
|
7
7
|
import { PATHS } from "../core/paths.js";
|
|
8
8
|
import { installAgentGuide } from "../core/agent-guide.js";
|
|
9
|
-
import { buildVersionYml, mergeDeployValues, resolveUpdatedBy } from "../core/version-yml.js";
|
|
9
|
+
import { buildVersionYml, mergeDeployValues, resolveUpdatedBy, customYml } from "../core/version-yml.js";
|
|
10
10
|
import { markerForType } from "../core/detect.js";
|
|
11
11
|
import { addVersionSectionToReadme } from "../core/copy/readme.js";
|
|
12
12
|
import { applyLabelStyle } from "../core/label-style.js";
|
|
@@ -17,7 +17,7 @@ export function runVersion(context, tempDir, targetRoot = ".") {
|
|
|
17
17
|
const { version, types = [], paths = new Map(), branch = "main", versionCode = 1,
|
|
18
18
|
now, today, templateVersion = "unknown", deployTarget = "docker-ssh", publishTargets = [], includeSecretBackup = false,
|
|
19
19
|
changelogProvider = "commit", changelogBaseUrl = "", codeReviewCoderabbit = true,
|
|
20
|
-
deployBranch = "", recordMode = "version", semverAuto = true , appRelease = null, labelStyle = "en", closeOnRelease = null, projectsSync = null, language = null, excludedWorkflows = null } = context;
|
|
20
|
+
deployBranch = "", recordMode = "version", semverAuto = true , appRelease = null, labelStyle = "en", closeOnRelease = null, projectsSync = null, language = null, excludedWorkflows = null, storeLocales = null } = context;
|
|
21
21
|
|
|
22
22
|
const pathMarkers = new Map();
|
|
23
23
|
for (const [t] of paths) pathMarkers.set(t, markerForType(t));
|
|
@@ -30,11 +30,13 @@ export function runVersion(context, tempDir, targetRoot = ".") {
|
|
|
30
30
|
buildVersionYml({
|
|
31
31
|
version, types, paths, pathMarkers, branch, deployBranch, versionCode, now, today,
|
|
32
32
|
deployValues,
|
|
33
|
+
// 생성기가 모르는 키(issue_helper 등)를 지킨다 — version.yml 을 매번 다시 쓰므로 안 그러면 업데이트마다 사라진다 (#835)
|
|
34
|
+
...customYml(vyFile),
|
|
33
35
|
updatedBy: resolveUpdatedBy(existsSync(vyFile) ? readFileSync(vyFile, "utf8") : "", targetRoot), // #811
|
|
34
36
|
// mode(#502): version 모드가 기존 full 통합 기록을 "version"으로 강등하지 않도록
|
|
35
37
|
// 호출부가 recordMode로 기존 값을 넘긴다 (full이 우세 — 업데이트 재실행 범위 축소 방지).
|
|
36
38
|
templateOptions: { templateVersion, deployTarget, publishTargets, includeSecretBackup, optionsDate: today,
|
|
37
|
-
changelogProvider, changelogBaseUrl, codeReviewCoderabbit, mode: recordMode, semverAuto, appRelease, labelStyle, closeOnRelease, projectsSync, language, excludedWorkflows },
|
|
39
|
+
changelogProvider, changelogBaseUrl, codeReviewCoderabbit, mode: recordMode, semverAuto, appRelease, labelStyle, closeOnRelease, projectsSync, language, excludedWorkflows, storeLocales },
|
|
38
40
|
}));
|
|
39
41
|
addVersionSectionToReadme(version, targetRoot);
|
|
40
42
|
copyScripts(tempDir, targetRoot);
|
package/src/context.js
CHANGED
|
@@ -32,6 +32,7 @@ export function createContext(overrides = {}) {
|
|
|
32
32
|
language: null, // 레포 문구 언어 (#769). null=미설정(신규 en / 기존 레포 ko)
|
|
33
33
|
labelStyle: null, // 상태 라벨 표기 (#776). null=미설정(신규 en / 기존 레포 ko)
|
|
34
34
|
excludedWorkflows: null, // 설치하지 않을 워크플로우 파일명 (#810). null=없음
|
|
35
|
+
storeLocales: null, // 스토어 릴리스 노트 언어 (#829). null=미설정 → 현행 동작 유지
|
|
35
36
|
appRelease: null, // 앱 심사 배포 레포인가 (#553). null=미설정(키 기록 안 함)
|
|
36
37
|
templateVersion: "",
|
|
37
38
|
tempDir: "",
|
package/src/core/constants.js
CHANGED
|
@@ -21,6 +21,13 @@ export const INTENT_VALUES = Object.freeze(["app", "library", "both", "none", "m
|
|
|
21
21
|
/** 상태 라벨 표기 — en: `status: todo`, ko: 기존 한글 `작업전` (#776) */
|
|
22
22
|
export const LABEL_STYLES = Object.freeze(["en", "ko"]);
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* 저장값이 없을 때 CodeRabbit 코드 리뷰 설정을 설치할지의 기본값 (#832).
|
|
26
|
+
* 꺼 둔다 — CodeRabbit 은 별도 GitHub 앱을 설치해야 동작하므로, 설치 없이 켜 두면 설정 파일만 있고 아무도 리뷰하지 않는다.
|
|
27
|
+
* 대화형(질문하지 않는 version·issues 모드)과 비대화형이 각자 기본값을 가져 `true`/`false` 로 갈려 있던 것을 한 곳으로 모았다.
|
|
28
|
+
*/
|
|
29
|
+
export const DEFAULT_CODE_REVIEW_CODERABBIT = false;
|
|
30
|
+
|
|
24
31
|
/**
|
|
25
32
|
* 릴리스 노트 생성기 (#455, #566). 기본값은 commit — 외부 의존이 없어 어디서나 결과가 나온다.
|
|
26
33
|
* 실제 구현은 .github/scripts/changelog_providers/ 에 있고, 그쪽 목록과의 정합은 테스트가 본다.
|
package/src/core/copy/simple.js
CHANGED
|
@@ -37,6 +37,8 @@ export function copyScripts(tempDir, targetRoot = ".") {
|
|
|
37
37
|
"close_issues_on_release.py",
|
|
38
38
|
// Play Store/TestFlight 배포 모드를 코드 설정(.github/config/store-deploy.json)에서 읽는다 (#767).
|
|
39
39
|
"store_deploy_config.py",
|
|
40
|
+
// 스토어 릴리스 노트를 언어별로 준비 (#829). version.yml 의 store_locales 가 없으면 아무것도 하지 않는다.
|
|
41
|
+
"store_notes.py",
|
|
40
42
|
"changelog_providers/_common.py", "changelog_providers/ladder.py",
|
|
41
43
|
"changelog_providers/commit.py", "changelog_providers/copilot.py",
|
|
42
44
|
"changelog_providers/openai_compatible.py",
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
// 워크플로우 복사
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// 워크플로우 복사 엔진. 구 template_integrator.sh(#458 로 삭제됨)의 copy_workflows 를 포팅한 것이다.
|
|
2
|
+
// 파일 안의 ".sh ... 등가" 표기는 그 삭제된 스크립트를 가리킨다 — 줄 번호는 더 이상 찾을 수 없다.
|
|
3
|
+
//
|
|
4
|
+
// 충돌(기존 파일과 다름)의 처리 결정은 hooks.decisions(Map<파일명, 'skip'|'backup'|'template'>)로 받는다.
|
|
5
|
+
// - 실제 마법사(commands/interactive.js)는 listWorkflowConflicts 로 충돌 목록을 얻어 직접 Map 을 만든다.
|
|
6
|
+
// - copyWorkflowsInteractive 는 onConflict 콜백으로 같은 Map 을 만들어 주는 보조 API 다. 프로덕션 경로는 쓰지 않고
|
|
7
|
+
// 테스트(copy-workflows-conflict.test.js)만 쓴다.
|
|
5
8
|
import { join, basename } from "node:path";
|
|
6
9
|
import { existsSync, readFileSync, writeFileSync, renameSync } from "node:fs";
|
|
7
10
|
import { PATHS } from "../paths.js";
|
|
@@ -378,7 +381,7 @@ function applyDecision(decision, srcDir, workflowsDir, filename, counters, trace
|
|
|
378
381
|
});
|
|
379
382
|
}
|
|
380
383
|
|
|
381
|
-
// 대상 워크플로우 디렉토리에서 changed(충돌) 파일 목록만 뽑는다 — copyWorkflowsInteractive
|
|
384
|
+
// 대상 워크플로우 디렉토리에서 changed(충돌) 파일 목록만 뽑는다 — 마법사(commands/interactive.js)와 copyWorkflowsInteractive 가 쓴다.
|
|
382
385
|
// copyWorkflows 본체와 동일한 classify 기준을 써야 결정 Map이 실제 처리 대상과 1:1로 맞는다.
|
|
383
386
|
export function listWorkflowConflicts(context, tempDir, targetRoot = ".") {
|
|
384
387
|
const { types = [], paths = new Map(), deployTarget = "docker-ssh", repoName = "", resolvers = {}, branch = "", deployBranch = "" } = context;
|
package/src/core/exclusions.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// 템플릿 다운로드 후 제거하는 항목 (복사 제외 목록).
|
|
2
2
|
// ⚠️ CLAUDE.md "템플릿 전용 파일 추가" 규칙의 동기화 지점:
|
|
3
|
-
// template_initializer.
|
|
3
|
+
// template_initializer.py(삭제) / 이 파일(복사 제외)
|
|
4
4
|
// (구 template_integrator.sh/.ps1의 배열은 #458 EOF로 소멸 — 여기가 유일한 복사 제외 지점)
|
|
5
5
|
export const DOCS_TO_REMOVE = [
|
|
6
6
|
"CONTRIBUTING.md",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
// tier - "safe" : 신형이 같은 기능을 대체(순수 리네임). 공존 시 이중 트리거 실해
|
|
12
12
|
// → 확인 1회 후 자동 무해화(.bak) / 삭제
|
|
13
13
|
// "confirm" : 배포 파이프라인일 수 있음(그 레포의 유일한 현역 배포 가능성)
|
|
14
|
-
// →
|
|
14
|
+
// → 기본은 건드리지 않고 완료 요약·doctor 에 목록만 안내한다.
|
|
15
|
+
// --remove-legacy(비대화형) 또는 대화형 확인(기본 아니오)이 있을 때만 .bak 으로 무해화 (#809)
|
|
15
16
|
// "ask" : 사용자 콘텐츠가 담긴 폴더 등 — 손실 없는 이동이지만 사용자 소유물 (#476)
|
|
16
17
|
// → 대화형: 확인 후 이동 / 비대화형: 자동 조치 없이 안내만
|
|
17
18
|
// file - 정확한 파일명 (글롭 금지 — 사용자 커스텀 보호의 핵심)
|
package/src/core/options-ask.js
CHANGED
|
@@ -13,7 +13,7 @@ import { PATHS } from "./paths.js";
|
|
|
13
13
|
import { t as msg } from "../i18n/index.js";
|
|
14
14
|
import { parseTemplateOptions, inferIntent } from "./version-yml.js";
|
|
15
15
|
import { branchStatus, createBranch, pushBranch } from "./git-branch.js";
|
|
16
|
-
import { DEPLOY_TARGETS, PUBLISH_TARGETS, CHANGELOG_PROVIDERS } from "./constants.js";
|
|
16
|
+
import { DEPLOY_TARGETS, PUBLISH_TARGETS, CHANGELOG_PROVIDERS, DEFAULT_CODE_REVIEW_CODERABBIT } from "./constants.js";
|
|
17
17
|
|
|
18
18
|
// 개발(배포) 브랜치 존재 확인 + 생성 제안 (#477) — 대화형 전용.
|
|
19
19
|
// 로컬에 없고 원격에도 없(거나 불명이)면 기본 브랜치에서 생성을 제안하고, 생성 후 push 여부도 묻는다.
|
|
@@ -336,7 +336,7 @@ export async function askAllOptionalWorkflows({
|
|
|
336
336
|
if (force || !tty || typeof io.select !== "function") {
|
|
337
337
|
// 비대화형 기본값: CodeRabbit은 별도 앱 설치가 있어야 실제로 동작하므로
|
|
338
338
|
// 자동화 환경에서 켜봐야 의미가 없다. 설정 없이 바로 도는 요약만 켠다.
|
|
339
|
-
codeReviewCoderabbit = codeReviewCoderabbit ??
|
|
339
|
+
codeReviewCoderabbit = codeReviewCoderabbit ?? DEFAULT_CODE_REVIEW_CODERABBIT;
|
|
340
340
|
aiPrSummary = aiPrSummary ?? true;
|
|
341
341
|
} else {
|
|
342
342
|
say("");
|
|
@@ -58,6 +58,9 @@ export const FLAGS = [
|
|
|
58
58
|
{ flag: "--ai-summary", negation: "--no-ai-summary",
|
|
59
59
|
description: "Include / exclude the workflow that comments an AI summary on work PRs.", default: "included",
|
|
60
60
|
versionYml: "metadata.template.options.code_review.ai_summary" },
|
|
61
|
+
{ flag: "--coderabbit", negation: "--no-coderabbit",
|
|
62
|
+
description: "Install the CodeRabbit code review config (.coderabbit.yaml). Only --mode full installs it.",
|
|
63
|
+
default: "off unless version.yml already says on", versionYml: "metadata.template.options.code_review.coderabbit" },
|
|
61
64
|
{ flag: "--projects-sync", negation: "--no-projects-sync",
|
|
62
65
|
description: "Include / exclude the GitHub Projects status sync workflow. Needs a PAT secret and PROJECT_URL variable.",
|
|
63
66
|
default: "excluded for new installs; kept if already installed", versionYml: "metadata.template.options.projects_sync" },
|
|
@@ -105,6 +108,9 @@ export const VERSION_YML = [
|
|
|
105
108
|
{ path: "metadata.template.options.excluded_workflows", parserKey: "excludedWorkflows", type: "string[]",
|
|
106
109
|
description: "Workflow file names the update must never install. Deleted files are also not restored; a copy is kept in .github/.projectops/incoming/.",
|
|
107
110
|
edit: "by hand" },
|
|
111
|
+
{ path: "metadata.template.options.store_locales", parserKey: "storeLocales", type: "string[]",
|
|
112
|
+
description: "Store release-note languages, first is the default (for example [\"ko-KR\", \"en-US\", \"ja-JP\", \"zh-CN\"]). Missing = one text for every store language, as before. Per-language text comes from store_notes in CHANGELOG.json; a language without text gets the default-language text (the stores reject an empty What's New).",
|
|
113
|
+
edit: "by hand" },
|
|
108
114
|
{ path: "metadata.template.options.code_review.coderabbit", parserKey: "codeReviewCoderabbit", type: "boolean", description: "CodeRabbit config installed.", edit: "wizard" },
|
|
109
115
|
{ path: "metadata.template.options.code_review.ai_summary", parserKey: "aiPrSummary", type: "boolean", default: true, description: "AI PR summary workflow installed.", edit: "wizard (--ai-summary)" },
|
|
110
116
|
{ path: "metadata.template.options.changelog.provider", parserKey: "changelogProvider", type: "enum",
|
|
@@ -113,6 +119,200 @@ export const VERSION_YML = [
|
|
|
113
119
|
{ path: "metadata.template.options.changelog.base_url", parserKey: "changelogBaseUrl", type: "string", description: "Only for provider ollama.", edit: "by hand" },
|
|
114
120
|
];
|
|
115
121
|
|
|
122
|
+
// ── agent 가 읽고 스스로 판단하게 하는 상세 설명 ───────────────────────────────────────────────
|
|
123
|
+
// 짧은 description 만으로는 "언제 쓰나 · 쓰면 레포에서 정확히 무엇이 바뀌나 · 안 쓰면 어떻게 되나"를 알 수 없다.
|
|
124
|
+
// 사람 없이 이 CLI 를 부르는 AI agent 가 추측하지 않도록 항목마다 적는다.
|
|
125
|
+
// when : 이 값을 직접 정해야 하는 상황 / 그냥 둬도 되는 상황
|
|
126
|
+
// effect : 레포에서 실제로 바뀌는 것 (어떤 파일이 생기고 사라지는지, 어떤 워크플로가 도는지)
|
|
127
|
+
// omitted : 안 주면 어떻게 정해지는가
|
|
128
|
+
// example : 그대로 실행할 수 있는 명령
|
|
129
|
+
// caution : 되돌리기 어렵거나 놓치기 쉬운 것
|
|
130
|
+
// test/options-schema.test.js 가 모든 (폐기 아닌) 플래그·옵션에 when·effect 가 있는지 검사한다 — 새 항목은 여기에도 적는다.
|
|
131
|
+
export const FLAG_DETAILS = {
|
|
132
|
+
"--mode": {
|
|
133
|
+
when: "Always pass it explicitly from an agent or CI. Without it the CLI opens interactive questions.",
|
|
134
|
+
effect: "Chooses which parts are installed. full = everything; workflows = only .github/workflows (+ scripts, config, util); version = only version.yml + README version section; issues = only issue/PR templates; skills = IDE agent skills; doctor and options never write anything.",
|
|
135
|
+
omitted: "interactive: asks questions in the terminal and fails without a TTY.",
|
|
136
|
+
example: "npx projectops --mode full --force",
|
|
137
|
+
caution: "Any mode other than interactive decides every option from flags, version.yml and defaults — it does not ask. Without a TTY, --force is also required.",
|
|
138
|
+
},
|
|
139
|
+
"--type": {
|
|
140
|
+
when: "Pass it when auto-detection is wrong or the repo has several stacks (a monorepo).",
|
|
141
|
+
effect: "Selects which project-types/<type>/ workflows are installed and which build file version_manager keeps in sync (pubspec.yaml, package.json, build.gradle, pyproject.toml ...).",
|
|
142
|
+
omitted: "Detected from marker files in the repo root (pubspec.yaml → flutter, build.gradle → spring, package.json → node/react ...). Nothing detected → basic (version.yml only).",
|
|
143
|
+
example: "npx projectops --mode full --force --type spring,react",
|
|
144
|
+
caution: "The first type is the primary one. Do not change types after setup.",
|
|
145
|
+
},
|
|
146
|
+
"--project-version": {
|
|
147
|
+
when: "Only for a repo that has no version.yml yet and where the detected version is wrong.",
|
|
148
|
+
effect: "Sets the starting version written into version.yml.",
|
|
149
|
+
omitted: "Read from the build file (pubspec.yaml, package.json ...); an existing version.yml value is always kept.",
|
|
150
|
+
example: "npx projectops --mode full --force --project-version 1.0.0",
|
|
151
|
+
},
|
|
152
|
+
"--paths": {
|
|
153
|
+
when: "Monorepos where a stack lives in a subfolder (app/, client/ ...).",
|
|
154
|
+
effect: "Writes project_paths into version.yml; version_manager then syncs the version file inside that folder and workflow path filters point at it.",
|
|
155
|
+
omitted: "Every type is assumed to be at the repo root.",
|
|
156
|
+
example: 'npx projectops --mode full --force --type flutter,react --paths "flutter=app,react=client"',
|
|
157
|
+
caution: "Each folder must exist inside the repo. Absolute paths and '..' are rejected.",
|
|
158
|
+
},
|
|
159
|
+
"--intent": {
|
|
160
|
+
when: "Rarely needed. It only matters for interactive questions and for deriving --deploy / --publish.",
|
|
161
|
+
effect: "app → deploy asked, publish skipped; library → publish asked, deploy forced to none; both → both asked; none → neither; manual → both asked one by one. Stored as options.intent.",
|
|
162
|
+
omitted: "Inferred from --deploy and --publish (or from the stored version.yml values).",
|
|
163
|
+
example: "npx projectops --mode full --force --intent library --publish npm",
|
|
164
|
+
},
|
|
165
|
+
"--deploy": {
|
|
166
|
+
when: "Set it when the app is deployed by this repo. Library repos use --publish instead.",
|
|
167
|
+
effect: "docker-ssh installs the SSH + Docker server deploy workflows (SIMPLE-CICD, NONSTOP-*, PR-PREVIEW for spring/python; CICD for react). vercel installs the Vercel deploy workflow. none installs no deploy workflow. Mobile types (flutter, react-native*) ignore it — their store deploy workflows are part of the type.",
|
|
168
|
+
omitted: "docker-ssh for server stacks, none for mobile apps and library repos.",
|
|
169
|
+
example: "npx projectops --mode full --force --type spring --deploy vercel",
|
|
170
|
+
caution: "docker-ssh workflows need server secrets (SERVER_HOST, SERVER_USER, SERVER_PASSWORD or SSH_KEY, DOCKERHUB_*). Until they are registered the workflows fail — the completion summary lists the missing secrets.",
|
|
171
|
+
},
|
|
172
|
+
"--publish": {
|
|
173
|
+
when: "Set it when the repo is a library published to a package registry.",
|
|
174
|
+
effect: "nexus installs NEXUS-CI and NEXUS-PUBLISH (spring); npm installs NODE-NPM-PUBLISH (needs NPM_TOKEN); github-packages installs GITHUB-PACKAGES-PUBLISH (spring). Several values are allowed.",
|
|
175
|
+
omitted: "No publish workflow.",
|
|
176
|
+
example: "npx projectops --mode workflows --force --type node --publish npm",
|
|
177
|
+
},
|
|
178
|
+
"--deploy-branch": {
|
|
179
|
+
when: "Only if your development branch is not named develop.",
|
|
180
|
+
effect: "Writes metadata.deploy_branch and rewrites the branch name inside the installed workflows (release PR head, CI triggers, PR summary base). The branch is NOT created by this flag.",
|
|
181
|
+
omitted: "develop.",
|
|
182
|
+
example: "npx projectops --mode full --force --deploy-branch dev",
|
|
183
|
+
caution: "Despite the name this is the development branch (head of the release PR), not the branch that deploys. The deploying branch is the repository default branch.",
|
|
184
|
+
},
|
|
185
|
+
"--language": {
|
|
186
|
+
when: "Set it only to change the language of an existing repo or to start a new repo in Korean.",
|
|
187
|
+
effect: "Chooses the language of the issue/PR templates and bot comments written into the repo, and of the comments inside version.yml.",
|
|
188
|
+
omitted: "New installs: en. Existing installs keep the stored value (ko if none was stored).",
|
|
189
|
+
example: "npx projectops --mode full --force --language ko",
|
|
190
|
+
caution: "This is not the language of the CLI screen — that is --lang.",
|
|
191
|
+
},
|
|
192
|
+
"--label-style": {
|
|
193
|
+
when: "Only when the repo already uses one label naming and you must keep it.",
|
|
194
|
+
effect: "Writes the label definitions (.github/config/issue-labels.yml) and the label names used inside the installed workflows in that style: en = 'status: todo', 'status: done'; ko = the legacy Korean names (작업전, 작업완료). Workflows and skills accept both. Labels are created in GitHub by running the PROJECT-COMMON-SYNC-ISSUE-LABELS workflow once.",
|
|
195
|
+
omitted: "New installs: en. Existing installs keep ko unless a value is stored.",
|
|
196
|
+
example: "npx projectops --mode full --force --label-style ko",
|
|
197
|
+
},
|
|
198
|
+
"--secret-backup": {
|
|
199
|
+
when: "Only if you want GitHub Secrets copied to your own server over SSH.",
|
|
200
|
+
effect: "Installs PROJECT-COMMON-SECRET-FILE-UPLOAD, which runs on pushes to the development branch and needs SERVER_HOST, SERVER_USER and SERVER_PASSWORD.",
|
|
201
|
+
omitted: "Not installed.",
|
|
202
|
+
example: "npx projectops --mode workflows --force --secret-backup",
|
|
203
|
+
},
|
|
204
|
+
"--ai-summary": {
|
|
205
|
+
when: "Leave the default unless you want no automatic PR comment.",
|
|
206
|
+
effect: "Installs PROJECT-COMMON-AI-PR-SUMMARY, which comments a change summary on work PRs into the development branch. It works with no extra setup; registering an AI key (GEMINI_API_KEY, GROQ_API_KEY, ...) makes the wording better.",
|
|
207
|
+
omitted: "Installed (or kept as stored).",
|
|
208
|
+
example: "npx projectops --mode workflows --force --no-ai-summary",
|
|
209
|
+
},
|
|
210
|
+
"--coderabbit": {
|
|
211
|
+
when: "Pass --coderabbit only if the team uses CodeRabbit for pull request code review.",
|
|
212
|
+
effect: "Copies .coderabbit.yaml to the repo root. Only --mode full installs it. If the file already exists it is overwritten and the old one is saved as .coderabbit.yaml.bak. This is the PR review bot only; release notes are generated separately (PR body → AI key → commit analysis) and do not need it.",
|
|
213
|
+
omitted: "Off, unless version.yml already stores code_review.coderabbit: true (a stored choice is kept). The wizard asks interactively; non-interactive runs never turn it on by themselves.",
|
|
214
|
+
example: "npx projectops --mode full --force --coderabbit",
|
|
215
|
+
caution: "CodeRabbit needs its GitHub app installed and granted access to the repo (https://coderabbit.ai). Without the app the file does nothing and no review comments appear.",
|
|
216
|
+
},
|
|
217
|
+
"--projects-sync": {
|
|
218
|
+
when: "Only if the repo uses a GitHub Projects board whose Status column should follow issue labels.",
|
|
219
|
+
effect: "Installs PROJECT-COMMON-PROJECTS-SYNC-MANAGER. It needs the secret _GITHUB_PAT_TOKEN and the repository variable PROJECT_URL; without them it fails on every label change.",
|
|
220
|
+
omitted: "Not installed for new installs; kept if it is already installed.",
|
|
221
|
+
example: "npx projectops --mode workflows --force --projects-sync",
|
|
222
|
+
},
|
|
223
|
+
"--remove-legacy": {
|
|
224
|
+
when: "After an update printed 'retired workflows are still installed' (or doctor listed them) and the new workflow is working.",
|
|
225
|
+
effect: "Renames retired old-generation workflows (for example PROJECT-SPRING-SYNOLOGY-*, PROJECT-COMMON-RELEASE-PUBLISH) to *.yaml.bak so they stop running. Delete the .bak to restore.",
|
|
226
|
+
omitted: "Retired workflows are left in place and only listed in the completion summary and in doctor. Plain renames that have an identical replacement are neutralised automatically; deploy pipelines are never touched without this flag.",
|
|
227
|
+
example: "npx projectops --mode workflows --force --remove-legacy",
|
|
228
|
+
caution: "Old deploy workflows may be the repo's only live deployment. Check that the replacement works first.",
|
|
229
|
+
},
|
|
230
|
+
"--force": {
|
|
231
|
+
when: "Always, from an agent or CI.",
|
|
232
|
+
effect: "Skips every confirmation (including the breaking-changes warning) and uses defaults for anything not given as a flag. Files you modified are NOT overwritten: they are kept and a copy of the new template is saved in .github/.projectops/incoming/ for you to diff.",
|
|
233
|
+
omitted: "Confirmation prompts appear when a TTY is attached; without a TTY the command stops with an error asking for --force.",
|
|
234
|
+
example: "npx projectops --mode workflows --force",
|
|
235
|
+
caution: "Idempotent: running it again changes nothing that is already current.",
|
|
236
|
+
},
|
|
237
|
+
"--lang": {
|
|
238
|
+
when: "Rarely. Only to force the language of the terminal output.",
|
|
239
|
+
effect: "Changes the language of CLI messages (not of files written into the repo).",
|
|
240
|
+
omitted: "System language; en in CI and whenever --force is given.",
|
|
241
|
+
example: "npx projectops --mode doctor --lang ko",
|
|
242
|
+
},
|
|
243
|
+
"--json": {
|
|
244
|
+
when: "With --mode options, whenever a program or agent reads the output.",
|
|
245
|
+
effect: "Prints the full option table as JSON instead of text. No files are written.",
|
|
246
|
+
omitted: "Human-readable text.",
|
|
247
|
+
example: "npx projectops --mode options --json",
|
|
248
|
+
},
|
|
249
|
+
"--version": { when: "To learn which projectops release is running.", effect: "Prints the package version and exits. Writes nothing.", omitted: "-", example: "npx projectops --version" },
|
|
250
|
+
"--help": { when: "To read the short human help.", effect: "Prints the help text and exits. Writes nothing. For agents, --mode options --json is more complete.", omitted: "-", example: "npx projectops --help" },
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
export const KEY_DETAILS = {
|
|
254
|
+
"version": { when: "Read it to know the current version. Edit by hand only when asked to change the version.", effect: "version_manager syncs this value into pubspec.yaml / package.json / build.gradle / pyproject.toml ...", omitted: "Created from the build file at install." },
|
|
255
|
+
"version_code": { when: "Do not edit.", effect: "Build number. +1 on every release; used by Android release builds (iOS and test builds use build_number.py).", omitted: "1" },
|
|
256
|
+
"project_types": { when: "Set by the wizard. Do not change after setup.", effect: "Decides which type workflows run and which build files are synced. The first entry is primary.", omitted: "Detected at install." },
|
|
257
|
+
"project_paths": { when: "Monorepos only.", effect: "Maps a type to its subfolder so version sync and workflow path filters look there.", omitted: "Every type at the repo root." },
|
|
258
|
+
"metadata.default_branch": { when: "Do not edit unless the repository default branch was renamed.", effect: "The branch whose pushes deploy and publish. Workflows trigger on it.", omitted: "main" },
|
|
259
|
+
"metadata.deploy_branch": { when: "Only if the development branch is not develop.", effect: "Head of the release PR; workflows rewrite their develop references to this name.", omitted: "develop", caution: "Not the deploying branch, despite the name." },
|
|
260
|
+
"metadata.template.options.intent": { when: "Leave as is.", effect: "Remembers the project kind so a re-run does not ask the deploy questions again.", omitted: "Inferred from deploy and publish." },
|
|
261
|
+
"metadata.template.options.deploy": { when: "Edit via --deploy, not by hand.", effect: "Which deploy workflow family is installed on the next update (docker-ssh, vercel, none).", omitted: "docker-ssh for server stacks." },
|
|
262
|
+
"metadata.template.options.publish": { when: "Edit via --publish.", effect: "Which registry publish workflows are installed on the next update.", omitted: "[]" },
|
|
263
|
+
"metadata.template.options.secret_backup": { when: "Edit via --secret-backup.", effect: "Whether the secret backup workflow is installed.", omitted: "false" },
|
|
264
|
+
"metadata.template.options.semver_auto": { when: "Set false only if you want every release to be a patch bump.", effect: "true: the release PR and direct pushes to the default branch pick the bump from commit titles ('type!:' major, 'feat:' minor, otherwise patch). false: always patch.", omitted: "true (also for existing installs when the key is missing).", caution: "A repo that switches from patch-only to true can jump a minor version on its next release; the update summary warns about it." },
|
|
265
|
+
"metadata.template.options.close_on_release": { when: "Set false to keep finished issues open after a release.", effect: "A release merge closes issues that carry the done label and are referenced by a release commit.", omitted: "true for new installs; off (key missing) for existing installs." },
|
|
266
|
+
"metadata.template.options.language": { when: "Edit via --language.", effect: "Language of issue/PR templates, bot comments and version.yml comments.", omitted: "en for new installs, ko for existing ones." },
|
|
267
|
+
"metadata.template.options.label_style": { when: "Edit via --label-style.", effect: "Status label names (en 'status: todo' or legacy ko).", omitted: "en for new installs, ko for existing ones." },
|
|
268
|
+
"metadata.template.options.projects_sync": { when: "Edit via --projects-sync.", effect: "Whether the Projects status sync workflow is installed.", omitted: "false for new installs; kept if installed." },
|
|
269
|
+
"metadata.template.options.app_release": { when: "Set true by hand for repos whose releases go through App Store / Play Store review.", effect: "The deploy skill (pro-changelog-deploy) treats releases as going through store review: release notes get a review warning banner and are checked more strictly, and the release PR always waits for manual approval instead of automerging.", omitted: "Not set. Never enabled automatically.", caution: "Do not set it on repos that do not ship to a store." },
|
|
270
|
+
"metadata.template.options.excluded_workflows": { when: "Add a file name to stop the update from ever installing that template workflow.", effect: "Listed files are skipped on install and update. Files you deleted yourself are also not restored; a copy of the template is kept in .github/.projectops/incoming/.", omitted: "[]", example: 'excluded_workflows: ["PROJECT-COMMON-TEMPLATE-UTIL-VERSION-SYNC.yml"]' },
|
|
271
|
+
"metadata.template.options.store_locales": { when: "Set it when the store listing has several languages and release notes must not be one Korean text copied into all of them.", effect: "Turns on per-language store release notes (Play Console 'Release notes', App Store 'What's New'). The first entry is the default language. Each other language uses the text in store_notes[<language>] of that version in CHANGELOG.json; a language without text gets the default-language text, because the stores reject an empty What's New.", omitted: "Off: the same text is used for every store language, as before.", example: 'store_locales: ["ko-KR", "en-US", "ja-JP", "zh-CN"]' },
|
|
272
|
+
"metadata.template.options.code_review.coderabbit": { when: "Edit via --coderabbit / --no-coderabbit.", effect: "Whether .coderabbit.yaml is installed by --mode full. PR review bot only; unrelated to release notes.", omitted: "false" },
|
|
273
|
+
"metadata.template.options.code_review.ai_summary": { when: "Edit via --ai-summary.", effect: "Whether the AI PR summary workflow is installed.", omitted: "true" },
|
|
274
|
+
"metadata.template.options.changelog.provider": { when: "Leave the default. Set only to force one generator.", effect: "Release notes: the PR body is used as is when it already has notes; otherwise a registered AI key (GEMINI_API_KEY, GROQ_API_KEY, MISTRAL_API_KEY, OPENAI_API_KEY, ANTHROPIC_API_KEY) is used; otherwise the commit analysis, which always works and costs nothing. 'copilot' consumes Copilot premium requests, so it is only used when set here.", omitted: "commit (an AI key is still picked up automatically)." },
|
|
275
|
+
"metadata.template.options.changelog.base_url": { when: "Only with provider ollama.", effect: "Base URL of the Ollama server (OpenAI-compatible, ending in /v1).", omitted: "" },
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
// 자주 하는 일 → 그대로 실행할 명령. agent 가 옵션을 조합하다 틀리지 않게 한다.
|
|
279
|
+
export const RECIPES = [
|
|
280
|
+
{ goal: "Install everything in a new repo (stack auto-detected)", command: "npx projectops --mode full --force", note: "Add --type spring,react for several stacks." },
|
|
281
|
+
{ goal: "Update an existing install to the newest templates", command: "npx projectops --mode full --force", note: "Files you modified are kept; read .github/.projectops/incoming/ and the completion summary for what was skipped." },
|
|
282
|
+
{ goal: "Refresh only the workflows, scripts and config", command: "npx projectops --mode workflows --force", note: "version.yml is not rewritten." },
|
|
283
|
+
{ goal: "See the state of an install and the repository settings (read-only)", command: "npx projectops --mode doctor", note: "Add GITHUB_TOKEN=... to also check repository settings." },
|
|
284
|
+
{ goal: "Read every option with values, defaults and effects", command: "npx projectops --mode options --json" },
|
|
285
|
+
{ goal: "Install the CodeRabbit PR review config", command: "npx projectops --mode full --force --coderabbit", note: "Needs the CodeRabbit GitHub app; overwrites an existing .coderabbit.yaml (backup saved as .bak)." },
|
|
286
|
+
{ goal: "Never install a template workflow again", command: "Add its file name to metadata.template.options.excluded_workflows in version.yml", note: "Delete the installed file too; the update will not bring it back." },
|
|
287
|
+
{ goal: "Stop retired workflows from running next to their replacements", command: "npx projectops --mode workflows --force --remove-legacy", note: "Run only after the new workflow is working." },
|
|
288
|
+
{ goal: "Keep every release a patch bump", command: "Set metadata.template.options.semver_auto: false in version.yml" },
|
|
289
|
+
{ goal: "Install a library publish workflow", command: "npx projectops --mode workflows --force --type node --publish npm", note: "Needs the NPM_TOKEN secret." },
|
|
290
|
+
];
|
|
291
|
+
|
|
292
|
+
// 이 CLI 를 사람 없이 쓰는 agent 가 지킬 규칙
|
|
293
|
+
export const AGENT_RULES = [
|
|
294
|
+
"Always pass an explicit --mode and --force. Without --force and without a TTY the command stops; with --mode interactive it needs a human.",
|
|
295
|
+
"Read before you write: run --mode doctor (state) and --mode options --json (this table). Both are read-only and need no network.",
|
|
296
|
+
"Only flags you pass, the stored version.yml values and the defaults decide anything — nothing is asked. Check the 'omitted' text of a flag before relying on its default.",
|
|
297
|
+
"Your edits are safe: a workflow you changed is kept as is, and a copy of the new template goes to .github/.projectops/incoming/. Compare with: diff -u .github/workflows/<file> .github/.projectops/incoming/<file>.",
|
|
298
|
+
"After a run read the completion summary on stderr: it lists kept files, retired workflows still installed, workflows not installed, placeholders that still need a value (__NAME__) and secrets that must be registered.",
|
|
299
|
+
"Every run is recorded in .github/.projectops/logs/ (.log for people, .jsonl for programs). Read the newest .log to see why something was decided. Never edit those files.",
|
|
300
|
+
"Exit code 0 means the run completed (doctor also returns 0 when it finds things to look at). 1 means invalid arguments or a failed run; the message says which.",
|
|
301
|
+
"Never delete .github/.projectops/baseline.json: it is how the next update tells your edits from template changes.",
|
|
302
|
+
];
|
|
303
|
+
|
|
304
|
+
export const OUTPUT_FILES = [
|
|
305
|
+
{ path: "version.yml", written_by: "full, version", note: "Regenerated on every full/version run from the stored values plus flags. Options and top-level keys the wizard does not generate itself (for example options.issue_helper) are kept verbatim, comments included; legacy keys (nexus, npm_publish, synology, project_type) are converted to their new form and not kept; a malformed block is dropped rather than copied. workflows mode does not touch version.yml." },
|
|
306
|
+
{ path: ".github/workflows/PROJECT-*.yaml", written_by: "full, workflows", note: "Installed per type and options. A file you modified is kept." },
|
|
307
|
+
{ path: ".github/scripts/, .github/config/, .github/util/", written_by: "full, workflows", note: "Helper scripts used by the workflows." },
|
|
308
|
+
{ path: ".github/ISSUE_TEMPLATE/, PULL_REQUEST_TEMPLATE.md", written_by: "full, issues", note: "Language follows options.language." },
|
|
309
|
+
{ path: ".coderabbit.yaml", written_by: "full (only with --coderabbit)", note: "Overwritten with a .bak backup if present." },
|
|
310
|
+
{ path: ".github/.projectops/AGENT-GUIDE.md", written_by: "full, version, workflows", note: "Short guide for AI agents working in the repo. Regenerated on every update." },
|
|
311
|
+
{ path: ".github/.projectops/baseline.json", written_by: "full, workflows", note: "Hashes of what was installed. Tracked by git; do not delete." },
|
|
312
|
+
{ path: ".github/.projectops/incoming/", written_by: "full, workflows", note: "New template copies of files that were kept or not installed. Not tracked by git." },
|
|
313
|
+
{ path: ".github/.projectops/logs/", written_by: "full, workflows", note: "Run log and event trace. Not tracked by git." },
|
|
314
|
+
];
|
|
315
|
+
|
|
116
316
|
export function buildSchema(packageVersion = "") {
|
|
117
317
|
return {
|
|
118
318
|
tool: "projectops",
|
|
@@ -120,8 +320,11 @@ export function buildSchema(packageVersion = "") {
|
|
|
120
320
|
schemaVersion: SCHEMA_VERSION,
|
|
121
321
|
usage: "npx projectops --mode <mode> [--type csv] [--force] ... (agents: always pass --force and an explicit --mode)",
|
|
122
322
|
modes: MODES,
|
|
123
|
-
flags: FLAGS,
|
|
124
|
-
versionYml: VERSION_YML,
|
|
323
|
+
flags: FLAGS.map((f) => ({ ...f, ...(FLAG_DETAILS[f.flag] || {}) })),
|
|
324
|
+
versionYml: VERSION_YML.map((k) => ({ ...k, ...(KEY_DETAILS[k.path] || {}) })),
|
|
325
|
+
agentRules: AGENT_RULES,
|
|
326
|
+
recipes: RECIPES,
|
|
327
|
+
outputFiles: OUTPUT_FILES,
|
|
125
328
|
related: {
|
|
126
329
|
diagnose: "npx projectops --mode doctor",
|
|
127
330
|
optionsJson: "npx projectops --mode options --json",
|
|
@@ -134,17 +337,30 @@ export function buildSchema(packageVersion = "") {
|
|
|
134
337
|
export function renderText(schema) {
|
|
135
338
|
const lines = [`projectops ${schema.packageVersion} — options (schema v${schema.schemaVersion})`, "", schema.usage, "", "MODES"];
|
|
136
339
|
for (const m of schema.modes) lines.push(` ${m.name.padEnd(12)} ${m.description}`);
|
|
340
|
+
const detail = (o) => {
|
|
341
|
+
for (const [label, key] of [["when", "when"], ["effect", "effect"], ["if omitted", "omitted"], ["caution", "caution"], ["example", "example"]]) {
|
|
342
|
+
if (o[key]) lines.push(` ${label}: ${o[key]}`);
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
lines.push("", "RULES FOR AGENTS");
|
|
346
|
+
schema.agentRules.forEach((r, i) => lines.push(` ${i + 1}. ${r}`));
|
|
347
|
+
lines.push("", "RECIPES");
|
|
348
|
+
for (const r of schema.recipes) lines.push(` ${r.goal}`, ` ${r.command}`, ...(r.note ? [` (${r.note})`] : []));
|
|
137
349
|
lines.push("", "FLAGS");
|
|
138
350
|
for (const f of schema.flags) {
|
|
139
351
|
const name = [...(f.aliases ?? [f.alias]), f.flag + (f.value ? ` ${f.value}` : ""), f.negation].filter(Boolean).join(", ");
|
|
140
352
|
const desc = f.deprecated ? `(deprecated) ${f.deprecated}` : f.description;
|
|
141
353
|
const extra = [f.values ? `values: ${f.values.join("|")}` : "", f.default !== undefined ? `default: ${f.default}` : ""].filter(Boolean).join("; ");
|
|
142
354
|
lines.push(` ${name}`, ` ${desc}${extra ? ` [${extra}]` : ""}`);
|
|
355
|
+
detail(f);
|
|
143
356
|
}
|
|
144
357
|
lines.push("", "VERSION.YML KEYS");
|
|
145
358
|
for (const k of schema.versionYml) {
|
|
146
359
|
const extra = [k.type, k.values ? k.values.join("|") : "", k.default !== undefined ? `default: ${JSON.stringify(k.default)}` : ""].filter(Boolean).join("; ");
|
|
147
360
|
lines.push(` ${k.path} [${extra}]`, ` ${k.description}`);
|
|
361
|
+
detail(k);
|
|
148
362
|
}
|
|
363
|
+
lines.push("", "FILES WRITTEN");
|
|
364
|
+
for (const o of schema.outputFiles) lines.push(` ${o.path} (${o.written_by})`, ` ${o.note}`);
|
|
149
365
|
return lines.join("\n");
|
|
150
366
|
}
|
package/src/core/version-yml.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// ⚠️ YAML 재직렬화 금지 — 주석이 데이터. .sh heredoc과 바이트 동일한 템플릿 문자열.
|
|
3
3
|
// 실측 기준: template_integrator.sh 2184~2354.
|
|
4
4
|
import { execFileSync } from "node:child_process";
|
|
5
|
+
import { readFileSync } from "node:fs";
|
|
5
6
|
import { DEPLOY_TARGETS, LABEL_STYLES } from "./constants.js";
|
|
6
7
|
import { REPO_LANGUAGES } from "./repo-language.js";
|
|
7
8
|
|
|
@@ -115,6 +116,7 @@ const COMMENTS = {
|
|
|
115
116
|
appRelease: "whether releases go through App Store / Play Store review",
|
|
116
117
|
deploy: "deploy settings remembered by the wizard (non-sensitive, safe to edit by hand)",
|
|
117
118
|
excluded: "workflow files the update must never install (safe to edit by hand)",
|
|
119
|
+
storeLocales: "store release-note languages, first is the default (missing = one text for every store language)",
|
|
118
120
|
},
|
|
119
121
|
ko: {
|
|
120
122
|
versionCode: "app build number",
|
|
@@ -130,6 +132,7 @@ const COMMENTS = {
|
|
|
130
132
|
appRelease: "앱스토어·플레이스토어 심사로 이어지는 배포인가",
|
|
131
133
|
deploy: "마법사가 기억하는 배포 설정 (비민감 / 직접 수정 가능)",
|
|
132
134
|
excluded: "업데이트가 절대 설치하지 않을 워크플로우 파일 (직접 수정 가능)",
|
|
135
|
+
storeLocales: "스토어 릴리스 노트 언어 목록, 첫 항목이 기본 언어 (미기재면 모든 스토어 언어에 같은 문구)",
|
|
133
136
|
},
|
|
134
137
|
};
|
|
135
138
|
const commentsFor = (lang) => COMMENTS[lang === "ko" ? "ko" : "en"];
|
|
@@ -158,7 +161,7 @@ export function resolveUpdatedBy(existingContent = "", cwd = ".") {
|
|
|
158
161
|
export function parseTemplateOptions(content) {
|
|
159
162
|
const out = { deploy: null, publish: null, secretBackup: null,
|
|
160
163
|
changelogProvider: null, changelogBaseUrl: null, codeReviewCoderabbit: null, aiPrSummary: null, projectsSync: null,
|
|
161
|
-
deployBranch: null, intent: null, semverAuto: null, appRelease: null, labelStyle: null, closeOnRelease: null, language: null, excludedWorkflows: null };
|
|
164
|
+
deployBranch: null, intent: null, semverAuto: null, appRelease: null, labelStyle: null, closeOnRelease: null, language: null, excludedWorkflows: null, storeLocales: null };
|
|
162
165
|
// deploy_branch는 metadata 직속(#456) — template.options 밖이라 별도로 스캔한다.
|
|
163
166
|
for (const line of String(content || "").split("\n")) {
|
|
164
167
|
if (line.startsWith("#")) continue;
|
|
@@ -266,6 +269,12 @@ export function parseTemplateOptions(content) {
|
|
|
266
269
|
out.excludedWorkflows = m[1].split(",").map((s) => s.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
|
|
267
270
|
continue;
|
|
268
271
|
}
|
|
272
|
+
// 스토어 릴리스 노트 언어 (#829) — 인라인 배열, 첫 항목이 기본 언어. 미기재면 현행 동작(한 문구를 모든 언어에).
|
|
273
|
+
m = line.match(/^\s+store_locales:\s*\[([^\]]*)\]/);
|
|
274
|
+
if (m) {
|
|
275
|
+
out.storeLocales = m[1].split(",").map((s) => s.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
269
278
|
// 프로젝트 성격(#553) — 앱 심사로 이어지는 레포인가. 워크플로우와 스킬이 같은 값을 본다.
|
|
270
279
|
m = line.match(/^\s+app_release:\s*["']?(true|false)["']?/);
|
|
271
280
|
if (m) {
|
|
@@ -398,6 +407,86 @@ export function parseExisting(content) {
|
|
|
398
407
|
return { version, versionCode, types, paths, templateVersion, templateMode, options, defaultBranch, deployBranch };
|
|
399
408
|
}
|
|
400
409
|
|
|
410
|
+
// ── 생성기가 모르는 키를 지키기 (#835) ─────────────────────────────────────────────
|
|
411
|
+
// version.yml 은 매 full/version 실행마다 처음부터 다시 쓴다. 생성기가 아는 키만 쓰면 사용자가 넣은 것은 사라진다 —
|
|
412
|
+
// 특히 문서화된 `options.issue_helper`(브랜치 접두사·커밋 템플릿·시간대 …)가 업데이트마다 조용히 초기화됐다.
|
|
413
|
+
// 그래서 아는 키는 지금처럼 만들고, 모르는 키는 **글자 그대로**(들여쓰기·주석 포함) 되돌려 쓴다.
|
|
414
|
+
// project_type(단수)는 v4.1.0 에서 없앴다 — 남기면 version_manager 가 거부한다. 그래서 보존하지 않고 버린다.
|
|
415
|
+
const OWNED_TOP = new Set(["version", "version_code", "project_types", "project_type", "project_paths", "metadata", "deploy"]);
|
|
416
|
+
// metadata.template.options 아래에서 생성기가 직접 쓰는 키. 여기에 없는 것만 보존한다.
|
|
417
|
+
// nexus · npm_publish · synology 는 옛 키다 — 파서가 신형(publish · secret_backup)으로 바꿔 쓰므로 그대로 두면 이중 기록이 된다.
|
|
418
|
+
const OWNED_OPTION = new Set(["intent", "deploy", "publish", "secret_backup", "semver_auto", "projects_sync", "close_on_release",
|
|
419
|
+
"language", "label_style", "app_release", "excluded_workflows", "store_locales", "code_review", "changelog",
|
|
420
|
+
"nexus", "npm_publish", "synology"]);
|
|
421
|
+
|
|
422
|
+
// indent 칸으로 시작하는 키 한 줄과 그 자식(더 깊은 들여쓰기·빈 줄·그 아래의 주석)을 한 덩어리로 자른다.
|
|
423
|
+
function keyBlocks(lines, indent, isOwned) {
|
|
424
|
+
const keyRe = new RegExp(`^ {${indent}}([A-Za-z_][\\w-]*):`);
|
|
425
|
+
const blocks = [];
|
|
426
|
+
for (let i = 0; i < lines.length; i++) {
|
|
427
|
+
const m = lines[i].match(keyRe);
|
|
428
|
+
if (!m) continue;
|
|
429
|
+
let j = i + 1;
|
|
430
|
+
while (j < lines.length && (lines[j].trim() === "" || /^\s/.test(lines[j]) && lines[j].search(/\S/) > indent
|
|
431
|
+
|| (lines[j].trim().startsWith("#") && lines[j].search(/\S/) > indent))) j++;
|
|
432
|
+
while (j > i + 1 && lines[j - 1].trim() === "") j--; // 덩어리 끝의 빈 줄은 뺀다
|
|
433
|
+
const block = lines.slice(i, j).join("\n");
|
|
434
|
+
if (!isOwned(m[1]) && looksWellFormed(block)) blocks.push(block);
|
|
435
|
+
i = j - 1;
|
|
436
|
+
}
|
|
437
|
+
return blocks;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// 되돌려 쓸 덩어리가 깨진 YAML 이면 version.yml 전체가 깨진다 — 그러면 모든 워크플로가 버전을 못 읽는다.
|
|
441
|
+
// 의존성 없이 확인할 수 있는 만큼만 본다: 괄호·중괄호 짝, 닫히지 않은 따옴표. 의심스러우면 버린다(보존보다 파일 무결성이 먼저).
|
|
442
|
+
function looksWellFormed(block) {
|
|
443
|
+
const text = block.split("\n").map((l) => l.replace(/\s#.*$/, "")).join("\n"); // 줄 끝 주석은 짝 검사에서 뺀다
|
|
444
|
+
const stack = [];
|
|
445
|
+
const pairs = { "]": "[", "}": "{" };
|
|
446
|
+
let quote = null;
|
|
447
|
+
for (const ch of text) {
|
|
448
|
+
if (quote) { if (ch === quote) quote = null; continue; }
|
|
449
|
+
if (ch === '"' || ch === "'") { quote = ch; continue; }
|
|
450
|
+
if (ch === "[" || ch === "{") stack.push(ch);
|
|
451
|
+
else if (ch === "]" || ch === "}") { if (stack.pop() !== pairs[ch]) return false; }
|
|
452
|
+
}
|
|
453
|
+
return !quote && stack.length === 0;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/** 기존 version.yml 에서 생성기가 모르는 것만 뽑는다. { optionBlocks: string[], topBlocks: string[] } */
|
|
457
|
+
export function extractCustomYml(content) {
|
|
458
|
+
const lines = String(content || "").split(/\r?\n/);
|
|
459
|
+
const topBlocks = keyBlocks(lines, 0, (k) => OWNED_TOP.has(k));
|
|
460
|
+
// metadata → template → options 구간만 잘라 6칸 들여쓴 키를 본다
|
|
461
|
+
const find = (from, to, re) => { for (let i = from; i < to; i++) if (re.test(lines[i])) return i; return -1; };
|
|
462
|
+
const end = (start, indent) => { let j = start + 1; while (j < lines.length && (lines[j].trim() === "" || lines[j].search(/\S/) > indent)) j++; return j; };
|
|
463
|
+
const md = find(0, lines.length, /^metadata:/);
|
|
464
|
+
let optionBlocks = [];
|
|
465
|
+
if (md >= 0) {
|
|
466
|
+
const mdEnd = end(md, 0);
|
|
467
|
+
const tp = find(md + 1, mdEnd, /^ {2}template:/);
|
|
468
|
+
if (tp >= 0) {
|
|
469
|
+
const tpEnd = end(tp, 2);
|
|
470
|
+
const op = find(tp + 1, tpEnd, /^ {4}options:/);
|
|
471
|
+
if (op >= 0) optionBlocks = keyBlocks(lines.slice(op + 1, end(op, 4)), 6, (k) => OWNED_OPTION.has(k));
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return { optionBlocks, topBlocks };
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* buildVersionYml 에 그대로 펼쳐 넣는 도우미: `...customYml(경로)`.
|
|
479
|
+
* 파일이 없거나 읽을 수 없으면 아무것도 보존하지 않는다 — 읽기 실패가 업데이트를 막으면 안 된다.
|
|
480
|
+
*/
|
|
481
|
+
export function customYml(file) {
|
|
482
|
+
try {
|
|
483
|
+
const { optionBlocks, topBlocks } = extractCustomYml(readFileSync(file, "utf8"));
|
|
484
|
+
return { customOptionBlocks: optionBlocks, customTopBlocks: topBlocks };
|
|
485
|
+
} catch {
|
|
486
|
+
return {};
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
401
490
|
// 기존 version.yml 의 deploy 블록 → Map<type, Map<key,value>> (#670 — 재실행 시 사용자 수정값 보존용).
|
|
402
491
|
// 4.28.0 이 만든 깨진 구조(deploy 아래에 2칸 들여쓴 template 이 딸려 있음)도 읽을 수 있게,
|
|
403
492
|
// 2칸 키 `template` 은 타입이 아니라 블록 종료로 본다.
|
|
@@ -436,7 +525,7 @@ export function mergeDeployValues(existingContent, fresh) {
|
|
|
436
525
|
// today = "YYYY-MM-DD" (UTC)
|
|
437
526
|
// pathMarkers = Map<type, markerFilename> (project_paths 주석용)
|
|
438
527
|
// templateOptions = { templateVersion, deployTarget, publishTargets, includeSecretBackup, optionsDate } (template 블록)
|
|
439
|
-
export function buildVersionYml({ version, types = [], paths = new Map(), pathMarkers = new Map(), branch = "main", deployBranch = "", versionCode = 1, now, today, templateOptions = null, deployValues = new Map(), updatedBy = "template_integrator" }) {
|
|
528
|
+
export function buildVersionYml({ version, types = [], paths = new Map(), pathMarkers = new Map(), branch = "main", deployBranch = "", versionCode = 1, now, today, templateOptions = null, deployValues = new Map(), updatedBy = "template_integrator", customOptionBlocks = [], customTopBlocks = [] }) {
|
|
440
529
|
const lang = templateOptions?.language === "ko" ? "ko" : "en";
|
|
441
530
|
const C = commentsFor(lang);
|
|
442
531
|
const typesJson = types.length ? `[${types.map((t) => `"${t}"`).join(",")}]` : `["basic"]`;
|
|
@@ -470,7 +559,7 @@ export function buildVersionYml({ version, types = [], paths = new Map(), pathMa
|
|
|
470
559
|
if (templateOptions) {
|
|
471
560
|
const { templateVersion = "unknown", deployTarget = "docker-ssh", publishTargets = [], includeSecretBackup = false, optionsDate = today,
|
|
472
561
|
changelogProvider = "commit", changelogBaseUrl = "", codeReviewCoderabbit = true, aiPrSummary = true, intent = null, mode = null,
|
|
473
|
-
semverAuto = true, appRelease = null, labelStyle = null, closeOnRelease = null, projectsSync = null, language = null, excludedWorkflows = null } = templateOptions;
|
|
562
|
+
semverAuto = true, appRelease = null, labelStyle = null, closeOnRelease = null, projectsSync = null, language = null, excludedWorkflows = null, storeLocales = null } = templateOptions;
|
|
474
563
|
const publishJson = `[${publishTargets.map((t) => `"${t}"`).join(",")}]`;
|
|
475
564
|
// intent(프로젝트 성격, #485) — 미지정이면 deploy/publish에서 역추론해 기록 (재통합 시 진입 질문 생략용)
|
|
476
565
|
const intentVal = intent || inferIntent(deployTarget, publishTargets) || "manual";
|
|
@@ -496,6 +585,8 @@ export function buildVersionYml({ version, types = [], paths = new Map(), pathMa
|
|
|
496
585
|
// 레포 문구 언어(#769) — 미지정이면 키를 쓰지 않는다(기존 레포 무변화).
|
|
497
586
|
// 설치 제외 목록(#810) — 전체 재생성이라 다시 쓰지 않으면 사용자가 적은 목록이 사라진다.
|
|
498
587
|
if (excludedWorkflows && excludedWorkflows.length) out += ` excluded_workflows: [${excludedWorkflows.map((f) => `"${f}"`).join(", ")}] # ${C.excluded}\n`;
|
|
588
|
+
// 스토어 릴리스 노트 언어(#829) — 미지정이면 키를 쓰지 않는다. 전체 재생성이라 다시 쓰지 않으면 사용자가 적은 값이 사라진다.
|
|
589
|
+
if (storeLocales && storeLocales.length) out += ` store_locales: [${storeLocales.map((l) => `"${l}"`).join(", ")}] # ${C.storeLocales}\n`;
|
|
499
590
|
if (language) out += ` language: ${language} # ${C.language}\n`;
|
|
500
591
|
if (labelStyle) out += ` label_style: ${labelStyle} # ${C.labelStyle}\n`;
|
|
501
592
|
// 앱 심사 배포 레포 여부(#553) — 미지정이면 키를 쓰지 않는다(기존 레포 무변화).
|
|
@@ -508,6 +599,8 @@ export function buildVersionYml({ version, types = [], paths = new Map(), pathMa
|
|
|
508
599
|
out += ` changelog:\n`;
|
|
509
600
|
out += ` provider: "${changelogProvider}"\n`;
|
|
510
601
|
out += ` base_url: "${changelogBaseUrl}"\n`;
|
|
602
|
+
// 생성기가 모르는 옵션(issue_helper 등)은 그대로 되돌려 쓴다 (#835)
|
|
603
|
+
for (const b of customOptionBlocks) out += `${b}\n`;
|
|
511
604
|
}
|
|
512
605
|
|
|
513
606
|
// deploy 블록 (.sh update_version_yml_deploy). deployValues: Map<type, Map<key,value>>.
|
|
@@ -522,5 +615,7 @@ export function buildVersionYml({ version, types = [], paths = new Map(), pathMa
|
|
|
522
615
|
for (const [k, v] of deployValues.get(t)) out += ` ${k}: "${v}"\n`;
|
|
523
616
|
}
|
|
524
617
|
}
|
|
618
|
+
// 최상위의 모르는 키도 그대로 (#835)
|
|
619
|
+
for (const b of customTopBlocks) out += `\n${b}\n`;
|
|
525
620
|
return out;
|
|
526
621
|
}
|
package/src/i18n/doctor.en.js
CHANGED
|
@@ -7,6 +7,7 @@ export default {
|
|
|
7
7
|
"doctor.integ.notIntegrated": "The template is not integrated in this folder.",
|
|
8
8
|
"doctor.integ.howTo": "To integrate: npx projectops",
|
|
9
9
|
"doctor.integ.versions": "Template v{tpl} / Project v{proj}",
|
|
10
|
+
"doctor.integ.versionsNoTemplate": "Template version not recorded / Project v{proj}",
|
|
10
11
|
"doctor.wf.name": "Installed workflows",
|
|
11
12
|
"doctor.wf.purpose": "Automation running in this repository",
|
|
12
13
|
"doctor.count": "{n}",
|
package/src/i18n/doctor.ko.js
CHANGED
|
@@ -7,6 +7,7 @@ export default {
|
|
|
7
7
|
"doctor.integ.notIntegrated": "이 폴더에는 템플릿이 통합되어 있지 않습니다.",
|
|
8
8
|
"doctor.integ.howTo": "통합하려면: npx projectops",
|
|
9
9
|
"doctor.integ.versions": "템플릿 v{tpl} / 프로젝트 v{proj}",
|
|
10
|
+
"doctor.integ.versionsNoTemplate": "템플릿 버전 기록 없음 / 프로젝트 v{proj}",
|
|
10
11
|
"doctor.wf.name": "설치된 워크플로우",
|
|
11
12
|
"doctor.wf.purpose": "이 저장소에서 도는 자동화",
|
|
12
13
|
"doctor.count": "{n}개",
|
package/src/index.js
CHANGED
|
@@ -20,6 +20,7 @@ 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
22
|
import { applicableTargets, migrateProvider } from "./core/options-ask.js";
|
|
23
|
+
import { DEFAULT_CODE_REVIEW_CODERABBIT } from "./core/constants.js";
|
|
23
24
|
import { printBannerCompact } from "./ui/banner.js";
|
|
24
25
|
import { printSummary } from "./ui/summary.js";
|
|
25
26
|
import { runFull } from "./commands/full.js";
|
|
@@ -252,7 +253,8 @@ async function runCore(argv, { cwd = process.cwd(), source = { type: "git" }, cl
|
|
|
252
253
|
// changelog/code_review 축(#455): 비대화형은 저장값 → 기본값. null이 흘러 provider:"null"로 기록되던 버그 수정.
|
|
253
254
|
changelogProvider: migrateProvider(existing?.options?.changelogProvider) ?? "commit",
|
|
254
255
|
changelogBaseUrl: existing?.options?.changelogBaseUrl ?? "",
|
|
255
|
-
|
|
256
|
+
// 플래그 > 저장값 > 기본(꺼짐). 쓰겠다고 명시하면 켜지고, 아무 말이 없을 때만 기본값이 적용된다.
|
|
257
|
+
codeReviewCoderabbit: opts.codeReviewCoderabbit ?? existing?.options?.codeReviewCoderabbit ?? DEFAULT_CODE_REVIEW_CODERABBIT,
|
|
256
258
|
// semver 자동 승격(#546, #814): 저장값(명시적 false 포함)은 존중하고, 키가 없으면 신규·기존 모두 켠다.
|
|
257
259
|
// 기존 레포에서 처음 켜질 때는 버전이 예고 없이 오르지 않게 완료 화면이 알린다 (semverAutoNewlyOn).
|
|
258
260
|
semverAuto: existing?.options?.semverAuto ?? true,
|
|
@@ -260,6 +262,7 @@ async function runCore(argv, { cwd = process.cwd(), source = { type: "git" }, cl
|
|
|
260
262
|
// (사용자가 version.yml에 직접 쓰거나 스킬이 기록한 값을 그대로 유지).
|
|
261
263
|
appRelease: existing?.options?.appRelease ?? null,
|
|
262
264
|
excludedWorkflows: existing?.options?.excludedWorkflows ?? null, // #810 저장값 보존
|
|
265
|
+
storeLocales: existing?.options?.storeLocales ?? null, // #829 저장값 보존 (전체 재생성이라 안 넘기면 사라진다)
|
|
263
266
|
// 상태 라벨 표기(#776): 플래그 > 저장값 > (신규 en / 기존 ko). 기존 레포의 라벨 이름은 업데이트만으로 바뀌지 않는다.
|
|
264
267
|
// 릴리스 시 완료 이슈 닫기(#771): 저장값 보존, 없으면 신규만 true. 기존 레포는 키를 만들지 않아 현행 유지.
|
|
265
268
|
closeOnRelease: existing?.options?.closeOnRelease ?? (existing ? null : true),
|
|
@@ -361,7 +364,7 @@ async function runCore(argv, { cwd = process.cwd(), source = { type: "git" }, cl
|
|
|
361
364
|
migrationGuidePath = appendGuideEntry(cwd, {
|
|
362
365
|
now, mode: opts.mode, types, repoName,
|
|
363
366
|
templateFrom: existing?.templateVersion || "", templateTo: context.templateVersion,
|
|
364
|
-
options: { deploy: deployTarget, publish: publishTargets, secretBackup: context.includeSecretBackup, coderabbit: context.codeReviewCoderabbit, changelogProvider: context.changelogProvider, intent, semverAuto: context.semverAuto , appRelease: context.appRelease, language: context.language, labelStyle: context.labelStyle, closeOnRelease: context.closeOnRelease, projectsSync: context.projectsSync, excludedWorkflows: context.excludedWorkflows },
|
|
367
|
+
options: { deploy: deployTarget, publish: publishTargets, secretBackup: context.includeSecretBackup, coderabbit: context.codeReviewCoderabbit, changelogProvider: context.changelogProvider, intent, semverAuto: context.semverAuto , appRelease: context.appRelease, language: context.language, labelStyle: context.labelStyle, closeOnRelease: context.closeOnRelease, projectsSync: context.projectsSync, excludedWorkflows: context.excludedWorkflows, storeLocales: context.storeLocales },
|
|
365
368
|
branches: { defaultBranch: branch, deployBranch: context.deployBranch || "develop", ready: null, created: null },
|
|
366
369
|
breaking: breakingReport, migrations: migrationsResult, orphans: { cleaned: [], pending: orphanPending },
|
|
367
370
|
events: trace.events, counters: { skipped: result?.workflows?.skipped ?? 0 },
|