projectops 4.72.0 โ 4.74.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 +5 -4
- package/package.json +1 -1
- package/src/commands/full.js +2 -1
- package/src/commands/issues.js +1 -1
- package/src/core/breaking-check.js +9 -6
- package/src/core/breaking.js +10 -0
- package/src/core/development-branch.js +2 -1
- package/src/core/git-branch.js +25 -6
- package/src/core/ide/skills-copy.js +9 -4
- package/src/core/label-style.js +15 -0
- package/src/core/options-ask.js +4 -3
- package/src/core/options-schema.js +1 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
**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)).
|
|
12
12
|
|
|
13
13
|
<!-- AUTO-VERSION-SECTION: DO NOT EDIT MANUALLY -->
|
|
14
|
-
## Latest version : v4.
|
|
14
|
+
## Latest version : v4.73.0 (2026-10-22)
|
|
15
15
|
|
|
16
16
|
[View full version history](CHANGELOG.md)
|
|
17
17
|
|
|
@@ -51,7 +51,7 @@ This project automates your development workflow on two axes.
|
|
|
51
51
|
| Format every issue by hand | `/pro-github` writes and files an issue from the standard template in one step |
|
|
52
52
|
| Copy the issue URL into commit messages | `/pro-commit` completes the message from the issue context, in your own style |
|
|
53
53
|
| Write PR descriptions and reports by hand | `/pro-report` analyzes the git diff and generates one |
|
|
54
|
-
| Re-type prompts for every code review or analysis |
|
|
54
|
+
| Re-type prompts for every code review or analysis | 15 skills give consistent results without re-typing |
|
|
55
55
|
|
|
56
56
|
---
|
|
57
57
|
|
|
@@ -140,11 +140,11 @@ What it changed: descriptions were cut to short English triggers, a shared contr
|
|
|
140
140
|
| Part | What it does | Docs |
|
|
141
141
|
|---|---|---|
|
|
142
142
|
| **GitHub Actions** | One push to main handles versioning, changelogs, and CI/CD deployment | [Versioning](https://cassiiopeia.github.io/projectops/version-control) |
|
|
143
|
-
| **Agent Skills** (
|
|
143
|
+
| **Agent Skills** (15) | `/pro-github`, `/pro-commit`, `/pro-report` and more let an AI agent drive each step: issues, commits in your style, reports, release PRs | [Skills guide](https://cassiiopeia.github.io/projectops/skills) |
|
|
144
144
|
| **npx CLI** | Installs and updates workflows in an existing project, and diagnoses its state | [CLI reference](https://cassiiopeia.github.io/projectops/cli) |
|
|
145
145
|
|
|
146
146
|
<details>
|
|
147
|
-
<summary>See all
|
|
147
|
+
<summary>See all 15 Agent Skills</summary>
|
|
148
148
|
|
|
149
149
|
### ๐ Development cycle automation
|
|
150
150
|
|
|
@@ -168,6 +168,7 @@ What it changed: descriptions were cut to short English triggers, a shared contr
|
|
|
168
168
|
|------|------|
|
|
169
169
|
| `/pro-figma` | Move a Figma design into code, then compare the result with the design pixel by pixel |
|
|
170
170
|
| `/pro-launch` | Launch, drive, and capture apps, web, and servers: fixed status bar, width changes, response mocking (empty list, failures), render from code |
|
|
171
|
+
| `/pro-desktop-control` | Drive programs on this computer's screen that are not web pages or emulators (installer wizards, OS dialogs, desktop apps): reads elements, presses without moving the mouse, asks before installs, hands security prompts to the user. Windows and macOS (macOS also answers notifications and system dialogs, such as the Apple Account sign-in code) |
|
|
171
172
|
| `/pro-design-brief` | A design request for a screen you built before the mockup exists: state captures, alternatives, copy candidates, and must-keep rules as a board for the designer |
|
|
172
173
|
| `/pro-oss-consult` | Diagnose a GitHub repo as an open-source project: type detection, 6-axis scoring, maturity stage, and edits to About, topics, labels, and files only after approval. One repo or a whole account |
|
|
173
174
|
|
package/package.json
CHANGED
package/src/commands/full.js
CHANGED
|
@@ -71,6 +71,8 @@ export function runFull(context, tempDir, targetRoot = ".", hooks = {}) {
|
|
|
71
71
|
// 7. issue / discussion ํ
ํ๋ฆฟ
|
|
72
72
|
step("copy-templates", () => {
|
|
73
73
|
copyIssueTemplates(tempDir, targetRoot);
|
|
74
|
+
// Discussion ํ
ํ๋ฆฟ๋ ์ค๋ฒ๋ ์ด ๋์ (#988) โ ์ค๋ฒ๋ ์ด๋ณด๋ค ๋จผ์ ๋ณต์ฌํด์ผ ํ๊ตญ์ดํ์ด ๋ฎ์ธ๋ค
|
|
75
|
+
copyDiscussionTemplates(tempDir, targetRoot);
|
|
74
76
|
// ํ
ํ๋ฆฟ ์ธ์ด(#769) โ ์๋ณธ์ ์๋ฌธ์ด๋ผ ํ๊ตญ์ด ๋ ํฌ๋ ๋ณต์ฌ ์งํ ์ค๋ฒ๋ ์ด๋ก ๋ฎ๋๋ค.
|
|
75
77
|
// ๋ผ๋ฒจ ํ๊ธฐ ๋ณํ(apply-label-style)๋ณด๋ค ๋ฐ๋์ ๋จผ์ ์ฌ์ผ ํ๋ค.
|
|
76
78
|
const { changed: overlaid, failed: overlayFailed } = applyRepoLanguage(tempDir, targetRoot, language);
|
|
@@ -79,7 +81,6 @@ export function runFull(context, tempDir, targetRoot = ".", hooks = {}) {
|
|
|
79
81
|
// ๋ด๋น์ ๊ณ ์ ๊ฐ ์ ๊ฑฐ(#782) โ ์ค๋ฒ๋ ์ด๊ฐ ๋ค์ ๋ฃ์ผ๋ฏ๋ก ๊ทธ ๋ค์ ํ๋ค.
|
|
80
82
|
hooks.trace?.event("copy", "strip-assignees", "", { files: stripIssueTemplateAssignees(targetRoot).length });
|
|
81
83
|
if (overlayFailed.length) console.error(t("language.overlayFailed", { n: overlayFailed.length, files: overlayFailed.map((f) => f.file).join(", ") }));
|
|
82
|
-
copyDiscussionTemplates(tempDir, targetRoot);
|
|
83
84
|
});
|
|
84
85
|
|
|
85
86
|
// 8. coderabbit / gitignore / setup guide
|
package/src/commands/issues.js
CHANGED
|
@@ -7,9 +7,9 @@ import { copyIssueTemplates, copyDiscussionTemplates, stripIssueTemplateAssignee
|
|
|
7
7
|
|
|
8
8
|
export function runIssues(context, tempDir, targetRoot = ".") {
|
|
9
9
|
copyIssueTemplates(tempDir, targetRoot);
|
|
10
|
+
copyDiscussionTemplates(tempDir, targetRoot); // #988 โ ์ค๋ฒ๋ ์ดยท๋ผ๋ฒจ ๋ณํ ๋์์ด๋ผ ๊ทธ๋ณด๋ค ๋จผ์
|
|
10
11
|
const { failed } = applyRepoLanguage(tempDir, targetRoot, context.language ?? "en"); // #769 โ ๋ผ๋ฒจ ๋ณํ๋ณด๋ค ๋จผ์
|
|
11
12
|
if (failed.length) console.error(t("language.overlayFailed", { n: failed.length, files: failed.map((f) => f.file).join(", ") }));
|
|
12
13
|
stripIssueTemplateAssignees(targetRoot); // #782 โ ์ค๋ฒ๋ ์ด๊ฐ ๋ค์ ๋ฃ์ผ๋ฏ๋ก ๊ทธ ๋ค์
|
|
13
14
|
applyLabelStyle(targetRoot, context.labelStyle ?? "en"); // #776
|
|
14
|
-
copyDiscussionTemplates(tempDir, targetRoot);
|
|
15
15
|
}
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
// collectBreaking(์์ ๋น๊ต)์ breaking.js โ ์ด ๋ชจ๋์ ๋ก๋(์๊ฒฉโclone๋ณธ ํด๋ฐฑ)ยทํ์ยทํ์ธ ๊ฒ์ดํธ.
|
|
3
3
|
import { readFileSync, existsSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
-
import { collectBreaking } from "./breaking.js";
|
|
5
|
+
import { collectBreaking, localizeBreakingEntry } from "./breaking.js";
|
|
6
6
|
import { parseExisting } from "./version-yml.js";
|
|
7
|
-
import { t } from "../i18n/index.js";
|
|
7
|
+
import { t, getLang } from "../i18n/index.js";
|
|
8
8
|
|
|
9
9
|
const BC_URL = "https://raw.githubusercontent.com/Cassiiopeia/projectops/main/.github/config/breaking-changes.json";
|
|
10
10
|
|
|
@@ -46,11 +46,13 @@ export async function runBreakingCheck({ cwd, tempDir, templateVersion, askYesNo
|
|
|
46
46
|
// ์์ฝ ๋ฆฌ์คํธ ํ์ (#473 โ ์ ๋ฌธ ํต๋คํ๋ ๋ฒฝ๊ธ์ด ๋์ด ์ ์ CRITICAL์ด ์ ์ฝํ๊ณ ,
|
|
47
47
|
// ๊ธด ๋ณธ๋ฌธ ๋ํ์ด โ ๋ฐ์ค ๊ฒฝ๊ณ๋ฅผ ๋ถ๊ดด์์ผฐ๋ค. ๋ฒ์ ยท์ ๋ชฉ๋ง ํ ์ค์ฉ, ์ ๋ฌธ์ ์ ํ ์ด๋.)
|
|
48
48
|
const e = (s = "") => process.stderr.write(s + "\n");
|
|
49
|
+
// ํ๋ฉด์ ํ๋ฉด ์ธ์ด ๋ฌธ๊ตฌ (onItems ๋ก ๋๊ธด ๊ฐ์ด๋์ฉ ํญ๋ชฉ์ ์์ด ์๋ฌธ ๊ทธ๋๋ก โ ์คํ ๊ธฐ๋ก์ ์์ด)
|
|
50
|
+
const localized = (item) => localizeBreakingEntry(item, getLang());
|
|
49
51
|
e("");
|
|
50
52
|
e(t("breaking.title", { current, target: templateVersion, critical: critical.length, warnings: warnings.length }));
|
|
51
53
|
e("");
|
|
52
|
-
for (const c of critical) e(` โ [CRITICAL] ${c.version} - ${c.title
|
|
53
|
-
for (const w of warnings) e(` โ ๏ธ [WARNING] ${w.version} - ${w.title
|
|
54
|
+
for (const c of critical) e(` โ [CRITICAL] ${c.version} - ${localized(c).title}`);
|
|
55
|
+
for (const w of warnings) e(` โ ๏ธ [WARNING] ${w.version} - ${localized(w).title}`);
|
|
54
56
|
e("");
|
|
55
57
|
|
|
56
58
|
if (askYesNo) {
|
|
@@ -59,8 +61,9 @@ export async function runBreakingCheck({ cwd, tempDir, templateVersion, askYesNo
|
|
|
59
61
|
if (detail === true) {
|
|
60
62
|
for (const it of [...critical, ...warnings]) {
|
|
61
63
|
e("");
|
|
62
|
-
|
|
63
|
-
e(
|
|
64
|
+
const { title, message } = localized(it);
|
|
65
|
+
e(`โ ${it.version} - ${title}`);
|
|
66
|
+
e(` ${message}`);
|
|
64
67
|
}
|
|
65
68
|
e("");
|
|
66
69
|
}
|
package/src/core/breaking.js
CHANGED
|
@@ -24,3 +24,13 @@ export function collectBreaking(json, current, target) {
|
|
|
24
24
|
}
|
|
25
25
|
return { critical, warnings };
|
|
26
26
|
}
|
|
27
|
+
|
|
28
|
+
// ํญ๋ชฉ ๋ฌธ๊ตฌ > ํ๋ฉด ์ธ์ด: ko๋ฉด title_ko/message_ko, ์๊ฑฐ๋ ๊ทธ ์ธ ์ธ์ด๋ฉด ์์ด ์๋ฌธ(title/message)
|
|
29
|
+
// ์์ด ํค๊ฐ ์ ๋ณธ โ ๊ตฌ๋ฒ์ CLI๋ main์ ์ด ํ์ผ์์ title/message๋ง ์ฝ์ (#988)
|
|
30
|
+
export function localizeBreakingEntry(entry, lang) {
|
|
31
|
+
const pickField = (field) => {
|
|
32
|
+
const koreanText = lang === "ko" ? entry?.[`${field}_ko`] : null;
|
|
33
|
+
return koreanText || entry?.[field] || "";
|
|
34
|
+
};
|
|
35
|
+
return { title: pickField("title"), message: pickField("message") };
|
|
36
|
+
}
|
|
@@ -27,7 +27,8 @@ function manualCommandFor(state, branch, base) {
|
|
|
27
27
|
*/
|
|
28
28
|
export async function createDevelopmentBranchUnattended({ targetRoot = ".", branch = "develop", defaultBranch = "", say = () => {} }) {
|
|
29
29
|
const answerYes = { confirm: async () => true };
|
|
30
|
-
|
|
30
|
+
// ๋ฌด์ธ ์คํ: ์๊ฒฉ ์ฆ๋ช
์ ๋ฌป์ง ์๊ณ ์คํจ์์ผ failed ๊ฒฝ๋ก(์๋ ๋ช
๋ น)๋ก ๋ณด๋ธ๋ค (#988)
|
|
31
|
+
const outcome = await ensureDeployBranch({ targetRoot, deployBranch: branch, defaultBranch, io: answerYes, say, isNonInteractive: true });
|
|
31
32
|
const status = STATUS_BY_STATE[outcome.state] ?? "failed";
|
|
32
33
|
return {
|
|
33
34
|
status,
|
package/src/core/git-branch.js
CHANGED
|
@@ -2,18 +2,36 @@
|
|
|
2
2
|
// ๋ชจ๋ ํจ์๋ ์คํจ ์ ์กฐ์ฉํ false/null์ ๋ฐํํ๋ค (git ๋ฏธ์ค์นยท๋น๋ ํฌยท์๊ฒฉ ์์์์ ๋ง๋ฒ์ฌ๊ฐ ์ฃฝ์ง ์๊ฒ).
|
|
3
3
|
import { execFileSync } from "node:child_process";
|
|
4
4
|
|
|
5
|
-
function git(root, args) {
|
|
5
|
+
function git(root, args, env = undefined) {
|
|
6
6
|
try {
|
|
7
|
-
return execFileSync("git", args, { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
7
|
+
return execFileSync("git", args, { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], env }).trim();
|
|
8
8
|
} catch { return null; }
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* ๋น๋ํํ ์๊ฒฉ ์ ๊ทผ์ฉ ํ๊ฒฝ (#988): ์๊ฒฉ ์ฆ๋ช
์ง๋ฌธ ์ฐจ๋จ > ์ฆ์ ์คํจ > ์๋ ๋ช
๋ น ์๋ด
|
|
13
|
+
* ์ฐจ๋จ 4๊ฐ: ํฐ๋ฏธ๋ ํ๋กฌํํธ, askpass(GIT_/SSH_), Git Credential Manager ์ฐฝ, ssh ๋น๋ฐ๋ฒํธ ์ง๋ฌธ
|
|
14
|
+
* ssh ๋ ์ฌ์ฉ์ ์ง์ ๋ช
๋ น(GIT_SSH_COMMANDยทGIT_SSHยทcore.sshCommand)์ด ์์ ๋๋ง BatchMode ๋ก ๋ฎ๋๋ค
|
|
15
|
+
*/
|
|
16
|
+
export function nonInteractiveGitEnv(baseEnv = process.env, { hasCustomSshCommand = false } = {}) {
|
|
17
|
+
const env = { ...baseEnv, GIT_TERMINAL_PROMPT: "0", GIT_ASKPASS: "", SSH_ASKPASS: "", GCM_INTERACTIVE: "never" };
|
|
18
|
+
if (!hasCustomSshCommand && !baseEnv.GIT_SSH_COMMAND && !baseEnv.GIT_SSH) env.GIT_SSH_COMMAND = "ssh -o BatchMode=yes";
|
|
19
|
+
return env;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ์๊ฒฉ ๋ช
๋ น ํ๊ฒฝ: ๋ํํ์ ์์(undefined), ๋น๋ํํ์ ์ง๋ฌธ ์ฐจ๋จ ํ๊ฒฝ
|
|
23
|
+
function remoteEnv(root, isNonInteractive) {
|
|
24
|
+
if (!isNonInteractive) return undefined;
|
|
25
|
+
const hasCustomSshCommand = Boolean(git(root, ["config", "--get", "core.sshCommand"]));
|
|
26
|
+
return nonInteractiveGitEnv(process.env, { hasCustomSshCommand });
|
|
27
|
+
}
|
|
28
|
+
|
|
11
29
|
// ๋ธ๋์น ์กด์ฌ ์ํ. remote๋ origin ์กฐํ ์คํจ(์๊ฒฉ ์์ยท๋คํธ์ํฌ) ์ null(๋ถ๋ช
).
|
|
12
|
-
export function branchStatus(root, name) {
|
|
30
|
+
export function branchStatus(root, name, { isNonInteractive = false } = {}) {
|
|
13
31
|
const isRepo = git(root, ["rev-parse", "--git-dir"]) !== null;
|
|
14
32
|
if (!isRepo || !name) return { isRepo, local: false, remote: null };
|
|
15
33
|
const local = git(root, ["show-ref", "--verify", `refs/heads/${name}`]) !== null;
|
|
16
|
-
const ls = git(root, ["ls-remote", "--heads", "origin", name]);
|
|
34
|
+
const ls = git(root, ["ls-remote", "--heads", "origin", name], remoteEnv(root, isNonInteractive));
|
|
17
35
|
const remote = ls === null ? null : ls.length > 0;
|
|
18
36
|
return { isRepo, local, remote };
|
|
19
37
|
}
|
|
@@ -29,6 +47,7 @@ export function createBranch(root, name, fromBranch = "") {
|
|
|
29
47
|
}
|
|
30
48
|
|
|
31
49
|
// ์๊ฒฉ(origin)์ ๋ธ๋์น push (-u). ์๊ฒฉ/๋คํธ์ํฌ ์คํจ ์ false.
|
|
32
|
-
|
|
33
|
-
|
|
50
|
+
// ๋น๋ํํ์ ์๊ฒฉ ์ฆ๋ช
์ ๋ฌป์ง ์๊ณ ๋ฐ๋ก ์คํจ (#988 โ ๋ฌป๋ ์๊ฐ ๋ฌด์ธ ์คํ์ด ๋ฉ์ถ๋ค)
|
|
51
|
+
export function pushBranch(root, name, { isNonInteractive = false } = {}) {
|
|
52
|
+
return git(root, ["push", "-u", "origin", name], remoteEnv(root, isNonInteractive)) !== null;
|
|
34
53
|
}
|
|
@@ -16,6 +16,10 @@ const OWNED_SKILL_NAME = /^(pro|suh)-/;
|
|
|
16
16
|
const SHARED_SCRIPTS_MARKER = join("common", "__init__.py");
|
|
17
17
|
const SHARED_SCRIPTS_DIR_NAME = "scripts";
|
|
18
18
|
const PYTHON_CACHE_DIR_NAME = "__pycache__";
|
|
19
|
+
// ๊ณต์ฉ ํ์ด์ฌ ์ค ์ ์ฅ์ ๊ด๋ฆฌ์๋ง ์ฐ๋ ์ต์์ ํญ๋ชฉ 3๊ฐ: ์๋ด ๋ฌธ์, ํธ๋ฌ๋ธ์ํ
์์ธ ์์ฑ๊ธฐ, extras ์ฌ๋ณธ ๋๊ธฐํ (#988)
|
|
20
|
+
// ํ์ฉ ๋ชฉ๋ก์ด ์๋๋ผ ์ ์ธ ๋ชฉ๋ก์ธ ์ด์ : ์ ๋ฐํ์ ๋ชจ๋์ ๋น ๋จ๋ฆฌ๋ฉด ์คํฌ์ด import ์์ ์ฃฝ์ง๋ง,
|
|
21
|
+
// ์ ๊ฐ๋ฐ ํ์ผ์ ๋น ๋จ๋ฆฌ๋ฉด ํ์ผ ํ๋๊ฐ ๋ ๊ฐ ๋ฟ์ด๋ค. ๋ฐํ์์ด ์ฐ๋ ํญ๋ชฉ์ด ์ฌ๊ธฐ ๋ค์ด์ค๋ฉด test/ide-home-install-e2e.test.js ๊ฐ ์ก๋๋ค
|
|
22
|
+
export const SHARED_SCRIPTS_DEV_ONLY = new Set(["README.md", "build_troubleshooting.py", "sync_extras.py"]);
|
|
19
23
|
|
|
20
24
|
// ์คํฌ ์์ค: ๋ค์ด๋ก๋๋ ํ
ํ๋ฆฟ(TEMP)/skills ์ฐ์ , ์์ผ๋ฉด ๋ก์ปฌ skills/
|
|
21
25
|
export function resolveSkillsSrc(ctx = {}) {
|
|
@@ -43,11 +47,12 @@ export function readSkillsMeta(metaFile) {
|
|
|
43
47
|
|
|
44
48
|
// ์์ค ์ต์์ ํญ๋ชฉ ๋ณต์ฌ (ํ
์คํธยทconftest ์ ์ธ). ํํฐ๋ ์์ค ๊ธฐ์ค ์๋๊ฒฝ๋ก๋ก ๋ณธ๋ค โ
|
|
45
49
|
// ์ ๋๊ฒฝ๋ก๋ก ๋ณด๋ฉด ์์ค๊ฐ ~/test/... ์๋์ ์์ ๋ ์ ๋ถ ๊ฑธ๋ฌ์ง๋ค.
|
|
46
|
-
|
|
50
|
+
// skipNames: ์ถ๊ฐ๋ก ๋นผ๋ ์ต์์ ์ด๋ฆ (๊ณต์ฉ ํ์ด์ฌ์ ๊ฐ๋ฐ ์ ์ฉ ํญ๋ชฉ)
|
|
51
|
+
function copyEntriesWithoutTests(src, dest, skipNames = new Set()) {
|
|
47
52
|
mkdirSync(dest, { recursive: true });
|
|
48
53
|
const copiedNames = [];
|
|
49
54
|
for (const e of readdirSync(src, { withFileTypes: true })) {
|
|
50
|
-
if (SKIP_IN_COPY.has(e.name) || TEST_PATH.test(e.name)) continue;
|
|
55
|
+
if (SKIP_IN_COPY.has(e.name) || TEST_PATH.test(e.name) || skipNames.has(e.name)) continue;
|
|
51
56
|
cpSync(join(src, e.name), join(dest, e.name), {
|
|
52
57
|
recursive: true,
|
|
53
58
|
// ์ด ์ ์ฅ์ ์๊ธฐ ์ฝ๋๋ฅผ ๊ฒ์ฌํ๋ ํ
์คํธ๋ ๋จ์ ์ปดํจํฐ๋ก ๋๊ฐ ์ด์ ๊ฐ ์๋ค (#591).
|
|
@@ -79,10 +84,10 @@ export function copySkillsTree(src, dest, metaFileName) {
|
|
|
79
84
|
return obsoleteNames;
|
|
80
85
|
}
|
|
81
86
|
|
|
82
|
-
// ๊ณต์ฉ ํ์ด์ฌ ๊ต์ฒด ๋ณต์ฌ: ์ง๋ ์ค์น ํญ๋ชฉ ์ ๊ฑฐ >
|
|
87
|
+
// ๊ณต์ฉ ํ์ด์ฌ ๊ต์ฒด ๋ณต์ฌ: ์ง๋ ์ค์น ํญ๋ชฉ ์ ๊ฑฐ > ๋ฐํ์ ํญ๋ชฉ๋ง ๋ณต์ฌ (ํ๊ธฐ ๋ชจ๋ยท์์ ์ ๊น๋ฆฐ ๊ฐ๋ฐ ํ์ผ์ด ๋จ์ง ์๊ฒ). ๋ณต์ฌํ ํญ๋ชฉ ์ด๋ฆ ๋ฐํ
|
|
83
88
|
function replaceSharedScripts(scriptsSrc, scriptsDest, previousNames) {
|
|
84
89
|
removeNamedEntries(scriptsDest, previousNames);
|
|
85
|
-
return copyEntriesWithoutTests(scriptsSrc, scriptsDest);
|
|
90
|
+
return copyEntriesWithoutTests(scriptsSrc, scriptsDest, SHARED_SCRIPTS_DEV_ONLY);
|
|
86
91
|
}
|
|
87
92
|
|
|
88
93
|
// ์ด๋ฆ ๋ชฉ๋ก + ํ์ด์ฌ ์คํ์ด ๋ง๋ __pycache__ ์ญ์ > ํด๋๊ฐ ๋น๋ฉด ํด๋๋ ์ญ์
|
package/src/core/label-style.js
CHANGED
|
@@ -69,6 +69,21 @@ export function applyLabelStyle(targetRoot, style) {
|
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
// Discussion ํ
ํ๋ฆฟ ๊ธฐ๋ณธ ๋ผ๋ฒจ (#988): ๋งจ ์ labels: ๋ชฉ๋ก์ "- ์ด๋ฆ" ์ค๋ง ๋์
|
|
73
|
+
const discussionDir = join(gh, "DISCUSSION_TEMPLATE");
|
|
74
|
+
if (existsSync(discussionDir)) {
|
|
75
|
+
for (const name of readdirSync(discussionDir).filter((f) => /\.ya?ml$/.test(f))) {
|
|
76
|
+
const f = join(discussionDir, name);
|
|
77
|
+
if (rewrite(f, (txt) => txt.replace(/^labels:\s*\n((?:[ \t]+-[^\n]*\n)+)/m, (m, list) => {
|
|
78
|
+
let out = list;
|
|
79
|
+
for (const [en, ko] of LABEL_PAIRS) {
|
|
80
|
+
out = out.replace(new RegExp(`^([ \\t]+-[ \\t]*)["']?${quoted(en)}["']?[ \\t]*$`, "gm"), `$1"${ko}"`);
|
|
81
|
+
}
|
|
82
|
+
return `labels:\n${out}`;
|
|
83
|
+
}))) changed.push(`.github/DISCUSSION_TEMPLATE/${name}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
72
87
|
// QA ์ด์ ์์ฑ ๋ด์ด ๋ถ์ด๋ ๊ธฐ๋ณธ ๋ผ๋ฒจ
|
|
73
88
|
const bot = join(gh, "workflows", "PROJECT-COMMON-QA-ISSUE-CREATION-BOT.yaml");
|
|
74
89
|
if (rewrite(bot, (txt) => txt.replace(/labels:\s*\['status: todo'\]/g, `labels: ['${EN_TO_KO.get("status: todo")}']`))) {
|
package/src/core/options-ask.js
CHANGED
|
@@ -23,9 +23,10 @@ import { DEPLOY_TARGETS, PUBLISH_TARGETS, CHANGELOG_PROVIDERS, DEFAULT_CODE_REVI
|
|
|
23
23
|
// null = ํ์ธ ์์ฒด๋ฅผ ๋ชป ํจ(๋น๋ ํฌยท์ง๋ฌธ ๋ถ๊ฐ) โ ์๋ด ์ ์ง(๋ณด์์ )
|
|
24
24
|
// ๋ฐํ state (#715 โ ๋น๋ํํ ๊ฒฐ๊ณผ ๊ธฐ๋ก์ฉ): not-checked ยท not-a-repo ยท exists-local ยท exists-remote ยท
|
|
25
25
|
// declined ยท create-failed ยท push-declined ยท pushed ยท push-failed
|
|
26
|
-
|
|
26
|
+
// isNonInteractive: ์๊ฒฉ ์กฐํยทpush ์์ ์๊ฒฉ ์ฆ๋ช
์ง๋ฌธ ์ฐจ๋จ (#988 โ ๋ํํ์ ๊ธฐ์กด ๊ทธ๋๋ก)
|
|
27
|
+
export async function ensureDeployBranch({ targetRoot = ".", deployBranch = "", defaultBranch = "", io = {}, say = () => {}, isNonInteractive = false }) {
|
|
27
28
|
if (!deployBranch || typeof io.confirm !== "function") return { created: false, pushed: false, ready: null, state: "not-checked" };
|
|
28
|
-
const st = branchStatus(targetRoot, deployBranch);
|
|
29
|
+
const st = branchStatus(targetRoot, deployBranch, { isNonInteractive });
|
|
29
30
|
if (!st.isRepo) return { created: false, pushed: false, ready: null, state: "not-a-repo" };
|
|
30
31
|
if (st.local) return { created: false, pushed: false, ready: true, state: "exists-local" };
|
|
31
32
|
if (st.remote === true) {
|
|
@@ -46,7 +47,7 @@ export async function ensureDeployBranch({ targetRoot = ".", deployBranch = "",
|
|
|
46
47
|
// #481 โ push ์ง๋ฌธ์ ๋ธ๋์น๋ช
๋ช
์ ("์ด๋ ๋ธ๋์น๋ฅผ pushํ๋์ง" ๋ถ๋ช
ํ ๋ฐฉ์ง)
|
|
47
48
|
const up = await io.confirm({ message: msg("options.branch.pushPrompt", { branch: deployBranch }), initialValue: true });
|
|
48
49
|
if (up !== true) return { created: true, pushed: false, ready: true, state: "push-declined" };
|
|
49
|
-
if (pushBranch(targetRoot, deployBranch)) {
|
|
50
|
+
if (pushBranch(targetRoot, deployBranch, { isNonInteractive })) {
|
|
50
51
|
say(msg("options.branch.pushed", { branch: deployBranch }));
|
|
51
52
|
return { created: true, pushed: true, ready: true, state: "pushed" };
|
|
52
53
|
}
|
|
@@ -195,7 +195,7 @@ export const FLAG_DETAILS = {
|
|
|
195
195
|
effect: "After the install, checks the development branch (--deploy-branch, default develop). Exists locally or on origin: nothing is done. Missing: creates it from the repository default branch (no checkout) and pushes it with git push -u origin <branch>. The result (created, skipped or failed) is printed in the completion summary and recorded in .github/.projectops/logs/ and the migration guide checklist.",
|
|
196
196
|
omitted: "The branch is never created or pushed; the completion summary lists it as a manual step with the command to run.",
|
|
197
197
|
example: "npx projectops --mode full --force --create-development-branch",
|
|
198
|
-
caution: "It pushes to the remote. A failed push (credentials, network, no origin) does not fail the run: the exit code stays 0 and the summary prints the command to run yourself.",
|
|
198
|
+
caution: "It pushes to the remote and never asks for credentials (terminal, askpass and ssh password prompts are turned off for that push), so a remote that needs a login fails at once instead of waiting. A failed push (credentials, network, no origin) does not fail the run: the exit code stays 0 and the summary prints the command to run yourself.",
|
|
199
199
|
},
|
|
200
200
|
"--language": {
|
|
201
201
|
when: "Set it only to change the language of an existing repo or to start a new repo in Korean.",
|