uilint 0.2.108 → 0.2.110
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-6CC356LV.js +206 -0
- package/dist/chunk-6CC356LV.js.map +1 -0
- package/dist/index.js +299 -147
- package/dist/index.js.map +1 -1
- package/dist/render-KO44SVF3.js +393 -0
- package/dist/render-KO44SVF3.js.map +1 -0
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
getDashboardStore
|
|
4
|
+
} from "./chunk-6CC356LV.js";
|
|
2
5
|
import {
|
|
3
6
|
detectCoverageSetup,
|
|
4
7
|
detectNextAppRouter,
|
|
@@ -1604,6 +1607,190 @@ function writeVisionMarkdownReport(args) {
|
|
|
1604
1607
|
return { outPath, content };
|
|
1605
1608
|
}
|
|
1606
1609
|
|
|
1610
|
+
// src/commands/serve/dashboard/logger.ts
|
|
1611
|
+
import pc2 from "picocolors";
|
|
1612
|
+
function consoleInfo(message) {
|
|
1613
|
+
console.log(pc2.blue("i") + " " + message);
|
|
1614
|
+
}
|
|
1615
|
+
function consoleSuccess(message) {
|
|
1616
|
+
console.log(pc2.green("\u2713") + " " + message);
|
|
1617
|
+
}
|
|
1618
|
+
function consoleWarning(message) {
|
|
1619
|
+
console.log(pc2.yellow("\u26A0") + " " + message);
|
|
1620
|
+
}
|
|
1621
|
+
function consoleError(message) {
|
|
1622
|
+
console.log(pc2.red("\u2717") + " " + message);
|
|
1623
|
+
}
|
|
1624
|
+
var useDashboard = false;
|
|
1625
|
+
function enableDashboard() {
|
|
1626
|
+
useDashboard = true;
|
|
1627
|
+
}
|
|
1628
|
+
function disableDashboard() {
|
|
1629
|
+
useDashboard = false;
|
|
1630
|
+
}
|
|
1631
|
+
function isDashboardEnabled() {
|
|
1632
|
+
return useDashboard;
|
|
1633
|
+
}
|
|
1634
|
+
function logActivity(type, message, detail, isError, isWarning) {
|
|
1635
|
+
if (useDashboard) {
|
|
1636
|
+
const store = getDashboardStore();
|
|
1637
|
+
store.addActivity({ type, message, detail, isError, isWarning });
|
|
1638
|
+
} else {
|
|
1639
|
+
const prefix = `${pc2.dim("[ws]")} `;
|
|
1640
|
+
if (isError) {
|
|
1641
|
+
consoleError(prefix + message + (detail ? `
|
|
1642
|
+
${detail}` : ""));
|
|
1643
|
+
} else if (isWarning) {
|
|
1644
|
+
consoleWarning(prefix + message + (detail ? `
|
|
1645
|
+
${detail}` : ""));
|
|
1646
|
+
} else {
|
|
1647
|
+
consoleInfo(prefix + message + (detail ? `
|
|
1648
|
+
${detail}` : ""));
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
function logLint(filePath, requestId) {
|
|
1653
|
+
const msg = filePath + (requestId ? ` (req ${requestId})` : "");
|
|
1654
|
+
logActivity("lint:file", msg);
|
|
1655
|
+
}
|
|
1656
|
+
function logLintDone(filePath, issueCount, elapsedMs) {
|
|
1657
|
+
logActivity("lint:done", `${filePath} \u2192 ${issueCount} issue(s) (${elapsedMs}ms)`);
|
|
1658
|
+
}
|
|
1659
|
+
function logSubscribe(filePath) {
|
|
1660
|
+
logActivity("subscribe", filePath);
|
|
1661
|
+
}
|
|
1662
|
+
function logCacheInvalidate(filePath) {
|
|
1663
|
+
logActivity("cache:invalidate", filePath ?? "(all)");
|
|
1664
|
+
}
|
|
1665
|
+
function logVisionAnalyze(route, requestId) {
|
|
1666
|
+
const msg = route + (requestId ? ` (req ${requestId})` : "");
|
|
1667
|
+
logActivity("vision:analyze", msg);
|
|
1668
|
+
}
|
|
1669
|
+
function logVisionDone(route, issueCount, elapsedMs) {
|
|
1670
|
+
logActivity("vision:done", `${route} \u2192 ${issueCount} issue(s) (${elapsedMs}ms)`);
|
|
1671
|
+
}
|
|
1672
|
+
function logVisionCheck(requestId) {
|
|
1673
|
+
logActivity("vision:check", requestId ? `(req ${requestId})` : "");
|
|
1674
|
+
}
|
|
1675
|
+
function logConfigSet(key, value) {
|
|
1676
|
+
logActivity("config:set", `${key} = ${JSON.stringify(value)}`);
|
|
1677
|
+
}
|
|
1678
|
+
function logRuleConfigSet(ruleId, severity, hasOptions) {
|
|
1679
|
+
logActivity(
|
|
1680
|
+
"rule:config:set",
|
|
1681
|
+
`${ruleId} \u2192 ${severity}${hasOptions ? " (with options)" : ""}`
|
|
1682
|
+
);
|
|
1683
|
+
}
|
|
1684
|
+
function logScreenshotSave(route, requestId) {
|
|
1685
|
+
const msg = route + (requestId ? ` (req ${requestId})` : "");
|
|
1686
|
+
logActivity("screenshot:save", msg);
|
|
1687
|
+
}
|
|
1688
|
+
function logScreenshotSaved(filename, sizeKb) {
|
|
1689
|
+
logActivity("screenshot:saved", `${filename} (${sizeKb}kb)`);
|
|
1690
|
+
}
|
|
1691
|
+
function logCoverageResult(fileCount) {
|
|
1692
|
+
logActivity("coverage:result", `${fileCount} files`);
|
|
1693
|
+
}
|
|
1694
|
+
function logClientConnect(totalClients) {
|
|
1695
|
+
if (useDashboard) {
|
|
1696
|
+
const store = getDashboardStore();
|
|
1697
|
+
store.incrementClients();
|
|
1698
|
+
store.addActivity({ type: "client:connect", message: `(${totalClients} total)` });
|
|
1699
|
+
} else {
|
|
1700
|
+
consoleInfo(`Client connected (${totalClients} total)`);
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
function logClientDisconnect(totalClients) {
|
|
1704
|
+
if (useDashboard) {
|
|
1705
|
+
const store = getDashboardStore();
|
|
1706
|
+
store.decrementClients();
|
|
1707
|
+
store.addActivity({ type: "client:disconnect", message: `(${totalClients} total)` });
|
|
1708
|
+
} else {
|
|
1709
|
+
consoleInfo(`Client disconnected (${totalClients} total)`);
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
function logServerError(message, detail) {
|
|
1713
|
+
logActivity("error", message, detail, true);
|
|
1714
|
+
}
|
|
1715
|
+
function logServerWarning(message, detail) {
|
|
1716
|
+
logActivity("warning", message, detail, false, true);
|
|
1717
|
+
}
|
|
1718
|
+
function logServerInfo(message, detail) {
|
|
1719
|
+
logActivity("info", message, detail);
|
|
1720
|
+
}
|
|
1721
|
+
function setWorkspaceInfo(workspaceRoot, appRoot, serverCwd) {
|
|
1722
|
+
if (useDashboard) {
|
|
1723
|
+
const store = getDashboardStore();
|
|
1724
|
+
store.setWorkspace({ workspaceRoot, appRoot, serverCwd });
|
|
1725
|
+
} else {
|
|
1726
|
+
consoleInfo(`Workspace root: ${pc2.dim(workspaceRoot)}`);
|
|
1727
|
+
consoleInfo(`App root: ${pc2.dim(appRoot)}`);
|
|
1728
|
+
consoleInfo(`Server cwd: ${pc2.dim(serverCwd)}`);
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
function setServerRunning(port) {
|
|
1732
|
+
if (useDashboard) {
|
|
1733
|
+
const store = getDashboardStore();
|
|
1734
|
+
store.setPort(port);
|
|
1735
|
+
store.setRunning(true);
|
|
1736
|
+
} else {
|
|
1737
|
+
consoleSuccess(
|
|
1738
|
+
`UILint WebSocket server running on ${pc2.cyan(`ws://localhost:${port}`)}`
|
|
1739
|
+
);
|
|
1740
|
+
consoleInfo("Press Ctrl+C to stop");
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
function updateSubscriptionCount(count) {
|
|
1744
|
+
if (useDashboard) {
|
|
1745
|
+
const store = getDashboardStore();
|
|
1746
|
+
store.updateStats({ subscriptions: count });
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
function updateCacheCount(count) {
|
|
1750
|
+
if (useDashboard) {
|
|
1751
|
+
const store = getDashboardStore();
|
|
1752
|
+
store.setCacheEntries(count);
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
function startBackgroundTask(id, name, message) {
|
|
1756
|
+
if (useDashboard) {
|
|
1757
|
+
const store = getDashboardStore();
|
|
1758
|
+
store.setBackgroundTask({
|
|
1759
|
+
id,
|
|
1760
|
+
name,
|
|
1761
|
+
status: "running",
|
|
1762
|
+
progress: 0,
|
|
1763
|
+
message
|
|
1764
|
+
});
|
|
1765
|
+
} else {
|
|
1766
|
+
consoleInfo(`${pc2.blue(name)}...`);
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
function updateBackgroundTaskProgress(id, progress, current, total, message) {
|
|
1770
|
+
if (useDashboard) {
|
|
1771
|
+
const store = getDashboardStore();
|
|
1772
|
+
store.updateBackgroundTaskProgress(id, progress, current, total, message);
|
|
1773
|
+
} else if (message) {
|
|
1774
|
+
if (current !== void 0 && total !== void 0) {
|
|
1775
|
+
consoleInfo(` ${message} (${current}/${total})`);
|
|
1776
|
+
} else {
|
|
1777
|
+
consoleInfo(` ${message}`);
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
function completeBackgroundTask(id, successMessage, error) {
|
|
1782
|
+
if (useDashboard) {
|
|
1783
|
+
const store = getDashboardStore();
|
|
1784
|
+
store.completeBackgroundTask(id, error);
|
|
1785
|
+
} else {
|
|
1786
|
+
if (error) {
|
|
1787
|
+
consoleError(`Failed: ${error}`);
|
|
1788
|
+
} else if (successMessage) {
|
|
1789
|
+
consoleSuccess(successMessage);
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1607
1794
|
// src/commands/serve.ts
|
|
1608
1795
|
import { ruleRegistry } from "uilint-eslint";
|
|
1609
1796
|
function pickAppRoot(params) {
|
|
@@ -1916,25 +2103,17 @@ async function handleMessage(ws, data) {
|
|
|
1916
2103
|
if (message.type === "lint:file" || message.type === "lint:element") {
|
|
1917
2104
|
const fp = message.filePath;
|
|
1918
2105
|
const rid = message.requestId;
|
|
1919
|
-
|
|
1920
|
-
`${pc.dim("[ws]")} ${pc.bold(message.type)} ${pc.dim(fp ?? "")}${rid ? ` ${pc.dim(`(req ${rid})`)}` : ""}`
|
|
1921
|
-
);
|
|
2106
|
+
logLint(fp ?? "", rid);
|
|
1922
2107
|
} else if (message.type === "subscribe:file") {
|
|
1923
|
-
|
|
2108
|
+
logSubscribe(message.filePath);
|
|
1924
2109
|
} else if (message.type === "cache:invalidate") {
|
|
1925
|
-
|
|
1926
|
-
`${pc.dim("[ws]")} cache:invalidate ${pc.dim(
|
|
1927
|
-
message.filePath ?? "(all)"
|
|
1928
|
-
)}`
|
|
1929
|
-
);
|
|
2110
|
+
logCacheInvalidate(message.filePath);
|
|
1930
2111
|
} else if (message.type === "vision:analyze") {
|
|
1931
2112
|
} else if (message.type === "vision:check") {
|
|
1932
2113
|
} else if (message.type === "config:set") {
|
|
1933
2114
|
} else if (message.type === "screenshot:save") {
|
|
1934
2115
|
const rid = message.requestId;
|
|
1935
|
-
|
|
1936
|
-
`${pc.dim("[ws]")} ${pc.bold("screenshot:save")} ${pc.dim(message.route)}${rid ? ` ${pc.dim(`(req ${rid})`)}` : ""}`
|
|
1937
|
-
);
|
|
2116
|
+
logScreenshotSave(message.route, rid);
|
|
1938
2117
|
}
|
|
1939
2118
|
switch (message.type) {
|
|
1940
2119
|
case "lint:file": {
|
|
@@ -1950,28 +2129,17 @@ async function handleMessage(ws, data) {
|
|
|
1950
2129
|
if (!existsSync5(resolved)) {
|
|
1951
2130
|
const cwd = process.cwd();
|
|
1952
2131
|
const wsRoot = findWorkspaceRoot4(cwd);
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
` filePath: ${pc.dim(filePath)}`,
|
|
1957
|
-
` resolved: ${pc.dim(resolved)}`,
|
|
1958
|
-
` cwd: ${pc.dim(cwd)}`,
|
|
1959
|
-
` wsRoot: ${pc.dim(wsRoot)}`,
|
|
1960
|
-
` hint: In monorepos, ensure paths like ${pc.dim(
|
|
1961
|
-
"app/page.tsx"
|
|
1962
|
-
)} exist under ${pc.dim("apps/*/")} or use absolute paths.`
|
|
1963
|
-
].join("\n")
|
|
2132
|
+
logServerWarning(
|
|
2133
|
+
`File not found: ${filePath}`,
|
|
2134
|
+
`resolved: ${resolved}, cwd: ${cwd}, wsRoot: ${wsRoot}`
|
|
1964
2135
|
);
|
|
1965
2136
|
}
|
|
1966
2137
|
const issues = await lintFile(filePath, (phase) => {
|
|
1967
2138
|
sendMessage(ws, { type: "lint:progress", filePath, requestId, phase });
|
|
1968
2139
|
});
|
|
1969
2140
|
const elapsed = Date.now() - startedAt;
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
`${issues.length}`
|
|
1973
|
-
)} issue(s) ${pc.dim(`(${elapsed}ms)`)}`
|
|
1974
|
-
);
|
|
2141
|
+
logLintDone(filePath, issues.length, elapsed);
|
|
2142
|
+
updateCacheCount(cache.size);
|
|
1975
2143
|
sendMessage(ws, {
|
|
1976
2144
|
type: "lint:progress",
|
|
1977
2145
|
filePath,
|
|
@@ -2023,6 +2191,7 @@ async function handleMessage(ws, data) {
|
|
|
2023
2191
|
}
|
|
2024
2192
|
}
|
|
2025
2193
|
subscribers.add({ ws, clientFilePath: filePath });
|
|
2194
|
+
updateSubscriptionCount(subscriptions.size);
|
|
2026
2195
|
break;
|
|
2027
2196
|
}
|
|
2028
2197
|
case "cache:invalidate": {
|
|
@@ -2033,6 +2202,7 @@ async function handleMessage(ws, data) {
|
|
|
2033
2202
|
} else {
|
|
2034
2203
|
cache.clear();
|
|
2035
2204
|
}
|
|
2205
|
+
updateCacheCount(cache.size);
|
|
2036
2206
|
break;
|
|
2037
2207
|
}
|
|
2038
2208
|
case "vision:analyze": {
|
|
@@ -2044,9 +2214,7 @@ async function handleMessage(ws, data) {
|
|
|
2044
2214
|
manifest,
|
|
2045
2215
|
requestId
|
|
2046
2216
|
} = message;
|
|
2047
|
-
|
|
2048
|
-
`${pc.dim("[ws]")} ${pc.bold("vision:analyze")} ${pc.dim(route)}${requestId ? ` ${pc.dim(`(req ${requestId})`)}` : ""}`
|
|
2049
|
-
);
|
|
2217
|
+
logVisionAnalyze(route, requestId);
|
|
2050
2218
|
sendMessage(ws, {
|
|
2051
2219
|
type: "vision:progress",
|
|
2052
2220
|
route,
|
|
@@ -2059,20 +2227,22 @@ async function handleMessage(ws, data) {
|
|
|
2059
2227
|
const screenshotBytes = typeof screenshot === "string" ? Buffer.byteLength(screenshot) : 0;
|
|
2060
2228
|
const analyzerModel = typeof analyzer.getModel === "function" ? analyzer.getModel() : void 0;
|
|
2061
2229
|
const analyzerBaseUrl = typeof analyzer.getBaseUrl === "function" ? analyzer.getBaseUrl() : void 0;
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
screenshot
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2230
|
+
if (!isDashboardEnabled()) {
|
|
2231
|
+
logInfo(
|
|
2232
|
+
[
|
|
2233
|
+
`${pc.dim("[ws]")} ${pc.dim("vision")} details`,
|
|
2234
|
+
` route: ${pc.dim(route)}`,
|
|
2235
|
+
` requestId: ${pc.dim(requestId ?? "(none)")}`,
|
|
2236
|
+
` manifest: ${pc.dim(String(manifest.length))} element(s)`,
|
|
2237
|
+
` screenshot: ${pc.dim(
|
|
2238
|
+
screenshot ? `${Math.round(screenshotBytes / 1024)}kb` : "none"
|
|
2239
|
+
)}`,
|
|
2240
|
+
` screenshotFile: ${pc.dim(screenshotFile ?? "(none)")}`,
|
|
2241
|
+
` ollamaUrl: ${pc.dim(analyzerBaseUrl ?? "(default)")}`,
|
|
2242
|
+
` visionModel: ${pc.dim(analyzerModel ?? "(default)")}`
|
|
2243
|
+
].join("\n")
|
|
2244
|
+
);
|
|
2245
|
+
}
|
|
2076
2246
|
if (!screenshot) {
|
|
2077
2247
|
sendMessage(ws, {
|
|
2078
2248
|
type: "vision:result",
|
|
@@ -2103,10 +2273,9 @@ async function handleMessage(ws, data) {
|
|
|
2103
2273
|
});
|
|
2104
2274
|
if (typeof screenshotFile === "string" && screenshotFile.length > 0) {
|
|
2105
2275
|
if (!isValidScreenshotFilename(screenshotFile)) {
|
|
2106
|
-
|
|
2107
|
-
`Skipping vision report write: invalid screenshotFile
|
|
2108
|
-
|
|
2109
|
-
)}`
|
|
2276
|
+
logServerWarning(
|
|
2277
|
+
`Skipping vision report write: invalid screenshotFile`,
|
|
2278
|
+
screenshotFile
|
|
2110
2279
|
);
|
|
2111
2280
|
} else {
|
|
2112
2281
|
const screenshotsDir = join3(
|
|
@@ -2117,10 +2286,9 @@ async function handleMessage(ws, data) {
|
|
|
2117
2286
|
const imagePath = join3(screenshotsDir, screenshotFile);
|
|
2118
2287
|
try {
|
|
2119
2288
|
if (!existsSync5(imagePath)) {
|
|
2120
|
-
|
|
2121
|
-
`Skipping vision report write: screenshot file not found
|
|
2122
|
-
|
|
2123
|
-
)}`
|
|
2289
|
+
logServerWarning(
|
|
2290
|
+
`Skipping vision report write: screenshot file not found`,
|
|
2291
|
+
imagePath
|
|
2124
2292
|
);
|
|
2125
2293
|
} else {
|
|
2126
2294
|
const report = writeVisionMarkdownReport({
|
|
@@ -2139,32 +2307,18 @@ async function handleMessage(ws, data) {
|
|
|
2139
2307
|
requestId: requestId ?? null
|
|
2140
2308
|
}
|
|
2141
2309
|
});
|
|
2142
|
-
|
|
2143
|
-
`${pc.dim("[ws]")} wrote vision report ${pc.dim(
|
|
2144
|
-
report.outPath
|
|
2145
|
-
)}`
|
|
2146
|
-
);
|
|
2310
|
+
logServerInfo(`Wrote vision report`, report.outPath);
|
|
2147
2311
|
}
|
|
2148
2312
|
} catch (e) {
|
|
2149
|
-
|
|
2150
|
-
`Failed to write vision report for ${
|
|
2313
|
+
logServerWarning(
|
|
2314
|
+
`Failed to write vision report for ${screenshotFile}`,
|
|
2315
|
+
e instanceof Error ? e.message : String(e)
|
|
2151
2316
|
);
|
|
2152
2317
|
}
|
|
2153
2318
|
}
|
|
2154
2319
|
}
|
|
2155
2320
|
const elapsed = Date.now() - startedAt;
|
|
2156
|
-
|
|
2157
|
-
`${pc.dim("[ws]")} vision:analyze done ${pc.dim(route)} \u2192 ${pc.bold(
|
|
2158
|
-
`${result.issues.length}`
|
|
2159
|
-
)} issue(s) ${pc.dim(`(${elapsed}ms)`)}`
|
|
2160
|
-
);
|
|
2161
|
-
if (result.rawResponse) {
|
|
2162
|
-
logInfo(
|
|
2163
|
-
`${pc.dim("[ws]")} vision rawResponse ${pc.dim(
|
|
2164
|
-
`${result.rawResponse.length} chars`
|
|
2165
|
-
)}`
|
|
2166
|
-
);
|
|
2167
|
-
}
|
|
2321
|
+
logVisionDone(route, result.issues.length, elapsed);
|
|
2168
2322
|
sendMessage(ws, {
|
|
2169
2323
|
type: "vision:result",
|
|
2170
2324
|
route,
|
|
@@ -2174,16 +2328,9 @@ async function handleMessage(ws, data) {
|
|
|
2174
2328
|
});
|
|
2175
2329
|
} catch (error) {
|
|
2176
2330
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
`Vision analysis failed`,
|
|
2181
|
-
` route: ${route}`,
|
|
2182
|
-
` requestId: ${requestId ?? "(none)"}`,
|
|
2183
|
-
` error: ${errorMessage}`,
|
|
2184
|
-
stack ? ` stack:
|
|
2185
|
-
${stack}` : ""
|
|
2186
|
-
].filter(Boolean).join("\n")
|
|
2331
|
+
logServerError(
|
|
2332
|
+
`Vision analysis failed for ${route}`,
|
|
2333
|
+
errorMessage
|
|
2187
2334
|
);
|
|
2188
2335
|
sendMessage(ws, {
|
|
2189
2336
|
type: "vision:result",
|
|
@@ -2198,9 +2345,7 @@ ${stack}` : ""
|
|
|
2198
2345
|
}
|
|
2199
2346
|
case "vision:check": {
|
|
2200
2347
|
const { requestId } = message;
|
|
2201
|
-
|
|
2202
|
-
`${pc.dim("[ws]")} ${pc.bold("vision:check")}${requestId ? ` ${pc.dim(`(req ${requestId})`)}` : ""}`
|
|
2203
|
-
);
|
|
2348
|
+
logVisionCheck(requestId);
|
|
2204
2349
|
try {
|
|
2205
2350
|
const analyzer = getVisionAnalyzerInstance();
|
|
2206
2351
|
const model = typeof analyzer.getModel === "function" ? analyzer.getModel() : void 0;
|
|
@@ -2276,7 +2421,7 @@ ${stack}` : ""
|
|
|
2276
2421
|
break;
|
|
2277
2422
|
}
|
|
2278
2423
|
const coverageData = JSON.parse(readFileSync(coveragePath, "utf-8"));
|
|
2279
|
-
|
|
2424
|
+
logCoverageResult(Object.keys(coverageData).length);
|
|
2280
2425
|
sendMessage(ws, {
|
|
2281
2426
|
type: "coverage:result",
|
|
2282
2427
|
coverage: coverageData,
|
|
@@ -2285,7 +2430,7 @@ ${stack}` : ""
|
|
|
2285
2430
|
});
|
|
2286
2431
|
} catch (error) {
|
|
2287
2432
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2288
|
-
|
|
2433
|
+
logServerError(`coverage:error`, errorMessage);
|
|
2289
2434
|
sendMessage(ws, {
|
|
2290
2435
|
type: "coverage:error",
|
|
2291
2436
|
error: errorMessage,
|
|
@@ -2341,11 +2486,7 @@ ${stack}` : ""
|
|
|
2341
2486
|
savedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2342
2487
|
};
|
|
2343
2488
|
writeFileSync4(sidecarPath, JSON.stringify(sidecarData, null, 2));
|
|
2344
|
-
|
|
2345
|
-
`${pc.dim("[ws]")} screenshot:saved ${pc.dim(filename)} ${pc.dim(
|
|
2346
|
-
`(${Math.round(imageBuffer.length / 1024)}kb)`
|
|
2347
|
-
)}`
|
|
2348
|
-
);
|
|
2489
|
+
logScreenshotSaved(filename, Math.round(imageBuffer.length / 1024));
|
|
2349
2490
|
sendMessage(ws, {
|
|
2350
2491
|
type: "screenshot:saved",
|
|
2351
2492
|
filename,
|
|
@@ -2354,7 +2495,7 @@ ${stack}` : ""
|
|
|
2354
2495
|
});
|
|
2355
2496
|
} catch (error) {
|
|
2356
2497
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2357
|
-
|
|
2498
|
+
logServerError(`screenshot:error`, errorMessage);
|
|
2358
2499
|
sendMessage(ws, {
|
|
2359
2500
|
type: "screenshot:error",
|
|
2360
2501
|
error: errorMessage,
|
|
@@ -2379,6 +2520,7 @@ function handleDisconnect(ws) {
|
|
|
2379
2520
|
}
|
|
2380
2521
|
}
|
|
2381
2522
|
}
|
|
2523
|
+
updateSubscriptionCount(subscriptions.size);
|
|
2382
2524
|
}
|
|
2383
2525
|
function handleFileChange(filePath) {
|
|
2384
2526
|
const subscribers = subscriptions.get(filePath);
|
|
@@ -2391,7 +2533,7 @@ function handleFileChange(filePath) {
|
|
|
2391
2533
|
function handleCoverageFileChange(filePath) {
|
|
2392
2534
|
try {
|
|
2393
2535
|
const coverageData = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
2394
|
-
|
|
2536
|
+
logCoverageResult(Object.keys(coverageData).length);
|
|
2395
2537
|
broadcast({
|
|
2396
2538
|
type: "coverage:result",
|
|
2397
2539
|
coverage: coverageData,
|
|
@@ -2399,7 +2541,7 @@ function handleCoverageFileChange(filePath) {
|
|
|
2399
2541
|
});
|
|
2400
2542
|
} catch (error) {
|
|
2401
2543
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2402
|
-
|
|
2544
|
+
logServerError(`Failed to read coverage data`, errorMessage);
|
|
2403
2545
|
broadcast({
|
|
2404
2546
|
type: "coverage:error",
|
|
2405
2547
|
error: `Failed to read coverage: ${errorMessage}`
|
|
@@ -2416,7 +2558,7 @@ function broadcastConfigUpdate(key, value) {
|
|
|
2416
2558
|
}
|
|
2417
2559
|
function handleConfigSet(key, value) {
|
|
2418
2560
|
configStore.set(key, value);
|
|
2419
|
-
|
|
2561
|
+
logConfigSet(key, value);
|
|
2420
2562
|
broadcastConfigUpdate(key, value);
|
|
2421
2563
|
}
|
|
2422
2564
|
function broadcastRuleConfigChange(ruleId, severity, options) {
|
|
@@ -2443,17 +2585,14 @@ async function buildDuplicatesIndex(appRoot) {
|
|
|
2443
2585
|
return;
|
|
2444
2586
|
}
|
|
2445
2587
|
isIndexing = true;
|
|
2446
|
-
|
|
2588
|
+
startBackgroundTask("duplicates-index", "Duplicates Index", "Starting...");
|
|
2447
2589
|
broadcast({ type: "duplicates:indexing:start" });
|
|
2448
2590
|
try {
|
|
2449
2591
|
const { indexDirectory } = await import("uilint-duplicates");
|
|
2450
2592
|
const result = await indexDirectory(appRoot, {
|
|
2451
2593
|
onProgress: (message, current, total) => {
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
} else {
|
|
2455
|
-
logInfo(` ${message}`);
|
|
2456
|
-
}
|
|
2594
|
+
const progress = total && total > 0 ? Math.round((current || 0) / total * 100) : 0;
|
|
2595
|
+
updateBackgroundTaskProgress("duplicates-index", progress, current, total, message);
|
|
2457
2596
|
broadcast({
|
|
2458
2597
|
type: "duplicates:indexing:progress",
|
|
2459
2598
|
message,
|
|
@@ -2462,9 +2601,8 @@ async function buildDuplicatesIndex(appRoot) {
|
|
|
2462
2601
|
});
|
|
2463
2602
|
}
|
|
2464
2603
|
});
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
);
|
|
2604
|
+
const successMsg = `${result.totalChunks} chunks (${result.added} added, ${result.modified} modified, ${result.deleted} deleted) in ${(result.duration / 1e3).toFixed(1)}s`;
|
|
2605
|
+
completeBackgroundTask("duplicates-index", `Index complete: ${successMsg}`);
|
|
2468
2606
|
broadcast({
|
|
2469
2607
|
type: "duplicates:indexing:complete",
|
|
2470
2608
|
added: result.added,
|
|
@@ -2475,7 +2613,7 @@ async function buildDuplicatesIndex(appRoot) {
|
|
|
2475
2613
|
});
|
|
2476
2614
|
} catch (error) {
|
|
2477
2615
|
const msg = error instanceof Error ? error.message : String(error);
|
|
2478
|
-
|
|
2616
|
+
completeBackgroundTask("duplicates-index", void 0, msg);
|
|
2479
2617
|
broadcast({ type: "duplicates:indexing:error", error: msg });
|
|
2480
2618
|
} finally {
|
|
2481
2619
|
isIndexing = false;
|
|
@@ -2488,7 +2626,7 @@ function scheduleReindex(appRoot, filePath) {
|
|
|
2488
2626
|
reindexTimeout = setTimeout(async () => {
|
|
2489
2627
|
const count = pendingIndexChanges.size;
|
|
2490
2628
|
pendingIndexChanges.clear();
|
|
2491
|
-
|
|
2629
|
+
logServerInfo(`${count} file(s) changed, updating index...`);
|
|
2492
2630
|
await buildDuplicatesIndex(appRoot);
|
|
2493
2631
|
}, 2e3);
|
|
2494
2632
|
}
|
|
@@ -2506,15 +2644,15 @@ async function buildCoverageData(appRoot) {
|
|
|
2506
2644
|
isPreparingCoverage = true;
|
|
2507
2645
|
try {
|
|
2508
2646
|
if (!isCoverageRuleEnabled(appRoot)) {
|
|
2509
|
-
|
|
2647
|
+
logServerInfo("Coverage rule not enabled, skipping preparation");
|
|
2510
2648
|
return;
|
|
2511
2649
|
}
|
|
2512
2650
|
const setup = detectCoverageSetup(appRoot);
|
|
2513
2651
|
if (!needsCoveragePreparation(setup)) {
|
|
2514
|
-
|
|
2652
|
+
logServerInfo("Coverage data is up-to-date");
|
|
2515
2653
|
return;
|
|
2516
2654
|
}
|
|
2517
|
-
|
|
2655
|
+
startBackgroundTask("coverage-prep", "Coverage Prep", "Starting...");
|
|
2518
2656
|
broadcast({ type: "coverage:setup:start" });
|
|
2519
2657
|
const skipPackageInstall = process.env.UILINT_SKIP_COVERAGE_INSTALL === "1";
|
|
2520
2658
|
const skipTests = process.env.UILINT_SKIP_COVERAGE_TESTS === "1";
|
|
@@ -2523,20 +2661,21 @@ async function buildCoverageData(appRoot) {
|
|
|
2523
2661
|
skipPackageInstall,
|
|
2524
2662
|
skipTests,
|
|
2525
2663
|
onProgress: (message, phase) => {
|
|
2526
|
-
|
|
2664
|
+
updateBackgroundTaskProgress("coverage-prep", 50, void 0, void 0, message);
|
|
2527
2665
|
broadcast({ type: "coverage:setup:progress", message, phase });
|
|
2528
2666
|
}
|
|
2529
2667
|
});
|
|
2530
2668
|
if (result.error) {
|
|
2531
|
-
|
|
2669
|
+
completeBackgroundTask("coverage-prep", void 0, result.error);
|
|
2532
2670
|
} else {
|
|
2533
2671
|
const parts = [];
|
|
2534
2672
|
if (result.packageAdded) parts.push("package installed");
|
|
2535
2673
|
if (result.configModified) parts.push("config modified");
|
|
2536
2674
|
if (result.testsRan) parts.push("tests ran");
|
|
2537
2675
|
if (result.coverageGenerated) parts.push("coverage generated");
|
|
2538
|
-
|
|
2539
|
-
|
|
2676
|
+
completeBackgroundTask(
|
|
2677
|
+
"coverage-prep",
|
|
2678
|
+
`Coverage prepared: ${parts.join(", ")} in ${(result.duration / 1e3).toFixed(1)}s`
|
|
2540
2679
|
);
|
|
2541
2680
|
}
|
|
2542
2681
|
broadcast({
|
|
@@ -2545,20 +2684,18 @@ async function buildCoverageData(appRoot) {
|
|
|
2545
2684
|
});
|
|
2546
2685
|
} catch (error) {
|
|
2547
2686
|
const msg = error instanceof Error ? error.message : String(error);
|
|
2548
|
-
|
|
2687
|
+
completeBackgroundTask("coverage-prep", void 0, msg);
|
|
2549
2688
|
broadcast({ type: "coverage:setup:error", error: msg });
|
|
2550
2689
|
} finally {
|
|
2551
2690
|
isPreparingCoverage = false;
|
|
2552
2691
|
}
|
|
2553
2692
|
}
|
|
2554
2693
|
function handleRuleConfigSet(ws, ruleId, severity, options, requestId) {
|
|
2555
|
-
|
|
2556
|
-
`${pc.dim("[ws]")} rule:config:set ${pc.bold(ruleId)} -> ${pc.dim(severity)}${options ? ` with options` : ""}`
|
|
2557
|
-
);
|
|
2694
|
+
logRuleConfigSet(ruleId, severity, !!options);
|
|
2558
2695
|
const configPath = findEslintConfigFile(serverAppRootForVision);
|
|
2559
2696
|
if (!configPath) {
|
|
2560
2697
|
const error = `No ESLint config file found in ${serverAppRootForVision}`;
|
|
2561
|
-
|
|
2698
|
+
logServerError(error);
|
|
2562
2699
|
sendMessage(ws, {
|
|
2563
2700
|
type: "rule:config:result",
|
|
2564
2701
|
ruleId,
|
|
@@ -2577,11 +2714,10 @@ function handleRuleConfigSet(ws, ruleId, severity, options, requestId) {
|
|
|
2577
2714
|
result = updateRuleSeverityInConfig(configPath, ruleId, severity);
|
|
2578
2715
|
}
|
|
2579
2716
|
if (result.success) {
|
|
2580
|
-
|
|
2581
|
-
`${pc.dim("[ws]")} Updated ${pc.bold(`uilint/${ruleId}`)} -> ${pc.dim(severity)}`
|
|
2582
|
-
);
|
|
2717
|
+
logServerInfo(`Updated uilint/${ruleId} -> ${severity}`);
|
|
2583
2718
|
eslintInstances.clear();
|
|
2584
2719
|
cache.clear();
|
|
2720
|
+
updateCacheCount(0);
|
|
2585
2721
|
sendMessage(ws, {
|
|
2586
2722
|
type: "rule:config:result",
|
|
2587
2723
|
ruleId,
|
|
@@ -2592,7 +2728,7 @@ function handleRuleConfigSet(ws, ruleId, severity, options, requestId) {
|
|
|
2592
2728
|
});
|
|
2593
2729
|
broadcastRuleConfigChange(ruleId, severity, options);
|
|
2594
2730
|
} else {
|
|
2595
|
-
|
|
2731
|
+
logServerError(`Failed to update rule: ${result.error}`);
|
|
2596
2732
|
sendMessage(ws, {
|
|
2597
2733
|
type: "rule:config:result",
|
|
2598
2734
|
ruleId,
|
|
@@ -2606,13 +2742,17 @@ function handleRuleConfigSet(ws, ruleId, severity, options, requestId) {
|
|
|
2606
2742
|
}
|
|
2607
2743
|
async function serve(options) {
|
|
2608
2744
|
const port = options.port || 9234;
|
|
2745
|
+
const useDashboardUI = process.stdout.isTTY && !options.noDashboard;
|
|
2746
|
+
if (useDashboardUI) {
|
|
2747
|
+
enableDashboard();
|
|
2748
|
+
} else {
|
|
2749
|
+
disableDashboard();
|
|
2750
|
+
}
|
|
2609
2751
|
const cwd = process.cwd();
|
|
2610
2752
|
const wsRoot = findWorkspaceRoot4(cwd);
|
|
2611
2753
|
const appRoot = pickAppRoot({ cwd, workspaceRoot: wsRoot });
|
|
2612
2754
|
serverAppRootForVision = appRoot;
|
|
2613
|
-
|
|
2614
|
-
logInfo(`App root: ${pc.dim(appRoot)}`);
|
|
2615
|
-
logInfo(`Server cwd: ${pc.dim(cwd)}`);
|
|
2755
|
+
setWorkspaceInfo(wsRoot, appRoot, cwd);
|
|
2616
2756
|
fileWatcher = watch([], {
|
|
2617
2757
|
persistent: true,
|
|
2618
2758
|
ignoreInitial: true
|
|
@@ -2629,19 +2769,19 @@ async function serve(options) {
|
|
|
2629
2769
|
const coveragePath = join3(appRoot, "coverage", "coverage-final.json");
|
|
2630
2770
|
if (existsSync5(coveragePath)) {
|
|
2631
2771
|
fileWatcher.add(coveragePath);
|
|
2632
|
-
|
|
2772
|
+
logServerInfo(`Watching coverage`, coveragePath);
|
|
2633
2773
|
}
|
|
2634
2774
|
const wss = new WebSocketServer({ port });
|
|
2635
2775
|
buildDuplicatesIndex(appRoot).catch((err) => {
|
|
2636
|
-
|
|
2776
|
+
logServerError(`Failed to build duplicates index`, err.message);
|
|
2637
2777
|
});
|
|
2638
2778
|
buildCoverageData(appRoot).catch((err) => {
|
|
2639
|
-
|
|
2779
|
+
logServerWarning(`Failed to prepare coverage`, err.message);
|
|
2640
2780
|
});
|
|
2641
2781
|
wss.on("connection", (ws) => {
|
|
2642
2782
|
connectedClients += 1;
|
|
2643
2783
|
connectedClientsSet.add(ws);
|
|
2644
|
-
|
|
2784
|
+
logClientConnect(connectedClients);
|
|
2645
2785
|
sendMessage(ws, {
|
|
2646
2786
|
type: "workspace:info",
|
|
2647
2787
|
appRoot,
|
|
@@ -2654,7 +2794,7 @@ async function serve(options) {
|
|
|
2654
2794
|
postToolUseHook: hookInfo
|
|
2655
2795
|
});
|
|
2656
2796
|
if (hookInfo.enabled) {
|
|
2657
|
-
|
|
2797
|
+
logServerInfo(`Post-tool-use hook detected: ${hookInfo.provider}`);
|
|
2658
2798
|
}
|
|
2659
2799
|
const eslintConfigPath = findEslintConfigFile(appRoot);
|
|
2660
2800
|
const currentRuleConfigs = eslintConfigPath ? readRuleConfigsFromConfig(eslintConfigPath) : /* @__PURE__ */ new Map();
|
|
@@ -2685,28 +2825,39 @@ async function serve(options) {
|
|
|
2685
2825
|
ws.on("close", () => {
|
|
2686
2826
|
connectedClients = Math.max(0, connectedClients - 1);
|
|
2687
2827
|
connectedClientsSet.delete(ws);
|
|
2688
|
-
|
|
2828
|
+
logClientDisconnect(connectedClients);
|
|
2689
2829
|
handleDisconnect(ws);
|
|
2690
2830
|
});
|
|
2691
2831
|
ws.on("error", (error) => {
|
|
2692
|
-
|
|
2832
|
+
logServerError(`WebSocket error`, error.message);
|
|
2693
2833
|
});
|
|
2694
2834
|
});
|
|
2695
2835
|
wss.on("error", (error) => {
|
|
2696
|
-
|
|
2836
|
+
logServerError(`Server error`, error.message);
|
|
2697
2837
|
});
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2838
|
+
setServerRunning(port);
|
|
2839
|
+
if (useDashboardUI) {
|
|
2840
|
+
const { renderDashboard } = await import("./render-KO44SVF3.js");
|
|
2841
|
+
const { waitUntilExit } = renderDashboard({
|
|
2842
|
+
onQuit: () => {
|
|
2843
|
+
wss.close();
|
|
2844
|
+
fileWatcher?.close();
|
|
2845
|
+
},
|
|
2846
|
+
onRebuildIndex: () => {
|
|
2847
|
+
buildDuplicatesIndex(appRoot);
|
|
2848
|
+
}
|
|
2708
2849
|
});
|
|
2709
|
-
|
|
2850
|
+
await waitUntilExit();
|
|
2851
|
+
} else {
|
|
2852
|
+
await new Promise((resolve11) => {
|
|
2853
|
+
process.on("SIGINT", () => {
|
|
2854
|
+
logServerInfo("Shutting down...");
|
|
2855
|
+
wss.close();
|
|
2856
|
+
fileWatcher?.close();
|
|
2857
|
+
resolve11();
|
|
2858
|
+
});
|
|
2859
|
+
});
|
|
2860
|
+
}
|
|
2710
2861
|
}
|
|
2711
2862
|
|
|
2712
2863
|
// src/commands/vision.ts
|
|
@@ -4977,9 +5128,10 @@ program.command("remove").description("Remove UILint components from your projec
|
|
|
4977
5128
|
const { removeUI } = await import("./remove-ui-O4B6EYSF.js");
|
|
4978
5129
|
await removeUI({ dryRun: options.dryRun, yes: options.yes });
|
|
4979
5130
|
});
|
|
4980
|
-
program.command("serve").description("Start WebSocket server for real-time UI linting").option("-p, --port <number>", "Port to listen on", "9234").action(async (options) => {
|
|
5131
|
+
program.command("serve").description("Start WebSocket server for real-time UI linting").option("-p, --port <number>", "Port to listen on", "9234").option("--no-dashboard", "Disable dashboard UI (use simple logging)").action(async (options) => {
|
|
4981
5132
|
await serve({
|
|
4982
|
-
port: parseInt(options.port, 10)
|
|
5133
|
+
port: parseInt(options.port, 10),
|
|
5134
|
+
noDashboard: !options.dashboard
|
|
4983
5135
|
});
|
|
4984
5136
|
});
|
|
4985
5137
|
program.command("vision").description("Analyze a screenshot with Ollama vision models (requires a manifest)").option("--list", "List available .uilint/screenshots sidecars and exit").option(
|