@travetto/compiler 8.0.0-alpha.20 → 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.
- package/README.md +3 -3
- package/__index__.ts +4 -4
- package/bin/hook.js +4 -3
- package/bin/trvc-target.js +2 -1
- package/bin/trvc.js +2 -1
- package/package.json +4 -4
- package/src/common.ts +15 -9
- package/src/compiler.ts +16 -19
- package/src/event.ts +7 -5
- package/src/log.ts +57 -22
- package/src/queue.ts +4 -2
- package/src/server/client.ts +38 -24
- package/src/server/manager.ts +16 -11
- package/src/server/process-handle.ts +14 -10
- package/src/server/server.ts +43 -23
- package/src/state.ts +74 -41
- package/src/ts-proxy.ts +5 -3
- package/src/types.ts +22 -15
- package/src/util.ts +1 -3
- package/src/watch.ts +80 -68
- package/support/invoke.ts +36 -21
- package/tsconfig.trv.json +4 -12
package/README.md
CHANGED
|
@@ -17,12 +17,12 @@ This module expands upon the [Typescript](https://typescriptlang.org) compiler,
|
|
|
17
17
|
* Integration with the [Transformation](https://github.com/travetto/travetto/tree/main/module/transformer#readme "Functionality for AST transformations, with transformer registration, and general utils") module, allowing for rich, type-aware transformations
|
|
18
18
|
* Automatic conversion to either [Ecmascript Module](https://nodejs.org/api/esm.html) or [CommonJS](https://nodejs.org/api/modules.html) based on the [Package JSON](https://docs.npmjs.com/cli/v9/configuring-npm/package-json) `type` value
|
|
19
19
|
* Removal of type only imports which can break [Ecmascript Module](https://nodejs.org/api/esm.html)-style output
|
|
20
|
-
* Automatic addition of `.js` extension to imports to also support
|
|
20
|
+
* Automatic addition of `.js` extension to imports to also support [Ecmascript Module](https://nodejs.org/api/esm.html)-style output
|
|
21
21
|
|
|
22
22
|
Beyond the [Typescript](https://typescriptlang.org) compiler functionality, the module provides the primary entry point into the development process.
|
|
23
23
|
|
|
24
24
|
## CLI
|
|
25
|
-
The compiler cli, [trvc](https://github.com/travetto/travetto/tree/main/module/compiler/bin/trvc.js) is the entry point for compilation-related operations. It has the ability to check for active builds, and ongoing watch operations to ensure only one process is building at a time.
|
|
25
|
+
The compiler cli, [trvc](https://github.com/travetto/travetto/tree/main/module/compiler/bin/trvc.js) is the entry point for compilation-related operations. It has the ability to check for active builds, and ongoing watch operations to ensure only one process is building at a time. Within the framework, regardless of mono-repo or not, the compilation always targets the entire project. With the efficient caching behavior, this leads to generally a minimal overhead but allows for centralization of all operations.
|
|
26
26
|
|
|
27
27
|
The compiler cli supports the following operations:
|
|
28
28
|
* `start` - Run the compiler in watch mode
|
|
@@ -36,7 +36,7 @@ The compiler cli supports the following operations:
|
|
|
36
36
|
* `manifest [output]` - Generate the project manifest
|
|
37
37
|
* `manifest:production [output]` - Generate the production project manifest
|
|
38
38
|
|
|
39
|
-
In addition to the normal output, the compiler supports an environment variable `TRV_BUILD` that supports the following values: `debug`, `info`, `warn` or `none`.
|
|
39
|
+
In addition to the normal output, the compiler supports an environment variable `TRV_BUILD` that supports the following values: `debug`, `info`, `warn` or `none`. This provides different level of logging during the build process which is helpful to diagnose any odd behaviors. When invoking an unknown command (e.g. `<other>` from above), the default level is `warn`. Otherwise the default logging level is `info`.
|
|
40
40
|
|
|
41
41
|
**Terminal: Sample trv output with debug logging**
|
|
42
42
|
```bash
|
package/__index__.ts
CHANGED
|
@@ -2,11 +2,11 @@ export * from './src/common.ts';
|
|
|
2
2
|
export * from './src/compiler.ts';
|
|
3
3
|
export * from './src/event.ts';
|
|
4
4
|
export * from './src/log.ts';
|
|
5
|
+
export * from './src/server/client.ts';
|
|
6
|
+
export * from './src/server/manager.ts';
|
|
7
|
+
export * from './src/server/process-handle.ts';
|
|
8
|
+
export * from './src/server/server.ts';
|
|
5
9
|
export * from './src/state.ts';
|
|
6
10
|
export * from './src/types.ts';
|
|
7
11
|
export * from './src/util.ts';
|
|
8
12
|
export * from './src/watch.ts';
|
|
9
|
-
export * from './src/server/client.ts';
|
|
10
|
-
export * from './src/server/server.ts';
|
|
11
|
-
export * from './src/server/manager.ts';
|
|
12
|
-
export * from './src/server/process-handle.ts';
|
package/bin/hook.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
import { readFileSync } from 'node:fs';
|
|
4
|
+
import module from 'node:module';
|
|
4
5
|
import { fileURLToPath } from 'node:url';
|
|
5
6
|
|
|
6
7
|
const ogEmitWarning = process.emitWarning.bind(process);
|
|
@@ -10,7 +11,7 @@ module.registerHooks({
|
|
|
10
11
|
if (/[.]tsx?$/.test(url)) {
|
|
11
12
|
const source = readFileSync(fileURLToPath(url), 'utf8');
|
|
12
13
|
try {
|
|
13
|
-
process.emitWarning = () => {
|
|
14
|
+
process.emitWarning = () => {};
|
|
14
15
|
return { format: 'module', source: module.stripTypeScriptTypes(source), shortCircuit: true };
|
|
15
16
|
} finally {
|
|
16
17
|
process.emitWarning = ogEmitWarning;
|
|
@@ -19,4 +20,4 @@ module.registerHooks({
|
|
|
19
20
|
return nextLoad(url, context);
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
|
-
});
|
|
23
|
+
});
|
package/bin/trvc-target.js
CHANGED
package/bin/trvc.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/compiler",
|
|
3
|
-
"version": "8.0.0-alpha.
|
|
3
|
+
"version": "8.0.0-alpha.21",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The compiler infrastructure for the Travetto framework",
|
|
6
6
|
"keywords": [
|
|
@@ -31,11 +31,11 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@parcel/watcher": "^2.5.6",
|
|
34
|
-
"@travetto/manifest": "^8.0.0-alpha.
|
|
35
|
-
"@travetto/transformer": "^8.0.0-alpha.
|
|
34
|
+
"@travetto/manifest": "^8.0.0-alpha.11",
|
|
35
|
+
"@travetto/transformer": "^8.0.0-alpha.14"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
|
-
"@travetto/cli": "^8.0.0-alpha.
|
|
38
|
+
"@travetto/cli": "^8.0.0-alpha.28"
|
|
39
39
|
},
|
|
40
40
|
"peerDependenciesMeta": {
|
|
41
41
|
"@travetto/cli": {
|
package/src/common.ts
CHANGED
|
@@ -6,11 +6,14 @@ import { type ManifestContext, path } from '@travetto/manifest';
|
|
|
6
6
|
import { Log } from './log.ts';
|
|
7
7
|
|
|
8
8
|
export class CommonUtil {
|
|
9
|
-
|
|
10
9
|
/**
|
|
11
10
|
* Restartable Event Stream
|
|
12
11
|
*/
|
|
13
|
-
static async *
|
|
12
|
+
static async *restartableEvents<T>(
|
|
13
|
+
input: (signal: AbortSignal) => AsyncIterable<T>,
|
|
14
|
+
parent: AbortSignal,
|
|
15
|
+
shouldRestart: (item: T) => boolean
|
|
16
|
+
): AsyncIterable<T> {
|
|
14
17
|
const log = Log.scoped('event-stream');
|
|
15
18
|
outer: while (!parent.aborted) {
|
|
16
19
|
const controller = new AbortController();
|
|
@@ -37,7 +40,8 @@ export class CommonUtil {
|
|
|
37
40
|
log.debug('Finished event stream');
|
|
38
41
|
|
|
39
42
|
// Natural exit, we done
|
|
40
|
-
if (!controller.signal.aborted) {
|
|
43
|
+
if (!controller.signal.aborted) {
|
|
44
|
+
// Shutdown source if still running
|
|
41
45
|
controller.abort();
|
|
42
46
|
}
|
|
43
47
|
return;
|
|
@@ -48,14 +52,14 @@ export class CommonUtil {
|
|
|
48
52
|
* Non-blocking timeout
|
|
49
53
|
*/
|
|
50
54
|
static nonBlockingTimeout(time: number): Promise<void> {
|
|
51
|
-
return timers.setTimeout(time, undefined, { ref: false }).catch(() => {
|
|
55
|
+
return timers.setTimeout(time, undefined, { ref: false }).catch(() => {});
|
|
52
56
|
}
|
|
53
57
|
|
|
54
58
|
/**
|
|
55
59
|
* Blocking timeout
|
|
56
60
|
*/
|
|
57
61
|
static blockingTimeout(time: number): Promise<void> {
|
|
58
|
-
return timers.setTimeout(time, undefined, { ref: true }).catch(() => {
|
|
62
|
+
return timers.setTimeout(time, undefined, { ref: true }).catch(() => {});
|
|
59
63
|
}
|
|
60
64
|
|
|
61
65
|
/**
|
|
@@ -76,8 +80,10 @@ export class CommonUtil {
|
|
|
76
80
|
* Write to stdout with backpressure handling
|
|
77
81
|
*/
|
|
78
82
|
static async writeStdout(level: number, data: unknown): Promise<void> {
|
|
79
|
-
if (data === undefined) {
|
|
83
|
+
if (data === undefined) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
80
86
|
process.stdout.write(`${JSON.stringify(data, undefined, level)}\n`) ||
|
|
81
|
-
await new Promise(resolve => process.stdout.once('drain', resolve));
|
|
82
|
-
}
|
|
83
|
-
}
|
|
87
|
+
(await new Promise(resolve => process.stdout.once('drain', resolve)));
|
|
88
|
+
}
|
|
89
|
+
}
|
package/src/compiler.ts
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import fs from 'node:fs/promises';
|
|
2
1
|
import { setMaxListeners } from 'node:events';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
3
|
|
|
4
|
-
import { getManifestContext, ManifestDeltaUtil, ManifestIndex, ManifestUtil
|
|
4
|
+
import { type DeltaEvent, getManifestContext, ManifestDeltaUtil, ManifestIndex, ManifestUtil } from '@travetto/manifest';
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import { CompilerWatcher } from './watch.ts';
|
|
8
|
-
import { type CompileEmitEvent, CompilerReset } from './types.ts';
|
|
6
|
+
import { CommonUtil } from './common.ts';
|
|
9
7
|
import { EventUtil } from './event.ts';
|
|
10
|
-
|
|
11
8
|
import { IpcLogger } from './log.ts';
|
|
12
|
-
import {
|
|
9
|
+
import { CompilerState } from './state.ts';
|
|
10
|
+
import { type CompileEmitEvent, CompilerReset } from './types.ts';
|
|
11
|
+
import { CompilerWatcher } from './watch.ts';
|
|
13
12
|
|
|
14
13
|
const log = new IpcLogger({ level: 'debug' });
|
|
15
14
|
|
|
@@ -17,7 +16,6 @@ const log = new IpcLogger({ level: 'debug' });
|
|
|
17
16
|
* Compilation support
|
|
18
17
|
*/
|
|
19
18
|
export class Compiler {
|
|
20
|
-
|
|
21
19
|
/**
|
|
22
20
|
* Run compiler as a main entry point
|
|
23
21
|
*/
|
|
@@ -44,9 +42,7 @@ export class Compiler {
|
|
|
44
42
|
this.#shutdownController = new AbortController();
|
|
45
43
|
this.#shutdownSignal = this.#shutdownController.signal;
|
|
46
44
|
setMaxListeners(1000, this.#shutdownSignal);
|
|
47
|
-
process
|
|
48
|
-
.once('disconnect', () => this.#shutdown('manual'))
|
|
49
|
-
.on('message', event => (event === 'shutdown') && this.#shutdown('manual'));
|
|
45
|
+
process.once('disconnect', () => this.#shutdown('manual')).on('message', event => event === 'shutdown' && this.#shutdown('manual'));
|
|
50
46
|
}
|
|
51
47
|
|
|
52
48
|
#shutdown(mode: 'error' | 'manual' | 'complete' | 'reset', errorMessage?: string): void {
|
|
@@ -111,7 +107,7 @@ export class Compiler {
|
|
|
111
107
|
/**
|
|
112
108
|
* Emit all files as a stream
|
|
113
109
|
*/
|
|
114
|
-
async *
|
|
110
|
+
async *emit(files: string[]): AsyncIterable<CompileEmitEvent> {
|
|
115
111
|
let i = 0;
|
|
116
112
|
let lastSent = Date.now();
|
|
117
113
|
|
|
@@ -122,8 +118,9 @@ export class Compiler {
|
|
|
122
118
|
const nodeModSeparator = 'node_modules/';
|
|
123
119
|
const nodeModIdx = file.lastIndexOf(nodeModSeparator);
|
|
124
120
|
const imp = nodeModIdx >= 0 ? file.substring(nodeModIdx + nodeModSeparator.length) : file;
|
|
125
|
-
yield { file: imp, i: i += 1, errors, total: files.length, duration };
|
|
126
|
-
if (
|
|
121
|
+
yield { file: imp, i: (i += 1), errors, total: files.length, duration };
|
|
122
|
+
if (Date.now() - lastSent > 50) {
|
|
123
|
+
// Limit to 1 every 50ms
|
|
127
124
|
lastSent = Date.now();
|
|
128
125
|
EventUtil.sendEvent('progress', { total: files.length, idx: i, message: imp, operation: 'compile' });
|
|
129
126
|
}
|
|
@@ -154,7 +151,7 @@ export class Compiler {
|
|
|
154
151
|
|
|
155
152
|
const metrics: CompileEmitEvent[] = [];
|
|
156
153
|
const isCompilerChanged = this.#deltaEvents.some(event => this.#state.isCompilerFile(event.sourceFile));
|
|
157
|
-
const changedFiles =
|
|
154
|
+
const changedFiles = isCompilerChanged ? this.#state.getAllFiles() : this.#deltaEvents.map(event => event.sourceFile);
|
|
158
155
|
|
|
159
156
|
if (changedFiles.length) {
|
|
160
157
|
for await (const event of this.emit(changedFiles)) {
|
|
@@ -174,9 +171,9 @@ export class Compiler {
|
|
|
174
171
|
log.debug('Compilation aborted');
|
|
175
172
|
} else if (failures.size) {
|
|
176
173
|
const sortedFailures = [...failures.entries()].sort((a, b) => a[0].localeCompare(b[0]));
|
|
177
|
-
log.error(
|
|
178
|
-
|
|
179
|
-
|
|
174
|
+
log.error(
|
|
175
|
+
'Compilation failed',
|
|
176
|
+
['', sortedFailures.flatMap(([file, count]) => `- ${file}: ${count} errors found`)].flat(3).join('\n')
|
|
180
177
|
);
|
|
181
178
|
} else {
|
|
182
179
|
log.debug('Compilation succeeded');
|
|
@@ -249,4 +246,4 @@ export class Compiler {
|
|
|
249
246
|
|
|
250
247
|
this.#shutdown('complete');
|
|
251
248
|
}
|
|
252
|
-
}
|
|
249
|
+
}
|
package/src/event.ts
CHANGED
|
@@ -3,14 +3,16 @@ import type { CompilerEvent, CompilerEventPayload, CompilerEventType } from './t
|
|
|
3
3
|
const VALID_EVENT_TYPES = new Set<CompilerEventType>(['change', 'log', 'progress', 'state', 'all', 'file']);
|
|
4
4
|
|
|
5
5
|
export class EventUtil {
|
|
6
|
-
|
|
7
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
8
6
|
static isComplilerEventType = (value: string): value is CompilerEventType => VALID_EVENT_TYPES.has(value as CompilerEventType);
|
|
9
7
|
|
|
10
8
|
static isCompilerEvent = (value: unknown): value is CompilerEvent =>
|
|
11
|
-
typeof value === 'object' &&
|
|
9
|
+
typeof value === 'object' &&
|
|
10
|
+
value !== null &&
|
|
11
|
+
'type' in value &&
|
|
12
|
+
typeof value.type === 'string' &&
|
|
13
|
+
EventUtil.isComplilerEventType(value.type);
|
|
12
14
|
|
|
13
15
|
static sendEvent<K extends CompilerEventType, T extends CompilerEventPayload<K>>(type: K, payload: T): void {
|
|
14
|
-
process.connected && process.send!({ type, payload }, undefined, undefined, () => {
|
|
16
|
+
process.connected && process.send!({ type, payload }, undefined, undefined, () => {});
|
|
15
17
|
}
|
|
16
|
-
}
|
|
18
|
+
}
|
package/src/log.ts
CHANGED
|
@@ -15,7 +15,6 @@ export type LogShape = Record<'info' | 'debug' | 'warn' | 'error', (message: str
|
|
|
15
15
|
const ESC = '\x1b[';
|
|
16
16
|
|
|
17
17
|
export class Logger implements LogConfig, LogShape {
|
|
18
|
-
|
|
19
18
|
static #linePartial: boolean | undefined;
|
|
20
19
|
|
|
21
20
|
/** Rewrite text line, tracking cleanup as necessary */
|
|
@@ -23,7 +22,8 @@ export class Logger implements LogConfig, LogShape {
|
|
|
23
22
|
if ((!text && !this.#linePartial) || !process.stdout.isTTY) {
|
|
24
23
|
return;
|
|
25
24
|
}
|
|
26
|
-
if (this.#linePartial === undefined) {
|
|
25
|
+
if (this.#linePartial === undefined) {
|
|
26
|
+
// First time
|
|
27
27
|
process.stdout.write(`${ESC}?25l`); // Hide cursor
|
|
28
28
|
process.on('exit', () => this.reset());
|
|
29
29
|
}
|
|
@@ -35,7 +35,9 @@ export class Logger implements LogConfig, LogShape {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
static reset(): void {
|
|
38
|
+
static reset(): void {
|
|
39
|
+
process.stdout.write(`${ESC}!p${ESC}?25h`);
|
|
40
|
+
}
|
|
39
41
|
|
|
40
42
|
level?: CompilerLogLevel | 'none';
|
|
41
43
|
root: string = process.cwd();
|
|
@@ -47,28 +49,38 @@ export class Logger implements LogConfig, LogShape {
|
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
valid(event: CompilerLogEvent): boolean {
|
|
50
|
-
return LEVEL_TO_PRIORITY[this.level ?? this.parent?.level
|
|
52
|
+
return LEVEL_TO_PRIORITY[this.level ?? this.parent?.level ?? 'none'] <= LEVEL_TO_PRIORITY[event.level];
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
/** Log event with filtering by level */
|
|
54
56
|
render(event: CompilerLogEvent): void {
|
|
55
|
-
if (!this.valid(event)) {
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
if (!this.valid(event)) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const params = [event.message, ...(event.args ?? [])].map(arg =>
|
|
61
|
+
typeof arg === 'string' ? arg.replaceAll(this.root ?? this.parent?.root, '.') : arg
|
|
62
|
+
);
|
|
58
63
|
|
|
59
64
|
if (event.scope ?? this.scope) {
|
|
60
65
|
params.unshift(`[${(event.scope ?? this.scope!).padEnd(SCOPE_MAX, ' ')}]`);
|
|
61
66
|
}
|
|
62
67
|
params.unshift(new Date().toISOString(), `${event.level.padEnd(5)}`);
|
|
63
68
|
Logger.rewriteLine(''); // Clear out progress line, if active
|
|
64
|
-
// eslint-disable-next-line no-console
|
|
65
69
|
console[event.level]!(...params);
|
|
66
70
|
}
|
|
67
71
|
|
|
68
|
-
info(message: string, ...args: unknown[]): void {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
+
info(message: string, ...args: unknown[]): void {
|
|
73
|
+
this.render({ level: 'info', message, args });
|
|
74
|
+
}
|
|
75
|
+
debug(message: string, ...args: unknown[]): void {
|
|
76
|
+
this.render({ level: 'debug', message, args });
|
|
77
|
+
}
|
|
78
|
+
warn(message: string, ...args: unknown[]): void {
|
|
79
|
+
this.render({ level: 'warn', message, args });
|
|
80
|
+
}
|
|
81
|
+
error(message: string, ...args: unknown[]): void {
|
|
82
|
+
this.render({ level: 'error', message, args });
|
|
83
|
+
}
|
|
72
84
|
}
|
|
73
85
|
|
|
74
86
|
class $RootLogger extends Logger {
|
|
@@ -86,9 +98,17 @@ class $RootLogger extends Logger {
|
|
|
86
98
|
initLevel(defaultLevel: CompilerLogLevel | 'none'): void {
|
|
87
99
|
const value = process.env.TRV_QUIET !== 'true' ? process.env.TRV_BUILD : 'none';
|
|
88
100
|
switch (value) {
|
|
89
|
-
case 'debug':
|
|
90
|
-
case
|
|
91
|
-
case '
|
|
101
|
+
case 'debug':
|
|
102
|
+
case 'warn':
|
|
103
|
+
case 'error':
|
|
104
|
+
case 'info':
|
|
105
|
+
this.level = value;
|
|
106
|
+
break;
|
|
107
|
+
case undefined:
|
|
108
|
+
this.level = defaultLevel;
|
|
109
|
+
break;
|
|
110
|
+
default:
|
|
111
|
+
this.level = 'none';
|
|
92
112
|
}
|
|
93
113
|
}
|
|
94
114
|
|
|
@@ -100,21 +120,34 @@ class $RootLogger extends Logger {
|
|
|
100
120
|
/** Scope and provide a callback pattern for access to a logger */
|
|
101
121
|
wrap<T = unknown>(scope: string, operation: (log: Logger) => Promise<T>, basic = true): Promise<T> {
|
|
102
122
|
const logger = this.scoped(scope);
|
|
103
|
-
|
|
123
|
+
if (basic) {
|
|
124
|
+
logger.debug('Started');
|
|
125
|
+
return operation(logger).finally(() => logger.debug('Completed'));
|
|
126
|
+
} else {
|
|
127
|
+
return operation(logger);
|
|
128
|
+
}
|
|
104
129
|
}
|
|
105
130
|
|
|
106
131
|
/** Write progress event, if active */
|
|
107
132
|
onProgressEvent(event: CompilerProgressEvent): void | Promise<void> {
|
|
108
|
-
if (!
|
|
109
|
-
|
|
110
|
-
|
|
133
|
+
if (!this.logProgress) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const progress = Math.trunc((event.idx * 100) / event.total);
|
|
137
|
+
const text = event.complete
|
|
138
|
+
? ''
|
|
139
|
+
: `Compiling [${'#'.repeat(Math.trunc(progress / 10)).padEnd(10, ' ')}] [${event.idx}/${event.total}] ${event.message}`;
|
|
111
140
|
return Logger.rewriteLine(text);
|
|
112
141
|
}
|
|
113
142
|
|
|
114
143
|
/** Write all progress events if active */
|
|
115
144
|
async consumeProgressEvents(input: () => AsyncIterable<CompilerProgressEvent>): Promise<void> {
|
|
116
|
-
if (!
|
|
117
|
-
|
|
145
|
+
if (!this.logProgress) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
for await (const event of input()) {
|
|
149
|
+
this.onProgressEvent(event);
|
|
150
|
+
}
|
|
118
151
|
Logger.reset();
|
|
119
152
|
}
|
|
120
153
|
}
|
|
@@ -123,7 +156,9 @@ export const Log = new $RootLogger();
|
|
|
123
156
|
|
|
124
157
|
export class IpcLogger extends Logger {
|
|
125
158
|
render(event: CompilerLogEvent): void {
|
|
126
|
-
if (!this.valid(event)) {
|
|
159
|
+
if (!this.valid(event)) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
127
162
|
if (process.connected && process.send) {
|
|
128
163
|
process.send({ type: 'log', payload: event });
|
|
129
164
|
}
|
package/src/queue.ts
CHANGED
|
@@ -10,7 +10,9 @@ export class AsyncQueue<X> implements AsyncIterator<X>, AsyncIterable<X> {
|
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
[Symbol.asyncIterator](): AsyncIterator<X> {
|
|
13
|
+
[Symbol.asyncIterator](): AsyncIterator<X> {
|
|
14
|
+
return this;
|
|
15
|
+
}
|
|
14
16
|
|
|
15
17
|
async next(): Promise<IteratorResult<X>> {
|
|
16
18
|
while (!this.#done && !this.#queue.length) {
|
|
@@ -35,4 +37,4 @@ export class AsyncQueue<X> implements AsyncIterator<X>, AsyncIterable<X> {
|
|
|
35
37
|
this.#done = true;
|
|
36
38
|
this.#ready.resolve();
|
|
37
39
|
}
|
|
38
|
-
}
|
|
40
|
+
}
|
package/src/server/client.ts
CHANGED
|
@@ -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
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
83
|
-
.
|
|
84
|
-
|
|
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(() => {
|
|
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 *
|
|
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) {
|
|
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)) {
|
|
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
|
+
}
|