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