@travetto/compiler 4.0.0-rc.4 → 4.0.0-rc.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/compiler",
3
- "version": "4.0.0-rc.4",
3
+ "version": "4.0.0-rc.6",
4
4
  "description": "The compiler infrastructure for the Travetto framework",
5
5
  "keywords": [
6
6
  "compiler",
@@ -31,12 +31,12 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@parcel/watcher": "^2.4.0",
34
- "@travetto/manifest": "^4.0.0-rc.4",
35
- "@travetto/transformer": "^4.0.0-rc.4",
34
+ "@travetto/manifest": "^4.0.0-rc.6",
35
+ "@travetto/transformer": "^4.0.0-rc.6",
36
36
  "@types/node": "^20.11.16"
37
37
  },
38
38
  "peerDependencies": {
39
- "@travetto/cli": "^4.0.0-rc.5"
39
+ "@travetto/cli": "^4.0.0-rc.7"
40
40
  },
41
41
  "peerDependenciesMeta": {
42
42
  "@travetto/cli": {
package/src/compiler.ts CHANGED
@@ -1,4 +1,3 @@
1
- import ts from 'typescript';
2
1
  import timers from 'node:timers/promises';
3
2
  import fs from 'node:fs/promises';
4
3
  import { setMaxListeners } from 'node:events';
@@ -9,7 +8,7 @@ import { CompilerUtil } from './util';
9
8
  import { CompilerState } from './state';
10
9
  import { CompilerWatcher } from './watch';
11
10
  import { Log } from './log';
12
- import { CompileEmitError, CompileEmitEvent, CompileEmitter } from './types';
11
+ import { CompileEmitEvent, CompileEmitter } from './types';
13
12
  import { EventUtil } from './event';
14
13
 
15
14
  /**
@@ -70,7 +69,7 @@ export class Compiler {
70
69
  break;
71
70
  }
72
71
  case 'reset': {
73
- Log.info('Triggering reset due to change in core files');
72
+ Log.info('Triggering reset due to change in core files', err?.cause);
74
73
  EventUtil.sendEvent('state', { state: 'reset' });
75
74
  process.exitCode = 0;
76
75
  break;
@@ -86,29 +85,8 @@ export class Compiler {
86
85
  /**
87
86
  * Compile in a single pass, only emitting dirty files
88
87
  */
89
- async getCompiler(): Promise<CompileEmitter> {
90
- let program: ts.Program;
91
-
92
- const emit = async (inputFile: string, needsNewProgram = program === undefined): Promise<CompileEmitError | undefined> => {
93
- try {
94
- if (needsNewProgram) {
95
- program = this.#state.createProgram(program);
96
- }
97
- await timers.setImmediate();
98
- const result = this.#state.writeInputFile(program, inputFile);
99
- if (result?.diagnostics?.length) {
100
- return result.diagnostics;
101
- }
102
- } catch (err) {
103
- if (err instanceof Error) {
104
- return err;
105
- } else {
106
- throw err;
107
- }
108
- }
109
- };
110
-
111
- return emit;
88
+ getCompiler(): CompileEmitter {
89
+ return (inputFile: string, needsNewProgram?: boolean) => this.#state.writeInputFile(inputFile, needsNewProgram);
112
90
  }
113
91
 
114
92
  /**
package/src/state.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import ts from 'typescript';
2
+ import timers from 'node:timers/promises';
2
3
 
3
4
  import { path, ManifestModuleUtil, ManifestModule, ManifestRoot, ManifestIndex } from '@travetto/manifest';
4
5
  import { TransformerManager } from '@travetto/transformer';
@@ -6,7 +7,7 @@ import { TransformerManager } from '@travetto/transformer';
6
7
  import { CommonUtil } from '../support/util';
7
8
 
8
9
  import { CompilerUtil } from './util';
9
- import { CompileStateEntry } from './types';
10
+ import { CompileEmitError, CompileStateEntry } from './types';
10
11
 
11
12
  function folderMapper(root: string, prefix: string): { dir: string, translate: (val: string) => string } {
12
13
  let matched: string = '~~';
@@ -44,6 +45,7 @@ export class CompilerState implements ts.CompilerHost {
44
45
  #modules: ManifestModule[];
45
46
  #transformerManager: TransformerManager;
46
47
  #compilerOptions: ts.CompilerOptions;
48
+ #program: ts.Program;
47
49
 
48
50
  #readFile(inputFile: string): string | undefined {
49
51
  return ts.sys.readFile(this.#inputToEntry.get(inputFile)?.sourceFile ?? this.#inputPathToSourcePath(inputFile));
@@ -104,24 +106,40 @@ export class CompilerState implements ts.CompilerHost {
104
106
  return this.getBySource(this.#manifestIndex.getModule('@travetto/manifest')!.files.src[0].sourceFile)!.inputFile;
105
107
  }
106
108
 
107
- createProgram(oldProgram?: ts.Program): ts.Program {
108
- const prog = ts.createProgram({ rootNames: this.getAllFiles(), host: this, options: this.#compilerOptions, oldProgram });
109
- this.#transformerManager.init(prog.getTypeChecker());
110
- return prog;
111
- }
112
-
113
- writeInputFile(program: ts.Program, inputFile: string): ts.EmitResult | undefined | void {
114
- switch (ManifestModuleUtil.getFileType(inputFile)) {
115
- case 'package-json':
116
- return this.writeFile(this.#inputToEntry.get(inputFile)!.outputFile!, this.readFile(inputFile)!, false);
117
- case 'js':
118
- return this.writeFile(this.#inputToEntry.get(inputFile)!.outputFile!, ts.transpile(this.readFile(inputFile)!, this.#compilerOptions), false);
119
- case 'ts':
120
- return program.emit(
121
- program.getSourceFile(inputFile)!,
122
- (...args) => this.writeFile(...args), undefined, false,
123
- this.#transformerManager.get()
124
- );
109
+ async createProgram(force = false): Promise<ts.Program> {
110
+ if (force || !this.#program) {
111
+ this.#program = ts.createProgram({ rootNames: this.getAllFiles(), host: this, options: this.#compilerOptions, oldProgram: this.#program });
112
+ this.#transformerManager.init(this.#program.getTypeChecker());
113
+ await timers.setImmediate();
114
+ }
115
+ return this.#program;
116
+ }
117
+
118
+ async writeInputFile(inputFile: string, needsNewProgram = false): Promise<CompileEmitError | undefined> {
119
+ const program = await this.createProgram(needsNewProgram);
120
+ try {
121
+ switch (ManifestModuleUtil.getFileType(inputFile)) {
122
+ case 'package-json':
123
+ this.writeFile(this.#inputToEntry.get(inputFile)!.outputFile!, this.readFile(inputFile)!, false), undefined;
124
+ break;
125
+ case 'js':
126
+ this.writeFile(this.#inputToEntry.get(inputFile)!.outputFile!, ts.transpile(this.readFile(inputFile)!, this.#compilerOptions), false);
127
+ break;
128
+ case 'ts': {
129
+ const result = program.emit(
130
+ program.getSourceFile(inputFile)!,
131
+ (...args) => this.writeFile(...args), undefined, false,
132
+ this.#transformerManager.get()
133
+ );
134
+ return result?.diagnostics?.length ? result.diagnostics : undefined;
135
+ }
136
+ }
137
+ } catch (err) {
138
+ if (err instanceof Error) {
139
+ return err;
140
+ } else {
141
+ throw err;
142
+ }
125
143
  }
126
144
  }
127
145
 
package/src/watch.ts CHANGED
@@ -128,7 +128,7 @@ export class CompilerWatcher {
128
128
  sourceFile === ROOT_PKG ||
129
129
  (action === 'delete' && (sourceFile === OUTPUT_PATH || sourceFile === COMPILER_PATH))
130
130
  ) {
131
- throw new Error('RESET');
131
+ throw new Error('RESET', { cause: `${action}:${sourceFile}` });
132
132
  }
133
133
 
134
134
  const fileType = ManifestModuleUtil.getFileType(sourceFile);
@@ -5,7 +5,7 @@ import path from 'node:path';
5
5
  import type { ManifestContext } from '@travetto/manifest';
6
6
 
7
7
  import type { CompilerLogLevel, CompilerMode, CompilerServerInfo } from './types';
8
- import { LogUtil } from './log';
8
+ import { CompilerLogger } from './log';
9
9
  import { CommonUtil } from './util';
10
10
  import { CompilerSetup } from './setup';
11
11
  import { CompilerServer } from './server/server';
@@ -14,39 +14,39 @@ import { CompilerClient } from './server/client';
14
14
 
15
15
  // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
16
16
  export const main = (ctx: ManifestContext) => {
17
- const log = LogUtil.logger('client.main');
18
- const client = new CompilerClient(ctx, log);
17
+ const client = new CompilerClient(ctx, new CompilerLogger('client'));
19
18
  const buildFolders = [ctx.build.outputFolder, ctx.build.compilerFolder];
20
19
 
21
20
  /** Main entry point for compilation */
22
21
  const compile = async (op: CompilerMode, logLevel: CompilerLogLevel, setupOnly = false): Promise<void> => {
23
- LogUtil.initLogs(ctx, logLevel ?? 'info');
22
+ CompilerLogger.init(ctx, logLevel ?? 'info');
24
23
 
25
24
  const server = await new CompilerServer(ctx, op).listen();
25
+ const log = new CompilerLogger('main');
26
26
 
27
27
  // Wait for build to be ready
28
28
  if (server) {
29
- log('debug', 'Start Server');
29
+ log.debug('Start Server');
30
30
  await server.processEvents(async function* (signal) {
31
31
  const changed = await CompilerSetup.setup(ctx);
32
32
  if (!setupOnly) {
33
33
  yield* CompilerRunner.runProcess(ctx, changed, op, signal);
34
34
  }
35
35
  });
36
- log('debug', 'End Server');
36
+ log.debug('End Server');
37
37
  } else {
38
- log('info', 'Server already running, waiting for initial compile to complete');
38
+ log.info('Server already running, waiting for initial compile to complete');
39
39
  const ctrl = new AbortController();
40
- LogUtil.consumeProgressEvents(() => client.fetchEvents('progress', { until: ev => !!ev.complete, signal: ctrl.signal }));
40
+ log.consumeProgressEvents(() => client.fetchEvents('progress', { until: ev => !!ev.complete, signal: ctrl.signal }));
41
41
  await client.waitForState(['compile-end', 'watch-start'], 'Successfully built');
42
42
  ctrl.abort();
43
43
  }
44
- LogUtil.cleanup();
45
44
  };
46
45
 
47
46
  const ops = {
48
47
  /** Stop the server */
49
48
  async stop(): Promise<void> {
49
+ CompilerLogger.init(ctx);
50
50
  if (await client.stop()) {
51
51
  console.log(`Stopped server ${ctx.workspace.path}: ${client}`);
52
52
  } else {
@@ -62,6 +62,7 @@ export const main = (ctx: ManifestContext) => {
62
62
 
63
63
  /** Clean the server */
64
64
  async clean(): Promise<void> {
65
+ CompilerLogger.init(ctx);
65
66
  if (await client.clean()) {
66
67
  return console.log(`Clean triggered ${ctx.workspace.path}:`, buildFolders);
67
68
  } else {
@@ -72,7 +73,7 @@ export const main = (ctx: ManifestContext) => {
72
73
 
73
74
  /** Stream events */
74
75
  events: async (type: string, handler: (ev: unknown) => unknown): Promise<void> => {
75
- LogUtil.initLogs(ctx, 'error');
76
+ CompilerLogger.init(ctx, 'error');
76
77
  if (type === 'change' || type === 'log' || type === 'progress' || type === 'state') {
77
78
  for await (const ev of client.fetchEvents(type)) { await handler(ev); }
78
79
  } else {
package/support/log.ts CHANGED
@@ -1,112 +1,107 @@
1
1
  import type { ManifestContext } from '@travetto/manifest';
2
2
  import type { CompilerLogEvent, CompilerLogLevel, CompilerProgressEvent } from './types';
3
3
 
4
- export type CompilerLogger = (level: CompilerLogLevel, message: string, ...args: unknown[]) => void;
5
- export type WithLogger<T> = (log: CompilerLogger) => Promise<T>;
6
-
7
- type ProgressWriter = (ev: CompilerProgressEvent) => (unknown | Promise<unknown>);
8
-
9
4
  const LEVEL_TO_PRI: Record<CompilerLogLevel, number> = { debug: 1, info: 2, warn: 3, error: 4 };
10
-
11
5
  const SCOPE_MAX = 15;
12
-
13
6
  const ESC = '\x1b[';
14
7
 
15
- export class LogUtil {
8
+ export class CompilerLogger {
16
9
 
17
- static root = process.cwd();
10
+ static #root = process.cwd();
11
+ static #logLevel: CompilerLogLevel = 'error';
12
+ static #linePartial: boolean | undefined;
18
13
 
19
- static logLevel: CompilerLogLevel = 'error';
14
+ static logProgress = false;
20
15
 
21
- static logProgress?: ProgressWriter;
22
-
23
- static linePartial = false;
24
-
25
- static #rewriteLine(text: string): Promise<void> | void {
16
+ /** Rewrite text line, tracking cleanup as necessary */
17
+ static rewriteLine(text: string): Promise<void> | void {
18
+ if ((!text && !this.#linePartial) || !process.stdout.isTTY) {
19
+ return;
20
+ }
21
+ if (this.#linePartial === undefined) { // First time
22
+ process.stdout.write(`${ESC}?25l`); // Hide cursor
23
+ process.on('exit', () => this.reset());
24
+ }
26
25
  // Move to 1st position, and clear after text
27
26
  const done = process.stdout.write(`${ESC}1G${text}${ESC}0K`);
28
- this.linePartial = !!text;
27
+ this.#linePartial = !!text;
29
28
  if (!done) {
30
29
  return new Promise<void>(r => process.stdout.once('drain', r));
31
30
  }
32
31
  }
33
32
 
33
+ /** Are we in a shell that is interactive */
34
+ static get isInteractiveShell(): boolean {
35
+ return !!process.env.PS1 && process.stdout.isTTY;
36
+ }
37
+
34
38
  /**
35
39
  * Set level for operation
36
40
  */
37
- static initLogs(ctx: ManifestContext, defaultLevel: CompilerLogLevel): void {
41
+ static init(ctx: ManifestContext, defaultLevel?: CompilerLogLevel): void {
38
42
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
39
43
  const build = process.env.TRV_BUILD as CompilerLogLevel | 'none';
40
44
  if (build !== 'none' && process.env.TRV_QUIET !== 'true') {
41
- this.logLevel = build || defaultLevel;
45
+ this.#logLevel = build || defaultLevel;
46
+ this.logProgress = this.isInteractiveShell;
42
47
  }
43
- this.root = ctx.workspace.path;
44
- // If we are in info or a terminal and also in a tty
45
- this.logProgress = ((this.isLevelActive('info') || process.env.PS1) && process.stdout.isTTY) ? this.#logProgressEvent : undefined;
46
- if (this.logProgress) {
47
- process.stdout.write(`${ESC}?25l`); // Hide cursor
48
- }
49
- process.on('exit', () => this.cleanup());
48
+ this.#root = ctx.workspace.path;
50
49
  }
51
50
 
52
- static cleanup(): void {
53
- if (this.logProgress) {
54
- process.stdout.write(`${ESC}!p`); // Reset
51
+ /** Cleanup to restore behavior */
52
+ static reset(): void {
53
+ if (process.stdout.isTTY) {
54
+ process.stdout.write(`${ESC}!p${ESC}?25h`);
55
55
  }
56
56
  }
57
57
 
58
- static #logProgressEvent(ev: CompilerProgressEvent): Promise<void> | void {
59
- const pct = Math.trunc(ev.idx * 100 / ev.total);
60
- const text = ev.complete ? '' : `Compiling [${'#'.repeat(Math.trunc(pct / 10)).padEnd(10, ' ')}] [${ev.idx}/${ev.total}] ${ev.message}`;
61
- return this.#rewriteLine(text);
62
- }
58
+ constructor(
59
+ private scope: string,
60
+ private level?: CompilerLogLevel,
61
+ private logProgress?: boolean,
62
+ private root = CompilerLogger.#root,
63
+ ) { }
63
64
 
64
- /**
65
- * Is the log level active?
66
- */
67
- static isLevelActive(lvl: CompilerLogLevel): boolean {
68
- return LEVEL_TO_PRI[this.logLevel] <= LEVEL_TO_PRI[lvl];
65
+ isActive(level: CompilerLogLevel): boolean {
66
+ return LEVEL_TO_PRI[this.level ?? CompilerLogger.#logLevel] <= LEVEL_TO_PRI[level];
69
67
  }
70
68
 
71
- /**
72
- * Log event with filtering by level
73
- */
74
- static logEvent(ev: CompilerLogEvent): void {
75
- if (this.isLevelActive(ev.level)) {
76
- const params = [ev.message, ...ev.args ?? []].map(x => typeof x === 'string' ? x.replaceAll(this.root, '.') : x);
77
- if (ev.scope) {
78
- params.unshift(`[${ev.scope.padEnd(SCOPE_MAX, ' ')}]`);
79
- }
80
- params.unshift(new Date().toISOString(), `${ev.level.padEnd(5)}`);
81
- if (this.linePartial) {
82
- this.#rewriteLine(''); // Clear out progress line
83
- }
84
- // eslint-disable-next-line no-console
85
- console[ev.level]!(...params);
69
+ /** Log event with filtering by level */
70
+ onLogEvent(ev: CompilerLogEvent): void {
71
+ if (!this.isActive(ev.level)) { return; }
72
+ const params = [ev.message, ...ev.args ?? []].map(x => typeof x === 'string' ? x.replaceAll(this.root, '.') : x);
73
+ if (ev.scope) {
74
+ params.unshift(`[${ev.scope.padEnd(SCOPE_MAX, ' ')}]`);
86
75
  }
76
+ params.unshift(new Date().toISOString(), `${ev.level.padEnd(5)}`);
77
+ CompilerLogger.rewriteLine(''); // Clear out progress line, if active
78
+ // eslint-disable-next-line no-console
79
+ console[ev.level]!(...params);
87
80
  }
88
81
 
89
- /**
90
- * With logger
91
- */
92
- static withLogger<T>(scope: string, op: WithLogger<T>, basic = true): Promise<T> {
93
- const log = this.logger(scope);
94
- basic && log('debug', 'Started');
95
- return op(log).finally(() => basic && log('debug', 'Completed'));
82
+ /** Write progress event, if active */
83
+ onProgressEvent(ev: CompilerProgressEvent): void | Promise<void> {
84
+ if (!(this.logProgress ?? CompilerLogger.logProgress)) { return; }
85
+ const pct = Math.trunc(ev.idx * 100 / ev.total);
86
+ const text = ev.complete ? '' : `Compiling [${'#'.repeat(Math.trunc(pct / 10)).padEnd(10, ' ')}] [${ev.idx}/${ev.total}] ${ev.message}`;
87
+ return CompilerLogger.rewriteLine(text);
96
88
  }
97
89
 
98
- /**
99
- * With scope
100
- */
101
- static logger(scope: string): CompilerLogger {
102
- return (level, message, ...args) => this.logEvent({ scope, message, level, args, time: Date.now() });
90
+ /** Write all progress events if active */
91
+ async consumeProgressEvents(src: () => AsyncIterable<CompilerProgressEvent>): Promise<void> {
92
+ if (!(this.logProgress ?? CompilerLogger.logProgress)) { return; }
93
+ for await (const ev of src()) { this.onProgressEvent(ev); }
94
+ await CompilerLogger.reset();
103
95
  }
104
96
 
105
- /**
106
- * Write all progress events if active
107
- */
108
- static async consumeProgressEvents(src: () => AsyncIterable<CompilerProgressEvent>): Promise<void> {
109
- if (!this.logProgress) { return; }
110
- for await (const item of src()) { await this.logProgress?.(item); }
97
+ log(level: CompilerLogLevel, message: string, args: unknown[]): void {
98
+ this.onLogEvent({ scope: this.scope, message, level, args, time: Date.now() });
99
+ }
100
+ info(message: string, ...args: unknown[]): void { return this.log('info', message, args); }
101
+ debug(message: string, ...args: unknown[]): void { return this.log('debug', message, args); }
102
+ warn(message: string, ...args: unknown[]): void { return this.log('warn', message, args); }
103
+ error(message: string, ...args: unknown[]): void { return this.log('error', message, args); }
104
+ wrap<T = unknown>(op: (log: typeof this) => Promise<T>, basic = false): Promise<T> {
105
+ return basic ? (this.debug('Started'), op(this).finally(() => this.debug('Completed'))) : op(this);
111
106
  }
112
107
  }
@@ -14,16 +14,18 @@ type FetchEventsConfig<T> = {
14
14
  enforceIteration?: boolean;
15
15
  };
16
16
 
17
+ type SimpleLogger = Pick<CompilerLogger, 'error' | 'info' | 'debug'>;
18
+
17
19
  /**
18
20
  * Compiler Client Operations
19
21
  */
20
22
  export class CompilerClient {
21
23
 
22
24
  #url: string;
23
- #log: CompilerLogger;
25
+ #log: SimpleLogger;
24
26
  #handle: Record<'compiler' | 'server', ProcessHandle>;
25
27
 
26
- constructor(ctx: ManifestContext, log: CompilerLogger) {
28
+ constructor(ctx: ManifestContext, log: SimpleLogger) {
27
29
  this.#url = ctx.build.compilerUrl.replace('localhost', '127.0.0.1');
28
30
  this.#log = log;
29
31
  this.#handle = { compiler: new ProcessHandle(ctx, 'compiler'), server: new ProcessHandle(ctx, 'server') };
@@ -41,9 +43,9 @@ export class CompilerClient {
41
43
  const ctrl = new AbortController();
42
44
  opts?.signal?.addEventListener('abort', () => ctrl.abort());
43
45
  const timeoutId = setTimeout(() => {
44
- logTimeout && this.#log('error', `Timeout on request to ${this.#url}${rel}`);
46
+ logTimeout && this.#log.error(`Timeout on request to ${this.#url}${rel}`);
45
47
  ctrl.abort('TIMEOUT');
46
- }, 100).unref();
48
+ }, opts?.timeout ?? 100).unref();
47
49
  try {
48
50
  return await fetch(`${this.#url}${rel}`, { ...opts, signal: ctrl.signal });
49
51
  } finally {
@@ -53,7 +55,7 @@ export class CompilerClient {
53
55
 
54
56
  /** Get server information, if server is running */
55
57
  info(): Promise<CompilerServerInfo | undefined> {
56
- return this.#fetch('/info', {}, false).then(v => v.json(), () => undefined)
58
+ return this.#fetch('/info', { timeout: 200 }, false).then(v => v.json(), () => undefined)
57
59
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
58
60
  .then(v => v as CompilerServerInfo);
59
61
  }
@@ -71,13 +73,13 @@ export class CompilerClient {
71
73
  async stop(): Promise<boolean> {
72
74
  const info = await this.info();
73
75
  if (!info) {
74
- this.#log('debug', 'Stopping server, info not found, manual killing');
76
+ this.#log.debug('Stopping server, info not found, manual killing');
75
77
  return Promise.all([this.#handle.server.kill(), this.#handle.compiler.kill()])
76
78
  .then(v => v.some(x => x));
77
79
  }
78
80
 
79
81
  await this.#fetch('/stop').catch(() => { }); // Trigger
80
- this.#log('debug', 'Waiting for compiler to exit');
82
+ this.#log.debug('Waiting for compiler to exit');
81
83
  await this.#handle.compiler.ensureKilled();
82
84
  return true;
83
85
  }
@@ -92,7 +94,7 @@ export class CompilerClient {
92
94
  return;
93
95
  }
94
96
 
95
- this.#log('debug', `Starting watch for events of type "${type}"`);
97
+ this.#log.debug(`Starting watch for events of type "${type}"`);
96
98
 
97
99
  let signal = cfg.signal;
98
100
 
@@ -132,15 +134,15 @@ export class CompilerClient {
132
134
  info = await this.info();
133
135
 
134
136
  if (ctrl.signal.reason === 'TIMEOUT') {
135
- this.#log('debug', 'Failed due to timeout');
137
+ this.#log.debug('Failed due to timeout');
136
138
  return;
137
139
  }
138
140
 
139
141
  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}"`);
142
+ this.#log.debug(`Stopping watch for events of type "${type}"`);
141
143
  return;
142
144
  } else {
143
- this.#log('debug', `Restarting watch for events of type "${type}"`);
145
+ this.#log.debug(`Restarting watch for events of type "${type}"`);
144
146
  }
145
147
  }
146
148
  }
@@ -149,11 +151,11 @@ export class CompilerClient {
149
151
  async waitForState(states: CompilerStateType[], message?: string, signal?: AbortSignal): Promise<void> {
150
152
  const set = new Set(states);
151
153
  // Loop until
152
- this.#log('debug', `Waiting for states, ${states.join(', ')}`);
154
+ this.#log.debug(`Waiting for states, ${states.join(', ')}`);
153
155
  for await (const _ of this.fetchEvents('state', { signal, until: s => set.has(s.state) })) { }
154
- this.#log('debug', `Found state, one of ${states.join(', ')} `);
156
+ this.#log.debug(`Found state, one of ${states.join(', ')} `);
155
157
  if (message) {
156
- this.#log('info', message);
158
+ this.#log.info(message);
157
159
  }
158
160
  }
159
161
  }
@@ -3,13 +3,16 @@ import path from 'node:path';
3
3
  import timers from 'node:timers/promises';
4
4
 
5
5
  import type { ManifestContext } from '@travetto/manifest';
6
+ import { CompilerLogger } from '../log';
6
7
 
7
8
  export class ProcessHandle {
8
9
 
9
10
  #file: string;
11
+ #log: CompilerLogger;
10
12
 
11
13
  constructor(ctx: ManifestContext, name: string) {
12
14
  this.#file = path.resolve(ctx.workspace.path, ctx.build.toolFolder, `${name}.pid`);
15
+ this.#log = new CompilerLogger(`process-handle.${name}`);
13
16
  }
14
17
 
15
18
  async writePid(pid: number): Promise<void> {
@@ -26,15 +29,19 @@ export class ProcessHandle {
26
29
  if (!pid) { return false; }
27
30
  try {
28
31
  process.kill(pid, 0); // See if process is still running
29
- return false;
30
- } catch { }
31
- return true; // Still running
32
+ this.#log.debug('Is running', pid);
33
+ return true;
34
+ } catch {
35
+ this.#log.debug('Is not running', pid);
36
+ }
37
+ return false; // Not running
32
38
  }
33
39
 
34
40
  async kill(): Promise<boolean> {
35
41
  const pid = await this.getPid();
36
42
  if (pid && await this.isRunning()) {
37
43
  try {
44
+ this.#log.debug('Killing', pid);
38
45
  return process.kill(pid);
39
46
  } catch { }
40
47
  }
@@ -44,6 +51,7 @@ export class ProcessHandle {
44
51
  async ensureKilled(gracePeriod: number = 3000): Promise<boolean> {
45
52
  const start = Date.now();
46
53
  const pid = await this.getPid();
54
+ this.#log.debug('Ensuring Killed', pid);
47
55
  while (pid && (Date.now() - start) < gracePeriod) { // Ensure its done
48
56
  if (!await this.isRunning()) {
49
57
  return true;
@@ -51,8 +59,10 @@ export class ProcessHandle {
51
59
  await timers.setTimeout(100);
52
60
  }
53
61
  try {
62
+ this.#log.debug('Force Killing', pid);
54
63
  pid && process.kill(pid); // Force kill
55
64
  } catch { }
65
+ this.#log.debug('Did Kill', this.#file, !!pid);
56
66
  return pid !== undefined;
57
67
  }
58
68
  }
@@ -6,10 +6,10 @@ import type { ManifestContext, DeltaEvent } from '@travetto/manifest';
6
6
 
7
7
  import type { CompilerEvent, CompilerMode } from '../types';
8
8
  import { AsyncQueue } from '../queue';
9
- import { LogUtil } from '../log';
9
+ import { CompilerLogger } from '../log';
10
10
  import { CommonUtil } from '../util';
11
11
 
12
- const log = LogUtil.logger('compiler-exec');
12
+ const log = new CompilerLogger('compiler-exec');
13
13
  const isEvent = (msg: unknown): msg is CompilerEvent => !!msg && typeof msg === 'object' && 'type' in msg;
14
14
 
15
15
  /**
@@ -22,17 +22,17 @@ export class CompilerRunner {
22
22
  */
23
23
  static async * runProcess(ctx: ManifestContext, changed: DeltaEvent[], mode: CompilerMode, signal: AbortSignal): AsyncIterable<CompilerEvent> {
24
24
  if (signal.aborted) {
25
- log('debug', 'Skipping, shutting down');
25
+ log.debug('Skipping, shutting down');
26
26
  return;
27
27
  }
28
28
 
29
29
  const watch = mode === 'watch';
30
30
  if (!changed.length && !watch) {
31
31
  yield { type: 'state', payload: { state: 'compile-end' } };
32
- log('debug', 'Skipped');
32
+ log.debug('Skipped');
33
33
  return;
34
34
  } else {
35
- log('debug', `Started watch=${watch} changed=${changed.slice(0, 10).map(x => `${x.module}/${x.file}`)}`);
35
+ log.debug(`Started watch=${watch} changed=${changed.slice(0, 10).map(x => `${x.module}/${x.file}`)}`);
36
36
  }
37
37
 
38
38
  const main = path.resolve(ctx.workspace.path, ctx.build.compilerFolder, 'node_modules', '@travetto/compiler/support/entry.compiler.js');
@@ -45,7 +45,7 @@ export class CompilerRunner {
45
45
  try {
46
46
  await CommonUtil.writeTextFile(deltaFile, changedFiles.join('\n'));
47
47
 
48
- log('info', 'Launching compiler');
48
+ log.info('Launching compiler');
49
49
  const proc = cp.spawn(process.argv0, [main, deltaFile, `${watch}`], {
50
50
  env: {
51
51
  ...process.env,
@@ -58,7 +58,7 @@ export class CompilerRunner {
58
58
  .on('exit', () => queue.close());
59
59
 
60
60
  const kill = (): unknown => {
61
- log('debug', 'Shutting down process');
61
+ log.debug('Shutting down process');
62
62
  return (proc.connected ? proc.send('shutdown', (err) => proc.kill()) : proc.kill());
63
63
  };
64
64
 
@@ -68,11 +68,11 @@ export class CompilerRunner {
68
68
  yield* queue;
69
69
 
70
70
  if (proc.exitCode !== 0) {
71
- log('error', `Terminated during compilation, code=${proc.exitCode}, killed=${proc.killed}`);
71
+ log.error(`Terminated during compilation, code=${proc.exitCode}, killed=${proc.killed}`);
72
72
  }
73
73
  process.off('SIGINT', kill);
74
74
 
75
- log('debug', 'Finished');
75
+ log.debug('Finished');
76
76
  } finally {
77
77
  rmSync(deltaFile, { force: true });
78
78
  }
@@ -6,12 +6,12 @@ import { setMaxListeners } from 'node:events';
6
6
  import type { ManifestContext } from '@travetto/manifest';
7
7
 
8
8
  import type { CompilerMode, CompilerProgressEvent, CompilerEvent, CompilerEventType, CompilerServerInfo } from '../types';
9
- import { LogUtil } from '../log';
10
- import { CompilerClient } from './client';
9
+ import { CompilerLogger } from '../log';
11
10
  import { CommonUtil } from '../util';
11
+ import { CompilerClient } from './client';
12
12
  import { ProcessHandle } from './process-handle';
13
13
 
14
- const log = LogUtil.logger('compiler-server');
14
+ const log = new CompilerLogger('compiler-server');
15
15
 
16
16
  /**
17
17
  * Compiler Server Class
@@ -30,7 +30,7 @@ export class CompilerServer {
30
30
 
31
31
  constructor(ctx: ManifestContext, mode: CompilerMode) {
32
32
  this.#ctx = ctx;
33
- this.#client = new CompilerClient(ctx, LogUtil.logger('client.server'));
33
+ this.#client = new CompilerClient(ctx, new CompilerLogger('client.server'));
34
34
  this.#url = this.#client.url;
35
35
  this.#handle = { server: new ProcessHandle(ctx, 'server'), compiler: new ProcessHandle(ctx, 'compiler') };
36
36
 
@@ -70,11 +70,11 @@ export class CompilerServer {
70
70
  const info = await this.#client.info();
71
71
  resolve((info && info.mode === 'build' && this.mode === 'watch') ? 'retry' : 'running');
72
72
  } else {
73
- log('warn', 'Failed in running server', err);
73
+ log.warn('Failed in running server', err);
74
74
  reject(err);
75
75
  }
76
76
  })
77
- .on('close', () => log('debug', 'Server close event'));
77
+ .on('close', () => log.debug('Server close event'));
78
78
 
79
79
  const url = new URL(this.#url);
80
80
  setTimeout(() => this.#server.listen(+url.port, url.hostname), 1); // Run async
@@ -84,7 +84,7 @@ export class CompilerServer {
84
84
  if (attempt >= 5) {
85
85
  throw new Error('Unable to verify compilation server');
86
86
  }
87
- log('info', 'Waiting for build to finish, before retrying');
87
+ log.info('Waiting for build to finish, before retrying');
88
88
  // Let the server finish
89
89
  await this.#client.waitForState(['close'], 'Server closed', this.signal);
90
90
  return this.#tryListen(attempt + 1);
@@ -119,12 +119,13 @@ export class CompilerServer {
119
119
  }
120
120
 
121
121
  async #disconnectActive(): Promise<void> {
122
- log('info', 'Server disconnect requested');
122
+ log.info('Server disconnect requested');
123
123
  this.info.iteration = Date.now();
124
124
  await new Promise(r => setTimeout(r, 20));
125
125
  for (const el of Object.values(this.#listeners)) {
126
- el.res.end();
126
+ try { el.res.end(); } catch { }
127
127
  }
128
+ this.#listeners = {}; // Ensure its empty
128
129
  }
129
130
 
130
131
  async #clean(): Promise<{ clean: boolean }> {
@@ -141,22 +142,19 @@ export class CompilerServer {
141
142
 
142
143
  const [, action, subAction] = new URL(`${this.#url}${req.url}`).pathname.split('/');
143
144
 
144
- log('debug', 'Receive request', { action, subAction });
145
-
146
145
  let out: unknown;
146
+ let close = false;
147
147
  switch (action) {
148
148
  case 'event': return await this.#addListener(subAction, res);
149
149
  case 'clean': out = await this.#clean(); break;
150
- case 'stop': {
151
- // Must send immediately
152
- res.end(JSON.stringify({ closing: true }));
153
- await this.close();
154
- break;
155
- }
150
+ case 'stop': out = JSON.stringify({ closing: true }); close = true; break;
156
151
  case 'info':
157
152
  default: out = this.info ?? {}; break;
158
153
  }
159
154
  res.end(JSON.stringify(out));
155
+ if (close) {
156
+ await this.close();
157
+ }
160
158
  }
161
159
 
162
160
  /**
@@ -165,7 +163,7 @@ export class CompilerServer {
165
163
  async processEvents(src: (signal: AbortSignal) => AsyncIterable<CompilerEvent>): Promise<void> {
166
164
  for await (const ev of CommonUtil.restartableEvents(src, this.signal, this.isResetEvent)) {
167
165
  if (ev.type === 'progress') {
168
- await LogUtil.logProgress?.(ev.payload);
166
+ await log.onProgressEvent(ev.payload);
169
167
  }
170
168
 
171
169
  this.#emitEvent(ev);
@@ -176,9 +174,9 @@ export class CompilerServer {
176
174
  if (ev.payload.state === 'init' && ev.payload.extra && 'pid' in ev.payload.extra && typeof ev.payload.extra.pid === 'number') {
177
175
  this.info.compilerPid = ev.payload.extra.pid;
178
176
  }
179
- log('info', `State changed: ${this.info.state}`);
177
+ log.info(`State changed: ${this.info.state}`);
180
178
  } else if (ev.type === 'log') {
181
- LogUtil.logEvent(ev.payload);
179
+ log.onLogEvent(ev.payload);
182
180
  }
183
181
  if (this.isResetEvent(ev)) {
184
182
  await this.#disconnectActive();
@@ -188,19 +186,19 @@ export class CompilerServer {
188
186
  // Terminate, after letting all remaining events emit
189
187
  await this.close();
190
188
 
191
- log('debug', 'Finished processing events');
189
+ log.debug('Finished processing events');
192
190
  }
193
191
 
194
192
  /**
195
193
  * Close server
196
194
  */
197
195
  async close(): Promise<void> {
198
- log('info', 'Closing down server');
196
+ log.info('Closing down server');
199
197
 
200
198
  // If we are in a place where progress exists
201
199
  if (this.info.state === 'compile-start') {
202
200
  const cancel: CompilerProgressEvent = { complete: true, idx: 0, total: 0, message: 'Complete', operation: 'compile' };
203
- LogUtil.logProgress?.(cancel);
201
+ await log.onProgressEvent(cancel);
204
202
  this.#emitEvent({ type: 'progress', payload: cancel });
205
203
  }
206
204
 
@@ -217,14 +215,10 @@ export class CompilerServer {
217
215
  } catch { // Timeout or other error
218
216
  // Force shutdown
219
217
  this.#server.closeAllConnections();
220
- if (this.info.compilerPid) { // Ensure its killed
221
- try {
222
- process.kill(this.info.compilerPid);
223
- } catch { }
224
- }
218
+ await this.#handle.compiler.kill();
225
219
  }
226
220
 
227
- log('info', 'Closed down server');
221
+ log.info('Closed down server');
228
222
  }
229
223
 
230
224
  /**
@@ -232,7 +226,7 @@ export class CompilerServer {
232
226
  */
233
227
  async listen(): Promise<CompilerServer | undefined> {
234
228
  const running = await this.#tryListen() === 'ok';
235
- log('info', running ? 'Starting server' : 'Server already running under a different process', this.#url);
229
+ log.info(running ? 'Starting server' : 'Server already running under a different process', this.#url);
236
230
  return running ? this : undefined;
237
231
  }
238
232
  }
package/support/setup.ts CHANGED
@@ -4,7 +4,7 @@ import fs from 'node:fs/promises';
4
4
 
5
5
  import { type DeltaEvent, type ManifestContext, type Package } from '@travetto/manifest';
6
6
 
7
- import { LogUtil } from './log';
7
+ import { CompilerLogger } from './log';
8
8
  import { CommonUtil } from './util';
9
9
 
10
10
  type ModFile = { input: string, output: string, stale: boolean };
@@ -128,19 +128,19 @@ export class CompilerSetup {
128
128
  const out: string[] = [];
129
129
 
130
130
  try {
131
- await LogUtil.withLogger(scope, async log => {
131
+ await new CompilerLogger(scope).wrap(async log => {
132
132
  if (files.some(f => f.stale)) {
133
- log('debug', 'Starting', mod);
133
+ log.debug('Starting', mod);
134
134
  for (const file of files.filter(x => x.stale)) {
135
135
  await this.#transpileFile(ctx, file.input, file.output);
136
136
  }
137
137
  if (changes.length) {
138
138
  out.push(...changes.map(x => `${mod}/${x}`));
139
- log('debug', `Source changed: ${changes.join(', ')}`, mod);
139
+ log.debug(`Source changed: ${changes.join(', ')}`, mod);
140
140
  }
141
- log('debug', 'Completed', mod);
141
+ log.debug('Completed', mod);
142
142
  } else {
143
- log('debug', 'Skipped', mod);
143
+ log.debug('Skipped', mod);
144
144
  }
145
145
  }, false);
146
146
  } catch (err) {
@@ -173,7 +173,7 @@ export class CompilerSetup {
173
173
  static async setup(ctx: ManifestContext): Promise<DeltaEvent[]> {
174
174
  let changes = 0;
175
175
 
176
- await LogUtil.withLogger('precompile', async () => {
176
+ await new CompilerLogger('precompile').wrap(async () => {
177
177
  for (const mod of PRECOMPILE_MODS) {
178
178
  changes += (await this.#compileIfStale(ctx, 'precompile', mod, SOURCE_SEED)).length;
179
179
  }
@@ -181,18 +181,18 @@ export class CompilerSetup {
181
181
 
182
182
  const { ManifestUtil, ManifestDeltaUtil } = await this.#importManifest(ctx);
183
183
 
184
- const manifest = await LogUtil.withLogger('manifest', () =>
184
+ const manifest = await new CompilerLogger('manifest').wrap(() =>
185
185
  ManifestUtil.buildManifest(ManifestUtil.getWorkspaceContext(ctx)));
186
186
 
187
- await LogUtil.withLogger('transformers', async () => {
187
+ await new CompilerLogger('transformers').wrap(async () => {
188
188
  for (const mod of Object.values(manifest.modules).filter(m => m.files.$transformer?.length)) {
189
189
  changes += (await this.#compileIfStale(ctx, 'transformers', mod.name, ['package.json', ...mod.files.$transformer!.map(x => x[0])])).length;
190
190
  }
191
191
  });
192
192
 
193
- const delta = await LogUtil.withLogger('delta', async log => {
193
+ const delta = await new CompilerLogger('delta').wrap(async log => {
194
194
  if (changes) {
195
- log('debug', 'Skipping, everything changed');
195
+ log.debug('Skipping, everything changed');
196
196
  return [{ type: 'changed', file: '*', module: ctx.workspace.name, sourceFile: '' } as const];
197
197
  } else {
198
198
  return ManifestDeltaUtil.produceDelta(manifest);
@@ -200,29 +200,29 @@ export class CompilerSetup {
200
200
  });
201
201
 
202
202
  if (changes) {
203
- await LogUtil.withLogger('reset', async log => {
203
+ await new CompilerLogger('reset').wrap(async log => {
204
204
  await fs.rm(path.resolve(ctx.workspace.path, ctx.build.outputFolder), { recursive: true, force: true });
205
- log('info', 'Clearing output due to compiler changes');
205
+ log.info('Clearing output due to compiler changes');
206
206
  }, false);
207
207
  }
208
208
 
209
209
  // Write manifest
210
- await LogUtil.withLogger('manifest', async log => {
210
+ await new CompilerLogger('manifest').wrap(async log => {
211
211
  await ManifestUtil.writeManifest(manifest);
212
- log('debug', `Wrote manifest ${ctx.workspace.name}`);
212
+ log.debug(`Wrote manifest ${ctx.workspace.name}`);
213
213
 
214
214
  // Update all manifests when in mono repo
215
215
  if (delta.length && ctx.workspace.mono) {
216
216
  const names: string[] = [];
217
217
  const mods = Object.values(manifest.modules).filter(x => x.workspace && x.name !== ctx.workspace.name);
218
218
  for (const mod of mods) {
219
- const modCtx = ManifestUtil.getModuleContext(ctx, mod.sourceFolder);
219
+ const modCtx = ManifestUtil.getModuleContext(ctx, mod.sourceFolder, true);
220
220
  const modManifest = await ManifestUtil.buildManifest(modCtx);
221
221
  await ManifestUtil.writeManifest(modManifest);
222
222
  names.push(mod.name);
223
223
  }
224
- log('debug', `Changes triggered ${delta.slice(0, 10).map(x => `${x.type}:${x.module}:${x.file}`)}`);
225
- log('debug', `Rewrote monorepo manifests [changes=${delta.length}] ${names.slice(0, 10).join(', ')}`);
224
+ log.debug(`Changes triggered ${delta.slice(0, 10).map(x => `${x.type}:${x.module}:${x.file}`)}`);
225
+ log.debug(`Rewrote monorepo manifests [changes=${delta.length}] ${names.slice(0, 10).join(', ')}`);
226
226
  }
227
227
  });
228
228
 
package/support/util.ts CHANGED
@@ -4,7 +4,7 @@ import { setMaxListeners } from 'node:events';
4
4
 
5
5
  import type { ManifestContext } from '@travetto/manifest';
6
6
 
7
- import { LogUtil } from './log';
7
+ import { CompilerLogger } from './log';
8
8
 
9
9
  const OPT_CACHE: Record<string, import('typescript').CompilerOptions> = {};
10
10
 
@@ -58,28 +58,30 @@ 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
+ const log = new CompilerLogger('event-stream');
62
62
  outer: while (!parent.aborted) {
63
63
  const controller = new AbortController();
64
64
  setMaxListeners(1000, controller.signal);
65
65
  // Chain
66
- parent.addEventListener('abort', () => controller.abort());
66
+ const kill = (): void => controller.abort();
67
+ parent.addEventListener('abort', kill);
67
68
 
68
69
  const comp = src(controller.signal);
69
70
 
70
- log('debug', 'Started event stream');
71
+ log.debug('Started event stream');
71
72
 
72
73
  // Wait for all events, close at the end
73
74
  for await (const ev of comp) {
74
75
  yield ev;
75
76
  if (shouldRestart(ev)) {
76
- log('debug', 'Restarting stream');
77
+ log.debug('Restarting stream');
77
78
  controller.abort(); // Ensure terminated of process
79
+ parent.removeEventListener('abort', kill);
78
80
  continue outer;
79
81
  }
80
82
  }
81
83
 
82
- log('debug', 'Finished event stream');
84
+ log.debug('Finished event stream');
83
85
 
84
86
  // Natural exit, we done
85
87
  if (!controller.signal.aborted) { // Shutdown source if still running