lwc-linter
Version:
A comprehensive CLI tool for linting Lightning Web Components v8.0.0+ with modern LWC patterns, decorators, lifecycle hooks, and Salesforce platform integration
433 lines (428 loc) ⢠19.3 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const commander_1 = require("commander");
const chalk_1 = __importDefault(require("chalk"));
const ora_1 = __importDefault(require("ora"));
const open_1 = __importDefault(require("open"));
const fs = __importStar(require("fs-extra"));
const path = __importStar(require("path"));
const linter_1 = require("./core/linter");
const config_loader_1 = require("./core/config-loader");
const output_formatter_1 = require("./core/output-formatter");
const rule_manager_1 = require("./core/rule-manager");
const dev_server_1 = require("./core/dev-server");
const program = new commander_1.Command();
// Helper function to find and validate LWC path
function resolveLWCPath(inputPath) {
// If user provided a specific path, use it
if (inputPath && inputPath !== '.') {
if (fs.existsSync(inputPath)) {
return path.resolve(inputPath);
}
else {
console.error(chalk_1.default.red(`ā Path not found: ${inputPath}`));
process.exit(1);
}
}
// Standard Salesforce DX LWC paths to check
const standardPaths = [
'force-app/main/default/lwc',
'force-app/main/default/lwc/',
'./force-app/main/default/lwc',
'./force-app/main/default/lwc/',
'src/lwc',
'src/lwc/',
'./src/lwc',
'./src/lwc/'
];
// Check for standard LWC paths
for (const lwcPath of standardPaths) {
const resolvedPath = path.resolve(lwcPath);
if (fs.existsSync(resolvedPath)) {
console.log(chalk_1.default.blue(`š Auto-detected LWC path: ${lwcPath}`));
return resolvedPath;
}
}
// If no standard paths found, use current directory
const currentDir = path.resolve('.');
console.log(chalk_1.default.yellow(`ā ļø No standard LWC folder found. Using current directory: ${currentDir}`));
console.log(chalk_1.default.gray(`š” Expected paths: ${standardPaths.slice(0, 2).join(', ')}`));
return currentDir;
}
// Helper function to show LWC project info
function showLWCProjectInfo(targetPath) {
const lwcComponents = [];
try {
if (fs.existsSync(targetPath)) {
const items = fs.readdirSync(targetPath, { withFileTypes: true });
for (const item of items) {
if (item.isDirectory()) {
const componentPath = path.join(targetPath, item.name);
const hasJS = fs.existsSync(path.join(componentPath, `${item.name}.js`));
const hasHTML = fs.existsSync(path.join(componentPath, `${item.name}.html`));
const hasCSS = fs.existsSync(path.join(componentPath, `${item.name}.css`));
if (hasJS || hasHTML || hasCSS) {
lwcComponents.push({
name: item.name,
files: { js: hasJS, html: hasHTML, css: hasCSS }
});
}
}
}
}
}
catch (error) {
// Ignore errors during component detection
}
if (lwcComponents.length > 0) {
console.log(chalk_1.default.green(`\nšÆ Found ${lwcComponents.length} LWC component(s):`));
lwcComponents.slice(0, 5).forEach(comp => {
const files = Object.entries(comp.files)
.filter(([_, exists]) => exists)
.map(([ext, _]) => ext.toUpperCase())
.join(', ');
console.log(chalk_1.default.gray(` ⢠${comp.name} (${files})`));
});
if (lwcComponents.length > 5) {
console.log(chalk_1.default.gray(` ... and ${lwcComponents.length - 5} more`));
}
}
}
program
.name('lwc-linter')
.description('š A comprehensive CLI tool for linting Lightning Web Components with code quality, accessibility, performance, security, ESLint, and Prettier integration')
.version('1.0.0');
// Main command (backwards compatibility)
program
.argument('[path]', 'Path to LWC directory (defaults to force-app/main/default/lwc/)', '.')
.option('-c, --config <file>', 'Configuration file path')
.option('-r, --report <format>', 'Output format (cli, json, html)', 'cli')
.option('--fix', 'Enable auto-fixing of issues')
.option('--rules [rules...]', 'Specific rules to run')
.option('--disable [rules...]', 'Rules to disable')
.option('--severity <level>', 'Minimum severity level (error, warn, info)', 'info')
.option('--open', 'Open HTML report in browser (when using --report html)')
.action(async (inputPath, options) => {
const targetPath = resolveLWCPath(inputPath === '.' ? undefined : inputPath);
console.log(chalk_1.default.blue.bold('\nš LWC Linter - Salesforce Lightning Web Components Analysis\n'));
if (options.rules) {
(0, rule_manager_1.listRules)();
return;
}
showLWCProjectInfo(targetPath);
const spinner = (0, ora_1.default)('Loading configuration and initializing linter...').start();
try {
const config = await (0, config_loader_1.loadConfig)(options.config);
// Apply CLI options
if (options.fix)
config.fix = true;
if (options.severity)
config.severity = options.severity;
if (options.rules && Array.isArray(options.rules)) {
// If specific rules are requested, disable all others and enable only the requested ones
const requestedRules = options.rules;
Object.keys(config.rules).forEach(rule => {
config.rules[rule] = requestedRules.includes(rule) ? 'warn' : 'off';
});
}
if (options.disable) {
options.disable.forEach((rule) => {
if (config.rules)
config.rules[rule] = 'off';
});
}
spinner.text = `Analyzing LWC components in ${path.relative(process.cwd(), targetPath)}...`;
const linter = new linter_1.LWCLinter(config);
const results = await linter.lintDirectory(targetPath);
spinner.succeed('Analysis completed!');
const formatter = new output_formatter_1.OutputFormatter(options.report);
const output = await formatter.format(results);
if (options.report === 'html') {
const reportFile = 'lwc-lint-report.html';
await fs.writeFile(reportFile, output);
console.log(chalk_1.default.green(`\nš HTML report saved: ${reportFile}`));
if (options.open) {
await (0, open_1.default)(reportFile);
console.log(chalk_1.default.blue('š Report opened in browser'));
}
console.log(chalk_1.default.cyan(`\nš” For live dashboard with hot refresh, use: ${chalk_1.default.bold('npx lwc-linter serve')}`));
}
else {
console.log(output);
}
// Show auto-fix suggestion
if (!options.fix && results.some(r => r.issues.some(i => i.fixable))) {
console.log(chalk_1.default.yellow(`\nš§ Auto-fix available! Run: ${chalk_1.default.bold(`npx lwc-linter "${targetPath}" --fix`)}`));
}
// Exit with appropriate code
const hasErrors = results.some(r => r.issues.some(i => i.severity === 'error'));
process.exit(hasErrors ? 1 : 0);
}
catch (error) {
spinner.fail('Failed to analyze LWC components');
console.error(chalk_1.default.red(`Error: ${error instanceof Error ? error.message : error}`));
process.exit(1);
}
});
// Quick scan command (recommended)
program
.command('scan [path]')
.description('š Quick scan: lint LWC project, generate report, and open in browser')
.option('-c, --config <file>', 'Configuration file path')
.option('--no-open', 'Don\'t open browser automatically')
.option('--fix', 'Show auto-fix suggestions')
.action(async (inputPath = '.', options) => {
const targetPath = resolveLWCPath(inputPath === '.' ? undefined : inputPath);
console.log(chalk_1.default.blue.bold('\nš LWC Quick Scan - Salesforce Lightning Web Components\n'));
showLWCProjectInfo(targetPath);
const spinner = (0, ora_1.default)('Scanning LWC components...').start();
try {
const config = await (0, config_loader_1.loadConfig)(options.config);
const linter = new linter_1.LWCLinter(config);
const results = await linter.lintDirectory(targetPath);
spinner.succeed('Scan completed!');
// Generate HTML report
const formatter = new output_formatter_1.OutputFormatter('html');
const htmlOutput = await formatter.format(results);
const reportFile = 'lwc-lint-report.html';
await fs.writeFile(reportFile, htmlOutput);
// Show summary
const totalIssues = results.reduce((sum, r) => sum + r.issues.length, 0);
const totalErrors = results.reduce((sum, r) => sum + r.issues.filter(i => i.severity === 'error').length, 0);
const totalWarnings = results.reduce((sum, r) => sum + r.issues.filter(i => i.severity === 'warn').length, 0);
const fixableIssues = results.reduce((sum, r) => sum + r.issues.filter(i => i.fixable).length, 0);
console.log(chalk_1.default.green(`\nš Scan Summary:`));
console.log(chalk_1.default.gray(` Files processed: ${results.length}`));
console.log(chalk_1.default.red(` Errors: ${totalErrors}`));
console.log(chalk_1.default.yellow(` Warnings: ${totalWarnings}`));
console.log(chalk_1.default.blue(` Info: ${totalIssues - totalErrors - totalWarnings}`));
if (fixableIssues > 0) {
console.log(chalk_1.default.green(` Fixable: ${fixableIssues}`));
}
console.log(chalk_1.default.green(`\nš Report saved: ${reportFile}`));
// Open in browser
if (options.open) {
await (0, open_1.default)(reportFile);
console.log(chalk_1.default.blue('š Report opened in browser'));
}
// Show next steps
console.log(chalk_1.default.cyan(`\nš” Next steps:`));
console.log(chalk_1.default.gray(` ⢠Live dashboard: ${chalk_1.default.bold('npx lwc-linter serve')}`));
if (fixableIssues > 0) {
console.log(chalk_1.default.gray(` ⢠Auto-fix issues: ${chalk_1.default.bold(`npx lwc-linter "${targetPath}" --fix`)}`));
}
}
catch (error) {
spinner.fail('Scan failed');
console.error(chalk_1.default.red(`Error: ${error instanceof Error ? error.message : error}`));
process.exit(1);
}
});
// Live server command
program
.command('serve [path]')
.description('š“ Start live development server with hot refresh for LWC components')
.option('-c, --config <file>', 'Configuration file path')
.option('-p, --port <number>', 'Server port (default: 3000)', '3000')
.option('--no-open', 'Don\'t open browser automatically')
.action(async (inputPath = '.', options) => {
const targetPath = resolveLWCPath(inputPath === '.' ? undefined : inputPath);
const port = parseInt(options.port);
console.log(chalk_1.default.red.bold('\nš“ LWC Linter Live Server - Hot Refresh Dashboard\n'));
showLWCProjectInfo(targetPath);
try {
const config = await (0, config_loader_1.loadConfig)(options.config);
const devServer = new dev_server_1.DevServer(config, targetPath, port);
console.log(chalk_1.default.blue('š“ Starting LWC Linter Live Server...'));
await devServer.start();
// Open in browser
if (options.open) {
console.log(chalk_1.default.blue('š Opening live dashboard in browser...'));
await (0, open_1.default)(`http://localhost:${port}`);
console.log(chalk_1.default.green('ā
Dashboard opened successfully!'));
}
// Show features
console.log(chalk_1.default.magenta('\nšÆ Live Dashboard Features:'));
console.log(chalk_1.default.gray(' ⢠Hot refresh: Instant updates when LWC files change'));
console.log(chalk_1.default.gray(' ⢠Interactive filtering and controls'));
console.log(chalk_1.default.gray(' ⢠All click events fully functional'));
console.log(chalk_1.default.gray(' ⢠Real-time file change detection'));
console.log(chalk_1.default.gray(' ⢠Export to Excel anytime'));
console.log(chalk_1.default.green('\n⨠Your dashboard is now LIVE! Edit LWC files and watch them update instantly!'));
// Graceful shutdown
const cleanup = () => {
devServer.stop();
process.exit(0);
};
process.on('SIGINT', cleanup);
process.on('SIGTERM', cleanup);
}
catch (error) {
console.error(chalk_1.default.red(`Failed to start server: ${error instanceof Error ? error.message : error}`));
process.exit(1);
}
});
// List rules command
program
.command('rules')
.description('š List all available linting rules')
.option('-c, --config <file>', 'Configuration file path')
.action(async (options) => {
try {
(0, rule_manager_1.listRules)();
}
catch (error) {
console.error(chalk_1.default.red(`Error: ${error instanceof Error ? error.message : error}`));
process.exit(1);
}
});
// Init command to create config
program
.command('init')
.description('š ļø Initialize LWC linter configuration')
.action(async () => {
const configPath = '.lwclintrc.json';
if (fs.existsSync(configPath)) {
console.log(chalk_1.default.yellow(`ā ļø Configuration file already exists: ${configPath}`));
return;
}
const defaultConfig = {
"rules": {
"no-unused-vars": "warn",
"no-console": "error",
"no-debugger": "error",
"prefer-const": "warn",
"no-var": "error",
"eqeqeq": "error",
"curly": "warn",
"no-trailing-spaces": "warn",
"indent": "warn",
"quotes": "warn",
"semi": "warn",
"aria-required": "error",
"alt-text-required": "error",
"focus-visible": "warn",
"keyboard-navigation": "warn",
"color-contrast": "info",
"semantic-html": "warn",
"avoid-dom-manipulation": "warn",
"no-innerhtml": "error",
"avoid-document-query": "warn",
"limit-dom-nodes": "info",
"optimize-list-rendering": "warn",
"lazy-loading": "info",
"no-inline-styles": "warn",
"no-eval": "error",
"validate-inputs": "error",
"sanitize-html": "error",
"secure-communication": "warn",
"eslint-integration": "warn",
"prettier-formatting": "warn",
"import-organization": "info"
},
"severity": "info",
"fix": false,
"exclude": [
"node_modules/**",
"**/*.test.js",
"**/*.spec.js",
"**/test/**",
"**/__tests__/**",
"**/coverage/**",
"lib/**",
"dist/**"
],
"include": [
"**/*.js",
"**/*.html",
"**/*.css"
],
"accessibility": {
"enabled": true,
"level": "AA"
},
"performance": {
"enabled": true,
"maxBundleSize": 500000,
"maxDomNodes": 1000
},
"security": {
"enabled": true,
"allowInnerHTML": false
},
"eslintIntegration": {
"enabled": true,
"configPath": ".eslintrc.js",
"ignorePatterns": ["node_modules/**", "lib/**", "*.min.js"]
},
"prettierIntegration": {
"enabled": true,
"configPath": ".prettierrc.js",
"printWidth": 100,
"tabWidth": 2,
"useTabs": false
}
};
await fs.writeFile(configPath, JSON.stringify(defaultConfig, null, 2));
console.log(chalk_1.default.green(`ā
Configuration file created: ${configPath}`));
console.log(chalk_1.default.blue('\nš” Next steps:'));
console.log(chalk_1.default.gray(` ⢠Edit ${configPath} to customize rules`));
console.log(chalk_1.default.gray(` ⢠Run ${chalk_1.default.bold('npx lwc-linter scan')} to start linting`));
console.log(chalk_1.default.gray(` ⢠Use ${chalk_1.default.bold('npx lwc-linter serve')} for live dashboard`));
});
// Help examples
program.addHelpText('after', `
Examples:
${chalk_1.default.bold('Standard Salesforce DX project:')}
$ npx lwc-linter scan # Auto-detects force-app/main/default/lwc/
$ npx lwc-linter serve # Live dashboard with hot refresh
${chalk_1.default.bold('Custom paths:')}
$ npx lwc-linter scan src/lwc # Scan specific LWC directory
$ npx lwc-linter "my-components/" --report html # Generate HTML report
${chalk_1.default.bold('Configuration:')}
$ npx lwc-linter init # Create .lwclintrc.json
$ npx lwc-linter rules # List all available rules
${chalk_1.default.bold('Auto-fixing:')}
$ npx lwc-linter scan --fix # Show fixable issues
$ npx lwc-linter force-app/main/default/lwc --fix # Auto-fix issues
${chalk_1.default.blue('š” Pro tip:')} Use ${chalk_1.default.bold('npx lwc-linter serve')} for the best experience with live hot refresh!
`);
program.parse();
//# sourceMappingURL=cli.js.map