claude-crap 0.3.3 → 0.3.5
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/CHANGELOG.md +36 -0
- package/README.md +116 -472
- package/dist/dashboard/server.d.ts.map +1 -1
- package/dist/dashboard/server.js +116 -3
- package/dist/dashboard/server.js.map +1 -1
- package/dist/sarif/sarif-store.d.ts.map +1 -1
- package/dist/sarif/sarif-store.js +1 -1
- package/dist/sarif/sarif-store.js.map +1 -1
- package/package.json +6 -2
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.mcp.json +2 -3
- package/plugin/bundle/launcher.mjs +112 -0
- package/plugin/bundle/mcp-server.mjs +120 -49
- package/plugin/bundle/mcp-server.mjs.map +3 -3
- package/plugin/eslint.config.mjs +27 -0
- package/plugin/hooks/lib/hook-io.mjs +1 -1
- package/plugin/hooks/pre-tool-use.mjs +1 -1
- package/plugin/launcher.mjs +112 -0
- package/plugin/package-lock.json +2714 -0
- package/plugin/package.json +5 -1
- package/scripts/bundle-plugin.mjs +30 -0
- package/scripts/doctor.mjs +2 -2
- package/src/dashboard/server.ts +144 -3
- package/src/sarif/sarif-store.ts +1 -0
|
@@ -7235,8 +7235,8 @@ function loadConfig() {
|
|
|
7235
7235
|
}
|
|
7236
7236
|
|
|
7237
7237
|
// src/dashboard/server.ts
|
|
7238
|
-
import { promises as fs2 } from "node:fs";
|
|
7239
|
-
import { dirname as dirname2, resolve as resolve2 } from "node:path";
|
|
7238
|
+
import { promises as fs2, existsSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
|
|
7239
|
+
import { dirname as dirname2, join as join2, resolve as resolve2 } from "node:path";
|
|
7240
7240
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
7241
7241
|
import Fastify from "fastify";
|
|
7242
7242
|
import fastifyStatic from "@fastify/static";
|
|
@@ -7416,7 +7416,7 @@ async function startDashboard(options) {
|
|
|
7416
7416
|
root: publicRoot,
|
|
7417
7417
|
prefix: "/"
|
|
7418
7418
|
});
|
|
7419
|
-
fastify.get("/api/health", async () => ({ status: "ok", server: "claude-crap", version: "0.3.
|
|
7419
|
+
fastify.get("/api/health", async () => ({ status: "ok", server: "claude-crap", version: "0.3.5" }));
|
|
7420
7420
|
fastify.get("/api/score", async () => {
|
|
7421
7421
|
const stats = await workspaceStatsProvider();
|
|
7422
7422
|
const score = await buildScore(config, sarifStore, stats, urlOf(fastify, config));
|
|
@@ -7426,12 +7426,16 @@ async function startDashboard(options) {
|
|
|
7426
7426
|
fastify.get("/", async (_request, reply) => {
|
|
7427
7427
|
return reply.sendFile("index.html");
|
|
7428
7428
|
});
|
|
7429
|
+
const pidFilePath = resolvePidFilePath(config);
|
|
7430
|
+
await killStaleDashboard(pidFilePath, config.dashboardPort, logger2);
|
|
7429
7431
|
await fastify.listen({ port: config.dashboardPort, host: "127.0.0.1" });
|
|
7430
7432
|
const url = `http://127.0.0.1:${config.dashboardPort}`;
|
|
7431
7433
|
logger2.info({ url, publicRoot }, "claude-crap dashboard listening");
|
|
7434
|
+
writePidFile(pidFilePath, config.dashboardPort);
|
|
7432
7435
|
return {
|
|
7433
7436
|
url,
|
|
7434
7437
|
async close() {
|
|
7438
|
+
removePidFile(pidFilePath);
|
|
7435
7439
|
await fastify.close();
|
|
7436
7440
|
}
|
|
7437
7441
|
};
|
|
@@ -7471,6 +7475,72 @@ function urlOf(fastify, config) {
|
|
|
7471
7475
|
}
|
|
7472
7476
|
return `http://127.0.0.1:${config.dashboardPort}`;
|
|
7473
7477
|
}
|
|
7478
|
+
function resolvePidFilePath(config) {
|
|
7479
|
+
return join2(config.pluginRoot, ".claude-crap", "dashboard.pid");
|
|
7480
|
+
}
|
|
7481
|
+
function writePidFile(path, port) {
|
|
7482
|
+
const data = {
|
|
7483
|
+
pid: process.pid,
|
|
7484
|
+
port,
|
|
7485
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7486
|
+
};
|
|
7487
|
+
try {
|
|
7488
|
+
writeFileSync(path, JSON.stringify(data, null, 2) + "\n");
|
|
7489
|
+
} catch {
|
|
7490
|
+
}
|
|
7491
|
+
}
|
|
7492
|
+
function removePidFile(path) {
|
|
7493
|
+
try {
|
|
7494
|
+
unlinkSync(path);
|
|
7495
|
+
} catch {
|
|
7496
|
+
}
|
|
7497
|
+
}
|
|
7498
|
+
function isPidAlive(pid) {
|
|
7499
|
+
try {
|
|
7500
|
+
process.kill(pid, 0);
|
|
7501
|
+
return true;
|
|
7502
|
+
} catch {
|
|
7503
|
+
return false;
|
|
7504
|
+
}
|
|
7505
|
+
}
|
|
7506
|
+
async function killStaleDashboard(pidFilePath, port, logger2) {
|
|
7507
|
+
if (!existsSync(pidFilePath)) return;
|
|
7508
|
+
let stale;
|
|
7509
|
+
try {
|
|
7510
|
+
stale = JSON.parse(readFileSync(pidFilePath, "utf8"));
|
|
7511
|
+
} catch {
|
|
7512
|
+
removePidFile(pidFilePath);
|
|
7513
|
+
return;
|
|
7514
|
+
}
|
|
7515
|
+
if (!isPidAlive(stale.pid)) {
|
|
7516
|
+
logger2.info({ stalePid: stale.pid }, "stale dashboard PID file found (process dead), removing");
|
|
7517
|
+
removePidFile(pidFilePath);
|
|
7518
|
+
return;
|
|
7519
|
+
}
|
|
7520
|
+
logger2.info(
|
|
7521
|
+
{ stalePid: stale.pid, port: stale.port, startedAt: stale.startedAt },
|
|
7522
|
+
"killing stale dashboard process from previous session"
|
|
7523
|
+
);
|
|
7524
|
+
try {
|
|
7525
|
+
process.kill(stale.pid, "SIGTERM");
|
|
7526
|
+
} catch {
|
|
7527
|
+
removePidFile(pidFilePath);
|
|
7528
|
+
return;
|
|
7529
|
+
}
|
|
7530
|
+
for (let i = 0; i < 30; i++) {
|
|
7531
|
+
if (!isPidAlive(stale.pid)) break;
|
|
7532
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
7533
|
+
}
|
|
7534
|
+
if (isPidAlive(stale.pid)) {
|
|
7535
|
+
try {
|
|
7536
|
+
process.kill(stale.pid, "SIGKILL");
|
|
7537
|
+
} catch {
|
|
7538
|
+
}
|
|
7539
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
7540
|
+
}
|
|
7541
|
+
removePidFile(pidFilePath);
|
|
7542
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
7543
|
+
}
|
|
7474
7544
|
async function buildScore(config, sarifStore, workspace, dashboardUrl) {
|
|
7475
7545
|
return computeProjectScore({
|
|
7476
7546
|
workspaceRoot: config.pluginRoot,
|
|
@@ -7514,7 +7584,7 @@ function computeCrap(input, threshold) {
|
|
|
7514
7584
|
|
|
7515
7585
|
// src/metrics/workspace-walker.ts
|
|
7516
7586
|
import { promises as fs3 } from "node:fs";
|
|
7517
|
-
import { join as
|
|
7587
|
+
import { join as join3 } from "node:path";
|
|
7518
7588
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
7519
7589
|
"node_modules",
|
|
7520
7590
|
".git",
|
|
@@ -7569,7 +7639,7 @@ async function estimateWorkspaceLoc(workspaceRoot) {
|
|
|
7569
7639
|
for (const entry of entries) {
|
|
7570
7640
|
if (truncated) return;
|
|
7571
7641
|
if (entry.name.startsWith(".") && entry.name !== ".claude-plugin") continue;
|
|
7572
|
-
const full =
|
|
7642
|
+
const full = join3(dir, entry.name);
|
|
7573
7643
|
if (entry.isDirectory()) {
|
|
7574
7644
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
7575
7645
|
await walk2(full);
|
|
@@ -7602,7 +7672,7 @@ async function estimateWorkspaceLoc(workspaceRoot) {
|
|
|
7602
7672
|
|
|
7603
7673
|
// src/sarif/sarif-store.ts
|
|
7604
7674
|
import { promises as fs4 } from "node:fs";
|
|
7605
|
-
import { dirname as dirname3, isAbsolute, join as
|
|
7675
|
+
import { dirname as dirname3, isAbsolute, join as join4, resolve as resolve3 } from "node:path";
|
|
7606
7676
|
|
|
7607
7677
|
// src/sarif/sarif-builder.ts
|
|
7608
7678
|
function buildSarifDocument(tool, findings) {
|
|
@@ -7665,7 +7735,7 @@ var SarifStore = class {
|
|
|
7665
7735
|
toolInvocations = 0;
|
|
7666
7736
|
constructor(options) {
|
|
7667
7737
|
const dir = isAbsolute(options.outputDir) ? options.outputDir : resolve3(options.workspaceRoot, options.outputDir);
|
|
7668
|
-
this.filePath =
|
|
7738
|
+
this.filePath = join4(dir, options.fileName ?? "latest.sarif");
|
|
7669
7739
|
}
|
|
7670
7740
|
/**
|
|
7671
7741
|
* Absolute path to the consolidated SARIF file on disk.
|
|
@@ -7736,7 +7806,8 @@ var SarifStore = class {
|
|
|
7736
7806
|
return;
|
|
7737
7807
|
}
|
|
7738
7808
|
throw new Error(
|
|
7739
|
-
`[sarif-store] Failed to load consolidated report at ${this.filePath}: ${error.message}
|
|
7809
|
+
`[sarif-store] Failed to load consolidated report at ${this.filePath}: ${error.message}`,
|
|
7810
|
+
{ cause: err }
|
|
7740
7811
|
);
|
|
7741
7812
|
}
|
|
7742
7813
|
}
|
|
@@ -7938,8 +8009,8 @@ function validateSarifDocument(doc) {
|
|
|
7938
8009
|
}
|
|
7939
8010
|
|
|
7940
8011
|
// src/crap-config.ts
|
|
7941
|
-
import { readFileSync } from "node:fs";
|
|
7942
|
-
import { join as
|
|
8012
|
+
import { readFileSync as readFileSync2 } from "node:fs";
|
|
8013
|
+
import { join as join5 } from "node:path";
|
|
7943
8014
|
var STRICTNESS_VALUES = ["strict", "warn", "advisory"];
|
|
7944
8015
|
var DEFAULT_STRICTNESS = "strict";
|
|
7945
8016
|
var CrapConfigError = class extends Error {
|
|
@@ -7964,10 +8035,10 @@ function loadCrapConfig(options) {
|
|
|
7964
8035
|
return { strictness: DEFAULT_STRICTNESS, strictnessSource: "default" };
|
|
7965
8036
|
}
|
|
7966
8037
|
function readFromFile(workspaceRoot) {
|
|
7967
|
-
const filePath =
|
|
8038
|
+
const filePath = join5(workspaceRoot, ".claude-crap.json");
|
|
7968
8039
|
let raw;
|
|
7969
8040
|
try {
|
|
7970
|
-
raw =
|
|
8041
|
+
raw = readFileSync2(filePath, "utf8");
|
|
7971
8042
|
} catch (err) {
|
|
7972
8043
|
const error = err;
|
|
7973
8044
|
if (error.code === "ENOENT") return null;
|
|
@@ -8010,7 +8081,7 @@ function isStrictness(value) {
|
|
|
8010
8081
|
|
|
8011
8082
|
// src/tools/test-harness.ts
|
|
8012
8083
|
import { promises as fs5 } from "node:fs";
|
|
8013
|
-
import { basename, dirname as dirname4, extname, isAbsolute as isAbsolute2, join as
|
|
8084
|
+
import { basename, dirname as dirname4, extname, isAbsolute as isAbsolute2, join as join6, relative, resolve as resolve4, sep } from "node:path";
|
|
8014
8085
|
var TEST_SUFFIX_PATTERN = /\.(test|spec)\./;
|
|
8015
8086
|
function isTestFile(filePath) {
|
|
8016
8087
|
const base = basename(filePath);
|
|
@@ -8028,21 +8099,21 @@ function candidatePaths(workspaceRoot, filePath) {
|
|
|
8028
8099
|
const relFromRoot = relative(absWorkspace, absSource);
|
|
8029
8100
|
const relDir = dirname4(relFromRoot);
|
|
8030
8101
|
const candidates = /* @__PURE__ */ new Set();
|
|
8031
|
-
candidates.add(
|
|
8032
|
-
candidates.add(
|
|
8033
|
-
candidates.add(
|
|
8034
|
-
candidates.add(
|
|
8102
|
+
candidates.add(join6(dir, `${base}.test${ext}`));
|
|
8103
|
+
candidates.add(join6(dir, `${base}.spec${ext}`));
|
|
8104
|
+
candidates.add(join6(dir, "__tests__", `${base}.test${ext}`));
|
|
8105
|
+
candidates.add(join6(dir, "__tests__", `${base}.spec${ext}`));
|
|
8035
8106
|
for (const testRoot of ["tests", "test", "__tests__"]) {
|
|
8036
|
-
candidates.add(
|
|
8037
|
-
candidates.add(
|
|
8038
|
-
candidates.add(
|
|
8107
|
+
candidates.add(join6(absWorkspace, testRoot, relDir, `${base}.test${ext}`));
|
|
8108
|
+
candidates.add(join6(absWorkspace, testRoot, relDir, `${base}.spec${ext}`));
|
|
8109
|
+
candidates.add(join6(absWorkspace, testRoot, relDir, `${base}${ext}`));
|
|
8039
8110
|
}
|
|
8040
8111
|
let current = dir;
|
|
8041
8112
|
while (current.length >= absWorkspace.length) {
|
|
8042
8113
|
for (const testRoot of ["tests", "test", "__tests__"]) {
|
|
8043
|
-
candidates.add(
|
|
8044
|
-
candidates.add(
|
|
8045
|
-
candidates.add(
|
|
8114
|
+
candidates.add(join6(current, testRoot, `${base}.test${ext}`));
|
|
8115
|
+
candidates.add(join6(current, testRoot, `${base}.spec${ext}`));
|
|
8116
|
+
candidates.add(join6(current, testRoot, `${base}${ext}`));
|
|
8046
8117
|
}
|
|
8047
8118
|
if (current === absWorkspace) break;
|
|
8048
8119
|
const parent = dirname4(current);
|
|
@@ -8050,9 +8121,9 @@ function candidatePaths(workspaceRoot, filePath) {
|
|
|
8050
8121
|
current = parent;
|
|
8051
8122
|
}
|
|
8052
8123
|
if (ext === ".py") {
|
|
8053
|
-
candidates.add(
|
|
8054
|
-
candidates.add(
|
|
8055
|
-
candidates.add(
|
|
8124
|
+
candidates.add(join6(dir, `test_${base}.py`));
|
|
8125
|
+
candidates.add(join6(absWorkspace, "tests", `test_${base}.py`));
|
|
8126
|
+
candidates.add(join6(absWorkspace, "tests", relDir, `test_${base}.py`));
|
|
8056
8127
|
}
|
|
8057
8128
|
return Array.from(candidates);
|
|
8058
8129
|
}
|
|
@@ -8086,12 +8157,12 @@ function resolveWithinWorkspace(workspaceRoot, filePath) {
|
|
|
8086
8157
|
}
|
|
8087
8158
|
|
|
8088
8159
|
// src/scanner/auto-scan.ts
|
|
8089
|
-
import { existsSync as
|
|
8090
|
-
import { join as
|
|
8160
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
8161
|
+
import { join as join10 } from "node:path";
|
|
8091
8162
|
|
|
8092
8163
|
// src/scanner/detector.ts
|
|
8093
|
-
import { existsSync, readFileSync as
|
|
8094
|
-
import { join as
|
|
8164
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
|
|
8165
|
+
import { join as join7 } from "node:path";
|
|
8095
8166
|
import { execFile } from "node:child_process";
|
|
8096
8167
|
var SCANNER_SIGNALS = {
|
|
8097
8168
|
eslint: {
|
|
@@ -8145,8 +8216,8 @@ var SCANNER_SIGNALS = {
|
|
|
8145
8216
|
function probeConfigFiles(workspaceRoot, scanner) {
|
|
8146
8217
|
const signals = SCANNER_SIGNALS[scanner];
|
|
8147
8218
|
for (const file of signals.configFiles) {
|
|
8148
|
-
const fullPath =
|
|
8149
|
-
if (
|
|
8219
|
+
const fullPath = join7(workspaceRoot, file);
|
|
8220
|
+
if (existsSync2(fullPath)) {
|
|
8150
8221
|
return { found: true, path: fullPath };
|
|
8151
8222
|
}
|
|
8152
8223
|
}
|
|
@@ -8155,10 +8226,10 @@ function probeConfigFiles(workspaceRoot, scanner) {
|
|
|
8155
8226
|
function probePackageJson(workspaceRoot, scanner) {
|
|
8156
8227
|
const signals = SCANNER_SIGNALS[scanner];
|
|
8157
8228
|
if (signals.packageJsonKeys.length === 0) return false;
|
|
8158
|
-
const pkgPath =
|
|
8159
|
-
if (!
|
|
8229
|
+
const pkgPath = join7(workspaceRoot, "package.json");
|
|
8230
|
+
if (!existsSync2(pkgPath)) return false;
|
|
8160
8231
|
try {
|
|
8161
|
-
const raw =
|
|
8232
|
+
const raw = readFileSync3(pkgPath, "utf-8");
|
|
8162
8233
|
const pkg = JSON.parse(raw);
|
|
8163
8234
|
const deps = {
|
|
8164
8235
|
...typeof pkg.dependencies === "object" && pkg.dependencies !== null ? pkg.dependencies : {},
|
|
@@ -8218,8 +8289,8 @@ async function detectScanners(workspaceRoot) {
|
|
|
8218
8289
|
|
|
8219
8290
|
// src/scanner/runner.ts
|
|
8220
8291
|
import { execFile as execFile2 } from "node:child_process";
|
|
8221
|
-
import { readFileSync as
|
|
8222
|
-
import { join as
|
|
8292
|
+
import { readFileSync as readFileSync4, existsSync as existsSync3 } from "node:fs";
|
|
8293
|
+
import { join as join8 } from "node:path";
|
|
8223
8294
|
function getScannerCommand(scanner, workspaceRoot) {
|
|
8224
8295
|
switch (scanner) {
|
|
8225
8296
|
case "eslint":
|
|
@@ -8249,7 +8320,7 @@ function getScannerCommand(scanner, workspaceRoot) {
|
|
|
8249
8320
|
args: ["stryker", "run"],
|
|
8250
8321
|
timeoutMs: 3e5,
|
|
8251
8322
|
nonZeroIsNormal: false,
|
|
8252
|
-
outputFile:
|
|
8323
|
+
outputFile: join8(workspaceRoot, "reports", "mutation", "mutation.json")
|
|
8253
8324
|
};
|
|
8254
8325
|
}
|
|
8255
8326
|
}
|
|
@@ -8272,9 +8343,9 @@ function runScanner(scanner, workspaceRoot) {
|
|
|
8272
8343
|
const durationMs = Date.now() - start;
|
|
8273
8344
|
const isFatalError = cmd.nonZeroIsNormal && err && (!stdout?.trim() || stderr?.includes("Oops!") || stderr?.includes("couldn't find"));
|
|
8274
8345
|
if (err && (!cmd.nonZeroIsNormal || isFatalError)) {
|
|
8275
|
-
if (cmd.outputFile &&
|
|
8346
|
+
if (cmd.outputFile && existsSync3(cmd.outputFile)) {
|
|
8276
8347
|
try {
|
|
8277
|
-
const fileOutput =
|
|
8348
|
+
const fileOutput = readFileSync4(cmd.outputFile, "utf-8");
|
|
8278
8349
|
resolve6({
|
|
8279
8350
|
scanner,
|
|
8280
8351
|
success: true,
|
|
@@ -8295,9 +8366,9 @@ function runScanner(scanner, workspaceRoot) {
|
|
|
8295
8366
|
return;
|
|
8296
8367
|
}
|
|
8297
8368
|
if (cmd.outputFile) {
|
|
8298
|
-
if (
|
|
8369
|
+
if (existsSync3(cmd.outputFile)) {
|
|
8299
8370
|
try {
|
|
8300
|
-
const fileOutput =
|
|
8371
|
+
const fileOutput = readFileSync4(cmd.outputFile, "utf-8");
|
|
8301
8372
|
resolve6({
|
|
8302
8373
|
scanner,
|
|
8303
8374
|
success: true,
|
|
@@ -8348,11 +8419,11 @@ function runScanner(scanner, workspaceRoot) {
|
|
|
8348
8419
|
}
|
|
8349
8420
|
|
|
8350
8421
|
// src/scanner/bootstrap.ts
|
|
8351
|
-
import { existsSync as
|
|
8352
|
-
import { join as
|
|
8422
|
+
import { existsSync as existsSync4, writeFileSync as writeFileSync2, readdirSync } from "node:fs";
|
|
8423
|
+
import { join as join9 } from "node:path";
|
|
8353
8424
|
import { execFile as execFile3 } from "node:child_process";
|
|
8354
8425
|
function detectProjectType(workspaceRoot) {
|
|
8355
|
-
const has = (file) =>
|
|
8426
|
+
const has = (file) => existsSync4(join9(workspaceRoot, file));
|
|
8356
8427
|
if (has("package.json")) {
|
|
8357
8428
|
if (has("tsconfig.json")) return "typescript";
|
|
8358
8429
|
return "javascript";
|
|
@@ -8440,8 +8511,8 @@ function npmInstall(workspaceRoot, packages) {
|
|
|
8440
8511
|
});
|
|
8441
8512
|
}
|
|
8442
8513
|
function writeEslintConfigFile(workspaceRoot, isTypeScript) {
|
|
8443
|
-
const configPath =
|
|
8444
|
-
if (
|
|
8514
|
+
const configPath = join9(workspaceRoot, "eslint.config.mjs");
|
|
8515
|
+
if (existsSync4(configPath)) {
|
|
8445
8516
|
return {
|
|
8446
8517
|
action: "create eslint.config.mjs",
|
|
8447
8518
|
success: true,
|
|
@@ -8449,7 +8520,7 @@ function writeEslintConfigFile(workspaceRoot, isTypeScript) {
|
|
|
8449
8520
|
};
|
|
8450
8521
|
}
|
|
8451
8522
|
try {
|
|
8452
|
-
|
|
8523
|
+
writeFileSync2(configPath, generateEslintConfig(isTypeScript), "utf-8");
|
|
8453
8524
|
return {
|
|
8454
8525
|
action: "create eslint.config.mjs",
|
|
8455
8526
|
success: true,
|
|
@@ -8666,7 +8737,7 @@ async function autoScan(workspaceRoot, sarifStore, logger2) {
|
|
|
8666
8737
|
".eslintrc.json"
|
|
8667
8738
|
];
|
|
8668
8739
|
const eslintDetected = available.some((d) => d.scanner === "eslint");
|
|
8669
|
-
const hasEslintConfig = eslintConfigFiles.some((f) =>
|
|
8740
|
+
const hasEslintConfig = eslintConfigFiles.some((f) => existsSync5(join10(workspaceRoot, f)));
|
|
8670
8741
|
if (eslintDetected && !hasEslintConfig) {
|
|
8671
8742
|
logger2.info("auto-scan: ESLint detected but no config \u2014 running bootstrap");
|
|
8672
8743
|
try {
|