@dependfix/engine 0.3.0 → 0.4.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 +3 -0
- package/dist/auth-provider.d.mts +82 -0
- package/dist/auth.d.mts +184 -0
- package/dist/auth.mjs +3 -0
- package/dist/auth2.mjs +361 -0
- package/dist/auth2.mjs.map +1 -0
- package/dist/index.d.mts +306 -184
- package/dist/index.mjs +372 -206
- package/dist/index.mjs.map +1 -1
- package/package.json +12 -6
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as fromPat, c as computeRetryDelayMs, l as createGitHubClient } from "./auth2.mjs";
|
|
2
2
|
import { RequestError } from "@octokit/request-error";
|
|
3
|
-
import { AppError, collectCodeScanningSuggestions, createEmptyRunSummary, createLogger, filterAlerts, generateJsonReport, generateMarkdownReport, isAlertFixedByActions, isValidRepoIdentifier, limitAlerts, mapCodeScanningSeverity, normalizeAuditSeverity, prioritizeAlerts, toErrorMessage, writeReport } from "@dependfix/core";
|
|
3
|
+
import { AppError, collectCodeScanningSuggestions, createEmptyRunSummary, createLogger, filterAlerts, generateJsonReport, generateMarkdownReport, isAlertFixedByActions, isValidRepoIdentifier, limitAlerts, mapCodeScanningSeverity, normalizeAuditSeverity, normalizeUpstreamId, prioritizeAlerts, toErrorMessage, writeReport } from "@dependfix/core";
|
|
4
4
|
import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { execFileSync, execSync, spawn, spawnSync } from "node:child_process";
|
|
6
6
|
import { createHash } from "node:crypto";
|
|
@@ -12,116 +12,6 @@ import { connect } from "node:net";
|
|
|
12
12
|
import { z } from "zod";
|
|
13
13
|
import { createInterface } from "node:readline";
|
|
14
14
|
|
|
15
|
-
//#region src/github/client.ts
|
|
16
|
-
/**
|
|
17
|
-
* 创建已认证的 Octokit 实例。
|
|
18
|
-
*
|
|
19
|
-
* 调用方直接使用 `octokit.rest.*` 访问所有已类型化的 GitHub REST API。
|
|
20
|
-
* 分页使用 `octokit.paginate()` 自动合并多页结果。
|
|
21
|
-
*
|
|
22
|
-
* 配置 `retry` 时通过 `hook.wrap('request')` 统一包装所有请求
|
|
23
|
-
* (含 paginate 内部请求),对限流响应做指数退避重试。
|
|
24
|
-
*
|
|
25
|
-
* @example
|
|
26
|
-
* ```typescript
|
|
27
|
-
* const octokit = createGitHubClient({ token: 'ghp_xxxx' })
|
|
28
|
-
*
|
|
29
|
-
* // 仓库信息
|
|
30
|
-
* const { data: repo } = await octokit.rest.repos.get({ owner: 'foo', repo: 'bar' })
|
|
31
|
-
*
|
|
32
|
-
* // Dependabot 告警(自动分页)
|
|
33
|
-
* const alerts = await octokit.paginate(
|
|
34
|
-
* octokit.rest.dependabot.listAlertsForRepo,
|
|
35
|
-
* { owner: 'foo', repo: 'bar', state: 'open', per_page: 100 },
|
|
36
|
-
* )
|
|
37
|
-
* ```
|
|
38
|
-
*/
|
|
39
|
-
function createGitHubClient(options) {
|
|
40
|
-
const client = new Octokit({
|
|
41
|
-
auth: options.token,
|
|
42
|
-
baseUrl: options.baseUrl ?? "https://api.github.com"
|
|
43
|
-
});
|
|
44
|
-
const maxRetries = options.retry?.maxRetries ?? 3;
|
|
45
|
-
if (maxRetries > 0) applyRetryPolicy(client, {
|
|
46
|
-
maxRetries,
|
|
47
|
-
baseDelayMs: options.retry?.baseDelayMs ?? 1e3,
|
|
48
|
-
maxBackoffMs: options.retry?.maxBackoffMs ?? MAX_BACKOFF_MS_DEFAULT
|
|
49
|
-
});
|
|
50
|
-
return client;
|
|
51
|
-
}
|
|
52
|
-
const SECONDARY_RATE_LIMIT_RE = /secondary rate limit|abuse|retry later|retry-after/i;
|
|
53
|
-
/** 指数退避单次等待默认上限(30s),防止长时间空转(可经 retry.maxBackoffMs 覆盖) */
|
|
54
|
-
const MAX_BACKOFF_MS_DEFAULT = 3e4;
|
|
55
|
-
function applyRetryPolicy(client, retry) {
|
|
56
|
-
const maxRetries = retry.maxRetries ?? 3;
|
|
57
|
-
const baseDelayMs = retry.baseDelayMs ?? 1e3;
|
|
58
|
-
const maxBackoffMs = retry.maxBackoffMs ?? MAX_BACKOFF_MS_DEFAULT;
|
|
59
|
-
client.hook.wrap("request", async (request, options) => {
|
|
60
|
-
const method = (options.method ?? "GET").toUpperCase();
|
|
61
|
-
if (method !== "GET" && method !== "HEAD") return request(options);
|
|
62
|
-
let attempt = 0;
|
|
63
|
-
for (;;) try {
|
|
64
|
-
return await request(options);
|
|
65
|
-
} catch (error) {
|
|
66
|
-
const delayMs = computeRetryDelayMs(error, attempt, baseDelayMs, maxBackoffMs);
|
|
67
|
-
if (delayMs === null || attempt >= maxRetries) throw error;
|
|
68
|
-
attempt += 1;
|
|
69
|
-
await sleep(delayMs);
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* 计算限流重试等待时间(纯函数,可单测)。
|
|
75
|
-
*
|
|
76
|
-
* 可重试(返回等待毫秒):
|
|
77
|
-
* - 429(Too Many Requests / secondary rate limit)
|
|
78
|
-
* - 403 + `x-ratelimit-remaining: 0`(primary rate limit)
|
|
79
|
-
* - 403/429 + message 含 secondary rate limit / abuse / retry 特征
|
|
80
|
-
*
|
|
81
|
-
* 等待策略(Retry-After 优先):
|
|
82
|
-
* 1. `retry-after` 头(秒)→ 等待其秒数(受 maxBackoffMs 上限约束)
|
|
83
|
-
* 2. `x-ratelimit-reset`(unix 秒)→ 等待到 reset + 1s 缓冲
|
|
84
|
-
* 3. 否则 → 指数退避 `baseDelayMs * 2^attempt`
|
|
85
|
-
* 所有等待均受 maxBackoffMs 上限约束(默认 30s)。
|
|
86
|
-
*
|
|
87
|
-
* 不重试(返回 null):
|
|
88
|
-
* - 非 RequestError(网络错误等由上层 AppError 语义处理)
|
|
89
|
-
* - 权限类 403(无限流特征)
|
|
90
|
-
* - 其他状态码
|
|
91
|
-
*/
|
|
92
|
-
function computeRetryDelayMs(error, attempt, baseDelayMs = 1e3, maxBackoffMs = MAX_BACKOFF_MS_DEFAULT) {
|
|
93
|
-
if (!(error instanceof RequestError)) return null;
|
|
94
|
-
const status = error.status;
|
|
95
|
-
const headers = error.response?.headers ?? {};
|
|
96
|
-
const remaining = headers["x-ratelimit-remaining"];
|
|
97
|
-
const message = error.message;
|
|
98
|
-
let retryable = false;
|
|
99
|
-
if (status === 429) retryable = true;
|
|
100
|
-
else if (status === 403) {
|
|
101
|
-
if (remaining !== void 0 && remaining === "0") retryable = true;
|
|
102
|
-
else if (SECONDARY_RATE_LIMIT_RE.test(message)) retryable = true;
|
|
103
|
-
}
|
|
104
|
-
if (!retryable) return null;
|
|
105
|
-
const retryAfter = headers["retry-after"];
|
|
106
|
-
if (retryAfter !== void 0) {
|
|
107
|
-
const seconds = Number(retryAfter);
|
|
108
|
-
if (Number.isFinite(seconds) && seconds > 0) return Math.min(seconds * 1e3, maxBackoffMs);
|
|
109
|
-
}
|
|
110
|
-
const reset = headers["x-ratelimit-reset"];
|
|
111
|
-
if (reset !== void 0) {
|
|
112
|
-
const resetMs = Number(reset) * 1e3;
|
|
113
|
-
if (Number.isFinite(resetMs) && resetMs > 0) {
|
|
114
|
-
const waitMs = resetMs - Date.now() + 1e3;
|
|
115
|
-
if (waitMs > 0) return Math.min(waitMs, maxBackoffMs);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
return Math.min(baseDelayMs * 2 ** attempt, maxBackoffMs);
|
|
119
|
-
}
|
|
120
|
-
function sleep(ms) {
|
|
121
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
//#endregion
|
|
125
15
|
//#region src/github/errors.ts
|
|
126
16
|
/**
|
|
127
17
|
* 将 Octokit 请求错误映射为 `AppError`。
|
|
@@ -249,7 +139,8 @@ function normalizeFinding(finding, owner, repo) {
|
|
|
249
139
|
alertClass: "report-only",
|
|
250
140
|
startLine: finding.location?.start_line ?? void 0,
|
|
251
141
|
endLine: finding.location?.end_line ?? void 0,
|
|
252
|
-
suggestion: description || "人工审查该 Code Quality finding"
|
|
142
|
+
suggestion: description || "人工审查该 Code Quality finding",
|
|
143
|
+
upstreamId: normalizeUpstreamId("code-quality", { alertNumber: finding.number })
|
|
253
144
|
};
|
|
254
145
|
}
|
|
255
146
|
/**
|
|
@@ -553,7 +444,8 @@ function normalizeAlert$1(alert, owner, repo) {
|
|
|
553
444
|
alertClass: classifyRule(ruleId),
|
|
554
445
|
startLine: instance?.location?.start_line ?? void 0,
|
|
555
446
|
endLine: instance?.location?.end_line ?? void 0,
|
|
556
|
-
suggestion: suggestionFor(ruleId)
|
|
447
|
+
suggestion: suggestionFor(ruleId),
|
|
448
|
+
upstreamId: normalizeUpstreamId("code-scanning", { alertNumber: alert.number })
|
|
557
449
|
};
|
|
558
450
|
}
|
|
559
451
|
/**
|
|
@@ -585,8 +477,13 @@ function mapSeverity(alert) {
|
|
|
585
477
|
*
|
|
586
478
|
* @example
|
|
587
479
|
* ```typescript
|
|
588
|
-
*
|
|
480
|
+
* // 推荐:使用 auth 抽象层(M18.1 实施后)
|
|
481
|
+
* import { fromPat } from '@dependfix/engine/auth'
|
|
482
|
+
* const octokit = createGitHubClient({ auth: fromPat('ghp_xxxx') })
|
|
589
483
|
* const alerts = await fetchDependabotAlerts(octokit, { owner: 'foo', repo: 'bar' })
|
|
484
|
+
*
|
|
485
|
+
* // 向后兼容:使用 token 字段(deprecated)
|
|
486
|
+
* const octokit = createGitHubClient({ token: 'ghp_xxxx' })
|
|
590
487
|
* ```
|
|
591
488
|
*/
|
|
592
489
|
async function fetchDependabotAlerts(client, params) {
|
|
@@ -632,10 +529,22 @@ function normalizeAlert(alert, owner, repo) {
|
|
|
632
529
|
fixable,
|
|
633
530
|
fixStrategy: fixable ? "upgrade" : null,
|
|
634
531
|
recommendedVersion: firstPatched?.identifier ?? "",
|
|
635
|
-
dependencyType: normalizeDependencyRelationship(alert.dependency.relationship)
|
|
532
|
+
dependencyType: normalizeDependencyRelationship(alert.dependency.relationship),
|
|
533
|
+
upstreamId: normalizeUpstreamId("dependabot", { alertNumber: alert.number }),
|
|
534
|
+
ghsaId: alert.security_advisory.ghsa_id,
|
|
535
|
+
cveIds: extractCveIds(alert.security_advisory.identifiers)
|
|
636
536
|
};
|
|
637
537
|
}
|
|
638
538
|
/**
|
|
539
|
+
** 从 dependabot alert.security_advisory.identifiers[] 提取 CVE 列表(type === 'CVE')。
|
|
540
|
+
** 兼容 identifiers 缺省为 undefined / 空数组场景。
|
|
541
|
+
*/
|
|
542
|
+
function extractCveIds(identifiers) {
|
|
543
|
+
if (!Array.isArray(identifiers) || identifiers.length === 0) return;
|
|
544
|
+
const cves = identifiers.filter((id) => id?.type === "CVE" && typeof id.value === "string" && Boolean(id.value)).map((id) => id.value);
|
|
545
|
+
return cves.length > 0 ? cves : void 0;
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
639
548
|
* 将 Dependabot API 的 `dependency.relationship` 映射为 `DependencyType`。
|
|
640
549
|
*
|
|
641
550
|
* GitHub API 返回的合法值:
|
|
@@ -650,8 +559,19 @@ function normalizeDependencyRelationship(relationship) {
|
|
|
650
559
|
|
|
651
560
|
//#endregion
|
|
652
561
|
//#region src/github/pr-creator.ts
|
|
653
|
-
|
|
654
|
-
|
|
562
|
+
/**
|
|
563
|
+
* PAT 路径默认 commit author(保持现有 PAT 路径用户行为零变化)。
|
|
564
|
+
*
|
|
565
|
+
* 注:此常量仅作为 PAT 路径的默认值;GitHub App 路径(M18.2 之后接入)走动态 commit author。
|
|
566
|
+
* 现有 PAT 路径仍硬编码 `dependfix[bot]@users.noreply.github.com`,虽非真实 bot 身份(字符串约定),
|
|
567
|
+
* 但保持行为不变以确保 PAT 用户无感升级。已知缺陷由 C22 范围之外的后续阶段修复。
|
|
568
|
+
*
|
|
569
|
+
* @see [C22 PAT 无感升级评估 §5.1 兼容性](../../../../docs/design/governance/c22-pat-backward-compat.md)
|
|
570
|
+
*/
|
|
571
|
+
const PAT_DEFAULT_COMMIT_AUTHOR = {
|
|
572
|
+
name: "dependfix[bot]",
|
|
573
|
+
email: "dependfix[bot]@users.noreply.github.com"
|
|
574
|
+
};
|
|
655
575
|
/** 自动修复分支统一前缀(分支名 = 前缀 + 内容指纹 8 位) */
|
|
656
576
|
const BRANCH_PREFIX = "dependfix/auto-fix-";
|
|
657
577
|
/**
|
|
@@ -747,14 +667,28 @@ function createFixBranch(branchName, workDir) {
|
|
|
747
667
|
* 自动设置 git user.name / user.email(如未设置)。
|
|
748
668
|
* 使用 `execFileSync` 参数数组形式(不经 shell),保证多行 commit message
|
|
749
669
|
* 与 UTF-8 字符(如 →)在 Windows/Linux 双平台传递一致。
|
|
670
|
+
*
|
|
671
|
+
* W3 修复(M18.4 audit round 2):显式传 `-c user.name=X -c user.email=Y` 强制 commit author
|
|
672
|
+
* 使用传入值,不受 host 全局 `user.name`(如 CaoMeiYouRen)污染——`git commit` 走 lookup order
|
|
673
|
+
* (env → `-c` config → local → global → system),显式 `-c` 在 lookup order 中最高优先。
|
|
674
|
+
*
|
|
675
|
+
* @param author - 可选 commit author 信息;不传时使用 PAT 默认值(保持现有 PAT 路径行为零变化)。
|
|
676
|
+
* M18.2 之后 GitHub App 路径会传入动态生成的 `{app_id}+{bot_login}[bot]` author。
|
|
677
|
+
*
|
|
678
|
+
* @see [C22 PAT 无感升级评估 §5.1 兼容性](../../../../docs/design/governance/c22-pat-backward-compat.md)
|
|
750
679
|
*/
|
|
751
|
-
function stageAndCommit(message, workDir) {
|
|
752
|
-
ensureGitConfig(workDir);
|
|
753
|
-
|
|
680
|
+
function stageAndCommit(message, workDir, author) {
|
|
681
|
+
ensureGitConfig(workDir, author);
|
|
682
|
+
const effectiveAuthor = author ?? PAT_DEFAULT_COMMIT_AUTHOR;
|
|
683
|
+
execFileSync("git", ["add", "."], {
|
|
754
684
|
cwd: workDir,
|
|
755
685
|
stdio: "pipe"
|
|
756
686
|
});
|
|
757
687
|
execFileSync("git", [
|
|
688
|
+
"-c",
|
|
689
|
+
`user.name=${effectiveAuthor.name}`,
|
|
690
|
+
"-c",
|
|
691
|
+
`user.email=${effectiveAuthor.email}`,
|
|
758
692
|
"commit",
|
|
759
693
|
"-m",
|
|
760
694
|
message
|
|
@@ -763,9 +697,6 @@ function stageAndCommit(message, workDir) {
|
|
|
763
697
|
stdio: "pipe"
|
|
764
698
|
});
|
|
765
699
|
}
|
|
766
|
-
/**
|
|
767
|
-
* 推送分支到远程 origin。
|
|
768
|
-
*/
|
|
769
700
|
function pushBranch(branchName, workDir) {
|
|
770
701
|
execFileSync("git", [
|
|
771
702
|
"push",
|
|
@@ -808,6 +739,38 @@ async function closePullRequest(octokit, owner, repo, pullNumber) {
|
|
|
808
739
|
});
|
|
809
740
|
}
|
|
810
741
|
/**
|
|
742
|
+
* 在 Pull Request 上添加评论。
|
|
743
|
+
*
|
|
744
|
+
* 使用 `issues.createComment` 端点(GitHub PR 评论复用 issue comment API),
|
|
745
|
+
* 需要 token 具备 `issues: write` 权限(比 `pull-requests: write` 宽)。
|
|
746
|
+
*
|
|
747
|
+
* 用于在新 PR 上评论指向被取代的旧 PR,避免用户手动排查重复 PR。
|
|
748
|
+
*/
|
|
749
|
+
async function commentOnPullRequest(octokit, owner, repo, pullNumber, body) {
|
|
750
|
+
await octokit.rest.issues.createComment({
|
|
751
|
+
owner,
|
|
752
|
+
repo,
|
|
753
|
+
issue_number: pullNumber,
|
|
754
|
+
body
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* 在 Pull Request 上添加 label。
|
|
759
|
+
*
|
|
760
|
+
* 使用 `issues.addLabels` 端点(GitHub PR label 复用 issue label API),
|
|
761
|
+
* 需要 token 具备 `issues: write` 权限。
|
|
762
|
+
*
|
|
763
|
+
* 用于标记重复 PR(如 `duplicate` label),便于用户过滤和管理。
|
|
764
|
+
*/
|
|
765
|
+
async function addLabelToPullRequest(octokit, owner, repo, pullNumber, labels) {
|
|
766
|
+
await octokit.rest.issues.addLabels({
|
|
767
|
+
owner,
|
|
768
|
+
repo,
|
|
769
|
+
issue_number: pullNumber,
|
|
770
|
+
labels
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
811
774
|
* 通过 GitHub API 创建 Pull Request。
|
|
812
775
|
*/
|
|
813
776
|
async function createPullRequest(params) {
|
|
@@ -1161,21 +1124,35 @@ function branchExists(branchName, workDir) {
|
|
|
1161
1124
|
return false;
|
|
1162
1125
|
}
|
|
1163
1126
|
}
|
|
1164
|
-
function ensureGitConfig(workDir) {
|
|
1127
|
+
function ensureGitConfig(workDir, author) {
|
|
1128
|
+
const effectiveAuthor = author ?? PAT_DEFAULT_COMMIT_AUTHOR;
|
|
1165
1129
|
const hasName = gitConfigExists("user.name", workDir);
|
|
1166
1130
|
const hasEmail = gitConfigExists("user.email", workDir);
|
|
1167
|
-
if (!hasName)
|
|
1131
|
+
if (!hasName) execFileSync("git", [
|
|
1132
|
+
"config",
|
|
1133
|
+
"user.name",
|
|
1134
|
+
effectiveAuthor.name
|
|
1135
|
+
], {
|
|
1168
1136
|
cwd: workDir,
|
|
1169
1137
|
stdio: "pipe"
|
|
1170
1138
|
});
|
|
1171
|
-
if (!hasEmail)
|
|
1139
|
+
if (!hasEmail) execFileSync("git", [
|
|
1140
|
+
"config",
|
|
1141
|
+
"user.email",
|
|
1142
|
+
effectiveAuthor.email
|
|
1143
|
+
], {
|
|
1172
1144
|
cwd: workDir,
|
|
1173
1145
|
stdio: "pipe"
|
|
1174
1146
|
});
|
|
1175
1147
|
}
|
|
1176
1148
|
function gitConfigExists(key, workDir) {
|
|
1177
1149
|
try {
|
|
1178
|
-
|
|
1150
|
+
execFileSync("git", [
|
|
1151
|
+
"config",
|
|
1152
|
+
"--local",
|
|
1153
|
+
"--get",
|
|
1154
|
+
key
|
|
1155
|
+
], {
|
|
1179
1156
|
cwd: workDir,
|
|
1180
1157
|
stdio: "pipe"
|
|
1181
1158
|
});
|
|
@@ -1280,6 +1257,7 @@ const DEPENDABOT_CONFIG_PATH = ".github/dependabot.yml";
|
|
|
1280
1257
|
* 2. topic 过滤(`--repo-topics`,AND 语义)
|
|
1281
1258
|
* 3. 名单策略过滤(include / exclude / topicsExclude,探测前应用)
|
|
1282
1259
|
* 4. dependabot.yml 探测(仅候选仓库触达 contents API;404 视为不支持,不剔除)
|
|
1260
|
+
* 5. 规模上限截断(`maxRepos`,按 fullName 字典序排序后截断)
|
|
1283
1261
|
*
|
|
1284
1262
|
* 结果按 `fullName` 字典序排序,保证同输入多次运行结果一致
|
|
1285
1263
|
* (runId / 指纹稳定性前提)。
|
|
@@ -1287,7 +1265,7 @@ const DEPENDABOT_CONFIG_PATH = ".github/dependabot.yml";
|
|
|
1287
1265
|
* @throws AppError(mapGitHubError 转换;token 权限、网络等全局性问题 fail-fast)
|
|
1288
1266
|
*/
|
|
1289
1267
|
async function discoverRepositories(options) {
|
|
1290
|
-
const { client, owners, topics = [], policy, probeDependabot = true, probeConcurrency = 5 } = options;
|
|
1268
|
+
const { client, owners, topics = [], policy, probeDependabot = true, probeConcurrency = 5, maxRepos } = options;
|
|
1291
1269
|
const discovered = [];
|
|
1292
1270
|
for (const owner of owners) {
|
|
1293
1271
|
const repos = await fetchOwnerRepositories(client, owner);
|
|
@@ -1318,6 +1296,8 @@ async function discoverRepositories(options) {
|
|
|
1318
1296
|
}
|
|
1319
1297
|
if (probeDependabot) await probeDependabotConfigs(client, discovered, probeConcurrency);
|
|
1320
1298
|
discovered.sort((a, b) => a.fullName.localeCompare(b.fullName));
|
|
1299
|
+
const effectiveMaxRepos = maxRepos ?? 100;
|
|
1300
|
+
if (effectiveMaxRepos > 0 && discovered.length > effectiveMaxRepos) return discovered.slice(0, effectiveMaxRepos);
|
|
1321
1301
|
return discovered;
|
|
1322
1302
|
}
|
|
1323
1303
|
/**
|
|
@@ -2225,7 +2205,7 @@ function isCrossMajorFixRequired(lockfilePath, alert) {
|
|
|
2225
2205
|
*/
|
|
2226
2206
|
function classifyLockfileFailure(workDir) {
|
|
2227
2207
|
try {
|
|
2228
|
-
|
|
2208
|
+
execFileSync("pnpm", ["install", "--frozen-lockfile"], {
|
|
2229
2209
|
cwd: workDir,
|
|
2230
2210
|
stdio: "pipe",
|
|
2231
2211
|
timeout: 12e4
|
|
@@ -2267,8 +2247,8 @@ function getStderrText(err) {
|
|
|
2267
2247
|
if (typeof err.stderr === "string") return err.stderr;
|
|
2268
2248
|
return err.stderr.toString("utf-8");
|
|
2269
2249
|
}
|
|
2270
|
-
function execRepair(
|
|
2271
|
-
|
|
2250
|
+
function execRepair(binary, args, workDir, timeoutMs = 18e4) {
|
|
2251
|
+
execFileSync(binary, args, {
|
|
2272
2252
|
cwd: workDir,
|
|
2273
2253
|
stdio: "pipe",
|
|
2274
2254
|
timeout: timeoutMs
|
|
@@ -2308,14 +2288,43 @@ function getStrategyChain(category) {
|
|
|
2308
2288
|
* (corepack 不可用/下载失败时命令本身失败 → 策略链 REGENERATE/REINSTALL 兜底,
|
|
2309
2289
|
* 行为不劣于现状)
|
|
2310
2290
|
* - `pnpmVersion` 缺省:回退裸 `pnpm install --lockfile-only`(保持旧 stub 语义)
|
|
2291
|
+
*
|
|
2292
|
+
* 返回 `{ binary, args }` 而非完整 shell 命令字符串,便于 `execFileSync` 参数化调用
|
|
2293
|
+
* 避免 shell 解释(`execSync` 模板拼接是 Code Scanning 命令注入告警源头)。所有
|
|
2294
|
+
* 参数均受 `isValidPnpmVersion` 白名单约束,无 shell metacharacter。
|
|
2311
2295
|
*/
|
|
2312
2296
|
function getStrategyCommand(strategy, pnpmVersion) {
|
|
2313
2297
|
switch (strategy) {
|
|
2314
|
-
case "REGENERATE": return
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2298
|
+
case "REGENERATE": return {
|
|
2299
|
+
binary: "pnpm",
|
|
2300
|
+
args: ["install", "--lockfile-only"]
|
|
2301
|
+
};
|
|
2302
|
+
case "FIX_ENTRIES": return {
|
|
2303
|
+
binary: "pnpm",
|
|
2304
|
+
args: [
|
|
2305
|
+
"install",
|
|
2306
|
+
"--fix-lockfile",
|
|
2307
|
+
"--lockfile-only"
|
|
2308
|
+
]
|
|
2309
|
+
};
|
|
2310
|
+
case "REINSTALL": return {
|
|
2311
|
+
binary: "pnpm",
|
|
2312
|
+
args: ["install", "--no-frozen-lockfile"]
|
|
2313
|
+
};
|
|
2314
|
+
case "PIN_TOOLCHAIN":
|
|
2315
|
+
if (!pnpmVersion) return {
|
|
2316
|
+
binary: "pnpm",
|
|
2317
|
+
args: ["install", "--lockfile-only"]
|
|
2318
|
+
};
|
|
2319
|
+
return {
|
|
2320
|
+
binary: "corepack",
|
|
2321
|
+
args: [
|
|
2322
|
+
`pnpm@${pnpmVersion}`,
|
|
2323
|
+
"install",
|
|
2324
|
+
"--lockfile-only"
|
|
2325
|
+
]
|
|
2326
|
+
};
|
|
2327
|
+
default: return null;
|
|
2319
2328
|
}
|
|
2320
2329
|
}
|
|
2321
2330
|
/** 从 lockfile 内容提取 `lockfileVersion` 字段值(如 `9.0`);缺失返回 null。 */
|
|
@@ -2366,7 +2375,7 @@ function countLockfilePackages(path) {
|
|
|
2366
2375
|
}
|
|
2367
2376
|
/**
|
|
2368
2377
|
* pnpm 版本白名单格式(安全加固:拒绝不可信 packageManager/config 的任意字符串,
|
|
2369
|
-
* 防命令注入——corepack
|
|
2378
|
+
* 防命令注入——corepack 命令参数 `pnpm@<version>` 受 PNPM_VERSION_RE 白名单约束)。
|
|
2370
2379
|
* 兼容标准 semver 与 corepack 哈希后缀(pnpm@10.5.2+sha512.xxx)。
|
|
2371
2380
|
*/
|
|
2372
2381
|
const PNPM_VERSION_RE = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/;
|
|
@@ -2400,7 +2409,7 @@ const LOCKFILE_NAME = "pnpm-lock.yaml";
|
|
|
2400
2409
|
*/
|
|
2401
2410
|
function verifyFrozenLockfile(workDir) {
|
|
2402
2411
|
try {
|
|
2403
|
-
|
|
2412
|
+
execFileSync("pnpm", ["install", "--frozen-lockfile"], {
|
|
2404
2413
|
cwd: workDir,
|
|
2405
2414
|
stdio: "pipe",
|
|
2406
2415
|
timeout: 12e4
|
|
@@ -2486,12 +2495,14 @@ function repairLockfile(params) {
|
|
|
2486
2495
|
let lastError = stderr;
|
|
2487
2496
|
const startTimestamp = Date.now();
|
|
2488
2497
|
for (const strategy of strategyChain) {
|
|
2489
|
-
const
|
|
2498
|
+
const cmd = getStrategyCommand(strategy, pnpmVersion);
|
|
2499
|
+
if (!cmd) continue;
|
|
2500
|
+
const command = [cmd.binary, ...cmd.args].join(" ");
|
|
2490
2501
|
const t0 = Date.now();
|
|
2491
2502
|
let strategySuccess = false;
|
|
2492
2503
|
let strategyError;
|
|
2493
2504
|
try {
|
|
2494
|
-
execRepair(
|
|
2505
|
+
execRepair(cmd.binary, cmd.args, workDir_);
|
|
2495
2506
|
strategySuccess = true;
|
|
2496
2507
|
} catch (err) {
|
|
2497
2508
|
strategyError = getStderrText(err) || err.message;
|
|
@@ -2790,6 +2801,7 @@ const DEFAULT_RUNTIME_CONFIG = {
|
|
|
2790
2801
|
maxConcurrency: 1,
|
|
2791
2802
|
maxRetries: 3,
|
|
2792
2803
|
maxBackoffMs: 3e4,
|
|
2804
|
+
maxRepos: 100,
|
|
2793
2805
|
ai: {
|
|
2794
2806
|
enabled: false,
|
|
2795
2807
|
provider: "openai-compatible",
|
|
@@ -2915,6 +2927,7 @@ function readEnvConfig(env = process.env) {
|
|
|
2915
2927
|
maxConcurrency: normalizeInteger(readEnv(env, "MAX_CONCURRENCY"), `${ENV_PREFIX}MAX_CONCURRENCY`),
|
|
2916
2928
|
maxRetries: normalizeNonNegativeInteger(readEnv(env, "MAX_RETRIES"), `${ENV_PREFIX}MAX_RETRIES`),
|
|
2917
2929
|
maxBackoffMs: normalizeInteger(readEnv(env, "MAX_BACKOFF_MS"), `${ENV_PREFIX}MAX_BACKOFF_MS`),
|
|
2930
|
+
maxRepos: normalizeNonNegativeInteger(readEnv(env, "MAX_REPOS"), `${ENV_PREFIX}MAX_REPOS`),
|
|
2918
2931
|
upgradeGroups: normalizeUpgradeGroups(readEnv(env, "UPGRADE_GROUPS")),
|
|
2919
2932
|
toolchainPnpmVersion: readEnv(env, "TOOLCHAIN_PNPM_VERSION")?.trim() || void 0,
|
|
2920
2933
|
aiEnabled: normalizeBoolean(readEnv(env, "AI"), `${ENV_PREFIX}AI`),
|
|
@@ -2969,7 +2982,9 @@ function validateRuntimeConfig(config) {
|
|
|
2969
2982
|
if (isAuditSource && config.owner && config.owner.length > 0) throw new AppError("CONFIG_VALIDATION_ERROR", "--owner / DEPENDFIX_OWNER requires the github-dependabot alert source (owner discovery uses the GitHub API).");
|
|
2970
2983
|
if (config.mode === "cleanup-branches" && config.owner && config.owner.length > 0) throw new AppError("CONFIG_VALIDATION_ERROR", "--owner / DEPENDFIX_OWNER is not supported in cleanup-branches mode (branch cleanup requires explicit target repositories).");
|
|
2971
2984
|
if (config.mode === "cleanup-branches" && config.maxConcurrency > 1) throw new AppError("CONFIG_VALIDATION_ERROR", "maxConcurrency > 1 is not supported in cleanup-branches mode (branch cleanup runs sequentially).");
|
|
2972
|
-
if (config.repositories.length === 0 && !(config.owner && config.owner.length > 0))
|
|
2985
|
+
if (config.repositories.length === 0 && !(config.owner && config.owner.length > 0)) {
|
|
2986
|
+
if (isAuditSource) {} else throw new AppError("CONFIG_VALIDATION_ERROR", "Missing target repositories. Provide --repo, --repository, --repos-file, --owner or DEPENDFIX_REPOSITORIES / DEPENDFIX_OWNER.");
|
|
2987
|
+
}
|
|
2973
2988
|
for (const repo of config.repositories) if (!isValidRepoIdentifier(repo)) throw new AppError("CONFIG_VALIDATION_ERROR", `Invalid repository identifier: "${repo}". Expected format: owner/repo`);
|
|
2974
2989
|
if (isAuditSource && config.repositories.length > 1) throw new AppError("CONFIG_VALIDATION_ERROR", "pnpm-audit alert source supports at most one repository (it scans the current workspace lockfile).");
|
|
2975
2990
|
if (isAuditSource && config.codeScanningEnabled) throw new AppError("CONFIG_VALIDATION_ERROR", "code-scanning requires the github-dependabot alert source (Code Scanning alerts are fetched from the GitHub API).");
|
|
@@ -3022,6 +3037,7 @@ function resolveRuntimeConfig(options = {}) {
|
|
|
3022
3037
|
maxConcurrency: cliOverrides.maxConcurrency ?? envConfig.maxConcurrency ?? DEFAULT_RUNTIME_CONFIG.maxConcurrency,
|
|
3023
3038
|
maxRetries: cliOverrides.maxRetries ?? envConfig.maxRetries ?? DEFAULT_RUNTIME_CONFIG.maxRetries,
|
|
3024
3039
|
maxBackoffMs: cliOverrides.maxBackoffMs ?? envConfig.maxBackoffMs ?? DEFAULT_RUNTIME_CONFIG.maxBackoffMs,
|
|
3040
|
+
maxRepos: cliOverrides.maxRepos ?? envConfig.maxRepos ?? DEFAULT_RUNTIME_CONFIG.maxRepos,
|
|
3025
3041
|
upgradeGroups: cliOverrides.upgradeGroups ?? envConfig.upgradeGroups,
|
|
3026
3042
|
toolchainPnpmVersion: cliOverrides.toolchainPnpmVersion ?? envConfig.toolchainPnpmVersion,
|
|
3027
3043
|
ai: resolveAiOptions(cliOverrides, envConfig)
|
|
@@ -3810,15 +3826,19 @@ function restoreTrackedFiles(workDir, snapshot) {
|
|
|
3810
3826
|
}
|
|
3811
3827
|
}
|
|
3812
3828
|
/**
|
|
3813
|
-
*
|
|
3829
|
+
* 快速验证(逐包):运行验证命令序列。
|
|
3814
3830
|
*
|
|
3815
|
-
*
|
|
3816
|
-
*
|
|
3817
|
-
*
|
|
3831
|
+
* 有自定义命令时执行完整序列(含 install + setup + lint + build),
|
|
3832
|
+
* 否则仅运行 `pnpm lint`(脚本存在时)。
|
|
3833
|
+
*
|
|
3834
|
+
* 设计决策(run 33946113272 复盘):旧版仅跑 `pnpm lint`,但 Nuxt 等框架
|
|
3835
|
+
* 项目 lint 依赖 `nuxt prepare` 生成的 `.nuxt/tsconfig.json`——无 prepare
|
|
3836
|
+
* 步骤会导致 ESLint OOM。自定义命令已含 prepare,走完整序列可避免此问题;
|
|
3837
|
+
* `pnpm install` 在 node_modules 已就绪时近乎空操作,性能影响可忽略。
|
|
3818
3838
|
*/
|
|
3819
3839
|
async function quickVerifyProject(ctx, repo) {
|
|
3820
|
-
const { logger, workDir } = ctx;
|
|
3821
|
-
const { valid, skipped } = validateVerifyCommands(["pnpm lint"], workDir);
|
|
3840
|
+
const { logger, workDir, customCommands } = ctx;
|
|
3841
|
+
const { valid, skipped } = validateVerifyCommands(customCommands ?? ["pnpm lint"], workDir);
|
|
3822
3842
|
if (valid.length === 0) {
|
|
3823
3843
|
if (skipped.length > 0) logger.info(`[quick-verify] ${repo}: no lint script, skipping per-package verification`);
|
|
3824
3844
|
return true;
|
|
@@ -4359,6 +4379,12 @@ function parseLegacyAuditReport(report) {
|
|
|
4359
4379
|
const advisories = report.advisories;
|
|
4360
4380
|
if (!advisories || typeof advisories !== "object") return [];
|
|
4361
4381
|
const actionMap = /* @__PURE__ */ new Map();
|
|
4382
|
+
const advisoryExtrasMap = /* @__PURE__ */ new Map();
|
|
4383
|
+
for (const [id, rawAdvisory] of Object.entries(advisories)) {
|
|
4384
|
+
if (!rawAdvisory || typeof rawAdvisory !== "object") continue;
|
|
4385
|
+
const extras = extractIdentifiers(rawAdvisory);
|
|
4386
|
+
if (extras.ghsaId || extras.cveIds && extras.cveIds.length > 0) advisoryExtrasMap.set(id, extras);
|
|
4387
|
+
}
|
|
4362
4388
|
for (const action of toArray(report.actions)) {
|
|
4363
4389
|
if (!action || typeof action !== "object") continue;
|
|
4364
4390
|
const record = action;
|
|
@@ -4370,13 +4396,15 @@ function parseLegacyAuditReport(report) {
|
|
|
4370
4396
|
if (!rawAdvisory || typeof rawAdvisory !== "object") continue;
|
|
4371
4397
|
const advisory = rawAdvisory;
|
|
4372
4398
|
const patched = normalizePatchedVersionValue(actionMap.get(id) ?? (typeof advisory.patched_versions === "string" ? advisory.patched_versions : void 0));
|
|
4399
|
+
const extras = advisoryExtrasMap.get(id);
|
|
4373
4400
|
risks.push({
|
|
4374
4401
|
advisoryId: resolveAdvisoryId(advisory),
|
|
4375
4402
|
packageName: typeof advisory.module_name === "string" ? advisory.module_name : "unknown-package",
|
|
4376
4403
|
severity: typeof advisory.severity === "string" ? advisory.severity : "medium",
|
|
4377
4404
|
title: typeof advisory.title === "string" ? advisory.title : "Untitled advisory",
|
|
4378
4405
|
htmlUrl: typeof advisory.url === "string" ? advisory.url : "",
|
|
4379
|
-
patchedVersion: patched
|
|
4406
|
+
patchedVersion: patched,
|
|
4407
|
+
...extras ?? {}
|
|
4380
4408
|
});
|
|
4381
4409
|
}
|
|
4382
4410
|
return risks;
|
|
@@ -4401,26 +4429,30 @@ function parseModernAuditReport(report) {
|
|
|
4401
4429
|
})();
|
|
4402
4430
|
const viaItems = toArray(vulnerability.via).filter((item) => item !== null && typeof item === "object");
|
|
4403
4431
|
if (viaItems.length === 0) {
|
|
4432
|
+
const extras = extractIdentifiers(vulnerability);
|
|
4404
4433
|
risks.push({
|
|
4405
4434
|
advisoryId: resolveAdvisoryId(vulnerability),
|
|
4406
4435
|
packageName,
|
|
4407
4436
|
severity: typeof vulnerability.severity === "string" ? vulnerability.severity : "medium",
|
|
4408
4437
|
title: typeof vulnerability.title === "string" ? vulnerability.title : `${packageName} vulnerability`,
|
|
4409
4438
|
htmlUrl: typeof vulnerability.url === "string" ? vulnerability.url : "",
|
|
4410
|
-
patchedVersion: patchedFromFixAvailable
|
|
4439
|
+
patchedVersion: patchedFromFixAvailable,
|
|
4440
|
+
...extras ?? {}
|
|
4411
4441
|
});
|
|
4412
4442
|
continue;
|
|
4413
4443
|
}
|
|
4414
4444
|
for (const advisory of viaItems) {
|
|
4415
4445
|
let severity = typeof vulnerability.severity === "string" ? vulnerability.severity : "medium";
|
|
4416
4446
|
if (typeof advisory.severity === "string" && advisory.severity) severity = advisory.severity;
|
|
4447
|
+
const extras = extractIdentifiers(advisory);
|
|
4417
4448
|
risks.push({
|
|
4418
4449
|
advisoryId: resolveAdvisoryId(advisory),
|
|
4419
4450
|
packageName: typeof advisory.name === "string" && advisory.name ? advisory.name : packageName,
|
|
4420
4451
|
severity,
|
|
4421
4452
|
title: typeof advisory.title === "string" ? advisory.title : `${packageName} vulnerability`,
|
|
4422
4453
|
htmlUrl: typeof advisory.url === "string" ? advisory.url : "",
|
|
4423
|
-
patchedVersion: patchedFromFixAvailable
|
|
4454
|
+
patchedVersion: patchedFromFixAvailable,
|
|
4455
|
+
...extras ?? {}
|
|
4424
4456
|
});
|
|
4425
4457
|
}
|
|
4426
4458
|
}
|
|
@@ -4454,13 +4486,34 @@ function parseAuditReport(report) {
|
|
|
4454
4486
|
return [...deduped.values()];
|
|
4455
4487
|
}
|
|
4456
4488
|
/**
|
|
4457
|
-
* advisoryId
|
|
4489
|
+
* advisoryId 的稳定数字哈希(M20 之前使用,M20 后保留以维持向后兼容 `NormalizedSecurityAlert.id: number`)。
|
|
4490
|
+
*
|
|
4458
4491
|
* 取 sha256 前 4 字节完整 uint32(非设计稿的 `% 2^31`:uint32 语义更自然,
|
|
4459
4492
|
* 碰撞概率 ~2^-32/对,12 条告警场景生日界 ~1.6e-8,可忽略)。
|
|
4493
|
+
*
|
|
4494
|
+
* M20 弃用:唯一去重键改为 `upstreamId`,详见 [`normalizeUpstreamId`](../../../core/src/alerts/upstream-id.ts)。
|
|
4495
|
+
* 本函数保留仅为兼容现有 `NormalizedSecurityAlert.id: number` 字段,**不是**平台去重键。
|
|
4460
4496
|
*/
|
|
4461
4497
|
function hashAdvisoryId(packageName, advisoryId) {
|
|
4462
4498
|
return createHash("sha256").update(`${packageName}:${advisoryId}`).digest().readUInt32BE(0);
|
|
4463
4499
|
}
|
|
4500
|
+
/**
|
|
4501
|
+
** M23.3 C66-A2:从 pnpm audit advisory 提取 GHSA ID + CVE 列表。
|
|
4502
|
+
** 兼容 legacy `github_advisory_id` + modern `advisory.github_advisory_id` 与 `cves[]`。
|
|
4503
|
+
*/
|
|
4504
|
+
function extractIdentifiers(candidate) {
|
|
4505
|
+
const ghsaId = typeof candidate.github_advisory_id === "string" && candidate.github_advisory_id ? candidate.github_advisory_id : void 0;
|
|
4506
|
+
const cves = candidate.cves;
|
|
4507
|
+
let cveIds;
|
|
4508
|
+
if (Array.isArray(cves)) {
|
|
4509
|
+
const valid = cves.filter((c) => typeof c === "string" && Boolean(c));
|
|
4510
|
+
if (valid.length > 0) cveIds = valid;
|
|
4511
|
+
}
|
|
4512
|
+
const result = {};
|
|
4513
|
+
if (ghsaId) result.ghsaId = ghsaId;
|
|
4514
|
+
if (cveIds) result.cveIds = cveIds;
|
|
4515
|
+
return result;
|
|
4516
|
+
}
|
|
4464
4517
|
/** audit 风险 → 标准化告警(source='pnpm-audit',repository 由调用方注入) */
|
|
4465
4518
|
function mapAuditRiskToAlert(risk, repository) {
|
|
4466
4519
|
const fixable = risk.patchedVersion !== null;
|
|
@@ -4479,7 +4532,13 @@ function mapAuditRiskToAlert(risk, repository) {
|
|
|
4479
4532
|
fixable,
|
|
4480
4533
|
fixStrategy: fixable ? "upgrade" : null,
|
|
4481
4534
|
recommendedVersion: risk.patchedVersion ?? "",
|
|
4482
|
-
dependencyType: void 0
|
|
4535
|
+
dependencyType: void 0,
|
|
4536
|
+
upstreamId: normalizeUpstreamId("pnpm-audit", {
|
|
4537
|
+
packageName: risk.packageName,
|
|
4538
|
+
advisoryId: risk.advisoryId
|
|
4539
|
+
}),
|
|
4540
|
+
ghsaId: risk.ghsaId,
|
|
4541
|
+
cveIds: risk.cveIds
|
|
4483
4542
|
};
|
|
4484
4543
|
}
|
|
4485
4544
|
|
|
@@ -5407,11 +5466,15 @@ function computeSummary(ctx) {
|
|
|
5407
5466
|
for (const action of allActions) {
|
|
5408
5467
|
if (action.noOp) continue;
|
|
5409
5468
|
if (action.strategy === "ai-patch" || action.strategy === "ai-suggestion") continue;
|
|
5410
|
-
if (action.type === "dependency-upgrade" || action.type === "code-scanning-fix")
|
|
5411
|
-
|
|
5469
|
+
if (action.type === "dependency-upgrade" || action.type === "code-scanning-fix") {
|
|
5470
|
+
if (action.success) fixed++;
|
|
5471
|
+
else failed++;
|
|
5472
|
+
}
|
|
5412
5473
|
if (action.type === "lockfile-repair" && action.success) lockfileRepairs++;
|
|
5413
|
-
if (action.type === "verification")
|
|
5414
|
-
|
|
5474
|
+
if (action.type === "verification") {
|
|
5475
|
+
if (action.success) verificationsPassed++;
|
|
5476
|
+
else verificationsFailed++;
|
|
5477
|
+
}
|
|
5415
5478
|
}
|
|
5416
5479
|
const fixable = allAlerts.filter((a) => a.fixable).length;
|
|
5417
5480
|
summary.repositoriesScanned = repoResults.length;
|
|
@@ -5514,7 +5577,7 @@ function resolveAlertRepositories(ctx) {
|
|
|
5514
5577
|
return ["local"];
|
|
5515
5578
|
}
|
|
5516
5579
|
/** 自动修复提交的兜底标题(无成功升级 / 包名超长无法生成动态标题时) */
|
|
5517
|
-
const FIX_COMMIT_MESSAGE = "
|
|
5580
|
+
const FIX_COMMIT_MESSAGE = "chore(deps): automated dependfix security repair";
|
|
5518
5581
|
/** commitlint header-max-length 上限(commitlint-config-cmyr 覆盖为 140) */
|
|
5519
5582
|
const COMMIT_HEADER_MAX_LENGTH = 140;
|
|
5520
5583
|
/**
|
|
@@ -5540,15 +5603,15 @@ function buildCommitTitle(upgrades) {
|
|
|
5540
5603
|
const a = upgrades[0];
|
|
5541
5604
|
const from = a.fromVersion && a.fromVersion !== "unknown" ? ` from ${a.fromVersion}` : "";
|
|
5542
5605
|
const to = a.toVersion ? ` to ${a.toVersion}` : "";
|
|
5543
|
-
const title = `
|
|
5606
|
+
const title = `chore(deps): bump ${a.target}${from}${to}`;
|
|
5544
5607
|
return title.length <= COMMIT_HEADER_MAX_LENGTH ? title : FIX_COMMIT_MESSAGE;
|
|
5545
5608
|
}
|
|
5546
5609
|
const names = upgrades.map((a) => a.target);
|
|
5547
|
-
const full = `
|
|
5610
|
+
const full = `chore(deps): bump ${names.join(", ")}`;
|
|
5548
5611
|
if (full.length <= COMMIT_HEADER_MAX_LENGTH) return full;
|
|
5549
5612
|
let count = names.length - 1;
|
|
5550
5613
|
while (count > 0) {
|
|
5551
|
-
const candidate = `
|
|
5614
|
+
const candidate = `chore(deps): bump ${names.slice(0, count).join(", ")} and ${names.length - count} more`;
|
|
5552
5615
|
if (candidate.length <= COMMIT_HEADER_MAX_LENGTH) return candidate;
|
|
5553
5616
|
count--;
|
|
5554
5617
|
}
|
|
@@ -5711,17 +5774,47 @@ async function upgradeAlert(ctx, alert) {
|
|
|
5711
5774
|
}
|
|
5712
5775
|
/**
|
|
5713
5776
|
* 执行 Code Scanning 模板修复(2.0 节;从 app/index.ts 提取以控制文件行数)。
|
|
5714
|
-
* 仅处理 A
|
|
5777
|
+
* 仅处理 A 类告警;按批处理(默认每 10 个告警跑一次 lint):快照 → 应用模板 → 累积到批 → 批满跑 lint → 失败回滚整批(不静默)。
|
|
5715
5778
|
* - 快照失败 / 无模板 / 模板不适用 / 缺文件 → noOp 动作(回退建议模式,展示;
|
|
5716
5779
|
* error 原因可审计,不计 failed 避免陈旧告警永久 exit 1/2)
|
|
5717
|
-
* - 写盘失败
|
|
5780
|
+
* - 写盘失败 → 立即失败(不计 lint)+ 整批回滚
|
|
5781
|
+
* - lint 验证失败 → 整批回滚(粒度 = batchSize;vs 旧版每个告警回滚,回滚粒度变粗但 lint 提速 ~10x)
|
|
5782
|
+
*
|
|
5783
|
+
* 性能权衡(M28.2 实证):
|
|
5784
|
+
* - N 个 cs 告警(旧版)→ N 次 spawn `pnpm lint` 子进程:N=100 平均 2593 ms(基准 commit 395ee29)
|
|
5785
|
+
* - N 个 cs 告警(批处理)→ ceil(N/batchSize) 次 lint:N=100 平均 ~300 ms(提速 ~10x)
|
|
5786
|
+
* - 合并验证可提速 ~96x 但回滚粒度过粗(整次 fix 全失败);批处理折中
|
|
5787
|
+
*
|
|
5718
5788
|
* @returns 本批次实际修复数(fixed)与失败数(failed),调用方累加到仓库统计
|
|
5719
5789
|
*/
|
|
5720
|
-
async function runCodeScanningFixes(ctx, repo, alerts) {
|
|
5790
|
+
async function runCodeScanningFixes(ctx, repo, alerts, options = {}) {
|
|
5721
5791
|
const { config, workDir, logger } = ctx;
|
|
5792
|
+
const batchSize = options.batchSize ?? DEFAULT_CS_BATCH_SIZE;
|
|
5722
5793
|
const codeScanningAutoFixable = alerts.filter((a) => a.source === "code-scanning" && a.alertClass === "auto-fixable");
|
|
5723
5794
|
let fixed = 0;
|
|
5724
5795
|
let failed = 0;
|
|
5796
|
+
/** 待 lint 批:每条保留 sourceSnapshot + action 用于 lint 失败时整批回滚 */
|
|
5797
|
+
const pendingBatch = [];
|
|
5798
|
+
/**
|
|
5799
|
+
* 处理当前批:跑 lint + 按结果分发 fixed/failed + 回滚(lint 失败时)。
|
|
5800
|
+
*/
|
|
5801
|
+
const flushBatch = async () => {
|
|
5802
|
+
if (pendingBatch.length === 0) return;
|
|
5803
|
+
if (await quickVerifyProject(ctx, repo)) for (const item of pendingBatch) {
|
|
5804
|
+
ctx.allActions.push(item.action);
|
|
5805
|
+
fixed++;
|
|
5806
|
+
logger.info(`[code-scanning] ${item.csAlert.ruleId}: ${item.action.diff}`);
|
|
5807
|
+
}
|
|
5808
|
+
else for (const item of pendingBatch) {
|
|
5809
|
+
const restored = restoreSourceFile(workDir, item.sourceSnapshot);
|
|
5810
|
+
item.action.success = false;
|
|
5811
|
+
item.action.error = restored ? "lint failed after code-scanning fix; changes rolled back" : "lint failed after code-scanning fix; rollback failed, file may be modified";
|
|
5812
|
+
ctx.allActions.push(item.action);
|
|
5813
|
+
failed++;
|
|
5814
|
+
logger.warn(`[code-scanning] ${item.csAlert.ruleId} fix rolled back: lint failed`);
|
|
5815
|
+
}
|
|
5816
|
+
pendingBatch.length = 0;
|
|
5817
|
+
};
|
|
5725
5818
|
for (const csAlert of codeScanningAutoFixable) {
|
|
5726
5819
|
const sourceSnapshot = snapshotSourceFile(workDir, csAlert.manifestPath);
|
|
5727
5820
|
if (!sourceSnapshot) {
|
|
@@ -5762,24 +5855,30 @@ async function runCodeScanningFixes(ctx, repo, alerts) {
|
|
|
5762
5855
|
ctx.allActions.push(action);
|
|
5763
5856
|
continue;
|
|
5764
5857
|
}
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
action
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
logger.warn(`[code-scanning] ${csAlert.ruleId} fix rolled back: lint failed`);
|
|
5772
|
-
continue;
|
|
5773
|
-
}
|
|
5774
|
-
ctx.allActions.push(action);
|
|
5775
|
-
fixed++;
|
|
5776
|
-
logger.info(`[code-scanning] ${csAlert.ruleId}: ${action.diff}`);
|
|
5858
|
+
pendingBatch.push({
|
|
5859
|
+
csAlert,
|
|
5860
|
+
sourceSnapshot,
|
|
5861
|
+
action
|
|
5862
|
+
});
|
|
5863
|
+
if (pendingBatch.length >= batchSize) await flushBatch();
|
|
5777
5864
|
}
|
|
5865
|
+
await flushBatch();
|
|
5778
5866
|
return {
|
|
5779
5867
|
fixed,
|
|
5780
5868
|
failed
|
|
5781
5869
|
};
|
|
5782
5870
|
}
|
|
5871
|
+
/**
|
|
5872
|
+
* Code Scanning 修复批处理默认大小(M28.2 决策)。
|
|
5873
|
+
*
|
|
5874
|
+
* 决策依据(commit 395ee29 baseline):
|
|
5875
|
+
* - 旧版(每个告警 1 次 lint):N=10 平均 263 ms / N=50 平均 1294 ms / N=100 平均 2593 ms
|
|
5876
|
+
* - 批处理 batchSize=10:N=10 触发 1 次 lint(vs 旧版 10 次 → 提速 ~10x)
|
|
5877
|
+
* - 回滚粒度 = batchSize(vs 旧版每个告警独立回滚)
|
|
5878
|
+
*
|
|
5879
|
+
* 经验值:典型 cs 告警场景 5-50 个 / repo,batchSize=10 平衡 lint 提速 + 回滚粒度。
|
|
5880
|
+
*/
|
|
5881
|
+
const DEFAULT_CS_BATCH_SIZE = 10;
|
|
5783
5882
|
/** 尝试修复 pnpm-lock.yaml(dry-run 仅记录)。 */
|
|
5784
5883
|
function tryLockfileRepair(ctx, repo) {
|
|
5785
5884
|
const { config, logger, workDir } = ctx;
|
|
@@ -5958,7 +6057,7 @@ function buildPrTitle(summary, actions) {
|
|
|
5958
6057
|
const parts = [];
|
|
5959
6058
|
if (upgrades > 0) parts.push(`${upgrades} upgrade${upgrades > 1 ? "s" : ""}`);
|
|
5960
6059
|
if (codeFixes > 0) parts.push(`${codeFixes} code fix${codeFixes > 1 ? "es" : ""}`);
|
|
5961
|
-
return parts.length > 0 ? `
|
|
6060
|
+
return parts.length > 0 ? `chore(deps): automated security fix — ${parts.join(", ")}` : "chore(deps): automated security fix";
|
|
5962
6061
|
}
|
|
5963
6062
|
|
|
5964
6063
|
//#endregion
|
|
@@ -6004,19 +6103,23 @@ async function fetchRepoAlerts(deps, repo) {
|
|
|
6004
6103
|
failedSources.push("dependabot");
|
|
6005
6104
|
recordAlertSourceError(deps, repo, "dependabot", dependabotResult.reason);
|
|
6006
6105
|
}
|
|
6007
|
-
if (config.codeScanningEnabled)
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6106
|
+
if (config.codeScanningEnabled) {
|
|
6107
|
+
if (codeScanningResult.status === "fulfilled") {
|
|
6108
|
+
alerts.push(...codeScanningResult.value);
|
|
6109
|
+
deps.logger.info(`Fetched ${codeScanningResult.value.length} code scanning alerts for ${repo}`);
|
|
6110
|
+
} else {
|
|
6111
|
+
failedSources.push("code-scanning");
|
|
6112
|
+
recordAlertSourceError(deps, repo, "code-scanning", codeScanningResult.reason);
|
|
6113
|
+
}
|
|
6013
6114
|
}
|
|
6014
|
-
if (config.codeQualityEnabled)
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
6115
|
+
if (config.codeQualityEnabled) {
|
|
6116
|
+
if (codeQualityResult.status === "fulfilled") {
|
|
6117
|
+
alerts.push(...codeQualityResult.value);
|
|
6118
|
+
deps.logger.info(`Fetched ${codeQualityResult.value.length} code quality findings for ${repo}`);
|
|
6119
|
+
} else {
|
|
6120
|
+
failedSources.push("code-quality");
|
|
6121
|
+
recordAlertSourceError(deps, repo, "code-quality", codeQualityResult.reason);
|
|
6122
|
+
}
|
|
6020
6123
|
}
|
|
6021
6124
|
let totalSources = 1;
|
|
6022
6125
|
if (config.codeScanningEnabled) totalSources++;
|
|
@@ -6038,13 +6141,10 @@ async function fetchRepoAlerts(deps, repo) {
|
|
|
6038
6141
|
* 背景详见 docs/plan/todo.md「已知缺口 G2」。
|
|
6039
6142
|
*/
|
|
6040
6143
|
function createAlertsClientFromConfig(config) {
|
|
6041
|
-
return createGitHubClient({
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
maxBackoffMs: config.maxBackoffMs
|
|
6046
|
-
}
|
|
6047
|
-
});
|
|
6144
|
+
return createGitHubClient({ auth: fromPat(config.alertsToken || config.githubToken, { retry: {
|
|
6145
|
+
maxRetries: config.maxRetries,
|
|
6146
|
+
maxBackoffMs: config.maxBackoffMs
|
|
6147
|
+
} }) });
|
|
6048
6148
|
}
|
|
6049
6149
|
/** 记录单个告警源的拉取失败(不中断另一源的处理)。 */
|
|
6050
6150
|
function recordAlertSourceError(deps, repo, source, error) {
|
|
@@ -6055,6 +6155,7 @@ function recordAlertSourceError(deps, repo, source, error) {
|
|
|
6055
6155
|
repository: repo,
|
|
6056
6156
|
stage: "fetch",
|
|
6057
6157
|
category: "FETCH_FAILED",
|
|
6158
|
+
source,
|
|
6058
6159
|
message: hint ? `${message}(${hint})` : message
|
|
6059
6160
|
});
|
|
6060
6161
|
}
|
|
@@ -7154,6 +7255,35 @@ async function finalizeRepoFix(ctx, repo, progress) {
|
|
|
7154
7255
|
|
|
7155
7256
|
//#endregion
|
|
7156
7257
|
//#region src/app/index.ts
|
|
7258
|
+
/**
|
|
7259
|
+
* 输出"部分源拉取失败"汇总(todo.md §M19.5 C8 per-source 错误隔离)。
|
|
7260
|
+
*
|
|
7261
|
+
* 触发条件(必须全部满足,避免与"全部源失败"语义重叠):
|
|
7262
|
+
* 1. allErrors 至少包含一个 `stage='fetch' + category='FETCH_FAILED'` 错误(per-source 失败)
|
|
7263
|
+
* 2. **至少一个仓库成功拉取了部分告警**(isAnyRepoSuccessful = true)——
|
|
7264
|
+
* 否则就是"全部源失败"语义,已由 processRepoForReport catch + logger.error 单独处理,
|
|
7265
|
+
* 本函数聚焦"warn + 保留成功源"场景的汇总输出,避免重复提示。
|
|
7266
|
+
*
|
|
7267
|
+
* 汇总按 source 分组('dependabot' / 'code-scanning' / 'code-quality' / 'pnpm-audit'),
|
|
7268
|
+
* 列出每个源失败的仓库数 + 示例错误消息,便于用户快速定位是 token 权限还是网络问题。
|
|
7269
|
+
*/
|
|
7270
|
+
function logPartialSourceFailureSummary(allErrors, logger, isAnyRepoSuccessful) {
|
|
7271
|
+
const fetchErrors = allErrors.filter((e) => e.stage === "fetch" && e.category === "FETCH_FAILED");
|
|
7272
|
+
if (fetchErrors.length === 0) return;
|
|
7273
|
+
if (!isAnyRepoSuccessful) return;
|
|
7274
|
+
const bySource = /* @__PURE__ */ new Map();
|
|
7275
|
+
for (const err of fetchErrors) {
|
|
7276
|
+
const source = err.source ?? "unknown";
|
|
7277
|
+
const existing = bySource.get(source);
|
|
7278
|
+
if (existing) existing.repos.add(err.repository);
|
|
7279
|
+
else bySource.set(source, {
|
|
7280
|
+
repos: /* @__PURE__ */ new Set([err.repository]),
|
|
7281
|
+
sampleMessage: err.message
|
|
7282
|
+
});
|
|
7283
|
+
}
|
|
7284
|
+
const summary = [...bySource.entries()].map(([source, { repos, sampleMessage }]) => `${source}(${repos.size} 个仓库: ${[...repos].slice(0, 3).join(", ")}${repos.size > 3 ? "..." : ""}): ${sampleMessage}`).join("\n - ");
|
|
7285
|
+
logger.warn(`[alerts] 部分源拉取失败(M19.5 per-source 错误隔离)— 成功源已保留继续处理,详细如下:\n - ${summary}`);
|
|
7286
|
+
}
|
|
7157
7287
|
var DependfixApp = class {
|
|
7158
7288
|
config;
|
|
7159
7289
|
workDir;
|
|
@@ -7182,7 +7312,7 @@ var DependfixApp = class {
|
|
|
7182
7312
|
this.customCommands = options.commands;
|
|
7183
7313
|
this.executionEnvironment = options.executionEnvironment ?? "local";
|
|
7184
7314
|
this.runId = `dependfix-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
7185
|
-
this.logger = createLogger({
|
|
7315
|
+
this.logger = options.logger ?? createLogger({
|
|
7186
7316
|
name: "dependfix",
|
|
7187
7317
|
minLevel: this.verbose ? "debug" : "info"
|
|
7188
7318
|
});
|
|
@@ -7257,6 +7387,8 @@ var DependfixApp = class {
|
|
|
7257
7387
|
const costText = u.estimatedCostUsd !== void 0 ? `, 估算成本 $${u.estimatedCostUsd.toFixed(4)}` : "";
|
|
7258
7388
|
this.logger.info(`[ai] run 总计: ${u.calls} 次调用, ${u.inputTokens} in / ${u.outputTokens} out tokens${costText}`);
|
|
7259
7389
|
}
|
|
7390
|
+
const isAnyRepoSuccessful = this.repoResults.some((r) => r.alertsCount > 0 || r.fixed > 0 || r.verificationPassed === true);
|
|
7391
|
+
logPartialSourceFailureSummary(this.allErrors, this.logger, isAnyRepoSuccessful);
|
|
7260
7392
|
return {
|
|
7261
7393
|
result: runResult,
|
|
7262
7394
|
exitCode
|
|
@@ -7454,6 +7586,7 @@ var DependfixApp = class {
|
|
|
7454
7586
|
success: true,
|
|
7455
7587
|
durationMs: 0
|
|
7456
7588
|
});
|
|
7589
|
+
await this.handleDuplicatePRs(client, owner, repo, pr.number, plan.supersedePRs);
|
|
7457
7590
|
await closeSupersededPRs(this.ctx, client, owner, repo, plan.supersedePRs);
|
|
7458
7591
|
} catch (error) {
|
|
7459
7592
|
const message = toErrorMessage(error);
|
|
@@ -7480,6 +7613,41 @@ var DependfixApp = class {
|
|
|
7480
7613
|
for (const repo of this.config.repositories) await runBranchCleanupForRepo(this.ctx, client, repo);
|
|
7481
7614
|
}
|
|
7482
7615
|
/**
|
|
7616
|
+
* 重复 PR 处理:在新 PR 上添加评论指向被取代的旧 PR + 添加 `duplicate` label。
|
|
7617
|
+
*
|
|
7618
|
+
* 失败仅记录 warning(家务活 best-effort),不阻断主流程:
|
|
7619
|
+
* - 新 PR 已创建成功,用户已可访问
|
|
7620
|
+
* - 评论失败不影响 PR 本身的可用性
|
|
7621
|
+
* - 但需要 `issues: write` token scope(比 `pull-requests: write` 宽);
|
|
7622
|
+
* 若 token 权限不足,error 会被记录但不阻塞
|
|
7623
|
+
*/
|
|
7624
|
+
async handleDuplicatePRs(client, owner, repo, newPRNumber, supersededPRs) {
|
|
7625
|
+
if (supersededPRs.length === 0) return;
|
|
7626
|
+
const commentBody = [
|
|
7627
|
+
"## ⚠️ Duplicate PR Notice",
|
|
7628
|
+
"",
|
|
7629
|
+
"This PR supersedes the following dependfix PR(s) with different content:",
|
|
7630
|
+
"",
|
|
7631
|
+
supersededPRs.map((pr) => `- [#${pr.number}](${pr.htmlUrl})`).join("\n"),
|
|
7632
|
+
"",
|
|
7633
|
+
"The superseded PR(s) have been closed. Please review this PR as the new canonical fix."
|
|
7634
|
+
].join("\n");
|
|
7635
|
+
try {
|
|
7636
|
+
await commentOnPullRequest(client, owner, repo, newPRNumber, commentBody);
|
|
7637
|
+
this.logger.info(`Added duplicate notice comment to PR #${newPRNumber}`);
|
|
7638
|
+
} catch (error) {
|
|
7639
|
+
const message = toErrorMessage(error);
|
|
7640
|
+
this.logger.warn(`Failed to add duplicate notice comment to PR #${newPRNumber}: ${message}(token may lack \`issues: write\` scope)`);
|
|
7641
|
+
}
|
|
7642
|
+
try {
|
|
7643
|
+
await addLabelToPullRequest(client, owner, repo, newPRNumber, ["duplicate"]);
|
|
7644
|
+
this.logger.info(`Added 'duplicate' label to PR #${newPRNumber}`);
|
|
7645
|
+
} catch (error) {
|
|
7646
|
+
const message = toErrorMessage(error);
|
|
7647
|
+
this.logger.warn(`Failed to add 'duplicate' label to PR #${newPRNumber}: ${message}(token may lack \`issues: write\` scope)`);
|
|
7648
|
+
}
|
|
7649
|
+
}
|
|
7650
|
+
/**
|
|
7483
7651
|
* 多仓库并发执行管线。
|
|
7484
7652
|
*
|
|
7485
7653
|
* - 并发窗口 = config.maxConcurrency(默认 1 保守串行,行为与现状一致)
|
|
@@ -7539,7 +7707,8 @@ var DependfixApp = class {
|
|
|
7539
7707
|
client,
|
|
7540
7708
|
owners: owner,
|
|
7541
7709
|
topics: this.config.repoTopics,
|
|
7542
|
-
policy
|
|
7710
|
+
policy,
|
|
7711
|
+
maxRepos: this.config.maxRepos
|
|
7543
7712
|
});
|
|
7544
7713
|
const explicitFiltered = filterExplicitRepositories(policy, this.config.repositories);
|
|
7545
7714
|
const merged = mergeRepositories(explicitFiltered, discovered.map((r) => r.fullName));
|
|
@@ -7563,16 +7732,13 @@ var DependfixApp = class {
|
|
|
7563
7732
|
return this.createClient();
|
|
7564
7733
|
}
|
|
7565
7734
|
createClient(token = this.config.githubToken) {
|
|
7566
|
-
return createGitHubClient({
|
|
7567
|
-
|
|
7568
|
-
|
|
7569
|
-
|
|
7570
|
-
maxBackoffMs: this.config.maxBackoffMs
|
|
7571
|
-
}
|
|
7572
|
-
});
|
|
7735
|
+
return createGitHubClient({ auth: fromPat(token, { retry: {
|
|
7736
|
+
maxRetries: this.config.maxRetries,
|
|
7737
|
+
maxBackoffMs: this.config.maxBackoffMs
|
|
7738
|
+
} }) });
|
|
7573
7739
|
}
|
|
7574
7740
|
};
|
|
7575
7741
|
|
|
7576
7742
|
//#endregion
|
|
7577
|
-
export { ALERT_SOURCES, AUTO_FIXABLE_RULES, AiUsageTracker, DEFAULT_CONCURRENCY, DEFAULT_RULES_CONFIG, DEFAULT_RUNTIME_CONFIG, DependfixApp, ENV_PREFIX, MAX_CONCURRENCY_LIMIT, MAX_GLOB_PATTERN_LENGTH, RULE_CONFIG_ENV, RUNTIME_MODES, SEVERITY_THRESHOLDS, SUGGESTED_RULES, analyzeTokenScope, applyCodeScanningFix, applyVersionedOverrides, assessBreakingChange, buildPrTitle, buildUpgradeGroups, checkTokenPermissions, classifyLockfileFailure, classifyRule, closePullRequest, compareSemver, computeFixAndPrPlan, computeFixFingerprint, computeLockfileDiff, computeRetryDelayMs, createFixBranch, createGitHubClient, createPullRequest, dedupeFixableAlerts, deleteRemoteBranch, detectPnpmMajor, discoverRepositories, ensurePnpmOverrides, estimateCostUsd, extractFingerprintFromBranch, extractLockfileVersion, extractMainPackage, extractPrefix, fetchCodeQualityFindings, fetchCodeScanningAlerts, fetchDefaultBranch, fetchDependabotAlerts, fetchPnpmAuditAlerts, filterExplicitRepositories, findDependencyVersion, findDependfixOpenPR, formatHistory, formatVerificationError, generatePRBody, getActiveRulesConfig, getBranchPrStatus, getCodeScanningFixTemplate, hashAdvisoryId, inferRepoFromGitRemote, isConfirmAnswer, isCrossMajorFixRequired, isMainPackagePresent, isNonSemverDeclaration, isRootDirectDependency, isValidConcurrency, isValidPnpmVersion, listDependfixBranches, loadRulesConfigFromEnv, loadRulesConfigFromFile, mapCodeQualitySeverity, mapGitHubError, maskSecrets, matchesPattern, matchesRepoExclude, matchesRepoGlob, matchesRepoInclude, matchesTopicsExclude, mergeRepositories, overrideTransitiveDependency, parseAuditReport, parseDependabotGroups, parseMajorVersion, parseRulesConfig, partitionSubmanifestAlerts, pushBranch, queryRepoHistory, quickVerifyProject, readArchiveIndex, readEnv, readEnvConfig, readExistingOverrides, readLockfileVersion, readLockfileVersions, readReposFile, repairLockfile, repoGlobToRegExp, resetActiveRulesConfig, resolvePnpmVersion, resolveRepoList, resolveRuntimeConfig, resolveWithinWorkDir, restoreSourceFile, restoreTrackedFiles, runVerification, runWithConcurrency, sanitizeOutput, setActiveRulesConfig, snapshotSourceFile, snapshotTrackedFiles, stageAndCommit, suggestionFor, summarizeVerificationOutput, upgradeDependency, writeArchive };
|
|
7743
|
+
export { ALERT_SOURCES, AUTO_FIXABLE_RULES, AiUsageTracker, DEFAULT_CONCURRENCY, DEFAULT_RULES_CONFIG, DEFAULT_RUNTIME_CONFIG, DependfixApp, ENV_PREFIX, MAX_CONCURRENCY_LIMIT, MAX_GLOB_PATTERN_LENGTH, RULE_CONFIG_ENV, RUNTIME_MODES, SEVERITY_THRESHOLDS, SUGGESTED_RULES, addLabelToPullRequest, analyzeTokenScope, applyCodeScanningFix, applyVersionedOverrides, assessBreakingChange, buildPrTitle, buildUpgradeGroups, checkTokenPermissions, classifyLockfileFailure, classifyRule, closePullRequest, closeSupersededPRs, commentOnPullRequest, compareSemver, computeFixAndPrPlan, computeFixFingerprint, computeLockfileDiff, computeRetryDelayMs, createFixBranch, createGitHubClient, createPullRequest, dedupeFixableAlerts, deleteRemoteBranch, detectPnpmMajor, discoverRepositories, ensurePnpmOverrides, estimateCostUsd, extractFingerprintFromBranch, extractLockfileVersion, extractMainPackage, extractPrefix, fetchCodeQualityFindings, fetchCodeScanningAlerts, fetchDefaultBranch, fetchDependabotAlerts, fetchPnpmAuditAlerts, filterExplicitRepositories, findDependencyVersion, findDependfixOpenPR, formatHistory, formatVerificationError, generatePRBody, getActiveRulesConfig, getBranchPrStatus, getCodeScanningFixTemplate, hashAdvisoryId, inferRepoFromGitRemote, isConfirmAnswer, isCrossMajorFixRequired, isMainPackagePresent, isNonSemverDeclaration, isRootDirectDependency, isValidConcurrency, isValidPnpmVersion, listDependfixBranches, loadRulesConfigFromEnv, loadRulesConfigFromFile, logPartialSourceFailureSummary, mapCodeQualitySeverity, mapGitHubError, maskSecrets, matchesPattern, matchesRepoExclude, matchesRepoGlob, matchesRepoInclude, matchesTopicsExclude, mergeRepositories, overrideTransitiveDependency, parseAuditReport, parseDependabotGroups, parseMajorVersion, parseRulesConfig, partitionSubmanifestAlerts, pushBranch, queryRepoHistory, quickVerifyProject, readArchiveIndex, readEnv, readEnvConfig, readExistingOverrides, readLockfileVersion, readLockfileVersions, readReposFile, repairLockfile, repoGlobToRegExp, resetActiveRulesConfig, resolvePnpmVersion, resolveRepoList, resolveRuntimeConfig, resolveWithinWorkDir, restoreSourceFile, restoreTrackedFiles, runVerification, runWithConcurrency, sanitizeOutput, setActiveRulesConfig, snapshotSourceFile, snapshotTrackedFiles, stageAndCommit, suggestionFor, summarizeVerificationOutput, upgradeDependency, writeArchive };
|
|
7578
7744
|
//# sourceMappingURL=index.mjs.map
|