projectops 4.36.4 → 4.38.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 +17 -16
- package/package.json +1 -1
- package/src/cli/args.js +10 -5
- package/src/cli/help.js +8 -2
- package/src/commands/doctor.js +16 -0
- package/src/commands/full.js +6 -3
- package/src/commands/interactive.js +9 -4
- package/src/commands/version.js +6 -3
- package/src/commands/workflows.js +2 -0
- package/src/context.js +2 -1
- package/src/core/agent-guide.js +44 -0
- package/src/core/constants.js +28 -0
- package/src/core/copy/simple.js +3 -2
- package/src/core/copy/workflows.js +85 -7
- package/src/core/exclusions.js +2 -0
- package/src/core/fsutil.js +2 -2
- package/src/core/migrations/index.js +17 -2
- package/src/core/options-ask.js +5 -4
- package/src/core/options-schema.js +150 -0
- package/src/core/version-yml.js +135 -24
- package/src/core/wizard-env.js +8 -0
- package/src/i18n/doctor.en.js +4 -0
- package/src/i18n/doctor.ko.js +4 -0
- package/src/i18n/migrate.en.js +3 -0
- package/src/i18n/migrate.ko.js +3 -0
- package/src/i18n/summary.en.js +10 -0
- package/src/i18n/summary.ko.js +10 -0
- package/src/index.js +18 -6
- package/src/ui/summary.js +32 -0
package/README.md
CHANGED
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/projectops) [](https://github.com/Cassiiopeia/projectops/releases) [](LICENSE) [](https://cassiiopeia.github.io/projectops/)
|
|
8
8
|
|
|
9
|
-
**Status: actively maintained.**
|
|
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.37.0 (2026-10-10)
|
|
13
13
|
|
|
14
14
|
[View full version history](CHANGELOG.md)
|
|
15
15
|
|
|
@@ -178,7 +178,7 @@ flowchart TD
|
|
|
178
178
|
K --> L["/pro-changelog-deploy<br/>Release PR + automerge"]
|
|
179
179
|
```
|
|
180
180
|
|
|
181
|
-
> Full list of Skills and usage: **[docs/
|
|
181
|
+
> Full list of Skills and usage: **[docs/en/skills.md](docs/en/skills.md)**
|
|
182
182
|
|
|
183
183
|
### GitHub Actions pipeline
|
|
184
184
|
|
|
@@ -215,14 +215,14 @@ codex plugin marketplace add Cassiiopeia/projectops
|
|
|
215
215
|
|
|
216
216
|
The `--mode skills` wizard registers the Codex marketplace and also prepares the native skills fallback. Use `/plugins` only to check or manage the install.
|
|
217
217
|
|
|
218
|
-
Where the Codex plugin marketplace is unavailable, use the fallback install described in the [Skills guide](docs/
|
|
218
|
+
Where the Codex plugin marketplace is unavailable, use the fallback install described in the [Skills guide](docs/en/skills.md).
|
|
219
219
|
|
|
220
220
|
```bash
|
|
221
221
|
# Cursor / the full Agent Skills install menu (recommended: npx)
|
|
222
222
|
npx projectops --mode skills
|
|
223
223
|
```
|
|
224
224
|
|
|
225
|
-
> Claude Code prefers `/pro-` autocomplete, Gemini prefers its extension, and Codex prefers its plugin marketplace. See the [Skills guide](docs/
|
|
225
|
+
> Claude Code prefers `/pro-` autocomplete, Gemini prefers its extension, and Codex prefers its plugin marketplace. See the [Skills guide](docs/en/skills.md) for details.
|
|
226
226
|
|
|
227
227
|
</details>
|
|
228
228
|
|
|
@@ -257,7 +257,7 @@ Run automation by commenting on an issue or PR.
|
|
|
257
257
|
| `@projectops ios build` | Build iOS only | Flutter |
|
|
258
258
|
| `@projectops create qa` | Create a QA issue automatically | All projects |
|
|
259
259
|
|
|
260
|
-
> Details: [PR Preview](docs/
|
|
260
|
+
> Details: [PR Preview](docs/en/pr-preview.md) | [Flutter builds](docs/FLUTTER-TEST-BUILD-TRIGGER.md) | [Issue automation](docs/en/issue-automation.md)
|
|
261
261
|
|
|
262
262
|
---
|
|
263
263
|
|
|
@@ -300,25 +300,25 @@ We would rather tell you up front when this is not a fit.
|
|
|
300
300
|
- **Releases assume a develop branch → default branch PR flow.** Branch names can be changed in `version.yml`, but it does not suit a repo that does not use two branches.
|
|
301
301
|
- **A personal access token is optional.** You need one only to merge past branch protection or to sync a Projects board. See [Setup](#setup).
|
|
302
302
|
- **It adds about 50 files to your repository** (workflows, helper scripts, templates). See [the measurement](#compared-with-other-tools).
|
|
303
|
-
- **New versions ship often.** Pin a version you have tested with `npx projectops@<version>`.
|
|
303
|
+
- **New versions ship often.** `latest` follows every push to `main` with no delay. Pin a version you have tested with `npx projectops@<version>`.
|
|
304
304
|
- **Server deploy workflows assume a Docker server reachable over SSH.**
|
|
305
305
|
|
|
306
306
|
---
|
|
307
307
|
|
|
308
308
|
## Documentation
|
|
309
309
|
|
|
310
|
-
Browse and search everything on the **[docs site](https://cassiiopeia.github.io/projectops/en/)**.
|
|
310
|
+
Browse and search everything on the **[docs site](https://cassiiopeia.github.io/projectops/en/)**. The main guides are in English. A few wizard pages are still Korean only (translations are welcome, see [#799](https://github.com/Cassiiopeia/projectops/issues/799)).
|
|
311
311
|
|
|
312
312
|
- [Getting started](https://cassiiopeia.github.io/projectops/en/getting-started)
|
|
313
313
|
- [CLI reference](https://cassiiopeia.github.io/projectops/en/cli)
|
|
314
|
-
- [Agent Skills guide
|
|
315
|
-
- [Versioning
|
|
316
|
-
- [Changelog automation
|
|
317
|
-
- [PR Preview
|
|
318
|
-
- [Issue automation
|
|
319
|
-
- [SSH + Docker deploy
|
|
320
|
-
- [Flutter CI/CD
|
|
321
|
-
- [Troubleshooting
|
|
314
|
+
- [Agent Skills guide](https://cassiiopeia.github.io/projectops/en/skills)
|
|
315
|
+
- [Versioning](https://cassiiopeia.github.io/projectops/en/version-control)
|
|
316
|
+
- [Changelog automation](https://cassiiopeia.github.io/projectops/en/changelog-automation)
|
|
317
|
+
- [PR Preview](https://cassiiopeia.github.io/projectops/en/pr-preview)
|
|
318
|
+
- [Issue automation](https://cassiiopeia.github.io/projectops/en/issue-automation)
|
|
319
|
+
- [SSH + Docker deploy](https://cassiiopeia.github.io/projectops/en/ssh-docker-deployment-guide)
|
|
320
|
+
- [Flutter CI/CD](https://cassiiopeia.github.io/projectops/en/flutter-cicd-overview)
|
|
321
|
+
- [Troubleshooting](https://cassiiopeia.github.io/projectops/en/troubleshooting)
|
|
322
322
|
|
|
323
323
|
---
|
|
324
324
|
|
|
@@ -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
|
+
- [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md): how we expect everyone to behave, and where to report problems
|
|
330
331
|
- [SECURITY.md](SECURITY.md): please report security vulnerabilities privately, not in a public issue
|
|
331
332
|
|
|
332
333
|
---
|
package/package.json
CHANGED
package/src/cli/args.js
CHANGED
|
@@ -5,17 +5,18 @@ import { VALID_TYPES } from "../context.js";
|
|
|
5
5
|
import { SUPPORTED_LANGS, t } from "../i18n/index.js";
|
|
6
6
|
import { REPO_LANGUAGES } from "../core/repo-language.js";
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
import { DEPLOY_TARGETS, PUBLISH_TARGETS, INTENT_VALUES, LABEL_STYLES } from "../core/constants.js";
|
|
9
|
+
|
|
10
|
+
// 값 목록은 core/constants.js 가 정본이다. 기존 import 경로를 깨지 않도록 여기서 다시 내보낸다.
|
|
11
|
+
export { DEPLOY_TARGETS, PUBLISH_TARGETS, INTENT_VALUES };
|
|
12
|
+
export const LABEL_STYLE_VALUES = LABEL_STYLES;
|
|
11
13
|
// 지원 언어의 단일 목록은 repo-language.js 다 (#787). 여기서 따로 들고 있으면 두 벌이 어긋난다.
|
|
12
14
|
export const LANGUAGE_VALUES = REPO_LANGUAGES;
|
|
13
|
-
export const INTENT_VALUES = ["app", "library", "both", "none", "manual"];
|
|
14
15
|
const SEMVER_RE = /^\d+\.\d+\.\d+$/;
|
|
15
16
|
// 유니코드 글자·숫자 허용 (#719) — 공백·셸 메타문자는 문자 클래스에 없어 그대로 거부된다.
|
|
16
17
|
const BRANCH_RE = /^[\p{L}\p{N}][\p{L}\p{N}._/-]*$/u;
|
|
17
18
|
// 허용 실행 모드 (#665) — 오타가 조용히 "복사 0건 성공"으로 끝나지 않게 한다.
|
|
18
|
-
export const MODE_VALUES = ["full", "version", "workflows", "issues", "skills", "doctor", "interactive"];
|
|
19
|
+
export const MODE_VALUES = ["full", "version", "workflows", "issues", "skills", "doctor", "options", "interactive"];
|
|
19
20
|
|
|
20
21
|
// argv(process.argv.slice(2)) → 파싱 결과. 오류 시 throw(호출부에서 exit 1).
|
|
21
22
|
export function parseArgs(argv) {
|
|
@@ -33,6 +34,8 @@ export function parseArgs(argv) {
|
|
|
33
34
|
includeSecretBackup: null,
|
|
34
35
|
aiPrSummary: null, // #566 — AI 변경 요약 워크플로우 포함 여부
|
|
35
36
|
projectsSync: null, // #716 — Projects 보드 동기화 워크플로우 포함 여부 (null=미지정)
|
|
37
|
+
json: false, // --json: 기계가 읽는 출력 (--mode options)
|
|
38
|
+
removeLegacy: false, // #809 — 구세대 배포 워크플로우(confirm 티어)도 .bak 으로 치운다
|
|
36
39
|
pathsCsv: "", // "flutter=app,react=client" 원문 (정규화는 resolve 단계)
|
|
37
40
|
force: false,
|
|
38
41
|
lang: null, // --lang en|ko (null = decided by src/i18n resolveLang)
|
|
@@ -170,6 +173,8 @@ export function parseArgs(argv) {
|
|
|
170
173
|
case "--no-ai-summary": result.aiPrSummary = false; break;
|
|
171
174
|
case "--projects-sync": result.projectsSync = true; break;
|
|
172
175
|
case "--no-projects-sync": result.projectsSync = false; break;
|
|
176
|
+
case "--remove-legacy": result.removeLegacy = true; break;
|
|
177
|
+
case "--json": result.json = true; break;
|
|
173
178
|
case "--npm-publish":
|
|
174
179
|
process.stderr.write(t("args.npmPublishDeprecated") + "\n");
|
|
175
180
|
result.publishTargets = [...new Set([...(result.publishTargets ?? []), "npm"])];
|
package/src/cli/help.js
CHANGED
|
@@ -7,8 +7,9 @@ Usage:
|
|
|
7
7
|
npx projectops [options]
|
|
8
8
|
|
|
9
9
|
Options:
|
|
10
|
-
-m, --mode MODE Mode (full | version | workflows | issues | skills | doctor)
|
|
10
|
+
-m, --mode MODE Mode (full | version | workflows | issues | skills | doctor | options)
|
|
11
11
|
doctor: diagnose the installation and repository settings (read-only)
|
|
12
|
+
options: print every flag and version.yml key; add --json for agents
|
|
12
13
|
default: interactive
|
|
13
14
|
-t, --type CSV Project types, comma separated (e.g. spring,react,python)
|
|
14
15
|
supported: spring flutter react react-native
|
|
@@ -26,7 +27,9 @@ Options:
|
|
|
26
27
|
--secret-backup / --no-secret-backup Include / exclude the secret backup workflow
|
|
27
28
|
--ai-summary / --no-ai-summary Include / exclude the PR summary workflow
|
|
28
29
|
--projects-sync / --no-projects-sync Include / exclude the GitHub Projects status sync workflow (default: off for new installs)
|
|
30
|
+
--remove-legacy Rename retired old-generation workflows to .bak (otherwise only listed)
|
|
29
31
|
--nexus / --npm-publish (deprecated: use --publish nexus / --publish npm)
|
|
32
|
+
--json With --mode options: machine-readable output
|
|
30
33
|
--force, -y, --yes Skip every confirmation and use non-interactive defaults
|
|
31
34
|
--lang en|ko Display language (default: system language, pinned to en in CI and with --force)
|
|
32
35
|
-v, --version Print the projectops version
|
|
@@ -45,8 +48,9 @@ const HELP_KO = `projectops — GitHub 프로젝트 자동화 템플릿 통합 C
|
|
|
45
48
|
npx projectops [옵션]
|
|
46
49
|
|
|
47
50
|
옵션:
|
|
48
|
-
-m, --mode MODE 통합 모드 (full | version | workflows | issues | skills | doctor)
|
|
51
|
+
-m, --mode MODE 통합 모드 (full | version | workflows | issues | skills | doctor | options)
|
|
49
52
|
doctor: 통합 상태·저장소 설정 진단 (읽기 전용)
|
|
53
|
+
options: 모든 플래그·version.yml 키 출력 (--json 이면 agent용 JSON)
|
|
50
54
|
기본: interactive (대화형)
|
|
51
55
|
-t, --type CSV 프로젝트 타입 csv (예: spring,react,python)
|
|
52
56
|
지원: spring flutter react react-native
|
|
@@ -64,7 +68,9 @@ const HELP_KO = `projectops — GitHub 프로젝트 자동화 템플릿 통합 C
|
|
|
64
68
|
--secret-backup / --no-secret-backup Secret 백업 워크플로우 포함/제외
|
|
65
69
|
--ai-summary / --no-ai-summary PR 변경 요약 워크플로우 포함/제외
|
|
66
70
|
--projects-sync / --no-projects-sync GitHub Projects 상태 동기화 워크플로우 포함/제외 (신규 설치 기본 제외)
|
|
71
|
+
--remove-legacy 은퇴한 구세대 워크플로우를 .bak 으로 치움 (없으면 목록만 안내)
|
|
67
72
|
--nexus / --npm-publish (deprecated — --publish nexus / --publish npm 사용)
|
|
73
|
+
--json --mode options 와 함께: 기계가 읽는 JSON 출력
|
|
68
74
|
--force, -y, --yes 모든 확인 생략, 비대화형 기본값 사용
|
|
69
75
|
--lang en|ko 화면 언어 (기본: 시스템 언어, CI/--force 는 en 고정)
|
|
70
76
|
-v, --version projectops 버전 출력
|
package/src/commands/doctor.js
CHANGED
|
@@ -19,6 +19,7 @@ import { parseExisting } from "../core/version-yml.js";
|
|
|
19
19
|
import { verifyInstall } from "../core/verify.js";
|
|
20
20
|
import { readBaseline } from "../core/baseline.js";
|
|
21
21
|
import { detectRepoName } from "../core/detect-fs.js";
|
|
22
|
+
import { detectMigrations } from "../core/migrations/index.js";
|
|
22
23
|
import { t } from "../i18n/index.js";
|
|
23
24
|
|
|
24
25
|
// 언어 전환(withLang 등) 시점에 평가되도록 함수로 둔다
|
|
@@ -149,6 +150,21 @@ export function localChecks(cwd = ".") {
|
|
|
149
150
|
});
|
|
150
151
|
}
|
|
151
152
|
|
|
153
|
+
// 은퇴한 구세대 워크플로우 (#809) — 업데이트는 감지만 하고 남겨 두므로 신형과 함께 돈다.
|
|
154
|
+
// 예전엔 로그에만 남아 doctor 가 "문제 없음"이라 했다.
|
|
155
|
+
const legacy = detectMigrations(cwd).confirm;
|
|
156
|
+
add({
|
|
157
|
+
name: t("doctor.legacy.name"), purpose: t("doctor.legacy.purpose"),
|
|
158
|
+
status: legacy.length ? "WARN" : "OK",
|
|
159
|
+
value: legacy.length ? t("doctor.count", { n: legacy.length }) : t("doctor.none"),
|
|
160
|
+
detail: legacy.length ? [
|
|
161
|
+
...legacy.map((e) => ` ${e.file}${e.replacedBy ? ` → ${e.replacedBy}` : ""}`),
|
|
162
|
+
t("doctor.legacy.l1"),
|
|
163
|
+
...legacy.map((e) => ` git rm .github/workflows/${e.file}`),
|
|
164
|
+
t("doctor.legacy.l2"),
|
|
165
|
+
] : null,
|
|
166
|
+
});
|
|
167
|
+
|
|
152
168
|
const baseline = readBaseline(cwd);
|
|
153
169
|
add({
|
|
154
170
|
name: t("doctor.bl.name"), purpose: t("doctor.bl.purpose"),
|
package/src/commands/full.js
CHANGED
|
@@ -5,7 +5,8 @@ import { join } from "node:path";
|
|
|
5
5
|
import { existsSync, readFileSync } from "node:fs";
|
|
6
6
|
import { writeText } from "../core/fsutil.js";
|
|
7
7
|
import { PATHS } from "../core/paths.js";
|
|
8
|
-
import {
|
|
8
|
+
import { installAgentGuide } from "../core/agent-guide.js";
|
|
9
|
+
import { buildVersionYml, mergeDeployValues, resolveUpdatedBy } from "../core/version-yml.js";
|
|
9
10
|
import { markerForType } from "../core/detect.js";
|
|
10
11
|
import { addVersionSectionToReadme } from "../core/copy/readme.js";
|
|
11
12
|
import { copyWorkflows } from "../core/copy/workflows.js";
|
|
@@ -29,7 +30,7 @@ export function runFull(context, tempDir, targetRoot = ".", hooks = {}) {
|
|
|
29
30
|
force = true, now, today, templateVersion = "unknown",
|
|
30
31
|
deployTarget = "docker-ssh", publishTargets = [], includeSecretBackup = false, aiPrSummary = true,
|
|
31
32
|
changelogProvider = "commit", changelogBaseUrl = "", codeReviewCoderabbit = true,
|
|
32
|
-
deployBranch = "", intent = null, semverAuto = true , appRelease = null, labelStyle = "en", closeOnRelease = null, projectsSync = null, language = "en" } = context;
|
|
33
|
+
deployBranch = "", intent = null, semverAuto = true , appRelease = null, labelStyle = "en", closeOnRelease = null, projectsSync = null, language = "en", excludedWorkflows = null } = context;
|
|
33
34
|
|
|
34
35
|
// project_paths 마커 계산 (.sh existing_marker_in_dir 등가 — 대표 마커명)
|
|
35
36
|
const pathMarkers = new Map();
|
|
@@ -50,8 +51,9 @@ export function runFull(context, tempDir, targetRoot = ".", hooks = {}) {
|
|
|
50
51
|
buildVersionYml({
|
|
51
52
|
version, types, paths, pathMarkers, branch, deployBranch, versionCode, now, today,
|
|
52
53
|
deployValues,
|
|
54
|
+
updatedBy: resolveUpdatedBy(existsSync(vyFile) ? readFileSync(vyFile, "utf8") : "", targetRoot), // #811
|
|
53
55
|
templateOptions: { templateVersion, deployTarget, publishTargets, includeSecretBackup, aiPrSummary, optionsDate: today,
|
|
54
|
-
changelogProvider, changelogBaseUrl, codeReviewCoderabbit, intent, mode: "full", semverAuto, appRelease, labelStyle, closeOnRelease, projectsSync, language },
|
|
56
|
+
changelogProvider, changelogBaseUrl, codeReviewCoderabbit, intent, mode: "full", semverAuto, appRelease, labelStyle, closeOnRelease, projectsSync, language, excludedWorkflows },
|
|
55
57
|
})), { version, versionCode });
|
|
56
58
|
|
|
57
59
|
// 2. README 버전 섹션
|
|
@@ -86,6 +88,7 @@ export function runFull(context, tempDir, targetRoot = ".", hooks = {}) {
|
|
|
86
88
|
{ enabled: codeReviewCoderabbit });
|
|
87
89
|
step("ensure-gitignore", () => ensureGitignore(targetRoot));
|
|
88
90
|
step("copy-setup-guide", () => copySetupGuide(tempDir, targetRoot));
|
|
91
|
+
step("install-agent-guide", () => installAgentGuide(targetRoot, templateVersion)); // agent 가 옵션을 확인하는 법 안내
|
|
89
92
|
|
|
90
93
|
// 상태 라벨 표기(#776) — 템플릿 원본은 영문이라, 한글 레포는 복사가 끝난 뒤 이름만 되돌린다.
|
|
91
94
|
step("apply-label-style", () => applyLabelStyle(targetRoot, labelStyle), { labelStyle });
|
|
@@ -137,11 +137,13 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
|
|
|
137
137
|
let deployBranchCreated = null; // #493 — 마법사가 직접 생성했는지 (가이드 기록용)
|
|
138
138
|
let intent = existing?.options?.intent ?? null; // #485 프로젝트 성격
|
|
139
139
|
// semver 자동 승격(#546) — 질문하지 않는다(#485 질문 부담 축소 방향 유지).
|
|
140
|
-
//
|
|
141
|
-
const semverAuto = existing?.options?.semverAuto ??
|
|
140
|
+
// 저장값(명시적 false 포함)은 보존하고, 키가 없으면 기존 레포도 ON. 켜진 사실은 완료 화면이 알린다.
|
|
141
|
+
const semverAuto = existing?.options?.semverAuto ?? true;
|
|
142
|
+
const semverAutoNewlyOn = !!existing && existing?.options?.semverAuto == null;
|
|
142
143
|
const appRelease = existing?.options?.appRelease ?? null; // #553 저장값 보존 (묻지 않음)
|
|
143
144
|
// 상태 라벨 표기(#776): 저장값 > (신규 en / 기존 ko). 기존 한글 레포에는 영문 전환을 한 번 제안한다(아래).
|
|
144
145
|
const closeOnRelease = existing?.options?.closeOnRelease ?? (existing ? null : true); // #771 묻지 않는다
|
|
146
|
+
const excludedWorkflows = existing?.options?.excludedWorkflows ?? null; // #810 저장값 보존
|
|
145
147
|
// Projects 보드 동기화(#716): 묻지 않는다. 저장값 > 이미 설치돼 있으면 유지 > 신규는 제외 (index.js 와 같은 규칙).
|
|
146
148
|
const projectsSync = existing?.options?.projectsSync
|
|
147
149
|
?? existsSync(join(cwd, ".github/workflows/PROJECT-COMMON-PROJECTS-SYNC-MANAGER.yaml"));
|
|
@@ -328,7 +330,7 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
|
|
|
328
330
|
const { now, today } = clock || utcNow();
|
|
329
331
|
const ctx = createContext({
|
|
330
332
|
mode, force: true, types, version, versionCode, branch, paths, deployTarget, publishTargets, includeSecretBackup,
|
|
331
|
-
codeReviewCoderabbit, changelogProvider, changelogBaseUrl, deployBranch, intent, semverAuto, appRelease, labelStyle, closeOnRelease, projectsSync, language,
|
|
333
|
+
codeReviewCoderabbit, changelogProvider, changelogBaseUrl, deployBranch, intent, semverAuto, appRelease, labelStyle, closeOnRelease, projectsSync, language, excludedWorkflows,
|
|
332
334
|
repoName, templateVersion, resolvers, envValues, envUseDefaults, now, today,
|
|
333
335
|
// #502 — version 모드가 기존 full 기록을 강등하지 않도록 (full이 우세)
|
|
334
336
|
recordMode: existing?.templateMode === "full" ? "full" : "version",
|
|
@@ -423,7 +425,7 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
|
|
|
423
425
|
migrationGuidePath = appendGuideEntry(cwd, {
|
|
424
426
|
now, mode, types, repoName,
|
|
425
427
|
templateFrom: existing?.templateVersion || "", templateTo: templateVersion,
|
|
426
|
-
options: { deploy: deployTarget, publish: publishTargets, secretBackup: includeSecretBackup, coderabbit: codeReviewCoderabbit, changelogProvider, intent, semverAuto , appRelease, labelStyle, closeOnRelease, projectsSync, language },
|
|
428
|
+
options: { deploy: deployTarget, publish: publishTargets, secretBackup: includeSecretBackup, coderabbit: codeReviewCoderabbit, changelogProvider, intent, semverAuto , appRelease, labelStyle, closeOnRelease, projectsSync, language, excludedWorkflows },
|
|
427
429
|
branches: { defaultBranch: branch, deployBranch, ready: deployBranchReady, created: deployBranchCreated },
|
|
428
430
|
breaking: breakingReport, migrations: migrationsResult, orphans: orphanReport,
|
|
429
431
|
events: trace.events, counters: { skipped: result?.workflows?.skipped ?? 0 },
|
|
@@ -437,6 +439,9 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
|
|
|
437
439
|
counters: { workflows: result?.workflows?.copied ?? 0, workflowFiles: result?.workflows?.copiedFiles ?? [], utilModules: 0 },
|
|
438
440
|
skippedConflicts: result?.workflows?.skippedConflicts ?? [], // #654 병합 안내
|
|
439
441
|
replacedBak: result?.workflows?.replacedBak ?? [], // #673 기준점 없이 교체한 파일 안내
|
|
442
|
+
legacyLeftover: migrationsResult?.confirmPending ?? [], // #809 신·구 세대 동시 실행 안내
|
|
443
|
+
notInstalled: result?.workflows?.notInstalled ?? [], // #810 깔지 않은 워크플로우 안내
|
|
444
|
+
semverAutoNewlyOn,
|
|
440
445
|
verification: result?.verification, // #549 설치 검증 결과
|
|
441
446
|
aiPrSummary, codeReviewCoderabbit, // #569 — 고른 것만 안내
|
|
442
447
|
logDir: files ? MIGRATION_DIR : null, // #561 기록 위치 안내
|
package/src/commands/version.js
CHANGED
|
@@ -5,7 +5,8 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { writeText } from "../core/fsutil.js";
|
|
7
7
|
import { PATHS } from "../core/paths.js";
|
|
8
|
-
import {
|
|
8
|
+
import { installAgentGuide } from "../core/agent-guide.js";
|
|
9
|
+
import { buildVersionYml, mergeDeployValues, resolveUpdatedBy } from "../core/version-yml.js";
|
|
9
10
|
import { markerForType } from "../core/detect.js";
|
|
10
11
|
import { addVersionSectionToReadme } from "../core/copy/readme.js";
|
|
11
12
|
import { applyLabelStyle } from "../core/label-style.js";
|
|
@@ -16,7 +17,7 @@ export function runVersion(context, tempDir, targetRoot = ".") {
|
|
|
16
17
|
const { version, types = [], paths = new Map(), branch = "main", versionCode = 1,
|
|
17
18
|
now, today, templateVersion = "unknown", deployTarget = "docker-ssh", publishTargets = [], includeSecretBackup = false,
|
|
18
19
|
changelogProvider = "commit", changelogBaseUrl = "", codeReviewCoderabbit = true,
|
|
19
|
-
deployBranch = "", recordMode = "version", semverAuto = true , appRelease = null, labelStyle = "en", closeOnRelease = null, projectsSync = null, language = null } = context;
|
|
20
|
+
deployBranch = "", recordMode = "version", semverAuto = true , appRelease = null, labelStyle = "en", closeOnRelease = null, projectsSync = null, language = null, excludedWorkflows = null } = context;
|
|
20
21
|
|
|
21
22
|
const pathMarkers = new Map();
|
|
22
23
|
for (const [t] of paths) pathMarkers.set(t, markerForType(t));
|
|
@@ -29,10 +30,11 @@ export function runVersion(context, tempDir, targetRoot = ".") {
|
|
|
29
30
|
buildVersionYml({
|
|
30
31
|
version, types, paths, pathMarkers, branch, deployBranch, versionCode, now, today,
|
|
31
32
|
deployValues,
|
|
33
|
+
updatedBy: resolveUpdatedBy(existsSync(vyFile) ? readFileSync(vyFile, "utf8") : "", targetRoot), // #811
|
|
32
34
|
// mode(#502): version 모드가 기존 full 통합 기록을 "version"으로 강등하지 않도록
|
|
33
35
|
// 호출부가 recordMode로 기존 값을 넘긴다 (full이 우세 — 업데이트 재실행 범위 축소 방지).
|
|
34
36
|
templateOptions: { templateVersion, deployTarget, publishTargets, includeSecretBackup, optionsDate: today,
|
|
35
|
-
changelogProvider, changelogBaseUrl, codeReviewCoderabbit, mode: recordMode, semverAuto, appRelease, labelStyle, closeOnRelease, projectsSync, language },
|
|
37
|
+
changelogProvider, changelogBaseUrl, codeReviewCoderabbit, mode: recordMode, semverAuto, appRelease, labelStyle, closeOnRelease, projectsSync, language, excludedWorkflows },
|
|
36
38
|
}));
|
|
37
39
|
addVersionSectionToReadme(version, targetRoot);
|
|
38
40
|
copyScripts(tempDir, targetRoot);
|
|
@@ -40,4 +42,5 @@ export function runVersion(context, tempDir, targetRoot = ".") {
|
|
|
40
42
|
applyLabelStyle(targetRoot, labelStyle); // #776
|
|
41
43
|
ensureGitignore(targetRoot);
|
|
42
44
|
copySetupGuide(tempDir, targetRoot);
|
|
45
|
+
installAgentGuide(targetRoot, templateVersion);
|
|
43
46
|
}
|
|
@@ -9,6 +9,7 @@ import { applyLabelStyle } from "../core/label-style.js";
|
|
|
9
9
|
import { copyScripts, copyConfigFolder, copySetupGuide } from "../core/copy/simple.js";
|
|
10
10
|
import { copyUtilModules } from "../core/copy/util.js";
|
|
11
11
|
import { convertLegacySingularType, mergeDeployValues } from "../core/version-yml.js";
|
|
12
|
+
import { installAgentGuide } from "../core/agent-guide.js";
|
|
12
13
|
import { verifyInstall } from "../core/verify.js";
|
|
13
14
|
|
|
14
15
|
export function runWorkflows(context, tempDir, targetRoot = ".", hooks = {}) {
|
|
@@ -35,6 +36,7 @@ export function runWorkflows(context, tempDir, targetRoot = ".", hooks = {}) {
|
|
|
35
36
|
applyLabelStyle(targetRoot, labelStyle); // #776 — 워크플로우(QA 봇)와 라벨 정의를 같은 표기로 맞춘다
|
|
36
37
|
for (const t of types) copyUtilModules(tempDir, t, { force }, targetRoot);
|
|
37
38
|
copySetupGuide(tempDir, targetRoot);
|
|
39
|
+
installAgentGuide(targetRoot, context.templateVersion);
|
|
38
40
|
|
|
39
41
|
// 설치 후 검증 (#549) — 워크플로우만 설치하는 모드라 오히려 더 필요하다.
|
|
40
42
|
const verification = verifyInstall(targetRoot);
|
package/src/context.js
CHANGED
|
@@ -27,10 +27,11 @@ export function createContext(overrides = {}) {
|
|
|
27
27
|
codeReviewCoderabbit: null,
|
|
28
28
|
deployBranch: "", // 릴리스 PR head 브랜치 (#456). 빈 값=metadata.deploy_branch 미출력
|
|
29
29
|
intent: null, // 프로젝트 성격 (#485 — app/library/both/none/manual). null=미설정(역추론)
|
|
30
|
-
semverAuto: null, // semver 자동 승격 (#546). null=미설정
|
|
30
|
+
semverAuto: null, // semver 자동 승격 (#546). null=미설정 → 신규·기존 모두 true (#814). 명시적 false 만 끈다
|
|
31
31
|
closeOnRelease: null, // 릴리스 시 완료 이슈 닫기 (#771). null=미설정(신규 true / 기존 레포 키 없음)
|
|
32
32
|
language: null, // 레포 문구 언어 (#769). null=미설정(신규 en / 기존 레포 ko)
|
|
33
33
|
labelStyle: null, // 상태 라벨 표기 (#776). null=미설정(신규 en / 기존 레포 ko)
|
|
34
|
+
excludedWorkflows: null, // 설치하지 않을 워크플로우 파일명 (#810). null=없음
|
|
34
35
|
appRelease: null, // 앱 심사 배포 레포인가 (#553). null=미설정(키 기록 안 함)
|
|
35
36
|
templateVersion: "",
|
|
36
37
|
tempDir: "",
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// 사용자 레포에 설치하는 AI agent 안내문 (.github/.projectops/AGENT-GUIDE.md).
|
|
2
|
+
//
|
|
3
|
+
// 왜 파일인가: 이 레포의 AGENTS.md 는 템플릿 전용이라 사용자 프로젝트로 가지 않는다. 그래서 projectops 가
|
|
4
|
+
// 깔린 레포에서 일하는 agent 는 "이 레포가 무엇으로 관리되는지, 옵션을 어디서 확인하는지"를 알 길이 없었다.
|
|
5
|
+
// 레포 루트의 AGENTS.md·CLAUDE.md 는 사용자 것이라 건드리지 않고, 폴더 안에 두고 한 줄 포인터만 안내한다.
|
|
6
|
+
//
|
|
7
|
+
// 내용에 옵션 표를 박지 않는다 — 박으면 곧 낡는다. 표는 항상 `npx projectops --mode options --json` 이 정본이다.
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { writeText } from "./fsutil.js";
|
|
10
|
+
|
|
11
|
+
export const AGENT_GUIDE_PATH = ".github/.projectops/AGENT-GUIDE.md";
|
|
12
|
+
|
|
13
|
+
export function agentGuideText(templateVersion = "") {
|
|
14
|
+
return `# projectops — guide for AI agents
|
|
15
|
+
|
|
16
|
+
This repository is managed with [projectops](https://github.com/Cassiiopeia/projectops)${templateVersion ? ` (installed template ${templateVersion})` : ""}.
|
|
17
|
+
GitHub Actions workflows in \`.github/workflows/PROJECT-*\` handle versioning, release notes, CI/CD and issue automation.
|
|
18
|
+
This file is generated by the installer and overwritten on update. Do not edit it.
|
|
19
|
+
|
|
20
|
+
## Ask the tool, do not guess
|
|
21
|
+
|
|
22
|
+
\`\`\`bash
|
|
23
|
+
npx projectops --mode options --json # every CLI flag and every version.yml key, with allowed values and defaults
|
|
24
|
+
npx projectops --mode doctor # read-only diagnosis of this install and the repository settings
|
|
25
|
+
\`\`\`
|
|
26
|
+
|
|
27
|
+
Both are read-only and need no network.
|
|
28
|
+
|
|
29
|
+
## Rules
|
|
30
|
+
|
|
31
|
+
- Settings live in \`version.yml\` under \`metadata.template.options\`. The options table says which keys are safe to edit by hand.
|
|
32
|
+
- \`version\` in \`version.yml\` is the single source of the project version. Workflows bump it on release; do not bump it by hand unless asked.
|
|
33
|
+
- Commit titles decide the release bump when \`semver_auto\` is on: \`feat:\` = minor, \`feat!:\` = major, anything else = patch.
|
|
34
|
+
- Running the installer again updates workflows. Files you changed are kept (a copy of the new version is saved in \`.github/.projectops/incoming/\`). Files you deleted are not restored.
|
|
35
|
+
- To stop a template workflow from ever being installed, list its file name in \`options.excluded_workflows\`.
|
|
36
|
+
- Run the installer from an agent or CI only with an explicit mode and \`--force\`, e.g. \`npx projectops --mode workflows --force\`. Without \`--force\` it asks questions and blocks.
|
|
37
|
+
- Do not edit \`.github/.projectops/logs\`, \`baseline.json\` or \`incoming/\`. They are diagnostics for the next update.
|
|
38
|
+
`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function installAgentGuide(targetRoot = ".", templateVersion = "") {
|
|
42
|
+
writeText(join(targetRoot, AGENT_GUIDE_PATH), agentGuideText(templateVersion));
|
|
43
|
+
return AGENT_GUIDE_PATH;
|
|
44
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// 여러 파일이 함께 써야 하는 값 목록의 정본.
|
|
2
|
+
//
|
|
3
|
+
// 왜 한 곳인가: deploy·publish 목록이 CLI 인자 검사(args.js), 마법사 질문(options-ask.js),
|
|
4
|
+
// 복사 엔진(copy/workflows.js), version.yml 파서(version-yml.js), 옵션 표(options-schema.js)
|
|
5
|
+
// 다섯 곳에 따로 적혀 있었다. 하나만 고치면 "CLI 는 받는데 파서가 버린다" 같은 조용한 불일치가 생긴다.
|
|
6
|
+
// 값을 추가할 때는 여기 한 줄만 고치고, 그 값에 해당하는 워크플로우 폴더를 만든다
|
|
7
|
+
// (deploy: project-types/common/deploy/<값>/ · publish: project-types/<type>/publish/<값>/).
|
|
8
|
+
//
|
|
9
|
+
// 이 파일은 아무것도 import 하지 않는다 — 어디서 불러도 순환 import 가 생기지 않게.
|
|
10
|
+
// 레포 문구 언어 목록은 repo-language.js 의 REPO_LANGUAGES 가 정본이다 (번역 파일과 같이 검사된다).
|
|
11
|
+
|
|
12
|
+
/** 실행물을 어디에 배포하나 — 택1 (#439) */
|
|
13
|
+
export const DEPLOY_TARGETS = Object.freeze(["docker-ssh", "vercel", "none"]);
|
|
14
|
+
|
|
15
|
+
/** 라이브러리를 어느 레지스트리에 내나 — 0..n (#439) */
|
|
16
|
+
export const PUBLISH_TARGETS = Object.freeze(["nexus", "npm", "github-packages"]);
|
|
17
|
+
|
|
18
|
+
/** 프로젝트 성격 — deploy/publish 질문을 유도한다 (#485) */
|
|
19
|
+
export const INTENT_VALUES = Object.freeze(["app", "library", "both", "none", "manual"]);
|
|
20
|
+
|
|
21
|
+
/** 상태 라벨 표기 — en: `status: todo`, ko: 기존 한글 `작업전` (#776) */
|
|
22
|
+
export const LABEL_STYLES = Object.freeze(["en", "ko"]);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 릴리스 노트 생성기 (#455, #566). 기본값은 commit — 외부 의존이 없어 어디서나 결과가 나온다.
|
|
26
|
+
* 실제 구현은 .github/scripts/changelog_providers/ 에 있고, 그쪽 목록과의 정합은 테스트가 본다.
|
|
27
|
+
*/
|
|
28
|
+
export const CHANGELOG_PROVIDERS = Object.freeze(["copilot", "coderabbit", "openai", "gemini", "claude", "groq", "mistral", "ollama", "commit"]);
|
package/src/core/copy/simple.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// 단순 복사 함수 (무조건 덮어쓰기류) — .sh copy_scripts/config/issue/discussion/setup_guide 등가.
|
|
2
2
|
// 실측: template_integrator.sh 3818, 3845, 3872, 3895, 4114.
|
|
3
|
-
import { join } from "node:path";
|
|
3
|
+
import { join, basename } from "node:path";
|
|
4
4
|
import { chmodSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { PATHS } from "../paths.js";
|
|
6
6
|
import { exists, copyFileSync, copyDirSync } from "../fsutil.js";
|
|
@@ -97,7 +97,8 @@ export function stripIssueTemplateAssignees(targetRoot = ".") {
|
|
|
97
97
|
// .github/ISSUE_TEMPLATE/ 전체 + PULL_REQUEST_TEMPLATE.md 덮어쓰기.
|
|
98
98
|
export function copyIssueTemplates(tempDir, targetRoot = ".") {
|
|
99
99
|
const srcIssue = join(tempDir, ".github", "ISSUE_TEMPLATE");
|
|
100
|
-
|
|
100
|
+
// `projectops-` 로 시작하는 파일은 이 저장소(projectops 자체)의 이슈 양식이라 사용자 레포로 복사하지 않는다 (#797).
|
|
101
|
+
if (exists(srcIssue)) copyDirSync(srcIssue, join(targetRoot, ".github", "ISSUE_TEMPLATE"), { filter: (src) => !basename(src).startsWith("projectops-") });
|
|
101
102
|
const srcPr = join(tempDir, ".github", "PULL_REQUEST_TEMPLATE.md");
|
|
102
103
|
if (exists(srcPr)) copyFileSync(srcPr, join(targetRoot, ".github", "PULL_REQUEST_TEMPLATE.md"));
|
|
103
104
|
}
|