mobbdev 1.0.125 → 1.0.126
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/dist/index.mjs +280 -217
- package/package.json +5 -4
package/dist/index.mjs
CHANGED
|
@@ -32,7 +32,7 @@ var init_env = __esm({
|
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
// src/mcp/core/configs.ts
|
|
35
|
-
var MCP_DEFAULT_API_URL, MCP_API_KEY_HEADER_NAME, MCP_LOGIN_MAX_WAIT, MCP_LOGIN_CHECK_DELAY, MCP_VUL_REPORT_DIGEST_TIMEOUT_MS, MCP_MAX_FILE_SIZE, MCP_PERIODIC_CHECK_INTERVAL, MCP_DEFAULT_MAX_FILES_TO_SCAN, MCP_REPORT_ID_EXPIRATION_MS, MCP_TOOLS_BROWSER_COOLDOWN_MS, MCP_DEFAULT_LIMIT;
|
|
35
|
+
var MCP_DEFAULT_API_URL, MCP_API_KEY_HEADER_NAME, MCP_LOGIN_MAX_WAIT, MCP_LOGIN_CHECK_DELAY, MCP_VUL_REPORT_DIGEST_TIMEOUT_MS, MCP_MAX_FILE_SIZE, MCP_PERIODIC_CHECK_INTERVAL, MCP_DEFAULT_MAX_FILES_TO_SCAN, MCP_REPORT_ID_EXPIRATION_MS, MCP_TOOLS_BROWSER_COOLDOWN_MS, MCP_DEFAULT_LIMIT, isAutoScan;
|
|
36
36
|
var init_configs = __esm({
|
|
37
37
|
"src/mcp/core/configs.ts"() {
|
|
38
38
|
"use strict";
|
|
@@ -48,6 +48,7 @@ var init_configs = __esm({
|
|
|
48
48
|
MCP_REPORT_ID_EXPIRATION_MS = 2 * 60 * 60 * 1e3;
|
|
49
49
|
MCP_TOOLS_BROWSER_COOLDOWN_MS = 24 * 60 * 60 * 1e3;
|
|
50
50
|
MCP_DEFAULT_LIMIT = 3;
|
|
51
|
+
isAutoScan = process.env["AUTO_SCAN"] !== "false";
|
|
51
52
|
}
|
|
52
53
|
});
|
|
53
54
|
|
|
@@ -2259,14 +2260,14 @@ var GetReportFixesDocument = `
|
|
|
2259
2260
|
var GetLatestReportByRepoUrlDocument = `
|
|
2260
2261
|
query GetLatestReportByRepoUrl($repoUrl: String!, $filters: fix_bool_exp = {}, $limit: Int!, $offset: Int!, $currentUserEmail: String!) {
|
|
2261
2262
|
fixReport(
|
|
2262
|
-
where: {_and: [{repo: {originalUrl: {_eq: $repoUrl}}}, {state: {_eq: Finished}}]}
|
|
2263
|
+
where: {_and: [{repo: {originalUrl: {_eq: $repoUrl}}}, {state: {_eq: Finished}}, {vulnerabilityReport: {_or: [{vendor: {_is_null: true}}, {vendor: {_nin: [semgrep, opengrep]}}]}}]}
|
|
2263
2264
|
order_by: {createdOn: desc}
|
|
2264
2265
|
limit: 1
|
|
2265
2266
|
) {
|
|
2266
2267
|
...FixReportSummaryFields
|
|
2267
2268
|
}
|
|
2268
2269
|
expiredReport: fixReport(
|
|
2269
|
-
where: {_and: [{repo: {originalUrl: {_eq: $repoUrl}}}, {state: {_eq: Expired}}]}
|
|
2270
|
+
where: {_and: [{repo: {originalUrl: {_eq: $repoUrl}}}, {state: {_eq: Expired}}, {_or: [{vulnerabilityReport: {vendor: {_is_null: true}}}, {vulnerabilityReport: {vendor: {_nin: [semgrep, opengrep]}}}]}]}
|
|
2270
2271
|
order_by: {createdOn: desc}
|
|
2271
2272
|
limit: 1
|
|
2272
2273
|
) {
|
|
@@ -11953,104 +11954,34 @@ import {
|
|
|
11953
11954
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
11954
11955
|
|
|
11955
11956
|
// src/mcp/Logger.ts
|
|
11956
|
-
|
|
11957
|
-
var
|
|
11958
|
-
var CIRCUIT_BREAKER_TIME = 5e3;
|
|
11959
|
-
var URL_CHECK_TIMEOUT = 200;
|
|
11960
|
-
var MAX_QUEUE_SIZE = 100;
|
|
11957
|
+
import Configstore3 from "configstore";
|
|
11958
|
+
var MAX_LOGS_SIZE = 1e3;
|
|
11961
11959
|
var Logger = class {
|
|
11962
11960
|
constructor() {
|
|
11963
|
-
__publicField(this, "
|
|
11964
|
-
__publicField(this, "
|
|
11965
|
-
|
|
11966
|
-
|
|
11961
|
+
__publicField(this, "mobbConfigStore");
|
|
11962
|
+
__publicField(this, "path");
|
|
11963
|
+
this.path = process.env["WORKSPACE_FOLDER_PATHS"] || "unknown";
|
|
11964
|
+
this.mobbConfigStore = new Configstore3("mobb-logs", {});
|
|
11965
|
+
this.mobbConfigStore.set("version", packageJson.version);
|
|
11967
11966
|
}
|
|
11967
|
+
/**
|
|
11968
|
+
* Log a message to the console.
|
|
11969
|
+
* @param message - The message to log.
|
|
11970
|
+
* @param level - The level of the message.
|
|
11971
|
+
* @param data - The data to log.
|
|
11972
|
+
*/
|
|
11968
11973
|
log(message, level = "info", data) {
|
|
11969
|
-
if (isTestEnvironment) return;
|
|
11970
|
-
if (this.queue.length >= MAX_QUEUE_SIZE) {
|
|
11971
|
-
this.queue.shift();
|
|
11972
|
-
}
|
|
11973
|
-
this.queue.push({ message, level, data });
|
|
11974
|
-
if (!this.isProcessing && !this.isCircuitBroken) {
|
|
11975
|
-
this.processQueue();
|
|
11976
|
-
}
|
|
11977
|
-
}
|
|
11978
|
-
async isUrlReachable(url) {
|
|
11979
|
-
try {
|
|
11980
|
-
const controller = new AbortController();
|
|
11981
|
-
const timeoutId = setTimeout(() => controller.abort(), URL_CHECK_TIMEOUT);
|
|
11982
|
-
await fetch(url, {
|
|
11983
|
-
method: "HEAD",
|
|
11984
|
-
signal: controller.signal
|
|
11985
|
-
});
|
|
11986
|
-
clearTimeout(timeoutId);
|
|
11987
|
-
return true;
|
|
11988
|
-
} catch (error) {
|
|
11989
|
-
return false;
|
|
11990
|
-
}
|
|
11991
|
-
}
|
|
11992
|
-
async processQueue() {
|
|
11993
|
-
if (this.queue.length === 0 || this.isCircuitBroken) {
|
|
11994
|
-
this.isProcessing = false;
|
|
11995
|
-
return;
|
|
11996
|
-
}
|
|
11997
|
-
this.isProcessing = true;
|
|
11998
|
-
const logEntry = this.queue[0];
|
|
11999
|
-
if (!logEntry) {
|
|
12000
|
-
this.isProcessing = false;
|
|
12001
|
-
return;
|
|
12002
|
-
}
|
|
12003
|
-
const isReachable = await this.isUrlReachable(loggerUrl);
|
|
12004
|
-
if (!isReachable) {
|
|
12005
|
-
this.triggerCircuitBreaker();
|
|
12006
|
-
return;
|
|
12007
|
-
}
|
|
12008
|
-
await this.sendLogEntry(logEntry);
|
|
12009
|
-
}
|
|
12010
|
-
async sendLogEntry(logEntry) {
|
|
12011
11974
|
const logMessage = {
|
|
12012
11975
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12013
|
-
level
|
|
12014
|
-
message
|
|
12015
|
-
data
|
|
12016
|
-
version: packageJson.version
|
|
11976
|
+
level,
|
|
11977
|
+
message,
|
|
11978
|
+
data
|
|
12017
11979
|
};
|
|
12018
|
-
const
|
|
12019
|
-
|
|
12020
|
-
|
|
12021
|
-
}, 500);
|
|
12022
|
-
try {
|
|
12023
|
-
await fetch(loggerUrl, {
|
|
12024
|
-
method: "POST",
|
|
12025
|
-
headers: { "Content-Type": "application/json" },
|
|
12026
|
-
body: JSON.stringify(logMessage),
|
|
12027
|
-
redirect: "error",
|
|
12028
|
-
// do not follow redirects
|
|
12029
|
-
signal: controller.signal
|
|
12030
|
-
});
|
|
12031
|
-
this.queue.shift();
|
|
12032
|
-
setTimeout(() => this.processQueue(), 0);
|
|
12033
|
-
} catch (error) {
|
|
12034
|
-
this.triggerCircuitBreaker();
|
|
12035
|
-
logError("Failed to send log entry", error);
|
|
12036
|
-
} finally {
|
|
12037
|
-
clearTimeout(timeoutId);
|
|
12038
|
-
}
|
|
12039
|
-
}
|
|
12040
|
-
triggerCircuitBreaker() {
|
|
12041
|
-
this.isCircuitBroken = true;
|
|
12042
|
-
this.queue = [];
|
|
12043
|
-
this.isProcessing = false;
|
|
12044
|
-
if (this.circuitBreakerTimer) {
|
|
12045
|
-
clearTimeout(this.circuitBreakerTimer);
|
|
11980
|
+
const logs = this.mobbConfigStore.get(this.path) || [];
|
|
11981
|
+
if (logs.length >= MAX_LOGS_SIZE) {
|
|
11982
|
+
logs.shift();
|
|
12046
11983
|
}
|
|
12047
|
-
this.
|
|
12048
|
-
this.isCircuitBroken = false;
|
|
12049
|
-
this.circuitBreakerTimer = null;
|
|
12050
|
-
if (this.queue.length > 0 && !this.isProcessing) {
|
|
12051
|
-
this.processQueue();
|
|
12052
|
-
}
|
|
12053
|
-
}, CIRCUIT_BREAKER_TIME);
|
|
11984
|
+
this.mobbConfigStore.set(this.path, [...logs, logMessage]);
|
|
12054
11985
|
}
|
|
12055
11986
|
};
|
|
12056
11987
|
var logger = new Logger();
|
|
@@ -12061,12 +11992,34 @@ var logDebug = (message, data) => logger.log(message, "debug", data);
|
|
|
12061
11992
|
var log = logger.log.bind(logger);
|
|
12062
11993
|
|
|
12063
11994
|
// src/mcp/services/McpGQLClient.ts
|
|
12064
|
-
import Configstore3 from "configstore";
|
|
12065
11995
|
import crypto3 from "crypto";
|
|
12066
11996
|
import { GraphQLClient as GraphQLClient2 } from "graphql-request";
|
|
12067
11997
|
import { v4 as uuidv42 } from "uuid";
|
|
12068
11998
|
init_configs();
|
|
12069
11999
|
|
|
12000
|
+
// src/mcp/services/ConfigStoreService.ts
|
|
12001
|
+
init_configs();
|
|
12002
|
+
import Configstore4 from "configstore";
|
|
12003
|
+
function createConfigStore(defaultValues = { apiToken: "" }) {
|
|
12004
|
+
const API_URL2 = process.env["API_URL"] || MCP_DEFAULT_API_URL;
|
|
12005
|
+
let domain = "";
|
|
12006
|
+
try {
|
|
12007
|
+
const url = new URL(API_URL2);
|
|
12008
|
+
domain = url.hostname;
|
|
12009
|
+
} catch (e) {
|
|
12010
|
+
domain = API_URL2.replace(/^https?:\/\//, "").replace(/\/.*$/, "").replace(/:\d+$/, "");
|
|
12011
|
+
}
|
|
12012
|
+
const sanitizedDomain = domain.replace(/\./g, "_");
|
|
12013
|
+
return new Configstore4(
|
|
12014
|
+
`${packageJson.name}-${sanitizedDomain}`,
|
|
12015
|
+
defaultValues
|
|
12016
|
+
);
|
|
12017
|
+
}
|
|
12018
|
+
function getConfigStore() {
|
|
12019
|
+
return createConfigStore();
|
|
12020
|
+
}
|
|
12021
|
+
var configStore = getConfigStore();
|
|
12022
|
+
|
|
12070
12023
|
// src/mcp/services/McpAuthService.ts
|
|
12071
12024
|
import crypto2 from "crypto";
|
|
12072
12025
|
import os2 from "os";
|
|
@@ -12147,16 +12100,17 @@ var McpAuthService = class {
|
|
|
12147
12100
|
};
|
|
12148
12101
|
|
|
12149
12102
|
// src/mcp/services/McpGQLClient.ts
|
|
12150
|
-
var mobbConfigStore = new Configstore3(packageJson.name, { apiToken: "" });
|
|
12151
12103
|
var McpGQLClient = class {
|
|
12152
12104
|
constructor(args) {
|
|
12153
12105
|
__publicField(this, "client");
|
|
12154
12106
|
__publicField(this, "clientSdk");
|
|
12155
12107
|
__publicField(this, "_auth");
|
|
12108
|
+
__publicField(this, "currentUser", null);
|
|
12109
|
+
__publicField(this, "apiUrl");
|
|
12156
12110
|
this._auth = args;
|
|
12157
|
-
|
|
12158
|
-
logDebug(
|
|
12159
|
-
this.client = new GraphQLClient2(
|
|
12111
|
+
this.apiUrl = process.env["API_URL"] || MCP_DEFAULT_API_URL;
|
|
12112
|
+
logDebug(`creating graphql client with api url ${this.apiUrl}`, { args });
|
|
12113
|
+
this.client = new GraphQLClient2(this.apiUrl, {
|
|
12160
12114
|
headers: args.type === "apiKey" ? { [MCP_API_KEY_HEADER_NAME]: args.apiKey || "" } : {
|
|
12161
12115
|
Authorization: `Bearer ${args.token}`
|
|
12162
12116
|
},
|
|
@@ -12175,7 +12129,7 @@ var McpGQLClient = class {
|
|
|
12175
12129
|
}
|
|
12176
12130
|
getErrorContext() {
|
|
12177
12131
|
return {
|
|
12178
|
-
endpoint:
|
|
12132
|
+
endpoint: this.apiUrl,
|
|
12179
12133
|
apiKey: this._auth.type === "apiKey" ? this._auth.apiKey : "",
|
|
12180
12134
|
headers: {
|
|
12181
12135
|
[MCP_API_KEY_HEADER_NAME]: this._auth.type === "apiKey" ? "[REDACTED]" : "undefined",
|
|
@@ -12183,10 +12137,10 @@ var McpGQLClient = class {
|
|
|
12183
12137
|
}
|
|
12184
12138
|
};
|
|
12185
12139
|
}
|
|
12186
|
-
async
|
|
12140
|
+
async isApiEndpointReachable() {
|
|
12187
12141
|
try {
|
|
12188
12142
|
logDebug("GraphQL: Calling Me query for API connection verification");
|
|
12189
|
-
const result = await this.
|
|
12143
|
+
const result = await this.getUserInfo();
|
|
12190
12144
|
logDebug("GraphQL: Me query successful", { result });
|
|
12191
12145
|
return true;
|
|
12192
12146
|
} catch (e) {
|
|
@@ -12199,6 +12153,23 @@ var McpGQLClient = class {
|
|
|
12199
12153
|
}
|
|
12200
12154
|
return true;
|
|
12201
12155
|
}
|
|
12156
|
+
/**
|
|
12157
|
+
* Verifies both API endpoint reachability and user authentication
|
|
12158
|
+
* @returns true if both API is reachable and user is authenticated
|
|
12159
|
+
*/
|
|
12160
|
+
async verifyApiConnection() {
|
|
12161
|
+
const isReachable = await this.isApiEndpointReachable();
|
|
12162
|
+
if (!isReachable) {
|
|
12163
|
+
return false;
|
|
12164
|
+
}
|
|
12165
|
+
try {
|
|
12166
|
+
await this.validateUserToken();
|
|
12167
|
+
return true;
|
|
12168
|
+
} catch (e) {
|
|
12169
|
+
logError("User token validation failed", { error: e });
|
|
12170
|
+
return false;
|
|
12171
|
+
}
|
|
12172
|
+
}
|
|
12202
12173
|
async uploadS3BucketInfo() {
|
|
12203
12174
|
try {
|
|
12204
12175
|
logDebug("GraphQL: Calling uploadS3BucketInfo mutation");
|
|
@@ -12253,8 +12224,9 @@ var McpGQLClient = class {
|
|
|
12253
12224
|
}
|
|
12254
12225
|
}
|
|
12255
12226
|
async subscribeToGetAnalysis(params) {
|
|
12227
|
+
const { scanContext } = params;
|
|
12256
12228
|
try {
|
|
12257
|
-
logDebug(
|
|
12229
|
+
logDebug(`[${scanContext}] GraphQL: Starting GetAnalysis subscription`, {
|
|
12258
12230
|
params: params.subscribeToAnalysisParams
|
|
12259
12231
|
});
|
|
12260
12232
|
const { callbackStates } = params;
|
|
@@ -12262,10 +12234,13 @@ var McpGQLClient = class {
|
|
|
12262
12234
|
GetAnalysisSubscriptionDocument,
|
|
12263
12235
|
params.subscribeToAnalysisParams,
|
|
12264
12236
|
async (resolve, reject, data) => {
|
|
12265
|
-
logDebug(
|
|
12237
|
+
logDebug(
|
|
12238
|
+
`[${scanContext}] GraphQL: GetAnalysis subscription data received ${data.analysis?.state}`,
|
|
12239
|
+
{ data }
|
|
12240
|
+
);
|
|
12266
12241
|
if (!data.analysis?.state || data.analysis?.state === "Failed" /* Failed */) {
|
|
12267
12242
|
const errorMessage = data.analysis?.failReason || `Analysis failed with id: ${data.analysis?.id}`;
|
|
12268
|
-
logError(
|
|
12243
|
+
logError(`[${scanContext}] GraphQL: Analysis failed`, {
|
|
12269
12244
|
analysisId: data.analysis?.id,
|
|
12270
12245
|
state: data.analysis?.state,
|
|
12271
12246
|
failReason: data.analysis?.failReason,
|
|
@@ -12275,11 +12250,14 @@ var McpGQLClient = class {
|
|
|
12275
12250
|
return;
|
|
12276
12251
|
}
|
|
12277
12252
|
if (callbackStates.includes(data.analysis?.state)) {
|
|
12278
|
-
logDebug(
|
|
12279
|
-
|
|
12280
|
-
|
|
12281
|
-
|
|
12282
|
-
|
|
12253
|
+
logDebug(
|
|
12254
|
+
`[${scanContext}] GraphQL: Analysis state matches callback states: ${data.analysis.state}`,
|
|
12255
|
+
{
|
|
12256
|
+
analysisId: data.analysis.id,
|
|
12257
|
+
state: data.analysis.state,
|
|
12258
|
+
callbackStates
|
|
12259
|
+
}
|
|
12260
|
+
);
|
|
12283
12261
|
await params.callback(data.analysis.id);
|
|
12284
12262
|
resolve(data);
|
|
12285
12263
|
}
|
|
@@ -12294,10 +12272,12 @@ var McpGQLClient = class {
|
|
|
12294
12272
|
timeoutInMs: params.timeoutInMs
|
|
12295
12273
|
}
|
|
12296
12274
|
);
|
|
12297
|
-
logDebug(
|
|
12275
|
+
logDebug(`[${scanContext}] GraphQL: GetAnalysis subscription completed`, {
|
|
12276
|
+
result
|
|
12277
|
+
});
|
|
12298
12278
|
return result;
|
|
12299
12279
|
} catch (e) {
|
|
12300
|
-
logError(
|
|
12280
|
+
logError(`[${scanContext}] GraphQL: GetAnalysis subscription failed`, {
|
|
12301
12281
|
error: e,
|
|
12302
12282
|
params: params.subscribeToAnalysisParams,
|
|
12303
12283
|
...this.getErrorContext()
|
|
@@ -12361,8 +12341,12 @@ var McpGQLClient = class {
|
|
|
12361
12341
|
}
|
|
12362
12342
|
async getUserInfo() {
|
|
12363
12343
|
const { me } = await this.clientSdk.Me();
|
|
12344
|
+
this.currentUser = me;
|
|
12364
12345
|
return me;
|
|
12365
12346
|
}
|
|
12347
|
+
getCurrentUser() {
|
|
12348
|
+
return this.currentUser;
|
|
12349
|
+
}
|
|
12366
12350
|
async validateUserToken() {
|
|
12367
12351
|
logDebug("validating user token");
|
|
12368
12352
|
try {
|
|
@@ -12555,16 +12539,16 @@ var McpGQLClient = class {
|
|
|
12555
12539
|
async function createAuthenticatedMcpGQLClient({
|
|
12556
12540
|
isBackgoundCall = false
|
|
12557
12541
|
} = {}) {
|
|
12558
|
-
logDebug("getting config", { apiToken:
|
|
12542
|
+
logDebug("getting config", { apiToken: configStore.get("apiToken") });
|
|
12559
12543
|
const initialClient = new McpGQLClient({
|
|
12560
12544
|
apiKey: process.env["MOBB_API_KEY"] || process.env["API_KEY"] || // fallback for backward compatibility
|
|
12561
|
-
|
|
12545
|
+
configStore.get("apiToken") || "",
|
|
12562
12546
|
type: "apiKey"
|
|
12563
12547
|
});
|
|
12564
|
-
const
|
|
12565
|
-
logDebug("API connection status", {
|
|
12566
|
-
if (!
|
|
12567
|
-
throw new ApiConnectionError("Error: failed to
|
|
12548
|
+
const isApiEndpointReachable = await initialClient.isApiEndpointReachable();
|
|
12549
|
+
logDebug("API connection status", { isApiEndpointReachable });
|
|
12550
|
+
if (!isApiEndpointReachable) {
|
|
12551
|
+
throw new ApiConnectionError("Error: failed to reach Mobb GraphQL endpoint");
|
|
12568
12552
|
}
|
|
12569
12553
|
logDebug("validating user token");
|
|
12570
12554
|
const userVerify = await initialClient.validateUserToken();
|
|
@@ -12573,7 +12557,7 @@ async function createAuthenticatedMcpGQLClient({
|
|
|
12573
12557
|
}
|
|
12574
12558
|
const authService = new McpAuthService(initialClient);
|
|
12575
12559
|
const newApiToken = await authService.authenticate(isBackgoundCall);
|
|
12576
|
-
|
|
12560
|
+
configStore.set("apiToken", newApiToken);
|
|
12577
12561
|
return new McpGQLClient({ apiKey: newApiToken, type: "apiKey" });
|
|
12578
12562
|
}
|
|
12579
12563
|
|
|
@@ -12582,6 +12566,9 @@ var MCP_TOOL_CHECK_FOR_NEW_AVAILABLE_FIXES = "check_for_new_available_fixes";
|
|
|
12582
12566
|
var MCP_TOOL_FETCH_AVAILABLE_FIXES = "fetch_available_fixes";
|
|
12583
12567
|
var MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES = "scan_and_fix_vulnerabilities";
|
|
12584
12568
|
|
|
12569
|
+
// src/mcp/core/McpServer.ts
|
|
12570
|
+
init_configs();
|
|
12571
|
+
|
|
12585
12572
|
// src/mcp/core/ToolRegistry.ts
|
|
12586
12573
|
var ToolRegistry = class {
|
|
12587
12574
|
constructor() {
|
|
@@ -12656,6 +12643,18 @@ var McpServer = class {
|
|
|
12656
12643
|
} else {
|
|
12657
12644
|
logWarn(`${message} (exit code: ${exitCode})`, { signal, exitCode });
|
|
12658
12645
|
}
|
|
12646
|
+
} else if (signal === "unhandledRejection") {
|
|
12647
|
+
const errorDetails = {
|
|
12648
|
+
signal,
|
|
12649
|
+
errorType: error?.constructor?.name || "Unknown",
|
|
12650
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
12651
|
+
errorStack: error instanceof Error ? error.stack : void 0,
|
|
12652
|
+
errorString: error?.toString(),
|
|
12653
|
+
errorJson: JSON.stringify(error, null, 2),
|
|
12654
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
12655
|
+
};
|
|
12656
|
+
logError(`${message} - Enhanced error details`, errorDetails);
|
|
12657
|
+
logError(`${message} - Raw error object`, { error, signal });
|
|
12659
12658
|
} else if (error) {
|
|
12660
12659
|
logError(`${message}`, { error, signal });
|
|
12661
12660
|
} else {
|
|
@@ -12700,30 +12699,43 @@ var McpServer = class {
|
|
|
12700
12699
|
});
|
|
12701
12700
|
}
|
|
12702
12701
|
async triggerScanForNewAvailableFixes() {
|
|
12703
|
-
|
|
12704
|
-
|
|
12705
|
-
|
|
12706
|
-
const isConnected = await gqlClient.verifyApiConnection();
|
|
12707
|
-
if (!isConnected) {
|
|
12708
|
-
logError("Failed to connect to the API, skipping scan");
|
|
12709
|
-
return;
|
|
12710
|
-
}
|
|
12711
|
-
if (process.env["WORKSPACE_FOLDER_PATHS"]) {
|
|
12712
|
-
logDebug("WORKSPACE_FOLDER_PATHS is set", {
|
|
12713
|
-
WORKSPACE_FOLDER_PATHS: process.env["WORKSPACE_FOLDER_PATHS"]
|
|
12702
|
+
try {
|
|
12703
|
+
const gqlClient = await createAuthenticatedMcpGQLClient({
|
|
12704
|
+
isBackgoundCall: true
|
|
12714
12705
|
});
|
|
12715
|
-
|
|
12716
|
-
|
|
12717
|
-
|
|
12718
|
-
|
|
12719
|
-
|
|
12720
|
-
|
|
12721
|
-
|
|
12722
|
-
|
|
12706
|
+
const isConnected = await gqlClient.verifyApiConnection();
|
|
12707
|
+
if (!isConnected) {
|
|
12708
|
+
logError("Failed to connect to the API, skipping background scan");
|
|
12709
|
+
return;
|
|
12710
|
+
}
|
|
12711
|
+
if (process.env["WORKSPACE_FOLDER_PATHS"]) {
|
|
12712
|
+
logDebug("WORKSPACE_FOLDER_PATHS is set", {
|
|
12713
|
+
WORKSPACE_FOLDER_PATHS: process.env["WORKSPACE_FOLDER_PATHS"]
|
|
12723
12714
|
});
|
|
12724
|
-
|
|
12725
|
-
|
|
12715
|
+
try {
|
|
12716
|
+
const checkForNewAvailableFixesTool = this.toolRegistry.getTool(
|
|
12717
|
+
MCP_TOOL_CHECK_FOR_NEW_AVAILABLE_FIXES
|
|
12718
|
+
);
|
|
12719
|
+
logInfo("Triggering periodic scan for new available fixes");
|
|
12720
|
+
checkForNewAvailableFixesTool.triggerScan({
|
|
12721
|
+
path: process.env["WORKSPACE_FOLDER_PATHS"],
|
|
12722
|
+
gqlClient
|
|
12723
|
+
});
|
|
12724
|
+
} catch (error) {
|
|
12725
|
+
logError("Error getting workspace folder path tool", { error });
|
|
12726
|
+
}
|
|
12727
|
+
}
|
|
12728
|
+
} catch (error) {
|
|
12729
|
+
if (error instanceof Error && (error.message.includes("Authentication") || error.message.includes("failed to connect to Mobb API"))) {
|
|
12730
|
+
logError(
|
|
12731
|
+
"Background scan skipped due to authentication failure. Please re-authenticate by running a manual scan.",
|
|
12732
|
+
{
|
|
12733
|
+
error: error.message
|
|
12734
|
+
}
|
|
12735
|
+
);
|
|
12736
|
+
return;
|
|
12726
12737
|
}
|
|
12738
|
+
logError("Unexpected error during background scan", { error });
|
|
12727
12739
|
}
|
|
12728
12740
|
}
|
|
12729
12741
|
async handleListToolsRequest(request) {
|
|
@@ -12737,7 +12749,11 @@ var McpServer = class {
|
|
|
12737
12749
|
logDebug("env", {
|
|
12738
12750
|
env: process.env
|
|
12739
12751
|
});
|
|
12740
|
-
|
|
12752
|
+
if (isAutoScan) {
|
|
12753
|
+
void this.triggerScanForNewAvailableFixes();
|
|
12754
|
+
} else {
|
|
12755
|
+
logDebug("Auto scan disabled, skipping triggerScanForNewAvailableFixes");
|
|
12756
|
+
}
|
|
12741
12757
|
const toolsDefinitions = this.toolRegistry.getAllTools();
|
|
12742
12758
|
const response = {
|
|
12743
12759
|
tools: toolsDefinitions.map((tool) => ({
|
|
@@ -12947,7 +12963,6 @@ var BaseTool = class {
|
|
|
12947
12963
|
|
|
12948
12964
|
// src/mcp/tools/checkForNewAvailableFixes/CheckForNewAvailableFixesService.ts
|
|
12949
12965
|
init_configs();
|
|
12950
|
-
import Configstore4 from "configstore";
|
|
12951
12966
|
|
|
12952
12967
|
// src/mcp/core/prompts.ts
|
|
12953
12968
|
init_configs();
|
|
@@ -13566,6 +13581,18 @@ var initializeSecurityReport = async (gqlClient, scanContext) => {
|
|
|
13566
13581
|
return repoUploadInfo;
|
|
13567
13582
|
} catch (error) {
|
|
13568
13583
|
const message = error.message;
|
|
13584
|
+
if (message.includes("Authentication hook unauthorized") || message.includes("access-denied")) {
|
|
13585
|
+
logError(
|
|
13586
|
+
"Authentication failed during security report initialization. Please re-authenticate.",
|
|
13587
|
+
{
|
|
13588
|
+
error: message
|
|
13589
|
+
}
|
|
13590
|
+
);
|
|
13591
|
+
throw new ReportInitializationError(
|
|
13592
|
+
"Authentication failed. Please re-authenticate and try again."
|
|
13593
|
+
);
|
|
13594
|
+
}
|
|
13595
|
+
logError("Error initializing security report", { error: message });
|
|
13569
13596
|
throw new ReportInitializationError(
|
|
13570
13597
|
`Error initializing security report: ${message}`
|
|
13571
13598
|
);
|
|
@@ -13646,7 +13673,8 @@ var executeSecurityScan = async ({
|
|
|
13646
13673
|
});
|
|
13647
13674
|
},
|
|
13648
13675
|
callbackStates: ["Finished" /* Finished */],
|
|
13649
|
-
timeoutInMs: MCP_VUL_REPORT_DIGEST_TIMEOUT_MS
|
|
13676
|
+
timeoutInMs: MCP_VUL_REPORT_DIGEST_TIMEOUT_MS,
|
|
13677
|
+
scanContext
|
|
13650
13678
|
});
|
|
13651
13679
|
} catch (error) {
|
|
13652
13680
|
logError(`[${scanContext}] Security analysis failed or timed out`, {
|
|
@@ -13680,6 +13708,8 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
13680
13708
|
__publicField(this, "intervalId", null);
|
|
13681
13709
|
__publicField(this, "isInitialScanComplete", false);
|
|
13682
13710
|
__publicField(this, "gqlClient", null);
|
|
13711
|
+
__publicField(this, "fullScanPathsScanned", []);
|
|
13712
|
+
this.fullScanPathsScanned = configStore.get("fullScanPathsScanned") || [];
|
|
13683
13713
|
}
|
|
13684
13714
|
static getInstance() {
|
|
13685
13715
|
if (!_CheckForNewAvailableFixesService.instance) {
|
|
@@ -13695,6 +13725,7 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
13695
13725
|
this.filesLastScanned = {};
|
|
13696
13726
|
this.freshFixes = [];
|
|
13697
13727
|
this.reportedFixes = [];
|
|
13728
|
+
this.fullScanPathsScanned = configStore.get("fullScanPathsScanned") || [];
|
|
13698
13729
|
if (this.intervalId) {
|
|
13699
13730
|
clearInterval(this.intervalId);
|
|
13700
13731
|
this.intervalId = null;
|
|
@@ -13715,61 +13746,84 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
13715
13746
|
});
|
|
13716
13747
|
if (!this.gqlClient) {
|
|
13717
13748
|
logInfo(`[${scanContext}] No GQL client found, skipping scan`);
|
|
13718
|
-
|
|
13719
|
-
}
|
|
13720
|
-
const isConnected = await this.gqlClient.verifyApiConnection();
|
|
13721
|
-
if (!isConnected) {
|
|
13722
|
-
logError(`[${scanContext}] Failed to connect to the API, scan aborted`);
|
|
13723
|
-
return;
|
|
13749
|
+
throw new Error("No GQL client found");
|
|
13724
13750
|
}
|
|
13725
|
-
|
|
13726
|
-
|
|
13727
|
-
|
|
13728
|
-
|
|
13729
|
-
|
|
13730
|
-
path: path13,
|
|
13731
|
-
isAllFilesScan
|
|
13732
|
-
});
|
|
13733
|
-
logDebug(`[${scanContext}] Active files`, { files });
|
|
13734
|
-
const filesToScan = files.filter((file) => {
|
|
13735
|
-
const lastScannedEditTime = this.filesLastScanned[file.fullPath];
|
|
13736
|
-
if (!lastScannedEditTime) {
|
|
13737
|
-
return true;
|
|
13751
|
+
try {
|
|
13752
|
+
const isConnected = await this.gqlClient.verifyApiConnection();
|
|
13753
|
+
if (!isConnected) {
|
|
13754
|
+
logError(`[${scanContext}] Failed to connect to the API, scan aborted`);
|
|
13755
|
+
throw new ApiConnectionError();
|
|
13738
13756
|
}
|
|
13739
|
-
|
|
13740
|
-
|
|
13741
|
-
|
|
13742
|
-
|
|
13743
|
-
|
|
13744
|
-
|
|
13745
|
-
|
|
13746
|
-
|
|
13747
|
-
|
|
13748
|
-
|
|
13749
|
-
|
|
13750
|
-
|
|
13751
|
-
|
|
13752
|
-
|
|
13753
|
-
|
|
13754
|
-
|
|
13755
|
-
|
|
13756
|
-
|
|
13757
|
-
|
|
13757
|
+
logDebug(
|
|
13758
|
+
`[${scanContext}] Connected to the API, assembling list of files to scan`,
|
|
13759
|
+
{ path: path13 }
|
|
13760
|
+
);
|
|
13761
|
+
const files = await getLocalFiles({
|
|
13762
|
+
path: path13,
|
|
13763
|
+
isAllFilesScan
|
|
13764
|
+
});
|
|
13765
|
+
logDebug(`[${scanContext}] Active files`, { files });
|
|
13766
|
+
const filesToScan = files.filter((file) => {
|
|
13767
|
+
const lastScannedEditTime = this.filesLastScanned[file.fullPath];
|
|
13768
|
+
if (!lastScannedEditTime) {
|
|
13769
|
+
return true;
|
|
13770
|
+
}
|
|
13771
|
+
return file.lastEdited > lastScannedEditTime;
|
|
13772
|
+
});
|
|
13773
|
+
if (filesToScan.length === 0) {
|
|
13774
|
+
logInfo(`[${scanContext}] No files require scanning`);
|
|
13775
|
+
return;
|
|
13776
|
+
}
|
|
13777
|
+
logDebug(`[${scanContext}] Files requiring security scan`, {
|
|
13778
|
+
filesToScan
|
|
13779
|
+
});
|
|
13780
|
+
const { fixReportId, projectId } = await scanFiles({
|
|
13781
|
+
fileList: filesToScan.map((file) => file.relativePath),
|
|
13782
|
+
repositoryPath: path13,
|
|
13783
|
+
gqlClient: this.gqlClient,
|
|
13784
|
+
isAllDetectionRulesScan,
|
|
13785
|
+
scanContext
|
|
13786
|
+
});
|
|
13787
|
+
logInfo(
|
|
13788
|
+
`[${scanContext}] Security scan completed for ${path13} reportId: ${fixReportId} projectId: ${projectId}`
|
|
13789
|
+
);
|
|
13790
|
+
if (isAllFilesScan) {
|
|
13791
|
+
return;
|
|
13792
|
+
}
|
|
13793
|
+
const fixes = await this.gqlClient.getReportFixesPaginated({
|
|
13794
|
+
reportId: fixReportId,
|
|
13795
|
+
offset: 0,
|
|
13796
|
+
limit: 1e3
|
|
13797
|
+
});
|
|
13798
|
+
const newFixes = fixes?.fixes?.filter(
|
|
13799
|
+
(fix) => !this.isFixAlreadyReported(fix)
|
|
13800
|
+
);
|
|
13801
|
+
logInfo(
|
|
13802
|
+
`[${scanContext}] Security fixes retrieved, total: ${fixes?.fixes?.length || 0}, new: ${newFixes?.length || 0}`
|
|
13803
|
+
);
|
|
13804
|
+
this.updateFreshFixesCache(newFixes || [], filesToScan);
|
|
13805
|
+
this.updateFilesScanTimestamps(filesToScan);
|
|
13806
|
+
this.isInitialScanComplete = true;
|
|
13807
|
+
} catch (error) {
|
|
13808
|
+
const errorMessage = error.message;
|
|
13809
|
+
if (errorMessage.includes("Authentication failed") || errorMessage.includes("access-denied") || errorMessage.includes("Authentication hook unauthorized")) {
|
|
13810
|
+
logError(
|
|
13811
|
+
"Periodic scan skipped due to authentication failure. Please re-authenticate by running a manual scan.",
|
|
13812
|
+
{
|
|
13813
|
+
error: errorMessage
|
|
13814
|
+
}
|
|
13815
|
+
);
|
|
13816
|
+
return;
|
|
13817
|
+
}
|
|
13818
|
+
if (errorMessage.includes("ReportInitializationError")) {
|
|
13819
|
+
logError("Periodic scan failed during report initialization", {
|
|
13820
|
+
error: errorMessage
|
|
13821
|
+
});
|
|
13822
|
+
return;
|
|
13823
|
+
}
|
|
13824
|
+
logError("Unexpected error during periodic security scan", { error });
|
|
13825
|
+
throw error;
|
|
13758
13826
|
}
|
|
13759
|
-
const fixes = await this.gqlClient.getReportFixesPaginated({
|
|
13760
|
-
reportId: fixReportId,
|
|
13761
|
-
offset: 0,
|
|
13762
|
-
limit: 1e3
|
|
13763
|
-
});
|
|
13764
|
-
const newFixes = fixes?.fixes?.filter(
|
|
13765
|
-
(fix) => !this.isFixAlreadyReported(fix)
|
|
13766
|
-
);
|
|
13767
|
-
logInfo(
|
|
13768
|
-
`[${scanContext}] Security fixes retrieved, total: ${fixes?.fixes?.length || 0}, new: ${newFixes?.length || 0}`
|
|
13769
|
-
);
|
|
13770
|
-
this.updateFreshFixesCache(newFixes || [], filesToScan);
|
|
13771
|
-
this.updateFilesScanTimestamps(filesToScan);
|
|
13772
|
-
this.isInitialScanComplete = true;
|
|
13773
13827
|
}
|
|
13774
13828
|
updateFreshFixesCache(newFixes, filesToScan) {
|
|
13775
13829
|
this.freshFixes = this.freshFixes.filter((fix) => !this.isFixFromOldScan(fix, filesToScan)).concat(newFixes).sort((a, b) => {
|
|
@@ -13823,7 +13877,7 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
13823
13877
|
if (!this.intervalId) {
|
|
13824
13878
|
this.startPeriodicScanning(path13);
|
|
13825
13879
|
this.executeInitialScan(path13);
|
|
13826
|
-
this.executeInitialFullScan(path13);
|
|
13880
|
+
void this.executeInitialFullScan(path13);
|
|
13827
13881
|
}
|
|
13828
13882
|
}
|
|
13829
13883
|
startPeriodicScanning(path13) {
|
|
@@ -13840,37 +13894,46 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
|
|
|
13840
13894
|
});
|
|
13841
13895
|
}, MCP_PERIODIC_CHECK_INTERVAL);
|
|
13842
13896
|
}
|
|
13843
|
-
executeInitialFullScan(path13) {
|
|
13844
|
-
|
|
13845
|
-
|
|
13846
|
-
|
|
13847
|
-
|
|
13848
|
-
|
|
13849
|
-
|
|
13897
|
+
async executeInitialFullScan(path13) {
|
|
13898
|
+
const scanContext = "FULL_SCAN";
|
|
13899
|
+
logDebug(`[${scanContext}] Triggering initial full security scan`, { path: path13 });
|
|
13900
|
+
logDebug(`[${scanContext}] Full scan paths scanned`, {
|
|
13901
|
+
fullScanPathsScanned: this.fullScanPathsScanned
|
|
13902
|
+
});
|
|
13903
|
+
if (this.fullScanPathsScanned.includes(path13)) {
|
|
13904
|
+
logDebug(`[${scanContext}] Full scan already executed for this path`, {
|
|
13905
|
+
path: path13
|
|
13906
|
+
});
|
|
13850
13907
|
return;
|
|
13851
13908
|
}
|
|
13852
|
-
|
|
13853
|
-
|
|
13854
|
-
|
|
13855
|
-
|
|
13856
|
-
|
|
13857
|
-
|
|
13858
|
-
|
|
13909
|
+
configStore.set("fullScanPathsScanned", [
|
|
13910
|
+
...this.fullScanPathsScanned,
|
|
13911
|
+
path13
|
|
13912
|
+
]);
|
|
13913
|
+
try {
|
|
13914
|
+
await this.scanForSecurityVulnerabilities({
|
|
13915
|
+
path: path13,
|
|
13916
|
+
isAllFilesScan: true,
|
|
13917
|
+
isAllDetectionRulesScan: true,
|
|
13918
|
+
scanContext: "FULL_SCAN"
|
|
13919
|
+
});
|
|
13920
|
+
if (!this.fullScanPathsScanned.includes(path13)) {
|
|
13921
|
+
this.fullScanPathsScanned.push(path13);
|
|
13922
|
+
configStore.set("fullScanPathsScanned", this.fullScanPathsScanned);
|
|
13923
|
+
}
|
|
13924
|
+
logInfo(`[${scanContext}] Full scan completed`, { path: path13 });
|
|
13925
|
+
} catch (error) {
|
|
13859
13926
|
logError("Error during initial full security scan", { error });
|
|
13860
|
-
}
|
|
13861
|
-
const fullScanPathsScanned2 = mobbConfigStore2.get("fullScanPathsScanned") || [];
|
|
13862
|
-
fullScanPathsScanned2.push(path13);
|
|
13863
|
-
mobbConfigStore2.set("fullScanPathsScanned", fullScanPathsScanned2);
|
|
13864
|
-
logDebug("Full scan completed", { path: path13 });
|
|
13865
|
-
});
|
|
13927
|
+
}
|
|
13866
13928
|
}
|
|
13867
13929
|
executeInitialScan(path13) {
|
|
13868
|
-
|
|
13930
|
+
const scanContext = "BACKGROUND_INITIAL";
|
|
13931
|
+
logDebug(`[${scanContext}] Triggering initial security scan`, { path: path13 });
|
|
13869
13932
|
this.scanForSecurityVulnerabilities({
|
|
13870
13933
|
path: path13,
|
|
13871
13934
|
scanContext: "BACKGROUND_INITIAL"
|
|
13872
13935
|
}).catch((error) => {
|
|
13873
|
-
logError(
|
|
13936
|
+
logError(`[${scanContext}] Error during initial security scan`, { error });
|
|
13874
13937
|
});
|
|
13875
13938
|
}
|
|
13876
13939
|
generateFreshFixesResponse() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mobbdev",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.126",
|
|
4
4
|
"description": "Automated secure code remediation tool",
|
|
5
5
|
"repository": "git+https://github.com/mobb-dev/bugsy.git",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -11,13 +11,14 @@
|
|
|
11
11
|
"postinstall": "node ./src/post_install/cx_install.mjs",
|
|
12
12
|
"build": "tsc && tsup-node --env.NODE_ENV production",
|
|
13
13
|
"build:dev": "tsup-node --env.NODE_ENV development",
|
|
14
|
+
"increment-version": "./src/scripts/increment-version.sh",
|
|
14
15
|
"test:mcp": "pnpm run build && vitest run __tests__/mcp/",
|
|
15
16
|
"test:mcp:watch": "vitest watch __tests__/mcp/",
|
|
16
17
|
"test:mcp:verbose": "pnpm run build && NODE_ENV=test VERBOSE=true vitest run __tests__/mcp/",
|
|
17
|
-
"test:mcp:integration": "pnpm run build && GIT_PROXY_HOST=http://tinyproxy:8888 API_URL=http://app-api:8080/v1/graphql TOKEN=$(../../scripts/login_auth0.sh) vitest run __tests__/integration.test.ts -t 'mcp|MCP'",
|
|
18
|
+
"test:mcp:integration": "pnpm run build && GIT_PROXY_HOST=http://tinyproxy:8888 API_URL=http://app-api:8080/v1/graphql TOKEN=$(../../scripts/login_auth0.sh) vitest run --sequence.concurrent=false false __tests__/integration.test.ts -t 'mcp|MCP'",
|
|
18
19
|
"test:mcp:all": "pnpm run test:mcp && pnpm run test:mcp:integration && cd ./__e2e__ && npm i && npm run test:mcp",
|
|
19
20
|
"test:unit": "GIT_PROXY_HOST=http://tinyproxy:8888 TOKEN=$(../../scripts/login_auth0.sh) vitest run --exclude='**/__tests__/integration.test.ts' --exclude='**/__tests__/mcp/**'",
|
|
20
|
-
"test:integration": "GIT_PROXY_HOST=http://tinyproxy:8888 TOKEN=$(../../scripts/login_auth0.sh) vitest run __tests__/integration.test.ts",
|
|
21
|
+
"test:integration": "GIT_PROXY_HOST=http://tinyproxy:8888 TOKEN=$(../../scripts/login_auth0.sh) vitest run --sequence.concurrent=false false __tests__/integration.test.ts",
|
|
21
22
|
"test:integration:watch": "GIT_PROXY_HOST=http://tinyproxy:8888 TOKEN=$(../../scripts/login_auth0.sh) vitest watch run __tests__/integration.test.ts",
|
|
22
23
|
"test": "pnpm run test:unit && pnpm run test:mcp && pnpm run test:integration",
|
|
23
24
|
"test:ado": "GIT_PROXY_HOST=http://tinyproxy:8888 TOKEN=$(../../scripts/login_auth0.sh) vitest run ado.test",
|
|
@@ -27,7 +28,7 @@
|
|
|
27
28
|
"test:cli:main": "GIT_PROXY_HOST=http://tinyproxy:8888 TOKEN=$(../../scripts/login_auth0.sh) vitest run cli-main.test",
|
|
28
29
|
"test:coverage": "GIT_PROXY_HOST=http://tinyproxy:8888 TOKEN=$(../../scripts/login_auth0.sh) vitest run --coverage",
|
|
29
30
|
"test:watch": "TOKEN=$(../../scripts/login_auth0.sh) vitest",
|
|
30
|
-
"test:integration:proxy": "GIT_PROXY_HOST=http://tinyproxy:8888 HTTP_PROXY=http://localhost:8888 API_URL=http://app-api:8080/v1/graphql TOKEN=$(../../scripts/login_auth0.sh) vitest run integration.test.ts",
|
|
31
|
+
"test:integration:proxy": "GIT_PROXY_HOST=http://tinyproxy:8888 HTTP_PROXY=http://localhost:8888 API_URL=http://app-api:8080/v1/graphql TOKEN=$(../../scripts/login_auth0.sh) vitest run --sequence.concurrent=false false integration.test.ts",
|
|
31
32
|
"lint": "eslint --cache --max-warnings 0 --ignore-path .eslintignore --ext .ts,.tsx,.jsx,.graphql .",
|
|
32
33
|
"lint:fix": "eslint --fix --cache --max-warnings 0 --ignore-path .eslintignore --ext .js,.ts,.tsx,.jsx,.graphql . && prettier --write \"src/**/*.graphql\"",
|
|
33
34
|
"lint:fix:files": "eslint --fix --cache --max-warnings 0 --ignore-path .eslintignore --ext .js,.ts,.tsx,.jsx,.graphql",
|