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.
- package/.env +2 -3
- package/dist/index.js +242 -198
- package/package.json +6 -5
package/.env
CHANGED
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
|
|
139
|
-
import
|
|
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
|
|
146
|
+
import Debug2 from "debug";
|
|
211
147
|
import { simpleGit } from "simple-git";
|
|
212
|
-
var
|
|
148
|
+
var debug2 = Debug2("mobbdev:git");
|
|
213
149
|
async function getGitInfo(srcDirPath) {
|
|
214
|
-
|
|
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
|
-
|
|
165
|
+
debug2("failed to run git %o", e);
|
|
230
166
|
if (e.message.includes(" spawn ")) {
|
|
231
|
-
|
|
167
|
+
debug2("git cli not installed");
|
|
232
168
|
} else if (e.message.includes(" not a git repository ")) {
|
|
233
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
440
|
-
var UploadFieldsZ =
|
|
441
|
-
bucket:
|
|
442
|
-
"X-Amz-Algorithm":
|
|
443
|
-
"X-Amz-Credential":
|
|
444
|
-
"X-Amz-Date":
|
|
445
|
-
Policy:
|
|
446
|
-
"X-Amz-Signature":
|
|
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 =
|
|
449
|
-
url:
|
|
450
|
-
fixReportId:
|
|
451
|
-
uploadFieldsJSON:
|
|
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
|
|
413
|
+
return z2.NEVER;
|
|
457
414
|
}
|
|
458
415
|
}),
|
|
459
|
-
uploadKey:
|
|
416
|
+
uploadKey: z2.string()
|
|
460
417
|
}).transform(({ uploadFieldsJSON, ...input }) => ({
|
|
461
418
|
...input,
|
|
462
419
|
uploadFields: uploadFieldsJSON
|
|
463
420
|
}));
|
|
464
|
-
var UploadS3BucketInfoZ =
|
|
465
|
-
uploadS3BucketInfo:
|
|
466
|
-
status:
|
|
467
|
-
error:
|
|
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 =
|
|
473
|
-
users:
|
|
474
|
-
|
|
475
|
-
userOrganizationsAndUserOrganizationRoles:
|
|
476
|
-
|
|
477
|
-
organization:
|
|
478
|
-
id:
|
|
479
|
-
projects:
|
|
480
|
-
|
|
481
|
-
id:
|
|
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
|
|
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 {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
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.
|
|
489
|
+
await this.getUserInfo();
|
|
510
490
|
} catch (e) {
|
|
511
|
-
|
|
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
|
-
|
|
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
|
|
555
|
+
import Debug5 from "debug";
|
|
565
556
|
import { globby } from "globby";
|
|
566
|
-
|
|
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
|
-
|
|
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
|
-
|
|
568
|
+
debug5("files found %d", filepaths.length);
|
|
576
569
|
const zip = new AdmZip();
|
|
577
|
-
|
|
570
|
+
debug5("compressing files");
|
|
578
571
|
for (const filepath of filepaths) {
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
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
|
-
|
|
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
|
|
635
|
+
import Debug6 from "debug";
|
|
636
636
|
import { createSpinner as createSpinner2 } from "nanospinner";
|
|
637
|
-
import
|
|
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
|
|
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
|
-
|
|
645
|
+
debug6("snyk executable path %s", SNYK_PATH);
|
|
646
646
|
async function forkSnyk(args, { display }) {
|
|
647
|
-
|
|
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
|
-
|
|
655
|
+
debug6("chunk received from snyk std %s", chunk);
|
|
656
656
|
out += chunk;
|
|
657
657
|
};
|
|
658
658
|
if (!child || !child?.stdout || !child?.stderr) {
|
|
659
|
-
|
|
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
|
-
|
|
669
|
+
debug6("snyk exit");
|
|
670
670
|
resolve(out);
|
|
671
671
|
});
|
|
672
672
|
child.on("error", (err) => {
|
|
673
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
707
|
+
debug6("answer %s", answer);
|
|
708
708
|
if (answer) {
|
|
709
|
-
|
|
710
|
-
await
|
|
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
|
|
724
|
+
import Debug7 from "debug";
|
|
725
725
|
import fetch2, { File, fileFrom, FormData } from "node-fetch";
|
|
726
|
-
var
|
|
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
|
-
|
|
734
|
-
|
|
735
|
-
|
|
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
|
-
|
|
742
|
+
debug7("upload file from path %s", file);
|
|
743
743
|
form.append("file", await fileFrom(file));
|
|
744
744
|
} else {
|
|
745
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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, {
|
|
785
|
-
|
|
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
|
-
|
|
810
|
+
debug8("start %s %s", dirname, repo);
|
|
810
811
|
const { createSpinner: createSpinner3 } = Spinner2({ ci });
|
|
811
812
|
skipPrompts = skipPrompts || ci;
|
|
812
|
-
let
|
|
813
|
-
|
|
814
|
-
|
|
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:
|
|
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
|
-
|
|
840
|
-
|
|
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
|
-
|
|
844
|
-
|
|
845
|
-
|
|
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
|
-
|
|
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 &&
|
|
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 (
|
|
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
|
|
928
|
+
const loginSpinner = createSpinner3().start();
|
|
923
929
|
if (!skipPrompts) {
|
|
924
|
-
|
|
930
|
+
loginSpinner.update({ text: MOBB_LOGIN_REQUIRED_MSG });
|
|
925
931
|
await keypress2();
|
|
926
932
|
}
|
|
927
|
-
|
|
933
|
+
loginSpinner.update({
|
|
928
934
|
text: "\u{1F513} Waiting for Mobb login..."
|
|
929
935
|
});
|
|
930
|
-
const
|
|
931
|
-
|
|
932
|
-
|
|
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
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
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
|
-
|
|
956
|
-
|
|
957
|
-
|
|
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
|
-
|
|
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}
|
|
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}
|
|
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
|
|
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 =
|
|
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.
|
|
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": "
|
|
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",
|