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.
Files changed (4) hide show
  1. package/.env +3 -2
  2. package/README.md +8 -2
  3. package/dist/index.mjs +1100 -275
  4. 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 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";
@@ -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 } = parseOwnerAndRepo(url);
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 } = parseOwnerAndRepo(repoUrl);
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 } = parseOwnerAndRepo(repoUrl);
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 } = parseOwnerAndRepo(repoUrl);
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 } = parseOwnerAndRepo(repoUrl);
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 } = parseOwnerAndRepo(options.repoUrl);
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 } = parseOwnerAndRepo(options.repoUrl);
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 } = parseOwnerAndRepo(repoUrl);
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 } = parseOwnerAndRepo(gitHubUrl);
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 parseOwnerAndRepo(gitHubUrl) {
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: path8 }, options) {
1295
- const { owner, repo } = parseOwnerAndRepo(gitHubUrl);
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: path8,
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 } = parseOwnerAndRepo(sourceRepoUrl);
1327
- const { owner, repo } = parseOwnerAndRepo(userRepoUrl);
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 z5 } from "zod";
1768
+ import { z as z6 } from "zod";
1455
1769
 
1456
1770
  // 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()
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: z4.string(),
1467
- repoUrl: z4.string()
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
- 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()
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 = z4.object({
1483
- type: z4.literal(submitToScmMessageType.submitFixesForDifferentBranch),
1484
- submitBranch: z4.string(),
1485
- baseBranch: z4.string()
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 = z4.union([
1801
+ var SubmitFixesMessageZ = z5.union([
1488
1802
  CommitToSameBranchParamsZ,
1489
1803
  SubmitFixesToDifferentBranchParamsZ
1490
1804
  ]);
1491
- var FixResponseArrayZ = z4.array(
1492
- z4.object({
1493
- fixId: z4.string().uuid()
1805
+ var FixResponseArrayZ = z5.array(
1806
+ z5.object({
1807
+ fixId: z5.string().uuid()
1494
1808
  })
1495
1809
  );
1496
- var SubmitFixesBaseResponseMessageZ = z4.object({
1497
- submitFixRequestId: z4.string().uuid(),
1498
- submitBranches: z4.array(
1499
- z4.object({
1500
- branchName: z4.string(),
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: z4.object({
1505
- type: z4.enum([
1818
+ error: z5.object({
1819
+ type: z5.enum([
1506
1820
  "InitialRepoAccessError",
1507
1821
  "PushBranchError",
1508
1822
  "UnknownError"
1509
1823
  ]),
1510
- info: z4.object({
1511
- message: z4.string(),
1512
- pushBranchName: z4.string().optional()
1824
+ info: z5.object({
1825
+ message: z5.string(),
1826
+ pushBranchName: z5.string().optional()
1513
1827
  })
1514
1828
  }).optional()
1515
1829
  });
1516
- var SubmitFixesToSameBranchResponseMessageZ = z4.object({
1517
- type: z4.literal(submitToScmMessageType.commitToSameBranch),
1518
- githubCommentId: z4.number().nullish()
1830
+ var SubmitFixesToSameBranchResponseMessageZ = z5.object({
1831
+ type: z5.literal(submitToScmMessageType.commitToSameBranch),
1832
+ githubCommentId: z5.number().nullish()
1519
1833
  }).merge(SubmitFixesBaseResponseMessageZ);
1520
- var SubmitFixesToDifferentBranchResponseMessageZ = z4.object({
1521
- type: z4.literal(submitToScmMessageType.submitFixesForDifferentBranch),
1522
- githubCommentId: z4.number().optional()
1834
+ var SubmitFixesToDifferentBranchResponseMessageZ = z5.object({
1835
+ type: z5.literal(submitToScmMessageType.submitFixesForDifferentBranch),
1836
+ githubCommentId: z5.number().optional()
1523
1837
  }).merge(SubmitFixesBaseResponseMessageZ);
1524
- var SubmitFixeResponseMessageZ = z4.discriminatedUnion("type", [
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 = z5.array(z5.object({ fixId: z5.string(), diff: z5.string() })).nonempty();
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 username = await this._getUsernameForAuthUrl();
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, path8) {
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: path8, gitlabUrl: this.url },
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 } = parseOwnerAndRepo(this.url);
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 } = parseOwnerAndRepo(this.url);
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 } = parseOwnerAndRepo(this.url);
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 } = parseOwnerAndRepo(this.url);
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 } = parseOwnerAndRepo(this.url);
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 } = parseOwnerAndRepo(this.url);
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 z6.string().parse(prRes.data);
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, path8) {
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: path8, gitHubUrl: this.url },
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 } = parseOwnerAndRepo(this.url);
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("getBranchList() not implemented");
2130
- throw new Error("getBranchList() not implemented");
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/gitlab.ts
2171
- function removeTrailingSlash2(str) {
2665
+ // src/features/analysis/scm/ado.ts
2666
+ function removeTrailingSlash3(str) {
2172
2667
  return str.trim().replace(/\/+$/, "");
2173
2668
  }
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 });
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 new Gitlab({ oauthToken: token });
2728
+ return "PAT" /* PAT */;
2184
2729
  }
2185
- async function gitlabValidateParams({
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
- const api = getGitBeaker({ gitlabAuthToken: accessToken });
2191
- if (accessToken) {
2192
- await api.Users.showCurrentUser();
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 { projectPath } = parseOwnerAndRepo2(url);
2196
- await api.Projects.show(projectPath);
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 gitlab access token`);
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 gitlab repo Url ${url}`);
2779
+ throw new InvalidRepoUrlError(`invalid ADO repo URL ${url}`);
2207
2780
  }
2208
2781
  throw e;
2209
2782
  }
2210
2783
  }
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,
2784
+ async function getAdoIsUserCollaborator({
2218
2785
  accessToken,
2786
+ tokenOrg,
2219
2787
  repoUrl
2220
2788
  }) {
2221
2789
  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
2790
+ const { owner, repo, projectName } = parseAdoOwnerAndRepo(repoUrl);
2791
+ const api2 = await getAdoApiClient({
2792
+ accessToken,
2793
+ tokenOrg,
2794
+ orgName: owner
2227
2795
  });
2228
- return !!members.find((member) => member.username === username);
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
- async function getGitlabMergeRequestStatus({
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
- mrNumber
2816
+ prNumber
2237
2817
  }) {
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}`);
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 getGitlabIsRemoteBranch({
2831
+ async function getAdoIsRemoteBranch({
2251
2832
  accessToken,
2833
+ tokenOrg,
2252
2834
  repoUrl,
2253
2835
  branch
2254
2836
  }) {
2255
- const { projectPath } = parseOwnerAndRepo2(repoUrl);
2256
- const api = getGitBeaker({ gitlabAuthToken: accessToken });
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 res = await api.Branches.show(projectPath, branch);
2259
- return res.name === branch;
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 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
- };
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
- async function getGitlabBranchList({
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 { projectPath } = parseOwnerAndRepo2(repoUrl);
2298
- const api = getGitBeaker({ gitlabAuthToken: accessToken });
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 api.Branches.all(projectPath, {
2301
- perPage: 100,
2302
- pagination: "keyset",
2303
- orderBy: "updated_at",
2304
- sort: "dec"
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.map((branch) => branch.name);
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 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,
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.iid;
2972
+ return res.pullRequestId;
2324
2973
  }
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");
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
- return project.default_branch;
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 getGitlabReferenceData({ ref, gitlabUrl }, options) {
2335
- const { projectPath } = parseOwnerAndRepo2(gitlabUrl);
2336
- const api = getGitBeaker({ gitlabAuthToken: options?.gitlabAuthToken });
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 api.Branches.show(projectPath, ref);
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.id,
3019
+ sha: res.commit.commitId,
2342
3020
  type: "BRANCH" /* BRANCH */,
2343
- date: res.commit.committed_date ? new Date(res.commit.committed_date) : void 0
3021
+ date: res.commit.committer?.date || /* @__PURE__ */ new Date()
2344
3022
  };
2345
3023
  })(),
2346
3024
  (async () => {
2347
- const res = await api.Commits.show(projectPath, ref);
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: res.id,
3039
+ sha: commit.commitId,
2350
3040
  type: "COMMIT" /* COMMIT */,
2351
- date: res.committed_date ? new Date(res.committed_date) : void 0
3041
+ date: commit.committer?.date || /* @__PURE__ */ new Date()
2352
3042
  };
2353
3043
  })(),
2354
3044
  (async () => {
2355
- const res = await api.Tags.show(projectPath, ref);
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: res.commit.id,
3071
+ sha: objectId,
2358
3072
  type: "TAG" /* TAG */,
2359
- date: res.commit.committed_date ? new Date(res.commit.committed_date) : void 0
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 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}`);
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 { owner: organization, repo: repoName, projectPath };
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 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
- });
3104
+ async function getAdoBlameRanges() {
3105
+ return [];
2403
3106
  }
2404
- var GitlabAuthResultZ = z7.object({
2405
- access_token: z7.string(),
2406
- token_type: z7.string(),
2407
- refresh_token: z7.string()
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: z8.string().parse(file.to),
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: path8, startLine, vulnerabilityReportIssue } = vulnerabilityReportIssueCodeNodes2;
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: path8,
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 config3 = await forkSnyk(["config"], { display: false });
3002
- const { message: configMessage } = config3;
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", config3);
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 scmAuthUrl = scmLibType === "GITHUB" /* GITHUB */ ? githubAuthUrl : scmLibType === "GITLAB" /* GITLAB */ ? gitlabAuthUrl : void 0;
3207
- let token = scmLibType === "GITHUB" /* GITHUB */ ? githubToken : scmLibType === "GITLAB" /* GITLAB */ ? gitlabToken : void 0;
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
- token = await handleScmIntegration(token, scmLibType, scmAuthUrl) || "";
3959
+ myToken = await handleScmIntegration(token, scmLibType, scmAuthUrl) || "";
3215
3960
  const isRepoAvailable2 = await scmCanReachRepo({
3216
3961
  repoUrl: repo,
3217
- githubToken: token,
3218
- gitlabToken: token
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: z9.nativeEnum(SCANNERS).parse(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: z9.string().parse(repo),
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 path7 from "path";
3652
- import { z as z10 } from "zod";
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 = z10.string({
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(path7.extname(reportFile))) {
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 (!fs4.existsSync(argv.f)) {
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 fs5 from "node:fs";
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 (!fs5.existsSync(argv.f)) {
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."