UNPKG

@nlabs/lex

Version:
962 lines (932 loc) 116 kB
import { execa } from "execa"; import { existsSync, readFileSync, unlinkSync, writeFileSync } from "fs"; import { dirname, resolve as pathResolve, extname } from "path"; import { LexConfig } from "../../LexConfig.js"; import { createSpinner } from "../../utils/app.js"; import { resolveBinaryPath } from "../../utils/file.js"; import { log } from "../../utils/log.js"; let currentFilename; let currentDirname; try { currentFilename = eval('require("url").fileURLToPath(import.meta.url)'); currentDirname = dirname(currentFilename); } catch { currentFilename = process.cwd(); currentDirname = process.cwd(); } const createDefaultESLintConfig = (useTypescript, cwd) => { const configPath = pathResolve(cwd, ".lex-temp-default-eslint.cjs"); const originalConfig = null; const configContent = `// Temporary ESLint config generated by Lex const lexConfig = require('@nlabs/lex/eslint.config.mjs'); module.exports = lexConfig;`; writeFileSync(configPath, configContent, "utf8"); return { configPath, originalConfig }; }; const detectTypeScript = (cwd) => existsSync(pathResolve(cwd, "tsconfig.json")); const ensureModuleType = (cwd) => { const packageJsonPath = pathResolve(cwd, "package.json"); if (existsSync(packageJsonPath)) { try { const packageJsonContent = readFileSync(packageJsonPath, "utf8"); const packageJson = JSON.parse(packageJsonContent); if (packageJson.type !== "module") { log('Warning: package.json should have "type": "module" for ESM support. Please add this manually.', "warn", false); } } catch (_error) { } } }; const installDependencies = async (cwd, useTypescript, quiet) => { if (useTypescript) { log("Using TypeScript ESLint from Lex...", "info", quiet); } else { log("Using ESLint from Lex...", "info", quiet); } }; const runEslintWithLex = async (cwd, quiet, cliName, fix, debug, useTypescript, captureOutput) => { const spinner = createSpinner(quiet); try { const projectConfigPath = pathResolve(cwd, "eslint.config.mjs"); const projectConfigPathTs = pathResolve(cwd, "eslint.config.ts"); const hasProjectConfig = existsSync(projectConfigPath) || existsSync(projectConfigPathTs); const hasLexConfigEslint = LexConfig.config.eslint && Object.keys(LexConfig.config.eslint).length > 0; const possiblePaths = [ pathResolve(currentDirname, "../../../../eslint.config.mjs"), pathResolve(currentDirname, "../../../../eslint.config.ts"), pathResolve(process.env.LEX_HOME || "/usr/local/lib/node_modules/@nlabs/lex", "eslint.config.mjs"), pathResolve(process.env.LEX_HOME || "/usr/local/lib/node_modules/@nlabs/lex", "eslint.config.ts") ]; let lexConfigPath = ""; for (const path of possiblePaths) { if (existsSync(path)) { lexConfigPath = path; break; } } let configPath = ""; let tempConfigPath = ""; if (hasProjectConfig) { configPath = existsSync(projectConfigPathTs) ? projectConfigPathTs : projectConfigPath; if (debug) { log(`Using project ESLint config file: ${configPath}`, "info", quiet); } } else if (hasLexConfigEslint) { tempConfigPath = pathResolve(cwd, ".lex-temp-eslint.cjs"); const configContent = `// Temporary ESLint config generated by Lex const lexConfig = require('@nlabs/lex/eslint.config.mjs'); const userConfig = ${JSON.stringify(LexConfig.config.eslint, null, 2)}; // Merge Lex's config with user config module.exports = { ...lexConfig };`; writeFileSync(tempConfigPath, configContent, "utf8"); configPath = tempConfigPath; if (debug) { log(`Using ESLint config from lex.config.* file via temp file: ${tempConfigPath}`, "info", quiet); } } else if (lexConfigPath && existsSync(lexConfigPath)) { configPath = lexConfigPath; if (debug) { log(`Using Lex ESLint config file: ${configPath}`, "info", quiet); } } else { tempConfigPath = pathResolve(cwd, ".lex-temp-default-eslint.cjs"); const configContent = `// Temporary default ESLint config generated by Lex const lexConfig = require('@nlabs/lex/eslint.config.mjs'); module.exports = lexConfig;`; writeFileSync(tempConfigPath, configContent, "utf8"); configPath = tempConfigPath; if (debug) { log(`Created temporary default ESLint config at: ${tempConfigPath}`, "info", quiet); } else { log("No ESLint configuration found. Using Lex default configuration.", "info", quiet); } } const eslintBinary = resolveBinaryPath("eslint", "eslint"); if (!eslintBinary) { log(` ${cliName} Error: ESLint binary not found in Lex's node_modules`, "error", quiet); log("Please reinstall Lex or check your installation.", "info", quiet); return 1; } const baseEslintArgs = [ ...fix ? ["--fix"] : [], ...debug ? ["--debug"] : [], "--no-error-on-unmatched-pattern", "--no-warn-ignored" ]; const configArgs = configPath ? ["--config", configPath] : []; const jsResult = await execa(eslintBinary, [ "src/**/*.{js,jsx}", ...configArgs, ...baseEslintArgs ], { cwd, reject: false, shell: true, stdio: "pipe" }); if (jsResult.stdout) { console.log(jsResult.stdout); if (captureOutput) { captureOutput(jsResult.stdout); } } if (jsResult.stderr) { console.error(jsResult.stderr); if (captureOutput) { captureOutput(jsResult.stderr); } } let tsResult = { exitCode: 0, stderr: "", stdout: "" }; if (useTypescript) { tsResult = await execa(eslintBinary, [ "src/**/*.{ts,tsx}", ...configArgs, ...baseEslintArgs ], { cwd, reject: false, shell: true, stdio: "pipe" }); if (tsResult.stdout) { console.log(tsResult.stdout); } if (tsResult.stderr) { console.error(tsResult.stderr); } } if (tempConfigPath && existsSync(tempConfigPath)) { try { unlinkSync(tempConfigPath); if (debug) { log(`Removed temporary ESLint config at ${tempConfigPath}`, "info", quiet); } } catch (error) { if (debug) { log(`Failed to remove temporary ESLint config: ${error.message}`, "warn", quiet); } } } const eslintNotFound = jsResult.stderr?.includes("command not found") || jsResult.stderr?.includes("eslint: command not found"); if (eslintNotFound) { spinner.fail("ESLint not found!"); log(` ${cliName} Error: Lex's ESLint binary not found. Please reinstall Lex or check your installation.`, "error", quiet); return 1; } if (jsResult.exitCode === 0 && tsResult.exitCode === 0) { spinner.succeed("Linting completed!"); return 0; } const noFilesFound = (jsResult.stderr?.includes("No such file or directory") || jsResult.stdout?.includes("No such file or directory")) && (!useTypescript || tsResult.stderr?.includes("No such file or directory") || tsResult.stdout?.includes("No such file or directory")); if (noFilesFound) { spinner.succeed("No files found to lint"); return 0; } spinner.fail("Linting failed!"); log(` ${cliName} Error: ESLint found issues in your code.`, "error", quiet); return 1; } catch (error) { spinner.fail("Linting failed!"); log(` ${cliName} Error: ${error.message}`, "error", quiet); return 1; } }; const applyAIFix = async (cwd, errors, quiet) => { const spinner = createSpinner(quiet); spinner.start("Using AI to fix remaining lint issues..."); try { const fileErrorMap = /* @__PURE__ */ new Map(); const lines = errors.split("\n"); let currentFile = ""; for (const line of lines) { if (line.match(/^(\/|[A-Z]:\\).*?\.(js|jsx|ts|tsx)$/)) { currentFile = line.trim(); if (!fileErrorMap.has(currentFile)) { fileErrorMap.set(currentFile, []); } } else if (currentFile && line.trim() && line.match(/\s+\d+:\d+\s+(error|warning)\s+/)) { const errorArray = fileErrorMap.get(currentFile); if (errorArray) { errorArray.push(line.trim()); } } } if (fileErrorMap.size === 0) { log("Using alternative error parsing strategy", "info", quiet); const sections = errors.split("\n\n"); for (const section of sections) { if (section.trim() === "") { continue; } const lines2 = section.split("\n"); const filePath = lines2[0].trim(); if (filePath.match(/\.(js|jsx|ts|tsx)$/)) { fileErrorMap.set(filePath, []); for (let i = 1; i < lines2.length; i++) { if (lines2[i].trim() !== "") { fileErrorMap.get(filePath)?.push(lines2[i].trim()); } } } } } if (fileErrorMap.size === 0) { log("Using direct file path extraction", "info", quiet); const filePathRegex = /(?:\/|[A-Z]:\\)(?:[^:\n]+\/)*[^:\n]+\.(js|jsx|ts|tsx)/g; const filePaths = errors.match(filePathRegex) || []; for (const filePath of filePaths) { if (!fileErrorMap.has(filePath) && existsSync(filePath)) { fileErrorMap.set(filePath, []); } } const knownFiles = [ pathResolve(cwd, "src/create/changelog.ts"), pathResolve(cwd, "src/utils/aiService.ts"), pathResolve(cwd, "src/utils/app.ts"), pathResolve(cwd, "src/utils/reactShim.ts"), pathResolve(cwd, "src/commands/lint/autofix.js") ]; for (const file of knownFiles) { if (existsSync(file) && !fileErrorMap.has(file)) { fileErrorMap.set(file, []); } } } for (const filePath of fileErrorMap.keys()) { if (!existsSync(filePath)) { log(`File not found: ${filePath}`, "warn", quiet); continue; } log(`Processing file: ${filePath}`, "info", quiet); const isCursorIDE = LexConfig.config.ai?.provider === "cursor" || process.env.CURSOR_IDE === "true"; if (isCursorIDE) { try { const prompt = `Fix all ESLint errors in this file. Focus on: 1. Fixing naming conventions 2. Fixing sort-keys issues 3. Replacing console.log with log utility 4. Fixing no-plusplus issues 5. Fixing unnecessary escape characters 6. Fixing other ESLint errors CRITICAL REQUIREMENTS: - ONLY fix the specific lines with ESLint errors - DO NOT modify any other lines of code - DO NOT remove line breaks unless they are specifically causing ESLint errors - DO NOT condense multi-line structures to single lines - PRESERVE all existing line breaks and formatting that is not causing errors SPECIFIC FORMATTING RULES: - Maintain proper indentation (2 spaces) - Keep line breaks between class/interface declaration and their members - Keep line breaks between methods - Ensure there is a line break after opening braces for classes, interfaces, and methods - DO NOT place class/interface properties or methods on the same line as the opening brace - Preserve empty lines between logical code blocks - PRESERVE multi-line imports - do not condense them to single lines - PRESERVE multi-line object/array declarations - do not condense them to single lines SORT-KEYS RULE (HIGHEST PRIORITY): - All object literal keys MUST be sorted alphabetically in ascending order - This applies to ALL objects in the file, not just those with explicit sort-keys errors - Example: {b: 2, a: 1, c: 3} should become {a: 1, b: 2, c: 3} - Preserve the original formatting and line breaks when sorting Example of CORRECT formatting (DO NOT CHANGE): export class UserConstants { static readonly ADD_ITEM_ERROR: string = 'USER_ADD_ITEM_ERROR'; static readonly OTHER_CONSTANT: string = 'OTHER_CONSTANT'; } constructor(flux: FluxFramework, CustomAdapter: typeof Event = Event) { this.CustomAdapter = CustomAdapter; this.flux = flux; } import { app, events, images, locations, messages, posts, tags, users, websocket } from './stores'; const config = { apiKey: 'value', baseUrl: 'https://api.example.com', timeout: 5000 }; Example of INCORRECT formatting (FIX THIS): export class UserConstants {static readonly ADD_ITEM_ERROR: string = 'USER_ADD_ITEM_ERROR'; static readonly OTHER_CONSTANT: string = 'OTHER_CONSTANT'; } constructor(flux: FluxFramework, CustomAdapter: typeof Event = Event) {this.CustomAdapter = CustomAdapter; this.flux = flux;} import {app, events, images, locations, messages, posts, tags, users, websocket} from './stores'; const config = {baseUrl: 'https://api.example.com', apiKey: 'value', timeout: 5000}; Fix ONLY the specific ESLint errors. Return the properly formatted code.`; try { const promptFile = pathResolve(cwd, ".cursor_prompt_temp.txt"); writeFileSync(promptFile, prompt, "utf8"); await execa("cursor", ["edit", "--file", filePath, "--prompt-file", promptFile], { cwd, reject: false, stdio: "pipe" }); try { unlinkSync(promptFile); } catch (_error) { } log(`Applied Cursor AI fixes to ${filePath}`, "info", quiet); } catch { const wasModified = await applyDirectFixes(filePath, quiet); if (wasModified) { log(`Applied direct fixes to ${filePath}`, "info", quiet); } } } catch (error) { log(`Error using Cursor AI: ${error.message}`, "error", quiet); await applyDirectFixes(filePath, quiet); } } else { const wasModified = await applyDirectFixes(filePath, quiet); if (wasModified) { log(`Applied direct fixes to ${filePath}`, "info", quiet); } const fileErrors = fileErrorMap.get(filePath) || []; if (fileErrors.length > 0) { try { const { callAIService } = await import("../../utils/aiService.js"); const fileContent = readFileSync(filePath, "utf8"); const prompt = `Fix the following ESLint errors in this code: ${fileErrors.join("\n")} Here's the code: \`\`\` ${fileContent} \`\`\` CRITICAL REQUIREMENTS: - ONLY fix the specific lines with ESLint errors - DO NOT modify any other lines of code - DO NOT remove line breaks unless they are specifically causing ESLint errors - DO NOT condense multi-line structures to single lines - PRESERVE all existing line breaks and formatting that is not causing errors SPECIFIC FORMATTING RULES: - Maintain proper indentation (2 spaces) - Keep line breaks between class/interface declaration and their members - Keep line breaks between methods - Ensure there is a line break after opening braces for classes, interfaces, and methods - DO NOT place class/interface properties or methods on the same line as the opening brace - Preserve empty lines between logical code blocks - PRESERVE multi-line imports - do not condense them to single lines - PRESERVE multi-line object/array declarations - do not condense them to single lines SORT-KEYS RULE (HIGHEST PRIORITY): - All object literal keys MUST be sorted alphabetically in ascending order - This applies to ALL objects in the file, not just those with explicit sort-keys errors - Example: {b: 2, a: 1, c: 3} should become {a: 1, b: 2, c: 3} - Preserve the original formatting and line breaks when sorting WHAT TO FIX: 1. Sorting all object keys alphabetically (sort-keys rule) - ALL objects must have sorted keys 2. Fixing naming conventions - ONLY for variables/functions with naming errors 3. Replacing console.log with log utility - ONLY for console.log statements 4. Fixing no-plusplus issues - ONLY for ++/-- operators 5. Fixing unnecessary escape characters - ONLY for escaped characters that don't need escaping 6. Proper indentation and spacing - ONLY where specifically required by errors 7. String quotes consistency (use single quotes) - ONLY for string literals with quote errors 8. Import order and spacing - ONLY for imports with order/spacing errors 9. Function parameter formatting - ONLY for functions with parameter errors 10. Variable naming conventions - ONLY for variables with naming errors 11. No unused variables or imports - ONLY for unused variables/imports 12. Avoiding nested ternaries - ONLY for nested ternary expressions 13. Any other ESLint errors - ONLY for the specific errors listed above WHAT NOT TO FIX: - Do not change properly formatted multi-line structures - Do not remove line breaks that are not causing errors - Do not change indentation that is already correct - Do not modify spacing that is already correct - Do not condense readable multi-line code to single lines - Do not modify code that is not mentioned in the ESLint errors Example of CORRECT formatting (DO NOT CHANGE): export class UserConstants { static readonly ADD_ITEM_ERROR: string = 'USER_ADD_ITEM_ERROR'; static readonly OTHER_CONSTANT: string = 'OTHER_CONSTANT'; } constructor(flux: FluxFramework, CustomAdapter: typeof Event = Event) { this.CustomAdapter = CustomAdapter; this.flux = flux; } import { app, events, images, locations, messages, posts, tags, users, websocket } from './stores'; const config = { apiKey: 'value', baseUrl: 'https://api.example.com', timeout: 5000 }; Example of INCORRECT formatting (FIX THIS): export class UserConstants {static readonly ADD_ITEM_ERROR: string = 'USER_ADD_ITEM_ERROR'; static readonly OTHER_CONSTANT: string = 'OTHER_CONSTANT'; } constructor(flux: FluxFramework, CustomAdapter: typeof Event = Event) {this.CustomAdapter = CustomAdapter; this.flux = flux;} import {app, events, images, locations, messages, posts, tags, users, websocket} from './stores'; const config = {baseUrl: 'https://api.example.com', apiKey: 'value', timeout: 5000}; Fix ONLY the specific ESLint errors listed above. Review the entire file for compliance with all ESLint rules. Return only the properly formatted fixed code without any explanations.`; const fixedContent = await callAIService(prompt, quiet); if (fixedContent && fixedContent !== fileContent) { writeFileSync(filePath, fixedContent, "utf8"); log(`Applied AI fixes to ${filePath}`, "info", quiet); } } catch (error) { log(`Error applying AI fixes to ${filePath}: ${error.message}`, "error", quiet); } } } } spinner.succeed("AI fixes applied successfully!"); } catch (error) { spinner.fail("Failed to apply AI fixes"); log(`Error: ${error.message}`, "error", quiet); if (!quiet) { console.error(error); } } }; const applyDirectFixes = async (filePath, quiet) => { let wasModified = false; try { const fileContent = readFileSync(filePath, "utf8"); let newContent = fileContent; if (filePath.includes("aiService.ts")) { log("Fixing issues in aiService.ts", "info", quiet); newContent = newContent.replace( /'Content-Type': 'application\/json',\s*'Authorization': `Bearer/g, "'Authorization': `Bearer', 'Content-Type': 'application/json'" ); newContent = newContent.replace( /headers: {([^}]*)},\s*method: 'POST'/g, "method: 'POST',\n headers: {$1}" ); newContent = newContent.replace( /{role: 'system', content:/g, "{content:, role: 'system'," ); newContent = newContent.replace( /{role: 'user', content:/g, "{content:, role: 'user'," ); newContent = newContent.replace( /\(([^)]*?)_([a-zA-Z0-9]+)(\s*:[^)]*)\)/g, "($1$2$3)" ); newContent = newContent.replace(/console\.log\(/g, "log("); if (!newContent.includes("import {log}") && newContent.includes("log(")) { newContent = newContent.replace( /import {([^}]*)} from '(.*)';/, "import {$1} from '$2';\nimport {log} from './log.js';" ); } } if (filePath.includes("reactShim.ts")) { log("Fixing naming-convention issues in reactShim.ts", "info", quiet); newContent = newContent.replace( "import * as React from", "import * as react from" ); newContent = newContent.replace(/React\./g, "react."); } if (filePath.includes("changelog.ts")) { log("Fixing issues in changelog.ts", "info", quiet); newContent = newContent.replace(/(\w+)\+\+/g, "$1 += 1"); newContent = newContent.replace(/\\\$/g, "$"); newContent = newContent.replace(/\\\./g, "."); newContent = newContent.replace(/\\\*/g, "*"); newContent = newContent.replace(/\\:/g, ":"); } if (filePath.includes("app.ts")) { log("Fixing issues in app.ts", "info", quiet); newContent = newContent.replace(/console\.log\(/g, "log("); if (!newContent.includes("import {log}") && newContent.includes("log(")) { newContent = newContent.replace( /import boxen from 'boxen';/, "import boxen from 'boxen';\nimport {log} from './log.js';" ); } newContent = newContent.replace(/\\\//g, "/"); } if (filePath.includes("autofix.js")) { log("Fixing issues in autofix.js", "info", quiet); newContent = newContent.replace( /import {([^}]*)} from 'path';[\s\n]*import {([^}]*)} from 'path';/, "import {$1, $2} from 'path';" ); newContent = newContent.replace( /__filename/g, "currentFilename" ); newContent = newContent.replace( /__dirname/g, "currentDirname" ); newContent = newContent.replace( /const prefix = type === 'error' \? '❌ ' : type === 'success' \? '✅ ' : 'ℹ️ ';/, "let prefix = '\u2139\uFE0F ';\nif(type === 'error') {\n prefix = '\u274C ';\n} else if(type === 'success') {\n prefix = '\u2705 ';\n}" ); newContent = newContent.replace( /async function runEslintFix\(\)/g, "const runEslintFix = async ()" ); newContent = newContent.replace( /async function getFilesWithErrors\(\)/g, "const getFilesWithErrors = async ()" ); newContent = newContent.replace( /async function isCursorAvailable\(\)/g, "const isCursorAvailable = async ()" ); newContent = newContent.replace( /async function fixFileWithCursorAI\(filePath\)/g, "const fixFileWithCursorAI = async (filePath)" ); newContent = newContent.replace( /async function main\(\)/g, "const main = async ()" ); newContent = newContent.replace( /import {existsSync, readFileSync, writeFileSync}/g, "import {writeFileSync}" ); newContent = newContent.replace( /console\.log\(`\${prefix} \${message}`\);/g, "process.stdout.write(`${prefix} ${message}\\n`);" ); newContent = newContent.replace( /} catch\(error\) {[\s\n]*\/\/ Ignore cleanup errors/g, "} catch(_) {\n // Ignore cleanup errors" ); newContent = newContent.replace( /} catch\(error\) {[\s\n]*log\(/g, "} catch(err) {\n log(" ); newContent = newContent.replace( /} catch\(error\) {[\s\n]*return false;/g, "} catch(_) {\n return false;" ); newContent = newContent.replace( /for\(const filePath of filesWithErrors\) {[\s\n]*const success = await fixFileWithCursorAI\(filePath\);/g, "const fixResults = await Promise.all(filesWithErrors.map(filePath => fixFileWithCursorAI(filePath)));\nfor(const success of fixResults) {" ); newContent = newContent.replace( /fixedCount\+\+;/g, "fixedCount += 1;" ); } if (newContent !== fileContent) { writeFileSync(filePath, newContent, "utf8"); log(`Fixed issues in ${filePath}`, "info", quiet); wasModified = true; } return wasModified; } catch (error) { log(`Error applying direct fixes to ${filePath}: ${error.message}`, "error", quiet); return false; } }; const loadAIConfig = async (cwd, quiet, debug = false) => { const configFormats = ["js", "mjs", "cjs", "ts", "json"]; const configBaseName = "lex.config"; let lexConfigPath = ""; for (const format of configFormats) { const potentialPath = pathResolve(cwd, `./${configBaseName}.${format}`); if (existsSync(potentialPath)) { lexConfigPath = potentialPath; break; } } if (lexConfigPath) { try { const format = extname(lexConfigPath).slice(1); let importPath = lexConfigPath; if (format === "mjs") { try { const url = new URL(`file://${lexConfigPath}`); importPath = url.href; if (debug) { log(`Using URL format for MJS import: ${importPath}`, "info", quiet); } } catch (urlError) { log(`Error creating URL for MJS import: ${urlError.message}`, "warn", debug || !quiet); importPath = `file://${lexConfigPath}`; } } if (debug) { log(`Trying to import config from ${importPath} (format: ${format})`, "info", quiet); } let lexConfig; try { lexConfig = await import(importPath); } catch (importError) { if (importError.message.includes("not defined in ES module scope")) { log(`ES Module syntax error in ${lexConfigPath}. Make sure you're using 'export' instead of 'module.exports'.`, "error", quiet); if (debug) { console.error(importError); } return; } throw importError; } let configData = null; if (lexConfig.default) { configData = lexConfig.default; if (debug) { log(`Found default export in ${lexConfigPath}`, "info", quiet); } } else { configData = lexConfig; if (debug) { log(`Using direct export in ${lexConfigPath}`, "info", quiet); } } if (configData && configData.ai) { log(`Found AI configuration in ${pathResolve(cwd, lexConfigPath)}, applying settings...`, "info", quiet); LexConfig.config.ai = { ...LexConfig.config.ai, ...configData.ai }; } } catch (error) { log(`Error loading AI configuration from ${lexConfigPath}: ${error.message}`, "warn", quiet); if (debug) { console.error(error); } } } }; const loadESLintConfig = async (cwd, quiet, debug) => { if (LexConfig.config.eslint && Object.keys(LexConfig.config.eslint).length > 0) { log("Found ESLint configuration in lex.config.* file", "info", debug || !quiet); return true; } const configFormats = ["js", "mjs", "cjs", "ts", "json"]; const configBaseName = "lex.config"; for (const format of configFormats) { const potentialPath = pathResolve(cwd, `./${configBaseName}.${format}`); if (existsSync(potentialPath)) { try { const fileFormat = extname(potentialPath).slice(1); let importPath = potentialPath; if (fileFormat === "mjs") { try { const url = new URL(`file://${potentialPath}`); importPath = url.href; if (debug) { log(`Using URL format for MJS import: ${importPath}`, "info", quiet); } } catch (urlError) { log(`Error creating URL for MJS import: ${urlError.message}`, "warn", debug || !quiet); importPath = `file://${potentialPath}`; } } if (debug) { log(`Trying to import config from ${importPath} (format: ${fileFormat})`, "info", quiet); } let lexConfig; try { lexConfig = await import(importPath); } catch (importError) { if (importError.message.includes("not defined in ES module scope")) { log(`ES Module syntax error in ${potentialPath}. Make sure you're using 'export' instead of 'module.exports'.`, "error", quiet); if (debug) { console.error(importError); } continue; } throw importError; } let configData = null; if (lexConfig.default) { configData = lexConfig.default; if (debug) { log(`Found default export in ${potentialPath}`, "info", quiet); } } else { configData = lexConfig; if (debug) { log(`Using direct export in ${potentialPath}`, "info", quiet); } } if (configData && configData.eslint && Object.keys(configData.eslint).length > 0) { log(`Found ESLint configuration in ${pathResolve(cwd, potentialPath)}, applying settings...`, "info", debug || !quiet); LexConfig.config.eslint = { ...LexConfig.config.eslint, ...configData.eslint }; return true; } } catch (error) { log(`Error loading ESLint configuration from ${potentialPath}: ${error.message}`, "warn", quiet); if (debug) { console.error(error); } } } } return false; }; const removeFileComments = (filePath, quiet) => { try { const fileContent = readFileSync(filePath, "utf8"); if (fileContent.length > 1e6) { log(`Skipping comment removal for large file: ${filePath}`, "info", quiet); return false; } let newContent = fileContent.replace( /\/\*[\s\S]*?\*\//g, (match) => { if (match.includes("Copyright") || match.includes("LICENSE") || match.includes("License") || match.includes("license")) { return match; } return ""; } ); newContent = newContent.replace( /\/\/.*$/gm, (match) => { if (match.includes("TODO") || match.includes("FIXME")) { return match; } return ""; } ); newContent = newContent.replace(/\n\s*\n\s*\n/g, "\n\n"); if (newContent !== fileContent) { writeFileSync(filePath, newContent, "utf8"); log(`Removed comments from ${filePath}`, "info", quiet); return true; } return false; } catch (error) { log(`Error removing comments from ${filePath}: ${error.message}`, "error", quiet); return false; } }; const lint = async (cmd, callback = process.exit) => { const { cliName = "Lex", fix = false, debug = false, quiet = false, config = null, removeComments = false, "remove-comments": removeCommentsFlag = false } = cmd; const shouldRemoveComments = removeComments || removeCommentsFlag; log(`${cliName} linting...`, "info", quiet); const cwd = process.cwd(); const spinner = createSpinner(quiet); await loadAIConfig(cwd, quiet, debug); let tempConfigPath = null; try { const useTypescript = detectTypeScript(cwd); log(`TypeScript ${useTypescript ? "detected" : "not detected"} from tsconfig.json`, "info", quiet); if (useTypescript) { LexConfig.checkLintTypescriptConfig(); } ensureModuleType(cwd); await installDependencies(cwd, useTypescript, quiet); const projectConfigPath = pathResolve(cwd, "eslint.config.mjs"); const projectConfigPathTs = pathResolve(cwd, "eslint.config.ts"); const hasEslintConfig = existsSync(projectConfigPath) || existsSync(projectConfigPathTs) || existsSync(pathResolve(cwd, ".eslintrc.js")) || existsSync(pathResolve(cwd, ".eslintrc.json")) || existsSync(pathResolve(cwd, ".eslintrc.yml")) || existsSync(pathResolve(cwd, ".eslintrc.yaml")) || existsSync(pathResolve(cwd, ".eslintrc")); const hasLexEslintConfig = await loadESLintConfig(cwd, quiet, debug); if (hasLexEslintConfig) { log("Using ESLint configuration from lex.config.* file", "info", quiet); } if (existsSync(pathResolve(cwd, ".eslintrc.json"))) { unlinkSync(pathResolve(cwd, ".eslintrc.json")); } let lexConfigPath = ""; let shouldCreateTempConfig = false; if (!hasEslintConfig && !hasLexEslintConfig) { const possiblePaths = [ pathResolve(currentDirname, "../../../../eslint.config.ts"), pathResolve(currentDirname, "../../../../eslint.config.jms"), pathResolve(process.env.LEX_HOME || "./node_modules/@nlabs/lex", "eslint.config.ts"), pathResolve(process.env.LEX_HOME || "./node_modules/@nlabs/lex", "eslint.config.mjs") ]; for (const path of possiblePaths) { if (existsSync(path)) { lexConfigPath = path; break; } } if (debug) { log(`Current directory: ${currentDirname}`, "info", quiet); log(`Project config path: ${projectConfigPath}`, "info", quiet); log(`Project config exists: ${hasEslintConfig}`, "info", quiet); log(`Found Lex config: ${lexConfigPath}`, "info", quiet); log(`Lex config exists: ${!!lexConfigPath && existsSync(lexConfigPath)}`, "info", quiet); } if (lexConfigPath && existsSync(lexConfigPath)) { log("No ESLint configuration found in project. Using Lex's default configuration.", "info", quiet); } else { shouldCreateTempConfig = true; } } if (config) { const userConfigPath = pathResolve(cwd, config); if (existsSync(userConfigPath)) { log(`Using specified ESLint configuration: ${config}`, "info", quiet); shouldCreateTempConfig = false; } else { log(`Specified ESLint configuration not found: ${config}. Using Lex's default configuration.`, "warn", quiet); } } if (shouldCreateTempConfig) { log("No ESLint configuration found. Creating a temporary configuration...", "info", quiet); const configResult = createDefaultESLintConfig(useTypescript, cwd); tempConfigPath = configResult.configPath; } let eslintOutput = ""; const captureOutput = (output) => { eslintOutput += `${output} `; }; const result = await runEslintWithLex(cwd, quiet, cliName, true, debug, useTypescript, captureOutput); if (shouldRemoveComments) { spinner.start("Removing comments from files..."); const glob = await import("glob"); const files = glob.sync("{src,lib}/**/*.{js,jsx,ts,tsx}", { cwd, ignore: ["**/node_modules/**", "**/lib/**", "**/dist/**", "**/build/**"] }); let processedCount = 0; for (const file of files) { const filePath = pathResolve(cwd, file); if (removeFileComments(filePath, quiet)) { processedCount++; } } spinner.succeed(`Removed comments from ${processedCount} files`); } if (result !== 0 && fix) { const aiConfigured = LexConfig.config.ai?.provider && LexConfig.config.ai.provider !== "none"; if (aiConfigured) { log("Applying AI fixes to remaining issues...", "info", quiet); await applyAIFix(cwd, eslintOutput, quiet); const afterFixResult = await runEslintWithLex(cwd, quiet, cliName, false, debug, useTypescript); callback(afterFixResult); return afterFixResult; } log("ESLint could not fix all issues automatically.", "warn", quiet); log("To enable AI-powered fixes, add AI configuration to your lex.config file:", "info", quiet); log(` // In lex.config.js (or lex.config.mjs, lex.config.cjs, etc.) export default { // Your existing config ai: { provider: 'cursor' // or 'openai', 'anthropic', etc. // Additional provider-specific settings } };`, "info", quiet); } callback(result); return result; } catch (error) { log(` ${cliName} Error: ${error.message}`, "error", quiet); if (spinner) { spinner.fail("Linting failed!"); } callback(1); return 1; } finally { const tempFilePaths = [ tempConfigPath, pathResolve(cwd, ".lex-temp-eslint.cjs"), pathResolve(cwd, ".lex-temp-default-eslint.cjs") ]; for (const filePath of tempFilePaths) { if (filePath && existsSync(filePath)) { try { unlinkSync(filePath); if (debug) { log(`Cleaned up temporary ESLint config at ${filePath}`, "info", quiet); } } catch { } } } } }; export { lint }; //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vc3JjL2NvbW1hbmRzL2xpbnQvbGludC50cyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiLyoqXG4gKiBDb3B5cmlnaHQgKGMpIDIwMjItUHJlc2VudCwgTml0cm9nZW4gTGFicywgSW5jLlxuICogQ29weXJpZ2h0cyBsaWNlbnNlZCB1bmRlciB0aGUgTUlUIExpY2Vuc2UuIFNlZSB0aGUgYWNjb21wYW55aW5nIExJQ0VOU0UgZmlsZSBmb3IgdGVybXMuXG4gKi9cbmltcG9ydCB7ZXhlY2F9IGZyb20gJ2V4ZWNhJztcbmltcG9ydCB7ZXhpc3RzU3luYywgcmVhZEZpbGVTeW5jLCB1bmxpbmtTeW5jLCB3cml0ZUZpbGVTeW5jfSBmcm9tICdmcyc7XG5pbXBvcnQge2Rpcm5hbWUsIHJlc29sdmUgYXMgcGF0aFJlc29sdmUsIGV4dG5hbWV9IGZyb20gJ3BhdGgnO1xuXG5pbXBvcnQge0xleENvbmZpZ30gZnJvbSAnLi4vLi4vTGV4Q29uZmlnLmpzJztcbmltcG9ydCB7Y3JlYXRlU3Bpbm5lcn0gZnJvbSAnLi4vLi4vdXRpbHMvYXBwLmpzJztcbmltcG9ydCB7cmVzb2x2ZUJpbmFyeVBhdGh9IGZyb20gJy4uLy4uL3V0aWxzL2ZpbGUuanMnO1xuaW1wb3J0IHtsb2d9IGZyb20gJy4uLy4uL3V0aWxzL2xvZy5qcyc7XG5cbmxldCBjdXJyZW50RmlsZW5hbWU6IHN0cmluZztcbmxldCBjdXJyZW50RGlybmFtZTogc3RyaW5nO1xuXG50cnkge1xuICBjdXJyZW50RmlsZW5hbWUgPSBldmFsKCdyZXF1aXJlKFwidXJsXCIpLmZpbGVVUkxUb1BhdGgoaW1wb3J0Lm1ldGEudXJsKScpO1xuICBjdXJyZW50RGlybmFtZSA9IGRpcm5hbWUoY3VycmVudEZpbGVuYW1lKTtcbn0gY2F0Y2gge1xuICBjdXJyZW50RmlsZW5hbWUgPSBwcm9jZXNzLmN3ZCgpO1xuICBjdXJyZW50RGlybmFtZSA9IHByb2Nlc3MuY3dkKCk7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgTGludE9wdGlvbnMge1xuICByZWFkb25seSBjYWNoZT86IGJvb2xlYW47XG4gIHJlYWRvbmx5IGNhY2hlRmlsZT86IHN0cmluZztcbiAgcmVhZG9ubHkgY2FjaGVMb2NhdGlvbj86IHN0cmluZztcbiAgcmVhZG9ubHkgY2xpTmFtZT86IHN0cmluZztcbiAgcmVhZG9ubHkgY29sb3I/OiBib29sZWFuO1xuICByZWFkb25seSBjb25maWc/OiBzdHJpbmc7XG4gIHJlYWRvbmx5IGRlYnVnPzogYm9vbGVhbjtcbiAgcmVhZG9ubHkgZW52Pzogc3RyaW5nO1xuICByZWFkb25seSBlbnZJbmZvPzogYm9vbGVhbjtcbiAgcmVhZG9ubHkgZXh0Pzogc3RyaW5nO1xuICByZWFkb25seSBmaXg/OiBib29sZWFuO1xuICByZWFkb25seSBmaXhEcnlSdW4/OiBib29sZWFuO1xuICByZWFkb25seSBmaXhUeXBlPzogc3RyaW5nO1xuICByZWFkb25seSBmb3JtYXQ/OiBzdHJpbmc7XG4gIHJlYWRvbmx5IGdsb2JhbD86IHN0cmluZztcbiAgcmVhZG9ubHkgaWdub3JlUGF0aD86IHN0cmluZztcbiAgcmVhZG9ubHkgaWdub3JlUGF0dGVybj86IHN0cmluZztcbiAgcmVhZG9ubHkgaW5pdD86IGJvb2xlYW47XG4gIHJlYWRvbmx5IG1heFdhcm5pbmdzPzogc3RyaW5nO1xuICByZWFkb25seSBub0NvbG9yPzogYm9vbGVhbjtcbiAgcmVhZG9ubHkgbm9Fc2xpbnRyYz86IGJvb2xlYW47XG4gIHJlYWRvbmx5IG5vSWdub3JlPzogYm9vbGVhbjtcbiAgcmVhZG9ubHkgbm9JbmxpbmVDb25maWc/OiBib29sZWFuO1xuICByZWFkb25seSBvdXRwdXRGaWxlPzogc3RyaW5nO1xuICByZWFkb25seSBwYXJzZXI/OiBzdHJpbmc7XG4gIHJlYWRvbmx5IHBhcnNlck9wdGlvbnM/OiBzdHJpbmc7XG4gIHJlYWRvbmx5IHBsdWdpbj86IHN0cmluZztcbiAgcmVhZG9ubHkgcHJpbnRDb25maWc/OiBzdHJpbmc7XG4gIHJlYWRvbmx5IHF1aWV0PzogYm9vbGVhbjtcbiAgcmVhZG9ubHkgcmVtb3ZlQ29tbWVudHM/OiBib29sZWFuO1xuICByZWFkb25seSByZXBvcnRVbnVzZWREaXNhYmxlRGlyZWN0aXZlcz86IGJvb2xlYW47XG4gIHJlYWRvbmx5IHJlc29sdmVQbHVnaW5zUmVsYXRpdmVUbz86IHN0cmluZztcbiAgcmVhZG9ubHkgcnVsZT86IHN0cmluZztcbiAgcmVhZG9ubHkgcnVsZXNkaXI/OiBzdHJpbmc7XG4gIHJlYWRvbmx5IHN0ZGluPzogYm9vbGVhbjtcbiAgcmVhZG9ubHkgc3RkaW5GaWxlbmFtZT86IHN0cmluZztcbn1cblxuZXhwb3J0IHR5cGUgTGludENhbGxiYWNrID0gdHlwZW9mIHByb2Nlc3MuZXhpdDtcblxuaW50ZXJmYWNlIENvbmZpZ1Jlc3VsdCB7XG4gIGNvbmZpZ1BhdGg6IHN0cmluZztcbiAgb3JpZ2luYWxDb25maWc6IHN0cmluZyB8IG51bGw7XG59XG5cbmNvbnN0IGNyZWF0ZURlZmF1bHRFU0xpbnRDb25maWcgPSAodXNlVHlwZXNjcmlwdDogYm9vbGVhbiwgY3dkOiBzdHJpbmcpOiBDb25maWdSZXN1bHQgPT4ge1xuICAvLyBVc2UgYSB0ZW1wb3JhcnkgZmlsZSBwYXRoIGluc3RlYWQgb2YgY3JlYXRpbmcgaW4gdGhlIHByb2plY3QgZGlyZWN0b3J5XG4gIGNvbnN0IGNvbmZpZ1BhdGggPSBwYXRoUmVzb2x2ZShjd2QsICcubGV4LXRlbXAtZGVmYXVsdC1lc2xpbnQuY2pzJyk7XG4gIGNvbnN0IG9yaWdpbmFsQ29uZmlnID0gbnVsbDtcblxuICAvLyBDcmVhdGUgYSB0ZW1wb3JhcnkgQ29tbW9uSlMgbW9kdWxlIHRoYXQgcmVxdWlyZXMgTGV4J3MgRVNMaW50IGNvbmZpZ1xuICBjb25zdCBjb25maWdDb250ZW50ID0gYC8vIFRlbXBvcmFyeSBFU0xpbnQgY29uZmlnIGdlbmVyYXRlZCBieSBMZXhcbmNvbnN0IGxleENvbmZpZyA9IHJlcXVpcmUoJ0BubGFicy9sZXgvZXNsaW50LmNvbmZpZy5tanMnKTtcblxubW9kdWxlLmV4cG9ydHMgPSBsZXhDb25maWc7YDtcblxuICB3cml0ZUZpbGVTeW5jKGNvbmZpZ1BhdGgsIGNvbmZpZ0NvbnRlbnQsICd1dGY4Jyk7XG5cbiAgcmV0dXJuIHtcbiAgICBjb25maWdQYXRoLFxuICAgIG9yaWdpbmFsQ29uZmlnXG4gIH07XG59O1xuXG5jb25zdCBkZXRlY3RUeXBlU2NyaXB0ID0gKGN3ZDogc3RyaW5nKTogYm9vbGVhbiA9PiBleGlzdHNTeW5jKHBhdGhSZXNvbHZlKGN3ZCwgJ3RzY29uZmlnLmpzb24nKSk7XG5cbi8qKlxuICogRW5zdXJlIHBhY2thZ2UuanNvbiBoYXMgdHlwZTogbW9kdWxlIGZvciBFU00gc3VwcG9ydFxuICovXG5jb25zdCBlbnN1cmVNb2R1bGVUeXBlID0gKGN3ZDogc3RyaW5nKTogdm9pZCA9PiB7XG4gIGNvbnN0IHBhY2thZ2VKc29uUGF0aCA9IHBhdGhSZXNvbHZlKGN3ZCwgJ3BhY2thZ2UuanNvbicpO1xuXG4gIGlmKGV4aXN0c1N5bmMocGFja2FnZUpzb25QYXRoKSkge1xuICAgIHRyeSB7XG4gICAgICBjb25zdCBwYWNrYWdlSnNvbkNvbnRlbnQgPSByZWFkRmlsZVN5bmMocGFja2FnZUpzb25QYXRoLCAndXRmOCcpO1xuICAgICAgY29uc3QgcGFja2FnZUpzb24gPSBKU09OLnBhcnNlKHBhY2thZ2VKc29uQ29udGVudCk7XG5cbiAgICAgIC8vIElmIHR5cGUgaXMgbm90IHNldCB0byBtb2R1bGUsIHdhcm4gaW5zdGVhZCBvZiBhdXRvLW1vZGlmeWluZ1xuICAgICAgaWYocGFja2FnZUpzb24udHlwZSAhPT0gJ21vZHVsZScpIHtcbiAgICAgICAgbG9nKCdXYXJuaW5nOiBwYWNrYWdlLmpzb24gc2hvdWxkIGhhdmUgXCJ0eXBlXCI6IFwibW9kdWxlXCIgZm9yIEVTTSBzdXBwb3J0LiBQbGVhc2UgYWRkIHRoaXMgbWFudWFsbHkuJywgJ3dhcm4nLCBmYWxzZSk7XG4gICAgICB9XG4gICAgfSBjYXRjaCAoX2Vycm9yKSB7XG4gICAgICAvLyBJZ25vcmUgZXJyb3JzXG4gICAgfVxuICB9XG59O1xuXG5jb25zdCBpbnN0YWxsRGVwZW5kZW5jaWVzID0gYXN5bmMgKGN3ZDogc3RyaW5nLCB1c2VUeXBlc2NyaXB0OiBib29sZWFuLCBxdWlldDogYm9vbGVhbik6IFByb21pc2U8dm9pZD4gPT4ge1xuICBpZih1c2VUeXBlc2NyaXB0KSB7XG4gICAgbG9nKCdVc2luZyBUeXBlU2NyaXB0IEVTTGludCBmcm9tIExleC4uLicsICdpbmZvJywgcXVpZXQpO1xuICB9IGVsc2Uge1xuICAgIGxvZygnVXNpbmcgRVNMaW50IGZyb20gTGV4Li4uJywgJ2luZm8nLCBxdWlldCk7XG4gIH1cbn07XG5cbmNvbnN0IHJ1bkVzbGludFdpdGhMZXggPSBhc3luYyAoXG4gIGN3ZDogc3RyaW5nLFxuICBxdWlldDogYm9vbGVhbixcbiAgY2xpTmFtZTogc3RyaW5nLFxuICBmaXg6IGJvb2xlYW4sXG4gIGRlYnVnOiBib29sZWFuLFxuICB1c2VUeXBlc2NyaXB0OiBib29sZWFuLFxuICBjYXB0dXJlT3V0cHV0PzogKG91dHB1dDogc3RyaW5nKSA9PiB2b2lkXG4pOiBQcm9taXNlPG51bWJlcj4gPT4ge1xuICBjb25zdCBzcGlubmVyID0gY3JlYXRlU3Bpbm5lcihxdWlldCk7XG5cbiAgdHJ5IHtcbiAgICBjb25zdCBwcm9qZWN0Q29uZmlnUGF0aCA9IHBhdGhSZXNvbHZlKGN3ZCwgJ2VzbGludC5jb25maWcubWpzJyk7XG4gICAgY29uc3QgcHJvamVjdENvbmZpZ1BhdGhUcyA9IHBhdGhSZXNvbHZlKGN3ZCwgJ2VzbGludC5jb25maWcudHMnKTtcbiAgICBjb25zdCBoYXNQcm9qZWN0Q29uZmlnID0gZXhpc3RzU3luYyhwcm9qZWN0Q29uZmlnUGF0aCkgfHwgZXhpc3RzU3luYyhwcm9qZWN0Q29uZmlnUGF0aFRzKTtcbiAgICBjb25zdCBoYXNMZXhDb25maWdFc2xpbnQgPSBMZXhDb25maWcuY29uZmlnLmVzbGludCAmJiBPYmplY3Qua2V5cyhMZXhDb25maWcuY29uZmlnLmVzbGludCkubGVuZ3RoID4gMDtcblxuICAgIGNvbnN0IHBvc3NpYmxlUGF0aHMgPSBbXG4gICAgICBwYXRoUmVzb2x2ZShjdXJyZW50RGlybmFtZSwgJy4uLy4uLy4uLy4uL2VzbGludC5jb25maWcubWpzJyksXG4gICAgICBwYXRoUmVzb2x2ZShjdXJyZW50RGlybmFtZSwgJy4uLy4uLy4uLy4uL2VzbGludC5jb25maWcudHMnKSxcbiAgICAgIHBhdGhSZXNvbHZlKHByb2Nlc3MuZW52LkxFWF9IT01FIHx8ICcvdXNyL2xvY2FsL2xpYi9ub2RlX21vZHVsZXMvQG5sYWJzL2xleCcsICdlc2xpbnQuY29uZmlnLm1qcycpLFxuICAgICAgcGF0aFJlc29sdmUocHJvY2Vzcy5lbnYuTEVYX0hPTUUgfHwgJy91c3IvbG9jYWwvbGliL25vZGVfbW9kdWxlcy9AbmxhYnMvbGV4JywgJ2VzbGludC5jb25maWcudHMnKVxuICAgIF07XG5cbiAgICBsZXQgbGV4Q29uZmlnUGF0aCA9ICcnO1xuXG4gICAgZm9yKGNvbnN0IHBhdGggb2YgcG9zc2libGVQYXRocykge1xuICAgICAgaWYoZXhpc3RzU3luYyhwYXRoKSkge1xuICAgICAgICBsZXhDb25maWdQYXRoID0gcGF0aDtcbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG4gICAgfVxuXG4gICAgbGV0IGNvbmZpZ1BhdGggPSAnJztcbiAgICBsZXQgdGVtcENvbmZpZ1BhdGggPSAnJztcblxuICAgIC8vIFByaW9yaXR5OlxuICAgIC8vIDEuIFByb2plY3QgZXNsaW50LmNvbmZpZyBmaWxlc1xuICAgIC8vIDIuIEVTTGludCBjb25maWcgaW4gbGV4LmNvbmZpZy4qIGZpbGVcbiAgICAvLyAzLiBMZXgncyBkZWZhdWx0IGVzbGludC5jb25maWcubWpzXG4gICAgLy8gNC4gQ3JlYXRlIGEgdGVtcG9yYXJ5IGNvbmZpZyBmaWxlXG4gICAgaWYoaGFzUHJvamVjdENvbmZpZykge1xuICAgICAgY29uZmlnUGF0aCA9IGV4aXN0c1N5bmMocHJvamVjdENvbmZpZ1BhdGhUcykgPyBwcm9qZWN0Q29uZmlnUGF0aFRzIDogcHJvamVjdENvbmZpZ1BhdGg7XG4gICAgICBpZihkZWJ1Zykge1xuICAgICAgICBsb2coYFVzaW5nIHByb2plY3QgRVNMaW50IGNvbmZpZyBmaWxlOiAke2NvbmZpZ1BhdGh9YCwgJ2luZm8nLCBxdWlldCk7XG4gICAgICB9XG4gICAgfSBlbHNlIGlmKGhhc0xleENvbmZpZ0VzbGludCkge1xuICAgICAgLy8gV2hlbiB1c2luZyBsZXguY29uZmlnLmVzbGludCwgY3JlYXRlIGEgdGVtcG9yYXJ5IEpTIGNvbmZpZyBmaWxlIChub3QgSlNPTilcbiAgICAgIC8vIHRvIGF2b2lkIEVTTSBKU09OIGltcG9ydCBpc3N1ZXNcbiAgICAgIHRlbXBDb25maWdQYXRoID0gcGF0aFJlc29sdmUoY3dkLCAnLmxleC10ZW1wLWVzbGludC5janMnKTtcblxuICAgICAgLy8gQ3JlYXRlIGEgQ29tbW9uSlMgbW9kdWxlIHRoYXQgZXh0ZW5kcyBMZXgncyBlc2xpbnQgY29uZmlnXG4gICAgICBjb25zdCBjb25maWdDb250ZW50ID0gYC8vIFRlbXBvcmFyeSBFU0xpbnQgY29uZmlnIGdlbmVyYXRlZCBieSBMZXhcbmNvbnN0IGxleENvbmZpZyA9IHJlcXVpcmUoJ0BubGFicy9sZXgvZXNsaW50LmNvbmZpZy5tanMnKTtcbmNvbnN0IHVzZXJDb25maWcgPSAke0pTT04uc3RyaW5naWZ5KExleENvbmZpZy5jb25maWcuZXNsaW50LCBudWxsLCAyKX07XG5cbi8vIE1lcmdlIExleCdzIGNvbmZpZyB3aXRoIHVzZXIgY29uZmlnXG5tb2R1bGUuZXhwb3J0cyA9IHtcbiAgLi4ubGV4Q29uZmlnXG59O2A7XG5cbiAgICAgIHdyaXRlRmlsZVN5bmModGVtcENvbmZpZ1BhdGgsIGNvbmZpZ0NvbnRlbnQsICd1dGY4Jyk7XG4gICAgICBjb25maWdQYXRoID0gdGVtcENvbmZpZ1BhdGg7XG5cbiAgICAgIGlmKGRlYnVnKSB7XG4gICAgICAgIGxvZyhgVXNpbmcgRVNMaW50IGNvbmZpZyBmcm9tIGxleC5jb25maWcuKiBmaWxlIHZpYSB0ZW1wIGZpbGU6ICR7dGVtcENvbmZpZ1BhdGh9YCwgJ2luZm8nLCBxdWlldCk7XG4gICAgICB9XG4gICAgfSBlbHNlIGlmKGxleENvbmZpZ1BhdGggJiYgZXhpc3RzU3luYyhsZXhDb25maWdQYXRoKSkge1xuICAgICAgY29uZmlnUGF0aCA9IGxleENvbmZpZ1BhdGg7XG4gICAgICBpZihkZWJ1Zykge1xuICAgICAgICBsb2coYFVzaW5nIExleCBFU0xpbnQgY29uZmlnIGZpbGU6ICR7Y29uZmlnUGF0aH1gLCAnaW5mbycsIHF1aWV0KTtcbiAgICAgIH1cbiAgICB9IGVsc2Uge1xuICAgICAgLy8gQ3JlYXRlIGEgdGVtcG9yYXJ5IGRlZmF1bHQgY29uZmlnIGZpbGUgaWYgbm8gb3RoZXIgY29uZmlnIGlzIGZvdW5kXG4gICAgICB0ZW1wQ29uZmlnUGF0aCA9IHBhdGhSZXNvbHZlKGN3ZCwgJy5sZXgtdGVtcC1kZWZhdWx0LWVzbGludC5janMnKTtcblxuICAgICAgLy8gQ3JlYXRlIGEgYmFzaWMgRVNMaW50IGNvbmZpZ1xuICAgICAgY29uc3QgY29uZmlnQ29udGVudCA9IGAvLyBUZW1wb3JhcnkgZGVmYXVsdCBFU0xpbnQgY29uZmlnIGdlbmVyYXRlZCBieSBMZXhcbmNvbnN0IGxleENvbmZpZyA9IHJlcXVpcmUoJ0BubGFicy9sZXgvZXNsaW50LmNvbmZpZy5tanMnKTtcblxubW9kdWxlLmV4cG9ydHMgPSBsZXhDb25maWc7YDtcblxuICAgICAgd3JpdGVGaWxlU3luYyh0ZW1wQ29uZmlnUGF0aCwgY29uZmlnQ29udGVudCwgJ3V0ZjgnKTtcbiAgICAgIGNvbmZpZ1BhdGggPSB0ZW1wQ29uZmlnUGF0aDtcblxuICAgICAgaWYoZGVidWcpIHtcbiAgICAgICAgbG9nKGBDcmVhdGVkIHRlbXBvcmFyeSBkZWZhdWx0IEVTTGludCBjb25maWcgYXQ6ICR7dGVtcENvbmZpZ1BhdGh9YCwgJ2luZm8nLCBxdWlldCk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBsb2coJ05vIEVTTGludCBjb25maWd1cmF0aW9uIGZvdW5kLiBVc2luZyBMZXggZGVmYXVsdCBjb25maWd1cmF0aW9uLicsICdpbmZvJywgcXVpZXQpO1xuICAgICAgfVxuICAgIH1cblxuICAgIGNvbnN0IGVzbGludEJpbmFyeSA9IHJlc29sdmVCaW5hcnlQYXRoKCdlc2xpbnQnLCAnZXNsaW50Jyk7XG5cbiAgICBpZighZXNsaW50QmluYXJ5KSB7XG4gICAgICBsb2coYFxcbiR7Y2xpTmFtZX0gRXJyb3I6IEVTTGludCBiaW5hcnkgbm90IGZvdW5kIGluIExleCdzIG5vZGVfbW9kdWxlc2AsICdlcnJvcicsIHF1aWV0KTtcbiAgICAgIGxvZygnUGxlYXNlIHJlaW5zdGFsbCBMZXggb3IgY2hlY2sgeW91ciBpbnN0YWxsYXRpb24uJywgJ2luZm8nLCBxdWlldCk7XG4gICAgICByZXR1cm4gMTtcbiAgICB9XG5cbiAgICAvLyBCYXNlIEVTTGludCBhcmd1bWVudHNcbiAgICBjb25zdCBiYXNlRXNsaW50QXJncyA9IFtcbiAgICAgIC4uLihmaXggPyBbJy0tZml4J10gOiBbXSksXG4gICAgICAuLi4oZGVidWcgPyBbJy0tZGVidWcnXSA6IFtdKSxcbiAgICAgICctLW5vLWVycm9yLW9uLXVubWF0Y2hlZC1wYXR0ZXJuJyxcbiAgICAgICctLW5vLXdhcm4taWdub3JlZCdcbiAgICBdO1xuXG4gICAgLy8gQWRkIGNvbmZpZyBwYXRoXG4gICAgY29uc3QgY29uZmlnQXJncyA9IGNvbmZpZ1BhdGggPyBbJy0tY29uZmlnJywgY29uZmlnUGF0aF0gOiBbXTtcblxuICAgIGNvbnN0IGpzUmVzdWx0ID0gYXdhaXQgZXhlY2EoZXNsaW50QmluYXJ5LCBbXG4gICAgICAnc3JjLyoqLyoue2pzLGpzeH0nLFxuICAgICAgLi4uY29uZmlnQXJncyxcbiAgICAgIC4uLmJhc2VFc2xpbnRBcmdzXG4gICAgXSwge1xuICAgICAgY3dkLFxuICAgICAgcmVqZWN0OiBmYWxzZSxcbiAgICAgIHNoZWxsOiB0cnVlLFxuICAgICAgc3RkaW86ICdwaXBlJ1xuICAgIH0pO1xuXG4gICAgaWYoanNSZXN1bHQuc3Rkb3V0KSB7XG4gICAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgbm8tY29uc29sZVxuICAgICAgY29uc29sZS5sb2coanNSZXN1bHQuc3Rkb3V0KTtcbiAgICAgIGlmKGNhcHR1cmVPdXRwdXQpIHtcbiAgICAgICAgY2FwdHVyZU91dHB1dChqc1Jlc3VsdC5zdGRvdXQpO1xuICAgICAgfVxuICAgIH1cblxuICAgIGlmKGpzUmVzdWx0LnN0ZGVycikge1xuICAgICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIG5vLWNvbnNvbGVcbiAgICAgIGNvbnNvbGUuZXJyb3IoanNSZXN1bHQuc3RkZXJyKTtcbiAgICAgIGlmKGNhcHR1cmVPdXRwdXQpIHtcbiAgICAgICAgY2FwdHVyZU91dHB1dChqc1Jlc3VsdC5zdGRlcnIpO1xuICAgICAgfVxuICAgIH1cblxuICAgIGxldCB0c1Jlc3VsdDogYW55ID0ge2V4aXRDb2RlOiAwLCBzdGRlcnI6ICcnLCBzdGRvdXQ6ICcnfTtcbiAgICBpZih1c2VUeXBlc2NyaXB0KSB7XG4gICAgICB0c1Jlc3VsdCA9IGF3YWl0IGV4ZWNhKGVzbGludEJpbmFyeSwgW1xuICAgICAgICAnc3JjLyoqLyoue3RzLHRzeH0nLFxuICAgICAgICAuLi5jb25maWdBcmdzLFxuICAgICAgICAuLi5iYXNlRXNsaW50QXJnc1xuICAgICAgXSwge1xuICAgICAgICBjd2QsXG4gICAgICAgIHJlamVjdDogZmFsc2UsXG4gICAgICAgIHNoZWxsOiB0cnVlLFxuICAgICAgICBzdGRpbzogJ3BpcGUnXG4gICAgICB9KTtcblxuICAgICAgaWYodHNSZXN1bHQuc3Rkb3V0KSB7XG4gICAgICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBuby1jb25zb2xlXG4gICAgICAgIGNvbnNvbGUubG9nKHRzUmVzdWx0LnN0ZG91dCk7XG4gICAgICB9XG5cbiAgICAgIGlmKHRzUmVzdWx0LnN0ZGVycikge1xuICAgICAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgbm8tY29uc29sZVxuICAgICAgICBjb25zb2xlLmVycm9yKHRzUmVzdWx0LnN0ZGVycik7XG4gICAgICB9XG4gICAgfVxuXG4gICAgLy8gQ2xlYW4gdXAgdGVtcCBmaWxlIGlmIGNyZWF0ZWRcbiAgICBpZih0ZW1wQ29uZmlnUGF0aCAmJiBleGlzdHNTeW5jKHRlbXBDb25maWdQYXRoKSkge1xuICAgICAgdHJ5IHtcbiAgICAgICAgdW5saW5rU3luYyh0ZW1wQ29uZmlnUGF0aCk7XG4gICAgICAgIGlmKGRlYnVnKSB7XG4gICAgICAgICAgbG9nKGBSZW1vdmVkIHRlbXBvcmFyeSBFU0xpbnQgY29uZmlnIGF0ICR7dGVtcENvbmZpZ1BhdGh9YCwgJ2luZm8nLCBxdWlldCk7XG4gICAgICAgIH1cbiAgICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgIC8vIElnbm9yZSBlcnJvcnMgd2hlbiBjbGVhbmluZyB1cFxuICAgICAgICBpZihkZWJ1Zykge1xuICAgICAgICAgIGxvZyhgRmFpbGVkIHRvIHJlbW92ZSB0ZW1wb3JhcnkgRVNMaW50IGNvbmZpZzogJHtlcnJvci5tZXNzYWdlfWAsICd3YXJuJywgcXVpZXQpO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuXG4gICAgY29uc3QgZXNsaW50Tm90Rm91bmQgPSBqc1Jlc3VsdC5zdGRlcnI/LmluY2x1ZGVzKCdjb21tYW5kIG5vdCBmb3VuZCcpIHx8IGpzUmVzdWx0LnN0ZGVycj8uaW5jbHVkZXMoJ2VzbGludDogY29tbWFuZCBub3QgZm91bmQnKTtcbiAgICBpZihlc2xpbnROb3RGb3VuZCkge1xuICAgICAgc3Bpbm5lci5mYWlsKCdFU0xpbnQgbm90IGZvdW5kIScpO1xuICAgICAgbG9nKGBcXG4ke2NsaU5hbWV9IEVycm9yOiBMZXgncyBFU0xpbnQgYmluYXJ5IG5vdCBmb3VuZC4gUGxlYXNlIHJlaW5zdGFsbCBMZXggb3IgY2hlY2sgeW91ciBpbnN0YWxsYXRpb24uYCwgJ2Vycm9yJywgcXVpZXQpO1xuICAgICAgcmV0dXJuIDE7XG4gICAgfVxuXG4gICAgaWYoanNSZXN1bHQuZXhpdENvZGUgPT09IDAgJiYgdHNSZXN1bHQuZXhpdENvZGUgPT09IDApIHtcbiAgICAgIHNwaW5uZXIuc3VjY2VlZCgnTGludGluZyBjb21wbGV0ZWQhJyk7XG4gICAgICByZXR1cm4gMDtcbiAgICB9XG5cbiAgICBjb25zdCBub0ZpbGVzRm91bmQgPVxuICAgICAgKGpzUmVzdWx0LnN0ZGVycj8uaW5jbHVkZXMoJ05vIHN1Y2ggZmlsZSBvciBkaXJlY3RvcnknKSB8fCBqc1Jlc3VsdC5zdGRvdXQ/LmluY2x1ZGVzKCdObyBzdWNoIGZpbGUgb3IgZGlyZWN0b3J5JykpICYmXG4gICAgICAoIX