@nlabs/lex
Version:
962 lines (932 loc) • 116 kB
JavaScript
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