aislop 0.5.0 → 0.6.0
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/README.md +4 -4
- package/dist/cli.js +1748 -127
- package/dist/index.js +471 -128
- package/dist/{json-BScQXSOX.js → json-DcE9soYJ.js} +1 -1
- package/dist/{version-CxBRws3M.js → version-C2lM_2fE.js} +1 -1
- package/package.json +4 -4
- package/scripts/postinstall-tools.mjs +2 -2
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as ENGINE_INFO, r as getEngineLabel, t as APP_VERSION } from "./version-
|
|
1
|
+
import { n as ENGINE_INFO, r as getEngineLabel, t as APP_VERSION } from "./version-C2lM_2fE.js";
|
|
2
2
|
import { n as runSubprocess, t as isToolInstalled } from "./subprocess-CQUJDGgn.js";
|
|
3
3
|
import { r as runGenericLinter, t as fixRubyLint } from "./generic-BrcWMW7E.js";
|
|
4
4
|
import { n as runExpoDoctor } from "./expo-doctor-Bz0LZhQ6.js";
|
|
@@ -1568,7 +1568,7 @@ const detectSwallowedExceptions = async (context) => {
|
|
|
1568
1568
|
};
|
|
1569
1569
|
|
|
1570
1570
|
//#endregion
|
|
1571
|
-
//#region src/engines/ai-slop/narrative-comments.ts
|
|
1571
|
+
//#region src/engines/ai-slop/narrative-comments-patterns.ts
|
|
1572
1572
|
const DECORATIVE_SEPARATOR = /^[-=─━~_*#]{6,}$/;
|
|
1573
1573
|
const DECORATIVE_SECTION_HEADER = /^[-=─━~_*#]{3,}[\s\S]+?[-=─━~_*#]{3,}$/;
|
|
1574
1574
|
const SECTION_HEADER = /^(Phase|Step|Section|Part)\s+\d+[:.-]/i;
|
|
@@ -1611,7 +1611,49 @@ const MEANINGFUL_JSDOC_TAGS = new Set([
|
|
|
1611
1611
|
"todo",
|
|
1612
1612
|
"link",
|
|
1613
1613
|
"license",
|
|
1614
|
-
"preserve"
|
|
1614
|
+
"preserve",
|
|
1615
|
+
"swagger",
|
|
1616
|
+
"openapi",
|
|
1617
|
+
"route",
|
|
1618
|
+
"group",
|
|
1619
|
+
"summary",
|
|
1620
|
+
"description",
|
|
1621
|
+
"operationid",
|
|
1622
|
+
"response",
|
|
1623
|
+
"responses",
|
|
1624
|
+
"request",
|
|
1625
|
+
"requestbody",
|
|
1626
|
+
"security",
|
|
1627
|
+
"tag",
|
|
1628
|
+
"tags",
|
|
1629
|
+
"path",
|
|
1630
|
+
"body",
|
|
1631
|
+
"query",
|
|
1632
|
+
"queryparam",
|
|
1633
|
+
"header",
|
|
1634
|
+
"headers",
|
|
1635
|
+
"produces",
|
|
1636
|
+
"accept",
|
|
1637
|
+
"middleware",
|
|
1638
|
+
"api",
|
|
1639
|
+
"apiname",
|
|
1640
|
+
"apidefine",
|
|
1641
|
+
"apigroup",
|
|
1642
|
+
"apiparam",
|
|
1643
|
+
"apiquery",
|
|
1644
|
+
"apibody",
|
|
1645
|
+
"apiheader",
|
|
1646
|
+
"apisuccess",
|
|
1647
|
+
"apierror",
|
|
1648
|
+
"apiexample",
|
|
1649
|
+
"apiversion",
|
|
1650
|
+
"apidescription",
|
|
1651
|
+
"apipermission",
|
|
1652
|
+
"apiuse",
|
|
1653
|
+
"apiignore",
|
|
1654
|
+
"apiprivate",
|
|
1655
|
+
"namespace",
|
|
1656
|
+
"category"
|
|
1615
1657
|
]);
|
|
1616
1658
|
const SUPPORTED_EXTS = new Set([
|
|
1617
1659
|
".ts",
|
|
@@ -1627,6 +1669,19 @@ const SUPPORTED_EXTS = new Set([
|
|
|
1627
1669
|
".java",
|
|
1628
1670
|
".php"
|
|
1629
1671
|
]);
|
|
1672
|
+
const DECL_START = /^(\s*)(export\s+)?(async\s+)?(const|let|var|function|class|type|interface|enum|abstract\s+class)\s+/;
|
|
1673
|
+
const EXPORT_DEFAULT = /^\s*export\s+default\b/;
|
|
1674
|
+
const TS_MEMBER_DECL_START = /^\s*(?:readonly\s+|static\s+|public\s+|private\s+|protected\s+|abstract\s+|override\s+)*[\w$]+\??\s*:/;
|
|
1675
|
+
const PY_DECL_START = /^\s*(async\s+def|def|class)\s+/;
|
|
1676
|
+
const GO_DECL_START = /^\s*(func|type|var|const)\s+/;
|
|
1677
|
+
const RUST_DECL_START = /^\s*(pub\s+)?(async\s+)?(fn|struct|enum|trait|impl|const|static|type|mod)\s+/;
|
|
1678
|
+
const RUBY_DECL_START = /^\s*(class|module|def)\s+/;
|
|
1679
|
+
const JAVA_DECL_START = /^\s*(?:public|private|protected|static|final|abstract|sealed|non-sealed|\s)+(?:class|interface|enum|record|@interface|\w[^(){};=]*\s+\w+\s*\()/;
|
|
1680
|
+
const JAVA_DECL_START_FALLBACK = /^\s*(class|interface|enum|record|@interface)\s+/;
|
|
1681
|
+
const PHP_DECL_START = /^\s*(?:public|private|protected|static|final|abstract|readonly\s+)*(function|class|interface|trait|enum|const)\s+/;
|
|
1682
|
+
|
|
1683
|
+
//#endregion
|
|
1684
|
+
//#region src/engines/ai-slop/narrative-comments.ts
|
|
1630
1685
|
const stripJsdocLine = (line) => line.replace(/^\s*\/\*\*+\s?/, "").replace(/\s*\*+\/\s*$/, "").replace(/^\s*\*\s?/, "").trim();
|
|
1631
1686
|
const stripLineComment = (line) => line.replace(/^\s*(?:(?:\/\/)|#)\s?/, "");
|
|
1632
1687
|
const getCommentSyntax = (ext) => {
|
|
@@ -1718,16 +1773,6 @@ const collectBlocks = (sourceLines, syntax) => {
|
|
|
1718
1773
|
}
|
|
1719
1774
|
return blocks;
|
|
1720
1775
|
};
|
|
1721
|
-
const DECL_START = /^(\s*)(export\s+)?(async\s+)?(const|let|var|function|class|type|interface|enum|abstract\s+class)\s+/;
|
|
1722
|
-
const EXPORT_DEFAULT = /^\s*export\s+default\b/;
|
|
1723
|
-
const TS_MEMBER_DECL_START = /^\s*(?:readonly\s+|static\s+|public\s+|private\s+|protected\s+|abstract\s+|override\s+)*[\w$]+\??\s*:/;
|
|
1724
|
-
const PY_DECL_START = /^\s*(async\s+def|def|class)\s+/;
|
|
1725
|
-
const GO_DECL_START = /^\s*(func|type|var|const)\s+/;
|
|
1726
|
-
const RUST_DECL_START = /^\s*(pub\s+)?(async\s+)?(fn|struct|enum|trait|impl|const|static|type|mod)\s+/;
|
|
1727
|
-
const RUBY_DECL_START = /^\s*(class|module|def)\s+/;
|
|
1728
|
-
const JAVA_DECL_START = /^\s*(?:public|private|protected|static|final|abstract|sealed|non-sealed|\s)+(?:class|interface|enum|record|@interface|\w[^(){};=]*\s+\w+\s*\()/;
|
|
1729
|
-
const JAVA_DECL_START_FALLBACK = /^\s*(class|interface|enum|record|@interface)\s+/;
|
|
1730
|
-
const PHP_DECL_START = /^\s*(?:public|private|protected|static|final|abstract|readonly\s+)*(function|class|interface|trait|enum|const)\s+/;
|
|
1731
1776
|
const looksLikeDeclarationPreamble = (nextLine, ext) => {
|
|
1732
1777
|
if (nextLine === null) return false;
|
|
1733
1778
|
if (DECL_START.test(nextLine) || EXPORT_DEFAULT.test(nextLine)) return true;
|
|
@@ -2256,72 +2301,12 @@ const architectureEngine = {
|
|
|
2256
2301
|
};
|
|
2257
2302
|
|
|
2258
2303
|
//#endregion
|
|
2259
|
-
//#region src/engines/code-quality/
|
|
2260
|
-
const FUNCTION_PATTERNS = [
|
|
2261
|
-
{
|
|
2262
|
-
regex: /^\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/,
|
|
2263
|
-
langFilter: [
|
|
2264
|
-
".js",
|
|
2265
|
-
".ts",
|
|
2266
|
-
".jsx",
|
|
2267
|
-
".tsx",
|
|
2268
|
-
".mjs",
|
|
2269
|
-
".cjs"
|
|
2270
|
-
]
|
|
2271
|
-
},
|
|
2272
|
-
{
|
|
2273
|
-
regex: /^\s*(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s+)?\(([^)]*)\)\s*(?:=>|:\s*\w)/,
|
|
2274
|
-
langFilter: [
|
|
2275
|
-
".js",
|
|
2276
|
-
".ts",
|
|
2277
|
-
".jsx",
|
|
2278
|
-
".tsx",
|
|
2279
|
-
".mjs",
|
|
2280
|
-
".cjs"
|
|
2281
|
-
]
|
|
2282
|
-
},
|
|
2283
|
-
{
|
|
2284
|
-
regex: /^\s*def\s+(\w+)\s*\(([^)]*)\)/,
|
|
2285
|
-
langFilter: [".py"]
|
|
2286
|
-
},
|
|
2287
|
-
{
|
|
2288
|
-
regex: /^\s*func\s+(?:\([^)]*\)\s+)?(\w+)\s*\(([^)]*)\)/,
|
|
2289
|
-
langFilter: [".go"]
|
|
2290
|
-
},
|
|
2291
|
-
{
|
|
2292
|
-
regex: /^\s*fn\s+(\w+)\s*\(([^)]*)\)/,
|
|
2293
|
-
langFilter: [".rs"]
|
|
2294
|
-
},
|
|
2295
|
-
{
|
|
2296
|
-
regex: /^\s*(?:public|private|protected)?\s*(?:static\s+)?(?:\w+\s+)(\w+)\s*\(([^)]*)\)/,
|
|
2297
|
-
langFilter: [
|
|
2298
|
-
".java",
|
|
2299
|
-
".cs",
|
|
2300
|
-
".cpp",
|
|
2301
|
-
".c",
|
|
2302
|
-
".php"
|
|
2303
|
-
]
|
|
2304
|
-
}
|
|
2305
|
-
];
|
|
2306
|
-
const countParams = (p) => p.trim() ? p.split(",").length : 0;
|
|
2307
|
-
const matchFunctionOnLine = (line, ext) => {
|
|
2308
|
-
for (let i = 0; i < FUNCTION_PATTERNS.length; i++) {
|
|
2309
|
-
const pattern = FUNCTION_PATTERNS[i];
|
|
2310
|
-
if (!pattern.langFilter.includes(ext)) continue;
|
|
2311
|
-
const match = line.match(pattern.regex);
|
|
2312
|
-
if (match) return {
|
|
2313
|
-
name: match[1],
|
|
2314
|
-
params: match[2] ?? "",
|
|
2315
|
-
patternIndex: i
|
|
2316
|
-
};
|
|
2317
|
-
}
|
|
2318
|
-
return null;
|
|
2319
|
-
};
|
|
2304
|
+
//#region src/engines/code-quality/function-boundaries.ts
|
|
2320
2305
|
const PYTHON_CONTROL_FLOW_RE = /^\s*(?:if|for|while|with|try|except|else|elif|finally|def|class)\b/;
|
|
2321
|
-
const
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2306
|
+
const ARROW_BLOCK_RE = /* @__PURE__ */ new RegExp("=>\\s*\\{");
|
|
2307
|
+
const ARROW_END_RE = /* @__PURE__ */ new RegExp("=>\\s*$");
|
|
2308
|
+
const BRACE_START_RE = /* @__PURE__ */ new RegExp("^\\s*\\{");
|
|
2309
|
+
const NEW_STATEMENT_RE = /* @__PURE__ */ new RegExp("^(?:export\\s+)?(?:const|let|var|function|class)\\s");
|
|
2325
2310
|
const isControlFlowBrace = (lineText, braceIndex) => {
|
|
2326
2311
|
const before = lineText.substring(0, braceIndex).trimEnd();
|
|
2327
2312
|
if (before.endsWith(")")) return true;
|
|
@@ -2401,16 +2386,10 @@ const findPythonFunctionEnd = (lines, startIndex) => {
|
|
|
2401
2386
|
maxNesting
|
|
2402
2387
|
};
|
|
2403
2388
|
};
|
|
2404
|
-
const
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
const dataLinePattern = /^\s*[{}[\]"']/;
|
|
2408
|
-
return nonEmpty.filter((l) => dataLinePattern.test(l)).length / nonEmpty.length > .8;
|
|
2389
|
+
const findFunctionEnd = (lines, startIndex, isPython) => {
|
|
2390
|
+
if (isPython) return findPythonFunctionEnd(lines, startIndex);
|
|
2391
|
+
return findBraceFunctionEnd(lines, startIndex);
|
|
2409
2392
|
};
|
|
2410
|
-
const ARROW_BLOCK_RE = /* @__PURE__ */ new RegExp("=>\\s*\\{");
|
|
2411
|
-
const ARROW_END_RE = /* @__PURE__ */ new RegExp("=>\\s*$");
|
|
2412
|
-
const BRACE_START_RE = /* @__PURE__ */ new RegExp("^\\s*\\{");
|
|
2413
|
-
const NEW_STATEMENT_RE = /* @__PURE__ */ new RegExp("^(?:export\\s+)?(?:const|let|var|function|class)\\s");
|
|
2414
2393
|
const isBlockArrow = (lines, startIndex) => {
|
|
2415
2394
|
if (ARROW_BLOCK_RE.test(lines[startIndex])) return true;
|
|
2416
2395
|
if (ARROW_END_RE.test(lines[startIndex])) {
|
|
@@ -2446,6 +2425,75 @@ const countTemplateLines = (bodyLines) => {
|
|
|
2446
2425
|
}
|
|
2447
2426
|
return templateLineCount;
|
|
2448
2427
|
};
|
|
2428
|
+
|
|
2429
|
+
//#endregion
|
|
2430
|
+
//#region src/engines/code-quality/complexity.ts
|
|
2431
|
+
const FUNCTION_PATTERNS = [
|
|
2432
|
+
{
|
|
2433
|
+
regex: /^\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/,
|
|
2434
|
+
langFilter: [
|
|
2435
|
+
".js",
|
|
2436
|
+
".ts",
|
|
2437
|
+
".jsx",
|
|
2438
|
+
".tsx",
|
|
2439
|
+
".mjs",
|
|
2440
|
+
".cjs"
|
|
2441
|
+
]
|
|
2442
|
+
},
|
|
2443
|
+
{
|
|
2444
|
+
regex: /^\s*(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s+)?\(([^)]*)\)\s*(?:=>|:\s*\w)/,
|
|
2445
|
+
langFilter: [
|
|
2446
|
+
".js",
|
|
2447
|
+
".ts",
|
|
2448
|
+
".jsx",
|
|
2449
|
+
".tsx",
|
|
2450
|
+
".mjs",
|
|
2451
|
+
".cjs"
|
|
2452
|
+
]
|
|
2453
|
+
},
|
|
2454
|
+
{
|
|
2455
|
+
regex: /^\s*def\s+(\w+)\s*\(([^)]*)\)/,
|
|
2456
|
+
langFilter: [".py"]
|
|
2457
|
+
},
|
|
2458
|
+
{
|
|
2459
|
+
regex: /^\s*func\s+(?:\([^)]*\)\s+)?(\w+)\s*\(([^)]*)\)/,
|
|
2460
|
+
langFilter: [".go"]
|
|
2461
|
+
},
|
|
2462
|
+
{
|
|
2463
|
+
regex: /^\s*fn\s+(\w+)\s*\(([^)]*)\)/,
|
|
2464
|
+
langFilter: [".rs"]
|
|
2465
|
+
},
|
|
2466
|
+
{
|
|
2467
|
+
regex: /^\s*(?:public|private|protected)?\s*(?:static\s+)?(?:\w+\s+)(\w+)\s*\(([^)]*)\)/,
|
|
2468
|
+
langFilter: [
|
|
2469
|
+
".java",
|
|
2470
|
+
".cs",
|
|
2471
|
+
".cpp",
|
|
2472
|
+
".c",
|
|
2473
|
+
".php"
|
|
2474
|
+
]
|
|
2475
|
+
}
|
|
2476
|
+
];
|
|
2477
|
+
const countParams = (p) => p.trim() ? p.split(",").length : 0;
|
|
2478
|
+
const matchFunctionOnLine = (line, ext) => {
|
|
2479
|
+
for (let i = 0; i < FUNCTION_PATTERNS.length; i++) {
|
|
2480
|
+
const pattern = FUNCTION_PATTERNS[i];
|
|
2481
|
+
if (!pattern.langFilter.includes(ext)) continue;
|
|
2482
|
+
const match = line.match(pattern.regex);
|
|
2483
|
+
if (match) return {
|
|
2484
|
+
name: match[1],
|
|
2485
|
+
params: match[2] ?? "",
|
|
2486
|
+
patternIndex: i
|
|
2487
|
+
};
|
|
2488
|
+
}
|
|
2489
|
+
return null;
|
|
2490
|
+
};
|
|
2491
|
+
const isDataFile = (content) => {
|
|
2492
|
+
const nonEmpty = content.split("\n").filter((l) => l.trim().length > 0);
|
|
2493
|
+
if (nonEmpty.length === 0) return false;
|
|
2494
|
+
const dataLinePattern = /^\s*[{}[\]"']/;
|
|
2495
|
+
return nonEmpty.filter((l) => dataLinePattern.test(l)).length / nonEmpty.length > .8;
|
|
2496
|
+
};
|
|
2449
2497
|
const analyzeFunctions = (content, ext) => {
|
|
2450
2498
|
const lines = content.split("\n");
|
|
2451
2499
|
const functions = [];
|
|
@@ -2468,13 +2516,14 @@ const analyzeFunctions = (content, ext) => {
|
|
|
2468
2516
|
}
|
|
2469
2517
|
return functions;
|
|
2470
2518
|
};
|
|
2519
|
+
const JSX_FILE_LOC_MULTIPLIER = 1.5;
|
|
2471
2520
|
const checkFileDiagnostics = (relativePath, content, limits) => {
|
|
2472
2521
|
const results = [];
|
|
2473
2522
|
const lineCount = content.split("\n").length;
|
|
2474
2523
|
const ext = path.extname(relativePath).toLowerCase();
|
|
2475
2524
|
if (isDataFile(content)) return results;
|
|
2476
|
-
const effectiveMax = ext === ".jsx" || ext === ".tsx" ? limits.maxFileLoc *
|
|
2477
|
-
if (lineCount >
|
|
2525
|
+
const effectiveMax = ext === ".jsx" || ext === ".tsx" ? Math.ceil(limits.maxFileLoc * JSX_FILE_LOC_MULTIPLIER) : limits.maxFileLoc;
|
|
2526
|
+
if (lineCount > effectiveMax) results.push({
|
|
2478
2527
|
filePath: relativePath,
|
|
2479
2528
|
engine: "code-quality",
|
|
2480
2529
|
rule: "complexity/file-too-large",
|
|
@@ -3957,6 +4006,216 @@ const runCargoAudit = async (rootDir, timeout) => {
|
|
|
3957
4006
|
}
|
|
3958
4007
|
};
|
|
3959
4008
|
|
|
4009
|
+
//#endregion
|
|
4010
|
+
//#region src/utils/source-masker.ts
|
|
4011
|
+
const JS_EXTS = new Set([
|
|
4012
|
+
".ts",
|
|
4013
|
+
".tsx",
|
|
4014
|
+
".js",
|
|
4015
|
+
".jsx",
|
|
4016
|
+
".mjs",
|
|
4017
|
+
".cjs"
|
|
4018
|
+
]);
|
|
4019
|
+
const PY_EXTS = new Set([".py"]);
|
|
4020
|
+
const RB_EXTS = new Set([".rb"]);
|
|
4021
|
+
const PHP_EXTS = new Set([".php"]);
|
|
4022
|
+
const familyForExt = (ext) => {
|
|
4023
|
+
if (JS_EXTS.has(ext)) return "js";
|
|
4024
|
+
if (PY_EXTS.has(ext)) return "py";
|
|
4025
|
+
if (RB_EXTS.has(ext)) return "rb";
|
|
4026
|
+
if (PHP_EXTS.has(ext)) return "php";
|
|
4027
|
+
return "none";
|
|
4028
|
+
};
|
|
4029
|
+
const maskStringsAndComments = (content, ext) => {
|
|
4030
|
+
const family = familyForExt(ext);
|
|
4031
|
+
if (family === "none") return content;
|
|
4032
|
+
if (family === "js") return maskJs(content);
|
|
4033
|
+
return maskSimple(content, family);
|
|
4034
|
+
};
|
|
4035
|
+
const maskJs = (content) => {
|
|
4036
|
+
const out = content.split("");
|
|
4037
|
+
const len = content.length;
|
|
4038
|
+
const tplStack = [];
|
|
4039
|
+
let i = 0;
|
|
4040
|
+
const mask = (start, end) => {
|
|
4041
|
+
for (let k = start; k < end; k++) if (out[k] !== "\n") out[k] = " ";
|
|
4042
|
+
};
|
|
4043
|
+
while (i < len) {
|
|
4044
|
+
const c = content[i];
|
|
4045
|
+
const next = content[i + 1];
|
|
4046
|
+
if (tplStack.length > 0) {
|
|
4047
|
+
if (c === "{") {
|
|
4048
|
+
tplStack[tplStack.length - 1]++;
|
|
4049
|
+
i++;
|
|
4050
|
+
continue;
|
|
4051
|
+
}
|
|
4052
|
+
if (c === "}") {
|
|
4053
|
+
if (tplStack[tplStack.length - 1] === 0) {
|
|
4054
|
+
tplStack.pop();
|
|
4055
|
+
const scan = consumeTemplateString(content, i + 1);
|
|
4056
|
+
mask(i + 1, scan.maskEnd);
|
|
4057
|
+
if (scan.openedInterp) tplStack.push(0);
|
|
4058
|
+
i = scan.resumeAt;
|
|
4059
|
+
continue;
|
|
4060
|
+
}
|
|
4061
|
+
tplStack[tplStack.length - 1]--;
|
|
4062
|
+
i++;
|
|
4063
|
+
continue;
|
|
4064
|
+
}
|
|
4065
|
+
if (c === "\"" || c === "'") {
|
|
4066
|
+
const strStart = i;
|
|
4067
|
+
i = consumeQuotedString(content, i, c);
|
|
4068
|
+
mask(strStart + 1, i - 1);
|
|
4069
|
+
continue;
|
|
4070
|
+
}
|
|
4071
|
+
if (c === "`") {
|
|
4072
|
+
const scan = consumeTemplateString(content, i + 1);
|
|
4073
|
+
mask(i + 1, scan.maskEnd);
|
|
4074
|
+
if (scan.openedInterp) tplStack.push(0);
|
|
4075
|
+
i = scan.resumeAt;
|
|
4076
|
+
continue;
|
|
4077
|
+
}
|
|
4078
|
+
if (c === "/" && next === "/") {
|
|
4079
|
+
const strStart = i;
|
|
4080
|
+
while (i < len && content[i] !== "\n") i++;
|
|
4081
|
+
mask(strStart, i);
|
|
4082
|
+
continue;
|
|
4083
|
+
}
|
|
4084
|
+
if (c === "/" && next === "*") {
|
|
4085
|
+
const strStart = i;
|
|
4086
|
+
i += 2;
|
|
4087
|
+
while (i < len - 1 && !(content[i] === "*" && content[i + 1] === "/")) i++;
|
|
4088
|
+
if (i < len - 1) i += 2;
|
|
4089
|
+
mask(strStart, i);
|
|
4090
|
+
continue;
|
|
4091
|
+
}
|
|
4092
|
+
i++;
|
|
4093
|
+
continue;
|
|
4094
|
+
}
|
|
4095
|
+
if (c === "\"" || c === "'") {
|
|
4096
|
+
const strStart = i;
|
|
4097
|
+
i = consumeQuotedString(content, i, c);
|
|
4098
|
+
mask(strStart + 1, i - 1);
|
|
4099
|
+
continue;
|
|
4100
|
+
}
|
|
4101
|
+
if (c === "`") {
|
|
4102
|
+
const scan = consumeTemplateString(content, i + 1);
|
|
4103
|
+
mask(i + 1, scan.maskEnd);
|
|
4104
|
+
if (scan.openedInterp) tplStack.push(0);
|
|
4105
|
+
i = scan.resumeAt;
|
|
4106
|
+
continue;
|
|
4107
|
+
}
|
|
4108
|
+
if (c === "/" && next === "/") {
|
|
4109
|
+
const strStart = i;
|
|
4110
|
+
while (i < len && content[i] !== "\n") i++;
|
|
4111
|
+
mask(strStart, i);
|
|
4112
|
+
continue;
|
|
4113
|
+
}
|
|
4114
|
+
if (c === "/" && next === "*") {
|
|
4115
|
+
const strStart = i;
|
|
4116
|
+
i += 2;
|
|
4117
|
+
while (i < len - 1 && !(content[i] === "*" && content[i + 1] === "/")) i++;
|
|
4118
|
+
if (i < len - 1) i += 2;
|
|
4119
|
+
mask(strStart, i);
|
|
4120
|
+
continue;
|
|
4121
|
+
}
|
|
4122
|
+
i++;
|
|
4123
|
+
}
|
|
4124
|
+
return out.join("");
|
|
4125
|
+
};
|
|
4126
|
+
const consumeQuotedString = (content, start, quote) => {
|
|
4127
|
+
const len = content.length;
|
|
4128
|
+
let i = start + 1;
|
|
4129
|
+
while (i < len) {
|
|
4130
|
+
const c = content[i];
|
|
4131
|
+
if (c === "\\" && i + 1 < len) {
|
|
4132
|
+
i += 2;
|
|
4133
|
+
continue;
|
|
4134
|
+
}
|
|
4135
|
+
if (c === quote) return i + 1;
|
|
4136
|
+
if (c === "\n") return i;
|
|
4137
|
+
i++;
|
|
4138
|
+
}
|
|
4139
|
+
return i;
|
|
4140
|
+
};
|
|
4141
|
+
const consumeTemplateString = (content, start) => {
|
|
4142
|
+
const len = content.length;
|
|
4143
|
+
let i = start;
|
|
4144
|
+
while (i < len) {
|
|
4145
|
+
const c = content[i];
|
|
4146
|
+
if (c === "\\" && i + 1 < len) {
|
|
4147
|
+
i += 2;
|
|
4148
|
+
continue;
|
|
4149
|
+
}
|
|
4150
|
+
if (c === "`") return {
|
|
4151
|
+
maskEnd: i,
|
|
4152
|
+
resumeAt: i + 1,
|
|
4153
|
+
openedInterp: false
|
|
4154
|
+
};
|
|
4155
|
+
if (c === "$" && content[i + 1] === "{") return {
|
|
4156
|
+
maskEnd: i,
|
|
4157
|
+
resumeAt: i + 2,
|
|
4158
|
+
openedInterp: true
|
|
4159
|
+
};
|
|
4160
|
+
i++;
|
|
4161
|
+
}
|
|
4162
|
+
return {
|
|
4163
|
+
maskEnd: i,
|
|
4164
|
+
resumeAt: i,
|
|
4165
|
+
openedInterp: false
|
|
4166
|
+
};
|
|
4167
|
+
};
|
|
4168
|
+
const maskSimple = (content, family) => {
|
|
4169
|
+
const out = content.split("");
|
|
4170
|
+
const len = content.length;
|
|
4171
|
+
let i = 0;
|
|
4172
|
+
const mask = (start, end) => {
|
|
4173
|
+
for (let k = start; k < end; k++) if (out[k] !== "\n") out[k] = " ";
|
|
4174
|
+
};
|
|
4175
|
+
while (i < len) {
|
|
4176
|
+
const c = content[i];
|
|
4177
|
+
const next = content[i + 1];
|
|
4178
|
+
if (family === "py" && (c === "\"" || c === "'")) {
|
|
4179
|
+
if (content[i + 1] === c && content[i + 2] === c) {
|
|
4180
|
+
const triple = c + c + c;
|
|
4181
|
+
const end = content.indexOf(triple, i + 3);
|
|
4182
|
+
const stop = end === -1 ? len : end + 3;
|
|
4183
|
+
mask(i + 3, stop - 3);
|
|
4184
|
+
i = stop;
|
|
4185
|
+
continue;
|
|
4186
|
+
}
|
|
4187
|
+
}
|
|
4188
|
+
if (c === "\"" || c === "'") {
|
|
4189
|
+
const strStart = i;
|
|
4190
|
+
i = consumeQuotedString(content, i, c);
|
|
4191
|
+
mask(strStart + 1, i - 1);
|
|
4192
|
+
continue;
|
|
4193
|
+
}
|
|
4194
|
+
if ((family === "py" || family === "rb" || family === "php") && c === "#") {
|
|
4195
|
+
const strStart = i;
|
|
4196
|
+
while (i < len && content[i] !== "\n") i++;
|
|
4197
|
+
mask(strStart, i);
|
|
4198
|
+
continue;
|
|
4199
|
+
}
|
|
4200
|
+
if (family === "php" && c === "/" && next === "/") {
|
|
4201
|
+
const strStart = i;
|
|
4202
|
+
while (i < len && content[i] !== "\n") i++;
|
|
4203
|
+
mask(strStart, i);
|
|
4204
|
+
continue;
|
|
4205
|
+
}
|
|
4206
|
+
if (family === "php" && c === "/" && next === "*") {
|
|
4207
|
+
const strStart = i;
|
|
4208
|
+
i += 2;
|
|
4209
|
+
while (i < len - 1 && !(content[i] === "*" && content[i + 1] === "/")) i++;
|
|
4210
|
+
if (i < len - 1) i += 2;
|
|
4211
|
+
mask(strStart, i);
|
|
4212
|
+
continue;
|
|
4213
|
+
}
|
|
4214
|
+
i++;
|
|
4215
|
+
}
|
|
4216
|
+
return out.join("");
|
|
4217
|
+
};
|
|
4218
|
+
|
|
3960
4219
|
//#endregion
|
|
3961
4220
|
//#region src/engines/security/risky.ts
|
|
3962
4221
|
const ev = "eval";
|
|
@@ -4086,12 +4345,13 @@ const detectRiskyConstructs = async (context) => {
|
|
|
4086
4345
|
const relativePath = path.relative(context.rootDirectory, filePath);
|
|
4087
4346
|
const normalizedPath = relativePath.split(path.sep).join("/");
|
|
4088
4347
|
const isMigrationOrSeeder = /(?:^|\/)(migrations|seeders|seeds|migrate)\//.test(normalizedPath);
|
|
4348
|
+
const masked = maskStringsAndComments(content, ext);
|
|
4089
4349
|
for (const { pattern, extensions, name, message, help } of RISKY_PATTERNS) {
|
|
4090
4350
|
if (!extensions.includes(ext)) continue;
|
|
4091
4351
|
if (isMigrationOrSeeder && name === "sql-injection") continue;
|
|
4092
4352
|
const regex = new RegExp(pattern.source, pattern.flags);
|
|
4093
4353
|
let match;
|
|
4094
|
-
while ((match = regex.exec(
|
|
4354
|
+
while ((match = regex.exec(masked)) !== null) {
|
|
4095
4355
|
const line = content.slice(0, match.index).split("\n").length;
|
|
4096
4356
|
if (name === "innerhtml") {
|
|
4097
4357
|
const beforeMatch = content.slice(Math.max(0, match.index - 200), match.index);
|
|
@@ -4733,7 +4993,7 @@ const renderCleanRun = (input, deps = {}) => {
|
|
|
4733
4993
|
//#region src/utils/git.ts
|
|
4734
4994
|
const MAX_BUFFER = 50 * 1024 * 1024;
|
|
4735
4995
|
const getChangedFiles = (cwd, base) => {
|
|
4736
|
-
const
|
|
4996
|
+
const diff = spawnSync("git", [
|
|
4737
4997
|
"diff",
|
|
4738
4998
|
"--name-only",
|
|
4739
4999
|
"--diff-filter=ACMR",
|
|
@@ -4743,8 +5003,22 @@ const getChangedFiles = (cwd, base) => {
|
|
|
4743
5003
|
encoding: "utf-8",
|
|
4744
5004
|
maxBuffer: MAX_BUFFER
|
|
4745
5005
|
});
|
|
4746
|
-
if (
|
|
4747
|
-
|
|
5006
|
+
if (diff.error || diff.status !== 0) return [];
|
|
5007
|
+
const untracked = spawnSync("git", [
|
|
5008
|
+
"ls-files",
|
|
5009
|
+
"--others",
|
|
5010
|
+
"--exclude-standard"
|
|
5011
|
+
], {
|
|
5012
|
+
cwd,
|
|
5013
|
+
encoding: "utf-8",
|
|
5014
|
+
maxBuffer: MAX_BUFFER
|
|
5015
|
+
});
|
|
5016
|
+
const names = /* @__PURE__ */ new Set();
|
|
5017
|
+
for (const line of diff.stdout.split("\n")) if (line.length > 0) names.add(line);
|
|
5018
|
+
if (!untracked.error && untracked.status === 0) {
|
|
5019
|
+
for (const line of untracked.stdout.split("\n")) if (line.length > 0) names.add(line);
|
|
5020
|
+
}
|
|
5021
|
+
return Array.from(names).map((f) => path.resolve(cwd, f));
|
|
4748
5022
|
};
|
|
4749
5023
|
const getStagedFiles = (cwd) => {
|
|
4750
5024
|
const result = spawnSync("git", [
|
|
@@ -4915,7 +5189,7 @@ const scanCommand = async (directory, config, options) => {
|
|
|
4915
5189
|
});
|
|
4916
5190
|
}
|
|
4917
5191
|
if (options.json) {
|
|
4918
|
-
const { buildJsonOutput } = await import("./json-
|
|
5192
|
+
const { buildJsonOutput } = await import("./json-DcE9soYJ.js");
|
|
4919
5193
|
const jsonOut = buildJsonOutput(results, scoreResult, projectInfo.sourceFileCount, elapsedMs);
|
|
4920
5194
|
console.log(JSON.stringify(jsonOut, null, 2));
|
|
4921
5195
|
return { exitCode };
|
|
@@ -5686,42 +5960,46 @@ const removeUnusedDeclarations = (rootDirectory, declarations) => {
|
|
|
5686
5960
|
|
|
5687
5961
|
//#endregion
|
|
5688
5962
|
//#region src/commands/fix-force.ts
|
|
5689
|
-
const
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
if (fs.existsSync(path.join(rootDirectory, "package-lock.json")) || fs.existsSync(path.join(rootDirectory, "package.json"))) return {
|
|
5695
|
-
command: "npm",
|
|
5696
|
-
args: ["audit", "fix"]
|
|
5697
|
-
};
|
|
5963
|
+
const INSTALL_TIMEOUT = 1800 * 1e3;
|
|
5964
|
+
const AUDIT_TIMEOUT = 60 * 1e3;
|
|
5965
|
+
const detectPackageManager = (rootDirectory) => {
|
|
5966
|
+
if (fs.existsSync(path.join(rootDirectory, "pnpm-lock.yaml"))) return "pnpm";
|
|
5967
|
+
if (fs.existsSync(path.join(rootDirectory, "package-lock.json")) || fs.existsSync(path.join(rootDirectory, "package.json"))) return "npm";
|
|
5698
5968
|
return null;
|
|
5699
5969
|
};
|
|
5700
|
-
const INSTALL_TIMEOUT = 1800 * 1e3;
|
|
5701
5970
|
const fixDependencyAudit = async (context, onProgress) => {
|
|
5702
|
-
const
|
|
5703
|
-
if (!
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5971
|
+
const pm = detectPackageManager(context.rootDirectory);
|
|
5972
|
+
if (!pm) return;
|
|
5973
|
+
if (pm === "npm") {
|
|
5974
|
+
await runNpmAuditFix(context.rootDirectory, onProgress);
|
|
5975
|
+
await tryNpmOverrides(context.rootDirectory, onProgress);
|
|
5976
|
+
return;
|
|
5977
|
+
}
|
|
5978
|
+
if (await tryPnpmOverrides(context.rootDirectory, onProgress)) return;
|
|
5979
|
+
if (fs.existsSync(path.join(context.rootDirectory, "package-lock.json"))) {
|
|
5980
|
+
await runNpmAuditFix(context.rootDirectory, onProgress);
|
|
5981
|
+
await tryNpmOverrides(context.rootDirectory, onProgress);
|
|
5982
|
+
return;
|
|
5983
|
+
}
|
|
5984
|
+
onProgress?.("Dependency audit fixes · skipping (pnpm audit unavailable and no package-lock.json for npm fallback)");
|
|
5985
|
+
};
|
|
5986
|
+
const runNpmAuditFix = async (rootDir, onProgress) => {
|
|
5987
|
+
onProgress?.("Dependency audit fixes · running npm audit fix (can take a few minutes)");
|
|
5988
|
+
const result = await runSubprocess("npm", ["audit", "fix"], {
|
|
5989
|
+
cwd: rootDir,
|
|
5707
5990
|
timeout: INSTALL_TIMEOUT
|
|
5708
5991
|
});
|
|
5709
|
-
if (result.exitCode !== 0 && !result.stdout && !result.stderr) throw new Error(
|
|
5710
|
-
onProgress?.(
|
|
5711
|
-
const installResult = await runSubprocess(
|
|
5712
|
-
cwd:
|
|
5992
|
+
if (result.exitCode !== 0 && !result.stdout && !result.stderr) throw new Error("npm audit fix failed");
|
|
5993
|
+
onProgress?.("Dependency audit fixes · running npm install");
|
|
5994
|
+
const installResult = await runSubprocess("npm", ["install"], {
|
|
5995
|
+
cwd: rootDir,
|
|
5713
5996
|
timeout: INSTALL_TIMEOUT
|
|
5714
5997
|
});
|
|
5715
|
-
if (installResult.exitCode !== 0) throw new Error(installResult.stderr || installResult.stdout ||
|
|
5716
|
-
if (auditFix.command === "npm") await tryNpmOverrides(context.rootDirectory, onProgress);
|
|
5998
|
+
if (installResult.exitCode !== 0) throw new Error(installResult.stderr || installResult.stdout || "npm install failed after audit fix");
|
|
5717
5999
|
};
|
|
5718
|
-
|
|
5719
|
-
* For unresolvable transitive vulnerabilities, attempt to add npm overrides
|
|
5720
|
-
* in package.json. This forces a newer version of the vulnerable transitive dep.
|
|
5721
|
-
*/
|
|
5722
|
-
const fetchLatestVersion = async (rootDir, pkgName) => {
|
|
6000
|
+
const fetchLatestVersion = async (rootDir, pkgName, pm) => {
|
|
5723
6001
|
try {
|
|
5724
|
-
const result = await runSubprocess(
|
|
6002
|
+
const result = await runSubprocess(pm, [
|
|
5725
6003
|
"view",
|
|
5726
6004
|
pkgName,
|
|
5727
6005
|
"version",
|
|
@@ -5735,11 +6013,11 @@ const fetchLatestVersion = async (rootDir, pkgName) => {
|
|
|
5735
6013
|
return null;
|
|
5736
6014
|
}
|
|
5737
6015
|
};
|
|
5738
|
-
const collectOverrides = async (rootDir, vulnerabilities) => {
|
|
6016
|
+
const collectOverrides = async (rootDir, vulnerabilities, pm) => {
|
|
5739
6017
|
const overrides = {};
|
|
5740
6018
|
for (const [pkgName, vuln] of Object.entries(vulnerabilities)) {
|
|
5741
6019
|
if (vuln.fixAvailable !== false || !vuln.range) continue;
|
|
5742
|
-
const latest = await fetchLatestVersion(rootDir, pkgName);
|
|
6020
|
+
const latest = await fetchLatestVersion(rootDir, pkgName, pm);
|
|
5743
6021
|
if (latest) overrides[pkgName] = latest;
|
|
5744
6022
|
}
|
|
5745
6023
|
return overrides;
|
|
@@ -5748,12 +6026,12 @@ const tryNpmOverrides = async (rootDir, onProgress) => {
|
|
|
5748
6026
|
try {
|
|
5749
6027
|
const auditResult = await runSubprocess("npm", ["audit", "--json"], {
|
|
5750
6028
|
cwd: rootDir,
|
|
5751
|
-
timeout:
|
|
6029
|
+
timeout: AUDIT_TIMEOUT
|
|
5752
6030
|
});
|
|
5753
6031
|
if (!auditResult.stdout) return;
|
|
5754
6032
|
const vulnerabilities = JSON.parse(auditResult.stdout).vulnerabilities;
|
|
5755
6033
|
if (!vulnerabilities) return;
|
|
5756
|
-
const overrides = await collectOverrides(rootDir, vulnerabilities);
|
|
6034
|
+
const overrides = await collectOverrides(rootDir, vulnerabilities, "npm");
|
|
5757
6035
|
if (Object.keys(overrides).length === 0) return;
|
|
5758
6036
|
const pkgPath = path.join(rootDir, "package.json");
|
|
5759
6037
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
@@ -5761,7 +6039,7 @@ const tryNpmOverrides = async (rootDir, onProgress) => {
|
|
|
5761
6039
|
...pkg.overrides || {},
|
|
5762
6040
|
...overrides
|
|
5763
6041
|
};
|
|
5764
|
-
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2)
|
|
6042
|
+
fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
5765
6043
|
onProgress?.("Dependency audit fixes · applying npm overrides (npm install)");
|
|
5766
6044
|
await runSubprocess("npm", ["install"], {
|
|
5767
6045
|
cwd: rootDir,
|
|
@@ -5769,6 +6047,70 @@ const tryNpmOverrides = async (rootDir, onProgress) => {
|
|
|
5769
6047
|
});
|
|
5770
6048
|
} catch {}
|
|
5771
6049
|
};
|
|
6050
|
+
const patchedRangeToVersion = (patched) => {
|
|
6051
|
+
const match = patched.match(/^\s*>=?\s*([0-9]+\.[0-9]+\.[0-9]+[^\s]*)/);
|
|
6052
|
+
return match ? `^${match[1]}` : null;
|
|
6053
|
+
};
|
|
6054
|
+
const overrideKey = (name, vulnerable, patched) => {
|
|
6055
|
+
if (vulnerable && vulnerable.trim().length > 0 && !/^\*$/.test(vulnerable.trim())) return `${name}@${vulnerable.trim()}`;
|
|
6056
|
+
const first = patched.match(/([0-9]+\.[0-9]+\.[0-9]+)/)?.[1];
|
|
6057
|
+
return first ? `${name}@<${first}` : name;
|
|
6058
|
+
};
|
|
6059
|
+
const collectPnpmOverrides = (advisories) => {
|
|
6060
|
+
const overrides = {};
|
|
6061
|
+
for (const adv of Object.values(advisories)) {
|
|
6062
|
+
if (!adv.module_name || !adv.patched_versions) continue;
|
|
6063
|
+
const target = patchedRangeToVersion(adv.patched_versions);
|
|
6064
|
+
if (!target) continue;
|
|
6065
|
+
const key = overrideKey(adv.module_name, adv.vulnerable_versions, adv.patched_versions);
|
|
6066
|
+
overrides[key] = target;
|
|
6067
|
+
}
|
|
6068
|
+
return overrides;
|
|
6069
|
+
};
|
|
6070
|
+
const isPnpmAuditRetired = (stdout, stderr) => {
|
|
6071
|
+
const haystack = `${stdout}\n${stderr}`.toLowerCase();
|
|
6072
|
+
return haystack.includes("410") || haystack.includes("gone") || haystack.includes("retired") || haystack.includes("endpoint") || haystack.includes("err_pnpm_audit") || haystack.includes("audit endpoint");
|
|
6073
|
+
};
|
|
6074
|
+
const tryPnpmOverrides = async (rootDir, onProgress) => {
|
|
6075
|
+
onProgress?.("Dependency audit fixes · running pnpm audit");
|
|
6076
|
+
const auditResult = await runSubprocess("pnpm", ["audit", "--json"], {
|
|
6077
|
+
cwd: rootDir,
|
|
6078
|
+
timeout: AUDIT_TIMEOUT
|
|
6079
|
+
});
|
|
6080
|
+
if (!auditResult.stdout) {
|
|
6081
|
+
if (isPnpmAuditRetired(auditResult.stdout ?? "", auditResult.stderr ?? "")) return false;
|
|
6082
|
+
return auditResult.exitCode === 0;
|
|
6083
|
+
}
|
|
6084
|
+
let parsed;
|
|
6085
|
+
try {
|
|
6086
|
+
parsed = JSON.parse(auditResult.stdout);
|
|
6087
|
+
} catch {
|
|
6088
|
+
if (auditResult.exitCode !== 0 || isPnpmAuditRetired(auditResult.stdout, auditResult.stderr ?? "")) return false;
|
|
6089
|
+
return true;
|
|
6090
|
+
}
|
|
6091
|
+
const advisories = parsed.advisories;
|
|
6092
|
+
if (!advisories || Object.keys(advisories).length === 0) return true;
|
|
6093
|
+
const overrides = collectPnpmOverrides(advisories);
|
|
6094
|
+
if (Object.keys(overrides).length === 0) return true;
|
|
6095
|
+
const pkgPath = path.join(rootDir, "package.json");
|
|
6096
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
6097
|
+
const pnpmBlock = pkg.pnpm ?? {};
|
|
6098
|
+
const existing = pnpmBlock.overrides ?? {};
|
|
6099
|
+
pkg.pnpm = {
|
|
6100
|
+
...pnpmBlock,
|
|
6101
|
+
overrides: {
|
|
6102
|
+
...existing,
|
|
6103
|
+
...overrides
|
|
6104
|
+
}
|
|
6105
|
+
};
|
|
6106
|
+
fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
6107
|
+
onProgress?.("Dependency audit fixes · applying pnpm overrides (pnpm install)");
|
|
6108
|
+
await runSubprocess("pnpm", ["install"], {
|
|
6109
|
+
cwd: rootDir,
|
|
6110
|
+
timeout: INSTALL_TIMEOUT
|
|
6111
|
+
});
|
|
6112
|
+
return true;
|
|
6113
|
+
};
|
|
5772
6114
|
const fixExpoDependencies = async (context, onProgress) => {
|
|
5773
6115
|
await removeDisallowedExpoPackages(context.rootDirectory, onProgress);
|
|
5774
6116
|
onProgress?.("Expo dependency alignment · running expo install --fix (can take a few minutes)");
|
|
@@ -5883,8 +6225,9 @@ const runFormattingStep = async (deps) => {
|
|
|
5883
6225
|
const runForceSteps = async (deps) => {
|
|
5884
6226
|
if (!deps.force) return;
|
|
5885
6227
|
if (deps.config.engines["code-quality"] && hasJsOrTs(deps.projectInfo)) await deps.runStep("Remove unused files", () => runKnipUnusedFiles(deps.resolvedDir), () => fixUnusedFiles(deps.resolvedDir));
|
|
5886
|
-
|
|
5887
|
-
if (deps.
|
|
6228
|
+
const railUpdate = (label) => deps.rail.setActiveLabel(label);
|
|
6229
|
+
if (deps.config.engines.security) await deps.runStep("Dependency audit fixes", () => runDependencyAudit(deps.context), () => fixDependencyAudit(deps.context, railUpdate));
|
|
6230
|
+
if (deps.projectInfo.frameworks.includes("expo")) await deps.runStep("Expo dependency alignment", () => runExpoDoctor(deps.context), () => fixExpoDependencies(deps.context, railUpdate));
|
|
5888
6231
|
};
|
|
5889
6232
|
|
|
5890
6233
|
//#endregion
|