@nlabs/lex
Version:
955 lines (924 loc) • 126 kB
JavaScript
/**
* 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