uilint 0.2.22 → 0.2.23
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-PB5DLLVC.js +1000 -0
- package/dist/chunk-PB5DLLVC.js.map +1 -0
- package/dist/index.js +216 -16
- package/dist/index.js.map +1 -1
- package/dist/{install-ui-HTVB5HDB.js → install-ui-TXV7A34M.js} +212 -789
- package/dist/install-ui-TXV7A34M.js.map +1 -0
- package/package.json +3 -3
- package/dist/chunk-FRNXXIEM.js +0 -197
- package/dist/chunk-FRNXXIEM.js.map +0 -1
- package/dist/install-ui-HTVB5HDB.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/prompts.ts","../src/utils/next-detect.ts","../src/utils/eslint-config-inject.ts"],"sourcesContent":["/**\n * Shared clack/prompts utilities for UILint CLI\n * Provides branded intro/outro, spinners, and common UI patterns\n */\n\nimport * as p from \"@clack/prompts\";\nimport pc from \"picocolors\";\nimport { readFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\nimport { fileURLToPath } from \"url\";\n\n/**\n * Get the CLI version from package.json\n */\nfunction getCLIVersion(): string {\n try {\n const __dirname = dirname(fileURLToPath(import.meta.url));\n const pkgPath = join(__dirname, \"..\", \"..\", \"package.json\");\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\")) as {\n version?: string;\n };\n return pkg.version || \"0.0.0\";\n } catch {\n return \"0.0.0\";\n }\n}\n\n/**\n * Branded UILint intro with logo and version\n */\nexport function intro(title?: string): void {\n const version = getCLIVersion();\n const header = pc.bold(pc.cyan(\"◆ UILint\")) + pc.dim(` v${version}`);\n \n console.log();\n p.intro(title ? `${header} ${pc.dim(\"·\")} ${title}` : header);\n}\n\n/**\n * Styled outro with next steps\n */\nexport function outro(message: string): void {\n p.outro(pc.green(message));\n}\n\n/**\n * Cancel message when user exits\n */\nexport function cancel(message = \"Operation cancelled.\"): void {\n p.cancel(pc.yellow(message));\n process.exit(0);\n}\n\n/**\n * Check if user cancelled a prompt\n */\nexport function isCancel(value: unknown): value is symbol {\n return p.isCancel(value);\n}\n\n/**\n * Handle cancel check - exits if cancelled\n */\nexport function handleCancel<T>(value: T | symbol): T {\n if (p.isCancel(value)) {\n cancel();\n process.exit(0);\n }\n return value as T;\n}\n\n/**\n * Spinner wrapper with automatic error handling\n */\nexport async function withSpinner<T>(\n message: string,\n fn: (() => Promise<T>) | ((spinner: ReturnType<typeof p.spinner>) => Promise<T>)\n): Promise<T> {\n const s = p.spinner();\n s.start(message);\n try {\n const result =\n fn.length >= 1\n ? await (fn as (spinner: ReturnType<typeof p.spinner>) => Promise<T>)(s)\n : await (fn as () => Promise<T>)();\n s.stop(pc.green(\"✓ \") + message);\n return result;\n } catch (error) {\n s.stop(pc.red(\"✗ \") + message);\n throw error;\n }\n}\n\n/**\n * Spinner that can be updated\n */\nexport function createSpinner() {\n return p.spinner();\n}\n\n/**\n * Display a note box\n */\nexport function note(message: string, title?: string): void {\n p.note(message, title);\n}\n\n/**\n * Display a log message\n */\nexport function log(message: string): void {\n p.log.message(message);\n}\n\n/**\n * Display an info message\n */\nexport function logInfo(message: string): void {\n p.log.info(message);\n}\n\n/**\n * Display a success message\n */\nexport function logSuccess(message: string): void {\n p.log.success(message);\n}\n\n/**\n * Display a warning message\n */\nexport function logWarning(message: string): void {\n p.log.warn(message);\n}\n\n/**\n * Display an error message\n */\nexport function logError(message: string): void {\n p.log.error(message);\n}\n\n/**\n * Display a step message\n */\nexport function logStep(message: string): void {\n p.log.step(message);\n}\n\n/**\n * Select prompt wrapper\n */\nexport async function select<T extends string>(options: {\n message: string;\n options: Array<{ value: T; label: string; hint?: string }>;\n initialValue?: T;\n}): Promise<T> {\n const result = await p.select({\n message: options.message,\n options: options.options as { value: T; label: string; hint?: string }[],\n initialValue: options.initialValue,\n } as Parameters<typeof p.select>[0]);\n return handleCancel(result) as T;\n}\n\n/**\n * Confirm prompt wrapper\n */\nexport async function confirm(options: {\n message: string;\n initialValue?: boolean;\n}): Promise<boolean> {\n const result = await p.confirm({\n message: options.message,\n initialValue: options.initialValue ?? true,\n });\n return handleCancel(result);\n}\n\n/**\n * Text input prompt wrapper\n */\nexport async function text(options: {\n message: string;\n placeholder?: string;\n defaultValue?: string;\n validate?: (value: string) => string | Error | undefined;\n}): Promise<string> {\n const result = await p.text(options);\n return handleCancel(result);\n}\n\n/**\n * Multiselect prompt wrapper\n */\nexport async function multiselect<T extends string>(options: {\n message: string;\n options: Array<{ value: T; label: string; hint?: string }>;\n required?: boolean;\n initialValues?: T[];\n}): Promise<T[]> {\n const result = await p.multiselect({\n message: options.message,\n options: options.options as { value: T; label: string; hint?: string }[],\n required: options.required,\n initialValues: options.initialValues,\n } as Parameters<typeof p.multiselect>[0]);\n return handleCancel(result) as T[];\n}\n\n/**\n * Group of tasks displayed together\n */\nexport async function group<T extends Record<string, unknown>>(\n prompts: p.PromptGroup<T>,\n options?: p.PromptGroupOptions<T>\n): Promise<T> {\n const result = await p.group(prompts, options);\n return result;\n}\n\n// Re-export picocolors for consistent styling\nexport { pc };\n","import { existsSync, readdirSync } from \"fs\";\nimport { join } from \"path\";\n\nexport interface NextAppRouterDetection {\n /**\n * Relative path to the Next App Router root dir (either \"app\" or \"src/app\").\n */\n appRoot: string;\n /**\n * Absolute path to the App Router root dir.\n */\n appRootAbs: string;\n /**\n * Candidate entry files (relative paths) that are good injection targets.\n */\n candidates: string[];\n}\n\nfunction fileExists(projectPath: string, relPath: string): boolean {\n return existsSync(join(projectPath, relPath));\n}\n\nexport function detectNextAppRouter(\n projectPath: string\n): NextAppRouterDetection | null {\n const roots = [\"app\", join(\"src\", \"app\")];\n const candidates: string[] = [];\n\n let chosenRoot: string | null = null;\n for (const root of roots) {\n if (existsSync(join(projectPath, root))) {\n chosenRoot = root;\n break;\n }\n }\n\n if (!chosenRoot) return null;\n\n // Prioritize layout files (Next App Router canonical integration point).\n const entryCandidates = [\n join(chosenRoot, \"layout.tsx\"),\n join(chosenRoot, \"layout.jsx\"),\n join(chosenRoot, \"layout.ts\"),\n join(chosenRoot, \"layout.js\"),\n // Fallbacks (less ideal, but can work):\n join(chosenRoot, \"page.tsx\"),\n join(chosenRoot, \"page.jsx\"),\n ];\n\n for (const rel of entryCandidates) {\n if (fileExists(projectPath, rel)) candidates.push(rel);\n }\n\n // If nothing exists, still return detection so routes can be installed.\n return {\n appRoot: chosenRoot,\n appRootAbs: join(projectPath, chosenRoot),\n candidates,\n };\n}\n\nexport interface NextAppRouterProjectMatch {\n /**\n * Absolute path to the Next project root (dir containing app/ or src/app/).\n */\n projectPath: string;\n detection: NextAppRouterDetection;\n}\n\nconst DEFAULT_IGNORE_DIRS = new Set([\n \"node_modules\",\n \".git\",\n \".next\",\n \"dist\",\n \"build\",\n \"out\",\n \".turbo\",\n \".vercel\",\n \".cursor\",\n \"coverage\",\n \".uilint\",\n]);\n\n/**\n * Best-effort monorepo discovery for Next.js App Router apps.\n *\n * Walks down from `rootDir` looking for directories that contain `app/` or\n * `src/app/`. Skips common large/irrelevant dirs.\n */\nexport function findNextAppRouterProjects(\n rootDir: string,\n options?: { maxDepth?: number; ignoreDirs?: Set<string> }\n): NextAppRouterProjectMatch[] {\n const maxDepth = options?.maxDepth ?? 4;\n const ignoreDirs = options?.ignoreDirs ?? DEFAULT_IGNORE_DIRS;\n const results: NextAppRouterProjectMatch[] = [];\n const visited = new Set<string>();\n\n function walk(dir: string, depth: number) {\n if (depth > maxDepth) return;\n if (visited.has(dir)) return;\n visited.add(dir);\n\n const detection = detectNextAppRouter(dir);\n if (detection) {\n results.push({ projectPath: dir, detection });\n // Don't descend further once we found a project root (avoid nested hits).\n return;\n }\n\n let entries: Array<{ name: string; isDirectory: boolean }> = [];\n try {\n entries = readdirSync(dir, { withFileTypes: true }).map((d) => ({\n name: d.name,\n isDirectory: d.isDirectory(),\n }));\n } catch {\n return;\n }\n\n for (const ent of entries) {\n if (!ent.isDirectory) continue;\n if (ignoreDirs.has(ent.name)) continue;\n // Skip hidden dirs by default (except `src` which matters)\n if (ent.name.startsWith(\".\") && ent.name !== \".\") continue;\n walk(join(dir, ent.name), depth + 1);\n }\n }\n\n walk(rootDir, 0);\n return results;\n}\n","/**\n * Inject uilint-eslint rules into ESLint config\n *\n * Modifies eslint.config.{ts,mjs,js,cjs} to add uilint import and selected rules\n */\n\nimport { existsSync, readFileSync, writeFileSync } from \"fs\";\nimport { join, relative, dirname } from \"path\";\nimport type { RuleMetadata } from \"uilint-eslint\";\nimport { parseExpression, parseModule, generateCode } from \"magicast\";\nimport { findWorkspaceRoot } from \"uilint-core/node\";\n\nexport interface InstallEslintPluginOptions {\n projectPath: string;\n selectedRules: RuleMetadata[];\n force?: boolean;\n confirmAddMissingRules?: (\n relPath: string,\n missingRules: RuleMetadata[]\n ) => Promise<boolean>;\n}\n\nconst CONFIG_EXTENSIONS = [\".ts\", \".mjs\", \".js\", \".cjs\"];\n\n/**\n * Find the eslint.config file in a project\n */\nexport function findEslintConfigFile(projectPath: string): string | null {\n for (const ext of CONFIG_EXTENSIONS) {\n const configPath = join(projectPath, `eslint.config${ext}`);\n if (existsSync(configPath)) {\n return configPath;\n }\n }\n return null;\n}\n\n/**\n * Get the relative config filename for display\n */\nexport function getEslintConfigFilename(configPath: string): string {\n const parts = configPath.split(\"/\");\n return parts[parts.length - 1] || \"eslint.config.mjs\";\n}\n\n/**\n * Check if the source already has uilint rules configured\n */\nfunction hasUilintRules(source: string): boolean {\n return source.includes('\"uilint/') || source.includes(\"'uilint/\");\n}\n\n/**\n * Check if the source already has uilint imported\n */\nfunction hasUilintImport(source: string): boolean {\n return (\n source.includes('from \"uilint-eslint\"') ||\n source.includes(\"from 'uilint-eslint'\") ||\n source.includes('require(\"uilint-eslint\")') ||\n source.includes(\"require('uilint-eslint')\")\n );\n}\n\ntype UilintEslintConfigInfo = {\n /** Set of configured `uilint/*` rule IDs (without the `uilint/` prefix). */\n configuredRuleIds: Set<string>;\n /** Whether config appears to configure uilint rules. */\n configured: boolean;\n};\n\nfunction walkAst(node: any, visit: (n: any) => void): void {\n if (!node || typeof node !== \"object\") return;\n visit(node);\n for (const key of Object.keys(node)) {\n const v = (node as any)[key];\n if (!v) continue;\n if (Array.isArray(v)) {\n for (const item of v) walkAst(item, visit);\n } else if (typeof v === \"object\" && v.type) {\n walkAst(v, visit);\n }\n }\n}\n\nfunction isIdentifier(node: any, name?: string): boolean {\n return (\n !!node &&\n node.type === \"Identifier\" &&\n (name ? node.name === name : typeof node.name === \"string\")\n );\n}\n\nfunction isStringLiteral(node: any): node is { type: string; value: string } {\n return (\n !!node &&\n (node.type === \"StringLiteral\" || node.type === \"Literal\") &&\n typeof node.value === \"string\"\n );\n}\n\nfunction getObjectPropertyValue(obj: any, keyName: string): any | null {\n if (!obj || obj.type !== \"ObjectExpression\") return null;\n for (const prop of obj.properties ?? []) {\n if (!prop) continue;\n if (prop.type === \"ObjectProperty\" || prop.type === \"Property\") {\n const key = prop.key;\n const keyMatch =\n (key?.type === \"Identifier\" && key.name === keyName) ||\n (isStringLiteral(key) && key.value === keyName);\n if (keyMatch) return prop.value;\n }\n }\n return null;\n}\n\nfunction hasSpreadProperties(obj: any): boolean {\n if (!obj || obj.type !== \"ObjectExpression\") return false;\n return (obj.properties ?? []).some(\n (p: any) => p && (p.type === \"SpreadElement\" || p.type === \"SpreadProperty\")\n );\n}\n\nconst IGNORED_AST_KEYS = new Set([\n \"loc\",\n \"start\",\n \"end\",\n \"extra\",\n \"leadingComments\",\n \"trailingComments\",\n \"innerComments\",\n]);\n\nfunction normalizeAstForCompare(node: any): any {\n if (node === null) return null;\n if (node === undefined) return undefined;\n if (typeof node !== \"object\") return node;\n if (Array.isArray(node)) return node.map(normalizeAstForCompare);\n\n const out: Record<string, any> = {};\n const keys = Object.keys(node)\n .filter((k) => !IGNORED_AST_KEYS.has(k))\n .sort();\n for (const k of keys) {\n // Avoid proxy-ish or non-serializable fields if present.\n if (k.startsWith(\"$\")) continue;\n out[k] = normalizeAstForCompare(node[k]);\n }\n return out;\n}\n\nfunction astEquivalent(a: any, b: any): boolean {\n try {\n return (\n JSON.stringify(normalizeAstForCompare(a)) ===\n JSON.stringify(normalizeAstForCompare(b))\n );\n } catch {\n return false;\n }\n}\n\nfunction collectUilintRuleIdsFromRulesObject(rulesObj: any): Set<string> {\n const ids = new Set<string>();\n if (!rulesObj || rulesObj.type !== \"ObjectExpression\") return ids;\n for (const prop of rulesObj.properties ?? []) {\n if (!prop) continue;\n if (prop.type !== \"ObjectProperty\" && prop.type !== \"Property\") continue;\n const key = prop.key;\n if (!isStringLiteral(key)) continue;\n const val = key.value;\n if (typeof val !== \"string\") continue;\n if (val.startsWith(\"uilint/\")) {\n ids.add(val.slice(\"uilint/\".length));\n }\n }\n return ids;\n}\n\nfunction findExportedConfigArrayExpression(mod: any): {\n kind: \"esm\" | \"cjs\";\n arrayExpr: any;\n program: any;\n} | null {\n function unwrapExpression(expr: any): any {\n let e = expr;\n // Best-effort unwrap for TS/parenthesized wrappers. (These can appear if the\n // config is authored in TS/JS with type assertions or parentheses.)\n while (e) {\n if (e.type === \"TSAsExpression\" || e.type === \"TSNonNullExpression\") {\n e = e.expression;\n continue;\n }\n if (e.type === \"TSSatisfiesExpression\") {\n e = e.expression;\n continue;\n }\n if (e.type === \"ParenthesizedExpression\") {\n e = e.expression;\n continue;\n }\n break;\n }\n return e;\n }\n\n function resolveTopLevelIdentifierToArrayExpr(\n program: any,\n name: string\n ): any | null {\n if (!program || program.type !== \"Program\") return null;\n for (const stmt of program.body ?? []) {\n if (stmt?.type !== \"VariableDeclaration\") continue;\n for (const decl of stmt.declarations ?? []) {\n const id = decl?.id;\n if (!isIdentifier(id, name)) continue;\n const init = unwrapExpression(decl?.init);\n if (!init) return null;\n if (init.type === \"ArrayExpression\") return init;\n if (\n init.type === \"CallExpression\" &&\n isIdentifier(init.callee, \"defineConfig\") &&\n unwrapExpression(init.arguments?.[0])?.type === \"ArrayExpression\"\n ) {\n return unwrapExpression(init.arguments?.[0]);\n }\n return null;\n }\n }\n return null;\n }\n\n // Prefer reading directly from the program AST so we can handle:\n // - export default [ ... ]\n // - export default defineConfig([ ... ])\n // - export default eslintConfig; (where eslintConfig is a top-level array)\n const program = mod?.$ast;\n if (program && program.type === \"Program\") {\n for (const stmt of program.body ?? []) {\n if (!stmt || stmt.type !== \"ExportDefaultDeclaration\") continue;\n const decl = unwrapExpression(stmt.declaration);\n if (!decl) break;\n\n if (decl.type === \"ArrayExpression\") {\n return { kind: \"esm\", arrayExpr: decl, program };\n }\n if (\n decl.type === \"CallExpression\" &&\n isIdentifier(decl.callee, \"defineConfig\") &&\n unwrapExpression(decl.arguments?.[0])?.type === \"ArrayExpression\"\n ) {\n return {\n kind: \"esm\",\n arrayExpr: unwrapExpression(decl.arguments?.[0]),\n program,\n };\n }\n if (decl.type === \"Identifier\" && typeof decl.name === \"string\") {\n const resolved = resolveTopLevelIdentifierToArrayExpr(\n program,\n decl.name\n );\n if (resolved) return { kind: \"esm\", arrayExpr: resolved, program };\n }\n break;\n }\n }\n\n // CommonJS: module.exports = [ ... ] OR module.exports = defineConfig([ ... ])\n if (!program || program.type !== \"Program\") return null;\n\n for (const stmt of program.body ?? []) {\n if (!stmt || stmt.type !== \"ExpressionStatement\") continue;\n const expr = stmt.expression;\n if (!expr || expr.type !== \"AssignmentExpression\") continue;\n const left = expr.left;\n const right = expr.right;\n const isModuleExports =\n left?.type === \"MemberExpression\" &&\n isIdentifier(left.object, \"module\") &&\n isIdentifier(left.property, \"exports\");\n if (!isModuleExports) continue;\n\n if (right?.type === \"ArrayExpression\") {\n return { kind: \"cjs\", arrayExpr: right, program };\n }\n if (\n right?.type === \"CallExpression\" &&\n isIdentifier(right.callee, \"defineConfig\") &&\n right.arguments?.[0]?.type === \"ArrayExpression\"\n ) {\n return { kind: \"cjs\", arrayExpr: right.arguments[0], program };\n }\n if (right?.type === \"Identifier\" && typeof right.name === \"string\") {\n const resolved = resolveTopLevelIdentifierToArrayExpr(\n program,\n right.name\n );\n if (resolved) return { kind: \"cjs\", arrayExpr: resolved, program };\n }\n }\n\n return null;\n}\n\nfunction collectConfiguredUilintRuleIdsFromConfigArray(\n arrayExpr: any\n): Set<string> {\n const ids = new Set<string>();\n if (!arrayExpr || arrayExpr.type !== \"ArrayExpression\") return ids;\n for (const el of arrayExpr.elements ?? []) {\n if (!el || el.type !== \"ObjectExpression\") continue;\n const rules = getObjectPropertyValue(el, \"rules\");\n for (const id of collectUilintRuleIdsFromRulesObject(rules)) ids.add(id);\n }\n return ids;\n}\n\nfunction findExistingUilintRulesObject(arrayExpr: any): {\n configObj: any | null;\n rulesObj: any | null;\n safeToMutate: boolean;\n} {\n if (!arrayExpr || arrayExpr.type !== \"ArrayExpression\") {\n return { configObj: null, rulesObj: null, safeToMutate: false };\n }\n\n for (const el of arrayExpr.elements ?? []) {\n if (!el || el.type !== \"ObjectExpression\") continue;\n\n const plugins = getObjectPropertyValue(el, \"plugins\");\n const rules = getObjectPropertyValue(el, \"rules\");\n\n const hasUilintPlugin =\n plugins?.type === \"ObjectExpression\" &&\n getObjectPropertyValue(plugins, \"uilint\") !== null;\n\n const uilintIds = collectUilintRuleIdsFromRulesObject(rules);\n const hasUilintRules = uilintIds.size > 0;\n\n if (!hasUilintPlugin && !hasUilintRules) continue;\n\n const safe =\n rules?.type === \"ObjectExpression\" && !hasSpreadProperties(rules);\n return { configObj: el, rulesObj: rules, safeToMutate: safe };\n }\n\n return { configObj: null, rulesObj: null, safeToMutate: false };\n}\n\nfunction collectTopLevelBindings(program: any): Set<string> {\n const names = new Set<string>();\n if (!program || program.type !== \"Program\") return names;\n\n for (const stmt of program.body ?? []) {\n if (stmt?.type === \"VariableDeclaration\") {\n for (const decl of stmt.declarations ?? []) {\n const id = decl?.id;\n if (id?.type === \"Identifier\" && typeof id.name === \"string\") {\n names.add(id.name);\n }\n }\n } else if (stmt?.type === \"FunctionDeclaration\") {\n if (stmt.id?.type === \"Identifier\" && typeof stmt.id.name === \"string\") {\n names.add(stmt.id.name);\n }\n }\n }\n return names;\n}\n\nfunction chooseUniqueIdentifier(base: string, used: Set<string>): string {\n if (!used.has(base)) return base;\n let i = 2;\n while (used.has(`${base}${i}`)) i++;\n return `${base}${i}`;\n}\n\n/**\n * Add imports for local rules from .uilint/rules/\n */\nfunction addLocalRuleImportsAst(\n mod: any,\n selectedRules: RuleMetadata[],\n configPath: string,\n rulesRoot: string,\n fileExtension: string = \".js\"\n): { importNames: Map<string, string>; changed: boolean } {\n const importNames = new Map<string, string>();\n let changed = false;\n\n // Calculate relative path from config file to .uilint/rules/\n const configDir = dirname(configPath);\n const rulesDir = join(rulesRoot, \".uilint\", \"rules\");\n const relativeRulesPath = relative(configDir, rulesDir).replace(/\\\\/g, \"/\");\n\n // Ensure it starts with ./ or ../ (note: `.foo` is NOT a valid relative import)\n const normalizedRulesPath =\n relativeRulesPath.startsWith(\"./\") || relativeRulesPath.startsWith(\"../\")\n ? relativeRulesPath\n : `./${relativeRulesPath}`;\n\n const used = collectTopLevelBindings(mod.$ast);\n\n for (const rule of selectedRules) {\n // Generate a safe import name (e.g., noArbitraryTailwindRule)\n const importName = chooseUniqueIdentifier(\n `${rule.id\n .replace(/-([a-z])/g, (_: string, c: string) => c.toUpperCase())\n .replace(/^./, (c: string) => c.toUpperCase())}Rule`,\n used\n );\n importNames.set(rule.id, importName);\n used.add(importName);\n\n // Add import statement\n const rulePath = `${normalizedRulesPath}/${rule.id}${fileExtension}`;\n mod.imports.$add({\n imported: \"default\",\n local: importName,\n from: rulePath,\n });\n changed = true;\n }\n\n return { importNames, changed };\n}\n\n/**\n * Add require statements for local rules (CommonJS)\n */\nfunction addLocalRuleRequiresAst(\n program: any,\n selectedRules: RuleMetadata[],\n configPath: string,\n rulesRoot: string,\n fileExtension: string = \".js\"\n): { importNames: Map<string, string>; changed: boolean } {\n const importNames = new Map<string, string>();\n let changed = false;\n\n if (!program || program.type !== \"Program\") {\n return { importNames, changed };\n }\n\n // Calculate relative path from config file to .uilint/rules/\n const configDir = dirname(configPath);\n const rulesDir = join(rulesRoot, \".uilint\", \"rules\");\n const relativeRulesPath = relative(configDir, rulesDir).replace(/\\\\/g, \"/\");\n\n // Ensure it starts with ./ or ../ (note: `.foo` is NOT a valid relative require)\n const normalizedRulesPath =\n relativeRulesPath.startsWith(\"./\") || relativeRulesPath.startsWith(\"../\")\n ? relativeRulesPath\n : `./${relativeRulesPath}`;\n\n const used = collectTopLevelBindings(program);\n\n for (const rule of selectedRules) {\n // Generate a safe import name\n const importName = chooseUniqueIdentifier(\n `${rule.id\n .replace(/-([a-z])/g, (_: string, c: string) => c.toUpperCase())\n .replace(/^./, (c: string) => c.toUpperCase())}Rule`,\n used\n );\n importNames.set(rule.id, importName);\n used.add(importName);\n\n // Add require statement\n const rulePath = `${normalizedRulesPath}/${rule.id}${fileExtension}`;\n const stmtMod = parseModule(\n `const ${importName} = require(\"${rulePath}\");`\n );\n const stmt = (stmtMod.$ast as any).body?.[0];\n if (stmt) {\n // Place after a leading \"use strict\" if present.\n let insertAt = 0;\n const first = program.body?.[0];\n if (\n first?.type === \"ExpressionStatement\" &&\n first.expression?.type === \"StringLiteral\" &&\n first.expression.value === \"use strict\"\n ) {\n insertAt = 1;\n }\n program.body.splice(insertAt, 0, stmt);\n changed = true;\n }\n }\n\n return { importNames, changed };\n}\n\nfunction appendUilintConfigBlockToArray(\n arrayExpr: any,\n selectedRules: RuleMetadata[],\n ruleImportNames: Map<string, string>\n): void {\n // Build plugin object with local rule imports\n const pluginRulesCode = Array.from(ruleImportNames.entries())\n .map(([ruleId, importName]) => ` \"${ruleId}\": ${importName},`)\n .join(\"\\n\");\n\n const rulesPropsCode = selectedRules\n .map((r) => {\n const ruleKey = `uilint/${r.id}`;\n const valueCode =\n r.defaultOptions && r.defaultOptions.length > 0\n ? `[\"${r.defaultSeverity}\", ...${JSON.stringify(\n r.defaultOptions,\n null,\n 2\n )}]`\n : `\"${r.defaultSeverity}\"`;\n return ` \"${ruleKey}\": ${valueCode},`;\n })\n .join(\"\\n\");\n\n const blockCode = `{\n files: [\n \"src/**/*.{js,jsx,ts,tsx}\",\n \"app/**/*.{js,jsx,ts,tsx}\",\n \"pages/**/*.{js,jsx,ts,tsx}\",\n ],\n plugins: {\n uilint: {\n rules: {\n${pluginRulesCode}\n },\n },\n },\n rules: {\n${rulesPropsCode}\n },\n }`;\n\n const objExpr = (parseExpression(blockCode) as any).$ast;\n arrayExpr.elements.push(objExpr);\n}\n\nfunction getUilintEslintConfigInfoFromSourceAst(source: string):\n | {\n info: UilintEslintConfigInfo;\n mod: any;\n arrayExpr: any;\n kind: \"esm\" | \"cjs\";\n }\n | { error: string } {\n try {\n const mod = parseModule(source);\n const found = findExportedConfigArrayExpression(mod);\n if (!found) {\n return {\n error:\n \"Could not locate an exported ESLint flat config array (expected `export default [...]`, `export default defineConfig([...])`, `module.exports = [...]`, or `module.exports = defineConfig([...])`).\",\n };\n }\n\n const configuredRuleIds = collectConfiguredUilintRuleIdsFromConfigArray(\n found.arrayExpr\n );\n const existingUilint = findExistingUilintRulesObject(found.arrayExpr);\n const configured =\n configuredRuleIds.size > 0 || existingUilint.configObj !== null;\n\n return {\n info: { configuredRuleIds, configured },\n mod,\n arrayExpr: found.arrayExpr,\n kind: found.kind,\n };\n } catch {\n return {\n error:\n \"Unable to parse ESLint config as JavaScript. Please update it manually or simplify the config so it can be safely auto-modified.\",\n };\n }\n}\n\nexport function getUilintEslintConfigInfoFromSource(\n source: string\n): UilintEslintConfigInfo {\n const ast = getUilintEslintConfigInfoFromSourceAst(source);\n if (\"error\" in ast) {\n // Fallback (best-effort) to string heuristics for scan-only scenarios.\n const configuredRuleIds = extractConfiguredUilintRuleIds(source);\n return {\n configuredRuleIds,\n configured: configuredRuleIds.size > 0,\n };\n }\n return ast.info;\n}\n\nfunction findEsmExportedConfigArrayStartIndex(source: string): number | null {\n // Supported:\n // - export default [ ... ]\n // - export default defineConfig([ ... ])\n const patterns: RegExp[] = [\n /export\\s+default\\s+\\[/,\n /export\\s+default\\s+defineConfig\\s*\\(\\s*\\[/,\n ];\n\n for (const re of patterns) {\n const m = source.match(re);\n if (!m || m.index === undefined) continue;\n return m.index + m[0].length;\n }\n\n return null;\n}\n\nfunction findCommonJsExportedConfigArrayStartIndex(\n source: string\n): number | null {\n // Supported:\n // - module.exports = [ ... ]\n // - module.exports = defineConfig([ ... ]) (best-effort)\n const patterns: RegExp[] = [\n /module\\.exports\\s*=\\s*\\[/,\n /module\\.exports\\s*=\\s*defineConfig\\s*\\(\\s*\\[/,\n ];\n\n for (const re of patterns) {\n const m = source.match(re);\n if (!m || m.index === undefined) continue;\n return m.index + m[0].length;\n }\n\n return null;\n}\n\n/**\n * Extract configured uilint rule IDs from source.\n * Matches keys like: \"uilint/no-arbitrary-tailwind\": \"error\"\n */\nfunction extractConfiguredUilintRuleIds(source: string): Set<string> {\n const ids = new Set<string>();\n const re = /[\"']uilint\\/([^\"']+)[\"']\\s*:/g;\n for (const m of source.matchAll(re)) {\n if (m[1]) ids.add(m[1]);\n }\n return ids;\n}\n\nfunction getMissingSelectedRules(\n selectedRules: RuleMetadata[],\n configuredIds: Set<string>\n): RuleMetadata[] {\n return selectedRules.filter((r) => !configuredIds.has(r.id));\n}\n\n/**\n * Get rules that exist but need updating (different options or severity)\n */\nfunction buildDesiredRuleValueExpression(rule: RuleMetadata): string {\n if (rule.defaultOptions && rule.defaultOptions.length > 0) {\n // Match the shape we generate elsewhere: [\"severity\", ...[options...]]\n return `[\"${rule.defaultSeverity}\", ...${JSON.stringify(\n rule.defaultOptions,\n null,\n 2\n )}]`;\n }\n return `\"${rule.defaultSeverity}\"`;\n}\n\nfunction collectUilintRuleValueNodesFromConfigArray(\n arrayExpr: any\n): Map<string, any> {\n const out = new Map<string, any>();\n if (!arrayExpr || arrayExpr.type !== \"ArrayExpression\") return out;\n\n for (const el of arrayExpr.elements ?? []) {\n if (!el || el.type !== \"ObjectExpression\") continue;\n const rules = getObjectPropertyValue(el, \"rules\");\n if (!rules || rules.type !== \"ObjectExpression\") continue;\n\n for (const prop of rules.properties ?? []) {\n if (!prop) continue;\n if (prop.type !== \"ObjectProperty\" && prop.type !== \"Property\") continue;\n const key = prop.key;\n if (!isStringLiteral(key)) continue;\n const k = key.value;\n if (typeof k !== \"string\" || !k.startsWith(\"uilint/\")) continue;\n const id = k.slice(\"uilint/\".length);\n // First occurrence wins; that's enough for detecting up-to-date configs.\n if (!out.has(id)) out.set(id, prop.value);\n }\n }\n\n return out;\n}\n\nfunction getRulesNeedingUpdate(\n selectedRules: RuleMetadata[],\n configuredIds: Set<string>,\n arrayExpr: any\n): RuleMetadata[] {\n // Only consider rules that are already configured, and only update if the\n // existing severity/options differ from what we would generate.\n const existingVals = collectUilintRuleValueNodesFromConfigArray(arrayExpr);\n\n return selectedRules.filter((r) => {\n if (!configuredIds.has(r.id)) return false;\n const existing = existingVals.get(r.id);\n if (!existing) return true;\n\n const desiredExpr = buildDesiredRuleValueExpression(r);\n const desiredAst = (parseExpression(desiredExpr) as any).$ast;\n return !astEquivalent(existing, desiredAst);\n });\n}\n\n/**\n * Generate a single rule config string\n */\nfunction generateSingleRuleConfig(rule: RuleMetadata): string {\n const ruleKey = `\"uilint/${rule.id}\"`;\n\n if (rule.defaultOptions && rule.defaultOptions.length > 0) {\n // Rule with options\n const optionsStr = JSON.stringify(rule.defaultOptions, null, 6)\n .split(\"\\n\")\n .join(\"\\n \");\n return ` ${ruleKey}: [\"${rule.defaultSeverity}\", ...${optionsStr}],`;\n } else {\n // Simple rule\n return ` ${ruleKey}: \"${rule.defaultSeverity}\",`;\n }\n}\n\n/**\n * Add the uilint import to the source if not present\n */\nfunction ensureUilintImport(source: string, isCommonJS: boolean): string {\n if (hasUilintImport(source)) {\n return source;\n }\n\n const importLine = isCommonJS\n ? `const uilint = require(\"uilint-eslint\");\\n`\n : `import uilint from \"uilint-eslint\";\\n`;\n\n // Find the last import/require statement and insert after it\n const header = source.slice(0, Math.min(source.length, 5000));\n const importRegex = isCommonJS\n ? /^(?:const|var|let)\\s+.*?=\\s*require\\([^)]+\\);?\\s*$/gm\n : /^import[\\s\\S]*?;\\s*$/gm;\n\n let lastImportEnd = -1;\n for (const m of header.matchAll(importRegex)) {\n lastImportEnd = (m.index ?? 0) + m[0].length;\n }\n\n if (lastImportEnd !== -1) {\n return (\n source.slice(0, lastImportEnd) +\n \"\\n\" +\n importLine +\n source.slice(lastImportEnd)\n );\n }\n\n // No imports found, add at the beginning\n return importLine + source;\n}\n\n/**\n * Generate the rules config object from selected rules\n */\nfunction generateRulesConfig(selectedRules: RuleMetadata[]): string {\n const lines: string[] = [];\n\n for (const rule of selectedRules) {\n const ruleKey = `\"uilint/${rule.id}\"`;\n\n if (rule.defaultOptions && rule.defaultOptions.length > 0) {\n // Rule with options\n const optionsStr = JSON.stringify(rule.defaultOptions, null, 6)\n .split(\"\\n\")\n .join(\"\\n \");\n lines.push(\n ` ${ruleKey}: [\"${rule.defaultSeverity}\", ...${optionsStr}],`\n );\n } else {\n // Simple rule\n lines.push(` ${ruleKey}: \"${rule.defaultSeverity}\",`);\n }\n }\n\n return lines.join(\"\\n\");\n}\n\nfunction detectIndent(source: string, index: number): string {\n const lineStart = source.lastIndexOf(\"\\n\", index);\n const start = lineStart === -1 ? 0 : lineStart + 1;\n const line = source.slice(start, index);\n const m = line.match(/^\\s*/);\n return m?.[0] ?? \"\";\n}\n\n/**\n * Insert missing uilint rule keys into an existing `rules: { ... }` object\n * that already contains at least one \"uilint/\" key.\n *\n * This is intentionally a best-effort string transform (no JS AST dependency).\n */\nfunction insertMissingRulesIntoExistingRulesObject(\n source: string,\n missingRules: RuleMetadata[]\n): string {\n if (missingRules.length === 0) return source;\n\n // Anchor on an existing uilint rule key, then look backwards for the\n // nearest `rules:` preceding it.\n const uilintKeyMatch = source.match(/[\"']uilint\\/[^\"']+[\"']\\s*:/);\n if (!uilintKeyMatch || uilintKeyMatch.index === undefined) return source;\n\n const uilintKeyIndex = uilintKeyMatch.index;\n const searchStart = Math.max(0, uilintKeyIndex - 4000);\n const before = source.slice(searchStart, uilintKeyIndex);\n const rulesKwIndexRel = before.lastIndexOf(\"rules\");\n if (rulesKwIndexRel === -1) return source;\n\n const rulesKwIndex = searchStart + rulesKwIndexRel;\n const braceOpenIndex = source.indexOf(\"{\", rulesKwIndex);\n if (braceOpenIndex === -1 || braceOpenIndex > uilintKeyIndex) return source;\n\n // Find the matching closing brace for the rules object.\n let depth = 0;\n let braceCloseIndex = -1;\n for (let i = braceOpenIndex; i < source.length; i++) {\n const ch = source[i];\n if (ch === \"{\") depth++;\n else if (ch === \"}\") {\n depth--;\n if (depth === 0) {\n braceCloseIndex = i;\n break;\n }\n }\n }\n if (braceCloseIndex === -1) return source;\n\n const rulesIndent = detectIndent(source, braceOpenIndex);\n const entryIndent = rulesIndent + \" \";\n const entryTextRaw = generateRulesConfig(missingRules);\n const entryText = entryTextRaw\n .split(\"\\n\")\n .map((l) => (l.trim().length === 0 ? l : entryIndent + l.trimStart()))\n .join(\"\\n\");\n\n const insertion =\n (source.slice(braceOpenIndex + 1, braceCloseIndex).trim().length === 0\n ? \"\\n\"\n : \"\\n\") +\n entryText +\n \"\\n\" +\n rulesIndent;\n\n return (\n source.slice(0, braceCloseIndex) + insertion + source.slice(braceCloseIndex)\n );\n}\n\n/**\n * Find the end of a rule value in the source code\n * Handles: \"error\", [\"error\"], [\"error\", {...}], [\"error\", ...[{...}]]\n */\nfunction findRuleValueEnd(source: string, startIndex: number): number {\n let pos = startIndex;\n let depth = 0;\n let inString = false;\n let stringChar = \"\";\n let foundArray = false;\n\n while (pos < source.length) {\n const ch = source[pos];\n const prevCh = pos > 0 ? source[pos - 1] : \"\";\n\n // Handle string literals\n if (!inString && (ch === '\"' || ch === \"'\")) {\n inString = true;\n stringChar = ch;\n } else if (inString && ch === stringChar && prevCh !== \"\\\\\") {\n inString = false;\n } else if (!inString) {\n // Track brackets/braces\n if (ch === \"[\") {\n depth++;\n foundArray = true;\n } else if (ch === \"]\") {\n depth--;\n if (depth === 0 && foundArray) {\n // Found the end of the array\n pos++;\n // Skip whitespace and include trailing comma if present\n while (pos < source.length && /\\s/.test(source[pos])) {\n pos++;\n }\n if (pos < source.length && source[pos] === \",\") {\n pos++;\n }\n return pos;\n }\n } else if (ch === \"{\" || ch === \"(\") {\n depth++;\n } else if (ch === \"}\" || ch === \")\") {\n depth--;\n } else if (!foundArray && depth === 0) {\n // Simple string value - ends at comma or closing brace\n if (ch === \",\" || ch === \"}\") {\n return pos + (ch === \",\" ? 1 : 0);\n }\n }\n }\n\n pos++;\n }\n\n return pos;\n}\n\n/**\n * Update existing uilint rule configurations with new options/severity\n *\n * This finds existing rule entries and replaces them with updated configurations.\n * Uses a more robust approach to handle multi-line rules with spread syntax.\n */\nfunction updateExistingRulesWithNewOptions(\n source: string,\n rulesToUpdate: RuleMetadata[]\n): string {\n if (rulesToUpdate.length === 0) return source;\n\n let updated = source;\n\n // Process rules in reverse order to avoid index shifting issues\n for (let i = rulesToUpdate.length - 1; i >= 0; i--) {\n const rule = rulesToUpdate[i]!;\n const ruleKeyPattern = new RegExp(\n `[\"']uilint/${rule.id.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")}[\"']\\\\s*:`,\n \"g\"\n );\n\n // Find all occurrences (should only be one, but be safe)\n const matches: Array<{ index: number; length: number }> = [];\n let match;\n while ((match = ruleKeyPattern.exec(updated)) !== null) {\n if (match.index !== undefined) {\n matches.push({ index: match.index, length: match[0].length });\n }\n }\n\n // Process matches in reverse order\n for (let j = matches.length - 1; j >= 0; j--) {\n const keyMatch = matches[j]!;\n const keyStart = keyMatch.index;\n const keyEnd = keyStart + keyMatch.length;\n\n // Find the value start (after colon and whitespace)\n let valueStart = keyEnd;\n while (valueStart < updated.length && /\\s/.test(updated[valueStart])) {\n valueStart++;\n }\n\n // Find the value end\n const valueEnd = findRuleValueEnd(updated, valueStart);\n\n // Generate new rule config\n const newRuleConfig = generateSingleRuleConfig(rule);\n\n // Find the indentation of the rule key line\n const indent = detectIndent(updated, keyStart);\n\n // Replace the old rule with the new one\n const before = updated.slice(0, keyStart);\n const after = updated.slice(valueEnd);\n\n updated = before + newRuleConfig + \"\\n\" + indent + after;\n }\n }\n\n return updated;\n}\n\n/**\n * Inject uilint rules into the export default array\n */\nfunction injectUilintRules(\n source: string,\n selectedRules: RuleMetadata[]\n): { source: string; injected: boolean } {\n if (hasUilintRules(source)) {\n // Already has uilint rules - don't inject again\n return { source, injected: false };\n }\n\n const rulesConfig = generateRulesConfig(selectedRules);\n\n const configBlock = ` {\n files: [\n \"src/**/*.{js,jsx,ts,tsx}\",\n \"app/**/*.{js,jsx,ts,tsx}\",\n \"pages/**/*.{js,jsx,ts,tsx}\",\n ],\n plugins: { uilint: uilint },\n rules: {\n${rulesConfig}\n },\n },`;\n\n const arrayStart = findEsmExportedConfigArrayStartIndex(source);\n if (arrayStart === null) {\n return { source, injected: false };\n }\n\n const afterExport = source.slice(arrayStart);\n\n // Insert at the beginning of the array (after opening bracket)\n // Add a newline if the array doesn't start on a new line\n const needsNewline = !afterExport.trimStart().startsWith(\"\\n\");\n const insertion = needsNewline\n ? \"\\n\" + configBlock + \"\\n\"\n : configBlock + \"\\n\";\n\n return {\n source: source.slice(0, arrayStart) + insertion + source.slice(arrayStart),\n injected: true,\n };\n}\n\n/**\n * Inject uilint rules into the CommonJS export\n */\nfunction injectUilintRulesCommonJS(\n source: string,\n selectedRules: RuleMetadata[]\n): { source: string; injected: boolean } {\n if (hasUilintRules(source)) {\n return { source, injected: false };\n }\n\n const rulesConfig = generateRulesConfig(selectedRules);\n\n const configBlock = ` {\n files: [\n \"src/**/*.{js,jsx,ts,tsx}\",\n \"app/**/*.{js,jsx,ts,tsx}\",\n \"pages/**/*.{js,jsx,ts,tsx}\",\n ],\n plugins: { uilint: uilint },\n rules: {\n${rulesConfig}\n },\n },`;\n\n const arrayStart = findCommonJsExportedConfigArrayStartIndex(source);\n if (arrayStart === null) {\n return { source, injected: false };\n }\n\n const afterExport = source.slice(arrayStart);\n const needsNewline = !afterExport.trimStart().startsWith(\"\\n\");\n const insertion = needsNewline\n ? \"\\n\" + configBlock + \"\\n\"\n : configBlock + \"\\n\";\n\n return {\n source: source.slice(0, arrayStart) + insertion + source.slice(arrayStart),\n injected: true,\n };\n}\n\n/**\n * Install uilint-eslint into eslint config\n */\nexport async function installEslintPlugin(\n opts: InstallEslintPluginOptions\n): Promise<{\n configFile: string | null;\n modified: boolean;\n missingRuleIds: string[];\n configured: boolean;\n error?: string;\n}> {\n const configPath = findEslintConfigFile(opts.projectPath);\n\n if (!configPath) {\n return {\n configFile: null,\n modified: false,\n missingRuleIds: [],\n configured: false,\n };\n }\n\n const configFilename = getEslintConfigFilename(configPath);\n const original = readFileSync(configPath, \"utf-8\");\n const isCommonJS = configPath.endsWith(\".cjs\");\n\n const ast = getUilintEslintConfigInfoFromSourceAst(original);\n if (\"error\" in ast) {\n return {\n configFile: configFilename,\n modified: false,\n missingRuleIds: [],\n configured: false,\n error: ast.error,\n };\n }\n\n const { info, mod, arrayExpr, kind } = ast;\n const configuredIds = info.configuredRuleIds;\n\n const missingRules = getMissingSelectedRules(\n opts.selectedRules,\n configuredIds\n );\n const rulesToUpdate = getRulesNeedingUpdate(\n opts.selectedRules,\n configuredIds,\n arrayExpr\n );\n\n // Decide what rules to apply, respecting prompts.\n let rulesToApply: RuleMetadata[] = [];\n if (!info.configured) {\n rulesToApply = opts.selectedRules;\n } else {\n // When already configured, we only apply updates + missing rules.\n rulesToApply = [...missingRules, ...rulesToUpdate];\n if (missingRules.length > 0 && !opts.force) {\n const ok = await opts.confirmAddMissingRules?.(\n configFilename,\n missingRules\n );\n if (!ok) {\n return {\n configFile: configFilename,\n modified: false,\n missingRuleIds: missingRules.map((r) => r.id),\n configured: true,\n };\n }\n }\n }\n\n if (rulesToApply.length === 0) {\n return {\n configFile: configFilename,\n modified: false,\n missingRuleIds: missingRules.map((r) => r.id),\n configured: info.configured,\n };\n }\n\n let modifiedAst = false;\n\n // Check if .uilint/rules/ directory exists alongside this target package/app.\n // (Also allow a fallback to workspace root for backwards compatibility.)\n const localRulesDir = join(opts.projectPath, \".uilint\", \"rules\");\n const workspaceRoot = findWorkspaceRoot(opts.projectPath);\n const workspaceRulesDir = join(workspaceRoot, \".uilint\", \"rules\");\n\n const rulesRoot = existsSync(localRulesDir)\n ? opts.projectPath\n : workspaceRoot;\n\n // Always use local rules (they should have been copied by the plan phase)\n // For TypeScript configs, omit the extension (TypeScript will resolve .ts files)\n // For JavaScript configs, use .js extension\n // Note: We don't use .ts extension directly because it requires allowImportingTsExtensions\n const isTypeScriptConfig = configPath.endsWith(\".ts\");\n let fileExtension = isTypeScriptConfig ? \"\" : \".js\";\n\n let ruleImportNames: Map<string, string> | undefined;\n\n // Add imports for local rules\n if (kind === \"esm\") {\n const result = addLocalRuleImportsAst(\n mod,\n rulesToApply,\n configPath,\n rulesRoot,\n fileExtension\n );\n ruleImportNames = result.importNames;\n if (result.changed) modifiedAst = true;\n } else {\n const result = addLocalRuleRequiresAst(\n mod.$ast,\n rulesToApply,\n configPath,\n rulesRoot,\n fileExtension\n );\n ruleImportNames = result.importNames;\n if (result.changed) modifiedAst = true;\n }\n\n // Add config block with local rules\n if (ruleImportNames && ruleImportNames.size > 0) {\n appendUilintConfigBlockToArray(arrayExpr, rulesToApply, ruleImportNames);\n modifiedAst = true;\n }\n\n // Ensure uilint-eslint import for utilities (createRule, etc.)\n if (!info.configured) {\n if (kind === \"esm\") {\n // Import createRule utility from uilint-eslint\n mod.imports.$add({\n imported: \"createRule\",\n local: \"createRule\",\n from: \"uilint-eslint\",\n });\n modifiedAst = true;\n } else {\n // CommonJS: add require for createRule\n const stmtMod = parseModule(\n `const { createRule } = require(\"uilint-eslint\");`\n );\n const stmt = (stmtMod.$ast as any).body?.[0];\n if (stmt) {\n let insertAt = 0;\n const first = mod.$ast.body?.[0];\n if (\n first?.type === \"ExpressionStatement\" &&\n first.expression?.type === \"StringLiteral\" &&\n first.expression.value === \"use strict\"\n ) {\n insertAt = 1;\n }\n mod.$ast.body.splice(insertAt, 0, stmt);\n modifiedAst = true;\n }\n }\n }\n\n const updated = modifiedAst ? generateCode(mod).code : original;\n\n if (updated !== original) {\n writeFileSync(configPath, updated, \"utf-8\");\n return {\n configFile: configFilename,\n modified: true,\n missingRuleIds: missingRules.map((r) => r.id),\n configured: getUilintEslintConfigInfoFromSource(updated).configured,\n };\n }\n\n return {\n configFile: configFilename,\n modified: false,\n missingRuleIds: missingRules.map((r) => r.id),\n configured: getUilintEslintConfigInfoFromSource(updated).configured,\n };\n}\n\nexport interface UninstallEslintPluginOptions {\n projectPath: string;\n}\n\nexport interface UninstallEslintPluginResult {\n success: boolean;\n error?: string;\n modifiedFiles?: string[];\n}\n\n/**\n * Remove uilint-eslint rules from ESLint config\n *\n * This is a best-effort uninstall that:\n * 1. Removes uilint rule imports\n * 2. Removes uilint config blocks from the flat config array\n * 3. Removes uilint-eslint package import\n */\nexport async function uninstallEslintPlugin(\n options: UninstallEslintPluginOptions\n): Promise<UninstallEslintPluginResult> {\n const { projectPath } = options;\n\n const configPath = findEslintConfigFile(projectPath);\n if (!configPath) {\n return {\n success: true, // Nothing to uninstall\n modifiedFiles: [],\n };\n }\n\n try {\n const original = readFileSync(configPath, \"utf-8\");\n\n // Simple regex-based removal for now\n // Remove uilint rule imports (import { ... } from \"./uilint-rules/...\" or \"./.uilint/rules/...\")\n let updated = original.replace(\n /^import\\s+\\{[^}]*\\}\\s+from\\s+[\"'][^\"']*\\.uilint\\/rules[^\"']*[\"'];?\\s*$/gm,\n \"\"\n );\n\n // Remove default imports from .uilint/rules (e.g., import RuleName from \"./.uilint/rules/rule-name\")\n updated = updated.replace(\n /^import\\s+\\w+\\s+from\\s+[\"'][^\"']*\\.uilint\\/rules[^\"']*[\"'];?\\s*$/gm,\n \"\"\n );\n\n // Remove uilint-eslint import\n updated = updated.replace(\n /^import\\s+\\{[^}]*\\}\\s+from\\s+[\"']uilint-eslint[\"'];?\\s*$/gm,\n \"\"\n );\n\n // Remove createRule require\n updated = updated.replace(\n /^const\\s+\\{[^}]*createRule[^}]*\\}\\s*=\\s*require\\s*\\(\\s*[\"']uilint-eslint[\"']\\s*\\)\\s*;?\\s*$/gm,\n \"\"\n );\n\n // Remove uilint rules from rules objects (e.g., \"uilint/rule-name\": \"error\")\n updated = updated.replace(\n /[\"']uilint\\/[^\"']+[\"']\\s*:\\s*[\"'][^\"']+[\"']\\s*,?\\s*/g,\n \"\"\n );\n\n // Remove uilint rules array form (e.g., \"uilint/rule-name\": [\"error\", {}])\n updated = updated.replace(\n /[\"']uilint\\/[^\"']+[\"']\\s*:\\s*\\[[^\\]]*\\]\\s*,?\\s*/g,\n \"\"\n );\n\n // Remove entire uilint config block from flat config array\n // This matches: { plugins: { uilint: { rules: {...} } }, rules: {...} }\n updated = updated.replace(\n /\\{\\s*plugins:\\s*\\{\\s*uilint:\\s*\\{[^}]*\\}[^}]*\\}[^}]*rules:\\s*\\{[^}]*\\}[^}]*\\}\\s*,?\\s*/gs,\n \"\"\n );\n\n // Clean up empty lines\n updated = updated.replace(/\\n{3,}/g, \"\\n\\n\");\n\n if (updated !== original) {\n writeFileSync(configPath, updated, \"utf-8\");\n return {\n success: true,\n modifiedFiles: [configPath],\n };\n }\n\n return {\n success: true,\n modifiedFiles: [],\n };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\nexport interface UpdateRuleConfigResult {\n success: boolean;\n error?: string;\n}\n\nexport interface RuleConfigFromFile {\n severity: \"error\" | \"warn\" | \"off\";\n options?: Record<string, unknown>;\n}\n\n/**\n * Extract severity from an AST node representing a rule value.\n * Handles both formats:\n * - Simple string: \"error\"\n * - Array: [\"error\", { ...options }]\n */\nfunction extractSeverityFromValueNode(\n valueNode: any\n): \"error\" | \"warn\" | \"off\" | null {\n if (!valueNode) return null;\n\n // Simple string format: \"error\"\n if (isStringLiteral(valueNode)) {\n const val = valueNode.value;\n if (val === \"error\" || val === \"warn\" || val === \"off\") {\n return val;\n }\n return null;\n }\n\n // Array format: [\"error\", { ...options }]\n if (valueNode.type === \"ArrayExpression\") {\n const firstEl = valueNode.elements?.[0];\n if (firstEl && isStringLiteral(firstEl)) {\n const val = firstEl.value;\n if (val === \"error\" || val === \"warn\" || val === \"off\") {\n return val;\n }\n }\n }\n\n return null;\n}\n\n/**\n * Extract options object from an AST node representing a rule value in array format.\n * Returns undefined if not in array format or no options present.\n */\nfunction extractOptionsFromValueNode(\n valueNode: any\n): Record<string, unknown> | undefined {\n if (!valueNode || valueNode.type !== \"ArrayExpression\") return undefined;\n\n const elements = valueNode.elements ?? [];\n if (elements.length < 2) return undefined;\n\n const optionsNode = elements[1];\n if (!optionsNode || optionsNode.type !== \"ObjectExpression\") return undefined;\n\n // Convert AST object to plain JS object\n try {\n const obj: Record<string, unknown> = {};\n for (const prop of optionsNode.properties ?? []) {\n if (!prop || (prop.type !== \"ObjectProperty\" && prop.type !== \"Property\"))\n continue;\n\n const key = prop.key;\n let keyName: string | null = null;\n if (key?.type === \"Identifier\") {\n keyName = key.name;\n } else if (isStringLiteral(key)) {\n keyName = key.value;\n }\n if (!keyName) continue;\n\n const val = prop.value;\n if (isStringLiteral(val)) {\n obj[keyName] = val.value;\n } else if (\n val?.type === \"NumericLiteral\" ||\n (val?.type === \"Literal\" && typeof val.value === \"number\")\n ) {\n obj[keyName] = val.value;\n } else if (\n val?.type === \"BooleanLiteral\" ||\n (val?.type === \"Literal\" && typeof val.value === \"boolean\")\n ) {\n obj[keyName] = val.value;\n } else if (val?.type === \"ArrayExpression\") {\n // Simple array of literals\n const arr: unknown[] = [];\n for (const el of val.elements ?? []) {\n if (isStringLiteral(el)) arr.push(el.value);\n else if (\n el?.type === \"NumericLiteral\" ||\n (el?.type === \"Literal\" && typeof el.value === \"number\")\n )\n arr.push(el.value);\n else if (\n el?.type === \"BooleanLiteral\" ||\n (el?.type === \"Literal\" && typeof el.value === \"boolean\")\n )\n arr.push(el.value);\n }\n obj[keyName] = arr;\n }\n }\n return Object.keys(obj).length > 0 ? obj : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Read current rule configurations from an ESLint config file.\n * Uses magicast to parse the config and extract uilint rule severities and options.\n *\n * @param configPath Path to the ESLint config file\n * @returns Map of ruleId (without \"uilint/\" prefix) to { severity, options }\n */\nexport function readRuleConfigsFromConfig(\n configPath: string\n): Map<string, RuleConfigFromFile> {\n const configs = new Map<string, RuleConfigFromFile>();\n\n try {\n const source = readFileSync(configPath, \"utf-8\");\n const mod = parseModule(source);\n const found = findExportedConfigArrayExpression(mod);\n\n if (!found) {\n return configs;\n }\n\n const { arrayExpr } = found;\n const valueNodes = collectUilintRuleValueNodesFromConfigArray(arrayExpr);\n\n for (const [ruleId, valueNode] of valueNodes) {\n const severity = extractSeverityFromValueNode(valueNode);\n if (severity) {\n const options = extractOptionsFromValueNode(valueNode);\n configs.set(ruleId, { severity, options });\n }\n }\n } catch (error) {\n console.error(\"[eslint-config-inject] Failed to read rule configs:\", error);\n }\n\n return configs;\n}\n\n/**\n * Find a rule property node in the config array by rule ID.\n * Returns the property node and its parent rules object.\n */\nfunction findRulePropertyInConfigArray(\n arrayExpr: any,\n ruleId: string\n): { prop: any; rulesObj: any } | null {\n const fullRuleKey = `uilint/${ruleId}`;\n\n if (!arrayExpr || arrayExpr.type !== \"ArrayExpression\") return null;\n\n for (const el of arrayExpr.elements ?? []) {\n if (!el || el.type !== \"ObjectExpression\") continue;\n const rules = getObjectPropertyValue(el, \"rules\");\n if (!rules || rules.type !== \"ObjectExpression\") continue;\n\n for (const prop of rules.properties ?? []) {\n if (!prop) continue;\n if (prop.type !== \"ObjectProperty\" && prop.type !== \"Property\") continue;\n const key = prop.key;\n if (!isStringLiteral(key)) continue;\n if (key.value === fullRuleKey) {\n return { prop, rulesObj: rules };\n }\n }\n }\n\n return null;\n}\n\n/**\n * Update the severity of a single uilint rule in the ESLint config file.\n * Uses magicast for proper AST-based editing.\n *\n * Handles both formats:\n * - Simple: \"uilint/rule-name\": \"error\"\n * - With options: \"uilint/rule-name\": [\"error\", { ... }]\n *\n * Only updates existing rules - returns error if rule not found.\n */\nexport function updateRuleSeverityInConfig(\n configPath: string,\n ruleId: string,\n severity: \"error\" | \"warn\" | \"off\"\n): UpdateRuleConfigResult {\n try {\n const source = readFileSync(configPath, \"utf-8\");\n const mod = parseModule(source);\n const found = findExportedConfigArrayExpression(mod);\n\n if (!found) {\n return {\n success: false,\n error: \"Could not parse ESLint config array\",\n };\n }\n\n const { arrayExpr } = found;\n const ruleInfo = findRulePropertyInConfigArray(arrayExpr, ruleId);\n\n if (!ruleInfo) {\n return {\n success: false,\n error: `Rule \"uilint/${ruleId}\" not found in config. Use 'uilint install' to add new rules.`,\n };\n }\n\n const { prop } = ruleInfo;\n const valueNode = prop.value;\n\n // Update the severity based on the format\n if (isStringLiteral(valueNode)) {\n // Simple string format - replace the value\n valueNode.value = severity;\n } else if (valueNode?.type === \"ArrayExpression\") {\n // Array format - update the first element\n const firstEl = valueNode.elements?.[0];\n if (firstEl && isStringLiteral(firstEl)) {\n firstEl.value = severity;\n } else {\n // Create a new string literal for severity\n const severityNode = (parseExpression(`\"${severity}\"`) as any).$ast;\n if (valueNode.elements && valueNode.elements.length > 0) {\n valueNode.elements[0] = severityNode;\n } else {\n valueNode.elements = [severityNode];\n }\n }\n } else {\n return {\n success: false,\n error: `Rule \"uilint/${ruleId}\" has unexpected format`,\n };\n }\n\n const updated = generateCode(mod).code;\n writeFileSync(configPath, updated, \"utf-8\");\n return { success: true };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\n/**\n * Update the severity AND options of a single uilint rule in the ESLint config file.\n * Uses magicast for proper AST-based editing.\n *\n * Converts the rule to array format: [\"severity\", { ...options }]\n *\n * Only updates existing rules - returns error if rule not found.\n */\nexport function updateRuleConfigInConfig(\n configPath: string,\n ruleId: string,\n severity: \"error\" | \"warn\" | \"off\",\n options: Record<string, unknown>\n): UpdateRuleConfigResult {\n try {\n const source = readFileSync(configPath, \"utf-8\");\n const mod = parseModule(source);\n const found = findExportedConfigArrayExpression(mod);\n\n if (!found) {\n return {\n success: false,\n error: \"Could not parse ESLint config array\",\n };\n }\n\n const { arrayExpr } = found;\n const ruleInfo = findRulePropertyInConfigArray(arrayExpr, ruleId);\n\n if (!ruleInfo) {\n return {\n success: false,\n error: `Rule \"uilint/${ruleId}\" not found in config. Use 'uilint install' to add new rules.`,\n };\n }\n\n const { prop } = ruleInfo;\n\n // Build the new value as array format: [\"severity\", { ...options }]\n const optionsJson = JSON.stringify(options);\n const newValueExpr = `[\"${severity}\", ${optionsJson}]`;\n const newValueNode = (parseExpression(newValueExpr) as any).$ast;\n\n // Replace the value\n prop.value = newValueNode;\n\n const updated = generateCode(mod).code;\n writeFileSync(configPath, updated, \"utf-8\");\n return { success: true };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n"],"mappings":"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","names":["intro","outro","cancel","cancel","note","log","select","confirm","multiselect","join","existsSync","readFileSync","join","dirname","p","program","dirname","join","readFileSync","join","existsSync"]}
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
createSpinner,
|
|
4
4
|
detectNextAppRouter,
|
|
5
|
+
findEslintConfigFile,
|
|
5
6
|
findNextAppRouterProjects,
|
|
6
7
|
intro,
|
|
7
8
|
logError,
|
|
@@ -11,8 +12,11 @@ import {
|
|
|
11
12
|
note,
|
|
12
13
|
outro,
|
|
13
14
|
pc,
|
|
15
|
+
readRuleConfigsFromConfig,
|
|
16
|
+
updateRuleConfigInConfig,
|
|
17
|
+
updateRuleSeverityInConfig,
|
|
14
18
|
withSpinner
|
|
15
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-PB5DLLVC.js";
|
|
16
20
|
|
|
17
21
|
// src/index.ts
|
|
18
22
|
import { Command } from "commander";
|
|
@@ -2154,6 +2158,11 @@ ${stack}` : ""
|
|
|
2154
2158
|
handleConfigSet(key, value);
|
|
2155
2159
|
break;
|
|
2156
2160
|
}
|
|
2161
|
+
case "rule:config:set": {
|
|
2162
|
+
const { ruleId, severity, options, requestId } = message;
|
|
2163
|
+
handleRuleConfigSet(ws, ruleId, severity, options, requestId);
|
|
2164
|
+
break;
|
|
2165
|
+
}
|
|
2157
2166
|
}
|
|
2158
2167
|
}
|
|
2159
2168
|
function handleDisconnect(ws) {
|
|
@@ -2192,6 +2201,70 @@ function handleConfigSet(key, value) {
|
|
|
2192
2201
|
logInfo(`${pc.dim("[ws]")} config:set ${pc.bold(key)} = ${pc.dim(JSON.stringify(value))}`);
|
|
2193
2202
|
broadcastConfigUpdate(key, value);
|
|
2194
2203
|
}
|
|
2204
|
+
function broadcastRuleConfigChange(ruleId, severity, options) {
|
|
2205
|
+
const message = {
|
|
2206
|
+
type: "rule:config:changed",
|
|
2207
|
+
ruleId,
|
|
2208
|
+
severity,
|
|
2209
|
+
options
|
|
2210
|
+
};
|
|
2211
|
+
for (const ws of connectedClientsSet) {
|
|
2212
|
+
sendMessage(ws, message);
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
function handleRuleConfigSet(ws, ruleId, severity, options, requestId) {
|
|
2216
|
+
logInfo(
|
|
2217
|
+
`${pc.dim("[ws]")} rule:config:set ${pc.bold(ruleId)} -> ${pc.dim(severity)}${options ? ` with options` : ""}`
|
|
2218
|
+
);
|
|
2219
|
+
const configPath = findEslintConfigFile(serverAppRootForVision);
|
|
2220
|
+
if (!configPath) {
|
|
2221
|
+
const error = `No ESLint config file found in ${serverAppRootForVision}`;
|
|
2222
|
+
logError(`${pc.dim("[ws]")} ${error}`);
|
|
2223
|
+
sendMessage(ws, {
|
|
2224
|
+
type: "rule:config:result",
|
|
2225
|
+
ruleId,
|
|
2226
|
+
severity,
|
|
2227
|
+
options,
|
|
2228
|
+
success: false,
|
|
2229
|
+
error,
|
|
2230
|
+
requestId
|
|
2231
|
+
});
|
|
2232
|
+
return;
|
|
2233
|
+
}
|
|
2234
|
+
let result;
|
|
2235
|
+
if (options && Object.keys(options).length > 0) {
|
|
2236
|
+
result = updateRuleConfigInConfig(configPath, ruleId, severity, options);
|
|
2237
|
+
} else {
|
|
2238
|
+
result = updateRuleSeverityInConfig(configPath, ruleId, severity);
|
|
2239
|
+
}
|
|
2240
|
+
if (result.success) {
|
|
2241
|
+
logSuccess(
|
|
2242
|
+
`${pc.dim("[ws]")} Updated ${pc.bold(`uilint/${ruleId}`)} -> ${pc.dim(severity)}`
|
|
2243
|
+
);
|
|
2244
|
+
eslintInstances.clear();
|
|
2245
|
+
cache.clear();
|
|
2246
|
+
sendMessage(ws, {
|
|
2247
|
+
type: "rule:config:result",
|
|
2248
|
+
ruleId,
|
|
2249
|
+
severity,
|
|
2250
|
+
options,
|
|
2251
|
+
success: true,
|
|
2252
|
+
requestId
|
|
2253
|
+
});
|
|
2254
|
+
broadcastRuleConfigChange(ruleId, severity, options);
|
|
2255
|
+
} else {
|
|
2256
|
+
logError(`${pc.dim("[ws]")} Failed to update rule: ${result.error}`);
|
|
2257
|
+
sendMessage(ws, {
|
|
2258
|
+
type: "rule:config:result",
|
|
2259
|
+
ruleId,
|
|
2260
|
+
severity,
|
|
2261
|
+
options,
|
|
2262
|
+
success: false,
|
|
2263
|
+
error: result.error,
|
|
2264
|
+
requestId
|
|
2265
|
+
});
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2195
2268
|
async function serve(options) {
|
|
2196
2269
|
const port = options.port || 9234;
|
|
2197
2270
|
const cwd = process.cwd();
|
|
@@ -2219,15 +2292,25 @@ async function serve(options) {
|
|
|
2219
2292
|
workspaceRoot: wsRoot,
|
|
2220
2293
|
serverCwd: cwd
|
|
2221
2294
|
});
|
|
2295
|
+
const eslintConfigPath = findEslintConfigFile(appRoot);
|
|
2296
|
+
const currentRuleConfigs = eslintConfigPath ? readRuleConfigsFromConfig(eslintConfigPath) : /* @__PURE__ */ new Map();
|
|
2222
2297
|
sendMessage(ws, {
|
|
2223
2298
|
type: "rules:metadata",
|
|
2224
|
-
rules: ruleRegistry.map((rule) =>
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2299
|
+
rules: ruleRegistry.map((rule) => {
|
|
2300
|
+
const currentConfig = currentRuleConfigs.get(rule.id);
|
|
2301
|
+
return {
|
|
2302
|
+
id: rule.id,
|
|
2303
|
+
name: rule.name,
|
|
2304
|
+
description: rule.description,
|
|
2305
|
+
category: rule.category,
|
|
2306
|
+
defaultSeverity: rule.defaultSeverity,
|
|
2307
|
+
currentSeverity: currentConfig?.severity,
|
|
2308
|
+
currentOptions: currentConfig?.options,
|
|
2309
|
+
docs: rule.docs,
|
|
2310
|
+
optionSchema: rule.optionSchema,
|
|
2311
|
+
defaultOptions: rule.defaultOptions
|
|
2312
|
+
};
|
|
2313
|
+
})
|
|
2231
2314
|
});
|
|
2232
2315
|
for (const [key, value] of configStore) {
|
|
2233
2316
|
sendMessage(ws, { type: "config:update", key, value });
|
|
@@ -2776,6 +2859,30 @@ function presetToPosition(preset) {
|
|
|
2776
2859
|
};
|
|
2777
2860
|
return positions[preset] || { x: 500, y: 30 };
|
|
2778
2861
|
}
|
|
2862
|
+
function parseRuleConfig(value) {
|
|
2863
|
+
const match = value.match(/^([^:]+):(error|warn|off)$/);
|
|
2864
|
+
if (!match) {
|
|
2865
|
+
return null;
|
|
2866
|
+
}
|
|
2867
|
+
return {
|
|
2868
|
+
ruleId: match[1],
|
|
2869
|
+
severity: match[2]
|
|
2870
|
+
};
|
|
2871
|
+
}
|
|
2872
|
+
function parseOptionsJson(optionsStr) {
|
|
2873
|
+
if (!optionsStr) {
|
|
2874
|
+
return void 0;
|
|
2875
|
+
}
|
|
2876
|
+
try {
|
|
2877
|
+
const parsed = JSON.parse(optionsStr);
|
|
2878
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
2879
|
+
return void 0;
|
|
2880
|
+
}
|
|
2881
|
+
return parsed;
|
|
2882
|
+
} catch {
|
|
2883
|
+
return void 0;
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2779
2886
|
async function sendConfigMessage(port, key, value) {
|
|
2780
2887
|
return new Promise((resolve7) => {
|
|
2781
2888
|
const url = `ws://localhost:${port}`;
|
|
@@ -2809,7 +2916,56 @@ async function sendConfigMessage(port, key, value) {
|
|
|
2809
2916
|
});
|
|
2810
2917
|
});
|
|
2811
2918
|
}
|
|
2812
|
-
async function
|
|
2919
|
+
async function sendRuleConfigMessage(port, ruleId, severity, options) {
|
|
2920
|
+
return new Promise((resolve7) => {
|
|
2921
|
+
const url = `ws://localhost:${port}`;
|
|
2922
|
+
const ws = new WebSocket2(url);
|
|
2923
|
+
let resolved = false;
|
|
2924
|
+
const requestId = `cli-${Date.now()}`;
|
|
2925
|
+
const timeout = setTimeout(() => {
|
|
2926
|
+
if (!resolved) {
|
|
2927
|
+
resolved = true;
|
|
2928
|
+
ws.close();
|
|
2929
|
+
resolve7({ success: false, error: "Request timed out" });
|
|
2930
|
+
}
|
|
2931
|
+
}, 1e4);
|
|
2932
|
+
ws.on("open", () => {
|
|
2933
|
+
const message = JSON.stringify({
|
|
2934
|
+
type: "rule:config:set",
|
|
2935
|
+
ruleId,
|
|
2936
|
+
severity,
|
|
2937
|
+
options,
|
|
2938
|
+
requestId
|
|
2939
|
+
});
|
|
2940
|
+
ws.send(message);
|
|
2941
|
+
});
|
|
2942
|
+
ws.on("message", (data) => {
|
|
2943
|
+
try {
|
|
2944
|
+
const msg = JSON.parse(data.toString());
|
|
2945
|
+
if (msg.type === "rule:config:result" && msg.requestId === requestId) {
|
|
2946
|
+
if (!resolved) {
|
|
2947
|
+
resolved = true;
|
|
2948
|
+
clearTimeout(timeout);
|
|
2949
|
+
ws.close();
|
|
2950
|
+
resolve7({
|
|
2951
|
+
success: msg.success,
|
|
2952
|
+
error: msg.error
|
|
2953
|
+
});
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2956
|
+
} catch {
|
|
2957
|
+
}
|
|
2958
|
+
});
|
|
2959
|
+
ws.on("error", () => {
|
|
2960
|
+
if (!resolved) {
|
|
2961
|
+
resolved = true;
|
|
2962
|
+
clearTimeout(timeout);
|
|
2963
|
+
resolve7({ success: false, error: "Connection error" });
|
|
2964
|
+
}
|
|
2965
|
+
});
|
|
2966
|
+
});
|
|
2967
|
+
}
|
|
2968
|
+
async function handleSet(key, value, port, extraArg) {
|
|
2813
2969
|
switch (key) {
|
|
2814
2970
|
case "position": {
|
|
2815
2971
|
const parsed = parsePosition(value);
|
|
@@ -2839,6 +2995,50 @@ Expected format: x,y (e.g., 100,50) or preset (top-center, top-left, etc.)`
|
|
|
2839
2995
|
} else {
|
|
2840
2996
|
logError(
|
|
2841
2997
|
`Failed to set position. Is the server running?
|
|
2998
|
+
Start it with: ${pc.bold("npx uilint serve")}`
|
|
2999
|
+
);
|
|
3000
|
+
process.exit(1);
|
|
3001
|
+
}
|
|
3002
|
+
break;
|
|
3003
|
+
}
|
|
3004
|
+
case "rule": {
|
|
3005
|
+
const parsed = parseRuleConfig(value);
|
|
3006
|
+
if (!parsed) {
|
|
3007
|
+
logError(
|
|
3008
|
+
`Invalid rule config: ${value}
|
|
3009
|
+
Expected format: <ruleId>:<severity>
|
|
3010
|
+
severity: error, warn, or off
|
|
3011
|
+
|
|
3012
|
+
Examples:
|
|
3013
|
+
uilint config set rule no-arbitrary-tailwind:warn
|
|
3014
|
+
uilint config set rule no-prop-drilling-depth:error '{"maxDepth":3}'`
|
|
3015
|
+
);
|
|
3016
|
+
process.exit(1);
|
|
3017
|
+
}
|
|
3018
|
+
const options = parseOptionsJson(extraArg);
|
|
3019
|
+
if (extraArg && !options) {
|
|
3020
|
+
logError(
|
|
3021
|
+
`Invalid options JSON: ${extraArg}
|
|
3022
|
+
Expected a valid JSON object, e.g., '{"maxDepth": 3}'`
|
|
3023
|
+
);
|
|
3024
|
+
process.exit(1);
|
|
3025
|
+
}
|
|
3026
|
+
logInfo(
|
|
3027
|
+
`Setting rule "${parsed.ruleId}" to ${parsed.severity}` + (options ? ` with options: ${JSON.stringify(options)}` : "")
|
|
3028
|
+
);
|
|
3029
|
+
const result = await sendRuleConfigMessage(
|
|
3030
|
+
port,
|
|
3031
|
+
parsed.ruleId,
|
|
3032
|
+
parsed.severity,
|
|
3033
|
+
options
|
|
3034
|
+
);
|
|
3035
|
+
if (result.success) {
|
|
3036
|
+
logSuccess(
|
|
3037
|
+
`Rule "${parsed.ruleId}" set to ${parsed.severity}` + (options ? ` with options` : "")
|
|
3038
|
+
);
|
|
3039
|
+
} else {
|
|
3040
|
+
logError(
|
|
3041
|
+
result.error || `Failed to set rule config. Is the server running?
|
|
2842
3042
|
Start it with: ${pc.bold("npx uilint serve")}`
|
|
2843
3043
|
);
|
|
2844
3044
|
process.exit(1);
|
|
@@ -2847,7 +3047,7 @@ Start it with: ${pc.bold("npx uilint serve")}`
|
|
|
2847
3047
|
}
|
|
2848
3048
|
default:
|
|
2849
3049
|
logError(`Unknown config key: ${key}`);
|
|
2850
|
-
logInfo(`Available keys: position`);
|
|
3050
|
+
logInfo(`Available keys: position, rule`);
|
|
2851
3051
|
process.exit(1);
|
|
2852
3052
|
}
|
|
2853
3053
|
}
|
|
@@ -2866,7 +3066,7 @@ To view it, check your browser's dev tools:
|
|
|
2866
3066
|
process.exit(1);
|
|
2867
3067
|
}
|
|
2868
3068
|
}
|
|
2869
|
-
async function config2(action, key, value, options = {}) {
|
|
3069
|
+
async function config2(action, key, value, extraArg, options = {}) {
|
|
2870
3070
|
const port = options.port || 9234;
|
|
2871
3071
|
switch (action) {
|
|
2872
3072
|
case "set":
|
|
@@ -2874,14 +3074,14 @@ async function config2(action, key, value, options = {}) {
|
|
|
2874
3074
|
logError(`Missing value for config set ${key}`);
|
|
2875
3075
|
process.exit(1);
|
|
2876
3076
|
}
|
|
2877
|
-
await handleSet(key, value, port);
|
|
3077
|
+
await handleSet(key, value, port, extraArg);
|
|
2878
3078
|
break;
|
|
2879
3079
|
case "get":
|
|
2880
3080
|
await handleGet(key, port);
|
|
2881
3081
|
break;
|
|
2882
3082
|
default:
|
|
2883
3083
|
logError(`Unknown action: ${action}`);
|
|
2884
|
-
logInfo(`Usage: uilint config <set|get> <key> [value]`);
|
|
3084
|
+
logInfo(`Usage: uilint config <set|get> <key> [value] [options]`);
|
|
2885
3085
|
process.exit(1);
|
|
2886
3086
|
}
|
|
2887
3087
|
}
|
|
@@ -2981,7 +3181,7 @@ program.command("update").description("Update existing style guide with new styl
|
|
|
2981
3181
|
});
|
|
2982
3182
|
});
|
|
2983
3183
|
program.command("install").description("Install UILint integration").option("--force", "Overwrite existing configuration files").action(async (options) => {
|
|
2984
|
-
const { installUI } = await import("./install-ui-
|
|
3184
|
+
const { installUI } = await import("./install-ui-TXV7A34M.js");
|
|
2985
3185
|
await installUI({ force: options.force });
|
|
2986
3186
|
});
|
|
2987
3187
|
program.command("serve").description("Start WebSocket server for real-time UI linting").option("-p, --port <number>", "Port to listen on", "9234").action(async (options) => {
|
|
@@ -3023,8 +3223,8 @@ program.command("vision").description("Analyze a screenshot with Ollama vision m
|
|
|
3023
3223
|
debugDump: options.debugDump
|
|
3024
3224
|
});
|
|
3025
3225
|
});
|
|
3026
|
-
program.command("config").description("Get or set UILint configuration options").argument("<action>", "Action: set or get").argument("<key>", "Config key (e.g., position)").argument("[value]", "Value to set (for set action)").option("-p, --port <number>", "WebSocket server port", "9234").action(async (action, key, value, options) => {
|
|
3027
|
-
await config2(action, key, value, {
|
|
3226
|
+
program.command("config").description("Get or set UILint configuration options").argument("<action>", "Action: set or get").argument("<key>", "Config key (e.g., position, rule)").argument("[value]", "Value to set (for set action)").argument("[extraArg]", "Extra argument (e.g., options JSON for rule config)").option("-p, --port <number>", "WebSocket server port", "9234").action(async (action, key, value, extraArg, options) => {
|
|
3227
|
+
await config2(action, key, value, extraArg, {
|
|
3028
3228
|
port: parseInt(options.port, 10)
|
|
3029
3229
|
});
|
|
3030
3230
|
});
|