1import { CommandError, UnimplementedError } from '../../utils/errors'; 2import { memoize } from '../../utils/fn'; 3 4/** An error that is memoized and asserted whenever a Prerequisite.assertAsync is subsequently called. */ 5export class PrerequisiteCommandError extends CommandError { 6 constructor(code: string, message: string = '') { 7 super(message ? 'VALIDATE_' + code : code, message); 8 } 9} 10 11export class Prerequisite<T = void, TProps = void> { 12 /** Memoized results of `assertImplementation` */ 13 private _assertAsync: (props: TProps) => Promise<T>; 14 15 constructor() { 16 this._assertAsync = memoize(this.assertImplementation.bind(this)); 17 } 18 19 /** An optional warning to call before running the memoized assertion. */ 20 protected cachedError?: PrerequisiteCommandError; 21 22 /** Reset the assertion memo and warning message. */ 23 public resetAssertion(props: TProps) { 24 this.cachedError = undefined; 25 this._assertAsync = memoize(this.assertImplementation.bind(this)); 26 } 27 28 async assertAsync(props: TProps): Promise<T> { 29 if (this.cachedError) { 30 throw this.cachedError; 31 } 32 try { 33 return await this._assertAsync(props); 34 } catch (error) { 35 if (error instanceof PrerequisiteCommandError) { 36 this.cachedError = error; 37 } 38 throw error; 39 } 40 } 41 42 /** Exposed for testing. */ 43 async assertImplementation(props: TProps): Promise<T> { 44 throw new UnimplementedError(); 45 } 46} 47 48/** A prerequisite that is project specific. */ 49export class ProjectPrerequisite<T = void, TProps = void> extends Prerequisite<T, TProps> { 50 constructor(protected projectRoot: string) { 51 super(); 52 } 53} 54