uilint 0.2.22 → 0.2.26

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.
@@ -0,0 +1,1226 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ detectPackageManager,
4
+ installDependencies,
5
+ runTestsWithCoverage
6
+ } from "./chunk-P4I4RKBY.js";
7
+
8
+ // src/utils/prompts.ts
9
+ import * as p from "@clack/prompts";
10
+ import pc from "picocolors";
11
+ import { readFileSync } from "fs";
12
+ import { dirname, join } from "path";
13
+ import { fileURLToPath } from "url";
14
+ function getCLIVersion() {
15
+ try {
16
+ const __dirname = dirname(fileURLToPath(import.meta.url));
17
+ const pkgPath = join(__dirname, "..", "..", "package.json");
18
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
19
+ return pkg.version || "0.0.0";
20
+ } catch {
21
+ return "0.0.0";
22
+ }
23
+ }
24
+ function intro2(title) {
25
+ const version = getCLIVersion();
26
+ const header = pc.bold(pc.cyan("\u25C6 UILint")) + pc.dim(` v${version}`);
27
+ console.log();
28
+ p.intro(title ? `${header} ${pc.dim("\xB7")} ${title}` : header);
29
+ }
30
+ function outro2(message) {
31
+ p.outro(pc.green(message));
32
+ }
33
+ function cancel2(message = "Operation cancelled.") {
34
+ p.cancel(pc.yellow(message));
35
+ process.exit(0);
36
+ }
37
+ function handleCancel(value) {
38
+ if (p.isCancel(value)) {
39
+ cancel2();
40
+ process.exit(0);
41
+ }
42
+ return value;
43
+ }
44
+ async function withSpinner(message, fn) {
45
+ const s = p.spinner();
46
+ s.start(message);
47
+ try {
48
+ const result = fn.length >= 1 ? await fn(s) : await fn();
49
+ s.stop(pc.green("\u2713 ") + message);
50
+ return result;
51
+ } catch (error) {
52
+ s.stop(pc.red("\u2717 ") + message);
53
+ throw error;
54
+ }
55
+ }
56
+ function createSpinner() {
57
+ return p.spinner();
58
+ }
59
+ function note2(message, title) {
60
+ p.note(message, title);
61
+ }
62
+ function log2(message) {
63
+ p.log.message(message);
64
+ }
65
+ function logInfo(message) {
66
+ p.log.info(message);
67
+ }
68
+ function logSuccess(message) {
69
+ p.log.success(message);
70
+ }
71
+ function logWarning(message) {
72
+ p.log.warn(message);
73
+ }
74
+ function logError(message) {
75
+ p.log.error(message);
76
+ }
77
+ async function select2(options) {
78
+ const result = await p.select({
79
+ message: options.message,
80
+ options: options.options,
81
+ initialValue: options.initialValue
82
+ });
83
+ return handleCancel(result);
84
+ }
85
+ async function confirm2(options) {
86
+ const result = await p.confirm({
87
+ message: options.message,
88
+ initialValue: options.initialValue ?? true
89
+ });
90
+ return handleCancel(result);
91
+ }
92
+ async function multiselect2(options) {
93
+ const result = await p.multiselect({
94
+ message: options.message,
95
+ options: options.options,
96
+ required: options.required,
97
+ initialValues: options.initialValues
98
+ });
99
+ return handleCancel(result);
100
+ }
101
+
102
+ // src/utils/coverage-detect.ts
103
+ import { existsSync, readFileSync as readFileSync2, statSync } from "fs";
104
+ import { join as join2 } from "path";
105
+ var VITEST_CONFIG_FILES = [
106
+ "vitest.config.ts",
107
+ "vitest.config.js",
108
+ "vitest.config.mts",
109
+ "vitest.config.mjs"
110
+ ];
111
+ function checkPackageDeps(projectPath) {
112
+ try {
113
+ const pkgPath = join2(projectPath, "package.json");
114
+ if (!existsSync(pkgPath)) return { hasVitest: false, hasCoveragePackage: false };
115
+ const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
116
+ const deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
117
+ const hasVitest = "vitest" in deps;
118
+ const hasCoveragePackage = "@vitest/coverage-v8" in deps || "@vitest/coverage-istanbul" in deps;
119
+ return { hasVitest, hasCoveragePackage };
120
+ } catch {
121
+ return { hasVitest: false, hasCoveragePackage: false };
122
+ }
123
+ }
124
+ function findVitestConfig(projectPath) {
125
+ for (const configFile of VITEST_CONFIG_FILES) {
126
+ const configPath = join2(projectPath, configFile);
127
+ if (existsSync(configPath)) {
128
+ return configPath;
129
+ }
130
+ }
131
+ return null;
132
+ }
133
+ function parseCoverageConfig(configPath) {
134
+ try {
135
+ const content = readFileSync2(configPath, "utf-8");
136
+ const hasCoverageConfig = /coverage\s*:\s*\{/.test(content);
137
+ if (!hasCoverageConfig) {
138
+ return { hasCoverageConfig: false, coverageProvider: null };
139
+ }
140
+ const providerMatch = content.match(/provider\s*:\s*["']?(v8|istanbul)["']?/);
141
+ const coverageProvider = providerMatch ? providerMatch[1] : null;
142
+ return { hasCoverageConfig, coverageProvider };
143
+ } catch {
144
+ return { hasCoverageConfig: false, coverageProvider: null };
145
+ }
146
+ }
147
+ function findCoverageData(projectPath) {
148
+ const coverageDataPath = join2(projectPath, "coverage", "coverage-final.json");
149
+ if (existsSync(coverageDataPath)) {
150
+ try {
151
+ const stats = statSync(coverageDataPath);
152
+ const age = Date.now() - stats.mtimeMs;
153
+ return { path: coverageDataPath, age };
154
+ } catch {
155
+ return { path: coverageDataPath, age: null };
156
+ }
157
+ }
158
+ return { path: null, age: null };
159
+ }
160
+ function detectCoverageSetup(projectPath) {
161
+ const { hasVitest, hasCoveragePackage } = checkPackageDeps(projectPath);
162
+ const vitestConfigPath = findVitestConfig(projectPath);
163
+ const hasVitestConfig = vitestConfigPath !== null;
164
+ let hasCoverageConfig = false;
165
+ let coverageProvider = null;
166
+ if (vitestConfigPath) {
167
+ const coverageInfo = parseCoverageConfig(vitestConfigPath);
168
+ hasCoverageConfig = coverageInfo.hasCoverageConfig;
169
+ coverageProvider = coverageInfo.coverageProvider;
170
+ }
171
+ const coverageData = findCoverageData(projectPath);
172
+ const hasCoverageData = coverageData.path !== null;
173
+ const needsCoveragePackage = hasVitest && !hasCoveragePackage;
174
+ const needsCoverageConfig = hasVitest && hasVitestConfig && !hasCoverageConfig;
175
+ return {
176
+ hasVitest,
177
+ hasVitestConfig,
178
+ vitestConfigPath,
179
+ hasCoverageConfig,
180
+ coverageProvider,
181
+ hasCoverageData,
182
+ coverageDataPath: coverageData.path,
183
+ needsCoveragePackage,
184
+ needsCoverageConfig,
185
+ coverageDataAge: coverageData.age
186
+ };
187
+ }
188
+
189
+ // src/utils/next-detect.ts
190
+ import { existsSync as existsSync2, readdirSync } from "fs";
191
+ import { join as join3 } from "path";
192
+ function fileExists(projectPath, relPath) {
193
+ return existsSync2(join3(projectPath, relPath));
194
+ }
195
+ function detectNextAppRouter(projectPath) {
196
+ const roots = ["app", join3("src", "app")];
197
+ const candidates = [];
198
+ let chosenRoot = null;
199
+ for (const root of roots) {
200
+ if (existsSync2(join3(projectPath, root))) {
201
+ chosenRoot = root;
202
+ break;
203
+ }
204
+ }
205
+ if (!chosenRoot) return null;
206
+ const entryCandidates = [
207
+ join3(chosenRoot, "layout.tsx"),
208
+ join3(chosenRoot, "layout.jsx"),
209
+ join3(chosenRoot, "layout.ts"),
210
+ join3(chosenRoot, "layout.js"),
211
+ // Fallbacks (less ideal, but can work):
212
+ join3(chosenRoot, "page.tsx"),
213
+ join3(chosenRoot, "page.jsx")
214
+ ];
215
+ for (const rel of entryCandidates) {
216
+ if (fileExists(projectPath, rel)) candidates.push(rel);
217
+ }
218
+ return {
219
+ appRoot: chosenRoot,
220
+ appRootAbs: join3(projectPath, chosenRoot),
221
+ candidates
222
+ };
223
+ }
224
+ var DEFAULT_IGNORE_DIRS = /* @__PURE__ */ new Set([
225
+ "node_modules",
226
+ ".git",
227
+ ".next",
228
+ "dist",
229
+ "build",
230
+ "out",
231
+ ".turbo",
232
+ ".vercel",
233
+ ".cursor",
234
+ "coverage",
235
+ ".uilint"
236
+ ]);
237
+ function findNextAppRouterProjects(rootDir, options) {
238
+ const maxDepth = options?.maxDepth ?? 4;
239
+ const ignoreDirs = options?.ignoreDirs ?? DEFAULT_IGNORE_DIRS;
240
+ const results = [];
241
+ const visited = /* @__PURE__ */ new Set();
242
+ function walk(dir, depth) {
243
+ if (depth > maxDepth) return;
244
+ if (visited.has(dir)) return;
245
+ visited.add(dir);
246
+ const detection = detectNextAppRouter(dir);
247
+ if (detection) {
248
+ results.push({ projectPath: dir, detection });
249
+ return;
250
+ }
251
+ let entries = [];
252
+ try {
253
+ entries = readdirSync(dir, { withFileTypes: true }).map((d) => ({
254
+ name: d.name,
255
+ isDirectory: d.isDirectory()
256
+ }));
257
+ } catch {
258
+ return;
259
+ }
260
+ for (const ent of entries) {
261
+ if (!ent.isDirectory) continue;
262
+ if (ignoreDirs.has(ent.name)) continue;
263
+ if (ent.name.startsWith(".") && ent.name !== ".") continue;
264
+ walk(join3(dir, ent.name), depth + 1);
265
+ }
266
+ }
267
+ walk(rootDir, 0);
268
+ return results;
269
+ }
270
+
271
+ // src/utils/eslint-config-inject.ts
272
+ import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync } from "fs";
273
+ import { join as join4, relative, dirname as dirname2 } from "path";
274
+ import { parseExpression, parseModule, generateCode } from "magicast";
275
+ import { findWorkspaceRoot } from "uilint-core/node";
276
+ var CONFIG_EXTENSIONS = [".ts", ".mjs", ".js", ".cjs"];
277
+ function findEslintConfigFile(projectPath) {
278
+ for (const ext of CONFIG_EXTENSIONS) {
279
+ const configPath = join4(projectPath, `eslint.config${ext}`);
280
+ if (existsSync3(configPath)) {
281
+ return configPath;
282
+ }
283
+ }
284
+ return null;
285
+ }
286
+ function getEslintConfigFilename(configPath) {
287
+ const parts = configPath.split("/");
288
+ return parts[parts.length - 1] || "eslint.config.mjs";
289
+ }
290
+ function isIdentifier(node, name) {
291
+ return !!node && node.type === "Identifier" && (name ? node.name === name : typeof node.name === "string");
292
+ }
293
+ function isStringLiteral(node) {
294
+ return !!node && (node.type === "StringLiteral" || node.type === "Literal") && typeof node.value === "string";
295
+ }
296
+ function getObjectPropertyValue(obj, keyName) {
297
+ if (!obj || obj.type !== "ObjectExpression") return null;
298
+ for (const prop of obj.properties ?? []) {
299
+ if (!prop) continue;
300
+ if (prop.type === "ObjectProperty" || prop.type === "Property") {
301
+ const key = prop.key;
302
+ const keyMatch = key?.type === "Identifier" && key.name === keyName || isStringLiteral(key) && key.value === keyName;
303
+ if (keyMatch) return prop.value;
304
+ }
305
+ }
306
+ return null;
307
+ }
308
+ function hasSpreadProperties(obj) {
309
+ if (!obj || obj.type !== "ObjectExpression") return false;
310
+ return (obj.properties ?? []).some(
311
+ (p2) => p2 && (p2.type === "SpreadElement" || p2.type === "SpreadProperty")
312
+ );
313
+ }
314
+ var IGNORED_AST_KEYS = /* @__PURE__ */ new Set([
315
+ "loc",
316
+ "start",
317
+ "end",
318
+ "extra",
319
+ "leadingComments",
320
+ "trailingComments",
321
+ "innerComments"
322
+ ]);
323
+ function normalizeAstForCompare(node) {
324
+ if (node === null) return null;
325
+ if (node === void 0) return void 0;
326
+ if (typeof node !== "object") return node;
327
+ if (Array.isArray(node)) return node.map(normalizeAstForCompare);
328
+ const out = {};
329
+ const keys = Object.keys(node).filter((k) => !IGNORED_AST_KEYS.has(k)).sort();
330
+ for (const k of keys) {
331
+ if (k.startsWith("$")) continue;
332
+ out[k] = normalizeAstForCompare(node[k]);
333
+ }
334
+ return out;
335
+ }
336
+ function astEquivalent(a, b) {
337
+ try {
338
+ return JSON.stringify(normalizeAstForCompare(a)) === JSON.stringify(normalizeAstForCompare(b));
339
+ } catch {
340
+ return false;
341
+ }
342
+ }
343
+ function collectUilintRuleIdsFromRulesObject(rulesObj) {
344
+ const ids = /* @__PURE__ */ new Set();
345
+ if (!rulesObj || rulesObj.type !== "ObjectExpression") return ids;
346
+ for (const prop of rulesObj.properties ?? []) {
347
+ if (!prop) continue;
348
+ if (prop.type !== "ObjectProperty" && prop.type !== "Property") continue;
349
+ const key = prop.key;
350
+ if (!isStringLiteral(key)) continue;
351
+ const val = key.value;
352
+ if (typeof val !== "string") continue;
353
+ if (val.startsWith("uilint/")) {
354
+ ids.add(val.slice("uilint/".length));
355
+ }
356
+ }
357
+ return ids;
358
+ }
359
+ function findExportedConfigArrayExpression(mod) {
360
+ function unwrapExpression(expr) {
361
+ let e = expr;
362
+ while (e) {
363
+ if (e.type === "TSAsExpression" || e.type === "TSNonNullExpression") {
364
+ e = e.expression;
365
+ continue;
366
+ }
367
+ if (e.type === "TSSatisfiesExpression") {
368
+ e = e.expression;
369
+ continue;
370
+ }
371
+ if (e.type === "ParenthesizedExpression") {
372
+ e = e.expression;
373
+ continue;
374
+ }
375
+ break;
376
+ }
377
+ return e;
378
+ }
379
+ function resolveTopLevelIdentifierToArrayExpr(program2, name) {
380
+ if (!program2 || program2.type !== "Program") return null;
381
+ for (const stmt of program2.body ?? []) {
382
+ if (stmt?.type !== "VariableDeclaration") continue;
383
+ for (const decl of stmt.declarations ?? []) {
384
+ const id = decl?.id;
385
+ if (!isIdentifier(id, name)) continue;
386
+ const init = unwrapExpression(decl?.init);
387
+ if (!init) return null;
388
+ if (init.type === "ArrayExpression") return init;
389
+ if (init.type === "CallExpression" && isIdentifier(init.callee, "defineConfig") && unwrapExpression(init.arguments?.[0])?.type === "ArrayExpression") {
390
+ return unwrapExpression(init.arguments?.[0]);
391
+ }
392
+ return null;
393
+ }
394
+ }
395
+ return null;
396
+ }
397
+ const program = mod?.$ast;
398
+ if (program && program.type === "Program") {
399
+ for (const stmt of program.body ?? []) {
400
+ if (!stmt || stmt.type !== "ExportDefaultDeclaration") continue;
401
+ const decl = unwrapExpression(stmt.declaration);
402
+ if (!decl) break;
403
+ if (decl.type === "ArrayExpression") {
404
+ return { kind: "esm", arrayExpr: decl, program };
405
+ }
406
+ if (decl.type === "CallExpression" && isIdentifier(decl.callee, "defineConfig") && unwrapExpression(decl.arguments?.[0])?.type === "ArrayExpression") {
407
+ return {
408
+ kind: "esm",
409
+ arrayExpr: unwrapExpression(decl.arguments?.[0]),
410
+ program
411
+ };
412
+ }
413
+ if (decl.type === "Identifier" && typeof decl.name === "string") {
414
+ const resolved = resolveTopLevelIdentifierToArrayExpr(
415
+ program,
416
+ decl.name
417
+ );
418
+ if (resolved) return { kind: "esm", arrayExpr: resolved, program };
419
+ }
420
+ break;
421
+ }
422
+ }
423
+ if (!program || program.type !== "Program") return null;
424
+ for (const stmt of program.body ?? []) {
425
+ if (!stmt || stmt.type !== "ExpressionStatement") continue;
426
+ const expr = stmt.expression;
427
+ if (!expr || expr.type !== "AssignmentExpression") continue;
428
+ const left = expr.left;
429
+ const right = expr.right;
430
+ const isModuleExports = left?.type === "MemberExpression" && isIdentifier(left.object, "module") && isIdentifier(left.property, "exports");
431
+ if (!isModuleExports) continue;
432
+ if (right?.type === "ArrayExpression") {
433
+ return { kind: "cjs", arrayExpr: right, program };
434
+ }
435
+ if (right?.type === "CallExpression" && isIdentifier(right.callee, "defineConfig") && right.arguments?.[0]?.type === "ArrayExpression") {
436
+ return { kind: "cjs", arrayExpr: right.arguments[0], program };
437
+ }
438
+ if (right?.type === "Identifier" && typeof right.name === "string") {
439
+ const resolved = resolveTopLevelIdentifierToArrayExpr(
440
+ program,
441
+ right.name
442
+ );
443
+ if (resolved) return { kind: "cjs", arrayExpr: resolved, program };
444
+ }
445
+ }
446
+ return null;
447
+ }
448
+ function collectConfiguredUilintRuleIdsFromConfigArray(arrayExpr) {
449
+ const ids = /* @__PURE__ */ new Set();
450
+ if (!arrayExpr || arrayExpr.type !== "ArrayExpression") return ids;
451
+ for (const el of arrayExpr.elements ?? []) {
452
+ if (!el || el.type !== "ObjectExpression") continue;
453
+ const rules = getObjectPropertyValue(el, "rules");
454
+ for (const id of collectUilintRuleIdsFromRulesObject(rules)) ids.add(id);
455
+ }
456
+ return ids;
457
+ }
458
+ function findExistingUilintRulesObject(arrayExpr) {
459
+ if (!arrayExpr || arrayExpr.type !== "ArrayExpression") {
460
+ return { configObj: null, rulesObj: null, safeToMutate: false };
461
+ }
462
+ for (const el of arrayExpr.elements ?? []) {
463
+ if (!el || el.type !== "ObjectExpression") continue;
464
+ const plugins = getObjectPropertyValue(el, "plugins");
465
+ const rules = getObjectPropertyValue(el, "rules");
466
+ const hasUilintPlugin = plugins?.type === "ObjectExpression" && getObjectPropertyValue(plugins, "uilint") !== null;
467
+ const uilintIds = collectUilintRuleIdsFromRulesObject(rules);
468
+ const hasUilintRules = uilintIds.size > 0;
469
+ if (!hasUilintPlugin && !hasUilintRules) continue;
470
+ const safe = rules?.type === "ObjectExpression" && !hasSpreadProperties(rules);
471
+ return { configObj: el, rulesObj: rules, safeToMutate: safe };
472
+ }
473
+ return { configObj: null, rulesObj: null, safeToMutate: false };
474
+ }
475
+ function collectTopLevelBindings(program) {
476
+ const names = /* @__PURE__ */ new Set();
477
+ if (!program || program.type !== "Program") return names;
478
+ for (const stmt of program.body ?? []) {
479
+ if (stmt?.type === "ImportDeclaration") {
480
+ for (const spec of stmt.specifiers ?? []) {
481
+ const local = spec?.local;
482
+ if (local?.type === "Identifier" && typeof local.name === "string") {
483
+ names.add(local.name);
484
+ }
485
+ }
486
+ continue;
487
+ }
488
+ if (stmt?.type === "VariableDeclaration") {
489
+ for (const decl of stmt.declarations ?? []) {
490
+ const id = decl?.id;
491
+ if (id?.type === "Identifier" && typeof id.name === "string") {
492
+ names.add(id.name);
493
+ }
494
+ }
495
+ } else if (stmt?.type === "FunctionDeclaration") {
496
+ if (stmt.id?.type === "Identifier" && typeof stmt.id.name === "string") {
497
+ names.add(stmt.id.name);
498
+ }
499
+ }
500
+ }
501
+ return names;
502
+ }
503
+ function chooseUniqueIdentifier(base, used) {
504
+ if (!used.has(base)) return base;
505
+ let i = 2;
506
+ while (used.has(`${base}${i}`)) i++;
507
+ return `${base}${i}`;
508
+ }
509
+ function findExistingDefaultImportLocalName(program, from) {
510
+ if (!program || program.type !== "Program") return null;
511
+ for (const stmt of program.body ?? []) {
512
+ if (stmt?.type !== "ImportDeclaration") continue;
513
+ const src = stmt.source?.value;
514
+ if (typeof src !== "string" || src !== from) continue;
515
+ for (const spec of stmt.specifiers ?? []) {
516
+ if (spec?.type === "ImportDefaultSpecifier") {
517
+ const local = spec.local;
518
+ if (local?.type === "Identifier" && typeof local.name === "string") {
519
+ return local.name;
520
+ }
521
+ }
522
+ }
523
+ }
524
+ return null;
525
+ }
526
+ function addLocalRuleImportsAst(mod, selectedRules, configPath, rulesRoot, fileExtension = ".js") {
527
+ const importNames = /* @__PURE__ */ new Map();
528
+ let changed = false;
529
+ const configDir = dirname2(configPath);
530
+ const rulesDir = join4(rulesRoot, ".uilint", "rules");
531
+ const relativeRulesPath = relative(configDir, rulesDir).replace(/\\/g, "/");
532
+ const normalizedRulesPath = relativeRulesPath.startsWith("./") || relativeRulesPath.startsWith("../") ? relativeRulesPath : `./${relativeRulesPath}`;
533
+ const used = collectTopLevelBindings(mod.$ast);
534
+ for (const rule of selectedRules) {
535
+ const rulePath = `${normalizedRulesPath}/${rule.id}${fileExtension}`;
536
+ const existingLocal = findExistingDefaultImportLocalName(mod.$ast, rulePath);
537
+ if (existingLocal) {
538
+ importNames.set(rule.id, existingLocal);
539
+ used.add(existingLocal);
540
+ continue;
541
+ }
542
+ const importName = chooseUniqueIdentifier(
543
+ `${rule.id.replace(/-([a-z])/g, (_, c) => c.toUpperCase()).replace(/^./, (c) => c.toUpperCase())}Rule`,
544
+ used
545
+ );
546
+ importNames.set(rule.id, importName);
547
+ used.add(importName);
548
+ mod.imports.$add({
549
+ imported: "default",
550
+ local: importName,
551
+ from: rulePath
552
+ });
553
+ changed = true;
554
+ }
555
+ return { importNames, changed };
556
+ }
557
+ function addLocalRuleRequiresAst(program, selectedRules, configPath, rulesRoot, fileExtension = ".js") {
558
+ const importNames = /* @__PURE__ */ new Map();
559
+ let changed = false;
560
+ if (!program || program.type !== "Program") {
561
+ return { importNames, changed };
562
+ }
563
+ const configDir = dirname2(configPath);
564
+ const rulesDir = join4(rulesRoot, ".uilint", "rules");
565
+ const relativeRulesPath = relative(configDir, rulesDir).replace(/\\/g, "/");
566
+ const normalizedRulesPath = relativeRulesPath.startsWith("./") || relativeRulesPath.startsWith("../") ? relativeRulesPath : `./${relativeRulesPath}`;
567
+ const used = collectTopLevelBindings(program);
568
+ for (const rule of selectedRules) {
569
+ const importName = chooseUniqueIdentifier(
570
+ `${rule.id.replace(/-([a-z])/g, (_, c) => c.toUpperCase()).replace(/^./, (c) => c.toUpperCase())}Rule`,
571
+ used
572
+ );
573
+ importNames.set(rule.id, importName);
574
+ used.add(importName);
575
+ const rulePath = `${normalizedRulesPath}/${rule.id}${fileExtension}`;
576
+ const stmtMod = parseModule(
577
+ `const ${importName} = require("${rulePath}");`
578
+ );
579
+ const stmt = stmtMod.$ast.body?.[0];
580
+ if (stmt) {
581
+ let insertAt = 0;
582
+ const first = program.body?.[0];
583
+ if (first?.type === "ExpressionStatement" && first.expression?.type === "StringLiteral" && first.expression.value === "use strict") {
584
+ insertAt = 1;
585
+ }
586
+ program.body.splice(insertAt, 0, stmt);
587
+ changed = true;
588
+ }
589
+ }
590
+ return { importNames, changed };
591
+ }
592
+ function appendUilintConfigBlockToArray(arrayExpr, selectedRules, ruleImportNames) {
593
+ const pluginRulesCode = Array.from(ruleImportNames.entries()).map(([ruleId, importName]) => ` "${ruleId}": ${importName},`).join("\n");
594
+ const rulesPropsCode = selectedRules.map((r) => {
595
+ const ruleKey = `uilint/${r.id}`;
596
+ const valueCode = r.defaultOptions && r.defaultOptions.length > 0 ? `["${r.defaultSeverity}", ...${JSON.stringify(
597
+ r.defaultOptions,
598
+ null,
599
+ 2
600
+ )}]` : `"${r.defaultSeverity}"`;
601
+ return ` "${ruleKey}": ${valueCode},`;
602
+ }).join("\n");
603
+ const blockCode = `{
604
+ files: [
605
+ "src/**/*.{js,jsx,ts,tsx}",
606
+ "app/**/*.{js,jsx,ts,tsx}",
607
+ "pages/**/*.{js,jsx,ts,tsx}",
608
+ ],
609
+ plugins: {
610
+ uilint: {
611
+ rules: {
612
+ ${pluginRulesCode}
613
+ },
614
+ },
615
+ },
616
+ rules: {
617
+ ${rulesPropsCode}
618
+ },
619
+ }`;
620
+ const objExpr = parseExpression(blockCode).$ast;
621
+ arrayExpr.elements.push(objExpr);
622
+ }
623
+ function getUilintEslintConfigInfoFromSourceAst(source) {
624
+ try {
625
+ const mod = parseModule(source);
626
+ const found = findExportedConfigArrayExpression(mod);
627
+ if (!found) {
628
+ return {
629
+ error: "Could not locate an exported ESLint flat config array (expected `export default [...]`, `export default defineConfig([...])`, `module.exports = [...]`, or `module.exports = defineConfig([...])`)."
630
+ };
631
+ }
632
+ const configuredRuleIds = collectConfiguredUilintRuleIdsFromConfigArray(
633
+ found.arrayExpr
634
+ );
635
+ const existingUilint = findExistingUilintRulesObject(found.arrayExpr);
636
+ const configured = configuredRuleIds.size > 0 || existingUilint.configObj !== null;
637
+ return {
638
+ info: { configuredRuleIds, configured },
639
+ mod,
640
+ arrayExpr: found.arrayExpr,
641
+ kind: found.kind
642
+ };
643
+ } catch {
644
+ return {
645
+ error: "Unable to parse ESLint config as JavaScript. Please update it manually or simplify the config so it can be safely auto-modified."
646
+ };
647
+ }
648
+ }
649
+ function getUilintEslintConfigInfoFromSource(source) {
650
+ const ast = getUilintEslintConfigInfoFromSourceAst(source);
651
+ if ("error" in ast) {
652
+ const configuredRuleIds = extractConfiguredUilintRuleIds(source);
653
+ return {
654
+ configuredRuleIds,
655
+ configured: configuredRuleIds.size > 0
656
+ };
657
+ }
658
+ return ast.info;
659
+ }
660
+ function extractConfiguredUilintRuleIds(source) {
661
+ const ids = /* @__PURE__ */ new Set();
662
+ const re = /["']uilint\/([^"']+)["']\s*:/g;
663
+ for (const m of source.matchAll(re)) {
664
+ if (m[1]) ids.add(m[1]);
665
+ }
666
+ return ids;
667
+ }
668
+ function getMissingSelectedRules(selectedRules, configuredIds) {
669
+ return selectedRules.filter((r) => !configuredIds.has(r.id));
670
+ }
671
+ function buildDesiredRuleValueExpression(rule) {
672
+ if (rule.defaultOptions && rule.defaultOptions.length > 0) {
673
+ return `["${rule.defaultSeverity}", ...${JSON.stringify(
674
+ rule.defaultOptions,
675
+ null,
676
+ 2
677
+ )}]`;
678
+ }
679
+ return `"${rule.defaultSeverity}"`;
680
+ }
681
+ function collectUilintRuleValueNodesFromConfigArray(arrayExpr) {
682
+ const out = /* @__PURE__ */ new Map();
683
+ if (!arrayExpr || arrayExpr.type !== "ArrayExpression") return out;
684
+ for (const el of arrayExpr.elements ?? []) {
685
+ if (!el || el.type !== "ObjectExpression") continue;
686
+ const rules = getObjectPropertyValue(el, "rules");
687
+ if (!rules || rules.type !== "ObjectExpression") continue;
688
+ for (const prop of rules.properties ?? []) {
689
+ if (!prop) continue;
690
+ if (prop.type !== "ObjectProperty" && prop.type !== "Property") continue;
691
+ const key = prop.key;
692
+ if (!isStringLiteral(key)) continue;
693
+ const k = key.value;
694
+ if (typeof k !== "string" || !k.startsWith("uilint/")) continue;
695
+ const id = k.slice("uilint/".length);
696
+ if (!out.has(id)) out.set(id, prop.value);
697
+ }
698
+ }
699
+ return out;
700
+ }
701
+ function getRulesNeedingUpdate(selectedRules, configuredIds, arrayExpr) {
702
+ const existingVals = collectUilintRuleValueNodesFromConfigArray(arrayExpr);
703
+ return selectedRules.filter((r) => {
704
+ if (!configuredIds.has(r.id)) return false;
705
+ const existing = existingVals.get(r.id);
706
+ if (!existing) return true;
707
+ const desiredExpr = buildDesiredRuleValueExpression(r);
708
+ const desiredAst = parseExpression(desiredExpr).$ast;
709
+ return !astEquivalent(existing, desiredAst);
710
+ });
711
+ }
712
+ async function installEslintPlugin(opts) {
713
+ const configPath = findEslintConfigFile(opts.projectPath);
714
+ if (!configPath) {
715
+ return {
716
+ configFile: null,
717
+ modified: false,
718
+ missingRuleIds: [],
719
+ configured: false
720
+ };
721
+ }
722
+ const configFilename = getEslintConfigFilename(configPath);
723
+ const original = readFileSync3(configPath, "utf-8");
724
+ const isCommonJS = configPath.endsWith(".cjs");
725
+ const ast = getUilintEslintConfigInfoFromSourceAst(original);
726
+ if ("error" in ast) {
727
+ return {
728
+ configFile: configFilename,
729
+ modified: false,
730
+ missingRuleIds: [],
731
+ configured: false,
732
+ error: ast.error
733
+ };
734
+ }
735
+ const { info, mod, arrayExpr, kind } = ast;
736
+ const configuredIds = info.configuredRuleIds;
737
+ const missingRules = getMissingSelectedRules(
738
+ opts.selectedRules,
739
+ configuredIds
740
+ );
741
+ const rulesToUpdate = getRulesNeedingUpdate(
742
+ opts.selectedRules,
743
+ configuredIds,
744
+ arrayExpr
745
+ );
746
+ let rulesToApply = [];
747
+ if (!info.configured) {
748
+ rulesToApply = opts.selectedRules;
749
+ } else {
750
+ rulesToApply = [...missingRules, ...rulesToUpdate];
751
+ if (missingRules.length > 0 && !opts.force) {
752
+ const ok = await opts.confirmAddMissingRules?.(
753
+ configFilename,
754
+ missingRules
755
+ );
756
+ if (!ok) {
757
+ return {
758
+ configFile: configFilename,
759
+ modified: false,
760
+ missingRuleIds: missingRules.map((r) => r.id),
761
+ configured: true
762
+ };
763
+ }
764
+ }
765
+ }
766
+ if (rulesToApply.length === 0) {
767
+ return {
768
+ configFile: configFilename,
769
+ modified: false,
770
+ missingRuleIds: missingRules.map((r) => r.id),
771
+ configured: info.configured
772
+ };
773
+ }
774
+ let modifiedAst = false;
775
+ const localRulesDir = join4(opts.projectPath, ".uilint", "rules");
776
+ const workspaceRoot = findWorkspaceRoot(opts.projectPath);
777
+ const workspaceRulesDir = join4(workspaceRoot, ".uilint", "rules");
778
+ const rulesRoot = existsSync3(localRulesDir) ? opts.projectPath : workspaceRoot;
779
+ const isTypeScriptConfig = configPath.endsWith(".ts");
780
+ let fileExtension = isTypeScriptConfig ? "" : ".js";
781
+ let ruleImportNames;
782
+ if (kind === "esm") {
783
+ const result = addLocalRuleImportsAst(
784
+ mod,
785
+ rulesToApply,
786
+ configPath,
787
+ rulesRoot,
788
+ fileExtension
789
+ );
790
+ ruleImportNames = result.importNames;
791
+ if (result.changed) modifiedAst = true;
792
+ } else {
793
+ const result = addLocalRuleRequiresAst(
794
+ mod.$ast,
795
+ rulesToApply,
796
+ configPath,
797
+ rulesRoot,
798
+ fileExtension
799
+ );
800
+ ruleImportNames = result.importNames;
801
+ if (result.changed) modifiedAst = true;
802
+ }
803
+ if (ruleImportNames && ruleImportNames.size > 0) {
804
+ appendUilintConfigBlockToArray(arrayExpr, rulesToApply, ruleImportNames);
805
+ modifiedAst = true;
806
+ }
807
+ if (!info.configured) {
808
+ if (kind === "esm") {
809
+ mod.imports.$add({
810
+ imported: "createRule",
811
+ local: "createRule",
812
+ from: "uilint-eslint"
813
+ });
814
+ modifiedAst = true;
815
+ } else {
816
+ const stmtMod = parseModule(
817
+ `const { createRule } = require("uilint-eslint");`
818
+ );
819
+ const stmt = stmtMod.$ast.body?.[0];
820
+ if (stmt) {
821
+ let insertAt = 0;
822
+ const first = mod.$ast.body?.[0];
823
+ if (first?.type === "ExpressionStatement" && first.expression?.type === "StringLiteral" && first.expression.value === "use strict") {
824
+ insertAt = 1;
825
+ }
826
+ mod.$ast.body.splice(insertAt, 0, stmt);
827
+ modifiedAst = true;
828
+ }
829
+ }
830
+ }
831
+ const updated = modifiedAst ? generateCode(mod).code : original;
832
+ if (updated !== original) {
833
+ writeFileSync(configPath, updated, "utf-8");
834
+ return {
835
+ configFile: configFilename,
836
+ modified: true,
837
+ missingRuleIds: missingRules.map((r) => r.id),
838
+ configured: getUilintEslintConfigInfoFromSource(updated).configured
839
+ };
840
+ }
841
+ return {
842
+ configFile: configFilename,
843
+ modified: false,
844
+ missingRuleIds: missingRules.map((r) => r.id),
845
+ configured: getUilintEslintConfigInfoFromSource(updated).configured
846
+ };
847
+ }
848
+ async function uninstallEslintPlugin(options) {
849
+ const { projectPath } = options;
850
+ const configPath = findEslintConfigFile(projectPath);
851
+ if (!configPath) {
852
+ return {
853
+ success: true,
854
+ // Nothing to uninstall
855
+ modifiedFiles: []
856
+ };
857
+ }
858
+ try {
859
+ const original = readFileSync3(configPath, "utf-8");
860
+ let updated = original.replace(
861
+ /^import\s+\{[^}]*\}\s+from\s+["'][^"']*\.uilint\/rules[^"']*["'];?\s*$/gm,
862
+ ""
863
+ );
864
+ updated = updated.replace(
865
+ /^import\s+\w+\s+from\s+["'][^"']*\.uilint\/rules[^"']*["'];?\s*$/gm,
866
+ ""
867
+ );
868
+ updated = updated.replace(
869
+ /^import\s+\{[^}]*\}\s+from\s+["']uilint-eslint["'];?\s*$/gm,
870
+ ""
871
+ );
872
+ updated = updated.replace(
873
+ /^const\s+\{[^}]*createRule[^}]*\}\s*=\s*require\s*\(\s*["']uilint-eslint["']\s*\)\s*;?\s*$/gm,
874
+ ""
875
+ );
876
+ updated = updated.replace(
877
+ /["']uilint\/[^"']+["']\s*:\s*["'][^"']+["']\s*,?\s*/g,
878
+ ""
879
+ );
880
+ updated = updated.replace(
881
+ /["']uilint\/[^"']+["']\s*:\s*\[[^\]]*\]\s*,?\s*/g,
882
+ ""
883
+ );
884
+ updated = updated.replace(
885
+ /\{\s*plugins:\s*\{\s*uilint:\s*\{[^}]*\}[^}]*\}[^}]*rules:\s*\{[^}]*\}[^}]*\}\s*,?\s*/gs,
886
+ ""
887
+ );
888
+ updated = updated.replace(/\n{3,}/g, "\n\n");
889
+ if (updated !== original) {
890
+ writeFileSync(configPath, updated, "utf-8");
891
+ return {
892
+ success: true,
893
+ modifiedFiles: [configPath]
894
+ };
895
+ }
896
+ return {
897
+ success: true,
898
+ modifiedFiles: []
899
+ };
900
+ } catch (error) {
901
+ return {
902
+ success: false,
903
+ error: error instanceof Error ? error.message : String(error)
904
+ };
905
+ }
906
+ }
907
+ function extractSeverityFromValueNode(valueNode) {
908
+ if (!valueNode) return null;
909
+ if (isStringLiteral(valueNode)) {
910
+ const val = valueNode.value;
911
+ if (val === "error" || val === "warn" || val === "off") {
912
+ return val;
913
+ }
914
+ return null;
915
+ }
916
+ if (valueNode.type === "ArrayExpression") {
917
+ const firstEl = valueNode.elements?.[0];
918
+ if (firstEl && isStringLiteral(firstEl)) {
919
+ const val = firstEl.value;
920
+ if (val === "error" || val === "warn" || val === "off") {
921
+ return val;
922
+ }
923
+ }
924
+ }
925
+ return null;
926
+ }
927
+ function extractOptionsFromValueNode(valueNode) {
928
+ if (!valueNode || valueNode.type !== "ArrayExpression") return void 0;
929
+ const elements = valueNode.elements ?? [];
930
+ if (elements.length < 2) return void 0;
931
+ const optionsNode = elements[1];
932
+ if (!optionsNode || optionsNode.type !== "ObjectExpression") return void 0;
933
+ try {
934
+ const obj = {};
935
+ for (const prop of optionsNode.properties ?? []) {
936
+ if (!prop || prop.type !== "ObjectProperty" && prop.type !== "Property")
937
+ continue;
938
+ const key = prop.key;
939
+ let keyName = null;
940
+ if (key?.type === "Identifier") {
941
+ keyName = key.name;
942
+ } else if (isStringLiteral(key)) {
943
+ keyName = key.value;
944
+ }
945
+ if (!keyName) continue;
946
+ const val = prop.value;
947
+ if (isStringLiteral(val)) {
948
+ obj[keyName] = val.value;
949
+ } else if (val?.type === "NumericLiteral" || val?.type === "Literal" && typeof val.value === "number") {
950
+ obj[keyName] = val.value;
951
+ } else if (val?.type === "BooleanLiteral" || val?.type === "Literal" && typeof val.value === "boolean") {
952
+ obj[keyName] = val.value;
953
+ } else if (val?.type === "ArrayExpression") {
954
+ const arr = [];
955
+ for (const el of val.elements ?? []) {
956
+ if (isStringLiteral(el)) arr.push(el.value);
957
+ else if (el?.type === "NumericLiteral" || el?.type === "Literal" && typeof el.value === "number")
958
+ arr.push(el.value);
959
+ else if (el?.type === "BooleanLiteral" || el?.type === "Literal" && typeof el.value === "boolean")
960
+ arr.push(el.value);
961
+ }
962
+ obj[keyName] = arr;
963
+ }
964
+ }
965
+ return Object.keys(obj).length > 0 ? obj : void 0;
966
+ } catch {
967
+ return void 0;
968
+ }
969
+ }
970
+ function readRuleConfigsFromConfig(configPath) {
971
+ const configs = /* @__PURE__ */ new Map();
972
+ try {
973
+ const source = readFileSync3(configPath, "utf-8");
974
+ const mod = parseModule(source);
975
+ const found = findExportedConfigArrayExpression(mod);
976
+ if (!found) {
977
+ return configs;
978
+ }
979
+ const { arrayExpr } = found;
980
+ const valueNodes = collectUilintRuleValueNodesFromConfigArray(arrayExpr);
981
+ for (const [ruleId, valueNode] of valueNodes) {
982
+ const severity = extractSeverityFromValueNode(valueNode);
983
+ if (severity) {
984
+ const options = extractOptionsFromValueNode(valueNode);
985
+ configs.set(ruleId, { severity, options });
986
+ }
987
+ }
988
+ } catch (error) {
989
+ console.error("[eslint-config-inject] Failed to read rule configs:", error);
990
+ }
991
+ return configs;
992
+ }
993
+ function findRulePropertyInConfigArray(arrayExpr, ruleId) {
994
+ const fullRuleKey = `uilint/${ruleId}`;
995
+ if (!arrayExpr || arrayExpr.type !== "ArrayExpression") return null;
996
+ for (const el of arrayExpr.elements ?? []) {
997
+ if (!el || el.type !== "ObjectExpression") continue;
998
+ const rules = getObjectPropertyValue(el, "rules");
999
+ if (!rules || rules.type !== "ObjectExpression") continue;
1000
+ for (const prop of rules.properties ?? []) {
1001
+ if (!prop) continue;
1002
+ if (prop.type !== "ObjectProperty" && prop.type !== "Property") continue;
1003
+ const key = prop.key;
1004
+ if (!isStringLiteral(key)) continue;
1005
+ if (key.value === fullRuleKey) {
1006
+ return { prop, rulesObj: rules };
1007
+ }
1008
+ }
1009
+ }
1010
+ return null;
1011
+ }
1012
+ function updateRuleSeverityInConfig(configPath, ruleId, severity) {
1013
+ try {
1014
+ const source = readFileSync3(configPath, "utf-8");
1015
+ const mod = parseModule(source);
1016
+ const found = findExportedConfigArrayExpression(mod);
1017
+ if (!found) {
1018
+ return {
1019
+ success: false,
1020
+ error: "Could not parse ESLint config array"
1021
+ };
1022
+ }
1023
+ const { arrayExpr } = found;
1024
+ const ruleInfo = findRulePropertyInConfigArray(arrayExpr, ruleId);
1025
+ if (!ruleInfo) {
1026
+ return {
1027
+ success: false,
1028
+ error: `Rule "uilint/${ruleId}" not found in config. Use 'uilint install' to add new rules.`
1029
+ };
1030
+ }
1031
+ const { prop } = ruleInfo;
1032
+ const valueNode = prop.value;
1033
+ if (isStringLiteral(valueNode)) {
1034
+ valueNode.value = severity;
1035
+ } else if (valueNode?.type === "ArrayExpression") {
1036
+ const firstEl = valueNode.elements?.[0];
1037
+ if (firstEl && isStringLiteral(firstEl)) {
1038
+ firstEl.value = severity;
1039
+ } else {
1040
+ const severityNode = parseExpression(`"${severity}"`).$ast;
1041
+ if (valueNode.elements && valueNode.elements.length > 0) {
1042
+ valueNode.elements[0] = severityNode;
1043
+ } else {
1044
+ valueNode.elements = [severityNode];
1045
+ }
1046
+ }
1047
+ } else {
1048
+ return {
1049
+ success: false,
1050
+ error: `Rule "uilint/${ruleId}" has unexpected format`
1051
+ };
1052
+ }
1053
+ const updated = generateCode(mod).code;
1054
+ writeFileSync(configPath, updated, "utf-8");
1055
+ return { success: true };
1056
+ } catch (error) {
1057
+ return {
1058
+ success: false,
1059
+ error: error instanceof Error ? error.message : String(error)
1060
+ };
1061
+ }
1062
+ }
1063
+ function updateRuleConfigInConfig(configPath, ruleId, severity, options) {
1064
+ try {
1065
+ const source = readFileSync3(configPath, "utf-8");
1066
+ const mod = parseModule(source);
1067
+ const found = findExportedConfigArrayExpression(mod);
1068
+ if (!found) {
1069
+ return {
1070
+ success: false,
1071
+ error: "Could not parse ESLint config array"
1072
+ };
1073
+ }
1074
+ const { arrayExpr } = found;
1075
+ const ruleInfo = findRulePropertyInConfigArray(arrayExpr, ruleId);
1076
+ if (!ruleInfo) {
1077
+ return {
1078
+ success: false,
1079
+ error: `Rule "uilint/${ruleId}" not found in config. Use 'uilint install' to add new rules.`
1080
+ };
1081
+ }
1082
+ const { prop } = ruleInfo;
1083
+ const optionsJson = JSON.stringify(options);
1084
+ const newValueExpr = `["${severity}", ${optionsJson}]`;
1085
+ const newValueNode = parseExpression(newValueExpr).$ast;
1086
+ prop.value = newValueNode;
1087
+ const updated = generateCode(mod).code;
1088
+ writeFileSync(configPath, updated, "utf-8");
1089
+ return { success: true };
1090
+ } catch (error) {
1091
+ return {
1092
+ success: false,
1093
+ error: error instanceof Error ? error.message : String(error)
1094
+ };
1095
+ }
1096
+ }
1097
+
1098
+ // src/utils/coverage-prepare.ts
1099
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
1100
+ function injectCoverageConfig(vitestConfigPath) {
1101
+ try {
1102
+ const content = readFileSync4(vitestConfigPath, "utf-8");
1103
+ if (/coverage\s*:\s*\{/.test(content)) {
1104
+ return false;
1105
+ }
1106
+ const testBlockRegex = /(test\s*:\s*\{)/;
1107
+ const match = content.match(testBlockRegex);
1108
+ if (!match) {
1109
+ return false;
1110
+ }
1111
+ const coverageConfig = `
1112
+ coverage: {
1113
+ provider: "v8",
1114
+ reporter: ["text", "json"],
1115
+ reportsDirectory: "./coverage",
1116
+ },`;
1117
+ const newContent = content.replace(
1118
+ testBlockRegex,
1119
+ `$1${coverageConfig}`
1120
+ );
1121
+ writeFileSync2(vitestConfigPath, newContent, "utf-8");
1122
+ return true;
1123
+ } catch {
1124
+ return false;
1125
+ }
1126
+ }
1127
+ async function prepareCoverage(options) {
1128
+ const { appRoot, onProgress, skipPackageInstall, skipTests } = options;
1129
+ const start = Date.now();
1130
+ const result = {
1131
+ packageAdded: false,
1132
+ configModified: false,
1133
+ testsRan: false,
1134
+ coverageGenerated: false,
1135
+ duration: 0
1136
+ };
1137
+ try {
1138
+ onProgress?.("Detecting coverage setup...", "detect");
1139
+ const setup = detectCoverageSetup(appRoot);
1140
+ if (!setup.hasVitest) {
1141
+ result.error = "Vitest not found in dependencies";
1142
+ result.duration = Date.now() - start;
1143
+ return result;
1144
+ }
1145
+ if (setup.needsCoveragePackage && !skipPackageInstall) {
1146
+ onProgress?.("Installing @vitest/coverage-v8...", "install");
1147
+ const pm = detectPackageManager(appRoot);
1148
+ try {
1149
+ await installDependencies(pm, appRoot, ["@vitest/coverage-v8"]);
1150
+ result.packageAdded = true;
1151
+ } catch (err) {
1152
+ const msg = err instanceof Error ? err.message : String(err);
1153
+ result.error = `Failed to install coverage package: ${msg}`;
1154
+ result.duration = Date.now() - start;
1155
+ return result;
1156
+ }
1157
+ }
1158
+ if (setup.needsCoverageConfig && setup.vitestConfigPath) {
1159
+ onProgress?.("Adding coverage configuration...", "config");
1160
+ result.configModified = injectCoverageConfig(setup.vitestConfigPath);
1161
+ }
1162
+ if (!skipTests) {
1163
+ const updatedSetup = detectCoverageSetup(appRoot);
1164
+ if (!updatedSetup.hasCoverageData || result.configModified) {
1165
+ onProgress?.("Running tests with coverage...", "test");
1166
+ const pm = detectPackageManager(appRoot);
1167
+ try {
1168
+ await runTestsWithCoverage(pm, appRoot);
1169
+ result.testsRan = true;
1170
+ } catch (err) {
1171
+ const msg = err instanceof Error ? err.message : String(err);
1172
+ result.error = `Tests failed: ${msg}`;
1173
+ }
1174
+ const finalSetup = detectCoverageSetup(appRoot);
1175
+ result.coverageGenerated = finalSetup.hasCoverageData;
1176
+ }
1177
+ } else {
1178
+ onProgress?.("Skipping tests (skipTests=true)", "skip");
1179
+ }
1180
+ result.duration = Date.now() - start;
1181
+ return result;
1182
+ } catch (err) {
1183
+ const msg = err instanceof Error ? err.message : String(err);
1184
+ result.error = `Coverage preparation failed: ${msg}`;
1185
+ result.duration = Date.now() - start;
1186
+ return result;
1187
+ }
1188
+ }
1189
+ function needsCoveragePreparation(setup) {
1190
+ if (!setup.hasVitest) {
1191
+ return false;
1192
+ }
1193
+ return setup.needsCoveragePackage || setup.needsCoverageConfig || !setup.hasCoverageData;
1194
+ }
1195
+
1196
+ export {
1197
+ pc,
1198
+ intro2 as intro,
1199
+ outro2 as outro,
1200
+ withSpinner,
1201
+ createSpinner,
1202
+ note2 as note,
1203
+ log2 as log,
1204
+ logInfo,
1205
+ logSuccess,
1206
+ logWarning,
1207
+ logError,
1208
+ select2 as select,
1209
+ confirm2 as confirm,
1210
+ multiselect2 as multiselect,
1211
+ detectNextAppRouter,
1212
+ findNextAppRouterProjects,
1213
+ findEslintConfigFile,
1214
+ getEslintConfigFilename,
1215
+ getUilintEslintConfigInfoFromSource,
1216
+ installEslintPlugin,
1217
+ uninstallEslintPlugin,
1218
+ readRuleConfigsFromConfig,
1219
+ updateRuleSeverityInConfig,
1220
+ updateRuleConfigInConfig,
1221
+ detectCoverageSetup,
1222
+ injectCoverageConfig,
1223
+ prepareCoverage,
1224
+ needsCoveragePreparation
1225
+ };
1226
+ //# sourceMappingURL=chunk-VNANPKR2.js.map