uilint 0.2.22 → 0.2.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-P4I4RKBY.js +126 -0
- package/dist/chunk-P4I4RKBY.js.map +1 -0
- package/dist/{chunk-PBEKMDUH.js → chunk-TWUDB36F.js} +62 -54
- package/dist/chunk-TWUDB36F.js.map +1 -0
- package/dist/chunk-VNANPKR2.js +1226 -0
- package/dist/chunk-VNANPKR2.js.map +1 -0
- package/dist/index.js +690 -29
- package/dist/index.js.map +1 -1
- package/dist/{install-ui-HTVB5HDB.js → install-ui-CCZ3XJDE.js} +438 -793
- package/dist/install-ui-CCZ3XJDE.js.map +1 -0
- package/dist/{plan-SIXVCXCK.js → plan-5WHKVACB.js} +95 -40
- package/dist/plan-5WHKVACB.js.map +1 -0
- package/package.json +9 -4
- package/dist/chunk-FRNXXIEM.js +0 -197
- package/dist/chunk-FRNXXIEM.js.map +0 -1
- package/dist/chunk-PBEKMDUH.js.map +0 -1
- package/dist/install-ui-HTVB5HDB.js.map +0 -1
- package/dist/plan-SIXVCXCK.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,21 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
createSpinner,
|
|
4
|
+
detectCoverageSetup,
|
|
4
5
|
detectNextAppRouter,
|
|
6
|
+
findEslintConfigFile,
|
|
5
7
|
findNextAppRouterProjects,
|
|
6
8
|
intro,
|
|
7
9
|
logError,
|
|
8
10
|
logInfo,
|
|
9
11
|
logSuccess,
|
|
10
12
|
logWarning,
|
|
13
|
+
needsCoveragePreparation,
|
|
11
14
|
note,
|
|
12
15
|
outro,
|
|
13
16
|
pc,
|
|
17
|
+
prepareCoverage,
|
|
18
|
+
readRuleConfigsFromConfig,
|
|
19
|
+
updateRuleConfigInConfig,
|
|
20
|
+
updateRuleSeverityInConfig,
|
|
14
21
|
withSpinner
|
|
15
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-VNANPKR2.js";
|
|
23
|
+
import "./chunk-P4I4RKBY.js";
|
|
16
24
|
|
|
17
25
|
// src/index.ts
|
|
18
|
-
import { Command } from "commander";
|
|
26
|
+
import { Command as Command6 } from "commander";
|
|
19
27
|
|
|
20
28
|
// src/commands/scan.ts
|
|
21
29
|
import { dirname, resolve as resolve2 } from "path";
|
|
@@ -251,8 +259,8 @@ async function initializeLangfuseIfEnabled() {
|
|
|
251
259
|
},
|
|
252
260
|
{ asType: "generation" }
|
|
253
261
|
);
|
|
254
|
-
await new Promise((
|
|
255
|
-
resolveTrace =
|
|
262
|
+
await new Promise((resolve8) => {
|
|
263
|
+
resolveTrace = resolve8;
|
|
256
264
|
});
|
|
257
265
|
if (endData && generationRef) {
|
|
258
266
|
const usageDetails = endData.usage ? Object.fromEntries(
|
|
@@ -1168,14 +1176,14 @@ import {
|
|
|
1168
1176
|
} from "uilint-core";
|
|
1169
1177
|
import { ensureOllamaReady as ensureOllamaReady3 } from "uilint-core/node";
|
|
1170
1178
|
async function readStdin2() {
|
|
1171
|
-
return new Promise((
|
|
1179
|
+
return new Promise((resolve8) => {
|
|
1172
1180
|
let data = "";
|
|
1173
1181
|
const rl = createInterface({ input: process.stdin });
|
|
1174
1182
|
rl.on("line", (line) => {
|
|
1175
1183
|
data += line;
|
|
1176
1184
|
});
|
|
1177
1185
|
rl.on("close", () => {
|
|
1178
|
-
|
|
1186
|
+
resolve8(data);
|
|
1179
1187
|
});
|
|
1180
1188
|
});
|
|
1181
1189
|
}
|
|
@@ -2154,6 +2162,42 @@ ${stack}` : ""
|
|
|
2154
2162
|
handleConfigSet(key, value);
|
|
2155
2163
|
break;
|
|
2156
2164
|
}
|
|
2165
|
+
case "rule:config:set": {
|
|
2166
|
+
const { ruleId, severity, options, requestId } = message;
|
|
2167
|
+
handleRuleConfigSet(ws, ruleId, severity, options, requestId);
|
|
2168
|
+
break;
|
|
2169
|
+
}
|
|
2170
|
+
case "coverage:request": {
|
|
2171
|
+
const { requestId } = message;
|
|
2172
|
+
try {
|
|
2173
|
+
const coveragePath = join3(serverAppRootForVision, "coverage", "coverage-final.json");
|
|
2174
|
+
if (!existsSync5(coveragePath)) {
|
|
2175
|
+
sendMessage(ws, {
|
|
2176
|
+
type: "coverage:error",
|
|
2177
|
+
error: "Coverage data not found. Run tests with coverage first (e.g., `vitest run --coverage`)",
|
|
2178
|
+
requestId
|
|
2179
|
+
});
|
|
2180
|
+
break;
|
|
2181
|
+
}
|
|
2182
|
+
const coverageData = JSON.parse(readFileSync(coveragePath, "utf-8"));
|
|
2183
|
+
logInfo(`${pc.dim("[ws]")} coverage:result ${pc.dim(`${Object.keys(coverageData).length} files`)}`);
|
|
2184
|
+
sendMessage(ws, {
|
|
2185
|
+
type: "coverage:result",
|
|
2186
|
+
coverage: coverageData,
|
|
2187
|
+
timestamp: Date.now(),
|
|
2188
|
+
requestId
|
|
2189
|
+
});
|
|
2190
|
+
} catch (error) {
|
|
2191
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2192
|
+
logError(`${pc.dim("[ws]")} coverage:error ${errorMessage}`);
|
|
2193
|
+
sendMessage(ws, {
|
|
2194
|
+
type: "coverage:error",
|
|
2195
|
+
error: errorMessage,
|
|
2196
|
+
requestId
|
|
2197
|
+
});
|
|
2198
|
+
}
|
|
2199
|
+
break;
|
|
2200
|
+
}
|
|
2157
2201
|
}
|
|
2158
2202
|
}
|
|
2159
2203
|
function handleDisconnect(ws) {
|
|
@@ -2179,6 +2223,24 @@ function handleFileChange(filePath) {
|
|
|
2179
2223
|
sendMessage(ws, { type: "file:changed", filePath: clientFilePath });
|
|
2180
2224
|
}
|
|
2181
2225
|
}
|
|
2226
|
+
function handleCoverageFileChange(filePath) {
|
|
2227
|
+
try {
|
|
2228
|
+
const coverageData = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
2229
|
+
logInfo(`${pc.dim("[ws]")} coverage:changed ${pc.dim(`${Object.keys(coverageData).length} files`)}`);
|
|
2230
|
+
broadcast({
|
|
2231
|
+
type: "coverage:result",
|
|
2232
|
+
coverage: coverageData,
|
|
2233
|
+
timestamp: Date.now()
|
|
2234
|
+
});
|
|
2235
|
+
} catch (error) {
|
|
2236
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2237
|
+
logError(`${pc.dim("[ws]")} Failed to read coverage data: ${errorMessage}`);
|
|
2238
|
+
broadcast({
|
|
2239
|
+
type: "coverage:error",
|
|
2240
|
+
error: `Failed to read coverage: ${errorMessage}`
|
|
2241
|
+
});
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2182
2244
|
var configStore = /* @__PURE__ */ new Map();
|
|
2183
2245
|
var connectedClientsSet = /* @__PURE__ */ new Set();
|
|
2184
2246
|
function broadcastConfigUpdate(key, value) {
|
|
@@ -2192,6 +2254,191 @@ function handleConfigSet(key, value) {
|
|
|
2192
2254
|
logInfo(`${pc.dim("[ws]")} config:set ${pc.bold(key)} = ${pc.dim(JSON.stringify(value))}`);
|
|
2193
2255
|
broadcastConfigUpdate(key, value);
|
|
2194
2256
|
}
|
|
2257
|
+
function broadcastRuleConfigChange(ruleId, severity, options) {
|
|
2258
|
+
const message = {
|
|
2259
|
+
type: "rule:config:changed",
|
|
2260
|
+
ruleId,
|
|
2261
|
+
severity,
|
|
2262
|
+
options
|
|
2263
|
+
};
|
|
2264
|
+
for (const ws of connectedClientsSet) {
|
|
2265
|
+
sendMessage(ws, message);
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
function broadcast(message) {
|
|
2269
|
+
for (const ws of connectedClientsSet) {
|
|
2270
|
+
sendMessage(ws, message);
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
var isIndexing = false;
|
|
2274
|
+
var reindexTimeout = null;
|
|
2275
|
+
var pendingIndexChanges = /* @__PURE__ */ new Set();
|
|
2276
|
+
async function buildDuplicatesIndex(appRoot) {
|
|
2277
|
+
if (isIndexing) {
|
|
2278
|
+
return;
|
|
2279
|
+
}
|
|
2280
|
+
isIndexing = true;
|
|
2281
|
+
logInfo(`${pc.blue("Building duplicates index...")}`);
|
|
2282
|
+
broadcast({ type: "duplicates:indexing:start" });
|
|
2283
|
+
try {
|
|
2284
|
+
const { indexDirectory } = await import("uilint-duplicates");
|
|
2285
|
+
const result = await indexDirectory(appRoot, {
|
|
2286
|
+
onProgress: (message, current, total) => {
|
|
2287
|
+
if (current !== void 0 && total !== void 0) {
|
|
2288
|
+
logInfo(` ${message} (${current}/${total})`);
|
|
2289
|
+
} else {
|
|
2290
|
+
logInfo(` ${message}`);
|
|
2291
|
+
}
|
|
2292
|
+
broadcast({
|
|
2293
|
+
type: "duplicates:indexing:progress",
|
|
2294
|
+
message,
|
|
2295
|
+
current,
|
|
2296
|
+
total
|
|
2297
|
+
});
|
|
2298
|
+
}
|
|
2299
|
+
});
|
|
2300
|
+
logSuccess(
|
|
2301
|
+
`${pc.green("Index complete:")} ${result.totalChunks} chunks (${result.added} added, ${result.modified} modified, ${result.deleted} deleted) in ${(result.duration / 1e3).toFixed(1)}s`
|
|
2302
|
+
);
|
|
2303
|
+
broadcast({
|
|
2304
|
+
type: "duplicates:indexing:complete",
|
|
2305
|
+
added: result.added,
|
|
2306
|
+
modified: result.modified,
|
|
2307
|
+
deleted: result.deleted,
|
|
2308
|
+
totalChunks: result.totalChunks,
|
|
2309
|
+
duration: result.duration
|
|
2310
|
+
});
|
|
2311
|
+
} catch (error) {
|
|
2312
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
2313
|
+
logError(`Index failed: ${msg}`);
|
|
2314
|
+
broadcast({ type: "duplicates:indexing:error", error: msg });
|
|
2315
|
+
} finally {
|
|
2316
|
+
isIndexing = false;
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
function scheduleReindex(appRoot, filePath) {
|
|
2320
|
+
if (!/\.(tsx?|jsx?)$/.test(filePath)) return;
|
|
2321
|
+
pendingIndexChanges.add(filePath);
|
|
2322
|
+
if (reindexTimeout) clearTimeout(reindexTimeout);
|
|
2323
|
+
reindexTimeout = setTimeout(async () => {
|
|
2324
|
+
const count = pendingIndexChanges.size;
|
|
2325
|
+
pendingIndexChanges.clear();
|
|
2326
|
+
logInfo(`${pc.dim(`[index] ${count} file(s) changed, updating index...`)}`);
|
|
2327
|
+
await buildDuplicatesIndex(appRoot);
|
|
2328
|
+
}, 2e3);
|
|
2329
|
+
}
|
|
2330
|
+
var isPreparingCoverage = false;
|
|
2331
|
+
function isCoverageRuleEnabled(appRoot) {
|
|
2332
|
+
const eslintConfigPath = findEslintConfigFile(appRoot);
|
|
2333
|
+
if (!eslintConfigPath) return false;
|
|
2334
|
+
const ruleConfigs = readRuleConfigsFromConfig(eslintConfigPath);
|
|
2335
|
+
const coverageConfig = ruleConfigs.get("require-test-coverage");
|
|
2336
|
+
if (!coverageConfig) return false;
|
|
2337
|
+
return coverageConfig.severity !== "off";
|
|
2338
|
+
}
|
|
2339
|
+
async function buildCoverageData(appRoot) {
|
|
2340
|
+
if (isPreparingCoverage) return;
|
|
2341
|
+
isPreparingCoverage = true;
|
|
2342
|
+
try {
|
|
2343
|
+
if (!isCoverageRuleEnabled(appRoot)) {
|
|
2344
|
+
logInfo(`${pc.dim("Coverage rule not enabled, skipping preparation")}`);
|
|
2345
|
+
return;
|
|
2346
|
+
}
|
|
2347
|
+
const setup = detectCoverageSetup(appRoot);
|
|
2348
|
+
if (!needsCoveragePreparation(setup)) {
|
|
2349
|
+
logInfo(`${pc.dim("Coverage data is up-to-date")}`);
|
|
2350
|
+
return;
|
|
2351
|
+
}
|
|
2352
|
+
logInfo(`${pc.blue("Preparing coverage data...")}`);
|
|
2353
|
+
broadcast({ type: "coverage:setup:start" });
|
|
2354
|
+
const skipPackageInstall = process.env.UILINT_SKIP_COVERAGE_INSTALL === "1";
|
|
2355
|
+
const skipTests = process.env.UILINT_SKIP_COVERAGE_TESTS === "1";
|
|
2356
|
+
const result = await prepareCoverage({
|
|
2357
|
+
appRoot,
|
|
2358
|
+
skipPackageInstall,
|
|
2359
|
+
skipTests,
|
|
2360
|
+
onProgress: (message, phase) => {
|
|
2361
|
+
logInfo(` ${message}`);
|
|
2362
|
+
broadcast({ type: "coverage:setup:progress", message, phase });
|
|
2363
|
+
}
|
|
2364
|
+
});
|
|
2365
|
+
if (result.error) {
|
|
2366
|
+
logWarning(`Coverage preparation completed with errors: ${result.error}`);
|
|
2367
|
+
} else {
|
|
2368
|
+
const parts = [];
|
|
2369
|
+
if (result.packageAdded) parts.push("package installed");
|
|
2370
|
+
if (result.configModified) parts.push("config modified");
|
|
2371
|
+
if (result.testsRan) parts.push("tests ran");
|
|
2372
|
+
if (result.coverageGenerated) parts.push("coverage generated");
|
|
2373
|
+
logSuccess(
|
|
2374
|
+
`${pc.green("Coverage prepared:")} ${parts.join(", ")} in ${(result.duration / 1e3).toFixed(1)}s`
|
|
2375
|
+
);
|
|
2376
|
+
}
|
|
2377
|
+
broadcast({
|
|
2378
|
+
type: "coverage:setup:complete",
|
|
2379
|
+
...result
|
|
2380
|
+
});
|
|
2381
|
+
} catch (error) {
|
|
2382
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
2383
|
+
logError(`Coverage preparation failed: ${msg}`);
|
|
2384
|
+
broadcast({ type: "coverage:setup:error", error: msg });
|
|
2385
|
+
} finally {
|
|
2386
|
+
isPreparingCoverage = false;
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
function handleRuleConfigSet(ws, ruleId, severity, options, requestId) {
|
|
2390
|
+
logInfo(
|
|
2391
|
+
`${pc.dim("[ws]")} rule:config:set ${pc.bold(ruleId)} -> ${pc.dim(severity)}${options ? ` with options` : ""}`
|
|
2392
|
+
);
|
|
2393
|
+
const configPath = findEslintConfigFile(serverAppRootForVision);
|
|
2394
|
+
if (!configPath) {
|
|
2395
|
+
const error = `No ESLint config file found in ${serverAppRootForVision}`;
|
|
2396
|
+
logError(`${pc.dim("[ws]")} ${error}`);
|
|
2397
|
+
sendMessage(ws, {
|
|
2398
|
+
type: "rule:config:result",
|
|
2399
|
+
ruleId,
|
|
2400
|
+
severity,
|
|
2401
|
+
options,
|
|
2402
|
+
success: false,
|
|
2403
|
+
error,
|
|
2404
|
+
requestId
|
|
2405
|
+
});
|
|
2406
|
+
return;
|
|
2407
|
+
}
|
|
2408
|
+
let result;
|
|
2409
|
+
if (options && Object.keys(options).length > 0) {
|
|
2410
|
+
result = updateRuleConfigInConfig(configPath, ruleId, severity, options);
|
|
2411
|
+
} else {
|
|
2412
|
+
result = updateRuleSeverityInConfig(configPath, ruleId, severity);
|
|
2413
|
+
}
|
|
2414
|
+
if (result.success) {
|
|
2415
|
+
logSuccess(
|
|
2416
|
+
`${pc.dim("[ws]")} Updated ${pc.bold(`uilint/${ruleId}`)} -> ${pc.dim(severity)}`
|
|
2417
|
+
);
|
|
2418
|
+
eslintInstances.clear();
|
|
2419
|
+
cache.clear();
|
|
2420
|
+
sendMessage(ws, {
|
|
2421
|
+
type: "rule:config:result",
|
|
2422
|
+
ruleId,
|
|
2423
|
+
severity,
|
|
2424
|
+
options,
|
|
2425
|
+
success: true,
|
|
2426
|
+
requestId
|
|
2427
|
+
});
|
|
2428
|
+
broadcastRuleConfigChange(ruleId, severity, options);
|
|
2429
|
+
} else {
|
|
2430
|
+
logError(`${pc.dim("[ws]")} Failed to update rule: ${result.error}`);
|
|
2431
|
+
sendMessage(ws, {
|
|
2432
|
+
type: "rule:config:result",
|
|
2433
|
+
ruleId,
|
|
2434
|
+
severity,
|
|
2435
|
+
options,
|
|
2436
|
+
success: false,
|
|
2437
|
+
error: result.error,
|
|
2438
|
+
requestId
|
|
2439
|
+
});
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2195
2442
|
async function serve(options) {
|
|
2196
2443
|
const port = options.port || 9234;
|
|
2197
2444
|
const cwd = process.cwd();
|
|
@@ -2206,9 +2453,26 @@ async function serve(options) {
|
|
|
2206
2453
|
ignoreInitial: true
|
|
2207
2454
|
});
|
|
2208
2455
|
fileWatcher.on("change", (path) => {
|
|
2209
|
-
|
|
2456
|
+
const resolvedPath = resolve5(path);
|
|
2457
|
+
if (resolvedPath.endsWith("coverage-final.json")) {
|
|
2458
|
+
handleCoverageFileChange(resolvedPath);
|
|
2459
|
+
return;
|
|
2460
|
+
}
|
|
2461
|
+
handleFileChange(resolvedPath);
|
|
2462
|
+
scheduleReindex(appRoot, resolvedPath);
|
|
2210
2463
|
});
|
|
2464
|
+
const coveragePath = join3(appRoot, "coverage", "coverage-final.json");
|
|
2465
|
+
if (existsSync5(coveragePath)) {
|
|
2466
|
+
fileWatcher.add(coveragePath);
|
|
2467
|
+
logInfo(`Watching coverage: ${pc.dim(coveragePath)}`);
|
|
2468
|
+
}
|
|
2211
2469
|
const wss = new WebSocketServer({ port });
|
|
2470
|
+
buildDuplicatesIndex(appRoot).catch((err) => {
|
|
2471
|
+
logError(`Failed to build duplicates index: ${err.message}`);
|
|
2472
|
+
});
|
|
2473
|
+
buildCoverageData(appRoot).catch((err) => {
|
|
2474
|
+
logWarning(`Failed to prepare coverage: ${err.message}`);
|
|
2475
|
+
});
|
|
2212
2476
|
wss.on("connection", (ws) => {
|
|
2213
2477
|
connectedClients += 1;
|
|
2214
2478
|
connectedClientsSet.add(ws);
|
|
@@ -2219,15 +2483,25 @@ async function serve(options) {
|
|
|
2219
2483
|
workspaceRoot: wsRoot,
|
|
2220
2484
|
serverCwd: cwd
|
|
2221
2485
|
});
|
|
2486
|
+
const eslintConfigPath = findEslintConfigFile(appRoot);
|
|
2487
|
+
const currentRuleConfigs = eslintConfigPath ? readRuleConfigsFromConfig(eslintConfigPath) : /* @__PURE__ */ new Map();
|
|
2222
2488
|
sendMessage(ws, {
|
|
2223
2489
|
type: "rules:metadata",
|
|
2224
|
-
rules: ruleRegistry.map((rule) =>
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2490
|
+
rules: ruleRegistry.filter((rule) => currentRuleConfigs.has(rule.id)).map((rule) => {
|
|
2491
|
+
const currentConfig = currentRuleConfigs.get(rule.id);
|
|
2492
|
+
return {
|
|
2493
|
+
id: rule.id,
|
|
2494
|
+
name: rule.name,
|
|
2495
|
+
description: rule.description,
|
|
2496
|
+
category: rule.category,
|
|
2497
|
+
defaultSeverity: rule.defaultSeverity,
|
|
2498
|
+
currentSeverity: currentConfig?.severity,
|
|
2499
|
+
currentOptions: currentConfig?.options,
|
|
2500
|
+
docs: rule.docs,
|
|
2501
|
+
optionSchema: rule.optionSchema,
|
|
2502
|
+
defaultOptions: rule.defaultOptions
|
|
2503
|
+
};
|
|
2504
|
+
})
|
|
2231
2505
|
});
|
|
2232
2506
|
for (const [key, value] of configStore) {
|
|
2233
2507
|
sendMessage(ws, { type: "config:update", key, value });
|
|
@@ -2252,12 +2526,12 @@ async function serve(options) {
|
|
|
2252
2526
|
`UILint WebSocket server running on ${pc.cyan(`ws://localhost:${port}`)}`
|
|
2253
2527
|
);
|
|
2254
2528
|
logInfo("Press Ctrl+C to stop");
|
|
2255
|
-
await new Promise((
|
|
2529
|
+
await new Promise((resolve8) => {
|
|
2256
2530
|
process.on("SIGINT", () => {
|
|
2257
2531
|
logInfo("Shutting down...");
|
|
2258
2532
|
wss.close();
|
|
2259
2533
|
fileWatcher?.close();
|
|
2260
|
-
|
|
2534
|
+
resolve8();
|
|
2261
2535
|
});
|
|
2262
2536
|
});
|
|
2263
2537
|
}
|
|
@@ -2776,8 +3050,32 @@ function presetToPosition(preset) {
|
|
|
2776
3050
|
};
|
|
2777
3051
|
return positions[preset] || { x: 500, y: 30 };
|
|
2778
3052
|
}
|
|
3053
|
+
function parseRuleConfig(value) {
|
|
3054
|
+
const match = value.match(/^([^:]+):(error|warn|off)$/);
|
|
3055
|
+
if (!match) {
|
|
3056
|
+
return null;
|
|
3057
|
+
}
|
|
3058
|
+
return {
|
|
3059
|
+
ruleId: match[1],
|
|
3060
|
+
severity: match[2]
|
|
3061
|
+
};
|
|
3062
|
+
}
|
|
3063
|
+
function parseOptionsJson(optionsStr) {
|
|
3064
|
+
if (!optionsStr) {
|
|
3065
|
+
return void 0;
|
|
3066
|
+
}
|
|
3067
|
+
try {
|
|
3068
|
+
const parsed = JSON.parse(optionsStr);
|
|
3069
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
3070
|
+
return void 0;
|
|
3071
|
+
}
|
|
3072
|
+
return parsed;
|
|
3073
|
+
} catch {
|
|
3074
|
+
return void 0;
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
2779
3077
|
async function sendConfigMessage(port, key, value) {
|
|
2780
|
-
return new Promise((
|
|
3078
|
+
return new Promise((resolve8) => {
|
|
2781
3079
|
const url = `ws://localhost:${port}`;
|
|
2782
3080
|
const ws = new WebSocket2(url);
|
|
2783
3081
|
let resolved = false;
|
|
@@ -2785,7 +3083,7 @@ async function sendConfigMessage(port, key, value) {
|
|
|
2785
3083
|
if (!resolved) {
|
|
2786
3084
|
resolved = true;
|
|
2787
3085
|
ws.close();
|
|
2788
|
-
|
|
3086
|
+
resolve8(false);
|
|
2789
3087
|
}
|
|
2790
3088
|
}, 5e3);
|
|
2791
3089
|
ws.on("open", () => {
|
|
@@ -2796,7 +3094,7 @@ async function sendConfigMessage(port, key, value) {
|
|
|
2796
3094
|
resolved = true;
|
|
2797
3095
|
clearTimeout(timeout);
|
|
2798
3096
|
ws.close();
|
|
2799
|
-
|
|
3097
|
+
resolve8(true);
|
|
2800
3098
|
}
|
|
2801
3099
|
}, 100);
|
|
2802
3100
|
});
|
|
@@ -2804,12 +3102,61 @@ async function sendConfigMessage(port, key, value) {
|
|
|
2804
3102
|
if (!resolved) {
|
|
2805
3103
|
resolved = true;
|
|
2806
3104
|
clearTimeout(timeout);
|
|
2807
|
-
|
|
3105
|
+
resolve8(false);
|
|
3106
|
+
}
|
|
3107
|
+
});
|
|
3108
|
+
});
|
|
3109
|
+
}
|
|
3110
|
+
async function sendRuleConfigMessage(port, ruleId, severity, options) {
|
|
3111
|
+
return new Promise((resolve8) => {
|
|
3112
|
+
const url = `ws://localhost:${port}`;
|
|
3113
|
+
const ws = new WebSocket2(url);
|
|
3114
|
+
let resolved = false;
|
|
3115
|
+
const requestId = `cli-${Date.now()}`;
|
|
3116
|
+
const timeout = setTimeout(() => {
|
|
3117
|
+
if (!resolved) {
|
|
3118
|
+
resolved = true;
|
|
3119
|
+
ws.close();
|
|
3120
|
+
resolve8({ success: false, error: "Request timed out" });
|
|
3121
|
+
}
|
|
3122
|
+
}, 1e4);
|
|
3123
|
+
ws.on("open", () => {
|
|
3124
|
+
const message = JSON.stringify({
|
|
3125
|
+
type: "rule:config:set",
|
|
3126
|
+
ruleId,
|
|
3127
|
+
severity,
|
|
3128
|
+
options,
|
|
3129
|
+
requestId
|
|
3130
|
+
});
|
|
3131
|
+
ws.send(message);
|
|
3132
|
+
});
|
|
3133
|
+
ws.on("message", (data) => {
|
|
3134
|
+
try {
|
|
3135
|
+
const msg = JSON.parse(data.toString());
|
|
3136
|
+
if (msg.type === "rule:config:result" && msg.requestId === requestId) {
|
|
3137
|
+
if (!resolved) {
|
|
3138
|
+
resolved = true;
|
|
3139
|
+
clearTimeout(timeout);
|
|
3140
|
+
ws.close();
|
|
3141
|
+
resolve8({
|
|
3142
|
+
success: msg.success,
|
|
3143
|
+
error: msg.error
|
|
3144
|
+
});
|
|
3145
|
+
}
|
|
3146
|
+
}
|
|
3147
|
+
} catch {
|
|
3148
|
+
}
|
|
3149
|
+
});
|
|
3150
|
+
ws.on("error", () => {
|
|
3151
|
+
if (!resolved) {
|
|
3152
|
+
resolved = true;
|
|
3153
|
+
clearTimeout(timeout);
|
|
3154
|
+
resolve8({ success: false, error: "Connection error" });
|
|
2808
3155
|
}
|
|
2809
3156
|
});
|
|
2810
3157
|
});
|
|
2811
3158
|
}
|
|
2812
|
-
async function handleSet(key, value, port) {
|
|
3159
|
+
async function handleSet(key, value, port, extraArg) {
|
|
2813
3160
|
switch (key) {
|
|
2814
3161
|
case "position": {
|
|
2815
3162
|
const parsed = parsePosition(value);
|
|
@@ -2839,6 +3186,50 @@ Expected format: x,y (e.g., 100,50) or preset (top-center, top-left, etc.)`
|
|
|
2839
3186
|
} else {
|
|
2840
3187
|
logError(
|
|
2841
3188
|
`Failed to set position. Is the server running?
|
|
3189
|
+
Start it with: ${pc.bold("npx uilint serve")}`
|
|
3190
|
+
);
|
|
3191
|
+
process.exit(1);
|
|
3192
|
+
}
|
|
3193
|
+
break;
|
|
3194
|
+
}
|
|
3195
|
+
case "rule": {
|
|
3196
|
+
const parsed = parseRuleConfig(value);
|
|
3197
|
+
if (!parsed) {
|
|
3198
|
+
logError(
|
|
3199
|
+
`Invalid rule config: ${value}
|
|
3200
|
+
Expected format: <ruleId>:<severity>
|
|
3201
|
+
severity: error, warn, or off
|
|
3202
|
+
|
|
3203
|
+
Examples:
|
|
3204
|
+
uilint config set rule no-arbitrary-tailwind:warn
|
|
3205
|
+
uilint config set rule no-prop-drilling-depth:error '{"maxDepth":3}'`
|
|
3206
|
+
);
|
|
3207
|
+
process.exit(1);
|
|
3208
|
+
}
|
|
3209
|
+
const options = parseOptionsJson(extraArg);
|
|
3210
|
+
if (extraArg && !options) {
|
|
3211
|
+
logError(
|
|
3212
|
+
`Invalid options JSON: ${extraArg}
|
|
3213
|
+
Expected a valid JSON object, e.g., '{"maxDepth": 3}'`
|
|
3214
|
+
);
|
|
3215
|
+
process.exit(1);
|
|
3216
|
+
}
|
|
3217
|
+
logInfo(
|
|
3218
|
+
`Setting rule "${parsed.ruleId}" to ${parsed.severity}` + (options ? ` with options: ${JSON.stringify(options)}` : "")
|
|
3219
|
+
);
|
|
3220
|
+
const result = await sendRuleConfigMessage(
|
|
3221
|
+
port,
|
|
3222
|
+
parsed.ruleId,
|
|
3223
|
+
parsed.severity,
|
|
3224
|
+
options
|
|
3225
|
+
);
|
|
3226
|
+
if (result.success) {
|
|
3227
|
+
logSuccess(
|
|
3228
|
+
`Rule "${parsed.ruleId}" set to ${parsed.severity}` + (options ? ` with options` : "")
|
|
3229
|
+
);
|
|
3230
|
+
} else {
|
|
3231
|
+
logError(
|
|
3232
|
+
result.error || `Failed to set rule config. Is the server running?
|
|
2842
3233
|
Start it with: ${pc.bold("npx uilint serve")}`
|
|
2843
3234
|
);
|
|
2844
3235
|
process.exit(1);
|
|
@@ -2847,7 +3238,7 @@ Start it with: ${pc.bold("npx uilint serve")}`
|
|
|
2847
3238
|
}
|
|
2848
3239
|
default:
|
|
2849
3240
|
logError(`Unknown config key: ${key}`);
|
|
2850
|
-
logInfo(`Available keys: position`);
|
|
3241
|
+
logInfo(`Available keys: position, rule`);
|
|
2851
3242
|
process.exit(1);
|
|
2852
3243
|
}
|
|
2853
3244
|
}
|
|
@@ -2866,7 +3257,7 @@ To view it, check your browser's dev tools:
|
|
|
2866
3257
|
process.exit(1);
|
|
2867
3258
|
}
|
|
2868
3259
|
}
|
|
2869
|
-
async function config2(action, key, value, options = {}) {
|
|
3260
|
+
async function config2(action, key, value, extraArg, options = {}) {
|
|
2870
3261
|
const port = options.port || 9234;
|
|
2871
3262
|
switch (action) {
|
|
2872
3263
|
case "set":
|
|
@@ -2874,18 +3265,287 @@ async function config2(action, key, value, options = {}) {
|
|
|
2874
3265
|
logError(`Missing value for config set ${key}`);
|
|
2875
3266
|
process.exit(1);
|
|
2876
3267
|
}
|
|
2877
|
-
await handleSet(key, value, port);
|
|
3268
|
+
await handleSet(key, value, port, extraArg);
|
|
2878
3269
|
break;
|
|
2879
3270
|
case "get":
|
|
2880
3271
|
await handleGet(key, port);
|
|
2881
3272
|
break;
|
|
2882
3273
|
default:
|
|
2883
3274
|
logError(`Unknown action: ${action}`);
|
|
2884
|
-
logInfo(`Usage: uilint config <set|get> <key> [value]`);
|
|
3275
|
+
logInfo(`Usage: uilint config <set|get> <key> [value] [options]`);
|
|
2885
3276
|
process.exit(1);
|
|
2886
3277
|
}
|
|
2887
3278
|
}
|
|
2888
3279
|
|
|
3280
|
+
// src/commands/duplicates/index.ts
|
|
3281
|
+
import { Command as Command5 } from "commander";
|
|
3282
|
+
|
|
3283
|
+
// src/commands/duplicates/index-cmd.ts
|
|
3284
|
+
import { Command } from "commander";
|
|
3285
|
+
import chalk2 from "chalk";
|
|
3286
|
+
import ora from "ora";
|
|
3287
|
+
function indexCommand() {
|
|
3288
|
+
return new Command("index").description("Build or update the semantic duplicates index").option("--force", "Rebuild index from scratch").option("--model <name>", "Embedding model (default: nomic-embed-text)").option(
|
|
3289
|
+
"--exclude <glob>",
|
|
3290
|
+
"Exclude patterns (repeatable)",
|
|
3291
|
+
(val, prev) => [...prev, val],
|
|
3292
|
+
[]
|
|
3293
|
+
).option("-o, --output <format>", "Output format: text or json", "text").action(async (options) => {
|
|
3294
|
+
const { indexDirectory } = await import("uilint-duplicates");
|
|
3295
|
+
const projectRoot = process.cwd();
|
|
3296
|
+
const isJson = options.output === "json";
|
|
3297
|
+
let spinner;
|
|
3298
|
+
if (!isJson) {
|
|
3299
|
+
spinner = ora("Initializing indexer...").start();
|
|
3300
|
+
}
|
|
3301
|
+
try {
|
|
3302
|
+
const result = await indexDirectory(projectRoot, {
|
|
3303
|
+
force: options.force,
|
|
3304
|
+
model: options.model,
|
|
3305
|
+
exclude: options.exclude,
|
|
3306
|
+
onProgress: (message, current, total) => {
|
|
3307
|
+
if (spinner) {
|
|
3308
|
+
if (current && total) {
|
|
3309
|
+
spinner.text = `${message} (${current}/${total})`;
|
|
3310
|
+
} else {
|
|
3311
|
+
spinner.text = message;
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
3314
|
+
}
|
|
3315
|
+
});
|
|
3316
|
+
if (isJson) {
|
|
3317
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3318
|
+
} else {
|
|
3319
|
+
spinner?.succeed(chalk2.green("Index complete"));
|
|
3320
|
+
console.log();
|
|
3321
|
+
console.log(chalk2.bold("Index Statistics:"));
|
|
3322
|
+
console.log(` Files added: ${result.added}`);
|
|
3323
|
+
console.log(` Files modified: ${result.modified}`);
|
|
3324
|
+
console.log(` Files deleted: ${result.deleted}`);
|
|
3325
|
+
console.log(` Total chunks: ${result.totalChunks}`);
|
|
3326
|
+
console.log(` Duration: ${(result.duration / 1e3).toFixed(2)}s`);
|
|
3327
|
+
}
|
|
3328
|
+
} catch (error) {
|
|
3329
|
+
if (spinner) {
|
|
3330
|
+
spinner.fail(chalk2.red("Index failed"));
|
|
3331
|
+
}
|
|
3332
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3333
|
+
if (isJson) {
|
|
3334
|
+
console.log(JSON.stringify({ error: message }, null, 2));
|
|
3335
|
+
} else {
|
|
3336
|
+
console.error(chalk2.red(`Error: ${message}`));
|
|
3337
|
+
}
|
|
3338
|
+
process.exit(1);
|
|
3339
|
+
}
|
|
3340
|
+
});
|
|
3341
|
+
}
|
|
3342
|
+
|
|
3343
|
+
// src/commands/duplicates/find.ts
|
|
3344
|
+
import { Command as Command2 } from "commander";
|
|
3345
|
+
import { relative as relative2 } from "path";
|
|
3346
|
+
import chalk3 from "chalk";
|
|
3347
|
+
function findCommand() {
|
|
3348
|
+
return new Command2("find").description("Find semantic duplicate groups in the codebase").option(
|
|
3349
|
+
"--threshold <n>",
|
|
3350
|
+
"Similarity threshold 0-1 (default: 0.85)",
|
|
3351
|
+
parseFloat
|
|
3352
|
+
).option("--min-size <n>", "Minimum group size (default: 2)", parseInt).option("--kind <type>", "Filter: component, hook, function").option("-o, --output <format>", "Output format: text or json", "text").action(async (options) => {
|
|
3353
|
+
const { findDuplicates } = await import("uilint-duplicates");
|
|
3354
|
+
const projectRoot = process.cwd();
|
|
3355
|
+
const isJson = options.output === "json";
|
|
3356
|
+
try {
|
|
3357
|
+
const groups = await findDuplicates({
|
|
3358
|
+
path: projectRoot,
|
|
3359
|
+
threshold: options.threshold,
|
|
3360
|
+
minGroupSize: options.minSize,
|
|
3361
|
+
kind: options.kind
|
|
3362
|
+
});
|
|
3363
|
+
if (isJson) {
|
|
3364
|
+
console.log(JSON.stringify({ groups }, null, 2));
|
|
3365
|
+
return;
|
|
3366
|
+
}
|
|
3367
|
+
if (groups.length === 0) {
|
|
3368
|
+
console.log(chalk3.green("No semantic duplicates found."));
|
|
3369
|
+
return;
|
|
3370
|
+
}
|
|
3371
|
+
console.log(
|
|
3372
|
+
chalk3.bold(
|
|
3373
|
+
`Found ${groups.length} duplicate group${groups.length > 1 ? "s" : ""}:
|
|
3374
|
+
`
|
|
3375
|
+
)
|
|
3376
|
+
);
|
|
3377
|
+
groups.forEach((group, idx) => {
|
|
3378
|
+
const similarity = Math.round(group.avgSimilarity * 100);
|
|
3379
|
+
console.log(
|
|
3380
|
+
chalk3.yellow(
|
|
3381
|
+
`Duplicate Group ${idx + 1} (${similarity}% similar, ${group.members.length} occurrences):`
|
|
3382
|
+
)
|
|
3383
|
+
);
|
|
3384
|
+
group.members.forEach((member) => {
|
|
3385
|
+
const relPath = relative2(projectRoot, member.filePath);
|
|
3386
|
+
const location = `${relPath}:${member.startLine}-${member.endLine}`;
|
|
3387
|
+
const name = member.name || "(anonymous)";
|
|
3388
|
+
const score = member.score === 1 ? "" : chalk3.dim(` (${Math.round(member.score * 100)}%)`);
|
|
3389
|
+
console.log(` ${chalk3.cyan(location.padEnd(50))} ${name}${score}`);
|
|
3390
|
+
});
|
|
3391
|
+
console.log(
|
|
3392
|
+
chalk3.dim(
|
|
3393
|
+
` Suggestion: Consider extracting shared logic into a reusable ${group.kind}
|
|
3394
|
+
`
|
|
3395
|
+
)
|
|
3396
|
+
);
|
|
3397
|
+
});
|
|
3398
|
+
} catch (error) {
|
|
3399
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3400
|
+
if (isJson) {
|
|
3401
|
+
console.log(JSON.stringify({ error: message }, null, 2));
|
|
3402
|
+
} else {
|
|
3403
|
+
console.error(chalk3.red(`Error: ${message}`));
|
|
3404
|
+
}
|
|
3405
|
+
process.exit(1);
|
|
3406
|
+
}
|
|
3407
|
+
});
|
|
3408
|
+
}
|
|
3409
|
+
|
|
3410
|
+
// src/commands/duplicates/search.ts
|
|
3411
|
+
import { Command as Command3 } from "commander";
|
|
3412
|
+
import { relative as relative3 } from "path";
|
|
3413
|
+
import chalk4 from "chalk";
|
|
3414
|
+
function searchCommand() {
|
|
3415
|
+
return new Command3("search").description("Semantic search for similar code").argument("<query>", "Search query (natural language)").option("-k, --top <n>", "Number of results (default: 10)", parseInt).option("--threshold <n>", "Minimum similarity (default: 0.5)", parseFloat).option("-o, --output <format>", "Output format: text or json", "text").action(async (query, options) => {
|
|
3416
|
+
const { searchSimilar } = await import("uilint-duplicates");
|
|
3417
|
+
const projectRoot = process.cwd();
|
|
3418
|
+
const isJson = options.output === "json";
|
|
3419
|
+
try {
|
|
3420
|
+
const results = await searchSimilar(query, {
|
|
3421
|
+
path: projectRoot,
|
|
3422
|
+
top: options.top,
|
|
3423
|
+
threshold: options.threshold
|
|
3424
|
+
});
|
|
3425
|
+
if (isJson) {
|
|
3426
|
+
console.log(JSON.stringify({ results }, null, 2));
|
|
3427
|
+
return;
|
|
3428
|
+
}
|
|
3429
|
+
if (results.length === 0) {
|
|
3430
|
+
console.log(chalk4.yellow("No matching code found."));
|
|
3431
|
+
return;
|
|
3432
|
+
}
|
|
3433
|
+
console.log(chalk4.bold(`Found ${results.length} matching results:
|
|
3434
|
+
`));
|
|
3435
|
+
results.forEach((result, idx) => {
|
|
3436
|
+
const relPath = relative3(projectRoot, result.filePath);
|
|
3437
|
+
const location = `${relPath}:${result.startLine}-${result.endLine}`;
|
|
3438
|
+
const name = result.name || "(anonymous)";
|
|
3439
|
+
const score = Math.round(result.score * 100);
|
|
3440
|
+
const kindLabel = result.kind.padEnd(10);
|
|
3441
|
+
console.log(
|
|
3442
|
+
`${chalk4.dim(`${idx + 1}.`)} ${chalk4.cyan(location)}`
|
|
3443
|
+
);
|
|
3444
|
+
console.log(
|
|
3445
|
+
` ${chalk4.dim(kindLabel)} ${chalk4.bold(name)} ${chalk4.green(`(${score}% similar)`)}`
|
|
3446
|
+
);
|
|
3447
|
+
});
|
|
3448
|
+
} catch (error) {
|
|
3449
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3450
|
+
if (isJson) {
|
|
3451
|
+
console.log(JSON.stringify({ error: message }, null, 2));
|
|
3452
|
+
} else {
|
|
3453
|
+
console.error(chalk4.red(`Error: ${message}`));
|
|
3454
|
+
}
|
|
3455
|
+
process.exit(1);
|
|
3456
|
+
}
|
|
3457
|
+
});
|
|
3458
|
+
}
|
|
3459
|
+
|
|
3460
|
+
// src/commands/duplicates/similar.ts
|
|
3461
|
+
import { Command as Command4 } from "commander";
|
|
3462
|
+
import { relative as relative4, resolve as resolve7, isAbsolute as isAbsolute2 } from "path";
|
|
3463
|
+
import chalk5 from "chalk";
|
|
3464
|
+
function similarCommand() {
|
|
3465
|
+
return new Command4("similar").description("Find code similar to a specific location").argument("<location>", "File location in format file:line (e.g., src/Button.tsx:15)").option("-k, --top <n>", "Number of results (default: 10)", parseInt).option("--threshold <n>", "Minimum similarity (default: 0.7)", parseFloat).option("-o, --output <format>", "Output format: text or json", "text").action(async (location, options) => {
|
|
3466
|
+
const { findSimilarAtLocation } = await import("uilint-duplicates");
|
|
3467
|
+
const projectRoot = process.cwd();
|
|
3468
|
+
const isJson = options.output === "json";
|
|
3469
|
+
const colonIdx = location.lastIndexOf(":");
|
|
3470
|
+
if (colonIdx === -1) {
|
|
3471
|
+
const message = "Invalid location format. Use file:line (e.g., src/Button.tsx:15)";
|
|
3472
|
+
if (isJson) {
|
|
3473
|
+
console.log(JSON.stringify({ error: message }, null, 2));
|
|
3474
|
+
} else {
|
|
3475
|
+
console.error(chalk5.red(`Error: ${message}`));
|
|
3476
|
+
}
|
|
3477
|
+
process.exit(1);
|
|
3478
|
+
}
|
|
3479
|
+
const filePart = location.slice(0, colonIdx);
|
|
3480
|
+
const linePart = location.slice(colonIdx + 1);
|
|
3481
|
+
const line = parseInt(linePart, 10);
|
|
3482
|
+
if (isNaN(line)) {
|
|
3483
|
+
const message = `Invalid line number: ${linePart}`;
|
|
3484
|
+
if (isJson) {
|
|
3485
|
+
console.log(JSON.stringify({ error: message }, null, 2));
|
|
3486
|
+
} else {
|
|
3487
|
+
console.error(chalk5.red(`Error: ${message}`));
|
|
3488
|
+
}
|
|
3489
|
+
process.exit(1);
|
|
3490
|
+
}
|
|
3491
|
+
const filePath = isAbsolute2(filePart) ? filePart : resolve7(projectRoot, filePart);
|
|
3492
|
+
try {
|
|
3493
|
+
const results = await findSimilarAtLocation({
|
|
3494
|
+
path: projectRoot,
|
|
3495
|
+
filePath,
|
|
3496
|
+
line,
|
|
3497
|
+
top: options.top,
|
|
3498
|
+
threshold: options.threshold ?? 0.7
|
|
3499
|
+
});
|
|
3500
|
+
if (isJson) {
|
|
3501
|
+
console.log(JSON.stringify({ results }, null, 2));
|
|
3502
|
+
return;
|
|
3503
|
+
}
|
|
3504
|
+
if (results.length === 0) {
|
|
3505
|
+
console.log(chalk5.yellow("No similar code found."));
|
|
3506
|
+
return;
|
|
3507
|
+
}
|
|
3508
|
+
console.log(
|
|
3509
|
+
chalk5.bold(
|
|
3510
|
+
`Found ${results.length} similar code locations to ${relative4(projectRoot, filePath)}:${line}:
|
|
3511
|
+
`
|
|
3512
|
+
)
|
|
3513
|
+
);
|
|
3514
|
+
results.forEach((result, idx) => {
|
|
3515
|
+
const relPath = relative4(projectRoot, result.filePath);
|
|
3516
|
+
const locationStr = `${relPath}:${result.startLine}-${result.endLine}`;
|
|
3517
|
+
const name = result.name || "(anonymous)";
|
|
3518
|
+
const score = Math.round(result.score * 100);
|
|
3519
|
+
const kindLabel = result.kind.padEnd(10);
|
|
3520
|
+
console.log(
|
|
3521
|
+
`${chalk5.dim(`${idx + 1}.`)} ${chalk5.cyan(locationStr)}`
|
|
3522
|
+
);
|
|
3523
|
+
console.log(
|
|
3524
|
+
` ${chalk5.dim(kindLabel)} ${chalk5.bold(name)} ${chalk5.green(`(${score}% similar)`)}`
|
|
3525
|
+
);
|
|
3526
|
+
});
|
|
3527
|
+
} catch (error) {
|
|
3528
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3529
|
+
if (isJson) {
|
|
3530
|
+
console.log(JSON.stringify({ error: message }, null, 2));
|
|
3531
|
+
} else {
|
|
3532
|
+
console.error(chalk5.red(`Error: ${message}`));
|
|
3533
|
+
}
|
|
3534
|
+
process.exit(1);
|
|
3535
|
+
}
|
|
3536
|
+
});
|
|
3537
|
+
}
|
|
3538
|
+
|
|
3539
|
+
// src/commands/duplicates/index.ts
|
|
3540
|
+
function createDuplicatesCommand() {
|
|
3541
|
+
const duplicates = new Command5("duplicates").description("Semantic code duplicate detection");
|
|
3542
|
+
duplicates.addCommand(indexCommand());
|
|
3543
|
+
duplicates.addCommand(findCommand());
|
|
3544
|
+
duplicates.addCommand(searchCommand());
|
|
3545
|
+
duplicates.addCommand(similarCommand());
|
|
3546
|
+
return duplicates;
|
|
3547
|
+
}
|
|
3548
|
+
|
|
2889
3549
|
// src/index.ts
|
|
2890
3550
|
import { readFileSync as readFileSync3 } from "fs";
|
|
2891
3551
|
import { dirname as dirname7, join as join5 } from "path";
|
|
@@ -2902,7 +3562,7 @@ function assertNodeVersion(minMajor) {
|
|
|
2902
3562
|
}
|
|
2903
3563
|
}
|
|
2904
3564
|
assertNodeVersion(20);
|
|
2905
|
-
var program = new
|
|
3565
|
+
var program = new Command6();
|
|
2906
3566
|
function getCLIVersion() {
|
|
2907
3567
|
try {
|
|
2908
3568
|
const __dirname = dirname7(fileURLToPath(import.meta.url));
|
|
@@ -2981,7 +3641,7 @@ program.command("update").description("Update existing style guide with new styl
|
|
|
2981
3641
|
});
|
|
2982
3642
|
});
|
|
2983
3643
|
program.command("install").description("Install UILint integration").option("--force", "Overwrite existing configuration files").action(async (options) => {
|
|
2984
|
-
const { installUI } = await import("./install-ui-
|
|
3644
|
+
const { installUI } = await import("./install-ui-CCZ3XJDE.js");
|
|
2985
3645
|
await installUI({ force: options.force });
|
|
2986
3646
|
});
|
|
2987
3647
|
program.command("serve").description("Start WebSocket server for real-time UI linting").option("-p, --port <number>", "Port to listen on", "9234").action(async (options) => {
|
|
@@ -3023,10 +3683,11 @@ program.command("vision").description("Analyze a screenshot with Ollama vision m
|
|
|
3023
3683
|
debugDump: options.debugDump
|
|
3024
3684
|
});
|
|
3025
3685
|
});
|
|
3026
|
-
program.command("config").description("Get or set UILint configuration options").argument("<action>", "Action: set or get").argument("<key>", "Config key (e.g., position)").argument("[value]", "Value to set (for set action)").option("-p, --port <number>", "WebSocket server port", "9234").action(async (action, key, value, options) => {
|
|
3027
|
-
await config2(action, key, value, {
|
|
3686
|
+
program.command("config").description("Get or set UILint configuration options").argument("<action>", "Action: set or get").argument("<key>", "Config key (e.g., position, rule)").argument("[value]", "Value to set (for set action)").argument("[extraArg]", "Extra argument (e.g., options JSON for rule config)").option("-p, --port <number>", "WebSocket server port", "9234").action(async (action, key, value, extraArg, options) => {
|
|
3687
|
+
await config2(action, key, value, extraArg, {
|
|
3028
3688
|
port: parseInt(options.port, 10)
|
|
3029
3689
|
});
|
|
3030
3690
|
});
|
|
3691
|
+
program.addCommand(createDuplicatesCommand());
|
|
3031
3692
|
program.parse();
|
|
3032
3693
|
//# sourceMappingURL=index.js.map
|