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 9const debug = require('debug')('expo:utils:fileNotifier') as typeof console.log; 10 11/** Observes and reports file changes. */ 12export class FileNotifier { 13 static instances: FileNotifier[] = []; 14 15 static stopAll() { 16 for (const instance of FileNotifier.instances) { 17 instance.stopObserving(); 18 } 19 } 20 21 private unsubscribe: (() => void) | null = null; 22 23 constructor( 24 /** Project root to resolve the module IDs relative to. */ 25 private projectRoot: string, 26 /** List of module IDs sorted by priority. Only the first file that exists will be observed. */ 27 private moduleIds: string[], 28 private settings: { 29 /** An additional warning message to add to the notice. */ 30 additionalWarning?: string; 31 } = {} 32 ) { 33 FileNotifier.instances.push(this); 34 } 35 36 /** Get the file in the project. */ 37 private resolveFilePath(): string | null { 38 for (const moduleId of this.moduleIds) { 39 const filePath = resolveFrom.silent(this.projectRoot, moduleId); 40 if (filePath) { 41 return filePath; 42 } 43 } 44 return null; 45 } 46 47 public startObserving(callback?: (cur: any, prev: any) => void) { 48 const configPath = this.resolveFilePath(); 49 if (configPath) { 50 debug(`Observing ${configPath}`); 51 return this.watchFile(configPath, callback); 52 } 53 return configPath; 54 } 55 56 public stopObserving() { 57 this.unsubscribe?.(); 58 } 59 60 /** Watch the file and warn to reload the CLI if it changes. */ 61 public watchFile = memoize(this.startWatchingFile.bind(this)); 62 63 private startWatchingFile(filePath: string, callback?: (cur: any, prev: any) => void): string { 64 const configName = path.relative(this.projectRoot, filePath); 65 const listener = (cur: any, prev: any) => { 66 if (prev.size || cur.size) { 67 Log.log( 68 `\u203A Detected a change in ${chalk.bold( 69 configName 70 )}. Restart the server to see the new results.` + (this.settings.additionalWarning || '') 71 ); 72 } 73 }; 74 75 const watcher = watchFile(filePath, callback ?? listener); 76 77 this.unsubscribe = () => { 78 watcher.unref(); 79 }; 80 81 return filePath; 82 } 83} 84