aislop 0.2.1 → 0.3.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 +3 -1
- package/dist/cli.js +669 -92
- package/dist/{engine-info-DFze-2GQ.js → engine-info-CXT2Q_Jy.js} +1 -1
- package/dist/{expo-doctor-CGXGLgMJ.js → expo-doctor-Cm5892Y8.js} +76 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2971 -2536
- package/dist/{json-Ci_gvHLS.js → json-C52xnH_4.js} +1 -1
- package/package.json +1 -1
- package/dist/expo-doctor-vDz4kh9-.js +0 -127
- package/dist/subprocess-99puEEGl.js +0 -59
package/dist/cli.js
CHANGED
|
@@ -13,6 +13,23 @@ import pc from "picocolors";
|
|
|
13
13
|
import ora from "ora";
|
|
14
14
|
import { emitKeypressEvents } from "node:readline";
|
|
15
15
|
|
|
16
|
+
//#region \0rolldown/runtime.js
|
|
17
|
+
var __defProp = Object.defineProperty;
|
|
18
|
+
var __exportAll = (all, no_symbols) => {
|
|
19
|
+
let target = {};
|
|
20
|
+
for (var name in all) {
|
|
21
|
+
__defProp(target, name, {
|
|
22
|
+
get: all[name],
|
|
23
|
+
enumerable: true
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
if (!no_symbols) {
|
|
27
|
+
__defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
28
|
+
}
|
|
29
|
+
return target;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
//#endregion
|
|
16
33
|
//#region src/config/defaults.ts
|
|
17
34
|
const DEFAULT_CONFIG = {
|
|
18
35
|
version: 1,
|
|
@@ -36,18 +53,18 @@ const DEFAULT_CONFIG = {
|
|
|
36
53
|
},
|
|
37
54
|
scoring: {
|
|
38
55
|
weights: {
|
|
39
|
-
format: .
|
|
40
|
-
lint:
|
|
41
|
-
"code-quality":
|
|
42
|
-
"ai-slop":
|
|
56
|
+
format: .3,
|
|
57
|
+
lint: .6,
|
|
58
|
+
"code-quality": .8,
|
|
59
|
+
"ai-slop": 2.5,
|
|
43
60
|
architecture: 1,
|
|
44
|
-
security:
|
|
61
|
+
security: 1.5
|
|
45
62
|
},
|
|
46
63
|
thresholds: {
|
|
47
64
|
good: 75,
|
|
48
65
|
ok: 50
|
|
49
66
|
},
|
|
50
|
-
smoothing:
|
|
67
|
+
smoothing: 20
|
|
51
68
|
},
|
|
52
69
|
ci: {
|
|
53
70
|
failBelow: 0,
|
|
@@ -77,15 +94,16 @@ security:
|
|
|
77
94
|
|
|
78
95
|
scoring:
|
|
79
96
|
weights:
|
|
80
|
-
format: 0.
|
|
81
|
-
lint:
|
|
82
|
-
code-quality:
|
|
83
|
-
ai-slop:
|
|
97
|
+
format: 0.3
|
|
98
|
+
lint: 0.6
|
|
99
|
+
code-quality: 0.8
|
|
100
|
+
ai-slop: 2.5
|
|
84
101
|
architecture: 1.0
|
|
85
|
-
security:
|
|
102
|
+
security: 1.5
|
|
86
103
|
thresholds:
|
|
87
104
|
good: 75
|
|
88
105
|
ok: 50
|
|
106
|
+
smoothing: 20
|
|
89
107
|
|
|
90
108
|
ci:
|
|
91
109
|
failBelow: 0
|
|
@@ -113,12 +131,12 @@ const DEFAULT_RULES_YAML = `# Architecture rules (BYO)
|
|
|
113
131
|
//#endregion
|
|
114
132
|
//#region src/config/schema.ts
|
|
115
133
|
const DEFAULT_WEIGHTS = {
|
|
116
|
-
format: .
|
|
117
|
-
lint:
|
|
118
|
-
"code-quality":
|
|
119
|
-
"ai-slop":
|
|
134
|
+
format: .3,
|
|
135
|
+
lint: .6,
|
|
136
|
+
"code-quality": .8,
|
|
137
|
+
"ai-slop": 2.5,
|
|
120
138
|
architecture: 1,
|
|
121
|
-
security:
|
|
139
|
+
security: 1.5
|
|
122
140
|
};
|
|
123
141
|
const EnginesSchema = z.object({
|
|
124
142
|
format: z.boolean().default(true),
|
|
@@ -148,7 +166,7 @@ const ScoringSchema = z.object({
|
|
|
148
166
|
good: 75,
|
|
149
167
|
ok: 50
|
|
150
168
|
})),
|
|
151
|
-
smoothing: z.number().nonnegative().default(
|
|
169
|
+
smoothing: z.number().nonnegative().default(20)
|
|
152
170
|
});
|
|
153
171
|
const CiSchema = z.object({
|
|
154
172
|
failBelow: z.number().default(0),
|
|
@@ -181,7 +199,7 @@ const AislopConfigSchema = z.object({
|
|
|
181
199
|
good: 75,
|
|
182
200
|
ok: 50
|
|
183
201
|
},
|
|
184
|
-
smoothing:
|
|
202
|
+
smoothing: 20
|
|
185
203
|
})),
|
|
186
204
|
ci: CiSchema.default(() => ({
|
|
187
205
|
failBelow: 0,
|
|
@@ -1842,10 +1860,10 @@ const codeQualityEngine = {
|
|
|
1842
1860
|
|
|
1843
1861
|
//#endregion
|
|
1844
1862
|
//#region src/engines/format/biome.ts
|
|
1845
|
-
const esmRequire$
|
|
1863
|
+
const esmRequire$3 = createRequire(import.meta.url);
|
|
1846
1864
|
const resolveLocalBiomeScript = () => {
|
|
1847
1865
|
try {
|
|
1848
|
-
const packageJsonPath = esmRequire$
|
|
1866
|
+
const packageJsonPath = esmRequire$3.resolve("@biomejs/biome/package.json");
|
|
1849
1867
|
return path.join(path.dirname(packageJsonPath), "bin", "biome");
|
|
1850
1868
|
} catch {
|
|
1851
1869
|
return null;
|
|
@@ -2092,7 +2110,7 @@ const fixGofmt = async (rootDirectory) => {
|
|
|
2092
2110
|
//#endregion
|
|
2093
2111
|
//#region src/utils/tooling.ts
|
|
2094
2112
|
const THIS_FILE = fileURLToPath(import.meta.url);
|
|
2095
|
-
const esmRequire$
|
|
2113
|
+
const esmRequire$2 = createRequire(import.meta.url);
|
|
2096
2114
|
const resolvePackageRoot = (startFile) => {
|
|
2097
2115
|
let current = path.dirname(startFile);
|
|
2098
2116
|
while (true) {
|
|
@@ -2123,7 +2141,7 @@ const isToolAvailable = async (toolName) => {
|
|
|
2123
2141
|
};
|
|
2124
2142
|
const isNodePackageAvailable = (packageName) => {
|
|
2125
2143
|
try {
|
|
2126
|
-
esmRequire$
|
|
2144
|
+
esmRequire$2.resolve(`${packageName}/package.json`);
|
|
2127
2145
|
return true;
|
|
2128
2146
|
} catch {
|
|
2129
2147
|
return false;
|
|
@@ -2406,10 +2424,10 @@ const createOxlintConfig = (options) => {
|
|
|
2406
2424
|
|
|
2407
2425
|
//#endregion
|
|
2408
2426
|
//#region src/engines/lint/oxlint.ts
|
|
2409
|
-
const esmRequire = createRequire(import.meta.url);
|
|
2427
|
+
const esmRequire$1 = createRequire(import.meta.url);
|
|
2410
2428
|
const resolveOxlintBinary = () => {
|
|
2411
2429
|
try {
|
|
2412
|
-
const oxlintMainPath = esmRequire.resolve("oxlint");
|
|
2430
|
+
const oxlintMainPath = esmRequire$1.resolve("oxlint");
|
|
2413
2431
|
const oxlintDir = path.resolve(path.dirname(oxlintMainPath), "..");
|
|
2414
2432
|
return path.join(oxlintDir, "bin", "oxlint");
|
|
2415
2433
|
} catch {
|
|
@@ -2444,6 +2462,144 @@ const detectTestFramework = (rootDir) => {
|
|
|
2444
2462
|
} catch {}
|
|
2445
2463
|
return null;
|
|
2446
2464
|
};
|
|
2465
|
+
const extractUnusedVarName = (message) => {
|
|
2466
|
+
const variableMatch = message.match(/Variable '([^']+)' is declared but never used/);
|
|
2467
|
+
if (variableMatch?.[1]) return {
|
|
2468
|
+
name: variableMatch[1],
|
|
2469
|
+
type: "variable"
|
|
2470
|
+
};
|
|
2471
|
+
const paramMatch = message.match(/Parameter '([^']+)' is declared but never used/);
|
|
2472
|
+
if (paramMatch?.[1]) return {
|
|
2473
|
+
name: paramMatch[1],
|
|
2474
|
+
type: "parameter"
|
|
2475
|
+
};
|
|
2476
|
+
const catchMatch = message.match(/Catch parameter '([^']+)' is caught but never used/);
|
|
2477
|
+
if (catchMatch?.[1]) return {
|
|
2478
|
+
name: catchMatch[1],
|
|
2479
|
+
type: "parameter"
|
|
2480
|
+
};
|
|
2481
|
+
return null;
|
|
2482
|
+
};
|
|
2483
|
+
const collectUnusedVarCandidates = (diagnostics) => diagnostics.filter((d) => d.rule === "eslint/no-unused-vars").map((d) => {
|
|
2484
|
+
const extracted = extractUnusedVarName(d.message);
|
|
2485
|
+
if (!extracted || extracted.name.startsWith("_")) return null;
|
|
2486
|
+
return {
|
|
2487
|
+
filePath: d.filePath,
|
|
2488
|
+
line: d.line,
|
|
2489
|
+
column: d.column,
|
|
2490
|
+
name: extracted.name,
|
|
2491
|
+
type: extracted.type
|
|
2492
|
+
};
|
|
2493
|
+
}).filter((candidate) => candidate !== null);
|
|
2494
|
+
const prefixIdentifierOnLine = (line, name, column, type) => {
|
|
2495
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2496
|
+
if (type === "parameter") {
|
|
2497
|
+
const paramPattern = new RegExp(`\\b${escaped}\\b`);
|
|
2498
|
+
if (paramPattern.test(line)) return line.replace(paramPattern, `_${name}`);
|
|
2499
|
+
return line;
|
|
2500
|
+
}
|
|
2501
|
+
const assignPattern = new RegExp(`(\\s*)(const|let|var)\\s+${escaped}\\s*=\\s*(.+)$`);
|
|
2502
|
+
const assignMatch = line.match(assignPattern);
|
|
2503
|
+
if (assignMatch) {
|
|
2504
|
+
const indent = assignMatch[1];
|
|
2505
|
+
const expression = assignMatch[3];
|
|
2506
|
+
if (/await\s/.test(expression) || /\w+\s*\(/.test(expression)) return `${indent}${expression}`;
|
|
2507
|
+
return "";
|
|
2508
|
+
}
|
|
2509
|
+
const destructureMatch = line.match(/\{[^}]*\}/);
|
|
2510
|
+
if (destructureMatch) {
|
|
2511
|
+
const destructureContent = destructureMatch[0];
|
|
2512
|
+
const destructureStart = destructureMatch.index;
|
|
2513
|
+
if (new RegExp(`\\b${escaped}\\b`).test(destructureContent)) {
|
|
2514
|
+
let updated = destructureContent;
|
|
2515
|
+
const propPattern = new RegExp(`\\b${escaped}\\b\\s*,?`);
|
|
2516
|
+
updated = updated.replace(propPattern, (match) => {
|
|
2517
|
+
if (match.endsWith(",")) return "";
|
|
2518
|
+
return "";
|
|
2519
|
+
});
|
|
2520
|
+
updated = updated.replace(/,\s*\},/, "}");
|
|
2521
|
+
updated = updated.replace(/\{,\s*/, "{");
|
|
2522
|
+
updated = updated.replace(/\s*,\s*\}/, "}");
|
|
2523
|
+
if (updated !== destructureContent) return line.slice(0, destructureStart) + updated + line.slice(destructureStart + destructureContent.length);
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
let bestStart = -1;
|
|
2527
|
+
let bestEnd = -1;
|
|
2528
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
2529
|
+
const target = Math.max(0, column - 1);
|
|
2530
|
+
for (const match of line.matchAll(new RegExp(`\\b${escaped}\\b`, "g"))) {
|
|
2531
|
+
if (match.index === void 0) continue;
|
|
2532
|
+
const start = match.index;
|
|
2533
|
+
const end = start + name.length;
|
|
2534
|
+
if (start > 0 && line[start - 1] === "_") continue;
|
|
2535
|
+
const distance = target >= start && target <= end ? 0 : Math.abs(start - target);
|
|
2536
|
+
if (distance < bestDistance) {
|
|
2537
|
+
bestDistance = distance;
|
|
2538
|
+
bestStart = start;
|
|
2539
|
+
bestEnd = end;
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
if (bestStart < 0 || bestEnd < 0) return line;
|
|
2543
|
+
return `${line.slice(0, bestStart)}_${name}${line.slice(bestEnd)}`;
|
|
2544
|
+
};
|
|
2545
|
+
const applyUnusedVarPrefixFixes = (rootDirectory, candidates) => {
|
|
2546
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
2547
|
+
for (const candidate of candidates) {
|
|
2548
|
+
const absolute = path.isAbsolute(candidate.filePath) ? candidate.filePath : path.join(rootDirectory, candidate.filePath);
|
|
2549
|
+
const entries = byFile.get(absolute) ?? [];
|
|
2550
|
+
entries.push(candidate);
|
|
2551
|
+
byFile.set(absolute, entries);
|
|
2552
|
+
}
|
|
2553
|
+
for (const [filePath, fileCandidates] of byFile.entries()) {
|
|
2554
|
+
if (!fs.existsSync(filePath)) continue;
|
|
2555
|
+
const lines = fs.readFileSync(filePath, "utf-8").split("\n");
|
|
2556
|
+
const ordered = [...fileCandidates].sort((a, b) => {
|
|
2557
|
+
if (a.line !== b.line) return a.line - b.line;
|
|
2558
|
+
return a.column - b.column;
|
|
2559
|
+
});
|
|
2560
|
+
let changed = false;
|
|
2561
|
+
for (const candidate of ordered) {
|
|
2562
|
+
const lineIndex = candidate.line - 1;
|
|
2563
|
+
if (lineIndex < 0 || lineIndex >= lines.length) continue;
|
|
2564
|
+
const current = lines[lineIndex];
|
|
2565
|
+
const updated = prefixIdentifierOnLine(current, candidate.name, candidate.column, candidate.type);
|
|
2566
|
+
if (updated !== current) {
|
|
2567
|
+
lines[lineIndex] = updated;
|
|
2568
|
+
changed = true;
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
if (changed) fs.writeFileSync(filePath, lines.join("\n"));
|
|
2572
|
+
}
|
|
2573
|
+
};
|
|
2574
|
+
const removeDuplicateKeyLines = (rootDirectory, diagnostics) => {
|
|
2575
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
2576
|
+
for (const d of diagnostics) {
|
|
2577
|
+
const keyMatch = d.message.match(/Duplicate key '([^']+)'/);
|
|
2578
|
+
if (!keyMatch) continue;
|
|
2579
|
+
const absolute = path.isAbsolute(d.filePath) ? d.filePath : path.join(rootDirectory, d.filePath);
|
|
2580
|
+
const entries = byFile.get(absolute) ?? [];
|
|
2581
|
+
entries.push({
|
|
2582
|
+
key: keyMatch[1],
|
|
2583
|
+
line: d.line
|
|
2584
|
+
});
|
|
2585
|
+
byFile.set(absolute, entries);
|
|
2586
|
+
}
|
|
2587
|
+
for (const [filePath, dupes] of byFile) {
|
|
2588
|
+
if (!fs.existsSync(filePath)) continue;
|
|
2589
|
+
const lines = fs.readFileSync(filePath, "utf-8").split("\n");
|
|
2590
|
+
const toRemove = /* @__PURE__ */ new Set();
|
|
2591
|
+
for (const { key } of dupes) {
|
|
2592
|
+
const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2593
|
+
const keyPattern = new RegExp(`^\\s*['"]?${escaped}['"]?\\s*:|^\\s*${escaped}\\s*:`);
|
|
2594
|
+
const matches = [];
|
|
2595
|
+
for (let i = 0; i < lines.length; i++) if (keyPattern.test(lines[i])) matches.push(i);
|
|
2596
|
+
for (let j = 1; j < matches.length; j++) toRemove.add(matches[j]);
|
|
2597
|
+
}
|
|
2598
|
+
if (toRemove.size === 0) continue;
|
|
2599
|
+
const filtered = lines.filter((_, i) => !toRemove.has(i));
|
|
2600
|
+
fs.writeFileSync(filePath, filtered.join("\n"));
|
|
2601
|
+
}
|
|
2602
|
+
};
|
|
2447
2603
|
const runOxlint = async (context) => {
|
|
2448
2604
|
const configPath = path.join(os.tmpdir(), `aislop-oxlintrc-${process.pid}.json`);
|
|
2449
2605
|
const config = createOxlintConfig({
|
|
@@ -2506,6 +2662,7 @@ const fixOxlint = async (context) => {
|
|
|
2506
2662
|
configPath,
|
|
2507
2663
|
"--fix",
|
|
2508
2664
|
"--fix-suggestions",
|
|
2665
|
+
"--fix-dangerously",
|
|
2509
2666
|
"."
|
|
2510
2667
|
];
|
|
2511
2668
|
const result = await runSubprocess(process.execPath, args, {
|
|
@@ -2513,10 +2670,18 @@ const fixOxlint = async (context) => {
|
|
|
2513
2670
|
timeout: 12e4
|
|
2514
2671
|
});
|
|
2515
2672
|
if (result.exitCode !== 0) throw new Error(result.stderr || result.stdout || `Oxlint exited with code ${result.exitCode}`);
|
|
2673
|
+
const remaining = await runOxlint(context);
|
|
2674
|
+
const candidates = collectUnusedVarCandidates(remaining);
|
|
2675
|
+
if (candidates.length > 0) applyUnusedVarPrefixFixes(context.rootDirectory, candidates);
|
|
2676
|
+
const duplicateKeys = remaining.filter((d) => d.message.startsWith("Duplicate key"));
|
|
2677
|
+
if (duplicateKeys.length > 0) removeDuplicateKeyLines(context.rootDirectory, duplicateKeys);
|
|
2516
2678
|
} finally {
|
|
2517
2679
|
if (fs.existsSync(configPath)) fs.unlinkSync(configPath);
|
|
2518
2680
|
}
|
|
2519
2681
|
};
|
|
2682
|
+
const fixOxlintForce = async (context) => {
|
|
2683
|
+
return fixOxlint(context);
|
|
2684
|
+
};
|
|
2520
2685
|
|
|
2521
2686
|
//#endregion
|
|
2522
2687
|
//#region src/engines/lint/ruff.ts
|
|
@@ -2559,6 +2724,18 @@ const fixRuffLint = async (rootDirectory) => {
|
|
|
2559
2724
|
});
|
|
2560
2725
|
if (result.exitCode !== 0) throw new Error(result.stderr || result.stdout || `ruff check --fix exited with code ${result.exitCode}`);
|
|
2561
2726
|
};
|
|
2727
|
+
const fixRuffLintForce = async (rootDirectory) => {
|
|
2728
|
+
const result = await runSubprocess(resolveToolBinary("ruff"), [
|
|
2729
|
+
"check",
|
|
2730
|
+
"--fix",
|
|
2731
|
+
"--unsafe-fixes",
|
|
2732
|
+
rootDirectory
|
|
2733
|
+
], {
|
|
2734
|
+
cwd: rootDirectory,
|
|
2735
|
+
timeout: 6e4
|
|
2736
|
+
});
|
|
2737
|
+
if (result.exitCode !== 0) throw new Error(result.stderr || result.stdout || `ruff check --fix exited with code ${result.exitCode}`);
|
|
2738
|
+
};
|
|
2562
2739
|
|
|
2563
2740
|
//#endregion
|
|
2564
2741
|
//#region src/engines/lint/index.ts
|
|
@@ -2569,7 +2746,7 @@ const lintEngine = {
|
|
|
2569
2746
|
const { languages, installedTools } = context;
|
|
2570
2747
|
const promises = [];
|
|
2571
2748
|
if (languages.includes("typescript") || languages.includes("javascript")) promises.push(runOxlint(context));
|
|
2572
|
-
if (context.frameworks.includes("expo")) promises.push(
|
|
2749
|
+
if (context.frameworks.includes("expo")) promises.push(Promise.resolve().then(() => expo_doctor_exports).then((mod) => mod.runExpoDoctor(context)));
|
|
2573
2750
|
if (languages.includes("python") && installedTools["ruff"]) promises.push(runRuffLint(context));
|
|
2574
2751
|
if (languages.includes("go") && installedTools["golangci-lint"]) promises.push(runGolangciLint(context));
|
|
2575
2752
|
if (languages.includes("rust") && installedTools["cargo"]) promises.push(runGenericLinter(context, "rust"));
|
|
@@ -3194,11 +3371,11 @@ const logger = {
|
|
|
3194
3371
|
* Application version — injected at build time by tsdown from package.json.
|
|
3195
3372
|
* The fallback should always match the "version" field in package.json.
|
|
3196
3373
|
*/
|
|
3197
|
-
const APP_VERSION = "0.
|
|
3374
|
+
const APP_VERSION = "0.3.0";
|
|
3198
3375
|
|
|
3199
3376
|
//#endregion
|
|
3200
3377
|
//#region src/output/layout.ts
|
|
3201
|
-
const formatElapsed$
|
|
3378
|
+
const formatElapsed$2 = (elapsedMs) => elapsedMs < 1e3 ? `${Math.round(elapsedMs)}ms` : `${(elapsedMs / 1e3).toFixed(1)}s`;
|
|
3202
3379
|
const printCommandHeader = (commandName) => {
|
|
3203
3380
|
logger.log(`${highlighter.bold(`aislop ${commandName.toLowerCase()}`)} ${highlighter.dim(`v${APP_VERSION}`)}`);
|
|
3204
3381
|
logger.break();
|
|
@@ -3213,7 +3390,7 @@ const printProjectMetadata = (project) => {
|
|
|
3213
3390
|
|
|
3214
3391
|
//#endregion
|
|
3215
3392
|
//#region src/output/scan-progress.ts
|
|
3216
|
-
const SPINNER_FRAMES = [
|
|
3393
|
+
const SPINNER_FRAMES$1 = [
|
|
3217
3394
|
"⠋",
|
|
3218
3395
|
"⠙",
|
|
3219
3396
|
"⠹",
|
|
@@ -3226,20 +3403,20 @@ const SPINNER_FRAMES = [
|
|
|
3226
3403
|
"⠏"
|
|
3227
3404
|
];
|
|
3228
3405
|
const shouldRenderLiveScanProgress = () => Boolean(process.stderr.isTTY) && process.env.CI !== "true" && process.env.CI !== "1";
|
|
3229
|
-
const formatElapsed = (elapsedMs) => elapsedMs < 1e3 ? `${Math.round(elapsedMs)}ms` : `${(elapsedMs / 1e3).toFixed(1)}s`;
|
|
3406
|
+
const formatElapsed$1 = (elapsedMs) => elapsedMs < 1e3 ? `${Math.round(elapsedMs)}ms` : `${(elapsedMs / 1e3).toFixed(1)}s`;
|
|
3230
3407
|
const truncateText = (text, maxLength = 52) => text.length <= maxLength ? text : `${text.slice(0, maxLength - 1)}…`;
|
|
3231
3408
|
const getIssueSummary = (result) => {
|
|
3232
3409
|
const errors = result.diagnostics.filter((d) => d.severity === "error").length;
|
|
3233
3410
|
const warnings = result.diagnostics.filter((d) => d.severity === "warning").length;
|
|
3234
|
-
if (errors === 0 && warnings === 0) return `Done (0 issues, ${formatElapsed(result.elapsed)})`;
|
|
3411
|
+
if (errors === 0 && warnings === 0) return `Done (0 issues, ${formatElapsed$1(result.elapsed)})`;
|
|
3235
3412
|
const parts = [];
|
|
3236
3413
|
if (errors > 0) parts.push(`${errors} error${errors === 1 ? "" : "s"}`);
|
|
3237
3414
|
if (warnings > 0) parts.push(`${warnings} warning${warnings === 1 ? "" : "s"}`);
|
|
3238
|
-
return `Done (${parts.join(", ")}, ${formatElapsed(result.elapsed)})`;
|
|
3415
|
+
return `Done (${parts.join(", ")}, ${formatElapsed$1(result.elapsed)})`;
|
|
3239
3416
|
};
|
|
3240
|
-
const getStatusParts = (state, frameIndex) => {
|
|
3417
|
+
const getStatusParts$1 = (state, frameIndex) => {
|
|
3241
3418
|
if (state.status === "running") return {
|
|
3242
|
-
icon: highlighter.info(SPINNER_FRAMES[frameIndex % SPINNER_FRAMES.length]),
|
|
3419
|
+
icon: highlighter.info(SPINNER_FRAMES$1[frameIndex % SPINNER_FRAMES$1.length]),
|
|
3243
3420
|
detail: highlighter.info("Running")
|
|
3244
3421
|
};
|
|
3245
3422
|
if (state.status === "done") {
|
|
@@ -3278,11 +3455,11 @@ const renderScanProgressBlock = (states, frameIndex) => {
|
|
|
3278
3455
|
const headingStatus = completedCount === states.length ? highlighter.dim("complete") : runningCount > 0 ? highlighter.dim(`${runningCount} running`) : highlighter.dim("starting");
|
|
3279
3456
|
return `${[` ${highlighter.bold(`Engines ${completedCount}/${states.length}`)} ${headingStatus}`, ...states.map((state) => {
|
|
3280
3457
|
const label = getEngineLabel(state.engine).padEnd(labelWidth, " ");
|
|
3281
|
-
const { icon, detail } = getStatusParts(state, frameIndex);
|
|
3458
|
+
const { icon, detail } = getStatusParts$1(state, frameIndex);
|
|
3282
3459
|
return ` ${icon} ${label} ${detail}`;
|
|
3283
3460
|
})].join("\n")}\n`;
|
|
3284
3461
|
};
|
|
3285
|
-
const clearRenderedLines$
|
|
3462
|
+
const clearRenderedLines$2 = (lineCount) => {
|
|
3286
3463
|
if (lineCount === 0) return;
|
|
3287
3464
|
process.stderr.write(`\u001B[${lineCount}F`);
|
|
3288
3465
|
for (let index = 0; index < lineCount; index += 1) {
|
|
@@ -3334,7 +3511,7 @@ var ScanProgressRenderer = class {
|
|
|
3334
3511
|
}
|
|
3335
3512
|
render() {
|
|
3336
3513
|
if (!shouldRenderLiveScanProgress()) return;
|
|
3337
|
-
if (this.previousLineCount > 0) clearRenderedLines$
|
|
3514
|
+
if (this.previousLineCount > 0) clearRenderedLines$2(this.previousLineCount);
|
|
3338
3515
|
const output = renderScanProgressBlock(this.states, this.frameIndex);
|
|
3339
3516
|
process.stderr.write(output);
|
|
3340
3517
|
this.previousLineCount = output.split("\n").length - 1;
|
|
@@ -3976,6 +4153,42 @@ const doctorCommand = async (directory) => {
|
|
|
3976
4153
|
printDoctorConclusion(isAllGood());
|
|
3977
4154
|
};
|
|
3978
4155
|
|
|
4156
|
+
//#endregion
|
|
4157
|
+
//#region src/engines/ai-slop/dead-patterns-fix.ts
|
|
4158
|
+
/**
|
|
4159
|
+
* Removes lines flagged as fixable by the trivial-comment and dead-pattern detectors.
|
|
4160
|
+
* Specifically handles:
|
|
4161
|
+
* - ai-slop/trivial-comment (trivial comments that restate the code)
|
|
4162
|
+
* - ai-slop/console-leftover (console.log/debug/info left in production)
|
|
4163
|
+
*/
|
|
4164
|
+
const fixDeadPatterns = async (context) => {
|
|
4165
|
+
const fixable = [...await detectTrivialComments(context), ...await detectDeadPatterns(context)].filter((d) => d.fixable);
|
|
4166
|
+
if (fixable.length === 0) return;
|
|
4167
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
4168
|
+
for (const d of fixable) {
|
|
4169
|
+
const absolute = path.isAbsolute(d.filePath) ? d.filePath : path.join(context.rootDirectory, d.filePath);
|
|
4170
|
+
const lines = byFile.get(absolute) ?? /* @__PURE__ */ new Set();
|
|
4171
|
+
lines.add(d.line);
|
|
4172
|
+
byFile.set(absolute, lines);
|
|
4173
|
+
}
|
|
4174
|
+
for (const [filePath, lineNumbers] of byFile) {
|
|
4175
|
+
if (!fs.existsSync(filePath)) continue;
|
|
4176
|
+
const lines = fs.readFileSync(filePath, "utf-8").split("\n");
|
|
4177
|
+
const filtered = [];
|
|
4178
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4179
|
+
const lineNo = i + 1;
|
|
4180
|
+
if (lineNumbers.has(lineNo)) continue;
|
|
4181
|
+
filtered.push(lines[i]);
|
|
4182
|
+
}
|
|
4183
|
+
const collapsed = [];
|
|
4184
|
+
for (const line of filtered) {
|
|
4185
|
+
if (line.trim() === "" && collapsed.length > 0 && collapsed[collapsed.length - 1].trim() === "") continue;
|
|
4186
|
+
collapsed.push(line);
|
|
4187
|
+
}
|
|
4188
|
+
fs.writeFileSync(filePath, collapsed.join("\n"));
|
|
4189
|
+
}
|
|
4190
|
+
};
|
|
4191
|
+
|
|
3979
4192
|
//#endregion
|
|
3980
4193
|
//#region src/engines/ai-slop/unused-imports-fix.ts
|
|
3981
4194
|
const fixUnusedImports = async (context) => {
|
|
@@ -3997,7 +4210,7 @@ const fixUnusedImports = async (context) => {
|
|
|
3997
4210
|
for (const [lineNo, syms] of symbolsByLine) {
|
|
3998
4211
|
const lineIdx = lineNo - 1;
|
|
3999
4212
|
const allUnused = syms.every((s) => unusedNames.has(s.name));
|
|
4000
|
-
const importSpan =
|
|
4213
|
+
const importSpan = JS_EXTENSIONS.has(analysis.ext) ? getJsImportSpan(lines, lineIdx) : [lineIdx];
|
|
4001
4214
|
if (allUnused) for (const idx of importSpan) linesToRemove.add(idx);
|
|
4002
4215
|
else if (JS_EXTENSIONS.has(analysis.ext)) rewriteJsImportSpan(lines, importSpan, syms, unusedNames);
|
|
4003
4216
|
else if (PY_EXTENSIONS.has(analysis.ext)) rewritePyImportLine(lines, lineIdx, syms, unusedNames);
|
|
@@ -4010,11 +4223,14 @@ const fixUnusedImports = async (context) => {
|
|
|
4010
4223
|
fs.writeFileSync(filePath, filtered.join("\n"));
|
|
4011
4224
|
}
|
|
4012
4225
|
};
|
|
4013
|
-
const
|
|
4226
|
+
const getJsImportSpan = (lines, startIdx) => {
|
|
4014
4227
|
const span = [startIdx];
|
|
4228
|
+
let fullImport = lines[startIdx]?.trim() ?? "";
|
|
4229
|
+
if (!fullImport.startsWith("import ")) return span;
|
|
4015
4230
|
let idx = startIdx + 1;
|
|
4016
|
-
while (
|
|
4231
|
+
while (!fullImport.includes("from") && idx < lines.length) {
|
|
4017
4232
|
span.push(idx);
|
|
4233
|
+
fullImport += ` ${lines[idx].trim()}`;
|
|
4018
4234
|
idx++;
|
|
4019
4235
|
}
|
|
4020
4236
|
return span;
|
|
@@ -4024,23 +4240,31 @@ const rewriteJsImportSpan = (lines, span, syms, unusedNames) => {
|
|
|
4024
4240
|
const namedMatch = fullImport.match(/\{([^}]+)\}/s);
|
|
4025
4241
|
if (!namedMatch) return;
|
|
4026
4242
|
const unusedNamed = syms.filter((s) => !s.isDefault && !s.isNamespace && unusedNames.has(s.name));
|
|
4027
|
-
|
|
4243
|
+
const defaultUnused = syms.some((s) => s.isDefault && unusedNames.has(s.name));
|
|
4244
|
+
if (unusedNamed.length === 0 && !defaultUnused) return;
|
|
4028
4245
|
const unusedNamedSet = new Set(unusedNamed.map((s) => s.name));
|
|
4029
4246
|
const keptSpecifiers = namedMatch[1].split(",").map((s) => s.trim()).filter(Boolean).filter((spec) => {
|
|
4030
4247
|
const parts = spec.split(/\s+as\s+/);
|
|
4031
4248
|
const localName = parts.length > 1 ? parts[1].trim().replace(/^type\s+/, "") : parts[0].trim().replace(/^type\s+/, "");
|
|
4032
4249
|
return !unusedNamedSet.has(localName);
|
|
4033
4250
|
});
|
|
4251
|
+
const fromMatch = fullImport.match(/from\s+["']([^"']+)["'];?/);
|
|
4252
|
+
const fromClause = fromMatch ? `from "${fromMatch[1]}"` : "";
|
|
4034
4253
|
if (keptSpecifiers.length === 0) {
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4254
|
+
const usedDefault = syms.find((s) => s.isDefault && !unusedNames.has(s.name));
|
|
4255
|
+
if (usedDefault) {
|
|
4256
|
+
const defaultMatch = fullImport.match(/^import\s+(\w+)/);
|
|
4257
|
+
const defaultName = defaultMatch ? defaultMatch[1] : usedDefault.name;
|
|
4258
|
+
lines[span[0]] = `import ${defaultName} ${fromClause};`;
|
|
4038
4259
|
for (let i = 1; i < span.length; i++) lines[span[i]] = REMOVE_MARKER;
|
|
4039
|
-
}
|
|
4260
|
+
} else for (const idx of span) lines[idx] = REMOVE_MARKER;
|
|
4261
|
+
return;
|
|
4262
|
+
}
|
|
4263
|
+
if (defaultUnused) {
|
|
4264
|
+
lines[span[0]] = `import { ${keptSpecifiers.join(", ")} } ${fromClause};`;
|
|
4265
|
+
for (let i = 1; i < span.length; i++) lines[span[i]] = REMOVE_MARKER;
|
|
4040
4266
|
return;
|
|
4041
4267
|
}
|
|
4042
|
-
const fromMatch = fullImport.match(/\}\s*(from\s+.+)$/s);
|
|
4043
|
-
const fromClause = fromMatch ? fromMatch[1].trim() : "";
|
|
4044
4268
|
const importPrefix = fullImport.match(/^(import\s+(?:\w+\s*,\s*)?)/);
|
|
4045
4269
|
const prefix = importPrefix ? importPrefix[1] : "import ";
|
|
4046
4270
|
const wasMultiLine = span.length > 1;
|
|
@@ -4048,8 +4272,8 @@ const rewriteJsImportSpan = (lines, span, syms, unusedNames) => {
|
|
|
4048
4272
|
if (wasMultiLine && keptSpecifiers.length > 2) {
|
|
4049
4273
|
const indentMatch = lines[span[1]]?.match(/^(\s+)/);
|
|
4050
4274
|
const indent = indentMatch ? indentMatch[1] : " ";
|
|
4051
|
-
newImport = `${prefix}{\n${keptSpecifiers.map((s) => `${indent}${s},`).join("\n")}\n} ${fromClause}
|
|
4052
|
-
} else newImport = `${prefix}{ ${keptSpecifiers.join(", ")} } ${fromClause}
|
|
4275
|
+
newImport = `${prefix}{\n${keptSpecifiers.map((s) => `${indent}${s},`).join("\n")}\n} ${fromClause};`;
|
|
4276
|
+
} else newImport = `${prefix}{ ${keptSpecifiers.join(", ")} } ${fromClause};`;
|
|
4053
4277
|
lines[span[0]] = newImport;
|
|
4054
4278
|
for (let i = 1; i < span.length; i++) lines[span[i]] = REMOVE_MARKER;
|
|
4055
4279
|
};
|
|
@@ -4070,7 +4294,289 @@ const rewritePyImportLine = (lines, lineIdx, syms, unusedNames) => {
|
|
|
4070
4294
|
};
|
|
4071
4295
|
|
|
4072
4296
|
//#endregion
|
|
4073
|
-
//#region src/
|
|
4297
|
+
//#region src/engines/lint/expo-doctor.ts
|
|
4298
|
+
var expo_doctor_exports = /* @__PURE__ */ __exportAll({ runExpoDoctor: () => runExpoDoctor });
|
|
4299
|
+
const esmRequire = createRequire(import.meta.url);
|
|
4300
|
+
const ISSUE_PREFIX = "✖ ";
|
|
4301
|
+
const resolveExpoDoctorScript = () => {
|
|
4302
|
+
try {
|
|
4303
|
+
const packageJsonPath = esmRequire.resolve("expo-doctor/package.json");
|
|
4304
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
4305
|
+
const binRelativePath = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.["expo-doctor"];
|
|
4306
|
+
if (!binRelativePath) return null;
|
|
4307
|
+
return path.join(path.dirname(packageJsonPath), binRelativePath);
|
|
4308
|
+
} catch {
|
|
4309
|
+
return null;
|
|
4310
|
+
}
|
|
4311
|
+
};
|
|
4312
|
+
const toRuleSuffix = (title) => {
|
|
4313
|
+
const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
4314
|
+
return slug.length > 0 ? slug : "issue";
|
|
4315
|
+
};
|
|
4316
|
+
const parseIssues = (output) => {
|
|
4317
|
+
const lines = output.split("\n").map((line) => line.trimEnd());
|
|
4318
|
+
const startIndex = lines.findIndex((line) => line.includes("Possible issues detected:"));
|
|
4319
|
+
if (startIndex < 0) return [];
|
|
4320
|
+
const issues = [];
|
|
4321
|
+
let current = null;
|
|
4322
|
+
let inAdvice = false;
|
|
4323
|
+
for (let i = startIndex + 1; i < lines.length; i += 1) {
|
|
4324
|
+
const line = lines[i].trim();
|
|
4325
|
+
if (/^\d+\s+checks failed/.test(line)) break;
|
|
4326
|
+
if (line.length === 0) continue;
|
|
4327
|
+
if (line.startsWith(ISSUE_PREFIX)) {
|
|
4328
|
+
if (current) issues.push(current);
|
|
4329
|
+
current = {
|
|
4330
|
+
title: line.slice(2).trim(),
|
|
4331
|
+
details: [],
|
|
4332
|
+
advice: []
|
|
4333
|
+
};
|
|
4334
|
+
inAdvice = false;
|
|
4335
|
+
continue;
|
|
4336
|
+
}
|
|
4337
|
+
if (!current) continue;
|
|
4338
|
+
if (line === "Advice:") {
|
|
4339
|
+
inAdvice = true;
|
|
4340
|
+
continue;
|
|
4341
|
+
}
|
|
4342
|
+
if (inAdvice) current.advice.push(line);
|
|
4343
|
+
else current.details.push(line);
|
|
4344
|
+
}
|
|
4345
|
+
if (current) issues.push(current);
|
|
4346
|
+
return issues;
|
|
4347
|
+
};
|
|
4348
|
+
const parseConfigError = (output) => {
|
|
4349
|
+
const line = output.split("\n").find((candidate) => candidate.trim().startsWith("ConfigError:"));
|
|
4350
|
+
return line ? line.trim() : null;
|
|
4351
|
+
};
|
|
4352
|
+
const toDiagnostics = (issues) => issues.map((issue) => {
|
|
4353
|
+
const helpParts = [issue.details.join(" ").trim(), issue.advice.join(" ").trim()].filter((part) => part.length > 0);
|
|
4354
|
+
return {
|
|
4355
|
+
filePath: "package.json",
|
|
4356
|
+
engine: "lint",
|
|
4357
|
+
rule: `expo-doctor/${toRuleSuffix(issue.title)}`,
|
|
4358
|
+
severity: "warning",
|
|
4359
|
+
message: `Expo Doctor: ${issue.title}`,
|
|
4360
|
+
help: helpParts.join(" "),
|
|
4361
|
+
line: 0,
|
|
4362
|
+
column: 0,
|
|
4363
|
+
category: "Expo",
|
|
4364
|
+
fixable: false
|
|
4365
|
+
};
|
|
4366
|
+
});
|
|
4367
|
+
const runExpoDoctor = async (context) => {
|
|
4368
|
+
const scriptPath = resolveExpoDoctorScript();
|
|
4369
|
+
let stdout = "";
|
|
4370
|
+
let stderr = "";
|
|
4371
|
+
try {
|
|
4372
|
+
if (scriptPath) {
|
|
4373
|
+
const result = await runSubprocess(process.execPath, [
|
|
4374
|
+
scriptPath,
|
|
4375
|
+
context.rootDirectory,
|
|
4376
|
+
"--verbose"
|
|
4377
|
+
], {
|
|
4378
|
+
cwd: context.rootDirectory,
|
|
4379
|
+
timeout: 12e4
|
|
4380
|
+
});
|
|
4381
|
+
stdout = result.stdout;
|
|
4382
|
+
stderr = result.stderr;
|
|
4383
|
+
} else {
|
|
4384
|
+
const result = await runSubprocess("npx", [
|
|
4385
|
+
"--yes",
|
|
4386
|
+
"expo-doctor",
|
|
4387
|
+
context.rootDirectory,
|
|
4388
|
+
"--verbose"
|
|
4389
|
+
], {
|
|
4390
|
+
cwd: context.rootDirectory,
|
|
4391
|
+
timeout: 12e4
|
|
4392
|
+
});
|
|
4393
|
+
stdout = result.stdout;
|
|
4394
|
+
stderr = result.stderr;
|
|
4395
|
+
}
|
|
4396
|
+
} catch {
|
|
4397
|
+
return [];
|
|
4398
|
+
}
|
|
4399
|
+
const output = [stdout, stderr].filter(Boolean).join("\n");
|
|
4400
|
+
if (!output) return [];
|
|
4401
|
+
const configError = parseConfigError(output);
|
|
4402
|
+
if (configError) return [{
|
|
4403
|
+
filePath: "package.json",
|
|
4404
|
+
engine: "lint",
|
|
4405
|
+
rule: "expo-doctor/config-error",
|
|
4406
|
+
severity: "warning",
|
|
4407
|
+
message: configError,
|
|
4408
|
+
help: "Install project dependencies, then re-run `aislop scan`.",
|
|
4409
|
+
line: 0,
|
|
4410
|
+
column: 0,
|
|
4411
|
+
category: "Expo",
|
|
4412
|
+
fixable: false
|
|
4413
|
+
}];
|
|
4414
|
+
return toDiagnostics(parseIssues(output));
|
|
4415
|
+
};
|
|
4416
|
+
|
|
4417
|
+
//#endregion
|
|
4418
|
+
//#region src/output/fix-progress.ts
|
|
4419
|
+
const SPINNER_FRAMES = [
|
|
4420
|
+
"⠋",
|
|
4421
|
+
"⠙",
|
|
4422
|
+
"⠹",
|
|
4423
|
+
"⠸",
|
|
4424
|
+
"⠼",
|
|
4425
|
+
"⠴",
|
|
4426
|
+
"⠦",
|
|
4427
|
+
"⠧",
|
|
4428
|
+
"⠇",
|
|
4429
|
+
"⠏"
|
|
4430
|
+
];
|
|
4431
|
+
const shouldRenderLive = () => Boolean(process.stderr.isTTY) && process.env.CI !== "true" && process.env.CI !== "1";
|
|
4432
|
+
const formatElapsed = (elapsedMs) => elapsedMs < 1e3 ? `${Math.round(elapsedMs)}ms` : `${(elapsedMs / 1e3).toFixed(1)}s`;
|
|
4433
|
+
const getStepSummary = (result) => {
|
|
4434
|
+
if (result.failed) return `Failed (${result.afterIssues} remain)`;
|
|
4435
|
+
if (result.beforeIssues === 0) return `0 issues, ${formatElapsed(result.elapsedMs)}`;
|
|
4436
|
+
if (result.afterIssues === 0) return `${result.resolvedIssues} resolved, ${formatElapsed(result.elapsedMs)}`;
|
|
4437
|
+
if (result.resolvedIssues > 0) return `${result.resolvedIssues} resolved, ${result.afterIssues} remaining, ${formatElapsed(result.elapsedMs)}`;
|
|
4438
|
+
return `no changes, ${result.afterIssues} issue${result.afterIssues === 1 ? "" : "s"}, ${formatElapsed(result.elapsedMs)}`;
|
|
4439
|
+
};
|
|
4440
|
+
const getStatusParts = (state, frameIndex) => {
|
|
4441
|
+
if (state.status === "running") return {
|
|
4442
|
+
icon: highlighter.info(SPINNER_FRAMES[frameIndex % SPINNER_FRAMES.length]),
|
|
4443
|
+
detail: highlighter.info("Running")
|
|
4444
|
+
};
|
|
4445
|
+
if (state.status === "done" && state.result) return {
|
|
4446
|
+
icon: state.result.afterIssues > 0 ? highlighter.warn("!") : highlighter.success("✓"),
|
|
4447
|
+
detail: highlighter.dim(`Done (${getStepSummary(state.result)})`)
|
|
4448
|
+
};
|
|
4449
|
+
if (state.status === "failed" && state.result) return {
|
|
4450
|
+
icon: highlighter.error("✗"),
|
|
4451
|
+
detail: highlighter.dim(`Failed (${getStepSummary(state.result)})`)
|
|
4452
|
+
};
|
|
4453
|
+
return {
|
|
4454
|
+
icon: highlighter.dim("○"),
|
|
4455
|
+
detail: highlighter.dim("Waiting")
|
|
4456
|
+
};
|
|
4457
|
+
};
|
|
4458
|
+
const clearRenderedLines$1 = (lineCount) => {
|
|
4459
|
+
if (lineCount === 0) return;
|
|
4460
|
+
process.stderr.write(`\u001B[${lineCount}F`);
|
|
4461
|
+
for (let index = 0; index < lineCount; index += 1) {
|
|
4462
|
+
process.stderr.write("\x1B[2K");
|
|
4463
|
+
if (index < lineCount - 1) process.stderr.write("\x1B[1E");
|
|
4464
|
+
}
|
|
4465
|
+
if (lineCount > 1) process.stderr.write(`\u001B[${lineCount - 1}F`);
|
|
4466
|
+
};
|
|
4467
|
+
const renderFixProgressBlock = (states, frameIndex) => {
|
|
4468
|
+
if (states.length === 0) return ` ${highlighter.bold("Fixes 0/0")} ${highlighter.dim("nothing to run")}\n`;
|
|
4469
|
+
const completedCount = states.filter((s) => s.status === "done" || s.status === "failed").length;
|
|
4470
|
+
const runningCount = states.filter((s) => s.status === "running").length;
|
|
4471
|
+
const labelWidth = Math.max(...states.map((s) => s.name.length));
|
|
4472
|
+
const headingStatus = completedCount === states.length ? highlighter.dim("complete") : runningCount > 0 ? highlighter.dim(`${runningCount} running`) : highlighter.dim("starting");
|
|
4473
|
+
return `${[` ${highlighter.bold(`Fixes ${completedCount}/${states.length}`)} ${headingStatus}`, ...states.map((state) => {
|
|
4474
|
+
const label = state.name.padEnd(labelWidth, " ");
|
|
4475
|
+
const { icon, detail } = getStatusParts(state, frameIndex);
|
|
4476
|
+
return ` ${icon} ${label} ${detail}`;
|
|
4477
|
+
})].join("\n")}\n`;
|
|
4478
|
+
};
|
|
4479
|
+
var FixProgressRenderer = class {
|
|
4480
|
+
states;
|
|
4481
|
+
previousLineCount = 0;
|
|
4482
|
+
frameIndex = 0;
|
|
4483
|
+
timer;
|
|
4484
|
+
live;
|
|
4485
|
+
constructor(stepNames) {
|
|
4486
|
+
this.states = stepNames.map((name) => ({
|
|
4487
|
+
name,
|
|
4488
|
+
status: "pending"
|
|
4489
|
+
}));
|
|
4490
|
+
this.live = shouldRenderLive();
|
|
4491
|
+
}
|
|
4492
|
+
isLive() {
|
|
4493
|
+
return this.live;
|
|
4494
|
+
}
|
|
4495
|
+
start() {
|
|
4496
|
+
if (!this.live) return;
|
|
4497
|
+
this.render();
|
|
4498
|
+
this.timer = setInterval(() => {
|
|
4499
|
+
this.frameIndex += 1;
|
|
4500
|
+
this.render();
|
|
4501
|
+
}, 100);
|
|
4502
|
+
this.timer.unref();
|
|
4503
|
+
}
|
|
4504
|
+
markStarted(name) {
|
|
4505
|
+
const state = this.states.find((s) => s.name === name);
|
|
4506
|
+
if (!state) return;
|
|
4507
|
+
state.status = "running";
|
|
4508
|
+
this.render();
|
|
4509
|
+
}
|
|
4510
|
+
markComplete(name, result) {
|
|
4511
|
+
const state = this.states.find((s) => s.name === name);
|
|
4512
|
+
if (!state) return;
|
|
4513
|
+
state.status = result.failed ? "failed" : "done";
|
|
4514
|
+
state.result = result;
|
|
4515
|
+
this.render();
|
|
4516
|
+
}
|
|
4517
|
+
stop() {
|
|
4518
|
+
if (this.timer) {
|
|
4519
|
+
clearInterval(this.timer);
|
|
4520
|
+
this.timer = void 0;
|
|
4521
|
+
}
|
|
4522
|
+
if (!this.live) return;
|
|
4523
|
+
this.render();
|
|
4524
|
+
}
|
|
4525
|
+
render() {
|
|
4526
|
+
if (!this.live) return;
|
|
4527
|
+
if (this.previousLineCount > 0) clearRenderedLines$1(this.previousLineCount);
|
|
4528
|
+
const output = renderFixProgressBlock(this.states, this.frameIndex);
|
|
4529
|
+
process.stderr.write(output);
|
|
4530
|
+
this.previousLineCount = output.split("\n").length - 1;
|
|
4531
|
+
}
|
|
4532
|
+
};
|
|
4533
|
+
|
|
4534
|
+
//#endregion
|
|
4535
|
+
//#region src/commands/fix-force.ts
|
|
4536
|
+
const getJsAuditFixCommand = (rootDirectory) => {
|
|
4537
|
+
if (fs.existsSync(path.join(rootDirectory, "pnpm-lock.yaml"))) return {
|
|
4538
|
+
command: "pnpm",
|
|
4539
|
+
args: ["audit", "--fix"]
|
|
4540
|
+
};
|
|
4541
|
+
if (fs.existsSync(path.join(rootDirectory, "package-lock.json")) || fs.existsSync(path.join(rootDirectory, "package.json"))) return {
|
|
4542
|
+
command: "npm",
|
|
4543
|
+
args: ["audit", "fix"]
|
|
4544
|
+
};
|
|
4545
|
+
return null;
|
|
4546
|
+
};
|
|
4547
|
+
const fixDependencyAudit = async (context) => {
|
|
4548
|
+
const auditFix = getJsAuditFixCommand(context.rootDirectory);
|
|
4549
|
+
if (!auditFix) return;
|
|
4550
|
+
const result = await runSubprocess(auditFix.command, auditFix.args, {
|
|
4551
|
+
cwd: context.rootDirectory,
|
|
4552
|
+
timeout: 18e4
|
|
4553
|
+
});
|
|
4554
|
+
if (result.exitCode !== 0) throw new Error(result.stderr || result.stdout || `${auditFix.command} audit fix failed`);
|
|
4555
|
+
};
|
|
4556
|
+
const fixExpoDependencies = async (context) => {
|
|
4557
|
+
if ((await runSubprocess("npx", [
|
|
4558
|
+
"--yes",
|
|
4559
|
+
"expo",
|
|
4560
|
+
"install",
|
|
4561
|
+
"--fix"
|
|
4562
|
+
], {
|
|
4563
|
+
cwd: context.rootDirectory,
|
|
4564
|
+
timeout: 18e4
|
|
4565
|
+
})).exitCode === 0) return;
|
|
4566
|
+
const checkResult = await runSubprocess("npx", [
|
|
4567
|
+
"--yes",
|
|
4568
|
+
"expo",
|
|
4569
|
+
"install",
|
|
4570
|
+
"--check"
|
|
4571
|
+
], {
|
|
4572
|
+
cwd: context.rootDirectory,
|
|
4573
|
+
timeout: 18e4
|
|
4574
|
+
});
|
|
4575
|
+
if (checkResult.exitCode !== 0) throw new Error(checkResult.stderr || checkResult.stdout || "expo dependency check failed");
|
|
4576
|
+
};
|
|
4577
|
+
|
|
4578
|
+
//#endregion
|
|
4579
|
+
//#region src/commands/fix-step.ts
|
|
4074
4580
|
const uniqueFiles = (diagnostics) => [...new Set(diagnostics.map((d) => d.filePath))];
|
|
4075
4581
|
const uniqueFileCount = (diagnostics) => uniqueFiles(diagnostics).length;
|
|
4076
4582
|
const getFilePreviewLines = (title, files, verbose) => {
|
|
@@ -4094,7 +4600,8 @@ const getStepStatusLine = (result, name, elapsedLabel) => {
|
|
|
4094
4600
|
if (result.resolvedIssues > 0) return highlighter.warn(` ! ${name}: done (${result.resolvedIssues} resolved, ${result.afterIssues} remaining, ${elapsedLabel})`);
|
|
4095
4601
|
return highlighter.warn(` ! ${name}: done (no auto-fix changes, ${result.afterIssues} issue${result.afterIssues === 1 ? "" : "s"}, ${elapsedLabel})`);
|
|
4096
4602
|
};
|
|
4097
|
-
const runFixStep = async (name, detect, applyFix, options) => {
|
|
4603
|
+
const runFixStep = async (name, detect, applyFix, options, progress) => {
|
|
4604
|
+
progress.markStarted(name);
|
|
4098
4605
|
const stepStart = performance.now();
|
|
4099
4606
|
const before = await detect();
|
|
4100
4607
|
let applyError = null;
|
|
@@ -4114,28 +4621,21 @@ const runFixStep = async (name, detect, applyFix, options) => {
|
|
|
4114
4621
|
failed: applyError !== null && before.length === after.length,
|
|
4115
4622
|
elapsedMs
|
|
4116
4623
|
};
|
|
4117
|
-
|
|
4118
|
-
if (
|
|
4119
|
-
const
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4624
|
+
progress.markComplete(name, result);
|
|
4625
|
+
if (!progress.isLive()) {
|
|
4626
|
+
const lines = [getStepStatusLine(result, name, formatElapsed$2(result.elapsedMs))];
|
|
4627
|
+
if (applyError) {
|
|
4628
|
+
const reasonLines = getReasonLines(applyError instanceof Error ? applyError.message : String(applyError));
|
|
4629
|
+
const reasonToPrint = options.verbose ? reasonLines.printable : reasonLines.firstLine;
|
|
4630
|
+
for (const line of reasonToPrint.split("\n")) lines.push(highlighter.dim(` ${line}`));
|
|
4631
|
+
if (!options.verbose && reasonLines.printable !== reasonToPrint) lines.push(highlighter.dim(" Re-run with -d for full tool output."));
|
|
4632
|
+
}
|
|
4633
|
+
lines.push(...getFilePreviewLines("Affected", uniqueFiles(before), options.verbose));
|
|
4634
|
+
if (after.length > 0) lines.push(...getFilePreviewLines("Remaining", uniqueFiles(after), options.verbose));
|
|
4635
|
+
process.stdout.write(`${lines.join("\n")}\n\n`);
|
|
4636
|
+
}
|
|
4127
4637
|
return result;
|
|
4128
4638
|
};
|
|
4129
|
-
const createEngineContext = (rootDirectory, projectInfo, config) => ({
|
|
4130
|
-
rootDirectory,
|
|
4131
|
-
languages: projectInfo.languages,
|
|
4132
|
-
frameworks: projectInfo.frameworks,
|
|
4133
|
-
installedTools: projectInfo.installedTools,
|
|
4134
|
-
config: {
|
|
4135
|
-
quality: config.quality,
|
|
4136
|
-
security: config.security
|
|
4137
|
-
}
|
|
4138
|
-
});
|
|
4139
4639
|
const summarizeFixRun = (steps) => {
|
|
4140
4640
|
const totals = steps.reduce((acc, step) => {
|
|
4141
4641
|
acc.beforeIssues += step.beforeIssues;
|
|
@@ -4155,6 +4655,19 @@ const summarizeFixRun = (steps) => {
|
|
|
4155
4655
|
} else logger.log(` Fix summary: checked ${steps.length} step(s), resolved ${totals.resolvedIssues} issue(s), remaining ${totals.afterIssues}.`);
|
|
4156
4656
|
if (totals.failedSteps === 0 && totals.beforeIssues > 0 && totals.resolvedIssues === 0) logger.dim(" No auto-fixable changes were applied. Current findings are likely manual-fix categories.");
|
|
4157
4657
|
};
|
|
4658
|
+
|
|
4659
|
+
//#endregion
|
|
4660
|
+
//#region src/commands/fix.ts
|
|
4661
|
+
const createEngineContext = (rootDirectory, projectInfo, config) => ({
|
|
4662
|
+
rootDirectory,
|
|
4663
|
+
languages: projectInfo.languages,
|
|
4664
|
+
frameworks: projectInfo.frameworks,
|
|
4665
|
+
installedTools: projectInfo.installedTools,
|
|
4666
|
+
config: {
|
|
4667
|
+
quality: config.quality,
|
|
4668
|
+
security: config.security
|
|
4669
|
+
}
|
|
4670
|
+
});
|
|
4158
4671
|
const fixCommand = async (directory, config, options = {
|
|
4159
4672
|
verbose: false,
|
|
4160
4673
|
showHeader: true
|
|
@@ -4166,38 +4679,98 @@ const fixCommand = async (directory, config, options = {
|
|
|
4166
4679
|
printProjectMetadata(projectInfo);
|
|
4167
4680
|
const context = createEngineContext(resolvedDir, projectInfo, config);
|
|
4168
4681
|
const steps = [];
|
|
4169
|
-
|
|
4682
|
+
const stepNames = [];
|
|
4683
|
+
if (config.engines["ai-slop"]) {
|
|
4684
|
+
stepNames.push("Unused imports");
|
|
4685
|
+
stepNames.push("Dead code & comments");
|
|
4686
|
+
}
|
|
4687
|
+
if (config.engines.lint) {
|
|
4688
|
+
if (projectInfo.languages.includes("typescript") || projectInfo.languages.includes("javascript")) stepNames.push("JS/TS lint fixes");
|
|
4689
|
+
if (projectInfo.languages.includes("python") && projectInfo.installedTools.ruff) stepNames.push("Python lint fixes");
|
|
4690
|
+
}
|
|
4691
|
+
if (config.engines["code-quality"]) {
|
|
4692
|
+
if (projectInfo.languages.includes("typescript") || projectInfo.languages.includes("javascript")) stepNames.push("Unused dependencies");
|
|
4693
|
+
}
|
|
4694
|
+
if (config.engines.format) {
|
|
4695
|
+
if (projectInfo.languages.includes("typescript") || projectInfo.languages.includes("javascript")) stepNames.push("JS/TS formatting");
|
|
4696
|
+
if (projectInfo.languages.includes("python") && projectInfo.installedTools.ruff) stepNames.push("Python formatting");
|
|
4697
|
+
if (projectInfo.languages.includes("go") && projectInfo.installedTools.gofmt) stepNames.push("Go formatting");
|
|
4698
|
+
}
|
|
4699
|
+
if (options.force) {
|
|
4700
|
+
if (config.engines.security) stepNames.push("Dependency audit fixes");
|
|
4701
|
+
if (projectInfo.frameworks.includes("expo")) stepNames.push("Expo dependency alignment");
|
|
4702
|
+
}
|
|
4703
|
+
const progress = new FixProgressRenderer(stepNames);
|
|
4704
|
+
progress.start();
|
|
4705
|
+
if (config.engines["ai-slop"]) {
|
|
4706
|
+
steps.push(await runFixStep("Unused imports", () => detectUnusedImports(context), () => fixUnusedImports(context), options, progress));
|
|
4707
|
+
const detectFixableSlop = async () => {
|
|
4708
|
+
const [comments, dead] = await Promise.all([detectTrivialComments(context), detectDeadPatterns(context)]);
|
|
4709
|
+
return [...comments, ...dead].filter((d) => d.fixable);
|
|
4710
|
+
};
|
|
4711
|
+
steps.push(await runFixStep("Dead code & comments", detectFixableSlop, () => fixDeadPatterns(context), options, progress));
|
|
4712
|
+
}
|
|
4170
4713
|
if (config.engines.lint) {
|
|
4171
|
-
if (projectInfo.languages.includes("typescript") || projectInfo.languages.includes("javascript")) steps.push(await runFixStep("JS/TS lint fixes", () => runOxlint(context), () => fixOxlint(context), options));
|
|
4172
|
-
if (projectInfo.languages.includes("python") && projectInfo.installedTools.ruff) steps.push(await runFixStep("Python lint fixes", () => runRuffLint(context), () => fixRuffLint(resolvedDir), options));
|
|
4714
|
+
if (projectInfo.languages.includes("typescript") || projectInfo.languages.includes("javascript")) steps.push(await runFixStep("JS/TS lint fixes", () => runOxlint(context), () => options.force ? fixOxlintForce(context) : fixOxlint(context), options, progress));
|
|
4715
|
+
if (projectInfo.languages.includes("python") && projectInfo.installedTools.ruff) steps.push(await runFixStep("Python lint fixes", () => runRuffLint(context), () => options.force ? fixRuffLintForce(resolvedDir) : fixRuffLint(resolvedDir), options, progress));
|
|
4173
4716
|
else if (projectInfo.languages.includes("python")) logger.warn(" Python detected but ruff is not installed; skipping Python lint fixes.");
|
|
4174
4717
|
}
|
|
4175
4718
|
if (config.engines["code-quality"]) {
|
|
4176
|
-
if (projectInfo.languages.includes("typescript") || projectInfo.languages.includes("javascript")) steps.push(await runFixStep("Unused dependencies", () => runKnipDependencyCheck(resolvedDir), () => fixUnusedDependencies(resolvedDir), options));
|
|
4719
|
+
if (projectInfo.languages.includes("typescript") || projectInfo.languages.includes("javascript")) steps.push(await runFixStep("Unused dependencies", () => runKnipDependencyCheck(resolvedDir), () => fixUnusedDependencies(resolvedDir), options, progress));
|
|
4177
4720
|
}
|
|
4178
4721
|
if (config.engines.format) {
|
|
4179
|
-
if (projectInfo.languages.includes("typescript") || projectInfo.languages.includes("javascript")) steps.push(await runFixStep("JS/TS formatting", () => runBiomeFormat(context), () => fixBiomeFormat(context), options));
|
|
4180
|
-
if (projectInfo.languages.includes("python") && projectInfo.installedTools.ruff) steps.push(await runFixStep("Python formatting", () => runRuffFormat(context), () => fixRuffFormat(resolvedDir), options));
|
|
4722
|
+
if (projectInfo.languages.includes("typescript") || projectInfo.languages.includes("javascript")) steps.push(await runFixStep("JS/TS formatting", () => runBiomeFormat(context), () => fixBiomeFormat(context), options, progress));
|
|
4723
|
+
if (projectInfo.languages.includes("python") && projectInfo.installedTools.ruff) steps.push(await runFixStep("Python formatting", () => runRuffFormat(context), () => fixRuffFormat(resolvedDir), options, progress));
|
|
4181
4724
|
else if (projectInfo.languages.includes("python")) logger.warn(" Python detected but ruff is not installed; skipping Python formatting fixes.");
|
|
4182
|
-
if (projectInfo.languages.includes("go") && projectInfo.installedTools.gofmt) steps.push(await runFixStep("Go formatting", () => runGofmt(context), () => fixGofmt(resolvedDir), options));
|
|
4725
|
+
if (projectInfo.languages.includes("go") && projectInfo.installedTools.gofmt) steps.push(await runFixStep("Go formatting", () => runGofmt(context), () => fixGofmt(resolvedDir), options, progress));
|
|
4183
4726
|
else if (projectInfo.languages.includes("go")) logger.warn(" Go detected but gofmt is not installed; skipping Go formatting fixes.");
|
|
4184
4727
|
}
|
|
4728
|
+
if (options.force) {
|
|
4729
|
+
if (config.engines.security) steps.push(await runFixStep("Dependency audit fixes", () => runDependencyAudit(context), () => fixDependencyAudit(context), options, progress));
|
|
4730
|
+
if (projectInfo.frameworks.includes("expo")) steps.push(await runFixStep("Expo dependency alignment", () => runExpoDoctor(context), () => fixExpoDependencies(context), options, progress));
|
|
4731
|
+
}
|
|
4732
|
+
progress.stop();
|
|
4733
|
+
const totalResolved = steps.reduce((sum, s) => sum + s.resolvedIssues, 0);
|
|
4185
4734
|
if (steps.length === 0) logger.dim(" No applicable auto-fixers found for this project.");
|
|
4186
4735
|
else {
|
|
4187
4736
|
logger.break();
|
|
4188
4737
|
summarizeFixRun(steps);
|
|
4189
4738
|
}
|
|
4190
|
-
if (!isTelemetryDisabled(config.telemetry?.enabled)) {
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
fixResolved: totalResolved
|
|
4197
|
-
});
|
|
4198
|
-
}
|
|
4739
|
+
if (!isTelemetryDisabled(config.telemetry?.enabled)) trackEvent({
|
|
4740
|
+
command: "fix",
|
|
4741
|
+
languages: projectInfo.languages,
|
|
4742
|
+
fixSteps: steps.length,
|
|
4743
|
+
fixResolved: totalResolved
|
|
4744
|
+
});
|
|
4199
4745
|
logger.break();
|
|
4200
|
-
|
|
4746
|
+
const configDir = findConfigDir(resolvedDir);
|
|
4747
|
+
const rulesPath = configDir ? path.join(configDir, RULES_FILE) : void 0;
|
|
4748
|
+
const engineConfig = {
|
|
4749
|
+
quality: config.quality,
|
|
4750
|
+
security: config.security,
|
|
4751
|
+
architectureRulesPath: config.engines.architecture ? rulesPath : void 0
|
|
4752
|
+
};
|
|
4753
|
+
const allDiagnostics = (await runEngines({
|
|
4754
|
+
rootDirectory: resolvedDir,
|
|
4755
|
+
languages: projectInfo.languages,
|
|
4756
|
+
frameworks: projectInfo.frameworks,
|
|
4757
|
+
installedTools: projectInfo.installedTools,
|
|
4758
|
+
config: engineConfig
|
|
4759
|
+
}, config.engines, () => {}, () => {})).flatMap((r) => r.diagnostics);
|
|
4760
|
+
const scoreResult = calculateScore(allDiagnostics, config.scoring.weights, config.scoring.thresholds, projectInfo.sourceFileCount, config.scoring.smoothing);
|
|
4761
|
+
const errors = allDiagnostics.filter((d) => d.severity === "error").length;
|
|
4762
|
+
const warnings = allDiagnostics.filter((d) => d.severity === "warning").length;
|
|
4763
|
+
const fixable = allDiagnostics.filter((d) => d.fixable).length;
|
|
4764
|
+
const manual = errors + warnings - fixable;
|
|
4765
|
+
const scoreColor = scoreResult.score >= config.scoring.thresholds.good ? highlighter.success : scoreResult.score >= config.scoring.thresholds.ok ? highlighter.warn : highlighter.error;
|
|
4766
|
+
logger.log(highlighter.dim("------------------------------------------------------------"));
|
|
4767
|
+
logger.log(highlighter.bold("Result"));
|
|
4768
|
+
logger.log(` Score: ${scoreColor(`${scoreResult.score}/100`)} ${scoreColor(`(${scoreResult.label})`)}`);
|
|
4769
|
+
logger.log(` Resolved: ${highlighter.success(String(totalResolved))} issue${totalResolved === 1 ? "" : "s"}`);
|
|
4770
|
+
logger.log(` Remaining: ${errors + warnings > 0 ? highlighter.warn(String(errors + warnings)) : highlighter.success("0")} (${errors} error${errors === 1 ? "" : "s"}, ${warnings} warning${warnings === 1 ? "" : "s"})`);
|
|
4771
|
+
if (fixable > 0) logger.log(` Auto-fixable: ${highlighter.info(String(fixable))}`);
|
|
4772
|
+
if (manual > 0) logger.log(` Manual effort: ${highlighter.dim(String(manual))}`);
|
|
4773
|
+
logger.log(highlighter.dim("------------------------------------------------------------"));
|
|
4201
4774
|
logger.break();
|
|
4202
4775
|
};
|
|
4203
4776
|
|
|
@@ -4558,7 +5131,7 @@ const program = new Command().name("aislop").description("The unified code quali
|
|
|
4558
5131
|
}).addHelpText("after", `
|
|
4559
5132
|
${highlighter.dim("Commands:")}
|
|
4560
5133
|
aislop scan [dir] Full code quality scan
|
|
4561
|
-
aislop fix [dir] Auto-fix
|
|
5134
|
+
aislop fix [dir] Auto-fix ai slop in codebase
|
|
4562
5135
|
aislop init [dir] Initialize aislop config
|
|
4563
5136
|
aislop doctor [dir] Check installed tools
|
|
4564
5137
|
aislop ci [dir] CI-friendly JSON output
|
|
@@ -4570,7 +5143,8 @@ ${highlighter.dim("Examples:")}
|
|
|
4570
5143
|
aislop scan -d Scan with file/line details
|
|
4571
5144
|
aislop scan --changes Scan only changed files
|
|
4572
5145
|
aislop scan --staged Scan only staged files (for hooks)
|
|
4573
|
-
aislop fix Auto-fix
|
|
5146
|
+
aislop fix Auto-fix ai slop in codebase
|
|
5147
|
+
aislop fix --force Run aggressive fixes (includes audit and dependency alignment)
|
|
4574
5148
|
aislop ci JSON output for CI pipelines
|
|
4575
5149
|
`);
|
|
4576
5150
|
program.command("scan [directory]").description("Run full code quality scan").option("--changes", "only scan changed files").option("--staged", "only scan staged files").option("-d, --verbose", "show file details per rule").option("--json", "output JSON").action(async (directory = ".", _flags, command) => {
|
|
@@ -4586,9 +5160,12 @@ program.command("scan [directory]").description("Run full code quality scan").op
|
|
|
4586
5160
|
process.exit(exitCode);
|
|
4587
5161
|
}
|
|
4588
5162
|
});
|
|
4589
|
-
program.command("fix [directory]").description("Auto-fix
|
|
5163
|
+
program.command("fix [directory]").description("Auto-fix ai slop in codebase").option("-d, --verbose", "show detailed fix progress").option("-f, --force", "run aggressive fixes (audit and framework dependency alignment)").action(async (directory = ".", _flags, command) => {
|
|
4590
5164
|
const flags = command.optsWithGlobals();
|
|
4591
|
-
await fixCommand(directory, loadConfig(directory), {
|
|
5165
|
+
await fixCommand(directory, loadConfig(directory), {
|
|
5166
|
+
verbose: Boolean(flags.verbose),
|
|
5167
|
+
force: Boolean(flags.force)
|
|
5168
|
+
});
|
|
4592
5169
|
});
|
|
4593
5170
|
program.command("init [directory]").description("Initialize aislop config in project").action(async (directory = ".") => {
|
|
4594
5171
|
await initCommand(directory);
|
|
@@ -4613,4 +5190,4 @@ const main = async () => {
|
|
|
4613
5190
|
main();
|
|
4614
5191
|
|
|
4615
5192
|
//#endregion
|
|
4616
|
-
export { ENGINE_INFO as n,
|
|
5193
|
+
export { ENGINE_INFO as n, APP_VERSION as t };
|