mobbdev 0.0.74 → 0.0.77
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/.env +3 -2
- package/README.md +8 -2
- package/dist/index.mjs +1100 -275
- package/package.json +5 -2
package/dist/index.mjs
CHANGED
|
@@ -14,6 +14,7 @@ import { hideBin } from "yargs/helpers";
|
|
|
14
14
|
|
|
15
15
|
// src/types.ts
|
|
16
16
|
var mobbCliCommand = {
|
|
17
|
+
addScmToken: "add-scm-token",
|
|
17
18
|
scan: "scan",
|
|
18
19
|
analyze: "analyze",
|
|
19
20
|
review: "review"
|
|
@@ -24,7 +25,11 @@ import chalk9 from "chalk";
|
|
|
24
25
|
import yargs from "yargs/yargs";
|
|
25
26
|
|
|
26
27
|
// src/args/commands/analyze.ts
|
|
28
|
+
import fs5 from "node:fs";
|
|
29
|
+
|
|
30
|
+
// src/commands/index.ts
|
|
27
31
|
import fs4 from "node:fs";
|
|
32
|
+
import path7 from "node:path";
|
|
28
33
|
|
|
29
34
|
// src/constants.ts
|
|
30
35
|
import path from "node:path";
|
|
@@ -36,6 +41,11 @@ import { z } from "zod";
|
|
|
36
41
|
var debug = Debug("mobbdev:constants");
|
|
37
42
|
var __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
38
43
|
dotenv.config({ path: path.join(__dirname, "../.env") });
|
|
44
|
+
var ScmTypes = {
|
|
45
|
+
Github: "GitHub",
|
|
46
|
+
Gitlab: "GitLab",
|
|
47
|
+
AzureDevOps: "Ado"
|
|
48
|
+
};
|
|
39
49
|
var SCANNERS = {
|
|
40
50
|
Checkmarx: "checkmarx",
|
|
41
51
|
Codeql: "codeql",
|
|
@@ -83,7 +93,16 @@ var API_URL = envVariables.API_URL;
|
|
|
83
93
|
var errorMessages = {
|
|
84
94
|
missingCxProjectName: `project name ${chalk.bold(
|
|
85
95
|
"(--cx-project-name)"
|
|
86
|
-
)} is needed if you're using checkmarx
|
|
96
|
+
)} is needed if you're using checkmarx`,
|
|
97
|
+
missingScmType: `SCM type ${chalk.bold(
|
|
98
|
+
"(--scm-type)"
|
|
99
|
+
)} is needed if you're adding an SCM token`,
|
|
100
|
+
invalidScmType: `SCM type ${chalk.bold(
|
|
101
|
+
"(--scm-type)"
|
|
102
|
+
)} is invalid, please use one of: ${Object.values(ScmTypes).join(", ")}`,
|
|
103
|
+
missingToken: `SCM token ${chalk.bold(
|
|
104
|
+
"(--token)"
|
|
105
|
+
)} is needed if you're adding an SCM token`
|
|
87
106
|
};
|
|
88
107
|
|
|
89
108
|
// src/features/analysis/index.ts
|
|
@@ -163,7 +182,7 @@ import fetch3 from "node-fetch";
|
|
|
163
182
|
import open2 from "open";
|
|
164
183
|
import semver from "semver";
|
|
165
184
|
import tmp2 from "tmp";
|
|
166
|
-
import { z as
|
|
185
|
+
import { z as z10 } from "zod";
|
|
167
186
|
|
|
168
187
|
// src/features/analysis/git.ts
|
|
169
188
|
import Debug2 from "debug";
|
|
@@ -216,6 +235,28 @@ import { v4 as uuidv4 } from "uuid";
|
|
|
216
235
|
|
|
217
236
|
// src/features/analysis/graphql/mutations.ts
|
|
218
237
|
import { gql } from "graphql-request";
|
|
238
|
+
var UPDATE_SCM_TOKEN = gql`
|
|
239
|
+
mutation updateScmToken(
|
|
240
|
+
$type: ScmType!
|
|
241
|
+
$token: String!
|
|
242
|
+
$org: String
|
|
243
|
+
$username: String
|
|
244
|
+
$refreshToken: String
|
|
245
|
+
) {
|
|
246
|
+
updateScmToken(
|
|
247
|
+
type: $type
|
|
248
|
+
token: $token
|
|
249
|
+
org: $org
|
|
250
|
+
username: $username
|
|
251
|
+
refreshToken: $refreshToken
|
|
252
|
+
) {
|
|
253
|
+
id
|
|
254
|
+
adoAccessToken
|
|
255
|
+
gitlabAccessToken
|
|
256
|
+
gitHubAccessToken
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
`;
|
|
219
260
|
var UPLOAD_S3_BUCKET_INFO = gql`
|
|
220
261
|
mutation uploadS3BucketInfo($fileName: String!) {
|
|
221
262
|
uploadS3BucketInfo(fileName: $fileName) {
|
|
@@ -329,6 +370,8 @@ var ME = gql2`
|
|
|
329
370
|
email
|
|
330
371
|
githubToken
|
|
331
372
|
gitlabToken
|
|
373
|
+
adoToken
|
|
374
|
+
adoOrg
|
|
332
375
|
}
|
|
333
376
|
}
|
|
334
377
|
`;
|
|
@@ -520,6 +563,14 @@ function subscribe(query, variables, callback, wsClientOptions) {
|
|
|
520
563
|
|
|
521
564
|
// src/features/analysis/graphql/types.ts
|
|
522
565
|
import { z as z2 } from "zod";
|
|
566
|
+
var UpdateScmTokenZ = z2.object({
|
|
567
|
+
updateScmToken: z2.object({
|
|
568
|
+
id: z2.string(),
|
|
569
|
+
gitHubAccessToken: z2.string().nullable(),
|
|
570
|
+
gitlabAccessToken: z2.string().nullable(),
|
|
571
|
+
adoAccessToken: z2.string().nullable()
|
|
572
|
+
})
|
|
573
|
+
});
|
|
523
574
|
var UploadFieldsZ = z2.object({
|
|
524
575
|
bucket: z2.string(),
|
|
525
576
|
"X-Amz-Algorithm": z2.string(),
|
|
@@ -751,6 +802,17 @@ var GQLClient = class {
|
|
|
751
802
|
debug3("create community user failed %o", e);
|
|
752
803
|
}
|
|
753
804
|
}
|
|
805
|
+
async updateScmToken(args) {
|
|
806
|
+
const { type: type2, token, org, username, refreshToken } = args;
|
|
807
|
+
const updateScmTokenResult = await this._client.request(UPDATE_SCM_TOKEN, {
|
|
808
|
+
type: type2,
|
|
809
|
+
token,
|
|
810
|
+
org,
|
|
811
|
+
username,
|
|
812
|
+
refreshToken
|
|
813
|
+
});
|
|
814
|
+
return UpdateScmTokenZ.parse(updateScmTokenResult);
|
|
815
|
+
}
|
|
754
816
|
async uploadS3BucketInfo() {
|
|
755
817
|
const uploadS3BucketInfoResult = await this._client.request(UPLOAD_S3_BUCKET_INFO, {
|
|
756
818
|
fileName: "report.json"
|
|
@@ -902,20 +964,16 @@ var GQLClient = class {
|
|
|
902
964
|
// src/features/analysis/handle_finished_analysis.ts
|
|
903
965
|
import Debug4 from "debug";
|
|
904
966
|
import parseDiff from "parse-diff";
|
|
905
|
-
import { z as
|
|
906
|
-
|
|
907
|
-
// src/features/analysis/scm/constants.ts
|
|
908
|
-
var MOBB_ICON_IMG = "https://svgshare.com/i/12DK.svg";
|
|
909
|
-
var COMMIT_FIX_SVG = `https://app.mobb.ai/gh-action/commit-button.svg`;
|
|
967
|
+
import { z as z9 } from "zod";
|
|
910
968
|
|
|
911
|
-
// src/features/analysis/scm/
|
|
912
|
-
import
|
|
913
|
-
import
|
|
914
|
-
import { z as
|
|
969
|
+
// src/features/analysis/scm/ado.ts
|
|
970
|
+
import querystring2 from "node:querystring";
|
|
971
|
+
import * as api from "azure-devops-node-api";
|
|
972
|
+
import { z as z8 } from "zod";
|
|
915
973
|
|
|
916
974
|
// src/features/analysis/scm/scm.ts
|
|
917
975
|
import { Octokit as Octokit2 } from "@octokit/core";
|
|
918
|
-
import { z as
|
|
976
|
+
import { z as z7 } from "zod";
|
|
919
977
|
|
|
920
978
|
// src/features/analysis/scm/github/encryptSecret.ts
|
|
921
979
|
import sodium from "libsodium-wrappers";
|
|
@@ -934,12 +992,22 @@ import { z as z3 } from "zod";
|
|
|
934
992
|
|
|
935
993
|
// src/features/analysis/scm/urlParser.ts
|
|
936
994
|
var pathnameParsingMap = {
|
|
995
|
+
"dev.azure.com": (pathname) => {
|
|
996
|
+
if (pathname.length < 2)
|
|
997
|
+
return null;
|
|
998
|
+
return {
|
|
999
|
+
organization: pathname[0],
|
|
1000
|
+
repoName: pathname[pathname.length - 1],
|
|
1001
|
+
projectName: pathname[1]
|
|
1002
|
+
};
|
|
1003
|
+
},
|
|
937
1004
|
"gitlab.com": (pathname) => {
|
|
938
1005
|
if (pathname.length < 2)
|
|
939
1006
|
return null;
|
|
940
1007
|
return {
|
|
941
1008
|
organization: pathname[0],
|
|
942
|
-
repoName: pathname[pathname.length - 1]
|
|
1009
|
+
repoName: pathname[pathname.length - 1],
|
|
1010
|
+
projectName: void 0
|
|
943
1011
|
};
|
|
944
1012
|
},
|
|
945
1013
|
"github.com": (pathname) => {
|
|
@@ -947,7 +1015,8 @@ var pathnameParsingMap = {
|
|
|
947
1015
|
return null;
|
|
948
1016
|
return {
|
|
949
1017
|
organization: pathname[0],
|
|
950
|
-
repoName: pathname[1]
|
|
1018
|
+
repoName: pathname[1],
|
|
1019
|
+
projectName: void 0
|
|
951
1020
|
};
|
|
952
1021
|
}
|
|
953
1022
|
};
|
|
@@ -964,7 +1033,7 @@ var parseScmURL = (scmURL) => {
|
|
|
964
1033
|
);
|
|
965
1034
|
if (!repo)
|
|
966
1035
|
return null;
|
|
967
|
-
const { organization, repoName } = repo;
|
|
1036
|
+
const { organization, repoName, projectName } = repo;
|
|
968
1037
|
if (!organization || !repoName)
|
|
969
1038
|
return null;
|
|
970
1039
|
if (!organization.match(NAME_REGEX) || !repoName.match(NAME_REGEX))
|
|
@@ -973,7 +1042,9 @@ var parseScmURL = (scmURL) => {
|
|
|
973
1042
|
hostname: url.hostname,
|
|
974
1043
|
organization,
|
|
975
1044
|
projectPath,
|
|
976
|
-
repoName
|
|
1045
|
+
repoName,
|
|
1046
|
+
projectName,
|
|
1047
|
+
pathElements: projectPath.split("/")
|
|
977
1048
|
};
|
|
978
1049
|
} catch (e) {
|
|
979
1050
|
return null;
|
|
@@ -1034,7 +1105,7 @@ async function githubValidateParams(url, accessToken) {
|
|
|
1034
1105
|
await oktoKit.rest.users.getAuthenticated();
|
|
1035
1106
|
}
|
|
1036
1107
|
if (url) {
|
|
1037
|
-
const { owner, repo } =
|
|
1108
|
+
const { owner, repo } = parseGithubOwnerAndRepo(url);
|
|
1038
1109
|
await oktoKit.rest.repos.get({ repo, owner });
|
|
1039
1110
|
}
|
|
1040
1111
|
} catch (e) {
|
|
@@ -1056,7 +1127,7 @@ async function getGithubUsername(accessToken) {
|
|
|
1056
1127
|
}
|
|
1057
1128
|
async function getGithubIsUserCollaborator(username, accessToken, repoUrl) {
|
|
1058
1129
|
try {
|
|
1059
|
-
const { owner, repo } =
|
|
1130
|
+
const { owner, repo } = parseGithubOwnerAndRepo(repoUrl);
|
|
1060
1131
|
const oktoKit = getOktoKit({ githubAuthToken: accessToken });
|
|
1061
1132
|
const res = await oktoKit.rest.repos.checkCollaborator({
|
|
1062
1133
|
owner,
|
|
@@ -1072,7 +1143,7 @@ async function getGithubIsUserCollaborator(username, accessToken, repoUrl) {
|
|
|
1072
1143
|
return false;
|
|
1073
1144
|
}
|
|
1074
1145
|
async function getGithubPullRequestStatus(accessToken, repoUrl, prNumber) {
|
|
1075
|
-
const { owner, repo } =
|
|
1146
|
+
const { owner, repo } = parseGithubOwnerAndRepo(repoUrl);
|
|
1076
1147
|
const oktoKit = getOktoKit({ githubAuthToken: accessToken });
|
|
1077
1148
|
const res = await oktoKit.rest.pulls.get({
|
|
1078
1149
|
owner,
|
|
@@ -1088,7 +1159,7 @@ async function getGithubPullRequestStatus(accessToken, repoUrl, prNumber) {
|
|
|
1088
1159
|
return res.data.state;
|
|
1089
1160
|
}
|
|
1090
1161
|
async function getGithubIsRemoteBranch(accessToken, repoUrl, branch) {
|
|
1091
|
-
const { owner, repo } =
|
|
1162
|
+
const { owner, repo } = parseGithubOwnerAndRepo(repoUrl);
|
|
1092
1163
|
const oktoKit = getOktoKit({ githubAuthToken: accessToken });
|
|
1093
1164
|
try {
|
|
1094
1165
|
const res = await oktoKit.rest.repos.getBranch({
|
|
@@ -1132,7 +1203,7 @@ async function getGithubRepoList(accessToken) {
|
|
|
1132
1203
|
}
|
|
1133
1204
|
}
|
|
1134
1205
|
async function getGithubBranchList(accessToken, repoUrl) {
|
|
1135
|
-
const { owner, repo } =
|
|
1206
|
+
const { owner, repo } = parseGithubOwnerAndRepo(repoUrl);
|
|
1136
1207
|
const oktoKit = getOktoKit({ githubAuthToken: accessToken });
|
|
1137
1208
|
const res = await oktoKit.rest.repos.listBranches({
|
|
1138
1209
|
owner,
|
|
@@ -1143,7 +1214,7 @@ async function getGithubBranchList(accessToken, repoUrl) {
|
|
|
1143
1214
|
return res.data.map((branch) => branch.name);
|
|
1144
1215
|
}
|
|
1145
1216
|
async function createPullRequest(options) {
|
|
1146
|
-
const { owner, repo } =
|
|
1217
|
+
const { owner, repo } = parseGithubOwnerAndRepo(options.repoUrl);
|
|
1147
1218
|
const oktoKit = getOktoKit({ githubAuthToken: options.accessToken });
|
|
1148
1219
|
const res = await oktoKit.rest.pulls.create({
|
|
1149
1220
|
owner,
|
|
@@ -1158,7 +1229,7 @@ async function createPullRequest(options) {
|
|
|
1158
1229
|
return res.data.number;
|
|
1159
1230
|
}
|
|
1160
1231
|
async function forkRepo(options) {
|
|
1161
|
-
const { owner, repo } =
|
|
1232
|
+
const { owner, repo } = parseGithubOwnerAndRepo(options.repoUrl);
|
|
1162
1233
|
const oktoKit = getOktoKit({ githubAuthToken: options.accessToken });
|
|
1163
1234
|
const res = await oktoKit.rest.repos.createFork({
|
|
1164
1235
|
owner,
|
|
@@ -1178,11 +1249,11 @@ async function getRepos(oktoKit) {
|
|
|
1178
1249
|
}
|
|
1179
1250
|
async function getGithubRepoDefaultBranch(repoUrl, options) {
|
|
1180
1251
|
const oktoKit = getOktoKit(options);
|
|
1181
|
-
const { owner, repo } =
|
|
1252
|
+
const { owner, repo } = parseGithubOwnerAndRepo(repoUrl);
|
|
1182
1253
|
return (await oktoKit.rest.repos.get({ repo, owner })).data.default_branch;
|
|
1183
1254
|
}
|
|
1184
1255
|
async function getGithubReferenceData({ ref, gitHubUrl }, options) {
|
|
1185
|
-
const { owner, repo } =
|
|
1256
|
+
const { owner, repo } = parseGithubOwnerAndRepo(gitHubUrl);
|
|
1186
1257
|
let res;
|
|
1187
1258
|
try {
|
|
1188
1259
|
const oktoKit = getOktoKit(options);
|
|
@@ -1257,7 +1328,7 @@ async function getCommit({
|
|
|
1257
1328
|
commit_sha: commitSha
|
|
1258
1329
|
});
|
|
1259
1330
|
}
|
|
1260
|
-
function
|
|
1331
|
+
function parseGithubOwnerAndRepo(gitHubUrl) {
|
|
1261
1332
|
gitHubUrl = removeTrailingSlash(gitHubUrl);
|
|
1262
1333
|
const parsingResult = parseScmURL(gitHubUrl);
|
|
1263
1334
|
if (!parsingResult || parsingResult.hostname !== "github.com") {
|
|
@@ -1291,12 +1362,12 @@ async function queryGithubGraphql(query, variables, options) {
|
|
|
1291
1362
|
throw e;
|
|
1292
1363
|
}
|
|
1293
1364
|
}
|
|
1294
|
-
async function getGithubBlameRanges({ ref, gitHubUrl, path:
|
|
1295
|
-
const { owner, repo } =
|
|
1365
|
+
async function getGithubBlameRanges({ ref, gitHubUrl, path: path9 }, options) {
|
|
1366
|
+
const { owner, repo } = parseGithubOwnerAndRepo(gitHubUrl);
|
|
1296
1367
|
const variables = {
|
|
1297
1368
|
owner,
|
|
1298
1369
|
repo,
|
|
1299
|
-
path:
|
|
1370
|
+
path: path9,
|
|
1300
1371
|
ref
|
|
1301
1372
|
};
|
|
1302
1373
|
const res = await queryGithubGraphql(
|
|
@@ -1323,8 +1394,8 @@ async function createPr({
|
|
|
1323
1394
|
body
|
|
1324
1395
|
}, options) {
|
|
1325
1396
|
const oktoKit = getOktoKit(options);
|
|
1326
|
-
const { owner: sourceOwner, repo: sourceRepo } =
|
|
1327
|
-
const { owner, repo } =
|
|
1397
|
+
const { owner: sourceOwner, repo: sourceRepo } = parseGithubOwnerAndRepo(sourceRepoUrl);
|
|
1398
|
+
const { owner, repo } = parseGithubOwnerAndRepo(userRepoUrl);
|
|
1328
1399
|
const [sourceFilePath, secondFilePath] = filesPaths;
|
|
1329
1400
|
const sourceFileContentResponse = await oktoKit.rest.repos.getContent({
|
|
1330
1401
|
owner: sourceOwner,
|
|
@@ -1445,83 +1516,326 @@ function getARepositoryPublicKey(client, params) {
|
|
|
1445
1516
|
return client.request(GET_A_REPOSITORY_PUBLIC_KEY, params);
|
|
1446
1517
|
}
|
|
1447
1518
|
|
|
1519
|
+
// src/features/analysis/scm/gitlab.ts
|
|
1520
|
+
import querystring from "node:querystring";
|
|
1521
|
+
import { Gitlab } from "@gitbeaker/rest";
|
|
1522
|
+
import { z as z4 } from "zod";
|
|
1523
|
+
function removeTrailingSlash2(str) {
|
|
1524
|
+
return str.trim().replace(/\/+$/, "");
|
|
1525
|
+
}
|
|
1526
|
+
var EnvVariablesZod2 = z4.object({
|
|
1527
|
+
GITLAB_API_TOKEN: z4.string().optional()
|
|
1528
|
+
});
|
|
1529
|
+
var { GITLAB_API_TOKEN } = EnvVariablesZod2.parse(process.env);
|
|
1530
|
+
function getGitBeaker(options) {
|
|
1531
|
+
const token = options?.gitlabAuthToken ?? GITLAB_API_TOKEN ?? "";
|
|
1532
|
+
if (token?.startsWith("glpat-") || token === "") {
|
|
1533
|
+
return new Gitlab({ token });
|
|
1534
|
+
}
|
|
1535
|
+
return new Gitlab({ oauthToken: token });
|
|
1536
|
+
}
|
|
1537
|
+
async function gitlabValidateParams({
|
|
1538
|
+
url,
|
|
1539
|
+
accessToken
|
|
1540
|
+
}) {
|
|
1541
|
+
try {
|
|
1542
|
+
const api2 = getGitBeaker({ gitlabAuthToken: accessToken });
|
|
1543
|
+
if (accessToken) {
|
|
1544
|
+
await api2.Users.showCurrentUser();
|
|
1545
|
+
}
|
|
1546
|
+
if (url) {
|
|
1547
|
+
const { projectPath } = parseGitlabOwnerAndRepo(url);
|
|
1548
|
+
await api2.Projects.show(projectPath);
|
|
1549
|
+
}
|
|
1550
|
+
} catch (e) {
|
|
1551
|
+
const error = e;
|
|
1552
|
+
const code = error.code || error.status || error.statusCode || error.response?.status || error.response?.statusCode || error.response?.code;
|
|
1553
|
+
const description = error.description || `${e}`;
|
|
1554
|
+
if (code === 401 || code === 403 || description.includes("401") || description.includes("403")) {
|
|
1555
|
+
throw new InvalidAccessTokenError(`invalid gitlab access token`);
|
|
1556
|
+
}
|
|
1557
|
+
if (code === 404 || description.includes("404") || description.includes("Not Found")) {
|
|
1558
|
+
throw new InvalidRepoUrlError(`invalid gitlab repo URL: ${url}`);
|
|
1559
|
+
}
|
|
1560
|
+
throw e;
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
async function getGitlabUsername(accessToken) {
|
|
1564
|
+
const api2 = getGitBeaker({ gitlabAuthToken: accessToken });
|
|
1565
|
+
const res = await api2.Users.showCurrentUser();
|
|
1566
|
+
return res.username;
|
|
1567
|
+
}
|
|
1568
|
+
async function getGitlabIsUserCollaborator({
|
|
1569
|
+
username,
|
|
1570
|
+
accessToken,
|
|
1571
|
+
repoUrl
|
|
1572
|
+
}) {
|
|
1573
|
+
try {
|
|
1574
|
+
const { projectPath } = parseGitlabOwnerAndRepo(repoUrl);
|
|
1575
|
+
const api2 = getGitBeaker({ gitlabAuthToken: accessToken });
|
|
1576
|
+
const res = await api2.Projects.show(projectPath);
|
|
1577
|
+
const members = await api2.ProjectMembers.all(res.id, {
|
|
1578
|
+
includeInherited: true
|
|
1579
|
+
});
|
|
1580
|
+
return !!members.find((member) => member.username === username);
|
|
1581
|
+
} catch (e) {
|
|
1582
|
+
return false;
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
async function getGitlabMergeRequestStatus({
|
|
1586
|
+
accessToken,
|
|
1587
|
+
repoUrl,
|
|
1588
|
+
mrNumber
|
|
1589
|
+
}) {
|
|
1590
|
+
const { projectPath } = parseGitlabOwnerAndRepo(repoUrl);
|
|
1591
|
+
const api2 = getGitBeaker({ gitlabAuthToken: accessToken });
|
|
1592
|
+
const res = await api2.MergeRequests.show(projectPath, mrNumber);
|
|
1593
|
+
switch (res.state) {
|
|
1594
|
+
case "merged" /* merged */:
|
|
1595
|
+
case "opened" /* opened */:
|
|
1596
|
+
case "closed" /* closed */:
|
|
1597
|
+
return res.state;
|
|
1598
|
+
default:
|
|
1599
|
+
throw new Error(`unknown merge request state ${res.state}`);
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
async function getGitlabIsRemoteBranch({
|
|
1603
|
+
accessToken,
|
|
1604
|
+
repoUrl,
|
|
1605
|
+
branch
|
|
1606
|
+
}) {
|
|
1607
|
+
const { projectPath } = parseGitlabOwnerAndRepo(repoUrl);
|
|
1608
|
+
const api2 = getGitBeaker({ gitlabAuthToken: accessToken });
|
|
1609
|
+
try {
|
|
1610
|
+
const res = await api2.Branches.show(projectPath, branch);
|
|
1611
|
+
return res.name === branch;
|
|
1612
|
+
} catch (e) {
|
|
1613
|
+
return false;
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
async function getGitlabRepoList(accessToken) {
|
|
1617
|
+
const api2 = getGitBeaker({ gitlabAuthToken: accessToken });
|
|
1618
|
+
const res = await api2.Projects.all({
|
|
1619
|
+
membership: true,
|
|
1620
|
+
//TODO: a bug in the sorting mechanism of this api call
|
|
1621
|
+
//disallows us to sort by updated_at in descending order
|
|
1622
|
+
//so we have to sort by updated_at in ascending order.
|
|
1623
|
+
//We can wait for the bug to be fixed or call the api
|
|
1624
|
+
//directly with fetch()
|
|
1625
|
+
sort: "asc",
|
|
1626
|
+
orderBy: "updated_at",
|
|
1627
|
+
perPage: 100
|
|
1628
|
+
});
|
|
1629
|
+
return Promise.all(
|
|
1630
|
+
res.map(async (project) => {
|
|
1631
|
+
const proj = await api2.Projects.show(project.id);
|
|
1632
|
+
const owner = proj.namespace.name;
|
|
1633
|
+
const repoLanguages = await api2.Projects.showLanguages(project.id);
|
|
1634
|
+
return {
|
|
1635
|
+
repoName: project.path,
|
|
1636
|
+
repoUrl: project.web_url,
|
|
1637
|
+
repoOwner: owner,
|
|
1638
|
+
repoLanguages: Object.keys(repoLanguages),
|
|
1639
|
+
repoIsPublic: project.visibility === "public",
|
|
1640
|
+
repoUpdatedAt: project.last_activity_at
|
|
1641
|
+
};
|
|
1642
|
+
})
|
|
1643
|
+
);
|
|
1644
|
+
}
|
|
1645
|
+
async function getGitlabBranchList({
|
|
1646
|
+
accessToken,
|
|
1647
|
+
repoUrl
|
|
1648
|
+
}) {
|
|
1649
|
+
const { projectPath } = parseGitlabOwnerAndRepo(repoUrl);
|
|
1650
|
+
const api2 = getGitBeaker({ gitlabAuthToken: accessToken });
|
|
1651
|
+
try {
|
|
1652
|
+
const res = await api2.Branches.all(projectPath, {
|
|
1653
|
+
perPage: 100,
|
|
1654
|
+
pagination: "keyset",
|
|
1655
|
+
orderBy: "updated_at",
|
|
1656
|
+
sort: "dec"
|
|
1657
|
+
});
|
|
1658
|
+
return res.map((branch) => branch.name);
|
|
1659
|
+
} catch (e) {
|
|
1660
|
+
return [];
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
async function createMergeRequest(options) {
|
|
1664
|
+
const { projectPath } = parseGitlabOwnerAndRepo(options.repoUrl);
|
|
1665
|
+
const api2 = getGitBeaker({ gitlabAuthToken: options.accessToken });
|
|
1666
|
+
const res = await api2.MergeRequests.create(
|
|
1667
|
+
projectPath,
|
|
1668
|
+
options.sourceBranchName,
|
|
1669
|
+
options.targetBranchName,
|
|
1670
|
+
options.title,
|
|
1671
|
+
{
|
|
1672
|
+
description: options.body
|
|
1673
|
+
}
|
|
1674
|
+
);
|
|
1675
|
+
return res.iid;
|
|
1676
|
+
}
|
|
1677
|
+
async function getGitlabRepoDefaultBranch(repoUrl, options) {
|
|
1678
|
+
const api2 = getGitBeaker({ gitlabAuthToken: options?.gitlabAuthToken });
|
|
1679
|
+
const { projectPath } = parseGitlabOwnerAndRepo(repoUrl);
|
|
1680
|
+
const project = await api2.Projects.show(projectPath);
|
|
1681
|
+
if (!project.default_branch) {
|
|
1682
|
+
throw new Error("no default branch");
|
|
1683
|
+
}
|
|
1684
|
+
return project.default_branch;
|
|
1685
|
+
}
|
|
1686
|
+
async function getGitlabReferenceData({ ref, gitlabUrl }, options) {
|
|
1687
|
+
const { projectPath } = parseGitlabOwnerAndRepo(gitlabUrl);
|
|
1688
|
+
const api2 = getGitBeaker({ gitlabAuthToken: options?.gitlabAuthToken });
|
|
1689
|
+
const results = await Promise.allSettled([
|
|
1690
|
+
(async () => {
|
|
1691
|
+
const res = await api2.Branches.show(projectPath, ref);
|
|
1692
|
+
return {
|
|
1693
|
+
sha: res.commit.id,
|
|
1694
|
+
type: "BRANCH" /* BRANCH */,
|
|
1695
|
+
date: res.commit.committed_date ? new Date(res.commit.committed_date) : void 0
|
|
1696
|
+
};
|
|
1697
|
+
})(),
|
|
1698
|
+
(async () => {
|
|
1699
|
+
const res = await api2.Commits.show(projectPath, ref);
|
|
1700
|
+
return {
|
|
1701
|
+
sha: res.id,
|
|
1702
|
+
type: "COMMIT" /* COMMIT */,
|
|
1703
|
+
date: res.committed_date ? new Date(res.committed_date) : void 0
|
|
1704
|
+
};
|
|
1705
|
+
})(),
|
|
1706
|
+
(async () => {
|
|
1707
|
+
const res = await api2.Tags.show(projectPath, ref);
|
|
1708
|
+
return {
|
|
1709
|
+
sha: res.commit.id,
|
|
1710
|
+
type: "TAG" /* TAG */,
|
|
1711
|
+
date: res.commit.committed_date ? new Date(res.commit.committed_date) : void 0
|
|
1712
|
+
};
|
|
1713
|
+
})()
|
|
1714
|
+
]);
|
|
1715
|
+
const [branchRes, commitRes, tagRes] = results;
|
|
1716
|
+
if (tagRes.status === "fulfilled") {
|
|
1717
|
+
return tagRes.value;
|
|
1718
|
+
}
|
|
1719
|
+
if (branchRes.status === "fulfilled") {
|
|
1720
|
+
return branchRes.value;
|
|
1721
|
+
}
|
|
1722
|
+
if (commitRes.status === "fulfilled") {
|
|
1723
|
+
return commitRes.value;
|
|
1724
|
+
}
|
|
1725
|
+
throw new RefNotFoundError(`ref: ${ref} does not exist`);
|
|
1726
|
+
}
|
|
1727
|
+
function parseGitlabOwnerAndRepo(gitlabUrl) {
|
|
1728
|
+
gitlabUrl = removeTrailingSlash2(gitlabUrl);
|
|
1729
|
+
const parsingResult = parseScmURL(gitlabUrl);
|
|
1730
|
+
if (!parsingResult || parsingResult.hostname !== "gitlab.com") {
|
|
1731
|
+
throw new InvalidUrlPatternError(`invalid gitlab repo Url ${gitlabUrl}`);
|
|
1732
|
+
}
|
|
1733
|
+
const { organization, repoName, projectPath } = parsingResult;
|
|
1734
|
+
return { owner: organization, repo: repoName, projectPath };
|
|
1735
|
+
}
|
|
1736
|
+
async function getGitlabBlameRanges({ ref, gitlabUrl, path: path9 }, options) {
|
|
1737
|
+
const { projectPath } = parseGitlabOwnerAndRepo(gitlabUrl);
|
|
1738
|
+
const api2 = getGitBeaker({ gitlabAuthToken: options?.gitlabAuthToken });
|
|
1739
|
+
const resp = await api2.RepositoryFiles.allFileBlames(projectPath, path9, ref);
|
|
1740
|
+
let lineNumber = 1;
|
|
1741
|
+
return resp.filter((range) => range.lines).map((range) => {
|
|
1742
|
+
const oldLineNumber = lineNumber;
|
|
1743
|
+
if (!range.lines) {
|
|
1744
|
+
throw new Error("range.lines should not be undefined");
|
|
1745
|
+
}
|
|
1746
|
+
lineNumber += range.lines.length;
|
|
1747
|
+
return {
|
|
1748
|
+
startingLine: oldLineNumber,
|
|
1749
|
+
endingLine: lineNumber - 1,
|
|
1750
|
+
login: range.commit.author_email,
|
|
1751
|
+
email: range.commit.author_email,
|
|
1752
|
+
name: range.commit.author_name
|
|
1753
|
+
};
|
|
1754
|
+
});
|
|
1755
|
+
}
|
|
1756
|
+
var GitlabAuthResultZ = z4.object({
|
|
1757
|
+
access_token: z4.string(),
|
|
1758
|
+
token_type: z4.string(),
|
|
1759
|
+
refresh_token: z4.string()
|
|
1760
|
+
});
|
|
1761
|
+
|
|
1448
1762
|
// src/features/analysis/scm/scmSubmit/index.ts
|
|
1449
1763
|
import fs from "node:fs/promises";
|
|
1450
1764
|
import os from "os";
|
|
1451
1765
|
import path3 from "path";
|
|
1452
1766
|
import { simpleGit as simpleGit2 } from "simple-git";
|
|
1453
1767
|
import tmp from "tmp";
|
|
1454
|
-
import { z as
|
|
1768
|
+
import { z as z6 } from "zod";
|
|
1455
1769
|
|
|
1456
1770
|
// src/features/analysis/scm/scmSubmit/types.ts
|
|
1457
|
-
import { z as
|
|
1458
|
-
var BaseSubmitToScmMessageZ =
|
|
1459
|
-
submitFixRequestId:
|
|
1460
|
-
fixes:
|
|
1461
|
-
|
|
1462
|
-
fixId:
|
|
1463
|
-
diff:
|
|
1771
|
+
import { z as z5 } from "zod";
|
|
1772
|
+
var BaseSubmitToScmMessageZ = z5.object({
|
|
1773
|
+
submitFixRequestId: z5.string().uuid(),
|
|
1774
|
+
fixes: z5.array(
|
|
1775
|
+
z5.object({
|
|
1776
|
+
fixId: z5.string().uuid(),
|
|
1777
|
+
diff: z5.string()
|
|
1464
1778
|
})
|
|
1465
1779
|
),
|
|
1466
|
-
commitHash:
|
|
1467
|
-
repoUrl:
|
|
1780
|
+
commitHash: z5.string(),
|
|
1781
|
+
repoUrl: z5.string()
|
|
1468
1782
|
});
|
|
1469
1783
|
var submitToScmMessageType = {
|
|
1470
1784
|
commitToSameBranch: "commitToSameBranch",
|
|
1471
1785
|
submitFixesForDifferentBranch: "submitFixesForDifferentBranch"
|
|
1472
1786
|
};
|
|
1473
1787
|
var CommitToSameBranchParamsZ = BaseSubmitToScmMessageZ.merge(
|
|
1474
|
-
|
|
1475
|
-
type:
|
|
1476
|
-
branch:
|
|
1477
|
-
commitMessage:
|
|
1478
|
-
commitDescription:
|
|
1479
|
-
githubCommentId:
|
|
1788
|
+
z5.object({
|
|
1789
|
+
type: z5.literal(submitToScmMessageType.commitToSameBranch),
|
|
1790
|
+
branch: z5.string(),
|
|
1791
|
+
commitMessage: z5.string(),
|
|
1792
|
+
commitDescription: z5.string().nullish(),
|
|
1793
|
+
githubCommentId: z5.number().nullish()
|
|
1480
1794
|
})
|
|
1481
1795
|
);
|
|
1482
|
-
var SubmitFixesToDifferentBranchParamsZ =
|
|
1483
|
-
type:
|
|
1484
|
-
submitBranch:
|
|
1485
|
-
baseBranch:
|
|
1796
|
+
var SubmitFixesToDifferentBranchParamsZ = z5.object({
|
|
1797
|
+
type: z5.literal(submitToScmMessageType.submitFixesForDifferentBranch),
|
|
1798
|
+
submitBranch: z5.string(),
|
|
1799
|
+
baseBranch: z5.string()
|
|
1486
1800
|
}).merge(BaseSubmitToScmMessageZ);
|
|
1487
|
-
var SubmitFixesMessageZ =
|
|
1801
|
+
var SubmitFixesMessageZ = z5.union([
|
|
1488
1802
|
CommitToSameBranchParamsZ,
|
|
1489
1803
|
SubmitFixesToDifferentBranchParamsZ
|
|
1490
1804
|
]);
|
|
1491
|
-
var FixResponseArrayZ =
|
|
1492
|
-
|
|
1493
|
-
fixId:
|
|
1805
|
+
var FixResponseArrayZ = z5.array(
|
|
1806
|
+
z5.object({
|
|
1807
|
+
fixId: z5.string().uuid()
|
|
1494
1808
|
})
|
|
1495
1809
|
);
|
|
1496
|
-
var SubmitFixesBaseResponseMessageZ =
|
|
1497
|
-
submitFixRequestId:
|
|
1498
|
-
submitBranches:
|
|
1499
|
-
|
|
1500
|
-
branchName:
|
|
1810
|
+
var SubmitFixesBaseResponseMessageZ = z5.object({
|
|
1811
|
+
submitFixRequestId: z5.string().uuid(),
|
|
1812
|
+
submitBranches: z5.array(
|
|
1813
|
+
z5.object({
|
|
1814
|
+
branchName: z5.string(),
|
|
1501
1815
|
fixes: FixResponseArrayZ
|
|
1502
1816
|
})
|
|
1503
1817
|
),
|
|
1504
|
-
error:
|
|
1505
|
-
type:
|
|
1818
|
+
error: z5.object({
|
|
1819
|
+
type: z5.enum([
|
|
1506
1820
|
"InitialRepoAccessError",
|
|
1507
1821
|
"PushBranchError",
|
|
1508
1822
|
"UnknownError"
|
|
1509
1823
|
]),
|
|
1510
|
-
info:
|
|
1511
|
-
message:
|
|
1512
|
-
pushBranchName:
|
|
1824
|
+
info: z5.object({
|
|
1825
|
+
message: z5.string(),
|
|
1826
|
+
pushBranchName: z5.string().optional()
|
|
1513
1827
|
})
|
|
1514
1828
|
}).optional()
|
|
1515
1829
|
});
|
|
1516
|
-
var SubmitFixesToSameBranchResponseMessageZ =
|
|
1517
|
-
type:
|
|
1518
|
-
githubCommentId:
|
|
1830
|
+
var SubmitFixesToSameBranchResponseMessageZ = z5.object({
|
|
1831
|
+
type: z5.literal(submitToScmMessageType.commitToSameBranch),
|
|
1832
|
+
githubCommentId: z5.number().nullish()
|
|
1519
1833
|
}).merge(SubmitFixesBaseResponseMessageZ);
|
|
1520
|
-
var SubmitFixesToDifferentBranchResponseMessageZ =
|
|
1521
|
-
type:
|
|
1522
|
-
githubCommentId:
|
|
1834
|
+
var SubmitFixesToDifferentBranchResponseMessageZ = z5.object({
|
|
1835
|
+
type: z5.literal(submitToScmMessageType.submitFixesForDifferentBranch),
|
|
1836
|
+
githubCommentId: z5.number().optional()
|
|
1523
1837
|
}).merge(SubmitFixesBaseResponseMessageZ);
|
|
1524
|
-
var
|
|
1838
|
+
var SubmitFixesResponseMessageZ = z5.discriminatedUnion("type", [
|
|
1525
1839
|
SubmitFixesToSameBranchResponseMessageZ,
|
|
1526
1840
|
SubmitFixesToDifferentBranchResponseMessageZ
|
|
1527
1841
|
]);
|
|
@@ -1539,7 +1853,7 @@ var isValidBranchName = async (branchName) => {
|
|
|
1539
1853
|
return false;
|
|
1540
1854
|
}
|
|
1541
1855
|
};
|
|
1542
|
-
var FixesZ =
|
|
1856
|
+
var FixesZ = z6.array(z6.object({ fixId: z6.string(), diff: z6.string() })).nonempty();
|
|
1543
1857
|
|
|
1544
1858
|
// src/features/analysis/scm/scm.ts
|
|
1545
1859
|
function getScmLibTypeFromUrl(url) {
|
|
@@ -1552,19 +1866,25 @@ function getScmLibTypeFromUrl(url) {
|
|
|
1552
1866
|
if (url.toLowerCase().startsWith("https://github.com/")) {
|
|
1553
1867
|
return "GITHUB" /* GITHUB */;
|
|
1554
1868
|
}
|
|
1869
|
+
if (url.toLowerCase().startsWith("https://dev.azure.com/")) {
|
|
1870
|
+
return "ADO" /* ADO */;
|
|
1871
|
+
}
|
|
1555
1872
|
return void 0;
|
|
1556
1873
|
}
|
|
1557
1874
|
async function scmCanReachRepo({
|
|
1558
1875
|
repoUrl,
|
|
1559
1876
|
githubToken,
|
|
1560
|
-
gitlabToken
|
|
1877
|
+
gitlabToken,
|
|
1878
|
+
adoToken,
|
|
1879
|
+
scmOrg
|
|
1561
1880
|
}) {
|
|
1562
1881
|
try {
|
|
1563
1882
|
const scmLibType = getScmLibTypeFromUrl(repoUrl);
|
|
1564
1883
|
await SCMLib.init({
|
|
1565
1884
|
url: repoUrl,
|
|
1566
|
-
accessToken: scmLibType === "GITHUB" /* GITHUB */ ? githubToken : scmLibType === "GITLAB" /* GITLAB */ ? gitlabToken : "",
|
|
1567
|
-
scmType: scmLibType
|
|
1885
|
+
accessToken: scmLibType === "GITHUB" /* GITHUB */ ? githubToken : scmLibType === "GITLAB" /* GITLAB */ ? gitlabToken : scmLibType === "ADO" /* ADO */ ? adoToken : "",
|
|
1886
|
+
scmType: scmLibType,
|
|
1887
|
+
scmOrg
|
|
1568
1888
|
});
|
|
1569
1889
|
return true;
|
|
1570
1890
|
} catch (e) {
|
|
@@ -1597,11 +1917,13 @@ var RepoNoTokenAccessError = class extends Error {
|
|
|
1597
1917
|
}
|
|
1598
1918
|
};
|
|
1599
1919
|
var SCMLib = class {
|
|
1600
|
-
constructor(url, accessToken) {
|
|
1920
|
+
constructor(url, accessToken, scmOrg) {
|
|
1601
1921
|
__publicField(this, "url");
|
|
1602
1922
|
__publicField(this, "accessToken");
|
|
1923
|
+
__publicField(this, "scmOrg");
|
|
1603
1924
|
this.accessToken = accessToken;
|
|
1604
1925
|
this.url = url;
|
|
1926
|
+
this.scmOrg = scmOrg;
|
|
1605
1927
|
}
|
|
1606
1928
|
async getUrlWithCredentials() {
|
|
1607
1929
|
if (!this.url) {
|
|
@@ -1612,9 +1934,13 @@ var SCMLib = class {
|
|
|
1612
1934
|
if (!this.accessToken) {
|
|
1613
1935
|
return trimmedUrl;
|
|
1614
1936
|
}
|
|
1615
|
-
const
|
|
1937
|
+
const scmLibType = getScmLibTypeFromUrl(trimmedUrl);
|
|
1938
|
+
if (scmLibType === "ADO" /* ADO */) {
|
|
1939
|
+
return `https://${this.accessToken}@${trimmedUrl.toLowerCase().replace("https://", "")}`;
|
|
1940
|
+
}
|
|
1616
1941
|
const is_http = trimmedUrl.toLowerCase().startsWith("http://");
|
|
1617
1942
|
const is_https = trimmedUrl.toLowerCase().startsWith("https://");
|
|
1943
|
+
const username = await this._getUsernameForAuthUrl();
|
|
1618
1944
|
if (is_http) {
|
|
1619
1945
|
return `http://${username}:${this.accessToken}@${trimmedUrl.toLowerCase().replace("http://", "")}`;
|
|
1620
1946
|
} else if (is_https) {
|
|
@@ -1642,7 +1968,8 @@ var SCMLib = class {
|
|
|
1642
1968
|
static async init({
|
|
1643
1969
|
url,
|
|
1644
1970
|
accessToken,
|
|
1645
|
-
scmType
|
|
1971
|
+
scmType,
|
|
1972
|
+
scmOrg
|
|
1646
1973
|
}) {
|
|
1647
1974
|
let trimmedUrl = void 0;
|
|
1648
1975
|
if (url) {
|
|
@@ -1650,12 +1977,17 @@ var SCMLib = class {
|
|
|
1650
1977
|
}
|
|
1651
1978
|
try {
|
|
1652
1979
|
if ("GITHUB" /* GITHUB */ === scmType) {
|
|
1653
|
-
const scm = new GithubSCMLib(trimmedUrl, accessToken);
|
|
1980
|
+
const scm = new GithubSCMLib(trimmedUrl, accessToken, scmOrg);
|
|
1654
1981
|
await scm.validateParams();
|
|
1655
1982
|
return scm;
|
|
1656
1983
|
}
|
|
1657
1984
|
if ("GITLAB" /* GITLAB */ === scmType) {
|
|
1658
|
-
const scm = new GitlabSCMLib(trimmedUrl, accessToken);
|
|
1985
|
+
const scm = new GitlabSCMLib(trimmedUrl, accessToken, scmOrg);
|
|
1986
|
+
await scm.validateParams();
|
|
1987
|
+
return scm;
|
|
1988
|
+
}
|
|
1989
|
+
if ("ADO" /* ADO */ === scmType) {
|
|
1990
|
+
const scm = new AdoSCMLib(trimmedUrl, accessToken, scmOrg);
|
|
1659
1991
|
await scm.validateParams();
|
|
1660
1992
|
return scm;
|
|
1661
1993
|
}
|
|
@@ -1664,7 +1996,170 @@ var SCMLib = class {
|
|
|
1664
1996
|
throw new RepoNoTokenAccessError("no access to repo");
|
|
1665
1997
|
}
|
|
1666
1998
|
}
|
|
1667
|
-
return new StubSCMLib(trimmedUrl);
|
|
1999
|
+
return new StubSCMLib(trimmedUrl, void 0, void 0);
|
|
2000
|
+
}
|
|
2001
|
+
};
|
|
2002
|
+
var AdoSCMLib = class extends SCMLib {
|
|
2003
|
+
updatePrComment(_params, _oktokit) {
|
|
2004
|
+
throw new Error("updatePrComment not implemented.");
|
|
2005
|
+
}
|
|
2006
|
+
getPrComment(_commentId) {
|
|
2007
|
+
throw new Error("getPrComment not implemented.");
|
|
2008
|
+
}
|
|
2009
|
+
async forkRepo() {
|
|
2010
|
+
throw new Error("forkRepo not supported yet");
|
|
2011
|
+
}
|
|
2012
|
+
async createOrUpdateRepositorySecret() {
|
|
2013
|
+
throw new Error("createOrUpdateRepositorySecret not supported yet");
|
|
2014
|
+
}
|
|
2015
|
+
async createPullRequestWithNewFile(_sourceRepoUrl, _filesPaths, _userRepoUrl, _title, _body) {
|
|
2016
|
+
throw new Error("createPullRequestWithNewFile not supported yet");
|
|
2017
|
+
}
|
|
2018
|
+
async createSubmitRequest(targetBranchName, sourceBranchName, title, body) {
|
|
2019
|
+
if (!this.accessToken || !this.url) {
|
|
2020
|
+
console.error("no access token or no url");
|
|
2021
|
+
throw new Error("no access token or no url");
|
|
2022
|
+
}
|
|
2023
|
+
return String(
|
|
2024
|
+
await createAdoPullRequest({
|
|
2025
|
+
title,
|
|
2026
|
+
body,
|
|
2027
|
+
targetBranchName,
|
|
2028
|
+
sourceBranchName,
|
|
2029
|
+
repoUrl: this.url,
|
|
2030
|
+
accessToken: this.accessToken,
|
|
2031
|
+
tokenOrg: this.scmOrg
|
|
2032
|
+
})
|
|
2033
|
+
);
|
|
2034
|
+
}
|
|
2035
|
+
async validateParams() {
|
|
2036
|
+
return adoValidateParams({
|
|
2037
|
+
url: this.url,
|
|
2038
|
+
accessToken: this.accessToken,
|
|
2039
|
+
tokenOrg: this.scmOrg
|
|
2040
|
+
});
|
|
2041
|
+
}
|
|
2042
|
+
async getRepoList(scmOrg) {
|
|
2043
|
+
if (!this.accessToken) {
|
|
2044
|
+
console.error("no access token");
|
|
2045
|
+
throw new Error("no access token");
|
|
2046
|
+
}
|
|
2047
|
+
return getAdoRepoList({
|
|
2048
|
+
orgName: scmOrg,
|
|
2049
|
+
tokenOrg: this.scmOrg,
|
|
2050
|
+
accessToken: this.accessToken
|
|
2051
|
+
});
|
|
2052
|
+
}
|
|
2053
|
+
async getBranchList() {
|
|
2054
|
+
if (!this.accessToken || !this.url) {
|
|
2055
|
+
console.error("no access token or no url");
|
|
2056
|
+
throw new Error("no access token or no url");
|
|
2057
|
+
}
|
|
2058
|
+
return getAdoBranchList({
|
|
2059
|
+
accessToken: this.accessToken,
|
|
2060
|
+
tokenOrg: this.scmOrg,
|
|
2061
|
+
repoUrl: this.url
|
|
2062
|
+
});
|
|
2063
|
+
}
|
|
2064
|
+
getAuthHeaders() {
|
|
2065
|
+
if (this.accessToken) {
|
|
2066
|
+
if (getAdoTokenType(this.accessToken) === "OAUTH" /* OAUTH */) {
|
|
2067
|
+
return {
|
|
2068
|
+
authorization: `Bearer ${this.accessToken}`
|
|
2069
|
+
};
|
|
2070
|
+
} else {
|
|
2071
|
+
return {
|
|
2072
|
+
authorization: `Basic ${Buffer.from(":" + this.accessToken).toString(
|
|
2073
|
+
"base64"
|
|
2074
|
+
)}`
|
|
2075
|
+
};
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
return {};
|
|
2079
|
+
}
|
|
2080
|
+
getDownloadUrl(sha) {
|
|
2081
|
+
if (!this.url) {
|
|
2082
|
+
console.error("no url");
|
|
2083
|
+
throw new Error("no url");
|
|
2084
|
+
}
|
|
2085
|
+
return getAdoDownloadUrl({ repoUrl: this.url, branch: sha });
|
|
2086
|
+
}
|
|
2087
|
+
async _getUsernameForAuthUrl() {
|
|
2088
|
+
throw new Error("_getUsernameForAuthUrl() is not relevant for ADO");
|
|
2089
|
+
}
|
|
2090
|
+
async getIsRemoteBranch(branch) {
|
|
2091
|
+
if (!this.accessToken || !this.url) {
|
|
2092
|
+
console.error("no access token or no url");
|
|
2093
|
+
throw new Error("no access token or no url");
|
|
2094
|
+
}
|
|
2095
|
+
return getAdoIsRemoteBranch({
|
|
2096
|
+
accessToken: this.accessToken,
|
|
2097
|
+
tokenOrg: this.scmOrg,
|
|
2098
|
+
repoUrl: this.url,
|
|
2099
|
+
branch
|
|
2100
|
+
});
|
|
2101
|
+
}
|
|
2102
|
+
async getUserHasAccessToRepo() {
|
|
2103
|
+
if (!this.accessToken || !this.url) {
|
|
2104
|
+
console.error("no access token or no url");
|
|
2105
|
+
throw new Error("no access token or no url");
|
|
2106
|
+
}
|
|
2107
|
+
return getAdoIsUserCollaborator({
|
|
2108
|
+
accessToken: this.accessToken,
|
|
2109
|
+
tokenOrg: this.scmOrg,
|
|
2110
|
+
repoUrl: this.url
|
|
2111
|
+
});
|
|
2112
|
+
}
|
|
2113
|
+
async getUsername() {
|
|
2114
|
+
throw new Error("getUsername() is not relevant for ADO");
|
|
2115
|
+
}
|
|
2116
|
+
async getSubmitRequestStatus(scmSubmitRequestId) {
|
|
2117
|
+
if (!this.accessToken || !this.url) {
|
|
2118
|
+
console.error("no access token or no url");
|
|
2119
|
+
throw new Error("no access token or no url");
|
|
2120
|
+
}
|
|
2121
|
+
const state = await getAdoPullRequestStatus({
|
|
2122
|
+
accessToken: this.accessToken,
|
|
2123
|
+
tokenOrg: this.scmOrg,
|
|
2124
|
+
repoUrl: this.url,
|
|
2125
|
+
prNumber: Number(scmSubmitRequestId)
|
|
2126
|
+
});
|
|
2127
|
+
switch (state) {
|
|
2128
|
+
case "completed" /* completed */:
|
|
2129
|
+
return "MERGED" /* MERGED */;
|
|
2130
|
+
case "active" /* active */:
|
|
2131
|
+
return "OPEN" /* OPEN */;
|
|
2132
|
+
case "abandoned" /* abandoned */:
|
|
2133
|
+
return "CLOSED" /* CLOSED */;
|
|
2134
|
+
default:
|
|
2135
|
+
throw new Error(`unknown state ${state}`);
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
async getRepoBlameRanges(_ref, _path) {
|
|
2139
|
+
return await getAdoBlameRanges();
|
|
2140
|
+
}
|
|
2141
|
+
async getReferenceData(ref) {
|
|
2142
|
+
if (!this.url) {
|
|
2143
|
+
console.error("no url");
|
|
2144
|
+
throw new Error("no url");
|
|
2145
|
+
}
|
|
2146
|
+
return await getAdoReferenceData({
|
|
2147
|
+
ref,
|
|
2148
|
+
repoUrl: this.url,
|
|
2149
|
+
accessToken: this.accessToken,
|
|
2150
|
+
tokenOrg: this.scmOrg
|
|
2151
|
+
});
|
|
2152
|
+
}
|
|
2153
|
+
async getRepoDefaultBranch() {
|
|
2154
|
+
if (!this.url) {
|
|
2155
|
+
console.error("no url");
|
|
2156
|
+
throw new Error("no url");
|
|
2157
|
+
}
|
|
2158
|
+
return await getAdoRepoDefaultBranch({
|
|
2159
|
+
repoUrl: this.url,
|
|
2160
|
+
tokenOrg: this.scmOrg,
|
|
2161
|
+
accessToken: this.accessToken
|
|
2162
|
+
});
|
|
1668
2163
|
}
|
|
1669
2164
|
};
|
|
1670
2165
|
var GitlabSCMLib = class extends SCMLib {
|
|
@@ -1707,7 +2202,7 @@ var GitlabSCMLib = class extends SCMLib {
|
|
|
1707
2202
|
async createPullRequestWithNewFile(_sourceRepoUrl, _filesPaths, _userRepoUrl, _title, _body) {
|
|
1708
2203
|
throw new Error("not implemented");
|
|
1709
2204
|
}
|
|
1710
|
-
async getRepoList() {
|
|
2205
|
+
async getRepoList(_scmOrg) {
|
|
1711
2206
|
if (!this.accessToken) {
|
|
1712
2207
|
console.error("no access token");
|
|
1713
2208
|
throw new Error("no access token");
|
|
@@ -1795,13 +2290,13 @@ var GitlabSCMLib = class extends SCMLib {
|
|
|
1795
2290
|
throw new Error(`unknown state ${state}`);
|
|
1796
2291
|
}
|
|
1797
2292
|
}
|
|
1798
|
-
async getRepoBlameRanges(ref,
|
|
2293
|
+
async getRepoBlameRanges(ref, path9) {
|
|
1799
2294
|
if (!this.url) {
|
|
1800
2295
|
console.error("no url");
|
|
1801
2296
|
throw new Error("no url");
|
|
1802
2297
|
}
|
|
1803
2298
|
return await getGitlabBlameRanges(
|
|
1804
|
-
{ ref, path:
|
|
2299
|
+
{ ref, path: path9, gitlabUrl: this.url },
|
|
1805
2300
|
{
|
|
1806
2301
|
gitlabAuthToken: this.accessToken
|
|
1807
2302
|
}
|
|
@@ -1837,8 +2332,8 @@ var GitlabSCMLib = class extends SCMLib {
|
|
|
1837
2332
|
};
|
|
1838
2333
|
var GithubSCMLib = class extends SCMLib {
|
|
1839
2334
|
// we don't always need a url, what's important is that we have an access token
|
|
1840
|
-
constructor(url, accessToken) {
|
|
1841
|
-
super(url, accessToken);
|
|
2335
|
+
constructor(url, accessToken, scmOrg) {
|
|
2336
|
+
super(url, accessToken, scmOrg);
|
|
1842
2337
|
__publicField(this, "oktokit");
|
|
1843
2338
|
this.oktokit = new Octokit2({ auth: accessToken });
|
|
1844
2339
|
}
|
|
@@ -1873,7 +2368,7 @@ var GithubSCMLib = class extends SCMLib {
|
|
|
1873
2368
|
throw new Error("cannot delete comment without access token or url");
|
|
1874
2369
|
}
|
|
1875
2370
|
const oktokit = _oktokit || this.oktokit;
|
|
1876
|
-
const { owner, repo } =
|
|
2371
|
+
const { owner, repo } = parseGithubOwnerAndRepo(this.url);
|
|
1877
2372
|
const { data: repositoryPublicKeyResponse } = await getARepositoryPublicKey(
|
|
1878
2373
|
oktokit,
|
|
1879
2374
|
{
|
|
@@ -1914,7 +2409,7 @@ var GithubSCMLib = class extends SCMLib {
|
|
|
1914
2409
|
throw new Error("cannot post on PR without access token or url");
|
|
1915
2410
|
}
|
|
1916
2411
|
const oktokit = _oktokit || this.oktokit;
|
|
1917
|
-
const { owner, repo } =
|
|
2412
|
+
const { owner, repo } = parseGithubOwnerAndRepo(this.url);
|
|
1918
2413
|
return postPrComment(oktokit, {
|
|
1919
2414
|
...params,
|
|
1920
2415
|
owner,
|
|
@@ -1926,7 +2421,7 @@ var GithubSCMLib = class extends SCMLib {
|
|
|
1926
2421
|
throw new Error("cannot update on PR without access token or url");
|
|
1927
2422
|
}
|
|
1928
2423
|
const oktokit = _oktokit || this.oktokit;
|
|
1929
|
-
const { owner, repo } =
|
|
2424
|
+
const { owner, repo } = parseGithubOwnerAndRepo(this.url);
|
|
1930
2425
|
return updatePrComment(oktokit, {
|
|
1931
2426
|
...params,
|
|
1932
2427
|
owner,
|
|
@@ -1938,7 +2433,7 @@ var GithubSCMLib = class extends SCMLib {
|
|
|
1938
2433
|
throw new Error("cannot delete comment without access token or url");
|
|
1939
2434
|
}
|
|
1940
2435
|
const oktokit = _oktokit || this.oktokit;
|
|
1941
|
-
const { owner, repo } =
|
|
2436
|
+
const { owner, repo } = parseGithubOwnerAndRepo(this.url);
|
|
1942
2437
|
return deleteComment(oktokit, {
|
|
1943
2438
|
...params,
|
|
1944
2439
|
owner,
|
|
@@ -1950,7 +2445,7 @@ var GithubSCMLib = class extends SCMLib {
|
|
|
1950
2445
|
throw new Error("cannot get Pr Comments without access token or url");
|
|
1951
2446
|
}
|
|
1952
2447
|
const oktokit = _oktokit || this.oktokit;
|
|
1953
|
-
const { owner, repo } =
|
|
2448
|
+
const { owner, repo } = parseGithubOwnerAndRepo(this.url);
|
|
1954
2449
|
return getPrComments(oktokit, {
|
|
1955
2450
|
per_page: 100,
|
|
1956
2451
|
...params,
|
|
@@ -1962,15 +2457,15 @@ var GithubSCMLib = class extends SCMLib {
|
|
|
1962
2457
|
if (!this.accessToken || !this.url) {
|
|
1963
2458
|
throw new Error("cannot get Pr Comments without access token or url");
|
|
1964
2459
|
}
|
|
1965
|
-
const { owner, repo } =
|
|
2460
|
+
const { owner, repo } = parseGithubOwnerAndRepo(this.url);
|
|
1966
2461
|
const prRes = await getPrDiff(this.oktokit, {
|
|
1967
2462
|
...params,
|
|
1968
2463
|
owner,
|
|
1969
2464
|
repo
|
|
1970
2465
|
});
|
|
1971
|
-
return
|
|
2466
|
+
return z7.string().parse(prRes.data);
|
|
1972
2467
|
}
|
|
1973
|
-
async getRepoList() {
|
|
2468
|
+
async getRepoList(_scmOrg) {
|
|
1974
2469
|
if (!this.accessToken) {
|
|
1975
2470
|
console.error("no access token");
|
|
1976
2471
|
throw new Error("no access token");
|
|
@@ -2042,13 +2537,13 @@ var GithubSCMLib = class extends SCMLib {
|
|
|
2042
2537
|
}
|
|
2043
2538
|
throw new Error(`unknown state ${state}`);
|
|
2044
2539
|
}
|
|
2045
|
-
async getRepoBlameRanges(ref,
|
|
2540
|
+
async getRepoBlameRanges(ref, path9) {
|
|
2046
2541
|
if (!this.url) {
|
|
2047
2542
|
console.error("no url");
|
|
2048
2543
|
throw new Error("no url");
|
|
2049
2544
|
}
|
|
2050
2545
|
return await getGithubBlameRanges(
|
|
2051
|
-
{ ref, path:
|
|
2546
|
+
{ ref, path: path9, gitHubUrl: this.url },
|
|
2052
2547
|
{
|
|
2053
2548
|
githubAuthToken: this.accessToken
|
|
2054
2549
|
}
|
|
@@ -2071,7 +2566,7 @@ var GithubSCMLib = class extends SCMLib {
|
|
|
2071
2566
|
console.error("no url");
|
|
2072
2567
|
throw new Error("no url");
|
|
2073
2568
|
}
|
|
2074
|
-
const { owner, repo } =
|
|
2569
|
+
const { owner, repo } = parseGithubOwnerAndRepo(this.url);
|
|
2075
2570
|
return await getPrComment(this.oktokit, {
|
|
2076
2571
|
repo,
|
|
2077
2572
|
owner,
|
|
@@ -2125,9 +2620,9 @@ var StubSCMLib = class extends SCMLib {
|
|
|
2125
2620
|
console.error("createPullRequestWithNewFile() not implemented");
|
|
2126
2621
|
throw new Error("createPullRequestWithNewFile() not implemented");
|
|
2127
2622
|
}
|
|
2128
|
-
async getRepoList() {
|
|
2129
|
-
console.error("
|
|
2130
|
-
throw new Error("
|
|
2623
|
+
async getRepoList(_scmOrg) {
|
|
2624
|
+
console.error("getRepoList() not implemented");
|
|
2625
|
+
throw new Error("getRepoList() not implemented");
|
|
2131
2626
|
}
|
|
2132
2627
|
async getBranchList() {
|
|
2133
2628
|
console.error("getBranchList() not implemented");
|
|
@@ -2167,196 +2662,415 @@ var StubSCMLib = class extends SCMLib {
|
|
|
2167
2662
|
}
|
|
2168
2663
|
};
|
|
2169
2664
|
|
|
2170
|
-
// src/features/analysis/scm/
|
|
2171
|
-
function
|
|
2665
|
+
// src/features/analysis/scm/ado.ts
|
|
2666
|
+
function removeTrailingSlash3(str) {
|
|
2172
2667
|
return str.trim().replace(/\/+$/, "");
|
|
2173
2668
|
}
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2669
|
+
async function _getOrgsForOauthToken({ oauthToken }) {
|
|
2670
|
+
const profileZ = z8.object({
|
|
2671
|
+
displayName: z8.string(),
|
|
2672
|
+
publicAlias: z8.string().min(1),
|
|
2673
|
+
emailAddress: z8.string(),
|
|
2674
|
+
coreRevision: z8.number(),
|
|
2675
|
+
timeStamp: z8.string(),
|
|
2676
|
+
id: z8.string(),
|
|
2677
|
+
revision: z8.number()
|
|
2678
|
+
});
|
|
2679
|
+
const accountsZ = z8.object({
|
|
2680
|
+
count: z8.number(),
|
|
2681
|
+
value: z8.array(
|
|
2682
|
+
z8.object({
|
|
2683
|
+
accountId: z8.string(),
|
|
2684
|
+
accountUri: z8.string(),
|
|
2685
|
+
accountName: z8.string()
|
|
2686
|
+
})
|
|
2687
|
+
)
|
|
2688
|
+
});
|
|
2689
|
+
const profileRes = await fetch(
|
|
2690
|
+
"https://app.vssps.visualstudio.com/_apis/profile/profiles/me?api-version=6.0",
|
|
2691
|
+
{
|
|
2692
|
+
method: "GET",
|
|
2693
|
+
headers: {
|
|
2694
|
+
Authorization: `Bearer ${oauthToken}`
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
);
|
|
2698
|
+
const profileJson = await profileRes.json();
|
|
2699
|
+
const profile = profileZ.parse(profileJson);
|
|
2700
|
+
const accountsRes = await fetch(
|
|
2701
|
+
`https://app.vssps.visualstudio.com/_apis/accounts?memberId=${profile.publicAlias}&api-version=6.0`,
|
|
2702
|
+
{
|
|
2703
|
+
method: "GET",
|
|
2704
|
+
headers: {
|
|
2705
|
+
Authorization: `Bearer ${oauthToken}`
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
);
|
|
2709
|
+
const accountsJson = await accountsRes.json();
|
|
2710
|
+
const accounts = accountsZ.parse(accountsJson);
|
|
2711
|
+
const orgs = accounts.value.map((account) => account.accountName).filter((value, index, array) => array.indexOf(value) === index);
|
|
2712
|
+
return orgs;
|
|
2713
|
+
}
|
|
2714
|
+
function _getPublicAdoClient({ orgName }) {
|
|
2715
|
+
const orgUrl = `https://dev.azure.com/${orgName}`;
|
|
2716
|
+
const authHandler = api.getPersonalAccessTokenHandler("");
|
|
2717
|
+
authHandler.canHandleAuthentication = () => false;
|
|
2718
|
+
authHandler.prepareRequest = (_options) => {
|
|
2719
|
+
return;
|
|
2720
|
+
};
|
|
2721
|
+
const connection = new api.WebApi(orgUrl, authHandler);
|
|
2722
|
+
return connection;
|
|
2723
|
+
}
|
|
2724
|
+
function getAdoTokenType(token) {
|
|
2725
|
+
if (token.includes(".")) {
|
|
2726
|
+
return "OAUTH" /* OAUTH */;
|
|
2182
2727
|
}
|
|
2183
|
-
return
|
|
2728
|
+
return "PAT" /* PAT */;
|
|
2184
2729
|
}
|
|
2185
|
-
async function
|
|
2730
|
+
async function getAdoApiClient({
|
|
2731
|
+
accessToken,
|
|
2732
|
+
tokenOrg,
|
|
2733
|
+
orgName
|
|
2734
|
+
}) {
|
|
2735
|
+
if (!accessToken || tokenOrg && tokenOrg !== orgName) {
|
|
2736
|
+
return _getPublicAdoClient({ orgName });
|
|
2737
|
+
}
|
|
2738
|
+
const orgUrl = `https://dev.azure.com/${orgName}`;
|
|
2739
|
+
if (getAdoTokenType(accessToken) === "OAUTH" /* OAUTH */) {
|
|
2740
|
+
const connection2 = new api.WebApi(orgUrl, api.getBearerHandler(accessToken));
|
|
2741
|
+
return connection2;
|
|
2742
|
+
}
|
|
2743
|
+
const authHandler = api.getPersonalAccessTokenHandler(accessToken);
|
|
2744
|
+
const connection = new api.WebApi(orgUrl, authHandler);
|
|
2745
|
+
return connection;
|
|
2746
|
+
}
|
|
2747
|
+
async function adoValidateParams({
|
|
2186
2748
|
url,
|
|
2187
|
-
accessToken
|
|
2749
|
+
accessToken,
|
|
2750
|
+
tokenOrg
|
|
2188
2751
|
}) {
|
|
2189
2752
|
try {
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2753
|
+
if (!url && accessToken && getAdoTokenType(accessToken) === "OAUTH" /* OAUTH */) {
|
|
2754
|
+
await _getOrgsForOauthToken({ oauthToken: accessToken });
|
|
2755
|
+
return;
|
|
2193
2756
|
}
|
|
2757
|
+
let org = tokenOrg;
|
|
2194
2758
|
if (url) {
|
|
2195
|
-
const {
|
|
2196
|
-
|
|
2759
|
+
const { owner } = parseAdoOwnerAndRepo(url);
|
|
2760
|
+
org = owner;
|
|
2761
|
+
}
|
|
2762
|
+
if (!org) {
|
|
2763
|
+
throw new InvalidRepoUrlError(`invalid ADO ORG ${org}`);
|
|
2197
2764
|
}
|
|
2765
|
+
const api2 = await getAdoApiClient({
|
|
2766
|
+
accessToken,
|
|
2767
|
+
tokenOrg,
|
|
2768
|
+
orgName: org
|
|
2769
|
+
});
|
|
2770
|
+
await api2.connect();
|
|
2198
2771
|
} catch (e) {
|
|
2199
2772
|
const error = e;
|
|
2200
2773
|
const code = error.code || error.status || error.statusCode || error.response?.status || error.response?.statusCode || error.response?.code;
|
|
2201
2774
|
const description = error.description || `${e}`;
|
|
2202
2775
|
if (code === 401 || code === 403 || description.includes("401") || description.includes("403")) {
|
|
2203
|
-
throw new InvalidAccessTokenError(`invalid
|
|
2776
|
+
throw new InvalidAccessTokenError(`invalid ADO access token`);
|
|
2204
2777
|
}
|
|
2205
2778
|
if (code === 404 || description.includes("404") || description.includes("Not Found")) {
|
|
2206
|
-
throw new InvalidRepoUrlError(`invalid
|
|
2779
|
+
throw new InvalidRepoUrlError(`invalid ADO repo URL ${url}`);
|
|
2207
2780
|
}
|
|
2208
2781
|
throw e;
|
|
2209
2782
|
}
|
|
2210
2783
|
}
|
|
2211
|
-
async function
|
|
2212
|
-
const api = getGitBeaker({ gitlabAuthToken: accessToken });
|
|
2213
|
-
const res = await api.Users.showCurrentUser();
|
|
2214
|
-
return res.username;
|
|
2215
|
-
}
|
|
2216
|
-
async function getGitlabIsUserCollaborator({
|
|
2217
|
-
username,
|
|
2784
|
+
async function getAdoIsUserCollaborator({
|
|
2218
2785
|
accessToken,
|
|
2786
|
+
tokenOrg,
|
|
2219
2787
|
repoUrl
|
|
2220
2788
|
}) {
|
|
2221
2789
|
try {
|
|
2222
|
-
const {
|
|
2223
|
-
const
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2790
|
+
const { owner, repo, projectName } = parseAdoOwnerAndRepo(repoUrl);
|
|
2791
|
+
const api2 = await getAdoApiClient({
|
|
2792
|
+
accessToken,
|
|
2793
|
+
tokenOrg,
|
|
2794
|
+
orgName: owner
|
|
2227
2795
|
});
|
|
2228
|
-
|
|
2796
|
+
const git = await api2.getGitApi();
|
|
2797
|
+
const branches = await git.getBranches(repo, projectName);
|
|
2798
|
+
if (!branches || branches.length === 0) {
|
|
2799
|
+
throw new InvalidRepoUrlError("no branches");
|
|
2800
|
+
}
|
|
2801
|
+
return true;
|
|
2229
2802
|
} catch (e) {
|
|
2230
2803
|
return false;
|
|
2231
2804
|
}
|
|
2232
2805
|
}
|
|
2233
|
-
|
|
2806
|
+
var adoStatusNumberToEnumMap = {
|
|
2807
|
+
1: "active" /* active */,
|
|
2808
|
+
2: "abandoned" /* abandoned */,
|
|
2809
|
+
3: "completed" /* completed */,
|
|
2810
|
+
4: "all" /* all */
|
|
2811
|
+
};
|
|
2812
|
+
async function getAdoPullRequestStatus({
|
|
2234
2813
|
accessToken,
|
|
2814
|
+
tokenOrg,
|
|
2235
2815
|
repoUrl,
|
|
2236
|
-
|
|
2816
|
+
prNumber
|
|
2237
2817
|
}) {
|
|
2238
|
-
const {
|
|
2239
|
-
const
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2818
|
+
const { owner, repo, projectName } = parseAdoOwnerAndRepo(repoUrl);
|
|
2819
|
+
const api2 = await getAdoApiClient({
|
|
2820
|
+
accessToken,
|
|
2821
|
+
tokenOrg,
|
|
2822
|
+
orgName: owner
|
|
2823
|
+
});
|
|
2824
|
+
const git = await api2.getGitApi();
|
|
2825
|
+
const res = await git.getPullRequest(repo, prNumber, projectName);
|
|
2826
|
+
if (!res.status || res.status < 1 || res.status > 3) {
|
|
2827
|
+
throw new Error("bad pr status for ADO");
|
|
2248
2828
|
}
|
|
2829
|
+
return adoStatusNumberToEnumMap[res.status];
|
|
2249
2830
|
}
|
|
2250
|
-
async function
|
|
2831
|
+
async function getAdoIsRemoteBranch({
|
|
2251
2832
|
accessToken,
|
|
2833
|
+
tokenOrg,
|
|
2252
2834
|
repoUrl,
|
|
2253
2835
|
branch
|
|
2254
2836
|
}) {
|
|
2255
|
-
const {
|
|
2256
|
-
const
|
|
2837
|
+
const { owner, repo, projectName } = parseAdoOwnerAndRepo(repoUrl);
|
|
2838
|
+
const api2 = await getAdoApiClient({
|
|
2839
|
+
accessToken,
|
|
2840
|
+
tokenOrg,
|
|
2841
|
+
orgName: owner
|
|
2842
|
+
});
|
|
2843
|
+
const git = await api2.getGitApi();
|
|
2257
2844
|
try {
|
|
2258
|
-
const
|
|
2259
|
-
|
|
2845
|
+
const branchStatus = await git.getBranch(repo, branch, projectName);
|
|
2846
|
+
if (!branchStatus || !branchStatus.commit) {
|
|
2847
|
+
throw new InvalidRepoUrlError("no branch status");
|
|
2848
|
+
}
|
|
2849
|
+
return branchStatus.name === branch;
|
|
2260
2850
|
} catch (e) {
|
|
2261
2851
|
return false;
|
|
2262
2852
|
}
|
|
2263
2853
|
}
|
|
2264
|
-
async function
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
const
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2854
|
+
async function getAdoRepoList({
|
|
2855
|
+
orgName,
|
|
2856
|
+
tokenOrg,
|
|
2857
|
+
accessToken
|
|
2858
|
+
}) {
|
|
2859
|
+
let orgs = [];
|
|
2860
|
+
if (getAdoTokenType(accessToken) === "OAUTH" /* OAUTH */) {
|
|
2861
|
+
orgs = await _getOrgsForOauthToken({ oauthToken: accessToken });
|
|
2862
|
+
}
|
|
2863
|
+
if (orgs.length === 0 && !orgName) {
|
|
2864
|
+
throw new Error(`no orgs for ADO`);
|
|
2865
|
+
} else if (orgs.length === 0 && orgName) {
|
|
2866
|
+
orgs = [orgName];
|
|
2867
|
+
}
|
|
2868
|
+
const repos = (await Promise.allSettled(
|
|
2869
|
+
orgs.map(async (org) => {
|
|
2870
|
+
const orgApi = await getAdoApiClient({
|
|
2871
|
+
accessToken,
|
|
2872
|
+
tokenOrg,
|
|
2873
|
+
orgName: org
|
|
2874
|
+
});
|
|
2875
|
+
const gitOrg = await orgApi.getGitApi();
|
|
2876
|
+
const orgRepos = await gitOrg.getRepositories();
|
|
2877
|
+
const repoInfoList = (await Promise.allSettled(
|
|
2878
|
+
orgRepos.map(async (repo) => {
|
|
2879
|
+
if (!repo.name || !repo.remoteUrl || !repo.defaultBranch) {
|
|
2880
|
+
throw new InvalidRepoUrlError("bad repo");
|
|
2881
|
+
}
|
|
2882
|
+
const branch = await gitOrg.getBranch(
|
|
2883
|
+
repo.name,
|
|
2884
|
+
repo.defaultBranch.replace(/^refs\/heads\//, ""),
|
|
2885
|
+
repo.project?.name
|
|
2886
|
+
);
|
|
2887
|
+
return {
|
|
2888
|
+
repoName: repo.name,
|
|
2889
|
+
repoUrl: repo.remoteUrl.replace(
|
|
2890
|
+
/^[hH][tT][tT][pP][sS]:\/\/[^/]+@/,
|
|
2891
|
+
"https://"
|
|
2892
|
+
),
|
|
2893
|
+
repoOwner: org,
|
|
2894
|
+
repoIsPublic: repo.project?.visibility === 2,
|
|
2895
|
+
//2 is public in the ADO API
|
|
2896
|
+
repoLanguages: [],
|
|
2897
|
+
repoUpdatedAt: branch.commit?.committer?.date?.toDateString() || repo.project?.lastUpdateTime?.toDateString() || (/* @__PURE__ */ new Date()).toDateString()
|
|
2898
|
+
};
|
|
2899
|
+
})
|
|
2900
|
+
)).reduce((acc, res) => {
|
|
2901
|
+
if (res.status === "fulfilled") {
|
|
2902
|
+
acc.push(res.value);
|
|
2903
|
+
}
|
|
2904
|
+
return acc;
|
|
2905
|
+
}, []);
|
|
2906
|
+
return repoInfoList;
|
|
2290
2907
|
})
|
|
2291
|
-
)
|
|
2908
|
+
)).reduce((acc, res) => {
|
|
2909
|
+
if (res.status === "fulfilled") {
|
|
2910
|
+
return acc.concat(res.value);
|
|
2911
|
+
}
|
|
2912
|
+
return acc;
|
|
2913
|
+
}, []);
|
|
2914
|
+
return repos;
|
|
2292
2915
|
}
|
|
2293
|
-
|
|
2916
|
+
function getAdoDownloadUrl({
|
|
2917
|
+
repoUrl,
|
|
2918
|
+
branch
|
|
2919
|
+
}) {
|
|
2920
|
+
const { owner, repo, projectName } = parseAdoOwnerAndRepo(repoUrl);
|
|
2921
|
+
return `https://dev.azure.com/${owner}/${projectName}/_apis/git/repositories/${repo}/items/items?path=/&versionDescriptor[versionOptions]=0&versionDescriptor[versionType]=commit&versionDescriptor[version]=${branch}&resolveLfs=true&$format=zip&api-version=5.0&download=true`;
|
|
2922
|
+
}
|
|
2923
|
+
async function getAdoBranchList({
|
|
2294
2924
|
accessToken,
|
|
2925
|
+
tokenOrg,
|
|
2295
2926
|
repoUrl
|
|
2296
2927
|
}) {
|
|
2297
|
-
const {
|
|
2298
|
-
const
|
|
2928
|
+
const { owner, repo, projectName } = parseAdoOwnerAndRepo(repoUrl);
|
|
2929
|
+
const api2 = await getAdoApiClient({
|
|
2930
|
+
accessToken,
|
|
2931
|
+
tokenOrg,
|
|
2932
|
+
orgName: owner
|
|
2933
|
+
});
|
|
2934
|
+
const git = await api2.getGitApi();
|
|
2299
2935
|
try {
|
|
2300
|
-
const res = await
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2936
|
+
const res = await git.getBranches(repo, projectName);
|
|
2937
|
+
res.sort((a, b) => {
|
|
2938
|
+
if (!a.commit?.committer?.date || !b.commit?.committer?.date) {
|
|
2939
|
+
return 0;
|
|
2940
|
+
}
|
|
2941
|
+
return b.commit?.committer?.date.getTime() - a.commit?.committer?.date.getTime();
|
|
2305
2942
|
});
|
|
2306
|
-
return res.
|
|
2943
|
+
return res.reduce((acc, branch) => {
|
|
2944
|
+
if (!branch.name) {
|
|
2945
|
+
return acc;
|
|
2946
|
+
}
|
|
2947
|
+
acc.push(branch.name);
|
|
2948
|
+
return acc;
|
|
2949
|
+
}, []);
|
|
2307
2950
|
} catch (e) {
|
|
2308
2951
|
return [];
|
|
2309
2952
|
}
|
|
2310
2953
|
}
|
|
2311
|
-
async function
|
|
2312
|
-
const {
|
|
2313
|
-
const
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2954
|
+
async function createAdoPullRequest(options) {
|
|
2955
|
+
const { owner, repo, projectName } = parseAdoOwnerAndRepo(options.repoUrl);
|
|
2956
|
+
const api2 = await getAdoApiClient({
|
|
2957
|
+
accessToken: options.accessToken,
|
|
2958
|
+
tokenOrg: options.tokenOrg,
|
|
2959
|
+
orgName: owner
|
|
2960
|
+
});
|
|
2961
|
+
const git = await api2.getGitApi();
|
|
2962
|
+
const res = await git.createPullRequest(
|
|
2319
2963
|
{
|
|
2964
|
+
sourceRefName: `refs/heads/${options.sourceBranchName}`,
|
|
2965
|
+
targetRefName: `refs/heads/${options.targetBranchName}`,
|
|
2966
|
+
title: options.title,
|
|
2320
2967
|
description: options.body
|
|
2321
|
-
}
|
|
2968
|
+
},
|
|
2969
|
+
repo,
|
|
2970
|
+
projectName
|
|
2322
2971
|
);
|
|
2323
|
-
return res.
|
|
2972
|
+
return res.pullRequestId;
|
|
2324
2973
|
}
|
|
2325
|
-
async function
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2974
|
+
async function getAdoRepoDefaultBranch({
|
|
2975
|
+
repoUrl,
|
|
2976
|
+
tokenOrg,
|
|
2977
|
+
accessToken
|
|
2978
|
+
}) {
|
|
2979
|
+
const { owner, repo, projectName } = parseAdoOwnerAndRepo(repoUrl);
|
|
2980
|
+
const api2 = await getAdoApiClient({
|
|
2981
|
+
accessToken,
|
|
2982
|
+
tokenOrg,
|
|
2983
|
+
orgName: owner
|
|
2984
|
+
});
|
|
2985
|
+
const git = await api2.getGitApi();
|
|
2986
|
+
const branches = await git.getBranches(repo, projectName);
|
|
2987
|
+
if (!branches || branches.length === 0) {
|
|
2988
|
+
throw new InvalidRepoUrlError("no branches");
|
|
2331
2989
|
}
|
|
2332
|
-
|
|
2990
|
+
const res = branches.find((branch) => branch.isBaseVersion);
|
|
2991
|
+
if (!res || !res.name) {
|
|
2992
|
+
throw new InvalidRepoUrlError("no default branch");
|
|
2993
|
+
}
|
|
2994
|
+
return res.name;
|
|
2333
2995
|
}
|
|
2334
|
-
async function
|
|
2335
|
-
|
|
2336
|
-
|
|
2996
|
+
async function getAdoReferenceData({
|
|
2997
|
+
ref,
|
|
2998
|
+
repoUrl,
|
|
2999
|
+
accessToken,
|
|
3000
|
+
tokenOrg
|
|
3001
|
+
}) {
|
|
3002
|
+
const { owner, repo, projectName } = parseAdoOwnerAndRepo(repoUrl);
|
|
3003
|
+
const api2 = await getAdoApiClient({
|
|
3004
|
+
accessToken,
|
|
3005
|
+
tokenOrg,
|
|
3006
|
+
orgName: owner
|
|
3007
|
+
});
|
|
3008
|
+
if (!projectName) {
|
|
3009
|
+
throw new InvalidUrlPatternError("no project name");
|
|
3010
|
+
}
|
|
3011
|
+
const git = await api2.getGitApi();
|
|
2337
3012
|
const results = await Promise.allSettled([
|
|
2338
3013
|
(async () => {
|
|
2339
|
-
const res = await
|
|
3014
|
+
const res = await git.getBranch(repo, ref, projectName);
|
|
3015
|
+
if (!res.commit || !res.commit.commitId) {
|
|
3016
|
+
throw new InvalidRepoUrlError("no commit on branch");
|
|
3017
|
+
}
|
|
2340
3018
|
return {
|
|
2341
|
-
sha: res.commit.
|
|
3019
|
+
sha: res.commit.commitId,
|
|
2342
3020
|
type: "BRANCH" /* BRANCH */,
|
|
2343
|
-
date: res.commit.
|
|
3021
|
+
date: res.commit.committer?.date || /* @__PURE__ */ new Date()
|
|
2344
3022
|
};
|
|
2345
3023
|
})(),
|
|
2346
3024
|
(async () => {
|
|
2347
|
-
const res = await
|
|
3025
|
+
const res = await git.getCommits(
|
|
3026
|
+
repo,
|
|
3027
|
+
{
|
|
3028
|
+
fromCommitId: ref,
|
|
3029
|
+
toCommitId: ref,
|
|
3030
|
+
$top: 1
|
|
3031
|
+
},
|
|
3032
|
+
projectName
|
|
3033
|
+
);
|
|
3034
|
+
const commit = res[0];
|
|
3035
|
+
if (!commit || !commit.commitId) {
|
|
3036
|
+
throw new Error("no commit");
|
|
3037
|
+
}
|
|
2348
3038
|
return {
|
|
2349
|
-
sha:
|
|
3039
|
+
sha: commit.commitId,
|
|
2350
3040
|
type: "COMMIT" /* COMMIT */,
|
|
2351
|
-
date:
|
|
3041
|
+
date: commit.committer?.date || /* @__PURE__ */ new Date()
|
|
2352
3042
|
};
|
|
2353
3043
|
})(),
|
|
2354
3044
|
(async () => {
|
|
2355
|
-
const res = await
|
|
3045
|
+
const res = await git.getRefs(repo, projectName, `tags/${ref}`);
|
|
3046
|
+
if (!res[0] || !res[0].objectId) {
|
|
3047
|
+
throw new Error("no tag ref");
|
|
3048
|
+
}
|
|
3049
|
+
let objectId = res[0].objectId;
|
|
3050
|
+
try {
|
|
3051
|
+
const tag = await git.getAnnotatedTag(projectName, repo, objectId);
|
|
3052
|
+
if (tag.taggedObject?.objectId) {
|
|
3053
|
+
objectId = tag.taggedObject.objectId;
|
|
3054
|
+
}
|
|
3055
|
+
} catch (e) {
|
|
3056
|
+
}
|
|
3057
|
+
const commitRes2 = await git.getCommits(
|
|
3058
|
+
repo,
|
|
3059
|
+
{
|
|
3060
|
+
fromCommitId: objectId,
|
|
3061
|
+
toCommitId: objectId,
|
|
3062
|
+
$top: 1
|
|
3063
|
+
},
|
|
3064
|
+
projectName
|
|
3065
|
+
);
|
|
3066
|
+
const commit = commitRes2[0];
|
|
3067
|
+
if (!commit) {
|
|
3068
|
+
throw new Error("no commit");
|
|
3069
|
+
}
|
|
2356
3070
|
return {
|
|
2357
|
-
sha:
|
|
3071
|
+
sha: objectId,
|
|
2358
3072
|
type: "TAG" /* TAG */,
|
|
2359
|
-
date:
|
|
3073
|
+
date: commit.committer?.date || /* @__PURE__ */ new Date()
|
|
2360
3074
|
};
|
|
2361
3075
|
})()
|
|
2362
3076
|
]);
|
|
@@ -2372,41 +3086,34 @@ async function getGitlabReferenceData({ ref, gitlabUrl }, options) {
|
|
|
2372
3086
|
}
|
|
2373
3087
|
throw new RefNotFoundError(`ref: ${ref} does not exist`);
|
|
2374
3088
|
}
|
|
2375
|
-
function
|
|
2376
|
-
|
|
2377
|
-
const parsingResult = parseScmURL(
|
|
2378
|
-
if (!parsingResult || parsingResult.hostname !== "
|
|
2379
|
-
throw new InvalidUrlPatternError(`invalid
|
|
3089
|
+
function parseAdoOwnerAndRepo(adoUrl) {
|
|
3090
|
+
adoUrl = removeTrailingSlash3(adoUrl);
|
|
3091
|
+
const parsingResult = parseScmURL(adoUrl);
|
|
3092
|
+
if (!parsingResult || parsingResult.hostname !== "dev.azure.com") {
|
|
3093
|
+
throw new InvalidUrlPatternError(`invalid ADO repo URL: ${adoUrl}`);
|
|
2380
3094
|
}
|
|
2381
|
-
const { organization, repoName, projectPath } = parsingResult;
|
|
2382
|
-
return {
|
|
3095
|
+
const { organization, repoName, projectName, projectPath, pathElements } = parsingResult;
|
|
3096
|
+
return {
|
|
3097
|
+
owner: organization,
|
|
3098
|
+
repo: repoName,
|
|
3099
|
+
projectName,
|
|
3100
|
+
projectPath,
|
|
3101
|
+
pathElements
|
|
3102
|
+
};
|
|
2383
3103
|
}
|
|
2384
|
-
async function
|
|
2385
|
-
|
|
2386
|
-
const api = getGitBeaker({ gitlabAuthToken: options?.gitlabAuthToken });
|
|
2387
|
-
const resp = await api.RepositoryFiles.allFileBlames(projectPath, path8, ref);
|
|
2388
|
-
let lineNumber = 1;
|
|
2389
|
-
return resp.filter((range) => range.lines).map((range) => {
|
|
2390
|
-
const oldLineNumber = lineNumber;
|
|
2391
|
-
if (!range.lines) {
|
|
2392
|
-
throw new Error("range.lines should not be undefined");
|
|
2393
|
-
}
|
|
2394
|
-
lineNumber += range.lines.length;
|
|
2395
|
-
return {
|
|
2396
|
-
startingLine: oldLineNumber,
|
|
2397
|
-
endingLine: lineNumber - 1,
|
|
2398
|
-
login: range.commit.author_email,
|
|
2399
|
-
email: range.commit.author_email,
|
|
2400
|
-
name: range.commit.author_name
|
|
2401
|
-
};
|
|
2402
|
-
});
|
|
3104
|
+
async function getAdoBlameRanges() {
|
|
3105
|
+
return [];
|
|
2403
3106
|
}
|
|
2404
|
-
var
|
|
2405
|
-
access_token:
|
|
2406
|
-
token_type:
|
|
2407
|
-
refresh_token:
|
|
3107
|
+
var AdoAuthResultZ = z8.object({
|
|
3108
|
+
access_token: z8.string().min(1),
|
|
3109
|
+
token_type: z8.string().min(1),
|
|
3110
|
+
refresh_token: z8.string().min(1)
|
|
2408
3111
|
});
|
|
2409
3112
|
|
|
3113
|
+
// src/features/analysis/scm/constants.ts
|
|
3114
|
+
var MOBB_ICON_IMG = "https://svgshare.com/i/12DK.svg";
|
|
3115
|
+
var COMMIT_FIX_SVG = `https://app.mobb.ai/gh-action/commit-button.svg`;
|
|
3116
|
+
|
|
2410
3117
|
// src/features/analysis/scm/utils/get_issue_type.ts
|
|
2411
3118
|
var getIssueType = (issueType) => {
|
|
2412
3119
|
switch (issueType) {
|
|
@@ -2569,7 +3276,7 @@ async function getFixesFromDiff(params) {
|
|
|
2569
3276
|
});
|
|
2570
3277
|
const lineAddedRanges = calculateRanges(fileNumbers);
|
|
2571
3278
|
const fileFilter = {
|
|
2572
|
-
path:
|
|
3279
|
+
path: z9.string().parse(file.to),
|
|
2573
3280
|
ranges: lineAddedRanges.map(([startLine, endLine]) => ({
|
|
2574
3281
|
endLine,
|
|
2575
3282
|
startLine
|
|
@@ -2629,7 +3336,7 @@ async function handleFinishedAnalysis({
|
|
|
2629
3336
|
return Promise.all(
|
|
2630
3337
|
vulnerabilityReportIssueCodeNodes.map(
|
|
2631
3338
|
async (vulnerabilityReportIssueCodeNodes2) => {
|
|
2632
|
-
const { path:
|
|
3339
|
+
const { path: path9, startLine, vulnerabilityReportIssue } = vulnerabilityReportIssueCodeNodes2;
|
|
2633
3340
|
const { fixId } = vulnerabilityReportIssue;
|
|
2634
3341
|
const { fix_by_pk } = await gqlClient.getFix(fixId);
|
|
2635
3342
|
const {
|
|
@@ -2640,7 +3347,7 @@ async function handleFinishedAnalysis({
|
|
|
2640
3347
|
body: "empty",
|
|
2641
3348
|
pull_number: pullRequest,
|
|
2642
3349
|
commit_id: commitSha,
|
|
2643
|
-
path:
|
|
3350
|
+
path: path9,
|
|
2644
3351
|
line: startLine
|
|
2645
3352
|
},
|
|
2646
3353
|
githubActionOctokit
|
|
@@ -2998,8 +3705,8 @@ async function forkSnyk(args, { display }) {
|
|
|
2998
3705
|
}
|
|
2999
3706
|
async function getSnykReport(reportPath, repoRoot, { skipPrompts = false }) {
|
|
3000
3707
|
debug7("get snyk report start %s %s", reportPath, repoRoot);
|
|
3001
|
-
const
|
|
3002
|
-
const { message: configMessage } =
|
|
3708
|
+
const config4 = await forkSnyk(["config"], { display: false });
|
|
3709
|
+
const { message: configMessage } = config4;
|
|
3003
3710
|
if (!configMessage.includes("api: ")) {
|
|
3004
3711
|
const snykLoginSpinner = createSpinner3().start();
|
|
3005
3712
|
if (!skipPrompts) {
|
|
@@ -3011,7 +3718,7 @@ async function getSnykReport(reportPath, repoRoot, { skipPrompts = false }) {
|
|
|
3011
3718
|
snykLoginSpinner.update({
|
|
3012
3719
|
text: "\u{1F513} Waiting for Snyk login to complete"
|
|
3013
3720
|
});
|
|
3014
|
-
debug7("no token in the config %s",
|
|
3721
|
+
debug7("no token in the config %s", config4);
|
|
3015
3722
|
await forkSnyk(["auth"], { display: true });
|
|
3016
3723
|
snykLoginSpinner.success({ text: "\u{1F513} Login to Snyk Successful" });
|
|
3017
3724
|
}
|
|
@@ -3083,8 +3790,6 @@ async function uploadFile({
|
|
|
3083
3790
|
// src/features/analysis/index.ts
|
|
3084
3791
|
var { CliError: CliError2, Spinner: Spinner2, keypress: keypress2, getDirName: getDirName2 } = utils_exports;
|
|
3085
3792
|
var webLoginUrl = `${WEB_APP_URL}/cli-login`;
|
|
3086
|
-
var githubAuthUrl = `${WEB_APP_URL}/github-auth`;
|
|
3087
|
-
var gitlabAuthUrl = `${WEB_APP_URL}/gitlab-auth`;
|
|
3088
3793
|
async function downloadRepo({
|
|
3089
3794
|
repoUrl,
|
|
3090
3795
|
authHeaders,
|
|
@@ -3096,6 +3801,7 @@ async function downloadRepo({
|
|
|
3096
3801
|
const repoSpinner = createSpinner4("\u{1F4BE} Downloading Repo").start();
|
|
3097
3802
|
debug9("download repo %s %s %s", repoUrl, dirname);
|
|
3098
3803
|
const zipFilePath = path6.join(dirname, "repo.zip");
|
|
3804
|
+
debug9("download URL: %s auth headers: %o", downloadUrl, authHeaders);
|
|
3099
3805
|
const response = await fetch3(downloadUrl, {
|
|
3100
3806
|
method: "GET",
|
|
3101
3807
|
headers: {
|
|
@@ -3158,6 +3864,38 @@ async function runAnalysis(params, options) {
|
|
|
3158
3864
|
tmpObj.removeCallback();
|
|
3159
3865
|
}
|
|
3160
3866
|
}
|
|
3867
|
+
function _getTokenAndUrlForScmType({
|
|
3868
|
+
scmLibType,
|
|
3869
|
+
githubToken,
|
|
3870
|
+
gitlabToken,
|
|
3871
|
+
adoToken
|
|
3872
|
+
}) {
|
|
3873
|
+
const githubAuthUrl = `${WEB_APP_URL}/github-auth`;
|
|
3874
|
+
const gitlabAuthUrl = `${WEB_APP_URL}/gitlab-auth`;
|
|
3875
|
+
const adoAuthUrl = `${WEB_APP_URL}/ado-auth`;
|
|
3876
|
+
switch (scmLibType) {
|
|
3877
|
+
case "GITHUB" /* GITHUB */:
|
|
3878
|
+
return {
|
|
3879
|
+
token: githubToken,
|
|
3880
|
+
authUrl: githubAuthUrl
|
|
3881
|
+
};
|
|
3882
|
+
case "GITLAB" /* GITLAB */:
|
|
3883
|
+
return {
|
|
3884
|
+
token: gitlabToken,
|
|
3885
|
+
authUrl: gitlabAuthUrl
|
|
3886
|
+
};
|
|
3887
|
+
case "ADO" /* ADO */:
|
|
3888
|
+
return {
|
|
3889
|
+
token: adoToken,
|
|
3890
|
+
authUrl: adoAuthUrl
|
|
3891
|
+
};
|
|
3892
|
+
default:
|
|
3893
|
+
return {
|
|
3894
|
+
token: void 0,
|
|
3895
|
+
authUrl: void 0
|
|
3896
|
+
};
|
|
3897
|
+
}
|
|
3898
|
+
}
|
|
3161
3899
|
async function _scan(params, { skipPrompts = false } = {}) {
|
|
3162
3900
|
const {
|
|
3163
3901
|
dirname,
|
|
@@ -3196,26 +3934,35 @@ async function _scan(params, { skipPrompts = false } = {}) {
|
|
|
3196
3934
|
throw new Error("repo is required in case srcPath is not provided");
|
|
3197
3935
|
}
|
|
3198
3936
|
const userInfo = await gqlClient.getUserInfo();
|
|
3199
|
-
const { githubToken, gitlabToken } = userInfo;
|
|
3937
|
+
const { githubToken, gitlabToken, adoToken, adoOrg } = userInfo;
|
|
3200
3938
|
const isRepoAvailable = await scmCanReachRepo({
|
|
3201
3939
|
repoUrl: repo,
|
|
3202
3940
|
githubToken,
|
|
3203
|
-
gitlabToken
|
|
3941
|
+
gitlabToken,
|
|
3942
|
+
adoToken,
|
|
3943
|
+
scmOrg: adoOrg
|
|
3204
3944
|
});
|
|
3205
3945
|
const scmLibType = getScmLibTypeFromUrl(repo);
|
|
3206
|
-
const
|
|
3207
|
-
|
|
3946
|
+
const { authUrl: scmAuthUrl, token } = _getTokenAndUrlForScmType({
|
|
3947
|
+
scmLibType,
|
|
3948
|
+
githubToken,
|
|
3949
|
+
gitlabToken,
|
|
3950
|
+
adoToken
|
|
3951
|
+
});
|
|
3952
|
+
let myToken = token;
|
|
3208
3953
|
if (!isRepoAvailable) {
|
|
3209
3954
|
if (ci || !scmLibType || !scmAuthUrl) {
|
|
3210
3955
|
const errorMessage = scmAuthUrl ? `Cannot access repo ${repo}` : `Cannot access repo ${repo} with the provided token, please visit ${scmAuthUrl} to refresh your source control management system token`;
|
|
3211
3956
|
throw new Error(errorMessage);
|
|
3212
3957
|
}
|
|
3213
3958
|
if (scmLibType && scmAuthUrl) {
|
|
3214
|
-
|
|
3959
|
+
myToken = await handleScmIntegration(token, scmLibType, scmAuthUrl) || "";
|
|
3215
3960
|
const isRepoAvailable2 = await scmCanReachRepo({
|
|
3216
3961
|
repoUrl: repo,
|
|
3217
|
-
githubToken:
|
|
3218
|
-
gitlabToken:
|
|
3962
|
+
githubToken: myToken,
|
|
3963
|
+
gitlabToken: myToken,
|
|
3964
|
+
adoToken: myToken,
|
|
3965
|
+
scmOrg: adoOrg
|
|
3219
3966
|
});
|
|
3220
3967
|
if (!isRepoAvailable2) {
|
|
3221
3968
|
throw new Error(
|
|
@@ -3227,7 +3974,8 @@ async function _scan(params, { skipPrompts = false } = {}) {
|
|
|
3227
3974
|
const scm = await SCMLib.init({
|
|
3228
3975
|
url: repo,
|
|
3229
3976
|
accessToken: token,
|
|
3230
|
-
scmType: scmLibType
|
|
3977
|
+
scmType: scmLibType,
|
|
3978
|
+
scmOrg: adoOrg
|
|
3231
3979
|
});
|
|
3232
3980
|
const reference = ref ?? await scm.getRepoDefaultBranch();
|
|
3233
3981
|
const { sha } = await scm.getReferenceData(reference);
|
|
@@ -3270,7 +4018,7 @@ async function _scan(params, { skipPrompts = false } = {}) {
|
|
|
3270
4018
|
gqlClient,
|
|
3271
4019
|
scm,
|
|
3272
4020
|
githubActionOctokit: new Octokit3({ auth: githubActionToken }),
|
|
3273
|
-
scanner:
|
|
4021
|
+
scanner: z10.nativeEnum(SCANNERS).parse(scanner)
|
|
3274
4022
|
})
|
|
3275
4023
|
);
|
|
3276
4024
|
}
|
|
@@ -3282,7 +4030,7 @@ async function _scan(params, { skipPrompts = false } = {}) {
|
|
|
3282
4030
|
try {
|
|
3283
4031
|
const sumbitRes = await gqlClient.submitVulnerabilityReport({
|
|
3284
4032
|
fixReportId: reportUploadInfo.fixReportId,
|
|
3285
|
-
repoUrl:
|
|
4033
|
+
repoUrl: z10.string().parse(repo),
|
|
3286
4034
|
reference,
|
|
3287
4035
|
projectId,
|
|
3288
4036
|
vulnerabilityReportFileName: "report.json",
|
|
@@ -3400,7 +4148,7 @@ async function _scan(params, { skipPrompts = false } = {}) {
|
|
|
3400
4148
|
}
|
|
3401
4149
|
}
|
|
3402
4150
|
async function handleScmIntegration(oldToken, scmLibType2, scmAuthUrl2) {
|
|
3403
|
-
const scmName = scmLibType2 === "GITHUB" /* GITHUB */ ? "Github" : scmLibType2 === "GITLAB" /* GITLAB */ ? "Gitlab" : "";
|
|
4151
|
+
const scmName = scmLibType2 === "GITHUB" /* GITHUB */ ? "Github" : scmLibType2 === "GITLAB" /* GITLAB */ ? "Gitlab" : scmLibType2 === "ADO" /* ADO */ ? "Azure DevOps" : "";
|
|
3404
4152
|
const addScmIntegration = skipPrompts ? true : await scmIntegrationPrompt(scmName);
|
|
3405
4153
|
const scmSpinner = createSpinner4(
|
|
3406
4154
|
`\u{1F517} Waiting for ${scmName} integration...`
|
|
@@ -3513,6 +4261,8 @@ async function _scan(params, { skipPrompts = false } = {}) {
|
|
|
3513
4261
|
|
|
3514
4262
|
// src/commands/index.ts
|
|
3515
4263
|
import chalkAnimation from "chalk-animation";
|
|
4264
|
+
import Configstore2 from "configstore";
|
|
4265
|
+
var { getDirName: getDirName3 } = utils_exports;
|
|
3516
4266
|
async function review(params, { skipPrompts = true } = {}) {
|
|
3517
4267
|
const {
|
|
3518
4268
|
repo,
|
|
@@ -3569,6 +4319,23 @@ async function analyze({
|
|
|
3569
4319
|
{ skipPrompts }
|
|
3570
4320
|
);
|
|
3571
4321
|
}
|
|
4322
|
+
var packageJson2 = JSON.parse(
|
|
4323
|
+
fs4.readFileSync(path7.join(getDirName3(), "../package.json"), "utf8")
|
|
4324
|
+
);
|
|
4325
|
+
var config3 = new Configstore2(packageJson2.name, { apiToken: "" });
|
|
4326
|
+
async function addScmToken(addScmTokenOptions) {
|
|
4327
|
+
const { apiKey, token, organization, scm, username, refreshToken } = addScmTokenOptions;
|
|
4328
|
+
const gqlClient = new GQLClient({
|
|
4329
|
+
apiKey: apiKey || config3.get("apiToken")
|
|
4330
|
+
});
|
|
4331
|
+
await gqlClient.updateScmToken({
|
|
4332
|
+
type: scm,
|
|
4333
|
+
token,
|
|
4334
|
+
org: organization,
|
|
4335
|
+
username,
|
|
4336
|
+
refreshToken
|
|
4337
|
+
});
|
|
4338
|
+
}
|
|
3572
4339
|
async function scan(scanOptions, { skipPrompts = false } = {}) {
|
|
3573
4340
|
const { scanner, ci } = scanOptions;
|
|
3574
4341
|
!ci && await showWelcomeMessage(skipPrompts);
|
|
@@ -3603,7 +4370,7 @@ var repoOption = {
|
|
|
3603
4370
|
alias: "r",
|
|
3604
4371
|
demandOption: true,
|
|
3605
4372
|
type: "string",
|
|
3606
|
-
describe: chalk5.bold("Github / GitLab repository URL")
|
|
4373
|
+
describe: chalk5.bold("Github / GitLab / Azure DevOps repository URL")
|
|
3607
4374
|
};
|
|
3608
4375
|
var projectNameOption = {
|
|
3609
4376
|
type: "string",
|
|
@@ -3645,11 +4412,31 @@ var commitHashOption = {
|
|
|
3645
4412
|
describe: chalk5.bold("Hash of the commit"),
|
|
3646
4413
|
type: "string"
|
|
3647
4414
|
};
|
|
4415
|
+
var scmTypeOption = {
|
|
4416
|
+
describe: chalk5.bold("SCM type (GitHub, GitLab, Ado)"),
|
|
4417
|
+
type: "string"
|
|
4418
|
+
};
|
|
4419
|
+
var scmOrgOption = {
|
|
4420
|
+
describe: chalk5.bold("Organization name in SCM (used in Azure DevOps)"),
|
|
4421
|
+
type: "string"
|
|
4422
|
+
};
|
|
4423
|
+
var scmUsernameOption = {
|
|
4424
|
+
describe: chalk5.bold("Username in SCM (used in GitHub)"),
|
|
4425
|
+
type: "string"
|
|
4426
|
+
};
|
|
4427
|
+
var scmRefreshTokenOption = {
|
|
4428
|
+
describe: chalk5.bold("SCM refresh token (used in GitLab)"),
|
|
4429
|
+
type: "string"
|
|
4430
|
+
};
|
|
4431
|
+
var scmTokenOption = {
|
|
4432
|
+
describe: chalk5.bold("SCM API token"),
|
|
4433
|
+
type: "string"
|
|
4434
|
+
};
|
|
3648
4435
|
|
|
3649
4436
|
// src/args/validation.ts
|
|
3650
4437
|
import chalk6 from "chalk";
|
|
3651
|
-
import
|
|
3652
|
-
import { z as
|
|
4438
|
+
import path8 from "path";
|
|
4439
|
+
import { z as z11 } from "zod";
|
|
3653
4440
|
function throwRepoUrlErrorMessage({
|
|
3654
4441
|
error,
|
|
3655
4442
|
repoUrl,
|
|
@@ -3666,10 +4453,10 @@ Example:
|
|
|
3666
4453
|
)}`;
|
|
3667
4454
|
throw new CliError(formattedErrorMessage);
|
|
3668
4455
|
}
|
|
3669
|
-
var UrlZ =
|
|
3670
|
-
invalid_type_error: "is not a valid GitHub / GitLab URL"
|
|
4456
|
+
var UrlZ = z11.string({
|
|
4457
|
+
invalid_type_error: "is not a valid GitHub / GitLab / ADO URL"
|
|
3671
4458
|
}).refine((data) => !!parseScmURL(data), {
|
|
3672
|
-
message: "is not a valid GitHub / GitLab URL"
|
|
4459
|
+
message: "is not a valid GitHub / GitLab / ADO URL"
|
|
3673
4460
|
});
|
|
3674
4461
|
function validateRepoUrl(args) {
|
|
3675
4462
|
const repoSafeParseResult = UrlZ.safeParse(args.repo);
|
|
@@ -3688,7 +4475,7 @@ function validateRepoUrl(args) {
|
|
|
3688
4475
|
}
|
|
3689
4476
|
var supportExtensions = [".json", ".xml", ".fpr", ".sarif"];
|
|
3690
4477
|
function validateReportFileFormat(reportFile) {
|
|
3691
|
-
if (!supportExtensions.includes(
|
|
4478
|
+
if (!supportExtensions.includes(path8.extname(reportFile))) {
|
|
3692
4479
|
throw new CliError(
|
|
3693
4480
|
`
|
|
3694
4481
|
${chalk6.bold(
|
|
@@ -3726,7 +4513,7 @@ function analyzeBuilder(yargs2) {
|
|
|
3726
4513
|
).help();
|
|
3727
4514
|
}
|
|
3728
4515
|
function validateAnalyzeOptions(argv) {
|
|
3729
|
-
if (!
|
|
4516
|
+
if (!fs5.existsSync(argv.f)) {
|
|
3730
4517
|
throw new CliError(`
|
|
3731
4518
|
Can't access ${chalk7.bold(argv.f)}`);
|
|
3732
4519
|
}
|
|
@@ -3747,7 +4534,7 @@ async function analyzeHandler(args) {
|
|
|
3747
4534
|
}
|
|
3748
4535
|
|
|
3749
4536
|
// src/args/commands/review.ts
|
|
3750
|
-
import
|
|
4537
|
+
import fs6 from "node:fs";
|
|
3751
4538
|
import chalk8 from "chalk";
|
|
3752
4539
|
function reviewBuilder(yargs2) {
|
|
3753
4540
|
return yargs2.option("f", {
|
|
@@ -3777,7 +4564,7 @@ function reviewBuilder(yargs2) {
|
|
|
3777
4564
|
).help();
|
|
3778
4565
|
}
|
|
3779
4566
|
function validateReviewOptions(argv) {
|
|
3780
|
-
if (!
|
|
4567
|
+
if (!fs6.existsSync(argv.f)) {
|
|
3781
4568
|
throw new CliError(`
|
|
3782
4569
|
Can't access ${chalk8.bold(argv.f)}`);
|
|
3783
4570
|
}
|
|
@@ -3813,6 +4600,37 @@ async function scanHandler(args) {
|
|
|
3813
4600
|
await scan(args, { skipPrompts: args.yes });
|
|
3814
4601
|
}
|
|
3815
4602
|
|
|
4603
|
+
// src/args/commands/token.ts
|
|
4604
|
+
function addScmTokenBuilder(args) {
|
|
4605
|
+
return args.option("scm", scmTypeOption).option("token", scmTokenOption).option("organization", scmOrgOption).option("username", scmUsernameOption).option("refresh-token", scmRefreshTokenOption).option("api-key", apiKeyOption).example(
|
|
4606
|
+
"$0 add-scm-token --scm ado --token abcdef0123456 --organization myOrg",
|
|
4607
|
+
"Add your SCM (Github, Gitlab, Azure DevOps) token to Mobb to enable automated fixes."
|
|
4608
|
+
).help().demandOption(["scm", "token"]);
|
|
4609
|
+
}
|
|
4610
|
+
function validateAddScmTokenOptions(argv) {
|
|
4611
|
+
if (!argv.scm) {
|
|
4612
|
+
throw new CliError(errorMessages.missingScmType);
|
|
4613
|
+
}
|
|
4614
|
+
if (!Object.values(ScmTypes).includes(argv.scm)) {
|
|
4615
|
+
throw new CliError(errorMessages.invalidScmType);
|
|
4616
|
+
}
|
|
4617
|
+
if (!argv.token) {
|
|
4618
|
+
throw new CliError(errorMessages.missingToken);
|
|
4619
|
+
}
|
|
4620
|
+
if (argv.scm === ScmTypes.AzureDevOps && !argv.organization) {
|
|
4621
|
+
throw new CliError(
|
|
4622
|
+
"\nError: --organization flag is required for Azure DevOps"
|
|
4623
|
+
);
|
|
4624
|
+
}
|
|
4625
|
+
if (argv.scm === ScmTypes.Github && !argv.username) {
|
|
4626
|
+
throw new CliError("\nError: --username flag is required for GitHub");
|
|
4627
|
+
}
|
|
4628
|
+
}
|
|
4629
|
+
async function addScmTokenHandler(args) {
|
|
4630
|
+
validateAddScmTokenOptions(args);
|
|
4631
|
+
await addScmToken(args);
|
|
4632
|
+
}
|
|
4633
|
+
|
|
3816
4634
|
// src/args/yargs.ts
|
|
3817
4635
|
var parseArgs = async (args) => {
|
|
3818
4636
|
const yargsInstance = yargs(args);
|
|
@@ -3830,6 +4648,13 @@ var parseArgs = async (args) => {
|
|
|
3830
4648
|
)} ${chalk9.dim("[options]")}
|
|
3831
4649
|
`
|
|
3832
4650
|
).version(false).command(
|
|
4651
|
+
mobbCliCommand.addScmToken,
|
|
4652
|
+
chalk9.bold(
|
|
4653
|
+
"Add your SCM (Github, Gitlab, Azure DevOps) token to Mobb to enable automated fixes."
|
|
4654
|
+
),
|
|
4655
|
+
addScmTokenBuilder,
|
|
4656
|
+
addScmTokenHandler
|
|
4657
|
+
).command(
|
|
3833
4658
|
mobbCliCommand.scan,
|
|
3834
4659
|
chalk9.bold(
|
|
3835
4660
|
"Scan your code for vulnerabilities, get automated fixes right away."
|