@travetto/compiler 3.4.2 → 4.0.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,11 +1,8 @@
1
- import { readFileSync } from 'fs';
2
- import { setMaxListeners } from 'events';
1
+ import { readFileSync } from 'node:fs';
3
2
 
4
3
  import {
5
- ManifestContext, ManifestModuleUtil, ManifestUtil, ManifestModuleFolderType, ManifestModuleFileType,
6
- path, ManifestModule,
4
+ ManifestModuleUtil, ManifestUtil, ManifestModuleFolderType, ManifestModuleFileType, path, ManifestModule,
7
5
  } from '@travetto/manifest';
8
- import { getManifestContext } from '@travetto/manifest/bin/context';
9
6
 
10
7
  import type { CompileStateEntry } from './types';
11
8
  import { CompilerState } from './state';
@@ -20,22 +17,14 @@ type DirtyFile = { modFolder: string, mod: string, remove?: boolean, moduleFile:
20
17
  */
21
18
  export class CompilerWatcher {
22
19
 
23
- /**
24
- * Watch state
25
- * @param state
26
- * @returns
27
- */
28
- static watch(state: CompilerState): AsyncIterable<WatchEvent<{ entry: CompileStateEntry }>> {
29
- return new CompilerWatcher(state).watchChanges();
30
- }
31
-
32
20
  #sourceHashes = new Map<string, number>();
33
- #manifestContexts = new Map<string, ManifestContext>();
34
21
  #dirtyFiles: DirtyFile[] = [];
35
22
  #state: CompilerState;
23
+ #signal: AbortSignal;
36
24
 
37
- constructor(state: CompilerState) {
25
+ constructor(state: CompilerState, signal: AbortSignal) {
38
26
  this.#state = state;
27
+ this.#signal = signal;
39
28
  }
40
29
 
41
30
  async #rebuildManifestsIfNeeded(): Promise<void> {
@@ -43,13 +32,9 @@ export class CompilerWatcher {
43
32
  return;
44
33
  }
45
34
  const mods = [...new Set(this.#dirtyFiles.map(x => x.modFolder))];
46
- const contexts = await Promise.all(mods.map(async folder => {
47
- if (!this.#manifestContexts.has(folder)) {
48
- const ctx = await getManifestContext(folder);
49
- this.#manifestContexts.set(folder, ctx);
50
- }
51
- return this.#manifestContexts.get(folder)!;
52
- }));
35
+ const contexts = await Promise.all(mods.map(folder =>
36
+ ManifestUtil.getModuleContext(this.#state.manifest, folder)
37
+ ));
53
38
 
54
39
  const files = this.#dirtyFiles.slice(0);
55
40
  this.#dirtyFiles = [];
@@ -72,7 +57,7 @@ export class CompilerWatcher {
72
57
  }
73
58
  }
74
59
  }
75
- await ManifestUtil.writeManifest(ctx, newManifest);
60
+ await ManifestUtil.writeManifest(newManifest);
76
61
  }
77
62
  // Reindex
78
63
  this.#state.manifestIndex.init(this.#state.manifestIndex.manifestFile);
@@ -80,7 +65,7 @@ export class CompilerWatcher {
80
65
 
81
66
  #getModuleMap(): Record<string, ManifestModule> {
82
67
  return Object.fromEntries(
83
- Object.values(this.#state.manifest.modules).map(x => [path.resolve(this.#state.manifest.workspacePath, x.sourceFolder), x])
68
+ Object.values(this.#state.manifest.modules).map(x => [path.resolve(this.#state.manifest.workspace.path, x.sourceFolder), x])
84
69
  );
85
70
  }
86
71
 
@@ -99,18 +84,20 @@ export class CompilerWatcher {
99
84
  * @returns
100
85
  */
101
86
  async * watchChanges(): AsyncIterable<WatchEvent<{ entry: CompileStateEntry }>> {
87
+ if (this.#signal.aborted) {
88
+ yield* [];
89
+ return;
90
+ }
91
+
102
92
  const mods = this.#getModuleMap();
103
- const ctrl = new AbortController();
104
- setMaxListeners(1000, ctrl.signal);
105
93
 
106
94
  const modules = [...this.#state.manifestIndex.getModuleList('all')].map(x => this.#state.manifestIndex.getModule(x)!);
107
95
 
108
- const stream = fileWatchEvents(this.#state.manifest, modules, ctrl.signal);
96
+ const stream = fileWatchEvents(this.#state.manifest, modules, this.#signal);
109
97
  for await (const ev of stream) {
110
98
 
111
99
  if (ev.action === 'reset') {
112
100
  yield ev;
113
- ctrl.abort();
114
101
  return;
115
102
  }
116
103
 
@@ -118,7 +105,7 @@ export class CompilerWatcher {
118
105
  const mod = mods[folder];
119
106
  const moduleFile = mod.sourceFolder ?
120
107
  (sourceFile.includes(mod.sourceFolder) ? sourceFile.split(`${mod.sourceFolder}/`)[1] : sourceFile) :
121
- sourceFile.replace(`${this.#state.manifest.workspacePath}/`, '');
108
+ sourceFile.replace(`${this.#state.manifest.workspace.path}/`, '');
122
109
 
123
110
  let entry = this.#state.getBySource(sourceFile);
124
111
 
@@ -1,63 +1,69 @@
1
- import fs from 'fs/promises';
2
- import path from 'path';
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
3
 
4
4
  import type { ManifestContext } from '@travetto/manifest';
5
5
 
6
+ import type { CompilerEventType, CompilerOp, CompilerServerInfo } from './types';
6
7
  import { LogUtil } from './log';
8
+ import { CommonUtil } from './util';
7
9
  import { CompilerSetup } from './setup';
8
10
  import { CompilerServer } from './server/server';
9
11
  import { CompilerRunner } from './server/runner';
10
- import type { CompilerOp, CompilerServerInfo } from './types';
11
- import { CompilerClientUtil } from './server/client';
12
- import { CommonUtil } from './util';
12
+ import { CompilerClient } from './server/client';
13
13
 
14
14
  // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
15
- export const main = (root: ManifestContext, ctx: ManifestContext) => {
15
+ export const main = (ctx: ManifestContext) => {
16
+ const client = new CompilerClient(ctx, LogUtil.scoped('client.main'));
17
+ const buildFolders = [ctx.build.outputFolder, ctx.build.compilerFolder];
18
+
16
19
  const ops = {
17
20
  /** Stop the server */
18
21
  async stop(): Promise<void> {
19
- if (await fetch(`${ctx.compilerUrl}/stop`).then(v => v.ok, () => false)) {
20
- console.log(`Stopped server ${ctx.workspacePath}: [${ctx.compilerUrl}]`);
22
+ if (await client.stop()) {
23
+ console.log(`Stopped server ${ctx.workspace.path}: ${client}`);
21
24
  } else {
22
- console.log(`Server not running ${ctx.workspacePath}: [${ctx.compilerUrl}]`);
25
+ console.log(`Server not running ${ctx.workspace.path}: ${client}`);
23
26
  }
24
27
  },
25
28
 
26
29
  /** Get server info */
27
- info(): Promise<CompilerServerInfo> {
28
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
29
- return fetch(ctx.compilerUrl).then(v => v.json(), () => ({ state: 'Server not running' })) as Promise<CompilerServerInfo>;
30
- },
30
+ info: (): Promise<CompilerServerInfo | undefined> => client.info(),
31
31
 
32
32
  /** Clean the server */
33
33
  async clean(): Promise<void> {
34
- const folders = [ctx.outputFolder, ctx.compilerFolder];
35
- if (await fetch(`${ctx.compilerUrl}/clean`).then(v => v.ok, () => false)) {
36
- return console.log(`Clean triggered ${ctx.workspacePath}:`, folders);
34
+ if (await client.clean()) {
35
+ return console.log(`Clean triggered ${ctx.workspace.path}:`, buildFolders);
37
36
  } else {
38
- await Promise.all(folders.map(f => fs.rm(path.resolve(ctx.workspacePath, f), { force: true, recursive: true })));
39
- return console.log(`Cleaned ${ctx.workspacePath}:`, folders);
37
+ await Promise.all(buildFolders.map(f => fs.rm(path.resolve(ctx.workspace.path, f), { force: true, recursive: true })));
38
+ return console.log(`Cleaned ${ctx.workspace.path}:`, buildFolders);
40
39
  }
41
40
  },
42
41
 
42
+ /** Stream events */
43
+ events: async (type: CompilerEventType, handler: (ev: unknown) => unknown): Promise<void> => {
44
+ LogUtil.initLogs(ctx, 'error');
45
+ for await (const ev of client.fetchEvents(type)) { await handler(ev); }
46
+ },
47
+
43
48
  /** Main entry point for compilation */
44
49
  async compile(op: CompilerOp, setupOnly = false): Promise<(mod: string) => Promise<unknown>> {
45
50
  LogUtil.initLogs(ctx, op === 'run' ? 'error' : 'info');
46
51
 
47
- const server = await new CompilerServer(root, op).listen();
52
+ const server = await new CompilerServer(ctx, op).listen();
48
53
 
49
54
  // Wait for build to be ready
50
55
  if (server) {
51
56
  await server.processEvents(async function* (signal) {
52
- const { changed, manifest } = await CompilerSetup.setup(root);
57
+ const changed = await CompilerSetup.setup(ctx);
53
58
  if (!setupOnly) {
54
- yield* CompilerRunner.runProcess(root, manifest, changed, op, signal);
55
- } else {
56
- yield* [];
59
+ yield* CompilerRunner.runProcess(ctx, changed, op, signal);
57
60
  }
58
61
  });
59
62
  } else {
60
- await CompilerClientUtil.waitForBuild(root);
63
+ const ctrl = new AbortController();
64
+ LogUtil.consumeProgressEvents(() => client.fetchEvents('progress', { until: ev => !!ev.complete, signal: ctrl.signal }));
65
+ await client.waitForState(['compile-end', 'watch-start'], 'Successfully built');
66
+ ctrl.abort();
61
67
  }
62
68
  return CommonUtil.moduleLoader(ctx);
63
69
  },
@@ -69,5 +75,4 @@ export const main = (root: ManifestContext, ctx: ManifestContext) => {
69
75
  }
70
76
  };
71
77
  return ops;
72
- };
73
-
78
+ };
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,32 @@ export class LogUtil {
14
16
 
15
17
  static logLevel: CompilerLogLevel = 'error';
16
18
 
19
+ static logProgress?: ProgressWriter;
20
+
17
21
  /**
18
22
  * Set level for operation
19
23
  */
20
24
  static initLogs(ctx: ManifestContext, defaultLevel: CompilerLogLevel): void {
21
- // Listen only if we aren't in quiet
22
- if ((process.env.TRV_BUILD || !process.env.TRV_QUIET)) {
23
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
24
- this.logLevel = (process.env.TRV_BUILD as 'debug') || defaultLevel;
25
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
26
+ const build = process.env.TRV_BUILD as CompilerLogLevel | 'none';
27
+ if (build !== 'none' && process.env.TRV_QUIET !== 'true') {
28
+ this.logLevel = build || defaultLevel;
29
+ }
30
+ this.root = ctx.workspace.path;
31
+
32
+ if (this.isLevelActive('info') && process.stdout.isTTY) {
33
+ this.logProgress = this.#logProgressEvent;
34
+ }
35
+ }
36
+
37
+ static #logProgressEvent(ev: CompilerProgressEvent): Promise<void> | void {
38
+ const pct = Math.trunc(ev.idx * 100 / ev.total);
39
+ const text = ev.complete ? '' : `Compiling [${'#'.repeat(Math.trunc(pct / 10)).padEnd(10, ' ')}] [${ev.idx}/${ev.total}] ${ev.message}`;
40
+ // Move to 1st position, and clear after text
41
+ const done = process.stdout.write(`\x1b[1G${text}\x1b[0K`);
42
+ if (!done) {
43
+ return new Promise<void>(r => process.stdout.once('drain', r));
25
44
  }
26
- this.root = ctx.workspacePath;
27
45
  }
28
46
 
29
47
  /**
@@ -36,34 +54,49 @@ export class LogUtil {
36
54
  /**
37
55
  * Log message with filtering by level
38
56
  */
39
- static log(scope: string, level: CompilerLogLevel, message: string, ...args: unknown[]): void {
40
- LogUtil.sendLogEventToConsole({ level, scope, message, args, time: Date.now() });
57
+ static log(event: CompilerLogEvent): void;
58
+ static log(scope: string, ...args: Parameters<CompilerLogger>): void;
59
+ static log(scopeOrEvent: string | CompilerLogEvent, level?: CompilerLogLevel, message?: string, ...args: unknown[]): void {
60
+ const ev = typeof scopeOrEvent === 'string' ? { scope: scopeOrEvent, level: level!, message, args } : scopeOrEvent;
61
+ if (this.isLevelActive(ev.level)) {
62
+ const params = [ev.message, ...ev.args ?? []].map(x => typeof x === 'string' ? x.replaceAll(this.root, '.') : x);
63
+ if (ev.scope) {
64
+ params.unshift(`[${ev.scope.padEnd(SCOPE_MAX, ' ')}]`);
65
+ }
66
+ params.unshift(new Date().toISOString(), `${ev.level.padEnd(5)}`);
67
+ // eslint-disable-next-line no-console
68
+ console[ev.level]!(...params);
69
+ }
41
70
  }
42
71
 
43
72
  /**
44
73
  * With logger
45
74
  */
46
75
  static withLogger<T>(scope: string, op: WithLogger<T>, basic = true): Promise<T> {
47
- const log = this.log.bind(null, scope);
76
+ const log = this.scoped(scope);
48
77
  basic && log('debug', 'Started');
49
78
  return op(log).finally(() => basic && log('debug', 'Completed'));
50
79
  }
51
80
 
52
81
  /**
53
- * Compiler log event to console
82
+ * With scope
54
83
  */
55
- static sendLogEventToConsole(ev: CompilerLogEvent): void {
56
- if (this.isLevelActive(ev.level)) {
57
- const params = [ev.message, ...ev.args ?? []].map(x => typeof x === 'string' ? x.replaceAll(LogUtil.root, '.') : x);
58
- if (ev.scope) {
59
- params.unshift(`[${ev.scope.padEnd(SCOPE_MAX, ' ')}]`);
60
- }
61
- params.unshift(`${ev.level.padEnd(5)}`);
62
- if (!/(0|false|off|no)$/i.test(process.env.TRV_LOG_TIME ?? '')) {
63
- params.unshift(new Date().toISOString());
64
- }
65
- // eslint-disable-next-line no-console
66
- console[ev.level]!(...params);
67
- }
84
+ static scoped(scope: string): CompilerLogger {
85
+ return this.log.bind(this, scope);
86
+ }
87
+
88
+ /**
89
+ * Stream Compiler log events to console
90
+ */
91
+ static async consumeLogEvents(src: AsyncIterable<CompilerLogEvent>): Promise<void> {
92
+ for await (const ev of src) { this.log(ev); }
93
+ }
94
+
95
+ /**
96
+ * Write all progress events if active
97
+ */
98
+ static async consumeProgressEvents(src: () => AsyncIterable<CompilerProgressEvent>): Promise<void> {
99
+ if (!this.logProgress) { return; }
100
+ for await (const item of src()) { await this.logProgress?.(item); }
68
101
  }
69
102
  }
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,8 @@ 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
- add(item: X, immediate = false): void {
29
- if (!immediate) {
30
- this.#queue.push(item);
31
- } else {
32
- this.#queue.unshift(item);
33
- }
31
+ add(item: X): void {
32
+ this.#queue.push(item);
34
33
  this.#ready.resolve();
35
34
  }
36
35
 
@@ -1,118 +1,131 @@
1
- import rl from 'readline/promises';
2
- import { Readable } from 'stream';
1
+ import rl from 'node:readline/promises';
2
+ import timers from 'node:timers/promises';
3
+ import { Readable } from 'node:stream';
3
4
 
4
- import type { ManifestContext } from '@travetto/manifest';
5
- import type { CompilerServerEvent, CompilerServerEventType, CompilerServerInfo, CompilerStateType } from '../types';
5
+ import { ManifestContext } from '@travetto/manifest';
6
6
 
7
- import { LogUtil } from '../log';
7
+ import type { CompilerEvent, CompilerEventType, CompilerServerInfo, CompilerStateType } from '../types';
8
+ import type { CompilerLogger } from '../log';
8
9
 
9
10
  declare global {
10
11
  interface RequestInit { timeout?: number }
11
12
  }
12
13
 
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
- }
14
+ type FetchEventsConfig<T> = {
15
+ signal?: AbortSignal;
16
+ until?: (ev: T) => boolean;
17
+ enforceIteration?: boolean;
18
+ };
24
19
 
25
20
  /**
26
21
  * Compiler Client Operations
27
22
  */
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
- }
23
+ export class CompilerClient {
24
+
25
+ #url: string;
26
+ #log?: CompilerLogger;
27
+
28
+ constructor(ctx: ManifestContext, log?: CompilerLogger) {
29
+ this.#url = ctx.build.compilerUrl.replace('localhost', '127.0.0.1');
30
+ this.#log = log;
31
+ }
32
+
33
+ toString(): string {
34
+ return `[${this.constructor.name} url=${this.#url}]`;
35
+ }
36
+
37
+ get url(): string {
38
+ return this.#url;
39
+ }
40
+
41
+ /** Get server information, if server is running */
42
+ info(): Promise<CompilerServerInfo | undefined> {
43
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
44
+ return fetch(`${this.#url}/info`).then(v => v.json(), () => undefined) as Promise<CompilerServerInfo>;
45
+ }
46
+
47
+ /** Clean the server */
48
+ clean(): Promise<boolean> {
49
+ return fetch(`${this.#url}/clean`).then(v => v.ok, () => false);
50
+ }
51
+
52
+ /** Stop server */
53
+ stop(): Promise<boolean> {
54
+ return fetch(`${this.#url}/stop`).then(v => v.ok, () => false);
41
55
  }
42
56
 
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}"`);
57
+ /** Fetch compiler events */
58
+ async * fetchEvents<
59
+ V extends CompilerEventType,
60
+ T extends (CompilerEvent & { type: V })['payload']
61
+ >(type: V, cfg: FetchEventsConfig<T> = {}): AsyncIterable<T> {
62
+ let info = await this.info();
63
+ if (!info) {
64
+ return;
65
+ }
66
+
67
+ this.#log?.('debug', `Starting watch for events of type "${type}"`);
68
+
69
+ let signal = cfg.signal;
70
+
71
+ // Ensure we capture end of process at least
72
+ if (!signal) {
73
+ const ctrl = new AbortController();
74
+ process.on('SIGINT', () => ctrl.abort());
75
+ signal = ctrl.signal;
76
+ }
77
+
51
78
 
52
- signal = getSignal(signal);
79
+ const { iteration } = info;
53
80
 
54
81
  for (; ;) {
55
82
  const ctrl = new AbortController();
56
83
  try {
57
84
  signal.addEventListener('abort', () => ctrl.abort());
58
- const stream = await fetch(`${ctx.compilerUrl}/event/${type}`, {
85
+ const stream = await fetch(`${this.#url}/event/${type}`, {
59
86
  signal: ctrl.signal,
60
87
  timeout: 1000 * 60 * 60
61
88
  });
62
89
  for await (const line of rl.createInterface(Readable.fromWeb(stream.body!))) {
63
90
  if (line.trim().charAt(0) === '{') {
64
91
  const val = JSON.parse(line);
65
- if (until?.(val)) {
66
- setTimeout(() => ctrl.abort(), 1);
92
+ if (cfg.until?.(val)) {
93
+ await timers.setTimeout(1);
94
+ ctrl.abort();
67
95
  }
68
96
  yield val;
69
97
  }
70
98
  }
71
99
  } catch (err) { }
72
100
 
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}"`);
101
+ await timers.setTimeout(1);
102
+
103
+ info = await this.info();
104
+
105
+ if (ctrl.signal.aborted || !info || (cfg.enforceIteration && info.iteration !== iteration)) { // If health check fails, or aborted
106
+ this.#log?.('debug', `Stopping watch for events of type "${type}"`);
75
107
  return;
76
108
  } else {
77
- log('debug', `Restarting watch for events of type "${type}"`);
109
+ this.#log?.('debug', `Restarting watch for events of type "${type}"`);
78
110
  }
79
111
  }
80
112
  }
81
113
 
82
- /**
83
- * Wait for one of N states to be achieved
84
- */
85
- static async waitForState(ctx: ManifestContext, states: CompilerStateType[], signal?: AbortSignal): Promise<void> {
114
+ /** Wait for one of N states to be achieved */
115
+ async waitForState(states: CompilerStateType[], message?: string, signal?: AbortSignal): Promise<void> {
86
116
  const set = new Set(states);
87
- const existing = await this.getServerInfo(ctx);
88
- log('debug', `Existing: ${JSON.stringify(existing)}`);
117
+ const existing = await this.info();
118
+ this.#log?.('debug', `Existing: ${JSON.stringify(existing)}`);
89
119
  if (existing && set.has(existing.state)) {
90
- log('debug', `Waited for state, ${existing.state} in server info`);
120
+ this.#log?.('debug', `Waited for state, ${existing.state} in server info`);
91
121
  return;
92
122
  }
93
123
  // 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);
124
+ this.#log?.('debug', `Waiting for states, ${states.join(', ')}`);
125
+ for await (const _ of this.fetchEvents('state', { signal, until: s => set.has(s.state) })) { }
126
+ this.#log?.('debug', `Found state, one of ${states.join(', ')} `);
127
+ if (message) {
128
+ this.#log?.('info', message);
108
129
  }
109
130
  }
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
131
  }