mobbdev 0.0.31 → 0.0.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/.env +2 -3
  2. package/dist/index.js +242 -198
  3. package/package.json +6 -5
package/.env CHANGED
@@ -1,5 +1,4 @@
1
- # production@v11
1
+ # production@v12
2
2
  WEB_LOGIN_URL="https://app.mobb.ai/cli-login"
3
3
  API_URL="https://api.mobb.ai/v1/graphql"
4
- WEB_APP_URL="https://app.mobb.ai"
5
- GITHUB_CLIENT_ID="49d729663b401f91afe5"
4
+ WEB_APP_URL="https://app.mobb.ai"
package/dist/index.js CHANGED
@@ -69,7 +69,9 @@ var WEB_APP_URL = envVariables.WEB_APP_URL;
69
69
  var API_URL = envVariables.API_URL;
70
70
 
71
71
  // src/features/analysis/index.ts
72
+ import crypto from "node:crypto";
72
73
  import fs3 from "node:fs";
74
+ import os from "node:os";
73
75
  import path5 from "node:path";
74
76
 
75
77
  // src/utils/index.ts
@@ -135,83 +137,17 @@ var CliError = class extends Error {
135
137
  // src/features/analysis/index.ts
136
138
  import chalk3 from "chalk";
137
139
  import Configstore from "configstore";
138
- import Debug9 from "debug";
139
- import open3 from "open";
140
+ import Debug8 from "debug";
141
+ import open2 from "open";
140
142
  import semver from "semver";
141
143
  import tmp from "tmp";
142
144
 
143
- // src/features/analysis/callback-server.ts
144
- import * as http from "node:http";
145
- import * as querystring from "node:querystring";
146
- import { clearTimeout, setTimeout as setTimeout2 } from "node:timers";
147
- import Debug2 from "debug";
148
- import open from "open";
149
- import { z as z2 } from "zod";
150
- var debug2 = Debug2("mobbdev:web-login");
151
- async function callbackServer({
152
- url,
153
- redirectUrl
154
- }) {
155
- debug2("web login start");
156
- let responseResolver;
157
- let responseRejecter;
158
- const responseAwaiter = new Promise((resolve, reject) => {
159
- responseResolver = resolve;
160
- responseRejecter = reject;
161
- });
162
- const timerHandler = setTimeout2(() => {
163
- debug2("timeout happened");
164
- responseRejecter(new Error("No login happened in three minutes."));
165
- }, 18e4);
166
- const server = http.createServer((req, res) => {
167
- debug2("incoming request");
168
- let body = "";
169
- req.on("data", (chunk) => {
170
- debug2("http server get chunk %s", chunk);
171
- body += chunk;
172
- });
173
- req.on("end", () => {
174
- debug2("http server end %s", body);
175
- res.writeHead(301, {
176
- Location: redirectUrl
177
- }).end();
178
- const safeBody = z2.object({ token: z2.string() }).safeParse(querystring.parse(body));
179
- if (!safeBody.success) {
180
- return responseRejecter(new Error("Failed to parse the response"));
181
- }
182
- responseResolver({ token: safeBody.data.token });
183
- });
184
- });
185
- debug2("http server starting");
186
- const port = await new Promise((resolve, reject) => {
187
- server.listen(0, "127.0.0.1", () => {
188
- const address = server?.address();
189
- if (typeof address === "string" || address == null) {
190
- reject(new Error("Failed to get port"));
191
- } else {
192
- resolve(address.port);
193
- }
194
- });
195
- });
196
- debug2("http server started on port %d", port);
197
- debug2("opening the browser on %s", `${url}?port=${port}`);
198
- await open(`${url}?port=${port}`);
199
- try {
200
- debug2("waiting for http request");
201
- return await responseAwaiter;
202
- } finally {
203
- debug2("http server close");
204
- clearTimeout(timerHandler);
205
- server.close();
206
- }
207
- }
208
-
209
145
  // src/features/analysis/git.ts
210
- import Debug3 from "debug";
146
+ import Debug2 from "debug";
211
147
  import { simpleGit } from "simple-git";
212
- var debug3 = Debug3("mobbdev:git");
148
+ var debug2 = Debug2("mobbdev:git");
213
149
  async function getGitInfo(srcDirPath) {
214
- debug3("getting git info for %s", srcDirPath);
150
+ debug2("getting git info for %s", srcDirPath);
215
151
  const git = simpleGit({
216
152
  baseDir: srcDirPath,
217
153
  maxConcurrentProcesses: 1,
@@ -226,11 +162,11 @@ async function getGitInfo(srcDirPath) {
226
162
  reference = await git.revparse(["--abbrev-ref", "HEAD"]) || "";
227
163
  } catch (e) {
228
164
  if (e instanceof Error) {
229
- debug3("failed to run git %o", e);
165
+ debug2("failed to run git %o", e);
230
166
  if (e.message.includes(" spawn ")) {
231
- debug3("git cli not installed");
167
+ debug2("git cli not installed");
232
168
  } else if (e.message.includes(" not a git repository ")) {
233
- debug3("folder is not a git repo");
169
+ debug2("folder is not a git repo");
234
170
  } else {
235
171
  throw e;
236
172
  }
@@ -257,12 +193,12 @@ import stream from "node:stream";
257
193
  import { promisify } from "node:util";
258
194
  import { RequestError } from "@octokit/request-error";
259
195
  import chalk from "chalk";
260
- import Debug4 from "debug";
196
+ import Debug3 from "debug";
261
197
  import extract from "extract-zip";
262
198
  import fetch from "node-fetch";
263
199
  import { Octokit } from "octokit";
264
200
  var pipeline = promisify(stream.pipeline);
265
- var debug4 = Debug4("mobbdev:github");
201
+ var debug3 = Debug3("mobbdev:github");
266
202
  async function _getRepo({ owner, repo }, { token } = {}) {
267
203
  const octokit = new Octokit({ auth: token });
268
204
  return octokit.rest.repos.get({
@@ -271,7 +207,7 @@ async function _getRepo({ owner, repo }, { token } = {}) {
271
207
  });
272
208
  }
273
209
  function extractSlug(repoUrl) {
274
- debug4("get default branch %s", repoUrl);
210
+ debug3("get default branch %s", repoUrl);
275
211
  let slug = repoUrl.replace(/https?:\/\/github\.com\//i, "");
276
212
  if (slug.endsWith("/")) {
277
213
  slug = slug.substring(0, slug.length - 1);
@@ -279,7 +215,7 @@ function extractSlug(repoUrl) {
279
215
  if (slug.endsWith(".git")) {
280
216
  slug = slug.substring(0, slug.length - ".git".length);
281
217
  }
282
- debug4("slug %s", slug);
218
+ debug3("slug %s", slug);
283
219
  return slug;
284
220
  }
285
221
  function parseRepoUrl(repoUrl) {
@@ -309,7 +245,7 @@ async function getRepo(repoUrl, { token } = {}) {
309
245
  return res;
310
246
  } catch (e) {
311
247
  if (e instanceof RequestError) {
312
- debug4("GH request failed %s %s", e.message, e.status);
248
+ debug3("GH request failed %s %s", e.message, e.status);
313
249
  throw new CliError(
314
250
  `Can't get repository, make sure you have access to : ${repoUrl}.`
315
251
  );
@@ -320,7 +256,7 @@ async function getRepo(repoUrl, { token } = {}) {
320
256
  async function downloadRepo({ repoUrl, reference, dirname, ci }, { token } = {}) {
321
257
  const { createSpinner: createSpinner3 } = Spinner({ ci });
322
258
  const repoSpinner = createSpinner3("\u{1F4BE} Downloading Repo").start();
323
- debug4("download repo %s %s %s", repoUrl, reference, dirname);
259
+ debug3("download repo %s %s %s", repoUrl, reference, dirname);
324
260
  const zipFilePath = path3.join(dirname, "repo.zip");
325
261
  const response = await fetch(`${repoUrl}/zipball/${reference}`, {
326
262
  method: "GET",
@@ -329,7 +265,7 @@ async function downloadRepo({ repoUrl, reference, dirname, ci }, { token } = {})
329
265
  }
330
266
  });
331
267
  if (!response.ok) {
332
- debug4("GH zipball request failed %s %s", response.body, response.status);
268
+ debug3("GH zipball request failed %s %s", response.body, response.status);
333
269
  repoSpinner.error({ text: "\u{1F4BE} Repo download failed" });
334
270
  throw new Error(
335
271
  `Can't access the the branch ${chalk.bold(reference)} on ${chalk.bold(
@@ -347,13 +283,13 @@ async function downloadRepo({ repoUrl, reference, dirname, ci }, { token } = {})
347
283
  if (!repoRoot) {
348
284
  throw new Error("Repo root not found");
349
285
  }
350
- debug4("repo root %s", repoRoot);
286
+ debug3("repo root %s", repoRoot);
351
287
  repoSpinner.success({ text: "\u{1F4BE} Repo downloaded successfully" });
352
288
  return path3.join(dirname, repoRoot);
353
289
  }
354
290
 
355
291
  // src/features/analysis/graphql/gql.ts
356
- import Debug5 from "debug";
292
+ import Debug4 from "debug";
357
293
  import { GraphQLClient } from "graphql-request";
358
294
 
359
295
  // src/features/analysis/graphql/mutations.ts
@@ -408,6 +344,20 @@ var CREATE_COMMUNITY_USER = gql`
408
344
  }
409
345
  }
410
346
  `;
347
+ var CREATE_CLI_LOGIN = gql`
348
+ mutation CreateCliLogin($publicKey: String!) {
349
+ insert_cli_login_one(object: { publicKey: $publicKey }) {
350
+ id
351
+ }
352
+ }
353
+ `;
354
+ var PERFORM_CLI_LOGIN = gql`
355
+ mutation performCliLogin($loginId: String!) {
356
+ performCliLogin(loginId: $loginId) {
357
+ status
358
+ }
359
+ }
360
+ `;
411
361
 
412
362
  // src/features/analysis/graphql/queries.ts
413
363
  import { gql as gql2 } from "graphql-request";
@@ -434,51 +384,58 @@ var GET_ORG_AND_PROJECT_ID = gql2`
434
384
  }
435
385
  }
436
386
  `;
387
+ var GET_ENCRYPTED_API_TOKEN = gql2`
388
+ query GetEncryptedApiToken($loginId: uuid!) {
389
+ cli_login_by_pk(id: $loginId) {
390
+ encryptedApiToken
391
+ }
392
+ }
393
+ `;
437
394
 
438
395
  // src/features/analysis/graphql/types.ts
439
- import { z as z3 } from "zod";
440
- var UploadFieldsZ = z3.object({
441
- bucket: z3.string(),
442
- "X-Amz-Algorithm": z3.string(),
443
- "X-Amz-Credential": z3.string(),
444
- "X-Amz-Date": z3.string(),
445
- Policy: z3.string(),
446
- "X-Amz-Signature": z3.string()
396
+ import { z as z2 } from "zod";
397
+ var UploadFieldsZ = z2.object({
398
+ bucket: z2.string(),
399
+ "X-Amz-Algorithm": z2.string(),
400
+ "X-Amz-Credential": z2.string(),
401
+ "X-Amz-Date": z2.string(),
402
+ Policy: z2.string(),
403
+ "X-Amz-Signature": z2.string()
447
404
  });
448
- var ReportUploadInfoZ = z3.object({
449
- url: z3.string(),
450
- fixReportId: z3.string(),
451
- uploadFieldsJSON: z3.string().transform((str, ctx) => {
405
+ var ReportUploadInfoZ = z2.object({
406
+ url: z2.string(),
407
+ fixReportId: z2.string(),
408
+ uploadFieldsJSON: z2.string().transform((str, ctx) => {
452
409
  try {
453
410
  return JSON.parse(str);
454
411
  } catch (e) {
455
412
  ctx.addIssue({ code: "custom", message: "Invalid JSON" });
456
- return z3.NEVER;
413
+ return z2.NEVER;
457
414
  }
458
415
  }),
459
- uploadKey: z3.string()
416
+ uploadKey: z2.string()
460
417
  }).transform(({ uploadFieldsJSON, ...input }) => ({
461
418
  ...input,
462
419
  uploadFields: uploadFieldsJSON
463
420
  }));
464
- var UploadS3BucketInfoZ = z3.object({
465
- uploadS3BucketInfo: z3.object({
466
- status: z3.string(),
467
- error: z3.string().nullish(),
421
+ var UploadS3BucketInfoZ = z2.object({
422
+ uploadS3BucketInfo: z2.object({
423
+ status: z2.string(),
424
+ error: z2.string().nullish(),
468
425
  reportUploadInfo: ReportUploadInfoZ,
469
426
  repoUploadInfo: ReportUploadInfoZ
470
427
  })
471
428
  });
472
- var GetOrgAndProjectIdQueryZ = z3.object({
473
- users: z3.array(
474
- z3.object({
475
- userOrganizationsAndUserOrganizationRoles: z3.array(
476
- z3.object({
477
- organization: z3.object({
478
- id: z3.string(),
479
- projects: z3.array(
480
- z3.object({
481
- id: z3.string()
429
+ var GetOrgAndProjectIdQueryZ = z2.object({
430
+ users: z2.array(
431
+ z2.object({
432
+ userOrganizationsAndUserOrganizationRoles: z2.array(
433
+ z2.object({
434
+ organization: z2.object({
435
+ id: z2.string(),
436
+ projects: z2.array(
437
+ z2.object({
438
+ id: z2.string()
482
439
  })
483
440
  ).nonempty()
484
441
  })
@@ -487,28 +444,51 @@ var GetOrgAndProjectIdQueryZ = z3.object({
487
444
  })
488
445
  ).nonempty()
489
446
  });
447
+ var CreateCliLoginZ = z2.object({
448
+ insert_cli_login_one: z2.object({
449
+ id: z2.string()
450
+ })
451
+ });
452
+ var GetEncryptedApiTokenZ = z2.object({
453
+ cli_login_by_pk: z2.object({
454
+ encryptedApiToken: z2.string().nullable()
455
+ })
456
+ });
490
457
 
491
458
  // src/features/analysis/graphql/gql.ts
492
- var debug5 = Debug5("mobbdev:gql");
459
+ var debug4 = Debug4("mobbdev:gql");
460
+ var API_KEY_HEADER_NAME = "x-mobb-key";
493
461
  var GQLClient = class {
494
462
  constructor(args) {
495
- const { token, apiKey } = args;
496
- debug5(`init with apiKey ${apiKey} token ${token}`);
497
- const headers = apiKey ? { "x-mobb-key": apiKey } : {
498
- authorization: `Bearer ${token}`
499
- };
500
- this._client = new GraphQLClient(API_URL, { headers });
463
+ const { apiKey } = args;
464
+ debug4(`init with apiKey ${apiKey}`);
465
+ this._client = new GraphQLClient(API_URL, {
466
+ headers: { [API_KEY_HEADER_NAME]: apiKey || "" }
467
+ });
501
468
  }
502
469
  async getUserInfo() {
503
470
  const { me } = await this._client.request(ME);
504
471
  return me;
505
472
  }
473
+ async createCliLogin(variables) {
474
+ const res = CreateCliLoginZ.parse(
475
+ await this._client.request(
476
+ CREATE_CLI_LOGIN,
477
+ variables,
478
+ {
479
+ // We may have outdated API key in the config storage. Avoid using it for the login request.
480
+ [API_KEY_HEADER_NAME]: ""
481
+ }
482
+ )
483
+ );
484
+ return res.insert_cli_login_one.id;
485
+ }
506
486
  async verifyToken() {
507
487
  await this.createCommunityUser();
508
488
  try {
509
- await this._client.request(ME);
489
+ await this.getUserInfo();
510
490
  } catch (e) {
511
- debug5("verify token failed %o", e);
491
+ debug4("verify token failed %o", e);
512
492
  return false;
513
493
  }
514
494
  return true;
@@ -526,11 +506,22 @@ var GQLClient = class {
526
506
  projectId: org.projects[0].id
527
507
  };
528
508
  }
509
+ async getEncryptedApiToken(variables) {
510
+ const res = await this._client.request(
511
+ GET_ENCRYPTED_API_TOKEN,
512
+ variables,
513
+ {
514
+ // We may have outdated API key in the config storage. Avoid using it for the login request.
515
+ [API_KEY_HEADER_NAME]: ""
516
+ }
517
+ );
518
+ return GetEncryptedApiTokenZ.parse(res).cli_login_by_pk.encryptedApiToken;
519
+ }
529
520
  async createCommunityUser() {
530
521
  try {
531
522
  await this._client.request(CREATE_COMMUNITY_USER);
532
523
  } catch (e) {
533
- debug5("create community user failed %o", e);
524
+ debug4("create community user failed %o", e);
534
525
  }
535
526
  }
536
527
  async uploadS3BucketInfo() {
@@ -561,27 +552,36 @@ var GQLClient = class {
561
552
  import fs2 from "node:fs";
562
553
  import path4 from "node:path";
563
554
  import AdmZip from "adm-zip";
564
- import Debug6 from "debug";
555
+ import Debug5 from "debug";
565
556
  import { globby } from "globby";
566
- var debug6 = Debug6("mobbdev:pack");
557
+ import { isBinary } from "istextorbinary";
558
+ var debug5 = Debug5("mobbdev:pack");
559
+ var MAX_FILE_SIZE = 1024 * 1024 * 5;
567
560
  async function pack(srcDirPath) {
568
- debug6("pack folder %s", srcDirPath);
561
+ debug5("pack folder %s", srcDirPath);
569
562
  const filepaths = await globby("**", {
570
563
  gitignore: true,
571
564
  onlyFiles: true,
572
565
  cwd: srcDirPath,
573
566
  followSymbolicLinks: false
574
567
  });
575
- debug6("files found %d", filepaths.length);
568
+ debug5("files found %d", filepaths.length);
576
569
  const zip = new AdmZip();
577
- debug6("compressing files");
570
+ debug5("compressing files");
578
571
  for (const filepath of filepaths) {
579
- zip.addFile(
580
- filepath.toString(),
581
- fs2.readFileSync(path4.join(srcDirPath, filepath.toString()))
582
- );
572
+ const absFilepath = path4.join(srcDirPath, filepath.toString());
573
+ if (fs2.lstatSync(absFilepath).size > MAX_FILE_SIZE) {
574
+ debug5("ignoring %s because the size is > 5MB", filepath);
575
+ continue;
576
+ }
577
+ const data = fs2.readFileSync(absFilepath);
578
+ if (isBinary(null, data)) {
579
+ debug5("ignoring %s because is seems to be a binary file", filepath);
580
+ continue;
581
+ }
582
+ zip.addFile(filepath.toString(), data);
583
583
  }
584
- debug6("get zip file buffer");
584
+ debug5("get zip file buffer");
585
585
  return zip.toBuffer();
586
586
  }
587
587
 
@@ -632,19 +632,19 @@ async function snykArticlePrompt() {
632
632
  import cp from "node:child_process";
633
633
  import { createRequire } from "node:module";
634
634
  import chalk2 from "chalk";
635
- import Debug7 from "debug";
635
+ import Debug6 from "debug";
636
636
  import { createSpinner as createSpinner2 } from "nanospinner";
637
- import open2 from "open";
637
+ import open from "open";
638
638
  import * as process2 from "process";
639
639
  import supportsColor from "supports-color";
640
640
  var { stdout: stdout2 } = supportsColor;
641
- var debug7 = Debug7("mobbdev:snyk");
641
+ var debug6 = Debug6("mobbdev:snyk");
642
642
  var require2 = createRequire(import.meta.url);
643
643
  var SNYK_PATH = require2.resolve("snyk/bin/snyk");
644
644
  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";
645
- debug7("snyk executable path %s", SNYK_PATH);
645
+ debug6("snyk executable path %s", SNYK_PATH);
646
646
  async function forkSnyk(args, { display }) {
647
- debug7("fork snyk with args %o %s", args, display);
647
+ debug6("fork snyk with args %o %s", args, display);
648
648
  return new Promise((resolve, reject) => {
649
649
  const child = cp.fork(SNYK_PATH, args, {
650
650
  stdio: ["inherit", "pipe", "pipe", "ipc"],
@@ -652,11 +652,11 @@ async function forkSnyk(args, { display }) {
652
652
  });
653
653
  let out = "";
654
654
  const onData = (chunk) => {
655
- debug7("chunk received from snyk std %s", chunk);
655
+ debug6("chunk received from snyk std %s", chunk);
656
656
  out += chunk;
657
657
  };
658
658
  if (!child || !child?.stdout || !child?.stderr) {
659
- debug7("unable to fork snyk");
659
+ debug6("unable to fork snyk");
660
660
  reject(new Error("unable to fork snyk"));
661
661
  }
662
662
  child.stdout?.on("data", onData);
@@ -666,17 +666,17 @@ async function forkSnyk(args, { display }) {
666
666
  child.stderr?.pipe(process2.stderr);
667
667
  }
668
668
  child.on("exit", () => {
669
- debug7("snyk exit");
669
+ debug6("snyk exit");
670
670
  resolve(out);
671
671
  });
672
672
  child.on("error", (err) => {
673
- debug7("snyk error %o", err);
673
+ debug6("snyk error %o", err);
674
674
  reject(err);
675
675
  });
676
676
  });
677
677
  }
678
678
  async function getSnykReport(reportPath, repoRoot, { skipPrompts = false }) {
679
- debug7("get snyk report start %s %s", reportPath, repoRoot);
679
+ debug6("get snyk report start %s %s", reportPath, repoRoot);
680
680
  const config3 = await forkSnyk(["config"], { display: false });
681
681
  if (!config3.includes("api: ")) {
682
682
  const snykLoginSpinner = createSpinner2().start();
@@ -689,7 +689,7 @@ async function getSnykReport(reportPath, repoRoot, { skipPrompts = false }) {
689
689
  snykLoginSpinner.update({
690
690
  text: "\u{1F513} Waiting for Snyk login to complete"
691
691
  });
692
- debug7("no token in the config %s", config3);
692
+ debug6("no token in the config %s", config3);
693
693
  await forkSnyk(["auth"], { display: true });
694
694
  snykLoginSpinner.success({ text: "\u{1F513} Login to Snyk Successful" });
695
695
  }
@@ -701,13 +701,13 @@ async function getSnykReport(reportPath, repoRoot, { skipPrompts = false }) {
701
701
  if (out.includes(
702
702
  "Snyk Code is not supported for org: enable in Settings > Snyk Code"
703
703
  )) {
704
- debug7("snyk code is not enabled %s", out);
704
+ debug6("snyk code is not enabled %s", out);
705
705
  snykSpinner.error({ text: "\u{1F50D} Snyk configuration needed" });
706
706
  const answer = await snykArticlePrompt();
707
- debug7("answer %s", answer);
707
+ debug6("answer %s", answer);
708
708
  if (answer) {
709
- debug7("opening the browser");
710
- await open2(SNYK_ARTICLE_URL);
709
+ debug6("opening the browser");
710
+ await open(SNYK_ARTICLE_URL);
711
711
  }
712
712
  console.log(
713
713
  chalk2.bgBlue(
@@ -721,28 +721,28 @@ async function getSnykReport(reportPath, repoRoot, { skipPrompts = false }) {
721
721
  }
722
722
 
723
723
  // src/features/analysis/upload-file.ts
724
- import Debug8 from "debug";
724
+ import Debug7 from "debug";
725
725
  import fetch2, { File, fileFrom, FormData } from "node-fetch";
726
- var debug8 = Debug8("mobbdev:upload-file");
726
+ var debug7 = Debug7("mobbdev:upload-file");
727
727
  async function uploadFile({
728
728
  file,
729
729
  url,
730
730
  uploadKey,
731
731
  uploadFields
732
732
  }) {
733
- debug8("upload file start %s", url);
734
- debug8("upload fields %o", uploadFields);
735
- debug8("upload key %s", uploadKey);
733
+ debug7("upload file start %s", url);
734
+ debug7("upload fields %o", uploadFields);
735
+ debug7("upload key %s", uploadKey);
736
736
  const form = new FormData();
737
737
  Object.entries(uploadFields).forEach(([key, value]) => {
738
738
  form.append(key, value);
739
739
  });
740
740
  form.append("key", uploadKey);
741
741
  if (typeof file === "string") {
742
- debug8("upload file from path %s", file);
742
+ debug7("upload file from path %s", file);
743
743
  form.append("file", await fileFrom(file));
744
744
  } else {
745
- debug8("upload file from buffer");
745
+ debug7("upload file from buffer");
746
746
  form.append("file", new File([file], "file"));
747
747
  }
748
748
  const response = await fetch2(url, {
@@ -750,17 +750,18 @@ async function uploadFile({
750
750
  body: form
751
751
  });
752
752
  if (!response.ok) {
753
- debug8("error from S3 %s %s", response.body, response.status);
753
+ debug7("error from S3 %s %s", response.body, response.status);
754
754
  throw new Error(`Failed to upload the file: ${response.status}`);
755
755
  }
756
- debug8("upload file done");
756
+ debug7("upload file done");
757
757
  }
758
758
 
759
759
  // src/features/analysis/index.ts
760
760
  var { CliError: CliError2, Spinner: Spinner2, keypress: keypress2, getDirName: getDirName2 } = utils_exports;
761
761
  var webLoginUrl = `${WEB_APP_URL}/cli-login`;
762
- var githubSubmitUrl = `${WEB_APP_URL}/gh-callback`;
763
762
  var githubAuthUrl = `${WEB_APP_URL}/github-auth`;
763
+ var LOGIN_MAX_WAIT = 10 * 60 * 1e3;
764
+ var LOGIN_CHECK_DELAY = 5 * 1e3;
764
765
  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, ${chalk3.bgBlue(
765
766
  "press any key to continue"
766
767
  )};`;
@@ -772,7 +773,7 @@ var getReportUrl = ({
772
773
  projectId,
773
774
  fixReportId
774
775
  }) => `${WEB_APP_URL}/organization/${organizationId}/project/${projectId}/report/${fixReportId}`;
775
- var debug9 = Debug9("mobbdev:index");
776
+ var debug8 = Debug8("mobbdev:index");
776
777
  var packageJson = JSON.parse(
777
778
  fs3.readFileSync(path5.join(getDirName2(), "../package.json"), "utf8")
778
779
  );
@@ -781,8 +782,8 @@ if (!semver.satisfies(process.version, packageJson.engines.node)) {
781
782
  `${packageJson.name} requires node version ${packageJson.engines.node}, but running ${process.version}.`
782
783
  );
783
784
  }
784
- var config2 = new Configstore(packageJson.name, { token: "" });
785
- debug9("config %o", config2);
785
+ var config2 = new Configstore(packageJson.name, { apiToken: "" });
786
+ debug8("config %o", config2);
786
787
  async function runAnalysis(params, options) {
787
788
  try {
788
789
  await _scan(
@@ -806,13 +807,12 @@ async function _scan({
806
807
  commitHash,
807
808
  ref
808
809
  }, { skipPrompts = false } = {}) {
809
- debug9("start %s %s", dirname, repo);
810
+ debug8("start %s %s", dirname, repo);
810
811
  const { createSpinner: createSpinner3 } = Spinner2({ ci });
811
812
  skipPrompts = skipPrompts || ci;
812
- let token = config2.get("token");
813
- debug9("token %s", token);
814
- apiKey ?? debug9("token %s", apiKey);
815
- let gqlClient = new GQLClient(apiKey ? { apiKey } : { token });
813
+ let gqlClient = new GQLClient({
814
+ apiKey: apiKey || config2.get("apiToken")
815
+ });
816
816
  await handleMobbLogin();
817
817
  const { projectId, organizationId } = await gqlClient.getOrgAndProjectId();
818
818
  const {
@@ -828,7 +828,7 @@ async function _scan({
828
828
  const userInfo = await gqlClient.getUserInfo();
829
829
  let { githubToken } = userInfo;
830
830
  const isRepoAvailable = await canReachRepo(repo, {
831
- token: userInfo.githubToken
831
+ token: githubToken
832
832
  });
833
833
  if (!isRepoAvailable) {
834
834
  if (ci) {
@@ -836,13 +836,20 @@ async function _scan({
836
836
  `Cannot access repo ${repo} with the provided token, please visit ${githubAuthUrl} to refresh your Github token`
837
837
  );
838
838
  }
839
- const { token: token2 } = await handleGithubIntegration();
840
- githubToken = token2;
839
+ githubToken = await handleGithubIntegration(githubToken);
840
+ const isRepoAvailable2 = await canReachRepo(repo, {
841
+ token: githubToken
842
+ });
843
+ if (!isRepoAvailable2) {
844
+ throw new Error(
845
+ `Cannot access repo ${repo} with the provided credentials`
846
+ );
847
+ }
841
848
  }
842
849
  const reference = ref ?? (await getRepo(repo, { token: githubToken })).data.default_branch;
843
- debug9("org id %s", organizationId);
844
- debug9("project id %s", projectId);
845
- debug9("default branch %s", reference);
850
+ debug8("org id %s", organizationId);
851
+ debug8("project id %s", projectId);
852
+ debug8("default branch %s", reference);
846
853
  const repositoryRoot = await downloadRepo(
847
854
  {
848
855
  repoUrl: repo,
@@ -887,7 +894,7 @@ async function _scan({
887
894
  async function getReportFromSnyk() {
888
895
  const reportPath2 = path5.join(dirname, "report.json");
889
896
  if (!await getSnykReport(reportPath2, repositoryRoot, { skipPrompts })) {
890
- debug9("snyk code is not enabled");
897
+ debug8("snyk code is not enabled");
891
898
  throw new CliError2("Snyk code is not enabled");
892
899
  }
893
900
  return reportPath2;
@@ -901,49 +908,75 @@ async function _scan({
901
908
  !ci && console.log("You can access the report at: \n");
902
909
  console.log(chalk3.bold(reportUrl));
903
910
  !skipPrompts && await mobbAnalysisPrompt();
904
- !ci && open3(reportUrl);
911
+ !ci && open2(reportUrl);
905
912
  !ci && console.log(
906
913
  chalk3.bgBlue("\n\n My work here is done for now, see you soon! \u{1F575}\uFE0F\u200D\u2642\uFE0F ")
907
914
  );
908
915
  }
909
916
  async function handleMobbLogin() {
910
- if (token && await gqlClient.verifyToken() || apiKey && await gqlClient.verifyToken()) {
917
+ if (await gqlClient.verifyToken()) {
911
918
  createSpinner3().start().success({
912
919
  text: "\u{1F513} Logged in to Mobb successfully"
913
920
  });
914
921
  return;
915
- }
916
- if (apiKey && !await gqlClient.verifyToken()) {
922
+ } else if (apiKey) {
917
923
  createSpinner3().start().error({
918
924
  text: "\u{1F513} Logged in to Mobb failed - check your api-key"
919
925
  });
920
926
  throw new CliError2();
921
927
  }
922
- const mobbLoginSpinner = createSpinner3().start();
928
+ const loginSpinner = createSpinner3().start();
923
929
  if (!skipPrompts) {
924
- mobbLoginSpinner.update({ text: MOBB_LOGIN_REQUIRED_MSG });
930
+ loginSpinner.update({ text: MOBB_LOGIN_REQUIRED_MSG });
925
931
  await keypress2();
926
932
  }
927
- mobbLoginSpinner.update({
933
+ loginSpinner.update({
928
934
  text: "\u{1F513} Waiting for Mobb login..."
929
935
  });
930
- const loginResponse = await callbackServer({
931
- url: webLoginUrl,
932
- redirectUrl: `${webLoginUrl}?done=true`
936
+ const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
937
+ modulusLength: 2048
938
+ });
939
+ const loginId = await gqlClient.createCliLogin({
940
+ publicKey: publicKey.export({ format: "pem", type: "pkcs1" }).toString()
933
941
  });
934
- token = loginResponse.token;
935
- gqlClient = new GQLClient({ token });
936
- if (!await gqlClient.verifyToken()) {
937
- mobbLoginSpinner.error({
942
+ const browserUrl = `${webLoginUrl}/${loginId}?hostname=${os.hostname()}`;
943
+ !ci && console.log(
944
+ `If the page does not open automatically, kindly access it through ${browserUrl}.`
945
+ );
946
+ await open2(browserUrl);
947
+ let newApiToken = null;
948
+ for (let i = 0; i < LOGIN_MAX_WAIT / LOGIN_CHECK_DELAY; i++) {
949
+ const encryptedApiToken = await gqlClient.getEncryptedApiToken({
950
+ loginId
951
+ });
952
+ loginSpinner.spin();
953
+ if (encryptedApiToken) {
954
+ debug8("encrypted API token received %s", encryptedApiToken);
955
+ newApiToken = crypto.privateDecrypt(privateKey, Buffer.from(encryptedApiToken, "base64")).toString("utf-8");
956
+ debug8("API token decrypted");
957
+ break;
958
+ }
959
+ await sleep(LOGIN_CHECK_DELAY);
960
+ }
961
+ if (!newApiToken) {
962
+ loginSpinner.error({
963
+ text: "Login timeout error"
964
+ });
965
+ throw new CliError2();
966
+ }
967
+ gqlClient = new GQLClient({ apiKey: newApiToken });
968
+ if (await gqlClient.verifyToken()) {
969
+ debug8("set api token %s", newApiToken);
970
+ config2.set("apiToken", newApiToken);
971
+ loginSpinner.success({ text: "\u{1F513} Login to Mobb successful!" });
972
+ } else {
973
+ loginSpinner.error({
938
974
  text: "Something went wrong, API token is invalid."
939
975
  });
940
976
  throw new CliError2();
941
977
  }
942
- debug9("set token %s", token);
943
- config2.set("token", token);
944
- mobbLoginSpinner.success({ text: "\u{1F513} Login to Mobb successful!" });
945
978
  }
946
- async function handleGithubIntegration() {
979
+ async function handleGithubIntegration(oldToken) {
947
980
  const addGithubIntegration = skipPrompts ? true : await githubIntegrationPrompt();
948
981
  const githubSpinner = createSpinner3(
949
982
  "\u{1F517} Waiting for github integration..."
@@ -952,12 +985,23 @@ async function _scan({
952
985
  githubSpinner.error();
953
986
  throw Error("Could not reach github repo");
954
987
  }
955
- const result = await callbackServer({
956
- url: githubAuthUrl,
957
- redirectUrl: `${githubSubmitUrl}?done=true`
988
+ console.log(
989
+ `If the page does not open automatically, kindly access it through ${githubAuthUrl}.`
990
+ );
991
+ await open2(githubAuthUrl);
992
+ for (let i = 0; i < LOGIN_MAX_WAIT / LOGIN_CHECK_DELAY; i++) {
993
+ const { githubToken: githubToken2 } = await gqlClient.getUserInfo();
994
+ if (githubToken2 && githubToken2 !== oldToken) {
995
+ githubSpinner.success({ text: "\u{1F517} Github integration successful!" });
996
+ return githubToken2;
997
+ }
998
+ githubSpinner.spin();
999
+ await sleep(LOGIN_CHECK_DELAY);
1000
+ }
1001
+ githubSpinner.error({
1002
+ text: "Github login timeout error"
958
1003
  });
959
- githubSpinner.success({ text: "\u{1F517} Github integration successful!" });
960
- return result;
1004
+ throw new CliError2("Github login timeout");
961
1005
  }
962
1006
  async function uploadExistingRepo() {
963
1007
  if (!srcPath || !reportPath) {
@@ -976,7 +1020,7 @@ async function _scan({
976
1020
  uploadKey: reportUploadInfo.uploadKey
977
1021
  });
978
1022
  } catch (e) {
979
- uploadReportSpinner2.error({ text: "\u{1F4C1} Repo upload failed" });
1023
+ uploadReportSpinner2.error({ text: "\u{1F4C1} Report upload failed" });
980
1024
  throw e;
981
1025
  }
982
1026
  uploadReportSpinner2.success({
@@ -991,7 +1035,7 @@ async function _scan({
991
1035
  uploadKey: repoUploadInfo.uploadKey
992
1036
  });
993
1037
  } catch (e) {
994
- uploadRepoSpinner.error({ text: "\u{1F4C1} Report upload failed" });
1038
+ uploadRepoSpinner.error({ text: "\u{1F4C1} Repo upload failed" });
995
1039
  throw e;
996
1040
  }
997
1041
  uploadRepoSpinner.success({ text: "\u{1F4C1} Uploading Repo successful!" });
@@ -1094,7 +1138,7 @@ var commitHashOption = {
1094
1138
  // src/args/validation.ts
1095
1139
  import chalk5 from "chalk";
1096
1140
  import path6 from "path";
1097
- import { z as z4 } from "zod";
1141
+ import { z as z3 } from "zod";
1098
1142
  function throwRepoUrlErrorMessage({
1099
1143
  error,
1100
1144
  repoUrl,
@@ -1112,7 +1156,7 @@ Example:
1112
1156
  throw new CliError(formattedErrorMessage);
1113
1157
  }
1114
1158
  var GITHUB_REPO_URL_PATTERN = new RegExp("https://github.com/[\\w-]+/[\\w-]+");
1115
- var UrlZ = z4.string({
1159
+ var UrlZ = z3.string({
1116
1160
  invalid_type_error: "is not a valid github URL"
1117
1161
  }).regex(GITHUB_REPO_URL_PATTERN, {
1118
1162
  message: "is not a valid github URL"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mobbdev",
3
- "version": "0.0.31",
3
+ "version": "0.0.35",
4
4
  "description": "Automated secure code remediation tool",
5
5
  "repository": "https://github.com/mobb-dev/bugsy",
6
6
  "main": "dist/index.js",
@@ -13,7 +13,7 @@
13
13
  "lint": "eslint --cache --max-warnings 0 --ignore-path .eslintignore --ext .ts,.tsx,.jsx .",
14
14
  "lint:fix": "eslint --fix --cache --max-warnings 0 --ignore-path .eslintignore --ext .js,.ts,.tsx,.jsx .",
15
15
  "lint:fix:files": "eslint --fix --cache --max-warnings 0 --ignore-path .eslintignore --ext .js,.ts,.tsx,.jsx",
16
- "prepack": "pnpm build && dotenv-vault pull production .env"
16
+ "prepack": "dotenv-vault pull production .env && pnpm build"
17
17
  },
18
18
  "bin": {
19
19
  "mobbdev": "bin/cli.mjs"
@@ -22,16 +22,17 @@
22
22
  "license": "MIT",
23
23
  "dependencies": {
24
24
  "adm-zip": "0.5.10",
25
- "chalk-animation": "2.0.3",
26
25
  "chalk": "5.3.0",
26
+ "chalk-animation": "2.0.3",
27
27
  "configstore": "6.0.0",
28
28
  "debug": "4.3.4",
29
29
  "dotenv": "16.0.3",
30
30
  "extract-zip": "2.0.1",
31
31
  "globby": "13.2.2",
32
- "graphql-request": "5.0.0",
33
32
  "graphql": "16.6.0",
33
+ "graphql-request": "5.0.0",
34
34
  "inquirer": "9.2.7",
35
+ "istextorbinary": "6.0.0",
35
36
  "nanospinner": "1.1.0",
36
37
  "node-fetch": "3.3.1",
37
38
  "octokit": "2.0.14",
@@ -57,10 +58,10 @@
57
58
  "@types/yargs": "17.0.24",
58
59
  "@typescript-eslint/eslint-plugin": "5.44.0",
59
60
  "@typescript-eslint/parser": "5.44.0",
61
+ "eslint": "8.36.0",
60
62
  "eslint-plugin-import": "2.27.5",
61
63
  "eslint-plugin-prettier": "4.2.1",
62
64
  "eslint-plugin-simple-import-sort": "10.0.0",
63
- "eslint": "8.36.0",
64
65
  "prettier": "2.8.4",
65
66
  "tsup": "7.2.0",
66
67
  "typescript": "4.9.3",