1import chalk from 'chalk';
2import { watchFile } from 'fs';
3import path from 'path';
4import resolveFrom from 'resolve-from';
5
6import * as Log from '../log';
7import { memoize } from './fn';
8
9/** Observes and reports file changes. */
10export class FileNotifier {
11  constructor(
12    /** Project root to resolve the module IDs relative to. */
13    private projectRoot: string,
14    /** List of module IDs sorted by priority. Only the first file that exists will be observed. */
15    private moduleIds: string[],
16    private settings: {
17      /** An additional warning message to add to the notice. */
18      additionalWarning?: string;
19    } = {}
20  ) {}
21
22  /** Get the file in the project. */
23  private resolveFilePath(): string | null {
24    for (const moduleId of this.moduleIds) {
25      const filePath = resolveFrom.silent(this.projectRoot, moduleId);
26      if (filePath) {
27        return filePath;
28      }
29    }
30    return null;
31  }
32
33  public startObserving() {
34    const configPath = this.resolveFilePath();
35    if (configPath) {
36      return this.watchFile(configPath);
37    }
38    return configPath;
39  }
40
41  /** Watch the file and warn to reload the CLI if it changes. */
42  public watchFile = memoize(this.startWatchingFile.bind(this));
43
44  private startWatchingFile(filePath: string): string {
45    const configName = path.relative(this.projectRoot, filePath);
46    watchFile(filePath, (cur: any, prev: any) => {
47      if (prev.size || cur.size) {
48        Log.log(
49          `\u203A Detected a change in ${chalk.bold(
50            configName
51          )}. Restart the server to see the new results.` + (this.settings.additionalWarning || '')
52        );
53      }
54    });
55    return filePath;
56  }
57}
58