@travetto/runtime 7.0.0-rc.2 → 7.0.0-rc.4

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 CHANGED
@@ -19,6 +19,7 @@ Runtime is the foundation of all [Travetto](https://travetto.dev) applications.
19
19
  * Standard Error Support
20
20
  * Console Management
21
21
  * Resource Access
22
+ * JSON Utilities
22
23
  * Common Utilities
23
24
  * Time Utilities
24
25
  * Process Execution
@@ -36,10 +37,10 @@ class $Runtime {
36
37
  constructor(idx: ManifestIndex, resourceOverrides?: Record<string, string>);
37
38
  /** Get env name, with support for the default env */
38
39
  get env(): string | undefined;
39
- /** Are we in development mode */
40
+ /** Are we in production mode */
40
41
  get production(): boolean;
41
- /** Is the app in dynamic mode? */
42
- get dynamic(): boolean;
42
+ /** Get environment type mode */
43
+ get envType(): 'production' | 'development' | 'test';
43
44
  /** Get debug value */
44
45
  get debug(): false | string;
45
46
  /** Manifest main */
@@ -95,10 +96,6 @@ interface EnvData {
95
96
  * Special role to run as, used to access additional files from the manifest during runtime.
96
97
  */
97
98
  TRV_ROLE: Role;
98
- /**
99
- * Whether or not to run the program in dynamic mode, allowing for real-time updates
100
- */
101
- TRV_DYNAMIC: boolean;
102
99
  /**
103
100
  * The folders to use for resource lookup
104
101
  */
@@ -258,9 +255,18 @@ $ DEBUG=express:*,@travetto/web npx trv run web
258
255
  ## Resource Access
259
256
  The primary access patterns for resources, is to directly request a file, and to resolve that file either via file-system look up or leveraging the [Manifest](https://github.com/travetto/travetto/tree/main/module/manifest#readme "Support for project indexing, manifesting, along with file watching")'s data for what resources were found at manifesting time.
260
257
 
261
- The [FileLoader](https://github.com/travetto/travetto/tree/main/module/runtime/src/file-loader.ts#L11) allows for accessing information about the resources, and subsequently reading the file as text/binary or to access the resource as a `Readable` stream. If a file is not found, it will throw an [AppError](https://github.com/travetto/travetto/tree/main/module/runtime/src/error.ts#L26) with a category of 'notfound'.
258
+ The [FileLoader](https://github.com/travetto/travetto/tree/main/module/runtime/src/file-loader.ts#L12) allows for accessing information about the resources, and subsequently reading the file as text/binary or to access the resource as a `Readable` stream. If a file is not found, it will throw an [AppError](https://github.com/travetto/travetto/tree/main/module/runtime/src/error.ts#L26) with a category of 'notfound'.
259
+
260
+ The [FileLoader](https://github.com/travetto/travetto/tree/main/module/runtime/src/file-loader.ts#L12) also supports tying itself to [Env](https://github.com/travetto/travetto/tree/main/module/runtime/src/env.ts#L114)'s `TRV_RESOURCES` information on where to attempt to find a requested resource.
261
+
262
+ ## JSON Utilities
263
+ The framework provides utilities for working with JSON data. This module provides methods for reading and writing JSON files, as well as serializing and deserializing JSON data. It also provides support for working with Base64 encoded data for web safe transfer. The primary goal is ease of use, but also a centralized location for performance and security improvements over time.
262
264
 
263
- The [FileLoader](https://github.com/travetto/travetto/tree/main/module/runtime/src/file-loader.ts#L11) also supports tying itself to [Env](https://github.com/travetto/travetto/tree/main/module/runtime/src/env.ts#L114)'s `TRV_RESOURCES` information on where to attempt to find a requested resource.
265
+ * `parseSafe(input: string | Buffer)` parses JSON safely from a string or Buffer.
266
+ * `stringifyBase64(value: any)` encodes a JSON value as a base64 encoded string.
267
+ * `parseBase64(input: string)` decodes a JSON value from a base64 encoded string.
268
+ * `readFile(file: string)` reads a JSON file asynchronously.
269
+ * `readFileSync(file: string, onMissing?: any)` reads a JSON file synchronously.
264
270
 
265
271
  ## Common Utilities
266
272
  Common utilities used throughout the framework. Currently [Util](https://github.com/travetto/travetto/tree/main/module/runtime/src/util.ts#L12) includes:
@@ -323,7 +329,7 @@ export class TimeUtil {
323
329
  ```
324
330
 
325
331
  ## Process Execution
326
- [ExecUtil](https://github.com/travetto/travetto/tree/main/module/runtime/src/exec.ts#L43) exposes `getResult` as a means to wrap [child_process](https://nodejs.org/api/child_process.html)'s process object. This wrapper allows for a promise-based resolution of the subprocess with the ability to capture the stderr/stdout.
332
+ [ExecUtil](https://github.com/travetto/travetto/tree/main/module/runtime/src/exec.ts#L40) exposes `getResult` as a means to wrap [child_process](https://nodejs.org/api/child_process.html)'s process object. This wrapper allows for a promise-based resolution of the subprocess with the ability to capture the stderr/stdout.
327
333
 
328
334
  A simple example would be:
329
335
 
package/__index__.ts CHANGED
@@ -9,6 +9,7 @@ export * from './src/exec.ts';
9
9
  export * from './src/env.ts';
10
10
  export * from './src/file-loader.ts';
11
11
  export * from './src/function.ts';
12
+ export * from './src/json.ts';
12
13
  export * from './src/manifest-index.ts';
13
14
  export * from './src/queue.ts';
14
15
  export * from './src/resources.ts';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/runtime",
3
- "version": "7.0.0-rc.2",
3
+ "version": "7.0.0-rc.4",
4
4
  "description": "Runtime for travetto applications.",
5
5
  "keywords": [
6
6
  "console-manager",
@@ -25,12 +25,12 @@
25
25
  "directory": "module/runtime"
26
26
  },
27
27
  "dependencies": {
28
- "@travetto/manifest": "^7.0.0-rc.1",
28
+ "@travetto/manifest": "^7.0.0-rc.2",
29
29
  "@types/debug": "^4.1.12",
30
30
  "debug": "^4.4.3"
31
31
  },
32
32
  "peerDependencies": {
33
- "@travetto/transformer": "^7.0.0-rc.2"
33
+ "@travetto/transformer": "^7.0.0-rc.3"
34
34
  },
35
35
  "peerDependenciesMeta": {
36
36
  "@travetto/transformer": {
package/src/context.ts CHANGED
@@ -7,6 +7,7 @@ import { Env } from './env.ts';
7
7
  import { RuntimeIndex } from './manifest-index.ts';
8
8
  import { describeFunction } from './function.ts';
9
9
  import { castTo } from './types.ts';
10
+ import { JSONUtil } from './json.ts';
10
11
 
11
12
  /** Constrained version of {@type ManifestContext} */
12
13
  class $Runtime {
@@ -31,14 +32,18 @@ class $Runtime {
31
32
  return Env.TRV_ENV.value || (!this.production ? this.#idx.manifest.workspace.defaultEnv : undefined);
32
33
  }
33
34
 
34
- /** Are we in development mode */
35
+ /** Are we in production mode */
35
36
  get production(): boolean {
36
37
  return process.env.NODE_ENV === 'production';
37
38
  }
38
39
 
39
- /** Is the app in dynamic mode? */
40
- get dynamic(): boolean {
41
- return Env.TRV_DYNAMIC.isTrue;
40
+ /** Get environment type mode */
41
+ get envType(): 'production' | 'development' | 'test' {
42
+ switch (process.env.NODE_ENV) {
43
+ case 'production': return 'production';
44
+ case 'test': return 'test';
45
+ default: return 'development';
46
+ }
42
47
  }
43
48
 
44
49
  /** Get debug value */
@@ -118,7 +123,7 @@ class $Runtime {
118
123
  throw new Error(`Unable to find ${imp}, not in the manifest`);
119
124
  } else if (imp.endsWith('.json')) {
120
125
  imp = this.#idx.getFromImport(imp)?.sourceFile ?? imp;
121
- return fs.readFile(imp, 'utf8').then(JSON.parse);
126
+ return fs.readFile(imp, 'utf8').then(JSONUtil.parseSafe<T>);
122
127
  }
123
128
 
124
129
  if (!ManifestModuleUtil.SOURCE_EXT_REGEX.test(imp)) {
package/src/exec.ts CHANGED
@@ -1,14 +1,23 @@
1
- import { ChildProcess } from 'node:child_process';
2
- import { Readable } from 'node:stream';
1
+ import { type ChildProcess, spawn, type SpawnOptions } from 'node:child_process';
2
+ import type { Readable } from 'node:stream';
3
3
  import { createInterface } from 'node:readline/promises';
4
4
 
5
- import { castTo } from './types.ts';
6
-
7
- const MINUTE = (1000 * 60);
5
+ import { castTo, type Any } from './types.ts';
8
6
 
9
7
  const ResultSymbol = Symbol();
10
8
 
11
- interface ExecutionBaseResult {
9
+ /**
10
+ * Result of an execution
11
+ */
12
+ export interface ExecutionResult<T extends string | Buffer = string | Buffer> {
13
+ /**
14
+ * Stdout
15
+ */
16
+ stdout: T;
17
+ /**
18
+ * Stderr
19
+ */
20
+ stderr: T;
12
21
  /**
13
22
  * Exit code
14
23
  */
@@ -23,77 +32,19 @@ interface ExecutionBaseResult {
23
32
  valid: boolean;
24
33
  }
25
34
 
26
- /**
27
- * Result of an execution
28
- */
29
- export interface ExecutionResult<T extends string | Buffer = string | Buffer> extends ExecutionBaseResult {
30
- /**
31
- * Stdout
32
- */
33
- stdout: T;
34
- /**
35
- * Stderr
36
- */
37
- stderr: T;
38
- }
35
+ type ExecutionBaseResult = Omit<ExecutionResult, 'stdout' | 'stderr'>;
39
36
 
40
37
  /**
41
38
  * Standard utilities for managing executions
42
39
  */
43
40
  export class ExecUtil {
44
41
 
45
- static RESTART_EXIT_CODE = 200;
46
-
47
42
  /**
48
- * Run with automatic restart support
49
- * @param run The factory to produce the next running process
50
- * @param maxRetriesPerMinute The number of times to allow a retry within a minute
43
+ * Spawn wrapper that ensures performant invocation of trv commands
51
44
  */
52
- static async withRestart(
53
- run: () => ChildProcess,
54
- config?: { maxRetriesPerMinute?: number, relayInterrupt?: boolean }
55
- ): Promise<ExecutionResult> {
56
- const maxRetries = config?.maxRetriesPerMinute ?? 5;
57
- const relayInterrupt = config?.relayInterrupt ?? false;
58
-
59
- const restarts: number[] = [];
60
-
61
- if (!relayInterrupt) {
62
- process.removeAllListeners('SIGINT'); // Remove any existing listeners
63
- process.on('SIGINT', () => { }); // Prevents SIGINT from killing parent process, the child will handle
64
- }
65
-
66
- for (; ;) {
67
- const subProcess = run();
68
- const interrupt = (): void => { subProcess.kill('SIGINT'); };
69
- const toMessage = (value: unknown): void => { subProcess.send?.(value!); };
70
-
71
- // Proxy kill requests
72
- process.on('message', toMessage);
73
- if (relayInterrupt) {
74
- process.on('SIGINT', interrupt);
75
- }
76
- subProcess.on('message', value => process.send?.(value));
77
-
78
- const result = await this.getResult(subProcess, { catch: true });
79
- if (result.code !== this.RESTART_EXIT_CODE) {
80
- return result;
81
- } else {
82
- if (relayInterrupt) {
83
- process.off('SIGINT', interrupt);
84
- }
85
- process.off('message', toMessage);
86
- restarts.unshift(Date.now());
87
- if (restarts.length === maxRetries) {
88
- if ((restarts[0] - restarts[maxRetries - 1]) <= MINUTE) {
89
- console.error(`Bailing, due to ${maxRetries} restarts in under a minute`);
90
- return result;
91
- }
92
- restarts.pop(); // Keep list short
93
- }
94
- console.error('Restarting...', { pid: process.pid });
95
- }
96
- }
45
+ static spawnTrv(cmd: string, args: string[], options: SpawnOptions): ChildProcess {
46
+ const entry = (globalThis as Any).__entry_point__ ?? process.argv.at(1);
47
+ return spawn(process.argv0, [entry, cmd, ...args], options);
97
48
  }
98
49
 
99
50
  /**
@@ -4,6 +4,7 @@ import fs from 'node:fs/promises';
4
4
  import path from 'node:path';
5
5
 
6
6
  import { AppError } from './error.ts';
7
+ import { JSONUtil } from './json.ts';
7
8
 
8
9
  /**
9
10
  * File loader that will search for files across the provided search paths
@@ -64,4 +65,12 @@ export class FileLoader {
64
65
  async readFile(relativePath: string): Promise<File> {
65
66
  return new File([await this.read(relativePath, true)], path.basename(relativePath));
66
67
  }
68
+
69
+ /**
70
+ * Read relative file as JSON
71
+ */
72
+ async readJSON<T>(relativePath: string): Promise<T> {
73
+ const location = await this.resolve(relativePath);
74
+ return JSONUtil.readFile<T>(location);
75
+ }
67
76
  }
package/src/json.ts ADDED
@@ -0,0 +1,74 @@
1
+ import fs from 'node:fs/promises';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+
4
+ import type { Any } from './types';
5
+
6
+ /**
7
+ * JSON Utility functions
8
+ */
9
+ export class JSONUtil {
10
+
11
+ /**
12
+ * Parse JSON safely
13
+ */
14
+ static parseSafe<T>(input: string | Buffer, reviver?: (this: unknown, key: string, value: Any) => unknown): T {
15
+ if (typeof input !== 'string') {
16
+ input = input.toString('utf8');
17
+ }
18
+ // TODO: Ensure we aren't vulnerable to prototype pollution
19
+ return JSON.parse(input, reviver);
20
+ }
21
+
22
+ /**
23
+ * Encode JSON value as base64 encoded string
24
+ */
25
+ static stringifyBase64<T>(value: T | undefined): string | undefined {
26
+ if (value === undefined) {
27
+ return;
28
+ }
29
+ const text = JSON.stringify(value);
30
+ return Buffer.from(text, 'utf8').toString('base64');
31
+ }
32
+
33
+ /**
34
+ * Decode JSON value from base64 encoded string
35
+ */
36
+ static parseBase64<T>(input: string): T;
37
+ static parseBase64<T>(input?: string | undefined): T | undefined;
38
+ static parseBase64<T>(input?: string | undefined): T | undefined {
39
+ if (!input) {
40
+ return undefined;
41
+ }
42
+
43
+ let decoded = Buffer.from(input, 'base64').toString('utf8');
44
+
45
+ // Read from encoded if it happens
46
+ if (decoded.startsWith('%')) {
47
+ decoded = decodeURIComponent(decoded);
48
+ }
49
+
50
+ return this.parseSafe(decoded);
51
+ }
52
+
53
+ /**
54
+ * Read JSON file asynchronously
55
+ * @param file
56
+ * @returns
57
+ */
58
+ static async readFile<T>(file: string): Promise<T> {
59
+ const content = await fs.readFile(file, 'utf8');
60
+ return this.parseSafe(content);
61
+ }
62
+
63
+ /**
64
+ * Read JSON file synchronously
65
+ * @param file
66
+ */
67
+ static readFileSync<T>(file: string, onMissing?: T): T {
68
+ if (!existsSync(file) && onMissing !== undefined) {
69
+ return onMissing;
70
+ }
71
+ const content = readFileSync(file, 'utf8');
72
+ return this.parseSafe(content);
73
+ }
74
+ }
package/src/trv.d.ts CHANGED
@@ -21,10 +21,6 @@ declare module "@travetto/runtime" {
21
21
  * Special role to run as, used to access additional files from the manifest during runtime.
22
22
  */
23
23
  TRV_ROLE: Role;
24
- /**
25
- * Whether or not to run the program in dynamic mode, allowing for real-time updates
26
- */
27
- TRV_DYNAMIC: boolean;
28
24
  /**
29
25
  * The folders to use for resource lookup
30
26
  */
package/src/types.ts CHANGED
@@ -37,6 +37,7 @@ export const TypedObject: {
37
37
  keys<T = unknown, K extends keyof T = keyof T & string>(value: T): K[];
38
38
  fromEntries<K extends string | symbol, V>(items: ([K, V] | readonly [K, V])[]): Record<K, V>;
39
39
  entries<K extends Record<symbol | string, unknown>>(record: K): [keyof K, K[keyof K]][];
40
+ assign<T extends {}, U extends T>(target: T, ...sources: U[]): U;
40
41
  } & ObjectConstructor = Object;
41
42
 
42
43
  export const safeAssign = <T extends {}, U extends {}>(target: T, ...sources: U[]): T & U =>
@@ -47,6 +48,7 @@ export function castTo<T>(input: unknown): T {
47
48
  return input as T;
48
49
  }
49
50
 
51
+ export const isClass = (input: unknown): input is Class => typeof input === 'function' && 'Ⲑid' in input;
50
52
  export const castKey = <T>(input: string | number | symbol): keyof T => castTo(input);
51
53
  export const asFull = <T>(input: Partial<T>): T => castTo(input);
52
54
  export const asConstructable = <Z = unknown>(input: Class | unknown): { constructor: Class<Z> } => castTo(input);
@@ -61,6 +63,11 @@ export const hasFunction = <T>(key: keyof T) => (value: unknown): value is T =>
61
63
 
62
64
  export const hasToJSON = hasFunction<{ toJSON(): object }>('toJSON');
63
65
 
66
+ /**
67
+ * A type representing unknown type
68
+ */
69
+ export class UnknownType { }
70
+
64
71
  export function toConcrete<T extends unknown>(): Class<T> {
65
72
  return arguments[0];
66
73
  }
package/src/util.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import crypto from 'node:crypto';
2
2
  import timers from 'node:timers/promises';
3
3
 
4
- import { castTo, hasToJSON } from './types.ts';
4
+ import { castTo } from './types.ts';
5
5
  import { AppError } from './error.ts';
6
6
 
7
7
  type MapFn<T, U> = (value: T, i: number) => U | Promise<U>;
@@ -120,82 +120,6 @@ export class Util {
120
120
  }
121
121
  }
122
122
 
123
- /**
124
- * Encode JSON value as base64 encoded string
125
- */
126
- static encodeSafeJSON<T>(value: T | undefined): string | undefined {
127
- if (value === undefined) {
128
- return;
129
- }
130
- const text = JSON.stringify(value);
131
- return Buffer.from(text, 'utf8').toString('base64');
132
- }
133
-
134
- /**
135
- * Decode JSON value from base64 encoded string
136
- */
137
- static decodeSafeJSON<T>(input: string): T;
138
- static decodeSafeJSON<T>(input?: string | undefined): T | undefined;
139
- static decodeSafeJSON<T>(input?: string | undefined): T | undefined {
140
- if (!input) {
141
- return undefined;
142
- }
143
-
144
- let decoded = Buffer.from(input, 'base64').toString('utf8');
145
-
146
- // Read from encoded if it happens
147
- if (decoded.startsWith('%')) {
148
- decoded = decodeURIComponent(decoded);
149
- }
150
-
151
- return JSON.parse(decoded, undefined);
152
- }
153
-
154
- /**
155
- * Serialize to JSON
156
- */
157
- static serializeToJSON<T>(out: T): string {
158
- return JSON.stringify(out, function (key, value) {
159
- const objectValue = this[key];
160
- if (objectValue && objectValue instanceof Error) {
161
- return {
162
- $: true,
163
- ...hasToJSON(objectValue) ? objectValue.toJSON() : objectValue,
164
- name: objectValue.name,
165
- message: objectValue.message,
166
- stack: objectValue.stack,
167
- };
168
- } else if (typeof value === 'bigint') {
169
- return `${value.toString()}$n`;
170
- } else {
171
- return value;
172
- }
173
- });
174
- }
175
-
176
- /**
177
- * Deserialize from JSON
178
- */
179
- static deserializeFromJson<T = unknown>(input: string): T {
180
- return JSON.parse(input, function (key, value) {
181
- if (value && typeof value === 'object' && '$' in value) {
182
- const error = AppError.fromJSON(value) ?? new Error();
183
- if (!(error instanceof AppError)) {
184
- const { $: _, ...rest } = value;
185
- Object.assign(error, rest);
186
- }
187
- error.message = value.message;
188
- error.stack = value.stack;
189
- error.name = value.name;
190
- return error;
191
- } else if (typeof value === 'string' && /^\d+[$]n$/.test(value)) {
192
- return BigInt(value.split('$')[0]);
193
- } else {
194
- return value;
195
- }
196
- });
197
- }
198
-
199
123
  /**
200
124
  * Retry an operation, with a custom conflict handler
201
125
  * @param operation The operation to retry
package/src/watch.ts CHANGED
@@ -1,11 +1,23 @@
1
+ import { ManifestModuleUtil, type ChangeEventType, type ManifestModuleFileType } from '@travetto/manifest';
2
+
1
3
  import { RuntimeIndex } from './manifest-index.ts';
2
- import { ExecUtil } from './exec.ts';
3
4
  import { ShutdownManager } from './shutdown.ts';
4
5
  import { Util } from './util.ts';
5
6
 
6
- export type WatchEvent = { file: string, action: 'create' | 'update' | 'delete', output: string, module: string, time: number };
7
+ type WatchEvent = { file: string, action: ChangeEventType, output: string, module: string, import: string, time: number };
8
+
9
+ type WatchCompilerOptions = {
10
+ /**
11
+ * Restart the watch loop on compiler exit
12
+ */
13
+ restartOnCompilerExit?: boolean;
14
+ /**
15
+ * Run on restart
16
+ */
17
+ onRestart?: () => void;
18
+ };
7
19
 
8
- export async function* watchCompiler(config?: { restartOnExit?: boolean, signal?: AbortSignal }): AsyncIterable<WatchEvent> {
20
+ export async function* watchCompiler(config?: WatchCompilerOptions): AsyncIterable<WatchEvent> {
9
21
  // Load at runtime
10
22
  const { CompilerClient } = await import('@travetto/compiler/support/server/client.ts');
11
23
 
@@ -19,21 +31,56 @@ export async function* watchCompiler(config?: { restartOnExit?: boolean, signal?
19
31
  const controller = new AbortController();
20
32
  const remove = ShutdownManager.onGracefulShutdown(async () => controller.abort());
21
33
 
22
- await client.waitForState(['compile-end', 'watch-start'], undefined, controller.signal);
34
+ const maxIterations = 10;
35
+ const maxWindow = 10 * 1000;
36
+ const iterations: number[] = [];
37
+ let iterationsExhausted = false;
38
+
39
+ while (
40
+ !controller.signal.aborted &&
41
+ !iterationsExhausted &&
42
+ (config?.restartOnCompilerExit || iterations.length === 0)
43
+ ) {
44
+ if (iterations.length) { // Wait on next iteration
45
+ await Util.nonBlockingTimeout(10);
46
+ }
47
+
48
+ await client.waitForState(['compile-end', 'watch-start'], undefined, controller.signal);
23
49
 
24
- if (!await client.isWatching()) { // If we get here, without a watch
25
- while (!await client.isWatching()) { // Wait until watch starts
26
- await Util.nonBlockingTimeout(1000 * 60);
50
+ if (!await client.isWatching()) { // If we get here, without a watch
51
+ await Util.nonBlockingTimeout(maxWindow / (maxIterations * 2));
52
+ } else {
53
+ if (iterations.length) {
54
+ config?.onRestart?.();
55
+ }
56
+ yield* client.fetchEvents('change', { signal: controller.signal, enforceIteration: true });
57
+ }
58
+
59
+ iterations.push(Date.now());
60
+ if (iterations.length >= maxIterations) {
61
+ iterationsExhausted = (Date.now() - iterations[0]) > maxWindow;
62
+ iterations.shift();
27
63
  }
28
- } else {
29
- yield* client.fetchEvents('change', { signal: controller.signal, enforceIteration: true });
30
64
  }
31
65
 
32
66
  remove();
67
+ }
68
+
69
+ export function listenForSourceChanges(onChange: () => void, debounceDelay = 10): void {
70
+ let timeout: ReturnType<typeof setTimeout> | undefined;
71
+
72
+ const validFileTypes = new Set<ManifestModuleFileType>(['ts', 'js', 'package-json', 'typings']);
33
73
 
34
- if (config?.restartOnExit) {
35
- // We are done, request restart
36
- await ShutdownManager.gracefulShutdown('@travetto/runtime:watchCompiler');
37
- process.exit(ExecUtil.RESTART_EXIT_CODE);
74
+ function send(): void {
75
+ clearTimeout(timeout);
76
+ timeout = setTimeout(onChange, debounceDelay);
38
77
  }
78
+
79
+ (async function (): Promise<void> {
80
+ for await (const item of watchCompiler({ restartOnCompilerExit: true, onRestart: send })) {
81
+ if (validFileTypes.has(ManifestModuleUtil.getFileType(item.file)) && RuntimeIndex.findModuleForArbitraryFile(item.file)) {
82
+ send();
83
+ }
84
+ }
85
+ })();
39
86
  }
@@ -1,4 +1,4 @@
1
- import * as ts from 'typescript';
1
+ import ts from 'typescript';
2
2
 
3
3
  import { FunctionMetadataTag } from '@travetto/runtime';
4
4
  import { CoreUtil, Import, SystemUtil, TransformerState } from '@travetto/transformer';