uilint 0.2.21 → 0.2.23

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.
@@ -2,13 +2,18 @@
2
2
  import {
3
3
  confirm,
4
4
  detectNextAppRouter,
5
+ findEslintConfigFile,
5
6
  findNextAppRouterProjects,
7
+ getEslintConfigFilename,
8
+ getUilintEslintConfigInfoFromSource,
9
+ installEslintPlugin,
6
10
  log,
7
11
  multiselect,
8
12
  note,
9
13
  pc,
10
- select
11
- } from "./chunk-FRNXXIEM.js";
14
+ select,
15
+ uninstallEslintPlugin
16
+ } from "./chunk-PB5DLLVC.js";
12
17
  import {
13
18
  GENSTYLEGUIDE_COMMAND_MD,
14
19
  detectPackageManager,
@@ -137,8 +142,11 @@ function ProjectSelector({
137
142
  // src/commands/install/components/MultiSelect.tsx
138
143
  import { useState as useState3, useCallback } from "react";
139
144
  import { Box as Box2, Text as Text3, useInput as useInput2, useApp as useApp2 } from "ink";
140
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
141
- function StatusIndicator({ status, isSelected }) {
145
+ import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
146
+ function StatusIndicator({ status, isSelected, isMarkedForUninstall }) {
147
+ if (isMarkedForUninstall) {
148
+ return /* @__PURE__ */ jsx3(Text3, { color: "red", children: "\u2717" });
149
+ }
142
150
  if (status === "installed") {
143
151
  return /* @__PURE__ */ jsx3(Text3, { color: "green", children: "\u2713" });
144
152
  }
@@ -153,7 +161,10 @@ function StatusIndicator({ status, isSelected }) {
153
161
  }
154
162
  return /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "\u25CB" });
155
163
  }
156
- function StatusLabel({ status }) {
164
+ function StatusLabel({ status, isMarkedForUninstall }) {
165
+ if (isMarkedForUninstall) {
166
+ return /* @__PURE__ */ jsx3(Text3, { color: "red", dimColor: true, children: "uninstall" });
167
+ }
157
168
  if (status === "installed") {
158
169
  return /* @__PURE__ */ jsx3(Text3, { color: "green", dimColor: true, children: "installed" });
159
170
  }
@@ -177,6 +188,7 @@ function ConfigSelector({
177
188
  items.filter((item) => item.status !== "installed" && !item.disabled).map((item) => item.id)
178
189
  );
179
190
  });
191
+ const [markedForUninstall, setMarkedForUninstall] = useState3(/* @__PURE__ */ new Set());
180
192
  const categories = Array.from(new Set(items.map((item) => item.category)));
181
193
  const itemsByCategory = /* @__PURE__ */ new Map();
182
194
  for (const cat of categories) {
@@ -185,8 +197,19 @@ function ConfigSelector({
185
197
  const flatItems = items;
186
198
  const handleToggle = useCallback(() => {
187
199
  const item = flatItems[cursor];
188
- const canToggle = item && !item.disabled && item.status !== "installed";
189
- if (!canToggle) return;
200
+ if (!item || item.disabled) return;
201
+ if (item.status === "installed") {
202
+ setMarkedForUninstall((prev) => {
203
+ const next = new Set(prev);
204
+ if (next.has(item.id)) {
205
+ next.delete(item.id);
206
+ } else {
207
+ next.add(item.id);
208
+ }
209
+ return next;
210
+ });
211
+ return;
212
+ }
190
213
  setSelected((prev) => {
191
214
  const next = new Set(prev);
192
215
  if (next.has(item.id)) {
@@ -205,7 +228,7 @@ function ConfigSelector({
205
228
  } else if (input === " ") {
206
229
  handleToggle();
207
230
  } else if (key.return) {
208
- onSubmit(Array.from(selected));
231
+ onSubmit(Array.from(selected), Array.from(markedForUninstall));
209
232
  } else if (input === "q" || key.escape) {
210
233
  onCancel?.();
211
234
  exit();
@@ -215,8 +238,10 @@ function ConfigSelector({
215
238
  items.filter((item) => item.status !== "installed" && !item.disabled).map((item) => item.id)
216
239
  )
217
240
  );
241
+ setMarkedForUninstall(/* @__PURE__ */ new Set());
218
242
  } else if (input === "n") {
219
243
  setSelected(/* @__PURE__ */ new Set());
244
+ setMarkedForUninstall(/* @__PURE__ */ new Set());
220
245
  }
221
246
  });
222
247
  let globalIndex = 0;
@@ -234,20 +259,21 @@ function ConfigSelector({
234
259
  const itemIndex = globalIndex++;
235
260
  const isCursor = itemIndex === cursor;
236
261
  const isItemSelected = selected.has(item.id);
237
- const isDisabled = item.disabled || item.status === "installed";
262
+ const isItemMarkedForUninstall = markedForUninstall.has(item.id);
263
+ const isDisabled = item.disabled === true;
238
264
  return /* @__PURE__ */ jsxs2(Box2, { paddingLeft: 2, children: [
239
265
  /* @__PURE__ */ jsx3(Text3, { color: isCursor ? "cyan" : void 0, children: isCursor ? "\u203A " : " " }),
240
- /* @__PURE__ */ jsx3(Box2, { width: 2, children: /* @__PURE__ */ jsx3(StatusIndicator, { status: item.status, isSelected: isItemSelected }) }),
266
+ /* @__PURE__ */ jsx3(Box2, { width: 2, children: /* @__PURE__ */ jsx3(StatusIndicator, { status: item.status, isSelected: isItemSelected, isMarkedForUninstall: isItemMarkedForUninstall }) }),
241
267
  /* @__PURE__ */ jsx3(Box2, { width: 28, children: /* @__PURE__ */ jsx3(
242
268
  Text3,
243
269
  {
244
- color: isDisabled ? void 0 : isCursor ? "cyan" : void 0,
245
- dimColor: isDisabled,
270
+ color: isItemMarkedForUninstall ? "red" : isDisabled ? void 0 : isCursor ? "cyan" : void 0,
271
+ dimColor: isDisabled && !isItemMarkedForUninstall,
246
272
  children: item.label
247
273
  }
248
274
  ) }),
249
275
  /* @__PURE__ */ jsx3(Box2, { width: 20, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: item.hint || "" }) }),
250
- /* @__PURE__ */ jsx3(StatusLabel, { status: item.status })
276
+ /* @__PURE__ */ jsx3(StatusLabel, { status: item.status, isMarkedForUninstall: isItemMarkedForUninstall })
251
277
  ] }, item.id);
252
278
  })
253
279
  ] }, category);
@@ -273,10 +299,11 @@ function ConfigSelector({
273
299
  ] }) }),
274
300
  /* @__PURE__ */ jsx3(Box2, { marginTop: 1, children: /* @__PURE__ */ jsxs2(Text3, { children: [
275
301
  /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: selected.size }),
276
- /* @__PURE__ */ jsxs2(Text3, { dimColor: true, children: [
277
- " item",
278
- selected.size !== 1 ? "s" : "",
279
- " selected"
302
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: " to install" }),
303
+ markedForUninstall.size > 0 && /* @__PURE__ */ jsxs2(Fragment, { children: [
304
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: ", " }),
305
+ /* @__PURE__ */ jsx3(Text3, { color: "red", children: markedForUninstall.size }),
306
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: " to uninstall" })
280
307
  ] })
281
308
  ] }) })
282
309
  ] });
@@ -819,7 +846,7 @@ var nextOverlayInstaller = {
819
846
  label: app.projectPath.split("/").pop() || app.projectPath,
820
847
  path: app.projectPath,
821
848
  hint: "App Router",
822
- isInstalled: false
849
+ isInstalled: app.hasUilintOverlay
823
850
  }));
824
851
  },
825
852
  plan(targets, config, project) {
@@ -892,6 +919,32 @@ var nextOverlayInstaller = {
892
919
  type: "complete",
893
920
  message: "Next.js overlay installed"
894
921
  };
922
+ },
923
+ planUninstall(targets, project) {
924
+ const actions = [];
925
+ if (targets.length === 0) return { actions };
926
+ const target = targets[0];
927
+ const appInfo = project.nextApps.find(
928
+ (app) => app.projectPath === target.path
929
+ );
930
+ if (!appInfo) return { actions };
931
+ const { projectPath, detection } = appInfo;
932
+ actions.push({
933
+ type: "remove_react",
934
+ projectPath,
935
+ appRoot: detection.appRoot,
936
+ mode: "next"
937
+ });
938
+ actions.push({
939
+ type: "remove_next_config",
940
+ projectPath
941
+ });
942
+ actions.push({
943
+ type: "remove_next_routes",
944
+ projectPath,
945
+ appRoot: detection.appRoot
946
+ });
947
+ return { actions };
895
948
  }
896
949
  };
897
950
 
@@ -1029,6 +1082,7 @@ function InstallApp({
1029
1082
  const [selections, setSelections] = useState6([]);
1030
1083
  const [configItems, setConfigItems] = useState6([]);
1031
1084
  const [selectedFeatureIds, setSelectedFeatureIds] = useState6([]);
1085
+ const [uninstallFeatureIds, setUninstallFeatureIds] = useState6([]);
1032
1086
  const [error, setError] = useState6(null);
1033
1087
  const [injectionPoints, setInjectionPoints] = useState6([]);
1034
1088
  const [selectedInjectionPoint, setSelectedInjectionPoint] = useState6(void 0);
@@ -1086,8 +1140,9 @@ function InstallApp({
1086
1140
  setPhase("select-project");
1087
1141
  }
1088
1142
  };
1089
- const handleFeatureSubmit = (selectedIds) => {
1143
+ const handleFeatureSubmit = (selectedIds, uninstallIds) => {
1090
1144
  setSelectedFeatureIds(selectedIds);
1145
+ setUninstallFeatureIds(uninstallIds);
1091
1146
  const nextSelected = selectedIds.some((id) => id.startsWith("next:"));
1092
1147
  if (nextSelected && project && selectedProject) {
1093
1148
  const appInfo = project.nextApps.find(
@@ -1143,6 +1198,7 @@ function InstallApp({
1143
1198
  };
1144
1199
  const finishInstallation = (selectedIds, eslintRules, injectionConfig) => {
1145
1200
  const selectedSet = new Set(selectedIds);
1201
+ const uninstallSet = new Set(uninstallFeatureIds);
1146
1202
  const updatedSelections = selections.map((sel) => {
1147
1203
  const selectedTargets = sel.targets.filter(
1148
1204
  (t) => selectedSet.has(`${sel.installer.id}:${t.id}`)
@@ -1153,8 +1209,18 @@ function InstallApp({
1153
1209
  selected: selectedTargets.length > 0
1154
1210
  };
1155
1211
  });
1212
+ const uninstallSelections = selections.map((sel) => {
1213
+ const uninstallTargets = sel.targets.filter(
1214
+ (t) => uninstallSet.has(`${sel.installer.id}:${t.id}`)
1215
+ );
1216
+ return {
1217
+ ...sel,
1218
+ targets: uninstallTargets,
1219
+ selected: uninstallTargets.length > 0
1220
+ };
1221
+ }).filter((sel) => sel.selected);
1156
1222
  setSelections(updatedSelections);
1157
- onComplete(updatedSelections, eslintRules, injectionConfig);
1223
+ onComplete(updatedSelections, eslintRules, injectionConfig, uninstallSelections.length > 0 ? uninstallSelections : void 0);
1158
1224
  };
1159
1225
  const handleCancel = () => {
1160
1226
  exit();
@@ -1248,9 +1314,9 @@ function InstallApp({
1248
1314
  }
1249
1315
 
1250
1316
  // src/commands/install/analyze.ts
1251
- import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
1252
- import { join as join5 } from "path";
1253
- import { findWorkspaceRoot as findWorkspaceRoot2 } from "uilint-core/node";
1317
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
1318
+ import { join as join4 } from "path";
1319
+ import { findWorkspaceRoot } from "uilint-core/node";
1254
1320
 
1255
1321
  // src/utils/vite-detect.ts
1256
1322
  import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2 } from "fs";
@@ -1493,585 +1559,63 @@ function findPackages(rootDir, options) {
1493
1559
  });
1494
1560
  }
1495
1561
 
1496
- // src/utils/eslint-config-inject.ts
1497
- import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync } from "fs";
1498
- import { join as join4, relative as relative3, dirname as dirname2 } from "path";
1499
- import { parseExpression, parseModule as parseModule2, generateCode } from "magicast";
1500
- import { findWorkspaceRoot } from "uilint-core/node";
1501
- var CONFIG_EXTENSIONS = [".ts", ".mjs", ".js", ".cjs"];
1502
- function findEslintConfigFile(projectPath) {
1503
- for (const ext of CONFIG_EXTENSIONS) {
1504
- const configPath = join4(projectPath, `eslint.config${ext}`);
1505
- if (existsSync4(configPath)) {
1506
- return configPath;
1507
- }
1508
- }
1509
- return null;
1510
- }
1511
- function getEslintConfigFilename(configPath) {
1512
- const parts = configPath.split("/");
1513
- return parts[parts.length - 1] || "eslint.config.mjs";
1514
- }
1515
- function isIdentifier(node, name) {
1516
- return !!node && node.type === "Identifier" && (name ? node.name === name : typeof node.name === "string");
1517
- }
1518
- function isStringLiteral(node) {
1519
- return !!node && (node.type === "StringLiteral" || node.type === "Literal") && typeof node.value === "string";
1520
- }
1521
- function getObjectPropertyValue(obj, keyName) {
1522
- if (!obj || obj.type !== "ObjectExpression") return null;
1523
- for (const prop of obj.properties ?? []) {
1524
- if (!prop) continue;
1525
- if (prop.type === "ObjectProperty" || prop.type === "Property") {
1526
- const key = prop.key;
1527
- const keyMatch = key?.type === "Identifier" && key.name === keyName || isStringLiteral(key) && key.value === keyName;
1528
- if (keyMatch) return prop.value;
1529
- }
1530
- }
1531
- return null;
1532
- }
1533
- function hasSpreadProperties(obj) {
1534
- if (!obj || obj.type !== "ObjectExpression") return false;
1535
- return (obj.properties ?? []).some(
1536
- (p) => p && (p.type === "SpreadElement" || p.type === "SpreadProperty")
1537
- );
1538
- }
1539
- var IGNORED_AST_KEYS = /* @__PURE__ */ new Set([
1540
- "loc",
1541
- "start",
1542
- "end",
1543
- "extra",
1544
- "leadingComments",
1545
- "trailingComments",
1546
- "innerComments"
1547
- ]);
1548
- function normalizeAstForCompare(node) {
1549
- if (node === null) return null;
1550
- if (node === void 0) return void 0;
1551
- if (typeof node !== "object") return node;
1552
- if (Array.isArray(node)) return node.map(normalizeAstForCompare);
1553
- const out = {};
1554
- const keys = Object.keys(node).filter((k) => !IGNORED_AST_KEYS.has(k)).sort();
1555
- for (const k of keys) {
1556
- if (k.startsWith("$")) continue;
1557
- out[k] = normalizeAstForCompare(node[k]);
1558
- }
1559
- return out;
1560
- }
1561
- function astEquivalent(a, b) {
1562
- try {
1563
- return JSON.stringify(normalizeAstForCompare(a)) === JSON.stringify(normalizeAstForCompare(b));
1564
- } catch {
1565
- return false;
1566
- }
1567
- }
1568
- function collectUilintRuleIdsFromRulesObject(rulesObj) {
1569
- const ids = /* @__PURE__ */ new Set();
1570
- if (!rulesObj || rulesObj.type !== "ObjectExpression") return ids;
1571
- for (const prop of rulesObj.properties ?? []) {
1572
- if (!prop) continue;
1573
- if (prop.type !== "ObjectProperty" && prop.type !== "Property") continue;
1574
- const key = prop.key;
1575
- if (!isStringLiteral(key)) continue;
1576
- const val = key.value;
1577
- if (typeof val !== "string") continue;
1578
- if (val.startsWith("uilint/")) {
1579
- ids.add(val.slice("uilint/".length));
1580
- }
1581
- }
1582
- return ids;
1583
- }
1584
- function findExportedConfigArrayExpression(mod) {
1585
- function unwrapExpression2(expr) {
1586
- let e = expr;
1587
- while (e) {
1588
- if (e.type === "TSAsExpression" || e.type === "TSNonNullExpression") {
1589
- e = e.expression;
1590
- continue;
1591
- }
1592
- if (e.type === "TSSatisfiesExpression") {
1593
- e = e.expression;
1594
- continue;
1595
- }
1596
- if (e.type === "ParenthesizedExpression") {
1597
- e = e.expression;
1598
- continue;
1599
- }
1600
- break;
1601
- }
1602
- return e;
1603
- }
1604
- function resolveTopLevelIdentifierToArrayExpr(program2, name) {
1605
- if (!program2 || program2.type !== "Program") return null;
1606
- for (const stmt of program2.body ?? []) {
1607
- if (stmt?.type !== "VariableDeclaration") continue;
1608
- for (const decl of stmt.declarations ?? []) {
1609
- const id = decl?.id;
1610
- if (!isIdentifier(id, name)) continue;
1611
- const init = unwrapExpression2(decl?.init);
1612
- if (!init) return null;
1613
- if (init.type === "ArrayExpression") return init;
1614
- if (init.type === "CallExpression" && isIdentifier(init.callee, "defineConfig") && unwrapExpression2(init.arguments?.[0])?.type === "ArrayExpression") {
1615
- return unwrapExpression2(init.arguments?.[0]);
1616
- }
1617
- return null;
1618
- }
1619
- }
1620
- return null;
1621
- }
1622
- const program = mod?.$ast;
1623
- if (program && program.type === "Program") {
1624
- for (const stmt of program.body ?? []) {
1625
- if (!stmt || stmt.type !== "ExportDefaultDeclaration") continue;
1626
- const decl = unwrapExpression2(stmt.declaration);
1627
- if (!decl) break;
1628
- if (decl.type === "ArrayExpression") {
1629
- return { kind: "esm", arrayExpr: decl, program };
1630
- }
1631
- if (decl.type === "CallExpression" && isIdentifier(decl.callee, "defineConfig") && unwrapExpression2(decl.arguments?.[0])?.type === "ArrayExpression") {
1632
- return {
1633
- kind: "esm",
1634
- arrayExpr: unwrapExpression2(decl.arguments?.[0]),
1635
- program
1636
- };
1637
- }
1638
- if (decl.type === "Identifier" && typeof decl.name === "string") {
1639
- const resolved = resolveTopLevelIdentifierToArrayExpr(
1640
- program,
1641
- decl.name
1642
- );
1643
- if (resolved) return { kind: "esm", arrayExpr: resolved, program };
1644
- }
1645
- break;
1646
- }
1647
- }
1648
- if (!program || program.type !== "Program") return null;
1649
- for (const stmt of program.body ?? []) {
1650
- if (!stmt || stmt.type !== "ExpressionStatement") continue;
1651
- const expr = stmt.expression;
1652
- if (!expr || expr.type !== "AssignmentExpression") continue;
1653
- const left = expr.left;
1654
- const right = expr.right;
1655
- const isModuleExports = left?.type === "MemberExpression" && isIdentifier(left.object, "module") && isIdentifier(left.property, "exports");
1656
- if (!isModuleExports) continue;
1657
- if (right?.type === "ArrayExpression") {
1658
- return { kind: "cjs", arrayExpr: right, program };
1659
- }
1660
- if (right?.type === "CallExpression" && isIdentifier(right.callee, "defineConfig") && right.arguments?.[0]?.type === "ArrayExpression") {
1661
- return { kind: "cjs", arrayExpr: right.arguments[0], program };
1662
- }
1663
- if (right?.type === "Identifier" && typeof right.name === "string") {
1664
- const resolved = resolveTopLevelIdentifierToArrayExpr(
1665
- program,
1666
- right.name
1667
- );
1668
- if (resolved) return { kind: "cjs", arrayExpr: resolved, program };
1669
- }
1670
- }
1671
- return null;
1672
- }
1673
- function collectConfiguredUilintRuleIdsFromConfigArray(arrayExpr) {
1674
- const ids = /* @__PURE__ */ new Set();
1675
- if (!arrayExpr || arrayExpr.type !== "ArrayExpression") return ids;
1676
- for (const el of arrayExpr.elements ?? []) {
1677
- if (!el || el.type !== "ObjectExpression") continue;
1678
- const rules = getObjectPropertyValue(el, "rules");
1679
- for (const id of collectUilintRuleIdsFromRulesObject(rules)) ids.add(id);
1680
- }
1681
- return ids;
1682
- }
1683
- function findExistingUilintRulesObject(arrayExpr) {
1684
- if (!arrayExpr || arrayExpr.type !== "ArrayExpression") {
1685
- return { configObj: null, rulesObj: null, safeToMutate: false };
1686
- }
1687
- for (const el of arrayExpr.elements ?? []) {
1688
- if (!el || el.type !== "ObjectExpression") continue;
1689
- const plugins = getObjectPropertyValue(el, "plugins");
1690
- const rules = getObjectPropertyValue(el, "rules");
1691
- const hasUilintPlugin = plugins?.type === "ObjectExpression" && getObjectPropertyValue(plugins, "uilint") !== null;
1692
- const uilintIds = collectUilintRuleIdsFromRulesObject(rules);
1693
- const hasUilintRules = uilintIds.size > 0;
1694
- if (!hasUilintPlugin && !hasUilintRules) continue;
1695
- const safe = rules?.type === "ObjectExpression" && !hasSpreadProperties(rules);
1696
- return { configObj: el, rulesObj: rules, safeToMutate: safe };
1697
- }
1698
- return { configObj: null, rulesObj: null, safeToMutate: false };
1699
- }
1700
- function collectTopLevelBindings(program) {
1701
- const names = /* @__PURE__ */ new Set();
1702
- if (!program || program.type !== "Program") return names;
1703
- for (const stmt of program.body ?? []) {
1704
- if (stmt?.type === "VariableDeclaration") {
1705
- for (const decl of stmt.declarations ?? []) {
1706
- const id = decl?.id;
1707
- if (id?.type === "Identifier" && typeof id.name === "string") {
1708
- names.add(id.name);
1709
- }
1710
- }
1711
- } else if (stmt?.type === "FunctionDeclaration") {
1712
- if (stmt.id?.type === "Identifier" && typeof stmt.id.name === "string") {
1713
- names.add(stmt.id.name);
1714
- }
1715
- }
1716
- }
1717
- return names;
1718
- }
1719
- function chooseUniqueIdentifier(base, used) {
1720
- if (!used.has(base)) return base;
1721
- let i = 2;
1722
- while (used.has(`${base}${i}`)) i++;
1723
- return `${base}${i}`;
1724
- }
1725
- function addLocalRuleImportsAst(mod, selectedRules, configPath, rulesRoot, fileExtension = ".js") {
1726
- const importNames = /* @__PURE__ */ new Map();
1727
- let changed = false;
1728
- const configDir = dirname2(configPath);
1729
- const rulesDir = join4(rulesRoot, ".uilint", "rules");
1730
- const relativeRulesPath = relative3(configDir, rulesDir).replace(/\\/g, "/");
1731
- const normalizedRulesPath = relativeRulesPath.startsWith("./") || relativeRulesPath.startsWith("../") ? relativeRulesPath : `./${relativeRulesPath}`;
1732
- const used = collectTopLevelBindings(mod.$ast);
1733
- for (const rule of selectedRules) {
1734
- const importName = chooseUniqueIdentifier(
1735
- `${rule.id.replace(/-([a-z])/g, (_, c) => c.toUpperCase()).replace(/^./, (c) => c.toUpperCase())}Rule`,
1736
- used
1737
- );
1738
- importNames.set(rule.id, importName);
1739
- used.add(importName);
1740
- const rulePath = `${normalizedRulesPath}/${rule.id}${fileExtension}`;
1741
- mod.imports.$add({
1742
- imported: "default",
1743
- local: importName,
1744
- from: rulePath
1745
- });
1746
- changed = true;
1747
- }
1748
- return { importNames, changed };
1749
- }
1750
- function addLocalRuleRequiresAst(program, selectedRules, configPath, rulesRoot, fileExtension = ".js") {
1751
- const importNames = /* @__PURE__ */ new Map();
1752
- let changed = false;
1753
- if (!program || program.type !== "Program") {
1754
- return { importNames, changed };
1755
- }
1756
- const configDir = dirname2(configPath);
1757
- const rulesDir = join4(rulesRoot, ".uilint", "rules");
1758
- const relativeRulesPath = relative3(configDir, rulesDir).replace(/\\/g, "/");
1759
- const normalizedRulesPath = relativeRulesPath.startsWith("./") || relativeRulesPath.startsWith("../") ? relativeRulesPath : `./${relativeRulesPath}`;
1760
- const used = collectTopLevelBindings(program);
1761
- for (const rule of selectedRules) {
1762
- const importName = chooseUniqueIdentifier(
1763
- `${rule.id.replace(/-([a-z])/g, (_, c) => c.toUpperCase()).replace(/^./, (c) => c.toUpperCase())}Rule`,
1764
- used
1765
- );
1766
- importNames.set(rule.id, importName);
1767
- used.add(importName);
1768
- const rulePath = `${normalizedRulesPath}/${rule.id}${fileExtension}`;
1769
- const stmtMod = parseModule2(
1770
- `const ${importName} = require("${rulePath}");`
1771
- );
1772
- const stmt = stmtMod.$ast.body?.[0];
1773
- if (stmt) {
1774
- let insertAt = 0;
1775
- const first = program.body?.[0];
1776
- if (first?.type === "ExpressionStatement" && first.expression?.type === "StringLiteral" && first.expression.value === "use strict") {
1777
- insertAt = 1;
1778
- }
1779
- program.body.splice(insertAt, 0, stmt);
1780
- changed = true;
1781
- }
1782
- }
1783
- return { importNames, changed };
1784
- }
1785
- function appendUilintConfigBlockToArray(arrayExpr, selectedRules, ruleImportNames) {
1786
- const pluginRulesCode = Array.from(ruleImportNames.entries()).map(([ruleId, importName]) => ` "${ruleId}": ${importName},`).join("\n");
1787
- const rulesPropsCode = selectedRules.map((r) => {
1788
- const ruleKey = `uilint/${r.id}`;
1789
- const valueCode = r.defaultOptions && r.defaultOptions.length > 0 ? `["${r.defaultSeverity}", ...${JSON.stringify(
1790
- r.defaultOptions,
1791
- null,
1792
- 2
1793
- )}]` : `"${r.defaultSeverity}"`;
1794
- return ` "${ruleKey}": ${valueCode},`;
1795
- }).join("\n");
1796
- const blockCode = `{
1797
- files: [
1798
- "src/**/*.{js,jsx,ts,tsx}",
1799
- "app/**/*.{js,jsx,ts,tsx}",
1800
- "pages/**/*.{js,jsx,ts,tsx}",
1801
- ],
1802
- plugins: {
1803
- uilint: {
1804
- rules: {
1805
- ${pluginRulesCode}
1806
- },
1807
- },
1808
- },
1809
- rules: {
1810
- ${rulesPropsCode}
1811
- },
1812
- }`;
1813
- const objExpr = parseExpression(blockCode).$ast;
1814
- arrayExpr.elements.push(objExpr);
1815
- }
1816
- function getUilintEslintConfigInfoFromSourceAst(source) {
1562
+ // src/commands/install/analyze.ts
1563
+ function safeParseJson(filePath) {
1817
1564
  try {
1818
- const mod = parseModule2(source);
1819
- const found = findExportedConfigArrayExpression(mod);
1820
- if (!found) {
1821
- return {
1822
- error: "Could not locate an exported ESLint flat config array (expected `export default [...]`, `export default defineConfig([...])`, `module.exports = [...]`, or `module.exports = defineConfig([...])`)."
1823
- };
1824
- }
1825
- const configuredRuleIds = collectConfiguredUilintRuleIdsFromConfigArray(
1826
- found.arrayExpr
1827
- );
1828
- const existingUilint = findExistingUilintRulesObject(found.arrayExpr);
1829
- const configured = configuredRuleIds.size > 0 || existingUilint.configObj !== null;
1830
- return {
1831
- info: { configuredRuleIds, configured },
1832
- mod,
1833
- arrayExpr: found.arrayExpr,
1834
- kind: found.kind
1835
- };
1565
+ const content = readFileSync4(filePath, "utf-8");
1566
+ return JSON.parse(content);
1836
1567
  } catch {
1837
- return {
1838
- error: "Unable to parse ESLint config as JavaScript. Please update it manually or simplify the config so it can be safely auto-modified."
1839
- };
1840
- }
1841
- }
1842
- function getUilintEslintConfigInfoFromSource(source) {
1843
- const ast = getUilintEslintConfigInfoFromSourceAst(source);
1844
- if ("error" in ast) {
1845
- const configuredRuleIds = extractConfiguredUilintRuleIds(source);
1846
- return {
1847
- configuredRuleIds,
1848
- configured: configuredRuleIds.size > 0
1849
- };
1850
- }
1851
- return ast.info;
1852
- }
1853
- function extractConfiguredUilintRuleIds(source) {
1854
- const ids = /* @__PURE__ */ new Set();
1855
- const re = /["']uilint\/([^"']+)["']\s*:/g;
1856
- for (const m of source.matchAll(re)) {
1857
- if (m[1]) ids.add(m[1]);
1858
- }
1859
- return ids;
1860
- }
1861
- function getMissingSelectedRules(selectedRules, configuredIds) {
1862
- return selectedRules.filter((r) => !configuredIds.has(r.id));
1863
- }
1864
- function buildDesiredRuleValueExpression(rule) {
1865
- if (rule.defaultOptions && rule.defaultOptions.length > 0) {
1866
- return `["${rule.defaultSeverity}", ...${JSON.stringify(
1867
- rule.defaultOptions,
1868
- null,
1869
- 2
1870
- )}]`;
1871
- }
1872
- return `"${rule.defaultSeverity}"`;
1873
- }
1874
- function collectUilintRuleValueNodesFromConfigArray(arrayExpr) {
1875
- const out = /* @__PURE__ */ new Map();
1876
- if (!arrayExpr || arrayExpr.type !== "ArrayExpression") return out;
1877
- for (const el of arrayExpr.elements ?? []) {
1878
- if (!el || el.type !== "ObjectExpression") continue;
1879
- const rules = getObjectPropertyValue(el, "rules");
1880
- if (!rules || rules.type !== "ObjectExpression") continue;
1881
- for (const prop of rules.properties ?? []) {
1882
- if (!prop) continue;
1883
- if (prop.type !== "ObjectProperty" && prop.type !== "Property") continue;
1884
- const key = prop.key;
1885
- if (!isStringLiteral(key)) continue;
1886
- const k = key.value;
1887
- if (typeof k !== "string" || !k.startsWith("uilint/")) continue;
1888
- const id = k.slice("uilint/".length);
1889
- if (!out.has(id)) out.set(id, prop.value);
1890
- }
1568
+ return void 0;
1891
1569
  }
1892
- return out;
1893
1570
  }
1894
- function getRulesNeedingUpdate(selectedRules, configuredIds, arrayExpr) {
1895
- const existingVals = collectUilintRuleValueNodesFromConfigArray(arrayExpr);
1896
- return selectedRules.filter((r) => {
1897
- if (!configuredIds.has(r.id)) return false;
1898
- const existing = existingVals.get(r.id);
1899
- if (!existing) return true;
1900
- const desiredExpr = buildDesiredRuleValueExpression(r);
1901
- const desiredAst = parseExpression(desiredExpr).$ast;
1902
- return !astEquivalent(existing, desiredAst);
1903
- });
1571
+ function hasUilintOverlayInstalled(projectPath) {
1572
+ const pkgPath = join4(projectPath, "package.json");
1573
+ const pkg = safeParseJson(pkgPath);
1574
+ if (!pkg) return false;
1575
+ return !!(pkg.dependencies?.["uilint-react"] || pkg.devDependencies?.["uilint-react"]);
1904
1576
  }
1905
- async function installEslintPlugin(opts) {
1906
- const configPath = findEslintConfigFile(opts.projectPath);
1907
- if (!configPath) {
1908
- return {
1909
- configFile: null,
1910
- modified: false,
1911
- missingRuleIds: [],
1912
- configured: false
1913
- };
1914
- }
1915
- const configFilename = getEslintConfigFilename(configPath);
1916
- const original = readFileSync4(configPath, "utf-8");
1917
- const isCommonJS = configPath.endsWith(".cjs");
1918
- const ast = getUilintEslintConfigInfoFromSourceAst(original);
1919
- if ("error" in ast) {
1920
- return {
1921
- configFile: configFilename,
1922
- modified: false,
1923
- missingRuleIds: [],
1924
- configured: false,
1925
- error: ast.error
1926
- };
1927
- }
1928
- const { info, mod, arrayExpr, kind } = ast;
1929
- const configuredIds = info.configuredRuleIds;
1930
- const missingRules = getMissingSelectedRules(
1931
- opts.selectedRules,
1932
- configuredIds
1933
- );
1934
- const rulesToUpdate = getRulesNeedingUpdate(
1935
- opts.selectedRules,
1936
- configuredIds,
1937
- arrayExpr
1938
- );
1939
- let rulesToApply = [];
1940
- if (!info.configured) {
1941
- rulesToApply = opts.selectedRules;
1942
- } else {
1943
- rulesToApply = [...missingRules, ...rulesToUpdate];
1944
- if (missingRules.length > 0 && !opts.force) {
1945
- const ok = await opts.confirmAddMissingRules?.(
1946
- configFilename,
1947
- missingRules
1948
- );
1949
- if (!ok) {
1950
- return {
1951
- configFile: configFilename,
1952
- modified: false,
1953
- missingRuleIds: missingRules.map((r) => r.id),
1954
- configured: true
1955
- };
1956
- }
1957
- }
1958
- }
1959
- if (rulesToApply.length === 0) {
1960
- return {
1961
- configFile: configFilename,
1962
- modified: false,
1963
- missingRuleIds: missingRules.map((r) => r.id),
1964
- configured: info.configured
1965
- };
1966
- }
1967
- let modifiedAst = false;
1968
- const localRulesDir = join4(opts.projectPath, ".uilint", "rules");
1969
- const workspaceRoot = findWorkspaceRoot(opts.projectPath);
1970
- const workspaceRulesDir = join4(workspaceRoot, ".uilint", "rules");
1971
- const rulesRoot = existsSync4(localRulesDir) ? opts.projectPath : workspaceRoot;
1972
- const isTypeScriptConfig = configPath.endsWith(".ts");
1973
- let fileExtension = isTypeScriptConfig ? "" : ".js";
1974
- let ruleImportNames;
1975
- if (kind === "esm") {
1976
- const result = addLocalRuleImportsAst(
1977
- mod,
1978
- rulesToApply,
1979
- configPath,
1980
- rulesRoot,
1981
- fileExtension
1982
- );
1983
- ruleImportNames = result.importNames;
1984
- if (result.changed) modifiedAst = true;
1985
- } else {
1986
- const result = addLocalRuleRequiresAst(
1987
- mod.$ast,
1988
- rulesToApply,
1989
- configPath,
1990
- rulesRoot,
1991
- fileExtension
1992
- );
1993
- ruleImportNames = result.importNames;
1994
- if (result.changed) modifiedAst = true;
1995
- }
1996
- if (ruleImportNames && ruleImportNames.size > 0) {
1997
- appendUilintConfigBlockToArray(arrayExpr, rulesToApply, ruleImportNames);
1998
- modifiedAst = true;
1999
- }
2000
- if (!info.configured) {
2001
- if (kind === "esm") {
2002
- mod.imports.$add({
2003
- imported: "createRule",
2004
- local: "createRule",
2005
- from: "uilint-eslint"
2006
- });
2007
- modifiedAst = true;
2008
- } else {
2009
- const stmtMod = parseModule2(
2010
- `const { createRule } = require("uilint-eslint");`
2011
- );
2012
- const stmt = stmtMod.$ast.body?.[0];
2013
- if (stmt) {
2014
- let insertAt = 0;
2015
- const first = mod.$ast.body?.[0];
2016
- if (first?.type === "ExpressionStatement" && first.expression?.type === "StringLiteral" && first.expression.value === "use strict") {
2017
- insertAt = 1;
2018
- }
2019
- mod.$ast.body.splice(insertAt, 0, stmt);
2020
- modifiedAst = true;
2021
- }
2022
- }
2023
- }
2024
- const updated = modifiedAst ? generateCode(mod).code : original;
2025
- if (updated !== original) {
2026
- writeFileSync(configPath, updated, "utf-8");
2027
- return {
2028
- configFile: configFilename,
2029
- modified: true,
2030
- missingRuleIds: missingRules.map((r) => r.id),
2031
- configured: getUilintEslintConfigInfoFromSource(updated).configured
2032
- };
2033
- }
2034
- return {
2035
- configFile: configFilename,
2036
- modified: false,
2037
- missingRuleIds: missingRules.map((r) => r.id),
2038
- configured: getUilintEslintConfigInfoFromSource(updated).configured
2039
- };
2040
- }
2041
-
2042
- // src/commands/install/analyze.ts
2043
1577
  async function analyze(projectPath = process.cwd()) {
2044
- const workspaceRoot = findWorkspaceRoot2(projectPath);
1578
+ const workspaceRoot = findWorkspaceRoot(projectPath);
2045
1579
  const packageManager = detectPackageManager(projectPath);
2046
- const cursorDir = join5(projectPath, ".cursor");
2047
- const cursorDirExists = existsSync5(cursorDir);
2048
- const styleguidePath = join5(projectPath, ".uilint", "styleguide.md");
2049
- const styleguideExists = existsSync5(styleguidePath);
2050
- const commandsDir = join5(cursorDir, "commands");
2051
- const genstyleguideExists = existsSync5(join5(commandsDir, "genstyleguide.md"));
1580
+ const cursorDir = join4(projectPath, ".cursor");
1581
+ const cursorDirExists = existsSync4(cursorDir);
1582
+ const styleguidePath = join4(projectPath, ".uilint", "styleguide.md");
1583
+ const styleguideExists = existsSync4(styleguidePath);
1584
+ const commandsDir = join4(cursorDir, "commands");
1585
+ const genstyleguideExists = existsSync4(join4(commandsDir, "genstyleguide.md"));
2052
1586
  const nextApps = [];
2053
1587
  const directDetection = detectNextAppRouter(projectPath);
2054
1588
  if (directDetection) {
2055
- nextApps.push({ projectPath, detection: directDetection });
1589
+ nextApps.push({
1590
+ projectPath,
1591
+ detection: directDetection,
1592
+ hasUilintOverlay: hasUilintOverlayInstalled(projectPath)
1593
+ });
2056
1594
  } else {
2057
1595
  const matches = findNextAppRouterProjects(workspaceRoot, { maxDepth: 5 });
2058
1596
  for (const match of matches) {
2059
1597
  nextApps.push({
2060
1598
  projectPath: match.projectPath,
2061
- detection: match.detection
1599
+ detection: match.detection,
1600
+ hasUilintOverlay: hasUilintOverlayInstalled(match.projectPath)
2062
1601
  });
2063
1602
  }
2064
1603
  }
2065
1604
  const viteApps = [];
2066
1605
  const directVite = detectViteReact(projectPath);
2067
1606
  if (directVite) {
2068
- viteApps.push({ projectPath, detection: directVite });
1607
+ viteApps.push({
1608
+ projectPath,
1609
+ detection: directVite,
1610
+ hasUilintOverlay: hasUilintOverlayInstalled(projectPath)
1611
+ });
2069
1612
  } else {
2070
1613
  const matches = findViteReactProjects(workspaceRoot, { maxDepth: 5 });
2071
1614
  for (const match of matches) {
2072
1615
  viteApps.push({
2073
1616
  projectPath: match.projectPath,
2074
- detection: match.detection
1617
+ detection: match.detection,
1618
+ hasUilintOverlay: hasUilintOverlayInstalled(match.projectPath)
2075
1619
  });
2076
1620
  }
2077
1621
  }
@@ -2084,7 +1628,7 @@ async function analyze(projectPath = process.cwd()) {
2084
1628
  if (eslintConfigPath) {
2085
1629
  eslintConfigFilename = getEslintConfigFilename(eslintConfigPath);
2086
1630
  try {
2087
- const source = readFileSync5(eslintConfigPath, "utf-8");
1631
+ const source = readFileSync4(eslintConfigPath, "utf-8");
2088
1632
  const info = getUilintEslintConfigInfoFromSource(source);
2089
1633
  hasRules = info.configuredRuleIds.size > 0;
2090
1634
  configuredRuleIds = Array.from(info.configuredRuleIds);
@@ -2122,49 +1666,50 @@ async function analyze(projectPath = process.cwd()) {
2122
1666
 
2123
1667
  // src/commands/install/execute.ts
2124
1668
  import {
2125
- existsSync as existsSync11,
1669
+ existsSync as existsSync10,
2126
1670
  mkdirSync,
2127
- writeFileSync as writeFileSync5,
2128
- readFileSync as readFileSync9,
1671
+ writeFileSync as writeFileSync4,
1672
+ readFileSync as readFileSync8,
2129
1673
  unlinkSync,
2130
- chmodSync
1674
+ chmodSync,
1675
+ rmSync
2131
1676
  } from "fs";
2132
- import { dirname as dirname5 } from "path";
1677
+ import { dirname as dirname4 } from "path";
2133
1678
 
2134
1679
  // src/utils/react-inject.ts
2135
- import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "fs";
2136
- import { join as join6, relative as relative4 } from "path";
2137
- import { parseModule as parseModule3, generateCode as generateCode2 } from "magicast";
1680
+ import { existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync } from "fs";
1681
+ import { join as join5, relative as relative3 } from "path";
1682
+ import { parseModule as parseModule2, generateCode } from "magicast";
2138
1683
  function getDefaultCandidates(projectPath, appRoot) {
2139
1684
  const viteMainCandidates = [
2140
- join6(appRoot, "main.tsx"),
2141
- join6(appRoot, "main.jsx"),
2142
- join6(appRoot, "main.ts"),
2143
- join6(appRoot, "main.js")
1685
+ join5(appRoot, "main.tsx"),
1686
+ join5(appRoot, "main.jsx"),
1687
+ join5(appRoot, "main.ts"),
1688
+ join5(appRoot, "main.js")
2144
1689
  ];
2145
1690
  const existingViteMain = viteMainCandidates.filter(
2146
- (rel) => existsSync6(join6(projectPath, rel))
1691
+ (rel) => existsSync5(join5(projectPath, rel))
2147
1692
  );
2148
1693
  if (existingViteMain.length > 0) return existingViteMain;
2149
- const viteAppCandidates = [join6(appRoot, "App.tsx"), join6(appRoot, "App.jsx")];
1694
+ const viteAppCandidates = [join5(appRoot, "App.tsx"), join5(appRoot, "App.jsx")];
2150
1695
  const existingViteApp = viteAppCandidates.filter(
2151
- (rel) => existsSync6(join6(projectPath, rel))
1696
+ (rel) => existsSync5(join5(projectPath, rel))
2152
1697
  );
2153
1698
  if (existingViteApp.length > 0) return existingViteApp;
2154
1699
  const layoutCandidates = [
2155
- join6(appRoot, "layout.tsx"),
2156
- join6(appRoot, "layout.jsx"),
2157
- join6(appRoot, "layout.ts"),
2158
- join6(appRoot, "layout.js")
1700
+ join5(appRoot, "layout.tsx"),
1701
+ join5(appRoot, "layout.jsx"),
1702
+ join5(appRoot, "layout.ts"),
1703
+ join5(appRoot, "layout.js")
2159
1704
  ];
2160
1705
  const existingLayouts = layoutCandidates.filter(
2161
- (rel) => existsSync6(join6(projectPath, rel))
1706
+ (rel) => existsSync5(join5(projectPath, rel))
2162
1707
  );
2163
1708
  if (existingLayouts.length > 0) {
2164
1709
  return existingLayouts;
2165
1710
  }
2166
- const pageCandidates = [join6(appRoot, "page.tsx"), join6(appRoot, "page.jsx")];
2167
- return pageCandidates.filter((rel) => existsSync6(join6(projectPath, rel)));
1711
+ const pageCandidates = [join5(appRoot, "page.tsx"), join5(appRoot, "page.jsx")];
1712
+ return pageCandidates.filter((rel) => existsSync5(join5(projectPath, rel)));
2168
1713
  }
2169
1714
  function isUseClientDirective(stmt) {
2170
1715
  return stmt?.type === "ExpressionStatement" && stmt.expression?.type === "StringLiteral" && stmt.expression.value === "use client";
@@ -2198,12 +1743,12 @@ function ensureNamedImport(program, from, name) {
2198
1743
  (s) => s?.type === "ImportSpecifier" && (s.imported?.name === name || s.imported?.value === name)
2199
1744
  );
2200
1745
  if (has) return { changed: false };
2201
- const spec = parseModule3(`import { ${name} } from "${from}";`).$ast.body?.[0]?.specifiers?.[0];
1746
+ const spec = parseModule2(`import { ${name} } from "${from}";`).$ast.body?.[0]?.specifiers?.[0];
2202
1747
  if (!spec) return { changed: false };
2203
1748
  existing.specifiers = [...existing.specifiers ?? [], spec];
2204
1749
  return { changed: true };
2205
1750
  }
2206
- const importDecl = parseModule3(`import { ${name} } from "${from}";`).$ast.body?.[0];
1751
+ const importDecl = parseModule2(`import { ${name} } from "${from}";`).$ast.body?.[0];
2207
1752
  if (!importDecl) return { changed: false };
2208
1753
  const body = program.body ?? [];
2209
1754
  let insertAt = 0;
@@ -2233,7 +1778,7 @@ function hasUILintDevtoolsJsx(program) {
2233
1778
  function addDevtoolsElementNextJs(program) {
2234
1779
  if (!program || program.type !== "Program") return { changed: false };
2235
1780
  if (hasUILintDevtoolsJsx(program)) return { changed: false };
2236
- const devtoolsMod = parseModule3(
1781
+ const devtoolsMod = parseModule2(
2237
1782
  "const __uilint_devtools = <uilint-devtools />;"
2238
1783
  );
2239
1784
  const devtoolsJsx = devtoolsMod.$ast.body?.[0]?.declarations?.[0]?.init ?? null;
@@ -2271,12 +1816,12 @@ function addDevtoolsElementVite(program) {
2271
1816
  const arg0 = node.arguments?.[0];
2272
1817
  if (!arg0) return;
2273
1818
  if (arg0.type !== "JSXElement" && arg0.type !== "JSXFragment") return;
2274
- const devtoolsMod = parseModule3(
1819
+ const devtoolsMod = parseModule2(
2275
1820
  "const __uilint_devtools = <uilint-devtools />;"
2276
1821
  );
2277
1822
  const devtoolsJsx = devtoolsMod.$ast.body?.[0]?.declarations?.[0]?.init ?? null;
2278
1823
  if (!devtoolsJsx) return;
2279
- const fragmentMod = parseModule3(
1824
+ const fragmentMod = parseModule2(
2280
1825
  "const __fragment = <></>;"
2281
1826
  );
2282
1827
  const fragmentJsx = fragmentMod.$ast.body?.[0]?.declarations?.[0]?.init ?? null;
@@ -2296,7 +1841,7 @@ function ensureSideEffectImport(program, from) {
2296
1841
  if (!program || program.type !== "Program") return { changed: false };
2297
1842
  const existing = findImportDeclaration(program, from);
2298
1843
  if (existing) return { changed: false };
2299
- const importDecl = parseModule3(`import "${from}";`).$ast.body?.[0];
1844
+ const importDecl = parseModule2(`import "${from}";`).$ast.body?.[0];
2300
1845
  if (!importDecl) return { changed: false };
2301
1846
  const body = program.body ?? [];
2302
1847
  let insertAt = 0;
@@ -2312,7 +1857,7 @@ function ensureSideEffectImport(program, from) {
2312
1857
  function addDevtoolsToClientComponent(program) {
2313
1858
  if (!program || program.type !== "Program") return { changed: false };
2314
1859
  if (hasUILintDevtoolsJsx(program)) return { changed: false };
2315
- const devtoolsMod = parseModule3(
1860
+ const devtoolsMod = parseModule2(
2316
1861
  "const __uilint_devtools = <uilint-devtools />;"
2317
1862
  );
2318
1863
  const devtoolsJsx = devtoolsMod.$ast.body?.[0]?.declarations?.[0]?.init ?? null;
@@ -2338,7 +1883,7 @@ function addDevtoolsToClientComponent(program) {
2338
1883
  const arg = node.argument;
2339
1884
  if (!arg) return;
2340
1885
  if (arg.type !== "JSXElement" && arg.type !== "JSXFragment") return;
2341
- const fragmentMod = parseModule3("const __fragment = <></>;");
1886
+ const fragmentMod = parseModule2("const __fragment = <></>;");
2342
1887
  const fragmentJsx = fragmentMod.$ast.body?.[0]?.declarations?.[0]?.init ?? null;
2343
1888
  if (!fragmentJsx) return;
2344
1889
  fragmentJsx.children = [arg, devtoolsJsx];
@@ -2393,7 +1938,7 @@ function wrapChildrenWithProviders(program, providersImportPath) {
2393
1938
  (child) => child?.type === "JSXExpressionContainer" && child.expression?.type === "Identifier" && child.expression.name === "children"
2394
1939
  );
2395
1940
  if (childrenIndex === -1) return;
2396
- const providersMod = parseModule3(
1941
+ const providersMod = parseModule2(
2397
1942
  "const __providers = <Providers>{children}</Providers>;"
2398
1943
  );
2399
1944
  const providersJsx = providersMod.$ast.body?.[0]?.declarations?.[0]?.init ?? null;
@@ -2411,8 +1956,8 @@ function wrapChildrenWithProviders(program, providersImportPath) {
2411
1956
  function findLayoutFile2(projectPath, appRoot) {
2412
1957
  const extensions = [".tsx", ".jsx", ".ts", ".js"];
2413
1958
  for (const ext of extensions) {
2414
- const layoutPath = join6(projectPath, appRoot, `layout${ext}`);
2415
- if (existsSync6(layoutPath)) return layoutPath;
1959
+ const layoutPath = join5(projectPath, appRoot, `layout${ext}`);
1960
+ if (existsSync5(layoutPath)) return layoutPath;
2416
1961
  }
2417
1962
  return null;
2418
1963
  }
@@ -2423,32 +1968,32 @@ async function createProvidersAndModifyLayout(projectPath, appRoot) {
2423
1968
  }
2424
1969
  const isTypeScript = layoutPath.endsWith(".tsx") || layoutPath.endsWith(".ts");
2425
1970
  const providersExt = isTypeScript ? ".tsx" : ".jsx";
2426
- const providersPath = join6(projectPath, appRoot, `providers${providersExt}`);
2427
- if (existsSync6(providersPath)) {
1971
+ const providersPath = join5(projectPath, appRoot, `providers${providersExt}`);
1972
+ if (existsSync5(providersPath)) {
2428
1973
  throw new Error(
2429
1974
  `providers${providersExt} already exists. Please select it from the list instead.`
2430
1975
  );
2431
1976
  }
2432
1977
  const providersContent = generateProvidersContent(isTypeScript);
2433
- writeFileSync2(providersPath, providersContent, "utf-8");
2434
- const layoutContent = readFileSync6(layoutPath, "utf-8");
1978
+ writeFileSync(providersPath, providersContent, "utf-8");
1979
+ const layoutContent = readFileSync5(layoutPath, "utf-8");
2435
1980
  let layoutMod;
2436
1981
  try {
2437
- layoutMod = parseModule3(layoutContent);
1982
+ layoutMod = parseModule2(layoutContent);
2438
1983
  } catch {
2439
1984
  throw new Error(
2440
- `Unable to parse ${relative4(projectPath, layoutPath)} as JavaScript/TypeScript.`
1985
+ `Unable to parse ${relative3(projectPath, layoutPath)} as JavaScript/TypeScript.`
2441
1986
  );
2442
1987
  }
2443
1988
  const layoutProgram = layoutMod.$ast;
2444
1989
  const wrapRes = wrapChildrenWithProviders(layoutProgram, "./providers");
2445
1990
  if (wrapRes.changed) {
2446
- const updatedLayout = generateCode2(layoutMod).code;
2447
- writeFileSync2(layoutPath, updatedLayout, "utf-8");
1991
+ const updatedLayout = generateCode(layoutMod).code;
1992
+ writeFileSync(layoutPath, updatedLayout, "utf-8");
2448
1993
  }
2449
1994
  return {
2450
- providersFile: relative4(projectPath, providersPath),
2451
- layoutFile: relative4(projectPath, layoutPath),
1995
+ providersFile: relative3(projectPath, providersPath),
1996
+ layoutFile: relative3(projectPath, layoutPath),
2452
1997
  modified: true
2453
1998
  };
2454
1999
  }
@@ -2460,8 +2005,8 @@ async function installReactUILintOverlay(opts) {
2460
2005
  );
2461
2006
  const modifiedFiles = [];
2462
2007
  if (result.modified) {
2463
- modifiedFiles.push(join6(opts.projectPath, result.providersFile));
2464
- modifiedFiles.push(join6(opts.projectPath, result.layoutFile));
2008
+ modifiedFiles.push(join5(opts.projectPath, result.providersFile));
2009
+ modifiedFiles.push(join5(opts.projectPath, result.layoutFile));
2465
2010
  }
2466
2011
  return {
2467
2012
  targetFile: result.providersFile,
@@ -2473,14 +2018,14 @@ async function installReactUILintOverlay(opts) {
2473
2018
  }
2474
2019
  if (opts.targetFile) {
2475
2020
  const absTarget2 = opts.targetFile;
2476
- const relTarget = relative4(opts.projectPath, absTarget2);
2477
- if (!existsSync6(absTarget2)) {
2021
+ const relTarget = relative3(opts.projectPath, absTarget2);
2022
+ if (!existsSync5(absTarget2)) {
2478
2023
  throw new Error(`Target file not found: ${relTarget}`);
2479
2024
  }
2480
- const original2 = readFileSync6(absTarget2, "utf-8");
2025
+ const original2 = readFileSync5(absTarget2, "utf-8");
2481
2026
  let mod2;
2482
2027
  try {
2483
- mod2 = parseModule3(original2);
2028
+ mod2 = parseModule2(original2);
2484
2029
  } catch {
2485
2030
  throw new Error(
2486
2031
  `Unable to parse ${relTarget} as JavaScript/TypeScript. Please update it manually.`
@@ -2503,10 +2048,10 @@ async function installReactUILintOverlay(opts) {
2503
2048
  if (importRes2.changed) changed2 = true;
2504
2049
  const addRes2 = addDevtoolsToClientComponent(program2);
2505
2050
  if (addRes2.changed) changed2 = true;
2506
- const updated2 = changed2 ? generateCode2(mod2).code : original2;
2051
+ const updated2 = changed2 ? generateCode(mod2).code : original2;
2507
2052
  const modified2 = updated2 !== original2;
2508
2053
  if (modified2) {
2509
- writeFileSync2(absTarget2, updated2, "utf-8");
2054
+ writeFileSync(absTarget2, updated2, "utf-8");
2510
2055
  }
2511
2056
  return {
2512
2057
  targetFile: relTarget,
@@ -2527,11 +2072,11 @@ async function installReactUILintOverlay(opts) {
2527
2072
  } else {
2528
2073
  chosen = candidates[0];
2529
2074
  }
2530
- const absTarget = join6(opts.projectPath, chosen);
2531
- const original = readFileSync6(absTarget, "utf-8");
2075
+ const absTarget = join5(opts.projectPath, chosen);
2076
+ const original = readFileSync5(absTarget, "utf-8");
2532
2077
  let mod;
2533
2078
  try {
2534
- mod = parseModule3(original);
2079
+ mod = parseModule2(original);
2535
2080
  } catch {
2536
2081
  throw new Error(
2537
2082
  `Unable to parse ${chosen} as JavaScript/TypeScript. Please update it manually.`
@@ -2547,10 +2092,10 @@ async function installReactUILintOverlay(opts) {
2547
2092
  const mode = opts.mode ?? "next";
2548
2093
  const addRes = mode === "vite" ? addDevtoolsElementVite(program) : addDevtoolsElementNextJs(program);
2549
2094
  if (addRes.changed) changed = true;
2550
- const updated = changed ? generateCode2(mod).code : original;
2095
+ const updated = changed ? generateCode(mod).code : original;
2551
2096
  const modified = updated !== original;
2552
2097
  if (modified) {
2553
- writeFileSync2(absTarget, updated, "utf-8");
2098
+ writeFileSync(absTarget, updated, "utf-8");
2554
2099
  }
2555
2100
  return {
2556
2101
  targetFile: chosen,
@@ -2559,16 +2104,53 @@ async function installReactUILintOverlay(opts) {
2559
2104
  modifiedFiles: modified ? [absTarget] : []
2560
2105
  };
2561
2106
  }
2107
+ async function uninstallReactUILintOverlay(options) {
2108
+ const { projectPath, appRoot, mode = "next" } = options;
2109
+ const candidates = getDefaultCandidates(projectPath, appRoot);
2110
+ const modifiedFiles = [];
2111
+ for (const candidate of candidates) {
2112
+ const absPath = join5(projectPath, candidate);
2113
+ if (!existsSync5(absPath)) continue;
2114
+ try {
2115
+ const original = readFileSync5(absPath, "utf-8");
2116
+ let updated = original.replace(
2117
+ /^import\s+["']uilint-react\/devtools["'];?\s*$/gm,
2118
+ ""
2119
+ );
2120
+ updated = updated.replace(
2121
+ /^import\s+\{[^}]*UILintProvider[^}]*\}\s+from\s+["']uilint-react["'];?\s*$/gm,
2122
+ ""
2123
+ );
2124
+ updated = updated.replace(/<uilint-devtools\s*\/>/g, "");
2125
+ updated = updated.replace(/<uilint-devtools><\/uilint-devtools>/g, "");
2126
+ updated = updated.replace(/<uilint-devtools\s*>\s*<\/uilint-devtools>/g, "");
2127
+ updated = updated.replace(
2128
+ /<UILintProvider[^>]*>([\s\S]*?)<\/UILintProvider>/g,
2129
+ "$1"
2130
+ );
2131
+ updated = updated.replace(/\n{3,}/g, "\n\n");
2132
+ if (updated !== original) {
2133
+ writeFileSync(absPath, updated, "utf-8");
2134
+ modifiedFiles.push(absPath);
2135
+ }
2136
+ } catch {
2137
+ }
2138
+ }
2139
+ return {
2140
+ success: true,
2141
+ modifiedFiles
2142
+ };
2143
+ }
2562
2144
 
2563
2145
  // src/utils/next-config-inject.ts
2564
- import { existsSync as existsSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
2565
- import { join as join7 } from "path";
2566
- import { parseModule as parseModule4, generateCode as generateCode3 } from "magicast";
2567
- var CONFIG_EXTENSIONS2 = [".ts", ".mjs", ".js", ".cjs"];
2146
+ import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "fs";
2147
+ import { join as join6 } from "path";
2148
+ import { parseModule as parseModule3, generateCode as generateCode2 } from "magicast";
2149
+ var CONFIG_EXTENSIONS = [".ts", ".mjs", ".js", ".cjs"];
2568
2150
  function findNextConfigFile(projectPath) {
2569
- for (const ext of CONFIG_EXTENSIONS2) {
2570
- const configPath = join7(projectPath, `next.config${ext}`);
2571
- if (existsSync7(configPath)) {
2151
+ for (const ext of CONFIG_EXTENSIONS) {
2152
+ const configPath = join6(projectPath, `next.config${ext}`);
2153
+ if (existsSync6(configPath)) {
2572
2154
  return configPath;
2573
2155
  }
2574
2156
  }
@@ -2578,10 +2160,10 @@ function getNextConfigFilename(configPath) {
2578
2160
  const parts = configPath.split("/");
2579
2161
  return parts[parts.length - 1] || "next.config.ts";
2580
2162
  }
2581
- function isIdentifier2(node, name) {
2163
+ function isIdentifier(node, name) {
2582
2164
  return !!node && node.type === "Identifier" && (name ? node.name === name : typeof node.name === "string");
2583
2165
  }
2584
- function isStringLiteral2(node) {
2166
+ function isStringLiteral(node) {
2585
2167
  return !!node && (node.type === "StringLiteral" || node.type === "Literal") && typeof node.value === "string";
2586
2168
  }
2587
2169
  function ensureEsmWithJsxLocImport(program) {
@@ -2594,12 +2176,12 @@ function ensureEsmWithJsxLocImport(program) {
2594
2176
  (sp) => sp?.type === "ImportSpecifier" && (sp.imported?.name === "withJsxLoc" || sp.imported?.value === "withJsxLoc")
2595
2177
  );
2596
2178
  if (has) return { changed: false };
2597
- const spec = parseModule4('import { withJsxLoc } from "jsx-loc-plugin";').$ast.body?.[0]?.specifiers?.[0];
2179
+ const spec = parseModule3('import { withJsxLoc } from "jsx-loc-plugin";').$ast.body?.[0]?.specifiers?.[0];
2598
2180
  if (!spec) return { changed: false };
2599
2181
  existing.specifiers = [...existing.specifiers ?? [], spec];
2600
2182
  return { changed: true };
2601
2183
  }
2602
- const importDecl = parseModule4('import { withJsxLoc } from "jsx-loc-plugin";').$ast.body?.[0];
2184
+ const importDecl = parseModule3('import { withJsxLoc } from "jsx-loc-plugin";').$ast.body?.[0];
2603
2185
  if (!importDecl) return { changed: false };
2604
2186
  const body = program.body ?? [];
2605
2187
  let insertAt = 0;
@@ -2615,14 +2197,14 @@ function ensureCjsWithJsxLocRequire(program) {
2615
2197
  if (stmt?.type !== "VariableDeclaration") continue;
2616
2198
  for (const decl of stmt.declarations ?? []) {
2617
2199
  const init = decl?.init;
2618
- if (init?.type === "CallExpression" && isIdentifier2(init.callee, "require") && isStringLiteral2(init.arguments?.[0]) && init.arguments[0].value === "jsx-loc-plugin") {
2200
+ if (init?.type === "CallExpression" && isIdentifier(init.callee, "require") && isStringLiteral(init.arguments?.[0]) && init.arguments[0].value === "jsx-loc-plugin") {
2619
2201
  if (decl.id?.type === "ObjectPattern") {
2620
2202
  const has = (decl.id.properties ?? []).some((p) => {
2621
2203
  if (p?.type !== "ObjectProperty" && p?.type !== "Property") return false;
2622
- return isIdentifier2(p.key, "withJsxLoc");
2204
+ return isIdentifier(p.key, "withJsxLoc");
2623
2205
  });
2624
2206
  if (has) return { changed: false };
2625
- const prop = parseModule4('const { withJsxLoc } = require("jsx-loc-plugin");').$ast.body?.[0]?.declarations?.[0]?.id?.properties?.[0];
2207
+ const prop = parseModule3('const { withJsxLoc } = require("jsx-loc-plugin");').$ast.body?.[0]?.declarations?.[0]?.id?.properties?.[0];
2626
2208
  if (!prop) return { changed: false };
2627
2209
  decl.id.properties = [...decl.id.properties ?? [], prop];
2628
2210
  return { changed: true };
@@ -2631,7 +2213,7 @@ function ensureCjsWithJsxLocRequire(program) {
2631
2213
  }
2632
2214
  }
2633
2215
  }
2634
- const reqDecl = parseModule4('const { withJsxLoc } = require("jsx-loc-plugin");').$ast.body?.[0];
2216
+ const reqDecl = parseModule3('const { withJsxLoc } = require("jsx-loc-plugin");').$ast.body?.[0];
2635
2217
  if (!reqDecl) return { changed: false };
2636
2218
  program.body.unshift(reqDecl);
2637
2219
  return { changed: true };
@@ -2643,7 +2225,7 @@ function wrapEsmExportDefault(program) {
2643
2225
  );
2644
2226
  if (!exportDecl) return { changed: false };
2645
2227
  const decl = exportDecl.declaration;
2646
- if (decl?.type === "CallExpression" && isIdentifier2(decl.callee, "withJsxLoc")) {
2228
+ if (decl?.type === "CallExpression" && isIdentifier(decl.callee, "withJsxLoc")) {
2647
2229
  return { changed: false };
2648
2230
  }
2649
2231
  exportDecl.declaration = {
@@ -2661,9 +2243,9 @@ function wrapCjsModuleExports(program) {
2661
2243
  if (!expr || expr.type !== "AssignmentExpression") continue;
2662
2244
  const left = expr.left;
2663
2245
  const right = expr.right;
2664
- const isModuleExports = left?.type === "MemberExpression" && isIdentifier2(left.object, "module") && isIdentifier2(left.property, "exports");
2246
+ const isModuleExports = left?.type === "MemberExpression" && isIdentifier(left.object, "module") && isIdentifier(left.property, "exports");
2665
2247
  if (!isModuleExports) continue;
2666
- if (right?.type === "CallExpression" && isIdentifier2(right.callee, "withJsxLoc")) {
2248
+ if (right?.type === "CallExpression" && isIdentifier(right.callee, "withJsxLoc")) {
2667
2249
  return { changed: false };
2668
2250
  }
2669
2251
  expr.right = {
@@ -2681,10 +2263,10 @@ async function installJsxLocPlugin(opts) {
2681
2263
  return { configFile: null, modified: false, modifiedFiles: [] };
2682
2264
  }
2683
2265
  const configFilename = getNextConfigFilename(configPath);
2684
- const original = readFileSync7(configPath, "utf-8");
2266
+ const original = readFileSync6(configPath, "utf-8");
2685
2267
  let mod;
2686
2268
  try {
2687
- mod = parseModule4(original);
2269
+ mod = parseModule3(original);
2688
2270
  } catch {
2689
2271
  return { configFile: configFilename, modified: false, modifiedFiles: [] };
2690
2272
  }
@@ -2702,23 +2284,69 @@ async function installJsxLocPlugin(opts) {
2702
2284
  const wrapRes = wrapEsmExportDefault(program);
2703
2285
  if (wrapRes.changed) changed = true;
2704
2286
  }
2705
- const updated = changed ? generateCode3(mod).code : original;
2287
+ const updated = changed ? generateCode2(mod).code : original;
2706
2288
  if (updated !== original) {
2707
- writeFileSync3(configPath, updated, "utf-8");
2289
+ writeFileSync2(configPath, updated, "utf-8");
2708
2290
  return { configFile: configFilename, modified: true, modifiedFiles: [configPath] };
2709
2291
  }
2710
2292
  return { configFile: configFilename, modified: false, modifiedFiles: [] };
2711
2293
  }
2294
+ async function uninstallJsxLocPlugin(options) {
2295
+ const { projectPath } = options;
2296
+ const configPath = findNextConfigFile(projectPath);
2297
+ if (!configPath) {
2298
+ return {
2299
+ success: true,
2300
+ modifiedFiles: []
2301
+ };
2302
+ }
2303
+ try {
2304
+ const original = readFileSync6(configPath, "utf-8");
2305
+ let updated = original.replace(
2306
+ /^import\s+\{[^}]*withJsxLoc[^}]*\}\s+from\s+["']jsx-loc-plugin\/next["'];?\s*$/gm,
2307
+ ""
2308
+ );
2309
+ updated = updated.replace(
2310
+ /^const\s+\{[^}]*withJsxLoc[^}]*\}\s*=\s*require\s*\(\s*["']jsx-loc-plugin\/next["']\s*\)\s*;?\s*$/gm,
2311
+ ""
2312
+ );
2313
+ updated = updated.replace(
2314
+ /export\s+default\s+withJsxLoc\s*\(\s*([\s\S]*?)\s*\)\s*;?/g,
2315
+ "export default $1;"
2316
+ );
2317
+ updated = updated.replace(
2318
+ /module\.exports\s*=\s*withJsxLoc\s*\(\s*([\s\S]*?)\s*\)\s*;?/g,
2319
+ "module.exports = $1;"
2320
+ );
2321
+ updated = updated.replace(/\n{3,}/g, "\n\n");
2322
+ if (updated !== original) {
2323
+ writeFileSync2(configPath, updated, "utf-8");
2324
+ return {
2325
+ success: true,
2326
+ modifiedFiles: [configPath]
2327
+ };
2328
+ }
2329
+ return {
2330
+ success: true,
2331
+ modifiedFiles: []
2332
+ };
2333
+ } catch (error) {
2334
+ return {
2335
+ success: false,
2336
+ error: error instanceof Error ? error.message : String(error)
2337
+ };
2338
+ }
2339
+ }
2712
2340
 
2713
2341
  // src/utils/vite-config-inject.ts
2714
- import { existsSync as existsSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
2715
- import { join as join8 } from "path";
2716
- import { parseModule as parseModule5, generateCode as generateCode4 } from "magicast";
2717
- var CONFIG_EXTENSIONS3 = [".ts", ".mjs", ".js", ".cjs"];
2342
+ import { existsSync as existsSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
2343
+ import { join as join7 } from "path";
2344
+ import { parseModule as parseModule4, generateCode as generateCode3 } from "magicast";
2345
+ var CONFIG_EXTENSIONS2 = [".ts", ".mjs", ".js", ".cjs"];
2718
2346
  function findViteConfigFile2(projectPath) {
2719
- for (const ext of CONFIG_EXTENSIONS3) {
2720
- const configPath = join8(projectPath, `vite.config${ext}`);
2721
- if (existsSync8(configPath)) return configPath;
2347
+ for (const ext of CONFIG_EXTENSIONS2) {
2348
+ const configPath = join7(projectPath, `vite.config${ext}`);
2349
+ if (existsSync7(configPath)) return configPath;
2722
2350
  }
2723
2351
  return null;
2724
2352
  }
@@ -2726,10 +2354,10 @@ function getViteConfigFilename(configPath) {
2726
2354
  const parts = configPath.split("/");
2727
2355
  return parts[parts.length - 1] || "vite.config.ts";
2728
2356
  }
2729
- function isIdentifier3(node, name) {
2357
+ function isIdentifier2(node, name) {
2730
2358
  return !!node && node.type === "Identifier" && (name ? node.name === name : typeof node.name === "string");
2731
2359
  }
2732
- function isStringLiteral3(node) {
2360
+ function isStringLiteral2(node) {
2733
2361
  return !!node && (node.type === "StringLiteral" || node.type === "Literal") && typeof node.value === "string";
2734
2362
  }
2735
2363
  function unwrapExpression(expr) {
@@ -2761,7 +2389,7 @@ function findExportedConfigObjectExpression(mod) {
2761
2389
  if (decl.type === "ObjectExpression") {
2762
2390
  return { kind: "esm", objExpr: decl, program };
2763
2391
  }
2764
- if (decl.type === "CallExpression" && isIdentifier3(decl.callee, "defineConfig") && unwrapExpression(decl.arguments?.[0])?.type === "ObjectExpression") {
2392
+ if (decl.type === "CallExpression" && isIdentifier2(decl.callee, "defineConfig") && unwrapExpression(decl.arguments?.[0])?.type === "ObjectExpression") {
2765
2393
  return {
2766
2394
  kind: "esm",
2767
2395
  objExpr: unwrapExpression(decl.arguments?.[0]),
@@ -2776,12 +2404,12 @@ function findExportedConfigObjectExpression(mod) {
2776
2404
  if (!expr || expr.type !== "AssignmentExpression") continue;
2777
2405
  const left = expr.left;
2778
2406
  const right = unwrapExpression(expr.right);
2779
- const isModuleExports = left?.type === "MemberExpression" && isIdentifier3(left.object, "module") && isIdentifier3(left.property, "exports");
2407
+ const isModuleExports = left?.type === "MemberExpression" && isIdentifier2(left.object, "module") && isIdentifier2(left.property, "exports");
2780
2408
  if (!isModuleExports) continue;
2781
2409
  if (right?.type === "ObjectExpression") {
2782
2410
  return { kind: "cjs", objExpr: right, program };
2783
2411
  }
2784
- if (right?.type === "CallExpression" && isIdentifier3(right.callee, "defineConfig") && unwrapExpression(right.arguments?.[0])?.type === "ObjectExpression") {
2412
+ if (right?.type === "CallExpression" && isIdentifier2(right.callee, "defineConfig") && unwrapExpression(right.arguments?.[0])?.type === "ObjectExpression") {
2785
2413
  return {
2786
2414
  kind: "cjs",
2787
2415
  objExpr: unwrapExpression(right.arguments?.[0]),
@@ -2797,7 +2425,7 @@ function getObjectProperty(obj, keyName) {
2797
2425
  if (!prop) continue;
2798
2426
  if (prop.type !== "ObjectProperty" && prop.type !== "Property") continue;
2799
2427
  const key = prop.key;
2800
- const keyMatch = key?.type === "Identifier" && key.name === keyName || isStringLiteral3(key) && key.value === keyName;
2428
+ const keyMatch = key?.type === "Identifier" && key.name === keyName || isStringLiteral2(key) && key.value === keyName;
2801
2429
  if (keyMatch) return prop;
2802
2430
  }
2803
2431
  return null;
@@ -2812,12 +2440,12 @@ function ensureEsmJsxLocImport(program) {
2812
2440
  (sp) => sp?.type === "ImportSpecifier" && (sp.imported?.name === "jsxLoc" || sp.imported?.value === "jsxLoc")
2813
2441
  );
2814
2442
  if (has) return { changed: false };
2815
- const spec = parseModule5('import { jsxLoc } from "jsx-loc-plugin/vite";').$ast.body?.[0]?.specifiers?.[0];
2443
+ const spec = parseModule4('import { jsxLoc } from "jsx-loc-plugin/vite";').$ast.body?.[0]?.specifiers?.[0];
2816
2444
  if (!spec) return { changed: false };
2817
2445
  existing.specifiers = [...existing.specifiers ?? [], spec];
2818
2446
  return { changed: true };
2819
2447
  }
2820
- const importDecl = parseModule5('import { jsxLoc } from "jsx-loc-plugin/vite";').$ast.body?.[0];
2448
+ const importDecl = parseModule4('import { jsxLoc } from "jsx-loc-plugin/vite";').$ast.body?.[0];
2821
2449
  if (!importDecl) return { changed: false };
2822
2450
  const body = program.body ?? [];
2823
2451
  let insertAt = 0;
@@ -2833,14 +2461,14 @@ function ensureCjsJsxLocRequire(program) {
2833
2461
  if (stmt?.type !== "VariableDeclaration") continue;
2834
2462
  for (const decl of stmt.declarations ?? []) {
2835
2463
  const init = decl?.init;
2836
- if (init?.type === "CallExpression" && isIdentifier3(init.callee, "require") && isStringLiteral3(init.arguments?.[0]) && init.arguments[0].value === "jsx-loc-plugin/vite") {
2464
+ if (init?.type === "CallExpression" && isIdentifier2(init.callee, "require") && isStringLiteral2(init.arguments?.[0]) && init.arguments[0].value === "jsx-loc-plugin/vite") {
2837
2465
  if (decl.id?.type === "ObjectPattern") {
2838
2466
  const has = (decl.id.properties ?? []).some((p) => {
2839
2467
  if (p?.type !== "ObjectProperty" && p?.type !== "Property") return false;
2840
- return isIdentifier3(p.key, "jsxLoc");
2468
+ return isIdentifier2(p.key, "jsxLoc");
2841
2469
  });
2842
2470
  if (has) return { changed: false };
2843
- const prop = parseModule5('const { jsxLoc } = require("jsx-loc-plugin/vite");').$ast.body?.[0]?.declarations?.[0]?.id?.properties?.[0];
2471
+ const prop = parseModule4('const { jsxLoc } = require("jsx-loc-plugin/vite");').$ast.body?.[0]?.declarations?.[0]?.id?.properties?.[0];
2844
2472
  if (!prop) return { changed: false };
2845
2473
  decl.id.properties = [...decl.id.properties ?? [], prop];
2846
2474
  return { changed: true };
@@ -2849,7 +2477,7 @@ function ensureCjsJsxLocRequire(program) {
2849
2477
  }
2850
2478
  }
2851
2479
  }
2852
- const reqDecl = parseModule5('const { jsxLoc } = require("jsx-loc-plugin/vite");').$ast.body?.[0];
2480
+ const reqDecl = parseModule4('const { jsxLoc } = require("jsx-loc-plugin/vite");').$ast.body?.[0];
2853
2481
  if (!reqDecl) return { changed: false };
2854
2482
  program.body.unshift(reqDecl);
2855
2483
  return { changed: true };
@@ -2859,16 +2487,16 @@ function pluginsHasJsxLoc(arr) {
2859
2487
  for (const el of arr.elements ?? []) {
2860
2488
  const e = unwrapExpression(el);
2861
2489
  if (!e) continue;
2862
- if (e.type === "CallExpression" && isIdentifier3(e.callee, "jsxLoc")) return true;
2490
+ if (e.type === "CallExpression" && isIdentifier2(e.callee, "jsxLoc")) return true;
2863
2491
  }
2864
2492
  return false;
2865
2493
  }
2866
2494
  function ensurePluginsContainsJsxLoc(configObj) {
2867
2495
  const pluginsProp = getObjectProperty(configObj, "plugins");
2868
2496
  if (!pluginsProp) {
2869
- const prop = parseModule5("export default { plugins: [jsxLoc()] };").$ast.body?.find((s) => s.type === "ExportDefaultDeclaration")?.declaration?.properties?.find((p) => {
2497
+ const prop = parseModule4("export default { plugins: [jsxLoc()] };").$ast.body?.find((s) => s.type === "ExportDefaultDeclaration")?.declaration?.properties?.find((p) => {
2870
2498
  const k = p?.key;
2871
- return k?.type === "Identifier" && k.name === "plugins" || isStringLiteral3(k) && k.value === "plugins";
2499
+ return k?.type === "Identifier" && k.name === "plugins" || isStringLiteral2(k) && k.value === "plugins";
2872
2500
  });
2873
2501
  if (!prop) return { changed: false };
2874
2502
  configObj.properties = [...configObj.properties ?? [], prop];
@@ -2878,12 +2506,12 @@ function ensurePluginsContainsJsxLoc(configObj) {
2878
2506
  if (!value) return { changed: false };
2879
2507
  if (value.type === "ArrayExpression") {
2880
2508
  if (pluginsHasJsxLoc(value)) return { changed: false };
2881
- const jsxLocCall2 = parseModule5("const __x = jsxLoc();").$ast.body?.[0]?.declarations?.[0]?.init;
2509
+ const jsxLocCall2 = parseModule4("const __x = jsxLoc();").$ast.body?.[0]?.declarations?.[0]?.init;
2882
2510
  if (!jsxLocCall2) return { changed: false };
2883
2511
  value.elements.push(jsxLocCall2);
2884
2512
  return { changed: true };
2885
2513
  }
2886
- const jsxLocCall = parseModule5("const __x = jsxLoc();").$ast.body?.[0]?.declarations?.[0]?.init;
2514
+ const jsxLocCall = parseModule4("const __x = jsxLoc();").$ast.body?.[0]?.declarations?.[0]?.init;
2887
2515
  if (!jsxLocCall) return { changed: false };
2888
2516
  const spread = { type: "SpreadElement", argument: value };
2889
2517
  pluginsProp.value = { type: "ArrayExpression", elements: [spread, jsxLocCall] };
@@ -2893,11 +2521,11 @@ async function installViteJsxLocPlugin(opts) {
2893
2521
  const configPath = findViteConfigFile2(opts.projectPath);
2894
2522
  if (!configPath) return { configFile: null, modified: false, modifiedFiles: [] };
2895
2523
  const configFilename = getViteConfigFilename(configPath);
2896
- const original = readFileSync8(configPath, "utf-8");
2524
+ const original = readFileSync7(configPath, "utf-8");
2897
2525
  const isCjs = configPath.endsWith(".cjs");
2898
2526
  let mod;
2899
2527
  try {
2900
- mod = parseModule5(original);
2528
+ mod = parseModule4(original);
2901
2529
  } catch {
2902
2530
  return { configFile: configFilename, modified: false, modifiedFiles: [] };
2903
2531
  }
@@ -2913,18 +2541,61 @@ async function installViteJsxLocPlugin(opts) {
2913
2541
  }
2914
2542
  const pluginsRes = ensurePluginsContainsJsxLoc(found.objExpr);
2915
2543
  if (pluginsRes.changed) changed = true;
2916
- const updated = changed ? generateCode4(mod).code : original;
2544
+ const updated = changed ? generateCode3(mod).code : original;
2917
2545
  if (updated !== original) {
2918
- writeFileSync4(configPath, updated, "utf-8");
2546
+ writeFileSync3(configPath, updated, "utf-8");
2919
2547
  return { configFile: configFilename, modified: true, modifiedFiles: [configPath] };
2920
2548
  }
2921
2549
  return { configFile: configFilename, modified: false, modifiedFiles: [] };
2922
2550
  }
2551
+ async function uninstallViteJsxLocPlugin(options) {
2552
+ const { projectPath } = options;
2553
+ const configPath = findViteConfigFile2(projectPath);
2554
+ if (!configPath) {
2555
+ return {
2556
+ success: true,
2557
+ modifiedFiles: []
2558
+ };
2559
+ }
2560
+ try {
2561
+ const original = readFileSync7(configPath, "utf-8");
2562
+ let updated = original.replace(
2563
+ /^import\s+\{[^}]*jsxLoc[^}]*\}\s+from\s+["']jsx-loc-plugin\/vite["'];?\s*$/gm,
2564
+ ""
2565
+ );
2566
+ updated = updated.replace(
2567
+ /^import\s+jsxLoc\s+from\s+["']jsx-loc-plugin\/vite["'];?\s*$/gm,
2568
+ ""
2569
+ );
2570
+ updated = updated.replace(
2571
+ /^const\s+\{[^}]*jsxLoc[^}]*\}\s*=\s*require\s*\(\s*["']jsx-loc-plugin\/vite["']\s*\)\s*;?\s*$/gm,
2572
+ ""
2573
+ );
2574
+ updated = updated.replace(/jsxLoc\s*\(\s*\)\s*,?\s*/g, "");
2575
+ updated = updated.replace(/\n{3,}/g, "\n\n");
2576
+ if (updated !== original) {
2577
+ writeFileSync3(configPath, updated, "utf-8");
2578
+ return {
2579
+ success: true,
2580
+ modifiedFiles: [configPath]
2581
+ };
2582
+ }
2583
+ return {
2584
+ success: true,
2585
+ modifiedFiles: []
2586
+ };
2587
+ } catch (error) {
2588
+ return {
2589
+ success: false,
2590
+ error: error instanceof Error ? error.message : String(error)
2591
+ };
2592
+ }
2593
+ }
2923
2594
 
2924
2595
  // src/utils/next-routes.ts
2925
- import { existsSync as existsSync9 } from "fs";
2596
+ import { existsSync as existsSync8 } from "fs";
2926
2597
  import { mkdir, writeFile } from "fs/promises";
2927
- import { join as join9 } from "path";
2598
+ import { join as join8 } from "path";
2928
2599
  var DEV_SOURCE_ROUTE_TS = `/**
2929
2600
  * Dev-only API route for fetching source files
2930
2601
  *
@@ -3380,40 +3051,56 @@ export async function GET(request: NextRequest) {
3380
3051
  }
3381
3052
  `;
3382
3053
  async function writeRouteFile(absPath, relPath, content, opts) {
3383
- if (existsSync9(absPath) && !opts.force) return;
3054
+ if (existsSync8(absPath) && !opts.force) return;
3384
3055
  await writeFile(absPath, content, "utf-8");
3385
3056
  }
3386
3057
  async function installNextUILintRoutes(opts) {
3387
- const baseRel = join9(opts.appRoot, "api", ".uilint");
3388
- const baseAbs = join9(opts.projectPath, baseRel);
3389
- await mkdir(join9(baseAbs, "source"), { recursive: true });
3058
+ const baseRel = join8(opts.appRoot, "api", ".uilint");
3059
+ const baseAbs = join8(opts.projectPath, baseRel);
3060
+ await mkdir(join8(baseAbs, "source"), { recursive: true });
3390
3061
  await writeRouteFile(
3391
- join9(baseAbs, "source", "route.ts"),
3392
- join9(baseRel, "source", "route.ts"),
3062
+ join8(baseAbs, "source", "route.ts"),
3063
+ join8(baseRel, "source", "route.ts"),
3393
3064
  DEV_SOURCE_ROUTE_TS,
3394
3065
  opts
3395
3066
  );
3396
- await mkdir(join9(baseAbs, "screenshots"), { recursive: true });
3067
+ await mkdir(join8(baseAbs, "screenshots"), { recursive: true });
3397
3068
  await writeRouteFile(
3398
- join9(baseAbs, "screenshots", "route.ts"),
3399
- join9(baseRel, "screenshots", "route.ts"),
3069
+ join8(baseAbs, "screenshots", "route.ts"),
3070
+ join8(baseRel, "screenshots", "route.ts"),
3400
3071
  SCREENSHOT_ROUTE_TS,
3401
3072
  opts
3402
3073
  );
3403
3074
  }
3075
+ async function uninstallNextUILintRoutes(options) {
3076
+ const { projectPath, appRoot } = options;
3077
+ const { rm } = await import("fs/promises");
3078
+ const baseAbs = join8(projectPath, appRoot, "api", ".uilint");
3079
+ try {
3080
+ if (existsSync8(baseAbs)) {
3081
+ await rm(baseAbs, { recursive: true, force: true });
3082
+ }
3083
+ return { success: true };
3084
+ } catch (error) {
3085
+ return {
3086
+ success: false,
3087
+ error: error instanceof Error ? error.message : String(error)
3088
+ };
3089
+ }
3090
+ }
3404
3091
 
3405
3092
  // src/utils/prettier.ts
3406
- import { existsSync as existsSync10, utimesSync } from "fs";
3093
+ import { existsSync as existsSync9, utimesSync } from "fs";
3407
3094
  import { spawn } from "child_process";
3408
- import { join as join10, dirname as dirname4 } from "path";
3095
+ import { join as join9, dirname as dirname3 } from "path";
3409
3096
  function getPrettierPath(projectPath) {
3410
- const localPath = join10(projectPath, "node_modules", ".bin", "prettier");
3411
- if (existsSync10(localPath)) return localPath;
3097
+ const localPath = join9(projectPath, "node_modules", ".bin", "prettier");
3098
+ if (existsSync9(localPath)) return localPath;
3412
3099
  let dir = projectPath;
3413
3100
  for (let i = 0; i < 10; i++) {
3414
- const binPath = join10(dir, "node_modules", ".bin", "prettier");
3415
- if (existsSync10(binPath)) return binPath;
3416
- const parent = dirname4(dir);
3101
+ const binPath = join9(dir, "node_modules", ".bin", "prettier");
3102
+ if (existsSync9(binPath)) return binPath;
3103
+ const parent = dirname3(dir);
3417
3104
  if (parent === dir) break;
3418
3105
  dir = parent;
3419
3106
  }
@@ -3544,7 +3231,7 @@ function touchFiles(filePaths) {
3544
3231
  const now = /* @__PURE__ */ new Date();
3545
3232
  for (const filePath of filePaths) {
3546
3233
  try {
3547
- if (existsSync10(filePath)) {
3234
+ if (existsSync9(filePath)) {
3548
3235
  utimesSync(filePath, now, now);
3549
3236
  }
3550
3237
  } catch {
@@ -3565,7 +3252,7 @@ async function executeAction(action, options) {
3565
3252
  wouldDo: `Create directory: ${action.path}`
3566
3253
  };
3567
3254
  }
3568
- if (!existsSync11(action.path)) {
3255
+ if (!existsSync10(action.path)) {
3569
3256
  mkdirSync(action.path, { recursive: true });
3570
3257
  }
3571
3258
  return { action, success: true };
@@ -3578,11 +3265,11 @@ async function executeAction(action, options) {
3578
3265
  wouldDo: `Create file: ${action.path}${action.permissions ? ` (mode: ${action.permissions.toString(8)})` : ""}`
3579
3266
  };
3580
3267
  }
3581
- const dir = dirname5(action.path);
3582
- if (!existsSync11(dir)) {
3268
+ const dir = dirname4(action.path);
3269
+ if (!existsSync10(dir)) {
3583
3270
  mkdirSync(dir, { recursive: true });
3584
3271
  }
3585
- writeFileSync5(action.path, action.content, "utf-8");
3272
+ writeFileSync4(action.path, action.content, "utf-8");
3586
3273
  if (action.permissions) {
3587
3274
  chmodSync(action.path, action.permissions);
3588
3275
  }
@@ -3597,18 +3284,18 @@ async function executeAction(action, options) {
3597
3284
  };
3598
3285
  }
3599
3286
  let existing = {};
3600
- if (existsSync11(action.path)) {
3287
+ if (existsSync10(action.path)) {
3601
3288
  try {
3602
- existing = JSON.parse(readFileSync9(action.path, "utf-8"));
3289
+ existing = JSON.parse(readFileSync8(action.path, "utf-8"));
3603
3290
  } catch {
3604
3291
  }
3605
3292
  }
3606
3293
  const merged = deepMerge(existing, action.merge);
3607
- const dir = dirname5(action.path);
3608
- if (!existsSync11(dir)) {
3294
+ const dir = dirname4(action.path);
3295
+ if (!existsSync10(dir)) {
3609
3296
  mkdirSync(dir, { recursive: true });
3610
3297
  }
3611
- writeFileSync5(action.path, JSON.stringify(merged, null, 2), "utf-8");
3298
+ writeFileSync4(action.path, JSON.stringify(merged, null, 2), "utf-8");
3612
3299
  return { action, success: true };
3613
3300
  }
3614
3301
  case "delete_file": {
@@ -3619,7 +3306,7 @@ async function executeAction(action, options) {
3619
3306
  wouldDo: `Delete file: ${action.path}`
3620
3307
  };
3621
3308
  }
3622
- if (existsSync11(action.path)) {
3309
+ if (existsSync10(action.path)) {
3623
3310
  unlinkSync(action.path);
3624
3311
  }
3625
3312
  return { action, success: true };
@@ -3632,12 +3319,12 @@ async function executeAction(action, options) {
3632
3319
  wouldDo: `Append to file: ${action.path}`
3633
3320
  };
3634
3321
  }
3635
- if (existsSync11(action.path)) {
3636
- const content = readFileSync9(action.path, "utf-8");
3322
+ if (existsSync10(action.path)) {
3323
+ const content = readFileSync8(action.path, "utf-8");
3637
3324
  if (action.ifNotContains && content.includes(action.ifNotContains)) {
3638
3325
  return { action, success: true };
3639
3326
  }
3640
- writeFileSync5(action.path, content + action.content, "utf-8");
3327
+ writeFileSync4(action.path, content + action.content, "utf-8");
3641
3328
  }
3642
3329
  return { action, success: true };
3643
3330
  }
@@ -3656,6 +3343,25 @@ async function executeAction(action, options) {
3656
3343
  case "install_next_routes": {
3657
3344
  return await executeInstallNextRoutes(action, options);
3658
3345
  }
3346
+ // Uninstall actions
3347
+ case "remove_eslint": {
3348
+ return await executeRemoveEslint(action, options);
3349
+ }
3350
+ case "remove_react": {
3351
+ return await executeRemoveReact(action, options);
3352
+ }
3353
+ case "remove_next_config": {
3354
+ return await executeRemoveNextConfig(action, options);
3355
+ }
3356
+ case "remove_vite_config": {
3357
+ return await executeRemoveViteConfig(action, options);
3358
+ }
3359
+ case "remove_next_routes": {
3360
+ return await executeRemoveNextRoutes(action, options);
3361
+ }
3362
+ case "remove_directory": {
3363
+ return await executeRemoveDirectory(action, options);
3364
+ }
3659
3365
  default: {
3660
3366
  const _exhaustive = action;
3661
3367
  return {
@@ -3781,6 +3487,117 @@ async function executeInstallNextRoutes(action, options) {
3781
3487
  });
3782
3488
  return { action, success: true };
3783
3489
  }
3490
+ async function executeRemoveEslint(action, options) {
3491
+ const { dryRun = false } = options;
3492
+ if (dryRun) {
3493
+ return {
3494
+ action,
3495
+ success: true,
3496
+ wouldDo: `Remove uilint ESLint rules from: ${action.configPath}`
3497
+ };
3498
+ }
3499
+ const result = await uninstallEslintPlugin({
3500
+ projectPath: action.packagePath
3501
+ });
3502
+ return {
3503
+ action,
3504
+ success: result.success,
3505
+ error: result.error,
3506
+ modifiedFiles: result.modifiedFiles
3507
+ };
3508
+ }
3509
+ async function executeRemoveReact(action, options) {
3510
+ const { dryRun = false } = options;
3511
+ if (dryRun) {
3512
+ return {
3513
+ action,
3514
+ success: true,
3515
+ wouldDo: `Remove <uilint-devtools /> from: ${action.projectPath}`
3516
+ };
3517
+ }
3518
+ const result = await uninstallReactUILintOverlay({
3519
+ projectPath: action.projectPath,
3520
+ appRoot: action.appRoot,
3521
+ mode: action.mode
3522
+ });
3523
+ return {
3524
+ action,
3525
+ success: result.success,
3526
+ error: result.error,
3527
+ modifiedFiles: result.modifiedFiles
3528
+ };
3529
+ }
3530
+ async function executeRemoveNextConfig(action, options) {
3531
+ const { dryRun = false } = options;
3532
+ if (dryRun) {
3533
+ return {
3534
+ action,
3535
+ success: true,
3536
+ wouldDo: `Remove jsx-loc-plugin from next.config: ${action.projectPath}`
3537
+ };
3538
+ }
3539
+ const result = await uninstallJsxLocPlugin({
3540
+ projectPath: action.projectPath
3541
+ });
3542
+ return {
3543
+ action,
3544
+ success: result.success,
3545
+ error: result.error,
3546
+ modifiedFiles: result.modifiedFiles
3547
+ };
3548
+ }
3549
+ async function executeRemoveViteConfig(action, options) {
3550
+ const { dryRun = false } = options;
3551
+ if (dryRun) {
3552
+ return {
3553
+ action,
3554
+ success: true,
3555
+ wouldDo: `Remove jsx-loc-plugin from vite.config: ${action.projectPath}`
3556
+ };
3557
+ }
3558
+ const result = await uninstallViteJsxLocPlugin({
3559
+ projectPath: action.projectPath
3560
+ });
3561
+ return {
3562
+ action,
3563
+ success: result.success,
3564
+ error: result.error,
3565
+ modifiedFiles: result.modifiedFiles
3566
+ };
3567
+ }
3568
+ async function executeRemoveNextRoutes(action, options) {
3569
+ const { dryRun = false } = options;
3570
+ if (dryRun) {
3571
+ return {
3572
+ action,
3573
+ success: true,
3574
+ wouldDo: `Remove Next.js API routes: ${action.projectPath}`
3575
+ };
3576
+ }
3577
+ const result = await uninstallNextUILintRoutes({
3578
+ projectPath: action.projectPath,
3579
+ appRoot: action.appRoot
3580
+ });
3581
+ return {
3582
+ action,
3583
+ success: result.success,
3584
+ error: result.error
3585
+ };
3586
+ }
3587
+ async function executeRemoveDirectory(action, options) {
3588
+ const { dryRun = false } = options;
3589
+ if (dryRun) {
3590
+ return {
3591
+ action,
3592
+ success: true,
3593
+ wouldDo: `Remove directory: ${action.path}`
3594
+ };
3595
+ }
3596
+ if (existsSync10(action.path)) {
3597
+ rmSync(action.path, { recursive: true, force: true });
3598
+ }
3599
+ return { action, success: true };
3600
+ }
3784
3601
  function deepMerge(target, source) {
3785
3602
  const result = { ...target };
3786
3603
  for (const key of Object.keys(source)) {
@@ -4013,7 +3830,7 @@ async function execute(plan, options = {}) {
4013
3830
  import { ruleRegistry as ruleRegistry3 } from "uilint-eslint";
4014
3831
 
4015
3832
  // src/commands/install/installers/genstyleguide.ts
4016
- import { join as join11 } from "path";
3833
+ import { join as join10 } from "path";
4017
3834
  var genstyleguideInstaller = {
4018
3835
  id: "genstyleguide",
4019
3836
  name: "/genstyleguide command",
@@ -4023,7 +3840,7 @@ var genstyleguideInstaller = {
4023
3840
  return true;
4024
3841
  },
4025
3842
  getTargets(project) {
4026
- const commandPath = join11(project.cursorDir.path, "commands", "genstyleguide.md");
3843
+ const commandPath = join10(project.cursorDir.path, "commands", "genstyleguide.md");
4027
3844
  const isInstalled = project.commands.genstyleguide;
4028
3845
  return [
4029
3846
  {
@@ -4036,7 +3853,7 @@ var genstyleguideInstaller = {
4036
3853
  },
4037
3854
  plan(targets, config, project) {
4038
3855
  const actions = [];
4039
- const commandsDir = join11(project.cursorDir.path, "commands");
3856
+ const commandsDir = join10(project.cursorDir.path, "commands");
4040
3857
  if (!project.cursorDir.exists) {
4041
3858
  actions.push({
4042
3859
  type: "create_directory",
@@ -4049,7 +3866,7 @@ var genstyleguideInstaller = {
4049
3866
  });
4050
3867
  actions.push({
4051
3868
  type: "create_file",
4052
- path: join11(commandsDir, "genstyleguide.md"),
3869
+ path: join10(commandsDir, "genstyleguide.md"),
4053
3870
  content: GENSTYLEGUIDE_COMMAND_MD
4054
3871
  });
4055
3872
  return {
@@ -4075,12 +3892,21 @@ var genstyleguideInstaller = {
4075
3892
  type: "complete",
4076
3893
  message: "Installed /genstyleguide command"
4077
3894
  };
3895
+ },
3896
+ planUninstall(targets, project) {
3897
+ const actions = [];
3898
+ const commandPath = join10(project.cursorDir.path, "commands", "genstyleguide.md");
3899
+ actions.push({
3900
+ type: "delete_file",
3901
+ path: commandPath
3902
+ });
3903
+ return { actions };
4078
3904
  }
4079
3905
  };
4080
3906
 
4081
3907
  // src/commands/install/installers/skill.ts
4082
- import { existsSync as existsSync12 } from "fs";
4083
- import { join as join12 } from "path";
3908
+ import { existsSync as existsSync11 } from "fs";
3909
+ import { join as join11 } from "path";
4084
3910
  var skillInstaller = {
4085
3911
  id: "skill",
4086
3912
  name: "UI Consistency Agent skill",
@@ -4090,9 +3916,9 @@ var skillInstaller = {
4090
3916
  return true;
4091
3917
  },
4092
3918
  getTargets(project) {
4093
- const skillsDir = join12(project.cursorDir.path, "skills", "ui-consistency-enforcer");
4094
- const skillMdPath = join12(skillsDir, "SKILL.md");
4095
- const isInstalled = existsSync12(skillMdPath);
3919
+ const skillsDir = join11(project.cursorDir.path, "skills", "ui-consistency-enforcer");
3920
+ const skillMdPath = join11(skillsDir, "SKILL.md");
3921
+ const isInstalled = existsSync11(skillMdPath);
4096
3922
  return [
4097
3923
  {
4098
3924
  id: "ui-consistency-skill",
@@ -4111,21 +3937,21 @@ var skillInstaller = {
4111
3937
  path: project.cursorDir.path
4112
3938
  });
4113
3939
  }
4114
- const skillsDir = join12(project.cursorDir.path, "skills");
3940
+ const skillsDir = join11(project.cursorDir.path, "skills");
4115
3941
  actions.push({
4116
3942
  type: "create_directory",
4117
3943
  path: skillsDir
4118
3944
  });
4119
3945
  try {
4120
3946
  const skill = loadSkill("ui-consistency-enforcer");
4121
- const skillDir = join12(skillsDir, skill.name);
3947
+ const skillDir = join11(skillsDir, skill.name);
4122
3948
  actions.push({
4123
3949
  type: "create_directory",
4124
3950
  path: skillDir
4125
3951
  });
4126
3952
  for (const file of skill.files) {
4127
- const filePath = join12(skillDir, file.relativePath);
4128
- const fileDir = join12(
3953
+ const filePath = join11(skillDir, file.relativePath);
3954
+ const fileDir = join11(
4129
3955
  skillDir,
4130
3956
  file.relativePath.split("/").slice(0, -1).join("/")
4131
3957
  );
@@ -4178,11 +4004,20 @@ var skillInstaller = {
4178
4004
  error: error instanceof Error ? error.message : String(error)
4179
4005
  };
4180
4006
  }
4007
+ },
4008
+ planUninstall(targets, project) {
4009
+ const actions = [];
4010
+ const skillDir = join11(project.cursorDir.path, "skills", "ui-consistency-enforcer");
4011
+ actions.push({
4012
+ type: "remove_directory",
4013
+ path: skillDir
4014
+ });
4015
+ return { actions };
4181
4016
  }
4182
4017
  };
4183
4018
 
4184
4019
  // src/commands/install/installers/eslint.ts
4185
- import { join as join13 } from "path";
4020
+ import { join as join12 } from "path";
4186
4021
  import { ruleRegistry as ruleRegistry2, getRulesByCategory as getRulesByCategory2 } from "uilint-eslint";
4187
4022
  function getUpgradeInfo(configuredRuleIds) {
4188
4023
  const configuredSet = new Set(configuredRuleIds);
@@ -4358,7 +4193,7 @@ var eslintInstaller = {
4358
4193
  for (const target of targets) {
4359
4194
  const pkgInfo = project.packages.find((p) => p.path === target.path);
4360
4195
  if (!pkgInfo || !pkgInfo.eslintConfigPath) continue;
4361
- const rulesDir = join13(target.path, ".uilint", "rules");
4196
+ const rulesDir = join12(target.path, ".uilint", "rules");
4362
4197
  actions.push({
4363
4198
  type: "create_directory",
4364
4199
  path: rulesDir
@@ -4376,7 +4211,7 @@ var eslintInstaller = {
4376
4211
  hasExistingRules: pkgInfo.hasUilintRules
4377
4212
  });
4378
4213
  }
4379
- const gitignorePath = join13(project.workspaceRoot, ".gitignore");
4214
+ const gitignorePath = join12(project.workspaceRoot, ".gitignore");
4380
4215
  actions.push({
4381
4216
  type: "append_to_file",
4382
4217
  path: gitignorePath,
@@ -4407,6 +4242,24 @@ var eslintInstaller = {
4407
4242
  type: "complete",
4408
4243
  message: `ESLint plugin installed in ${targets.length} package(s)`
4409
4244
  };
4245
+ },
4246
+ planUninstall(targets, project) {
4247
+ const actions = [];
4248
+ for (const target of targets) {
4249
+ const pkgInfo = project.packages.find((p) => p.path === target.path);
4250
+ if (!pkgInfo || !pkgInfo.eslintConfigPath) continue;
4251
+ actions.push({
4252
+ type: "remove_eslint",
4253
+ packagePath: target.path,
4254
+ configPath: pkgInfo.eslintConfigPath
4255
+ });
4256
+ const rulesDir = join12(target.path, ".uilint", "rules");
4257
+ actions.push({
4258
+ type: "remove_directory",
4259
+ path: rulesDir
4260
+ });
4261
+ }
4262
+ return { actions };
4410
4263
  }
4411
4264
  };
4412
4265
 
@@ -4425,8 +4278,7 @@ var viteOverlayInstaller = {
4425
4278
  label: app.projectPath.split("/").pop() || app.projectPath,
4426
4279
  path: app.projectPath,
4427
4280
  hint: "React + Vite",
4428
- isInstalled: false
4429
- // TODO: Detect if already installed
4281
+ isInstalled: app.hasUilintOverlay
4430
4282
  }));
4431
4283
  },
4432
4284
  plan(targets, config, project) {
@@ -4485,6 +4337,27 @@ var viteOverlayInstaller = {
4485
4337
  type: "complete",
4486
4338
  message: "Vite overlay installed"
4487
4339
  };
4340
+ },
4341
+ planUninstall(targets, project) {
4342
+ const actions = [];
4343
+ if (targets.length === 0) return { actions };
4344
+ const target = targets[0];
4345
+ const appInfo = project.viteApps.find(
4346
+ (app) => app.projectPath === target.path
4347
+ );
4348
+ if (!appInfo) return { actions };
4349
+ const { projectPath, detection } = appInfo;
4350
+ actions.push({
4351
+ type: "remove_react",
4352
+ projectPath,
4353
+ appRoot: detection.entryRoot,
4354
+ mode: "vite"
4355
+ });
4356
+ actions.push({
4357
+ type: "remove_vite_config",
4358
+ projectPath
4359
+ });
4360
+ return { actions };
4488
4361
  }
4489
4362
  };
4490
4363
 
@@ -4566,23 +4439,41 @@ async function installUI(options = {}, executeOptions = {}) {
4566
4439
  InstallApp,
4567
4440
  {
4568
4441
  projectPromise,
4569
- onComplete: async (selections, eslintRules, injectionPointConfig) => {
4442
+ onComplete: async (selections, eslintRules, injectionPointConfig, uninstallSelections) => {
4570
4443
  const project = await projectPromise;
4571
4444
  const choices = selectionsToUserChoices(selections, project, eslintRules, injectionPointConfig);
4572
- if (choices.items.length === 0) {
4573
- console.log("\nNo items selected for installation");
4445
+ const hasInstalls = choices.items.length > 0;
4446
+ const hasUninstalls = uninstallSelections && uninstallSelections.length > 0;
4447
+ if (!hasInstalls && !hasUninstalls) {
4448
+ console.log("\nNo changes selected");
4574
4449
  process.exit(0);
4575
4450
  }
4576
4451
  const { createPlan } = await import("./plan-SIXVCXCK.js");
4577
4452
  const plan = createPlan(project, choices, { force: options.force });
4453
+ if (hasUninstalls && uninstallSelections) {
4454
+ for (const selection of uninstallSelections) {
4455
+ if (!selection.selected || selection.targets.length === 0) continue;
4456
+ const { installer, targets } = selection;
4457
+ if (installer.planUninstall) {
4458
+ const uninstallPlan = installer.planUninstall(targets, project);
4459
+ plan.actions = [...uninstallPlan.actions, ...plan.actions];
4460
+ }
4461
+ }
4462
+ }
4578
4463
  const result = await execute(plan, {
4579
4464
  ...executeOptions,
4580
4465
  projectPath: project.projectPath
4581
4466
  });
4582
4467
  if (result.success) {
4583
- console.log("\n\u2713 Installation completed successfully!");
4468
+ if (hasInstalls && hasUninstalls) {
4469
+ console.log("\n\u2713 Changes applied successfully!");
4470
+ } else if (hasUninstalls) {
4471
+ console.log("\n\u2713 Uninstallation completed successfully!");
4472
+ } else {
4473
+ console.log("\n\u2713 Installation completed successfully!");
4474
+ }
4584
4475
  } else {
4585
- console.log("\n\u26A0 Installation completed with errors");
4476
+ console.log("\n\u26A0 Operation completed with errors");
4586
4477
  }
4587
4478
  process.exit(result.success ? 0 : 1);
4588
4479
  },
@@ -4598,4 +4489,4 @@ async function installUI(options = {}, executeOptions = {}) {
4598
4489
  export {
4599
4490
  installUI
4600
4491
  };
4601
- //# sourceMappingURL=install-ui-2HMR6TZT.js.map
4492
+ //# sourceMappingURL=install-ui-TXV7A34M.js.map