UNPKG

@nlabs/lex

Version:
955 lines (924 loc) 126 kB
/** * Copyright (c) 2022-Present, Nitrogen Labs, Inc. * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms. */ 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)=>{ // Use a temporary file path instead of creating in the project directory const configPath = pathResolve(cwd, '.lex-temp-default-eslint.cjs'); const originalConfig = null; // Create a temporary CommonJS module that requires Lex's ESLint config 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')); /** * Ensure package.json has type: module for ESM support */ const ensureModuleType = (cwd)=>{ const packageJsonPath = pathResolve(cwd, 'package.json'); if (existsSync(packageJsonPath)) { try { const packageJsonContent = readFileSync(packageJsonPath, 'utf8'); const packageJson = JSON.parse(packageJsonContent); // If type is not set to module, warn instead of auto-modifying if (packageJson.type !== 'module') { log('Warning: package.json should have "type": "module" for ESM support. Please add this manually.', 'warn', false); } } catch (_error) { // Ignore errors } } }; 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 = ''; // Priority: // 1. Project eslint.config files // 2. ESLint config in lex.config.* file // 3. Lex's default eslint.config.mjs // 4. Create a temporary config file if (hasProjectConfig) { configPath = existsSync(projectConfigPathTs) ? projectConfigPathTs : projectConfigPath; if (debug) { log(`Using project ESLint config file: ${configPath}`, 'info', quiet); } } else if (hasLexConfigEslint) { // When using lex.config.eslint, create a temporary JS config file (not JSON) // to avoid ESM JSON import issues tempConfigPath = pathResolve(cwd, '.lex-temp-eslint.cjs'); // Create a CommonJS module that extends Lex's eslint config 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 { // Create a temporary default config file if no other config is found tempConfigPath = pathResolve(cwd, '.lex-temp-default-eslint.cjs'); // Create a basic ESLint config 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(`\n${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; } // Base ESLint arguments const baseEslintArgs = [ ...fix ? [ '--fix' ] : [], ...debug ? [ '--debug' ] : [], '--no-error-on-unmatched-pattern', '--no-warn-ignored' ]; // Add config path 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) { // eslint-disable-next-line no-console console.log(jsResult.stdout); if (captureOutput) { captureOutput(jsResult.stdout); } } if (jsResult.stderr) { // eslint-disable-next-line no-console 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) { // eslint-disable-next-line no-console console.log(tsResult.stdout); } if (tsResult.stderr) { // eslint-disable-next-line no-console console.error(tsResult.stderr); } } // Clean up temp file if created if (tempConfigPath && existsSync(tempConfigPath)) { try { unlinkSync(tempConfigPath); if (debug) { log(`Removed temporary ESLint config at ${tempConfigPath}`, 'info', quiet); } } catch (error) { // Ignore errors when cleaning up 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(`\n${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(`\n${cliName} Error: ESLint found issues in your code.`, 'error', quiet); return 1; } catch (error) { spinner.fail('Linting failed!'); log(`\n${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 = 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 lines = section.split('\n'); const filePath = lines[0].trim(); if (filePath.match(/\.(js|jsx|ts|tsx)$/)) { fileErrorMap.set(filePath, []); for(let i = 1; i < lines.length; i++){ if (lines[i].trim() !== '') { fileErrorMap.get(filePath)?.push(lines[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'); // Use Cursor CLI to fix the file 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 = \'ℹ️ \';\nif(type === \'error\') {\n prefix = \'❌ \';\n} else if(type === \'success\') {\n prefix = \'✅ \';\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 { // For MJS files, we need to use dynamic import with URL for compatibility const format = extname(lexConfigPath).slice(1); let importPath = lexConfigPath; // Use URL protocol for ESM imports 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; } // Handle both ESM (default export) and CommonJS (module.exports) let configData = null; if (lexConfig.default) { configData = lexConfig.default; if (debug) { log(`Found default export in ${lexConfigPath}`, 'info', quiet); } } else { // For CommonJS or other module systems 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); } } } }; /** * Load ESLint configuration from lex.config.* files */ const loadESLintConfig = async (cwd, quiet, debug)=>{ // Check if LexConfig already has ESLint configuration loaded if (LexConfig.config.eslint && Object.keys(LexConfig.config.eslint).length > 0) { log('Found ESLint configuration in lex.config.* file', 'info', debug || !quiet); return true; } // Try to load from lex.config.* files if not already loaded 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 { // For MJS files, we need to use dynamic import with URL for compatibility const fileFormat = extname(potentialPath).slice(1); let importPath = potentialPath; // Use URL protocol for ESM imports 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; } // Handle both ESM (default export) and CommonJS (module.exports) let configData = null; if (lexConfig.default) { configData = lexConfig.default; if (debug) { log(`Found default export in ${potentialPath}`, 'info', quiet); } } else { // For CommonJS or other module systems 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 > 1000000) { log(`Skipping comment removal for large file: ${filePath}`, 'info', quiet); return false; } // Use regex to match different types of comments // Preserves: // 1. Copyright notices (/* Copyright ... */) // 2. TODO comments (// TODO: ...) // 3. License headers (/* ... License ... */) // Handle multi-line comments first - preserve copyright/license notices let newContent = fileContent.replace(/\/\*[\s\S]*?\*\//g, (match)=>{ if (match.includes('Copyright') || match.includes('LICENSE') || match.includes('License') || match.includes('license')) { return match; } return ''; }); // Handle single-line comments - preserve TODOs newContent = newContent.replace(/\/\/.*$/gm, (match)=>{ if (match.includes('TODO') || match.includes('FIXME')) { return match; } return ''; }); // Clean up any multiple blank lines created by comment removal newContent = newContent.replace(/\n\s*\n\s*\n/g, '\n\n'); // If the file was modified, save it 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; } }; export 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}\n`; }; 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(`\n${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 {} } } } }; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9jb21tYW5kcy9saW50L2xpbnQudHMiXSwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBDb3B5cmlnaHQgKGMpIDIwMjItUHJlc2VudCwgTml0cm9nZW4gTGFicywgSW5jLlxuICogQ29weXJpZ2h0cyBsaWNlbnNlZCB1bmRlciB0aGUgTUlUIExpY2Vuc2UuIFNlZSB0aGUgYWNjb21wYW55aW5nIExJQ0VOU0UgZmlsZSBmb3IgdGVybXMuXG4gKi9cbmltcG9ydCB7ZXhlY2F9IGZyb20gJ2V4ZWNhJztcbmltcG9ydCB7ZXhpc3RzU3luYywgcmVhZEZpbGVTeW5jLCB1bmxpbmtTeW5jLCB3cml0ZUZpbGVTeW5jfSBmcm9tICdmcyc7XG5pbXBvcnQge2Rpcm5hbWUsIHJlc29sdmUgYXMgcGF0aFJlc29sdmUsIGV4dG5hbWV9IGZyb20gJ3BhdGgnO1xuXG5pbXBvcnQge0xleENvbmZpZ30gZnJvbSAnLi4vLi4vTGV4Q29uZmlnLmpzJztcbmltcG9ydCB7Y3JlYXRlU3Bpbm5lcn0gZnJvbSAnLi4vLi4vdXRpbHMvYXBwLmpzJztcbmltcG9ydCB7cmVzb2x2ZUJpbmFyeVBhdGh9IGZyb20gJy4uLy4uL3V0aWxzL2ZpbGUuanMnO1xuaW1wb3J0IHtsb2d9IGZyb20gJy4uLy4uL3V0aWxzL2xvZy5qcyc7XG5cbmxldCBjdXJyZW50RmlsZW5hbWU6IHN0cmluZztcbmxldCBjdXJyZW50RGlybmFtZTogc3RyaW5nO1xuXG50cnkge1xuICBjdXJyZW50RmlsZW5hbWUgPSBldmFsKCdyZXF1aXJlKFwidXJsXCIpLmZpbGVVUkxUb1BhdGgoaW1wb3J0Lm1ldGEudXJsKScpO1xuICBjdXJyZW50RGlybmFtZSA9IGRpcm5hbWUoY3VycmVudEZpbGVuYW1lKTtcbn0gY2F0Y2h7XG4gIGN1cnJlbnRGaWxlbmFtZSA9IHByb2Nlc3MuY3dkKCk7XG4gIGN1cnJlbnREaXJuYW1lID0gcHJvY2Vzcy5jd2QoKTtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBMaW50T3B0aW9ucyB7XG4gIHJlYWRvbmx5IGNhY2hlPzogYm9vbGVhbjtcbiAgcmVhZG9ubHkgY2FjaGVGaWxlPzogc3RyaW5nO1xuICByZWFkb25seSBjYWNoZUxvY2F0aW9uPzogc3RyaW5nO1xuICByZWFkb25seSBjbGlOYW1lPzogc3RyaW5nO1xuICByZWFkb25seSBjb2xvcj86IGJvb2xlYW47XG4gIHJlYWRvbmx5IGNvbmZpZz86IHN0cmluZztcbiAgcmVhZG9ubHkgZGVidWc/OiBib29sZWFuO1xuICByZWFkb25seSBlbnY/OiBzdHJpbmc7XG4gIHJlYWRvbmx5IGVudkluZm8/OiBib29sZWFuO1xuICByZWFkb25seSBleHQ/OiBzdHJpbmc7XG4gIHJlYWRvbmx5IGZpeD86IGJvb2xlYW47XG4gIHJlYWRvbmx5IGZpeERyeVJ1bj86IGJvb2xlYW47XG4gIHJlYWRvbmx5IGZpeFR5cGU/OiBzdHJpbmc7XG4gIHJlYWRvbmx5IGZvcm1hdD86IHN0cmluZztcbiAgcmVhZG9ubHkgZ2xvYmFsPzogc3RyaW5nO1xuICByZWFkb25seSBpZ25vcmVQYXRoPzogc3RyaW5nO1xuICByZWFkb25seSBpZ25vcmVQYXR0ZXJuPzogc3RyaW5nO1xuICByZWFkb25seSBpbml0PzogYm9vbGVhbjtcbiAgcmVhZG9ubHkgbWF4V2FybmluZ3M/OiBzdHJpbmc7XG4gIHJlYWRvbmx5IG5vQ29sb3I/OiBib29sZWFuO1xuICByZWFkb25seSBub0VzbGludHJjPzogYm9vbGVhbjtcbiAgcmVhZG9ubHkgbm9JZ25vcmU/OiBib29sZWFuO1xuICByZWFkb25seSBub0lubGluZUNvbmZpZz86IGJvb2xlYW47XG4gIHJlYWRvbmx5IG91dHB1dEZpbGU/OiBzdHJpbmc7XG4gIHJlYWRvbmx5IHBhcnNlcj86IHN0cmluZztcbiAgcmVhZG9ubHkgcGFyc2VyT3B0aW9ucz86IHN0cmluZztcbiAgcmVhZG9ubHkgcGx1Z2luPzogc3RyaW5nO1xuICByZWFkb25seSBwcmludENvbmZpZz86IHN0cmluZztcbiAgcmVhZG9ubHkgcXVpZXQ/OiBib29sZWFuO1xuICByZWFkb25seSByZW1vdmVDb21tZW50cz86IGJvb2xlYW47XG4gIHJlYWRvbmx5IHJlcG9ydFVudXNlZERpc2FibGVEaXJlY3RpdmVzPzogYm9vbGVhbjtcbiAgcmVhZG9ubHkgcmVzb2x2ZVBsdWdpbnNSZWxhdGl2ZVRvPzogc3RyaW5nO1xuICByZWFkb25seSBydWxlPzogc3RyaW5nO1xuICByZWFkb25seSBydWxlc2Rpcj86IHN0cmluZztcbiAgcmVhZG9ubHkgc3RkaW4/OiBib29sZWFuO1xuICByZWFkb25seSBzdGRpbkZpbGVuYW1lPzogc3RyaW5nO1xufVxuXG5leHBvcnQgdHlwZSBMaW50Q2FsbGJhY2sgPSB0eXBlb2YgcHJvY2Vzcy5leGl0O1xuXG5pbnRlcmZhY2UgQ29uZmlnUmVzdWx0IHtcbiAgY29uZmlnUGF0aDogc3RyaW5nO1xuICBvcmlnaW5hbENvbmZpZzogc3RyaW5nIHwgbnVsbDtcbn1cblxuY29uc3QgY3JlYXRlRGVmYXVsdEVTTGludENvbmZpZyA9ICh1c2VUeXBlc2NyaXB0OiBib29sZWFuLCBjd2Q6IHN0cmluZyk6IENvbmZpZ1Jlc3VsdCA9PiB7XG4gIC8vIFVzZSBhIHRlbXBvcmFyeSBmaWxlIHBhdGggaW5zdGVhZCBvZiBjcmVhdGluZyBpbiB0aGUgcHJvamVjdCBkaXJlY3RvcnlcbiAgY29uc3QgY29uZmlnUGF0aCA9IHBhdGhSZXNvbHZlKGN3ZCwgJy5sZXgtdGVtcC1kZWZhdWx0LWVzbGludC5janMnKTtcbiAgY29uc3Qgb3JpZ2luYWxDb25maWcgPSBudWxsO1xuXG4gIC8vIENyZWF0ZSBhIHRlbXBvcmFyeSBDb21tb25KUyBtb2R1bGUgdGhhdCByZXF1aXJlcyBMZXgncyBFU0xpbnQgY29uZmlnXG4gIGNvbnN0IGNvbmZpZ0NvbnRlbnQgPSBgLy8gVGVtcG9yYXJ5IEVTTGludCBjb25maWcgZ2VuZXJhdGVkIGJ5IExleFxuY29uc3QgbGV4Q29uZmlnID0gcmVxdWlyZSgnQG5sYWJzL2xleC9lc2xpbnQuY29uZmlnLm1qcycpO1xuXG5tb2R1bGUuZXhwb3J0cyA9IGxleENvbmZpZztgO1xuXG4gIHdyaXRlRmlsZVN5bmMoY29uZmlnUGF0aCwgY29uZmlnQ29udGVudCwgJ3V0ZjgnKTtcblxuICByZXR1cm4ge1xuICAgIGNvbmZpZ1BhdGgsXG4gICAgb3JpZ2luYWxDb25maWdcbiAgfTtcbn07XG5cbmNvbnN0IGRldGVjdFR5cGVTY3JpcHQgPSAoY3dkOiBzdHJpbmcpOiBib29sZWFuID0+IGV4aXN0c1N5bmMocGF0aFJlc29sdmUoY3dkLCAndHNjb25maWcuanNvbicpKTtcblxuLyoqXG4gKiBFbnN1cmUgcGFja2FnZS5qc29uIGhhcyB0eXBlOiBtb2R1bGUgZm9yIEVTTSBzdXBwb3J0XG4gKi9cbmNvbnN0IGVuc3VyZU1vZHVsZVR5cGUgPSAoY3dkOiBzdHJpbmcpOiB2b2lkID0+IHtcbiAgY29uc3QgcGFja2FnZUpzb25QYXRoID0gcGF0aFJlc29sdmUoY3dkLCAncGFja2FnZS5qc29uJyk7XG5cbiAgaWYoZXhpc3RzU3luYyhwYWNrYWdlSnNvblBhdGgpKSB7XG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IHBhY2thZ2VKc29uQ29udGVudCA9IHJlYWRGaWxlU3luYyhwYWNrYWdlSnNvblBhdGgsICd1dGY4Jyk7XG4gICAgICBjb25zdCBwYWNrYWdlSnNvbiA9IEpTT04ucGFyc2UocGFja2FnZUpzb25Db250ZW50KTtcblxuICAgICAgLy8gSWYgdHlwZSBpcyBub3Qgc2V0IHRvIG1vZHVsZSwgd2FybiBpbnN0ZWFkIG9mIGF1dG8tbW9kaWZ5aW5nXG4gICAgICBpZihwYWNrYWdlSnNvbi50eXBlICE9PSAnbW9kdWxlJykge1xuICAgICAgICBsb2coJ1dhcm5pbmc6IHBhY2thZ2UuanNvbiBzaG91bGQgaGF2ZSBcInR5cGVcIjogXCJtb2R1bGVcIiBmb3IgRVNNIHN1cHBvcnQuIFBsZWFzZSBhZGQgdGhpcyBtYW51YWxseS4nLCAnd2FybicsIGZhbHNlKTtcbiAgICAgIH1cbiAgICB9IGNhdGNoKF9lcnJvcikge1xuICAgICAgLy8gSWdub3JlIGVycm9yc1xuICAgIH1cbiAgfVxufTtcblxuY29uc3QgaW5zdGFsbERlcGVuZGVuY2llcyA9IGFzeW5jIChjd2Q6IHN0cmluZywgdXNlVHlwZXNjcmlwdDogYm9vbGVhbiwgcXVpZXQ6IGJvb2xlYW4pOiBQcm9taXNlPHZvaWQ+ID0+IHtcbiAgaWYodXNlVHlwZXNjcmlwdCkge1xuICAgIGxvZygnVXNpbmcgVHlwZVNjcmlwdCBFU0xpbnQgZnJvbSBMZXguLi4nLCAnaW5mbycsIHF1aWV0KTtcbiAgfSBlbHNlIHtcbiAgICBsb2coJ1VzaW5nIEVTTGludCBmcm9tIExleC4uLicsICdpbmZvJywgcXVpZXQpO1xuICB9XG59O1xuXG5jb25zdCBydW5Fc2xpbnRXaXRoTGV4ID0gYXN5bmMgKFxuICBjd2Q6IHN0cmluZyxcbiAgcXVpZXQ6IGJvb2xlYW4sXG4gIGNsaU5hbWU6IHN0cmluZyxcbiAgZml4OiBib29sZWFuLFxuICBkZWJ1ZzogYm9vbGVhbixcbiAgdXNlVHlwZXNjcmlwdDogYm9vbGVhbixcbiAgY2FwdHVyZU91dHB1dD86IChvdXRwdXQ6IHN0cmluZyk9PiB2b2lkXG4pOiBQcm9taXNlPG51bWJlcj4gPT4ge1xuICBjb25zdCBzcGlubmVyID0gY3JlYXRlU3Bpbm5lcihxdWlldCk7XG5cbiAgdHJ5IHtcbiAgICBjb25zdCBwcm9qZWN0Q29uZmlnUGF0aCA9IHBhdGhSZXNvbHZlKGN3ZCwgJ2VzbGludC5jb25maWcubWpzJyk7XG4gICAgY29uc3QgcHJvamVjdENvbmZpZ1BhdGhUcyA9IHBhdGhSZXNvbHZlKGN3ZCwgJ2VzbGludC5jb25maWcudHMnKTtcbiAgICBjb25zdCBoYXNQcm9qZWN0Q29uZmlnID0gZXhpc3RzU3luYyhwcm9qZWN0Q29uZmlnUGF0aCkgfHwgZXhpc3RzU3luYyhwcm9qZWN0Q29uZmlnUGF0aFRzKTtcbiAgICBjb25zdCBoYXNMZXhDb25maWdFc2xpbnQgPSBMZXhDb25maWcuY29uZmlnLmVzbGludCAmJiBPYmplY3Qua2V5cyhMZXhDb25maWcuY29uZmlnLmVzbGludCkubGVuZ3RoID4gMDtcblxuICAgIGNvbnN0IHBvc3NpYmxlUGF0aHMgPSBbXG4gICAgICBwYXRoUmVzb2x2ZShjdXJyZW50RGlybmFtZSwgJy4uLy4uLy4uLy4uL2VzbGludC5jb25maWcubWpzJyksXG4gICAgICBwYXRoUmVzb2x2ZShjdXJyZW50RGlybmFtZSwgJy4uLy4uLy4uLy4uL2VzbGludC5jb25maWcudHMnKSxcbiAgICAgIHBhdGhSZXNvbHZlKHByb2Nlc3MuZW52LkxFWF9IT01FIHx8ICcvdXNyL2xvY2FsL2xpYi9ub2RlX21vZHVsZXMvQG5sYWJzL2xleCcsICdlc2xpbnQuY29uZmlnLm1qcycpLFxuICAgICAgcGF0aFJlc29sdmUocHJvY2Vzcy5lbnYuTEVYX0hPTUUgfHwgJy91c3IvbG9jYWwvbGliL25vZGVfbW9kdWxlcy9AbmxhYnMvbGV4JywgJ2VzbGludC5jb25maWcudHMnKVxuICAgIF07XG5cbiAgICBsZXQgbGV4Q29uZmlnUGF0aCA9ICcnO1xuXG4gICAgZm9yKGNvbnN0IHBhdGggb2YgcG9zc2libGVQYXRocykge1xuICAgICAgaWYoZXhpc3RzU3luYyhwYXRoKSkge1xuICAgICAgICBsZXhDb25maWdQYXRoID0gcGF0aDtcbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG4gICAgfVxuXG4gICAgbGV0IGNvbmZpZ1BhdGggPSAnJztcbiAgICBsZXQgdGVtcENvbmZpZ1BhdGggPSAnJztcblxuICAgIC8vIFByaW9yaXR5OlxuICAgIC8vIDEuIFByb2plY3QgZXNsaW50LmNvbmZpZyBmaWxlc1xuICAgIC8vIDIuIEVTTGludCBjb25maWcgaW4gbGV4LmNvbmZpZy4qIGZpbGVcbiAgICAvLyAzLiBMZXgncyBkZWZhdWx0IGVzbGludC5jb25maWcubWpzXG4gICAgLy8gNC4gQ3JlY