-
Notifications
You must be signed in to change notification settings - Fork 0
Reduce runtime dependencies from 6 to 4 #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,7 +4,7 @@ | |
| * CLI entry point for lostconf | ||
| */ | ||
|
|
||
| import { Command } from 'commander'; | ||
| import { parseArgs } from 'node:util'; | ||
| import fs from 'fs/promises'; | ||
| import { createEngine } from './core/engine.js'; | ||
| import { getBuiltinParsers } from './parsers/index.js'; | ||
|
|
@@ -14,39 +14,70 @@ import { createSarifFormatter } from './output/sarif.js'; | |
| import type { Formatter } from './output/formatter.js'; | ||
| import { Severity } from './core/types.js'; | ||
|
|
||
| const program = new Command(); | ||
|
|
||
| program | ||
| .name('lostconf') | ||
| .description('A meta-linter that detects stale references in configuration files') | ||
| .version('0.4.0') | ||
| .argument('[paths...]', 'Paths to scan (default: current directory)') | ||
| .option('-f, --format <fmt>', 'Output format: text, json, sarif', 'text') | ||
| .option('-o, --output <file>', 'Write to file instead of stdout') | ||
| .option('--include <glob...>', 'Only check matching config files') | ||
| .option('--exclude <glob...>', 'Skip matching config files') | ||
| .option('--skip-ignore-files', 'Skip .gitignore, .prettierignore, etc. (reduces noise)') | ||
| .option('--exclude-parsers <names...>', 'Skip specific parsers (e.g., gitignore prettierignore)') | ||
| .option( | ||
| '--min-severity <level>', | ||
| 'Minimum severity to show: low, medium, high (default: medium)', | ||
| 'medium' | ||
| ) | ||
| .option('--show-all', 'Show all findings including low severity (same as --min-severity=low)') | ||
| .option('--fail-on-stale', 'Exit code 1 if stale patterns found') | ||
| .option('-q, --quiet', 'Suppress non-error output') | ||
| .option('-v, --verbose', 'Show debug info') | ||
| .option('--no-progress', 'Disable progress indicator') | ||
| .action(async (paths: string[], options) => { | ||
| try { | ||
| await run(paths, options); | ||
| } catch (err) { | ||
| if (!options.quiet) { | ||
| console.error(`Error: ${err instanceof Error ? err.message : String(err)}`); | ||
| } | ||
| process.exit(2); | ||
| } | ||
| }); | ||
| const VERSION = '0.4.1'; | ||
|
|
||
| const HELP = `Usage: lostconf [options] [paths...] | ||
|
|
||
| A meta-linter that detects stale references in configuration files | ||
|
|
||
| Arguments: | ||
| paths Paths to scan (default: current directory) | ||
|
|
||
| Options: | ||
| -V, --version output the version number | ||
| -f, --format <fmt> Output format: text, json, sarif (default: "text") | ||
| -o, --output <file> Write to file instead of stdout | ||
| --include <glob> Only check matching config files (repeatable) | ||
| --exclude <glob> Skip matching config files (repeatable) | ||
| --skip-ignore-files Skip .gitignore, .prettierignore, etc. (reduces noise) | ||
| --exclude-parsers <name> Skip specific parsers (repeatable, e.g., gitignore prettierignore) | ||
| --min-severity <level> Minimum severity to show: low, medium, high (default: "medium") | ||
| --show-all Show all findings including low severity (same as --min-severity=low) | ||
| --fail-on-stale Exit code 1 if stale patterns found | ||
| -q, --quiet Suppress non-error output | ||
| -v, --verbose Show debug info | ||
| --no-progress Disable progress indicator | ||
| -h, --help display help for command | ||
| `; | ||
|
|
||
| const options = { | ||
| version: { type: 'boolean' as const, short: 'V' }, | ||
| help: { type: 'boolean' as const, short: 'h' }, | ||
| format: { type: 'string' as const, short: 'f', default: 'text' }, | ||
| output: { type: 'string' as const, short: 'o' }, | ||
| include: { type: 'string' as const, multiple: true }, | ||
| exclude: { type: 'string' as const, multiple: true }, | ||
| 'skip-ignore-files': { type: 'boolean' as const, default: false }, | ||
| 'exclude-parsers': { type: 'string' as const, multiple: true }, | ||
| 'min-severity': { type: 'string' as const, default: 'medium' }, | ||
|
Comment on lines
+48
to
+52
|
||
| 'show-all': { type: 'boolean' as const, default: false }, | ||
| 'fail-on-stale': { type: 'boolean' as const, default: false }, | ||
| quiet: { type: 'boolean' as const, short: 'q', default: false }, | ||
| verbose: { type: 'boolean' as const, short: 'v', default: false }, | ||
| progress: { type: 'boolean' as const, default: true } | ||
| }; | ||
|
Comment on lines
+43
to
+58
|
||
|
|
||
| let values: Record<string, unknown>; | ||
| let positionals: string[]; | ||
| try { | ||
| const parsed = parseArgs({ options, allowPositionals: true, strict: true }); | ||
| values = parsed.values as Record<string, unknown>; | ||
| positionals = parsed.positionals; | ||
| } catch (err) { | ||
| console.error(`error: ${err instanceof Error ? err.message : String(err)}`); | ||
| console.error(`Try 'lostconf --help' for more information.`); | ||
| process.exit(2); | ||
| } | ||
|
|
||
| if (values.help) { | ||
| process.stdout.write(HELP); | ||
| process.exit(0); | ||
| } | ||
|
|
||
| if (values.version) { | ||
| console.log(VERSION); | ||
| process.exit(0); | ||
| } | ||
|
|
||
| interface CliOptions { | ||
| format: string; | ||
|
|
@@ -63,6 +94,30 @@ interface CliOptions { | |
| progress?: boolean; | ||
| } | ||
|
|
||
| const cliOptions: CliOptions = { | ||
| format: (values.format as string | undefined) ?? 'text', | ||
| output: values.output as string | undefined, | ||
| include: values.include as string[] | undefined, | ||
| exclude: values.exclude as string[] | undefined, | ||
| skipIgnoreFiles: values['skip-ignore-files'] as boolean | undefined, | ||
| excludeParsers: values['exclude-parsers'] as string[] | undefined, | ||
| minSeverity: values['min-severity'] as string | undefined, | ||
| showAll: values['show-all'] as boolean | undefined, | ||
| failOnStale: values['fail-on-stale'] as boolean | undefined, | ||
| quiet: values.quiet as boolean | undefined, | ||
| verbose: values.verbose as boolean | undefined, | ||
| progress: values.progress as boolean | undefined | ||
| }; | ||
|
|
||
| try { | ||
| await run(positionals, cliOptions); | ||
| } catch (err) { | ||
| if (!cliOptions.quiet) { | ||
| console.error(`Error: ${err instanceof Error ? err.message : String(err)}`); | ||
| } | ||
| process.exit(2); | ||
| } | ||
|
|
||
| async function run(paths: string[], options: CliOptions): Promise<void> { | ||
| const { | ||
| format, | ||
|
|
@@ -194,5 +249,3 @@ function getFormatter(format: string): Formatter { | |
| return createTextFormatter(); | ||
| } | ||
| } | ||
|
|
||
| program.parse(); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,8 +2,16 @@ | |
| * Text output formatter | ||
| */ | ||
|
|
||
| import chalk from 'chalk'; | ||
| import type { ValidationResult, Finding, StaleReason } from '../core/types.js'; | ||
|
|
||
| /** Inline ANSI color helpers (replaces chalk) */ | ||
| const ansi = (code: number, close: number) => (s: string) => `\x1b[${code}m${s}\x1b[${close}m`; | ||
| const gray = ansi(90, 39); | ||
| const yellow = ansi(33, 39); | ||
| const red = ansi(31, 39); | ||
| const cyan = ansi(36, 39); | ||
| const dim = ansi(2, 22); | ||
| const green = ansi(32, 39); | ||
|
Comment on lines
+7
to
+14
|
||
| import { Severity } from '../core/types.js'; | ||
| import type { Formatter } from './formatter.js'; | ||
|
|
||
|
|
@@ -23,20 +31,20 @@ function formatReason(reason: StaleReason): string { | |
| function getSeverityIcon(severity: Severity): string { | ||
| switch (severity) { | ||
| case Severity.LOW: | ||
| return chalk.gray('○'); | ||
| return gray('○'); | ||
| case Severity.MEDIUM: | ||
| return chalk.yellow('●'); | ||
| return yellow('●'); | ||
| case Severity.HIGH: | ||
| return chalk.red('●'); | ||
| return red('●'); | ||
| } | ||
| } | ||
|
|
||
| /** Format a single finding */ | ||
| function formatFinding(finding: Finding): string { | ||
| const severityIcon = getSeverityIcon(finding.severity); | ||
| const location = chalk.cyan(`${finding.file}:${finding.line}`); | ||
| const pattern = chalk.yellow(finding.pattern); | ||
| const reason = chalk.dim(formatReason(finding.reason)); | ||
| const location = cyan(`${finding.file}:${finding.line}`); | ||
| const pattern = yellow(finding.pattern); | ||
| const reason = dim(formatReason(finding.reason)); | ||
|
|
||
| // Calculate padding for alignment | ||
| const locationStr = `${finding.file}:${finding.line}`; | ||
|
|
@@ -51,7 +59,7 @@ export const textFormatter: Formatter = { | |
| const lines: string[] = []; | ||
|
|
||
| if (result.findings.length === 0) { | ||
| lines.push(chalk.green('No stale patterns found')); | ||
| lines.push(green('No stale patterns found')); | ||
| return lines.join('\n'); | ||
| } | ||
|
|
||
|
|
@@ -84,13 +92,13 @@ export const textFormatter: Formatter = { | |
|
|
||
| const parts = []; | ||
| if (severityCounts[Severity.HIGH] > 0) { | ||
| parts.push(chalk.red(`${severityCounts[Severity.HIGH]} high`)); | ||
| parts.push(red(`${severityCounts[Severity.HIGH]} high`)); | ||
| } | ||
| if (severityCounts[Severity.MEDIUM] > 0) { | ||
| parts.push(chalk.yellow(`${severityCounts[Severity.MEDIUM]} medium`)); | ||
| parts.push(yellow(`${severityCounts[Severity.MEDIUM]} medium`)); | ||
| } | ||
| if (severityCounts[Severity.LOW] > 0) { | ||
| parts.push(chalk.gray(`${severityCounts[Severity.LOW]} low`)); | ||
| parts.push(gray(`${severityCounts[Severity.LOW]} low`)); | ||
| } | ||
|
|
||
| lines.push( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This switch to
node:utilparseArgseffectively requires Node versions whereparseArgsis stable/available (noted in the PR description as 18.3+). However,package.jsonstill declaresnode >=18.0.0, which can lead to runtime failures or unsupported behavior on 18.0–18.2. Consider bumping the engine requirement to >=18.3.0 (or add a small fallback/guard) so the published package matches the CLI’s runtime needs.