prscan 1.0.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.
Files changed (63) hide show
  1. package/.vscode/launch.json +14 -0
  2. package/README.MD +32 -0
  3. package/dist/bot/lark.d.ts +2 -0
  4. package/dist/bot/lark.d.ts.map +1 -0
  5. package/dist/bot/lark.js +156 -0
  6. package/dist/bot/lark.js.map +1 -0
  7. package/dist/cli/cli.d.ts +2 -0
  8. package/dist/cli/cli.d.ts.map +1 -0
  9. package/dist/cli/cli.js +77 -0
  10. package/dist/cli/cli.js.map +1 -0
  11. package/dist/index.d.ts +2 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +46 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/report/index.d.ts +7 -0
  16. package/dist/report/index.d.ts.map +1 -0
  17. package/dist/report/index.js +45 -0
  18. package/dist/report/index.js.map +1 -0
  19. package/dist/tool/prscan.d.ts +72 -0
  20. package/dist/tool/prscan.d.ts.map +1 -0
  21. package/dist/tool/prscan.js +477 -0
  22. package/dist/tool/prscan.js.map +1 -0
  23. package/dist/util/analyze.d.ts +4 -0
  24. package/dist/util/analyze.d.ts.map +1 -0
  25. package/dist/util/analyze.js +213 -0
  26. package/dist/util/analyze.js.map +1 -0
  27. package/dist/util/archive.d.ts +34 -0
  28. package/dist/util/archive.d.ts.map +1 -0
  29. package/dist/util/archive.js +110 -0
  30. package/dist/util/archive.js.map +1 -0
  31. package/dist/util/memory-archive.d.ts +37 -0
  32. package/dist/util/memory-archive.d.ts.map +1 -0
  33. package/dist/util/memory-archive.js +128 -0
  34. package/dist/util/memory-archive.js.map +1 -0
  35. package/dist/util/npm.d.ts +46 -0
  36. package/dist/util/npm.d.ts.map +1 -0
  37. package/dist/util/npm.js +35 -0
  38. package/dist/util/npm.js.map +1 -0
  39. package/dist/util/parse.d.ts +18 -0
  40. package/dist/util/parse.d.ts.map +1 -0
  41. package/dist/util/parse.js +92 -0
  42. package/dist/util/parse.js.map +1 -0
  43. package/dist/util/proxy.d.ts +45 -0
  44. package/dist/util/proxy.d.ts.map +1 -0
  45. package/dist/util/proxy.js +143 -0
  46. package/dist/util/proxy.js.map +1 -0
  47. package/dist/util/repo.d.ts +103 -0
  48. package/dist/util/repo.d.ts.map +1 -0
  49. package/dist/util/repo.js +170 -0
  50. package/dist/util/repo.js.map +1 -0
  51. package/package.json +35 -0
  52. package/report.png +0 -0
  53. package/src/bot/lark.ts +184 -0
  54. package/src/cli/cli.ts +80 -0
  55. package/src/index.ts +67 -0
  56. package/src/report/index.ts +50 -0
  57. package/src/tool/prscan.ts +634 -0
  58. package/src/util/analyze.ts +248 -0
  59. package/src/util/memory-archive.ts +184 -0
  60. package/src/util/npm.ts +100 -0
  61. package/src/util/parse.ts +103 -0
  62. package/src/util/repo.ts +224 -0
  63. package/tsconfig.json +43 -0
@@ -0,0 +1,224 @@
1
+ import { Octokit } from "octokit";
2
+ import got from "got";
3
+
4
+ export class GitHubRepo {
5
+ private octokit: Octokit;
6
+ private maxRetries: number;
7
+
8
+ constructor(authToken?: string, maxRetries = 3) {
9
+ this.maxRetries = maxRetries;
10
+ this.octokit = new Octokit(authToken ? { auth: authToken } : {});
11
+ }
12
+
13
+ public async getPRInfo(owner: string, repo: string, pull_number: number) {
14
+ for (let i = 0; i < this.maxRetries; i++) {
15
+ try {
16
+ console.info(
17
+ `Fetching PR info ${owner}/${repo}#${pull_number}`
18
+ );
19
+ const { data } = await this.octokit.rest.pulls.get({
20
+ owner,
21
+ repo,
22
+ pull_number,
23
+ });
24
+ return data;
25
+ } catch (error) {}
26
+ }
27
+
28
+ throw new Error("读取PR信息失败");
29
+ }
30
+
31
+ public async getPRChangedFiles(
32
+ owner: string,
33
+ repo: string,
34
+ pull_number: number,
35
+ per_page = 30
36
+ ) {
37
+ const files = [];
38
+
39
+ let page = 1;
40
+ while (true) {
41
+ let data;
42
+ for (let i = 0; i < this.maxRetries; i++) {
43
+ try {
44
+ console.info(
45
+ `Fetching PR files ${owner}/${repo}#${pull_number} page ${page}`
46
+ );
47
+ data = await this.octokit.rest.pulls.listFiles({
48
+ owner,
49
+ repo,
50
+ pull_number,
51
+ per_page,
52
+ page,
53
+ });
54
+ break; // 成功获取数据,跳出重试循环
55
+ } catch (error) {}
56
+ }
57
+ if (data) {
58
+ files.push(...data.data);
59
+ page++;
60
+ if (data.data.length < per_page) {
61
+ break;
62
+ }
63
+ } else {
64
+ throw new Error("读取PR变更文件失败");
65
+ }
66
+ }
67
+
68
+ return files;
69
+ }
70
+
71
+ public async getTextFileContent(
72
+ owner: string,
73
+ repo: string,
74
+ path: string,
75
+ ref: string
76
+ ): Promise<string | null> {
77
+ // 使用多种方式获取文件内容,解决原API经常超时的问题
78
+
79
+ try {
80
+ console.info(
81
+ `Fetching ${owner}/${repo}/${path}@${ref} via Git Data API`
82
+ );
83
+
84
+ // 先获取commit的tree
85
+ const { data: commit } = await this.octokit.rest.git.getCommit({
86
+ owner,
87
+ repo,
88
+ commit_sha: ref,
89
+ });
90
+
91
+ // 递归查找文件
92
+ const blob = await this.findFileInTree(
93
+ owner,
94
+ repo,
95
+ commit.tree.sha,
96
+ path
97
+ );
98
+ if (blob) {
99
+ const { data: blobData } = await this.octokit.rest.git.getBlob({
100
+ owner,
101
+ repo,
102
+ file_sha: blob.sha!,
103
+ });
104
+
105
+ if (blobData.encoding === "base64") {
106
+ return Buffer.from(blobData.content, "base64").toString(
107
+ "utf-8"
108
+ );
109
+ }
110
+ }
111
+ } catch (error) {
112
+ console.warn(`Git Data API failed for ${path}:`, error);
113
+ }
114
+
115
+ for (let i = 0; i < this.maxRetries; i++) {
116
+ try {
117
+ console.info(
118
+ `Fetching ${owner}/${repo}/${path}@${ref} via Contents API (attempt ${
119
+ i + 1
120
+ })`
121
+ );
122
+ const { data } = await this.octokit.rest.repos.getContent({
123
+ owner,
124
+ repo,
125
+ path,
126
+ ref,
127
+ });
128
+
129
+ if ("content" in data && data.content) {
130
+ // 使用 Buffer 解码 base64 内容 (替代 atob,更可靠)
131
+ return Buffer.from(data.content, "base64").toString(
132
+ "utf-8"
133
+ );
134
+ } else if ("download_url" in data && data.download_url) {
135
+ // 文件过大时:使用 download_url 获取原始内容
136
+ const response = await got(data.download_url);
137
+ if (response.statusCode === 200) {
138
+ return response.body;
139
+ }
140
+ }
141
+ } catch (error) {
142
+ console.warn(
143
+ `Contents API attempt ${i + 1} failed for ${path}:`,
144
+ error
145
+ );
146
+ if (i === this.maxRetries - 1) {
147
+ // 最后一次重试失败,尝试其他方法
148
+ break;
149
+ }
150
+ // 等待一段时间再重试
151
+ await new Promise((resolve) =>
152
+ setTimeout(resolve, 200 * (i + 1))
153
+ );
154
+ }
155
+ }
156
+
157
+ try {
158
+ console.info(
159
+ `Fetching ${owner}/${repo}/${path}@${ref} via raw URL`
160
+ );
161
+ const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${path}`;
162
+ const response = await got(rawUrl);
163
+ if (response.statusCode === 200) {
164
+ return response.body;
165
+ }
166
+ } catch (error) {
167
+ console.warn(`Raw URL failed for ${path}:`, error);
168
+ }
169
+
170
+ console.error(
171
+ `All methods failed to fetch ${owner}/${repo}/${path}@${ref}`
172
+ );
173
+ return null;
174
+ }
175
+
176
+ /**
177
+ * 在Git树中递归查找文件
178
+ */
179
+ private async findFileInTree(
180
+ owner: string,
181
+ repo: string,
182
+ treeSha: string,
183
+ filePath: string
184
+ ): Promise<{ sha: string } | null> {
185
+ const pathParts = filePath.split("/");
186
+ let currentTreeSha = treeSha;
187
+ let currentPath = "";
188
+
189
+ for (let i = 0; i < pathParts.length; i++) {
190
+ const part = pathParts[i]!;
191
+ currentPath = currentPath ? `${currentPath}/${part}` : part;
192
+
193
+ try {
194
+ const { data: tree } = await this.octokit.rest.git.getTree({
195
+ owner,
196
+ repo,
197
+ tree_sha: currentTreeSha,
198
+ });
199
+
200
+ const item = tree.tree.find((item) => item.path === part);
201
+ if (!item) {
202
+ return null;
203
+ }
204
+
205
+ if (i === pathParts.length - 1) {
206
+ // 这是最后一个部分,应该是文件
207
+ return item.type === "blob" ? { sha: item.sha! } : null;
208
+ } else {
209
+ // 这是一个目录,继续递归
210
+ if (item.type === "tree") {
211
+ currentTreeSha = item.sha!;
212
+ } else {
213
+ return null;
214
+ }
215
+ }
216
+ } catch (error) {
217
+ console.warn(`Failed to get tree ${currentTreeSha}:`, error);
218
+ return null;
219
+ }
220
+ }
221
+
222
+ return null;
223
+ }
224
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ // Visit https://aka.ms/tsconfig to read more about this file
3
+ "compilerOptions": {
4
+ // File Layout
5
+ "rootDir": "./src",
6
+ "outDir": "./dist",
7
+
8
+ // Environment Settings
9
+ // See also https://aka.ms/tsconfig/module
10
+ "module": "nodenext",
11
+ "target": "esnext",
12
+ "lib": ["esnext"],
13
+ // For nodejs with fetch support:
14
+ "types": ["node"],
15
+ // and npm install -D @types/node
16
+
17
+ // Other Outputs
18
+ "sourceMap": true,
19
+ "declaration": true,
20
+ "declarationMap": true,
21
+
22
+ // Stricter Typechecking Options
23
+ "noUncheckedIndexedAccess": true,
24
+ "exactOptionalPropertyTypes": true,
25
+
26
+ // Style Options
27
+ // "noImplicitReturns": true,
28
+ // "noImplicitOverride": true,
29
+ // "noUnusedLocals": true,
30
+ // "noUnusedParameters": true,
31
+ // "noFallthroughCasesInSwitch": true,
32
+ // "noPropertyAccessFromIndexSignature": true,
33
+
34
+ // Recommended Options
35
+ "strict": true,
36
+ "jsx": "react-jsx",
37
+ "verbatimModuleSyntax": true,
38
+ "isolatedModules": true,
39
+ "noUncheckedSideEffectImports": true,
40
+ "moduleDetection": "force",
41
+ "skipLibCheck": true,
42
+ }
43
+ }