mobbdev 0.0.76 → 0.0.79

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 (3) hide show
  1. package/README.md +8 -2
  2. package/dist/index.mjs +1146 -298
  3. package/package.json +16 -15
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 z9 } from "zod";
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 z8 } from "zod";
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/gitlab.ts
912
- import querystring from "node:querystring";
913
- import { Gitlab } from "@gitbeaker/rest";
914
- import { z as z7 } from "zod";
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 z6 } from "zod";
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";
@@ -933,47 +991,81 @@ import { Octokit } from "octokit";
933
991
  import { z as z3 } from "zod";
934
992
 
935
993
  // src/features/analysis/scm/urlParser.ts
936
- var pathnameParsingMap = {
937
- "gitlab.com": (pathname) => {
938
- if (pathname.length < 2)
939
- return null;
940
- return {
941
- organization: pathname[0],
942
- repoName: pathname[pathname.length - 1]
943
- };
944
- },
945
- "github.com": (pathname) => {
946
- if (pathname.length !== 2)
947
- return null;
948
- return {
949
- organization: pathname[0],
950
- repoName: pathname[1]
951
- };
994
+ function getRepoInfo(pathname, hostname) {
995
+ const hostnameParts = hostname.split(".");
996
+ if (hostnameParts.length === 3 && hostnameParts[1] === "visualstudio" && hostnameParts[2] === "com") {
997
+ if (pathname.length === 2 && pathname[0] === "_git") {
998
+ return {
999
+ organization: hostnameParts[0],
1000
+ projectName: pathname[1],
1001
+ repoName: pathname[1]
1002
+ };
1003
+ }
1004
+ if (pathname.length === 3 && pathname[1] === "_git") {
1005
+ return {
1006
+ organization: hostnameParts[0],
1007
+ projectName: pathname[0],
1008
+ repoName: pathname[2]
1009
+ };
1010
+ }
952
1011
  }
953
- };
1012
+ if (hostname === "dev.azure.com") {
1013
+ if (pathname.length === 3 && pathname[1] === "_git") {
1014
+ return {
1015
+ organization: pathname[0],
1016
+ projectName: pathname[2],
1017
+ repoName: pathname[2]
1018
+ };
1019
+ }
1020
+ if (pathname.length === 4 && pathname[2] === "_git") {
1021
+ return {
1022
+ organization: pathname[0],
1023
+ projectName: pathname[1],
1024
+ repoName: pathname[3]
1025
+ };
1026
+ }
1027
+ }
1028
+ if (hostname === "github.com") {
1029
+ if (pathname.length === 2) {
1030
+ return {
1031
+ organization: pathname[0],
1032
+ projectName: void 0,
1033
+ repoName: pathname[1]
1034
+ };
1035
+ }
1036
+ }
1037
+ if (hostname === "gitlab.com") {
1038
+ if (pathname.length >= 2) {
1039
+ return {
1040
+ organization: pathname[0],
1041
+ projectName: void 0,
1042
+ repoName: pathname[pathname.length - 1]
1043
+ };
1044
+ }
1045
+ }
1046
+ return null;
1047
+ }
954
1048
  var NAME_REGEX = /[a-z0-9\-_.+]+/i;
955
1049
  var parseScmURL = (scmURL) => {
956
1050
  try {
957
1051
  const url = new URL(scmURL);
958
1052
  const hostname = url.hostname.toLowerCase();
959
- if (!(hostname in pathnameParsingMap))
960
- return null;
961
1053
  const projectPath = url.pathname.substring(1).replace(/.git$/i, "");
962
- const repo = pathnameParsingMap[hostname](
963
- projectPath.split("/")
964
- );
1054
+ const repo = getRepoInfo(projectPath.split("/"), hostname);
965
1055
  if (!repo)
966
1056
  return null;
967
- const { organization, repoName } = repo;
1057
+ const { organization, repoName, projectName } = repo;
968
1058
  if (!organization || !repoName)
969
1059
  return null;
970
1060
  if (!organization.match(NAME_REGEX) || !repoName.match(NAME_REGEX))
971
1061
  return null;
972
1062
  return {
973
- hostname: url.hostname,
1063
+ hostname,
974
1064
  organization,
975
1065
  projectPath,
976
- repoName
1066
+ repoName,
1067
+ projectName,
1068
+ pathElements: projectPath.split("/")
977
1069
  };
978
1070
  } catch (e) {
979
1071
  return null;
@@ -1034,7 +1126,7 @@ async function githubValidateParams(url, accessToken) {
1034
1126
  await oktoKit.rest.users.getAuthenticated();
1035
1127
  }
1036
1128
  if (url) {
1037
- const { owner, repo } = parseOwnerAndRepo(url);
1129
+ const { owner, repo } = parseGithubOwnerAndRepo(url);
1038
1130
  await oktoKit.rest.repos.get({ repo, owner });
1039
1131
  }
1040
1132
  } catch (e) {
@@ -1056,7 +1148,7 @@ async function getGithubUsername(accessToken) {
1056
1148
  }
1057
1149
  async function getGithubIsUserCollaborator(username, accessToken, repoUrl) {
1058
1150
  try {
1059
- const { owner, repo } = parseOwnerAndRepo(repoUrl);
1151
+ const { owner, repo } = parseGithubOwnerAndRepo(repoUrl);
1060
1152
  const oktoKit = getOktoKit({ githubAuthToken: accessToken });
1061
1153
  const res = await oktoKit.rest.repos.checkCollaborator({
1062
1154
  owner,
@@ -1072,7 +1164,7 @@ async function getGithubIsUserCollaborator(username, accessToken, repoUrl) {
1072
1164
  return false;
1073
1165
  }
1074
1166
  async function getGithubPullRequestStatus(accessToken, repoUrl, prNumber) {
1075
- const { owner, repo } = parseOwnerAndRepo(repoUrl);
1167
+ const { owner, repo } = parseGithubOwnerAndRepo(repoUrl);
1076
1168
  const oktoKit = getOktoKit({ githubAuthToken: accessToken });
1077
1169
  const res = await oktoKit.rest.pulls.get({
1078
1170
  owner,
@@ -1088,7 +1180,7 @@ async function getGithubPullRequestStatus(accessToken, repoUrl, prNumber) {
1088
1180
  return res.data.state;
1089
1181
  }
1090
1182
  async function getGithubIsRemoteBranch(accessToken, repoUrl, branch) {
1091
- const { owner, repo } = parseOwnerAndRepo(repoUrl);
1183
+ const { owner, repo } = parseGithubOwnerAndRepo(repoUrl);
1092
1184
  const oktoKit = getOktoKit({ githubAuthToken: accessToken });
1093
1185
  try {
1094
1186
  const res = await oktoKit.rest.repos.getBranch({
@@ -1132,7 +1224,7 @@ async function getGithubRepoList(accessToken) {
1132
1224
  }
1133
1225
  }
1134
1226
  async function getGithubBranchList(accessToken, repoUrl) {
1135
- const { owner, repo } = parseOwnerAndRepo(repoUrl);
1227
+ const { owner, repo } = parseGithubOwnerAndRepo(repoUrl);
1136
1228
  const oktoKit = getOktoKit({ githubAuthToken: accessToken });
1137
1229
  const res = await oktoKit.rest.repos.listBranches({
1138
1230
  owner,
@@ -1143,7 +1235,7 @@ async function getGithubBranchList(accessToken, repoUrl) {
1143
1235
  return res.data.map((branch) => branch.name);
1144
1236
  }
1145
1237
  async function createPullRequest(options) {
1146
- const { owner, repo } = parseOwnerAndRepo(options.repoUrl);
1238
+ const { owner, repo } = parseGithubOwnerAndRepo(options.repoUrl);
1147
1239
  const oktoKit = getOktoKit({ githubAuthToken: options.accessToken });
1148
1240
  const res = await oktoKit.rest.pulls.create({
1149
1241
  owner,
@@ -1158,7 +1250,7 @@ async function createPullRequest(options) {
1158
1250
  return res.data.number;
1159
1251
  }
1160
1252
  async function forkRepo(options) {
1161
- const { owner, repo } = parseOwnerAndRepo(options.repoUrl);
1253
+ const { owner, repo } = parseGithubOwnerAndRepo(options.repoUrl);
1162
1254
  const oktoKit = getOktoKit({ githubAuthToken: options.accessToken });
1163
1255
  const res = await oktoKit.rest.repos.createFork({
1164
1256
  owner,
@@ -1178,11 +1270,11 @@ async function getRepos(oktoKit) {
1178
1270
  }
1179
1271
  async function getGithubRepoDefaultBranch(repoUrl, options) {
1180
1272
  const oktoKit = getOktoKit(options);
1181
- const { owner, repo } = parseOwnerAndRepo(repoUrl);
1273
+ const { owner, repo } = parseGithubOwnerAndRepo(repoUrl);
1182
1274
  return (await oktoKit.rest.repos.get({ repo, owner })).data.default_branch;
1183
1275
  }
1184
1276
  async function getGithubReferenceData({ ref, gitHubUrl }, options) {
1185
- const { owner, repo } = parseOwnerAndRepo(gitHubUrl);
1277
+ const { owner, repo } = parseGithubOwnerAndRepo(gitHubUrl);
1186
1278
  let res;
1187
1279
  try {
1188
1280
  const oktoKit = getOktoKit(options);
@@ -1257,7 +1349,7 @@ async function getCommit({
1257
1349
  commit_sha: commitSha
1258
1350
  });
1259
1351
  }
1260
- function parseOwnerAndRepo(gitHubUrl) {
1352
+ function parseGithubOwnerAndRepo(gitHubUrl) {
1261
1353
  gitHubUrl = removeTrailingSlash(gitHubUrl);
1262
1354
  const parsingResult = parseScmURL(gitHubUrl);
1263
1355
  if (!parsingResult || parsingResult.hostname !== "github.com") {
@@ -1291,12 +1383,12 @@ async function queryGithubGraphql(query, variables, options) {
1291
1383
  throw e;
1292
1384
  }
1293
1385
  }
1294
- async function getGithubBlameRanges({ ref, gitHubUrl, path: path8 }, options) {
1295
- const { owner, repo } = parseOwnerAndRepo(gitHubUrl);
1386
+ async function getGithubBlameRanges({ ref, gitHubUrl, path: path9 }, options) {
1387
+ const { owner, repo } = parseGithubOwnerAndRepo(gitHubUrl);
1296
1388
  const variables = {
1297
1389
  owner,
1298
1390
  repo,
1299
- path: path8,
1391
+ path: path9,
1300
1392
  ref
1301
1393
  };
1302
1394
  const res = await queryGithubGraphql(
@@ -1323,8 +1415,8 @@ async function createPr({
1323
1415
  body
1324
1416
  }, options) {
1325
1417
  const oktoKit = getOktoKit(options);
1326
- const { owner: sourceOwner, repo: sourceRepo } = parseOwnerAndRepo(sourceRepoUrl);
1327
- const { owner, repo } = parseOwnerAndRepo(userRepoUrl);
1418
+ const { owner: sourceOwner, repo: sourceRepo } = parseGithubOwnerAndRepo(sourceRepoUrl);
1419
+ const { owner, repo } = parseGithubOwnerAndRepo(userRepoUrl);
1328
1420
  const [sourceFilePath, secondFilePath] = filesPaths;
1329
1421
  const sourceFileContentResponse = await oktoKit.rest.repos.getContent({
1330
1422
  owner: sourceOwner,
@@ -1445,83 +1537,326 @@ function getARepositoryPublicKey(client, params) {
1445
1537
  return client.request(GET_A_REPOSITORY_PUBLIC_KEY, params);
1446
1538
  }
1447
1539
 
1540
+ // src/features/analysis/scm/gitlab.ts
1541
+ import querystring from "node:querystring";
1542
+ import { Gitlab } from "@gitbeaker/rest";
1543
+ import { z as z4 } from "zod";
1544
+ function removeTrailingSlash2(str) {
1545
+ return str.trim().replace(/\/+$/, "");
1546
+ }
1547
+ var EnvVariablesZod2 = z4.object({
1548
+ GITLAB_API_TOKEN: z4.string().optional()
1549
+ });
1550
+ var { GITLAB_API_TOKEN } = EnvVariablesZod2.parse(process.env);
1551
+ function getGitBeaker(options) {
1552
+ const token = options?.gitlabAuthToken ?? GITLAB_API_TOKEN ?? "";
1553
+ if (token?.startsWith("glpat-") || token === "") {
1554
+ return new Gitlab({ token });
1555
+ }
1556
+ return new Gitlab({ oauthToken: token });
1557
+ }
1558
+ async function gitlabValidateParams({
1559
+ url,
1560
+ accessToken
1561
+ }) {
1562
+ try {
1563
+ const api2 = getGitBeaker({ gitlabAuthToken: accessToken });
1564
+ if (accessToken) {
1565
+ await api2.Users.showCurrentUser();
1566
+ }
1567
+ if (url) {
1568
+ const { projectPath } = parseGitlabOwnerAndRepo(url);
1569
+ await api2.Projects.show(projectPath);
1570
+ }
1571
+ } catch (e) {
1572
+ const error = e;
1573
+ const code = error.code || error.status || error.statusCode || error.response?.status || error.response?.statusCode || error.response?.code;
1574
+ const description = error.description || `${e}`;
1575
+ if (code === 401 || code === 403 || description.includes("401") || description.includes("403")) {
1576
+ throw new InvalidAccessTokenError(`invalid gitlab access token`);
1577
+ }
1578
+ if (code === 404 || description.includes("404") || description.includes("Not Found")) {
1579
+ throw new InvalidRepoUrlError(`invalid gitlab repo URL: ${url}`);
1580
+ }
1581
+ throw e;
1582
+ }
1583
+ }
1584
+ async function getGitlabUsername(accessToken) {
1585
+ const api2 = getGitBeaker({ gitlabAuthToken: accessToken });
1586
+ const res = await api2.Users.showCurrentUser();
1587
+ return res.username;
1588
+ }
1589
+ async function getGitlabIsUserCollaborator({
1590
+ username,
1591
+ accessToken,
1592
+ repoUrl
1593
+ }) {
1594
+ try {
1595
+ const { projectPath } = parseGitlabOwnerAndRepo(repoUrl);
1596
+ const api2 = getGitBeaker({ gitlabAuthToken: accessToken });
1597
+ const res = await api2.Projects.show(projectPath);
1598
+ const members = await api2.ProjectMembers.all(res.id, {
1599
+ includeInherited: true
1600
+ });
1601
+ return !!members.find((member) => member.username === username);
1602
+ } catch (e) {
1603
+ return false;
1604
+ }
1605
+ }
1606
+ async function getGitlabMergeRequestStatus({
1607
+ accessToken,
1608
+ repoUrl,
1609
+ mrNumber
1610
+ }) {
1611
+ const { projectPath } = parseGitlabOwnerAndRepo(repoUrl);
1612
+ const api2 = getGitBeaker({ gitlabAuthToken: accessToken });
1613
+ const res = await api2.MergeRequests.show(projectPath, mrNumber);
1614
+ switch (res.state) {
1615
+ case "merged" /* merged */:
1616
+ case "opened" /* opened */:
1617
+ case "closed" /* closed */:
1618
+ return res.state;
1619
+ default:
1620
+ throw new Error(`unknown merge request state ${res.state}`);
1621
+ }
1622
+ }
1623
+ async function getGitlabIsRemoteBranch({
1624
+ accessToken,
1625
+ repoUrl,
1626
+ branch
1627
+ }) {
1628
+ const { projectPath } = parseGitlabOwnerAndRepo(repoUrl);
1629
+ const api2 = getGitBeaker({ gitlabAuthToken: accessToken });
1630
+ try {
1631
+ const res = await api2.Branches.show(projectPath, branch);
1632
+ return res.name === branch;
1633
+ } catch (e) {
1634
+ return false;
1635
+ }
1636
+ }
1637
+ async function getGitlabRepoList(accessToken) {
1638
+ const api2 = getGitBeaker({ gitlabAuthToken: accessToken });
1639
+ const res = await api2.Projects.all({
1640
+ membership: true,
1641
+ //TODO: a bug in the sorting mechanism of this api call
1642
+ //disallows us to sort by updated_at in descending order
1643
+ //so we have to sort by updated_at in ascending order.
1644
+ //We can wait for the bug to be fixed or call the api
1645
+ //directly with fetch()
1646
+ sort: "asc",
1647
+ orderBy: "updated_at",
1648
+ perPage: 100
1649
+ });
1650
+ return Promise.all(
1651
+ res.map(async (project) => {
1652
+ const proj = await api2.Projects.show(project.id);
1653
+ const owner = proj.namespace.name;
1654
+ const repoLanguages = await api2.Projects.showLanguages(project.id);
1655
+ return {
1656
+ repoName: project.path,
1657
+ repoUrl: project.web_url,
1658
+ repoOwner: owner,
1659
+ repoLanguages: Object.keys(repoLanguages),
1660
+ repoIsPublic: project.visibility === "public",
1661
+ repoUpdatedAt: project.last_activity_at
1662
+ };
1663
+ })
1664
+ );
1665
+ }
1666
+ async function getGitlabBranchList({
1667
+ accessToken,
1668
+ repoUrl
1669
+ }) {
1670
+ const { projectPath } = parseGitlabOwnerAndRepo(repoUrl);
1671
+ const api2 = getGitBeaker({ gitlabAuthToken: accessToken });
1672
+ try {
1673
+ const res = await api2.Branches.all(projectPath, {
1674
+ perPage: 100,
1675
+ pagination: "keyset",
1676
+ orderBy: "updated_at",
1677
+ sort: "dec"
1678
+ });
1679
+ return res.map((branch) => branch.name);
1680
+ } catch (e) {
1681
+ return [];
1682
+ }
1683
+ }
1684
+ async function createMergeRequest(options) {
1685
+ const { projectPath } = parseGitlabOwnerAndRepo(options.repoUrl);
1686
+ const api2 = getGitBeaker({ gitlabAuthToken: options.accessToken });
1687
+ const res = await api2.MergeRequests.create(
1688
+ projectPath,
1689
+ options.sourceBranchName,
1690
+ options.targetBranchName,
1691
+ options.title,
1692
+ {
1693
+ description: options.body
1694
+ }
1695
+ );
1696
+ return res.iid;
1697
+ }
1698
+ async function getGitlabRepoDefaultBranch(repoUrl, options) {
1699
+ const api2 = getGitBeaker({ gitlabAuthToken: options?.gitlabAuthToken });
1700
+ const { projectPath } = parseGitlabOwnerAndRepo(repoUrl);
1701
+ const project = await api2.Projects.show(projectPath);
1702
+ if (!project.default_branch) {
1703
+ throw new Error("no default branch");
1704
+ }
1705
+ return project.default_branch;
1706
+ }
1707
+ async function getGitlabReferenceData({ ref, gitlabUrl }, options) {
1708
+ const { projectPath } = parseGitlabOwnerAndRepo(gitlabUrl);
1709
+ const api2 = getGitBeaker({ gitlabAuthToken: options?.gitlabAuthToken });
1710
+ const results = await Promise.allSettled([
1711
+ (async () => {
1712
+ const res = await api2.Branches.show(projectPath, ref);
1713
+ return {
1714
+ sha: res.commit.id,
1715
+ type: "BRANCH" /* BRANCH */,
1716
+ date: res.commit.committed_date ? new Date(res.commit.committed_date) : void 0
1717
+ };
1718
+ })(),
1719
+ (async () => {
1720
+ const res = await api2.Commits.show(projectPath, ref);
1721
+ return {
1722
+ sha: res.id,
1723
+ type: "COMMIT" /* COMMIT */,
1724
+ date: res.committed_date ? new Date(res.committed_date) : void 0
1725
+ };
1726
+ })(),
1727
+ (async () => {
1728
+ const res = await api2.Tags.show(projectPath, ref);
1729
+ return {
1730
+ sha: res.commit.id,
1731
+ type: "TAG" /* TAG */,
1732
+ date: res.commit.committed_date ? new Date(res.commit.committed_date) : void 0
1733
+ };
1734
+ })()
1735
+ ]);
1736
+ const [branchRes, commitRes, tagRes] = results;
1737
+ if (tagRes.status === "fulfilled") {
1738
+ return tagRes.value;
1739
+ }
1740
+ if (branchRes.status === "fulfilled") {
1741
+ return branchRes.value;
1742
+ }
1743
+ if (commitRes.status === "fulfilled") {
1744
+ return commitRes.value;
1745
+ }
1746
+ throw new RefNotFoundError(`ref: ${ref} does not exist`);
1747
+ }
1748
+ function parseGitlabOwnerAndRepo(gitlabUrl) {
1749
+ gitlabUrl = removeTrailingSlash2(gitlabUrl);
1750
+ const parsingResult = parseScmURL(gitlabUrl);
1751
+ if (!parsingResult || parsingResult.hostname !== "gitlab.com") {
1752
+ throw new InvalidUrlPatternError(`invalid gitlab repo Url ${gitlabUrl}`);
1753
+ }
1754
+ const { organization, repoName, projectPath } = parsingResult;
1755
+ return { owner: organization, repo: repoName, projectPath };
1756
+ }
1757
+ async function getGitlabBlameRanges({ ref, gitlabUrl, path: path9 }, options) {
1758
+ const { projectPath } = parseGitlabOwnerAndRepo(gitlabUrl);
1759
+ const api2 = getGitBeaker({ gitlabAuthToken: options?.gitlabAuthToken });
1760
+ const resp = await api2.RepositoryFiles.allFileBlames(projectPath, path9, ref);
1761
+ let lineNumber = 1;
1762
+ return resp.filter((range) => range.lines).map((range) => {
1763
+ const oldLineNumber = lineNumber;
1764
+ if (!range.lines) {
1765
+ throw new Error("range.lines should not be undefined");
1766
+ }
1767
+ lineNumber += range.lines.length;
1768
+ return {
1769
+ startingLine: oldLineNumber,
1770
+ endingLine: lineNumber - 1,
1771
+ login: range.commit.author_email,
1772
+ email: range.commit.author_email,
1773
+ name: range.commit.author_name
1774
+ };
1775
+ });
1776
+ }
1777
+ var GitlabAuthResultZ = z4.object({
1778
+ access_token: z4.string(),
1779
+ token_type: z4.string(),
1780
+ refresh_token: z4.string()
1781
+ });
1782
+
1448
1783
  // src/features/analysis/scm/scmSubmit/index.ts
1449
1784
  import fs from "node:fs/promises";
1450
1785
  import os from "os";
1451
1786
  import path3 from "path";
1452
1787
  import { simpleGit as simpleGit2 } from "simple-git";
1453
1788
  import tmp from "tmp";
1454
- import { z as z5 } from "zod";
1789
+ import { z as z6 } from "zod";
1455
1790
 
1456
1791
  // src/features/analysis/scm/scmSubmit/types.ts
1457
- import { z as z4 } from "zod";
1458
- var BaseSubmitToScmMessageZ = z4.object({
1459
- submitFixRequestId: z4.string().uuid(),
1460
- fixes: z4.array(
1461
- z4.object({
1462
- fixId: z4.string().uuid(),
1463
- diff: z4.string()
1792
+ import { z as z5 } from "zod";
1793
+ var BaseSubmitToScmMessageZ = z5.object({
1794
+ submitFixRequestId: z5.string().uuid(),
1795
+ fixes: z5.array(
1796
+ z5.object({
1797
+ fixId: z5.string().uuid(),
1798
+ diff: z5.string()
1464
1799
  })
1465
1800
  ),
1466
- commitHash: z4.string(),
1467
- repoUrl: z4.string()
1801
+ commitHash: z5.string(),
1802
+ repoUrl: z5.string()
1468
1803
  });
1469
1804
  var submitToScmMessageType = {
1470
1805
  commitToSameBranch: "commitToSameBranch",
1471
1806
  submitFixesForDifferentBranch: "submitFixesForDifferentBranch"
1472
1807
  };
1473
1808
  var CommitToSameBranchParamsZ = BaseSubmitToScmMessageZ.merge(
1474
- z4.object({
1475
- type: z4.literal(submitToScmMessageType.commitToSameBranch),
1476
- branch: z4.string(),
1477
- commitMessage: z4.string(),
1478
- commitDescription: z4.string().nullish(),
1479
- githubCommentId: z4.number().nullish()
1809
+ z5.object({
1810
+ type: z5.literal(submitToScmMessageType.commitToSameBranch),
1811
+ branch: z5.string(),
1812
+ commitMessage: z5.string(),
1813
+ commitDescription: z5.string().nullish(),
1814
+ githubCommentId: z5.number().nullish()
1480
1815
  })
1481
1816
  );
1482
- var SubmitFixesToDifferentBranchParamsZ = z4.object({
1483
- type: z4.literal(submitToScmMessageType.submitFixesForDifferentBranch),
1484
- submitBranch: z4.string(),
1485
- baseBranch: z4.string()
1817
+ var SubmitFixesToDifferentBranchParamsZ = z5.object({
1818
+ type: z5.literal(submitToScmMessageType.submitFixesForDifferentBranch),
1819
+ submitBranch: z5.string(),
1820
+ baseBranch: z5.string()
1486
1821
  }).merge(BaseSubmitToScmMessageZ);
1487
- var SubmitFixesMessageZ = z4.union([
1822
+ var SubmitFixesMessageZ = z5.union([
1488
1823
  CommitToSameBranchParamsZ,
1489
1824
  SubmitFixesToDifferentBranchParamsZ
1490
1825
  ]);
1491
- var FixResponseArrayZ = z4.array(
1492
- z4.object({
1493
- fixId: z4.string().uuid()
1826
+ var FixResponseArrayZ = z5.array(
1827
+ z5.object({
1828
+ fixId: z5.string().uuid()
1494
1829
  })
1495
1830
  );
1496
- var SubmitFixesBaseResponseMessageZ = z4.object({
1497
- submitFixRequestId: z4.string().uuid(),
1498
- submitBranches: z4.array(
1499
- z4.object({
1500
- branchName: z4.string(),
1831
+ var SubmitFixesBaseResponseMessageZ = z5.object({
1832
+ submitFixRequestId: z5.string().uuid(),
1833
+ submitBranches: z5.array(
1834
+ z5.object({
1835
+ branchName: z5.string(),
1501
1836
  fixes: FixResponseArrayZ
1502
1837
  })
1503
1838
  ),
1504
- error: z4.object({
1505
- type: z4.enum([
1839
+ error: z5.object({
1840
+ type: z5.enum([
1506
1841
  "InitialRepoAccessError",
1507
1842
  "PushBranchError",
1508
1843
  "UnknownError"
1509
1844
  ]),
1510
- info: z4.object({
1511
- message: z4.string(),
1512
- pushBranchName: z4.string().optional()
1845
+ info: z5.object({
1846
+ message: z5.string(),
1847
+ pushBranchName: z5.string().optional()
1513
1848
  })
1514
1849
  }).optional()
1515
1850
  });
1516
- var SubmitFixesToSameBranchResponseMessageZ = z4.object({
1517
- type: z4.literal(submitToScmMessageType.commitToSameBranch),
1518
- githubCommentId: z4.number().nullish()
1851
+ var SubmitFixesToSameBranchResponseMessageZ = z5.object({
1852
+ type: z5.literal(submitToScmMessageType.commitToSameBranch),
1853
+ githubCommentId: z5.number().nullish()
1519
1854
  }).merge(SubmitFixesBaseResponseMessageZ);
1520
- var SubmitFixesToDifferentBranchResponseMessageZ = z4.object({
1521
- type: z4.literal(submitToScmMessageType.submitFixesForDifferentBranch),
1522
- githubCommentId: z4.number().optional()
1855
+ var SubmitFixesToDifferentBranchResponseMessageZ = z5.object({
1856
+ type: z5.literal(submitToScmMessageType.submitFixesForDifferentBranch),
1857
+ githubCommentId: z5.number().optional()
1523
1858
  }).merge(SubmitFixesBaseResponseMessageZ);
1524
- var SubmitFixeResponseMessageZ = z4.discriminatedUnion("type", [
1859
+ var SubmitFixesResponseMessageZ = z5.discriminatedUnion("type", [
1525
1860
  SubmitFixesToSameBranchResponseMessageZ,
1526
1861
  SubmitFixesToDifferentBranchResponseMessageZ
1527
1862
  ]);
@@ -1539,32 +1874,40 @@ var isValidBranchName = async (branchName) => {
1539
1874
  return false;
1540
1875
  }
1541
1876
  };
1542
- var FixesZ = z5.array(z5.object({ fixId: z5.string(), diff: z5.string() })).nonempty();
1877
+ var FixesZ = z6.array(z6.object({ fixId: z6.string(), diff: z6.string() })).nonempty();
1543
1878
 
1544
1879
  // src/features/analysis/scm/scm.ts
1545
1880
  function getScmLibTypeFromUrl(url) {
1546
1881
  if (!url) {
1547
1882
  return void 0;
1548
1883
  }
1549
- if (url.toLowerCase().startsWith("https://gitlab.com/")) {
1884
+ const urlObject = new URL(url);
1885
+ const hostname = urlObject.hostname;
1886
+ if (hostname === "gitlab.com") {
1550
1887
  return "GITLAB" /* GITLAB */;
1551
1888
  }
1552
- if (url.toLowerCase().startsWith("https://github.com/")) {
1889
+ if (hostname === "github.com") {
1553
1890
  return "GITHUB" /* GITHUB */;
1554
1891
  }
1892
+ if (hostname === "dev.azure.com" || hostname.endsWith(".visualstudio.com")) {
1893
+ return "ADO" /* ADO */;
1894
+ }
1555
1895
  return void 0;
1556
1896
  }
1557
1897
  async function scmCanReachRepo({
1558
1898
  repoUrl,
1559
1899
  githubToken,
1560
- gitlabToken
1900
+ gitlabToken,
1901
+ adoToken,
1902
+ scmOrg
1561
1903
  }) {
1562
1904
  try {
1563
1905
  const scmLibType = getScmLibTypeFromUrl(repoUrl);
1564
1906
  await SCMLib.init({
1565
1907
  url: repoUrl,
1566
- accessToken: scmLibType === "GITHUB" /* GITHUB */ ? githubToken : scmLibType === "GITLAB" /* GITLAB */ ? gitlabToken : "",
1567
- scmType: scmLibType
1908
+ accessToken: scmLibType === "GITHUB" /* GITHUB */ ? githubToken : scmLibType === "GITLAB" /* GITLAB */ ? gitlabToken : scmLibType === "ADO" /* ADO */ ? adoToken : "",
1909
+ scmType: scmLibType,
1910
+ scmOrg
1568
1911
  });
1569
1912
  return true;
1570
1913
  } catch (e) {
@@ -1597,11 +1940,13 @@ var RepoNoTokenAccessError = class extends Error {
1597
1940
  }
1598
1941
  };
1599
1942
  var SCMLib = class {
1600
- constructor(url, accessToken) {
1943
+ constructor(url, accessToken, scmOrg) {
1601
1944
  __publicField(this, "url");
1602
1945
  __publicField(this, "accessToken");
1946
+ __publicField(this, "scmOrg");
1603
1947
  this.accessToken = accessToken;
1604
1948
  this.url = url;
1949
+ this.scmOrg = scmOrg;
1605
1950
  }
1606
1951
  async getUrlWithCredentials() {
1607
1952
  if (!this.url) {
@@ -1612,9 +1957,13 @@ var SCMLib = class {
1612
1957
  if (!this.accessToken) {
1613
1958
  return trimmedUrl;
1614
1959
  }
1615
- const username = await this._getUsernameForAuthUrl();
1960
+ const scmLibType = getScmLibTypeFromUrl(trimmedUrl);
1961
+ if (scmLibType === "ADO" /* ADO */) {
1962
+ return `https://${this.accessToken}@${trimmedUrl.toLowerCase().replace("https://", "")}`;
1963
+ }
1616
1964
  const is_http = trimmedUrl.toLowerCase().startsWith("http://");
1617
1965
  const is_https = trimmedUrl.toLowerCase().startsWith("https://");
1966
+ const username = await this._getUsernameForAuthUrl();
1618
1967
  if (is_http) {
1619
1968
  return `http://${username}:${this.accessToken}@${trimmedUrl.toLowerCase().replace("http://", "")}`;
1620
1969
  } else if (is_https) {
@@ -1642,7 +1991,8 @@ var SCMLib = class {
1642
1991
  static async init({
1643
1992
  url,
1644
1993
  accessToken,
1645
- scmType
1994
+ scmType,
1995
+ scmOrg
1646
1996
  }) {
1647
1997
  let trimmedUrl = void 0;
1648
1998
  if (url) {
@@ -1650,12 +2000,17 @@ var SCMLib = class {
1650
2000
  }
1651
2001
  try {
1652
2002
  if ("GITHUB" /* GITHUB */ === scmType) {
1653
- const scm = new GithubSCMLib(trimmedUrl, accessToken);
2003
+ const scm = new GithubSCMLib(trimmedUrl, accessToken, scmOrg);
1654
2004
  await scm.validateParams();
1655
2005
  return scm;
1656
2006
  }
1657
2007
  if ("GITLAB" /* GITLAB */ === scmType) {
1658
- const scm = new GitlabSCMLib(trimmedUrl, accessToken);
2008
+ const scm = new GitlabSCMLib(trimmedUrl, accessToken, scmOrg);
2009
+ await scm.validateParams();
2010
+ return scm;
2011
+ }
2012
+ if ("ADO" /* ADO */ === scmType) {
2013
+ const scm = new AdoSCMLib(trimmedUrl, accessToken, scmOrg);
1659
2014
  await scm.validateParams();
1660
2015
  return scm;
1661
2016
  }
@@ -1664,7 +2019,170 @@ var SCMLib = class {
1664
2019
  throw new RepoNoTokenAccessError("no access to repo");
1665
2020
  }
1666
2021
  }
1667
- return new StubSCMLib(trimmedUrl);
2022
+ return new StubSCMLib(trimmedUrl, void 0, void 0);
2023
+ }
2024
+ };
2025
+ var AdoSCMLib = class extends SCMLib {
2026
+ updatePrComment(_params, _oktokit) {
2027
+ throw new Error("updatePrComment not implemented.");
2028
+ }
2029
+ getPrComment(_commentId) {
2030
+ throw new Error("getPrComment not implemented.");
2031
+ }
2032
+ async forkRepo() {
2033
+ throw new Error("forkRepo not supported yet");
2034
+ }
2035
+ async createOrUpdateRepositorySecret() {
2036
+ throw new Error("createOrUpdateRepositorySecret not supported yet");
2037
+ }
2038
+ async createPullRequestWithNewFile(_sourceRepoUrl, _filesPaths, _userRepoUrl, _title, _body) {
2039
+ throw new Error("createPullRequestWithNewFile not supported yet");
2040
+ }
2041
+ async createSubmitRequest(targetBranchName, sourceBranchName, title, body) {
2042
+ if (!this.accessToken || !this.url) {
2043
+ console.error("no access token or no url");
2044
+ throw new Error("no access token or no url");
2045
+ }
2046
+ return String(
2047
+ await createAdoPullRequest({
2048
+ title,
2049
+ body,
2050
+ targetBranchName,
2051
+ sourceBranchName,
2052
+ repoUrl: this.url,
2053
+ accessToken: this.accessToken,
2054
+ tokenOrg: this.scmOrg
2055
+ })
2056
+ );
2057
+ }
2058
+ async validateParams() {
2059
+ return adoValidateParams({
2060
+ url: this.url,
2061
+ accessToken: this.accessToken,
2062
+ tokenOrg: this.scmOrg
2063
+ });
2064
+ }
2065
+ async getRepoList(scmOrg) {
2066
+ if (!this.accessToken) {
2067
+ console.error("no access token");
2068
+ throw new Error("no access token");
2069
+ }
2070
+ return getAdoRepoList({
2071
+ orgName: scmOrg,
2072
+ tokenOrg: this.scmOrg,
2073
+ accessToken: this.accessToken
2074
+ });
2075
+ }
2076
+ async getBranchList() {
2077
+ if (!this.accessToken || !this.url) {
2078
+ console.error("no access token or no url");
2079
+ throw new Error("no access token or no url");
2080
+ }
2081
+ return getAdoBranchList({
2082
+ accessToken: this.accessToken,
2083
+ tokenOrg: this.scmOrg,
2084
+ repoUrl: this.url
2085
+ });
2086
+ }
2087
+ getAuthHeaders() {
2088
+ if (this.accessToken) {
2089
+ if (getAdoTokenType(this.accessToken) === "OAUTH" /* OAUTH */) {
2090
+ return {
2091
+ authorization: `Bearer ${this.accessToken}`
2092
+ };
2093
+ } else {
2094
+ return {
2095
+ authorization: `Basic ${Buffer.from(":" + this.accessToken).toString(
2096
+ "base64"
2097
+ )}`
2098
+ };
2099
+ }
2100
+ }
2101
+ return {};
2102
+ }
2103
+ getDownloadUrl(sha) {
2104
+ if (!this.url) {
2105
+ console.error("no url");
2106
+ throw new Error("no url");
2107
+ }
2108
+ return getAdoDownloadUrl({ repoUrl: this.url, branch: sha });
2109
+ }
2110
+ async _getUsernameForAuthUrl() {
2111
+ throw new Error("_getUsernameForAuthUrl() is not relevant for ADO");
2112
+ }
2113
+ async getIsRemoteBranch(branch) {
2114
+ if (!this.accessToken || !this.url) {
2115
+ console.error("no access token or no url");
2116
+ throw new Error("no access token or no url");
2117
+ }
2118
+ return getAdoIsRemoteBranch({
2119
+ accessToken: this.accessToken,
2120
+ tokenOrg: this.scmOrg,
2121
+ repoUrl: this.url,
2122
+ branch
2123
+ });
2124
+ }
2125
+ async getUserHasAccessToRepo() {
2126
+ if (!this.accessToken || !this.url) {
2127
+ console.error("no access token or no url");
2128
+ throw new Error("no access token or no url");
2129
+ }
2130
+ return getAdoIsUserCollaborator({
2131
+ accessToken: this.accessToken,
2132
+ tokenOrg: this.scmOrg,
2133
+ repoUrl: this.url
2134
+ });
2135
+ }
2136
+ async getUsername() {
2137
+ throw new Error("getUsername() is not relevant for ADO");
2138
+ }
2139
+ async getSubmitRequestStatus(scmSubmitRequestId) {
2140
+ if (!this.accessToken || !this.url) {
2141
+ console.error("no access token or no url");
2142
+ throw new Error("no access token or no url");
2143
+ }
2144
+ const state = await getAdoPullRequestStatus({
2145
+ accessToken: this.accessToken,
2146
+ tokenOrg: this.scmOrg,
2147
+ repoUrl: this.url,
2148
+ prNumber: Number(scmSubmitRequestId)
2149
+ });
2150
+ switch (state) {
2151
+ case "completed" /* completed */:
2152
+ return "MERGED" /* MERGED */;
2153
+ case "active" /* active */:
2154
+ return "OPEN" /* OPEN */;
2155
+ case "abandoned" /* abandoned */:
2156
+ return "CLOSED" /* CLOSED */;
2157
+ default:
2158
+ throw new Error(`unknown state ${state}`);
2159
+ }
2160
+ }
2161
+ async getRepoBlameRanges(_ref, _path) {
2162
+ return await getAdoBlameRanges();
2163
+ }
2164
+ async getReferenceData(ref) {
2165
+ if (!this.url) {
2166
+ console.error("no url");
2167
+ throw new Error("no url");
2168
+ }
2169
+ return await getAdoReferenceData({
2170
+ ref,
2171
+ repoUrl: this.url,
2172
+ accessToken: this.accessToken,
2173
+ tokenOrg: this.scmOrg
2174
+ });
2175
+ }
2176
+ async getRepoDefaultBranch() {
2177
+ if (!this.url) {
2178
+ console.error("no url");
2179
+ throw new Error("no url");
2180
+ }
2181
+ return await getAdoRepoDefaultBranch({
2182
+ repoUrl: this.url,
2183
+ tokenOrg: this.scmOrg,
2184
+ accessToken: this.accessToken
2185
+ });
1668
2186
  }
1669
2187
  };
1670
2188
  var GitlabSCMLib = class extends SCMLib {
@@ -1707,7 +2225,7 @@ var GitlabSCMLib = class extends SCMLib {
1707
2225
  async createPullRequestWithNewFile(_sourceRepoUrl, _filesPaths, _userRepoUrl, _title, _body) {
1708
2226
  throw new Error("not implemented");
1709
2227
  }
1710
- async getRepoList() {
2228
+ async getRepoList(_scmOrg) {
1711
2229
  if (!this.accessToken) {
1712
2230
  console.error("no access token");
1713
2231
  throw new Error("no access token");
@@ -1795,13 +2313,13 @@ var GitlabSCMLib = class extends SCMLib {
1795
2313
  throw new Error(`unknown state ${state}`);
1796
2314
  }
1797
2315
  }
1798
- async getRepoBlameRanges(ref, path8) {
2316
+ async getRepoBlameRanges(ref, path9) {
1799
2317
  if (!this.url) {
1800
2318
  console.error("no url");
1801
2319
  throw new Error("no url");
1802
2320
  }
1803
2321
  return await getGitlabBlameRanges(
1804
- { ref, path: path8, gitlabUrl: this.url },
2322
+ { ref, path: path9, gitlabUrl: this.url },
1805
2323
  {
1806
2324
  gitlabAuthToken: this.accessToken
1807
2325
  }
@@ -1837,8 +2355,8 @@ var GitlabSCMLib = class extends SCMLib {
1837
2355
  };
1838
2356
  var GithubSCMLib = class extends SCMLib {
1839
2357
  // 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);
2358
+ constructor(url, accessToken, scmOrg) {
2359
+ super(url, accessToken, scmOrg);
1842
2360
  __publicField(this, "oktokit");
1843
2361
  this.oktokit = new Octokit2({ auth: accessToken });
1844
2362
  }
@@ -1873,7 +2391,7 @@ var GithubSCMLib = class extends SCMLib {
1873
2391
  throw new Error("cannot delete comment without access token or url");
1874
2392
  }
1875
2393
  const oktokit = _oktokit || this.oktokit;
1876
- const { owner, repo } = parseOwnerAndRepo(this.url);
2394
+ const { owner, repo } = parseGithubOwnerAndRepo(this.url);
1877
2395
  const { data: repositoryPublicKeyResponse } = await getARepositoryPublicKey(
1878
2396
  oktokit,
1879
2397
  {
@@ -1914,7 +2432,7 @@ var GithubSCMLib = class extends SCMLib {
1914
2432
  throw new Error("cannot post on PR without access token or url");
1915
2433
  }
1916
2434
  const oktokit = _oktokit || this.oktokit;
1917
- const { owner, repo } = parseOwnerAndRepo(this.url);
2435
+ const { owner, repo } = parseGithubOwnerAndRepo(this.url);
1918
2436
  return postPrComment(oktokit, {
1919
2437
  ...params,
1920
2438
  owner,
@@ -1926,7 +2444,7 @@ var GithubSCMLib = class extends SCMLib {
1926
2444
  throw new Error("cannot update on PR without access token or url");
1927
2445
  }
1928
2446
  const oktokit = _oktokit || this.oktokit;
1929
- const { owner, repo } = parseOwnerAndRepo(this.url);
2447
+ const { owner, repo } = parseGithubOwnerAndRepo(this.url);
1930
2448
  return updatePrComment(oktokit, {
1931
2449
  ...params,
1932
2450
  owner,
@@ -1938,7 +2456,7 @@ var GithubSCMLib = class extends SCMLib {
1938
2456
  throw new Error("cannot delete comment without access token or url");
1939
2457
  }
1940
2458
  const oktokit = _oktokit || this.oktokit;
1941
- const { owner, repo } = parseOwnerAndRepo(this.url);
2459
+ const { owner, repo } = parseGithubOwnerAndRepo(this.url);
1942
2460
  return deleteComment(oktokit, {
1943
2461
  ...params,
1944
2462
  owner,
@@ -1950,7 +2468,7 @@ var GithubSCMLib = class extends SCMLib {
1950
2468
  throw new Error("cannot get Pr Comments without access token or url");
1951
2469
  }
1952
2470
  const oktokit = _oktokit || this.oktokit;
1953
- const { owner, repo } = parseOwnerAndRepo(this.url);
2471
+ const { owner, repo } = parseGithubOwnerAndRepo(this.url);
1954
2472
  return getPrComments(oktokit, {
1955
2473
  per_page: 100,
1956
2474
  ...params,
@@ -1962,15 +2480,15 @@ var GithubSCMLib = class extends SCMLib {
1962
2480
  if (!this.accessToken || !this.url) {
1963
2481
  throw new Error("cannot get Pr Comments without access token or url");
1964
2482
  }
1965
- const { owner, repo } = parseOwnerAndRepo(this.url);
2483
+ const { owner, repo } = parseGithubOwnerAndRepo(this.url);
1966
2484
  const prRes = await getPrDiff(this.oktokit, {
1967
2485
  ...params,
1968
2486
  owner,
1969
2487
  repo
1970
2488
  });
1971
- return z6.string().parse(prRes.data);
2489
+ return z7.string().parse(prRes.data);
1972
2490
  }
1973
- async getRepoList() {
2491
+ async getRepoList(_scmOrg) {
1974
2492
  if (!this.accessToken) {
1975
2493
  console.error("no access token");
1976
2494
  throw new Error("no access token");
@@ -2042,13 +2560,13 @@ var GithubSCMLib = class extends SCMLib {
2042
2560
  }
2043
2561
  throw new Error(`unknown state ${state}`);
2044
2562
  }
2045
- async getRepoBlameRanges(ref, path8) {
2563
+ async getRepoBlameRanges(ref, path9) {
2046
2564
  if (!this.url) {
2047
2565
  console.error("no url");
2048
2566
  throw new Error("no url");
2049
2567
  }
2050
2568
  return await getGithubBlameRanges(
2051
- { ref, path: path8, gitHubUrl: this.url },
2569
+ { ref, path: path9, gitHubUrl: this.url },
2052
2570
  {
2053
2571
  githubAuthToken: this.accessToken
2054
2572
  }
@@ -2071,7 +2589,7 @@ var GithubSCMLib = class extends SCMLib {
2071
2589
  console.error("no url");
2072
2590
  throw new Error("no url");
2073
2591
  }
2074
- const { owner, repo } = parseOwnerAndRepo(this.url);
2592
+ const { owner, repo } = parseGithubOwnerAndRepo(this.url);
2075
2593
  return await getPrComment(this.oktokit, {
2076
2594
  repo,
2077
2595
  owner,
@@ -2125,9 +2643,9 @@ var StubSCMLib = class extends SCMLib {
2125
2643
  console.error("createPullRequestWithNewFile() not implemented");
2126
2644
  throw new Error("createPullRequestWithNewFile() not implemented");
2127
2645
  }
2128
- async getRepoList() {
2129
- console.error("getBranchList() not implemented");
2130
- throw new Error("getBranchList() not implemented");
2646
+ async getRepoList(_scmOrg) {
2647
+ console.error("getRepoList() not implemented");
2648
+ throw new Error("getRepoList() not implemented");
2131
2649
  }
2132
2650
  async getBranchList() {
2133
2651
  console.error("getBranchList() not implemented");
@@ -2167,196 +2685,415 @@ var StubSCMLib = class extends SCMLib {
2167
2685
  }
2168
2686
  };
2169
2687
 
2170
- // src/features/analysis/scm/gitlab.ts
2171
- function removeTrailingSlash2(str) {
2688
+ // src/features/analysis/scm/ado.ts
2689
+ function removeTrailingSlash3(str) {
2172
2690
  return str.trim().replace(/\/+$/, "");
2173
2691
  }
2174
- var EnvVariablesZod2 = z7.object({
2175
- GITLAB_API_TOKEN: z7.string().optional()
2176
- });
2177
- var { GITLAB_API_TOKEN } = EnvVariablesZod2.parse(process.env);
2178
- function getGitBeaker(options) {
2179
- const token = options?.gitlabAuthToken ?? GITLAB_API_TOKEN ?? "";
2180
- if (token?.startsWith("glpat-") || token === "") {
2181
- return new Gitlab({ token });
2692
+ async function _getOrgsForOauthToken({ oauthToken }) {
2693
+ const profileZ = z8.object({
2694
+ displayName: z8.string(),
2695
+ publicAlias: z8.string().min(1),
2696
+ emailAddress: z8.string(),
2697
+ coreRevision: z8.number(),
2698
+ timeStamp: z8.string(),
2699
+ id: z8.string(),
2700
+ revision: z8.number()
2701
+ });
2702
+ const accountsZ = z8.object({
2703
+ count: z8.number(),
2704
+ value: z8.array(
2705
+ z8.object({
2706
+ accountId: z8.string(),
2707
+ accountUri: z8.string(),
2708
+ accountName: z8.string()
2709
+ })
2710
+ )
2711
+ });
2712
+ const profileRes = await fetch(
2713
+ "https://app.vssps.visualstudio.com/_apis/profile/profiles/me?api-version=6.0",
2714
+ {
2715
+ method: "GET",
2716
+ headers: {
2717
+ Authorization: `Bearer ${oauthToken}`
2718
+ }
2719
+ }
2720
+ );
2721
+ const profileJson = await profileRes.json();
2722
+ const profile = profileZ.parse(profileJson);
2723
+ const accountsRes = await fetch(
2724
+ `https://app.vssps.visualstudio.com/_apis/accounts?memberId=${profile.publicAlias}&api-version=6.0`,
2725
+ {
2726
+ method: "GET",
2727
+ headers: {
2728
+ Authorization: `Bearer ${oauthToken}`
2729
+ }
2730
+ }
2731
+ );
2732
+ const accountsJson = await accountsRes.json();
2733
+ const accounts = accountsZ.parse(accountsJson);
2734
+ const orgs = accounts.value.map((account) => account.accountName).filter((value, index, array) => array.indexOf(value) === index);
2735
+ return orgs;
2736
+ }
2737
+ function _getPublicAdoClient({ orgName }) {
2738
+ const orgUrl = `https://dev.azure.com/${orgName}`;
2739
+ const authHandler = api.getPersonalAccessTokenHandler("");
2740
+ authHandler.canHandleAuthentication = () => false;
2741
+ authHandler.prepareRequest = (_options) => {
2742
+ return;
2743
+ };
2744
+ const connection = new api.WebApi(orgUrl, authHandler);
2745
+ return connection;
2746
+ }
2747
+ function getAdoTokenType(token) {
2748
+ if (token.includes(".")) {
2749
+ return "OAUTH" /* OAUTH */;
2182
2750
  }
2183
- return new Gitlab({ oauthToken: token });
2751
+ return "PAT" /* PAT */;
2184
2752
  }
2185
- async function gitlabValidateParams({
2753
+ async function getAdoApiClient({
2754
+ accessToken,
2755
+ tokenOrg,
2756
+ orgName
2757
+ }) {
2758
+ if (!accessToken || tokenOrg && tokenOrg !== orgName) {
2759
+ return _getPublicAdoClient({ orgName });
2760
+ }
2761
+ const orgUrl = `https://dev.azure.com/${orgName}`;
2762
+ if (getAdoTokenType(accessToken) === "OAUTH" /* OAUTH */) {
2763
+ const connection2 = new api.WebApi(orgUrl, api.getBearerHandler(accessToken));
2764
+ return connection2;
2765
+ }
2766
+ const authHandler = api.getPersonalAccessTokenHandler(accessToken);
2767
+ const connection = new api.WebApi(orgUrl, authHandler);
2768
+ return connection;
2769
+ }
2770
+ async function adoValidateParams({
2186
2771
  url,
2187
- accessToken
2772
+ accessToken,
2773
+ tokenOrg
2188
2774
  }) {
2189
2775
  try {
2190
- const api = getGitBeaker({ gitlabAuthToken: accessToken });
2191
- if (accessToken) {
2192
- await api.Users.showCurrentUser();
2776
+ if (!url && accessToken && getAdoTokenType(accessToken) === "OAUTH" /* OAUTH */) {
2777
+ await _getOrgsForOauthToken({ oauthToken: accessToken });
2778
+ return;
2193
2779
  }
2780
+ let org = tokenOrg;
2194
2781
  if (url) {
2195
- const { projectPath } = parseOwnerAndRepo2(url);
2196
- await api.Projects.show(projectPath);
2782
+ const { owner } = parseAdoOwnerAndRepo(url);
2783
+ org = owner;
2784
+ }
2785
+ if (!org) {
2786
+ throw new InvalidRepoUrlError(`invalid ADO ORG ${org}`);
2197
2787
  }
2788
+ const api2 = await getAdoApiClient({
2789
+ accessToken,
2790
+ tokenOrg,
2791
+ orgName: org
2792
+ });
2793
+ await api2.connect();
2198
2794
  } catch (e) {
2199
2795
  const error = e;
2200
2796
  const code = error.code || error.status || error.statusCode || error.response?.status || error.response?.statusCode || error.response?.code;
2201
2797
  const description = error.description || `${e}`;
2202
2798
  if (code === 401 || code === 403 || description.includes("401") || description.includes("403")) {
2203
- throw new InvalidAccessTokenError(`invalid gitlab access token`);
2799
+ throw new InvalidAccessTokenError(`invalid ADO access token`);
2204
2800
  }
2205
2801
  if (code === 404 || description.includes("404") || description.includes("Not Found")) {
2206
- throw new InvalidRepoUrlError(`invalid gitlab repo Url ${url}`);
2802
+ throw new InvalidRepoUrlError(`invalid ADO repo URL ${url}`);
2207
2803
  }
2208
2804
  throw e;
2209
2805
  }
2210
2806
  }
2211
- async function getGitlabUsername(accessToken) {
2212
- const api = getGitBeaker({ gitlabAuthToken: accessToken });
2213
- const res = await api.Users.showCurrentUser();
2214
- return res.username;
2215
- }
2216
- async function getGitlabIsUserCollaborator({
2217
- username,
2807
+ async function getAdoIsUserCollaborator({
2218
2808
  accessToken,
2809
+ tokenOrg,
2219
2810
  repoUrl
2220
2811
  }) {
2221
2812
  try {
2222
- const { projectPath } = parseOwnerAndRepo2(repoUrl);
2223
- const api = getGitBeaker({ gitlabAuthToken: accessToken });
2224
- const res = await api.Projects.show(projectPath);
2225
- const members = await api.ProjectMembers.all(res.id, {
2226
- includeInherited: true
2813
+ const { owner, repo, projectName } = parseAdoOwnerAndRepo(repoUrl);
2814
+ const api2 = await getAdoApiClient({
2815
+ accessToken,
2816
+ tokenOrg,
2817
+ orgName: owner
2227
2818
  });
2228
- return !!members.find((member) => member.username === username);
2819
+ const git = await api2.getGitApi();
2820
+ const branches = await git.getBranches(repo, projectName);
2821
+ if (!branches || branches.length === 0) {
2822
+ throw new InvalidRepoUrlError("no branches");
2823
+ }
2824
+ return true;
2229
2825
  } catch (e) {
2230
2826
  return false;
2231
2827
  }
2232
2828
  }
2233
- async function getGitlabMergeRequestStatus({
2829
+ var adoStatusNumberToEnumMap = {
2830
+ 1: "active" /* active */,
2831
+ 2: "abandoned" /* abandoned */,
2832
+ 3: "completed" /* completed */,
2833
+ 4: "all" /* all */
2834
+ };
2835
+ async function getAdoPullRequestStatus({
2234
2836
  accessToken,
2837
+ tokenOrg,
2235
2838
  repoUrl,
2236
- mrNumber
2839
+ prNumber
2237
2840
  }) {
2238
- const { projectPath } = parseOwnerAndRepo2(repoUrl);
2239
- const api = getGitBeaker({ gitlabAuthToken: accessToken });
2240
- const res = await api.MergeRequests.show(projectPath, mrNumber);
2241
- switch (res.state) {
2242
- case "merged" /* merged */:
2243
- case "opened" /* opened */:
2244
- case "closed" /* closed */:
2245
- return res.state;
2246
- default:
2247
- throw new Error(`unknown merge request state ${res.state}`);
2841
+ const { owner, repo, projectName } = parseAdoOwnerAndRepo(repoUrl);
2842
+ const api2 = await getAdoApiClient({
2843
+ accessToken,
2844
+ tokenOrg,
2845
+ orgName: owner
2846
+ });
2847
+ const git = await api2.getGitApi();
2848
+ const res = await git.getPullRequest(repo, prNumber, projectName);
2849
+ if (!res.status || res.status < 1 || res.status > 3) {
2850
+ throw new Error("bad pr status for ADO");
2248
2851
  }
2852
+ return adoStatusNumberToEnumMap[res.status];
2249
2853
  }
2250
- async function getGitlabIsRemoteBranch({
2854
+ async function getAdoIsRemoteBranch({
2251
2855
  accessToken,
2856
+ tokenOrg,
2252
2857
  repoUrl,
2253
2858
  branch
2254
2859
  }) {
2255
- const { projectPath } = parseOwnerAndRepo2(repoUrl);
2256
- const api = getGitBeaker({ gitlabAuthToken: accessToken });
2860
+ const { owner, repo, projectName } = parseAdoOwnerAndRepo(repoUrl);
2861
+ const api2 = await getAdoApiClient({
2862
+ accessToken,
2863
+ tokenOrg,
2864
+ orgName: owner
2865
+ });
2866
+ const git = await api2.getGitApi();
2257
2867
  try {
2258
- const res = await api.Branches.show(projectPath, branch);
2259
- return res.name === branch;
2868
+ const branchStatus = await git.getBranch(repo, branch, projectName);
2869
+ if (!branchStatus || !branchStatus.commit) {
2870
+ throw new InvalidRepoUrlError("no branch status");
2871
+ }
2872
+ return branchStatus.name === branch;
2260
2873
  } catch (e) {
2261
2874
  return false;
2262
2875
  }
2263
2876
  }
2264
- async function getGitlabRepoList(accessToken) {
2265
- const api = getGitBeaker({ gitlabAuthToken: accessToken });
2266
- const res = await api.Projects.all({
2267
- membership: true,
2268
- //TODO: a bug in the sorting mechanism of this api call
2269
- //disallows us to sort by updated_at in descending order
2270
- //so we have to sort by updated_at in ascending order.
2271
- //We can wait for the bug to be fixed or call the api
2272
- //directly with fetch()
2273
- sort: "asc",
2274
- orderBy: "updated_at",
2275
- perPage: 100
2276
- });
2277
- return Promise.all(
2278
- res.map(async (project) => {
2279
- const proj = await api.Projects.show(project.id);
2280
- const owner = proj.namespace.name;
2281
- const repoLanguages = await api.Projects.showLanguages(project.id);
2282
- return {
2283
- repoName: project.path,
2284
- repoUrl: project.web_url,
2285
- repoOwner: owner,
2286
- repoLanguages: Object.keys(repoLanguages),
2287
- repoIsPublic: project.visibility === "public",
2288
- repoUpdatedAt: project.last_activity_at
2289
- };
2877
+ async function getAdoRepoList({
2878
+ orgName,
2879
+ tokenOrg,
2880
+ accessToken
2881
+ }) {
2882
+ let orgs = [];
2883
+ if (getAdoTokenType(accessToken) === "OAUTH" /* OAUTH */) {
2884
+ orgs = await _getOrgsForOauthToken({ oauthToken: accessToken });
2885
+ }
2886
+ if (orgs.length === 0 && !orgName) {
2887
+ throw new Error(`no orgs for ADO`);
2888
+ } else if (orgs.length === 0 && orgName) {
2889
+ orgs = [orgName];
2890
+ }
2891
+ const repos = (await Promise.allSettled(
2892
+ orgs.map(async (org) => {
2893
+ const orgApi = await getAdoApiClient({
2894
+ accessToken,
2895
+ tokenOrg,
2896
+ orgName: org
2897
+ });
2898
+ const gitOrg = await orgApi.getGitApi();
2899
+ const orgRepos = await gitOrg.getRepositories();
2900
+ const repoInfoList = (await Promise.allSettled(
2901
+ orgRepos.map(async (repo) => {
2902
+ if (!repo.name || !repo.remoteUrl || !repo.defaultBranch) {
2903
+ throw new InvalidRepoUrlError("bad repo");
2904
+ }
2905
+ const branch = await gitOrg.getBranch(
2906
+ repo.name,
2907
+ repo.defaultBranch.replace(/^refs\/heads\//, ""),
2908
+ repo.project?.name
2909
+ );
2910
+ return {
2911
+ repoName: repo.name,
2912
+ repoUrl: repo.remoteUrl.replace(
2913
+ /^[hH][tT][tT][pP][sS]:\/\/[^/]+@/,
2914
+ "https://"
2915
+ ),
2916
+ repoOwner: org,
2917
+ repoIsPublic: repo.project?.visibility === 2,
2918
+ //2 is public in the ADO API
2919
+ repoLanguages: [],
2920
+ repoUpdatedAt: branch.commit?.committer?.date?.toDateString() || repo.project?.lastUpdateTime?.toDateString() || (/* @__PURE__ */ new Date()).toDateString()
2921
+ };
2922
+ })
2923
+ )).reduce((acc, res) => {
2924
+ if (res.status === "fulfilled") {
2925
+ acc.push(res.value);
2926
+ }
2927
+ return acc;
2928
+ }, []);
2929
+ return repoInfoList;
2290
2930
  })
2291
- );
2931
+ )).reduce((acc, res) => {
2932
+ if (res.status === "fulfilled") {
2933
+ return acc.concat(res.value);
2934
+ }
2935
+ return acc;
2936
+ }, []);
2937
+ return repos;
2292
2938
  }
2293
- async function getGitlabBranchList({
2939
+ function getAdoDownloadUrl({
2940
+ repoUrl,
2941
+ branch
2942
+ }) {
2943
+ const { owner, repo, projectName } = parseAdoOwnerAndRepo(repoUrl);
2944
+ 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`;
2945
+ }
2946
+ async function getAdoBranchList({
2294
2947
  accessToken,
2948
+ tokenOrg,
2295
2949
  repoUrl
2296
2950
  }) {
2297
- const { projectPath } = parseOwnerAndRepo2(repoUrl);
2298
- const api = getGitBeaker({ gitlabAuthToken: accessToken });
2951
+ const { owner, repo, projectName } = parseAdoOwnerAndRepo(repoUrl);
2952
+ const api2 = await getAdoApiClient({
2953
+ accessToken,
2954
+ tokenOrg,
2955
+ orgName: owner
2956
+ });
2957
+ const git = await api2.getGitApi();
2299
2958
  try {
2300
- const res = await api.Branches.all(projectPath, {
2301
- perPage: 100,
2302
- pagination: "keyset",
2303
- orderBy: "updated_at",
2304
- sort: "dec"
2959
+ const res = await git.getBranches(repo, projectName);
2960
+ res.sort((a, b) => {
2961
+ if (!a.commit?.committer?.date || !b.commit?.committer?.date) {
2962
+ return 0;
2963
+ }
2964
+ return b.commit?.committer?.date.getTime() - a.commit?.committer?.date.getTime();
2305
2965
  });
2306
- return res.map((branch) => branch.name);
2966
+ return res.reduce((acc, branch) => {
2967
+ if (!branch.name) {
2968
+ return acc;
2969
+ }
2970
+ acc.push(branch.name);
2971
+ return acc;
2972
+ }, []);
2307
2973
  } catch (e) {
2308
2974
  return [];
2309
2975
  }
2310
2976
  }
2311
- async function createMergeRequest(options) {
2312
- const { projectPath } = parseOwnerAndRepo2(options.repoUrl);
2313
- const api = getGitBeaker({ gitlabAuthToken: options.accessToken });
2314
- const res = await api.MergeRequests.create(
2315
- projectPath,
2316
- options.sourceBranchName,
2317
- options.targetBranchName,
2318
- options.title,
2977
+ async function createAdoPullRequest(options) {
2978
+ const { owner, repo, projectName } = parseAdoOwnerAndRepo(options.repoUrl);
2979
+ const api2 = await getAdoApiClient({
2980
+ accessToken: options.accessToken,
2981
+ tokenOrg: options.tokenOrg,
2982
+ orgName: owner
2983
+ });
2984
+ const git = await api2.getGitApi();
2985
+ const res = await git.createPullRequest(
2319
2986
  {
2987
+ sourceRefName: `refs/heads/${options.sourceBranchName}`,
2988
+ targetRefName: `refs/heads/${options.targetBranchName}`,
2989
+ title: options.title,
2320
2990
  description: options.body
2321
- }
2991
+ },
2992
+ repo,
2993
+ projectName
2322
2994
  );
2323
- return res.iid;
2995
+ return res.pullRequestId;
2324
2996
  }
2325
- async function getGitlabRepoDefaultBranch(repoUrl, options) {
2326
- const api = getGitBeaker({ gitlabAuthToken: options?.gitlabAuthToken });
2327
- const { projectPath } = parseOwnerAndRepo2(repoUrl);
2328
- const project = await api.Projects.show(projectPath);
2329
- if (!project.default_branch) {
2330
- throw new Error("no default branch");
2997
+ async function getAdoRepoDefaultBranch({
2998
+ repoUrl,
2999
+ tokenOrg,
3000
+ accessToken
3001
+ }) {
3002
+ const { owner, repo, projectName } = parseAdoOwnerAndRepo(repoUrl);
3003
+ const api2 = await getAdoApiClient({
3004
+ accessToken,
3005
+ tokenOrg,
3006
+ orgName: owner
3007
+ });
3008
+ const git = await api2.getGitApi();
3009
+ const branches = await git.getBranches(repo, projectName);
3010
+ if (!branches || branches.length === 0) {
3011
+ throw new InvalidRepoUrlError("no branches");
2331
3012
  }
2332
- return project.default_branch;
3013
+ const res = branches.find((branch) => branch.isBaseVersion);
3014
+ if (!res || !res.name) {
3015
+ throw new InvalidRepoUrlError("no default branch");
3016
+ }
3017
+ return res.name;
2333
3018
  }
2334
- async function getGitlabReferenceData({ ref, gitlabUrl }, options) {
2335
- const { projectPath } = parseOwnerAndRepo2(gitlabUrl);
2336
- const api = getGitBeaker({ gitlabAuthToken: options?.gitlabAuthToken });
3019
+ async function getAdoReferenceData({
3020
+ ref,
3021
+ repoUrl,
3022
+ accessToken,
3023
+ tokenOrg
3024
+ }) {
3025
+ const { owner, repo, projectName } = parseAdoOwnerAndRepo(repoUrl);
3026
+ const api2 = await getAdoApiClient({
3027
+ accessToken,
3028
+ tokenOrg,
3029
+ orgName: owner
3030
+ });
3031
+ if (!projectName) {
3032
+ throw new InvalidUrlPatternError("no project name");
3033
+ }
3034
+ const git = await api2.getGitApi();
2337
3035
  const results = await Promise.allSettled([
2338
3036
  (async () => {
2339
- const res = await api.Branches.show(projectPath, ref);
3037
+ const res = await git.getBranch(repo, ref, projectName);
3038
+ if (!res.commit || !res.commit.commitId) {
3039
+ throw new InvalidRepoUrlError("no commit on branch");
3040
+ }
2340
3041
  return {
2341
- sha: res.commit.id,
3042
+ sha: res.commit.commitId,
2342
3043
  type: "BRANCH" /* BRANCH */,
2343
- date: res.commit.committed_date ? new Date(res.commit.committed_date) : void 0
3044
+ date: res.commit.committer?.date || /* @__PURE__ */ new Date()
2344
3045
  };
2345
3046
  })(),
2346
3047
  (async () => {
2347
- const res = await api.Commits.show(projectPath, ref);
3048
+ const res = await git.getCommits(
3049
+ repo,
3050
+ {
3051
+ fromCommitId: ref,
3052
+ toCommitId: ref,
3053
+ $top: 1
3054
+ },
3055
+ projectName
3056
+ );
3057
+ const commit = res[0];
3058
+ if (!commit || !commit.commitId) {
3059
+ throw new Error("no commit");
3060
+ }
2348
3061
  return {
2349
- sha: res.id,
3062
+ sha: commit.commitId,
2350
3063
  type: "COMMIT" /* COMMIT */,
2351
- date: res.committed_date ? new Date(res.committed_date) : void 0
3064
+ date: commit.committer?.date || /* @__PURE__ */ new Date()
2352
3065
  };
2353
3066
  })(),
2354
3067
  (async () => {
2355
- const res = await api.Tags.show(projectPath, ref);
3068
+ const res = await git.getRefs(repo, projectName, `tags/${ref}`);
3069
+ if (!res[0] || !res[0].objectId) {
3070
+ throw new Error("no tag ref");
3071
+ }
3072
+ let objectId = res[0].objectId;
3073
+ try {
3074
+ const tag = await git.getAnnotatedTag(projectName, repo, objectId);
3075
+ if (tag.taggedObject?.objectId) {
3076
+ objectId = tag.taggedObject.objectId;
3077
+ }
3078
+ } catch (e) {
3079
+ }
3080
+ const commitRes2 = await git.getCommits(
3081
+ repo,
3082
+ {
3083
+ fromCommitId: objectId,
3084
+ toCommitId: objectId,
3085
+ $top: 1
3086
+ },
3087
+ projectName
3088
+ );
3089
+ const commit = commitRes2[0];
3090
+ if (!commit) {
3091
+ throw new Error("no commit");
3092
+ }
2356
3093
  return {
2357
- sha: res.commit.id,
3094
+ sha: objectId,
2358
3095
  type: "TAG" /* TAG */,
2359
- date: res.commit.committed_date ? new Date(res.commit.committed_date) : void 0
3096
+ date: commit.committer?.date || /* @__PURE__ */ new Date()
2360
3097
  };
2361
3098
  })()
2362
3099
  ]);
@@ -2372,41 +3109,34 @@ async function getGitlabReferenceData({ ref, gitlabUrl }, options) {
2372
3109
  }
2373
3110
  throw new RefNotFoundError(`ref: ${ref} does not exist`);
2374
3111
  }
2375
- function parseOwnerAndRepo2(gitlabUrl) {
2376
- gitlabUrl = removeTrailingSlash2(gitlabUrl);
2377
- const parsingResult = parseScmURL(gitlabUrl);
2378
- if (!parsingResult || parsingResult.hostname !== "gitlab.com") {
2379
- throw new InvalidUrlPatternError(`invalid gitlab repo Url ${gitlabUrl}`);
3112
+ function parseAdoOwnerAndRepo(adoUrl) {
3113
+ adoUrl = removeTrailingSlash3(adoUrl);
3114
+ const parsingResult = parseScmURL(adoUrl);
3115
+ if (!parsingResult || parsingResult.hostname !== "dev.azure.com" && !parsingResult.hostname.endsWith(".visualstudio.com")) {
3116
+ throw new InvalidUrlPatternError(`invalid ADO repo URL: ${adoUrl}`);
2380
3117
  }
2381
- const { organization, repoName, projectPath } = parsingResult;
2382
- return { owner: organization, repo: repoName, projectPath };
3118
+ const { organization, repoName, projectName, projectPath, pathElements } = parsingResult;
3119
+ return {
3120
+ owner: organization,
3121
+ repo: repoName,
3122
+ projectName,
3123
+ projectPath,
3124
+ pathElements
3125
+ };
2383
3126
  }
2384
- async function getGitlabBlameRanges({ ref, gitlabUrl, path: path8 }, options) {
2385
- const { projectPath } = parseOwnerAndRepo2(gitlabUrl);
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
- });
3127
+ async function getAdoBlameRanges() {
3128
+ return [];
2403
3129
  }
2404
- var GitlabAuthResultZ = z7.object({
2405
- access_token: z7.string(),
2406
- token_type: z7.string(),
2407
- refresh_token: z7.string()
3130
+ var AdoAuthResultZ = z8.object({
3131
+ access_token: z8.string().min(1),
3132
+ token_type: z8.string().min(1),
3133
+ refresh_token: z8.string().min(1)
2408
3134
  });
2409
3135
 
3136
+ // src/features/analysis/scm/constants.ts
3137
+ var MOBB_ICON_IMG = "https://svgshare.com/i/12DK.svg";
3138
+ var COMMIT_FIX_SVG = `https://app.mobb.ai/gh-action/commit-button.svg`;
3139
+
2410
3140
  // src/features/analysis/scm/utils/get_issue_type.ts
2411
3141
  var getIssueType = (issueType) => {
2412
3142
  switch (issueType) {
@@ -2569,7 +3299,7 @@ async function getFixesFromDiff(params) {
2569
3299
  });
2570
3300
  const lineAddedRanges = calculateRanges(fileNumbers);
2571
3301
  const fileFilter = {
2572
- path: z8.string().parse(file.to),
3302
+ path: z9.string().parse(file.to),
2573
3303
  ranges: lineAddedRanges.map(([startLine, endLine]) => ({
2574
3304
  endLine,
2575
3305
  startLine
@@ -2629,7 +3359,7 @@ async function handleFinishedAnalysis({
2629
3359
  return Promise.all(
2630
3360
  vulnerabilityReportIssueCodeNodes.map(
2631
3361
  async (vulnerabilityReportIssueCodeNodes2) => {
2632
- const { path: path8, startLine, vulnerabilityReportIssue } = vulnerabilityReportIssueCodeNodes2;
3362
+ const { path: path9, startLine, vulnerabilityReportIssue } = vulnerabilityReportIssueCodeNodes2;
2633
3363
  const { fixId } = vulnerabilityReportIssue;
2634
3364
  const { fix_by_pk } = await gqlClient.getFix(fixId);
2635
3365
  const {
@@ -2640,7 +3370,7 @@ async function handleFinishedAnalysis({
2640
3370
  body: "empty",
2641
3371
  pull_number: pullRequest,
2642
3372
  commit_id: commitSha,
2643
- path: path8,
3373
+ path: path9,
2644
3374
  line: startLine
2645
3375
  },
2646
3376
  githubActionOctokit
@@ -2998,8 +3728,8 @@ async function forkSnyk(args, { display }) {
2998
3728
  }
2999
3729
  async function getSnykReport(reportPath, repoRoot, { skipPrompts = false }) {
3000
3730
  debug7("get snyk report start %s %s", reportPath, repoRoot);
3001
- const config3 = await forkSnyk(["config"], { display: false });
3002
- const { message: configMessage } = config3;
3731
+ const config4 = await forkSnyk(["config"], { display: false });
3732
+ const { message: configMessage } = config4;
3003
3733
  if (!configMessage.includes("api: ")) {
3004
3734
  const snykLoginSpinner = createSpinner3().start();
3005
3735
  if (!skipPrompts) {
@@ -3011,7 +3741,7 @@ async function getSnykReport(reportPath, repoRoot, { skipPrompts = false }) {
3011
3741
  snykLoginSpinner.update({
3012
3742
  text: "\u{1F513} Waiting for Snyk login to complete"
3013
3743
  });
3014
- debug7("no token in the config %s", config3);
3744
+ debug7("no token in the config %s", config4);
3015
3745
  await forkSnyk(["auth"], { display: true });
3016
3746
  snykLoginSpinner.success({ text: "\u{1F513} Login to Snyk Successful" });
3017
3747
  }
@@ -3083,8 +3813,6 @@ async function uploadFile({
3083
3813
  // src/features/analysis/index.ts
3084
3814
  var { CliError: CliError2, Spinner: Spinner2, keypress: keypress2, getDirName: getDirName2 } = utils_exports;
3085
3815
  var webLoginUrl = `${WEB_APP_URL}/cli-login`;
3086
- var githubAuthUrl = `${WEB_APP_URL}/github-auth`;
3087
- var gitlabAuthUrl = `${WEB_APP_URL}/gitlab-auth`;
3088
3816
  async function downloadRepo({
3089
3817
  repoUrl,
3090
3818
  authHeaders,
@@ -3096,6 +3824,7 @@ async function downloadRepo({
3096
3824
  const repoSpinner = createSpinner4("\u{1F4BE} Downloading Repo").start();
3097
3825
  debug9("download repo %s %s %s", repoUrl, dirname);
3098
3826
  const zipFilePath = path6.join(dirname, "repo.zip");
3827
+ debug9("download URL: %s auth headers: %o", downloadUrl, authHeaders);
3099
3828
  const response = await fetch3(downloadUrl, {
3100
3829
  method: "GET",
3101
3830
  headers: {
@@ -3158,6 +3887,38 @@ async function runAnalysis(params, options) {
3158
3887
  tmpObj.removeCallback();
3159
3888
  }
3160
3889
  }
3890
+ function _getTokenAndUrlForScmType({
3891
+ scmLibType,
3892
+ githubToken,
3893
+ gitlabToken,
3894
+ adoToken
3895
+ }) {
3896
+ const githubAuthUrl = `${WEB_APP_URL}/github-auth`;
3897
+ const gitlabAuthUrl = `${WEB_APP_URL}/gitlab-auth`;
3898
+ const adoAuthUrl = `${WEB_APP_URL}/ado-auth`;
3899
+ switch (scmLibType) {
3900
+ case "GITHUB" /* GITHUB */:
3901
+ return {
3902
+ token: githubToken,
3903
+ authUrl: githubAuthUrl
3904
+ };
3905
+ case "GITLAB" /* GITLAB */:
3906
+ return {
3907
+ token: gitlabToken,
3908
+ authUrl: gitlabAuthUrl
3909
+ };
3910
+ case "ADO" /* ADO */:
3911
+ return {
3912
+ token: adoToken,
3913
+ authUrl: adoAuthUrl
3914
+ };
3915
+ default:
3916
+ return {
3917
+ token: void 0,
3918
+ authUrl: void 0
3919
+ };
3920
+ }
3921
+ }
3161
3922
  async function _scan(params, { skipPrompts = false } = {}) {
3162
3923
  const {
3163
3924
  dirname,
@@ -3196,26 +3957,35 @@ async function _scan(params, { skipPrompts = false } = {}) {
3196
3957
  throw new Error("repo is required in case srcPath is not provided");
3197
3958
  }
3198
3959
  const userInfo = await gqlClient.getUserInfo();
3199
- const { githubToken, gitlabToken } = userInfo;
3960
+ const { githubToken, gitlabToken, adoToken, adoOrg } = userInfo;
3200
3961
  const isRepoAvailable = await scmCanReachRepo({
3201
3962
  repoUrl: repo,
3202
3963
  githubToken,
3203
- gitlabToken
3964
+ gitlabToken,
3965
+ adoToken,
3966
+ scmOrg: adoOrg
3204
3967
  });
3205
3968
  const scmLibType = getScmLibTypeFromUrl(repo);
3206
- const scmAuthUrl = scmLibType === "GITHUB" /* GITHUB */ ? githubAuthUrl : scmLibType === "GITLAB" /* GITLAB */ ? gitlabAuthUrl : void 0;
3207
- let token = scmLibType === "GITHUB" /* GITHUB */ ? githubToken : scmLibType === "GITLAB" /* GITLAB */ ? gitlabToken : void 0;
3969
+ const { authUrl: scmAuthUrl, token } = _getTokenAndUrlForScmType({
3970
+ scmLibType,
3971
+ githubToken,
3972
+ gitlabToken,
3973
+ adoToken
3974
+ });
3975
+ let myToken = token;
3208
3976
  if (!isRepoAvailable) {
3209
3977
  if (ci || !scmLibType || !scmAuthUrl) {
3210
3978
  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
3979
  throw new Error(errorMessage);
3212
3980
  }
3213
3981
  if (scmLibType && scmAuthUrl) {
3214
- token = await handleScmIntegration(token, scmLibType, scmAuthUrl) || "";
3982
+ myToken = await handleScmIntegration(token, scmLibType, scmAuthUrl) || "";
3215
3983
  const isRepoAvailable2 = await scmCanReachRepo({
3216
3984
  repoUrl: repo,
3217
- githubToken: token,
3218
- gitlabToken: token
3985
+ githubToken: myToken,
3986
+ gitlabToken: myToken,
3987
+ adoToken: myToken,
3988
+ scmOrg: adoOrg
3219
3989
  });
3220
3990
  if (!isRepoAvailable2) {
3221
3991
  throw new Error(
@@ -3227,7 +3997,8 @@ async function _scan(params, { skipPrompts = false } = {}) {
3227
3997
  const scm = await SCMLib.init({
3228
3998
  url: repo,
3229
3999
  accessToken: token,
3230
- scmType: scmLibType
4000
+ scmType: scmLibType,
4001
+ scmOrg: adoOrg
3231
4002
  });
3232
4003
  const reference = ref ?? await scm.getRepoDefaultBranch();
3233
4004
  const { sha } = await scm.getReferenceData(reference);
@@ -3270,7 +4041,7 @@ async function _scan(params, { skipPrompts = false } = {}) {
3270
4041
  gqlClient,
3271
4042
  scm,
3272
4043
  githubActionOctokit: new Octokit3({ auth: githubActionToken }),
3273
- scanner: z9.nativeEnum(SCANNERS).parse(scanner)
4044
+ scanner: z10.nativeEnum(SCANNERS).parse(scanner)
3274
4045
  })
3275
4046
  );
3276
4047
  }
@@ -3282,7 +4053,7 @@ async function _scan(params, { skipPrompts = false } = {}) {
3282
4053
  try {
3283
4054
  const sumbitRes = await gqlClient.submitVulnerabilityReport({
3284
4055
  fixReportId: reportUploadInfo.fixReportId,
3285
- repoUrl: z9.string().parse(repo),
4056
+ repoUrl: z10.string().parse(repo),
3286
4057
  reference,
3287
4058
  projectId,
3288
4059
  vulnerabilityReportFileName: "report.json",
@@ -3400,7 +4171,7 @@ async function _scan(params, { skipPrompts = false } = {}) {
3400
4171
  }
3401
4172
  }
3402
4173
  async function handleScmIntegration(oldToken, scmLibType2, scmAuthUrl2) {
3403
- const scmName = scmLibType2 === "GITHUB" /* GITHUB */ ? "Github" : scmLibType2 === "GITLAB" /* GITLAB */ ? "Gitlab" : "";
4174
+ const scmName = scmLibType2 === "GITHUB" /* GITHUB */ ? "Github" : scmLibType2 === "GITLAB" /* GITLAB */ ? "Gitlab" : scmLibType2 === "ADO" /* ADO */ ? "Azure DevOps" : "";
3404
4175
  const addScmIntegration = skipPrompts ? true : await scmIntegrationPrompt(scmName);
3405
4176
  const scmSpinner = createSpinner4(
3406
4177
  `\u{1F517} Waiting for ${scmName} integration...`
@@ -3513,6 +4284,8 @@ async function _scan(params, { skipPrompts = false } = {}) {
3513
4284
 
3514
4285
  // src/commands/index.ts
3515
4286
  import chalkAnimation from "chalk-animation";
4287
+ import Configstore2 from "configstore";
4288
+ var { getDirName: getDirName3 } = utils_exports;
3516
4289
  async function review(params, { skipPrompts = true } = {}) {
3517
4290
  const {
3518
4291
  repo,
@@ -3569,6 +4342,23 @@ async function analyze({
3569
4342
  { skipPrompts }
3570
4343
  );
3571
4344
  }
4345
+ var packageJson2 = JSON.parse(
4346
+ fs4.readFileSync(path7.join(getDirName3(), "../package.json"), "utf8")
4347
+ );
4348
+ var config3 = new Configstore2(packageJson2.name, { apiToken: "" });
4349
+ async function addScmToken(addScmTokenOptions) {
4350
+ const { apiKey, token, organization, scm, username, refreshToken } = addScmTokenOptions;
4351
+ const gqlClient = new GQLClient({
4352
+ apiKey: apiKey || config3.get("apiToken")
4353
+ });
4354
+ await gqlClient.updateScmToken({
4355
+ type: scm,
4356
+ token,
4357
+ org: organization,
4358
+ username,
4359
+ refreshToken
4360
+ });
4361
+ }
3572
4362
  async function scan(scanOptions, { skipPrompts = false } = {}) {
3573
4363
  const { scanner, ci } = scanOptions;
3574
4364
  !ci && await showWelcomeMessage(skipPrompts);
@@ -3603,7 +4393,7 @@ var repoOption = {
3603
4393
  alias: "r",
3604
4394
  demandOption: true,
3605
4395
  type: "string",
3606
- describe: chalk5.bold("Github / GitLab repository URL")
4396
+ describe: chalk5.bold("Github / GitLab / Azure DevOps repository URL")
3607
4397
  };
3608
4398
  var projectNameOption = {
3609
4399
  type: "string",
@@ -3645,11 +4435,31 @@ var commitHashOption = {
3645
4435
  describe: chalk5.bold("Hash of the commit"),
3646
4436
  type: "string"
3647
4437
  };
4438
+ var scmTypeOption = {
4439
+ describe: chalk5.bold("SCM type (GitHub, GitLab, Ado)"),
4440
+ type: "string"
4441
+ };
4442
+ var scmOrgOption = {
4443
+ describe: chalk5.bold("Organization name in SCM (used in Azure DevOps)"),
4444
+ type: "string"
4445
+ };
4446
+ var scmUsernameOption = {
4447
+ describe: chalk5.bold("Username in SCM (used in GitHub)"),
4448
+ type: "string"
4449
+ };
4450
+ var scmRefreshTokenOption = {
4451
+ describe: chalk5.bold("SCM refresh token (used in GitLab)"),
4452
+ type: "string"
4453
+ };
4454
+ var scmTokenOption = {
4455
+ describe: chalk5.bold("SCM API token"),
4456
+ type: "string"
4457
+ };
3648
4458
 
3649
4459
  // src/args/validation.ts
3650
4460
  import chalk6 from "chalk";
3651
- import path7 from "path";
3652
- import { z as z10 } from "zod";
4461
+ import path8 from "path";
4462
+ import { z as z11 } from "zod";
3653
4463
  function throwRepoUrlErrorMessage({
3654
4464
  error,
3655
4465
  repoUrl,
@@ -3666,10 +4476,10 @@ Example:
3666
4476
  )}`;
3667
4477
  throw new CliError(formattedErrorMessage);
3668
4478
  }
3669
- var UrlZ = z10.string({
3670
- invalid_type_error: "is not a valid GitHub / GitLab URL"
4479
+ var UrlZ = z11.string({
4480
+ invalid_type_error: "is not a valid GitHub / GitLab / ADO URL"
3671
4481
  }).refine((data) => !!parseScmURL(data), {
3672
- message: "is not a valid GitHub / GitLab URL"
4482
+ message: "is not a valid GitHub / GitLab / ADO URL"
3673
4483
  });
3674
4484
  function validateRepoUrl(args) {
3675
4485
  const repoSafeParseResult = UrlZ.safeParse(args.repo);
@@ -3688,7 +4498,7 @@ function validateRepoUrl(args) {
3688
4498
  }
3689
4499
  var supportExtensions = [".json", ".xml", ".fpr", ".sarif"];
3690
4500
  function validateReportFileFormat(reportFile) {
3691
- if (!supportExtensions.includes(path7.extname(reportFile))) {
4501
+ if (!supportExtensions.includes(path8.extname(reportFile))) {
3692
4502
  throw new CliError(
3693
4503
  `
3694
4504
  ${chalk6.bold(
@@ -3726,7 +4536,7 @@ function analyzeBuilder(yargs2) {
3726
4536
  ).help();
3727
4537
  }
3728
4538
  function validateAnalyzeOptions(argv) {
3729
- if (!fs4.existsSync(argv.f)) {
4539
+ if (!fs5.existsSync(argv.f)) {
3730
4540
  throw new CliError(`
3731
4541
  Can't access ${chalk7.bold(argv.f)}`);
3732
4542
  }
@@ -3747,7 +4557,7 @@ async function analyzeHandler(args) {
3747
4557
  }
3748
4558
 
3749
4559
  // src/args/commands/review.ts
3750
- import fs5 from "node:fs";
4560
+ import fs6 from "node:fs";
3751
4561
  import chalk8 from "chalk";
3752
4562
  function reviewBuilder(yargs2) {
3753
4563
  return yargs2.option("f", {
@@ -3777,7 +4587,7 @@ function reviewBuilder(yargs2) {
3777
4587
  ).help();
3778
4588
  }
3779
4589
  function validateReviewOptions(argv) {
3780
- if (!fs5.existsSync(argv.f)) {
4590
+ if (!fs6.existsSync(argv.f)) {
3781
4591
  throw new CliError(`
3782
4592
  Can't access ${chalk8.bold(argv.f)}`);
3783
4593
  }
@@ -3813,6 +4623,37 @@ async function scanHandler(args) {
3813
4623
  await scan(args, { skipPrompts: args.yes });
3814
4624
  }
3815
4625
 
4626
+ // src/args/commands/token.ts
4627
+ function addScmTokenBuilder(args) {
4628
+ return args.option("scm", scmTypeOption).option("token", scmTokenOption).option("organization", scmOrgOption).option("username", scmUsernameOption).option("refresh-token", scmRefreshTokenOption).option("api-key", apiKeyOption).example(
4629
+ "$0 add-scm-token --scm ado --token abcdef0123456 --organization myOrg",
4630
+ "Add your SCM (Github, Gitlab, Azure DevOps) token to Mobb to enable automated fixes."
4631
+ ).help().demandOption(["scm", "token"]);
4632
+ }
4633
+ function validateAddScmTokenOptions(argv) {
4634
+ if (!argv.scm) {
4635
+ throw new CliError(errorMessages.missingScmType);
4636
+ }
4637
+ if (!Object.values(ScmTypes).includes(argv.scm)) {
4638
+ throw new CliError(errorMessages.invalidScmType);
4639
+ }
4640
+ if (!argv.token) {
4641
+ throw new CliError(errorMessages.missingToken);
4642
+ }
4643
+ if (argv.scm === ScmTypes.AzureDevOps && !argv.organization) {
4644
+ throw new CliError(
4645
+ "\nError: --organization flag is required for Azure DevOps"
4646
+ );
4647
+ }
4648
+ if (argv.scm === ScmTypes.Github && !argv.username) {
4649
+ throw new CliError("\nError: --username flag is required for GitHub");
4650
+ }
4651
+ }
4652
+ async function addScmTokenHandler(args) {
4653
+ validateAddScmTokenOptions(args);
4654
+ await addScmToken(args);
4655
+ }
4656
+
3816
4657
  // src/args/yargs.ts
3817
4658
  var parseArgs = async (args) => {
3818
4659
  const yargsInstance = yargs(args);
@@ -3830,6 +4671,13 @@ var parseArgs = async (args) => {
3830
4671
  )} ${chalk9.dim("[options]")}
3831
4672
  `
3832
4673
  ).version(false).command(
4674
+ mobbCliCommand.addScmToken,
4675
+ chalk9.bold(
4676
+ "Add your SCM (Github, Gitlab, Azure DevOps) token to Mobb to enable automated fixes."
4677
+ ),
4678
+ addScmTokenBuilder,
4679
+ addScmTokenHandler
4680
+ ).command(
3833
4681
  mobbCliCommand.scan,
3834
4682
  chalk9.bold(
3835
4683
  "Scan your code for vulnerabilities, get automated fixes right away."