@travetto/compiler 3.3.1 → 3.4.0-rc.0
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 +42 -44
- package/bin/{trv.js → common.js} +22 -27
- package/bin/trvc.js +62 -0
- package/package.json +7 -6
- package/src/compiler.ts +40 -24
- package/src/event.ts +7 -0
- package/src/internal/watch-core.ts +104 -0
- package/src/log.ts +8 -9
- package/src/state.ts +4 -4
- package/src/types.ts +0 -6
- package/src/watch.ts +58 -97
- package/support/entry.trvc.ts +39 -0
- package/support/log.ts +40 -29
- package/support/queue.ts +41 -0
- package/support/server/client.ts +118 -0
- package/support/server/runner.ts +104 -0
- package/support/server/server.ts +189 -0
- package/support/setup.ts +229 -0
- package/support/types.ts +28 -0
- package/support/util.ts +108 -0
- package/tsconfig.trv.json +7 -0
- package/support/launcher.ts +0 -160
- package/support/lock-pinger.ts +0 -25
- package/support/lock.ts +0 -234
- package/support/transpile.ts +0 -256
- /package/support/{compiler-entry.ts → entry.compiler.ts} +0 -0
package/src/watch.ts
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import { readFileSync } from 'fs';
|
|
2
|
+
import { setMaxListeners } from 'events';
|
|
2
3
|
|
|
3
4
|
import {
|
|
4
|
-
ManifestContext, ManifestModuleUtil, ManifestUtil,
|
|
5
|
-
|
|
5
|
+
ManifestContext, ManifestModuleUtil, ManifestUtil, ManifestModuleFolderType, ManifestModuleFileType,
|
|
6
|
+
path, ManifestModule,
|
|
6
7
|
} from '@travetto/manifest';
|
|
7
8
|
import { getManifestContext } from '@travetto/manifest/bin/context';
|
|
8
9
|
|
|
10
|
+
import type { CompileStateEntry } from './types';
|
|
9
11
|
import { CompilerState } from './state';
|
|
10
12
|
import { CompilerUtil } from './util';
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
import { WatchEvent, fileWatchEvents } from './internal/watch-core';
|
|
15
|
+
|
|
16
|
+
type DirtyFile = { modFolder: string, mod: string, remove?: boolean, moduleFile: string, folderKey: ManifestModuleFolderType, type: ManifestModuleFileType };
|
|
14
17
|
|
|
15
18
|
/**
|
|
16
|
-
*
|
|
19
|
+
* Watch support, based on compiler state and manifest details
|
|
17
20
|
*/
|
|
18
21
|
export class CompilerWatcher {
|
|
19
22
|
|
|
@@ -22,13 +25,13 @@ export class CompilerWatcher {
|
|
|
22
25
|
* @param state
|
|
23
26
|
* @returns
|
|
24
27
|
*/
|
|
25
|
-
static watch(state: CompilerState): AsyncIterable<
|
|
28
|
+
static watch(state: CompilerState): AsyncIterable<WatchEvent<{ entry: CompileStateEntry }>> {
|
|
26
29
|
return new CompilerWatcher(state).watchChanges();
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
#sourceHashes = new Map<string, number>();
|
|
30
33
|
#manifestContexts = new Map<string, ManifestContext>();
|
|
31
|
-
#dirtyFiles:
|
|
34
|
+
#dirtyFiles: DirtyFile[] = [];
|
|
32
35
|
#state: CompilerState;
|
|
33
36
|
|
|
34
37
|
constructor(state: CompilerState) {
|
|
@@ -48,15 +51,25 @@ export class CompilerWatcher {
|
|
|
48
51
|
return this.#manifestContexts.get(folder)!;
|
|
49
52
|
}));
|
|
50
53
|
|
|
51
|
-
const files = this.#dirtyFiles;
|
|
54
|
+
const files = this.#dirtyFiles.slice(0);
|
|
52
55
|
this.#dirtyFiles = [];
|
|
53
56
|
|
|
54
57
|
for (const ctx of [...contexts, this.#state.manifest]) {
|
|
55
58
|
const newManifest = await ManifestUtil.buildManifest(ctx);
|
|
56
59
|
for (const file of files) {
|
|
57
|
-
if (file.
|
|
58
|
-
newManifest.modules[file.mod].files[file.folderKey] ??= [];
|
|
59
|
-
|
|
60
|
+
if (file.mod in newManifest.modules) {
|
|
61
|
+
const modFiles = newManifest.modules[file.mod].files[file.folderKey] ??= [];
|
|
62
|
+
const idx = modFiles.findIndex(x => x[0] === file.moduleFile);
|
|
63
|
+
|
|
64
|
+
if (!file.remove && idx < 0) {
|
|
65
|
+
modFiles.push([file.moduleFile, file.type, Date.now()]);
|
|
66
|
+
} else if (idx >= 0) {
|
|
67
|
+
if (file.remove) {
|
|
68
|
+
modFiles.splice(idx, 1);
|
|
69
|
+
} else {
|
|
70
|
+
modFiles[idx] = [file.moduleFile, file.type, Date.now()];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
60
73
|
}
|
|
61
74
|
}
|
|
62
75
|
await ManifestUtil.writeManifest(ctx, newManifest);
|
|
@@ -71,131 +84,79 @@ export class CompilerWatcher {
|
|
|
71
84
|
);
|
|
72
85
|
}
|
|
73
86
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const stream = this.#watchFiles();
|
|
87
|
+
#addDirtyFile(mod: ManifestModule, folder: string, moduleFile: string, remove = false): void {
|
|
88
|
+
this.#dirtyFiles.push({
|
|
89
|
+
mod: mod.name, modFolder: folder, remove, moduleFile,
|
|
90
|
+
folderKey: ManifestModuleUtil.getFolderKey(moduleFile),
|
|
91
|
+
type: ManifestModuleUtil.getFileType(moduleFile),
|
|
92
|
+
});
|
|
93
|
+
}
|
|
82
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Get a watcher for a given compiler state
|
|
97
|
+
* @param state
|
|
98
|
+
* @param handler
|
|
99
|
+
* @returns
|
|
100
|
+
*/
|
|
101
|
+
async * watchChanges(): AsyncIterable<WatchEvent<{ entry: CompileStateEntry }>> {
|
|
83
102
|
const mods = this.#getModuleMap();
|
|
84
|
-
|
|
103
|
+
const ctrl = new AbortController();
|
|
104
|
+
setMaxListeners(1000, ctrl.signal);
|
|
105
|
+
|
|
106
|
+
const modules = [...this.#state.manifestIndex.getModuleList('all')].map(x => this.#state.manifestIndex.getModule(x)!);
|
|
107
|
+
|
|
108
|
+
const stream = fileWatchEvents(this.#state.manifest, modules, ctrl.signal);
|
|
109
|
+
for await (const ev of stream) {
|
|
85
110
|
|
|
86
|
-
if (
|
|
87
|
-
yield
|
|
111
|
+
if (ev.action === 'reset') {
|
|
112
|
+
yield ev;
|
|
113
|
+
ctrl.abort();
|
|
88
114
|
return;
|
|
89
115
|
}
|
|
90
116
|
|
|
117
|
+
const { action, file: sourceFile, folder } = ev;
|
|
91
118
|
const mod = mods[folder];
|
|
92
119
|
const moduleFile = mod.sourceFolder ?
|
|
93
120
|
(sourceFile.includes(mod.sourceFolder) ? sourceFile.split(`${mod.sourceFolder}/`)[1] : sourceFile) :
|
|
94
121
|
sourceFile.replace(`${this.#state.manifest.workspacePath}/`, '');
|
|
122
|
+
|
|
123
|
+
let entry = this.#state.getBySource(sourceFile);
|
|
124
|
+
|
|
95
125
|
switch (action) {
|
|
96
126
|
case 'create': {
|
|
97
127
|
const fileType = ManifestModuleUtil.getFileType(moduleFile);
|
|
98
|
-
this.#
|
|
99
|
-
mod: mod.name,
|
|
100
|
-
modFolder: folder,
|
|
101
|
-
moduleFile,
|
|
102
|
-
folderKey: ManifestModuleUtil.getFolderKey(moduleFile),
|
|
103
|
-
type: ManifestModuleUtil.getFileType(moduleFile)
|
|
104
|
-
});
|
|
128
|
+
this.#addDirtyFile(mod, folder, moduleFile);
|
|
105
129
|
if (CompilerUtil.validFile(fileType)) {
|
|
106
|
-
await this.#rebuildManifestsIfNeeded();
|
|
107
|
-
|
|
108
130
|
const hash = CompilerUtil.naiveHash(readFileSync(sourceFile, 'utf8'));
|
|
109
|
-
|
|
131
|
+
entry = this.#state.registerInput(mod, moduleFile);
|
|
110
132
|
this.#sourceHashes.set(sourceFile, hash);
|
|
111
|
-
yield { action, file: input, folder };
|
|
112
133
|
}
|
|
113
134
|
break;
|
|
114
135
|
}
|
|
115
136
|
case 'update': {
|
|
116
|
-
await this.#rebuildManifestsIfNeeded();
|
|
117
|
-
const entry = this.#state.getBySource(sourceFile);
|
|
118
137
|
if (entry) {
|
|
119
138
|
const hash = CompilerUtil.naiveHash(readFileSync(sourceFile, 'utf8'));
|
|
120
139
|
if (this.#sourceHashes.get(sourceFile) !== hash) {
|
|
121
140
|
this.#state.resetInputSource(entry.input);
|
|
122
141
|
this.#sourceHashes.set(sourceFile, hash);
|
|
123
|
-
yield { action, file: entry.input, folder };
|
|
124
142
|
}
|
|
125
143
|
}
|
|
126
144
|
break;
|
|
127
145
|
}
|
|
128
146
|
case 'delete': {
|
|
129
|
-
const entry = this.#state.getBySource(sourceFile);
|
|
130
147
|
if (entry) {
|
|
131
148
|
this.#state.removeInput(entry.input);
|
|
132
149
|
if (entry.output) {
|
|
133
|
-
this.#
|
|
134
|
-
yield { action, file: entry.output, folder };
|
|
150
|
+
this.#addDirtyFile(mod, folder, moduleFile, true);
|
|
135
151
|
}
|
|
136
152
|
}
|
|
137
153
|
}
|
|
138
154
|
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
155
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
#watchFiles(): WatchStream {
|
|
146
|
-
const idx = this.#state.manifestIndex;
|
|
147
|
-
const modules = [...idx.getModuleList('all')].map(x => idx.getModule(x)!);
|
|
148
|
-
const options: Partial<WatchFolder> = {
|
|
149
|
-
filter: (ev: WatchEvent): boolean => {
|
|
150
|
-
const type = ManifestModuleUtil.getFileType(ev.file);
|
|
151
|
-
return type === 'ts' || type === 'typings' || type === 'js' || type === 'package-json';
|
|
152
|
-
},
|
|
153
|
-
ignore: ['node_modules', '**/.trv_*'],
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
const moduleFolders: WatchFolder[] = modules
|
|
157
|
-
.filter(x => !idx.manifest.monoRepo || x.sourcePath !== idx.manifest.workspacePath)
|
|
158
|
-
.map(x => ({ src: x.sourcePath, target: x.sourcePath }));
|
|
159
|
-
|
|
160
|
-
// Add monorepo folders
|
|
161
|
-
if (idx.manifest.monoRepo) {
|
|
162
|
-
const mono = modules.find(x => x.sourcePath === idx.manifest.workspacePath)!;
|
|
163
|
-
for (const folder of Object.keys(mono.files)) {
|
|
164
|
-
if (!folder.startsWith('$')) {
|
|
165
|
-
moduleFolders.push({ src: path.resolve(mono.sourcePath, folder), target: mono.sourcePath });
|
|
166
|
-
}
|
|
156
|
+
if (entry) {
|
|
157
|
+
await this.#rebuildManifestsIfNeeded();
|
|
158
|
+
yield { action, file: entry.source, folder, entry };
|
|
167
159
|
}
|
|
168
|
-
moduleFolders.push({ src: mono.sourcePath, target: mono.sourcePath, immediate: true });
|
|
169
160
|
}
|
|
170
|
-
|
|
171
|
-
// Watch output folders
|
|
172
|
-
const outputWatch = (root: string, sources: string[]): WatchFolder => {
|
|
173
|
-
const valid = new Set(sources.map(src => path.resolve(root, src)));
|
|
174
|
-
return {
|
|
175
|
-
src: root, target: RESTART_SIGNAL, immediate: true, includeHidden: true,
|
|
176
|
-
filter: ev => ev.action === 'delete' && valid.has(path.resolve(root, ev.file))
|
|
177
|
-
};
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
const topLevelFiles = (root: string, files: string[]): WatchFolder => {
|
|
181
|
-
const valid = new Set(files.map(src => path.resolve(root, src)));
|
|
182
|
-
return {
|
|
183
|
-
src: root, target: RESTART_SIGNAL, immediate: true,
|
|
184
|
-
filter: ev => valid.has(path.resolve(root, ev.file))
|
|
185
|
-
};
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
moduleFolders.push(
|
|
189
|
-
outputWatch(RootIndex.manifest.workspacePath, [
|
|
190
|
-
RootIndex.manifest.outputFolder,
|
|
191
|
-
RootIndex.manifest.compilerFolder
|
|
192
|
-
]),
|
|
193
|
-
topLevelFiles(RootIndex.manifest.workspacePath, [
|
|
194
|
-
'package.json',
|
|
195
|
-
'package-lock.json'
|
|
196
|
-
])
|
|
197
|
-
);
|
|
198
|
-
|
|
199
|
-
return watchFolders(moduleFolders, options);
|
|
200
161
|
}
|
|
201
162
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ManifestContext } from '@travetto/manifest';
|
|
2
|
+
|
|
3
|
+
import { LogUtil } from './log';
|
|
4
|
+
import { CompilerSetup } from './setup';
|
|
5
|
+
import { CompilerServer } from './server/server';
|
|
6
|
+
import { CompilerRunner } from './server/runner';
|
|
7
|
+
import type { BuildOp, EntryOp } from './types';
|
|
8
|
+
import { CompilerClientUtil } from './server/client';
|
|
9
|
+
import { CommonUtil } from './util';
|
|
10
|
+
|
|
11
|
+
async function build(root: ManifestContext, op: BuildOp): Promise<void> {
|
|
12
|
+
const server = await new CompilerServer(root, op).listen();
|
|
13
|
+
|
|
14
|
+
// Wait for build to be ready
|
|
15
|
+
if (server) {
|
|
16
|
+
await server.processEvents(async function* (signal) {
|
|
17
|
+
const { changed, manifest } = await CompilerSetup.setup(root);
|
|
18
|
+
yield* CompilerRunner.runProcess(root, manifest, changed, op === 'watch', signal);
|
|
19
|
+
});
|
|
20
|
+
} else {
|
|
21
|
+
await CompilerClientUtil.waitForBuild(root);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Main entry point for trv.js
|
|
27
|
+
*/
|
|
28
|
+
export async function main(ctx: ManifestContext, root: ManifestContext, op: EntryOp, args: (string | undefined)[] = []): Promise<((mod: string) => Promise<unknown>) | undefined> {
|
|
29
|
+
LogUtil.initLogs(ctx, op);
|
|
30
|
+
switch (op) {
|
|
31
|
+
case 'manifest': await CompilerSetup.exportManifest(ctx, ...args.filter(x => !x?.startsWith('-'))); return;
|
|
32
|
+
case 'watch':
|
|
33
|
+
case 'build': await build(root, op); return;
|
|
34
|
+
case 'run': {
|
|
35
|
+
await build(root, 'build');
|
|
36
|
+
return CommonUtil.moduleLoader(ctx);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
package/support/log.ts
CHANGED
|
@@ -1,51 +1,62 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import type { ManifestContext } from '@travetto/manifest';
|
|
2
|
+
import type { CompilerLogEvent, CompilerLogLevel, EntryOp } from './types';
|
|
3
|
+
|
|
4
|
+
export type CompilerLogger = (level: CompilerLogLevel, message: string, ...args: unknown[]) => void;
|
|
3
5
|
export type WithLogger<T> = (log: CompilerLogger) => Promise<T>;
|
|
4
6
|
|
|
7
|
+
const LEVEL_TO_PRI = Object.fromEntries((['debug', 'info', 'warn', 'error'] as const).map((x, i) => [x, i + 1]));
|
|
8
|
+
|
|
5
9
|
const SCOPE_MAX = 15;
|
|
6
10
|
|
|
7
11
|
export class LogUtil {
|
|
8
12
|
|
|
9
|
-
static
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
warn: boolean;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
static set level(value: string) {
|
|
16
|
-
this.levels = {
|
|
17
|
-
warn: /^(debug|info|warn)$/.test(value),
|
|
18
|
-
info: /^(debug|info)$/.test(value),
|
|
19
|
-
debug: /^debug$/.test(value),
|
|
20
|
-
};
|
|
21
|
-
}
|
|
13
|
+
static root = process.cwd();
|
|
14
|
+
|
|
15
|
+
static logLevel?: CompilerLogLevel;
|
|
22
16
|
|
|
23
17
|
/**
|
|
24
|
-
*
|
|
18
|
+
* Set level for operation
|
|
25
19
|
*/
|
|
26
|
-
static
|
|
20
|
+
static initLogs(ctx: ManifestContext, op: EntryOp): void {
|
|
21
|
+
// Listen only if we aren't in quiet
|
|
22
|
+
if ((process.env.TRV_BUILD || !process.env.TRV_QUIET) && op !== 'manifest') {
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
24
|
+
this.logLevel = (process.env.TRV_BUILD as 'debug') ?? (op === 'run' ? 'warn' : 'info');
|
|
25
|
+
}
|
|
26
|
+
this.root = ctx.workspacePath;
|
|
27
|
+
}
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
30
|
* Log message with filtering by level
|
|
30
31
|
*/
|
|
31
|
-
static log(scope: string,
|
|
32
|
-
|
|
33
|
-
if (LogUtil.levels[level]) {
|
|
34
|
-
const params = [`[${scope.padEnd(SCOPE_MAX, ' ')}]`, ...args, message];
|
|
35
|
-
if (!/(0|false|off|no)$/i.test(process.env.TRV_LOG_TIME ?? '')) {
|
|
36
|
-
params.unshift(new Date().toISOString());
|
|
37
|
-
}
|
|
38
|
-
// eslint-disable-next-line no-console
|
|
39
|
-
console[level]!(...params);
|
|
40
|
-
}
|
|
32
|
+
static log(scope: string, level: CompilerLogLevel, message: string, ...args: unknown[]): void {
|
|
33
|
+
LogUtil.sendLogEventToConsole({ level, scope, message, args, time: Date.now() });
|
|
41
34
|
}
|
|
42
35
|
|
|
43
36
|
/**
|
|
44
37
|
* With logger
|
|
45
38
|
*/
|
|
46
|
-
static withLogger<T>(scope: string, op: WithLogger<T>, basic = true
|
|
47
|
-
const log = this.log.bind(null, scope
|
|
39
|
+
static withLogger<T>(scope: string, op: WithLogger<T>, basic = true): Promise<T> {
|
|
40
|
+
const log = this.log.bind(null, scope);
|
|
48
41
|
basic && log('debug', 'Started');
|
|
49
42
|
return op(log).finally(() => basic && log('debug', 'Completed'));
|
|
50
43
|
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Compiler log event to console
|
|
47
|
+
*/
|
|
48
|
+
static sendLogEventToConsole(ev: CompilerLogEvent): void {
|
|
49
|
+
if (this.logLevel && LEVEL_TO_PRI[this.logLevel] <= LEVEL_TO_PRI[ev.level]) {
|
|
50
|
+
const params = [ev.message, ...ev.args ?? []].map(x => typeof x === 'string' ? x.replaceAll(LogUtil.root, '.') : x);
|
|
51
|
+
if (ev.scope) {
|
|
52
|
+
params.unshift(`[${ev.scope.padEnd(SCOPE_MAX, ' ')}]`);
|
|
53
|
+
}
|
|
54
|
+
params.unshift(`${ev.level.padEnd(5)}`);
|
|
55
|
+
if (!/(0|false|off|no)$/i.test(process.env.TRV_LOG_TIME ?? '')) {
|
|
56
|
+
params.unshift(new Date().toISOString());
|
|
57
|
+
}
|
|
58
|
+
// eslint-disable-next-line no-console
|
|
59
|
+
console[ev.level]!(...params);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
51
62
|
}
|
package/support/queue.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
type PromiseResolver<T> = { resolve: (v: T) => void, reject: (err?: unknown) => void };
|
|
2
|
+
|
|
3
|
+
function resolvablePromise<T = void>(): Promise<T> & PromiseResolver<T> {
|
|
4
|
+
let ops: PromiseResolver<T>;
|
|
5
|
+
const prom = new Promise<T>((resolve, reject) => ops = { resolve, reject });
|
|
6
|
+
return Object.assign(prom, ops!);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class AsyncQueue<X> implements AsyncIterator<X>, AsyncIterable<X> {
|
|
10
|
+
#queue: X[] = [];
|
|
11
|
+
#done = false;
|
|
12
|
+
#ready = resolvablePromise();
|
|
13
|
+
|
|
14
|
+
constructor(signal?: AbortSignal) {
|
|
15
|
+
signal?.addEventListener('abort', () => this.close());
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
[Symbol.asyncIterator](): AsyncIterator<X> { return this; }
|
|
19
|
+
|
|
20
|
+
async next(): Promise<IteratorResult<X>> {
|
|
21
|
+
while (!this.#done && !this.#queue.length) {
|
|
22
|
+
await this.#ready;
|
|
23
|
+
this.#ready = resolvablePromise();
|
|
24
|
+
}
|
|
25
|
+
return { value: (this.#queue.length ? this.#queue.shift() : undefined)!, done: this.#done };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
add(item: X, immediate = false): void {
|
|
29
|
+
if (!immediate) {
|
|
30
|
+
this.#queue.push(item);
|
|
31
|
+
} else {
|
|
32
|
+
this.#queue.unshift(item);
|
|
33
|
+
}
|
|
34
|
+
this.#ready.resolve();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
close(): void {
|
|
38
|
+
this.#done = true;
|
|
39
|
+
this.#ready.resolve();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import rl from 'readline/promises';
|
|
2
|
+
import { Readable } from 'stream';
|
|
3
|
+
|
|
4
|
+
import type { ManifestContext } from '@travetto/manifest';
|
|
5
|
+
import type { CompilerServerEvent, CompilerServerEventType, CompilerServerInfo, CompilerStateType } from '../types';
|
|
6
|
+
|
|
7
|
+
import { LogUtil } from '../log';
|
|
8
|
+
|
|
9
|
+
declare global {
|
|
10
|
+
interface RequestInit { timeout?: number }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const log = LogUtil.log.bind(LogUtil, 'compiler-client');
|
|
14
|
+
|
|
15
|
+
function getSignal(input?: AbortSignal): AbortSignal {
|
|
16
|
+
// Ensure we capture end of process at least
|
|
17
|
+
if (!input) {
|
|
18
|
+
const ctrl = new AbortController();
|
|
19
|
+
process.on('SIGINT', () => ctrl.abort());
|
|
20
|
+
input = ctrl.signal;
|
|
21
|
+
}
|
|
22
|
+
return input;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Compiler Client Operations
|
|
27
|
+
*/
|
|
28
|
+
export class CompilerClientUtil {
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get server information, if server is running
|
|
32
|
+
*/
|
|
33
|
+
static async getServerInfo(ctx: ManifestContext): Promise<CompilerServerInfo | undefined> {
|
|
34
|
+
try {
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
36
|
+
const res = (await fetch(`${ctx.compilerUrl}/info`).then(v => v.json())) as CompilerServerInfo;
|
|
37
|
+
return res;
|
|
38
|
+
} catch (err) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Fetch compiler events
|
|
45
|
+
*/
|
|
46
|
+
static async * fetchEvents<
|
|
47
|
+
V extends CompilerServerEventType,
|
|
48
|
+
T extends (CompilerServerEvent & { type: V })['payload']
|
|
49
|
+
>(ctx: ManifestContext, type: V, signal?: AbortSignal, until?: (ev: T) => boolean): AsyncIterable<T> {
|
|
50
|
+
log('debug', `Starting watch for events of type "${type}"`);
|
|
51
|
+
|
|
52
|
+
signal = getSignal(signal);
|
|
53
|
+
|
|
54
|
+
for (; ;) {
|
|
55
|
+
const ctrl = new AbortController();
|
|
56
|
+
try {
|
|
57
|
+
signal.addEventListener('abort', () => ctrl.abort());
|
|
58
|
+
const stream = await fetch(`${ctx.compilerUrl}/event/${type}`, {
|
|
59
|
+
signal: ctrl.signal,
|
|
60
|
+
timeout: 1000 * 60 * 60
|
|
61
|
+
});
|
|
62
|
+
for await (const line of rl.createInterface(Readable.fromWeb(stream.body!))) {
|
|
63
|
+
if (line.trim().charAt(0) === '{') {
|
|
64
|
+
const val = JSON.parse(line);
|
|
65
|
+
if (until?.(val)) {
|
|
66
|
+
setTimeout(() => ctrl.abort(), 1);
|
|
67
|
+
}
|
|
68
|
+
yield val;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
} catch (err) { }
|
|
72
|
+
|
|
73
|
+
if (ctrl.signal.aborted || !(await this.getServerInfo(ctx))) { // If health check fails, or aborted
|
|
74
|
+
log('debug', `Stopping watch for events of type "${type}"`);
|
|
75
|
+
return;
|
|
76
|
+
} else {
|
|
77
|
+
log('debug', `Restarting watch for events of type "${type}"`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Wait for one of N states to be achieved
|
|
84
|
+
*/
|
|
85
|
+
static async waitForState(ctx: ManifestContext, states: CompilerStateType[], signal?: AbortSignal): Promise<void> {
|
|
86
|
+
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
|
+
// Loop until
|
|
94
|
+
log('debug', `Waiting for states, ${states.join(', ')}`);
|
|
95
|
+
for await (const _ of this.fetchEvents(ctx, 'state', signal, s => set.has(s.state))) { }
|
|
96
|
+
log('debug', `Found state, one of ${states.join(', ')}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Stream logs
|
|
101
|
+
*/
|
|
102
|
+
static async streamLogs(ctx: ManifestContext, signal?: AbortSignal): Promise<void> {
|
|
103
|
+
if (!LogUtil.logLevel) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
for await (const ev of this.fetchEvents(ctx, 'log', signal!)) {
|
|
107
|
+
LogUtil.sendLogEventToConsole(ev);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
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
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import cp from 'child_process';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
import type { ManifestContext, ManifestRoot, DeltaEvent } from '@travetto/manifest';
|
|
5
|
+
|
|
6
|
+
import type { CompilerProgressEvent, CompilerServerEvent } from '../types';
|
|
7
|
+
import { AsyncQueue } from '../queue';
|
|
8
|
+
import { LogUtil } from '../log';
|
|
9
|
+
import { CommonUtil } from '../util';
|
|
10
|
+
import { CompilerClientUtil } from './client';
|
|
11
|
+
|
|
12
|
+
const log = LogUtil.log.bind(null, 'compiler-exec');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Running the compiler
|
|
16
|
+
*/
|
|
17
|
+
export class CompilerRunner {
|
|
18
|
+
|
|
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
|
+
/**
|
|
36
|
+
* Run compile process
|
|
37
|
+
*/
|
|
38
|
+
static async* runProcess(ctx: ManifestContext, manifest: ManifestRoot, changed: DeltaEvent[], watch: boolean, signal: AbortSignal): AsyncIterable<CompilerServerEvent> {
|
|
39
|
+
if (!changed.length && !watch) {
|
|
40
|
+
yield { type: 'state', payload: { state: 'compile-end' } };
|
|
41
|
+
log('debug', 'Skipped');
|
|
42
|
+
return;
|
|
43
|
+
} else {
|
|
44
|
+
log('debug', `Started watch=${watch} changed=${changed.slice(0, 10).map(x => `${x.module}/${x.file}`)}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Track progress
|
|
48
|
+
this.trackProgress(ctx, CompilerClientUtil.fetchEvents(ctx, 'progress', signal, ev => !!ev.complete));
|
|
49
|
+
|
|
50
|
+
const compiler = path.resolve(ctx.workspacePath, ctx.compilerFolder);
|
|
51
|
+
const main = path.resolve(compiler, 'node_modules', '@travetto/compiler/support/entry.compiler.js');
|
|
52
|
+
const deltaFile = path.resolve(ctx.workspacePath, ctx.toolFolder, 'manifest-delta.json');
|
|
53
|
+
|
|
54
|
+
const changedFiles = changed[0]?.file === '*' ? ['*'] : changed.map(ev =>
|
|
55
|
+
path.resolve(manifest.workspacePath, manifest.modules[ev.module].sourceFolder, ev.file)
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
let proc: cp.ChildProcess | undefined;
|
|
59
|
+
let kill: (() => void) | undefined;
|
|
60
|
+
|
|
61
|
+
const queue = new AsyncQueue<CompilerServerEvent>(signal);
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
await CommonUtil.writeTextFile(deltaFile, changedFiles.join('\n'));
|
|
65
|
+
|
|
66
|
+
log('info', 'Launching compiler');
|
|
67
|
+
proc = cp.spawn(process.argv0, [main, deltaFile, `${watch}`], {
|
|
68
|
+
env: {
|
|
69
|
+
...process.env,
|
|
70
|
+
TRV_MANIFEST: path.resolve(ctx.workspacePath, ctx.outputFolder, 'node_modules', ctx.mainModule),
|
|
71
|
+
},
|
|
72
|
+
stdio: ['pipe', 'pipe', 2, 'ipc'],
|
|
73
|
+
})
|
|
74
|
+
.on('message', msg => {
|
|
75
|
+
if (msg && typeof msg === 'object' && 'type' in msg) {
|
|
76
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
77
|
+
queue.add(msg as CompilerServerEvent);
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
.on('exit', code => {
|
|
81
|
+
if (code !== null && code > 0) {
|
|
82
|
+
log('error', 'Failed during compilation');
|
|
83
|
+
}
|
|
84
|
+
queue.close();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
kill = (): void => { proc?.kill('SIGKILL'); };
|
|
88
|
+
signal.addEventListener('abort', kill);
|
|
89
|
+
process.on('exit', kill);
|
|
90
|
+
|
|
91
|
+
yield* queue;
|
|
92
|
+
|
|
93
|
+
log('debug', `exit code: ${proc?.exitCode}`);
|
|
94
|
+
log('debug', 'Finished');
|
|
95
|
+
} finally {
|
|
96
|
+
if (proc?.killed === false) {
|
|
97
|
+
proc.kill('SIGKILL');
|
|
98
|
+
}
|
|
99
|
+
if (kill) {
|
|
100
|
+
process.off('exit', kill);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|