18d307f52SEvan Baconimport { CommandError, UnimplementedError } from '../../utils/errors';
28d307f52SEvan Baconimport { memoize } from '../../utils/fn';
38d307f52SEvan Bacon
48d307f52SEvan Bacon/** An error that is memoized and asserted whenever a Prerequisite.assertAsync is subsequently called. */
58d307f52SEvan Baconexport class PrerequisiteCommandError extends CommandError {
68d307f52SEvan Bacon  constructor(code: string, message: string = '') {
78d307f52SEvan Bacon    super(message ? 'VALIDATE_' + code : code, message);
88d307f52SEvan Bacon  }
98d307f52SEvan Bacon}
108d307f52SEvan Bacon
11c4ef02aeSEvan Baconexport class Prerequisite<T = void, TProps = void> {
128d307f52SEvan Bacon  /** Memoized results of `assertImplementation` */
13c4ef02aeSEvan Bacon  private _assertAsync: (props: TProps) => Promise<T>;
148d307f52SEvan Bacon
158d307f52SEvan Bacon  constructor() {
168d307f52SEvan Bacon    this._assertAsync = memoize(this.assertImplementation.bind(this));
178d307f52SEvan Bacon  }
188d307f52SEvan Bacon
198d307f52SEvan Bacon  /** An optional warning to call before running the memoized assertion.  */
208d307f52SEvan Bacon  protected cachedError?: PrerequisiteCommandError;
218d307f52SEvan Bacon
228d307f52SEvan Bacon  /** Reset the assertion memo and warning message. */
23c4ef02aeSEvan Bacon  public resetAssertion(props: TProps) {
248d307f52SEvan Bacon    this.cachedError = undefined;
258d307f52SEvan Bacon    this._assertAsync = memoize(this.assertImplementation.bind(this));
268d307f52SEvan Bacon  }
278d307f52SEvan Bacon
28c4ef02aeSEvan Bacon  async assertAsync(props: TProps): Promise<T> {
298d307f52SEvan Bacon    if (this.cachedError) {
308d307f52SEvan Bacon      throw this.cachedError;
318d307f52SEvan Bacon    }
328d307f52SEvan Bacon    try {
33c4ef02aeSEvan Bacon      return await this._assertAsync(props);
348d307f52SEvan Bacon    } catch (error) {
358d307f52SEvan Bacon      if (error instanceof PrerequisiteCommandError) {
368d307f52SEvan Bacon        this.cachedError = error;
378d307f52SEvan Bacon      }
388d307f52SEvan Bacon      throw error;
398d307f52SEvan Bacon    }
408d307f52SEvan Bacon  }
418d307f52SEvan Bacon
428d307f52SEvan Bacon  /** Exposed for testing. */
43c4ef02aeSEvan Bacon  async assertImplementation(props: TProps): Promise<T> {
448d307f52SEvan Bacon    throw new UnimplementedError();
458d307f52SEvan Bacon  }
468d307f52SEvan Bacon}
478d307f52SEvan Bacon
488d307f52SEvan Bacon/** A prerequisite that is project specific. */
49*33643b60SEvan Baconexport class ProjectPrerequisite<T = void, TProps = void> extends Prerequisite<T, TProps> {
508d307f52SEvan Bacon  constructor(protected projectRoot: string) {
518d307f52SEvan Bacon    super();
528d307f52SEvan Bacon  }
538d307f52SEvan Bacon}
54