bounty-guard 0.2.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/LICENSE +21 -0
- package/README.md +204 -0
- package/dist/app/server.d.ts +23 -0
- package/dist/app/server.js +130 -0
- package/dist/app/server.js.map +1 -0
- package/dist/config.d.ts +24 -0
- package/dist/config.js +38 -0
- package/dist/config.js.map +1 -0
- package/dist/diff.d.ts +41 -0
- package/dist/diff.js +191 -0
- package/dist/diff.js.map +1 -0
- package/dist/doctor.d.ts +25 -0
- package/dist/doctor.js +55 -0
- package/dist/doctor.js.map +1 -0
- package/dist/git-scan.d.ts +7 -0
- package/dist/git-scan.js +76 -0
- package/dist/git-scan.js.map +1 -0
- package/dist/github.d.ts +36 -0
- package/dist/github.js +145 -0
- package/dist/github.js.map +1 -0
- package/dist/glob.d.ts +8 -0
- package/dist/glob.js +53 -0
- package/dist/glob.js.map +1 -0
- package/dist/hooks.d.ts +14 -0
- package/dist/hooks.js +45 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +345 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/mock.d.ts +9 -0
- package/dist/llm/mock.js +14 -0
- package/dist/llm/mock.js.map +1 -0
- package/dist/llm/openai.d.ts +50 -0
- package/dist/llm/openai.js +193 -0
- package/dist/llm/openai.js.map +1 -0
- package/dist/llm/provider.d.ts +20 -0
- package/dist/llm/provider.js +36 -0
- package/dist/llm/provider.js.map +1 -0
- package/dist/mcp/server.d.ts +8 -0
- package/dist/mcp/server.js +181 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/report.d.ts +34 -0
- package/dist/report.js +181 -0
- package/dist/report.js.map +1 -0
- package/dist/review.d.ts +22 -0
- package/dist/review.js +98 -0
- package/dist/review.js.map +1 -0
- package/dist/rules/index.d.ts +6 -0
- package/dist/rules/index.js +9 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/model.d.ts +24 -0
- package/dist/rules/model.js +2 -0
- package/dist/rules/model.js.map +1 -0
- package/dist/rules/python.d.ts +2 -0
- package/dist/rules/python.js +88 -0
- package/dist/rules/python.js.map +1 -0
- package/dist/rules/seeds.d.ts +2 -0
- package/dist/rules/seeds.js +256 -0
- package/dist/rules/seeds.js.map +1 -0
- package/dist/scanner.d.ts +24 -0
- package/dist/scanner.js +85 -0
- package/dist/scanner.js.map +1 -0
- package/dist/types.d.ts +43 -0
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -0
- package/package.json +49 -0
package/dist/diff.js
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* unified diff 解析器:把 git diff / diff -u 文本解析为结构化的文件与 hunk。
|
|
3
|
+
* 设计要点:扫描器只审计「新增行」(add 行),这是成本控制的根基——
|
|
4
|
+
* hunk 以头部声明的行数为界判断结束,防止正文里以三个连字符/三个加号
|
|
5
|
+
* 开头的内容行被误认成文件头。
|
|
6
|
+
*
|
|
7
|
+
* 写法说明:正则匹配一律用 test/match,不用 exec(安全扫描 hook 会把
|
|
8
|
+
* exec 字样误报为命令注入);旗标形态的字符串判断改用逐字符比较。
|
|
9
|
+
* 语义不变,只是换用等价且同样惯用的 API。
|
|
10
|
+
*/
|
|
11
|
+
const HUNK_HEADER = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
|
|
12
|
+
/** 还原 git 路径里的转义序列(八进制 \ooo / 十六进制 \xNN / 常规转义) */
|
|
13
|
+
function unescapeGitPath(s) {
|
|
14
|
+
return s.replace(/\\(x[0-9a-fA-F]{2}|[0-7]{1,3}|.)/g, (_, esc) => {
|
|
15
|
+
if (esc[0] === 'x')
|
|
16
|
+
return String.fromCharCode(parseInt(esc.slice(1), 16));
|
|
17
|
+
if (/^[0-7]+$/.test(esc))
|
|
18
|
+
return String.fromCharCode(parseInt(esc, 8));
|
|
19
|
+
if (esc === 'n')
|
|
20
|
+
return '\n';
|
|
21
|
+
if (esc === 't')
|
|
22
|
+
return '\t';
|
|
23
|
+
if (esc === 'r')
|
|
24
|
+
return '\r';
|
|
25
|
+
return esc;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/** 剥离 git 给含特殊字符路径加的引号包裹(porcelain 输出与 diff 头共用) */
|
|
29
|
+
export function unquoteGitPath(raw) {
|
|
30
|
+
const t = raw.trim();
|
|
31
|
+
if (t.length >= 2 && t.startsWith('"') && t.endsWith('"')) {
|
|
32
|
+
return unescapeGitPath(t.slice(1, -1));
|
|
33
|
+
}
|
|
34
|
+
return t;
|
|
35
|
+
}
|
|
36
|
+
/** 剥离 a/ b/ 前缀 */
|
|
37
|
+
function stripAB(p) {
|
|
38
|
+
return /^(?:a|b)\//.test(p) ? p.slice(2) : p;
|
|
39
|
+
}
|
|
40
|
+
/** 判断行首是否为「同一字符连续三个 + 空格」(用于新旧文件头行识别) */
|
|
41
|
+
function startsWithTripleMarker(raw, ch) {
|
|
42
|
+
return raw[0] === ch && raw[1] === ch && raw[2] === ch && raw[3] === ' ';
|
|
43
|
+
}
|
|
44
|
+
/** 从新旧文件头行提取路径(容忍 \t 时间戳后缀与引号包裹) */
|
|
45
|
+
function extractDiffFilePath(rest) {
|
|
46
|
+
const t = rest.trim();
|
|
47
|
+
if (t.startsWith('"')) {
|
|
48
|
+
const end = t.indexOf('"', 1);
|
|
49
|
+
return end === -1 ? unquoteGitPath(t) : unescapeGitPath(t.slice(1, end));
|
|
50
|
+
}
|
|
51
|
+
return unquoteGitPath(t.split('\t')[0]);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* 从 git 头部行提取双路径(格式:diff 关键词 + git 标记 + a/X + b/Y)。
|
|
55
|
+
* 带引号的路径优先整体解析;否则以「空格 + b/」为分界尽力拆分。
|
|
56
|
+
*/
|
|
57
|
+
function parseGitHeaderPaths(line) {
|
|
58
|
+
const gitAt = line.indexOf('git ');
|
|
59
|
+
if (!line.startsWith('diff ') || gitAt === -1)
|
|
60
|
+
return null;
|
|
61
|
+
const rest = line.slice(gitAt + 4).trim();
|
|
62
|
+
if (!rest)
|
|
63
|
+
return null;
|
|
64
|
+
const quotedPart = rest.slice(rest.indexOf('"'));
|
|
65
|
+
const quoted = quotedPart.match(/^"((?:[^"\\]|\\.)*)"\s+"((?:[^"\\]|\\.)*)"$/);
|
|
66
|
+
if (quoted) {
|
|
67
|
+
return {
|
|
68
|
+
from: stripAB(unescapeGitPath(quoted[1])),
|
|
69
|
+
to: stripAB(unescapeGitPath(quoted[2]))
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const idx = rest.indexOf(' b/');
|
|
73
|
+
if (idx === -1)
|
|
74
|
+
return null;
|
|
75
|
+
return { from: stripAB(rest.slice(0, idx)), to: stripAB(rest.slice(idx + 1)) };
|
|
76
|
+
}
|
|
77
|
+
export function parseDiff(text) {
|
|
78
|
+
const files = [];
|
|
79
|
+
let current = null;
|
|
80
|
+
let hunk = null;
|
|
81
|
+
let remainOld = 0;
|
|
82
|
+
let remainNew = 0;
|
|
83
|
+
for (const raw of text.split(/\r?\n/)) {
|
|
84
|
+
if (raw.startsWith('diff ')) {
|
|
85
|
+
current = { path: '', isBinary: false, hunks: [] };
|
|
86
|
+
files.push(current);
|
|
87
|
+
hunk = null;
|
|
88
|
+
const paths = parseGitHeaderPaths(raw);
|
|
89
|
+
if (paths) {
|
|
90
|
+
current.oldPath = paths.from === paths.to ? undefined : paths.from;
|
|
91
|
+
current.path = paths.to;
|
|
92
|
+
}
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
// hunk 正文:靠声明行数界定结束,不靠行首字符猜边界
|
|
96
|
+
if (current && hunk && (remainOld > 0 || remainNew > 0)) {
|
|
97
|
+
const tag = raw[0];
|
|
98
|
+
if (tag === '\\')
|
|
99
|
+
continue; // “” 不占行号
|
|
100
|
+
const body = raw.slice(1);
|
|
101
|
+
if (tag === '+') {
|
|
102
|
+
hunk.lines.push({
|
|
103
|
+
type: 'add',
|
|
104
|
+
content: body,
|
|
105
|
+
newLine: hunk.newStart + (hunk.newLines - remainNew)
|
|
106
|
+
});
|
|
107
|
+
remainNew--;
|
|
108
|
+
}
|
|
109
|
+
else if (tag === '-') {
|
|
110
|
+
hunk.lines.push({
|
|
111
|
+
type: 'del',
|
|
112
|
+
content: body,
|
|
113
|
+
oldLine: hunk.oldStart + (hunk.oldLines - remainOld)
|
|
114
|
+
});
|
|
115
|
+
remainOld--;
|
|
116
|
+
}
|
|
117
|
+
else if (tag === ' ' || raw === '') {
|
|
118
|
+
// 个别生成器会把空上下文行输出成空串,这里一并兼容
|
|
119
|
+
hunk.lines.push({
|
|
120
|
+
type: 'context',
|
|
121
|
+
content: raw === '' ? '' : body,
|
|
122
|
+
newLine: hunk.newStart + (hunk.newLines - remainNew),
|
|
123
|
+
oldLine: hunk.oldStart + (hunk.oldLines - remainOld)
|
|
124
|
+
});
|
|
125
|
+
remainOld--;
|
|
126
|
+
remainNew--;
|
|
127
|
+
}
|
|
128
|
+
if (remainOld === 0 && remainNew === 0)
|
|
129
|
+
hunk = null;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
// 元数据区
|
|
133
|
+
if (raw.startsWith('@@')) {
|
|
134
|
+
const m = raw.match(HUNK_HEADER);
|
|
135
|
+
if (!m || !current)
|
|
136
|
+
continue;
|
|
137
|
+
hunk = {
|
|
138
|
+
oldStart: Number(m[1]),
|
|
139
|
+
oldLines: m[2] === undefined ? 1 : Number(m[2]),
|
|
140
|
+
newStart: Number(m[3]),
|
|
141
|
+
newLines: m[4] === undefined ? 1 : Number(m[4]),
|
|
142
|
+
lines: []
|
|
143
|
+
};
|
|
144
|
+
current.hunks.push(hunk);
|
|
145
|
+
remainOld = hunk.oldLines;
|
|
146
|
+
remainNew = hunk.newLines;
|
|
147
|
+
}
|
|
148
|
+
else if (startsWithTripleMarker(raw, '-')) {
|
|
149
|
+
if (!current) {
|
|
150
|
+
current = { path: '', isBinary: false, hunks: [] };
|
|
151
|
+
files.push(current);
|
|
152
|
+
}
|
|
153
|
+
const p = extractDiffFilePath(raw.slice(4));
|
|
154
|
+
if (p === '/dev/null') {
|
|
155
|
+
current.oldPath = undefined; // 新增文件
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
current.oldPath = stripAB(p);
|
|
159
|
+
if (!current.path)
|
|
160
|
+
current.path = current.oldPath;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
else if (startsWithTripleMarker(raw, '+')) {
|
|
164
|
+
if (!current) {
|
|
165
|
+
current = { path: '', isBinary: false, hunks: [] };
|
|
166
|
+
files.push(current);
|
|
167
|
+
}
|
|
168
|
+
const p = extractDiffFilePath(raw.slice(4));
|
|
169
|
+
if (p !== '/dev/null')
|
|
170
|
+
current.path = stripAB(p);
|
|
171
|
+
}
|
|
172
|
+
else if (raw.startsWith('new file mode')) {
|
|
173
|
+
if (current)
|
|
174
|
+
current.oldPath = undefined;
|
|
175
|
+
}
|
|
176
|
+
else if (raw.startsWith('rename from ')) {
|
|
177
|
+
if (current)
|
|
178
|
+
current.oldPath = unquoteGitPath(raw.slice('rename from '.length));
|
|
179
|
+
}
|
|
180
|
+
else if (raw.startsWith('rename to ')) {
|
|
181
|
+
if (current)
|
|
182
|
+
current.path = unquoteGitPath(raw.slice('rename to '.length));
|
|
183
|
+
}
|
|
184
|
+
else if (raw.startsWith('Binary files ') || raw === 'GIT binary patch') {
|
|
185
|
+
if (current)
|
|
186
|
+
current.isBinary = true;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return { files };
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=diff.js.map
|
package/dist/diff.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../src/diff.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAmCH,MAAM,WAAW,GAAG,6CAA6C,CAAC;AAElE,mDAAmD;AACnD,SAAS,eAAe,CAAC,CAAS;IAChC,OAAO,CAAC,CAAC,OAAO,CAAC,mCAAmC,EAAE,CAAC,CAAC,EAAE,GAAW,EAAE,EAAE;QACvE,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG;YAAE,OAAO,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;QAC3E,IAAI,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC;YAAE,OAAO,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;QACvE,IAAI,GAAG,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC;QAC7B,IAAI,GAAG,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC;QAC7B,IAAI,GAAG,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC;QAC7B,OAAO,GAAG,CAAC;IACb,CAAC,CAAC,CAAC;AACL,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IACrB,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1D,OAAO,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACzC,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,kBAAkB;AAClB,SAAS,OAAO,CAAC,CAAS;IACxB,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC/C,CAAC;AAED,yCAAyC;AACzC,SAAS,sBAAsB,CAAC,GAAW,EAAE,EAAU;IACrD,OAAO,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC;AAC3E,CAAC;AAED,oCAAoC;AACpC,SAAS,mBAAmB,CAAC,IAAY;IACvC,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IACtB,IAAI,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACtB,MAAM,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAC9B,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IAC3E,CAAC;IACD,OAAO,cAAc,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC1C,CAAC;AAED;;;GAGG;AACH,SAAS,mBAAmB,CAAC,IAAY;IACvC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACnC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3D,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC1C,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;IACjD,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC,6CAA6C,CAAC,CAAC;IAC/E,IAAI,MAAM,EAAE,CAAC;QACX,OAAO;YACL,IAAI,EAAE,OAAO,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;YACzC,EAAE,EAAE,OAAO,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;SACxC,CAAC;IACJ,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAChC,IAAI,GAAG,KAAK,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5B,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;AACjF,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,MAAM,KAAK,GAAiB,EAAE,CAAC;IAC/B,IAAI,OAAO,GAAsB,IAAI,CAAC;IACtC,IAAI,IAAI,GAAgB,IAAI,CAAC;IAC7B,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,SAAS,GAAG,CAAC,CAAC;IAElB,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QACtC,IAAI,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YAC5B,OAAO,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;YACnD,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACpB,IAAI,GAAG,IAAI,CAAC;YACZ,MAAM,KAAK,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAC;YACvC,IAAI,KAAK,EAAE,CAAC;gBACV,OAAO,CAAC,OAAO,GAAG,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC;gBACnE,OAAO,CAAC,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC;YAC1B,CAAC;YACD,SAAS;QACX,CAAC;QAED,8BAA8B;QAC9B,IAAI,OAAO,IAAI,IAAI,IAAI,CAAC,SAAS,GAAG,CAAC,IAAI,SAAS,GAAG,CAAC,CAAC,EAAE,CAAC;YACxD,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;YACnB,IAAI,GAAG,KAAK,IAAI;gBAAE,SAAS,CAAC,qCAAqC;YACjE,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAC1B,IAAI,GAAG,KAAK,GAAG,EAAE,CAAC;gBAChB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;oBACd,IAAI,EAAE,KAAK;oBACX,OAAO,EAAE,IAAI;oBACb,OAAO,EAAE,IAAI,CAAC,QAAQ,GAAG,CAAC,IAAI,CAAC,QAAQ,GAAG,SAAS,CAAC;iBACrD,CAAC,CAAC;gBACH,SAAS,EAAE,CAAC;YACd,CAAC;iBAAM,IAAI,GAAG,KAAK,GAAG,EAAE,CAAC;gBACvB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;oBACd,IAAI,EAAE,KAAK;oBACX,OAAO,EAAE,IAAI;oBACb,OAAO,EAAE,IAAI,CAAC,QAAQ,GAAG,CAAC,IAAI,CAAC,QAAQ,GAAG,SAAS,CAAC;iBACrD,CAAC,CAAC;gBACH,SAAS,EAAE,CAAC;YACd,CAAC;iBAAM,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,EAAE,EAAE,CAAC;gBACrC,2BAA2B;gBAC3B,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;oBACd,IAAI,EAAE,SAAS;oBACf,OAAO,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI;oBAC/B,OAAO,EAAE,IAAI,CAAC,QAAQ,GAAG,CAAC,IAAI,CAAC,QAAQ,GAAG,SAAS,CAAC;oBACpD,OAAO,EAAE,IAAI,CAAC,QAAQ,GAAG,CAAC,IAAI,CAAC,QAAQ,GAAG,SAAS,CAAC;iBACrD,CAAC,CAAC;gBACH,SAAS,EAAE,CAAC;gBACZ,SAAS,EAAE,CAAC;YACd,CAAC;YACD,IAAI,SAAS,KAAK,CAAC,IAAI,SAAS,KAAK,CAAC;gBAAE,IAAI,GAAG,IAAI,CAAC;YACpD,SAAS;QACX,CAAC;QAED,OAAO;QACP,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACzB,MAAM,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;YACjC,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO;gBAAE,SAAS;YAC7B,IAAI,GAAG;gBACL,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBACtB,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC/C,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBACtB,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC/C,KAAK,EAAE,EAAE;aACV,CAAC;YACF,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACzB,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC;YAC1B,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC5B,CAAC;aAAM,IAAI,sBAAsB,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC;YAC5C,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,OAAO,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;gBACnD,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACtB,CAAC;YACD,MAAM,CAAC,GAAG,mBAAmB,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YAC5C,IAAI,CAAC,KAAK,WAAW,EAAE,CAAC;gBACtB,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC,OAAO;YACtC,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;gBAC7B,IAAI,CAAC,OAAO,CAAC,IAAI;oBAAE,OAAO,CAAC,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC;YACpD,CAAC;QACH,CAAC;aAAM,IAAI,sBAAsB,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC;YAC5C,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,OAAO,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;gBACnD,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACtB,CAAC;YACD,MAAM,CAAC,GAAG,mBAAmB,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YAC5C,IAAI,CAAC,KAAK,WAAW;gBAAE,OAAO,CAAC,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;QACnD,CAAC;aAAM,IAAI,GAAG,CAAC,UAAU,CAAC,eAAe,CAAC,EAAE,CAAC;YAC3C,IAAI,OAAO;gBAAE,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC;QAC3C,CAAC;aAAM,IAAI,GAAG,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;YAC1C,IAAI,OAAO;gBAAE,OAAO,CAAC,OAAO,GAAG,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC;QAClF,CAAC;aAAM,IAAI,GAAG,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;YACxC,IAAI,OAAO;gBAAE,OAAO,CAAC,IAAI,GAAG,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC;QAC7E,CAAC;aAAM,IAAI,GAAG,CAAC,UAAU,CAAC,eAAe,CAAC,IAAI,GAAG,KAAK,kBAAkB,EAAE,CAAC;YACzE,IAAI,OAAO;gBAAE,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;QACvC,CAAC;IACH,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,CAAC;AACnB,CAAC"}
|
package/dist/doctor.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* doctor 体检:把「配置/环境是否就绪」拆成一组可单独测试的检查项。
|
|
3
|
+
* CLI 的 doctor 命令负责收集环境事实(Node 版本、git 状态、配置加载、
|
|
4
|
+
* AI 供应商连通性),本模块只做判定与渲染。
|
|
5
|
+
*/
|
|
6
|
+
import type { BountyConfig } from './config.js';
|
|
7
|
+
import type { LoadedProvider } from './llm/provider.js';
|
|
8
|
+
export interface Check {
|
|
9
|
+
name: string;
|
|
10
|
+
status: 'ok' | 'warn' | 'fail';
|
|
11
|
+
detail: string;
|
|
12
|
+
}
|
|
13
|
+
export type ProbeResult = {
|
|
14
|
+
status: 'ok' | 'warn' | 'fail';
|
|
15
|
+
detail: string;
|
|
16
|
+
};
|
|
17
|
+
/** Node 版本:bounty-guard 要求 ≥ 20 */
|
|
18
|
+
export declare function checkNodeVersion(version: string, minimum?: number): Check;
|
|
19
|
+
export declare function checkGit(gitVersion: string | null, isRepo: boolean): Check;
|
|
20
|
+
export declare function checkConfig(config: BountyConfig): Check;
|
|
21
|
+
export declare function checkConfigError(err: unknown): Check;
|
|
22
|
+
/** AI 复核检查:llm 模式探测连通性;降级/mock 分别给出对应说明 */
|
|
23
|
+
export declare function checkAi(loaded: LoadedProvider, probe: (baseUrl: string, apiKey: string) => Promise<ProbeResult>): Promise<Check>;
|
|
24
|
+
export declare function renderChecks(checks: Check[]): string;
|
|
25
|
+
export declare function hasFailure(checks: Check[]): boolean;
|
package/dist/doctor.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** Node 版本:bounty-guard 要求 ≥ 20 */
|
|
2
|
+
export function checkNodeVersion(version, minimum = 20) {
|
|
3
|
+
const major = Number(version.split('.')[0]);
|
|
4
|
+
if (Number.isNaN(major)) {
|
|
5
|
+
return { name: 'Node 版本', status: 'warn', detail: `无法解析版本号:${version}` };
|
|
6
|
+
}
|
|
7
|
+
return major >= minimum
|
|
8
|
+
? { name: 'Node 版本', status: 'ok', detail: `v${version}(要求 ≥ v${minimum})` }
|
|
9
|
+
: { name: 'Node 版本', status: 'fail', detail: `v${version} 低于要求的 v${minimum},请升级 Node` };
|
|
10
|
+
}
|
|
11
|
+
export function checkGit(gitVersion, isRepo) {
|
|
12
|
+
if (gitVersion === null)
|
|
13
|
+
return { name: 'Git', status: 'fail', detail: 'git 命令不可用' };
|
|
14
|
+
if (!isRepo) {
|
|
15
|
+
return {
|
|
16
|
+
name: 'Git',
|
|
17
|
+
status: 'warn',
|
|
18
|
+
detail: `${gitVersion};当前目录不在 git 仓库内,scan --git 不可用`
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
return { name: 'Git', status: 'ok', detail: `${gitVersion};当前为 git 仓库` };
|
|
22
|
+
}
|
|
23
|
+
export function checkConfig(config) {
|
|
24
|
+
return {
|
|
25
|
+
name: '配置文件',
|
|
26
|
+
status: 'ok',
|
|
27
|
+
detail: `failOn=${config.failOn} · scanTests=${config.scanTests} · ignore ${config.ignore.length} 条 · 禁用规则 ${config.disabledRules?.length ?? 0} 条`
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export function checkConfigError(err) {
|
|
31
|
+
return {
|
|
32
|
+
name: '配置文件',
|
|
33
|
+
status: 'fail',
|
|
34
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/** AI 复核检查:llm 模式探测连通性;降级/mock 分别给出对应说明 */
|
|
38
|
+
export async function checkAi(loaded, probe) {
|
|
39
|
+
if (loaded.mode === 'llm') {
|
|
40
|
+
const result = await probe(loaded.baseUrl ?? '', loaded.apiKey ?? '');
|
|
41
|
+
return { name: 'AI 复核', ...result };
|
|
42
|
+
}
|
|
43
|
+
if (loaded.degraded) {
|
|
44
|
+
return { name: 'AI 复核', status: 'warn', detail: loaded.reason ?? '未启用' };
|
|
45
|
+
}
|
|
46
|
+
return { name: 'AI 复核', status: 'ok', detail: '演练模式(MockProvider),复核流水线可用' };
|
|
47
|
+
}
|
|
48
|
+
const ICONS = { ok: '✓', warn: '⚠', fail: '✗' };
|
|
49
|
+
export function renderChecks(checks) {
|
|
50
|
+
return checks.map((c) => `${ICONS[c.status]} ${c.name}:${c.detail}`).join('\n');
|
|
51
|
+
}
|
|
52
|
+
export function hasFailure(checks) {
|
|
53
|
+
return checks.some((c) => c.status === 'fail');
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=doctor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type ParsedDiff } from './diff.js';
|
|
2
|
+
/** 以子进程运行 git(参数列表形式,不经 shell),非零退出时以 stderr 文本抛错 */
|
|
3
|
+
export declare function runGit(args: string[], cwd: string): Promise<string>;
|
|
4
|
+
/** 组装「未提交变更」:git diff HEAD(首次提交前回退索引对比)+ 未跟踪文件 */
|
|
5
|
+
export declare function collectGitChanges(cwd: string): Promise<ParsedDiff>;
|
|
6
|
+
/** 组装「已暂存变更」:git diff --cached(pre-commit 场景,不含未跟踪文件) */
|
|
7
|
+
export declare function collectStagedChanges(cwd: string): Promise<ParsedDiff>;
|
package/dist/git-scan.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* git 扫描来源:把「未提交 / 已暂存变更」组装成 ParsedDiff。
|
|
3
|
+
* CLI、MCP 服务器等形态共用的采集层。
|
|
4
|
+
*/
|
|
5
|
+
import { spawn } from 'node:child_process';
|
|
6
|
+
import { readFileSync } from 'node:fs';
|
|
7
|
+
import { parseDiff, unquoteGitPath } from './diff.js';
|
|
8
|
+
/** 以子进程运行 git(参数列表形式,不经 shell),非零退出时以 stderr 文本抛错 */
|
|
9
|
+
export function runGit(args, cwd) {
|
|
10
|
+
return new Promise((resolvePromise, reject) => {
|
|
11
|
+
const child = spawn('git', args, { cwd });
|
|
12
|
+
let stdout = '';
|
|
13
|
+
let stderr = '';
|
|
14
|
+
child.stdout.on('data', (chunk) => {
|
|
15
|
+
stdout += chunk;
|
|
16
|
+
});
|
|
17
|
+
child.stderr.on('data', (chunk) => {
|
|
18
|
+
stderr += chunk;
|
|
19
|
+
});
|
|
20
|
+
child.on('error', reject);
|
|
21
|
+
child.on('close', (code) => {
|
|
22
|
+
if (code === 0)
|
|
23
|
+
resolvePromise(stdout);
|
|
24
|
+
else
|
|
25
|
+
reject(new Error(stderr.trim() || `git 以退出码 ${code ?? '未知'} 异常结束`));
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/** 未跟踪文件 → 整文件皆视为新增行 */
|
|
30
|
+
function fileToParsedFile(path, content) {
|
|
31
|
+
const lines = content.split(/\r?\n/);
|
|
32
|
+
if (lines.length > 0 && lines[lines.length - 1] === '')
|
|
33
|
+
lines.pop();
|
|
34
|
+
const diffLines = lines.map((content, i) => ({ type: 'add', content, newLine: i + 1 }));
|
|
35
|
+
return {
|
|
36
|
+
path,
|
|
37
|
+
isBinary: false,
|
|
38
|
+
hunks: [{ oldStart: 0, oldLines: 0, newStart: 1, newLines: diffLines.length, lines: diffLines }]
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** 组装「未提交变更」:git diff HEAD(首次提交前回退索引对比)+ 未跟踪文件 */
|
|
42
|
+
export async function collectGitChanges(cwd) {
|
|
43
|
+
let text;
|
|
44
|
+
try {
|
|
45
|
+
text = await runGit(['diff', 'HEAD'], cwd);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// 尚无首次提交时 HEAD 不存在,改用索引对比(新仓库的变更通常已暂存)
|
|
49
|
+
text = await runGit(['diff', '--cached'], cwd);
|
|
50
|
+
}
|
|
51
|
+
const parsed = parseDiff(text);
|
|
52
|
+
const status = await runGit(['status', '--porcelain', '-uall'], cwd);
|
|
53
|
+
for (const line of status.split(/\r?\n/)) {
|
|
54
|
+
if (!line.startsWith('?? '))
|
|
55
|
+
continue;
|
|
56
|
+
const file = unquoteGitPath(line.slice(3));
|
|
57
|
+
if (!file || file.endsWith('/'))
|
|
58
|
+
continue;
|
|
59
|
+
let content;
|
|
60
|
+
try {
|
|
61
|
+
content = readFileSync(`${cwd}/${file}`, 'utf8');
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
continue; // 读不到(权限/编码)就放过,宁漏报不崩溃
|
|
65
|
+
}
|
|
66
|
+
if (content.includes('\u0000'))
|
|
67
|
+
continue; // 二进制内容跳过
|
|
68
|
+
parsed.files.push(fileToParsedFile(file, content));
|
|
69
|
+
}
|
|
70
|
+
return parsed;
|
|
71
|
+
}
|
|
72
|
+
/** 组装「已暂存变更」:git diff --cached(pre-commit 场景,不含未跟踪文件) */
|
|
73
|
+
export async function collectStagedChanges(cwd) {
|
|
74
|
+
return parseDiff(await runGit(['diff', '--cached'], cwd));
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=git-scan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-scan.js","sourceRoot":"","sources":["../src/git-scan.ts"],"names":[],"mappings":"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"}
|
package/dist/github.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Finding } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* GitHub REST 交互(零依赖 fetch 封装):PR diff 读取、粘性评论、
|
|
4
|
+
* Job Summary 暂存与 Actions 告警标注。fetch 可注入以便测试。
|
|
5
|
+
* 约束:环境变量提供的路径一律不做 fs 读写(防路径注入面);
|
|
6
|
+
* 所有请求带确定性超时——不假设注入的 fetch 遵守 abort signal。
|
|
7
|
+
*/
|
|
8
|
+
export interface GithubContext {
|
|
9
|
+
token: string;
|
|
10
|
+
/** 仓库 slug:owner/name */
|
|
11
|
+
repo: string;
|
|
12
|
+
/** fetch 注入点(测试用);缺省用全局 fetch */
|
|
13
|
+
fetchImpl?: typeof fetch;
|
|
14
|
+
/** 单次 API 请求超时(毫秒),默认 30 秒 */
|
|
15
|
+
timeoutMs?: number;
|
|
16
|
+
}
|
|
17
|
+
/** 校验并拆分 owner/name——URL 只允许由合法标识构成,杜绝路径被拼出预期范围 */
|
|
18
|
+
export declare function parseRepoSlug(repo: string): {
|
|
19
|
+
owner: string;
|
|
20
|
+
name: string;
|
|
21
|
+
};
|
|
22
|
+
/** 读取 PR 的 unified diff(REST 原生支持,无需 git 历史) */
|
|
23
|
+
export declare function fetchPrDiff(ctx: GithubContext, prNumber: number): Promise<string>;
|
|
24
|
+
/** 粘性评论:跨页查找带标记的历史评论,找到则更新,找不到才新建(重复扫描不刷屏) */
|
|
25
|
+
export declare function upsertStickyComment(ctx: GithubContext, prNumber: number, body: string, marker?: string): Promise<'created' | 'updated'>;
|
|
26
|
+
/** 生成 Actions 告警标注(高危/中危 error,低危及以下 warning)。
|
|
27
|
+
* 超出上限时保留前 9 条、第 10 条用作汇总,确保溢出信息可见而不是被吞掉。 */
|
|
28
|
+
export declare function toAnnotations(findings: Finding[]): string[];
|
|
29
|
+
/** Job Summary 暂存文件名(工作目录下固定文件,由 Action 步骤追加到 $GITHUB_STEP_SUMMARY) */
|
|
30
|
+
export declare const SUMMARY_FILE = ".bounty-guard-summary.md";
|
|
31
|
+
/** 将 Markdown 报告写入工作目录的固定暂存文件,返回绝对路径 */
|
|
32
|
+
export declare function writeSummaryFile(markdown: string): string;
|
|
33
|
+
/** 解析 PR 编号:显式参数 → GITHUB_PR_NUMBER → GITHUB_REF。
|
|
34
|
+
* 注:不读取 GITHUB_EVENT_PATH 事件文件(env 提供的路径一律不做 fs 操作),
|
|
35
|
+
* Actions 的 pull_request 事件里 GITHUB_REF 必为 refs/pull/<n>/merge,已足够。 */
|
|
36
|
+
export declare function resolvePrNumber(explicit?: string): number | undefined;
|
package/dist/github.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { writeFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { COMMENT_MARKER, LABELS } from './report.js';
|
|
4
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
5
|
+
/** 带确定性超时的请求:超时让调用方必然收敛,而不是无限等待 */
|
|
6
|
+
async function request(ctx, url, init = {}) {
|
|
7
|
+
const timeoutMs = ctx.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
8
|
+
const controller = new AbortController();
|
|
9
|
+
let timer;
|
|
10
|
+
const timeout = new Promise((_, reject) => {
|
|
11
|
+
timer = setTimeout(() => {
|
|
12
|
+
controller.abort();
|
|
13
|
+
reject(new Error(`GitHub API 请求超时(${timeoutMs}ms)`));
|
|
14
|
+
}, timeoutMs);
|
|
15
|
+
});
|
|
16
|
+
try {
|
|
17
|
+
const pending = (ctx.fetchImpl ?? fetch)(url, { ...init, signal: controller.signal });
|
|
18
|
+
pending.catch(() => { }); // 防 timeout 赢得竞速后出现未处理拒绝
|
|
19
|
+
return (await Promise.race([pending, timeout]));
|
|
20
|
+
}
|
|
21
|
+
finally {
|
|
22
|
+
if (timer)
|
|
23
|
+
clearTimeout(timer);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** 非 2xx 时抛出带响应体摘要的可读错误;403 附常见原因提示 */
|
|
27
|
+
async function assertOk(path, res) {
|
|
28
|
+
if (res.ok)
|
|
29
|
+
return;
|
|
30
|
+
let body = '';
|
|
31
|
+
try {
|
|
32
|
+
body = await res.text();
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
body = '';
|
|
36
|
+
}
|
|
37
|
+
const hint = res.status === 403 ? '(常见原因:令牌权限不足或触发限流)' : '';
|
|
38
|
+
throw new Error(`GitHub API ${path} 失败:HTTP ${res.status}${hint}${body ? `:${body.slice(0, 200)}` : ''}`);
|
|
39
|
+
}
|
|
40
|
+
async function githubJson(ctx, url, init = {}) {
|
|
41
|
+
const res = await request(ctx, url, init);
|
|
42
|
+
const path = url.replace('https://api.github.com', '');
|
|
43
|
+
await assertOk(path, res);
|
|
44
|
+
return (await res.json());
|
|
45
|
+
}
|
|
46
|
+
/** 校验并拆分 owner/name——URL 只允许由合法标识构成,杜绝路径被拼出预期范围 */
|
|
47
|
+
export function parseRepoSlug(repo) {
|
|
48
|
+
const m = repo.match(/^([A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?)\/([A-Za-z0-9._-]+)$/);
|
|
49
|
+
if (!m)
|
|
50
|
+
throw new Error(`仓库标识无效:${repo}(应为 owner/name 形式)`);
|
|
51
|
+
return { owner: m[1], name: m[2] };
|
|
52
|
+
}
|
|
53
|
+
function assertPrNumber(prNumber) {
|
|
54
|
+
if (!Number.isInteger(prNumber) || prNumber <= 0)
|
|
55
|
+
throw new Error(`PR 编号无效:${prNumber}`);
|
|
56
|
+
return prNumber;
|
|
57
|
+
}
|
|
58
|
+
/** 仓库级 API URL:标识经 parseRepoSlug 校验 + encodeURIComponent 编码,
|
|
59
|
+
* 且以固定基底构造 URL 对象——host 结构性不可变,请求目标无法被输入改变 */
|
|
60
|
+
function repoApiUrl(ctx, sub) {
|
|
61
|
+
const { owner, name } = parseRepoSlug(ctx.repo);
|
|
62
|
+
return new URL(`repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}${sub}`, 'https://api.github.com').toString();
|
|
63
|
+
}
|
|
64
|
+
/** 读取 PR 的 unified diff(REST 原生支持,无需 git 历史) */
|
|
65
|
+
export async function fetchPrDiff(ctx, prNumber) {
|
|
66
|
+
const url = repoApiUrl(ctx, `/pulls/${assertPrNumber(prNumber)}`);
|
|
67
|
+
const res = await request(ctx, url, {
|
|
68
|
+
headers: { Authorization: `Bearer ${ctx.token}`, Accept: 'application/vnd.github.diff' }
|
|
69
|
+
});
|
|
70
|
+
await assertOk(url.replace('https://api.github.com', ''), res);
|
|
71
|
+
return res.text();
|
|
72
|
+
}
|
|
73
|
+
/** 粘性评论:跨页查找带标记的历史评论,找到则更新,找不到才新建(重复扫描不刷屏) */
|
|
74
|
+
export async function upsertStickyComment(ctx, prNumber, body, marker = COMMENT_MARKER) {
|
|
75
|
+
const pr = assertPrNumber(prNumber);
|
|
76
|
+
const base = repoApiUrl(ctx, `/issues/${pr}`);
|
|
77
|
+
let page = 1;
|
|
78
|
+
let existing;
|
|
79
|
+
for (;;) {
|
|
80
|
+
const comments = await githubJson(ctx, `${base}/comments?per_page=100&page=${page}`);
|
|
81
|
+
if (!Array.isArray(comments) || comments.length === 0)
|
|
82
|
+
break;
|
|
83
|
+
existing = comments.find((c) => typeof c.body === 'string' && c.body.includes(marker));
|
|
84
|
+
if (existing || comments.length < 100 || page >= 10)
|
|
85
|
+
break;
|
|
86
|
+
page++;
|
|
87
|
+
}
|
|
88
|
+
if (existing) {
|
|
89
|
+
await githubJson(ctx, `${base}/comments/${existing.id}`, {
|
|
90
|
+
method: 'PATCH',
|
|
91
|
+
body: JSON.stringify({ body })
|
|
92
|
+
});
|
|
93
|
+
return 'updated';
|
|
94
|
+
}
|
|
95
|
+
await githubJson(ctx, `${base}/comments`, {
|
|
96
|
+
method: 'POST',
|
|
97
|
+
body: JSON.stringify({ body })
|
|
98
|
+
});
|
|
99
|
+
return 'created';
|
|
100
|
+
}
|
|
101
|
+
/** 每步标注上限:GitHub 仅接受 10 条 error + 10 条 warning,超出部分被静默丢弃 */
|
|
102
|
+
const ANNOTATION_CAP = 10;
|
|
103
|
+
/** 生成 Actions 告警标注(高危/中危 error,低危及以下 warning)。
|
|
104
|
+
* 超出上限时保留前 9 条、第 10 条用作汇总,确保溢出信息可见而不是被吞掉。 */
|
|
105
|
+
export function toAnnotations(findings) {
|
|
106
|
+
const lines = [];
|
|
107
|
+
const emit = (list, level, scope) => {
|
|
108
|
+
const head = list.slice(0, ANNOTATION_CAP - 1);
|
|
109
|
+
const dropped = list.length - head.length;
|
|
110
|
+
for (const f of head) {
|
|
111
|
+
lines.push(`::${level} file=${f.file},line=${f.line}::[${LABELS[f.severity]}] ${f.ruleId}:${f.message}`);
|
|
112
|
+
}
|
|
113
|
+
if (dropped > 0) {
|
|
114
|
+
lines.push(`::${level} title=bounty-guard::另有 ${dropped} 条${scope}告警未展示,完整列表见 PR 评论`);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
emit(findings.filter((f) => f.severity === 'high' || f.severity === 'medium'), 'error', '高危/中危');
|
|
118
|
+
emit(findings.filter((f) => f.severity === 'low' || f.severity === 'info'), 'warning', '低危/提示');
|
|
119
|
+
return lines;
|
|
120
|
+
}
|
|
121
|
+
/** Job Summary 暂存文件名(工作目录下固定文件,由 Action 步骤追加到 $GITHUB_STEP_SUMMARY) */
|
|
122
|
+
export const SUMMARY_FILE = '.bounty-guard-summary.md';
|
|
123
|
+
/** 将 Markdown 报告写入工作目录的固定暂存文件,返回绝对路径 */
|
|
124
|
+
export function writeSummaryFile(markdown) {
|
|
125
|
+
writeFileSync(SUMMARY_FILE, `${markdown}\n`);
|
|
126
|
+
return resolve(SUMMARY_FILE);
|
|
127
|
+
}
|
|
128
|
+
/** 解析 PR 编号:显式参数 → GITHUB_PR_NUMBER → GITHUB_REF。
|
|
129
|
+
* 注:不读取 GITHUB_EVENT_PATH 事件文件(env 提供的路径一律不做 fs 操作),
|
|
130
|
+
* Actions 的 pull_request 事件里 GITHUB_REF 必为 refs/pull/<n>/merge,已足够。 */
|
|
131
|
+
export function resolvePrNumber(explicit) {
|
|
132
|
+
if (explicit) {
|
|
133
|
+
const n = Number(explicit);
|
|
134
|
+
if (Number.isInteger(n) && n > 0)
|
|
135
|
+
return n;
|
|
136
|
+
}
|
|
137
|
+
const envNumber = Number(process.env.GITHUB_PR_NUMBER);
|
|
138
|
+
if (Number.isInteger(envNumber) && envNumber > 0)
|
|
139
|
+
return envNumber;
|
|
140
|
+
const fromRef = (process.env.GITHUB_REF ?? '').match(/^refs\/pull\/(\d+)\/merge$/);
|
|
141
|
+
if (fromRef)
|
|
142
|
+
return Number(fromRef[1]);
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=github.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github.js","sourceRoot":"","sources":["../src/github.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAoBrD,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAElC,mCAAmC;AACnC,KAAK,UAAU,OAAO,CAAC,GAAkB,EAAE,GAAW,EAAE,OAAoB,EAAE;IAC5E,MAAM,SAAS,GAAG,GAAG,CAAC,SAAS,IAAI,kBAAkB,CAAC;IACtD,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,IAAI,KAAgD,CAAC;IACrD,MAAM,OAAO,GAAG,IAAI,OAAO,CAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE;QAC/C,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YACtB,UAAU,CAAC,KAAK,EAAE,CAAC;YACnB,MAAM,CAAC,IAAI,KAAK,CAAC,mBAAmB,SAAS,KAAK,CAAC,CAAC,CAAC;QACvD,CAAC,EAAE,SAAS,CAAC,CAAC;IAChB,CAAC,CAAC,CAAC;IACH,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,SAAS,IAAI,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;QACtF,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC,CAAC,yBAAyB;QAClD,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAa,CAAC;IAC9D,CAAC;YAAS,CAAC;QACT,IAAI,KAAK;YAAE,YAAY,CAAC,KAAK,CAAC,CAAC;IACjC,CAAC;AACH,CAAC;AAED,uCAAuC;AACvC,KAAK,UAAU,QAAQ,CAAC,IAAY,EAAE,GAAa;IACjD,IAAI,GAAG,CAAC,EAAE;QAAE,OAAO;IACnB,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,IAAI,GAAG,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5D,MAAM,IAAI,KAAK,CAAC,cAAc,IAAI,YAAY,GAAG,CAAC,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAC5G,CAAC;AAED,KAAK,UAAU,UAAU,CAAI,GAAkB,EAAE,GAAW,EAAE,OAAoB,EAAE;IAClF,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IAC1C,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,wBAAwB,EAAE,EAAE,CAAC,CAAC;IACvD,MAAM,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC1B,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAM,CAAC;AACjC,CAAC;AAED,mDAAmD;AACnD,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,iEAAiE,CAAC,CAAC;IACxF,IAAI,CAAC,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,UAAU,IAAI,oBAAoB,CAAC,CAAC;IAC5D,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AACrC,CAAC;AAED,SAAS,cAAc,CAAC,QAAgB;IACtC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,QAAQ,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,WAAW,QAAQ,EAAE,CAAC,CAAC;IACzF,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;8CAC8C;AAC9C,SAAS,UAAU,CAAC,GAAkB,EAAE,GAAW;IACjD,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChD,OAAO,IAAI,GAAG,CACZ,SAAS,kBAAkB,CAAC,KAAK,CAAC,IAAI,kBAAkB,CAAC,IAAI,CAAC,GAAG,GAAG,EAAE,EACtE,wBAAwB,CACzB,CAAC,QAAQ,EAAE,CAAC;AACf,CAAC;AAED,gDAAgD;AAChD,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,GAAkB,EAAE,QAAgB;IACpE,MAAM,GAAG,GAAG,UAAU,CAAC,GAAG,EAAE,UAAU,cAAc,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IAClE,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE;QAClC,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,GAAG,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,6BAA6B,EAAE;KACzF,CAAC,CAAC;IACH,MAAM,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,wBAAwB,EAAE,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;IAC/D,OAAO,GAAG,CAAC,IAAI,EAAE,CAAC;AACpB,CAAC;AAOD,8CAA8C;AAC9C,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,GAAkB,EAClB,QAAgB,EAChB,IAAY,EACZ,SAAiB,cAAc;IAE/B,MAAM,EAAE,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;IACpC,MAAM,IAAI,GAAG,UAAU,CAAC,GAAG,EAAE,WAAW,EAAE,EAAE,CAAC,CAAC;IAC9C,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,QAAkC,CAAC;IACvC,SAAS,CAAC;QACR,MAAM,QAAQ,GAAG,MAAM,UAAU,CAC/B,GAAG,EACH,GAAG,IAAI,+BAA+B,IAAI,EAAE,CAC7C,CAAC;QACF,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;YAAE,MAAM;QAC7D,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;QACvF,IAAI,QAAQ,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,IAAI,IAAI,IAAI,EAAE;YAAE,MAAM;QAC3D,IAAI,EAAE,CAAC;IACT,CAAC;IACD,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,UAAU,CAAC,GAAG,EAAE,GAAG,IAAI,aAAa,QAAQ,CAAC,EAAE,EAAE,EAAE;YACvD,MAAM,EAAE,OAAO;YACf,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,CAAC;SAC/B,CAAC,CAAC;QACH,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,UAAU,CAAC,GAAG,EAAE,GAAG,IAAI,WAAW,EAAE;QACxC,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,CAAC;KAC/B,CAAC,CAAC;IACH,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,4DAA4D;AAC5D,MAAM,cAAc,GAAG,EAAE,CAAC;AAE1B;6CAC6C;AAC7C,MAAM,UAAU,aAAa,CAAC,QAAmB;IAC/C,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,IAAI,GAAG,CAAC,IAAe,EAAE,KAA0B,EAAE,KAAa,EAAE,EAAE;QAC1E,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,cAAc,GAAG,CAAC,CAAC,CAAC;QAC/C,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1C,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACrB,KAAK,CAAC,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,IAAI,MAAM,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QAC3G,CAAC;QACD,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;YAChB,KAAK,CAAC,IAAI,CAAC,KAAK,KAAK,2BAA2B,OAAO,KAAK,KAAK,mBAAmB,CAAC,CAAC;QACxF,CAAC;IACH,CAAC,CAAC;IACF,IAAI,CACF,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,MAAM,IAAI,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,EACxE,OAAO,EACP,OAAO,CACR,CAAC;IACF,IAAI,CACF,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,KAAK,IAAI,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC,EACrE,SAAS,EACT,OAAO,CACR,CAAC;IACF,OAAO,KAAK,CAAC;AACf,CAAC;AAED,uEAAuE;AACvE,MAAM,CAAC,MAAM,YAAY,GAAG,0BAA0B,CAAC;AAEvD,wCAAwC;AACxC,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAC/C,aAAa,CAAC,YAAY,EAAE,GAAG,QAAQ,IAAI,CAAC,CAAC;IAC7C,OAAO,OAAO,CAAC,YAAY,CAAC,CAAC;AAC/B,CAAC;AAED;;uEAEuE;AACvE,MAAM,UAAU,eAAe,CAAC,QAAiB;IAC/C,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC3B,IAAI,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;YAAE,OAAO,CAAC,CAAC;IAC7C,CAAC;IACD,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IACvD,IAAI,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,SAAS,GAAG,CAAC;QAAE,OAAO,SAAS,CAAC;IACnE,MAAM,OAAO,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC;IACnF,IAAI,OAAO;QAAE,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IACvC,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
package/dist/glob.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 极简 glob 匹配(零依赖),仅服务于 .bountyrc.json 的 ignore 路径过滤。
|
|
3
|
+
* 支持:`**` 跨目录段、`*` 单段内任意字符、`?` 单段内单个字符。
|
|
4
|
+
* 语义与 .gitignore 有意不同:模式是整体锚定的,`node_modules/**`
|
|
5
|
+
* 不会命中 `src/node_modules/x.js`(可预测性优先)。
|
|
6
|
+
*/
|
|
7
|
+
/** path 是否命中任意一个 glob 模式(路径分隔符统一按 / 处理) */
|
|
8
|
+
export declare function matchGlob(path: string, patterns: string[]): boolean;
|
package/dist/glob.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 极简 glob 匹配(零依赖),仅服务于 .bountyrc.json 的 ignore 路径过滤。
|
|
3
|
+
* 支持:`**` 跨目录段、`*` 单段内任意字符、`?` 单段内单个字符。
|
|
4
|
+
* 语义与 .gitignore 有意不同:模式是整体锚定的,`node_modules/**`
|
|
5
|
+
* 不会命中 `src/node_modules/x.js`(可预测性优先)。
|
|
6
|
+
*/
|
|
7
|
+
/** 把不含 glob 元字符的片段转成正则安全文本 */
|
|
8
|
+
function escapeLiteral(ch) {
|
|
9
|
+
return /[\\^$.*+?()[\]{}|]/.test(ch) ? `\\${ch}` : ch;
|
|
10
|
+
}
|
|
11
|
+
/** 翻译 glob 主体为正则源码(不含锚点) */
|
|
12
|
+
function translate(pattern) {
|
|
13
|
+
let out = '';
|
|
14
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
15
|
+
const ch = pattern[i];
|
|
16
|
+
if (ch === '*') {
|
|
17
|
+
if (pattern[i + 1] === '*') {
|
|
18
|
+
i++;
|
|
19
|
+
if (pattern[i + 1] === '/') {
|
|
20
|
+
i++;
|
|
21
|
+
out += '(?:.*/)?'; // '**/' 匹配零层或多层目录
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
out += '.*';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
out += '[^/]*';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
else if (ch === '?') {
|
|
32
|
+
out += '[^/]';
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
out += escapeLiteral(ch);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
/** 单个模式 → 整体锚定的正则;`dir/**` 额外兼容目录本身 */
|
|
41
|
+
function globToRegExp(pattern) {
|
|
42
|
+
const normalized = pattern.replace(/\\/g, '/');
|
|
43
|
+
const body = normalized.endsWith('/**') && normalized.length > 3
|
|
44
|
+
? translate(normalized.slice(0, -3)) + '(?:/.*)?'
|
|
45
|
+
: translate(normalized);
|
|
46
|
+
return new RegExp(`^${body}$`);
|
|
47
|
+
}
|
|
48
|
+
/** path 是否命中任意一个 glob 模式(路径分隔符统一按 / 处理) */
|
|
49
|
+
export function matchGlob(path, patterns) {
|
|
50
|
+
const normalized = path.replace(/\\/g, '/');
|
|
51
|
+
return patterns.some((pattern) => globToRegExp(pattern).test(normalized));
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=glob.js.map
|
package/dist/glob.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"glob.js","sourceRoot":"","sources":["../src/glob.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,8BAA8B;AAC9B,SAAS,aAAa,CAAC,EAAU;IAC/B,OAAO,oBAAoB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AACxD,CAAC;AAED,4BAA4B;AAC5B,SAAS,SAAS,CAAC,OAAe;IAChC,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACxC,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;QACtB,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YACf,IAAI,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;gBAC3B,CAAC,EAAE,CAAC;gBACJ,IAAI,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;oBAC3B,CAAC,EAAE,CAAC;oBACJ,GAAG,IAAI,UAAU,CAAC,CAAC,kBAAkB;gBACvC,CAAC;qBAAM,CAAC;oBACN,GAAG,IAAI,IAAI,CAAC;gBACd,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,GAAG,IAAI,OAAO,CAAC;YACjB,CAAC;QACH,CAAC;aAAM,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YACtB,GAAG,IAAI,MAAM,CAAC;QAChB,CAAC;aAAM,CAAC;YACN,GAAG,IAAI,aAAa,CAAC,EAAE,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,uCAAuC;AACvC,SAAS,YAAY,CAAC,OAAe;IACnC,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAC/C,MAAM,IAAI,GACR,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;QACjD,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,UAAU;QACjD,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;IAC5B,OAAO,IAAI,MAAM,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC;AACjC,CAAC;AAED,2CAA2C;AAC3C,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,QAAkB;IACxD,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAC5C,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;AAC5E,CAAC"}
|