uilint 0.2.9 → 0.2.11
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-FRNXXIEM.js +197 -0
- package/dist/chunk-FRNXXIEM.js.map +1 -0
- package/dist/index.js +105 -151
- package/dist/index.js.map +1 -1
- package/dist/{install-ui-OEFHX4FG.js → install-ui-KI7YHOVZ.js} +939 -259
- package/dist/install-ui-KI7YHOVZ.js.map +1 -0
- package/package.json +3 -3
- package/dist/chunk-RHTG6DUD.js +0 -89
- package/dist/chunk-RHTG6DUD.js.map +0 -1
- package/dist/install-ui-OEFHX4FG.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
createSpinner,
|
|
3
4
|
detectNextAppRouter,
|
|
4
|
-
findNextAppRouterProjects
|
|
5
|
-
|
|
5
|
+
findNextAppRouterProjects,
|
|
6
|
+
intro,
|
|
7
|
+
logError,
|
|
8
|
+
logInfo,
|
|
9
|
+
logSuccess,
|
|
10
|
+
logWarning,
|
|
11
|
+
note,
|
|
12
|
+
outro,
|
|
13
|
+
pc,
|
|
14
|
+
withSpinner
|
|
15
|
+
} from "./chunk-FRNXXIEM.js";
|
|
6
16
|
|
|
7
17
|
// src/index.ts
|
|
8
18
|
import { Command } from "commander";
|
|
9
19
|
|
|
10
20
|
// src/commands/scan.ts
|
|
11
|
-
import { dirname
|
|
21
|
+
import { dirname, resolve as resolve2 } from "path";
|
|
12
22
|
import { existsSync as existsSync2, mkdirSync, statSync, writeFileSync } from "fs";
|
|
13
23
|
import {
|
|
14
24
|
createStyleSummary,
|
|
@@ -353,62 +363,6 @@ function maybeMs(ms) {
|
|
|
353
363
|
return ms == null ? "n/a" : formatMs(ms);
|
|
354
364
|
}
|
|
355
365
|
|
|
356
|
-
// src/utils/prompts.ts
|
|
357
|
-
import * as p from "@clack/prompts";
|
|
358
|
-
import pc from "picocolors";
|
|
359
|
-
import { readFileSync } from "fs";
|
|
360
|
-
import { dirname, join as join2 } from "path";
|
|
361
|
-
import { fileURLToPath } from "url";
|
|
362
|
-
function getCLIVersion() {
|
|
363
|
-
try {
|
|
364
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
365
|
-
const pkgPath = join2(__dirname, "..", "..", "package.json");
|
|
366
|
-
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
367
|
-
return pkg.version || "0.0.0";
|
|
368
|
-
} catch {
|
|
369
|
-
return "0.0.0";
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
function intro2(title) {
|
|
373
|
-
const version = getCLIVersion();
|
|
374
|
-
const header = pc.bold(pc.cyan("\u25C6 UILint")) + pc.dim(` v${version}`);
|
|
375
|
-
console.log();
|
|
376
|
-
p.intro(title ? `${header} ${pc.dim("\xB7")} ${title}` : header);
|
|
377
|
-
}
|
|
378
|
-
function outro2(message) {
|
|
379
|
-
p.outro(pc.green(message));
|
|
380
|
-
}
|
|
381
|
-
async function withSpinner(message, fn) {
|
|
382
|
-
const s = p.spinner();
|
|
383
|
-
s.start(message);
|
|
384
|
-
try {
|
|
385
|
-
const result = fn.length >= 1 ? await fn(s) : await fn();
|
|
386
|
-
s.stop(pc.green("\u2713 ") + message);
|
|
387
|
-
return result;
|
|
388
|
-
} catch (error) {
|
|
389
|
-
s.stop(pc.red("\u2717 ") + message);
|
|
390
|
-
throw error;
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
function createSpinner() {
|
|
394
|
-
return p.spinner();
|
|
395
|
-
}
|
|
396
|
-
function note2(message, title) {
|
|
397
|
-
p.note(message, title);
|
|
398
|
-
}
|
|
399
|
-
function logInfo(message) {
|
|
400
|
-
p.log.info(message);
|
|
401
|
-
}
|
|
402
|
-
function logSuccess(message) {
|
|
403
|
-
p.log.success(message);
|
|
404
|
-
}
|
|
405
|
-
function logWarning(message) {
|
|
406
|
-
p.log.warn(message);
|
|
407
|
-
}
|
|
408
|
-
function logError(message) {
|
|
409
|
-
p.log.error(message);
|
|
410
|
-
}
|
|
411
|
-
|
|
412
366
|
// src/utils/output.ts
|
|
413
367
|
import chalk from "chalk";
|
|
414
368
|
import {
|
|
@@ -425,9 +379,9 @@ function envTruthy(name) {
|
|
|
425
379
|
if (!v) return false;
|
|
426
380
|
return v === "1" || v.toLowerCase() === "true" || v.toLowerCase() === "yes";
|
|
427
381
|
}
|
|
428
|
-
function preview(
|
|
429
|
-
if (
|
|
430
|
-
return
|
|
382
|
+
function preview(text, maxLen) {
|
|
383
|
+
if (text.length <= maxLen) return text;
|
|
384
|
+
return text.slice(0, maxLen) + "\n\u2026<truncated>\u2026\n" + text.slice(-maxLen);
|
|
431
385
|
}
|
|
432
386
|
function debugEnabled(options) {
|
|
433
387
|
return Boolean(options.debug) || envTruthy("UILINT_DEBUG");
|
|
@@ -461,7 +415,7 @@ async function scan(options) {
|
|
|
461
415
|
const dbgFull = debugFullEnabled(options);
|
|
462
416
|
const dbgDump = debugDumpPath(options);
|
|
463
417
|
if (!isJsonOutput) {
|
|
464
|
-
|
|
418
|
+
intro("Scan for UI Issues");
|
|
465
419
|
}
|
|
466
420
|
try {
|
|
467
421
|
let snapshot;
|
|
@@ -562,7 +516,7 @@ async function scan(options) {
|
|
|
562
516
|
} else {
|
|
563
517
|
const startPath = snapshot.kind === "source" ? snapshot.inputPath : snapshot.kind === "dom" ? snapshot.inputPath : void 0;
|
|
564
518
|
if (startPath) {
|
|
565
|
-
styleguideLocation = findUILintStyleGuideUpwards(
|
|
519
|
+
styleguideLocation = findUILintStyleGuideUpwards(dirname(startPath));
|
|
566
520
|
}
|
|
567
521
|
styleguideLocation = styleguideLocation ?? findStyleGuidePath(projectPath);
|
|
568
522
|
if (styleguideLocation) {
|
|
@@ -576,12 +530,12 @@ async function scan(options) {
|
|
|
576
530
|
} else if (!styleGuide) {
|
|
577
531
|
if (!isJsonOutput) {
|
|
578
532
|
logWarning("No styleguide found");
|
|
579
|
-
|
|
533
|
+
note(
|
|
580
534
|
[
|
|
581
535
|
`Searched in: ${options.styleguide || projectPath}`,
|
|
582
536
|
"",
|
|
583
537
|
"Looked for:",
|
|
584
|
-
...STYLEGUIDE_PATHS.map((
|
|
538
|
+
...STYLEGUIDE_PATHS.map((p) => ` \u2022 ${p}`),
|
|
585
539
|
"",
|
|
586
540
|
`Create ${pc.cyan(
|
|
587
541
|
".uilint/styleguide.md"
|
|
@@ -601,7 +555,7 @@ async function scan(options) {
|
|
|
601
555
|
} else if (dbg && styleGuide) {
|
|
602
556
|
debugLog(dbg, "Styleguide contents (preview)", preview(styleGuide, 800));
|
|
603
557
|
}
|
|
604
|
-
const tailwindSearchDir = (snapshot.kind === "source" || snapshot.kind === "dom") && snapshot.inputPath ?
|
|
558
|
+
const tailwindSearchDir = (snapshot.kind === "source" || snapshot.kind === "dom") && snapshot.inputPath ? dirname(snapshot.inputPath) : projectPath;
|
|
605
559
|
const tailwindTheme = readTailwindThemeTokens(tailwindSearchDir);
|
|
606
560
|
const styleSummary = snapshot.kind === "dom" ? createStyleSummary(snapshot.snapshot.styles, {
|
|
607
561
|
html: snapshot.snapshot.html,
|
|
@@ -667,7 +621,7 @@ async function scan(options) {
|
|
|
667
621
|
const safeStamp = now.toISOString().replace(/[:.]/g, "-");
|
|
668
622
|
const resolved = resolve2(process.cwd(), dbgDump);
|
|
669
623
|
const dumpFile = resolved.endsWith(".json") || resolved.endsWith(".jsonl") ? resolved : resolve2(resolved, `scan-debug-${safeStamp}.json`);
|
|
670
|
-
mkdirSync(
|
|
624
|
+
mkdirSync(dirname(dumpFile), { recursive: true });
|
|
671
625
|
writeFileSync(
|
|
672
626
|
dumpFile,
|
|
673
627
|
JSON.stringify(
|
|
@@ -813,7 +767,7 @@ async function scan(options) {
|
|
|
813
767
|
(firstAnswerNs ?? lastThinkingNs ?? analysisEndNs) - firstThinkingNs
|
|
814
768
|
) : null;
|
|
815
769
|
const outputMs = firstAnswerNs && (lastAnswerNs || analysisEndNs) ? nsToMs((lastAnswerNs ?? analysisEndNs) - firstAnswerNs) : null;
|
|
816
|
-
|
|
770
|
+
note(
|
|
817
771
|
[
|
|
818
772
|
`Prepare Ollama: ${formatMs(prepMs)}`,
|
|
819
773
|
`Time to first token: ${maybeMs(ttftMs)}`,
|
|
@@ -863,7 +817,7 @@ async function scan(options) {
|
|
|
863
817
|
}
|
|
864
818
|
|
|
865
819
|
// src/commands/analyze.ts
|
|
866
|
-
import { dirname as
|
|
820
|
+
import { dirname as dirname2, resolve as resolve3 } from "path";
|
|
867
821
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
868
822
|
import {
|
|
869
823
|
buildSourceScanPrompt,
|
|
@@ -878,9 +832,9 @@ function envTruthy2(name) {
|
|
|
878
832
|
if (!v) return false;
|
|
879
833
|
return v === "1" || v.toLowerCase() === "true" || v.toLowerCase() === "yes";
|
|
880
834
|
}
|
|
881
|
-
function preview2(
|
|
882
|
-
if (
|
|
883
|
-
return
|
|
835
|
+
function preview2(text, maxLen) {
|
|
836
|
+
if (text.length <= maxLen) return text;
|
|
837
|
+
return text.slice(0, maxLen) + "\n\u2026<truncated>\u2026\n" + text.slice(-maxLen);
|
|
884
838
|
}
|
|
885
839
|
function debugEnabled2(options) {
|
|
886
840
|
return Boolean(options.debug) || envTruthy2("UILINT_DEBUG");
|
|
@@ -913,22 +867,22 @@ async function resolveStyleGuideForAnalyze(options) {
|
|
|
913
867
|
return { content: options.styleGuide, path: null };
|
|
914
868
|
}
|
|
915
869
|
if (options.styleguidePath && typeof options.styleguidePath === "string") {
|
|
916
|
-
const
|
|
917
|
-
if (existsSync3(
|
|
918
|
-
return { content: await readStyleGuide2(
|
|
870
|
+
const p = resolvePathSpecifier(options.styleguidePath, process.cwd());
|
|
871
|
+
if (existsSync3(p)) {
|
|
872
|
+
return { content: await readStyleGuide2(p), path: p };
|
|
919
873
|
}
|
|
920
874
|
return { content: null, path: null };
|
|
921
875
|
}
|
|
922
876
|
const env = process.env.UILINT_STYLEGUIDE_PATH;
|
|
923
877
|
if (env && env.trim()) {
|
|
924
|
-
const
|
|
925
|
-
if (existsSync3(
|
|
926
|
-
return { content: await readStyleGuide2(
|
|
878
|
+
const p = resolvePathSpecifier(env.trim(), process.cwd());
|
|
879
|
+
if (existsSync3(p)) {
|
|
880
|
+
return { content: await readStyleGuide2(p), path: p };
|
|
927
881
|
}
|
|
928
882
|
}
|
|
929
883
|
if (options.inputFile) {
|
|
930
884
|
const absInput = resolvePathSpecifier(options.inputFile, process.cwd());
|
|
931
|
-
const found = findUILintStyleGuideUpwards2(
|
|
885
|
+
const found = findUILintStyleGuideUpwards2(dirname2(absInput));
|
|
932
886
|
if (found) return { content: await readStyleGuide2(found), path: found };
|
|
933
887
|
}
|
|
934
888
|
const cwdPath = findStyleGuidePath2(process.cwd());
|
|
@@ -953,7 +907,7 @@ async function analyze(options) {
|
|
|
953
907
|
const dbgFull = debugFullEnabled2(options);
|
|
954
908
|
const dbgDump = debugDumpPath2(options);
|
|
955
909
|
if (!isJsonOutput) {
|
|
956
|
-
|
|
910
|
+
intro("Analyze Source Code");
|
|
957
911
|
}
|
|
958
912
|
try {
|
|
959
913
|
debugLog2(dbg, "Input options", {
|
|
@@ -1047,7 +1001,7 @@ async function analyze(options) {
|
|
|
1047
1001
|
const safeStamp = now.toISOString().replace(/[:.]/g, "-");
|
|
1048
1002
|
const resolved = resolve3(process.cwd(), dbgDump);
|
|
1049
1003
|
const dumpFile = resolved.endsWith(".json") || resolved.endsWith(".jsonl") ? resolved : resolve3(resolved, `analyze-debug-${safeStamp}.json`);
|
|
1050
|
-
mkdirSync2(
|
|
1004
|
+
mkdirSync2(dirname2(dumpFile), { recursive: true });
|
|
1051
1005
|
writeFileSync2(
|
|
1052
1006
|
dumpFile,
|
|
1053
1007
|
JSON.stringify(
|
|
@@ -1150,7 +1104,7 @@ async function analyze(options) {
|
|
|
1150
1104
|
(firstAnswerNs ?? lastThinkingNs ?? analysisEndNs) - firstThinkingNs
|
|
1151
1105
|
) : null;
|
|
1152
1106
|
const outputMs = firstAnswerNs && (lastAnswerNs || analysisEndNs) ? nsToMs((lastAnswerNs ?? analysisEndNs) - firstAnswerNs) : null;
|
|
1153
|
-
|
|
1107
|
+
note(
|
|
1154
1108
|
[
|
|
1155
1109
|
`Prepare Ollama: ${formatMs(prepMs)}`,
|
|
1156
1110
|
`Time to first token: ${maybeMs(ttftMs)}`,
|
|
@@ -1228,7 +1182,7 @@ async function readStdin2() {
|
|
|
1228
1182
|
async function consistency(options) {
|
|
1229
1183
|
const isJsonOutput = options.output === "json";
|
|
1230
1184
|
if (!isJsonOutput) {
|
|
1231
|
-
|
|
1185
|
+
intro("UI Consistency Analysis");
|
|
1232
1186
|
}
|
|
1233
1187
|
try {
|
|
1234
1188
|
let inputJson = options.inputJson;
|
|
@@ -1318,7 +1272,7 @@ async function consistency(options) {
|
|
|
1318
1272
|
}
|
|
1319
1273
|
|
|
1320
1274
|
// src/commands/update.ts
|
|
1321
|
-
import { dirname as
|
|
1275
|
+
import { dirname as dirname3, resolve as resolve4 } from "path";
|
|
1322
1276
|
import {
|
|
1323
1277
|
createStyleSummary as createStyleSummary2,
|
|
1324
1278
|
parseStyleGuide,
|
|
@@ -1333,13 +1287,13 @@ import {
|
|
|
1333
1287
|
readTailwindThemeTokens as readTailwindThemeTokens2
|
|
1334
1288
|
} from "uilint-core/node";
|
|
1335
1289
|
async function update(options) {
|
|
1336
|
-
|
|
1290
|
+
intro("Update Style Guide");
|
|
1337
1291
|
try {
|
|
1338
1292
|
const projectPath = process.cwd();
|
|
1339
1293
|
const styleGuidePath = options.styleguide || findStyleGuidePath3(projectPath);
|
|
1340
1294
|
if (!styleGuidePath) {
|
|
1341
1295
|
logError("No style guide found");
|
|
1342
|
-
|
|
1296
|
+
note(
|
|
1343
1297
|
`Create ${pc.cyan(
|
|
1344
1298
|
".uilint/styleguide.md"
|
|
1345
1299
|
)} first (recommended: run ${pc.cyan("/genstyleguide")} in Cursor).`,
|
|
@@ -1362,7 +1316,7 @@ async function update(options) {
|
|
|
1362
1316
|
process.exit(1);
|
|
1363
1317
|
}
|
|
1364
1318
|
logInfo(`Found ${pc.cyan(String(snapshot.elementCount))} elements`);
|
|
1365
|
-
const tailwindSearchDir = options.inputFile ?
|
|
1319
|
+
const tailwindSearchDir = options.inputFile ? dirname3(resolve4(projectPath, options.inputFile)) : projectPath;
|
|
1366
1320
|
const tailwindTheme = readTailwindThemeTokens2(tailwindSearchDir);
|
|
1367
1321
|
if (options.llm) {
|
|
1368
1322
|
await withSpinner("Preparing Ollama", async () => {
|
|
@@ -1388,15 +1342,15 @@ async function update(options) {
|
|
|
1388
1342
|
}
|
|
1389
1343
|
return line;
|
|
1390
1344
|
});
|
|
1391
|
-
|
|
1345
|
+
note(
|
|
1392
1346
|
suggestions.join("\n\n"),
|
|
1393
1347
|
`Found ${result.issues.length} suggestion(s)`
|
|
1394
1348
|
);
|
|
1395
1349
|
logInfo("Edit the styleguide manually to apply these changes.");
|
|
1396
|
-
|
|
1350
|
+
outro("Analysis complete");
|
|
1397
1351
|
} else {
|
|
1398
1352
|
logSuccess("Style guide is up to date!");
|
|
1399
|
-
|
|
1353
|
+
outro("No changes needed");
|
|
1400
1354
|
}
|
|
1401
1355
|
} else {
|
|
1402
1356
|
const updatedContent = await withSpinner("Merging styles", async () => {
|
|
@@ -1422,14 +1376,14 @@ async function update(options) {
|
|
|
1422
1376
|
});
|
|
1423
1377
|
if (updatedContent === existingContent) {
|
|
1424
1378
|
logSuccess("Style guide is already up to date!");
|
|
1425
|
-
|
|
1379
|
+
outro("No changes needed");
|
|
1426
1380
|
return;
|
|
1427
1381
|
}
|
|
1428
1382
|
await withSpinner("Writing styleguide", async () => {
|
|
1429
1383
|
await writeStyleGuide(styleGuidePath, updatedContent);
|
|
1430
1384
|
});
|
|
1431
|
-
|
|
1432
|
-
|
|
1385
|
+
note(`Updated: ${pc.dim(styleGuidePath)}`, "Success");
|
|
1386
|
+
outro("Style guide updated!");
|
|
1433
1387
|
}
|
|
1434
1388
|
} catch (error) {
|
|
1435
1389
|
logError(error instanceof Error ? error.message : "Update failed");
|
|
@@ -1440,9 +1394,9 @@ async function update(options) {
|
|
|
1440
1394
|
}
|
|
1441
1395
|
|
|
1442
1396
|
// src/commands/serve.ts
|
|
1443
|
-
import { existsSync as existsSync5, statSync as statSync3, readdirSync, readFileSync
|
|
1397
|
+
import { existsSync as existsSync5, statSync as statSync3, readdirSync, readFileSync } from "fs";
|
|
1444
1398
|
import { createRequire } from "module";
|
|
1445
|
-
import { dirname as
|
|
1399
|
+
import { dirname as dirname5, resolve as resolve5, relative, join as join3, parse as parse2 } from "path";
|
|
1446
1400
|
import { WebSocketServer, WebSocket } from "ws";
|
|
1447
1401
|
import { watch } from "chokidar";
|
|
1448
1402
|
import {
|
|
@@ -1451,7 +1405,7 @@ import {
|
|
|
1451
1405
|
} from "uilint-core/node";
|
|
1452
1406
|
|
|
1453
1407
|
// src/utils/vision-run.ts
|
|
1454
|
-
import { dirname as
|
|
1408
|
+
import { dirname as dirname4, join as join2, parse } from "path";
|
|
1455
1409
|
import { existsSync as existsSync4, statSync as statSync2, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
1456
1410
|
import {
|
|
1457
1411
|
ensureOllamaReady as ensureOllamaReady5,
|
|
@@ -1496,12 +1450,12 @@ async function ensureOllamaReadyCached(params) {
|
|
|
1496
1450
|
const key = `${params.baseUrl}::${params.model}`;
|
|
1497
1451
|
const existing = ollamaReadyOnce.get(key);
|
|
1498
1452
|
if (existing) return existing;
|
|
1499
|
-
const
|
|
1453
|
+
const p = ensureOllamaReady5({ model: params.model, baseUrl: params.baseUrl }).then(() => void 0).catch((e) => {
|
|
1500
1454
|
ollamaReadyOnce.delete(key);
|
|
1501
1455
|
throw e;
|
|
1502
1456
|
});
|
|
1503
|
-
ollamaReadyOnce.set(key,
|
|
1504
|
-
return
|
|
1457
|
+
ollamaReadyOnce.set(key, p);
|
|
1458
|
+
return p;
|
|
1505
1459
|
}
|
|
1506
1460
|
function writeVisionDebugDump(params) {
|
|
1507
1461
|
const resolvedDirOrFile = resolvePathSpecifier(
|
|
@@ -1510,7 +1464,7 @@ function writeVisionDebugDump(params) {
|
|
|
1510
1464
|
);
|
|
1511
1465
|
const safeStamp = params.now.toISOString().replace(/[:.]/g, "-");
|
|
1512
1466
|
const dumpFile = resolvedDirOrFile.endsWith(".json") || resolvedDirOrFile.endsWith(".jsonl") ? resolvedDirOrFile : `${resolvedDirOrFile}/vision-debug-${safeStamp}.json`;
|
|
1513
|
-
mkdirSync3(
|
|
1467
|
+
mkdirSync3(dirname4(dumpFile), { recursive: true });
|
|
1514
1468
|
writeFileSync3(
|
|
1515
1469
|
dumpFile,
|
|
1516
1470
|
JSON.stringify(
|
|
@@ -1598,12 +1552,12 @@ async function runVisionAnalysis(args) {
|
|
|
1598
1552
|
};
|
|
1599
1553
|
}
|
|
1600
1554
|
function writeVisionMarkdownReport(args) {
|
|
1601
|
-
const
|
|
1602
|
-
const outPath = args.outPath ??
|
|
1555
|
+
const p = parse(args.imagePath);
|
|
1556
|
+
const outPath = args.outPath ?? join2(p.dir, `${p.name || p.base}.vision.md`);
|
|
1603
1557
|
const lines = [];
|
|
1604
1558
|
lines.push(`# UILint Vision Report`);
|
|
1605
1559
|
lines.push(``);
|
|
1606
|
-
lines.push(`- Image: \`${
|
|
1560
|
+
lines.push(`- Image: \`${p.base}\``);
|
|
1607
1561
|
if (args.route) lines.push(`- Route: \`${args.route}\``);
|
|
1608
1562
|
if (typeof args.timestamp === "number") {
|
|
1609
1563
|
lines.push(`- Timestamp: \`${new Date(args.timestamp).toISOString()}\``);
|
|
@@ -1635,7 +1589,7 @@ function writeVisionMarkdownReport(args) {
|
|
|
1635
1589
|
lines.push("```");
|
|
1636
1590
|
lines.push(``);
|
|
1637
1591
|
const content = lines.join("\n");
|
|
1638
|
-
mkdirSync3(
|
|
1592
|
+
mkdirSync3(dirname4(outPath), { recursive: true });
|
|
1639
1593
|
writeFileSync3(outPath, content, "utf-8");
|
|
1640
1594
|
return { outPath, content };
|
|
1641
1595
|
}
|
|
@@ -1749,17 +1703,17 @@ function findESLintCwd(startDir) {
|
|
|
1749
1703
|
let dir = startDir;
|
|
1750
1704
|
for (let i = 0; i < 30; i++) {
|
|
1751
1705
|
for (const cfg of ESLINT_CONFIG_FILES) {
|
|
1752
|
-
if (existsSync5(
|
|
1706
|
+
if (existsSync5(join3(dir, cfg))) return dir;
|
|
1753
1707
|
}
|
|
1754
|
-
if (existsSync5(
|
|
1755
|
-
const parent =
|
|
1708
|
+
if (existsSync5(join3(dir, "package.json"))) return dir;
|
|
1709
|
+
const parent = dirname5(dir);
|
|
1756
1710
|
if (parent === dir) break;
|
|
1757
1711
|
dir = parent;
|
|
1758
1712
|
}
|
|
1759
1713
|
return startDir;
|
|
1760
1714
|
}
|
|
1761
|
-
function normalizePathSlashes(
|
|
1762
|
-
return
|
|
1715
|
+
function normalizePathSlashes(p) {
|
|
1716
|
+
return p.replace(/\\/g, "/");
|
|
1763
1717
|
}
|
|
1764
1718
|
function normalizeDataLocFilePath(absoluteFilePath, projectCwd) {
|
|
1765
1719
|
const abs = normalizePathSlashes(resolve5(absoluteFilePath));
|
|
@@ -1788,16 +1742,16 @@ function resolveRequestedFilePath(filePath) {
|
|
|
1788
1742
|
return fromWs;
|
|
1789
1743
|
}
|
|
1790
1744
|
for (const top of ["apps", "packages"]) {
|
|
1791
|
-
const base =
|
|
1745
|
+
const base = join3(wsRoot, top);
|
|
1792
1746
|
if (!existsSync5(base)) continue;
|
|
1793
1747
|
try {
|
|
1794
1748
|
const entries = readdirSync(base, { withFileTypes: true });
|
|
1795
1749
|
for (const ent of entries) {
|
|
1796
1750
|
if (!ent.isDirectory()) continue;
|
|
1797
|
-
const
|
|
1798
|
-
if (existsSync5(
|
|
1799
|
-
resolvedPathCache.set(filePath,
|
|
1800
|
-
return
|
|
1751
|
+
const p = resolve5(base, ent.name, filePath);
|
|
1752
|
+
if (existsSync5(p)) {
|
|
1753
|
+
resolvedPathCache.set(filePath, p);
|
|
1754
|
+
return p;
|
|
1801
1755
|
}
|
|
1802
1756
|
}
|
|
1803
1757
|
} catch {
|
|
@@ -1810,7 +1764,7 @@ async function getESLintForProject(projectCwd) {
|
|
|
1810
1764
|
const cached = eslintInstances.get(projectCwd);
|
|
1811
1765
|
if (cached) return cached;
|
|
1812
1766
|
try {
|
|
1813
|
-
const req = createRequire(
|
|
1767
|
+
const req = createRequire(join3(projectCwd, "package.json"));
|
|
1814
1768
|
const mod = req("eslint");
|
|
1815
1769
|
const ESLintCtor = mod?.ESLint ?? mod?.default?.ESLint ?? mod?.default ?? mod;
|
|
1816
1770
|
if (!ESLintCtor) return null;
|
|
@@ -1839,7 +1793,7 @@ async function lintFile(filePath, onProgress) {
|
|
|
1839
1793
|
onProgress("Cache hit (unchanged)");
|
|
1840
1794
|
return cached.issues;
|
|
1841
1795
|
}
|
|
1842
|
-
const fileDir =
|
|
1796
|
+
const fileDir = dirname5(absolutePath);
|
|
1843
1797
|
const projectCwd = findESLintCwd(fileDir);
|
|
1844
1798
|
onProgress(`Resolving ESLint project... ${pc.dim(projectCwd)}`);
|
|
1845
1799
|
const eslint = await getESLintForProject(projectCwd);
|
|
@@ -1862,7 +1816,7 @@ async function lintFile(filePath, onProgress) {
|
|
|
1862
1816
|
let codeLength = 0;
|
|
1863
1817
|
try {
|
|
1864
1818
|
onProgress("Building JSX map...");
|
|
1865
|
-
const code =
|
|
1819
|
+
const code = readFileSync(absolutePath, "utf-8");
|
|
1866
1820
|
codeLength = code.length;
|
|
1867
1821
|
lineStarts = buildLineStarts(code);
|
|
1868
1822
|
spans = buildJsxElementSpans(code, dataLocFile);
|
|
@@ -2106,12 +2060,12 @@ async function handleMessage(ws, data) {
|
|
|
2106
2060
|
)}`
|
|
2107
2061
|
);
|
|
2108
2062
|
} else {
|
|
2109
|
-
const screenshotsDir =
|
|
2063
|
+
const screenshotsDir = join3(
|
|
2110
2064
|
serverAppRootForVision,
|
|
2111
2065
|
".uilint",
|
|
2112
2066
|
"screenshots"
|
|
2113
2067
|
);
|
|
2114
|
-
const imagePath =
|
|
2068
|
+
const imagePath = join3(screenshotsDir, screenshotFile);
|
|
2115
2069
|
try {
|
|
2116
2070
|
if (!existsSync5(imagePath)) {
|
|
2117
2071
|
logWarning(
|
|
@@ -2274,10 +2228,10 @@ async function serve(options) {
|
|
|
2274
2228
|
}
|
|
2275
2229
|
|
|
2276
2230
|
// src/commands/vision.ts
|
|
2277
|
-
import { dirname as
|
|
2231
|
+
import { dirname as dirname6, resolve as resolve6, join as join4 } from "path";
|
|
2278
2232
|
import {
|
|
2279
2233
|
existsSync as existsSync6,
|
|
2280
|
-
readFileSync as
|
|
2234
|
+
readFileSync as readFileSync2,
|
|
2281
2235
|
readdirSync as readdirSync2
|
|
2282
2236
|
} from "fs";
|
|
2283
2237
|
import {
|
|
@@ -2290,9 +2244,9 @@ function envTruthy3(name) {
|
|
|
2290
2244
|
if (!v) return false;
|
|
2291
2245
|
return v === "1" || v.toLowerCase() === "true" || v.toLowerCase() === "yes";
|
|
2292
2246
|
}
|
|
2293
|
-
function preview3(
|
|
2294
|
-
if (
|
|
2295
|
-
return
|
|
2247
|
+
function preview3(text, maxLen) {
|
|
2248
|
+
if (text.length <= maxLen) return text;
|
|
2249
|
+
return text.slice(0, maxLen) + "\n\u2026<truncated>\u2026\n" + text.slice(-maxLen);
|
|
2296
2250
|
}
|
|
2297
2251
|
function debugEnabled3(options) {
|
|
2298
2252
|
return Boolean(options.debug) || envTruthy3("UILINT_DEBUG");
|
|
@@ -2323,9 +2277,9 @@ function debugLog3(enabled, message, obj) {
|
|
|
2323
2277
|
function findScreenshotsDirUpwards(startDir) {
|
|
2324
2278
|
let dir = startDir;
|
|
2325
2279
|
for (let i = 0; i < 20; i++) {
|
|
2326
|
-
const candidate =
|
|
2280
|
+
const candidate = join4(dir, ".uilint", "screenshots");
|
|
2327
2281
|
if (existsSync6(candidate)) return candidate;
|
|
2328
|
-
const parent =
|
|
2282
|
+
const parent = dirname6(dir);
|
|
2329
2283
|
if (parent === dir) break;
|
|
2330
2284
|
dir = parent;
|
|
2331
2285
|
}
|
|
@@ -2333,23 +2287,23 @@ function findScreenshotsDirUpwards(startDir) {
|
|
|
2333
2287
|
}
|
|
2334
2288
|
function listScreenshotSidecars(dirPath) {
|
|
2335
2289
|
if (!existsSync6(dirPath)) return [];
|
|
2336
|
-
const entries = readdirSync2(dirPath).filter((f) => f.endsWith(".json")).map((f) =>
|
|
2290
|
+
const entries = readdirSync2(dirPath).filter((f) => f.endsWith(".json")).map((f) => join4(dirPath, f));
|
|
2337
2291
|
const out = [];
|
|
2338
|
-
for (const
|
|
2292
|
+
for (const p of entries) {
|
|
2339
2293
|
try {
|
|
2340
|
-
const json = loadJsonFile(
|
|
2294
|
+
const json = loadJsonFile(p);
|
|
2341
2295
|
const issues = Array.isArray(json?.issues) ? json.issues : json?.analysisResult?.issues;
|
|
2342
2296
|
out.push({
|
|
2343
|
-
path:
|
|
2344
|
-
filename: json?.filename || json?.screenshotFile ||
|
|
2297
|
+
path: p,
|
|
2298
|
+
filename: json?.filename || json?.screenshotFile || p.split("/").pop() || p,
|
|
2345
2299
|
timestamp: typeof json?.timestamp === "number" ? json.timestamp : void 0,
|
|
2346
2300
|
route: typeof json?.route === "string" ? json.route : void 0,
|
|
2347
2301
|
issueCount: Array.isArray(issues) ? issues.length : void 0
|
|
2348
2302
|
});
|
|
2349
2303
|
} catch {
|
|
2350
2304
|
out.push({
|
|
2351
|
-
path:
|
|
2352
|
-
filename:
|
|
2305
|
+
path: p,
|
|
2306
|
+
filename: p.split("/").pop() || p
|
|
2353
2307
|
});
|
|
2354
2308
|
}
|
|
2355
2309
|
}
|
|
@@ -2362,11 +2316,11 @@ function listScreenshotSidecars(dirPath) {
|
|
|
2362
2316
|
return out;
|
|
2363
2317
|
}
|
|
2364
2318
|
function readImageAsBase64(imagePath) {
|
|
2365
|
-
const bytes =
|
|
2319
|
+
const bytes = readFileSync2(imagePath);
|
|
2366
2320
|
return { base64: bytes.toString("base64"), sizeBytes: bytes.byteLength };
|
|
2367
2321
|
}
|
|
2368
2322
|
function loadJsonFile(filePath) {
|
|
2369
|
-
const raw =
|
|
2323
|
+
const raw = readFileSync2(filePath, "utf-8");
|
|
2370
2324
|
return JSON.parse(raw);
|
|
2371
2325
|
}
|
|
2372
2326
|
function formatIssuesText(issues) {
|
|
@@ -2383,7 +2337,7 @@ async function vision(options) {
|
|
|
2383
2337
|
const dbg = debugEnabled3(options);
|
|
2384
2338
|
const dbgFull = debugFullEnabled3(options);
|
|
2385
2339
|
const dbgDump = debugDumpPath3(options);
|
|
2386
|
-
if (!isJsonOutput)
|
|
2340
|
+
if (!isJsonOutput) intro("Vision (Screenshot) Analysis");
|
|
2387
2341
|
try {
|
|
2388
2342
|
const projectPath = process.cwd();
|
|
2389
2343
|
if (options.list) {
|
|
@@ -2471,7 +2425,7 @@ async function vision(options) {
|
|
|
2471
2425
|
const resolved = await resolveVisionStyleGuide({
|
|
2472
2426
|
projectPath,
|
|
2473
2427
|
styleguide: options.styleguide,
|
|
2474
|
-
startDir: startPath ?
|
|
2428
|
+
startDir: startPath ? dirname6(startPath) : projectPath
|
|
2475
2429
|
});
|
|
2476
2430
|
styleGuide = resolved.styleGuide;
|
|
2477
2431
|
styleguideLocation = resolved.styleguideLocation;
|
|
@@ -2481,12 +2435,12 @@ async function vision(options) {
|
|
|
2481
2435
|
logSuccess(`Using styleguide: ${pc.dim(styleguideLocation)}`);
|
|
2482
2436
|
} else if (!styleGuide && !isJsonOutput) {
|
|
2483
2437
|
logWarning("No styleguide found");
|
|
2484
|
-
|
|
2438
|
+
note(
|
|
2485
2439
|
[
|
|
2486
2440
|
`Searched in: ${options.styleguide || projectPath}`,
|
|
2487
2441
|
"",
|
|
2488
2442
|
"Looked for:",
|
|
2489
|
-
...STYLEGUIDE_PATHS2.map((
|
|
2443
|
+
...STYLEGUIDE_PATHS2.map((p) => ` \u2022 ${p}`),
|
|
2490
2444
|
"",
|
|
2491
2445
|
`Create ${pc.cyan(
|
|
2492
2446
|
".uilint/styleguide.md"
|
|
@@ -2520,7 +2474,7 @@ async function vision(options) {
|
|
|
2520
2474
|
const resolvedImagePath = imagePath || (() => {
|
|
2521
2475
|
const screenshotFile = typeof sidecar?.screenshotFile === "string" ? sidecar.screenshotFile : typeof sidecar?.filename === "string" ? sidecar.filename : void 0;
|
|
2522
2476
|
if (!screenshotFile) return null;
|
|
2523
|
-
const baseDir = sidecarPath ?
|
|
2477
|
+
const baseDir = sidecarPath ? dirname6(sidecarPath) : projectPath;
|
|
2524
2478
|
const abs = resolve6(baseDir, screenshotFile);
|
|
2525
2479
|
return abs;
|
|
2526
2480
|
})();
|
|
@@ -2699,7 +2653,7 @@ async function vision(options) {
|
|
|
2699
2653
|
(firstAnswerNs ?? lastThinkingNs ?? analysisEndNs) - firstThinkingNs
|
|
2700
2654
|
) : null;
|
|
2701
2655
|
const outputMs = firstAnswerNs && (lastAnswerNs || analysisEndNs) ? nsToMs((lastAnswerNs ?? analysisEndNs) - firstAnswerNs) : null;
|
|
2702
|
-
|
|
2656
|
+
note(
|
|
2703
2657
|
[
|
|
2704
2658
|
`Prepare Ollama: ${formatMs(prepMs)}`,
|
|
2705
2659
|
`Time to first token: ${maybeMs(ttftMs)}`,
|
|
@@ -2757,9 +2711,9 @@ async function vision(options) {
|
|
|
2757
2711
|
}
|
|
2758
2712
|
|
|
2759
2713
|
// src/index.ts
|
|
2760
|
-
import { readFileSync as
|
|
2761
|
-
import { dirname as
|
|
2762
|
-
import { fileURLToPath
|
|
2714
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
2715
|
+
import { dirname as dirname7, join as join5 } from "path";
|
|
2716
|
+
import { fileURLToPath } from "url";
|
|
2763
2717
|
function assertNodeVersion(minMajor) {
|
|
2764
2718
|
const ver = process.versions.node || "";
|
|
2765
2719
|
const majorStr = ver.split(".")[0] || "";
|
|
@@ -2773,17 +2727,17 @@ function assertNodeVersion(minMajor) {
|
|
|
2773
2727
|
}
|
|
2774
2728
|
assertNodeVersion(20);
|
|
2775
2729
|
var program = new Command();
|
|
2776
|
-
function
|
|
2730
|
+
function getCLIVersion() {
|
|
2777
2731
|
try {
|
|
2778
|
-
const __dirname =
|
|
2779
|
-
const pkgPath =
|
|
2780
|
-
const pkg = JSON.parse(
|
|
2732
|
+
const __dirname = dirname7(fileURLToPath(import.meta.url));
|
|
2733
|
+
const pkgPath = join5(__dirname, "..", "package.json");
|
|
2734
|
+
const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
|
|
2781
2735
|
return pkg.version || "0.0.0";
|
|
2782
2736
|
} catch {
|
|
2783
2737
|
return "0.0.0";
|
|
2784
2738
|
}
|
|
2785
2739
|
}
|
|
2786
|
-
program.name("uilint").description("AI-powered UI consistency checker").version(
|
|
2740
|
+
program.name("uilint").description("AI-powered UI consistency checker").version(getCLIVersion());
|
|
2787
2741
|
program.command("analyze").description(
|
|
2788
2742
|
"Analyze a source file/snippet for style issues (data-loc aware)"
|
|
2789
2743
|
).option("-f, --input-file <path>", "Path to a source file to analyze").option("--source-code <code>", "Source code to analyze (string)").option("--file-path <path>", "File path label shown in the prompt").option("--style-guide <text>", "Inline styleguide content to use").option("--styleguide-path <path>", "Path to a style guide file").option("--component-name <name>", "Component name for focused analysis").option(
|
|
@@ -2851,7 +2805,7 @@ program.command("update").description("Update existing style guide with new styl
|
|
|
2851
2805
|
});
|
|
2852
2806
|
});
|
|
2853
2807
|
program.command("install").description("Install UILint integration").option("--force", "Overwrite existing configuration files").action(async (options) => {
|
|
2854
|
-
const { installUI } = await import("./install-ui-
|
|
2808
|
+
const { installUI } = await import("./install-ui-KI7YHOVZ.js");
|
|
2855
2809
|
await installUI({ force: options.force });
|
|
2856
2810
|
});
|
|
2857
2811
|
program.command("serve").description("Start WebSocket server for real-time UI linting").option("-p, --port <number>", "Port to listen on", "9234").action(async (options) => {
|