UNPKG

knip-guard

Version:

Baseline / no-new-issues guard for Knip

141 lines 5.92 kB
#!/usr/bin/env node import process from 'node:process'; import { Command } from 'commander'; import { diffBaseline, extractIssueKeys, loadBaseline, runKnipJson, loadKnipReportFromFile, saveBaseline } from './index.js'; /** * Gets the current set of issue keys from either a provided report or by running knip * @param options - CLI options with baseline, report, and command paths * @returns A Set of issue key strings */ async function getCurrentIssueKeys(options) { let report; if (options.report) { report = await loadKnipReportFromFile(options.report); } else { const command = options.command ?? 'npx knip'; report = await runKnipJson(command); } return extractIssueKeys(report); } /** * Initializes a new baseline from the current Knip report * @param options - CLI options with baseline path and report source * @returns Exit code (0 = success, 1 = failure) */ async function cmdInit(options) { const issueKeys = await getCurrentIssueKeys(options); const existingBaseline = await loadBaseline(options.baseline); if (existingBaseline) { console.warn(`Baseline already exists at ${options.baseline}. It will be overwritten with current issues.`); } await saveBaseline(options.baseline, issueKeys, existingBaseline, options.command); console.log(`Baseline initialized at ${options.baseline} with ${issueKeys.size} issues recorded.`); return 0; } /** * Checks if there are any new issues compared to the baseline * Fails with exit code 1 if new issues are detected * @param options - CLI options with baseline path and report source * @returns Exit code (0 = no new issues, 1 = new issues detected or baseline missing) */ async function cmdCheck(options) { const baseline = await loadBaseline(options.baseline); if (!baseline) { console.error(`No baseline found at ${options.baseline}. Run "knip-guard init" first in a clean state.`); return 1; } const issueKeys = await getCurrentIssueKeys(options); const { newIssues, resolvedIssues } = diffBaseline(baseline, issueKeys); if (resolvedIssues.length > 0) { console.log(`Resolved issues since baseline: ${resolvedIssues.length}`); } if (newIssues.length === 0) { console.log('No new Knip issues compared to baseline. ✅'); return 0; } console.error(`New Knip issues detected since baseline: ${newIssues.length}`); for (const key of newIssues) { console.error(` - ${key}`); } return 1; } /** * Accepts the current issues and updates the baseline * Useful after reviewing and intentionally accepting new issues * @param options - CLI options with baseline path and report source * @returns Exit code (0 = success, 1 = failure) */ async function cmdAccept(options) { const baseline = await loadBaseline(options.baseline); if (!baseline) { console.warn(`No existing baseline at ${options.baseline}. This will create a new one from current issues.`); } const issueKeys = await getCurrentIssueKeys(options); const newBaseline = await saveBaseline(options.baseline, issueKeys, baseline, options.command); console.log(`Baseline updated at ${options.baseline}. Recorded ${newBaseline.issues.length} issues.`); return 0; } /** * Core entry point for CLI and programmatic use * Sets up all available commands (init, check, accept) and parses arguments * @param argv - Command line arguments (typically process.argv.slice(2)) * @returns Exit code (0 = success, non-zero = failure) * @example * const code = await run(['check', '--baseline', '.knip-baseline.json']); * process.exitCode = code; */ export async function run(argv) { const program = new Command(); program .name('knip-guard') .description('Baseline / no-new-issues guard for Knip') .version('1.0.0'); program .command('init') .description('Initialize baseline from current Knip report') .option('-b, --baseline <path>', 'Path to baseline file', '.knip-baseline.json') .option('-r, --report <path>', 'Path to existing Knip JSON report (otherwise run knip)') .option('-c, --command <command>', 'Command to run knip, e.g. "npm run knip"') .action(async (options) => { const code = await cmdInit(options); process.exitCode = code; }); program .command('check') .description('Compare current report with baseline and fail on NEW issues') .option('-b, --baseline <path>', 'Path to baseline file', '.knip-baseline.json') .option('-r, --report <path>', 'Path to existing Knip JSON report (otherwise run knip)') .option('-c, --command <command>', 'Command to run knip, e.g. "npm run knip"') .action(async (options) => { const code = await cmdCheck(options); process.exitCode = code; }); program .command('accept') .description('Update baseline to current issues (after review)') .option('-b, --baseline <path>', 'Path to baseline file', '.knip-baseline.json') .option('-r, --report <path>', 'Path to existing Knip JSON report (otherwise run knip)') .option('-c, --command <command>', 'Command to run knip, e.g. "npm run knip"') .action(async (options) => { const code = await cmdAccept(options); process.exitCode = code; }); await program.parseAsync(argv, { from: 'user' }); return process.exitCode ?? 0; } /** * Auto-run when executed as a script (not when imported as a module) * Only works in ESM builds */ if (import.meta.url === `file://${process.argv[1]}`) { run(process.argv.slice(2)) .then((code) => { process.exitCode = code; }) .catch((err) => { console.error('Unexpected error in knip-guard:', err); process.exitCode = 1; }); } //# sourceMappingURL=cli.js.map