@travetto/compiler 4.0.0-rc.0 → 4.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -29
- package/bin/common.js +41 -28
- package/bin/trvc.js +5 -6
- package/package.json +6 -6
- package/src/compiler.ts +68 -37
- package/src/event.ts +1 -1
- package/src/state.ts +39 -21
- package/src/types.ts +1 -1
- package/src/util.ts +4 -4
- package/src/watch.ts +101 -98
- package/support/entry.trvc.ts +47 -22
- package/support/log.ts +22 -24
- package/support/queue.ts +6 -0
- package/support/server/client.ts +59 -31
- package/support/server/process-handle.ts +57 -0
- package/support/server/runner.ts +16 -11
- package/support/server/server.ts +62 -45
- package/support/setup.ts +1 -1
- package/support/types.ts +1 -2
- package/support/util.ts +4 -2
- package/src/internal/watch-core.ts +0 -107
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import timers from 'node:timers/promises';
|
|
4
|
+
|
|
5
|
+
import type { ManifestContext } from '@travetto/manifest';
|
|
6
|
+
|
|
7
|
+
export class ProcessHandle {
|
|
8
|
+
|
|
9
|
+
#file: string;
|
|
10
|
+
|
|
11
|
+
constructor(ctx: ManifestContext, name: string) {
|
|
12
|
+
this.#file = path.resolve(ctx.workspace.path, ctx.build.toolFolder, `${name}.pid`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
writePid(pid: number): Promise<void> {
|
|
16
|
+
return fs.writeFile(this.#file, JSON.stringify(pid), 'utf8');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
getPid(): Promise<number | undefined> {
|
|
20
|
+
return fs.readFile(this.#file, 'utf8').then(v => +v, () => undefined);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async isRunning(): Promise<boolean> {
|
|
24
|
+
const pid = await this.getPid();
|
|
25
|
+
if (!pid) { return false; }
|
|
26
|
+
try {
|
|
27
|
+
process.kill(pid, 0); // See if process is still running
|
|
28
|
+
return false;
|
|
29
|
+
} catch { }
|
|
30
|
+
return true; // Still running
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async kill(): Promise<boolean> {
|
|
34
|
+
const pid = await this.getPid();
|
|
35
|
+
if (pid && await this.isRunning()) {
|
|
36
|
+
try {
|
|
37
|
+
return process.kill(pid);
|
|
38
|
+
} catch { }
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async ensureKilled(gracePeriod: number = 3000): Promise<boolean> {
|
|
44
|
+
const start = Date.now();
|
|
45
|
+
const pid = await this.getPid();
|
|
46
|
+
while (pid && (Date.now() - start) < gracePeriod) { // Ensure its done
|
|
47
|
+
if (!await this.isRunning()) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
await timers.setTimeout(100);
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
pid && process.kill(pid); // Force kill
|
|
54
|
+
} catch { }
|
|
55
|
+
return pid !== undefined;
|
|
56
|
+
}
|
|
57
|
+
}
|
package/support/server/runner.ts
CHANGED
|
@@ -4,12 +4,12 @@ import { rmSync } from 'node:fs';
|
|
|
4
4
|
|
|
5
5
|
import type { ManifestContext, DeltaEvent } from '@travetto/manifest';
|
|
6
6
|
|
|
7
|
-
import type {
|
|
7
|
+
import type { CompilerEvent, CompilerMode } from '../types';
|
|
8
8
|
import { AsyncQueue } from '../queue';
|
|
9
9
|
import { LogUtil } from '../log';
|
|
10
10
|
import { CommonUtil } from '../util';
|
|
11
11
|
|
|
12
|
-
const log = LogUtil.
|
|
12
|
+
const log = LogUtil.logger('compiler-exec');
|
|
13
13
|
const isEvent = (msg: unknown): msg is CompilerEvent => !!msg && typeof msg === 'object' && 'type' in msg;
|
|
14
14
|
|
|
15
15
|
/**
|
|
@@ -20,8 +20,13 @@ export class CompilerRunner {
|
|
|
20
20
|
/**
|
|
21
21
|
* Run compile process
|
|
22
22
|
*/
|
|
23
|
-
static async * runProcess(ctx: ManifestContext, changed: DeltaEvent[],
|
|
24
|
-
|
|
23
|
+
static async * runProcess(ctx: ManifestContext, changed: DeltaEvent[], mode: CompilerMode, signal: AbortSignal): AsyncIterable<CompilerEvent> {
|
|
24
|
+
if (signal.aborted) {
|
|
25
|
+
log('debug', 'Skipping, shutting down');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const watch = mode === 'watch';
|
|
25
30
|
if (!changed.length && !watch) {
|
|
26
31
|
yield { type: 'state', payload: { state: 'compile-end' } };
|
|
27
32
|
log('debug', 'Skipped');
|
|
@@ -36,7 +41,6 @@ export class CompilerRunner {
|
|
|
36
41
|
const changedFiles = changed[0]?.file === '*' ? ['*'] : changed.map(ev => ev.sourceFile);
|
|
37
42
|
|
|
38
43
|
const queue = new AsyncQueue<CompilerEvent>();
|
|
39
|
-
let kill: (() => void) | undefined;
|
|
40
44
|
|
|
41
45
|
try {
|
|
42
46
|
await CommonUtil.writeTextFile(deltaFile, changedFiles.join('\n'));
|
|
@@ -53,22 +57,23 @@ export class CompilerRunner {
|
|
|
53
57
|
.on('message', msg => isEvent(msg) && queue.add(msg))
|
|
54
58
|
.on('exit', () => queue.close());
|
|
55
59
|
|
|
56
|
-
kill = (): unknown =>
|
|
60
|
+
const kill = (): unknown => {
|
|
61
|
+
log('debug', 'Shutting down process');
|
|
62
|
+
return (proc.connected ? proc.send('shutdown', (err) => proc.kill()) : proc.kill());
|
|
63
|
+
};
|
|
57
64
|
|
|
58
65
|
process.once('SIGINT', kill);
|
|
59
66
|
signal.addEventListener('abort', kill);
|
|
60
67
|
|
|
61
68
|
yield* queue;
|
|
62
69
|
|
|
63
|
-
if (
|
|
64
|
-
log('error', `
|
|
70
|
+
if (proc.exitCode !== 0) {
|
|
71
|
+
log('error', `Terminated during compilation, code=${proc.exitCode}, killed=${proc.killed}`);
|
|
65
72
|
}
|
|
73
|
+
process.off('SIGINT', kill);
|
|
66
74
|
|
|
67
75
|
log('debug', 'Finished');
|
|
68
76
|
} finally {
|
|
69
|
-
if (kill) {
|
|
70
|
-
process.off('SIGINT', kill);
|
|
71
|
-
}
|
|
72
77
|
rmSync(deltaFile, { force: true });
|
|
73
78
|
}
|
|
74
79
|
}
|
package/support/server/server.ts
CHANGED
|
@@ -1,36 +1,23 @@
|
|
|
1
1
|
import http from 'node:http';
|
|
2
2
|
import fs from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { setMaxListeners } from 'node:events';
|
|
4
5
|
|
|
5
6
|
import type { ManifestContext } from '@travetto/manifest';
|
|
6
7
|
|
|
7
|
-
import type { CompilerMode,
|
|
8
|
+
import type { CompilerMode, CompilerProgressEvent, CompilerEvent, CompilerEventType, CompilerServerInfo } from '../types';
|
|
8
9
|
import { LogUtil } from '../log';
|
|
9
10
|
import { CompilerClient } from './client';
|
|
10
11
|
import { CommonUtil } from '../util';
|
|
12
|
+
import { ProcessHandle } from './process-handle';
|
|
11
13
|
|
|
12
|
-
const log = LogUtil.
|
|
14
|
+
const log = LogUtil.logger('compiler-server');
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
17
|
* Compiler Server Class
|
|
16
18
|
*/
|
|
17
19
|
export class CompilerServer {
|
|
18
20
|
|
|
19
|
-
static readJSONRequest<T>(req: http.IncomingMessage): Promise<T> {
|
|
20
|
-
return new Promise<T>((res, rej) => {
|
|
21
|
-
const body: Buffer[] = [];
|
|
22
|
-
req.on('data', (chunk) => body.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk));
|
|
23
|
-
req.on('end', () => {
|
|
24
|
-
try {
|
|
25
|
-
res(JSON.parse(Buffer.concat(body).toString('utf8')));
|
|
26
|
-
} catch (err) {
|
|
27
|
-
rej(err);
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
req.on('error', rej);
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
|
|
34
21
|
#ctx: ManifestContext;
|
|
35
22
|
#server: http.Server;
|
|
36
23
|
#listeners: { res: http.ServerResponse, type: CompilerEventType }[] = [];
|
|
@@ -39,15 +26,18 @@ export class CompilerServer {
|
|
|
39
26
|
info: CompilerServerInfo;
|
|
40
27
|
#client: CompilerClient;
|
|
41
28
|
#url: string;
|
|
29
|
+
#handle: Record<'compiler' | 'server', ProcessHandle>;
|
|
42
30
|
|
|
43
|
-
constructor(ctx: ManifestContext,
|
|
31
|
+
constructor(ctx: ManifestContext, mode: CompilerMode) {
|
|
44
32
|
this.#ctx = ctx;
|
|
45
|
-
this.#client = new CompilerClient(ctx, LogUtil.
|
|
33
|
+
this.#client = new CompilerClient(ctx, LogUtil.logger('client.server'));
|
|
46
34
|
this.#url = this.#client.url;
|
|
35
|
+
this.#handle = { server: new ProcessHandle(ctx, 'server'), compiler: new ProcessHandle(ctx, 'compiler') };
|
|
36
|
+
|
|
47
37
|
this.info = {
|
|
48
38
|
state: 'startup',
|
|
49
39
|
iteration: Date.now(),
|
|
50
|
-
mode
|
|
40
|
+
mode,
|
|
51
41
|
serverPid: process.pid,
|
|
52
42
|
compilerPid: -1,
|
|
53
43
|
path: ctx.workspace.path,
|
|
@@ -60,7 +50,7 @@ export class CompilerServer {
|
|
|
60
50
|
keepAliveTimeout: 1000 * 60 * 60,
|
|
61
51
|
}, (req, res) => this.#onRequest(req, res));
|
|
62
52
|
|
|
63
|
-
|
|
53
|
+
setMaxListeners(1000, this.signal);
|
|
64
54
|
}
|
|
65
55
|
|
|
66
56
|
get mode(): CompilerMode {
|
|
@@ -80,9 +70,11 @@ export class CompilerServer {
|
|
|
80
70
|
const info = await this.#client.info();
|
|
81
71
|
resolve((info && info.mode === 'build' && this.mode === 'watch') ? 'retry' : 'running');
|
|
82
72
|
} else {
|
|
73
|
+
log('warn', 'Failed in running server', err);
|
|
83
74
|
reject(err);
|
|
84
75
|
}
|
|
85
|
-
})
|
|
76
|
+
})
|
|
77
|
+
.on('close', () => log('debug', 'Server close event'));
|
|
86
78
|
|
|
87
79
|
const url = new URL(this.#url);
|
|
88
80
|
setTimeout(() => this.#server.listen(+url.port, url.hostname), 1); // Run async
|
|
@@ -96,6 +88,8 @@ export class CompilerServer {
|
|
|
96
88
|
// Let the server finish
|
|
97
89
|
await this.#client.waitForState(['close'], 'Server closed', this.signal);
|
|
98
90
|
return this.#tryListen(attempt + 1);
|
|
91
|
+
} else if (output === 'ok') {
|
|
92
|
+
await this.#handle.server.writePid(this.info.serverPid);
|
|
99
93
|
}
|
|
100
94
|
|
|
101
95
|
return output;
|
|
@@ -105,6 +99,10 @@ export class CompilerServer {
|
|
|
105
99
|
res.writeHead(200);
|
|
106
100
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
107
101
|
this.#listeners.push({ res, type: type as 'change' });
|
|
102
|
+
if (type === 'state') { // Send on initial connect
|
|
103
|
+
res.write(JSON.stringify({ state: this.info.state }));
|
|
104
|
+
}
|
|
105
|
+
res.write('\n'); // Send at least one byte on listen
|
|
108
106
|
await new Promise(resolve => res.on('close', resolve));
|
|
109
107
|
this.#listeners.splice(this.#listeners.findIndex(x => x.res === res), 1);
|
|
110
108
|
res.end();
|
|
@@ -123,7 +121,9 @@ export class CompilerServer {
|
|
|
123
121
|
log('info', 'Server disconnect requested');
|
|
124
122
|
this.info.iteration = Date.now();
|
|
125
123
|
await new Promise(r => setTimeout(r, 20));
|
|
126
|
-
this.#
|
|
124
|
+
for (const el of this.#listeners) {
|
|
125
|
+
el.res.destroy();
|
|
126
|
+
}
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
async #clean(): Promise<{ clean: boolean }> {
|
|
@@ -144,10 +144,14 @@ export class CompilerServer {
|
|
|
144
144
|
|
|
145
145
|
let out: unknown;
|
|
146
146
|
switch (action) {
|
|
147
|
-
case 'send-event': await this.#emitEvent(await CompilerServer.readJSONRequest(req)); out = { received: true }; break;
|
|
148
147
|
case 'event': return await this.#addListener(subAction, res);
|
|
149
|
-
case 'stop': out = await this.close(true); break;
|
|
150
148
|
case 'clean': out = await this.#clean(); break;
|
|
149
|
+
case 'stop': {
|
|
150
|
+
// Must send immediately
|
|
151
|
+
res.end(JSON.stringify({ closing: true }));
|
|
152
|
+
await this.close();
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
151
155
|
case 'info':
|
|
152
156
|
default: out = this.info ?? {}; break;
|
|
153
157
|
}
|
|
@@ -158,10 +162,6 @@ export class CompilerServer {
|
|
|
158
162
|
* Process events
|
|
159
163
|
*/
|
|
160
164
|
async processEvents(src: (signal: AbortSignal) => AsyncIterable<CompilerEvent>): Promise<void> {
|
|
161
|
-
|
|
162
|
-
LogUtil.log('compiler', 'debug', 'Started, streaming logs');
|
|
163
|
-
LogUtil.consumeLogEvents(this.#client.fetchEvents('log', { signal: this.signal }));
|
|
164
|
-
|
|
165
165
|
for await (const ev of CommonUtil.restartableEvents(src, this.signal, this.isResetEvent)) {
|
|
166
166
|
if (ev.type === 'progress') {
|
|
167
167
|
await LogUtil.logProgress?.(ev.payload);
|
|
@@ -171,10 +171,13 @@ export class CompilerServer {
|
|
|
171
171
|
|
|
172
172
|
if (ev.type === 'state') {
|
|
173
173
|
this.info.state = ev.payload.state;
|
|
174
|
+
await this.#handle.compiler.writePid(this.info.compilerPid);
|
|
174
175
|
if (ev.payload.state === 'init' && ev.payload.extra && 'pid' in ev.payload.extra && typeof ev.payload.extra.pid === 'number') {
|
|
175
176
|
this.info.compilerPid = ev.payload.extra.pid;
|
|
176
177
|
}
|
|
177
178
|
log('info', `State changed: ${this.info.state}`);
|
|
179
|
+
} else if (ev.type === 'log') {
|
|
180
|
+
LogUtil.logEvent(ev.payload);
|
|
178
181
|
}
|
|
179
182
|
if (this.isResetEvent(ev)) {
|
|
180
183
|
await this.#disconnectActive();
|
|
@@ -182,31 +185,45 @@ export class CompilerServer {
|
|
|
182
185
|
}
|
|
183
186
|
|
|
184
187
|
// Terminate, after letting all remaining events emit
|
|
185
|
-
await this.close(
|
|
188
|
+
await this.close();
|
|
189
|
+
|
|
190
|
+
log('debug', 'Finished processing events');
|
|
186
191
|
}
|
|
187
192
|
|
|
188
193
|
/**
|
|
189
194
|
* Close server
|
|
190
195
|
*/
|
|
191
|
-
async close(
|
|
196
|
+
async close(): Promise<void> {
|
|
192
197
|
log('info', 'Closing down server');
|
|
193
|
-
await new Promise(r => {
|
|
194
198
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
199
|
+
// If we are in a place where progress exists
|
|
200
|
+
if (this.info.state === 'compile-start') {
|
|
201
|
+
const cancel: CompilerProgressEvent = { complete: true, idx: 0, total: 0, message: 'Complete', operation: 'compile' };
|
|
202
|
+
LogUtil.logProgress?.(cancel);
|
|
203
|
+
this.#emitEvent({ type: 'progress', payload: cancel });
|
|
204
|
+
}
|
|
200
205
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
this.#
|
|
206
|
-
|
|
206
|
+
try {
|
|
207
|
+
await new Promise((resolve, reject) => {
|
|
208
|
+
setTimeout(reject, 2000).unref(); // 2s max wait
|
|
209
|
+
this.#server.close(resolve);
|
|
210
|
+
this.#emitEvent({ type: 'state', payload: { state: 'close' } });
|
|
211
|
+
setImmediate(() => {
|
|
212
|
+
this.#server.closeAllConnections();
|
|
213
|
+
this.#shutdown.abort();
|
|
214
|
+
});
|
|
207
215
|
});
|
|
208
|
-
}
|
|
209
|
-
|
|
216
|
+
} catch { // Timeout or other error
|
|
217
|
+
// Force shutdown
|
|
218
|
+
this.#server.closeAllConnections();
|
|
219
|
+
if (this.info.compilerPid) { // Ensure its killed
|
|
220
|
+
try {
|
|
221
|
+
process.kill(this.info.compilerPid);
|
|
222
|
+
} catch { }
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
log('info', 'Closed down server');
|
|
210
227
|
}
|
|
211
228
|
|
|
212
229
|
/**
|
package/support/setup.ts
CHANGED
|
@@ -214,7 +214,7 @@ export class CompilerSetup {
|
|
|
214
214
|
// Update all manifests when in mono repo
|
|
215
215
|
if (delta.length && ctx.workspace.mono) {
|
|
216
216
|
const names: string[] = [];
|
|
217
|
-
const mods = Object.values(manifest.modules).filter(x => x.
|
|
217
|
+
const mods = Object.values(manifest.modules).filter(x => x.workspace && x.name !== ctx.workspace.name);
|
|
218
218
|
for (const mod of mods) {
|
|
219
219
|
const modCtx = ManifestUtil.getModuleContext(ctx, mod.sourceFolder);
|
|
220
220
|
const modManifest = await ManifestUtil.buildManifest(modCtx);
|
package/support/types.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
export type CompilerMode = 'build' | 'watch';
|
|
2
|
-
export type CompilerOp = CompilerMode | 'run';
|
|
3
2
|
|
|
4
3
|
export type CompilerStateType = 'startup' | 'init' | 'compile-start' | 'compile-end' | 'watch-start' | 'watch-end' | 'reset' | 'close';
|
|
5
|
-
export type CompilerChangeEvent = { file: string, action: 'create' | 'update' | 'delete',
|
|
4
|
+
export type CompilerChangeEvent = { file: string, action: 'create' | 'update' | 'delete', output: string, module: string, time: number };
|
|
6
5
|
export type CompilerLogLevel = 'info' | 'debug' | 'warn' | 'error';
|
|
7
6
|
export type CompilerLogEvent = { level: CompilerLogLevel, message: string, time: number, args?: unknown[], scope?: string };
|
|
8
7
|
export type CompilerProgressEvent = { idx: number, total: number, message: string, operation: 'compile', complete?: boolean };
|
package/support/util.ts
CHANGED
|
@@ -58,6 +58,7 @@ export class CommonUtil {
|
|
|
58
58
|
* Restartable Event Stream
|
|
59
59
|
*/
|
|
60
60
|
static async * restartableEvents<T>(src: (signal: AbortSignal) => AsyncIterable<T>, parent: AbortSignal, shouldRestart: (item: T) => boolean): AsyncIterable<T> {
|
|
61
|
+
const log = LogUtil.logger('event-stream');
|
|
61
62
|
outer: while (!parent.aborted) {
|
|
62
63
|
const controller = new AbortController();
|
|
63
64
|
setMaxListeners(1000, controller.signal);
|
|
@@ -66,18 +67,19 @@ export class CommonUtil {
|
|
|
66
67
|
|
|
67
68
|
const comp = src(controller.signal);
|
|
68
69
|
|
|
69
|
-
|
|
70
|
+
log('debug', 'Started event stream');
|
|
70
71
|
|
|
71
72
|
// Wait for all events, close at the end
|
|
72
73
|
for await (const ev of comp) {
|
|
73
74
|
yield ev;
|
|
74
75
|
if (shouldRestart(ev)) {
|
|
76
|
+
log('debug', 'Restarting stream');
|
|
75
77
|
controller.abort(); // Ensure terminated of process
|
|
76
78
|
continue outer;
|
|
77
79
|
}
|
|
78
80
|
}
|
|
79
81
|
|
|
80
|
-
|
|
82
|
+
log('debug', 'Finished event stream');
|
|
81
83
|
|
|
82
84
|
// Natural exit, we done
|
|
83
85
|
if (!controller.signal.aborted) { // Shutdown source if still running
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs/promises';
|
|
2
|
-
|
|
3
|
-
import { IndexedModule, ManifestContext, ManifestModuleUtil, path } from '@travetto/manifest';
|
|
4
|
-
|
|
5
|
-
import { AsyncQueue } from '../../support/queue';
|
|
6
|
-
|
|
7
|
-
export type WatchEvent<T = {}> =
|
|
8
|
-
({ action: 'create' | 'update' | 'delete', file: string, folder: string } & T) |
|
|
9
|
-
{ action: 'reset', file: string };
|
|
10
|
-
|
|
11
|
-
const CREATE_THRESHOLD = 50;
|
|
12
|
-
const VALID_TYPES = new Set(['ts', 'typings', 'js', 'package-json']);
|
|
13
|
-
type ToWatch = { file: string, actions: string[] };
|
|
14
|
-
|
|
15
|
-
/** Watch file for reset */
|
|
16
|
-
function watchForReset(q: AsyncQueue<WatchEvent>, root: string, files: ToWatch[], signal: AbortSignal): void {
|
|
17
|
-
const watchers: Record<string, { folder: string, files: Map<string, (ToWatch & { name: string, actionSet: Set<string> })> }> = {};
|
|
18
|
-
// Group by base path
|
|
19
|
-
for (const el of files) {
|
|
20
|
-
const full = path.resolve(root, el.file);
|
|
21
|
-
const folder = path.dirname(full);
|
|
22
|
-
const tgt = { ...el, name: path.basename(el.file), actionSet: new Set(el.actions) };
|
|
23
|
-
const watcher = (watchers[folder] ??= { folder, files: new Map() });
|
|
24
|
-
watcher.files.set(tgt.name, tgt);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Fire them all off
|
|
28
|
-
Object.values(watchers).map(async (watcher) => {
|
|
29
|
-
try {
|
|
30
|
-
for await (const ev of fs.watch(watcher.folder, { persistent: true, encoding: 'utf8', signal })) {
|
|
31
|
-
const toWatch = watcher.files.get(ev.filename!);
|
|
32
|
-
if (toWatch) {
|
|
33
|
-
const stat = await fs.stat(path.resolve(root, ev.filename!)).catch(() => undefined);
|
|
34
|
-
const action = !stat ? 'delete' : ((Date.now() - stat.ctimeMs) < CREATE_THRESHOLD) ? 'create' : 'update';
|
|
35
|
-
if (toWatch.actionSet.has(action)) {
|
|
36
|
-
q.add({ action: 'reset', file: ev.filename! });
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
} catch (err) {
|
|
41
|
-
// Ignore
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** Watch recursive files for a given folder */
|
|
47
|
-
async function watchFolder(ctx: ManifestContext, q: AsyncQueue<WatchEvent>, src: string, target: string, signal: AbortSignal): Promise<void> {
|
|
48
|
-
const lib = await import('@parcel/watcher');
|
|
49
|
-
const ignore = [
|
|
50
|
-
'node_modules', '**/.trv',
|
|
51
|
-
...((!ctx.workspace.mono || src === ctx.workspace.path) ? [ctx.build.compilerFolder, ctx.build.outputFolder] : []),
|
|
52
|
-
...(await fs.readdir(src)).filter(x => x.startsWith('.'))
|
|
53
|
-
];
|
|
54
|
-
|
|
55
|
-
const cleanup = await lib.subscribe(src, async (err, events) => {
|
|
56
|
-
if (err) {
|
|
57
|
-
console.error('Watch Error', err);
|
|
58
|
-
}
|
|
59
|
-
for (const ev of events) {
|
|
60
|
-
const finalEv = { action: ev.type, file: path.toPosix(ev.path), folder: target };
|
|
61
|
-
if (ev.type !== 'delete') {
|
|
62
|
-
const stats = await fs.stat(finalEv.file);
|
|
63
|
-
if ((Date.now() - stats.ctimeMs) < CREATE_THRESHOLD) {
|
|
64
|
-
ev.type = 'create'; // Force create on newly stated files
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (ev.type === 'delete' && finalEv.file === src) {
|
|
69
|
-
return q.close();
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const matches = !finalEv.file.includes('/.') && VALID_TYPES.has(ManifestModuleUtil.getFileType(finalEv.file));
|
|
73
|
-
if (matches) {
|
|
74
|
-
q.add(finalEv);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}, { ignore });
|
|
78
|
-
signal.addEventListener('abort', () => cleanup.unsubscribe());
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/** Watch files */
|
|
82
|
-
export async function* fileWatchEvents(manifest: ManifestContext, modules: IndexedModule[], signal: AbortSignal): AsyncIterable<WatchEvent> {
|
|
83
|
-
const q = new AsyncQueue<WatchEvent>(signal);
|
|
84
|
-
|
|
85
|
-
for (const m of modules.filter(x => !manifest.workspace.mono || x.sourcePath !== manifest.workspace.path)) {
|
|
86
|
-
await watchFolder(manifest, q, m.sourcePath, m.sourcePath, signal);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Add monorepo folders
|
|
90
|
-
if (manifest.workspace.mono) {
|
|
91
|
-
const mono = modules.find(x => x.sourcePath === manifest.workspace.path)!;
|
|
92
|
-
for (const folder of Object.keys(mono.files)) {
|
|
93
|
-
if (!folder.startsWith('$')) {
|
|
94
|
-
await watchFolder(manifest, q, path.resolve(mono.sourcePath, folder), mono.sourcePath, signal);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
watchForReset(q, manifest.workspace.path, [
|
|
100
|
-
{ file: manifest.build.outputFolder, actions: ['delete'] },
|
|
101
|
-
{ file: manifest.build.compilerFolder, actions: ['delete'] },
|
|
102
|
-
{ file: 'package-lock.json', actions: ['delete', 'update', 'create'] },
|
|
103
|
-
{ file: 'package.json', actions: ['delete', 'update', 'create'] }
|
|
104
|
-
], signal);
|
|
105
|
-
|
|
106
|
-
yield* q;
|
|
107
|
-
}
|