@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/README.md +29 -27
- package/bin/common.js +51 -46
- package/bin/trvc.js +21 -14
- package/package.json +6 -7
- package/src/compiler.ts +96 -43
- package/src/event.ts +3 -3
- package/src/log.ts +1 -1
- package/src/state.ts +43 -25
- package/src/types.ts +1 -1
- package/src/util.ts +6 -6
- package/src/watch.ts +107 -117
- package/support/entry.trvc.ts +67 -37
- package/support/log.ts +55 -24
- package/support/queue.ts +11 -6
- package/support/server/client.ts +122 -81
- package/support/server/process-handle.ts +57 -0
- package/support/server/runner.ts +37 -64
- package/support/server/server.ts +86 -58
- package/support/setup.ts +38 -36
- package/support/types.ts +3 -4
- package/support/util.ts +20 -25
- package/src/internal/watch-core.ts +0 -104
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
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
this.logLevel =
|
|
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.
|
|
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
|
|
60
|
+
* Log event with filtering by level
|
|
38
61
|
*/
|
|
39
|
-
static
|
|
40
|
-
|
|
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.
|
|
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
|
-
*
|
|
87
|
+
* With scope
|
|
54
88
|
*/
|
|
55
|
-
static
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
|
package/support/server/client.ts
CHANGED
|
@@ -1,118 +1,159 @@
|
|
|
1
|
-
import rl from 'readline/promises';
|
|
2
|
-
import
|
|
1
|
+
import rl from 'node:readline/promises';
|
|
2
|
+
import timers from 'node:timers/promises';
|
|
3
|
+
import { Readable } from 'node:stream';
|
|
3
4
|
|
|
4
|
-
import
|
|
5
|
-
import type { CompilerServerEvent, CompilerServerEventType, CompilerServerInfo, CompilerStateType } from '../types';
|
|
5
|
+
import { ManifestContext } from '@travetto/manifest';
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import type { CompilerEvent, CompilerEventType, CompilerServerInfo, CompilerStateType } from '../types';
|
|
8
|
+
import type { CompilerLogger } from '../log';
|
|
9
|
+
import { ProcessHandle } from './process-handle';
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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 =
|
|
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',
|
|
58
|
-
const stream = await fetch(
|
|
59
|
-
|
|
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
|
-
|
|
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.
|
|
74
|
-
log('debug',
|
|
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
|
-
|
|
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(
|
|
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
|
+
}
|
package/support/server/runner.ts
CHANGED
|
@@ -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,
|
|
5
|
+
import type { ManifestContext, DeltaEvent } from '@travetto/manifest';
|
|
5
6
|
|
|
6
|
-
import type {
|
|
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.
|
|
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,
|
|
39
|
-
|
|
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
|
-
|
|
49
|
-
|
|
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
|
-
|
|
62
|
-
let kill: (() => void) | undefined;
|
|
41
|
+
const changedFiles = changed[0]?.file === '*' ? ['*'] : changed.map(ev => ev.sourceFile);
|
|
63
42
|
|
|
64
|
-
const queue = new AsyncQueue<
|
|
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.
|
|
52
|
+
TRV_MANIFEST: path.resolve(ctx.workspace.path, ctx.build.outputFolder, 'node_modules', ctx.workspace.name),
|
|
74
53
|
},
|
|
75
|
-
|
|
54
|
+
detached: true,
|
|
55
|
+
stdio: ['pipe', 1, 2, 'ipc'],
|
|
76
56
|
})
|
|
77
|
-
.on('message', msg =>
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|