@anren-utils/mcp-audit 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 (115) hide show
  1. package/.editorconfig +13 -0
  2. package/dist/audit/auditUtils.d.ts +12 -0
  3. package/dist/audit/auditUtils.d.ts.map +1 -0
  4. package/dist/audit/auditUtils.js +22 -0
  5. package/dist/audit/auditUtils.js.map +1 -0
  6. package/dist/audit/currentAudit.d.ts +53 -0
  7. package/dist/audit/currentAudit.d.ts.map +1 -0
  8. package/dist/audit/currentAudit.js +54 -0
  9. package/dist/audit/currentAudit.js.map +1 -0
  10. package/dist/audit/getDepChain.d.ts +16 -0
  11. package/dist/audit/getDepChain.d.ts.map +1 -0
  12. package/dist/audit/getDepChain.js +60 -0
  13. package/dist/audit/getDepChain.js.map +1 -0
  14. package/dist/audit/index.d.ts +11 -0
  15. package/dist/audit/index.d.ts.map +1 -0
  16. package/dist/audit/index.js +64 -0
  17. package/dist/audit/index.js.map +1 -0
  18. package/dist/audit/normalizeAuditResult.d.ts +13 -0
  19. package/dist/audit/normalizeAuditResult.d.ts.map +1 -0
  20. package/dist/audit/normalizeAuditResult.js +81 -0
  21. package/dist/audit/normalizeAuditResult.js.map +1 -0
  22. package/dist/audit/remoteAudit.d.ts +3 -0
  23. package/dist/audit/remoteAudit.d.ts.map +1 -0
  24. package/dist/audit/remoteAudit.js +24 -0
  25. package/dist/audit/remoteAudit.js.map +1 -0
  26. package/dist/generateLock/index.d.ts +17 -0
  27. package/dist/generateLock/index.d.ts.map +1 -0
  28. package/dist/generateLock/index.js +141 -0
  29. package/dist/generateLock/index.js.map +1 -0
  30. package/dist/index.d.ts +7 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +48 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/mcpServer.d.ts +2 -0
  35. package/dist/mcpServer.d.ts.map +1 -0
  36. package/dist/mcpServer.js +34 -0
  37. package/dist/mcpServer.js.map +1 -0
  38. package/dist/parseProject/detectPackageManager.d.ts +8 -0
  39. package/dist/parseProject/detectPackageManager.d.ts.map +1 -0
  40. package/dist/parseProject/detectPackageManager.js +22 -0
  41. package/dist/parseProject/detectPackageManager.js.map +1 -0
  42. package/dist/parseProject/index.d.ts +11 -0
  43. package/dist/parseProject/index.d.ts.map +1 -0
  44. package/dist/parseProject/index.js +20 -0
  45. package/dist/parseProject/index.js.map +1 -0
  46. package/dist/parseProject/parseLocalProject.d.ts +17 -0
  47. package/dist/parseProject/parseLocalProject.d.ts.map +1 -0
  48. package/dist/parseProject/parseLocalProject.js +28 -0
  49. package/dist/parseProject/parseLocalProject.js.map +1 -0
  50. package/dist/parseProject/parseLocalWorkspace.d.ts +2 -0
  51. package/dist/parseProject/parseLocalWorkspace.d.ts.map +1 -0
  52. package/dist/parseProject/parseLocalWorkspace.js +2 -0
  53. package/dist/parseProject/parseLocalWorkspace.js.map +1 -0
  54. package/dist/parseProject/parseRemoteProject.d.ts +41 -0
  55. package/dist/parseProject/parseRemoteProject.d.ts.map +1 -0
  56. package/dist/parseProject/parseRemoteProject.js +180 -0
  57. package/dist/parseProject/parseRemoteProject.js.map +1 -0
  58. package/dist/parseProject/parseRemoteWorkspace.d.ts +2 -0
  59. package/dist/parseProject/parseRemoteWorkspace.d.ts.map +1 -0
  60. package/dist/parseProject/parseRemoteWorkspace.js +2 -0
  61. package/dist/parseProject/parseRemoteWorkspace.js.map +1 -0
  62. package/dist/parseProject/parseWorkspace.d.ts +19 -0
  63. package/dist/parseProject/parseWorkspace.d.ts.map +1 -0
  64. package/dist/parseProject/parseWorkspace.js +140 -0
  65. package/dist/parseProject/parseWorkspace.js.map +1 -0
  66. package/dist/render/index.d.ts +9 -0
  67. package/dist/render/index.d.ts.map +1 -0
  68. package/dist/render/index.js +24 -0
  69. package/dist/render/index.js.map +1 -0
  70. package/dist/render/markdown.d.ts +12 -0
  71. package/dist/render/markdown.d.ts.map +1 -0
  72. package/dist/render/markdown.js +16 -0
  73. package/dist/render/markdown.js.map +1 -0
  74. package/dist/render/template/audit.ejs +30 -0
  75. package/dist/render/template/detail-item.ejs +32 -0
  76. package/dist/render/template/detail.ejs +7 -0
  77. package/dist/render/template/index.ejs +8 -0
  78. package/dist/types.d.ts +371 -0
  79. package/dist/types.d.ts.map +1 -0
  80. package/dist/types.js +2 -0
  81. package/dist/types.js.map +1 -0
  82. package/dist/utils/dirUtils.d.ts +11 -0
  83. package/dist/utils/dirUtils.d.ts.map +1 -0
  84. package/dist/utils/dirUtils.js +28 -0
  85. package/dist/utils/dirUtils.js.map +1 -0
  86. package/dist/utils/index.d.ts +34 -0
  87. package/dist/utils/index.d.ts.map +1 -0
  88. package/dist/utils/index.js +74 -0
  89. package/dist/utils/index.js.map +1 -0
  90. package/eslint.config.js +38 -0
  91. package/package.json +38 -0
  92. package/src/audit/auditUtils.ts +24 -0
  93. package/src/audit/currentAudit.ts +116 -0
  94. package/src/audit/getDepChain.ts +71 -0
  95. package/src/audit/index.ts +90 -0
  96. package/src/audit/normalizeAuditResult.ts +99 -0
  97. package/src/audit/remoteAudit.ts +26 -0
  98. package/src/generateLock/index.ts +203 -0
  99. package/src/index.ts +48 -0
  100. package/src/mcpServer.ts +43 -0
  101. package/src/parseProject/detectPackageManager.ts +24 -0
  102. package/src/parseProject/index.ts +20 -0
  103. package/src/parseProject/parseLocalProject.ts +39 -0
  104. package/src/parseProject/parseRemoteProject.ts +225 -0
  105. package/src/parseProject/parseWorkspace.ts +202 -0
  106. package/src/render/index.ts +30 -0
  107. package/src/render/markdown.ts +29 -0
  108. package/src/render/template/audit.ejs +30 -0
  109. package/src/render/template/detail-item.ejs +32 -0
  110. package/src/render/template/detail.ejs +7 -0
  111. package/src/render/template/index.ejs +8 -0
  112. package/src/types.ts +429 -0
  113. package/src/utils/dirUtils.ts +31 -0
  114. package/src/utils/index.ts +88 -0
  115. package/tsconfig.json +42 -0
@@ -0,0 +1,24 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import type { PackageManager } from "../types.js";
4
+
5
+ /**
6
+ * 检测当前项目使用的包管理器
7
+ * @param projectRoot - 项目根目录
8
+ * @returns 'npm' | 'pnpm' | 'yarn',默认返回 'npm'
9
+ */
10
+ export function detectPackageManager(projectRoot: string): PackageManager {
11
+ // 检查锁文件
12
+ if (fs.existsSync(path.join(projectRoot, "pnpm-lock.yaml"))) {
13
+ return "pnpm";
14
+ }
15
+ if (fs.existsSync(path.join(projectRoot, "package-lock.json"))) {
16
+ return "npm";
17
+ }
18
+ if (fs.existsSync(path.join(projectRoot, "yarn.lock"))) {
19
+ return "yarn";
20
+ }
21
+
22
+ // 默认返回 npm 作为兜底
23
+ return "npm";
24
+ }
@@ -0,0 +1,20 @@
1
+ import { parseLocalProject } from "./parseLocalProject.js";
2
+ import { parseRemoteProject } from "./parseRemoteProject.js";
3
+
4
+ /**
5
+ * 解析工程根目录下的package.json文件,得到文件内容
6
+ * @param {string} projectRoot 工程本地的根目录或远程仓库的URL
7
+ * @example
8
+ * parseProject('/path/to/local/project');
9
+ * parseProject('https://github.com/webpack/webpack');
10
+ * @returns {Promise<Object>} 返回解析后的package.json内容
11
+ * @throws {Error} 如果解析失败或文件不存在
12
+ */
13
+ export function parseProject(projectRoot: string) {
14
+ // 分辨是本地工程还是远程仓库
15
+ if (projectRoot.startsWith("http://") || projectRoot.startsWith("https://")) {
16
+ // 远程项目
17
+ return parseRemoteProject(projectRoot);
18
+ }
19
+ return parseLocalProject(projectRoot);
20
+ }
@@ -0,0 +1,39 @@
1
+ import path from "path";
2
+ import fs from "fs";
3
+ import type { PackageJsonInfo } from "../types.js";
4
+
5
+ /**
6
+ * @Desc: 解析本地工程根目录下的package.json文件
7
+ * @param {string} projectRoot 本地工程的根目录
8
+ * @return {Promise<Object>} 返回解析后的package.json内容
9
+ */
10
+ export async function parseLocalProject(projectRoot: string) {
11
+ const packageJsonPath = path.join(projectRoot, "package.json");
12
+ const json = await fs.promises.readFile(packageJsonPath, "utf8");
13
+ return JSON.parse(json) as PackageJsonInfo;
14
+ }
15
+
16
+ /**
17
+ * @Desc: 解析本地monorepo工程根目录下的package.json文件
18
+ * @param {string} projectRoot 本地工程的根目录
19
+ * @param {string[]} workspaceNames 工作空间名称列表,每个元素为一个子包的名称
20
+ * @return {Promise<Object[]>} 返回解析后的package.json内容列表
21
+ */
22
+ export async function parseMonorepoProject(
23
+ projectRoot: string,
24
+ workspaceNames: string[],
25
+ ) {
26
+ // 获取workspaceNames对应的package.json文件内容
27
+ const packageJsonList = await Promise.all(
28
+ workspaceNames.map(async (workspaceName) => {
29
+ const packageJsonPath = path.join(
30
+ projectRoot,
31
+ workspaceName,
32
+ "package.json",
33
+ );
34
+ const json = await fs.promises.readFile(packageJsonPath, "utf8");
35
+ return { [workspaceName]: JSON.parse(json) as Object };
36
+ }),
37
+ );
38
+ return packageJsonList;
39
+ }
@@ -0,0 +1,225 @@
1
+ import type { PackageJsonInfo } from "../types.js";
2
+
3
+ export interface RemoteProjectUrlInfo {
4
+ owner: string;
5
+ repo: string;
6
+ path: string;
7
+ platform: string;
8
+ originalUrl: string;
9
+ }
10
+
11
+ interface GitProjectInfo {
12
+ default_branch: string;
13
+ }
14
+
15
+ export interface PackageJsonUrlInfo {
16
+ branchName: string;
17
+ packageJsonUrl: string;
18
+ }
19
+
20
+ /**
21
+ * 解析 Git 仓库 URL,提取 owner、repo、platform 和后续路径
22
+ * 支持格式:
23
+ * - https://github.com/owner/repo
24
+ * - https://github.com/owner/repo/tree/branch
25
+ * - https://github.com/owner/repo/blame
26
+ * - https://gitee.com/he_kai1/creat-exam-ts
27
+ * - https://gitee.com/he_kai1/creat-exam-ts/tree/dev/
28
+ * - https://gitlab.com/owner/repo
29
+ * - https://gitlab.com/owner/repo/-/tree/branch
30
+ * - ...
31
+ * @param {string} url - Git 仓库 URL
32
+ * @returns {Object} { owner, repo, path, platform, originalUrl }
33
+ * @throws {Error} 如果 URL 格式不合法或无法解析
34
+ */
35
+ export function parseGitUrl(url: string) {
36
+ try {
37
+ const parsedUrl = new URL(url);
38
+ const hostname = parsedUrl.hostname;
39
+
40
+ // 确定平台
41
+ let platform: string;
42
+ if (hostname === "github.com" || hostname.endsWith(".github.com")) {
43
+ platform = "github";
44
+ } else if (hostname === "gitee.com" || hostname.endsWith(".gitee.com")) {
45
+ platform = "gitee";
46
+ } else if (hostname === "gitlab.com" || hostname.endsWith(".gitlab.com")) {
47
+ platform = "gitlab";
48
+ } else {
49
+ platform = "unknown";
50
+ }
51
+
52
+ // 获取路径并去除空字符串(如开头的 /)
53
+ const parts = parsedUrl.pathname.split("/").filter(Boolean);
54
+
55
+ // 至少需要 owner(项目所属者) 和 repo(项目名称) 两段
56
+ // https://github.com/anren-hk/slt-erp
57
+ if (parts.length < 2) {
58
+ throw new Error("Invalid repository URL: insufficient path segments");
59
+ }
60
+
61
+ const owner = parts[0]!;
62
+ const repo = parts[1]!;
63
+ // https://github.com/anren-hk/webpack-vue-create/tree/gh-pages
64
+ const restPath = parts.slice(2); // 剩余路径,如 ['tree', 'v5.2.2'],即分支名称或者版本号
65
+
66
+ // 构造 path:如果有后续路径,则以 '/' 开头拼接;否则为空字符串
67
+ const path = restPath.length > 0 ? "/" + restPath.join("/") : "";
68
+
69
+ const projectUrlInfo: RemoteProjectUrlInfo = {
70
+ owner,
71
+ repo,
72
+ path,
73
+ platform,
74
+ originalUrl: url,
75
+ };
76
+ return projectUrlInfo;
77
+ } catch (error) {
78
+ if (error instanceof TypeError) {
79
+ throw new Error("Invalid URL: malformed or missing");
80
+ }
81
+ throw error;
82
+ }
83
+ }
84
+
85
+ /**
86
+ * @Desc: 获取 GitHub 仓库的 package.json URL
87
+ */
88
+ async function getGithubPackageJsonUrl(
89
+ projectUrlInfo: RemoteProjectUrlInfo,
90
+ ): Promise<PackageJsonUrlInfo> {
91
+ const { owner, repo, path } = projectUrlInfo;
92
+ let branchName: string;
93
+
94
+ // 如果path以 /tree/ 开头,则表示是分支路径,如 /tree/v5.2.2或者/tree/dev,则取分支名称 v5.2.2
95
+ if (path.startsWith("/tree/")) {
96
+ const pathParts = path.split("/").filter(Boolean);
97
+ branchName = pathParts[1]!;
98
+ } else {
99
+ // 通过 API 获取仓库信息,获取默认分支名称
100
+ const url = `https://api.github.com/repos/${owner}/${repo}`;
101
+ const info = (await fetch(url).then((resp) =>
102
+ resp.json(),
103
+ )) as GitProjectInfo;
104
+ branchName = info.default_branch;
105
+ }
106
+ return {
107
+ branchName,
108
+ packageJsonUrl: `https://raw.githubusercontent.com/${owner}/${repo}/${branchName}/package.json`,
109
+ };
110
+ }
111
+
112
+ /**
113
+ * @Desc: 获取 Gitee 仓库的 package.json URL
114
+ */
115
+ async function getGiteePackageJsonUrl(
116
+ projectUrlInfo: RemoteProjectUrlInfo,
117
+ ): Promise<PackageJsonUrlInfo> {
118
+ const { owner, repo, path } = projectUrlInfo;
119
+ let branchName: string;
120
+
121
+ if (path.startsWith("/tree/")) {
122
+ const pathParts = path.split("/").filter(Boolean);
123
+ branchName = pathParts[1]!;
124
+ } else {
125
+ // 通过 API 获取仓库信息,获取默认分支名称
126
+ const url = `https://gitee.com/api/v5/repos/${owner}/${repo}`;
127
+ const info = (await fetch(url).then((resp) =>
128
+ resp.json(),
129
+ )) as GitProjectInfo;
130
+ branchName = info.default_branch;
131
+ }
132
+ return {
133
+ branchName,
134
+ packageJsonUrl: `https://gitee.com/${owner}/${repo}/raw/${branchName}/package.json`,
135
+ };
136
+ }
137
+
138
+ /**
139
+ * @Desc: 获取 GitLab 仓库的 package.json URL
140
+ */
141
+ async function getGitlabPackageJsonUrl(
142
+ projectUrlInfo: RemoteProjectUrlInfo,
143
+ ): Promise<PackageJsonUrlInfo> {
144
+ const { owner, repo, path } = projectUrlInfo;
145
+ let branchName: string;
146
+
147
+ if (path.startsWith("/tree/")) {
148
+ const pathParts = path.split("/").filter(Boolean);
149
+ branchName = `${pathParts[1]}`;
150
+ } else {
151
+ // 通过 API 获取仓库信息,获取默认分支名称
152
+ // TODO:地址可能有问题
153
+ const url = `https://gitlab.com/api/v4/projects/${owner}/${repo}`;
154
+ const info = (await fetch(url).then((resp) =>
155
+ resp.json(),
156
+ )) as GitProjectInfo;
157
+ branchName = info.default_branch;
158
+ }
159
+ return {
160
+ branchName,
161
+ packageJsonUrl: `https://gitlab.com/${owner}/${repo}/-/raw/${branchName}/package.json`,
162
+ };
163
+ }
164
+
165
+ /**
166
+ * @Desc: 获取 Git 仓库的 package.json URL
167
+ * @return {string} package.json 的 URL
168
+ * @param {Object} gitUrlInfo - 解析后的 Git 仓库信息 { owner, repo, path, platform }
169
+ */
170
+ export async function getPackageJsonUrl(gitUrlInfo: RemoteProjectUrlInfo) {
171
+ // 具体是何种远程仓库,根据平台选择不同的 URL 构造函数
172
+ switch (gitUrlInfo.platform) {
173
+ case "github":
174
+ return await getGithubPackageJsonUrl(gitUrlInfo);
175
+ case "gitee":
176
+ return await getGiteePackageJsonUrl(gitUrlInfo);
177
+ case "gitlab":
178
+ return await getGitlabPackageJsonUrl(gitUrlInfo);
179
+ default:
180
+ throw new Error(`Unsupported platform: ${gitUrlInfo.platform}`);
181
+ }
182
+ }
183
+
184
+ /**
185
+ * @Desc: 获取远程仓库的 package.json文件内容
186
+ * @return {*}
187
+ * @param {string} gitUrl
188
+ */
189
+ export async function parseRemoteProject(gitUrl: string) {
190
+ const gitInfo = parseGitUrl(gitUrl);
191
+ const { packageJsonUrl } = await getPackageJsonUrl(gitInfo);
192
+ // 返回json
193
+ return (await fetch(packageJsonUrl).then((resp) =>
194
+ resp.json(),
195
+ )) as Promise<PackageJsonInfo>;
196
+ }
197
+
198
+ /**
199
+ * @Desc: 获取 GitHub 仓库的 package.json URL
200
+ * @param {Object} projectUrlInfo - 解析后的 Git 仓库信息 { owner, repo, path, platform }
201
+ * @param {string[]} workspaceNames - 工作空间名称列表,每个元素为一个子包的名称
202
+ * @return {Promise<string>} package.json 的 URL
203
+ */
204
+ async function getGithubSubPackagePackageJsonUrl(
205
+ projectUrlInfo: RemoteProjectUrlInfo,
206
+ workspaceNames: string[],
207
+ ): Promise<string> {
208
+ const { owner, repo, path } = projectUrlInfo;
209
+ let branchName: string;
210
+
211
+ // 如果path以 /tree/ 开头,则表示是分支路径,如 /tree/v5.2.2或者/tree/dev,则取分支名称 v5.2.2
212
+ if (path.startsWith("/tree/")) {
213
+ const pathParts = path.split("/").filter(Boolean);
214
+ branchName = pathParts[1]!;
215
+ } else {
216
+ // 通过 API 获取仓库信息,获取默认分支名称
217
+ const url = `https://api.github.com/repos/${owner}/${repo}`;
218
+ const info = (await fetch(url).then((resp) =>
219
+ resp.json(),
220
+ )) as GitProjectInfo;
221
+ branchName = info.default_branch;
222
+ }
223
+ // return `https://raw.githubusercontent.com/${owner}/${repo}/${branchName}/package.json`;
224
+ return `https://raw.githubusercontent.com/anren-hk/slt-erp/main/packages/net/package.json`;
225
+ }
@@ -0,0 +1,202 @@
1
+ import { readdirSync, promises } from "fs";
2
+ import { join } from "path";
3
+ import yaml from "js-yaml";
4
+ import type { PackageJsonInfo } from "../types.js";
5
+ import {
6
+ getPackageJsonUrl,
7
+ parseGitUrl,
8
+ type RemoteProjectUrlInfo,
9
+ } from "./parseRemoteProject.js";
10
+
11
+ interface ParseWorkspaceOptions {
12
+ /** 项目地址链接 */
13
+ projectRoot: string;
14
+ /** 项目根package.json文件内容 */
15
+ packageJsonRoot: PackageJsonInfo;
16
+ }
17
+
18
+ interface GitContentInfo {
19
+ /** 文件或目录名称 */
20
+ name: string;
21
+ /** 文件类型 */
22
+ type: string;
23
+ /** 文件下载地址 */
24
+ download_url: string;
25
+ }
26
+
27
+ export interface RemoteWorkspaceInfo {
28
+ gitInfo?: RemoteProjectUrlInfo;
29
+ branchName?: string;
30
+ subPackageNames: string[];
31
+ }
32
+
33
+ /**
34
+ * 工具项目地址,判断是本地工程还是远程仓库
35
+ */
36
+ export function parseWorkspace(options: ParseWorkspaceOptions) {
37
+ const { projectRoot } = options;
38
+ // 分辨是本地工程还是远程仓库
39
+ if (projectRoot.startsWith("http://") || projectRoot.startsWith("https://")) {
40
+ // 远程项目
41
+ return parseRemoteWorkspace(options);
42
+ }
43
+ return parseLocalWorkspace(options);
44
+ }
45
+
46
+ /**
47
+ * @Desc: 获取本地项目中Monorepo项目相关信息
48
+ * @param {string} projectRoot 本地项目地址
49
+ * @param {PackageJsonInfo} packageJsonRoot 项目根package.json文件内容
50
+ * @return {string[]} 项目中的子包位置信息,例如:[ 'packages/**', 'apps/**', 'features/**' ]
51
+ */
52
+ async function getMonorepoInfoByLocal(
53
+ projectRoot: string,
54
+ packageJsonRoot: PackageJsonInfo,
55
+ ) {
56
+ // 判断指定目录下是否有文件名包含workspace.yaml的文件,例如:pnpm-workspace.yaml
57
+ const fileNames = await readdirSync(projectRoot);
58
+ const workspaceFileName = fileNames.find((fileName) =>
59
+ fileName.includes("workspace.yaml"),
60
+ );
61
+ if (workspaceFileName) {
62
+ const workspaceFile = join(projectRoot, workspaceFileName);
63
+ const fileContent = await promises.readFile(workspaceFile, "utf8");
64
+ const fileInfo = yaml.load(fileContent) as { packages: string[] };
65
+ return {
66
+ workspaces: fileInfo.packages,
67
+ };
68
+ }
69
+ // 判断packageJson文件中是否有workspaces字段
70
+ if (packageJsonRoot.workspaces && packageJsonRoot.workspaces.length > 0) {
71
+ return {
72
+ workspaces: packageJsonRoot.workspaces as string[],
73
+ };
74
+ }
75
+ return null;
76
+ }
77
+
78
+ /**
79
+ * @Desc: 获取本地项目中workspace相关信息
80
+ * @param {options} options 包含projectRoot和packageJsonRoot 项目根package.json文件内容
81
+ * @param {string[]} options.projectRoot 本地项目地址
82
+ * @param {string} options.packageJsonRoot package.json文件内容
83
+ */
84
+ async function parseLocalWorkspace(
85
+ options: ParseWorkspaceOptions,
86
+ ): Promise<RemoteWorkspaceInfo | null> {
87
+ const { projectRoot, packageJsonRoot } = options;
88
+ const monorepoInfo = await getMonorepoInfoByLocal(
89
+ projectRoot,
90
+ packageJsonRoot,
91
+ );
92
+ if (!monorepoInfo) {
93
+ // 本地项目不是monorepo工程
94
+ return null;
95
+ }
96
+ const { workspaces } = monorepoInfo;
97
+ // 获取子包所在位置
98
+ const workspaceNames = workspaces.map((workspace) => workspace.split("/")[0]);
99
+ const subPackageNames = await Promise.all(
100
+ workspaceNames.map(async (workspaceName) => {
101
+ if (!workspaceName) {
102
+ return [];
103
+ }
104
+ const packageName = join(projectRoot, workspaceName);
105
+ // 获取所有子包项目
106
+ const subFileNames = await readdirSync(packageName);
107
+ return subFileNames.map((sub) => workspaceName + "/" + sub);
108
+ }),
109
+ );
110
+ return {
111
+ subPackageNames: subPackageNames.flat(),
112
+ };
113
+ }
114
+
115
+ /**
116
+ * @Desc: 获取远程项目中Monorepo项目相关信息
117
+ * @param {string} projectRoot 远程项目地址
118
+ * @param {PackageJsonInfo} packageJsonRoot 项目根package.json文件内容
119
+ * @return {string[]} 项目中的子包位置信息,例如:[ 'packages/**', 'apps/**', 'features/**' ]
120
+ */
121
+ async function getMonorepoInfoByRemote(
122
+ projectRoot: string,
123
+ packageJsonRoot: PackageJsonInfo,
124
+ ) {
125
+ const gitInfo = parseGitUrl(projectRoot);
126
+ const { owner, repo } = gitInfo;
127
+ const { branchName } = await getPackageJsonUrl(gitInfo);
128
+ const projectInfoUrl = `https://api.github.com/repos/${owner}/${repo}/contents?ref=${branchName}`;
129
+ const contentInfoList = (await fetch(projectInfoUrl).then((resp) =>
130
+ resp.json(),
131
+ )) as GitContentInfo[];
132
+ const workspaceFileInfo = contentInfoList.findLast((item) =>
133
+ item.name.includes("workspace.yaml"),
134
+ );
135
+
136
+ if (workspaceFileInfo) {
137
+ const fileContent = await fetch(workspaceFileInfo.download_url).then(
138
+ (resp) => resp.text(),
139
+ );
140
+ const fileInfo = yaml.load(fileContent) as { packages: string[] };
141
+ return {
142
+ gitInfo,
143
+ branchName,
144
+ workspaces: fileInfo.packages,
145
+ };
146
+ }
147
+ // 判断packageJson文件中是否有workspaces字段
148
+ if (packageJsonRoot.workspaces && packageJsonRoot.workspaces.length > 0) {
149
+ return {
150
+ gitInfo,
151
+ branchName,
152
+ workspaces: packageJsonRoot.workspaces,
153
+ };
154
+ }
155
+ return null;
156
+ }
157
+
158
+ /**
159
+ * @Desc: 获取远程项目中workspace相关信息
160
+ * @param {options} options 包含gitInfo和packageJsonRoot 项目根package.json文件内容
161
+ * @param {string[]} options.projectRoot 远程项目地址
162
+ * @param {string} options.packageJsonRoot package.json文件内容
163
+ */
164
+ async function parseRemoteWorkspace(
165
+ options: ParseWorkspaceOptions,
166
+ ): Promise<RemoteWorkspaceInfo | null> {
167
+ const { projectRoot, packageJsonRoot } = options;
168
+ const monorepoInfo = await getMonorepoInfoByRemote(
169
+ projectRoot,
170
+ packageJsonRoot,
171
+ );
172
+
173
+ if (!monorepoInfo) {
174
+ // 远程项目不是monorepo工程
175
+ return null;
176
+ }
177
+ const { gitInfo, branchName, workspaces } = monorepoInfo;
178
+ const { owner, repo } = gitInfo;
179
+ // 获取子包所在位置
180
+ const workspaceNames = workspaces.map((workspace) => workspace.split("/")[0]);
181
+ const subPackageNames = await Promise.all(
182
+ workspaceNames.map(async (workspaceName) => {
183
+ if (!workspaceName) {
184
+ return [];
185
+ }
186
+ const packageInfoUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${workspaceName}?ref=${branchName}`;
187
+ // 获取所有子包项目
188
+ const subFileInfoList = (await fetch(packageInfoUrl)
189
+ .then((resp) => resp.json())
190
+ .catch((err) => {
191
+ return [];
192
+ })) as GitContentInfo[];
193
+
194
+ return subFileInfoList.map((sub) => workspaceName + "/" + sub.name);
195
+ }),
196
+ );
197
+ return {
198
+ gitInfo,
199
+ branchName,
200
+ subPackageNames: subPackageNames.flat(),
201
+ };
202
+ }
@@ -0,0 +1,30 @@
1
+ import type { NormalizedResult } from "../audit/normalizeAuditResult.js";
2
+ import type { PackageJsonInfo } from "../types.js";
3
+ import { renderMarkdown, type MarkdownData } from "./markdown.js";
4
+
5
+ const desc = {
6
+ severityLevels: {
7
+ low: "低危",
8
+ moderate: "中危",
9
+ high: "高危",
10
+ critical: "严重",
11
+ info: "信息",
12
+ },
13
+ };
14
+
15
+ /**
16
+ * 讲auditResult渲染为markdown格式的字符串
17
+ * @param {object} auditResult 规范化的审计结果
18
+ * @param {object} packageJson 包的package.json内容
19
+ */
20
+ export async function render(
21
+ auditResult: NormalizedResult,
22
+ packageJson: PackageJsonInfo,
23
+ ) {
24
+ const data: MarkdownData = {
25
+ audit: auditResult,
26
+ desc,
27
+ packageJson,
28
+ };
29
+ return await renderMarkdown(data);
30
+ }
@@ -0,0 +1,29 @@
1
+ import ejs from "ejs";
2
+ import { join } from "path";
3
+ import { getDirname } from "../utils/index.js";
4
+ import type { NormalizedResult } from "../audit/normalizeAuditResult.js";
5
+ import type { AuditSeverity, PackageJsonInfo } from "../types.js";
6
+
7
+ export interface MarkdownDesc {
8
+ severityLevels: Record<AuditSeverity, string>;
9
+ }
10
+
11
+ export interface MarkdownData {
12
+ audit: NormalizedResult;
13
+ desc: MarkdownDesc;
14
+ packageJson: PackageJsonInfo;
15
+ }
16
+
17
+ const templatePath = join(getDirname(import.meta.url), "./template/index.ejs");
18
+
19
+ export function renderMarkdown(data: MarkdownData): Promise<string> {
20
+ return new Promise((resolve, reject) => {
21
+ ejs.renderFile(templatePath, data, (err, str) => {
22
+ if (err) {
23
+ reject(err);
24
+ return;
25
+ }
26
+ resolve(str);
27
+ });
28
+ });
29
+ }
@@ -0,0 +1,30 @@
1
+ 您所审计的工程总共有 **<%- audit.summary.total %>** 个风险漏洞。
2
+
3
+ 其中:
4
+
5
+ - **<%- desc.severityLevels.critical %>漏洞**:共计 **<%- audit.summary.critical %>** 个
6
+ - **<%- desc.severityLevels.high %>漏洞**:共计 **<%- audit.summary.high %>** 个
7
+ - **<%- desc.severityLevels.moderate %>漏洞**:共计 **<%- audit.summary.moderate %>** 个
8
+ - **<%- desc.severityLevels.low %>漏洞**:共计 **<%- audit.summary.low %>** 个
9
+
10
+ > 说明:
11
+ >
12
+ > - **<%- desc.severityLevels.critical %>**漏洞被认为是极其严重的,应该立即修复。
13
+ > - **<%- desc.severityLevels.high %>**漏洞被认为是严重的,应该尽快修复。
14
+ > - **<%- desc.severityLevels.moderate %>**漏洞被认为是中等严重的,可以选择在时间允许时修复。
15
+ > - **<%- desc.severityLevels.low %>**漏洞被认为是轻微的,可以根据自行需要进行修复。
16
+
17
+ 下面是漏洞的详细信息
18
+
19
+ <% if (audit.summary.critical) { %>
20
+ <%- include('./detail.ejs', {type:'critical'}); %>
21
+ <% } %>
22
+ <% if (audit.summary.high) { %>
23
+ <%- include('./detail.ejs', {type:'high'}); %>
24
+ <% } %>
25
+ <% if (audit.summary.moderate) { %>
26
+ <%- include('./detail.ejs', {type:'moderate'}); %>
27
+ <% } %>
28
+ <% if (audit.summary.low) { %>
29
+ <%- include('./detail.ejs', {type:'low'}); %>
30
+ <% } %>
@@ -0,0 +1,32 @@
1
+ ### `<%- item.name -%>`
2
+
3
+ **漏洞描述**:
4
+ <% item.problems.forEach((problem) => { %>
5
+ - <%- problem.title %>
6
+ - npm漏洞编号:`<%- problem.source %>`
7
+ - 漏洞详细说明:<%- problem.url %>
8
+ - 漏洞等级:<%- desc.severityLevels[problem.severity] %>
9
+ - 受影响的版本:`<%- problem.range %>`
10
+ <% }); %>
11
+
12
+ **依赖关系**:
13
+ <% if(item.depChains.length === 0) { %>
14
+ <% if(item.name === packageJson.name) { %>
15
+ 当前工程
16
+ <% } else { %>
17
+ - `<%- packageJson.name %>` / <%- item.name %>
18
+ <% } %>
19
+ <% } else { %>
20
+ <% item.depChains.forEach((chain) => { %>
21
+ <% if(chain.length === 1 && chain[0] === packageJson.name) { %>
22
+ 当前工程
23
+ <% } else { %>
24
+ - `<%- packageJson.name %>` / <%- chain.map(c=>`\`${c}\``).join(' / ') %>
25
+ <% } %>
26
+ <% }); %>
27
+ <% } %>
28
+
29
+ **漏洞包所在目录**:
30
+ <% item.nodes.forEach((path) => { %>
31
+ - `<%- path %>`
32
+ <% }); %>
@@ -0,0 +1,7 @@
1
+ ## <%-desc.severityLevels[type] %>漏洞
2
+
3
+ 共计 **<%- audit.summary[type] %>** 个
4
+
5
+ <% audit.vulnerabilities[type].forEach(function(item){ %>
6
+ <%- include('./detail-item.ejs', {item: item}) %>
7
+ <% }); %>
@@ -0,0 +1,8 @@
1
+ # `<%- packageJson.name %>`审计结果
2
+
3
+ <% if(audit.summary.total) { %>
4
+ <%- include('./audit.ejs'); %>
5
+ <% } %>
6
+ <% if(audit.summary.total === 0) { %>
7
+ 你项目的所有直接依赖和间接依赖都没有发现任何风险漏洞。
8
+ <% } %>