@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/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, WatchEvent, ManifestModuleFolderType,
5
- ManifestModuleFileType, path, ManifestModule, watchFolders, WatchFolder, RootIndex, WatchStream
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
- type CompileWatchEvent = WatchEvent | { action: 'restart', file: string };
13
- const RESTART_SIGNAL = 'RESTART_SIGNAL';
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
- * Utils for watching
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<CompileWatchEvent> {
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: { modFolder: string, mod: string, moduleFile?: string, folderKey?: ManifestModuleFolderType, type?: ManifestModuleFileType }[] = [];
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.folderKey && file.moduleFile && file.type && file.mod in newManifest.modules) {
58
- newManifest.modules[file.mod].files[file.folderKey] ??= [];
59
- newManifest.modules[file.mod].files[file.folderKey]!.push([file.moduleFile, file.type, Date.now()]);
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
- * Get a watcher for a given compiler state
76
- * @param state
77
- * @param handler
78
- * @returns
79
- */
80
- async * watchChanges(): AsyncIterable<CompileWatchEvent> {
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
- for await (const { file: sourceFile, action, folder } of stream) {
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 (folder === RESTART_SIGNAL) {
87
- yield { action: 'restart', file: sourceFile };
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.#dirtyFiles.push({
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
- const input = this.#state.registerInput(mod, moduleFile);
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.#dirtyFiles.push({ mod: mod.name, modFolder: folder });
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
- * Watch files based on root index
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
- export type CompilerLogEvent = [level: 'info' | 'debug' | 'warn', message: string];
2
- export type CompilerLogger = (...args: CompilerLogEvent) => void;
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 levels: {
10
- debug: boolean;
11
- info: boolean;
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
- * Is object a log event
18
+ * Set level for operation
25
19
  */
26
- static isLogEvent = (o: unknown): o is CompilerLogEvent => o !== null && o !== undefined && Array.isArray(o);
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, args: string[], ...[level, msg]: CompilerLogEvent): void {
32
- const message = msg.replaceAll(process.cwd(), '.');
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, args: string[] = []): Promise<T> {
47
- const log = this.log.bind(null, scope, args);
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
  }
@@ -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
+ }