proof-pr 0.1.12 → 0.1.14
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 -2
- package/dist/index.js +369 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ ProofPR 是给开源维护者和工程团队使用的 PR 证据门禁。它在
|
|
|
12
12
|
npx proof-pr@latest --version
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
当前应输出 `0.1.
|
|
15
|
+
当前应输出 `0.1.14`。
|
|
16
16
|
|
|
17
17
|
不知道用哪个功能时:
|
|
18
18
|
|
|
@@ -22,6 +22,13 @@ npx proof-pr@latest
|
|
|
22
22
|
npx proof-pr@latest guide
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
+
不接入仓库,先体验报告:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx proof-pr@latest demo workflow --locale zh-CN
|
|
29
|
+
npx proof-pr@latest demo --list
|
|
30
|
+
```
|
|
31
|
+
|
|
25
32
|
初始化配置和 GitHub Action:
|
|
26
33
|
|
|
27
34
|
```bash
|
|
@@ -30,6 +37,14 @@ npx proof-pr@latest init
|
|
|
30
37
|
|
|
31
38
|
这个命令会生成 `.proofpr.yml` 和 `.github/workflows/proofpr.yml`,提交后打开 PR 即可看到报告。
|
|
32
39
|
|
|
40
|
+
体检接入状态:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npx proof-pr@latest doctor
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
这个命令会检查配置文件、workflow、Action 版本、PR 权限和本地 diff 是否可读。
|
|
47
|
+
|
|
33
48
|
本地扫描当前分支:
|
|
34
49
|
|
|
35
50
|
```bash
|
|
@@ -57,7 +72,7 @@ npx proof-pr@latest benchmark --cases benchmarks/cases
|
|
|
57
72
|
## GitHub Action
|
|
58
73
|
|
|
59
74
|
```yaml
|
|
60
|
-
- uses: linsk27/proof-pr@v0.1.
|
|
75
|
+
- uses: linsk27/proof-pr@v0.1.14
|
|
61
76
|
with:
|
|
62
77
|
fail-on: high
|
|
63
78
|
comment: "true"
|
package/dist/index.js
CHANGED
|
@@ -25728,17 +25728,187 @@ function dedupeFindings(findings) {
|
|
|
25728
25728
|
|
|
25729
25729
|
|
|
25730
25730
|
const execFileAsync = (0,external_node_util_namespaceObject.promisify)(external_node_child_process_.execFile);
|
|
25731
|
+
const CLI_VERSION = "0.1.14";
|
|
25732
|
+
const DEMO_CASES = [
|
|
25733
|
+
{
|
|
25734
|
+
id: "workflow",
|
|
25735
|
+
title: "高权限 workflow 运行不可信 PR 代码",
|
|
25736
|
+
description: "演示 pull_request_target 与 PR head checkout 组合风险。",
|
|
25737
|
+
diffText: `diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml
|
|
25738
|
+
index 1111111..2222222 100644
|
|
25739
|
+
--- a/.github/workflows/pr.yml
|
|
25740
|
+
+++ b/.github/workflows/pr.yml
|
|
25741
|
+
@@ -1,7 +1,17 @@
|
|
25742
|
+
name: PR automation
|
|
25743
|
+
on:
|
|
25744
|
+
+ pull_request_target:
|
|
25745
|
+
+ types: [opened, synchronize]
|
|
25746
|
+
+
|
|
25747
|
+
jobs:
|
|
25748
|
+
test:
|
|
25749
|
+
runs-on: ubuntu-latest
|
|
25750
|
+
steps:
|
|
25751
|
+
- - uses: actions/checkout@v4
|
|
25752
|
+
+ - uses: actions/checkout@v4
|
|
25753
|
+
+ with:
|
|
25754
|
+
+ repository: \${{ github.event.pull_request.head.repo.full_name }}
|
|
25755
|
+
+ ref: \${{ github.event.pull_request.head.sha }}
|
|
25756
|
+
+ - run: pnpm install
|
|
25757
|
+
+ - run: pnpm test
|
|
25758
|
+
`
|
|
25759
|
+
},
|
|
25760
|
+
{
|
|
25761
|
+
id: "secret",
|
|
25762
|
+
title: "疑似 secret 被提交",
|
|
25763
|
+
description: "演示 .env、OpenAI key、数据库连接串等敏感内容会被拦截。",
|
|
25764
|
+
diffText: `diff --git a/.env b/.env
|
|
25765
|
+
new file mode 100644
|
|
25766
|
+
index 0000000..1111111
|
|
25767
|
+
--- /dev/null
|
|
25768
|
+
+++ b/.env
|
|
25769
|
+
@@ -0,0 +1,2 @@
|
|
25770
|
+
+OPENAI_API_KEY=sk-proj-examplevalueexamplevalue1234567890
|
|
25771
|
+
+DATABASE_URL=postgres://demo:super-secret-password@example.com:5432/app
|
|
25772
|
+
`
|
|
25773
|
+
},
|
|
25774
|
+
{
|
|
25775
|
+
id: "dependency",
|
|
25776
|
+
title: "依赖大版本升级",
|
|
25777
|
+
description: "演示依赖 major upgrade 需要 changelog、迁移说明和验证证据。",
|
|
25778
|
+
diffText: `diff --git a/package.json b/package.json
|
|
25779
|
+
index 1111111..2222222 100644
|
|
25780
|
+
--- a/package.json
|
|
25781
|
+
+++ b/package.json
|
|
25782
|
+
@@ -1,8 +1,8 @@
|
|
25783
|
+
{
|
|
25784
|
+
"dependencies": {
|
|
25785
|
+
- "react": "^18.2.0",
|
|
25786
|
+
+ "react": "^19.0.0",
|
|
25787
|
+
"zod": "^3.25.1"
|
|
25788
|
+
},
|
|
25789
|
+
"devDependencies": {
|
|
25790
|
+
"typescript": "^5.9.3"
|
|
25791
|
+
}
|
|
25792
|
+
}
|
|
25793
|
+
`
|
|
25794
|
+
},
|
|
25795
|
+
{
|
|
25796
|
+
id: "mcp",
|
|
25797
|
+
title: "MCP 本地命令和凭据面",
|
|
25798
|
+
description: "演示 MCP / agent 配置中的 command、args、env 风险。",
|
|
25799
|
+
diffText: `diff --git a/.cursor/mcp.json b/.cursor/mcp.json
|
|
25800
|
+
new file mode 100644
|
|
25801
|
+
index 0000000..1111111
|
|
25802
|
+
--- /dev/null
|
|
25803
|
+
+++ b/.cursor/mcp.json
|
|
25804
|
+
@@ -0,0 +1,11 @@
|
|
25805
|
+
+{
|
|
25806
|
+
+ "mcpServers": {
|
|
25807
|
+
+ "local-admin": {
|
|
25808
|
+
+ "command": "node",
|
|
25809
|
+
+ "args": ["scripts/admin-server.js"],
|
|
25810
|
+
+ "env": {
|
|
25811
|
+
+ "API_TOKEN": "\${LOCAL_API_TOKEN}"
|
|
25812
|
+
+ }
|
|
25813
|
+
+ }
|
|
25814
|
+
+ }
|
|
25815
|
+
+}
|
|
25816
|
+
`,
|
|
25817
|
+
config: { preset: "mcp-security" }
|
|
25818
|
+
},
|
|
25819
|
+
{
|
|
25820
|
+
id: "ui-evidence",
|
|
25821
|
+
title: "UI 改动缺少截图证据",
|
|
25822
|
+
description: "演示 Evidence Contract:组件改动必须提供截图和验证说明。",
|
|
25823
|
+
diffText: `diff --git a/src/components/Button.tsx b/src/components/Button.tsx
|
|
25824
|
+
index 1111111..2222222 100644
|
|
25825
|
+
--- a/src/components/Button.tsx
|
|
25826
|
+
+++ b/src/components/Button.tsx
|
|
25827
|
+
@@ -1,3 +1,7 @@
|
|
25828
|
+
export function Button() {
|
|
25829
|
+
- return <button>Save</button>;
|
|
25830
|
+
+ return (
|
|
25831
|
+
+ <button className="primary">
|
|
25832
|
+
+ Save
|
|
25833
|
+
+ </button>
|
|
25834
|
+
+ );
|
|
25835
|
+
}
|
|
25836
|
+
`,
|
|
25837
|
+
config: {
|
|
25838
|
+
evidence: {
|
|
25839
|
+
contracts: [
|
|
25840
|
+
{
|
|
25841
|
+
id: "ui-screenshot",
|
|
25842
|
+
title: "UI changes need screenshots",
|
|
25843
|
+
paths: ["src/components/**"],
|
|
25844
|
+
requires: ["screenshot", "verification"],
|
|
25845
|
+
severity: "medium"
|
|
25846
|
+
}
|
|
25847
|
+
]
|
|
25848
|
+
}
|
|
25849
|
+
},
|
|
25850
|
+
pullRequest: {
|
|
25851
|
+
title: "Update button styling",
|
|
25852
|
+
body: "This updates the primary button style and spacing so the layout is easier to scan."
|
|
25853
|
+
}
|
|
25854
|
+
}
|
|
25855
|
+
];
|
|
25731
25856
|
const build_program = new Command();
|
|
25732
25857
|
build_program
|
|
25733
25858
|
.name("proof-pr")
|
|
25734
25859
|
.description("Review pull request evidence, scope, and safety before maintainers spend time on it.")
|
|
25735
|
-
.version(
|
|
25860
|
+
.version(CLI_VERSION);
|
|
25736
25861
|
build_program
|
|
25737
25862
|
.command("guide")
|
|
25738
25863
|
.description("Show a copy-paste friendly guide for common ProofPR tasks.")
|
|
25739
25864
|
.action(() => {
|
|
25740
25865
|
process.stdout.write(renderGuide());
|
|
25741
25866
|
});
|
|
25867
|
+
build_program
|
|
25868
|
+
.command("doctor")
|
|
25869
|
+
.description("Check whether ProofPR is installed correctly in the current repository.")
|
|
25870
|
+
.option("--config <path>", "Path to .proofpr.yml.", ".proofpr.yml")
|
|
25871
|
+
.option("--workflow-path <path>", "Path to the GitHub Actions workflow.", ".github/workflows/proofpr.yml")
|
|
25872
|
+
.option("--base <ref>", "Base git ref used for local diff checks.", "origin/main")
|
|
25873
|
+
.option("--head <ref>", "Head git ref used for local diff checks.", "HEAD")
|
|
25874
|
+
.action(async (options) => {
|
|
25875
|
+
const report = await runDoctor(options);
|
|
25876
|
+
process.stdout.write(renderDoctorReport(report));
|
|
25877
|
+
if (report.checks.some((check) => check.level === "fail")) {
|
|
25878
|
+
process.exitCode = 1;
|
|
25879
|
+
}
|
|
25880
|
+
});
|
|
25881
|
+
build_program
|
|
25882
|
+
.command("demo")
|
|
25883
|
+
.description("Run a built-in ProofPR demo case without cloning this repository.")
|
|
25884
|
+
.argument("[case]", "Demo case id. Use --list to see available cases.", "workflow")
|
|
25885
|
+
.option("--list", "List built-in demo cases.", false)
|
|
25886
|
+
.option("--format <format>", "Output format: markdown, json, sarif, or html.", parseFormat, "markdown")
|
|
25887
|
+
.option("--output <path>", "Write demo report output to a file instead of stdout.")
|
|
25888
|
+
.option("--locale <locale>", "Report language: en or zh-CN.", "zh-CN")
|
|
25889
|
+
.action(async (caseId, options) => {
|
|
25890
|
+
if (options.list) {
|
|
25891
|
+
process.stdout.write(renderDemoList());
|
|
25892
|
+
return;
|
|
25893
|
+
}
|
|
25894
|
+
const demoCase = DEMO_CASES.find((item) => item.id === caseId);
|
|
25895
|
+
if (!demoCase) {
|
|
25896
|
+
throw new Error(`Unknown demo case "${caseId}". Run "proof-pr demo --list" to see available cases.`);
|
|
25897
|
+
}
|
|
25898
|
+
const result = scanDiff(demoCase.diffText, {
|
|
25899
|
+
config: demoCase.config,
|
|
25900
|
+
pullRequest: demoCase.pullRequest
|
|
25901
|
+
});
|
|
25902
|
+
const locale = parseLocale(options.locale, "zh-CN");
|
|
25903
|
+
const output = renderDemoOutput(demoCase, result, options.format, locale);
|
|
25904
|
+
if (options.output) {
|
|
25905
|
+
await writeOutput(options.output, `${output}\n`);
|
|
25906
|
+
process.stdout.write(`ProofPR demo ${options.format} report written to ${options.output}\n`);
|
|
25907
|
+
}
|
|
25908
|
+
else {
|
|
25909
|
+
process.stdout.write(`${output}\n`);
|
|
25910
|
+
}
|
|
25911
|
+
});
|
|
25742
25912
|
build_program
|
|
25743
25913
|
.command("scan", { isDefault: true })
|
|
25744
25914
|
.description("Scan a git diff and print a ProofPR report.")
|
|
@@ -25836,27 +26006,34 @@ function renderGuide() {
|
|
|
25836
26006
|
npx proof-pr@latest init
|
|
25837
26007
|
然后提交 .proofpr.yml 和 .github/workflows/proofpr.yml,打开 PR 后看评论和 Actions summary。
|
|
25838
26008
|
|
|
25839
|
-
2.
|
|
26009
|
+
2. 体检当前仓库接入状态
|
|
26010
|
+
npx proof-pr@latest doctor
|
|
26011
|
+
检查配置文件、workflow、Action 版本、PR 权限和本地 diff 是否正常。
|
|
26012
|
+
|
|
26013
|
+
3. 本地检查当前分支
|
|
25840
26014
|
npx proof-pr@latest scan --base origin/main --head HEAD --locale zh-CN
|
|
25841
26015
|
适合在发 PR 前先看风险、证据评分和 Review 行动清单。
|
|
25842
26016
|
|
|
25843
|
-
|
|
26017
|
+
4. 生成可分享 HTML 报告
|
|
25844
26018
|
npx proof-pr@latest scan --base origin/main --head HEAD --locale zh-CN --format html --output proofpr-report.html
|
|
25845
26019
|
生成后用浏览器打开 proofpr-report.html。
|
|
25846
26020
|
|
|
25847
|
-
|
|
26021
|
+
5. 生成 GitHub Code Scanning 的 SARIF
|
|
25848
26022
|
npx proof-pr@latest scan --base origin/main --head HEAD --format sarif --output proofpr.sarif
|
|
25849
26023
|
适合在 CI 里配合 github/codeql-action/upload-sarif 使用。
|
|
25850
26024
|
|
|
25851
|
-
|
|
25852
|
-
npx proof-pr@latest
|
|
25853
|
-
|
|
26025
|
+
6. 不接入仓库,先试跑内置案例
|
|
26026
|
+
npx proof-pr@latest demo workflow --locale zh-CN
|
|
26027
|
+
不需要 clone 仓库或寻找 examples 文件,也能快速看到 ProofPR 会抓什么风险。
|
|
25854
26028
|
|
|
25855
|
-
|
|
26029
|
+
7. 查看所有内置案例
|
|
26030
|
+
npx proof-pr@latest demo --list
|
|
26031
|
+
|
|
26032
|
+
8. 验证规则样本是否仍然命中
|
|
25856
26033
|
npx proof-pr@latest benchmark --cases benchmarks/cases
|
|
25857
26034
|
适合维护 ProofPR 规则或发版前回归。
|
|
25858
26035
|
|
|
25859
|
-
|
|
26036
|
+
9. 调整审查强度
|
|
25860
26037
|
打开 .proofpr.yml,把 preset 改成 security-strict、dependency-careful 或 mcp-security。
|
|
25861
26038
|
|
|
25862
26039
|
结果在哪里看:
|
|
@@ -25864,6 +26041,188 @@ function renderGuide() {
|
|
|
25864
26041
|
- 本地 CLI:终端输出;如果用了 --output,就看写出的 HTML / JSON / SARIF / Markdown 文件。
|
|
25865
26042
|
`;
|
|
25866
26043
|
}
|
|
26044
|
+
function renderDemoList() {
|
|
26045
|
+
const rows = DEMO_CASES.map((item) => `- ${item.id}: ${item.title}\n ${item.description}`).join("\n");
|
|
26046
|
+
return `ProofPR 内置案例
|
|
26047
|
+
|
|
26048
|
+
用法:
|
|
26049
|
+
npx proof-pr@latest demo <case> --locale zh-CN
|
|
26050
|
+
|
|
26051
|
+
可用案例:
|
|
26052
|
+
${rows}
|
|
26053
|
+
`;
|
|
26054
|
+
}
|
|
26055
|
+
function renderDemoOutput(demoCase, result, format, locale) {
|
|
26056
|
+
if (format === "json") {
|
|
26057
|
+
return JSON.stringify({
|
|
26058
|
+
demo: {
|
|
26059
|
+
id: demoCase.id,
|
|
26060
|
+
title: demoCase.title,
|
|
26061
|
+
description: demoCase.description
|
|
26062
|
+
},
|
|
26063
|
+
result
|
|
26064
|
+
}, null, 2);
|
|
26065
|
+
}
|
|
26066
|
+
if (format === "markdown") {
|
|
26067
|
+
return `# ProofPR demo: ${demoCase.title}
|
|
26068
|
+
|
|
26069
|
+
${demoCase.description}
|
|
26070
|
+
|
|
26071
|
+
${renderMarkdownReport(result, locale)}`;
|
|
26072
|
+
}
|
|
26073
|
+
return renderOutput(result, format, locale);
|
|
26074
|
+
}
|
|
26075
|
+
async function runDoctor(options) {
|
|
26076
|
+
const checks = [];
|
|
26077
|
+
const nextSteps = new Set();
|
|
26078
|
+
if (await pathExists(options.config)) {
|
|
26079
|
+
try {
|
|
26080
|
+
const config = await loadConfig(options.config);
|
|
26081
|
+
checks.push({
|
|
26082
|
+
level: "pass",
|
|
26083
|
+
title: `${options.config} 可读取`,
|
|
26084
|
+
detail: `locale=${config.locale}, preset=${config.preset}, riskThreshold=${config.riskThreshold}`
|
|
26085
|
+
});
|
|
26086
|
+
if (config.comment.enabled) {
|
|
26087
|
+
checks.push({ level: "pass", title: "PR 评论已启用", detail: "comment.enabled=true" });
|
|
26088
|
+
}
|
|
26089
|
+
else {
|
|
26090
|
+
checks.push({ level: "warn", title: "PR 评论未启用", detail: "comment.enabled=false" });
|
|
26091
|
+
nextSteps.add("如果希望在 PR Conversation 里看到报告,把 .proofpr.yml 的 comment.enabled 改成 true。");
|
|
26092
|
+
}
|
|
26093
|
+
}
|
|
26094
|
+
catch (error) {
|
|
26095
|
+
checks.push({
|
|
26096
|
+
level: "fail",
|
|
26097
|
+
title: `${options.config} 解析失败`,
|
|
26098
|
+
detail: error instanceof Error ? error.message : String(error)
|
|
26099
|
+
});
|
|
26100
|
+
nextSteps.add("修复 .proofpr.yml 的 YAML 格式,或重新运行 npx proof-pr@latest init --force。");
|
|
26101
|
+
}
|
|
26102
|
+
}
|
|
26103
|
+
else {
|
|
26104
|
+
checks.push({ level: "fail", title: `缺少 ${options.config}` });
|
|
26105
|
+
nextSteps.add("运行 npx proof-pr@latest init 生成 .proofpr.yml 和 GitHub Actions workflow。");
|
|
26106
|
+
}
|
|
26107
|
+
if (await pathExists(options.workflowPath)) {
|
|
26108
|
+
const workflow = await (0,promises_namespaceObject.readFile)(options.workflowPath, "utf8");
|
|
26109
|
+
checks.push({ level: "pass", title: `${options.workflowPath} 已存在` });
|
|
26110
|
+
inspectWorkflow(workflow, checks, nextSteps);
|
|
26111
|
+
}
|
|
26112
|
+
else {
|
|
26113
|
+
checks.push({ level: "fail", title: `缺少 ${options.workflowPath}` });
|
|
26114
|
+
nextSteps.add("运行 npx proof-pr@latest init 生成 .github/workflows/proofpr.yml。");
|
|
26115
|
+
}
|
|
26116
|
+
await inspectGitDiff(options, checks, nextSteps);
|
|
26117
|
+
if (nextSteps.size === 0) {
|
|
26118
|
+
nextSteps.add("当前接入状态正常;可以打开 PR,或运行 npx proof-pr@latest scan --base origin/main --head HEAD --locale zh-CN 做本地自查。");
|
|
26119
|
+
}
|
|
26120
|
+
return { checks, nextSteps: [...nextSteps] };
|
|
26121
|
+
}
|
|
26122
|
+
function inspectWorkflow(workflow, checks, nextSteps) {
|
|
26123
|
+
if (/pull_request\s*:/.test(workflow)) {
|
|
26124
|
+
checks.push({ level: "pass", title: "workflow 会在 Pull Request 事件运行" });
|
|
26125
|
+
}
|
|
26126
|
+
else {
|
|
26127
|
+
checks.push({ level: "fail", title: "workflow 没有监听 pull_request" });
|
|
26128
|
+
nextSteps.add("确认 .github/workflows/proofpr.yml 包含 on.pull_request。");
|
|
26129
|
+
}
|
|
26130
|
+
const actionVersion = workflow.match(/linsk27\/proof-pr@(v[0-9]+\.[0-9]+\.[0-9]+)/)?.[1];
|
|
26131
|
+
if (!actionVersion) {
|
|
26132
|
+
checks.push({ level: "fail", title: "workflow 没有使用 linsk27/proof-pr Action" });
|
|
26133
|
+
nextSteps.add(`把 workflow step 更新为 uses: linsk27/proof-pr@v${CLI_VERSION}。`);
|
|
26134
|
+
}
|
|
26135
|
+
else if (actionVersion === `v${CLI_VERSION}`) {
|
|
26136
|
+
checks.push({ level: "pass", title: `GitHub Action 版本为 ${actionVersion}` });
|
|
26137
|
+
}
|
|
26138
|
+
else {
|
|
26139
|
+
checks.push({
|
|
26140
|
+
level: "warn",
|
|
26141
|
+
title: `GitHub Action 版本较旧:${actionVersion}`,
|
|
26142
|
+
detail: `当前 CLI 版本是 v${CLI_VERSION}`
|
|
26143
|
+
});
|
|
26144
|
+
nextSteps.add(`把 workflow 里的 uses 更新为 linsk27/proof-pr@v${CLI_VERSION}。`);
|
|
26145
|
+
}
|
|
26146
|
+
if (/pull-requests\s*:\s*write/.test(workflow)) {
|
|
26147
|
+
checks.push({ level: "pass", title: "workflow 具备写 PR 评论权限" });
|
|
26148
|
+
}
|
|
26149
|
+
else {
|
|
26150
|
+
checks.push({ level: "warn", title: "workflow 可能缺少 pull-requests: write 权限" });
|
|
26151
|
+
nextSteps.add("如果需要自动评论 PR,在 workflow permissions 中加入 pull-requests: write。");
|
|
26152
|
+
}
|
|
26153
|
+
if (/contents\s*:\s*read/.test(workflow)) {
|
|
26154
|
+
checks.push({ level: "pass", title: "workflow 具备读取仓库内容权限" });
|
|
26155
|
+
}
|
|
26156
|
+
else {
|
|
26157
|
+
checks.push({ level: "warn", title: "workflow 未显式声明 contents: read" });
|
|
26158
|
+
nextSteps.add("建议在 workflow permissions 中加入 contents: read。");
|
|
26159
|
+
}
|
|
26160
|
+
}
|
|
26161
|
+
async function inspectGitDiff(options, checks, nextSteps) {
|
|
26162
|
+
try {
|
|
26163
|
+
const { stdout } = await execFileAsync("git", ["rev-parse", "--is-inside-work-tree"]);
|
|
26164
|
+
if (stdout.trim() !== "true") {
|
|
26165
|
+
checks.push({ level: "warn", title: "当前目录不是 Git 仓库" });
|
|
26166
|
+
nextSteps.add("进入项目 Git 仓库根目录后再运行 npx proof-pr@latest doctor。");
|
|
26167
|
+
return;
|
|
26168
|
+
}
|
|
26169
|
+
}
|
|
26170
|
+
catch {
|
|
26171
|
+
checks.push({ level: "warn", title: "当前目录不是 Git 仓库" });
|
|
26172
|
+
nextSteps.add("进入项目 Git 仓库根目录后再运行 npx proof-pr@latest doctor。");
|
|
26173
|
+
return;
|
|
26174
|
+
}
|
|
26175
|
+
checks.push({ level: "pass", title: "当前目录位于 Git 仓库中" });
|
|
26176
|
+
try {
|
|
26177
|
+
const branch = (await execFileAsync("git", ["branch", "--show-current"])).stdout.trim();
|
|
26178
|
+
checks.push({
|
|
26179
|
+
level: "info",
|
|
26180
|
+
title: branch ? `当前分支:${branch}` : "当前处于 detached HEAD"
|
|
26181
|
+
});
|
|
26182
|
+
}
|
|
26183
|
+
catch {
|
|
26184
|
+
checks.push({ level: "info", title: "无法读取当前分支名" });
|
|
26185
|
+
}
|
|
26186
|
+
try {
|
|
26187
|
+
const diff = await readGitDiff(options.base, options.head);
|
|
26188
|
+
checks.push({
|
|
26189
|
+
level: "pass",
|
|
26190
|
+
title: `可以读取 ${options.base}...${options.head} diff`,
|
|
26191
|
+
detail: diff.length === 0 ? "当前没有可扫描的 diff。" : `diff 大小约 ${diff.length} 字符。`
|
|
26192
|
+
});
|
|
26193
|
+
}
|
|
26194
|
+
catch (error) {
|
|
26195
|
+
checks.push({
|
|
26196
|
+
level: "warn",
|
|
26197
|
+
title: `无法读取 ${options.base}...${options.head} diff`,
|
|
26198
|
+
detail: error instanceof Error ? error.message : String(error)
|
|
26199
|
+
});
|
|
26200
|
+
nextSteps.add(`运行 git fetch origin 后重试;如果主分支不是 main,请使用 --base origin/master 或你的实际主分支。`);
|
|
26201
|
+
}
|
|
26202
|
+
}
|
|
26203
|
+
function renderDoctorReport(report) {
|
|
26204
|
+
const failCount = report.checks.filter((check) => check.level === "fail").length;
|
|
26205
|
+
const warnCount = report.checks.filter((check) => check.level === "warn").length;
|
|
26206
|
+
const status = failCount > 0 ? "需要先修复" : warnCount > 0 ? "基本可用,但建议优化" : "接入正常";
|
|
26207
|
+
const checks = report.checks
|
|
26208
|
+
.map((check) => {
|
|
26209
|
+
const detail = check.detail ? `\n ${check.detail}` : "";
|
|
26210
|
+
return `[${check.level}] ${check.title}${detail}`;
|
|
26211
|
+
})
|
|
26212
|
+
.join("\n");
|
|
26213
|
+
const nextSteps = report.nextSteps.map((step) => `- ${step}`).join("\n");
|
|
26214
|
+
return `ProofPR doctor
|
|
26215
|
+
|
|
26216
|
+
状态:${status}
|
|
26217
|
+
统计:${failCount} fail, ${warnCount} warn, ${report.checks.length} checks
|
|
26218
|
+
|
|
26219
|
+
Checks:
|
|
26220
|
+
${checks}
|
|
26221
|
+
|
|
26222
|
+
Next:
|
|
26223
|
+
${nextSteps}
|
|
26224
|
+
`;
|
|
26225
|
+
}
|
|
25867
26226
|
async function readGitDiff(base, head) {
|
|
25868
26227
|
const args = ["diff", "--no-ext-diff", "--unified=0"];
|
|
25869
26228
|
if (base) {
|
|
@@ -25925,7 +26284,7 @@ jobs:
|
|
|
25925
26284
|
runs-on: ubuntu-latest
|
|
25926
26285
|
steps:
|
|
25927
26286
|
- uses: actions/checkout@v4
|
|
25928
|
-
- uses: linsk27/proof-pr@
|
|
26287
|
+
- uses: linsk27/proof-pr@v${CLI_VERSION}
|
|
25929
26288
|
with:
|
|
25930
26289
|
fail-on: ${failOn}
|
|
25931
26290
|
comment: "true"
|