/
watcher.ts
150 lines (129 loc) · 4.73 KB
/
watcher.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import { executeCodegen } from '../codegen';
import { Types } from '@graphql-codegen/plugin-helpers';
import { 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';
const isValidPath = require('is-valid-path');
function log(msg: string) {
// double spaces to inline the message with Listr
getLogger().info(` ${msg}`);
}
function emitWatching() {
log(`${logSymbols.info} Watching for changes...`);
}
export const createWatcher = (initalContext: CodegenContext, onNext: (result: Types.FileOutput[]) => Promise<Types.FileOutput[]>) => {
debugLog(`[Watcher] Starting watcher...`);
let config: Types.Config = initalContext.getConfig();
const files: string[] = [initalContext.filepath].filter(a => a);
const documents = normalizeInstanceOrArray<Types.OperationDocument>(config.documents);
const schemas = normalizeInstanceOrArray<Types.Schema>(config.schema);
// Add schemas and documents from "generates"
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: string) => {
if (isGlob(schema) || isValidPath(schema)) {
files.push(schema);
}
});
if (typeof config.watch !== 'boolean') {
files.push(...normalizeInstanceOrArray<string>(config.watch));
}
let watcher: FSWatcher;
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: string[] = [];
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: true,
interval: 100,
binaryInterval: 300,
depth: 99,
awaitWriteFinish: true,
ignorePermissionErrors: false,
atomic: true,
ignored,
});
debugLog(`[Watcher] Started`);
const shutdown = async () => {
isShutdown = true;
debugLog(`[Watcher] Shutting down`);
log(`Shutting down watch...`);
watcher.close();
lifecycleHooks(config.hooks).beforeDone();
};
// it doesn't matter what has changed, need to run whole process anyway
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);
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() as Types.Config;
newParsedConfig.watch = config.watch;
newParsedConfig.silent = config.silent;
newParsedConfig.overwrite = config.overwrite;
newParsedConfig.configFilePath = config.configFilePath;
config = newParsedConfig;
}
debouncedExec();
});
process.once('SIGINT', shutdown);
process.once('SIGTERM', shutdown);
};
// the promise never resolves to keep process running
return new Promise((_, reject) => {
executeCodegen(initalContext)
.then(onNext, () => Promise.resolve())
.then(runWatcher)
.catch(err => {
watcher.close();
reject(err);
});
});
};