uilint 0.2.3 → 0.2.4
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 +467 -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,130 @@ 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 getUilintEslintPackageRoot() {
|
|
2927
|
+
const entry = require2.resolve("uilint-eslint");
|
|
2928
|
+
const entryDir = dirname8(entry);
|
|
2929
|
+
return dirname8(entryDir);
|
|
2930
|
+
}
|
|
2931
|
+
function getUilintEslintSrcDir() {
|
|
2932
|
+
const devPath = join10(
|
|
2933
|
+
__dirname2,
|
|
2934
|
+
"..",
|
|
2935
|
+
"..",
|
|
2936
|
+
"..",
|
|
2937
|
+
"..",
|
|
2938
|
+
"uilint-eslint",
|
|
2939
|
+
"src"
|
|
2940
|
+
);
|
|
2941
|
+
if (existsSync11(devPath)) return devPath;
|
|
2942
|
+
const pkgRoot = getUilintEslintPackageRoot();
|
|
2943
|
+
const srcPath = join10(pkgRoot, "src");
|
|
2944
|
+
if (existsSync11(srcPath)) return srcPath;
|
|
2945
|
+
throw new Error(
|
|
2946
|
+
'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.'
|
|
2947
|
+
);
|
|
2948
|
+
}
|
|
2949
|
+
function getUilintEslintDistDir() {
|
|
2950
|
+
const devPath = join10(
|
|
2951
|
+
__dirname2,
|
|
2952
|
+
"..",
|
|
2953
|
+
"..",
|
|
2954
|
+
"..",
|
|
2955
|
+
"..",
|
|
2956
|
+
"uilint-eslint",
|
|
2957
|
+
"dist"
|
|
2958
|
+
);
|
|
2959
|
+
if (existsSync11(devPath)) return devPath;
|
|
2960
|
+
const pkgRoot = getUilintEslintPackageRoot();
|
|
2961
|
+
const distPath = join10(pkgRoot, "dist");
|
|
2962
|
+
if (existsSync11(distPath)) return distPath;
|
|
2963
|
+
throw new Error(
|
|
2964
|
+
'Could not find uilint-eslint "dist/" directory. This is a bug in uilint installation.'
|
|
2965
|
+
);
|
|
2966
|
+
}
|
|
2967
|
+
function transformRuleContent(content) {
|
|
2968
|
+
let transformed = content;
|
|
2969
|
+
transformed = transformed.replace(
|
|
2970
|
+
/import\s+{\s*createRule\s*}\s+from\s+["']\.\.\/utils\/create-rule\.js["'];?/g,
|
|
2971
|
+
'import { createRule } from "uilint-eslint";'
|
|
2972
|
+
);
|
|
2973
|
+
transformed = transformed.replace(
|
|
2974
|
+
/import\s+createRule\s+from\s+["']\.\.\/utils\/create-rule\.js["'];?/g,
|
|
2975
|
+
'import { createRule } from "uilint-eslint";'
|
|
2976
|
+
);
|
|
2977
|
+
transformed = transformed.replace(
|
|
2978
|
+
/import\s+{([^}]+)}\s+from\s+["']\.\.\/utils\/([^"']+)\.js["'];?/g,
|
|
2979
|
+
(match, imports, utilFile) => {
|
|
2980
|
+
const utilsFromPackage = ["cache", "styleguide-loader", "import-graph"];
|
|
2981
|
+
if (utilsFromPackage.includes(utilFile)) {
|
|
2982
|
+
return `import {${imports}} from "uilint-eslint";`;
|
|
2983
|
+
}
|
|
2984
|
+
return match;
|
|
2985
|
+
}
|
|
2986
|
+
);
|
|
2987
|
+
return transformed;
|
|
2988
|
+
}
|
|
2989
|
+
function loadRule(ruleId, options = { typescript: true }) {
|
|
2990
|
+
const { typescript } = options;
|
|
2991
|
+
const extension = typescript ? ".ts" : ".js";
|
|
2992
|
+
if (typescript) {
|
|
2993
|
+
const rulesDir = join10(getUilintEslintSrcDir(), "rules");
|
|
2994
|
+
const implPath = join10(rulesDir, `${ruleId}.ts`);
|
|
2995
|
+
const testPath = join10(rulesDir, `${ruleId}.test.ts`);
|
|
2996
|
+
if (!existsSync11(implPath)) {
|
|
2997
|
+
throw new Error(`Rule "${ruleId}" not found at ${implPath}`);
|
|
2998
|
+
}
|
|
2999
|
+
const rawContent = readFileSync7(implPath, "utf-8");
|
|
3000
|
+
const transformedContent = transformRuleContent(rawContent);
|
|
3001
|
+
const implementation = {
|
|
3002
|
+
relativePath: `${ruleId}.ts`,
|
|
3003
|
+
content: transformedContent
|
|
3004
|
+
};
|
|
3005
|
+
const test = existsSync11(testPath) ? {
|
|
3006
|
+
relativePath: `${ruleId}.test.ts`,
|
|
3007
|
+
content: transformRuleContent(readFileSync7(testPath, "utf-8"))
|
|
3008
|
+
} : void 0;
|
|
3009
|
+
return {
|
|
3010
|
+
ruleId,
|
|
3011
|
+
implementation,
|
|
3012
|
+
test
|
|
3013
|
+
};
|
|
3014
|
+
} else {
|
|
3015
|
+
const rulesDir = join10(getUilintEslintDistDir(), "rules");
|
|
3016
|
+
const implPath = join10(rulesDir, `${ruleId}.js`);
|
|
3017
|
+
if (!existsSync11(implPath)) {
|
|
3018
|
+
throw new Error(
|
|
3019
|
+
`Rule "${ruleId}" not found at ${implPath}. Make sure uilint-eslint has been built.`
|
|
3020
|
+
);
|
|
3021
|
+
}
|
|
3022
|
+
const content = readFileSync7(implPath, "utf-8");
|
|
3023
|
+
const implementation = {
|
|
3024
|
+
relativePath: `${ruleId}.js`,
|
|
3025
|
+
content
|
|
3026
|
+
};
|
|
3027
|
+
return {
|
|
3028
|
+
ruleId,
|
|
3029
|
+
implementation
|
|
3030
|
+
};
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
3033
|
+
function loadSelectedRules(ruleIds, options = { typescript: true }) {
|
|
3034
|
+
return ruleIds.map((id) => loadRule(id, options));
|
|
3035
|
+
}
|
|
3036
|
+
|
|
3037
|
+
// src/commands/install/plan.ts
|
|
3038
|
+
var require3 = createRequire2(import.meta.url);
|
|
2926
3039
|
function getSelfDependencyVersionRange(pkgName) {
|
|
2927
3040
|
try {
|
|
2928
|
-
const pkgJson =
|
|
3041
|
+
const pkgJson = require3("uilint/package.json");
|
|
2929
3042
|
const deps = pkgJson?.dependencies;
|
|
2930
3043
|
const optDeps = pkgJson?.optionalDependencies;
|
|
2931
3044
|
const peerDeps = pkgJson?.peerDependencies;
|
|
@@ -3000,7 +3113,7 @@ function createPlan(state, choices, options = {}) {
|
|
|
3000
3113
|
}
|
|
3001
3114
|
}
|
|
3002
3115
|
if (items.includes("hooks")) {
|
|
3003
|
-
const hooksDir =
|
|
3116
|
+
const hooksDir = join11(state.cursorDir.path, "hooks");
|
|
3004
3117
|
actions.push({
|
|
3005
3118
|
type: "create_directory",
|
|
3006
3119
|
path: hooksDir
|
|
@@ -3026,63 +3139,66 @@ function createPlan(state, choices, options = {}) {
|
|
|
3026
3139
|
});
|
|
3027
3140
|
actions.push({
|
|
3028
3141
|
type: "create_file",
|
|
3029
|
-
path:
|
|
3142
|
+
path: join11(hooksDir, "uilint-session-start.sh"),
|
|
3030
3143
|
content: SESSION_START_SCRIPT,
|
|
3031
3144
|
permissions: 493
|
|
3032
3145
|
});
|
|
3033
3146
|
actions.push({
|
|
3034
3147
|
type: "create_file",
|
|
3035
|
-
path:
|
|
3148
|
+
path: join11(hooksDir, "uilint-track.sh"),
|
|
3036
3149
|
content: TRACK_SCRIPT,
|
|
3037
3150
|
permissions: 493
|
|
3038
3151
|
});
|
|
3039
3152
|
actions.push({
|
|
3040
3153
|
type: "create_file",
|
|
3041
|
-
path:
|
|
3154
|
+
path: join11(hooksDir, "uilint-session-end.sh"),
|
|
3042
3155
|
content: SESSION_END_SCRIPT,
|
|
3043
3156
|
permissions: 493
|
|
3044
3157
|
});
|
|
3045
3158
|
}
|
|
3046
3159
|
if (items.includes("genstyleguide")) {
|
|
3047
|
-
const commandsDir =
|
|
3160
|
+
const commandsDir = join11(state.cursorDir.path, "commands");
|
|
3048
3161
|
actions.push({
|
|
3049
3162
|
type: "create_directory",
|
|
3050
3163
|
path: commandsDir
|
|
3051
3164
|
});
|
|
3052
3165
|
actions.push({
|
|
3053
3166
|
type: "create_file",
|
|
3054
|
-
path:
|
|
3167
|
+
path: join11(commandsDir, "genstyleguide.md"),
|
|
3055
3168
|
content: GENSTYLEGUIDE_COMMAND_MD
|
|
3056
3169
|
});
|
|
3057
3170
|
}
|
|
3058
3171
|
if (items.includes("genrules")) {
|
|
3059
|
-
const commandsDir =
|
|
3172
|
+
const commandsDir = join11(state.cursorDir.path, "commands");
|
|
3060
3173
|
actions.push({
|
|
3061
3174
|
type: "create_directory",
|
|
3062
3175
|
path: commandsDir
|
|
3063
3176
|
});
|
|
3064
3177
|
actions.push({
|
|
3065
3178
|
type: "create_file",
|
|
3066
|
-
path:
|
|
3179
|
+
path: join11(commandsDir, "genrules.md"),
|
|
3067
3180
|
content: GENRULES_COMMAND_MD
|
|
3068
3181
|
});
|
|
3069
3182
|
}
|
|
3070
3183
|
if (items.includes("skill")) {
|
|
3071
|
-
const skillsDir =
|
|
3184
|
+
const skillsDir = join11(state.cursorDir.path, "skills");
|
|
3072
3185
|
actions.push({
|
|
3073
3186
|
type: "create_directory",
|
|
3074
3187
|
path: skillsDir
|
|
3075
3188
|
});
|
|
3076
3189
|
try {
|
|
3077
3190
|
const skill = loadSkill("ui-consistency-enforcer");
|
|
3078
|
-
const skillDir =
|
|
3191
|
+
const skillDir = join11(skillsDir, skill.name);
|
|
3079
3192
|
actions.push({
|
|
3080
3193
|
type: "create_directory",
|
|
3081
3194
|
path: skillDir
|
|
3082
3195
|
});
|
|
3083
3196
|
for (const file of skill.files) {
|
|
3084
|
-
const filePath =
|
|
3085
|
-
const fileDir =
|
|
3197
|
+
const filePath = join11(skillDir, file.relativePath);
|
|
3198
|
+
const fileDir = join11(
|
|
3199
|
+
skillDir,
|
|
3200
|
+
file.relativePath.split("/").slice(0, -1).join("/")
|
|
3201
|
+
);
|
|
3086
3202
|
if (fileDir !== skillDir && file.relativePath.includes("/")) {
|
|
3087
3203
|
actions.push({
|
|
3088
3204
|
type: "create_directory",
|
|
@@ -3142,6 +3258,35 @@ function createPlan(state, choices, options = {}) {
|
|
|
3142
3258
|
const { packagePaths, selectedRules } = choices.eslint;
|
|
3143
3259
|
for (const pkgPath of packagePaths) {
|
|
3144
3260
|
const pkgInfo = state.packages.find((p2) => p2.path === pkgPath);
|
|
3261
|
+
const rulesDir = join11(pkgPath, ".uilint", "rules");
|
|
3262
|
+
actions.push({
|
|
3263
|
+
type: "create_directory",
|
|
3264
|
+
path: rulesDir
|
|
3265
|
+
});
|
|
3266
|
+
const isTypeScript = pkgInfo?.isTypeScript ?? true;
|
|
3267
|
+
try {
|
|
3268
|
+
const ruleFiles = loadSelectedRules(
|
|
3269
|
+
selectedRules.map((r) => r.id),
|
|
3270
|
+
{
|
|
3271
|
+
typescript: isTypeScript
|
|
3272
|
+
}
|
|
3273
|
+
);
|
|
3274
|
+
for (const ruleFile of ruleFiles) {
|
|
3275
|
+
actions.push({
|
|
3276
|
+
type: "create_file",
|
|
3277
|
+
path: join11(rulesDir, ruleFile.implementation.relativePath),
|
|
3278
|
+
content: ruleFile.implementation.content
|
|
3279
|
+
});
|
|
3280
|
+
if (ruleFile.test && isTypeScript) {
|
|
3281
|
+
actions.push({
|
|
3282
|
+
type: "create_file",
|
|
3283
|
+
path: join11(rulesDir, ruleFile.test.relativePath),
|
|
3284
|
+
content: ruleFile.test.content
|
|
3285
|
+
});
|
|
3286
|
+
}
|
|
3287
|
+
}
|
|
3288
|
+
} catch {
|
|
3289
|
+
}
|
|
3145
3290
|
dependencies.push({
|
|
3146
3291
|
packagePath: pkgPath,
|
|
3147
3292
|
packageManager: state.packageManager,
|
|
@@ -3157,7 +3302,7 @@ function createPlan(state, choices, options = {}) {
|
|
|
3157
3302
|
});
|
|
3158
3303
|
}
|
|
3159
3304
|
}
|
|
3160
|
-
const gitignorePath =
|
|
3305
|
+
const gitignorePath = join11(state.workspaceRoot, ".gitignore");
|
|
3161
3306
|
actions.push({
|
|
3162
3307
|
type: "append_to_file",
|
|
3163
3308
|
path: gitignorePath,
|
|
@@ -3170,49 +3315,49 @@ function createPlan(state, choices, options = {}) {
|
|
|
3170
3315
|
|
|
3171
3316
|
// src/commands/install/execute.ts
|
|
3172
3317
|
import {
|
|
3173
|
-
existsSync as
|
|
3318
|
+
existsSync as existsSync16,
|
|
3174
3319
|
mkdirSync as mkdirSync3,
|
|
3175
3320
|
writeFileSync as writeFileSync7,
|
|
3176
|
-
readFileSync as
|
|
3321
|
+
readFileSync as readFileSync11,
|
|
3177
3322
|
unlinkSync,
|
|
3178
3323
|
chmodSync
|
|
3179
3324
|
} from "fs";
|
|
3180
|
-
import { dirname as
|
|
3325
|
+
import { dirname as dirname9 } from "path";
|
|
3181
3326
|
|
|
3182
3327
|
// src/utils/react-inject.ts
|
|
3183
|
-
import { existsSync as
|
|
3184
|
-
import { join as
|
|
3328
|
+
import { existsSync as existsSync12, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
|
|
3329
|
+
import { join as join12 } from "path";
|
|
3185
3330
|
import { parseModule as parseModule2, generateCode as generateCode2 } from "magicast";
|
|
3186
3331
|
function getDefaultCandidates(projectPath, appRoot) {
|
|
3187
3332
|
const viteMainCandidates = [
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3333
|
+
join12(appRoot, "main.tsx"),
|
|
3334
|
+
join12(appRoot, "main.jsx"),
|
|
3335
|
+
join12(appRoot, "main.ts"),
|
|
3336
|
+
join12(appRoot, "main.js")
|
|
3192
3337
|
];
|
|
3193
3338
|
const existingViteMain = viteMainCandidates.filter(
|
|
3194
|
-
(rel) =>
|
|
3339
|
+
(rel) => existsSync12(join12(projectPath, rel))
|
|
3195
3340
|
);
|
|
3196
3341
|
if (existingViteMain.length > 0) return existingViteMain;
|
|
3197
|
-
const viteAppCandidates = [
|
|
3342
|
+
const viteAppCandidates = [join12(appRoot, "App.tsx"), join12(appRoot, "App.jsx")];
|
|
3198
3343
|
const existingViteApp = viteAppCandidates.filter(
|
|
3199
|
-
(rel) =>
|
|
3344
|
+
(rel) => existsSync12(join12(projectPath, rel))
|
|
3200
3345
|
);
|
|
3201
3346
|
if (existingViteApp.length > 0) return existingViteApp;
|
|
3202
3347
|
const layoutCandidates = [
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3348
|
+
join12(appRoot, "layout.tsx"),
|
|
3349
|
+
join12(appRoot, "layout.jsx"),
|
|
3350
|
+
join12(appRoot, "layout.ts"),
|
|
3351
|
+
join12(appRoot, "layout.js")
|
|
3207
3352
|
];
|
|
3208
3353
|
const existingLayouts = layoutCandidates.filter(
|
|
3209
|
-
(rel) =>
|
|
3354
|
+
(rel) => existsSync12(join12(projectPath, rel))
|
|
3210
3355
|
);
|
|
3211
3356
|
if (existingLayouts.length > 0) {
|
|
3212
3357
|
return existingLayouts;
|
|
3213
3358
|
}
|
|
3214
|
-
const pageCandidates = [
|
|
3215
|
-
return pageCandidates.filter((rel) =>
|
|
3359
|
+
const pageCandidates = [join12(appRoot, "page.tsx"), join12(appRoot, "page.jsx")];
|
|
3360
|
+
return pageCandidates.filter((rel) => existsSync12(join12(projectPath, rel)));
|
|
3216
3361
|
}
|
|
3217
3362
|
function isUseClientDirective(stmt) {
|
|
3218
3363
|
return stmt?.type === "ExpressionStatement" && stmt.expression?.type === "StringLiteral" && stmt.expression.value === "use client";
|
|
@@ -3225,16 +3370,16 @@ function findImportDeclaration(program2, from) {
|
|
|
3225
3370
|
}
|
|
3226
3371
|
return null;
|
|
3227
3372
|
}
|
|
3228
|
-
function
|
|
3373
|
+
function walkAst(node, visit) {
|
|
3229
3374
|
if (!node || typeof node !== "object") return;
|
|
3230
3375
|
if (node.type) visit(node);
|
|
3231
3376
|
for (const key of Object.keys(node)) {
|
|
3232
3377
|
const v = node[key];
|
|
3233
3378
|
if (!v) continue;
|
|
3234
3379
|
if (Array.isArray(v)) {
|
|
3235
|
-
for (const item of v)
|
|
3380
|
+
for (const item of v) walkAst(item, visit);
|
|
3236
3381
|
} else if (typeof v === "object" && v.type) {
|
|
3237
|
-
|
|
3382
|
+
walkAst(v, visit);
|
|
3238
3383
|
}
|
|
3239
3384
|
}
|
|
3240
3385
|
}
|
|
@@ -3266,7 +3411,7 @@ function ensureNamedImport(program2, from, name) {
|
|
|
3266
3411
|
}
|
|
3267
3412
|
function hasUILintProviderJsx(program2) {
|
|
3268
3413
|
let found = false;
|
|
3269
|
-
|
|
3414
|
+
walkAst(program2, (node) => {
|
|
3270
3415
|
if (found) return;
|
|
3271
3416
|
if (node.type !== "JSXElement") return;
|
|
3272
3417
|
const name = node.openingElement?.name;
|
|
@@ -3286,7 +3431,7 @@ function wrapFirstChildrenExpressionWithProvider(program2) {
|
|
|
3286
3431
|
if (!providerJsx || providerJsx.type !== "JSXElement")
|
|
3287
3432
|
return { changed: false };
|
|
3288
3433
|
let replaced = false;
|
|
3289
|
-
|
|
3434
|
+
walkAst(program2, (node) => {
|
|
3290
3435
|
if (replaced) return;
|
|
3291
3436
|
if (node.type === "JSXExpressionContainer" && node.expression?.type === "Identifier" && node.expression.name === "children") {
|
|
3292
3437
|
Object.keys(node).forEach((k) => delete node[k]);
|
|
@@ -3310,7 +3455,7 @@ function wrapFirstRenderCallArgumentWithProvider(program2) {
|
|
|
3310
3455
|
return { changed: false };
|
|
3311
3456
|
providerJsx.children = providerJsx.children ?? [];
|
|
3312
3457
|
let wrapped = false;
|
|
3313
|
-
|
|
3458
|
+
walkAst(program2, (node) => {
|
|
3314
3459
|
if (wrapped) return;
|
|
3315
3460
|
if (node.type !== "CallExpression") return;
|
|
3316
3461
|
const callee = node.callee;
|
|
@@ -3345,8 +3490,8 @@ async function installReactUILintOverlay(opts) {
|
|
|
3345
3490
|
} else {
|
|
3346
3491
|
chosen = candidates[0];
|
|
3347
3492
|
}
|
|
3348
|
-
const absTarget =
|
|
3349
|
-
const original =
|
|
3493
|
+
const absTarget = join12(opts.projectPath, chosen);
|
|
3494
|
+
const original = readFileSync8(absTarget, "utf-8");
|
|
3350
3495
|
let mod;
|
|
3351
3496
|
try {
|
|
3352
3497
|
mod = parseModule2(original);
|
|
@@ -3380,14 +3525,14 @@ async function installReactUILintOverlay(opts) {
|
|
|
3380
3525
|
}
|
|
3381
3526
|
|
|
3382
3527
|
// src/utils/next-config-inject.ts
|
|
3383
|
-
import { existsSync as
|
|
3384
|
-
import { join as
|
|
3528
|
+
import { existsSync as existsSync13, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "fs";
|
|
3529
|
+
import { join as join13 } from "path";
|
|
3385
3530
|
import { parseModule as parseModule3, generateCode as generateCode3 } from "magicast";
|
|
3386
3531
|
var CONFIG_EXTENSIONS2 = [".ts", ".mjs", ".js", ".cjs"];
|
|
3387
3532
|
function findNextConfigFile(projectPath) {
|
|
3388
3533
|
for (const ext of CONFIG_EXTENSIONS2) {
|
|
3389
|
-
const configPath =
|
|
3390
|
-
if (
|
|
3534
|
+
const configPath = join13(projectPath, `next.config${ext}`);
|
|
3535
|
+
if (existsSync13(configPath)) {
|
|
3391
3536
|
return configPath;
|
|
3392
3537
|
}
|
|
3393
3538
|
}
|
|
@@ -3500,7 +3645,7 @@ async function installJsxLocPlugin(opts) {
|
|
|
3500
3645
|
return { configFile: null, modified: false };
|
|
3501
3646
|
}
|
|
3502
3647
|
const configFilename = getNextConfigFilename(configPath);
|
|
3503
|
-
const original =
|
|
3648
|
+
const original = readFileSync9(configPath, "utf-8");
|
|
3504
3649
|
let mod;
|
|
3505
3650
|
try {
|
|
3506
3651
|
mod = parseModule3(original);
|
|
@@ -3530,14 +3675,14 @@ async function installJsxLocPlugin(opts) {
|
|
|
3530
3675
|
}
|
|
3531
3676
|
|
|
3532
3677
|
// src/utils/vite-config-inject.ts
|
|
3533
|
-
import { existsSync as
|
|
3534
|
-
import { join as
|
|
3678
|
+
import { existsSync as existsSync14, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "fs";
|
|
3679
|
+
import { join as join14 } from "path";
|
|
3535
3680
|
import { parseModule as parseModule4, generateCode as generateCode4 } from "magicast";
|
|
3536
3681
|
var CONFIG_EXTENSIONS3 = [".ts", ".mjs", ".js", ".cjs"];
|
|
3537
3682
|
function findViteConfigFile2(projectPath) {
|
|
3538
3683
|
for (const ext of CONFIG_EXTENSIONS3) {
|
|
3539
|
-
const configPath =
|
|
3540
|
-
if (
|
|
3684
|
+
const configPath = join14(projectPath, `vite.config${ext}`);
|
|
3685
|
+
if (existsSync14(configPath)) return configPath;
|
|
3541
3686
|
}
|
|
3542
3687
|
return null;
|
|
3543
3688
|
}
|
|
@@ -3712,7 +3857,7 @@ async function installViteJsxLocPlugin(opts) {
|
|
|
3712
3857
|
const configPath = findViteConfigFile2(opts.projectPath);
|
|
3713
3858
|
if (!configPath) return { configFile: null, modified: false };
|
|
3714
3859
|
const configFilename = getViteConfigFilename(configPath);
|
|
3715
|
-
const original =
|
|
3860
|
+
const original = readFileSync10(configPath, "utf-8");
|
|
3716
3861
|
const isCjs = configPath.endsWith(".cjs");
|
|
3717
3862
|
let mod;
|
|
3718
3863
|
try {
|
|
@@ -3741,9 +3886,9 @@ async function installViteJsxLocPlugin(opts) {
|
|
|
3741
3886
|
}
|
|
3742
3887
|
|
|
3743
3888
|
// src/utils/next-routes.ts
|
|
3744
|
-
import { existsSync as
|
|
3889
|
+
import { existsSync as existsSync15 } from "fs";
|
|
3745
3890
|
import { mkdir, writeFile } from "fs/promises";
|
|
3746
|
-
import { join as
|
|
3891
|
+
import { join as join15 } from "path";
|
|
3747
3892
|
var DEV_SOURCE_ROUTE_TS = `/**
|
|
3748
3893
|
* Dev-only API route for fetching source files
|
|
3749
3894
|
*
|
|
@@ -4199,23 +4344,23 @@ export async function GET(request: NextRequest) {
|
|
|
4199
4344
|
}
|
|
4200
4345
|
`;
|
|
4201
4346
|
async function writeRouteFile(absPath, relPath, content, opts) {
|
|
4202
|
-
if (
|
|
4347
|
+
if (existsSync15(absPath) && !opts.force) return;
|
|
4203
4348
|
await writeFile(absPath, content, "utf-8");
|
|
4204
4349
|
}
|
|
4205
4350
|
async function installNextUILintRoutes(opts) {
|
|
4206
|
-
const baseRel =
|
|
4207
|
-
const baseAbs =
|
|
4208
|
-
await mkdir(
|
|
4351
|
+
const baseRel = join15(opts.appRoot, "api", ".uilint");
|
|
4352
|
+
const baseAbs = join15(opts.projectPath, baseRel);
|
|
4353
|
+
await mkdir(join15(baseAbs, "source"), { recursive: true });
|
|
4209
4354
|
await writeRouteFile(
|
|
4210
|
-
|
|
4211
|
-
|
|
4355
|
+
join15(baseAbs, "source", "route.ts"),
|
|
4356
|
+
join15(baseRel, "source", "route.ts"),
|
|
4212
4357
|
DEV_SOURCE_ROUTE_TS,
|
|
4213
4358
|
opts
|
|
4214
4359
|
);
|
|
4215
|
-
await mkdir(
|
|
4360
|
+
await mkdir(join15(baseAbs, "screenshots"), { recursive: true });
|
|
4216
4361
|
await writeRouteFile(
|
|
4217
|
-
|
|
4218
|
-
|
|
4362
|
+
join15(baseAbs, "screenshots", "route.ts"),
|
|
4363
|
+
join15(baseRel, "screenshots", "route.ts"),
|
|
4219
4364
|
SCREENSHOT_ROUTE_TS,
|
|
4220
4365
|
opts
|
|
4221
4366
|
);
|
|
@@ -4234,7 +4379,7 @@ async function executeAction(action, options) {
|
|
|
4234
4379
|
wouldDo: `Create directory: ${action.path}`
|
|
4235
4380
|
};
|
|
4236
4381
|
}
|
|
4237
|
-
if (!
|
|
4382
|
+
if (!existsSync16(action.path)) {
|
|
4238
4383
|
mkdirSync3(action.path, { recursive: true });
|
|
4239
4384
|
}
|
|
4240
4385
|
return { action, success: true };
|
|
@@ -4247,8 +4392,8 @@ async function executeAction(action, options) {
|
|
|
4247
4392
|
wouldDo: `Create file: ${action.path}${action.permissions ? ` (mode: ${action.permissions.toString(8)})` : ""}`
|
|
4248
4393
|
};
|
|
4249
4394
|
}
|
|
4250
|
-
const dir =
|
|
4251
|
-
if (!
|
|
4395
|
+
const dir = dirname9(action.path);
|
|
4396
|
+
if (!existsSync16(dir)) {
|
|
4252
4397
|
mkdirSync3(dir, { recursive: true });
|
|
4253
4398
|
}
|
|
4254
4399
|
writeFileSync7(action.path, action.content, "utf-8");
|
|
@@ -4266,15 +4411,15 @@ async function executeAction(action, options) {
|
|
|
4266
4411
|
};
|
|
4267
4412
|
}
|
|
4268
4413
|
let existing = {};
|
|
4269
|
-
if (
|
|
4414
|
+
if (existsSync16(action.path)) {
|
|
4270
4415
|
try {
|
|
4271
|
-
existing = JSON.parse(
|
|
4416
|
+
existing = JSON.parse(readFileSync11(action.path, "utf-8"));
|
|
4272
4417
|
} catch {
|
|
4273
4418
|
}
|
|
4274
4419
|
}
|
|
4275
4420
|
const merged = deepMerge(existing, action.merge);
|
|
4276
|
-
const dir =
|
|
4277
|
-
if (!
|
|
4421
|
+
const dir = dirname9(action.path);
|
|
4422
|
+
if (!existsSync16(dir)) {
|
|
4278
4423
|
mkdirSync3(dir, { recursive: true });
|
|
4279
4424
|
}
|
|
4280
4425
|
writeFileSync7(action.path, JSON.stringify(merged, null, 2), "utf-8");
|
|
@@ -4288,7 +4433,7 @@ async function executeAction(action, options) {
|
|
|
4288
4433
|
wouldDo: `Delete file: ${action.path}`
|
|
4289
4434
|
};
|
|
4290
4435
|
}
|
|
4291
|
-
if (
|
|
4436
|
+
if (existsSync16(action.path)) {
|
|
4292
4437
|
unlinkSync(action.path);
|
|
4293
4438
|
}
|
|
4294
4439
|
return { action, success: true };
|
|
@@ -4301,8 +4446,8 @@ async function executeAction(action, options) {
|
|
|
4301
4446
|
wouldDo: `Append to file: ${action.path}`
|
|
4302
4447
|
};
|
|
4303
4448
|
}
|
|
4304
|
-
if (
|
|
4305
|
-
const content =
|
|
4449
|
+
if (existsSync16(action.path)) {
|
|
4450
|
+
const content = readFileSync11(action.path, "utf-8");
|
|
4306
4451
|
if (action.ifNotContains && content.includes(action.ifNotContains)) {
|
|
4307
4452
|
return { action, success: true };
|
|
4308
4453
|
}
|
|
@@ -4988,7 +5133,7 @@ function displayResults(result) {
|
|
|
4988
5133
|
if (summary.nextApp) {
|
|
4989
5134
|
installedItems.push(
|
|
4990
5135
|
`${pc.cyan("Next Routes")} \u2192 ${pc.dim(
|
|
4991
|
-
|
|
5136
|
+
join16(summary.nextApp.appRoot, "api/.uilint")
|
|
4992
5137
|
)}`
|
|
4993
5138
|
);
|
|
4994
5139
|
installedItems.push(
|
|
@@ -5123,19 +5268,19 @@ async function install(options = {}, prompter = cliPrompter, executeOptions = {}
|
|
|
5123
5268
|
}
|
|
5124
5269
|
|
|
5125
5270
|
// src/commands/serve.ts
|
|
5126
|
-
import { existsSync as
|
|
5127
|
-
import { createRequire as
|
|
5128
|
-
import { dirname as
|
|
5271
|
+
import { existsSync as existsSync18, statSync as statSync4, readdirSync as readdirSync5, readFileSync as readFileSync12 } from "fs";
|
|
5272
|
+
import { createRequire as createRequire3 } from "module";
|
|
5273
|
+
import { dirname as dirname11, resolve as resolve5, relative as relative4, join as join18, parse as parse2 } from "path";
|
|
5129
5274
|
import { WebSocketServer, WebSocket } from "ws";
|
|
5130
5275
|
import { watch } from "chokidar";
|
|
5131
5276
|
import {
|
|
5132
|
-
findWorkspaceRoot as
|
|
5277
|
+
findWorkspaceRoot as findWorkspaceRoot6,
|
|
5133
5278
|
getVisionAnalyzer as getCoreVisionAnalyzer
|
|
5134
5279
|
} from "uilint-core/node";
|
|
5135
5280
|
|
|
5136
5281
|
// src/utils/vision-run.ts
|
|
5137
|
-
import { dirname as
|
|
5138
|
-
import { existsSync as
|
|
5282
|
+
import { dirname as dirname10, join as join17, parse } from "path";
|
|
5283
|
+
import { existsSync as existsSync17, statSync as statSync3, mkdirSync as mkdirSync4, writeFileSync as writeFileSync8 } from "fs";
|
|
5139
5284
|
import {
|
|
5140
5285
|
ensureOllamaReady as ensureOllamaReady5,
|
|
5141
5286
|
findStyleGuidePath as findStyleGuidePath4,
|
|
@@ -5149,7 +5294,7 @@ async function resolveVisionStyleGuide(args) {
|
|
|
5149
5294
|
const startDir = args.startDir ?? projectPath;
|
|
5150
5295
|
if (args.styleguide) {
|
|
5151
5296
|
const styleguideArg = resolvePathSpecifier(args.styleguide, projectPath);
|
|
5152
|
-
if (
|
|
5297
|
+
if (existsSync17(styleguideArg)) {
|
|
5153
5298
|
const stat = statSync3(styleguideArg);
|
|
5154
5299
|
if (stat.isFile()) {
|
|
5155
5300
|
return {
|
|
@@ -5193,7 +5338,7 @@ function writeVisionDebugDump(params) {
|
|
|
5193
5338
|
);
|
|
5194
5339
|
const safeStamp = params.now.toISOString().replace(/[:.]/g, "-");
|
|
5195
5340
|
const dumpFile = resolvedDirOrFile.endsWith(".json") || resolvedDirOrFile.endsWith(".jsonl") ? resolvedDirOrFile : `${resolvedDirOrFile}/vision-debug-${safeStamp}.json`;
|
|
5196
|
-
mkdirSync4(
|
|
5341
|
+
mkdirSync4(dirname10(dumpFile), { recursive: true });
|
|
5197
5342
|
writeFileSync8(
|
|
5198
5343
|
dumpFile,
|
|
5199
5344
|
JSON.stringify(
|
|
@@ -5282,7 +5427,7 @@ async function runVisionAnalysis(args) {
|
|
|
5282
5427
|
}
|
|
5283
5428
|
function writeVisionMarkdownReport(args) {
|
|
5284
5429
|
const p2 = parse(args.imagePath);
|
|
5285
|
-
const outPath = args.outPath ??
|
|
5430
|
+
const outPath = args.outPath ?? join17(p2.dir, `${p2.name || p2.base}.vision.md`);
|
|
5286
5431
|
const lines = [];
|
|
5287
5432
|
lines.push(`# UILint Vision Report`);
|
|
5288
5433
|
lines.push(``);
|
|
@@ -5318,7 +5463,7 @@ function writeVisionMarkdownReport(args) {
|
|
|
5318
5463
|
lines.push("```");
|
|
5319
5464
|
lines.push(``);
|
|
5320
5465
|
const content = lines.join("\n");
|
|
5321
|
-
mkdirSync4(
|
|
5466
|
+
mkdirSync4(dirname10(outPath), { recursive: true });
|
|
5322
5467
|
writeFileSync8(outPath, content, "utf-8");
|
|
5323
5468
|
return { outPath, content };
|
|
5324
5469
|
}
|
|
@@ -5354,7 +5499,7 @@ var resolvedPathCache = /* @__PURE__ */ new Map();
|
|
|
5354
5499
|
var subscriptions = /* @__PURE__ */ new Map();
|
|
5355
5500
|
var fileWatcher = null;
|
|
5356
5501
|
var connectedClients = 0;
|
|
5357
|
-
var localRequire =
|
|
5502
|
+
var localRequire = createRequire3(import.meta.url);
|
|
5358
5503
|
function buildLineStarts(code) {
|
|
5359
5504
|
const starts = [0];
|
|
5360
5505
|
for (let i = 0; i < code.length; i++) {
|
|
@@ -5432,10 +5577,10 @@ function findESLintCwd(startDir) {
|
|
|
5432
5577
|
let dir = startDir;
|
|
5433
5578
|
for (let i = 0; i < 30; i++) {
|
|
5434
5579
|
for (const cfg of ESLINT_CONFIG_FILES2) {
|
|
5435
|
-
if (
|
|
5580
|
+
if (existsSync18(join18(dir, cfg))) return dir;
|
|
5436
5581
|
}
|
|
5437
|
-
if (
|
|
5438
|
-
const parent =
|
|
5582
|
+
if (existsSync18(join18(dir, "package.json"))) return dir;
|
|
5583
|
+
const parent = dirname11(dir);
|
|
5439
5584
|
if (parent === dir) break;
|
|
5440
5585
|
dir = parent;
|
|
5441
5586
|
}
|
|
@@ -5448,7 +5593,7 @@ function normalizeDataLocFilePath(absoluteFilePath, projectCwd) {
|
|
|
5448
5593
|
const abs = normalizePathSlashes(resolve5(absoluteFilePath));
|
|
5449
5594
|
const cwd = normalizePathSlashes(resolve5(projectCwd));
|
|
5450
5595
|
if (abs === cwd || abs.startsWith(cwd + "/")) {
|
|
5451
|
-
return normalizePathSlashes(
|
|
5596
|
+
return normalizePathSlashes(relative4(cwd, abs));
|
|
5452
5597
|
}
|
|
5453
5598
|
return abs;
|
|
5454
5599
|
}
|
|
@@ -5460,25 +5605,25 @@ function resolveRequestedFilePath(filePath) {
|
|
|
5460
5605
|
if (cached) return cached;
|
|
5461
5606
|
const cwd = process.cwd();
|
|
5462
5607
|
const fromCwd = resolve5(cwd, filePath);
|
|
5463
|
-
if (
|
|
5608
|
+
if (existsSync18(fromCwd)) {
|
|
5464
5609
|
resolvedPathCache.set(filePath, fromCwd);
|
|
5465
5610
|
return fromCwd;
|
|
5466
5611
|
}
|
|
5467
|
-
const wsRoot =
|
|
5612
|
+
const wsRoot = findWorkspaceRoot6(cwd);
|
|
5468
5613
|
const fromWs = resolve5(wsRoot, filePath);
|
|
5469
|
-
if (
|
|
5614
|
+
if (existsSync18(fromWs)) {
|
|
5470
5615
|
resolvedPathCache.set(filePath, fromWs);
|
|
5471
5616
|
return fromWs;
|
|
5472
5617
|
}
|
|
5473
5618
|
for (const top of ["apps", "packages"]) {
|
|
5474
|
-
const base =
|
|
5475
|
-
if (!
|
|
5619
|
+
const base = join18(wsRoot, top);
|
|
5620
|
+
if (!existsSync18(base)) continue;
|
|
5476
5621
|
try {
|
|
5477
5622
|
const entries = readdirSync5(base, { withFileTypes: true });
|
|
5478
5623
|
for (const ent of entries) {
|
|
5479
5624
|
if (!ent.isDirectory()) continue;
|
|
5480
5625
|
const p2 = resolve5(base, ent.name, filePath);
|
|
5481
|
-
if (
|
|
5626
|
+
if (existsSync18(p2)) {
|
|
5482
5627
|
resolvedPathCache.set(filePath, p2);
|
|
5483
5628
|
return p2;
|
|
5484
5629
|
}
|
|
@@ -5493,7 +5638,7 @@ async function getESLintForProject(projectCwd) {
|
|
|
5493
5638
|
const cached = eslintInstances.get(projectCwd);
|
|
5494
5639
|
if (cached) return cached;
|
|
5495
5640
|
try {
|
|
5496
|
-
const req =
|
|
5641
|
+
const req = createRequire3(join18(projectCwd, "package.json"));
|
|
5497
5642
|
const mod = req("eslint");
|
|
5498
5643
|
const ESLintCtor = mod?.ESLint ?? mod?.default?.ESLint ?? mod?.default ?? mod;
|
|
5499
5644
|
if (!ESLintCtor) return null;
|
|
@@ -5506,7 +5651,7 @@ async function getESLintForProject(projectCwd) {
|
|
|
5506
5651
|
}
|
|
5507
5652
|
async function lintFile(filePath, onProgress) {
|
|
5508
5653
|
const absolutePath = resolveRequestedFilePath(filePath);
|
|
5509
|
-
if (!
|
|
5654
|
+
if (!existsSync18(absolutePath)) {
|
|
5510
5655
|
onProgress(`File not found: ${pc.dim(absolutePath)}`);
|
|
5511
5656
|
return [];
|
|
5512
5657
|
}
|
|
@@ -5522,7 +5667,7 @@ async function lintFile(filePath, onProgress) {
|
|
|
5522
5667
|
onProgress("Cache hit (unchanged)");
|
|
5523
5668
|
return cached.issues;
|
|
5524
5669
|
}
|
|
5525
|
-
const fileDir =
|
|
5670
|
+
const fileDir = dirname11(absolutePath);
|
|
5526
5671
|
const projectCwd = findESLintCwd(fileDir);
|
|
5527
5672
|
onProgress(`Resolving ESLint project... ${pc.dim(projectCwd)}`);
|
|
5528
5673
|
const eslint = await getESLintForProject(projectCwd);
|
|
@@ -5545,7 +5690,7 @@ async function lintFile(filePath, onProgress) {
|
|
|
5545
5690
|
let codeLength = 0;
|
|
5546
5691
|
try {
|
|
5547
5692
|
onProgress("Building JSX map...");
|
|
5548
|
-
const code =
|
|
5693
|
+
const code = readFileSync12(absolutePath, "utf-8");
|
|
5549
5694
|
codeLength = code.length;
|
|
5550
5695
|
lineStarts = buildLineStarts(code);
|
|
5551
5696
|
spans = buildJsxElementSpans(code, dataLocFile);
|
|
@@ -5627,9 +5772,9 @@ async function handleMessage(ws, data) {
|
|
|
5627
5772
|
});
|
|
5628
5773
|
const startedAt = Date.now();
|
|
5629
5774
|
const resolved = resolveRequestedFilePath(filePath);
|
|
5630
|
-
if (!
|
|
5775
|
+
if (!existsSync18(resolved)) {
|
|
5631
5776
|
const cwd = process.cwd();
|
|
5632
|
-
const wsRoot =
|
|
5777
|
+
const wsRoot = findWorkspaceRoot6(cwd);
|
|
5633
5778
|
logWarning(
|
|
5634
5779
|
[
|
|
5635
5780
|
`${pc.dim("[ws]")} File not found for request`,
|
|
@@ -5789,14 +5934,14 @@ async function handleMessage(ws, data) {
|
|
|
5789
5934
|
)}`
|
|
5790
5935
|
);
|
|
5791
5936
|
} else {
|
|
5792
|
-
const screenshotsDir =
|
|
5937
|
+
const screenshotsDir = join18(
|
|
5793
5938
|
serverAppRootForVision,
|
|
5794
5939
|
".uilint",
|
|
5795
5940
|
"screenshots"
|
|
5796
5941
|
);
|
|
5797
|
-
const imagePath =
|
|
5942
|
+
const imagePath = join18(screenshotsDir, screenshotFile);
|
|
5798
5943
|
try {
|
|
5799
|
-
if (!
|
|
5944
|
+
if (!existsSync18(imagePath)) {
|
|
5800
5945
|
logWarning(
|
|
5801
5946
|
`Skipping vision report write: screenshot file not found ${pc.dim(
|
|
5802
5947
|
imagePath
|
|
@@ -5904,7 +6049,7 @@ function handleFileChange(filePath) {
|
|
|
5904
6049
|
async function serve(options) {
|
|
5905
6050
|
const port = options.port || 9234;
|
|
5906
6051
|
const cwd = process.cwd();
|
|
5907
|
-
const wsRoot =
|
|
6052
|
+
const wsRoot = findWorkspaceRoot6(cwd);
|
|
5908
6053
|
const appRoot = pickAppRoot({ cwd, workspaceRoot: wsRoot });
|
|
5909
6054
|
serverAppRootForVision = appRoot;
|
|
5910
6055
|
logInfo(`Workspace root: ${pc.dim(wsRoot)}`);
|
|
@@ -5957,10 +6102,10 @@ async function serve(options) {
|
|
|
5957
6102
|
}
|
|
5958
6103
|
|
|
5959
6104
|
// src/commands/vision.ts
|
|
5960
|
-
import { dirname as
|
|
6105
|
+
import { dirname as dirname12, resolve as resolve6, join as join19 } from "path";
|
|
5961
6106
|
import {
|
|
5962
|
-
existsSync as
|
|
5963
|
-
readFileSync as
|
|
6107
|
+
existsSync as existsSync19,
|
|
6108
|
+
readFileSync as readFileSync13,
|
|
5964
6109
|
readdirSync as readdirSync6
|
|
5965
6110
|
} from "fs";
|
|
5966
6111
|
import {
|
|
@@ -6006,17 +6151,17 @@ function debugLog3(enabled, message, obj) {
|
|
|
6006
6151
|
function findScreenshotsDirUpwards(startDir) {
|
|
6007
6152
|
let dir = startDir;
|
|
6008
6153
|
for (let i = 0; i < 20; i++) {
|
|
6009
|
-
const candidate =
|
|
6010
|
-
if (
|
|
6011
|
-
const parent =
|
|
6154
|
+
const candidate = join19(dir, ".uilint", "screenshots");
|
|
6155
|
+
if (existsSync19(candidate)) return candidate;
|
|
6156
|
+
const parent = dirname12(dir);
|
|
6012
6157
|
if (parent === dir) break;
|
|
6013
6158
|
dir = parent;
|
|
6014
6159
|
}
|
|
6015
6160
|
return null;
|
|
6016
6161
|
}
|
|
6017
6162
|
function listScreenshotSidecars(dirPath) {
|
|
6018
|
-
if (!
|
|
6019
|
-
const entries = readdirSync6(dirPath).filter((f) => f.endsWith(".json")).map((f) =>
|
|
6163
|
+
if (!existsSync19(dirPath)) return [];
|
|
6164
|
+
const entries = readdirSync6(dirPath).filter((f) => f.endsWith(".json")).map((f) => join19(dirPath, f));
|
|
6020
6165
|
const out = [];
|
|
6021
6166
|
for (const p2 of entries) {
|
|
6022
6167
|
try {
|
|
@@ -6045,11 +6190,11 @@ function listScreenshotSidecars(dirPath) {
|
|
|
6045
6190
|
return out;
|
|
6046
6191
|
}
|
|
6047
6192
|
function readImageAsBase64(imagePath) {
|
|
6048
|
-
const bytes =
|
|
6193
|
+
const bytes = readFileSync13(imagePath);
|
|
6049
6194
|
return { base64: bytes.toString("base64"), sizeBytes: bytes.byteLength };
|
|
6050
6195
|
}
|
|
6051
6196
|
function loadJsonFile(filePath) {
|
|
6052
|
-
const raw =
|
|
6197
|
+
const raw = readFileSync13(filePath, "utf-8");
|
|
6053
6198
|
return JSON.parse(raw);
|
|
6054
6199
|
}
|
|
6055
6200
|
function formatIssuesText(issues) {
|
|
@@ -6123,13 +6268,13 @@ async function vision(options) {
|
|
|
6123
6268
|
await flushLangfuse();
|
|
6124
6269
|
process.exit(1);
|
|
6125
6270
|
}
|
|
6126
|
-
if (imagePath && !
|
|
6271
|
+
if (imagePath && !existsSync19(imagePath)) {
|
|
6127
6272
|
throw new Error(`Image not found: ${imagePath}`);
|
|
6128
6273
|
}
|
|
6129
|
-
if (sidecarPath && !
|
|
6274
|
+
if (sidecarPath && !existsSync19(sidecarPath)) {
|
|
6130
6275
|
throw new Error(`Sidecar not found: ${sidecarPath}`);
|
|
6131
6276
|
}
|
|
6132
|
-
if (manifestFilePath && !
|
|
6277
|
+
if (manifestFilePath && !existsSync19(manifestFilePath)) {
|
|
6133
6278
|
throw new Error(`Manifest file not found: ${manifestFilePath}`);
|
|
6134
6279
|
}
|
|
6135
6280
|
const sidecar = sidecarPath ? loadJsonFile(sidecarPath) : null;
|
|
@@ -6154,7 +6299,7 @@ async function vision(options) {
|
|
|
6154
6299
|
const resolved = await resolveVisionStyleGuide({
|
|
6155
6300
|
projectPath,
|
|
6156
6301
|
styleguide: options.styleguide,
|
|
6157
|
-
startDir: startPath ?
|
|
6302
|
+
startDir: startPath ? dirname12(startPath) : projectPath
|
|
6158
6303
|
});
|
|
6159
6304
|
styleGuide = resolved.styleGuide;
|
|
6160
6305
|
styleguideLocation = resolved.styleguideLocation;
|
|
@@ -6203,7 +6348,7 @@ async function vision(options) {
|
|
|
6203
6348
|
const resolvedImagePath = imagePath || (() => {
|
|
6204
6349
|
const screenshotFile = typeof sidecar?.screenshotFile === "string" ? sidecar.screenshotFile : typeof sidecar?.filename === "string" ? sidecar.filename : void 0;
|
|
6205
6350
|
if (!screenshotFile) return null;
|
|
6206
|
-
const baseDir = sidecarPath ?
|
|
6351
|
+
const baseDir = sidecarPath ? dirname12(sidecarPath) : projectPath;
|
|
6207
6352
|
const abs = resolve6(baseDir, screenshotFile);
|
|
6208
6353
|
return abs;
|
|
6209
6354
|
})();
|
|
@@ -6212,7 +6357,7 @@ async function vision(options) {
|
|
|
6212
6357
|
"No image path could be resolved. Provide --image or a sidecar with `screenshotFile`/`filename`."
|
|
6213
6358
|
);
|
|
6214
6359
|
}
|
|
6215
|
-
if (!
|
|
6360
|
+
if (!existsSync19(resolvedImagePath)) {
|
|
6216
6361
|
throw new Error(`Image not found: ${resolvedImagePath}`);
|
|
6217
6362
|
}
|
|
6218
6363
|
const { base64, sizeBytes } = readImageAsBase64(resolvedImagePath);
|
|
@@ -6440,8 +6585,8 @@ async function vision(options) {
|
|
|
6440
6585
|
}
|
|
6441
6586
|
|
|
6442
6587
|
// src/commands/session.ts
|
|
6443
|
-
import { existsSync as
|
|
6444
|
-
import { basename, dirname as
|
|
6588
|
+
import { existsSync as existsSync20, readFileSync as readFileSync14, writeFileSync as writeFileSync10, unlinkSync as unlinkSync2 } from "fs";
|
|
6589
|
+
import { basename, dirname as dirname13, resolve as resolve7 } from "path";
|
|
6445
6590
|
import { createStyleSummary as createStyleSummary3 } from "uilint-core";
|
|
6446
6591
|
import {
|
|
6447
6592
|
ensureOllamaReady as ensureOllamaReady7,
|
|
@@ -6452,11 +6597,11 @@ import {
|
|
|
6452
6597
|
var SESSION_FILE = "/tmp/uilint-session.json";
|
|
6453
6598
|
var UI_FILE_EXTENSIONS = [".tsx", ".jsx", ".css", ".scss", ".module.css"];
|
|
6454
6599
|
function readSession() {
|
|
6455
|
-
if (!
|
|
6600
|
+
if (!existsSync20(SESSION_FILE)) {
|
|
6456
6601
|
return { files: [], startedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
6457
6602
|
}
|
|
6458
6603
|
try {
|
|
6459
|
-
const content =
|
|
6604
|
+
const content = readFileSync14(SESSION_FILE, "utf-8");
|
|
6460
6605
|
return JSON.parse(content);
|
|
6461
6606
|
} catch {
|
|
6462
6607
|
return { files: [], startedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
@@ -6474,7 +6619,7 @@ function isScannableMarkupFile(filePath) {
|
|
|
6474
6619
|
);
|
|
6475
6620
|
}
|
|
6476
6621
|
async function sessionClear() {
|
|
6477
|
-
if (
|
|
6622
|
+
if (existsSync20(SESSION_FILE)) {
|
|
6478
6623
|
unlinkSync2(SESSION_FILE);
|
|
6479
6624
|
}
|
|
6480
6625
|
console.log(JSON.stringify({ cleared: true }));
|
|
@@ -6545,13 +6690,13 @@ async function sessionScan(options = {}) {
|
|
|
6545
6690
|
const client = await createLLMClient({});
|
|
6546
6691
|
const results = [];
|
|
6547
6692
|
for (const filePath of session.files) {
|
|
6548
|
-
if (!
|
|
6693
|
+
if (!existsSync20(filePath)) continue;
|
|
6549
6694
|
if (!isScannableMarkupFile(filePath)) continue;
|
|
6550
6695
|
try {
|
|
6551
6696
|
const absolutePath = resolve7(process.cwd(), filePath);
|
|
6552
|
-
const htmlLike =
|
|
6697
|
+
const htmlLike = readFileSync14(filePath, "utf-8");
|
|
6553
6698
|
const snapshot = parseCLIInput2(htmlLike);
|
|
6554
|
-
const tailwindSearchDir =
|
|
6699
|
+
const tailwindSearchDir = dirname13(absolutePath);
|
|
6555
6700
|
const tailwindTheme = readTailwindThemeTokens3(tailwindSearchDir);
|
|
6556
6701
|
const styleSummary = createStyleSummary3(snapshot.styles, {
|
|
6557
6702
|
html: snapshot.html,
|
|
@@ -6604,7 +6749,7 @@ async function sessionScan(options = {}) {
|
|
|
6604
6749
|
};
|
|
6605
6750
|
console.log(JSON.stringify(result));
|
|
6606
6751
|
}
|
|
6607
|
-
if (
|
|
6752
|
+
if (existsSync20(SESSION_FILE)) {
|
|
6608
6753
|
unlinkSync2(SESSION_FILE);
|
|
6609
6754
|
}
|
|
6610
6755
|
await flushLangfuse();
|
|
@@ -6615,9 +6760,9 @@ async function sessionList() {
|
|
|
6615
6760
|
}
|
|
6616
6761
|
|
|
6617
6762
|
// src/index.ts
|
|
6618
|
-
import { readFileSync as
|
|
6619
|
-
import { dirname as
|
|
6620
|
-
import { fileURLToPath as
|
|
6763
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
6764
|
+
import { dirname as dirname14, join as join20 } from "path";
|
|
6765
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
6621
6766
|
function assertNodeVersion(minMajor) {
|
|
6622
6767
|
const ver = process.versions.node || "";
|
|
6623
6768
|
const majorStr = ver.split(".")[0] || "";
|
|
@@ -6633,9 +6778,9 @@ assertNodeVersion(20);
|
|
|
6633
6778
|
var program = new Command();
|
|
6634
6779
|
function getCLIVersion2() {
|
|
6635
6780
|
try {
|
|
6636
|
-
const
|
|
6637
|
-
const pkgPath =
|
|
6638
|
-
const pkg = JSON.parse(
|
|
6781
|
+
const __dirname3 = dirname14(fileURLToPath4(import.meta.url));
|
|
6782
|
+
const pkgPath = join20(__dirname3, "..", "package.json");
|
|
6783
|
+
const pkg = JSON.parse(readFileSync15(pkgPath, "utf-8"));
|
|
6639
6784
|
return pkg.version || "0.0.0";
|
|
6640
6785
|
} catch {
|
|
6641
6786
|
return "0.0.0";
|