uilint 0.2.3 → 0.2.5
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/index.js +488 -322
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/skills/ui-consistency-enforcer/SKILL.md +40 -50
package/dist/index.js
CHANGED
|
@@ -357,8 +357,8 @@ import { dirname, join as join2 } from "path";
|
|
|
357
357
|
import { fileURLToPath } from "url";
|
|
358
358
|
function getCLIVersion() {
|
|
359
359
|
try {
|
|
360
|
-
const
|
|
361
|
-
const pkgPath = join2(
|
|
360
|
+
const __dirname3 = dirname(fileURLToPath(import.meta.url));
|
|
361
|
+
const pkgPath = join2(__dirname3, "..", "..", "package.json");
|
|
362
362
|
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
363
363
|
return pkg.version || "0.0.0";
|
|
364
364
|
} catch {
|
|
@@ -1475,13 +1475,13 @@ async function update(options) {
|
|
|
1475
1475
|
}
|
|
1476
1476
|
|
|
1477
1477
|
// src/commands/install.ts
|
|
1478
|
-
import { join as
|
|
1478
|
+
import { join as join16 } from "path";
|
|
1479
1479
|
import { ruleRegistry as ruleRegistry2 } from "uilint-eslint";
|
|
1480
1480
|
|
|
1481
1481
|
// src/commands/install/analyze.ts
|
|
1482
1482
|
import { existsSync as existsSync9, readFileSync as readFileSync5 } from "fs";
|
|
1483
1483
|
import { join as join8 } from "path";
|
|
1484
|
-
import { findWorkspaceRoot as
|
|
1484
|
+
import { findWorkspaceRoot as findWorkspaceRoot5 } from "uilint-core/node";
|
|
1485
1485
|
|
|
1486
1486
|
// src/utils/next-detect.ts
|
|
1487
1487
|
import { existsSync as existsSync4, readdirSync } from "fs";
|
|
@@ -1714,6 +1714,24 @@ function isFrontendPackage(pkgJson) {
|
|
|
1714
1714
|
};
|
|
1715
1715
|
return FRONTEND_INDICATORS.some((pkg) => pkg in deps);
|
|
1716
1716
|
}
|
|
1717
|
+
function isTypeScriptPackage(dir, pkgJson) {
|
|
1718
|
+
if (existsSync6(join5(dir, "tsconfig.json"))) {
|
|
1719
|
+
return true;
|
|
1720
|
+
}
|
|
1721
|
+
const deps = {
|
|
1722
|
+
...pkgJson.dependencies,
|
|
1723
|
+
...pkgJson.devDependencies
|
|
1724
|
+
};
|
|
1725
|
+
if ("typescript" in deps) {
|
|
1726
|
+
return true;
|
|
1727
|
+
}
|
|
1728
|
+
for (const configFile of ESLINT_CONFIG_FILES) {
|
|
1729
|
+
if (configFile.endsWith(".ts") && existsSync6(join5(dir, configFile))) {
|
|
1730
|
+
return true;
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
return false;
|
|
1734
|
+
}
|
|
1717
1735
|
function hasEslintConfig(dir) {
|
|
1718
1736
|
for (const file of ESLINT_CONFIG_FILES) {
|
|
1719
1737
|
if (existsSync6(join5(dir, file))) {
|
|
@@ -1747,7 +1765,8 @@ function findPackages(rootDir, options) {
|
|
|
1747
1765
|
name,
|
|
1748
1766
|
hasEslintConfig: hasEslintConfig(dir),
|
|
1749
1767
|
isFrontend: isFrontendPackage(pkg),
|
|
1750
|
-
isRoot
|
|
1768
|
+
isRoot,
|
|
1769
|
+
isTypeScript: isTypeScriptPackage(dir, pkg)
|
|
1751
1770
|
};
|
|
1752
1771
|
} catch {
|
|
1753
1772
|
return null;
|
|
@@ -1853,8 +1872,9 @@ async function installDependencies(pm, projectPath, packages) {
|
|
|
1853
1872
|
|
|
1854
1873
|
// src/utils/eslint-config-inject.ts
|
|
1855
1874
|
import { existsSync as existsSync8, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
1856
|
-
import { join as join7 } from "path";
|
|
1875
|
+
import { join as join7, relative as relative2, dirname as dirname6 } from "path";
|
|
1857
1876
|
import { parseExpression, parseModule, generateCode } from "magicast";
|
|
1877
|
+
import { findWorkspaceRoot as findWorkspaceRoot4 } from "uilint-core/node";
|
|
1858
1878
|
var CONFIG_EXTENSIONS = [".ts", ".mjs", ".js", ".cjs"];
|
|
1859
1879
|
function findEslintConfigFile(projectPath) {
|
|
1860
1880
|
for (const ext of CONFIG_EXTENSIONS) {
|
|
@@ -1869,25 +1889,6 @@ function getEslintConfigFilename(configPath) {
|
|
|
1869
1889
|
const parts = configPath.split("/");
|
|
1870
1890
|
return parts[parts.length - 1] || "eslint.config.mjs";
|
|
1871
1891
|
}
|
|
1872
|
-
function hasUilintImport(source) {
|
|
1873
|
-
return source.includes('from "uilint-eslint"') || source.includes("from 'uilint-eslint'") || source.includes('require("uilint-eslint")') || source.includes("require('uilint-eslint')");
|
|
1874
|
-
}
|
|
1875
|
-
function hasUilintConfigsUsage(source) {
|
|
1876
|
-
return /\builint\s*\.\s*configs\s*\./.test(source);
|
|
1877
|
-
}
|
|
1878
|
-
function walkAst(node, visit) {
|
|
1879
|
-
if (!node || typeof node !== "object") return;
|
|
1880
|
-
visit(node);
|
|
1881
|
-
for (const key of Object.keys(node)) {
|
|
1882
|
-
const v = node[key];
|
|
1883
|
-
if (!v) continue;
|
|
1884
|
-
if (Array.isArray(v)) {
|
|
1885
|
-
for (const item of v) walkAst(item, visit);
|
|
1886
|
-
} else if (typeof v === "object" && v.type) {
|
|
1887
|
-
walkAst(v, visit);
|
|
1888
|
-
}
|
|
1889
|
-
}
|
|
1890
|
-
}
|
|
1891
1892
|
function isIdentifier(node, name) {
|
|
1892
1893
|
return !!node && node.type === "Identifier" && (name ? node.name === name : typeof node.name === "string");
|
|
1893
1894
|
}
|
|
@@ -1912,35 +1913,6 @@ function hasSpreadProperties(obj) {
|
|
|
1912
1913
|
(p2) => p2 && (p2.type === "SpreadElement" || p2.type === "SpreadProperty")
|
|
1913
1914
|
);
|
|
1914
1915
|
}
|
|
1915
|
-
var IGNORED_AST_KEYS = /* @__PURE__ */ new Set([
|
|
1916
|
-
"loc",
|
|
1917
|
-
"start",
|
|
1918
|
-
"end",
|
|
1919
|
-
"extra",
|
|
1920
|
-
"leadingComments",
|
|
1921
|
-
"trailingComments",
|
|
1922
|
-
"innerComments"
|
|
1923
|
-
]);
|
|
1924
|
-
function normalizeAstForCompare(node) {
|
|
1925
|
-
if (node === null) return null;
|
|
1926
|
-
if (node === void 0) return void 0;
|
|
1927
|
-
if (typeof node !== "object") return node;
|
|
1928
|
-
if (Array.isArray(node)) return node.map(normalizeAstForCompare);
|
|
1929
|
-
const out = {};
|
|
1930
|
-
const keys = Object.keys(node).filter((k) => !IGNORED_AST_KEYS.has(k)).sort();
|
|
1931
|
-
for (const k of keys) {
|
|
1932
|
-
if (k.startsWith("$")) continue;
|
|
1933
|
-
out[k] = normalizeAstForCompare(node[k]);
|
|
1934
|
-
}
|
|
1935
|
-
return out;
|
|
1936
|
-
}
|
|
1937
|
-
function astEquivalent(a, b) {
|
|
1938
|
-
try {
|
|
1939
|
-
return JSON.stringify(normalizeAstForCompare(a)) === JSON.stringify(normalizeAstForCompare(b));
|
|
1940
|
-
} catch {
|
|
1941
|
-
return false;
|
|
1942
|
-
}
|
|
1943
|
-
}
|
|
1944
1916
|
function collectUilintRuleIdsFromRulesObject(rulesObj) {
|
|
1945
1917
|
const ids = /* @__PURE__ */ new Set();
|
|
1946
1918
|
if (!rulesObj || rulesObj.type !== "ObjectExpression") return ids;
|
|
@@ -1951,8 +1923,9 @@ function collectUilintRuleIdsFromRulesObject(rulesObj) {
|
|
|
1951
1923
|
if (!isStringLiteral(key)) continue;
|
|
1952
1924
|
const val = key.value;
|
|
1953
1925
|
if (typeof val !== "string") continue;
|
|
1954
|
-
if (
|
|
1955
|
-
|
|
1926
|
+
if (val.startsWith("uilint/")) {
|
|
1927
|
+
ids.add(val.slice("uilint/".length));
|
|
1928
|
+
}
|
|
1956
1929
|
}
|
|
1957
1930
|
return ids;
|
|
1958
1931
|
}
|
|
@@ -2045,24 +2018,6 @@ function findExportedConfigArrayExpression(mod) {
|
|
|
2045
2018
|
}
|
|
2046
2019
|
return null;
|
|
2047
2020
|
}
|
|
2048
|
-
function findUsesUilintConfigs(program2) {
|
|
2049
|
-
let found = false;
|
|
2050
|
-
walkAst(program2, (n) => {
|
|
2051
|
-
if (found) return;
|
|
2052
|
-
if (n?.type === "MemberExpression") {
|
|
2053
|
-
const obj = n.object;
|
|
2054
|
-
const prop = n.property;
|
|
2055
|
-
if (isIdentifier(prop, "configs") && isIdentifier(obj, "uilint")) {
|
|
2056
|
-
found = true;
|
|
2057
|
-
return;
|
|
2058
|
-
}
|
|
2059
|
-
if (obj?.type === "MemberExpression" && isIdentifier(obj.object, "uilint") && isIdentifier(obj.property, "configs")) {
|
|
2060
|
-
found = true;
|
|
2061
|
-
}
|
|
2062
|
-
}
|
|
2063
|
-
});
|
|
2064
|
-
return found;
|
|
2065
|
-
}
|
|
2066
2021
|
function collectConfiguredUilintRuleIdsFromConfigArray(arrayExpr) {
|
|
2067
2022
|
const ids = /* @__PURE__ */ new Set();
|
|
2068
2023
|
if (!arrayExpr || arrayExpr.type !== "ArrayExpression") return ids;
|
|
@@ -2115,69 +2070,68 @@ function chooseUniqueIdentifier(base, used) {
|
|
|
2115
2070
|
while (used.has(`${base}${i}`)) i++;
|
|
2116
2071
|
return `${base}${i}`;
|
|
2117
2072
|
}
|
|
2118
|
-
function
|
|
2119
|
-
const
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
);
|
|
2123
|
-
|
|
2124
|
-
}
|
|
2125
|
-
|
|
2126
|
-
const
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
const id = decl?.id;
|
|
2141
|
-
const init = decl?.init;
|
|
2142
|
-
if (!isIdentifier(id)) continue;
|
|
2143
|
-
if (init?.type === "CallExpression" && isIdentifier(init.callee, "require") && isStringLiteral(init.arguments?.[0]) && init.arguments[0].value === "uilint-eslint") {
|
|
2144
|
-
return id.name;
|
|
2145
|
-
}
|
|
2146
|
-
}
|
|
2073
|
+
function addLocalRuleImportsAst(mod, selectedRules, configPath, rulesRoot, fileExtension = ".js") {
|
|
2074
|
+
const importNames = /* @__PURE__ */ new Map();
|
|
2075
|
+
let changed = false;
|
|
2076
|
+
const configDir = dirname6(configPath);
|
|
2077
|
+
const rulesDir = join7(rulesRoot, ".uilint", "rules");
|
|
2078
|
+
const relativeRulesPath = relative2(configDir, rulesDir).replace(/\\/g, "/");
|
|
2079
|
+
const normalizedRulesPath = relativeRulesPath.startsWith("./") || relativeRulesPath.startsWith("../") ? relativeRulesPath : `./${relativeRulesPath}`;
|
|
2080
|
+
const used = collectTopLevelBindings(mod.$ast);
|
|
2081
|
+
for (const rule of selectedRules) {
|
|
2082
|
+
const importName = chooseUniqueIdentifier(
|
|
2083
|
+
`${rule.id.replace(/-([a-z])/g, (_, c) => c.toUpperCase()).replace(/^./, (c) => c.toUpperCase())}Rule`,
|
|
2084
|
+
used
|
|
2085
|
+
);
|
|
2086
|
+
importNames.set(rule.id, importName);
|
|
2087
|
+
used.add(importName);
|
|
2088
|
+
const rulePath = `${normalizedRulesPath}/${rule.id}${fileExtension}`;
|
|
2089
|
+
mod.imports.$add({
|
|
2090
|
+
imported: "default",
|
|
2091
|
+
local: importName,
|
|
2092
|
+
from: rulePath
|
|
2093
|
+
});
|
|
2094
|
+
changed = true;
|
|
2147
2095
|
}
|
|
2148
|
-
return
|
|
2096
|
+
return { importNames, changed };
|
|
2149
2097
|
}
|
|
2150
|
-
function
|
|
2098
|
+
function addLocalRuleRequiresAst(program2, selectedRules, configPath, rulesRoot, fileExtension = ".js") {
|
|
2099
|
+
const importNames = /* @__PURE__ */ new Map();
|
|
2100
|
+
let changed = false;
|
|
2151
2101
|
if (!program2 || program2.type !== "Program") {
|
|
2152
|
-
return {
|
|
2102
|
+
return { importNames, changed };
|
|
2153
2103
|
}
|
|
2154
|
-
const
|
|
2155
|
-
|
|
2104
|
+
const configDir = dirname6(configPath);
|
|
2105
|
+
const rulesDir = join7(rulesRoot, ".uilint", "rules");
|
|
2106
|
+
const relativeRulesPath = relative2(configDir, rulesDir).replace(/\\/g, "/");
|
|
2107
|
+
const normalizedRulesPath = relativeRulesPath.startsWith("./") || relativeRulesPath.startsWith("../") ? relativeRulesPath : `./${relativeRulesPath}`;
|
|
2156
2108
|
const used = collectTopLevelBindings(program2);
|
|
2157
|
-
const
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
}
|
|
2180
|
-
|
|
2109
|
+
for (const rule of selectedRules) {
|
|
2110
|
+
const importName = chooseUniqueIdentifier(
|
|
2111
|
+
`${rule.id.replace(/-([a-z])/g, (_, c) => c.toUpperCase()).replace(/^./, (c) => c.toUpperCase())}Rule`,
|
|
2112
|
+
used
|
|
2113
|
+
);
|
|
2114
|
+
importNames.set(rule.id, importName);
|
|
2115
|
+
used.add(importName);
|
|
2116
|
+
const rulePath = `${normalizedRulesPath}/${rule.id}${fileExtension}`;
|
|
2117
|
+
const stmtMod = parseModule(
|
|
2118
|
+
`const ${importName} = require("${rulePath}");`
|
|
2119
|
+
);
|
|
2120
|
+
const stmt = stmtMod.$ast.body?.[0];
|
|
2121
|
+
if (stmt) {
|
|
2122
|
+
let insertAt = 0;
|
|
2123
|
+
const first = program2.body?.[0];
|
|
2124
|
+
if (first?.type === "ExpressionStatement" && first.expression?.type === "StringLiteral" && first.expression.value === "use strict") {
|
|
2125
|
+
insertAt = 1;
|
|
2126
|
+
}
|
|
2127
|
+
program2.body.splice(insertAt, 0, stmt);
|
|
2128
|
+
changed = true;
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
return { importNames, changed };
|
|
2132
|
+
}
|
|
2133
|
+
function appendUilintConfigBlockToArray(arrayExpr, selectedRules, ruleImportNames) {
|
|
2134
|
+
const pluginRulesCode = Array.from(ruleImportNames.entries()).map(([ruleId, importName]) => ` "${ruleId}": ${importName},`).join("\n");
|
|
2181
2135
|
const rulesPropsCode = selectedRules.map((r) => {
|
|
2182
2136
|
const ruleKey = `uilint/${r.id}`;
|
|
2183
2137
|
const valueCode = r.defaultOptions && r.defaultOptions.length > 0 ? `["${r.defaultSeverity}", ...${JSON.stringify(
|
|
@@ -2193,7 +2147,13 @@ function appendUilintConfigBlockToArray(arrayExpr, selectedRules, uilintRef) {
|
|
|
2193
2147
|
"app/**/*.{js,jsx,ts,tsx}",
|
|
2194
2148
|
"pages/**/*.{js,jsx,ts,tsx}",
|
|
2195
2149
|
],
|
|
2196
|
-
plugins: {
|
|
2150
|
+
plugins: {
|
|
2151
|
+
uilint: {
|
|
2152
|
+
rules: {
|
|
2153
|
+
${pluginRulesCode}
|
|
2154
|
+
},
|
|
2155
|
+
},
|
|
2156
|
+
},
|
|
2197
2157
|
rules: {
|
|
2198
2158
|
${rulesPropsCode}
|
|
2199
2159
|
},
|
|
@@ -2210,14 +2170,13 @@ function getUilintEslintConfigInfoFromSourceAst(source) {
|
|
|
2210
2170
|
error: "Could not locate an exported ESLint flat config array (expected `export default [...]`, `export default defineConfig([...])`, `module.exports = [...]`, or `module.exports = defineConfig([...])`)."
|
|
2211
2171
|
};
|
|
2212
2172
|
}
|
|
2213
|
-
const usesUilintConfigs = findUsesUilintConfigs(found.program);
|
|
2214
2173
|
const configuredRuleIds = collectConfiguredUilintRuleIdsFromConfigArray(
|
|
2215
2174
|
found.arrayExpr
|
|
2216
2175
|
);
|
|
2217
2176
|
const existingUilint = findExistingUilintRulesObject(found.arrayExpr);
|
|
2218
|
-
const configured =
|
|
2177
|
+
const configured = configuredRuleIds.size > 0 || existingUilint.configObj !== null;
|
|
2219
2178
|
return {
|
|
2220
|
-
info: {
|
|
2179
|
+
info: { configuredRuleIds, configured },
|
|
2221
2180
|
mod,
|
|
2222
2181
|
arrayExpr: found.arrayExpr,
|
|
2223
2182
|
kind: found.kind
|
|
@@ -2232,11 +2191,9 @@ function getUilintEslintConfigInfoFromSource(source) {
|
|
|
2232
2191
|
const ast = getUilintEslintConfigInfoFromSourceAst(source);
|
|
2233
2192
|
if ("error" in ast) {
|
|
2234
2193
|
const configuredRuleIds = extractConfiguredUilintRuleIds(source);
|
|
2235
|
-
const usesUilintConfigs = hasUilintConfigsUsage(source);
|
|
2236
2194
|
return {
|
|
2237
|
-
usesUilintConfigs,
|
|
2238
2195
|
configuredRuleIds,
|
|
2239
|
-
configured:
|
|
2196
|
+
configured: configuredRuleIds.size > 0
|
|
2240
2197
|
};
|
|
2241
2198
|
}
|
|
2242
2199
|
return ast.info;
|
|
@@ -2279,10 +2236,15 @@ async function installEslintPlugin(opts) {
|
|
|
2279
2236
|
};
|
|
2280
2237
|
}
|
|
2281
2238
|
const { info, mod, arrayExpr, kind } = ast;
|
|
2282
|
-
const usesUilintConfigs = info.usesUilintConfigs;
|
|
2283
2239
|
const configuredIds = info.configuredRuleIds;
|
|
2284
|
-
const missingRules =
|
|
2285
|
-
|
|
2240
|
+
const missingRules = getMissingSelectedRules(
|
|
2241
|
+
opts.selectedRules,
|
|
2242
|
+
configuredIds
|
|
2243
|
+
);
|
|
2244
|
+
const rulesToUpdate = getRulesNeedingUpdate(
|
|
2245
|
+
opts.selectedRules,
|
|
2246
|
+
configuredIds
|
|
2247
|
+
);
|
|
2286
2248
|
let rulesToApply = [];
|
|
2287
2249
|
if (!info.configured) {
|
|
2288
2250
|
rulesToApply = opts.selectedRules;
|
|
@@ -2303,47 +2265,79 @@ async function installEslintPlugin(opts) {
|
|
|
2303
2265
|
}
|
|
2304
2266
|
}
|
|
2305
2267
|
}
|
|
2268
|
+
if (rulesToApply.length === 0) {
|
|
2269
|
+
return {
|
|
2270
|
+
configFile: configFilename,
|
|
2271
|
+
modified: false,
|
|
2272
|
+
missingRuleIds: missingRules.map((r) => r.id),
|
|
2273
|
+
configured: info.configured
|
|
2274
|
+
};
|
|
2275
|
+
}
|
|
2306
2276
|
let modifiedAst = false;
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
if (!newProp) continue;
|
|
2322
|
-
if (existingProp) {
|
|
2323
|
-
if (!astEquivalent(existingProp.value, newProp.value)) {
|
|
2324
|
-
existingProp.value = newProp.value;
|
|
2325
|
-
changedRules = true;
|
|
2326
|
-
}
|
|
2327
|
-
} else {
|
|
2328
|
-
props.push(newProp);
|
|
2329
|
-
changedRules = true;
|
|
2330
|
-
}
|
|
2331
|
-
}
|
|
2332
|
-
if (changedRules) modifiedAst = true;
|
|
2333
|
-
} else {
|
|
2334
|
-
const uilintRef = kind === "esm" ? ensureUilintImportAst(mod).local : ensureUilintRequireAst(mod.$ast).local;
|
|
2335
|
-
appendUilintConfigBlockToArray(arrayExpr, rulesToApply, uilintRef);
|
|
2336
|
-
modifiedAst = true;
|
|
2277
|
+
const localRulesDir = join7(opts.projectPath, ".uilint", "rules");
|
|
2278
|
+
const workspaceRoot = findWorkspaceRoot4(opts.projectPath);
|
|
2279
|
+
const workspaceRulesDir = join7(workspaceRoot, ".uilint", "rules");
|
|
2280
|
+
const rulesRoot = existsSync8(localRulesDir) ? opts.projectPath : workspaceRoot;
|
|
2281
|
+
let fileExtension = ".js";
|
|
2282
|
+
if (rulesToApply.length > 0) {
|
|
2283
|
+
const firstRulePath = join7(
|
|
2284
|
+
rulesRoot,
|
|
2285
|
+
".uilint",
|
|
2286
|
+
"rules",
|
|
2287
|
+
`${rulesToApply[0].id}.ts`
|
|
2288
|
+
);
|
|
2289
|
+
if (existsSync8(firstRulePath)) {
|
|
2290
|
+
fileExtension = ".ts";
|
|
2337
2291
|
}
|
|
2338
|
-
} else if (!info.configured && !usesUilintConfigs) {
|
|
2339
2292
|
}
|
|
2340
|
-
|
|
2293
|
+
let ruleImportNames;
|
|
2294
|
+
if (kind === "esm") {
|
|
2295
|
+
const result = addLocalRuleImportsAst(
|
|
2296
|
+
mod,
|
|
2297
|
+
rulesToApply,
|
|
2298
|
+
configPath,
|
|
2299
|
+
rulesRoot,
|
|
2300
|
+
fileExtension
|
|
2301
|
+
);
|
|
2302
|
+
ruleImportNames = result.importNames;
|
|
2303
|
+
if (result.changed) modifiedAst = true;
|
|
2304
|
+
} else {
|
|
2305
|
+
const result = addLocalRuleRequiresAst(
|
|
2306
|
+
mod.$ast,
|
|
2307
|
+
rulesToApply,
|
|
2308
|
+
configPath,
|
|
2309
|
+
rulesRoot,
|
|
2310
|
+
fileExtension
|
|
2311
|
+
);
|
|
2312
|
+
ruleImportNames = result.importNames;
|
|
2313
|
+
if (result.changed) modifiedAst = true;
|
|
2314
|
+
}
|
|
2315
|
+
if (ruleImportNames && ruleImportNames.size > 0) {
|
|
2316
|
+
appendUilintConfigBlockToArray(arrayExpr, rulesToApply, ruleImportNames);
|
|
2317
|
+
modifiedAst = true;
|
|
2318
|
+
}
|
|
2319
|
+
if (!info.configured) {
|
|
2341
2320
|
if (kind === "esm") {
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2321
|
+
mod.imports.$add({
|
|
2322
|
+
imported: "createRule",
|
|
2323
|
+
local: "createRule",
|
|
2324
|
+
from: "uilint-eslint"
|
|
2325
|
+
});
|
|
2326
|
+
modifiedAst = true;
|
|
2327
|
+
} else {
|
|
2328
|
+
const stmtMod = parseModule(
|
|
2329
|
+
`const { createRule } = require("uilint-eslint");`
|
|
2330
|
+
);
|
|
2331
|
+
const stmt = stmtMod.$ast.body?.[0];
|
|
2332
|
+
if (stmt) {
|
|
2333
|
+
let insertAt = 0;
|
|
2334
|
+
const first = mod.$ast.body?.[0];
|
|
2335
|
+
if (first?.type === "ExpressionStatement" && first.expression?.type === "StringLiteral" && first.expression.value === "use strict") {
|
|
2336
|
+
insertAt = 1;
|
|
2337
|
+
}
|
|
2338
|
+
mod.$ast.body.splice(insertAt, 0, stmt);
|
|
2339
|
+
modifiedAst = true;
|
|
2340
|
+
}
|
|
2347
2341
|
}
|
|
2348
2342
|
}
|
|
2349
2343
|
const updated = modifiedAst ? generateCode(mod).code : original;
|
|
@@ -2375,7 +2369,7 @@ function safeParseJson(filePath) {
|
|
|
2375
2369
|
}
|
|
2376
2370
|
}
|
|
2377
2371
|
async function analyze2(projectPath = process.cwd()) {
|
|
2378
|
-
const workspaceRoot =
|
|
2372
|
+
const workspaceRoot = findWorkspaceRoot5(projectPath);
|
|
2379
2373
|
const packageManager = detectPackageManager(projectPath);
|
|
2380
2374
|
const cursorDir = join8(projectPath, ".cursor");
|
|
2381
2375
|
const cursorDirExists = existsSync9(cursorDir);
|
|
@@ -2435,7 +2429,7 @@ async function analyze2(projectPath = process.cwd()) {
|
|
|
2435
2429
|
try {
|
|
2436
2430
|
const source = readFileSync5(eslintConfigPath, "utf-8");
|
|
2437
2431
|
const info = getUilintEslintConfigInfoFromSource(source);
|
|
2438
|
-
hasRules = info.configuredRuleIds.size > 0
|
|
2432
|
+
hasRules = info.configuredRuleIds.size > 0;
|
|
2439
2433
|
configuredRuleIds = Array.from(info.configuredRuleIds);
|
|
2440
2434
|
} catch {
|
|
2441
2435
|
}
|
|
@@ -2483,8 +2477,8 @@ async function analyze2(projectPath = process.cwd()) {
|
|
|
2483
2477
|
}
|
|
2484
2478
|
|
|
2485
2479
|
// src/commands/install/plan.ts
|
|
2486
|
-
import { join as
|
|
2487
|
-
import { createRequire } from "module";
|
|
2480
|
+
import { join as join11 } from "path";
|
|
2481
|
+
import { createRequire as createRequire2 } from "module";
|
|
2488
2482
|
|
|
2489
2483
|
// src/commands/install/constants.ts
|
|
2490
2484
|
var HOOKS_CONFIG = {
|
|
@@ -2874,10 +2868,10 @@ Generate in \`.uilint/rules/\`:
|
|
|
2874
2868
|
|
|
2875
2869
|
// src/utils/skill-loader.ts
|
|
2876
2870
|
import { readFileSync as readFileSync6, readdirSync as readdirSync4, statSync as statSync2, existsSync as existsSync10 } from "fs";
|
|
2877
|
-
import { join as join9, dirname as
|
|
2871
|
+
import { join as join9, dirname as dirname7, relative as relative3 } from "path";
|
|
2878
2872
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2879
2873
|
var __filename = fileURLToPath2(import.meta.url);
|
|
2880
|
-
var __dirname =
|
|
2874
|
+
var __dirname = dirname7(__filename);
|
|
2881
2875
|
function getSkillsDir() {
|
|
2882
2876
|
const devPath = join9(__dirname, "..", "..", "skills");
|
|
2883
2877
|
const prodPath = join9(__dirname, "..", "skills");
|
|
@@ -2900,7 +2894,7 @@ function collectFiles(dir, baseDir) {
|
|
|
2900
2894
|
if (stat.isDirectory()) {
|
|
2901
2895
|
files.push(...collectFiles(fullPath, baseDir));
|
|
2902
2896
|
} else if (stat.isFile()) {
|
|
2903
|
-
const relativePath =
|
|
2897
|
+
const relativePath = relative3(baseDir, fullPath);
|
|
2904
2898
|
const content = readFileSync6(fullPath, "utf-8");
|
|
2905
2899
|
files.push({ relativePath, content });
|
|
2906
2900
|
}
|
|
@@ -2921,11 +2915,154 @@ function loadSkill(name) {
|
|
|
2921
2915
|
return { name, files };
|
|
2922
2916
|
}
|
|
2923
2917
|
|
|
2924
|
-
// src/
|
|
2918
|
+
// src/utils/rule-loader.ts
|
|
2919
|
+
import { readFileSync as readFileSync7, existsSync as existsSync11 } from "fs";
|
|
2920
|
+
import { join as join10, dirname as dirname8 } from "path";
|
|
2921
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2922
|
+
import { createRequire } from "module";
|
|
2923
|
+
var __filename2 = fileURLToPath3(import.meta.url);
|
|
2924
|
+
var __dirname2 = dirname8(__filename2);
|
|
2925
2925
|
var require2 = createRequire(import.meta.url);
|
|
2926
|
+
function findNodeModulesPackageRoot(pkgName, startDir) {
|
|
2927
|
+
let dir = startDir;
|
|
2928
|
+
while (true) {
|
|
2929
|
+
const candidate = join10(dir, "node_modules", pkgName);
|
|
2930
|
+
if (existsSync11(join10(candidate, "package.json"))) return candidate;
|
|
2931
|
+
const parent = dirname8(dir);
|
|
2932
|
+
if (parent === dir) break;
|
|
2933
|
+
dir = parent;
|
|
2934
|
+
}
|
|
2935
|
+
return null;
|
|
2936
|
+
}
|
|
2937
|
+
function getUilintEslintPackageRoot() {
|
|
2938
|
+
const fromCwd = findNodeModulesPackageRoot("uilint-eslint", process.cwd());
|
|
2939
|
+
if (fromCwd) return fromCwd;
|
|
2940
|
+
const fromHere = findNodeModulesPackageRoot("uilint-eslint", __dirname2);
|
|
2941
|
+
if (fromHere) return fromHere;
|
|
2942
|
+
try {
|
|
2943
|
+
const entry = require2.resolve("uilint-eslint");
|
|
2944
|
+
const entryDir = dirname8(entry);
|
|
2945
|
+
return dirname8(entryDir);
|
|
2946
|
+
} catch (e) {
|
|
2947
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
2948
|
+
throw new Error(
|
|
2949
|
+
`Unable to locate uilint-eslint in node_modules (searched upwards from cwd and uilint's install path).
|
|
2950
|
+
Resolver error: ${msg}
|
|
2951
|
+
Fix: ensure uilint-eslint is installed in the target project (or workspace) and try again.`
|
|
2952
|
+
);
|
|
2953
|
+
}
|
|
2954
|
+
}
|
|
2955
|
+
function getUilintEslintSrcDir() {
|
|
2956
|
+
const devPath = join10(
|
|
2957
|
+
__dirname2,
|
|
2958
|
+
"..",
|
|
2959
|
+
"..",
|
|
2960
|
+
"..",
|
|
2961
|
+
"..",
|
|
2962
|
+
"uilint-eslint",
|
|
2963
|
+
"src"
|
|
2964
|
+
);
|
|
2965
|
+
if (existsSync11(devPath)) return devPath;
|
|
2966
|
+
const pkgRoot = getUilintEslintPackageRoot();
|
|
2967
|
+
const srcPath = join10(pkgRoot, "src");
|
|
2968
|
+
if (existsSync11(srcPath)) return srcPath;
|
|
2969
|
+
throw new Error(
|
|
2970
|
+
'Could not find uilint-eslint "src/" directory. If you are using a published install of uilint-eslint, ensure it includes source files, or run a JS-only rules install.'
|
|
2971
|
+
);
|
|
2972
|
+
}
|
|
2973
|
+
function getUilintEslintDistDir() {
|
|
2974
|
+
const devPath = join10(
|
|
2975
|
+
__dirname2,
|
|
2976
|
+
"..",
|
|
2977
|
+
"..",
|
|
2978
|
+
"..",
|
|
2979
|
+
"..",
|
|
2980
|
+
"uilint-eslint",
|
|
2981
|
+
"dist"
|
|
2982
|
+
);
|
|
2983
|
+
if (existsSync11(devPath)) return devPath;
|
|
2984
|
+
const pkgRoot = getUilintEslintPackageRoot();
|
|
2985
|
+
const distPath = join10(pkgRoot, "dist");
|
|
2986
|
+
if (existsSync11(distPath)) return distPath;
|
|
2987
|
+
throw new Error(
|
|
2988
|
+
'Could not find uilint-eslint "dist/" directory. This is a bug in uilint installation.'
|
|
2989
|
+
);
|
|
2990
|
+
}
|
|
2991
|
+
function transformRuleContent(content) {
|
|
2992
|
+
let transformed = content;
|
|
2993
|
+
transformed = transformed.replace(
|
|
2994
|
+
/import\s+{\s*createRule\s*}\s+from\s+["']\.\.\/utils\/create-rule\.js["'];?/g,
|
|
2995
|
+
'import { createRule } from "uilint-eslint";'
|
|
2996
|
+
);
|
|
2997
|
+
transformed = transformed.replace(
|
|
2998
|
+
/import\s+createRule\s+from\s+["']\.\.\/utils\/create-rule\.js["'];?/g,
|
|
2999
|
+
'import { createRule } from "uilint-eslint";'
|
|
3000
|
+
);
|
|
3001
|
+
transformed = transformed.replace(
|
|
3002
|
+
/import\s+{([^}]+)}\s+from\s+["']\.\.\/utils\/([^"']+)\.js["'];?/g,
|
|
3003
|
+
(match, imports, utilFile) => {
|
|
3004
|
+
const utilsFromPackage = ["cache", "styleguide-loader", "import-graph"];
|
|
3005
|
+
if (utilsFromPackage.includes(utilFile)) {
|
|
3006
|
+
return `import {${imports}} from "uilint-eslint";`;
|
|
3007
|
+
}
|
|
3008
|
+
return match;
|
|
3009
|
+
}
|
|
3010
|
+
);
|
|
3011
|
+
return transformed;
|
|
3012
|
+
}
|
|
3013
|
+
function loadRule(ruleId, options = { typescript: true }) {
|
|
3014
|
+
const { typescript } = options;
|
|
3015
|
+
const extension = typescript ? ".ts" : ".js";
|
|
3016
|
+
if (typescript) {
|
|
3017
|
+
const rulesDir = join10(getUilintEslintSrcDir(), "rules");
|
|
3018
|
+
const implPath = join10(rulesDir, `${ruleId}.ts`);
|
|
3019
|
+
const testPath = join10(rulesDir, `${ruleId}.test.ts`);
|
|
3020
|
+
if (!existsSync11(implPath)) {
|
|
3021
|
+
throw new Error(`Rule "${ruleId}" not found at ${implPath}`);
|
|
3022
|
+
}
|
|
3023
|
+
const rawContent = readFileSync7(implPath, "utf-8");
|
|
3024
|
+
const transformedContent = transformRuleContent(rawContent);
|
|
3025
|
+
const implementation = {
|
|
3026
|
+
relativePath: `${ruleId}.ts`,
|
|
3027
|
+
content: transformedContent
|
|
3028
|
+
};
|
|
3029
|
+
const test = existsSync11(testPath) ? {
|
|
3030
|
+
relativePath: `${ruleId}.test.ts`,
|
|
3031
|
+
content: transformRuleContent(readFileSync7(testPath, "utf-8"))
|
|
3032
|
+
} : void 0;
|
|
3033
|
+
return {
|
|
3034
|
+
ruleId,
|
|
3035
|
+
implementation,
|
|
3036
|
+
test
|
|
3037
|
+
};
|
|
3038
|
+
} else {
|
|
3039
|
+
const rulesDir = join10(getUilintEslintDistDir(), "rules");
|
|
3040
|
+
const implPath = join10(rulesDir, `${ruleId}.js`);
|
|
3041
|
+
if (!existsSync11(implPath)) {
|
|
3042
|
+
throw new Error(
|
|
3043
|
+
`Rule "${ruleId}" not found at ${implPath}. For JavaScript-only projects, uilint-eslint must be built to include compiled rule files in dist/rules/. If you're developing uilint-eslint, run 'pnpm build' in packages/uilint-eslint. If you're using a published package, ensure it includes the dist/ directory.`
|
|
3044
|
+
);
|
|
3045
|
+
}
|
|
3046
|
+
const content = readFileSync7(implPath, "utf-8");
|
|
3047
|
+
const implementation = {
|
|
3048
|
+
relativePath: `${ruleId}.js`,
|
|
3049
|
+
content
|
|
3050
|
+
};
|
|
3051
|
+
return {
|
|
3052
|
+
ruleId,
|
|
3053
|
+
implementation
|
|
3054
|
+
};
|
|
3055
|
+
}
|
|
3056
|
+
}
|
|
3057
|
+
function loadSelectedRules(ruleIds, options = { typescript: true }) {
|
|
3058
|
+
return ruleIds.map((id) => loadRule(id, options));
|
|
3059
|
+
}
|
|
3060
|
+
|
|
3061
|
+
// src/commands/install/plan.ts
|
|
3062
|
+
var require3 = createRequire2(import.meta.url);
|
|
2926
3063
|
function getSelfDependencyVersionRange(pkgName) {
|
|
2927
3064
|
try {
|
|
2928
|
-
const pkgJson =
|
|
3065
|
+
const pkgJson = require3("uilint/package.json");
|
|
2929
3066
|
const deps = pkgJson?.dependencies;
|
|
2930
3067
|
const optDeps = pkgJson?.optionalDependencies;
|
|
2931
3068
|
const peerDeps = pkgJson?.peerDependencies;
|
|
@@ -3000,7 +3137,7 @@ function createPlan(state, choices, options = {}) {
|
|
|
3000
3137
|
}
|
|
3001
3138
|
}
|
|
3002
3139
|
if (items.includes("hooks")) {
|
|
3003
|
-
const hooksDir =
|
|
3140
|
+
const hooksDir = join11(state.cursorDir.path, "hooks");
|
|
3004
3141
|
actions.push({
|
|
3005
3142
|
type: "create_directory",
|
|
3006
3143
|
path: hooksDir
|
|
@@ -3026,63 +3163,66 @@ function createPlan(state, choices, options = {}) {
|
|
|
3026
3163
|
});
|
|
3027
3164
|
actions.push({
|
|
3028
3165
|
type: "create_file",
|
|
3029
|
-
path:
|
|
3166
|
+
path: join11(hooksDir, "uilint-session-start.sh"),
|
|
3030
3167
|
content: SESSION_START_SCRIPT,
|
|
3031
3168
|
permissions: 493
|
|
3032
3169
|
});
|
|
3033
3170
|
actions.push({
|
|
3034
3171
|
type: "create_file",
|
|
3035
|
-
path:
|
|
3172
|
+
path: join11(hooksDir, "uilint-track.sh"),
|
|
3036
3173
|
content: TRACK_SCRIPT,
|
|
3037
3174
|
permissions: 493
|
|
3038
3175
|
});
|
|
3039
3176
|
actions.push({
|
|
3040
3177
|
type: "create_file",
|
|
3041
|
-
path:
|
|
3178
|
+
path: join11(hooksDir, "uilint-session-end.sh"),
|
|
3042
3179
|
content: SESSION_END_SCRIPT,
|
|
3043
3180
|
permissions: 493
|
|
3044
3181
|
});
|
|
3045
3182
|
}
|
|
3046
3183
|
if (items.includes("genstyleguide")) {
|
|
3047
|
-
const commandsDir =
|
|
3184
|
+
const commandsDir = join11(state.cursorDir.path, "commands");
|
|
3048
3185
|
actions.push({
|
|
3049
3186
|
type: "create_directory",
|
|
3050
3187
|
path: commandsDir
|
|
3051
3188
|
});
|
|
3052
3189
|
actions.push({
|
|
3053
3190
|
type: "create_file",
|
|
3054
|
-
path:
|
|
3191
|
+
path: join11(commandsDir, "genstyleguide.md"),
|
|
3055
3192
|
content: GENSTYLEGUIDE_COMMAND_MD
|
|
3056
3193
|
});
|
|
3057
3194
|
}
|
|
3058
3195
|
if (items.includes("genrules")) {
|
|
3059
|
-
const commandsDir =
|
|
3196
|
+
const commandsDir = join11(state.cursorDir.path, "commands");
|
|
3060
3197
|
actions.push({
|
|
3061
3198
|
type: "create_directory",
|
|
3062
3199
|
path: commandsDir
|
|
3063
3200
|
});
|
|
3064
3201
|
actions.push({
|
|
3065
3202
|
type: "create_file",
|
|
3066
|
-
path:
|
|
3203
|
+
path: join11(commandsDir, "genrules.md"),
|
|
3067
3204
|
content: GENRULES_COMMAND_MD
|
|
3068
3205
|
});
|
|
3069
3206
|
}
|
|
3070
3207
|
if (items.includes("skill")) {
|
|
3071
|
-
const skillsDir =
|
|
3208
|
+
const skillsDir = join11(state.cursorDir.path, "skills");
|
|
3072
3209
|
actions.push({
|
|
3073
3210
|
type: "create_directory",
|
|
3074
3211
|
path: skillsDir
|
|
3075
3212
|
});
|
|
3076
3213
|
try {
|
|
3077
3214
|
const skill = loadSkill("ui-consistency-enforcer");
|
|
3078
|
-
const skillDir =
|
|
3215
|
+
const skillDir = join11(skillsDir, skill.name);
|
|
3079
3216
|
actions.push({
|
|
3080
3217
|
type: "create_directory",
|
|
3081
3218
|
path: skillDir
|
|
3082
3219
|
});
|
|
3083
3220
|
for (const file of skill.files) {
|
|
3084
|
-
const filePath =
|
|
3085
|
-
const fileDir =
|
|
3221
|
+
const filePath = join11(skillDir, file.relativePath);
|
|
3222
|
+
const fileDir = join11(
|
|
3223
|
+
skillDir,
|
|
3224
|
+
file.relativePath.split("/").slice(0, -1).join("/")
|
|
3225
|
+
);
|
|
3086
3226
|
if (fileDir !== skillDir && file.relativePath.includes("/")) {
|
|
3087
3227
|
actions.push({
|
|
3088
3228
|
type: "create_directory",
|
|
@@ -3142,6 +3282,32 @@ function createPlan(state, choices, options = {}) {
|
|
|
3142
3282
|
const { packagePaths, selectedRules } = choices.eslint;
|
|
3143
3283
|
for (const pkgPath of packagePaths) {
|
|
3144
3284
|
const pkgInfo = state.packages.find((p2) => p2.path === pkgPath);
|
|
3285
|
+
const rulesDir = join11(pkgPath, ".uilint", "rules");
|
|
3286
|
+
actions.push({
|
|
3287
|
+
type: "create_directory",
|
|
3288
|
+
path: rulesDir
|
|
3289
|
+
});
|
|
3290
|
+
const isTypeScript = pkgInfo?.isTypeScript ?? true;
|
|
3291
|
+
const ruleFiles = loadSelectedRules(
|
|
3292
|
+
selectedRules.map((r) => r.id),
|
|
3293
|
+
{
|
|
3294
|
+
typescript: isTypeScript
|
|
3295
|
+
}
|
|
3296
|
+
);
|
|
3297
|
+
for (const ruleFile of ruleFiles) {
|
|
3298
|
+
actions.push({
|
|
3299
|
+
type: "create_file",
|
|
3300
|
+
path: join11(rulesDir, ruleFile.implementation.relativePath),
|
|
3301
|
+
content: ruleFile.implementation.content
|
|
3302
|
+
});
|
|
3303
|
+
if (ruleFile.test && isTypeScript) {
|
|
3304
|
+
actions.push({
|
|
3305
|
+
type: "create_file",
|
|
3306
|
+
path: join11(rulesDir, ruleFile.test.relativePath),
|
|
3307
|
+
content: ruleFile.test.content
|
|
3308
|
+
});
|
|
3309
|
+
}
|
|
3310
|
+
}
|
|
3145
3311
|
dependencies.push({
|
|
3146
3312
|
packagePath: pkgPath,
|
|
3147
3313
|
packageManager: state.packageManager,
|
|
@@ -3157,7 +3323,7 @@ function createPlan(state, choices, options = {}) {
|
|
|
3157
3323
|
});
|
|
3158
3324
|
}
|
|
3159
3325
|
}
|
|
3160
|
-
const gitignorePath =
|
|
3326
|
+
const gitignorePath = join11(state.workspaceRoot, ".gitignore");
|
|
3161
3327
|
actions.push({
|
|
3162
3328
|
type: "append_to_file",
|
|
3163
3329
|
path: gitignorePath,
|
|
@@ -3170,49 +3336,49 @@ function createPlan(state, choices, options = {}) {
|
|
|
3170
3336
|
|
|
3171
3337
|
// src/commands/install/execute.ts
|
|
3172
3338
|
import {
|
|
3173
|
-
existsSync as
|
|
3339
|
+
existsSync as existsSync16,
|
|
3174
3340
|
mkdirSync as mkdirSync3,
|
|
3175
3341
|
writeFileSync as writeFileSync7,
|
|
3176
|
-
readFileSync as
|
|
3342
|
+
readFileSync as readFileSync11,
|
|
3177
3343
|
unlinkSync,
|
|
3178
3344
|
chmodSync
|
|
3179
3345
|
} from "fs";
|
|
3180
|
-
import { dirname as
|
|
3346
|
+
import { dirname as dirname9 } from "path";
|
|
3181
3347
|
|
|
3182
3348
|
// src/utils/react-inject.ts
|
|
3183
|
-
import { existsSync as
|
|
3184
|
-
import { join as
|
|
3349
|
+
import { existsSync as existsSync12, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
|
|
3350
|
+
import { join as join12 } from "path";
|
|
3185
3351
|
import { parseModule as parseModule2, generateCode as generateCode2 } from "magicast";
|
|
3186
3352
|
function getDefaultCandidates(projectPath, appRoot) {
|
|
3187
3353
|
const viteMainCandidates = [
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3354
|
+
join12(appRoot, "main.tsx"),
|
|
3355
|
+
join12(appRoot, "main.jsx"),
|
|
3356
|
+
join12(appRoot, "main.ts"),
|
|
3357
|
+
join12(appRoot, "main.js")
|
|
3192
3358
|
];
|
|
3193
3359
|
const existingViteMain = viteMainCandidates.filter(
|
|
3194
|
-
(rel) =>
|
|
3360
|
+
(rel) => existsSync12(join12(projectPath, rel))
|
|
3195
3361
|
);
|
|
3196
3362
|
if (existingViteMain.length > 0) return existingViteMain;
|
|
3197
|
-
const viteAppCandidates = [
|
|
3363
|
+
const viteAppCandidates = [join12(appRoot, "App.tsx"), join12(appRoot, "App.jsx")];
|
|
3198
3364
|
const existingViteApp = viteAppCandidates.filter(
|
|
3199
|
-
(rel) =>
|
|
3365
|
+
(rel) => existsSync12(join12(projectPath, rel))
|
|
3200
3366
|
);
|
|
3201
3367
|
if (existingViteApp.length > 0) return existingViteApp;
|
|
3202
3368
|
const layoutCandidates = [
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3369
|
+
join12(appRoot, "layout.tsx"),
|
|
3370
|
+
join12(appRoot, "layout.jsx"),
|
|
3371
|
+
join12(appRoot, "layout.ts"),
|
|
3372
|
+
join12(appRoot, "layout.js")
|
|
3207
3373
|
];
|
|
3208
3374
|
const existingLayouts = layoutCandidates.filter(
|
|
3209
|
-
(rel) =>
|
|
3375
|
+
(rel) => existsSync12(join12(projectPath, rel))
|
|
3210
3376
|
);
|
|
3211
3377
|
if (existingLayouts.length > 0) {
|
|
3212
3378
|
return existingLayouts;
|
|
3213
3379
|
}
|
|
3214
|
-
const pageCandidates = [
|
|
3215
|
-
return pageCandidates.filter((rel) =>
|
|
3380
|
+
const pageCandidates = [join12(appRoot, "page.tsx"), join12(appRoot, "page.jsx")];
|
|
3381
|
+
return pageCandidates.filter((rel) => existsSync12(join12(projectPath, rel)));
|
|
3216
3382
|
}
|
|
3217
3383
|
function isUseClientDirective(stmt) {
|
|
3218
3384
|
return stmt?.type === "ExpressionStatement" && stmt.expression?.type === "StringLiteral" && stmt.expression.value === "use client";
|
|
@@ -3225,16 +3391,16 @@ function findImportDeclaration(program2, from) {
|
|
|
3225
3391
|
}
|
|
3226
3392
|
return null;
|
|
3227
3393
|
}
|
|
3228
|
-
function
|
|
3394
|
+
function walkAst(node, visit) {
|
|
3229
3395
|
if (!node || typeof node !== "object") return;
|
|
3230
3396
|
if (node.type) visit(node);
|
|
3231
3397
|
for (const key of Object.keys(node)) {
|
|
3232
3398
|
const v = node[key];
|
|
3233
3399
|
if (!v) continue;
|
|
3234
3400
|
if (Array.isArray(v)) {
|
|
3235
|
-
for (const item of v)
|
|
3401
|
+
for (const item of v) walkAst(item, visit);
|
|
3236
3402
|
} else if (typeof v === "object" && v.type) {
|
|
3237
|
-
|
|
3403
|
+
walkAst(v, visit);
|
|
3238
3404
|
}
|
|
3239
3405
|
}
|
|
3240
3406
|
}
|
|
@@ -3266,7 +3432,7 @@ function ensureNamedImport(program2, from, name) {
|
|
|
3266
3432
|
}
|
|
3267
3433
|
function hasUILintProviderJsx(program2) {
|
|
3268
3434
|
let found = false;
|
|
3269
|
-
|
|
3435
|
+
walkAst(program2, (node) => {
|
|
3270
3436
|
if (found) return;
|
|
3271
3437
|
if (node.type !== "JSXElement") return;
|
|
3272
3438
|
const name = node.openingElement?.name;
|
|
@@ -3286,7 +3452,7 @@ function wrapFirstChildrenExpressionWithProvider(program2) {
|
|
|
3286
3452
|
if (!providerJsx || providerJsx.type !== "JSXElement")
|
|
3287
3453
|
return { changed: false };
|
|
3288
3454
|
let replaced = false;
|
|
3289
|
-
|
|
3455
|
+
walkAst(program2, (node) => {
|
|
3290
3456
|
if (replaced) return;
|
|
3291
3457
|
if (node.type === "JSXExpressionContainer" && node.expression?.type === "Identifier" && node.expression.name === "children") {
|
|
3292
3458
|
Object.keys(node).forEach((k) => delete node[k]);
|
|
@@ -3310,7 +3476,7 @@ function wrapFirstRenderCallArgumentWithProvider(program2) {
|
|
|
3310
3476
|
return { changed: false };
|
|
3311
3477
|
providerJsx.children = providerJsx.children ?? [];
|
|
3312
3478
|
let wrapped = false;
|
|
3313
|
-
|
|
3479
|
+
walkAst(program2, (node) => {
|
|
3314
3480
|
if (wrapped) return;
|
|
3315
3481
|
if (node.type !== "CallExpression") return;
|
|
3316
3482
|
const callee = node.callee;
|
|
@@ -3345,8 +3511,8 @@ async function installReactUILintOverlay(opts) {
|
|
|
3345
3511
|
} else {
|
|
3346
3512
|
chosen = candidates[0];
|
|
3347
3513
|
}
|
|
3348
|
-
const absTarget =
|
|
3349
|
-
const original =
|
|
3514
|
+
const absTarget = join12(opts.projectPath, chosen);
|
|
3515
|
+
const original = readFileSync8(absTarget, "utf-8");
|
|
3350
3516
|
let mod;
|
|
3351
3517
|
try {
|
|
3352
3518
|
mod = parseModule2(original);
|
|
@@ -3380,14 +3546,14 @@ async function installReactUILintOverlay(opts) {
|
|
|
3380
3546
|
}
|
|
3381
3547
|
|
|
3382
3548
|
// src/utils/next-config-inject.ts
|
|
3383
|
-
import { existsSync as
|
|
3384
|
-
import { join as
|
|
3549
|
+
import { existsSync as existsSync13, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "fs";
|
|
3550
|
+
import { join as join13 } from "path";
|
|
3385
3551
|
import { parseModule as parseModule3, generateCode as generateCode3 } from "magicast";
|
|
3386
3552
|
var CONFIG_EXTENSIONS2 = [".ts", ".mjs", ".js", ".cjs"];
|
|
3387
3553
|
function findNextConfigFile(projectPath) {
|
|
3388
3554
|
for (const ext of CONFIG_EXTENSIONS2) {
|
|
3389
|
-
const configPath =
|
|
3390
|
-
if (
|
|
3555
|
+
const configPath = join13(projectPath, `next.config${ext}`);
|
|
3556
|
+
if (existsSync13(configPath)) {
|
|
3391
3557
|
return configPath;
|
|
3392
3558
|
}
|
|
3393
3559
|
}
|
|
@@ -3500,7 +3666,7 @@ async function installJsxLocPlugin(opts) {
|
|
|
3500
3666
|
return { configFile: null, modified: false };
|
|
3501
3667
|
}
|
|
3502
3668
|
const configFilename = getNextConfigFilename(configPath);
|
|
3503
|
-
const original =
|
|
3669
|
+
const original = readFileSync9(configPath, "utf-8");
|
|
3504
3670
|
let mod;
|
|
3505
3671
|
try {
|
|
3506
3672
|
mod = parseModule3(original);
|
|
@@ -3530,14 +3696,14 @@ async function installJsxLocPlugin(opts) {
|
|
|
3530
3696
|
}
|
|
3531
3697
|
|
|
3532
3698
|
// src/utils/vite-config-inject.ts
|
|
3533
|
-
import { existsSync as
|
|
3534
|
-
import { join as
|
|
3699
|
+
import { existsSync as existsSync14, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "fs";
|
|
3700
|
+
import { join as join14 } from "path";
|
|
3535
3701
|
import { parseModule as parseModule4, generateCode as generateCode4 } from "magicast";
|
|
3536
3702
|
var CONFIG_EXTENSIONS3 = [".ts", ".mjs", ".js", ".cjs"];
|
|
3537
3703
|
function findViteConfigFile2(projectPath) {
|
|
3538
3704
|
for (const ext of CONFIG_EXTENSIONS3) {
|
|
3539
|
-
const configPath =
|
|
3540
|
-
if (
|
|
3705
|
+
const configPath = join14(projectPath, `vite.config${ext}`);
|
|
3706
|
+
if (existsSync14(configPath)) return configPath;
|
|
3541
3707
|
}
|
|
3542
3708
|
return null;
|
|
3543
3709
|
}
|
|
@@ -3712,7 +3878,7 @@ async function installViteJsxLocPlugin(opts) {
|
|
|
3712
3878
|
const configPath = findViteConfigFile2(opts.projectPath);
|
|
3713
3879
|
if (!configPath) return { configFile: null, modified: false };
|
|
3714
3880
|
const configFilename = getViteConfigFilename(configPath);
|
|
3715
|
-
const original =
|
|
3881
|
+
const original = readFileSync10(configPath, "utf-8");
|
|
3716
3882
|
const isCjs = configPath.endsWith(".cjs");
|
|
3717
3883
|
let mod;
|
|
3718
3884
|
try {
|
|
@@ -3741,9 +3907,9 @@ async function installViteJsxLocPlugin(opts) {
|
|
|
3741
3907
|
}
|
|
3742
3908
|
|
|
3743
3909
|
// src/utils/next-routes.ts
|
|
3744
|
-
import { existsSync as
|
|
3910
|
+
import { existsSync as existsSync15 } from "fs";
|
|
3745
3911
|
import { mkdir, writeFile } from "fs/promises";
|
|
3746
|
-
import { join as
|
|
3912
|
+
import { join as join15 } from "path";
|
|
3747
3913
|
var DEV_SOURCE_ROUTE_TS = `/**
|
|
3748
3914
|
* Dev-only API route for fetching source files
|
|
3749
3915
|
*
|
|
@@ -4199,23 +4365,23 @@ export async function GET(request: NextRequest) {
|
|
|
4199
4365
|
}
|
|
4200
4366
|
`;
|
|
4201
4367
|
async function writeRouteFile(absPath, relPath, content, opts) {
|
|
4202
|
-
if (
|
|
4368
|
+
if (existsSync15(absPath) && !opts.force) return;
|
|
4203
4369
|
await writeFile(absPath, content, "utf-8");
|
|
4204
4370
|
}
|
|
4205
4371
|
async function installNextUILintRoutes(opts) {
|
|
4206
|
-
const baseRel =
|
|
4207
|
-
const baseAbs =
|
|
4208
|
-
await mkdir(
|
|
4372
|
+
const baseRel = join15(opts.appRoot, "api", ".uilint");
|
|
4373
|
+
const baseAbs = join15(opts.projectPath, baseRel);
|
|
4374
|
+
await mkdir(join15(baseAbs, "source"), { recursive: true });
|
|
4209
4375
|
await writeRouteFile(
|
|
4210
|
-
|
|
4211
|
-
|
|
4376
|
+
join15(baseAbs, "source", "route.ts"),
|
|
4377
|
+
join15(baseRel, "source", "route.ts"),
|
|
4212
4378
|
DEV_SOURCE_ROUTE_TS,
|
|
4213
4379
|
opts
|
|
4214
4380
|
);
|
|
4215
|
-
await mkdir(
|
|
4381
|
+
await mkdir(join15(baseAbs, "screenshots"), { recursive: true });
|
|
4216
4382
|
await writeRouteFile(
|
|
4217
|
-
|
|
4218
|
-
|
|
4383
|
+
join15(baseAbs, "screenshots", "route.ts"),
|
|
4384
|
+
join15(baseRel, "screenshots", "route.ts"),
|
|
4219
4385
|
SCREENSHOT_ROUTE_TS,
|
|
4220
4386
|
opts
|
|
4221
4387
|
);
|
|
@@ -4234,7 +4400,7 @@ async function executeAction(action, options) {
|
|
|
4234
4400
|
wouldDo: `Create directory: ${action.path}`
|
|
4235
4401
|
};
|
|
4236
4402
|
}
|
|
4237
|
-
if (!
|
|
4403
|
+
if (!existsSync16(action.path)) {
|
|
4238
4404
|
mkdirSync3(action.path, { recursive: true });
|
|
4239
4405
|
}
|
|
4240
4406
|
return { action, success: true };
|
|
@@ -4247,8 +4413,8 @@ async function executeAction(action, options) {
|
|
|
4247
4413
|
wouldDo: `Create file: ${action.path}${action.permissions ? ` (mode: ${action.permissions.toString(8)})` : ""}`
|
|
4248
4414
|
};
|
|
4249
4415
|
}
|
|
4250
|
-
const dir =
|
|
4251
|
-
if (!
|
|
4416
|
+
const dir = dirname9(action.path);
|
|
4417
|
+
if (!existsSync16(dir)) {
|
|
4252
4418
|
mkdirSync3(dir, { recursive: true });
|
|
4253
4419
|
}
|
|
4254
4420
|
writeFileSync7(action.path, action.content, "utf-8");
|
|
@@ -4266,15 +4432,15 @@ async function executeAction(action, options) {
|
|
|
4266
4432
|
};
|
|
4267
4433
|
}
|
|
4268
4434
|
let existing = {};
|
|
4269
|
-
if (
|
|
4435
|
+
if (existsSync16(action.path)) {
|
|
4270
4436
|
try {
|
|
4271
|
-
existing = JSON.parse(
|
|
4437
|
+
existing = JSON.parse(readFileSync11(action.path, "utf-8"));
|
|
4272
4438
|
} catch {
|
|
4273
4439
|
}
|
|
4274
4440
|
}
|
|
4275
4441
|
const merged = deepMerge(existing, action.merge);
|
|
4276
|
-
const dir =
|
|
4277
|
-
if (!
|
|
4442
|
+
const dir = dirname9(action.path);
|
|
4443
|
+
if (!existsSync16(dir)) {
|
|
4278
4444
|
mkdirSync3(dir, { recursive: true });
|
|
4279
4445
|
}
|
|
4280
4446
|
writeFileSync7(action.path, JSON.stringify(merged, null, 2), "utf-8");
|
|
@@ -4288,7 +4454,7 @@ async function executeAction(action, options) {
|
|
|
4288
4454
|
wouldDo: `Delete file: ${action.path}`
|
|
4289
4455
|
};
|
|
4290
4456
|
}
|
|
4291
|
-
if (
|
|
4457
|
+
if (existsSync16(action.path)) {
|
|
4292
4458
|
unlinkSync(action.path);
|
|
4293
4459
|
}
|
|
4294
4460
|
return { action, success: true };
|
|
@@ -4301,8 +4467,8 @@ async function executeAction(action, options) {
|
|
|
4301
4467
|
wouldDo: `Append to file: ${action.path}`
|
|
4302
4468
|
};
|
|
4303
4469
|
}
|
|
4304
|
-
if (
|
|
4305
|
-
const content =
|
|
4470
|
+
if (existsSync16(action.path)) {
|
|
4471
|
+
const content = readFileSync11(action.path, "utf-8");
|
|
4306
4472
|
if (action.ifNotContains && content.includes(action.ifNotContains)) {
|
|
4307
4473
|
return { action, success: true };
|
|
4308
4474
|
}
|
|
@@ -4988,7 +5154,7 @@ function displayResults(result) {
|
|
|
4988
5154
|
if (summary.nextApp) {
|
|
4989
5155
|
installedItems.push(
|
|
4990
5156
|
`${pc.cyan("Next Routes")} \u2192 ${pc.dim(
|
|
4991
|
-
|
|
5157
|
+
join16(summary.nextApp.appRoot, "api/.uilint")
|
|
4992
5158
|
)}`
|
|
4993
5159
|
);
|
|
4994
5160
|
installedItems.push(
|
|
@@ -5123,19 +5289,19 @@ async function install(options = {}, prompter = cliPrompter, executeOptions = {}
|
|
|
5123
5289
|
}
|
|
5124
5290
|
|
|
5125
5291
|
// src/commands/serve.ts
|
|
5126
|
-
import { existsSync as
|
|
5127
|
-
import { createRequire as
|
|
5128
|
-
import { dirname as
|
|
5292
|
+
import { existsSync as existsSync18, statSync as statSync4, readdirSync as readdirSync5, readFileSync as readFileSync12 } from "fs";
|
|
5293
|
+
import { createRequire as createRequire3 } from "module";
|
|
5294
|
+
import { dirname as dirname11, resolve as resolve5, relative as relative4, join as join18, parse as parse2 } from "path";
|
|
5129
5295
|
import { WebSocketServer, WebSocket } from "ws";
|
|
5130
5296
|
import { watch } from "chokidar";
|
|
5131
5297
|
import {
|
|
5132
|
-
findWorkspaceRoot as
|
|
5298
|
+
findWorkspaceRoot as findWorkspaceRoot6,
|
|
5133
5299
|
getVisionAnalyzer as getCoreVisionAnalyzer
|
|
5134
5300
|
} from "uilint-core/node";
|
|
5135
5301
|
|
|
5136
5302
|
// src/utils/vision-run.ts
|
|
5137
|
-
import { dirname as
|
|
5138
|
-
import { existsSync as
|
|
5303
|
+
import { dirname as dirname10, join as join17, parse } from "path";
|
|
5304
|
+
import { existsSync as existsSync17, statSync as statSync3, mkdirSync as mkdirSync4, writeFileSync as writeFileSync8 } from "fs";
|
|
5139
5305
|
import {
|
|
5140
5306
|
ensureOllamaReady as ensureOllamaReady5,
|
|
5141
5307
|
findStyleGuidePath as findStyleGuidePath4,
|
|
@@ -5149,7 +5315,7 @@ async function resolveVisionStyleGuide(args) {
|
|
|
5149
5315
|
const startDir = args.startDir ?? projectPath;
|
|
5150
5316
|
if (args.styleguide) {
|
|
5151
5317
|
const styleguideArg = resolvePathSpecifier(args.styleguide, projectPath);
|
|
5152
|
-
if (
|
|
5318
|
+
if (existsSync17(styleguideArg)) {
|
|
5153
5319
|
const stat = statSync3(styleguideArg);
|
|
5154
5320
|
if (stat.isFile()) {
|
|
5155
5321
|
return {
|
|
@@ -5193,7 +5359,7 @@ function writeVisionDebugDump(params) {
|
|
|
5193
5359
|
);
|
|
5194
5360
|
const safeStamp = params.now.toISOString().replace(/[:.]/g, "-");
|
|
5195
5361
|
const dumpFile = resolvedDirOrFile.endsWith(".json") || resolvedDirOrFile.endsWith(".jsonl") ? resolvedDirOrFile : `${resolvedDirOrFile}/vision-debug-${safeStamp}.json`;
|
|
5196
|
-
mkdirSync4(
|
|
5362
|
+
mkdirSync4(dirname10(dumpFile), { recursive: true });
|
|
5197
5363
|
writeFileSync8(
|
|
5198
5364
|
dumpFile,
|
|
5199
5365
|
JSON.stringify(
|
|
@@ -5282,7 +5448,7 @@ async function runVisionAnalysis(args) {
|
|
|
5282
5448
|
}
|
|
5283
5449
|
function writeVisionMarkdownReport(args) {
|
|
5284
5450
|
const p2 = parse(args.imagePath);
|
|
5285
|
-
const outPath = args.outPath ??
|
|
5451
|
+
const outPath = args.outPath ?? join17(p2.dir, `${p2.name || p2.base}.vision.md`);
|
|
5286
5452
|
const lines = [];
|
|
5287
5453
|
lines.push(`# UILint Vision Report`);
|
|
5288
5454
|
lines.push(``);
|
|
@@ -5318,7 +5484,7 @@ function writeVisionMarkdownReport(args) {
|
|
|
5318
5484
|
lines.push("```");
|
|
5319
5485
|
lines.push(``);
|
|
5320
5486
|
const content = lines.join("\n");
|
|
5321
|
-
mkdirSync4(
|
|
5487
|
+
mkdirSync4(dirname10(outPath), { recursive: true });
|
|
5322
5488
|
writeFileSync8(outPath, content, "utf-8");
|
|
5323
5489
|
return { outPath, content };
|
|
5324
5490
|
}
|
|
@@ -5354,7 +5520,7 @@ var resolvedPathCache = /* @__PURE__ */ new Map();
|
|
|
5354
5520
|
var subscriptions = /* @__PURE__ */ new Map();
|
|
5355
5521
|
var fileWatcher = null;
|
|
5356
5522
|
var connectedClients = 0;
|
|
5357
|
-
var localRequire =
|
|
5523
|
+
var localRequire = createRequire3(import.meta.url);
|
|
5358
5524
|
function buildLineStarts(code) {
|
|
5359
5525
|
const starts = [0];
|
|
5360
5526
|
for (let i = 0; i < code.length; i++) {
|
|
@@ -5432,10 +5598,10 @@ function findESLintCwd(startDir) {
|
|
|
5432
5598
|
let dir = startDir;
|
|
5433
5599
|
for (let i = 0; i < 30; i++) {
|
|
5434
5600
|
for (const cfg of ESLINT_CONFIG_FILES2) {
|
|
5435
|
-
if (
|
|
5601
|
+
if (existsSync18(join18(dir, cfg))) return dir;
|
|
5436
5602
|
}
|
|
5437
|
-
if (
|
|
5438
|
-
const parent =
|
|
5603
|
+
if (existsSync18(join18(dir, "package.json"))) return dir;
|
|
5604
|
+
const parent = dirname11(dir);
|
|
5439
5605
|
if (parent === dir) break;
|
|
5440
5606
|
dir = parent;
|
|
5441
5607
|
}
|
|
@@ -5448,7 +5614,7 @@ function normalizeDataLocFilePath(absoluteFilePath, projectCwd) {
|
|
|
5448
5614
|
const abs = normalizePathSlashes(resolve5(absoluteFilePath));
|
|
5449
5615
|
const cwd = normalizePathSlashes(resolve5(projectCwd));
|
|
5450
5616
|
if (abs === cwd || abs.startsWith(cwd + "/")) {
|
|
5451
|
-
return normalizePathSlashes(
|
|
5617
|
+
return normalizePathSlashes(relative4(cwd, abs));
|
|
5452
5618
|
}
|
|
5453
5619
|
return abs;
|
|
5454
5620
|
}
|
|
@@ -5460,25 +5626,25 @@ function resolveRequestedFilePath(filePath) {
|
|
|
5460
5626
|
if (cached) return cached;
|
|
5461
5627
|
const cwd = process.cwd();
|
|
5462
5628
|
const fromCwd = resolve5(cwd, filePath);
|
|
5463
|
-
if (
|
|
5629
|
+
if (existsSync18(fromCwd)) {
|
|
5464
5630
|
resolvedPathCache.set(filePath, fromCwd);
|
|
5465
5631
|
return fromCwd;
|
|
5466
5632
|
}
|
|
5467
|
-
const wsRoot =
|
|
5633
|
+
const wsRoot = findWorkspaceRoot6(cwd);
|
|
5468
5634
|
const fromWs = resolve5(wsRoot, filePath);
|
|
5469
|
-
if (
|
|
5635
|
+
if (existsSync18(fromWs)) {
|
|
5470
5636
|
resolvedPathCache.set(filePath, fromWs);
|
|
5471
5637
|
return fromWs;
|
|
5472
5638
|
}
|
|
5473
5639
|
for (const top of ["apps", "packages"]) {
|
|
5474
|
-
const base =
|
|
5475
|
-
if (!
|
|
5640
|
+
const base = join18(wsRoot, top);
|
|
5641
|
+
if (!existsSync18(base)) continue;
|
|
5476
5642
|
try {
|
|
5477
5643
|
const entries = readdirSync5(base, { withFileTypes: true });
|
|
5478
5644
|
for (const ent of entries) {
|
|
5479
5645
|
if (!ent.isDirectory()) continue;
|
|
5480
5646
|
const p2 = resolve5(base, ent.name, filePath);
|
|
5481
|
-
if (
|
|
5647
|
+
if (existsSync18(p2)) {
|
|
5482
5648
|
resolvedPathCache.set(filePath, p2);
|
|
5483
5649
|
return p2;
|
|
5484
5650
|
}
|
|
@@ -5493,7 +5659,7 @@ async function getESLintForProject(projectCwd) {
|
|
|
5493
5659
|
const cached = eslintInstances.get(projectCwd);
|
|
5494
5660
|
if (cached) return cached;
|
|
5495
5661
|
try {
|
|
5496
|
-
const req =
|
|
5662
|
+
const req = createRequire3(join18(projectCwd, "package.json"));
|
|
5497
5663
|
const mod = req("eslint");
|
|
5498
5664
|
const ESLintCtor = mod?.ESLint ?? mod?.default?.ESLint ?? mod?.default ?? mod;
|
|
5499
5665
|
if (!ESLintCtor) return null;
|
|
@@ -5506,7 +5672,7 @@ async function getESLintForProject(projectCwd) {
|
|
|
5506
5672
|
}
|
|
5507
5673
|
async function lintFile(filePath, onProgress) {
|
|
5508
5674
|
const absolutePath = resolveRequestedFilePath(filePath);
|
|
5509
|
-
if (!
|
|
5675
|
+
if (!existsSync18(absolutePath)) {
|
|
5510
5676
|
onProgress(`File not found: ${pc.dim(absolutePath)}`);
|
|
5511
5677
|
return [];
|
|
5512
5678
|
}
|
|
@@ -5522,7 +5688,7 @@ async function lintFile(filePath, onProgress) {
|
|
|
5522
5688
|
onProgress("Cache hit (unchanged)");
|
|
5523
5689
|
return cached.issues;
|
|
5524
5690
|
}
|
|
5525
|
-
const fileDir =
|
|
5691
|
+
const fileDir = dirname11(absolutePath);
|
|
5526
5692
|
const projectCwd = findESLintCwd(fileDir);
|
|
5527
5693
|
onProgress(`Resolving ESLint project... ${pc.dim(projectCwd)}`);
|
|
5528
5694
|
const eslint = await getESLintForProject(projectCwd);
|
|
@@ -5545,7 +5711,7 @@ async function lintFile(filePath, onProgress) {
|
|
|
5545
5711
|
let codeLength = 0;
|
|
5546
5712
|
try {
|
|
5547
5713
|
onProgress("Building JSX map...");
|
|
5548
|
-
const code =
|
|
5714
|
+
const code = readFileSync12(absolutePath, "utf-8");
|
|
5549
5715
|
codeLength = code.length;
|
|
5550
5716
|
lineStarts = buildLineStarts(code);
|
|
5551
5717
|
spans = buildJsxElementSpans(code, dataLocFile);
|
|
@@ -5627,9 +5793,9 @@ async function handleMessage(ws, data) {
|
|
|
5627
5793
|
});
|
|
5628
5794
|
const startedAt = Date.now();
|
|
5629
5795
|
const resolved = resolveRequestedFilePath(filePath);
|
|
5630
|
-
if (!
|
|
5796
|
+
if (!existsSync18(resolved)) {
|
|
5631
5797
|
const cwd = process.cwd();
|
|
5632
|
-
const wsRoot =
|
|
5798
|
+
const wsRoot = findWorkspaceRoot6(cwd);
|
|
5633
5799
|
logWarning(
|
|
5634
5800
|
[
|
|
5635
5801
|
`${pc.dim("[ws]")} File not found for request`,
|
|
@@ -5789,14 +5955,14 @@ async function handleMessage(ws, data) {
|
|
|
5789
5955
|
)}`
|
|
5790
5956
|
);
|
|
5791
5957
|
} else {
|
|
5792
|
-
const screenshotsDir =
|
|
5958
|
+
const screenshotsDir = join18(
|
|
5793
5959
|
serverAppRootForVision,
|
|
5794
5960
|
".uilint",
|
|
5795
5961
|
"screenshots"
|
|
5796
5962
|
);
|
|
5797
|
-
const imagePath =
|
|
5963
|
+
const imagePath = join18(screenshotsDir, screenshotFile);
|
|
5798
5964
|
try {
|
|
5799
|
-
if (!
|
|
5965
|
+
if (!existsSync18(imagePath)) {
|
|
5800
5966
|
logWarning(
|
|
5801
5967
|
`Skipping vision report write: screenshot file not found ${pc.dim(
|
|
5802
5968
|
imagePath
|
|
@@ -5904,7 +6070,7 @@ function handleFileChange(filePath) {
|
|
|
5904
6070
|
async function serve(options) {
|
|
5905
6071
|
const port = options.port || 9234;
|
|
5906
6072
|
const cwd = process.cwd();
|
|
5907
|
-
const wsRoot =
|
|
6073
|
+
const wsRoot = findWorkspaceRoot6(cwd);
|
|
5908
6074
|
const appRoot = pickAppRoot({ cwd, workspaceRoot: wsRoot });
|
|
5909
6075
|
serverAppRootForVision = appRoot;
|
|
5910
6076
|
logInfo(`Workspace root: ${pc.dim(wsRoot)}`);
|
|
@@ -5957,10 +6123,10 @@ async function serve(options) {
|
|
|
5957
6123
|
}
|
|
5958
6124
|
|
|
5959
6125
|
// src/commands/vision.ts
|
|
5960
|
-
import { dirname as
|
|
6126
|
+
import { dirname as dirname12, resolve as resolve6, join as join19 } from "path";
|
|
5961
6127
|
import {
|
|
5962
|
-
existsSync as
|
|
5963
|
-
readFileSync as
|
|
6128
|
+
existsSync as existsSync19,
|
|
6129
|
+
readFileSync as readFileSync13,
|
|
5964
6130
|
readdirSync as readdirSync6
|
|
5965
6131
|
} from "fs";
|
|
5966
6132
|
import {
|
|
@@ -6006,17 +6172,17 @@ function debugLog3(enabled, message, obj) {
|
|
|
6006
6172
|
function findScreenshotsDirUpwards(startDir) {
|
|
6007
6173
|
let dir = startDir;
|
|
6008
6174
|
for (let i = 0; i < 20; i++) {
|
|
6009
|
-
const candidate =
|
|
6010
|
-
if (
|
|
6011
|
-
const parent =
|
|
6175
|
+
const candidate = join19(dir, ".uilint", "screenshots");
|
|
6176
|
+
if (existsSync19(candidate)) return candidate;
|
|
6177
|
+
const parent = dirname12(dir);
|
|
6012
6178
|
if (parent === dir) break;
|
|
6013
6179
|
dir = parent;
|
|
6014
6180
|
}
|
|
6015
6181
|
return null;
|
|
6016
6182
|
}
|
|
6017
6183
|
function listScreenshotSidecars(dirPath) {
|
|
6018
|
-
if (!
|
|
6019
|
-
const entries = readdirSync6(dirPath).filter((f) => f.endsWith(".json")).map((f) =>
|
|
6184
|
+
if (!existsSync19(dirPath)) return [];
|
|
6185
|
+
const entries = readdirSync6(dirPath).filter((f) => f.endsWith(".json")).map((f) => join19(dirPath, f));
|
|
6020
6186
|
const out = [];
|
|
6021
6187
|
for (const p2 of entries) {
|
|
6022
6188
|
try {
|
|
@@ -6045,11 +6211,11 @@ function listScreenshotSidecars(dirPath) {
|
|
|
6045
6211
|
return out;
|
|
6046
6212
|
}
|
|
6047
6213
|
function readImageAsBase64(imagePath) {
|
|
6048
|
-
const bytes =
|
|
6214
|
+
const bytes = readFileSync13(imagePath);
|
|
6049
6215
|
return { base64: bytes.toString("base64"), sizeBytes: bytes.byteLength };
|
|
6050
6216
|
}
|
|
6051
6217
|
function loadJsonFile(filePath) {
|
|
6052
|
-
const raw =
|
|
6218
|
+
const raw = readFileSync13(filePath, "utf-8");
|
|
6053
6219
|
return JSON.parse(raw);
|
|
6054
6220
|
}
|
|
6055
6221
|
function formatIssuesText(issues) {
|
|
@@ -6123,13 +6289,13 @@ async function vision(options) {
|
|
|
6123
6289
|
await flushLangfuse();
|
|
6124
6290
|
process.exit(1);
|
|
6125
6291
|
}
|
|
6126
|
-
if (imagePath && !
|
|
6292
|
+
if (imagePath && !existsSync19(imagePath)) {
|
|
6127
6293
|
throw new Error(`Image not found: ${imagePath}`);
|
|
6128
6294
|
}
|
|
6129
|
-
if (sidecarPath && !
|
|
6295
|
+
if (sidecarPath && !existsSync19(sidecarPath)) {
|
|
6130
6296
|
throw new Error(`Sidecar not found: ${sidecarPath}`);
|
|
6131
6297
|
}
|
|
6132
|
-
if (manifestFilePath && !
|
|
6298
|
+
if (manifestFilePath && !existsSync19(manifestFilePath)) {
|
|
6133
6299
|
throw new Error(`Manifest file not found: ${manifestFilePath}`);
|
|
6134
6300
|
}
|
|
6135
6301
|
const sidecar = sidecarPath ? loadJsonFile(sidecarPath) : null;
|
|
@@ -6154,7 +6320,7 @@ async function vision(options) {
|
|
|
6154
6320
|
const resolved = await resolveVisionStyleGuide({
|
|
6155
6321
|
projectPath,
|
|
6156
6322
|
styleguide: options.styleguide,
|
|
6157
|
-
startDir: startPath ?
|
|
6323
|
+
startDir: startPath ? dirname12(startPath) : projectPath
|
|
6158
6324
|
});
|
|
6159
6325
|
styleGuide = resolved.styleGuide;
|
|
6160
6326
|
styleguideLocation = resolved.styleguideLocation;
|
|
@@ -6203,7 +6369,7 @@ async function vision(options) {
|
|
|
6203
6369
|
const resolvedImagePath = imagePath || (() => {
|
|
6204
6370
|
const screenshotFile = typeof sidecar?.screenshotFile === "string" ? sidecar.screenshotFile : typeof sidecar?.filename === "string" ? sidecar.filename : void 0;
|
|
6205
6371
|
if (!screenshotFile) return null;
|
|
6206
|
-
const baseDir = sidecarPath ?
|
|
6372
|
+
const baseDir = sidecarPath ? dirname12(sidecarPath) : projectPath;
|
|
6207
6373
|
const abs = resolve6(baseDir, screenshotFile);
|
|
6208
6374
|
return abs;
|
|
6209
6375
|
})();
|
|
@@ -6212,7 +6378,7 @@ async function vision(options) {
|
|
|
6212
6378
|
"No image path could be resolved. Provide --image or a sidecar with `screenshotFile`/`filename`."
|
|
6213
6379
|
);
|
|
6214
6380
|
}
|
|
6215
|
-
if (!
|
|
6381
|
+
if (!existsSync19(resolvedImagePath)) {
|
|
6216
6382
|
throw new Error(`Image not found: ${resolvedImagePath}`);
|
|
6217
6383
|
}
|
|
6218
6384
|
const { base64, sizeBytes } = readImageAsBase64(resolvedImagePath);
|
|
@@ -6440,8 +6606,8 @@ async function vision(options) {
|
|
|
6440
6606
|
}
|
|
6441
6607
|
|
|
6442
6608
|
// src/commands/session.ts
|
|
6443
|
-
import { existsSync as
|
|
6444
|
-
import { basename, dirname as
|
|
6609
|
+
import { existsSync as existsSync20, readFileSync as readFileSync14, writeFileSync as writeFileSync10, unlinkSync as unlinkSync2 } from "fs";
|
|
6610
|
+
import { basename, dirname as dirname13, resolve as resolve7 } from "path";
|
|
6445
6611
|
import { createStyleSummary as createStyleSummary3 } from "uilint-core";
|
|
6446
6612
|
import {
|
|
6447
6613
|
ensureOllamaReady as ensureOllamaReady7,
|
|
@@ -6452,11 +6618,11 @@ import {
|
|
|
6452
6618
|
var SESSION_FILE = "/tmp/uilint-session.json";
|
|
6453
6619
|
var UI_FILE_EXTENSIONS = [".tsx", ".jsx", ".css", ".scss", ".module.css"];
|
|
6454
6620
|
function readSession() {
|
|
6455
|
-
if (!
|
|
6621
|
+
if (!existsSync20(SESSION_FILE)) {
|
|
6456
6622
|
return { files: [], startedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
6457
6623
|
}
|
|
6458
6624
|
try {
|
|
6459
|
-
const content =
|
|
6625
|
+
const content = readFileSync14(SESSION_FILE, "utf-8");
|
|
6460
6626
|
return JSON.parse(content);
|
|
6461
6627
|
} catch {
|
|
6462
6628
|
return { files: [], startedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
@@ -6474,7 +6640,7 @@ function isScannableMarkupFile(filePath) {
|
|
|
6474
6640
|
);
|
|
6475
6641
|
}
|
|
6476
6642
|
async function sessionClear() {
|
|
6477
|
-
if (
|
|
6643
|
+
if (existsSync20(SESSION_FILE)) {
|
|
6478
6644
|
unlinkSync2(SESSION_FILE);
|
|
6479
6645
|
}
|
|
6480
6646
|
console.log(JSON.stringify({ cleared: true }));
|
|
@@ -6545,13 +6711,13 @@ async function sessionScan(options = {}) {
|
|
|
6545
6711
|
const client = await createLLMClient({});
|
|
6546
6712
|
const results = [];
|
|
6547
6713
|
for (const filePath of session.files) {
|
|
6548
|
-
if (!
|
|
6714
|
+
if (!existsSync20(filePath)) continue;
|
|
6549
6715
|
if (!isScannableMarkupFile(filePath)) continue;
|
|
6550
6716
|
try {
|
|
6551
6717
|
const absolutePath = resolve7(process.cwd(), filePath);
|
|
6552
|
-
const htmlLike =
|
|
6718
|
+
const htmlLike = readFileSync14(filePath, "utf-8");
|
|
6553
6719
|
const snapshot = parseCLIInput2(htmlLike);
|
|
6554
|
-
const tailwindSearchDir =
|
|
6720
|
+
const tailwindSearchDir = dirname13(absolutePath);
|
|
6555
6721
|
const tailwindTheme = readTailwindThemeTokens3(tailwindSearchDir);
|
|
6556
6722
|
const styleSummary = createStyleSummary3(snapshot.styles, {
|
|
6557
6723
|
html: snapshot.html,
|
|
@@ -6604,7 +6770,7 @@ async function sessionScan(options = {}) {
|
|
|
6604
6770
|
};
|
|
6605
6771
|
console.log(JSON.stringify(result));
|
|
6606
6772
|
}
|
|
6607
|
-
if (
|
|
6773
|
+
if (existsSync20(SESSION_FILE)) {
|
|
6608
6774
|
unlinkSync2(SESSION_FILE);
|
|
6609
6775
|
}
|
|
6610
6776
|
await flushLangfuse();
|
|
@@ -6615,9 +6781,9 @@ async function sessionList() {
|
|
|
6615
6781
|
}
|
|
6616
6782
|
|
|
6617
6783
|
// src/index.ts
|
|
6618
|
-
import { readFileSync as
|
|
6619
|
-
import { dirname as
|
|
6620
|
-
import { fileURLToPath as
|
|
6784
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
6785
|
+
import { dirname as dirname14, join as join20 } from "path";
|
|
6786
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
6621
6787
|
function assertNodeVersion(minMajor) {
|
|
6622
6788
|
const ver = process.versions.node || "";
|
|
6623
6789
|
const majorStr = ver.split(".")[0] || "";
|
|
@@ -6633,9 +6799,9 @@ assertNodeVersion(20);
|
|
|
6633
6799
|
var program = new Command();
|
|
6634
6800
|
function getCLIVersion2() {
|
|
6635
6801
|
try {
|
|
6636
|
-
const
|
|
6637
|
-
const pkgPath =
|
|
6638
|
-
const pkg = JSON.parse(
|
|
6802
|
+
const __dirname3 = dirname14(fileURLToPath4(import.meta.url));
|
|
6803
|
+
const pkgPath = join20(__dirname3, "..", "package.json");
|
|
6804
|
+
const pkg = JSON.parse(readFileSync15(pkgPath, "utf-8"));
|
|
6639
6805
|
return pkg.version || "0.0.0";
|
|
6640
6806
|
} catch {
|
|
6641
6807
|
return "0.0.0";
|