import { executeCodegen } from '../codegen' ; import { Types , normalizeInstanceOrArray , normalizeOutputParam } from '@graphql-codegen/plugin-helpers' ; import isGlob from 'is-glob' ; import debounce from 'debounce' ; import logSymbols from 'log-symbols' ; import { debugLog } from './debugging' ; import { getLogger } from './logger' ; import { join } from 'path' ; import { FSWatcher } from 'chokidar' ; import { lifecycleHooks } from '../hooks' ; import { loadContext , CodegenContext } from '../config' ; import { isValidPath } from '@graphql-tools/utils' ; function log ( msg ) { getLogger ( ) . info ( `  ${ msg }` ) ; } function emitWatching ( ) { log ( `${ logSymbols . info } Watching for changes...` ) ; } export const createWatcher = ( initalContext , onNext ) : Promise < void > => { debugLog ( `[Watcher] Starting watcher...` ) ; let config = initalContext . getConfig ( ) ; const files = [ initalContext . filepath ] . filter ( a => a ) ; const documents = normalizeInstanceOrArray < Types . OperationDocument > ( config . documents ) ; const schemas = normalizeInstanceOrArray < Types . Schema > ( config . schema ) ; Object . keys ( config . generates ) . map ( filename => normalizeOutputParam ( config . generates [ filename ] ) ) . forEach ( conf => { schemas . push ( ... normalizeInstanceOrArray < Types . Schema > ( conf . schema ) ) ; documents . push ( ... normalizeInstanceOrArray < Types . OperationDocument > ( conf . documents ) ) ; } ) ; if ( documents ) { documents . forEach ( doc => { if ( typeof doc === 'string' ) { files . push ( doc ) ; } else { files . push ( ... Object . keys ( doc ) ) ; } } ) ; } schemas . forEach ( ( schema ) => { if ( isGlob ( schema ) || isValidPath ( schema ) ) { files . push ( schema ) ; } } ) ; if ( typeof config . watch !== 'boolean' ) { files . push ( ... normalizeInstanceOrArray < string > ( config . watch ) ) ; } let watcher ; const runWatcher = async ( ) => { const chokidar = await import ( 'chokidar' ) ; let isShutdown = false ; const debouncedExec = debounce ( ( ) => { if ( ! isShutdown ) { executeCodegen ( initalContext ) . then ( onNext , ( ) => Promise . resolve ( ) ) . then ( ( ) => emitWatching ( ) ) ; } } , 100 ) ; emitWatching ( ) ; const ignored = [ ] ; Object . keys ( config . generates ) . map ( filename => ( { filename , config : normalizeOutputParam ( config . generates [ filename ] ) } ) ) . forEach ( entry => { if ( entry . config . preset ) { const extension = entry . config . presetConfig && entry . config . presetConfig . extension ; if ( extension ) { ignored . push ( join ( entry . filename , '**' , '*' + extension ) ) ; } } else { ignored . push ( entry . filename ) ; } } ) ; watcher = chokidar . watch ( files , { persistent : true , ignoreInitial : true , followSymlinks : true , cwd : process . cwd ( ) , disableGlobbing : false , usePolling : config . watchConfig ?. usePolling , interval : config . watchConfig ?. interval , depth : 99 , awaitWriteFinish : true , ignorePermissionErrors : false , atomic : true , ignored , } ) ; debugLog ( `[Watcher] Started` ) ; const shutdown = ( ) => { isShutdown = true ; debugLog ( `[Watcher] Shutting down` ) ; log ( `Shutting down watch...` ) ; watcher . close ( ) ; lifecycleHooks ( config . hooks ) . beforeDone ( ) ; } ; watcher . on ( 'all' , async ( eventName , path ) => { lifecycleHooks ( config . hooks ) . onWatchTriggered ( eventName , path ) ; debugLog ( `[Watcher] triggered due to a file ${ eventName } event: ${ path }` ) ; const fullPath = join ( process . cwd ( ) , path ) ; try { delete require . cache [ fullPath ] ; } catch ( err ) { } if ( eventName === 'change' && config . configFilePath && fullPath === config . configFilePath ) { log ( `${ logSymbols . info } Config file has changed, reloading...` ) ; const context = await loadContext ( config . configFilePath ) ; const newParsedConfig = context . getConfig ( ) ; newParsedConfig . watch = config . watch ; newParsedConfig . silent = config . silent ; newParsedConfig . overwrite = config . overwrite ; newParsedConfig . configFilePath = config . configFilePath ; config = newParsedConfig ; initalContext . updateConfig ( config ) ; } debouncedExec ( ) ; } ) ; process . once ( 'SIGINT' , shutdown ) ; process . once ( 'SIGTERM' , shutdown ) ; } ; return new Promise < void > ( ( resolve , reject ) => { executeCodegen ( initalContext ) . then ( onNext , ( ) => Promise . resolve ( ) ) . then ( runWatcher ) . catch ( err => { watcher . close ( ) ; reject ( err ) ; } ) ; } ) ; } ;