uilint 0.2.22 → 0.2.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-P4I4RKBY.js +126 -0
- package/dist/chunk-P4I4RKBY.js.map +1 -0
- package/dist/{chunk-PBEKMDUH.js → chunk-TWUDB36F.js} +62 -54
- package/dist/chunk-TWUDB36F.js.map +1 -0
- package/dist/chunk-VNANPKR2.js +1226 -0
- package/dist/chunk-VNANPKR2.js.map +1 -0
- package/dist/index.js +690 -29
- package/dist/index.js.map +1 -1
- package/dist/{install-ui-HTVB5HDB.js → install-ui-CCZ3XJDE.js} +438 -793
- package/dist/install-ui-CCZ3XJDE.js.map +1 -0
- package/dist/{plan-SIXVCXCK.js → plan-5WHKVACB.js} +95 -40
- package/dist/plan-5WHKVACB.js.map +1 -0
- package/package.json +9 -4
- package/dist/chunk-FRNXXIEM.js +0 -197
- package/dist/chunk-FRNXXIEM.js.map +0 -1
- package/dist/chunk-PBEKMDUH.js.map +0 -1
- package/dist/install-ui-HTVB5HDB.js.map +0 -1
- package/dist/plan-SIXVCXCK.js.map +0 -1
|
@@ -1,20 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
confirm,
|
|
4
|
+
detectCoverageSetup,
|
|
4
5
|
detectNextAppRouter,
|
|
6
|
+
findEslintConfigFile,
|
|
5
7
|
findNextAppRouterProjects,
|
|
8
|
+
getEslintConfigFilename,
|
|
9
|
+
getUilintEslintConfigInfoFromSource,
|
|
10
|
+
injectCoverageConfig,
|
|
11
|
+
installEslintPlugin,
|
|
6
12
|
log,
|
|
7
13
|
multiselect,
|
|
8
14
|
note,
|
|
9
15
|
pc,
|
|
10
|
-
select
|
|
11
|
-
|
|
16
|
+
select,
|
|
17
|
+
uninstallEslintPlugin
|
|
18
|
+
} from "./chunk-VNANPKR2.js";
|
|
12
19
|
import {
|
|
13
20
|
GENSTYLEGUIDE_COMMAND_MD,
|
|
21
|
+
loadSkill,
|
|
22
|
+
toInstallSpecifier
|
|
23
|
+
} from "./chunk-TWUDB36F.js";
|
|
24
|
+
import {
|
|
14
25
|
detectPackageManager,
|
|
15
26
|
installDependencies,
|
|
16
|
-
|
|
17
|
-
} from "./chunk-
|
|
27
|
+
runTestsWithCoverage
|
|
28
|
+
} from "./chunk-P4I4RKBY.js";
|
|
18
29
|
|
|
19
30
|
// src/commands/install-ui.tsx
|
|
20
31
|
import { render } from "ink";
|
|
@@ -841,7 +852,7 @@ var nextOverlayInstaller = {
|
|
|
841
852
|
label: app.projectPath.split("/").pop() || app.projectPath,
|
|
842
853
|
path: app.projectPath,
|
|
843
854
|
hint: "App Router",
|
|
844
|
-
isInstalled:
|
|
855
|
+
isInstalled: app.hasUilintOverlay
|
|
845
856
|
}));
|
|
846
857
|
},
|
|
847
858
|
plan(targets, config, project) {
|
|
@@ -865,7 +876,19 @@ var nextOverlayInstaller = {
|
|
|
865
876
|
dependencies.push({
|
|
866
877
|
packagePath: projectPath,
|
|
867
878
|
packageManager: project.packageManager,
|
|
868
|
-
packages: [
|
|
879
|
+
packages: [
|
|
880
|
+
toInstallSpecifier("uilint-react", {
|
|
881
|
+
preferWorkspaceProtocol: project.packageManager === "pnpm",
|
|
882
|
+
workspaceRoot: project.workspaceRoot,
|
|
883
|
+
targetProjectPath: projectPath
|
|
884
|
+
}),
|
|
885
|
+
toInstallSpecifier("uilint-core", {
|
|
886
|
+
preferWorkspaceProtocol: project.packageManager === "pnpm",
|
|
887
|
+
workspaceRoot: project.workspaceRoot,
|
|
888
|
+
targetProjectPath: projectPath
|
|
889
|
+
}),
|
|
890
|
+
"jsx-loc-plugin"
|
|
891
|
+
]
|
|
869
892
|
});
|
|
870
893
|
actions.push({
|
|
871
894
|
type: "inject_react",
|
|
@@ -1309,9 +1332,9 @@ function InstallApp({
|
|
|
1309
1332
|
}
|
|
1310
1333
|
|
|
1311
1334
|
// src/commands/install/analyze.ts
|
|
1312
|
-
import { existsSync as
|
|
1313
|
-
import { join as
|
|
1314
|
-
import { findWorkspaceRoot
|
|
1335
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
|
|
1336
|
+
import { join as join4 } from "path";
|
|
1337
|
+
import { findWorkspaceRoot } from "uilint-core/node";
|
|
1315
1338
|
|
|
1316
1339
|
// src/utils/vite-detect.ts
|
|
1317
1340
|
import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2 } from "fs";
|
|
@@ -1554,644 +1577,63 @@ function findPackages(rootDir, options) {
|
|
|
1554
1577
|
});
|
|
1555
1578
|
}
|
|
1556
1579
|
|
|
1557
|
-
// src/
|
|
1558
|
-
|
|
1559
|
-
import { join as join4, relative as relative3, dirname as dirname2 } from "path";
|
|
1560
|
-
import { parseExpression, parseModule as parseModule2, generateCode } from "magicast";
|
|
1561
|
-
import { findWorkspaceRoot } from "uilint-core/node";
|
|
1562
|
-
var CONFIG_EXTENSIONS = [".ts", ".mjs", ".js", ".cjs"];
|
|
1563
|
-
function findEslintConfigFile(projectPath) {
|
|
1564
|
-
for (const ext of CONFIG_EXTENSIONS) {
|
|
1565
|
-
const configPath = join4(projectPath, `eslint.config${ext}`);
|
|
1566
|
-
if (existsSync4(configPath)) {
|
|
1567
|
-
return configPath;
|
|
1568
|
-
}
|
|
1569
|
-
}
|
|
1570
|
-
return null;
|
|
1571
|
-
}
|
|
1572
|
-
function getEslintConfigFilename(configPath) {
|
|
1573
|
-
const parts = configPath.split("/");
|
|
1574
|
-
return parts[parts.length - 1] || "eslint.config.mjs";
|
|
1575
|
-
}
|
|
1576
|
-
function isIdentifier(node, name) {
|
|
1577
|
-
return !!node && node.type === "Identifier" && (name ? node.name === name : typeof node.name === "string");
|
|
1578
|
-
}
|
|
1579
|
-
function isStringLiteral(node) {
|
|
1580
|
-
return !!node && (node.type === "StringLiteral" || node.type === "Literal") && typeof node.value === "string";
|
|
1581
|
-
}
|
|
1582
|
-
function getObjectPropertyValue(obj, keyName) {
|
|
1583
|
-
if (!obj || obj.type !== "ObjectExpression") return null;
|
|
1584
|
-
for (const prop of obj.properties ?? []) {
|
|
1585
|
-
if (!prop) continue;
|
|
1586
|
-
if (prop.type === "ObjectProperty" || prop.type === "Property") {
|
|
1587
|
-
const key = prop.key;
|
|
1588
|
-
const keyMatch = key?.type === "Identifier" && key.name === keyName || isStringLiteral(key) && key.value === keyName;
|
|
1589
|
-
if (keyMatch) return prop.value;
|
|
1590
|
-
}
|
|
1591
|
-
}
|
|
1592
|
-
return null;
|
|
1593
|
-
}
|
|
1594
|
-
function hasSpreadProperties(obj) {
|
|
1595
|
-
if (!obj || obj.type !== "ObjectExpression") return false;
|
|
1596
|
-
return (obj.properties ?? []).some(
|
|
1597
|
-
(p) => p && (p.type === "SpreadElement" || p.type === "SpreadProperty")
|
|
1598
|
-
);
|
|
1599
|
-
}
|
|
1600
|
-
var IGNORED_AST_KEYS = /* @__PURE__ */ new Set([
|
|
1601
|
-
"loc",
|
|
1602
|
-
"start",
|
|
1603
|
-
"end",
|
|
1604
|
-
"extra",
|
|
1605
|
-
"leadingComments",
|
|
1606
|
-
"trailingComments",
|
|
1607
|
-
"innerComments"
|
|
1608
|
-
]);
|
|
1609
|
-
function normalizeAstForCompare(node) {
|
|
1610
|
-
if (node === null) return null;
|
|
1611
|
-
if (node === void 0) return void 0;
|
|
1612
|
-
if (typeof node !== "object") return node;
|
|
1613
|
-
if (Array.isArray(node)) return node.map(normalizeAstForCompare);
|
|
1614
|
-
const out = {};
|
|
1615
|
-
const keys = Object.keys(node).filter((k) => !IGNORED_AST_KEYS.has(k)).sort();
|
|
1616
|
-
for (const k of keys) {
|
|
1617
|
-
if (k.startsWith("$")) continue;
|
|
1618
|
-
out[k] = normalizeAstForCompare(node[k]);
|
|
1619
|
-
}
|
|
1620
|
-
return out;
|
|
1621
|
-
}
|
|
1622
|
-
function astEquivalent(a, b) {
|
|
1623
|
-
try {
|
|
1624
|
-
return JSON.stringify(normalizeAstForCompare(a)) === JSON.stringify(normalizeAstForCompare(b));
|
|
1625
|
-
} catch {
|
|
1626
|
-
return false;
|
|
1627
|
-
}
|
|
1628
|
-
}
|
|
1629
|
-
function collectUilintRuleIdsFromRulesObject(rulesObj) {
|
|
1630
|
-
const ids = /* @__PURE__ */ new Set();
|
|
1631
|
-
if (!rulesObj || rulesObj.type !== "ObjectExpression") return ids;
|
|
1632
|
-
for (const prop of rulesObj.properties ?? []) {
|
|
1633
|
-
if (!prop) continue;
|
|
1634
|
-
if (prop.type !== "ObjectProperty" && prop.type !== "Property") continue;
|
|
1635
|
-
const key = prop.key;
|
|
1636
|
-
if (!isStringLiteral(key)) continue;
|
|
1637
|
-
const val = key.value;
|
|
1638
|
-
if (typeof val !== "string") continue;
|
|
1639
|
-
if (val.startsWith("uilint/")) {
|
|
1640
|
-
ids.add(val.slice("uilint/".length));
|
|
1641
|
-
}
|
|
1642
|
-
}
|
|
1643
|
-
return ids;
|
|
1644
|
-
}
|
|
1645
|
-
function findExportedConfigArrayExpression(mod) {
|
|
1646
|
-
function unwrapExpression2(expr) {
|
|
1647
|
-
let e = expr;
|
|
1648
|
-
while (e) {
|
|
1649
|
-
if (e.type === "TSAsExpression" || e.type === "TSNonNullExpression") {
|
|
1650
|
-
e = e.expression;
|
|
1651
|
-
continue;
|
|
1652
|
-
}
|
|
1653
|
-
if (e.type === "TSSatisfiesExpression") {
|
|
1654
|
-
e = e.expression;
|
|
1655
|
-
continue;
|
|
1656
|
-
}
|
|
1657
|
-
if (e.type === "ParenthesizedExpression") {
|
|
1658
|
-
e = e.expression;
|
|
1659
|
-
continue;
|
|
1660
|
-
}
|
|
1661
|
-
break;
|
|
1662
|
-
}
|
|
1663
|
-
return e;
|
|
1664
|
-
}
|
|
1665
|
-
function resolveTopLevelIdentifierToArrayExpr(program2, name) {
|
|
1666
|
-
if (!program2 || program2.type !== "Program") return null;
|
|
1667
|
-
for (const stmt of program2.body ?? []) {
|
|
1668
|
-
if (stmt?.type !== "VariableDeclaration") continue;
|
|
1669
|
-
for (const decl of stmt.declarations ?? []) {
|
|
1670
|
-
const id = decl?.id;
|
|
1671
|
-
if (!isIdentifier(id, name)) continue;
|
|
1672
|
-
const init = unwrapExpression2(decl?.init);
|
|
1673
|
-
if (!init) return null;
|
|
1674
|
-
if (init.type === "ArrayExpression") return init;
|
|
1675
|
-
if (init.type === "CallExpression" && isIdentifier(init.callee, "defineConfig") && unwrapExpression2(init.arguments?.[0])?.type === "ArrayExpression") {
|
|
1676
|
-
return unwrapExpression2(init.arguments?.[0]);
|
|
1677
|
-
}
|
|
1678
|
-
return null;
|
|
1679
|
-
}
|
|
1680
|
-
}
|
|
1681
|
-
return null;
|
|
1682
|
-
}
|
|
1683
|
-
const program = mod?.$ast;
|
|
1684
|
-
if (program && program.type === "Program") {
|
|
1685
|
-
for (const stmt of program.body ?? []) {
|
|
1686
|
-
if (!stmt || stmt.type !== "ExportDefaultDeclaration") continue;
|
|
1687
|
-
const decl = unwrapExpression2(stmt.declaration);
|
|
1688
|
-
if (!decl) break;
|
|
1689
|
-
if (decl.type === "ArrayExpression") {
|
|
1690
|
-
return { kind: "esm", arrayExpr: decl, program };
|
|
1691
|
-
}
|
|
1692
|
-
if (decl.type === "CallExpression" && isIdentifier(decl.callee, "defineConfig") && unwrapExpression2(decl.arguments?.[0])?.type === "ArrayExpression") {
|
|
1693
|
-
return {
|
|
1694
|
-
kind: "esm",
|
|
1695
|
-
arrayExpr: unwrapExpression2(decl.arguments?.[0]),
|
|
1696
|
-
program
|
|
1697
|
-
};
|
|
1698
|
-
}
|
|
1699
|
-
if (decl.type === "Identifier" && typeof decl.name === "string") {
|
|
1700
|
-
const resolved = resolveTopLevelIdentifierToArrayExpr(
|
|
1701
|
-
program,
|
|
1702
|
-
decl.name
|
|
1703
|
-
);
|
|
1704
|
-
if (resolved) return { kind: "esm", arrayExpr: resolved, program };
|
|
1705
|
-
}
|
|
1706
|
-
break;
|
|
1707
|
-
}
|
|
1708
|
-
}
|
|
1709
|
-
if (!program || program.type !== "Program") return null;
|
|
1710
|
-
for (const stmt of program.body ?? []) {
|
|
1711
|
-
if (!stmt || stmt.type !== "ExpressionStatement") continue;
|
|
1712
|
-
const expr = stmt.expression;
|
|
1713
|
-
if (!expr || expr.type !== "AssignmentExpression") continue;
|
|
1714
|
-
const left = expr.left;
|
|
1715
|
-
const right = expr.right;
|
|
1716
|
-
const isModuleExports = left?.type === "MemberExpression" && isIdentifier(left.object, "module") && isIdentifier(left.property, "exports");
|
|
1717
|
-
if (!isModuleExports) continue;
|
|
1718
|
-
if (right?.type === "ArrayExpression") {
|
|
1719
|
-
return { kind: "cjs", arrayExpr: right, program };
|
|
1720
|
-
}
|
|
1721
|
-
if (right?.type === "CallExpression" && isIdentifier(right.callee, "defineConfig") && right.arguments?.[0]?.type === "ArrayExpression") {
|
|
1722
|
-
return { kind: "cjs", arrayExpr: right.arguments[0], program };
|
|
1723
|
-
}
|
|
1724
|
-
if (right?.type === "Identifier" && typeof right.name === "string") {
|
|
1725
|
-
const resolved = resolveTopLevelIdentifierToArrayExpr(
|
|
1726
|
-
program,
|
|
1727
|
-
right.name
|
|
1728
|
-
);
|
|
1729
|
-
if (resolved) return { kind: "cjs", arrayExpr: resolved, program };
|
|
1730
|
-
}
|
|
1731
|
-
}
|
|
1732
|
-
return null;
|
|
1733
|
-
}
|
|
1734
|
-
function collectConfiguredUilintRuleIdsFromConfigArray(arrayExpr) {
|
|
1735
|
-
const ids = /* @__PURE__ */ new Set();
|
|
1736
|
-
if (!arrayExpr || arrayExpr.type !== "ArrayExpression") return ids;
|
|
1737
|
-
for (const el of arrayExpr.elements ?? []) {
|
|
1738
|
-
if (!el || el.type !== "ObjectExpression") continue;
|
|
1739
|
-
const rules = getObjectPropertyValue(el, "rules");
|
|
1740
|
-
for (const id of collectUilintRuleIdsFromRulesObject(rules)) ids.add(id);
|
|
1741
|
-
}
|
|
1742
|
-
return ids;
|
|
1743
|
-
}
|
|
1744
|
-
function findExistingUilintRulesObject(arrayExpr) {
|
|
1745
|
-
if (!arrayExpr || arrayExpr.type !== "ArrayExpression") {
|
|
1746
|
-
return { configObj: null, rulesObj: null, safeToMutate: false };
|
|
1747
|
-
}
|
|
1748
|
-
for (const el of arrayExpr.elements ?? []) {
|
|
1749
|
-
if (!el || el.type !== "ObjectExpression") continue;
|
|
1750
|
-
const plugins = getObjectPropertyValue(el, "plugins");
|
|
1751
|
-
const rules = getObjectPropertyValue(el, "rules");
|
|
1752
|
-
const hasUilintPlugin = plugins?.type === "ObjectExpression" && getObjectPropertyValue(plugins, "uilint") !== null;
|
|
1753
|
-
const uilintIds = collectUilintRuleIdsFromRulesObject(rules);
|
|
1754
|
-
const hasUilintRules = uilintIds.size > 0;
|
|
1755
|
-
if (!hasUilintPlugin && !hasUilintRules) continue;
|
|
1756
|
-
const safe = rules?.type === "ObjectExpression" && !hasSpreadProperties(rules);
|
|
1757
|
-
return { configObj: el, rulesObj: rules, safeToMutate: safe };
|
|
1758
|
-
}
|
|
1759
|
-
return { configObj: null, rulesObj: null, safeToMutate: false };
|
|
1760
|
-
}
|
|
1761
|
-
function collectTopLevelBindings(program) {
|
|
1762
|
-
const names = /* @__PURE__ */ new Set();
|
|
1763
|
-
if (!program || program.type !== "Program") return names;
|
|
1764
|
-
for (const stmt of program.body ?? []) {
|
|
1765
|
-
if (stmt?.type === "VariableDeclaration") {
|
|
1766
|
-
for (const decl of stmt.declarations ?? []) {
|
|
1767
|
-
const id = decl?.id;
|
|
1768
|
-
if (id?.type === "Identifier" && typeof id.name === "string") {
|
|
1769
|
-
names.add(id.name);
|
|
1770
|
-
}
|
|
1771
|
-
}
|
|
1772
|
-
} else if (stmt?.type === "FunctionDeclaration") {
|
|
1773
|
-
if (stmt.id?.type === "Identifier" && typeof stmt.id.name === "string") {
|
|
1774
|
-
names.add(stmt.id.name);
|
|
1775
|
-
}
|
|
1776
|
-
}
|
|
1777
|
-
}
|
|
1778
|
-
return names;
|
|
1779
|
-
}
|
|
1780
|
-
function chooseUniqueIdentifier(base, used) {
|
|
1781
|
-
if (!used.has(base)) return base;
|
|
1782
|
-
let i = 2;
|
|
1783
|
-
while (used.has(`${base}${i}`)) i++;
|
|
1784
|
-
return `${base}${i}`;
|
|
1785
|
-
}
|
|
1786
|
-
function addLocalRuleImportsAst(mod, selectedRules, configPath, rulesRoot, fileExtension = ".js") {
|
|
1787
|
-
const importNames = /* @__PURE__ */ new Map();
|
|
1788
|
-
let changed = false;
|
|
1789
|
-
const configDir = dirname2(configPath);
|
|
1790
|
-
const rulesDir = join4(rulesRoot, ".uilint", "rules");
|
|
1791
|
-
const relativeRulesPath = relative3(configDir, rulesDir).replace(/\\/g, "/");
|
|
1792
|
-
const normalizedRulesPath = relativeRulesPath.startsWith("./") || relativeRulesPath.startsWith("../") ? relativeRulesPath : `./${relativeRulesPath}`;
|
|
1793
|
-
const used = collectTopLevelBindings(mod.$ast);
|
|
1794
|
-
for (const rule of selectedRules) {
|
|
1795
|
-
const importName = chooseUniqueIdentifier(
|
|
1796
|
-
`${rule.id.replace(/-([a-z])/g, (_, c) => c.toUpperCase()).replace(/^./, (c) => c.toUpperCase())}Rule`,
|
|
1797
|
-
used
|
|
1798
|
-
);
|
|
1799
|
-
importNames.set(rule.id, importName);
|
|
1800
|
-
used.add(importName);
|
|
1801
|
-
const rulePath = `${normalizedRulesPath}/${rule.id}${fileExtension}`;
|
|
1802
|
-
mod.imports.$add({
|
|
1803
|
-
imported: "default",
|
|
1804
|
-
local: importName,
|
|
1805
|
-
from: rulePath
|
|
1806
|
-
});
|
|
1807
|
-
changed = true;
|
|
1808
|
-
}
|
|
1809
|
-
return { importNames, changed };
|
|
1810
|
-
}
|
|
1811
|
-
function addLocalRuleRequiresAst(program, selectedRules, configPath, rulesRoot, fileExtension = ".js") {
|
|
1812
|
-
const importNames = /* @__PURE__ */ new Map();
|
|
1813
|
-
let changed = false;
|
|
1814
|
-
if (!program || program.type !== "Program") {
|
|
1815
|
-
return { importNames, changed };
|
|
1816
|
-
}
|
|
1817
|
-
const configDir = dirname2(configPath);
|
|
1818
|
-
const rulesDir = join4(rulesRoot, ".uilint", "rules");
|
|
1819
|
-
const relativeRulesPath = relative3(configDir, rulesDir).replace(/\\/g, "/");
|
|
1820
|
-
const normalizedRulesPath = relativeRulesPath.startsWith("./") || relativeRulesPath.startsWith("../") ? relativeRulesPath : `./${relativeRulesPath}`;
|
|
1821
|
-
const used = collectTopLevelBindings(program);
|
|
1822
|
-
for (const rule of selectedRules) {
|
|
1823
|
-
const importName = chooseUniqueIdentifier(
|
|
1824
|
-
`${rule.id.replace(/-([a-z])/g, (_, c) => c.toUpperCase()).replace(/^./, (c) => c.toUpperCase())}Rule`,
|
|
1825
|
-
used
|
|
1826
|
-
);
|
|
1827
|
-
importNames.set(rule.id, importName);
|
|
1828
|
-
used.add(importName);
|
|
1829
|
-
const rulePath = `${normalizedRulesPath}/${rule.id}${fileExtension}`;
|
|
1830
|
-
const stmtMod = parseModule2(
|
|
1831
|
-
`const ${importName} = require("${rulePath}");`
|
|
1832
|
-
);
|
|
1833
|
-
const stmt = stmtMod.$ast.body?.[0];
|
|
1834
|
-
if (stmt) {
|
|
1835
|
-
let insertAt = 0;
|
|
1836
|
-
const first = program.body?.[0];
|
|
1837
|
-
if (first?.type === "ExpressionStatement" && first.expression?.type === "StringLiteral" && first.expression.value === "use strict") {
|
|
1838
|
-
insertAt = 1;
|
|
1839
|
-
}
|
|
1840
|
-
program.body.splice(insertAt, 0, stmt);
|
|
1841
|
-
changed = true;
|
|
1842
|
-
}
|
|
1843
|
-
}
|
|
1844
|
-
return { importNames, changed };
|
|
1845
|
-
}
|
|
1846
|
-
function appendUilintConfigBlockToArray(arrayExpr, selectedRules, ruleImportNames) {
|
|
1847
|
-
const pluginRulesCode = Array.from(ruleImportNames.entries()).map(([ruleId, importName]) => ` "${ruleId}": ${importName},`).join("\n");
|
|
1848
|
-
const rulesPropsCode = selectedRules.map((r) => {
|
|
1849
|
-
const ruleKey = `uilint/${r.id}`;
|
|
1850
|
-
const valueCode = r.defaultOptions && r.defaultOptions.length > 0 ? `["${r.defaultSeverity}", ...${JSON.stringify(
|
|
1851
|
-
r.defaultOptions,
|
|
1852
|
-
null,
|
|
1853
|
-
2
|
|
1854
|
-
)}]` : `"${r.defaultSeverity}"`;
|
|
1855
|
-
return ` "${ruleKey}": ${valueCode},`;
|
|
1856
|
-
}).join("\n");
|
|
1857
|
-
const blockCode = `{
|
|
1858
|
-
files: [
|
|
1859
|
-
"src/**/*.{js,jsx,ts,tsx}",
|
|
1860
|
-
"app/**/*.{js,jsx,ts,tsx}",
|
|
1861
|
-
"pages/**/*.{js,jsx,ts,tsx}",
|
|
1862
|
-
],
|
|
1863
|
-
plugins: {
|
|
1864
|
-
uilint: {
|
|
1865
|
-
rules: {
|
|
1866
|
-
${pluginRulesCode}
|
|
1867
|
-
},
|
|
1868
|
-
},
|
|
1869
|
-
},
|
|
1870
|
-
rules: {
|
|
1871
|
-
${rulesPropsCode}
|
|
1872
|
-
},
|
|
1873
|
-
}`;
|
|
1874
|
-
const objExpr = parseExpression(blockCode).$ast;
|
|
1875
|
-
arrayExpr.elements.push(objExpr);
|
|
1876
|
-
}
|
|
1877
|
-
function getUilintEslintConfigInfoFromSourceAst(source) {
|
|
1580
|
+
// src/commands/install/analyze.ts
|
|
1581
|
+
function safeParseJson(filePath) {
|
|
1878
1582
|
try {
|
|
1879
|
-
const
|
|
1880
|
-
|
|
1881
|
-
if (!found) {
|
|
1882
|
-
return {
|
|
1883
|
-
error: "Could not locate an exported ESLint flat config array (expected `export default [...]`, `export default defineConfig([...])`, `module.exports = [...]`, or `module.exports = defineConfig([...])`)."
|
|
1884
|
-
};
|
|
1885
|
-
}
|
|
1886
|
-
const configuredRuleIds = collectConfiguredUilintRuleIdsFromConfigArray(
|
|
1887
|
-
found.arrayExpr
|
|
1888
|
-
);
|
|
1889
|
-
const existingUilint = findExistingUilintRulesObject(found.arrayExpr);
|
|
1890
|
-
const configured = configuredRuleIds.size > 0 || existingUilint.configObj !== null;
|
|
1891
|
-
return {
|
|
1892
|
-
info: { configuredRuleIds, configured },
|
|
1893
|
-
mod,
|
|
1894
|
-
arrayExpr: found.arrayExpr,
|
|
1895
|
-
kind: found.kind
|
|
1896
|
-
};
|
|
1583
|
+
const content = readFileSync4(filePath, "utf-8");
|
|
1584
|
+
return JSON.parse(content);
|
|
1897
1585
|
} catch {
|
|
1898
|
-
return
|
|
1899
|
-
error: "Unable to parse ESLint config as JavaScript. Please update it manually or simplify the config so it can be safely auto-modified."
|
|
1900
|
-
};
|
|
1901
|
-
}
|
|
1902
|
-
}
|
|
1903
|
-
function getUilintEslintConfigInfoFromSource(source) {
|
|
1904
|
-
const ast = getUilintEslintConfigInfoFromSourceAst(source);
|
|
1905
|
-
if ("error" in ast) {
|
|
1906
|
-
const configuredRuleIds = extractConfiguredUilintRuleIds(source);
|
|
1907
|
-
return {
|
|
1908
|
-
configuredRuleIds,
|
|
1909
|
-
configured: configuredRuleIds.size > 0
|
|
1910
|
-
};
|
|
1911
|
-
}
|
|
1912
|
-
return ast.info;
|
|
1913
|
-
}
|
|
1914
|
-
function extractConfiguredUilintRuleIds(source) {
|
|
1915
|
-
const ids = /* @__PURE__ */ new Set();
|
|
1916
|
-
const re = /["']uilint\/([^"']+)["']\s*:/g;
|
|
1917
|
-
for (const m of source.matchAll(re)) {
|
|
1918
|
-
if (m[1]) ids.add(m[1]);
|
|
1919
|
-
}
|
|
1920
|
-
return ids;
|
|
1921
|
-
}
|
|
1922
|
-
function getMissingSelectedRules(selectedRules, configuredIds) {
|
|
1923
|
-
return selectedRules.filter((r) => !configuredIds.has(r.id));
|
|
1924
|
-
}
|
|
1925
|
-
function buildDesiredRuleValueExpression(rule) {
|
|
1926
|
-
if (rule.defaultOptions && rule.defaultOptions.length > 0) {
|
|
1927
|
-
return `["${rule.defaultSeverity}", ...${JSON.stringify(
|
|
1928
|
-
rule.defaultOptions,
|
|
1929
|
-
null,
|
|
1930
|
-
2
|
|
1931
|
-
)}]`;
|
|
1932
|
-
}
|
|
1933
|
-
return `"${rule.defaultSeverity}"`;
|
|
1934
|
-
}
|
|
1935
|
-
function collectUilintRuleValueNodesFromConfigArray(arrayExpr) {
|
|
1936
|
-
const out = /* @__PURE__ */ new Map();
|
|
1937
|
-
if (!arrayExpr || arrayExpr.type !== "ArrayExpression") return out;
|
|
1938
|
-
for (const el of arrayExpr.elements ?? []) {
|
|
1939
|
-
if (!el || el.type !== "ObjectExpression") continue;
|
|
1940
|
-
const rules = getObjectPropertyValue(el, "rules");
|
|
1941
|
-
if (!rules || rules.type !== "ObjectExpression") continue;
|
|
1942
|
-
for (const prop of rules.properties ?? []) {
|
|
1943
|
-
if (!prop) continue;
|
|
1944
|
-
if (prop.type !== "ObjectProperty" && prop.type !== "Property") continue;
|
|
1945
|
-
const key = prop.key;
|
|
1946
|
-
if (!isStringLiteral(key)) continue;
|
|
1947
|
-
const k = key.value;
|
|
1948
|
-
if (typeof k !== "string" || !k.startsWith("uilint/")) continue;
|
|
1949
|
-
const id = k.slice("uilint/".length);
|
|
1950
|
-
if (!out.has(id)) out.set(id, prop.value);
|
|
1951
|
-
}
|
|
1952
|
-
}
|
|
1953
|
-
return out;
|
|
1954
|
-
}
|
|
1955
|
-
function getRulesNeedingUpdate(selectedRules, configuredIds, arrayExpr) {
|
|
1956
|
-
const existingVals = collectUilintRuleValueNodesFromConfigArray(arrayExpr);
|
|
1957
|
-
return selectedRules.filter((r) => {
|
|
1958
|
-
if (!configuredIds.has(r.id)) return false;
|
|
1959
|
-
const existing = existingVals.get(r.id);
|
|
1960
|
-
if (!existing) return true;
|
|
1961
|
-
const desiredExpr = buildDesiredRuleValueExpression(r);
|
|
1962
|
-
const desiredAst = parseExpression(desiredExpr).$ast;
|
|
1963
|
-
return !astEquivalent(existing, desiredAst);
|
|
1964
|
-
});
|
|
1965
|
-
}
|
|
1966
|
-
async function installEslintPlugin(opts) {
|
|
1967
|
-
const configPath = findEslintConfigFile(opts.projectPath);
|
|
1968
|
-
if (!configPath) {
|
|
1969
|
-
return {
|
|
1970
|
-
configFile: null,
|
|
1971
|
-
modified: false,
|
|
1972
|
-
missingRuleIds: [],
|
|
1973
|
-
configured: false
|
|
1974
|
-
};
|
|
1975
|
-
}
|
|
1976
|
-
const configFilename = getEslintConfigFilename(configPath);
|
|
1977
|
-
const original = readFileSync4(configPath, "utf-8");
|
|
1978
|
-
const isCommonJS = configPath.endsWith(".cjs");
|
|
1979
|
-
const ast = getUilintEslintConfigInfoFromSourceAst(original);
|
|
1980
|
-
if ("error" in ast) {
|
|
1981
|
-
return {
|
|
1982
|
-
configFile: configFilename,
|
|
1983
|
-
modified: false,
|
|
1984
|
-
missingRuleIds: [],
|
|
1985
|
-
configured: false,
|
|
1986
|
-
error: ast.error
|
|
1987
|
-
};
|
|
1988
|
-
}
|
|
1989
|
-
const { info, mod, arrayExpr, kind } = ast;
|
|
1990
|
-
const configuredIds = info.configuredRuleIds;
|
|
1991
|
-
const missingRules = getMissingSelectedRules(
|
|
1992
|
-
opts.selectedRules,
|
|
1993
|
-
configuredIds
|
|
1994
|
-
);
|
|
1995
|
-
const rulesToUpdate = getRulesNeedingUpdate(
|
|
1996
|
-
opts.selectedRules,
|
|
1997
|
-
configuredIds,
|
|
1998
|
-
arrayExpr
|
|
1999
|
-
);
|
|
2000
|
-
let rulesToApply = [];
|
|
2001
|
-
if (!info.configured) {
|
|
2002
|
-
rulesToApply = opts.selectedRules;
|
|
2003
|
-
} else {
|
|
2004
|
-
rulesToApply = [...missingRules, ...rulesToUpdate];
|
|
2005
|
-
if (missingRules.length > 0 && !opts.force) {
|
|
2006
|
-
const ok = await opts.confirmAddMissingRules?.(
|
|
2007
|
-
configFilename,
|
|
2008
|
-
missingRules
|
|
2009
|
-
);
|
|
2010
|
-
if (!ok) {
|
|
2011
|
-
return {
|
|
2012
|
-
configFile: configFilename,
|
|
2013
|
-
modified: false,
|
|
2014
|
-
missingRuleIds: missingRules.map((r) => r.id),
|
|
2015
|
-
configured: true
|
|
2016
|
-
};
|
|
2017
|
-
}
|
|
2018
|
-
}
|
|
2019
|
-
}
|
|
2020
|
-
if (rulesToApply.length === 0) {
|
|
2021
|
-
return {
|
|
2022
|
-
configFile: configFilename,
|
|
2023
|
-
modified: false,
|
|
2024
|
-
missingRuleIds: missingRules.map((r) => r.id),
|
|
2025
|
-
configured: info.configured
|
|
2026
|
-
};
|
|
2027
|
-
}
|
|
2028
|
-
let modifiedAst = false;
|
|
2029
|
-
const localRulesDir = join4(opts.projectPath, ".uilint", "rules");
|
|
2030
|
-
const workspaceRoot = findWorkspaceRoot(opts.projectPath);
|
|
2031
|
-
const workspaceRulesDir = join4(workspaceRoot, ".uilint", "rules");
|
|
2032
|
-
const rulesRoot = existsSync4(localRulesDir) ? opts.projectPath : workspaceRoot;
|
|
2033
|
-
const isTypeScriptConfig = configPath.endsWith(".ts");
|
|
2034
|
-
let fileExtension = isTypeScriptConfig ? "" : ".js";
|
|
2035
|
-
let ruleImportNames;
|
|
2036
|
-
if (kind === "esm") {
|
|
2037
|
-
const result = addLocalRuleImportsAst(
|
|
2038
|
-
mod,
|
|
2039
|
-
rulesToApply,
|
|
2040
|
-
configPath,
|
|
2041
|
-
rulesRoot,
|
|
2042
|
-
fileExtension
|
|
2043
|
-
);
|
|
2044
|
-
ruleImportNames = result.importNames;
|
|
2045
|
-
if (result.changed) modifiedAst = true;
|
|
2046
|
-
} else {
|
|
2047
|
-
const result = addLocalRuleRequiresAst(
|
|
2048
|
-
mod.$ast,
|
|
2049
|
-
rulesToApply,
|
|
2050
|
-
configPath,
|
|
2051
|
-
rulesRoot,
|
|
2052
|
-
fileExtension
|
|
2053
|
-
);
|
|
2054
|
-
ruleImportNames = result.importNames;
|
|
2055
|
-
if (result.changed) modifiedAst = true;
|
|
2056
|
-
}
|
|
2057
|
-
if (ruleImportNames && ruleImportNames.size > 0) {
|
|
2058
|
-
appendUilintConfigBlockToArray(arrayExpr, rulesToApply, ruleImportNames);
|
|
2059
|
-
modifiedAst = true;
|
|
2060
|
-
}
|
|
2061
|
-
if (!info.configured) {
|
|
2062
|
-
if (kind === "esm") {
|
|
2063
|
-
mod.imports.$add({
|
|
2064
|
-
imported: "createRule",
|
|
2065
|
-
local: "createRule",
|
|
2066
|
-
from: "uilint-eslint"
|
|
2067
|
-
});
|
|
2068
|
-
modifiedAst = true;
|
|
2069
|
-
} else {
|
|
2070
|
-
const stmtMod = parseModule2(
|
|
2071
|
-
`const { createRule } = require("uilint-eslint");`
|
|
2072
|
-
);
|
|
2073
|
-
const stmt = stmtMod.$ast.body?.[0];
|
|
2074
|
-
if (stmt) {
|
|
2075
|
-
let insertAt = 0;
|
|
2076
|
-
const first = mod.$ast.body?.[0];
|
|
2077
|
-
if (first?.type === "ExpressionStatement" && first.expression?.type === "StringLiteral" && first.expression.value === "use strict") {
|
|
2078
|
-
insertAt = 1;
|
|
2079
|
-
}
|
|
2080
|
-
mod.$ast.body.splice(insertAt, 0, stmt);
|
|
2081
|
-
modifiedAst = true;
|
|
2082
|
-
}
|
|
2083
|
-
}
|
|
2084
|
-
}
|
|
2085
|
-
const updated = modifiedAst ? generateCode(mod).code : original;
|
|
2086
|
-
if (updated !== original) {
|
|
2087
|
-
writeFileSync(configPath, updated, "utf-8");
|
|
2088
|
-
return {
|
|
2089
|
-
configFile: configFilename,
|
|
2090
|
-
modified: true,
|
|
2091
|
-
missingRuleIds: missingRules.map((r) => r.id),
|
|
2092
|
-
configured: getUilintEslintConfigInfoFromSource(updated).configured
|
|
2093
|
-
};
|
|
1586
|
+
return void 0;
|
|
2094
1587
|
}
|
|
2095
|
-
return {
|
|
2096
|
-
configFile: configFilename,
|
|
2097
|
-
modified: false,
|
|
2098
|
-
missingRuleIds: missingRules.map((r) => r.id),
|
|
2099
|
-
configured: getUilintEslintConfigInfoFromSource(updated).configured
|
|
2100
|
-
};
|
|
2101
1588
|
}
|
|
2102
|
-
|
|
2103
|
-
const
|
|
2104
|
-
const
|
|
2105
|
-
if (!
|
|
2106
|
-
|
|
2107
|
-
success: true,
|
|
2108
|
-
// Nothing to uninstall
|
|
2109
|
-
modifiedFiles: []
|
|
2110
|
-
};
|
|
2111
|
-
}
|
|
2112
|
-
try {
|
|
2113
|
-
const original = readFileSync4(configPath, "utf-8");
|
|
2114
|
-
let updated = original.replace(
|
|
2115
|
-
/^import\s+\{[^}]*\}\s+from\s+["'][^"']*\.uilint\/rules[^"']*["'];?\s*$/gm,
|
|
2116
|
-
""
|
|
2117
|
-
);
|
|
2118
|
-
updated = updated.replace(
|
|
2119
|
-
/^import\s+\w+\s+from\s+["'][^"']*\.uilint\/rules[^"']*["'];?\s*$/gm,
|
|
2120
|
-
""
|
|
2121
|
-
);
|
|
2122
|
-
updated = updated.replace(
|
|
2123
|
-
/^import\s+\{[^}]*\}\s+from\s+["']uilint-eslint["'];?\s*$/gm,
|
|
2124
|
-
""
|
|
2125
|
-
);
|
|
2126
|
-
updated = updated.replace(
|
|
2127
|
-
/^const\s+\{[^}]*createRule[^}]*\}\s*=\s*require\s*\(\s*["']uilint-eslint["']\s*\)\s*;?\s*$/gm,
|
|
2128
|
-
""
|
|
2129
|
-
);
|
|
2130
|
-
updated = updated.replace(
|
|
2131
|
-
/["']uilint\/[^"']+["']\s*:\s*["'][^"']+["']\s*,?\s*/g,
|
|
2132
|
-
""
|
|
2133
|
-
);
|
|
2134
|
-
updated = updated.replace(
|
|
2135
|
-
/["']uilint\/[^"']+["']\s*:\s*\[[^\]]*\]\s*,?\s*/g,
|
|
2136
|
-
""
|
|
2137
|
-
);
|
|
2138
|
-
updated = updated.replace(
|
|
2139
|
-
/\{\s*plugins:\s*\{\s*uilint:\s*\{[^}]*\}[^}]*\}[^}]*rules:\s*\{[^}]*\}[^}]*\}\s*,?\s*/gs,
|
|
2140
|
-
""
|
|
2141
|
-
);
|
|
2142
|
-
updated = updated.replace(/\n{3,}/g, "\n\n");
|
|
2143
|
-
if (updated !== original) {
|
|
2144
|
-
writeFileSync(configPath, updated, "utf-8");
|
|
2145
|
-
return {
|
|
2146
|
-
success: true,
|
|
2147
|
-
modifiedFiles: [configPath]
|
|
2148
|
-
};
|
|
2149
|
-
}
|
|
2150
|
-
return {
|
|
2151
|
-
success: true,
|
|
2152
|
-
modifiedFiles: []
|
|
2153
|
-
};
|
|
2154
|
-
} catch (error) {
|
|
2155
|
-
return {
|
|
2156
|
-
success: false,
|
|
2157
|
-
error: error instanceof Error ? error.message : String(error)
|
|
2158
|
-
};
|
|
2159
|
-
}
|
|
1589
|
+
function hasUilintOverlayInstalled(projectPath) {
|
|
1590
|
+
const pkgPath = join4(projectPath, "package.json");
|
|
1591
|
+
const pkg = safeParseJson(pkgPath);
|
|
1592
|
+
if (!pkg) return false;
|
|
1593
|
+
return !!(pkg.dependencies?.["uilint-react"] || pkg.devDependencies?.["uilint-react"]);
|
|
2160
1594
|
}
|
|
2161
|
-
|
|
2162
|
-
// src/commands/install/analyze.ts
|
|
2163
1595
|
async function analyze(projectPath = process.cwd()) {
|
|
2164
|
-
const workspaceRoot =
|
|
1596
|
+
const workspaceRoot = findWorkspaceRoot(projectPath);
|
|
2165
1597
|
const packageManager = detectPackageManager(projectPath);
|
|
2166
|
-
const cursorDir =
|
|
2167
|
-
const cursorDirExists =
|
|
2168
|
-
const styleguidePath =
|
|
2169
|
-
const styleguideExists =
|
|
2170
|
-
const commandsDir =
|
|
2171
|
-
const genstyleguideExists =
|
|
1598
|
+
const cursorDir = join4(projectPath, ".cursor");
|
|
1599
|
+
const cursorDirExists = existsSync4(cursorDir);
|
|
1600
|
+
const styleguidePath = join4(projectPath, ".uilint", "styleguide.md");
|
|
1601
|
+
const styleguideExists = existsSync4(styleguidePath);
|
|
1602
|
+
const commandsDir = join4(cursorDir, "commands");
|
|
1603
|
+
const genstyleguideExists = existsSync4(join4(commandsDir, "genstyleguide.md"));
|
|
2172
1604
|
const nextApps = [];
|
|
2173
1605
|
const directDetection = detectNextAppRouter(projectPath);
|
|
2174
1606
|
if (directDetection) {
|
|
2175
|
-
nextApps.push({
|
|
1607
|
+
nextApps.push({
|
|
1608
|
+
projectPath,
|
|
1609
|
+
detection: directDetection,
|
|
1610
|
+
hasUilintOverlay: hasUilintOverlayInstalled(projectPath)
|
|
1611
|
+
});
|
|
2176
1612
|
} else {
|
|
2177
1613
|
const matches = findNextAppRouterProjects(workspaceRoot, { maxDepth: 5 });
|
|
2178
1614
|
for (const match of matches) {
|
|
2179
1615
|
nextApps.push({
|
|
2180
1616
|
projectPath: match.projectPath,
|
|
2181
|
-
detection: match.detection
|
|
1617
|
+
detection: match.detection,
|
|
1618
|
+
hasUilintOverlay: hasUilintOverlayInstalled(match.projectPath)
|
|
2182
1619
|
});
|
|
2183
1620
|
}
|
|
2184
1621
|
}
|
|
2185
1622
|
const viteApps = [];
|
|
2186
1623
|
const directVite = detectViteReact(projectPath);
|
|
2187
1624
|
if (directVite) {
|
|
2188
|
-
viteApps.push({
|
|
1625
|
+
viteApps.push({
|
|
1626
|
+
projectPath,
|
|
1627
|
+
detection: directVite,
|
|
1628
|
+
hasUilintOverlay: hasUilintOverlayInstalled(projectPath)
|
|
1629
|
+
});
|
|
2189
1630
|
} else {
|
|
2190
1631
|
const matches = findViteReactProjects(workspaceRoot, { maxDepth: 5 });
|
|
2191
1632
|
for (const match of matches) {
|
|
2192
1633
|
viteApps.push({
|
|
2193
1634
|
projectPath: match.projectPath,
|
|
2194
|
-
detection: match.detection
|
|
1635
|
+
detection: match.detection,
|
|
1636
|
+
hasUilintOverlay: hasUilintOverlayInstalled(match.projectPath)
|
|
2195
1637
|
});
|
|
2196
1638
|
}
|
|
2197
1639
|
}
|
|
@@ -2204,7 +1646,7 @@ async function analyze(projectPath = process.cwd()) {
|
|
|
2204
1646
|
if (eslintConfigPath) {
|
|
2205
1647
|
eslintConfigFilename = getEslintConfigFilename(eslintConfigPath);
|
|
2206
1648
|
try {
|
|
2207
|
-
const source =
|
|
1649
|
+
const source = readFileSync4(eslintConfigPath, "utf-8");
|
|
2208
1650
|
const info = getUilintEslintConfigInfoFromSource(source);
|
|
2209
1651
|
hasRules = info.configuredRuleIds.size > 0;
|
|
2210
1652
|
configuredRuleIds = Array.from(info.configuredRuleIds);
|
|
@@ -2242,50 +1684,50 @@ async function analyze(projectPath = process.cwd()) {
|
|
|
2242
1684
|
|
|
2243
1685
|
// src/commands/install/execute.ts
|
|
2244
1686
|
import {
|
|
2245
|
-
existsSync as
|
|
1687
|
+
existsSync as existsSync10,
|
|
2246
1688
|
mkdirSync,
|
|
2247
|
-
writeFileSync as
|
|
2248
|
-
readFileSync as
|
|
1689
|
+
writeFileSync as writeFileSync4,
|
|
1690
|
+
readFileSync as readFileSync8,
|
|
2249
1691
|
unlinkSync,
|
|
2250
1692
|
chmodSync,
|
|
2251
1693
|
rmSync
|
|
2252
1694
|
} from "fs";
|
|
2253
|
-
import { dirname as
|
|
1695
|
+
import { dirname as dirname4, join as join10 } from "path";
|
|
2254
1696
|
|
|
2255
1697
|
// src/utils/react-inject.ts
|
|
2256
|
-
import { existsSync as
|
|
2257
|
-
import { join as
|
|
2258
|
-
import { parseModule as
|
|
1698
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync } from "fs";
|
|
1699
|
+
import { join as join5, relative as relative3 } from "path";
|
|
1700
|
+
import { parseModule as parseModule2, generateCode } from "magicast";
|
|
2259
1701
|
function getDefaultCandidates(projectPath, appRoot) {
|
|
2260
1702
|
const viteMainCandidates = [
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
1703
|
+
join5(appRoot, "main.tsx"),
|
|
1704
|
+
join5(appRoot, "main.jsx"),
|
|
1705
|
+
join5(appRoot, "main.ts"),
|
|
1706
|
+
join5(appRoot, "main.js")
|
|
2265
1707
|
];
|
|
2266
1708
|
const existingViteMain = viteMainCandidates.filter(
|
|
2267
|
-
(rel) =>
|
|
1709
|
+
(rel) => existsSync5(join5(projectPath, rel))
|
|
2268
1710
|
);
|
|
2269
1711
|
if (existingViteMain.length > 0) return existingViteMain;
|
|
2270
|
-
const viteAppCandidates = [
|
|
1712
|
+
const viteAppCandidates = [join5(appRoot, "App.tsx"), join5(appRoot, "App.jsx")];
|
|
2271
1713
|
const existingViteApp = viteAppCandidates.filter(
|
|
2272
|
-
(rel) =>
|
|
1714
|
+
(rel) => existsSync5(join5(projectPath, rel))
|
|
2273
1715
|
);
|
|
2274
1716
|
if (existingViteApp.length > 0) return existingViteApp;
|
|
2275
1717
|
const layoutCandidates = [
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
1718
|
+
join5(appRoot, "layout.tsx"),
|
|
1719
|
+
join5(appRoot, "layout.jsx"),
|
|
1720
|
+
join5(appRoot, "layout.ts"),
|
|
1721
|
+
join5(appRoot, "layout.js")
|
|
2280
1722
|
];
|
|
2281
1723
|
const existingLayouts = layoutCandidates.filter(
|
|
2282
|
-
(rel) =>
|
|
1724
|
+
(rel) => existsSync5(join5(projectPath, rel))
|
|
2283
1725
|
);
|
|
2284
1726
|
if (existingLayouts.length > 0) {
|
|
2285
1727
|
return existingLayouts;
|
|
2286
1728
|
}
|
|
2287
|
-
const pageCandidates = [
|
|
2288
|
-
return pageCandidates.filter((rel) =>
|
|
1729
|
+
const pageCandidates = [join5(appRoot, "page.tsx"), join5(appRoot, "page.jsx")];
|
|
1730
|
+
return pageCandidates.filter((rel) => existsSync5(join5(projectPath, rel)));
|
|
2289
1731
|
}
|
|
2290
1732
|
function isUseClientDirective(stmt) {
|
|
2291
1733
|
return stmt?.type === "ExpressionStatement" && stmt.expression?.type === "StringLiteral" && stmt.expression.value === "use client";
|
|
@@ -2319,12 +1761,12 @@ function ensureNamedImport(program, from, name) {
|
|
|
2319
1761
|
(s) => s?.type === "ImportSpecifier" && (s.imported?.name === name || s.imported?.value === name)
|
|
2320
1762
|
);
|
|
2321
1763
|
if (has) return { changed: false };
|
|
2322
|
-
const spec =
|
|
1764
|
+
const spec = parseModule2(`import { ${name} } from "${from}";`).$ast.body?.[0]?.specifiers?.[0];
|
|
2323
1765
|
if (!spec) return { changed: false };
|
|
2324
1766
|
existing.specifiers = [...existing.specifiers ?? [], spec];
|
|
2325
1767
|
return { changed: true };
|
|
2326
1768
|
}
|
|
2327
|
-
const importDecl =
|
|
1769
|
+
const importDecl = parseModule2(`import { ${name} } from "${from}";`).$ast.body?.[0];
|
|
2328
1770
|
if (!importDecl) return { changed: false };
|
|
2329
1771
|
const body = program.body ?? [];
|
|
2330
1772
|
let insertAt = 0;
|
|
@@ -2354,7 +1796,7 @@ function hasUILintDevtoolsJsx(program) {
|
|
|
2354
1796
|
function addDevtoolsElementNextJs(program) {
|
|
2355
1797
|
if (!program || program.type !== "Program") return { changed: false };
|
|
2356
1798
|
if (hasUILintDevtoolsJsx(program)) return { changed: false };
|
|
2357
|
-
const devtoolsMod =
|
|
1799
|
+
const devtoolsMod = parseModule2(
|
|
2358
1800
|
"const __uilint_devtools = <uilint-devtools />;"
|
|
2359
1801
|
);
|
|
2360
1802
|
const devtoolsJsx = devtoolsMod.$ast.body?.[0]?.declarations?.[0]?.init ?? null;
|
|
@@ -2392,12 +1834,12 @@ function addDevtoolsElementVite(program) {
|
|
|
2392
1834
|
const arg0 = node.arguments?.[0];
|
|
2393
1835
|
if (!arg0) return;
|
|
2394
1836
|
if (arg0.type !== "JSXElement" && arg0.type !== "JSXFragment") return;
|
|
2395
|
-
const devtoolsMod =
|
|
1837
|
+
const devtoolsMod = parseModule2(
|
|
2396
1838
|
"const __uilint_devtools = <uilint-devtools />;"
|
|
2397
1839
|
);
|
|
2398
1840
|
const devtoolsJsx = devtoolsMod.$ast.body?.[0]?.declarations?.[0]?.init ?? null;
|
|
2399
1841
|
if (!devtoolsJsx) return;
|
|
2400
|
-
const fragmentMod =
|
|
1842
|
+
const fragmentMod = parseModule2(
|
|
2401
1843
|
"const __fragment = <></>;"
|
|
2402
1844
|
);
|
|
2403
1845
|
const fragmentJsx = fragmentMod.$ast.body?.[0]?.declarations?.[0]?.init ?? null;
|
|
@@ -2417,7 +1859,7 @@ function ensureSideEffectImport(program, from) {
|
|
|
2417
1859
|
if (!program || program.type !== "Program") return { changed: false };
|
|
2418
1860
|
const existing = findImportDeclaration(program, from);
|
|
2419
1861
|
if (existing) return { changed: false };
|
|
2420
|
-
const importDecl =
|
|
1862
|
+
const importDecl = parseModule2(`import "${from}";`).$ast.body?.[0];
|
|
2421
1863
|
if (!importDecl) return { changed: false };
|
|
2422
1864
|
const body = program.body ?? [];
|
|
2423
1865
|
let insertAt = 0;
|
|
@@ -2433,7 +1875,7 @@ function ensureSideEffectImport(program, from) {
|
|
|
2433
1875
|
function addDevtoolsToClientComponent(program) {
|
|
2434
1876
|
if (!program || program.type !== "Program") return { changed: false };
|
|
2435
1877
|
if (hasUILintDevtoolsJsx(program)) return { changed: false };
|
|
2436
|
-
const devtoolsMod =
|
|
1878
|
+
const devtoolsMod = parseModule2(
|
|
2437
1879
|
"const __uilint_devtools = <uilint-devtools />;"
|
|
2438
1880
|
);
|
|
2439
1881
|
const devtoolsJsx = devtoolsMod.$ast.body?.[0]?.declarations?.[0]?.init ?? null;
|
|
@@ -2459,7 +1901,7 @@ function addDevtoolsToClientComponent(program) {
|
|
|
2459
1901
|
const arg = node.argument;
|
|
2460
1902
|
if (!arg) return;
|
|
2461
1903
|
if (arg.type !== "JSXElement" && arg.type !== "JSXFragment") return;
|
|
2462
|
-
const fragmentMod =
|
|
1904
|
+
const fragmentMod = parseModule2("const __fragment = <></>;");
|
|
2463
1905
|
const fragmentJsx = fragmentMod.$ast.body?.[0]?.declarations?.[0]?.init ?? null;
|
|
2464
1906
|
if (!fragmentJsx) return;
|
|
2465
1907
|
fragmentJsx.children = [arg, devtoolsJsx];
|
|
@@ -2514,7 +1956,7 @@ function wrapChildrenWithProviders(program, providersImportPath) {
|
|
|
2514
1956
|
(child) => child?.type === "JSXExpressionContainer" && child.expression?.type === "Identifier" && child.expression.name === "children"
|
|
2515
1957
|
);
|
|
2516
1958
|
if (childrenIndex === -1) return;
|
|
2517
|
-
const providersMod =
|
|
1959
|
+
const providersMod = parseModule2(
|
|
2518
1960
|
"const __providers = <Providers>{children}</Providers>;"
|
|
2519
1961
|
);
|
|
2520
1962
|
const providersJsx = providersMod.$ast.body?.[0]?.declarations?.[0]?.init ?? null;
|
|
@@ -2532,8 +1974,8 @@ function wrapChildrenWithProviders(program, providersImportPath) {
|
|
|
2532
1974
|
function findLayoutFile2(projectPath, appRoot) {
|
|
2533
1975
|
const extensions = [".tsx", ".jsx", ".ts", ".js"];
|
|
2534
1976
|
for (const ext of extensions) {
|
|
2535
|
-
const layoutPath =
|
|
2536
|
-
if (
|
|
1977
|
+
const layoutPath = join5(projectPath, appRoot, `layout${ext}`);
|
|
1978
|
+
if (existsSync5(layoutPath)) return layoutPath;
|
|
2537
1979
|
}
|
|
2538
1980
|
return null;
|
|
2539
1981
|
}
|
|
@@ -2544,32 +1986,32 @@ async function createProvidersAndModifyLayout(projectPath, appRoot) {
|
|
|
2544
1986
|
}
|
|
2545
1987
|
const isTypeScript = layoutPath.endsWith(".tsx") || layoutPath.endsWith(".ts");
|
|
2546
1988
|
const providersExt = isTypeScript ? ".tsx" : ".jsx";
|
|
2547
|
-
const providersPath =
|
|
2548
|
-
if (
|
|
1989
|
+
const providersPath = join5(projectPath, appRoot, `providers${providersExt}`);
|
|
1990
|
+
if (existsSync5(providersPath)) {
|
|
2549
1991
|
throw new Error(
|
|
2550
1992
|
`providers${providersExt} already exists. Please select it from the list instead.`
|
|
2551
1993
|
);
|
|
2552
1994
|
}
|
|
2553
1995
|
const providersContent = generateProvidersContent(isTypeScript);
|
|
2554
|
-
|
|
2555
|
-
const layoutContent =
|
|
1996
|
+
writeFileSync(providersPath, providersContent, "utf-8");
|
|
1997
|
+
const layoutContent = readFileSync5(layoutPath, "utf-8");
|
|
2556
1998
|
let layoutMod;
|
|
2557
1999
|
try {
|
|
2558
|
-
layoutMod =
|
|
2000
|
+
layoutMod = parseModule2(layoutContent);
|
|
2559
2001
|
} catch {
|
|
2560
2002
|
throw new Error(
|
|
2561
|
-
`Unable to parse ${
|
|
2003
|
+
`Unable to parse ${relative3(projectPath, layoutPath)} as JavaScript/TypeScript.`
|
|
2562
2004
|
);
|
|
2563
2005
|
}
|
|
2564
2006
|
const layoutProgram = layoutMod.$ast;
|
|
2565
2007
|
const wrapRes = wrapChildrenWithProviders(layoutProgram, "./providers");
|
|
2566
2008
|
if (wrapRes.changed) {
|
|
2567
|
-
const updatedLayout =
|
|
2568
|
-
|
|
2009
|
+
const updatedLayout = generateCode(layoutMod).code;
|
|
2010
|
+
writeFileSync(layoutPath, updatedLayout, "utf-8");
|
|
2569
2011
|
}
|
|
2570
2012
|
return {
|
|
2571
|
-
providersFile:
|
|
2572
|
-
layoutFile:
|
|
2013
|
+
providersFile: relative3(projectPath, providersPath),
|
|
2014
|
+
layoutFile: relative3(projectPath, layoutPath),
|
|
2573
2015
|
modified: true
|
|
2574
2016
|
};
|
|
2575
2017
|
}
|
|
@@ -2581,8 +2023,8 @@ async function installReactUILintOverlay(opts) {
|
|
|
2581
2023
|
);
|
|
2582
2024
|
const modifiedFiles = [];
|
|
2583
2025
|
if (result.modified) {
|
|
2584
|
-
modifiedFiles.push(
|
|
2585
|
-
modifiedFiles.push(
|
|
2026
|
+
modifiedFiles.push(join5(opts.projectPath, result.providersFile));
|
|
2027
|
+
modifiedFiles.push(join5(opts.projectPath, result.layoutFile));
|
|
2586
2028
|
}
|
|
2587
2029
|
return {
|
|
2588
2030
|
targetFile: result.providersFile,
|
|
@@ -2594,14 +2036,14 @@ async function installReactUILintOverlay(opts) {
|
|
|
2594
2036
|
}
|
|
2595
2037
|
if (opts.targetFile) {
|
|
2596
2038
|
const absTarget2 = opts.targetFile;
|
|
2597
|
-
const relTarget =
|
|
2598
|
-
if (!
|
|
2039
|
+
const relTarget = relative3(opts.projectPath, absTarget2);
|
|
2040
|
+
if (!existsSync5(absTarget2)) {
|
|
2599
2041
|
throw new Error(`Target file not found: ${relTarget}`);
|
|
2600
2042
|
}
|
|
2601
|
-
const original2 =
|
|
2043
|
+
const original2 = readFileSync5(absTarget2, "utf-8");
|
|
2602
2044
|
let mod2;
|
|
2603
2045
|
try {
|
|
2604
|
-
mod2 =
|
|
2046
|
+
mod2 = parseModule2(original2);
|
|
2605
2047
|
} catch {
|
|
2606
2048
|
throw new Error(
|
|
2607
2049
|
`Unable to parse ${relTarget} as JavaScript/TypeScript. Please update it manually.`
|
|
@@ -2624,10 +2066,10 @@ async function installReactUILintOverlay(opts) {
|
|
|
2624
2066
|
if (importRes2.changed) changed2 = true;
|
|
2625
2067
|
const addRes2 = addDevtoolsToClientComponent(program2);
|
|
2626
2068
|
if (addRes2.changed) changed2 = true;
|
|
2627
|
-
const updated2 = changed2 ?
|
|
2069
|
+
const updated2 = changed2 ? generateCode(mod2).code : original2;
|
|
2628
2070
|
const modified2 = updated2 !== original2;
|
|
2629
2071
|
if (modified2) {
|
|
2630
|
-
|
|
2072
|
+
writeFileSync(absTarget2, updated2, "utf-8");
|
|
2631
2073
|
}
|
|
2632
2074
|
return {
|
|
2633
2075
|
targetFile: relTarget,
|
|
@@ -2648,11 +2090,11 @@ async function installReactUILintOverlay(opts) {
|
|
|
2648
2090
|
} else {
|
|
2649
2091
|
chosen = candidates[0];
|
|
2650
2092
|
}
|
|
2651
|
-
const absTarget =
|
|
2652
|
-
const original =
|
|
2093
|
+
const absTarget = join5(opts.projectPath, chosen);
|
|
2094
|
+
const original = readFileSync5(absTarget, "utf-8");
|
|
2653
2095
|
let mod;
|
|
2654
2096
|
try {
|
|
2655
|
-
mod =
|
|
2097
|
+
mod = parseModule2(original);
|
|
2656
2098
|
} catch {
|
|
2657
2099
|
throw new Error(
|
|
2658
2100
|
`Unable to parse ${chosen} as JavaScript/TypeScript. Please update it manually.`
|
|
@@ -2668,10 +2110,10 @@ async function installReactUILintOverlay(opts) {
|
|
|
2668
2110
|
const mode = opts.mode ?? "next";
|
|
2669
2111
|
const addRes = mode === "vite" ? addDevtoolsElementVite(program) : addDevtoolsElementNextJs(program);
|
|
2670
2112
|
if (addRes.changed) changed = true;
|
|
2671
|
-
const updated = changed ?
|
|
2113
|
+
const updated = changed ? generateCode(mod).code : original;
|
|
2672
2114
|
const modified = updated !== original;
|
|
2673
2115
|
if (modified) {
|
|
2674
|
-
|
|
2116
|
+
writeFileSync(absTarget, updated, "utf-8");
|
|
2675
2117
|
}
|
|
2676
2118
|
return {
|
|
2677
2119
|
targetFile: chosen,
|
|
@@ -2685,10 +2127,10 @@ async function uninstallReactUILintOverlay(options) {
|
|
|
2685
2127
|
const candidates = getDefaultCandidates(projectPath, appRoot);
|
|
2686
2128
|
const modifiedFiles = [];
|
|
2687
2129
|
for (const candidate of candidates) {
|
|
2688
|
-
const absPath =
|
|
2689
|
-
if (!
|
|
2130
|
+
const absPath = join5(projectPath, candidate);
|
|
2131
|
+
if (!existsSync5(absPath)) continue;
|
|
2690
2132
|
try {
|
|
2691
|
-
const original =
|
|
2133
|
+
const original = readFileSync5(absPath, "utf-8");
|
|
2692
2134
|
let updated = original.replace(
|
|
2693
2135
|
/^import\s+["']uilint-react\/devtools["'];?\s*$/gm,
|
|
2694
2136
|
""
|
|
@@ -2706,7 +2148,7 @@ async function uninstallReactUILintOverlay(options) {
|
|
|
2706
2148
|
);
|
|
2707
2149
|
updated = updated.replace(/\n{3,}/g, "\n\n");
|
|
2708
2150
|
if (updated !== original) {
|
|
2709
|
-
|
|
2151
|
+
writeFileSync(absPath, updated, "utf-8");
|
|
2710
2152
|
modifiedFiles.push(absPath);
|
|
2711
2153
|
}
|
|
2712
2154
|
} catch {
|
|
@@ -2719,14 +2161,14 @@ async function uninstallReactUILintOverlay(options) {
|
|
|
2719
2161
|
}
|
|
2720
2162
|
|
|
2721
2163
|
// src/utils/next-config-inject.ts
|
|
2722
|
-
import { existsSync as
|
|
2723
|
-
import { join as
|
|
2724
|
-
import { parseModule as
|
|
2725
|
-
var
|
|
2164
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "fs";
|
|
2165
|
+
import { join as join6 } from "path";
|
|
2166
|
+
import { parseModule as parseModule3, generateCode as generateCode2 } from "magicast";
|
|
2167
|
+
var CONFIG_EXTENSIONS = [".ts", ".mjs", ".js", ".cjs"];
|
|
2726
2168
|
function findNextConfigFile(projectPath) {
|
|
2727
|
-
for (const ext of
|
|
2728
|
-
const configPath =
|
|
2729
|
-
if (
|
|
2169
|
+
for (const ext of CONFIG_EXTENSIONS) {
|
|
2170
|
+
const configPath = join6(projectPath, `next.config${ext}`);
|
|
2171
|
+
if (existsSync6(configPath)) {
|
|
2730
2172
|
return configPath;
|
|
2731
2173
|
}
|
|
2732
2174
|
}
|
|
@@ -2736,10 +2178,10 @@ function getNextConfigFilename(configPath) {
|
|
|
2736
2178
|
const parts = configPath.split("/");
|
|
2737
2179
|
return parts[parts.length - 1] || "next.config.ts";
|
|
2738
2180
|
}
|
|
2739
|
-
function
|
|
2181
|
+
function isIdentifier(node, name) {
|
|
2740
2182
|
return !!node && node.type === "Identifier" && (name ? node.name === name : typeof node.name === "string");
|
|
2741
2183
|
}
|
|
2742
|
-
function
|
|
2184
|
+
function isStringLiteral(node) {
|
|
2743
2185
|
return !!node && (node.type === "StringLiteral" || node.type === "Literal") && typeof node.value === "string";
|
|
2744
2186
|
}
|
|
2745
2187
|
function ensureEsmWithJsxLocImport(program) {
|
|
@@ -2752,12 +2194,12 @@ function ensureEsmWithJsxLocImport(program) {
|
|
|
2752
2194
|
(sp) => sp?.type === "ImportSpecifier" && (sp.imported?.name === "withJsxLoc" || sp.imported?.value === "withJsxLoc")
|
|
2753
2195
|
);
|
|
2754
2196
|
if (has) return { changed: false };
|
|
2755
|
-
const spec =
|
|
2197
|
+
const spec = parseModule3('import { withJsxLoc } from "jsx-loc-plugin";').$ast.body?.[0]?.specifiers?.[0];
|
|
2756
2198
|
if (!spec) return { changed: false };
|
|
2757
2199
|
existing.specifiers = [...existing.specifiers ?? [], spec];
|
|
2758
2200
|
return { changed: true };
|
|
2759
2201
|
}
|
|
2760
|
-
const importDecl =
|
|
2202
|
+
const importDecl = parseModule3('import { withJsxLoc } from "jsx-loc-plugin";').$ast.body?.[0];
|
|
2761
2203
|
if (!importDecl) return { changed: false };
|
|
2762
2204
|
const body = program.body ?? [];
|
|
2763
2205
|
let insertAt = 0;
|
|
@@ -2773,14 +2215,14 @@ function ensureCjsWithJsxLocRequire(program) {
|
|
|
2773
2215
|
if (stmt?.type !== "VariableDeclaration") continue;
|
|
2774
2216
|
for (const decl of stmt.declarations ?? []) {
|
|
2775
2217
|
const init = decl?.init;
|
|
2776
|
-
if (init?.type === "CallExpression" &&
|
|
2218
|
+
if (init?.type === "CallExpression" && isIdentifier(init.callee, "require") && isStringLiteral(init.arguments?.[0]) && init.arguments[0].value === "jsx-loc-plugin") {
|
|
2777
2219
|
if (decl.id?.type === "ObjectPattern") {
|
|
2778
2220
|
const has = (decl.id.properties ?? []).some((p) => {
|
|
2779
2221
|
if (p?.type !== "ObjectProperty" && p?.type !== "Property") return false;
|
|
2780
|
-
return
|
|
2222
|
+
return isIdentifier(p.key, "withJsxLoc");
|
|
2781
2223
|
});
|
|
2782
2224
|
if (has) return { changed: false };
|
|
2783
|
-
const prop =
|
|
2225
|
+
const prop = parseModule3('const { withJsxLoc } = require("jsx-loc-plugin");').$ast.body?.[0]?.declarations?.[0]?.id?.properties?.[0];
|
|
2784
2226
|
if (!prop) return { changed: false };
|
|
2785
2227
|
decl.id.properties = [...decl.id.properties ?? [], prop];
|
|
2786
2228
|
return { changed: true };
|
|
@@ -2789,7 +2231,7 @@ function ensureCjsWithJsxLocRequire(program) {
|
|
|
2789
2231
|
}
|
|
2790
2232
|
}
|
|
2791
2233
|
}
|
|
2792
|
-
const reqDecl =
|
|
2234
|
+
const reqDecl = parseModule3('const { withJsxLoc } = require("jsx-loc-plugin");').$ast.body?.[0];
|
|
2793
2235
|
if (!reqDecl) return { changed: false };
|
|
2794
2236
|
program.body.unshift(reqDecl);
|
|
2795
2237
|
return { changed: true };
|
|
@@ -2801,7 +2243,7 @@ function wrapEsmExportDefault(program) {
|
|
|
2801
2243
|
);
|
|
2802
2244
|
if (!exportDecl) return { changed: false };
|
|
2803
2245
|
const decl = exportDecl.declaration;
|
|
2804
|
-
if (decl?.type === "CallExpression" &&
|
|
2246
|
+
if (decl?.type === "CallExpression" && isIdentifier(decl.callee, "withJsxLoc")) {
|
|
2805
2247
|
return { changed: false };
|
|
2806
2248
|
}
|
|
2807
2249
|
exportDecl.declaration = {
|
|
@@ -2819,9 +2261,9 @@ function wrapCjsModuleExports(program) {
|
|
|
2819
2261
|
if (!expr || expr.type !== "AssignmentExpression") continue;
|
|
2820
2262
|
const left = expr.left;
|
|
2821
2263
|
const right = expr.right;
|
|
2822
|
-
const isModuleExports = left?.type === "MemberExpression" &&
|
|
2264
|
+
const isModuleExports = left?.type === "MemberExpression" && isIdentifier(left.object, "module") && isIdentifier(left.property, "exports");
|
|
2823
2265
|
if (!isModuleExports) continue;
|
|
2824
|
-
if (right?.type === "CallExpression" &&
|
|
2266
|
+
if (right?.type === "CallExpression" && isIdentifier(right.callee, "withJsxLoc")) {
|
|
2825
2267
|
return { changed: false };
|
|
2826
2268
|
}
|
|
2827
2269
|
expr.right = {
|
|
@@ -2839,10 +2281,10 @@ async function installJsxLocPlugin(opts) {
|
|
|
2839
2281
|
return { configFile: null, modified: false, modifiedFiles: [] };
|
|
2840
2282
|
}
|
|
2841
2283
|
const configFilename = getNextConfigFilename(configPath);
|
|
2842
|
-
const original =
|
|
2284
|
+
const original = readFileSync6(configPath, "utf-8");
|
|
2843
2285
|
let mod;
|
|
2844
2286
|
try {
|
|
2845
|
-
mod =
|
|
2287
|
+
mod = parseModule3(original);
|
|
2846
2288
|
} catch {
|
|
2847
2289
|
return { configFile: configFilename, modified: false, modifiedFiles: [] };
|
|
2848
2290
|
}
|
|
@@ -2860,9 +2302,9 @@ async function installJsxLocPlugin(opts) {
|
|
|
2860
2302
|
const wrapRes = wrapEsmExportDefault(program);
|
|
2861
2303
|
if (wrapRes.changed) changed = true;
|
|
2862
2304
|
}
|
|
2863
|
-
const updated = changed ?
|
|
2305
|
+
const updated = changed ? generateCode2(mod).code : original;
|
|
2864
2306
|
if (updated !== original) {
|
|
2865
|
-
|
|
2307
|
+
writeFileSync2(configPath, updated, "utf-8");
|
|
2866
2308
|
return { configFile: configFilename, modified: true, modifiedFiles: [configPath] };
|
|
2867
2309
|
}
|
|
2868
2310
|
return { configFile: configFilename, modified: false, modifiedFiles: [] };
|
|
@@ -2877,7 +2319,7 @@ async function uninstallJsxLocPlugin(options) {
|
|
|
2877
2319
|
};
|
|
2878
2320
|
}
|
|
2879
2321
|
try {
|
|
2880
|
-
const original =
|
|
2322
|
+
const original = readFileSync6(configPath, "utf-8");
|
|
2881
2323
|
let updated = original.replace(
|
|
2882
2324
|
/^import\s+\{[^}]*withJsxLoc[^}]*\}\s+from\s+["']jsx-loc-plugin\/next["'];?\s*$/gm,
|
|
2883
2325
|
""
|
|
@@ -2896,7 +2338,7 @@ async function uninstallJsxLocPlugin(options) {
|
|
|
2896
2338
|
);
|
|
2897
2339
|
updated = updated.replace(/\n{3,}/g, "\n\n");
|
|
2898
2340
|
if (updated !== original) {
|
|
2899
|
-
|
|
2341
|
+
writeFileSync2(configPath, updated, "utf-8");
|
|
2900
2342
|
return {
|
|
2901
2343
|
success: true,
|
|
2902
2344
|
modifiedFiles: [configPath]
|
|
@@ -2915,14 +2357,14 @@ async function uninstallJsxLocPlugin(options) {
|
|
|
2915
2357
|
}
|
|
2916
2358
|
|
|
2917
2359
|
// src/utils/vite-config-inject.ts
|
|
2918
|
-
import { existsSync as
|
|
2919
|
-
import { join as
|
|
2920
|
-
import { parseModule as
|
|
2921
|
-
var
|
|
2360
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
|
|
2361
|
+
import { join as join7 } from "path";
|
|
2362
|
+
import { parseModule as parseModule4, generateCode as generateCode3 } from "magicast";
|
|
2363
|
+
var CONFIG_EXTENSIONS2 = [".ts", ".mjs", ".js", ".cjs"];
|
|
2922
2364
|
function findViteConfigFile2(projectPath) {
|
|
2923
|
-
for (const ext of
|
|
2924
|
-
const configPath =
|
|
2925
|
-
if (
|
|
2365
|
+
for (const ext of CONFIG_EXTENSIONS2) {
|
|
2366
|
+
const configPath = join7(projectPath, `vite.config${ext}`);
|
|
2367
|
+
if (existsSync7(configPath)) return configPath;
|
|
2926
2368
|
}
|
|
2927
2369
|
return null;
|
|
2928
2370
|
}
|
|
@@ -2930,10 +2372,10 @@ function getViteConfigFilename(configPath) {
|
|
|
2930
2372
|
const parts = configPath.split("/");
|
|
2931
2373
|
return parts[parts.length - 1] || "vite.config.ts";
|
|
2932
2374
|
}
|
|
2933
|
-
function
|
|
2375
|
+
function isIdentifier2(node, name) {
|
|
2934
2376
|
return !!node && node.type === "Identifier" && (name ? node.name === name : typeof node.name === "string");
|
|
2935
2377
|
}
|
|
2936
|
-
function
|
|
2378
|
+
function isStringLiteral2(node) {
|
|
2937
2379
|
return !!node && (node.type === "StringLiteral" || node.type === "Literal") && typeof node.value === "string";
|
|
2938
2380
|
}
|
|
2939
2381
|
function unwrapExpression(expr) {
|
|
@@ -2965,7 +2407,7 @@ function findExportedConfigObjectExpression(mod) {
|
|
|
2965
2407
|
if (decl.type === "ObjectExpression") {
|
|
2966
2408
|
return { kind: "esm", objExpr: decl, program };
|
|
2967
2409
|
}
|
|
2968
|
-
if (decl.type === "CallExpression" &&
|
|
2410
|
+
if (decl.type === "CallExpression" && isIdentifier2(decl.callee, "defineConfig") && unwrapExpression(decl.arguments?.[0])?.type === "ObjectExpression") {
|
|
2969
2411
|
return {
|
|
2970
2412
|
kind: "esm",
|
|
2971
2413
|
objExpr: unwrapExpression(decl.arguments?.[0]),
|
|
@@ -2980,12 +2422,12 @@ function findExportedConfigObjectExpression(mod) {
|
|
|
2980
2422
|
if (!expr || expr.type !== "AssignmentExpression") continue;
|
|
2981
2423
|
const left = expr.left;
|
|
2982
2424
|
const right = unwrapExpression(expr.right);
|
|
2983
|
-
const isModuleExports = left?.type === "MemberExpression" &&
|
|
2425
|
+
const isModuleExports = left?.type === "MemberExpression" && isIdentifier2(left.object, "module") && isIdentifier2(left.property, "exports");
|
|
2984
2426
|
if (!isModuleExports) continue;
|
|
2985
2427
|
if (right?.type === "ObjectExpression") {
|
|
2986
2428
|
return { kind: "cjs", objExpr: right, program };
|
|
2987
2429
|
}
|
|
2988
|
-
if (right?.type === "CallExpression" &&
|
|
2430
|
+
if (right?.type === "CallExpression" && isIdentifier2(right.callee, "defineConfig") && unwrapExpression(right.arguments?.[0])?.type === "ObjectExpression") {
|
|
2989
2431
|
return {
|
|
2990
2432
|
kind: "cjs",
|
|
2991
2433
|
objExpr: unwrapExpression(right.arguments?.[0]),
|
|
@@ -3001,7 +2443,7 @@ function getObjectProperty(obj, keyName) {
|
|
|
3001
2443
|
if (!prop) continue;
|
|
3002
2444
|
if (prop.type !== "ObjectProperty" && prop.type !== "Property") continue;
|
|
3003
2445
|
const key = prop.key;
|
|
3004
|
-
const keyMatch = key?.type === "Identifier" && key.name === keyName ||
|
|
2446
|
+
const keyMatch = key?.type === "Identifier" && key.name === keyName || isStringLiteral2(key) && key.value === keyName;
|
|
3005
2447
|
if (keyMatch) return prop;
|
|
3006
2448
|
}
|
|
3007
2449
|
return null;
|
|
@@ -3016,12 +2458,12 @@ function ensureEsmJsxLocImport(program) {
|
|
|
3016
2458
|
(sp) => sp?.type === "ImportSpecifier" && (sp.imported?.name === "jsxLoc" || sp.imported?.value === "jsxLoc")
|
|
3017
2459
|
);
|
|
3018
2460
|
if (has) return { changed: false };
|
|
3019
|
-
const spec =
|
|
2461
|
+
const spec = parseModule4('import { jsxLoc } from "jsx-loc-plugin/vite";').$ast.body?.[0]?.specifiers?.[0];
|
|
3020
2462
|
if (!spec) return { changed: false };
|
|
3021
2463
|
existing.specifiers = [...existing.specifiers ?? [], spec];
|
|
3022
2464
|
return { changed: true };
|
|
3023
2465
|
}
|
|
3024
|
-
const importDecl =
|
|
2466
|
+
const importDecl = parseModule4('import { jsxLoc } from "jsx-loc-plugin/vite";').$ast.body?.[0];
|
|
3025
2467
|
if (!importDecl) return { changed: false };
|
|
3026
2468
|
const body = program.body ?? [];
|
|
3027
2469
|
let insertAt = 0;
|
|
@@ -3037,14 +2479,14 @@ function ensureCjsJsxLocRequire(program) {
|
|
|
3037
2479
|
if (stmt?.type !== "VariableDeclaration") continue;
|
|
3038
2480
|
for (const decl of stmt.declarations ?? []) {
|
|
3039
2481
|
const init = decl?.init;
|
|
3040
|
-
if (init?.type === "CallExpression" &&
|
|
2482
|
+
if (init?.type === "CallExpression" && isIdentifier2(init.callee, "require") && isStringLiteral2(init.arguments?.[0]) && init.arguments[0].value === "jsx-loc-plugin/vite") {
|
|
3041
2483
|
if (decl.id?.type === "ObjectPattern") {
|
|
3042
2484
|
const has = (decl.id.properties ?? []).some((p) => {
|
|
3043
2485
|
if (p?.type !== "ObjectProperty" && p?.type !== "Property") return false;
|
|
3044
|
-
return
|
|
2486
|
+
return isIdentifier2(p.key, "jsxLoc");
|
|
3045
2487
|
});
|
|
3046
2488
|
if (has) return { changed: false };
|
|
3047
|
-
const prop =
|
|
2489
|
+
const prop = parseModule4('const { jsxLoc } = require("jsx-loc-plugin/vite");').$ast.body?.[0]?.declarations?.[0]?.id?.properties?.[0];
|
|
3048
2490
|
if (!prop) return { changed: false };
|
|
3049
2491
|
decl.id.properties = [...decl.id.properties ?? [], prop];
|
|
3050
2492
|
return { changed: true };
|
|
@@ -3053,7 +2495,7 @@ function ensureCjsJsxLocRequire(program) {
|
|
|
3053
2495
|
}
|
|
3054
2496
|
}
|
|
3055
2497
|
}
|
|
3056
|
-
const reqDecl =
|
|
2498
|
+
const reqDecl = parseModule4('const { jsxLoc } = require("jsx-loc-plugin/vite");').$ast.body?.[0];
|
|
3057
2499
|
if (!reqDecl) return { changed: false };
|
|
3058
2500
|
program.body.unshift(reqDecl);
|
|
3059
2501
|
return { changed: true };
|
|
@@ -3063,16 +2505,16 @@ function pluginsHasJsxLoc(arr) {
|
|
|
3063
2505
|
for (const el of arr.elements ?? []) {
|
|
3064
2506
|
const e = unwrapExpression(el);
|
|
3065
2507
|
if (!e) continue;
|
|
3066
|
-
if (e.type === "CallExpression" &&
|
|
2508
|
+
if (e.type === "CallExpression" && isIdentifier2(e.callee, "jsxLoc")) return true;
|
|
3067
2509
|
}
|
|
3068
2510
|
return false;
|
|
3069
2511
|
}
|
|
3070
2512
|
function ensurePluginsContainsJsxLoc(configObj) {
|
|
3071
2513
|
const pluginsProp = getObjectProperty(configObj, "plugins");
|
|
3072
2514
|
if (!pluginsProp) {
|
|
3073
|
-
const prop =
|
|
2515
|
+
const prop = parseModule4("export default { plugins: [jsxLoc()] };").$ast.body?.find((s) => s.type === "ExportDefaultDeclaration")?.declaration?.properties?.find((p) => {
|
|
3074
2516
|
const k = p?.key;
|
|
3075
|
-
return k?.type === "Identifier" && k.name === "plugins" ||
|
|
2517
|
+
return k?.type === "Identifier" && k.name === "plugins" || isStringLiteral2(k) && k.value === "plugins";
|
|
3076
2518
|
});
|
|
3077
2519
|
if (!prop) return { changed: false };
|
|
3078
2520
|
configObj.properties = [...configObj.properties ?? [], prop];
|
|
@@ -3082,12 +2524,12 @@ function ensurePluginsContainsJsxLoc(configObj) {
|
|
|
3082
2524
|
if (!value) return { changed: false };
|
|
3083
2525
|
if (value.type === "ArrayExpression") {
|
|
3084
2526
|
if (pluginsHasJsxLoc(value)) return { changed: false };
|
|
3085
|
-
const jsxLocCall2 =
|
|
2527
|
+
const jsxLocCall2 = parseModule4("const __x = jsxLoc();").$ast.body?.[0]?.declarations?.[0]?.init;
|
|
3086
2528
|
if (!jsxLocCall2) return { changed: false };
|
|
3087
2529
|
value.elements.push(jsxLocCall2);
|
|
3088
2530
|
return { changed: true };
|
|
3089
2531
|
}
|
|
3090
|
-
const jsxLocCall =
|
|
2532
|
+
const jsxLocCall = parseModule4("const __x = jsxLoc();").$ast.body?.[0]?.declarations?.[0]?.init;
|
|
3091
2533
|
if (!jsxLocCall) return { changed: false };
|
|
3092
2534
|
const spread = { type: "SpreadElement", argument: value };
|
|
3093
2535
|
pluginsProp.value = { type: "ArrayExpression", elements: [spread, jsxLocCall] };
|
|
@@ -3097,11 +2539,11 @@ async function installViteJsxLocPlugin(opts) {
|
|
|
3097
2539
|
const configPath = findViteConfigFile2(opts.projectPath);
|
|
3098
2540
|
if (!configPath) return { configFile: null, modified: false, modifiedFiles: [] };
|
|
3099
2541
|
const configFilename = getViteConfigFilename(configPath);
|
|
3100
|
-
const original =
|
|
2542
|
+
const original = readFileSync7(configPath, "utf-8");
|
|
3101
2543
|
const isCjs = configPath.endsWith(".cjs");
|
|
3102
2544
|
let mod;
|
|
3103
2545
|
try {
|
|
3104
|
-
mod =
|
|
2546
|
+
mod = parseModule4(original);
|
|
3105
2547
|
} catch {
|
|
3106
2548
|
return { configFile: configFilename, modified: false, modifiedFiles: [] };
|
|
3107
2549
|
}
|
|
@@ -3117,9 +2559,9 @@ async function installViteJsxLocPlugin(opts) {
|
|
|
3117
2559
|
}
|
|
3118
2560
|
const pluginsRes = ensurePluginsContainsJsxLoc(found.objExpr);
|
|
3119
2561
|
if (pluginsRes.changed) changed = true;
|
|
3120
|
-
const updated = changed ?
|
|
2562
|
+
const updated = changed ? generateCode3(mod).code : original;
|
|
3121
2563
|
if (updated !== original) {
|
|
3122
|
-
|
|
2564
|
+
writeFileSync3(configPath, updated, "utf-8");
|
|
3123
2565
|
return { configFile: configFilename, modified: true, modifiedFiles: [configPath] };
|
|
3124
2566
|
}
|
|
3125
2567
|
return { configFile: configFilename, modified: false, modifiedFiles: [] };
|
|
@@ -3134,7 +2576,7 @@ async function uninstallViteJsxLocPlugin(options) {
|
|
|
3134
2576
|
};
|
|
3135
2577
|
}
|
|
3136
2578
|
try {
|
|
3137
|
-
const original =
|
|
2579
|
+
const original = readFileSync7(configPath, "utf-8");
|
|
3138
2580
|
let updated = original.replace(
|
|
3139
2581
|
/^import\s+\{[^}]*jsxLoc[^}]*\}\s+from\s+["']jsx-loc-plugin\/vite["'];?\s*$/gm,
|
|
3140
2582
|
""
|
|
@@ -3150,7 +2592,7 @@ async function uninstallViteJsxLocPlugin(options) {
|
|
|
3150
2592
|
updated = updated.replace(/jsxLoc\s*\(\s*\)\s*,?\s*/g, "");
|
|
3151
2593
|
updated = updated.replace(/\n{3,}/g, "\n\n");
|
|
3152
2594
|
if (updated !== original) {
|
|
3153
|
-
|
|
2595
|
+
writeFileSync3(configPath, updated, "utf-8");
|
|
3154
2596
|
return {
|
|
3155
2597
|
success: true,
|
|
3156
2598
|
modifiedFiles: [configPath]
|
|
@@ -3169,9 +2611,9 @@ async function uninstallViteJsxLocPlugin(options) {
|
|
|
3169
2611
|
}
|
|
3170
2612
|
|
|
3171
2613
|
// src/utils/next-routes.ts
|
|
3172
|
-
import { existsSync as
|
|
2614
|
+
import { existsSync as existsSync8 } from "fs";
|
|
3173
2615
|
import { mkdir, writeFile } from "fs/promises";
|
|
3174
|
-
import { join as
|
|
2616
|
+
import { join as join8 } from "path";
|
|
3175
2617
|
var DEV_SOURCE_ROUTE_TS = `/**
|
|
3176
2618
|
* Dev-only API route for fetching source files
|
|
3177
2619
|
*
|
|
@@ -3627,23 +3069,23 @@ export async function GET(request: NextRequest) {
|
|
|
3627
3069
|
}
|
|
3628
3070
|
`;
|
|
3629
3071
|
async function writeRouteFile(absPath, relPath, content, opts) {
|
|
3630
|
-
if (
|
|
3072
|
+
if (existsSync8(absPath) && !opts.force) return;
|
|
3631
3073
|
await writeFile(absPath, content, "utf-8");
|
|
3632
3074
|
}
|
|
3633
3075
|
async function installNextUILintRoutes(opts) {
|
|
3634
|
-
const baseRel =
|
|
3635
|
-
const baseAbs =
|
|
3636
|
-
await mkdir(
|
|
3076
|
+
const baseRel = join8(opts.appRoot, "api", ".uilint");
|
|
3077
|
+
const baseAbs = join8(opts.projectPath, baseRel);
|
|
3078
|
+
await mkdir(join8(baseAbs, "source"), { recursive: true });
|
|
3637
3079
|
await writeRouteFile(
|
|
3638
|
-
|
|
3639
|
-
|
|
3080
|
+
join8(baseAbs, "source", "route.ts"),
|
|
3081
|
+
join8(baseRel, "source", "route.ts"),
|
|
3640
3082
|
DEV_SOURCE_ROUTE_TS,
|
|
3641
3083
|
opts
|
|
3642
3084
|
);
|
|
3643
|
-
await mkdir(
|
|
3085
|
+
await mkdir(join8(baseAbs, "screenshots"), { recursive: true });
|
|
3644
3086
|
await writeRouteFile(
|
|
3645
|
-
|
|
3646
|
-
|
|
3087
|
+
join8(baseAbs, "screenshots", "route.ts"),
|
|
3088
|
+
join8(baseRel, "screenshots", "route.ts"),
|
|
3647
3089
|
SCREENSHOT_ROUTE_TS,
|
|
3648
3090
|
opts
|
|
3649
3091
|
);
|
|
@@ -3651,9 +3093,9 @@ async function installNextUILintRoutes(opts) {
|
|
|
3651
3093
|
async function uninstallNextUILintRoutes(options) {
|
|
3652
3094
|
const { projectPath, appRoot } = options;
|
|
3653
3095
|
const { rm } = await import("fs/promises");
|
|
3654
|
-
const baseAbs =
|
|
3096
|
+
const baseAbs = join8(projectPath, appRoot, "api", ".uilint");
|
|
3655
3097
|
try {
|
|
3656
|
-
if (
|
|
3098
|
+
if (existsSync8(baseAbs)) {
|
|
3657
3099
|
await rm(baseAbs, { recursive: true, force: true });
|
|
3658
3100
|
}
|
|
3659
3101
|
return { success: true };
|
|
@@ -3666,17 +3108,17 @@ async function uninstallNextUILintRoutes(options) {
|
|
|
3666
3108
|
}
|
|
3667
3109
|
|
|
3668
3110
|
// src/utils/prettier.ts
|
|
3669
|
-
import { existsSync as
|
|
3111
|
+
import { existsSync as existsSync9, utimesSync } from "fs";
|
|
3670
3112
|
import { spawn } from "child_process";
|
|
3671
|
-
import { join as
|
|
3113
|
+
import { join as join9, dirname as dirname3 } from "path";
|
|
3672
3114
|
function getPrettierPath(projectPath) {
|
|
3673
|
-
const localPath =
|
|
3674
|
-
if (
|
|
3115
|
+
const localPath = join9(projectPath, "node_modules", ".bin", "prettier");
|
|
3116
|
+
if (existsSync9(localPath)) return localPath;
|
|
3675
3117
|
let dir = projectPath;
|
|
3676
3118
|
for (let i = 0; i < 10; i++) {
|
|
3677
|
-
const binPath =
|
|
3678
|
-
if (
|
|
3679
|
-
const parent =
|
|
3119
|
+
const binPath = join9(dir, "node_modules", ".bin", "prettier");
|
|
3120
|
+
if (existsSync9(binPath)) return binPath;
|
|
3121
|
+
const parent = dirname3(dir);
|
|
3680
3122
|
if (parent === dir) break;
|
|
3681
3123
|
dir = parent;
|
|
3682
3124
|
}
|
|
@@ -3807,7 +3249,7 @@ function touchFiles(filePaths) {
|
|
|
3807
3249
|
const now = /* @__PURE__ */ new Date();
|
|
3808
3250
|
for (const filePath of filePaths) {
|
|
3809
3251
|
try {
|
|
3810
|
-
if (
|
|
3252
|
+
if (existsSync9(filePath)) {
|
|
3811
3253
|
utimesSync(filePath, now, now);
|
|
3812
3254
|
}
|
|
3813
3255
|
} catch {
|
|
@@ -3816,6 +3258,7 @@ function touchFiles(filePaths) {
|
|
|
3816
3258
|
}
|
|
3817
3259
|
|
|
3818
3260
|
// src/commands/install/execute.ts
|
|
3261
|
+
import { findWorkspaceRoot as findWorkspaceRoot2 } from "uilint-core/node";
|
|
3819
3262
|
async function executeAction(action, options) {
|
|
3820
3263
|
const { dryRun = false } = options;
|
|
3821
3264
|
try {
|
|
@@ -3828,7 +3271,7 @@ async function executeAction(action, options) {
|
|
|
3828
3271
|
wouldDo: `Create directory: ${action.path}`
|
|
3829
3272
|
};
|
|
3830
3273
|
}
|
|
3831
|
-
if (!
|
|
3274
|
+
if (!existsSync10(action.path)) {
|
|
3832
3275
|
mkdirSync(action.path, { recursive: true });
|
|
3833
3276
|
}
|
|
3834
3277
|
return { action, success: true };
|
|
@@ -3841,11 +3284,11 @@ async function executeAction(action, options) {
|
|
|
3841
3284
|
wouldDo: `Create file: ${action.path}${action.permissions ? ` (mode: ${action.permissions.toString(8)})` : ""}`
|
|
3842
3285
|
};
|
|
3843
3286
|
}
|
|
3844
|
-
const dir =
|
|
3845
|
-
if (!
|
|
3287
|
+
const dir = dirname4(action.path);
|
|
3288
|
+
if (!existsSync10(dir)) {
|
|
3846
3289
|
mkdirSync(dir, { recursive: true });
|
|
3847
3290
|
}
|
|
3848
|
-
|
|
3291
|
+
writeFileSync4(action.path, action.content, "utf-8");
|
|
3849
3292
|
if (action.permissions) {
|
|
3850
3293
|
chmodSync(action.path, action.permissions);
|
|
3851
3294
|
}
|
|
@@ -3860,18 +3303,18 @@ async function executeAction(action, options) {
|
|
|
3860
3303
|
};
|
|
3861
3304
|
}
|
|
3862
3305
|
let existing = {};
|
|
3863
|
-
if (
|
|
3306
|
+
if (existsSync10(action.path)) {
|
|
3864
3307
|
try {
|
|
3865
|
-
existing = JSON.parse(
|
|
3308
|
+
existing = JSON.parse(readFileSync8(action.path, "utf-8"));
|
|
3866
3309
|
} catch {
|
|
3867
3310
|
}
|
|
3868
3311
|
}
|
|
3869
3312
|
const merged = deepMerge(existing, action.merge);
|
|
3870
|
-
const dir =
|
|
3871
|
-
if (!
|
|
3313
|
+
const dir = dirname4(action.path);
|
|
3314
|
+
if (!existsSync10(dir)) {
|
|
3872
3315
|
mkdirSync(dir, { recursive: true });
|
|
3873
3316
|
}
|
|
3874
|
-
|
|
3317
|
+
writeFileSync4(action.path, JSON.stringify(merged, null, 2), "utf-8");
|
|
3875
3318
|
return { action, success: true };
|
|
3876
3319
|
}
|
|
3877
3320
|
case "delete_file": {
|
|
@@ -3882,7 +3325,7 @@ async function executeAction(action, options) {
|
|
|
3882
3325
|
wouldDo: `Delete file: ${action.path}`
|
|
3883
3326
|
};
|
|
3884
3327
|
}
|
|
3885
|
-
if (
|
|
3328
|
+
if (existsSync10(action.path)) {
|
|
3886
3329
|
unlinkSync(action.path);
|
|
3887
3330
|
}
|
|
3888
3331
|
return { action, success: true };
|
|
@@ -3895,12 +3338,12 @@ async function executeAction(action, options) {
|
|
|
3895
3338
|
wouldDo: `Append to file: ${action.path}`
|
|
3896
3339
|
};
|
|
3897
3340
|
}
|
|
3898
|
-
if (
|
|
3899
|
-
const content =
|
|
3341
|
+
if (existsSync10(action.path)) {
|
|
3342
|
+
const content = readFileSync8(action.path, "utf-8");
|
|
3900
3343
|
if (action.ifNotContains && content.includes(action.ifNotContains)) {
|
|
3901
3344
|
return { action, success: true };
|
|
3902
3345
|
}
|
|
3903
|
-
|
|
3346
|
+
writeFileSync4(action.path, content + action.content, "utf-8");
|
|
3904
3347
|
}
|
|
3905
3348
|
return { action, success: true };
|
|
3906
3349
|
}
|
|
@@ -3916,6 +3359,9 @@ async function executeAction(action, options) {
|
|
|
3916
3359
|
case "inject_vite_config": {
|
|
3917
3360
|
return await executeInjectViteConfig(action, options);
|
|
3918
3361
|
}
|
|
3362
|
+
case "inject_vitest_coverage": {
|
|
3363
|
+
return await executeInjectVitestCoverage(action, options);
|
|
3364
|
+
}
|
|
3919
3365
|
case "install_next_routes": {
|
|
3920
3366
|
return await executeInstallNextRoutes(action, options);
|
|
3921
3367
|
}
|
|
@@ -4027,6 +3473,31 @@ async function executeInjectViteConfig(action, options) {
|
|
|
4027
3473
|
modifiedFiles: result.modifiedFiles
|
|
4028
3474
|
};
|
|
4029
3475
|
}
|
|
3476
|
+
async function executeInjectVitestCoverage(action, options) {
|
|
3477
|
+
const { dryRun = false } = options;
|
|
3478
|
+
const setup = detectCoverageSetup(action.projectPath);
|
|
3479
|
+
const vitestConfigPath = action.vitestConfigPath || setup.vitestConfigPath;
|
|
3480
|
+
if (!vitestConfigPath) {
|
|
3481
|
+
return {
|
|
3482
|
+
action,
|
|
3483
|
+
success: true
|
|
3484
|
+
// Don't fail the install, coverage config is optional
|
|
3485
|
+
};
|
|
3486
|
+
}
|
|
3487
|
+
if (dryRun) {
|
|
3488
|
+
return {
|
|
3489
|
+
action,
|
|
3490
|
+
success: true,
|
|
3491
|
+
wouldDo: `Inject coverage config into vitest.config: ${vitestConfigPath}`
|
|
3492
|
+
};
|
|
3493
|
+
}
|
|
3494
|
+
const modified = injectCoverageConfig(vitestConfigPath);
|
|
3495
|
+
return {
|
|
3496
|
+
action,
|
|
3497
|
+
success: true,
|
|
3498
|
+
modifiedFiles: modified ? [vitestConfigPath] : void 0
|
|
3499
|
+
};
|
|
3500
|
+
}
|
|
4030
3501
|
async function executeInjectNextConfig(action, options) {
|
|
4031
3502
|
const { dryRun = false } = options;
|
|
4032
3503
|
if (dryRun) {
|
|
@@ -4169,7 +3640,7 @@ async function executeRemoveDirectory(action, options) {
|
|
|
4169
3640
|
wouldDo: `Remove directory: ${action.path}`
|
|
4170
3641
|
};
|
|
4171
3642
|
}
|
|
4172
|
-
if (
|
|
3643
|
+
if (existsSync10(action.path)) {
|
|
4173
3644
|
rmSync(action.path, { recursive: true, force: true });
|
|
4174
3645
|
}
|
|
4175
3646
|
return { action, success: true };
|
|
@@ -4316,6 +3787,36 @@ function getProjectPathFromActions(actionsPerformed) {
|
|
|
4316
3787
|
}
|
|
4317
3788
|
return void 0;
|
|
4318
3789
|
}
|
|
3790
|
+
function normalizePnpmWorkspaceUilintSpecs(packagePath, packages) {
|
|
3791
|
+
const workspaceRoot = findWorkspaceRoot2(packagePath);
|
|
3792
|
+
if (!existsSync10(join10(workspaceRoot, "pnpm-workspace.yaml"))) return packages;
|
|
3793
|
+
const touchesUilint = packages.some((p) => p === "uilint-eslint" || p.startsWith("uilint-"));
|
|
3794
|
+
if (!touchesUilint) return packages;
|
|
3795
|
+
const pkgJsonPath = join10(packagePath, "package.json");
|
|
3796
|
+
if (!existsSync10(pkgJsonPath)) return packages;
|
|
3797
|
+
let pkg = null;
|
|
3798
|
+
try {
|
|
3799
|
+
pkg = JSON.parse(readFileSync8(pkgJsonPath, "utf-8"));
|
|
3800
|
+
} catch {
|
|
3801
|
+
return packages;
|
|
3802
|
+
}
|
|
3803
|
+
const wanted = ["uilint-core", "uilint-eslint", "uilint-react"];
|
|
3804
|
+
const present = /* @__PURE__ */ new Set();
|
|
3805
|
+
for (const name of wanted) {
|
|
3806
|
+
const range = pkg?.dependencies?.[name] ?? pkg?.devDependencies?.[name];
|
|
3807
|
+
if (typeof range === "string") {
|
|
3808
|
+
present.add(name);
|
|
3809
|
+
}
|
|
3810
|
+
}
|
|
3811
|
+
if (present.size === 0) return packages;
|
|
3812
|
+
const filtered = packages.filter(
|
|
3813
|
+
(p) => !/^uilint-(core|eslint|react)(@.+)?$/.test(p)
|
|
3814
|
+
);
|
|
3815
|
+
for (const name of present) {
|
|
3816
|
+
filtered.push(`${name}@workspace:*`);
|
|
3817
|
+
}
|
|
3818
|
+
return filtered;
|
|
3819
|
+
}
|
|
4319
3820
|
async function execute(plan, options = {}) {
|
|
4320
3821
|
const {
|
|
4321
3822
|
dryRun = false,
|
|
@@ -4339,13 +3840,14 @@ async function execute(plan, options = {}) {
|
|
|
4339
3840
|
continue;
|
|
4340
3841
|
}
|
|
4341
3842
|
try {
|
|
3843
|
+
const pkgs = dep.packageManager === "pnpm" ? normalizePnpmWorkspaceUilintSpecs(dep.packagePath, dep.packages) : dep.packages;
|
|
4342
3844
|
await installDependencies2(
|
|
4343
3845
|
dep.packageManager,
|
|
4344
3846
|
dep.packagePath,
|
|
4345
|
-
|
|
3847
|
+
pkgs
|
|
4346
3848
|
);
|
|
4347
3849
|
dependencyResults.push({
|
|
4348
|
-
install: dep,
|
|
3850
|
+
install: { ...dep, packages: pkgs },
|
|
4349
3851
|
success: true
|
|
4350
3852
|
});
|
|
4351
3853
|
} catch (error) {
|
|
@@ -4481,7 +3983,7 @@ var genstyleguideInstaller = {
|
|
|
4481
3983
|
};
|
|
4482
3984
|
|
|
4483
3985
|
// src/commands/install/installers/skill.ts
|
|
4484
|
-
import { existsSync as
|
|
3986
|
+
import { existsSync as existsSync11 } from "fs";
|
|
4485
3987
|
import { join as join12 } from "path";
|
|
4486
3988
|
var skillInstaller = {
|
|
4487
3989
|
id: "skill",
|
|
@@ -4494,7 +3996,7 @@ var skillInstaller = {
|
|
|
4494
3996
|
getTargets(project) {
|
|
4495
3997
|
const skillsDir = join12(project.cursorDir.path, "skills", "ui-consistency-enforcer");
|
|
4496
3998
|
const skillMdPath = join12(skillsDir, "SKILL.md");
|
|
4497
|
-
const isInstalled =
|
|
3999
|
+
const isInstalled = existsSync11(skillMdPath);
|
|
4498
4000
|
return [
|
|
4499
4001
|
{
|
|
4500
4002
|
id: "ui-consistency-skill",
|
|
@@ -4608,9 +4110,6 @@ function getUpgradeInfo(configuredRuleIds) {
|
|
|
4608
4110
|
summary
|
|
4609
4111
|
};
|
|
4610
4112
|
}
|
|
4611
|
-
function toInstallSpecifier(pkgName) {
|
|
4612
|
-
return pkgName;
|
|
4613
|
-
}
|
|
4614
4113
|
function formatRuleOption(rule) {
|
|
4615
4114
|
const severityBadge = rule.defaultSeverity === "error" ? pc.red("error") : pc.yellow("warn");
|
|
4616
4115
|
return {
|
|
@@ -4747,6 +4246,9 @@ var eslintInstaller = {
|
|
|
4747
4246
|
const warnCount = configuredRules.filter(
|
|
4748
4247
|
(r) => r.severity === "warn"
|
|
4749
4248
|
).length;
|
|
4249
|
+
const hasSemanticDuplicates = configuredRules.some(
|
|
4250
|
+
(cr) => cr.rule.id === "no-semantic-duplicates"
|
|
4251
|
+
);
|
|
4750
4252
|
log("");
|
|
4751
4253
|
note(
|
|
4752
4254
|
configuredRules.map(
|
|
@@ -4754,6 +4256,19 @@ var eslintInstaller = {
|
|
|
4754
4256
|
).join("\n"),
|
|
4755
4257
|
`Selected ${configuredRules.length} rules (${errorCount} errors, ${warnCount} warnings)`
|
|
4756
4258
|
);
|
|
4259
|
+
if (hasSemanticDuplicates) {
|
|
4260
|
+
log("");
|
|
4261
|
+
log(
|
|
4262
|
+
pc.yellow(
|
|
4263
|
+
"\u26A0\uFE0F The no-semantic-duplicates rule requires a semantic index."
|
|
4264
|
+
)
|
|
4265
|
+
);
|
|
4266
|
+
log(
|
|
4267
|
+
pc.dim(
|
|
4268
|
+
" Run 'uilint duplicates index' in each target app to build it."
|
|
4269
|
+
)
|
|
4270
|
+
);
|
|
4271
|
+
}
|
|
4757
4272
|
return { configuredRules };
|
|
4758
4273
|
},
|
|
4759
4274
|
plan(targets, config, project) {
|
|
@@ -4777,7 +4292,14 @@ var eslintInstaller = {
|
|
|
4777
4292
|
dependencies.push({
|
|
4778
4293
|
packagePath: target.path,
|
|
4779
4294
|
packageManager: detectPackageManager(target.path),
|
|
4780
|
-
packages: [
|
|
4295
|
+
packages: [
|
|
4296
|
+
toInstallSpecifier("uilint-eslint", {
|
|
4297
|
+
preferWorkspaceProtocol: project.packageManager === "pnpm",
|
|
4298
|
+
workspaceRoot: project.workspaceRoot,
|
|
4299
|
+
targetProjectPath: target.path
|
|
4300
|
+
}),
|
|
4301
|
+
"typescript-eslint"
|
|
4302
|
+
]
|
|
4781
4303
|
});
|
|
4782
4304
|
actions.push({
|
|
4783
4305
|
type: "inject_eslint",
|
|
@@ -4854,8 +4376,7 @@ var viteOverlayInstaller = {
|
|
|
4854
4376
|
label: app.projectPath.split("/").pop() || app.projectPath,
|
|
4855
4377
|
path: app.projectPath,
|
|
4856
4378
|
hint: "React + Vite",
|
|
4857
|
-
isInstalled:
|
|
4858
|
-
// TODO: Detect if already installed
|
|
4379
|
+
isInstalled: app.hasUilintOverlay
|
|
4859
4380
|
}));
|
|
4860
4381
|
},
|
|
4861
4382
|
plan(targets, config, project) {
|
|
@@ -4863,13 +4384,27 @@ var viteOverlayInstaller = {
|
|
|
4863
4384
|
const dependencies = [];
|
|
4864
4385
|
if (targets.length === 0) return { actions, dependencies };
|
|
4865
4386
|
const target = targets[0];
|
|
4866
|
-
const appInfo = project.viteApps.find(
|
|
4387
|
+
const appInfo = project.viteApps.find(
|
|
4388
|
+
(app) => app.projectPath === target.path
|
|
4389
|
+
);
|
|
4867
4390
|
if (!appInfo) return { actions, dependencies };
|
|
4868
4391
|
const { projectPath, detection } = appInfo;
|
|
4869
4392
|
dependencies.push({
|
|
4870
4393
|
packagePath: projectPath,
|
|
4871
4394
|
packageManager: project.packageManager,
|
|
4872
|
-
packages: [
|
|
4395
|
+
packages: [
|
|
4396
|
+
toInstallSpecifier("uilint-react", {
|
|
4397
|
+
preferWorkspaceProtocol: project.packageManager === "pnpm",
|
|
4398
|
+
workspaceRoot: project.workspaceRoot,
|
|
4399
|
+
targetProjectPath: projectPath
|
|
4400
|
+
}),
|
|
4401
|
+
toInstallSpecifier("uilint-core", {
|
|
4402
|
+
preferWorkspaceProtocol: project.packageManager === "pnpm",
|
|
4403
|
+
workspaceRoot: project.workspaceRoot,
|
|
4404
|
+
targetProjectPath: projectPath
|
|
4405
|
+
}),
|
|
4406
|
+
"jsx-loc-plugin"
|
|
4407
|
+
]
|
|
4873
4408
|
});
|
|
4874
4409
|
actions.push({
|
|
4875
4410
|
type: "inject_react",
|
|
@@ -4947,6 +4482,105 @@ registerInstaller(viteOverlayInstaller);
|
|
|
4947
4482
|
|
|
4948
4483
|
// src/commands/install-ui.tsx
|
|
4949
4484
|
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
4485
|
+
function limitList(items, max) {
|
|
4486
|
+
if (items.length <= max) return items;
|
|
4487
|
+
return [...items.slice(0, max), pc.dim(`\u2026and ${items.length - max} more`)];
|
|
4488
|
+
}
|
|
4489
|
+
function printInstallReport(result, testCoverageResult) {
|
|
4490
|
+
const failedDeps = result.dependencyResults.filter((r) => !r.success);
|
|
4491
|
+
const okDeps = result.dependencyResults.filter((r) => r.success);
|
|
4492
|
+
const failedActions = result.actionsPerformed.filter((r) => !r.success);
|
|
4493
|
+
const okActions = result.actionsPerformed.filter((r) => r.success);
|
|
4494
|
+
if (result.success) {
|
|
4495
|
+
console.log(`
|
|
4496
|
+
${pc.green("\u2713")} Operation completed successfully`);
|
|
4497
|
+
} else {
|
|
4498
|
+
console.log(`
|
|
4499
|
+
${pc.yellow("\u26A0")} Operation completed with errors`);
|
|
4500
|
+
}
|
|
4501
|
+
const { summary } = result;
|
|
4502
|
+
const installed = summary.installedItems.map((x) => String(x));
|
|
4503
|
+
const created = summary.filesCreated;
|
|
4504
|
+
const modified = summary.filesModified;
|
|
4505
|
+
const deleted = summary.filesDeleted;
|
|
4506
|
+
if (installed.length > 0) {
|
|
4507
|
+
console.log(`
|
|
4508
|
+
${pc.bold("Installed:")}`);
|
|
4509
|
+
for (const item of installed) console.log(`- ${pc.green("\u2713")} ${item}`);
|
|
4510
|
+
}
|
|
4511
|
+
if (summary.eslintTargets.length > 0) {
|
|
4512
|
+
console.log(`
|
|
4513
|
+
${pc.bold("ESLint configured:")}`);
|
|
4514
|
+
for (const t of summary.eslintTargets) {
|
|
4515
|
+
console.log(
|
|
4516
|
+
`- ${pc.green("\u2713")} ${t.displayName} ${pc.dim(`(${t.configFile})`)}`
|
|
4517
|
+
);
|
|
4518
|
+
}
|
|
4519
|
+
}
|
|
4520
|
+
if (created.length + modified.length + deleted.length > 0) {
|
|
4521
|
+
console.log(`
|
|
4522
|
+
${pc.bold("Files:")}`);
|
|
4523
|
+
for (const p of limitList(created, 20))
|
|
4524
|
+
console.log(`- ${pc.green("+")} ${p}`);
|
|
4525
|
+
for (const p of limitList(modified, 20))
|
|
4526
|
+
console.log(`- ${pc.yellow("~")} ${p}`);
|
|
4527
|
+
for (const p of limitList(deleted, 20))
|
|
4528
|
+
console.log(`- ${pc.red("-")} ${p}`);
|
|
4529
|
+
}
|
|
4530
|
+
if (summary.dependenciesInstalled.length > 0) {
|
|
4531
|
+
console.log(`
|
|
4532
|
+
${pc.bold("Dependencies installed:")}`);
|
|
4533
|
+
for (const d of summary.dependenciesInstalled) {
|
|
4534
|
+
console.log(
|
|
4535
|
+
`- ${pc.green("\u2713")} ${d.packagePath} ${pc.dim(`\u2190 ${d.packages.join(", ")}`)}`
|
|
4536
|
+
);
|
|
4537
|
+
}
|
|
4538
|
+
}
|
|
4539
|
+
if (testCoverageResult?.ran) {
|
|
4540
|
+
console.log(`
|
|
4541
|
+
${pc.bold("Test coverage:")}`);
|
|
4542
|
+
if (testCoverageResult.success) {
|
|
4543
|
+
console.log(`- ${pc.green("\u2713")} Coverage data generated successfully`);
|
|
4544
|
+
} else {
|
|
4545
|
+
console.log(`- ${pc.yellow("\u26A0")} Tests ran with errors`);
|
|
4546
|
+
if (testCoverageResult.error) {
|
|
4547
|
+
console.log(pc.dim(` ${testCoverageResult.error.split("\n")[0]}`));
|
|
4548
|
+
}
|
|
4549
|
+
}
|
|
4550
|
+
}
|
|
4551
|
+
if (failedDeps.length > 0 || failedActions.length > 0) {
|
|
4552
|
+
console.log(`
|
|
4553
|
+
${pc.bold(pc.red("Failures:"))}`);
|
|
4554
|
+
}
|
|
4555
|
+
if (failedDeps.length > 0) {
|
|
4556
|
+
console.log(`
|
|
4557
|
+
${pc.bold("Dependency installs failed:")}`);
|
|
4558
|
+
for (const dep of failedDeps) {
|
|
4559
|
+
const pkgs = dep.install.packages.join(", ");
|
|
4560
|
+
console.log(
|
|
4561
|
+
`- ${pc.red("\u2717")} ${dep.install.packageManager} in ${dep.install.packagePath} ${pc.dim(`\u2190 ${pkgs}`)}`
|
|
4562
|
+
);
|
|
4563
|
+
if (dep.error) console.log(pc.dim(dep.error.split("\n").slice(0, 30).join("\n")));
|
|
4564
|
+
}
|
|
4565
|
+
}
|
|
4566
|
+
if (failedActions.length > 0) {
|
|
4567
|
+
console.log(`
|
|
4568
|
+
${pc.bold("Actions failed:")}`);
|
|
4569
|
+
for (const a of failedActions) {
|
|
4570
|
+
const action = a.action;
|
|
4571
|
+
const type = String(action.type || "unknown");
|
|
4572
|
+
const pathish = typeof action.path === "string" && action.path || typeof action.projectPath === "string" && action.projectPath || typeof action.packagePath === "string" && action.packagePath || "";
|
|
4573
|
+
console.error(`- ${type}${pathish ? ` (${pathish})` : ""}`);
|
|
4574
|
+
if (a.error) console.error(` ${a.error}`);
|
|
4575
|
+
}
|
|
4576
|
+
}
|
|
4577
|
+
console.log(
|
|
4578
|
+
pc.dim(
|
|
4579
|
+
`
|
|
4580
|
+
Summary: ${okActions.length} action(s) ok, ${failedActions.length} failed \xB7 ${okDeps.length} dep install(s) ok, ${failedDeps.length} failed`
|
|
4581
|
+
)
|
|
4582
|
+
);
|
|
4583
|
+
}
|
|
4950
4584
|
function selectionsToUserChoices(selections, project, eslintRules, injectionPointConfig) {
|
|
4951
4585
|
const items = [];
|
|
4952
4586
|
const choices = { items };
|
|
@@ -5025,7 +4659,7 @@ async function installUI(options = {}, executeOptions = {}) {
|
|
|
5025
4659
|
console.log("\nNo changes selected");
|
|
5026
4660
|
process.exit(0);
|
|
5027
4661
|
}
|
|
5028
|
-
const { createPlan } = await import("./plan-
|
|
4662
|
+
const { createPlan } = await import("./plan-5WHKVACB.js");
|
|
5029
4663
|
const plan = createPlan(project, choices, { force: options.force });
|
|
5030
4664
|
if (hasUninstalls && uninstallSelections) {
|
|
5031
4665
|
for (const selection of uninstallSelections) {
|
|
@@ -5041,17 +4675,28 @@ async function installUI(options = {}, executeOptions = {}) {
|
|
|
5041
4675
|
...executeOptions,
|
|
5042
4676
|
projectPath: project.projectPath
|
|
5043
4677
|
});
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
4678
|
+
let testCoverageResult;
|
|
4679
|
+
if (result.success && eslintRules?.some((r) => r.rule.id === "require-test-coverage")) {
|
|
4680
|
+
const eslintTargetPaths = choices.eslint?.packagePaths ?? [];
|
|
4681
|
+
for (const targetPath of eslintTargetPaths) {
|
|
4682
|
+
const coverageSetup = detectCoverageSetup(targetPath);
|
|
4683
|
+
if (coverageSetup.hasVitest && coverageSetup.hasCoverageConfig) {
|
|
4684
|
+
console.log(`
|
|
4685
|
+
${pc.blue("Running tests with coverage...")}`);
|
|
4686
|
+
const pm = detectPackageManager(targetPath);
|
|
4687
|
+
try {
|
|
4688
|
+
await runTestsWithCoverage(pm, targetPath);
|
|
4689
|
+
testCoverageResult = { ran: true, success: true };
|
|
4690
|
+
console.log(`${pc.green("\u2713")} Coverage data generated`);
|
|
4691
|
+
} catch (error) {
|
|
4692
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
4693
|
+
testCoverageResult = { ran: true, success: false, error: errorMsg };
|
|
4694
|
+
console.log(`${pc.yellow("\u26A0")} Tests completed with errors`);
|
|
4695
|
+
}
|
|
4696
|
+
}
|
|
5051
4697
|
}
|
|
5052
|
-
} else {
|
|
5053
|
-
console.log("\n\u26A0 Operation completed with errors");
|
|
5054
4698
|
}
|
|
4699
|
+
printInstallReport(result, testCoverageResult);
|
|
5055
4700
|
process.exit(result.success ? 0 : 1);
|
|
5056
4701
|
},
|
|
5057
4702
|
onError: (error) => {
|
|
@@ -5066,4 +4711,4 @@ async function installUI(options = {}, executeOptions = {}) {
|
|
|
5066
4711
|
export {
|
|
5067
4712
|
installUI
|
|
5068
4713
|
};
|
|
5069
|
-
//# sourceMappingURL=install-ui-
|
|
4714
|
+
//# sourceMappingURL=install-ui-CCZ3XJDE.js.map
|