uilint 0.2.20 → 0.2.22

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.
@@ -137,8 +137,11 @@ function ProjectSelector({
137
137
  // src/commands/install/components/MultiSelect.tsx
138
138
  import { useState as useState3, useCallback } from "react";
139
139
  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 }) {
140
+ import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
141
+ function StatusIndicator({ status, isSelected, isMarkedForUninstall }) {
142
+ if (isMarkedForUninstall) {
143
+ return /* @__PURE__ */ jsx3(Text3, { color: "red", children: "\u2717" });
144
+ }
142
145
  if (status === "installed") {
143
146
  return /* @__PURE__ */ jsx3(Text3, { color: "green", children: "\u2713" });
144
147
  }
@@ -153,7 +156,10 @@ function StatusIndicator({ status, isSelected }) {
153
156
  }
154
157
  return /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "\u25CB" });
155
158
  }
156
- function StatusLabel({ status }) {
159
+ function StatusLabel({ status, isMarkedForUninstall }) {
160
+ if (isMarkedForUninstall) {
161
+ return /* @__PURE__ */ jsx3(Text3, { color: "red", dimColor: true, children: "uninstall" });
162
+ }
157
163
  if (status === "installed") {
158
164
  return /* @__PURE__ */ jsx3(Text3, { color: "green", dimColor: true, children: "installed" });
159
165
  }
@@ -177,6 +183,7 @@ function ConfigSelector({
177
183
  items.filter((item) => item.status !== "installed" && !item.disabled).map((item) => item.id)
178
184
  );
179
185
  });
186
+ const [markedForUninstall, setMarkedForUninstall] = useState3(/* @__PURE__ */ new Set());
180
187
  const categories = Array.from(new Set(items.map((item) => item.category)));
181
188
  const itemsByCategory = /* @__PURE__ */ new Map();
182
189
  for (const cat of categories) {
@@ -185,8 +192,19 @@ function ConfigSelector({
185
192
  const flatItems = items;
186
193
  const handleToggle = useCallback(() => {
187
194
  const item = flatItems[cursor];
188
- const canToggle = item && !item.disabled && item.status !== "installed";
189
- if (!canToggle) return;
195
+ if (!item || item.disabled) return;
196
+ if (item.status === "installed") {
197
+ setMarkedForUninstall((prev) => {
198
+ const next = new Set(prev);
199
+ if (next.has(item.id)) {
200
+ next.delete(item.id);
201
+ } else {
202
+ next.add(item.id);
203
+ }
204
+ return next;
205
+ });
206
+ return;
207
+ }
190
208
  setSelected((prev) => {
191
209
  const next = new Set(prev);
192
210
  if (next.has(item.id)) {
@@ -205,7 +223,7 @@ function ConfigSelector({
205
223
  } else if (input === " ") {
206
224
  handleToggle();
207
225
  } else if (key.return) {
208
- onSubmit(Array.from(selected));
226
+ onSubmit(Array.from(selected), Array.from(markedForUninstall));
209
227
  } else if (input === "q" || key.escape) {
210
228
  onCancel?.();
211
229
  exit();
@@ -215,8 +233,10 @@ function ConfigSelector({
215
233
  items.filter((item) => item.status !== "installed" && !item.disabled).map((item) => item.id)
216
234
  )
217
235
  );
236
+ setMarkedForUninstall(/* @__PURE__ */ new Set());
218
237
  } else if (input === "n") {
219
238
  setSelected(/* @__PURE__ */ new Set());
239
+ setMarkedForUninstall(/* @__PURE__ */ new Set());
220
240
  }
221
241
  });
222
242
  let globalIndex = 0;
@@ -234,20 +254,21 @@ function ConfigSelector({
234
254
  const itemIndex = globalIndex++;
235
255
  const isCursor = itemIndex === cursor;
236
256
  const isItemSelected = selected.has(item.id);
237
- const isDisabled = item.disabled || item.status === "installed";
257
+ const isItemMarkedForUninstall = markedForUninstall.has(item.id);
258
+ const isDisabled = item.disabled === true;
238
259
  return /* @__PURE__ */ jsxs2(Box2, { paddingLeft: 2, children: [
239
260
  /* @__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 }) }),
261
+ /* @__PURE__ */ jsx3(Box2, { width: 2, children: /* @__PURE__ */ jsx3(StatusIndicator, { status: item.status, isSelected: isItemSelected, isMarkedForUninstall: isItemMarkedForUninstall }) }),
241
262
  /* @__PURE__ */ jsx3(Box2, { width: 28, children: /* @__PURE__ */ jsx3(
242
263
  Text3,
243
264
  {
244
- color: isDisabled ? void 0 : isCursor ? "cyan" : void 0,
245
- dimColor: isDisabled,
265
+ color: isItemMarkedForUninstall ? "red" : isDisabled ? void 0 : isCursor ? "cyan" : void 0,
266
+ dimColor: isDisabled && !isItemMarkedForUninstall,
246
267
  children: item.label
247
268
  }
248
269
  ) }),
249
270
  /* @__PURE__ */ jsx3(Box2, { width: 20, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: item.hint || "" }) }),
250
- /* @__PURE__ */ jsx3(StatusLabel, { status: item.status })
271
+ /* @__PURE__ */ jsx3(StatusLabel, { status: item.status, isMarkedForUninstall: isItemMarkedForUninstall })
251
272
  ] }, item.id);
252
273
  })
253
274
  ] }, category);
@@ -273,10 +294,11 @@ function ConfigSelector({
273
294
  ] }) }),
274
295
  /* @__PURE__ */ jsx3(Box2, { marginTop: 1, children: /* @__PURE__ */ jsxs2(Text3, { children: [
275
296
  /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: selected.size }),
276
- /* @__PURE__ */ jsxs2(Text3, { dimColor: true, children: [
277
- " item",
278
- selected.size !== 1 ? "s" : "",
279
- " selected"
297
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: " to install" }),
298
+ markedForUninstall.size > 0 && /* @__PURE__ */ jsxs2(Fragment, { children: [
299
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: ", " }),
300
+ /* @__PURE__ */ jsx3(Text3, { color: "red", children: markedForUninstall.size }),
301
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: " to uninstall" })
280
302
  ] })
281
303
  ] }) })
282
304
  ] });
@@ -892,6 +914,32 @@ var nextOverlayInstaller = {
892
914
  type: "complete",
893
915
  message: "Next.js overlay installed"
894
916
  };
917
+ },
918
+ planUninstall(targets, project) {
919
+ const actions = [];
920
+ if (targets.length === 0) return { actions };
921
+ const target = targets[0];
922
+ const appInfo = project.nextApps.find(
923
+ (app) => app.projectPath === target.path
924
+ );
925
+ if (!appInfo) return { actions };
926
+ const { projectPath, detection } = appInfo;
927
+ actions.push({
928
+ type: "remove_react",
929
+ projectPath,
930
+ appRoot: detection.appRoot,
931
+ mode: "next"
932
+ });
933
+ actions.push({
934
+ type: "remove_next_config",
935
+ projectPath
936
+ });
937
+ actions.push({
938
+ type: "remove_next_routes",
939
+ projectPath,
940
+ appRoot: detection.appRoot
941
+ });
942
+ return { actions };
895
943
  }
896
944
  };
897
945
 
@@ -1029,6 +1077,7 @@ function InstallApp({
1029
1077
  const [selections, setSelections] = useState6([]);
1030
1078
  const [configItems, setConfigItems] = useState6([]);
1031
1079
  const [selectedFeatureIds, setSelectedFeatureIds] = useState6([]);
1080
+ const [uninstallFeatureIds, setUninstallFeatureIds] = useState6([]);
1032
1081
  const [error, setError] = useState6(null);
1033
1082
  const [injectionPoints, setInjectionPoints] = useState6([]);
1034
1083
  const [selectedInjectionPoint, setSelectedInjectionPoint] = useState6(void 0);
@@ -1086,8 +1135,9 @@ function InstallApp({
1086
1135
  setPhase("select-project");
1087
1136
  }
1088
1137
  };
1089
- const handleFeatureSubmit = (selectedIds) => {
1138
+ const handleFeatureSubmit = (selectedIds, uninstallIds) => {
1090
1139
  setSelectedFeatureIds(selectedIds);
1140
+ setUninstallFeatureIds(uninstallIds);
1091
1141
  const nextSelected = selectedIds.some((id) => id.startsWith("next:"));
1092
1142
  if (nextSelected && project && selectedProject) {
1093
1143
  const appInfo = project.nextApps.find(
@@ -1143,6 +1193,7 @@ function InstallApp({
1143
1193
  };
1144
1194
  const finishInstallation = (selectedIds, eslintRules, injectionConfig) => {
1145
1195
  const selectedSet = new Set(selectedIds);
1196
+ const uninstallSet = new Set(uninstallFeatureIds);
1146
1197
  const updatedSelections = selections.map((sel) => {
1147
1198
  const selectedTargets = sel.targets.filter(
1148
1199
  (t) => selectedSet.has(`${sel.installer.id}:${t.id}`)
@@ -1153,8 +1204,18 @@ function InstallApp({
1153
1204
  selected: selectedTargets.length > 0
1154
1205
  };
1155
1206
  });
1207
+ const uninstallSelections = selections.map((sel) => {
1208
+ const uninstallTargets = sel.targets.filter(
1209
+ (t) => uninstallSet.has(`${sel.installer.id}:${t.id}`)
1210
+ );
1211
+ return {
1212
+ ...sel,
1213
+ targets: uninstallTargets,
1214
+ selected: uninstallTargets.length > 0
1215
+ };
1216
+ }).filter((sel) => sel.selected);
1156
1217
  setSelections(updatedSelections);
1157
- onComplete(updatedSelections, eslintRules, injectionConfig);
1218
+ onComplete(updatedSelections, eslintRules, injectionConfig, uninstallSelections.length > 0 ? uninstallSelections : void 0);
1158
1219
  };
1159
1220
  const handleCancel = () => {
1160
1221
  exit();
@@ -2038,6 +2099,65 @@ async function installEslintPlugin(opts) {
2038
2099
  configured: getUilintEslintConfigInfoFromSource(updated).configured
2039
2100
  };
2040
2101
  }
2102
+ async function uninstallEslintPlugin(options) {
2103
+ const { projectPath } = options;
2104
+ const configPath = findEslintConfigFile(projectPath);
2105
+ if (!configPath) {
2106
+ return {
2107
+ success: true,
2108
+ // Nothing to uninstall
2109
+ modifiedFiles: []
2110
+ };
2111
+ }
2112
+ try {
2113
+ const original = readFileSync4(configPath, "utf-8");
2114
+ let updated = original.replace(
2115
+ /^import\s+\{[^}]*\}\s+from\s+["'][^"']*\.uilint\/rules[^"']*["'];?\s*$/gm,
2116
+ ""
2117
+ );
2118
+ updated = updated.replace(
2119
+ /^import\s+\w+\s+from\s+["'][^"']*\.uilint\/rules[^"']*["'];?\s*$/gm,
2120
+ ""
2121
+ );
2122
+ updated = updated.replace(
2123
+ /^import\s+\{[^}]*\}\s+from\s+["']uilint-eslint["'];?\s*$/gm,
2124
+ ""
2125
+ );
2126
+ updated = updated.replace(
2127
+ /^const\s+\{[^}]*createRule[^}]*\}\s*=\s*require\s*\(\s*["']uilint-eslint["']\s*\)\s*;?\s*$/gm,
2128
+ ""
2129
+ );
2130
+ updated = updated.replace(
2131
+ /["']uilint\/[^"']+["']\s*:\s*["'][^"']+["']\s*,?\s*/g,
2132
+ ""
2133
+ );
2134
+ updated = updated.replace(
2135
+ /["']uilint\/[^"']+["']\s*:\s*\[[^\]]*\]\s*,?\s*/g,
2136
+ ""
2137
+ );
2138
+ updated = updated.replace(
2139
+ /\{\s*plugins:\s*\{\s*uilint:\s*\{[^}]*\}[^}]*\}[^}]*rules:\s*\{[^}]*\}[^}]*\}\s*,?\s*/gs,
2140
+ ""
2141
+ );
2142
+ updated = updated.replace(/\n{3,}/g, "\n\n");
2143
+ if (updated !== original) {
2144
+ writeFileSync(configPath, updated, "utf-8");
2145
+ return {
2146
+ success: true,
2147
+ modifiedFiles: [configPath]
2148
+ };
2149
+ }
2150
+ return {
2151
+ success: true,
2152
+ modifiedFiles: []
2153
+ };
2154
+ } catch (error) {
2155
+ return {
2156
+ success: false,
2157
+ error: error instanceof Error ? error.message : String(error)
2158
+ };
2159
+ }
2160
+ }
2041
2161
 
2042
2162
  // src/commands/install/analyze.ts
2043
2163
  async function analyze(projectPath = process.cwd()) {
@@ -2127,7 +2247,8 @@ import {
2127
2247
  writeFileSync as writeFileSync5,
2128
2248
  readFileSync as readFileSync9,
2129
2249
  unlinkSync,
2130
- chmodSync
2250
+ chmodSync,
2251
+ rmSync
2131
2252
  } from "fs";
2132
2253
  import { dirname as dirname5 } from "path";
2133
2254
 
@@ -2458,11 +2579,17 @@ async function installReactUILintOverlay(opts) {
2458
2579
  opts.projectPath,
2459
2580
  opts.appRoot
2460
2581
  );
2582
+ const modifiedFiles = [];
2583
+ if (result.modified) {
2584
+ modifiedFiles.push(join6(opts.projectPath, result.providersFile));
2585
+ modifiedFiles.push(join6(opts.projectPath, result.layoutFile));
2586
+ }
2461
2587
  return {
2462
2588
  targetFile: result.providersFile,
2463
2589
  modified: result.modified,
2464
2590
  createdFile: result.providersFile,
2465
- layoutModified: result.layoutFile
2591
+ layoutModified: result.layoutFile,
2592
+ modifiedFiles
2466
2593
  };
2467
2594
  }
2468
2595
  if (opts.targetFile) {
@@ -2488,7 +2615,8 @@ async function installReactUILintOverlay(opts) {
2488
2615
  return {
2489
2616
  targetFile: relTarget,
2490
2617
  modified: false,
2491
- alreadyConfigured: true
2618
+ alreadyConfigured: true,
2619
+ modifiedFiles: []
2492
2620
  };
2493
2621
  }
2494
2622
  let changed2 = false;
@@ -2504,7 +2632,8 @@ async function installReactUILintOverlay(opts) {
2504
2632
  return {
2505
2633
  targetFile: relTarget,
2506
2634
  modified: modified2,
2507
- alreadyConfigured: false
2635
+ alreadyConfigured: false,
2636
+ modifiedFiles: modified2 ? [absTarget2] : []
2508
2637
  };
2509
2638
  }
2510
2639
  const candidates = getDefaultCandidates(opts.projectPath, opts.appRoot);
@@ -2547,7 +2676,45 @@ async function installReactUILintOverlay(opts) {
2547
2676
  return {
2548
2677
  targetFile: chosen,
2549
2678
  modified,
2550
- alreadyConfigured: alreadyConfigured && !modified
2679
+ alreadyConfigured: alreadyConfigured && !modified,
2680
+ modifiedFiles: modified ? [absTarget] : []
2681
+ };
2682
+ }
2683
+ async function uninstallReactUILintOverlay(options) {
2684
+ const { projectPath, appRoot, mode = "next" } = options;
2685
+ const candidates = getDefaultCandidates(projectPath, appRoot);
2686
+ const modifiedFiles = [];
2687
+ for (const candidate of candidates) {
2688
+ const absPath = join6(projectPath, candidate);
2689
+ if (!existsSync6(absPath)) continue;
2690
+ try {
2691
+ const original = readFileSync6(absPath, "utf-8");
2692
+ let updated = original.replace(
2693
+ /^import\s+["']uilint-react\/devtools["'];?\s*$/gm,
2694
+ ""
2695
+ );
2696
+ updated = updated.replace(
2697
+ /^import\s+\{[^}]*UILintProvider[^}]*\}\s+from\s+["']uilint-react["'];?\s*$/gm,
2698
+ ""
2699
+ );
2700
+ updated = updated.replace(/<uilint-devtools\s*\/>/g, "");
2701
+ updated = updated.replace(/<uilint-devtools><\/uilint-devtools>/g, "");
2702
+ updated = updated.replace(/<uilint-devtools\s*>\s*<\/uilint-devtools>/g, "");
2703
+ updated = updated.replace(
2704
+ /<UILintProvider[^>]*>([\s\S]*?)<\/UILintProvider>/g,
2705
+ "$1"
2706
+ );
2707
+ updated = updated.replace(/\n{3,}/g, "\n\n");
2708
+ if (updated !== original) {
2709
+ writeFileSync2(absPath, updated, "utf-8");
2710
+ modifiedFiles.push(absPath);
2711
+ }
2712
+ } catch {
2713
+ }
2714
+ }
2715
+ return {
2716
+ success: true,
2717
+ modifiedFiles
2551
2718
  };
2552
2719
  }
2553
2720
 
@@ -2669,7 +2836,7 @@ function wrapCjsModuleExports(program) {
2669
2836
  async function installJsxLocPlugin(opts) {
2670
2837
  const configPath = findNextConfigFile(opts.projectPath);
2671
2838
  if (!configPath) {
2672
- return { configFile: null, modified: false };
2839
+ return { configFile: null, modified: false, modifiedFiles: [] };
2673
2840
  }
2674
2841
  const configFilename = getNextConfigFilename(configPath);
2675
2842
  const original = readFileSync7(configPath, "utf-8");
@@ -2677,7 +2844,7 @@ async function installJsxLocPlugin(opts) {
2677
2844
  try {
2678
2845
  mod = parseModule4(original);
2679
2846
  } catch {
2680
- return { configFile: configFilename, modified: false };
2847
+ return { configFile: configFilename, modified: false, modifiedFiles: [] };
2681
2848
  }
2682
2849
  const program = mod.$ast;
2683
2850
  const isCjs = configPath.endsWith(".cjs");
@@ -2696,9 +2863,55 @@ async function installJsxLocPlugin(opts) {
2696
2863
  const updated = changed ? generateCode3(mod).code : original;
2697
2864
  if (updated !== original) {
2698
2865
  writeFileSync3(configPath, updated, "utf-8");
2699
- return { configFile: configFilename, modified: true };
2866
+ return { configFile: configFilename, modified: true, modifiedFiles: [configPath] };
2867
+ }
2868
+ return { configFile: configFilename, modified: false, modifiedFiles: [] };
2869
+ }
2870
+ async function uninstallJsxLocPlugin(options) {
2871
+ const { projectPath } = options;
2872
+ const configPath = findNextConfigFile(projectPath);
2873
+ if (!configPath) {
2874
+ return {
2875
+ success: true,
2876
+ modifiedFiles: []
2877
+ };
2878
+ }
2879
+ try {
2880
+ const original = readFileSync7(configPath, "utf-8");
2881
+ let updated = original.replace(
2882
+ /^import\s+\{[^}]*withJsxLoc[^}]*\}\s+from\s+["']jsx-loc-plugin\/next["'];?\s*$/gm,
2883
+ ""
2884
+ );
2885
+ updated = updated.replace(
2886
+ /^const\s+\{[^}]*withJsxLoc[^}]*\}\s*=\s*require\s*\(\s*["']jsx-loc-plugin\/next["']\s*\)\s*;?\s*$/gm,
2887
+ ""
2888
+ );
2889
+ updated = updated.replace(
2890
+ /export\s+default\s+withJsxLoc\s*\(\s*([\s\S]*?)\s*\)\s*;?/g,
2891
+ "export default $1;"
2892
+ );
2893
+ updated = updated.replace(
2894
+ /module\.exports\s*=\s*withJsxLoc\s*\(\s*([\s\S]*?)\s*\)\s*;?/g,
2895
+ "module.exports = $1;"
2896
+ );
2897
+ updated = updated.replace(/\n{3,}/g, "\n\n");
2898
+ if (updated !== original) {
2899
+ writeFileSync3(configPath, updated, "utf-8");
2900
+ return {
2901
+ success: true,
2902
+ modifiedFiles: [configPath]
2903
+ };
2904
+ }
2905
+ return {
2906
+ success: true,
2907
+ modifiedFiles: []
2908
+ };
2909
+ } catch (error) {
2910
+ return {
2911
+ success: false,
2912
+ error: error instanceof Error ? error.message : String(error)
2913
+ };
2700
2914
  }
2701
- return { configFile: configFilename, modified: false };
2702
2915
  }
2703
2916
 
2704
2917
  // src/utils/vite-config-inject.ts
@@ -2882,7 +3095,7 @@ function ensurePluginsContainsJsxLoc(configObj) {
2882
3095
  }
2883
3096
  async function installViteJsxLocPlugin(opts) {
2884
3097
  const configPath = findViteConfigFile2(opts.projectPath);
2885
- if (!configPath) return { configFile: null, modified: false };
3098
+ if (!configPath) return { configFile: null, modified: false, modifiedFiles: [] };
2886
3099
  const configFilename = getViteConfigFilename(configPath);
2887
3100
  const original = readFileSync8(configPath, "utf-8");
2888
3101
  const isCjs = configPath.endsWith(".cjs");
@@ -2890,10 +3103,10 @@ async function installViteJsxLocPlugin(opts) {
2890
3103
  try {
2891
3104
  mod = parseModule5(original);
2892
3105
  } catch {
2893
- return { configFile: configFilename, modified: false };
3106
+ return { configFile: configFilename, modified: false, modifiedFiles: [] };
2894
3107
  }
2895
3108
  const found = findExportedConfigObjectExpression(mod);
2896
- if (!found) return { configFile: configFilename, modified: false };
3109
+ if (!found) return { configFile: configFilename, modified: false, modifiedFiles: [] };
2897
3110
  let changed = false;
2898
3111
  if (isCjs) {
2899
3112
  const reqRes = ensureCjsJsxLocRequire(found.program);
@@ -2907,9 +3120,52 @@ async function installViteJsxLocPlugin(opts) {
2907
3120
  const updated = changed ? generateCode4(mod).code : original;
2908
3121
  if (updated !== original) {
2909
3122
  writeFileSync4(configPath, updated, "utf-8");
2910
- return { configFile: configFilename, modified: true };
3123
+ return { configFile: configFilename, modified: true, modifiedFiles: [configPath] };
3124
+ }
3125
+ return { configFile: configFilename, modified: false, modifiedFiles: [] };
3126
+ }
3127
+ async function uninstallViteJsxLocPlugin(options) {
3128
+ const { projectPath } = options;
3129
+ const configPath = findViteConfigFile2(projectPath);
3130
+ if (!configPath) {
3131
+ return {
3132
+ success: true,
3133
+ modifiedFiles: []
3134
+ };
3135
+ }
3136
+ try {
3137
+ const original = readFileSync8(configPath, "utf-8");
3138
+ let updated = original.replace(
3139
+ /^import\s+\{[^}]*jsxLoc[^}]*\}\s+from\s+["']jsx-loc-plugin\/vite["'];?\s*$/gm,
3140
+ ""
3141
+ );
3142
+ updated = updated.replace(
3143
+ /^import\s+jsxLoc\s+from\s+["']jsx-loc-plugin\/vite["'];?\s*$/gm,
3144
+ ""
3145
+ );
3146
+ updated = updated.replace(
3147
+ /^const\s+\{[^}]*jsxLoc[^}]*\}\s*=\s*require\s*\(\s*["']jsx-loc-plugin\/vite["']\s*\)\s*;?\s*$/gm,
3148
+ ""
3149
+ );
3150
+ updated = updated.replace(/jsxLoc\s*\(\s*\)\s*,?\s*/g, "");
3151
+ updated = updated.replace(/\n{3,}/g, "\n\n");
3152
+ if (updated !== original) {
3153
+ writeFileSync4(configPath, updated, "utf-8");
3154
+ return {
3155
+ success: true,
3156
+ modifiedFiles: [configPath]
3157
+ };
3158
+ }
3159
+ return {
3160
+ success: true,
3161
+ modifiedFiles: []
3162
+ };
3163
+ } catch (error) {
3164
+ return {
3165
+ success: false,
3166
+ error: error instanceof Error ? error.message : String(error)
3167
+ };
2911
3168
  }
2912
- return { configFile: configFilename, modified: false };
2913
3169
  }
2914
3170
 
2915
3171
  // src/utils/next-routes.ts
@@ -3392,9 +3648,25 @@ async function installNextUILintRoutes(opts) {
3392
3648
  opts
3393
3649
  );
3394
3650
  }
3651
+ async function uninstallNextUILintRoutes(options) {
3652
+ const { projectPath, appRoot } = options;
3653
+ const { rm } = await import("fs/promises");
3654
+ const baseAbs = join9(projectPath, appRoot, "api", ".uilint");
3655
+ try {
3656
+ if (existsSync9(baseAbs)) {
3657
+ await rm(baseAbs, { recursive: true, force: true });
3658
+ }
3659
+ return { success: true };
3660
+ } catch (error) {
3661
+ return {
3662
+ success: false,
3663
+ error: error instanceof Error ? error.message : String(error)
3664
+ };
3665
+ }
3666
+ }
3395
3667
 
3396
3668
  // src/utils/prettier.ts
3397
- import { existsSync as existsSync10 } from "fs";
3669
+ import { existsSync as existsSync10, utimesSync } from "fs";
3398
3670
  import { spawn } from "child_process";
3399
3671
  import { join as join10, dirname as dirname4 } from "path";
3400
3672
  function getPrettierPath(projectPath) {
@@ -3531,6 +3803,17 @@ async function formatFilesWithPrettier(filePaths, projectPath) {
3531
3803
  });
3532
3804
  });
3533
3805
  }
3806
+ function touchFiles(filePaths) {
3807
+ const now = /* @__PURE__ */ new Date();
3808
+ for (const filePath of filePaths) {
3809
+ try {
3810
+ if (existsSync10(filePath)) {
3811
+ utimesSync(filePath, now, now);
3812
+ }
3813
+ } catch {
3814
+ }
3815
+ }
3816
+ }
3534
3817
 
3535
3818
  // src/commands/install/execute.ts
3536
3819
  async function executeAction(action, options) {
@@ -3636,6 +3919,25 @@ async function executeAction(action, options) {
3636
3919
  case "install_next_routes": {
3637
3920
  return await executeInstallNextRoutes(action, options);
3638
3921
  }
3922
+ // Uninstall actions
3923
+ case "remove_eslint": {
3924
+ return await executeRemoveEslint(action, options);
3925
+ }
3926
+ case "remove_react": {
3927
+ return await executeRemoveReact(action, options);
3928
+ }
3929
+ case "remove_next_config": {
3930
+ return await executeRemoveNextConfig(action, options);
3931
+ }
3932
+ case "remove_vite_config": {
3933
+ return await executeRemoveViteConfig(action, options);
3934
+ }
3935
+ case "remove_next_routes": {
3936
+ return await executeRemoveNextRoutes(action, options);
3937
+ }
3938
+ case "remove_directory": {
3939
+ return await executeRemoveDirectory(action, options);
3940
+ }
3639
3941
  default: {
3640
3942
  const _exhaustive = action;
3641
3943
  return {
@@ -3701,7 +4003,8 @@ async function executeInjectReact(action, options) {
3701
4003
  return {
3702
4004
  action,
3703
4005
  success,
3704
- error: success ? void 0 : "Failed to configure React overlay"
4006
+ error: success ? void 0 : "Failed to configure React overlay",
4007
+ modifiedFiles: result.modifiedFiles
3705
4008
  };
3706
4009
  }
3707
4010
  async function executeInjectViteConfig(action, options) {
@@ -3720,7 +4023,8 @@ async function executeInjectViteConfig(action, options) {
3720
4023
  return {
3721
4024
  action,
3722
4025
  success: result.modified || result.configFile !== null,
3723
- error: result.configFile === null ? "No vite.config found" : void 0
4026
+ error: result.configFile === null ? "No vite.config found" : void 0,
4027
+ modifiedFiles: result.modifiedFiles
3724
4028
  };
3725
4029
  }
3726
4030
  async function executeInjectNextConfig(action, options) {
@@ -3739,7 +4043,8 @@ async function executeInjectNextConfig(action, options) {
3739
4043
  return {
3740
4044
  action,
3741
4045
  success: result.modified || result.configFile !== null,
3742
- error: result.configFile === null ? "No next.config found" : void 0
4046
+ error: result.configFile === null ? "No next.config found" : void 0,
4047
+ modifiedFiles: result.modifiedFiles
3743
4048
  };
3744
4049
  }
3745
4050
  async function executeInstallNextRoutes(action, options) {
@@ -3758,6 +4063,117 @@ async function executeInstallNextRoutes(action, options) {
3758
4063
  });
3759
4064
  return { action, success: true };
3760
4065
  }
4066
+ async function executeRemoveEslint(action, options) {
4067
+ const { dryRun = false } = options;
4068
+ if (dryRun) {
4069
+ return {
4070
+ action,
4071
+ success: true,
4072
+ wouldDo: `Remove uilint ESLint rules from: ${action.configPath}`
4073
+ };
4074
+ }
4075
+ const result = await uninstallEslintPlugin({
4076
+ projectPath: action.packagePath
4077
+ });
4078
+ return {
4079
+ action,
4080
+ success: result.success,
4081
+ error: result.error,
4082
+ modifiedFiles: result.modifiedFiles
4083
+ };
4084
+ }
4085
+ async function executeRemoveReact(action, options) {
4086
+ const { dryRun = false } = options;
4087
+ if (dryRun) {
4088
+ return {
4089
+ action,
4090
+ success: true,
4091
+ wouldDo: `Remove <uilint-devtools /> from: ${action.projectPath}`
4092
+ };
4093
+ }
4094
+ const result = await uninstallReactUILintOverlay({
4095
+ projectPath: action.projectPath,
4096
+ appRoot: action.appRoot,
4097
+ mode: action.mode
4098
+ });
4099
+ return {
4100
+ action,
4101
+ success: result.success,
4102
+ error: result.error,
4103
+ modifiedFiles: result.modifiedFiles
4104
+ };
4105
+ }
4106
+ async function executeRemoveNextConfig(action, options) {
4107
+ const { dryRun = false } = options;
4108
+ if (dryRun) {
4109
+ return {
4110
+ action,
4111
+ success: true,
4112
+ wouldDo: `Remove jsx-loc-plugin from next.config: ${action.projectPath}`
4113
+ };
4114
+ }
4115
+ const result = await uninstallJsxLocPlugin({
4116
+ projectPath: action.projectPath
4117
+ });
4118
+ return {
4119
+ action,
4120
+ success: result.success,
4121
+ error: result.error,
4122
+ modifiedFiles: result.modifiedFiles
4123
+ };
4124
+ }
4125
+ async function executeRemoveViteConfig(action, options) {
4126
+ const { dryRun = false } = options;
4127
+ if (dryRun) {
4128
+ return {
4129
+ action,
4130
+ success: true,
4131
+ wouldDo: `Remove jsx-loc-plugin from vite.config: ${action.projectPath}`
4132
+ };
4133
+ }
4134
+ const result = await uninstallViteJsxLocPlugin({
4135
+ projectPath: action.projectPath
4136
+ });
4137
+ return {
4138
+ action,
4139
+ success: result.success,
4140
+ error: result.error,
4141
+ modifiedFiles: result.modifiedFiles
4142
+ };
4143
+ }
4144
+ async function executeRemoveNextRoutes(action, options) {
4145
+ const { dryRun = false } = options;
4146
+ if (dryRun) {
4147
+ return {
4148
+ action,
4149
+ success: true,
4150
+ wouldDo: `Remove Next.js API routes: ${action.projectPath}`
4151
+ };
4152
+ }
4153
+ const result = await uninstallNextUILintRoutes({
4154
+ projectPath: action.projectPath,
4155
+ appRoot: action.appRoot
4156
+ });
4157
+ return {
4158
+ action,
4159
+ success: result.success,
4160
+ error: result.error
4161
+ };
4162
+ }
4163
+ async function executeRemoveDirectory(action, options) {
4164
+ const { dryRun = false } = options;
4165
+ if (dryRun) {
4166
+ return {
4167
+ action,
4168
+ success: true,
4169
+ wouldDo: `Remove directory: ${action.path}`
4170
+ };
4171
+ }
4172
+ if (existsSync11(action.path)) {
4173
+ rmSync(action.path, { recursive: true, force: true });
4174
+ }
4175
+ return { action, success: true };
4176
+ }
3761
4177
  function deepMerge(target, source) {
3762
4178
  const result = { ...target };
3763
4179
  for (const key of Object.keys(source)) {
@@ -3873,6 +4289,14 @@ function collectFormattableFiles(actionsPerformed) {
3873
4289
  files.push(filePath);
3874
4290
  }
3875
4291
  }
4292
+ if (result.modifiedFiles) {
4293
+ for (const modifiedPath of result.modifiedFiles) {
4294
+ const ext = modifiedPath.slice(modifiedPath.lastIndexOf(".")).toLowerCase();
4295
+ if (formattableExtensions.has(ext) && !files.includes(modifiedPath)) {
4296
+ files.push(modifiedPath);
4297
+ }
4298
+ }
4299
+ }
3876
4300
  }
3877
4301
  return files;
3878
4302
  }
@@ -3941,6 +4365,8 @@ async function execute(plan, options = {}) {
3941
4365
  () => {
3942
4366
  }
3943
4367
  );
4368
+ await new Promise((resolve) => setTimeout(resolve, 100));
4369
+ touchFiles(filesToFormat);
3944
4370
  }
3945
4371
  }
3946
4372
  }
@@ -4042,6 +4468,15 @@ var genstyleguideInstaller = {
4042
4468
  type: "complete",
4043
4469
  message: "Installed /genstyleguide command"
4044
4470
  };
4471
+ },
4472
+ planUninstall(targets, project) {
4473
+ const actions = [];
4474
+ const commandPath = join11(project.cursorDir.path, "commands", "genstyleguide.md");
4475
+ actions.push({
4476
+ type: "delete_file",
4477
+ path: commandPath
4478
+ });
4479
+ return { actions };
4045
4480
  }
4046
4481
  };
4047
4482
 
@@ -4145,6 +4580,15 @@ var skillInstaller = {
4145
4580
  error: error instanceof Error ? error.message : String(error)
4146
4581
  };
4147
4582
  }
4583
+ },
4584
+ planUninstall(targets, project) {
4585
+ const actions = [];
4586
+ const skillDir = join12(project.cursorDir.path, "skills", "ui-consistency-enforcer");
4587
+ actions.push({
4588
+ type: "remove_directory",
4589
+ path: skillDir
4590
+ });
4591
+ return { actions };
4148
4592
  }
4149
4593
  };
4150
4594
 
@@ -4374,6 +4818,24 @@ var eslintInstaller = {
4374
4818
  type: "complete",
4375
4819
  message: `ESLint plugin installed in ${targets.length} package(s)`
4376
4820
  };
4821
+ },
4822
+ planUninstall(targets, project) {
4823
+ const actions = [];
4824
+ for (const target of targets) {
4825
+ const pkgInfo = project.packages.find((p) => p.path === target.path);
4826
+ if (!pkgInfo || !pkgInfo.eslintConfigPath) continue;
4827
+ actions.push({
4828
+ type: "remove_eslint",
4829
+ packagePath: target.path,
4830
+ configPath: pkgInfo.eslintConfigPath
4831
+ });
4832
+ const rulesDir = join13(target.path, ".uilint", "rules");
4833
+ actions.push({
4834
+ type: "remove_directory",
4835
+ path: rulesDir
4836
+ });
4837
+ }
4838
+ return { actions };
4377
4839
  }
4378
4840
  };
4379
4841
 
@@ -4452,6 +4914,27 @@ var viteOverlayInstaller = {
4452
4914
  type: "complete",
4453
4915
  message: "Vite overlay installed"
4454
4916
  };
4917
+ },
4918
+ planUninstall(targets, project) {
4919
+ const actions = [];
4920
+ if (targets.length === 0) return { actions };
4921
+ const target = targets[0];
4922
+ const appInfo = project.viteApps.find(
4923
+ (app) => app.projectPath === target.path
4924
+ );
4925
+ if (!appInfo) return { actions };
4926
+ const { projectPath, detection } = appInfo;
4927
+ actions.push({
4928
+ type: "remove_react",
4929
+ projectPath,
4930
+ appRoot: detection.entryRoot,
4931
+ mode: "vite"
4932
+ });
4933
+ actions.push({
4934
+ type: "remove_vite_config",
4935
+ projectPath
4936
+ });
4937
+ return { actions };
4455
4938
  }
4456
4939
  };
4457
4940
 
@@ -4533,23 +5016,41 @@ async function installUI(options = {}, executeOptions = {}) {
4533
5016
  InstallApp,
4534
5017
  {
4535
5018
  projectPromise,
4536
- onComplete: async (selections, eslintRules, injectionPointConfig) => {
5019
+ onComplete: async (selections, eslintRules, injectionPointConfig, uninstallSelections) => {
4537
5020
  const project = await projectPromise;
4538
5021
  const choices = selectionsToUserChoices(selections, project, eslintRules, injectionPointConfig);
4539
- if (choices.items.length === 0) {
4540
- console.log("\nNo items selected for installation");
5022
+ const hasInstalls = choices.items.length > 0;
5023
+ const hasUninstalls = uninstallSelections && uninstallSelections.length > 0;
5024
+ if (!hasInstalls && !hasUninstalls) {
5025
+ console.log("\nNo changes selected");
4541
5026
  process.exit(0);
4542
5027
  }
4543
5028
  const { createPlan } = await import("./plan-SIXVCXCK.js");
4544
5029
  const plan = createPlan(project, choices, { force: options.force });
5030
+ if (hasUninstalls && uninstallSelections) {
5031
+ for (const selection of uninstallSelections) {
5032
+ if (!selection.selected || selection.targets.length === 0) continue;
5033
+ const { installer, targets } = selection;
5034
+ if (installer.planUninstall) {
5035
+ const uninstallPlan = installer.planUninstall(targets, project);
5036
+ plan.actions = [...uninstallPlan.actions, ...plan.actions];
5037
+ }
5038
+ }
5039
+ }
4545
5040
  const result = await execute(plan, {
4546
5041
  ...executeOptions,
4547
5042
  projectPath: project.projectPath
4548
5043
  });
4549
5044
  if (result.success) {
4550
- console.log("\n\u2713 Installation completed successfully!");
5045
+ if (hasInstalls && hasUninstalls) {
5046
+ console.log("\n\u2713 Changes applied successfully!");
5047
+ } else if (hasUninstalls) {
5048
+ console.log("\n\u2713 Uninstallation completed successfully!");
5049
+ } else {
5050
+ console.log("\n\u2713 Installation completed successfully!");
5051
+ }
4551
5052
  } else {
4552
- console.log("\n\u26A0 Installation completed with errors");
5053
+ console.log("\n\u26A0 Operation completed with errors");
4553
5054
  }
4554
5055
  process.exit(result.success ? 0 : 1);
4555
5056
  },
@@ -4565,4 +5066,4 @@ async function installUI(options = {}, executeOptions = {}) {
4565
5066
  export {
4566
5067
  installUI
4567
5068
  };
4568
- //# sourceMappingURL=install-ui-FE5AA75P.js.map
5069
+ //# sourceMappingURL=install-ui-HTVB5HDB.js.map