mobbdev 0.0.61 → 0.0.63

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 (2) hide show
  1. package/dist/index.mjs +1676 -945
  2. package/package.json +7 -1
package/dist/index.mjs CHANGED
@@ -12,8 +12,15 @@ var __publicField = (obj, key, value) => {
12
12
  // src/index.ts
13
13
  import { hideBin } from "yargs/helpers";
14
14
 
15
+ // src/types.ts
16
+ var mobbCliCommand = {
17
+ scan: "scan",
18
+ analyze: "analyze",
19
+ review: "review"
20
+ };
21
+
15
22
  // src/args/yargs.ts
16
- import chalk8 from "chalk";
23
+ import chalk9 from "chalk";
17
24
  import yargs from "yargs/yargs";
18
25
 
19
26
  // src/args/commands/analyze.ts
@@ -35,6 +42,7 @@ var SCANNERS = {
35
42
  Fortify: "fortify",
36
43
  Snyk: "snyk"
37
44
  };
45
+ var SupportedScannersZ = z.enum([SCANNERS.Checkmarx, SCANNERS.Snyk]);
38
46
  var envVariablesSchema = z.object({
39
47
  WEB_APP_URL: z.string(),
40
48
  API_URL: z.string()
@@ -146,14 +154,16 @@ var CliError = class extends Error {
146
154
  };
147
155
 
148
156
  // src/features/analysis/index.ts
157
+ import { Octokit as Octokit3 } from "@octokit/core";
149
158
  import chalk4 from "chalk";
150
159
  import Configstore from "configstore";
151
- import Debug9 from "debug";
160
+ import Debug10 from "debug";
152
161
  import extract from "extract-zip";
153
162
  import fetch3 from "node-fetch";
154
163
  import open2 from "open";
155
164
  import semver from "semver";
156
- import tmp from "tmp";
165
+ import tmp2 from "tmp";
166
+ import { z as z8 } from "zod";
157
167
 
158
168
  // src/features/analysis/git.ts
159
169
  import Debug2 from "debug";
@@ -265,16 +275,22 @@ var SUBMIT_VULNERABILITY_REPORT = gql`
265
275
  $projectId: String!
266
276
  $sha: String
267
277
  $vulnerabilityReportFileName: String
278
+ $pullRequest: Int
268
279
  ) {
269
280
  submitVulnerabilityReport(
270
281
  fixReportId: $fixReportId
271
282
  repoUrl: $repoUrl
272
283
  reference: $reference
273
284
  sha: $sha
285
+ pullRequest: $pullRequest
274
286
  projectId: $projectId
275
287
  vulnerabilityReportFileName: $vulnerabilityReportFileName
276
288
  ) {
277
289
  __typename
290
+ ... on VulnerabilityReport {
291
+ vulnerabilityReportId
292
+ fixReportId
293
+ }
278
294
  }
279
295
  }
280
296
  `;
@@ -352,6 +368,136 @@ var GET_VULNERABILITY_REPORT_PATHS = gql2`
352
368
  }
353
369
  }
354
370
  `;
371
+ var SUBSCRIBE_TO_ANALYSIS = gql2`
372
+ subscription getAnalysis($analysisId: uuid!) {
373
+ analysis: fixReport_by_pk(id: $analysisId) {
374
+ id
375
+ state
376
+ }
377
+ }
378
+ `;
379
+ var GET_ANALYSIS = gql2`
380
+ query getAnalsyis($analysisId: uuid!) {
381
+ analysis: fixReport_by_pk(id: $analysisId) {
382
+ id
383
+ state
384
+ repo {
385
+ commitSha
386
+ pullRequest
387
+ }
388
+ fixes {
389
+ id
390
+ issueType
391
+ vulnerabilityReportIssues {
392
+ issueLanguage
393
+ state
394
+ issueType
395
+ vendorIssueId
396
+ }
397
+ }
398
+ vulnerabilityReport {
399
+ projectId
400
+ project {
401
+ organizationId
402
+ }
403
+ file {
404
+ signedFile {
405
+ url
406
+ }
407
+ }
408
+ }
409
+ }
410
+ }
411
+ `;
412
+ var GET_FIX = gql2`
413
+ query getFix($fixId: uuid!) {
414
+ fix_by_pk(id: $fixId) {
415
+ patchAndQuestions {
416
+ patch
417
+ }
418
+ }
419
+ }
420
+ `;
421
+
422
+ // src/features/analysis/graphql/subscirbe.ts
423
+ import { createClient } from "graphql-ws";
424
+ import WebSocket from "ws";
425
+ var SUBSCRIPTION_TIMEOUT_MS = 5 * 60 * 1e3;
426
+ function createWSClient(options) {
427
+ return createClient({
428
+ url: options.url,
429
+ webSocketImpl: options.websocket || WebSocket,
430
+ connectionParams: () => {
431
+ return {
432
+ headers: {
433
+ [API_KEY_HEADER_NAME]: options.apiKey
434
+ }
435
+ };
436
+ }
437
+ });
438
+ }
439
+ function subscribe(query, variables, callback, wsClientOptions) {
440
+ return new Promise((resolve, reject) => {
441
+ let timer = null;
442
+ const { timeoutInMs = SUBSCRIPTION_TIMEOUT_MS } = wsClientOptions;
443
+ const client = createWSClient({
444
+ ...wsClientOptions,
445
+ websocket: WebSocket,
446
+ url: API_URL.replace("http", "ws")
447
+ });
448
+ const unsubscribe = client.subscribe(
449
+ { query, variables },
450
+ {
451
+ next: (data) => {
452
+ function callbackResolve(data2) {
453
+ unsubscribe();
454
+ if (timer) {
455
+ clearTimeout(timer);
456
+ }
457
+ resolve(data2);
458
+ }
459
+ function callbackReject(data2) {
460
+ unsubscribe();
461
+ if (timer) {
462
+ clearTimeout(timer);
463
+ }
464
+ reject(data2);
465
+ }
466
+ if (!data.data) {
467
+ reject(
468
+ new Error(
469
+ `Broken data object from graphQL subscribe: ${JSON.stringify(
470
+ data
471
+ )} for query: ${query}`
472
+ )
473
+ );
474
+ } else {
475
+ callback(callbackResolve, callbackReject, data.data);
476
+ }
477
+ },
478
+ error: (error) => {
479
+ if (timer) {
480
+ clearTimeout(timer);
481
+ }
482
+ reject(error);
483
+ },
484
+ complete: () => {
485
+ return;
486
+ }
487
+ }
488
+ );
489
+ if (typeof timeoutInMs === "number") {
490
+ timer = setTimeout(() => {
491
+ unsubscribe();
492
+ reject(
493
+ new Error(
494
+ `Timeout expired for graphQL subscribe query: ${query} with timeout: ${timeoutInMs}`
495
+ )
496
+ );
497
+ }, timeoutInMs);
498
+ }
499
+ });
500
+ }
355
501
 
356
502
  // src/features/analysis/graphql/types.ts
357
503
  import { z as z2 } from "zod";
@@ -421,9 +567,25 @@ var DigestVulnerabilityReportZ = z2.object({
421
567
  vulnerabilityReportId: z2.string()
422
568
  })
423
569
  });
570
+ var AnalysisStateZ = z2.enum([
571
+ "Created",
572
+ "Deleted",
573
+ "Digested",
574
+ "Expired",
575
+ "Failed",
576
+ "Finished",
577
+ "Initialized",
578
+ "Requested"
579
+ ]);
424
580
  var GetFixReportZ = z2.object({
425
581
  fixReport_by_pk: z2.object({
426
- state: z2.string()
582
+ state: AnalysisStateZ
583
+ })
584
+ });
585
+ var GetFixReportSubscriptionZ = z2.object({
586
+ analysis: z2.object({
587
+ id: z2.string(),
588
+ state: AnalysisStateZ
427
589
  })
428
590
  });
429
591
  var GetVulnerabilityReportPathsZ = z2.object({
@@ -433,6 +595,55 @@ var GetVulnerabilityReportPathsZ = z2.object({
433
595
  })
434
596
  )
435
597
  });
598
+ var CreateUpdateFixReportMutationZ = z2.object({
599
+ submitVulnerabilityReport: z2.object({
600
+ __typename: z2.literal("VulnerabilityReport"),
601
+ vulnerabilityReportId: z2.string(),
602
+ fixReportId: z2.string()
603
+ })
604
+ });
605
+ var GetAnalysisQueryZ = z2.object({
606
+ analysis: z2.object({
607
+ id: z2.string(),
608
+ state: z2.string(),
609
+ repo: z2.object({
610
+ commitSha: z2.string(),
611
+ pullRequest: z2.number()
612
+ }),
613
+ fixes: z2.array(
614
+ z2.object({
615
+ id: z2.string(),
616
+ issueType: z2.string(),
617
+ vulnerabilityReportIssues: z2.array(
618
+ z2.object({
619
+ issueLanguage: z2.string(),
620
+ state: z2.string(),
621
+ issueType: z2.string(),
622
+ vendorIssueId: z2.string()
623
+ })
624
+ )
625
+ })
626
+ ),
627
+ vulnerabilityReport: z2.object({
628
+ projectId: z2.string(),
629
+ project: z2.object({
630
+ organizationId: z2.string()
631
+ }),
632
+ file: z2.object({
633
+ signedFile: z2.object({
634
+ url: z2.string()
635
+ })
636
+ })
637
+ })
638
+ })
639
+ });
640
+ var GetFixQueryZ = z2.object({
641
+ fix_by_pk: z2.object({
642
+ patchAndQuestions: z2.object({
643
+ patch: z2.string()
644
+ })
645
+ })
646
+ });
436
647
 
437
648
  // src/features/analysis/graphql/gql.ts
438
649
  var debug3 = Debug3("mobbdev:gql");
@@ -441,7 +652,9 @@ var REPORT_STATE_CHECK_DELAY = 5 * 1e3;
441
652
  var GQLClient = class {
442
653
  constructor(args) {
443
654
  __publicField(this, "_client");
655
+ __publicField(this, "_apiKey");
444
656
  const { apiKey } = args;
657
+ this._apiKey = apiKey;
445
658
  debug3(`init with apiKey ${apiKey}`);
446
659
  this._client = new GraphQLClient(API_URL, {
447
660
  headers: { [API_KEY_HEADER_NAME]: apiKey || "" },
@@ -539,22 +752,26 @@ var GQLClient = class {
539
752
  );
540
753
  return DigestVulnerabilityReportZ.parse(res).digestVulnerabilityReport;
541
754
  }
542
- async submitVulnerabilityReport({
543
- fixReportId,
544
- repoUrl,
545
- reference,
546
- projectId,
547
- sha,
548
- vulnerabilityReportFileName
549
- }) {
550
- await this._client.request(SUBMIT_VULNERABILITY_REPORT, {
755
+ async submitVulnerabilityReport(params) {
756
+ const {
757
+ fixReportId,
758
+ repoUrl,
759
+ reference,
760
+ projectId,
761
+ sha,
762
+ vulnerabilityReportFileName,
763
+ pullRequest
764
+ } = params;
765
+ const res = await this._client.request(SUBMIT_VULNERABILITY_REPORT, {
551
766
  fixReportId,
552
767
  repoUrl,
553
768
  reference,
554
769
  vulnerabilityReportFileName,
555
770
  projectId,
771
+ pullRequest,
556
772
  sha: sha || ""
557
773
  });
774
+ return CreateUpdateFixReportMutationZ.parse(res);
558
775
  }
559
776
  async getFixReportState(fixReportId) {
560
777
  const res = await this._client.request(
@@ -588,836 +805,608 @@ var GQLClient = class {
588
805
  res
589
806
  ).vulnerability_report_path.map((p) => p.path);
590
807
  }
808
+ async subscribeToAnalysis(params, callback) {
809
+ return subscribe(
810
+ SUBSCRIBE_TO_ANALYSIS,
811
+ params,
812
+ async (resolve, reject, data) => {
813
+ if (data.analysis.state === "Failed") {
814
+ reject(data);
815
+ throw new Error(`Analysis failed with id: ${data.analysis.id}`);
816
+ }
817
+ if (data.analysis?.state === "Finished") {
818
+ await callback(data.analysis.id);
819
+ resolve(data);
820
+ }
821
+ },
822
+ {
823
+ apiKey: this._apiKey
824
+ }
825
+ );
826
+ }
827
+ async getAnalysis(analysisId) {
828
+ const res = await this._client.request(GET_ANALYSIS, {
829
+ analysisId
830
+ });
831
+ return GetAnalysisQueryZ.parse(res);
832
+ }
833
+ async getFix(fixId) {
834
+ const res = await this._client.request(GET_FIX, {
835
+ fixId
836
+ });
837
+ return GetFixQueryZ.parse(res);
838
+ }
591
839
  };
592
840
 
593
- // src/features/analysis/pack.ts
594
- import fs from "node:fs";
595
- import path3 from "node:path";
596
- import AdmZip from "adm-zip";
841
+ // src/features/analysis/handle_finished_analysis.ts
597
842
  import Debug4 from "debug";
598
- import { globby } from "globby";
599
- import { isBinary } from "istextorbinary";
600
- var debug4 = Debug4("mobbdev:pack");
601
- var MAX_FILE_SIZE = 1024 * 1024 * 5;
602
- function endsWithAny(str, suffixes) {
603
- return suffixes.some(function(suffix) {
604
- return str.endsWith(suffix);
605
- });
843
+ import { z as z7 } from "zod";
844
+
845
+ // src/features/analysis/scm/gitlab.ts
846
+ import querystring from "node:querystring";
847
+ import { Gitlab } from "@gitbeaker/rest";
848
+ import { z as z5 } from "zod";
849
+
850
+ // src/features/analysis/scm/scm.ts
851
+ import { Octokit as Octokit2 } from "@octokit/core";
852
+
853
+ // src/features/analysis/scm/github/github.ts
854
+ import { RequestError } from "@octokit/request-error";
855
+ import { Octokit } from "octokit";
856
+ import { z as z3 } from "zod";
857
+
858
+ // src/features/analysis/scm/urlParser.ts
859
+ var pathnameParsingMap = {
860
+ "gitlab.com": (pathname) => {
861
+ if (pathname.length < 2)
862
+ return null;
863
+ return {
864
+ organization: pathname[0],
865
+ repoName: pathname[pathname.length - 1]
866
+ };
867
+ },
868
+ "github.com": (pathname) => {
869
+ if (pathname.length !== 2)
870
+ return null;
871
+ return {
872
+ organization: pathname[0],
873
+ repoName: pathname[1]
874
+ };
875
+ }
876
+ };
877
+ var NAME_REGEX = /[a-z0-9\-_.+]+/i;
878
+ var parseScmURL = (scmURL) => {
879
+ try {
880
+ const url = new URL(scmURL);
881
+ const hostname = url.hostname.toLowerCase();
882
+ if (!(hostname in pathnameParsingMap))
883
+ return null;
884
+ const projectPath = url.pathname.substring(1).replace(/.git$/i, "");
885
+ const repo = pathnameParsingMap[hostname](
886
+ projectPath.split("/")
887
+ );
888
+ if (!repo)
889
+ return null;
890
+ const { organization, repoName } = repo;
891
+ if (!organization || !repoName)
892
+ return null;
893
+ if (!organization.match(NAME_REGEX) || !repoName.match(NAME_REGEX))
894
+ return null;
895
+ return {
896
+ hostname: url.hostname,
897
+ organization,
898
+ projectPath,
899
+ repoName
900
+ };
901
+ } catch (e) {
902
+ return null;
903
+ }
904
+ };
905
+
906
+ // src/features/analysis/scm/github/github.ts
907
+ function removeTrailingSlash(str) {
908
+ return str.trim().replace(/\/+$/, "");
606
909
  }
607
- async function pack(srcDirPath, vulnFiles) {
608
- debug4("pack folder %s", srcDirPath);
609
- const filepaths = await globby("**", {
610
- gitignore: true,
611
- onlyFiles: true,
612
- cwd: srcDirPath,
613
- followSymbolicLinks: false
614
- });
615
- debug4("files found %d", filepaths.length);
616
- const zip = new AdmZip();
617
- debug4("compressing files");
618
- for (const filepath of filepaths) {
619
- const absFilepath = path3.join(srcDirPath, filepath.toString());
620
- if (!endsWithAny(
621
- absFilepath.toString().replaceAll(path3.win32.sep, path3.posix.sep),
622
- vulnFiles
623
- )) {
624
- debug4("ignoring %s because it is not a vulnerability file", filepath);
625
- continue;
910
+ var EnvVariablesZod = z3.object({
911
+ GITHUB_API_TOKEN: z3.string().optional()
912
+ });
913
+ var { GITHUB_API_TOKEN } = EnvVariablesZod.parse(process.env);
914
+ var GetBlameDocument = `
915
+ query GetBlame(
916
+ $owner: String!
917
+ $repo: String!
918
+ $ref: String!
919
+ $path: String!
920
+ ) {
921
+ repository(name: $repo, owner: $owner) {
922
+ # branch name
923
+ object(expression: $ref) {
924
+ # cast Target to a Commit
925
+ ... on Commit {
926
+ # full repo-relative path to blame file
927
+ blame(path: $path) {
928
+ ranges {
929
+ commit {
930
+ author {
931
+ user {
932
+ name
933
+ login
934
+ }
935
+ }
936
+ authoredDate
937
+ }
938
+ startingLine
939
+ endingLine
940
+ age
941
+ }
942
+ }
943
+ }
944
+
945
+ }
946
+ }
947
+ }
948
+ `;
949
+ function getOktoKit(options) {
950
+ const token = options?.githubAuthToken ?? GITHUB_API_TOKEN ?? "";
951
+ return new Octokit({ auth: token });
952
+ }
953
+ async function githubValidateParams(url, accessToken) {
954
+ try {
955
+ const oktoKit = getOktoKit({ githubAuthToken: accessToken });
956
+ if (accessToken) {
957
+ await oktoKit.rest.users.getAuthenticated();
626
958
  }
627
- if (fs.lstatSync(absFilepath).size > MAX_FILE_SIZE) {
628
- debug4("ignoring %s because the size is > 5MB", filepath);
629
- continue;
959
+ if (url) {
960
+ const { owner, repo } = parseOwnerAndRepo(url);
961
+ await oktoKit.rest.repos.get({ repo, owner });
630
962
  }
631
- const data = fs.readFileSync(absFilepath);
632
- if (isBinary(null, data)) {
633
- debug4("ignoring %s because is seems to be a binary file", filepath);
634
- continue;
963
+ } catch (e) {
964
+ const error = e;
965
+ const code = error.status || error.statusCode || error.response?.status || error.response?.statusCode || error.response?.code;
966
+ if (code === 401 || code === 403) {
967
+ throw new InvalidAccessTokenError(`invalid github access token`);
635
968
  }
636
- zip.addFile(filepath.toString(), data);
969
+ if (code === 404) {
970
+ throw new InvalidRepoUrlError(`invalid github repo Url ${url}`);
971
+ }
972
+ throw e;
637
973
  }
638
- debug4("get zip file buffer");
639
- return zip.toBuffer();
640
- }
641
-
642
- // src/features/analysis/prompts.ts
643
- import inquirer from "inquirer";
644
- import { createSpinner } from "nanospinner";
645
- var scannerChoices = [
646
- { name: "Snyk", value: SCANNERS.Snyk },
647
- { name: "Checkmarx", value: SCANNERS.Checkmarx },
648
- { name: "Codeql", value: SCANNERS.Codeql },
649
- { name: "Fortify", value: SCANNERS.Fortify }
650
- ];
651
- async function choseScanner() {
652
- const { scanner } = await inquirer.prompt({
653
- name: "scanner",
654
- message: "Choose a scanner you wish to use to scan your code",
655
- type: "list",
656
- choices: scannerChoices
657
- });
658
- return scanner;
659
- }
660
- async function tryCheckmarxConfiguarationAgain() {
661
- console.log(
662
- "\u{1F513} Oops, seems like checkmarx does not accept the current configuration"
663
- );
664
- const { confirmCheckmarxRetryConfigrations } = await inquirer.prompt({
665
- name: "confirmCheckmarxRetryConfigrations",
666
- type: "confirm",
667
- message: "Would like to try to configure them again? ",
668
- default: true
669
- });
670
- return confirmCheckmarxRetryConfigrations;
671
974
  }
672
- async function startCheckmarxConfigationPrompt() {
673
- const checkmarxConfigreSpinner = createSpinner(
674
- "\u{1F513} Checkmarx needs to be configured before we start, press any key to continue"
675
- ).start();
676
- await keypress();
677
- checkmarxConfigreSpinner.success();
975
+ async function getGithubUsername(accessToken) {
976
+ const oktoKit = getOktoKit({ githubAuthToken: accessToken });
977
+ const res = await oktoKit.rest.users.getAuthenticated();
978
+ return res.data.login;
678
979
  }
679
- async function scmIntegrationPrompt(scmName) {
680
- const answers = await inquirer.prompt({
681
- name: "scmConfirm",
682
- type: "confirm",
683
- message: `It seems we don't have access to the repo, do you want to grant access to your ${scmName} account?`,
684
- default: true
980
+ async function getGithubIsUserCollaborator(username, accessToken, repoUrl) {
981
+ try {
982
+ const { owner, repo } = parseOwnerAndRepo(repoUrl);
983
+ const oktoKit = getOktoKit({ githubAuthToken: accessToken });
984
+ const res = await oktoKit.rest.repos.checkCollaborator({
985
+ owner,
986
+ repo,
987
+ username
988
+ });
989
+ if (res.status === 204) {
990
+ return true;
991
+ }
992
+ } catch (e) {
993
+ return false;
994
+ }
995
+ return false;
996
+ }
997
+ async function getGithubPullRequestStatus(accessToken, repoUrl, prNumber) {
998
+ const { owner, repo } = parseOwnerAndRepo(repoUrl);
999
+ const oktoKit = getOktoKit({ githubAuthToken: accessToken });
1000
+ const res = await oktoKit.rest.pulls.get({
1001
+ owner,
1002
+ repo,
1003
+ pull_number: prNumber
685
1004
  });
686
- return answers.scmConfirm;
1005
+ if (res.data.merged) {
1006
+ return "merged";
1007
+ }
1008
+ if (res.data.draft) {
1009
+ return "draft";
1010
+ }
1011
+ return res.data.state;
687
1012
  }
688
- async function mobbAnalysisPrompt() {
689
- const spinner = createSpinner().start();
690
- spinner.update({ text: "Hit any key to view available fixes" });
691
- await keypress();
692
- return spinner.success();
1013
+ async function getGithubIsRemoteBranch(accessToken, repoUrl, branch) {
1014
+ const { owner, repo } = parseOwnerAndRepo(repoUrl);
1015
+ const oktoKit = getOktoKit({ githubAuthToken: accessToken });
1016
+ try {
1017
+ const res = await oktoKit.rest.repos.getBranch({
1018
+ owner,
1019
+ repo,
1020
+ branch
1021
+ });
1022
+ return branch === res.data.name;
1023
+ } catch (e) {
1024
+ return false;
1025
+ }
693
1026
  }
694
- async function snykArticlePrompt() {
695
- const { snykArticleConfirm } = await inquirer.prompt({
696
- name: "snykArticleConfirm",
697
- type: "confirm",
698
- message: "Do you want to be taken to the relevant Snyk's online article?",
699
- default: true
700
- });
701
- return snykArticleConfirm;
1027
+ async function getGithubRepoList(accessToken) {
1028
+ const oktoKit = getOktoKit({ githubAuthToken: accessToken });
1029
+ try {
1030
+ const githubRepos = await getRepos(oktoKit);
1031
+ return githubRepos.map(
1032
+ (repo) => {
1033
+ const repoLanguages = [];
1034
+ if (repo.language) {
1035
+ repoLanguages.push(repo.language);
1036
+ }
1037
+ return {
1038
+ repoName: repo.name,
1039
+ repoUrl: repo.html_url,
1040
+ repoOwner: repo.owner.login,
1041
+ repoLanguages,
1042
+ repoIsPublic: !repo.private,
1043
+ repoUpdatedAt: repo.updated_at
1044
+ };
1045
+ }
1046
+ );
1047
+ } catch (e) {
1048
+ if (e instanceof RequestError && e.status === 401) {
1049
+ return [];
1050
+ }
1051
+ if (e instanceof RequestError && e.status === 404) {
1052
+ return [];
1053
+ }
1054
+ throw e;
1055
+ }
702
1056
  }
703
-
704
- // src/features/analysis/scanners/checkmarx.ts
705
- import { createRequire } from "node:module";
706
-
707
- // src/post_install/constants.mjs
708
- var cxOperatingSystemSupportMessage = `Your operating system does not support checkmarx.
709
- You can see the list of supported operating systems here: https://github.com/Checkmarx/ast-cli#releases`;
710
-
711
- // src/utils/child_process.ts
712
- import cp from "node:child_process";
713
- import Debug5 from "debug";
714
- import * as process2 from "process";
715
- import supportsColor from "supports-color";
716
- var { stdout: stdout2 } = supportsColor;
717
- function createFork({ args, processPath, name }, options) {
718
- const child = cp.fork(processPath, args, {
719
- stdio: ["inherit", "pipe", "pipe", "ipc"],
720
- env: { FORCE_COLOR: stdout2 ? "1" : "0" }
1057
+ async function getGithubBranchList(accessToken, repoUrl) {
1058
+ const { owner, repo } = parseOwnerAndRepo(repoUrl);
1059
+ const oktoKit = getOktoKit({ githubAuthToken: accessToken });
1060
+ const res = await oktoKit.rest.repos.listBranches({
1061
+ owner,
1062
+ repo,
1063
+ per_page: 1e3,
1064
+ page: 1
721
1065
  });
722
- return createChildProcess({ childProcess: child, name }, options);
1066
+ return res.data.map((branch) => branch.name);
723
1067
  }
724
- function createSpwan({ args, processPath, name }, options) {
725
- const child = cp.spawn(processPath, args, {
726
- stdio: ["inherit", "pipe", "pipe", "ipc"],
727
- env: { FORCE_COLOR: stdout2 ? "1" : "0" }
1068
+ async function createPullRequest(options) {
1069
+ const { owner, repo } = parseOwnerAndRepo(options.repoUrl);
1070
+ const oktoKit = getOktoKit({ githubAuthToken: options.accessToken });
1071
+ const res = await oktoKit.rest.pulls.create({
1072
+ owner,
1073
+ repo,
1074
+ title: options.title,
1075
+ body: options.body,
1076
+ head: options.sourceBranchName,
1077
+ base: options.targetBranchName,
1078
+ draft: false,
1079
+ maintainer_can_modify: true
728
1080
  });
729
- return createChildProcess({ childProcess: child, name }, options);
1081
+ return res.data.number;
730
1082
  }
731
- function createChildProcess({ childProcess, name }, options) {
732
- const debug9 = Debug5(`mobbdev:${name}`);
733
- const { display } = options;
734
- return new Promise((resolve, reject) => {
735
- let out = "";
736
- const onData = (chunk) => {
737
- debug9(`chunk received from ${name} std ${chunk}`);
738
- out += chunk;
739
- };
740
- if (!childProcess || !childProcess?.stdout || !childProcess?.stderr) {
741
- debug9(`unable to fork ${name}`);
742
- reject(new Error(`unable to fork ${name}`));
743
- }
744
- childProcess.stdout?.on("data", onData);
745
- childProcess.stderr?.on("data", onData);
746
- if (display) {
747
- childProcess.stdout?.pipe(process2.stdout);
748
- childProcess.stderr?.pipe(process2.stderr);
1083
+ async function getRepos(oktoKit) {
1084
+ const res = await oktoKit.request("GET /user/repos?sort=updated", {
1085
+ headers: {
1086
+ "X-GitHub-Api-Version": "2022-11-28",
1087
+ per_page: 100
749
1088
  }
750
- childProcess.on("exit", (code) => {
751
- debug9(`${name} exit code ${code}`);
752
- resolve({ message: out, code });
753
- });
754
- childProcess.on("error", (err) => {
755
- debug9(`${name} error %o`, err);
756
- reject(err);
757
- });
758
1089
  });
1090
+ return res.data;
759
1091
  }
760
-
761
- // src/features/analysis/scanners/checkmarx.ts
762
- import chalk2 from "chalk";
763
- import Debug6 from "debug";
764
- import { existsSync } from "fs";
765
- import { createSpinner as createSpinner2 } from "nanospinner";
766
- import { type } from "os";
767
- import path4 from "path";
768
- var debug5 = Debug6("mobbdev:checkmarx");
769
- var require2 = createRequire(import.meta.url);
770
- var getCheckmarxPath = () => {
771
- const os3 = type();
772
- const cxFileName = os3 === "Windows_NT" ? "cx.exe" : "cx";
1092
+ async function getGithubRepoDefaultBranch(repoUrl, options) {
1093
+ const oktoKit = getOktoKit(options);
1094
+ const { owner, repo } = parseOwnerAndRepo(repoUrl);
1095
+ return (await oktoKit.rest.repos.get({ repo, owner })).data.default_branch;
1096
+ }
1097
+ async function getGithubReferenceData({ ref, gitHubUrl }, options) {
1098
+ const { owner, repo } = parseOwnerAndRepo(gitHubUrl);
1099
+ let res;
773
1100
  try {
774
- return require2.resolve(`.bin/${cxFileName}`);
1101
+ const oktoKit = getOktoKit(options);
1102
+ res = await Promise.any([
1103
+ getBranch({ owner, repo, branch: ref }, oktoKit).then((result) => ({
1104
+ date: result.data.commit.commit.committer?.date ? new Date(result.data.commit.commit.committer?.date) : void 0,
1105
+ type: "BRANCH" /* BRANCH */,
1106
+ sha: result.data.commit.sha
1107
+ })),
1108
+ getCommit({ commitSha: ref, repo, owner }, oktoKit).then((commit) => ({
1109
+ date: new Date(commit.data.committer.date),
1110
+ type: "COMMIT" /* COMMIT */,
1111
+ sha: commit.data.sha
1112
+ })),
1113
+ getTagDate({ owner, repo, tag: ref }, oktoKit).then((data) => ({
1114
+ date: new Date(data.date),
1115
+ type: "TAG" /* TAG */,
1116
+ sha: data.sha
1117
+ }))
1118
+ ]);
1119
+ return res;
775
1120
  } catch (e) {
776
- throw new CliError(cxOperatingSystemSupportMessage);
1121
+ if (e instanceof AggregateError) {
1122
+ throw new RefNotFoundError(`ref: ${ref} does not exist`);
1123
+ }
1124
+ throw e;
777
1125
  }
778
- };
779
- var getCheckmarxCommandArgs = ({
780
- repoPath,
781
- branch,
782
- fileName,
783
- filePath,
784
- projectName
785
- }) => [
786
- "--project-name",
787
- projectName,
788
- "-s",
789
- repoPath,
790
- "--branch",
791
- branch,
792
- "--scan-types",
793
- "sast",
794
- "--output-path",
795
- filePath,
796
- "--output-name",
797
- fileName,
798
- "--report-format",
799
- "json"
800
- ];
801
- var VALIDATE_COMMAND = ["auth", "validate"];
802
- var CONFIGURE_COMMAND = ["configure"];
803
- var SCAN_COMMAND = ["scan", "create"];
804
- var CHECKMARX_SUCCESS_CODE = 0;
805
- function validateCheckmarxInstallation() {
806
- existsSync(getCheckmarxPath());
807
1126
  }
808
- async function forkCheckmarx(args, { display }) {
809
- debug5("fork checkmarx with args %o %s", args.join(" "), display);
810
- return createSpwan(
811
- { args, processPath: getCheckmarxPath(), name: "checkmarx" },
812
- { display }
813
- );
1127
+ async function getBranch({ branch, owner, repo }, oktoKit) {
1128
+ return oktoKit.rest.repos.getBranch({
1129
+ branch,
1130
+ owner,
1131
+ repo
1132
+ });
814
1133
  }
815
- async function getCheckmarxReport({ reportPath, repositoryRoot, branch, projectName }, { skipPrompts = false }) {
816
- debug5("get checkmarx report start %s %s", reportPath, repositoryRoot);
817
- const { code: loginCode } = await forkCheckmarx(VALIDATE_COMMAND, {
818
- display: false
1134
+ async function getTagDate({ tag, owner, repo }, oktoKit) {
1135
+ const refResponse = await oktoKit.rest.git.getRef({
1136
+ ref: `tags/${tag}`,
1137
+ owner,
1138
+ repo
819
1139
  });
820
- if (loginCode !== CHECKMARX_SUCCESS_CODE) {
821
- if (skipPrompts) {
822
- await throwCheckmarxConfigError();
823
- }
824
- await startCheckmarxConfigationPrompt();
825
- await validateCheckamxCredentials();
1140
+ const tagSha = refResponse.data.object.sha;
1141
+ if (refResponse.data.object.type === "commit") {
1142
+ const res2 = await oktoKit.rest.git.getCommit({
1143
+ commit_sha: tagSha,
1144
+ owner,
1145
+ repo
1146
+ });
1147
+ return {
1148
+ date: res2.data.committer.date,
1149
+ sha: res2.data.sha
1150
+ };
826
1151
  }
827
- const extension = path4.extname(reportPath);
828
- const filePath = path4.dirname(reportPath);
829
- const fileName = path4.basename(reportPath, extension);
830
- const checkmarxCommandArgs = getCheckmarxCommandArgs({
831
- repoPath: repositoryRoot,
832
- branch,
833
- filePath,
834
- fileName,
835
- projectName
1152
+ const res = await oktoKit.rest.git.getTag({
1153
+ tag_sha: tagSha,
1154
+ owner,
1155
+ repo
836
1156
  });
837
- console.log("\u280B \u{1F50D} Initiating Checkmarx Scan ");
838
- const { code: scanCode } = await forkCheckmarx(
839
- [...SCAN_COMMAND, ...checkmarxCommandArgs],
840
- {
841
- display: true
842
- }
843
- );
844
- if (scanCode !== CHECKMARX_SUCCESS_CODE) {
845
- createSpinner2("\u{1F50D} Something went wrong with the checkmarx scan").start().error();
846
- throw new CliError();
847
- }
848
- await createSpinner2("\u{1F50D} Checkmarx Scan completed").start().success();
849
- return true;
850
- }
851
- async function throwCheckmarxConfigError() {
852
- await createSpinner2("\u{1F513} Checkmarx is not configued correctly").start().error();
853
- throw new CliError(
854
- `Checkmarx is not configued correctly
855
- you can configure it by using the ${chalk2.bold(
856
- "cx configure"
857
- )} command`
858
- );
1157
+ return {
1158
+ date: res.data.tagger.date,
1159
+ sha: res.data.sha
1160
+ };
859
1161
  }
860
- async function validateCheckamxCredentials() {
861
- console.log(`
862
- Here's a suggestion for checkmarx configuation:
863
- ${chalk2.bold("AST Base URI:")} https://ast.checkmarx.net
864
- ${chalk2.bold("AST Base Auth URI (IAM):")} https://iam.checkmarx.net
865
- `);
866
- await forkCheckmarx(CONFIGURE_COMMAND, { display: true });
867
- const { code: loginCode } = await forkCheckmarx(VALIDATE_COMMAND, {
868
- display: false
1162
+ async function getCommit({
1163
+ commitSha,
1164
+ owner,
1165
+ repo
1166
+ }, oktoKit) {
1167
+ return oktoKit.rest.git.getCommit({
1168
+ repo,
1169
+ owner,
1170
+ commit_sha: commitSha
869
1171
  });
870
- if (loginCode !== CHECKMARX_SUCCESS_CODE) {
871
- const tryAgain = await tryCheckmarxConfiguarationAgain();
872
- if (!tryAgain) {
873
- await throwCheckmarxConfigError();
874
- }
875
- if (await tryCheckmarxConfiguarationAgain()) {
876
- validateCheckamxCredentials();
877
- }
878
- }
879
- await createSpinner2("\u{1F513} Checkmarx configured successfully!").start().success();
880
1172
  }
881
-
882
- // src/features/analysis/scanners/snyk.ts
883
- import { createRequire as createRequire2 } from "node:module";
884
- import chalk3 from "chalk";
885
- import Debug7 from "debug";
886
- import { createSpinner as createSpinner3 } from "nanospinner";
887
- import open from "open";
888
- var debug6 = Debug7("mobbdev:snyk");
889
- var require3 = createRequire2(import.meta.url);
890
- var SNYK_PATH = require3.resolve("snyk/bin/snyk");
891
- var SNYK_ARTICLE_URL = "https://docs.snyk.io/scan-application-code/snyk-code/getting-started-with-snyk-code/activating-snyk-code-using-the-web-ui/step-1-enabling-the-snyk-code-option";
892
- debug6("snyk executable path %s", SNYK_PATH);
893
- async function forkSnyk(args, { display }) {
894
- debug6("fork snyk with args %o %s", args, display);
895
- return createFork(
896
- { args, processPath: SNYK_PATH, name: "checkmarx" },
897
- { display }
898
- );
1173
+ function parseOwnerAndRepo(gitHubUrl) {
1174
+ gitHubUrl = removeTrailingSlash(gitHubUrl);
1175
+ const parsingResult = parseScmURL(gitHubUrl);
1176
+ if (!parsingResult || parsingResult.hostname !== "github.com") {
1177
+ throw new InvalidUrlPatternError(`invalid github repo Url ${gitHubUrl}`);
1178
+ }
1179
+ const { organization, repoName } = parsingResult;
1180
+ if (!organization || !repoName) {
1181
+ throw new InvalidUrlPatternError(`invalid github repo Url ${gitHubUrl}`);
1182
+ }
1183
+ return { owner: organization, repo: repoName };
899
1184
  }
900
- async function getSnykReport(reportPath, repoRoot, { skipPrompts = false }) {
901
- debug6("get snyk report start %s %s", reportPath, repoRoot);
902
- const config3 = await forkSnyk(["config"], { display: false });
903
- const { message: configMessage } = config3;
904
- if (!configMessage.includes("api: ")) {
905
- const snykLoginSpinner = createSpinner3().start();
906
- if (!skipPrompts) {
907
- snykLoginSpinner.update({
908
- text: "\u{1F513} Login to Snyk is required, press any key to continue"
909
- });
910
- await keypress();
1185
+ async function queryGithubGraphql(query, variables, options) {
1186
+ const token = options?.githubAuthToken ?? GITHUB_API_TOKEN ?? "";
1187
+ const parameters = variables ?? {};
1188
+ const authorizationHeader = {
1189
+ headers: {
1190
+ authorization: `bearer ${token}`
911
1191
  }
912
- snykLoginSpinner.update({
913
- text: "\u{1F513} Waiting for Snyk login to complete"
1192
+ };
1193
+ try {
1194
+ const oktoKit = getOktoKit(options);
1195
+ const res = await oktoKit.graphql(query, {
1196
+ ...parameters,
1197
+ ...authorizationHeader
914
1198
  });
915
- debug6("no token in the config %s", config3);
916
- await forkSnyk(["auth"], { display: true });
917
- snykLoginSpinner.success({ text: "\u{1F513} Login to Snyk Successful" });
1199
+ return res;
1200
+ } catch (e) {
1201
+ if (e instanceof RequestError) {
1202
+ return null;
1203
+ }
1204
+ throw e;
918
1205
  }
919
- const snykSpinner = createSpinner3("\u{1F50D} Scanning your repo with Snyk ").start();
920
- const { message: scanOutput } = await forkSnyk(
921
- ["code", "test", `--sarif-file-output=${reportPath}`, repoRoot],
922
- { display: true }
1206
+ }
1207
+ async function getGithubBlameRanges({ ref, gitHubUrl, path: path8 }, options) {
1208
+ const { owner, repo } = parseOwnerAndRepo(gitHubUrl);
1209
+ const variables = {
1210
+ owner,
1211
+ repo,
1212
+ path: path8,
1213
+ ref
1214
+ };
1215
+ const res = await queryGithubGraphql(
1216
+ GetBlameDocument,
1217
+ variables,
1218
+ options
923
1219
  );
924
- if (scanOutput.includes(
925
- "Snyk Code is not supported for org: enable in Settings > Snyk Code"
926
- )) {
927
- debug6("snyk code is not enabled %s", scanOutput);
928
- snykSpinner.error({ text: "\u{1F50D} Snyk configuration needed" });
929
- const answer = await snykArticlePrompt();
930
- debug6("answer %s", answer);
931
- if (answer) {
932
- debug6("opening the browser");
933
- await open(SNYK_ARTICLE_URL);
934
- }
935
- console.log(
936
- chalk3.bgBlue(
937
- "\nPlease enable Snyk Code in your Snyk account and try again."
938
- )
939
- );
940
- throw Error("snyk is not enbabled");
1220
+ if (!res?.repository?.object?.blame?.ranges) {
1221
+ return [];
941
1222
  }
942
- snykSpinner.success({ text: "\u{1F50D} Snyk code scan completed" });
943
- return true;
1223
+ return res.repository.object.blame.ranges.map((range) => ({
1224
+ startingLine: range.startingLine,
1225
+ endingLine: range.endingLine,
1226
+ email: range.commit.author.user.email,
1227
+ name: range.commit.author.user.name,
1228
+ login: range.commit.author.user.login
1229
+ }));
944
1230
  }
945
1231
 
946
- // src/features/analysis/scm/gitlab.ts
947
- import querystring from "node:querystring";
948
- import { Gitlab } from "@gitbeaker/rest";
949
- import { z as z5 } from "zod";
1232
+ // src/features/analysis/scm/github/consts.ts
1233
+ var POST_COMMENT_PATH = "POST /repos/{owner}/{repo}/pulls/{pull_number}/comments";
1234
+ var DELETE_COMMENT_PATH = "DELETE /repos/{owner}/{repo}/pulls/comments/{comment_id}";
1235
+ var UPDATE_COMMENT_PATH = "PATCH /repos/{owner}/{repo}/pulls/comments/{comment_id}";
1236
+ var GET_PR_COMMENTS_PATH = "GET /repos/{owner}/{repo}/pulls/comments";
950
1237
 
951
- // src/features/analysis/scm/github.ts
952
- import { RequestError } from "@octokit/request-error";
953
- import { Octokit } from "octokit";
954
- import { z as z3 } from "zod";
955
- function removeTrailingSlash(str) {
956
- return str.trim().replace(/\/+$/, "");
1238
+ // src/features/analysis/scm/github/github-v2.ts
1239
+ function postPrComment(client, params) {
1240
+ return client.request(POST_COMMENT_PATH, params);
957
1241
  }
958
- var EnvVariablesZod = z3.object({
959
- GITHUB_API_TOKEN: z3.string().optional()
960
- });
961
- var { GITHUB_API_TOKEN } = EnvVariablesZod.parse(process.env);
962
- var GetBlameDocument = `
963
- query GetBlame(
964
- $owner: String!
965
- $repo: String!
966
- $ref: String!
967
- $path: String!
968
- ) {
969
- repository(name: $repo, owner: $owner) {
970
- # branch name
971
- object(expression: $ref) {
972
- # cast Target to a Commit
973
- ... on Commit {
974
- # full repo-relative path to blame file
975
- blame(path: $path) {
976
- ranges {
977
- commit {
978
- author {
979
- user {
980
- name
981
- login
982
- }
983
- }
984
- authoredDate
985
- }
986
- startingLine
987
- endingLine
988
- age
989
- }
990
- }
991
- }
992
-
993
- }
994
- }
995
- }
996
- `;
997
- var githubUrlRegex = /^http[s]?:\/\/[^/\s]+\/([^/.\s]+\/[^/.\s]+)(\.git)?(\/)?$/i;
998
- function getOktoKit(options) {
999
- const token = options?.githubAuthToken ?? GITHUB_API_TOKEN ?? "";
1000
- return new Octokit({ auth: token });
1242
+ function updatePrComment(client, params) {
1243
+ return client.request(UPDATE_COMMENT_PATH, params);
1001
1244
  }
1002
- async function githubValidateParams(url, accessToken) {
1245
+ function getPrComments(client, params) {
1246
+ return client.request(GET_PR_COMMENTS_PATH, params);
1247
+ }
1248
+ function deleteComment(client, params) {
1249
+ return client.request(DELETE_COMMENT_PATH, params);
1250
+ }
1251
+
1252
+ // src/features/analysis/scm/scmSubmit.ts
1253
+ import fs from "node:fs/promises";
1254
+ import os from "os";
1255
+ import path3 from "path";
1256
+ import { simpleGit as simpleGit2 } from "simple-git";
1257
+ import tmp from "tmp";
1258
+ import { z as z4 } from "zod";
1259
+ var isValidBranchName = async (branchName) => {
1260
+ const git = simpleGit2();
1003
1261
  try {
1004
- const oktoKit = getOktoKit({ githubAuthToken: accessToken });
1005
- if (accessToken) {
1006
- await oktoKit.rest.users.getAuthenticated();
1007
- }
1008
- if (url) {
1009
- const { owner, repo } = parseOwnerAndRepo(url);
1010
- await oktoKit.rest.repos.get({ repo, owner });
1262
+ const res = await git.raw(["check-ref-format", "--branch", branchName]);
1263
+ if (res) {
1264
+ return true;
1011
1265
  }
1266
+ return false;
1012
1267
  } catch (e) {
1013
- const error = e;
1014
- const code = error.status || error.statusCode || error.response?.status || error.response?.statusCode || error.response?.code;
1015
- if (code === 401 || code === 403) {
1016
- throw new InvalidAccessTokenError(`invalid github access token`);
1017
- }
1018
- if (code === 404) {
1019
- throw new InvalidRepoUrlError(`invalid github repo Url ${url}`);
1020
- }
1021
- throw e;
1268
+ return false;
1022
1269
  }
1023
- }
1024
- async function getGithubUsername(accessToken) {
1025
- const oktoKit = getOktoKit({ githubAuthToken: accessToken });
1026
- const res = await oktoKit.rest.users.getAuthenticated();
1027
- return res.data.login;
1028
- }
1029
- async function getGithubIsUserCollaborator(username, accessToken, repoUrl) {
1030
- try {
1031
- const { owner, repo } = parseOwnerAndRepo(repoUrl);
1032
- const oktoKit = getOktoKit({ githubAuthToken: accessToken });
1033
- const res = await oktoKit.rest.repos.checkCollaborator({
1034
- owner,
1035
- repo,
1036
- username
1037
- });
1038
- if (res.status === 204) {
1039
- return true;
1040
- }
1041
- } catch (e) {
1042
- return false;
1270
+ };
1271
+ var BaseSubmitToScmMessageZ = z4.object({
1272
+ submitFixRequestId: z4.string().uuid(),
1273
+ fixes: z4.array(
1274
+ z4.object({
1275
+ fixId: z4.string().uuid(),
1276
+ diff: z4.string()
1277
+ })
1278
+ ),
1279
+ commitHash: z4.string(),
1280
+ repoUrl: z4.string()
1281
+ });
1282
+ var submitToScmMessageType = {
1283
+ commitToSameBranch: "commitToSameBranch",
1284
+ submitFixesForDifferentBranch: "submitFixesForDifferentBranch"
1285
+ };
1286
+ var CommitToSameBranchParamsZ = BaseSubmitToScmMessageZ.merge(
1287
+ z4.object({
1288
+ type: z4.literal(submitToScmMessageType.commitToSameBranch),
1289
+ branch: z4.string()
1290
+ })
1291
+ );
1292
+ var SubmitFixesToDifferentBranchParamsZ = z4.object({
1293
+ type: z4.literal(submitToScmMessageType.submitFixesForDifferentBranch),
1294
+ submitBranch: z4.string(),
1295
+ baseBranch: z4.string()
1296
+ }).merge(BaseSubmitToScmMessageZ);
1297
+ var SubmitFixesMessageZ = z4.union([
1298
+ CommitToSameBranchParamsZ,
1299
+ SubmitFixesToDifferentBranchParamsZ
1300
+ ]);
1301
+ var FixResponseArrayZ = z4.array(
1302
+ z4.object({
1303
+ fixId: z4.string().uuid()
1304
+ })
1305
+ );
1306
+ var SubmitFixesResponseMessageZ = z4.object({
1307
+ type: z4.nativeEnum(submitToScmMessageType),
1308
+ submitFixRequestId: z4.string().uuid(),
1309
+ submitBranches: z4.array(
1310
+ z4.object({
1311
+ branchName: z4.string(),
1312
+ fixes: FixResponseArrayZ
1313
+ })
1314
+ ),
1315
+ error: z4.object({
1316
+ type: z4.enum([
1317
+ "InitialRepoAccessError",
1318
+ "PushBranchError",
1319
+ "UnknownError"
1320
+ ]),
1321
+ info: z4.object({
1322
+ message: z4.string(),
1323
+ pushBranchName: z4.string().optional()
1324
+ })
1325
+ }).optional()
1326
+ });
1327
+ var FixesZ = z4.array(z4.object({ fixId: z4.string(), diff: z4.string() })).nonempty();
1328
+
1329
+ // src/features/analysis/scm/scm.ts
1330
+ function getScmLibTypeFromUrl(url) {
1331
+ if (!url) {
1332
+ return void 0;
1043
1333
  }
1044
- return false;
1045
- }
1046
- async function getGithubPullRequestStatus(accessToken, repoUrl, prNumber) {
1047
- const { owner, repo } = parseOwnerAndRepo(repoUrl);
1048
- const oktoKit = getOktoKit({ githubAuthToken: accessToken });
1049
- const res = await oktoKit.rest.pulls.get({
1050
- owner,
1051
- repo,
1052
- pull_number: prNumber
1053
- });
1054
- if (res.data.merged) {
1055
- return "merged";
1334
+ if (url.toLowerCase().startsWith("https://gitlab.com/")) {
1335
+ return "GITLAB" /* GITLAB */;
1056
1336
  }
1057
- if (res.data.draft) {
1058
- return "draft";
1337
+ if (url.toLowerCase().startsWith("https://github.com/")) {
1338
+ return "GITHUB" /* GITHUB */;
1059
1339
  }
1060
- return res.data.state;
1340
+ return void 0;
1061
1341
  }
1062
- async function getGithubIsRemoteBranch(accessToken, repoUrl, branch) {
1063
- const { owner, repo } = parseOwnerAndRepo(repoUrl);
1064
- const oktoKit = getOktoKit({ githubAuthToken: accessToken });
1342
+ async function scmCanReachRepo({
1343
+ repoUrl,
1344
+ githubToken,
1345
+ gitlabToken
1346
+ }) {
1065
1347
  try {
1066
- const res = await oktoKit.rest.repos.getBranch({
1067
- owner,
1068
- repo,
1069
- branch
1348
+ const scmLibType = getScmLibTypeFromUrl(repoUrl);
1349
+ await SCMLib.init({
1350
+ url: repoUrl,
1351
+ accessToken: scmLibType === "GITHUB" /* GITHUB */ ? githubToken : scmLibType === "GITLAB" /* GITLAB */ ? gitlabToken : "",
1352
+ scmType: scmLibType
1070
1353
  });
1071
- return branch === res.data.name;
1354
+ return true;
1072
1355
  } catch (e) {
1073
1356
  return false;
1074
1357
  }
1075
1358
  }
1076
- async function getGithubRepoList(accessToken) {
1077
- const oktoKit = getOktoKit({ githubAuthToken: accessToken });
1078
- try {
1079
- const githubRepos = await getRepos(oktoKit);
1080
- return githubRepos.map(
1081
- (repo) => {
1082
- const repoLanguages = [];
1083
- if (repo.language) {
1084
- repoLanguages.push(repo.language);
1085
- }
1086
- return {
1087
- repoName: repo.name,
1088
- repoUrl: repo.html_url,
1089
- repoOwner: repo.owner.login,
1090
- repoLanguages,
1091
- repoIsPublic: !repo.private,
1092
- repoUpdatedAt: repo.updated_at
1093
- };
1094
- }
1095
- );
1096
- } catch (e) {
1097
- if (e instanceof RequestError && e.status === 401) {
1098
- return [];
1099
- }
1100
- if (e instanceof RequestError && e.status === 404) {
1101
- return [];
1102
- }
1103
- throw e;
1359
+ var InvalidRepoUrlError = class extends Error {
1360
+ constructor(m) {
1361
+ super(m);
1104
1362
  }
1105
- }
1106
- async function getGithubBranchList(accessToken, repoUrl) {
1107
- const { owner, repo } = parseOwnerAndRepo(repoUrl);
1108
- const oktoKit = getOktoKit({ githubAuthToken: accessToken });
1109
- const res = await oktoKit.rest.repos.listBranches({
1110
- owner,
1111
- repo,
1112
- per_page: 1e3,
1113
- page: 1
1114
- });
1115
- return res.data.map((branch) => branch.name);
1116
- }
1117
- async function createPullRequest(options) {
1118
- const { owner, repo } = parseOwnerAndRepo(options.repoUrl);
1119
- const oktoKit = getOktoKit({ githubAuthToken: options.accessToken });
1120
- const res = await oktoKit.rest.pulls.create({
1121
- owner,
1122
- repo,
1123
- title: options.title,
1124
- body: options.body,
1125
- head: options.sourceBranchName,
1126
- base: options.targetBranchName,
1127
- draft: false,
1128
- maintainer_can_modify: true
1129
- });
1130
- return res.data.number;
1131
- }
1132
- async function getRepos(oktoKit) {
1133
- const res = await oktoKit.request("GET /user/repos?sort=updated", {
1134
- headers: {
1135
- "X-GitHub-Api-Version": "2022-11-28",
1136
- per_page: 100
1363
+ };
1364
+ var InvalidAccessTokenError = class extends Error {
1365
+ constructor(m) {
1366
+ super(m);
1367
+ }
1368
+ };
1369
+ var InvalidUrlPatternError = class extends Error {
1370
+ constructor(m) {
1371
+ super(m);
1372
+ }
1373
+ };
1374
+ var RefNotFoundError = class extends Error {
1375
+ constructor(m) {
1376
+ super(m);
1377
+ }
1378
+ };
1379
+ var RepoNoTokenAccessError = class extends Error {
1380
+ constructor(m) {
1381
+ super(m);
1382
+ }
1383
+ };
1384
+ var SCMLib = class {
1385
+ constructor(url, accessToken) {
1386
+ __publicField(this, "url");
1387
+ __publicField(this, "accessToken");
1388
+ this.accessToken = accessToken;
1389
+ this.url = url;
1390
+ }
1391
+ async getUrlWithCredentials() {
1392
+ if (!this.url) {
1393
+ console.error("no url for getUrlWithCredentials()");
1394
+ throw new Error("no url");
1137
1395
  }
1138
- });
1139
- return res.data;
1140
- }
1141
- async function getGithubRepoDefaultBranch(repoUrl, options) {
1142
- const oktoKit = getOktoKit(options);
1143
- const { owner, repo } = parseOwnerAndRepo(repoUrl);
1144
- return (await oktoKit.rest.repos.get({ repo, owner })).data.default_branch;
1145
- }
1146
- async function getGithubReferenceData({ ref, gitHubUrl }, options) {
1147
- const { owner, repo } = parseOwnerAndRepo(gitHubUrl);
1148
- let res;
1149
- try {
1150
- const oktoKit = getOktoKit(options);
1151
- res = await Promise.any([
1152
- getBranch({ owner, repo, branch: ref }, oktoKit).then((result) => ({
1153
- date: result.data.commit.commit.committer?.date ? new Date(result.data.commit.commit.committer?.date) : void 0,
1154
- type: "BRANCH" /* BRANCH */,
1155
- sha: result.data.commit.sha
1156
- })),
1157
- getCommit({ commitSha: ref, repo, owner }, oktoKit).then((commit) => ({
1158
- date: new Date(commit.data.committer.date),
1159
- type: "COMMIT" /* COMMIT */,
1160
- sha: commit.data.sha
1161
- })),
1162
- getTagDate({ owner, repo, tag: ref }, oktoKit).then((data) => ({
1163
- date: new Date(data.date),
1164
- type: "TAG" /* TAG */,
1165
- sha: data.sha
1166
- }))
1167
- ]);
1168
- return res;
1169
- } catch (e) {
1170
- if (e instanceof AggregateError) {
1171
- throw new RefNotFoundError(`ref: ${ref} does not exist`);
1172
- }
1173
- throw e;
1174
- }
1175
- }
1176
- async function getBranch({ branch, owner, repo }, oktoKit) {
1177
- return oktoKit.rest.repos.getBranch({
1178
- branch,
1179
- owner,
1180
- repo
1181
- });
1182
- }
1183
- async function getTagDate({ tag, owner, repo }, oktoKit) {
1184
- const refResponse = await oktoKit.rest.git.getRef({
1185
- ref: `tags/${tag}`,
1186
- owner,
1187
- repo
1188
- });
1189
- const tagSha = refResponse.data.object.sha;
1190
- if (refResponse.data.object.type === "commit") {
1191
- const res2 = await oktoKit.rest.git.getCommit({
1192
- commit_sha: tagSha,
1193
- owner,
1194
- repo
1195
- });
1196
- return {
1197
- date: res2.data.committer.date,
1198
- sha: res2.data.sha
1199
- };
1200
- }
1201
- const res = await oktoKit.rest.git.getTag({
1202
- tag_sha: tagSha,
1203
- owner,
1204
- repo
1205
- });
1206
- return {
1207
- date: res.data.tagger.date,
1208
- sha: res.data.sha
1209
- };
1210
- }
1211
- async function getCommit({
1212
- commitSha,
1213
- owner,
1214
- repo
1215
- }, oktoKit) {
1216
- return oktoKit.rest.git.getCommit({
1217
- repo,
1218
- owner,
1219
- commit_sha: commitSha
1220
- });
1221
- }
1222
- function parseOwnerAndRepo(gitHubUrl) {
1223
- gitHubUrl = removeTrailingSlash(gitHubUrl);
1224
- if (!githubUrlRegex.test(gitHubUrl)) {
1225
- throw new InvalidUrlPatternError(`invalid github repo Url ${gitHubUrl}`);
1226
- }
1227
- const groups = gitHubUrl.split(githubUrlRegex).filter((res) => res);
1228
- const ownerAndRepo = groups[0]?.split("/");
1229
- const owner = ownerAndRepo?.at(0);
1230
- const repo = ownerAndRepo?.at(1);
1231
- if (!owner || !repo) {
1232
- throw new InvalidUrlPatternError(`invalid github repo Url ${gitHubUrl}`);
1233
- }
1234
- return { owner, repo };
1235
- }
1236
- async function queryGithubGraphql(query, variables, options) {
1237
- const token = options?.githubAuthToken ?? GITHUB_API_TOKEN ?? "";
1238
- const parameters = variables ?? {};
1239
- const authorizationHeader = {
1240
- headers: {
1241
- authorization: `bearer ${token}`
1242
- }
1243
- };
1244
- try {
1245
- const oktoKit = getOktoKit(options);
1246
- const res = await oktoKit.graphql(query, {
1247
- ...parameters,
1248
- ...authorizationHeader
1249
- });
1250
- return res;
1251
- } catch (e) {
1252
- if (e instanceof RequestError) {
1253
- return null;
1254
- }
1255
- throw e;
1256
- }
1257
- }
1258
- async function getGithubBlameRanges({ ref, gitHubUrl, path: path8 }, options) {
1259
- const { owner, repo } = parseOwnerAndRepo(gitHubUrl);
1260
- const variables = {
1261
- owner,
1262
- repo,
1263
- path: path8,
1264
- ref
1265
- };
1266
- const res = await queryGithubGraphql(
1267
- GetBlameDocument,
1268
- variables,
1269
- options
1270
- );
1271
- if (!res?.repository?.object?.blame?.ranges) {
1272
- return [];
1273
- }
1274
- return res.repository.object.blame.ranges.map((range) => ({
1275
- startingLine: range.startingLine,
1276
- endingLine: range.endingLine,
1277
- email: range.commit.author.user.email,
1278
- name: range.commit.author.user.name,
1279
- login: range.commit.author.user.login
1280
- }));
1281
- }
1282
-
1283
- // src/features/analysis/scm/scmSubmit.ts
1284
- import fs2 from "node:fs/promises";
1285
- import os from "os";
1286
- import path5 from "path";
1287
- import { simpleGit as simpleGit2 } from "simple-git";
1288
- import { z as z4 } from "zod";
1289
- var isValidBranchName = async (branchName) => {
1290
- const git = simpleGit2();
1291
- try {
1292
- const res = await git.raw(["check-ref-format", "--branch", branchName]);
1293
- if (res) {
1294
- return true;
1295
- }
1296
- return false;
1297
- } catch (e) {
1298
- return false;
1299
- }
1300
- };
1301
- var SubmitFixesMessageZ = z4.object({
1302
- submitFixRequestId: z4.string().uuid(),
1303
- fixes: z4.array(
1304
- z4.object({
1305
- fixId: z4.string().uuid(),
1306
- diff: z4.string()
1307
- })
1308
- ),
1309
- branchName: z4.string(),
1310
- commitHash: z4.string(),
1311
- targetBranch: z4.string(),
1312
- repoUrl: z4.string()
1313
- });
1314
- var FixResponseArrayZ = z4.array(
1315
- z4.object({
1316
- fixId: z4.string().uuid()
1317
- })
1318
- );
1319
- var SubmitFixesResponseMessageZ = z4.object({
1320
- submitFixRequestId: z4.string().uuid(),
1321
- submitBranches: z4.array(
1322
- z4.object({
1323
- branchName: z4.string(),
1324
- fixes: FixResponseArrayZ
1325
- })
1326
- ),
1327
- error: z4.object({
1328
- type: z4.enum([
1329
- "InitialRepoAccessError",
1330
- "PushBranchError",
1331
- "UnknownError"
1332
- ]),
1333
- info: z4.object({
1334
- message: z4.string(),
1335
- pushBranchName: z4.string().optional()
1336
- })
1337
- }).optional()
1338
- });
1339
-
1340
- // src/features/analysis/scm/scm.ts
1341
- function getScmLibTypeFromUrl(url) {
1342
- if (!url) {
1343
- return void 0;
1344
- }
1345
- if (url.toLowerCase().startsWith("https://gitlab.com/")) {
1346
- return "GITLAB" /* GITLAB */;
1347
- }
1348
- if (url.toLowerCase().startsWith("https://github.com/")) {
1349
- return "GITHUB" /* GITHUB */;
1350
- }
1351
- return void 0;
1352
- }
1353
- async function scmCanReachRepo({
1354
- repoUrl,
1355
- githubToken,
1356
- gitlabToken
1357
- }) {
1358
- try {
1359
- const scmLibType = getScmLibTypeFromUrl(repoUrl);
1360
- await SCMLib.init({
1361
- url: repoUrl,
1362
- accessToken: scmLibType === "GITHUB" /* GITHUB */ ? githubToken : scmLibType === "GITLAB" /* GITLAB */ ? gitlabToken : "",
1363
- scmType: scmLibType
1364
- });
1365
- return true;
1366
- } catch (e) {
1367
- return false;
1368
- }
1369
- }
1370
- var InvalidRepoUrlError = class extends Error {
1371
- constructor(m) {
1372
- super(m);
1373
- }
1374
- };
1375
- var InvalidAccessTokenError = class extends Error {
1376
- constructor(m) {
1377
- super(m);
1378
- }
1379
- };
1380
- var InvalidUrlPatternError = class extends Error {
1381
- constructor(m) {
1382
- super(m);
1383
- }
1384
- };
1385
- var RefNotFoundError = class extends Error {
1386
- constructor(m) {
1387
- super(m);
1388
- }
1389
- };
1390
- var RepoNoTokenAccessError = class extends Error {
1391
- constructor(m) {
1392
- super(m);
1393
- }
1394
- };
1395
- var SCMLib = class {
1396
- constructor(url, accessToken) {
1397
- __publicField(this, "url");
1398
- __publicField(this, "accessToken");
1399
- this.accessToken = accessToken;
1400
- this.url = url;
1401
- }
1402
- async getUrlWithCredentials() {
1403
- if (!this.url) {
1404
- console.error("no url for getUrlWithCredentials()");
1405
- throw new Error("no url");
1406
- }
1407
- const trimmedUrl = this.url.trim().replace(/\/$/, "");
1408
- if (!this.accessToken) {
1409
- return trimmedUrl;
1410
- }
1411
- const username = await this._getUsernameForAuthUrl();
1412
- const is_http = trimmedUrl.toLowerCase().startsWith("http://");
1413
- const is_https = trimmedUrl.toLowerCase().startsWith("https://");
1414
- if (is_http) {
1415
- return `http://${username}:${this.accessToken}@${trimmedUrl.toLowerCase().replace("http://", "")}`;
1416
- } else if (is_https) {
1417
- return `https://${username}:${this.accessToken}@${trimmedUrl.toLowerCase().replace("https://", "")}`;
1418
- } else {
1419
- console.error(`invalid scm url ${trimmedUrl}`);
1420
- throw new Error(`invalid scm url ${trimmedUrl}`);
1396
+ const trimmedUrl = this.url.trim().replace(/\/$/, "");
1397
+ if (!this.accessToken) {
1398
+ return trimmedUrl;
1399
+ }
1400
+ const username = await this._getUsernameForAuthUrl();
1401
+ const is_http = trimmedUrl.toLowerCase().startsWith("http://");
1402
+ const is_https = trimmedUrl.toLowerCase().startsWith("https://");
1403
+ if (is_http) {
1404
+ return `http://${username}:${this.accessToken}@${trimmedUrl.toLowerCase().replace("http://", "")}`;
1405
+ } else if (is_https) {
1406
+ return `https://${username}:${this.accessToken}@${trimmedUrl.toLowerCase().replace("https://", "")}`;
1407
+ } else {
1408
+ console.error(`invalid scm url ${trimmedUrl}`);
1409
+ throw new Error(`invalid scm url ${trimmedUrl}`);
1421
1410
  }
1422
1411
  }
1423
1412
  getAccessToken() {
@@ -1609,6 +1598,11 @@ var GitlabSCMLib = class extends SCMLib {
1609
1598
  }
1610
1599
  };
1611
1600
  var GithubSCMLib = class extends SCMLib {
1601
+ constructor(url, accessToken) {
1602
+ super(url, accessToken);
1603
+ __publicField(this, "oktokit");
1604
+ this.oktokit = new Octokit2({ auth: accessToken });
1605
+ }
1612
1606
  async createSubmitRequest(targetBranchName, sourceBranchName, title, body) {
1613
1607
  if (!this.accessToken || !this.url) {
1614
1608
  console.error("no access token or no url");
@@ -1628,6 +1622,55 @@ var GithubSCMLib = class extends SCMLib {
1628
1622
  async validateParams() {
1629
1623
  return githubValidateParams(this.url, this.accessToken);
1630
1624
  }
1625
+ async postPrComment(params, _oktokit) {
1626
+ if (!_oktokit && !this.accessToken || !this.url) {
1627
+ throw new Error("cannot post on PR without access token or url");
1628
+ }
1629
+ const oktokit = _oktokit || this.oktokit;
1630
+ const { owner, repo } = parseOwnerAndRepo(this.url);
1631
+ return postPrComment(oktokit, {
1632
+ ...params,
1633
+ owner,
1634
+ repo
1635
+ });
1636
+ }
1637
+ async updatePrComment(params, _oktokit) {
1638
+ if (!_oktokit && !this.accessToken || !this.url) {
1639
+ throw new Error("cannot update on PR without access token or url");
1640
+ }
1641
+ const oktokit = _oktokit || this.oktokit;
1642
+ const { owner, repo } = parseOwnerAndRepo(this.url);
1643
+ return updatePrComment(oktokit, {
1644
+ ...params,
1645
+ owner,
1646
+ repo
1647
+ });
1648
+ }
1649
+ async deleteComment(params, _oktokit) {
1650
+ if (!_oktokit && !this.accessToken || !this.url) {
1651
+ throw new Error("cannot delete comment without access token or url");
1652
+ }
1653
+ const oktokit = _oktokit || this.oktokit;
1654
+ const { owner, repo } = parseOwnerAndRepo(this.url);
1655
+ return deleteComment(oktokit, {
1656
+ ...params,
1657
+ owner,
1658
+ repo
1659
+ });
1660
+ }
1661
+ async getPrComments(params, _oktokit) {
1662
+ if (!_oktokit && !this.accessToken || !this.url) {
1663
+ throw new Error("cannot get Pr Comments without access token or url");
1664
+ }
1665
+ const oktokit = _oktokit || this.oktokit;
1666
+ const { owner, repo } = parseOwnerAndRepo(this.url);
1667
+ return getPrComments(oktokit, {
1668
+ per_page: 100,
1669
+ ...params,
1670
+ owner,
1671
+ repo
1672
+ });
1673
+ }
1631
1674
  async getRepoList() {
1632
1675
  if (!this.accessToken) {
1633
1676
  console.error("no access token");
@@ -1801,7 +1844,6 @@ var EnvVariablesZod2 = z5.object({
1801
1844
  GITLAB_API_TOKEN: z5.string().optional()
1802
1845
  });
1803
1846
  var { GITLAB_API_TOKEN } = EnvVariablesZod2.parse(process.env);
1804
- var gitlabUrlRegex = /^http[s]?:\/\/[^/\s]+\/(([^/.\s]+[/])+)([^/.\s]+)(\.git)?(\/)?$/i;
1805
1847
  function getGitBeaker(options) {
1806
1848
  const token = options?.gitlabAuthToken ?? GITLAB_API_TOKEN ?? "";
1807
1849
  if (token?.startsWith("glpat-") || token === "") {
@@ -1934,121 +1976,709 @@ async function getGitlabBranchList({
1934
1976
  } catch (e) {
1935
1977
  return [];
1936
1978
  }
1937
- }
1938
- async function createMergeRequest(options) {
1939
- const { projectPath } = parseOwnerAndRepo2(options.repoUrl);
1940
- const api = getGitBeaker({ gitlabAuthToken: options.accessToken });
1941
- const res = await api.MergeRequests.create(
1942
- projectPath,
1943
- options.sourceBranchName,
1944
- options.targetBranchName,
1945
- options.title,
1979
+ }
1980
+ async function createMergeRequest(options) {
1981
+ const { projectPath } = parseOwnerAndRepo2(options.repoUrl);
1982
+ const api = getGitBeaker({ gitlabAuthToken: options.accessToken });
1983
+ const res = await api.MergeRequests.create(
1984
+ projectPath,
1985
+ options.sourceBranchName,
1986
+ options.targetBranchName,
1987
+ options.title,
1988
+ {
1989
+ description: options.body
1990
+ }
1991
+ );
1992
+ return res.iid;
1993
+ }
1994
+ async function getGitlabRepoDefaultBranch(repoUrl, options) {
1995
+ const api = getGitBeaker({ gitlabAuthToken: options?.gitlabAuthToken });
1996
+ const { projectPath } = parseOwnerAndRepo2(repoUrl);
1997
+ const project = await api.Projects.show(projectPath);
1998
+ if (!project.default_branch) {
1999
+ throw new Error("no default branch");
2000
+ }
2001
+ return project.default_branch;
2002
+ }
2003
+ async function getGitlabReferenceData({ ref, gitlabUrl }, options) {
2004
+ const { projectPath } = parseOwnerAndRepo2(gitlabUrl);
2005
+ const api = getGitBeaker({ gitlabAuthToken: options?.gitlabAuthToken });
2006
+ const results = await Promise.allSettled([
2007
+ (async () => {
2008
+ const res = await api.Branches.show(projectPath, ref);
2009
+ return {
2010
+ sha: res.commit.id,
2011
+ type: "BRANCH" /* BRANCH */,
2012
+ date: res.commit.committed_date ? new Date(res.commit.committed_date) : void 0
2013
+ };
2014
+ })(),
2015
+ (async () => {
2016
+ const res = await api.Commits.show(projectPath, ref);
2017
+ return {
2018
+ sha: res.id,
2019
+ type: "COMMIT" /* COMMIT */,
2020
+ date: res.committed_date ? new Date(res.committed_date) : void 0
2021
+ };
2022
+ })(),
2023
+ (async () => {
2024
+ const res = await api.Tags.show(projectPath, ref);
2025
+ return {
2026
+ sha: res.commit.id,
2027
+ type: "TAG" /* TAG */,
2028
+ date: res.commit.committed_date ? new Date(res.commit.committed_date) : void 0
2029
+ };
2030
+ })()
2031
+ ]);
2032
+ const [branchRes, commitRes, tagRes] = results;
2033
+ if (tagRes.status === "fulfilled") {
2034
+ return tagRes.value;
2035
+ }
2036
+ if (branchRes.status === "fulfilled") {
2037
+ return branchRes.value;
2038
+ }
2039
+ if (commitRes.status === "fulfilled") {
2040
+ return commitRes.value;
2041
+ }
2042
+ throw new RefNotFoundError(`ref: ${ref} does not exist`);
2043
+ }
2044
+ function parseOwnerAndRepo2(gitlabUrl) {
2045
+ gitlabUrl = removeTrailingSlash2(gitlabUrl);
2046
+ const parsingResult = parseScmURL(gitlabUrl);
2047
+ if (!parsingResult || parsingResult.hostname !== "gitlab.com") {
2048
+ throw new InvalidUrlPatternError(`invalid gitlab repo Url ${gitlabUrl}`);
2049
+ }
2050
+ const { organization, repoName, projectPath } = parsingResult;
2051
+ return { owner: organization, repo: repoName, projectPath };
2052
+ }
2053
+ async function getGitlabBlameRanges({ ref, gitlabUrl, path: path8 }, options) {
2054
+ const { projectPath } = parseOwnerAndRepo2(gitlabUrl);
2055
+ const api = getGitBeaker({ gitlabAuthToken: options?.gitlabAuthToken });
2056
+ const resp = await api.RepositoryFiles.allFileBlames(projectPath, path8, ref);
2057
+ let lineNumber = 1;
2058
+ return resp.filter((range) => range.lines).map((range) => {
2059
+ const oldLineNumber = lineNumber;
2060
+ if (!range.lines) {
2061
+ throw new Error("range.lines should not be undefined");
2062
+ }
2063
+ lineNumber += range.lines.length;
2064
+ return {
2065
+ startingLine: oldLineNumber,
2066
+ endingLine: lineNumber - 1,
2067
+ login: range.commit.author_email,
2068
+ email: range.commit.author_email,
2069
+ name: range.commit.author_name
2070
+ };
2071
+ });
2072
+ }
2073
+ var GitlabAuthResultZ = z5.object({
2074
+ access_token: z5.string(),
2075
+ token_type: z5.string(),
2076
+ refresh_token: z5.string()
2077
+ });
2078
+
2079
+ // src/features/analysis/types.ts
2080
+ import { z as z6 } from "zod";
2081
+ var VulReportLocationZ = z6.object({
2082
+ physicalLocation: z6.object({
2083
+ artifactLocation: z6.object({
2084
+ uri: z6.string(),
2085
+ uriBaseId: z6.string(),
2086
+ index: z6.number()
2087
+ }),
2088
+ region: z6.object({
2089
+ startLine: z6.number(),
2090
+ startColumn: z6.number(),
2091
+ endColumn: z6.number()
2092
+ })
2093
+ })
2094
+ });
2095
+
2096
+ // src/features/analysis/utils/get_issue_type.ts
2097
+ var getIssueType = (issueType) => {
2098
+ switch (issueType) {
2099
+ case "SQL_Injection" /* SqlInjection */:
2100
+ return "SQL Injection";
2101
+ case "CMDi_relative_path_command" /* CmDiRelativePathCommand */:
2102
+ return "Relative Path Command Injection";
2103
+ case "CMDi" /* CmDi */:
2104
+ return "Command Injection";
2105
+ case "XXE" /* Xxe */:
2106
+ return "XXE";
2107
+ case "XSS" /* Xss */:
2108
+ return "XSS";
2109
+ case "PT" /* Pt */:
2110
+ return "Path Traversal";
2111
+ case "INSECURE_RANDOMNESS" /* InsecureRandomness */:
2112
+ return "Insecure Randomness";
2113
+ case "SSRF" /* Ssrf */:
2114
+ return "Server Side Request Forgery";
2115
+ case "TYPE_CONFUSION" /* TypeConfusion */:
2116
+ return "Type Confusion";
2117
+ case "REGEX_INJECTION" /* RegexInjection */:
2118
+ return "Regular Expression Injection";
2119
+ case "INCOMPLETE_URL_SANITIZATION" /* IncompleteUrlSanitization */:
2120
+ return "Incomplete URL Sanitization";
2121
+ case "LOG_FORGING" /* LogForging */:
2122
+ return "Log Forging";
2123
+ case "MISSING_CHECK_AGAINST_NULL" /* MissingCheckAgainstNull */:
2124
+ return "Missing Check against Null";
2125
+ case "PASSWORD_IN_COMMENT" /* PasswordInComment */:
2126
+ return "Password in Comment";
2127
+ case "OVERLY_BROAD_CATCH" /* OverlyBroadCatch */:
2128
+ return "Poor Error Handling: Overly Broad Catch";
2129
+ case "USE_OF_SYSTEM_OUTPUT_STREAM" /* UseOfSystemOutputStream */:
2130
+ return "Use of System.out/System.err";
2131
+ case "DANGEROUS_FUNCTION_OVERFLOW" /* DangerousFunctionOverflow */:
2132
+ return "Use of dangerous function";
2133
+ case "DOS_STRING_BUILDER" /* DosStringBuilder */:
2134
+ return "Denial of Service: StringBuilder";
2135
+ case "OPEN_REDIRECT" /* OpenRedirect */:
2136
+ return "Open Redirect";
2137
+ case "WEAK_XML_SCHEMA_UNBOUNDED_OCCURRENCES" /* WeakXmlSchemaUnboundedOccurrences */:
2138
+ return "Weak XML Schema: Unbounded Occurrences";
2139
+ case "SYSTEM_INFORMATION_LEAK" /* SystemInformationLeak */:
2140
+ return "System Information Leak";
2141
+ case "HTTP_RESPONSE_SPLITTING" /* HttpResponseSplitting */:
2142
+ return "HTTP response splitting";
2143
+ case "HTTP_ONLY_COOKIE" /* HttpOnlyCookie */:
2144
+ return "Cookie is not HttpOnly";
2145
+ case "INSECURE_COOKIE" /* InsecureCookie */:
2146
+ return "Insecure Cookie";
2147
+ default: {
2148
+ return issueType ? issueType.replaceAll("_", " ") : "Other";
2149
+ }
2150
+ }
2151
+ };
2152
+
2153
+ // src/features/analysis/utils/index.ts
2154
+ function getFixUrl({
2155
+ fixId,
2156
+ projectId,
2157
+ organizationId,
2158
+ analysisId
2159
+ }) {
2160
+ return `${WEB_APP_URL}/organization/${organizationId}/project/${projectId}/report/${analysisId}/fix/${fixId}`;
2161
+ }
2162
+ function getCommitUrl({
2163
+ fixId,
2164
+ projectId,
2165
+ organizationId,
2166
+ analysisId,
2167
+ redirectUrl
2168
+ }) {
2169
+ return `${getFixUrl({
2170
+ fixId,
2171
+ projectId,
2172
+ organizationId,
2173
+ analysisId
2174
+ })}/commit?redirect_url=${encodeURIComponent(redirectUrl)}`;
2175
+ }
2176
+
2177
+ // src/features/analysis/handle_finished_analysis.ts
2178
+ var debug4 = Debug4("mobbdev:handle-finished-analysis");
2179
+ var MOBB_ICON_IMG = "![image](https://github.com/yhaggai/GitHub-Fixer-Demo/assets/1255845/30f566df-6544-4612-929e-2ee5e8b9d345)";
2180
+ var COMMIT_FIX_SVG = `https://felt-laptop-20190711103614-deployment.s3.us-east-1.amazonaws.com/commit-button.svg`;
2181
+ var commitFixButton = (commitUrl) => `<a href="${commitUrl}"><img src=${COMMIT_FIX_SVG}></a>`;
2182
+ function scannerToFriendlyString(scanner) {
2183
+ switch (scanner) {
2184
+ case "checkmarx":
2185
+ return "Checkmarx";
2186
+ case "codeql":
2187
+ return "CodeQL";
2188
+ case "fortify":
2189
+ return "Fortify";
2190
+ case "snyk":
2191
+ return "Snyk";
2192
+ }
2193
+ }
2194
+ async function handleFinishedAnalysis({
2195
+ analysisId,
2196
+ scm: _scm,
2197
+ gqlClient,
2198
+ githubActionOctokit,
2199
+ scanner
2200
+ }) {
2201
+ if (_scm instanceof GithubSCMLib === false) {
2202
+ return;
2203
+ }
2204
+ const scm = _scm;
2205
+ const res = await gqlClient.getAnalysis(analysisId);
2206
+ const comments = await scm.getPrComments({}, githubActionOctokit);
2207
+ await Promise.all(
2208
+ comments.data.filter((comment) => {
2209
+ return comment.body.includes("fix by Mobb is ready");
2210
+ }).map((comment) => {
2211
+ try {
2212
+ return scm.deleteComment(
2213
+ { comment_id: comment.id },
2214
+ githubActionOctokit
2215
+ );
2216
+ } catch (e) {
2217
+ debug4("delete comment failed %s", e);
2218
+ return Promise.resolve();
2219
+ }
2220
+ })
2221
+ );
2222
+ const {
2223
+ vulnerabilityReport: {
2224
+ file: {
2225
+ signedFile: { url: vulReportUrl }
2226
+ }
2227
+ },
2228
+ repo: { commitSha, pullRequest }
2229
+ } = res.analysis;
2230
+ const {
2231
+ projectId,
2232
+ project: { organizationId }
2233
+ } = res.analysis.vulnerabilityReport;
2234
+ const vulReportRes = await fetch(vulReportUrl);
2235
+ const vulReport = await vulReportRes.json();
2236
+ return Promise.all(
2237
+ res.analysis.fixes.map((fix) => {
2238
+ const [vulnerabilityReportIssue] = fix.vulnerabilityReportIssues;
2239
+ const issueIndex = parseInt(
2240
+ z7.string().parse(vulnerabilityReportIssue?.vendorIssueId)
2241
+ );
2242
+ const results = vulReport.runs[0]?.results || [];
2243
+ const ruleId = results[issueIndex]?.ruleId;
2244
+ const location = VulReportLocationZ.parse(
2245
+ results[issueIndex]?.locations[0]
2246
+ );
2247
+ const { uri: filePath } = location.physicalLocation.artifactLocation;
2248
+ const { startLine, startColumn, endColumn } = location.physicalLocation.region;
2249
+ const fixLocation = {
2250
+ filePath,
2251
+ startLine,
2252
+ startColumn,
2253
+ endColumn,
2254
+ ruleId
2255
+ };
2256
+ return {
2257
+ fix,
2258
+ fixLocation
2259
+ };
2260
+ }).map(async ({ fix, fixLocation }) => {
2261
+ const { filePath, startLine } = fixLocation;
2262
+ const getFixContent = await gqlClient.getFix(fix.id);
2263
+ const {
2264
+ fix_by_pk: {
2265
+ patchAndQuestions: { patch }
2266
+ }
2267
+ } = getFixContent;
2268
+ const commentRes = await scm.postPrComment(
2269
+ {
2270
+ body: "empty",
2271
+ pull_number: pullRequest,
2272
+ commit_id: commitSha,
2273
+ path: filePath,
2274
+ line: startLine
2275
+ },
2276
+ githubActionOctokit
2277
+ );
2278
+ const commitUrl = getCommitUrl({
2279
+ fixId: fix.id,
2280
+ projectId,
2281
+ analysisId,
2282
+ organizationId,
2283
+ redirectUrl: commentRes.data.html_url
2284
+ });
2285
+ const fixUrl = getFixUrl({
2286
+ fixId: fix.id,
2287
+ projectId,
2288
+ analysisId,
2289
+ organizationId
2290
+ });
2291
+ const scanerString = scannerToFriendlyString(scanner);
2292
+ const issueType = getIssueType(fix.issueType);
2293
+ const title = `# ${MOBB_ICON_IMG} ${issueType} fix by Mobb is ready`;
2294
+ const subTitle = `### Apply the following code change to fix ${issueType} issue detected by ${scanerString}:`;
2295
+ const diff = `\`\`\`diff
2296
+ ${patch}
2297
+ \`\`\``;
2298
+ const fixPageLink = `[Learn more and fine tune the fix](${fixUrl})`;
2299
+ await scm.updatePrComment(
2300
+ {
2301
+ body: `${title}
2302
+ ${subTitle}
2303
+ ${diff}
2304
+ ${commitFixButton(
2305
+ commitUrl
2306
+ )}
2307
+ ${fixPageLink}`,
2308
+ comment_id: commentRes.data.id
2309
+ },
2310
+ githubActionOctokit
2311
+ );
2312
+ })
2313
+ );
2314
+ }
2315
+
2316
+ // src/features/analysis/pack.ts
2317
+ import fs2 from "node:fs";
2318
+ import path4 from "node:path";
2319
+ import AdmZip from "adm-zip";
2320
+ import Debug5 from "debug";
2321
+ import { globby } from "globby";
2322
+ import { isBinary } from "istextorbinary";
2323
+ var debug5 = Debug5("mobbdev:pack");
2324
+ var MAX_FILE_SIZE = 1024 * 1024 * 5;
2325
+ function endsWithAny(str, suffixes) {
2326
+ return suffixes.some(function(suffix) {
2327
+ return str.endsWith(suffix);
2328
+ });
2329
+ }
2330
+ async function pack(srcDirPath, vulnFiles) {
2331
+ debug5("pack folder %s", srcDirPath);
2332
+ const filepaths = await globby("**", {
2333
+ gitignore: true,
2334
+ onlyFiles: true,
2335
+ cwd: srcDirPath,
2336
+ followSymbolicLinks: false
2337
+ });
2338
+ debug5("files found %d", filepaths.length);
2339
+ const zip = new AdmZip();
2340
+ debug5("compressing files");
2341
+ for (const filepath of filepaths) {
2342
+ const absFilepath = path4.join(srcDirPath, filepath.toString());
2343
+ if (!endsWithAny(
2344
+ absFilepath.toString().replaceAll(path4.win32.sep, path4.posix.sep),
2345
+ vulnFiles
2346
+ )) {
2347
+ debug5("ignoring %s because it is not a vulnerability file", filepath);
2348
+ continue;
2349
+ }
2350
+ if (fs2.lstatSync(absFilepath).size > MAX_FILE_SIZE) {
2351
+ debug5("ignoring %s because the size is > 5MB", filepath);
2352
+ continue;
2353
+ }
2354
+ const data = fs2.readFileSync(absFilepath);
2355
+ if (isBinary(null, data)) {
2356
+ debug5("ignoring %s because is seems to be a binary file", filepath);
2357
+ continue;
2358
+ }
2359
+ zip.addFile(filepath.toString(), data);
2360
+ }
2361
+ debug5("get zip file buffer");
2362
+ return zip.toBuffer();
2363
+ }
2364
+
2365
+ // src/features/analysis/prompts.ts
2366
+ import inquirer from "inquirer";
2367
+ import { createSpinner } from "nanospinner";
2368
+ var scannerChoices = [
2369
+ { name: "Snyk", value: SCANNERS.Snyk },
2370
+ { name: "Checkmarx", value: SCANNERS.Checkmarx },
2371
+ { name: "Codeql", value: SCANNERS.Codeql },
2372
+ { name: "Fortify", value: SCANNERS.Fortify }
2373
+ ];
2374
+ async function choseScanner() {
2375
+ const { scanner } = await inquirer.prompt({
2376
+ name: "scanner",
2377
+ message: "Choose a scanner you wish to use to scan your code",
2378
+ type: "list",
2379
+ choices: scannerChoices
2380
+ });
2381
+ return scanner;
2382
+ }
2383
+ async function tryCheckmarxConfiguarationAgain() {
2384
+ console.log(
2385
+ "\u{1F513} Oops, seems like checkmarx does not accept the current configuration"
2386
+ );
2387
+ const { confirmCheckmarxRetryConfigrations } = await inquirer.prompt({
2388
+ name: "confirmCheckmarxRetryConfigrations",
2389
+ type: "confirm",
2390
+ message: "Would like to try to configure them again? ",
2391
+ default: true
2392
+ });
2393
+ return confirmCheckmarxRetryConfigrations;
2394
+ }
2395
+ async function startCheckmarxConfigationPrompt() {
2396
+ const checkmarxConfigreSpinner = createSpinner(
2397
+ "\u{1F513} Checkmarx needs to be configured before we start, press any key to continue"
2398
+ ).start();
2399
+ await keypress();
2400
+ checkmarxConfigreSpinner.success();
2401
+ }
2402
+ async function scmIntegrationPrompt(scmName) {
2403
+ const answers = await inquirer.prompt({
2404
+ name: "scmConfirm",
2405
+ type: "confirm",
2406
+ message: `It seems we don't have access to the repo, do you want to grant access to your ${scmName} account?`,
2407
+ default: true
2408
+ });
2409
+ return answers.scmConfirm;
2410
+ }
2411
+ async function mobbAnalysisPrompt() {
2412
+ const spinner = createSpinner().start();
2413
+ spinner.update({ text: "Hit any key to view available fixes" });
2414
+ await keypress();
2415
+ return spinner.success();
2416
+ }
2417
+ async function snykArticlePrompt() {
2418
+ const { snykArticleConfirm } = await inquirer.prompt({
2419
+ name: "snykArticleConfirm",
2420
+ type: "confirm",
2421
+ message: "Do you want to be taken to the relevant Snyk's online article?",
2422
+ default: true
2423
+ });
2424
+ return snykArticleConfirm;
2425
+ }
2426
+
2427
+ // src/features/analysis/scanners/checkmarx.ts
2428
+ import { createRequire } from "node:module";
2429
+
2430
+ // src/post_install/constants.mjs
2431
+ var cxOperatingSystemSupportMessage = `Your operating system does not support checkmarx.
2432
+ You can see the list of supported operating systems here: https://github.com/Checkmarx/ast-cli#releases`;
2433
+
2434
+ // src/utils/child_process.ts
2435
+ import cp from "node:child_process";
2436
+ import Debug6 from "debug";
2437
+ import * as process2 from "process";
2438
+ import supportsColor from "supports-color";
2439
+ var { stdout: stdout2 } = supportsColor;
2440
+ function createFork({ args, processPath, name }, options) {
2441
+ const child = cp.fork(processPath, args, {
2442
+ stdio: ["inherit", "pipe", "pipe", "ipc"],
2443
+ env: { FORCE_COLOR: stdout2 ? "1" : "0" }
2444
+ });
2445
+ return createChildProcess({ childProcess: child, name }, options);
2446
+ }
2447
+ function createSpwan({ args, processPath, name }, options) {
2448
+ const child = cp.spawn(processPath, args, {
2449
+ stdio: ["inherit", "pipe", "pipe", "ipc"],
2450
+ env: { FORCE_COLOR: stdout2 ? "1" : "0" }
2451
+ });
2452
+ return createChildProcess({ childProcess: child, name }, options);
2453
+ }
2454
+ function createChildProcess({ childProcess, name }, options) {
2455
+ const debug10 = Debug6(`mobbdev:${name}`);
2456
+ const { display } = options;
2457
+ return new Promise((resolve, reject) => {
2458
+ let out = "";
2459
+ const onData = (chunk) => {
2460
+ debug10(`chunk received from ${name} std ${chunk}`);
2461
+ out += chunk;
2462
+ };
2463
+ if (!childProcess || !childProcess?.stdout || !childProcess?.stderr) {
2464
+ debug10(`unable to fork ${name}`);
2465
+ reject(new Error(`unable to fork ${name}`));
2466
+ }
2467
+ childProcess.stdout?.on("data", onData);
2468
+ childProcess.stderr?.on("data", onData);
2469
+ if (display) {
2470
+ childProcess.stdout?.pipe(process2.stdout);
2471
+ childProcess.stderr?.pipe(process2.stderr);
2472
+ }
2473
+ childProcess.on("exit", (code) => {
2474
+ debug10(`${name} exit code ${code}`);
2475
+ resolve({ message: out, code });
2476
+ });
2477
+ childProcess.on("error", (err) => {
2478
+ debug10(`${name} error %o`, err);
2479
+ reject(err);
2480
+ });
2481
+ });
2482
+ }
2483
+
2484
+ // src/features/analysis/scanners/checkmarx.ts
2485
+ import chalk2 from "chalk";
2486
+ import Debug7 from "debug";
2487
+ import { existsSync } from "fs";
2488
+ import { createSpinner as createSpinner2 } from "nanospinner";
2489
+ import { type } from "os";
2490
+ import path5 from "path";
2491
+ var debug6 = Debug7("mobbdev:checkmarx");
2492
+ var require2 = createRequire(import.meta.url);
2493
+ var getCheckmarxPath = () => {
2494
+ const os3 = type();
2495
+ const cxFileName = os3 === "Windows_NT" ? "cx.exe" : "cx";
2496
+ try {
2497
+ return require2.resolve(`.bin/${cxFileName}`);
2498
+ } catch (e) {
2499
+ throw new CliError(cxOperatingSystemSupportMessage);
2500
+ }
2501
+ };
2502
+ var getCheckmarxCommandArgs = ({
2503
+ repoPath,
2504
+ branch,
2505
+ fileName,
2506
+ filePath,
2507
+ projectName
2508
+ }) => [
2509
+ "--project-name",
2510
+ projectName,
2511
+ "-s",
2512
+ repoPath,
2513
+ "--branch",
2514
+ branch,
2515
+ "--scan-types",
2516
+ "sast",
2517
+ "--output-path",
2518
+ filePath,
2519
+ "--output-name",
2520
+ fileName,
2521
+ "--report-format",
2522
+ "json"
2523
+ ];
2524
+ var VALIDATE_COMMAND = ["auth", "validate"];
2525
+ var CONFIGURE_COMMAND = ["configure"];
2526
+ var SCAN_COMMAND = ["scan", "create"];
2527
+ var CHECKMARX_SUCCESS_CODE = 0;
2528
+ function validateCheckmarxInstallation() {
2529
+ existsSync(getCheckmarxPath());
2530
+ }
2531
+ async function forkCheckmarx(args, { display }) {
2532
+ debug6("fork checkmarx with args %o %s", args.join(" "), display);
2533
+ return createSpwan(
2534
+ { args, processPath: getCheckmarxPath(), name: "checkmarx" },
2535
+ { display }
2536
+ );
2537
+ }
2538
+ async function getCheckmarxReport({ reportPath, repositoryRoot, branch, projectName }, { skipPrompts = false }) {
2539
+ debug6("get checkmarx report start %s %s", reportPath, repositoryRoot);
2540
+ const { code: loginCode } = await forkCheckmarx(VALIDATE_COMMAND, {
2541
+ display: false
2542
+ });
2543
+ if (loginCode !== CHECKMARX_SUCCESS_CODE) {
2544
+ if (skipPrompts) {
2545
+ await throwCheckmarxConfigError();
2546
+ }
2547
+ await startCheckmarxConfigationPrompt();
2548
+ await validateCheckamxCredentials();
2549
+ }
2550
+ const extension = path5.extname(reportPath);
2551
+ const filePath = path5.dirname(reportPath);
2552
+ const fileName = path5.basename(reportPath, extension);
2553
+ const checkmarxCommandArgs = getCheckmarxCommandArgs({
2554
+ repoPath: repositoryRoot,
2555
+ branch,
2556
+ filePath,
2557
+ fileName,
2558
+ projectName
2559
+ });
2560
+ console.log("\u280B \u{1F50D} Initiating Checkmarx Scan ");
2561
+ const { code: scanCode } = await forkCheckmarx(
2562
+ [...SCAN_COMMAND, ...checkmarxCommandArgs],
1946
2563
  {
1947
- description: options.body
2564
+ display: true
1948
2565
  }
1949
2566
  );
1950
- return res.iid;
1951
- }
1952
- async function getGitlabRepoDefaultBranch(repoUrl, options) {
1953
- const api = getGitBeaker({ gitlabAuthToken: options?.gitlabAuthToken });
1954
- const { projectPath } = parseOwnerAndRepo2(repoUrl);
1955
- const project = await api.Projects.show(projectPath);
1956
- if (!project.default_branch) {
1957
- throw new Error("no default branch");
2567
+ if (scanCode !== CHECKMARX_SUCCESS_CODE) {
2568
+ createSpinner2("\u{1F50D} Something went wrong with the checkmarx scan").start().error();
2569
+ throw new CliError();
1958
2570
  }
1959
- return project.default_branch;
2571
+ await createSpinner2("\u{1F50D} Checkmarx Scan completed").start().success();
2572
+ return true;
1960
2573
  }
1961
- async function getGitlabReferenceData({ ref, gitlabUrl }, options) {
1962
- const { projectPath } = parseOwnerAndRepo2(gitlabUrl);
1963
- const api = getGitBeaker({ gitlabAuthToken: options?.gitlabAuthToken });
1964
- const results = await Promise.allSettled([
1965
- (async () => {
1966
- const res = await api.Branches.show(projectPath, ref);
1967
- return {
1968
- sha: res.commit.id,
1969
- type: "BRANCH" /* BRANCH */,
1970
- date: res.commit.committed_date ? new Date(res.commit.committed_date) : void 0
1971
- };
1972
- })(),
1973
- (async () => {
1974
- const res = await api.Commits.show(projectPath, ref);
1975
- return {
1976
- sha: res.id,
1977
- type: "COMMIT" /* COMMIT */,
1978
- date: res.committed_date ? new Date(res.committed_date) : void 0
1979
- };
1980
- })(),
1981
- (async () => {
1982
- const res = await api.Tags.show(projectPath, ref);
1983
- return {
1984
- sha: res.commit.id,
1985
- type: "TAG" /* TAG */,
1986
- date: res.commit.committed_date ? new Date(res.commit.committed_date) : void 0
1987
- };
1988
- })()
1989
- ]);
1990
- const [branchRes, commitRes, tagRes] = results;
1991
- if (tagRes.status === "fulfilled") {
1992
- return tagRes.value;
1993
- }
1994
- if (branchRes.status === "fulfilled") {
1995
- return branchRes.value;
1996
- }
1997
- if (commitRes.status === "fulfilled") {
1998
- return commitRes.value;
1999
- }
2000
- throw new RefNotFoundError(`ref: ${ref} does not exist`);
2574
+ async function throwCheckmarxConfigError() {
2575
+ await createSpinner2("\u{1F513} Checkmarx is not configued correctly").start().error();
2576
+ throw new CliError(
2577
+ `Checkmarx is not configued correctly
2578
+ you can configure it by using the ${chalk2.bold(
2579
+ "cx configure"
2580
+ )} command`
2581
+ );
2001
2582
  }
2002
- function parseOwnerAndRepo2(gitlabUrl) {
2003
- gitlabUrl = removeTrailingSlash2(gitlabUrl);
2004
- if (!gitlabUrlRegex.test(gitlabUrl)) {
2005
- throw new InvalidUrlPatternError(`invalid gitlab repo Url ${gitlabUrl}`);
2583
+ async function validateCheckamxCredentials() {
2584
+ console.log(`
2585
+ Here's a suggestion for checkmarx configuation:
2586
+ ${chalk2.bold("AST Base URI:")} https://ast.checkmarx.net
2587
+ ${chalk2.bold("AST Base Auth URI (IAM):")} https://iam.checkmarx.net
2588
+ `);
2589
+ await forkCheckmarx(CONFIGURE_COMMAND, { display: true });
2590
+ const { code: loginCode } = await forkCheckmarx(VALIDATE_COMMAND, {
2591
+ display: false
2592
+ });
2593
+ if (loginCode !== CHECKMARX_SUCCESS_CODE) {
2594
+ const tryAgain = await tryCheckmarxConfiguarationAgain();
2595
+ if (!tryAgain) {
2596
+ await throwCheckmarxConfigError();
2597
+ }
2598
+ if (await tryCheckmarxConfiguarationAgain()) {
2599
+ validateCheckamxCredentials();
2600
+ }
2006
2601
  }
2007
- const groups = gitlabUrl.split(gitlabUrlRegex).filter((res) => res);
2008
- const owner = groups[0]?.split("/")[0];
2009
- const repo = groups[2];
2010
- const projectPath = `${groups[0]}${repo}`;
2011
- return { owner, repo, projectPath };
2602
+ await createSpinner2("\u{1F513} Checkmarx configured successfully!").start().success();
2012
2603
  }
2013
- async function getGitlabBlameRanges({ ref, gitlabUrl, path: path8 }, options) {
2014
- const { projectPath } = parseOwnerAndRepo2(gitlabUrl);
2015
- const api = getGitBeaker({ gitlabAuthToken: options?.gitlabAuthToken });
2016
- const resp = await api.RepositoryFiles.allFileBlames(projectPath, path8, ref);
2017
- let lineNumber = 1;
2018
- return resp.filter((range) => range.lines).map((range) => {
2019
- const oldLineNumber = lineNumber;
2020
- if (!range.lines) {
2021
- throw new Error("range.lines should not be undefined");
2604
+
2605
+ // src/features/analysis/scanners/snyk.ts
2606
+ import { createRequire as createRequire2 } from "node:module";
2607
+ import chalk3 from "chalk";
2608
+ import Debug8 from "debug";
2609
+ import { createSpinner as createSpinner3 } from "nanospinner";
2610
+ import open from "open";
2611
+ var debug7 = Debug8("mobbdev:snyk");
2612
+ var require3 = createRequire2(import.meta.url);
2613
+ var SNYK_PATH = require3.resolve("snyk/bin/snyk");
2614
+ var SNYK_ARTICLE_URL = "https://docs.snyk.io/scan-application-code/snyk-code/getting-started-with-snyk-code/activating-snyk-code-using-the-web-ui/step-1-enabling-the-snyk-code-option";
2615
+ debug7("snyk executable path %s", SNYK_PATH);
2616
+ async function forkSnyk(args, { display }) {
2617
+ debug7("fork snyk with args %o %s", args, display);
2618
+ return createFork(
2619
+ { args, processPath: SNYK_PATH, name: "checkmarx" },
2620
+ { display }
2621
+ );
2622
+ }
2623
+ async function getSnykReport(reportPath, repoRoot, { skipPrompts = false }) {
2624
+ debug7("get snyk report start %s %s", reportPath, repoRoot);
2625
+ const config3 = await forkSnyk(["config"], { display: false });
2626
+ const { message: configMessage } = config3;
2627
+ if (!configMessage.includes("api: ")) {
2628
+ const snykLoginSpinner = createSpinner3().start();
2629
+ if (!skipPrompts) {
2630
+ snykLoginSpinner.update({
2631
+ text: "\u{1F513} Login to Snyk is required, press any key to continue"
2632
+ });
2633
+ await keypress();
2022
2634
  }
2023
- lineNumber += range.lines.length;
2024
- return {
2025
- startingLine: oldLineNumber,
2026
- endingLine: lineNumber - 1,
2027
- login: range.commit.author_email,
2028
- email: range.commit.author_email,
2029
- name: range.commit.author_name
2030
- };
2031
- });
2635
+ snykLoginSpinner.update({
2636
+ text: "\u{1F513} Waiting for Snyk login to complete"
2637
+ });
2638
+ debug7("no token in the config %s", config3);
2639
+ await forkSnyk(["auth"], { display: true });
2640
+ snykLoginSpinner.success({ text: "\u{1F513} Login to Snyk Successful" });
2641
+ }
2642
+ const snykSpinner = createSpinner3("\u{1F50D} Scanning your repo with Snyk ").start();
2643
+ const { message: scanOutput } = await forkSnyk(
2644
+ ["code", "test", `--sarif-file-output=${reportPath}`, repoRoot],
2645
+ { display: true }
2646
+ );
2647
+ if (scanOutput.includes(
2648
+ "Snyk Code is not supported for org: enable in Settings > Snyk Code"
2649
+ )) {
2650
+ debug7("snyk code is not enabled %s", scanOutput);
2651
+ snykSpinner.error({ text: "\u{1F50D} Snyk configuration needed" });
2652
+ const answer = await snykArticlePrompt();
2653
+ debug7("answer %s", answer);
2654
+ if (answer) {
2655
+ debug7("opening the browser");
2656
+ await open(SNYK_ARTICLE_URL);
2657
+ }
2658
+ console.log(
2659
+ chalk3.bgBlue(
2660
+ "\nPlease enable Snyk Code in your Snyk account and try again."
2661
+ )
2662
+ );
2663
+ throw Error("snyk is not enbabled");
2664
+ }
2665
+ snykSpinner.success({ text: "\u{1F50D} Snyk code scan completed" });
2666
+ return true;
2032
2667
  }
2033
- var GitlabAuthResultZ = z5.object({
2034
- access_token: z5.string(),
2035
- token_type: z5.string(),
2036
- refresh_token: z5.string()
2037
- });
2038
2668
 
2039
2669
  // src/features/analysis/upload-file.ts
2040
- import Debug8 from "debug";
2670
+ import Debug9 from "debug";
2041
2671
  import fetch2, { File, fileFrom, FormData } from "node-fetch";
2042
- var debug7 = Debug8("mobbdev:upload-file");
2672
+ var debug8 = Debug9("mobbdev:upload-file");
2043
2673
  async function uploadFile({
2044
2674
  file,
2045
2675
  url,
2046
2676
  uploadKey,
2047
2677
  uploadFields
2048
2678
  }) {
2049
- debug7("upload file start %s", url);
2050
- debug7("upload fields %o", uploadFields);
2051
- debug7("upload key %s", uploadKey);
2679
+ debug8("upload file start %s", url);
2680
+ debug8("upload fields %o", uploadFields);
2681
+ debug8("upload key %s", uploadKey);
2052
2682
  const form = new FormData();
2053
2683
  Object.entries(uploadFields).forEach(([key, value]) => {
2054
2684
  form.append(key, value);
@@ -2057,10 +2687,10 @@ async function uploadFile({
2057
2687
  form.append("key", uploadKey);
2058
2688
  }
2059
2689
  if (typeof file === "string") {
2060
- debug7("upload file from path %s", file);
2690
+ debug8("upload file from path %s", file);
2061
2691
  form.append("file", await fileFrom(file));
2062
2692
  } else {
2063
- debug7("upload file from buffer");
2693
+ debug8("upload file from buffer");
2064
2694
  form.append("file", new File([file], "file"));
2065
2695
  }
2066
2696
  const response = await fetch2(url, {
@@ -2068,10 +2698,10 @@ async function uploadFile({
2068
2698
  body: form
2069
2699
  });
2070
2700
  if (!response.ok) {
2071
- debug7("error from S3 %s %s", response.body, response.status);
2701
+ debug8("error from S3 %s %s", response.body, response.status);
2072
2702
  throw new Error(`Failed to upload the file: ${response.status}`);
2073
2703
  }
2074
- debug7("upload file done");
2704
+ debug8("upload file done");
2075
2705
  }
2076
2706
 
2077
2707
  // src/features/analysis/index.ts
@@ -2088,7 +2718,7 @@ async function downloadRepo({
2088
2718
  }) {
2089
2719
  const { createSpinner: createSpinner4 } = Spinner2({ ci });
2090
2720
  const repoSpinner = createSpinner4("\u{1F4BE} Downloading Repo").start();
2091
- debug8("download repo %s %s %s", repoUrl, dirname);
2721
+ debug9("download repo %s %s %s", repoUrl, dirname);
2092
2722
  const zipFilePath = path6.join(dirname, "repo.zip");
2093
2723
  const response = await fetch3(downloadUrl, {
2094
2724
  method: "GET",
@@ -2097,7 +2727,7 @@ async function downloadRepo({
2097
2727
  }
2098
2728
  });
2099
2729
  if (!response.ok) {
2100
- debug8("SCM zipball request failed %s %s", response.body, response.status);
2730
+ debug9("SCM zipball request failed %s %s", response.body, response.status);
2101
2731
  repoSpinner.error({ text: "\u{1F4BE} Repo download failed" });
2102
2732
  throw new Error(`Can't access ${chalk4.bold(repoUrl)}`);
2103
2733
  }
@@ -2111,7 +2741,7 @@ async function downloadRepo({
2111
2741
  if (!repoRoot) {
2112
2742
  throw new Error("Repo root not found");
2113
2743
  }
2114
- debug8("repo root %s", repoRoot);
2744
+ debug9("repo root %s", repoRoot);
2115
2745
  repoSpinner.success({ text: "\u{1F4BE} Repo downloaded successfully" });
2116
2746
  return path6.join(dirname, repoRoot);
2117
2747
  }
@@ -2120,7 +2750,7 @@ var LOGIN_CHECK_DELAY = 5 * 1e3;
2120
2750
  var MOBB_LOGIN_REQUIRED_MSG = `\u{1F513} Login to Mobb is Required, you will be redirected to our login page, once the authorization is complete return to this prompt, ${chalk4.bgBlue(
2121
2751
  "press any key to continue"
2122
2752
  )};`;
2123
- var tmpObj = tmp.dirSync({
2753
+ var tmpObj = tmp2.dirSync({
2124
2754
  unsafeCleanup: true
2125
2755
  });
2126
2756
  var getReportUrl = ({
@@ -2128,7 +2758,7 @@ var getReportUrl = ({
2128
2758
  projectId,
2129
2759
  fixReportId
2130
2760
  }) => `${WEB_APP_URL}/organization/${organizationId}/project/${projectId}/report/${fixReportId}`;
2131
- var debug8 = Debug9("mobbdev:index");
2761
+ var debug9 = Debug10("mobbdev:index");
2132
2762
  var packageJson = JSON.parse(
2133
2763
  fs3.readFileSync(path6.join(getDirName2(), "../package.json"), "utf8")
2134
2764
  );
@@ -2138,7 +2768,7 @@ if (!semver.satisfies(process.version, packageJson.engines.node)) {
2138
2768
  );
2139
2769
  }
2140
2770
  var config2 = new Configstore(packageJson.name, { apiToken: "" });
2141
- debug8("config %o", config2);
2771
+ debug9("config %o", config2);
2142
2772
  async function runAnalysis(params, options) {
2143
2773
  try {
2144
2774
  await _scan(
@@ -2152,20 +2782,23 @@ async function runAnalysis(params, options) {
2152
2782
  tmpObj.removeCallback();
2153
2783
  }
2154
2784
  }
2155
- async function _scan({
2156
- dirname,
2157
- repo,
2158
- scanFile,
2159
- apiKey,
2160
- ci,
2161
- srcPath,
2162
- commitHash,
2163
- ref,
2164
- scanner,
2165
- cxProjectName,
2166
- mobbProjectName
2167
- }, { skipPrompts = false } = {}) {
2168
- debug8("start %s %s", dirname, repo);
2785
+ async function _scan(params, { skipPrompts = false } = {}) {
2786
+ const {
2787
+ dirname,
2788
+ repo,
2789
+ scanFile,
2790
+ apiKey,
2791
+ ci,
2792
+ srcPath,
2793
+ commitHash,
2794
+ ref,
2795
+ scanner,
2796
+ cxProjectName,
2797
+ mobbProjectName,
2798
+ githubToken: githubActionToken,
2799
+ command
2800
+ } = params;
2801
+ debug9("start %s %s", dirname, repo);
2169
2802
  const { createSpinner: createSpinner4 } = Spinner2({ ci });
2170
2803
  skipPrompts = skipPrompts || ci;
2171
2804
  let gqlClient = new GQLClient({
@@ -2221,9 +2854,9 @@ async function _scan({
2221
2854
  });
2222
2855
  const reference = ref ?? await scm.getRepoDefaultBranch();
2223
2856
  const { sha } = await scm.getReferenceData(reference);
2224
- debug8("org id %s", organizationId);
2225
- debug8("project id %s", projectId);
2226
- debug8("default branch %s", reference);
2857
+ debug9("org id %s", organizationId);
2858
+ debug9("project id %s", projectId);
2859
+ debug9("default branch %s", reference);
2227
2860
  const repositoryRoot = await downloadRepo({
2228
2861
  repoUrl: repo,
2229
2862
  dirname,
@@ -2231,8 +2864,8 @@ async function _scan({
2231
2864
  authHeaders: scm.getAuthHeaders(),
2232
2865
  downloadUrl: scm.getDownloadUrl(sha)
2233
2866
  });
2234
- if (scanner) {
2235
- reportPath = await getReport(scanner);
2867
+ if (command === "scan") {
2868
+ reportPath = await getReport(SupportedScannersZ.parse(scanner));
2236
2869
  }
2237
2870
  if (!reportPath) {
2238
2871
  throw new Error("reportPath is null");
@@ -2251,23 +2884,43 @@ async function _scan({
2251
2884
  }
2252
2885
  uploadReportSpinner.success({ text: "\u{1F4C1} Report uploaded successfully" });
2253
2886
  const mobbSpinner = createSpinner4("\u{1F575}\uFE0F\u200D\u2642\uFE0F Initiating Mobb analysis").start();
2254
- try {
2255
- await gqlClient.submitVulnerabilityReport({
2256
- fixReportId: reportUploadInfo.fixReportId,
2257
- repoUrl: repo,
2258
- reference,
2259
- projectId,
2260
- vulnerabilityReportFileName: "report.json",
2261
- sha
2262
- });
2263
- } catch (e) {
2264
- mobbSpinner.error({ text: "\u{1F575}\uFE0F\u200D\u2642\uFE0F Mobb analysis failed" });
2265
- throw e;
2887
+ const sendReportRes = await sendReport();
2888
+ if (command === "review") {
2889
+ await gqlClient.subscribeToAnalysis(
2890
+ { analysisId: sendReportRes.submitVulnerabilityReport.fixReportId },
2891
+ (analysisId) => handleFinishedAnalysis({
2892
+ analysisId,
2893
+ gqlClient,
2894
+ scm,
2895
+ githubActionOctokit: new Octokit3({ auth: githubActionToken }),
2896
+ scanner: z8.nativeEnum(SCANNERS).parse(scanner)
2897
+ })
2898
+ );
2266
2899
  }
2267
2900
  mobbSpinner.success({
2268
2901
  text: "\u{1F575}\uFE0F\u200D\u2642\uFE0F Generating fixes..."
2269
2902
  });
2270
2903
  await askToOpenAnalysis();
2904
+ async function sendReport() {
2905
+ try {
2906
+ const sumbitRes = await gqlClient.submitVulnerabilityReport({
2907
+ fixReportId: reportUploadInfo.fixReportId,
2908
+ repoUrl: z8.string().parse(repo),
2909
+ reference,
2910
+ projectId,
2911
+ vulnerabilityReportFileName: "report.json",
2912
+ sha,
2913
+ pullRequest: params.pullRequest
2914
+ });
2915
+ if (sumbitRes.submitVulnerabilityReport.__typename !== "VulnerabilityReport") {
2916
+ throw new Error("\u{1F575}\uFE0F\u200D\u2642\uFE0F Mobb analysis failed");
2917
+ }
2918
+ return sumbitRes;
2919
+ } catch (e) {
2920
+ mobbSpinner.error({ text: "\u{1F575}\uFE0F\u200D\u2642\uFE0F Mobb analysis failed" });
2921
+ throw e;
2922
+ }
2923
+ }
2271
2924
  async function getReport(scanner2) {
2272
2925
  const reportPath2 = path6.join(dirname, "report.json");
2273
2926
  switch (scanner2) {
@@ -2298,7 +2951,6 @@ async function _scan({
2298
2951
  fixReportId: reportUploadInfo.fixReportId
2299
2952
  });
2300
2953
  !ci && console.log("You can access the analysis at: \n");
2301
- console.log(chalk4.bold(reportUrl));
2302
2954
  !skipPrompts && await mobbAnalysisPrompt();
2303
2955
  !ci && open2(reportUrl);
2304
2956
  !ci && console.log(
@@ -2343,9 +2995,9 @@ async function _scan({
2343
2995
  });
2344
2996
  loginSpinner.spin();
2345
2997
  if (encryptedApiToken) {
2346
- debug8("encrypted API token received %s", encryptedApiToken);
2998
+ debug9("encrypted API token received %s", encryptedApiToken);
2347
2999
  newApiToken = crypto.privateDecrypt(privateKey, Buffer.from(encryptedApiToken, "base64")).toString("utf-8");
2348
- debug8("API token decrypted");
3000
+ debug9("API token decrypted");
2349
3001
  break;
2350
3002
  }
2351
3003
  await sleep(LOGIN_CHECK_DELAY);
@@ -2358,7 +3010,7 @@ async function _scan({
2358
3010
  }
2359
3011
  gqlClient = new GQLClient({ apiKey: newApiToken });
2360
3012
  if (await gqlClient.verifyToken()) {
2361
- debug8("set api token %s", newApiToken);
3013
+ debug9("set api token %s", newApiToken);
2362
3014
  config2.set("apiToken", newApiToken);
2363
3015
  loginSpinner.success({ text: "\u{1F513} Login to Mobb successful!" });
2364
3016
  } else {
@@ -2482,6 +3134,35 @@ async function _scan({
2482
3134
 
2483
3135
  // src/commands/index.ts
2484
3136
  import chalkAnimation from "chalk-animation";
3137
+ async function review(params, { skipPrompts = true } = {}) {
3138
+ const {
3139
+ repo,
3140
+ f: scanFile,
3141
+ ref,
3142
+ apiKey,
3143
+ commitHash,
3144
+ mobbProjectName,
3145
+ pullRequest,
3146
+ githubToken,
3147
+ scanner
3148
+ } = params;
3149
+ await runAnalysis(
3150
+ {
3151
+ repo,
3152
+ scanFile,
3153
+ ref,
3154
+ apiKey,
3155
+ ci: true,
3156
+ commitHash,
3157
+ mobbProjectName,
3158
+ pullRequest,
3159
+ githubToken,
3160
+ scanner,
3161
+ command: "review"
3162
+ },
3163
+ { skipPrompts }
3164
+ );
3165
+ }
2485
3166
  async function analyze({
2486
3167
  repo,
2487
3168
  f: scanFile,
@@ -2502,7 +3183,8 @@ async function analyze({
2502
3183
  ci,
2503
3184
  commitHash,
2504
3185
  mobbProjectName,
2505
- srcPath
3186
+ srcPath,
3187
+ command: "analyze"
2506
3188
  },
2507
3189
  { skipPrompts }
2508
3190
  );
@@ -2521,7 +3203,7 @@ async function scan(scanOptions, { skipPrompts = false } = {}) {
2521
3203
  throw new CliError(errorMessages.missingCxProjectName);
2522
3204
  }
2523
3205
  await runAnalysis(
2524
- { ...scanOptions, scanner: selectedScanner },
3206
+ { ...scanOptions, scanner: selectedScanner, command: "scan" },
2525
3207
  { skipPrompts }
2526
3208
  );
2527
3209
  }
@@ -2587,7 +3269,7 @@ var commitHashOption = {
2587
3269
  // src/args/validation.ts
2588
3270
  import chalk6 from "chalk";
2589
3271
  import path7 from "path";
2590
- import { z as z6 } from "zod";
3272
+ import { z as z9 } from "zod";
2591
3273
  function throwRepoUrlErrorMessage({
2592
3274
  error,
2593
3275
  repoUrl,
@@ -2604,10 +3286,9 @@ Example:
2604
3286
  )}`;
2605
3287
  throw new CliError(formattedErrorMessage);
2606
3288
  }
2607
- var GIT_REPO_URL_PATTERN = /^https:\/\/(gitlab|github)\.com\/(([^/.\s]+[/])+)([^/.\s]+)(\.git)?(\/)?$/i;
2608
- var UrlZ = z6.string({
3289
+ var UrlZ = z9.string({
2609
3290
  invalid_type_error: "is not a valid GitHub / GitLab URL"
2610
- }).regex(GIT_REPO_URL_PATTERN, {
3291
+ }).refine((data) => !!parseScmURL(data), {
2611
3292
  message: "is not a valid GitHub / GitLab URL"
2612
3293
  });
2613
3294
  function validateRepoUrl(args) {
@@ -2685,6 +3366,49 @@ async function analyzeHandler(args) {
2685
3366
  await analyze(args, { skipPrompts: args.yes });
2686
3367
  }
2687
3368
 
3369
+ // src/args/commands/review.ts
3370
+ import fs5 from "node:fs";
3371
+ import chalk8 from "chalk";
3372
+ function reviewBuilder(yargs2) {
3373
+ return yargs2.option("f", {
3374
+ alias: "scan-file",
3375
+ demandOption: true,
3376
+ type: "string",
3377
+ describe: chalk8.bold(
3378
+ "Select the vulnerability report to analyze (Checkmarx, Snyk, Fortify, CodeQL)"
3379
+ )
3380
+ }).option("repo", { ...repoOption, demandOption: true }).option("scanner", { ...scannerOptions, demandOption: true }).option("ref", { ...refOption, demandOption: true }).option("ch", {
3381
+ alias: "commit-hash",
3382
+ describe: chalk8.bold("Hash of the commit"),
3383
+ type: "string",
3384
+ demandOption: true
3385
+ }).option("mobb-project-name", mobbProjectNameOption).option("api-key", { ...apiKeyOption, demandOption: true }).option("commit-hash", { ...commitHashOption, demandOption: true }).option("github-token", {
3386
+ describe: chalk8.bold("Github action token"),
3387
+ type: "string",
3388
+ demandOption: true
3389
+ }).option("pull-request", {
3390
+ alias: "pr",
3391
+ describe: chalk8.bold("Number of the pull request"),
3392
+ type: "number",
3393
+ demandOption: true
3394
+ }).example(
3395
+ "$0 review -r https://github.com/WebGoat/WebGoat -f <your_vulirabitliy_report_path> --ch <pr_last_commit> --pr <pr_number> --ref <pr_branch_name> --api-key <api_key>",
3396
+ "add fixes to your pr"
3397
+ ).help();
3398
+ }
3399
+ function validateReviewOptions(argv) {
3400
+ if (!fs5.existsSync(argv.f)) {
3401
+ throw new CliError(`
3402
+ Can't access ${chalk8.bold(argv.f)}`);
3403
+ }
3404
+ validateRepoUrl(argv);
3405
+ validateReportFileFormat(argv.f);
3406
+ }
3407
+ async function reviewHandler(args) {
3408
+ validateReviewOptions(args);
3409
+ await review(args, { skipPrompts: true });
3410
+ }
3411
+
2688
3412
  // src/args/commands/scan.ts
2689
3413
  function scanBuilder(args) {
2690
3414
  return args.coerce("scanner", (arg) => arg.toLowerCase()).option("repo", repoOption).option("ref", refOption).option("scanner", scannerOptions).option("mobb-project-name", mobbProjectNameOption).option("y", yesOption).option("ci", ciOption).option("api-key", apiKeyOption).option("cx-project-name", projectNameOption).example(
@@ -2713,32 +3437,39 @@ async function scanHandler(args) {
2713
3437
  var parseArgs = async (args) => {
2714
3438
  const yargsInstance = yargs(args);
2715
3439
  return yargsInstance.updateStrings({
2716
- "Commands:": chalk8.yellow.underline.bold("Commands:"),
2717
- "Options:": chalk8.yellow.underline.bold("Options:"),
2718
- "Examples:": chalk8.yellow.underline.bold("Examples:"),
2719
- "Show help": chalk8.bold("Show help")
3440
+ "Commands:": chalk9.yellow.underline.bold("Commands:"),
3441
+ "Options:": chalk9.yellow.underline.bold("Options:"),
3442
+ "Examples:": chalk9.yellow.underline.bold("Examples:"),
3443
+ "Show help": chalk9.bold("Show help")
2720
3444
  }).usage(
2721
- `${chalk8.bold(
3445
+ `${chalk9.bold(
2722
3446
  "\n Bugsy - Trusted, Automatic Vulnerability Fixer \u{1F575}\uFE0F\u200D\u2642\uFE0F\n\n"
2723
- )} ${chalk8.yellow.underline.bold("Usage:")}
2724
- $0 ${chalk8.green(
3447
+ )} ${chalk9.yellow.underline.bold("Usage:")}
3448
+ $0 ${chalk9.green(
2725
3449
  "<command>"
2726
- )} ${chalk8.dim("[options]")}
3450
+ )} ${chalk9.dim("[options]")}
2727
3451
  `
2728
3452
  ).version(false).command(
2729
- "scan",
2730
- chalk8.bold(
3453
+ mobbCliCommand.scan,
3454
+ chalk9.bold(
2731
3455
  "Scan your code for vulnerabilities, get automated fixes right away."
2732
3456
  ),
2733
3457
  scanBuilder,
2734
3458
  scanHandler
2735
3459
  ).command(
2736
- "analyze",
2737
- chalk8.bold(
3460
+ mobbCliCommand.analyze,
3461
+ chalk9.bold(
2738
3462
  "Provide a vulnerability report and relevant code repository, get automated fixes right away."
2739
3463
  ),
2740
3464
  analyzeBuilder,
2741
3465
  analyzeHandler
3466
+ ).command(
3467
+ mobbCliCommand.review,
3468
+ chalk9.bold(
3469
+ "(beta) Mobb will review your github pull requests and provide comments with fixes "
3470
+ ),
3471
+ reviewBuilder,
3472
+ reviewHandler
2742
3473
  ).example(
2743
3474
  "$0 scan -r https://github.com/WebGoat/WebGoat",
2744
3475
  "Scan an existing repository"
@@ -2747,7 +3478,7 @@ var parseArgs = async (args) => {
2747
3478
  handler() {
2748
3479
  yargsInstance.showHelp();
2749
3480
  }
2750
- }).strictOptions().help("h").alias("h", "help").epilog(chalk8.bgBlue("Made with \u2764\uFE0F by Mobb")).showHelpOnFail(true).wrap(Math.min(120, yargsInstance.terminalWidth())).parse();
3481
+ }).strictOptions().help("h").alias("h", "help").epilog(chalk9.bgBlue("Made with \u2764\uFE0F by Mobb")).showHelpOnFail(true).wrap(Math.min(120, yargsInstance.terminalWidth())).parse();
2751
3482
  };
2752
3483
 
2753
3484
  // src/index.ts