proof-pr 0.1.2 → 0.1.3
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/dist/index.js +250 -46
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,13 +11,14 @@ ProofPR 帮助维护者在投入深入 review 之前,先检查 PR 的证据、
|
|
|
11
11
|
```bash
|
|
12
12
|
npx proof-pr init
|
|
13
13
|
npx proof-pr scan --base origin/main --head HEAD
|
|
14
|
+
npx proof-pr scan --base origin/main --head HEAD --locale zh-CN
|
|
14
15
|
npx proof-pr scan --base origin/main --pr-body-file pr-body.md --format json
|
|
15
16
|
```
|
|
16
17
|
|
|
17
18
|
## GitHub Action
|
|
18
19
|
|
|
19
20
|
```yaml
|
|
20
|
-
- uses: linsk27/proof-pr@v0.1.
|
|
21
|
+
- uses: linsk27/proof-pr@v0.1.3
|
|
21
22
|
with:
|
|
22
23
|
fail-on: high
|
|
23
24
|
```
|
package/dist/index.js
CHANGED
|
@@ -23111,7 +23111,9 @@ function preprocess(fn, schema) {
|
|
|
23111
23111
|
|
|
23112
23112
|
|
|
23113
23113
|
const riskLevelSchema = schemas_enum(["low", "medium", "high"]);
|
|
23114
|
+
const localeSchema = schemas_enum(["en", "zh-CN"]);
|
|
23114
23115
|
const configSchema = object({
|
|
23116
|
+
locale: localeSchema.default("en"),
|
|
23115
23117
|
riskThreshold: riskLevelSchema.default("high"),
|
|
23116
23118
|
ignorePaths: array(schemas_string()).default([]),
|
|
23117
23119
|
sensitivePaths: array(schemas_string())
|
|
@@ -23174,6 +23176,10 @@ function riskMeetsThreshold(risk, threshold) {
|
|
|
23174
23176
|
function riskRank(risk) {
|
|
23175
23177
|
return { low: 1, medium: 2, high: 3 }[risk];
|
|
23176
23178
|
}
|
|
23179
|
+
function parseLocale(value, fallback = "en") {
|
|
23180
|
+
const result = localeSchema.safeParse(value);
|
|
23181
|
+
return result.success ? result.data : fallback;
|
|
23182
|
+
}
|
|
23177
23183
|
function isMissingFileError(error) {
|
|
23178
23184
|
return (typeof error === "object" &&
|
|
23179
23185
|
error !== null &&
|
|
@@ -23183,35 +23189,11 @@ function isMissingFileError(error) {
|
|
|
23183
23189
|
//# sourceMappingURL=config.js.map
|
|
23184
23190
|
;// CONCATENATED MODULE: ../core/dist/reporters.js
|
|
23185
23191
|
const REPORT_MARKER = "<!-- proof-pr-report -->";
|
|
23186
|
-
function renderMarkdownReport(result) {
|
|
23187
|
-
|
|
23188
|
-
|
|
23189
|
-
"# ProofPR Review",
|
|
23190
|
-
"",
|
|
23191
|
-
`Risk: **${result.risk}**`,
|
|
23192
|
-
"",
|
|
23193
|
-
"## Evidence",
|
|
23194
|
-
"",
|
|
23195
|
-
`- Files changed: ${result.summary.filesChanged}`,
|
|
23196
|
-
`- Additions: ${result.summary.additions}`,
|
|
23197
|
-
`- Deletions: ${result.summary.deletions}`,
|
|
23198
|
-
`- Test files changed: ${result.summary.testFilesChanged}`,
|
|
23199
|
-
`- Sensitive files changed: ${result.summary.sensitiveFilesChanged}`,
|
|
23200
|
-
`- PR description: ${result.summary.pullRequestDescription}`,
|
|
23201
|
-
`- Verification evidence: ${formatBoolean(result.summary.verificationEvidence)}`,
|
|
23202
|
-
`- Reproduction context: ${formatBoolean(result.summary.reproductionEvidence)}`,
|
|
23203
|
-
""
|
|
23204
|
-
];
|
|
23205
|
-
if (result.findings.length === 0) {
|
|
23206
|
-
lines.push("## Findings", "", "No review-risk findings detected by the enabled rules.", "");
|
|
23207
|
-
return lines.join("\n");
|
|
23192
|
+
function renderMarkdownReport(result, locale = "en") {
|
|
23193
|
+
if (locale === "zh-CN") {
|
|
23194
|
+
return renderChineseMarkdownReport(result);
|
|
23208
23195
|
}
|
|
23209
|
-
|
|
23210
|
-
for (const finding of result.findings) {
|
|
23211
|
-
lines.push(formatFinding(finding), "");
|
|
23212
|
-
}
|
|
23213
|
-
lines.push("## Maintainer Focus", "", ...maintainerFocus(result.findings).map((item) => `- ${item}`), "");
|
|
23214
|
-
return lines.join("\n");
|
|
23196
|
+
return renderEnglishMarkdownReport(result);
|
|
23215
23197
|
}
|
|
23216
23198
|
function getReportMarker() {
|
|
23217
23199
|
return REPORT_MARKER;
|
|
@@ -23234,7 +23216,7 @@ function renderSarifReport(result) {
|
|
|
23234
23216
|
tool: {
|
|
23235
23217
|
driver: {
|
|
23236
23218
|
name: "ProofPR",
|
|
23237
|
-
informationUri: "https://github.com/
|
|
23219
|
+
informationUri: "https://github.com/linsk27/proof-pr",
|
|
23238
23220
|
rules: [...rules.values()]
|
|
23239
23221
|
}
|
|
23240
23222
|
},
|
|
@@ -23256,7 +23238,67 @@ function renderSarifReport(result) {
|
|
|
23256
23238
|
]
|
|
23257
23239
|
}, null, 2);
|
|
23258
23240
|
}
|
|
23259
|
-
function
|
|
23241
|
+
function renderEnglishMarkdownReport(result) {
|
|
23242
|
+
const lines = [
|
|
23243
|
+
REPORT_MARKER,
|
|
23244
|
+
"# ProofPR Review",
|
|
23245
|
+
"",
|
|
23246
|
+
`Risk: **${result.risk}**`,
|
|
23247
|
+
"",
|
|
23248
|
+
"## Evidence",
|
|
23249
|
+
"",
|
|
23250
|
+
`- Files changed: ${result.summary.filesChanged}`,
|
|
23251
|
+
`- Additions: ${result.summary.additions}`,
|
|
23252
|
+
`- Deletions: ${result.summary.deletions}`,
|
|
23253
|
+
`- Test files changed: ${result.summary.testFilesChanged}`,
|
|
23254
|
+
`- Sensitive files changed: ${result.summary.sensitiveFilesChanged}`,
|
|
23255
|
+
`- PR description: ${result.summary.pullRequestDescription}`,
|
|
23256
|
+
`- Verification evidence: ${formatBoolean(result.summary.verificationEvidence)}`,
|
|
23257
|
+
`- Reproduction context: ${formatBoolean(result.summary.reproductionEvidence)}`,
|
|
23258
|
+
""
|
|
23259
|
+
];
|
|
23260
|
+
if (result.findings.length === 0) {
|
|
23261
|
+
lines.push("## Findings", "", "No review-risk findings detected by the enabled rules.", "");
|
|
23262
|
+
return lines.join("\n");
|
|
23263
|
+
}
|
|
23264
|
+
lines.push("## Findings", "");
|
|
23265
|
+
for (const finding of result.findings) {
|
|
23266
|
+
lines.push(formatEnglishFinding(finding), "");
|
|
23267
|
+
}
|
|
23268
|
+
lines.push("## Maintainer Focus", "", ...maintainerFocus(result.findings, "en").map((item) => `- ${item}`), "");
|
|
23269
|
+
return lines.join("\n");
|
|
23270
|
+
}
|
|
23271
|
+
function renderChineseMarkdownReport(result) {
|
|
23272
|
+
const lines = [
|
|
23273
|
+
REPORT_MARKER,
|
|
23274
|
+
"# ProofPR 审查报告",
|
|
23275
|
+
"",
|
|
23276
|
+
`风险等级:**${translateRisk(result.risk)}**`,
|
|
23277
|
+
"",
|
|
23278
|
+
"## 证据概览",
|
|
23279
|
+
"",
|
|
23280
|
+
`- 改动文件数:${result.summary.filesChanged}`,
|
|
23281
|
+
`- 新增行数:${result.summary.additions}`,
|
|
23282
|
+
`- 删除行数:${result.summary.deletions}`,
|
|
23283
|
+
`- 测试文件改动数:${result.summary.testFilesChanged}`,
|
|
23284
|
+
`- 敏感文件改动数:${result.summary.sensitiveFilesChanged}`,
|
|
23285
|
+
`- PR 描述质量:${translateDescriptionState(result.summary.pullRequestDescription)}`,
|
|
23286
|
+
`- 验证证据:${formatChineseBoolean(result.summary.verificationEvidence)}`,
|
|
23287
|
+
`- 复现上下文:${formatChineseBoolean(result.summary.reproductionEvidence)}`,
|
|
23288
|
+
""
|
|
23289
|
+
];
|
|
23290
|
+
if (result.findings.length === 0) {
|
|
23291
|
+
lines.push("## 风险发现", "", "启用的规则没有发现需要优先关注的 review 风险。", "");
|
|
23292
|
+
return lines.join("\n");
|
|
23293
|
+
}
|
|
23294
|
+
lines.push("## 风险发现", "");
|
|
23295
|
+
for (const finding of result.findings) {
|
|
23296
|
+
lines.push(formatChineseFinding(finding), "");
|
|
23297
|
+
}
|
|
23298
|
+
lines.push("## 维护者关注点", "", ...maintainerFocus(result.findings, "zh-CN").map((item) => `- ${item}`), "");
|
|
23299
|
+
return lines.join("\n");
|
|
23300
|
+
}
|
|
23301
|
+
function formatEnglishFinding(finding) {
|
|
23260
23302
|
const lines = [
|
|
23261
23303
|
`### ${finding.title}`,
|
|
23262
23304
|
"",
|
|
@@ -23276,39 +23318,166 @@ function formatFinding(finding) {
|
|
|
23276
23318
|
}
|
|
23277
23319
|
return lines.join("\n");
|
|
23278
23320
|
}
|
|
23279
|
-
function
|
|
23321
|
+
function formatChineseFinding(finding) {
|
|
23322
|
+
const translated = translateFinding(finding);
|
|
23323
|
+
const lines = [
|
|
23324
|
+
`### ${translated.title}`,
|
|
23325
|
+
"",
|
|
23326
|
+
`- 规则:\`${finding.ruleId}\``,
|
|
23327
|
+
`- 严重程度:\`${translateSeverity(finding.severity)}\``,
|
|
23328
|
+
finding.path ? `- 路径:\`${finding.path}\`` : undefined,
|
|
23329
|
+
`- 详情:${translated.message}`
|
|
23330
|
+
].filter((line) => Boolean(line));
|
|
23331
|
+
if (finding.evidence && finding.evidence.length > 0) {
|
|
23332
|
+
lines.push("- 证据:");
|
|
23333
|
+
for (const item of finding.evidence) {
|
|
23334
|
+
lines.push(` - \`${translateEvidence(item)}\``);
|
|
23335
|
+
}
|
|
23336
|
+
}
|
|
23337
|
+
if (translated.recommendation) {
|
|
23338
|
+
lines.push(`- 建议:${translated.recommendation}`);
|
|
23339
|
+
}
|
|
23340
|
+
return lines.join("\n");
|
|
23341
|
+
}
|
|
23342
|
+
function maintainerFocus(findings, locale) {
|
|
23280
23343
|
const focus = new Set();
|
|
23281
23344
|
for (const finding of findings) {
|
|
23282
23345
|
if (finding.ruleId.startsWith("secret-detected")) {
|
|
23283
|
-
focus.add(
|
|
23346
|
+
focus.add(locale === "zh-CN"
|
|
23347
|
+
? "轮换任何可能暴露的凭证,并在移除 secret 前阻止合并。"
|
|
23348
|
+
: "Rotate any exposed credential and block the PR until secrets are removed.");
|
|
23284
23349
|
}
|
|
23285
23350
|
else if (finding.ruleId === "workflow-permission-change") {
|
|
23286
|
-
focus.add(
|
|
23351
|
+
focus.add(locale === "zh-CN"
|
|
23352
|
+
? "合并前重点审查 GitHub Actions 权限。"
|
|
23353
|
+
: "Review GitHub Actions permissions before merging.");
|
|
23287
23354
|
}
|
|
23288
23355
|
else if (finding.ruleId === "missing-tests") {
|
|
23289
|
-
focus.add(
|
|
23356
|
+
focus.add(locale === "zh-CN"
|
|
23357
|
+
? "要求补充测试或清晰的手动验证说明。"
|
|
23358
|
+
: "Ask for tests or a manual verification note.");
|
|
23290
23359
|
}
|
|
23291
23360
|
else if (finding.ruleId === "thin-pr-description") {
|
|
23292
|
-
focus.add(
|
|
23361
|
+
focus.add(locale === "zh-CN"
|
|
23362
|
+
? "深入 review 前要求补充更清楚的 PR 描述。"
|
|
23363
|
+
: "Ask for a clearer PR description before deep review.");
|
|
23293
23364
|
}
|
|
23294
23365
|
else if (finding.ruleId === "missing-reproduction-context") {
|
|
23295
|
-
focus.add(
|
|
23366
|
+
focus.add(locale === "zh-CN"
|
|
23367
|
+
? "要求补充复现步骤或 before/after 上下文。"
|
|
23368
|
+
: "Ask for reproduction steps or before/after context.");
|
|
23296
23369
|
}
|
|
23297
23370
|
else if (finding.ruleId === "change-size") {
|
|
23298
|
-
focus.add(
|
|
23371
|
+
focus.add(locale === "zh-CN"
|
|
23372
|
+
? "要求拆分 PR,或提供逐文件 review 指南。"
|
|
23373
|
+
: "Request a smaller PR or a file-by-file review guide.");
|
|
23299
23374
|
}
|
|
23300
23375
|
else if (finding.ruleId === "mcp-credential-risk") {
|
|
23301
|
-
focus.add(
|
|
23376
|
+
focus.add(locale === "zh-CN"
|
|
23377
|
+
? "重点审查 MCP command、args 和凭证处理方式。"
|
|
23378
|
+
: "Review MCP commands, args, and credential handling.");
|
|
23302
23379
|
}
|
|
23303
23380
|
}
|
|
23304
23381
|
if (focus.size === 0) {
|
|
23305
|
-
focus.add(
|
|
23382
|
+
focus.add(locale === "zh-CN"
|
|
23383
|
+
? "审查列出的敏感文件;如果上下文不足,要求贡献者补充证据。"
|
|
23384
|
+
: "Review the listed sensitive files and ask for evidence where context is thin.");
|
|
23306
23385
|
}
|
|
23307
23386
|
return [...focus];
|
|
23308
23387
|
}
|
|
23388
|
+
function translateFinding(finding) {
|
|
23389
|
+
if (finding.ruleId === "change-size") {
|
|
23390
|
+
const files = finding.evidence?.find((item) => item.startsWith("files: "))?.replace("files: ", "");
|
|
23391
|
+
const lines = finding.evidence?.find((item) => item.startsWith("changed lines: "))?.replace("changed lines: ", "");
|
|
23392
|
+
return {
|
|
23393
|
+
title: finding.severity === "high" ? "review 面积过大" : "review 面积偏大",
|
|
23394
|
+
message: files && lines ? `该改动涉及 ${files} 个文件、${lines} 行变更。` : finding.message,
|
|
23395
|
+
recommendation: finding.severity === "high"
|
|
23396
|
+
? "建议要求拆分 PR,或提供清晰的 review map 后再投入深度 review。"
|
|
23397
|
+
: "建议要求贡献者解释改动边界,并标出最需要重点 review 的文件。"
|
|
23398
|
+
};
|
|
23399
|
+
}
|
|
23400
|
+
if (finding.ruleId === "sensitive-path") {
|
|
23401
|
+
return {
|
|
23402
|
+
title: "敏感文件发生变更",
|
|
23403
|
+
message: finding.path ? `${finding.path} 命中了敏感路径配置。` : finding.message,
|
|
23404
|
+
recommendation: "请重点审查权限、凭证、发布、依赖和 CI 相关变更。"
|
|
23405
|
+
};
|
|
23406
|
+
}
|
|
23407
|
+
if (finding.ruleId === "missing-tests") {
|
|
23408
|
+
return {
|
|
23409
|
+
title: "缺少验证证据",
|
|
23410
|
+
message: "代码发生变更,但没有检测到测试文件改动或 PR 验证说明。",
|
|
23411
|
+
recommendation: "建议要求补充测试,或提供清晰的手动验证说明后再深入 review。"
|
|
23412
|
+
};
|
|
23413
|
+
}
|
|
23414
|
+
if (finding.ruleId === "thin-pr-description") {
|
|
23415
|
+
const missing = finding.title.toLowerCase().includes("missing");
|
|
23416
|
+
return {
|
|
23417
|
+
title: missing ? "PR 描述为空" : "PR 描述过薄",
|
|
23418
|
+
message: missing ? "PR 正文为空,维护者缺少 review 前的上下文。" : "PR 正文较短,可能不足以支撑有效 review。",
|
|
23419
|
+
recommendation: "建议要求补充改动动机、验证证据、兼容性和发布影响说明。"
|
|
23420
|
+
};
|
|
23421
|
+
}
|
|
23422
|
+
if (finding.ruleId === "missing-reproduction-context") {
|
|
23423
|
+
return {
|
|
23424
|
+
title: "缺少复现或 before/after 上下文",
|
|
23425
|
+
message: "PR 未提到复现步骤、预期行为、实际行为或 before/after 说明。",
|
|
23426
|
+
recommendation: "建议要求补充复现步骤或 before/after 说明,方便 reviewer 验证改动路径。"
|
|
23427
|
+
};
|
|
23428
|
+
}
|
|
23429
|
+
if (finding.ruleId === "dependency-added") {
|
|
23430
|
+
return {
|
|
23431
|
+
title: "依赖清单发生变更",
|
|
23432
|
+
message: finding.path ? `${finding.path} 中新增或修改了类似依赖的条目。` : finding.message,
|
|
23433
|
+
recommendation: "请确认包名、许可证、来源可信度,以及 lockfile 是否匹配预期依赖变化。"
|
|
23434
|
+
};
|
|
23435
|
+
}
|
|
23436
|
+
if (finding.ruleId === "workflow-permission-change") {
|
|
23437
|
+
return {
|
|
23438
|
+
title: "Workflow 权限发生变更",
|
|
23439
|
+
message: finding.path ? `${finding.path} 新增或修改了 GitHub Actions 权限。` : finding.message,
|
|
23440
|
+
recommendation: "请确认 workflow 是否真的需要写权限或 token 权限,并检查不可信 PR 是否能触达该 workflow。"
|
|
23441
|
+
};
|
|
23442
|
+
}
|
|
23443
|
+
if (finding.ruleId === "mcp-credential-risk") {
|
|
23444
|
+
return {
|
|
23445
|
+
title: "MCP 配置需要重点审查",
|
|
23446
|
+
message: finding.path ? `${finding.path} 新增了与命令或凭证相关的 MCP 配置。` : finding.message,
|
|
23447
|
+
recommendation: "避免在 MCP 配置中提交凭证,并审查 command 与 args 是否会扩大本地执行面。"
|
|
23448
|
+
};
|
|
23449
|
+
}
|
|
23450
|
+
if (finding.ruleId.startsWith("secret-detected")) {
|
|
23451
|
+
return {
|
|
23452
|
+
title: "可能提交了 secret",
|
|
23453
|
+
message: finding.message.replace("Added line looks like it contains", "新增行疑似包含"),
|
|
23454
|
+
recommendation: "请将凭证移到 secret manager 或 CI secret store,轮换任何已暴露的值,并只提交占位符。"
|
|
23455
|
+
};
|
|
23456
|
+
}
|
|
23457
|
+
return finding;
|
|
23458
|
+
}
|
|
23459
|
+
function translateEvidence(item) {
|
|
23460
|
+
return item
|
|
23461
|
+
.replace("files: ", "文件数:")
|
|
23462
|
+
.replace("changed lines: ", "变更行数:")
|
|
23463
|
+
.replace("line ", "第 ")
|
|
23464
|
+
.replace(": ", " 行:");
|
|
23465
|
+
}
|
|
23466
|
+
function translateRisk(risk) {
|
|
23467
|
+
return { low: "低", medium: "中", high: "高" }[risk] ?? risk;
|
|
23468
|
+
}
|
|
23469
|
+
function translateSeverity(severity) {
|
|
23470
|
+
return { info: "信息", low: "低", medium: "中", high: "高" }[severity] ?? severity;
|
|
23471
|
+
}
|
|
23472
|
+
function translateDescriptionState(state) {
|
|
23473
|
+
return { unavailable: "不可用", missing: "缺失", thin: "过薄", present: "充足" }[state] ?? state;
|
|
23474
|
+
}
|
|
23309
23475
|
function formatBoolean(value) {
|
|
23310
23476
|
return value ? "yes" : "no";
|
|
23311
23477
|
}
|
|
23478
|
+
function formatChineseBoolean(value) {
|
|
23479
|
+
return value ? "有" : "无";
|
|
23480
|
+
}
|
|
23312
23481
|
function sarifLevel(severity) {
|
|
23313
23482
|
if (severity === "high") {
|
|
23314
23483
|
return "error";
|
|
@@ -23579,6 +23748,29 @@ function redactLine(line) {
|
|
|
23579
23748
|
|
|
23580
23749
|
|
|
23581
23750
|
|
|
23751
|
+
const PACKAGE_JSON_NON_DEPENDENCY_KEYS = new Set([
|
|
23752
|
+
"author",
|
|
23753
|
+
"bin",
|
|
23754
|
+
"bugs",
|
|
23755
|
+
"description",
|
|
23756
|
+
"engines",
|
|
23757
|
+
"exports",
|
|
23758
|
+
"files",
|
|
23759
|
+
"homepage",
|
|
23760
|
+
"keywords",
|
|
23761
|
+
"license",
|
|
23762
|
+
"main",
|
|
23763
|
+
"module",
|
|
23764
|
+
"name",
|
|
23765
|
+
"packageManager",
|
|
23766
|
+
"private",
|
|
23767
|
+
"publishConfig",
|
|
23768
|
+
"repository",
|
|
23769
|
+
"scripts",
|
|
23770
|
+
"type",
|
|
23771
|
+
"types",
|
|
23772
|
+
"version"
|
|
23773
|
+
]);
|
|
23582
23774
|
function analyzeDiffFiles(files, config, pullRequest) {
|
|
23583
23775
|
const activeFiles = files.filter((file) => !matchesAny(file.path, config.ignorePaths));
|
|
23584
23776
|
const findings = [];
|
|
@@ -23738,7 +23930,15 @@ function analyzeDependencyChanges(files, config) {
|
|
|
23738
23930
|
}
|
|
23739
23931
|
function isDependencyLikeAddition(path, line) {
|
|
23740
23932
|
if (path.endsWith("package.json")) {
|
|
23741
|
-
|
|
23933
|
+
const match = /^"(?<key>[@A-Za-z0-9_.-]+)"\s*:\s*"(?<value>[^"]*)"/.exec(line);
|
|
23934
|
+
if (!match?.groups) {
|
|
23935
|
+
return false;
|
|
23936
|
+
}
|
|
23937
|
+
const { key, value } = match.groups;
|
|
23938
|
+
if (!key || !value || PACKAGE_JSON_NON_DEPENDENCY_KEYS.has(key)) {
|
|
23939
|
+
return false;
|
|
23940
|
+
}
|
|
23941
|
+
return /^(?:\^|~|>=?|<=?|\d|workspace:|npm:|file:|link:|portal:|git\+|https?:|github:)/.test(value);
|
|
23742
23942
|
}
|
|
23743
23943
|
if (path.endsWith("requirements.txt")) {
|
|
23744
23944
|
return /^[A-Za-z0-9_.-]+(?:\[.*\])?\s*(?:==|>=|<=|~=|>|<)\s*[^#\s]+/.test(line);
|
|
@@ -23866,7 +24066,7 @@ const build_program = new Command();
|
|
|
23866
24066
|
build_program
|
|
23867
24067
|
.name("proof-pr")
|
|
23868
24068
|
.description("Review pull request evidence, scope, and safety before maintainers spend time on it.")
|
|
23869
|
-
.version("0.1.
|
|
24069
|
+
.version("0.1.3");
|
|
23870
24070
|
build_program
|
|
23871
24071
|
.command("scan", { isDefault: true })
|
|
23872
24072
|
.description("Scan a git diff and print a ProofPR report.")
|
|
@@ -23878,6 +24078,7 @@ build_program
|
|
|
23878
24078
|
.option("--pr-body-file <path>", "Read a pull request body from a Markdown file.")
|
|
23879
24079
|
.option("--config <path>", "Path to .proofpr.yml.", ".proofpr.yml")
|
|
23880
24080
|
.option("--format <format>", "Output format: markdown, json, or sarif.", parseFormat, "markdown")
|
|
24081
|
+
.option("--locale <locale>", "Report language: en or zh-CN.")
|
|
23881
24082
|
.option("--fail-on <level>", "Exit with code 1 on risk level: low, medium, high, or never.", parseFailLevel, "never")
|
|
23882
24083
|
.action(async (options) => {
|
|
23883
24084
|
const diffText = options.diffFile
|
|
@@ -23889,7 +24090,8 @@ build_program
|
|
|
23889
24090
|
? { title: options.prTitle, body: prBody }
|
|
23890
24091
|
: undefined;
|
|
23891
24092
|
const result = scanDiff(diffText, { config, pullRequest });
|
|
23892
|
-
const
|
|
24093
|
+
const locale = parseLocale(options.locale, config.locale);
|
|
24094
|
+
const output = renderOutput(result, options.format, locale);
|
|
23893
24095
|
process.stdout.write(`${output}\n`);
|
|
23894
24096
|
if (riskMeetsThreshold(result.risk, options.failOn)) {
|
|
23895
24097
|
process.exitCode = 1;
|
|
@@ -23943,7 +24145,9 @@ async function pathExists(path) {
|
|
|
23943
24145
|
}
|
|
23944
24146
|
}
|
|
23945
24147
|
function renderConfigTemplate() {
|
|
23946
|
-
return `
|
|
24148
|
+
return `locale: zh-CN
|
|
24149
|
+
|
|
24150
|
+
riskThreshold: high
|
|
23947
24151
|
|
|
23948
24152
|
sensitivePaths:
|
|
23949
24153
|
- ".github/workflows/**"
|
|
@@ -23994,20 +24198,20 @@ jobs:
|
|
|
23994
24198
|
runs-on: ubuntu-latest
|
|
23995
24199
|
steps:
|
|
23996
24200
|
- uses: actions/checkout@v4
|
|
23997
|
-
- uses: linsk27/proof-pr@v0.1.
|
|
24201
|
+
- uses: linsk27/proof-pr@v0.1.3
|
|
23998
24202
|
with:
|
|
23999
24203
|
fail-on: ${failOn}
|
|
24000
24204
|
comment: "true"
|
|
24001
24205
|
`;
|
|
24002
24206
|
}
|
|
24003
|
-
function renderOutput(result, format) {
|
|
24207
|
+
function renderOutput(result, format, locale) {
|
|
24004
24208
|
if (format === "json") {
|
|
24005
24209
|
return JSON.stringify(result, null, 2);
|
|
24006
24210
|
}
|
|
24007
24211
|
if (format === "sarif") {
|
|
24008
24212
|
return renderSarifReport(result);
|
|
24009
24213
|
}
|
|
24010
|
-
return renderMarkdownReport(result);
|
|
24214
|
+
return renderMarkdownReport(result, locale);
|
|
24011
24215
|
}
|
|
24012
24216
|
function parseFormat(value) {
|
|
24013
24217
|
if (value === "json" || value === "markdown" || value === "sarif") {
|