-
Notifications
You must be signed in to change notification settings - Fork 1.4k
/
Copy pathwatcher.ts
150 lines (130 loc) · 5.01 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 { join } from 'path';
import { normalizeInstanceOrArray, normalizeOutputParam, Types } from '@graphql-codegen/plugin-helpers';
import { isValidPath } from '@graphql-tools/utils';
import type { subscribe } from '@parcel/watcher';
import debounce from 'debounce';
import isGlob from 'is-glob';
import logSymbols from 'log-symbols';
import { executeCodegen } from '../codegen.js';
import { CodegenContext, loadContext } from '../config.js';
import { lifecycleHooks } from '../hooks.js';
import { debugLog } from './debugging.js';
import { getLogger } from './logger.js';
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[]>
): Promise<void> => {
debugLog(`[Watcher] Starting watcher...`);
let config: Types.Config & { configFilePath?: string } = 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 watcherSubscription: Awaited<ReturnType<typeof subscribe>>;
const runWatcher = async () => {
const parcelWatcher = await import('@parcel/watcher');
debugLog(`[Watcher] Parcel watcher loaded...`);
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?.extension;
if (extension) {
ignored.push(join(entry.filename, '**', '*' + extension));
}
} else {
ignored.push(entry.filename);
}
});
watcherSubscription = await parcelWatcher.subscribe(
process.cwd(),
async (_, events) => {
// it doesn't matter what has changed, need to run whole process anyway
await Promise.all(
events.map(async ({ type: eventName, path }) => {
lifecycleHooks(config.hooks).onWatchTriggered(eventName, path);
debugLog(`[Watcher] triggered due to a file ${eventName} event: ${path}`);
const fullPath = join(process.cwd(), path);
// In ESM require is not defined
try {
delete require.cache[fullPath];
} catch (err) {}
if (eventName === 'update' && config.configFilePath && fullPath === config.configFilePath) {
log(`${logSymbols.info} Config file has changed, reloading...`);
const context = await loadContext(config.configFilePath);
const newParsedConfig: Types.Config & { configFilePath?: string } = context.getConfig();
newParsedConfig.watch = config.watch;
newParsedConfig.silent = config.silent;
newParsedConfig.overwrite = config.overwrite;
newParsedConfig.configFilePath = config.configFilePath;
config = newParsedConfig;
initalContext.updateConfig(config);
}
debouncedExec();
})
);
},
{ ignore: ignored }
);
debugLog(`[Watcher] Started`);
const shutdown = () => {
isShutdown = true;
debugLog(`[Watcher] Shutting down`);
log(`Shutting down watch...`);
watcherSubscription.unsubscribe();
lifecycleHooks(config.hooks).beforeDone();
};
process.once('SIGINT', shutdown);
process.once('SIGTERM', shutdown);
};
// the promise never resolves to keep process running
return new Promise<void>((resolve, reject) => {
executeCodegen(initalContext)
.then(onNext, () => Promise.resolve())
.then(runWatcher)
.catch(err => {
watcherSubscription.unsubscribe();
reject(err);
});
});
};