@travetto/compiler 8.0.0-alpha.2 → 8.0.0-alpha.21

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.
@@ -1,13 +1,13 @@
1
- import rl from 'node:readline/promises';
2
- import timers from 'node:timers/promises';
3
1
  import fs from 'node:fs/promises';
4
2
  import http, { Agent } from 'node:http';
3
+ import rl from 'node:readline/promises';
4
+ import timers from 'node:timers/promises';
5
5
 
6
6
  import type { ManifestContext } from '@travetto/manifest';
7
7
 
8
- import type { CompilerEventPayload, CompilerEventType, CompilerServerInfo, CompilerStateType } from '../types.ts';
9
- import type { LogShape } from '../log.ts';
10
8
  import { CommonUtil } from '../common.ts';
9
+ import type { LogShape } from '../log.ts';
10
+ import type { CompilerEventPayload, CompilerEventType, CompilerServerInfo, CompilerStateType } from '../types.ts';
11
11
  import { ProcessHandle } from './process-handle.ts';
12
12
 
13
13
  type FetchEventsConfig<T> = {
@@ -26,7 +26,6 @@ const streamAgent = new Agent({
26
26
  * Compiler Client Operations
27
27
  */
28
28
  export class CompilerClient {
29
-
30
29
  #url: string;
31
30
  #log: LogShape;
32
31
  #handle: Record<'compiler' | 'server', ProcessHandle>;
@@ -47,17 +46,18 @@ export class CompilerClient {
47
46
  return this.#url;
48
47
  }
49
48
 
50
- async #fetch(urlPath: string, options?: RequestInit & { timeout?: number }, logTimeout = true): Promise<{ ok: boolean, text: string }> {
49
+ async #fetch(urlPath: string, options?: RequestInit & { timeout?: number }, logTimeout = true): Promise<{ ok: boolean; text: string }> {
51
50
  const controller = new AbortController();
52
51
  const timeoutController = new AbortController();
53
52
 
54
53
  options?.signal?.addEventListener('abort', () => controller.abort());
55
- timers.setTimeout(options?.timeout ?? 100, undefined, { ref: false, signal: timeoutController.signal })
54
+ timers
55
+ .setTimeout(options?.timeout ?? 100, undefined, { ref: false, signal: timeoutController.signal })
56
56
  .then(() => {
57
57
  logTimeout && this.#log.error(`Timeout on request to ${this.#url}${urlPath}`);
58
58
  controller.abort('TIMEOUT');
59
59
  })
60
- .catch(() => { });
60
+ .catch(() => {});
61
61
  const response = await fetch(`${this.#url}${urlPath}`, { ...options, signal: controller.signal });
62
62
  const out = { ok: response.ok, text: await response.text() };
63
63
  timeoutController.abort();
@@ -66,7 +66,10 @@ export class CompilerClient {
66
66
 
67
67
  /** Get server information, if server is running */
68
68
  info(): Promise<CompilerServerInfo | undefined> {
69
- return this.#fetch('/info', { timeout: 200 }, false).then(response => JSON.parse(response.text), () => undefined);
69
+ return this.#fetch('/info', { timeout: 200 }, false).then(
70
+ response => JSON.parse(response.text),
71
+ () => undefined
72
+ );
70
73
  }
71
74
 
72
75
  async isWatching(): Promise<boolean> {
@@ -75,13 +78,19 @@ export class CompilerClient {
75
78
 
76
79
  /** Clean the server */
77
80
  async clean(forceOnFailure?: boolean): Promise<boolean> {
78
- const result = await this.#fetch('/clean', { timeout: 300 }).then(response => response.ok, () => false);
81
+ const result = await this.#fetch('/clean', { timeout: 300 }).then(
82
+ response => response.ok,
83
+ () => false
84
+ );
79
85
  if (!result && forceOnFailure) {
80
86
  this.#log.warn('Clean request failed, forcing cleanup');
81
87
  try {
82
- await Promise.all([this.#ctx.build.outputFolder, this.#ctx.build.typesFolder]
83
- .map(file => fs.rm(CommonUtil.resolveWorkspace(this.#ctx, file), { force: true, recursive: true })));
84
- } catch { }
88
+ await Promise.all(
89
+ [this.#ctx.build.outputFolder, this.#ctx.build.typesFolder].map(file =>
90
+ fs.rm(CommonUtil.resolveWorkspace(this.#ctx, file), { force: true, recursive: true })
91
+ )
92
+ );
93
+ } catch {}
85
94
  }
86
95
  return result;
87
96
  }
@@ -91,18 +100,20 @@ export class CompilerClient {
91
100
  const info = await this.info();
92
101
  if (!info) {
93
102
  this.#log.debug('Stopping server, info not found, manual killing');
94
- return Promise.all([this.#handle.server.kill(), this.#handle.compiler.kill()])
95
- .then(results => results.some(result => result));
103
+ return Promise.all([this.#handle.server.kill(), this.#handle.compiler.kill()]).then(results => results.some(result => result));
96
104
  }
97
105
 
98
- await this.#fetch('/stop').catch(() => { }); // Trigger
106
+ await this.#fetch('/stop').catch(() => {}); // Trigger
99
107
  this.#log.debug('Waiting for compiler to exit');
100
108
  await this.#handle.compiler.ensureKilled();
101
109
  return true;
102
110
  }
103
111
 
104
112
  /** Fetch compiler events */
105
- async * fetchEvents<V extends CompilerEventType, T extends CompilerEventPayload<V>>(type: V, config: FetchEventsConfig<T> = {}): AsyncIterable<T> {
113
+ async *fetchEvents<V extends CompilerEventType, T extends CompilerEventPayload<V>>(
114
+ type: V,
115
+ config: FetchEventsConfig<T> = {}
116
+ ): AsyncIterable<T> {
106
117
  let info = await this.info();
107
118
  if (!info) {
108
119
  return;
@@ -121,14 +132,13 @@ export class CompilerClient {
121
132
 
122
133
  const { iteration } = info;
123
134
 
124
- for (; ;) {
135
+ for (;;) {
125
136
  const controller = new AbortController();
126
137
  const quit = (): void => controller.abort();
127
138
  try {
128
139
  signal.addEventListener('abort', quit);
129
140
  const response = await new Promise<http.IncomingMessage>((resolve, reject) =>
130
- http.get(`${this.#url}/event/${type}`, { agent: streamAgent, signal: controller.signal }, resolve)
131
- .on('error', reject)
141
+ http.get(`${this.#url}/event/${type}`, { agent: streamAgent, signal: controller.signal }, resolve).on('error', reject)
132
142
  );
133
143
 
134
144
  for await (const line of rl.createInterface(response)) {
@@ -143,7 +153,9 @@ export class CompilerClient {
143
153
  }
144
154
  } catch (error) {
145
155
  const aborted = controller.signal.aborted || (typeof error === 'object' && error && 'code' in error && error.code === 'ECONNRESET');
146
- if (!aborted) { throw error; }
156
+ if (!aborted) {
157
+ throw error;
158
+ }
147
159
  }
148
160
  signal.removeEventListener('abort', quit);
149
161
 
@@ -156,7 +168,8 @@ export class CompilerClient {
156
168
  return;
157
169
  }
158
170
 
159
- if (controller.signal.aborted || !info || (config.enforceIteration && info.iteration !== iteration)) { // If health check fails, or aborted
171
+ if (controller.signal.aborted || !info || (config.enforceIteration && info.iteration !== iteration)) {
172
+ // If health check fails, or aborted
160
173
  this.#log.debug(`Stopping watch for events of type "${type}"`);
161
174
  return;
162
175
  } else {
@@ -170,10 +183,11 @@ export class CompilerClient {
170
183
  const set = new Set(states);
171
184
  // Loop until
172
185
  this.#log.debug(`Waiting for states, ${states.join(', ')}`);
173
- for await (const _ of this.fetchEvents('state', { signal, until: event => set.has(event.state) })) { }
186
+ for await (const _ of this.fetchEvents('state', { signal, until: event => set.has(event.state) })) {
187
+ }
174
188
  this.#log.debug(`Found state, one of ${states.join(', ')} `);
175
189
  if (message) {
176
190
  this.#log.info(message);
177
191
  }
178
192
  }
179
- }
193
+ }
@@ -2,11 +2,11 @@ import { spawn } from 'node:child_process';
2
2
 
3
3
  import type { ManifestContext } from '@travetto/manifest';
4
4
 
5
- import type { CompilerEvent, CompilerLogLevel } from '../types.ts';
6
- import { AsyncQueue } from '../queue.ts';
7
- import { Log } from '../log.ts';
8
5
  import { CommonUtil } from '../common.ts';
9
6
  import { EventUtil } from '../event.ts';
7
+ import { Log } from '../log.ts';
8
+ import { AsyncQueue } from '../queue.ts';
9
+ import type { CompilerEvent, CompilerLogLevel } from '../types.ts';
10
10
  import type { CompilerClient } from './client.ts';
11
11
  import { CompilerServer } from './server.ts';
12
12
 
@@ -17,7 +17,7 @@ const log = Log.scoped('compiler-exec');
17
17
  */
18
18
  export class CompilerManager {
19
19
  /** Run compile process */
20
- static async * #runTarget(ctx: ManifestContext, watching: boolean, signal: AbortSignal): AsyncIterable<CompilerEvent> {
20
+ static async *#runTarget(ctx: ManifestContext, watching: boolean, signal: AbortSignal): AsyncIterable<CompilerEvent> {
21
21
  if (signal.aborted) {
22
22
  log.debug('Skipping, shutting down');
23
23
  return;
@@ -30,17 +30,17 @@ export class CompilerManager {
30
30
  env: {
31
31
  ...process.env,
32
32
  TRV_COMPILER_WATCH: String(watching),
33
- TRV_MANIFEST: CommonUtil.resolveWorkspace(ctx, ctx.build.outputFolder, 'node_modules', ctx.workspace.name),
33
+ TRV_MANIFEST: CommonUtil.resolveWorkspace(ctx, ctx.build.outputFolder, 'node_modules', ctx.workspace.name)
34
34
  },
35
35
  detached: true,
36
- stdio: ['pipe', 1, 2, 'ipc'],
36
+ stdio: ['pipe', 1, 2, 'ipc']
37
37
  })
38
38
  .on('message', message => EventUtil.isCompilerEvent(message) && queue.add(message))
39
39
  .on('exit', () => queue.close());
40
40
 
41
41
  const kill = (): unknown => {
42
42
  log.debug('Shutting down process');
43
- return (subProcess.connected ? subProcess.send('shutdown', () => subProcess.kill()) : subProcess.kill());
43
+ return subProcess.connected ? subProcess.send('shutdown', () => subProcess.kill()) : subProcess.kill();
44
44
  };
45
45
 
46
46
  process.once('SIGINT', kill);
@@ -48,8 +48,9 @@ export class CompilerManager {
48
48
 
49
49
  yield* queue;
50
50
 
51
- if (subProcess.exitCode !== 0) {
51
+ if (subProcess.exitCode !== 0 && subProcess.exitCode) {
52
52
  log.error(`Terminated during compilation, code=${subProcess.exitCode}, killed=${subProcess.killed}`);
53
+ process.exitCode = subProcess.exitCode;
53
54
  }
54
55
  process.off('SIGINT', kill);
55
56
 
@@ -57,11 +58,15 @@ export class CompilerManager {
57
58
  }
58
59
 
59
60
  /** Main entry point for compilation */
60
- static async compile(ctx: ManifestContext, client: CompilerClient, config: { watch?: boolean, logLevel?: CompilerLogLevel, forceRestart?: boolean }): Promise<void> {
61
+ static async compile(
62
+ ctx: ManifestContext,
63
+ client: CompilerClient,
64
+ config: { watch?: boolean; logLevel?: CompilerLogLevel; forceRestart?: boolean }
65
+ ): Promise<void> {
61
66
  Log.initLevel(config.logLevel ?? 'info');
62
67
  const watch = !!config.watch;
63
68
 
64
- if (config.forceRestart && await client.stop()) {
69
+ if (config.forceRestart && (await client.stop())) {
65
70
  log.info('Stopped existing server');
66
71
  }
67
72
 
@@ -83,8 +88,9 @@ export class CompilerManager {
83
88
 
84
89
  /** Compile only if necessary */
85
90
  static async compileIfNecessary(ctx: ManifestContext, client: CompilerClient): Promise<void> {
86
- if (!(await client.isWatching())) { // Short circuit if we can
91
+ if (!(await client.isWatching())) {
92
+ // Short circuit if we can
87
93
  await this.compile(ctx, client, { watch: false, logLevel: 'error' });
88
94
  }
89
95
  }
90
- }
96
+ }
@@ -3,11 +3,10 @@ import path from 'node:path';
3
3
 
4
4
  import type { ManifestContext } from '@travetto/manifest';
5
5
 
6
- import { Log, type Logger } from '../log.ts';
7
6
  import { CommonUtil } from '../common.ts';
7
+ import { Log, type Logger } from '../log.ts';
8
8
 
9
9
  export class ProcessHandle {
10
-
11
10
  #file: string;
12
11
  #log: Logger;
13
12
 
@@ -22,13 +21,17 @@ export class ProcessHandle {
22
21
  }
23
22
 
24
23
  getProcessId(): Promise<number | undefined> {
25
- return fs.readFile(this.#file, 'utf8')
26
- .then(processId => +processId > 0 ? +processId : undefined, () => undefined);
24
+ return fs.readFile(this.#file, 'utf8').then(
25
+ processId => (+processId > 0 ? +processId : undefined),
26
+ () => undefined
27
+ );
27
28
  }
28
29
 
29
30
  async isRunning(): Promise<boolean> {
30
31
  const processId = await this.getProcessId();
31
- if (!processId) { return false; }
32
+ if (!processId) {
33
+ return false;
34
+ }
32
35
  try {
33
36
  process.kill(processId, 0); // See if process is still running
34
37
  this.#log.debug('Is running', processId);
@@ -41,11 +44,11 @@ export class ProcessHandle {
41
44
 
42
45
  async kill(): Promise<boolean> {
43
46
  const processId = await this.getProcessId();
44
- if (processId && await this.isRunning()) {
47
+ if (processId && (await this.isRunning())) {
45
48
  try {
46
49
  this.#log.debug('Killing', processId);
47
50
  return process.kill(processId);
48
- } catch { }
51
+ } catch {}
49
52
  }
50
53
  return false;
51
54
  }
@@ -58,8 +61,9 @@ export class ProcessHandle {
58
61
  }
59
62
 
60
63
  this.#log.debug('Ensuring Killed', processId);
61
- while ((Date.now() - start) < gracePeriod) { // Ensure its done
62
- if (!await this.isRunning()) {
64
+ while (Date.now() - start < gracePeriod) {
65
+ // Ensure its done
66
+ if (!(await this.isRunning())) {
63
67
  return true;
64
68
  }
65
69
  await CommonUtil.blockingTimeout(100);
@@ -67,7 +71,7 @@ export class ProcessHandle {
67
71
  try {
68
72
  this.#log.debug('Force Killing', processId);
69
73
  process.kill(processId); // Force kill
70
- } catch { }
74
+ } catch {}
71
75
  this.#log.debug('Did Kill', this.#file, !!processId);
72
76
  return true;
73
77
  }
@@ -1,15 +1,15 @@
1
- import http from 'node:http';
2
- import fs from 'node:fs/promises';
3
1
  import { setMaxListeners } from 'node:events';
2
+ import fs from 'node:fs/promises';
3
+ import http from 'node:http';
4
4
 
5
5
  import type { ManifestContext } from '@travetto/manifest';
6
6
 
7
- import type { CompilerProgressEvent, CompilerEvent, CompilerEventType, CompilerServerInfo } from '../types.ts';
8
- import { Log } from '../log.ts';
9
7
  import { CommonUtil } from '../common.ts';
8
+ import { EventUtil } from '../event.ts';
9
+ import { Log } from '../log.ts';
10
+ import type { CompilerEvent, CompilerEventType, CompilerProgressEvent, CompilerServerInfo } from '../types.ts';
10
11
  import { CompilerClient } from './client.ts';
11
12
  import { ProcessHandle } from './process-handle.ts';
12
- import { EventUtil } from '../event.ts';
13
13
 
14
14
  const log = Log.scoped('server');
15
15
 
@@ -17,7 +17,6 @@ const log = Log.scoped('server');
17
17
  * Compiler Server Class
18
18
  */
19
19
  export class CompilerServer {
20
-
21
20
  #ctx: ManifestContext;
22
21
  #server: http.Server;
23
22
  #listenersAll = new Set<http.ServerResponse>();
@@ -27,12 +26,14 @@ export class CompilerServer {
27
26
  #client: CompilerClient;
28
27
  #url: string;
29
28
  #handle: Record<'compiler' | 'server', ProcessHandle>;
29
+ #suppressLogs: boolean;
30
30
 
31
- constructor(ctx: ManifestContext, watching: boolean) {
31
+ constructor(ctx: ManifestContext, watching: boolean, suppressLogs?: boolean) {
32
32
  this.#ctx = ctx;
33
33
  this.#client = new CompilerClient(ctx, Log.scoped('server.client'));
34
34
  this.#url = this.#client.url;
35
35
  this.#handle = { server: new ProcessHandle(ctx, 'server'), compiler: new ProcessHandle(ctx, 'compiler') };
36
+ this.#suppressLogs = suppressLogs ?? /^(1|true|on|yes)$/i.test(process.env.TRV_QUIET ?? '');
36
37
 
37
38
  this.info = {
38
39
  state: 'startup',
@@ -44,11 +45,14 @@ export class CompilerServer {
44
45
  url: this.#url
45
46
  };
46
47
 
47
- this.#server = http.createServer({
48
- keepAlive: true,
49
- requestTimeout: 1000 * 60 * 60,
50
- keepAliveTimeout: 1000 * 60 * 60,
51
- }, (request, response) => this.#onRequest(request, response));
48
+ this.#server = http.createServer(
49
+ {
50
+ keepAlive: true,
51
+ requestTimeout: 1000 * 60 * 60,
52
+ keepAliveTimeout: 1000 * 60 * 60
53
+ },
54
+ (request, response) => this.#onRequest(request, response)
55
+ );
52
56
 
53
57
  setMaxListeners(1000, this.signal);
54
58
  }
@@ -72,7 +76,7 @@ export class CompilerServer {
72
76
  .on('error', async error => {
73
77
  if ('code' in error && error.code === 'EADDRINUSE') {
74
78
  const info = await this.#client.info();
75
- resolve((info && !info.watching && this.watching) ? 'retry' : 'running');
79
+ resolve(info && !info.watching && this.watching ? 'retry' : 'running');
76
80
  } else {
77
81
  log.warn('Failed in running server', error);
78
82
  reject(error);
@@ -104,7 +108,8 @@ export class CompilerServer {
104
108
  const id = `id_${Date.now()}_${Math.random()}`.replace('.', '1');
105
109
  (this.#listeners[type] ??= {})[id] = response;
106
110
  this.#listenersAll.add(response);
107
- if (type === 'state' || type === 'all') { // Send on initial connect
111
+ if (type === 'state' || type === 'all') {
112
+ // Send on initial connect
108
113
  this.#emitEvent({ type: 'state', payload: { state: this.info.state } }, id);
109
114
  } else {
110
115
  response.write('\n'); // Send at least one byte on listen
@@ -144,15 +149,20 @@ export class CompilerServer {
144
149
  this.info.iteration = Date.now();
145
150
  await CommonUtil.blockingTimeout(20);
146
151
  for (const listener of this.#listenersAll) {
147
- try { listener.end(); } catch { }
152
+ try {
153
+ listener.end();
154
+ } catch {}
148
155
  }
149
156
  this.#listeners = {}; // Ensure its empty
150
157
  this.#listenersAll.clear();
151
158
  }
152
159
 
153
160
  async #clean(): Promise<{ clean: boolean }> {
154
- await Promise.all([this.#ctx.build.outputFolder, this.#ctx.build.typesFolder]
155
- .map(folder => fs.rm(CommonUtil.resolveWorkspace(this.#ctx, folder), { recursive: true, force: true })));
161
+ await Promise.all(
162
+ [this.#ctx.build.outputFolder, this.#ctx.build.typesFolder].map(folder =>
163
+ fs.rm(CommonUtil.resolveWorkspace(this.#ctx, folder), { recursive: true, force: true })
164
+ )
165
+ );
156
166
  return { clean: true };
157
167
  }
158
168
 
@@ -173,10 +183,16 @@ export class CompilerServer {
173
183
  }
174
184
  return;
175
185
  }
176
- case 'clean': out = await this.#clean(); break;
177
- case 'stop': out = JSON.stringify({ closing: true }); close = true; break;
178
- case 'info':
179
- default: out = this.info ?? {}; break;
186
+ case 'clean':
187
+ out = await this.#clean();
188
+ break;
189
+ case 'stop':
190
+ out = JSON.stringify({ closing: true });
191
+ close = true;
192
+ break;
193
+ default:
194
+ out = this.info ?? {};
195
+ break;
180
196
  }
181
197
  response.end(JSON.stringify(out));
182
198
  if (close) {
@@ -197,7 +213,12 @@ export class CompilerServer {
197
213
 
198
214
  if (event.type === 'state') {
199
215
  this.info.state = event.payload.state;
200
- if (event.payload.state === 'init' && event.payload.extra && 'processId' in event.payload.extra && typeof event.payload.extra.processId === 'number') {
216
+ if (
217
+ event.payload.state === 'init' &&
218
+ event.payload.extra &&
219
+ 'processId' in event.payload.extra &&
220
+ typeof event.payload.extra.processId === 'number'
221
+ ) {
201
222
  if (this.info.watching && !this.info.compilerProcessId) {
202
223
  // Ensure we are killing in watch mode on first set
203
224
  await this.#handle.compiler.kill();
@@ -207,7 +228,9 @@ export class CompilerServer {
207
228
  }
208
229
  log.info(`State changed: ${this.info.state}`);
209
230
  } else if (event.type === 'log') {
210
- log.render(event.payload);
231
+ if (!this.#suppressLogs) {
232
+ log.render(event.payload);
233
+ }
211
234
  }
212
235
  if (this.isResetEvent(event)) {
213
236
  await this.#disconnectActive();
@@ -243,7 +266,8 @@ export class CompilerServer {
243
266
  this.#shutdown.abort();
244
267
  });
245
268
  });
246
- } catch { // Timeout or other error
269
+ } catch {
270
+ // Timeout or other error
247
271
  // Force shutdown
248
272
  this.#server.closeAllConnections();
249
273
  await this.#handle.compiler.kill();
@@ -256,7 +280,7 @@ export class CompilerServer {
256
280
  * Start the server listening
257
281
  */
258
282
  async listen(): Promise<CompilerServer | undefined> {
259
- const running = await this.#tryListen() === 'ok';
283
+ const running = (await this.#tryListen()) === 'ok';
260
284
  log.info(running ? 'Starting server' : 'Server already running under a different process', this.#url);
261
285
  return running ? this : undefined;
262
286
  }