UNPKG

diffjam

Version:
308 lines (264 loc) 8.04 kB
/* Performs top-level actions of the CLI. Connects the file paths found in the `CurrentWorkingDirectory` to the worker pool. */ import chalk from "chalk"; import { equal } from "node:assert"; import { Config } from "./Config"; import { CurrentWorkingDirectory } from "./CurrentWorkingDirectory"; import { Flags } from "./cli"; import { Policy } from "./Policy"; import { GREEN_CHECK, logAllResultDetails, logCheckFailedError, logResults, } from "./log"; import { ResultsMap } from "./match"; import { actionPolicyModify } from "./policyModify"; type ResultMap = { [key: string]: { measurement: number } }; interface WorkerPool { resultsMap: ResultsMap; filesChecked: string[]; processFile(filePath: string): void; onFilesDone(): void; onResults(): void; } export class Runner { private policies: Policy[]; private ran = false; constructor( public config: Config, private flags: Flags, private cwd: CurrentWorkingDirectory, private workerPool: WorkerPool ) { this.policies = Object.values(config.policyMap); for (const policyName in config.policyMap) { this.workerPool.resultsMap[policyName] = { policy: config.policyMap[policyName], matches: [], }; } } private run(): Promise<{ resultsMap: ResultsMap; filesChecked: string[] }> { equal(this.ran, false, "Runner.run() should only be called once"); equal( this.policies.length > 0, true, "No policies specified.\nPlease check your config file at " + this.config.filePath ); return new Promise((resolve) => { this.workerPool.onResults = () => { this.ran = true; resolve({ resultsMap: this.workerPool.resultsMap, filesChecked: this.workerPool.filesChecked, }); }; this.cwd.allNonGitIgnoredFiles( this.processFile.bind(this), this.workerPool.onFilesDone.bind(this.workerPool) ); }); } async check() { const { resultsMap, filesChecked } = await this.run(); const { breaches } = logResults(resultsMap, filesChecked); if (breaches.length) { logCheckFailedError(); process.exitCode = 1; } else { console.log( `\n${GREEN_CHECK} ${chalk.bold( `All policies passed with ${filesChecked.length} matching files checked` )}` ); } } async count() { const start = new Date(); const { resultsMap, filesChecked } = await this.run(); const { breaches, all } = logResults(resultsMap, filesChecked); const results: ResultMap = {}; for (const result of all) { results[result.policy.name] = { measurement: result.matches.length, }; } const verbose = Boolean(this.flags.verbose); if (breaches.length) { logCheckFailedError(); } console.log( chalk.green.bold(`Done in ${Date.now() - start.getTime()} ms.`) ); } async cinch() { const { resultsMap, filesChecked } = await this.run(); const { breaches, successes } = logResults(resultsMap, filesChecked); console.log(); if (breaches.length > 0) { console.error( chalk.bold( "Cannot cinch a policy that doesn't even meet the baseline. \n" ) ); process.exitCode = 1; return; } let anyCinched = false; for (const success of successes) { if (success.policy.isCountCinchable(success.matches)) { anyCinched = true; const before = success.policy.baseline; success.policy.baseline = success.matches.length; console.log( `${GREEN_CHECK} cinching ${chalk.bold( success.policy.name )} from ${before} to ${chalk.bold( success.matches.length.toString() )}!` ); } } if (anyCinched) { this.config.write(); } else { console.log( `${GREEN_CHECK} ${chalk.bold( "All policies are already exactly at their baseline, so none were cinched" )}` ); } } async bump() { const { resultsMap, filesChecked } = await this.run(); const { breaches } = logResults(resultsMap, filesChecked); console.log(); for (const breach of breaches) { const before = breach.policy.baseline; breach.policy.baseline = breach.matches.length; console.log( `🎚 bumping ${chalk.bold( breach.policy.name )} from ${before} to ${chalk.bold(breach.matches.length.toString())}!` ); } if (breaches.length) { this.config.write(); } else { console.log( `${GREEN_CHECK} ${chalk.bold( "All policies are already exactly at their baseline, so none were bumped" )}` ); } } async addPolicy() { // Requiring ui inline as it adds a fair bit of time to startup const ui = require("./ui"); const name = await ui.textInput("Enter a name for this policy: "); const isRegex = await ui.confirm("Is this a regex search?"); let search: string; const negativeSearchTerms = []; if (isRegex) { const regex = await ui.textInput("Enter the regex to search for: "); search = `regex:${regex}`; } else { search = await ui.textInput("Enter the string to match : "); while (true) { const negativeSearchTerm = await ui.textInput( "Enter any string to negate (or leave blank to continue): " ); if (negativeSearchTerm.trim()) { negativeSearchTerms.push(negativeSearchTerm); } else { break; } } } const filePattern = await ui.textInput( "Enter the filePattern to search for this policy: " ); const ignoreFilePatterns = []; while (true) { const ignoreFilePattern = await ui.textInput( "Enter any filePatterns to ignore (or leave blank to continue): " ); if (ignoreFilePattern.trim()) { ignoreFilePatterns.push(ignoreFilePattern); } else { break; } } const description = await ui.textInput( "Give a description for this policy: " ); const policy = new Policy( name, description, filePattern, [search, ...negativeSearchTerms], 0, ignoreFilePatterns ); this.config.setPolicy(policy); this.policies = [policy]; this.workerPool.resultsMap[policy.name] = { policy, matches: [], }; const { resultsMap, filesChecked } = await this.run(); const { matches } = resultsMap[policy.name]; policy.baseline = matches.length; if ( await ui.confirm( `There are currently ${policy.baseline} matches for that configuration after checking ${filesChecked.length} files. Save it?` ) ) { this.config.write(); console.log("Saved!"); } else { console.log("Cancelled save."); } } async runSinglePolicy(policyName: string) { const policy = this.config.getPolicy(policyName); if (!policy) { throw new Error(`Could not find policy ${policyName}`); } const { resultsMap } = await this.run(); return resultsMap[policyName]; } async removePolicy() { const ui = require("./ui"); const policy = await ui.select( "Select a policy to remove: ", this.config.policyMap ); await this.config.deletePolicy(policy.name); await this.config.write(); console.log(`Removed ${policy.name}`); } modifyPolicy() { return actionPolicyModify(this); } async checkBreachesForPolicy() { const ui = require("./ui"); const policy = await ui.select( "Select a policy to check for breaches: ", this.config.policyMap ); const result = await this.runSinglePolicy(policy.name); logAllResultDetails(result); } private processFile(filePath: string) { const isUnderPolicy = this.policies.some((policy) => policy.isFileUnderPolicy(filePath) ); if (!isUnderPolicy) return; this.workerPool.processFile(filePath); } }