@travetto/compiler 3.4.4 → 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/support/log.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  import type { ManifestContext } from '@travetto/manifest';
2
- import type { CompilerLogEvent, CompilerLogLevel } from './types';
2
+ import type { CompilerLogEvent, CompilerLogLevel, CompilerProgressEvent } from './types';
3
3
 
4
4
  export type CompilerLogger = (level: CompilerLogLevel, message: string, ...args: unknown[]) => void;
5
5
  export type WithLogger<T> = (log: CompilerLogger) => Promise<T>;
6
6
 
7
+ type ProgressWriter = (ev: CompilerProgressEvent) => (unknown | Promise<unknown>);
8
+
7
9
  const LEVEL_TO_PRI: Record<CompilerLogLevel, number> = { debug: 1, info: 2, warn: 3, error: 4 };
8
10
 
9
11
  const SCOPE_MAX = 15;
@@ -14,16 +16,37 @@ export class LogUtil {
14
16
 
15
17
  static logLevel: CompilerLogLevel = 'error';
16
18
 
19
+ static logProgress?: ProgressWriter;
20
+
21
+ static linePartial = false;
22
+
23
+ static #rewriteLine(text: string): Promise<void> | void {
24
+ // Move to 1st position, and clear after text
25
+ const done = process.stdout.write(`\x1b[1G${text}\x1b[0K`);
26
+ this.linePartial = !!text;
27
+ if (!done) {
28
+ return new Promise<void>(r => process.stdout.once('drain', r));
29
+ }
30
+ }
31
+
32
+
17
33
  /**
18
34
  * Set level for operation
19
35
  */
20
36
  static initLogs(ctx: ManifestContext, defaultLevel: CompilerLogLevel): void {
21
- // Listen only if we aren't in quiet
22
- if ((process.env.TRV_BUILD || !process.env.TRV_QUIET)) {
23
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
24
- this.logLevel = (process.env.TRV_BUILD as 'debug') || defaultLevel;
37
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
38
+ const build = process.env.TRV_BUILD as CompilerLogLevel | 'none';
39
+ if (build !== 'none' && process.env.TRV_QUIET !== 'true') {
40
+ this.logLevel = build || defaultLevel;
25
41
  }
26
- this.root = ctx.workspacePath;
42
+ this.root = ctx.workspace.path;
43
+ this.logProgress = (this.isLevelActive('info') && process.stdout.isTTY) ? this.#logProgressEvent : undefined;
44
+ }
45
+
46
+ static #logProgressEvent(ev: CompilerProgressEvent): Promise<void> | void {
47
+ const pct = Math.trunc(ev.idx * 100 / ev.total);
48
+ const text = ev.complete ? '' : `Compiling [${'#'.repeat(Math.trunc(pct / 10)).padEnd(10, ' ')}] [${ev.idx}/${ev.total}] ${ev.message}`;
49
+ return this.#rewriteLine(text);
27
50
  }
28
51
 
29
52
  /**
@@ -34,36 +57,44 @@ export class LogUtil {
34
57
  }
35
58
 
36
59
  /**
37
- * Log message with filtering by level
60
+ * Log event with filtering by level
38
61
  */
39
- static log(scope: string, level: CompilerLogLevel, message: string, ...args: unknown[]): void {
40
- LogUtil.sendLogEventToConsole({ level, scope, message, args, time: Date.now() });
62
+ static logEvent(ev: CompilerLogEvent): void {
63
+ if (this.isLevelActive(ev.level)) {
64
+ const params = [ev.message, ...ev.args ?? []].map(x => typeof x === 'string' ? x.replaceAll(this.root, '.') : x);
65
+ if (ev.scope) {
66
+ params.unshift(`[${ev.scope.padEnd(SCOPE_MAX, ' ')}]`);
67
+ }
68
+ params.unshift(new Date().toISOString(), `${ev.level.padEnd(5)}`);
69
+ if (this.linePartial) {
70
+ this.#rewriteLine(''); // Clear out progress line
71
+ }
72
+ // eslint-disable-next-line no-console
73
+ console[ev.level]!(...params);
74
+ }
41
75
  }
42
76
 
43
77
  /**
44
78
  * With logger
45
79
  */
46
80
  static withLogger<T>(scope: string, op: WithLogger<T>, basic = true): Promise<T> {
47
- const log = this.log.bind(null, scope);
81
+ const log = this.logger(scope);
48
82
  basic && log('debug', 'Started');
49
83
  return op(log).finally(() => basic && log('debug', 'Completed'));
50
84
  }
51
85
 
52
86
  /**
53
- * Compiler log event to console
87
+ * With scope
54
88
  */
55
- static sendLogEventToConsole(ev: CompilerLogEvent): void {
56
- if (this.isLevelActive(ev.level)) {
57
- const params = [ev.message, ...ev.args ?? []].map(x => typeof x === 'string' ? x.replaceAll(LogUtil.root, '.') : x);
58
- if (ev.scope) {
59
- params.unshift(`[${ev.scope.padEnd(SCOPE_MAX, ' ')}]`);
60
- }
61
- params.unshift(`${ev.level.padEnd(5)}`);
62
- if (!/(0|false|off|no)$/i.test(process.env.TRV_LOG_TIME ?? '')) {
63
- params.unshift(new Date().toISOString());
64
- }
65
- // eslint-disable-next-line no-console
66
- console[ev.level]!(...params);
67
- }
89
+ static logger(scope: string): CompilerLogger {
90
+ return (level, message, ...args) => this.logEvent({ scope, message, level, args, time: Date.now() });
91
+ }
92
+
93
+ /**
94
+ * Write all progress events if active
95
+ */
96
+ static async consumeProgressEvents(src: () => AsyncIterable<CompilerProgressEvent>): Promise<void> {
97
+ if (!this.logProgress) { return; }
98
+ for await (const item of src()) { await this.logProgress?.(item); }
68
99
  }
69
100
  }
package/support/queue.ts CHANGED
@@ -13,6 +13,9 @@ export class AsyncQueue<X> implements AsyncIterator<X>, AsyncIterable<X> {
13
13
 
14
14
  constructor(signal?: AbortSignal) {
15
15
  signal?.addEventListener('abort', () => this.close());
16
+ if (signal?.aborted) {
17
+ this.close();
18
+ }
16
19
  }
17
20
 
18
21
  [Symbol.asyncIterator](): AsyncIterator<X> { return this; }
@@ -25,12 +28,14 @@ export class AsyncQueue<X> implements AsyncIterator<X>, AsyncIterable<X> {
25
28
  return { value: (this.#queue.length ? this.#queue.shift() : undefined)!, done: this.#done };
26
29
  }
27
30
 
28
- add(item: X, immediate = false): void {
29
- if (!immediate) {
30
- this.#queue.push(item);
31
- } else {
32
- this.#queue.unshift(item);
33
- }
31
+ async throw(e?: Error): Promise<IteratorResult<X>> {
32
+ this.#done = true;
33
+ this.#ready.reject(e);
34
+ return { value: undefined, done: this.#done };
35
+ }
36
+
37
+ add(item: X): void {
38
+ this.#queue.push(item);
34
39
  this.#ready.resolve();
35
40
  }
36
41
 
@@ -1,118 +1,159 @@
1
- import rl from 'readline/promises';
2
- import { Readable } from 'stream';
1
+ import rl from 'node:readline/promises';
2
+ import timers from 'node:timers/promises';
3
+ import { Readable } from 'node:stream';
3
4
 
4
- import type { ManifestContext } from '@travetto/manifest';
5
- import type { CompilerServerEvent, CompilerServerEventType, CompilerServerInfo, CompilerStateType } from '../types';
5
+ import { ManifestContext } from '@travetto/manifest';
6
6
 
7
- import { LogUtil } from '../log';
7
+ import type { CompilerEvent, CompilerEventType, CompilerServerInfo, CompilerStateType } from '../types';
8
+ import type { CompilerLogger } from '../log';
9
+ import { ProcessHandle } from './process-handle';
8
10
 
9
- declare global {
10
- interface RequestInit { timeout?: number }
11
- }
12
-
13
- const log = LogUtil.log.bind(LogUtil, 'compiler-client');
14
-
15
- function getSignal(input?: AbortSignal): AbortSignal {
16
- // Ensure we capture end of process at least
17
- if (!input) {
18
- const ctrl = new AbortController();
19
- process.on('SIGINT', () => ctrl.abort());
20
- input = ctrl.signal;
21
- }
22
- return input;
23
- }
11
+ type FetchEventsConfig<T> = {
12
+ signal?: AbortSignal;
13
+ until?: (ev: T) => boolean;
14
+ enforceIteration?: boolean;
15
+ };
24
16
 
25
17
  /**
26
18
  * Compiler Client Operations
27
19
  */
28
- export class CompilerClientUtil {
20
+ export class CompilerClient {
21
+
22
+ #url: string;
23
+ #log: CompilerLogger;
24
+ #handle: Record<'compiler' | 'server', ProcessHandle>;
25
+
26
+ constructor(ctx: ManifestContext, log: CompilerLogger) {
27
+ this.#url = ctx.build.compilerUrl.replace('localhost', '127.0.0.1');
28
+ this.#log = log;
29
+ this.#handle = { compiler: new ProcessHandle(ctx, 'compiler'), server: new ProcessHandle(ctx, 'server') };
30
+ }
31
+
32
+ toString(): string {
33
+ return `[${this.constructor.name} url=${this.#url}]`;
34
+ }
35
+
36
+ get url(): string {
37
+ return this.#url;
38
+ }
29
39
 
30
- /**
31
- * Get server information, if server is running
32
- */
33
- static async getServerInfo(ctx: ManifestContext): Promise<CompilerServerInfo | undefined> {
40
+ async #fetch(rel: string, opts?: RequestInit & { timeout?: number }): Promise<Response> {
41
+ const ctrl = new AbortController();
42
+ opts?.signal?.addEventListener('abort', () => ctrl.abort());
43
+ const timeoutId = setTimeout(() => {
44
+ this.#log('error', `Timeout on request to ${this.#url}${rel}`);
45
+ ctrl.abort('TIMEOUT');
46
+ }, 100).unref();
34
47
  try {
48
+ return await fetch(`${this.#url}${rel}`, { ...opts, signal: ctrl.signal });
49
+ } finally {
50
+ clearTimeout(timeoutId);
51
+ }
52
+ }
53
+
54
+ /** Get server information, if server is running */
55
+ info(): Promise<CompilerServerInfo | undefined> {
56
+ return this.#fetch('/info').then(v => v.json(), () => undefined)
35
57
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
36
- const res = (await fetch(`${ctx.compilerUrl}/info`).then(v => v.json())) as CompilerServerInfo;
37
- return res;
38
- } catch (err) {
39
- return undefined;
58
+ .then(v => v as CompilerServerInfo);
59
+ }
60
+
61
+ async isWatching(): Promise<boolean> {
62
+ return (await this.info())?.state === 'watch-start';
63
+ }
64
+
65
+ /** Clean the server */
66
+ clean(): Promise<boolean> {
67
+ return this.#fetch('/clean').then(v => v.ok, () => false);
68
+ }
69
+
70
+ /** Stop server and wait for shutdown */
71
+ async stop(): Promise<boolean> {
72
+ const info = await this.info();
73
+ if (!info) {
74
+ this.#log('debug', 'Stopping server, info not found, manual killing');
75
+ return Promise.all([this.#handle.server.kill(), this.#handle.compiler.kill()])
76
+ .then(v => v.some(x => x));
40
77
  }
78
+
79
+ await this.#fetch('/stop').catch(() => { }); // Trigger
80
+ this.#log('debug', 'Waiting for compiler to exit');
81
+ await this.#handle.compiler.ensureKilled();
82
+ return true;
41
83
  }
42
84
 
43
- /**
44
- * Fetch compiler events
45
- */
46
- static async * fetchEvents<
47
- V extends CompilerServerEventType,
48
- T extends (CompilerServerEvent & { type: V })['payload']
49
- >(ctx: ManifestContext, type: V, signal?: AbortSignal, until?: (ev: T) => boolean): AsyncIterable<T> {
50
- log('debug', `Starting watch for events of type "${type}"`);
85
+ /** Fetch compiler events */
86
+ async * fetchEvents<
87
+ V extends CompilerEventType,
88
+ T extends (CompilerEvent & { type: V })['payload']
89
+ >(type: V, cfg: FetchEventsConfig<T> = {}): AsyncIterable<T> {
90
+ let info = await this.info();
91
+ if (!info) {
92
+ return;
93
+ }
94
+
95
+ this.#log('debug', `Starting watch for events of type "${type}"`);
51
96
 
52
- signal = getSignal(signal);
97
+ let signal = cfg.signal;
98
+
99
+ // Ensure we capture end of process at least
100
+ if (!signal) {
101
+ const ctrl = new AbortController();
102
+ process.on('SIGINT', () => ctrl.abort());
103
+ signal = ctrl.signal;
104
+ }
105
+
106
+ const { iteration } = info;
53
107
 
54
108
  for (; ;) {
55
109
  const ctrl = new AbortController();
110
+ const quit = (): void => ctrl.abort();
56
111
  try {
57
- signal.addEventListener('abort', () => ctrl.abort());
58
- const stream = await fetch(`${ctx.compilerUrl}/event/${type}`, {
59
- signal: ctrl.signal,
60
- timeout: 1000 * 60 * 60
61
- });
112
+ signal.addEventListener('abort', quit);
113
+ const stream = await this.#fetch(`/event/${type}`, { signal: ctrl.signal, keepalive: true });
114
+
62
115
  for await (const line of rl.createInterface(Readable.fromWeb(stream.body!))) {
63
116
  if (line.trim().charAt(0) === '{') {
64
117
  const val = JSON.parse(line);
65
- if (until?.(val)) {
66
- setTimeout(() => ctrl.abort(), 1);
118
+ if (cfg.until?.(val)) {
119
+ await timers.setTimeout(1);
120
+ ctrl.abort();
67
121
  }
68
122
  yield val;
69
123
  }
70
124
  }
71
- } catch (err) { }
125
+ } catch (err) {
126
+ if (!ctrl.signal.aborted) { throw err; }
127
+ }
128
+ signal.removeEventListener('abort', quit);
129
+
130
+ await timers.setTimeout(1);
131
+
132
+ info = await this.info();
72
133
 
73
- if (ctrl.signal.aborted || !(await this.getServerInfo(ctx))) { // If health check fails, or aborted
74
- log('debug', `Stopping watch for events of type "${type}"`);
134
+ if (ctrl.signal.reason === 'TIMEOUT') {
135
+ this.#log('debug', 'Failed due to timeout');
136
+ return;
137
+ }
138
+
139
+ if (ctrl.signal.aborted || !info || (cfg.enforceIteration && info.iteration !== iteration)) { // If health check fails, or aborted
140
+ this.#log('debug', `Stopping watch for events of type "${type}"`);
75
141
  return;
76
142
  } else {
77
- log('debug', `Restarting watch for events of type "${type}"`);
143
+ this.#log('debug', `Restarting watch for events of type "${type}"`);
78
144
  }
79
145
  }
80
146
  }
81
147
 
82
- /**
83
- * Wait for one of N states to be achieved
84
- */
85
- static async waitForState(ctx: ManifestContext, states: CompilerStateType[], signal?: AbortSignal): Promise<void> {
148
+ /** Wait for one of N states to be achieved */
149
+ async waitForState(states: CompilerStateType[], message?: string, signal?: AbortSignal): Promise<void> {
86
150
  const set = new Set(states);
87
- const existing = await this.getServerInfo(ctx);
88
- log('debug', `Existing: ${JSON.stringify(existing)}`);
89
- if (existing && set.has(existing.state)) {
90
- log('debug', `Waited for state, ${existing.state} in server info`);
91
- return;
92
- }
93
151
  // Loop until
94
- log('debug', `Waiting for states, ${states.join(', ')}`);
95
- for await (const _ of this.fetchEvents(ctx, 'state', signal, s => set.has(s.state))) { }
96
- log('debug', `Found state, one of ${states.join(', ')}`);
97
- }
98
-
99
- /**
100
- * Stream logs
101
- */
102
- static async streamLogs(ctx: ManifestContext, signal?: AbortSignal): Promise<void> {
103
- if (!LogUtil.logLevel) {
104
- return;
105
- }
106
- for await (const ev of this.fetchEvents(ctx, 'log', signal!)) {
107
- LogUtil.sendLogEventToConsole(ev);
152
+ this.#log('debug', `Waiting for states, ${states.join(', ')}`);
153
+ for await (const _ of this.fetchEvents('state', { signal, until: s => set.has(s.state) })) { }
154
+ this.#log('debug', `Found state, one of ${states.join(', ')} `);
155
+ if (message) {
156
+ this.#log('info', message);
108
157
  }
109
158
  }
110
-
111
- /**
112
- * Wait for build
113
- */
114
- static async waitForBuild(ctx: ManifestContext, signal?: AbortSignal): Promise<void> {
115
- await this.waitForState(ctx, ['compile-end', 'watch-start'], signal);
116
- log('info', 'Successfully built');
117
- }
118
159
  }
@@ -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
+ }
@@ -1,42 +1,32 @@
1
- import cp from 'child_process';
2
- import path from 'path';
1
+ import cp from 'node:child_process';
2
+ import path from 'node:path';
3
+ import { rmSync } from 'node:fs';
3
4
 
4
- import type { ManifestContext, ManifestRoot, DeltaEvent } from '@travetto/manifest';
5
+ import type { ManifestContext, DeltaEvent } from '@travetto/manifest';
5
6
 
6
- import type { CompilerOp, CompilerProgressEvent, CompilerServerEvent } from '../types';
7
+ import type { CompilerEvent, CompilerMode } from '../types';
7
8
  import { AsyncQueue } from '../queue';
8
9
  import { LogUtil } from '../log';
9
10
  import { CommonUtil } from '../util';
10
- import { CompilerClientUtil } from './client';
11
11
 
12
- const log = LogUtil.log.bind(null, 'compiler-exec');
12
+ const log = LogUtil.logger('compiler-exec');
13
+ const isEvent = (msg: unknown): msg is CompilerEvent => !!msg && typeof msg === 'object' && 'type' in msg;
13
14
 
14
15
  /**
15
16
  * Running the compiler
16
17
  */
17
18
  export class CompilerRunner {
18
19
 
19
- /**
20
- * Track compiler progress
21
- */
22
- static async trackProgress(ctx: ManifestContext, src: AsyncIterable<CompilerProgressEvent>): Promise<void> {
23
- const compiler = path.resolve(ctx.workspacePath, ctx.compilerFolder);
24
- const main = path.resolve(compiler, 'node_modules', '@travetto/terminal/__index__.js');
25
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
26
- const { GlobalTerminal } = await import(main) as typeof import('@travetto/terminal');
27
-
28
- await GlobalTerminal.init();
29
- await GlobalTerminal.trackProgress(src,
30
- x => ({ ...x, text: `Compiling [%idx/%total] -- ${x.message}` }),
31
- { position: 'bottom', minDelay: 50, staticMessage: 'Compiling' }
32
- );
33
- }
34
-
35
20
  /**
36
21
  * Run compile process
37
22
  */
38
- static async* runProcess(ctx: ManifestContext, manifest: ManifestRoot, changed: DeltaEvent[], op: CompilerOp, signal: AbortSignal): AsyncIterable<CompilerServerEvent> {
39
- const watch = op === 'watch';
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';
40
30
  if (!changed.length && !watch) {
41
31
  yield { type: 'state', payload: { state: 'compile-end' } };
42
32
  log('debug', 'Skipped');
@@ -45,63 +35,46 @@ export class CompilerRunner {
45
35
  log('debug', `Started watch=${watch} changed=${changed.slice(0, 10).map(x => `${x.module}/${x.file}`)}`);
46
36
  }
47
37
 
48
- // Track progress if not in run mode
49
- if (op !== 'run') {
50
- this.trackProgress(ctx, CompilerClientUtil.fetchEvents(ctx, 'progress', signal, ev => !!ev.complete));
51
- }
52
-
53
- const compiler = path.resolve(ctx.workspacePath, ctx.compilerFolder);
54
- const main = path.resolve(compiler, 'node_modules', '@travetto/compiler/support/entry.compiler.js');
55
- const deltaFile = path.resolve(ctx.workspacePath, ctx.toolFolder, 'manifest-delta.json');
56
-
57
- const changedFiles = changed[0]?.file === '*' ? ['*'] : changed.map(ev =>
58
- path.resolve(manifest.workspacePath, manifest.modules[ev.module].sourceFolder, ev.file)
59
- );
38
+ const main = path.resolve(ctx.workspace.path, ctx.build.compilerFolder, 'node_modules', '@travetto/compiler/support/entry.compiler.js');
39
+ const deltaFile = path.resolve(ctx.workspace.path, ctx.build.compilerFolder, `manifest-delta-${Date.now()}.json`);
60
40
 
61
- let proc: cp.ChildProcess | undefined;
62
- let kill: (() => void) | undefined;
41
+ const changedFiles = changed[0]?.file === '*' ? ['*'] : changed.map(ev => ev.sourceFile);
63
42
 
64
- const queue = new AsyncQueue<CompilerServerEvent>(signal);
43
+ const queue = new AsyncQueue<CompilerEvent>();
65
44
 
66
45
  try {
67
46
  await CommonUtil.writeTextFile(deltaFile, changedFiles.join('\n'));
68
47
 
69
48
  log('info', 'Launching compiler');
70
- proc = cp.spawn(process.argv0, [main, deltaFile, `${watch}`], {
49
+ const proc = cp.spawn(process.argv0, [main, deltaFile, `${watch}`], {
71
50
  env: {
72
51
  ...process.env,
73
- TRV_MANIFEST: path.resolve(ctx.workspacePath, ctx.outputFolder, 'node_modules', ctx.mainModule),
52
+ TRV_MANIFEST: path.resolve(ctx.workspace.path, ctx.build.outputFolder, 'node_modules', ctx.workspace.name),
74
53
  },
75
- stdio: ['pipe', 'pipe', 2, 'ipc'],
54
+ detached: true,
55
+ stdio: ['pipe', 1, 2, 'ipc'],
76
56
  })
77
- .on('message', msg => {
78
- if (msg && typeof msg === 'object' && 'type' in msg) {
79
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
80
- queue.add(msg as CompilerServerEvent);
81
- }
82
- })
83
- .on('exit', code => {
84
- if (code !== null && code > 0) {
85
- log('error', 'Failed during compilation');
86
- }
87
- queue.close();
88
- });
89
-
90
- kill = (): void => { proc?.kill('SIGKILL'); };
57
+ .on('message', msg => isEvent(msg) && queue.add(msg))
58
+ .on('exit', () => queue.close());
59
+
60
+ const kill = (): unknown => {
61
+ log('debug', 'Shutting down process');
62
+ return (proc.connected ? proc.send('shutdown', (err) => proc.kill()) : proc.kill());
63
+ };
64
+
65
+ process.once('SIGINT', kill);
91
66
  signal.addEventListener('abort', kill);
92
- process.on('exit', kill);
93
67
 
94
68
  yield* queue;
95
69
 
96
- log('debug', `exit code: ${proc?.exitCode}`);
70
+ if (proc.exitCode !== 0) {
71
+ log('error', `Terminated during compilation, code=${proc.exitCode}, killed=${proc.killed}`);
72
+ }
73
+ process.off('SIGINT', kill);
74
+
97
75
  log('debug', 'Finished');
98
76
  } finally {
99
- if (proc?.killed === false) {
100
- proc.kill('SIGKILL');
101
- }
102
- if (kill) {
103
- process.off('exit', kill);
104
- }
77
+ rmSync(deltaFile, { force: true });
105
78
  }
106
79
  }
107
80
  }